@keyou007/agent-bot 0.1.20 → 0.1.21

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 (59) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +15 -5
  3. package/README.zh.md +15 -5
  4. package/dist/codex/CodexEventMapper.js +21 -1
  5. package/dist/codex/CodexEventMapper.js.map +1 -1
  6. package/dist/codex/CodexProcessManager.js +3 -0
  7. package/dist/codex/CodexProcessManager.js.map +1 -1
  8. package/dist/codex/CodexRuntime.js +136 -4
  9. package/dist/codex/CodexRuntime.js.map +1 -1
  10. package/dist/codex/ProviderModels.js +159 -0
  11. package/dist/codex/ProviderModels.js.map +1 -0
  12. package/dist/commands/CommandRouter.js +8 -3
  13. package/dist/commands/CommandRouter.js.map +1 -1
  14. package/dist/console/ConsoleTurnPresenter.js +2 -1
  15. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  16. package/dist/feishu/CardRenderer.js +46 -16
  17. package/dist/feishu/CardRenderer.js.map +1 -1
  18. package/dist/feishu/FeishuTurnPresenter.js +34 -8
  19. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  20. package/dist/index.js +2 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/local-files/LocalFileViewerServer.js +151 -16
  23. package/dist/local-files/LocalFileViewerServer.js.map +1 -1
  24. package/dist/local-files/TurnPreviewPage.js +725 -0
  25. package/dist/local-files/TurnPreviewPage.js.map +1 -0
  26. package/dist/presentation/OutboundRouter.js +6 -1
  27. package/dist/presentation/OutboundRouter.js.map +1 -1
  28. package/dist/presentation/TurnStateReducer.js +142 -3
  29. package/dist/presentation/TurnStateReducer.js.map +1 -1
  30. package/dist/proxy/ProxySessionController.js +186 -80
  31. package/dist/proxy/ProxySessionController.js.map +1 -1
  32. package/dist/state/StateStore.js +7 -0
  33. package/dist/state/StateStore.js.map +1 -1
  34. package/dist/utils/formatStorageSize.js +15 -0
  35. package/dist/utils/formatStorageSize.js.map +1 -0
  36. package/docs/technical-reference.md +7 -1
  37. package/docs/technical-reference.zh.md +7 -1
  38. package/npm-shrinkwrap.json +2 -2
  39. package/package.json +1 -1
  40. package/skills/agent-bot/SKILL.md +5 -1
  41. package/src/codex/CodexEventMapper.ts +31 -1
  42. package/src/codex/CodexProcessManager.ts +4 -0
  43. package/src/codex/CodexRuntime.ts +153 -9
  44. package/src/codex/ProviderModels.ts +185 -0
  45. package/src/commands/CommandRouter.ts +7 -2
  46. package/src/commands/commandTypes.ts +1 -1
  47. package/src/console/ConsoleTurnPresenter.ts +3 -0
  48. package/src/feishu/CardRenderer.ts +47 -16
  49. package/src/feishu/FeishuTurnPresenter.ts +46 -4
  50. package/src/index.ts +2 -0
  51. package/src/local-files/LocalFileViewerServer.ts +166 -17
  52. package/src/local-files/TurnPreviewPage.ts +792 -0
  53. package/src/presentation/OutboundRouter.ts +8 -0
  54. package/src/presentation/TurnStateReducer.ts +175 -2
  55. package/src/presentation/turnViewTypes.ts +13 -0
  56. package/src/proxy/ProxySessionController.ts +186 -67
  57. package/src/runtime/types.ts +18 -1
  58. package/src/state/StateStore.ts +9 -0
  59. package/src/utils/formatStorageSize.ts +13 -0
@@ -16,7 +16,9 @@ export interface TurnPresenter {
16
16
  taskTitle?: string,
17
17
  projectCwd?: string,
18
18
  agentLabel?: string,
19
+ model?: string,
19
20
  ): void;
21
+ updateSessionModel(sessionId: string, model?: string): void;
20
22
  updateSessionTitle(sessionId: string, taskTitle: string): void;
