cli-jaw 2.17.30 → 2.17.35

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 (135) hide show
  1. package/README.md +2 -0
  2. package/dist/src/agent/codex-app-client.js +69 -1
  3. package/dist/src/agent/codex-app-client.js.map +1 -1
  4. package/dist/src/agent/cursor-runtime.js +9 -0
  5. package/dist/src/agent/cursor-runtime.js.map +1 -1
  6. package/dist/src/agent/pi-runtime.js +1 -1
  7. package/dist/src/agent/pi-runtime.js.map +1 -1
  8. package/dist/src/agent/prompt-context.js +39 -0
  9. package/dist/src/agent/prompt-context.js.map +1 -1
  10. package/dist/src/agent/spawn.js +138 -20
  11. package/dist/src/agent/spawn.js.map +1 -1
  12. package/dist/src/browser/web-ai/capability-observation-presets.js +4 -0
  13. package/dist/src/browser/web-ai/capability-observation-presets.js.map +1 -1
  14. package/dist/src/browser/web-ai/chatgpt-model.js +9 -5
  15. package/dist/src/browser/web-ai/chatgpt-model.js.map +1 -1
  16. package/dist/src/browser/web-ai/grok-model.js +18 -5
  17. package/dist/src/browser/web-ai/grok-model.js.map +1 -1
  18. package/dist/src/cli/claude-models.js +6 -0
  19. package/dist/src/cli/claude-models.js.map +1 -1
  20. package/dist/src/cli/handlers-runtime.js +29 -4
  21. package/dist/src/cli/handlers-runtime.js.map +1 -1
  22. package/dist/src/cli/registry.js +56 -15
  23. package/dist/src/cli/registry.js.map +1 -1
  24. package/dist/src/code-mode/model-options.js +4 -4
  25. package/dist/src/code-mode/model-options.js.map +1 -1
  26. package/dist/src/core/config.js +1 -1
  27. package/dist/src/core/config.js.map +1 -1
  28. package/dist/src/core/db.js +19 -0
  29. package/dist/src/core/db.js.map +1 -1
  30. package/dist/src/core/runtime-settings.js +11 -2
  31. package/dist/src/core/runtime-settings.js.map +1 -1
  32. package/dist/src/orchestrator/attestation.js +66 -0
  33. package/dist/src/orchestrator/attestation.js.map +1 -1
  34. package/dist/src/orchestrator/gateway.js +11 -3
  35. package/dist/src/orchestrator/gateway.js.map +1 -1
  36. package/dist/src/orchestrator/pipeline.js +5 -0
  37. package/dist/src/orchestrator/pipeline.js.map +1 -1
  38. package/dist/src/routes/command.js +4 -1
  39. package/dist/src/routes/command.js.map +1 -1
  40. package/dist/src/routes/orchestrate.js +14 -2
  41. package/dist/src/routes/orchestrate.js.map +1 -1
  42. package/dist/src/slack/commands.js +1 -0
  43. package/dist/src/slack/commands.js.map +1 -1
  44. package/dist/src/telegram/bot.js +3 -2
  45. package/dist/src/telegram/bot.js.map +1 -1
  46. package/package.json +5 -1
  47. package/public/dist/assets/{Agent-BjxJRsKG.js → Agent-kyyjcnqC.js} +1 -1
  48. package/public/dist/assets/BrowserPanel-0b3deLGR.js +3 -0
  49. package/public/dist/assets/BrowserPanel-D7SnTts8.css +1 -0
  50. package/public/dist/assets/{Heartbeat-Ca8DmJcw.js → Heartbeat-CD1R95Kv.js} +1 -1
  51. package/public/dist/assets/{Memory-BY88ur_h.js → Memory-CpZIdE1o.js} +1 -1
  52. package/public/dist/assets/{ModelProvider-mtAtYLmW.js → ModelProvider-tL42E5dy.js} +1 -1
  53. package/public/dist/assets/agent-meta-KPnVam9G.js +1 -0
  54. package/public/dist/assets/{app-CYayckBa.js → app-BvRV-aEt.js} +4 -4
  55. package/public/dist/assets/bgtask-badge-DLqpImpn.js +1 -0
  56. package/public/dist/assets/{bgtask-badge-CT6s6rND.js → bgtask-badge-FiEB3FJm.js} +1 -1
  57. package/public/dist/assets/constants-BfYYzHNr.js +1 -0
  58. package/public/dist/assets/{employees-DCju7V1x.js → employees-86F_D_Ce.js} +1 -1
  59. package/public/dist/assets/iframe-renderer-C_swzquc.js +1 -0
  60. package/public/dist/assets/{iframe-renderer-LZp7Nqm1.js → iframe-renderer-eozJpgjk.js} +2 -2
  61. package/public/dist/assets/manager-BnmWx9xL.js +13 -0
  62. package/public/dist/assets/manager-Cmu0aKPb.css +1 -0
  63. package/public/dist/assets/{memory-V5caYbpS.js → memory-DX7U3WV_.js} +1 -1
  64. package/public/dist/assets/memory-DfPbqPTD.js +1 -0
  65. package/public/dist/assets/{message-history-Bp_sUDSx.js → message-history-B_fIGorC.js} +1 -1
  66. package/public/dist/assets/message-history-BhSsLjis.js +1 -0
  67. package/public/dist/assets/{render-Cd4frZ2U.js → render-fKAuISeM.js} +1 -1
  68. package/public/dist/assets/settings-BNqSR9Zr.js +1 -0
  69. package/public/dist/assets/{settings-a0blK1fr.js → settings-CH6exiZf.js} +1 -1
  70. package/public/dist/assets/{settings-core-C5qo8giT.js → settings-core-DqKyyxbQ.js} +1 -1
  71. package/public/dist/assets/settings-core-x7CmBZq1.js +1 -0
  72. package/public/dist/assets/{sidebar-FbI4udzs.js → sidebar-qyLy9Og8.js} +3 -3
  73. package/public/dist/assets/skills-C7Yi3LIq.js +1 -0
  74. package/public/dist/assets/{skills-DeG9lWF2.js → skills-DAhOtbfP.js} +1 -1
  75. package/public/dist/assets/{slash-commands-FoQuZXga.js → slash-commands-BYH6YVS4.js} +1 -1
  76. package/public/dist/assets/slash-commands-DEbJk3sj.js +1 -0
  77. package/public/dist/assets/{trace-drawer-CWs8ydMO.js → trace-drawer-nyAJ3Bm_.js} +1 -1
  78. package/public/dist/assets/{ui-Cd8etFwN.js → ui-C62w6bJS.js} +1 -1
  79. package/public/dist/assets/ui-CBErZPBp.js +1 -0
  80. package/public/dist/index.html +1 -1
  81. package/public/dist/manager/index.html +2 -2
  82. package/public/js/constants.ts +22 -5
  83. package/public/js/features/settings-types.ts +5 -0
  84. package/public/manager/src/App.tsx +1 -1
  85. package/public/manager/src/AppChrome.tsx +18 -3
  86. package/public/manager/src/SidebarRailRouter.tsx +47 -1
  87. package/public/manager/src/browser-panel/BrowserPanel.tsx +151 -57
  88. package/public/manager/src/browser-panel/browser-address-bar.tsx +53 -0
  89. package/public/manager/src/browser-panel/browser-address-state.ts +41 -0
  90. package/public/manager/src/browser-panel/browser-favicon.ts +18 -0
  91. package/public/manager/src/browser-panel/browser-history-store.ts +80 -0
  92. package/public/manager/src/browser-panel/browser-panel.css +141 -24
  93. package/public/manager/src/components/CommandBar.tsx +17 -5
  94. package/public/manager/src/components/InstanceGroups.tsx +143 -23
  95. package/public/manager/src/components/InstanceNavigator.tsx +55 -2
  96. package/public/manager/src/components/InstanceRow.tsx +51 -3
  97. package/public/manager/src/components/SidebarRail.tsx +10 -10
  98. package/public/manager/src/components/SidebarResizeHandle.tsx +22 -0
  99. package/public/manager/src/components/WorkspaceLayout.tsx +22 -8
  100. package/public/manager/src/components/instance-row-status.ts +48 -0
  101. package/public/manager/src/components/sidebar-keyboard.ts +129 -0
  102. package/public/manager/src/dashboard-settings/DashboardSettingsWorkspace.tsx +42 -0
  103. package/public/manager/src/help/helpContent.tsx +1 -0
  104. package/public/manager/src/hooks/useSidebarGroupCollapse.ts +46 -0
  105. package/public/manager/src/hooks/useSidebarWidth.ts +88 -0
  106. package/public/manager/src/hooks/useWindowFullscreen.ts +23 -0
  107. package/public/manager/src/manager-components.css +206 -31
  108. package/public/manager/src/manager-layout.css +30 -19
  109. package/public/manager/src/manager-p0-1-1.css +72 -36
  110. package/public/manager/src/manager-polish.css +6 -45
  111. package/public/manager/src/manager-shortcut-runner.ts +37 -1
  112. package/public/manager/src/manager-shortcuts.ts +21 -0
  113. package/public/manager/src/manager-tokens.css +190 -127
  114. package/public/manager/src/panels/PanelResizer.tsx +18 -2
  115. package/public/manager/src/panels/desktop-bridge.ts +12 -1
  116. package/public/manager/src/panels/panels.css +32 -32
  117. package/public/manager/src/settings/pages/Agent.tsx +26 -0
  118. package/public/manager/src/settings/pages/components/agent/agent-meta.ts +6 -5
  119. package/public/manager/src/styles.css +15 -7
  120. package/public/manager/src/types.ts +5 -1
  121. package/public/dist/assets/BrowserPanel-CsYKexNf.js +0 -3
  122. package/public/dist/assets/BrowserPanel-DpntR8EJ.css +0 -1
  123. package/public/dist/assets/agent-meta-_Idad3qq.js +0 -1
  124. package/public/dist/assets/bgtask-badge-CidVvjWb.js +0 -1
  125. package/public/dist/assets/constants-DK-21dMG.js +0 -1
  126. package/public/dist/assets/iframe-renderer-aFXUtTXX.js +0 -1
  127. package/public/dist/assets/manager-CwqrZ2MH.js +0 -13
  128. package/public/dist/assets/manager-LkGs6qEJ.css +0 -1
  129. package/public/dist/assets/memory-BcZdf_oz.js +0 -1
  130. package/public/dist/assets/message-history-DrEPUdTy.js +0 -1
  131. package/public/dist/assets/settings-core-Cm-CmVLA.js +0 -1
  132. package/public/dist/assets/settings-mbGEtP5B.js +0 -1
  133. package/public/dist/assets/skills-G8KUeHBV.js +0 -1
  134. package/public/dist/assets/slash-commands-BYv3yboz.js +0 -1
  135. package/public/dist/assets/ui-yrn3s72A.js +0 -1
