@zhushanwen/pi-subagent-workflow 8.6.0 → 8.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/package.json +9 -13
- package/relay/relay.mjs +1 -1
- package/src/host/__tests__/pi-host.test.ts +214 -0
- package/src/host/pi-host.ts +109 -0
- package/src/index.ts +89 -32
- package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
- package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
- package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
- package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
- package/src/injectors/engine-awareness.ts +123 -0
- package/src/injectors/model-list-injector.ts +1 -1
- package/src/injectors/subagent-list-injector.ts +8 -8
- package/src/injectors/workflow-list-injector.ts +8 -8
- package/src/interface/__tests__/detectors.test.ts +3 -3
- package/src/interface/__tests__/subagent-tool-path-guard.test.ts +2 -2
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
- package/src/interface/bg-notify-render.ts +4 -4
- package/src/interface/commands.ts +3 -3
- package/src/interface/format.ts +2 -2
- package/src/interface/helpers.ts +1 -1
- package/src/interface/list-component.ts +4 -4
- package/src/interface/list-shared.ts +2 -2
- package/src/interface/list-view.ts +2 -2
- package/src/interface/subagent-actions.ts +263 -11
- package/src/interface/subagent-tool-schema.ts +20 -13
- package/src/interface/subagent-tool.ts +11 -6
- package/src/interface/subagents.ts +3 -3
- package/src/interface/tool-render.ts +34 -2
- package/src/interface/tool-workflow-script.ts +4 -4
- package/src/interface/tool-workflow.ts +6 -6
- package/src/interface/views/WorkflowsView.ts +5 -5
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
- package/src/interface/views/detail-content.ts +4 -4
- package/src/interface/views/format.ts +4 -4
- package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +11 -11
- package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
- package/src/execution/__tests__/agent-registry.test.ts +0 -363
- package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
- package/src/execution/__tests__/alive-store.test.ts +0 -147
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
- package/src/execution/__tests__/bg-notify-render.test.ts +0 -329
- package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
- package/src/execution/__tests__/chat-engine-routing.test.ts +0 -601
- package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
- package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
- package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
- package/src/execution/__tests__/config.test.ts +0 -110
- package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
- package/src/execution/__tests__/crash-recovery.test.ts +0 -320
- package/src/execution/__tests__/delivery-methods.test.ts +0 -422
- package/src/execution/__tests__/dialog-queue.test.ts +0 -299
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
- package/src/execution/__tests__/execute-nesting.test.ts +0 -323
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/__tests__/execution-record.test.ts +0 -1394
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +0 -171
- package/src/execution/__tests__/finalize-record.test.ts +0 -368
- package/src/execution/__tests__/finalized-marker.test.ts +0 -82
- package/src/execution/__tests__/format-schema-instruction.test.ts +0 -169
- package/src/execution/__tests__/format.test.ts +0 -458
- package/src/execution/__tests__/gc-timer.test.ts +0 -184
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
- package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
- package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
- package/src/execution/__tests__/helpers/spawn-mock.ts +0 -242
- package/src/execution/__tests__/host-mode.test.ts +0 -87
- package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
- package/src/execution/__tests__/index-session-start.test.ts +0 -672
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
- package/src/execution/__tests__/lifecycle-manager.test.ts +0 -383
- package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
- package/src/execution/__tests__/list-component.test.ts +0 -401
- package/src/execution/__tests__/list-fields.test.ts +0 -140
- package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
- package/src/execution/__tests__/model-resolver.test.ts +0 -465
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
- package/src/execution/__tests__/nested-visibility.test.ts +0 -325
- package/src/execution/__tests__/notifier-flush.test.ts +0 -612
- package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
- package/src/execution/__tests__/notify-ledger.test.ts +0 -826
- package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
- package/src/execution/__tests__/output-collector.test.ts +0 -358
- package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
- package/src/execution/__tests__/path-encoding.test.ts +0 -104
- package/src/execution/__tests__/pi-invocation.test.ts +0 -134
- package/src/execution/__tests__/record-store.test.ts +0 -1057
- package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
- package/src/execution/__tests__/relay-agent.test.ts +0 -448
- package/src/execution/__tests__/relay-env.test.ts +0 -42
- package/src/execution/__tests__/resource-policy.test.ts +0 -109
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -687
- 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 -199
- package/src/execution/__tests__/sdk-contract.test.ts +0 -273
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
- package/src/execution/__tests__/session-file-gc.test.ts +0 -293
- package/src/execution/__tests__/session-pending.test.ts +0 -197
- package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -350
- package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
- package/src/execution/__tests__/spawn-args.test.ts +0 -445
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
- package/src/execution/__tests__/spawned-children.test.ts +0 -92
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
- package/src/execution/__tests__/status-refactor.test.ts +0 -345
- package/src/execution/__tests__/stdin-writer.test.ts +0 -463
- package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
- package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
- package/src/execution/__tests__/subagent-service.test.ts +0 -788
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -599
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -615
- package/src/execution/__tests__/tombstone-store.test.ts +0 -73
- package/src/execution/__tests__/tool-action.test.ts +0 -476
- package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
- 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-channels.test.ts +0 -187
- package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
- package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
- package/src/execution/__tests__/worktree-manager.test.ts +0 -633
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
- package/src/execution/__tests__/worktree-registry.test.ts +0 -199
- package/src/execution/agent-registry.ts +0 -199
- package/src/execution/agent-result-mapper.ts +0 -90
- package/src/execution/alive-store.ts +0 -92
- package/src/execution/argv-mirror.ts +0 -113
- package/src/execution/best-effort.ts +0 -38
- package/src/execution/channel-registry-access.ts +0 -144
- package/src/execution/concurrency-pool.ts +0 -116
- package/src/execution/config.ts +0 -96
- package/src/execution/dialog-queue.ts +0 -330
- package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
- package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
- package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
- package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
- package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
- package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
- package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
- package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/__tests__/registry.test.ts +0 -120
- package/src/execution/engine/__tests__/routing.test.ts +0 -231
- package/src/execution/engine/common/data-dir.ts +0 -62
- package/src/execution/engine/common/errors.ts +0 -183
- package/src/execution/engine/common/event-journal.ts +0 -254
- package/src/execution/engine/common/journal-replay.ts +0 -62
- package/src/execution/engine/common/kill-chain.ts +0 -221
- package/src/execution/engine/common/nesting-guard.ts +0 -50
- package/src/execution/engine/common/persona-router.ts +0 -108
- package/src/execution/engine/common/pool-manager.ts +0 -226
- 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/engine-discovery.ts +0 -65
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
- package/src/execution/engine/engines/pi/reader.ts +0 -48
- package/src/execution/engine/engines/pi/registration.ts +0 -35
- 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__/parser.test.ts +0 -246
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
- package/src/execution/engine/engines/zcode/constants.ts +0 -43
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/engine/engines/zcode/parser.ts +0 -436
- package/src/execution/engine/engines/zcode/preparer.ts +0 -363
- 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/zcode-engine.ts +0 -658
- package/src/execution/engine/host-task-spec.ts +0 -47
- package/src/execution/engine/model-prompt.ts +0 -59
- package/src/execution/engine/paths.ts +0 -42
- package/src/execution/engine/port.ts +0 -153
- package/src/execution/engine/registry.ts +0 -123
- package/src/execution/engine/routing.ts +0 -218
- package/src/execution/engine/types.ts +0 -309
- package/src/execution/execute-options-mapper.ts +0 -112
- package/src/execution/execution-record.ts +0 -965
- package/src/execution/finalize-record.ts +0 -251
- package/src/execution/finalized-marker.ts +0 -51
- package/src/execution/get-state-handshake.ts +0 -104
- package/src/execution/host-mode.ts +0 -56
- package/src/execution/idle-gc.ts +0 -47
- package/src/execution/lifecycle-manager.ts +0 -513
- package/src/execution/lifecycle-predicates.ts +0 -65
- package/src/execution/manifest-store.ts +0 -256
- package/src/execution/model-config-service.ts +0 -219
- package/src/execution/model-resolver.ts +0 -248
- package/src/execution/notifier.ts +0 -330
- package/src/execution/notify-ledger.ts +0 -580
- package/src/execution/output-collector.ts +0 -228
- package/src/execution/path-encoding.ts +0 -60
- package/src/execution/pi-invocation.ts +0 -120
- package/src/execution/record-entry.ts +0 -132
- package/src/execution/record-store.ts +0 -1234
- package/src/execution/relay-env.ts +0 -37
- package/src/execution/session-context-resolver.ts +0 -64
- package/src/execution/session-file-gc.ts +0 -120
- package/src/execution/session-pending.ts +0 -164
- package/src/execution/session-reconstructor.ts +0 -678
- package/src/execution/session-runner.ts +0 -1781
- package/src/execution/sessions-index.ts +0 -304
- package/src/execution/spawn-event-adapter.ts +0 -363
- package/src/execution/stdin-writer.ts +0 -198
- package/src/execution/stream-sink.ts +0 -126
- package/src/execution/subagent-service.ts +0 -2141
- package/src/execution/subprocess-agent-runner.ts +0 -317
- package/src/execution/temp-prompt.ts +0 -62
- package/src/execution/tombstone-store.ts +0 -72
- package/src/execution/turn-limiter.ts +0 -102
- package/src/execution/types.ts +0 -973
- package/src/execution/ui-channels.ts +0 -216
- package/src/execution/ui-interaction-model.ts +0 -48
- package/src/execution/ui-request-handler-factory.ts +0 -212
- package/src/execution/ui-request-observability.ts +0 -81
- package/src/execution/ui-request-queue.ts +0 -184
- package/src/execution/worktree-manager.ts +0 -688
- package/src/execution/worktree-registry.ts +0 -222
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -349
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -202
- package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -152
- package/src/orchestration/__tests__/args-validator.test.ts +0 -143
- package/src/orchestration/__tests__/config-loader.test.ts +0 -503
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -809
- package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
- package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
- package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -451
- package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +0 -150
- package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
- package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +0 -202
- package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -600
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
- package/src/orchestration/__tests__/lifecycle.test.ts +0 -659
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +0 -95
- package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -2025
- package/src/orchestration/__tests__/script-lint.test.ts +0 -831
- package/src/orchestration/__tests__/skill-discovery.test.ts +0 -201
- package/src/orchestration/__tests__/test-mocks.ts +0 -197
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +0 -368
- package/src/orchestration/__tests__/worker-host.test.ts +0 -120
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -563
- package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -129
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
- package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
- package/src/orchestration/agent-opts-resolver.ts +0 -174
- package/src/orchestration/args-validator.ts +0 -127
- package/src/orchestration/config-loader.ts +0 -315
- package/src/orchestration/error-recovery.ts +0 -1018
- package/src/orchestration/execute-agent-call.ts +0 -256
- package/src/orchestration/launcher.ts +0 -455
- package/src/orchestration/lifecycle.ts +0 -399
- package/src/orchestration/models/__tests__/budget.test.ts +0 -307
- package/src/orchestration/models/__tests__/trace.test.ts +0 -408
- package/src/orchestration/models/agent-call.ts +0 -83
- package/src/orchestration/models/budget.ts +0 -118
- package/src/orchestration/models/ports.ts +0 -164
- package/src/orchestration/models/run-runtime.ts +0 -104
- package/src/orchestration/models/run-spec.ts +0 -83
- package/src/orchestration/models/run-state.ts +0 -44
- package/src/orchestration/models/trace.ts +0 -183
- package/src/orchestration/models/types.ts +0 -301
- package/src/orchestration/models/workflow-run.ts +0 -254
- package/src/orchestration/models/workflow-script-registry.ts +0 -35
- package/src/orchestration/models/workflow-script.ts +0 -117
- package/src/orchestration/script-lint.ts +0 -766
- package/src/orchestration/skill-discovery.ts +0 -115
- package/src/orchestration/worker-handle.ts +0 -115
- package/src/orchestration/worker-host.ts +0 -98
- package/src/orchestration/worker-script-builder.ts +0 -421
- package/src/orchestration/workflow-files.ts +0 -85
- package/src/orchestration/workflow-script-registry-impl.ts +0 -133
- package/src/shared/__tests__/agent-ref.test.ts +0 -34
- package/src/shared/__tests__/meta-parser.test.ts +0 -304
- package/src/shared/__tests__/model-ref.test.ts +0 -306
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
- package/src/shared/__tests__/resource-discovery.test.ts +0 -736
- package/src/shared/__tests__/resource-meta.test.ts +0 -51
- package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
- package/src/shared/__tests__/timer-delay.test.ts +0 -61
- package/src/shared/agent-event.ts +0 -13
- package/src/shared/agent-ref.ts +0 -57
- package/src/shared/meta-parser.ts +0 -261
- package/src/shared/model-ref.ts +0 -286
- package/src/shared/resource-discovery.ts +0 -811
- package/src/shared/resource-meta.ts +0 -65
- package/src/shared/schema-env.ts +0 -44
- package/src/shared/schema-jsonify.ts +0 -58
- package/src/shared/timer-delay.ts +0 -54
- package/src/shared/xml-injection.ts +0 -35
- package/workflows/README.md +0 -81
- package/workflows/_shared/agent-refs.cjs +0 -40
- package/workflows/chain.js +0 -131
- package/workflows/map-reduce.js +0 -174
- package/workflows/parallel.js +0 -148
- package/workflows/review-fix-loop-utils.cjs +0 -1309
- package/workflows/review-fix-loop.js +0 -1404
- package/workflows/scatter-gather.js +0 -178
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// engine-awareness 单测([engine-awareness U3])
|
|
2
|
+
//
|
|
3
|
+
// 覆盖(设计 docs/design/subagent-engine-awareness-injection.md 验收挂钩 D1/D1b/D2/D3/D5):
|
|
4
|
+
// 1. normalizeEngineId:缺省/空白归一到 'pi'(单一权威源 registry.ts,经本模块再导出)
|
|
5
|
+
// 2. buildEngineChangeNotice:§3.1 文案骨架、pi/非 pi 指路段分界、不含任何模型清单(D4)
|
|
6
|
+
// 3. runEngineAwarenessTurn 编排:
|
|
7
|
+
// - 变更触发 apply + 通知(D2 顺序硬约束:提交缓存先于通知先于记账)
|
|
8
|
+
// - applyRead 收到的参数与 readConfig 返回值引用相等(构造性同源,消灭双读分叉)
|
|
9
|
+
// - 无变更无事
|
|
10
|
+
// - 读失败保持 lastEngine 不动、不发通知(D5 态 3,防 torn write 伪通知)
|
|
11
|
+
// - ENOENT(absent)= 合法缺省 pi,正常触发变更(D5 态 2)
|
|
12
|
+
// - 首 turn lastEngine === undefined 静默基线化,无伪通知(D1b)
|
|
13
|
+
// - 通知消息形态(customType / display / details,D3+D8 对齐 notifier 约定)
|
|
14
|
+
// - 多 session 各自独立 lastEngine(per-session diff 基准互不干扰)
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from "vitest";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
buildEngineChangeNotice,
|
|
20
|
+
ENGINE_CHANGE_CUSTOM_TYPE,
|
|
21
|
+
normalizeEngineId,
|
|
22
|
+
runEngineAwarenessTurn,
|
|
23
|
+
type EngineAwarenessDeps,
|
|
24
|
+
type EngineAwarenessOutcome,
|
|
25
|
+
} from "../engine-awareness";
|
|
26
|
+
import type { GlobalConfigReadResult } from "@zhushanwen/subagent-core/execution/config.ts";
|
|
27
|
+
|
|
28
|
+
// ── 测试数据 ────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/** ok 态读取结果工厂(config 形状与 sanitize 输出一致)。 */
|
|
31
|
+
function okRead(defaultEngine: string | undefined): GlobalConfigReadResult {
|
|
32
|
+
return {
|
|
33
|
+
status: "ok",
|
|
34
|
+
config: { version: 1, maxConcurrent: 6, ...(defaultEngine !== undefined ? { defaultEngine } : {}) },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 调用序记录 + 发送捕获的 deps 工厂。 */
|
|
39
|
+
function makeDeps(overrides: Partial<EngineAwarenessDeps> = {}) {
|
|
40
|
+
const calls: string[] = [];
|
|
41
|
+
const sent: Array<{ customType: string; content: string; display: boolean; details?: unknown }> = [];
|
|
42
|
+
const applied: GlobalConfigReadResult[] = [];
|
|
43
|
+
let lastEngine: string | undefined;
|
|
44
|
+
const deps: EngineAwarenessDeps = {
|
|
45
|
+
readConfig: () => okRead(undefined),
|
|
46
|
+
applyRead: (read) => {
|
|
47
|
+
calls.push("apply");
|
|
48
|
+
applied.push(read);
|
|
49
|
+
},
|
|
50
|
+
sendMessage: (message) => {
|
|
51
|
+
calls.push("send");
|
|
52
|
+
sent.push(message);
|
|
53
|
+
},
|
|
54
|
+
getLastEngine: () => lastEngine,
|
|
55
|
+
setLastEngine: (engine) => {
|
|
56
|
+
calls.push(`set:${engine}`);
|
|
57
|
+
lastEngine = engine;
|
|
58
|
+
},
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
return { deps, calls, sent, applied, getLast: () => lastEngine };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── normalizeEngineId ──────────────────────────────────
|
|
65
|
+
|
|
66
|
+
describe("normalizeEngineId", () => {
|
|
67
|
+
it("undefined 缺省归一到 'pi'", () => {
|
|
68
|
+
expect(normalizeEngineId(undefined)).toBe("pi");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("空白字符串归一到 'pi'(sanitize 会拦非字符串,但防御空白透传)", () => {
|
|
72
|
+
expect(normalizeEngineId(" ")).toBe("pi");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("非 pi 引擎透传", () => {
|
|
76
|
+
expect(normalizeEngineId("zcode")).toBe("zcode");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ── buildEngineChangeNotice(D4 不含模型清单)────────────
|
|
81
|
+
|
|
82
|
+
describe("buildEngineChangeNotice", () => {
|
|
83
|
+
it("pi 目标:§3.1 文案骨架(zcode → pi),指向 <available_provider_models>", () => {
|
|
84
|
+
const content = buildEngineChangeNotice("zcode", "pi");
|
|
85
|
+
expect(content).toBe(
|
|
86
|
+
[
|
|
87
|
+
"Subagent default engine changed: zcode → pi (effective this turn).",
|
|
88
|
+
"Use pi-registry ids from <available_provider_models> for explicit models;",
|
|
89
|
+
"omit `model` to inherit. The <current_subagent_engine> section reflects the current state.",
|
|
90
|
+
].join("\n"),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("非 pi 目标:指向该引擎清单段 <available_<engine>_models>", () => {
|
|
95
|
+
const content = buildEngineChangeNotice("pi", "zcode");
|
|
96
|
+
expect(content).toContain("Subagent default engine changed: pi → zcode (effective this turn).");
|
|
97
|
+
expect(content).toContain("<available_zcode_models>");
|
|
98
|
+
expect(content).not.toContain("<available_provider_models>");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("不含任何模型清单(无 <model> 标签 / 无 provider id 形态,D4)", () => {
|
|
102
|
+
for (const to of ["pi", "zcode"]) {
|
|
103
|
+
const content = buildEngineChangeNotice("zcode", to);
|
|
104
|
+
expect(content).not.toContain("<model>");
|
|
105
|
+
// provider id 形态:zai-coding-cn/glm-5.3、builtin:bigmodel-coding-plan/... 等
|
|
106
|
+
expect(content).not.toMatch(/[a-z][\w-]*\/[\w.-]+/i);
|
|
107
|
+
expect(content).not.toContain("builtin:");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ── runEngineAwarenessTurn:变更编排 ─────────────────────
|
|
113
|
+
|
|
114
|
+
describe("runEngineAwarenessTurn", () => {
|
|
115
|
+
it("变更触发 apply + 通知 + 记账,顺序硬约束 apply → send → set(D2)", () => {
|
|
116
|
+
const read = okRead("pi");
|
|
117
|
+
const { deps, calls, sent, applied } = makeDeps({
|
|
118
|
+
readConfig: () => read,
|
|
119
|
+
getLastEngine: () => "zcode",
|
|
120
|
+
});
|
|
121
|
+
const result = runEngineAwarenessTurn(deps);
|
|
122
|
+
expect(result).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
|
|
123
|
+
expect(calls).toEqual(["apply", "send", "set:pi"]);
|
|
124
|
+
// 构造性同源:提交到路由缓存的就是本次读取返回的同一对象(非重读、非拷贝)
|
|
125
|
+
expect(applied[0]).toBe(read);
|
|
126
|
+
expect(sent).toHaveLength(1);
|
|
127
|
+
expect(sent[0].customType).toBe(ENGINE_CHANGE_CUSTOM_TYPE);
|
|
128
|
+
expect(sent[0].content).toContain("zcode → pi");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("通知消息形态对齐 notifier 约定(display:true + details 携带 from/to,D3/D8)", () => {
|
|
132
|
+
const { deps, sent } = makeDeps({
|
|
133
|
+
readConfig: () => okRead("zcode"),
|
|
134
|
+
getLastEngine: () => "pi",
|
|
135
|
+
});
|
|
136
|
+
runEngineAwarenessTurn(deps);
|
|
137
|
+
expect(sent[0].display).toBe(true);
|
|
138
|
+
expect(sent[0].details).toEqual({ from: "pi", to: "zcode" });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("无变更无事:零 apply 零通知零写入", () => {
|
|
142
|
+
const { deps, calls, sent } = makeDeps({
|
|
143
|
+
readConfig: () => okRead("zcode"),
|
|
144
|
+
getLastEngine: () => "zcode",
|
|
145
|
+
});
|
|
146
|
+
const result: EngineAwarenessOutcome = runEngineAwarenessTurn(deps);
|
|
147
|
+
expect(result).toEqual({ outcome: "unchanged", engine: "zcode" });
|
|
148
|
+
expect(calls).toEqual([]);
|
|
149
|
+
expect(sent).toHaveLength(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("读失败保持 lastEngine 不动、不 apply 不通知(D5 态 3)", () => {
|
|
153
|
+
let lastEngine: string | undefined = "zcode";
|
|
154
|
+
const { deps, calls, sent } = makeDeps({
|
|
155
|
+
readConfig: () => ({ status: "failed", reason: "Unexpected token in JSON" }),
|
|
156
|
+
getLastEngine: () => lastEngine,
|
|
157
|
+
setLastEngine: (engine) => {
|
|
158
|
+
lastEngine = engine;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const result = runEngineAwarenessTurn(deps);
|
|
162
|
+
expect(result).toEqual({ outcome: "read-failed", reason: "Unexpected token in JSON" });
|
|
163
|
+
expect(calls).toEqual([]);
|
|
164
|
+
expect(sent).toHaveLength(0);
|
|
165
|
+
expect(lastEngine).toBe("zcode");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("ENOENT(absent)= 合法缺省 pi:正常触发变更(D5 态 2)", () => {
|
|
169
|
+
const read: GlobalConfigReadResult = { status: "absent", config: { version: 1, maxConcurrent: 6 } };
|
|
170
|
+
const { deps, calls, sent, applied } = makeDeps({
|
|
171
|
+
readConfig: () => read,
|
|
172
|
+
getLastEngine: () => "zcode",
|
|
173
|
+
});
|
|
174
|
+
const result = runEngineAwarenessTurn(deps);
|
|
175
|
+
expect(result).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
|
|
176
|
+
expect(calls).toEqual(["apply", "send", "set:pi"]);
|
|
177
|
+
// absent 态同样构造性同源(删配置切回缺省也走同一次读取)
|
|
178
|
+
expect(applied[0]).toBe(read);
|
|
179
|
+
expect(sent[0].content).toContain("zcode → pi");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("缺省 config(defaultEngine 字段缺失)diff 基准归一为 pi:zcode → 无字段 视为变更到 pi", () => {
|
|
183
|
+
const { deps, sent } = makeDeps({
|
|
184
|
+
readConfig: () => okRead(undefined),
|
|
185
|
+
getLastEngine: () => "zcode",
|
|
186
|
+
});
|
|
187
|
+
const result = runEngineAwarenessTurn(deps);
|
|
188
|
+
expect(result).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
|
|
189
|
+
expect(sent[0].content).toContain("zcode → pi");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ── runEngineAwarenessTurn:D1b 基线化 ───────────────────
|
|
194
|
+
|
|
195
|
+
describe("runEngineAwarenessTurn lastEngine 基线化(D1b)", () => {
|
|
196
|
+
it("首 turn lastEngine === undefined:静默基线化为当前值,无伪通知,但 apply 先行(D1b 修订)", () => {
|
|
197
|
+
const read = okRead("zcode");
|
|
198
|
+
const { deps, calls, sent, applied, getLast } = makeDeps({
|
|
199
|
+
readConfig: () => read,
|
|
200
|
+
// session_start 初始化读失败 → lastEngine 未设置(undefined)
|
|
201
|
+
});
|
|
202
|
+
const result = runEngineAwarenessTurn(deps);
|
|
203
|
+
expect(result).toEqual({ outcome: "baseline", engine: "zcode" });
|
|
204
|
+
// D1b 修订:基线分支必须先提交读取结果把 Service 缓存对齐到刚读到的现值——
|
|
205
|
+
// 否则「session_start 读失败缓存回落 + 文件此后被修好」形态下,
|
|
206
|
+
// 缓存/路由/状态段永停旧值且永不通知
|
|
207
|
+
expect(calls).toEqual(["apply", "set:zcode"]);
|
|
208
|
+
// 构造性同源:基线分支提交的也是本次读取的同一对象
|
|
209
|
+
expect(applied[0]).toBe(read);
|
|
210
|
+
expect(sent).toHaveLength(0);
|
|
211
|
+
expect(getLast()).toBe("zcode");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("基线化后再 turn 读到同值 → unchanged(基线生效,且 unchanged 仍零调用)", () => {
|
|
215
|
+
let lastEngine: string | undefined;
|
|
216
|
+
const { deps, calls, sent } = makeDeps({
|
|
217
|
+
readConfig: () => okRead("zcode"),
|
|
218
|
+
getLastEngine: () => lastEngine,
|
|
219
|
+
setLastEngine: (engine) => {
|
|
220
|
+
lastEngine = engine;
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
expect(runEngineAwarenessTurn(deps)).toEqual({ outcome: "baseline", engine: "zcode" });
|
|
224
|
+
expect(runEngineAwarenessTurn(deps)).toEqual({ outcome: "unchanged", engine: "zcode" });
|
|
225
|
+
// setLastEngine 被本用例 override(写局部变量、不记录 calls),故 calls 仅剩
|
|
226
|
+
// baseline 分支的 apply;unchanged 分支零调用
|
|
227
|
+
expect(calls).toEqual(["apply"]);
|
|
228
|
+
expect(sent).toHaveLength(0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("lastEngine === undefined 且读失败:连基线都不做,不写入不通知", () => {
|
|
232
|
+
const { deps, calls, sent, getLast } = makeDeps({
|
|
233
|
+
readConfig: () => ({ status: "failed", reason: "EACCES" }),
|
|
234
|
+
});
|
|
235
|
+
const result = runEngineAwarenessTurn(deps);
|
|
236
|
+
expect(result).toEqual({ outcome: "read-failed", reason: "EACCES" });
|
|
237
|
+
expect(calls).toEqual([]);
|
|
238
|
+
expect(sent).toHaveLength(0);
|
|
239
|
+
expect(getLast()).toBeUndefined();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ── 多 session 独立性 ──────────────────────────────────
|
|
244
|
+
|
|
245
|
+
describe("runEngineAwarenessTurn 多 session 独立 lastEngine", () => {
|
|
246
|
+
/**
|
|
247
|
+
* 模拟 index.ts 装配形态:共享 per-session 状态 Map(sessionState),
|
|
248
|
+
* 每个 sid 一组 deps(getLastEngine/setLastEngine 绑定各自 sid)。
|
|
249
|
+
*/
|
|
250
|
+
function makeMultiSessionState() {
|
|
251
|
+
const lastEngines = new Map<string, string | undefined>();
|
|
252
|
+
const notices: Array<{ sid: string; content: string }> = [];
|
|
253
|
+
function depsFor(sid: string, read: () => GlobalConfigReadResult): EngineAwarenessDeps {
|
|
254
|
+
return {
|
|
255
|
+
readConfig: read,
|
|
256
|
+
applyRead: () => {},
|
|
257
|
+
sendMessage: (message) => {
|
|
258
|
+
notices.push({ sid, content: message.content });
|
|
259
|
+
},
|
|
260
|
+
getLastEngine: () => lastEngines.get(sid),
|
|
261
|
+
setLastEngine: (engine) => {
|
|
262
|
+
lastEngines.set(sid, engine);
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return { lastEngines, notices, depsFor };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
it("session1 检测变更不污染 session2 的基线(各自独立边沿)", () => {
|
|
270
|
+
const { lastEngines, notices, depsFor } = makeMultiSessionState();
|
|
271
|
+
const readZcode = (): GlobalConfigReadResult => okRead("zcode");
|
|
272
|
+
const readPi = (): GlobalConfigReadResult => okRead("pi");
|
|
273
|
+
|
|
274
|
+
// 各自首 turn 基线化
|
|
275
|
+
const sid1 = depsFor("s1", readZcode);
|
|
276
|
+
const sid2 = depsFor("s2", readPi);
|
|
277
|
+
expect(runEngineAwarenessTurn(sid1)).toEqual({ outcome: "baseline", engine: "zcode" });
|
|
278
|
+
expect(runEngineAwarenessTurn(sid2)).toEqual({ outcome: "baseline", engine: "pi" });
|
|
279
|
+
|
|
280
|
+
// session1 的引擎切换(zcode → pi):session1 收通知,session2 无感知
|
|
281
|
+
const sid1After = depsFor("s1", readPi);
|
|
282
|
+
expect(runEngineAwarenessTurn(sid1After)).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
|
|
283
|
+
// session2 再 turn:仍 unchanged,零通知
|
|
284
|
+
const sid2After = depsFor("s2", readPi);
|
|
285
|
+
expect(runEngineAwarenessTurn(sid2After)).toEqual({ outcome: "unchanged", engine: "pi" });
|
|
286
|
+
|
|
287
|
+
expect(notices).toHaveLength(1);
|
|
288
|
+
expect(notices[0].sid).toBe("s1");
|
|
289
|
+
expect(lastEngines.get("s1")).toBe("pi");
|
|
290
|
+
expect(lastEngines.get("s2")).toBe("pi");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// engine-section-stability.test.ts —— [engine-awareness U4 stability-guard]
|
|
2
|
+
//
|
|
3
|
+
// 字节稳定守护(设计 docs/design/subagent-engine-awareness-injection.md §3.3 D7 /
|
|
4
|
+
// §4 A8 的代码级前置):engine 段延续注入纪律——确定性渲染、链尾位置,引擎切换只断
|
|
5
|
+
// system prompt 尾部 cache 前缀(与 provider models 段变更同判,不新增 cache 破坏面,
|
|
6
|
+
// cache-probe 前缀指纹归因兼容)。
|
|
7
|
+
//
|
|
8
|
+
// 三层覆盖:
|
|
9
|
+
// 1. 段渲染确定性:pi / zcode / ghost 三形态多次独立调用逐字节相等(toBe 级);
|
|
10
|
+
// 2. 引擎切换只变尾部:provider models 段前缀不变,变化只发生在 engine 段位置(尾部);
|
|
11
|
+
// 3. 段序守护:engine 段恒位于 provider models 段之后(恒链尾)。双保险实现:
|
|
12
|
+
// a) handler 链模拟——真实导入 setupModelListInjector + 复刻 index.ts engine
|
|
13
|
+
// handler 的渲染拼装(检测编排部分归 engine-awareness.test.ts),模拟 pi 的
|
|
14
|
+
// before_agent_start 链式叠加语义;
|
|
15
|
+
// b) index.ts 源码级结构断言——锚定注册序与拼装形态。
|
|
16
|
+
//
|
|
17
|
+
// 源码级断言的脆弱性权衡(刻意为之):engine handler 是 index.ts 内联闭包(无法
|
|
18
|
+
// 导入),而 before_agent_start 多 handler 的段序由注册序唯一决定,「注册序」这一
|
|
19
|
+
// 事实只存在于 index.ts 源码中——源码结构断言是唯一能锚定它的方式。重构挪动注册
|
|
20
|
+
// 位置导致本断言失败属预期行为(提醒同步更新链模拟与守护锚点),不是误报。
|
|
21
|
+
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
26
|
+
|
|
27
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
28
|
+
|
|
29
|
+
import { formatModelList, setupModelListInjector, type ModelEntry } from "../model-list-injector.ts";
|
|
30
|
+
import type { EnginePort } from "@zhushanwen/subagent-core/execution/engine/port.ts";
|
|
31
|
+
import { clearEngines, registerEngine } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
|
|
32
|
+
import {
|
|
33
|
+
buildEngineModelsPromptAppend,
|
|
34
|
+
buildSubagentEngineSection,
|
|
35
|
+
} from "@zhushanwen/subagent-core/execution/engine/model-prompt.ts";
|
|
36
|
+
|
|
37
|
+
// ── 测试数据 ────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** pi registry 风格条目(provider models 段数据源;同时充当 ctx.modelRegistry 快照)。 */
|
|
40
|
+
const PROVIDER_ENTRIES: ModelEntry[] = [
|
|
41
|
+
{
|
|
42
|
+
provider: "zai-coding-cn",
|
|
43
|
+
id: "glm-5.3",
|
|
44
|
+
name: "GLM 5.3",
|
|
45
|
+
reasoning: true,
|
|
46
|
+
input: ["text"],
|
|
47
|
+
contextWindow: 200_000,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
provider: "minimax-cn",
|
|
51
|
+
id: "MiniMax-M3",
|
|
52
|
+
name: "MiniMax M3",
|
|
53
|
+
reasoning: true,
|
|
54
|
+
input: ["text", "image"],
|
|
55
|
+
contextWindow: 1_000_000,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/** zcode 引擎清单(v2 registry 风格 id)。 */
|
|
60
|
+
const ZCODE_MODELS: Array<{ id: string; name?: string }> = [
|
|
61
|
+
{ id: "builtin:bigmodel-coding-plan/GLM-5.3", name: "GLM-5.3" },
|
|
62
|
+
{ id: "builtin:bigmodel-coding-plan/GLM-5.3-Flash" },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/** 模拟 pi 核心 system prompt(engine 追加段之外的全部内容)。 */
|
|
66
|
+
const BASE = "You are a coding agent.\n\n# System\nCore prompt body.";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* engine handler 的追加段拼装(与 index.ts engine handler 同款:状态段 + 清单段、
|
|
70
|
+
* 空段剔除、\n\n 连接)。复刻保真由下方「段序守护」describe 的源码锚点断言保证。
|
|
71
|
+
*/
|
|
72
|
+
function composeEngineAppend(defaultEngine: string | undefined): string {
|
|
73
|
+
return [buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]
|
|
74
|
+
.filter((part) => part !== "")
|
|
75
|
+
.join("\n\n");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** index.ts 尾部追加模板的分隔符(`${event.systemPrompt}\n\n${append}`)。 */
|
|
79
|
+
const APPEND_SEPARATOR = "\n\n";
|
|
80
|
+
|
|
81
|
+
/** 合成一个 turn 的 system prompt 尾部:BASE + provider models 段 + engine 追加段。 */
|
|
82
|
+
function composeTurn(defaultEngine: string | undefined): string {
|
|
83
|
+
const providerSection = formatModelList(PROVIDER_ENTRIES);
|
|
84
|
+
const afterProvider = providerSection === "" ? BASE : BASE + providerSection;
|
|
85
|
+
const append = composeEngineAppend(defaultEngine);
|
|
86
|
+
return append === "" ? afterProvider : `${afterProvider}${APPEND_SEPARATOR}${append}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 最大公共前缀长度(逐码元比较;用于「分叉点不早于稳定头部」断言)。 */
|
|
90
|
+
function commonPrefixLength(a: string, b: string): number {
|
|
91
|
+
const n = Math.min(a.length, b.length);
|
|
92
|
+
let i = 0;
|
|
93
|
+
while (i < n && a[i] === b[i]) i++;
|
|
94
|
+
return i;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 最小 fake 引擎(照 model-prompt.test.ts 同款形态):仅 listModels 参与注入链。 */
|
|
98
|
+
function fakeEngine(id: string, models: Array<{ id: string; name?: string }>): EnginePort {
|
|
99
|
+
return {
|
|
100
|
+
id,
|
|
101
|
+
capabilities: () => ({ conversation: "unsupported", steer: "unsupported", sandbox: "none" }),
|
|
102
|
+
probe: async () => ({ ok: true, engineVersion: "test" }),
|
|
103
|
+
run: async () => {
|
|
104
|
+
throw new Error("not used in this test");
|
|
105
|
+
},
|
|
106
|
+
interact: async () => {
|
|
107
|
+
throw new Error("not used in this test");
|
|
108
|
+
},
|
|
109
|
+
read: async () => ({ engineId: id, turns: [], source: "outcome-only" }),
|
|
110
|
+
// 每次调用返回新数组实例(模拟 registry 每 turn 现值)——渲染必须与实例无关
|
|
111
|
+
listModels: () => models.map((m) => ({ ...m })),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
clearEngines();
|
|
117
|
+
registerEngine("zcode", () => fakeEngine("zcode", ZCODE_MODELS));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
afterEach(() => {
|
|
121
|
+
clearEngines();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ── 1. 段渲染确定性(D7)────────────────────────────────
|
|
125
|
+
|
|
126
|
+
describe("段渲染确定性(D7:同输入恒同输出)", () => {
|
|
127
|
+
it("pi 形态:状态段多次独立调用逐字节相等;清单段恒空串", () => {
|
|
128
|
+
// pi 形态不依赖注册表(直接走缺省分支),registry 中有 zcode 不影响
|
|
129
|
+
const first = buildSubagentEngineSection("pi");
|
|
130
|
+
const second = buildSubagentEngineSection("pi");
|
|
131
|
+
const third = buildSubagentEngineSection("pi");
|
|
132
|
+
expect(first).toBe(second);
|
|
133
|
+
expect(second).toBe(third);
|
|
134
|
+
// 非空自检:确认 toBe 断言真的在比对段内容而非空串
|
|
135
|
+
expect(first).toContain("<current_subagent_engine>");
|
|
136
|
+
expect(buildEngineModelsPromptAppend("pi")).toBe("");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("zcode 形态:状态段 + 清单段多次独立调用逐字节相等(listModels 每次返回新数组实例)", () => {
|
|
140
|
+
const s1 = buildSubagentEngineSection("zcode");
|
|
141
|
+
const s2 = buildSubagentEngineSection("zcode");
|
|
142
|
+
const m1 = buildEngineModelsPromptAppend("zcode");
|
|
143
|
+
const m2 = buildEngineModelsPromptAppend("zcode");
|
|
144
|
+
expect(s1).toBe(s2);
|
|
145
|
+
expect(m1).toBe(m2);
|
|
146
|
+
expect(m1).toContain("<available_zcode_models>");
|
|
147
|
+
expect(m1).toContain("<id>builtin:bigmodel-coding-plan/GLM-5.3</id>");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("ghost 形态(未注册引擎):警告段多次独立调用逐字节相等", () => {
|
|
151
|
+
const first = buildSubagentEngineSection("ghost");
|
|
152
|
+
const second = buildSubagentEngineSection("ghost");
|
|
153
|
+
expect(first).toBe(second);
|
|
154
|
+
expect(first).toContain("engine 'ghost' is not registered");
|
|
155
|
+
expect(buildEngineModelsPromptAppend("ghost")).toBe("");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("形态间干扰不产生状态残留:zcode → pi → zcode,两次 zcode 输出逐字节相等", () => {
|
|
159
|
+
const before = buildSubagentEngineSection("zcode");
|
|
160
|
+
// 穿插渲染其他形态(含清单段消费 listModels),再回到 zcode
|
|
161
|
+
buildSubagentEngineSection("pi");
|
|
162
|
+
buildEngineModelsPromptAppend("zcode");
|
|
163
|
+
buildSubagentEngineSection("ghost");
|
|
164
|
+
expect(buildSubagentEngineSection("zcode")).toBe(before);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("注册表重建(clearEngines + 同 id 同清单重注册)后同形态输出不变", () => {
|
|
168
|
+
const firstSection = buildSubagentEngineSection("zcode");
|
|
169
|
+
const firstAppend = buildEngineModelsPromptAppend("zcode");
|
|
170
|
+
clearEngines();
|
|
171
|
+
registerEngine("zcode", () => fakeEngine("zcode", ZCODE_MODELS));
|
|
172
|
+
expect(buildSubagentEngineSection("zcode")).toBe(firstSection);
|
|
173
|
+
expect(buildEngineModelsPromptAppend("zcode")).toBe(firstAppend);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("组合拼装(index.ts 同款:状态段 + 清单段 join)同引擎多次调用逐字节相等", () => {
|
|
177
|
+
for (const engine of ["zcode", "pi", "ghost"]) {
|
|
178
|
+
expect(composeEngineAppend(engine)).toBe(composeEngineAppend(engine));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ── 2. 引擎切换只变尾部(A8 前置)───────────────────────
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 断言「从 from 切到 to 时变化只发生在尾部 engine 段」:
|
|
187
|
+
* - provider models 段及其之前的头部逐字节不变(公共前缀 toBe 级断言);
|
|
188
|
+
* - 剥离尾部 engine 追加段后两串余部逐字节相等且恰为稳定头部;
|
|
189
|
+
* - 分叉点不早于稳定头部末尾(差异绝不侵入 provider models 段);
|
|
190
|
+
* - 切换后的引擎追加段恰居 prompt 尾部。
|
|
191
|
+
*/
|
|
192
|
+
function expectOnlyTailDiffers(from: string | undefined, to: string | undefined): void {
|
|
193
|
+
const stableHead = BASE + formatModelList(PROVIDER_ENTRIES);
|
|
194
|
+
const before = composeTurn(from);
|
|
195
|
+
const after = composeTurn(to);
|
|
196
|
+
const beforeAppend = composeEngineAppend(from);
|
|
197
|
+
const afterAppend = composeEngineAppend(to);
|
|
198
|
+
|
|
199
|
+
expect(before).not.toBe(after);
|
|
200
|
+
expect(before.startsWith(stableHead)).toBe(true);
|
|
201
|
+
expect(after.startsWith(stableHead)).toBe(true);
|
|
202
|
+
expect(before.slice(0, before.length - beforeAppend.length - APPEND_SEPARATOR.length)).toBe(
|
|
203
|
+
stableHead,
|
|
204
|
+
);
|
|
205
|
+
expect(after.slice(0, after.length - afterAppend.length - APPEND_SEPARATOR.length)).toBe(
|
|
206
|
+
stableHead,
|
|
207
|
+
);
|
|
208
|
+
expect(commonPrefixLength(before, after)).toBeGreaterThanOrEqual(stableHead.length);
|
|
209
|
+
expect(after.endsWith(afterAppend)).toBe(true);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
describe("引擎切换只变尾部(A8 前置:变化只断尾部 cache 前缀)", () => {
|
|
213
|
+
it("zcode → pi:provider models 段前缀不变,变化只发生在 engine 段(尾部)", () => {
|
|
214
|
+
expectOnlyTailDiffers("zcode", "pi");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("pi → zcode(反向切换):同款只变尾部", () => {
|
|
218
|
+
expectOnlyTailDiffers("pi", "zcode");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("zcode → ghost(配置手误形态):G4 降级段同样只替换尾部,不破坏前缀", () => {
|
|
222
|
+
expectOnlyTailDiffers("zcode", "ghost");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ── 3. 段序守护:engine 段恒链尾 ────────────────────────
|
|
227
|
+
|
|
228
|
+
/** 链模拟用 handler 形态(参数收窄到运行时实际消费的字段)。 */
|
|
229
|
+
type ChainHandler = (event: { systemPrompt: string }, ctx: unknown) => unknown;
|
|
230
|
+
|
|
231
|
+
/** fake ExtensionAPI:捕获 pi.on 注册的 handler(保持注册序)。 */
|
|
232
|
+
function capturePi(): { handlers: ChainHandler[]; api: ExtensionAPI } {
|
|
233
|
+
const handlers: ChainHandler[] = [];
|
|
234
|
+
const api = {
|
|
235
|
+
on: (_event: string, handler: (...args: unknown[]) => unknown) => {
|
|
236
|
+
// on 的 mock 签名参数为 unknown[],收窄到两参形态(pi 调用约定恒两参)
|
|
237
|
+
handlers.push(((event: unknown, ctx: unknown) => handler(event, ctx)) as ChainHandler);
|
|
238
|
+
},
|
|
239
|
+
} as unknown as ExtensionAPI;
|
|
240
|
+
return { handlers, api };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 模拟 pi 的 before_agent_start 串联语义:按注册序执行,前序返回的 systemPrompt 作为后序输入。 */
|
|
244
|
+
async function runHandlerChain(handlers: ChainHandler[], initialPrompt: string): Promise<string> {
|
|
245
|
+
let prompt = initialPrompt;
|
|
246
|
+
const ctx = { modelRegistry: { getAvailable: () => PROVIDER_ENTRIES } };
|
|
247
|
+
for (const handler of handlers) {
|
|
248
|
+
const result: unknown = await handler({ systemPrompt: prompt }, ctx);
|
|
249
|
+
// 运行时收窄:handler 返回 { systemPrompt: string } 时替换链上 prompt,否则保持
|
|
250
|
+
if (typeof result === "object" && result !== null && "systemPrompt" in result) {
|
|
251
|
+
const sp: unknown = (result as { systemPrompt: unknown }).systemPrompt;
|
|
252
|
+
if (typeof sp === "string") prompt = sp;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return prompt;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
describe("段序守护:engine 段恒链尾(D7)", () => {
|
|
259
|
+
it("handler 链模拟:按 index.ts 注册序执行后,engine 段位于 provider models 段之后且居尾", async () => {
|
|
260
|
+
const { handlers, api } = capturePi();
|
|
261
|
+
// 真实导入 model list injector(provider models 段的生产渲染路径)
|
|
262
|
+
setupModelListInjector(api);
|
|
263
|
+
// 复刻 index.ts engine handler 的渲染拼装部分(检测编排部分归 engine-awareness.test.ts)
|
|
264
|
+
const defaultEngine = "zcode";
|
|
265
|
+
handlers.push((event) => {
|
|
266
|
+
const append = composeEngineAppend(defaultEngine);
|
|
267
|
+
return { systemPrompt: `${event.systemPrompt}${APPEND_SEPARATOR}${append}` };
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const prompt = await runHandlerChain(handlers, BASE);
|
|
271
|
+
|
|
272
|
+
const providerIdx = prompt.indexOf("<available_provider_models>");
|
|
273
|
+
const statusIdx = prompt.indexOf("<current_subagent_engine>");
|
|
274
|
+
const zcodeListIdx = prompt.indexOf("<available_zcode_models>");
|
|
275
|
+
expect(providerIdx).toBeGreaterThanOrEqual(0);
|
|
276
|
+
// 段序硬约束:engine 状态段与引擎清单段都在 provider models 段之后
|
|
277
|
+
expect(statusIdx).toBeGreaterThan(providerIdx);
|
|
278
|
+
expect(zcodeListIdx).toBeGreaterThan(providerIdx);
|
|
279
|
+
// 恒链尾:engine 追加段是 prompt 的最后内容
|
|
280
|
+
expect(prompt.endsWith(`${APPEND_SEPARATOR}${composeEngineAppend(defaultEngine)}`)).toBe(true);
|
|
281
|
+
// model list handler 注入形态锚定(BASE + injection 无分隔,生产 handler 同款)
|
|
282
|
+
expect(prompt.startsWith(BASE + formatModelList(PROVIDER_ENTRIES))).toBe(true);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("对照:注册序颠倒(engine 先、model list 后)时段序断言必失败——守护有判别力", async () => {
|
|
286
|
+
const { handlers, api } = capturePi();
|
|
287
|
+
const defaultEngine = "zcode";
|
|
288
|
+
handlers.push((event) => {
|
|
289
|
+
const append = composeEngineAppend(defaultEngine);
|
|
290
|
+
return { systemPrompt: `${event.systemPrompt}${APPEND_SEPARATOR}${append}` };
|
|
291
|
+
});
|
|
292
|
+
setupModelListInjector(api);
|
|
293
|
+
|
|
294
|
+
const prompt = await runHandlerChain(handlers, BASE);
|
|
295
|
+
|
|
296
|
+
// 颠倒后 engine 段跑到 provider models 段之前——证明上一条 it 的断言非平凡
|
|
297
|
+
const providerIdx = prompt.indexOf("<available_provider_models>");
|
|
298
|
+
const statusIdx = prompt.indexOf("<current_subagent_engine>");
|
|
299
|
+
expect(statusIdx).toBeGreaterThanOrEqual(0);
|
|
300
|
+
expect(statusIdx).toBeLessThan(providerIdx);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("index.ts 源码级守护:engine handler 注册序在 model list injector 之后(段序由注册序唯一决定)", () => {
|
|
304
|
+
const src = readFileSync(fileURLToPath(new URL("../../index.ts", import.meta.url)), "utf8");
|
|
305
|
+
const lineOf = (needle: string): number => {
|
|
306
|
+
const idx = src.indexOf(needle);
|
|
307
|
+
if (idx < 0) throw new Error(`index.ts 中未找到守护锚点:${needle}`);
|
|
308
|
+
return src.slice(0, idx).split("\n").length;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// 注册序:subagents → workflows → provider models → engine handler(链尾,D7)。
|
|
312
|
+
// engine handler 是唯一的直接 pi.on("before_agent_start")(其余三段经 setup* 间接注册),
|
|
313
|
+
// 恰出现一次的断言让「链尾」语义精确成立;未来若新增直接注册需人工复核段序。
|
|
314
|
+
expect(src.split('pi.on("before_agent_start"')).toHaveLength(2);
|
|
315
|
+
const lineSubagents = lineOf("setupSubagentListInjector(pi);");
|
|
316
|
+
const lineWorkflows = lineOf("setupWorkflowListInjector(pi);");
|
|
317
|
+
const lineModels = lineOf("setupModelListInjector(pi);");
|
|
318
|
+
const lineEngineHandler = lineOf('pi.on("before_agent_start"');
|
|
319
|
+
expect(lineSubagents).toBeLessThan(lineWorkflows);
|
|
320
|
+
expect(lineWorkflows).toBeLessThan(lineModels);
|
|
321
|
+
expect(lineModels).toBeLessThan(lineEngineHandler);
|
|
322
|
+
|
|
323
|
+
// engine handler 内渲染调用序:状态段在清单段之前(append 内段序:状态段 → 清单段)。
|
|
324
|
+
// 两个调用在 index.ts 中位于同一行(数组字面量),行号无法区分先后——用字符偏移断言
|
|
325
|
+
const offsetStatusCall = src.indexOf("buildSubagentEngineSection(defaultEngine)");
|
|
326
|
+
const offsetModelsCall = src.indexOf("buildEngineModelsPromptAppend(defaultEngine)");
|
|
327
|
+
if (offsetStatusCall < 0 || offsetModelsCall < 0) {
|
|
328
|
+
throw new Error("index.ts 中未找到 engine 段渲染调用锚点");
|
|
329
|
+
}
|
|
330
|
+
expect(offsetStatusCall).toBeGreaterThan(src.indexOf('pi.on("before_agent_start"'));
|
|
331
|
+
expect(offsetStatusCall).toBeLessThan(offsetModelsCall);
|
|
332
|
+
|
|
333
|
+
// 链模拟复刻保真锚点:append 拼装形态(空段剔除 + \n\n 连接 + 尾部追加模板)。
|
|
334
|
+
// 这些锚点保证 composeEngineAppend 与 index.ts 生产拼装的一致性——生产形态
|
|
335
|
+
// 变化会先在这里红,提醒同步更新本文件全部复刻点。
|
|
336
|
+
expect(src).toContain('[buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]');
|
|
337
|
+
expect(src).toContain('.filter((part) => part !== "")');
|
|
338
|
+
expect(src).toContain('.join("\\n\\n")');
|
|
339
|
+
expect(src).toContain("${event.systemPrompt}\\n\\n${append}");
|
|
340
|
+
});
|
|
341
|
+
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
12
|
|
|
13
|
-
import type { DiscoveredResource } from "
|
|
13
|
+
import type { DiscoveredResource } from "@zhushanwen/subagent-core/shared/resource-discovery.ts";
|
|
14
14
|
// 共享 mock 基建(vi.mock 工厂 / mock pi / mock ctx):helpers/injector-test-mocks.ts
|
|
15
15
|
import { createDiscoveryModuleMock, createLoggerModuleMock, createMockCtx, createMockPi, type CapturedHandlers } from "./helpers/injector-test-mocks.ts";
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ import { createDiscoveryModuleMock, createLoggerModuleMock, createMockCtx, creat
|
|
|
19
19
|
// resetModules 调用计数连续,mockClear 控制每用例重置) ──
|
|
20
20
|
const spies = vi.hoisted(() => ({ discoverResources: vi.fn(), getCachedFileContent: vi.fn() }));
|
|
21
21
|
|
|
22
|
-
vi.mock("
|
|
22
|
+
vi.mock("@zhushanwen/subagent-core/shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
|
|
23
23
|
|
|
24
24
|
// 工厂必须写成箭头惰性形式:vi.mock 被提升到 import 之前执行,直接传
|
|
25
25
|
// createLoggerModuleMock 引用会在提升位置立即求值 import 绑定 → TDZ ReferenceError
|
|
@@ -306,7 +306,7 @@ describe("discoverAllAgents 顺序契约(KV-cache)", () => {
|
|
|
306
306
|
spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
|
|
307
307
|
|
|
308
308
|
const { discoverAllAgents } = await import("../subagent-list-injector");
|
|
309
|
-
const agents = await discoverAllAgents("/ws"
|
|
309
|
+
const agents = await discoverAllAgents("/ws");
|
|
310
310
|
expect(agents.map((a) => a.name)).toEqual(["alpha", "worker", "zeta"]);
|
|
311
311
|
});
|
|
312
312
|
|
|
@@ -328,8 +328,8 @@ describe("discoverAllAgents 顺序契约(KV-cache)", () => {
|
|
|
328
328
|
spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
|
|
329
329
|
|
|
330
330
|
const { discoverAllAgents, formatAgentList } = await import("../subagent-list-injector");
|
|
331
|
-
const first = await discoverAllAgents("/ws"
|
|
332
|
-
const second = await discoverAllAgents("/ws"
|
|
331
|
+
const first = await discoverAllAgents("/ws");
|
|
332
|
+
const second = await discoverAllAgents("/ws");
|
|
333
333
|
expect(second).toEqual(first);
|
|
334
334
|
expect(formatAgentList(second)).toBe(formatAgentList(first));
|
|
335
335
|
});
|