@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
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
// session-view-service-zcode-dbpath.test.ts —— zcode ①级 dbPath 白名单的分支守护。
|
|
2
|
-
//
|
|
3
|
-
// 三视角:①构建者——白名单三分支(绝对=宿主 db 精确匹配 / 绝对≠宿主 db 拒绝 /
|
|
4
|
-
// 相对池锚定 + 越界拒绝)逐路径断言;②使用者——共享 HOME 形态 record(写侧
|
|
5
|
-
// zcode-engine 恒绝对 dbPath)tier1 不再被误拒静默降②级;③观察者——reader 调用
|
|
6
|
-
// 参数(白名单通过后传入的 dbPath 精确值)与未调用事实(拒绝路径 reader 零触达)。
|
|
7
|
-
//
|
|
8
|
-
// readZcodeSessionView 以 vi.mock 替身:本文件测的是白名单分支(不可触达真实
|
|
9
|
-
// ~/.zcode/cli/db/db.sqlite——那是用户真机数据),reader 本体行为由
|
|
10
|
-
// engines/zcode/__tests__/reader.test.ts 真库覆盖。
|
|
11
|
-
|
|
12
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
13
|
-
import { homedir, tmpdir } from "node:os";
|
|
14
|
-
import { join, resolve } from "node:path";
|
|
15
|
-
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
17
|
-
|
|
18
|
-
vi.mock("../../engines/zcode/reader.ts", () => ({
|
|
19
|
-
readZcodeSessionView: vi.fn(),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
import { readZcodeSessionView } from "../../engines/zcode/reader.ts";
|
|
23
|
-
import { ZCODE_HOST_DB_SUFFIX } from "../../engines/zcode/constants.ts";
|
|
24
|
-
import { JournalWriter } from "../../common/event-journal.ts";
|
|
25
|
-
import { readSubagentHistoryMessages } from "../../common/session-view-service.ts";
|
|
26
|
-
import { resetNativeSessionReaders } from "../../common/session-view-service.ts";
|
|
27
|
-
import type { SubagentRecordSnapshot } from "../../common/session-view-types.ts";
|
|
28
|
-
import type { AgentEvent } from "../../../types.ts";
|
|
29
|
-
import type { SessionView } from "../../types.ts";
|
|
30
|
-
|
|
31
|
-
const mockedRead = vi.mocked(readZcodeSessionView);
|
|
32
|
-
|
|
33
|
-
/** 共享 HOME 形态写侧恒写入的唯一合法绝对 dbPath(与 zcode-engine hostZcodeDbPath 同源推导)。 */
|
|
34
|
-
const HOST_DB = resolve(homedir(), ...ZCODE_HOST_DB_SUFFIX);
|
|
35
|
-
|
|
36
|
-
let dataDir: string;
|
|
37
|
-
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
dataDir = mkdtempSync(join(tmpdir(), "svs-dbpath-test-"));
|
|
40
|
-
mockedRead.mockReset();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
rmSync(dataDir, { recursive: true, force: true });
|
|
45
|
-
resetNativeSessionReaders();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
function makeRecord(overrides: Partial<SubagentRecordSnapshot> = {}): SubagentRecordSnapshot {
|
|
49
|
-
return {
|
|
50
|
-
subagentId: "sub-1",
|
|
51
|
-
task: "do the thing",
|
|
52
|
-
startedAt: 1_000,
|
|
53
|
-
engine: "zcode",
|
|
54
|
-
engineHandle: {
|
|
55
|
-
sessionRef: { sessionId: "sess-1", dbPath: HOST_DB },
|
|
56
|
-
poolKey: "shared",
|
|
57
|
-
},
|
|
58
|
-
...overrides,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function writeJournal(path: string, events: AgentEvent[]): Promise<void> {
|
|
63
|
-
const writer = new JournalWriter({ path, taskId: "sub-1", engineId: "zcode" });
|
|
64
|
-
for (const ev of events) writer.append(ev);
|
|
65
|
-
await writer.close();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** record 带②级可达 journal(tier1 被拒后的可观察落点:journal 内容被投影)。 */
|
|
69
|
-
async function journalRecord(dbPath: string): Promise<SubagentRecordSnapshot> {
|
|
70
|
-
const journalPath = join(dataDir, "engines", "zcode", "shared", "journal-sub-1.jsonl");
|
|
71
|
-
await writeJournal(journalPath, [{ type: "text_delta", delta: "journal says" }]);
|
|
72
|
-
return makeRecord({
|
|
73
|
-
result: "outcome fallback",
|
|
74
|
-
engineHandle: {
|
|
75
|
-
sessionRef: { sessionId: "sess-1", dbPath },
|
|
76
|
-
poolKey: "shared",
|
|
77
|
-
journalPath,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
describe("zcode ①级 dbPath 白名单(分支守护)", () => {
|
|
83
|
-
it("绝对 dbPath = 宿主 db(SSOT 精确匹配)→ 白名单通过,reader 收到原样路径,tier1 命中", async () => {
|
|
84
|
-
const view: SessionView = {
|
|
85
|
-
engineId: "zcode",
|
|
86
|
-
sessionId: "sess-1",
|
|
87
|
-
turns: [{ text: "from host db", thinking: "", toolCalls: [], closed: true }],
|
|
88
|
-
source: "native",
|
|
89
|
-
};
|
|
90
|
-
mockedRead.mockResolvedValue(view);
|
|
91
|
-
const messages = await readSubagentHistoryMessages(makeRecord(), dataDir);
|
|
92
|
-
expect(mockedRead).toHaveBeenCalledTimes(1);
|
|
93
|
-
expect(mockedRead).toHaveBeenCalledWith(HOST_DB, "sess-1");
|
|
94
|
-
expect(messages[1]).toMatchObject({ role: "assistant", content: "from host db" });
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("绝对 dbPath ≠ 宿主 db → 拒绝 tier1(reader 零触达),降②级 journal", async () => {
|
|
98
|
-
const record = await journalRecord("/tmp/attacker-chosen/db.sqlite");
|
|
99
|
-
const messages = await readSubagentHistoryMessages(record, dataDir);
|
|
100
|
-
expect(mockedRead).not.toHaveBeenCalled();
|
|
101
|
-
expect(messages[1]).toMatchObject({ role: "assistant", content: "journal says" });
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("宿主 db 路径的目录内变体(/../ 注入)≠ 精确匹配 → 同样拒绝", async () => {
|
|
105
|
-
// 白名单是文本级精确匹配(resolve 不可用于反构造——会把 .. 规约掉变回正身)
|
|
106
|
-
const sneaky = `${HOST_DB}/../db.sqlite`;
|
|
107
|
-
const record = await journalRecord(sneaky);
|
|
108
|
-
const messages = await readSubagentHistoryMessages(record, dataDir);
|
|
109
|
-
expect(mockedRead).not.toHaveBeenCalled();
|
|
110
|
-
expect(messages[1]).toMatchObject({ content: "journal says" });
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("相对 dbPath 池内(旧池时代 record)→ 锚池目录解析,reader 收到池内绝对路径", async () => {
|
|
114
|
-
const view: SessionView = {
|
|
115
|
-
engineId: "zcode",
|
|
116
|
-
sessionId: "sess-1",
|
|
117
|
-
turns: [{ text: "legacy pool", thinking: "", toolCalls: [], closed: true }],
|
|
118
|
-
source: "native",
|
|
119
|
-
};
|
|
120
|
-
mockedRead.mockResolvedValue(view);
|
|
121
|
-
const record = makeRecord({
|
|
122
|
-
engineHandle: {
|
|
123
|
-
sessionRef: { sessionId: "sess-1", dbPath: "pool-db/db.sqlite" },
|
|
124
|
-
poolKey: "shared",
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
const messages = await readSubagentHistoryMessages(record, dataDir);
|
|
128
|
-
expect(mockedRead).toHaveBeenCalledWith(
|
|
129
|
-
join(dataDir, "engines", "zcode", "shared", "pool-db", "db.sqlite"),
|
|
130
|
-
"sess-1",
|
|
131
|
-
);
|
|
132
|
-
expect(messages[1]).toMatchObject({ content: "legacy pool" });
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("相对 dbPath ../ 逃逸池目录 → 拒绝 tier1(reader 零触达),降②级 journal", async () => {
|
|
136
|
-
const record = await journalRecord("../../escape/db.sqlite");
|
|
137
|
-
const messages = await readSubagentHistoryMessages(record, dataDir);
|
|
138
|
-
expect(mockedRead).not.toHaveBeenCalled();
|
|
139
|
-
expect(messages[1]).toMatchObject({ content: "journal says" });
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("白名单通过但 reader 抛错(结构化降级契约)→ 仍降②级不崩", async () => {
|
|
143
|
-
mockedRead.mockRejectedValue(new Error("engine_session_read_failed: db 文件不存在"));
|
|
144
|
-
const record = await journalRecord(HOST_DB);
|
|
145
|
-
const messages = await readSubagentHistoryMessages(record, dataDir);
|
|
146
|
-
expect(mockedRead).toHaveBeenCalledTimes(1);
|
|
147
|
-
expect(messages[1]).toMatchObject({ content: "journal says" });
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_meta": {
|
|
3
|
-
"engineId": "pi",
|
|
4
|
-
"description": "pi 引擎 golden 事件序列(统一 AgentEvent 形态——spawn-event-adapter/session-runner 翻译后的 onEvent 出口序列,即 journal 落盘形态)",
|
|
5
|
-
"source": "从 spawn-event-adapter.test.ts / session-runner 事件形状构造的典型流式序列:bash 工具调用(含 pendingTools 配对)→ 双段 text_delta → message_end(完整 usage)→ turn_end。字段形状与 handleSdkEvent 翻译产物逐一锚定(tool_start.toolName/args、tool_end.result/isError、text_delta.delta、message_end.usage 四项 token、turn_end)",
|
|
6
|
-
"recordedAt": "2026-08-25",
|
|
7
|
-
"usageMapping": "message_end.usage = pi SDK message.usage(input/output/cacheRead/cacheWrite 四项 + cost.total 投影为 cost)"
|
|
8
|
-
},
|
|
9
|
-
"events": [
|
|
10
|
-
{ "type": "tool_start", "toolName": "bash", "args": { "command": "ls -la /tmp/probe" } },
|
|
11
|
-
{
|
|
12
|
-
"type": "tool_end",
|
|
13
|
-
"toolName": "bash",
|
|
14
|
-
"args": { "command": "ls -la /tmp/probe" },
|
|
15
|
-
"result": { "output": "total 0\ndrwxr-xr-x 2 user staff 64 Aug 25 10:00:00 ." },
|
|
16
|
-
"isError": false
|
|
17
|
-
},
|
|
18
|
-
{ "type": "thinking_delta", "delta": "目录为空,无需进一步检查。" },
|
|
19
|
-
{ "type": "text_delta", "delta": "Probe directory is empty. " },
|
|
20
|
-
{ "type": "text_delta", "delta": "Nothing to clean up." },
|
|
21
|
-
{
|
|
22
|
-
"type": "message_end",
|
|
23
|
-
"usage": { "input": 12599, "output": 17, "cacheRead": 512, "cacheWrite": 0, "cost": 0.0001 }
|
|
24
|
-
},
|
|
25
|
-
{ "type": "turn_end" }
|
|
26
|
-
],
|
|
27
|
-
"content": "Probe directory is empty. Nothing to clean up."
|
|
28
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// zcode-appserver-harness.ts —— conformance 层的 fake app-server 引擎组装器(R6)。
|
|
2
|
-
//
|
|
3
|
-
// 与 engines/zcode/__tests__/zcode-engine-appserver.test.ts 的 makeEngine 同模式
|
|
4
|
-
// (scenario 文件随 env 固化进 fake 子进程),抽出为 conformance 专用副本而非跨测试
|
|
5
|
-
// 目录 import 测试文件——conformance 套件自包含(A12:套件不依赖被测引擎自己的
|
|
6
|
-
// 单测文件结构)。协议/断言面同源:fake-appserver.mjs + ZCODE_APPSERVER_GOLDEN。
|
|
7
|
-
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as os from "node:os";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
|
|
13
|
-
import { ZCODE_APPSERVER_GOLDEN } from "../../engines/zcode/golden-sample.ts";
|
|
14
|
-
import { ZcodeEngine } from "../../engines/zcode/zcode-engine.ts";
|
|
15
|
-
|
|
16
|
-
const FAKE_CLI = fileURLToPath(
|
|
17
|
-
new URL("../../engines/zcode/__tests__/__fixtures__/fake-appserver.mjs", import.meta.url),
|
|
18
|
-
);
|
|
19
|
-
const PROVIDER = "conformance-provider";
|
|
20
|
-
|
|
21
|
-
export interface AppserverHarnessOptions {
|
|
22
|
-
/** 覆盖 send 推送帧(缺省 = golden pushStream + 终态两帧;dropTurnTerminal 置真时去 turn.terminal)。 */
|
|
23
|
-
dropTurnTerminal?: boolean;
|
|
24
|
-
/** 覆盖 read 应答(read 是全文权威来源)。 */
|
|
25
|
-
readResult?: unknown;
|
|
26
|
-
/** session/stop 行为(fake scenario.stopBehavior;缺省 'terminal' = stop 优雅生效)。 */
|
|
27
|
-
stopBehavior?: "terminal" | "none";
|
|
28
|
-
/** 只保留 state.updated(挂起场景——turn 永不自然落定,abort 用例用)。 */
|
|
29
|
-
hangOnly?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface AppserverHarness {
|
|
33
|
-
engine: ZcodeEngine;
|
|
34
|
-
/** fake 子进程的事件流水文件(帧序断言数据源)。 */
|
|
35
|
-
stateFile: string;
|
|
36
|
-
workspace: string;
|
|
37
|
-
dataDir: string;
|
|
38
|
-
dispose(): Promise<void>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 组装连到 fake-appserver 的常驻引擎(单一 app-server 形态——2026-09 起无模式分派)。 */
|
|
42
|
-
export function makeAppserverHarness(opts: AppserverHarnessOptions = {}): AppserverHarness {
|
|
43
|
-
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zcode-conformance-appserver-"));
|
|
44
|
-
const dataDir = path.join(tmpRoot, "data");
|
|
45
|
-
const stateFile = path.join(tmpRoot, "state.jsonl");
|
|
46
|
-
const scenarioFile = path.join(tmpRoot, "scenario.json");
|
|
47
|
-
const workspace = path.join(tmpRoot, "ws");
|
|
48
|
-
const v2Path = path.join(tmpRoot, "v2.json");
|
|
49
|
-
|
|
50
|
-
fs.mkdirSync(path.dirname(v2Path), { recursive: true });
|
|
51
|
-
fs.writeFileSync(
|
|
52
|
-
v2Path,
|
|
53
|
-
JSON.stringify({
|
|
54
|
-
provider: { [PROVIDER]: { options: { apiKey: "k", baseURL: "https://t.example" }, models: { m1: {} } } },
|
|
55
|
-
}),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const pushes = opts.hangOnly
|
|
59
|
-
? [ZCODE_APPSERVER_GOLDEN.pushStream[0]].map((l) => JSON.parse(l) as unknown)
|
|
60
|
-
: [
|
|
61
|
-
...ZCODE_APPSERVER_GOLDEN.pushStream,
|
|
62
|
-
...(opts.dropTurnTerminal ? [] : [ZCODE_APPSERVER_GOLDEN.terminal[0]]),
|
|
63
|
-
ZCODE_APPSERVER_GOLDEN.terminal[1],
|
|
64
|
-
].map((l) => JSON.parse(l) as unknown);
|
|
65
|
-
fs.writeFileSync(
|
|
66
|
-
scenarioFile,
|
|
67
|
-
JSON.stringify({
|
|
68
|
-
createResult: JSON.parse(ZCODE_APPSERVER_GOLDEN.createResponse),
|
|
69
|
-
readResult: opts.readResult ?? JSON.parse(ZCODE_APPSERVER_GOLDEN.readResponse),
|
|
70
|
-
sendPushes: pushes,
|
|
71
|
-
...(opts.stopBehavior !== undefined ? { stopBehavior: opts.stopBehavior } : {}),
|
|
72
|
-
}),
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const engine = new ZcodeEngine({
|
|
76
|
-
engineDataDir: () => dataDir,
|
|
77
|
-
cliPath: FAKE_CLI,
|
|
78
|
-
sources: { v2ConfigPath: v2Path },
|
|
79
|
-
processEnv: {
|
|
80
|
-
PATH: process.env.PATH ?? "",
|
|
81
|
-
FAKE_STATE_FILE: stateFile,
|
|
82
|
-
FAKE_SESSION_SCENARIO: scenarioFile,
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
return {
|
|
86
|
-
engine,
|
|
87
|
-
stateFile,
|
|
88
|
-
workspace,
|
|
89
|
-
dataDir,
|
|
90
|
-
dispose: () =>
|
|
91
|
-
engine.dispose().finally(() => {
|
|
92
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
93
|
-
}),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** 读 fake 流水(帧序断言用)。 */
|
|
98
|
-
export function readFakeState(file: string): Array<Record<string, unknown>> {
|
|
99
|
-
try {
|
|
100
|
-
return fs
|
|
101
|
-
.readFileSync(file, "utf8")
|
|
102
|
-
.trim()
|
|
103
|
-
.split("\n")
|
|
104
|
-
.filter(Boolean)
|
|
105
|
-
.map((l) => JSON.parse(l) as Record<string, unknown>);
|
|
106
|
-
} catch {
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** fake 收到的客户端帧方法名序列(abort 链「stop 先于杀链」断言面)。 */
|
|
112
|
-
export function sentMethodNames(stateFile: string): string[] {
|
|
113
|
-
return readFakeState(stateFile)
|
|
114
|
-
.map((e) => (e as Record<string, unknown>)["frame"])
|
|
115
|
-
.filter((f): f is Record<string, unknown> => typeof f === "object" && f !== null && typeof (f as Record<string, unknown>)["method"] === "string")
|
|
116
|
-
.map((f) => f["method"] as string);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** golden read 应答里 assistant 消息的 text parts 拼接(read 全文——不变量 3a 的比对基准)。 */
|
|
120
|
-
export function goldenReadFullText(): string {
|
|
121
|
-
const read = JSON.parse(ZCODE_APPSERVER_GOLDEN.readResponse) as {
|
|
122
|
-
messages: Array<{ info: { role: string }; parts: Array<{ type: string; text?: string }> }>;
|
|
123
|
-
};
|
|
124
|
-
return (read.messages.at(-1)?.parts ?? [])
|
|
125
|
-
.filter((p) => p.type === "text")
|
|
126
|
-
.map((p) => p.text ?? "")
|
|
127
|
-
.join("");
|
|
128
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
resolveEngineDir,
|
|
5
|
-
resolveEnginesRoot,
|
|
6
|
-
resolveJournalPath,
|
|
7
|
-
resolvePoolDir,
|
|
8
|
-
sanitizeSeg,
|
|
9
|
-
} from '../paths.js';
|
|
10
|
-
|
|
11
|
-
describe('engine paths SSOT', () => {
|
|
12
|
-
it('engines 根锚定 dataDir 顶层', () => {
|
|
13
|
-
expect(resolveEnginesRoot('/data')).toBe('/data/engines');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('池目录 = engines/<engineId>/<poolKey>', () => {
|
|
17
|
-
expect(resolvePoolDir('/data', 'zcode', 'home-reviewer')).toBe('/data/engines/zcode/home-reviewer');
|
|
18
|
-
expect(resolveEngineDir('/data', 'zcode')).toBe('/data/engines/zcode');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('journal 路径带 taskId 文件名', () => {
|
|
22
|
-
expect(resolveJournalPath('/data', 'zcode', 'pool1', 'sa-123')).toBe(
|
|
23
|
-
'/data/engines/zcode/pool1/journal-sa-123.jsonl',
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('路径段安全编码:穿越/分隔符/空白折叠为连字符', () => {
|
|
28
|
-
expect(sanitizeSeg('a/b\\c')).toBe('a-b-c');
|
|
29
|
-
expect(sanitizeSeg('..')).toBe('default');
|
|
30
|
-
expect(sanitizeSeg(' ')).toBe('default');
|
|
31
|
-
expect(sanitizeSeg('home-reviewer')).toBe('home-reviewer');
|
|
32
|
-
expect(sanitizeSeg('中文名')).toBe('default');
|
|
33
|
-
expect(sanitizeSeg('x'.repeat(200))).toHaveLength(80);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('engineId 也走安全编码,防注入路径段', () => {
|
|
37
|
-
expect(resolvePoolDir('/data', '../../etc', 'p')).toBe('/data/engines/etc/p');
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/common/schema-emulation.ts
|
|
2
|
-
//
|
|
3
|
-
// schema 仿真降级(P2 公共降级层)。设计权威源:
|
|
4
|
-
// docs/architecture/subagent-engine-abstraction.md D4(native/emulated 硬分流)+ §3.3.3
|
|
5
|
-
// schema_emulation_failed 行 + §5 P2 行。
|
|
6
|
-
//
|
|
7
|
-
// ─── D4 硬分流(违反 = 历史事故形态) ───────────────────────────────
|
|
8
|
-
// 本模块只服务 capabilities.schemaEnforcement === 'emulated' 的引擎
|
|
9
|
-
// (zcode / opencode / kimi-code)。capabilities.schemaEnforcement === 'native' 的引擎
|
|
10
|
-
// (pi 的 PI_WORKFLOW_SCHEMA env 注入链路、claude-code --json-schema、codex
|
|
11
|
-
// --output-schema)**禁止** import 本模块做二次校验或改写其结果——宿主侧再叠一层
|
|
12
|
-
// ajv 会制造第二校验权威,恰是 structured-output 方案 A [HISTORICAL]
|
|
13
|
-
// (2026-08-01「校验自报 schema 致修复静默丢失」事故)的形态。
|
|
14
|
-
// ajv 只允许出现在 emulated 路径(本模块),这是全仓唯一例外。
|
|
15
|
-
//
|
|
16
|
-
// 重试语义:本模块两个函数保持纯——校验失败/提取失败由调用方(宿主编排层)决定
|
|
17
|
-
// 重试,语义与 structured-output 的 workflow-hook 对齐(重试一次、强化 prompt;
|
|
18
|
-
// 仍失败报 schema_emulation_failed,错误含原始输出尾部)。
|
|
19
|
-
//
|
|
20
|
-
// 文案对齐:错误回显风格对齐 structured-output 的 execute.ts("Schema validation
|
|
21
|
-
// failed: ..."),但不 import 其内部模块(公共层自实现;structured-output 是
|
|
22
|
-
// peer 依赖且可选,import 会造成公共层对 sibling extension 的硬依赖)。
|
|
23
|
-
|
|
24
|
-
import Ajv, { type ValidateFunction } from "ajv";
|
|
25
|
-
|
|
26
|
-
/** 原始输出尾部回显长度(结果对象的 tail 字段,供错误展示与重试 prompt 回灌)。 */
|
|
27
|
-
export const SCHEMA_EMULATION_TAIL_CHARS = 500;
|
|
28
|
-
|
|
29
|
-
// ============================================================
|
|
30
|
-
// prompt 注入段
|
|
31
|
-
// ============================================================
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 构造 prompt 注入段:schema 声明 + 输出格式约定(emulated 引擎的 launcher 把它拼进
|
|
35
|
-
* 最终 prompt,替代引擎不存在的 schema 强制通道)。
|
|
36
|
-
*
|
|
37
|
-
* 文案风格对齐 structured-output 的 promptGuidelines("Do not output JSON in text":emulated 引擎没有工具可调,JSON 必须出现在文本里)。
|
|
38
|
-
*/
|
|
39
|
-
export function buildSchemaEmulationSegment(schema: object): string {
|
|
40
|
-
const schemaJson = JSON.stringify(schema);
|
|
41
|
-
return [
|
|
42
|
-
"## Structured Output Requirement",
|
|
43
|
-
"Your final answer MUST contain exactly one JSON value conforming to this JSON Schema (draft-07):",
|
|
44
|
-
schemaJson,
|
|
45
|
-
"Output rules:",
|
|
46
|
-
"- Output ONLY the JSON value — no prose before or after it.",
|
|
47
|
-
"- If you wrap it in a markdown code fence, use a single ```json fence.",
|
|
48
|
-
"- Do not output multiple JSON values; the first complete JSON value is extracted.",
|
|
49
|
-
].join("\n");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ============================================================
|
|
53
|
-
// 三级容错提取 + ajv 校验
|
|
54
|
-
// ============================================================
|
|
55
|
-
|
|
56
|
-
/** 提取结果联合:ok=true 携带通过校验的 parsed;ok=false 携带 error 简述 + 原始输出尾部。 */
|
|
57
|
-
export type StructuredOutputResult =
|
|
58
|
-
| { ok: true; parsed: unknown }
|
|
59
|
-
| { ok: false; error: string; tail: string };
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 从模型文本输出提取并校验结构化 JSON(三级容错 → ajv)。
|
|
63
|
-
*
|
|
64
|
-
* 三级容错(逐级降级,任一级提取成功即进 ajv):
|
|
65
|
-
* 1. 直接 JSON.parse(trim 后整体是合法 JSON);
|
|
66
|
-
* 2. 剥 markdown code fence(```json ... ``` / ``` ... ```)取围栏内内容;
|
|
67
|
-
* 3. 首尾括号扫描(首个 '{'/'[' 到末个 '}'/']' 的子串——容纳前后杂文本)。
|
|
68
|
-
*
|
|
69
|
-
* ajv 校验失败/三级提取失败返回 ok:false(不 throw)——重试与否是宿主编排层的
|
|
70
|
-
* 决策(见文件头重试语义),本函数每次调用独立无状态。
|
|
71
|
-
*/
|
|
72
|
-
export function extractAndValidateStructuredOutput(
|
|
73
|
-
text: string,
|
|
74
|
-
schema: object,
|
|
75
|
-
): { ok: true; parsed: unknown } | { ok: false; error: string; tail: string } {
|
|
76
|
-
return extractImpl(text, schema);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** 实现体(签名拆分只为让导出签名与契约声明逐字一致,见 StructuredOutputResult)。 */
|
|
80
|
-
function extractImpl(text: string, schema: object): StructuredOutputResult {
|
|
81
|
-
const tail = text.length > SCHEMA_EMULATION_TAIL_CHARS
|
|
82
|
-
? text.slice(text.length - SCHEMA_EMULATION_TAIL_CHARS)
|
|
83
|
-
: text;
|
|
84
|
-
|
|
85
|
-
const extracted = extractJsonCandidate(text);
|
|
86
|
-
if (extracted === undefined) {
|
|
87
|
-
return {
|
|
88
|
-
ok: false,
|
|
89
|
-
error:
|
|
90
|
-
"could not extract JSON from model output after 3-stage fallback " +
|
|
91
|
-
"(direct parse -> code-fence strip -> bracket scan)",
|
|
92
|
-
tail,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const validate = getOrCompileValidator(schema);
|
|
97
|
-
if (validate === undefined) {
|
|
98
|
-
return {
|
|
99
|
-
ok: false,
|
|
100
|
-
error: "host-side JSON Schema compilation failed (invalid schema object)",
|
|
101
|
-
tail,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const valid = validate(extracted);
|
|
106
|
-
if (!valid) {
|
|
107
|
-
// 错误格式对齐 structured-output:"instancePath message" 逐条 join
|
|
108
|
-
const errors = validate.errors?.map((err) => `${err.instancePath} ${err.message}`).join("; ");
|
|
109
|
-
return {
|
|
110
|
-
ok: false,
|
|
111
|
-
error: `Schema validation failed: ${errors ?? "(no detail)"}`,
|
|
112
|
-
tail,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
return { ok: true, parsed: extracted };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ── 内部:三级提取 ──────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
/** 三级容错提取 JSON candidate;全部失败返回 undefined。 */
|
|
121
|
-
function extractJsonCandidate(text: string): unknown {
|
|
122
|
-
// 第 1 级:整体直接 parse(最常见——严格遵守输出约定的模型)
|
|
123
|
-
const direct = tryParse(text.trim());
|
|
124
|
-
if (direct.ok) return direct.value;
|
|
125
|
-
|
|
126
|
-
// 第 2 级:markdown code fence 剥离(模型爱用 ```json 包裹)
|
|
127
|
-
const fenced = extractFirstFencedBlock(text);
|
|
128
|
-
if (fenced !== undefined) {
|
|
129
|
-
const parsed = tryParse(fenced.trim());
|
|
130
|
-
if (parsed.ok) return parsed.value;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 第 3 级:首尾括号扫描(前后杂文本:"Here is the result: {...} Hope this helps")
|
|
134
|
-
const scanned = extractByBracketScan(text);
|
|
135
|
-
if (scanned !== undefined) {
|
|
136
|
-
const parsed = tryParse(scanned.trim());
|
|
137
|
-
if (parsed.ok) return parsed.value;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return undefined;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** JSON.parse 包装:失败返回 ok:false(malformed 不是异常态,是三级降级的输入)。 */
|
|
144
|
-
function tryParse(raw: string): { ok: true; value: unknown } | { ok: false } {
|
|
145
|
-
if (raw === "") return { ok: false };
|
|
146
|
-
try {
|
|
147
|
-
return { ok: true, value: JSON.parse(raw) };
|
|
148
|
-
} catch {
|
|
149
|
-
return { ok: false };
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** 取第一个 markdown code fence 块内容(```json / ``` 均可);无 fence 返回 undefined。 */
|
|
154
|
-
function extractFirstFencedBlock(text: string): string | undefined {
|
|
155
|
-
const match = /```(?:json)?\s*([\s\S]*?)\s*```/.exec(text);
|
|
156
|
-
return match?.[1];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** 首尾括号扫描:首个 '{'/'[' 到末个 '}'/']' 的子串;边界不合法返回 undefined。 */
|
|
160
|
-
function extractByBracketScan(text: string): string | undefined {
|
|
161
|
-
const objOpen = text.indexOf("{");
|
|
162
|
-
const arrOpen = text.indexOf("[");
|
|
163
|
-
const open = objOpen === -1 ? arrOpen : arrOpen === -1 ? objOpen : Math.min(objOpen, arrOpen);
|
|
164
|
-
const objClose = text.lastIndexOf("}");
|
|
165
|
-
const arrClose = text.lastIndexOf("]");
|
|
166
|
-
const close = Math.max(objClose, arrClose);
|
|
167
|
-
if (open === -1 || close === -1 || open >= close) return undefined;
|
|
168
|
-
return text.slice(open, close + 1);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── 内部:ajv 编译缓存 ──────────────────────────────────────────
|
|
172
|
-
|
|
173
|
-
// WeakMap 缓存(schema 对象引用即 key,GC 友好)——模式对齐 structured-output 的
|
|
174
|
-
// ajv-validator.ts(自实现,不跨包 import;见文件头依赖边界说明)。
|
|
175
|
-
const ajvCache = new WeakMap<object, ValidateFunction>();
|
|
176
|
-
|
|
177
|
-
/** 编译(或取缓存)validator;schema 非法(ajv 抛错)返回 undefined 由调用方报错。 */
|
|
178
|
-
function getOrCompileValidator(schema: object): ValidateFunction | undefined {
|
|
179
|
-
const cached = ajvCache.get(schema);
|
|
180
|
-
if (cached) return cached;
|
|
181
|
-
try {
|
|
182
|
-
// strict:false 对齐 structured-output 的宽容度(非标 keyword 不炸编译)
|
|
183
|
-
const validate = new Ajv({ strict: false }).compile(schema);
|
|
184
|
-
ajvCache.set(schema, validate);
|
|
185
|
-
return validate;
|
|
186
|
-
} catch {
|
|
187
|
-
return undefined;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/common/session-view-projection.ts
|
|
2
|
-
//
|
|
3
|
-
// Turn → ReplayedTurn 投影 + usage 聚合的单一实现(P5 收敛)。此前三份同构副本:
|
|
4
|
-
// common/journal-replay.ts(journal 重放投影)、engines/pi/pi-engine.ts read()(内联
|
|
5
|
-
// 重写)、engines/pi/reader.ts(native reader)——投影语义唯一(strip 内部态 + closed
|
|
6
|
-
// 恒 true + usageDelta 聚合),实现也必须唯一(防三处漂移)。
|
|
7
|
-
//
|
|
8
|
-
// 纯函数、零运行时依赖(仅类型 import):reader.ts 的双端复用约束(设计 §3.3.7,
|
|
9
|
-
// xyz-agent runtime 可 import)对本模块同样成立。
|
|
10
|
-
//
|
|
11
|
-
// 设计权威源:docs/architecture/subagent-engine-abstraction.md §3.3.6(重放等价性)。
|
|
12
|
-
|
|
13
|
-
import type { AgentUsageTotal, InternalToolCall, ToolCall, Turn } from "../../types.ts";
|
|
14
|
-
import type { ReplayedTurn } from "../types.ts";
|
|
15
|
-
|
|
16
|
-
/** InternalToolCall → ToolCall(导出纯净形状,不泄漏 running/done/failed 内部状态机)。 */
|
|
17
|
-
function toExportedToolCall(tc: InternalToolCall): ToolCall {
|
|
18
|
-
return {
|
|
19
|
-
toolName: tc.toolName,
|
|
20
|
-
...(tc.args !== undefined ? { args: tc.args } : {}),
|
|
21
|
-
...(tc.result !== undefined ? { result: tc.result } : {}),
|
|
22
|
-
...(tc.isError !== undefined ? { isError: tc.isError } : {}),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Turn → ReplayedTurn:剥离内部态(closed 恒 true——重放物无进行时语义,§3.3.6)。 */
|
|
27
|
-
export function toReplayedTurn(turn: Turn): ReplayedTurn {
|
|
28
|
-
return {
|
|
29
|
-
text: turn.text,
|
|
30
|
-
thinking: turn.thinking,
|
|
31
|
-
toolCalls: turn.toolCalls.map(toExportedToolCall),
|
|
32
|
-
closed: true,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** 各 turn usageDelta 聚合为 AgentUsageTotal(无任何 usage 数据时 undefined)。 */
|
|
37
|
-
export function aggregateUsage(turns: readonly Turn[]): AgentUsageTotal | undefined {
|
|
38
|
-
let acc: AgentUsageTotal | undefined;
|
|
39
|
-
for (const turn of turns) {
|
|
40
|
-
const d = turn.usageDelta;
|
|
41
|
-
if (!d) continue;
|
|
42
|
-
if (!acc) acc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, total: 0 };
|
|
43
|
-
acc.input += d.input;
|
|
44
|
-
acc.output += d.output;
|
|
45
|
-
acc.cacheRead += d.cacheRead;
|
|
46
|
-
acc.cacheWrite += d.cacheWrite;
|
|
47
|
-
acc.cost += d.cost ?? 0;
|
|
48
|
-
}
|
|
49
|
-
if (acc) acc.total = acc.input + acc.output + acc.cacheRead + acc.cacheWrite;
|
|
50
|
-
return acc;
|
|
51
|
-
}
|