@@ -18,7 +18,7 @@ import { settleOnce } from '../orchestrator/request-registry.js';
18
18
  import { settings, UPLOADS_DIR, detectCli, getProjectDirs } from '../core/config.js';
19
19
  import { migrateLegacyClaudeValue } from '../cli/claude-models.js';
20
20
  import { stripUndefined } from '../core/strip-undefined.js';
21
- import { clearEmployeeSession, getSession, insertMessage, insertMessageWithTraceRun, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, } from '../core/db.js';
21
+ import { clearEmployeeSession, getSession, insertMessage, insertMessageWithTraceRun, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, getMaxMessageId, getSteerSalvageAfter, } from '../core/db.js';
22
22
  import { sanitizeToolLogForDurableStorage } from '../shared/tool-log-sanitize.js';
23
23
  import { buildTaskSnapshot } from '../memory/runtime.js';
24
24
  import { getActiveChatSession, getRemoteBoundSessionId } from '../core/chat-sessions.js';
@@ -42,7 +42,7 @@ import { stripInterviewTracker } from '../orchestrator/sanitize.js';
42
42
  import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool, getLiveRun } from './live-run-state.js';
43
43
  import { setSpawnRef as setMemorySpawnRef, } from './memory-flush-controller.js';
44
44
  import { applyCliEnvDefaults, buildSessionResumeKey, ensureOpencodeAlwaysAllowPermissions, mergeEnvWindowsSafe } from './spawn-env.js';
