@co0ontty/wand 4.89.5 → 4.90.0

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 (197) hide show
  1. package/README.md +5 -3
  2. package/dist/agent-dispatch.js +4 -9
  3. package/dist/ai-team-employee-binding.js +10 -9
  4. package/dist/ai-team-runner.d.ts +2 -1
  5. package/dist/ai-team-runner.js +38 -2
  6. package/dist/ai-team-types.js +3 -2
  7. package/dist/ai-text-runner.d.ts +61 -0
  8. package/dist/ai-text-runner.js +653 -0
  9. package/dist/avatar-input.d.ts +2 -0
  10. package/dist/avatar-input.js +19 -0
  11. package/dist/bounded-process-output.d.ts +5 -0
  12. package/dist/bounded-process-output.js +9 -0
  13. package/dist/build-info.json +3 -3
  14. package/dist/command-error.d.ts +6 -0
  15. package/dist/command-error.js +8 -0
  16. package/dist/config.js +11 -42
  17. package/dist/conversation-service.d.ts +12 -2
  18. package/dist/conversation-service.js +104 -25
  19. package/dist/conversation-session-preview.d.ts +2 -0
  20. package/dist/conversation-session-preview.js +4 -0
  21. package/dist/decision-client.js +22 -6
  22. package/dist/decision-expert-employee.js +2 -2
  23. package/dist/decision-expert-identity.d.ts +1 -1
  24. package/dist/decision-expert-identity.js +1 -1
  25. package/dist/decision-expert-service.d.ts +1 -0
  26. package/dist/decision-expert-service.js +82 -76
  27. package/dist/decision-hardware.js +4 -3
  28. package/dist/decision-types.d.ts +4 -0
  29. package/dist/decision-types.js +4 -0
  30. package/dist/deployment-lock.d.ts +13 -0
  31. package/dist/deployment-lock.js +189 -0
  32. package/dist/employee-text.js +1 -1
  33. package/dist/fixed-employee-avatar.d.ts +7 -0
  34. package/dist/fixed-employee-avatar.js +13 -0
  35. package/dist/git-quick-commit.d.ts +5 -40
  36. package/dist/git-quick-commit.js +8 -636
  37. package/dist/hr-employee-identity.d.ts +5 -0
  38. package/dist/hr-employee-identity.js +5 -0
  39. package/dist/hr-employee.d.ts +5 -0
  40. package/dist/hr-employee.js +21 -0
  41. package/dist/middleware/path-safety.js +1 -1
  42. package/dist/milestone-scope.js +1 -1
  43. package/dist/model-auto-assign.d.ts +1 -1
  44. package/dist/model-auto-assign.js +1 -1
  45. package/dist/models.d.ts +4 -0
  46. package/dist/models.js +21 -1
  47. package/dist/npm-update-utils.js +5 -44
  48. package/dist/pi-auto-resources.js +6 -3
  49. package/dist/plush-avatar.d.ts +10 -1
  50. package/dist/plush-avatar.js +13 -1
  51. package/dist/plush-cat-appearance.d.ts +97 -0
  52. package/dist/plush-cat-appearance.js +118 -0
  53. package/dist/plush-cat-whiskers.d.ts +6 -0
  54. package/dist/plush-cat-whiskers.js +42 -0
  55. package/dist/process-manager.d.ts +22 -21
  56. package/dist/process-manager.js +49 -83
  57. package/dist/prompt-optimizer.d.ts +1 -1
  58. package/dist/prompt-optimizer.js +1 -1
  59. package/dist/render-host.js +1 -19
  60. package/dist/scheduled-task-service.d.ts +41 -0
  61. package/dist/scheduled-task-service.js +294 -0
  62. package/dist/scheduled-task-state.d.ts +11 -0
  63. package/dist/scheduled-task-state.js +65 -0
  64. package/dist/scheduled-task-time.d.ts +4 -0
  65. package/dist/scheduled-task-time.js +82 -0
  66. package/dist/scheduled-task-types.d.ts +71 -0
  67. package/dist/scheduled-task-types.js +1 -0
  68. package/dist/server-ai-team-routes.js +3 -17
  69. package/dist/server-employee-routes.d.ts +3 -0
  70. package/dist/server-employee-routes.js +20 -38
  71. package/dist/server-file-routes.js +44 -1
  72. package/dist/server-pi-recommendation-routes.js +13 -4
  73. package/dist/server-request.d.ts +2 -0
  74. package/dist/server-request.js +9 -0
  75. package/dist/server-resume-routes.js +1 -1
  76. package/dist/server-scheduled-task-routes.d.ts +4 -0
  77. package/dist/server-scheduled-task-routes.js +34 -0
  78. package/dist/server-session-routes.d.ts +5 -13
  79. package/dist/server-session-routes.js +35 -70
  80. package/dist/server-task-routes.d.ts +6 -21
  81. package/dist/server-task-routes.js +25 -120
  82. package/dist/server-team-dispatch-routes.js +12 -2
  83. package/dist/server-workspace-routes.d.ts +1 -1
  84. package/dist/server-workspace-routes.js +33 -78
  85. package/dist/server.js +55 -20
  86. package/dist/session-model.d.ts +15 -0
  87. package/dist/session-model.js +23 -0
  88. package/dist/session-registry.d.ts +5 -1
  89. package/dist/session-registry.js +20 -11
  90. package/dist/session-request.d.ts +9 -0
  91. package/dist/session-request.js +27 -0
  92. package/dist/session-topic.d.ts +1 -1
  93. package/dist/session-topic.js +1 -1
  94. package/dist/session-transport.d.ts +3 -1
  95. package/dist/session-transport.js +11 -5
  96. package/dist/silicon-employee-draft.d.ts +2 -2
  97. package/dist/silicon-employee-draft.js +3 -3
  98. package/dist/socket-probe.d.ts +2 -0
  99. package/dist/socket-probe.js +20 -0
  100. package/dist/storage-schedules.d.ts +20 -0
  101. package/dist/storage-schedules.js +78 -0
  102. package/dist/storage-schema.d.ts +3 -0
  103. package/dist/storage-schema.js +702 -0
  104. package/dist/storage.d.ts +42 -8
  105. package/dist/storage.js +182 -715
  106. package/dist/structured-claude-adapter.js +12 -0
  107. package/dist/structured-claude-protocol.d.ts +4 -0
  108. package/dist/structured-claude-protocol.js +18 -4
  109. package/dist/structured-codex-adapter.js +11 -0
  110. package/dist/structured-exec-pump.js +79 -38
  111. package/dist/structured-failure.d.ts +21 -0
  112. package/dist/structured-failure.js +14 -0
  113. package/dist/structured-gemini-adapter.js +12 -2
  114. package/dist/structured-grok-adapter.js +12 -2
  115. package/dist/structured-opencode-adapter.js +8 -1
  116. package/dist/structured-qoder-adapter.js +14 -0
  117. package/dist/structured-session-manager.d.ts +13 -18
  118. package/dist/structured-session-manager.js +147 -517
  119. package/dist/system-employee.d.ts +1 -1
  120. package/dist/system-employee.js +1 -1
  121. package/dist/task-archive-policy.js +1 -1
  122. package/dist/task-read-model.d.ts +27 -0
  123. package/dist/task-read-model.js +47 -0
  124. package/dist/task-retention.d.ts +1 -1
  125. package/dist/task-retention.js +1 -1
  126. package/dist/task-title-service.d.ts +30 -0
  127. package/dist/task-title-service.js +122 -0
  128. package/dist/task-title.d.ts +1 -1
  129. package/dist/task-title.js +1 -1
  130. package/dist/terminal-daemon-client.js +1 -18
  131. package/dist/user-memory.js +1 -1
  132. package/dist/web-ui/asset-runtime.d.ts +12 -0
  133. package/dist/web-ui/asset-runtime.js +6 -0
  134. package/dist/web-ui/composer-avatar-activity.d.ts +6 -0
  135. package/dist/web-ui/composer-avatar-activity.js +29 -0
  136. package/dist/web-ui/content/ai-teams.js +13 -13
  137. package/dist/web-ui/content/module-manifest.json +1 -0
  138. package/dist/web-ui/content/modules/5478be410b112154/chat-render-PY6DIXNZ.js +24 -0
  139. package/dist/web-ui/content/modules/5478be410b112154/chunk-2YZLCGYN.js +1 -0
  140. package/dist/web-ui/content/modules/5478be410b112154/chunk-6CFHW3FA.js +111 -0
  141. package/dist/web-ui/content/modules/5478be410b112154/chunk-6XQEBIEX.js +1 -0
  142. package/dist/web-ui/content/modules/5478be410b112154/chunk-DDSO7OX7.js +1 -0
  143. package/dist/web-ui/content/modules/5478be410b112154/chunk-G3CXNMA2.js +20 -0
  144. package/dist/web-ui/content/modules/5478be410b112154/chunk-LGVAPHRS.js +36 -0
  145. package/dist/web-ui/content/modules/5478be410b112154/chunk-NURCNMRU.js +1 -0
  146. package/dist/web-ui/content/modules/5478be410b112154/chunk-PCMFGZKX.js +1 -0
  147. package/dist/web-ui/content/modules/5478be410b112154/chunk-QRNA4D7E.js +1 -0
  148. package/dist/web-ui/content/modules/5478be410b112154/chunk-TMRPHNJO.js +1 -0
  149. package/dist/web-ui/content/modules/5478be410b112154/chunk-WC7RK7LP.js +1 -0
  150. package/dist/web-ui/content/modules/5478be410b112154/chunk-YOXXNXOX.js +2 -0
  151. package/dist/web-ui/content/modules/5478be410b112154/chunk-YX475LXO.js +1 -0
  152. package/dist/web-ui/content/modules/5478be410b112154/employee-card-MDWKU4V3.js +1 -0
  153. package/dist/web-ui/content/modules/5478be410b112154/host-56WPSON2.js +1 -0
  154. package/dist/web-ui/content/modules/5478be410b112154/host-5QOHKHNC.js +1 -0
  155. package/dist/web-ui/content/modules/5478be410b112154/host-AWIJ5L2K.js +1 -0
  156. package/dist/web-ui/content/modules/5478be410b112154/host-BOZLGIT5.js +69 -0
  157. package/dist/web-ui/content/modules/5478be410b112154/host-EZV22Q7Y.js +4 -0
  158. package/dist/web-ui/content/modules/5478be410b112154/host-IW43Z5ZF.js +1 -0
  159. package/dist/web-ui/content/modules/5478be410b112154/host-JT7NIUQL.js +1 -0
  160. package/dist/web-ui/content/modules/5478be410b112154/host-JWEIDB55.js +3 -0
  161. package/dist/web-ui/content/modules/5478be410b112154/host-OU5TRYH6.js +2 -0
  162. package/dist/web-ui/content/modules/5478be410b112154/host-PRSTCPVE.js +5 -0
  163. package/dist/web-ui/content/modules/5478be410b112154/host-TWPJI3HE.js +1 -0
  164. package/dist/web-ui/content/modules/5478be410b112154/host-WZGJSNML.js +1 -0
  165. package/dist/web-ui/content/modules/5478be410b112154/host-X6KQVFD6.js +1 -0
  166. package/dist/web-ui/content/modules/5478be410b112154/host-YKCQFMQR.js +1 -0
  167. package/dist/web-ui/content/modules/5478be410b112154/host-bridge-2LZX6UUZ.js +1 -0
  168. package/dist/web-ui/content/modules/5478be410b112154/library-bridge-MQFJGTGY.js +4 -0
  169. package/dist/web-ui/content/modules/5478be410b112154/page-T3VUCU2W.js +3 -0
  170. package/dist/web-ui/content/modules/5478be410b112154/panel-HJBGBBPY.js +1 -0
  171. package/dist/web-ui/content/modules/5478be410b112154/runtime.js +920 -0
  172. package/dist/web-ui/content/modules/5478be410b112154/sha256-ANXV72UC.js +1 -0
  173. package/dist/web-ui/content/modules/5478be410b112154/task-board-host-AVMDIHIV.js +2 -0
  174. package/dist/web-ui/content/modules/5478be410b112154/workspace-agent-picker-KI7QVSK3.js +1 -0
  175. package/dist/web-ui/content/modules/5478be410b112154/workspace-window-L542FGP2.js +1 -0
  176. package/dist/web-ui/content/plush-avatar.js +177 -177
  177. package/dist/web-ui/content/scripts.js +1 -994
  178. package/dist/web-ui/content/styles.css +1 -1
  179. package/dist/web-ui/content/tailwind.css +1 -1
  180. package/dist/web-ui/embedded-assets.d.ts +32 -0
  181. package/dist/web-ui/embedded-assets.js +10 -8
  182. package/dist/web-ui/markdown.d.ts +3 -2
  183. package/dist/web-ui/markdown.js +61 -48
  184. package/dist/web-ui/message-file-target.d.ts +13 -0
  185. package/dist/web-ui/message-file-target.js +85 -0
  186. package/dist/web-ui/provider-identity.d.ts +1 -1
  187. package/dist/web-ui/provider-identity.js +3 -4
  188. package/dist/web-ui/react/local-preview/controller.d.ts +2 -0
  189. package/dist/web-ui/react/local-preview/controller.js +12 -0
  190. package/dist/web-ui/scripts.d.ts +10 -0
  191. package/dist/web-ui/scripts.js +42 -5
  192. package/package.json +6 -3
  193. package/browser-extension/icons/wand-128.png +0 -0
  194. package/browser-extension/icons/wand-16.png +0 -0
  195. package/browser-extension/icons/wand-32.png +0 -0
  196. package/browser-extension/icons/wand-48.png +0 -0
  197. package/dist/web-ui/content/licenses/codex-LICENSE.txt +0 -21
