@zhushanwen/subagent-core 0.6.0 → 0.7.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/README.md +1 -1
- package/dist/chunk-KXVBIVOU.js +16 -0
- package/dist/chunk-T2SYBW3I.js +24 -0
- package/dist/chunk-VURUAGMM.js +2305 -0
- package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
- package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
- package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
- package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
- package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
- package/dist/execution/engine/engine-discovery-scan.js +20 -0
- package/dist/execution/engine/paths.cjs +6 -23
- package/dist/execution/engine/paths.d.cts +1 -8
- package/dist/execution/engine/paths.d.ts +1 -8
- package/dist/execution/engine/paths.js +1 -1
- package/dist/execution/relay-env.cjs +12 -28
- package/dist/execution/relay-env.d.cts +1 -33
- package/dist/execution/relay-env.d.ts +1 -33
- package/dist/execution/relay-env.js +5 -3
- package/dist/index.cjs +14045 -16059
- package/dist/index.d.cts +1035 -1579
- package/dist/index.d.ts +1035 -1579
- package/dist/index.js +9838 -13800
- package/dist.bundle/index.cjs +28875 -30323
- package/package.json +20 -26
- package/src/__tests__/record-store-last-line.test.ts +16 -7
- package/src/core/error-message.ts +7 -6
- package/src/core/host-services.ts +8 -2
- package/src/core/notify-ports.ts +17 -2
- package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
- package/src/execution/__tests__/delivery-methods.test.ts +179 -237
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -13
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
- package/src/execution/__tests__/gc-timer.test.ts +7 -22
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
- package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
- package/src/execution/__tests__/idle-gc.test.ts +200 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
- package/src/execution/__tests__/record-store.test.ts +60 -6
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
- package/src/execution/__tests__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
- package/src/execution/__tests__/subagent-service.test.ts +2 -2
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
- package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/agent-registry.ts +6 -2
- package/src/execution/dialog-queue.ts +6 -73
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
- package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
- package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
- package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
- package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
- package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
- package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
- package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
- package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
- package/src/execution/engine/__tests__/registry.test.ts +206 -1
- package/src/execution/engine/__tests__/routing.test.ts +97 -0
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
- package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
- package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
- package/src/execution/engine/client/client-options.ts +103 -0
- package/src/execution/engine/client/engine-client.ts +687 -0
- package/src/execution/engine/client/mirror.ts +167 -0
- package/src/execution/engine/client/pid-file.ts +392 -0
- package/src/execution/engine/client/reaper.ts +95 -0
- package/src/execution/engine/client/remote-engine.ts +441 -0
- package/src/execution/engine/client/reverse-router.ts +255 -0
- package/src/execution/engine/common/capability-gate.ts +109 -22
- package/src/execution/engine/common/errors.ts +9 -25
- package/src/execution/engine/common/event-journal.ts +4 -50
- package/src/execution/engine/common/journal-replay.ts +3 -1
- package/src/execution/engine/common/kill-chain.ts +18 -251
- package/src/execution/engine/common/nesting-guard.ts +18 -114
- package/src/execution/engine/common/pool-manager.ts +4 -0
- package/src/execution/engine/common/session-view-service.ts +56 -84
- package/src/execution/engine/config.ts +151 -0
- package/src/execution/engine/d8-compat.ts +346 -0
- package/src/execution/engine/engine-discovery-roots.ts +107 -0
- package/src/execution/engine/engine-discovery-scan.ts +269 -0
- package/src/execution/engine/engine-discovery.ts +30 -7
- package/src/execution/engine/engine-inspect-package.ts +282 -0
- package/src/execution/engine/engine-manifest.ts +244 -0
- package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
- package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
- package/src/execution/engine/host/host-bridge.ts +150 -0
- package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
- package/src/execution/engine/host/pi-host-binding.ts +91 -0
- package/src/execution/engine/host/spawned-children.ts +139 -0
- package/src/execution/engine/host-task-spec.ts +29 -2
- package/src/execution/engine/model-validation.ts +55 -2
- package/src/execution/engine/paths.ts +16 -41
- package/src/execution/engine/port.ts +48 -2
- package/src/execution/engine/registry.ts +153 -34
- package/src/execution/engine/routing.ts +225 -52
- package/src/execution/engine/types.ts +25 -200
- package/src/execution/finalize-record.ts +25 -0
- package/src/execution/idle-gc.ts +89 -14
- package/src/execution/lifecycle-manager.ts +10 -7
- package/src/execution/lifecycle-predicates.ts +10 -8
- package/src/execution/record-store.ts +29 -2
- package/src/execution/relay-env.ts +19 -36
- package/src/execution/round-supervisor/domain.ts +89 -0
- package/src/execution/round-supervisor/index.ts +33 -0
- package/src/execution/round-supervisor/notify-accounting.ts +87 -0
- package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
- package/src/execution/round-supervisor/service-binding.test.ts +661 -0
- package/src/execution/round-supervisor/service-binding.ts +273 -0
- package/src/execution/round-supervisor/supervisor.ts +426 -0
- package/src/execution/session-pending.ts +68 -74
- package/src/execution/settled-watchdog.ts +39 -0
- package/src/execution/subagent-service.ts +746 -400
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/types.ts +41 -117
- package/src/execution/ui-channels.ts +13 -214
- package/src/execution/ui-request-handler-factory.ts +7 -4
- package/src/execution/ui-request-observability.ts +2 -2
- package/src/execution/workflow-state-root.ts +67 -0
- package/src/execution/worktree-git-ops.ts +5 -0
- package/src/execution/worktree-manager.ts +11 -1
- package/src/index.ts +55 -26
- package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +144 -59
- package/src/orchestration/models/types.ts +43 -42
- package/src/shared/zcode-model-ref.ts +31 -0
- package/dist/chunk-43ONBFZX.js +0 -240
- package/dist/chunk-A4IVZWVX.js +0 -31
- package/dist/chunk-APZY4IME.js +0 -27
- package/dist/chunk-V3VHZ2VX.js +0 -59
- package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
- package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
- package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
- package/dist/execution/engine/engines/zcode/constants.js +0 -54
- package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
- package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
- package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
- package/dist/execution/engine/engines/zcode/reader.js +0 -9
- package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
- package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
- package/src/__tests__/session-runner.test.ts +0 -69
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
- package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
- package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
- package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
- package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
- package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
- package/src/execution/__tests__/output-collector.test.ts +0 -469
- package/src/execution/__tests__/pi-invocation.test.ts +0 -166
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
- package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
- package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
- package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
- package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
- package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
- package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
- package/src/execution/__tests__/spawn-args.test.ts +0 -446
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
- package/src/execution/__tests__/spawned-children.test.ts +0 -236
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/stdin-writer.test.ts +0 -464
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -616
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
- package/src/execution/__tests__/turn-limiter.test.ts +0 -65
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/common/schema-emulation.ts +0 -189
- package/src/execution/engine/common/session-view-projection.ts +0 -51
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
- package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
- package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
- package/src/execution/engine/engines/pi/output-collector.ts +0 -317
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
- package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
- package/src/execution/engine/engines/pi/reader.ts +0 -59
- package/src/execution/engine/engines/pi/registration.ts +0 -37
- package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
- package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
- package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
- package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
- package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
- package/src/execution/engine/engines/zcode/connection.ts +0 -598
- package/src/execution/engine/engines/zcode/constants.ts +0 -176
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
- package/src/execution/engine/engines/zcode/parser.ts +0 -102
- package/src/execution/engine/engines/zcode/preparer.ts +0 -235
- package/src/execution/engine/engines/zcode/reader.ts +0 -381
- package/src/execution/engine/engines/zcode/registration.ts +0 -37
- package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
- package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
- package/src/execution/round-settlement.ts +0 -93
- package/src/execution/ui-request-queue.ts +0 -215
- package/src/shared/schema-env.ts +0 -44
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/execution/engine/config.ts
|
|
2
|
+
//
|
|
3
|
+
// [W4] subagents/config.json 的 engines{} 段(三级发现的 L3 显式配置,设计 §3.4)。
|
|
4
|
+
//
|
|
5
|
+
// 契约(impl-plan §2.4):
|
|
6
|
+
// `engines: {"<id>": {command, args, config, cwd, enabled}}`
|
|
7
|
+
// - 无 `env` 键(v6 已删 extras 层——引擎 env 放行面归 manifest envPrefixes,L3 不重开);
|
|
8
|
+
// - `config` 经 initialize.engineConfig 透传(引擎不实现消费即死键,不校验内容);
|
|
9
|
+
// - `enabled: false` → 该引擎不装载(A12⑤:不进清单 + 派发报错含恢复指引)。
|
|
10
|
+
//
|
|
11
|
+
// 与 execution/config.ts 的关系:那边是全局配置的 sanitize 权威(maxConcurrent /
|
|
12
|
+
// defaultEngine / engineRouting / collectSync),其 SubagentsGlobalConfig 类型不含
|
|
13
|
+
// engines 键(sanitize 未知键忽略)。本模块只取 engines 段并逐条目校验——独立解析
|
|
14
|
+
// 避免 execution/config.ts 类型面为发现器扩键(发现器消费 raw 形态,字段级容错在本
|
|
15
|
+
// 模块内做,坏条目 warn 跳过、不影响其他条目)。
|
|
16
|
+
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
|
|
20
|
+
import { getLogger } from "../../core/logger.ts";
|
|
21
|
+
|
|
22
|
+
const logger = getLogger("subagents");
|
|
23
|
+
|
|
24
|
+
/** L3 显式引擎配置条目(config.json engines.<id>,字段级校验后形态)。 */
|
|
25
|
+
export interface ExplicitEngineEntry {
|
|
26
|
+
/** 引擎 CLI 命令(相对 PATH 的命令名或绝对/相对路径;装载期解析为可执行路径)。 */
|
|
27
|
+
command: string;
|
|
28
|
+
/** 引擎 CLI 参数(不含 command 本身;缺省 [])。 */
|
|
29
|
+
args: string[];
|
|
30
|
+
/** initialize.engineConfig 透传(Record<string,string>,缺省无)。 */
|
|
31
|
+
config?: Record<string, string>;
|
|
32
|
+
/** 引擎进程 cwd(缺省继承宿主进程)。 */
|
|
33
|
+
cwd?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 读 config.json 的 engines 段(已启用条目,键 = 引擎 id)。
|
|
38
|
+
*
|
|
39
|
+
* 容错契约(与 config.json 用户手编文件定位一致,execution/config.ts 同判):
|
|
40
|
+
* 文件缺失 / 坏 JSON / engines 段非对象 → 静默空表(L3 缺席是常态,不 warn——绝大多数
|
|
41
|
+
* 安装无显式配置);单条目形态坏 → warn 跳过该条目(不影响其他条目,A12②「其他引擎
|
|
42
|
+
* 正常」的 L3 版)。
|
|
43
|
+
*/
|
|
44
|
+
export function readExplicitEngines(agentDir: string): Record<string, ExplicitEngineEntry> {
|
|
45
|
+
const configPath = path.join(agentDir, "subagents", "config.json");
|
|
46
|
+
let parsed: unknown;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
49
|
+
} catch {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
if (typeof parsed !== "object" || parsed === null) return {};
|
|
53
|
+
const engines = (parsed as Record<string, unknown>)["engines"];
|
|
54
|
+
if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
|
|
55
|
+
if (engines !== undefined) {
|
|
56
|
+
logger.warn(
|
|
57
|
+
`[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} — ignoring L3 explicit engines`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
const out: Record<string, ExplicitEngineEntry> = {};
|
|
63
|
+
for (const [id, raw] of Object.entries(engines as Record<string, unknown>)) {
|
|
64
|
+
const entry = sanitizeExplicitEntry(id, raw);
|
|
65
|
+
if (entry !== undefined) out[id] = entry;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 单条目字段级校验:command 必需非空字符串;args/config/cwd 形态坏 → 丢弃该字段 + warn。 */
|
|
71
|
+
function sanitizeExplicitEntry(id: string, raw: unknown): ExplicitEngineEntry | undefined {
|
|
72
|
+
const v = requireEntryObject(id, raw);
|
|
73
|
+
if (v === undefined) return undefined;
|
|
74
|
+
if (v["enabled"] === false) return undefined; // A12⑤:显式禁用 = 不装载不进清单(非错误,不 warn)
|
|
75
|
+
const command = requireCommand(id, v["command"]);
|
|
76
|
+
if (command === undefined) return undefined;
|
|
77
|
+
return {
|
|
78
|
+
command,
|
|
79
|
+
...normalizeArgs(id, v["args"]),
|
|
80
|
+
...normalizeConfig(id, v["config"]),
|
|
81
|
+
...normalizeCwd(id, v["cwd"]),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 条目形态守卫:非对象(含数组/null)→ warn 跳过整条。 */
|
|
86
|
+
function requireEntryObject(id: string, raw: unknown): Record<string, unknown> | undefined {
|
|
87
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
88
|
+
logger.warn(
|
|
89
|
+
`[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} — skipping entry`,
|
|
90
|
+
);
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return raw as Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** command 校验:必需非空字符串,形态坏 → warn 跳过整条。 */
|
|
97
|
+
function requireCommand(id: string, command: unknown): string | undefined {
|
|
98
|
+
if (typeof command !== "string" || command.trim() === "") {
|
|
99
|
+
logger.warn(
|
|
100
|
+
`[engine-discovery] config.json engines.${id}.command is required (non-empty string) — skipping entry`,
|
|
101
|
+
);
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return command;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** args 字段:形态坏 warn 丢弃该字段;合法(或缺省)→ 条件 spread 形态。 */
|
|
108
|
+
function normalizeArgs(id: string, args: unknown): { args: string[] } {
|
|
109
|
+
if (args !== undefined && !isStringArray(args)) {
|
|
110
|
+
logger.warn(
|
|
111
|
+
`[engine-discovery] config.json engines.${id}.args must be a string array — ignoring args`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return { args: isStringArray(args) ? args : [] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** config 字段:形态坏 warn 丢弃该字段;合法 → 条件 spread 形态。 */
|
|
118
|
+
function normalizeConfig(id: string, config: unknown): { config?: Record<string, string> } {
|
|
119
|
+
if (config !== undefined && !isStringRecord(config)) {
|
|
120
|
+
logger.warn(
|
|
121
|
+
`[engine-discovery] config.json engines.${id}.config must be a Record<string, string> — ignoring config`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return isStringRecord(config) ? { config } : {};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** cwd 字段:形态坏 warn 丢弃该字段;合法 → 条件 spread 形态。 */
|
|
128
|
+
function normalizeCwd(id: string, cwd: unknown): { cwd?: string } {
|
|
129
|
+
if (cwd !== undefined && typeof cwd !== "string") {
|
|
130
|
+
logger.warn(
|
|
131
|
+
`[engine-discovery] config.json engines.${id}.cwd must be a string — ignoring cwd`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return typeof cwd === "string" ? { cwd } : {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isStringArray(value: unknown): value is string[] {
|
|
138
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
142
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
143
|
+
return Object.values(value as Record<string, unknown>).every((item) => typeof item === "string");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** unknown 值的 JSON 类型名(warn 文案用;不用 JSON.stringify——循环引用会炸)。 */
|
|
147
|
+
function jsonKindOf(value: unknown): string {
|
|
148
|
+
if (value === null) return "null";
|
|
149
|
+
if (Array.isArray(value)) return "array";
|
|
150
|
+
return typeof value;
|
|
151
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// src/execution/engine/d8-compat.ts
|
|
2
|
+
//
|
|
3
|
+
// [W8] D8 兼容公共面薄壳(设计 §3.6 D8 表 + impl-plan §2.8):宿主零改动的机械保证。
|
|
4
|
+
// 三个符号的兼容形态:
|
|
5
|
+
// - registerZcodeEngine(engineDataDir) 薄壳:确保 id 'zcode' 的 cli descriptor 已
|
|
6
|
+
// 注册,engineDataDir 经合成 env(XYZ_AGENT_DATA_DIR)记入 descriptor portFactory;
|
|
7
|
+
// - createZcodeEngine(deps) → RemoteEngine('zcode'):deps → 协议客户端映射
|
|
8
|
+
// (engineDataDir → dataDir / cliPath → command / processEnv → baseEnv /
|
|
9
|
+
// sources 不跨进程——不变量 5「引擎不落盘宿主数据、自行解析凭据」);
|
|
10
|
+
// - killAllSpawnedChildren 收敛在 engine/host/spawned-children 公共面([W3][F-7
|
|
11
|
+
// 注释纠偏] 仅镜像记账——core 侧 spawnedChildren 镜像整体置死,不发进程信号;
|
|
12
|
+
// 引擎进程回收 = stdin-EOF 自灭链(正常路径)+ disposeEngines() 显式触发
|
|
13
|
+
// cli 引擎 dispose(宿主 shutdown 链预留 API,现无生产接线),非「进程组收割」)。
|
|
14
|
+
//
|
|
15
|
+
// vendored 定位(设计 §3.6 二选一通道①):core 相对自身定位
|
|
16
|
+
// `<coreDir>/../<engine>-subagent-cli`(与 zsw core-ref.js:15 同款相对解析,零配置);
|
|
17
|
+
// 通道②(宿主注入 XYZ_AGENT_ENGINE_ROOTS)由发现器承载,薄壳在注册表已有 descriptor
|
|
18
|
+
// 时直接保留(发现器快照权威)。定位失败(打包态 staging 前 / 引擎包未安装):
|
|
19
|
+
// registerZcodeEngine 回退 inproc 注册(过渡期可用性优先,W11 删 inproc 后该分支
|
|
20
|
+
// 一并删除);createZcodeEngine 返回 command = 期望路径的 RemoteEngine——首次协议
|
|
21
|
+
// 调用 engine_crashed 显式报错(不变量 6「失败不静默」,构造同步不 throw,§3.5.3)。
|
|
22
|
+
//
|
|
23
|
+
// hostKind:registerZcodeEngine 缺省 'pi'(现调用方 = xyz-agent 扩展宿主组合根);
|
|
24
|
+
// createZcodeEngine 恒 'zsw'(消费方 = zsw CLI vendored 形态)——pidfile 实例维度
|
|
25
|
+
// 命名段(engine.<hostKind>.<hostPid>.pid),不同宿主进程 pid 不同不互误杀。
|
|
26
|
+
|
|
27
|
+
import * as fs from "node:fs";
|
|
28
|
+
import * as path from "node:path";
|
|
29
|
+
import { fileURLToPath } from "node:url";
|
|
30
|
+
|
|
31
|
+
import { EngineClient } from "./client/engine-client.ts";
|
|
32
|
+
import { RemoteEngine, type RemoteEngineManifestSnapshot } from "./client/remote-engine.ts";
|
|
33
|
+
import {
|
|
34
|
+
readRelayForwardEnv,
|
|
35
|
+
} from "../relay-env.ts";
|
|
36
|
+
import { getEngineDataDir } from "./common/data-dir.ts";
|
|
37
|
+
import {
|
|
38
|
+
CONSERVATIVE_CAPABILITIES,
|
|
39
|
+
parseCapabilities,
|
|
40
|
+
parseEnvPrefixes,
|
|
41
|
+
parseModelCatalog,
|
|
42
|
+
resolveManifestBin,
|
|
43
|
+
} from "./engine-manifest.ts";
|
|
44
|
+
import { inspectEnginePackage } from "./engine-discovery-scan.ts";
|
|
45
|
+
import { hasEngine, registerEngineDescriptor, type CliEngineDescriptor } from "./registry.ts";
|
|
46
|
+
import type { EngineCapabilities } from "./types.ts";
|
|
47
|
+
import type { EnginePort } from "./port.ts";
|
|
48
|
+
import type { ModelCatalogEntry } from "@zhushanwen/subagent-engine-sdk";
|
|
49
|
+
import { getLogger } from "../../core/logger.ts";
|
|
50
|
+
|
|
51
|
+
const logger = getLogger("subagents");
|
|
52
|
+
|
|
53
|
+
/** D8 薄壳服务的引擎 id(设计 §3.6 D8 表硬编码面;不用 engines/zcode/constants 的
|
|
54
|
+
* ZCODE_ENGINEID——薄壳生命周期跨 W11(inproc 目录删除后薄壳仍在),不绑内部模块)。 */
|
|
55
|
+
const D8_ZCODE_ENGINE_ID = "zcode";
|
|
56
|
+
|
|
57
|
+
/** createZcodeEngine 消费宿主(zsw CLI vendored 形态)——pidfile hostKind 命名段。 */
|
|
58
|
+
const D8_ZSW_HOST_KIND = "zsw";
|
|
59
|
+
|
|
60
|
+
/** registerZcodeEngine 缺省宿主(xyz-agent 扩展宿主组合根是现调用方)。 */
|
|
61
|
+
const D8_PI_HOST_KIND = "pi";
|
|
62
|
+
|
|
63
|
+
/** deps.sources 忽略的 warn-once(跨进程不可达面,重复注册不刷屏)。 */
|
|
64
|
+
let warnedSourcesIgnored = false;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* D8 兼容 deps 形状(zsw runner-core.js:163 实测调用面 {engineDataDir, cliPath?})。
|
|
68
|
+
* 与 engines/zcode ZcodeEngineDeps 结构兼容:zsw 传入对象可直接赋值;sources /
|
|
69
|
+
* probeVersion 为兼容残留字段——协议形态下不跨进程(忽略 + warn)。
|
|
70
|
+
*/
|
|
71
|
+
export interface D8CompatZcodeEngineDeps {
|
|
72
|
+
/** 引擎数据根(→ 引擎子进程 env XYZ_AGENT_DATA_DIR,见设计 §3.6 注入矩阵)。 */
|
|
73
|
+
engineDataDir: () => string;
|
|
74
|
+
/** 引擎 CLI 入口覆盖(L3 等价;缺省 = vendored 相对定位)。 */
|
|
75
|
+
cliPath?: string;
|
|
76
|
+
/** 兼容残留:模型来源/凭据路径——不跨进程(不变量 5),忽略。 */
|
|
77
|
+
sources?: unknown;
|
|
78
|
+
/** 兼容残留:版本探测执行器——协议 probe 由引擎实现承担,忽略。 */
|
|
79
|
+
probeVersion?: unknown;
|
|
80
|
+
/** env 基底合并(buildEngineChildEnv baseEnv;缺省 {...process.env})。 */
|
|
81
|
+
processEnv?: NodeJS.ProcessEnv;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* [W11 收口] 旧 ZcodeEngineDeps 的兼容别名:barrel 原类型导出(zsw 调用面的 deps
|
|
86
|
+
* 形状契约)与 D8CompatZcodeEngineDeps 合并——结构同源(engineDataDir/cliPath/
|
|
87
|
+
* sources/probeVersion/processEnv),别名保留免 zsw 侧机械改名。
|
|
88
|
+
*/
|
|
89
|
+
export type ZcodeEngineDeps = D8CompatZcodeEngineDeps;
|
|
90
|
+
|
|
91
|
+
/** vendored 引擎包定位结果。 */
|
|
92
|
+
interface LocatedEnginePkg {
|
|
93
|
+
pkgDir: string;
|
|
94
|
+
/** manifest bin 解析后的引擎 CLI 入口绝对路径(已过可执行校验)。 */
|
|
95
|
+
binPath: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 引擎包 manifest 的薄壳消费面(capabilities/envPrefixes/modelCatalog 快照)。 */
|
|
99
|
+
interface ManifestInfo {
|
|
100
|
+
capabilities: EngineCapabilities;
|
|
101
|
+
envPrefixes: string[];
|
|
102
|
+
modelCatalog?: { dynamic: boolean; models: ModelCatalogEntry[] } | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================
|
|
106
|
+
// vendored 定位(通道①)
|
|
107
|
+
// ============================================================
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* core 包根目录:本模块文件目录上 3 级(src 与 dist 同构——
|
|
111
|
+
* <pkg>/src/execution/engine/d8-compat.ts 与 <pkg>/dist/execution/engine/d8-compat.js
|
|
112
|
+
* 的 dirname 上 3 级都是包根)。不可得(bundle 进宿主无文件位置语义)→ undefined,
|
|
113
|
+
* 调用方走通道②(宿主注入发现根)或显式 cliPath。
|
|
114
|
+
*/
|
|
115
|
+
function corePackageDir(): string | undefined {
|
|
116
|
+
let moduleDir: string | undefined;
|
|
117
|
+
try {
|
|
118
|
+
moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
119
|
+
} catch {
|
|
120
|
+
moduleDir = undefined;
|
|
121
|
+
}
|
|
122
|
+
if (moduleDir === undefined && typeof __dirname !== "undefined") {
|
|
123
|
+
moduleDir = __dirname;
|
|
124
|
+
}
|
|
125
|
+
if (moduleDir === undefined) return undefined;
|
|
126
|
+
return path.resolve(moduleDir, "..", "..", "..");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 引擎包 npm 名(D8 vendored 布局约定 <engineId>-subagent-cli)。 */
|
|
130
|
+
function enginePkgName(engineId: string): string {
|
|
131
|
+
return `@zhushanwen/${engineId}-subagent-cli`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 期望的 vendored bin 路径(定位失败时 createZcodeEngine 的 command 占位——spawn 时
|
|
135
|
+
* engine_crashed 显式报错附恢复指引,不留静默)。 */
|
|
136
|
+
function expectedVendoredBinPath(engineId: string): string {
|
|
137
|
+
const pkgDir = attemptVendoredPkgDir(engineId);
|
|
138
|
+
return path.join(pkgDir, "bin", `${engineId}-subagent-cli.mjs`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function attemptVendoredPkgDir(engineId: string): string {
|
|
142
|
+
const coreDir = corePackageDir();
|
|
143
|
+
const packagesDir = coreDir !== undefined ? path.dirname(coreDir) : path.resolve("packages");
|
|
144
|
+
return path.join(packagesDir, `${engineId}-subagent-cli`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* vendored package.json 的 manifest 段原始解析(单一来源,locateVendoredEnginePkg 与
|
|
149
|
+
* readEngineManifest 共用):读取 pkgDir/package.json 并 duck-typed 三段下降
|
|
150
|
+
* pkg["xyz-agent"]["subagentEngine"]。读文件/JSON 解析失败或 manifest 段缺失/非对象
|
|
151
|
+
* (含 null 与数组)→ undefined。数组拒绝上收到共用守卫对两个调用点均结局等价:
|
|
152
|
+
* JSON.parse 产物为数组时不可能携带字符串键(JSON 语法限制),bin 提取路径对数组取
|
|
153
|
+
* ["bin"] 恒 undefined——与拒绝分支同收敛为定位失败。pkg 一并返回供调用方做 name
|
|
154
|
+
* 精确匹配与 bin 相对路径解析(单次解析,不重复读盘)。
|
|
155
|
+
*/
|
|
156
|
+
function readSubagentEngineManifestRaw(
|
|
157
|
+
pkgDir: string,
|
|
158
|
+
): { pkg: Record<string, unknown>; manifest: Record<string, unknown> } | undefined {
|
|
159
|
+
try {
|
|
160
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")) as Record<string, unknown>;
|
|
161
|
+
const agentNs = pkg["xyz-agent"];
|
|
162
|
+
const raw =
|
|
163
|
+
typeof agentNs === "object" && agentNs !== null
|
|
164
|
+
? (agentNs as Record<string, unknown>)["subagentEngine"]
|
|
165
|
+
: undefined;
|
|
166
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return undefined;
|
|
167
|
+
return { pkg, manifest: raw as Record<string, unknown> };
|
|
168
|
+
} catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* vendored 相对定位:`<coreDir>/../<engineId>-subagent-cli`。三重校验防同名目录冒充:
|
|
175
|
+
* package.json 可读 + name 精确匹配 + manifest bin 可执行。任一不成立 → undefined
|
|
176
|
+
* (调用方按通道②/显式 cliPath/期望路径降级)。
|
|
177
|
+
*/
|
|
178
|
+
function locateVendoredEnginePkg(engineId: string): LocatedEnginePkg | undefined {
|
|
179
|
+
const pkgDir = attemptVendoredPkgDir(engineId);
|
|
180
|
+
const parsed = readSubagentEngineManifestRaw(pkgDir);
|
|
181
|
+
if (parsed === undefined) return undefined;
|
|
182
|
+
const { pkg, manifest } = parsed;
|
|
183
|
+
if (pkg["name"] !== enginePkgName(engineId)) return undefined;
|
|
184
|
+
const manifestBin = manifest["bin"];
|
|
185
|
+
if (typeof manifestBin !== "string" || manifestBin.trim() === "") return undefined;
|
|
186
|
+
const binPath = resolveManifestBin(pkgDir, pkg, manifestBin);
|
|
187
|
+
if (binPath === undefined || !canExecute(binPath)) return undefined;
|
|
188
|
+
return { pkgDir, binPath };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** manifest 薄壳消费面解析(复用 W4 engine-manifest 字段级解析器——单源,不复制规则)。 */
|
|
192
|
+
function readEngineManifest(pkgDir: string): ManifestInfo | undefined {
|
|
193
|
+
const parsed = readSubagentEngineManifestRaw(pkgDir);
|
|
194
|
+
if (parsed === undefined) return undefined;
|
|
195
|
+
const manifest = parsed.manifest;
|
|
196
|
+
const id = typeof manifest["id"] === "string" ? manifest["id"] : D8_ZCODE_ENGINE_ID;
|
|
197
|
+
const modelCatalog = parseModelCatalog(id, manifest["modelCatalog"]);
|
|
198
|
+
return {
|
|
199
|
+
capabilities: parseCapabilities(id, manifest["capabilities"]),
|
|
200
|
+
envPrefixes: parseEnvPrefixes(id, manifest["envPrefixes"]),
|
|
201
|
+
...(modelCatalog !== undefined ? { modelCatalog } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function canExecute(p: string): boolean {
|
|
206
|
+
try {
|
|
207
|
+
fs.accessSync(p, fs.constants.X_OK);
|
|
208
|
+
return true;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================
|
|
215
|
+
// registerZcodeEngine 薄壳
|
|
216
|
+
// ============================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* D8 薄壳:确保 id 'zcode' 的引擎 descriptor 已注册,engineDataDir 记入 descriptor。
|
|
220
|
+
*
|
|
221
|
+
* 注册表已有 descriptor(W4 发现器先装载 / 重复调用)→ 直接保留——发现器 cli
|
|
222
|
+
* descriptor 携带完整 manifest 快照,权威高于薄壳构造。未注册 → vendored 定位构造
|
|
223
|
+
* cli descriptor(经 W4 inspectEnginePackage 全量 manifest 解析;engineDataDir 经
|
|
224
|
+
* 合成 env XYZ_AGENT_DATA_DIR 被 portFactory 捕获——「以显式值为准」)。[W11/DoD#5]
|
|
225
|
+
* 定位/解析失败保持未注册(inproc 回退已删,不变量 6「失败不静默」——派发期
|
|
226
|
+
* engine_not_found 显式报错 + 恢复指引;XYZ_ZCODE_CLI 覆盖通道由 createZcodeEngine
|
|
227
|
+
* 的 deps.cliPath 等价承载)。
|
|
228
|
+
*/
|
|
229
|
+
export function registerZcodeEngine(engineDataDir: () => string = getEngineDataDir): void {
|
|
230
|
+
const id = D8_ZCODE_ENGINE_ID;
|
|
231
|
+
if (hasEngine(id)) return;
|
|
232
|
+
// [W11/DoD#5] inproc 回退分支已删(不变量 6「失败不静默」):定位/解析失败保持
|
|
233
|
+
// 未注册——派发期 engine_not_found 显式报错(含「安装/发现引擎包」恢复指引),
|
|
234
|
+
// XYZ_ZCODE_CLI 覆盖通道由 createZcodeEngine 的 deps.cliPath 等价承载。
|
|
235
|
+
const located = locateVendoredEnginePkg(id);
|
|
236
|
+
if (located === undefined) {
|
|
237
|
+
logger.warn(
|
|
238
|
+
`[d8-compat] engine '${id}' vendored package not located (enginePkg ${enginePkgName(id)}); ` +
|
|
239
|
+
"left unregistered — dispatch will fail with engine_not_found + recovery guidance",
|
|
240
|
+
);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// 合成 env:显式 engineDataDir 写进 XYZ_AGENT_DATA_DIR,portFactory 闭包经
|
|
244
|
+
// getEngineDataDir(env) 解析即取显式值(W4 inspectEnginePackage 原样复用,零改动)。
|
|
245
|
+
const syntheticEnv: NodeJS.ProcessEnv = { ...process.env, XYZ_AGENT_DATA_DIR: engineDataDir() };
|
|
246
|
+
const inspection = inspectEnginePackage(located.pkgDir, "d8-compat", D8_PI_HOST_KIND, syntheticEnv);
|
|
247
|
+
if (inspection.status !== "ok") {
|
|
248
|
+
logger.warn(
|
|
249
|
+
`[d8-compat] engine '${id}' vendored package inspection failed (${inspection.reason}); ` +
|
|
250
|
+
"left unregistered — dispatch will fail with engine_not_found + recovery guidance",
|
|
251
|
+
);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
registerEngineDescriptor(id, inspection.entry.descriptor as CliEngineDescriptor);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================================
|
|
258
|
+
// createZcodeEngine 薄壳
|
|
259
|
+
// ============================================================
|
|
260
|
+
|
|
261
|
+
/** deps.sources 忽略的 warn-once(跨进程不可达面,重复注册不刷屏)。 */
|
|
262
|
+
function warnSourcesIgnoredOnce(deps: D8CompatZcodeEngineDeps): void {
|
|
263
|
+
if (deps.sources === undefined || warnedSourcesIgnored) return;
|
|
264
|
+
warnedSourcesIgnored = true;
|
|
265
|
+
logger.warn(
|
|
266
|
+
"[d8-compat] createZcodeEngine deps.sources is ignored (model/credential sources do not " +
|
|
267
|
+
"cross the process boundary — the engine resolves its own credentials, protocol invariant 5)",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* manifest 薄壳消费面解析 + 回退合成:vendored 包未定位/manifest 不可读 → capabilities
|
|
273
|
+
* 全保守 + envPrefixes 空(gate 同步拦生成,失败显式发生在首次协议调用)。
|
|
274
|
+
*/
|
|
275
|
+
function resolveManifestInfo(located: LocatedEnginePkg | undefined): ManifestInfo {
|
|
276
|
+
const info = located !== undefined ? readEngineManifest(located.pkgDir) : undefined;
|
|
277
|
+
return {
|
|
278
|
+
capabilities: info?.capabilities ?? { ...CONSERVATIVE_CAPABILITIES },
|
|
279
|
+
envPrefixes: info?.envPrefixes ?? [],
|
|
280
|
+
...(info?.modelCatalog !== undefined && info.modelCatalog !== null ? { modelCatalog: info.modelCatalog } : {}),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** RemoteEngine manifest 快照:capabilities 恒有;modelCatalog 三态语义(无枚举面不注入)。 */
|
|
285
|
+
function buildManifestSnapshot(manifestInfo: ManifestInfo): RemoteEngineManifestSnapshot {
|
|
286
|
+
return {
|
|
287
|
+
capabilities: manifestInfo.capabilities,
|
|
288
|
+
...(manifestInfo.modelCatalog !== undefined && manifestInfo.modelCatalog !== null
|
|
289
|
+
? { modelCatalog: manifestInfo.modelCatalog }
|
|
290
|
+
: {}),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** 协议客户端装配(H12 relay 透传 + manifestDiagnostics 诊断快照)。 */
|
|
295
|
+
function buildZcodeEngineClient(
|
|
296
|
+
id: string,
|
|
297
|
+
command: string,
|
|
298
|
+
dataDir: string,
|
|
299
|
+
baseEnv: NodeJS.ProcessEnv,
|
|
300
|
+
manifestInfo: ManifestInfo,
|
|
301
|
+
): EngineClient {
|
|
302
|
+
return new EngineClient({
|
|
303
|
+
engineId: id,
|
|
304
|
+
command,
|
|
305
|
+
args: [],
|
|
306
|
+
hostKind: D8_ZSW_HOST_KIND,
|
|
307
|
+
dataDir,
|
|
308
|
+
baseEnv,
|
|
309
|
+
envPrefixes: manifestInfo.envPrefixes,
|
|
310
|
+
// H12 relay 透传(zsw 宿主链路):宿主 env 的 relay 连接三键原样提取走 L0 注入;
|
|
311
|
+
// 身份键 SESSION_ID/RECORD_ID 由 buildEngineChildEnv L1 deny 剥除(引擎按
|
|
312
|
+
// run.params.ctx 重写,不靠 env 继承)。relay 未激活 → undefined → 不注入。
|
|
313
|
+
...(readRelayForwardEnv(baseEnv) !== undefined ? { relay: readRelayForwardEnv(baseEnv) } : {}),
|
|
314
|
+
manifestDiagnostics: {
|
|
315
|
+
capabilities: manifestInfo.capabilities,
|
|
316
|
+
models:
|
|
317
|
+
manifestInfo.modelCatalog === undefined || manifestInfo.modelCatalog === null
|
|
318
|
+
? null
|
|
319
|
+
: manifestInfo.modelCatalog.models,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* D8 薄壳:deps → 协议客户端映射,返回 RemoteEngine('zcode')(implements EnginePort,
|
|
326
|
+
* 对 zsw 透传结构兼容)。构造同步、不 throw(§3.5.3 代理形态)——引擎包缺失时
|
|
327
|
+
* command = 期望路径,失败显式发生在首次协议调用(engine_crashed + 恢复指引)。
|
|
328
|
+
*/
|
|
329
|
+
export function createZcodeEngine(deps: D8CompatZcodeEngineDeps): EnginePort {
|
|
330
|
+
const id = D8_ZCODE_ENGINE_ID;
|
|
331
|
+
warnSourcesIgnoredOnce(deps);
|
|
332
|
+
const dataDir = deps.engineDataDir();
|
|
333
|
+
const baseEnv: NodeJS.ProcessEnv = { ...process.env, ...(deps.processEnv ?? {}) };
|
|
334
|
+
const located = deps.cliPath !== undefined ? undefined : locateVendoredEnginePkg(id);
|
|
335
|
+
const command = deps.cliPath ?? located?.binPath ?? expectedVendoredBinPath(id);
|
|
336
|
+
const manifestInfo = resolveManifestInfo(located);
|
|
337
|
+
const manifestSnapshot = buildManifestSnapshot(manifestInfo);
|
|
338
|
+
const client = buildZcodeEngineClient(id, command, dataDir, baseEnv, manifestInfo);
|
|
339
|
+
return new RemoteEngine({
|
|
340
|
+
engineId: id,
|
|
341
|
+
client,
|
|
342
|
+
manifest: manifestSnapshot,
|
|
343
|
+
dataDir,
|
|
344
|
+
hostKind: D8_ZSW_HOST_KIND,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/execution/engine/engine-discovery-roots.ts
|
|
2
|
+
//
|
|
3
|
+
// [W4] 三级发现的搜索路径域(L1 env 根解析 / L2 node 解析域推导 / 发现根扫描)。
|
|
4
|
+
// 自 engine-discovery-scan.ts 拆出(max-lines 纪律)——路径收集与目录遍历是发现
|
|
5
|
+
// 主链的无状态前置面。设计权威源:§3.4 搜索路径表 + impl-plan §2.4。
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
import { getLogger } from "../../core/logger.ts";
|
|
11
|
+
|
|
12
|
+
const logger = getLogger("subagents");
|
|
13
|
+
|
|
14
|
+
/** L1 引擎发现根 env(设计 §3.4;值形态 = path.delimiter 分隔的绝对路径列表)。 */
|
|
15
|
+
export const ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
|
|
16
|
+
|
|
17
|
+
/** L1 env 根解析:path.delimiter 分隔;空段跳过;非绝对路径丢弃 + warn;去重(大小写敏感)。 */
|
|
18
|
+
export function parseEngineRootsEnv(env: NodeJS.ProcessEnv): string[] {
|
|
19
|
+
const raw = env[ENGINE_ROOTS_ENV];
|
|
20
|
+
if (raw === undefined || raw.trim() === "") return [];
|
|
21
|
+
const out: string[] = [];
|
|
22
|
+
const seen = new Set<string>();
|
|
23
|
+
for (const part of raw.split(path.delimiter)) {
|
|
24
|
+
const dir = part.trim();
|
|
25
|
+
if (dir === "") continue;
|
|
26
|
+
if (!path.isAbsolute(dir)) {
|
|
27
|
+
logger.warn(
|
|
28
|
+
`[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path — dropping entry`,
|
|
29
|
+
);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (seen.has(dir)) continue;
|
|
33
|
+
seen.add(dir);
|
|
34
|
+
out.push(dir);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* L2 根推导:宿主进程入口(process.argv[1])所在目录逐级上溯的 node_modules——与
|
|
41
|
+
* createRequire(<宿主入口>) 的 require.resolve 候选目录链同构(「宿主 node_modules
|
|
42
|
+
* require.resolve」的扫描化:零枚举发现要求扫目录而非 resolve 具体包名)。打包态
|
|
43
|
+
* (staged 扩展 / Bun standalone 无 node_modules 结构)与 zsw vendor 态自然为空——
|
|
44
|
+
* 规格明确两态 L2 无效。argv[1] 不可得(嵌入式 / worker)→ L2 缺席。
|
|
45
|
+
*/
|
|
46
|
+
export function deriveNodeModuleRoots(argvEntry: string | undefined = process.argv[1]): string[] {
|
|
47
|
+
if (!argvEntry || argvEntry.trim() === "") return [];
|
|
48
|
+
const roots: string[] = [];
|
|
49
|
+
let dir = path.dirname(path.resolve(argvEntry));
|
|
50
|
+
while (true) {
|
|
51
|
+
const nm = path.join(dir, "node_modules");
|
|
52
|
+
if (isDirectory(nm)) roots.push(nm);
|
|
53
|
+
const parent = path.dirname(dir);
|
|
54
|
+
if (parent === dir) break;
|
|
55
|
+
dir = parent;
|
|
56
|
+
}
|
|
57
|
+
return roots;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 扫描单个发现根:一级子目录直接含 package.json 即候选包;无 package.json 的目录
|
|
62
|
+
* 再下钻一层(org 分组布局:npm @scope/name、pi npm 安装位的 <org>/<pkg>)。二层封顶。
|
|
63
|
+
* 根不存在/不可读 → 静默(L2 上溯链多数层级无 node_modules,warn 会刷屏)。
|
|
64
|
+
*/
|
|
65
|
+
export function scanEngineRoot(root: string, visit: (pkgDir: string) => void): void {
|
|
66
|
+
let entries: fs.Dirent[];
|
|
67
|
+
try {
|
|
68
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
69
|
+
} catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const item of entries) {
|
|
73
|
+
if (!item.isDirectory()) continue;
|
|
74
|
+
const dir = path.join(root, item.name);
|
|
75
|
+
if (isFile(path.join(dir, "package.json"))) {
|
|
76
|
+
visit(dir);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let sub: fs.Dirent[];
|
|
80
|
+
try {
|
|
81
|
+
sub = fs.readdirSync(dir, { withFileTypes: true });
|
|
82
|
+
} catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const child of sub) {
|
|
86
|
+
if (!child.isDirectory()) continue;
|
|
87
|
+
const subDir = path.join(dir, child.name);
|
|
88
|
+
if (isFile(path.join(subDir, "package.json"))) visit(subDir);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isDirectory(p: string): boolean {
|
|
94
|
+
try {
|
|
95
|
+
return fs.statSync(p).isDirectory();
|
|
96
|
+
} catch {
|
|
97
|
+
return false; // ENOENT 等 → 非目录
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isFile(p: string): boolean {
|
|
102
|
+
try {
|
|
103
|
+
return fs.statSync(p).isFile();
|
|
104
|
+
} catch {
|
|
105
|
+
return false; // ENOENT 等 → 非文件
|
|
106
|
+
}
|
|
107
|
+
}
|