cli-jaw 2.2.12 → 2.2.13

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 (64) hide show
  1. package/dist/src/agent/error-classifier.js +11 -2
  2. package/dist/src/agent/error-classifier.js.map +1 -1
  3. package/dist/src/agent/events/claude.js +7 -1
  4. package/dist/src/agent/events/claude.js.map +1 -1
  5. package/dist/src/agent/events/fulltext-bound.js +32 -0
  6. package/dist/src/agent/events/fulltext-bound.js.map +1 -0
  7. package/dist/src/agent/events/grok.js +7 -1
  8. package/dist/src/agent/events/grok.js.map +1 -1
  9. package/dist/src/agent/events/helpers.js +17 -4
  10. package/dist/src/agent/events/helpers.js.map +1 -1
  11. package/dist/src/agent/lifecycle-handler.js +56 -5
  12. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  13. package/dist/src/agent/pi-runtime.js +30 -1
  14. package/dist/src/agent/pi-runtime.js.map +1 -1
  15. package/dist/src/agent/spawn/exit-drain.js +96 -0
  16. package/dist/src/agent/spawn/exit-drain.js.map +1 -0
  17. package/dist/src/agent/spawn/line-buffer.js +26 -0
  18. package/dist/src/agent/spawn/line-buffer.js.map +1 -0
  19. package/dist/src/agent/spawn/process-kill.js +31 -0
  20. package/dist/src/agent/spawn/process-kill.js.map +1 -1
  21. package/dist/src/agent/spawn.js +80 -23
  22. package/dist/src/agent/spawn.js.map +1 -1
  23. package/dist/src/agent/watchdog.js +21 -5
  24. package/dist/src/agent/watchdog.js.map +1 -1
  25. package/dist/src/routes/link-preview.js +14 -0
  26. package/dist/src/routes/link-preview.js.map +1 -1
  27. package/package.json +3 -1
  28. package/public/dist/assets/CodeCanvas-CbfPP7Ad.js +3 -0
  29. package/public/dist/assets/{DocPanel-DtYwv3n3.js → DocPanel-Dz-J_l5M.js} +1 -1
  30. package/public/dist/assets/MarkdownRenderer-CXrilOsG.js +15 -0
  31. package/public/dist/assets/MarkdownRenderer-DZVh_xX2.js +1 -0
  32. package/public/dist/assets/{MilkdownWysiwygEditor-BNoLGbFC.js → MilkdownWysiwygEditor-Em7iC6y9.js} +8 -8
  33. package/public/dist/assets/{react-dom-AWKMTIe7.js → bounded-set-CFlZiEF4.js} +1 -1
  34. package/public/dist/assets/manager-DaN201mN.js +13 -0
  35. package/public/dist/assets/{wiki-link-resolver-BfNXN9px.js → wiki-link-resolver-DyFzBTIQ.js} +1 -1
  36. package/public/dist/manager/index.html +1 -1
  37. package/public/manager/src/SidebarRailRouter.tsx +9 -3
  38. package/public/manager/src/browser-panel/use-embedded-target-sync.ts +19 -1
  39. package/public/manager/src/code/CodeCanvas.tsx +14 -2
  40. package/public/manager/src/code/CodeTranscript.tsx +33 -9
  41. package/public/manager/src/code/CodeWorkbench.tsx +1 -1
  42. package/public/manager/src/code/code-event-dedupe.ts +27 -3
  43. package/public/manager/src/code/use-code-transcript-scroll.ts +28 -3
  44. package/public/manager/src/code/use-throttled-markdown.ts +75 -0
  45. package/public/manager/src/code/useCodeTranscriptVirtualRows.ts +19 -0
  46. package/public/manager/src/electron-metrics.tsx +7 -0
  47. package/public/manager/src/jaw-ceo/useJawCeo.ts +6 -1
  48. package/public/manager/src/jaw-ceo/useJawCeoVirtualTimeline.ts +5 -0
  49. package/public/manager/src/lib/bounded-set.ts +15 -0
  50. package/public/manager/src/lib/fnv1a.ts +14 -0
  51. package/public/manager/src/notes/rendering/CodeBlock.tsx +13 -4
  52. package/public/manager/src/notes/rendering/MarkdownRenderer.tsx +14 -2
  53. package/public/manager/src/notes/rendering/highlight-cache.ts +111 -0
  54. package/public/manager/src/notes/useNotesExternalSync.ts +6 -1
  55. package/scripts/bundle-sidecar.sh +8 -0
  56. package/scripts/check-electron-no-native.cjs +90 -4
  57. package/scripts/check-native-load.cjs +157 -0
  58. package/scripts/check-sidecar-smoke.mjs +151 -0
  59. package/scripts/ensure-native-modules.cjs +222 -18
  60. package/scripts/release-gates.mjs +29 -0
  61. package/public/dist/assets/CodeCanvas-BZo3dohZ.js +0 -3
  62. package/public/dist/assets/MarkdownRenderer-CHacL2M_.js +0 -15
  63. package/public/dist/assets/MarkdownRenderer-DyrB9FbZ.js +0 -1
  64. package/public/dist/assets/manager-DL1GygQL.js +0 -13