45
- import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt } from './prompt-context.js';
45
+ import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt, withSteerContext } from './prompt-context.js';
46
46
  import { attachWatchdog, DEFAULT_WATCHDOG_ABSOLUTE_HARD_CAP_MS } from './watchdog.js';
47
47
  import { buildOpencodeRuntimeSnapshot, buildOpencodeSpawnAudit, pushOpencodeRawEvent, resolveOpencodeBinary, } from './opencode-diagnostics.js';
48
48
  import { asCliEventRecord, discriminate, fieldString } from '../types/cli-events.js';
@@ -352,6 +352,48 @@ function consumeKillReason(pid) {
352
352
  killReasons.delete(pid);
353
353
  return reason;
354
354
  }
355
+ // ─── Steer exit-settle barrier ─────────────────────
356
+ // killActiveAgent removes the scope's activeMainProcesses entry synchronously,
357
+ // so waitForProcessEnd() resolves immediately on a steer kill — long before the
358
+ // exit handler has written the interrupted partial output to the messages table.
359
+ // A follow-up spawn could then read history without the salvage row. The barrier
360
+ // is armed at kill time (never at exit-handler entry — that is already too late)
361
+ // and settled by the exit handler's completion, success or failure.
362
+ const exitSettlers = new Map();
363
+ /** Arm the barrier. Idempotent: a repeated steer keeps the first arm. */
364
+ export function armExitSettle(scopeKey) {
365
+ if (exitSettlers.has(scopeKey))
366
+ return;
367
+ let resolve;
368
+ const promise = new Promise(r => { resolve = r; });
369
+ exitSettlers.set(scopeKey, { promise, resolve });
370
+ }
371
+ /** Settle the barrier; a no-op when no steer kill armed it. */
372
+ export function settleExit(scopeKey) {
373
+ const entry = exitSettlers.get(scopeKey);
374
+ if (!entry)
375
+ return;
376
+ exitSettlers.delete(scopeKey);
377
+ entry.resolve();
378
+ }
379
+ /**
380
+ * Await the armed exit handler's completion, bounded. A timeout releases the
381
+ * waiter and drops the arm — a wedged exit handler must not hang the steer.
382
+ */
383
+ export function waitForExitSettled(scopeKey, timeoutMs = 5000) {
384
+ const entry = exitSettlers.get(scopeKey);
385
+ if (!entry)
386
+ return Promise.resolve();
387
+ // The timer is NOT unref'd and is always cleared: an unref'd timer can vanish
388
+ // with a drained event loop (test runner), leaving the waiter pending forever.
389
+ let timer;
390
+ const timeout = new Promise(r => { timer = setTimeout(r, timeoutMs); });
391
+ return Promise.race([entry.promise, timeout]).then(() => {
392
+ clearTimeout(timer);
393
+ if (exitSettlers.get(scopeKey) === entry)
394
+ exitSettlers.delete(scopeKey);
395
+ });
396
+ }
355
397
  /**
356
398
  * Fix A: 사용자 stop은 메모리 큐 + DB persisted_queue + frontend pending row를
357
399
  * 모두 폐기한다. exit handler의 scoped queue 자동 드레인이 stop 직후 잔존
@@ -417,6 +459,8 @@ export function killActiveAgent(scopeKeyOrReason = 'user', scopedReason) {
417
459
  if (run.process?.pid)
418
460
  killReasons.set(run.process.pid, reason);
419
461
  console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey)} action=lease.cancel`);
462
+ if (reason === 'steer' || reason === 'interrupt')
463
+ armExitSettle(scopeKey);
420
464
  run.cancelTurn(reason);
421
465
  if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt')
422
466
  activeMainProcesses.delete(scopeKey);
@@ -432,6 +476,8 @@ export function killActiveAgent(scopeKeyOrReason = 'user', scopedReason) {
432
476
  console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey) || 'unknown'} signal=${policy.signal} escalationMs=${policy.escalationMs}`);
433
477
  if (activeProcess.pid)
434
478
  killReasons.set(activeProcess.pid, reason);
479
+ if (reason === 'steer' || reason === 'interrupt')
480
+ armExitSettle(scopeKey);
435
481
  const proc = activeProcess;
436
482
  // One owner runs the whole termination: tree walk with the policy signal,
437
483
  // then escalation after policy.escalationMs that re-checks the ORIGINAL
@@ -537,7 +583,10 @@ export function waitForAllProcessesEnd(timeoutMs = 2000) {
537
583
  }
538
584
  export function canSteerAgent(scopeKey) {
539
585
  const run = activeMainProcesses.get(scopeKey);
540
- return run?.meta.cli === 'jwc' && jawRuntimesByScope.get(scopeKey)?.busy === true;
586
+ if (run?.meta.cli === 'jwc' && jawRuntimesByScope.get(scopeKey)?.busy === true)
587
+ return true;
588
+ // codex-app: the hook exists exactly while a steerable turn is in flight.
589
+ return typeof run?.steerTurnInBand === 'function';
541
590
  }
542
591
  export async function steerAgent(scopeKey, newPrompt, source, meta) {
543
592
  const run = activeMainProcesses.get(scopeKey);
@@ -552,27 +601,72 @@ export async function steerAgent(scopeKey, newPrompt, source, meta) {
552
601
  // event will ever carry this id. Settling here is what stops a caller
553
602
  // from waiting for an answer that structurally cannot arrive.
554
603
  settleOnce(meta?.requestId, 'steered');
555
- return;
604
+ return 'steered';
605
+ }
606
+ if (typeof run?.steerTurnInBand === 'function') {
607
+ // codex-app same-turn steer. The user row is written only AFTER the
608
+ // server accepts — a fallback must not leave a duplicate insert for the
609
+ // queued path to write again.
610
+ let outcome;
611
+ try {
612
+ outcome = await run.steerTurnInBand(newPrompt);
613
+ }
614
+ catch (err) {
615
+ console.error('[jaw:steer] codex-app in-band steer failed:', err.message);
616
+ return 'fallback-queue';
617
+ }
618
+ if (outcome !== 'steered') {
619
+ if (outcome === 'rejected') {
620
+ // review/compact turns structurally reject steer — tell the user
621
+ // their message was queued instead, not silently swallowed.
622
+ broadcast('steer_rejected', stripUndefined({ prompt: newPrompt, origin: source || 'web', scope: scopeKey, sessionId: chatSessionId, reason: 'turn-not-steerable', requestId: meta?.requestId }));
623
+ }
624
+ return 'fallback-queue';
625
+ }
626
+ insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
627
+ broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
628
+ broadcast('steer_started', stripUndefined({ prompt: newPrompt, origin: source || 'web', scope: scopeKey, sessionId: chatSessionId, target: meta?.target, chatId: meta?.chatId, requestId: meta?.requestId, remoteKey: meta?.remoteKey, replyViaTarget: meta?.replyViaTarget }));
629
+ settleOnce(meta?.requestId, 'steered');
630
+ return 'steered';
556
631
  }
557
632
  const steerWaitMs = getSteerWaitMsForActiveAgent(scopeKey);
633
+ // Snapshot BEFORE the kill: the interrupted partial-output row is identified
634
+ // as the first ⏹️-tagged assistant message with id above this mark. A
635
+ // created_at comparison is not safe (second-resolution UTC column).
636
+ const maxIdBeforeKill = getMaxMessageId(chatSessionId);
558
637
  const wasRunning = killActiveAgent(scopeKey, 'steer');
559
638
  if (wasRunning)
560
639
  await waitForProcessEnd(scopeKey, steerWaitMs);
640
+ // The kill removes the scope's map entry synchronously, so the wait above can
641
+ // return before the exit handler's salvage insert. Wait for the settle barrier
642
+ // armed by the kill so the follow-up run actually sees the partial output.
643
+ if (wasRunning)
644
+ await waitForExitSettled(scopeKey);
645
+ let steerContext = null;
646
+ if (wasRunning) {
647
+ const salvage = getSteerSalvageAfter(chatSessionId, maxIdBeforeKill);
648
+ // The ⏹️ tag is a human-facing marker; the model gets the payload only.
649
+ steerContext = salvage ? salvage.replace(/^⏹️ \[interrupted\]\s*/, '') : null;
650
+ }
561
651
  insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
