comfyui-mcp 0.50.0 → 0.50.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 (73) hide show
  1. package/dist/comfyui/fetch.js +61 -8
  2. package/dist/comfyui/fetch.js.map +1 -1
  3. package/dist/orchestrator/agent-backend.js.map +1 -1
  4. package/dist/orchestrator/call-tool-admission.js +18 -3
  5. package/dist/orchestrator/call-tool-admission.js.map +1 -1
  6. package/dist/orchestrator/claude-backend.js +262 -14
  7. package/dist/orchestrator/claude-backend.js.map +1 -1
  8. package/dist/orchestrator/index.js +84 -26
  9. package/dist/orchestrator/index.js.map +1 -1
  10. package/dist/orchestrator/panel-agent.js +8 -1
  11. package/dist/orchestrator/panel-agent.js.map +1 -1
  12. package/dist/orchestrator/panel-tools.js +215 -11
  13. package/dist/orchestrator/panel-tools.js.map +1 -1
  14. package/dist/orchestrator/run-completion-journal.js +129 -34
  15. package/dist/orchestrator/run-completion-journal.js.map +1 -1
  16. package/dist/orchestrator/turn-registry.js +227 -0
  17. package/dist/orchestrator/turn-registry.js.map +1 -0
  18. package/dist/services/download-cache.js +6 -20
  19. package/dist/services/download-cache.js.map +1 -1
  20. package/dist/services/image-management.js +31 -10
  21. package/dist/services/image-management.js.map +1 -1
  22. package/dist/services/instance-witness.js +99 -0
  23. package/dist/services/instance-witness.js.map +1 -0
  24. package/dist/services/live-widget-overlay.js +139 -0
  25. package/dist/services/live-widget-overlay.js.map +1 -0
  26. package/dist/services/lmstudio-lifecycle.js +2 -1
  27. package/dist/services/lmstudio-lifecycle.js.map +1 -1
  28. package/dist/services/local-vram.js +44 -0
  29. package/dist/services/local-vram.js.map +1 -0
  30. package/dist/services/model-resolver.js +63 -7
  31. package/dist/services/model-resolver.js.map +1 -1
  32. package/dist/services/node-management.js +79 -5
  33. package/dist/services/node-management.js.map +1 -1
  34. package/dist/services/panel-installer.js +36 -9
  35. package/dist/services/panel-installer.js.map +1 -1
  36. package/dist/services/panel-recovery.js +32 -3
  37. package/dist/services/panel-recovery.js.map +1 -1
  38. package/dist/services/panel-sync.js +50 -0
  39. package/dist/services/panel-sync.js.map +1 -1
  40. package/dist/services/panel-workspace.js +7 -3
  41. package/dist/services/panel-workspace.js.map +1 -1
  42. package/dist/services/process-control.js +165 -51
  43. package/dist/services/process-control.js.map +1 -1
  44. package/dist/services/queue-monitor.js +26 -0
  45. package/dist/services/queue-monitor.js.map +1 -1
  46. package/dist/services/queue-status-broadcast.test.js +2 -0
  47. package/dist/services/queue-status-broadcast.test.js.map +1 -1
  48. package/dist/services/self-update.js +290 -38
  49. package/dist/services/self-update.js.map +1 -1
  50. package/dist/services/session-scope.js +17 -0
  51. package/dist/services/session-scope.js.map +1 -1
  52. package/dist/services/ui-bridge.js +16 -2
  53. package/dist/services/ui-bridge.js.map +1 -1
  54. package/dist/services/update-comfyui.js +160 -2
  55. package/dist/services/update-comfyui.js.map +1 -1
  56. package/dist/services/workflow-converter.js +10 -1
  57. package/dist/services/workflow-converter.js.map +1 -1
  58. package/dist/tools/image-management.js +41 -6
  59. package/dist/tools/image-management.js.map +1 -1
  60. package/dist/tools/install-comfyui.js +1 -1
  61. package/dist/tools/install-comfyui.js.map +1 -1
  62. package/dist/tools/model-management.js +58 -7
  63. package/dist/tools/model-management.js.map +1 -1
  64. package/dist/tools/skills-access.js +8 -3
  65. package/dist/tools/skills-access.js.map +1 -1
  66. package/dist/tools/vocabulary.js +88 -0
  67. package/dist/tools/vocabulary.js.map +1 -1
  68. package/dist/utils/errors.js +45 -1
  69. package/dist/utils/errors.js.map +1 -1
  70. package/package.json +1 -1
  71. package/scripts/check-tool-vocabulary.mts +14 -1
  72. package/scripts/export-vocabulary.mts +33 -5
  73. package/scripts/gen-tool-docs.ts +10 -1
@@ -1,4 +1,42 @@
1
1
  import { getComfyUIAuthHeaders } from "../config.js";
