aws-runtime-bridge 1.9.10 → 1.9.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 (132) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +40 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +376 -10
  4. package/dist/adapter/AcodeSdkAdapter.test.js +155 -8
  5. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
  6. package/dist/adapter/ClaudeSdkAdapter.js +17 -1
  7. package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
  8. package/dist/adapter/CodexSdkAdapter.js +15 -1
  9. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  10. package/dist/adapter/OpencodeSdkAdapter.js +56 -4
  11. package/dist/adapter/instance-ai-config-resolver.d.ts +49 -0
  12. package/dist/adapter/instance-ai-config-resolver.d.ts.map +1 -0
  13. package/dist/adapter/instance-ai-config-resolver.js +46 -0
  14. package/dist/adapter/types.d.ts +29 -2
  15. package/dist/adapter/types.d.ts.map +1 -1
  16. package/dist/index.js +5 -1
  17. package/dist/routes/dashboard.d.ts.map +1 -1
  18. package/dist/routes/dashboard.js +749 -15
  19. package/dist/routes/events.d.ts +7 -0
  20. package/dist/routes/events.d.ts.map +1 -0
  21. package/dist/routes/events.js +22 -0
  22. package/dist/routes/file-browser.d.ts +1 -1
  23. package/dist/routes/file-browser.d.ts.map +1 -1
  24. package/dist/routes/file-browser.js +25 -9
  25. package/dist/routes/file-snapshot.d.ts +10 -0
  26. package/dist/routes/file-snapshot.d.ts.map +1 -0
  27. package/dist/routes/file-snapshot.js +156 -0
  28. package/dist/routes/git.d.ts +10 -0
  29. package/dist/routes/git.d.ts.map +1 -1
  30. package/dist/routes/git.js +405 -2
  31. package/dist/routes/git.test.js +39 -2
  32. package/dist/routes/instance.js +3 -1
  33. package/dist/routes/memory.d.ts +3 -0
  34. package/dist/routes/memory.d.ts.map +1 -1
  35. package/dist/routes/memory.js +3 -0
  36. package/dist/routes/terminal.d.ts +17 -3
  37. package/dist/routes/terminal.d.ts.map +1 -1
  38. package/dist/routes/terminal.js +464 -47
  39. package/dist/routes/terminal.test.js +37 -9
  40. package/dist/services/agent-event-store.d.ts +60 -0
  41. package/dist/services/agent-event-store.d.ts.map +1 -0
  42. package/dist/services/agent-event-store.js +68 -0
  43. package/dist/services/event-delta-pusher.d.ts +40 -0
  44. package/dist/services/event-delta-pusher.d.ts.map +1 -0
  45. package/dist/services/event-delta-pusher.js +95 -0
  46. package/dist/services/fileSnapshotStore.d.ts +96 -0
  47. package/dist/services/fileSnapshotStore.d.ts.map +1 -0
  48. package/dist/services/fileSnapshotStore.js +500 -0
  49. package/dist/services/instance-ai-config-service.d.ts +49 -0
  50. package/dist/services/instance-ai-config-service.d.ts.map +1 -0
  51. package/dist/services/instance-ai-config-service.js +174 -0
  52. package/dist/services/panel-auth.d.ts +8 -0
  53. package/dist/services/panel-auth.d.ts.map +1 -1
  54. package/dist/services/panel-auth.js +8 -0
  55. package/dist/services/ring-buffer.d.ts +67 -0
  56. package/dist/services/ring-buffer.d.ts.map +1 -0
  57. package/dist/services/ring-buffer.js +103 -0
  58. package/dist/services/session-output.d.ts +49 -3
  59. package/dist/services/session-output.d.ts.map +1 -1
  60. package/dist/services/session-output.js +115 -2
  61. package/dist/utils/instance-ai-config-debug-logger.d.ts +37 -0
  62. package/dist/utils/instance-ai-config-debug-logger.d.ts.map +1 -0
  63. package/dist/utils/instance-ai-config-debug-logger.js +63 -0
  64. package/dist/utils/logger.d.ts +18 -0
  65. package/dist/utils/logger.d.ts.map +1 -1
  66. package/dist/utils/logger.js +48 -4
  67. package/package/acode/dist/built-in-file-tools.d.ts +1 -1
  68. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  69. package/package/acode/dist/built-in-file-tools.js +5 -3
  70. package/package/acode/dist/builtins/commandArtifacts.d.ts +8 -1
  71. package/package/acode/dist/builtins/commandArtifacts.d.ts.map +1 -1
  72. package/package/acode/dist/builtins/commandArtifacts.js +24 -4
  73. package/package/acode/dist/builtins/shellCommandResult.d.ts +1 -0
  74. package/package/acode/dist/builtins/shellCommandResult.d.ts.map +1 -1
  75. package/package/acode/dist/builtins/shellCommandResult.js +3 -3
  76. package/package/acode/dist/builtins/shellCommandRunner.d.ts +1 -0
  77. package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +1 -1
  78. package/package/acode/dist/builtins/shellCommandRunner.js +1 -0
  79. package/package/acode/dist/builtins/shellOutputDecoder.d.ts +25 -2
  80. package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +1 -1
  81. package/package/acode/dist/builtins/shellOutputDecoder.js +69 -5
  82. package/package/acode/dist/cli.js +2 -0
  83. package/package/acode/dist/runtime.d.ts.map +1 -1
  84. package/package/acode/dist/runtime.js +156 -5
  85. package/package/acode/dist/types.d.ts +1 -0
  86. package/package/acode/dist/types.d.ts.map +1 -1
  87. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +10 -3
  88. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
  89. package/package/aws-client-agent-mcp/dist/agent-client.js +33 -11
  90. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
  91. package/package/aws-client-agent-mcp/dist/context-manager.d.ts +136 -0
  92. package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +1 -0
  93. package/package/aws-client-agent-mcp/dist/context-manager.js +473 -0
  94. package/package/aws-client-agent-mcp/dist/context-manager.js.map +1 -0
  95. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +6 -0
  96. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +1 -0
  97. package/package/aws-client-agent-mcp/dist/context-manager.test.js +307 -0
  98. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -0
  99. package/package/aws-client-agent-mcp/dist/http-client.d.ts +11 -0
  100. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  101. package/package/aws-client-agent-mcp/dist/http-client.js +67 -0
  102. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  103. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +166 -10
  104. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  105. package/package/aws-client-agent-mcp/dist/mcp-server.js +933 -41
  106. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  107. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +150 -130
  108. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  109. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +15 -1
  110. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  111. package/package/aws-client-agent-mcp/dist/mcp-tools.js +62 -2
  112. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  113. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +58 -1
  114. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
  115. package/package/aws-client-agent-mcp/dist/memory-store.js +246 -15
  116. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
  117. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +8 -0
  118. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
  119. package/package/aws-client-agent-mcp/dist/memory-tools.js +106 -2
  120. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
  121. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +7 -1
  122. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
  123. package/package/aws-client-agent-mcp/dist/server-memory-store.js +18 -0
  124. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
  125. package/package/aws-client-agent-mcp/dist/types.d.ts +103 -2
  126. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  127. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  128. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +27 -2
  129. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  130. package/package/aws-client-agent-mcp/dist/websocket-client.js +123 -5
  131. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  132. package/package.json +1 -1