562
652
  broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
563
653
  broadcast('steer_started', stripUndefined({ prompt: newPrompt, origin: source || 'web', scope: scopeKey, requestId: meta?.requestId }));
564
654
  const { orchestrate, orchestrateContinue, orchestrateReset, isContinueIntent, isResetIntent } = await import('../orchestrator/pipeline.js');
565
655
  const origin = source || 'web';
656
+ const steerMeta = stripUndefined({ origin, scope: scopeKey, chatSessionId, requestId: meta?.requestId, _skipInsert: true, _steerContext: steerContext || undefined });
566
657
  const task = isResetIntent(newPrompt)
567
- ? orchestrateReset(stripUndefined({ origin, scope: scopeKey, chatSessionId, requestId: meta?.requestId, _skipInsert: true }))
658
+ ? orchestrateReset(steerMeta)
568
659
  : isContinueIntent(newPrompt)
569
- ? orchestrateContinue(stripUndefined({ origin, scope: scopeKey, chatSessionId, requestId: meta?.requestId, _skipInsert: true }))
570
- : orchestrate(newPrompt, stripUndefined({ origin, scope: scopeKey, chatSessionId, requestId: meta?.requestId, _skipInsert: true }));
660
+ ? orchestrateContinue(steerMeta)
661
+ : orchestrate(newPrompt, steerMeta);
571
662
  task.catch(async (err) => {
572
663
  console.error('[steer:orchestrate]', err.message);
573
664
  broadcast('orchestrate_done', stripUndefined({ text: `[error] ${err.message}`, error: true, origin, requestId: meta?.requestId }));
574
665
  settleOnce(meta?.requestId, 'failed', { error: err.message });
575
666
  });
