@zhushanwen/subagent-core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-KXVBIVOU.js +16 -0
- package/dist/chunk-T2SYBW3I.js +24 -0
- package/dist/chunk-VURUAGMM.js +2305 -0
- package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
- package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
- package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
- package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
- package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
- package/dist/execution/engine/engine-discovery-scan.js +20 -0
- package/dist/execution/engine/paths.cjs +6 -23
- package/dist/execution/engine/paths.d.cts +1 -8
- package/dist/execution/engine/paths.d.ts +1 -8
- package/dist/execution/engine/paths.js +1 -1
- package/dist/execution/relay-env.cjs +12 -28
- package/dist/execution/relay-env.d.cts +1 -33
- package/dist/execution/relay-env.d.ts +1 -33
- package/dist/execution/relay-env.js +5 -3
- package/dist/index.cjs +14045 -16059
- package/dist/index.d.cts +1035 -1579
- package/dist/index.d.ts +1035 -1579
- package/dist/index.js +9838 -13800
- package/dist.bundle/index.cjs +28875 -30323
- package/package.json +20 -26
- package/src/__tests__/record-store-last-line.test.ts +16 -7
- package/src/core/error-message.ts +7 -6
- package/src/core/host-services.ts +8 -2
- package/src/core/notify-ports.ts +17 -2
- package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
- package/src/execution/__tests__/delivery-methods.test.ts +179 -237
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -13
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
- package/src/execution/__tests__/gc-timer.test.ts +7 -22
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
- package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
- package/src/execution/__tests__/idle-gc.test.ts +200 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
- package/src/execution/__tests__/record-store.test.ts +60 -6
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
- package/src/execution/__tests__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
- package/src/execution/__tests__/subagent-service.test.ts +2 -2
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
- package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/agent-registry.ts +6 -2
- package/src/execution/dialog-queue.ts +6 -73
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
- package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
- package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
- package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
- package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
- package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
- package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
- package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
- package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
- package/src/execution/engine/__tests__/registry.test.ts +206 -1
- package/src/execution/engine/__tests__/routing.test.ts +97 -0
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
- package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
- package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
- package/src/execution/engine/client/client-options.ts +103 -0
- package/src/execution/engine/client/engine-client.ts +687 -0
- package/src/execution/engine/client/mirror.ts +167 -0
- package/src/execution/engine/client/pid-file.ts +392 -0
- package/src/execution/engine/client/reaper.ts +95 -0
- package/src/execution/engine/client/remote-engine.ts +441 -0
- package/src/execution/engine/client/reverse-router.ts +255 -0
- package/src/execution/engine/common/capability-gate.ts +109 -22
- package/src/execution/engine/common/errors.ts +9 -25
- package/src/execution/engine/common/event-journal.ts +4 -50
- package/src/execution/engine/common/journal-replay.ts +3 -1
- package/src/execution/engine/common/kill-chain.ts +18 -251
- package/src/execution/engine/common/nesting-guard.ts +18 -114
- package/src/execution/engine/common/pool-manager.ts +4 -0
- package/src/execution/engine/common/session-view-service.ts +56 -84
- package/src/execution/engine/config.ts +151 -0
- package/src/execution/engine/d8-compat.ts +346 -0
- package/src/execution/engine/engine-discovery-roots.ts +107 -0
- package/src/execution/engine/engine-discovery-scan.ts +269 -0
- package/src/execution/engine/engine-discovery.ts +30 -7
- package/src/execution/engine/engine-inspect-package.ts +282 -0
- package/src/execution/engine/engine-manifest.ts +244 -0
- package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
- package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
- package/src/execution/engine/host/host-bridge.ts +150 -0
- package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
- package/src/execution/engine/host/pi-host-binding.ts +91 -0
- package/src/execution/engine/host/spawned-children.ts +139 -0
- package/src/execution/engine/host-task-spec.ts +29 -2
- package/src/execution/engine/model-validation.ts +55 -2
- package/src/execution/engine/paths.ts +16 -41
- package/src/execution/engine/port.ts +48 -2
- package/src/execution/engine/registry.ts +153 -34
- package/src/execution/engine/routing.ts +225 -52
- package/src/execution/engine/types.ts +25 -200
- package/src/execution/finalize-record.ts +25 -0
- package/src/execution/idle-gc.ts +89 -14
- package/src/execution/lifecycle-manager.ts +10 -7
- package/src/execution/lifecycle-predicates.ts +10 -8
- package/src/execution/record-store.ts +29 -2
- package/src/execution/relay-env.ts +19 -36
- package/src/execution/round-supervisor/domain.ts +89 -0
- package/src/execution/round-supervisor/index.ts +33 -0
- package/src/execution/round-supervisor/notify-accounting.ts +87 -0
- package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
- package/src/execution/round-supervisor/service-binding.test.ts +661 -0
- package/src/execution/round-supervisor/service-binding.ts +273 -0
- package/src/execution/round-supervisor/supervisor.ts +426 -0
- package/src/execution/session-pending.ts +68 -74
- package/src/execution/settled-watchdog.ts +39 -0
- package/src/execution/subagent-service.ts +746 -400
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/types.ts +41 -117
- package/src/execution/ui-channels.ts +13 -214
- package/src/execution/ui-request-handler-factory.ts +7 -4
- package/src/execution/ui-request-observability.ts +2 -2
- package/src/execution/workflow-state-root.ts +67 -0
- package/src/execution/worktree-git-ops.ts +5 -0
- package/src/execution/worktree-manager.ts +11 -1
- package/src/index.ts +55 -26
- package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +144 -59
- package/src/orchestration/models/types.ts +43 -42
- package/src/shared/zcode-model-ref.ts +31 -0
- package/dist/chunk-43ONBFZX.js +0 -240
- package/dist/chunk-A4IVZWVX.js +0 -31
- package/dist/chunk-APZY4IME.js +0 -27
- package/dist/chunk-V3VHZ2VX.js +0 -59
- package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
- package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
- package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
- package/dist/execution/engine/engines/zcode/constants.js +0 -54
- package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
- package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
- package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
- package/dist/execution/engine/engines/zcode/reader.js +0 -9
- package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
- package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
- package/src/__tests__/session-runner.test.ts +0 -69
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
- package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
- package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
- package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
- package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
- package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
- package/src/execution/__tests__/output-collector.test.ts +0 -469
- package/src/execution/__tests__/pi-invocation.test.ts +0 -166
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
- package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
- package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
- package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
- package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
- package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
- package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
- package/src/execution/__tests__/spawn-args.test.ts +0 -446
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
- package/src/execution/__tests__/spawned-children.test.ts +0 -236
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/stdin-writer.test.ts +0 -464
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -616
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
- package/src/execution/__tests__/turn-limiter.test.ts +0 -65
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/common/schema-emulation.ts +0 -189
- package/src/execution/engine/common/session-view-projection.ts +0 -51
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
- package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
- package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
- package/src/execution/engine/engines/pi/output-collector.ts +0 -317
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
- package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
- package/src/execution/engine/engines/pi/reader.ts +0 -59
- package/src/execution/engine/engines/pi/registration.ts +0 -37
- package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
- package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
- package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
- package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
- package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
- package/src/execution/engine/engines/zcode/connection.ts +0 -598
- package/src/execution/engine/engines/zcode/constants.ts +0 -176
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
- package/src/execution/engine/engines/zcode/parser.ts +0 -102
- package/src/execution/engine/engines/zcode/preparer.ts +0 -235
- package/src/execution/engine/engines/zcode/reader.ts +0 -381
- package/src/execution/engine/engines/zcode/registration.ts +0 -37
- package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
- package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
- package/src/execution/round-settlement.ts +0 -93
- package/src/execution/ui-request-queue.ts +0 -215
- package/src/shared/schema-env.ts +0 -44
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/engines/pi/spawn-event-adapter.ts
|
|
2
|
-
//
|
|
3
|
-
// pi 子进程 stdout JSON 事件流的解析器。Core 叶子原语(仅依赖 types.ts)。
|
|
4
|
-
//
|
|
5
|
-
// spawn 改造的基座模块。session-runner runSpawn 用 `pi --mode rpc` spawn 子进程。
|
|
6
|
-
// RPC mode 不向 stdout 输出 header 行(只有 json/print mode 才输出),故 runSpawn 额外
|
|
7
|
-
// 通过 get_state RPC 握手回填 sessionFile/sessionId。两种 stdout 行形态本模块统一解析:
|
|
8
|
-
// 1. header 行(json/print mode 首行):{ type: "session", id, timestamp, cwd, ... }
|
|
9
|
-
// —— session 元信息,含 session id(RPC mode 不发,靠 get_state 握手替代)
|
|
10
|
-
// 2. 事件行:{ type: "tool_execution_start" | "message_end" | ..., ... }
|
|
11
|
-
// —— 与 in-process session.subscribe 收到的 SdkEvent 同源同构
|
|
12
|
-
//
|
|
13
|
-
// 本模块只做「行 → 分类事件对象」的纯解析,不做累积/翻译(那是 runSpawn +
|
|
14
|
-
// handleSdkEvent 的职责)。刻意保持薄,便于单测。
|
|
15
|
-
//
|
|
16
|
-
// 与 session-runner.handleSdkEvent 的契约:parseSpawnLine 返回 kind:"event" 的
|
|
17
|
-
// event 字段即为 SdkEvent,可直接喂给 handleSdkEvent(事件 type schema 一致,
|
|
18
|
-
// 由 print-mode.ts:106 JSON.stringify(event) 保证同源)。
|
|
19
|
-
|
|
20
|
-
import * as fs from "node:fs";
|
|
21
|
-
import * as path from "node:path";
|
|
22
|
-
|
|
23
|
-
import type { SdkEvent } from "../../../types.ts";
|
|
24
|
-
import { toErrorMessage } from "../../../../core/error-message.ts";
|
|
25
|
-
|
|
26
|
-
/** pi stdout header 行(session 元信息)。type 固定为 "session"。 */
|
|
27
|
-
export interface SpawnSessionHeader {
|
|
28
|
-
readonly type: "session";
|
|
29
|
-
readonly id: string;
|
|
30
|
-
readonly timestamp: string;
|
|
31
|
-
readonly cwd: string;
|
|
32
|
-
readonly parentSession?: string;
|
|
33
|
-
readonly version?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Pi 原生 extension_ui_request 的方法特定字段(按 method 平铺)。
|
|
37
|
-
* 与 Pi rpc-types.ts L230-265 的 RpcExtensionUIRequest 1:1 对应。
|
|
38
|
-
* method 是判别字段;每个变体仅列出该 method 的已知字段(可选字段保持可选)。
|
|
39
|
-
* 未知 method 走 string fallback(保留 raw 字段,避免协议演进时丢字段)。 */
|
|
40
|
-
export type ExtensionUiRequest =
|
|
41
|
-
| { method: "select"; title: string; options: string[]; timeout?: number }
|
|
42
|
-
| { method: "confirm"; title: string; message: string; timeout?: number }
|
|
43
|
-
| { method: "input"; title: string; placeholder?: string; timeout?: number }
|
|
44
|
-
| { method: "editor"; title: string; prefill?: string }
|
|
45
|
-
| { method: "notify"; message: string; notifyType?: "info" | "warning" | "error" }
|
|
46
|
-
| { method: "setStatus"; statusKey: string; statusText: string | undefined }
|
|
47
|
-
| {
|
|
48
|
-
method: "setWidget";
|
|
49
|
-
widgetKey: string;
|
|
50
|
-
widgetLines: string[] | undefined;
|
|
51
|
-
widgetPlacement?: "aboveEditor" | "belowEditor";
|
|
52
|
-
}
|
|
53
|
-
| { method: "setTitle"; title: string }
|
|
54
|
-
| { method: "set_editor_text"; text: string }
|
|
55
|
-
// 未知 method fallback:保留原始字段,避免协议演进时丢信息
|
|
56
|
-
| { method: string; raw: Record<string, unknown> };
|
|
57
|
-
|
|
58
|
-
/** 解析后的 extension_ui_request 顶层形状(type 守卫用)。
|
|
59
|
-
* id 和 method 在顶层,method 特定字段平铺(与 Pi 原生格式一致)。 */
|
|
60
|
-
interface ExtensionUiRequestEnvelope {
|
|
61
|
-
type: "extension_ui_request";
|
|
62
|
-
id: string;
|
|
63
|
-
method: string;
|
|
64
|
-
[key: string]: unknown;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Pi 原生 RPC response 顶层形状(type 守卫用)。
|
|
68
|
-
* 与 Pi rpc-types.ts 的 RpcResponse 一致:type:"response" + command + success。 */
|
|
69
|
-
interface RpcResponseEnvelope {
|
|
70
|
-
type: "response";
|
|
71
|
-
command: string;
|
|
72
|
-
success: boolean;
|
|
73
|
-
id?: string;
|
|
74
|
-
data?: unknown;
|
|
75
|
-
error?: string;
|
|
76
|
-
[key: string]: unknown;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** parseSpawnLine 的分类结果。
|
|
80
|
-
*
|
|
81
|
-
* 关键改动(W1 协议层重写):
|
|
82
|
-
* - extension_ui_request 分支:从 {id, params:Record} 改为 {id, request: ExtensionUiRequest}
|
|
83
|
-
* (request 按 method 平铺,与 Pi rpc-types.ts 1:1)
|
|
84
|
-
* - response 分支:从 {id, result, error} 改为 {id?, command, success, data?, error?}
|
|
85
|
-
* (Pi 原生 RpcResponse 格式,SR-1 根因 1b) */
|
|
86
|
-
export type ParsedSpawnLine =
|
|
87
|
-
| { kind: "header"; header: SpawnSessionHeader }
|
|
88
|
-
| { kind: "event"; event: SdkEvent }
|
|
89
|
-
| {
|
|
90
|
-
kind: "response";
|
|
91
|
-
id?: string;
|
|
92
|
-
command: string;
|
|
93
|
-
success: boolean;
|
|
94
|
-
data?: unknown;
|
|
95
|
-
error?: string;
|
|
96
|
-
}
|
|
97
|
-
| { kind: "extension_ui_request"; id: string; request: ExtensionUiRequest }
|
|
98
|
-
| { kind: "invalid"; raw: string; error: string };
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 判断解析出的 JSON 是否为 header 行(type === "session")。
|
|
102
|
-
* 校验所有必需字段(id/timestamp/cwd),缺任一则不收窄——避免下游
|
|
103
|
-
* deriveSessionFilePath 访问 undefined 字段时抛 TypeError。
|
|
104
|
-
*/
|
|
105
|
-
function isSessionHeader(obj: unknown): obj is SpawnSessionHeader {
|
|
106
|
-
if (typeof obj !== "object" || obj === null) return false;
|
|
107
|
-
const r = obj as Record<string, unknown>;
|
|
108
|
-
return (
|
|
109
|
-
r.type === "session" &&
|
|
110
|
-
typeof r.id === "string" &&
|
|
111
|
-
typeof r.timestamp === "string" &&
|
|
112
|
-
typeof r.cwd === "string"
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 判断解析出的 JSON 是否为 Pi 原生 RPC response。
|
|
118
|
-
*
|
|
119
|
-
* SR-1 根因 1b 修复:旧守卫判 JSON-RPC 2.0(jsonrpc + id + result/error),
|
|
120
|
-
* 但 Pi 实际发 {type:"response", command, success, data?, error?}。
|
|
121
|
-
* 新守卫只认 Pi 原生格式:
|
|
122
|
-
* - type === "response"
|
|
123
|
-
* - command: string(调用的命令名,如 "run_tool")
|
|
124
|
-
* - success: boolean
|
|
125
|
-
* id 可选(通知型 response 无 id)。
|
|
126
|
-
*
|
|
127
|
-
* 旧 JSON-RPC 2.0 response({jsonrpc, id, result})不再被识别 → 落 invalid 分支。
|
|
128
|
-
*/
|
|
129
|
-
function isRpcResponse(obj: unknown): obj is RpcResponseEnvelope {
|
|
130
|
-
if (typeof obj !== "object" || obj === null) return false;
|
|
131
|
-
const r = obj as Record<string, unknown>;
|
|
132
|
-
return (
|
|
133
|
-
r.type === "response" &&
|
|
134
|
-
typeof r.command === "string" &&
|
|
135
|
-
typeof r.success === "boolean"
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 判断解析出的 JSON 是否为 Pi 原生 extension_ui_request。
|
|
141
|
-
*
|
|
142
|
-
* 关键改动(W1):旧守卫判 JSON-RPC 2.0(jsonrpc + method:"extension_ui_request" + params),
|
|
143
|
-
* 但 Pi 实际发平铺格式 {type:"extension_ui_request", id, method, ...method特定字段}。
|
|
144
|
-
* 新守卫:
|
|
145
|
-
* - type === "extension_ui_request"(顶层 type 字段,非 method 字段值)
|
|
146
|
-
* - id: string
|
|
147
|
-
* - method: string(select/confirm/.../set_editor_text 等具体方法名)
|
|
148
|
-
*
|
|
149
|
-
* 删掉 jsonrpc 守卫(Pi 不发 JSON-RPC 2.0 envelope)和 params 守卫(字段平铺,无 params 包裹)。
|
|
150
|
-
* 旧 JSON-RPC 2.0 格式({jsonrpc, method:"extension_ui_request", params})不再被识别。
|
|
151
|
-
*/
|
|
152
|
-
function isExtensionUiRequest(obj: unknown): obj is ExtensionUiRequestEnvelope {
|
|
153
|
-
if (typeof obj !== "object" || obj === null) return false;
|
|
154
|
-
const r = obj as Record<string, unknown>;
|
|
155
|
-
return (
|
|
156
|
-
r.type === "extension_ui_request" &&
|
|
157
|
-
typeof r.id === "string" &&
|
|
158
|
-
typeof r.method === "string"
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** 必填 string 字段提取:类型不符降级空串(字段容错——仍归类已知 method,不丢 method 信息)。 */
|
|
163
|
-
function reqStr(r: Record<string, unknown>, key: string): string {
|
|
164
|
-
const v = r[key];
|
|
165
|
-
return typeof v === "string" ? v : "";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** string[] 字段提取:数组剔除非字符串元素;非数组降级 onMissing(options 传 [],
|
|
169
|
-
* widgetLines 传 undefined——两者对缺失值的载荷语义不同:前者恒挂键,后者键挂值 undefined)。 */
|
|
170
|
-
function strArrayField<T extends string[] | undefined>(
|
|
171
|
-
r: Record<string, unknown>,
|
|
172
|
-
key: string,
|
|
173
|
-
onMissing: T,
|
|
174
|
-
): string[] | T {
|
|
175
|
-
const v = r[key];
|
|
176
|
-
return Array.isArray(v)
|
|
177
|
-
? v.filter((x): x is string => typeof x === "string")
|
|
178
|
-
: onMissing;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** timeout 条件展开:非 number 不挂键(timeout?: number 的字段省略语义)。 */
|
|
182
|
-
function optTimeout(r: Record<string, unknown>): { timeout?: number } {
|
|
183
|
-
return typeof r.timeout === "number" ? { timeout: r.timeout } : {};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** placeholder 条件展开:非 string 不挂键。 */
|
|
187
|
-
function optPlaceholder(r: Record<string, unknown>): { placeholder?: string } {
|
|
188
|
-
return typeof r.placeholder === "string" ? { placeholder: r.placeholder } : {};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** prefill 条件展开:非 string 不挂键。 */
|
|
192
|
-
function optPrefill(r: Record<string, unknown>): { prefill?: string } {
|
|
193
|
-
return typeof r.prefill === "string" ? { prefill: r.prefill } : {};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** notifyType 条件展开:仅接受三种已知枚举值,其余不挂键。 */
|
|
197
|
-
function optNotifyType(
|
|
198
|
-
r: Record<string, unknown>
|
|
199
|
-
): { notifyType?: "info" | "warning" | "error" } {
|
|
200
|
-
const v = r.notifyType;
|
|
201
|
-
return v === "info" || v === "warning" || v === "error" ? { notifyType: v } : {};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** widgetPlacement 条件展开:仅接受两种已知枚举值,其余不挂键。 */
|
|
205
|
-
function optWidgetPlacement(
|
|
206
|
-
r: Record<string, unknown>
|
|
207
|
-
): { widgetPlacement?: "aboveEditor" | "belowEditor" } {
|
|
208
|
-
const v = r.widgetPlacement;
|
|
209
|
-
return v === "aboveEditor" || v === "belowEditor" ? { widgetPlacement: v } : {};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* 从已通过 isExtensionUiRequest 守卫的 envelope 构造 ExtensionUiRequest 变体。
|
|
214
|
-
*
|
|
215
|
-
* 按 method 平铺提取字段(与 Pi rpc-types.ts L230-265 1:1)。已知 method
|
|
216
|
-
* 走对应变体;未知 method 走 string fallback(保留 raw 字段全量字段)。
|
|
217
|
-
*
|
|
218
|
-
* 字段类型容错:协议字段类型不符(如 options 非数组)时,该字段降级为空数组/undefined
|
|
219
|
-
*(数组类字段做元素类型过滤,剔除非字符串元素),仍归类为已知 method(不丢 method 信息)。
|
|
220
|
-
* 字段级提取/条件展开由上方字段 helper 承担,本函数只做 method 分发。
|
|
221
|
-
*/
|
|
222
|
-
function buildExtensionUiRequest(env: ExtensionUiRequestEnvelope): ExtensionUiRequest {
|
|
223
|
-
const r: Record<string, unknown> = env;
|
|
224
|
-
switch (env.method) {
|
|
225
|
-
case "select":
|
|
226
|
-
return {
|
|
227
|
-
method: "select",
|
|
228
|
-
title: reqStr(r, "title"),
|
|
229
|
-
options: strArrayField(r, "options", []),
|
|
230
|
-
...optTimeout(r),
|
|
231
|
-
};
|
|
232
|
-
case "confirm":
|
|
233
|
-
return {
|
|
234
|
-
method: "confirm",
|
|
235
|
-
title: reqStr(r, "title"),
|
|
236
|
-
message: reqStr(r, "message"),
|
|
237
|
-
...optTimeout(r),
|
|
238
|
-
};
|
|
239
|
-
case "input":
|
|
240
|
-
return {
|
|
241
|
-
method: "input",
|
|
242
|
-
title: reqStr(r, "title"),
|
|
243
|
-
...optPlaceholder(r),
|
|
244
|
-
...optTimeout(r),
|
|
245
|
-
};
|
|
246
|
-
case "editor":
|
|
247
|
-
return {
|
|
248
|
-
method: "editor",
|
|
249
|
-
title: reqStr(r, "title"),
|
|
250
|
-
...optPrefill(r),
|
|
251
|
-
};
|
|
252
|
-
case "notify":
|
|
253
|
-
return {
|
|
254
|
-
method: "notify",
|
|
255
|
-
message: reqStr(r, "message"),
|
|
256
|
-
...optNotifyType(r),
|
|
257
|
-
};
|
|
258
|
-
case "setStatus":
|
|
259
|
-
return {
|
|
260
|
-
method: "setStatus",
|
|
261
|
-
statusKey: reqStr(r, "statusKey"),
|
|
262
|
-
statusText: typeof r.statusText === "string" ? r.statusText : undefined,
|
|
263
|
-
};
|
|
264
|
-
case "setWidget":
|
|
265
|
-
return {
|
|
266
|
-
method: "setWidget",
|
|
267
|
-
widgetKey: reqStr(r, "widgetKey"),
|
|
268
|
-
widgetLines: strArrayField(r, "widgetLines", undefined),
|
|
269
|
-
...optWidgetPlacement(r),
|
|
270
|
-
};
|
|
271
|
-
case "setTitle":
|
|
272
|
-
return {
|
|
273
|
-
method: "setTitle",
|
|
274
|
-
title: reqStr(r, "title"),
|
|
275
|
-
};
|
|
276
|
-
case "set_editor_text":
|
|
277
|
-
return {
|
|
278
|
-
method: "set_editor_text",
|
|
279
|
-
text: reqStr(r, "text"),
|
|
280
|
-
};
|
|
281
|
-
default:
|
|
282
|
-
// 未知 method:保留全部原始字段,避免协议演进时丢信息
|
|
283
|
-
return { method: env.method, raw: r };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* 解析 pi stdout 的一行。
|
|
289
|
-
*
|
|
290
|
-
* @param line stdout 的一行(不含换行符;空行返回 null)
|
|
291
|
-
* @returns 分类结果,或 null(空行/仅空白)
|
|
292
|
-
*
|
|
293
|
-
* 分类规则(判定顺序关键,见 W1 bug 修复):
|
|
294
|
-
* - 空白行 → null(pi 可能输出空行,跳过)
|
|
295
|
-
* - 合法 JSON + type:"session" + 必需字段 → header
|
|
296
|
-
* - 合法 JSON + type:"extension_ui_request" + id + method → extension_ui_request
|
|
297
|
-
* (必须在 event 分支之前,否则被 typeof obj.type===string 吞为 event)
|
|
298
|
-
* - 合法 JSON + type:"response" + command + success → response
|
|
299
|
-
* - 合法 JSON + 有 type 字段 → event(SdkEvent,type schema 由调用方校验)
|
|
300
|
-
* - 合法 JSON 但无 type → invalid
|
|
301
|
-
* - 非法 JSON → invalid(记录 error,不抛——单行损坏不应中断整个流)
|
|
302
|
-
*
|
|
303
|
-
* 容错原则:stdout 是流式输出,任何单行解析失败都不应中断进程。invalid 归类
|
|
304
|
-
* 由 runSpawn 决定是否记录/忽略,本函数不丢弃信息。
|
|
305
|
-
*/
|
|
306
|
-
export function parseSpawnLine(line: string): ParsedSpawnLine | null {
|
|
307
|
-
const trimmed = line.trim();
|
|
308
|
-
if (trimmed === "") return null;
|
|
309
|
-
|
|
310
|
-
let obj: unknown;
|
|
311
|
-
try {
|
|
312
|
-
obj = JSON.parse(trimmed);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
return {
|
|
315
|
-
kind: "invalid",
|
|
316
|
-
raw: trimmed,
|
|
317
|
-
error: toErrorMessage(err),
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (isSessionHeader(obj)) {
|
|
322
|
-
return { kind: "header", header: obj };
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// extension_ui_request:必须在 event 分支之前判定(W1 判定顺序 bug 修复)。
|
|
326
|
-
// 原因:extension_ui_request 也有 type 字段,若 event 分支(typeof obj.type===string)
|
|
327
|
-
// 在前,会被当 event 静默吞掉。现在先于 event 判定,命中后按 method 构造 request。
|
|
328
|
-
if (isExtensionUiRequest(obj)) {
|
|
329
|
-
return { kind: "extension_ui_request", id: obj.id, request: buildExtensionUiRequest(obj) };
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// RPC response:Pi 原生格式 {type:"response", command, success, data?, error?}
|
|
333
|
-
if (isRpcResponse(obj)) {
|
|
334
|
-
return {
|
|
335
|
-
kind: "response",
|
|
336
|
-
...(typeof obj.id === "string" ? { id: obj.id } : {}),
|
|
337
|
-
command: obj.command,
|
|
338
|
-
success: obj.success,
|
|
339
|
-
...(obj.data !== undefined ? { data: obj.data } : {}),
|
|
340
|
-
...(typeof obj.error === "string" ? { error: obj.error } : {}),
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// 事件行:必须有 type 字段(SdkEvent 契约)
|
|
345
|
-
if (
|
|
346
|
-
typeof obj === "object" &&
|
|
347
|
-
obj !== null &&
|
|
348
|
-
typeof (obj as Record<string, unknown>).type === "string"
|
|
349
|
-
) {
|
|
350
|
-
return { kind: "event", event: obj as SdkEvent };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
kind: "invalid",
|
|
355
|
-
raw: trimmed,
|
|
356
|
-
error: "JSON missing string 'type' field",
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* 从已收集的 header + 事件流推导子进程的 session 文件路径。
|
|
362
|
-
*
|
|
363
|
-
* pi session 文件命名规则(session-manager.ts:846):
|
|
364
|
-
* `${fileTimestamp}_${sessionId}.jsonl`
|
|
365
|
-
* 其中 fileTimestamp = header.timestamp.replace(/[:.]/g, "-")
|
|
366
|
-
* (ISO 时间里的冒号和点替换为连字符,如 2026-07-03T12-34-56-789Z)。
|
|
367
|
-
*
|
|
368
|
-
* @param header spawn 解析出的 session header
|
|
369
|
-
* @param sessionDir 指定的 session 目录(spawn 时通过 --session-dir 传入)
|
|
370
|
-
* @returns 推导的 session 文件绝对路径(不保证文件存在——子进程可能仍在写入)
|
|
371
|
-
*
|
|
372
|
-
* 注意:这是基于 pi 命名规则的推导。runSpawn 会在进程结束后校验文件实际存在,
|
|
373
|
-
* 不存在时用 sessionId 后缀匹配兜底。
|
|
374
|
-
*/
|
|
375
|
-
export function deriveSessionFilePath(
|
|
376
|
-
header: SpawnSessionHeader,
|
|
377
|
-
sessionDir: string,
|
|
378
|
-
): string {
|
|
379
|
-
const fileTimestamp = header.timestamp.replace(/[:.]/g, "-");
|
|
380
|
-
return `${sessionDir}/${fileTimestamp}_${header.id}.jsonl`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* 在 sessionDir 中按 sessionId 后缀匹配查找实际存在的 session 文件。
|
|
385
|
-
*
|
|
386
|
-
* 兜底机制:deriveSessionFilePath 基于命名规则推导,若 pi 命名规则变化导致
|
|
387
|
-
* 推导路径不存在,用 sessionId 作为后缀匹配实际文件。sessionId 是 header
|
|
388
|
-
* 的稳定标识,文件名必含它,匹配可靠。
|
|
389
|
-
*
|
|
390
|
-
* @returns 匹配到的文件绝对路径,或 undefined(无匹配)
|
|
391
|
-
*/
|
|
392
|
-
export function findSessionFileByHeaderId(
|
|
393
|
-
sessionDir: string,
|
|
394
|
-
sessionId: string,
|
|
395
|
-
): string | undefined {
|
|
396
|
-
try {
|
|
397
|
-
const files = fs.readdirSync(sessionDir);
|
|
398
|
-
// 文件名格式:<fileTimestamp>_<sessionId>.jsonl,sessionId 是后缀(去掉 .jsonl)
|
|
399
|
-
const match = files.find((f) => f.endsWith(`_${sessionId}.jsonl`));
|
|
400
|
-
return match ? path.join(sessionDir, match) : undefined;
|
|
401
|
-
} catch {
|
|
402
|
-
return undefined;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/engines/pi/stdin-writer.ts
|
|
2
|
-
//
|
|
3
|
-
// 向 rpc 子进程 stdin 写入命令的 helper 集合。
|
|
4
|
-
//
|
|
5
|
-
// pi --mode rpc 通过 stdin 的 JSON RpcCommand / RpcExtensionUIResponse 驱动:
|
|
6
|
-
// - extension_ui_response(主进程回答子进程的 UI 请求,如 ask_user)
|
|
7
|
-
// - prompt(驱动子进程开始处理 task)
|
|
8
|
-
// 两者共用 child.stdin.write + 背压检查,提取到此模块统一维护。
|
|
9
|
-
|
|
10
|
-
import type { ChildProcess } from "node:child_process";
|
|
11
|
-
import * as crypto from "node:crypto";
|
|
12
|
-
|
|
13
|
-
import { getLogger } from "../../../../core/logger.ts";
|
|
14
|
-
|
|
15
|
-
import type { UiResponse } from "../../../dialog-queue.ts";
|
|
16
|
-
import { toErrorMessage } from "../../../../core/error-message.ts";
|
|
17
|
-
|
|
18
|
-
const logger = getLogger("subagents");
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* EPIPE 连续失败计数器(record.id → 连续 EPIPE 次数)。
|
|
22
|
-
*
|
|
23
|
-
* [v4 A-1] 计数器从 subagent-service.ts 迁移到本模块(stdin 错误域)。迁移原因:
|
|
24
|
-
* session-runner.ts 已被 subagent-service.ts import(runSpawn 等),若 session-runner
|
|
25
|
-
* 反向 import subagent-service 的 helper 会循环依赖。stdin-writer 不 import 两者,
|
|
26
|
-
* 是 stdin 域中立模块,epipe 计数属 stdin 错误域,职责合理。
|
|
27
|
-
*
|
|
28
|
-
* 错误处理两半面共用本计数器(合并计数,防 spawn→EPIPE→resume 死循环):
|
|
29
|
-
* ① 同步 write 抛错(writeStdinLine throw → PiEngine.deliverPrompt catch 递增)
|
|
30
|
-
* ② 异步 stream 'error' event(session-runner.ts child.stdin.on('error') 递增)
|
|
31
|
-
*/
|
|
32
|
-
const epipeConsecutiveFailures = new Map<string, number>();
|
|
33
|
-
|
|
34
|
-
/** 连续 EPIPE 失败阈值:达到即不再尝试 resume(避免无限 spawn → EPIPE → resume 循环)。 */
|
|
35
|
-
export const EPIPE_FAILURE_THRESHOLD = 2;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 递增 recordId 的 EPIPE 连续失败计数,返回递增后的新计数。
|
|
39
|
-
* 同步 writeStdinLine throw 与异步 child.stdin 'error' event 共用此入口(合并计数)。
|
|
40
|
-
*/
|
|
41
|
-
export function recordEpipeFailure(recordId: string): number {
|
|
42
|
-
const count = (epipeConsecutiveFailures.get(recordId) ?? 0) + 1;
|
|
43
|
-
epipeConsecutiveFailures.set(recordId, count);
|
|
44
|
-
return count;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 成功写入时清零某 record 的 EPIPE 连续失败计数(热路径成功 → 重置,允许后续重新计数)。 */
|
|
48
|
-
export function clearEpipeFailure(recordId: string): void {
|
|
49
|
-
epipeConsecutiveFailures.delete(recordId);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** dispose 时清空所有 EPIPE 计数(防跨 session 泄漏)。 */
|
|
53
|
-
export function resetAllEpipeFailures(): void {
|
|
54
|
-
epipeConsecutiveFailures.clear();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 按 UiResponse 形状构造 Pi 原生 extension_ui_response 并写 stdin。
|
|
59
|
-
*
|
|
60
|
-
* SR-5:ack(fire-and-forget)不写 stdin——Pi 对 fire-and-forget method 不期待响应,
|
|
61
|
-
* 写入会触发协议错配。其他三种 shape(value/confirmed/cancelled)按对应字段写。
|
|
62
|
-
*
|
|
63
|
-
* [R1] 背压检查:child.stdin.write 返回 false 时记 warn(不阻塞,内核缓冲会随后排空)。
|
|
64
|
-
* [R2] 序列化在本函数内逐分支完成。JSON.stringify 可能抛错(out.value 含循环引用 /
|
|
65
|
-
* BigInt 等不可序列化结构),try/catch 降级为 cancelled——宁可取消单次 dialog 也不让
|
|
66
|
-
* 父进程崩溃(UI 请求通道不应被脏数据拖垮)。
|
|
67
|
-
*
|
|
68
|
-
* @param child 子进程(stdin 写入响应)
|
|
69
|
-
* @param id 请求 id(关联 response)
|
|
70
|
-
* @param out UiResponse({value}/{confirmed}/{cancelled}/{ack})
|
|
71
|
-
* @param signal abort signal(已 aborted 时跳过写入)
|
|
72
|
-
*/
|
|
73
|
-
export function respond(child: ChildProcess, id: string, out: UiResponse, signal?: AbortSignal): void {
|
|
74
|
-
if (signal?.aborted) return;
|
|
75
|
-
let line: string | undefined;
|
|
76
|
-
try {
|
|
77
|
-
if ("value" in out) line = JSON.stringify({ type: "extension_ui_response", id, value: out.value });
|
|
78
|
-
else if ("confirmed" in out) line = JSON.stringify({ type: "extension_ui_response", id, confirmed: out.confirmed });
|
|
79
|
-
else if ("cancelled" in out) line = JSON.stringify({ type: "extension_ui_response", id, cancelled: true });
|
|
80
|
-
} catch (err) {
|
|
81
|
-
// [R2] out.value 含循环引用/BigInt 等不可序列化结构——降级 cancelled,避免父进程崩溃。
|
|
82
|
-
logger.warn(`[subagents] JSON.stringify failed for ui response ${id}, degrading to cancelled`, {
|
|
83
|
-
detail: toErrorMessage(err),
|
|
84
|
-
});
|
|
85
|
-
line = JSON.stringify({ type: "extension_ui_response", id, cancelled: true });
|
|
86
|
-
}
|
|
87
|
-
// ack: fire-and-forget,不写 stdin(SR-5)
|
|
88
|
-
if (line === undefined) return;
|
|
89
|
-
writeStdinLine(child, line, `ui response for request ${id}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* spawn 后向 rpc 子进程 stdin 写 prompt 命令,驱动 agent 开始处理 task。
|
|
94
|
-
*
|
|
95
|
-
* pi 的 runRpcMode 只通过 stdin RpcCommand 驱动——positional task arg / -p flag
|
|
96
|
-
* 在 rpc mode 下被 resolveAppMode 无视。必须在 spawn 后主动喂 prompt 命令,
|
|
97
|
-
* 否则子进程阻塞等 stdin、永不进入推理(totalTokens 恒 0)。
|
|
98
|
-
*
|
|
99
|
-
* 时机:spawn 后立即写。stdin 是 pipe,内核缓冲保证数据不丢;
|
|
100
|
-
* pi 在 await rebindSession() 后才挂 stdin reader(rpc-mode.ts:778-781),
|
|
101
|
-
* reader 处理 prompt 时 session 已就绪。
|
|
102
|
-
*
|
|
103
|
-
* [V2 决策 3] chatMode 续聊热路径用 prompt + streamingBehavior 统一投递(替代 steer/followUp
|
|
104
|
-
* 命令),pi 权威裁决 busy/idle(设计 F3/F4):busy 时 followUp 入队/steer 抢占;idle 时
|
|
105
|
-
* streamingBehavior 被忽略、直接开新 turn。不传 streamingBehavior(首帧 prompt / 旧调用方)
|
|
106
|
-
* 行为完全不变——向后兼容。
|
|
107
|
-
*
|
|
108
|
-
* @param child 子进程(stdin 写入 prompt 命令)
|
|
109
|
-
* @param task 完整 task 文本(含 schema 指令)
|
|
110
|
-
* @param options.streamingBehavior V2 统一投递语义:`"followUp"`(排队,当前轮后处理)/ `"steer"`(抢占,立即中断 streaming)。
|
|
111
|
-
* 省略时不写入该字段(首帧 prompt / 非 chatMode 调用方,行为不变)。
|
|
112
|
-
*/
|
|
113
|
-
export function sendPromptCommand(
|
|
114
|
-
child: ChildProcess,
|
|
115
|
-
task: string,
|
|
116
|
-
options?: { streamingBehavior?: "followUp" | "steer" },
|
|
117
|
-
): void {
|
|
118
|
-
if (!child.stdin || child.stdin.destroyed) return;
|
|
119
|
-
const payload: Record<string, unknown> = {
|
|
120
|
-
id: crypto.randomUUID(),
|
|
121
|
-
type: "prompt",
|
|
122
|
-
message: task,
|
|
123
|
-
};
|
|
124
|
-
if (options?.streamingBehavior) {
|
|
125
|
-
payload.streamingBehavior = options.streamingBehavior;
|
|
126
|
-
}
|
|
127
|
-
writeStdinLine(child, JSON.stringify(payload), "prompt command");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* [review 修复 round2] 已删除 sendFollowUpCommand / sendSteerCommand——deliverToRunning
|
|
132
|
-
* (busy 投递死机制,S2 删除)的唯一消费方移除后两函数成零生产调用方死 export。
|
|
133
|
-
* busy 投递语义由 prompt + streamingBehavior(sendPromptCommand 的
|
|
134
|
-
* options.streamingBehavior: "followUp" | "steer",pi 权威裁决)承担。
|
|
135
|
-
*/
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* 向 rpc 子进程 stdin 写 get_state 命令,查询 sessionFile/sessionId。
|
|
139
|
-
*
|
|
140
|
-
* FR-4: RPC get_state 握手。当 stdout header 未携带 sessionFile 时,
|
|
141
|
-
* 通过此命令向子进程查询当前 session 状态。子进程收到后返回
|
|
142
|
-
* {type:"response", command:"get_state", success:true, data:{sessionFile, sessionId}}。
|
|
143
|
-
*
|
|
144
|
-
* @param child 子进程(stdin 写入 get_state 命令)
|
|
145
|
-
* @returns 请求 id(用于匹配 response)
|
|
146
|
-
*/
|
|
147
|
-
export function sendGetStateCommand(child: ChildProcess): string {
|
|
148
|
-
const id = crypto.randomUUID();
|
|
149
|
-
const command = JSON.stringify({
|
|
150
|
-
id,
|
|
151
|
-
type: "get_state",
|
|
152
|
-
});
|
|
153
|
-
writeStdinLine(child, command, "get_state command");
|
|
154
|
-
return id;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* 向子进程 stdin 写一行(自动补换行),带背压检查 + EPIPE 检测。
|
|
159
|
-
*
|
|
160
|
-
* [R1] write 返回 false 时记 warn(不阻塞,内核缓冲会随后排空)。
|
|
161
|
-
* [R3] write 抛 EPIPE / ERR_STREAM_DESTROYED 时 throw 含 EPIPE 关键词的 Error,
|
|
162
|
-
* 让上层(PiEngine.deliverPrompt)能捕获并自动转冷路径 resume。
|
|
163
|
-
* stdin 已关闭/销毁时跳过——respond 已检查 signal,sendPromptCommand 已检查 destroyed。
|
|
164
|
-
*
|
|
165
|
-
* @param child 子进程
|
|
166
|
-
* @param line JSON 行(不含换行)
|
|
167
|
-
* @param warnTag warn 日志的语义标记
|
|
168
|
-
* @throws Error 含 "EPIPE" 关键词——stdin 管道已断(子进程已退出 / stdin 被销毁)
|
|
169
|
-
*/
|
|
170
|
-
function writeStdinLine(child: ChildProcess, line: string, warnTag: string): void {
|
|
171
|
-
if (!child.stdin || child.stdin.destroyed) return;
|
|
172
|
-
try {
|
|
173
|
-
const ok = child.stdin.write(line + "\n");
|
|
174
|
-
if (!ok) logger.warn(`[subagents] stdin backpressure on ${warnTag}`);
|
|
175
|
-
} catch (err) {
|
|
176
|
-
// [R3] EPIPE / ERR_STREAM_DESTROYED:stdin 管道已断,子进程已退出或 stdin 被销毁。
|
|
177
|
-
// [v4 A-1] 错误处理两半面:① 同步 write 抛错(本 catch)——上层(PiEngine.deliverPrompt)捕获后
|
|
178
|
-
// 调 recordEpipeFailure 递增计数;② 异步 stream 'error' event(session-runner.ts 的
|
|
179
|
-
// child.stdin.on('error') listener)。两者共用本模块 export 的 recordEpipeFailure
|
|
180
|
-
// helper 合并计数(防 spawn→EPIPE→resume 死循环)。
|
|
181
|
-
// throw 让上层(PiEngine.deliverPrompt)捕获并自动转冷路径 resume + 消息重放。
|
|
182
|
-
if (
|
|
183
|
-
err !== null &&
|
|
184
|
-
typeof err === "object" &&
|
|
185
|
-
"code" in err &&
|
|
186
|
-
((err as NodeJS.ErrnoException).code === "EPIPE" ||
|
|
187
|
-
(err as NodeJS.ErrnoException).code === "ERR_STREAM_DESTROYED")
|
|
188
|
-
) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
`[subagents] EPIPE on stdin write (${warnTag}): pipe broken, child process likely exited. ` +
|
|
191
|
-
`Recovery: treat as dead process and resume via cold path.`,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
// 非 EPIPE 错误(不应发生,但兜底降级为 warn 不崩溃)
|
|
195
|
-
logger.warn(`[subagents] unexpected stdin write error on ${warnTag}`, {
|
|
196
|
-
detail: toErrorMessage(err),
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/engines/pi/temp-prompt.ts
|
|
2
|
-
//
|
|
3
|
-
// 将 agent systemPrompt 写入临时文件,供 pi CLI --append-system-prompt 使用。
|
|
4
|
-
// Core 叶子原语(node 内置 + 同目录 bestEffort 清理 helper)。注意 bestEffort
|
|
5
|
-
// 经 extension-logger 有到 pi 的传递依赖(仅日志通路,无 pi 运行时调用)。
|
|
6
|
-
//
|
|
7
|
-
// pi CLI 的 --append-system-prompt 接受文件路径(非内联字符串),故 spawn 前
|
|
8
|
-
// 需把 systemPrompt 落盘。每次调用创建唯一临时目录,用完由 runSpawn 清理。
|
|
9
|
-
//
|
|
10
|
-
// 移植自 nicobailon subagent example,去掉 withFileMutationQueue(目录已唯一,
|
|
11
|
-
// 无并发写入风险,queue 冗余)。
|
|
12
|
-
|
|
13
|
-
import * as fs from "node:fs";
|
|
14
|
-
import * as os from "node:os";
|
|
15
|
-
import * as path from "node:path";
|
|
16
|
-
|
|
17
|
-
import { bestEffort } from "../../../best-effort.ts";
|
|
18
|
-
|
|
19
|
-
/** 临时 prompt 文件创建结果。dir 供调用方清理。 */
|
|
20
|
-
export interface TempPromptFile {
|
|
21
|
-
/** 临时目录绝对路径(mkdtemp 创建,调用方负责删除)。 */
|
|
22
|
-
dir: string;
|
|
23
|
-
/** prompt 文件绝对路径(dir 下)。 */
|
|
24
|
-
filePath: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 将 systemPrompt 写入临时文件。
|
|
29
|
-
*
|
|
30
|
-
* @param agentName agent 名称(用于文件名,非法字符替换为 _)
|
|
31
|
-
* @param prompt systemPrompt 全文
|
|
32
|
-
* @returns 临时文件信息(dir + filePath)
|
|
33
|
-
*
|
|
34
|
-
* 安全:文件权限 0o600(仅 owner 读写),防止其他用户读取 prompt 内容。
|
|
35
|
-
* 目录用 mkdtemp 保证唯一,无需额外锁。
|
|
36
|
-
*/
|
|
37
|
-
export async function writePromptToTempFile(
|
|
38
|
-
agentName: string,
|
|
39
|
-
prompt: string,
|
|
40
|
-
): Promise<TempPromptFile> {
|
|
41
|
-
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-"));
|
|
42
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
43
|
-
const filePath = path.join(dir, `prompt-${safeName}.md`);
|
|
44
|
-
await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
45
|
-
return { dir, filePath };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 删除临时 prompt 目录(含文件)。best-effort,失败不抛。
|
|
50
|
-
*
|
|
51
|
-
* runSpawn 的 finally 调用,确保临时文件不泄漏。子进程可能仍持有文件句柄
|
|
52
|
-
* (--append-system-prompt 读取后即释放),rm 用 recursive 容错。
|
|
53
|
-
*/
|
|
54
|
-
export async function cleanupTempPrompt(file: TempPromptFile): Promise<void> {
|
|
55
|
-
try {
|
|
56
|
-
await fs.promises.rm(file.dir, { recursive: true, force: true });
|
|
57
|
-
} catch (err) {
|
|
58
|
-
// best-effort:临时文件泄漏不影响功能,OS tmpdir 清理机制兜底;失败经
|
|
59
|
-
// bestEffort 记 debug 日志(对齐 idle-gc / finalize-record 的清理惯例)
|
|
60
|
-
bestEffort(err, `cleanup temp prompt dir ${file.dir}`);
|
|
61
|
-
}
|
|
62
|
-
}
|