2
+ import { describeFetchFailure, isBareFetchFailure } from "../utils/errors.js";
3
+ /** The request target, for the diagnostic. Never throws on an odd input. */
4
+ function targetOf(input) {
5
+ try {
6
+ if (typeof input === "string")
7
+ return input;
8
+ if (input instanceof URL)
9
+ return input.href;
10
+ return input.url ?? String(input);
11
+ }
12
+ catch {
13
+ return String(input);
14
+ }
15
+ }
16
+ /**
17
+ * Turn a network-layer throw into a diagnostic that says WHAT was attempted.
18
+ *
19
+ * `TypeError: fetch failed` was reaching tool results verbatim: #954 saw it from
20
+ * the workflow-templates listing (now `list_packs` action:"list_templates") and
21
+ * could not tell which host was tried, and #952 saw it from the readonly tools
22
+ * while the panel bridge was working fine — reading it, reasonably, as "the tool
23
+ * is broken" when the headless target was simply a different (unreachable)
24
+ * address than the one the panel is bound to.
25
+ *
26
+ * The two ARE separate targets by design: the panel talks to whichever ComfyUI
27
+ * the browser is on, while these calls go to the configured COMFYUI_URL. That is
28
+ * not a bug, but it is invisible unless the failure names the address.
29
+ */
30
+ function describeComfyFetchFailure(err, target) {
31
+ const { message, code } = describeFetchFailure(err);
32
+ const wrapped = new Error(`${message} — while requesting ${target}. ` +
33
+ `That is the headless ComfyUI target (COMFYUI_URL); a CONNECTED sidebar panel does not imply this address is reachable, ` +
34
+ `because the panel talks to whichever ComfyUI its browser tab is on. ` +
35
+ `Check the target with install_comfyui (action:"environment"), and confirm the server is up with get_system_stats (action:"health").`, { cause: err });
36
+ if (code)
37
+ wrapped.code = code;
38
+ return wrapped;
39
+ }
2
40
  /**
3
41
  * `fetch` wrapper for ComfyUI HTTP requests that injects the configured generic
4
42
  * auth header(s) (COMFYUI_AUTH_* — for self-hosted ComfyUI behind a reverse
@@ -9,15 +47,30 @@ import { getComfyUIAuthHeaders } from "../config.js";
9
47
  * server. Non-ComfyUI requests (HuggingFace, Civitai, Comfy Cloud) keep using
10
48
  * plain `fetch` — Comfy Cloud has its own X-API-Key path in cloud-client.ts.
11
49
  */