667
+ // The follow-up was started as a new run (kill-path or idle race). The caller
668
+ // must NOT also queue the message.
669
+ return 'new-run';
576
670
  }
577
671
  // ─── Helpers ─────────────────────────────────────────
578
672
  export function makeCleanEnv(extraEnv = {}, inheritedEnv = process.env, platform = process.platform) {
@@ -712,7 +806,7 @@ export const saveUpload = (buffer, originalName, options) => _saveUpload(UPLOADS
712
806
  export { buildMediaPrompt, buildMediaPromptMany };
713
807
  // ─── Spawn Agent ─────────────────────────────────────
714
808
  import { AcpClient } from '../cli/acp-client.js';
715
- import { CodexAppClient, isRecoverableResumeError } from './codex-app-client.js';
809
+ import { CodexAppClient, CodexSteerError, isRecoverableResumeError } from './codex-app-client.js';
716
810
  import { acquireCodexAppRuntime, acquirePiRuntime, } from './runtime-pool.js';
717
811
  import { acquireCodexAppLane, CodexHostGenerationStaleError, prepareCodexAppHost, } from './codex-host-pool.js';
718
812
  import { loadCatalogEfforts, resolveCatalogPath, validateModelEffort } from './codex-app-catalog.js';
@@ -1104,6 +1198,7 @@ export function spawnAgent(prompt, opts = {}) {
1104
1198
  sysPrompt,
1105
1199
  isResume,
1106
1200
  });
1201
+ promptForArgs = withSteerContext(promptForArgs, opts.steerContext);
1107
1202
  const agyResumeReplayPrefix = cli === 'agy' && isResume
1108
1203
  ? getLatestAssistantContentForAgyResume(settings["workingDir"], chatSessionId)
1109
1204
  : null;
@@ -1498,7 +1593,8 @@ export function spawnAgent(prompt, opts = {}) {
1498
1593
  const acpPrompt = needsHistoryFallback
1499
1594
  ? withHistoryPrompt(prompt, fallbackHistory)
1500
1595
  : (isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
1501
- const { promise: promptPromise } = acp.prompt(acpPrompt);
1596
+ const acpPromptWithSteer = withSteerContext(acpPrompt, opts.steerContext);
1597
+ const { promise: promptPromise } = acp.prompt(acpPromptWithSteer);
1502
1598
  const promptResult = await promptPromise;
1503
1599
  promptCompleted = true;
1504
1600
  if (process.env["DEBUG"])
@@ -1575,7 +1671,7 @@ export function spawnAgent(prompt, opts = {}) {
1575
1671
  processQueue,
1576
1672
  }).catch((err) => {
1577
1673
  console.error('[jaw:lifecycle] handleAgentExit failed (ACP):', err.message);
1578
- });
1674
+ }).finally(() => settleExit(scopeKey));
1579
1675
  });
