aws-runtime-bridge 1.7.50 → 1.8.1

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 (65) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +30 -3
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +461 -105
  4. package/dist/adapter/AcodeSdkAdapter.test.js +1282 -93
  5. package/dist/adapter/ClaudeSdkAdapter.test.js +31 -8
  6. package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
  7. package/dist/adapter/CodexSdkAdapter.js +2 -0
  8. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  9. package/dist/adapter/OpencodeSdkAdapter.js +2 -0
  10. package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
  11. package/dist/adapter/adapter.test.js +2 -2
  12. package/dist/adapter/idle-poll-loop.d.ts +4 -4
  13. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  14. package/dist/adapter/idle-poll-loop.js +3 -3
  15. package/dist/adapter/idle-poll-loop.test.js +15 -1
  16. package/dist/adapter/types.d.ts +25 -6
  17. package/dist/adapter/types.d.ts.map +1 -1
  18. package/dist/adapter/types.js +226 -111
  19. package/dist/adapter/types.test.js +10 -3
  20. package/dist/routes/ai-sources.js +5 -1
  21. package/dist/routes/ai-sources.test.js +21 -0
  22. package/dist/routes/instance.d.ts.map +1 -1
  23. package/dist/routes/instance.js +6 -7
  24. package/dist/routes/runtime-binding.js +2 -2
  25. package/dist/routes/terminal.d.ts +22 -5
  26. package/dist/routes/terminal.d.ts.map +1 -1
  27. package/dist/routes/terminal.js +522 -170
  28. package/dist/routes/terminal.test.js +497 -208
  29. package/dist/services/mcp-launch-binding-queue.d.ts +1 -0
  30. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  31. package/dist/services/mcp-launch-binding-queue.js +8 -2
  32. package/dist/services/mcp-launch-binding-queue.test.js +47 -4
  33. package/dist/services/session-output.d.ts +14 -6
  34. package/dist/services/session-output.d.ts.map +1 -1
  35. package/dist/services/session-output.js +29 -14
  36. package/dist/services/session-output.test.js +93 -39
  37. package/package/acode/dist/built-in-file-tools.d.ts +1 -1
  38. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  39. package/package/acode/dist/built-in-file-tools.js +388 -50
  40. package/package/acode/dist/runtime.d.ts +11 -3
  41. package/package/acode/dist/runtime.d.ts.map +1 -1
  42. package/package/acode/dist/runtime.js +902 -190
  43. package/package/acode/dist/types.d.ts +18 -5
  44. package/package/acode/dist/types.d.ts.map +1 -1
  45. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  46. package/package/aws-client-agent-mcp/dist/mcp-server.js +3 -2
  47. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  48. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +11 -9
  49. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  50. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
  51. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +1 -0
  52. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
  53. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +2 -0
  54. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  55. package/package/aws-client-agent-mcp/dist/status-reporter.js +31 -3
  56. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  57. package/package/aws-client-agent-mcp/dist/types.d.ts +1 -1
  58. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  59. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  60. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  61. package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -0
  62. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  63. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -0
  64. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
  65. package/package.json +1 -1
