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.
Files changed (117) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +2 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +84 -18
  4. package/dist/adapter/AcodeSdkAdapter.test.js +275 -13
  5. package/dist/adapter/acode-tool-names.d.ts +16 -0
  6. package/dist/adapter/acode-tool-names.d.ts.map +1 -0
  7. package/dist/adapter/acode-tool-names.js +37 -0
  8. package/dist/adapter/acode-tool-names.test.d.ts +2 -0
  9. package/dist/adapter/acode-tool-names.test.d.ts.map +1 -0
  10. package/dist/adapter/acode-tool-names.test.js +28 -0
  11. package/dist/adapter/types.d.ts +22 -3
  12. package/dist/adapter/types.d.ts.map +1 -1
  13. package/dist/adapter/types.js +2 -1
  14. package/dist/config.d.ts +0 -4
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +0 -4
  17. package/dist/index.js +1 -1
  18. package/dist/routes/ai-sources.js +18 -14
  19. package/dist/routes/git.d.ts +10 -0
  20. package/dist/routes/git.d.ts.map +1 -1
  21. package/dist/routes/git.js +37 -3
  22. package/dist/routes/git.test.js +47 -1
  23. package/dist/routes/sessions.d.ts +1 -1
  24. package/dist/routes/sessions.d.ts.map +1 -1
  25. package/dist/routes/sessions.js +11 -4
  26. package/dist/routes/sessions.test.js +3 -0
  27. package/dist/routes/terminal.d.ts +28 -8
  28. package/dist/routes/terminal.d.ts.map +1 -1
  29. package/dist/routes/terminal.js +266 -44
  30. package/dist/routes/terminal.stop-options.test.d.ts +2 -0
  31. package/dist/routes/terminal.stop-options.test.d.ts.map +1 -0
  32. package/dist/routes/terminal.stop-options.test.js +39 -0
  33. package/dist/routes/terminal.test.js +87 -4
  34. package/dist/services/auto-register.d.ts.map +1 -1
  35. package/dist/services/auto-register.js +4 -5
  36. package/dist/services/mcp-launch-binding-queue.d.ts +5 -0
  37. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  38. package/dist/services/mcp-launch-binding-queue.js +17 -0
  39. package/dist/services/mcp-launch-binding-queue.test.js +16 -1
  40. package/dist/services/session-output.d.ts +5 -0
  41. package/dist/services/session-output.d.ts.map +1 -1
  42. package/package/acode/dist/built-in-file-tools.d.ts +5 -1
  43. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  44. package/package/acode/dist/built-in-file-tools.js +14 -203
  45. package/package/acode/dist/builtins/commandArtifacts.d.ts +25 -0
  46. package/package/acode/dist/builtins/commandArtifacts.d.ts.map +1 -0
  47. package/package/acode/dist/builtins/commandArtifacts.js +77 -0
  48. package/package/acode/dist/builtins/commandOutputEvents.d.ts +21 -0
  49. package/package/acode/dist/builtins/commandOutputEvents.d.ts.map +1 -0
  50. package/package/acode/dist/builtins/commandOutputEvents.js +56 -0
  51. package/package/acode/dist/builtins/shellCommandPreparation.d.ts +15 -0
  52. package/package/acode/dist/builtins/shellCommandPreparation.d.ts.map +1 -0
  53. package/package/acode/dist/builtins/shellCommandPreparation.js +44 -0
  54. package/package/acode/dist/builtins/shellCommandResult.d.ts +30 -0
  55. package/package/acode/dist/builtins/shellCommandResult.d.ts.map +1 -0
  56. package/package/acode/dist/builtins/shellCommandResult.js +71 -0
  57. package/package/acode/dist/builtins/shellCommandRunner.d.ts +14 -0
  58. package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +1 -0
  59. package/package/acode/dist/builtins/shellCommandRunner.js +114 -0
  60. package/package/acode/dist/builtins/shellOutputDecoder.d.ts +5 -0
  61. package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +1 -0
  62. package/package/acode/dist/builtins/shellOutputDecoder.js +30 -0
  63. package/package/acode/dist/mcpToolNames.d.ts +11 -0
  64. package/package/acode/dist/mcpToolNames.d.ts.map +1 -0
  65. package/package/acode/dist/mcpToolNames.js +17 -0
  66. package/package/acode/dist/runtime.d.ts +19 -0
  67. package/package/acode/dist/runtime.d.ts.map +1 -1
  68. package/package/acode/dist/runtime.js +233 -35
  69. package/package/acode/dist/types.d.ts +1 -1
  70. package/package/acode/dist/types.d.ts.map +1 -1
  71. package/package/acode/package.json +1 -1
  72. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +20 -0
  73. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
  74. package/package/aws-client-agent-mcp/dist/agent-client.js +71 -2
  75. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
  76. package/package/aws-client-agent-mcp/dist/constants.d.ts +4 -1
  77. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
  78. package/package/aws-client-agent-mcp/dist/constants.js +5 -1
  79. package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
  80. package/package/aws-client-agent-mcp/dist/http-client.d.ts +17 -1
  81. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  82. package/package/aws-client-agent-mcp/dist/http-client.js +124 -28
  83. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  84. package/package/aws-client-agent-mcp/dist/http-client.test.js +108 -2
  85. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
  86. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +8 -3
  87. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  88. package/package/aws-client-agent-mcp/dist/mcp-server.js +63 -10
  89. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  90. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +47 -0
  91. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  92. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -36
  93. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  94. package/package/aws-client-agent-mcp/dist/mcp-tools.js +3 -169
  95. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  96. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts +2 -0
  97. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts.map +1 -0
  98. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +45 -0
  99. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js.map +1 -0
  100. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +11 -0
  101. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -0
  102. package/package/aws-client-agent-mcp/dist/memory-tools.js +144 -0
  103. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -0
  104. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
  105. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +79 -28
  106. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
  107. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +35 -0
  108. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -1
  109. package/package/aws-client-agent-mcp/dist/types.d.ts +9 -0
  110. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  111. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  112. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  113. package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -0
  114. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  115. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -3
  116. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
  117. package/package.json +1 -1
