@zhushanwen/subagent-core 0.2.0 → 0.4.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 +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-4KN4TTG7.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-X7SZ5HLQ.js +66 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +65 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +93 -1
- package/dist/execution/engine/engines/zcode/constants.d.ts +93 -1
- package/dist/execution/engine/engines/zcode/constants.js +43 -1
- package/dist/execution/engine/engines/zcode/reader.d.cts +1 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +1 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +14709 -100
- package/dist/index.d.cts +4053 -200
- package/dist/index.d.ts +4053 -200
- package/dist/index.js +14358 -102
- package/dist/{types-BxyAidGf.d.cts → types-DpUO16pj.d.cts} +464 -1
- package/dist/{types-BxyAidGf.d.ts → types-DpUO16pj.d.ts} +464 -1
- package/package.json +6 -3
- package/src/__tests__/review-fix-loop-script.test.ts +90 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +21 -0
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +71 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +1 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +2 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/epipe-fallback.test.ts +2 -0
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/finalize-record.test.ts +106 -0
- package/src/execution/__tests__/gc-timer.test.ts +2 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +2 -0
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +378 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/notify-ledger.test.ts +294 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +83 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +4 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +1 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +407 -17
- package/src/execution/__tests__/run-spawn-integration.test.ts +1 -1
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +12 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +89 -0
- package/src/execution/__tests__/session-pending.test.ts +228 -0
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +1 -0
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +226 -0
- package/src/execution/__tests__/settled-watchdog.test.ts +289 -0
- package/src/execution/__tests__/spawned-children.test.ts +153 -10
- package/src/execution/__tests__/subagent-actions-core.test.ts +997 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +71 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +266 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +3 -0
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +316 -0
- package/src/execution/__tests__/timeout-integration.test.ts +7 -7
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +95 -1
- package/src/execution/__tests__/ui-request-queue.test.ts +25 -0
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +225 -1
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +49 -2
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +87 -1
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +26 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +83 -1
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +131 -0
- package/src/execution/engine/__tests__/registry.test.ts +90 -1
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +3 -3
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +274 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +472 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +387 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +780 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +815 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +462 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +119 -1
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +136 -6
- package/src/execution/engine/engines/zcode/appserver-home.ts +442 -0
- package/src/execution/engine/engines/zcode/appserver-probe.ts +141 -0
- package/src/execution/engine/engines/zcode/connection.ts +585 -0
- package/src/execution/engine/engines/zcode/constants.ts +129 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +54 -8
- package/src/execution/engine/engines/zcode/preparer.ts +25 -22
- package/src/execution/engine/engines/zcode/session-channel.ts +656 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +909 -41
- package/src/execution/engine/host-task-spec.ts +3 -3
- package/src/execution/engine/port.ts +41 -1
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/finalize-record.ts +63 -5
- package/src/execution/get-state-handshake.ts +85 -10
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +29 -15
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-runner.ts +974 -128
- package/src/execution/sessions-index.ts +10 -55
- package/src/execution/settled-watchdog.ts +99 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +396 -49
- package/src/execution/ui-request-handler-factory.ts +27 -4
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +413 -6
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -4
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +1 -8
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +382 -0
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +389 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +77 -0
- package/src/orchestration/__tests__/run-snapshot.test.ts +353 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +7 -7
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/error-recovery.ts +416 -146
- package/src/orchestration/execute-agent-call.ts +9 -9
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +35 -26
- package/src/orchestration/lifecycle.ts +355 -79
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/run-runtime.ts +13 -13
- package/src/orchestration/models/trace.ts +8 -8
- package/src/orchestration/models/workflow-run.ts +27 -27
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +33 -33
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +3 -2
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +320 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +41 -1
- package/src/shared/resource-discovery.ts +104 -37
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +14 -3
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
// src/execution/engine/engines/zcode/zcode-engine.ts
|
|
2
2
|
//
|
|
3
|
-
// ZcodeEngine(P3
|
|
4
|
-
// docs/architecture/subagent-engine-abstraction.md D10(MVP
|
|
5
|
-
//
|
|
3
|
+
// ZcodeEngine(P3 → R4 双模式):zcode CLI 的 EnginePort 实现。设计权威源:
|
|
4
|
+
// docs/architecture/subagent-engine-abstraction.md D10(MVP 引擎集)/ §3.3.4(reviewer@zcode
|
|
5
|
+
// 物理数据流)/ §3.3.5(run 错误语义三条);docs/design/zcode-engine-appserver-resident.md
|
|
6
|
+
// §3.3 D1(每引擎实例一条连接)/ D3(abort 链)/ D4(会话自包含)/ D5(capabilities
|
|
7
|
+
// 升级序)/ D6(停机面 + 孤儿自愈)/ D7(常驻 HOME)/ §3.4 不变量 1-4。
|
|
8
|
+
//
|
|
9
|
+
// 双模式分派(R4;D2 降级链的骨架):
|
|
10
|
+
// - appserver(缺省):惰性常驻连接(connection.ts + session-channel.ts)上的
|
|
11
|
+
// create→subscribe→send→事件流→终态→read→close;per-session model 经 create
|
|
12
|
+
// 参数透传;常驻 HOME = engines/zcode/home-appserver(D7 全量语义见 appserver-home.ts);
|
|
13
|
+
// 缺省路径带 R5 降级链门控:协议冒烟探针(appserver-probe.ts,结论与 CLI mtime
|
|
14
|
+
// 绑定)失败 → 本任务起 spawn 直走;首任务命中漂移类 RPC 错误(-32601/-32602)
|
|
15
|
+
// → 本任务降级 spawn 重跑一次 + 后续任务直走 spawn(标志内存化);
|
|
16
|
+
// - spawn(XYZ_ZCODE_MODE=spawn 定向 / 降级目标):原单轮路径**零行为改动**
|
|
17
|
+
// 保留(runViaSpawn——launcher/parser 四件套原链路)。
|
|
6
18
|
//
|
|
7
19
|
// 职责编排(四件套的消费方):
|
|
8
20
|
// preparer(隔离 HOME 池 + 凭据 config)→ launcher(argv/env/spawn/杀链)
|
|
9
21
|
// → parser(stdout 收集 + 终 JSON + coarse 事件合成)→ reader(read 第①级 sqlite)。
|
|
10
22
|
//
|
|
11
|
-
// run 错误语义(设计 §3.3.5
|
|
23
|
+
// run 错误语义(设计 §3.3.5,两模式共用):
|
|
12
24
|
// ① prepare 期错误(credential_missing / model_not_available / prompt_too_large /
|
|
13
25
|
// capability 拒绝)在进程创建前 reject,不产生 handle;
|
|
14
26
|
// ② 运行中失败不 reject——合成 engine_run_failed outcome + 正常 handle 返回
|
|
15
27
|
// (record 必须收尾);
|
|
16
|
-
// ③ abort
|
|
17
|
-
//
|
|
28
|
+
// ③ abort:appserver 走 D3 链(session/stop → grace → killChain 连坐共享进程);
|
|
29
|
+
// spawn 走公共杀链(SIGTERM→grace→SIGKILL)——终态同为 exitCode=null + 杀链标记。
|
|
18
30
|
//
|
|
19
|
-
// schema 仿真接线(D4 emulated 侧):common/schema-emulation.ts
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
31
|
+
// schema 仿真接线(D4 emulated 侧):common/schema-emulation.ts——spawn 前拼 prompt
|
|
32
|
+
// 仿真段、终态后三级容错提取 + ajv 校验、失败强化重试一次、仍失败报
|
|
33
|
+
// schema_emulation_failed(appserver 路径同接线:prompt 组装共享 buildPrompt,
|
|
34
|
+
// 校验/重试编排共享 run 级重试轮)。read 第②级 journal 降级已接线(对齐点①:
|
|
35
|
+
// common/journal-replay 复用 live reducer)。
|
|
23
36
|
|
|
24
37
|
import { execFile } from "node:child_process";
|
|
25
38
|
import * as fs from "node:fs";
|
|
@@ -32,6 +45,7 @@ import {
|
|
|
32
45
|
extractAndValidateStructuredOutput,
|
|
33
46
|
} from "../../common/schema-emulation.ts";
|
|
34
47
|
import { HOST_TIMEOUT_ABORT_REASON, synthesizeTimeoutOutcome } from "../../common/kill-chain.ts";
|
|
48
|
+
import { applyPersona } from "../../common/persona-router.ts";
|
|
35
49
|
import { engineTimeoutDetail } from "../../common/errors.ts";
|
|
36
50
|
import { replayJournalToSessionView } from "../../common/journal-replay.ts";
|
|
37
51
|
import type { EnginePort, EngineRunResult, RunContext } from "../../port.ts";
|
|
@@ -49,10 +63,19 @@ import type {
|
|
|
49
63
|
import { resolvePoolDir } from "../../paths.ts";
|
|
50
64
|
import {
|
|
51
65
|
ZCODE_ADAPTER_VERSION,
|
|
66
|
+
ZCODE_APPSERVER_ABORT_GRACE_MS,
|
|
67
|
+
ZCODE_APPSERVER_DRIFT_RPC_CODES,
|
|
68
|
+
ZCODE_APPSERVER_ERR_BUSY_SESSION,
|
|
69
|
+
ZCODE_APPSERVER_ERR_MODEL_CONFIG_MISSING,
|
|
70
|
+
ZCODE_APPSERVER_HARVEST_GRACE_MS,
|
|
71
|
+
ZCODE_APPSERVER_PIDFILE_NAME,
|
|
72
|
+
ZCODE_APPSERVER_POOL_KEY,
|
|
73
|
+
ZCODE_APPSERVER_STOP_TIMEOUT_MS,
|
|
52
74
|
ZCODE_CLI_DEFAULT_PATH,
|
|
53
75
|
ZCODE_ENGINE_ID,
|
|
54
76
|
ZCODE_ERROR_TAIL_CHARS,
|
|
55
77
|
ZCODE_KILL_GRACE_MS,
|
|
78
|
+
ZCODE_MODE_ENV_VAR,
|
|
56
79
|
ZCODE_POOL_DB_RELATIVE_PATH,
|
|
57
80
|
} from "./constants.ts";
|
|
58
81
|
import { ZCODE_GOLDEN_STDOUT } from "./golden-sample.ts";
|
|
@@ -66,19 +89,64 @@ import {
|
|
|
66
89
|
import {
|
|
67
90
|
buildRunFailedMessage,
|
|
68
91
|
collectZcodeOutput,
|
|
92
|
+
mapZcodeOutcomeUsage,
|
|
93
|
+
mapZcodeUsage,
|
|
69
94
|
parseZcodeTerminal,
|
|
70
95
|
synthesizeCoarseEvents,
|
|
71
96
|
type ZcodeCollectedOutput,
|
|
72
97
|
type ZcodeTerminalPayload,
|
|
73
98
|
} from "./parser.ts";
|
|
74
|
-
import {
|
|
99
|
+
import {
|
|
100
|
+
acquireAppServerHome,
|
|
101
|
+
bootstrapAppServerConfig,
|
|
102
|
+
isLockHeldByUs,
|
|
103
|
+
startLockHeartbeat,
|
|
104
|
+
writeAppServerPidFile,
|
|
105
|
+
type AppServerHomeHandle,
|
|
106
|
+
} from "./appserver-home.ts";
|
|
107
|
+
import {
|
|
108
|
+
listZcodeModels,
|
|
109
|
+
prepareZcodeHome,
|
|
110
|
+
resolveZcodeModelRef,
|
|
111
|
+
splitZcodeModelRef,
|
|
112
|
+
type ZcodeSourcePaths,
|
|
113
|
+
} from "./preparer.ts";
|
|
75
114
|
import { readZcodeSessionView } from "./reader.ts";
|
|
115
|
+
import { runAppServerSmokeProbe } from "./appserver-probe.ts";
|
|
116
|
+
import { AppServerConnection, buildAppServerEnv, isAppServerRpcError } from "./connection.ts";
|
|
117
|
+
import { SessionChannel, type SessionCreateParams, type SessionTurnResult } from "./session-channel.ts";
|
|
76
118
|
|
|
77
119
|
const logger = getLogger("subagents");
|
|
78
120
|
|
|
79
121
|
/** probe 的版本探测超时(ms)——二进制无响应按探针失败处理,不静默挂死。 */
|
|
80
122
|
const PROBE_VERSION_TIMEOUT_MS = 15_000;
|
|
81
123
|
|
|
124
|
+
/**
|
|
125
|
+
* [RX2-F1] 常见 thoughtLevel 档位(仅作提示基准,非权威值域):pi 全 7 档
|
|
126
|
+
* (off/minimal/low/medium/high/xhigh/max)恒等透传不拦截——core 引擎层不掌握各模型
|
|
127
|
+
* 真实值域,禁止硬编码枚举做拒收或映射;不在此列的档位只触发一行提示(warnThoughtLevelUncommon)。
|
|
128
|
+
*/
|
|
129
|
+
const COMMON_THOUGHT_LEVELS: readonly string[] = ["low", "high", "max"];
|
|
130
|
+
|
|
131
|
+
// ============================================================
|
|
132
|
+
// [R4/R5] 执行模式分派(D2:定向不探不降;缺省探 + 降)
|
|
133
|
+
// ============================================================
|
|
134
|
+
|
|
135
|
+
export type ZcodeRunMode = "appserver" | "spawn";
|
|
136
|
+
|
|
137
|
+
/** [R5] 三态:undefined = 未定向(缺省路径,探 + 降);定向值原样。 */
|
|
138
|
+
export type ZcodeModePin = ZcodeRunMode | undefined;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* [R5 D2④] 定向判定:`XYZ_ZCODE_MODE=appserver|spawn` 是**定向**(不探不降——失败
|
|
142
|
+
* 直接上报,不给定向者换通道);undefined = 缺省(probe 门控 + 首败降级)。
|
|
143
|
+
* deps.processEnv 可注入(测试钉扎三态)。
|
|
144
|
+
*/
|
|
145
|
+
export function pinnedZcodeMode(env: NodeJS.ProcessEnv = process.env): ZcodeModePin {
|
|
146
|
+
const v = env[ZCODE_MODE_ENV_VAR];
|
|
147
|
+
return v === "appserver" || v === "spawn" ? v : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
82
150
|
/** ZcodeEngine 构造依赖(全部可注入——测试不依赖真机 CLI/真凭据)。 */
|
|
83
151
|
export interface ZcodeEngineDeps {
|
|
84
152
|
/**
|
|
@@ -94,8 +162,31 @@ export interface ZcodeEngineDeps {
|
|
|
94
162
|
probeVersion?: (cliPath: string) => Promise<string | undefined>;
|
|
95
163
|
/** spawn 执行器(测试注入 fake 进程)。 */
|
|
96
164
|
launch?: (opts: { cliPath: string; args: string[]; env: NodeJS.ProcessEnv }) => ZcodeLaunchedProcess;
|
|
97
|
-
/** env 基底(测试注入;缺省 process.env
|
|
165
|
+
/** env 基底(测试注入;缺省 process.env——模式分派与 app-server env 组装都经它)。 */
|
|
98
166
|
processEnv?: NodeJS.ProcessEnv;
|
|
167
|
+
/**
|
|
168
|
+
* [R5 D8] 协议冒烟探针总预算(缺省 ZCODE_APPSERVER_PROBE_BUDGET_MS = 10s;测试
|
|
169
|
+
* 注入短预算验证超时降级路径,不真等 10s)。
|
|
170
|
+
*/
|
|
171
|
+
probeBudgetMs?: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 常驻运行时(每引擎实例一份;dispose/凭据刷新/池变更时整件丢弃重建)。 */
|
|
175
|
+
interface AppServerRuntime {
|
|
176
|
+
conn: AppServerConnection;
|
|
177
|
+
channel: SessionChannel;
|
|
178
|
+
homePoolKey: string;
|
|
179
|
+
homeDir: string;
|
|
180
|
+
/** 在途会话登记(dispose 时 fire session/close 的目标集;settle 后移除)。 */
|
|
181
|
+
activeSessions: Set<string>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 已持有的常驻 HOME 记账(锁心跳归属;dispose 不释放——锁随宿主进程存活)。 */
|
|
185
|
+
interface AppServerHomeState {
|
|
186
|
+
poolKey: string;
|
|
187
|
+
homeDir: string;
|
|
188
|
+
lockPath: string;
|
|
189
|
+
stopHeartbeat: () => void;
|
|
99
190
|
}
|
|
100
191
|
|
|
101
192
|
/** zcode 引擎适配器。 */
|
|
@@ -104,35 +195,53 @@ export class ZcodeEngine implements EnginePort {
|
|
|
104
195
|
|
|
105
196
|
private readonly deps: ZcodeEngineDeps;
|
|
106
197
|
private probeCache: ProbeReport | undefined;
|
|
198
|
+
private appserverRuntime: AppServerRuntime | undefined;
|
|
199
|
+
private homeState: AppServerHomeState | undefined;
|
|
200
|
+
/** 并发任务的首次锁获取在途 promise(重入守卫,见 ensureAppServerHome)。 */
|
|
201
|
+
private homeAcquireInFlight: Promise<AppServerHomeHandle> | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* [R5 D2③] 探针结论(与 CLI 文件 mtime 绑定的内存缓存):mtime 未变不重探;
|
|
204
|
+
* zcode 升级(mtime 变化)后首个任务前重探。不落盘——进程重启后重探重建。
|
|
205
|
+
*/
|
|
206
|
+
private smokeConclusion: { cliMtimeMs: number; ok: boolean; detail: string } | undefined;
|
|
207
|
+
/**
|
|
208
|
+
* [R5 D2②] 漂移降级标志(内存化,不落盘):首任务运行中命中 -32601/-32602 后置
|
|
209
|
+
* true,本进程后续任务直走 spawn;进程重启后经探针门控重建(重探通过则恢复
|
|
210
|
+
* app-server)。
|
|
211
|
+
*/
|
|
212
|
+
private driftDegraded = false;
|
|
107
213
|
|
|
108
214
|
constructor(deps: ZcodeEngineDeps) {
|
|
109
215
|
this.deps = deps;
|
|
110
216
|
}
|
|
111
217
|
|
|
112
218
|
/**
|
|
113
|
-
* zcode
|
|
114
|
-
*
|
|
115
|
-
*
|
|
219
|
+
* zcode 链路实际接通的能力(D3 链路口径;R4 D5 升级序:eventGranularity
|
|
220
|
+
* coarse→stream——链路先行〔session/event payload.delta → text_delta 实时流出,
|
|
221
|
+
* turn.terminal → turn_end,收尾帧 usage → message_end.usage〕,其余能力位维持
|
|
222
|
+
* 现值。声明升级必须先改链路再改声明(C4 原则)。
|
|
116
223
|
*/
|
|
117
224
|
capabilities(): EngineCapabilities {
|
|
118
225
|
return {
|
|
119
226
|
// 无 --json-schema 类通道;公共 schema 仿真层(prompt 约定 + 容错提取 + ajv)
|
|
120
227
|
schemaEnforcement: "emulated",
|
|
121
|
-
//
|
|
228
|
+
// send-while-running 恒 -32010 硬错误(旧实测)——app-server 常驻化不改变此判据
|
|
122
229
|
steer: "unsupported",
|
|
123
|
-
// 无同进程 idle
|
|
230
|
+
// 无同进程 idle 复用(D4:每任务自包含 create→run→close);--resume 是冷启动
|
|
124
231
|
conversation: "unsupported",
|
|
125
232
|
// 无 --append-system-prompt flag(实测拒收)——persona 只能拼进 prompt
|
|
126
233
|
personaInjection: "prompt",
|
|
127
|
-
//
|
|
128
|
-
|
|
234
|
+
// app-server 推送流实时流出(R4);spawn 降级路径退化为终态两事件(D2 声明不降级
|
|
235
|
+
// ——降级是任务级兜底非能力级,record 留痕降级事实)
|
|
236
|
+
eventGranularity: "stream",
|
|
129
237
|
// 首期未接 worktree 隔离(公共层 worktree-manager 接入后升 emulated)
|
|
130
238
|
sandbox: "none",
|
|
131
239
|
// sqlite 三级 JOIN 完整重建 turns(reader 实测)
|
|
132
240
|
sessionRead: "full",
|
|
133
241
|
// --resume 冷启动可用(实测)
|
|
134
242
|
resume: "cold",
|
|
135
|
-
//
|
|
243
|
+
// abort 走 D3 链(stop→grace→killChain)但声明维持 kill-only 不升级(改链路
|
|
244
|
+
// 先于改声明;stop 链路经 conformance 真机验证后再评估升 native)
|
|
136
245
|
interrupt: "kill-only",
|
|
137
246
|
// --mode build/edit/plan/yolo 原生权限档位
|
|
138
247
|
permissionMode: "native",
|
|
@@ -221,13 +330,510 @@ export class ZcodeEngine implements EnginePort {
|
|
|
221
330
|
};
|
|
222
331
|
}
|
|
223
332
|
|
|
224
|
-
/**
|
|
333
|
+
/**
|
|
334
|
+
* D1 主语义 + [R5] D2 降级链四步:
|
|
335
|
+
* ① 定向(XYZ_ZCODE_MODE=appserver|spawn):不探不降——定向者要的就是这条通道,
|
|
336
|
+
* 失败直接上报(spawn 兜底原路径 / appserver 直连);
|
|
337
|
+
* ② 缺省 + 已漂移降级(内存标志):后续任务直走 spawn(record 标注降级事实);
|
|
338
|
+
* ③ 缺省 + 探针门控(结论与 CLI mtime 绑定):探针失败 → 本任务起直接 spawn;
|
|
339
|
+
* ④ 缺省 + 探针通过但首任务命中漂移类 RPC 错误(-32601/-32602)→ 本任务降级
|
|
340
|
+
* spawn 重跑一次(同一任务,结果标注降级)+ 后续任务直走 spawn。
|
|
341
|
+
*/
|
|
225
342
|
async run(task: AgentTaskSpec, ctx: RunContext): Promise<EngineRunResult> {
|
|
343
|
+
this.rejectUnsupportedTaskShapes(task);
|
|
344
|
+
const pin = pinnedZcodeMode(this.deps.processEnv ?? process.env);
|
|
345
|
+
if (pin === "appserver") return (await this.runViaAppServer(task, ctx)).result;
|
|
346
|
+
if (pin === "spawn") return this.runViaSpawn(task, ctx);
|
|
347
|
+
|
|
348
|
+
// 缺省:probe 门控 + 首败降级(D2)
|
|
349
|
+
if (this.driftDegraded) {
|
|
350
|
+
return this.runViaSpawn(task, ctx, {
|
|
351
|
+
degradedReason:
|
|
352
|
+
"protocol-drift(app-server 首任务命中 -32601/-32602 漂移类错误,本进程后续任务直走 spawn;进程重启后重探重建)",
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
// pre-aborted 短路:不触发探针(不 spawn 探针进程),由常驻路径内部合成中止终态
|
|
356
|
+
if (ctx.signal?.aborted !== true) {
|
|
357
|
+
const gate = await this.appServerProbeGate(task);
|
|
358
|
+
if (!gate.ok) {
|
|
359
|
+
return this.runViaSpawn(task, ctx, {
|
|
360
|
+
degradedReason: `probe-failed(协议冒烟探针未通过:${gate.detail};CLI mtime 变化后首个任务前重探)`,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const { result, driftCode } = await this.runViaAppServer(task, ctx);
|
|
365
|
+
if (driftCode === undefined) return result;
|
|
366
|
+
// D2② 首败降级:漂移错误 → 标志内存化 + 同一任务 spawn 重跑一次(结果标注)。
|
|
367
|
+
// [RX2-F3] skipCtxModelWarn:appserver 首跑已出声过 ctxModel 忽略留痕,同任务
|
|
368
|
+
// spawn 重跑再 warn 一遍是噪音(探针场景同款自我要求)——重跑侧跳过。
|
|
369
|
+
this.driftDegraded = true;
|
|
370
|
+
logger.warn(
|
|
371
|
+
`[zcode-engine] app-server 漂移类错误(RPC code ${driftCode})——本任务降级 spawn 重跑,后续任务直走 spawn`,
|
|
372
|
+
{ taskId: ctx.taskId },
|
|
373
|
+
);
|
|
374
|
+
return this.runViaSpawn(task, ctx, {
|
|
375
|
+
degradedReason: `protocol-drift(首任务 app-server 命中 RPC code ${driftCode},已降级 spawn 重跑本任务;后续任务直走 spawn)`,
|
|
376
|
+
skipCtxModelWarn: true,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* [R5 D8] 探针门控:CLI 文件 mtime 与结论绑定——mtime 未变命中缓存不重探;变化
|
|
382
|
+
* (zcode 升级)或首次 → 独立短命连接上跑协议冒烟(appserver-probe.ts;必须用已
|
|
383
|
+
* 引导的常驻 HOME——D7 教训「先 bootstrap 再 probe 否则永远误降级」)。CLI 不存在
|
|
384
|
+
* 按探针失败处理(spawn 路径自身还有 binary 检查兜底)。
|
|
385
|
+
*/
|
|
386
|
+
private async appServerProbeGate(task: AgentTaskSpec): Promise<{ ok: boolean; detail: string }> {
|
|
387
|
+
const cliPath = this.deps.cliPath ?? ZCODE_CLI_DEFAULT_PATH;
|
|
388
|
+
let cliMtimeMs: number;
|
|
389
|
+
try {
|
|
390
|
+
cliMtimeMs = fs.statSync(cliPath).mtimeMs;
|
|
391
|
+
} catch {
|
|
392
|
+
return { ok: false, detail: `zcode CLI 不存在:${cliPath}` };
|
|
393
|
+
}
|
|
394
|
+
if (this.smokeConclusion !== undefined && this.smokeConclusion.cliMtimeMs === cliMtimeMs) {
|
|
395
|
+
return this.smokeConclusion; // mtime 未变不重探(D2③)
|
|
396
|
+
}
|
|
397
|
+
const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
|
|
398
|
+
const home = await this.ensureAppServerHome(modelRef);
|
|
399
|
+
const r = await runAppServerSmokeProbe({
|
|
400
|
+
cliPath,
|
|
401
|
+
homeDir: home.homeDir,
|
|
402
|
+
baseEnv: this.deps.processEnv ?? process.env,
|
|
403
|
+
stderrLogPath: path.join(this.deps.engineDataDir(), "logs", "zcode-appserver-probe-stderr.log"),
|
|
404
|
+
...(this.deps.probeBudgetMs !== undefined ? { budgetMs: this.deps.probeBudgetMs } : {}),
|
|
405
|
+
});
|
|
406
|
+
this.smokeConclusion = { cliMtimeMs, ok: r.ok, detail: r.detail };
|
|
407
|
+
logger.debug("[zcode-engine] appserver smoke probe", { ok: r.ok, detail: r.detail, cliMtimeMs });
|
|
408
|
+
return this.smokeConclusion;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ============================================================
|
|
412
|
+
// [R4] app-server 常驻路径(D1/D3/D4/D7)
|
|
413
|
+
// ============================================================
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 常驻路径主编排:常驻 HOME(锁/派生/孤儿回收/凭据刷新——appserver-home D7 全量)→
|
|
417
|
+
* 惰性连接 + runTurn(事件时序前移:text_delta 流式、终态后 message_end/turn_end)→
|
|
418
|
+
* schema 仿真重试(与 spawn 同编排)→ outcome/handle。poolKey 静态常量,
|
|
419
|
+
* onPoolResolved 在 prepare 期、onHandleReady 在 create 应答后(§3.4 不变量 3)。
|
|
420
|
+
*
|
|
421
|
+
* [R5] 返回附带 driftCode:末轮 attempt 以漂移类 RPC 错误(-32601/-32602)收场时
|
|
422
|
+
* 给出 code(run 编排降级 spawn 重跑);其余终态(成功/中止/非漂移失败)为
|
|
423
|
+
* undefined——-32004/-32010/-32603 按错误规格表各自上报,不降级。
|
|
424
|
+
*/
|
|
425
|
+
private async runViaAppServer(
|
|
426
|
+
task: AgentTaskSpec,
|
|
427
|
+
ctx: RunContext,
|
|
428
|
+
): Promise<{ result: EngineRunResult; driftCode: number | undefined }> {
|
|
429
|
+
const startedAt = Date.now();
|
|
430
|
+
// pre-aborted 短路:取消先于启动——不创建会话、不触发连接惰性启动(防误杀共享
|
|
431
|
+
// 进程殃及在途任务;spawn 路径无此形态因每任务独占进程)
|
|
432
|
+
if (ctx.signal?.aborted === true) {
|
|
433
|
+
return this.abortedAppServerRun(task, ctx, startedAt);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ① prepare 期:模型解析(provider 体系校验——错误语义与 spawn 同为进程创建前
|
|
437
|
+
// reject)+ 常驻 HOME(锁判定/派生/pidfile 孤儿回收/config 内容 hash 刷新)
|
|
438
|
+
const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
|
|
439
|
+
this.warnIgnoredCtxModel(task, ctx, modelRef);
|
|
440
|
+
// [RX2-F1] 非常见档位出声一行(不拦截透传);放主编排而非 attemptAppServerTurn——
|
|
441
|
+
// schema 重试轮会二次进 attempt,warn 只应随任务出声一次
|
|
442
|
+
this.warnThoughtLevelUncommon(task, ctx);
|
|
443
|
+
const home = await this.ensureAppServerHome(modelRef);
|
|
444
|
+
// 对齐点③(不变量 3):poolKey 在 prepare 期声明(静态常量或派生目录名),
|
|
445
|
+
// 早于连接建立与首个事件
|
|
446
|
+
ctx.onPoolResolved?.(home.poolKey);
|
|
447
|
+
|
|
448
|
+
const cwd = task.cwd ?? process.cwd();
|
|
449
|
+
const schema = isPlainObject(task.schema) ? task.schema : undefined;
|
|
450
|
+
const basePrompt = this.buildPrompt(task, schema);
|
|
451
|
+
|
|
452
|
+
// ② 首轮执行 + schema 仿真重试(重试语义与不变量注释见 runAppServerAttemptsWithRetry)
|
|
453
|
+
const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false };
|
|
454
|
+
const final = await this.runAppServerAttemptsWithRetry(task, ctx, home, modelRef, cwd, basePrompt, schema, usageAcc);
|
|
455
|
+
|
|
456
|
+
const outcome = this.finalizeOutcome(task, ctx, final, usageAcc, startedAt);
|
|
457
|
+
return { result: { handle: this.appServerHandle(home.poolKey, outcome), outcome }, driftCode: driftCodeOf(final) };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** pre-aborted 短路收口(常驻路径专用):合成中止 outcome + 池锚定 handle。 */
|
|
461
|
+
private abortedAppServerRun(
|
|
462
|
+
task: AgentTaskSpec,
|
|
463
|
+
ctx: RunContext,
|
|
464
|
+
startedAt: number,
|
|
465
|
+
): { result: EngineRunResult; driftCode: number | undefined } {
|
|
466
|
+
// poolKey 锚定:已持有 HOME(含派生场景)用其真实 poolKey;首次即 abort 尚无
|
|
467
|
+
// HOME 可用,退回静态常量(此刻确实无派生事实可锚定)
|
|
468
|
+
const poolKey = this.homeState?.poolKey ?? ZCODE_APPSERVER_POOL_KEY;
|
|
469
|
+
// 对齐点③(不变量 3):onPoolResolved 必须先于首个事件 emit——本分支
|
|
470
|
+
// finalizeOutcome 经 applyAbortedOutcome emit error 事件,缺本调用会把事件落
|
|
471
|
+
// shared 占位池,与 handle.poolKey 漂移(契约同正常路径 prepare 期声明)
|
|
472
|
+
ctx.onPoolResolved?.(poolKey);
|
|
473
|
+
const outcome = this.finalizeOutcome(
|
|
474
|
+
task,
|
|
475
|
+
ctx,
|
|
476
|
+
abortedAppServerAttempt(ctx),
|
|
477
|
+
{ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false },
|
|
478
|
+
startedAt,
|
|
479
|
+
);
|
|
480
|
+
return { result: { handle: this.appServerHandle(poolKey, outcome), outcome }, driftCode: undefined };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 首轮执行 + schema 仿真重试编排(常驻路径):schema 任务校验失败时重试一次(强化
|
|
485
|
+
* JSON 输出指令——与 spawn/structured-output 的重试语义对齐)。重试轮是独立会话的
|
|
486
|
+
* 独立 LLM 调用:token 计入 outcome.usage 总量;事件面 text_delta 按实际流出(含
|
|
487
|
+
* 失败轮——journal 记录真实流水),message_end/turn_end 只在最终轮终态后合成
|
|
488
|
+
* (不变量 2/5)。
|
|
489
|
+
*/
|
|
490
|
+
private async runAppServerAttemptsWithRetry(
|
|
491
|
+
task: AgentTaskSpec,
|
|
492
|
+
ctx: RunContext,
|
|
493
|
+
home: AppServerHomeHandle,
|
|
494
|
+
modelRef: string,
|
|
495
|
+
cwd: string,
|
|
496
|
+
basePrompt: string,
|
|
497
|
+
schema: object | undefined,
|
|
498
|
+
usageAcc: { input: number; output: number; cacheRead: number; cacheWrite: number; has: boolean },
|
|
499
|
+
): Promise<AttemptResult> {
|
|
500
|
+
let final = await this.attemptAppServerTurn(task, ctx, home, modelRef, cwd, basePrompt);
|
|
501
|
+
accumulateUsage(usageAcc, final);
|
|
502
|
+
if (final.kind === "parsed" && final.schemaResult !== undefined && !final.schemaResult.ok && schema !== undefined) {
|
|
503
|
+
const retryPrompt = appendSchemaRetryDirective(basePrompt, final.schemaResult.error);
|
|
504
|
+
const retry = await this.attemptAppServerTurn(task, ctx, home, modelRef, cwd, retryPrompt);
|
|
505
|
+
accumulateUsage(usageAcc, retry);
|
|
506
|
+
final = retry;
|
|
507
|
+
}
|
|
508
|
+
return final;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** 常驻路径的 handle 合成(poolKey = 常驻 HOME 实际目录名——锚定不变量载体)。 */
|
|
512
|
+
private appServerHandle(poolKey: string, outcome: AgentOutcome): EngineHandle {
|
|
513
|
+
return {
|
|
514
|
+
data: {
|
|
515
|
+
v: 1,
|
|
516
|
+
engineId: ZCODE_ENGINE_ID,
|
|
517
|
+
sessionRef: {
|
|
518
|
+
dbPath: ZCODE_POOL_DB_RELATIVE_PATH,
|
|
519
|
+
...(outcome.sessionId !== undefined ? { sessionId: outcome.sessionId } : {}),
|
|
520
|
+
},
|
|
521
|
+
poolKey,
|
|
522
|
+
...(this.probeCache?.engineVersion !== undefined && this.probeCache.engineVersion !== ""
|
|
523
|
+
? { engineVersion: this.probeCache.engineVersion }
|
|
524
|
+
: {}),
|
|
525
|
+
adapterVersion: ZCODE_ADAPTER_VERSION,
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 单轮常驻执行:runTurn 组合面 + D3 abort 链 + 事件前移(text_delta 实时流出;
|
|
532
|
+
* 终态数据经 read 兜底收口后才 resolve——不变量 1/2)。产出三态与 spawn 同构。
|
|
533
|
+
*/
|
|
534
|
+
private async attemptAppServerTurn(
|
|
535
|
+
task: AgentTaskSpec,
|
|
536
|
+
ctx: RunContext,
|
|
537
|
+
home: AppServerHomeHandle,
|
|
538
|
+
modelRef: string,
|
|
539
|
+
cwd: string,
|
|
540
|
+
prompt: string,
|
|
541
|
+
): Promise<AttemptResult> {
|
|
542
|
+
const rt = this.ensureAppServerRuntime(home);
|
|
543
|
+
const { providerId, modelId } = splitZcodeModelRef(modelRef);
|
|
544
|
+
const createParams = buildAppServerCreateParams(task, providerId, modelId, cwd);
|
|
545
|
+
|
|
546
|
+
let currentSessionId: string | undefined;
|
|
547
|
+
let signalSessionCreated: (() => void) | undefined;
|
|
548
|
+
const sessionCreated = new Promise<void>((resolve) => {
|
|
549
|
+
signalSessionCreated = resolve;
|
|
550
|
+
});
|
|
551
|
+
const turn = rt.channel.runTurn(createParams, prompt, {
|
|
552
|
+
// 事件时序前移:payload.delta → text_delta 实时流出(stream 粒度,D5)
|
|
553
|
+
onTextDelta: (delta) => ctx.onEvent?.({ type: "text_delta", delta }),
|
|
554
|
+
onSessionCreated: (sessionId) => {
|
|
555
|
+
currentSessionId = sessionId;
|
|
556
|
+
rt.activeSessions.add(sessionId);
|
|
557
|
+
signalSessionCreated?.();
|
|
558
|
+
// §3.4 不变量 3:create 应答后立即回填(早于 subscribe/send/终态/run resolve)
|
|
559
|
+
ctx.onHandleReady?.({
|
|
560
|
+
sessionRef: { dbPath: ZCODE_POOL_DB_RELATIVE_PATH, sessionId },
|
|
561
|
+
poolKey: home.poolKey,
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// D3 abort 链:signal abort → ① session/stop {sessionId} ② grace 窗口确认终态
|
|
567
|
+
// ③ stop 失败/超时 → killChain 杀共享进程(接受连坐——协议已不可信)→ 在途
|
|
568
|
+
// 其他任务走崩溃路径。capabilities.interrupt 维持 kill-only 不升级(C4)。
|
|
569
|
+
const onAbort = (): void => {
|
|
570
|
+
void this.appServerAbortChain(rt, turn, () => currentSessionId, sessionCreated);
|
|
571
|
+
};
|
|
572
|
+
if (ctx.signal !== undefined) {
|
|
573
|
+
if (ctx.signal.aborted) onAbort();
|
|
574
|
+
else ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
const r = await turn;
|
|
579
|
+
// stop 优雅生效(终态在 grace 内到达):宿主已取消——按中止终态收口(record
|
|
580
|
+
// 终态迁移由编排层 CAS 决定,engine 侧保持与 spawn abort 同构语义)
|
|
581
|
+
if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
|
|
582
|
+
return parsedAppServerAttempt(task, r);
|
|
583
|
+
} catch (err) {
|
|
584
|
+
if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
|
|
585
|
+
return failedAppServerAttempt(err, home, currentSessionId);
|
|
586
|
+
} finally {
|
|
587
|
+
if (currentSessionId !== undefined) rt.activeSessions.delete(currentSessionId);
|
|
588
|
+
if (ctx.signal !== undefined) ctx.signal.removeEventListener("abort", onAbort);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* D3 abort 链执行体(fire-and-forget——与 turn promise 并行推进):
|
|
594
|
+
* stop 帧(超时 ZCODE_APPSERVER_STOP_TIMEOUT_MS)→ grace 窗口内 turn 落定即止
|
|
595
|
+
* (不杀共享进程)→ 超时 killChain(conn.shutdown 全序:SIGTERM→grace→SIGKILL)。
|
|
596
|
+
* turn 的最终落定由 attempt 主路径 await 收口,本链不直接产出终态。abort 与
|
|
597
|
+
* create 竞态(signal 先到、session 未建立):等会话建立(带上限)再发 stop——
|
|
598
|
+
* 否则 stop 永远发不出,直接连坐杀共享进程。
|
|
599
|
+
*/
|
|
600
|
+
private async appServerAbortChain(
|
|
601
|
+
rt: AppServerRuntime,
|
|
602
|
+
turn: Promise<SessionTurnResult>,
|
|
603
|
+
getSessionId: () => string | undefined,
|
|
604
|
+
sessionCreated: Promise<void>,
|
|
605
|
+
): Promise<void> {
|
|
606
|
+
let sessionId = getSessionId();
|
|
607
|
+
if (sessionId === undefined) {
|
|
608
|
+
await Promise.race([sessionCreated, delayResolved(ZCODE_APPSERVER_STOP_TIMEOUT_MS, undefined)]);
|
|
609
|
+
sessionId = getSessionId();
|
|
610
|
+
}
|
|
611
|
+
if (sessionId !== undefined) {
|
|
612
|
+
try {
|
|
613
|
+
await rt.conn.request("session/stop", { sessionId }, { timeoutMs: ZCODE_APPSERVER_STOP_TIMEOUT_MS });
|
|
614
|
+
} catch (err) {
|
|
615
|
+
logger.debug(
|
|
616
|
+
`[zcode-engine] session/stop 失败(${errMessage(err)})——grace 后走 killChain 兜底`,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const settled = await Promise.race([
|
|
621
|
+
turn.then(
|
|
622
|
+
() => true,
|
|
623
|
+
() => true,
|
|
624
|
+
),
|
|
625
|
+
delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false),
|
|
626
|
+
]);
|
|
627
|
+
if (settled) return; // stop 生效:终态在 grace 窗口内到达,共享进程不杀
|
|
628
|
+
logger.warn(
|
|
629
|
+
`[zcode-engine] abort grace 窗口内未见终态——killChain 收割共享进程(接受连坐,在途任务走崩溃路径)`,
|
|
630
|
+
);
|
|
631
|
+
await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ── 常驻 HOME 与运行时管理(D1/D6/D7)──────────────────────
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* 每任务的常驻 HOME 保障:已持有(lockfile.pid=本进程)→ 只做凭据刷新比对
|
|
638
|
+
* (config 内容 hash;不一致重写 + 重建连接——在途任务走崩溃路径,换取凭据变更
|
|
639
|
+
* 下一任务生效);未持有(首任务/锁被夺)→ acquireAppServerHome 全量(锁判定/
|
|
640
|
+
* 派生/接管 + pidfile 孤儿回收/引导)+ 启动锁心跳。
|
|
641
|
+
*/
|
|
642
|
+
private async ensureAppServerHome(modelRef: string): Promise<AppServerHomeHandle> {
|
|
643
|
+
const engineDataDir = this.deps.engineDataDir();
|
|
644
|
+
// 并发任务重入守卫:首任务在途的锁获取只跑一次(同进程并发 run 各自走判定循环
|
|
645
|
+
// 会把「自己的活锁」误判成他人活持有 → 派生 -2 → poolKey 漂移触发 teardown 杀
|
|
646
|
+
// 活连接)。在途完成后落回已持有路径(含凭据刷新比对)。
|
|
647
|
+
if (this.homeAcquireInFlight !== undefined) {
|
|
648
|
+
try {
|
|
649
|
+
await this.homeAcquireInFlight;
|
|
650
|
+
} catch (err) {
|
|
651
|
+
// 共享失败不击穿并发任务:首任务 acquire 失败(如凭据缺失)后 homeState 未落,
|
|
652
|
+
// 落到下方自行重走 acquire 一次——成功则本任务正常推进,仍失败则报本任务自身
|
|
653
|
+
// 的错误语义(而非继承首任务的异常实例)
|
|
654
|
+
logger.debug(
|
|
655
|
+
`[zcode-engine] 并发等待的首任务 home acquire 失败(${errMessage(err)})——自行重走 acquire`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (this.homeState !== undefined && isLockHeldByUs(this.homeState.lockPath)) {
|
|
660
|
+
const boot = bootstrapAppServerConfig({
|
|
661
|
+
homeDir: this.homeState.homeDir,
|
|
662
|
+
modelRef,
|
|
663
|
+
sources: this.deps.sources,
|
|
664
|
+
});
|
|
665
|
+
if (boot.wroteConfig) this.teardownAppServerRuntime("credential-refresh");
|
|
666
|
+
return {
|
|
667
|
+
poolKey: this.homeState.poolKey,
|
|
668
|
+
homeDir: this.homeState.homeDir,
|
|
669
|
+
lockPath: this.homeState.lockPath,
|
|
670
|
+
tookOver: false,
|
|
671
|
+
orphanReap: "not-applicable",
|
|
672
|
+
...boot,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const acquire = (async () => {
|
|
676
|
+
const fresh = await acquireAppServerHome({ engineDataDir, modelRef, sources: this.deps.sources });
|
|
677
|
+
if (this.homeState !== undefined) this.homeState.stopHeartbeat();
|
|
678
|
+
this.homeState = {
|
|
679
|
+
poolKey: fresh.poolKey,
|
|
680
|
+
homeDir: fresh.homeDir,
|
|
681
|
+
lockPath: fresh.lockPath,
|
|
682
|
+
stopHeartbeat: startLockHeartbeat(fresh.lockPath),
|
|
683
|
+
};
|
|
684
|
+
logger.debug("[zcode-engine] appserver home acquired", {
|
|
685
|
+
poolKey: fresh.poolKey,
|
|
686
|
+
tookOver: fresh.tookOver,
|
|
687
|
+
orphanReap: fresh.orphanReap,
|
|
688
|
+
wroteConfig: fresh.wroteConfig,
|
|
689
|
+
providers: fresh.providerIds.length,
|
|
690
|
+
});
|
|
691
|
+
if (fresh.wroteConfig || (this.appserverRuntime !== undefined && this.appserverRuntime.homePoolKey !== fresh.poolKey)) {
|
|
692
|
+
this.teardownAppServerRuntime(fresh.wroteConfig ? "credential-refresh" : "home-pool-changed");
|
|
693
|
+
}
|
|
694
|
+
return fresh;
|
|
695
|
+
})();
|
|
696
|
+
this.homeAcquireInFlight = acquire;
|
|
697
|
+
try {
|
|
698
|
+
return await acquire;
|
|
699
|
+
} finally {
|
|
700
|
+
this.homeAcquireInFlight = undefined;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 惰性获取常驻运行时(D1:每引擎实例一条连接,全任务共享)。池 key 未变直接复用
|
|
706
|
+
* (连接自身的崩溃重建在 connection 层内部完成——同一条代码路径,§3.4 不变量 4);
|
|
707
|
+
* 池变更(派生目录名变化)→ 旧运行时整件丢弃(shutdown fire)+ 新建。常驻进程
|
|
708
|
+
**不进**宿主 spawnedChildren、不调 onChildSpawned(D6——生命周期归 dispose)。
|
|
709
|
+
*/
|
|
710
|
+
private ensureAppServerRuntime(home: AppServerHomeHandle): AppServerRuntime {
|
|
711
|
+
if (this.appserverRuntime !== undefined && this.appserverRuntime.homePoolKey === home.poolKey) {
|
|
712
|
+
return this.appserverRuntime;
|
|
713
|
+
}
|
|
714
|
+
if (this.appserverRuntime !== undefined) this.teardownAppServerRuntime("home-pool-changed");
|
|
715
|
+
const conn = new AppServerConnection({
|
|
716
|
+
cliPath: this.deps.cliPath ?? ZCODE_CLI_DEFAULT_PATH,
|
|
717
|
+
// 进程级 --cwd 用稳定 HOME(连接跨任务共享,工作区由 create 的
|
|
718
|
+
// workspace.workspacePath 按任务传递——D10 基线不预设任务级进程 cwd)
|
|
719
|
+
cwd: home.homeDir,
|
|
720
|
+
env: buildAppServerEnv(home.homeDir, this.deps.processEnv ?? process.env),
|
|
721
|
+
stderrLogPath: path.join(this.deps.engineDataDir(), "logs", "zcode-appserver-stderr.log"),
|
|
722
|
+
// 每代进程 spawn 后写 pidfile(D6③ 孤儿回收的数据源;崩溃重建的代同样覆盖写)
|
|
723
|
+
onSpawned: (child) => {
|
|
724
|
+
void writeAppServerPidFile(home.homeDir, child.pid ?? -1).catch((err: unknown) => {
|
|
725
|
+
logger.debug(`[zcode-engine] pidfile 写入失败(best-effort): ${errMessage(err)}`);
|
|
726
|
+
});
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
const rt: AppServerRuntime = {
|
|
730
|
+
conn,
|
|
731
|
+
channel: new SessionChannel(conn),
|
|
732
|
+
homePoolKey: home.poolKey,
|
|
733
|
+
homeDir: home.homeDir,
|
|
734
|
+
activeSessions: new Set<string>(),
|
|
735
|
+
};
|
|
736
|
+
this.appserverRuntime = rt;
|
|
737
|
+
return rt;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/** 丢弃当前常驻运行时(凭据刷新/池变更):shutdown fire(killChain 全序),在途任务走崩溃路径。 */
|
|
741
|
+
private teardownAppServerRuntime(reason: string): void {
|
|
742
|
+
const rt = this.appserverRuntime;
|
|
743
|
+
if (rt === undefined) return;
|
|
744
|
+
this.appserverRuntime = undefined;
|
|
745
|
+
// 退订必须在崩溃收割**之后**:channel 的 onClose 收割(failAllTurns)是在途 turn
|
|
746
|
+
// 的快速失败通道,先退订会让它们挂到 turnTimeoutMs(300s)预算耗尽
|
|
747
|
+
void this.shutdownRuntimeAndDisposeChannel(rt).catch((err: unknown) => {
|
|
748
|
+
logger.debug(`[zcode-engine] 常驻连接关闭失败(${reason},best-effort): ${errMessage(err)}`);
|
|
749
|
+
});
|
|
750
|
+
logger.debug(`[zcode-engine] appserver runtime torn down (${reason})`, { poolKey: rt.homePoolKey });
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* [R5 修复 R4 既有竞态] shutdown → 等崩溃收割实际发生 → channel 退订。killChain 在
|
|
755
|
+
* `exit` 事件 resolve,而连接 finalize(onClose → channel 的 failAllTurns)挂
|
|
756
|
+
* `close` 事件——两者之间有一个事件循环窗口:shutdown resolve 后立即退订,在途
|
|
757
|
+
* turn 会错过收割、挂到 turnTimeoutMs(300s)。退订前等 onClose 触发(本方法先于
|
|
758
|
+
* shutdown 订阅;channel 的订阅在构造期更早——其 failAllTurns 先于本 promise
|
|
759
|
+
* resolve 执行);ZCODE_APPSERVER_HARVEST_GRACE_MS 兜底防 `close` 永不到达时挂死。
|
|
760
|
+
*/
|
|
761
|
+
private async shutdownRuntimeAndDisposeChannel(rt: AppServerRuntime): Promise<void> {
|
|
762
|
+
const harvested = new Promise<void>((resolve) => {
|
|
763
|
+
const off = rt.conn.onClose(() => {
|
|
764
|
+
off();
|
|
765
|
+
resolve();
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
|
|
769
|
+
await Promise.race([harvested, delayResolved(ZCODE_APPSERVER_HARVEST_GRACE_MS, undefined)]);
|
|
770
|
+
rt.channel.dispose();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* [R1 D6/R4 主体] 引擎停机面:①fire 全部在途会话的 session/close 帧(不等待
|
|
775
|
+
* 应答——D6① 顺序规定:close 帧必须先于 SIGTERM,否则对面来不及处理即被杀)→
|
|
776
|
+
* ②同步 SIGTERM(conn.shutdown 调用内 killChain 前缀同步执行——同步面在返回
|
|
777
|
+
* Promise 前完成)→ ③grace → SIGKILL(异步面,Promise resolve 于进程退出)。
|
|
778
|
+
* 幂等:运行时字段取走即置空,二次调用零副作用;dispose 后首个 run 经
|
|
779
|
+
* ensureAppServerRuntime 自动重建(与崩溃重建同一代码路径,不变量 4)。
|
|
780
|
+
* 锁不释放(随宿主进程存活——活宿主持有语义;进程死锁自然无主可接管)。
|
|
781
|
+
*/
|
|
782
|
+
async dispose(): Promise<void> {
|
|
783
|
+
const rt = this.appserverRuntime;
|
|
784
|
+
if (rt === undefined) return;
|
|
785
|
+
this.appserverRuntime = undefined;
|
|
786
|
+
// ①fire 全部在途会话的 session/close 帧(不等待应答——D6① 顺序规定:close 帧
|
|
787
|
+
// 必须先于 SIGTERM,否则对面来不及处理即被杀)。进程已死(child=null,如崩溃
|
|
788
|
+
// 收割与 activeSessions 清理之间的微拍)则整体跳过:post 会经 ensureStarted 惰性
|
|
789
|
+
// 拉起新进程再被同次 dispose 杀掉——无意义的 spawn+kill 循环(D6 dispose=防泄漏
|
|
790
|
+
// 语义不制造新进程),且对面进程已不在,close 帧也没有送达对象。同步循环内
|
|
791
|
+
// alive 不会中途翻转(进程退出是异步事件,本轮循环不可重入)
|
|
792
|
+
if (rt.conn.alive) {
|
|
793
|
+
for (const sessionId of [...rt.activeSessions]) {
|
|
794
|
+
rt.conn.post("session/close", { sessionId });
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// ②③ 同步 SIGTERM(killChain 前缀在 shutdown 调用内同步执行)→ grace → SIGKILL
|
|
798
|
+
//(异步面,resolve 于进程退出)。channel 退订放在崩溃收割之后(exit→close 窗口
|
|
799
|
+
//竞态的修复体,见 shutdownRuntimeAndDisposeChannel——先退订会让在途 turn 挂到
|
|
800
|
+
//turnTimeoutMs)
|
|
801
|
+
await this.shutdownRuntimeAndDisposeChannel(rt);
|
|
802
|
+
// 进程已死:pidfile 成为陈旧记录,清掉(防后续 pid 复用误判)
|
|
803
|
+
try {
|
|
804
|
+
fs.rmSync(path.join(rt.homeDir, ZCODE_APPSERVER_PIDFILE_NAME), { force: true });
|
|
805
|
+
} catch (err) {
|
|
806
|
+
logger.debug(`[zcode-engine] dispose 后 pidfile 清理失败(best-effort): ${errMessage(err)}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ============================================================
|
|
811
|
+
// spawn 单轮路径(D2 兜底——R4 起仅 XYZ_ZCODE_MODE=spawn 定向 / R5 降级可达)
|
|
812
|
+
// ============================================================
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* spawn 路径主编排(原 run 主体,行为零改动):preparer → launcher → parser → 仿真重试 → outcome/handle。
|
|
816
|
+
* [R5] degrade 参数:降级链落点(探针失败 / 漂移首败重跑 / 降级后直走)——结果经
|
|
817
|
+
* outcome.engineFallback 标注「degraded: spawn + 原因」(D9① 留痕面复用,record
|
|
818
|
+
* 同步投影;capabilities 声明不降级——D2 降级是任务级兜底非能力级)。
|
|
819
|
+
* [RX2-F3] degrade.skipCtxModelWarn(内部标志):漂移首败重跑场景置 true——该任务
|
|
820
|
+
* 的 appserver 首跑已输出过 ctxModel 忽略留痕,spawn 重跑侧跳过防同 taskId 双份
|
|
821
|
+
* 相同 warn;warnEffortUnsupportedBySpawn 不受此标志影响(降级重跑时最终结果出自
|
|
822
|
+
* spawn,其出声合理,保持现状)。探针失败/降级直走两个落点不置位——任务此前未走
|
|
823
|
+
* 过 appserver,spawn 侧的 warn 是首次出声。
|
|
824
|
+
*/
|
|
825
|
+
private async runViaSpawn(
|
|
826
|
+
task: AgentTaskSpec,
|
|
827
|
+
ctx: RunContext,
|
|
828
|
+
degrade?: { degradedReason: string; skipCtxModelWarn?: boolean },
|
|
829
|
+
): Promise<EngineRunResult> {
|
|
226
830
|
const startedAt = Date.now();
|
|
227
831
|
this.rejectUnsupportedTaskShapes(task);
|
|
832
|
+
this.warnEffortUnsupportedBySpawn(task, ctx);
|
|
228
833
|
|
|
229
834
|
// ① prepare 期:模型解析(provider 体系校验)+ 隔离 HOME 池引导(凭据 + model.main)
|
|
230
835
|
const modelRef = resolveZcodeModelRef(task.model, this.deps.sources);
|
|
836
|
+
if (degrade?.skipCtxModelWarn !== true) this.warnIgnoredCtxModel(task, ctx, modelRef);
|
|
231
837
|
const prepared = prepareZcodeHome({
|
|
232
838
|
engineDataDir: this.deps.engineDataDir(),
|
|
233
839
|
modelRef,
|
|
@@ -247,11 +853,36 @@ export class ZcodeEngine implements EnginePort {
|
|
|
247
853
|
const schema = isPlainObject(task.schema) ? task.schema : undefined;
|
|
248
854
|
const basePrompt = this.buildPrompt(task, schema);
|
|
249
855
|
|
|
250
|
-
// ②-④
|
|
251
|
-
// structured-output 的重试语义对齐,设计 §3.3.3 schema_emulation_failed 行)。
|
|
252
|
-
// 重试轮产生的新 session 是独立 LLM 调用:token 计入 outcome.usage 总量,
|
|
253
|
-
// 事件只在最终轮终态后一次性合成(不变量 5:事件 emit 完成先于 run resolve)。
|
|
856
|
+
// ②-④ 首轮执行 + schema 仿真重试(重试语义与不变量注释见 runSpawnAttemptsWithRetry)
|
|
254
857
|
const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, has: false };
|
|
858
|
+
const final = await this.runSpawnAttemptsWithRetry(task, ctx, prepared, cwd, basePrompt, schema, usageAcc);
|
|
859
|
+
|
|
860
|
+
const outcome = this.finalizeOutcome(task, ctx, final, usageAcc, startedAt);
|
|
861
|
+
|
|
862
|
+
const handle: EngineHandle = this.buildSpawnEngineHandle(prepared, outcome);
|
|
863
|
+
if (degrade !== undefined) {
|
|
864
|
+
// 降级标注(engineFallback 留痕面合并语义见 applyDegradeFallback)
|
|
865
|
+
applyDegradeFallback(outcome, degrade.degradedReason);
|
|
866
|
+
}
|
|
867
|
+
return { handle, outcome };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* 首轮执行 + schema 仿真重试编排(spawn 路径):schema 任务校验失败时重试一次(强化
|
|
872
|
+
* JSON 输出指令——与 structured-output 的重试语义对齐,设计 §3.3.3
|
|
873
|
+
* schema_emulation_failed 行)。重试轮产生的新 session 是独立 LLM 调用:token 计入
|
|
874
|
+
* outcome.usage 总量,事件只在最终轮终态后一次性合成(不变量 5:事件 emit 完成先于
|
|
875
|
+
* run resolve)。
|
|
876
|
+
*/
|
|
877
|
+
private async runSpawnAttemptsWithRetry(
|
|
878
|
+
task: AgentTaskSpec,
|
|
879
|
+
ctx: RunContext,
|
|
880
|
+
prepared: ReturnType<typeof prepareZcodeHome>,
|
|
881
|
+
cwd: string,
|
|
882
|
+
basePrompt: string,
|
|
883
|
+
schema: object | undefined,
|
|
884
|
+
usageAcc: { input: number; output: number; cacheRead: number; cacheWrite: number; has: boolean },
|
|
885
|
+
): Promise<AttemptResult> {
|
|
255
886
|
let final = await this.attemptOnce(task, ctx, prepared, cwd, basePrompt);
|
|
256
887
|
accumulateUsage(usageAcc, final);
|
|
257
888
|
if (final.kind === "parsed" && final.schemaResult !== undefined && !final.schemaResult.ok && schema !== undefined) {
|
|
@@ -260,10 +891,15 @@ export class ZcodeEngine implements EnginePort {
|
|
|
260
891
|
accumulateUsage(usageAcc, retry);
|
|
261
892
|
final = retry;
|
|
262
893
|
}
|
|
894
|
+
return final;
|
|
895
|
+
}
|
|
263
896
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
897
|
+
/** spawn 路径的 handle 合成(poolKey = 隔离池目录名;探针版本可留痕)。 */
|
|
898
|
+
private buildSpawnEngineHandle(
|
|
899
|
+
prepared: ReturnType<typeof prepareZcodeHome>,
|
|
900
|
+
outcome: AgentOutcome,
|
|
901
|
+
): EngineHandle {
|
|
902
|
+
return {
|
|
267
903
|
data: {
|
|
268
904
|
v: 1,
|
|
269
905
|
engineId: ZCODE_ENGINE_ID,
|
|
@@ -279,7 +915,6 @@ export class ZcodeEngine implements EnginePort {
|
|
|
279
915
|
adapterVersion: ZCODE_ADAPTER_VERSION,
|
|
280
916
|
},
|
|
281
917
|
};
|
|
282
|
-
return { handle, outcome };
|
|
283
918
|
}
|
|
284
919
|
|
|
285
920
|
/** 终态合成(extension-conventions 函数 80 行上限,从 run 提取):aborted / run-failed / parsed 三分支。 */
|
|
@@ -325,22 +960,29 @@ export class ZcodeEngine implements EnginePort {
|
|
|
325
960
|
// 对齐点④:宿主超时(mergeTimeoutSignal 的 timeout abort)统一走公共合成终态
|
|
326
961
|
// (common/kill-chain.synthesizeTimeoutOutcome——engine_timeout 文案 SSOT:stdout
|
|
327
962
|
// 尾部 + 「可用 engine: pi 重跑」建议);用户主动 cancel 维持 engine_run_failed
|
|
328
|
-
// 中止标记(非超时语义,不冒充超时)。?? 兜底是类型收窄(合成器恒写 error
|
|
963
|
+
// 中止标记(非超时语义,不冒充超时)。?? 兜底是类型收窄(合成器恒写 error)。
|
|
964
|
+
// appserver 路径经 abortMessage 描述 D3 链形态(与 spawn 杀链文案分立)
|
|
329
965
|
outcome.error = isHostTimeoutAbort(ctx)
|
|
330
966
|
? synthesizeTimeoutOutcome(task, final.output.stdoutText, ZCODE_ENGINE_ID).error ??
|
|
331
967
|
engineTimeoutDetail(final.output.stdoutText)
|
|
332
|
-
:
|
|
333
|
-
`
|
|
968
|
+
: final.abortMessage ??
|
|
969
|
+
`engine_run_failed: zcode 任务被中止(杀链 SIGTERM→${ZCODE_KILL_GRACE_MS}ms→SIGKILL,宿主合成终态)。` +
|
|
970
|
+
`stdout 尾部: ${final.output.stdoutText.slice(-ZCODE_ERROR_TAIL_CHARS)}`;
|
|
334
971
|
emit({ type: "error", message: outcome.error });
|
|
335
972
|
}
|
|
336
973
|
|
|
337
|
-
/**
|
|
974
|
+
/**
|
|
975
|
+
* run-failed 合成终态:错误信息由 buildRunFailedMessage 产出(已含恢复指引)直接透传;
|
|
976
|
+
* appserver 路径附带的会话 id 落 outcome.sessionId(错误规格表 -32004 行「含会话 id」
|
|
977
|
+
* ——appServerHandle 据此写 handle.sessionRef,run-failed 不再恒缺)。
|
|
978
|
+
*/
|
|
338
979
|
private applyRunFailedOutcome(
|
|
339
980
|
outcome: AgentOutcome,
|
|
340
981
|
final: Extract<AttemptResult, { kind: "run-failed" }>,
|
|
341
982
|
emit: (event: AgentEvent) => void,
|
|
342
983
|
): void {
|
|
343
984
|
outcome.exitCode = final.output.exitCode;
|
|
985
|
+
if (final.sessionId !== undefined) outcome.sessionId = final.sessionId;
|
|
344
986
|
outcome.error = final.message;
|
|
345
987
|
emit({ type: "error", message: outcome.error });
|
|
346
988
|
}
|
|
@@ -563,13 +1205,77 @@ export class ZcodeEngine implements EnginePort {
|
|
|
563
1205
|
}
|
|
564
1206
|
}
|
|
565
1207
|
|
|
1208
|
+
/**
|
|
1209
|
+
* [F15b] spawn 路径的 effort 丢弃信号:spawn CLI 无 thoughtLevel 类 flag(协议
|
|
1210
|
+
* 通道是 appserver 路径专属),effort 只能丢弃——但静默丢弃会让调用方误以为推理
|
|
1211
|
+
* 档位已生效,故出声留痕(引擎现成信号风格:logger.warn,同漂移降级先例)。
|
|
1212
|
+
* 诊断语义:effort 是可忽略档位(降档不改变任务正确性),warn 留痕而非硬拒绝
|
|
1213
|
+
* (与 maxTurns「传了上限却失控」的假象不同质性)。
|
|
1214
|
+
*/
|
|
1215
|
+
private warnEffortUnsupportedBySpawn(task: AgentTaskSpec, ctx: RunContext): void {
|
|
1216
|
+
const effort = task.effort?.trim();
|
|
1217
|
+
if (effort === undefined || effort === "") return;
|
|
1218
|
+
logger.warn(
|
|
1219
|
+
`[zcode-engine] effort=${effort} 被忽略:zcode spawn 不支持 thoughtLevel 通道(CLI 无对应 flag),任务按引擎缺省推理档位执行;需要 effort 请走 appserver 模式`,
|
|
1220
|
+
{ taskId: ctx.taskId },
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* [RX2-F1] appserver 路径的非常见档位提示:effort → thoughtLevel 恒等透传(F15a),
|
|
1226
|
+
* 全 7 档放行不拦截——但部分档位(off/minimal/medium/xhigh 等)不在部分模型的合法
|
|
1227
|
+
* 值域内(如 GLM-5.3 仅接受 low/high/max),app-server 侧对不支持的档位 warn-skip
|
|
1228
|
+
* (会话照常但档位静默失效),调用方无从察觉。此处仅对 COMMON_THOUGHT_LEVELS 之外
|
|
1229
|
+
* 的档位出声一行提示(措辞是「若不支持将被忽略/回落」的或然警告,非无效断言);
|
|
1230
|
+
* 是否真不支持由目标模型决定,core 不做权威校验(引擎层不掌握各模型值域)。
|
|
1231
|
+
*/
|
|
1232
|
+
private warnThoughtLevelUncommon(task: AgentTaskSpec, ctx: RunContext): void {
|
|
1233
|
+
const thoughtLevel = task.effort?.trim();
|
|
1234
|
+
if (thoughtLevel === undefined || thoughtLevel === "") return;
|
|
1235
|
+
if (COMMON_THOUGHT_LEVELS.includes(thoughtLevel)) return;
|
|
1236
|
+
logger.warn(
|
|
1237
|
+
`[zcode-engine] effort=${thoughtLevel} 已透传为 thoughtLevel(非常见档位):若目标模型不支持该档位将被忽略/回落到模型缺省推理档位(常见档位:${COMMON_THOUGHT_LEVELS.join("/")});档位是否生效以模型实际行为为准`,
|
|
1238
|
+
{ taskId: ctx.taskId },
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* [F16b] ctxModel 忽略留痕:ctxModel 是 pi 链路的第三层兜底(port.ts 契约——
|
|
1244
|
+
* 依赖 pi resolveModel 链的引擎才消费它),zcode 自带 provider 体系与缺省模型
|
|
1245
|
+
* (resolveZcodeModelRef:requested > ZCODE_FALLBACK_DEFAULT_MODEL),不消费
|
|
1246
|
+
* ctxModel。「调用方给了 ctxModel 但 task.model 未显式指定」时出声一行,说明
|
|
1247
|
+
* 实际落引擎缺省模型(含实际 model id)——防静默降档无据可查。只在「ctx 有模型
|
|
1248
|
+
* 但被忽略」场景输出:显式 task.model 走正常解析链、ctx 本就无模型属预期缺省,
|
|
1249
|
+
* 均不出声(避免噪音)。探针期(appServerProbeGate)不调用——同一任务的正式
|
|
1250
|
+
* run 链路必经此处,双份输出是噪音。[RX2-F3] 漂移首败的 spawn 重跑同理由调用方
|
|
1251
|
+
* 带 degrade.skipCtxModelWarn 跳过——appserver 首跑已出声过,同任务双份相同 warn
|
|
1252
|
+
* 是噪音(与探针场景同一自我要求)。
|
|
1253
|
+
*/
|
|
1254
|
+
private warnIgnoredCtxModel(task: AgentTaskSpec, ctx: RunContext, modelRef: string): void {
|
|
1255
|
+
if (ctx.ctxModel === undefined) return;
|
|
1256
|
+
const requested = task.model?.trim();
|
|
1257
|
+
if (requested !== undefined && requested !== "") return;
|
|
1258
|
+
logger.warn(
|
|
1259
|
+
`[zcode-engine] ctx.ctxModel(${ctx.ctxModel.id})被忽略——ctxModel 是 pi 链路兜底,zcode 不消费;` +
|
|
1260
|
+
`task.model 未显式指定,实际使用引擎缺省模型 ${modelRef}`,
|
|
1261
|
+
{ taskId: ctx.taskId },
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
566
1265
|
/**
|
|
567
1266
|
* persona 拼接后的完整 prompt(personaInjection: 'prompt'——zcode 无 flag 通道):
|
|
568
|
-
*
|
|
569
|
-
* (
|
|
1267
|
+
* persona 段经 common/persona-router.applyPersona 按 capabilities 路由产出
|
|
1268
|
+
* (agentRef/skillPath 引用行 + appendSystemPrompt 正文统一拼装,S5 接线——替换
|
|
1269
|
+
* 原手拼 appendSystemPrompt 段,skillPath/agentRef 不再丢弃),task 正文居中,
|
|
1270
|
+
* schema 仿真段尾置(common/schema-emulation 公共层产出,D4 emulated 侧——zcode
|
|
1271
|
+
* 无 native schema 通道)。
|
|
570
1272
|
*/
|
|
571
1273
|
private buildPrompt(task: AgentTaskSpec, schema: object | undefined): string {
|
|
572
|
-
const segments: string[] = [
|
|
1274
|
+
const segments: string[] = [];
|
|
1275
|
+
if (task.persona !== undefined) {
|
|
1276
|
+
const routed = applyPersona(task.persona, this.capabilities());
|
|
1277
|
+
if (routed.promptSegment !== "") segments.push(routed.promptSegment);
|
|
1278
|
+
}
|
|
573
1279
|
segments.push(task.task);
|
|
574
1280
|
if (schema !== undefined) segments.push(buildSchemaEmulationSegment(schema));
|
|
575
1281
|
return segments.join("\n\n");
|
|
@@ -578,10 +1284,20 @@ export class ZcodeEngine implements EnginePort {
|
|
|
578
1284
|
|
|
579
1285
|
// ── 模块级辅助(run 的重试编排件) ──
|
|
580
1286
|
|
|
581
|
-
/**
|
|
1287
|
+
/**
|
|
1288
|
+
* attempt 的三态产物(两模式共用;run 按序编排重试与终态合成)。appserver 路径的
|
|
1289
|
+
* output 为合成形态(stdoutText 恒空——其失败素材在 message/abortMessage 内)。
|
|
1290
|
+
*/
|
|
582
1291
|
type AttemptResult =
|
|
583
|
-
| { kind: "aborted"; output: ZcodeCollectedOutput }
|
|
584
|
-
| {
|
|
1292
|
+
| { kind: "aborted"; output: ZcodeCollectedOutput; abortMessage?: string }
|
|
1293
|
+
| {
|
|
1294
|
+
kind: "run-failed";
|
|
1295
|
+
output: ZcodeCollectedOutput;
|
|
1296
|
+
message: string;
|
|
1297
|
+
rpcCode?: number;
|
|
1298
|
+
/** appserver 路径失败时已建立的会话 id(错误规格表 -32004 行:按任务失败上报含会话 id)。 */
|
|
1299
|
+
sessionId?: string;
|
|
1300
|
+
}
|
|
585
1301
|
| {
|
|
586
1302
|
kind: "parsed";
|
|
587
1303
|
output: ZcodeCollectedOutput;
|
|
@@ -594,6 +1310,86 @@ function isPlainObject(v: unknown): v is object {
|
|
|
594
1310
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
595
1311
|
}
|
|
596
1312
|
|
|
1313
|
+
/** 错误/日志出声用的 message 提取(非 Error 值不抛二次异常)。 */
|
|
1314
|
+
function errMessage(err: unknown): string {
|
|
1315
|
+
return err instanceof Error ? err.message : String(err);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/** [R5 D2] 漂移类 RPC code 判据(错误规格表第 1 行:-32601 方法不存在 / -32602 参数变形)。 */
|
|
1319
|
+
function isDriftRpcCode(code: number): boolean {
|
|
1320
|
+
return (ZCODE_APPSERVER_DRIFT_RPC_CODES as readonly number[]).includes(code);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/** ms 后 resolve 指定值(abort 链 grace 窗口的 race 材料;unref 不阻塞进程退出)。 */
|
|
1324
|
+
function delayResolved<T>(ms: number, value: T): Promise<T> {
|
|
1325
|
+
return new Promise<T>((resolve) => {
|
|
1326
|
+
const t = setTimeout(() => resolve(value), ms);
|
|
1327
|
+
if (typeof t.unref === "function") t.unref();
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/** appserver 路径的合成 output(无 stdout 可收集——exitCode 语义:0=正常轮;null=中止/连接级失败)。 */
|
|
1332
|
+
function syntheticAppServerOutput(exitCode: number | null): ZcodeCollectedOutput {
|
|
1333
|
+
return { exitCode, stdoutText: "", stderrTail: "" };
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/** appserver 路径的 aborted 三态(abortMessage 描述 D3 链形态——与 spawn 杀链文案分立)。 */
|
|
1337
|
+
function abortedAppServerAttempt(ctx: RunContext): AttemptResult {
|
|
1338
|
+
void ctx;
|
|
1339
|
+
return {
|
|
1340
|
+
kind: "aborted",
|
|
1341
|
+
output: syntheticAppServerOutput(null),
|
|
1342
|
+
abortMessage:
|
|
1343
|
+
`engine_run_failed: zcode 任务被中止(app-server abort 链:session/stop → ${ZCODE_APPSERVER_ABORT_GRACE_MS}ms grace 确认 → ` +
|
|
1344
|
+
`超时 killChain SIGTERM→${ZCODE_KILL_GRACE_MS}ms→SIGKILL 收割共享进程,在途任务走崩溃路径)。`,
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/** runTurn 终态 → parser 载荷形态(usage 映射与 spawn 路径同源:parser.mapZcodeUsage 一族)。 */
|
|
1349
|
+
function turnResultToPayload(r: SessionTurnResult): ZcodeTerminalPayload {
|
|
1350
|
+
return {
|
|
1351
|
+
response: r.response,
|
|
1352
|
+
sessionId: r.sessionId,
|
|
1353
|
+
...(mapZcodeUsage(r.usage) !== undefined ? { usage: mapZcodeUsage(r.usage) } : {}),
|
|
1354
|
+
...(mapZcodeOutcomeUsage(r.usage, undefined) !== undefined
|
|
1355
|
+
? { outcomeUsage: mapZcodeOutcomeUsage(r.usage, undefined) }
|
|
1356
|
+
: {}),
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* appserver 路径运行中失败的结构化文案(错误规格表):-32603 "Model config is
|
|
1362
|
+
* missing" → engine_credential_missing(与 prepare 期同码——错误定位常驻 HOME config);
|
|
1363
|
+
* -32010 → 单会话一任务是结构保证(busy 不排队不打断),文案引导附带 sessionId/state
|
|
1364
|
+
* 流水报告;其余(连接崩溃/会话失败/漂移类透传——漂移降级归 R5)→ engine_run_failed +
|
|
1365
|
+
* 恢复指引。sessionId 已建立时随文案透出(错误规格表 -32004 行「按任务失败上报含会话 id」)。
|
|
1366
|
+
*/
|
|
1367
|
+
function buildAppServerRunFailedMessage(err: unknown, home: AppServerHomeHandle, sessionId?: string): string {
|
|
1368
|
+
if (
|
|
1369
|
+
isAppServerRpcError(err) &&
|
|
1370
|
+
err.code === ZCODE_APPSERVER_ERR_MODEL_CONFIG_MISSING &&
|
|
1371
|
+
/Model config is missing/.test(err.message)
|
|
1372
|
+
) {
|
|
1373
|
+
return (
|
|
1374
|
+
`engine_credential_missing: app-server 报 "Model config is missing"(常驻 HOME ${home.homeDir} 的 config.json ` +
|
|
1375
|
+
`无可用模型配置)。恢复指引:在 ZCode 桌面端登录并配置 provider 凭据后重跑本任务(引擎将在下任务重写常驻 config 并重建连接)。`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
if (isAppServerRpcError(err) && err.code === ZCODE_APPSERVER_ERR_BUSY_SESSION) {
|
|
1379
|
+
const sid = sessionId !== undefined ? `(会话 id: ${sessionId})` : "";
|
|
1380
|
+
return (
|
|
1381
|
+
`engine_run_failed: app-server 报 -32010${sid}(send 时该会话已有轮在跑,busy 不排队不打断)。` +
|
|
1382
|
+
`单会话一任务是结构保证,出现即 bug;请附带 sessionId 与 state 流水(连接/会话事件日志)上报问题。`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const code = isAppServerRpcError(err) && err.code !== undefined ? `(code ${err.code})` : "";
|
|
1386
|
+
const sid = sessionId !== undefined ? `(会话 ${sessionId})` : "";
|
|
1387
|
+
return (
|
|
1388
|
+
`engine_run_failed: app-server 会话执行失败${code}${sid}: ${errMessage(err).slice(-ZCODE_ERROR_TAIL_CHARS)}。` +
|
|
1389
|
+
`恢复指引:直接重跑本任务(连接崩溃后自动重建进程);若持续失败,跑 probe 核对协议漂移(R5 降级链)或改用 engine: pi。`
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
597
1393
|
/** 跨重试轮累计 token 用量(重试的 LLM 调用真实发生)。 */
|
|
598
1394
|
function accumulateUsage(acc: { input: number; output: number; cacheRead: number; cacheWrite: number; has: boolean }, r: AttemptResult): void {
|
|
599
1395
|
if (r.kind !== "parsed") return;
|
|
@@ -617,8 +1413,80 @@ function appendSchemaRetryDirective(basePrompt: string, validationError: string)
|
|
|
617
1413
|
);
|
|
618
1414
|
}
|
|
619
1415
|
|
|
620
|
-
/**
|
|
621
|
-
|
|
1416
|
+
/** create 参数组装(A.2 ① strict 键集:空白 thoughtLevel / 空 deny 清单不设键)。 */
|
|
1417
|
+
function buildAppServerCreateParams(
|
|
1418
|
+
task: AgentTaskSpec,
|
|
1419
|
+
providerId: string,
|
|
1420
|
+
modelId: string,
|
|
1421
|
+
cwd: string,
|
|
1422
|
+
): SessionCreateParams {
|
|
1423
|
+
const denyTools = (task.denyTools ?? []).filter((t) => typeof t === "string" && t.trim() !== "");
|
|
1424
|
+
// effort → thoughtLevel(A.2 ① 键集内):空白串归一为不设键——strict 对象下
|
|
1425
|
+
// 空值键位无语义且防 -32602 变形拒收(与 denyTools 空清单不设键同款纪律)
|
|
1426
|
+
const thoughtLevel = task.effort?.trim();
|
|
1427
|
+
return {
|
|
1428
|
+
workspacePath: cwd,
|
|
1429
|
+
mode: "yolo",
|
|
1430
|
+
// per-session model(G3):create 参数透传(A.2 ① strict 对象)——同进程任务
|
|
1431
|
+
// 各用各的模型,互不干扰
|
|
1432
|
+
model: { providerId, modelId },
|
|
1433
|
+
...(thoughtLevel !== undefined && thoughtLevel !== "" ? { thoughtLevel } : {}),
|
|
1434
|
+
...(denyTools.length > 0 ? { toolDenylist: denyTools } : {}),
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/** appserver 轮成功收口的 parsed 三态(read 兜底后的 response + schema 校验)。 */
|
|
1439
|
+
function parsedAppServerAttempt(task: AgentTaskSpec, r: SessionTurnResult): AttemptResult {
|
|
1440
|
+
const schema = isPlainObject(task.schema) ? task.schema : undefined;
|
|
1441
|
+
return {
|
|
1442
|
+
kind: "parsed",
|
|
1443
|
+
output: syntheticAppServerOutput(0),
|
|
1444
|
+
payload: turnResultToPayload(r),
|
|
1445
|
+
...(schema !== undefined
|
|
1446
|
+
? { schemaResult: extractAndValidateStructuredOutput(r.response, schema) }
|
|
1447
|
+
: {}),
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/** appserver 轮失败收口的 run-failed 三态(结构化文案 + RPC code 透传 + 会话 id 留痕)。 */
|
|
1452
|
+
function failedAppServerAttempt(
|
|
1453
|
+
err: unknown,
|
|
1454
|
+
home: AppServerHomeHandle,
|
|
1455
|
+
currentSessionId: string | undefined,
|
|
1456
|
+
): AttemptResult {
|
|
1457
|
+
return {
|
|
1458
|
+
kind: "run-failed",
|
|
1459
|
+
output: syntheticAppServerOutput(null),
|
|
1460
|
+
message: buildAppServerRunFailedMessage(err, home, currentSessionId),
|
|
1461
|
+
// [R5] RPC code 透传给 run 编排(-32601/-32602 漂移降级判据;连接级/超时类
|
|
1462
|
+
// 错误无 code 不参与降级)
|
|
1463
|
+
...(isAppServerRpcError(err) && err.code !== undefined ? { rpcCode: err.code } : {}),
|
|
1464
|
+
// 错误规格表 -32004 行「按任务失败上报(含会话 id)」:create 成功后运行中失败
|
|
1465
|
+
// (-32004/-32010 等)时留痕会话 id——经 applyRunFailedOutcome 落 outcome.sessionId
|
|
1466
|
+
// 与 handle.sessionRef(create 阶段失败无会话,缺省不带)
|
|
1467
|
+
...(currentSessionId !== undefined ? { sessionId: currentSessionId } : {}),
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/** [R5] 末轮 attempt 的漂移类 RPC code 提取(-32601/-32602 → run 编排降级判据;其余 undefined)。 */
|
|
1472
|
+
function driftCodeOf(final: AttemptResult): number | undefined {
|
|
1473
|
+
return final.kind === "run-failed" && final.rpcCode !== undefined && isDriftRpcCode(final.rpcCode)
|
|
1474
|
+
? final.rpcCode
|
|
1475
|
+
: undefined;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/** 降级留痕(outcome.engineFallback 唯一通道;路由级 fallback 已存在时合并不覆盖)。 */
|
|
1479
|
+
function applyDegradeFallback(outcome: AgentOutcome, degradedReason: string): void {
|
|
1480
|
+
const prior = outcome.engineFallback;
|
|
1481
|
+
outcome.engineFallback = {
|
|
1482
|
+
from: prior?.from ?? "zcode:appserver",
|
|
1483
|
+
reason: `${prior !== undefined ? `${prior.reason};` : ""}degraded: spawn(${degradedReason})`,
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/** prepare 期能力拒绝的载体(code 进 message 前缀,调用方可程序化分流)。
|
|
1488
|
+
* [U10① D6] execution 运行时面错误族成员:export 供宿主 instanceof 分流(纯追加,零逻辑改动)。 */
|
|
1489
|
+
export class ZcodeTaskShapeError extends Error {
|
|
622
1490
|
readonly code: string;
|
|
623
1491
|
|
|
624
1492
|
constructor(code: string, message: string) {
|