@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
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
// src/execution/__tests__/index-session-start.test.ts
|
|
2
|
-
//
|
|
3
|
-
// C2 critical: index.ts session_start 的 UI handler 注入链路测试。
|
|
4
|
-
//
|
|
5
|
-
// 测试目标:验证 `subagentsWorkflowExtension(pi)` 的 session_start handler
|
|
6
|
-
// 是否正确注入 uiRequestHandler,特别是 **SR-3**:
|
|
7
|
-
// 无论 new 还是 existing SubagentService,session_start 都必须调
|
|
8
|
-
// setUiRequestHandler——/resume /fork 复用 existingService 时旧 handler 可能失效。
|
|
9
|
-
//
|
|
10
|
-
// 4 个 case:
|
|
11
|
-
// 1. new 路径(existingService=null):setUiRequestHandler 被调,参数是函数(tui mode)
|
|
12
|
-
// 2. existing 路径(existingService=mockService):setUiRequestHandler 仍被调(SR-3 关键)
|
|
13
|
-
// 3. headless mode(json):createUiRequestHandlerForMode 返回 undefined,
|
|
14
|
-
// setUiRequestHandler(undefined) 被调(不注入但仍有调用,SR-3 形式不破)
|
|
15
|
-
// 4. rpc mode:setUiRequestHandler 被调,参数是函数
|
|
16
|
-
//
|
|
17
|
-
// 既有 crash-recovery.test.ts / session-start-reaper.test.ts mock 了 SubagentService
|
|
18
|
-
// 但**没有断言 setUiRequestHandler 被调用**——本测试补这个断言,且覆盖 existing 路径
|
|
19
|
-
// (既有测试 fixed getSubagentService() => null,只能走 new 分支)。
|
|
20
|
-
|
|
21
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
22
|
-
|
|
23
|
-
// ── mock modules(在 import 前声明;路径相对 src/execution/__tests/) ──
|
|
24
|
-
//
|
|
25
|
-
// [D15] 为何此处内联 vi.mock 与 vitest.config.ts 的 alias(包根 mocks/pi-coding-agent.ts)并存:
|
|
26
|
-
// 两套 mock 分工不同,不强制统一——
|
|
27
|
-
// 1. **config alias(包根 mocks/pi-coding-agent.ts)**:完整 stub,导出 ExtensionAPI/
|
|
28
|
-
// ExtensionContext/ExtensionMode 等 *类型*(编译期擦除)+ getAgentDir 运行时值。服务于
|
|
29
|
-
// 全仓库绝大多数测试的类型解析与轻量 mock(不 import 真实重模块的测试直接吃 alias)。
|
|
30
|
-
// 2. **本文件内联 vi.mock(下方)**:vi.mock 在运行时覆盖 config alias,确保本文件 import
|
|
31
|
-
// 真实 index.ts(它 `import { getAgentDir } from "@earendil-works/pi-coding-agent"` +
|
|
32
|
-
// 一组 type-only import)时,运行时只暴露 getAgentDir,彻底隔离真实 SDK 的模块顶层
|
|
33
|
-
// 副作用(避免 jiti 加载真实 pi 包触发未 mock 的依赖链)。
|
|
34
|
-
// 3. 形状一致性:内联 mock 的运行时值形状(`{ getAgentDir }`)与 alias stub 的运行时值
|
|
35
|
-
// 形状完全一致(alias stub 运行时也只导出 getAgentDir 函数,类型导出在运行期擦除)。
|
|
36
|
-
// 差异仅在「覆盖时机」——vi.mock 比 alias 更早介入模块图解析,保证真实 index.ts 加载时
|
|
37
|
-
// 拿到的是纯函数桩而非 alias 的完整对象。
|
|
38
|
-
// 4. crash-recovery.test.ts / session-start-reaper.test.ts 同此模式(import 真实模块的
|
|
39
|
-
// 测试统一用内联 vi.mock 覆盖),三者保持一致。
|
|
40
|
-
// 结论:不抽共享 mocks 文件。alias 已承担类型解析,内联 vi.mock 承担运行时隔离,职责正交。
|
|
41
|
-
|
|
42
|
-
vi.mock("@earendil-works/pi-coding-agent", () => ({
|
|
43
|
-
getAgentDir: () => "/home/user/.pi/agent",
|
|
44
|
-
}));
|
|
45
|
-
vi.mock("@earendil-works/pi-coding-agent", () => ({
|
|
46
|
-
getAgentDir: () => "/home/user/.pi/agent",
|
|
47
|
-
}));
|
|
48
|
-
vi.mock("@earendil-works/pi-ai", () => ({
|
|
49
|
-
StringEnum: (values: string[]) => ({ type: "string", enum: values }),
|
|
50
|
-
}));
|
|
51
|
-
vi.mock("@earendil-works/pi-ai", () => ({
|
|
52
|
-
StringEnum: (values: string[]) => ({ type: "string", enum: values }),
|
|
53
|
-
}));
|
|
54
|
-
vi.mock("typebox", () => ({
|
|
55
|
-
Type: {
|
|
56
|
-
Object: (props: Record<string, unknown>) => ({ type: "object", properties: props }),
|
|
57
|
-
Optional: (schema: unknown) => ({ ...(schema as object), optional: true }),
|
|
58
|
-
String: () => ({ type: "string" }),
|
|
59
|
-
Boolean: () => ({ type: "boolean" }),
|
|
60
|
-
Number: () => ({ type: "number" }),
|
|
61
|
-
Array: (items: unknown) => ({ type: "array", items }),
|
|
62
|
-
Record: (key: unknown, value: unknown) => ({ type: "object", additionalProperties: value, key }),
|
|
63
|
-
Unknown: () => ({ type: "unknown" }),
|
|
64
|
-
Union: (members: unknown[]) => ({ type: "union", members }),
|
|
65
|
-
Literal: (value: unknown) => ({ type: "literal", value }),
|
|
66
|
-
},
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
// ── hoisted mock 实例:捕获 setUiRequestHandler 调用 + 可控行为 ──
|
|
70
|
-
|
|
71
|
-
const {
|
|
72
|
-
mockSetUiRequestHandler,
|
|
73
|
-
mockInitSession,
|
|
74
|
-
mockLoadAll,
|
|
75
|
-
mockRecoverManifestTmpFiles,
|
|
76
|
-
/** existing service 引用——测试可改写以模拟 /resume /fork 复用。 */
|
|
77
|
-
existingServiceRef,
|
|
78
|
-
} = vi.hoisted(() => ({
|
|
79
|
-
mockSetUiRequestHandler: vi.fn(),
|
|
80
|
-
mockInitSession: vi.fn(),
|
|
81
|
-
mockLoadAll: vi.fn(async () => []),
|
|
82
|
-
// ADR-035 启动恢复接线守护:session_start 必须调 service.recoverManifestTmpFiles
|
|
83
|
-
mockRecoverManifestTmpFiles: vi.fn(async () => ({ deleted: 0, recovered: 0 })),
|
|
84
|
-
existingServiceRef: { current: null as unknown },
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
// SubagentService mock:每次构造都返回同一组 spy,setUiRequestHandler 可观察。
|
|
88
|
-
// getSubagentService 返回 existingServiceRef.current(null=走 new;非 null=走 existing)。
|
|
89
|
-
vi.mock("../subagent-service.ts", () => ({
|
|
90
|
-
SubagentService: class {
|
|
91
|
-
initSession = mockInitSession;
|
|
92
|
-
setUiRequestHandler = mockSetUiRequestHandler;
|
|
93
|
-
recoverManifestTmpFiles = mockRecoverManifestTmpFiles;
|
|
94
|
-
startGcTimer = vi.fn();
|
|
95
|
-
getStreamSink = () => null;
|
|
96
|
-
dispose = vi.fn();
|
|
97
|
-
},
|
|
98
|
-
getSubagentService: () => existingServiceRef.current,
|
|
99
|
-
setSubagentService: vi.fn(),
|
|
100
|
-
}));
|
|
101
|
-
|
|
102
|
-
vi.mock("../model-config-service.ts", () => ({
|
|
103
|
-
ModelConfigService: class {
|
|
104
|
-
initModel = vi.fn();
|
|
105
|
-
setCtxModel = vi.fn();
|
|
106
|
-
},
|
|
107
|
-
getModelConfigService: () => null,
|
|
108
|
-
setModelConfigService: vi.fn(),
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
vi.mock("../worktree-manager.ts", () => ({
|
|
112
|
-
WorktreeManager: class {
|
|
113
|
-
constructor(_agentDir: string) {
|
|
114
|
-
/* mock */
|
|
115
|
-
}
|
|
116
|
-
scan = vi.fn();
|
|
117
|
-
cleanup = vi.fn();
|
|
118
|
-
create = vi.fn();
|
|
119
|
-
collectPatch = vi.fn();
|
|
120
|
-
registerPid = vi.fn();
|
|
121
|
-
},
|
|
122
|
-
}));
|
|
123
|
-
|
|
124
|
-
vi.mock("../session-file-gc.ts", () => ({
|
|
125
|
-
maybeCleanupExpiredSessionFiles: vi.fn(),
|
|
126
|
-
}));
|
|
127
|
-
|
|
128
|
-
// JsonlRunStore mock:loadAll 默认空数组(session_start 后段不抛即可)。
|
|
129
|
-
// dispose/flushPendingSaves:session_shutdown handler 会调 state.store.dispose()(W2C5);
|
|
130
|
-
// dispose 用 hoisted spy 以便 W2TC16 断言(instance.dispose 即 spy)。
|
|
131
|
-
const { mockStoreDispose, mockStoreFlushPendingSaves } = vi.hoisted(() => ({
|
|
132
|
-
mockStoreDispose: vi.fn(async () => {}),
|
|
133
|
-
mockStoreFlushPendingSaves: vi.fn(async () => {}),
|
|
134
|
-
}));
|
|
135
|
-
vi.mock("../../orchestration/jsonl-run-store.ts", () => ({
|
|
136
|
-
JsonlRunStore: class {
|
|
137
|
-
loadAll = mockLoadAll;
|
|
138
|
-
save = vi.fn(async () => {});
|
|
139
|
-
dispose = mockStoreDispose;
|
|
140
|
-
flushPendingSaves = mockStoreFlushPendingSaves;
|
|
141
|
-
},
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
// lifecycle mock:仅替换 terminateRunningRuns 为 hoisted spy(W2TC16 断言
|
|
145
|
-
// terminate→dispose 顺序用),其余导出(scheduleTimeBudget/runWorkflow/abortRun/
|
|
146
|
-
// evictDoneRunsBeyondCap)经 importOriginal 保留真实实现——本文件不执行真实
|
|
147
|
-
// workflow,替换只影响 shutdown handler 的 terminate 调用点。
|
|
148
|
-
const { mockTerminateRunningRuns } = vi.hoisted(() => ({
|
|
149
|
-
mockTerminateRunningRuns: vi.fn(async () => {}),
|
|
150
|
-
}));
|
|
151
|
-
vi.mock("../../orchestration/lifecycle.ts", async (importOriginal) => {
|
|
152
|
-
const actual = await importOriginal<typeof import("../../orchestration/lifecycle.ts")>();
|
|
153
|
-
return { ...actual, terminateRunningRuns: mockTerminateRunningRuns };
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// interface 层 mock:避免触发真实 pi.registerTool(pi 是 Proxy,真实模块访问 pi
|
|
157
|
-
// 属性时可能抛错)。路径相对 src/execution/__tests/ → ../../interface/...
|
|
158
|
-
// registerWorkflowTool / registerWorkflowsCommand 用 hoisted spy——W3TC7/8 需在
|
|
159
|
-
// it 内读 mock.calls 捕获 lazyDeps 与 runs getter(对齐 mockStoreDispose 先例)。
|
|
160
|
-
vi.mock("../../interface/subagent-tool.ts", () => ({
|
|
161
|
-
registerSubagentTool: vi.fn(),
|
|
162
|
-
}));
|
|
163
|
-
vi.mock("../../interface/subagents.ts", () => ({
|
|
164
|
-
registerSubagentsCommand: vi.fn(),
|
|
165
|
-
}));
|
|
166
|
-
vi.mock("../../interface/bg-notify-render.ts", () => ({
|
|
167
|
-
renderBgNotifyMessage: vi.fn(),
|
|
168
|
-
}));
|
|
169
|
-
const { mockRegisterWorkflowTool, mockRegisterWorkflowsCommand } = vi.hoisted(() => ({
|
|
170
|
-
mockRegisterWorkflowTool: vi.fn(),
|
|
171
|
-
mockRegisterWorkflowsCommand: vi.fn(),
|
|
172
|
-
}));
|
|
173
|
-
vi.mock("../../interface/tool-workflow.ts", () => ({
|
|
174
|
-
registerWorkflowTool: mockRegisterWorkflowTool,
|
|
175
|
-
}));
|
|
176
|
-
vi.mock("../../interface/tool-workflow-script.ts", () => ({
|
|
177
|
-
registerWorkflowScriptTool: vi.fn(),
|
|
178
|
-
}));
|
|
179
|
-
vi.mock("../../interface/commands.ts", () => ({
|
|
180
|
-
registerWorkflowsCommand: mockRegisterWorkflowsCommand,
|
|
181
|
-
}));
|
|
182
|
-
|
|
183
|
-
// ── import 被测工厂 ──
|
|
184
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
185
|
-
|
|
186
|
-
import subagentsExtension from "../../index.ts";
|
|
187
|
-
import { Budget } from "../../orchestration/models/budget.ts";
|
|
188
|
-
import { Trace } from "../../orchestration/models/trace.ts";
|
|
189
|
-
import { WorkflowRun } from "../../orchestration/models/workflow-run.ts";
|
|
190
|
-
|
|
191
|
-
// ── helpers ──
|
|
192
|
-
|
|
193
|
-
/** 创建可观察的 mock ExtensionAPI,捕获 session_start / session_shutdown handler。
|
|
194
|
-
* Proxy 兜底:未显式处理的 pi.xxx 返回 noop,避免抛错。
|
|
195
|
-
* spies(可选):U2 通知账本用例注入 appendEntry / sendMessage 观察桩。 */
|
|
196
|
-
function createMockPi(spies?: { appendEntry?: (customType: string, data: unknown) => void; sendMessage?: (message: unknown, options?: unknown) => void }): {
|
|
197
|
-
pi: ExtensionAPI;
|
|
198
|
-
getSessionStartHandler: () => ((event: unknown, ctx: unknown) => Promise<void>) | undefined;
|
|
199
|
-
getSessionShutdownHandler: () => ((event: unknown, ctx: unknown) => Promise<void>) | undefined;
|
|
200
|
-
} {
|
|
201
|
-
let sessionStartHandler: ((event: unknown, ctx: unknown) => Promise<void>) | undefined;
|
|
202
|
-
let sessionShutdownHandler: ((event: unknown, ctx: unknown) => Promise<void>) | undefined;
|
|
203
|
-
const events = { emit: vi.fn() };
|
|
204
|
-
const noop = (): void => {
|
|
205
|
-
/* mock */
|
|
206
|
-
};
|
|
207
|
-
const pi = new Proxy<ExtensionAPI>({} as ExtensionAPI, {
|
|
208
|
-
get(_target, prop: string | symbol): unknown {
|
|
209
|
-
if (prop === "on") {
|
|
210
|
-
return (event: string, handler: (...args: unknown[]) => unknown) => {
|
|
211
|
-
if (event === "session_start") {
|
|
212
|
-
sessionStartHandler = handler as (event: unknown, ctx: unknown) => Promise<void>;
|
|
213
|
-
}
|
|
214
|
-
if (event === "session_shutdown") {
|
|
215
|
-
sessionShutdownHandler = handler as (event: unknown, ctx: unknown) => Promise<void>;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
if (prop === "events") return events;
|
|
220
|
-
if (prop === "appendEntry") return spies?.appendEntry ?? noop;
|
|
221
|
-
if (prop === "sendMessage") return spies?.sendMessage ?? noop;
|
|
222
|
-
if (prop === "registerMessageRenderer") return noop;
|
|
223
|
-
return noop;
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
return {
|
|
227
|
-
pi,
|
|
228
|
-
getSessionStartHandler: () => sessionStartHandler,
|
|
229
|
-
getSessionShutdownHandler: () => sessionShutdownHandler,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** 最小 ExtensionContext mock。mode 由参数控制(决定 handler 注入行为)。
|
|
234
|
-
* entries(可选):U2 通知账本用例注入 sessionManager.getEntries 返回值
|
|
235
|
-
* (模拟重启前落盘的 ledger/ack entry)。 */
|
|
236
|
-
function createMockCtx(mode: "tui" | "rpc" | "json" | "print", entries?: unknown[]): Record<string, unknown> {
|
|
237
|
-
const sessionManager = {
|
|
238
|
-
getSessionId: () => "session-inject-1",
|
|
239
|
-
getSessionFile: () => "/home/user/.pi/agent/sessions/session-inject-1.jsonl",
|
|
240
|
-
getSessionDir: () => "/home/user/.pi/agent/sessions",
|
|
241
|
-
getCwd: () => "/home/user/project",
|
|
242
|
-
getEntries: () => entries ?? [],
|
|
243
|
-
getBranch: () => [],
|
|
244
|
-
getLeafId: () => null,
|
|
245
|
-
getLeafEntry: () => undefined,
|
|
246
|
-
getEntry: () => undefined,
|
|
247
|
-
getHeader: () => null,
|
|
248
|
-
getTree: () => [],
|
|
249
|
-
getSessionName: () => undefined,
|
|
250
|
-
};
|
|
251
|
-
const ui = mode === "rpc" ? { setWidget: vi.fn() } : undefined;
|
|
252
|
-
return {
|
|
253
|
-
cwd: "/home/user/project",
|
|
254
|
-
mode,
|
|
255
|
-
modelRegistry: {
|
|
256
|
-
getAvailable: () => [],
|
|
257
|
-
find: () => undefined,
|
|
258
|
-
hasConfiguredAuth: () => false,
|
|
259
|
-
},
|
|
260
|
-
model: undefined,
|
|
261
|
-
sessionManager,
|
|
262
|
-
ui,
|
|
263
|
-
isIdle: () => true,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
beforeEach(() => {
|
|
268
|
-
vi.clearAllMocks();
|
|
269
|
-
mockLoadAll.mockResolvedValue([]);
|
|
270
|
-
existingServiceRef.current = null;
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// ── tests ──
|
|
274
|
-
|
|
275
|
-
describe("session_start UI handler 注入链路(SR-3)", () => {
|
|
276
|
-
it("new 路径(existingService=null):setUiRequestHandler 被调,参数是函数(tui mode)", async () => {
|
|
277
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
278
|
-
subagentsExtension(pi);
|
|
279
|
-
|
|
280
|
-
const handler = getSessionStartHandler();
|
|
281
|
-
expect(handler).toBeDefined();
|
|
282
|
-
await handler!({ type: "session_start" }, createMockCtx("tui"));
|
|
283
|
-
|
|
284
|
-
// SR-3 核心:setUiRequestHandler 必须被调
|
|
285
|
-
expect(mockSetUiRequestHandler).toHaveBeenCalledTimes(1);
|
|
286
|
-
// 非 headless mode → 注入的是函数(UiRequestHandler),非 undefined
|
|
287
|
-
const injected = mockSetUiRequestHandler.mock.calls[0]?.[0];
|
|
288
|
-
expect(typeof injected).toBe("function");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it("existing 路径(existingService=mockService):setUiRequestHandler 仍被调(SR-3 关键)", async () => {
|
|
292
|
-
// 模拟 /resume /fork:getSubagentService() 返回已存在的 service
|
|
293
|
-
//(同一 mock 类的实例——spy 仍是 mockSetUiRequestHandler,断言可观察到调用)
|
|
294
|
-
existingServiceRef.current = {
|
|
295
|
-
initSession: mockInitSession,
|
|
296
|
-
setUiRequestHandler: mockSetUiRequestHandler,
|
|
297
|
-
// ADR-035:与 SubagentService mock class 形状一致——session_start
|
|
298
|
-
// 会调 service.recoverManifestTmpFiles(),缺方法会抛 TypeError 被吞为 console.warn
|
|
299
|
-
recoverManifestTmpFiles: mockRecoverManifestTmpFiles,
|
|
300
|
-
startGcTimer: vi.fn(),
|
|
301
|
-
getStreamSink: () => null,
|
|
302
|
-
dispose: vi.fn(),
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
306
|
-
subagentsExtension(pi);
|
|
307
|
-
|
|
308
|
-
const handler = getSessionStartHandler();
|
|
309
|
-
expect(handler).toBeDefined();
|
|
310
|
-
await handler!({ type: "session_start" }, createMockCtx("tui"));
|
|
311
|
-
|
|
312
|
-
// SR-3 关键断言:existing 路径下 setUiRequestHandler 仍被调
|
|
313
|
-
//(旧 handler 可能已失效,session_start 必须重新注入)
|
|
314
|
-
expect(mockSetUiRequestHandler).toHaveBeenCalledTimes(1);
|
|
315
|
-
const injected = mockSetUiRequestHandler.mock.calls[0]?.[0];
|
|
316
|
-
expect(typeof injected).toBe("function");
|
|
317
|
-
|
|
318
|
-
// ADR-035 existing 路径守护:/resume /fork 复用 service 时 session_start
|
|
319
|
-
// 也必须调 recoverManifestTmpFiles。守护 case 走 new 路径抓不到 existing 误删,
|
|
320
|
-
// 此处对称断言(与 setUiRequestHandler 在本 case 的独立断言同模式)。
|
|
321
|
-
expect(mockRecoverManifestTmpFiles).toHaveBeenCalledTimes(1);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it("headless mode(json):createUiRequestHandlerForMode 返回 undefined → setUiRequestHandler(undefined)", async () => {
|
|
325
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
326
|
-
subagentsExtension(pi);
|
|
327
|
-
|
|
328
|
-
const handler = getSessionStartHandler();
|
|
329
|
-
await handler!({ type: "session_start" }, createMockCtx("json"));
|
|
330
|
-
|
|
331
|
-
// SR-3 形式不破:仍调 setUiRequestHandler(参数是 undefined,表示不注入)
|
|
332
|
-
expect(mockSetUiRequestHandler).toHaveBeenCalledTimes(1);
|
|
333
|
-
expect(mockSetUiRequestHandler.mock.calls[0]?.[0]).toBeUndefined();
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("rpc mode:setUiRequestHandler 被调,参数是函数", async () => {
|
|
337
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
338
|
-
subagentsExtension(pi);
|
|
339
|
-
|
|
340
|
-
const handler = getSessionStartHandler();
|
|
341
|
-
await handler!({ type: "session_start" }, createMockCtx("rpc"));
|
|
342
|
-
|
|
343
|
-
expect(mockSetUiRequestHandler).toHaveBeenCalledTimes(1);
|
|
344
|
-
const injected = mockSetUiRequestHandler.mock.calls[0]?.[0];
|
|
345
|
-
expect(typeof injected).toBe("function");
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it("initSession 不再收 uiRequestHandler(#24 单一注入入口:setUiRequestHandler 唯一通道)", async () => {
|
|
349
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
350
|
-
subagentsExtension(pi);
|
|
351
|
-
|
|
352
|
-
const handler = getSessionStartHandler();
|
|
353
|
-
await handler!({ type: "session_start" }, createMockCtx("tui"));
|
|
354
|
-
|
|
355
|
-
// [#24] uiRequestHandler 单一注入入口:index.ts 只调 service.setUiRequestHandler(h),
|
|
356
|
-
// 不再重复传 initSession.uiRequestHandler——避免同一 handler 双路径注入造成
|
|
357
|
-
// "哪一个是 source of truth" 歧义。此处断言该契约不被回退。
|
|
358
|
-
expect(mockInitSession).toHaveBeenCalledTimes(1);
|
|
359
|
-
const initArg = mockInitSession.mock.calls[0]?.[0] as {
|
|
360
|
-
uiRequestHandler?: unknown;
|
|
361
|
-
mode?: unknown;
|
|
362
|
-
dialogQueue?: unknown;
|
|
363
|
-
} | undefined;
|
|
364
|
-
expect(initArg).toBeDefined();
|
|
365
|
-
// uiRequestHandler 不再走 initSession(已被 #24 移除)
|
|
366
|
-
expect(initArg?.uiRequestHandler).toBeUndefined();
|
|
367
|
-
// mode 仍需 session 级注入(uiObservability.setMode 依赖它)
|
|
368
|
-
expect(initArg?.mode).toBe("tui");
|
|
369
|
-
// dialogQueue 仍注入(SR-4 清理路径接通)
|
|
370
|
-
expect(initArg).toHaveProperty("dialogQueue");
|
|
371
|
-
// handler 经 setUiRequestHandler 注入(单一入口)——前面 case 已断言,此处复断不再赘述。
|
|
372
|
-
expect(mockSetUiRequestHandler).toHaveBeenCalledTimes(1);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it("session_start 调用 recoverManifestTmpFiles(接线守护,防 ADR-035 启动恢复再次断线)", async () => {
|
|
376
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
377
|
-
subagentsExtension(pi);
|
|
378
|
-
|
|
379
|
-
const handler = getSessionStartHandler();
|
|
380
|
-
expect(handler).toBeDefined();
|
|
381
|
-
await handler!({ type: "session_start" }, createMockCtx("tui"));
|
|
382
|
-
|
|
383
|
-
// ADR-035 接线断言:session_start 必须调 service.recoverManifestTmpFiles(防死代码回退)
|
|
384
|
-
expect(mockRecoverManifestTmpFiles).toHaveBeenCalledTimes(1);
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
describe("session_shutdown: store.dispose 接线(W2TC16)", () => {
|
|
389
|
-
it("W2TC16: session_shutdown 触发后 store 实例 dispose 被调用(terminate 完成之后、sessionState 清理之前)", async () => {
|
|
390
|
-
// 预热:session_start 建立一个 sessionState 条目(含 mock store 实例)。
|
|
391
|
-
// loadAll 注入 running run 使其进入 sessionState.runs——duck-typed 对象 +
|
|
392
|
-
// no-op transition:session_start 的 kill-9 恢复会把 running run 转 done,failed
|
|
393
|
-
// (真实 WorkflowRun.reconstruct 无法保持 running),no-op transition 吞掉该转换
|
|
394
|
-
// 让 run 以 running 进入 sessionState.runs。运行时形状由消费路径保证:handler
|
|
395
|
-
// 只读 state.status(string)与 transition(可调用),duck typing 满足。
|
|
396
|
-
mockStoreDispose.mockClear();
|
|
397
|
-
const runningRun = {
|
|
398
|
-
runId: "wf-w2tc16-1",
|
|
399
|
-
state: { status: "running", error: undefined as string | undefined },
|
|
400
|
-
transition: vi.fn(),
|
|
401
|
-
} as unknown as WorkflowRun;
|
|
402
|
-
mockLoadAll.mockResolvedValue([runningRun]);
|
|
403
|
-
const { pi, getSessionStartHandler, getSessionShutdownHandler } = createMockPi();
|
|
404
|
-
subagentsExtension(pi);
|
|
405
|
-
|
|
406
|
-
const startHandler = getSessionStartHandler();
|
|
407
|
-
expect(startHandler).toBeDefined();
|
|
408
|
-
await startHandler!({ type: "session_start" }, createMockCtx("tui"));
|
|
409
|
-
|
|
410
|
-
// terminate gate:deferred 模拟 terminateRunningRuns 内部 `await store.save(run)`
|
|
411
|
-
// 落盘边界——resolve 前 dispose 不得发生(terminate 未完成 = failed 状态未落盘,
|
|
412
|
-
// 此刻 dispose 刷 pending 批会把未定稿的 running 尾巴刷出去,重启后 kill-9
|
|
413
|
-
// 恢复误判)。
|
|
414
|
-
let resolveTerminate = (): void => {};
|
|
415
|
-
mockTerminateRunningRuns.mockImplementationOnce(async () => {
|
|
416
|
-
await new Promise<void>((r) => {
|
|
417
|
-
resolveTerminate = r;
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// 触发 session_shutdown(event 触发模式对齐本文件 session_start 既有写法)
|
|
422
|
-
const shutdownHandler = getSessionShutdownHandler();
|
|
423
|
-
expect(shutdownHandler).toBeDefined();
|
|
424
|
-
const shutdownDone = shutdownHandler!({ type: "session_shutdown" }, createMockCtx("tui"));
|
|
425
|
-
|
|
426
|
-
// terminate 未完成(save 未落盘)→ dispose 未被调(await 边界真实生效)
|
|
427
|
-
await Promise.resolve();
|
|
428
|
-
expect(mockStoreDispose).not.toHaveBeenCalled();
|
|
429
|
-
|
|
430
|
-
resolveTerminate();
|
|
431
|
-
await shutdownDone;
|
|
432
|
-
|
|
433
|
-
// 每 sessionState 条目一次:session_start 建了 1 个 session → terminate 调用 1 次
|
|
434
|
-
expect(mockTerminateRunningRuns).toHaveBeenCalledTimes(1);
|
|
435
|
-
// 原因串字面值(session_shutdown 路径,接口契约锁定)
|
|
436
|
-
expect(mockTerminateRunningRuns.mock.calls[0]?.[1]).toBe("Session shutdown: run terminated");
|
|
437
|
-
// dispose 在 terminate 完成后被调(每 session 1 次)
|
|
438
|
-
expect(mockStoreDispose).toHaveBeenCalledTimes(1);
|
|
439
|
-
|
|
440
|
-
// 顺序断言「sessionState 清理之前」(标题后半):sessionState.delete 是 Map
|
|
441
|
-
// 同步操作、无外部可观察 spy——用第二次 shutdown 的幂等性间接证明:第一次
|
|
442
|
-
// handler 内条目已删(delete 在 dispose await 之后执行),重复 shutdown 遍历
|
|
443
|
-
// 空 Map 不再二次 terminate/dispose。若清理被跳过,此处会涨到 2 次。
|
|
444
|
-
await shutdownHandler!({ type: "session_shutdown" }, createMockCtx("tui"));
|
|
445
|
-
expect(mockTerminateRunningRuns).toHaveBeenCalledTimes(1);
|
|
446
|
-
expect(mockStoreDispose).toHaveBeenCalledTimes(1);
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// ── W-N: done run 淘汰接线(loadAll 裁剪 + onRunDone 裁剪) ──
|
|
451
|
-
|
|
452
|
-
/** fixture 基准时刻(过去时刻,保证 now 恒晚于全部 fixture completedAt)。 */
|
|
453
|
-
const W3_T0 = Date.parse("2020-01-01T00:00:00.000Z");
|
|
454
|
-
function w3IsoAt(min: number): string {
|
|
455
|
-
return new Date(W3_T0 + min * 60_000).toISOString();
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/** loadAll 重水合输入 fixture:done run(status done reason completed + completedAt)。 */
|
|
459
|
-
function makeLoadedDoneRun(runId: string, completedAt: string): WorkflowRun {
|
|
460
|
-
return WorkflowRun.reconstruct(
|
|
461
|
-
runId,
|
|
462
|
-
{
|
|
463
|
-
scriptSource: "execute() {}",
|
|
464
|
-
args: {},
|
|
465
|
-
scriptName: "test",
|
|
466
|
-
scriptPath: "/fake/test.js",
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
status: "done",
|
|
470
|
-
reason: "completed",
|
|
471
|
-
budget: new Budget({ maxTokens: 1000 }),
|
|
472
|
-
calls: new Map(),
|
|
473
|
-
trace: new Trace(),
|
|
474
|
-
errorLogs: [],
|
|
475
|
-
},
|
|
476
|
-
{ startedAt: completedAt, completedAt },
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/** loadAll 重水合输入 fixture:running run(kill-9 恢复输入,reconstruct 跳 I1 合法)。 */
|
|
481
|
-
function makeLoadedRunningRun(runId: string): WorkflowRun {
|
|
482
|
-
return WorkflowRun.reconstruct(
|
|
483
|
-
runId,
|
|
484
|
-
{
|
|
485
|
-
scriptSource: "execute() {}",
|
|
486
|
-
args: {},
|
|
487
|
-
scriptName: "test",
|
|
488
|
-
scriptPath: "/fake/test.js",
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
status: "running",
|
|
492
|
-
budget: new Budget({ maxTokens: 1000 }),
|
|
493
|
-
calls: new Map(),
|
|
494
|
-
trace: new Trace(),
|
|
495
|
-
errorLogs: [],
|
|
496
|
-
},
|
|
497
|
-
{ startedAt: w3IsoAt(0) },
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
describe("W-3: done run 淘汰接线(loadAll 裁剪 + onRunDone 裁剪)", () => {
|
|
502
|
-
it("W3TC7: session_start loadAll 灌入后裁剪到 K(21 done + 1 running kill-9 恢复)", async () => {
|
|
503
|
-
// 21 个 done(completedAt 递增)+ 1 个 running(kill-9 恢复输入)
|
|
504
|
-
const doneRuns = Array.from({ length: 21 }, (_, i) =>
|
|
505
|
-
makeLoadedDoneRun(`wf-loaded-${i}`, w3IsoAt(i)));
|
|
506
|
-
const runningRun = makeLoadedRunningRun("wf-recovered-1");
|
|
507
|
-
mockLoadAll.mockResolvedValue([...doneRuns, runningRun]);
|
|
508
|
-
|
|
509
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
510
|
-
subagentsExtension(pi);
|
|
511
|
-
const handler = getSessionStartHandler();
|
|
512
|
-
expect(handler).toBeDefined();
|
|
513
|
-
await handler!({ type: "session_start" }, createMockCtx("tui"));
|
|
514
|
-
|
|
515
|
-
// runs Map 观察点 = registerWorkflowsCommand 第二参数的 runs getter
|
|
516
|
-
//(工厂入口同步调用,mock.calls[0] 恒存在)
|
|
517
|
-
const getRuns = mockRegisterWorkflowsCommand.mock.calls[0]?.[1] as
|
|
518
|
-
| (() => Map<string, WorkflowRun>)
|
|
519
|
-
| undefined;
|
|
520
|
-
expect(typeof getRuns).toBe("function");
|
|
521
|
-
const runs = getRuns!();
|
|
522
|
-
|
|
523
|
-
// kill-9 恢复链把 running 转 done,failed(既有语义)后共 22 done → 裁到 20
|
|
524
|
-
expect(runs.size).toBe(20);
|
|
525
|
-
// 恢复 run 的 completedAt 为 transition 时刻(now,晚于全部 fixture 过去时间戳)
|
|
526
|
-
// → 必在保留端
|
|
527
|
-
expect(runs.has("wf-recovered-1")).toBe(true);
|
|
528
|
-
expect(runs.get("wf-recovered-1")?.state.status).toBe("done");
|
|
529
|
-
expect(runs.get("wf-recovered-1")?.state.reason).toBe("failed");
|
|
530
|
-
// 被淘汰 2 个 = fixture 最旧两个
|
|
531
|
-
expect(runs.has("wf-loaded-0")).toBe(false);
|
|
532
|
-
expect(runs.has("wf-loaded-1")).toBe(false);
|
|
533
|
-
for (let i = 2; i < 21; i++) {
|
|
534
|
-
expect(runs.has(`wf-loaded-${i}`)).toBe(true);
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
it("W3TC8: onRunDone 回调接线——notify → track → evict,本轮 run 不被自身触发的裁剪淘汰", async () => {
|
|
539
|
-
// session_start 预热:恰好 20 个 done(completedAt t0..t19)
|
|
540
|
-
const doneRuns = Array.from({ length: 20 }, (_, i) =>
|
|
541
|
-
makeLoadedDoneRun(`wf-pre-${i}`, w3IsoAt(i)));
|
|
542
|
-
mockLoadAll.mockResolvedValue(doneRuns);
|
|
543
|
-
|
|
544
|
-
const { pi, getSessionStartHandler } = createMockPi();
|
|
545
|
-
subagentsExtension(pi);
|
|
546
|
-
const handler = getSessionStartHandler();
|
|
547
|
-
expect(handler).toBeDefined();
|
|
548
|
-
await handler!({ type: "session_start" }, createMockCtx("tui"));
|
|
549
|
-
|
|
550
|
-
// lazyDeps = registerWorkflowTool 第二参数(getter 转发 makeDeps 的 onRunDone)
|
|
551
|
-
const lazyDeps = mockRegisterWorkflowTool.mock.calls[0]?.[1] as
|
|
552
|
-
| { onRunDone: (run: WorkflowRun) => void }
|
|
553
|
-
| undefined;
|
|
554
|
-
expect(lazyDeps).toBeDefined();
|
|
555
|
-
|
|
556
|
-
// 本轮 newDoneRun:completedAt = now(晚于全部 fixture 过去时间戳)
|
|
557
|
-
const newDoneRun = WorkflowRun.reconstruct(
|
|
558
|
-
"wf-current-1",
|
|
559
|
-
{
|
|
560
|
-
scriptSource: "execute() {}",
|
|
561
|
-
args: {},
|
|
562
|
-
scriptName: "test",
|
|
563
|
-
scriptPath: "/fake/test.js",
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
status: "done",
|
|
567
|
-
reason: "completed",
|
|
568
|
-
budget: new Budget({ maxTokens: 1000 }),
|
|
569
|
-
calls: new Map(),
|
|
570
|
-
trace: new Trace(),
|
|
571
|
-
errorLogs: [],
|
|
572
|
-
},
|
|
573
|
-
{ startedAt: w3IsoAt(999), completedAt: new Date().toISOString() },
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
// 模拟真实流程的注册步骤:lifecycle.ts runWorkflow 在创建时 deps.runs.set(runId, run)
|
|
577
|
-
//(onRunDone 回调只裁剪不注册——本轮 run 必须先在 Map 中,「保留端」断言才有对象)
|
|
578
|
-
const getRuns = mockRegisterWorkflowsCommand.mock.calls[0]?.[1] as
|
|
579
|
-
| (() => Map<string, WorkflowRun>)
|
|
580
|
-
| undefined;
|
|
581
|
-
expect(typeof getRuns).toBe("function");
|
|
582
|
-
const runs = getRuns!();
|
|
583
|
-
runs.set("wf-current-1", newDoneRun);
|
|
584
|
-
|
|
585
|
-
// 回调不抛错(notifyDone 对 mock pi Proxy 安全:sendMessage 返回 noop;
|
|
586
|
-
// toGuiCtx(tui ctx) isGuiCapable false 走无 __gui__ 分支)
|
|
587
|
-
expect(() => lazyDeps!.onRunDone(newDoneRun)).not.toThrow();
|
|
588
|
-
|
|
589
|
-
// 21 done 裁 1:size 回到 20
|
|
590
|
-
expect(runs.size).toBe(20);
|
|
591
|
-
// 被淘汰的是 t0 最旧 run 而非本轮 run(completedAt 最新恒在保留端)
|
|
592
|
-
expect(runs.has("wf-current-1")).toBe(true);
|
|
593
|
-
expect(runs.has("wf-pre-0")).toBe(false);
|
|
594
|
-
for (let i = 1; i < 20; i++) {
|
|
595
|
-
expect(runs.has(`wf-pre-${i}`)).toBe(true);
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// ============================================================
|
|
601
|
-
// [U2] session_start 通知账本装配 + 重启恢复钩子(设计 D4)
|
|
602
|
-
// ============================================================
|
|
603
|
-
//
|
|
604
|
-
// mock session 文件(ctx.sessionManager.getEntries)含 ledger/ack entry →
|
|
605
|
-
// session_start handler 装配账本并重放差集:未销账号经 pi.sendMessage({triggerTurn:true})
|
|
606
|
-
// 单通道重投;已销账零重发。bind 顺序在 service.initSession 之前(notifier.notify
|
|
607
|
-
// 经模块级绑定消费账本)。
|
|
608
|
-
|
|
609
|
-
import {
|
|
610
|
-
_resetNotifyLedgerForTest,
|
|
611
|
-
NOTIFY_ACK_CUSTOM_TYPE,
|
|
612
|
-
NOTIFY_CUSTOM_TYPE,
|
|
613
|
-
NOTIFY_LEDGER_CUSTOM_TYPE,
|
|
614
|
-
} from "../notify-ledger.ts";
|
|
615
|
-
|
|
616
|
-
describe("session_start 通知账本恢复钩子(U2 B-ledger)", () => {
|
|
617
|
-
afterEach(() => {
|
|
618
|
-
_resetNotifyLedgerForTest();
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
it("mock session 含 ledger/ack entry → 未销账号重放(单通道 triggerTurn),已销账零重发", async () => {
|
|
622
|
-
const appendEntryCalls: Array<{ customType: string; data: unknown }> = [];
|
|
623
|
-
const sendMessageCalls: Array<{ message: unknown; options: unknown }> = [];
|
|
624
|
-
const { pi, getSessionStartHandler } = createMockPi({
|
|
625
|
-
appendEntry: (customType, data) => {
|
|
626
|
-
appendEntryCalls.push({ customType, data });
|
|
627
|
-
},
|
|
628
|
-
sendMessage: (message, options) => {
|
|
629
|
-
sendMessageCalls.push({ message, options });
|
|
630
|
-
},
|
|
631
|
-
});
|
|
632
|
-
subagentsExtension(pi);
|
|
633
|
-
|
|
634
|
-
// 模拟重启前落盘:2 条 ledger entry(s-a 已销账 + s-b 未销账)+ 1 条 ack
|
|
635
|
-
const entries: unknown[] = [
|
|
636
|
-
{ type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-a", content: "ca", record: { notifyId: "s-a" } } },
|
|
637
|
-
{ type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-b", content: "cb", record: { notifyId: "s-b" } } },
|
|
638
|
-
{ type: "custom", customType: NOTIFY_ACK_CUSTOM_TYPE, data: { v: 1, notifyId: "s-a" } },
|
|
639
|
-
];
|
|
640
|
-
const handler = getSessionStartHandler();
|
|
641
|
-
expect(handler).toBeDefined();
|
|
642
|
-
await handler!({ type: "session_start" }, createMockCtx("rpc", entries));
|
|
643
|
-
|
|
644
|
-
// 未销账 s-b 单条重放:sendMessage 恰 1 次,options 恰 {triggerTurn:true}
|
|
645
|
-
expect(sendMessageCalls).toHaveLength(1);
|
|
646
|
-
expect(sendMessageCalls[0]!.options).toEqual({ triggerTurn: true });
|
|
647
|
-
const sent = sendMessageCalls[0]!.message as { customType: string; content: string; details?: { notifyId?: string } };
|
|
648
|
-
expect(sent.customType).toBe(NOTIFY_CUSTOM_TYPE);
|
|
649
|
-
expect(sent.content).toBe("cb");
|
|
650
|
-
expect(sent.details?.notifyId).toBe("s-b");
|
|
651
|
-
// 已销账 s-a 零重发(不追加新 ledger entry,不重投)
|
|
652
|
-
expect(appendEntryCalls.filter((c) => c.customType === NOTIFY_LEDGER_CUSTOM_TYPE)).toHaveLength(0);
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
it("全部已销账(差集为空)→ 零重放零发送", async () => {
|
|
656
|
-
const sendMessageCalls: unknown[] = [];
|
|
657
|
-
const { pi, getSessionStartHandler } = createMockPi({
|
|
658
|
-
sendMessage: () => {
|
|
659
|
-
sendMessageCalls.push({});
|
|
660
|
-
},
|
|
661
|
-
});
|
|
662
|
-
subagentsExtension(pi);
|
|
663
|
-
|
|
664
|
-
const entries: unknown[] = [
|
|
665
|
-
{ type: "custom", customType: NOTIFY_LEDGER_CUSTOM_TYPE, data: { v: 1, notifyId: "s-x", content: "cx", record: { notifyId: "s-x" } } },
|
|
666
|
-
{ type: "custom", customType: NOTIFY_ACK_CUSTOM_TYPE, data: { v: 1, notifyId: "s-x" } },
|
|
667
|
-
];
|
|
668
|
-
await getSessionStartHandler()!({ type: "session_start" }, createMockCtx("rpc", entries));
|
|
669
|
-
|
|
670
|
-
expect(sendMessageCalls).toHaveLength(0);
|
|
671
|
-
});
|
|
672
|
-
});
|