@zhushanwen/pi-subagent-workflow 8.5.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 +13 -16
- 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 +142 -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 +157 -0
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +12 -0
- 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 +34 -13
- package/src/interface/commands.ts +3 -3
- package/src/interface/format.ts +2 -2
- package/src/interface/helpers.ts +3 -3
- 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 +288 -19
- package/src/interface/subagent-tool-schema.ts +163 -0
- package/src/interface/subagent-tool.ts +65 -129
- package/src/interface/subagents.ts +5 -5
- 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} +132 -18
- 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 -256
- package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
- package/src/execution/__tests__/chat-engine-routing.test.ts +0 -597
- 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 -385
- 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 -189
- package/src/execution/__tests__/execution-record.test.ts +0 -1284
- 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 -138
- 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 -238
- 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 -593
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
- package/src/execution/__tests__/lifecycle-manager.test.ts +0 -337
- 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 -109
- package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
- package/src/execution/__tests__/model-resolver.test.ts +0 -413
- 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 -574
- package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
- package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
- package/src/execution/__tests__/output-collector.test.ts +0 -61
- 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 -644
- 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__/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 -311
- package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
- package/src/execution/__tests__/spawn-args.test.ts +0 -434
- 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__/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 -506
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -397
- package/src/execution/__tests__/tombstone-store.test.ts +0 -73
- package/src/execution/__tests__/tool-action.test.ts +0 -385
- 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 -193
- package/src/execution/agent-result-mapper.ts +0 -90
- package/src/execution/alive-store.ts +0 -92
- package/src/execution/argv-mirror.ts +0 -109
- 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 -567
- 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 -648
- 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 -304
- package/src/execution/execute-options-mapper.ts +0 -111
- package/src/execution/execution-record.ts +0 -906
- 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 -491
- package/src/execution/lifecycle-predicates.ts +0 -65
- package/src/execution/manifest-store.ts +0 -256
- package/src/execution/model-config-service.ts +0 -204
- package/src/execution/model-resolver.ts +0 -276
- package/src/execution/notifier.ts +0 -260
- package/src/execution/output-collector.ts +0 -88
- 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 -1548
- 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 -2119
- package/src/execution/subprocess-agent-runner.ts +0 -303
- 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 -910
- 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 -174
- 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 -336
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -208
- package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -157
- package/src/orchestration/__tests__/args-validator.test.ts +0 -143
- package/src/orchestration/__tests__/concurrency-gate.test.ts +0 -125
- package/src/orchestration/__tests__/config-loader.test.ts +0 -503
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -724
- 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 -314
- package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
- package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -277
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
- package/src/orchestration/__tests__/lifecycle.test.ts +0 -625
- package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -1971
- package/src/orchestration/__tests__/script-lint.test.ts +0 -831
- package/src/orchestration/__tests__/skill-discovery.test.ts +0 -157
- package/src/orchestration/__tests__/test-mocks.ts +0 -197
- 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 -520
- package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -110
- 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 -93
- package/src/orchestration/args-validator.ts +0 -127
- package/src/orchestration/concurrency-gate.ts +0 -69
- package/src/orchestration/config-loader.ts +0 -315
- package/src/orchestration/error-recovery.ts +0 -862
- package/src/orchestration/execute-agent-call.ts +0 -217
- package/src/orchestration/launcher.ts +0 -410
- package/src/orchestration/lifecycle.ts +0 -396
- package/src/orchestration/models/__tests__/budget.test.ts +0 -367
- 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 -148
- package/src/orchestration/models/ports.ts +0 -164
- package/src/orchestration/models/run-runtime.ts +0 -89
- 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 -292
- 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 -92
- package/src/orchestration/worker-handle.ts +0 -115
- package/src/orchestration/worker-host.ts +0 -98
- package/src/orchestration/worker-script-builder.ts +0 -408
- 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__/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/agent-event.ts +0 -13
- package/src/shared/agent-ref.ts +0 -57
- package/src/shared/meta-parser.ts +0 -261
- package/src/shared/resource-discovery.ts +0 -811
- package/src/shared/resource-meta.ts +0 -65
- package/src/shared/schema-jsonify.ts +0 -56
- 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 -1307
- package/workflows/review-fix-loop.js +0 -1396
- package/workflows/scatter-gather.js +0 -178
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
import type { Component } from "@earendil-works/pi-tui";
|
|
12
12
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
13
13
|
|
|
14
|
-
import { computeElapsedSeconds } from "
|
|
15
|
-
import type { SubagentService } from "
|
|
16
|
-
import type { SubagentRecord } from "
|
|
17
|
-
import { displayAgentName } from "
|
|
14
|
+
import { computeElapsedSeconds } from "@zhushanwen/subagent-core/execution/execution-record.ts";
|
|
15
|
+
import type { SubagentService } from "@zhushanwen/subagent-core/execution/subagent-service.ts";
|
|
16
|
+
import type { SubagentRecord } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
17
|
+
import { displayAgentName } from "@zhushanwen/subagent-core/shared/agent-ref.ts";
|
|
18
18
|
import {
|
|
19
19
|
firstLine,
|
|
20
20
|
formatDisplayItem,
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// 按键处理 processKey 仍留 list-view,由 list-view factory 注入给组件(KeyHandler 类型在此),
|
|
7
7
|
// 故 list-component 无需 import list-view,循环消除。
|
|
8
8
|
|
|
9
|
-
import type { SubagentService } from "
|
|
10
|
-
import type { SubagentRecord } from "
|
|
9
|
+
import type { SubagentService } from "@zhushanwen/subagent-core/execution/subagent-service.ts";
|
|
10
|
+
import type { SubagentRecord } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
11
11
|
|
|
12
12
|
// ============================================================
|
|
13
13
|
// 常量
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
import { matchesKey } from "@earendil-works/pi-tui";
|
|
36
36
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
37
37
|
|
|
38
|
-
import type { SubagentService } from "
|
|
39
|
-
import type { SubagentRecord } from "
|
|
38
|
+
import type { SubagentService } from "@zhushanwen/subagent-core/execution/subagent-service.ts";
|
|
39
|
+
import type { SubagentRecord } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
40
40
|
import { type ThemeLike } from "./format.ts";
|
|
41
41
|
import { SubagentsListComponent } from "./list-component.ts";
|
|
42
42
|
import {
|
|
@@ -16,25 +16,27 @@ import {
|
|
|
16
16
|
isGuiCapable,
|
|
17
17
|
} from "@xyz-agent/extension-protocol";
|
|
18
18
|
|
|
19
|
-
import { SLUG_MAX_LENGTH } from "
|
|
20
|
-
import { computeElapsedSeconds } from "
|
|
21
|
-
import { isResumable } from "
|
|
22
|
-
import type { ExecutionRecord } from "
|
|
23
|
-
import type { ModelInfo } from "
|
|
24
|
-
import type { SubagentService } from "
|
|
25
|
-
import { displayAgentName } from "
|
|
19
|
+
import { SLUG_MAX_LENGTH } from "@zhushanwen/subagent-core/execution/execute-options-mapper.ts";
|
|
20
|
+
import { computeElapsedSeconds, projectOutcome } from "@zhushanwen/subagent-core/execution/execution-record.ts";
|
|
21
|
+
import { isResumable } from "@zhushanwen/subagent-core/execution/lifecycle-predicates.ts";
|
|
22
|
+
import type { ExecutionRecord } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
23
|
+
import type { ModelInfo } from "@zhushanwen/subagent-core/execution/model-resolver.ts";
|
|
24
|
+
import type { SubagentService } from "@zhushanwen/subagent-core/execution/subagent-service.ts";
|
|
25
|
+
import { displayAgentName } from "@zhushanwen/subagent-core/shared/agent-ref.ts";
|
|
26
26
|
import type {
|
|
27
27
|
BgResponse,
|
|
28
28
|
CancelResponse,
|
|
29
29
|
CloseResponse,
|
|
30
30
|
ExecutionStatus,
|
|
31
31
|
ExternalState,
|
|
32
|
+
ForkFromResponse,
|
|
32
33
|
ListResponse,
|
|
33
34
|
MessageResponse,
|
|
34
35
|
SubagentListItem,
|
|
35
36
|
SubagentRecord,
|
|
36
37
|
SubagentToolResult,
|
|
37
|
-
} from "
|
|
38
|
+
} from "@zhushanwen/subagent-core/execution/types.ts";
|
|
39
|
+
import { ResurrectDeniedError } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
38
40
|
import { mapRunIcon, mapRunStatus } from "./gui-mappers.ts";
|
|
39
41
|
|
|
40
42
|
// ============================================================
|
|
@@ -49,6 +51,9 @@ const MAX_LIST_LIMIT = 100;
|
|
|
49
51
|
/** background 启动提示文案(spec FR-3 bgResponse.message)。 */
|
|
50
52
|
const BG_MESSAGE = "detached, will notify on completion (auto-injected message, do not poll)";
|
|
51
53
|
|
|
54
|
+
/** 通知投递契约回显恒值(U1 预置,U2 账本兑现,见 BgResponse.notifyContract)。 */
|
|
55
|
+
const NOTIFY_CONTRACT = "ledger+at-least-once" as const;
|
|
56
|
+
|
|
52
57
|
/** subagentId(UUID)在 GUI header 的截断显示长度。 */
|
|
53
58
|
const SUBAGENT_ID_PREVIEW = 8;
|
|
54
59
|
|
|
@@ -79,7 +84,10 @@ export interface StartHandlerInput {
|
|
|
79
84
|
cwd?: string;
|
|
80
85
|
/** 可持续对话模式(true = chatMode,轮次完成进 idle 等续聊)。 */
|
|
81
86
|
conversation?: boolean;
|
|
82
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* 空闲超时毫秒数(仅 conversation 模式有意义,覆盖默认 5min)。
|
|
89
|
+
* 显式传 0/负数 = 禁用 idle GC(不挂 timer);不传走 env/默认优先级。
|
|
90
|
+
*/
|
|
83
91
|
idleTimeoutMs?: number;
|
|
84
92
|
/** 执行引擎(D4 三层路由第一层:本参数 > agent frontmatter engine > config defaultEngine)。 */
|
|
85
93
|
engine?: string;
|
|
@@ -92,6 +100,11 @@ export type StartHandlerResult = {
|
|
|
92
100
|
sessionFile: string | undefined;
|
|
93
101
|
/** 短标签,来自 record(handle.details.slug)。用于 result 行展示。 */
|
|
94
102
|
slug: string;
|
|
103
|
+
/**
|
|
104
|
+
* registry 全等回显(U1):handle.details.model = record.model = `${provider}/${id}`,
|
|
105
|
+
* 源头是 resolveModel 裁决放行的条目——通过校验 = 子进程必然按此名执行。
|
|
106
|
+
*/
|
|
107
|
+
model: string;
|
|
95
108
|
response: BgResponse;
|
|
96
109
|
};
|
|
97
110
|
|
|
@@ -115,6 +128,100 @@ export interface CancelHandlerResult {
|
|
|
115
128
|
response: CancelResponse;
|
|
116
129
|
}
|
|
117
130
|
|
|
131
|
+
// ============================================================
|
|
132
|
+
// [v8.5 A1/B] message 拒绝文案分流 + fork-from 引导语框架
|
|
133
|
+
// ============================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* [v8.5 A1] message 拒绝时的可行动文案分流。
|
|
137
|
+
*
|
|
138
|
+
* 背景:getRecordForAction 冷查只认 status==='running'(SP-2 可续聊重建),任何终态/
|
|
139
|
+
* 异归属记录都落到同一个「not found or not owned」错误,把两类完全不同的场景混为一谈:
|
|
140
|
+
* - user-close/cancelled:用户主动告别,记录真没了 → 引导 start 新的
|
|
141
|
+
* - parent-shutdown/gc/orphan 等:父会话重启/进程退出导致的断联,对话 jsonl 完好,
|
|
142
|
+
* resume/fork 基建现成 → 引导 fork-from 从旧记录接续
|
|
143
|
+
*
|
|
144
|
+
* 分流规则(消费方是 LLM,保持正交简单):
|
|
145
|
+
* - 找不到记录 → 原样透传 getRecordForAction 错误(id 打错最常见,原文案最准)
|
|
146
|
+
* - closed + user-close/cancelled →「已主动关闭,无法续聊」文案
|
|
147
|
+
* - 其余(closed 其他 reason / running 但异归属)→「断联可接续」文案
|
|
148
|
+
*/
|
|
149
|
+
export function endedMessageGuard(service: SubagentService, id: string, original: unknown): Error {
|
|
150
|
+
// [v8.5 D] 透明重生守卫拒绝(worktree/异进程占用)原样透传——错误自带完整行动语言,
|
|
151
|
+
// 若被下方 fork-from 指引改写会误导 agent 走已被判死的通道(types.ts 契约声明)。
|
|
152
|
+
if (original instanceof ResurrectDeniedError) return original;
|
|
153
|
+
let snap: SubagentRecord | undefined;
|
|
154
|
+
try {
|
|
155
|
+
snap = service.lookupRecordAnyState(id);
|
|
156
|
+
} catch {
|
|
157
|
+
snap = undefined;
|
|
158
|
+
}
|
|
159
|
+
if (!snap) {
|
|
160
|
+
return original instanceof Error ? original : new Error(String(original));
|
|
161
|
+
}
|
|
162
|
+
if (snap.status === "closed") {
|
|
163
|
+
if (snap.closedReason === "cancelled" || snap.closedReason === "user-close") {
|
|
164
|
+
return new Error(
|
|
165
|
+
`subagent ${id} was deliberately closed by user (closedReason: ${snap.closedReason}) — ` +
|
|
166
|
+
`it cannot be messaged or resumed; nothing can reattach to it. ` +
|
|
167
|
+
`Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return new Error(
|
|
171
|
+
`subagent ${id} is ended but reconnectable (closedReason: ${snap.closedReason ?? "unknown"}` +
|
|
172
|
+
`${describeClosedContext(snap)}). Its conversation history is intact at ${snap.sessionFile ?? "(session file unavailable)"}. ` +
|
|
173
|
+
`Recovery: resume from that history with {"action":"fork-from","forkFromParam":{"sourceSubagentId":"${id}"}}, ` +
|
|
174
|
+
`or read key points directly from the session file.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
// running 但未通过 getRecordForAction:记录属于当前进程外的另一个 session 树
|
|
178
|
+
//(主会话重启前的遗留态,或其他并发 pi 进程的活跃 subagent)。无论哪种,历史
|
|
179
|
+
// jsonl 只读安全,fork-from 快照接续均有效。
|
|
180
|
+
return new Error(
|
|
181
|
+
`subagent ${id} is alive but belongs to a different session tree than this one` +
|
|
182
|
+
`${describeClosedContext(snap)}. You cannot message it from here. ` +
|
|
183
|
+
`Recovery: branch from its history with {"action":"fork-from","forkFromParam":{"sourceSubagentId":"${id}"}}` +
|
|
184
|
+
`${snap.sessionFile ? ` (source session: ${snap.sessionFile})` : ``}; otherwise start a new subagent.`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 终态快照的上下文人话短语(仅作补充描述,主分支逻辑在 endedMessageGuard)。 */
|
|
189
|
+
function describeClosedContext(r: SubagentRecord): string {
|
|
190
|
+
switch (r.closedReason) {
|
|
191
|
+
case "parent-shutdown":
|
|
192
|
+
return " — it was disconnected when the previous parent session exited";
|
|
193
|
+
case "parent-fork":
|
|
194
|
+
return " — it was detached when the previous parent session forked";
|
|
195
|
+
case "parent-new":
|
|
196
|
+
return " — it was detached when the previous parent session switched";
|
|
197
|
+
case "disconnected":
|
|
198
|
+
return " — it ended in a previous session (exact cause unknown)";
|
|
199
|
+
default:
|
|
200
|
+
return "";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* [v8.5 B] fork-from 开场引导语框架(prompt 未指定时注入):先从继承的历史重建状态
|
|
206
|
+
* 再继续,防猜。
|
|
207
|
+
*/
|
|
208
|
+
const FORK_FROM_DEFAULT_PROMPT =
|
|
209
|
+
"You are taking over work from a previous subagent whose full conversation history you inherited (--fork). " +
|
|
210
|
+
"First reconstruct state from that history: list what was already done, decided, and left unfinished (a few bullet lines). " +
|
|
211
|
+
"Then continue the remaining work to completion.";
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* [v8.5 B] 有显式接续指令时的包裹框架:指令在前、上下文重建要求在后——指令首见即达,
|
|
215
|
+
* 不湮没在元说明里(弱模型友好)。
|
|
216
|
+
*/
|
|
217
|
+
function wrapForkFromPrompt(prompt: string): string {
|
|
218
|
+
return (
|
|
219
|
+
prompt.trim() +
|
|
220
|
+
"\n\n(You are continuing a previous subagent's inherited conversation via --fork. " +
|
|
221
|
+
"Reconstruct state from that history first — what was done, decided, and remains — then execute the instruction above.)"
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
118
225
|
// ============================================================
|
|
119
226
|
// helpers(模块内)
|
|
120
227
|
// ============================================================
|
|
@@ -154,9 +261,11 @@ export function mapExternalState(status: ExecutionStatus): ExternalState {
|
|
|
154
261
|
}
|
|
155
262
|
|
|
156
263
|
/** SubagentRecord → SubagentListItem(state 四态主字段 + status 调试字段,duration 实时计算)。
|
|
157
|
-
* [v4 A-6] 新增 parent/resumable
|
|
158
|
-
*
|
|
159
|
-
*
|
|
264
|
+
* [v4 A-6] 新增 parent/resumable:parent 从 record.parentRecordId 派生(配合 A-5 直接父
|
|
265
|
+
* 守卫),resumable 从 isResumable 派生(B-1「可续聊」对外表达)。
|
|
266
|
+
* [U3 C-outcome] 新增 outcome 一等终态语义(projectOutcome 唯一出口);closedReason
|
|
267
|
+
* 退出对外 JSON(保留为 record 内部诊断字段),对外成败判读收口到 outcome,消费方
|
|
268
|
+
* 零手写推导(三处同构 switch 已收敛删除)。
|
|
160
269
|
* agent 是 GUI/TUI list 共用的显示名——取 basename 短名(displayAgentName),
|
|
161
270
|
* 完整路径保留在 record.agent(数据层)。 */
|
|
162
271
|
export function recordToListItem(r: SubagentRecord): SubagentListItem {
|
|
@@ -173,7 +282,7 @@ export function recordToListItem(r: SubagentRecord): SubagentListItem {
|
|
|
173
282
|
sessionFile: r.sessionFile,
|
|
174
283
|
parent: r.parentRecordId,
|
|
175
284
|
resumable: isResumable(r),
|
|
176
|
-
|
|
285
|
+
outcome: projectOutcome(r),
|
|
177
286
|
};
|
|
178
287
|
}
|
|
179
288
|
|
|
@@ -232,10 +341,13 @@ export async function startHandler(
|
|
|
232
341
|
subagentId: handle.subagentId,
|
|
233
342
|
sessionFile: handle.sessionFile,
|
|
234
343
|
slug: handle.details.slug,
|
|
344
|
+
// [U1] registry 全等回显:record.model 由 resolved(裁决放行条目)拼接,原样透出。
|
|
345
|
+
model: handle.details.model,
|
|
235
346
|
response: {
|
|
236
347
|
status: "running",
|
|
237
348
|
mode: "background",
|
|
238
349
|
message: BG_MESSAGE,
|
|
350
|
+
notifyContract: NOTIFY_CONTRACT,
|
|
239
351
|
},
|
|
240
352
|
};
|
|
241
353
|
}
|
|
@@ -370,8 +482,16 @@ export async function messageHandler(
|
|
|
370
482
|
);
|
|
371
483
|
const interrupt = input?.interrupt === true;
|
|
372
484
|
|
|
373
|
-
// 归属守卫(决策 3):getRecordForAction 内部校验 rootSessionId
|
|
374
|
-
|
|
485
|
+
// 归属守卫(决策 3):getRecordForAction 内部校验 rootSessionId。
|
|
486
|
+
// [v8.5 A1] 拒绝时经 endedMessageGuard 分流:找不到 → 原错误;user-close/cancelled
|
|
487
|
+
// → 「已主动关闭」;断联/已完成/异归属 → fork-from 可行动指引。仅 message action
|
|
488
|
+
// 升级文案——close/cancel 维持原语义(它们不需要恢复通道)。
|
|
489
|
+
let record: ExecutionRecord;
|
|
490
|
+
try {
|
|
491
|
+
record = service.getRecordForAction(id, { allowReconnect: true });
|
|
492
|
+
} catch (err) {
|
|
493
|
+
throw endedMessageGuard(service, id, err);
|
|
494
|
+
}
|
|
375
495
|
|
|
376
496
|
// SP-5 one-shot upgrade:非 chatMode 的 active record(running/idle)收到 message 时
|
|
377
497
|
// 自动升级为 chatMode,后续走 deliverMessage 统一投递路径(热路径或冷路径 resume)。
|
|
@@ -446,6 +566,140 @@ export async function closeHandler(
|
|
|
446
566
|
return { kind: "close", subagentId: id, response: { closed: true } };
|
|
447
567
|
}
|
|
448
568
|
|
|
569
|
+
// ============================================================
|
|
570
|
+
// fork-from handler([v8.5 B] 断联恢复通道)
|
|
571
|
+
// ============================================================
|
|
572
|
+
|
|
573
|
+
export interface ForkFromHandlerInput {
|
|
574
|
+
sourceSubagentId?: string;
|
|
575
|
+
prompt?: string;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** fork-from 领域对象(adapter 包成 forkFromResponse)。 */
|
|
579
|
+
export type ForkFromHandlerResult = {
|
|
580
|
+
kind: "fork-from";
|
|
581
|
+
/** 新 subagent 的 record id(后续续聊用 action:'message')。 */
|
|
582
|
+
subagentId: string;
|
|
583
|
+
/** 作为 --fork 继承源的旧记录 session 文件。 */
|
|
584
|
+
sourceSessionFile: string;
|
|
585
|
+
response: ForkFromResponse;
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* fork-from action handler:从旧 subagent 的会话历史 spawn 新 id 接续。
|
|
590
|
+
*
|
|
591
|
+
* 用于 subagent 因会话重启/进程退出而断联后的恢复:新进程以 --fork 指向旧 session
|
|
592
|
+
* 文件(copy-on-write 建分支会话),继承全部对话历史;源文件只读不续写。
|
|
593
|
+
* 旧记录本身不动——closed 单向状态机不变量、tryTransition 语义均不触碰。
|
|
594
|
+
*
|
|
595
|
+
* 守卫链(拒绝原因与行动语言对齐现有 MF-4 风格):
|
|
596
|
+
* 1. 本进程内存 running → 还活着,应走 message(防双写同一子 session 文件)
|
|
597
|
+
* 2. 不存在 → 引导 list 确认
|
|
598
|
+
* 3. 异进程活跃(磁盘 .alive pid 存活 / running 快照)→ 别处正跑,不可从此接续
|
|
599
|
+
* (同 id 双写风险;等待其结束或在其所属会话内操作)
|
|
600
|
+
* 4. tombstone/cancelled → 用户主动取消,真没了(不提供接续通道)
|
|
601
|
+
* 5. worktree 记录 → checkout 不可复用,fork 子进程 cwd 会回落主仓破坏隔离
|
|
602
|
+
* (对齐 deliverMessage 的 hadWorktree 守卫语义)
|
|
603
|
+
* 6. 无子 session 文件 → 无历史可继承(entry-only 孤儿:spawn 窗口期中断)
|
|
604
|
+
*
|
|
605
|
+
* @throws Error 各守卫命中 / service.execute 失败(引擎不支持等)
|
|
606
|
+
*/
|
|
607
|
+
export async function forkFromHandler(
|
|
608
|
+
service: SubagentService,
|
|
609
|
+
input: ForkFromHandlerInput | undefined,
|
|
610
|
+
): Promise<ForkFromHandlerResult> {
|
|
611
|
+
const id = input?.sourceSubagentId?.trim();
|
|
612
|
+
if (!id) throw new Error("forkFromParam.sourceSubagentId is required for action:'fork-from'");
|
|
613
|
+
const prompt = input?.prompt?.trim() ?? "";
|
|
614
|
+
const task = prompt ? wrapForkFromPrompt(prompt) : FORK_FROM_DEFAULT_PROMPT;
|
|
615
|
+
|
|
616
|
+
const source = assertAndLookupForkFromSource(service, id);
|
|
617
|
+
|
|
618
|
+
// slug 派生:源 slug + -resumed 后缀(截断到上限)。仅展示标签,不需唯一。
|
|
619
|
+
const baseSlug = (source.slug || source.agent || "resumed").slice(0, SLUG_MAX_LENGTH - "-resumed".length);
|
|
620
|
+
const handle = await service.execute({
|
|
621
|
+
task,
|
|
622
|
+
slug: `${baseSlug}-resumed`,
|
|
623
|
+
forkFromSessionFile: source.sessionFile,
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
kind: "fork-from",
|
|
628
|
+
subagentId: handle.subagentId,
|
|
629
|
+
sourceSessionFile: source.sessionFile,
|
|
630
|
+
response: { newSubagentId: handle.subagentId, sourceSessionFile: source.sessionFile },
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** forkFromHandler 的守卫链(fork-from handler doc 的守卫 1–6 原样提取):按序校验
|
|
635
|
+
* 源记录可接续,命中即抛带行动语言的 Error;全部通过则返回源 SubagentRecord
|
|
636
|
+
* (守卫 6 已保证 sessionFile 非空,返回类型随之收窄)。 */
|
|
637
|
+
function assertAndLookupForkFromSource(service: SubagentService, id: string): SubagentRecord & { sessionFile: string } {
|
|
638
|
+
// 守卫 1:本进程内存 running —— 直接 message 即可,fork-from 会双写其 session 文件。
|
|
639
|
+
if (service.findRecord(id)) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`subagent ${id} is still active in this process — use action:'message' to continue it directly. ` +
|
|
642
|
+
`If you want a parallel branch from its history, close it first (action:'close'), then fork-from.`,
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// 守卫 2:全态查找(内存 archived + 磁盘重建)。
|
|
647
|
+
const source = service.lookupRecordAnyState(id);
|
|
648
|
+
if (!source) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`No subagent record with id "${id}". It may never have existed or been garbage-collected — ` +
|
|
651
|
+
`use action:'list' with includeFinished:true to verify the id.`,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// 守卫 3:异进程活跃(磁盘分支 3 externalInstance = 另一进程的活 pid marker)。
|
|
656
|
+
// 双写防护:fork 虽 copy-on-write(历史 jsonl 只读),但源仍在异进程运行时接续容易
|
|
657
|
+
// 读到半截历史,等它结束再接更安全。判据只认 externalInstance(真实活 pid 探针
|
|
658
|
+
// 命中),不拦 status==='running' 的快照——后者含 B-1 跨重启回退重建的 running 记录
|
|
659
|
+
//(无活 pid,历史已完整落盘),它们正是 endedMessageGuard 指引 fork-from 的目标;
|
|
660
|
+
// 拦了会让 agent 在「建议 fork-from」与「fork-from 拒绝 running」两条错误间死循环。
|
|
661
|
+
if (source.externalInstance !== undefined) {
|
|
662
|
+
throw new Error(
|
|
663
|
+
`subagent ${id} is still running in another process (alive pid marker present). ` +
|
|
664
|
+
`Recovery: wait until it finishes, or operate it in its own session; then retry fork-from.`,
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// 守卫 4:主动告别(cancelled tombstone / user-close 正式关闭)——close 语义无旁路:
|
|
669
|
+
// fork-from 与 message 一致拒绝(guard 一致性规格),文案升级为统一「主动关闭」形态
|
|
670
|
+
// (含 closedReason 显式列入),与 deliverMessage 的 X 分支同语系不同落地(此处强调不可 branch)。
|
|
671
|
+
if (source.status === "closed" && (source.closedReason === "cancelled" || source.closedReason === "user-close")) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
`subagent ${id} was deliberately closed by user (closedReason: ${source.closedReason}) — ` +
|
|
674
|
+
`deliberately-closed records cannot be resumed or branched from; nothing can reattach to them. ` +
|
|
675
|
+
`Recovery: start a fresh subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.`,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// 守卫 5:worktree 记录 —— WorktreeHandle 不可序列化,checkout 已被 reaper/cleanup
|
|
680
|
+
// 回收;fork 子进程若复用旧路径会回落主 repo(破坏文件隔离)。与 deliverMessage 的
|
|
681
|
+
// hadWorktree 守卫同一判据同一理由。
|
|
682
|
+
if (source.worktree === true) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`subagent ${id} was created with worktree isolation; that binding was lost when its parent process ended. ` +
|
|
685
|
+
`Resuming from its history would run outside the original worktree isolation. ` +
|
|
686
|
+
`Recovery: start a new subagent with action:'start' and carry over key findings manually ` +
|
|
687
|
+
`(read ${source.sessionFile ?? "its session file"} if needed).`,
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// 守卫 6:无子 session 文件(entry-born 孤儿:spawn 窗口期中断,从未开跑)。
|
|
692
|
+
const sessionFile = source.sessionFile;
|
|
693
|
+
if (!sessionFile) {
|
|
694
|
+
throw new Error(
|
|
695
|
+
`subagent ${id} has no child session file to inherit from (it never started successfully). ` +
|
|
696
|
+
`Recovery: start a fresh subagent (action:'start') describing the task again.`,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return { ...source, sessionFile };
|
|
701
|
+
}
|
|
702
|
+
|
|
449
703
|
// ============================================================
|
|
450
704
|
// adapter(领域对象 → SubagentToolResult + {content, details})
|
|
451
705
|
// ============================================================
|
|
@@ -459,7 +713,8 @@ type AdapterInput =
|
|
|
459
713
|
| { action: "list"; domain: ListHandlerResult }
|
|
460
714
|
| { action: "cancel"; domain: CancelHandlerResult }
|
|
461
715
|
| { action: "message"; domain: MessageHandlerResult }
|
|
462
|
-
| { action: "close"; domain: CloseHandlerResult }
|
|
716
|
+
| { action: "close"; domain: CloseHandlerResult }
|
|
717
|
+
| { action: "fork-from"; domain: ForkFromHandlerResult };
|
|
463
718
|
|
|
464
719
|
export function adapter(
|
|
465
720
|
input: AdapterInput,
|
|
@@ -471,13 +726,19 @@ export function adapter(
|
|
|
471
726
|
const d = input.domain;
|
|
472
727
|
// MF-3(决策 10 细则 4):LLM content (text) 用 null 瘦身,防诱导 agent 用 read 绕过工具
|
|
473
728
|
// 直接读 session 文件。真实 sessionFile 仅 details 保留(供 GUI/程序化消费)。
|
|
474
|
-
|
|
729
|
+
// [U1] model 为 registry 全等回显(放行即全等)。
|
|
730
|
+
result = { action, subagentId: d.subagentId, sessionFile: null, slug: d.slug, model: d.model, bgResponse: d.response };
|
|
475
731
|
} else if (action === "list") {
|
|
476
732
|
result = { action, subagentId: null, sessionFile: null, listResponse: input.domain.response };
|
|
477
733
|
} else if (action === "cancel") {
|
|
478
734
|
result = { action, subagentId: input.domain.subagentId, sessionFile: null, cancelResponse: input.domain.response };
|
|
479
735
|
} else if (action === "message") {
|
|
480
736
|
result = { action, subagentId: input.domain.subagentId, sessionFile: null, messageResponse: input.domain.response };
|
|
737
|
+
} else if (action === "fork-from") {
|
|
738
|
+
// MF-3 同款瘦身:sourceSessionFile 真实路径只在 forkFromResponse 内层(LLM 接续
|
|
739
|
+
// 不需要拼路径,需要时 list/details 可取);顶层 sessionFile 保持 null 一致性。
|
|
740
|
+
const d = input.domain;
|
|
741
|
+
result = { action, subagentId: d.subagentId, sessionFile: null, forkFromResponse: d.response };
|
|
481
742
|
} else {
|
|
482
743
|
// action === "close"
|
|
483
744
|
result = { action, subagentId: input.domain.subagentId, sessionFile: null, closeResponse: input.domain.response };
|
|
@@ -491,7 +752,7 @@ export function adapter(
|
|
|
491
752
|
let detailsBase: SubagentToolResult = result;
|
|
492
753
|
if (action === "start") {
|
|
493
754
|
const d = input.domain;
|
|
494
|
-
detailsBase = { action: "start", subagentId: d.subagentId, sessionFile: d.sessionFile ?? null, slug: d.slug, bgResponse: d.response };
|
|
755
|
+
detailsBase = { action: "start", subagentId: d.subagentId, sessionFile: d.sessionFile ?? null, slug: d.slug, model: d.model, bgResponse: d.response };
|
|
495
756
|
}
|
|
496
757
|
|
|
497
758
|
// GUI 协议:RPC 模式下附加结构化渲染数据(union 各成员已声明 __gui__?,无需强转)
|
|
@@ -503,7 +764,7 @@ export function adapter(
|
|
|
503
764
|
// reminder 作为第二个 text block(独立追加,不污染 details/JSON schema)。
|
|
504
765
|
// 只有 list 触发——start 的 reminder 已在 BG_MESSAGE 里;cancel 无需。
|
|
505
766
|
const reminder = action === "list"
|
|
506
|
-
? "\n\nReminder: Subagent completion is auto-notified via injected message (
|
|
767
|
+
? "\n\nReminder: Subagent completion is auto-notified via auto-injected message (turn-triggering on idle). Do NOT poll in a loop — there is no poll action. Use action:'list' only when you concretely need state, then continue working or stop." // g4-allow: 契约文案——reminder 字符串描述自动注入通道(triggerTurn 单通道,U2/D5 无 deliverAs),非实际投递调用
|
|
507
768
|
: "";
|
|
508
769
|
|
|
509
770
|
return {
|
|
@@ -548,6 +809,14 @@ export function buildGuiComponent(
|
|
|
548
809
|
items: [{ label: "messaged", value: input.domain.subagentId, severity: "ok" }],
|
|
549
810
|
});
|
|
550
811
|
}
|
|
812
|
+
if (action === "fork-from") {
|
|
813
|
+
return guiComponent("stats-line", {
|
|
814
|
+
items: [
|
|
815
|
+
{ label: "forked-from", value: input.domain.sourceSessionFile },
|
|
816
|
+
{ label: "new subagent", value: input.domain.subagentId, severity: "ok" },
|
|
817
|
+
],
|
|
818
|
+
});
|
|
819
|
+
}
|
|
551
820
|
if (action === "close") {
|
|
552
821
|
return guiComponent("stats-line", {
|
|
553
822
|
items: [{ label: "closed", value: input.domain.subagentId, severity: "warn" }],
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// src/interface/subagent-tool-schema.ts
|
|
2
|
+
//
|
|
3
|
+
// `subagent` 工具的参数 schema 纯常量叶子(零运行时依赖,先例 shared/schema-env.ts)。
|
|
4
|
+
//
|
|
5
|
+
// 抽取自 subagent-tool.ts(跨包契约另一半):subagent-tool 依赖树沉重(pi SDK /
|
|
6
|
+
// handler / render 链),structured-output 侧的跨包契约测试若从它 import schema
|
|
7
|
+
// 会把整条依赖树拖进测试进程。本模块只含 schema 常量,运行时 import 仅
|
|
8
|
+
// typebox(Type 构造)与 pi-ai(StringEnum helper),为 structured-output 侧
|
|
9
|
+
//(及任何消费者)的跨包契约测试提供稳定 import 点。
|
|
10
|
+
//
|
|
11
|
+
// [跨包契约] structured-output 的 cross-package-contract.test.ts 经真实 typebox
|
|
12
|
+
// 编译本 schema 并断言 required/description/enum/pattern 存活——SW 自身测试环境
|
|
13
|
+
// 把 typebox alias 到 mock(丢 options),SO 侧测试以真实构造为对照基准。
|
|
14
|
+
//
|
|
15
|
+
// 层归属:Interface(工具 schema 的家)。SLUG_MAX_LENGTH 权威定义回归 core 侧
|
|
16
|
+
// execution/execute-options-mapper.ts(subagent-core 包抽离 u1-move:core 切面不得
|
|
17
|
+
// 反向 import 壳侧 interface,常量随之内化)——此处 re-export 保持既有 import 路径。
|
|
18
|
+
|
|
19
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
20
|
+
import { type Static, Type } from "typebox";
|
|
21
|
+
|
|
22
|
+
import { THINKING_ORDER } from "@zhushanwen/subagent-core/shared/model-ref.ts";
|
|
23
|
+
|
|
24
|
+
import { SLUG_MAX_LENGTH } from "@zhushanwen/subagent-core/execution/execute-options-mapper.ts";
|
|
25
|
+
|
|
26
|
+
export { SLUG_MAX_LENGTH };
|
|
27
|
+
|
|
28
|
+
// Params schema(跨包契约测试的真实 typebox 校验入口)。
|
|
29
|
+
//
|
|
30
|
+
// action:"start" 的 13 字段(task/slug/agent/model/...)拍平在顶层,不再用 startParam
|
|
31
|
+
// 嵌套容器包。原因:弱模型(GLM/DeepSeek)信任 schema 结构信号 > 文本信号,经常省略
|
|
32
|
+
// startParam 嵌套层把 task/slug 直接平铺到顶层导致调用失败。拍平后 schema 结构与模型
|
|
33
|
+
// 的自然倾向一致,消除这层误用。task/slug 必填性由 startHandler runtime 校验(flat
|
|
34
|
+
// JSON Schema 无法表达「action 条件必填」)。
|
|
35
|
+
//
|
|
36
|
+
// TODO(long-term, option-A): listParam/cancelParam 仍标 Optional 也是 flat JSON Schema
|
|
37
|
+
// 表达「action 分发条件必填」的妥协——长期方案是拆成 3 个独立 tool
|
|
38
|
+
// (subagent_start / subagent_list / subagent_cancel),让每个 tool 的 schema 真实
|
|
39
|
+
// 反映必填性。勿在此基础上继续堆 action 条件逻辑——要加就拆 tool。
|
|
40
|
+
export const SubagentParams = Type.Object({
|
|
41
|
+
action: StringEnum(["start", "list", "cancel", "message", "close", "fork-from"], {
|
|
42
|
+
description: "Operation: 'start' runs a subagent, 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to a running subagent (one-shot subagents are auto-upgraded to conversation mode on first message), 'close' ends a running subagent (conversation-mode or one-shot), 'fork-from' spawns a NEW subagent inheriting an older one's history (recovery for restart-disconnected subagents; the old record is untouched).",
|
|
43
|
+
}),
|
|
44
|
+
// ── action:"start" fields (flattened to top level). task/slug REQUIRED for start. ──
|
|
45
|
+
// Missing/empty task or slug throws at runtime (startHandler).
|
|
46
|
+
// (flat JSON Schema can't express conditional requirement — see file-level TODO.)
|
|
47
|
+
task: Type.Optional(Type.String({
|
|
48
|
+
description: "REQUIRED for action:'start'. The task for the subagent to execute. Throws if missing or whitespace-only.",
|
|
49
|
+
})),
|
|
50
|
+
slug: Type.Optional(Type.String({
|
|
51
|
+
description:
|
|
52
|
+
"REQUIRED for action:'start'. Short label (≤35 chars) for this subagent, e.g. 'fix-login', 'extract-urls'. " +
|
|
53
|
+
"Shown in TUI to distinguish concurrent subagents.",
|
|
54
|
+
maxLength: SLUG_MAX_LENGTH,
|
|
55
|
+
})),
|
|
56
|
+
agent: Type.Optional(Type.String({
|
|
57
|
+
description: 'Agent ref: absolute path to the agent .md file (use <location> from <available_subagents>). If omitted, defaults to "general-purpose" — a generic agent that inherits the main agent\'s model and project context. Do not invent names — only use paths from the injected list.',
|
|
58
|
+
})),
|
|
59
|
+
model: Type.Optional(Type.String({
|
|
60
|
+
description: 'Model override in "provider/modelId" format. CASE-SENSITIVE: the string must equal a registry entry exactly, including letter case (e.g. "zai-coding-cn/GLM-5.3-Flash", NOT "zai-coding-cn/glm-5.3-flash"). A non-exact match is rejected immediately with "Did you mean" suggestions — retry with the exact suggested string; the system never auto-corrects your input. Resolution order (top wins): (1) this param, (2) agent .md frontmatter model, (3) the main agent\'s current model (zero-config default). An explicit model (param or frontmatter) that is missing or unauthorized THROWS — there is no silent fallback to the main model. Omit this param to inherit the main model.',
|
|
61
|
+
})),
|
|
62
|
+
thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
|
|
63
|
+
description: "Thinking depth override (derived from THINKING_ORDER SSOT, includes 'max'). Omit to default to the model's highest available level (not the main agent's level).",
|
|
64
|
+
})),
|
|
65
|
+
skillPath: Type.Optional(Type.String({
|
|
66
|
+
description:
|
|
67
|
+
"Absolute path to a skill directory, injected into the subagent's pi process via --skill " +
|
|
68
|
+
"(e.g. a path under .agents/skills/ already resolved for the caller). Must be an absolute path; " +
|
|
69
|
+
"'..' traversal segments are rejected.",
|
|
70
|
+
pattern: "^/",
|
|
71
|
+
})),
|
|
72
|
+
appendSystemPrompt: Type.Optional(Type.Array(Type.String())),
|
|
73
|
+
schema: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
74
|
+
maxTurns: Type.Optional(Type.Number({
|
|
75
|
+
description: "Turn limit. The subagent is terminated via SIGTERM after maxTurns turn_end events + graceTurns of slack. There is no graceful wrap-up message — the process is killed. 0 or omitted = unlimited.",
|
|
76
|
+
})),
|
|
77
|
+
graceTurns: Type.Optional(Type.Number({
|
|
78
|
+
description: "Extra turns allowed after maxTurns is reached before SIGTERM (default 2). Only meaningful when maxTurns is set.",
|
|
79
|
+
})),
|
|
80
|
+
fork: Type.Optional(Type.Boolean({
|
|
81
|
+
description: "Fork mode: inherit the parent's conversation context. When true, the subagent receives the parent's session file via --fork and builds a branched conversation (it sees prior turns/messages). The subagent still runs in a separate spawned child process (process isolation) — fork is about context inheritance, not process sharing; independent of worktree (file-system isolation, see worktree param). When to use: only when the task extends from the parent and genuinely needs key information from the parent's conversation history that a self-contained task prompt cannot carry — most tasks a plain prompt can describe do NOT need fork, so keep false by default and enable only when the user explicitly asks or the task truly depends on seeing prior turns. Caveat: fork drags in the parent's dispatch records and unrelated task context, polluting the subagent (it cannot tell 'context meant for me' from 'parent dispatching me'); when state lives in an external store the subagent can query (e.g., cw handoff), prefer that over fork.",
|
|
82
|
+
})),
|
|
83
|
+
worktree: Type.Optional(Type.Boolean({
|
|
84
|
+
description: "Worktree isolation: run the subagent in a dedicated git worktree, providing file-system level isolation from the parent session (prevents concurrent file-write conflicts). Independent of fork — worktree may be combined with fork:false (file isolation does not require context inheritance). When to use: parallel development scenarios where multiple agents write files concurrently and need isolated working directories (each gets its own checkout; merge later); leave false for single-agent or read-only tasks.",
|
|
85
|
+
})),
|
|
86
|
+
cwd: Type.Optional(Type.String({
|
|
87
|
+
description: 'Override the working directory for the subagent execution. Must be an absolute path (no "~" shorthand, no relative paths); ".." segments are rejected. Defaults to the parent session\'s cwd.',
|
|
88
|
+
pattern: "^/",
|
|
89
|
+
})),
|
|
90
|
+
conversation: Type.Optional(Type.Boolean({
|
|
91
|
+
description:
|
|
92
|
+
"Enable continuous chat with this subagent. When true, the subagent stays available after each reply — you can send follow-up messages (action:'message') and it keeps the full conversation context across rounds, with no need to re-spawn or re-explain. " +
|
|
93
|
+
"\nUse conversation:true for: multi-round collaboration (iterative review-fix loops, back-and-forth refinement), any task where you expect to send follow-up messages after the initial result. " +
|
|
94
|
+
"\nOmit (or false) for: one-shot tasks — single exploration, lookup, file read, code generation that needs no follow-up. The subagent runs once, notifies on completion, and is cleaned up automatically (default). " +
|
|
95
|
+
"\nFor long-interval collaboration (each round spaced >5min apart), set conversation:true AND increase idleTimeoutMs to avoid premature timeout. " +
|
|
96
|
+
"Cost: a conversation-mode subagent holds resources (memory, and a worktree if enabled) until you explicitly end it with action:'close'. Always close when done.",
|
|
97
|
+
})),
|
|
98
|
+
idleTimeoutMs: Type.Optional(Type.Number({
|
|
99
|
+
description:
|
|
100
|
+
"Idle timeout in milliseconds for conversation-mode subagents. Controls how long an idle subagent (between rounds) stays alive before automatic cleanup. " +
|
|
101
|
+
"Default: 300000 (5min). Override for long-interval collaboration where each round is spaced >5min apart. " +
|
|
102
|
+
"Pass 0 or a negative value to DISABLE idle cleanup entirely (subagent stays alive until explicitly closed). " +
|
|
103
|
+
"Only meaningful with conversation:true; ignored for one-shot subagents.",
|
|
104
|
+
})),
|
|
105
|
+
engine: Type.Optional(StringEnum(["pi", "zcode"], {
|
|
106
|
+
description:
|
|
107
|
+
"Execution engine for this subagent. Omit to inherit the global config. " +
|
|
108
|
+
"Three-layer priority: this parameter > agent .md frontmatter engine > config.json defaultEngine. " +
|
|
109
|
+
"Non-pi engines do not support conversation/fork/worktree (rejected before the subagent is created).",
|
|
110
|
+
})),
|
|
111
|
+
// action:"list" → listParam OPTIONAL (all fields optional, defaults apply). Ignored by other actions.
|
|
112
|
+
listParam: Type.Optional(Type.Object({
|
|
113
|
+
includeFinished: Type.Optional(Type.Boolean({
|
|
114
|
+
description: "Include finished (done/failed/cancelled) records. Default false (running only).",
|
|
115
|
+
})),
|
|
116
|
+
limit: Type.Optional(Type.Number({
|
|
117
|
+
description: "Max items to return. Default 20, clamped to [1, 100].",
|
|
118
|
+
})),
|
|
119
|
+
})),
|
|
120
|
+
// action:"cancel" → cancelParam.subagentId REQUIRED. Throws if missing. Ignored by other actions.
|
|
121
|
+
cancelParam: Type.Optional(Type.Object({
|
|
122
|
+
subagentId: Type.String({
|
|
123
|
+
description: "REQUIRED for action:'cancel'. The subagentId to cancel. Throws if missing. Only background subagents can be cancelled.",
|
|
124
|
+
}),
|
|
125
|
+
})),
|
|
126
|
+
// action:"message" → messageParam.subagentId + text REQUIRED. Any RUNNING subagent works —
|
|
127
|
+
// one-shot subagents are auto-upgraded to conversation mode on first message (SP-5); ended ones throw.
|
|
128
|
+
messageParam: Type.Optional(Type.Object({
|
|
129
|
+
subagentId: Type.String({
|
|
130
|
+
description: "REQUIRED for action:'message'. The subagentId to message (any running subagent; a one-shot subagent is auto-upgraded to conversation mode on first message, so you may also message one-shot subagents that are still running).",
|
|
131
|
+
}),
|
|
132
|
+
text: Type.String({
|
|
133
|
+
description: "REQUIRED for action:'message'. The message to send. Whitespace-only throws.",
|
|
134
|
+
}),
|
|
135
|
+
interrupt: Type.Optional(Type.Boolean({
|
|
136
|
+
description: "If true, interrupt the subagent's current work immediately (in-progress output stops, it switches to your new message). If false (default), the message is queued and processed after the current round completes. When the subagent is idle (between rounds), interrupt has no effect — the message always starts a new round.",
|
|
137
|
+
})),
|
|
138
|
+
})),
|
|
139
|
+
// action:"close" → closeParam.subagentId REQUIRED. Ends a running subagent (conversation-mode
|
|
140
|
+
// or one-shot — closeSubagent behavior split covers both).
|
|
141
|
+
closeParam: Type.Optional(Type.Object({
|
|
142
|
+
subagentId: Type.String({
|
|
143
|
+
description: "REQUIRED for action:'close'. The subagentId to close (any running subagent, conversation-mode or one-shot).",
|
|
144
|
+
}),
|
|
145
|
+
force: Type.Optional(Type.Boolean({
|
|
146
|
+
description: "If true, terminate immediately even if mid-round (in-progress work is lost). If false (default), let the current round finish, then close. When idle, the subagent closes immediately regardless.",
|
|
147
|
+
})),
|
|
148
|
+
})),
|
|
149
|
+
// action:"fork-from" → forkFromParam.sourceSubagentId REQUIRED ([v8.5 B] 断联恢复通道).
|
|
150
|
+
// 从旧 subagent 的会话历史 spawn 新 id:新进程 --fork 指向旧 session 文件(copy-on-write,
|
|
151
|
+
// 源文件只读不续写);旧记录/状态机不动。pi 引擎限定(非 pi 在 execute 层拒绝)。
|
|
152
|
+
forkFromParam: Type.Optional(Type.Object({
|
|
153
|
+
sourceSubagentId: Type.String({
|
|
154
|
+
description: "REQUIRED for action:'fork-from'. The OLD subagentId whose conversation history becomes the inherited context of the new subagent. Works for records disconnected by a session restart or already finished; cancelled/worktree-bound/still-running ones are rejected with guidance.",
|
|
155
|
+
}),
|
|
156
|
+
prompt: Type.Optional(Type.String({
|
|
157
|
+
description: "Continuation instruction for the new subagent (what to do next on top of the inherited history). When omitted, a standard handover frame is injected: reconstruct done/decided/remaining from the inherited history, then continue to completion. Whitespace-only treated as omitted.",
|
|
158
|
+
})),
|
|
159
|
+
})),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/** Params schema 的 Static 投影(消费方经 `Static<typeof SubagentParams>` 使用,见 subagent-tool.ts)。 */
|
|
163
|
+
export type SubagentParamsStatic = Static<typeof SubagentParams>;
|