cli-jaw 2.17.46 → 2.17.47
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.
- package/README.md +4 -0
- package/dist/bin/commands/browser-web-ai.js +29 -10
- package/dist/bin/commands/browser-web-ai.js.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/src/agent/claude-runtime-run.js +2 -1
- package/dist/src/agent/claude-runtime-run.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +15 -4
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/pi-runtime.js +103 -86
- package/dist/src/agent/pi-runtime.js.map +1 -1
- package/dist/src/agent/spawn/exit-settle.js +74 -0
- package/dist/src/agent/spawn/exit-settle.js.map +1 -0
- package/dist/src/agent/spawn/kill-reason.js +38 -0
- package/dist/src/agent/spawn/kill-reason.js.map +1 -0
- package/dist/src/agent/spawn/queue.js +8 -0
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn/steer-event.js +33 -0
- package/dist/src/agent/spawn/steer-event.js.map +1 -0
- package/dist/src/agent/spawn.js +157 -173
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/agent/watchdog.js +9 -4
- package/dist/src/agent/watchdog.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-escalation.js +18 -1
- package/dist/src/browser/adaptive-fetch/browser-escalation.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-flow.js +36 -9
- package/dist/src/browser/adaptive-fetch/browser-flow.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-session.js +10 -1
- package/dist/src/browser/adaptive-fetch/browser-session.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/camoufox-session.js +16 -1
- package/dist/src/browser/adaptive-fetch/camoufox-session.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/defuddle-provenance.js +49 -0
- package/dist/src/browser/adaptive-fetch/defuddle-provenance.js.map +1 -0
- package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js +61 -31
- package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/fetcher.js +12 -1
- package/dist/src/browser/adaptive-fetch/fetcher.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/live-smoke-manifest.js +12 -18
- package/dist/src/browser/adaptive-fetch/live-smoke-manifest.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/public-endpoint-normalizers.js +85 -1
- package/dist/src/browser/adaptive-fetch/public-endpoint-normalizers.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/safety.js +5 -3
- package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/scheduler.js +34 -5
- package/dist/src/browser/adaptive-fetch/scheduler.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/tls-fetch.js +54 -33
- package/dist/src/browser/adaptive-fetch/tls-fetch.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/vendor/README.md +33 -1
- package/dist/src/browser/adaptive-fetch/ytdlp-reader.js +54 -9
- package/dist/src/browser/adaptive-fetch/ytdlp-reader.js.map +1 -1
- package/dist/src/browser/web-ai/capability-observation-presets.js +12 -10
- package/dist/src/browser/web-ai/capability-observation-presets.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-model.js +80 -7
- package/dist/src/browser/web-ai/chatgpt-model.js.map +1 -1
- package/dist/src/browser/web-ai/gemini-model.js +18 -0
- package/dist/src/browser/web-ai/gemini-model.js.map +1 -1
- package/dist/src/browser/web-ai/grok-model.js +64 -15
- package/dist/src/browser/web-ai/grok-model.js.map +1 -1
- package/dist/src/code-mode/providers/codex-app.js +83 -0
- package/dist/src/code-mode/providers/codex-app.js.map +1 -1
- package/dist/src/core/config.js +77 -73
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +227 -151
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/settings-merge.js +78 -102
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/discord/bot.js +37 -54
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/discord-file.js +29 -3
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/send-only-client.js +56 -8
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/manager/routes/telegram-hub.js +8 -1
- package/dist/src/manager/routes/telegram-hub.js.map +1 -1
- package/dist/src/manager/telegram-hub/hub-bot.js +7 -1
- package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
- package/dist/src/messaging/delivery-outcome.js +30 -0
- package/dist/src/messaging/delivery-outcome.js.map +1 -1
- package/dist/src/messaging/file-receipt.js +49 -0
- package/dist/src/messaging/file-receipt.js.map +1 -0
- package/dist/src/messaging/native-body.js +31 -0
- package/dist/src/messaging/native-body.js.map +1 -0
- package/dist/src/messaging/queue-notice-record.js +48 -0
- package/dist/src/messaging/queue-notice-record.js.map +1 -0
- package/dist/src/messaging/send.js +13 -2
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/target-reply-guard.js +66 -0
- package/dist/src/messaging/target-reply-guard.js.map +1 -0
- package/dist/src/orchestrator/collect.js +18 -3
- package/dist/src/orchestrator/collect.js.map +1 -1
- package/dist/src/orchestrator/gateway.js +3 -1
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +7 -1
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/session-lanes.js +2 -2
- package/dist/src/orchestrator/session-lanes.js.map +1 -1
- package/dist/src/prompt/builder.js +2 -2
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/routes/i18n.js +9 -4
- package/dist/src/routes/i18n.js.map +1 -1
- package/dist/src/routes/instance.js +8 -4
- package/dist/src/routes/instance.js.map +1 -1
- package/dist/src/routes/jaw-memory.js +5 -5
- package/dist/src/routes/jaw-memory.js.map +1 -1
- package/dist/src/routes/link-preview.js +5 -1
- package/dist/src/routes/link-preview.js.map +1 -1
- package/dist/src/routes/memory.js +9 -7
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/messaging.js +21 -10
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +127 -118
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/settings.js +7 -7
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/system.js +9 -4
- package/dist/src/routes/system.js.map +1 -1
- package/dist/src/slack/bot.js +199 -132
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/events.js +31 -8
- package/dist/src/slack/events.js.map +1 -1
- package/dist/src/slack/ingress.js +3 -1
- package/dist/src/slack/ingress.js.map +1 -1
- package/dist/src/slack/progress-activity.js +24 -3
- package/dist/src/slack/progress-activity.js.map +1 -1
- package/dist/src/slack/progress-lifecycle.js +29 -0
- package/dist/src/slack/progress-lifecycle.js.map +1 -1
- package/dist/src/slack/progress.js +1 -1
- package/dist/src/slack/progress.js.map +1 -1
- package/dist/src/slack/send-only-client.js +16 -3
- package/dist/src/slack/send-only-client.js.map +1 -1
- package/dist/src/slack/slack-file.js +5 -3
- package/dist/src/slack/slack-file.js.map +1 -1
- package/dist/src/slack/workflow.js +134 -0
- package/dist/src/slack/workflow.js.map +1 -0
- package/dist/src/telegram/bot.js +60 -55
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/telegram/rich-message.js +28 -20
- package/dist/src/telegram/rich-message.js.map +1 -1
- package/dist/src/telegram/telegram-file.js +45 -9
- package/dist/src/telegram/telegram-file.js.map +1 -1
- package/dist/src/trace/activity-journal.js +3 -6
- package/dist/src/trace/activity-journal.js.map +1 -1
- package/dist/src/trace/activity-retention.js +29 -0
- package/dist/src/trace/activity-retention.js.map +1 -1
- package/package.json +1 -1
- package/public/dist/.vite/manifest.json +20 -20
- package/public/dist/assets/{ChannelEnablementControl-DwUDKjX9.js → ChannelEnablementControl-egwTtO9-.js} +8 -8
- package/public/dist/assets/{ChannelSetupEntry-DH0AuNNX.js → ChannelSetupEntry-CCrUYd2-.js} +1 -1
- package/public/dist/assets/{ChannelsDiscord-7NmoTjR7.js → ChannelsDiscord-B6rJAsQG.js} +1 -1
- package/public/dist/assets/{ChannelsSlack-Ba0ax6iT.js → ChannelsSlack-CrSIw4Wd.js} +1 -1
- package/public/dist/assets/{ChannelsTelegram-DKA9atf2.js → ChannelsTelegram-DPrdLEnT.js} +1 -1
- package/public/dist/assets/CodeCanvas-D5jp8-t5.js +2 -0
- package/public/dist/assets/{manager-iX7KosWI.js → manager-CFwOAoe0.js} +2 -2
- package/public/dist/assets/{settings-embedding-DIqST-91.js → settings-embedding-DspkJ6sr.js} +1 -1
- package/public/dist/assets/{settings-KIPNUOIV.js → settings-qkC0NuQ7.js} +1 -1
- package/public/dist/manager/index.html +1 -1
- package/public/dist/settings/index.html +1 -1
- package/public/locales/en.json +9 -3
- package/public/locales/ja.json +9 -3
- package/public/locales/ko.json +9 -3
- package/public/locales/zh.json +9 -3
- package/public/manager/src/code/CodeWorkbench.tsx +11 -4
- package/public/manager/src/code/code-controller-draft-storage.ts +7 -3
- package/public/manager/src/code/code-controller-drafts.ts +9 -2
- package/public/manager/src/code/code-controller-runtime.ts +125 -12
- package/public/manager/src/code/code-controller-types.ts +2 -0
- package/public/manager/src/code/pending-user-item.ts +5 -0
- package/scripts/ci/windows-unit-manifest.txt +6 -0
- package/public/dist/assets/CodeCanvas-DWsx6sv5.js +0 -2
package/dist/src/agent/spawn.js
CHANGED
|
@@ -32,7 +32,7 @@ import { resolveMainCli, consumePendingBootstrapPrompt, peekPendingBootstrapProm
|
|
|
32
32
|
import { getSessionOwnershipGeneration, isCurrentSessionOwner, persistMainSession, } from './session-persistence.js';
|
|
33
33
|
import { isCompactMarkerRow } from '../core/compact.js';
|
|
34
34
|
import { isRuntimeSettingsMutationInFlight, waitForRuntimeSettingsIdle } from '../core/runtime-settings-gate.js';
|
|
35
|
-
import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers, clearWorkersForScope } from '../orchestrator/worker-registry.js';
|
|
35
|
+
import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers, clearWorkersForScope, cancelWorker } from '../orchestrator/worker-registry.js';
|
|
36
36
|
import { sanitizeWorkerProgressTools } from '../orchestrator/worker-progress.js';
|
|
37
37
|
import { handleAgentExit, setSpawnAgent, setMainMetaHandler } from './lifecycle-handler.js';
|
|
38
38
|
import { buildServicePath } from '../core/runtime-path.js';
|
|
@@ -84,8 +84,6 @@ export const activeProcesses = new Map(); // agentId → child process
|
|
|
84
84
|
export function hasActiveAgent(agentId) {
|
|
85
85
|
return activeProcesses.has(agentId) || hasClaudeWorker(agentId);
|
|
86
86
|
}
|
|
87
|
-
/** Kill reason recorded when a duplicate registration reaps the previous child. */
|
|
88
|
-
const DUP_REGISTRATION_KILL_REASON = 'dup-registration';
|
|
89
87
|
/** Grace before escalating that kill to SIGKILL, matching the sibling kill paths. */
|
|
90
88
|
const DUP_REGISTRATION_KILL_GRACE_MS = 2_000;
|
|
91
89
|
function cancelOwnedPiProcess(child, reason) {
|
|
@@ -173,7 +171,10 @@ export function releaseMainRun(scopeKey, child, ownerGeneration) {
|
|
|
173
171
|
activeMainProcesses.delete(scopeKey);
|
|
174
172
|
return true;
|
|
175
173
|
}
|
|
176
|
-
import { hasChildExited,
|
|
174
|
+
import { hasChildExited, ownProcess } from './spawn/process-kill.js';
|
|
175
|
+
import { DUP_REGISTRATION_KILL_REASON, isLifecycleSteerReason } from './spawn/kill-reason.js';
|
|
176
|
+
import { buildSteerStartedEvent } from './spawn/steer-event.js';
|
|
177
|
+
import { armExitSettle, captureExitSettler, settleCapturedExit, settleExit, waitForExitSettled, } from './spawn/exit-settle.js';
|
|
177
178
|
import { releaseChildOutputAfterExit } from './spawn/exit-drain.js';
|
|
178
179
|
import { clampPendingLine } from './spawn/line-buffer.js';
|
|
179
180
|
import { appendBoundedFullText } from './events/fulltext-bound.js';
|
|
@@ -256,26 +257,32 @@ export function killAgentById(agentId) {
|
|
|
256
257
|
try {
|
|
257
258
|
if (cancelOwnedPiProcess(proc, 'user'))
|
|
258
259
|
return true;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
260
|
+
// Record the stop the way killActiveAgent does for a main run (below). Only
|
|
261
|
+
// the Pi branch above used to stamp a reason, so a deliberately stopped
|
|
262
|
+
// employee on every other runtime reached handleAgentExit with
|
|
263
|
+
// wasKilled=false and was classified as a crash. For an employee with a
|
|
264
|
+
// non-zero exit that is not cosmetic: lifecycle-handler's employee retry
|
|
265
|
+
// branch (`isEmployee && code !== 0 && !wasKilled`) respawns the very turn
|
|
266
|
+
// the caller just cancelled.
|
|
267
|
+
//
|
|
268
|
+
// Live pid only, for the same reason cancelOwnedPiProcess checks: stamping
|
|
269
|
+
// an already-exited pid is how a recycled pid inherits a foreign kill reason.
|
|
270
|
+
if (proc.pid && !hasChildExited(proc))
|
|
271
|
+
killReasons.set(proc.pid, 'user');
|
|
272
|
+
// Same owner as killActiveAgent. The hand-rolled version here escalated on a
|
|
273
|
+
// bare 3s timer with no liveness re-check, so a CLI that traps SIGTERM either
|
|
274
|
+
// survived or, worse, the delayed SIGKILL landed on a recycled PID. Worker
|
|
275
|
+
// stop (orchestrator/distribute.ts, orchestrator/pipeline.ts) reaches this
|
|
276
|
+
// path, which is why it had different lifetime rules from main stop.
|
|
277
|
+
ownProcess(proc).terminate('cancel');
|
|
278
|
+
// Stdio teardown stays on its own timer: it must happen even when the owner
|
|
279
|
+
// short-circuits because the child had already exited.
|
|
280
|
+
const teardown = setTimeout(() => {
|
|
275
281
|
proc.stdin?.destroy();
|
|
276
282
|
proc.stdout?.destroy();
|
|
277
283
|
proc.stderr?.destroy();
|
|
278
|
-
},
|
|
284
|
+
}, DEFAULT_KILL_ESCALATION_MS);
|
|
285
|
+
teardown.unref?.();
|
|
279
286
|
return true;
|
|
280
287
|
}
|
|
281
288
|
catch {
|
|
@@ -360,11 +367,23 @@ function getActiveMainCli(scopeKey) {
|
|
|
360
367
|
const cli = activeMainProcesses.get(scopeKey)?.meta.cli;
|
|
361
368
|
return typeof cli === 'string' ? cli : null;
|
|
362
369
|
}
|
|
363
|
-
|
|
364
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Read this scope's `agentTimeout` block, global keys overlaid by per-CLI keys.
|
|
372
|
+
*
|
|
373
|
+
* `settings` is a live binding from core/config, so this reflects a settings edit made
|
|
374
|
+
* after the turn started — which is what a steer wants: it is deciding how long to wait
|
|
375
|
+
* right now, not how long the run was configured to take when it was spawned.
|
|
376
|
+
*/
|
|
377
|
+
function mergeAgentTimeoutCfg(cli) {
|
|
378
|
+
const raw = settings['agentTimeout'];
|
|
379
|
+
const global = raw && typeof raw === 'object' ? raw : {};
|
|
380
|
+
const perCli = cli && global[cli] && typeof global[cli] === 'object'
|
|
381
|
+
? global[cli] : {};
|
|
382
|
+
return { ...global, ...perCli };
|
|
365
383
|
}
|
|
366
|
-
export function getSteerWaitMsForActiveAgent(
|
|
367
|
-
|
|
384
|
+
export function getSteerWaitMsForActiveAgent(scopeKey = 'default') {
|
|
385
|
+
const configured = mergeAgentTimeoutCfg(getActiveMainCli(scopeKey))['steerWaitMs'];
|
|
386
|
+
return typeof configured === 'number' && configured > 0 ? configured : DEFAULT_STEER_WAIT_MS;
|
|
368
387
|
}
|
|
369
388
|
/** Get kill reason for a process (by PID), consuming it */
|
|
370
389
|
function consumeKillReason(pid) {
|
|
@@ -375,48 +394,10 @@ function consumeKillReason(pid) {
|
|
|
375
394
|
killReasons.delete(pid);
|
|
376
395
|
return reason;
|
|
377
396
|
}
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
// A follow-up spawn could then read history without the salvage row. The barrier
|
|
383
|
-
// is armed at kill time (never at exit-handler entry — that is already too late)
|
|
384
|
-
// and settled by the exit handler's completion, success or failure.
|
|
385
|
-
const exitSettlers = new Map();
|
|
386
|
-
/** Arm the barrier. Idempotent: a repeated steer keeps the first arm. */
|
|
387
|
-
export function armExitSettle(scopeKey) {
|
|
388
|
-
if (exitSettlers.has(scopeKey))
|
|
389
|
-
return;
|
|
390
|
-
let resolve;
|
|
391
|
-
const promise = new Promise(r => { resolve = r; });
|
|
392
|
-
exitSettlers.set(scopeKey, { promise, resolve });
|
|
393
|
-
}
|
|
394
|
-
/** Settle the barrier; a no-op when no steer kill armed it. */
|
|
395
|
-
export function settleExit(scopeKey) {
|
|
396
|
-
const entry = exitSettlers.get(scopeKey);
|
|
397
|
-
if (!entry)
|
|
398
|
-
return;
|
|
399
|
-
exitSettlers.delete(scopeKey);
|
|
400
|
-
entry.resolve();
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Await the armed exit handler's completion, bounded. A timeout releases the
|
|
404
|
-
* waiter and drops the arm — a wedged exit handler must not hang the steer.
|
|
405
|
-
*/
|
|
406
|
-
export function waitForExitSettled(scopeKey, timeoutMs = 5000) {
|
|
407
|
-
const entry = exitSettlers.get(scopeKey);
|
|
408
|
-
if (!entry)
|
|
409
|
-
return Promise.resolve();
|
|
410
|
-
// The timer is NOT unref'd and is always cleared: an unref'd timer can vanish
|
|
411
|
-
// with a drained event loop (test runner), leaving the waiter pending forever.
|
|
412
|
-
let timer;
|
|
413
|
-
const timeout = new Promise(r => { timer = setTimeout(r, timeoutMs); });
|
|
414
|
-
return Promise.race([entry.promise, timeout]).then(() => {
|
|
415
|
-
clearTimeout(timer);
|
|
416
|
-
if (exitSettlers.get(scopeKey) === entry)
|
|
417
|
-
exitSettlers.delete(scopeKey);
|
|
418
|
-
});
|
|
419
|
-
}
|
|
397
|
+
// The steer exit-settle barrier moved to ./spawn/exit-settle.js so it can be imported
|
|
398
|
+
// without this module behind it. Re-exported here because every caller — the HTTP
|
|
399
|
+
// route, the CLI slash handler and eleven test files — imports it from spawn.
|
|
400
|
+
export { armExitSettle, settleExit, waitForExitSettled };
|
|
420
401
|
/**
|
|
421
402
|
* Fix A: 사용자 stop은 메모리 큐 + DB persisted_queue + frontend pending row를
|
|
422
403
|
* 모두 폐기한다. exit handler의 scoped queue 자동 드레인이 stop 직후 잔존
|
|
@@ -428,12 +409,32 @@ export function waitForExitSettled(scopeKey, timeoutMs = 5000) {
|
|
|
428
409
|
* 도 검사하므로, 이걸 비우지 않으면 stop 직후 새 메시지가 busy 분기 → 큐로 떨어지고
|
|
429
410
|
* 프론트는 (1) 낙관 bubble + (2) applyQueuedOverlay 가 만든 queued bubble = 2개를 보여준다.
|
|
430
411
|
*/
|
|
412
|
+
/**
|
|
413
|
+
* Stop the scope's employees, then forget them.
|
|
414
|
+
*
|
|
415
|
+
* Clearing the registry only forgets the slot; it never stopped the child. Claude
|
|
416
|
+
* employees die earlier, inside cancelClaudeScope(..., includeWorkers), so the gap
|
|
417
|
+
* was invisible there — and every OTHER runtime's employee survived a user stop:
|
|
418
|
+
* still running, still streaming into a scope the boss had abandoned, still holding
|
|
419
|
+
* its isolated cwd. The two sibling paths already get this right: orchestrateReset
|
|
420
|
+
* kills before clearing (orchestrator/pipeline.ts) and the worker timeout calls
|
|
421
|
+
* killAgentById (orchestrator/distribute.ts). Only the stop path was missing it.
|
|
422
|
+
*
|
|
423
|
+
* cancelWorker matches reset for the same reason: deleting a slot leaves its worker
|
|
424
|
+
* run row 'running' forever, because finishWorker and failWorker both no-op once the
|
|
425
|
+
* slot is gone. getActiveWorkers returns a fresh array, so killing inside the loop
|
|
426
|
+
* cannot mutate what is being iterated.
|
|
427
|
+
*/
|
|
431
428
|
function clearWorkerSlotsOnStop(scopeKey, reason) {
|
|
432
|
-
const active = getActiveWorkers(scopeKey)
|
|
433
|
-
if (active === 0 && !hasPendingWorkerReplays(scopeKey))
|
|
429
|
+
const active = getActiveWorkers(scopeKey);
|
|
430
|
+
if (active.length === 0 && !hasPendingWorkerReplays(scopeKey))
|
|
434
431
|
return;
|
|
432
|
+
for (const slot of active) {
|
|
433
|
+
killAgentById(slot.agentId);
|
|
434
|
+
cancelWorker(slot.agentId);
|
|
435
|
+
}
|
|
435
436
|
clearWorkersForScope(scopeKey);
|
|
436
|
-
console.log(`[jaw:stop] cleared worker registry (active=${active}, scope=${scopeKey}, reason=${reason})`);
|
|
437
|
+
console.log(`[jaw:stop] stopped and cleared worker registry (active=${active.length}, scope=${scopeKey}, reason=${reason})`);
|
|
437
438
|
}
|
|
438
439
|
function clearMainLiveRunOnStop(scopeKey, reason) {
|
|
439
440
|
if (reason !== 'api' && reason !== 'user' && reason !== 'steer' && reason !== 'interrupt')
|
|
@@ -482,21 +483,22 @@ export function killActiveAgent(scopeKeyOrReason = 'user', scopedReason) {
|
|
|
482
483
|
activeMainProcesses.delete(scopeKey);
|
|
483
484
|
return hadTimer || cancelledPendingMain || cancelledClaude;
|
|
484
485
|
}
|
|
485
|
-
|
|
486
|
-
console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey) || 'unknown'} signal=${policy.signal} escalationMs=${policy.escalationMs}`);
|
|
486
|
+
console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey) || 'unknown'} signal=SIGTERM escalationMs=${DEFAULT_KILL_ESCALATION_MS}`);
|
|
487
487
|
if (activeProcess.pid)
|
|
488
488
|
killReasons.set(activeProcess.pid, reason);
|
|
489
489
|
if (reason === 'steer' || reason === 'interrupt')
|
|
490
490
|
armExitSettle(scopeKey);
|
|
491
491
|
const proc = activeProcess;
|
|
492
|
-
// One owner runs the whole termination: tree walk
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
492
|
+
// One owner runs the whole termination: tree walk, then escalation after the
|
|
493
|
+
// grace that re-checks the ORIGINAL child. The previous escalation guarded on
|
|
494
|
+
// `!proc.killed`, which only records that a signal was delivered — a CLI that
|
|
495
|
+
// traps SIGTERM stays alive with killed set, and was therefore never escalated.
|
|
496
|
+
//
|
|
497
|
+
// No policy override: the owner is memoized on child identity and the FIRST call
|
|
498
|
+
// wins, so a policy passed here would be honoured for a main run and silently
|
|
499
|
+
// dropped for a worker that registerActiveProcess already owns. Taking the
|
|
500
|
+
// default keeps one rule for both.
|
|
501
|
+
ownProcess(proc).terminate(reason === 'steer' ? 'steer' : 'cancel');
|
|
500
502
|
// Immediately sever stdio to stop late output from reaching broadcast handlers
|
|
501
503
|
proc.stdout?.removeAllListeners('data');
|
|
502
504
|
proc.stderr?.removeAllListeners('data');
|
|
@@ -506,7 +508,7 @@ export function killActiveAgent(scopeKeyOrReason = 'user', scopedReason) {
|
|
|
506
508
|
proc.stdin?.destroy();
|
|
507
509
|
proc.stdout?.destroy();
|
|
508
510
|
proc.stderr?.destroy();
|
|
509
|
-
},
|
|
511
|
+
}, DEFAULT_KILL_ESCALATION_MS);
|
|
510
512
|
teardown.unref?.();
|
|
511
513
|
// Fix C1: 사용자 stop/steer 시 해당 scope busy가 즉시 false가 되도록 참조를 동기 해제.
|
|
512
514
|
// 실제 child 종료는 위 setTimeout SIGKILL이 백그라운드에서 마무리.
|
|
@@ -661,10 +663,10 @@ export async function steerAgent(scopeKey, newPrompt, source, meta) {
|
|
|
661
663
|
attempted = true; // A partial recording failure must never retry this input.
|
|
662
664
|
insertMessage.run('user', newPrompt, source, '', workingDir, chatSessionId);
|
|
663
665
|
broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
|
|
664
|
-
broadcast('steer_started',
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
broadcast('steer_started', buildSteerStartedEvent({
|
|
667
|
+
prompt: newPrompt, source, scopeKey, chatSessionId, meta: capturedMeta,
|
|
668
|
+
mode: 'cancel-reprompt', extra: { localDispatch: true },
|
|
669
|
+
}));
|
|
668
670
|
settleOnce(capturedMeta.requestId, 'steered');
|
|
669
671
|
});
|
|
670
672
|
}
|
|
@@ -708,7 +710,9 @@ export async function steerAgent(scopeKey, newPrompt, source, meta) {
|
|
|
708
710
|
}
|
|
709
711
|
insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
|
|
710
712
|
broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
|
|
711
|
-
broadcast('steer_started',
|
|
713
|
+
broadcast('steer_started', buildSteerStartedEvent({
|
|
714
|
+
prompt: newPrompt, source, scopeKey, chatSessionId, meta, mode: 'native-input',
|
|
715
|
+
}));
|
|
712
716
|
settleOnce(meta?.requestId, 'steered');
|
|
713
717
|
return 'steered';
|
|
714
718
|
}
|
|
@@ -751,9 +755,9 @@ export async function steerAgent(scopeKey, newPrompt, source, meta) {
|
|
|
751
755
|
}
|
|
752
756
|
insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
|
|
753
757
|
broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
|
|
754
|
-
broadcast('steer_started',
|
|
755
|
-
|
|
756
|
-
|
|
758
|
+
broadcast('steer_started', buildSteerStartedEvent({
|
|
759
|
+
prompt: newPrompt, source, scopeKey, chatSessionId, meta, mode: 'kill-steer', extra: slackRestart,
|
|
760
|
+
}));
|
|
757
761
|
const { orchestrate, orchestrateContinue, orchestrateReset, isContinueIntent, isResetIntent } = await import('../orchestrator/pipeline.js');
|
|
758
762
|
const origin = source || 'web';
|
|
759
763
|
// Union of both contracts: the #655 steer identity (target/chatId/remoteKey/
|
|
@@ -1308,12 +1312,11 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1308
1312
|
const agyLogFile = cli === 'agy'
|
|
1309
1313
|
? join(os.tmpdir(), `jaw-agy-${agentId || 'main'}-${Date.now()}-${crypto.randomUUID()}.log`)
|
|
1310
1314
|
: null;
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
const mergedTimeoutCfg = { ...globalTimeoutCfg, ...cliTimeoutCfg };
|
|
1315
|
+
// The single agentTimeout parse for this run. Claude, the print watchdog and
|
|
1316
|
+
// codex-app all read THIS object; before #682 the print path reparsed the same
|
|
1317
|
+
// settings a second time, so fixing a timeout knob meant finding which of two
|
|
1318
|
+
// parsers a given runtime happened to use.
|
|
1319
|
+
const mergedTimeoutCfg = mergeAgentTimeoutCfg(cli);
|
|
1317
1320
|
const resolvedAgyPrintTimeoutMs = typeof mergedTimeoutCfg['absoluteHardCapMs'] === 'number'
|
|
1318
1321
|
? mergedTimeoutCfg['absoluteHardCapMs']
|
|
1319
1322
|
: DEFAULT_WATCHDOG_ABSOLUTE_HARD_CAP_MS;
|
|
@@ -1493,7 +1496,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1493
1496
|
cancelling: reason => {
|
|
1494
1497
|
if (reason === 'steer' || reason === 'interrupt') {
|
|
1495
1498
|
armExitSettle(scopeKey);
|
|
1496
|
-
capturedExit ??=
|
|
1499
|
+
capturedExit ??= captureExitSettler(scopeKey);
|
|
1497
1500
|
}
|
|
1498
1501
|
},
|
|
1499
1502
|
exit: { cli, model: runtimeModel, effectiveProvider, agentLabel, mainManaged, origin, resumeKey, prompt, opts,
|
|
@@ -1535,10 +1538,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1535
1538
|
cleanupClaudeWorker();
|
|
1536
1539
|
}
|
|
1537
1540
|
finally {
|
|
1538
|
-
|
|
1539
|
-
exitSettlers.delete(scopeKey);
|
|
1540
|
-
capturedExit.resolve();
|
|
1541
|
-
}
|
|
1541
|
+
settleCapturedExit(scopeKey, capturedExit);
|
|
1542
1542
|
if (mainManaged && (queued || !activeMainProcesses.has(scopeKey)))
|
|
1543
1543
|
void processQueue(scopeKey);
|
|
1544
1544
|
}
|
|
@@ -1696,7 +1696,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1696
1696
|
stopReason ??= reason;
|
|
1697
1697
|
if (reason === 'steer' || reason === 'interrupt') {
|
|
1698
1698
|
armExitSettle(scopeKey);
|
|
1699
|
-
capturedExit ??=
|
|
1699
|
+
capturedExit ??= captureExitSettler(scopeKey);
|
|
1700
1700
|
}
|
|
1701
1701
|
nativeRun.cancel(reason);
|
|
1702
1702
|
};
|
|
@@ -1823,7 +1823,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1823
1823
|
ctx.sessionId = lease.session.nativeSessionId || null;
|
|
1824
1824
|
const recordedReason = consumeKillReason(lease?.child.pid);
|
|
1825
1825
|
const killReason = stopReason || recordedReason;
|
|
1826
|
-
const wasKilled = Boolean(killReason), wasSteer = killReason
|
|
1826
|
+
const wasKilled = Boolean(killReason), wasSteer = isLifecycleSteerReason(killReason);
|
|
1827
1827
|
const code = outcome.status === 'done' ? 0 : outcome.status === 'stopped' ? 130 : 1;
|
|
1828
1828
|
handoffRuntimeOutcome(ctx, outcome);
|
|
1829
1829
|
try {
|
|
@@ -1878,10 +1878,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1878
1878
|
// row running or rewrite a lifecycle that already selected a result.
|
|
1879
1879
|
if (ctx.runtimeOutcome)
|
|
1880
1880
|
closeFailedTrace(ctx.runtimeOutcome);
|
|
1881
|
-
|
|
1882
|
-
exitSettlers.delete(scopeKey);
|
|
1883
|
-
capturedExit.resolve();
|
|
1884
|
-
}
|
|
1881
|
+
settleCapturedExit(scopeKey, capturedExit);
|
|
1885
1882
|
// Exceptional settlement can release the slot before lifecycle requests its normal wake.
|
|
1886
1883
|
if (queueRequested || !activeMainProcesses.has(scopeKey))
|
|
1887
1884
|
void processQueue(scopeKey);
|
|
@@ -2225,7 +2222,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2225
2222
|
console.warn(`[acp:unexpected-exit] code=${code} signal=${signal} sessionId=${ctx.sessionId || 'none'}`);
|
|
2226
2223
|
}
|
|
2227
2224
|
const wasKilled = !!acpKillReason;
|
|
2228
|
-
const wasSteer = acpKillReason
|
|
2225
|
+
const wasSteer = isLifecycleSteerReason(acpKillReason);
|
|
2229
2226
|
flushThinking(); // Flush any remaining thinking buffer
|
|
2230
2227
|
const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, code, cli);
|
|
2231
2228
|
const acpCode = promptCompleted ? 0 : (code ?? 1);
|
|
@@ -2261,13 +2258,8 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2261
2258
|
}
|
|
2262
2259
|
// ─── Pi RPC branch ─────────────────────────────
|
|
2263
2260
|
if (cli === 'pi') {
|
|
2264
|
-
let piExit =
|
|
2265
|
-
const settlePiExit = () => {
|
|
2266
|
-
if (!piExit || exitSettlers.get(scopeKey) !== piExit)
|
|
2267
|
-
return;
|
|
2268
|
-
exitSettlers.delete(scopeKey);
|
|
2269
|
-
piExit.resolve();
|
|
2270
|
-
};
|
|
2261
|
+
let piExit = captureExitSettler(scopeKey);
|
|
2262
|
+
const settlePiExit = () => { settleCapturedExit(scopeKey, piExit); };
|
|
2271
2263
|
const pi = normalizePiSettings(settings["pi"]);
|
|
2272
2264
|
const profileId = cfg.provider || pi.defaultProfileId;
|
|
2273
2265
|
const profile = pi.profiles.find((entry) => entry.id === profileId) || pi.profiles[0];
|
|
@@ -2414,15 +2406,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2414
2406
|
if (!lease) {
|
|
2415
2407
|
if (cancelOwnedPiProcess(child))
|
|
2416
2408
|
return Promise.resolve();
|
|
2417
|
-
|
|
2418
|
-
const pid = child.pid;
|
|
2419
|
-
killProcessTree(pid, 'SIGTERM');
|
|
2420
|
-
setTimeout(() => {
|
|
2421
|
-
killProcessTreeIfAlive(child, pid);
|
|
2422
|
-
}, 5_000);
|
|
2423
|
-
}
|
|
2424
|
-
else
|
|
2425
|
-
child.kill('SIGTERM');
|
|
2409
|
+
ownProcess(child).terminate('cancel');
|
|
2426
2410
|
return Promise.resolve();
|
|
2427
2411
|
}
|
|
2428
2412
|
leaseCancel ??= lease.cancel();
|
|
@@ -2432,7 +2416,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2432
2416
|
if (cleanupDone)
|
|
2433
2417
|
return;
|
|
2434
2418
|
if (reason === 'steer' || reason === 'interrupt')
|
|
2435
|
-
piExit =
|
|
2419
|
+
piExit = captureExitSettler(scopeKey);
|
|
2436
2420
|
void requestCancel();
|
|
2437
2421
|
};
|
|
2438
2422
|
if (lease && mainRun)
|
|
@@ -2521,10 +2505,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2521
2505
|
console.warn('[jaw:pi] exit observer failed');
|
|
2522
2506
|
}
|
|
2523
2507
|
const wasKilled = !!killReason;
|
|
2524
|
-
|
|
2525
|
-
// replacement child already owns this label, so the stale exit handler
|
|
2526
|
-
// must not delete the new child's map entry.
|
|
2527
|
-
const wasSteer = killReason === 'steer' || killReason === DUP_REGISTRATION_KILL_REASON;
|
|
2508
|
+
const wasSteer = isLifecycleSteerReason(killReason);
|
|
2528
2509
|
const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, result.code, cli);
|
|
2529
2510
|
return handleAgentExit({
|
|
2530
2511
|
onRuntimeEnd: (end) => { activity.close(end); },
|
|
@@ -2555,7 +2536,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2555
2536
|
handoffRuntimeOutcome(ctx, failedOutcome);
|
|
2556
2537
|
const killReason = consumeKillReason(child.pid);
|
|
2557
2538
|
const wasKilled = !!killReason;
|
|
2558
|
-
const wasSteer = killReason
|
|
2539
|
+
const wasSteer = isLifecycleSteerReason(killReason);
|
|
2559
2540
|
stopWatchdog();
|
|
2560
2541
|
if (ctx.stderrBuf.length < 4000)
|
|
2561
2542
|
ctx.stderrBuf += err.message;
|
|
@@ -2882,10 +2863,15 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2882
2863
|
if (!opts.internal)
|
|
2883
2864
|
broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
|
|
2884
2865
|
const processExit = { value: null };
|
|
2866
|
+
// The one documented cross-runtime exception. codex-app keeps a 300s idle
|
|
2867
|
+
// bound where the shared watchdog defaults to 90s, because an appserver turn
|
|
2868
|
+
// reports sparse STRUCTURED progress instead of a continuous stream and
|
|
2869
|
+
// nothing here counts stdout as liveness. settings.agentTimeout deliberately
|
|
2870
|
+
// does not narrow it either: a global idleMs written for print CLIs would
|
|
2871
|
+
// cut the default runtime's bound by two thirds. The env overrides remain
|
|
2872
|
+
// the supported knob and are mapped into the watchdog config below.
|
|
2885
2873
|
const idleMs = configuredPositiveMs(process.env["CODEX_APP_TURN_IDLE_MS"], DEFAULT_CODEX_APP_TURN_IDLE_MS);
|
|
2886
2874
|
const absoluteMs = configuredPositiveMs(process.env["CODEX_APP_TURN_ABS_MS"], DEFAULT_CODEX_APP_TURN_ABS_MS);
|
|
2887
|
-
let idleTimer;
|
|
2888
|
-
let absoluteTimer;
|
|
2889
2875
|
let watchdogCancel = null;
|
|
2890
2876
|
let leaseCancel = null;
|
|
2891
2877
|
const requestLeaseCancel = () => {
|
|
@@ -2920,20 +2906,32 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2920
2906
|
};
|
|
2921
2907
|
if (mainRun)
|
|
2922
2908
|
mainRun.steerTurnInBand = steerHook;
|
|
2923
|
-
|
|
2909
|
+
// The same watchdog every other runtime uses. The bespoke timer pair this
|
|
2910
|
+
// replaces never set `ctx.stallReason`, and that field is the ONLY trigger
|
|
2911
|
+
// for the #405 truncation notice (`error-classifier.ts:149-159`). So on the
|
|
2912
|
+
// DEFAULT runtime a timeout surfaced as a generic error and the #405 fix
|
|
2913
|
+
// never applied to the path most turns actually take.
|
|
2914
|
+
const turnWatchdog = attachWatchdog(child, agentLabel, reason => {
|
|
2924
2915
|
if (watchdogCancel)
|
|
2925
2916
|
return;
|
|
2926
|
-
console.warn(`[codex-app:turn] watchdog stall (${
|
|
2917
|
+
console.warn(`[codex-app:turn] watchdog stall (${reason}, idleMs=${idleMs}, absoluteMs=${absoluteMs})`);
|
|
2918
|
+
ctx.stallReason = reason;
|
|
2927
2919
|
watchdogCancel = requestLeaseCancel();
|
|
2928
|
-
rejectTurn(new Error(`Codex AppServer turn ${
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2920
|
+
rejectTurn(new Error(`Codex AppServer turn watchdog timeout: ${reason}`));
|
|
2921
|
+
}, {
|
|
2922
|
+
// This child's stdout IS the JSON-RPC stream and readline already owns
|
|
2923
|
+
// it, so counting raw traffic as liveness would keep a wedged turn
|
|
2924
|
+
// looking alive forever. Progress comes from the turn adapter instead.
|
|
2925
|
+
observeStdio: false,
|
|
2926
|
+
// The two bespoke timers map onto the watchdog's two real gates:
|
|
2927
|
+
// markProgress slides absoluteDeadline to now+absoluteMs, giving
|
|
2928
|
+
// "no progress for idleMs", capped at startedAt+absoluteHardCapMs,
|
|
2929
|
+
// giving "total run beyond absoluteMs". firstProgressMs bounds a turn
|
|
2930
|
+
// that never reports at all.
|
|
2931
|
+
firstProgressMs: idleMs, idleMs, absoluteMs: idleMs, absoluteHardCapMs: absoluteMs,
|
|
2932
|
+
});
|
|
2933
|
+
ctx.stallWatchdog = turnWatchdog;
|
|
2934
|
+
markCodexProgress = () => { turnWatchdog.markProgress(); };
|
|
2937
2935
|
const listener = listenCodexAppTurnAdapter(appClient, lease, laneScope, ctx, {
|
|
2938
2936
|
onProgress: () => { markCodexProgress(); },
|
|
2939
2937
|
onRawNotification: (method, params) => {
|
|
@@ -3043,8 +3041,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3043
3041
|
appClient.kill();
|
|
3044
3042
|
}
|
|
3045
3043
|
finally {
|
|
3046
|
-
|
|
3047
|
-
clearTimeout(absoluteTimer);
|
|
3044
|
+
turnWatchdog.stop();
|
|
3048
3045
|
markCodexProgress = () => { };
|
|
3049
3046
|
if (watchdogCancel)
|
|
3050
3047
|
await watchdogCancel;
|
|
@@ -3072,8 +3069,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3072
3069
|
console.warn(`[codex-app:unexpected-exit] code=${processExit.value.code} signal=${processExit.value.signal} threadId=${ctx.sessionId || 'none'}`);
|
|
3073
3070
|
}
|
|
3074
3071
|
const wasKilled = !!killReason;
|
|
3075
|
-
|
|
3076
|
-
const wasSteer = killReason === 'steer' || killReason === DUP_REGISTRATION_KILL_REASON;
|
|
3072
|
+
const wasSteer = isLifecycleSteerReason(killReason);
|
|
3077
3073
|
flushCodexAppThinking();
|
|
3078
3074
|
const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, exitCode, cli);
|
|
3079
3075
|
await handleAgentExit({
|
|
@@ -3512,10 +3508,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3512
3508
|
console.log(`[jaw:agy] output quiet for ${quietCompletionDelayMs}ms — completing print run`);
|
|
3513
3509
|
killReasons.set(child.pid, AGY_COMPLETE_KILL_REASON);
|
|
3514
3510
|
try {
|
|
3515
|
-
|
|
3516
|
-
setTimeout(() => {
|
|
3517
|
-
killProcessTreeIfAlive(child);
|
|
3518
|
-
}, DEFAULT_KILL_ESCALATION_MS);
|
|
3511
|
+
ownProcess(child).terminate('completion');
|
|
3519
3512
|
}
|
|
3520
3513
|
catch (e) {
|
|
3521
3514
|
console.warn('[jaw:agy] quiet completion kill failed:', e.message);
|
|
@@ -3523,21 +3516,18 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3523
3516
|
}, quietCompletionDelayMs);
|
|
3524
3517
|
};
|
|
3525
3518
|
// ─── Subprocess stall watchdog (Phase 1: #178 OAuth2 stall recovery) ───
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
const cCfg = gCfg[cli] && typeof gCfg[cli] === 'object'
|
|
3530
|
-
? gCfg[cli] : {};
|
|
3531
|
-
const agentTimeoutCfg = { ...gCfg, ...cCfg };
|
|
3519
|
+
// Reads the same mergedTimeoutCfg the Claude branch does. This block used to
|
|
3520
|
+
// reparse settings.agentTimeout on its own, so a timeout knob behaved differently
|
|
3521
|
+
// depending on which parser a runtime happened to reach (#682).
|
|
3532
3522
|
const watchdogConfig = {};
|
|
3533
|
-
if (typeof
|
|
3534
|
-
watchdogConfig.firstProgressMs =
|
|
3535
|
-
if (typeof
|
|
3536
|
-
watchdogConfig.idleMs =
|
|
3537
|
-
if (typeof
|
|
3538
|
-
watchdogConfig.absoluteMs =
|
|
3539
|
-
if (typeof
|
|
3540
|
-
watchdogConfig.absoluteHardCapMs =
|
|
3523
|
+
if (typeof mergedTimeoutCfg['firstProgressMs'] === 'number')
|
|
3524
|
+
watchdogConfig.firstProgressMs = mergedTimeoutCfg['firstProgressMs'];
|
|
3525
|
+
if (typeof mergedTimeoutCfg['idleMs'] === 'number')
|
|
3526
|
+
watchdogConfig.idleMs = mergedTimeoutCfg['idleMs'];
|
|
3527
|
+
if (typeof mergedTimeoutCfg['absoluteMs'] === 'number')
|
|
3528
|
+
watchdogConfig.absoluteMs = mergedTimeoutCfg['absoluteMs'];
|
|
3529
|
+
if (typeof mergedTimeoutCfg['absoluteHardCapMs'] === 'number')
|
|
3530
|
+
watchdogConfig.absoluteHardCapMs = mergedTimeoutCfg['absoluteHardCapMs'];
|
|
3541
3531
|
const stallWatchdog = attachWatchdog(child, agentLabel, (reason) => {
|
|
3542
3532
|
console.log(`[jaw:watchdog] killing ${agentLabel} — ${reason}`);
|
|
3543
3533
|
ctx.stallReason = reason;
|
|
@@ -3547,12 +3537,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3547
3537
|
ctx.stderrBuf = ctx.stderrBuf ? `${ctx.stderrBuf}\n${agyWatchdogContext}` : agyWatchdogContext;
|
|
3548
3538
|
pushTrace(ctx, agyWatchdogContext);
|
|
3549
3539
|
}
|
|
3550
|
-
|
|
3551
|
-
killProcessTree(child.pid, 'SIGTERM');
|
|
3552
|
-
setTimeout(() => {
|
|
3553
|
-
killProcessTreeIfAlive(child);
|
|
3554
|
-
}, 5_000);
|
|
3555
|
-
}
|
|
3540
|
+
ownProcess(child).terminate('stall');
|
|
3556
3541
|
}, watchdogConfig);
|
|
3557
3542
|
ctx.stallWatchdog = stallWatchdog;
|
|
3558
3543
|
let agyTranscriptWatcher = null;
|
|
@@ -3671,8 +3656,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3671
3656
|
if (agyResumeDecision.ok && !agyGuardedStaleDetected && isAgyStaleSessionOutput(text)) {
|
|
3672
3657
|
agyGuardedStaleDetected = true;
|
|
3673
3658
|
console.log('[jaw:agy] stale guarded resume output detected — terminating for fresh retry');
|
|
3674
|
-
|
|
3675
|
-
killProcessTree(child.pid, 'SIGTERM');
|
|
3659
|
+
ownProcess(child).terminate('cancel');
|
|
3676
3660
|
return;
|
|
3677
3661
|
}
|
|
3678
3662
|
if (!ctx.sessionId)
|
|
@@ -3820,7 +3804,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3820
3804
|
const stdKillReason = consumeKillReason(child.pid);
|
|
3821
3805
|
const agyCompletedByQuietOutput = cli === 'agy' && stdKillReason === AGY_COMPLETE_KILL_REASON;
|
|
3822
3806
|
const wasKilled = !!stdKillReason && !agyCompletedByQuietOutput;
|
|
3823
|
-
const wasSteer = stdKillReason
|
|
3807
|
+
const wasSteer = isLifecycleSteerReason(stdKillReason);
|
|
3824
3808
|
if (cli === 'agy' && !ctx.sessionId)
|
|
3825
3809
|
ctx.sessionId = extractAgyConversationId(ctx.fullText);
|
|
3826
3810
|
if (cli === 'agy' && agyLogFile && !ctx.sessionId) {
|