Fix repo typecheck failures
This commit is contained in:
@@ -856,7 +856,7 @@ const stylesheet = StyleSheet.create((theme) => ({
|
|||||||
workingIndicatorText: {
|
workingIndicatorText: {
|
||||||
color: theme.colors.mutedForeground,
|
color: theme.colors.mutedForeground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.semibold,
|
||||||
},
|
},
|
||||||
workingDotsRow: {
|
workingDotsRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { hydrateStreamState, type StreamItem, type ToolCallItem } from "./stream";
|
import {
|
||||||
|
hydrateStreamState,
|
||||||
|
type StreamItem,
|
||||||
|
type AgentToolCallItem,
|
||||||
|
type ToolCallStatus,
|
||||||
|
isAgentToolCallItem,
|
||||||
|
} from "./stream";
|
||||||
import type { AgentStreamEventPayload } from "@server/server/messages";
|
import type { AgentStreamEventPayload } from "@server/server/messages";
|
||||||
|
|
||||||
type HarnessUpdate = { event: AgentStreamEventPayload; timestamp: Date };
|
type HarnessUpdate = { event: AgentStreamEventPayload; timestamp: Date };
|
||||||
@@ -200,14 +206,14 @@ function buildToolStartEvent({
|
|||||||
tool,
|
tool,
|
||||||
input,
|
input,
|
||||||
kind,
|
kind,
|
||||||
status = "pending",
|
status = "executing",
|
||||||
}: {
|
}: {
|
||||||
callId: string;
|
callId: string;
|
||||||
server: string;
|
server: string;
|
||||||
tool: string;
|
tool: string;
|
||||||
input?: Record<string, unknown>;
|
input?: Record<string, unknown>;
|
||||||
kind?: string;
|
kind?: string;
|
||||||
status?: string;
|
status?: ToolCallStatus;
|
||||||
}): AgentStreamEventPayload {
|
}): AgentStreamEventPayload {
|
||||||
return {
|
return {
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
@@ -260,14 +266,15 @@ function buildToolResultEvent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractHarnessSnapshots(state: StreamItem[]): Record<keyof typeof HARNESS_CALL_IDS, ToolCallItem | undefined> {
|
function extractHarnessSnapshots(
|
||||||
const lookup = Object.values(HARNESS_CALL_IDS).reduce<Record<string, ToolCallItem | undefined>>(
|
state: StreamItem[]
|
||||||
(acc, id) => {
|
): Record<keyof typeof HARNESS_CALL_IDS, AgentToolCallItem | undefined> {
|
||||||
|
const lookup = Object.values(HARNESS_CALL_IDS).reduce<
|
||||||
|
Record<string, AgentToolCallItem | undefined>
|
||||||
|
>((acc, id) => {
|
||||||
acc[id] = findToolByCallId(state, id);
|
acc[id] = findToolByCallId(state, id);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
}, {});
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
command: lookup[HARNESS_CALL_IDS.command],
|
command: lookup[HARNESS_CALL_IDS.command],
|
||||||
@@ -276,11 +283,9 @@ function extractHarnessSnapshots(state: StreamItem[]): Record<keyof typeof HARNE
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function findToolByCallId(state: StreamItem[], callId: string): ToolCallItem | undefined {
|
function findToolByCallId(state: StreamItem[], callId: string): AgentToolCallItem | undefined {
|
||||||
return state.find(
|
return state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === "tool_call" &&
|
isAgentToolCallItem(item) && item.payload.data.callId === callId
|
||||||
item.payload.source === "agent" &&
|
|
||||||
item.payload.data.callId === callId
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,21 @@ import {
|
|||||||
type StreamItem,
|
type StreamItem,
|
||||||
type ToolCallItem,
|
type ToolCallItem,
|
||||||
type TodoListItem,
|
type TodoListItem,
|
||||||
|
type AgentToolCallItem,
|
||||||
|
isAgentToolCallItem,
|
||||||
} from "./stream";
|
} from "./stream";
|
||||||
|
|
||||||
type AgentStreamEventPayload = Parameters<typeof reduceStreamUpdate>[1];
|
type AgentStreamEventPayload = Parameters<typeof reduceStreamUpdate>[1];
|
||||||
type TestAgentProvider = "claude" | "codex";
|
type TestAgentProvider = "claude" | "codex";
|
||||||
|
|
||||||
|
function expectAgentToolCallItem(
|
||||||
|
item: ToolCallItem | undefined,
|
||||||
|
message = "Expected agent tool call"
|
||||||
|
): asserts item is AgentToolCallItem {
|
||||||
|
assert.ok(item, message);
|
||||||
|
assert.ok(isAgentToolCallItem(item), message);
|
||||||
|
}
|
||||||
|
|
||||||
function assistantTimeline(text: string, provider: TestAgentProvider = "claude"): AgentStreamEventPayload {
|
function assistantTimeline(text: string, provider: TestAgentProvider = "claude"): AgentStreamEventPayload {
|
||||||
return {
|
return {
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
@@ -157,7 +167,7 @@ function testUserMessageDeduplication() {
|
|||||||
|
|
||||||
const state = hydrateStreamState(updates);
|
const state = hydrateStreamState(updates);
|
||||||
|
|
||||||
const toolCalls = state.filter((item) => item.kind === "tool_call");
|
const toolCalls = state.filter(isAgentToolCallItem);
|
||||||
|
|
||||||
assert.strictEqual(toolCalls.length, 1, "Pending/completed tool entries should reconcile");
|
assert.strictEqual(toolCalls.length, 1, "Pending/completed tool entries should reconcile");
|
||||||
assert.strictEqual(toolCalls[0].payload.source, "agent");
|
assert.strictEqual(toolCalls[0].payload.source, "agent");
|
||||||
@@ -212,13 +222,12 @@ function testToolCallInputPreservation() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const state = hydrateStreamState(updates);
|
const state = hydrateStreamState(updates);
|
||||||
const toolCallEntry = state.find(
|
const toolCallEntry = state.find((item): item is AgentToolCallItem =>
|
||||||
(item): item is ToolCallItem =>
|
isAgentToolCallItem(item)
|
||||||
item.kind === "tool_call" && item.payload.source === "agent"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(toolCallEntry, "Tool call entry expected after hydration");
|
expectAgentToolCallItem(toolCallEntry, "Tool call entry expected after hydration");
|
||||||
const rawPayload = toolCallEntry!.payload.data.raw as unknown;
|
const rawPayload = toolCallEntry.payload.data.raw as unknown;
|
||||||
|
|
||||||
assert.ok(Array.isArray(rawPayload), "Raw payload should contain both input and result entries");
|
assert.ok(Array.isArray(rawPayload), "Raw payload should contain both input and result entries");
|
||||||
assert.strictEqual(rawPayload[0], toolInput);
|
assert.strictEqual(rawPayload[0], toolInput);
|
||||||
@@ -263,8 +272,8 @@ function testToolCallStatusInference() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const toolEntry = state.find(
|
const toolEntry = state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' && item.payload.source === 'agent' && item.payload.data.callId === toolCallId
|
isAgentToolCallItem(item) && item.payload.data.callId === toolCallId
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(toolEntry, "Tool entry should exist after hydration");
|
assert.ok(toolEntry, "Tool entry should exist after hydration");
|
||||||
@@ -296,10 +305,7 @@ function testToolCallStatusInferenceFromRawOnly() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const state = hydrateStreamState([{ event: rawEvent, timestamp }]);
|
const state = hydrateStreamState([{ event: rawEvent, timestamp }]);
|
||||||
const toolEntry = state.find(
|
const toolEntry = state.find(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem =>
|
|
||||||
item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(toolEntry?.payload.data.status, 'completed');
|
assert.strictEqual(toolEntry?.payload.data.status, 'completed');
|
||||||
}
|
}
|
||||||
@@ -328,12 +334,9 @@ function testToolCallFailureInferenceFromRaw() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const state = hydrateStreamState([{ event: rawEvent, timestamp }]);
|
const state = hydrateStreamState([{ event: rawEvent, timestamp }]);
|
||||||
const toolEntry = state.find(
|
const toolEntry = state.find(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem =>
|
expectAgentToolCallItem(toolEntry);
|
||||||
item.kind === 'tool_call' && item.payload.source === 'agent'
|
assert.strictEqual(toolEntry.payload.data.status, 'failed');
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(toolEntry?.payload.data.status, 'failed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testToolCallLateCallIdReconciliation() {
|
function testToolCallLateCallIdReconciliation() {
|
||||||
@@ -349,10 +352,7 @@ function testToolCallLateCallIdReconciliation() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const state = hydrateStreamState(updates);
|
const state = hydrateStreamState(updates);
|
||||||
const toolCalls = state.filter(
|
const toolCalls = state.filter(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem =>
|
|
||||||
item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(toolCalls.length, 1, 'late call ids should merge entries');
|
assert.strictEqual(toolCalls.length, 1, 'late call ids should merge entries');
|
||||||
assert.strictEqual(toolCalls[0].payload.data.status, 'completed');
|
assert.strictEqual(toolCalls[0].payload.data.status, 'completed');
|
||||||
@@ -460,16 +460,12 @@ function testToolCallParsedPayloadHydration() {
|
|||||||
|
|
||||||
const state = hydrateStreamState(updates);
|
const state = hydrateStreamState(updates);
|
||||||
const readEntry = state.find(
|
const readEntry = state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === readCallId
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === readCallId
|
|
||||||
);
|
);
|
||||||
const commandEntry = state.find(
|
const commandEntry = state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === commandCallId
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === commandCallId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const readPass = Boolean(
|
const readPass = Boolean(
|
||||||
@@ -711,22 +707,16 @@ function testClaudeHydratedToolBodies() {
|
|||||||
|
|
||||||
const state = hydrateStreamState(updates);
|
const state = hydrateStreamState(updates);
|
||||||
const editEntry = state.find(
|
const editEntry = state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === editCallId
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === editCallId
|
|
||||||
);
|
);
|
||||||
const readEntry = state.find(
|
const readEntry = state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === readCallId
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === readCallId
|
|
||||||
);
|
);
|
||||||
const commandEntry = state.find(
|
const commandEntry = state.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === commandCallId
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === commandCallId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const editHasDiff = Boolean(
|
const editHasDiff = Boolean(
|
||||||
@@ -838,9 +828,7 @@ function testPermissionToolCallFiltering() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const state = hydrateStreamState(updates);
|
const state = hydrateStreamState(updates);
|
||||||
const permissionEntries = state.filter(
|
const permissionEntries = state.filter(isAgentToolCallItem);
|
||||||
(item) => item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(permissionEntries.length, 0);
|
assert.strictEqual(permissionEntries.length, 0);
|
||||||
}
|
}
|
||||||
@@ -1030,7 +1018,7 @@ function buildConcurrentToolCallUpdates(provider: ToolCallProvider) {
|
|||||||
function validateToolCallDeduplication(
|
function validateToolCallDeduplication(
|
||||||
updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }>,
|
updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }>,
|
||||||
mode: 'live' | 'hydrated'
|
mode: 'live' | 'hydrated'
|
||||||
): ToolCallItem[] {
|
): AgentToolCallItem[] {
|
||||||
const finalState =
|
const finalState =
|
||||||
mode === 'live'
|
mode === 'live'
|
||||||
? updates.reduce<StreamItem[]>((state, { event, timestamp }) => {
|
? updates.reduce<StreamItem[]>((state, { event, timestamp }) => {
|
||||||
@@ -1038,10 +1026,7 @@ function validateToolCallDeduplication(
|
|||||||
}, [])
|
}, [])
|
||||||
: hydrateStreamState(updates);
|
: hydrateStreamState(updates);
|
||||||
|
|
||||||
return finalState.filter(
|
return finalState.filter(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem =>
|
|
||||||
item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testToolCallDeduplicationLive() {
|
function testToolCallDeduplicationLive() {
|
||||||
@@ -1116,9 +1101,7 @@ function testOutOfOrderToolCallMergingLive() {
|
|||||||
const finalState = updates.reduce<StreamItem[]>((state, { event, timestamp }) => {
|
const finalState = updates.reduce<StreamItem[]>((state, { event, timestamp }) => {
|
||||||
return reduceStreamUpdate(state, event, timestamp);
|
return reduceStreamUpdate(state, event, timestamp);
|
||||||
}, []);
|
}, []);
|
||||||
const toolCalls = finalState.filter(
|
const toolCalls = finalState.filter(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
assert.strictEqual(toolCalls.length, 1, `${provider} live stream should not duplicate out-of-order calls`);
|
assert.strictEqual(toolCalls.length, 1, `${provider} live stream should not duplicate out-of-order calls`);
|
||||||
assert.strictEqual(toolCalls[0]?.payload.data.status, 'completed', `${provider} live stream should keep completed status`);
|
assert.strictEqual(toolCalls[0]?.payload.data.status, 'completed', `${provider} live stream should keep completed status`);
|
||||||
});
|
});
|
||||||
@@ -1128,9 +1111,7 @@ function testOutOfOrderToolCallMergingHydrated() {
|
|||||||
(['claude', 'codex'] as const).forEach((provider) => {
|
(['claude', 'codex'] as const).forEach((provider) => {
|
||||||
const updates = buildOutOfOrderToolCallSequence(provider);
|
const updates = buildOutOfOrderToolCallSequence(provider);
|
||||||
const hydrated = hydrateStreamState(updates);
|
const hydrated = hydrateStreamState(updates);
|
||||||
const toolCalls = hydrated.filter(
|
const toolCalls = hydrated.filter(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
assert.strictEqual(toolCalls.length, 1, `${provider} hydration should not duplicate out-of-order calls`);
|
assert.strictEqual(toolCalls.length, 1, `${provider} hydration should not duplicate out-of-order calls`);
|
||||||
assert.strictEqual(toolCalls[0]?.payload.data.status, 'completed', `${provider} hydration should keep completed status`);
|
assert.strictEqual(toolCalls[0]?.payload.data.status, 'completed', `${provider} hydration should keep completed status`);
|
||||||
});
|
});
|
||||||
@@ -1178,7 +1159,7 @@ function buildMetadataReplaySequence(provider: ToolCallProvider) {
|
|||||||
function validateMetadataReplayDeduplication(
|
function validateMetadataReplayDeduplication(
|
||||||
updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }>,
|
updates: Array<{ event: AgentStreamEventPayload; timestamp: Date }>,
|
||||||
mode: 'live' | 'hydrated'
|
mode: 'live' | 'hydrated'
|
||||||
) {
|
): AgentToolCallItem[] {
|
||||||
const finalState =
|
const finalState =
|
||||||
mode === 'live'
|
mode === 'live'
|
||||||
? updates.reduce<StreamItem[]>((state, { event, timestamp }) => {
|
? updates.reduce<StreamItem[]>((state, { event, timestamp }) => {
|
||||||
@@ -1186,11 +1167,7 @@ function validateMetadataReplayDeduplication(
|
|||||||
}, [])
|
}, [])
|
||||||
: hydrateStreamState(updates);
|
: hydrateStreamState(updates);
|
||||||
|
|
||||||
const toolCalls = finalState.filter(
|
return finalState.filter(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
|
|
||||||
return toolCalls;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testMetadataReplayDeduplicationLive() {
|
function testMetadataReplayDeduplicationLive() {
|
||||||
@@ -1241,9 +1218,7 @@ function testFallbackToolCallIdsStayUnique() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const hydrated = hydrateStreamState(updates);
|
const hydrated = hydrateStreamState(updates);
|
||||||
const toolCalls = hydrated.filter(
|
const toolCalls = hydrated.filter(isAgentToolCallItem);
|
||||||
(item): item is ToolCallItem => item.kind === 'tool_call' && item.payload.source === 'agent'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(toolCalls.length, 2, 'Hydration should retain multiple fallback tool entries');
|
assert.strictEqual(toolCalls.length, 2, 'Hydration should retain multiple fallback tool entries');
|
||||||
const ids = toolCalls.map((item) => item.id);
|
const ids = toolCalls.map((item) => item.id);
|
||||||
@@ -1284,10 +1259,8 @@ function testCodexProviderEventsProduceToolCalls() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const commandTool = commandState.find(
|
const commandTool = commandState.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === 'cmd-provider-1'
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === 'cmd-provider-1'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(commandTool, 'Codex provider command events should create tool call entries');
|
assert.ok(commandTool, 'Codex provider command events should create tool call entries');
|
||||||
@@ -1327,10 +1300,8 @@ function testCodexProviderEventsProduceToolCalls() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const mcpTool = mcpState.find(
|
const mcpTool = mcpState.find(
|
||||||
(item): item is ToolCallItem =>
|
(item): item is AgentToolCallItem =>
|
||||||
item.kind === 'tool_call' &&
|
isAgentToolCallItem(item) && item.payload.data.callId === 'mcp-provider-1'
|
||||||
item.payload.source === 'agent' &&
|
|
||||||
item.payload.data.callId === 'mcp-provider-1'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(mcpTool, 'Codex provider MCP events should create tool call entries');
|
assert.ok(mcpTool, 'Codex provider MCP events should create tool call entries');
|
||||||
|
|||||||
@@ -90,20 +90,22 @@ export interface ThoughtItem {
|
|||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToolCallStatus = "executing" | "completed" | "failed";
|
||||||
|
|
||||||
interface OrchestratorToolCallData {
|
interface OrchestratorToolCallData {
|
||||||
toolCallId: string;
|
toolCallId: string;
|
||||||
toolName: string;
|
toolName: string;
|
||||||
arguments: unknown;
|
arguments: unknown;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
status: "executing" | "completed" | "failed";
|
status: ToolCallStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentToolCallData {
|
export interface AgentToolCallData {
|
||||||
provider: AgentProvider;
|
provider: AgentProvider;
|
||||||
server: string;
|
server: string;
|
||||||
tool: string;
|
tool: string;
|
||||||
status: string;
|
status?: ToolCallStatus;
|
||||||
raw?: unknown;
|
raw?: unknown;
|
||||||
callId?: string;
|
callId?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
@@ -126,10 +128,14 @@ export interface ToolCallItem {
|
|||||||
payload: ToolCallPayload;
|
payload: ToolCallPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentToolCallItem = ToolCallItem & {
|
export type AgentToolCallItem = ToolCallItem & {
|
||||||
payload: { source: "agent"; data: AgentToolCallData };
|
payload: { source: "agent"; data: AgentToolCallData };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isAgentToolCallItem(item: StreamItem): item is AgentToolCallItem {
|
||||||
|
return item.kind === "tool_call" && item.payload.source === "agent";
|
||||||
|
}
|
||||||
|
|
||||||
type ActivityLogType = "system" | "info" | "success" | "error";
|
type ActivityLogType = "system" | "info" | "success" | "error";
|
||||||
|
|
||||||
export interface ActivityLogItem {
|
export interface ActivityLogItem {
|
||||||
@@ -532,12 +538,6 @@ function findExistingAgentToolCallIndex(
|
|||||||
return matches.length > 0 ? matches : candidates;
|
return matches.length > 0 ? matches : candidates;
|
||||||
};
|
};
|
||||||
|
|
||||||
const byDisplayName = filterByComparableField(
|
|
||||||
fallbackCandidates,
|
|
||||||
(entry) => normalizeComparableString(entry.payload.data.displayName),
|
|
||||||
normalizedDisplayName
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectCandidate = (
|
const selectCandidate = (
|
||||||
candidates: Array<{ index: number; item: AgentToolCallItem }>
|
candidates: Array<{ index: number; item: AgentToolCallItem }>
|
||||||
): number => {
|
): number => {
|
||||||
@@ -773,7 +773,7 @@ function normalizeToolCallStatus(
|
|||||||
result?: unknown,
|
result?: unknown,
|
||||||
error?: unknown,
|
error?: unknown,
|
||||||
raw?: unknown
|
raw?: unknown
|
||||||
): "executing" | "completed" | "failed" {
|
): ToolCallStatus {
|
||||||
const normalizedFromStatus = normalizeStatusString(status);
|
const normalizedFromStatus = normalizeStatusString(status);
|
||||||
if (normalizedFromStatus === "failed") {
|
if (normalizedFromStatus === "failed") {
|
||||||
return "failed";
|
return "failed";
|
||||||
@@ -798,9 +798,9 @@ function normalizeToolCallStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mergeToolCallStatus(
|
function mergeToolCallStatus(
|
||||||
existing: "executing" | "completed" | "failed" | undefined,
|
existing: ToolCallStatus | undefined,
|
||||||
incoming: "executing" | "completed" | "failed"
|
incoming: ToolCallStatus
|
||||||
): "executing" | "completed" | "failed" {
|
): ToolCallStatus {
|
||||||
if (existing === "failed" || incoming === "failed") {
|
if (existing === "failed" || incoming === "failed") {
|
||||||
return "failed";
|
return "failed";
|
||||||
}
|
}
|
||||||
@@ -938,7 +938,7 @@ export function reduceStreamUpdate(
|
|||||||
provider: event.provider,
|
provider: event.provider,
|
||||||
server: item.server,
|
server: item.server,
|
||||||
tool: item.tool,
|
tool: item.tool,
|
||||||
status: item.status ?? "executing",
|
status: normalizeStatusString(item.status) ?? "executing",
|
||||||
raw: rawPayload,
|
raw: rawPayload,
|
||||||
callId: item.callId,
|
callId: item.callId,
|
||||||
displayName: item.displayName,
|
displayName: item.displayName,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"dev:tsx": "NODE_ENV=development tsx watch --ignore '**/*.timestamp-*' src/server/index.ts",
|
"dev:tsx": "NODE_ENV=development tsx watch --ignore '**/*.timestamp-*' src/server/index.ts",
|
||||||
"build": "tsc -p tsconfig.server.json",
|
"build": "tsc -p tsconfig.server.json",
|
||||||
"start": "NODE_ENV=production node dist/server/index.js",
|
"start": "NODE_ENV=production node dist/server/index.js",
|
||||||
"typecheck": "tsc -p tsconfig.server.json --noEmit",
|
"typecheck": "tsc -p tsconfig.server.typecheck.json --noEmit",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { ClaudeAgentClient, convertClaudeHistoryEntry } from "./claude-agent.js";
|
import { ClaudeAgentClient, convertClaudeHistoryEntry } from "./claude-agent.js";
|
||||||
import { hydrateStreamState, type StreamItem, type ToolCallItem, type AgentToolCallData } from "../../../../../app/src/types/stream.js";
|
import {
|
||||||
|
hydrateStreamState,
|
||||||
|
type StreamItem,
|
||||||
|
type AgentToolCallData,
|
||||||
|
isAgentToolCallItem,
|
||||||
|
} from "../../../../../app/src/types/stream.js";
|
||||||
import type { AgentStreamEventPayload } from "../../messages.js";
|
import type { AgentStreamEventPayload } from "../../messages.js";
|
||||||
import type { AgentProvider, AgentSessionConfig, AgentStreamEvent, AgentTimelineItem } from "../agent-sdk-types.js";
|
import type { AgentProvider, AgentSessionConfig, AgentStreamEvent, AgentTimelineItem } from "../agent-sdk-types.js";
|
||||||
|
|
||||||
@@ -596,12 +601,7 @@ function stateIncludesUserMessage(state: StreamItem[], marker: string): boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractAgentToolSnapshots(state: StreamItem[]): ToolSnapshot[] {
|
function extractAgentToolSnapshots(state: StreamItem[]): ToolSnapshot[] {
|
||||||
return state
|
return state.filter(isAgentToolCallItem).map((item) => ({
|
||||||
.filter(
|
|
||||||
(item): item is ToolCallItem =>
|
|
||||||
item.kind === "tool_call" && item.payload.source === "agent"
|
|
||||||
)
|
|
||||||
.map((item) => ({
|
|
||||||
key: buildToolSnapshotKey(item.payload.data, item.id),
|
key: buildToolSnapshotKey(item.payload.data, item.id),
|
||||||
data: item.payload.data,
|
data: item.payload.data,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"lib": ["ES2020"],
|
"lib": ["ES2020"],
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"paths": {
|
||||||
|
"@server/*": ["./*"]
|
||||||
|
},
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.server.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../../"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,3 +82,8 @@
|
|||||||
- `packages/app/src/components/agent-input-area.tsx` now shows a lone Send control when text/images are queued while keeping Dictate + Realtime visible only when the input is empty or the agent is actively running.
|
- `packages/app/src/components/agent-input-area.tsx` now shows a lone Send control when text/images are queued while keeping Dictate + Realtime visible only when the input is empty or the agent is actively running.
|
||||||
- [x] Remove the agent status pill next to the permission selector; instead show a “working” indicator inside the chat scroll view itself (three bouncing dots animation) whenever the agent is busy.
|
- [x] Remove the agent status pill next to the permission selector; instead show a “working” indicator inside the chat scroll view itself (three bouncing dots animation) whenever the agent is busy.
|
||||||
- Dropped the old status dot from `AgentStatusBar` (the permission/mode selector now stands alone) and introduced an in-stream "Working" chip inside `AgentStreamView` that renders three bouncing dots via Reanimated whenever the agent reports `status === "running"`, so busy turns surface directly in the chat timeline.
|
- Dropped the old status dot from `AgentStatusBar` (the permission/mode selector now stands alone) and introduced an in-stream "Working" chip inside `AgentStreamView` that renders three bouncing dots via Reanimated whenever the agent reports `status === "running"`, so busy turns surface directly in the chat timeline.
|
||||||
|
- [x] npm run typecheck and fix all problems
|
||||||
|
- Split the server build/typecheck configs so the typechecker can include the app stream helpers without breaking builds, added path aliases for `@server/*`, and tightened the shared stream types (status unions + `isAgentToolCallItem`) so the server e2e suites can import them safely. Cleaned up every failing app/server test by narrowing tool-call payloads via the helper, updated the harness + Codex/Claude specs, and re-ran `npm run typecheck` (app + server) to green.
|
||||||
|
- [ ] audit codebase for unecessary untyped code, hacks, casts, and `any`, type things properly
|
||||||
|
- [ ] audit codebase for duplicated code, clean it up
|
||||||
|
- [ ] rename the project to Paseo, including the Expo app, package names etc.
|
||||||
|
|||||||
Reference in New Issue
Block a user