@@ -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 = 100;
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.type === "error" ? "error" : "info",
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 ? `接口: ${data.endpoint}` : undefined,
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
- return data.requestBody
713
- ? `${base}${AI_REQUEST_BODY_MARKER}${data.requestBody}`
714
- : base;
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
- export function resolveRuntimeStatusFromProviderEvent(event) {
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
- entry.currentRuntimeStatus = status;
836
- entry.currentRuntimeActionType = actionInfo?.actionType;
837
- entry.currentRuntimeActionLabel = actionInfo?.actionLabel;
838
- entry.currentRuntimeActionDetail = actionInfo?.actionDetail;
839
- console.log(`[${definition.displayName} Adapter] Status changed: ${status} for agent ${entry.agentId}`, actionInfo || "");
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
- try {
996
- // 等待一小段时间让进程启动
997
- await new Promise((resolve) => setTimeout(resolve, 500));
998
- // 尝试通过 adapter 获取 PID(如果支持)
999
- sdkPid = adapter.getSessionPid?.(sessionId);
1000
- // 如果 adapter 不支持,尝试通过进程检测服务查找
1001
- if (!sdkPid) {
1002
- const processes = findClaudeCodeProcesses();
1003
- // 查找匹配 agentId 或工作目录的进程
1004
- const matched = processes.find((p) => p.agentId === agentId ||
1005
- p.cwd === workspacePath ||
1006
- p.command.includes(workspacePath));
1007
- if (matched) {
1008
- sdkPid = matched.pid;
1009
- console.log(`[Runtime] Found SDK process PID: ${sdkPid} for agentId=${agentId}`);
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
- catch (pidError) {
1014
- console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
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({ ok: true, status: "stopped", mode: "none" });
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
- await sendStatus(agentId, null, "stopped", undefined, undefined, {
1399
- runtimeAccessToken: sdkEntry.runtimeAccessToken,
1400
- schedulerBaseUrl: sdkEntry.schedulerBaseUrl,
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=terminal.stop-options.test.d.ts.map
@@ -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
+ });