@@ -52,14 +52,48 @@ import { getEmployeeMcpServers } from './mcp-passthrough.js';
52
52
  // ─── State ───────────────────────────────────────────
53
53
  export let activeProcess = null;
54
54
  export const activeProcesses = new Map(); // agentId → child process
55
+ /** Kill reason recorded when a duplicate registration reaps the previous child. */
56
+ const DUP_REGISTRATION_KILL_REASON = 'dup-registration';
57
+ /** Grace before escalating that kill to SIGKILL, matching the sibling kill paths. */
58
+ const DUP_REGISTRATION_KILL_GRACE_MS = 2_000;
55
59
  function registerActiveProcess(agentLabel, child) {
56
60
  const prev = activeProcesses.get(agentLabel);
57
61
  if (prev && prev !== child) {
58
- if (prev.exitCode !== null || prev.killed) {
62
+ // `killed` only records that a signal was delivered, so it is not a
63
+ // liveness test: a CLI that traps SIGTERM stays alive with killed set.
64
+ // Treating it as exited would drop that survivor from the map without
65
+ // even scheduling the escalation below — the exact invisible process
66
+ // this branch exists to prevent.
67
+ if (hasChildExited(prev)) {
59
68
  activeProcesses.delete(agentLabel);
60
69
  }
61
70
  else {
62
- console.warn(`[spawn:dup] activeProcesses already has child for ${agentLabel} orphaning previous reference`);
71
+ // Dropping a live child from the map makes it invisible to
72
+ // killAllAgents, so it survives stop, shutdown, and restart while
73
+ // still holding its own memory. Reap it instead of leaking it.
74
+ console.warn(`[spawn:dup] activeProcesses already has a live child for ${agentLabel} — killing it before overwrite (pid=${prev.pid ?? 'unknown'})`);
75
+ if (prev.pid) {
76
+ const prevPid = prev.pid;
77
+ // Record a kill reason so the stale exit handler classifies this
78
+ // as an intentional kill rather than a genuine agent error.
79
+ killReasons.set(prevPid, DUP_REGISTRATION_KILL_REASON);
80
+ try {
81
+ killProcessTree(prevPid, 'SIGTERM');
82
+ }
83
+ catch (error) {
84
+ console.warn(`[spawn:dup] failed to kill previous child for ${agentLabel}:`, error?.message ?? error);
85
+ }
86
+ // Escalate like every sibling kill path does: a CLI that traps
87
+ // SIGTERM would otherwise survive with no map entry to find it.
88
+ // Route through killProcessTreeIfAlive so a child that exited
89
+ // during the grace period cannot have its recycled PID killed:
90
+ // killProcessTree walks `pgrep -P`, so a blind escalation would
91
+ // take an unrelated process tree down with it.
92
+ const escalate = setTimeout(() => {
93
+ killProcessTreeIfAlive(prev, prevPid);
94
+ }, DUP_REGISTRATION_KILL_GRACE_MS);
95
+ escalate.unref?.();
96
+ }
63
97
  }
64
98
  }
65
99
  activeProcesses.set(agentLabel, child);
@@ -90,7 +124,10 @@ export function buildAiERuntimeStatusMeta(cli, provider, model) {
90
124
  },
91
125
  };
92
126
  }