12
- export function comfyuiFetch(input, init = {}) {
50
+ export async function comfyuiFetch(input, init = {}) {
13
51
  const auth = getComfyUIAuthHeaders();
14
- if (Object.keys(auth).length === 0)
15
- return fetch(input, init);
16
- const headers = new Headers(init.headers);
17
- for (const [name, value] of Object.entries(auth)) {
18
- if (!headers.has(name))
19
- headers.set(name, value);
52
+ let request;
53
+ if (Object.keys(auth).length === 0) {
54
+ request = fetch(input, init);
55
+ }
56
+ else {
57
+ const headers = new Headers(init.headers);
58
+ for (const [name, value] of Object.entries(auth)) {
59
+ if (!headers.has(name))
60
+ headers.set(name, value);
61
+ }
62
+ request = fetch(input, { ...init, headers });
63
+ }
64
+ try {
65
+ return await request;
66
+ }
67
+ catch (err) {
68
+ // ONLY the opaque undici failure is rewritten. An AbortError, a timeout with
69
+ // its own message, or anything else already says what happened, and
70
+ // replacing that text would be a downgrade.
71
+ if (!isBareFetchFailure(err))
72
+ throw err;
73
+ throw describeComfyFetchFailure(err, targetOf(input));
20
74
  }
21
- return fetch(input, { ...init, headers });
22
75
  }
23
76
  //# sourceMappingURL=fetch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/comfyui/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC1B,KAA6B,EAC7B,OAAoB,EAAE;IAEtB,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/comfyui/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,4EAA4E;AAC5E,SAAS,QAAQ,CAAC,KAA6B;IAC7C,IAAI,CAAC;QACH,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,IAAI,KAAK,YAAY,GAAG;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC;QAC5C,OAAQ,KAAiB,CAAC,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,yBAAyB,CAAC,GAAY,EAAE,MAAc;IAC7D,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,IAAI,KAAK,CACvB,GAAG,OAAO,uBAAuB,MAAM,IAAI;QACzC,yHAAyH;QACzH,sEAAsE;QACtE,qIAAqI,EACvI,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;IACF,IAAI,IAAI;QAAG,OAA6B,CAAC,IAAI,GAAG,IAAI,CAAC;IACrD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAA6B,EAC7B,OAAoB,EAAE;IAEtB,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,IAAI,OAA0B,CAAC;IAC/B,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,GAAG,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6EAA6E;QAC7E,oEAAoE;QACpE,4CAA4C;QAC5C,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC;YAAE,MAAM,GAAG,CAAC;QACxC,MAAM,yBAAyB,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-backend.js","sourceRoot":"","sources":["../../src/orchestrator/agent-backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-backend.js","sourceRoot":"","sources":["../../src/orchestrator/agent-backend.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  * decision is a pure, unit-testable function — the dispatcher is a one-line
5
5
  * call site.
6
6
  */
7
- import { retiredToolMessage } from "../tools/vocabulary.js";
7
+ import { retiredToolMessage, TOOL_NAMES } from "../tools/vocabulary.js";
8
8
  /** The direct tool channel: a mobile client can invoke these READ/DOWNLOAD backend
9
9
  * tools without an agent turn (structured nav data + rig downloads). The
10
10
  * bridge is already token-gated; this whitelist keeps call_tool to non-destructive
@@ -434,8 +434,23 @@ export function callToolAdmission(tool, args) {
434
434
  const retired = retiredToolMessage(tool);
435
435
  if (retired !== undefined)
436
436
  return retired;
437
- if (!CALL_TOOL_WHITELIST.has(tool))
438
- return `tool "${tool}" is not permitted`;
437
+ if (!CALL_TOOL_WHITELIST.has(tool)) {
438
+ // Distinguish "does not exist" from "exists, but not on THIS channel" (#908).
439
+ // The bare "is not permitted" was a dead end: the panel's RunPod Deploy/Start
440
+ // buttons returned it verbatim for ~2 weeks after #278 correctly removed those
441
+ // two tools from this whitelist (both start BILLING, and a confirmation-less
442
+ // mirrored tab must not spend money). The exclusion was right; the message
443
+ // told nobody what had happened or what to do instead, so it read as "the
444
+ // panel is broken" rather than "this channel deliberately does not carry it".
445
+ const exists = TOOL_NAMES.includes(tool);
446
+ return exists
447
+ ? `tool "${tool}" exists but is not available on the direct call_tool channel ` +
448
+ `(the canvas-less path used by mobile, mirrored tabs and panel buttons). This is a ` +
449
+ `deliberate exclusion, not a missing tool — some tools are withheld here because they ` +
450
+ `spend money or mutate state that this channel cannot confirm with the user. Ask the ` +
451
+ `agent to run it instead: the agent session carries the full tool surface and can confirm.`
452
+ : `tool "${tool}" is not permitted`;
453
+ }
439
454
  const actions = CALL_TOOL_ACTION_WHITELIST.get(tool);
440
455
  if (actions !== undefined) {
441
456
  const action = typeof args.action === "string" ? args.action : undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"call-tool-admission.js","sourceRoot":"","sources":["../../src/orchestrator/call-tool-admission.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D;;;;;;;;;;oCAUoC;AACpC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS;IACjD,0EAA0E;IAC1E,6EAA6E;IAC7E,yEAAyE;IACzE,2EAA2E;IAC3E,uEAAuE;IACvE,wEAAwE;IACxE,iFAAiF;IACjF,cAAc;IACd,4EAA4E;IAC5E,wEAAwE;IACxE,0EAA0E;IAC1E,2EAA2E;IAC3E,8EAA8E;IAC9E,gEAAgE;IAChE,EAAE;IACF,+EAA+E;IAC/E,8EAA8E;IAC9E,wEAAwE;IACxE,qEAAqE;IACrE,8EAA8E;IAC9E,gDAAgD;IAChD,WAAW;IACX,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,mBAAmB;IACnB,wEAAwE;IACxE,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,wEAAwE;IACxE,8BAA8B;IAC9B,gBAAgB;IAChB,8EAA8E;IAC9E,2EAA2E;IAC3E,2EAA2E;IAC3E,wBAAwB;IACxB,4EAA4E;IAC5E,+EAA+E;IAC/E,8EAA8E;IAC9E,+EAA+E;IAC/E,yEAAyE;IACzE,kDAAkD;IAClD,8BAA8B;IAC9B,kBAAkB;IAClB,0EAA0E;IAC1E,2EAA2E;IAC3E,6EAA6E;IAC7E,mEAAmE;IACnE,EAAE;IACF,uEAAuE;IACvE,oEAAoE;IACpE,mDAAmD;IACnD,eAAe;IACf,wEAAwE;IACxE,qEAAqE;IACrE,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,uEAAuE;IACvE,6EAA6E;IAC7E,oEAAoE;IACpE,8DAA8D;IAC9D,OAAO;IACP,iFAAiF;IACjF,2EAA2E;IAC3E,8EAA8E;IAC9E,aAAa;IACb,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,sEAAsE;IACtE,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,6BAA6B;IAC7B,aAAa;IACb,qEAAqE;IACrE,uEAAuE;IACvE,8EAA8E;IAC9E,sEAAsE;IACtE,8EAA8E;IAC9E,gBAAgB;IAChB,EAAE;IACF,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,uEAAuE;IACvE,uBAAuB;IACvB,aAAa;IACb,cAAc;IACd,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,8EAA8E;IAC9E,+EAA+E;IAC/E,qDAAqD;IACrD,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,4BAA4B;IAC5B,EAAE;IACF,+EAA+E;IAC/E,yEAAyE;IACzE,8EAA8E;IAC9E,wEAAwE;IACxE,8BAA8B;IAC9B,QAAQ;IACR,wEAAwE;IACxE,yEAAyE;IACzE,8EAA8E;IAC9E,0EAA0E;IAC1E,kEAAkE;IAClE,cAAc;IACd,4EAA4E;IAC5E,wEAAwE;IACxE,+EAA+E;IAC/E,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,wEAAwE;IACxE,6EAA6E;IAC7E,6BAA6B;IAC7B,MAAM;IACN,8EAA8E;IAC9E,+EAA+E;IAC/E,8EAA8E;IAC9E,mFAAmF;IACnF,+DAA+D;IAC/D,4BAA4B;IAC5B,2EAA2E;IAC3E,2EAA2E;IAC3E,6EAA6E;IAC7E,+CAA+C;IAC/C,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,yEAAyE;IACzE,YAAY;IACZ,EAAE;IACF,+DAA+D;IAC/D,0EAA0E;IAC1E,yEAAyE;IACzE,2EAA2E;IAC3E,qEAAqE;IACrE,8EAA8E;IAC9E,6DAA6D;IAC7D,qBAAqB;IACrB,6EAA6E;IAC7E,EAAE;IACF,4EAA4E;IAC5E,sEAAsE;IACtE,2EAA2E;IAC3E,8EAA8E;IAC9E,qEAAqE;IACrE,yEAAyE;IACzE,sDAAsD;IACtD,EAAE;IACF,4EAA4E;IAC5E,yEAAyE;IACzE,8EAA8E;IAC9E,yDAAyD;CAC1D,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAA6C,IAAI,GAAG,CAAC;IAC1F,uEAAuE;IACvE,wEAAwE;IACxE,6EAA6E;IAC7E,mEAAmE;IACnE,uEAAuE;IACvE,2DAA2D;IAC3D,yCAAyC;IACzC,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9B,8EAA8E;IAC9E,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,sEAAsE;IACtE,EAAE;IACF,4EAA4E;IAC5E,4CAA4C;IAC5C,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;IACtF,2EAA2E;IAC3E,uEAAuE;IACvE,2EAA2E;IAC3E,6EAA6E;IAC7E,uEAAuE;IACvE,0EAA0E;IAC1E,0EAA0E;IAC1E,mEAAmE;IACnE,yEAAyE;IACzE,+DAA+D;IAC/D,sEAAsE;IACtE,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,iEAAiE;IACjE,+EAA+E;IAC/E,2EAA2E;IAC3E,+EAA+E;IAC/E,iCAAiC;IACjC,CAAC,YAAY,EAAE,IAAI,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;IACzC,wEAAwE;IACxE,+EAA+E;IAC/E,0EAA0E;IAC1E,+EAA+E;IAC/E,wEAAwE;IACxE,uFAAuF;IACvF,8EAA8E;IAC9E,2EAA2E;IAC3E,0EAA0E;IAC1E,qBAAqB;IACrB,CAAC,cAAc,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrC,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,0EAA0E;IAC1E,4EAA4E;IAC5E,8EAA8E;IAC9E,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,uCAAuC;IACvC,CAAC,mBAAmB,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,qEAAqE;IACrE,wEAAwE;IACxE,6EAA6E;IAC7E,2EAA2E;IAC3E,yCAAyC;IACzC;QACE,gBAAgB;QAChB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAC;KAClG;IACD,0EAA0E;IAC1E,yEAAyE;IACzE,8EAA8E;IAC9E,8BAA8B;IAC9B,EAAE;IACF,+EAA+E;IAC/E,gFAAgF;IAChF,iFAAiF;IACjF,4EAA4E;IAC5E,sDAAsD;IACtD,6EAA6E;IAC7E,mFAAmF;IACnF,iFAAiF;IACjF,gFAAgF;IAChF,iFAAiF;IACjF,EAAE;IACF,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,iDAAiD;IACjD,CAAC,cAAc,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAC5E,8EAA8E;IAC9E,4EAA4E;IAC5E,qBAAqB;IACrB,EAAE;IACF,+EAA+E;IAC/E,8EAA8E;IAC9E,gFAAgF;IAChF,oEAAoE;IACpE,4EAA4E;IAC5E,8EAA8E;IAC9E,mDAAmD;IACnD,EAAE;IACF,6EAA6E;IAC7E,CAAC,eAAe,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACpC,uEAAuE;IACvE,gFAAgF;IAChF,gFAAgF;IAChF,6EAA6E;IAC7E,EAAE;IACF,gFAAgF;IAChF,+EAA+E;IAC/E,4EAA4E;IAC5E,wEAAwE;IACxE,4EAA4E;IAC5E,sBAAsB;IACtB,EAAE;IACF,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,mEAAmE;IACnE,EAAE;IACF,+EAA+E;IAC/E,yEAAyE;IACzE,iDAAiD;IACjD,CAAC,qBAAqB,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,4EAA4E;IAC5E,wEAAwE;IACxE,WAAW;IACX,EAAE;IACF,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,sCAAsC;IACtC,yEAAyE;IACzE,sEAAsE;IACtE,gFAAgF;IAChF,iFAAiF;IACjF,iFAAiF;IACjF,kEAAkE;IAClE,2DAA2D;IAC3D,EAAE;IACF,uEAAuE;IACvE,8EAA8E;IAC9E,gFAAgF;IAChF,gFAAgF;IAChF,mEAAmE;IACnE,gFAAgF;IAChF,gBAAgB;IAChB,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;IAC/C,wEAAwE;IACxE,0EAA0E;IAC1E,8EAA8E;IAC9E,kCAAkC;IAClC,EAAE;IACF,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,mEAAmE;IACnE,4EAA4E;IAC5E,oDAAoD;IACpD,EAAE;IACF,8EAA8E;IAC9E,wEAAwE;IACxE,CAAC,kBAAkB,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1C,4EAA4E;IAC5E,+EAA+E;IAC/E,gFAAgF;IAChF,8EAA8E;IAC9E,8EAA8E;IAC9E,CAAC,aAAa,EAAE,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,IAA6B;IAE7B,gFAAgF;IAChF,gEAAgE;IAChE,EAAE;IACF,0EAA0E;IAC1E,6EAA6E;IAC7E,+EAA+E;IAC/E,+EAA+E;IAC/E,4EAA4E;IAC5E,+EAA+E;IAC/E,8EAA8E;IAC9E,8EAA8E;IAC9E,8EAA8E;IAC9E,eAAe;IACf,EAAE;IACF,+EAA+E;IAC/E,+EAA+E;IAC/E,iFAAiF;IACjF,+EAA+E;IAC/E,8EAA8E;IAC9E,gFAAgF;IAChF,0EAA0E;IAC1E,sDAAsD;IACtD,EAAE;IACF,iFAAiF;IACjF,kFAAkF;IAClF,kFAAkF;IAClF,6EAA6E;IAC7E,4EAA4E;IAC5E,kFAAkF;IAClF,iFAAiF;IACjF,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IAE1C,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,IAAI,oBAAoB,CAAC;IAC7E,MAAM,OAAO,GAAG,0BAA0B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACjD,OAAO,SAAS,IAAI,kCAAkC,MAAM,IAAI,WAAW,GAAG,CAAC;QACjF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"call-tool-admission.js","sourceRoot":"","sources":["../../src/orchestrator/call-tool-admission.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,8 @@
12
12
  import { logger } from "../utils/logger.js";
13
13
  import { errorText } from "./error-text.js";
14
14
  import { buildAgentSpawnEnv } from "../services/panel-secrets.js";
15
+ import { loadTurnRegistry, saveTurnRegistry, tombstoneTurn } from "./turn-registry.js";
16
+ import { randomUUID } from "node:crypto";
15
17
  import { CLAUDE_CAPABILITIES, } from "./agent-backend.js";
16
18
  // ---- reasoning effort mapping ----
17
19
  // Effort is now a provider-neutral union (it must survive a provider switch — see
@@ -204,6 +206,11 @@ export async function fetchSupportedCommands(model) {
204
206
  * silently realigning it: the pop that crosses the evicted gap (and any
205
207
  * traceless result) fails closed as unverifiable, never ok:true (#745 r3). */
206
208
  const MAX_UNRESOLVED_TURNS = 16;
209
+ /** Bound on REMEMBERED dead-session turn ids (#886). The set exists to
210
+ * recognize a late result from a restarted session — it is not a
211
+ * classification input, so a cap here only bounds how far back recognition
212
+ * reaches; beyond it a stray falls back to the plain traceless report. */
213
+ const MAX_REMEMBERED_DEAD_TURNS = 64;
207
214
  /**
208
215
  * The Claude Agent SDK adapter. One instance per PanelAgent; it holds the live
209
216
  * `Query` for the current session and re-creates it on each `run()`.
@@ -260,6 +267,51 @@ export class ClaudeBackend {
260
267
  * unconsumed candidate it fails closed as traceless (#728 r6). */
261
268
  consumedInterrupted = new Set();
262
269
  turns = [];
270
+ // #886 — "submitted" must survive the session: when a run() restart drops the
271
+ // live traces above, their ids move here (and a RESUMED session rehydrates
272
+ // them from the durable registry written by the previous process). A
273
+ // traceless result while this set is non-empty is then RECOGNIZED for what
274
+ // it most likely is — a turn that was in flight when its session died,
275
+ // completing late — and reported as "completed but unverified": never a
276
+ // fabricated success (fail-closed #745 stands), but also no longer the false
277
+ // "could not be matched to any submitted turn", which claimed the turn never
278
+ // existed when only its record was lost. Insertion-ordered; recognition
279
+ // consumes the oldest entry (results land in submission order).
280
+ //
281
+ // Entries are owner-TAGGED (`<instanceId>:<turnId>`): turnSeq restarts per
282
+ // process, so two instances can each leave their own turn "1" outstanding —
283
+ // untagged ids would collapse those into one record and the second late
284
+ // result would be falsely reported as matching no submission (codex gate).
285
+ instanceId = randomUUID();
286
+ rememberedDeadTurns = new Set();
287
+ /** The remembered set's bound dropped a record at some point — persisted
288
+ * into the registry file (`incomplete`) so a RESPAWN also knows the record
289
+ * is not provably complete, not just this process (codex gate r2). */
290
+ rememberedIncomplete = false;
291
+ /** Session id the durable registry file belongs to (learned at init). Null
292
+ * until then — a submission pulled before init is processed can't be filed
293
+ * under a session nobody can resume yet. */
294
+ registrySessionId = null;
295
+ /** A registry read/write failed — the record of outstanding turns is not
296
+ * provably complete, so log it and never lean on the registry's ABSENCE as
297
+ * evidence in that state. */
298
+ registryDegraded = false;
299
+ // #885 — a stall is not a user rejection. The Agent SDK has no mid-turn steer
300
+ // (Codex's turn/steer equivalent): a user message pushed into the stream is
301
+ // queued by the CLI until the current turn ENDS — exactly what a frozen turn
302
+ // cannot do — so the notice cannot ride the stalled turn itself. What must
303
+ // not happen is the transcript keeping ONLY the interrupt's generic
304
+ // user-rejection payload: on the next turn the model reads it and tells the
305
+ // user "you cancelled" — a lie about the user's own actions. So the notice
306
+ // is held here and prepended (clearly delimited as harness text, not the
307
+ // user's words) to the NEXT user turn, where it lands in the transcript
308
+ // right beside that payload. recoverStalledTurn therefore returns false:
309
+ // PanelAgent keeps its proven bounded interrupt/restart recovery for the
310
+ // frozen turn, and the correction rides the turn after.
311
+ pendingStallNotice = null;
312
+ /** turnSeq when the notice was stored — a genuine `success` landing for that
313
+ * turn means the "stall" recovered by itself and the notice is stale. */
314
+ pendingStallNoticeTurn = 0;
263
315
  constructor(deps) {
264
316
  this.deps = deps;
265
317
  }
@@ -300,7 +352,20 @@ export class ClaudeBackend {
300
352
  * image refs to inline base64 blocks so the agent SEES them in this turn (no
301
353
  * get_image view/fetch round-trip). */
302
354
  async shapeTurn(turn) {
303
- const text = turn.text;
355
+ let text = turn.text;
356
+ // #885 — deliver the held stall correction AHEAD of the user's words, so
357
+ // the model reads the true origin of the interrupt right next to the
358
+ // generic rejection payload it would otherwise take at face value. The
359
+ // notice names the PREVIOUS turn explicitly: on this new turn, a bare
360
+ // "this turn stalled" would mispoint.
361
+ if (this.pendingStallNotice) {
362
+ text =
363
+ `${this.pendingStallNotice}\n\n` +
364
+ `[harness note: the notice above is about the PREVIOUS turn — the one the harness cleared. ` +
365
+ `It is not part of the user's message and the user did not write it. The user's message follows.]\n\n` +
366
+ text;
367
+ this.pendingStallNotice = null;
368
+ }
304
369
  const images = turn.images ?? [];
305
370
  let content = text;
306
371
  if (images.length) {
@@ -382,23 +447,51 @@ export class ClaudeBackend {
382
447
  const query = await loadQuery();
383
448
  const self = this;
384
449
  // A (re)started session can never produce results for turns submitted to a
385
- // PREVIOUS session drop any dead traces and advance the result sequence
386
- // past EVERY turn stamped so far (turnSeq), whether or not traces remain:
387
- // in a FULLY DRAINED (e.g. poisoned) session the FIFO is already empty, but
388
- // the sequence must still move past the old ids or the new session's first
389
- // result re-crosses the old gap and falsely re-poisons (#745 r5). Stray
390
- // late results from the dead session then arrive TRACELESS and fail closed
391
- // instead. This genuine restart boundary is also the ONLY reset of the
450
+ // PREVIOUS session through the live FIFO clear it and advance the result
451
+ // sequence past EVERY turn stamped so far (turnSeq), whether or not traces
452
+ // remain: in a FULLY DRAINED (e.g. poisoned) session the FIFO is already
453
+ // empty, but the sequence must still move past the old ids or the new
454
+ // session's first result re-crosses the old gap and falsely re-poisons
455
+ // (#745 r5). This genuine restart boundary is also the ONLY reset of the
392
456
  // sticky classification poison (#745 r4). Done here, at run() entry (BEFORE
393
457
  // the new query exists), rather than on system/init: the SDK's prompt drain
394
458
  // legitimately pulls the first batch before init is processed, and an
395
459
  // init-time clear would unmark that genuinely in-flight turn (#745).
460
+ //
461
+ // But "the trace is gone" must not become "no turn was ever submitted"
462
+ // (#886): the dead session's unresolved ids are REMEMBERED (and, when this
463
+ // run RESUMES a session, rehydrated from the durable registry a previous
464
+ // process wrote), so a stray late result is recognized as a submitted
465
+ // turn's late landing and reported "completed but unverified" instead of
466
+ // "matched to no submitted turn". Recognition never upgrades a result to
467
+ // ok:true — fail-closed stands.
468
+ for (const dead of this.turns)
469
+ this.rememberDeadTurn(`${this.instanceId}:${dead.id}`);
396
470
  this.turns.length = 0;
397
471
  this.lastResultTurnId = this.turnSeq;
398
472
  this.classificationPoisoned = false;
399
473
  this.parkedInterrupted.clear(); // dead session's parks die with it
400
474
  this.consumedInterrupted.clear(); // …and its landing history
401
475
  this.markerBase = this.lastResultTurnId; // turn markers are run-relative (#728)
476
+ const resumeTarget = opts.resume ?? opts.sessionId ?? null;
477
+ // Registry writes before init (the prompt drain can submit ahead of it)
478
+ // file under the resume target — the session they will actually run in.
479
+ // A fresh start files nothing until init names the new session.
480
+ this.registrySessionId = resumeTarget;
481
+ if (resumeTarget) {
482
+ const loaded = loadTurnRegistry(resumeTarget);
483
+ // Hydrate whatever the session's files prove is outstanding — including
484
+ // this instance's OWN file (tags dedupe against the in-memory carry, and
485
+ // tombstones keep already-reported strays from resurrecting). A degraded
486
+ // or self-declared-incomplete record must not later ground the confident
487
+ // "matched to no submitted turn".
488
+ for (const tag of loaded.tags)
489
+ this.rememberDeadTurn(tag);
490
+ if (loaded.kind === "degraded" || loaded.incomplete) {
491
+ this.registryDegraded = true;
492
+ logger.warn(`[claude-backend] turn registry for the resumed session is ${loaded.kind === "degraded" ? `unreadable (${loaded.reason})` : "incomplete"} — outstanding-turn recognition is degraded (#886)`);
493
+ }
494
+ }
402
495
  // Wrap the neutral channel: shape each turn into the SDK's native form right
403
496
  // before it's read, so PanelAgent never deals in SDKUserMessage.
404
497
  async function* prompt() {
@@ -416,6 +509,7 @@ export class ClaudeBackend {
416
509
  const evicted = self.turns.shift();
417
510
  logger.warn(`[claude-backend] over ${MAX_UNRESOLVED_TURNS} unresolved turns — evicted turn ${evicted?.id}'s trace; its result will fail closed as unverifiable (#740)`);
418
511
  }
512
+ self.saveRegistry(); // the submission record must survive a restart (#886)
419
513
  yield msg;
420
514
  }
421
515
  }
@@ -470,6 +564,22 @@ export class ClaudeBackend {
470
564
  active.interrupted = true;
471
565
  await this.q?.interrupt();
472
566
  }
567
+ /**
568
+ * #885 — record the harness-stall notice so the NEXT user turn carries it
569
+ * into the transcript beside the interrupt's generic user-rejection payload
570
+ * (see the pendingStallNotice field comment for why the notice cannot ride
571
+ * the stalled turn itself on this provider). Returns false: no mid-turn
572
+ * steer exists here, so PanelAgent keeps its bounded interrupt/restart
573
+ * recovery for the frozen turn — the notice's job is only to stop the agent
574
+ * from telling the user THEY cancelled, and to tell it a retry is safe.
575
+ */
576
+ async recoverStalledTurn(notice) {
577
+ if (notice.trim()) {
578
+ this.pendingStallNotice = notice;
579
+ this.pendingStallNoticeTurn = this.turnSeq;
580
+ }
581
+ return false;
582
+ }
473
583
  /** Permanently dispose of the live SDK query. The Agent SDK has no explicit
474
584
  * "dispose" beyond interrupt(), which both stops the in-flight turn and lets
475
585
  * the underlying transport wind down once the prompt generator is no longer
@@ -492,6 +602,71 @@ export class ClaudeBackend {
492
602
  async listModels() {
493
603
  return [];
494
604
  }
605
+ /** Remember a submitted turn whose result can no longer arrive through the
606
+ * live FIFO (its session restarted). Recognition-only (#886): membership
607
+ * changes what an unmatched result can honestly be CALLED, never how it is
608
+ * classified. Bounded — oldest drops first, but a drop means the record is
609
+ * no longer provably complete, so it degrades recognition (the hedged
610
+ * wording) rather than silently reverting to the confident negative. */
611
+ rememberDeadTurn(tag) {
612
+ if (this.rememberedDeadTurns.has(tag))
613
+ return;
614
+ this.rememberedDeadTurns.add(tag);
615
+ if (this.rememberedDeadTurns.size > MAX_REMEMBERED_DEAD_TURNS) {
616
+ const oldest = this.rememberedDeadTurns.keys().next().value;
617
+ if (oldest !== undefined)
618
+ this.rememberedDeadTurns.delete(oldest);
619
+ this.registryDegraded = true;
620
+ this.rememberedIncomplete = true; // persisted — a respawn must hedge too
621
+ logger.warn(`[claude-backend] over ${MAX_REMEMBERED_DEAD_TURNS} remembered dead turns — dropped the oldest record; outstanding-turn recognition is degraded (#886)`);
622
+ }
623
+ }
624
+ /** Consume the oldest remembered dead-turn tag, if any — a recognized late
625
+ * landing spends the record (in memory, in this instance's file, and in the
626
+ * session's tombstones) so a second stray can never re-claim it. */
627
+ takeOldestRememberedDeadTurn() {
628
+ const oldest = this.rememberedDeadTurns.keys().next().value;
629
+ if (oldest === undefined)
630
+ return null;
631
+ this.rememberedDeadTurns.delete(oldest);
632
+ const sid = this.registrySessionId;
633
+ if (sid) {
634
+ try {
635
+ tombstoneTurn(sid, oldest);
636
+ }
637
+ catch (err) {
638
+ // A lost tombstone can only REVIVE an already-reported record (a
639
+ // repeated hedged disclosure) — never a fabricated success. Log, move on.
640
+ logger.warn(`[claude-backend] turn tombstone write failed: ${msgOf(err)} (#886)`);
641
+ }
642
+ }
643
+ this.saveRegistry();
644
+ return oldest;
645
+ }
646
+ /** Persist which submitted turns are still outstanding (#886). A failure
647
+ * degrades recognition (flagged + logged) but must never break the turn
648
+ * itself — the registry is a recognition aid, not load-bearing for the
649
+ * turn in flight.
650
+ *
651
+ * Ordering: saves happen AFTER the trace push/pop they record. A crash in
652
+ * the pop→save window leaves a STALE record (a turn whose result was seen
653
+ * still listed outstanding) — over-recognition, which produces the hedged
654
+ * "completed but unverified" disclosure. The reverse order could LOSE a
655
+ * record, and a lost record produces the confident "matched to no
656
+ * submitted turn" — a false assertion — so over-recognition is the
657
+ * deliberate failure side. */
658
+ saveRegistry() {
659
+ const sid = this.registrySessionId;
660
+ if (!sid)
661
+ return;
662
+ try {
663
+ saveTurnRegistry(sid, this.instanceId, this.turns.map((t) => t.id), [...this.rememberedDeadTurns], this.rememberedIncomplete);
664
+ }
665
+ catch (err) {
666
+ this.registryDegraded = true;
667
+ logger.warn(`[claude-backend] turn registry write failed: ${msgOf(err)} — outstanding-turn recognition is degraded (#886)`);
668
+ }
669
+ }
495
670
  /** Normalize one SDK message into zero or more canonical AgentEvents. */
496
671
  *route(message) {
497
672
  switch (message.type) {
@@ -499,9 +674,15 @@ export class ClaudeBackend {
499
674
  if (message.subtype === "init") {
500
675
  // New session (or a restarted one). No turn state is reset here: all
501
676
  // of it is per-turn (the `turns` FIFO), and dead traces from a
502
- // previous session are dropped at run() entry — an init-time clear
503
- // could unmark a turn the prompt drain already handed to the SDK
504
- // before this init was processed (#745 review).
677
+ // previous session were already carried into `rememberedDeadTurns`
678
+ // at run() entry an init-time clear could unmark a turn the prompt
679
+ // drain already handed to the SDK before this init was processed
680
+ // (#745 review).
681
+ // The durable turn registry is keyed by THIS session id from now on
682
+ // (#886) — including a pre-init submission the prompt drain already
683
+ // pulled, which couldn't be filed before the session id was known.
684
+ this.registrySessionId = message.session_id;
685
+ this.saveRegistry();
505
686
  yield {
506
687
  type: "session",
507
688
  sessionId: message.session_id,
@@ -696,13 +877,63 @@ export class ClaudeBackend {
696
877
  idx = this.turns.findIndex((t) => t.interrupted && this.parkedInterrupted.has(t.id) && !this.consumedInterrupted.has(t.id));
697
878
  }
698
879
  const trace = idx >= 0 ? (this.turns.splice(idx, 1)[0] ?? null) : null;
880
+ if (trace) {
881
+ // The pop retires the turn's outstanding record (#886) — in this
882
+ // instance's own file AND in the session's tombstones: a concurrent
883
+ // resumer may have copied this turn's id into its own file as a dead
884
+ // record while it was still live, and only a tombstone invalidates
885
+ // that replica (rewriting this instance's file does not). A lost
886
+ // tombstone can only revive the copy as a hedged over-recognition —
887
+ // never a fabricated success — so a failure logs and moves on.
888
+ const sid = this.registrySessionId;
889
+ if (sid) {
890
+ try {
891
+ tombstoneTurn(sid, `${this.instanceId}:${trace.id}`);
892
+ }
893
+ catch (err) {
894
+ logger.warn(`[claude-backend] turn tombstone write failed: ${msgOf(err)} (#886)`);
895
+ }
896
+ }
897
+ this.saveRegistry();
898
+ }
699
899
  let unverifiable = null;
900
+ // #886: an unmatched result with a REMEMBERED outstanding submission is
901
+ // a different report than one with none. Both fail closed — only the
902
+ // honesty of the message differs.
903
+ let unverifiedCompletion = false;
700
904
  if (this.classificationPoisoned) {
701
905
  unverifiable = "Turn bookkeeping was poisoned by an earlier overflow — no result in this session can be verified";
702
906
  }
703
907
  else if (!trace) {
704
- unverifiable = "The result could not be matched to any submitted turn";
705
- logger.warn(`[claude-backend] ${unverifiable} unverifiable, failing closed (#740)`);
908
+ const deadId = this.takeOldestRememberedDeadTurn();
909
+ if (deadId !== null) {
910
+ // A turn WAS submitted (before a session restart dropped its trace)
911
+ // and never reported an outcome — this result is most plausibly its
912
+ // late landing. Say exactly that: not a success (unverifiable — the
913
+ // match is inference, not proof), and not a failure either — the
914
+ // turn may have finished real work, and "turn failed, try again"
915
+ // would invite a destructive duplicate retry.
916
+ unverifiedCompletion = true;
917
+ unverifiable =
918
+ `A result (reported as "${resultSubtype}") arrived that matches no live turn, but a turn ` +
919
+ `submitted before the session restarted never reported an outcome — this is most likely that ` +
920
+ `turn completing late. It may have finished real work (files written, changes made), yet I ` +
921
+ `cannot verify which turn it was, so I am NOT counting it as a success. Check what that turn ` +
922
+ `was doing before asking for it again, so the work is not done twice.`;
923
+ logger.warn(`[claude-backend] traceless result (${resultSubtype}) recognized as a likely late landing for a pre-restart submitted turn — completed but unverified, failing closed (#886)`);
924
+ }
925
+ else if (this.registryDegraded) {
926
+ // The recognition record itself is not provably complete — do not
927
+ // assert the negative as confidently as a healthy registry allows.
928
+ unverifiable =
929
+ "The result could not be matched to any submitted turn, and the durable turn record " +
930
+ "could not be fully read or written, so an outstanding earlier submission can't be ruled out";
931
+ logger.warn(`[claude-backend] ${unverifiable} — unverifiable, failing closed (#740/#886)`);
932
+ }
933
+ else {
934
+ unverifiable = "The result could not be matched to any submitted turn";
935
+ logger.warn(`[claude-backend] ${unverifiable} — unverifiable, failing closed (#740)`);
936
+ }
706
937
  }
707
938
  else {
708
939
  let crossesGap = false;
@@ -755,7 +986,13 @@ export class ClaudeBackend {
755
986
  ok = false;
756
987
  yield {
757
988
  type: "error",
758
- message: `${unverifiable} reporting the turn as unverifiable rather than a success.`,
989
+ // The recognized late landing's message is self-contained (it says
990
+ // "not counted as a success" itself) and flagged so the panel
991
+ // doesn't frame it as a failure (#886).
992
+ message: unverifiedCompletion
993
+ ? unverifiable
994
+ : `${unverifiable} — reporting the turn as unverifiable rather than a success.`,
995
+ ...(unverifiedCompletion ? { unverifiedCompletion: true } : {}),
759
996
  ...stamp,
760
997
  };
761
998
  }
@@ -772,6 +1009,17 @@ export class ClaudeBackend {
772
1009
  ...stamp,
773
1010
  };
774
1011
  }
1012
+ // #885: a SELF landing for the stalled turn (success OR a genuine
1013
+ // error — i.e. not the interrupt's doing) means the turn ended on its
1014
+ // own and no rejection payload ever landed in the transcript, so the
1015
+ // held correction would be stale; drop it. An INTERRUPT landing
1016
+ // (trace.interrupted) is exactly the case the notice exists for.
1017
+ if (this.pendingStallNotice !== null &&
1018
+ trace !== null &&
1019
+ !trace.interrupted &&
1020
+ trace.id >= this.pendingStallNoticeTurn) {
1021
+ this.pendingStallNotice = null;
1022
+ }
775
1023
  yield {
776
1024
  type: "result",
777
1025
  ok,