@@ -6,6 +6,7 @@ import { startStructuredCli } from "./structured-exec-pump.js";
6
6
  import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
7
7
  import { thinkingEffortToClaudeCliEffort } from "./structured-provider-common.js";
8
8
  import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
9
+ import { cliUnacceptedFacts } from "./structured-failure.js";
9
10
  const ROOT_FALLBACK_ALLOWED_TOOLS = [
10
11
  "Bash", "Edit", "Write", "Read", "Glob", "Grep", "NotebookEdit", "WebFetch", "WebSearch",
11
12
  ];
@@ -143,6 +144,7 @@ export class ClaudeCliRunner {
143
144
  const reducer = new ClaudeCliProtocolReducer(context.session);
144
145
  let killedForQuestion = false;
145
146
  let stdoutTail = "";
147
+ let unparsedStdout = false;
146
148
  return startStructuredCli({
147
149
  sessionId: context.session.id,
148
150
  file: "claude",
@@ -164,6 +166,7 @@ export class ClaudeCliRunner {
164
166
  event = JSON.parse(trimmed);
165
167
  }
166
168
  catch {
169
+ unparsedStdout = true;
167
170
  return;
168
171
  }
169
172
  if (event && typeof event === "object" && !Array.isArray(event)) {
@@ -191,6 +194,15 @@ export class ClaudeCliRunner {
191
194
  primaryError: reducer.errorText,
192
195
  stopReason: killedForQuestion ? "ask-user-question" : undefined,
193
196
  spawnError,
197
+ ...cliUnacceptedFacts({
198
+ state: reducer.state,
199
+ primaryError: reducer.errorText,
200
+ protocolError: reducer.errorResultObserved,
201
+ unparsedStdout,
202
+ signal,
203
+ progressed: reducer.assistantWorkObserved,
204
+ ...(spawnError ? { spawnError } : {}),
205
+ }),
194
206
  }),
195
207
  });
196
208
  }