21
23
  unregisterSession(sessionId: string): void;
22
24
  startPendingTurn(
@@ -67,11 +69,13 @@ export class OutboundRouter {
67
69
  taskTitle?: string,
68
70
  projectCwd?: string,
69
71
  agentLabel?: string,
72
+ model?: string,
70
73
  ): void {
71
74
  const route = this.route(contextKey);
72
75
  this.sessionRoutes.set(sessionId, route);
73
76
  this.sessionContextKeys.set(sessionId, contextKey);
74
77
  route.presenter.registerSession(sessionId, contextKey, taskTitle, projectCwd, agentLabel);
78
+ if (model) route.presenter.updateSessionModel(sessionId, model);
75
79
  }
76
80
 
77
81
  getSessionContextKey(sessionId: string): string | undefined {
@@ -90,6 +94,10 @@ export class OutboundRouter {
90
94
  this.sessionRoutes.get(sessionId)?.presenter.updateSessionTitle(sessionId, taskTitle);
91
95
  }
92
96
 
97
+ updateSessionModel(sessionId: string, model?: string): void {
98
+ this.sessionRoutes.get(sessionId)?.presenter.updateSessionModel(sessionId, model);
99
+ }
100
+
93
101
  unregisterSession(sessionId: string): void {
94
102
  const route = this.sessionRoutes.get(sessionId);
95
103
  route?.presenter.unregisterSession(sessionId);
@@ -1,6 +1,7 @@
1
1
  import type { AgentEvent, ToolState } from "../runtime/types.js";
2
2
  import { appendGeneratedImageMarkdown } from "../utils/generatedImageMarkdown.js";
3
3
  import type { MessageReplyTarget } from "../feishu/types.js";
4
+ import { formatStorageSize } from "../utils/formatStorageSize.js";
4
5
  import type { FileSummary, TurnActivity, TurnViewState } from "./turnViewTypes.js";
5
6
 
6
7
  const MAX_TEXT = 6_000;
@@ -17,11 +18,13 @@ export function createTurnViewState(
17
18
  projectCwd?: string,
18
19
  prompt?: string,
19
20
  agentLabel?: string,
21
+ model?: string,
20
22
  ): TurnViewState {
21
23
  return {
22
24
  sessionId,
23
25
  turnId,
24
26
  agentLabel,
27
+ model,
25
28
  taskTitle,
26
29
  prompt,
27
30
  projectCwd,
@@ -31,6 +34,8 @@ export function createTurnViewState(
31
34
  assistantText: "",
32
35
  plan: [],
33
36
  activities: [],
37
+ fullToolOutputs: {},
38
+ fullToolErrors: {},
34
39
  totalToolCount: 0,
35
40
  completedToolCount: 0,
36
41
  failedToolCount: 0,
@@ -60,6 +65,93 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
60
65
  ...state,
61
66
  totalTokens: (state.totalTokens ?? 0) + delta,
62
67
  tokenUsageCumulative: Math.max(previousCumulative ?? 0, cumulativeTokens),
68
+ latestContextTokens: event.contextTokens === undefined
69
+ ? state.latestContextTokens
70
+ : normalizeTokenCount(event.contextTokens),
71
+ };
72
+ }
73
+ case "context_compaction": {
74
+ if (event.phase === "started") {
75
+ const compactionId = event.compactionId ?? `generated:${(state.contextCompactionCount ?? 0) + 1}`;
76
+ const beforeTokens = state.latestContextTokens;
77
+ const text = contextCompactionRunningText(beforeTokens, event.turnCount, event.storageBytes);
78
+ const activityId = `context-compaction:${compactionId}`;
79
+ const activityUpdate = upsertActivity(
80
+ state.activities ?? [],
81
+ (state.activities ?? []).findIndex((activity) => activity.id === activityId),
82
+ { kind: "assistant", id: activityId, text },
83
+ );
84
+ return {
85
+ ...state,
86
+ contextCompactionStatus: "running",
87
+ contextCompactionId: compactionId,
88
+ contextCompactionStartedAt: event.timestampMs ?? Date.now(),
89
+ contextCompactionDurationMs: undefined,
90
+ contextCompactionBeforeTokens: beforeTokens,
91
+ contextCompactionAfterTokens: undefined,
92
+ contextCompactionTurnCount: event.turnCount,
93
+ contextCompactionStorageBytes: event.storageBytes,
94
+ progressText: text,
95
+ activities: activityUpdate.activities,
96
+ activitiesTruncated: state.activitiesTruncated || activityUpdate.truncated,
97
+ };
98
+ }
99
+ const currentId = state.contextCompactionId;
100
+ const repeatedItemCompletion = event.compactionId !== undefined
101
+ && event.compactionId === currentId
102
+ && state.contextCompactionStatus === "completed";
103
+ const repeatedLegacyCompletion = event.compactionId === undefined
104
+ && state.contextCompactionStatus === "completed";
105
+ const repeatedCompletion = repeatedItemCompletion || repeatedLegacyCompletion;
106
+ const contextCompactionCount = repeatedCompletion
107
+ ? state.contextCompactionCount ?? 1
108
+ : (state.contextCompactionCount ?? 0) + 1;
109
+ const compactionId = event.compactionId
110
+ ?? currentId
111
+ ?? `legacy:${contextCompactionCount}`;
112
+ const completedAt = event.timestampMs ?? Date.now();
113
+ const durationMs = repeatedCompletion
114
+ ? state.contextCompactionDurationMs
115
+ : state.contextCompactionStartedAt === undefined
116
+ ? undefined
117
+ : Math.max(0, completedAt - state.contextCompactionStartedAt);
118
+ const beforeTokens = state.contextCompactionBeforeTokens;
119
+ const afterTokens = repeatedCompletion
120
+ ? state.contextCompactionAfterTokens
121
+ : state.latestContextTokens;
122
+ const turnCount = repeatedCompletion
123
+ ? state.contextCompactionTurnCount
124
+ : event.turnCount ?? state.contextCompactionTurnCount;
125
+ const storageBytes = repeatedCompletion
126
+ ? state.contextCompactionStorageBytes
127
+ : event.storageBytes ?? state.contextCompactionStorageBytes;
128
+ const text = contextCompactionCompletedText(
129
+ contextCompactionCount,
130
+ durationMs,
131
+ beforeTokens,
132
+ afterTokens,
133
+ turnCount,
134
+ storageBytes,
135
+ );
136
+ const activityId = `context-compaction:${compactionId}`;
137
+ const activityUpdate = upsertActivity(
138
+ state.activities ?? [],
139
+ (state.activities ?? []).findIndex((activity) => activity.id === activityId),
140
+ { kind: "assistant", id: activityId, text },
141
+ );
142
+ return {
143
+ ...state,
144
+ contextCompactionStatus: "completed",
145
+ contextCompactionCount,
146
+ contextCompactionId: compactionId,
147
+ contextCompactionDurationMs: durationMs,
148
+ contextCompactionBeforeTokens: beforeTokens,
149
+ contextCompactionAfterTokens: afterTokens,
150
+ contextCompactionTurnCount: turnCount,
151
+ contextCompactionStorageBytes: storageBytes,
152
+ progressText: text,
153
+ activities: activityUpdate.activities,
154
+ activitiesTruncated: state.activitiesTruncated || activityUpdate.truncated,
63
155
  };
64
156
  }
65
157
  case "progress": {
@@ -165,10 +257,11 @@ function reduceToolOutputDelta(state: TurnViewState, toolId: string, delta: stri
165
257
  if (!delta) return state;
166
258
  const tool = findTool(state, toolId);
167
259
  if (!tool || tool.status !== "running" || tool.kind !== "command") return state;
260
+ const fullOutput = `${state.fullToolOutputs?.[toolId] ?? tool.output ?? ""}${delta}`;
168
261
  return reduceToolUpdate(state, {
169
262
  ...tool,
170
263
  output: appendBoundedOutput(tool.output, delta),
171
- });
264
+ }, fullOutput);
172
265
  }
173
266
 
174
267
  function findTool(state: TurnViewState, toolId: string): ToolState | undefined {
@@ -197,8 +290,15 @@ function appendBoundedOutput(previous: string | undefined, delta: string): strin
197
290
  return `…${combined.slice(-(MAX_TEXT - 1))}`;
198
291
  }
199
292
 
200
- function reduceToolUpdate(state: TurnViewState, tool: ToolState): TurnViewState {
293
+ function reduceToolUpdate(state: TurnViewState, tool: ToolState, fullOutput?: string): TurnViewState {
201
294
  tool = withToolTiming(state, tool);
295
+ const nextFullOutput = fullOutput ?? tool.output;
296
+ const fullToolOutputs = nextFullOutput === undefined
297
+ ? state.fullToolOutputs
298
+ : { ...state.fullToolOutputs, [tool.id]: nextFullOutput };
299
+ const fullToolErrors = tool.error === undefined
300
+ ? state.fullToolErrors
301
+ : { ...state.fullToolErrors, [tool.id]: tool.error };
202
302
  const tracking = trackToolStatus(state, tool);
203
303
  const bounded = boundTool(tool);
204
304
  const activityUpdate = upsertToolActivity(state.activities ?? [], bounded);
@@ -210,6 +310,8 @@ function reduceToolUpdate(state: TurnViewState, tool: ToolState): TurnViewState
210
310
  return {
211
311
  ...state,
212
312
  ...tracking,
313
+ fullToolOutputs,
314
+ fullToolErrors,
213
315
  status: "tool_running",
214
316
  activeTool: bounded,
215
317
  activities: activityUpdate.activities,
@@ -224,6 +326,8 @@ function reduceToolUpdate(state: TurnViewState, tool: ToolState): TurnViewState
224
326
  return {
225
327
  ...state,
226
328
  ...tracking,
329
+ fullToolOutputs,
330
+ fullToolErrors,
227
331
  status: activeTool ? "tool_running" : "running",
228
332
  activeTool,
229
333
  activities: activityUpdate.activities,
@@ -237,6 +341,8 @@ function reduceToolUpdate(state: TurnViewState, tool: ToolState): TurnViewState
237
341
  return {
238
342
  ...state,
239
343
  ...tracking,
344
+ fullToolOutputs,
345
+ fullToolErrors,
240
346
  status: activeTool ? "tool_running" : "running",
241
347
  activeTool,
242
348
  activities: activityUpdate.activities,
@@ -360,3 +466,70 @@ function bound(value: string): string {
360
466
  function normalizeTokenCount(value: number): number {
361
467
  return Math.max(0, Math.round(value));
362
468
  }
469
+
470
+ function contextCompactionCompletedText(
471
+ count: number,
472
+ durationMs: number | undefined,
473
+ beforeTokens: number | undefined,
474
+ afterTokens: number | undefined,
475
+ turnCount: number | undefined,
476
+ storageBytes: number | undefined,
477
+ ): string {
478
+ const subject = count > 1
479
+ ? `Codex 已完成本轮第 ${count} 次上下文压缩`
480
+ : "Codex 已完成上下文压缩";
481
+ const details = [
482
+ durationMs === undefined ? undefined : `耗时 ${formatCompactionDuration(durationMs)}`,
483
+ contextCompactionSizeText(beforeTokens, afterTokens),
484
+ contextCompactionTaskMetricsText(turnCount, storageBytes),
485
+ ].filter((detail): detail is string => detail !== undefined);
486
+ return `${subject}${details.length > 0 ? ` · ${details.join(" · ")}` : ""},继续处理。`;
487
+ }
488
+
489
+ function contextCompactionRunningText(
490
+ beforeTokens: number | undefined,
491
+ turnCount: number | undefined,
492
+ storageBytes: number | undefined,
493
+ ): string {
494
+ const context = beforeTokens === undefined
495
+ ? "Codex 正在压缩上下文…"
496
+ : `Codex 正在压缩上下文(压缩前 ${formatInteger(beforeTokens)} tokens)…`;
497
+ const metrics = contextCompactionTaskMetricsText(turnCount, storageBytes);
498
+ return metrics ? `${context} · ${metrics}` : context;
499
+ }
500
+
501
+ function contextCompactionTaskMetricsText(
502
+ turnCount: number | undefined,
503
+ storageBytes: number | undefined,
504
+ ): string {
505
+ const turns = turnCount === undefined ? "已执行轮次未知" : `已执行 ${formatInteger(turnCount)} 轮`;
506
+ const storage = storageBytes === undefined ? "磁盘占用未知" : `磁盘占用 ${formatStorageSize(storageBytes)}`;
507
+ return `${turns} · ${storage}`;
508
+ }
509
+
510
+ function contextCompactionSizeText(
511
+ beforeTokens: number | undefined,
512
+ afterTokens: number | undefined,
513
+ ): string | undefined {
514
+ if (afterTokens === undefined) return undefined;
515
+ if (beforeTokens === undefined) return `压缩后 ${formatInteger(afterTokens)} tokens`;
516
+ if (afterTokens >= beforeTokens || beforeTokens === 0) return undefined;
517
+ const reduction = Math.round((1 - afterTokens / beforeTokens) * 100);
518
+ return `上下文 ${formatInteger(beforeTokens)} → ${formatInteger(afterTokens)} tokens(减少 ${reduction}%)`;
519
+ }
520
+
521
+ function formatCompactionDuration(durationMs: number): string {
522
+ const seconds = Math.max(0, durationMs) / 1_000;
523
+ if (seconds < 10) return `${Number(seconds.toFixed(1))}s`;
524
+ if (seconds < 60) return `${Math.round(seconds)}s`;
525
+ const totalSeconds = Math.round(seconds);
526
+ const hours = Math.floor(totalSeconds / 3_600);
527
+ const minutes = Math.floor((totalSeconds % 3_600) / 60);
528
+ const remainingSeconds = totalSeconds % 60;
529
+ const time = `${String(minutes).padStart(2, "0")}:${String(remainingSeconds).padStart(2, "0")}`;
530
+ return hours > 0 ? `${String(hours).padStart(2, "0")}:${time}` : time;
531
+ }
532
+
533
+ function formatInteger(value: number): string {
534
+ return normalizeTokenCount(value).toLocaleString("en-US");
535
+ }
@@ -26,6 +26,7 @@ export interface TurnViewState {
26
26
  sessionId: string;
27
27
  turnId: string;
28
28
  agentLabel?: string;
29
+ model?: string;
29
30
  taskTitle?: string;
30
31
  prompt?: string;
31
32
  projectCwd?: string;
@@ -36,10 +37,22 @@ export interface TurnViewState {
36
37
  durationMs?: number;
37
38
  totalTokens?: number;
38
39
  tokenUsageCumulative?: number;
40
+ contextCompactionStatus?: "running" | "completed";
41
+ contextCompactionCount?: number;
42
+ contextCompactionId?: string;
43
+ latestContextTokens?: number;
44
+ contextCompactionStartedAt?: number;
45
+ contextCompactionDurationMs?: number;
46
+ contextCompactionBeforeTokens?: number;
47
+ contextCompactionAfterTokens?: number;
48
+ contextCompactionTurnCount?: number;
49
+ contextCompactionStorageBytes?: number;
39
50
  progressText?: string;
40
51
  assistantText: string;
41
52
  plan: PlanStep[];
42
53
  activities: TurnActivity[];
54
+ fullToolOutputs?: Record<string, string>;
55
+ fullToolErrors?: Record<string, string>;
43
56
  activitiesTruncated?: boolean;
44
57
  totalToolCount?: number;
45
58
  completedToolCount?: number;