93
- import { killProcessTree } from './spawn/process-kill.js';
127
+ import { hasChildExited, killProcessTree, killProcessTreeIfAlive } from './spawn/process-kill.js';
128
+ import { releaseChildOutputAfterExit } from './spawn/exit-drain.js';
129
+ import { clampPendingLine } from './spawn/line-buffer.js';
130
+ import { appendBoundedFullText } from './events/fulltext-bound.js';
94
131
  /** Single choke point for streamed assistant text: appends to the live-run
95
132
  * accumulator and broadcasts agent_output tagged with the owning trace run
96
133
  * id plus the cumulative text length (`textLen`). The web UI uses the pair
@@ -174,9 +211,9 @@ export function killAgentById(agentId) {
174
211
  setTimeout(() => {
175
212
  try {
176
213
  if (proc.pid) {
177
- killProcessTree(proc.pid, 'SIGKILL');
214
+ killProcessTreeIfAlive(proc);
178
215
  }
179
- else {
216
+ else if (proc.exitCode === null && proc.signalCode === null) {
180
217
  proc.kill('SIGKILL');
181
218
  }
182
219
  }
@@ -1425,10 +1462,21 @@ export function spawnAgent(prompt, opts = {}) {
1425
1462
  const delta = String(event.text || '');
1426
1463
  if (!delta)
1427
1464
  return;
1428
- ctx.fullText += delta;
1465
+ {
1466
+ // D3: bound fullText — see events/fulltext-bound.ts.
1467
+ const bounded = appendBoundedFullText(ctx.fullText, delta);
1468
+ ctx.fullText = bounded.text;
1469
+ if (bounded.truncated)
1470
+ ctx.fullTextTruncated = true;
1471
+ }
1429
1472
  const displayDelta = normalizeAssistantDisplayText(delta);
1430
- if (ctx.liveOutputText !== undefined)
1431
- ctx.liveOutputText += displayDelta;
1473
+ if (ctx.liveOutputText !== undefined) {
1474
+ // Bound this too: it is promoted into fullText at close.
1475
+ const live = appendBoundedFullText(ctx.liveOutputText, displayDelta);
1476
+ ctx.liveOutputText = live.text;
1477
+ if (live.truncated)
1478
+ ctx.fullTextTruncated = true;
1479
+ }
1432
1480
  if (!ctx.outputTextStarted)
1433
1481
  ctx.outputTextStarted = true;
1434
1482
  broadcastAgentOutput(ctx, agentLabel, cli, displayDelta, empTag, traceAudience);
@@ -1456,10 +1504,7 @@ export function spawnAgent(prompt, opts = {}) {
1456
1504
  const pid = child.pid;
1457
1505
  killProcessTree(pid, 'SIGTERM');
1458
1506
  setTimeout(() => {
1459
- try {
1460
- killProcessTree(pid, 'SIGKILL');
1461
- }
1462
- catch { /* already dead */ }
1507
+ killProcessTreeIfAlive(child, pid);
1463
1508
  }, 5_000);
1464
1509
  }
1465
1510
  else
@@ -1515,7 +1560,10 @@ export function spawnAgent(prompt, opts = {}) {
1515
1560
  opts.lifecycle?.onExit?.(result.code);
1516
1561
  const killReason = consumeKillReason(child.pid);
1517
1562
  const wasKilled = !!killReason;
1518
- const wasSteer = killReason === 'steer';
1563
+ // 'dup-registration' behaves like a steer for cleanup purposes: a
1564
+ // replacement child already owns this label, so the stale exit handler
1565
+ // must not delete the new child's map entry.
1566
+ const wasSteer = killReason === 'steer' || killReason === DUP_REGISTRATION_KILL_REASON;
1519
1567
  const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, result.code, cli);
