@zhushanwen/subagent-core 0.7.1 → 0.9.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/dist/{chunk-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
- package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
- package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
- package/dist/execution/engine/engine-discovery-scan.cjs +325 -154
- package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
- package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
- package/dist/execution/engine/engine-discovery-scan.js +1 -1
- package/dist/execution/relay-env.js +5 -4
- package/dist/index.cjs +9799 -7954
- package/dist/index.d.cts +1896 -1360
- package/dist/index.d.ts +1896 -1360
- package/dist/index.js +8952 -7513
- package/dist.bundle/index.cjs +7897 -6054
- package/package.json +5 -5
- package/src/__tests__/manifest-store.test.ts +16 -15
- package/src/__tests__/record-store-last-line.test.ts +12 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
- package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
- package/src/core/logger.ts +1 -1
- package/src/execution/__tests__/alive-store.test.ts +117 -1
- package/src/execution/__tests__/batch-finalized.test.ts +206 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
- package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
- package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
- package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
- package/src/execution/__tests__/delivery-methods.test.ts +90 -176
- package/src/execution/__tests__/dialog-queue.test.ts +70 -3
- package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
- package/src/execution/__tests__/execute-nesting.test.ts +8 -5
- package/src/execution/__tests__/execution-record.test.ts +8 -148
- package/src/execution/__tests__/finalize-record.test.ts +295 -129
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
- package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
- package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
- package/src/execution/__tests__/idle-gc.test.ts +60 -0
- package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
- package/src/execution/__tests__/model-config-service.test.ts +111 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
- package/src/execution/__tests__/nested-visibility.test.ts +13 -1
- package/src/execution/__tests__/notify-ledger.test.ts +42 -1
- package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
- package/src/execution/__tests__/record-binding.test.ts +613 -0
- package/src/execution/__tests__/record-origin.test.ts +318 -0
- package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
- package/src/execution/__tests__/record-store.test.ts +72 -54
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
- package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
- package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
- package/src/execution/__tests__/run-orchestration.test.ts +199 -0
- package/src/execution/__tests__/session-file-gc.test.ts +35 -4
- package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
- package/src/execution/__tests__/state-marker.test.ts +288 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
- package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
- package/src/execution/__tests__/subagent-service.test.ts +17 -215
- package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
- package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
- package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
- package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
- package/src/execution/alive-store.ts +37 -28
- package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
- package/src/execution/config.ts +1 -1
- package/src/execution/conversation-continuation.ts +541 -0
- package/src/execution/dialog-queue.ts +60 -12
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
- package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
- package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
- package/src/execution/engine/__tests__/registry.test.ts +0 -1
- package/src/execution/engine/__tests__/routing.test.ts +0 -1
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
- package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
- package/src/execution/engine/client/engine-client.ts +111 -26
- package/src/execution/engine/client/mirror.ts +9 -0
- package/src/execution/engine/client/remote-engine.ts +52 -34
- package/src/execution/engine/client/reverse-router.ts +6 -27
- package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
- package/src/execution/engine/common/capability-gate.ts +27 -0
- package/src/execution/engine/common/data-dir.ts +3 -2
- package/src/execution/engine/common/errors.ts +1 -1
- package/src/execution/engine/common/journal-wiring.ts +18 -9
- package/src/execution/engine/common/run-signals.ts +83 -0
- package/src/execution/engine/d8-compat.ts +1 -1
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
- package/src/execution/engine/host/host-bridge.ts +26 -41
- package/src/execution/engine/host/pi-host-binding.ts +5 -5
- package/src/execution/engine/host/spawned-children.ts +1 -1
- package/src/execution/engine/inflight-snapshot.ts +92 -0
- package/src/execution/engine/port.ts +52 -54
- package/src/execution/engine/types.ts +11 -14
- package/src/execution/execution-record.ts +15 -47
- package/src/execution/finalize-record.ts +94 -197
- package/src/execution/idle-gc.ts +9 -3
- package/src/execution/lifecycle-manager.ts +25 -361
- package/src/execution/lifecycle-predicates.ts +22 -19
- package/src/execution/manifest-store.ts +40 -42
- package/src/execution/model-config-service.ts +1 -1
- package/src/execution/notifier.ts +39 -1
- package/src/execution/notify-host.ts +13 -4
- package/src/execution/path-encoding.ts +6 -0
- package/src/execution/record-entry.ts +20 -2
- package/src/execution/record-store.ts +925 -160
- package/src/execution/relay-env.ts +8 -3
- package/src/execution/round-supervisor/domain.ts +17 -5
- package/src/execution/round-supervisor/service-binding.test.ts +89 -53
- package/src/execution/round-supervisor/service-binding.ts +71 -21
- package/src/execution/round-supervisor/supervisor.ts +23 -9
- package/src/execution/service/record-access.ts +547 -0
- package/src/execution/service/record-lifecycle.ts +542 -0
- package/src/execution/service/run-orchestration.ts +1647 -0
- package/src/execution/service/service-bootstrap.ts +111 -0
- package/src/execution/service/service-constants.ts +27 -0
- package/src/execution/service/session-baselines.ts +401 -0
- package/src/execution/service/sync-collect-domain.ts +442 -0
- package/src/execution/service/workflow-dispatch.ts +567 -0
- package/src/execution/session-file-gc.ts +11 -8
- package/src/execution/session-reconstructor.ts +44 -1
- package/src/execution/sessions-index.ts +20 -3
- package/src/execution/settled-watchdog.ts +86 -43
- package/src/execution/state-marker.ts +516 -0
- package/src/execution/stream-sink.ts +1 -1
- package/src/execution/subagent-actions-core.ts +53 -31
- package/src/execution/subagent-service.ts +643 -2955
- package/src/execution/subprocess-agent-runner.ts +46 -323
- package/src/execution/types.ts +60 -43
- package/src/execution/ui-request-handler-factory.ts +5 -0
- package/src/execution/worktree-manager.ts +1 -1
- package/src/execution/worktree-registry.ts +12 -8
- package/src/index.ts +16 -5
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
- package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
- package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
- package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
- package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +1 -1
- package/src/orchestration/models/ports.ts +20 -13
- package/src/orchestration/models/types.ts +11 -13
- package/src/orchestration/run-snapshot.ts +18 -22
- package/src/orchestration/worker-message-pump.ts +33 -57
- package/src/shared/atomic-write.ts +10 -8
- package/dist/chunk-T2SYBW3I.js +0 -24
- package/dist/engine-discovery-scan-ocpM8FmI.d.cts +0 -1608
- package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
- package/src/execution/__tests__/finalized-marker.test.ts +0 -104
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
- package/src/execution/__tests__/tombstone-store.test.ts +0 -73
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
- package/src/execution/finalized-marker.ts +0 -70
- package/src/execution/tombstone-store.ts +0 -72
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// [H3/R6] SubagentService 壳的支撑文件:聚合面接口类型声明(SubagentQueries /
|
|
2
|
+
// SubagentChatActions / SubagentServiceInit)+ 进程单例访问器族(SERVICE_SLOT_KEY /
|
|
3
|
+
// getServiceSlot / getSubagentService / setSubagentService / createSubagentService)。
|
|
4
|
+
// R6 前同居壳文件(impl-plan §2 R6 行),外移达成 G1 壳终态(类体 ≤500 折算 +
|
|
5
|
+
// 单类成员 ≤40)。符号体自壳逐字节搬运,仅 import 通道新设。
|
|
6
|
+
//
|
|
7
|
+
// 方向纪律(scripts/check-subagent-service-boundary.mjs R6 扩向守卫):
|
|
8
|
+
// - 壳→本文件:仅 type-only(接口类型标注——编译后擦除,不成值环)
|
|
9
|
+
// - 本文件→壳:仅 SubagentService 类值 import(createSubagentService 构造依赖,
|
|
10
|
+
// 设计 v4 明文「bootstrap 必须 new SubagentService」;壳对本文件零 re-export,
|
|
11
|
+
// 防壳↔bootstrap 值环——壳侧 re-export 即成环)
|
|
12
|
+
// - 聚合→本文件:仅 type-only(聚合不消费装配工厂)
|
|
13
|
+
// - 本文件→聚合:无(现状零依赖)
|
|
14
|
+
|
|
15
|
+
import type { UiRequestHandler } from "../dialog-queue.ts";
|
|
16
|
+
import type { ModelConfigService } from "../model-config-service.ts";
|
|
17
|
+
import type { StatusFilter } from "../record-store.ts";
|
|
18
|
+
import { SubagentService } from "../subagent-service.ts";
|
|
19
|
+
import type { ExecutionRecord, RecordSnapshot, SubagentRecord } from "../types.ts";
|
|
20
|
+
|
|
21
|
+
/** [D4 查询面聚合] 读模型轴(record 快照读取 + store 订阅)——Service 上的
|
|
22
|
+
* `service.queries` 消费面。变化轴:改查询投影 / 过滤 / 订阅语义,只动 queries 组;
|
|
23
|
+
* Service 本体保留编排核(execute/executeAndAwait/cancel)与生命周期面。 */
|
|
24
|
+
export interface SubagentQueries {
|
|
25
|
+
/** 按 id 查内存 running record 的只读快照(G3-002 修复)。不存在返回 undefined。 */
|
|
26
|
+
findRecord(id: string): RecordSnapshot | undefined;
|
|
27
|
+
/** [v8.5 A1/B] 全态查找:任意状态 × 任意归属的 record 快照(message 拒绝文案分流
|
|
28
|
+
* 与 fork-from 源解析共用)。id 在内存与磁盘均不存在返回 undefined。 */
|
|
29
|
+
lookupRecordAnyState(id: string): SubagentRecord | undefined;
|
|
30
|
+
/** 合并内存 + 磁盘 record(/subagents list + tool list 消费,按 rootSessionId 过滤)。
|
|
31
|
+
* [H2 W1] includeWorkflow 缺省 false = 过滤 origin==="workflow"(D1 投影过滤①③④);
|
|
32
|
+
* true = 排查通道全量。 */
|
|
33
|
+
collectRecords(limit: number, statusFilter?: StatusFilter, includeWorkflow?: boolean): SubagentRecord[];
|
|
34
|
+
/** [H2 W1] 按 workflow run id 列 record(内存 ∪ 磁盘重建 ∪ manifest 口径,不过滤
|
|
35
|
+
* origin——W2 run 视图进度 / W3 下钻消费)。 */
|
|
36
|
+
collectRecordsByParentRunId(parentRunId: string, limit: number): SubagentRecord[];
|
|
37
|
+
/** [perf] 单 record 详情懒加载(全量:eventLog/displayItems/result/turns/tokens)。 */
|
|
38
|
+
getFullRecord(id: string): SubagentRecord | undefined;
|
|
39
|
+
/** 订阅 store 变更(widget/list requestRender)。返回取消订阅。 */
|
|
40
|
+
onChange(listener: () => void): () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** [D4 对话 action 面聚合] chat 域 message/close action 轴(M2-B3,原 Service 同节三方法)
|
|
44
|
+
* ——Service 上的 `service.chatActions` 消费面。变化轴:改对话域归属校验 / close 分流 /
|
|
45
|
+
投递编排,只动 chatActions 组。 */
|
|
46
|
+
export interface SubagentChatActions {
|
|
47
|
+
/** 按 id 查 record 并做归属校验(message/close action 的统一入口)。 */
|
|
48
|
+
getRecordForAction(id: string, opts?: { allowReconnect?: boolean }): ExecutionRecord;
|
|
49
|
+
/** close action 的统一行为分流(running 子态 × force)。 */
|
|
50
|
+
closeSubagent(record: ExecutionRecord, force: boolean): Promise<void>;
|
|
51
|
+
/** chatMode 统一投递入口(message action → ConversationContinuation.onMessage,
|
|
52
|
+
* [H1 U6] interrupt 参数随 D2 打断统一语义退役)。 */
|
|
53
|
+
deliverChatMessage(record: ExecutionRecord, text: string): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Service 构造参数(进程级)。
|
|
58
|
+
*
|
|
59
|
+
* @experimental execution 运行时面(设计 docs/design/subagent-core-sink-design.md §3.3 D6):
|
|
60
|
+
* 一个 minor 周期内允许签名微调,稳定后转常规 semver 承诺。
|
|
61
|
+
*/
|
|
62
|
+
export interface SubagentServiceInit {
|
|
63
|
+
cwd: string;
|
|
64
|
+
/** 配置/模型域 Service(execute 内部调其 resolveModel)。 */
|
|
65
|
+
modelService: ModelConfigService;
|
|
66
|
+
/** 缓存的主 session file 获取函数(fork source 解析用)。 */
|
|
67
|
+
getMainSessionFile?: () => string | undefined;
|
|
68
|
+
/** W2: UI 请求处理回调(ask_user 扩展)。
|
|
69
|
+
* 签名见 dialog-queue.ts UiRequestHandler:接收 UiRequest,返回 UiResponse。 */
|
|
70
|
+
uiRequestHandler?: UiRequestHandler;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── 进程单例访问器 ────────────────────────────────────
|
|
74
|
+
// globalThis[Symbol.for] 防 jiti 路径不同致单例分裂。详见 docs/standards.md §7.5。
|
|
75
|
+
const SERVICE_SLOT_KEY = Symbol.for("@zhushanwen/pi-subagents.service");
|
|
76
|
+
|
|
77
|
+
type ServiceSlot = { current: SubagentService | null };
|
|
78
|
+
|
|
79
|
+
function getServiceSlot(): ServiceSlot {
|
|
80
|
+
let slot = Reflect.get(globalThis, SERVICE_SLOT_KEY) as ServiceSlot | undefined;
|
|
81
|
+
if (!slot) {
|
|
82
|
+
slot = { current: null };
|
|
83
|
+
Reflect.set(globalThis, SERVICE_SLOT_KEY, slot);
|
|
84
|
+
}
|
|
85
|
+
return slot;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 获取进程单例。session_start 前为 null。 */
|
|
89
|
+
export function getSubagentService(): SubagentService | null {
|
|
90
|
+
return getServiceSlot().current;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 设置进程单例(session_start 首次创建时)。 */
|
|
94
|
+
export function setSubagentService(service: SubagentService): void {
|
|
95
|
+
getServiceSlot().current = service;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* [U10① D6] 第三宿主最小构造入口:仅凭参数注入构造 SubagentService(无全局查找)。
|
|
100
|
+
*
|
|
101
|
+
* 构造依赖(modelService / getMainSessionFile / uiRequestHandler)全部经 init
|
|
102
|
+
* 参数注入;本工厂是 `new SubagentService(init)` 的薄包装,不读也不写
|
|
103
|
+
* getSubagentService/setSubagentService 的全局槽位——session_start 单例流程
|
|
104
|
+
* 行为零改动,宿主自持实例时用本工厂。构造内部行为与直接 new 逐字等价。
|
|
105
|
+
*
|
|
106
|
+
* @experimental execution 运行时面(设计 docs/design/subagent-core-sink-design.md §3.3 D6):
|
|
107
|
+
* 一个 minor 周期内允许签名微调,稳定后转常规 semver 承诺。
|
|
108
|
+
*/
|
|
109
|
+
export function createSubagentService(init: SubagentServiceInit): SubagentService {
|
|
110
|
+
return new SubagentService(init);
|
|
111
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// [H3/R6] SubagentService 域常量 SSOT(零依赖叶子文件)。
|
|
2
|
+
//
|
|
3
|
+
// 落点决策(impl-plan §2 R6 允许 service-bootstrap.ts 或独立常量文件,实施取独立
|
|
4
|
+
// 文件):常量消费方含四个聚合 + 壳,若并入 service-bootstrap.ts 会制造
|
|
5
|
+
// 「聚合 → bootstrap → 壳(SubagentService 值边)→ 聚合」三节点模块环(运行时虽
|
|
6
|
+
// 延迟求值安全,但违背依赖方向清晰性);常量域(数据叶子)与 bootstrap 域(类型
|
|
7
|
+
// 声明 + 装配工厂)变化轴正交,独立成零 import 叶子文件后守卫方向规则无需特判。
|
|
8
|
+
//
|
|
9
|
+
// 归一来源(两笔偏差债 R6 兑现):
|
|
10
|
+
// - D-R4-4:PRIORITY_BACKGROUND / MS_PER_SECOND / SECONDS_PER_MINUTE 原在
|
|
11
|
+
// run-orchestration.ts 与 workflow-dispatch.ts 各自重复声明(R4 预授权零 import
|
|
12
|
+
// 约束下的值语义纯量),本单元归一。
|
|
13
|
+
// - D-R3-2:ENV_SELF_RECORD_ID 原声明在 session-baselines.ts,record-access.ts
|
|
14
|
+
// 的单向 import 登记为 R5 守卫台账合法边①,本单元归位后该边删除。
|
|
15
|
+
|
|
16
|
+
/** background 优先级(保留 priority 排序机制,单一值)。 */
|
|
17
|
+
export const PRIORITY_BACKGROUND = 1000;
|
|
18
|
+
|
|
19
|
+
/** 时间换算常数(settled watchdog 分钟数展示用;与 session-runner 同名常量同语义)。 */
|
|
20
|
+
export const MS_PER_SECOND = 1000;
|
|
21
|
+
export const SECONDS_PER_MINUTE = 60;
|
|
22
|
+
|
|
23
|
+
/** 子进程自身 record id 的跨进程身份贯穿 env 名(父进程 spawn 子进程时注入,子进程
|
|
24
|
+
* initSession / recoverOrphansIfRootProcess 读取)。env 族机制全貌与兄弟常量
|
|
25
|
+
* (ENV_ROOT_SESSION_ID / ENV_DEPTH / ENV_ROOT_CWD,消费主体在聚合内)见
|
|
26
|
+
* session-baselines.ts。 */
|
|
27
|
+
export const ENV_SELF_RECORD_ID = "PI_SUBAGENT_SELF_RECORD_ID";
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// [H3/R1] SessionBaselines 聚合(域 #2:session 注入 + ALS/嵌套身份基线)——自
|
|
2
|
+
// SubagentService 上帝类 strangler 抽取的首个聚合(设计
|
|
3
|
+
// docs/design/subagent-service-decomposition.md §2.1 域 #2 / §3.3 D4;字段归属以
|
|
4
|
+
// r0-inventory.md 清单①为准,含 impl-plan 漏列的 uiObservability/execNesting/forkDepthAls)。
|
|
5
|
+
//
|
|
6
|
+
// 单一职责:session 级基线状态(pi 句柄 / session 身份 / fork 深度 ALS / exec 嵌套
|
|
7
|
+
// ALS / UI handler 面)的**唯一宿主与唯一写者**。壳(subagent-service.ts)经同名
|
|
8
|
+
// 私有 getter 透传读路径(strangler 转发壳),对外方法签名零变化。
|
|
9
|
+
//
|
|
10
|
+
// [R1 打样模式——R2-R4 复用]
|
|
11
|
+
// 1. 依赖注入形态:deps 全晚绑定闭包(构造期零求值)——D4 late-bound getter
|
|
12
|
+
// `() => ({pi, disposed})` 形态的全 deps 推广。聚合持过期引用会破 session 复活
|
|
13
|
+
// (dispose → initSession 翻转 disposed 旗标),一切运行时可变态必须断言/调用时现读。
|
|
14
|
+
// 2. 转发壳写法:壳保留同名私有 getter(字段面)与同名方法(方法面)单行转发;
|
|
15
|
+
// 聚合公共面 = 壳转发面 + 测试改写后的聚合路径,两面同源零漂移。
|
|
16
|
+
// 3. 跨聚合边收敛:他域直写本域字段的写点收敛为显式接口方法(本聚合:
|
|
17
|
+
// disposeSessionUi ← 壳 dispose 直写 uiRequestHandler/uiObservability,清单① C-3);
|
|
18
|
+
// 本域直写他域字段的写点收敛为 deps 显式回调(resetSettledRescan ← initSession
|
|
19
|
+
// 直写 settledRescanState,清单① C-2;R2 抽取后回调改指其聚合显式接口)。
|
|
20
|
+
// 4. 深绑测试改写:ServiceInternals / Reflect.get(service, "字段") 改经
|
|
21
|
+
// `baselines` 聚合路径(断言对象与强度不变,路径对齐终态结构——见
|
|
22
|
+
// __tests__ 各改写文件头注释)。
|
|
23
|
+
|
|
24
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
|
+
|
|
26
|
+
import { getLogger } from "../../core/logger.ts";
|
|
27
|
+
|
|
28
|
+
import type { ExtensionMode } from "../host-mode.ts";
|
|
29
|
+
import type { DialogGlobalQueue, UiRequestHandler } from "../dialog-queue.ts";
|
|
30
|
+
import { ExecutionNestingContext } from "../engine/common/nesting-guard.ts";
|
|
31
|
+
import { setHostUiRequestEndpoint } from "../engine/host/host-ui-endpoint.ts";
|
|
32
|
+
import type { PiLike } from "../notify-host.ts";
|
|
33
|
+
import type { StreamSink } from "../stream-sink.ts";
|
|
34
|
+
import { UiRequestObservability } from "../ui-request-observability.ts";
|
|
35
|
+
// [R6/D-R3-2] ENV_SELF_RECORD_ID 因跨聚合消费(record-access)归位常量叶子文件,
|
|
36
|
+
// 本聚合 initSession 消费改经 import(聚合→支撑文件方向合法)。
|
|
37
|
+
import { ENV_SELF_RECORD_ID } from "./service-constants.ts";
|
|
38
|
+
|
|
39
|
+
const logger = getLogger("subagents");
|
|
40
|
+
|
|
41
|
+
/** 跨进程身份贯穿的 env 名(父进程 spawn 子进程时注入,子进程 initSession 读取)。
|
|
42
|
+
* 仿照 PI_SUBAGENT_FORK_DEPTH 机制,让递归 subagent 的身份(rootSessionId / parentRecordId / depth)
|
|
43
|
+
* 跨进程传递,使主进程 /subagents 能看到完整递归树(设计见 docs/design/recursive-subagent-visibility.md)。
|
|
44
|
+
* 语义:env 描述「子进程自己的身份」,不是父的身份(决策 1)。
|
|
45
|
+
* [MF-3] 第 4 个 env:真 ROOT 的 cwd(PI_SUBAGENT_ROOT_CWD)。worktree 模式下子进程 spawn cwd =
|
|
46
|
+
* checkout 路径,若按各自 cwd 编码落盘目录,深层 record 写到 enc(worktree) 段、ROOT 磁盘重建
|
|
47
|
+
* 扫不到 → 全树可见性深度 ≥ 2 断裂。子进程经本 env 拿 ROOT cwd,sessions 与 records 两套目录
|
|
48
|
+
* 统一编码在 enc(ROOT cwd) 段(与身份贯穿同构,见 session-runner 注入点)。
|
|
49
|
+
* [R1] 常量 SSOT 随消费主体(initSession/initExecContextBaseline/rootCwd 推导)自壳文件
|
|
50
|
+
* 迁入本聚合并 export——壳经 import 消费(壳→聚合正向合法),禁聚合→壳反向 import(D4)。
|
|
51
|
+
* [R6/D-R3-2] ENV_SELF_RECORD_ID 因 record-access 跨聚合消费归位 service-constants.ts
|
|
52
|
+
* (消费主体单一且在本聚合时留驻,跨聚合时迁叶子文件——消除 R5 台账合法边①)。 */
|
|
53
|
+
export const ENV_ROOT_SESSION_ID = "PI_SUBAGENT_ROOT_SESSION_ID";
|
|
54
|
+
export const ENV_DEPTH = "PI_SUBAGENT_DEPTH";
|
|
55
|
+
export const ENV_ROOT_CWD = "PI_SUBAGENT_ROOT_CWD";
|
|
56
|
+
|
|
57
|
+
/** dispose 后注入的 stub UI 请求 handler。
|
|
58
|
+
*
|
|
59
|
+
* [背景] Pi 单进程 session 串行接管。session A shutdown 时 SIGTERM 子进程后、
|
|
60
|
+
* 子进程彻底 close 前(pi 子进程 trap SIGTERM 做 graceful shutdown,窗口几十~几百 ms),
|
|
61
|
+
* 子进程的 trailing extension_ui_request 仍可能被父进程 pump 解析,调到 A 的 handler 闭包。
|
|
62
|
+
* 若 dispose 不清 uiRequestHandler,旧 handler 闭包仍持有 A 的 ctx,触发
|
|
63
|
+
* inproc UI 请求队列(已删) 的 catch 分支打 `[subagents] uiRequestHandler threw` 误导性
|
|
64
|
+
* logger.error(看起来像 bug,实际是预期竞态;三层兜底已确保功能正确)。
|
|
65
|
+
*
|
|
66
|
+
* stub 始终返回 {cancelled:true},不调 ctx.ui、不捕获任何 ctx,让 trailing ui_request
|
|
67
|
+
* 干净降级为 cancelled(等价于子进程主动取消)。
|
|
68
|
+
*
|
|
69
|
+
* 不置 undefined —— 那会让 trailing ui_request 走 inproc UI 请求队列(已删) 的 handler-missing
|
|
70
|
+
* 分支触发 notifyMissingHandlerGlobal warn,噪声性质从 threw-error 变 missing-handler,
|
|
71
|
+
* 没真正解决。
|
|
72
|
+
*
|
|
73
|
+
* [R1] 自壳文件迁入(消费点 = disposeSessionUi + 壳 dispose 的应答端 stub 替换)。 */
|
|
74
|
+
export const disposedUiRequestStub: UiRequestHandler = () => Promise.resolve({ cancelled: true });
|
|
75
|
+
|
|
76
|
+
/** session_start 注入参数(session 级)。
|
|
77
|
+
* [R1] 接口本体自壳文件迁入(唯一消费者 = SessionBaselines.initSession);壳经类型
|
|
78
|
+
* 别名 re-export 保持对外符号面(SubagentServiceSessionInit 仍从 subagent-service.ts
|
|
79
|
+
* 导出,消费方零改动)。 */
|
|
80
|
+
export interface SubagentServiceSessionInit {
|
|
81
|
+
pi: PiLike;
|
|
82
|
+
sessionId: string;
|
|
83
|
+
/** 主 session 文件路径(session_start 解析后直传)。
|
|
84
|
+
* [E2E 实测] 不能经闭包缓存(getCachedMainSessionFile)读:jiti 多实例分裂下闭包
|
|
85
|
+
* 变量不跨实例共享,恢复逻辑读到的是滞后一个事件的值(读到未 flush 的新 session
|
|
86
|
+
* ENOENT 路径,entry-born 孤儿整段漏判)。 */
|
|
87
|
+
mainSessionFile?: string;
|
|
88
|
+
/** UI streaming sink(ctx.ui.setWidget),用于 background text_delta 转发。 */
|
|
89
|
+
streamSink?: StreamSink;
|
|
90
|
+
/** 主进程运行模式(W4 守卫:headless 不注入 ask_user RPC 提示词)。
|
|
91
|
+
* initSession 读取后存入 this.sessionMode,buildSessionRunnerContext 透传给 session-runner。 */
|
|
92
|
+
mode?: ExtensionMode;
|
|
93
|
+
/** UI 请求 handler(session 级覆盖进程级)。
|
|
94
|
+
* [D4-④ UI 接线外提] 本字段是 handler 的唯一注入入口(原 setUiRequestHandler 方法已删)。
|
|
95
|
+
* 三态语义:undefined = 不动(保留进程级构造/上次值,供不注入 handler 的调用方);
|
|
96
|
+
* null = 显式清空(承载原 setUiRequestHandler(undefined) 语义——headless 的
|
|
97
|
+
* createUiRequestHandlerForMode 返回 undefined 时壳侧传 null);值 = 注入并重置
|
|
98
|
+
* 缺失告警去重。 */
|
|
99
|
+
uiRequestHandler?: UiRequestHandler | null;
|
|
100
|
+
/** L2 跨子进程全局 dialog 串行队列(进程单例)。子进程退出时经引擎镜像层
|
|
101
|
+
* (SpawnedChildrenMirror → notifyChildProcessExited)取消该 pid 的挂起请求(SR-4)。 */
|
|
102
|
+
dialogQueue?: DialogGlobalQueue;
|
|
103
|
+
/** [竞态修复] 主 agent 是否空闲查询(ctx.isIdle),透传给 notifier 的 flush isIdle gate。
|
|
104
|
+
* 避免 background 完成通知在 agent_end→finishRun 窗口里走错 sendMessage 分支丢失。
|
|
105
|
+
* 可选:未注入时 notifier flush 不 gate(原行为)。 */
|
|
106
|
+
isIdle?: () => boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* [R1 打样模式 1] 聚合协作 deps——**全部晚绑定闭包,构造期零求值**。
|
|
111
|
+
*
|
|
112
|
+
* 窄结构类型只声明聚合真实消费的通道(打样:deps 面 = 依赖最小化声明,不整实例注入)。
|
|
113
|
+
* 三类成员:
|
|
114
|
+
* - 断言状态 getter(readAssertState):D4 形态 `() => ({pi, disposed})`——pi 在
|
|
115
|
+
* initSession 时点晚绑定注入、disposed 随壳 dispose/revive 翻转,构造期注值必持
|
|
116
|
+
* 过期引用破 session 复活,必须断言时现读。
|
|
117
|
+
* - 复活/重置回调(reviveDisposed / resetSettledRescan):initSession 对壳旗标与
|
|
118
|
+
* 他域字段(#5 SyncCollect 的 settledRescanState,清单① C-2)的写点显式化——
|
|
119
|
+
* R2 抽取后回调改指其聚合显式接口,聚合间零直写(G2)。
|
|
120
|
+
* - 跨域编排回调(getStore/getNotifyHost/recoverOrphans/bootRoundSupervisor/
|
|
121
|
+
* runPendingReconcileSweep):initSession 复活后的跨域编排时序(R3/R4 域)以回调
|
|
122
|
+
* 注入,聚合→壳零 import(D4);R3/R4 抽取后同点改指聚合显式接口。
|
|
123
|
+
*/
|
|
124
|
+
export interface SessionBaselinesDeps {
|
|
125
|
+
/** [D4 late-bound getter] assertReady 断言状态快照源(pi 运行时注入 + 声明周期 disposed 旗标)。 */
|
|
126
|
+
readonly readAssertState: () => { pi: PiLike | null; disposed: boolean };
|
|
127
|
+
/** session 复活:壳 `_disposed` 置 false(dispose 的逆操作,/resume /fork /new 后)。 */
|
|
128
|
+
readonly reviveDisposed: () => void;
|
|
129
|
+
/** [C-2 显式回调] settled 重扫状态重置(壳 #5 SyncCollect 字段置 null;R2 改指聚合显式接口)。 */
|
|
130
|
+
readonly resetSettledRescan: () => void;
|
|
131
|
+
/** RecordStore 窄门面(setPi 同步注入 + revive;store 为 #1 留壳共享依赖)。 */
|
|
132
|
+
readonly getStore: () => { setPi(pi: PiLike): void; revive(): void };
|
|
133
|
+
/** NotifyHost 窄门面(revive;通知面 #1 留壳)。 */
|
|
134
|
+
readonly getNotifyHost: () => { revive(): void };
|
|
135
|
+
/** [跨域编排回调] 孤儿终态恢复(#3 RecordLifecycle 域方法;R3 改指聚合显式接口)。 */
|
|
136
|
+
readonly recoverOrphans: () => void;
|
|
137
|
+
/** [跨域编排回调] 轮次监督器 boot 分区(#14 协作面;R4 改指聚合显式接口)。 */
|
|
138
|
+
readonly bootRoundSupervisor: () => void;
|
|
139
|
+
/** [跨域编排回调] 注册对账 sweep(#14 service-binding 模块函数 + #18 finalize 委托闭包,壳装配)。 */
|
|
140
|
+
readonly runPendingReconcileSweep: () => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 构造期静态初值(进程级;与晚绑定 deps 分离——语义不同轴)。 */
|
|
144
|
+
export interface SessionBaselinesCtorInit {
|
|
145
|
+
/** 宿主进程 cwd(rootCwd env 推导的兜底值,见 rootCwd 字段注释 [MF-3])。 */
|
|
146
|
+
cwd: string;
|
|
147
|
+
/** 进程级 UI 请求 handler 初值(session 级覆盖走 initSession.uiRequestHandler)。 */
|
|
148
|
+
uiRequestHandler?: UiRequestHandler;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 域 #2 聚合:session 注入 + ALS/嵌套身份基线(R1 自 SubagentService 抽取)。
|
|
153
|
+
*
|
|
154
|
+
* session_start:
|
|
155
|
+
* 4. service.initSession({pi, sessionId}) ← 壳转发到本聚合
|
|
156
|
+
*
|
|
157
|
+
* 字段所有权(r0-inventory 清单① #13-#25 全部 13 个):本聚合唯一写者;壳经
|
|
158
|
+
* getter 只读透传。assertReady 断言状态经 deps.readAssertState 现读(D4)。
|
|
159
|
+
*/
|
|
160
|
+
export class SessionBaselines {
|
|
161
|
+
private readonly deps: SessionBaselinesDeps;
|
|
162
|
+
|
|
163
|
+
/** UI 请求 handler(进程级,可被 setUiRequestHandler / initSession 覆盖)。 */
|
|
164
|
+
private uiRequestHandler: SubagentServiceInitUiRequestHandler;
|
|
165
|
+
|
|
166
|
+
/** L2 dialog 串行队列(进程级)。SR-4:子进程退出时经引擎镜像层
|
|
167
|
+
* (SpawnedChildrenMirror → notifyChildProcessExited)取消该子进程的挂起请求。 */
|
|
168
|
+
private dialogQueue: DialogGlobalQueue | undefined;
|
|
169
|
+
|
|
170
|
+
/** UI 请求可观测性(sessionMode + handler 缺失告警去重,提取自原上帝类降低行数)。 */
|
|
171
|
+
private readonly _uiObservability = new UiRequestObservability();
|
|
172
|
+
|
|
173
|
+
private _pi: PiLike | null = null;
|
|
174
|
+
|
|
175
|
+
/** 当前 Pi session ID(本进程 pi session,事件路由等用;record 过滤不用它)。initSession 时注入。 */
|
|
176
|
+
private _sessionId: string | null = null;
|
|
177
|
+
|
|
178
|
+
/** 主 session 文件(initSession 按值直传——jiti 多实例下闭包缓存不可靠,见 SessionInit 注释)。 */
|
|
179
|
+
private _mainSessionFile: string | undefined;
|
|
180
|
+
|
|
181
|
+
/** 所属根 session ID(record 归属过滤用)。根进程 = sessionId(自己是 root);
|
|
182
|
+
* 子进程 = env PI_SUBAGENT_ROOT_SESSION_ID 贯穿的真 ROOT(initSession 读取)。
|
|
183
|
+
* 与 sessionId 正交:sessionId 是本进程 pi session(事件路由等),sessionRootId 是所属根
|
|
184
|
+
* (collectRecords filter 用,与 createRecordForMode 的 rootSessionId 盖章同源——子进程
|
|
185
|
+
* 因此看到整棵 ROOT 树)。设计见 recursive-subagent-visibility.md 决策 3。 */
|
|
186
|
+
private _sessionRootId: string | null = null;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* [D3-⑤ 嵌套防护合一] 进程内执行嵌套上下文(原 execCtxAls 私有字段下沉公共层
|
|
190
|
+
* common/nesting-guard.ts ExecutionNestingContext——机制注释含 ALS 断裂基线兜底)。
|
|
191
|
+
* 实例 per-Service:基线随宿主进程身份而异(initSession 从 env 建立)。
|
|
192
|
+
*/
|
|
193
|
+
private readonly _execNesting = new ExecutionNestingContext();
|
|
194
|
+
|
|
195
|
+
/** fork 深度基线(同 ALS 断裂问题:forkDepthAls.getStore() 兜底用)。根进程=0。 */
|
|
196
|
+
private forkDepthBaseline = 0;
|
|
197
|
+
|
|
198
|
+
/** [MF-3] 所属根进程 cwd(sessions/records 落盘目录编码键)。
|
|
199
|
+
* 根进程=自身 cwd(构造时 init.cwd);子进程=env PI_SUBAGENT_ROOT_CWD 贯穿的真 ROOT cwd。
|
|
200
|
+
* worktree 模式下子进程 this.cwd 是 checkout 路径,若按它编码目录,深层 record 落到
|
|
201
|
+
* enc(worktree) 段、ROOT 扫描不到 → 全树可见性深度 ≥ 2 断裂(与 sessionRootId 同构)。 */
|
|
202
|
+
private readonly _rootCwd: string;
|
|
203
|
+
|
|
204
|
+
/** UI streaming sink(ctx.ui.setWidget)。workflow 域经 getStreamSink() 取用。 */
|
|
205
|
+
private _streamSink: StreamSink | null = null;
|
|
206
|
+
|
|
207
|
+
/** [竞态修复] 主 agent isIdle 查询(ctx.isIdle)。notifier flush gate 用。
|
|
208
|
+
* initSession 注入,piAdapter 透传给 NotifierHost。 */
|
|
209
|
+
private _isIdleFn: (() => boolean) | undefined;
|
|
210
|
+
|
|
211
|
+
/** [MF#4][MF#2] fork 深度按 async 调用链传递(AsyncLocalStorage),替代共享可变计数器。
|
|
212
|
+
* 主 session=0;fork 进入子 session 期间推进为子深度,供嵌套 fork 经 ALS 读到自身深度作为
|
|
213
|
+
* parentForkDepth。并发 background fork 各自独立调用链,不再互相压低深度值。
|
|
214
|
+
* [MF#2] 旧实现用单实例字段跨执行链共享 → 并发下 A 还原深度后 B 读到被压低值 → 护栏失效。 */
|
|
215
|
+
private readonly forkDepthAls = new AsyncLocalStorage<number>();
|
|
216
|
+
|
|
217
|
+
constructor(init: SessionBaselinesCtorInit, deps: SessionBaselinesDeps) {
|
|
218
|
+
this.deps = deps;
|
|
219
|
+
this.uiRequestHandler = init.uiRequestHandler;
|
|
220
|
+
// [MF-3] rootCwd env 推导随字段迁入(原壳构造器逻辑逐行等价):读 env PI_SUBAGENT_ROOT_CWD
|
|
221
|
+
// (根进程无 env → init.cwd)。sessions 与 records 两套目录必须同源(同一 rootCwd),
|
|
222
|
+
// 否则 enc 段不变量断裂(只改其一会让同 record 的 session 文件与 manifest 分落两段,
|
|
223
|
+
// GC/重建互相找不到)——推导在构造期一次完成,壳经 this.rootCwd 读取。
|
|
224
|
+
const envRootCwd = process.env[ENV_ROOT_CWD];
|
|
225
|
+
this._rootCwd = envRootCwd && envRootCwd !== "" ? envRootCwd : init.cwd;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── 壳只读透传面(getter)──
|
|
229
|
+
// [R1 打样模式 2] 壳侧同名私有 getter 逐个透传到这些成员;写点全部收口在本聚合
|
|
230
|
+
// 方法内(D1 单写者)。getter 与存储字段分离 = 壳/测试只读,防写路径旁路聚合。
|
|
231
|
+
|
|
232
|
+
get pi(): PiLike | null { return this._pi; }
|
|
233
|
+
get sessionId(): string | null { return this._sessionId; }
|
|
234
|
+
get mainSessionFile(): string | undefined { return this._mainSessionFile; }
|
|
235
|
+
get sessionRootId(): string | null { return this._sessionRootId; }
|
|
236
|
+
get streamSink(): StreamSink | null { return this._streamSink; }
|
|
237
|
+
get isIdleFn(): (() => boolean) | undefined { return this._isIdleFn; }
|
|
238
|
+
get rootCwd(): string { return this._rootCwd; }
|
|
239
|
+
get uiObservability(): UiRequestObservability { return this._uiObservability; }
|
|
240
|
+
get execNesting(): ExecutionNestingContext { return this._execNesting; }
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* [F6] 当前根 session id 的只读访问——引擎接线方(SAR 等)构造 RunContext 注入
|
|
244
|
+
* `ctx.sessionRootId`(pi 引擎 relay 归属键 SESSION_ID 权威源)。initSession 后有值
|
|
245
|
+
* (根进程 = 本 session id;嵌套 = env 贯穿的真 ROOT)。壳保留同名公共方法转发(D3+
|
|
246
|
+
* 壳终态永久保留面)。
|
|
247
|
+
*/
|
|
248
|
+
getSessionRootId(): string | null {
|
|
249
|
+
return this._sessionRootId;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** UI streaming sink 只读访问(workflow 域消费)。壳保留同名公共方法转发(D3+)。 */
|
|
253
|
+
getStreamSink(): StreamSink | null {
|
|
254
|
+
return this._streamSink;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** session_start 注入 pi + revive(modelRegistry/entries 归 ModelConfigService.initModel)。
|
|
258
|
+
* 跨域编排(store.setPi/revive、通知面 revive、孤儿恢复、监督器 boot、对账 sweep)经
|
|
259
|
+
* deps 回调按原时序逐行执行——时序语义与抽取前逐行等价,见各回调注释。 */
|
|
260
|
+
initSession(init: SubagentServiceSessionInit): void {
|
|
261
|
+
this._pi = init.pi;
|
|
262
|
+
// 同步注入 pi 到 RecordStore(构造时 pi 为 null,session_start 后才有真实 handle)。
|
|
263
|
+
// RecordStore 跳过损坏 manifest 时调 appendEntry 上报用户可见——若不重新注入,
|
|
264
|
+
// 上报通道永远是 no-op,事故排查依然静默。
|
|
265
|
+
this.deps.getStore().setPi(this._pi);
|
|
266
|
+
this._sessionId = init.sessionId;
|
|
267
|
+
// 主 session 文件按值直传(jiti 多实例下闭包缓存不可靠,见接口注释)。
|
|
268
|
+
this._mainSessionFile = init.mainSessionFile;
|
|
269
|
+
this._streamSink = init.streamSink ?? null;
|
|
270
|
+
this._isIdleFn = init.isIdle;
|
|
271
|
+
// 读取 mode(W4 守卫透传给 session-runner)+ session 级 handler 覆盖
|
|
272
|
+
//([D4-④] initSession.uiRequestHandler 是唯一注入入口;三态语义见接口注释——
|
|
273
|
+
//null = 显式清空,承载原 setUiRequestHandler(undefined) 语义)。
|
|
274
|
+
this._uiObservability.setMode(init.mode);
|
|
275
|
+
if (init.uiRequestHandler !== undefined) {
|
|
276
|
+
this.uiRequestHandler = init.uiRequestHandler ?? undefined;
|
|
277
|
+
this._uiObservability.resetMissingHandlerWarnings();
|
|
278
|
+
// [W6 R3 MF-A] session 级覆盖同步进壳侧应答端登记(三态:null = 显式清空)。
|
|
279
|
+
setHostUiRequestEndpoint(this.uiRequestHandler);
|
|
280
|
+
}
|
|
281
|
+
// SR-4:注入 L2 dialog 队列(child close 清理路径)。undefined 时 buildSessionRunnerContext
|
|
282
|
+
// 透传 undefined,session-runner onClose 跳过 L2 清理(仅清 L1,保留旧行为)。
|
|
283
|
+
if (init.dialogQueue !== undefined) {
|
|
284
|
+
this.dialogQueue = init.dialogQueue;
|
|
285
|
+
}
|
|
286
|
+
this.initForkDepthBaseline();
|
|
287
|
+
// [递归可见性] 跨进程身份贯穿(设计 recursive-subagent-visibility.md)。
|
|
288
|
+
// 父进程 spawn 时注入 env 描述「子进程自己的身份」(rootSessionId / selfRecordId /
|
|
289
|
+
// depth / rootCwd),语义与基线建立见 initExecContextBaseline。根进程无 env →
|
|
290
|
+
// sessionRootId = init.sessionId(自己是 root),execCtxAls 不 enterWith(顶层)。
|
|
291
|
+
const envRoot = process.env[ENV_ROOT_SESSION_ID];
|
|
292
|
+
this._sessionRootId = envRoot ?? init.sessionId;
|
|
293
|
+
this.initExecContextBaseline(envRoot, init.sessionId);
|
|
294
|
+
// revive(dispose 的逆操作:/resume /fork /new 后复活)——壳 _disposed 旗标经 deps
|
|
295
|
+
// 回调复位(旗标所有权留壳:assertReady 断言面 readAssertState 的写侧)。
|
|
296
|
+
this.deps.reviveDisposed();
|
|
297
|
+
// [v2 D4] settled 重扫状态随 revive 重置:新 session 的 E1 若再判「仍有 running」
|
|
298
|
+
// 可重新注册。旧 handler 闭包捕获旧 state:正常时序(session_shutdown →
|
|
299
|
+
// session_start)下已随 dispose() 惰化;未经 dispose 的时序残留仍会在 settled
|
|
300
|
+
// 边沿执行——其扫描壳 mainSessionFile 当前值(非注册时的旧文件),行为等价于
|
|
301
|
+
// 新 session 多注册一次扫描,由账本 sync-batch:<hash> 幂等 + batchFinalized 候选
|
|
302
|
+
// 过滤收敛,无跨 session 污染面。(C-2:#5 字段写点显式化为 deps 回调。)
|
|
303
|
+
this.deps.resetSettledRescan();
|
|
304
|
+
this.deps.getStore().revive();
|
|
305
|
+
this.deps.getNotifyHost().revive();
|
|
306
|
+
// 孤儿终态恢复(放 initSession 末尾:setPi 已注入(appendEntry 可用)、
|
|
307
|
+
// sessionRootId 已建立(过滤当前根的 record);单扫描者判据见 recoverOrphansIfRootProcess)
|
|
308
|
+
this.deps.recoverOrphans();
|
|
309
|
+
// [W4] boot 分区 + 注册对账 sweep(须在孤儿恢复之后——依赖关系见两方法注释:
|
|
310
|
+
// 孤儿恢复把「重启前在途」record 直断 closed、把 resumable 形态保留 running 落
|
|
311
|
+
// entry,监督器重认领消费后者;sweep 再对终态 record 补发注销落盘——表 3 行 2
|
|
312
|
+
// 「注销经对账 sweep 保证落盘」的编排点)。
|
|
313
|
+
this.deps.bootRoundSupervisor();
|
|
314
|
+
this.deps.runPendingReconcileSweep();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* [SPAWN fork depth 跨进程传递] fork 链深度基线:子进程被父 spawn 时,父通过 env
|
|
319
|
+
* PI_SUBAGENT_FORK_DEPTH 传入当前 fork 链深度。子进程 session_start 时读取作为
|
|
320
|
+
* forkDepthAls 基线,使后续嵌套 spawn fork 能从正确深度递增。未设置(顶层主
|
|
321
|
+
* session)→ 基线 0。enterWith 贯穿整个 session 生命周期。
|
|
322
|
+
*/
|
|
323
|
+
private initForkDepthBaseline(): void {
|
|
324
|
+
const envDepth = process.env.PI_SUBAGENT_FORK_DEPTH;
|
|
325
|
+
if (envDepth !== undefined && envDepth !== "") {
|
|
326
|
+
const base = Number.parseInt(envDepth, 10);
|
|
327
|
+
if (!Number.isNaN(base) && base > 0) {
|
|
328
|
+
this.forkDepthAls.enterWith(base);
|
|
329
|
+
this.forkDepthBaseline = base;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* [递归可见性] exec 上下文基线:子进程读 env PI_SUBAGENT_SELF_RECORD_ID / DEPTH
|
|
336
|
+
* 建立身份基线后,createRecordForMode 读嵌套上下文自动正确(孙挂到子名下)。
|
|
337
|
+
* enterWith 贯穿整个 session 生命周期(与 forkDepthAls 同构,决策 4)。
|
|
338
|
+
*/
|
|
339
|
+
private initExecContextBaseline(envRoot: string | undefined, sessionId: string): void {
|
|
340
|
+
const envSelfRecord = process.env[ENV_SELF_RECORD_ID];
|
|
341
|
+
if (envSelfRecord !== undefined && envSelfRecord !== "") {
|
|
342
|
+
const envNestingDepth = Number.parseInt(process.env[ENV_DEPTH] ?? "0", 10);
|
|
343
|
+
const nestingDepth = Number.isNaN(envNestingDepth) ? 0 : envNestingDepth;
|
|
344
|
+
// [ALS 断裂修复] 基线兜底:enterWith 在 pi 事件回调模型下不可靠(机制注释见
|
|
345
|
+
// common/nesting-guard.ts ExecutionNestingContext),基线是 createRecordForMode /
|
|
346
|
+
// 护栏读 ALS store 失败时的权威回退。
|
|
347
|
+
this._execNesting.setBaseline({ recordId: envSelfRecord, depth: nestingDepth });
|
|
348
|
+
this._execNesting.enterWith({ recordId: envSelfRecord, depth: nestingDepth });
|
|
349
|
+
if (process.env.XYZ_AGENT_DEBUG) {
|
|
350
|
+
logger.debug(
|
|
351
|
+
`[subagents] execNesting initialized: recordId=${envSelfRecord} depth=${nestingDepth} rootSessionId=${envRoot ?? sessionId}`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** [C-3 显式接口收敛] dispose 时的 UI 面 stub 化:uiRequestHandler 换 stub +
|
|
358
|
+
* 缺失告警去重重置。原壳 dispose 直写本聚合两个字段,R1 收敛为本显式方法
|
|
359
|
+
* (壳 dispose 编排调用;壳侧应答端登记 setHostUiRequestEndpoint(stub) 仍留壳)。
|
|
360
|
+
*
|
|
361
|
+
* stub 化时序契约(原壳 dispose 注释):第一时间换 stub,防 trailing ui_request 调到
|
|
362
|
+
* stale handler 闭包(仍持有 disposed session 的 ctx)产生误导性 console.error;
|
|
363
|
+
* 必须在 emit/abort 之前——这些步骤可能同步触发 trailing pump。 */
|
|
364
|
+
disposeSessionUi(): void {
|
|
365
|
+
this.uiRequestHandler = disposedUiRequestStub;
|
|
366
|
+
this._uiObservability.resetMissingHandlerWarnings();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* [D4 下沉] 校验 Service 就绪(pi 已注入 + 未 dispose)。原壳私有方法整体迁入,
|
|
371
|
+
* 9 个壳调用点经壳同名私有方法转发(签名/错误文案逐字节不变)。
|
|
372
|
+
*
|
|
373
|
+
* dispose 后调用是异常路径:session_shutdown 已清资源,正常情况下紧接着
|
|
374
|
+
* session_start 会 initSession 复活。若走到这里说明 session_start 没跟上
|
|
375
|
+
* (RPC 边界 / reload 异常等),service 卡在 disposed 状态。
|
|
376
|
+
*
|
|
377
|
+
* 旧实现只抛 "hub disposed"——无信息,调用方和 AI 都看不懂,导致反复盲试。
|
|
378
|
+
* 现在给出原因 + 恢复指引(重启会话或 /new)。真实错误文本会经 renderResult
|
|
379
|
+
* 兜底透传到 AI(见 tool-render.ts extractResultError)。
|
|
380
|
+
*
|
|
381
|
+
* 断言状态经 deps.readAssertState **现读**(D4 late-bound getter):构造期注值
|
|
382
|
+
* 会持过期引用破 session 复活(initSession 复活用例锁定该语义)。
|
|
383
|
+
*/
|
|
384
|
+
assertReady(): void {
|
|
385
|
+
const { pi, disposed } = this.deps.readAssertState();
|
|
386
|
+
if (pi === null) {
|
|
387
|
+
throw new Error("pi not injected (initSession not called?)");
|
|
388
|
+
}
|
|
389
|
+
if (disposed) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
"subagents service disposed (session ended). " +
|
|
392
|
+
"This happens after session shutdown when the follow-up session_start did not arrive. " +
|
|
393
|
+
"Recovery: start a new session or run /new to revive the subagents runtime.",
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** 构造期 UI handler 初值类型(= SubagentServiceInit.uiRequestHandler 的同构窄类型;
|
|
400
|
+
* 聚合不 import 壳 Init 接口,结构等价由构造点同语句保证)。 */
|
|
401
|
+
type SubagentServiceInitUiRequestHandler = UiRequestHandler | undefined;
|