@@ -0,0 +1,174 @@
1
+ /**
2
+ * AI 配置服务
3
+ *
4
+ * 管理 ACode 实例的 AI provider 配置(model/baseURL/apiKey)。
5
+ * 支持三层来源(api > env > file)仲裁,deep-merge 后写入
6
+ * per-instance acode.json 供 ACode runtime 加载。
7
+ */
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { getAcodeConfigFile, getRuntimeHomeDir } from "../config.js";
11
+ import { getInstanceAiConfigDebugLogger } from "../utils/instance-ai-config-debug-logger.js";
12
+ // ── 敏感信息脱敏 ─────────────────────────────────────────
13
+ const SENSITIVE_KEYS = new Set([
14
+ "apiKey", "api_key", "apikey",
15
+ "key", "secret", "token", "password",
16
+ ]);
17
+ /**
18
+ * 脱敏日志中的敏感字段(apiKey / secret / token / password)。
19
+ * 返回新对象,不修改原对象。
20
+ */
21
+ export function redactForLog(obj) {
22
+ const result = { ...obj };
23
+ for (const key of Object.keys(result)) {
24
+ if (SENSITIVE_KEYS.has(key)) {
25
+ const val = result[key];
26
+ if (typeof val === "string" && val.length > 0) {
27
+ result[key] = val.length > 8
28
+ ? `${val.slice(0, 4)}...${val.slice(-4)}`
29
+ : "***";
30
+ }
31
+ }
32
+ else if (typeof result[key] === "object" && result[key] !== null) {
33
+ result[key] = redactForLog(result[key]);
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ // ── 配置准备 ─────────────────────────────────────────────
39
+ function loadEnvLayer() {
40
+ return {
41
+ ...(process.env.ACODE_MODEL ? { model: process.env.ACODE_MODEL } : {}),
42
+ ...(process.env.ACODE_BASE_URL ? { baseURL: process.env.ACODE_BASE_URL } : {}),
43
+ ...(process.env.ACODE_API_KEY ? { apiKey: process.env.ACODE_API_KEY } : {}),
44
+ ...(process.env.ACODE_EFFORT ? { effort: process.env.ACODE_EFFORT } : {}),
45
+ };
46
+ }
47
+ async function loadFileLayer(acodeConfigPath) {
48
+ try {
49
+ const raw = await fs.readFile(acodeConfigPath, "utf-8");
50
+ const parsed = JSON.parse(raw);
51
+ const provider = parsed.provider;
52
+ const def = provider?.default;
53
+ const opts = def?.options;
54
+ return {
55
+ ...(def?.model ? { model: def.model } : {}),
56
+ ...(opts?.baseURL || opts?.baseUrl ? { baseURL: opts.baseURL || opts.baseUrl } : {}),
57
+ ...(opts?.apiKey ? { apiKey: opts.apiKey } : {}),
58
+ };
59
+ }
60
+ catch {
61
+ return {};
62
+ }
63
+ }
64
+ /**
65
+ * 仲裁来源标签。优先级:api > env > file > default
66
+ */
67
+ function resolveSourceLabel(key, apiVal, envVal, fileVal) {
68
+ if (apiVal !== undefined)
69
+ return "api";
70
+ if (envVal !== undefined)
71
+ return "env";
72
+ if (fileVal !== undefined)
73
+ return "file";
74
+ return "default";
75
+ }
76
+ /**
77
+ * 准备 ACode 实例配置。
78
+ *
79
+ * 主流程:
80
+ * 1. 从 apiOverrides(请求体)、env(环境变量)、file(acode.json)三层收集配置
81
+ * 2. 按 api > env > file 优先级仲裁每个字段
82
+ * 3. 将仲裁结果 deep-merge 写入 per-instance acode.json
83
+ * 4. 返回合并后的 config 和 per-instance 文件路径
84
+ */
85
+ export async function prepareAcodeInstanceConfig(options) {
86
+ const runtimeHome = getRuntimeHomeDir();
87
+ const globalConfigPath = getAcodeConfigFile(runtimeHome);
88
+ const perInstanceDir = path.join(runtimeHome, ".aws-bridge", "instances", options.agentId);
89
+ const perInstanceConfigPath = path.join(perInstanceDir, "acode.json");
90
+ // 收集三层配置
91
+ const apiLayer = options.apiOverrides ?? {};
92
+ const envLayer = loadEnvLayer();
93
+ const fileLayer = await loadFileLayer(globalConfigPath);
94
+ // 逐字段仲裁(api > env > file)
95
+ const model = apiLayer.model ?? envLayer.model ?? fileLayer.model;
96
+ const baseURL = apiLayer.baseURL ?? envLayer.baseURL ?? fileLayer.baseURL;
97
+ const apiKey = apiLayer.apiKey ?? envLayer.apiKey ?? fileLayer.apiKey;
98
+ const effort = apiLayer.effort ?? envLayer.effort;
99
+ const resolvedConfig = {};
100
+ if (model)
101
+ resolvedConfig.model = model;
102
+ if (baseURL)
103
+ resolvedConfig.baseURL = baseURL;
104
+ if (apiKey)
105
+ resolvedConfig.apiKey = apiKey;
106
+ if (effort)
107
+ resolvedConfig.effort = effort;
108
+ const source = {
109
+ model: resolveSourceLabel("model", apiLayer.model, envLayer.model, fileLayer.model),
110
+ baseURL: resolveSourceLabel("baseURL", apiLayer.baseURL, envLayer.baseURL, fileLayer.baseURL),
111
+ apiKey: resolveSourceLabel("apiKey", apiLayer.apiKey, envLayer.apiKey, fileLayer.apiKey),
112
+ effort: resolveSourceLabel("effort", apiLayer.effort, envLayer.effort, undefined),
113
+ };
114
+ // 写入 per-instance acode.json(deep-merge 全局配置 + 仲裁结果)
115
+ const dbg = getInstanceAiConfigDebugLogger();
116
+ try {
117
+ await fs.mkdir(perInstanceDir, { recursive: true });
118
+ // 读取全局 acode.json 作为 base
119
+ let baseConfig = {};
120
+ try {
121
+ const globalRaw = await fs.readFile(globalConfigPath, "utf-8");
122
+ baseConfig = JSON.parse(globalRaw);
123
+ }
124
+ catch {
125
+ // 全局文件不存在则用空对象
126
+ }
127
+ // deep-merge provider.default.options
128
+ const mergedConfig = JSON.parse(JSON.stringify(baseConfig));
129
+ const provider = (mergedConfig.provider ?? {});
130
+ const def = (provider.default ?? {});
131
+ const options_ = (def.options ?? {});
132
+ if (model)
133
+ options_.model = model;
134
+ if (baseURL)
135
+ options_.baseURL = baseURL;
136
+ if (apiKey)
137
+ options_.apiKey = apiKey;
138
+ if (effort)
139
+ options_.effort = effort;
140
+ def.options = options_;
141
+ provider.default = def;
142
+ mergedConfig.provider = provider;
143
+ await fs.writeFile(perInstanceConfigPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
144
+ void dbg.log({
145
+ level: "info",
146
+ category: "apply",
147
+ message: "per-instance acode.json 已写入",
148
+ source: "bridge",
149
+ agentId: options.agentId,
150
+ sessionId: options.sessionId,
151
+ details: {
152
+ perInstanceConfigPath,
153
+ source,
154
+ },
155
+ });
156
+ }
157
+ catch (error) {
158
+ const message = error instanceof Error ? error.message : String(error);
159
+ void dbg.log({
160
+ level: "error",
161
+ category: "apply",
162
+ message: `per-instance acode.json 写入失败: ${message}`,
163
+ source: "bridge",
164
+ agentId: options.agentId,
165
+ });
166
+ }
167
+ return {
168
+ instanceConfig: {
169
+ config: resolvedConfig,
170
+ source,
171
+ },
172
+ perInstanceConfigPath,
173
+ };
174
+ }
@@ -12,6 +12,14 @@ export declare function createSession(): string;
12
12
  export declare function validateSession(token: string | undefined | null): boolean;
13
13
  /** 销毁指定会话(登出) */
14
14
  export declare function destroySession(token: string): void;
15
+ /** 列出所有活跃会话(用于管理面板) */
16
+ export declare function getAllSessions(): Array<{
17
+ token: string;
18
+ createdAt: number;
19
+ lastUsedAt: number;
20
+ }>;
21
+ /** 获取当前活跃会话数 */
22
+ export declare function getActiveSessionCount(): number;
15
23
  interface PanelCredentials {
16
24
  username: string;
17
25
  password: string;
@@ -1 +1 @@
1
- {"version":3,"file":"panel-auth.d.ts","sourceRoot":"","sources":["../../src/services/panel-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwCH,yBAAyB;AACzB,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED,kCAAkC;AAClC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAWzE;AAED,iBAAiB;AACjB,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAElD;AAID,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0BD,mCAAmC;AACnC,wBAAgB,mBAAmB,IAAI,gBAAgB,CAWtD;AAED,cAAc;AACd,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAGT;AAED,wBAAwB;AACxB,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAWlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,IAAI,CA8BN"}
1
+ {"version":3,"file":"panel-auth.d.ts","sourceRoot":"","sources":["../../src/services/panel-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwCH,yBAAyB;AACzB,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED,kCAAkC;AAClC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAWzE;AAED,iBAAiB;AACjB,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,uBAAuB;AACvB,wBAAgB,cAAc,IAAI,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAEhG;AAED,gBAAgB;AAChB,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAID,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0BD,mCAAmC;AACnC,wBAAgB,mBAAmB,IAAI,gBAAgB,CAWtD;AAED,cAAc;AACd,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAGT;AAED,wBAAwB;AACxB,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAWlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,IAAI,CA8BN"}
@@ -60,6 +60,14 @@ export function validateSession(token) {
60
60
  export function destroySession(token) {
61
61
  sessions.delete(token);
62
62
  }
63
+ /** 列出所有活跃会话(用于管理面板) */
64
+ export function getAllSessions() {
65
+ return Array.from(sessions.values());
66
+ }
67
+ /** 获取当前活跃会话数 */
68
+ export function getActiveSessionCount() {
69
+ return sessions.size;
70
+ }
63
71
  let cachedCredentials = null;
64
72
  function getConfigFilePath() {
65
73
  return path.join(os.homedir(), ".aws-bridge", "config.json");
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 环状缓冲区
3
+ *
4
+ * 提供固定容量的泛型环状缓冲区,支持追加和按条件合并。
5
+ * 当缓冲区满时自动覆盖最旧的元素。
6
+ */
7
+ export interface RingBufferEntry<T> {
8
+ data: T;
9
+ readonly timestamp: number;
10
+ readonly index: number;
11
+ }
12
+ export type RingBufferSlice<T> = readonly RingBufferEntry<T>[];
13
+ /**
14
+ * 泛型环状缓冲区。
15
+ * 容量固定,写入超过容量后自动覆盖最旧条目。
16
+ * 每个 entry 携带单调递增 index,支持增量查询。
17
+ */
18
+ export declare class RingBuffer<T> {
19
+ private readonly entries;
20
+ private readonly capacity;
21
+ private nextIndex;
22
+ private totalAppended;
23
+ constructor(capacity: number);
24
+ /**
25
+ * 追加一条记录到缓冲区尾部。
26
+ * 缓冲区满时自动覆盖最旧的记录。
27
+ * @returns 新创建的条目
28
+ */
29
+ append(item: T): RingBufferEntry<T>;
30
+ /**
31
+ * 按条件追加或合并到最后一条记录。
32
+ * 如果缓冲区非空且 condition 返回 true,将 item 合并到最后一条记录中;
33
+ * 否则直接追加新记录。
34
+ *
35
+ * @param item 待追加的数据
36
+ * @param condition 判断是否与最后一条合并,接收最后一条条目
37
+ * @param merge 合并函数,接收已有数据和新增数据,返回合并结果
38
+ * @returns 被合并或新创建的条目
39
+ */
40
+ appendOrMerge(item: T, condition: (lastEntry: RingBufferEntry<T>) => boolean, merge: (last: T, next: T) => T): RingBufferEntry<T>;
41
+ /**
42
+ * 返回当前所有条目的快照。
43
+ */
44
+ slice(): RingBufferSlice<T>;
45
+ /**
46
+ * 返回当前条目数。
47
+ */
48
+ get size(): number;
49
+ /**
50
+ * 返回最近 N 条记录。
51
+ */
52
+ getLastN(n: number): RingBufferSlice<T>;
53
+ /**
54
+ * 返回自指定 index(不含)之后的记录。
55
+ * @returns 包含 entries、nextIndex(下一个待分配 index)、oldestIndex(最老条目的 index)
56
+ */
57
+ getSinceIndex(index: number): {
58
+ entries: RingBufferSlice<T>;
59
+ nextIndex: number;
60
+ oldestIndex: number;
61
+ };
62
+ /**
63
+ * 清空缓冲区。
64
+ */
65
+ clear(): void;
66
+ }
67
+ //# sourceMappingURL=ring-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ring-buffer.d.ts","sourceRoot":"","sources":["../../src/services/ring-buffer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;AAE/D;;;;GAIG;AACH,qBAAa,UAAU,CAAC,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,aAAa,CAAK;gBAEd,QAAQ,EAAE,MAAM;IAQ5B;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC;IAanC;;;;;;;;;OASG;IACH,aAAa,CACX,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,OAAO,EACrD,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,GAC7B,eAAe,CAAC,CAAC,CAAC;IAWrB;;OAEG;IACH,KAAK,IAAI,eAAe,CAAC,CAAC,CAAC;IAI3B;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC;IAQvC;;;OAGG;IACH,aAAa,CACX,KAAK,EAAE,MAAM,GACZ;QAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAQ1E;;OAEG;IACH,KAAK,IAAI,IAAI;CAKd"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * 环状缓冲区
3
+ *
4
+ * 提供固定容量的泛型环状缓冲区,支持追加和按条件合并。
5
+ * 当缓冲区满时自动覆盖最旧的元素。
6
+ */
7
+ /**
8
+ * 泛型环状缓冲区。
9
+ * 容量固定,写入超过容量后自动覆盖最旧条目。
10
+ * 每个 entry 携带单调递增 index,支持增量查询。
11
+ */
12
+ export class RingBuffer {
13
+ constructor(capacity) {
14
+ this.nextIndex = 0;
15
+ this.totalAppended = 0;
16
+ if (capacity <= 0) {
17
+ throw new Error("RingBuffer capacity must be positive");
18
+ }
19
+ this.capacity = capacity;
20
+ this.entries = [];
21
+ }
22
+ /**
23
+ * 追加一条记录到缓冲区尾部。
24
+ * 缓冲区满时自动覆盖最旧的记录。
25
+ * @returns 新创建的条目
26
+ */
27
+ append(item) {
28
+ const index = this.totalAppended;
29
+ const entry = { data: item, timestamp: Date.now(), index };
30
+ if (this.entries.length < this.capacity) {
31
+ this.entries.push(entry);
32
+ }
33
+ else {
34
+ this.entries[this.nextIndex % this.capacity] = entry;
35
+ }
36
+ this.nextIndex++;
37
+ this.totalAppended++;
38
+ return entry;
39
+ }
40
+ /**
41
+ * 按条件追加或合并到最后一条记录。
42
+ * 如果缓冲区非空且 condition 返回 true,将 item 合并到最后一条记录中;
43
+ * 否则直接追加新记录。
44
+ *
45
+ * @param item 待追加的数据
46
+ * @param condition 判断是否与最后一条合并,接收最后一条条目
47
+ * @param merge 合并函数,接收已有数据和新增数据,返回合并结果
48
+ * @returns 被合并或新创建的条目
49
+ */
50
+ appendOrMerge(item, condition, merge) {
51
+ if (this.entries.length > 0) {
52
+ const lastEntry = this.entries[this.entries.length - 1];
53
+ if (condition(lastEntry)) {
54
+ lastEntry.data = merge(lastEntry.data, item);
55
+ return lastEntry;
56
+ }
57
+ }
58
+ return this.append(item);
59
+ }
60
+ /**
61
+ * 返回当前所有条目的快照。
62
+ */
63
+ slice() {
64
+ return [...this.entries];
65
+ }
66
+ /**
67
+ * 返回当前条目数。
68
+ */
69
+ get size() {
70
+ return this.entries.length;
71
+ }
72
+ /**
73
+ * 返回最近 N 条记录。
74
+ */
75
+ getLastN(n) {
76
+ if (n <= 0)
77
+ return [];
78
+ const total = this.entries.length;
79
+ if (total === 0)
80
+ return [];
81
+ const count = Math.min(n, total);
82
+ return this.entries.slice(total - count);
83
+ }
84
+ /**
85
+ * 返回自指定 index(不含)之后的记录。
86
+ * @returns 包含 entries、nextIndex(下一个待分配 index)、oldestIndex(最老条目的 index)
87
+ */
88
+ getSinceIndex(index) {
89
+ const oldestIndex = this.entries.length > 0
90
+ ? this.entries[0].index
91
+ : this.totalAppended;
92
+ const entries = this.entries.filter((e) => e.index > index);
93
+ return { entries, nextIndex: this.totalAppended, oldestIndex };
94
+ }
95
+ /**
96
+ * 清空缓冲区。
97
+ */
98
+ clear() {
99
+ this.entries.length = 0;
100
+ this.nextIndex = 0;
101
+ this.totalAppended = 0;
102
+ }
103
+ }
@@ -3,12 +3,58 @@
3
3
  *
4
4
  * 管理终端会话的输出缓冲和刷新机制
5
5
  */
6
- import type { AxiosResponse } from "axios";
6
+ import { type AxiosResponse } from "axios";
7
7
  import type { Session } from "../types.js";
8
+ import { RingBuffer, type RingBufferEntry, type RingBufferSlice } from "./ring-buffer.js";
9
+ /**
10
+ * 桥接侧本地事件的 category 分类。
11
+ * status — 状态变更事件;output — 输出增量事件;question — 询问事件;file_changed — 文件变更事件。
12
+ */
13
+ export type BridgeEventCategory = "status" | "output" | "question" | "file_changed";
14
+ /**
15
+ * 桥接侧 ring buffer 中存储的事件负载。
16
+ *
17
+ * 记录从 sendStatus / sendOutput / sendQuestionRequest / sendFileChanged
18
+ * 产生的非关键时间线数据,供本地增量查询和调试面板使用。
19
+ */
20
+ export interface BridgeEventPayload {
21
+ readonly category: BridgeEventCategory;
22
+ readonly agentId: string;
23
+ readonly sessionId: string | null;
24
+ readonly status?: string;
25
+ readonly output?: string;
26
+ readonly seq?: number;
27
+ readonly questions?: unknown[];
28
+ readonly actionType?: string;
29
+ readonly actionLabel?: string;
30
+ readonly actionDetail?: string;
31
+ readonly actionInput?: string;
32
+ readonly actionResult?: string;
33
+ readonly actionId?: string;
34
+ readonly inputTokens?: number;
35
+ readonly outputTokens?: number;
36
+ readonly sourceEventType?: string;
37
+ readonly logLevel?: string;
38
+ readonly logCategory?: string;
39
+ readonly workspacePath?: string;
40
+ readonly filePath?: string;
41
+ readonly eventType?: string;
42
+ readonly size?: number;
43
+ }
44
+ /** RingBufferEntry 的便捷导出 */
45
+ export type { RingBufferEntry, RingBufferSlice };
46
+ /**
47
+ * 全局桥接侧事件环状缓冲区。
48
+ *
49
+ * 容量 2000 条,适用于记录非关键状态/输出事件。
50
+ * 通过 sendStatus/sendOutput 自动写入,同时保留原有的 server 回调路径。
51
+ */
52
+ export declare const bridgeEventBuffer: RingBuffer<BridgeEventPayload>;
8
53
  interface RuntimeStatusActionInfo {
9
54
  actionType?: string;
10
55
  actionLabel?: string;
11
56
  actionDetail?: string;
57
+ actionInput?: string;
12
58
  actionResult?: string;
13
59
  actionId?: string;
14
60
  }
@@ -40,7 +86,8 @@ export declare const sessions: Map<string, Session>;
40
86
  export declare function sendStatus(agentId: string, sessionId: string | null, status: string, actionInfo?: RuntimeStatusActionInfo, usage?: {
41
87
  inputTokens: number;
42
88
  outputTokens: number;
43
- }, authOptions?: RuntimeCallbackAuthOptions): Promise<AxiosResponse<unknown> | undefined>;
89
+ contextLimit?: number;
90
+ }, authOptions?: RuntimeCallbackAuthOptions, timestamp?: number, sourceEventType?: string): Promise<AxiosResponse<unknown> | undefined>;
44
91
  /**
45
92
  * 发送输出到调度器
46
93
  *
@@ -88,5 +135,4 @@ export declare function scheduleOutputFlush(sessionId: string): void;
88
135
  * @param sessionId - 会话 ID
89
136
  */
90
137
  export declare function flushSessionOutput(sessionId: string): Promise<void>;
91
- export {};
92
138
  //# sourceMappingURL=session-output.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAI3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAgED,aAAa;AACb,eAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,uBAAuB,EACpC,KAAK,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EACrD,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAgE7C;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,qBAAqB,EAChC,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,IAAI,CAAC,CA6Cf;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAAE,EACpB,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,IAAI,CAAC,CAkCf;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCzE"}
1
+ {"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"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"}
@@ -4,10 +4,20 @@
4
4
  * 管理终端会话的输出缓冲和刷新机制
5
5
  */
6
6
  import axios from "axios";
7
- import { requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
8
7
  import { resolveSchedulerBaseUrlFrom } from "../config.js";
8
+ import { requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
9
9
  import { logger } from "../utils/logger.js";
10
+ import { agentEventStore } from "./agent-event-store.js";
11
+ import { scheduleEventPush } from "./event-delta-pusher.js";
12
+ import { RingBuffer } from "./ring-buffer.js";
10
13
  import { getRuntimeAccessToken } from "./runtime-binding.js";
14
+ /**
15
+ * 全局桥接侧事件环状缓冲区。
16
+ *
17
+ * 容量 2000 条,适用于记录非关键状态/输出事件。
18
+ * 通过 sendStatus/sendOutput 自动写入,同时保留原有的 server 回调路径。
19
+ */
20
+ export const bridgeEventBuffer = new RingBuffer(2000);
11
21
  function runtimeAuthHeaders(options, callbackBaseUrl = resolveRuntimeCallbackBaseUrl(options)) {
12
22
  const token = options?.runtimeAccessToken ||
13
23
  getRuntimeAccessToken(undefined, callbackBaseUrl) ||
@@ -59,6 +69,8 @@ async function tryRefreshRuntimeToken(serverBaseUrl) {
59
69
  tokenRefreshPromise = null;
60
70
  }
61
71
  }
72
+ /** 单条 output 事件的文本长度上限(需求 4.1.3 #1),超过则截断。 */
73
+ const MAX_SINGLE_OUTPUT_CHARS = 8000;
62
74
  /** 单个会话待发送输出上限,避免调度器不可达时内存无限增长。 */
63
75
  const MAX_PENDING_OUTPUT_CHARS = 64 * 1024;
64
76
  /** 单次回调最大发送量,避免一次 POST 携带过大的终端噪声。 */
@@ -73,8 +85,12 @@ export const sessions = new Map();
73
85
  * @param status - 状态
74
86
  * @param actionInfo - 可选的动作详情
75
87
  */
76
- export async function sendStatus(agentId, sessionId, status, actionInfo, usage, authOptions) {
88
+ export async function sendStatus(agentId, sessionId, status, actionInfo, usage, authOptions, timestamp, sourceEventType) {
77
89
  const payload = { agentId, sessionId, status };
90
+ // 添加时间戳(仅当提供了有效时间戳时才传递,保持原始事件时间顺序)
91
+ if (timestamp !== undefined) {
92
+ payload.timestamp = timestamp;
93
+ }
78
94
  // 添加细粒度动作信息
79
95
  if (actionInfo) {
80
96
  if (actionInfo.actionType)
@@ -83,6 +99,8 @@ export async function sendStatus(agentId, sessionId, status, actionInfo, usage,
83
99
  payload.actionLabel = actionInfo.actionLabel;
84
100
  if (actionInfo.actionDetail)
85
101
  payload.actionDetail = actionInfo.actionDetail;
102
+ if (actionInfo.actionInput)
103
+ payload.actionInput = actionInfo.actionInput;
86
104
  if (actionInfo.actionResult)
87
105
  payload.actionResult = actionInfo.actionResult;
88
106
  if (actionInfo.actionId)
@@ -92,6 +110,13 @@ export async function sendStatus(agentId, sessionId, status, actionInfo, usage,
92
110
  if (usage) {
93
111
  payload.inputTokens = usage.inputTokens;
94
112
  payload.outputTokens = usage.outputTokens;
113
+ if (usage.contextLimit) {
114
+ payload.contextLimit = usage.contextLimit;
115
+ }
116
+ }
117
+ // 添加来源事件类型(用于前端时间线灰度区分,如 thinking / ai_request)
118
+ if (sourceEventType) {
119
+ payload.sourceEventType = sourceEventType;
95
120
  }
96
121
  const callbackBaseUrl = resolveRuntimeCallbackBaseUrl(authOptions);
97
122
  const headers = runtimeAuthHeaders(authOptions, callbackBaseUrl);
@@ -129,6 +154,28 @@ export async function sendStatus(agentId, sessionId, status, actionInfo, usage,
129
154
  });
130
155
  return undefined;
131
156
  }
157
+ finally {
158
+ // 不论 server 回调成功或失败,都在桥接侧本地记录状态事件
159
+ const eventPayload = {
160
+ category: "status",
161
+ agentId,
162
+ sessionId,
163
+ status,
164
+ actionType: actionInfo?.actionType,
165
+ actionLabel: actionInfo?.actionLabel,
166
+ actionDetail: actionInfo?.actionDetail,
167
+ actionInput: actionInfo?.actionInput,
168
+ actionResult: actionInfo?.actionResult,
169
+ actionId: actionInfo?.actionId,
170
+ inputTokens: usage?.inputTokens,
171
+ outputTokens: usage?.outputTokens,
172
+ sourceEventType,
173
+ };
174
+ bridgeEventBuffer.append(eventPayload);
175
+ // 同步写入 per-agent 事件存储并调度差量推送
176
+ const entry = agentEventStore.append(agentId, eventPayload);
177
+ scheduleEventPush(agentId, entry, authOptions);
178
+ }
132
179
  }
133
180
  /**
134
181
  * 发送输出到调度器
@@ -174,6 +221,47 @@ export async function sendOutput(agentId, output, sessionId, seq, metadata, auth
174
221
  error,
175
222
  });
176
223
  }
224
+ finally {
225
+ // 不论 server 回调成功或失败,都在桥接侧本地记录输出事件
226
+ // 需求 4.1.3 #1:截断超长 output,确保单条事件体积可控
227
+ const truncatedOutput = output.length > MAX_SINGLE_OUTPUT_CHARS
228
+ ? `${output.slice(0, MAX_SINGLE_OUTPUT_CHARS)}...(truncated)`
229
+ : output;
230
+ const eventPayload = {
231
+ category: "output",
232
+ agentId,
233
+ sessionId,
234
+ output: truncatedOutput,
235
+ seq,
236
+ sourceEventType: metadata?.sourceEventType,
237
+ logLevel: metadata?.logLevel,
238
+ logCategory: metadata?.logCategory,
239
+ };
240
+ // output 事件合并写入:同 session + 同 sourceEventType + 10s 窗口内追加到同一条
241
+ bridgeEventBuffer.appendOrMerge(eventPayload, (lastEntry) => {
242
+ const last = lastEntry.data;
243
+ return (last.category === "output" &&
244
+ last.sessionId === sessionId &&
245
+ last.sourceEventType === metadata?.sourceEventType &&
246
+ Date.now() - lastEntry.timestamp <= 10_000);
247
+ }, (lastData, newData) => {
248
+ // 内容去重:如果新内容已包含在已合并内容中,跳过拼接防止重复
249
+ const existingOutput = lastData.output || "";
250
+ const newOutput = newData.output || "";
251
+ const merged = existingOutput.includes(newOutput)
252
+ ? existingOutput
253
+ : `${existingOutput}${newOutput}`;
254
+ return {
255
+ ...lastData,
256
+ output: merged.length > MAX_SINGLE_OUTPUT_CHARS
257
+ ? `${merged.slice(0, MAX_SINGLE_OUTPUT_CHARS)}...(truncated)`
258
+ : merged,
259
+ };
260
+ });
261
+ // 同步写入 per-agent 事件存储(同样合并)并调度差量推送
262
+ const entry = agentEventStore.appendOrMergeOutput(agentId, eventPayload);
263
+ scheduleEventPush(agentId, entry, authOptions);
264
+ }
177
265
  }
178
266
  /**
179
267
  * 发送 AskUserQuestion 事件到调度器。
@@ -210,6 +298,18 @@ export async function sendQuestionRequest(agentId, sessionId, questions, authOpt
210
298
  }
211
299
  // ignore transient callback failures
212
300
  }
301
+ finally {
302
+ // 记录 question 事件到 per-agent 存储
303
+ const eventPayload = {
304
+ category: "question",
305
+ agentId,
306
+ sessionId,
307
+ questions,
308
+ };
309
+ bridgeEventBuffer.append(eventPayload);
310
+ const entry = agentEventStore.append(agentId, eventPayload);
311
+ scheduleEventPush(agentId, entry, authOptions);
312
+ }
213
313
  }
214
314
  /**
215
315
  * 发送工作区文件变更事件到调度器,由调度器通过 WebSocket 通知前端刷新预览。
@@ -241,6 +341,19 @@ export async function sendFileChanged(payload) {
241
341
  }
242
342
  // ignore transient callback failures
243
343
  }
344
+ finally {
345
+ // 记录 file_changed 事件到 per-agent 存储
346
+ const eventPayload = {
347
+ category: "file_changed",
348
+ agentId: "",
349
+ sessionId: null,
350
+ workspacePath: payload.workspacePath,
351
+ filePath: payload.filePath,
352
+ eventType: payload.eventType,
353
+ size: payload.size,
354
+ };
355
+ bridgeEventBuffer.append(eventPayload);
356
+ }
244
357
  }
245
358
  /**
246
359
  * 追加会话输出到有界缓冲区。