1520
1568
  return handleAgentExit({
1521
1569
  ctx, code: result.code, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
@@ -1879,7 +1927,8 @@ export function spawnAgent(prompt, opts = {}) {
1879
1927
  console.warn(`[codex-app:unexpected-exit] code=${processExit.value.code} signal=${processExit.value.signal} threadId=${ctx.sessionId || 'none'}`);
1880
1928
  }
1881
1929
  const wasKilled = !!killReason;
1882
- const wasSteer = killReason === 'steer';
1930
+ // See above: a dup-registration kill must not clobber the replacement.
1931
+ const wasSteer = killReason === 'steer' || killReason === DUP_REGISTRATION_KILL_REASON;
1883
1932
  flushCodexAppThinking();
1884
1933
  const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, exitCode, cli);
1885
1934
  await handleAgentExit({
@@ -1961,6 +2010,14 @@ export function spawnAgent(prompt, opts = {}) {
1961
2010
  broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...runtimeStatusMeta, ...empTag });
1962
2011
  if (mainManaged && !opts.internal)
1963
2012
  beginLiveRun(liveScope, cli);
2013
+ // The turn settles on 'close', which waits for every stdio stream to close.
2014
+ // A descendant that inherited these pipes can outlive the child and hold
2015
+ // them open forever, so bound that wait while still draining short tails.
2016
+ const releaseExitDrain = releaseChildOutputAfterExit(child, {
2017
+ onRelease: (reason) => {
2018
+ console.warn(`[jaw:drain] ${agentLabel} exited but output stayed open — released after ${reason}`);
2019
+ },
2020
+ });
1964
2021
  // ─── DIFF-A: error guard — prevent uncaught ENOENT crash ───
1965
2022
  let stdSettled = false; // guard: error→close can fire sequentially
1966
2023
  let lastOpencodeIoAt = Date.now();
@@ -1981,6 +2038,7 @@ export function spawnAgent(prompt, opts = {}) {
1981
2038
  child.on('error', (err) => {
1982
2039
  clearOpencodeIdleTimer();
1983
2040
  clearAgyQuietCompletionTimer();
2041
+ releaseExitDrain();
1984
2042
  if (stdSettled)
1985
2043
  return;
1986
2044
  stdSettled = true;
@@ -2089,11 +2147,7 @@ export function spawnAgent(prompt, opts = {}) {
2089
2147
  try {
2090
2148
  killProcessTree(child.pid, 'SIGTERM');
2091
2149
  setTimeout(() => {
2092
- try {
2093
- if (child.pid)
2094
- killProcessTree(child.pid, 'SIGKILL');
2095
- }
2096
- catch { /* already dead */ }
2150
+ killProcessTreeIfAlive(child);
2097
2151
  }, DEFAULT_KILL_ESCALATION_MS);
2098
2152
  }
2099
2153
  catch (e) {
@@ -2129,10 +2183,7 @@ export function spawnAgent(prompt, opts = {}) {
2129
2183
  if (child.pid) {
2130
2184
  killProcessTree(child.pid, 'SIGTERM');
2131
2185
  setTimeout(() => {
2132
- try {
2133
- killProcessTree(child.pid, 'SIGKILL');
2134
- }
2135
- catch { /* already dead */ }
2186
+ killProcessTreeIfAlive(child);
2136
2187
  }, 5_000);
2137
2188
  }
2138
2189
  }, watchdogConfig);
@@ -2326,6 +2377,11 @@ export function spawnAgent(prompt, opts = {}) {
2326
2377
  buffer += chunk.toString();
2327
2378
  const lines = buffer.split('\n');
2328
2379
  buffer = lines.pop() ?? '';
2380
+ const clampedPending = clampPendingLine(buffer);
2381
+ if (clampedPending.overflowed) {
2382
+ console.warn(`[jaw:${agentLabel}] stdout line exceeded the pending-line cap without a newline — truncating`);
2383
+ buffer = clampedPending.buffer;
2384
+ }
2329
2385
  for (const line of lines) {
2330
2386
  if (!line.trim())
2331
2387
  continue;
@@ -2351,6 +2407,7 @@ export function spawnAgent(prompt, opts = {}) {
2351
2407
  clearOpencodeIdleTimer();
2352
2408
  clearAgyQuietCompletionTimer();
2353
2409
  stallWatchdog.stop();
2410
+ releaseExitDrain();
2354
2411
  if (stdSettled)
2355
2412
  return; // error handler already resolved
2356
2413
  // [I1] Flush residual NDJSON buffer — last event may lack trailing newline