@keyou007/agent-bot 0.1.24-alpha.2 → 0.1.24-alpha.4

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +33 -5
  3. package/README.zh.md +32 -5
  4. package/dist/acp/AcpJsonRpcConnection.js +3 -1
  5. package/dist/acp/AcpJsonRpcConnection.js.map +1 -1
  6. package/dist/acp/AcpSessionManager.js +0 -14
  7. package/dist/acp/AcpSessionManager.js.map +1 -1
  8. package/dist/acp/acpTypes.js.map +1 -1
  9. package/dist/cli/SelfUpdateRunner.js.map +1 -1
  10. package/dist/cli/SelfUpdater.js +2 -0
  11. package/dist/cli/SelfUpdater.js.map +1 -1
  12. package/dist/codex/AppServerConnection.js +3 -1
  13. package/dist/codex/AppServerConnection.js.map +1 -1
  14. package/dist/codex/CodexEventMapper.js +28 -1
  15. package/dist/codex/CodexEventMapper.js.map +1 -1
  16. package/dist/codex/CodexRuntime.js +92 -0
  17. package/dist/codex/CodexRuntime.js.map +1 -1
  18. package/dist/commands/CommandRouter.js +5 -3
  19. package/dist/commands/CommandRouter.js.map +1 -1
  20. package/dist/feishu/CardRenderer.js +45 -23
  21. package/dist/feishu/CardRenderer.js.map +1 -1
  22. package/dist/feishu/FeishuConnector.js +2 -0
  23. package/dist/feishu/FeishuConnector.js.map +1 -1
  24. package/dist/feishu/FeishuMessageClient.js +44 -0
  25. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  26. package/dist/feishu/FeishuTurnPresenter.js +114 -37
  27. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  28. package/dist/feishu/RecentMessageContext.js +70 -0
  29. package/dist/feishu/RecentMessageContext.js.map +1 -0
  30. package/dist/index.js +62 -24
  31. package/dist/index.js.map +1 -1
  32. package/dist/local-files/DiagramPreview.js +171 -0
  33. package/dist/local-files/DiagramPreview.js.map +1 -0
  34. package/dist/local-files/LocalFileViewerServer.js +141 -3
  35. package/dist/local-files/LocalFileViewerServer.js.map +1 -1
  36. package/dist/local-files/PreviewSyntaxHighlight.js +57 -0
  37. package/dist/local-files/PreviewSyntaxHighlight.js.map +1 -0
  38. package/dist/local-files/TurnPreviewPage.js +497 -62
  39. package/dist/local-files/TurnPreviewPage.js.map +1 -1
  40. package/dist/presentation/OutboundRouter.js +18 -9
  41. package/dist/presentation/OutboundRouter.js.map +1 -1
  42. package/dist/presentation/TurnStateReducer.js +155 -6
  43. package/dist/presentation/TurnStateReducer.js.map +1 -1
  44. package/dist/presentation/turnReasoning.js +23 -0
  45. package/dist/presentation/turnReasoning.js.map +1 -0
  46. package/dist/proxy/ProxySessionController.js +466 -38
  47. package/dist/proxy/ProxySessionController.js.map +1 -1
  48. package/dist/state/StateStore.js +43 -2
  49. package/dist/state/StateStore.js.map +1 -1
  50. package/dist/state/TurnPreviewJournal.js +469 -0
  51. package/dist/state/TurnPreviewJournal.js.map +1 -0
  52. package/dist/state/migrations.js +10 -0
  53. package/dist/state/migrations.js.map +1 -1
  54. package/dist/updates/AutomaticUpdatePreparer.js +16 -7
  55. package/dist/updates/AutomaticUpdatePreparer.js.map +1 -1
  56. package/dist/updates/ManualUpdateController.js +102 -0
  57. package/dist/updates/ManualUpdateController.js.map +1 -0
  58. package/dist/updates/PublishedRelease.js +42 -4
  59. package/dist/updates/PublishedRelease.js.map +1 -1
  60. package/dist/utils/ProtocolLineWarning.js +15 -0
  61. package/dist/utils/ProtocolLineWarning.js.map +1 -0
  62. package/docs/technical-reference.md +21 -1
  63. package/docs/technical-reference.zh.md +21 -1
  64. package/npm-shrinkwrap.json +1401 -10
  65. package/package.json +3 -1
  66. package/skills/agent-bot/SKILL.md +12 -1
  67. package/src/acp/AcpJsonRpcConnection.ts +3 -1
  68. package/src/acp/AcpSessionManager.ts +0 -17
  69. package/src/acp/acpTypes.ts +0 -1
  70. package/src/cli/SelfUpdateRunner.ts +1 -0
  71. package/src/cli/SelfUpdater.ts +2 -0
  72. package/src/codex/AppServerConnection.ts +3 -1
  73. package/src/codex/CodexEventMapper.ts +29 -1
  74. package/src/codex/CodexRuntime.ts +87 -0
  75. package/src/commands/CommandRouter.ts +4 -3
  76. package/src/commands/commandTypes.ts +1 -0
  77. package/src/feishu/CardRenderer.ts +43 -21
  78. package/src/feishu/FeishuConnector.ts +2 -0
  79. package/src/feishu/FeishuMessageClient.ts +44 -0
  80. package/src/feishu/FeishuTurnPresenter.ts +107 -31
  81. package/src/feishu/RecentMessageContext.ts +90 -0
  82. package/src/feishu/types.ts +5 -0
  83. package/src/index.ts +62 -22
  84. package/src/local-files/DiagramPreview.ts +173 -0
  85. package/src/local-files/LocalFileViewerServer.ts +129 -2
  86. package/src/local-files/PreviewSyntaxHighlight.ts +58 -0
  87. package/src/local-files/TurnPreviewPage.ts +537 -59
  88. package/src/presentation/OutboundRouter.ts +25 -7
  89. package/src/presentation/TurnStateReducer.ts +151 -7
  90. package/src/presentation/turnReasoning.ts +21 -0
  91. package/src/presentation/turnViewTypes.ts +19 -1
  92. package/src/proxy/ProxySessionController.ts +457 -27
  93. package/src/runtime/types.ts +21 -0
  94. package/src/state/StateStore.ts +45 -2
  95. package/src/state/TurnPreviewJournal.ts +398 -0
  96. package/src/state/migrations.ts +10 -0
  97. package/src/updates/AutomaticUpdatePreparer.ts +21 -7
  98. package/src/updates/ManualUpdateController.ts +116 -0
  99. package/src/updates/PublishedRelease.ts +49 -3
  100. 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 reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnViewState {
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
  }