@@ -1,345 +1,634 @@
1
- import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { describe, expect, it } from 'vitest';
5
- import { SDK_PROVIDER_DEFINITIONS } from '../adapter/SdkProviderSpi.js';
6
- import { buildClaudeCodeLaunchConfig, buildRuntimeEnv, buildToolResultTimelineActionInfo, createTerminalOutputDecoder, decodeTerminalOutputChunk, evaluatePersistedSessionReuse, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveSdkInputMessage, resolveTerminalOutputEncoding, resolveSdkProviderId, resolveSdkSessionDisplayStatus, resolveStatusChangeUsage, } from './terminal.js';
7
- describe('terminal route validation', () => {
8
- it('requires agentId and workspacePath for start', () => {
1
+ import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { SDK_PROVIDER_DEFINITIONS } from "../adapter/SdkProviderSpi.js";
6
+ import { buildAcodeCommandOutputPath, buildClaudeCodeLaunchConfig, buildRuntimeEnv, buildSdkOutputMetadata, buildToolResultTimelineActionInfo, countTerminalOutputLines, createTerminalOutputDecoder, decodeTerminalOutputChunk, evaluatePersistedSessionReuse, formatCommandInactivityTimeoutNotice, formatCommandOutputSpilloverNotice, formatTerminalPrompt, formatSdkOutputEvent, normalizeTerminalCommandInput, parseTerminalDirectoryChangeTarget, resolveSdkInputMessage, resolveTerminalOutputEncoding, resolveSdkProviderId, resolveSdkSessionDisplayStatus, resolveRuntimeStatusFromProviderEvent, resolveStatusChangeUsage, } from "./terminal.js";
7
+ describe("terminal route validation", () => {
8
+ it("requires agentId and workspacePath for start", () => {
9
9
  const validateStartRequest = (body) => {
10
10
  if (!body.agentId || !body.workspacePath)
11
- return { valid: false, error: 'agentId and workspacePath are required' };
11
+ return {
12
+ valid: false,
13
+ error: "agentId and workspacePath are required",
14
+ };
12
15
  return { valid: true };
13
16
  };
14
17
  expect(validateStartRequest({}).valid).toBe(false);
15
- expect(validateStartRequest({ agentId: 'agent-1', workspacePath: '/path' }).valid).toBe(true);
16
- });
17
- it('uses SDK default command when not provided', () => {
18
- const resolveCommand = (command) => command || 'acode';
19
- expect(resolveCommand(undefined)).toBe('acode');
20
- expect(resolveCommand('codex')).toBe('codex');
21
- });
22
- it('uses SDK launch mode for unsupported mode values', () => {
23
- const resolveLaunchMode = (mode) => (mode === 'sdk' ? 'sdk' : 'sdk');
24
- expect(resolveLaunchMode(undefined)).toBe('sdk');
25
- expect(resolveLaunchMode('sdk')).toBe('sdk');
26
- expect(resolveLaunchMode('unsupported')).toBe('sdk');
27
- });
28
- it('keeps shortcut mode available for manual key injection', () => {
29
- const buildInputResponse = (mode) => mode === 'shortcut' ? { ok: true, mode: 'shortcut' } : { ok: true, mode: 'sdk' };
30
- expect(buildInputResponse('shortcut')).toEqual({ ok: true, mode: 'shortcut' });
31
- expect(buildInputResponse(undefined)).toEqual({ ok: true, mode: 'sdk' });
32
- });
33
- it('does not downgrade unsupported shortcut input to normal SDK messages', () => {
34
- const buildShortcutFallbackResponse = (hasShortcutSupport) => (hasShortcutSupport
35
- ? { status: 200, body: { ok: true, mode: 'shortcut' } }
36
- : { status: 400, body: { error: 'shortcut input is not supported by this SDK provider' } });
18
+ expect(validateStartRequest({ agentId: "agent-1", workspacePath: "/path" })
19
+ .valid).toBe(true);
20
+ });
21
+ it("uses SDK default command when not provided", () => {
22
+ const resolveCommand = (command) => command || "acode";
23
+ expect(resolveCommand(undefined)).toBe("acode");
24
+ expect(resolveCommand("codex")).toBe("codex");
25
+ });
26
+ it("uses SDK launch mode for unsupported mode values", () => {
27
+ const resolveLaunchMode = (mode) => mode === "sdk" ? "sdk" : "sdk";
28
+ expect(resolveLaunchMode(undefined)).toBe("sdk");
29
+ expect(resolveLaunchMode("sdk")).toBe("sdk");
30
+ expect(resolveLaunchMode("unsupported")).toBe("sdk");
31
+ });
32
+ it("keeps shortcut mode available for manual key injection", () => {
33
+ const buildInputResponse = (mode) => mode === "shortcut"
34
+ ? { ok: true, mode: "shortcut" }
35
+ : { ok: true, mode: "sdk" };
36
+ expect(buildInputResponse("shortcut")).toEqual({
37
+ ok: true,
38
+ mode: "shortcut",
39
+ });
40
+ expect(buildInputResponse(undefined)).toEqual({ ok: true, mode: "sdk" });
41
+ });
42
+ it("does not downgrade unsupported shortcut input to normal SDK messages", () => {
43
+ const buildShortcutFallbackResponse = (hasShortcutSupport) => hasShortcutSupport
44
+ ? { status: 200, body: { ok: true, mode: "shortcut" } }
45
+ : {
46
+ status: 400,
47
+ body: {
48
+ error: "shortcut input is not supported by this SDK provider",
49
+ },
50
+ };
37
51
  expect(buildShortcutFallbackResponse(false)).toEqual({
38
52
  status: 400,
39
- body: { error: 'shortcut input is not supported by this SDK provider' },
53
+ body: { error: "shortcut input is not supported by this SDK provider" },
40
54
  });
41
55
  });
42
- it('requires sessionId and input for SDK terminal input', () => {
56
+ it("requires sessionId and input for SDK terminal input", () => {
43
57
  const validateInputRequest = (body) => {
44
58
  if (!body.sessionId || body.input === undefined || body.input === null) {
45
- return { valid: false, error: 'sessionId and input are required' };
59
+ return { valid: false, error: "sessionId and input are required" };
46
60
  }
47
61
  return { valid: true };
48
62
  };
49
63
  expect(validateInputRequest({}).valid).toBe(false);
50
- expect(validateInputRequest({ sessionId: 'session-1', input: 'continue' }).valid).toBe(true);
51
- });
52
- it('keeps resize contract as SDK no-op compatibility endpoint', () => {
53
- const buildResizeResponse = (cols, rows) => ({ ok: true, mode: 'sdk', noop: true, cols, rows });
54
- expect(buildResizeResponse(120, 30)).toEqual({ ok: true, mode: 'sdk', noop: true, cols: 120, rows: 30 });
64
+ expect(validateInputRequest({ sessionId: "session-1", input: "continue" }).valid).toBe(true);
65
+ });
66
+ it("keeps resize contract as SDK no-op compatibility endpoint", () => {
67
+ const buildResizeResponse = (cols, rows) => ({
68
+ ok: true,
69
+ mode: "sdk",
70
+ noop: true,
71
+ cols,
72
+ rows,
73
+ });
74
+ expect(buildResizeResponse(120, 30)).toEqual({
75
+ ok: true,
76
+ mode: "sdk",
77
+ noop: true,
78
+ cols: 120,
79
+ rows: 30,
80
+ });
55
81
  });
56
- it('requires sessionId for stop', () => {
82
+ it("requires sessionId for stop", () => {
57
83
  const validateStopRequest = (body) => {
58
84
  if (!body.sessionId)
59
- return { valid: false, error: 'sessionId is required' };
85
+ return { valid: false, error: "sessionId is required" };
60
86
  return { valid: true };
61
87
  };
62
88
  expect(validateStopRequest({}).valid).toBe(false);
63
- expect(validateStopRequest({ sessionId: 'session-1' }).valid).toBe(true);
89
+ expect(validateStopRequest({ sessionId: "session-1" }).valid).toBe(true);
90
+ });
91
+ });
92
+ describe("SDK startup idle command ordering", () => {
93
+ it("sets idle commands immediately after adapter startup and before PID probing or persistence", async () => {
94
+ const calls = [];
95
+ const adapter = {
96
+ async startSession() {
97
+ calls.push("startSession");
98
+ },
99
+ setIdleCommands() {
100
+ calls.push("setIdleCommands");
101
+ },
102
+ };
103
+ const startFlow = async () => {
104
+ await adapter.startSession();
105
+ adapter.setIdleCommands();
106
+ calls.push("probePid");
107
+ calls.push("persistSession");
108
+ };
109
+ await startFlow();
110
+ expect(calls).toEqual([
111
+ "startSession",
112
+ "setIdleCommands",
113
+ "probePid",
114
+ "persistSession",
115
+ ]);
64
116
  });
65
117
  });
66
- describe('terminal configuration', () => {
67
- it('builds correct SDK runtime environment', () => {
68
- const env = buildRuntimeEnv('agent-123', undefined, { PATH: '/usr/bin' });
69
- expect(env.AWS_AGENT_ID).toBe('agent-123');
118
+ describe("terminal configuration", () => {
119
+ it("builds correct SDK runtime environment", () => {
120
+ const env = buildRuntimeEnv("agent-123", undefined, { PATH: "/usr/bin" });
121
+ expect(env.AWS_AGENT_ID).toBe("agent-123");
70
122
  expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBeUndefined();
71
- expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe('true');
72
- });
73
- it('merges launch env overrides without allowing agent identity override', () => {
74
- const env = buildRuntimeEnv('agent-123', '/workspace', { PATH: '/usr/bin' }, {
75
- ANTHROPIC_BASE_URL: ' https://api.example.com ',
76
- ANTHROPIC_AUTH_TOKEN: 'secret-token',
77
- ANTHROPIC_MODEL: 'claude-sonnet-4-5',
78
- AWS_AGENT_ID: 'malicious-agent',
79
- EMPTY_VALUE: ' ',
123
+ expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe("true");
124
+ });
125
+ it("merges launch env overrides without allowing agent identity override", () => {
126
+ const env = buildRuntimeEnv("agent-123", "/workspace", { PATH: "/usr/bin" }, {
127
+ ANTHROPIC_BASE_URL: " https://api.example.com ",
128
+ ANTHROPIC_AUTH_TOKEN: "secret-token",
129
+ ANTHROPIC_MODEL: "claude-sonnet-4-5",
130
+ AWS_AGENT_ID: "malicious-agent",
131
+ EMPTY_VALUE: " ",
80
132
  });
81
- expect(env.ANTHROPIC_BASE_URL).toBe('https://api.example.com');
82
- expect(env.ANTHROPIC_AUTH_TOKEN).toBe('secret-token');
83
- expect(env.ANTHROPIC_MODEL).toBe('claude-sonnet-4-5');
133
+ expect(env.ANTHROPIC_BASE_URL).toBe("https://api.example.com");
134
+ expect(env.ANTHROPIC_AUTH_TOKEN).toBe("secret-token");
135
+ expect(env.ANTHROPIC_MODEL).toBe("claude-sonnet-4-5");
84
136
  expect(env.EMPTY_VALUE).toBeUndefined();
85
- expect(env.AWS_AGENT_ID).toBe('agent-123');
86
- expect(env.AWS_WORKSPACE_PATH).toBe('/workspace');
137
+ expect(env.AWS_AGENT_ID).toBe("agent-123");
138
+ expect(env.AWS_WORKSPACE_PATH).toBe("/workspace");
87
139
  });
88
- it('merges saved ClaudeCode AI settings into SDK launch env with request overrides taking precedence', () => {
140
+ it("merges saved ClaudeCode AI settings into SDK launch env with request overrides taking precedence", () => {
89
141
  const launchConfig = buildClaudeCodeLaunchConfig({
90
142
  env: {
91
- ANTHROPIC_BASE_URL: 'https://saved.example.com',
92
- ANTHROPIC_AUTH_TOKEN: 'sk-saved',
93
- ANTHROPIC_MODEL: 'claude-saved',
94
- EMPTY_SAVED: ' ',
143
+ ANTHROPIC_BASE_URL: "https://saved.example.com",
144
+ ANTHROPIC_AUTH_TOKEN: "sk-saved",
145
+ ANTHROPIC_MODEL: "claude-saved",
146
+ EMPTY_SAVED: " ",
95
147
  },
96
148
  }, {
97
- ANTHROPIC_MODEL: ' claude-request ',
98
- EXTRA_VALUE: 'enabled',
149
+ ANTHROPIC_MODEL: " claude-request ",
150
+ EXTRA_VALUE: "enabled",
99
151
  });
100
152
  expect(launchConfig).toEqual({
101
153
  envOverrides: {
102
- ANTHROPIC_BASE_URL: 'https://saved.example.com',
103
- ANTHROPIC_AUTH_TOKEN: 'sk-saved',
104
- ANTHROPIC_MODEL: 'claude-request',
105
- EXTRA_VALUE: 'enabled',
154
+ ANTHROPIC_BASE_URL: "https://saved.example.com",
155
+ ANTHROPIC_AUTH_TOKEN: "sk-saved",
156
+ ANTHROPIC_MODEL: "claude-request",
157
+ EXTRA_VALUE: "enabled",
106
158
  },
107
- model: 'claude-request',
159
+ model: "claude-request",
108
160
  });
109
161
  });
110
- it('uses CODEX_MODEL from request env overrides as SDK launch model', () => {
162
+ it("uses CODEX_MODEL from request env overrides as SDK launch model", () => {
111
163
  const launchConfig = buildClaudeCodeLaunchConfig(null, {
112
- OPENAI_API_KEY: 'sk-codex',
113
- CODEX_MODEL: ' gpt-5.3-codex ',
164
+ OPENAI_API_KEY: "sk-codex",
165
+ CODEX_MODEL: " gpt-5.3-codex ",
114
166
  });
115
167
  expect(launchConfig).toEqual({
116
168
  envOverrides: {
117
- OPENAI_API_KEY: 'sk-codex',
118
- CODEX_MODEL: 'gpt-5.3-codex',
169
+ OPENAI_API_KEY: "sk-codex",
170
+ CODEX_MODEL: "gpt-5.3-codex",
119
171
  },
120
- model: 'gpt-5.3-codex',
172
+ model: "gpt-5.3-codex",
121
173
  });
122
174
  });
123
175
  });
124
- describe('persisted SDK session reuse', () => {
125
- it('does not reuse a persisted session without a PID', () => {
176
+ describe("persisted SDK session reuse", () => {
177
+ it("does not reuse a persisted session without a PID", () => {
126
178
  const decision = evaluatePersistedSessionReuse({}, () => true);
127
- expect(decision).toEqual({ reusable: false, reason: 'missing-pid' });
179
+ expect(decision).toEqual({ reusable: false, reason: "missing-pid" });
128
180
  });
129
- it('does not reuse a persisted session when its PID is no longer running', () => {
181
+ it("does not reuse a persisted session when its PID is no longer running", () => {
130
182
  const decision = evaluatePersistedSessionReuse({ pid: 1234 }, () => false);
131
- expect(decision).toEqual({ reusable: false, reason: 'dead-pid' });
183
+ expect(decision).toEqual({ reusable: false, reason: "dead-pid" });
132
184
  });
133
- it('reuses a persisted session only when its PID is still running for externally controllable providers', () => {
134
- const decision = evaluatePersistedSessionReuse({ pid: 1234, command: 'claude' }, () => true);
135
- expect(decision).toEqual({ reusable: true, reason: 'active-pid' });
185
+ it("reuses a persisted session only when its PID is still running for externally controllable providers", () => {
186
+ const decision = evaluatePersistedSessionReuse({ pid: 1234, command: "claude" }, () => true);
187
+ expect(decision).toEqual({ reusable: true, reason: "active-pid" });
136
188
  });
137
- it('does not reuse ACode persisted sessions because their control channel is in-process only', () => {
138
- const decision = evaluatePersistedSessionReuse({ pid: 1234, command: 'acode' }, () => true);
139
- expect(decision).toEqual({ reusable: false, reason: 'in-process-provider' });
189
+ it("does not reuse ACode persisted sessions because their control channel is in-process only", () => {
190
+ const decision = evaluatePersistedSessionReuse({ pid: 1234, command: "acode" }, () => true);
191
+ expect(decision).toEqual({
192
+ reusable: false,
193
+ reason: "in-process-provider",
194
+ });
140
195
  });
141
196
  });
142
- describe('SDK session display status', () => {
143
- it('prefers adapter fine-grained status over coarse running fallback', () => {
144
- expect(resolveSdkSessionDisplayStatus('waiting_input', 'running')).toBe('waiting_input');
145
- expect(resolveSdkSessionDisplayStatus('thinking', 'running')).toBe('thinking');
197
+ describe("SDK session display status", () => {
198
+ it("prefers adapter fine-grained status over coarse running fallback", () => {
199
+ expect(resolveSdkSessionDisplayStatus("waiting_input", "running")).toBe("waiting_input");
200
+ expect(resolveSdkSessionDisplayStatus("thinking", "running")).toBe("thinking");
146
201
  });
147
- it('uses in-memory runtime status when adapter status is temporarily unavailable', () => {
148
- expect(resolveSdkSessionDisplayStatus(undefined, 'tool_using')).toBe('tool_using');
202
+ it("uses in-memory runtime status when adapter status is temporarily unavailable", () => {
203
+ expect(resolveSdkSessionDisplayStatus(undefined, "tool_using")).toBe("tool_using");
149
204
  });
150
- it('falls back to running only when no fine-grained state exists', () => {
151
- expect(resolveSdkSessionDisplayStatus(undefined, undefined)).toBe('running');
205
+ it("falls back to running only when no fine-grained state exists", () => {
206
+ expect(resolveSdkSessionDisplayStatus(undefined, undefined)).toBe("running");
152
207
  });
153
208
  });
154
- describe('terminal command helpers', () => {
155
- it('expands a whole mini-term clipboard paste file path into SDK input content', async () => {
156
- const tempDir = await mkdtemp(path.join(os.tmpdir(), 'terminal-input-'));
157
- const clipboardDir = path.join(tempDir, 'mini-term-clipboard');
209
+ describe("SDK provider event runtime status mapping", () => {
210
+ const event = (type, data = {}) => ({
211
+ type,
212
+ sessionId: "session-1",
213
+ timestamp: new Date().toISOString(),
214
+ data,
215
+ });
216
+ it("maps text and thinking stream events to thinking status", () => {
217
+ expect(resolveRuntimeStatusFromProviderEvent(event("thinking"))?.status).toBe("thinking");
218
+ expect(resolveRuntimeStatusFromProviderEvent(event("text_delta"))?.status).toBe("thinking");
219
+ });
220
+ it("maps AI request events to thinking status with request details", () => {
221
+ const state = resolveRuntimeStatusFromProviderEvent(event("ai_request", {
222
+ model: "test-model",
223
+ endpoint: "https://example.invalid/v1/chat/completions",
224
+ stream: true,
225
+ messageCount: 3,
226
+ toolCount: 2,
227
+ lastUserMessagePreview: "inspect status",
228
+ requestBody: '{"model":"test-model"}',
229
+ }));
230
+ expect(state?.status).toBe("thinking");
231
+ expect(state?.actionInfo).toMatchObject({
232
+ actionType: "ai_request",
233
+ actionLabel: "AI请求",
234
+ });
235
+ expect(state?.actionInfo?.actionDetail).toContain("模型: test-model");
236
+ expect(state?.actionInfo?.actionDetail).toContain("接口: https://example.invalid/v1/chat/completions");
237
+ expect(state?.actionInfo?.actionDetail).toContain("最后请求: inspect status");
238
+ expect(state?.actionInfo?.actionDetail).toContain('请求体:\n{"model":"test-model"}');
239
+ });
240
+ it("maps tool start events to tool_using with action info", () => {
241
+ const state = resolveRuntimeStatusFromProviderEvent(event("tool_use_start", {
242
+ toolName: "mcp__aws__my_task",
243
+ toolUseId: "tool-1",
244
+ }));
245
+ expect(state?.status).toBe("tool_using");
246
+ expect(state?.actionInfo?.actionType).toBe("mcp");
247
+ expect(state?.actionInfo?.actionId).toBe("tool-1");
248
+ });
249
+ it("maps turn completion to idle waiting_input status", () => {
250
+ expect(resolveRuntimeStatusFromProviderEvent(event("turn_complete"))?.status).toBe("waiting_input");
251
+ });
252
+ it("keeps idle tool action info with waiting_input status for display", () => {
253
+ const state = resolveRuntimeStatusFromProviderEvent(event("tool_use_start", {
254
+ actionType: "idle",
255
+ actionLabel: "监听消息",
256
+ actionDetail: "poll_message",
257
+ toolUseId: "tool-idle-1",
258
+ }));
259
+ expect(state?.status).toBe("waiting_input");
260
+ expect(state?.actionInfo).toMatchObject({
261
+ actionType: "idle",
262
+ actionLabel: "监听消息",
263
+ actionDetail: "poll_message",
264
+ actionId: "tool-idle-1",
265
+ });
266
+ });
267
+ });
268
+ describe("terminal command helpers", () => {
269
+ it("expands a whole mini-term clipboard paste file path into SDK input content", async () => {
270
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), "terminal-input-"));
271
+ const clipboardDir = path.join(tempDir, "mini-term-clipboard");
158
272
  await mkdir(clipboardDir, { recursive: true });
159
- const pasteFile = path.join(clipboardDir, 'paste-1780491671137.txt');
160
- await writeFile(pasteFile, 'real pasted prompt', 'utf-8');
161
- await expect(resolveSdkInputMessage(`"${pasteFile}"`)).resolves.toBe('real pasted prompt');
273
+ const pasteFile = path.join(clipboardDir, "paste-1780491671137.txt");
274
+ await writeFile(pasteFile, "real pasted prompt", "utf-8");
275
+ await expect(resolveSdkInputMessage(`"${pasteFile}"`)).resolves.toBe("real pasted prompt");
162
276
  });
163
- it('keeps explicit commands that mention mini-term clipboard files unchanged', async () => {
277
+ it("keeps explicit commands that mention mini-term clipboard files unchanged", async () => {
164
278
  const command = 'type "C:\\Users\\ZHUANG~1\\AppData\\Local\\Temp\\mini-term-clipboard\\paste-1780491671137.txt"';
165
279
  await expect(resolveSdkInputMessage(command)).resolves.toBe(command);
166
280
  });
167
- it('normalizes carriage-return terminal input into executable command text', () => {
168
- expect(normalizeTerminalCommandInput('npm test\r')).toBe('npm test');
169
- expect(normalizeTerminalCommandInput(' pwd \n')).toBe('pwd');
281
+ it("normalizes carriage-return terminal input into executable command text", () => {
282
+ expect(normalizeTerminalCommandInput("npm test\r")).toBe("npm test");
283
+ expect(normalizeTerminalCommandInput(" pwd \n")).toBe("pwd");
170
284
  });
171
- it('parses cd commands without treating other shell commands as directory changes', () => {
172
- expect(parseTerminalDirectoryChangeTarget('cd')).toBeNull();
173
- expect(parseTerminalDirectoryChangeTarget('cd /d "D:\\code\\repo"')).toBe('D:\\code\\repo');
174
- expect(parseTerminalDirectoryChangeTarget('npm run build')).toBeUndefined();
285
+ it("parses cd commands without treating other shell commands as directory changes", () => {
286
+ expect(parseTerminalDirectoryChangeTarget("cd")).toBeNull();
287
+ expect(parseTerminalDirectoryChangeTarget('cd /d "D:\\code\\repo"')).toBe("D:\\code\\repo");
288
+ expect(parseTerminalDirectoryChangeTarget("npm run build")).toBeUndefined();
175
289
  });
176
- it('formats the working directory prompt as terminal output text', () => {
177
- expect(formatTerminalPrompt('D:\\code\\repo')).toBe('\x1b[36mD:\\code\\repo>\x1b[0m ');
290
+ it("formats the working directory prompt as terminal output text", () => {
291
+ expect(formatTerminalPrompt("D:\\code\\repo")).toBe("\x1b[36mD:\\code\\repo>\x1b[0m ");
178
292
  });
179
- it('defaults Windows terminal command output to GB18030 decoding', () => {
180
- expect(resolveTerminalOutputEncoding('win32', {})).toBe('gb18030');
181
- expect(resolveTerminalOutputEncoding('linux', {})).toBe('utf-8');
182
- expect(resolveTerminalOutputEncoding('win32', { AWS_TERMINAL_OUTPUT_ENCODING: 'utf-8' })).toBe('utf-8');
293
+ it("defaults Windows terminal command output to GB18030 decoding", () => {
294
+ expect(resolveTerminalOutputEncoding("win32", {})).toBe("gb18030");
295
+ expect(resolveTerminalOutputEncoding("linux", {})).toBe("utf-8");
296
+ expect(resolveTerminalOutputEncoding("win32", {
297
+ AWS_TERMINAL_OUTPUT_ENCODING: "utf-8",
298
+ })).toBe("utf-8");
183
299
  });
184
- it('decodes GB18030 Chinese output across chunk boundaries', () => {
185
- const decoder = createTerminalOutputDecoder('gb18030');
300
+ it("decodes GB18030 Chinese output across chunk boundaries", () => {
301
+ const decoder = createTerminalOutputDecoder("gb18030");
186
302
  const first = decodeTerminalOutputChunk(decoder, Buffer.from([0xd6]));
187
303
  const second = decodeTerminalOutputChunk(decoder, Buffer.from([0xd0, 0xce, 0xc4]));
188
304
  const flushed = decodeTerminalOutputChunk(decoder, undefined, true);
189
- expect(`${first}${second}${flushed}`).toBe('中文');
305
+ expect(`${first}${second}${flushed}`).toBe("中文");
306
+ });
307
+ it("counts terminal output lines without treating a trailing line break as an extra line", () => {
308
+ expect(countTerminalOutputLines("")).toBe(0);
309
+ expect(countTerminalOutputLines("one")).toBe(1);
310
+ expect(countTerminalOutputLines("one\ntwo\n")).toBe(2);
311
+ expect(countTerminalOutputLines("one\r\ntwo\rthree")).toBe(3);
312
+ });
313
+ it("builds ACode command output spillover paths under the workspace cmd-dist directory", () => {
314
+ const outputPath = buildAcodeCommandOutputPath(path.join("D:", "code", "repo"), new Date(2026, 1, 3, 4, 5, 6, 7));
315
+ expect(outputPath).toBe(path.join("D:", "code", "repo", ".agentswork", "acode", "cmd-dist", "2026-02-03", "04", "05", "20260203-040506-007-cmd.out"));
316
+ });
317
+ it("formats ACode long-output and inactivity notices for terminal users", () => {
318
+ expect(formatCommandOutputSpilloverNotice("/repo/out.txt", 100)).toContain("命令输出超过 100 行");
319
+ expect(formatCommandOutputSpilloverNotice("/repo/out.txt", 100)).toContain("/repo/out.txt");
320
+ expect(formatCommandInactivityTimeoutNotice(60_000)).toContain("超过 60 秒没有新的输出");
190
321
  });
191
322
  });
192
- describe('resolveSdkProviderId', () => {
193
- it('exposes codex through the SDK provider SPI', () => {
194
- expect(SDK_PROVIDER_DEFINITIONS.map((definition) => definition.runtimeMode)).toContain('codex');
195
- expect(SDK_PROVIDER_DEFINITIONS.find((definition) => definition.runtimeMode === 'codex')).toMatchObject({
196
- providerId: 'codex',
197
- defaultCommand: 'codex',
323
+ describe("resolveSdkProviderId", () => {
324
+ it("exposes codex through the SDK provider SPI", () => {
325
+ expect(SDK_PROVIDER_DEFINITIONS.map((definition) => definition.runtimeMode)).toContain("codex");
326
+ expect(SDK_PROVIDER_DEFINITIONS.find((definition) => definition.runtimeMode === "codex")).toMatchObject({
327
+ providerId: "codex",
328
+ defaultCommand: "codex",
198
329
  });
199
330
  });
200
- it('resolves codex commands to codex provider', () => {
201
- expect(resolveSdkProviderId('codex')).toBe('codex');
202
- expect(resolveSdkProviderId('npx codex --model gpt-5-codex')).toBe('codex');
331
+ it("resolves codex commands to codex provider", () => {
332
+ expect(resolveSdkProviderId("codex")).toBe("codex");
333
+ expect(resolveSdkProviderId("npx codex --model gpt-5-codex")).toBe("codex");
203
334
  });
204
- it('resolves opencode commands to opencode provider', () => {
205
- expect(resolveSdkProviderId('opencode')).toBe('opencode');
335
+ it("resolves opencode commands to opencode provider", () => {
336
+ expect(resolveSdkProviderId("opencode")).toBe("opencode");
206
337
  });
207
- it('falls back to acode for unknown or empty commands while keeping claude mapped to claude-code', () => {
208
- expect(resolveSdkProviderId('claude')).toBe('claude-code');
209
- expect(resolveSdkProviderId('custom-binary')).toBe('acode');
210
- expect(resolveSdkProviderId(undefined)).toBe('acode');
338
+ it("falls back to acode for unknown or empty commands while keeping claude mapped to claude-code", () => {
339
+ expect(resolveSdkProviderId("claude")).toBe("claude-code");
340
+ expect(resolveSdkProviderId("acode")).toBe("acode");
341
+ expect(resolveSdkProviderId("custom-binary")).toBe("acode");
342
+ expect(resolveSdkProviderId(undefined)).toBe("acode");
211
343
  });
212
344
  });
213
- describe('SDK status usage forwarding', () => {
214
- it('keeps accumulated usage from status-change action info', () => {
215
- expect(resolveStatusChangeUsage({ usage: { inputTokens: 12000, outputTokens: 345 } })).toEqual({
345
+ describe("SDK status usage forwarding", () => {
346
+ it("keeps accumulated usage from status-change action info", () => {
347
+ expect(resolveStatusChangeUsage({
348
+ usage: { inputTokens: 12000, outputTokens: 345 },
349
+ })).toEqual({
216
350
  inputTokens: 12000,
217
351
  outputTokens: 345,
218
352
  });
219
353
  expect(resolveStatusChangeUsage({ usage: { inputTokens: 0, outputTokens: 0 } })).toBeUndefined();
220
- expect(resolveStatusChangeUsage({ actionType: 'mcp', actionLabel: 'Calling MCP' })).toBeUndefined();
354
+ expect(resolveStatusChangeUsage({
355
+ actionType: "mcp",
356
+ actionLabel: "Calling MCP",
357
+ })).toBeUndefined();
358
+ });
359
+ it("forwards structured AI response body through action result", () => {
360
+ const runtimeStatus = resolveRuntimeStatusFromProviderEvent({
361
+ type: "turn_complete",
362
+ sessionId: "session-1",
363
+ timestamp: new Date(0).toISOString(),
364
+ data: {
365
+ usage: { inputTokens: 12, outputTokens: 4 },
366
+ responseBody: '{"choices":[{"message":{"content":"done"}}]}',
367
+ finishReason: "stop",
368
+ },
369
+ });
370
+ expect(runtimeStatus).toEqual({
371
+ status: "waiting_input",
372
+ actionInfo: {
373
+ actionType: "ai_request",
374
+ actionLabel: "AI响应",
375
+ actionResult: '{"choices":[{"message":{"content":"done"}}]}',
376
+ usage: { inputTokens: 12, outputTokens: 4 },
377
+ },
378
+ });
379
+ });
380
+ it("includes file paths and commands in tool status action details", () => {
381
+ expect(resolveRuntimeStatusFromProviderEvent({
382
+ type: "tool_use_start",
383
+ sessionId: "session-1",
384
+ timestamp: new Date(0).toISOString(),
385
+ data: {
386
+ toolName: "builtin/read_file",
387
+ toolInput: { path: "src/views/App.vue" },
388
+ toolUseId: "tool-read",
389
+ },
390
+ })).toEqual({
391
+ status: "tool_using",
392
+ actionInfo: {
393
+ actionType: "read_file",
394
+ actionLabel: "读取文件",
395
+ actionDetail: "src/views/App.vue",
396
+ actionId: "tool-read",
397
+ },
398
+ });
399
+ expect(resolveRuntimeStatusFromProviderEvent({
400
+ type: "tool_use_start",
401
+ sessionId: "session-1",
402
+ timestamp: new Date(0).toISOString(),
403
+ data: {
404
+ toolName: "bash",
405
+ toolInput: { command: "npm run test:agent-timeline-panel" },
406
+ toolUseId: "tool-bash",
407
+ },
408
+ })).toEqual({
409
+ status: "tool_using",
410
+ actionInfo: {
411
+ actionType: "bash",
412
+ actionLabel: "执行命令",
413
+ actionDetail: "npm run test:agent-timeline-panel",
414
+ actionId: "tool-bash",
415
+ },
416
+ });
221
417
  });
222
418
  });
223
- describe('SDK output forwarding', () => {
224
- it('formats text and thinking provider events for the terminal output callback path', () => {
419
+ describe("SDK output forwarding", () => {
420
+ it("formats assistant text provider events for the terminal output callback path", () => {
225
421
  const textEvent = {
226
- type: 'text_delta',
227
- sessionId: 'session-1',
422
+ type: "text_delta",
423
+ sessionId: "session-1",
228
424
  timestamp: new Date(0).toISOString(),
229
- data: { text: 'hello' },
425
+ data: { text: "hello" },
230
426
  };
427
+ expect(formatSdkOutputEvent(textEvent)).toBe("hello");
428
+ });
429
+ it("formats thinking provider events as typed realtime output chunks", () => {
231
430
  const thinkingEvent = {
232
- type: 'thinking',
233
- sessionId: 'session-1',
431
+ type: "thinking",
432
+ sessionId: "session-1",
234
433
  timestamp: new Date(0).toISOString(),
235
- data: { text: 'thinking...' },
434
+ data: { text: "internal prompt or reasoning" },
236
435
  };
237
- expect(formatSdkOutputEvent(textEvent)).toBe('hello');
238
- expect(formatSdkOutputEvent(thinkingEvent)).toBe('thinking...');
436
+ expect(formatSdkOutputEvent(thinkingEvent)).toBe("internal prompt or reasoning");
239
437
  });
240
- it('formats SDK errors as terminal output and ignores non-output events', () => {
438
+ it("formats SDK errors as terminal output and ignores non-output events", () => {
241
439
  const errorEvent = {
242
- type: 'error',
243
- sessionId: 'session-1',
440
+ type: "error",
441
+ sessionId: "session-1",
244
442
  timestamp: new Date(0).toISOString(),
245
- data: { text: 'boom' },
443
+ data: { text: "boom" },
246
444
  };
247
445
  const toolEvent = {
248
- type: 'tool_use_start',
249
- sessionId: 'session-1',
446
+ type: "tool_use_start",
447
+ sessionId: "session-1",
250
448
  timestamp: new Date(0).toISOString(),
251
- data: { toolName: 'read_file' },
449
+ data: { toolName: "read_file" },
252
450
  };
253
- expect(formatSdkOutputEvent(errorEvent)).toBe('\r\n[SDK Error] boom\r\n');
451
+ expect(formatSdkOutputEvent(errorEvent)).toBe("\r\n[SDK Error] boom\r\n");
254
452
  expect(formatSdkOutputEvent(toolEvent)).toBeUndefined();
255
453
  });
454
+ it("formats AI request events as immediate realtime ACode output", () => {
455
+ const requestEvent = {
456
+ type: "ai_request",
457
+ sessionId: "session-1",
458
+ timestamp: new Date(0).toISOString(),
459
+ data: {
460
+ model: "test-model",
461
+ lastUserMessagePreview: "inspect status",
462
+ },
463
+ };
464
+ expect(formatSdkOutputEvent(requestEvent)).toBe("\r\n[ACode] 正在请求模型 test-model:inspect status\r\n");
465
+ });
466
+ it("builds ACode log metadata for output callbacks", () => {
467
+ const requestEvent = {
468
+ type: "ai_request",
469
+ sessionId: "session-1",
470
+ timestamp: new Date(0).toISOString(),
471
+ data: { model: "test-model", endpoint: "openai-compatible", toolCount: 2 },
472
+ };
473
+ const errorEvent = {
474
+ type: "error",
475
+ sessionId: "session-1",
476
+ timestamp: new Date(0).toISOString(),
477
+ data: { text: "provider failed" },
478
+ };
479
+ expect(buildSdkOutputMetadata(requestEvent)).toEqual({
480
+ sourceEventType: "ai_request",
481
+ logCategory: "acode",
482
+ logLevel: "info",
483
+ logMessage: "AI request started: test-model @ openai-compatible, tools=2",
484
+ });
485
+ expect(buildSdkOutputMetadata(errorEvent)).toEqual({
486
+ sourceEventType: "error",
487
+ logCategory: "acode",
488
+ logLevel: "error",
489
+ logMessage: "provider failed",
490
+ });
491
+ });
492
+ it("does not mark streaming text chunks as standalone ACode log rows", () => {
493
+ const metadata = buildSdkOutputMetadata({
494
+ type: "text_delta",
495
+ sessionId: "session-1",
496
+ data: { text: "word" },
497
+ });
498
+ expect(metadata).toEqual({
499
+ sourceEventType: "text_delta",
500
+ logCategory: "acode",
501
+ });
502
+ });
503
+ it("includes file paths and commands in ACode tool log metadata", () => {
504
+ const readEvent = {
505
+ type: "tool_use_start",
506
+ sessionId: "session-1",
507
+ timestamp: new Date(0).toISOString(),
508
+ data: {
509
+ toolName: "read_file",
510
+ toolInput: { path: "src/index.ts" },
511
+ },
512
+ };
513
+ const editEvent = {
514
+ type: "tool_use_start",
515
+ sessionId: "session-1",
516
+ timestamp: new Date(0).toISOString(),
517
+ data: {
518
+ toolName: "edit",
519
+ toolInput: { file_path: "src/app.ts" },
520
+ },
521
+ };
522
+ const writeEvent = {
523
+ type: "tool_use_end",
524
+ sessionId: "session-1",
525
+ timestamp: new Date(0).toISOString(),
526
+ data: {
527
+ toolName: "write_file",
528
+ toolInput: { path: "src/new-file.ts" },
529
+ },
530
+ };
531
+ const bashEvent = {
532
+ type: "tool_use_start",
533
+ sessionId: "session-1",
534
+ timestamp: new Date(0).toISOString(),
535
+ data: {
536
+ toolName: "bash",
537
+ toolInput: { command: "npm run build -- --mode production" },
538
+ },
539
+ };
540
+ expect(buildSdkOutputMetadata(readEvent).logMessage).toBe("开始读取文件: src/index.ts");
541
+ expect(buildSdkOutputMetadata(editEvent).logMessage).toBe("开始编辑文件: src/app.ts");
542
+ expect(buildSdkOutputMetadata(writeEvent).logMessage).toBe("完成写入文件: src/new-file.ts");
543
+ expect(buildSdkOutputMetadata(bashEvent).logMessage).toBe("开始执行命令: npm run build -- --mode production");
544
+ });
256
545
  });
257
- describe('SDK tool result timeline action info', () => {
258
- it('keeps MCP tool results on MCP timeline events', () => {
546
+ describe("SDK tool result timeline action info", () => {
547
+ it("keeps MCP tool results on MCP timeline events", () => {
259
548
  const event = {
260
- type: 'tool_use_end',
261
- sessionId: 'session-1',
549
+ type: "tool_use_end",
550
+ sessionId: "session-1",
262
551
  timestamp: new Date(0).toISOString(),
263
552
  data: {
264
- toolName: 'mcp__aws-mcp__get_profile',
265
- toolUseId: 'tool-1',
553
+ toolName: "mcp__aws-mcp__get_profile",
554
+ toolUseId: "tool-1",
266
555
  toolResult: '{"displayName":"Agent"}',
267
556
  },
268
557
  };
269
558
  expect(buildToolResultTimelineActionInfo(event)).toEqual({
270
- actionType: 'mcp',
271
- actionLabel: 'MCP返回',
272
- actionDetail: 'mcp__aws-mcp__get_profile',
273
- actionId: 'tool-1',
559
+ actionType: "mcp",
560
+ actionLabel: "MCP返回",
561
+ actionDetail: "mcp__aws-mcp__get_profile",
562
+ actionId: "tool-1",
274
563
  actionResult: '{"displayName":"Agent"}',
275
564
  });
276
565
  });
277
- it('forwards bash tool results to command timeline events', () => {
566
+ it("forwards bash tool results to command timeline events", () => {
278
567
  const event = {
279
- type: 'tool_use_end',
280
- sessionId: 'session-1',
568
+ type: "tool_use_end",
569
+ sessionId: "session-1",
281
570
  timestamp: new Date(0).toISOString(),
282
571
  data: {
283
- toolName: 'bash',
284
- toolInput: { command: 'npm run build' },
285
- toolUseId: 'tool-2',
286
- toolResult: 'build passed',
572
+ toolName: "bash",
573
+ toolInput: { command: "npm run build" },
574
+ toolUseId: "tool-2",
575
+ toolResult: "build passed",
287
576
  },
288
577
  };
289
578
  expect(buildToolResultTimelineActionInfo(event)).toEqual({
290
- actionType: 'bash',
291
- actionLabel: '执行命令',
292
- actionDetail: 'npm run build',
293
- actionId: 'tool-2',
294
- actionResult: 'build passed',
579
+ actionType: "bash",
580
+ actionLabel: "执行命令",
581
+ actionDetail: "npm run build",
582
+ actionId: "tool-2",
583
+ actionResult: "build passed",
295
584
  });
296
585
  });
297
- it('forwards read tool results to file-read timeline events', () => {
586
+ it("forwards read tool results to file-read timeline events", () => {
298
587
  const event = {
299
- type: 'tool_use_end',
300
- sessionId: 'session-1',
588
+ type: "tool_use_end",
589
+ sessionId: "session-1",
301
590
  timestamp: new Date(0).toISOString(),
302
591
  data: {
303
- toolName: 'read_file',
304
- toolInput: { path: 'src/index.ts' },
305
- actionId: 'tool-3',
306
- actionResult: 'export const ok = true',
592
+ toolName: "read_file",
593
+ toolInput: { path: "src/index.ts" },
594
+ actionId: "tool-3",
595
+ actionResult: "export const ok = true",
307
596
  },
308
597
  };
309
598
  expect(buildToolResultTimelineActionInfo(event)).toEqual({
310
- actionType: 'read_file',
311
- actionLabel: '读取文件',
312
- actionDetail: 'src/index.ts',
313
- actionId: 'tool-3',
314
- actionResult: 'export const ok = true',
599
+ actionType: "read_file",
600
+ actionLabel: "读取文件",
601
+ actionDetail: "src/index.ts",
602
+ actionId: "tool-3",
603
+ actionResult: "export const ok = true",
315
604
  });
316
605
  });
317
- it('keeps MCP-named filesystem results on semantic file-read timeline events', () => {
606
+ it("keeps MCP-named filesystem results on semantic file-read timeline events", () => {
318
607
  const event = {
319
- type: 'tool_use_end',
320
- sessionId: 'session-1',
608
+ type: "tool_use_end",
609
+ sessionId: "session-1",
321
610
  timestamp: new Date(0).toISOString(),
322
611
  data: {
323
- toolName: 'mcp__filesystem__read_file',
324
- toolInput: { path: 'src/index.ts' },
325
- toolUseId: 'tool-4',
326
- toolResult: 'export const ok = true',
612
+ toolName: "mcp__filesystem__read_file",
613
+ toolInput: { path: "src/index.ts" },
614
+ toolUseId: "tool-4",
615
+ toolResult: "export const ok = true",
327
616
  },
328
617
  };
329
618
  expect(buildToolResultTimelineActionInfo(event)).toEqual({
330
- actionType: 'read_file',
331
- actionLabel: '读取文件',
332
- actionDetail: 'src/index.ts',
333
- actionId: 'tool-4',
334
- actionResult: 'export const ok = true',
619
+ actionType: "read_file",
620
+ actionLabel: "读取文件",
621
+ actionDetail: "src/index.ts",
622
+ actionId: "tool-4",
623
+ actionResult: "export const ok = true",
335
624
  });
336
625
  });
337
- it('ignores tool result events without displayable result content', () => {
626
+ it("ignores tool result events without displayable result content", () => {
338
627
  const event = {
339
- type: 'tool_use_end',
340
- sessionId: 'session-1',
628
+ type: "tool_use_end",
629
+ sessionId: "session-1",
341
630
  timestamp: new Date(0).toISOString(),
342
- data: { toolName: 'read_file', toolResult: ' ' },
631
+ data: { toolName: "read_file", toolResult: " " },
343
632
  };
344
633
  expect(buildToolResultTimelineActionInfo(event)).toBeUndefined();
345
634
  });