@zhushanwen/subagent-core 0.7.1 → 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-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
- 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 +325 -154
- 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 +9799 -7954
- package/dist/index.d.cts +1896 -1360
- package/dist/index.d.ts +1896 -1360
- package/dist/index.js +8952 -7513
- package/dist.bundle/index.cjs +7897 -6054
- package/package.json +5 -5
- package/src/__tests__/manifest-store.test.ts +16 -15
- package/src/__tests__/record-store-last-line.test.ts +12 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
- package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
- package/src/core/logger.ts +1 -1
- 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__/inflight-production-wiring.test.ts +223 -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__/nested-visibility.test.ts +13 -1
- 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__/conformance/registry-fork-filter.test.ts +55 -53
- 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__/inflight-snapshot.test.ts +180 -0
- 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 +111 -26
- 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 +26 -41
- package/src/execution/engine/host/pi-host-binding.ts +5 -5
- package/src/execution/engine/host/spawned-children.ts +1 -1
- package/src/execution/engine/inflight-snapshot.ts +92 -0
- package/src/execution/engine/port.ts +52 -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/path-encoding.ts +6 -0
- 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 -2955
- 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 +12 -8
- package/src/index.ts +16 -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-ocpM8FmI.d.cts +0 -1608
- package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
- 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
|
}
|
|
@@ -367,18 +601,18 @@ var SpawnedChildrenMirror = class {
|
|
|
367
601
|
return this.entries.size;
|
|
368
602
|
}
|
|
369
603
|
/** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
|
|
370
|
-
onChange(
|
|
371
|
-
this.listeners.add(
|
|
604
|
+
onChange(listener2) {
|
|
605
|
+
this.listeners.add(listener2);
|
|
372
606
|
return () => {
|
|
373
|
-
this.listeners.delete(
|
|
607
|
+
this.listeners.delete(listener2);
|
|
374
608
|
};
|
|
375
609
|
}
|
|
376
610
|
emit(event) {
|
|
377
|
-
for (const
|
|
611
|
+
for (const listener2 of this.listeners) {
|
|
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
|
}
|
|
@@ -831,8 +1054,143 @@ function waitForChildExit(child, timeoutMs) {
|
|
|
831
1054
|
});
|
|
832
1055
|
}
|
|
833
1056
|
|
|
1057
|
+
// src/execution/engine/host/spawned-children.ts
|
|
1058
|
+
var CoreSpawnedChildrenMirror = class {
|
|
1059
|
+
entries = /* @__PURE__ */ new Map();
|
|
1060
|
+
/** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
|
|
1061
|
+
register(recordId, child) {
|
|
1062
|
+
this.entries.set(recordId, {
|
|
1063
|
+
pid: child.pid,
|
|
1064
|
+
killed: child.killed,
|
|
1065
|
+
updatedAt: Date.now()
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
/** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
|
|
1069
|
+
markKilled(recordId) {
|
|
1070
|
+
const entry = this.entries.get(recordId);
|
|
1071
|
+
if (entry !== void 0) {
|
|
1072
|
+
entry.killed = true;
|
|
1073
|
+
entry.updatedAt = Date.now();
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
/** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
|
|
1077
|
+
killAll() {
|
|
1078
|
+
this.entries.clear();
|
|
1079
|
+
}
|
|
1080
|
+
getChildByRecord(recordId) {
|
|
1081
|
+
return this.entries.get(recordId);
|
|
1082
|
+
}
|
|
1083
|
+
/** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
|
|
1084
|
+
hasLiveProcessHandle(recordId) {
|
|
1085
|
+
const entry = this.entries.get(recordId);
|
|
1086
|
+
return entry !== void 0 && !entry.killed;
|
|
1087
|
+
}
|
|
1088
|
+
/** 快照(诊断/测试)。 */
|
|
1089
|
+
snapshot() {
|
|
1090
|
+
return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
|
|
1091
|
+
}
|
|
1092
|
+
/** 测试隔离专用(生产禁用——进程级全局状态)。 */
|
|
1093
|
+
clear() {
|
|
1094
|
+
this.entries.clear();
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
|
|
1098
|
+
"@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
|
|
1099
|
+
);
|
|
1100
|
+
function coreMirrorSlot() {
|
|
1101
|
+
let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
|
|
1102
|
+
if (!slot) {
|
|
1103
|
+
slot = new CoreSpawnedChildrenMirror();
|
|
1104
|
+
Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
|
|
1105
|
+
}
|
|
1106
|
+
return slot;
|
|
1107
|
+
}
|
|
1108
|
+
function coreSpawnedChildrenMirror() {
|
|
1109
|
+
return coreMirrorSlot();
|
|
1110
|
+
}
|
|
1111
|
+
function registerSpawnedChildForRecord(recordId, child) {
|
|
1112
|
+
coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
|
|
1113
|
+
}
|
|
1114
|
+
function killRecordChildWithEscalation(recordId, _source) {
|
|
1115
|
+
coreMirrorSlot().markKilled(recordId);
|
|
1116
|
+
}
|
|
1117
|
+
function killAllSpawnedChildren(_signal = "SIGTERM") {
|
|
1118
|
+
const before = coreMirrorSlot().snapshot().length;
|
|
1119
|
+
coreMirrorSlot().killAll();
|
|
1120
|
+
return before;
|
|
1121
|
+
}
|
|
1122
|
+
function hasLiveProcessHandleCore(recordId) {
|
|
1123
|
+
return coreMirrorSlot().hasLiveProcessHandle(recordId);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/execution/lifecycle-manager.ts
|
|
1127
|
+
var logger9 = getLogger("subagents");
|
|
1128
|
+
var MS_PER_SECOND2 = 1e3;
|
|
1129
|
+
var SECONDS_PER_MINUTE2 = 60;
|
|
1130
|
+
var IDLE_TIMEOUT_MINUTES = 5;
|
|
1131
|
+
var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
|
|
1132
|
+
var idleTimers = /* @__PURE__ */ new Map();
|
|
1133
|
+
function disarmIdleTimer(recordId) {
|
|
1134
|
+
const entry = idleTimers.get(recordId);
|
|
1135
|
+
if (!entry) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
clearTimeout(entry.timer);
|
|
1139
|
+
idleTimers.delete(recordId);
|
|
1140
|
+
}
|
|
1141
|
+
function hasIdleTimer(recordId) {
|
|
1142
|
+
return idleTimers.has(recordId);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/execution/lifecycle-predicates.ts
|
|
1146
|
+
function hasLiveProcessHandle(recordId) {
|
|
1147
|
+
return hasLiveProcessHandleCore(recordId);
|
|
1148
|
+
}
|
|
1149
|
+
function isIdle(record) {
|
|
1150
|
+
return hasIdleTimer(record.id);
|
|
1151
|
+
}
|
|
1152
|
+
function isResumable(record) {
|
|
1153
|
+
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// src/execution/engine/inflight-snapshot.ts
|
|
1157
|
+
var listener = null;
|
|
1158
|
+
function setInFlightListener(next) {
|
|
1159
|
+
listener = next;
|
|
1160
|
+
}
|
|
1161
|
+
function getInFlightSnapshot() {
|
|
1162
|
+
return { inFlight: countInFlight() };
|
|
1163
|
+
}
|
|
1164
|
+
function notifyInFlightChanged() {
|
|
1165
|
+
if (listener === null) return;
|
|
1166
|
+
try {
|
|
1167
|
+
listener(getInFlightSnapshot());
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function countInFlight() {
|
|
1172
|
+
let count = 0;
|
|
1173
|
+
for (const entry of coreSpawnedChildrenMirror().snapshot()) {
|
|
1174
|
+
const recordId = entry.recordId;
|
|
1175
|
+
if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
|
|
1176
|
+
}
|
|
1177
|
+
return count;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
834
1180
|
// src/execution/engine/client/engine-client.ts
|
|
835
|
-
var
|
|
1181
|
+
var logger10 = getLogger7("subagents");
|
|
1182
|
+
function bridgeMirrorEventToCoreMirror(event, mirror) {
|
|
1183
|
+
if (event.recordId === void 0 || event.pid === void 0) return;
|
|
1184
|
+
const entry = mirror.getEntry(event.pid);
|
|
1185
|
+
if (entry === void 0) return;
|
|
1186
|
+
const core = coreSpawnedChildrenMirror();
|
|
1187
|
+
if (entry.killed) {
|
|
1188
|
+
core.markKilled(event.recordId);
|
|
1189
|
+
} else {
|
|
1190
|
+
core.register(event.recordId, { pid: entry.pid, killed: false });
|
|
1191
|
+
}
|
|
1192
|
+
notifyInFlightChanged();
|
|
1193
|
+
}
|
|
836
1194
|
var DISPOSE_GRACE_MS = 3e3;
|
|
837
1195
|
var SIGKILL_REAP_TIMEOUT_MS = 1e4;
|
|
838
1196
|
var FRAME_ECHO_MAX_CHARS = 200;
|
|
@@ -862,9 +1220,6 @@ var EngineClient = class {
|
|
|
862
1220
|
nextRequestId = 1;
|
|
863
1221
|
pending = /* @__PURE__ */ new Map();
|
|
864
1222
|
runRoutes = /* @__PURE__ */ new Map();
|
|
865
|
-
/** [W3 v1.x] chat 轮次路由(recordId 键,D1-A——续聊轮无 runId);注册经
|
|
866
|
-
* RemoteEngine.registerChatRoundRoute,record 终态注销。 */
|
|
867
|
-
recordRoutes = /* @__PURE__ */ new Map();
|
|
868
1223
|
stderrTail = "";
|
|
869
1224
|
unavailableReason;
|
|
870
1225
|
connectInFlight;
|
|
@@ -884,7 +1239,6 @@ var EngineClient = class {
|
|
|
884
1239
|
permissionHandler: opts.permissionHandler,
|
|
885
1240
|
log: opts.log,
|
|
886
1241
|
runRoutes: this.runRoutes,
|
|
887
|
-
recordRoutes: this.recordRoutes,
|
|
888
1242
|
mirror: this.mirror,
|
|
889
1243
|
setPartialHandle: (partial) => {
|
|
890
1244
|
this.lastPartialHandle = partial;
|
|
@@ -895,6 +1249,7 @@ var EngineClient = class {
|
|
|
895
1249
|
};
|
|
896
1250
|
this.mirror.onChange((event) => {
|
|
897
1251
|
opts.onMirrorChanged?.(event);
|
|
1252
|
+
bridgeMirrorEventToCoreMirror(event, this.mirror);
|
|
898
1253
|
});
|
|
899
1254
|
}
|
|
900
1255
|
get currentState() {
|
|
@@ -957,19 +1312,19 @@ var EngineClient = class {
|
|
|
957
1312
|
matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
|
|
958
1313
|
});
|
|
959
1314
|
if (sweep.killed.length > 0) {
|
|
960
|
-
|
|
1315
|
+
logger10.warn(
|
|
961
1316
|
`[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
|
|
962
1317
|
);
|
|
963
1318
|
}
|
|
964
1319
|
for (const removed of sweep.removed) {
|
|
965
|
-
|
|
1320
|
+
logger10.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
|
|
966
1321
|
}
|
|
967
1322
|
}
|
|
968
1323
|
let attempt = 0;
|
|
969
1324
|
while (attempt <= CRASH_REBUILD_MAX_ATTEMPTS) {
|
|
970
1325
|
if (attempt > 0) {
|
|
971
1326
|
const backoff = CRASH_REBUILD_BACKOFF_MS[attempt - 1];
|
|
972
|
-
|
|
1327
|
+
logger10.warn(
|
|
973
1328
|
`[engine-client:${this.engineId}] rebuild attempt ${attempt}/${CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
|
|
974
1329
|
);
|
|
975
1330
|
await delay(backoff);
|
|
@@ -1002,7 +1357,7 @@ var EngineClient = class {
|
|
|
1002
1357
|
markUnavailable(reason) {
|
|
1003
1358
|
this.state = "unavailable";
|
|
1004
1359
|
this.unavailableReason = reason;
|
|
1005
|
-
|
|
1360
|
+
logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
|
|
1006
1361
|
}
|
|
1007
1362
|
/** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
|
|
1008
1363
|
async spawnAndInitialize() {
|
|
@@ -1032,13 +1387,13 @@ var EngineClient = class {
|
|
|
1032
1387
|
});
|
|
1033
1388
|
const child = this.child;
|
|
1034
1389
|
child.on("error", (err) => {
|
|
1035
|
-
|
|
1390
|
+
logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
|
|
1036
1391
|
this.appendStderrTail(`spawn error: ${err.message}
|
|
1037
1392
|
`);
|
|
1038
1393
|
});
|
|
1039
1394
|
for (const pipe of ["stdin", "stdout", "stderr"]) {
|
|
1040
1395
|
child[pipe]?.on("error", (err) => {
|
|
1041
|
-
|
|
1396
|
+
logger10.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
|
|
1042
1397
|
});
|
|
1043
1398
|
}
|
|
1044
1399
|
child.stdout?.setEncoding("utf-8");
|
|
@@ -1107,7 +1462,7 @@ var EngineClient = class {
|
|
|
1107
1462
|
try {
|
|
1108
1463
|
frame = JSON.parse(line);
|
|
1109
1464
|
} catch {
|
|
1110
|
-
|
|
1465
|
+
logger10.warn(
|
|
1111
1466
|
`[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
1112
1467
|
);
|
|
1113
1468
|
return;
|
|
@@ -1124,7 +1479,7 @@ var EngineClient = class {
|
|
|
1124
1479
|
this.onReverseRequest(frame);
|
|
1125
1480
|
return;
|
|
1126
1481
|
}
|
|
1127
|
-
|
|
1482
|
+
logger10.warn(
|
|
1128
1483
|
`[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
|
|
1129
1484
|
);
|
|
1130
1485
|
}
|
|
@@ -1205,14 +1560,6 @@ var EngineClient = class {
|
|
|
1205
1560
|
if (this.runRoutes.get(runId) === route) this.runRoutes.delete(runId);
|
|
1206
1561
|
};
|
|
1207
1562
|
}
|
|
1208
|
-
/** [W3 v1.x] 注册 chat 轮次路由(recordId 键);分发见 reverse-router.ts
|
|
1209
|
-
* (与 runRoutes 同构,键空间互斥:runId=run 帧分配 / recordId=宿主铸造)。 */
|
|
1210
|
-
registerRecordRoute(recordId, route) {
|
|
1211
|
-
this.recordRoutes.set(recordId, route);
|
|
1212
|
-
return () => {
|
|
1213
|
-
if (this.recordRoutes.get(recordId) === route) this.recordRoutes.delete(recordId);
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
1563
|
/** 健康检查(ADR-0047:静默 ≠ 卡死,不据此杀任务)。 */
|
|
1217
1564
|
async ping() {
|
|
1218
1565
|
await this.ensureConnected();
|
|
@@ -1248,7 +1595,7 @@ var EngineClient = class {
|
|
|
1248
1595
|
try {
|
|
1249
1596
|
await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
|
|
1250
1597
|
} catch (err) {
|
|
1251
|
-
|
|
1598
|
+
logger10.debug(
|
|
1252
1599
|
`[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
|
|
1253
1600
|
);
|
|
1254
1601
|
}
|
|
@@ -1260,18 +1607,20 @@ var EngineClient = class {
|
|
|
1260
1607
|
onEngineExit(code, signal) {
|
|
1261
1608
|
const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
|
|
1262
1609
|
if (this.intentionalKill) {
|
|
1263
|
-
|
|
1610
|
+
logger10.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
|
|
1264
1611
|
} else {
|
|
1265
|
-
|
|
1612
|
+
logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
|
|
1266
1613
|
}
|
|
1267
1614
|
this.teardownProcess(detail);
|
|
1268
1615
|
this.state = "exited";
|
|
1269
1616
|
}
|
|
1270
1617
|
/**
|
|
1271
|
-
* 进程死亡 /
|
|
1272
|
-
*
|
|
1618
|
+
* 进程死亡 / 失败后的统一清理(收割/组杀分支各成子函数,此处只做顺序编排):
|
|
1619
|
+
* 孤儿收割(镜像置死前)→ 镜像置死(失效语义 2+3)→ pidfile 删 → 在途请求
|
|
1620
|
+
* engine_crashed(附 stderr 尾)→ run 路由清空 → 活引擎组杀兜底。
|
|
1273
1621
|
*/
|
|
1274
1622
|
teardownProcess(detail) {
|
|
1623
|
+
this.reapOrphansAfterUnexpectedDeath(detail);
|
|
1275
1624
|
this.mirror.killAll();
|
|
1276
1625
|
if (this.pidfileWritten !== void 0) {
|
|
1277
1626
|
removePidfile(this.pidfileWritten);
|
|
@@ -1284,8 +1633,46 @@ var EngineClient = class {
|
|
|
1284
1633
|
}
|
|
1285
1634
|
this.pending.clear();
|
|
1286
1635
|
this.runRoutes.clear();
|
|
1287
|
-
this.recordRoutes.clear();
|
|
1288
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) {
|
|
1289
1676
|
if (this.child !== void 0) {
|
|
1290
1677
|
const child = this.child;
|
|
1291
1678
|
this.child = void 0;
|
|
@@ -1295,8 +1682,30 @@ var EngineClient = class {
|
|
|
1295
1682
|
}
|
|
1296
1683
|
child.removeAllListeners();
|
|
1297
1684
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
+
});
|
|
1300
1709
|
}
|
|
1301
1710
|
}
|
|
1302
1711
|
// ── pidfile ──────────────────────────────────────────────────────────────
|
|
@@ -1311,7 +1720,7 @@ var EngineClient = class {
|
|
|
1311
1720
|
`);
|
|
1312
1721
|
return true;
|
|
1313
1722
|
} catch (err) {
|
|
1314
|
-
|
|
1723
|
+
logger10.debug(
|
|
1315
1724
|
`[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1316
1725
|
);
|
|
1317
1726
|
return false;
|
|
@@ -1334,11 +1743,25 @@ function truncate(text, max) {
|
|
|
1334
1743
|
}
|
|
1335
1744
|
|
|
1336
1745
|
// src/execution/engine/client/remote-engine.ts
|
|
1746
|
+
import { homedir as homedir2 } from "os";
|
|
1747
|
+
import { join as join4 } from "path";
|
|
1337
1748
|
import {
|
|
1338
1749
|
CANCEL_SETTLE_GRACE_MS as CANCEL_SETTLE_GRACE_MS2,
|
|
1339
1750
|
EngineSdkError as EngineSdkError2
|
|
1340
1751
|
} from "@zhushanwen/subagent-engine-sdk";
|
|
1341
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
|
+
|
|
1342
1765
|
// src/execution/engine/common/capability-gate.ts
|
|
1343
1766
|
import { engineConversationUnsupportedError } from "@zhushanwen/subagent-engine-sdk";
|
|
1344
1767
|
|
|
@@ -1353,7 +1776,7 @@ var EngineError = class extends Error {
|
|
|
1353
1776
|
this.code = code;
|
|
1354
1777
|
this.recovery = recovery;
|
|
1355
1778
|
}
|
|
1356
|
-
/** 结构化投影(
|
|
1779
|
+
/** 结构化投影(code/message 与 GUI 警告条共用形态)。 */
|
|
1357
1780
|
toStructured() {
|
|
1358
1781
|
return { code: this.code, message: this.message, recovery: this.recovery };
|
|
1359
1782
|
}
|
|
@@ -1410,6 +1833,13 @@ function assertGateCapabilitiesMatched(engineId, manifestCaps, answeredCaps) {
|
|
|
1410
1833
|
throw gateMismatchError(engineId, "sandbox\uFF08worktree\uFF09", manifestCaps.sandbox, answeredCaps.sandbox);
|
|
1411
1834
|
}
|
|
1412
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
|
+
}
|
|
1413
1843
|
function gateMismatchError(engineId, cap, declared, answered) {
|
|
1414
1844
|
return new EngineError(
|
|
1415
1845
|
"engine_capability_mismatch",
|
|
@@ -1531,22 +1961,6 @@ var RemoteEngine = class {
|
|
|
1531
1961
|
unregister();
|
|
1532
1962
|
}
|
|
1533
1963
|
}
|
|
1534
|
-
async interact(handle, action) {
|
|
1535
|
-
await this.opts.client.ensureConnected();
|
|
1536
|
-
const result = await this.opts.client.request("interact", {
|
|
1537
|
-
handle: handle.data,
|
|
1538
|
-
action
|
|
1539
|
-
});
|
|
1540
|
-
return result;
|
|
1541
|
-
}
|
|
1542
|
-
/**
|
|
1543
|
-
* [W3 v1.x] chat 轮次反向通道路由注册(recordId 键,EnginePort 可选面实现):
|
|
1544
|
-
* interact 续聊轮的 streamDelta / roundLifecycle 分发目标。薄委托 EngineClient
|
|
1545
|
-
* 的 recordRoutes(键分发见 reverse-router.ts)。
|
|
1546
|
-
*/
|
|
1547
|
-
registerChatRoundRoute(recordId, route) {
|
|
1548
|
-
return this.opts.client.registerRecordRoute(recordId, route);
|
|
1549
|
-
}
|
|
1550
1964
|
/** 协议 read(dataDir 必填——引擎数据根,构造注入)。 */
|
|
1551
1965
|
async read(handle) {
|
|
1552
1966
|
await this.opts.client.ensureConnected();
|
|
@@ -1578,6 +1992,15 @@ function isTransientRunFailure(err) {
|
|
|
1578
1992
|
if (!(err instanceof EngineSdkError2)) return false;
|
|
1579
1993
|
return err.code === "engine_crashed" || err.code === "engine_request_timeout" || err.code === "engine_handshake_timeout";
|
|
1580
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
|
+
}
|
|
1581
2004
|
function buildRunParams(task, ctx, runId) {
|
|
1582
2005
|
const ctxModelRef = ctx.ctxModel ? `${ctx.ctxModel.provider}/${ctx.ctxModel.id}` : void 0;
|
|
1583
2006
|
return {
|
|
@@ -1590,9 +2013,16 @@ function buildRunParams(task, ctx, runId) {
|
|
|
1590
2013
|
schemaEnv: ctx.schemaEnv ?? task.schemaEnv,
|
|
1591
2014
|
ctxModel: ctxModelRef,
|
|
1592
2015
|
engineFallback: ctx.engineFallback,
|
|
1593
|
-
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()
|
|
1594
2024
|
},
|
|
1595
|
-
...ctx.
|
|
2025
|
+
...ctx.resume !== void 0 ? { resume: ctx.resume } : {}
|
|
1596
2026
|
};
|
|
1597
2027
|
}
|
|
1598
2028
|
function buildRunRouteHandlers(ctx) {
|
|
@@ -1600,9 +2030,7 @@ function buildRunRouteHandlers(ctx) {
|
|
|
1600
2030
|
onEvent: (event) => ctx.onEvent?.(event),
|
|
1601
2031
|
onStreamDelta: (delta) => ctx.stream?.onDelta(delta),
|
|
1602
2032
|
onPoolResolved: (poolKey) => ctx.onPoolResolved?.(poolKey),
|
|
1603
|
-
onHandleReady: (partial) => ctx.onHandleReady?.(partial)
|
|
1604
|
-
// [W3 v1.x] 首轮(run 会话形态)轮次生命周期帧(runId 键)→ 宿主消费口。
|
|
1605
|
-
onRoundLifecycle: (phase) => ctx.onRoundLifecycle?.(phase)
|
|
2033
|
+
onHandleReady: (partial) => ctx.onHandleReady?.(partial)
|
|
1606
2034
|
};
|
|
1607
2035
|
}
|
|
1608
2036
|
function wireAbortSignal(client, runId, ctx) {
|
|
@@ -1695,11 +2123,11 @@ function getHostUiRequestEndpoint() {
|
|
|
1695
2123
|
}
|
|
1696
2124
|
|
|
1697
2125
|
// src/execution/engine/engine-inspect-package.ts
|
|
1698
|
-
var
|
|
2126
|
+
var logger11 = getLogger("subagents");
|
|
1699
2127
|
function readEnginePackageJson(pkgDir) {
|
|
1700
2128
|
let raw;
|
|
1701
2129
|
try {
|
|
1702
|
-
raw = fs.readFileSync(
|
|
2130
|
+
raw = fs.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
|
|
1703
2131
|
} catch (err) {
|
|
1704
2132
|
return { status: "skip", reason: `package.json unreadable: ${errorMessage(err)}` };
|
|
1705
2133
|
}
|
|
@@ -1774,12 +2202,12 @@ function parseOptionalDisplayFields(m, id) {
|
|
|
1774
2202
|
if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
|
|
1775
2203
|
displayName = rawDisplayName;
|
|
1776
2204
|
} else {
|
|
1777
|
-
|
|
2205
|
+
logger11.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
|
|
1778
2206
|
}
|
|
1779
2207
|
}
|
|
1780
2208
|
const rawDescription = m["description"];
|
|
1781
2209
|
if (rawDescription !== void 0 && typeof rawDescription !== "string") {
|
|
1782
|
-
|
|
2210
|
+
logger11.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
|
|
1783
2211
|
}
|
|
1784
2212
|
return displayName;
|
|
1785
2213
|
}
|
|
@@ -1867,19 +2295,19 @@ function errorMessage(err) {
|
|
|
1867
2295
|
|
|
1868
2296
|
// src/execution/engine/engine-discovery-roots.ts
|
|
1869
2297
|
import * as fs2 from "fs";
|
|
1870
|
-
import * as
|
|
1871
|
-
var
|
|
2298
|
+
import * as path4 from "path";
|
|
2299
|
+
var logger12 = getLogger("subagents");
|
|
1872
2300
|
var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
|
|
1873
2301
|
function parseEngineRootsEnv(env) {
|
|
1874
2302
|
const raw = env[ENGINE_ROOTS_ENV];
|
|
1875
2303
|
if (raw === void 0 || raw.trim() === "") return [];
|
|
1876
2304
|
const out = [];
|
|
1877
2305
|
const seen = /* @__PURE__ */ new Set();
|
|
1878
|
-
for (const part of raw.split(
|
|
2306
|
+
for (const part of raw.split(path4.delimiter)) {
|
|
1879
2307
|
const dir = part.trim();
|
|
1880
2308
|
if (dir === "") continue;
|
|
1881
|
-
if (!
|
|
1882
|
-
|
|
2309
|
+
if (!path4.isAbsolute(dir)) {
|
|
2310
|
+
logger12.warn(
|
|
1883
2311
|
`[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
|
|
1884
2312
|
);
|
|
1885
2313
|
continue;
|
|
@@ -1893,11 +2321,11 @@ function parseEngineRootsEnv(env) {
|
|
|
1893
2321
|
function deriveNodeModuleRoots(argvEntry = process.argv[1]) {
|
|
1894
2322
|
if (!argvEntry || argvEntry.trim() === "") return [];
|
|
1895
2323
|
const roots = [];
|
|
1896
|
-
let dir =
|
|
2324
|
+
let dir = path4.dirname(path4.resolve(argvEntry));
|
|
1897
2325
|
while (true) {
|
|
1898
|
-
const nm =
|
|
2326
|
+
const nm = path4.join(dir, "node_modules");
|
|
1899
2327
|
if (isDirectory(nm)) roots.push(nm);
|
|
1900
|
-
const parent =
|
|
2328
|
+
const parent = path4.dirname(dir);
|
|
1901
2329
|
if (parent === dir) break;
|
|
1902
2330
|
dir = parent;
|
|
1903
2331
|
}
|
|
@@ -1912,8 +2340,8 @@ function scanEngineRoot(root, visit) {
|
|
|
1912
2340
|
}
|
|
1913
2341
|
for (const item of entries) {
|
|
1914
2342
|
if (!item.isDirectory()) continue;
|
|
1915
|
-
const dir =
|
|
1916
|
-
if (isFile(
|
|
2343
|
+
const dir = path4.join(root, item.name);
|
|
2344
|
+
if (isFile(path4.join(dir, "package.json"))) {
|
|
1917
2345
|
visit(dir);
|
|
1918
2346
|
continue;
|
|
1919
2347
|
}
|
|
@@ -1925,8 +2353,8 @@ function scanEngineRoot(root, visit) {
|
|
|
1925
2353
|
}
|
|
1926
2354
|
for (const child of sub) {
|
|
1927
2355
|
if (!child.isDirectory()) continue;
|
|
1928
|
-
const subDir =
|
|
1929
|
-
if (isFile(
|
|
2356
|
+
const subDir = path4.join(dir, child.name);
|
|
2357
|
+
if (isFile(path4.join(subDir, "package.json"))) visit(subDir);
|
|
1930
2358
|
}
|
|
1931
2359
|
}
|
|
1932
2360
|
}
|
|
@@ -1947,10 +2375,10 @@ function isFile(p) {
|
|
|
1947
2375
|
|
|
1948
2376
|
// src/execution/engine/config.ts
|
|
1949
2377
|
import * as fs3 from "fs";
|
|
1950
|
-
import * as
|
|
1951
|
-
var
|
|
2378
|
+
import * as path5 from "path";
|
|
2379
|
+
var logger13 = getLogger("subagents");
|
|
1952
2380
|
function readExplicitEngines(agentDir) {
|
|
1953
|
-
const configPath =
|
|
2381
|
+
const configPath = path5.join(agentDir, "subagents", "config.json");
|
|
1954
2382
|
let parsed;
|
|
1955
2383
|
try {
|
|
1956
2384
|
parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
@@ -1961,7 +2389,7 @@ function readExplicitEngines(agentDir) {
|
|
|
1961
2389
|
const engines = parsed["engines"];
|
|
1962
2390
|
if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
|
|
1963
2391
|
if (engines !== void 0) {
|
|
1964
|
-
|
|
2392
|
+
logger13.warn(
|
|
1965
2393
|
`[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
|
|
1966
2394
|
);
|
|
1967
2395
|
}
|
|
@@ -1989,7 +2417,7 @@ function sanitizeExplicitEntry(id, raw) {
|
|
|
1989
2417
|
}
|
|
1990
2418
|
function requireEntryObject(id, raw) {
|
|
1991
2419
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1992
|
-
|
|
2420
|
+
logger13.warn(
|
|
1993
2421
|
`[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
|
|
1994
2422
|
);
|
|
1995
2423
|
return void 0;
|
|
@@ -1998,7 +2426,7 @@ function requireEntryObject(id, raw) {
|
|
|
1998
2426
|
}
|
|
1999
2427
|
function requireCommand(id, command) {
|
|
2000
2428
|
if (typeof command !== "string" || command.trim() === "") {
|
|
2001
|
-
|
|
2429
|
+
logger13.warn(
|
|
2002
2430
|
`[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
|
|
2003
2431
|
);
|
|
2004
2432
|
return void 0;
|
|
@@ -2007,7 +2435,7 @@ function requireCommand(id, command) {
|
|
|
2007
2435
|
}
|
|
2008
2436
|
function normalizeArgs(id, args) {
|
|
2009
2437
|
if (args !== void 0 && !isStringArray(args)) {
|
|
2010
|
-
|
|
2438
|
+
logger13.warn(
|
|
2011
2439
|
`[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
|
|
2012
2440
|
);
|
|
2013
2441
|
}
|
|
@@ -2015,7 +2443,7 @@ function normalizeArgs(id, args) {
|
|
|
2015
2443
|
}
|
|
2016
2444
|
function normalizeConfig(id, config) {
|
|
2017
2445
|
if (config !== void 0 && !isStringRecord(config)) {
|
|
2018
|
-
|
|
2446
|
+
logger13.warn(
|
|
2019
2447
|
`[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
|
|
2020
2448
|
);
|
|
2021
2449
|
}
|
|
@@ -2023,7 +2451,7 @@ function normalizeConfig(id, config) {
|
|
|
2023
2451
|
}
|
|
2024
2452
|
function normalizeCwd(id, cwd) {
|
|
2025
2453
|
if (cwd !== void 0 && typeof cwd !== "string") {
|
|
2026
|
-
|
|
2454
|
+
logger13.warn(
|
|
2027
2455
|
`[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
|
|
2028
2456
|
);
|
|
2029
2457
|
}
|
|
@@ -2043,7 +2471,7 @@ function jsonKindOf(value) {
|
|
|
2043
2471
|
}
|
|
2044
2472
|
|
|
2045
2473
|
// src/execution/engine/registry.ts
|
|
2046
|
-
var
|
|
2474
|
+
var logger14 = getLogger("subagents");
|
|
2047
2475
|
var DEFAULT_ENGINE_ID = "pi";
|
|
2048
2476
|
function normalizeEngineId(engine) {
|
|
2049
2477
|
return engine?.trim() || DEFAULT_ENGINE_ID;
|
|
@@ -2083,12 +2511,12 @@ function triggerEngineDispose(engine, source) {
|
|
|
2083
2511
|
if (typeof engine.dispose !== "function") return;
|
|
2084
2512
|
try {
|
|
2085
2513
|
engine.dispose().then(void 0, (err) => {
|
|
2086
|
-
|
|
2514
|
+
logger14.warn(
|
|
2087
2515
|
`[engine-registry] engine '${engine.id}' dispose rejected (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
|
|
2088
2516
|
);
|
|
2089
2517
|
});
|
|
2090
2518
|
} catch (err) {
|
|
2091
|
-
|
|
2519
|
+
logger14.warn(
|
|
2092
2520
|
`[engine-registry] engine '${engine.id}' dispose threw synchronously (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
|
|
2093
2521
|
);
|
|
2094
2522
|
}
|
|
@@ -2131,14 +2559,14 @@ function listEnginesByDisplayName() {
|
|
|
2131
2559
|
}
|
|
2132
2560
|
|
|
2133
2561
|
// src/execution/engine/engine-discovery-scan.ts
|
|
2134
|
-
var
|
|
2562
|
+
var logger15 = getLogger("subagents");
|
|
2135
2563
|
function scanEngines(opts) {
|
|
2136
2564
|
const env = opts.env ?? process.env;
|
|
2137
2565
|
const result = { discovered: [], skipped: [], unusable: [] };
|
|
2138
2566
|
const present = (entry) => {
|
|
2139
2567
|
const idx = result.discovered.findIndex((d) => d.id === entry.id);
|
|
2140
2568
|
if (idx >= 0) {
|
|
2141
|
-
|
|
2569
|
+
logger15.debug(
|
|
2142
2570
|
`[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
|
|
2143
2571
|
);
|
|
2144
2572
|
result.discovered[idx] = entry;
|
|
@@ -2150,13 +2578,13 @@ function scanEngines(opts) {
|
|
|
2150
2578
|
const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
|
|
2151
2579
|
if (inspection.status === "skip") {
|
|
2152
2580
|
if (!inspection.reason.includes("not an engine package")) {
|
|
2153
|
-
|
|
2581
|
+
logger15.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
|
|
2154
2582
|
result.skipped.push({ pkgDir, reason: inspection.reason });
|
|
2155
2583
|
}
|
|
2156
2584
|
return;
|
|
2157
2585
|
}
|
|
2158
2586
|
if (inspection.status === "unusable") {
|
|
2159
|
-
|
|
2587
|
+
logger15.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
|
|
2160
2588
|
result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
|
|
2161
2589
|
return;
|
|
2162
2590
|
}
|
|
@@ -2181,7 +2609,7 @@ function scanEngines(opts) {
|
|
|
2181
2609
|
const command = resolveExplicitCommand(entry.command, env);
|
|
2182
2610
|
if (command === void 0) {
|
|
2183
2611
|
const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
|
|
2184
|
-
|
|
2612
|
+
logger15.warn(`[engine-discovery] ${reason} \u2014 not registering`);
|
|
2185
2613
|
result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
|
|
2186
2614
|
continue;
|
|
2187
2615
|
}
|
|
@@ -2198,7 +2626,7 @@ function discoverAndRegisterEngines(opts) {
|
|
|
2198
2626
|
registerEngineDescriptor(entry.id, entry.descriptor);
|
|
2199
2627
|
loadedDiscoveryIdSet.add(entry.id);
|
|
2200
2628
|
if (existed) {
|
|
2201
|
-
|
|
2629
|
+
logger15.debug(
|
|
2202
2630
|
`[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
|
|
2203
2631
|
);
|
|
2204
2632
|
}
|
|
@@ -2215,20 +2643,20 @@ function ensureEngineDiscovered(id, opts) {
|
|
|
2215
2643
|
}
|
|
2216
2644
|
function resolveExplicitCommand(command, env) {
|
|
2217
2645
|
if (command.includes("/") || command.includes("\\")) {
|
|
2218
|
-
const p =
|
|
2646
|
+
const p = path6.isAbsolute(command) ? command : path6.resolve(command);
|
|
2219
2647
|
return canExecute(p) ? p : void 0;
|
|
2220
2648
|
}
|
|
2221
2649
|
const pathVar = env["PATH"] ?? env["Path"] ?? "";
|
|
2222
|
-
for (const dir of pathVar.split(
|
|
2650
|
+
for (const dir of pathVar.split(path6.delimiter)) {
|
|
2223
2651
|
if (dir === "") continue;
|
|
2224
|
-
const p =
|
|
2652
|
+
const p = path6.join(dir, command);
|
|
2225
2653
|
if (canExecute(p)) return p;
|
|
2226
2654
|
}
|
|
2227
2655
|
return void 0;
|
|
2228
2656
|
}
|
|
2229
2657
|
function buildExplicitDescriptor(id, command, entry, opts) {
|
|
2230
2658
|
const env = opts.env ?? process.env;
|
|
2231
|
-
|
|
2659
|
+
logger15.debug(
|
|
2232
2660
|
`[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
|
|
2233
2661
|
);
|
|
2234
2662
|
const caps = { ...CONSERVATIVE_CAPABILITIES };
|
|
@@ -2290,8 +2718,29 @@ export {
|
|
|
2290
2718
|
parseEnvPrefixes,
|
|
2291
2719
|
parseModelCatalog,
|
|
2292
2720
|
getEngineDataDir,
|
|
2721
|
+
MAX_TIMER_DELAY_MS,
|
|
2722
|
+
assertSafeTimerDelay,
|
|
2723
|
+
isDialogMethod,
|
|
2724
|
+
isValidDialogTimeout,
|
|
2725
|
+
DialogGlobalQueue,
|
|
2726
|
+
registerActiveDialogQueue,
|
|
2727
|
+
registerSpawnedChildForRecord,
|
|
2728
|
+
killRecordChildWithEscalation,
|
|
2729
|
+
killAllSpawnedChildren,
|
|
2730
|
+
DEFAULT_IDLE_TIMEOUT_MS,
|
|
2731
|
+
disarmIdleTimer,
|
|
2732
|
+
hasIdleTimer,
|
|
2733
|
+
hasLiveProcessHandle,
|
|
2734
|
+
isIdle,
|
|
2735
|
+
isResumable,
|
|
2736
|
+
setInFlightListener,
|
|
2737
|
+
getInFlightSnapshot,
|
|
2293
2738
|
EngineClient,
|
|
2739
|
+
encodeCwd,
|
|
2740
|
+
getSubagentSessionDir,
|
|
2741
|
+
getSubagentRecordsDir,
|
|
2294
2742
|
assertTaskShapeSupported,
|
|
2743
|
+
engineConversationUpgradeUnsupportedError,
|
|
2295
2744
|
RemoteEngine,
|
|
2296
2745
|
setHostUiRequestEndpoint,
|
|
2297
2746
|
inspectEnginePackage,
|