aws-runtime-bridge 1.9.0 → 1.9.2
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 +2 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +84 -18
- package/dist/adapter/AcodeSdkAdapter.test.js +275 -13
- package/dist/adapter/acode-tool-names.d.ts +16 -0
- package/dist/adapter/acode-tool-names.d.ts.map +1 -0
- package/dist/adapter/acode-tool-names.js +37 -0
- package/dist/adapter/acode-tool-names.test.d.ts +2 -0
- package/dist/adapter/acode-tool-names.test.d.ts.map +1 -0
- package/dist/adapter/acode-tool-names.test.js +28 -0
- package/dist/adapter/types.d.ts +22 -3
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +2 -1
- package/dist/config.d.ts +0 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -4
- package/dist/index.js +1 -1
- package/dist/routes/ai-sources.js +18 -14
- package/dist/routes/git.d.ts +10 -0
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +37 -3
- package/dist/routes/git.test.js +47 -1
- package/dist/routes/sessions.d.ts +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +11 -4
- package/dist/routes/sessions.test.js +3 -0
- package/dist/routes/terminal.d.ts +28 -8
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +266 -44
- package/dist/routes/terminal.stop-options.test.d.ts +2 -0
- package/dist/routes/terminal.stop-options.test.d.ts.map +1 -0
- package/dist/routes/terminal.stop-options.test.js +39 -0
- package/dist/routes/terminal.test.js +87 -4
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +4 -5
- package/dist/services/mcp-launch-binding-queue.d.ts +5 -0
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
- package/dist/services/mcp-launch-binding-queue.js +17 -0
- package/dist/services/mcp-launch-binding-queue.test.js +16 -1
- package/dist/services/session-output.d.ts +5 -0
- package/dist/services/session-output.d.ts.map +1 -1
- package/package/acode/dist/built-in-file-tools.d.ts +5 -1
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/built-in-file-tools.js +14 -203
- package/package/acode/dist/builtins/commandArtifacts.d.ts +25 -0
- package/package/acode/dist/builtins/commandArtifacts.d.ts.map +1 -0
- package/package/acode/dist/builtins/commandArtifacts.js +77 -0
- package/package/acode/dist/builtins/commandOutputEvents.d.ts +21 -0
- package/package/acode/dist/builtins/commandOutputEvents.d.ts.map +1 -0
- package/package/acode/dist/builtins/commandOutputEvents.js +56 -0
- package/package/acode/dist/builtins/shellCommandPreparation.d.ts +15 -0
- package/package/acode/dist/builtins/shellCommandPreparation.d.ts.map +1 -0
- package/package/acode/dist/builtins/shellCommandPreparation.js +44 -0
- package/package/acode/dist/builtins/shellCommandResult.d.ts +30 -0
- package/package/acode/dist/builtins/shellCommandResult.d.ts.map +1 -0
- package/package/acode/dist/builtins/shellCommandResult.js +71 -0
- package/package/acode/dist/builtins/shellCommandRunner.d.ts +14 -0
- package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +1 -0
- package/package/acode/dist/builtins/shellCommandRunner.js +114 -0
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts +5 -0
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +1 -0
- package/package/acode/dist/builtins/shellOutputDecoder.js +30 -0
- package/package/acode/dist/mcpToolNames.d.ts +11 -0
- package/package/acode/dist/mcpToolNames.d.ts.map +1 -0
- package/package/acode/dist/mcpToolNames.js +17 -0
- package/package/acode/dist/runtime.d.ts +19 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +233 -35
- package/package/acode/dist/types.d.ts +1 -1
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/acode/package.json +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +20 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +71 -2
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts +4 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/constants.js +5 -1
- package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +17 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +124 -28
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js +108 -2
- package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +8 -3
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +63 -10
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +47 -0
- 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 -36
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +3 -169
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +45 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +11 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.js +144 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +79 -28
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +35 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +9 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -3
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
- package/package.json +1 -1
package/dist/routes/terminal.js
CHANGED
|
@@ -24,7 +24,53 @@ export const terminalRouter = Router();
|
|
|
24
24
|
export const sdkSessions = new Map();
|
|
25
25
|
const TOOL_RESULT_PREVIEW_MAX_LENGTH = 4000;
|
|
26
26
|
const AI_REQUEST_BODY_MARKER = "\n\n请求体:\n";
|
|
27
|
+
const AI_REQUEST_HEADERS_MARKER = "\n\n请求头:\n";
|
|
27
28
|
const startingAgents = new Set();
|
|
29
|
+
export function resolveRuntimeContextResetSupport(providerId, hasResetContext) {
|
|
30
|
+
if (providerId !== "acode") {
|
|
31
|
+
return {
|
|
32
|
+
supported: false,
|
|
33
|
+
statusCode: 400,
|
|
34
|
+
error: "context reset without restart is only supported by ACode",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!hasResetContext) {
|
|
38
|
+
return {
|
|
39
|
+
supported: false,
|
|
40
|
+
statusCode: 500,
|
|
41
|
+
error: "ACode adapter does not expose resetContext",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { supported: true };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Decide whether SDK startup should wait for an external process PID.
|
|
48
|
+
* Main flow: in-process providers return immediately; external CLI providers keep PID probing for orphan cleanup.
|
|
49
|
+
*/
|
|
50
|
+
export function shouldProbeSdkProcessPid(providerId) {
|
|
51
|
+
return providerId !== "acode";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 主流程:解析 runtime stop 请求的状态通知选项。
|
|
55
|
+
* 内部上下文重置可抑制 stopped 广播;普通手动停止默认继续通知调度端。
|
|
56
|
+
*/
|
|
57
|
+
export function parseRuntimeStopOptions(body) {
|
|
58
|
+
const suppressStatus = typeof body === "object" &&
|
|
59
|
+
body !== null &&
|
|
60
|
+
"suppressStatus" in body &&
|
|
61
|
+
body.suppressStatus === true;
|
|
62
|
+
const reason = typeof body === "object" &&
|
|
63
|
+
body !== null &&
|
|
64
|
+
"reason" in body &&
|
|
65
|
+
typeof body.reason === "string" &&
|
|
66
|
+
body.reason.trim()
|
|
67
|
+
? body.reason.trim()
|
|
68
|
+
: undefined;
|
|
69
|
+
return {
|
|
70
|
+
statusNotified: !suppressStatus,
|
|
71
|
+
reason,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
28
74
|
export function resolveStatusChangeUsage(actionInfo) {
|
|
29
75
|
const usage = actionInfo?.usage;
|
|
30
76
|
if (!usage)
|
|
@@ -39,10 +85,24 @@ export function resolveStatusChangeUsage(actionInfo) {
|
|
|
39
85
|
return undefined;
|
|
40
86
|
return { inputTokens, outputTokens };
|
|
41
87
|
}
|
|
88
|
+
export function resolveStatusChangeRuntimeState(status, actionInfo) {
|
|
89
|
+
const normalizedStatus = String(status || "")
|
|
90
|
+
.trim()
|
|
91
|
+
.toLowerCase();
|
|
92
|
+
const normalizedActionType = String(actionInfo?.actionType || "")
|
|
93
|
+
.trim()
|
|
94
|
+
.toLowerCase();
|
|
95
|
+
if (normalizedStatus === "tool_using" && normalizedActionType === "idle") {
|
|
96
|
+
return actionInfo
|
|
97
|
+
? { status: "waiting_input", actionInfo }
|
|
98
|
+
: { status: "waiting_input" };
|
|
99
|
+
}
|
|
100
|
+
return actionInfo ? { status, actionInfo } : { status };
|
|
101
|
+
}
|
|
42
102
|
const terminalCommandStates = new Map();
|
|
43
103
|
const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = "gb18030";
|
|
44
104
|
const DEFAULT_TERMINAL_OUTPUT_ENCODING = "utf-8";
|
|
45
|
-
const TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT =
|
|
105
|
+
const TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT = 300;
|
|
46
106
|
const TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS = 60_000;
|
|
47
107
|
function padDatePart(value) {
|
|
48
108
|
return String(value).padStart(2, "0");
|
|
@@ -167,7 +227,7 @@ function findActiveSdkSessionByAgentId(agentId) {
|
|
|
167
227
|
}
|
|
168
228
|
async function buildExistingSessionResponse(entry) {
|
|
169
229
|
const adapter = adapterRegistry.get(entry.providerId || "claude-code");
|
|
170
|
-
const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus);
|
|
230
|
+
const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus, entry.currentRuntimeActionType);
|
|
171
231
|
return {
|
|
172
232
|
sessionId: entry.sessionId,
|
|
173
233
|
status,
|
|
@@ -187,9 +247,18 @@ async function buildExistingSessionResponse(entry) {
|
|
|
187
247
|
* 解析 SDK 会话对外展示状态。
|
|
188
248
|
* 主流程:优先使用 adapter 当前细粒度状态,其次使用路由内存记录的最近状态,最后才兼容旧会话回退 running。
|
|
189
249
|
*/
|
|
190
|
-
export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus) {
|
|
250
|
+
export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus, currentRuntimeActionType) {
|
|
191
251
|
const normalizedAdapterStatus = String(adapterStatus || "").trim();
|
|
192
252
|
if (normalizedAdapterStatus) {
|
|
253
|
+
const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
|
|
254
|
+
const normalizedActionType = String(currentRuntimeActionType || "")
|
|
255
|
+
.trim()
|
|
256
|
+
.toLowerCase();
|
|
257
|
+
if (normalizedAdapterStatus.toLowerCase() === "tool_using" &&
|
|
258
|
+
normalizedRuntimeStatus.toLowerCase() === "waiting_input" &&
|
|
259
|
+
normalizedActionType === "idle") {
|
|
260
|
+
return normalizedRuntimeStatus;
|
|
261
|
+
}
|
|
193
262
|
return normalizedAdapterStatus;
|
|
194
263
|
}
|
|
195
264
|
const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
|
|
@@ -575,6 +644,9 @@ export function formatSdkOutputEvent(event) {
|
|
|
575
644
|
if (event.type === "thinking") {
|
|
576
645
|
return event.data.text || undefined;
|
|
577
646
|
}
|
|
647
|
+
if (event.type === "command_output") {
|
|
648
|
+
return event.data.text || undefined;
|
|
649
|
+
}
|
|
578
650
|
if (event.type === "error") {
|
|
579
651
|
return event.data.text
|
|
580
652
|
? `\r\n[SDK Error] ${event.data.text}\r\n`
|
|
@@ -587,14 +659,44 @@ export function buildSdkOutputMetadata(event) {
|
|
|
587
659
|
return {
|
|
588
660
|
sourceEventType: event.type,
|
|
589
661
|
logCategory: "acode",
|
|
662
|
+
...buildCommandOutputMetadata(event),
|
|
590
663
|
...(logMessage
|
|
591
664
|
? {
|
|
592
|
-
logLevel: event
|
|
665
|
+
logLevel: resolveSdkLogLevel(event),
|
|
593
666
|
logMessage,
|
|
594
667
|
}
|
|
595
668
|
: {}),
|
|
596
669
|
};
|
|
597
670
|
}
|
|
671
|
+
function resolveSdkLogLevel(event) {
|
|
672
|
+
if (event.type === "error") {
|
|
673
|
+
return "error";
|
|
674
|
+
}
|
|
675
|
+
if (event.type === "command_output" && event.data.stream === "stderr") {
|
|
676
|
+
return "warn";
|
|
677
|
+
}
|
|
678
|
+
return "info";
|
|
679
|
+
}
|
|
680
|
+
function buildCommandOutputMetadata(event) {
|
|
681
|
+
if (event.type !== "command_output") {
|
|
682
|
+
return {};
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
...(typeof event.data.stream === "string"
|
|
686
|
+
? { stream: event.data.stream }
|
|
687
|
+
: {}),
|
|
688
|
+
...(typeof event.data.sequence === "number"
|
|
689
|
+
? { sequence: event.data.sequence }
|
|
690
|
+
: {}),
|
|
691
|
+
...(typeof event.data.cwd === "string" ? { cwd: event.data.cwd } : {}),
|
|
692
|
+
...(typeof event.data.commandFilePath === "string"
|
|
693
|
+
? { commandFilePath: event.data.commandFilePath }
|
|
694
|
+
: {}),
|
|
695
|
+
...(typeof event.data.commandLineCount === "number"
|
|
696
|
+
? { commandLineCount: event.data.commandLineCount }
|
|
697
|
+
: {}),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
598
700
|
function formatSdkLogMessage(event) {
|
|
599
701
|
if (event.type === "ai_request") {
|
|
600
702
|
const model = event.data.model || "unknown-model";
|
|
@@ -610,6 +712,9 @@ function formatSdkLogMessage(event) {
|
|
|
610
712
|
if (event.type === "tool_use_end") {
|
|
611
713
|
return formatSdkToolLogMessage(event, "完成");
|
|
612
714
|
}
|
|
715
|
+
if (event.type === "command_output") {
|
|
716
|
+
return formatCommandOutputLogMessage(event);
|
|
717
|
+
}
|
|
613
718
|
if (event.type === "turn_complete") {
|
|
614
719
|
const finishReason = event.data.finishReason
|
|
615
720
|
? `, finish=${event.data.finishReason}`
|
|
@@ -624,6 +729,15 @@ function formatSdkLogMessage(event) {
|
|
|
624
729
|
}
|
|
625
730
|
return undefined;
|
|
626
731
|
}
|
|
732
|
+
function formatCommandOutputLogMessage(event) {
|
|
733
|
+
const stream = typeof event.data.stream === "string" ? event.data.stream : "mixed";
|
|
734
|
+
const sequence = typeof event.data.sequence === "number" ? ` #${event.data.sequence}` : "";
|
|
735
|
+
const normalized = String(event.data.text || "")
|
|
736
|
+
.replace(/\s+/g, " ")
|
|
737
|
+
.trim();
|
|
738
|
+
const preview = normalized.length > 240 ? `${normalized.slice(0, 240)}...` : normalized;
|
|
739
|
+
return `命令输出(${stream}${sequence}): ${preview || "(empty)"}`;
|
|
740
|
+
}
|
|
627
741
|
function formatSdkToolLogMessage(event, phaseLabel) {
|
|
628
742
|
const actionInfo = getToolActionInfo(event.data.toolName, event.data.toolInput);
|
|
629
743
|
const actionLabel = event.data.actionLabel || actionInfo.actionLabel || "调用工具";
|
|
@@ -694,7 +808,10 @@ function displayStringList(value) {
|
|
|
694
808
|
function formatAiRequestDetail(data) {
|
|
695
809
|
const lines = [
|
|
696
810
|
data.model ? `模型: ${data.model}` : undefined,
|
|
697
|
-
data.endpoint
|
|
811
|
+
data.endpoint || data.requestUrl
|
|
812
|
+
? `接口: ${data.endpoint || data.requestUrl}`
|
|
813
|
+
: undefined,
|
|
814
|
+
data.requestMethod ? `方法: ${data.requestMethod}` : undefined,
|
|
698
815
|
typeof data.stream === "boolean"
|
|
699
816
|
? `流式: ${data.stream ? "是" : "否"}`
|
|
700
817
|
: undefined,
|
|
@@ -708,10 +825,22 @@ function formatAiRequestDetail(data) {
|
|
|
708
825
|
? `最后请求: ${data.lastUserMessagePreview}`
|
|
709
826
|
: undefined,
|
|
710
827
|
].filter((line) => Boolean(line));
|
|
828
|
+
const requestHeaderLines = formatAiRequestHeaders(data.requestHeaders);
|
|
711
829
|
const base = lines.join("\n") || "ACode 正在发送 AI 请求";
|
|
712
|
-
|
|
713
|
-
? `${
|
|
714
|
-
:
|
|
830
|
+
const headerBlock = requestHeaderLines.length > 0
|
|
831
|
+
? `${AI_REQUEST_HEADERS_MARKER}${requestHeaderLines.join("\n")}`
|
|
832
|
+
: "";
|
|
833
|
+
const bodyBlock = data.requestBody
|
|
834
|
+
? `${AI_REQUEST_BODY_MARKER}${data.requestBody}`
|
|
835
|
+
: "";
|
|
836
|
+
return `${base}${headerBlock}${bodyBlock}`;
|
|
837
|
+
}
|
|
838
|
+
function formatAiRequestHeaders(headers) {
|
|
839
|
+
if (!headers)
|
|
840
|
+
return [];
|
|
841
|
+
return Object.entries(headers)
|
|
842
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
843
|
+
.filter((line) => line.trim().length > 0);
|
|
715
844
|
}
|
|
716
845
|
/**
|
|
717
846
|
* 主干流程:把 provider 事件转换为面板运行状态。
|
|
@@ -719,7 +848,19 @@ function formatAiRequestDetail(data) {
|
|
|
719
848
|
* text_delta 表示助手正在生成;thinking 表示推理/内部过程。
|
|
720
849
|
* tool_use_start 表示正在执行工具;turn_complete 才表示真正空闲。
|
|
721
850
|
*/
|
|
722
|
-
|
|
851
|
+
function isWaitingInputProgressEvent(event, currentRuntimeStatus) {
|
|
852
|
+
const normalizedStatus = String(currentRuntimeStatus || "")
|
|
853
|
+
.trim()
|
|
854
|
+
.toLowerCase();
|
|
855
|
+
if (normalizedStatus !== "waiting_input") {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
return event.type === "thinking" || event.type === "text_delta";
|
|
859
|
+
}
|
|
860
|
+
export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatus) {
|
|
861
|
+
if (isWaitingInputProgressEvent(event, currentRuntimeStatus)) {
|
|
862
|
+
return undefined;
|
|
863
|
+
}
|
|
723
864
|
if (event.type === "ai_request") {
|
|
724
865
|
return {
|
|
725
866
|
status: "thinking",
|
|
@@ -812,7 +953,7 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
812
953
|
const entry = sdkSessions.get(event.sessionId);
|
|
813
954
|
if (entry) {
|
|
814
955
|
console.log(`[${definition.displayName} Adapter] Event: ${event.type} for session ${event.sessionId}`);
|
|
815
|
-
const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event);
|
|
956
|
+
const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event, entry.currentRuntimeStatus);
|
|
816
957
|
if (runtimeStatus) {
|
|
817
958
|
void sendStatus(entry.agentId, event.sessionId, runtimeStatus.status, runtimeStatus.actionInfo, resolveStatusChangeUsage(runtimeStatus.actionInfo), {
|
|
818
959
|
runtimeAccessToken: entry.runtimeAccessToken,
|
|
@@ -832,18 +973,20 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
832
973
|
adapter.on("status-change", (sessionId, status, actionInfo) => {
|
|
833
974
|
const entry = sdkSessions.get(sessionId);
|
|
834
975
|
if (entry) {
|
|
835
|
-
|
|
836
|
-
entry.
|
|
837
|
-
entry.
|
|
838
|
-
entry.
|
|
839
|
-
|
|
976
|
+
const runtimeState = resolveStatusChangeRuntimeState(status, actionInfo);
|
|
977
|
+
entry.currentRuntimeStatus = runtimeState.status;
|
|
978
|
+
entry.currentRuntimeActionType = runtimeState.actionInfo?.actionType;
|
|
979
|
+
entry.currentRuntimeActionLabel = runtimeState.actionInfo?.actionLabel;
|
|
980
|
+
entry.currentRuntimeActionDetail =
|
|
981
|
+
runtimeState.actionInfo?.actionDetail;
|
|
982
|
+
console.log(`[${definition.displayName} Adapter] Status changed: ${runtimeState.status} for agent ${entry.agentId}`, runtimeState.actionInfo || "");
|
|
840
983
|
void updatePersistedSessionRuntimeState(sessionId, {
|
|
841
|
-
runtimeStatus: status,
|
|
842
|
-
runtimeActionType: actionInfo?.actionType,
|
|
843
|
-
runtimeActionLabel: actionInfo?.actionLabel,
|
|
844
|
-
runtimeActionDetail: actionInfo?.actionDetail,
|
|
984
|
+
runtimeStatus: runtimeState.status,
|
|
985
|
+
runtimeActionType: runtimeState.actionInfo?.actionType,
|
|
986
|
+
runtimeActionLabel: runtimeState.actionInfo?.actionLabel,
|
|
987
|
+
runtimeActionDetail: runtimeState.actionInfo?.actionDetail,
|
|
845
988
|
});
|
|
846
|
-
void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo), {
|
|
989
|
+
void sendStatus(entry.agentId, sessionId, runtimeState.status, runtimeState.actionInfo, resolveStatusChangeUsage(runtimeState.actionInfo), {
|
|
847
990
|
runtimeAccessToken: entry.runtimeAccessToken,
|
|
848
991
|
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
849
992
|
});
|
|
@@ -992,29 +1135,31 @@ async function startSdkSession(req, res) {
|
|
|
992
1135
|
// ★ 尝试获取 SDK 进程的 PID(用于孤儿进程检测)
|
|
993
1136
|
// SDK 不直接暴露 PID,通过进程检测服务查找匹配的进程
|
|
994
1137
|
let sdkPid;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
p.
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1138
|
+
if (shouldProbeSdkProcessPid(providerId)) {
|
|
1139
|
+
try {
|
|
1140
|
+
// 等待一小段时间让进程启动
|
|
1141
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1142
|
+
// 尝试通过 adapter 获取 PID(如果支持)
|
|
1143
|
+
sdkPid = adapter.getSessionPid?.(sessionId);
|
|
1144
|
+
// 如果 adapter 不支持,尝试通过进程检测服务查找
|
|
1145
|
+
if (!sdkPid) {
|
|
1146
|
+
const processes = findClaudeCodeProcesses();
|
|
1147
|
+
// 查找匹配 agentId 或工作目录的进程
|
|
1148
|
+
const matched = processes.find((p) => p.agentId === agentId ||
|
|
1149
|
+
p.cwd === workspacePath ||
|
|
1150
|
+
p.command.includes(workspacePath));
|
|
1151
|
+
if (matched) {
|
|
1152
|
+
sdkPid = matched.pid;
|
|
1153
|
+
console.log(`[Runtime] Found SDK process PID: ${sdkPid} for agentId=${agentId}`);
|
|
1154
|
+
}
|
|
1010
1155
|
}
|
|
1011
1156
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1157
|
+
catch (pidError) {
|
|
1158
|
+
console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
|
|
1159
|
+
}
|
|
1015
1160
|
}
|
|
1016
1161
|
// ★ 持久化会话信息(包含 PID)
|
|
1017
|
-
const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus);
|
|
1162
|
+
const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus, sdkSessions.get(sessionId)?.currentRuntimeActionType);
|
|
1018
1163
|
await upsertPersistedSession({
|
|
1019
1164
|
sessionId,
|
|
1020
1165
|
agentId,
|
|
@@ -1196,6 +1341,8 @@ terminalRouter.post("/sdk/stop", validateToken, async (req, res) => {
|
|
|
1196
1341
|
// 根据 providerId 获取对应的 adapter
|
|
1197
1342
|
const providerId = entry.providerId || "claude-code";
|
|
1198
1343
|
const adapter = adapterRegistry.get(providerId);
|
|
1344
|
+
// 如果有活跃 AI 请求,先立即中止(fire-and-forget),再清理资源
|
|
1345
|
+
adapter.abortCurrentTurn(sessionId).catch(() => { });
|
|
1199
1346
|
await adapter.terminateSession(sessionId);
|
|
1200
1347
|
sdkSessions.delete(sessionId);
|
|
1201
1348
|
stopTerminalCommandProcess(sessionId);
|
|
@@ -1369,12 +1516,65 @@ terminalRouter.post("/resize", validateToken, (req, res) => {
|
|
|
1369
1516
|
rows: Number(rows),
|
|
1370
1517
|
});
|
|
1371
1518
|
});
|
|
1519
|
+
terminalRouter.post("/reset-context", validateToken, async (req, res) => {
|
|
1520
|
+
const { sessionId, reason } = req.body || {};
|
|
1521
|
+
if (!sessionId) {
|
|
1522
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const sdkEntry = sdkSessions.get(sessionId);
|
|
1526
|
+
if (!sdkEntry) {
|
|
1527
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const providerId = sdkEntry.providerId || "claude-code";
|
|
1531
|
+
const adapter = adapterRegistry.get(providerId);
|
|
1532
|
+
const resetContext = adapter.resetContext?.bind(adapter);
|
|
1533
|
+
const support = resolveRuntimeContextResetSupport(providerId, Boolean(resetContext));
|
|
1534
|
+
if (!support.supported) {
|
|
1535
|
+
res.status(support.statusCode).json({ error: support.error });
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
if (!resetContext) {
|
|
1539
|
+
res
|
|
1540
|
+
.status(500)
|
|
1541
|
+
.json({ error: "ACode adapter does not expose resetContext" });
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
try {
|
|
1545
|
+
await resetContext(String(sessionId));
|
|
1546
|
+
const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(String(sessionId)), sdkEntry.currentRuntimeStatus, sdkEntry.currentRuntimeActionType);
|
|
1547
|
+
res.json({
|
|
1548
|
+
ok: true,
|
|
1549
|
+
contextReset: true,
|
|
1550
|
+
sessionId,
|
|
1551
|
+
status,
|
|
1552
|
+
runtimeStatus: status,
|
|
1553
|
+
agentId: sdkEntry.agentId,
|
|
1554
|
+
mode: "sdk",
|
|
1555
|
+
providerId,
|
|
1556
|
+
reason: typeof reason === "string" && reason.trim()
|
|
1557
|
+
? reason.trim()
|
|
1558
|
+
: undefined,
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
catch (error) {
|
|
1562
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1563
|
+
res.status(500).json({
|
|
1564
|
+
ok: false,
|
|
1565
|
+
contextReset: false,
|
|
1566
|
+
mode: "sdk",
|
|
1567
|
+
error: errorMessage,
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1372
1571
|
/**
|
|
1373
1572
|
* 停止 SDK 会话。
|
|
1374
1573
|
* POST /runtime/stop
|
|
1375
1574
|
*/
|
|
1376
1575
|
terminalRouter.post("/stop", validateToken, async (req, res) => {
|
|
1377
1576
|
const { sessionId } = req.body || {};
|
|
1577
|
+
const stopOptions = parseRuntimeStopOptions(req.body);
|
|
1378
1578
|
if (!sessionId) {
|
|
1379
1579
|
res.status(400).json({ error: "sessionId is required" });
|
|
1380
1580
|
return;
|
|
@@ -1382,24 +1582,44 @@ terminalRouter.post("/stop", validateToken, async (req, res) => {
|
|
|
1382
1582
|
const sdkEntry = sdkSessions.get(sessionId);
|
|
1383
1583
|
if (!sdkEntry) {
|
|
1384
1584
|
await removePersistedSession(sessionId);
|
|
1385
|
-
res.json({
|
|
1585
|
+
res.json({
|
|
1586
|
+
ok: true,
|
|
1587
|
+
status: "stopped",
|
|
1588
|
+
mode: "none",
|
|
1589
|
+
statusNotified: false,
|
|
1590
|
+
reason: stopOptions.reason,
|
|
1591
|
+
});
|
|
1386
1592
|
return;
|
|
1387
1593
|
}
|
|
1388
1594
|
const agentId = sdkEntry.agentId;
|
|
1389
1595
|
try {
|
|
1596
|
+
console.log(`[Runtime] Stopping SDK session ${sessionId}; suppressStatus=${!stopOptions.statusNotified}, reason=${stopOptions.reason || "unspecified"}`);
|
|
1390
1597
|
const providerId = sdkEntry.providerId || "claude-code";
|
|
1391
1598
|
const adapter = adapterRegistry.get(providerId);
|
|
1599
|
+
// 如果有活跃 AI 请求,先立即中止(fire-and-forget),再清理资源
|
|
1600
|
+
adapter.abortCurrentTurn(sessionId).catch(() => { });
|
|
1392
1601
|
await adapter.terminateSession(sessionId);
|
|
1393
1602
|
sdkSessions.delete(sessionId);
|
|
1394
1603
|
stopTerminalCommandProcess(sessionId);
|
|
1395
1604
|
await removePersistedSession(sessionId);
|
|
1396
1605
|
const processManager = getAgentProcessManager();
|
|
1397
1606
|
await processManager.removeProcess(agentId);
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1607
|
+
if (stopOptions.statusNotified) {
|
|
1608
|
+
await sendStatus(agentId, null, "stopped", undefined, undefined, {
|
|
1609
|
+
runtimeAccessToken: sdkEntry.runtimeAccessToken,
|
|
1610
|
+
schedulerBaseUrl: sdkEntry.schedulerBaseUrl,
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
else {
|
|
1614
|
+
console.log(`[Runtime] Stopped status notification suppressed for SDK session ${sessionId}; reason=${stopOptions.reason || "unspecified"}`);
|
|
1615
|
+
}
|
|
1616
|
+
res.json({
|
|
1617
|
+
ok: true,
|
|
1618
|
+
status: "stopped",
|
|
1619
|
+
mode: "sdk",
|
|
1620
|
+
statusNotified: stopOptions.statusNotified,
|
|
1621
|
+
reason: stopOptions.reason,
|
|
1401
1622
|
});
|
|
1402
|
-
res.json({ ok: true, status: "stopped", mode: "sdk" });
|
|
1403
1623
|
}
|
|
1404
1624
|
catch (error) {
|
|
1405
1625
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1426,6 +1646,8 @@ terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
|
|
|
1426
1646
|
// 根据 providerId 获取对应的 adapter
|
|
1427
1647
|
const providerId = entry.providerId || "claude-code";
|
|
1428
1648
|
const adapter = adapterRegistry.get(providerId);
|
|
1649
|
+
// 如果有活跃 AI 请求,先立即中止(fire-and-forget),再清理资源
|
|
1650
|
+
adapter.abortCurrentTurn(sessionId).catch(() => { });
|
|
1429
1651
|
await adapter.terminateSession(sessionId);
|
|
1430
1652
|
sdkSessions.delete(sessionId);
|
|
1431
1653
|
stopTerminalCommandProcess(sessionId);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.stop-options.test.d.ts","sourceRoot":"","sources":["../../src/routes/terminal.stop-options.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { parseRuntimeStopOptions, resolveRuntimeContextResetSupport, } from "./terminal.js";
|
|
3
|
+
describe("runtime stop options", () => {
|
|
4
|
+
it("notifies stopped status for manual stop requests by default", () => {
|
|
5
|
+
const options = parseRuntimeStopOptions({ sessionId: "session-1" });
|
|
6
|
+
expect(options).toEqual({
|
|
7
|
+
statusNotified: true,
|
|
8
|
+
reason: undefined,
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
it("suppresses stopped status for internal context reset requests", () => {
|
|
12
|
+
const options = parseRuntimeStopOptions({
|
|
13
|
+
sessionId: "session-1",
|
|
14
|
+
suppressStatus: true,
|
|
15
|
+
reason: "context_reset_after_task_submit",
|
|
16
|
+
});
|
|
17
|
+
expect(options).toEqual({
|
|
18
|
+
statusNotified: false,
|
|
19
|
+
reason: "context_reset_after_task_submit",
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("runtime context reset support", () => {
|
|
24
|
+
it("allows only ACode sessions whose adapter exposes resetContext", () => {
|
|
25
|
+
expect(resolveRuntimeContextResetSupport("acode", true)).toEqual({
|
|
26
|
+
supported: true,
|
|
27
|
+
});
|
|
28
|
+
expect(resolveRuntimeContextResetSupport("claude-code", false)).toEqual({
|
|
29
|
+
supported: false,
|
|
30
|
+
statusCode: 400,
|
|
31
|
+
error: "context reset without restart is only supported by ACode",
|
|
32
|
+
});
|
|
33
|
+
expect(resolveRuntimeContextResetSupport("acode", false)).toEqual({
|
|
34
|
+
supported: false,
|
|
35
|
+
statusCode: 500,
|
|
36
|
+
error: "ACode adapter does not expose resetContext",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|