@keyou007/agent-bot 0.1.24-alpha.2 → 0.1.24-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/README.md +27 -5
- package/README.zh.md +26 -5
- package/dist/acp/AcpJsonRpcConnection.js +3 -1
- package/dist/acp/AcpJsonRpcConnection.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js.map +1 -1
- package/dist/cli/SelfUpdater.js +2 -0
- package/dist/cli/SelfUpdater.js.map +1 -1
- package/dist/codex/AppServerConnection.js +3 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexEventMapper.js +28 -1
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexRuntime.js +92 -0
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +5 -0
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +45 -23
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +2 -0
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +44 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +114 -37
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/RecentMessageContext.js +70 -0
- package/dist/feishu/RecentMessageContext.js.map +1 -0
- package/dist/index.js +62 -24
- package/dist/index.js.map +1 -1
- package/dist/local-files/DiagramPreview.js +171 -0
- package/dist/local-files/DiagramPreview.js.map +1 -0
- package/dist/local-files/LocalFileViewerServer.js +141 -3
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/PreviewSyntaxHighlight.js +57 -0
- package/dist/local-files/PreviewSyntaxHighlight.js.map +1 -0
- package/dist/local-files/TurnPreviewPage.js +495 -60
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +18 -9
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +155 -6
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/presentation/turnReasoning.js +23 -0
- package/dist/presentation/turnReasoning.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +460 -34
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +43 -2
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/TurnPreviewJournal.js +469 -0
- package/dist/state/TurnPreviewJournal.js.map +1 -0
- package/dist/state/migrations.js +10 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/updates/AutomaticUpdatePreparer.js +16 -7
- package/dist/updates/AutomaticUpdatePreparer.js.map +1 -1
- package/dist/updates/ManualUpdateController.js +102 -0
- package/dist/updates/ManualUpdateController.js.map +1 -0
- package/dist/updates/PublishedRelease.js +42 -4
- package/dist/updates/PublishedRelease.js.map +1 -1
- package/dist/utils/ProtocolLineWarning.js +15 -0
- package/dist/utils/ProtocolLineWarning.js.map +1 -0
- package/docs/technical-reference.md +21 -1
- package/docs/technical-reference.zh.md +21 -1
- package/npm-shrinkwrap.json +1401 -10
- package/package.json +3 -1
- package/skills/agent-bot/SKILL.md +8 -1
- package/src/acp/AcpJsonRpcConnection.ts +3 -1
- package/src/cli/SelfUpdateRunner.ts +1 -0
- package/src/cli/SelfUpdater.ts +2 -0
- package/src/codex/AppServerConnection.ts +3 -1
- package/src/codex/CodexEventMapper.ts +29 -1
- package/src/codex/CodexRuntime.ts +87 -0
- package/src/commands/CommandRouter.ts +4 -0
- package/src/commands/commandTypes.ts +1 -0
- package/src/feishu/CardRenderer.ts +43 -21
- package/src/feishu/FeishuConnector.ts +2 -0
- package/src/feishu/FeishuMessageClient.ts +44 -0
- package/src/feishu/FeishuTurnPresenter.ts +107 -31
- package/src/feishu/RecentMessageContext.ts +90 -0
- package/src/feishu/types.ts +5 -0
- package/src/index.ts +62 -22
- package/src/local-files/DiagramPreview.ts +173 -0
- package/src/local-files/LocalFileViewerServer.ts +129 -2
- package/src/local-files/PreviewSyntaxHighlight.ts +58 -0
- package/src/local-files/TurnPreviewPage.ts +535 -57
- package/src/presentation/OutboundRouter.ts +25 -7
- package/src/presentation/TurnStateReducer.ts +151 -7
- package/src/presentation/turnReasoning.ts +21 -0
- package/src/presentation/turnViewTypes.ts +19 -1
- package/src/proxy/ProxySessionController.ts +452 -24
- package/src/runtime/types.ts +21 -0
- package/src/state/StateStore.ts +45 -2
- package/src/state/TurnPreviewJournal.ts +398 -0
- package/src/state/migrations.ts +10 -0
- package/src/updates/AutomaticUpdatePreparer.ts +21 -7
- package/src/updates/ManualUpdateController.ts +116 -0
- package/src/updates/PublishedRelease.ts +49 -3
- package/src/utils/ProtocolLineWarning.ts +13 -0
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
MergedForwardContent,
|
|
8
8
|
MessageReplyTarget,
|
|
9
9
|
ReferencedMessageContent,
|
|
10
|
+
RecentContextMessage,
|
|
11
|
+
RecentMessageRequest,
|
|
10
12
|
} from "../feishu/types.js";
|
|
11
13
|
|
|
12
14
|
export interface TurnPresenter {
|
|
@@ -17,8 +19,9 @@ export interface TurnPresenter {
|
|
|
17
19
|
projectCwd?: string,
|
|
18
20
|
agentLabel?: string,
|
|
19
21
|
model?: string,
|
|
22
|
+
modelProvider?: string,
|
|
20
23
|
): void;
|
|
21
|
-
updateSessionModel(sessionId: string, model?: string): void;
|
|
24
|
+
updateSessionModel(sessionId: string, model?: string, modelProvider?: string): void;
|
|
22
25
|
updateSessionTitle(sessionId: string, taskTitle: string): void;
|
|
23
26
|
unregisterSession(sessionId: string): void;
|
|
24
27
|
startPendingTurn(
|
|
@@ -27,6 +30,7 @@ export interface TurnPresenter {
|
|
|
27
30
|
taskTitle?: string,
|
|
28
31
|
replyTarget?: MessageReplyTarget,
|
|
29
32
|
prompt?: string,
|
|
33
|
+
localImagePaths?: string[],
|
|
30
34
|
): Promise<string | undefined>;
|
|
31
35
|
failPendingTurn(
|
|
32
36
|
sessionId: string,
|
|
@@ -34,9 +38,10 @@ export interface TurnPresenter {
|
|
|
34
38
|
replacementCard?: Record<string, unknown>,
|
|
35
39
|
): Promise<boolean>;
|
|
36
40
|
interruptTurnForRecovery(sessionId: string, contextKey: string, turnId: string, message: string): Promise<void>;
|
|
37
|
-
appendSteerMessage(sessionId: string, turnId: string, text: string, messageId?: string): Promise<void>;
|
|
41
|
+
appendSteerMessage(sessionId: string, turnId: string, text: string, messageId?: string, localImagePaths?: string[]): Promise<void>;
|
|
38
42
|
onEvent(event: AgentEvent): Promise<void>;
|
|
39
43
|
showDetails(contextKey: string, turnId: string): Promise<void>;
|
|
44
|
+
getTurnPreviewUrl?(turnId: string): string | undefined;
|
|
40
45
|
showActivityPage(
|
|
41
46
|
contextKey: string,
|
|
42
47
|
turnId: string,
|
|
@@ -70,12 +75,13 @@ export class OutboundRouter {
|
|
|
70
75
|
projectCwd?: string,
|
|
71
76
|
agentLabel?: string,
|
|
72
77
|
model?: string,
|
|
78
|
+
modelProvider?: string,
|
|
73
79
|
): void {
|
|
74
80
|
const route = this.route(contextKey);
|
|
75
81
|
this.sessionRoutes.set(sessionId, route);
|
|
76
82
|
this.sessionContextKeys.set(sessionId, contextKey);
|
|
77
83
|
route.presenter.registerSession(sessionId, contextKey, taskTitle, projectCwd, agentLabel);
|
|
78
|
-
if (model) route.presenter.updateSessionModel(sessionId, model);
|
|
84
|
+
if (model || modelProvider) route.presenter.updateSessionModel(sessionId, model, modelProvider);
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
getSessionContextKey(sessionId: string): string | undefined {
|
|
@@ -94,8 +100,8 @@ export class OutboundRouter {
|
|
|
94
100
|
this.sessionRoutes.get(sessionId)?.presenter.updateSessionTitle(sessionId, taskTitle);
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
updateSessionModel(sessionId: string, model?: string): void {
|
|
98
|
-
this.sessionRoutes.get(sessionId)?.presenter.updateSessionModel(sessionId, model);
|
|
103
|
+
updateSessionModel(sessionId: string, model?: string, modelProvider?: string): void {
|
|
104
|
+
this.sessionRoutes.get(sessionId)?.presenter.updateSessionModel(sessionId, model, modelProvider);
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
unregisterSession(sessionId: string): void {
|
|
@@ -112,13 +118,14 @@ export class OutboundRouter {
|
|
|
112
118
|
taskTitle?: string,
|
|
113
119
|
replyTarget?: MessageReplyTarget,
|
|
114
120
|
prompt?: string,
|
|
121
|
+
localImagePaths?: string[],
|
|
115
122
|
): Promise<string | undefined> {
|
|
116
123
|
const route = this.route(contextKey);
|
|
117
124
|
this.sessionRoutes.set(sessionId, route);
|
|
118
125
|
this.sessionContextKeys.set(sessionId, contextKey);
|
|
119
126
|
if (replyTarget) this.sessionReplyTargets.set(sessionId, replyTarget);
|
|
120
127
|
else this.sessionReplyTargets.delete(sessionId);
|
|
121
|
-
return route.presenter.startPendingTurn(sessionId, contextKey, taskTitle, replyTarget, prompt);
|
|
128
|
+
return route.presenter.startPendingTurn(sessionId, contextKey, taskTitle, replyTarget, prompt, ...(localImagePaths ? [localImagePaths] : []));
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
async failPendingTurn(
|
|
@@ -147,8 +154,13 @@ export class OutboundRouter {
|
|
|
147
154
|
turnId: string,
|
|
148
155
|
text: string,
|
|
149
156
|
messageId?: string,
|
|
157
|
+
localImagePaths?: string[],
|
|
150
158
|
): Promise<void> {
|
|
151
|
-
await this.sessionRoutes.get(sessionId)?.presenter.appendSteerMessage(sessionId, turnId, text, messageId);
|
|
159
|
+
await this.sessionRoutes.get(sessionId)?.presenter.appendSteerMessage(sessionId, turnId, text, messageId, ...(localImagePaths ? [localImagePaths] : []));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getTurnPreviewUrl(contextKey: string, turnId: string): string | undefined {
|
|
163
|
+
return this.route(contextKey).presenter.getTurnPreviewUrl?.(turnId);
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
async onEvent(event: AgentEvent): Promise<void> {
|
|
@@ -206,6 +218,12 @@ export class OutboundRouter {
|
|
|
206
218
|
return read.call(outbound, messageId);
|
|
207
219
|
}
|
|
208
220
|
|
|
221
|
+
async readRecentMessages(contextKey: string, request: RecentMessageRequest): Promise<RecentContextMessage[]> {
|
|
222
|
+
const outbound = this.route(contextKey).outbound;
|
|
223
|
+
if (!outbound.readRecentMessages) throw new Error("当前消息通道不支持读取近期群消息。");
|
|
224
|
+
return outbound.readRecentMessages(request);
|
|
225
|
+
}
|
|
226
|
+
|
|
209
227
|
async readReferencedMessage(contextKey: string, messageId: string): Promise<ReferencedMessageContent> {
|
|
210
228
|
const outbound = this.route(contextKey).outbound;
|
|
211
229
|
const read = outbound.readReferencedMessage;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { AgentEvent, ToolState } from "../runtime/types.js";
|
|
1
|
+
import type { AgentEvent, RemoteTurnDetails, ToolState } from "../runtime/types.js";
|
|
2
2
|
import { appendGeneratedImageMarkdown } from "../utils/generatedImageMarkdown.js";
|
|
3
3
|
import type { MessageReplyTarget } from "../feishu/types.js";
|
|
4
4
|
import { formatStorageSize } from "../utils/formatStorageSize.js";
|
|
5
|
-
import type { FileSummary, TurnActivity, TurnViewState } from "./turnViewTypes.js";
|
|
5
|
+
import type { FileSummary, TurnActivity, TurnReasoningItem, TurnViewState } from "./turnViewTypes.js";
|
|
6
|
+
import { turnReasoningItems } from "./turnReasoning.js";
|
|
6
7
|
|
|
7
8
|
const MAX_TEXT = 6_000;
|
|
8
9
|
const MAX_COMPLETED_TOOLS = 20;
|
|
@@ -19,17 +20,22 @@ export function createTurnViewState(
|
|
|
19
20
|
prompt?: string,
|
|
20
21
|
agentLabel?: string,
|
|
21
22
|
model?: string,
|
|
23
|
+
promptImagePaths?: string[],
|
|
24
|
+
modelProvider?: string,
|
|
22
25
|
): TurnViewState {
|
|
23
26
|
return {
|
|
24
27
|
sessionId,
|
|
25
28
|
turnId,
|
|
26
29
|
agentLabel,
|
|
27
30
|
model,
|
|
31
|
+
modelProvider,
|
|
28
32
|
taskTitle,
|
|
29
33
|
prompt,
|
|
34
|
+
...(promptImagePaths?.length ? { promptImagePaths: [...new Set(promptImagePaths)] } : {}),
|
|
30
35
|
projectCwd,
|
|
31
36
|
replyTarget,
|
|
32
37
|
status: "starting",
|
|
38
|
+
historyDetail: "full",
|
|
33
39
|
startedAt,
|
|
34
40
|
assistantText: "",
|
|
35
41
|
plan: [],
|
|
@@ -46,7 +52,84 @@ export function createTurnViewState(
|
|
|
46
52
|
};
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
export function
|
|
55
|
+
export function hydrateTurnViewState(summary: TurnViewState, details: RemoteTurnDetails): TurnViewState {
|
|
56
|
+
let state: TurnViewState = {
|
|
57
|
+
...summary,
|
|
58
|
+
...createTurnViewState(summary.sessionId, summary.turnId, details.startedAt ?? summary.startedAt,
|
|
59
|
+
summary.taskTitle, summary.replyTarget, summary.projectCwd, summary.prompt,
|
|
60
|
+
summary.agentLabel, summary.model, summary.promptImagePaths, summary.modelProvider),
|
|
61
|
+
historyDetail: "full",
|
|
62
|
+
historyDetailError: undefined,
|
|
63
|
+
};
|
|
64
|
+
let hasPrompt = false;
|
|
65
|
+
const identity = { sessionId: state.sessionId, turnId: state.turnId };
|
|
66
|
+
for (const item of details.items) {
|
|
67
|
+
if (item.kind === "message") {
|
|
68
|
+
if (item.role === "user") {
|
|
69
|
+
if (!hasPrompt) {
|
|
70
|
+
state = { ...state, prompt: item.text, promptImagePaths: item.localImagePaths };
|
|
71
|
+
hasPrompt = true;
|
|
72
|
+
} else {
|
|
73
|
+
state = { ...state, activities: [...state.activities, {
|
|
74
|
+
kind: "user", id: item.id, text: item.text, localImagePaths: item.localImagePaths,
|
|
75
|
+
}] };
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
state = reduceTurnEvent(state, { ...identity, type: "progress", activityId: `commentary:${item.id}`, text: item.text });
|
|
79
|
+
}
|
|
80
|
+
} else if (item.kind === "tool") {
|
|
81
|
+
state = reduceToolUpdate(state, item.tool, undefined, true);
|
|
82
|
+
state.activities = state.activities.map((a) => a.kind === "tool" && a.id === item.tool.id ? { ...a, tool: item.tool } : a);
|
|
83
|
+
} else if (item.kind === "reasoning") {
|
|
84
|
+
for (const [summaryIndex, text] of item.summary.entries()) {
|
|
85
|
+
state = reduceTurnEvent(state, { ...identity, type: "progress", text,
|
|
86
|
+
activityId: `reasoning:${item.id}:${summaryIndex}`, reasoning: { itemId: item.id, summaryIndex } });
|
|
87
|
+
}
|
|
88
|
+
state = reduceTurnEvent(state, { ...identity, type: "reasoning_completed", itemId: item.id,
|
|
89
|
+
summary: item.summary, content: item.content });
|
|
90
|
+
} else {
|
|
91
|
+
state = reduceTurnEvent(state, { ...identity, type: "plan_updated", steps: item.steps });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const completedAt = details.completedAt ?? (details.durationMs === undefined || details.startedAt === undefined
|
|
95
|
+
? undefined : details.startedAt + details.durationMs);
|
|
96
|
+
return {
|
|
97
|
+
...state,
|
|
98
|
+
status: details.status,
|
|
99
|
+
completedAt,
|
|
100
|
+
durationMs: details.durationMs ?? (completedAt === undefined || details.startedAt === undefined
|
|
101
|
+
? undefined : Math.max(0, completedAt - details.startedAt)),
|
|
102
|
+
activeTool: undefined,
|
|
103
|
+
approval: undefined,
|
|
104
|
+
finalResponse: details.finalResponse,
|
|
105
|
+
error: details.error,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function reduceTurnEvent(state: TurnViewState, event: AgentEvent, complete = false): TurnViewState {
|
|
110
|
+
if (event.sessionId !== state.sessionId || event.turnId !== state.turnId) return state;
|
|
111
|
+
const next = reduceEvent(state, event);
|
|
112
|
+
if (!complete) return next;
|
|
113
|
+
if (event.type === "turn_completed") next.finalResponse = event.finalResponse;
|
|
114
|
+
if (event.type === "agent_text_delta") next.assistantText = state.assistantText + event.text;
|
|
115
|
+
if (event.type === "plan_updated") next.plan = event.steps;
|
|
116
|
+
if (event.type === "progress") {
|
|
117
|
+
const id = event.activityId ?? "progress";
|
|
118
|
+
const old = state.activities.find((a) => a.id === id);
|
|
119
|
+
next.activities = next.activities.map((a) => a.id === id && a.kind !== "tool"
|
|
120
|
+
? { ...a, text: (event.append && old && old.kind !== "tool" ? old.text : "") + event.text } : a);
|
|
121
|
+
}
|
|
122
|
+
if (event.type === "tool_started" || event.type === "tool_updated") {
|
|
123
|
+
next.activities = next.activities.map((a) => a.kind === "tool" && a.id === event.tool.id
|
|
124
|
+
? { ...a, tool: { ...a.tool, ...event.tool } } : a);
|
|
125
|
+
const files = new Map(state.fileSummary.map((f) => [f.path, f]));
|
|
126
|
+
for (const file of event.tool.files ?? []) files.set(file.path, file);
|
|
127
|
+
next.fileSummary = [...files.values()];
|
|
128
|
+
}
|
|
129
|
+
return next;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function reduceEvent(state: TurnViewState, event: AgentEvent): TurnViewState {
|
|
50
133
|
if (event.sessionId !== state.sessionId || event.turnId !== state.turnId) return state;
|
|
51
134
|
|
|
52
135
|
switch (event.type) {
|
|
@@ -173,7 +256,27 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
|
|
|
173
256
|
activitiesTruncated: state.activitiesTruncated || activityUpdate.truncated,
|
|
174
257
|
};
|
|
175
258
|
}
|
|
259
|
+
case "reasoning_delta":
|
|
260
|
+
return updateReasoningItem(state, event.itemId, (item) => {
|
|
261
|
+
if (item.completed) return item;
|
|
262
|
+
const content = [...item.content];
|
|
263
|
+
content[event.contentIndex] = (content[event.contentIndex] ?? "") + event.text;
|
|
264
|
+
return { ...item, content };
|
|
265
|
+
});
|
|
266
|
+
case "reasoning_completed":
|
|
267
|
+
return updateReasoningItem(state, event.itemId, (item) => ({
|
|
268
|
+
...item, summary: [...event.summary], content: [...event.content], completed: true,
|
|
269
|
+
}));
|
|
176
270
|
case "progress": {
|
|
271
|
+
const previewState = event.reasoning
|
|
272
|
+
? updateReasoningItem(state, event.reasoning.itemId, (item) => {
|
|
273
|
+
if (item.completed) return item;
|
|
274
|
+
const summary = [...item.summary];
|
|
275
|
+
summary[event.reasoning!.summaryIndex] = event.append
|
|
276
|
+
? (summary[event.reasoning!.summaryIndex] ?? "") + event.text : event.text;
|
|
277
|
+
return { ...item, summary };
|
|
278
|
+
})
|
|
279
|
+
: state;
|
|
177
280
|
const activities = state.activities ?? [];
|
|
178
281
|
const warning = event.severity === "warning";
|
|
179
282
|
const activityUpdate = upsertReasoningActivity(
|
|
@@ -186,6 +289,7 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
|
|
|
186
289
|
return {
|
|
187
290
|
...state,
|
|
188
291
|
progressText: bound(event.text),
|
|
292
|
+
...(previewState.reasoningItems ? { reasoningItems: previewState.reasoningItems } : {}),
|
|
189
293
|
activities: activityUpdate.activities,
|
|
190
294
|
activitiesTruncated: state.activitiesTruncated || activityUpdate.truncated,
|
|
191
295
|
};
|
|
@@ -258,15 +362,18 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
|
|
|
258
362
|
}
|
|
259
363
|
}
|
|
260
364
|
|
|
261
|
-
export function appendSteerMessage(state: TurnViewState, id: string, text: string): TurnViewState {
|
|
365
|
+
export function appendSteerMessage(state: TurnViewState, id: string, text: string, localImagePaths?: string[]): TurnViewState {
|
|
262
366
|
const normalized = text.trim();
|
|
263
|
-
if (!normalized) return state;
|
|
367
|
+
if (!normalized && !localImagePaths?.length) return state;
|
|
264
368
|
const activities = state.activities ?? [];
|
|
265
369
|
const index = activities.findIndex((activity) => activity.id === id);
|
|
370
|
+
const previous = activities[index];
|
|
371
|
+
const images = localImagePaths ?? (previous?.kind === "user" ? previous.localImagePaths : undefined);
|
|
266
372
|
const activityUpdate = upsertActivity(activities, index, {
|
|
267
373
|
kind: "user",
|
|
268
374
|
id,
|
|
269
375
|
text: bound(normalized),
|
|
376
|
+
...(images?.length ? { localImagePaths: [...new Set(images)] } : {}),
|
|
270
377
|
});
|
|
271
378
|
return {
|
|
272
379
|
...state,
|
|
@@ -312,8 +419,8 @@ function appendBoundedOutput(previous: string | undefined, delta: string): strin
|
|
|
312
419
|
return `…${combined.slice(-(MAX_TEXT - 1))}`;
|
|
313
420
|
}
|
|
314
421
|
|
|
315
|
-
function reduceToolUpdate(state: TurnViewState, tool: ToolState, fullOutput?: string): TurnViewState {
|
|
316
|
-
tool = withToolTiming(state, tool);
|
|
422
|
+
function reduceToolUpdate(state: TurnViewState, tool: ToolState, fullOutput?: string, historical = false): TurnViewState {
|
|
423
|
+
if (!historical) tool = withToolTiming(state, tool);
|
|
317
424
|
const nextFullOutput = fullOutput ?? tool.output;
|
|
318
425
|
const fullToolOutputs = nextFullOutput === undefined
|
|
319
426
|
? state.fullToolOutputs
|
|
@@ -411,6 +518,23 @@ function legacyToolStatuses(state: TurnViewState): Record<string, ToolState["sta
|
|
|
411
518
|
return statuses;
|
|
412
519
|
}
|
|
413
520
|
|
|
521
|
+
function updateReasoningItem(
|
|
522
|
+
state: TurnViewState,
|
|
523
|
+
itemId: string,
|
|
524
|
+
update: (item: TurnReasoningItem) => TurnReasoningItem,
|
|
525
|
+
): TurnViewState {
|
|
526
|
+
const items = [...(state.reasoningItems ?? [])];
|
|
527
|
+
const index = items.findIndex((item) => item.itemId === itemId);
|
|
528
|
+
const item = index >= 0 ? items[index]! : turnReasoningItems(state).find((item) => item.itemId === itemId) ?? {
|
|
529
|
+
itemId, afterActivityId: state.activities.at(-1)?.id, summary: [], content: [],
|
|
530
|
+
};
|
|
531
|
+
const next = update(item);
|
|
532
|
+
if (next === item) return state;
|
|
533
|
+
if (index >= 0) items[index] = next;
|
|
534
|
+
else items.push(next);
|
|
535
|
+
return { ...state, reasoningItems: items };
|
|
536
|
+
}
|
|
537
|
+
|
|
414
538
|
function upsertReasoningActivity(
|
|
415
539
|
activities: TurnActivity[],
|
|
416
540
|
id: string,
|
|
@@ -573,3 +697,23 @@ function formatCompactionDuration(durationMs: number): string {
|
|
|
573
697
|
function formatInteger(value: number): string {
|
|
574
698
|
return normalizeTokenCount(value).toLocaleString("en-US");
|
|
575
699
|
}
|
|
700
|
+
|
|
701
|
+
/** Bound the hot card projection, never the journal or final-delivery payload. */
|
|
702
|
+
export function compactTurnView(state: TurnViewState): TurnViewState {
|
|
703
|
+
const recent = state.activities.slice(-77);
|
|
704
|
+
const commentary = state.activities.slice(0, -77).filter((a) => a.kind === "assistant").slice(-3);
|
|
705
|
+
const retained = state.activities.length > 80 ? [...commentary, ...recent] : state.activities;
|
|
706
|
+
const activities = retained.map((a) => a.kind === "tool"
|
|
707
|
+
? { ...a, tool: boundTool(a.tool) } : { ...a, text: bound(a.text) });
|
|
708
|
+
const ids = new Set(activities.filter((a) => a.kind === "tool").map((a) => a.id));
|
|
709
|
+
return { ...state, previewJournal: true, previewCursor: undefined, activities,
|
|
710
|
+
assistantText: bound(state.assistantText), progressText: state.progressText === undefined ? undefined : bound(state.progressText),
|
|
711
|
+
plan: state.plan.slice(0, 30).map((step) => ({ ...step, text: bound(step.text) })),
|
|
712
|
+
activeTool: state.activeTool ? boundTool(state.activeTool) : undefined,
|
|
713
|
+
completedTools: state.completedTools.slice(-20).map(boundTool), failedTools: state.failedTools.slice(-5).map(boundTool),
|
|
714
|
+
fileSummary: state.fileSummary.slice(-30),
|
|
715
|
+
activitiesTruncated: state.activitiesTruncated || state.activities.length > 80,
|
|
716
|
+
reasoningItems: [], fullToolOutputs: {}, fullToolErrors: {},
|
|
717
|
+
toolStatuses: Object.fromEntries(Object.entries(state.toolStatuses ?? {}).filter(([id]) => ids.has(id))),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TurnReasoningItem, TurnViewState } from "./turnViewTypes.js";
|
|
2
|
+
|
|
3
|
+
export function turnReasoningItems(state: TurnViewState): TurnReasoningItem[] {
|
|
4
|
+
const items = new Map((state.reasoningItems ?? []).map((item) => [item.itemId, item]));
|
|
5
|
+
const saved = new Set(items.keys());
|
|
6
|
+
for (const [index, activity] of state.activities.entries()) {
|
|
7
|
+
if (activity.kind !== "reasoning" || activity.id.startsWith("commentary:")) continue;
|
|
8
|
+
const match = /^reasoning:(.+):(\d+)$/u.exec(activity.id);
|
|
9
|
+
const itemId = match?.[1] ?? activity.id;
|
|
10
|
+
if (saved.has(itemId)) continue;
|
|
11
|
+
const section = match ? Number(match[2]) : 0;
|
|
12
|
+
if (!Number.isSafeInteger(section) || section < 0 || section >= 10_000) continue;
|
|
13
|
+
const item = items.get(itemId) ?? {
|
|
14
|
+
itemId, afterActivityId: state.activities[index - 1]?.id, summary: [], content: [],
|
|
15
|
+
};
|
|
16
|
+
const summary = [...item.summary];
|
|
17
|
+
summary[section] = activity.text;
|
|
18
|
+
items.set(itemId, { ...item, summary });
|
|
19
|
+
}
|
|
20
|
+
return [...items.values()];
|
|
21
|
+
}
|
|
@@ -18,17 +18,32 @@ export interface FileSummary {
|
|
|
18
18
|
|
|
19
19
|
export type TurnActivity =
|
|
20
20
|
| { kind: "assistant"; id: string; text: string }
|
|
21
|
-
| { kind: "user"; id: string; text: string }
|
|
21
|
+
| { kind: "user"; id: string; text: string; localImagePaths?: string[] }
|
|
22
22
|
| { kind: "reasoning"; id: string; text: string }
|
|
23
23
|
| { kind: "tool"; id: string; tool: ToolState };
|
|
24
24
|
|
|
25
|
+
export interface TurnReasoningItem {
|
|
26
|
+
previewRevision?: number;
|
|
27
|
+
itemId: string;
|
|
28
|
+
afterActivityId?: string;
|
|
29
|
+
summary: string[];
|
|
30
|
+
content: string[];
|
|
31
|
+
completed?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
export interface TurnViewState {
|
|
35
|
+
/** Full execution content is stored in the append-only preview journal. */
|
|
36
|
+
previewJournal?: boolean;
|
|
37
|
+
/** Byte cursor for the on-demand preview, never needed for recovery. */
|
|
38
|
+
previewCursor?: number;
|
|
26
39
|
sessionId: string;
|
|
27
40
|
turnId: string;
|
|
28
41
|
agentLabel?: string;
|
|
29
42
|
model?: string;
|
|
43
|
+
modelProvider?: string;
|
|
30
44
|
taskTitle?: string;
|
|
31
45
|
prompt?: string;
|
|
46
|
+
promptImagePaths?: string[];
|
|
32
47
|
projectCwd?: string;
|
|
33
48
|
replyTarget?: MessageReplyTarget;
|
|
34
49
|
status: TurnViewStatus;
|
|
@@ -55,6 +70,7 @@ export interface TurnViewState {
|
|
|
55
70
|
assistantText: string;
|
|
56
71
|
plan: PlanStep[];
|
|
57
72
|
activities: TurnActivity[];
|
|
73
|
+
reasoningItems?: TurnReasoningItem[];
|
|
58
74
|
fullToolOutputs?: Record<string, string>;
|
|
59
75
|
fullToolErrors?: Record<string, string>;
|
|
60
76
|
activitiesTruncated?: boolean;
|
|
@@ -69,4 +85,6 @@ export interface TurnViewState {
|
|
|
69
85
|
approval?: ApprovalRequest;
|
|
70
86
|
finalResponse?: string;
|
|
71
87
|
error?: string;
|
|
88
|
+
historyDetail?: "summary" | "full";
|
|
89
|
+
historyDetailError?: string;
|
|
72
90
|
}
|