1580
1676
  return { child, promise: resultPromise };
1581
1677
  }
@@ -1589,7 +1685,7 @@ export function spawnAgent(prompt, opts = {}) {
1589
1685
  }
1590
1686
  const piSessionId = isResume && bucketSessionId ? bucketSessionId : '';
1591
1687
  console.log(`[jaw:pi] isResume=${isResume}, bucketSessionId=${bucketSessionId || 'none'}, piSessionId=${piSessionId || 'new'}`);
1592
- const piPrompt = piSessionId ? prompt : withHistoryPrompt(prompt, historyBlock);
1688
+ const piPrompt = withSteerContext(piSessionId ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext);
1593
1689
  const traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience });
1594
1690
  const ctx = {
1595
1691
  fullText: '',
@@ -1772,7 +1868,7 @@ export function spawnAgent(prompt, opts = {}) {
1772
1868
  fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
1773
1869
  fallbackMaxRetries: FALLBACK_MAX_RETRIES,
1774
1870
  processQueue,
1775
- });
1871
+ }).finally(() => settleExit(scopeKey));
1776
1872
  }).catch(async (err) => {
1777
1873
  piWatchdog.stop();
1778
1874
  await releaseLease().catch(() => { });
@@ -1798,7 +1894,7 @@ export function spawnAgent(prompt, opts = {}) {
1798
1894
  processQueue,
1799
1895
  }).catch((handleErr) => {
1800
1896
  console.error('[jaw:lifecycle] handleAgentExit failed (Pi):', handleErr.message);
1801
- });
1897
+ }).finally(() => settleExit(scopeKey));
1802
1898
  });
