aws-runtime-bridge 1.9.32 → 1.9.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/AcodeSdkAdapter.d.ts +35 -1
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +417 -40
- package/dist/adapter/ClaudeSdkAdapter.d.ts +6 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +23 -1
- package/dist/adapter/idle-poll-loop.d.ts +2 -1
- package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
- package/dist/adapter/types.d.ts +65 -3
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +5 -4
- package/dist/index.js +4 -0
- package/dist/routes/acode-todos.d.ts +11 -0
- package/dist/routes/acode-todos.d.ts.map +1 -0
- package/dist/routes/acode-todos.js +75 -0
- package/dist/routes/memory.d.ts +1 -1
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +3 -3
- package/dist/routes/pty.d.ts.map +1 -1
- package/dist/routes/pty.js +9 -0
- package/dist/routes/sub-agents.d.ts +14 -0
- package/dist/routes/sub-agents.d.ts.map +1 -0
- package/dist/routes/sub-agents.js +253 -0
- package/dist/routes/terminal.d.ts +9 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +207 -22
- package/dist/services/agent-event-store.d.ts +1 -0
- package/dist/services/agent-event-store.d.ts.map +1 -1
- package/dist/services/event-delta-pusher.js +4 -4
- package/dist/services/session-output.d.ts +3 -0
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +38 -2
- package/dist/services/sub-agent-manager-instance.d.ts +4 -0
- package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
- package/dist/services/sub-agent-manager-instance.js +12 -0
- package/dist/services/workspace-files.d.ts +1 -0
- package/dist/services/workspace-files.d.ts.map +1 -1
- package/dist/services/workspace-files.js +57 -1
- package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
- package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
- package/package/acode/dist/ask-user-question-tool.js +233 -0
- package/package/acode/dist/built-in-file-tools.d.ts +2 -0
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/compaction.d.ts +206 -0
- package/package/acode/dist/compaction.d.ts.map +1 -0
- package/package/acode/dist/compaction.js +664 -0
- package/package/acode/dist/config.d.ts +6 -1
- package/package/acode/dist/config.d.ts.map +1 -1
- package/package/acode/dist/config.js +27 -2
- package/package/acode/dist/index.d.ts +4 -0
- package/package/acode/dist/index.d.ts.map +1 -1
- package/package/acode/dist/index.js +4 -0
- package/package/acode/dist/runtime.d.ts +137 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +974 -172
- package/package/acode/dist/skill-tool.d.ts +30 -0
- package/package/acode/dist/skill-tool.d.ts.map +1 -0
- package/package/acode/dist/skill-tool.js +174 -0
- package/package/acode/dist/sub-agent-manager.d.ts +169 -0
- package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-manager.js +535 -0
- package/package/acode/dist/sub-agent-store.d.ts +33 -0
- package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-store.js +109 -0
- package/package/acode/dist/sub-agent-tools.d.ts +72 -0
- package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-tools.js +429 -0
- package/package/acode/dist/sub-agent-types.d.ts +81 -0
- package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-types.js +8 -0
- package/package/acode/dist/todo-store.d.ts +42 -0
- package/package/acode/dist/todo-store.d.ts.map +1 -0
- package/package/acode/dist/todo-store.js +68 -0
- package/package/acode/dist/todo-tool.d.ts +6 -0
- package/package/acode/dist/todo-tool.d.ts.map +1 -0
- package/package/acode/dist/todo-tool.js +138 -0
- package/package/acode/dist/types.d.ts +22 -1
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/acode/package.json +13 -2
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -2
- package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +83 -45
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +451 -325
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +259 -241
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -19
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +2 -69
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +17 -19
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +62 -99
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js +207 -125
- package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -4
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js +28 -33
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +27 -9
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.js +100 -23
- package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -3
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +7 -11
- package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +3 -25
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package.json +17 -7
- package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
- package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
- package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
- package/dist/adapter/AdapterRegistry.test.js +0 -118
- package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
- package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/CodexSdkAdapter.test.js +0 -270
- package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
- package/dist/adapter/acode-tool-names.test.d.ts +0 -2
- package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
- package/dist/adapter/acode-tool-names.test.js +0 -28
- package/dist/adapter/adapter.test.d.ts +0 -5
- package/dist/adapter/adapter.test.d.ts.map +0 -1
- package/dist/adapter/adapter.test.js +0 -180
- package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
- package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
- package/dist/adapter/idle-poll-loop.test.js +0 -85
- package/dist/adapter/types.test.d.ts +0 -2
- package/dist/adapter/types.test.d.ts.map +0 -1
- package/dist/adapter/types.test.js +0 -50
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
- package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
- package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
- package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
- package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
- package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -107
- package/dist/routes/ai-sources.test.d.ts +0 -2
- package/dist/routes/ai-sources.test.d.ts.map +0 -1
- package/dist/routes/ai-sources.test.js +0 -169
- package/dist/routes/file-browser.test.d.ts +0 -5
- package/dist/routes/file-browser.test.d.ts.map +0 -1
- package/dist/routes/file-browser.test.js +0 -155
- package/dist/routes/git.test.d.ts +0 -2
- package/dist/routes/git.test.d.ts.map +0 -1
- package/dist/routes/git.test.js +0 -330
- package/dist/routes/instance.test.d.ts +0 -5
- package/dist/routes/instance.test.d.ts.map +0 -1
- package/dist/routes/instance.test.js +0 -389
- package/dist/routes/mcp.test.d.ts +0 -5
- package/dist/routes/mcp.test.d.ts.map +0 -1
- package/dist/routes/mcp.test.js +0 -92
- package/dist/routes/memory-route-wiring.test.d.ts +0 -2
- package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
- package/dist/routes/memory-route-wiring.test.js +0 -15
- package/dist/routes/memory.test.d.ts +0 -2
- package/dist/routes/memory.test.d.ts.map +0 -1
- package/dist/routes/memory.test.js +0 -177
- package/dist/routes/properties.test.d.ts +0 -5
- package/dist/routes/properties.test.d.ts.map +0 -1
- package/dist/routes/properties.test.js +0 -72
- package/dist/routes/pty.test.d.ts +0 -2
- package/dist/routes/pty.test.d.ts.map +0 -1
- package/dist/routes/pty.test.js +0 -82
- package/dist/routes/runtime-binding.test.d.ts +0 -2
- package/dist/routes/runtime-binding.test.d.ts.map +0 -1
- package/dist/routes/runtime-binding.test.js +0 -32
- package/dist/routes/sessions.test.d.ts +0 -5
- package/dist/routes/sessions.test.d.ts.map +0 -1
- package/dist/routes/sessions.test.js +0 -86
- package/dist/routes/skills.test.d.ts +0 -5
- package/dist/routes/skills.test.d.ts.map +0 -1
- package/dist/routes/skills.test.js +0 -50
- package/dist/routes/system-metrics.test.d.ts +0 -2
- package/dist/routes/system-metrics.test.d.ts.map +0 -1
- package/dist/routes/system-metrics.test.js +0 -16
- package/dist/routes/terminal.stop-options.test.d.ts +0 -2
- package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
- package/dist/routes/terminal.stop-options.test.js +0 -39
- package/dist/routes/terminal.test.d.ts +0 -2
- package/dist/routes/terminal.test.d.ts.map +0 -1
- package/dist/routes/terminal.test.js +0 -746
- package/dist/routes/yml.test.d.ts +0 -5
- package/dist/routes/yml.test.d.ts.map +0 -1
- package/dist/routes/yml.test.js +0 -58
- package/dist/services/ai-source-apply.test.d.ts +0 -2
- package/dist/services/ai-source-apply.test.d.ts.map +0 -1
- package/dist/services/ai-source-apply.test.js +0 -138
- package/dist/services/auto-register.test.d.ts +0 -2
- package/dist/services/auto-register.test.d.ts.map +0 -1
- package/dist/services/auto-register.test.js +0 -184
- package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
- package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
- package/dist/services/aws-client-agent-mcp.test.js +0 -242
- package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
- package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
- package/dist/services/bridge-package-publish-hooks.test.js +0 -136
- package/dist/services/cc-switch-discovery.test.d.ts +0 -2
- package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
- package/dist/services/cc-switch-discovery.test.js +0 -56
- package/dist/services/cli-commands.test.d.ts +0 -2
- package/dist/services/cli-commands.test.d.ts.map +0 -1
- package/dist/services/cli-commands.test.js +0 -230
- package/dist/services/instance-init-service.test.d.ts +0 -2
- package/dist/services/instance-init-service.test.d.ts.map +0 -1
- package/dist/services/instance-init-service.test.js +0 -140
- package/dist/services/instance-state.test.d.ts +0 -2
- package/dist/services/instance-state.test.d.ts.map +0 -1
- package/dist/services/instance-state.test.js +0 -245
- package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
- package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
- package/dist/services/mcp-launch-binding-queue.test.js +0 -181
- package/dist/services/runtime-binding.test.d.ts +0 -2
- package/dist/services/runtime-binding.test.d.ts.map +0 -1
- package/dist/services/runtime-binding.test.js +0 -78
- package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
- package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
- package/dist/services/runtime-lifecycle-policy.test.js +0 -41
- package/dist/services/session-output.test.d.ts +0 -5
- package/dist/services/session-output.test.d.ts.map +0 -1
- package/dist/services/session-output.test.js +0 -177
- package/dist/services/startup-config-wizard.test.d.ts +0 -2
- package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
- package/dist/services/startup-config-wizard.test.js +0 -184
- package/dist/services/terminal-persistence.test.d.ts +0 -5
- package/dist/services/terminal-persistence.test.d.ts.map +0 -1
- package/dist/services/terminal-persistence.test.js +0 -159
- package/dist/services/tool-installer.test.d.ts +0 -2
- package/dist/services/tool-installer.test.d.ts.map +0 -1
- package/dist/services/tool-installer.test.js +0 -225
- package/dist/services/workspace-files.test.d.ts +0 -2
- package/dist/services/workspace-files.test.d.ts.map +0 -1
- package/dist/services/workspace-files.test.js +0 -648
- package/dist/services/workspace-watch.test.d.ts +0 -2
- package/dist/services/workspace-watch.test.d.ts.map +0 -1
- package/dist/services/workspace-watch.test.js +0 -38
- package/dist/utils/file-utils.test.d.ts +0 -2
- package/dist/utils/file-utils.test.d.ts.map +0 -1
- package/dist/utils/file-utils.test.js +0 -244
- package/dist/utils/logger.test.d.ts +0 -2
- package/dist/utils/logger.test.d.ts.map +0 -1
- package/dist/utils/logger.test.js +0 -93
- package/dist/utils/sdk-package-loader.test.d.ts +0 -2
- package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
- package/dist/utils/sdk-package-loader.test.js +0 -84
- package/dist/utils/validation.test.d.ts +0 -2
- package/dist/utils/validation.test.d.ts.map +0 -1
- package/dist/utils/validation.test.js +0 -88
- package/dist/utils/yaml-utils.test.d.ts +0 -2
- package/dist/utils/yaml-utils.test.d.ts.map +0 -1
- package/dist/utils/yaml-utils.test.js +0 -363
|
@@ -1,2000 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
-
import { AcodeSdkAdapter, isFailedAcodeTurnComplete, } from "./AcodeSdkAdapter.js";
|
|
4
|
-
class FailedTurnRuntime extends EventEmitter {
|
|
5
|
-
constructor(config) {
|
|
6
|
-
super();
|
|
7
|
-
this.config = config;
|
|
8
|
-
}
|
|
9
|
-
async start() { }
|
|
10
|
-
async submit(_message) {
|
|
11
|
-
this.emit("event", {
|
|
12
|
-
type: "error",
|
|
13
|
-
sessionId: this.config.sessionId,
|
|
14
|
-
timestamp: new Date(0).toISOString(),
|
|
15
|
-
data: { message: "provider down" },
|
|
16
|
-
});
|
|
17
|
-
this.emit("event", {
|
|
18
|
-
type: "turn_complete",
|
|
19
|
-
sessionId: this.config.sessionId,
|
|
20
|
-
timestamp: new Date(0).toISOString(),
|
|
21
|
-
data: { failed: true, usage: { inputTokens: 1, outputTokens: 0 } },
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
async abort() { }
|
|
25
|
-
async stop() { }
|
|
26
|
-
}
|
|
27
|
-
class McpStatusRuntime extends EventEmitter {
|
|
28
|
-
constructor(config) {
|
|
29
|
-
super();
|
|
30
|
-
this.config = config;
|
|
31
|
-
}
|
|
32
|
-
async start() { }
|
|
33
|
-
async submit(_message) {
|
|
34
|
-
this.emit("event", {
|
|
35
|
-
type: "mcp_status",
|
|
36
|
-
sessionId: this.config.sessionId,
|
|
37
|
-
timestamp: new Date(0).toISOString(),
|
|
38
|
-
data: {
|
|
39
|
-
action: "call_tool",
|
|
40
|
-
server: "aws-mcp",
|
|
41
|
-
tool: "create_memory",
|
|
42
|
-
toolUseId: "tool-call-1",
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async abort() { }
|
|
47
|
-
async stop() { }
|
|
48
|
-
}
|
|
49
|
-
class BuiltinReadStatusRuntime extends EventEmitter {
|
|
50
|
-
constructor(config) {
|
|
51
|
-
super();
|
|
52
|
-
this.config = config;
|
|
53
|
-
}
|
|
54
|
-
async start() { }
|
|
55
|
-
async submit(_message) {
|
|
56
|
-
this.emit("event", {
|
|
57
|
-
type: "mcp_status",
|
|
58
|
-
sessionId: this.config.sessionId,
|
|
59
|
-
timestamp: new Date(0).toISOString(),
|
|
60
|
-
data: {
|
|
61
|
-
action: "call_tool",
|
|
62
|
-
server: "builtin",
|
|
63
|
-
tool: "read_file",
|
|
64
|
-
exposedName: "builtin/read_file",
|
|
65
|
-
toolUseId: "tool-read-1",
|
|
66
|
-
arguments: { path: "src/views/App.vue" },
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
async abort() { }
|
|
71
|
-
async stop() { }
|
|
72
|
-
}
|
|
73
|
-
class McpResultRuntime extends EventEmitter {
|
|
74
|
-
constructor(config) {
|
|
75
|
-
super();
|
|
76
|
-
this.config = config;
|
|
77
|
-
}
|
|
78
|
-
async start() { }
|
|
79
|
-
async submit(_message) {
|
|
80
|
-
this.emit("event", {
|
|
81
|
-
type: "mcp_status",
|
|
82
|
-
sessionId: this.config.sessionId,
|
|
83
|
-
timestamp: new Date(0).toISOString(),
|
|
84
|
-
data: {
|
|
85
|
-
action: "call_tool",
|
|
86
|
-
server: "aws-mcp",
|
|
87
|
-
tool: "create_memory",
|
|
88
|
-
toolUseId: "tool-call-1",
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
this.emit("event", {
|
|
92
|
-
type: "mcp_status",
|
|
93
|
-
sessionId: this.config.sessionId,
|
|
94
|
-
timestamp: new Date(0).toISOString(),
|
|
95
|
-
data: {
|
|
96
|
-
action: "tool_result",
|
|
97
|
-
server: "aws-mcp",
|
|
98
|
-
tool: "create_memory",
|
|
99
|
-
toolUseId: "tool-call-1",
|
|
100
|
-
result: '{"uri":"core://agent/profile"}',
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
async abort() { }
|
|
105
|
-
async stop() { }
|
|
106
|
-
}
|
|
107
|
-
class PollMessageStatusRuntime extends EventEmitter {
|
|
108
|
-
constructor(config) {
|
|
109
|
-
super();
|
|
110
|
-
this.config = config;
|
|
111
|
-
}
|
|
112
|
-
async start() { }
|
|
113
|
-
async submit(_message) {
|
|
114
|
-
this.emit("event", {
|
|
115
|
-
type: "mcp_status",
|
|
116
|
-
sessionId: this.config.sessionId,
|
|
117
|
-
timestamp: new Date(0).toISOString(),
|
|
118
|
-
data: { action: "call_tool", server: "aws-mcp", tool: "poll_message" },
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
async abort() { }
|
|
122
|
-
async stop() { }
|
|
123
|
-
}
|
|
124
|
-
class EmptyPollResultRuntime extends EventEmitter {
|
|
125
|
-
constructor(config) {
|
|
126
|
-
super();
|
|
127
|
-
this.config = config;
|
|
128
|
-
}
|
|
129
|
-
async start() { }
|
|
130
|
-
async submit(_message) {
|
|
131
|
-
this.emit("event", {
|
|
132
|
-
type: "mcp_status",
|
|
133
|
-
sessionId: this.config.sessionId,
|
|
134
|
-
timestamp: new Date(0).toISOString(),
|
|
135
|
-
data: { action: "call_tool", server: "aws-mcp", tool: "poll_message" },
|
|
136
|
-
});
|
|
137
|
-
this.emit("event", {
|
|
138
|
-
type: "mcp_status",
|
|
139
|
-
sessionId: this.config.sessionId,
|
|
140
|
-
timestamp: new Date(0).toISOString(),
|
|
141
|
-
data: {
|
|
142
|
-
action: "tool_result",
|
|
143
|
-
server: "aws-mcp",
|
|
144
|
-
tool: "poll_message",
|
|
145
|
-
result: JSON.stringify({ directMessages: [], groupMessages: [] }),
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
async abort() { }
|
|
150
|
-
async stop() { }
|
|
151
|
-
}
|
|
152
|
-
class PollResultWithMessageRuntime extends EventEmitter {
|
|
153
|
-
constructor(config) {
|
|
154
|
-
super();
|
|
155
|
-
this.config = config;
|
|
156
|
-
}
|
|
157
|
-
async start() { }
|
|
158
|
-
async submit(_message) {
|
|
159
|
-
this.emit("event", {
|
|
160
|
-
type: "mcp_status",
|
|
161
|
-
sessionId: this.config.sessionId,
|
|
162
|
-
timestamp: new Date(0).toISOString(),
|
|
163
|
-
data: { action: "call_tool", server: "aws-mcp", tool: "poll_message" },
|
|
164
|
-
});
|
|
165
|
-
this.emit("event", {
|
|
166
|
-
type: "mcp_status",
|
|
167
|
-
sessionId: this.config.sessionId,
|
|
168
|
-
timestamp: new Date(0).toISOString(),
|
|
169
|
-
data: {
|
|
170
|
-
action: "tool_result",
|
|
171
|
-
server: "aws-mcp",
|
|
172
|
-
tool: "poll_message",
|
|
173
|
-
result: JSON.stringify({
|
|
174
|
-
directMessages: [{ senderName: "User", content: "ping" }],
|
|
175
|
-
groupMessages: [],
|
|
176
|
-
}),
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
async abort() { }
|
|
181
|
-
async stop() { }
|
|
182
|
-
}
|
|
183
|
-
class CommandStatusRuntime extends EventEmitter {
|
|
184
|
-
constructor(config) {
|
|
185
|
-
super();
|
|
186
|
-
this.config = config;
|
|
187
|
-
}
|
|
188
|
-
async start() { }
|
|
189
|
-
async submit(_message) {
|
|
190
|
-
this.emit("event", {
|
|
191
|
-
type: "command_status",
|
|
192
|
-
sessionId: this.config.sessionId,
|
|
193
|
-
timestamp: new Date(0).toISOString(),
|
|
194
|
-
data: { action: "slash_command", command: "review" },
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
async abort() { }
|
|
198
|
-
async stop() { }
|
|
199
|
-
}
|
|
200
|
-
class AiRequestRuntime extends EventEmitter {
|
|
201
|
-
constructor(config) {
|
|
202
|
-
super();
|
|
203
|
-
this.config = config;
|
|
204
|
-
}
|
|
205
|
-
async start() { }
|
|
206
|
-
async submit(_message) {
|
|
207
|
-
this.emit("event", {
|
|
208
|
-
type: "ai_request",
|
|
209
|
-
sessionId: this.config.sessionId,
|
|
210
|
-
timestamp: new Date(0).toISOString(),
|
|
211
|
-
data: {
|
|
212
|
-
model: "test-model",
|
|
213
|
-
endpoint: "https://example.invalid/v1/chat/completions",
|
|
214
|
-
requestMethod: "POST",
|
|
215
|
-
requestUrl: "https://example.invalid/v1/chat/completions",
|
|
216
|
-
requestHeaders: {
|
|
217
|
-
"Content-Type": "application/json",
|
|
218
|
-
Authorization: "Bearer ***",
|
|
219
|
-
},
|
|
220
|
-
stream: true,
|
|
221
|
-
messageCount: 3,
|
|
222
|
-
toolCount: 2,
|
|
223
|
-
lastUserMessagePreview: "inspect status",
|
|
224
|
-
estimatedContextTokens: 6800,
|
|
225
|
-
requestBody: '{"model":"test-model"}',
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
async abort() { }
|
|
230
|
-
async stop() { }
|
|
231
|
-
}
|
|
232
|
-
class ThinkingDeltaRuntime extends EventEmitter {
|
|
233
|
-
constructor(config) {
|
|
234
|
-
super();
|
|
235
|
-
this.config = config;
|
|
236
|
-
}
|
|
237
|
-
async start() { }
|
|
238
|
-
async submit(_message) {
|
|
239
|
-
this.emit("event", {
|
|
240
|
-
type: "thinking_delta",
|
|
241
|
-
sessionId: this.config.sessionId,
|
|
242
|
-
timestamp: new Date(0).toISOString(),
|
|
243
|
-
data: { text: "reasoning chunk" },
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
async abort() { }
|
|
247
|
-
async stop() { }
|
|
248
|
-
}
|
|
249
|
-
class AssistantDeltaRuntime extends EventEmitter {
|
|
250
|
-
constructor(config) {
|
|
251
|
-
super();
|
|
252
|
-
this.config = config;
|
|
253
|
-
}
|
|
254
|
-
async start() { }
|
|
255
|
-
async submit(_message) {
|
|
256
|
-
this.emit("event", {
|
|
257
|
-
type: "assistant_delta",
|
|
258
|
-
sessionId: this.config.sessionId,
|
|
259
|
-
timestamp: new Date(0).toISOString(),
|
|
260
|
-
data: { text: "answer chunk" },
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
async abort() { }
|
|
264
|
-
async stop() { }
|
|
265
|
-
}
|
|
266
|
-
class CommandOutputRuntime extends EventEmitter {
|
|
267
|
-
constructor(config) {
|
|
268
|
-
super();
|
|
269
|
-
this.config = config;
|
|
270
|
-
}
|
|
271
|
-
async start() { }
|
|
272
|
-
async submit(_message) {
|
|
273
|
-
this.emit("event", {
|
|
274
|
-
type: "command_output",
|
|
275
|
-
sessionId: this.config.sessionId,
|
|
276
|
-
timestamp: new Date(0).toISOString(),
|
|
277
|
-
data: {
|
|
278
|
-
text: "live command output",
|
|
279
|
-
stream: "stdout",
|
|
280
|
-
sequence: 2,
|
|
281
|
-
cwd: "/repo",
|
|
282
|
-
commandLineCount: 1,
|
|
283
|
-
commandFilePath: "/repo/.agentswork/acode/cmd-dist/run.cmd",
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
async abort() { }
|
|
288
|
-
async stop() { }
|
|
289
|
-
}
|
|
290
|
-
class ResponseBodyTurnCompleteRuntime extends EventEmitter {
|
|
291
|
-
constructor(config) {
|
|
292
|
-
super();
|
|
293
|
-
this.config = config;
|
|
294
|
-
}
|
|
295
|
-
async start() { }
|
|
296
|
-
async submit(_message) {
|
|
297
|
-
this.emit("event", {
|
|
298
|
-
type: "turn_complete",
|
|
299
|
-
sessionId: this.config.sessionId,
|
|
300
|
-
timestamp: new Date(0).toISOString(),
|
|
301
|
-
data: {
|
|
302
|
-
usage: { inputTokens: 9, outputTokens: 3 },
|
|
303
|
-
responseBody: '{"choices":[{"message":{"content":"done"}}]}',
|
|
304
|
-
finishReason: "stop",
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async abort() { }
|
|
309
|
-
async stop() { }
|
|
310
|
-
}
|
|
311
|
-
class ProviderUsageTurnCompleteRuntime extends EventEmitter {
|
|
312
|
-
constructor(config) {
|
|
313
|
-
super();
|
|
314
|
-
this.config = config;
|
|
315
|
-
}
|
|
316
|
-
async start() { }
|
|
317
|
-
async submit(_message) {
|
|
318
|
-
this.emit("event", {
|
|
319
|
-
type: "turn_complete",
|
|
320
|
-
sessionId: this.config.sessionId,
|
|
321
|
-
timestamp: new Date(0).toISOString(),
|
|
322
|
-
data: {
|
|
323
|
-
usage: { prompt_tokens: 31, completion_tokens: 58 },
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
async abort() { }
|
|
328
|
-
async stop() { }
|
|
329
|
-
}
|
|
330
|
-
class ResettableRuntime extends EventEmitter {
|
|
331
|
-
constructor(config) {
|
|
332
|
-
super();
|
|
333
|
-
this.config = config;
|
|
334
|
-
}
|
|
335
|
-
async start() { }
|
|
336
|
-
async submit(_message) {
|
|
337
|
-
this.emit("event", {
|
|
338
|
-
type: "assistant_delta",
|
|
339
|
-
sessionId: this.config.sessionId,
|
|
340
|
-
timestamp: new Date(0).toISOString(),
|
|
341
|
-
data: { text: "done" },
|
|
342
|
-
});
|
|
343
|
-
this.emit("event", {
|
|
344
|
-
type: "turn_complete",
|
|
345
|
-
sessionId: this.config.sessionId,
|
|
346
|
-
timestamp: new Date(0).toISOString(),
|
|
347
|
-
data: { usage: { inputTokens: 7, outputTokens: 3 } },
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
async resetContext() {
|
|
351
|
-
ResettableRuntime.resetCalls += 1;
|
|
352
|
-
}
|
|
353
|
-
async abort() { }
|
|
354
|
-
async stop() {
|
|
355
|
-
ResettableRuntime.stopCalls += 1;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
ResettableRuntime.resetCalls = 0;
|
|
359
|
-
ResettableRuntime.stopCalls = 0;
|
|
360
|
-
class ThinkingThenAssistantDeltaRuntime extends EventEmitter {
|
|
361
|
-
constructor(config) {
|
|
362
|
-
super();
|
|
363
|
-
this.config = config;
|
|
364
|
-
}
|
|
365
|
-
async start() { }
|
|
366
|
-
async submit(_message) {
|
|
367
|
-
this.emit("event", {
|
|
368
|
-
type: "thinking_delta",
|
|
369
|
-
sessionId: this.config.sessionId,
|
|
370
|
-
timestamp: new Date(0).toISOString(),
|
|
371
|
-
data: { text: "thinking chunk" },
|
|
372
|
-
});
|
|
373
|
-
this.emit("event", {
|
|
374
|
-
type: "assistant_delta",
|
|
375
|
-
sessionId: this.config.sessionId,
|
|
376
|
-
timestamp: new Date(1).toISOString(),
|
|
377
|
-
data: { text: "answer chunk" },
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
async abort() { }
|
|
381
|
-
async stop() { }
|
|
382
|
-
}
|
|
383
|
-
class McpThenSessionCompleteRuntime extends EventEmitter {
|
|
384
|
-
constructor(config) {
|
|
385
|
-
super();
|
|
386
|
-
this.config = config;
|
|
387
|
-
}
|
|
388
|
-
async start() { }
|
|
389
|
-
async submit(_message) {
|
|
390
|
-
this.emit("event", {
|
|
391
|
-
type: "mcp_status",
|
|
392
|
-
sessionId: this.config.sessionId,
|
|
393
|
-
timestamp: new Date(0).toISOString(),
|
|
394
|
-
data: { action: "call_tool", server: "aws-mcp", tool: "send_private" },
|
|
395
|
-
});
|
|
396
|
-
this.emit("event", {
|
|
397
|
-
type: "session_complete",
|
|
398
|
-
sessionId: this.config.sessionId,
|
|
399
|
-
timestamp: new Date(0).toISOString(),
|
|
400
|
-
data: { exitCode: 0 },
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
async abort() { }
|
|
404
|
-
async stop() { }
|
|
405
|
-
}
|
|
406
|
-
class DeferredInitialPromptRuntime extends EventEmitter {
|
|
407
|
-
constructor(config) {
|
|
408
|
-
super();
|
|
409
|
-
DeferredInitialPromptRuntime.receivedInitialPrompt = config.initialPrompt;
|
|
410
|
-
}
|
|
411
|
-
async start() { }
|
|
412
|
-
async submit(_message) {
|
|
413
|
-
DeferredInitialPromptRuntime.submitCalls += 1;
|
|
414
|
-
await new Promise(() => undefined);
|
|
415
|
-
}
|
|
416
|
-
async abort() { }
|
|
417
|
-
async stop() { }
|
|
418
|
-
}
|
|
419
|
-
DeferredInitialPromptRuntime.submitCalls = 0;
|
|
420
|
-
class AutoPollRuntime extends EventEmitter {
|
|
421
|
-
constructor(config) {
|
|
422
|
-
super();
|
|
423
|
-
this.config = config;
|
|
424
|
-
}
|
|
425
|
-
async start() { }
|
|
426
|
-
async submit(message) {
|
|
427
|
-
AutoPollRuntime.submitMessages.push(message);
|
|
428
|
-
if (AutoPollRuntime.submitMessages.length === 1) {
|
|
429
|
-
this.emit("event", {
|
|
430
|
-
type: "turn_complete",
|
|
431
|
-
sessionId: this.config.sessionId,
|
|
432
|
-
timestamp: new Date(0).toISOString(),
|
|
433
|
-
data: { usage: { inputTokens: 1, outputTokens: 1 } },
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
async abort() { }
|
|
438
|
-
async stop() { }
|
|
439
|
-
}
|
|
440
|
-
AutoPollRuntime.submitMessages = [];
|
|
441
|
-
class NonPollingAutoPollRuntime extends EventEmitter {
|
|
442
|
-
constructor(config) {
|
|
443
|
-
super();
|
|
444
|
-
this.config = config;
|
|
445
|
-
}
|
|
446
|
-
async start() { }
|
|
447
|
-
async submit(message) {
|
|
448
|
-
NonPollingAutoPollRuntime.submitMessages.push(message);
|
|
449
|
-
this.emit("event", {
|
|
450
|
-
type: "assistant_delta",
|
|
451
|
-
sessionId: this.config.sessionId,
|
|
452
|
-
timestamp: new Date(0).toISOString(),
|
|
453
|
-
data: { text: "I cannot call poll_message." },
|
|
454
|
-
});
|
|
455
|
-
this.emit("event", {
|
|
456
|
-
type: "turn_complete",
|
|
457
|
-
sessionId: this.config.sessionId,
|
|
458
|
-
timestamp: new Date(0).toISOString(),
|
|
459
|
-
data: { usage: { inputTokens: 1, outputTokens: 1 } },
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
async abort() { }
|
|
463
|
-
async stop() { }
|
|
464
|
-
}
|
|
465
|
-
NonPollingAutoPollRuntime.submitMessages = [];
|
|
466
|
-
class PollingAutoPollRuntime extends EventEmitter {
|
|
467
|
-
constructor(config) {
|
|
468
|
-
super();
|
|
469
|
-
this.config = config;
|
|
470
|
-
}
|
|
471
|
-
async start() { }
|
|
472
|
-
async submit(message) {
|
|
473
|
-
PollingAutoPollRuntime.submitMessages.push(message);
|
|
474
|
-
this.emit("event", {
|
|
475
|
-
type: "mcp_status",
|
|
476
|
-
sessionId: this.config.sessionId,
|
|
477
|
-
timestamp: new Date(0).toISOString(),
|
|
478
|
-
data: { action: "call_tool", server: "aws-mcp", tool: "poll_message" },
|
|
479
|
-
});
|
|
480
|
-
this.emit("event", {
|
|
481
|
-
type: "turn_complete",
|
|
482
|
-
sessionId: this.config.sessionId,
|
|
483
|
-
timestamp: new Date(0).toISOString(),
|
|
484
|
-
data: { usage: { inputTokens: 1, outputTokens: 1 } },
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
async abort() { }
|
|
488
|
-
async stop() { }
|
|
489
|
-
}
|
|
490
|
-
PollingAutoPollRuntime.submitMessages = [];
|
|
491
|
-
class TerminalAfterTurnRuntime extends EventEmitter {
|
|
492
|
-
constructor(config) {
|
|
493
|
-
super();
|
|
494
|
-
this.config = config;
|
|
495
|
-
}
|
|
496
|
-
async start() { }
|
|
497
|
-
async submit(message) {
|
|
498
|
-
TerminalAfterTurnRuntime.submitMessages.push(message);
|
|
499
|
-
this.emit("event", {
|
|
500
|
-
type: "turn_complete",
|
|
501
|
-
sessionId: this.config.sessionId,
|
|
502
|
-
timestamp: new Date(0).toISOString(),
|
|
503
|
-
data: { usage: { inputTokens: 1, outputTokens: 1 } },
|
|
504
|
-
});
|
|
505
|
-
this.emit("event", {
|
|
506
|
-
type: "session_complete",
|
|
507
|
-
sessionId: this.config.sessionId,
|
|
508
|
-
timestamp: new Date(0).toISOString(),
|
|
509
|
-
data: { exitCode: 0 },
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
async abort() { }
|
|
513
|
-
async stop() { }
|
|
514
|
-
}
|
|
515
|
-
TerminalAfterTurnRuntime.submitMessages = [];
|
|
516
|
-
class FailingAutoPollRuntime extends EventEmitter {
|
|
517
|
-
constructor(config) {
|
|
518
|
-
super();
|
|
519
|
-
this.config = config;
|
|
520
|
-
}
|
|
521
|
-
async start() { }
|
|
522
|
-
async submit(message) {
|
|
523
|
-
FailingAutoPollRuntime.submitMessages.push(message);
|
|
524
|
-
this.emit("event", {
|
|
525
|
-
type: "error",
|
|
526
|
-
sessionId: this.config.sessionId,
|
|
527
|
-
timestamp: new Date(0).toISOString(),
|
|
528
|
-
data: { message: "mcp tool timeout" },
|
|
529
|
-
});
|
|
530
|
-
this.emit("event", {
|
|
531
|
-
type: "turn_complete",
|
|
532
|
-
sessionId: this.config.sessionId,
|
|
533
|
-
timestamp: new Date(0).toISOString(),
|
|
534
|
-
data: { failed: true, usage: { inputTokens: 1, outputTokens: 0 } },
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
async abort() { }
|
|
538
|
-
async stop() { }
|
|
539
|
-
}
|
|
540
|
-
FailingAutoPollRuntime.submitMessages = [];
|
|
541
|
-
class ResolvedWithoutTerminalEventRuntime extends EventEmitter {
|
|
542
|
-
constructor(config) {
|
|
543
|
-
super();
|
|
544
|
-
this.config = config;
|
|
545
|
-
}
|
|
546
|
-
async start() { }
|
|
547
|
-
async submit(message) {
|
|
548
|
-
ResolvedWithoutTerminalEventRuntime.submitMessages.push(message);
|
|
549
|
-
this.emit("event", {
|
|
550
|
-
type: "command_status",
|
|
551
|
-
sessionId: this.config.sessionId,
|
|
552
|
-
timestamp: new Date(0).toISOString(),
|
|
553
|
-
data: { action: "slash_command", command: "review" },
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
async abort() { }
|
|
557
|
-
async stop() { }
|
|
558
|
-
}
|
|
559
|
-
ResolvedWithoutTerminalEventRuntime.submitMessages = [];
|
|
560
|
-
class BareErrorRuntime extends EventEmitter {
|
|
561
|
-
constructor(config) {
|
|
562
|
-
super();
|
|
563
|
-
this.config = config;
|
|
564
|
-
}
|
|
565
|
-
async start() { }
|
|
566
|
-
async submit(message) {
|
|
567
|
-
BareErrorRuntime.submitMessages.push(message);
|
|
568
|
-
this.emit("event", {
|
|
569
|
-
type: "error",
|
|
570
|
-
sessionId: this.config.sessionId,
|
|
571
|
-
timestamp: new Date(0).toISOString(),
|
|
572
|
-
data: { message: "provider emitted bare error" },
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
async abort() { }
|
|
576
|
-
async stop() { }
|
|
577
|
-
}
|
|
578
|
-
BareErrorRuntime.submitMessages = [];
|
|
579
|
-
class RejectingSubmitRuntime extends EventEmitter {
|
|
580
|
-
constructor(_config) {
|
|
581
|
-
super();
|
|
582
|
-
}
|
|
583
|
-
async start() { }
|
|
584
|
-
async submit(message) {
|
|
585
|
-
RejectingSubmitRuntime.submitMessages.push(message);
|
|
586
|
-
throw new Error("submit rejected before turn_complete");
|
|
587
|
-
}
|
|
588
|
-
async abort() { }
|
|
589
|
-
async stop() { }
|
|
590
|
-
}
|
|
591
|
-
RejectingSubmitRuntime.submitMessages = [];
|
|
592
|
-
class StartFailureRuntime extends EventEmitter {
|
|
593
|
-
constructor(_config) {
|
|
594
|
-
super();
|
|
595
|
-
}
|
|
596
|
-
async start() {
|
|
597
|
-
throw new Error("runtime start failed");
|
|
598
|
-
}
|
|
599
|
-
async submit(_message) { }
|
|
600
|
-
async abort() { }
|
|
601
|
-
async stop() { }
|
|
602
|
-
}
|
|
603
|
-
class FailingPollThenSuccessfulUserRuntime extends EventEmitter {
|
|
604
|
-
constructor(config) {
|
|
605
|
-
super();
|
|
606
|
-
this.config = config;
|
|
607
|
-
}
|
|
608
|
-
async start() { }
|
|
609
|
-
async submit(message) {
|
|
610
|
-
FailingPollThenSuccessfulUserRuntime.submitMessages.push(message);
|
|
611
|
-
if (message.includes("poll_message")) {
|
|
612
|
-
this.emit("event", {
|
|
613
|
-
type: "error",
|
|
614
|
-
sessionId: this.config.sessionId,
|
|
615
|
-
timestamp: new Date(0).toISOString(),
|
|
616
|
-
data: { message: "poll failed once" },
|
|
617
|
-
});
|
|
618
|
-
this.emit("event", {
|
|
619
|
-
type: "turn_complete",
|
|
620
|
-
sessionId: this.config.sessionId,
|
|
621
|
-
timestamp: new Date(0).toISOString(),
|
|
622
|
-
data: { failed: true, usage: { inputTokens: 1, outputTokens: 0 } },
|
|
623
|
-
});
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
this.emit("event", {
|
|
627
|
-
type: "turn_complete",
|
|
628
|
-
sessionId: this.config.sessionId,
|
|
629
|
-
timestamp: new Date(0).toISOString(),
|
|
630
|
-
data: { usage: { inputTokens: 1, outputTokens: 1 } },
|
|
631
|
-
});
|
|
632
|
-
this.emit("event", {
|
|
633
|
-
type: "session_complete",
|
|
634
|
-
sessionId: this.config.sessionId,
|
|
635
|
-
timestamp: new Date(0).toISOString(),
|
|
636
|
-
data: { exitCode: 0 },
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
async abort() { }
|
|
640
|
-
async stop() { }
|
|
641
|
-
}
|
|
642
|
-
FailingPollThenSuccessfulUserRuntime.submitMessages = [];
|
|
643
|
-
class StartupMcpStatusRuntime extends EventEmitter {
|
|
644
|
-
constructor(config) {
|
|
645
|
-
super();
|
|
646
|
-
this.config = config;
|
|
647
|
-
}
|
|
648
|
-
async start() {
|
|
649
|
-
// Mirrors the startup mcp_status event emitted by ACode's
|
|
650
|
-
// connectMcpServers before any user interaction. The R3 check in
|
|
651
|
-
// AcodeSdkAdapter validates the `tools` array (the tools the LLM
|
|
652
|
-
// can actually call) rather than the `servers` config keys, so
|
|
653
|
-
// the runtime must emit the tools form here to exercise that path.
|
|
654
|
-
this.emit("event", {
|
|
655
|
-
type: "mcp_status",
|
|
656
|
-
sessionId: this.config.sessionId,
|
|
657
|
-
timestamp: new Date(0).toISOString(),
|
|
658
|
-
data: {
|
|
659
|
-
tools: StartupMcpStatusRuntime.includeAwsMcp
|
|
660
|
-
? [{ name: "aws-mcp__poll_message", source: "aws-mcp.poll_message" }]
|
|
661
|
-
: [{ name: "other-mcp__other_tool", source: "other-mcp.other_tool" }],
|
|
662
|
-
},
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
async submit(_message) { }
|
|
666
|
-
async abort() { }
|
|
667
|
-
async stop() { }
|
|
668
|
-
}
|
|
669
|
-
// When true (default) the emitted startup mcp_status event includes a
|
|
670
|
-
// tool named 'aws-mcp__poll_message', mirroring the happy-path runtime
|
|
671
|
-
// configuration. When false, the tools list intentionally omits
|
|
672
|
-
// poll_message so the R3 check is forced to take the warning path.
|
|
673
|
-
StartupMcpStatusRuntime.includeAwsMcp = true;
|
|
674
|
-
class DifferentlyNamedMcpRuntime extends EventEmitter {
|
|
675
|
-
// Reproduces the bug-report scenario: the user's MCP is configured
|
|
676
|
-
// under a non-default name ('aws-mcp-server') and the runtime
|
|
677
|
-
// reports that name in the legacy `servers` field, but the LLM-
|
|
678
|
-
// visible tools are still prefixed with the canonical 'aws-mcp__'.
|
|
679
|
-
// The R3 check must validate against the tools (not the server
|
|
680
|
-
// names) so this configuration is accepted as healthy.
|
|
681
|
-
constructor(config) {
|
|
682
|
-
super();
|
|
683
|
-
this.config = config;
|
|
684
|
-
}
|
|
685
|
-
async start() {
|
|
686
|
-
this.emit("event", {
|
|
687
|
-
type: "mcp_status",
|
|
688
|
-
sessionId: this.config.sessionId,
|
|
689
|
-
timestamp: new Date(0).toISOString(),
|
|
690
|
-
data: {
|
|
691
|
-
servers: ["aws-mcp-server"],
|
|
692
|
-
tools: [
|
|
693
|
-
{ name: "aws-mcp__poll_message", source: "aws-mcp.poll_message" },
|
|
694
|
-
{
|
|
695
|
-
name: "aws-mcp__submit_response",
|
|
696
|
-
source: "aws-mcp.submit_response",
|
|
697
|
-
},
|
|
698
|
-
],
|
|
699
|
-
},
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
async submit(_message) { }
|
|
703
|
-
async abort() { }
|
|
704
|
-
async stop() { }
|
|
705
|
-
}
|
|
706
|
-
class FakePollMessageToolRuntime extends EventEmitter {
|
|
707
|
-
constructor(config) {
|
|
708
|
-
super();
|
|
709
|
-
this.config = config;
|
|
710
|
-
}
|
|
711
|
-
async start() {
|
|
712
|
-
this.emit("event", {
|
|
713
|
-
type: "mcp_status",
|
|
714
|
-
sessionId: this.config.sessionId,
|
|
715
|
-
timestamp: new Date(0).toISOString(),
|
|
716
|
-
data: {
|
|
717
|
-
tools: [
|
|
718
|
-
{
|
|
719
|
-
name: "other-mcp__not_poll_message",
|
|
720
|
-
source: "other-mcp.not_poll_message",
|
|
721
|
-
},
|
|
722
|
-
],
|
|
723
|
-
},
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
async submit(_message) { }
|
|
727
|
-
async abort() { }
|
|
728
|
-
async stop() { }
|
|
729
|
-
}
|
|
730
|
-
class DeferredInitialThenRegularIdleRuntime extends EventEmitter {
|
|
731
|
-
constructor(config) {
|
|
732
|
-
super();
|
|
733
|
-
this.config = config;
|
|
734
|
-
}
|
|
735
|
-
async start() { }
|
|
736
|
-
async submit(message) {
|
|
737
|
-
DeferredInitialThenRegularIdleRuntime.submitMessages.push(message);
|
|
738
|
-
if (DeferredInitialThenRegularIdleRuntime.submitMessages.length !== 1) {
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
await new Promise((resolve) => {
|
|
742
|
-
DeferredInitialThenRegularIdleRuntime.completeInitialTurn = () => {
|
|
743
|
-
this.emit("event", {
|
|
744
|
-
type: "turn_complete",
|
|
745
|
-
sessionId: this.config.sessionId,
|
|
746
|
-
timestamp: new Date(0).toISOString(),
|
|
747
|
-
data: { usage: { inputTokens: 1, outputTokens: 1 } },
|
|
748
|
-
});
|
|
749
|
-
DeferredInitialThenRegularIdleRuntime.completeInitialTurn = undefined;
|
|
750
|
-
resolve();
|
|
751
|
-
};
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
async abort() { }
|
|
755
|
-
async stop() { }
|
|
756
|
-
}
|
|
757
|
-
DeferredInitialThenRegularIdleRuntime.submitMessages = [];
|
|
758
|
-
describe("AcodeSdkAdapter turn completion state", () => {
|
|
759
|
-
it("keeps error status for failed turn_complete events", () => {
|
|
760
|
-
expect(isFailedAcodeTurnComplete({ failed: true })).toBe(true);
|
|
761
|
-
expect(isFailedAcodeTurnComplete({ failed: false })).toBe(false);
|
|
762
|
-
expect(isFailedAcodeTurnComplete({ usage: { inputTokens: 1, outputTokens: 2 } })).toBe(false);
|
|
763
|
-
});
|
|
764
|
-
it("recovers failed turn_complete to waiting_input so the agent never stays stuck in error (R4)", async () => {
|
|
765
|
-
const adapter = new AcodeSdkAdapter();
|
|
766
|
-
adapter.setRuntimeConstructorForTest(FailedTurnRuntime);
|
|
767
|
-
const events = [];
|
|
768
|
-
const statuses = [];
|
|
769
|
-
adapter.on("event", (event) => events.push(event));
|
|
770
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
771
|
-
await adapter.startSession("session-failed", {
|
|
772
|
-
command: "acode",
|
|
773
|
-
workingDirectory: process.cwd(),
|
|
774
|
-
autoAccept: true,
|
|
775
|
-
extraMcpServers: {},
|
|
776
|
-
envOverrides: {},
|
|
777
|
-
});
|
|
778
|
-
await adapter.sendMessage("session-failed", "fail please");
|
|
779
|
-
expect(events.map((event) => event.type)).toEqual(["error"]);
|
|
780
|
-
// R4: the session briefly enters 'error' but then recovers to 'waiting_input'
|
|
781
|
-
// so the agent can retry via the idle poll loop instead of being stuck forever.
|
|
782
|
-
expect(statuses).toEqual([
|
|
783
|
-
"starting",
|
|
784
|
-
"waiting_input",
|
|
785
|
-
"thinking",
|
|
786
|
-
"error",
|
|
787
|
-
"waiting_input",
|
|
788
|
-
]);
|
|
789
|
-
expect(adapter.getSessionStatus("session-failed")).toBe("waiting_input");
|
|
790
|
-
});
|
|
791
|
-
it("forwards ACode turn_complete response metadata", async () => {
|
|
792
|
-
const adapter = new AcodeSdkAdapter();
|
|
793
|
-
adapter.setRuntimeConstructorForTest(ResponseBodyTurnCompleteRuntime);
|
|
794
|
-
const events = [];
|
|
795
|
-
const statuses = [];
|
|
796
|
-
adapter.on("event", (event) => events.push(event));
|
|
797
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
798
|
-
statuses.push({ status, usage: actionInfo?.usage });
|
|
799
|
-
});
|
|
800
|
-
await adapter.startSession("session-response-body", {
|
|
801
|
-
command: "acode",
|
|
802
|
-
workingDirectory: process.cwd(),
|
|
803
|
-
autoAccept: true,
|
|
804
|
-
extraMcpServers: {},
|
|
805
|
-
envOverrides: {},
|
|
806
|
-
});
|
|
807
|
-
await adapter.sendMessage("session-response-body", "answer");
|
|
808
|
-
expect(events).toHaveLength(1);
|
|
809
|
-
expect(events[0]).toMatchObject({
|
|
810
|
-
type: "turn_complete",
|
|
811
|
-
data: {
|
|
812
|
-
usage: { inputTokens: 9, outputTokens: 3 },
|
|
813
|
-
responseBody: '{"choices":[{"message":{"content":"done"}}]}',
|
|
814
|
-
finishReason: "stop",
|
|
815
|
-
},
|
|
816
|
-
});
|
|
817
|
-
});
|
|
818
|
-
it("normalizes provider-native ACode usage fields for context display", async () => {
|
|
819
|
-
const adapter = new AcodeSdkAdapter();
|
|
820
|
-
adapter.setRuntimeConstructorForTest(ProviderUsageTurnCompleteRuntime);
|
|
821
|
-
const events = [];
|
|
822
|
-
const statuses = [];
|
|
823
|
-
adapter.on("event", (event) => events.push(event));
|
|
824
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
825
|
-
statuses.push({ status, usage: actionInfo?.usage });
|
|
826
|
-
});
|
|
827
|
-
await adapter.startSession("session-provider-usage", {
|
|
828
|
-
command: "acode",
|
|
829
|
-
workingDirectory: process.cwd(),
|
|
830
|
-
autoAccept: true,
|
|
831
|
-
extraMcpServers: {},
|
|
832
|
-
envOverrides: {},
|
|
833
|
-
});
|
|
834
|
-
await adapter.sendMessage("session-provider-usage", "answer");
|
|
835
|
-
expect(events).toHaveLength(1);
|
|
836
|
-
expect(events[0]).toMatchObject({
|
|
837
|
-
type: "turn_complete",
|
|
838
|
-
data: { usage: { inputTokens: 31, outputTokens: 58 } },
|
|
839
|
-
});
|
|
840
|
-
expect(statuses.at(-1)).toMatchObject({
|
|
841
|
-
status: "waiting_input",
|
|
842
|
-
usage: { inputTokens: 31, outputTokens: 58 },
|
|
843
|
-
});
|
|
844
|
-
});
|
|
845
|
-
it("resets ACode context in place without terminating the SDK session", async () => {
|
|
846
|
-
vi.useFakeTimers();
|
|
847
|
-
ResettableRuntime.resetCalls = 0;
|
|
848
|
-
ResettableRuntime.stopCalls = 0;
|
|
849
|
-
const adapter = new AcodeSdkAdapter();
|
|
850
|
-
adapter.setRuntimeConstructorForTest(ResettableRuntime);
|
|
851
|
-
const statuses = [];
|
|
852
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
853
|
-
statuses.push({ status, usage: actionInfo?.usage });
|
|
854
|
-
});
|
|
855
|
-
try {
|
|
856
|
-
await adapter.startSession("session-reset-context", {
|
|
857
|
-
command: "acode",
|
|
858
|
-
workingDirectory: process.cwd(),
|
|
859
|
-
autoAccept: true,
|
|
860
|
-
extraMcpServers: {},
|
|
861
|
-
envOverrides: {},
|
|
862
|
-
});
|
|
863
|
-
await adapter.sendMessage("session-reset-context", "finish work");
|
|
864
|
-
expect(adapter.getConversation("session-reset-context")).toHaveLength(2);
|
|
865
|
-
expect(statuses.at(-1)).toMatchObject({
|
|
866
|
-
status: "waiting_input",
|
|
867
|
-
usage: { inputTokens: 7, outputTokens: 3 },
|
|
868
|
-
});
|
|
869
|
-
await adapter.resetContext("session-reset-context");
|
|
870
|
-
expect(ResettableRuntime.resetCalls).toBe(1);
|
|
871
|
-
expect(ResettableRuntime.stopCalls).toBe(0);
|
|
872
|
-
expect(adapter.hasSession("session-reset-context")).toBe(true);
|
|
873
|
-
expect(adapter.getSessionStatus("session-reset-context")).toBe("waiting_input");
|
|
874
|
-
expect(adapter.getConversation("session-reset-context")).toEqual([]);
|
|
875
|
-
expect(statuses.at(-1)).toMatchObject({
|
|
876
|
-
status: "waiting_input",
|
|
877
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
finally {
|
|
881
|
-
vi.useRealTimers();
|
|
882
|
-
adapter.cleanup();
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
it("maps ACode MCP status events to tool_using instead of thinking", async () => {
|
|
886
|
-
const adapter = new AcodeSdkAdapter();
|
|
887
|
-
adapter.setRuntimeConstructorForTest(McpStatusRuntime);
|
|
888
|
-
const events = [];
|
|
889
|
-
const statuses = [];
|
|
890
|
-
adapter.on("event", (event) => events.push(event));
|
|
891
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
892
|
-
statuses.push({
|
|
893
|
-
status,
|
|
894
|
-
actionType: actionInfo?.actionType,
|
|
895
|
-
actionDetail: actionInfo?.actionDetail,
|
|
896
|
-
});
|
|
897
|
-
});
|
|
898
|
-
await adapter.startSession("session-mcp", {
|
|
899
|
-
command: "acode",
|
|
900
|
-
workingDirectory: process.cwd(),
|
|
901
|
-
autoAccept: true,
|
|
902
|
-
extraMcpServers: {},
|
|
903
|
-
envOverrides: {},
|
|
904
|
-
});
|
|
905
|
-
await adapter.sendMessage("session-mcp", "call mcp");
|
|
906
|
-
expect(events.map((event) => event.type)).toEqual(["tool_use_start"]);
|
|
907
|
-
expect(events[0]?.data).toMatchObject({
|
|
908
|
-
actionType: "mcp",
|
|
909
|
-
actionLabel: "调用MCP",
|
|
910
|
-
actionDetail: "aws-mcp/create_memory",
|
|
911
|
-
actionId: "tool-call-1",
|
|
912
|
-
toolUseId: "tool-call-1",
|
|
913
|
-
});
|
|
914
|
-
expect(statuses.at(-2)).toEqual({
|
|
915
|
-
status: "tool_using",
|
|
916
|
-
actionType: "mcp",
|
|
917
|
-
actionDetail: "aws-mcp/create_memory",
|
|
918
|
-
});
|
|
919
|
-
expect(statuses.at(-1)).toEqual({
|
|
920
|
-
status: "waiting_input",
|
|
921
|
-
actionType: undefined,
|
|
922
|
-
actionDetail: undefined,
|
|
923
|
-
});
|
|
924
|
-
expect(adapter.getSessionStatus("session-mcp")).toBe("waiting_input");
|
|
925
|
-
});
|
|
926
|
-
it("preserves built-in file tool arguments in ACode provider events and statuses", async () => {
|
|
927
|
-
const adapter = new AcodeSdkAdapter();
|
|
928
|
-
adapter.setRuntimeConstructorForTest(BuiltinReadStatusRuntime);
|
|
929
|
-
const events = [];
|
|
930
|
-
const statuses = [];
|
|
931
|
-
adapter.on("event", (event) => events.push(event));
|
|
932
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
933
|
-
statuses.push({
|
|
934
|
-
status,
|
|
935
|
-
actionType: actionInfo?.actionType,
|
|
936
|
-
actionDetail: actionInfo?.actionDetail,
|
|
937
|
-
});
|
|
938
|
-
});
|
|
939
|
-
await adapter.startSession("session-builtin-read", {
|
|
940
|
-
command: "acode",
|
|
941
|
-
workingDirectory: process.cwd(),
|
|
942
|
-
autoAccept: true,
|
|
943
|
-
extraMcpServers: {},
|
|
944
|
-
envOverrides: {},
|
|
945
|
-
});
|
|
946
|
-
await adapter.sendMessage("session-builtin-read", "read file");
|
|
947
|
-
expect(events.map((event) => event.type)).toEqual(["tool_use_start"]);
|
|
948
|
-
expect(events[0]?.data).toMatchObject({
|
|
949
|
-
actionType: "read_file",
|
|
950
|
-
actionLabel: "读取文件",
|
|
951
|
-
actionDetail: "src/views/App.vue",
|
|
952
|
-
actionId: "tool-read-1",
|
|
953
|
-
toolUseId: "tool-read-1",
|
|
954
|
-
toolInput: { path: "src/views/App.vue" },
|
|
955
|
-
});
|
|
956
|
-
expect(statuses.at(-2)).toEqual({
|
|
957
|
-
status: "tool_using",
|
|
958
|
-
actionType: "read_file",
|
|
959
|
-
actionDetail: "src/views/App.vue",
|
|
960
|
-
});
|
|
961
|
-
});
|
|
962
|
-
it("clears active MCP tool status when ACode reports a tool result", async () => {
|
|
963
|
-
const adapter = new AcodeSdkAdapter();
|
|
964
|
-
adapter.setRuntimeConstructorForTest(McpResultRuntime);
|
|
965
|
-
const events = [];
|
|
966
|
-
const statuses = [];
|
|
967
|
-
adapter.on("event", (event) => events.push(event));
|
|
968
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
969
|
-
statuses.push({
|
|
970
|
-
status,
|
|
971
|
-
actionType: actionInfo?.actionType,
|
|
972
|
-
actionDetail: actionInfo?.actionDetail,
|
|
973
|
-
});
|
|
974
|
-
});
|
|
975
|
-
await adapter.startSession("session-mcp-result", {
|
|
976
|
-
command: "acode",
|
|
977
|
-
workingDirectory: process.cwd(),
|
|
978
|
-
autoAccept: true,
|
|
979
|
-
extraMcpServers: {},
|
|
980
|
-
envOverrides: {},
|
|
981
|
-
});
|
|
982
|
-
await adapter.sendMessage("session-mcp-result", "remember profile");
|
|
983
|
-
expect(events.map((event) => event.type)).toEqual([
|
|
984
|
-
"tool_use_start",
|
|
985
|
-
"tool_use_end",
|
|
986
|
-
]);
|
|
987
|
-
expect(events[1]?.data).toMatchObject({
|
|
988
|
-
actionType: "mcp",
|
|
989
|
-
actionLabel: "调用MCP",
|
|
990
|
-
actionDetail: "aws-mcp/create_memory",
|
|
991
|
-
actionId: "tool-call-1",
|
|
992
|
-
toolUseId: "tool-call-1",
|
|
993
|
-
toolResult: '{"uri":"core://agent/profile"}',
|
|
994
|
-
});
|
|
995
|
-
expect(statuses.at(-2)).toEqual({
|
|
996
|
-
status: "thinking",
|
|
997
|
-
actionType: undefined,
|
|
998
|
-
actionDetail: undefined,
|
|
999
|
-
});
|
|
1000
|
-
expect(statuses.at(-1)).toEqual({
|
|
1001
|
-
status: "waiting_input",
|
|
1002
|
-
actionType: undefined,
|
|
1003
|
-
actionDetail: undefined,
|
|
1004
|
-
});
|
|
1005
|
-
expect(adapter.getSessionStatus("session-mcp-result")).toBe("waiting_input");
|
|
1006
|
-
});
|
|
1007
|
-
it("maps ACode poll_message MCP status events to waiting_input idle state", async () => {
|
|
1008
|
-
const adapter = new AcodeSdkAdapter();
|
|
1009
|
-
adapter.setRuntimeConstructorForTest(PollMessageStatusRuntime);
|
|
1010
|
-
const events = [];
|
|
1011
|
-
const statuses = [];
|
|
1012
|
-
adapter.on("event", (event) => events.push(event));
|
|
1013
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
1014
|
-
statuses.push({
|
|
1015
|
-
status,
|
|
1016
|
-
actionType: actionInfo?.actionType,
|
|
1017
|
-
actionDetail: actionInfo?.actionDetail,
|
|
1018
|
-
});
|
|
1019
|
-
});
|
|
1020
|
-
await adapter.startSession("session-poll-message", {
|
|
1021
|
-
command: "acode",
|
|
1022
|
-
workingDirectory: process.cwd(),
|
|
1023
|
-
autoAccept: true,
|
|
1024
|
-
extraMcpServers: {},
|
|
1025
|
-
envOverrides: {},
|
|
1026
|
-
});
|
|
1027
|
-
await adapter.sendMessage("session-poll-message", "listen");
|
|
1028
|
-
expect(events.map((event) => event.type)).toEqual(["tool_use_start"]);
|
|
1029
|
-
expect(events[0]?.data).toMatchObject({
|
|
1030
|
-
actionType: "idle",
|
|
1031
|
-
actionLabel: "监听消息",
|
|
1032
|
-
actionDetail: "aws-mcp/poll_message",
|
|
1033
|
-
});
|
|
1034
|
-
expect(statuses.at(-1)).toEqual({
|
|
1035
|
-
status: "waiting_input",
|
|
1036
|
-
actionType: "idle",
|
|
1037
|
-
actionDetail: "aws-mcp/poll_message",
|
|
1038
|
-
});
|
|
1039
|
-
expect(adapter.getSessionStatus("session-poll-message")).toBe("waiting_input");
|
|
1040
|
-
});
|
|
1041
|
-
it("keeps empty poll_message results in waiting_input instead of showing stuck thinking", async () => {
|
|
1042
|
-
const adapter = new AcodeSdkAdapter();
|
|
1043
|
-
adapter.setRuntimeConstructorForTest(EmptyPollResultRuntime);
|
|
1044
|
-
const events = [];
|
|
1045
|
-
const statuses = [];
|
|
1046
|
-
adapter.on("event", (event) => events.push(event));
|
|
1047
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1048
|
-
await adapter.startSession("session-empty-poll-result", {
|
|
1049
|
-
command: "acode",
|
|
1050
|
-
workingDirectory: process.cwd(),
|
|
1051
|
-
autoAccept: true,
|
|
1052
|
-
extraMcpServers: {},
|
|
1053
|
-
envOverrides: {},
|
|
1054
|
-
});
|
|
1055
|
-
await adapter.sendMessage("session-empty-poll-result", "listen");
|
|
1056
|
-
expect(events.map((event) => event.type)).toEqual([
|
|
1057
|
-
"tool_use_start",
|
|
1058
|
-
"tool_use_end",
|
|
1059
|
-
]);
|
|
1060
|
-
expect(statuses).toEqual([
|
|
1061
|
-
"starting",
|
|
1062
|
-
"waiting_input",
|
|
1063
|
-
"thinking",
|
|
1064
|
-
"waiting_input",
|
|
1065
|
-
"waiting_input",
|
|
1066
|
-
]);
|
|
1067
|
-
expect(adapter.getSessionStatus("session-empty-poll-result")).toBe("waiting_input");
|
|
1068
|
-
});
|
|
1069
|
-
it("shows thinking after poll_message returns actual messages to process", async () => {
|
|
1070
|
-
const adapter = new AcodeSdkAdapter();
|
|
1071
|
-
adapter.setRuntimeConstructorForTest(PollResultWithMessageRuntime);
|
|
1072
|
-
const events = [];
|
|
1073
|
-
const statuses = [];
|
|
1074
|
-
adapter.on("event", (event) => events.push(event));
|
|
1075
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1076
|
-
await adapter.startSession("session-poll-result-with-message", {
|
|
1077
|
-
command: "acode",
|
|
1078
|
-
workingDirectory: process.cwd(),
|
|
1079
|
-
autoAccept: true,
|
|
1080
|
-
extraMcpServers: {},
|
|
1081
|
-
envOverrides: {},
|
|
1082
|
-
});
|
|
1083
|
-
await adapter.sendMessage("session-poll-result-with-message", "listen");
|
|
1084
|
-
expect(events.map((event) => event.type)).toEqual([
|
|
1085
|
-
"tool_use_start",
|
|
1086
|
-
"tool_use_end",
|
|
1087
|
-
]);
|
|
1088
|
-
expect(statuses).toEqual([
|
|
1089
|
-
"starting",
|
|
1090
|
-
"waiting_input",
|
|
1091
|
-
"thinking",
|
|
1092
|
-
"waiting_input",
|
|
1093
|
-
"thinking",
|
|
1094
|
-
"waiting_input",
|
|
1095
|
-
]);
|
|
1096
|
-
expect(adapter.getSessionStatus("session-poll-result-with-message")).toBe("waiting_input");
|
|
1097
|
-
});
|
|
1098
|
-
it("forwards ACode command status as thinking without changing session status", async () => {
|
|
1099
|
-
const adapter = new AcodeSdkAdapter();
|
|
1100
|
-
adapter.setRuntimeConstructorForTest(CommandStatusRuntime);
|
|
1101
|
-
const events = [];
|
|
1102
|
-
const statuses = [];
|
|
1103
|
-
adapter.on("event", (event) => events.push(event));
|
|
1104
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1105
|
-
await adapter.startSession("session-command-status", {
|
|
1106
|
-
command: "acode",
|
|
1107
|
-
workingDirectory: process.cwd(),
|
|
1108
|
-
autoAccept: true,
|
|
1109
|
-
extraMcpServers: {},
|
|
1110
|
-
envOverrides: {},
|
|
1111
|
-
});
|
|
1112
|
-
await adapter.sendMessage("session-command-status", "/review src");
|
|
1113
|
-
expect(events).toHaveLength(1);
|
|
1114
|
-
expect(events[0]?.type).toBe("thinking");
|
|
1115
|
-
expect(String(events[0]?.data.text)).toContain("slash_command");
|
|
1116
|
-
expect(statuses).toEqual([
|
|
1117
|
-
"starting",
|
|
1118
|
-
"waiting_input",
|
|
1119
|
-
"thinking",
|
|
1120
|
-
"waiting_input",
|
|
1121
|
-
]);
|
|
1122
|
-
expect(adapter.getSessionStatus("session-command-status")).toBe("waiting_input");
|
|
1123
|
-
});
|
|
1124
|
-
it("forwards ACode AI request summaries as provider events", async () => {
|
|
1125
|
-
const adapter = new AcodeSdkAdapter();
|
|
1126
|
-
adapter.setRuntimeConstructorForTest(AiRequestRuntime);
|
|
1127
|
-
const events = [];
|
|
1128
|
-
const statuses = [];
|
|
1129
|
-
adapter.on("event", (event) => events.push(event));
|
|
1130
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
1131
|
-
statuses.push({ status, usage: actionInfo?.usage });
|
|
1132
|
-
});
|
|
1133
|
-
await adapter.startSession("session-ai-request", {
|
|
1134
|
-
command: "acode",
|
|
1135
|
-
workingDirectory: process.cwd(),
|
|
1136
|
-
autoAccept: true,
|
|
1137
|
-
extraMcpServers: {},
|
|
1138
|
-
envOverrides: {},
|
|
1139
|
-
});
|
|
1140
|
-
await adapter.sendMessage("session-ai-request", "inspect status");
|
|
1141
|
-
expect(events).toHaveLength(1);
|
|
1142
|
-
expect(events[0]).toMatchObject({
|
|
1143
|
-
type: "ai_request",
|
|
1144
|
-
data: {
|
|
1145
|
-
model: "test-model",
|
|
1146
|
-
endpoint: "https://example.invalid/v1/chat/completions",
|
|
1147
|
-
requestMethod: "POST",
|
|
1148
|
-
requestUrl: "https://example.invalid/v1/chat/completions",
|
|
1149
|
-
requestHeaders: {
|
|
1150
|
-
"Content-Type": "application/json",
|
|
1151
|
-
Authorization: "Bearer ***",
|
|
1152
|
-
},
|
|
1153
|
-
stream: true,
|
|
1154
|
-
messageCount: 3,
|
|
1155
|
-
toolCount: 2,
|
|
1156
|
-
lastUserMessagePreview: "inspect status",
|
|
1157
|
-
requestBody: '{"model":"test-model"}',
|
|
1158
|
-
},
|
|
1159
|
-
});
|
|
1160
|
-
expect(statuses).toEqual(expect.arrayContaining([
|
|
1161
|
-
expect.objectContaining({
|
|
1162
|
-
status: "thinking",
|
|
1163
|
-
usage: expect.objectContaining({ inputTokens: 6800, outputTokens: 0 }),
|
|
1164
|
-
}),
|
|
1165
|
-
]));
|
|
1166
|
-
});
|
|
1167
|
-
it("forwards ACode thinking deltas as realtime thinking provider events", async () => {
|
|
1168
|
-
const adapter = new AcodeSdkAdapter();
|
|
1169
|
-
adapter.setRuntimeConstructorForTest(ThinkingDeltaRuntime);
|
|
1170
|
-
const events = [];
|
|
1171
|
-
adapter.on("event", (event) => events.push(event));
|
|
1172
|
-
await adapter.startSession("session-thinking-delta", {
|
|
1173
|
-
command: "acode",
|
|
1174
|
-
workingDirectory: process.cwd(),
|
|
1175
|
-
autoAccept: true,
|
|
1176
|
-
extraMcpServers: {},
|
|
1177
|
-
envOverrides: {},
|
|
1178
|
-
});
|
|
1179
|
-
await adapter.sendMessage("session-thinking-delta", "think aloud");
|
|
1180
|
-
expect(events).toHaveLength(1);
|
|
1181
|
-
expect(events[0]).toMatchObject({
|
|
1182
|
-
type: "thinking",
|
|
1183
|
-
data: { text: "reasoning chunk" },
|
|
1184
|
-
});
|
|
1185
|
-
});
|
|
1186
|
-
it("forwards ACode assistant deltas as realtime text provider events", async () => {
|
|
1187
|
-
const adapter = new AcodeSdkAdapter();
|
|
1188
|
-
adapter.setRuntimeConstructorForTest(AssistantDeltaRuntime);
|
|
1189
|
-
const events = [];
|
|
1190
|
-
adapter.on("event", (event) => events.push(event));
|
|
1191
|
-
await adapter.startSession("session-assistant-delta", {
|
|
1192
|
-
command: "acode",
|
|
1193
|
-
workingDirectory: process.cwd(),
|
|
1194
|
-
autoAccept: true,
|
|
1195
|
-
extraMcpServers: {},
|
|
1196
|
-
envOverrides: {},
|
|
1197
|
-
});
|
|
1198
|
-
await adapter.sendMessage("session-assistant-delta", "answer now");
|
|
1199
|
-
expect(events).toHaveLength(1);
|
|
1200
|
-
expect(events[0]).toMatchObject({
|
|
1201
|
-
type: "text_delta",
|
|
1202
|
-
data: { text: "answer chunk" },
|
|
1203
|
-
});
|
|
1204
|
-
});
|
|
1205
|
-
it("forwards ACode command output as ordered realtime provider events", async () => {
|
|
1206
|
-
const adapter = new AcodeSdkAdapter();
|
|
1207
|
-
adapter.setRuntimeConstructorForTest(CommandOutputRuntime);
|
|
1208
|
-
const events = [];
|
|
1209
|
-
adapter.on("event", (event) => events.push(event));
|
|
1210
|
-
await adapter.startSession("session-command-output", {
|
|
1211
|
-
command: "acode",
|
|
1212
|
-
workingDirectory: process.cwd(),
|
|
1213
|
-
autoAccept: true,
|
|
1214
|
-
extraMcpServers: {},
|
|
1215
|
-
envOverrides: {},
|
|
1216
|
-
});
|
|
1217
|
-
await adapter.sendMessage("session-command-output", "run command");
|
|
1218
|
-
expect(events).toHaveLength(1);
|
|
1219
|
-
expect(events[0]).toMatchObject({
|
|
1220
|
-
type: "command_output",
|
|
1221
|
-
data: {
|
|
1222
|
-
text: "live command output",
|
|
1223
|
-
stream: "stdout",
|
|
1224
|
-
sequence: 2,
|
|
1225
|
-
cwd: "/repo",
|
|
1226
|
-
commandLineCount: 1,
|
|
1227
|
-
commandFilePath: "/repo/.agentswork/acode/cmd-dist/run.cmd",
|
|
1228
|
-
},
|
|
1229
|
-
});
|
|
1230
|
-
});
|
|
1231
|
-
it("preserves realtime thinking then answer delta order", async () => {
|
|
1232
|
-
const adapter = new AcodeSdkAdapter();
|
|
1233
|
-
adapter.setRuntimeConstructorForTest(ThinkingThenAssistantDeltaRuntime);
|
|
1234
|
-
const events = [];
|
|
1235
|
-
adapter.on("event", (event) => events.push(event));
|
|
1236
|
-
await adapter.startSession("session-thinking-answer-order", {
|
|
1237
|
-
command: "acode",
|
|
1238
|
-
workingDirectory: process.cwd(),
|
|
1239
|
-
autoAccept: true,
|
|
1240
|
-
extraMcpServers: {},
|
|
1241
|
-
envOverrides: {},
|
|
1242
|
-
});
|
|
1243
|
-
await adapter.sendMessage("session-thinking-answer-order", "think then answer");
|
|
1244
|
-
expect(events.map((event) => event.type)).toEqual([
|
|
1245
|
-
"thinking",
|
|
1246
|
-
"text_delta",
|
|
1247
|
-
]);
|
|
1248
|
-
expect(events.map((event) => event.data.text)).toEqual([
|
|
1249
|
-
"thinking chunk",
|
|
1250
|
-
"answer chunk",
|
|
1251
|
-
]);
|
|
1252
|
-
});
|
|
1253
|
-
it("recovers to waiting_input when ACode submit resolves without a terminal turn event", async () => {
|
|
1254
|
-
ResolvedWithoutTerminalEventRuntime.submitMessages = [];
|
|
1255
|
-
const adapter = new AcodeSdkAdapter();
|
|
1256
|
-
adapter.setRuntimeConstructorForTest(ResolvedWithoutTerminalEventRuntime);
|
|
1257
|
-
const statuses = [];
|
|
1258
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1259
|
-
await adapter.startSession("session-resolved-no-terminal", {
|
|
1260
|
-
command: "acode",
|
|
1261
|
-
workingDirectory: process.cwd(),
|
|
1262
|
-
autoAccept: true,
|
|
1263
|
-
extraMcpServers: {},
|
|
1264
|
-
envOverrides: {},
|
|
1265
|
-
});
|
|
1266
|
-
await adapter.sendMessage("session-resolved-no-terminal", "/review src");
|
|
1267
|
-
expect(ResolvedWithoutTerminalEventRuntime.submitMessages).toEqual([
|
|
1268
|
-
"/review src",
|
|
1269
|
-
]);
|
|
1270
|
-
expect(statuses).toEqual([
|
|
1271
|
-
"starting",
|
|
1272
|
-
"waiting_input",
|
|
1273
|
-
"thinking",
|
|
1274
|
-
"waiting_input",
|
|
1275
|
-
]);
|
|
1276
|
-
expect(adapter.getSessionStatus("session-resolved-no-terminal")).toBe("waiting_input");
|
|
1277
|
-
});
|
|
1278
|
-
it("forwards session_complete status after MCP calls so UI can clear active tool state", async () => {
|
|
1279
|
-
const adapter = new AcodeSdkAdapter();
|
|
1280
|
-
adapter.setRuntimeConstructorForTest(McpThenSessionCompleteRuntime);
|
|
1281
|
-
const events = [];
|
|
1282
|
-
const statuses = [];
|
|
1283
|
-
adapter.on("event", (event) => events.push(event));
|
|
1284
|
-
adapter.on("status-change", (_sessionId, status, actionInfo) => {
|
|
1285
|
-
statuses.push({
|
|
1286
|
-
status,
|
|
1287
|
-
actionType: actionInfo?.actionType,
|
|
1288
|
-
actionDetail: actionInfo?.actionDetail,
|
|
1289
|
-
});
|
|
1290
|
-
});
|
|
1291
|
-
await adapter.startSession("session-mcp-complete", {
|
|
1292
|
-
command: "acode",
|
|
1293
|
-
workingDirectory: process.cwd(),
|
|
1294
|
-
autoAccept: true,
|
|
1295
|
-
extraMcpServers: {},
|
|
1296
|
-
envOverrides: {},
|
|
1297
|
-
});
|
|
1298
|
-
await adapter.sendMessage("session-mcp-complete", "send private");
|
|
1299
|
-
expect(events.map((event) => event.type)).toEqual([
|
|
1300
|
-
"tool_use_start",
|
|
1301
|
-
"session_complete",
|
|
1302
|
-
]);
|
|
1303
|
-
expect(statuses.at(-2)).toEqual({
|
|
1304
|
-
status: "tool_using",
|
|
1305
|
-
actionType: "mcp",
|
|
1306
|
-
actionDetail: "aws-mcp/send_private",
|
|
1307
|
-
});
|
|
1308
|
-
expect(statuses.at(-1)).toEqual({
|
|
1309
|
-
status: "completed",
|
|
1310
|
-
actionType: undefined,
|
|
1311
|
-
actionDetail: undefined,
|
|
1312
|
-
});
|
|
1313
|
-
expect(adapter.getSessionStatus("session-mcp-complete")).toBe("completed");
|
|
1314
|
-
});
|
|
1315
|
-
it("does not block launch completion while the initial prompt runs in the background", async () => {
|
|
1316
|
-
DeferredInitialPromptRuntime.submitCalls = 0;
|
|
1317
|
-
DeferredInitialPromptRuntime.receivedInitialPrompt = undefined;
|
|
1318
|
-
const adapter = new AcodeSdkAdapter();
|
|
1319
|
-
adapter.setRuntimeConstructorForTest(DeferredInitialPromptRuntime);
|
|
1320
|
-
const statuses = [];
|
|
1321
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1322
|
-
await adapter.startSession("session-initial-prompt", {
|
|
1323
|
-
command: "acode",
|
|
1324
|
-
workingDirectory: process.cwd(),
|
|
1325
|
-
autoAccept: true,
|
|
1326
|
-
initialPrompt: "start working loop",
|
|
1327
|
-
extraMcpServers: {},
|
|
1328
|
-
envOverrides: {},
|
|
1329
|
-
});
|
|
1330
|
-
expect(DeferredInitialPromptRuntime.receivedInitialPrompt).toBeUndefined();
|
|
1331
|
-
expect(DeferredInitialPromptRuntime.submitCalls).toBe(1);
|
|
1332
|
-
expect(statuses).toEqual(["starting", "waiting_input", "thinking"]);
|
|
1333
|
-
expect(adapter.getSessionStatus("session-initial-prompt")).toBe("thinking");
|
|
1334
|
-
});
|
|
1335
|
-
it("runs regular idle commands configured while ACode is still busy", async () => {
|
|
1336
|
-
vi.useFakeTimers();
|
|
1337
|
-
DeferredInitialThenRegularIdleRuntime.submitMessages = [];
|
|
1338
|
-
DeferredInitialThenRegularIdleRuntime.completeInitialTurn = undefined;
|
|
1339
|
-
const adapter = new AcodeSdkAdapter();
|
|
1340
|
-
adapter.setRuntimeConstructorForTest(DeferredInitialThenRegularIdleRuntime);
|
|
1341
|
-
try {
|
|
1342
|
-
await adapter.startSession("session-regular-idle-busy-config", {
|
|
1343
|
-
command: "acode",
|
|
1344
|
-
workingDirectory: process.cwd(),
|
|
1345
|
-
autoAccept: true,
|
|
1346
|
-
initialPrompt: "bootstrap current task",
|
|
1347
|
-
extraMcpServers: {},
|
|
1348
|
-
envOverrides: {},
|
|
1349
|
-
});
|
|
1350
|
-
expect(DeferredInitialThenRegularIdleRuntime.submitMessages).toEqual([
|
|
1351
|
-
"bootstrap current task",
|
|
1352
|
-
]);
|
|
1353
|
-
expect(adapter.getSessionStatus("session-regular-idle-busy-config")).toBe("thinking");
|
|
1354
|
-
adapter.setIdleCommands("session-regular-idle-busy-config", {
|
|
1355
|
-
idleInputCommand: "continue after idle",
|
|
1356
|
-
nonInputCommand: "",
|
|
1357
|
-
});
|
|
1358
|
-
const completeInitialTurn = DeferredInitialThenRegularIdleRuntime.completeInitialTurn;
|
|
1359
|
-
expect(completeInitialTurn).toBeDefined();
|
|
1360
|
-
completeInitialTurn?.();
|
|
1361
|
-
await vi.advanceTimersByTimeAsync(1500);
|
|
1362
|
-
expect(DeferredInitialThenRegularIdleRuntime.submitMessages).toEqual([
|
|
1363
|
-
"bootstrap current task",
|
|
1364
|
-
"continue after idle",
|
|
1365
|
-
]);
|
|
1366
|
-
}
|
|
1367
|
-
finally {
|
|
1368
|
-
vi.useRealTimers();
|
|
1369
|
-
adapter.cleanup();
|
|
1370
|
-
}
|
|
1371
|
-
});
|
|
1372
|
-
it("continues the message loop by auto-submitting poll_message after a completed turn", async () => {
|
|
1373
|
-
vi.useFakeTimers();
|
|
1374
|
-
AutoPollRuntime.submitMessages = [];
|
|
1375
|
-
const adapter = new AcodeSdkAdapter();
|
|
1376
|
-
adapter.setRuntimeConstructorForTest(AutoPollRuntime);
|
|
1377
|
-
const statuses = [];
|
|
1378
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1379
|
-
try {
|
|
1380
|
-
await adapter.startSession("session-auto-poll", {
|
|
1381
|
-
command: "acode",
|
|
1382
|
-
workingDirectory: process.cwd(),
|
|
1383
|
-
autoAccept: true,
|
|
1384
|
-
extraMcpServers: {},
|
|
1385
|
-
envOverrides: {},
|
|
1386
|
-
});
|
|
1387
|
-
await adapter.sendMessage("session-auto-poll", "handle current work");
|
|
1388
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1389
|
-
expect(AutoPollRuntime.submitMessages).toHaveLength(2);
|
|
1390
|
-
expect(AutoPollRuntime.submitMessages[0]).toBe("handle current work");
|
|
1391
|
-
expect(AutoPollRuntime.submitMessages[1]).toContain("poll_message");
|
|
1392
|
-
expect(statuses).toEqual([
|
|
1393
|
-
"starting",
|
|
1394
|
-
"waiting_input",
|
|
1395
|
-
"thinking",
|
|
1396
|
-
"waiting_input",
|
|
1397
|
-
]);
|
|
1398
|
-
expect(adapter.getSessionStatus("session-auto-poll")).toBe("waiting_input");
|
|
1399
|
-
}
|
|
1400
|
-
finally {
|
|
1401
|
-
vi.useRealTimers();
|
|
1402
|
-
adapter.cleanup();
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
it("keeps waiting_input while dispatching an auto-poll prompt", async () => {
|
|
1406
|
-
vi.useFakeTimers();
|
|
1407
|
-
DeferredInitialPromptRuntime.submitCalls = 0;
|
|
1408
|
-
const adapter = new AcodeSdkAdapter();
|
|
1409
|
-
adapter.setRuntimeConstructorForTest(DeferredInitialPromptRuntime);
|
|
1410
|
-
const statuses = [];
|
|
1411
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1412
|
-
try {
|
|
1413
|
-
await adapter.startSession("session-auto-poll-pending", {
|
|
1414
|
-
command: "acode",
|
|
1415
|
-
workingDirectory: process.cwd(),
|
|
1416
|
-
autoAccept: true,
|
|
1417
|
-
extraMcpServers: {},
|
|
1418
|
-
envOverrides: {},
|
|
1419
|
-
});
|
|
1420
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1421
|
-
expect(DeferredInitialPromptRuntime.submitCalls).toBe(1);
|
|
1422
|
-
expect(adapter.getSessionStatus("session-auto-poll-pending")).toBe("waiting_input");
|
|
1423
|
-
expect(statuses).toEqual(["starting", "waiting_input"]);
|
|
1424
|
-
}
|
|
1425
|
-
finally {
|
|
1426
|
-
vi.useRealTimers();
|
|
1427
|
-
adapter.cleanup();
|
|
1428
|
-
}
|
|
1429
|
-
});
|
|
1430
|
-
it("stops re-arming auto-poll when the model answers without calling poll_message", async () => {
|
|
1431
|
-
vi.useFakeTimers();
|
|
1432
|
-
NonPollingAutoPollRuntime.submitMessages = [];
|
|
1433
|
-
const adapter = new AcodeSdkAdapter();
|
|
1434
|
-
adapter.setRuntimeConstructorForTest(NonPollingAutoPollRuntime);
|
|
1435
|
-
try {
|
|
1436
|
-
await adapter.startSession("session-non-polling-auto-poll", {
|
|
1437
|
-
command: "acode",
|
|
1438
|
-
workingDirectory: process.cwd(),
|
|
1439
|
-
autoAccept: true,
|
|
1440
|
-
extraMcpServers: {},
|
|
1441
|
-
envOverrides: {},
|
|
1442
|
-
});
|
|
1443
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1444
|
-
expect(NonPollingAutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1445
|
-
expect(NonPollingAutoPollRuntime.submitMessages[0]).toContain("poll_message");
|
|
1446
|
-
await vi.advanceTimersByTimeAsync(5_000);
|
|
1447
|
-
expect(NonPollingAutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1448
|
-
expect(adapter.getSessionStatus("session-non-polling-auto-poll")).toBe("waiting_input");
|
|
1449
|
-
}
|
|
1450
|
-
finally {
|
|
1451
|
-
vi.useRealTimers();
|
|
1452
|
-
adapter.cleanup();
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
it("keeps re-arming auto-poll when the model actually calls poll_message", async () => {
|
|
1456
|
-
vi.useFakeTimers();
|
|
1457
|
-
PollingAutoPollRuntime.submitMessages = [];
|
|
1458
|
-
const adapter = new AcodeSdkAdapter();
|
|
1459
|
-
adapter.setRuntimeConstructorForTest(PollingAutoPollRuntime);
|
|
1460
|
-
try {
|
|
1461
|
-
await adapter.startSession("session-polling-auto-poll", {
|
|
1462
|
-
command: "acode",
|
|
1463
|
-
workingDirectory: process.cwd(),
|
|
1464
|
-
autoAccept: true,
|
|
1465
|
-
extraMcpServers: {},
|
|
1466
|
-
envOverrides: {},
|
|
1467
|
-
});
|
|
1468
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1469
|
-
expect(PollingAutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1470
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1471
|
-
expect(PollingAutoPollRuntime.submitMessages).toHaveLength(2);
|
|
1472
|
-
}
|
|
1473
|
-
finally {
|
|
1474
|
-
vi.useRealTimers();
|
|
1475
|
-
adapter.cleanup();
|
|
1476
|
-
}
|
|
1477
|
-
});
|
|
1478
|
-
it("keeps re-arming auto-poll when a poll turn completes without assistant text", async () => {
|
|
1479
|
-
vi.useFakeTimers();
|
|
1480
|
-
AutoPollRuntime.submitMessages = [];
|
|
1481
|
-
const adapter = new AcodeSdkAdapter();
|
|
1482
|
-
adapter.setRuntimeConstructorForTest(AutoPollRuntime);
|
|
1483
|
-
try {
|
|
1484
|
-
await adapter.startSession("session-empty-auto-poll-turn", {
|
|
1485
|
-
command: "acode",
|
|
1486
|
-
workingDirectory: process.cwd(),
|
|
1487
|
-
autoAccept: true,
|
|
1488
|
-
extraMcpServers: {},
|
|
1489
|
-
envOverrides: {},
|
|
1490
|
-
});
|
|
1491
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1492
|
-
expect(AutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1493
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1494
|
-
expect(AutoPollRuntime.submitMessages).toHaveLength(2);
|
|
1495
|
-
expect(AutoPollRuntime.submitMessages[1]).toContain("poll_message");
|
|
1496
|
-
}
|
|
1497
|
-
finally {
|
|
1498
|
-
vi.useRealTimers();
|
|
1499
|
-
adapter.cleanup();
|
|
1500
|
-
}
|
|
1501
|
-
});
|
|
1502
|
-
it("cancels a pending auto-poll when a user message arrives", async () => {
|
|
1503
|
-
vi.useFakeTimers();
|
|
1504
|
-
AutoPollRuntime.submitMessages = [];
|
|
1505
|
-
const adapter = new AcodeSdkAdapter();
|
|
1506
|
-
adapter.setRuntimeConstructorForTest(AutoPollRuntime);
|
|
1507
|
-
try {
|
|
1508
|
-
await adapter.startSession("session-cancel-auto-poll", {
|
|
1509
|
-
command: "acode",
|
|
1510
|
-
workingDirectory: process.cwd(),
|
|
1511
|
-
autoAccept: true,
|
|
1512
|
-
extraMcpServers: {},
|
|
1513
|
-
envOverrides: {},
|
|
1514
|
-
});
|
|
1515
|
-
await adapter.sendMessage("session-cancel-auto-poll", "handle current work");
|
|
1516
|
-
await adapter.sendMessage("session-cancel-auto-poll", "user follow up");
|
|
1517
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1518
|
-
expect(AutoPollRuntime.submitMessages).toEqual([
|
|
1519
|
-
"handle current work",
|
|
1520
|
-
"user follow up",
|
|
1521
|
-
expect.stringContaining("poll_message"),
|
|
1522
|
-
]);
|
|
1523
|
-
expect(adapter.getSessionStatus("session-cancel-auto-poll")).toBe("waiting_input");
|
|
1524
|
-
}
|
|
1525
|
-
finally {
|
|
1526
|
-
vi.useRealTimers();
|
|
1527
|
-
adapter.cleanup();
|
|
1528
|
-
}
|
|
1529
|
-
});
|
|
1530
|
-
it("does not schedule auto-poll after terminal session status", async () => {
|
|
1531
|
-
vi.useFakeTimers();
|
|
1532
|
-
TerminalAfterTurnRuntime.submitMessages = [];
|
|
1533
|
-
const adapter = new AcodeSdkAdapter();
|
|
1534
|
-
adapter.setRuntimeConstructorForTest(TerminalAfterTurnRuntime);
|
|
1535
|
-
try {
|
|
1536
|
-
await adapter.startSession("session-terminal-auto-poll", {
|
|
1537
|
-
command: "acode",
|
|
1538
|
-
workingDirectory: process.cwd(),
|
|
1539
|
-
autoAccept: true,
|
|
1540
|
-
extraMcpServers: {},
|
|
1541
|
-
envOverrides: {},
|
|
1542
|
-
});
|
|
1543
|
-
await adapter.sendMessage("session-terminal-auto-poll", "complete");
|
|
1544
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1545
|
-
expect(TerminalAfterTurnRuntime.submitMessages).toEqual(["complete"]);
|
|
1546
|
-
expect(adapter.getSessionStatus("session-terminal-auto-poll")).toBe("completed");
|
|
1547
|
-
}
|
|
1548
|
-
finally {
|
|
1549
|
-
vi.useRealTimers();
|
|
1550
|
-
adapter.cleanup();
|
|
1551
|
-
}
|
|
1552
|
-
});
|
|
1553
|
-
it("arms auto-poll when session is started without an initial prompt (R1)", async () => {
|
|
1554
|
-
vi.useFakeTimers();
|
|
1555
|
-
AutoPollRuntime.submitMessages = [];
|
|
1556
|
-
const adapter = new AcodeSdkAdapter();
|
|
1557
|
-
adapter.setRuntimeConstructorForTest(AutoPollRuntime);
|
|
1558
|
-
const statuses = [];
|
|
1559
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1560
|
-
try {
|
|
1561
|
-
// No initialPrompt: previously the session sat at 'waiting_input'
|
|
1562
|
-
// forever because no auto-poll timer was armed and no setIdleCommands
|
|
1563
|
-
// was called yet. R1 fix arms the auto-poll on startSession instead.
|
|
1564
|
-
await adapter.startSession("session-r1-no-initial", {
|
|
1565
|
-
command: "acode",
|
|
1566
|
-
workingDirectory: process.cwd(),
|
|
1567
|
-
autoAccept: true,
|
|
1568
|
-
extraMcpServers: {},
|
|
1569
|
-
envOverrides: {},
|
|
1570
|
-
});
|
|
1571
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1572
|
-
// Status sequence: starting -> waiting_input (startSession) ->
|
|
1573
|
-
// waiting_input (poll turn completes and re-arms the loop). The
|
|
1574
|
-
// auto-poll prompt stays in waiting_input so a slow or missing
|
|
1575
|
-
// poll_message tool event cannot leave the dashboard on thinking.
|
|
1576
|
-
expect(AutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1577
|
-
expect(AutoPollRuntime.submitMessages[0]).toContain("poll_message");
|
|
1578
|
-
expect(statuses).toEqual(["starting", "waiting_input", "waiting_input"]);
|
|
1579
|
-
expect(adapter.getSessionStatus("session-r1-no-initial")).toBe("waiting_input");
|
|
1580
|
-
}
|
|
1581
|
-
finally {
|
|
1582
|
-
vi.useRealTimers();
|
|
1583
|
-
adapter.cleanup();
|
|
1584
|
-
}
|
|
1585
|
-
});
|
|
1586
|
-
it("recovers a polling-driven session to waiting_input after a failed turn and re-arms with a longer backoff (R2)", async () => {
|
|
1587
|
-
vi.useFakeTimers();
|
|
1588
|
-
FailingAutoPollRuntime.submitMessages = [];
|
|
1589
|
-
const adapter = new AcodeSdkAdapter();
|
|
1590
|
-
adapter.setRuntimeConstructorForTest(FailingAutoPollRuntime);
|
|
1591
|
-
const statuses = [];
|
|
1592
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1593
|
-
try {
|
|
1594
|
-
await adapter.startSession("session-r2-recover-failed", {
|
|
1595
|
-
command: "acode",
|
|
1596
|
-
workingDirectory: process.cwd(),
|
|
1597
|
-
autoAccept: true,
|
|
1598
|
-
extraMcpServers: {},
|
|
1599
|
-
envOverrides: {},
|
|
1600
|
-
});
|
|
1601
|
-
// R1 arms the auto-poll; advance the normal 250ms delay to fire it.
|
|
1602
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1603
|
-
expect(FailingAutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1604
|
-
// R2 fix runs synchronously during the failing submit: the
|
|
1605
|
-
// 'error' event sets status to 'error', then the 'turn_complete'
|
|
1606
|
-
// (failed) event triggers the recovery path which flips status
|
|
1607
|
-
// back to 'waiting_input' and schedules a 2s delayed re-arm.
|
|
1608
|
-
expect(adapter.getSessionStatus("session-r2-recover-failed")).toBe("waiting_input");
|
|
1609
|
-
expect(statuses).toEqual([
|
|
1610
|
-
"starting",
|
|
1611
|
-
"waiting_input",
|
|
1612
|
-
"error",
|
|
1613
|
-
"waiting_input",
|
|
1614
|
-
]);
|
|
1615
|
-
expect(statuses.at(-1)).toBe("waiting_input");
|
|
1616
|
-
// 250ms after recovery is too short for the 2s backoff.
|
|
1617
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1618
|
-
expect(FailingAutoPollRuntime.submitMessages).toHaveLength(1);
|
|
1619
|
-
// After the 2s backoff elapses, the recovery timer fires and submits
|
|
1620
|
-
// a new poll prompt, restoring the message loop.
|
|
1621
|
-
await vi.advanceTimersByTimeAsync(2000);
|
|
1622
|
-
expect(FailingAutoPollRuntime.submitMessages.length).toBeGreaterThanOrEqual(2);
|
|
1623
|
-
expect(FailingAutoPollRuntime.submitMessages[1]).toContain("poll_message");
|
|
1624
|
-
}
|
|
1625
|
-
finally {
|
|
1626
|
-
vi.useRealTimers();
|
|
1627
|
-
adapter.cleanup();
|
|
1628
|
-
}
|
|
1629
|
-
});
|
|
1630
|
-
it("recovers from a bare ACode error event so auto-poll does not get stuck terminal", async () => {
|
|
1631
|
-
vi.useFakeTimers();
|
|
1632
|
-
BareErrorRuntime.submitMessages = [];
|
|
1633
|
-
const adapter = new AcodeSdkAdapter();
|
|
1634
|
-
adapter.setRuntimeConstructorForTest(BareErrorRuntime);
|
|
1635
|
-
const statuses = [];
|
|
1636
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1637
|
-
try {
|
|
1638
|
-
await adapter.startSession("session-bare-error", {
|
|
1639
|
-
command: "acode",
|
|
1640
|
-
workingDirectory: process.cwd(),
|
|
1641
|
-
autoAccept: true,
|
|
1642
|
-
extraMcpServers: {},
|
|
1643
|
-
envOverrides: {},
|
|
1644
|
-
});
|
|
1645
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1646
|
-
expect(BareErrorRuntime.submitMessages).toHaveLength(1);
|
|
1647
|
-
expect(adapter.getSessionStatus("session-bare-error")).toBe("waiting_input");
|
|
1648
|
-
expect(statuses).toEqual([
|
|
1649
|
-
"starting",
|
|
1650
|
-
"waiting_input",
|
|
1651
|
-
"error",
|
|
1652
|
-
"waiting_input",
|
|
1653
|
-
]);
|
|
1654
|
-
await vi.advanceTimersByTimeAsync(1999);
|
|
1655
|
-
expect(BareErrorRuntime.submitMessages).toHaveLength(1);
|
|
1656
|
-
await vi.advanceTimersByTimeAsync(1);
|
|
1657
|
-
expect(BareErrorRuntime.submitMessages).toHaveLength(2);
|
|
1658
|
-
expect(BareErrorRuntime.submitMessages[1]).toContain("poll_message");
|
|
1659
|
-
}
|
|
1660
|
-
finally {
|
|
1661
|
-
vi.useRealTimers();
|
|
1662
|
-
adapter.cleanup();
|
|
1663
|
-
}
|
|
1664
|
-
});
|
|
1665
|
-
it("recovers from direct runtime submit rejection during user turns", async () => {
|
|
1666
|
-
vi.useFakeTimers();
|
|
1667
|
-
RejectingSubmitRuntime.submitMessages = [];
|
|
1668
|
-
const adapter = new AcodeSdkAdapter();
|
|
1669
|
-
adapter.setRuntimeConstructorForTest(RejectingSubmitRuntime);
|
|
1670
|
-
const statuses = [];
|
|
1671
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1672
|
-
try {
|
|
1673
|
-
await adapter.startSession("session-user-submit-reject", {
|
|
1674
|
-
command: "acode",
|
|
1675
|
-
workingDirectory: process.cwd(),
|
|
1676
|
-
autoAccept: true,
|
|
1677
|
-
extraMcpServers: {},
|
|
1678
|
-
envOverrides: {},
|
|
1679
|
-
});
|
|
1680
|
-
await adapter.sendMessage("session-user-submit-reject", "user work");
|
|
1681
|
-
expect(RejectingSubmitRuntime.submitMessages).toEqual(["user work"]);
|
|
1682
|
-
expect(adapter.getSessionStatus("session-user-submit-reject")).toBe("waiting_input");
|
|
1683
|
-
expect(statuses).toEqual([
|
|
1684
|
-
"starting",
|
|
1685
|
-
"waiting_input",
|
|
1686
|
-
"thinking",
|
|
1687
|
-
"error",
|
|
1688
|
-
"waiting_input",
|
|
1689
|
-
]);
|
|
1690
|
-
await vi.advanceTimersByTimeAsync(2000);
|
|
1691
|
-
expect(RejectingSubmitRuntime.submitMessages[1]).toContain("poll_message");
|
|
1692
|
-
}
|
|
1693
|
-
finally {
|
|
1694
|
-
vi.useRealTimers();
|
|
1695
|
-
adapter.cleanup();
|
|
1696
|
-
}
|
|
1697
|
-
});
|
|
1698
|
-
it("does not let stale failed-turn recovery poll after a user follow-up arrives", async () => {
|
|
1699
|
-
vi.useFakeTimers();
|
|
1700
|
-
FailingPollThenSuccessfulUserRuntime.submitMessages = [];
|
|
1701
|
-
const adapter = new AcodeSdkAdapter();
|
|
1702
|
-
adapter.setRuntimeConstructorForTest(FailingPollThenSuccessfulUserRuntime);
|
|
1703
|
-
try {
|
|
1704
|
-
await adapter.startSession("session-stale-recovery-user", {
|
|
1705
|
-
command: "acode",
|
|
1706
|
-
workingDirectory: process.cwd(),
|
|
1707
|
-
autoAccept: true,
|
|
1708
|
-
extraMcpServers: {},
|
|
1709
|
-
envOverrides: {},
|
|
1710
|
-
});
|
|
1711
|
-
await vi.advanceTimersByTimeAsync(250);
|
|
1712
|
-
expect(FailingPollThenSuccessfulUserRuntime.submitMessages).toHaveLength(1);
|
|
1713
|
-
await adapter.sendMessage("session-stale-recovery-user", "human follow up");
|
|
1714
|
-
await vi.advanceTimersByTimeAsync(2000);
|
|
1715
|
-
expect(FailingPollThenSuccessfulUserRuntime.submitMessages).toEqual([
|
|
1716
|
-
expect.stringContaining("poll_message"),
|
|
1717
|
-
"human follow up",
|
|
1718
|
-
]);
|
|
1719
|
-
expect(adapter.getSessionStatus("session-stale-recovery-user")).toBe("completed");
|
|
1720
|
-
}
|
|
1721
|
-
finally {
|
|
1722
|
-
vi.useRealTimers();
|
|
1723
|
-
adapter.cleanup();
|
|
1724
|
-
}
|
|
1725
|
-
});
|
|
1726
|
-
it("cleans up sessions when ACode runtime fails during startup", async () => {
|
|
1727
|
-
const adapter = new AcodeSdkAdapter();
|
|
1728
|
-
adapter.setRuntimeConstructorForTest(StartFailureRuntime);
|
|
1729
|
-
const statuses = [];
|
|
1730
|
-
adapter.on("status-change", (_sessionId, status) => statuses.push(status));
|
|
1731
|
-
await expect(adapter.startSession("session-start-failure", {
|
|
1732
|
-
command: "acode",
|
|
1733
|
-
workingDirectory: process.cwd(),
|
|
1734
|
-
autoAccept: true,
|
|
1735
|
-
extraMcpServers: {},
|
|
1736
|
-
envOverrides: {},
|
|
1737
|
-
})).rejects.toThrow("runtime start failed");
|
|
1738
|
-
expect(adapter.hasSession("session-start-failure")).toBe(false);
|
|
1739
|
-
expect(adapter.getSessionStatus("session-start-failure")).toBeUndefined();
|
|
1740
|
-
expect(statuses).toEqual(["starting", "error"]);
|
|
1741
|
-
});
|
|
1742
|
-
it("logs a warning but does not emit a fatal error event when poll_message is missing (R3)", async () => {
|
|
1743
|
-
StartupMcpStatusRuntime.includeAwsMcp = false;
|
|
1744
|
-
const adapter = new AcodeSdkAdapter();
|
|
1745
|
-
adapter.setRuntimeConstructorForTest(StartupMcpStatusRuntime);
|
|
1746
|
-
const events = [];
|
|
1747
|
-
const consoleWarn = vi
|
|
1748
|
-
.spyOn(console, "warn")
|
|
1749
|
-
.mockImplementation(() => undefined);
|
|
1750
|
-
adapter.on("event", (event) => events.push(event));
|
|
1751
|
-
try {
|
|
1752
|
-
await adapter.startSession("session-r3-no-poll-message", {
|
|
1753
|
-
command: "acode",
|
|
1754
|
-
workingDirectory: process.cwd(),
|
|
1755
|
-
autoAccept: true,
|
|
1756
|
-
extraMcpServers: {},
|
|
1757
|
-
envOverrides: {},
|
|
1758
|
-
});
|
|
1759
|
-
// R3 fix: a missing poll_message tool is a configuration smell
|
|
1760
|
-
// (the LLM will fail when it tries to call it), but it must NOT
|
|
1761
|
-
// be promoted to a session-level 'error' event. Doing so would
|
|
1762
|
-
// lock the dashboard timeline into a permanent '发生错误' state
|
|
1763
|
-
// even though the LLM itself never reports a real error.
|
|
1764
|
-
expect(events.find((event) => event.type === "error")).toBeUndefined();
|
|
1765
|
-
expect(consoleWarn).toHaveBeenCalled();
|
|
1766
|
-
const warnText = String(consoleWarn.mock.calls[0]?.[0] ?? "");
|
|
1767
|
-
expect(warnText).toContain("poll_message");
|
|
1768
|
-
}
|
|
1769
|
-
finally {
|
|
1770
|
-
consoleWarn.mockRestore();
|
|
1771
|
-
adapter.cleanup();
|
|
1772
|
-
}
|
|
1773
|
-
});
|
|
1774
|
-
it("does not log a warning or emit an error event when poll_message is exposed (R3 negative)", async () => {
|
|
1775
|
-
StartupMcpStatusRuntime.includeAwsMcp = true;
|
|
1776
|
-
const adapter = new AcodeSdkAdapter();
|
|
1777
|
-
adapter.setRuntimeConstructorForTest(StartupMcpStatusRuntime);
|
|
1778
|
-
const events = [];
|
|
1779
|
-
const consoleWarn = vi
|
|
1780
|
-
.spyOn(console, "warn")
|
|
1781
|
-
.mockImplementation(() => undefined);
|
|
1782
|
-
adapter.on("event", (event) => events.push(event));
|
|
1783
|
-
try {
|
|
1784
|
-
await adapter.startSession("session-r3-has-poll-message", {
|
|
1785
|
-
command: "acode",
|
|
1786
|
-
workingDirectory: process.cwd(),
|
|
1787
|
-
autoAccept: true,
|
|
1788
|
-
extraMcpServers: {},
|
|
1789
|
-
envOverrides: {},
|
|
1790
|
-
});
|
|
1791
|
-
expect(events.find((event) => event.type === "error")).toBeUndefined();
|
|
1792
|
-
expect(consoleWarn).not.toHaveBeenCalled();
|
|
1793
|
-
}
|
|
1794
|
-
finally {
|
|
1795
|
-
consoleWarn.mockRestore();
|
|
1796
|
-
adapter.cleanup();
|
|
1797
|
-
}
|
|
1798
|
-
});
|
|
1799
|
-
it("does not treat tools that only contain poll_message as a real listener", async () => {
|
|
1800
|
-
const adapter = new AcodeSdkAdapter();
|
|
1801
|
-
adapter.setRuntimeConstructorForTest(FakePollMessageToolRuntime);
|
|
1802
|
-
const events = [];
|
|
1803
|
-
const consoleWarn = vi
|
|
1804
|
-
.spyOn(console, "warn")
|
|
1805
|
-
.mockImplementation(() => undefined);
|
|
1806
|
-
adapter.on("event", (event) => events.push(event));
|
|
1807
|
-
try {
|
|
1808
|
-
await adapter.startSession("session-r3-fake-poll-message", {
|
|
1809
|
-
command: "acode",
|
|
1810
|
-
workingDirectory: process.cwd(),
|
|
1811
|
-
autoAccept: true,
|
|
1812
|
-
extraMcpServers: {},
|
|
1813
|
-
envOverrides: {},
|
|
1814
|
-
});
|
|
1815
|
-
expect(events.find((event) => event.type === "error")).toBeUndefined();
|
|
1816
|
-
expect(consoleWarn).toHaveBeenCalled();
|
|
1817
|
-
expect(String(consoleWarn.mock.calls[0]?.[0] ?? "")).toContain("poll_message");
|
|
1818
|
-
}
|
|
1819
|
-
finally {
|
|
1820
|
-
consoleWarn.mockRestore();
|
|
1821
|
-
adapter.cleanup();
|
|
1822
|
-
}
|
|
1823
|
-
});
|
|
1824
|
-
it("does not log a warning when the MCP server is named differently but still exposes poll_message (R3 user-scenario)", async () => {
|
|
1825
|
-
// Reproduces the production scenario from the bug report: the user
|
|
1826
|
-
// configures the AWS MCP under a non-default name (e.g. 'aws-mcp-server'
|
|
1827
|
-
// or 'agent-mcp'), but aws-mcp's tools are still exposed to the LLM
|
|
1828
|
-
// under names like 'aws-mcp__poll_message'. The old R3 check matched
|
|
1829
|
-
// the literal string 'aws-mcp' against the user-configured server
|
|
1830
|
-
// names, which produced a false-positive 'error' event and locked
|
|
1831
|
-
// the dashboard into a permanent '发生错误' state.
|
|
1832
|
-
const adapter = new AcodeSdkAdapter();
|
|
1833
|
-
adapter.setRuntimeConstructorForTest(DifferentlyNamedMcpRuntime);
|
|
1834
|
-
const events = [];
|
|
1835
|
-
const consoleWarn = vi
|
|
1836
|
-
.spyOn(console, "warn")
|
|
1837
|
-
.mockImplementation(() => undefined);
|
|
1838
|
-
adapter.on("event", (event) => events.push(event));
|
|
1839
|
-
try {
|
|
1840
|
-
await adapter.startSession("session-r3-differently-named-mcp", {
|
|
1841
|
-
command: "acode",
|
|
1842
|
-
workingDirectory: process.cwd(),
|
|
1843
|
-
autoAccept: true,
|
|
1844
|
-
extraMcpServers: {},
|
|
1845
|
-
envOverrides: {},
|
|
1846
|
-
});
|
|
1847
|
-
expect(events.find((event) => event.type === "error")).toBeUndefined();
|
|
1848
|
-
expect(consoleWarn).not.toHaveBeenCalled();
|
|
1849
|
-
}
|
|
1850
|
-
finally {
|
|
1851
|
-
consoleWarn.mockRestore();
|
|
1852
|
-
adapter.cleanup();
|
|
1853
|
-
}
|
|
1854
|
-
});
|
|
1855
|
-
});
|
|
1856
|
-
/**
|
|
1857
|
-
* 用于验证 AcodeSdkAdapter 在 startSession 阶段传给 ACodeRuntime 的 config 是否符合
|
|
1858
|
-
* "instance > global > default" 优先级。Stub 会在构造时把 model / configPath 缓存到
|
|
1859
|
-
* lastConfig 供断言读取。
|
|
1860
|
-
*/
|
|
1861
|
-
class CapturingConfigRuntime extends EventEmitter {
|
|
1862
|
-
constructor(config) {
|
|
1863
|
-
super();
|
|
1864
|
-
CapturingConfigRuntime.lastConfig = { ...config };
|
|
1865
|
-
}
|
|
1866
|
-
async start() { }
|
|
1867
|
-
async submit(_message) { }
|
|
1868
|
-
async abort() { }
|
|
1869
|
-
async stop() { }
|
|
1870
|
-
}
|
|
1871
|
-
describe("AcodeSdkAdapter instance AI configuration priority", () => {
|
|
1872
|
-
let tempDir;
|
|
1873
|
-
let globalAcodePath;
|
|
1874
|
-
let perInstanceAcodePath;
|
|
1875
|
-
beforeEach(async () => {
|
|
1876
|
-
const { mkdir, mkdtemp, writeFile } = await import("node:fs/promises");
|
|
1877
|
-
const os = await import("node:os");
|
|
1878
|
-
const path = await import("node:path");
|
|
1879
|
-
tempDir = await mkdtemp(path.join(os.tmpdir(), "acode-adapter-ai-"));
|
|
1880
|
-
globalAcodePath = path.join(tempDir, "global", ".acode", "config.json");
|
|
1881
|
-
perInstanceAcodePath = path.join(tempDir, "instance-acode-config.json");
|
|
1882
|
-
await mkdir(path.dirname(globalAcodePath), { recursive: true });
|
|
1883
|
-
await writeFile(globalAcodePath, JSON.stringify({
|
|
1884
|
-
provider: {
|
|
1885
|
-
default: {
|
|
1886
|
-
model: "global-model",
|
|
1887
|
-
options: {
|
|
1888
|
-
baseURL: "https://global.example.com/v1",
|
|
1889
|
-
apiKey: "sk-global",
|
|
1890
|
-
},
|
|
1891
|
-
},
|
|
1892
|
-
},
|
|
1893
|
-
}), "utf-8");
|
|
1894
|
-
process.env.AWS_RUNTIME_HOME_DIR = tempDir;
|
|
1895
|
-
process.env.AWS_RUNTIME_ACODE_CONFIG_FILE = globalAcodePath;
|
|
1896
|
-
});
|
|
1897
|
-
afterEach(async () => {
|
|
1898
|
-
const { rm } = await import("node:fs/promises");
|
|
1899
|
-
delete process.env.AWS_RUNTIME_HOME_DIR;
|
|
1900
|
-
delete process.env.AWS_RUNTIME_ACODE_CONFIG_FILE;
|
|
1901
|
-
CapturingConfigRuntime.lastConfig = undefined;
|
|
1902
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
1903
|
-
});
|
|
1904
|
-
it("无 instance 覆盖时使用全局 acode.json 的 model 与 configPath", async () => {
|
|
1905
|
-
const adapter = new AcodeSdkAdapter();
|
|
1906
|
-
adapter.setRuntimeConstructorForTest(CapturingConfigRuntime);
|
|
1907
|
-
await adapter.startSession("s-default", {
|
|
1908
|
-
command: "acode",
|
|
1909
|
-
workingDirectory: process.cwd(),
|
|
1910
|
-
autoAccept: true,
|
|
1911
|
-
extraMcpServers: {},
|
|
1912
|
-
envOverrides: {},
|
|
1913
|
-
});
|
|
1914
|
-
expect(CapturingConfigRuntime.lastConfig?.configPath).toBe(globalAcodePath);
|
|
1915
|
-
expect(CapturingConfigRuntime.lastConfig?.model).toBe("global-model");
|
|
1916
|
-
adapter.cleanup();
|
|
1917
|
-
});
|
|
1918
|
-
it("per-instance instanceAiConfig.model 覆盖全局 model", async () => {
|
|
1919
|
-
const adapter = new AcodeSdkAdapter();
|
|
1920
|
-
adapter.setRuntimeConstructorForTest(CapturingConfigRuntime);
|
|
1921
|
-
await adapter.startSession("s-instance-model", {
|
|
1922
|
-
command: "acode",
|
|
1923
|
-
workingDirectory: process.cwd(),
|
|
1924
|
-
autoAccept: true,
|
|
1925
|
-
mcpConfigPath: perInstanceAcodePath,
|
|
1926
|
-
instanceAiConfig: { model: "instance-model" },
|
|
1927
|
-
extraMcpServers: {},
|
|
1928
|
-
envOverrides: {},
|
|
1929
|
-
});
|
|
1930
|
-
// 没有 per-instance 文件时,adapter 仍使用调用方传入的 mcpConfigPath
|
|
1931
|
-
expect(CapturingConfigRuntime.lastConfig?.configPath).toBe(perInstanceAcodePath);
|
|
1932
|
-
expect(CapturingConfigRuntime.lastConfig?.model).toBe("instance-model");
|
|
1933
|
-
adapter.cleanup();
|
|
1934
|
-
});
|
|
1935
|
-
it("per-instance 文件已 deep-merge 时,baseURL/apiKey 从文件读取(instance 优先)", async () => {
|
|
1936
|
-
const { writeFile } = await import("node:fs/promises");
|
|
1937
|
-
await writeFile(perInstanceAcodePath, JSON.stringify({
|
|
1938
|
-
provider: {
|
|
1939
|
-
default: {
|
|
1940
|
-
model: "instance-file-model",
|
|
1941
|
-
options: {
|
|
1942
|
-
baseURL: "https://instance.example.com/v1",
|
|
1943
|
-
apiKey: "sk-instance",
|
|
1944
|
-
},
|
|
1945
|
-
},
|
|
1946
|
-
},
|
|
1947
|
-
mcpServers: { "kept-from-global": { type: "stdio", command: "x" } },
|
|
1948
|
-
}), "utf-8");
|
|
1949
|
-
const adapter = new AcodeSdkAdapter();
|
|
1950
|
-
adapter.setRuntimeConstructorForTest(CapturingConfigRuntime);
|
|
1951
|
-
await adapter.startSession("s-instance-file", {
|
|
1952
|
-
command: "acode",
|
|
1953
|
-
workingDirectory: process.cwd(),
|
|
1954
|
-
autoAccept: true,
|
|
1955
|
-
mcpConfigPath: perInstanceAcodePath,
|
|
1956
|
-
extraMcpServers: {},
|
|
1957
|
-
envOverrides: {},
|
|
1958
|
-
});
|
|
1959
|
-
expect(CapturingConfigRuntime.lastConfig?.configPath).toBe(perInstanceAcodePath);
|
|
1960
|
-
// model 走 per-instance 文件的 provider.default.model
|
|
1961
|
-
expect(CapturingConfigRuntime.lastConfig?.model).toBe("instance-file-model");
|
|
1962
|
-
adapter.cleanup();
|
|
1963
|
-
});
|
|
1964
|
-
it("per-session model 优先于 per-instance file model 与全局", async () => {
|
|
1965
|
-
const { writeFile } = await import("node:fs/promises");
|
|
1966
|
-
await writeFile(perInstanceAcodePath, JSON.stringify({
|
|
1967
|
-
provider: {
|
|
1968
|
-
default: { model: "instance-file-model" },
|
|
1969
|
-
},
|
|
1970
|
-
}), "utf-8");
|
|
1971
|
-
const adapter = new AcodeSdkAdapter();
|
|
1972
|
-
adapter.setRuntimeConstructorForTest(CapturingConfigRuntime);
|
|
1973
|
-
await adapter.startSession("s-session-wins", {
|
|
1974
|
-
command: "acode",
|
|
1975
|
-
workingDirectory: process.cwd(),
|
|
1976
|
-
autoAccept: true,
|
|
1977
|
-
mcpConfigPath: perInstanceAcodePath,
|
|
1978
|
-
model: "session-model",
|
|
1979
|
-
extraMcpServers: {},
|
|
1980
|
-
envOverrides: {},
|
|
1981
|
-
});
|
|
1982
|
-
expect(CapturingConfigRuntime.lastConfig?.model).toBe("session-model");
|
|
1983
|
-
adapter.cleanup();
|
|
1984
|
-
});
|
|
1985
|
-
it("全空时回退默认 'acode-default'", async () => {
|
|
1986
|
-
const adapter = new AcodeSdkAdapter();
|
|
1987
|
-
adapter.setRuntimeConstructorForTest(CapturingConfigRuntime);
|
|
1988
|
-
await adapter.startSession("s-all-empty", {
|
|
1989
|
-
command: "acode",
|
|
1990
|
-
workingDirectory: process.cwd(),
|
|
1991
|
-
autoAccept: true,
|
|
1992
|
-
// 指向一个不存在的 per-instance 文件,模拟 instance & global 都为空
|
|
1993
|
-
mcpConfigPath: perInstanceAcodePath,
|
|
1994
|
-
extraMcpServers: {},
|
|
1995
|
-
envOverrides: {},
|
|
1996
|
-
});
|
|
1997
|
-
expect(CapturingConfigRuntime.lastConfig?.model).toBe("acode-default");
|
|
1998
|
-
adapter.cleanup();
|
|
1999
|
-
});
|
|
2000
|
-
});
|