@@ -22,6 +22,10 @@ export declare class ClaudeCliProtocolReducer {
22
22
  askUserQuestionDetected: boolean;
23
23
  /** result 事件带 is_error 时的说明(如「API Error: 503 …」),作为这一轮的主错误。 */
24
24
  errorText: string | null;
25
+ /** 协议里出现过带 is_error 的 result 事件;没有本轮助手内容时才是可降级的执行前拒绝。 */
26
+ errorResultObserved: boolean;
27
+ /** 本轮真正跑出来的助手/工具内容,错误回显不算。 */
28
+ assistantWorkObserved: boolean;
25
29
  private readonly blocksByKey;
26
30
  private readonly keyOrder;
27
31
  private readonly taskMetaRegistry;
@@ -123,6 +123,10 @@ export class ClaudeCliProtocolReducer {
123
123
  askUserQuestionDetected = false;
124
124
  /** result 事件带 is_error 时的说明(如「API Error: 503 …」),作为这一轮的主错误。 */
125
125
  errorText = null;
126
+ /** 协议里出现过带 is_error 的 result 事件;没有本轮助手内容时才是可降级的执行前拒绝。 */
127
+ errorResultObserved = false;
128
+ /** 本轮真正跑出来的助手/工具内容,错误回显不算。 */
129
+ assistantWorkObserved = false;
126
130
  blocksByKey = new Map();
127
131
  keyOrder = [];
128
132
  taskMetaRegistry = new Map();
@@ -150,8 +154,10 @@ export class ClaudeCliProtocolReducer {
150
154
  const stamped = parentId === null
151
155
  ? stampSelfTask(extracted.content, this.taskMetaRegistry)
152
156
  : tagSubagentBlocks(extracted.content, parentId, this.taskMetaRegistry);
153
- if (stamped.length > 0)
157
+ if (stamped.length > 0) {
158
+ this.assistantWorkObserved = true;
154
159
  this.upsertBlocks(key, stamped);
160
+ }
155
161
  if (!managed && extracted.content.some((block) => block.type === "tool_use" && block.name === "AskUserQuestion")) {
156
162
  this.askUserQuestionDetected = true;
157
163
  }
@@ -181,15 +187,23 @@ export class ClaudeCliProtocolReducer {
181
187
  const stamped = parentId === null
182
188
  ? stampParentTaskResults(collected, this.taskMetaRegistry)
183
189
  : tagSubagentBlocks(collected, parentId, this.taskMetaRegistry);
184
- if (stamped.length > 0)
190
+ if (stamped.length > 0) {
191
+ this.assistantWorkObserved = true;
185
192
  this.upsertBlocks(`tool_result:${this.toolResultSequence++}`, stamped);
193
+ }
186
194
  return true;
187
195
  }
188
196
  if (event.type === "result") {
189
197
  if (typeof event.result === "string")
190
198
  this.state.result = event.result.trim();
191
- if (event.is_error === true && this.state.result)
192
- this.errorText = this.state.result;
199
+ if (event.is_error === true) {
200
+ this.errorResultObserved = true;
201
+ const detail = this.state.result
202
+ || (typeof event.subtype === "string" && event.subtype && event.subtype !== "success"
203
+ ? `Claude 以 ${event.subtype} 结束,未产生任何输出。` : "");
204
+ if (detail)
205
+ this.errorText = detail;
206
+ }
193
207
  this.state.model = extractClaudeModelName(event.modelUsage && typeof event.modelUsage === "object"
194
208
  ? event.modelUsage
195
209
  : undefined) ?? this.state.model;
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { startStructuredCli } from "./structured-exec-pump.js";
3
3
  import { CodexProtocolReducer } from "./structured-codex-protocol.js";
4
+ import { cliUnacceptedFacts } from "./structured-failure.js";
4
5
  import { whichSync } from "./path-repair.js";
5
6
  import { thinkingEffortToCodexReasoningEffort, promptWithSystemFallback } from "./structured-provider-common.js";
6
7
  /** Build the stable CLI contract for a structured Codex turn. */
@@ -43,6 +44,7 @@ export class CodexRunner {
43
44
  const args = buildCodexArgs(context.session);
44
45
  const file = whichSync("codex", { env: context.env }) ?? "codex";
45
46
  const reducer = new CodexProtocolReducer(context.session);
47
+ let unparsedStdout = false;
46
48
  return startStructuredCli({
47
49
  sessionId: context.session.id,
48
50
  file,
@@ -65,6 +67,7 @@ export class CodexRunner {
65
67
  event = JSON.parse(trimmed);
66
68
  }
67
69
  catch {
70
+ unparsedStdout = true;
68
71
  return;
69
72
  }
70
73
  if (event && typeof event === "object" && !Array.isArray(event)) {
@@ -81,6 +84,14 @@ export class CodexRunner {
81
84
  primaryError: reducer.primaryError,
82
85
  errors: reducer.errors,
83
86
  spawnError,
87
+ ...cliUnacceptedFacts({
88
+ state: reducer.state,
89
+ primaryError: reducer.primaryError,
90
+ protocolError: !!reducer.primaryError,
91
+ unparsedStdout,
92
+ signal,
93
+ ...(spawnError ? { spawnError } : {}),
94
+ }),
84
95
  }),
85
96
  });
86
97
  }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { appendDiagnosticTail, MAX_PROTOCOL_LINE_BYTES } from "./bounded-process-output.js";
2
3
  import { createUtf8TextDecoder, } from "./structured-exec-host.js";
3
4
  import { signalNameFromNumber } from "./signal-utils.js";
4
5
  export function startStructuredCli(options) {
@@ -8,41 +9,87 @@ export function startStructuredCli(options) {
8
9
  state: options.createState(),
9
10
  stderr: "",
10
11
  observer,
11
- requestStop: () => source?.interrupt(),
12
+ requestStop: () => requestStop(),
12
13
  };
13
14
  let source = null;
14
- let sourceReady;
15
15
  let lineBuffer = "";
16
+ let lineBytes = 0;
16
17
  let settled = false;
18
+ let stopRequested = false;
19
+ let stopDelivered = false;
20
+ // A pending spawn has no cancellable handle yet. Remember one request and
21
+ // deliver it once when ready; never install a self-referential placeholder.
22
+ function interruptSource() {
23
+ if (!source || stopDelivered)
24
+ return;
25
+ stopDelivered = true;
26
+ try {
27
+ source.interrupt();
28
+ }
29
+ catch { /* best-effort interruption */ }
30
+ }
31
+ function requestStop() {
32
+ if (settled || stopRequested)
33
+ return;
34
+ stopRequested = true;
35
+ interruptSource();
36
+ }
17
37
  let resolveCompletion;
18
- const completion = new Promise((resolve) => {
38
+ let rejectCompletion;
39
+ const completion = new Promise((resolve, reject) => {
19
40
  resolveCompletion = resolve;
41
+ rejectCompletion = reject;
20
42
  });
21
- const finish = (exitCode, signal, spawnError) => {
43
+ const finish = (exitCode, signal, spawnError, protocolError) => {
22
44
  if (settled)
23
45
  return;
24
46
  settled = true;
25
- if (lineBuffer.trim())
26
- options.processLine(lineBuffer, ctx);
27
- lineBuffer = "";
28
- resolveCompletion(options.finalize(ctx, exitCode, signal, spawnError));
47
+ try {
48
+ if (!protocolError && lineBuffer.trim())
49
+ options.processLine(lineBuffer, ctx);
50
+ const result = options.finalize(ctx, exitCode, signal, spawnError);
51
+ resolveCompletion(protocolError ? { ...result, exitCode: 1, primaryError: protocolError.message,
52
+ retryForbidden: true, failure: { kind: "runtime", delivery: "unknown", retryable: false } } : result);
53
+ }
54
+ catch (error) {
55
+ rejectCompletion(error);
56
+ }
57
+ finally {
58
+ lineBuffer = "";
59
+ lineBytes = 0;
60
+ }
29
61
  };
30
62
  const handleStdoutText = (text) => {
31
- if (!text || !observer.isActive())
63
+ if (settled || !text || !observer.isActive())
32
64
  return;
33
65
  observer.onStdout?.(text);
34
66
  options.onStdoutText?.(text);
35
- lineBuffer += text;
36
- const lines = lineBuffer.split("\n");
37
- lineBuffer = lines.pop() ?? "";
38
- for (const line of lines)
39
- options.processLine(line, ctx);
67
+ try {
68
+ const parts = text.split("\n");
69
+ for (let index = 0; index < parts.length; index++) {
70
+ const part = parts[index];
71
+ lineBytes += Buffer.byteLength(part, "utf8");
72
+ if (lineBytes > MAX_PROTOCOL_LINE_BYTES)
73
+ throw new Error("CLI 协议单行超过 8 MiB 上限;本轮已停止,送达结果不能据此推定为未接受。");
74
+ lineBuffer += part;
75
+ if (index < parts.length - 1) {
76
+ options.processLine(lineBuffer, ctx);
77
+ lineBuffer = "";
78
+ lineBytes = 0;
79
+ }
80
+ }
81
+ }
82
+ catch (error) {
83
+ // Settle the failure before kill can synchronously deliver a close event.
84
+ finish(1, null, undefined, error instanceof Error ? error : new Error(String(error)));
85
+ interruptSource();
86
+ }
40
87
  };
41
88
  const handleStderrText = (text) => {
42
- if (!text || !observer.isActive())
89
+ if (settled || !text || !observer.isActive())
43
90
  return;
44
91
  observer.onStderr?.(text);
45
- ctx.stderr += text;
92
+ ctx.stderr = appendDiagnosticTail(ctx.stderr, text);
46
93
  };
47
94
  const host = options.execHost;
48
95
  if (host?.persistent) {
@@ -54,23 +101,23 @@ export function startStructuredCli(options) {
54
101
  env: options.env,
55
102
  ...(options.stdinData !== undefined ? { stdinData: options.stdinData } : {}),
56
103
  };
57
- let pendingInterrupt = null;
58
- sourceReady = host.spawnStructured(request).then((handle) => {
59
- source = { pid: handle.pid, interrupt: () => handle.interrupt() };
60
- if (pendingInterrupt !== null)
61
- handle.interrupt(pendingInterrupt);
62
- wireRemoteHandle(handle, handleStdoutText, handleStderrText, finish);
63
- }, () => {
104
+ const unavailable = () => {
105
+ source = null;
64
106
  // Daemon vanished between health check and spawn; surface as spawn error.
65
107
  finish(null, null, Object.assign(new Error(`terminal daemon unavailable for ${options.file}`), { code: "EDAEMON" }));
66
- });
67
- source = {
68
- pid: null,
69
- interrupt: (signal) => {
70
- pendingInterrupt = signal ?? "SIGTERM";
71
- void sourceReady.then(() => source?.interrupt());
72
- },
73
108
  };
109
+ try {
110
+ void host.spawnStructured(request).then((handle) => {
111
+ source = { pid: handle.pid, interrupt: () => handle.interrupt() };
112
+ // Replay and subscribe before applying a pending external cancellation.
113
+ wireRemoteHandle(handle, handleStdoutText, handleStderrText, finish);
114
+ if (stopRequested && !settled)
115
+ interruptSource();
116
+ }, unavailable);
117
+ }
118
+ catch {
119
+ unavailable();
120
+ }
74
121
  }
75
122
  else {
76
123
  const spawnProcess = options.spawnProcess ?? spawn;
@@ -87,8 +134,8 @@ export function startStructuredCli(options) {
87
134
  child.stderr?.on("data", (chunk) => handleStderrText(stderrDecoder.write(chunk)));
88
135
  child.on("error", (error) => finish(null, null, error));
89
136
  child.stdin?.on("error", (error) => {
137
+ requestStop();
90
138
  finish(null, null, error);
91
- child.kill("SIGTERM");
92
139
  });
93
140
  child.on("close", (exitCode, signalName) => {
94
141
  handleStdoutText(stdoutDecoder.end());
@@ -97,7 +144,6 @@ export function startStructuredCli(options) {
97
144
  });
98
145
  if (wantsStdin)
99
146
  child.stdin?.end(options.stdinData);
100
- sourceReady = Promise.resolve();
101
147
  }
102
148
  return {
103
149
  args: options.args,
@@ -106,12 +152,7 @@ export function startStructuredCli(options) {
106
152
  return source?.pid ?? null;
107
153
  },
108
154
  completion,
109
- interrupt: () => {
110
- try {
111
- source?.interrupt();
112
- }
113
- catch { /* best-effort external interruption */ }
114
- },
155
+ interrupt: requestStop,
115
156
  };
116
157
  }
117
158
  function wireRemoteHandle(handle, onStdout, onStderr, finish) {
@@ -5,6 +5,27 @@ export interface StructuredFailure {
5
5
  delivery: "rejected" | "accepted" | "unknown";
6
6
  retryable: boolean;
7
7
  }
8
+ /** Extra facts a CLI-backed adapter adds to its finalize() result. */
9
+ export type StructuredCliUnacceptedFacts = Pick<StructuredRunnerResult, "inputAccepted" | "rejection">;
10
+ /**
11
+ * An in-band provider protocol error is the only evidence that lets a CLI adapter claim the input
12
+ * was never taken: the reply arrived from the provider itself, this turn produced no block, usage
13
+ * or cost, and stdout held nothing we could not account for as protocol traffic. Anything else —
14
+ * unparsed stdout, a signal, a spawn failure or work already done — stays unknown/accepted so no
15
+ * executed turn can be replayed on another candidate.
16
+ */
17
+ export declare function cliUnacceptedFacts(input: {
18
+ state: StructuredRunnerTurnState;
19
+ primaryError: string | null;
20
+ /** The provider reported this error inside its own protocol stream. */
21
+ protocolError: boolean;
22
+ /** Stdout carried bytes the adapter could not parse as protocol events. */
23
+ unparsedStdout: boolean;
24
+ signal: NodeJS.Signals | null;
25
+ spawnError?: NodeJS.ErrnoException;
26
+ /** Adapters whose protocol echoes the failure text into `result` state what really ran. */
27
+ progressed?: boolean;
28
+ }): StructuredCliUnacceptedFacts;
8
29
  /** Only call for a provider protocol error, never assistant text, stderr or a caught transport error. */
9
30
  export declare function classifyProviderRejection(message: string | null | undefined): ProviderRejectionKind | null;
10
31
  /** Monotonic observations matter: a final error can replace earlier usage or content. */
@@ -1,3 +1,17 @@
1
+ /**
2
+ * An in-band provider protocol error is the only evidence that lets a CLI adapter claim the input
3
+ * was never taken: the reply arrived from the provider itself, this turn produced no block, usage
4
+ * or cost, and stdout held nothing we could not account for as protocol traffic. Anything else —
5
+ * unparsed stdout, a signal, a spawn failure or work already done — stays unknown/accepted so no
6
+ * executed turn can be replayed on another candidate.
7
+ */
8
+ export function cliUnacceptedFacts(input) {
9
+ if (!input.protocolError || input.unparsedStdout || input.signal || input.spawnError || !input.primaryError?.trim())
10
+ return {};
11
+ if (input.progressed ?? hasStructuredExecutionProgress(input.state))
12
+ return {};
13
+ return { inputAccepted: false, rejection: classifyProviderRejection(input.primaryError) ?? "model-unavailable" };
14
+ }
1
15
  /** Only call for a provider protocol error, never assistant text, stderr or a caught transport error. */
2
16
  export function classifyProviderRejection(message) {
3
17
  const text = message?.trim() ?? "";
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { startStructuredCli } from "./structured-exec-pump.js";
3
3
  import { asRecord } from "./structured-content.js";
4
+ import { cliUnacceptedFacts } from "./structured-failure.js";
4
5
  import { promptWithSystemFallback } from "./structured-provider-common.js";
5
6
  /**
6
7
  * Gemini CLI 的会话写在 `~/.gemini/tmp/<project_hash>/chats/`。ID 找不回来时 headless
@@ -163,6 +164,8 @@ export class GeminiRunner {
163
164
  phase: "responding",
164
165
  };
165
166
  let primaryError = null;
167
+ let protocolError = false;
168
+ let unparsedStdout = false;
166
169
  return startStructuredCli({
167
170
  sessionId: context.session.id,
168
171
  file: "gemini",
@@ -180,10 +183,16 @@ export class GeminiRunner {
180
183
  try {
181
184
  const event = JSON.parse(line);
182
185
  observer.onEvent?.(event);
183
- primaryError = applyGeminiEvent(state, event) ?? primaryError;
186
+ const error = applyGeminiEvent(state, event);
187
+ if (error) {
188
+ primaryError = primaryError ?? error;
189
+ protocolError = true;
190
+ }
184
191
  observer.onUpdate(state);
185
192
  }
186
- catch { /* Gemini stdout is NDJSON; ignore non-protocol noise. */ }
193
+ catch {
194
+ unparsedStdout = true; /* Non-protocol stdout is not evidence of a safe refusal. */
195
+ }
187
196
  },
188
197
  finalize: (ctx, exitCode, signal, spawnError) => ({
189
198
  state,
@@ -192,6 +201,7 @@ export class GeminiRunner {
192
201
  stderr: ctx.stderr,
193
202
  primaryError,
194
203
  ...(spawnError ? { spawnError } : {}),
204
+ ...cliUnacceptedFacts({ state, primaryError, protocolError, unparsedStdout, signal, ...(spawnError ? { spawnError } : {}) }),
195
205
  }),
196
206
  });
197
207
  }
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { startStructuredCli } from "./structured-exec-pump.js";
3
3
  import { asRecord } from "./structured-content.js";
4
+ import { cliUnacceptedFacts } from "./structured-failure.js";
4
5
  import { systemPromptArgs, thinkingEffortToGrokEffort } from "./structured-provider-common.js";
5
6
  function grokText(value) {
6
7
  if (typeof value === "string")
@@ -220,6 +221,8 @@ export class GrokRunner {
220
221
  model: context.session.selectedModel ?? context.session.structuredState?.model,
221
222
  };
222
223
  let primaryError = null;
224
+ let protocolError = false;
225
+ let unparsedStdout = false;
223
226
  return startStructuredCli({
224
227
  sessionId: context.session.id,
225
228
  file: "grok",
@@ -239,13 +242,20 @@ export class GrokRunner {
239
242
  try {
240
243
  const event = JSON.parse(trimmed);
241
244
  observer.onEvent?.(event);
242
- primaryError = applyGrokEvent(state, event) ?? primaryError;
245
+ const error = applyGrokEvent(state, event);
246
+ if (error) {
247
+ primaryError = primaryError ?? error;
248
+ protocolError = true;
249
+ }
243
250
  observer.onUpdate(state);
244
251
  }
245
- catch { /* Grok diagnostics belong on stderr; ignore non-JSON stdout defensively. */ }
252
+ catch {
253
+ unparsedStdout = true; /* Non-protocol stdout is not evidence of a safe refusal. */
254
+ }
246
255
  },
247
256
  finalize: (ctx, exitCode, signal, spawnError) => ({
248
257
  state, exitCode, signal, stderr: ctx.stderr, primaryError, ...(spawnError ? { spawnError } : {}),
258
+ ...cliUnacceptedFacts({ state, primaryError, protocolError, unparsedStdout, signal, ...(spawnError ? { spawnError } : {}) }),
249
259
  }),
250
260
  });
251
261
  }
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { startStructuredCli } from "./structured-exec-pump.js";
4
4
  import { asRecord } from "./structured-content.js";
5
+ import { cliUnacceptedFacts } from "./structured-failure.js";
5
6
  import { thinkingEffortToOpenCodeVariant, promptWithSystemFallback } from "./structured-provider-common.js";
6
7
  function extractStructuredText(value) {
7
8
  if (typeof value === "string")
@@ -129,6 +130,8 @@ export class OpenCodeRunner {
129
130
  usage: undefined,
130
131
  };
131
132
  let primaryError = null;
133
+ let protocolError = false;
134
+ let unparsedStdout = false;
132
135
  return startStructuredCli({
133
136
  sessionId: context.session.id,
134
137
  file: "opencode",
@@ -151,12 +154,15 @@ export class OpenCodeRunner {
151
154
  event = JSON.parse(trimmed);
152
155
  }
153
156
  catch {
157
+ unparsedStdout = true;
154
158
  return;
155
159
  }
156
160
  observer.onEvent?.(event);
157
161
  const error = applyOpenCodeEvent(state, event);
158
- if (error)
162
+ if (error) {
159
163
  primaryError = error;
164
+ protocolError = true;
165
+ }
160
166
  observer.onUpdate(state);
161
167
  },
162
168
  finalize: (ctx, exitCode, signal, spawnError) => ({
@@ -166,6 +172,7 @@ export class OpenCodeRunner {
166
172
  stderr: ctx.stderr,
167
173
  primaryError,
168
174
  ...(spawnError ? { spawnError } : {}),
175
+ ...cliUnacceptedFacts({ state, primaryError, protocolError, unparsedStdout, signal, ...(spawnError ? { spawnError } : {}) }),
169
176
  }),
170
177
  });
171
178
  }
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
3
+ import { cliUnacceptedFacts } from "./structured-failure.js";
3
4
  import { startStructuredCli } from "./structured-exec-pump.js";
4
5
  import { systemPromptArgs, thinkingEffortToQoderEffort } from "./structured-provider-common.js";
5
6
  export function buildQoderArgs(session, prompt) {
@@ -31,6 +32,8 @@ export class QoderRunner {
31
32
  const reducer = new ClaudeCliProtocolReducer(context.session);
32
33
  let stdoutTail = "";
33
34
  let primaryError = null;
35
+ let protocolError = false;
36
+ let unparsedStdout = false;
34
37
  return startStructuredCli({
35
38
  sessionId: context.session.id,
36
39
  file: "qodercli",
@@ -52,6 +55,7 @@ export class QoderRunner {
52
55
  event = JSON.parse(trimmed);
53
56
  }
54
57
  catch {
58
+ unparsedStdout = true;
55
59
  return;
56
60
  }
57
61
  if (event && typeof event === "object" && !Array.isArray(event)) {
@@ -62,6 +66,7 @@ export class QoderRunner {
62
66
  ? record.errors.filter((item) => typeof item === "string")
63
67
  : [];
64
68
  primaryError = errors.join("\n") || "Qoder CLI execution failed";
69
+ protocolError = true;
65
70
  }
66
71
  }
67
72
  if (reducer.apply(event, context.session.mode === "managed"))
@@ -80,6 +85,15 @@ export class QoderRunner {
80
85
  stdoutTail,
81
86
  primaryError,
82
87
  ...(spawnError ? { spawnError } : {}),
88
+ ...cliUnacceptedFacts({
89
+ state: reducer.state,
90
+ primaryError,
91
+ protocolError: protocolError || reducer.errorResultObserved,
92
+ unparsedStdout,
93
+ signal,
94
+ progressed: reducer.assistantWorkObserved,
95
+ ...(spawnError ? { spawnError } : {}),
96
+ }),
83
97
  }),
84
98
  });
85
99
  }
@@ -70,6 +70,8 @@ interface CreateStructuredSessionOptions {
70
70
  * 按 automationId 前缀识别,重启后照样生效。
71
71
  */
72
72
  export type StructuredRelayHandler = (sessionId: string, text: string) => void | Promise<void>;
73
+ /** Quiet heartbeat is a status ping, not another copy of the transcript or uploaded avatar. */
74
+ export declare function structuredQuietStatusData(session: SessionSnapshot): Record<string, unknown>;
73
75
  /**
74
76
  * 返回最近一次真正提交给结构化会话的用户输入。
75
77
  *
@@ -113,7 +115,6 @@ export declare class StructuredSessionManager {
113
115
  private readonly seenIdempotencyKeys;
114
116
  private readonly relayHandlers;
115
117
  private emitEvent;
116
- private archiveTimer;
117
118
  /**
118
119
  * 静默期心跳:回合在飞且连续 TURN_QUIET_HEARTBEAT_MS 没有任何事件时,用现有的
119
120
  * status 事件重发一次权威快照(含最新 turn 锚点)。timer 只在 inFlight 期间存在。
@@ -122,6 +123,8 @@ export declare class StructuredSessionManager {
122
123
  private readonly topicCoordinator;
123
124
  private readonly nativeTitles;
124
125
  private readonly streamEmitTimers;
126
+ /** 员工候选链的「本轮还没开始执行」看门狗,按会话一个;首个协议事件或回合收敛即撤销。 */
127
+ private readonly candidateStartupWatchdogs;
125
128
  private readonly claudeCliRunner;
126
129
  private readonly codexRunner;
127
130
  private readonly openCodeRunner;
@@ -144,7 +147,6 @@ export declare class StructuredSessionManager {
144
147
  constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined, decisionRuntime?: () => DecisionRuntimeAccess | null, openRouter?: OpenRouterFreeModelsService | undefined,
145
148
  /** 「智能分配」专用的本地决策入口:只有真正能跑时才注入,没注入则用系统员工回退。 */
146
149
  autoAssignEvaluate?: () => DecisionRuntimeAccess["evaluate"]);
147
- private archiveExpiredSessions;
148
150
  setEventEmitter(emitEvent: (event: ProcessEvent) => void): void;
149
151
  getCoreTurnStatus(): CoreStatus;
150
152
  /** Keep current turns running; new Pi inputs stay in the durable input queue. */
@@ -172,6 +174,7 @@ export declare class StructuredSessionManager {
172
174
  /** Return lightweight snapshots for the session list (no output/messages). */
173
175
  listSlim(): SessionSnapshot[];
174
176
  get(id: string): SessionSnapshot | null;
177
+ getSlim(id: string): SessionSnapshot | null;
175
178
  setSessionWorkspace(id: string, membership: Pick<SessionSnapshot, "workspaceId" | "workspaceTaskId">): void;
176
179
  setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
177
180
  /** 归档只写标记,正在执行的回合和历史继续保留。 */
@@ -199,7 +202,12 @@ export declare class StructuredSessionManager {
199
202
  /** Consume only the exact failed request observed by dispatch, once, before a newer request can replace it. */
200
203
  consumeUnacceptedTeamFailure(id: string, requestId: string): StructuredFailure | null;
201
204
  private employeeCandidateModel;
202
- /** Only an unstarted employee conversation may replay its explicitly rejected first input. */
205
+ /**
206
+ * A turn the provider rejected before it accepted any input may replay on the next candidate.
207
+ * Same-tool switches carry the conversation as it stood before this turn — history plus the native
208
+ * session it already had — so they also cover an already-started chat. A cross-tool switch cannot
209
+ * hand that context over, so it stays limited to conversations that have not produced a turn yet.
210
+ */
203
211
  private retryEmployeeCandidate;
204
212
  private throwUnacceptedInput;
205
213
  /** 往转发会话里追加别的参与者的发言。 */
@@ -315,21 +323,8 @@ export declare class StructuredSessionManager {
315
323
  private flushNextQueuedMessage;
316
324
  private emit;
317
325
  private incrementApprovalStats;
318
- private runCodexStreaming;
319
- private runGrokStreaming;
320
- private runOpenCodeStreaming;
321
- /**
322
- * Spawn `claude -p --output-format stream-json` and parse NDJSON lines as
323
- * they arrive, emitting incremental WebSocket events so the UI can render
324
- * text / thinking / tool_use blocks in real-time.
325
- *
326
- * Permission handling:
327
- * - Non-root + full-access/managed: --permission-mode bypassPermissions
328
- * - Non-root + auto-edit: --permission-mode acceptEdits
329
- * - Root: --permission-mode acceptEdits + --allowedTools (extends approval
330
- * outside CWD). stdin is always "ignore" — no ACP bidirectional control.
331
- */
332
- private runClaudeStreaming;
326
+ /** One turn lifecycle for every structured adapter; provider protocol reducers stay separate. */
327
+ private runStructuredStreaming;
333
328
  private compactContentBlocks;
334
329
  private buildCompletedAssistantMessages;
335
330
  /**