@zhushanwen/subagent-core 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OL5BK4VN.js → chunk-LZJOEDGQ.js} +455 -203
- package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
- package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
- package/dist/execution/engine/engine-discovery-scan.cjs +216 -155
- package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
- package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
- package/dist/execution/engine/engine-discovery-scan.js +1 -1
- package/dist/execution/relay-env.js +5 -4
- package/dist/index.cjs +7686 -5894
- package/dist/index.d.cts +1876 -1360
- package/dist/index.d.ts +1876 -1360
- package/dist/index.js +7048 -5493
- package/dist.bundle/index.cjs +7747 -5957
- package/package.json +4 -4
- package/src/__tests__/manifest-store.test.ts +16 -15
- package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
- package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
- package/src/execution/__tests__/alive-store.test.ts +117 -1
- package/src/execution/__tests__/batch-finalized.test.ts +206 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
- package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
- package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
- package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
- package/src/execution/__tests__/delivery-methods.test.ts +90 -176
- package/src/execution/__tests__/dialog-queue.test.ts +70 -3
- package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
- package/src/execution/__tests__/execute-nesting.test.ts +8 -5
- package/src/execution/__tests__/execution-record.test.ts +8 -148
- package/src/execution/__tests__/finalize-record.test.ts +295 -129
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
- package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
- package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
- package/src/execution/__tests__/idle-gc.test.ts +60 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
- package/src/execution/__tests__/model-config-service.test.ts +111 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
- package/src/execution/__tests__/notify-ledger.test.ts +42 -1
- package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
- package/src/execution/__tests__/record-binding.test.ts +613 -0
- package/src/execution/__tests__/record-origin.test.ts +318 -0
- package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
- package/src/execution/__tests__/record-store.test.ts +72 -54
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
- package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
- package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
- package/src/execution/__tests__/run-orchestration.test.ts +199 -0
- package/src/execution/__tests__/session-file-gc.test.ts +35 -4
- package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
- package/src/execution/__tests__/state-marker.test.ts +288 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
- package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
- package/src/execution/__tests__/subagent-service.test.ts +17 -215
- package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
- package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
- package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
- package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
- package/src/execution/alive-store.ts +37 -28
- package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
- package/src/execution/config.ts +1 -1
- package/src/execution/conversation-continuation.ts +541 -0
- package/src/execution/dialog-queue.ts +60 -12
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
- package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
- package/src/execution/engine/__tests__/registry.test.ts +0 -1
- package/src/execution/engine/__tests__/routing.test.ts +0 -1
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
- package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
- package/src/execution/engine/client/engine-client.ts +81 -25
- package/src/execution/engine/client/mirror.ts +9 -0
- package/src/execution/engine/client/remote-engine.ts +52 -34
- package/src/execution/engine/client/reverse-router.ts +6 -27
- package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
- package/src/execution/engine/common/capability-gate.ts +27 -0
- package/src/execution/engine/common/data-dir.ts +3 -2
- package/src/execution/engine/common/errors.ts +1 -1
- package/src/execution/engine/common/journal-wiring.ts +18 -9
- package/src/execution/engine/common/run-signals.ts +83 -0
- package/src/execution/engine/d8-compat.ts +1 -1
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
- package/src/execution/engine/host/host-bridge.ts +19 -41
- package/src/execution/engine/host/pi-host-binding.ts +5 -5
- package/src/execution/engine/port.ts +34 -54
- package/src/execution/engine/types.ts +11 -14
- package/src/execution/execution-record.ts +15 -47
- package/src/execution/finalize-record.ts +94 -197
- package/src/execution/idle-gc.ts +9 -3
- package/src/execution/lifecycle-manager.ts +25 -361
- package/src/execution/lifecycle-predicates.ts +22 -19
- package/src/execution/manifest-store.ts +40 -42
- package/src/execution/model-config-service.ts +1 -1
- package/src/execution/notifier.ts +39 -1
- package/src/execution/notify-host.ts +13 -4
- package/src/execution/record-entry.ts +20 -2
- package/src/execution/record-store.ts +925 -160
- package/src/execution/relay-env.ts +8 -3
- package/src/execution/round-supervisor/domain.ts +17 -5
- package/src/execution/round-supervisor/service-binding.test.ts +89 -53
- package/src/execution/round-supervisor/service-binding.ts +71 -21
- package/src/execution/round-supervisor/supervisor.ts +23 -9
- package/src/execution/service/record-access.ts +547 -0
- package/src/execution/service/record-lifecycle.ts +542 -0
- package/src/execution/service/run-orchestration.ts +1647 -0
- package/src/execution/service/service-bootstrap.ts +111 -0
- package/src/execution/service/service-constants.ts +27 -0
- package/src/execution/service/session-baselines.ts +401 -0
- package/src/execution/service/sync-collect-domain.ts +442 -0
- package/src/execution/service/workflow-dispatch.ts +567 -0
- package/src/execution/session-file-gc.ts +11 -8
- package/src/execution/session-reconstructor.ts +44 -1
- package/src/execution/sessions-index.ts +20 -3
- package/src/execution/settled-watchdog.ts +86 -43
- package/src/execution/state-marker.ts +516 -0
- package/src/execution/stream-sink.ts +1 -1
- package/src/execution/subagent-actions-core.ts +53 -31
- package/src/execution/subagent-service.ts +643 -2980
- package/src/execution/subprocess-agent-runner.ts +46 -323
- package/src/execution/types.ts +60 -43
- package/src/execution/ui-request-handler-factory.ts +5 -0
- package/src/execution/worktree-manager.ts +1 -1
- package/src/execution/worktree-registry.ts +1 -1
- package/src/index.ts +6 -5
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
- package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
- package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
- package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
- package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +1 -1
- package/src/orchestration/models/ports.ts +20 -13
- package/src/orchestration/models/types.ts +11 -13
- package/src/orchestration/run-snapshot.ts +18 -22
- package/src/orchestration/worker-message-pump.ts +33 -57
- package/src/shared/atomic-write.ts +10 -8
- package/dist/chunk-T2SYBW3I.js +0 -24
- package/dist/engine-discovery-scan-BXLA8y-z.d.cts +0 -1624
- package/dist/engine-discovery-scan-BXLA8y-z.d.ts +0 -1624
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
- package/src/execution/__tests__/finalized-marker.test.ts +0 -104
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
- package/src/execution/__tests__/tombstone-store.test.ts +0 -73
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
- package/src/execution/finalized-marker.ts +0 -70
- package/src/execution/tombstone-store.ts +0 -72
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/execution/engine/engine-discovery-scan.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as path6 from "path";
|
|
3
3
|
|
|
4
4
|
// src/execution/engine/engine-manifest.ts
|
|
5
5
|
import * as path from "path";
|
|
@@ -247,7 +247,7 @@ function describeValue(value) {
|
|
|
247
247
|
|
|
248
248
|
// src/execution/engine/engine-inspect-package.ts
|
|
249
249
|
import * as fs from "fs";
|
|
250
|
-
import * as
|
|
250
|
+
import * as path3 from "path";
|
|
251
251
|
import { isProtocolVersionCompatible as isProtocolVersionCompatible2 } from "@zhushanwen/subagent-engine-sdk";
|
|
252
252
|
|
|
253
253
|
// src/execution/engine/common/data-dir.ts
|
|
@@ -293,7 +293,239 @@ import {
|
|
|
293
293
|
|
|
294
294
|
// src/execution/engine/client/mirror.ts
|
|
295
295
|
import { getLogger as getLogger2 } from "@zhushanwen/subagent-engine-sdk";
|
|
296
|
-
|
|
296
|
+
|
|
297
|
+
// src/shared/timer-delay.ts
|
|
298
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
299
|
+
function assertSafeTimerDelay(ms, source) {
|
|
300
|
+
if (!Number.isFinite(ms)) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (ms > MAX_TIMER_DELAY_MS) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/execution/ui-interaction-model.ts
|
|
313
|
+
var DIALOG_METHODS = /* @__PURE__ */ new Set([
|
|
314
|
+
"select",
|
|
315
|
+
"confirm",
|
|
316
|
+
"input",
|
|
317
|
+
"editor"
|
|
318
|
+
]);
|
|
319
|
+
function isDialogMethod(method) {
|
|
320
|
+
return DIALOG_METHODS.has(method);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/execution/dialog-queue.ts
|
|
324
|
+
var logger3 = getLogger("subagents");
|
|
325
|
+
var SECONDS_PER_MINUTE = 60;
|
|
326
|
+
var MS_PER_SECOND = 1e3;
|
|
327
|
+
var DEFAULT_DIALOG_TIMEOUT_MINUTES = 30;
|
|
328
|
+
var DEFAULT_DIALOG_TIMEOUT_MS = DEFAULT_DIALOG_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
329
|
+
function resolveDialogTimeoutMs(timeout) {
|
|
330
|
+
const resolved = isValidDialogTimeout(timeout) ? timeout : DEFAULT_DIALOG_TIMEOUT_MS;
|
|
331
|
+
return Math.min(resolved, MAX_TIMER_DELAY_MS);
|
|
332
|
+
}
|
|
333
|
+
function isValidDialogTimeout(timeout) {
|
|
334
|
+
return typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0;
|
|
335
|
+
}
|
|
336
|
+
function dialogTimeoutLogMessage(req, waitedMs) {
|
|
337
|
+
return `[subagents] dialog timed out after ${waitedMs}ms without response (id=${req.id}, method=${req.method}) \u2014 settled as cancelled. Recovery: the requesting agent can continue and re-issue the question (\u91CD\u65B0\u53D1\u8D77\u63D0\u95EE); to wait longer, pass an explicit timeout (ms) on the request.`;
|
|
338
|
+
}
|
|
339
|
+
var DialogGlobalQueue = class {
|
|
340
|
+
/** 等待处理的队列(FIFO)。正在处理的项从 queue shift 出后由 current 持有。 */
|
|
341
|
+
queue = [];
|
|
342
|
+
/** 正在处理的项(handler 已调、未 settle)。用于 rejectChildDialogs 取消占位中的 dialog。 */
|
|
343
|
+
current;
|
|
344
|
+
processing = false;
|
|
345
|
+
/**
|
|
346
|
+
* 入队一个 UI 请求,返回 Promise<UiResponse>。
|
|
347
|
+
*
|
|
348
|
+
* 调用方约定:只对 dialog 类(isDialogMethod===true)调 enqueue。fire-and-forget 由
|
|
349
|
+
* 调用方(ui-request-handler-factory.ts)在 enqueue 前判 isDialogMethod 后直接调 handler,
|
|
350
|
+
* 不经过本队列。enqueue 内仍防御性兼容 fire-and-forget(万一调用方未判):直接调 handler 返回,
|
|
351
|
+
* 不入队串行,但调用方不应依赖此防御行为。
|
|
352
|
+
*
|
|
353
|
+
* dialog 项处理(TC-E4 case 1):进队列 FIFO 串行,等前一个 settle 后才调 handler
|
|
354
|
+
*(争输入焦点,防并发弹窗)。
|
|
355
|
+
*
|
|
356
|
+
* handler 抛错兜底:catch → 回 {cancelled:true}(dialog 路径,队列不卡死)。
|
|
357
|
+
* SR-4:opts.child 用于 rejectChildDialogs 关联(dialog 项会被批量 reject,含 current)。
|
|
358
|
+
*
|
|
359
|
+
* @param req UI 请求(约定只传 dialog 类;fire-and-forget 防御性兼容)
|
|
360
|
+
* @param handler 真正执行请求的 handler(TUI/GUI 模式分流后的 realHandler)
|
|
361
|
+
* @param opts 可选 child 引用(用于 rejectChildDialogs 关联)
|
|
362
|
+
* @returns handler 的响应;dialog 抛错时回 {cancelled:true};child close 时回 {cancelled:true}
|
|
363
|
+
*/
|
|
364
|
+
enqueue(req, handler, opts) {
|
|
365
|
+
if (!isDialogMethod(req.method)) {
|
|
366
|
+
return handler(req);
|
|
367
|
+
}
|
|
368
|
+
return new Promise((resolve4) => {
|
|
369
|
+
this.queue.push({
|
|
370
|
+
req,
|
|
371
|
+
handler,
|
|
372
|
+
resolve: resolve4,
|
|
373
|
+
childPid: opts?.child?.pid,
|
|
374
|
+
settled: false
|
|
375
|
+
});
|
|
376
|
+
void this.processNext();
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* settle 一个 item(幂等)。handler 完成 / rejectChildDialogs / rejectAll / 超时 timer
|
|
381
|
+
* 都通过本方法,settled 标志保证只 settle 一次(防竞争)。
|
|
382
|
+
*
|
|
383
|
+
* #19 单一推进点:本方法 settle Promise + 清状态后,**唯一**调 processNext 推进队列。
|
|
384
|
+
* processNext 尾部不再调 processNext(旧代码双重推进,虽靠 processing 标志幂等,但语义混乱)。
|
|
385
|
+
* 为什么推进必须在 settleItem 而非 processNext 尾部:rejectChildDialogs 取消一个永不 settle
|
|
386
|
+
* 的 current(handler 等用户输入卡死)时,processNext 的 `await item.handler` 永不 resume,
|
|
387
|
+
* 尾部不会执行;只有 settleItem 里的 processNext 才能打破死锁,推进下一个。
|
|
388
|
+
*
|
|
389
|
+
* [A2-2] 超时 timer 清理也统一收口在这里:任何路径抢先 settle(reject/rejectAll/超时
|
|
390
|
+
* 自身)都必须撤下 armed timer——handler 永挂时 processNext 内联的 clearTimeout 不可达,
|
|
391
|
+
* 不在这里清则 timer 到期触发虚假「dialog timed out」warn 且句柄滞留至超时点。
|
|
392
|
+
*/
|
|
393
|
+
settleItem(item, resp) {
|
|
394
|
+
if (item.settled) return;
|
|
395
|
+
item.settled = true;
|
|
396
|
+
if (item.timeoutTimer) {
|
|
397
|
+
clearTimeout(item.timeoutTimer);
|
|
398
|
+
item.timeoutTimer = void 0;
|
|
399
|
+
}
|
|
400
|
+
item.resolve(resp);
|
|
401
|
+
if (this.current === item) {
|
|
402
|
+
this.current = void 0;
|
|
403
|
+
this.processing = false;
|
|
404
|
+
void this.processNext();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* SR-4:把指定 child 的所有 pending dialog resolve 为 {cancelled:true}。
|
|
409
|
+
*
|
|
410
|
+
* 触发场景:子进程 close(用户取消 / crash / 超时 kill)时,其 pending dialog 的 handler
|
|
411
|
+
* 可能永不 settle(等用户输入),导致 Promise 永挂 + 内存泄漏。本方法批量清理。
|
|
412
|
+
*
|
|
413
|
+
* 处理范围(TC-E4 case 2):
|
|
414
|
+
* - 正在处理中(current)的该 child 项:settle {cancelled:true},解阻塞队列推进下一个
|
|
415
|
+
* (关键:handler 可能永不 settle,必须由这里打破死锁)
|
|
416
|
+
* - 队列中等待处理的该 child 项:settle {cancelled:true} 并移除
|
|
417
|
+
*
|
|
418
|
+
* 不影响其他 child 的 pending dialog(TC-E4 case 2 子测试 2)。
|
|
419
|
+
*/
|
|
420
|
+
rejectChildDialogs(child) {
|
|
421
|
+
const matched = [];
|
|
422
|
+
if (this.queue.length > 0) {
|
|
423
|
+
const remaining = [];
|
|
424
|
+
for (const item of this.queue) {
|
|
425
|
+
if (item.childPid === child.pid) {
|
|
426
|
+
matched.push(item);
|
|
427
|
+
} else {
|
|
428
|
+
remaining.push(item);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
this.queue = remaining;
|
|
432
|
+
}
|
|
433
|
+
if (this.current && this.current.childPid === child.pid) {
|
|
434
|
+
this.settleItem(this.current, { cancelled: true });
|
|
435
|
+
}
|
|
436
|
+
for (const item of matched) {
|
|
437
|
+
this.settleItem(item, { cancelled: true });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 处理队列下一项(FIFO)。
|
|
442
|
+
*
|
|
443
|
+
* processing 标志保证串行:handler 运行期间 processing=true,新的 processNext 调用直接返回;
|
|
444
|
+
* handler settle 后由 settleItem 清 processing=false 并推进下一项(#19 单一推进点)。
|
|
445
|
+
*
|
|
446
|
+
* handler 抛错兜底(TC-E4 case 3):catch → settle {cancelled:true} → 继续。
|
|
447
|
+
* 不能让一个失败卡死队列(processing 永远 true)。
|
|
448
|
+
*
|
|
449
|
+
* LC-3/T2⑦ 超时上界:`await item.handler` 原本无上界——host UI promise 挂死或用户
|
|
450
|
+
* 永不回答时 processing 恒 true(全局 dialog 死锁)。现在每项挂队列级 timer:
|
|
451
|
+
* req.timeout(请求方显式传值)优先,未传/非法挂 DEFAULT_DIALOG_TIMEOUT_MS。
|
|
452
|
+
* 到点 settle {cancelled:true}(完整错误消息落父进程日志,含恢复指引与等待时长),
|
|
453
|
+
* settleItem 的 settled 标志保证与 handler 完成 / rejectChildDialogs 三方竞态下
|
|
454
|
+
* 恰 settle 一次。timer 回调闭包捕获 item(非读 this.current):迟到触发时
|
|
455
|
+
* settled 标志已置位,直接 noop,不误伤后继项。
|
|
456
|
+
*/
|
|
457
|
+
async processNext() {
|
|
458
|
+
if (this.processing) return;
|
|
459
|
+
if (this.queue.length === 0) return;
|
|
460
|
+
this.processing = true;
|
|
461
|
+
const item = this.queue.shift();
|
|
462
|
+
this.current = item;
|
|
463
|
+
const timeoutMs = resolveDialogTimeoutMs(item.req.timeout);
|
|
464
|
+
const timer = setTimeout(() => {
|
|
465
|
+
if (item.settled) return;
|
|
466
|
+
logger3.warn(dialogTimeoutLogMessage(item.req, timeoutMs));
|
|
467
|
+
this.settleItem(item, { cancelled: true });
|
|
468
|
+
}, timeoutMs);
|
|
469
|
+
item.timeoutTimer = timer;
|
|
470
|
+
try {
|
|
471
|
+
const resp = await item.handler(item.req);
|
|
472
|
+
clearTimeout(timer);
|
|
473
|
+
this.settleItem(item, resp);
|
|
474
|
+
} catch {
|
|
475
|
+
clearTimeout(timer);
|
|
476
|
+
this.settleItem(item, { cancelled: true });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* #10:把所有 pending dialog(queue + current)全部 settle 为 {cancelled:true},
|
|
481
|
+
* 并清空 queue/current/processing 状态。session_shutdown 调用,保证不留永挂 Promise。
|
|
482
|
+
*
|
|
483
|
+
* 约定签名:rejectAll(): void(无参,返 void)。Group C 的 index.ts session_shutdown 依赖此契约。
|
|
484
|
+
*
|
|
485
|
+
* 幂等:依赖 settleItem 的 settled 标志——重复调用只会对已 settled 项 noop。
|
|
486
|
+
* 顺序敏感(#19 推进点在 settleItem):必须先清空 queue 数组再 settle current,
|
|
487
|
+
* 否则 settleItem(current) 同步触发的 processNext 会从旧 queue 抢占下一项作为新 current,
|
|
488
|
+
* 避开本方法的 cancel 语义。清空后 processNext 看到空队列直接返回,新 current 不会被抢占。
|
|
489
|
+
*
|
|
490
|
+
* 单 session 假设(M-2):见类注释。本方法清空所有 pending 不分 session——依赖 Pi 单进程
|
|
491
|
+
* 单 session 串行保证。session_shutdown handler(index.ts)调用本方法时,进程内只会有当前
|
|
492
|
+
* session 的 pending dialog。多 session 并发场景的迁移策略(rejectAllForSession)见类注释。
|
|
493
|
+
*/
|
|
494
|
+
rejectAll() {
|
|
495
|
+
const items = this.queue;
|
|
496
|
+
this.queue = [];
|
|
497
|
+
if (this.current) {
|
|
498
|
+
this.settleItem(this.current, { cancelled: true });
|
|
499
|
+
}
|
|
500
|
+
for (const item of items) {
|
|
501
|
+
this.settleItem(item, { cancelled: true });
|
|
502
|
+
}
|
|
503
|
+
this.current = void 0;
|
|
504
|
+
this.processing = false;
|
|
505
|
+
}
|
|
506
|
+
/** 清空队列状态(仅在 rejectAll 之后调用)。pending Promise 必须先由 rejectAll settle。
|
|
507
|
+
* 不 settle Promise 的纯状态重置——单独调用会导致 Promise 永挂(footgun),故设为 private。
|
|
508
|
+
* 外部调用方应使用 rejectAll()(它 settle 所有 pending + 重置状态,是原子操作)。 */
|
|
509
|
+
resetState() {
|
|
510
|
+
this.queue = [];
|
|
511
|
+
this.current = void 0;
|
|
512
|
+
this.processing = false;
|
|
513
|
+
}
|
|
514
|
+
/** 当前队列长度(测试/诊断用)。含等待处理项(不含 current)。 */
|
|
515
|
+
get size() {
|
|
516
|
+
return this.queue.length;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
var activeDialogQueue;
|
|
520
|
+
function registerActiveDialogQueue(queue) {
|
|
521
|
+
activeDialogQueue = queue;
|
|
522
|
+
}
|
|
523
|
+
function notifyChildProcessExited(pid) {
|
|
524
|
+
activeDialogQueue?.rejectChildDialogs({ pid });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/execution/engine/client/mirror.ts
|
|
528
|
+
var logger4 = getLogger2("subagents");
|
|
297
529
|
var SpawnedChildrenMirror = class {
|
|
298
530
|
entries = /* @__PURE__ */ new Map();
|
|
299
531
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -322,6 +554,7 @@ var SpawnedChildrenMirror = class {
|
|
|
322
554
|
};
|
|
323
555
|
this.entries.set(patch.pid, entry);
|
|
324
556
|
this.emit({ reason: "childStateChanged", pid: patch.pid, recordId: patch.recordId });
|
|
557
|
+
if (patch.state === "exited") notifyChildProcessExited(patch.pid);
|
|
325
558
|
}
|
|
326
559
|
/**
|
|
327
560
|
* 失效语义 2 + 3:全部镜像项整体置死(killed=true + state=exited)→ 逐项广播 →
|
|
@@ -337,6 +570,7 @@ var SpawnedChildrenMirror = class {
|
|
|
337
570
|
entry.state = "exited";
|
|
338
571
|
entry.updatedAt = Date.now();
|
|
339
572
|
this.emit({ reason: "killedAll", pid: entry.pid, recordId: entry.recordId });
|
|
573
|
+
notifyChildProcessExited(entry.pid);
|
|
340
574
|
}
|
|
341
575
|
this.entries.clear();
|
|
342
576
|
}
|
|
@@ -378,7 +612,7 @@ var SpawnedChildrenMirror = class {
|
|
|
378
612
|
try {
|
|
379
613
|
listener2(event);
|
|
380
614
|
} catch (err) {
|
|
381
|
-
|
|
615
|
+
logger4.debug(
|
|
382
616
|
`[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
|
|
383
617
|
);
|
|
384
618
|
}
|
|
@@ -391,22 +625,22 @@ import { spawnSync } from "child_process";
|
|
|
391
625
|
import { readdirSync, readFileSync, renameSync, rmSync, writeFileSync, mkdirSync } from "fs";
|
|
392
626
|
import { join as join2 } from "path";
|
|
393
627
|
import { getLogger as getLogger3, resolveEngineDir } from "@zhushanwen/subagent-engine-sdk";
|
|
394
|
-
var
|
|
628
|
+
var logger5 = getLogger3("subagents");
|
|
395
629
|
var CMDLINE_PROBE_TIMEOUT_MS = 3e3;
|
|
396
630
|
var PIDFILE_JSON_INDENT = 2;
|
|
397
631
|
function pidfilePath(engineDataDir, engineId, hostKind, hostPid) {
|
|
398
632
|
return join2(resolveEngineDir(engineDataDir, engineId), `engine.${hostKind}.${hostPid}.pid`);
|
|
399
633
|
}
|
|
400
|
-
function writePidfileAtomic(
|
|
401
|
-
mkdirSync(join2(
|
|
402
|
-
const tmp = `${
|
|
634
|
+
function writePidfileAtomic(path7, content) {
|
|
635
|
+
mkdirSync(join2(path7, ".."), { recursive: true });
|
|
636
|
+
const tmp = `${path7}.${process.pid}.${Date.now()}.tmp`;
|
|
403
637
|
writeFileSync(tmp, `${JSON.stringify(content, null, PIDFILE_JSON_INDENT)}
|
|
404
638
|
`, "utf-8");
|
|
405
|
-
renameSync(tmp,
|
|
639
|
+
renameSync(tmp, path7);
|
|
406
640
|
}
|
|
407
|
-
function readPidfile(
|
|
641
|
+
function readPidfile(path7) {
|
|
408
642
|
try {
|
|
409
|
-
const raw = readFileSync(
|
|
643
|
+
const raw = readFileSync(path7, "utf-8");
|
|
410
644
|
const parsed = JSON.parse(raw);
|
|
411
645
|
if (typeof parsed.enginePid !== "number" || typeof parsed.hostPid !== "number" || typeof parsed.engineId !== "string" || typeof parsed.hostKind !== "string") {
|
|
412
646
|
return void 0;
|
|
@@ -420,21 +654,21 @@ function readPidfile(path6) {
|
|
|
420
654
|
writtenAt: typeof parsed.writtenAt === "string" ? parsed.writtenAt : ""
|
|
421
655
|
};
|
|
422
656
|
} catch (err) {
|
|
423
|
-
|
|
657
|
+
logger5.debug(`[pid-file] readPidfile(${path7}) failed, treating as stale: ${err instanceof Error ? err.message : String(err)}`);
|
|
424
658
|
return void 0;
|
|
425
659
|
}
|
|
426
660
|
}
|
|
427
|
-
function removePidfile(
|
|
661
|
+
function removePidfile(path7) {
|
|
428
662
|
try {
|
|
429
|
-
rmSync(
|
|
663
|
+
rmSync(path7, { force: true });
|
|
430
664
|
} catch (err) {
|
|
431
|
-
|
|
665
|
+
logger5.debug(`[pid-file] removePidfile(${path7}) failed (concurrent sweep = expected): ${err instanceof Error ? err.message : String(err)}`);
|
|
432
666
|
}
|
|
433
667
|
}
|
|
434
668
|
function registerEnginePidfile(reg) {
|
|
435
|
-
const
|
|
669
|
+
const path7 = pidfilePath(reg.engineDataDir, reg.engineId, reg.hostKind, reg.hostPid);
|
|
436
670
|
try {
|
|
437
|
-
writePidfileAtomic(
|
|
671
|
+
writePidfileAtomic(path7, {
|
|
438
672
|
enginePid: reg.enginePid,
|
|
439
673
|
hostPid: reg.hostPid,
|
|
440
674
|
engineStartTime: readProcessStartTime(reg.enginePid) ?? null,
|
|
@@ -442,9 +676,9 @@ function registerEnginePidfile(reg) {
|
|
|
442
676
|
hostKind: reg.hostKind,
|
|
443
677
|
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
444
678
|
});
|
|
445
|
-
return
|
|
679
|
+
return path7;
|
|
446
680
|
} catch (err) {
|
|
447
|
-
|
|
681
|
+
logger5.warn(
|
|
448
682
|
`[pid-file] pidfile write failed for ${reg.engineId} (sweep coverage degraded): ${err instanceof Error ? err.message : String(err)}`
|
|
449
683
|
);
|
|
450
684
|
return void 0;
|
|
@@ -454,7 +688,7 @@ function sweepEnginePidfiles(args) {
|
|
|
454
688
|
try {
|
|
455
689
|
return sweepStalePidfiles(args);
|
|
456
690
|
} catch (err) {
|
|
457
|
-
|
|
691
|
+
logger5.warn(
|
|
458
692
|
`[pid-file] pidfile sweep failed for ${args.engineId} (conservative skip): ${err instanceof Error ? err.message : String(err)}`
|
|
459
693
|
);
|
|
460
694
|
return { killed: [], removed: [], skipped: [] };
|
|
@@ -512,10 +746,10 @@ function sweepStalePidfiles(opts) {
|
|
|
512
746
|
}
|
|
513
747
|
return result;
|
|
514
748
|
}
|
|
515
|
-
function sweepOnePidfile(
|
|
516
|
-
const content = readPidfile(
|
|
749
|
+
function sweepOnePidfile(path7, file, opts, platform, result) {
|
|
750
|
+
const content = readPidfile(path7);
|
|
517
751
|
if (content === void 0) {
|
|
518
|
-
removePidfile(
|
|
752
|
+
removePidfile(path7);
|
|
519
753
|
result.removed.push({ file, reason: "unreadable-or-corrupt" });
|
|
520
754
|
return;
|
|
521
755
|
}
|
|
@@ -532,11 +766,11 @@ function sweepOnePidfile(path6, file, opts, platform, result) {
|
|
|
532
766
|
result.skipped.push({ file, reason: "host-pid-alive" });
|
|
533
767
|
return;
|
|
534
768
|
}
|
|
535
|
-
reapDeadHostEngine(
|
|
769
|
+
reapDeadHostEngine(path7, file, content, opts, platform, result);
|
|
536
770
|
}
|
|
537
|
-
function reapDeadHostEngine(
|
|
771
|
+
function reapDeadHostEngine(path7, file, content, opts, platform, result) {
|
|
538
772
|
if (platform === "win32") {
|
|
539
|
-
removePidfile(
|
|
773
|
+
removePidfile(path7);
|
|
540
774
|
result.removed.push({ file, reason: "stale (host dead); win32 never kills" });
|
|
541
775
|
return;
|
|
542
776
|
}
|
|
@@ -546,7 +780,7 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
|
|
|
546
780
|
return;
|
|
547
781
|
}
|
|
548
782
|
if (!engineAlive) {
|
|
549
|
-
removePidfile(
|
|
783
|
+
removePidfile(path7);
|
|
550
784
|
result.removed.push({ file, reason: "engine-pid-dead" });
|
|
551
785
|
return;
|
|
552
786
|
}
|
|
@@ -556,7 +790,7 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
|
|
|
556
790
|
return;
|
|
557
791
|
}
|
|
558
792
|
if (!opts.matchesEngineCmdline(cmdline)) {
|
|
559
|
-
removePidfile(
|
|
793
|
+
removePidfile(path7);
|
|
560
794
|
result.removed.push({ file, reason: "cmdline-mismatch (pid reused by unrelated process)" });
|
|
561
795
|
return;
|
|
562
796
|
}
|
|
@@ -566,13 +800,13 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
|
|
|
566
800
|
return;
|
|
567
801
|
}
|
|
568
802
|
if (content.engineStartTime === null || content.engineStartTime !== startTime) {
|
|
569
|
-
removePidfile(
|
|
803
|
+
removePidfile(path7);
|
|
570
804
|
result.removed.push({ file, reason: "engine-start-time-mismatch (pid reused)" });
|
|
571
805
|
return;
|
|
572
806
|
}
|
|
573
807
|
const killEngine = opts.killEngine ?? defaultKillEngineProcessGroup;
|
|
574
808
|
killEngine(content.enginePid);
|
|
575
|
-
removePidfile(
|
|
809
|
+
removePidfile(path7);
|
|
576
810
|
result.killed.push(content.enginePid);
|
|
577
811
|
result.removed.push({ file, reason: "swept after kill (stale orphan)" });
|
|
578
812
|
}
|
|
@@ -581,12 +815,12 @@ function defaultKillEngineProcessGroup(enginePid) {
|
|
|
581
815
|
process.kill(-enginePid, "SIGKILL");
|
|
582
816
|
return;
|
|
583
817
|
} catch (groupErr) {
|
|
584
|
-
|
|
818
|
+
logger5.debug(`[pid-file] process-group kill(-${enginePid}) failed, falling back to single kill: ${groupErr instanceof Error ? groupErr.message : String(groupErr)}`);
|
|
585
819
|
}
|
|
586
820
|
try {
|
|
587
821
|
process.kill(enginePid, "SIGKILL");
|
|
588
822
|
} catch (err) {
|
|
589
|
-
|
|
823
|
+
logger5.debug(`[pid-file] single kill(${enginePid}) failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`);
|
|
590
824
|
}
|
|
591
825
|
}
|
|
592
826
|
|
|
@@ -596,7 +830,7 @@ import {
|
|
|
596
830
|
REVERSE_CHANNEL_TIMEOUT_CLASS,
|
|
597
831
|
REVERSE_REQUEST_TIMEOUT_MS
|
|
598
832
|
} from "@zhushanwen/subagent-engine-sdk";
|
|
599
|
-
var
|
|
833
|
+
var logger6 = getLogger4("subagents");
|
|
600
834
|
function routeReverseRequest(deps, frame) {
|
|
601
835
|
if (frame.method === "host/askUser") {
|
|
602
836
|
handleInteractionRequest(deps, frame, deps.uiRequestHandler, (params) => params.request);
|
|
@@ -631,7 +865,7 @@ function handleInteractionRequest(deps, frame, handler, extract) {
|
|
|
631
865
|
const result = await handler(input);
|
|
632
866
|
if (!deps.isDisposed()) deps.sendResponse(frame.id, result);
|
|
633
867
|
} catch (err) {
|
|
634
|
-
|
|
868
|
+
logger6.warn(
|
|
635
869
|
`[engine-client:${deps.engineId}] ${frame.method} handler failed, answering cancelled: ${err instanceof Error ? err.message : String(err)}`
|
|
636
870
|
);
|
|
637
871
|
if (!deps.isDisposed()) deps.sendResponse(frame.id, { cancelled: true });
|
|
@@ -642,7 +876,7 @@ function handleDataPlaneRequest(deps, frame) {
|
|
|
642
876
|
let settled = false;
|
|
643
877
|
const guardTimer = setTimeout(() => {
|
|
644
878
|
if (settled) return;
|
|
645
|
-
|
|
879
|
+
logger6.error(
|
|
646
880
|
`[engine-client:${deps.engineId}] data-plane reverse request ${frame.method} unanswered for ${REVERSE_REQUEST_TIMEOUT_MS}ms \u2014 treating as engine fault`
|
|
647
881
|
);
|
|
648
882
|
void deps.failEngine(
|
|
@@ -658,7 +892,7 @@ function handleDataPlaneRequest(deps, frame) {
|
|
|
658
892
|
(err) => {
|
|
659
893
|
settled = true;
|
|
660
894
|
clearTimeout(guardTimer);
|
|
661
|
-
|
|
895
|
+
logger6.warn(
|
|
662
896
|
`[engine-client:${deps.engineId}] ${frame.method} dispatch failed (answering ok, host-side bug): ${err instanceof Error ? err.message : String(err)}`
|
|
663
897
|
);
|
|
664
898
|
deps.sendResponse(frame.id, { ok: true });
|
|
@@ -668,7 +902,6 @@ function handleDataPlaneRequest(deps, frame) {
|
|
|
668
902
|
var DATA_PLANE_HANDLERS = {
|
|
669
903
|
"host/log": (deps, params) => dispatchLog(deps, params),
|
|
670
904
|
"host/streamDelta": (deps, params) => dispatchStreamDelta(deps, params),
|
|
671
|
-
"host/roundLifecycle": (deps, params) => dispatchRoundLifecycle(deps, params),
|
|
672
905
|
"host/poolResolved": (deps, params) => dispatchPoolResolved(deps, params),
|
|
673
906
|
"host/handleReady": (deps, params) => dispatchHandleReady(deps, params),
|
|
674
907
|
"host/childSpawned": (deps, params) => dispatchChildSpawned(deps, params),
|
|
@@ -679,22 +912,12 @@ async function dispatchDataPlane(deps, method, params) {
|
|
|
679
912
|
}
|
|
680
913
|
function dispatchLog(deps, p) {
|
|
681
914
|
if (deps.log !== void 0) deps.log(p);
|
|
682
|
-
else
|
|
915
|
+
else logger6[p.level](`[engine:${p.component}] ${p.message}`, p.data);
|
|
683
916
|
}
|
|
684
917
|
async function dispatchStreamDelta(deps, p) {
|
|
685
|
-
if (p.
|
|
686
|
-
await deps.recordRoutes.get(p.recordId)?.onStreamDelta?.(p.delta);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
918
|
+
if (p.runId === void 0) return;
|
|
689
919
|
await deps.runRoutes.get(p.runId)?.onStreamDelta?.(p.delta);
|
|
690
920
|
}
|
|
691
|
-
async function dispatchRoundLifecycle(deps, p) {
|
|
692
|
-
if (p.recordId !== void 0) {
|
|
693
|
-
await deps.recordRoutes.get(p.recordId)?.onRoundLifecycle?.(p);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
await deps.runRoutes.get(p.runId)?.onRoundLifecycle?.(p);
|
|
697
|
-
}
|
|
698
921
|
async function dispatchPoolResolved(deps, p) {
|
|
699
922
|
await deps.runRoutes.get(p.runId)?.onPoolResolved?.(p.poolKey);
|
|
700
923
|
}
|
|
@@ -710,11 +933,11 @@ function dispatchChildSpawned(deps, p) {
|
|
|
710
933
|
if (process.platform !== "win32" && typeof p.pid === "number") {
|
|
711
934
|
try {
|
|
712
935
|
process.kill(-p.pid, 0);
|
|
713
|
-
|
|
936
|
+
logger6.warn(
|
|
714
937
|
`[engine-client:${deps.engineId}] childSpawned pid ${p.pid} leads its own process group (not in the engine harvest group \u2014 one-generation children + in-group descendants only; engine-detached descendants are an accepted cost per design \xA73.9)`
|
|
715
938
|
);
|
|
716
939
|
} catch (err) {
|
|
717
|
-
|
|
940
|
+
logger6.debug(
|
|
718
941
|
`[engine-client:${deps.engineId}] childSpawned pid ${p.pid} group probe settled (${err instanceof Error ? err.message : String(err)})`
|
|
719
942
|
);
|
|
720
943
|
}
|
|
@@ -736,7 +959,7 @@ import { basename } from "path";
|
|
|
736
959
|
import {
|
|
737
960
|
getLogger as getLogger5
|
|
738
961
|
} from "@zhushanwen/subagent-engine-sdk";
|
|
739
|
-
var
|
|
962
|
+
var logger7 = getLogger5("subagents");
|
|
740
963
|
function defaultEngineCmdlineMatcher(command) {
|
|
741
964
|
const base = basename(command);
|
|
742
965
|
return (cmdline) => {
|
|
@@ -749,7 +972,7 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
|
|
|
749
972
|
const answered = JSON.stringify(result.capabilities ?? null);
|
|
750
973
|
const declared = JSON.stringify(diag.capabilities);
|
|
751
974
|
if (answered !== declared) {
|
|
752
|
-
|
|
975
|
+
logger7.warn(
|
|
753
976
|
`[engine-client:${engineId}] initialize capabilities differ from manifest (diagnostic only): manifest=${declared} answered=${answered}`
|
|
754
977
|
);
|
|
755
978
|
}
|
|
@@ -758,7 +981,7 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
|
|
|
758
981
|
const answered = JSON.stringify(result.models ?? null);
|
|
759
982
|
const declared = JSON.stringify(diag.models ?? null);
|
|
760
983
|
if (answered !== declared) {
|
|
761
|
-
|
|
984
|
+
logger7.warn(
|
|
762
985
|
`[engine-client:${engineId}] initialize models differ from manifest (diagnostic only): manifest=${declared} answered=${answered}`
|
|
763
986
|
);
|
|
764
987
|
}
|
|
@@ -768,13 +991,13 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
|
|
|
768
991
|
// src/execution/engine/client/reaper.ts
|
|
769
992
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
770
993
|
import { getLogger as getLogger6 } from "@zhushanwen/subagent-engine-sdk";
|
|
771
|
-
var
|
|
994
|
+
var logger8 = getLogger6("subagents");
|
|
772
995
|
var KILL_GRACE_MS = 5e3;
|
|
773
996
|
function killProcessTree(pid, reason, opts) {
|
|
774
997
|
if (process.platform === "win32") {
|
|
775
998
|
const r = spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { timeout: 1e4 });
|
|
776
999
|
if (r.error || r.status !== 0) {
|
|
777
|
-
|
|
1000
|
+
logger8.warn(
|
|
778
1001
|
`[engine-client:${opts.engineId}] taskkill failed for pid ${pid} (${reason}): ${r.error?.message ?? `exit ${r.status}`}`
|
|
779
1002
|
);
|
|
780
1003
|
}
|
|
@@ -785,7 +1008,7 @@ function killProcessTree(pid, reason, opts) {
|
|
|
785
1008
|
process.kill(-pid, signal);
|
|
786
1009
|
return true;
|
|
787
1010
|
} catch (err) {
|
|
788
|
-
|
|
1011
|
+
logger8.debug(
|
|
789
1012
|
`[engine-client:${opts.engineId}] process.kill(-${pid}, ${signal}) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
790
1013
|
);
|
|
791
1014
|
return false;
|
|
@@ -795,7 +1018,7 @@ function killProcessTree(pid, reason, opts) {
|
|
|
795
1018
|
try {
|
|
796
1019
|
process.kill(pid, "SIGTERM");
|
|
797
1020
|
} catch (err) {
|
|
798
|
-
|
|
1021
|
+
logger8.debug(
|
|
799
1022
|
`[engine-client:${opts.engineId}] single SIGTERM to ${pid} failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`
|
|
800
1023
|
);
|
|
801
1024
|
return;
|
|
@@ -807,7 +1030,7 @@ function killProcessTree(pid, reason, opts) {
|
|
|
807
1030
|
try {
|
|
808
1031
|
process.kill(pid, "SIGKILL");
|
|
809
1032
|
} catch (err) {
|
|
810
|
-
|
|
1033
|
+
logger8.debug(
|
|
811
1034
|
`[engine-client:${opts.engineId}] SIGKILL to ${pid} failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`
|
|
812
1035
|
);
|
|
813
1036
|
}
|
|
@@ -900,57 +1123,13 @@ function hasLiveProcessHandleCore(recordId) {
|
|
|
900
1123
|
return coreMirrorSlot().hasLiveProcessHandle(recordId);
|
|
901
1124
|
}
|
|
902
1125
|
|
|
903
|
-
// src/shared/timer-delay.ts
|
|
904
|
-
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
905
|
-
function assertSafeTimerDelay(ms, source) {
|
|
906
|
-
if (!Number.isFinite(ms)) {
|
|
907
|
-
throw new Error(
|
|
908
|
-
`[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
if (ms > MAX_TIMER_DELAY_MS) {
|
|
912
|
-
throw new Error(
|
|
913
|
-
`[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
|
|
914
|
-
);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
1126
|
// src/execution/lifecycle-manager.ts
|
|
919
|
-
var
|
|
920
|
-
var
|
|
921
|
-
var
|
|
1127
|
+
var logger9 = getLogger("subagents");
|
|
1128
|
+
var MS_PER_SECOND2 = 1e3;
|
|
1129
|
+
var SECONDS_PER_MINUTE2 = 60;
|
|
922
1130
|
var IDLE_TIMEOUT_MINUTES = 5;
|
|
923
|
-
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES *
|
|
924
|
-
function getEnvIdleTimeoutMs() {
|
|
925
|
-
const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
|
|
926
|
-
if (!raw) return void 0;
|
|
927
|
-
const parsed = Number(raw);
|
|
928
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
929
|
-
logger8.warn(
|
|
930
|
-
`[lifecycle-manager] XYZ_SUBAGENT_IDLE_TIMEOUT_MS="${raw}" is invalid (expected a positive millisecond number) \u2014 falling back to DEFAULT_IDLE_TIMEOUT_MS (${DEFAULT_IDLE_TIMEOUT_MS}ms); set a plain ms value (e.g. 1800000) to override`
|
|
931
|
-
);
|
|
932
|
-
return void 0;
|
|
933
|
-
}
|
|
934
|
-
return parsed;
|
|
935
|
-
}
|
|
1131
|
+
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
|
|
936
1132
|
var idleTimers = /* @__PURE__ */ new Map();
|
|
937
|
-
function armIdleTimer(recordId, onTimeout, timeoutMs) {
|
|
938
|
-
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
939
|
-
disarmIdleTimer(recordId);
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
943
|
-
assertSafeTimerDelay(resolved, "idleTimeoutMs");
|
|
944
|
-
disarmIdleTimer(recordId);
|
|
945
|
-
const timer = setTimeout(() => {
|
|
946
|
-
if (idleTimers.get(recordId)?.timer === timer) {
|
|
947
|
-
idleTimers.delete(recordId);
|
|
948
|
-
}
|
|
949
|
-
onTimeout();
|
|
950
|
-
}, resolved);
|
|
951
|
-
timer.unref?.();
|
|
952
|
-
idleTimers.set(recordId, { timer, timeoutMs: resolved });
|
|
953
|
-
}
|
|
954
1133
|
function disarmIdleTimer(recordId) {
|
|
955
1134
|
const entry = idleTimers.get(recordId);
|
|
956
1135
|
if (!entry) {
|
|
@@ -962,8 +1141,6 @@ function disarmIdleTimer(recordId) {
|
|
|
962
1141
|
function hasIdleTimer(recordId) {
|
|
963
1142
|
return idleTimers.has(recordId);
|
|
964
1143
|
}
|
|
965
|
-
var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
|
|
966
|
-
var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND;
|
|
967
1144
|
|
|
968
1145
|
// src/execution/lifecycle-predicates.ts
|
|
969
1146
|
function hasLiveProcessHandle(recordId) {
|
|
@@ -1001,7 +1178,7 @@ function countInFlight() {
|
|
|
1001
1178
|
}
|
|
1002
1179
|
|
|
1003
1180
|
// src/execution/engine/client/engine-client.ts
|
|
1004
|
-
var
|
|
1181
|
+
var logger10 = getLogger7("subagents");
|
|
1005
1182
|
function bridgeMirrorEventToCoreMirror(event, mirror) {
|
|
1006
1183
|
if (event.recordId === void 0 || event.pid === void 0) return;
|
|
1007
1184
|
const entry = mirror.getEntry(event.pid);
|
|
@@ -1043,9 +1220,6 @@ var EngineClient = class {
|
|
|
1043
1220
|
nextRequestId = 1;
|
|
1044
1221
|
pending = /* @__PURE__ */ new Map();
|
|
1045
1222
|
runRoutes = /* @__PURE__ */ new Map();
|
|
1046
|
-
/** [W3 v1.x] chat 轮次路由(recordId 键,D1-A——续聊轮无 runId);注册经
|
|
1047
|
-
* RemoteEngine.registerChatRoundRoute,record 终态注销。 */
|
|
1048
|
-
recordRoutes = /* @__PURE__ */ new Map();
|
|
1049
1223
|
stderrTail = "";
|
|
1050
1224
|
unavailableReason;
|
|
1051
1225
|
connectInFlight;
|
|
@@ -1065,7 +1239,6 @@ var EngineClient = class {
|
|
|
1065
1239
|
permissionHandler: opts.permissionHandler,
|
|
1066
1240
|
log: opts.log,
|
|
1067
1241
|
runRoutes: this.runRoutes,
|
|
1068
|
-
recordRoutes: this.recordRoutes,
|
|
1069
1242
|
mirror: this.mirror,
|
|
1070
1243
|
setPartialHandle: (partial) => {
|
|
1071
1244
|
this.lastPartialHandle = partial;
|
|
@@ -1139,19 +1312,19 @@ var EngineClient = class {
|
|
|
1139
1312
|
matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
|
|
1140
1313
|
});
|
|
1141
1314
|
if (sweep.killed.length > 0) {
|
|
1142
|
-
|
|
1315
|
+
logger10.warn(
|
|
1143
1316
|
`[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
|
|
1144
1317
|
);
|
|
1145
1318
|
}
|
|
1146
1319
|
for (const removed of sweep.removed) {
|
|
1147
|
-
|
|
1320
|
+
logger10.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
|
|
1148
1321
|
}
|
|
1149
1322
|
}
|
|
1150
1323
|
let attempt = 0;
|
|
1151
1324
|
while (attempt <= CRASH_REBUILD_MAX_ATTEMPTS) {
|
|
1152
1325
|
if (attempt > 0) {
|
|
1153
1326
|
const backoff = CRASH_REBUILD_BACKOFF_MS[attempt - 1];
|
|
1154
|
-
|
|
1327
|
+
logger10.warn(
|
|
1155
1328
|
`[engine-client:${this.engineId}] rebuild attempt ${attempt}/${CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
|
|
1156
1329
|
);
|
|
1157
1330
|
await delay(backoff);
|
|
@@ -1184,7 +1357,7 @@ var EngineClient = class {
|
|
|
1184
1357
|
markUnavailable(reason) {
|
|
1185
1358
|
this.state = "unavailable";
|
|
1186
1359
|
this.unavailableReason = reason;
|
|
1187
|
-
|
|
1360
|
+
logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
|
|
1188
1361
|
}
|
|
1189
1362
|
/** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
|
|
1190
1363
|
async spawnAndInitialize() {
|
|
@@ -1214,13 +1387,13 @@ var EngineClient = class {
|
|
|
1214
1387
|
});
|
|
1215
1388
|
const child = this.child;
|
|
1216
1389
|
child.on("error", (err) => {
|
|
1217
|
-
|
|
1390
|
+
logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
|
|
1218
1391
|
this.appendStderrTail(`spawn error: ${err.message}
|
|
1219
1392
|
`);
|
|
1220
1393
|
});
|
|
1221
1394
|
for (const pipe of ["stdin", "stdout", "stderr"]) {
|
|
1222
1395
|
child[pipe]?.on("error", (err) => {
|
|
1223
|
-
|
|
1396
|
+
logger10.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
|
|
1224
1397
|
});
|
|
1225
1398
|
}
|
|
1226
1399
|
child.stdout?.setEncoding("utf-8");
|
|
@@ -1289,7 +1462,7 @@ var EngineClient = class {
|
|
|
1289
1462
|
try {
|
|
1290
1463
|
frame = JSON.parse(line);
|
|
1291
1464
|
} catch {
|
|
1292
|
-
|
|
1465
|
+
logger10.warn(
|
|
1293
1466
|
`[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
1294
1467
|
);
|
|
1295
1468
|
return;
|
|
@@ -1306,7 +1479,7 @@ var EngineClient = class {
|
|
|
1306
1479
|
this.onReverseRequest(frame);
|
|
1307
1480
|
return;
|
|
1308
1481
|
}
|
|
1309
|
-
|
|
1482
|
+
logger10.warn(
|
|
1310
1483
|
`[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
1311
1484
|
);
|
|
1312
1485
|
}
|
|
@@ -1387,14 +1560,6 @@ var EngineClient = class {
|
|
|
1387
1560
|
if (this.runRoutes.get(runId) === route) this.runRoutes.delete(runId);
|
|
1388
1561
|
};
|
|
1389
1562
|
}
|
|
1390
|
-
/** [W3 v1.x] 注册 chat 轮次路由(recordId 键);分发见 reverse-router.ts
|
|
1391
|
-
* (与 runRoutes 同构,键空间互斥:runId=run 帧分配 / recordId=宿主铸造)。 */
|
|
1392
|
-
registerRecordRoute(recordId, route) {
|
|
1393
|
-
this.recordRoutes.set(recordId, route);
|
|
1394
|
-
return () => {
|
|
1395
|
-
if (this.recordRoutes.get(recordId) === route) this.recordRoutes.delete(recordId);
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
1563
|
/** 健康检查(ADR-0047:静默 ≠ 卡死,不据此杀任务)。 */
|
|
1399
1564
|
async ping() {
|
|
1400
1565
|
await this.ensureConnected();
|
|
@@ -1430,7 +1595,7 @@ var EngineClient = class {
|
|
|
1430
1595
|
try {
|
|
1431
1596
|
await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
|
|
1432
1597
|
} catch (err) {
|
|
1433
|
-
|
|
1598
|
+
logger10.debug(
|
|
1434
1599
|
`[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
|
|
1435
1600
|
);
|
|
1436
1601
|
}
|
|
@@ -1442,18 +1607,20 @@ var EngineClient = class {
|
|
|
1442
1607
|
onEngineExit(code, signal) {
|
|
1443
1608
|
const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
|
|
1444
1609
|
if (this.intentionalKill) {
|
|
1445
|
-
|
|
1610
|
+
logger10.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
|
|
1446
1611
|
} else {
|
|
1447
|
-
|
|
1612
|
+
logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
|
|
1448
1613
|
}
|
|
1449
1614
|
this.teardownProcess(detail);
|
|
1450
1615
|
this.state = "exited";
|
|
1451
1616
|
}
|
|
1452
1617
|
/**
|
|
1453
|
-
* 进程死亡 /
|
|
1454
|
-
*
|
|
1618
|
+
* 进程死亡 / 失败后的统一清理(收割/组杀分支各成子函数,此处只做顺序编排):
|
|
1619
|
+
* 孤儿收割(镜像置死前)→ 镜像置死(失效语义 2+3)→ pidfile 删 → 在途请求
|
|
1620
|
+
* engine_crashed(附 stderr 尾)→ run 路由清空 → 活引擎组杀兜底。
|
|
1455
1621
|
*/
|
|
1456
1622
|
teardownProcess(detail) {
|
|
1623
|
+
this.reapOrphansAfterUnexpectedDeath(detail);
|
|
1457
1624
|
this.mirror.killAll();
|
|
1458
1625
|
if (this.pidfileWritten !== void 0) {
|
|
1459
1626
|
removePidfile(this.pidfileWritten);
|
|
@@ -1466,8 +1633,46 @@ var EngineClient = class {
|
|
|
1466
1633
|
}
|
|
1467
1634
|
this.pending.clear();
|
|
1468
1635
|
this.runRoutes.clear();
|
|
1469
|
-
this.recordRoutes.clear();
|
|
1470
1636
|
this.lastPartialHandle = void 0;
|
|
1637
|
+
this.killLeakedAliveChild(detail);
|
|
1638
|
+
if (this.state !== "unavailable" && this.state !== "disposed") {
|
|
1639
|
+
this.state = "exited";
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* [H1 U2 / 红线①收割链] 非主动死亡且引擎已死的路径,在镜像置死**之前**补发收割
|
|
1644
|
+
* 信号:孤儿真因 = 引擎意外死时宿主未发收割信号(原先 alreadyDead 分支直接跳过
|
|
1645
|
+
* killProcessTree),任务子进程留在无主进程组继续写 session 文件。插入点约束:
|
|
1646
|
+
* - 位置 = mirror.killAll() 置死清空之前——Windows 通道依赖镜像快照过滤活孤儿
|
|
1647
|
+
* (置死后全量项 killed=true 不可过滤);
|
|
1648
|
+
* - 条件 = !intentionalKill && alreadyDead——主动 dispose 链已有两层杀(引擎
|
|
1649
|
+
* killAllActiveChildren SIGTERM+30s + 宿主 killAll 组杀 5s 升级),跳过即不叠加、
|
|
1650
|
+
* 优雅窗零压缩、pi trap-flush 零截断;进程仍活的路径(握手失败重建)由
|
|
1651
|
+
* killLeakedAliveChild 组杀兜底承接。
|
|
1652
|
+
*/
|
|
1653
|
+
reapOrphansAfterUnexpectedDeath(detail) {
|
|
1654
|
+
if (!this.intentionalKill && this.child !== void 0) {
|
|
1655
|
+
const dying = this.child;
|
|
1656
|
+
const alreadyDead = dying.exitCode !== null || dying.signalCode !== null;
|
|
1657
|
+
if (alreadyDead && dying.pid !== void 0) {
|
|
1658
|
+
if (process.platform === "win32") {
|
|
1659
|
+
this.reapOrphansViaMirrorSnapshot(detail);
|
|
1660
|
+
} else {
|
|
1661
|
+
killProcessTree(
|
|
1662
|
+
dying.pid,
|
|
1663
|
+
`reap orphaned children after unexpected engine death (${detail})`,
|
|
1664
|
+
{ engineId: this.engineId }
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* 握手失败路径(版本越界/握手超时/启动僵死)引擎进程仍活着:必须组杀,否则
|
|
1672
|
+
* crash 重建循环每次 spawn 泄漏一个常驻孤儿(测试机实测积累 370+)。主动杀
|
|
1673
|
+
* (intentionalKill=true)与进程已死(exitCode/signalCode 非空)跳过重复发信号。
|
|
1674
|
+
*/
|
|
1675
|
+
killLeakedAliveChild(detail) {
|
|
1471
1676
|
if (this.child !== void 0) {
|
|
1472
1677
|
const child = this.child;
|
|
1473
1678
|
this.child = void 0;
|
|
@@ -1477,8 +1682,30 @@ var EngineClient = class {
|
|
|
1477
1682
|
}
|
|
1478
1683
|
child.removeAllListeners();
|
|
1479
1684
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* [H1 U2 / 红线①Windows 通道] 镜像快照收割活孤儿:镜像快照同步抓清单(置死前,
|
|
1688
|
+
* 全量项含引擎一生历史死 pid——镜像对已退出子进程只改 state 不删项、唯一清空点 =
|
|
1689
|
+
* killAll)→ 过滤活孤儿(state === "running" && !killed,活孤儿 ≤ 并发数)→ 逐 pid
|
|
1690
|
+
* **异步 spawn** taskkill /T /F fire-and-forget(树根 = 活着的任务子进程自身,树杀
|
|
1691
|
+
* 有效;快照同步抓、杀动作异步发——与 POSIX fire-and-forget 对称;禁 spawnSync:
|
|
1692
|
+
* 逐个同步 N×10s 上界会冻结 onEngineExit 同步回调、推迟 pending reject(Continuation
|
|
1693
|
+
* 失败通知链源头)与宿主事件循环)。
|
|
1694
|
+
*/
|
|
1695
|
+
reapOrphansViaMirrorSnapshot(detail) {
|
|
1696
|
+
const liveOrphans = this.mirror.snapshot().filter((e) => e.state === "running" && !e.killed);
|
|
1697
|
+
if (liveOrphans.length === 0) return;
|
|
1698
|
+
logger10.warn(
|
|
1699
|
+
`[engine-client:${this.engineId}] reaping ${liveOrphans.length} orphaned task child(ren) via mirror snapshot after unexpected engine death (${detail})`
|
|
1700
|
+
);
|
|
1701
|
+
for (const entry of liveOrphans) {
|
|
1702
|
+
const killer = spawn("taskkill", ["/PID", String(entry.pid), "/T", "/F"], { stdio: "ignore" });
|
|
1703
|
+
killer.unref();
|
|
1704
|
+
killer.on("error", (err) => {
|
|
1705
|
+
logger10.debug(
|
|
1706
|
+
`[engine-client:${this.engineId}] taskkill spawn failed for orphan pid ${entry.pid} (${err.message})`
|
|
1707
|
+
);
|
|
1708
|
+
});
|
|
1482
1709
|
}
|
|
1483
1710
|
}
|
|
1484
1711
|
// ── pidfile ──────────────────────────────────────────────────────────────
|
|
@@ -1493,7 +1720,7 @@ var EngineClient = class {
|
|
|
1493
1720
|
`);
|
|
1494
1721
|
return true;
|
|
1495
1722
|
} catch (err) {
|
|
1496
|
-
|
|
1723
|
+
logger10.debug(
|
|
1497
1724
|
`[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1498
1725
|
);
|
|
1499
1726
|
return false;
|
|
@@ -1516,11 +1743,25 @@ function truncate(text, max) {
|
|
|
1516
1743
|
}
|
|
1517
1744
|
|
|
1518
1745
|
// src/execution/engine/client/remote-engine.ts
|
|
1746
|
+
import { homedir as homedir2 } from "os";
|
|
1747
|
+
import { join as join4 } from "path";
|
|
1519
1748
|
import {
|
|
1520
1749
|
CANCEL_SETTLE_GRACE_MS as CANCEL_SETTLE_GRACE_MS2,
|
|
1521
1750
|
EngineSdkError as EngineSdkError2
|
|
1522
1751
|
} from "@zhushanwen/subagent-engine-sdk";
|
|
1523
1752
|
|
|
1753
|
+
// src/execution/path-encoding.ts
|
|
1754
|
+
import * as path2 from "path";
|
|
1755
|
+
function encodeCwd(cwd) {
|
|
1756
|
+
return "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
|
|
1757
|
+
}
|
|
1758
|
+
function getSubagentSessionDir(agentDir, mainCwd) {
|
|
1759
|
+
return path2.join(agentDir, "subagents", encodeCwd(mainCwd), "sessions");
|
|
1760
|
+
}
|
|
1761
|
+
function getSubagentRecordsDir(agentDir, mainCwd) {
|
|
1762
|
+
return path2.join(agentDir, "subagents", encodeCwd(mainCwd), "records");
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1524
1765
|
// src/execution/engine/common/capability-gate.ts
|
|
1525
1766
|
import { engineConversationUnsupportedError } from "@zhushanwen/subagent-engine-sdk";
|
|
1526
1767
|
|
|
@@ -1535,7 +1776,7 @@ var EngineError = class extends Error {
|
|
|
1535
1776
|
this.code = code;
|
|
1536
1777
|
this.recovery = recovery;
|
|
1537
1778
|
}
|
|
1538
|
-
/** 结构化投影(
|
|
1779
|
+
/** 结构化投影(code/message 与 GUI 警告条共用形态)。 */
|
|
1539
1780
|
toStructured() {
|
|
1540
1781
|
return { code: this.code, message: this.message, recovery: this.recovery };
|
|
1541
1782
|
}
|
|
@@ -1592,6 +1833,13 @@ function assertGateCapabilitiesMatched(engineId, manifestCaps, answeredCaps) {
|
|
|
1592
1833
|
throw gateMismatchError(engineId, "sandbox\uFF08worktree\uFF09", manifestCaps.sandbox, answeredCaps.sandbox);
|
|
1593
1834
|
}
|
|
1594
1835
|
}
|
|
1836
|
+
function engineConversationUpgradeUnsupportedError(engineId) {
|
|
1837
|
+
return new EngineError(
|
|
1838
|
+
"engine_capability_unsupported",
|
|
1839
|
+
`subagent completed as one-shot on engine '${engineId}' (capabilities.conversation = 'unsupported') \u2014 it cannot be upgraded to a resumable conversation, so the message is rejected.`,
|
|
1840
|
+
`Recovery: re-dispatch the follow-up as a new subagent (action:'start'), or branch from its session history with action:'fork-from', or pick an engine that declares the conversation capability.`
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1595
1843
|
function gateMismatchError(engineId, cap, declared, answered) {
|
|
1596
1844
|
return new EngineError(
|
|
1597
1845
|
"engine_capability_mismatch",
|
|
@@ -1713,22 +1961,6 @@ var RemoteEngine = class {
|
|
|
1713
1961
|
unregister();
|
|
1714
1962
|
}
|
|
1715
1963
|
}
|
|
1716
|
-
async interact(handle, action) {
|
|
1717
|
-
await this.opts.client.ensureConnected();
|
|
1718
|
-
const result = await this.opts.client.request("interact", {
|
|
1719
|
-
handle: handle.data,
|
|
1720
|
-
action
|
|
1721
|
-
});
|
|
1722
|
-
return result;
|
|
1723
|
-
}
|
|
1724
|
-
/**
|
|
1725
|
-
* [W3 v1.x] chat 轮次反向通道路由注册(recordId 键,EnginePort 可选面实现):
|
|
1726
|
-
* interact 续聊轮的 streamDelta / roundLifecycle 分发目标。薄委托 EngineClient
|
|
1727
|
-
* 的 recordRoutes(键分发见 reverse-router.ts)。
|
|
1728
|
-
*/
|
|
1729
|
-
registerChatRoundRoute(recordId, route) {
|
|
1730
|
-
return this.opts.client.registerRecordRoute(recordId, route);
|
|
1731
|
-
}
|
|
1732
1964
|
/** 协议 read(dataDir 必填——引擎数据根,构造注入)。 */
|
|
1733
1965
|
async read(handle) {
|
|
1734
1966
|
await this.opts.client.ensureConnected();
|
|
@@ -1760,6 +1992,15 @@ function isTransientRunFailure(err) {
|
|
|
1760
1992
|
if (!(err instanceof EngineSdkError2)) return false;
|
|
1761
1993
|
return err.code === "engine_crashed" || err.code === "engine_request_timeout" || err.code === "engine_handshake_timeout";
|
|
1762
1994
|
}
|
|
1995
|
+
var PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
|
|
1996
|
+
var PI_ROOT_CWD_ENV = "PI_SUBAGENT_ROOT_CWD";
|
|
1997
|
+
function deriveHostSubagentSessionDir() {
|
|
1998
|
+
const agentDir = process.env[PI_AGENT_DIR_ENV];
|
|
1999
|
+
const resolvedAgentDir = agentDir !== void 0 && agentDir !== "" ? agentDir : join4(homedir2(), ".pi", "agent");
|
|
2000
|
+
const rootCwd = process.env[PI_ROOT_CWD_ENV];
|
|
2001
|
+
const resolvedRootCwd = rootCwd !== void 0 && rootCwd !== "" ? rootCwd : process.cwd();
|
|
2002
|
+
return getSubagentSessionDir(resolvedAgentDir, resolvedRootCwd);
|
|
2003
|
+
}
|
|
1763
2004
|
function buildRunParams(task, ctx, runId) {
|
|
1764
2005
|
const ctxModelRef = ctx.ctxModel ? `${ctx.ctxModel.provider}/${ctx.ctxModel.id}` : void 0;
|
|
1765
2006
|
return {
|
|
@@ -1772,9 +2013,16 @@ function buildRunParams(task, ctx, runId) {
|
|
|
1772
2013
|
schemaEnv: ctx.schemaEnv ?? task.schemaEnv,
|
|
1773
2014
|
ctxModel: ctxModelRef,
|
|
1774
2015
|
engineFallback: ctx.engineFallback,
|
|
1775
|
-
streamMode: ctx.stream !== void 0 ? "stream" : void 0
|
|
2016
|
+
streamMode: ctx.stream !== void 0 ? "stream" : void 0,
|
|
2017
|
+
// [F6] 根 session id(relay 归属键 SESSION_ID 权威源)——undefined 不上 wire
|
|
2018
|
+
//(additive 语义,与顶层 chat 参数同写法)。
|
|
2019
|
+
...ctx.sessionRootId !== void 0 ? { sessionRootId: ctx.sessionRootId } : {},
|
|
2020
|
+
// [Option C 协议化] 权威 subagent session 目录(Gate B S6):宿主注入值优先,
|
|
2021
|
+
// 缺省同源 env 推导(deriveHostSubagentSessionDir)——恒有值恒上 wire,引擎
|
|
2022
|
+
// 据此组装 --session-dir 不自推导(引擎本地推导降级 [LEGACY] fallback)。
|
|
2023
|
+
sessionDir: ctx.sessionDir ?? deriveHostSubagentSessionDir()
|
|
1776
2024
|
},
|
|
1777
|
-
...ctx.
|
|
2025
|
+
...ctx.resume !== void 0 ? { resume: ctx.resume } : {}
|
|
1778
2026
|
};
|
|
1779
2027
|
}
|
|
1780
2028
|
function buildRunRouteHandlers(ctx) {
|
|
@@ -1782,9 +2030,7 @@ function buildRunRouteHandlers(ctx) {
|
|
|
1782
2030
|
onEvent: (event) => ctx.onEvent?.(event),
|
|
1783
2031
|
onStreamDelta: (delta) => ctx.stream?.onDelta(delta),
|
|
1784
2032
|
onPoolResolved: (poolKey) => ctx.onPoolResolved?.(poolKey),
|
|
1785
|
-
onHandleReady: (partial) => ctx.onHandleReady?.(partial)
|
|
1786
|
-
// [W3 v1.x] 首轮(run 会话形态)轮次生命周期帧(runId 键)→ 宿主消费口。
|
|
1787
|
-
onRoundLifecycle: (phase) => ctx.onRoundLifecycle?.(phase)
|
|
2033
|
+
onHandleReady: (partial) => ctx.onHandleReady?.(partial)
|
|
1788
2034
|
};
|
|
1789
2035
|
}
|
|
1790
2036
|
function wireAbortSignal(client, runId, ctx) {
|
|
@@ -1877,11 +2123,11 @@ function getHostUiRequestEndpoint() {
|
|
|
1877
2123
|
}
|
|
1878
2124
|
|
|
1879
2125
|
// src/execution/engine/engine-inspect-package.ts
|
|
1880
|
-
var
|
|
2126
|
+
var logger11 = getLogger("subagents");
|
|
1881
2127
|
function readEnginePackageJson(pkgDir) {
|
|
1882
2128
|
let raw;
|
|
1883
2129
|
try {
|
|
1884
|
-
raw = fs.readFileSync(
|
|
2130
|
+
raw = fs.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
|
|
1885
2131
|
} catch (err) {
|
|
1886
2132
|
return { status: "skip", reason: `package.json unreadable: ${errorMessage(err)}` };
|
|
1887
2133
|
}
|
|
@@ -1956,12 +2202,12 @@ function parseOptionalDisplayFields(m, id) {
|
|
|
1956
2202
|
if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
|
|
1957
2203
|
displayName = rawDisplayName;
|
|
1958
2204
|
} else {
|
|
1959
|
-
|
|
2205
|
+
logger11.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
|
|
1960
2206
|
}
|
|
1961
2207
|
}
|
|
1962
2208
|
const rawDescription = m["description"];
|
|
1963
2209
|
if (rawDescription !== void 0 && typeof rawDescription !== "string") {
|
|
1964
|
-
|
|
2210
|
+
logger11.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
|
|
1965
2211
|
}
|
|
1966
2212
|
return displayName;
|
|
1967
2213
|
}
|
|
@@ -2049,19 +2295,19 @@ function errorMessage(err) {
|
|
|
2049
2295
|
|
|
2050
2296
|
// src/execution/engine/engine-discovery-roots.ts
|
|
2051
2297
|
import * as fs2 from "fs";
|
|
2052
|
-
import * as
|
|
2053
|
-
var
|
|
2298
|
+
import * as path4 from "path";
|
|
2299
|
+
var logger12 = getLogger("subagents");
|
|
2054
2300
|
var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
|
|
2055
2301
|
function parseEngineRootsEnv(env) {
|
|
2056
2302
|
const raw = env[ENGINE_ROOTS_ENV];
|
|
2057
2303
|
if (raw === void 0 || raw.trim() === "") return [];
|
|
2058
2304
|
const out = [];
|
|
2059
2305
|
const seen = /* @__PURE__ */ new Set();
|
|
2060
|
-
for (const part of raw.split(
|
|
2306
|
+
for (const part of raw.split(path4.delimiter)) {
|
|
2061
2307
|
const dir = part.trim();
|
|
2062
2308
|
if (dir === "") continue;
|
|
2063
|
-
if (!
|
|
2064
|
-
|
|
2309
|
+
if (!path4.isAbsolute(dir)) {
|
|
2310
|
+
logger12.warn(
|
|
2065
2311
|
`[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
|
|
2066
2312
|
);
|
|
2067
2313
|
continue;
|
|
@@ -2075,11 +2321,11 @@ function parseEngineRootsEnv(env) {
|
|
|
2075
2321
|
function deriveNodeModuleRoots(argvEntry = process.argv[1]) {
|
|
2076
2322
|
if (!argvEntry || argvEntry.trim() === "") return [];
|
|
2077
2323
|
const roots = [];
|
|
2078
|
-
let dir =
|
|
2324
|
+
let dir = path4.dirname(path4.resolve(argvEntry));
|
|
2079
2325
|
while (true) {
|
|
2080
|
-
const nm =
|
|
2326
|
+
const nm = path4.join(dir, "node_modules");
|
|
2081
2327
|
if (isDirectory(nm)) roots.push(nm);
|
|
2082
|
-
const parent =
|
|
2328
|
+
const parent = path4.dirname(dir);
|
|
2083
2329
|
if (parent === dir) break;
|
|
2084
2330
|
dir = parent;
|
|
2085
2331
|
}
|
|
@@ -2094,8 +2340,8 @@ function scanEngineRoot(root, visit) {
|
|
|
2094
2340
|
}
|
|
2095
2341
|
for (const item of entries) {
|
|
2096
2342
|
if (!item.isDirectory()) continue;
|
|
2097
|
-
const dir =
|
|
2098
|
-
if (isFile(
|
|
2343
|
+
const dir = path4.join(root, item.name);
|
|
2344
|
+
if (isFile(path4.join(dir, "package.json"))) {
|
|
2099
2345
|
visit(dir);
|
|
2100
2346
|
continue;
|
|
2101
2347
|
}
|
|
@@ -2107,8 +2353,8 @@ function scanEngineRoot(root, visit) {
|
|
|
2107
2353
|
}
|
|
2108
2354
|
for (const child of sub) {
|
|
2109
2355
|
if (!child.isDirectory()) continue;
|
|
2110
|
-
const subDir =
|
|
2111
|
-
if (isFile(
|
|
2356
|
+
const subDir = path4.join(dir, child.name);
|
|
2357
|
+
if (isFile(path4.join(subDir, "package.json"))) visit(subDir);
|
|
2112
2358
|
}
|
|
2113
2359
|
}
|
|
2114
2360
|
}
|
|
@@ -2129,10 +2375,10 @@ function isFile(p) {
|
|
|
2129
2375
|
|
|
2130
2376
|
// src/execution/engine/config.ts
|
|
2131
2377
|
import * as fs3 from "fs";
|
|
2132
|
-
import * as
|
|
2133
|
-
var
|
|
2378
|
+
import * as path5 from "path";
|
|
2379
|
+
var logger13 = getLogger("subagents");
|
|
2134
2380
|
function readExplicitEngines(agentDir) {
|
|
2135
|
-
const configPath =
|
|
2381
|
+
const configPath = path5.join(agentDir, "subagents", "config.json");
|
|
2136
2382
|
let parsed;
|
|
2137
2383
|
try {
|
|
2138
2384
|
parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
@@ -2143,7 +2389,7 @@ function readExplicitEngines(agentDir) {
|
|
|
2143
2389
|
const engines = parsed["engines"];
|
|
2144
2390
|
if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
|
|
2145
2391
|
if (engines !== void 0) {
|
|
2146
|
-
|
|
2392
|
+
logger13.warn(
|
|
2147
2393
|
`[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
|
|
2148
2394
|
);
|
|
2149
2395
|
}
|
|
@@ -2171,7 +2417,7 @@ function sanitizeExplicitEntry(id, raw) {
|
|
|
2171
2417
|
}
|
|
2172
2418
|
function requireEntryObject(id, raw) {
|
|
2173
2419
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2174
|
-
|
|
2420
|
+
logger13.warn(
|
|
2175
2421
|
`[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
|
|
2176
2422
|
);
|
|
2177
2423
|
return void 0;
|
|
@@ -2180,7 +2426,7 @@ function requireEntryObject(id, raw) {
|
|
|
2180
2426
|
}
|
|
2181
2427
|
function requireCommand(id, command) {
|
|
2182
2428
|
if (typeof command !== "string" || command.trim() === "") {
|
|
2183
|
-
|
|
2429
|
+
logger13.warn(
|
|
2184
2430
|
`[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
|
|
2185
2431
|
);
|
|
2186
2432
|
return void 0;
|
|
@@ -2189,7 +2435,7 @@ function requireCommand(id, command) {
|
|
|
2189
2435
|
}
|
|
2190
2436
|
function normalizeArgs(id, args) {
|
|
2191
2437
|
if (args !== void 0 && !isStringArray(args)) {
|
|
2192
|
-
|
|
2438
|
+
logger13.warn(
|
|
2193
2439
|
`[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
|
|
2194
2440
|
);
|
|
2195
2441
|
}
|
|
@@ -2197,7 +2443,7 @@ function normalizeArgs(id, args) {
|
|
|
2197
2443
|
}
|
|
2198
2444
|
function normalizeConfig(id, config) {
|
|
2199
2445
|
if (config !== void 0 && !isStringRecord(config)) {
|
|
2200
|
-
|
|
2446
|
+
logger13.warn(
|
|
2201
2447
|
`[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
|
|
2202
2448
|
);
|
|
2203
2449
|
}
|
|
@@ -2205,7 +2451,7 @@ function normalizeConfig(id, config) {
|
|
|
2205
2451
|
}
|
|
2206
2452
|
function normalizeCwd(id, cwd) {
|
|
2207
2453
|
if (cwd !== void 0 && typeof cwd !== "string") {
|
|
2208
|
-
|
|
2454
|
+
logger13.warn(
|
|
2209
2455
|
`[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
|
|
2210
2456
|
);
|
|
2211
2457
|
}
|
|
@@ -2225,7 +2471,7 @@ function jsonKindOf(value) {
|
|
|
2225
2471
|
}
|
|
2226
2472
|
|
|
2227
2473
|
// src/execution/engine/registry.ts
|
|
2228
|
-
var
|
|
2474
|
+
var logger14 = getLogger("subagents");
|
|
2229
2475
|
var DEFAULT_ENGINE_ID = "pi";
|
|
2230
2476
|
function normalizeEngineId(engine) {
|
|
2231
2477
|
return engine?.trim() || DEFAULT_ENGINE_ID;
|
|
@@ -2265,12 +2511,12 @@ function triggerEngineDispose(engine, source) {
|
|
|
2265
2511
|
if (typeof engine.dispose !== "function") return;
|
|
2266
2512
|
try {
|
|
2267
2513
|
engine.dispose().then(void 0, (err) => {
|
|
2268
|
-
|
|
2514
|
+
logger14.warn(
|
|
2269
2515
|
`[engine-registry] engine '${engine.id}' dispose rejected (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
|
|
2270
2516
|
);
|
|
2271
2517
|
});
|
|
2272
2518
|
} catch (err) {
|
|
2273
|
-
|
|
2519
|
+
logger14.warn(
|
|
2274
2520
|
`[engine-registry] engine '${engine.id}' dispose threw synchronously (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
|
|
2275
2521
|
);
|
|
2276
2522
|
}
|
|
@@ -2313,14 +2559,14 @@ function listEnginesByDisplayName() {
|
|
|
2313
2559
|
}
|
|
2314
2560
|
|
|
2315
2561
|
// src/execution/engine/engine-discovery-scan.ts
|
|
2316
|
-
var
|
|
2562
|
+
var logger15 = getLogger("subagents");
|
|
2317
2563
|
function scanEngines(opts) {
|
|
2318
2564
|
const env = opts.env ?? process.env;
|
|
2319
2565
|
const result = { discovered: [], skipped: [], unusable: [] };
|
|
2320
2566
|
const present = (entry) => {
|
|
2321
2567
|
const idx = result.discovered.findIndex((d) => d.id === entry.id);
|
|
2322
2568
|
if (idx >= 0) {
|
|
2323
|
-
|
|
2569
|
+
logger15.debug(
|
|
2324
2570
|
`[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
|
|
2325
2571
|
);
|
|
2326
2572
|
result.discovered[idx] = entry;
|
|
@@ -2332,13 +2578,13 @@ function scanEngines(opts) {
|
|
|
2332
2578
|
const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
|
|
2333
2579
|
if (inspection.status === "skip") {
|
|
2334
2580
|
if (!inspection.reason.includes("not an engine package")) {
|
|
2335
|
-
|
|
2581
|
+
logger15.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
|
|
2336
2582
|
result.skipped.push({ pkgDir, reason: inspection.reason });
|
|
2337
2583
|
}
|
|
2338
2584
|
return;
|
|
2339
2585
|
}
|
|
2340
2586
|
if (inspection.status === "unusable") {
|
|
2341
|
-
|
|
2587
|
+
logger15.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
|
|
2342
2588
|
result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
|
|
2343
2589
|
return;
|
|
2344
2590
|
}
|
|
@@ -2363,7 +2609,7 @@ function scanEngines(opts) {
|
|
|
2363
2609
|
const command = resolveExplicitCommand(entry.command, env);
|
|
2364
2610
|
if (command === void 0) {
|
|
2365
2611
|
const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
|
|
2366
|
-
|
|
2612
|
+
logger15.warn(`[engine-discovery] ${reason} \u2014 not registering`);
|
|
2367
2613
|
result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
|
|
2368
2614
|
continue;
|
|
2369
2615
|
}
|
|
@@ -2380,7 +2626,7 @@ function discoverAndRegisterEngines(opts) {
|
|
|
2380
2626
|
registerEngineDescriptor(entry.id, entry.descriptor);
|
|
2381
2627
|
loadedDiscoveryIdSet.add(entry.id);
|
|
2382
2628
|
if (existed) {
|
|
2383
|
-
|
|
2629
|
+
logger15.debug(
|
|
2384
2630
|
`[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
|
|
2385
2631
|
);
|
|
2386
2632
|
}
|
|
@@ -2397,20 +2643,20 @@ function ensureEngineDiscovered(id, opts) {
|
|
|
2397
2643
|
}
|
|
2398
2644
|
function resolveExplicitCommand(command, env) {
|
|
2399
2645
|
if (command.includes("/") || command.includes("\\")) {
|
|
2400
|
-
const p =
|
|
2646
|
+
const p = path6.isAbsolute(command) ? command : path6.resolve(command);
|
|
2401
2647
|
return canExecute(p) ? p : void 0;
|
|
2402
2648
|
}
|
|
2403
2649
|
const pathVar = env["PATH"] ?? env["Path"] ?? "";
|
|
2404
|
-
for (const dir of pathVar.split(
|
|
2650
|
+
for (const dir of pathVar.split(path6.delimiter)) {
|
|
2405
2651
|
if (dir === "") continue;
|
|
2406
|
-
const p =
|
|
2652
|
+
const p = path6.join(dir, command);
|
|
2407
2653
|
if (canExecute(p)) return p;
|
|
2408
2654
|
}
|
|
2409
2655
|
return void 0;
|
|
2410
2656
|
}
|
|
2411
2657
|
function buildExplicitDescriptor(id, command, entry, opts) {
|
|
2412
2658
|
const env = opts.env ?? process.env;
|
|
2413
|
-
|
|
2659
|
+
logger15.debug(
|
|
2414
2660
|
`[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
|
|
2415
2661
|
);
|
|
2416
2662
|
const caps = { ...CONSERVATIVE_CAPABILITIES };
|
|
@@ -2472,13 +2718,16 @@ export {
|
|
|
2472
2718
|
parseEnvPrefixes,
|
|
2473
2719
|
parseModelCatalog,
|
|
2474
2720
|
getEngineDataDir,
|
|
2721
|
+
MAX_TIMER_DELAY_MS,
|
|
2722
|
+
assertSafeTimerDelay,
|
|
2723
|
+
isDialogMethod,
|
|
2724
|
+
isValidDialogTimeout,
|
|
2725
|
+
DialogGlobalQueue,
|
|
2726
|
+
registerActiveDialogQueue,
|
|
2475
2727
|
registerSpawnedChildForRecord,
|
|
2476
2728
|
killRecordChildWithEscalation,
|
|
2477
2729
|
killAllSpawnedChildren,
|
|
2478
|
-
MAX_TIMER_DELAY_MS,
|
|
2479
|
-
assertSafeTimerDelay,
|
|
2480
2730
|
DEFAULT_IDLE_TIMEOUT_MS,
|
|
2481
|
-
armIdleTimer,
|
|
2482
2731
|
disarmIdleTimer,
|
|
2483
2732
|
hasIdleTimer,
|
|
2484
2733
|
hasLiveProcessHandle,
|
|
@@ -2486,9 +2735,12 @@ export {
|
|
|
2486
2735
|
isResumable,
|
|
2487
2736
|
setInFlightListener,
|
|
2488
2737
|
getInFlightSnapshot,
|
|
2489
|
-
notifyInFlightChanged,
|
|
2490
2738
|
EngineClient,
|
|
2739
|
+
encodeCwd,
|
|
2740
|
+
getSubagentSessionDir,
|
|
2741
|
+
getSubagentRecordsDir,
|
|
2491
2742
|
assertTaskShapeSupported,
|
|
2743
|
+
engineConversationUpgradeUnsupportedError,
|
|
2492
2744
|
RemoteEngine,
|
|
2493
2745
|
setHostUiRequestEndpoint,
|
|
2494
2746
|
inspectEnginePackage,
|