@zhushanwen/subagent-core 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
- package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
- package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
- package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
- package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
- package/dist/execution/engine/engines/zcode/constants.js +17 -27
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +2 -2
- package/dist/index.cjs +16418 -14070
- package/dist/index.d.cts +5136 -4016
- package/dist/index.d.ts +5136 -4016
- package/dist/index.js +16343 -14048
- package/dist/{types-DpUO16pj.d.cts → types-Dv4QhSJ_.d.cts} +215 -112
- package/dist/{types-DpUO16pj.d.ts → types-Dv4QhSJ_.d.ts} +215 -112
- package/package.json +3 -3
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +4 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +4 -4
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +2 -2
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/delivery-methods.test.ts +75 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
- package/src/execution/__tests__/dialog-queue.test.ts +2 -2
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +173 -17
- package/src/execution/__tests__/gc-timer.test.ts +55 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
- package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +973 -18
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store.test.ts +10 -9
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
- package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +257 -14
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
- package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +2 -1
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +42 -22
- package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +8 -8
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +6 -5
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/agent-registry.ts +74 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +4 -2
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +25 -319
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +24 -11
- package/src/execution/engine/engines/zcode/constants.ts +75 -71
- package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +12 -143
- package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
- package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +24 -6
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +131 -93
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/record-store.ts +119 -73
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +117 -47
- package/src/execution/settled-watchdog.ts +264 -52
- package/src/execution/subagent-actions-core.ts +22 -22
- package/src/execution/subagent-service.ts +815 -963
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +102 -74
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/index.ts +442 -316
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +1 -1
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +19 -73
- package/src/orchestration/launcher.ts +128 -81
- package/src/orchestration/lifecycle.ts +43 -45
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +2 -2
- package/src/orchestration/models/trace.ts +1 -1
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +1 -1
- package/src/orchestration/script-lint.ts +91 -58
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery.test.ts +258 -215
- package/src/shared/atomic-write.ts +12 -9
- package/src/shared/meta-parser.ts +158 -84
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +9 -183
- package/src/shared/schema-jsonify.ts +1 -1
- package/workflows/review-fix-loop-utils.cjs +151 -89
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
- package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
- package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/__tests__/turn-limiter.test.ts
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
|
-
import { createTurnLimiter } from "../turn-limiter.ts";
|
|
4
|
+
import { createTurnLimiter } from "../engine/engines/pi/turn-limiter.ts";
|
|
5
5
|
|
|
6
6
|
describe("createTurnLimiter", () => {
|
|
7
7
|
it("does nothing before maxTurns", () => {
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
// realHandler 路由:channel 命中 → channelHandler(经 coerceUiResponse 形变);未命中 → defaultDialogForward(dialog 转发结果,fire-and-forget 转发 ctx.ui.* 后回 ack,未知 method warn + ack)。
|
|
10
10
|
// 测接口契约,不测实现细节。
|
|
11
11
|
|
|
12
|
-
import type { ExtensionContext,
|
|
12
|
+
import type { ExtensionContext, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
// ExtensionMode 未从 pi 包根 re-export(实装 0.84.4 核对),从权威 ExtensionContext 派生
|
|
15
|
+
type ExtensionMode = ExtensionContext["mode"];
|
|
13
16
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
17
|
|
|
15
18
|
const { loggerMock } = vi.hoisted(() => ({
|
|
@@ -39,14 +42,17 @@ function makeCtx(mode: ExtensionMode): ExtensionContext {
|
|
|
39
42
|
},
|
|
40
43
|
modelRegistry: undefined,
|
|
41
44
|
model: undefined,
|
|
42
|
-
} as ExtensionContext;
|
|
45
|
+
} as unknown as ExtensionContext;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/** 带 mock ctx.ui 的 ExtensionContext(GUI fire-and-forget 转发测试用)。
|
|
46
49
|
* dialog method(select/confirm/input/editor)返回 undefined/true/"" 兜底,
|
|
47
50
|
* fire-and-forget method(notify/setStatus/setWidget/setTitle/setEditorText)是 void spy。 */
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
// ui 成员 = 真实签名 & vi.fn 能力(映射交叉):调用点走真实签名,mock 打点方法可达
|
|
52
|
+
type MockedUi = { [K in keyof ExtensionUIContext]: ExtensionUIContext[K] & ReturnType<typeof vi.fn> };
|
|
53
|
+
|
|
54
|
+
function makeCtxWithUi(mode: ExtensionMode = "rpc"): Omit<ExtensionContext, "ui"> & { ui: MockedUi } {
|
|
55
|
+
const ui = {
|
|
50
56
|
select: vi.fn(async () => undefined),
|
|
51
57
|
confirm: vi.fn(async () => true),
|
|
52
58
|
input: vi.fn(async () => ""),
|
|
@@ -67,8 +73,8 @@ function makeCtxWithUi(mode: ExtensionMode = "rpc"): ExtensionContext & { ui: Re
|
|
|
67
73
|
},
|
|
68
74
|
modelRegistry: undefined,
|
|
69
75
|
model: undefined,
|
|
70
|
-
ui,
|
|
71
|
-
} as unknown as ExtensionContext & { ui:
|
|
76
|
+
ui: ui as unknown as MockedUi,
|
|
77
|
+
} as unknown as Omit<ExtensionContext, "ui"> & { ui: MockedUi };
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
function dialogReq(id: string, channel?: string): UiRequest {
|
|
@@ -18,8 +18,8 @@ import { PassThrough } from "node:stream";
|
|
|
18
18
|
|
|
19
19
|
import { describe, expect, it, vi } from "vitest";
|
|
20
20
|
|
|
21
|
-
import { ASK_USER_RPC_PROMPT } from "../session-runner.ts";
|
|
22
|
-
import { parseSpawnLine } from "../spawn-event-adapter.ts";
|
|
21
|
+
import { ASK_USER_RPC_PROMPT } from "../engine/engines/pi/session-runner.ts";
|
|
22
|
+
import { parseSpawnLine } from "../engine/engines/pi/spawn-event-adapter.ts";
|
|
23
23
|
import { parseChannel } from "../ui-channels.ts";
|
|
24
24
|
import { createUiRequestQueue, type UiRequest, type UiRequestHandler } from "../ui-request-queue.ts";
|
|
25
25
|
|
|
@@ -69,7 +69,8 @@ describe("parseSpawnLine — ask_user 请求解析(Pi 原生格式)", () =>
|
|
|
69
69
|
it("request.title === ASK_USER_MARKER(NUL 前缀 marker 原样保留,由 parseChannel 提取)", () => {
|
|
70
70
|
const result = parseSpawnLine(askUserLine("ui-req-001"));
|
|
71
71
|
if (result?.kind !== "extension_ui_request") return;
|
|
72
|
-
|
|
72
|
+
if (result.request.method !== "select") return expect.fail("request.method 应为 select");
|
|
73
|
+
expect((result.request as { title?: string }).title).toBe(ASK_USER_MARKER);
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
it("id 被正确提取(用于 response 关联)", () => {
|
|
@@ -55,7 +55,7 @@ describe("UI 请求队列", () => {
|
|
|
55
55
|
it("多个 extension_ui_request 按 FIFO 顺序处理", async () => {
|
|
56
56
|
const callOrder: string[] = [];
|
|
57
57
|
// 每个请求返回独立的可控 Promise
|
|
58
|
-
const resolvers: Array<(v:
|
|
58
|
+
const resolvers: Array<(v: UiResponse) => void> = [];
|
|
59
59
|
|
|
60
60
|
const handler: UiRequestHandler = vi.fn((req: UiRequest) => {
|
|
61
61
|
callOrder.push(req.title ?? "");
|
|
@@ -94,7 +94,7 @@ describe("UI 请求队列", () => {
|
|
|
94
94
|
|
|
95
95
|
it("第一个请求未 resolve 时第二个不调用 uiRequestHandler", async () => {
|
|
96
96
|
const callOrder: string[] = [];
|
|
97
|
-
let firstResolve: (v:
|
|
97
|
+
let firstResolve: (v: UiResponse) => void;
|
|
98
98
|
|
|
99
99
|
const handler: UiRequestHandler = vi.fn((req: UiRequest) => {
|
|
100
100
|
callOrder.push(req.title ?? "");
|
|
@@ -156,3 +156,143 @@ describe("UI 请求队列 — timeout 字段透传(LC-3/T2⑦)", () => {
|
|
|
156
156
|
expect(received[0].timeout).toBe(5000);
|
|
157
157
|
});
|
|
158
158
|
});
|
|
159
|
+
|
|
160
|
+
// ── extractMethodFields 字段复制链路(经 createUiRequestQueue → handleUiRequest 锁定)──
|
|
161
|
+
// 13 个 method-specific 字段的复制语义:typed 守卫字段(string/number/array)验型通过才复制、
|
|
162
|
+
// presence-only 字段(statusText/widgetLines/widgetPlacement)仅 in 检查直赋、缺失字段不复制。
|
|
163
|
+
// 协议形状来自 JSON 反序列化,坏类型运行时可达——用例以 as unknown as 构造越界形状做防御锁定。
|
|
164
|
+
|
|
165
|
+
describe("UI 请求队列 — method-specific 字段复制(extractMethodFields 链路)", () => {
|
|
166
|
+
function makeQueue(handler: UiRequestHandler): { enqueue: ReturnType<typeof createUiRequestQueue> } {
|
|
167
|
+
const child = makeFakeChild();
|
|
168
|
+
const ctx = { uiRequestHandler: handler } as Parameters<
|
|
169
|
+
typeof createUiRequestQueue
|
|
170
|
+
>[1];
|
|
171
|
+
return { enqueue: createUiRequestQueue(child, ctx) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
it("全字段请求:typed 守卫字段与 presence-only 字段全部复制到 UiRequest", async () => {
|
|
175
|
+
const received: UiRequest[] = [];
|
|
176
|
+
const { enqueue } = makeQueue((req) => {
|
|
177
|
+
received.push(req);
|
|
178
|
+
return Promise.resolve<UiResponse>({ ack: true });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// 13 字段全量形状(协议 union 无单变体携带全部字段,运行时平铺可达)
|
|
182
|
+
const req = {
|
|
183
|
+
method: "select",
|
|
184
|
+
title: "t",
|
|
185
|
+
options: ["a", "b"],
|
|
186
|
+
message: "m",
|
|
187
|
+
placeholder: "p",
|
|
188
|
+
prefill: "pf",
|
|
189
|
+
notifyType: "warning",
|
|
190
|
+
statusKey: "sk",
|
|
191
|
+
statusText: "st",
|
|
192
|
+
widgetKey: "wk",
|
|
193
|
+
widgetLines: ["l1"],
|
|
194
|
+
widgetPlacement: "belowEditor",
|
|
195
|
+
text: "tx",
|
|
196
|
+
timeout: 2500,
|
|
197
|
+
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
198
|
+
|
|
199
|
+
enqueue("r1", req);
|
|
200
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
201
|
+
|
|
202
|
+
expect(received).toHaveLength(1);
|
|
203
|
+
expect(received[0]).toMatchObject({
|
|
204
|
+
title: "t",
|
|
205
|
+
options: ["a", "b"],
|
|
206
|
+
message: "m",
|
|
207
|
+
placeholder: "p",
|
|
208
|
+
prefill: "pf",
|
|
209
|
+
notifyType: "warning",
|
|
210
|
+
statusKey: "sk",
|
|
211
|
+
statusText: "st",
|
|
212
|
+
widgetKey: "wk",
|
|
213
|
+
widgetLines: ["l1"],
|
|
214
|
+
widgetPlacement: "belowEditor",
|
|
215
|
+
text: "tx",
|
|
216
|
+
timeout: 2500,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("typed 守卫字段类型不符时跳过复制,其余字段照常", async () => {
|
|
221
|
+
const received: UiRequest[] = [];
|
|
222
|
+
const { enqueue } = makeQueue((req) => {
|
|
223
|
+
received.push(req);
|
|
224
|
+
return Promise.resolve<UiResponse>({ ack: true });
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const req = {
|
|
228
|
+
method: "select",
|
|
229
|
+
title: "kept-title", // 合法(title 非法值会在链路上游 startsWith 处炸掉整条请求,见下一条用例)
|
|
230
|
+
options: "not-array", // string ≠ array → 跳过
|
|
231
|
+
timeout: "5000", // string ≠ number → 跳过
|
|
232
|
+
message: "kept",
|
|
233
|
+
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
234
|
+
|
|
235
|
+
enqueue("r1", req);
|
|
236
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
237
|
+
|
|
238
|
+
expect(received).toHaveLength(1);
|
|
239
|
+
expect(received[0].title).toBe("kept-title");
|
|
240
|
+
expect("options" in received[0]).toBe(false);
|
|
241
|
+
expect("timeout" in received[0]).toBe(false);
|
|
242
|
+
expect(received[0].message).toBe("kept");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("typed 非法 title(number)在上游 startsWith 处炸链路 → handler 不被调、单请求失败被吞不阻塞队列", async () => {
|
|
246
|
+
// HEAD 真实行为锚定(探针实证):title: 42 在 extractMethodFields 之前的环节
|
|
247
|
+
// 触发 str.startsWith is not a function,异常被 handleUiRequest 的 .catch 吞掉
|
|
248
|
+
//(注释声明的「单个请求失败不阻塞后续」语义)——守卫的「跳过该字段」对 title 不可达。
|
|
249
|
+
const received: UiRequest[] = [];
|
|
250
|
+
const { enqueue } = makeQueue((req) => {
|
|
251
|
+
received.push(req);
|
|
252
|
+
return Promise.resolve<UiResponse>({ ack: true });
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const badReq = {
|
|
256
|
+
method: "select",
|
|
257
|
+
title: 42,
|
|
258
|
+
message: "kept",
|
|
259
|
+
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
260
|
+
enqueue("r1", badReq);
|
|
261
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
262
|
+
expect(received).toHaveLength(0); // handler 未被调(链路在上游失败)
|
|
263
|
+
|
|
264
|
+
// 队列不阻塞:后续合法请求照常处理
|
|
265
|
+
enqueue("r2", {
|
|
266
|
+
method: "select",
|
|
267
|
+
title: "Q2",
|
|
268
|
+
options: [JSON.stringify({ question: "Q2", options: [{ label: "A" }] })],
|
|
269
|
+
});
|
|
270
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
271
|
+
expect(received).toHaveLength(1);
|
|
272
|
+
expect(received[0].title).toBe("Q2");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("presence-only 字段仅 in 检查直赋;未声明的字段不复制(保持 UiRequest 可选)", async () => {
|
|
276
|
+
const received: UiRequest[] = [];
|
|
277
|
+
const { enqueue } = makeQueue((req) => {
|
|
278
|
+
received.push(req);
|
|
279
|
+
return Promise.resolve<UiResponse>({ ack: true });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// statusText 存在但值为 undefined(statusText: string | undefined 变体合法形态)→ 仍复制
|
|
283
|
+
const req = {
|
|
284
|
+
method: "setStatus",
|
|
285
|
+
statusKey: "k",
|
|
286
|
+
statusText: undefined,
|
|
287
|
+
unknownField: "should-not-copy",
|
|
288
|
+
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
289
|
+
|
|
290
|
+
enqueue("r1", req);
|
|
291
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
292
|
+
|
|
293
|
+
expect(received).toHaveLength(1);
|
|
294
|
+
expect("statusText" in received[0]).toBe(true);
|
|
295
|
+
expect(received[0].statusText).toBeUndefined();
|
|
296
|
+
expect("unknownField" in received[0]).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -80,7 +80,16 @@ import { isProcessAlive } from "../alive-store.ts";
|
|
|
80
80
|
import { encodeCwd } from "../path-encoding.ts";
|
|
81
81
|
import { WorktreeManager } from "../worktree-manager.ts";
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
// 被测链路(gitRunAsync)调用四参形态(file, args, options, callback);vi.mocked
|
|
84
|
+
// 直接包 execFile 会推导到无 options 重载,显式绑定四参签名
|
|
85
|
+
const mockExecFile = vi.mocked(
|
|
86
|
+
execFile as unknown as (
|
|
87
|
+
file: string,
|
|
88
|
+
args: readonly string[],
|
|
89
|
+
options: unknown,
|
|
90
|
+
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
|
91
|
+
) => void,
|
|
92
|
+
);
|
|
84
93
|
const mockExistsSync = vi.mocked(fs.existsSync);
|
|
85
94
|
const mockIsProcessAlive = vi.mocked(isProcessAlive);
|
|
86
95
|
|
|
@@ -95,7 +104,7 @@ type ExecFileResult = {
|
|
|
95
104
|
};
|
|
96
105
|
function setupExecFile(impl?: (args: readonly string[]) => ExecFileResult): void {
|
|
97
106
|
mockExecFile.mockImplementation(
|
|
98
|
-
(_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout
|
|
107
|
+
(_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
99
108
|
const r = impl ? impl(args) : { stdout: "" };
|
|
100
109
|
if (r.err) cb(r.err, r.stdout ?? "", r.stderr ?? "");
|
|
101
110
|
else cb(null, r.stdout ?? "", r.stderr ?? "");
|
|
@@ -178,7 +187,7 @@ describe("WorktreeManager", () => {
|
|
|
178
187
|
await mgr.create(MAIN_CWD, RECORD_ID);
|
|
179
188
|
|
|
180
189
|
expect(mockAdd).toHaveBeenCalledTimes(1);
|
|
181
|
-
const entry = mockAdd.mock.calls[0][0] as { repo: string; branch: string; pid: number };
|
|
190
|
+
const entry = mockAdd.mock.calls[0]![0] as { repo: string; branch: string; pid: number };
|
|
182
191
|
expect(entry.repo).toBe(MAIN_CWD);
|
|
183
192
|
expect(entry.branch).toBe(`pi-sub-${RECORD_ID}`);
|
|
184
193
|
expect(entry.pid).toBe(0);
|
|
@@ -266,7 +275,7 @@ describe("WorktreeManager", () => {
|
|
|
266
275
|
let maxWriteActive = 0;
|
|
267
276
|
let addCount = 0;
|
|
268
277
|
mockExecFile.mockImplementation(
|
|
269
|
-
(_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout
|
|
278
|
+
(_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
270
279
|
const isAdd = args[0] === "worktree" && args[1] === "add";
|
|
271
280
|
if (isAdd) {
|
|
272
281
|
// 只对写命令(worktree add)计并发——读命令(status/rev-parse)无锁可并行是设计预期
|
|
@@ -442,7 +451,7 @@ describe("WorktreeManager", () => {
|
|
|
442
451
|
let maxActive = 0;
|
|
443
452
|
// 异步完成 cb:若队列失效,后继命令的 impl 入口会先于前驱 cb 到达 → active=2
|
|
444
453
|
mockExecFile.mockImplementation(
|
|
445
|
-
(_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout
|
|
454
|
+
(_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
446
455
|
active++;
|
|
447
456
|
maxActive = Math.max(maxActive, active);
|
|
448
457
|
setTimeout(() => {
|
|
@@ -52,7 +52,7 @@ vi.mock("./pi-invocation.ts", () => ({
|
|
|
52
52
|
|
|
53
53
|
import { WorktreeManager } from "../worktree-manager.ts";
|
|
54
54
|
import { WorktreeRegistry, SPAWN_GRACE_MS } from "../worktree-registry.ts";
|
|
55
|
-
import { runSpawn } from "../session-runner.ts";
|
|
55
|
+
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
56
56
|
import type { WorktreeHandle } from "../types.ts";
|
|
57
57
|
import { makeCtx, makeOpts, makeRecord } from "./helpers/spawn-mock.ts";
|
|
58
58
|
|
|
@@ -118,7 +118,7 @@ async function cleanup(): Promise<void> {
|
|
|
118
118
|
handle = undefined;
|
|
119
119
|
}
|
|
120
120
|
try {
|
|
121
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
121
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
122
122
|
} catch {
|
|
123
123
|
// best-effort
|
|
124
124
|
}
|
|
@@ -213,7 +213,8 @@ describe("worktree pid 注册链路(真实 spawn 集成)", { timeout: 30_000
|
|
|
213
213
|
makeOpts({ worktree: handle }),
|
|
214
214
|
ctx,
|
|
215
215
|
);
|
|
216
|
-
|
|
216
|
+
// 历史「status !== error」弱断言:AgentResult 无 status 字段,经视图访问保持断言语义不变
|
|
217
|
+
expect((result as { status?: string }).status).not.toBe("error"); // 进程正常退出(exit 0),非 spawn 失败
|
|
217
218
|
|
|
218
219
|
// 3. pid 已补全(短命进程退出后 pid 仍有效,registerPid 同步执行不受退出影响)
|
|
219
220
|
const entry = readEntry(handle.branch);
|
|
@@ -56,41 +56,64 @@ export function parseAgentWithMeta(
|
|
|
56
56
|
content: string,
|
|
57
57
|
): { config: AgentConfig; meta: AgentMeta | null } {
|
|
58
58
|
const name = path.basename(filePath, ".md");
|
|
59
|
+
// 结构切分三态(与 parseAgentProfile 共用 splitAgentFrontmatter,切分逻辑逐字节一致)
|
|
60
|
+
const fm = splitAgentFrontmatter(content);
|
|
59
61
|
|
|
60
62
|
// 无 frontmatter → 整个内容作为 systemPrompt
|
|
61
|
-
if (
|
|
63
|
+
if (fm.kind === "none") {
|
|
62
64
|
return { config: { name, systemPrompt: content.trim() }, meta: null };
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
// 未闭合 frontmatter:提取 name,其余作为 systemPrompt
|
|
68
|
-
const yamlBlock = content.slice(FM_DELIM.length);
|
|
67
|
+
// 未闭合 frontmatter:提取 name,其余作为 systemPrompt
|
|
68
|
+
if (fm.kind === "unclosed") {
|
|
69
69
|
return {
|
|
70
70
|
config: {
|
|
71
|
-
name: extractYamlField(yamlBlock, "name") ?? name,
|
|
71
|
+
name: extractYamlField(fm.yamlBlock, "name") ?? name,
|
|
72
72
|
systemPrompt: content.trim(),
|
|
73
73
|
},
|
|
74
74
|
meta: null,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
return parseClosedFrontmatterAgent(filePath, name, content, fm.yamlBlock, fm.body);
|
|
79
|
+
}
|
|
80
80
|
|
|
81
|
+
/** closed frontmatter 的主路径:IF1 严格层 + legacy fallback 字段解析 + config 投影。
|
|
82
|
+
* 字段解析/warn/throw 时序与提取前一致(parseResourceMeta → fallback+warn → engine+校验 → 投影)。 */
|
|
83
|
+
function parseClosedFrontmatterAgent(
|
|
84
|
+
filePath: string,
|
|
85
|
+
name: string,
|
|
86
|
+
content: string,
|
|
87
|
+
yamlBlock: string,
|
|
88
|
+
body: string,
|
|
89
|
+
): { config: AgentConfig; meta: AgentMeta | null } {
|
|
81
90
|
// m2:结构化路由字段(name/model/tools)经 IF1 parseResourceMeta(统一 parser),
|
|
82
91
|
// 消灭本地 frontmatter parser 与 subagent-list-injector 的重复。thinkingLevel/defaultBackground
|
|
83
92
|
// 是执行配置(非 AgentMeta 路由字段),仍用 extractYamlField 取。
|
|
84
93
|
const meta = parseResourceMeta(content, "agent");
|
|
85
94
|
const agentMeta = meta?.kind === "agent" ? meta : null;
|
|
95
|
+
const legacyFallbacks = resolveLegacyRoutingFallbacks(agentMeta, yamlBlock, filePath);
|
|
96
|
+
// engine 字段(D9):结构化优先(IF1),legacy fallback 与 model/tools 同判——
|
|
97
|
+
// agentMeta 未通过 IF1 时配置不丢
|
|
98
|
+
const engine = resolveAgentEngine(agentMeta, yamlBlock, filePath);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
config: projectAgentConfig(name, body, agentMeta, legacyFallbacks, engine, yamlBlock),
|
|
102
|
+
meta: agentMeta,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** legacy fallback 路由字段(model/tools):IF1 未通过时不静默丢失(MF-3),并按需 warn。 */
|
|
107
|
+
function resolveLegacyRoutingFallbacks(
|
|
108
|
+
agentMeta: AgentMeta | null,
|
|
109
|
+
yamlBlock: string,
|
|
110
|
+
filePath: string,
|
|
111
|
+
): { modelFallback: string | undefined; toolsFallback: string[] | undefined } {
|
|
86
112
|
// MF-3 regression fix:agentMeta=null(IF1 要求 name/description 必填,缺则 parseResourceMeta
|
|
87
113
|
// 返 null)时,model/tools 不能静默丢失——fallback 用 extractYamlField 取(保留重构前行为)。
|
|
88
114
|
// 注入路由可见性由 discovery/injector 按 available 标志决定,与本处(direct-path loadByPath)无关。
|
|
89
115
|
const modelFallback = extractYamlField(yamlBlock, "model");
|
|
90
|
-
const
|
|
91
|
-
const toolsFallback = toolsFallbackRaw
|
|
92
|
-
? toolsFallbackRaw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
93
|
-
: undefined;
|
|
116
|
+
const toolsFallback = parseCommaListFallback(extractYamlField(yamlBlock, "tools"));
|
|
94
117
|
if (!agentMeta && /^model:|^tools:/m.test(yamlBlock)) {
|
|
95
118
|
// m2 exec-review MINOR-2 + MF-3:IF1 未通过(缺 name/description)→ model/tools 经 legacy
|
|
96
119
|
// fallback 取(direct-path loadByPath 不丢配置,与重构前一致);但结构化路由注入不可见,
|
|
@@ -100,32 +123,53 @@ export function parseAgentWithMeta(
|
|
|
100
123
|
"model/tools 经 legacy fallback 生效(直接路径不丢配置),但结构化路由不可见——请补充 description",
|
|
101
124
|
);
|
|
102
125
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
return { modelFallback, toolsFallback };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** engine 字段解析 + 解析期注册校验(D9:未注册 id 前置暴露,不留到运行时神秘失败)。
|
|
130
|
+
* 为什么在解析期而非路由期:配置错误的根源在 .md 文件,越早报错定位越准(错误含文件路径 + 注册清单)。 */
|
|
131
|
+
function resolveAgentEngine(
|
|
132
|
+
agentMeta: AgentMeta | null,
|
|
133
|
+
yamlBlock: string,
|
|
134
|
+
filePath: string,
|
|
135
|
+
): string | undefined {
|
|
106
136
|
const engine = agentMeta?.engine ?? extractYamlField(yamlBlock, "engine");
|
|
107
|
-
// 解析期校验(D9:未注册 id 前置暴露,不留到运行时神秘失败)。为什么在解析期而非
|
|
108
|
-
// 路由期:配置错误的根源在 .md 文件,越早报错定位越准(错误含文件路径 + 注册清单)
|
|
109
137
|
if (engine !== undefined && !hasEngine(engine)) {
|
|
110
138
|
throw new EngineNotFoundError(engine, listEngines(), filePath);
|
|
111
139
|
}
|
|
140
|
+
return engine;
|
|
141
|
+
}
|
|
112
142
|
|
|
143
|
+
/** closed frontmatter 的 AgentConfig 投影(IF1 严格层优先,逐字段 legacy fallback)。 */
|
|
144
|
+
function projectAgentConfig(
|
|
145
|
+
name: string,
|
|
146
|
+
body: string,
|
|
147
|
+
agentMeta: AgentMeta | null,
|
|
148
|
+
fallbacks: { modelFallback: string | undefined; toolsFallback: string[] | undefined },
|
|
149
|
+
engine: string | undefined,
|
|
150
|
+
yamlBlock: string,
|
|
151
|
+
): AgentConfig {
|
|
113
152
|
return {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
? agentMeta.tools
|
|
122
|
-
: (toolsFallback && toolsFallback.length > 0 ? toolsFallback : undefined),
|
|
123
|
-
defaultBackground: defaultBackgroundRaw === "true" ? true : undefined,
|
|
124
|
-
},
|
|
125
|
-
meta: agentMeta,
|
|
153
|
+
name: agentMeta?.name ?? name,
|
|
154
|
+
systemPrompt: body,
|
|
155
|
+
model: agentMeta?.model ?? fallbacks.modelFallback ?? undefined,
|
|
156
|
+
thinkingLevel: extractYamlField(yamlBlock, "thinkingLevel") ?? undefined,
|
|
157
|
+
...(engine !== undefined ? { engine } : {}),
|
|
158
|
+
tools: resolveAgentTools(agentMeta, fallbacks.toolsFallback),
|
|
159
|
+
defaultBackground: extractYamlField(yamlBlock, "defaultBackground") === "true" ? true : undefined,
|
|
126
160
|
};
|
|
127
161
|
}
|
|
128
162
|
|
|
163
|
+
/** tools 投影:IF1 严格层非空清单优先,否则 legacy fallback 非空清单,均缺省 undefined。 */
|
|
164
|
+
function resolveAgentTools(
|
|
165
|
+
agentMeta: AgentMeta | null,
|
|
166
|
+
toolsFallback: string[] | undefined,
|
|
167
|
+
): string[] | undefined {
|
|
168
|
+
return agentMeta?.tools && agentMeta.tools.length > 0
|
|
169
|
+
? agentMeta.tools
|
|
170
|
+
: (toolsFallback && toolsFallback.length > 0 ? toolsFallback : undefined);
|
|
171
|
+
}
|
|
172
|
+
|
|
129
173
|
/** 提取简单 `key: value` 字段,剥离引号。 */
|
|
130
174
|
function extractYamlField(yaml: string, key: string): string | undefined {
|
|
131
175
|
const regex = new RegExp(`^${key}:\\s*(.+)$`, "m");
|
|
@@ -255,7 +299,7 @@ type AgentFrontmatterSlice =
|
|
|
255
299
|
| { kind: "unclosed"; yamlBlock: string }
|
|
256
300
|
| { kind: "closed"; yamlBlock: string; body: string };
|
|
257
301
|
|
|
258
|
-
/** frontmatter
|
|
302
|
+
/** frontmatter 结构切分(产出三态供分派;parseAgentWithMeta 与 parseAgentProfile 共用单点)。 */
|
|
259
303
|
function splitAgentFrontmatter(text: string): AgentFrontmatterSlice {
|
|
260
304
|
if (!text.startsWith(FM_DELIM)) return { kind: "none" };
|
|
261
305
|
const closeIdx = text.indexOf(FM_DELIM, FM_DELIM.length);
|
|
@@ -35,6 +35,9 @@ export function mapToWorkflowAgentResult(
|
|
|
35
35
|
): WorkflowAgentResult {
|
|
36
36
|
return {
|
|
37
37
|
content: r.text,
|
|
38
|
+
// [D5-③] 失败分诊标签透传(产出侧 output-collector 写入,消费侧
|
|
39
|
+
// executeAgentCall 读字段分诊)。成功路径 undefined 不落键。
|
|
40
|
+
...(r.failureKind !== undefined ? { failureKind: r.failureKind } : {}),
|
|
38
41
|
parsedOutput: r.parsedOutput,
|
|
39
42
|
error: r.success ? undefined : (r.error || "Agent call failed (aborted or unknown error)"),
|
|
40
43
|
durationMs: r.durationMs,
|