Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
808,545 changes: 0 additions & 808,545 deletions dist-nosplit/cli.js

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed dist-nosplit/vendor/ripgrep/arm64-linux/rg
Binary file not shown.
4 changes: 2 additions & 2 deletions src/buddy/companionReact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function isAddressed(messages: Message[], name: string): boolean {
) {
const m = messages[i]
if (m?.type !== 'user') continue
const content = (m as any).message?.content
const content = m.message?.content
if (typeof content === 'string' && pattern.test(content)) return true
}
return false
Expand All @@ -89,7 +89,7 @@ function buildTranscript(messages: Message[]): string {
.filter(m => m.type === 'user' || m.type === 'assistant')
.map(m => {
const role = m.type === 'user' ? 'user' : 'claude'
const content = (m as any).message?.content
const content = m.message?.content
const text =
typeof content === 'string'
? content.slice(0, 300)
Expand Down
9 changes: 3 additions & 6 deletions src/cli/transports/ccrClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ export class CCRClient {
if (!result.ok) {
throw new RetryableError(
'client event POST failed',
(result as any).retryAfterMs,
result.retryAfterMs,
)
}
},
Expand All @@ -404,7 +404,7 @@ export class CCRClient {
if (!result.ok) {
throw new RetryableError(
'internal event POST failed',
(result as any).retryAfterMs,
result.retryAfterMs,
)
}
},
Expand Down Expand Up @@ -433,10 +433,7 @@ export class CCRClient {
'delivery batch',
)
if (!result.ok) {
throw new RetryableError(
'delivery POST failed',
(result as any).retryAfterMs,
)
throw new RetryableError('delivery POST failed', result.retryAfterMs)
}
},
baseDelayMs: 500,
Expand Down
12 changes: 7 additions & 5 deletions src/components/ConsoleOAuthFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ export function ConsoleOAuthFlow({
throw new Error((orgResult as { valid: false; message: string }).message);
}
// Reset modelType to anthropic when using OAuth login
updateSettingsForSource('userSettings', { modelType: 'anthropic' } as any);
updateSettingsForSource('userSettings', { modelType: 'anthropic' } as unknown as Parameters<
typeof updateSettingsForSource
>[1]);

setOAuthStatus({ state: 'success' });
void sendNotification(
Expand Down Expand Up @@ -662,9 +664,9 @@ function OAuthStatusMessage({
if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model;
const { error } = updateSettingsForSource('userSettings', {
modelType: 'anthropic' as any,
modelType: 'anthropic',
env,
} as any);
} as unknown as Parameters<typeof updateSettingsForSource>[1]);
if (error) {
setOAuthStatus({
state: 'error',
Expand Down Expand Up @@ -1153,9 +1155,9 @@ function OAuthStatusMessage({
if (finalVals.sonnet_model) env.GEMINI_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
if (finalVals.opus_model) env.GEMINI_DEFAULT_OPUS_MODEL = finalVals.opus_model;
const { error } = updateSettingsForSource('userSettings', {
modelType: 'gemini' as any,
modelType: 'gemini',
env,
} as any);
} as unknown as Parameters<typeof updateSettingsForSource>[1]);
if (error) {
setOAuthStatus({
state: 'error',
Expand Down
8 changes: 6 additions & 2 deletions src/components/FeedbackSurvey/useFrustrationDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export type FrustrationDetectionResult = {
}

function detectFrustration(messages: Message[]): boolean {
const apiErrors = messages.filter(m => (m as any).isApiErrorMessage)
const apiErrors = messages.filter(
m => 'isApiErrorMessage' in m && m.isApiErrorMessage === true,
)
return apiErrors.length >= 2
}

Expand All @@ -25,7 +27,9 @@ export function useFrustrationDetection(
const [state, setState] = useState<FrustrationState>('closed')

const config = getGlobalConfig() as { transcriptShareDismissed?: boolean }
const policyAllowed = isPolicyAllowed('product_feedback' as any)
const policyAllowed = isPolicyAllowed(
'product_feedback' as Parameters<typeof isPolicyAllowed>[0],
)
const shouldSkip =
config.transcriptShareDismissed ||
!policyAllowed ||
Expand Down
2 changes: 1 addition & 1 deletion src/components/PromptInput/PromptInputFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ function PipeStatusInline(): React.ReactNode {
if (!feature('UDS_INBOX')) return null;
// All hooks must be called before any conditional return to maintain
// consistent hook count across renders (React rules of hooks).
const pipeIpc = useAppState(s => (s as any).pipeIpc);
const pipeIpc = useAppState(s => s.pipeIpc);
const setAppState = useSetAppState();
const [cursorIndex, setCursorIndex] = useState(0);

Expand Down
55 changes: 55 additions & 0 deletions src/components/PromptInput/PromptInputFooterLeftSide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const NULL = () => null;
const MAX_VOICE_HINT_SHOWS = 3;

const RSS_UPDATE_INTERVAL_MS = 5_000;
const GOAL_TICK_INTERVAL_MS = 1_000;

type RssState = { text: string; level: 'normal' | 'warning' | 'error' };

Expand Down Expand Up @@ -127,6 +128,55 @@ function ProactiveCountdown(): React.ReactNode {
return <Text dimColor>waiting {formatDuration(remainingSeconds * 1000, { mostSignificantOnly: true })}</Text>;
}

/** Compact "goal (1h22min)" pill for the footer — colored by status. */
function GoalElapsedIndicator(): React.ReactNode {
const [tick, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setTick(t => t + 1), GOAL_TICK_INTERVAL_MS);
return () => clearInterval(id);
}, []);
void tick;

const goalModule = require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState');
const goal = goalModule.getGoal();
if (!goal) return null;

const elapsedMs = goalModule.getActiveElapsedMs(goal);
const totalSeconds = Math.floor(elapsedMs / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;

let timeStr: string;
if (hours >= 1) {
timeStr = `${hours}h${minutes}min`;
} else if (minutes >= 1) {
timeStr = `${minutes}min`;
} else {
timeStr = `${seconds}s`;
}

let color: string | undefined;
switch (goal.status) {
case 'active':
color = 'ansi:green';
break;
case 'paused':
case 'budget_limited':
case 'usage_limited':
color = 'ansi:yellow';
break;
case 'blocked':
color = 'ansi:red';
break;
case 'complete':
color = 'ansi:cyan';
break;
}

return <Text color={color as 'ansi:green'}>goal ({timeStr})</Text>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use as keyof Theme instead of as 'ansi:green' for Ink color prop.

The color variable can be 'ansi:green' | 'ansi:yellow' | 'ansi:red' | 'ansi:cyan' | undefined, but the type assertion forces it to 'ansi:green'. According to coding guidelines for Ink color prop, use as keyof Theme. As per coding guidelines, "Ink color prop must use as keyof Theme rather than as any".

♻️ Fix type assertion
-  return <Text color={color as 'ansi:green'}>goal ({timeStr})</Text>;
+  return <Text color={color as keyof Theme}>goal ({timeStr})</Text>;

If Theme is not imported, add it to the imports from @anthropic/ink:

-import { Box, Text, Link } from '`@anthropic/ink`';
+import { Box, Text, Link, type Theme } from '`@anthropic/ink`';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return <Text color={color as 'ansi:green'}>goal ({timeStr})</Text>;
return <Text color={color as keyof Theme}>goal ({timeStr})</Text>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/PromptInput/PromptInputFooterLeftSide.tsx` at line 178, The
color prop is incorrectly asserted to the literal 'ansi:green'; update the
return in PromptInputFooterLeftSide to assert color as keyof Theme (i.e., use
color as keyof Theme) instead of 'ansi:green', and if Theme is not already
imported add Theme to the imports from '`@anthropic/ink`'; ensure the Text
component uses color={color as keyof Theme} so the union type ('ansi:green' |
'ansi:yellow' | 'ansi:red' | 'ansi:cyan' | undefined) is correctly typed.

Source: Coding guidelines

}

export function PromptInputFooterLeftSide({
exitMessage,
vimMode,
Expand Down Expand Up @@ -376,6 +426,11 @@ function ModeIndicator({
</Text>,
]
: []),
// Goal elapsed indicator — compact "goal (XhYmin)" after PID
...(feature('GOAL') &&
(require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState')).getGoal()
? [<GoalElapsedIndicator key="goal-elapsed" />]
: []),
];

// Check if any in-process teammates exist (for hint text cycling)
Expand Down
8 changes: 4 additions & 4 deletions src/components/ultraplan/UltraplanChoiceDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ export function UltraplanChoiceDialog({
if (!isScrollable) return;
const halfPage = Math.max(1, Math.floor(visibleHeight / 2));

if ((key.ctrl && input === 'd') || (key as any).wheelDown) {
const step = (key as any).wheelDown ? 3 : halfPage;
if ((key.ctrl && input === 'd') || key.wheelDown) {
const step = key.wheelDown ? 3 : halfPage;
setScrollOffset(prev => Math.min(prev + step, maxOffset));
} else if ((key.ctrl && input === 'u') || (key as any).wheelUp) {
const step = (key as any).wheelUp ? 3 : halfPage;
} else if ((key.ctrl && input === 'u') || key.wheelUp) {
const step = key.wheelUp ? 3 : halfPage;
setScrollOffset(prev => Math.max(prev - step, 0));
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/entrypoints/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export async function startMCPServer(
)
if (validationResult && !validationResult.result) {
throw new Error(
`Tool ${name} input is invalid: ${(validationResult as any).message}`,
`Tool ${name} input is invalid: ${'message' in validationResult ? validationResult.message : String(validationResult)}`,
)
}
const finalResult = await tool.call(
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useBackgroundTaskNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function useBackgroundTaskNavigation(options?: {
const viewSelectionMode = useAppState(s => s.viewSelectionMode)
const viewingAgentTaskId = useAppState(s => s.viewingAgentTaskId)
const selectedIPAgentIndex = useAppState(s => s.selectedIPAgentIndex)
const pipeIpc = useAppState(s => (s as any).pipeIpc)
const pipeIpc = useAppState(s => s.pipeIpc)
const setAppState = useSetAppState()

// Filter to running teammates and sort alphabetically to match TeammateSpinnerTree display
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/usePipeRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function usePipeRouter({ store, setAppState, addNotification }: Deps): {
if (!input.trim() || input.trim().startsWith('/')) return false

/* eslint-disable @typescript-eslint/no-require-imports */
const pipeState = (store.getState() as any).pipeIpc
const pipeState = store.getState().pipeIpc
const selectedPipes: string[] = pipeState?.selectedPipes ?? []
const routeMode: 'selected' | 'local' = pipeState?.routeMode ?? 'selected'

Expand Down
2 changes: 1 addition & 1 deletion src/screens/REPL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4966,7 +4966,7 @@ export function REPL({
useMailboxBridge({ isLoading, onSubmitMessage: handleIncomingPrompt });
useMasterMonitor();
useSlaveNotifications();
const _pipeIpcState = useAppState(s => getPipeIpc(s as any));
const _pipeIpcState = useAppState(s => getPipeIpc(s));

usePipePermissionForward({ store, tools, setMessages, setToolUseConfirmQueue, getToolUseContext, mainLoopModel });
usePipeMuteSync({ setToolUseConfirmQueue });
Expand Down
37 changes: 23 additions & 14 deletions src/services/api/gemini/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type {
BetaToolUnion,
BetaMessage,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import { randomUUID } from 'crypto'
import type {
AssistantMessage,
Expand Down Expand Up @@ -112,21 +115,21 @@ export async function* queryModelGemini(
)

const adaptedStream = adaptGeminiStreamToAnthropic(stream, geminiModel)
const contentBlocks: Record<number, any> = {}
const contentBlocks: Record<number, Record<string, unknown>> = {}
const collectedMessages: AssistantMessage[] = []
let partialMessage: any
let partialMessage: BetaMessage | null = null
let ttftMs = 0
const start = Date.now()

for await (const event of adaptedStream) {
switch (event.type) {
case 'message_start':
partialMessage = (event as any).message
partialMessage = event.message
ttftMs = Date.now() - start
break
case 'content_block_start': {
const idx = (event as any).index
const cb = (event as any).content_block
const idx = event.index
const cb = event.content_block
if (cb.type === 'tool_use') {
contentBlocks[idx] = { ...cb, input: '' }
} else if (cb.type === 'text') {
Expand All @@ -139,17 +142,19 @@ export async function* queryModelGemini(
break
}
case 'content_block_delta': {
const idx = (event as any).index
const delta = (event as any).delta
const idx = event.index
const delta = event.delta
const block = contentBlocks[idx]
if (!block) break

if (delta.type === 'text_delta') {
block.text = (block.text || '') + delta.text
block.text = ((block.text as string | undefined) || '') + delta.text
} else if (delta.type === 'input_json_delta') {
block.input = (block.input || '') + delta.partial_json
block.input =
((block.input as string | undefined) || '') + delta.partial_json
} else if (delta.type === 'thinking_delta') {
block.thinking = (block.thinking || '') + delta.thinking
block.thinking =
((block.thinking as string | undefined) || '') + delta.thinking
} else if (delta.type === 'signature_delta') {
if (block.type === 'thinking') {
block.signature = delta.signature
Expand All @@ -160,15 +165,19 @@ export async function* queryModelGemini(
break
}
case 'content_block_stop': {
const idx = (event as any).index
const idx = event.index
const block = contentBlocks[idx]
if (!block || !partialMessage) break

const message: AssistantMessage = {
message: {
...partialMessage,
content: normalizeContentFromAPI([block], tools, options.agentId),
},
content: normalizeContentFromAPI(
[block] as unknown as BetaMessage['content'],
tools,
options.agentId,
),
} as AssistantMessage['message'],
requestId: undefined,
type: 'assistant',
uuid: randomUUID(),
Expand Down
Loading
Loading