1803
1899
  };
1804
1900
  if (opts.agentId) {
@@ -2026,6 +2122,25 @@ export function spawnAgent(prompt, opts = {}) {
2026
2122
  const cancelHook = (_reason) => { void requestLeaseCancel(); };
2027
2123
  if (lease && mainRun)
2028
2124
  mainRun.cancelTurn = cancelHook;
2125
+ // Same-turn steer (turn/steer) for the duration of THIS turn only:
2126
+ // installed and torn down with cancelHook so capability reads never
2127
+ // outlive the steerable window.
2128
+ const steerHook = async (text) => {
2129
+ try {
2130
+ await appClient.steerTurn(laneScope, text, { clientUserMessageId: crypto.randomUUID() });
2131
+ return 'steered';
2132
+ }
2133
+ catch (err) {
2134
+ if (err instanceof CodexSteerError) {
2135
+ // review/compact turns reject steer; a mismatched/finished
2136
+ // turn raced us. Both are queue-fallback territory.
2137
+ return err.code === 'not-steerable' ? 'rejected' : 'unavailable';
2138
+ }
2139
+ throw err;
2140
+ }
2141
+ };
2142
+ if (mainRun)
2143
+ mainRun.steerTurnInBand = steerHook;
2029
2144
  const watchdogTimeout = (kind) => {
2030
2145
  if (watchdogCancel)
2031
2146
  return;
@@ -2109,7 +2224,8 @@ export function spawnAgent(prompt, opts = {}) {
2109
2224
  const codexAppPrompt = (shouldPrependHistory && historyBlock)
2110
2225
  ? `${historyBlock}\n\n[User Message]\n${prompt}`
2111
2226
  : prompt;
2112
- const startTurn = appClient.startTurn(laneScope, codexAppPrompt);
2227
+ const codexAppPromptWithSteer = withSteerContext(codexAppPrompt, opts.steerContext);
2228
+ const startTurn = appClient.startTurn(laneScope, codexAppPromptWithSteer);
2113
2229
  await Promise.race([startTurn, turnDone]);
2114
2230
  await turnDone;
2115
2231
  turnCompleted = !turnReportedFailure;
@@ -2151,6 +2267,8 @@ export function spawnAgent(prompt, opts = {}) {
2151
2267
  await watchdogCancel;
2152
2268
  if (mainRun?.cancelTurn === cancelHook)
2153
2269
  delete mainRun.cancelTurn;
2270
+ if (mainRun?.steerTurnInBand === steerHook)
2271
+ delete mainRun.steerTurnInBand;
2154
2272
  listener.dispose();
2155
2273
  if (lease)
2156
2274
  lease.release();
@@ -2195,7 +2313,7 @@ export function spawnAgent(prompt, opts = {}) {
2195
2313
  processQueue,
2196
2314
  }).catch((err) => {
2197
2315
  console.error('[jaw:lifecycle] handleAgentExit failed (codex-app):', err.message);
2198
- });
2316
+ }).finally(() => settleExit(scopeKey));
2199
2317
  };
2200
2318
  if (opts.agentId) {
2201
2319
  const employeeLaneScope = `employee:${opts.agentId}`;
@@ -2498,21 +2616,21 @@ export function spawnAgent(prompt, opts = {}) {
2498
2616
  insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
2499
2617
  }
2500
2618
  if (cli === 'claude') {
2501
- child.stdin.write(isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
2619
+ child.stdin.write(withSteerContext(isResume ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext));
2502
2620
  }
2503
2621
  else if (cli === 'claude-e' || (cli === 'ai-e' && effectiveProvider === 'claude')) {
2504
- child.stdin.write(isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
2622
+ child.stdin.write(withSteerContext(isResume ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext));
2505
2623
  }
2506
2624
  else if (cli === 'codex' && !isResume) {
2507
2625
  const codexStdin = historyBlock
2508
2626
  ? `${historyBlock}\n\n[User Message]\n${prompt}`
2509
2627
  : `[User Message]\n${prompt}`;
2510
- child.stdin.write(codexStdin);
2628
+ child.stdin.write(withSteerContext(codexStdin, opts.steerContext));
2511
2629
  }
2512
2630
  else if (cli === 'codex' && isResume) {
2513
2631
  // Resume passes '-' in argv (see args.ts) so the prompt travels on stdin,
2514
2632
  // matching the fresh path.
2515
- child.stdin.write(prompt || '');
2633
+ child.stdin.write(withSteerContext(prompt || '', opts.steerContext));
2516
2634
  }
2517
2635
  child.stdin.end();
2518
2636
  if (!opts.internal)
@@ -3127,7 +3245,7 @@ export function spawnAgent(prompt, opts = {}) {
3127
3245
  ...(agyTotalOutputLen > 0 ? { outputLen: agyTotalOutputLen } : {}),
3128
3246
  }).catch((err) => {
3129
3247
  console.error('[jaw:lifecycle] handleAgentExit failed (CLI):', err.message);
3130
- });
3248
+ }).finally(() => settleExit(scopeKey));
3131
3249
  });
3132
3250
  return { child, promise: resultPromise };
3133
3251
  }