@zhushanwen/subagent-core 0.8.0 → 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-OL5BK4VN.js → chunk-LZJOEDGQ.js} +455 -203
- 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 +216 -155
- 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 +7686 -5894
- package/dist/index.d.cts +1876 -1360
- package/dist/index.d.ts +1876 -1360
- package/dist/index.js +7048 -5493
- package/dist.bundle/index.cjs +7747 -5957
- package/package.json +4 -4
- package/src/__tests__/manifest-store.test.ts +16 -15
- package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
- package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
- 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__/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__/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__/engine-discovery-scan.test.ts +0 -3
- package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
- 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 +81 -25
- 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 +19 -41
- package/src/execution/engine/host/pi-host-binding.ts +5 -5
- package/src/execution/engine/port.ts +34 -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/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 -2980
- 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 +1 -1
- package/src/index.ts +6 -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-BXLA8y-z.d.cts +0 -1624
- package/dist/engine-discovery-scan-BXLA8y-z.d.ts +0 -1624
- 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
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// src/execution/relay-env.ts
|
|
2
2
|
//
|
|
3
|
-
// relay 通道 env 名与协议常量的
|
|
3
|
+
// relay 通道 env 名与协议常量的 **semver 子入口载体**——单源在
|
|
4
4
|
// @zhushanwen/subagent-engine-sdk(round1-reuse R9 跨进程契约副本收编;core → SDK
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// 边界正向合法)。
|
|
6
|
+
//
|
|
7
|
+
// [L5 收窄] 本文件不再是 core 内部或引擎包的消费路径:core 内消费方(stream-sink /
|
|
8
|
+
// d8-compat)与 pi-subagent-cli 已直连 SDK,pi 包的 relay-env shim 已删除。保留本
|
|
9
|
+
// 文件的唯一理由 = package.json exports 的 ./relay-env 子入口(semver 面:runtime 与
|
|
10
|
+
// subagent-workflow 经它消费,两包当前不依赖 SDK——删本文件须同时给两包加 SDK 依赖
|
|
11
|
+
// 并改 import specifier,属独立的依赖面变更,未随 L5 执行)。
|
|
7
12
|
// 代理脚本 relay.mjs 仍为内嵌镜像(零依赖脚本不能 import workspace 包),
|
|
8
13
|
// 镜像一致性由 conformance relay 变体断言锁定。
|
|
9
14
|
|
|
@@ -27,16 +27,28 @@ export type SupervisorDomain =
|
|
|
27
27
|
/** run 域一次性任务(非 chatMode record)——监督域主体。 */
|
|
28
28
|
| "run"
|
|
29
29
|
/** conversation/chat 形态(record.chatMode)——豁免,现状机制管辖。 */
|
|
30
|
-
| "conversation"
|
|
30
|
+
| "conversation"
|
|
31
|
+
/**
|
|
32
|
+
* [H2 W2] workflow 脚本 agent() record(origin="workflow")——adopt 链豁免域:
|
|
33
|
+
* 引擎死亡即 run 失败即 record 终态化(service 分诊两处豁免),无脚本可回的
|
|
34
|
+
* resumable 等待无意义(adopt 链「唤醒→guidance→2h 看门狗→giveUp」全程死路,
|
|
35
|
+
* 还制造 2h 挂账)。豁免只覆盖 adopt 接管入口(adoptOnProcessDeath / boot 分区
|
|
36
|
+
* 重认领)——运行期记账(noteRunStarted/noteRunEnded)与 reconcile-sweep 对账
|
|
37
|
+
* 对 workflow record 照旧(H1 D8「非 chatMode 全量纳管」不因本豁免收窄)。
|
|
38
|
+
*/
|
|
39
|
+
| "workflow";
|
|
31
40
|
|
|
32
41
|
/**
|
|
33
42
|
* 域分类(裁决表 conversation 行「任何触发不入监督域」的判定锚)。
|
|
43
|
+
* record.origin === "workflow" → workflow(adopt 链豁免域,[H2 W2]);否则
|
|
34
44
|
* record.chatMode === true → conversation(豁免);否则 → run 域。
|
|
35
|
-
* (workflow
|
|
36
|
-
* 归档 + sweep 对账——workflow run 不在 RecordStore,其注册对账按 type=workflow
|
|
45
|
+
* (workflow run 不在 RecordStore,其注册对账按 type=workflow
|
|
37
46
|
* 保守跳过,见 reconcile-sweep.ts。)
|
|
38
47
|
*/
|
|
39
|
-
export function classifySupervisorDomain(
|
|
48
|
+
export function classifySupervisorDomain(
|
|
49
|
+
record: Pick<ExecutionRecord, "chatMode" | "origin">,
|
|
50
|
+
): SupervisorDomain {
|
|
51
|
+
if (record.origin === "workflow") return "workflow";
|
|
40
52
|
return record.chatMode === true ? "conversation" : "run";
|
|
41
53
|
}
|
|
42
54
|
|
|
@@ -66,7 +78,7 @@ export function isAwakeWarrantedShape(
|
|
|
66
78
|
/**
|
|
67
79
|
* boot 分区「already-resumable-idle」重认领谓词(与裁决表同源——设计 D4 连带面 2
|
|
68
80
|
* ②「启动扫描 resumable 且无进程驱动且非 conversation 形态」的单一权威实现;
|
|
69
|
-
* cold-
|
|
81
|
+
* cold-lookup.ts 的 message 冷查链不承载启动扫描,本谓词即其「cold-resurrect
|
|
70
82
|
* 扩展」落点,record-store 孤儿恢复的保留分支与 supervisor.bootPartition 共同消费
|
|
71
83
|
* 同一判据形态)。
|
|
72
84
|
*
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
// 分支,替身 getMutable 受控注入驱动契约);
|
|
11
11
|
// - finalizeClosed 异常 best-effort(不向上抛、终止通知仍发);
|
|
12
12
|
// - superseded:errorText 带替代 id、不发终止通知;
|
|
13
|
-
// - 磁盘态(boot 重认领后看门狗到期):
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
13
|
+
// - 磁盘态(boot 重认领后看门狗到期):[U2b] store.markFinalized 归口——
|
|
14
|
+
// `.state` sidecar + subagent-record 终态 entry(closed/gc/endedAt/error)+
|
|
15
|
+
// `.alive` release 一体落齐、无 pi 通知;
|
|
16
|
+
// - 磁盘态无 sessionFile / writeFinalizedState 返回 false(§3.4 零持久化
|
|
17
|
+
// 副作用 + 响亮 entry)/ 双 miss 收尾竞争 / entry 面抛错(appendEntry catch,
|
|
18
|
+
// 须先经 boot 重认领武装看门狗——无候选则 giveUp 不执行,用例空转)。
|
|
18
19
|
// 2. runPendingReconcileSweepForService 的 lookupRecordState 闭包 subagent 判据
|
|
19
20
|
// (workflow 判据回归钉在 workflow-state-root.test.ts,此处补 subagent 两方向:
|
|
20
21
|
// 活跃 record 不被 sweep 注销 + 终态/missing 差集补发注销)。
|
|
@@ -32,11 +33,11 @@ import * as path from "node:path";
|
|
|
32
33
|
|
|
33
34
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
34
35
|
|
|
35
|
-
import { COLD_LOOKUP_SCAN_LIMIT } from "../cold-
|
|
36
|
-
import * as
|
|
36
|
+
import { COLD_LOOKUP_SCAN_LIMIT } from "../cold-lookup.ts";
|
|
37
|
+
import * as stateMarker from "../state-marker.ts";
|
|
37
38
|
import type { PiLike } from "../notify-host.ts";
|
|
38
|
-
import
|
|
39
|
-
import type { ExecutionRecord, SubagentRecord } from "../types.ts";
|
|
39
|
+
import { RecordStore } from "../record-store.ts";
|
|
40
|
+
import type { ClosedReason, ExecutionRecord, SubagentRecord } from "../types.ts";
|
|
40
41
|
import { RoundSupervisor, ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS, type SupervisorCandidateRecord } from "./index.ts";
|
|
41
42
|
import {
|
|
42
43
|
createRoundSupervisorForService,
|
|
@@ -44,13 +45,13 @@ import {
|
|
|
44
45
|
type RoundSupervisorBinding,
|
|
45
46
|
} from "./service-binding.ts";
|
|
46
47
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
vi.mock("../
|
|
52
|
-
const actual = await importOriginal<typeof import("../
|
|
53
|
-
return { ...actual,
|
|
48
|
+
// writeFinalizedState 实装(U1 后)为响亮重试 + 失败返回 false(不抛——state-marker
|
|
49
|
+
// 内部 3 次退避重试),give-up 的 §3.4 false 分支真实磁盘故障无法稳定驱动,仅本文件
|
|
50
|
+
// 的「重试耗尽」用例经模块替身注入 false 返回;默认委托真实实现,相邻用例行为不变
|
|
51
|
+
//(vi.spyOn 对跨模块具名导入绑定不可拦截,必须走 vi.mock)。
|
|
52
|
+
vi.mock("../state-marker.ts", async (importOriginal) => {
|
|
53
|
+
const actual = await importOriginal<typeof import("../state-marker.ts")>();
|
|
54
|
+
return { ...actual, writeFinalizedState: vi.fn(actual.writeFinalizedState) };
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
// ============================================================
|
|
@@ -92,6 +93,8 @@ function makePi(): TestPi {
|
|
|
92
93
|
|
|
93
94
|
interface StoreHarness {
|
|
94
95
|
store: RecordStore;
|
|
96
|
+
/** 真实 RecordStore(磁盘态放弃分支 markFinalized 的委托目标——终态四面真写)。 */
|
|
97
|
+
realStore: RecordStore;
|
|
95
98
|
memory: Map<string, ExecutionRecord>;
|
|
96
99
|
disk: Map<string, SubagentRecord>;
|
|
97
100
|
/** collectRecords 返回的候选(替身投影;默认从 memory 生成)。 */
|
|
@@ -100,14 +103,17 @@ interface StoreHarness {
|
|
|
100
103
|
reported: SubagentRecord[];
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
/** duck-typed RecordStore 替身(service-binding
|
|
104
|
-
* collectRecords / reportSubagentRecord
|
|
106
|
+
/** duck-typed RecordStore 替身(service-binding 消费 getMutable / findLightById /
|
|
107
|
+
* collectRecords / reportSubagentRecord / markFinalized 五个读写点——markFinalized
|
|
108
|
+
* [U2b] 委托真实 store(`.state`/entry/manifest/marker release 真写),其余读点走
|
|
109
|
+
* 替身 map(真实磁盘发现需 session 文件扫描,与本文件断言面无关)。 */
|
|
105
110
|
function makeStore(): StoreHarness {
|
|
106
111
|
const memory = new Map<string, ExecutionRecord>();
|
|
107
112
|
const disk = new Map<string, SubagentRecord>();
|
|
108
113
|
const collectResult: SupervisorCandidateRecord[] = [];
|
|
109
114
|
const collectCalls: StoreHarness["collectCalls"] = [];
|
|
110
115
|
const reported: SubagentRecord[] = [];
|
|
116
|
+
const realStore = new RecordStore(path.join(tmpDir, "sessions"));
|
|
111
117
|
const store = {
|
|
112
118
|
getMutable: (id: string) => memory.get(id),
|
|
113
119
|
findLightById: (id: string) => disk.get(id),
|
|
@@ -118,8 +124,10 @@ function makeStore(): StoreHarness {
|
|
|
118
124
|
reportSubagentRecord: (record: SubagentRecord) => {
|
|
119
125
|
reported.push(record);
|
|
120
126
|
},
|
|
127
|
+
markFinalized: (record: ExecutionRecord, closedReason?: ClosedReason) =>
|
|
128
|
+
realStore.markFinalized(record, closedReason),
|
|
121
129
|
};
|
|
122
|
-
return { store: store as unknown as RecordStore, memory, disk, collectResult, collectCalls, reported };
|
|
130
|
+
return { store: store as unknown as RecordStore, realStore, memory, disk, collectResult, collectCalls, reported };
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
interface BindingHarness {
|
|
@@ -329,6 +337,9 @@ describe("supervisorGiveUp 内存态(watchdog-expired / superseded / CAS)",
|
|
|
329
337
|
describe("supervisorGiveUp 磁盘态(boot 重认领后看门狗到期)", () => {
|
|
330
338
|
function makeDiskBinding(diskRecord: SubagentRecord): BindingHarness {
|
|
331
339
|
const h = makeBinding({ sessionFile });
|
|
340
|
+
// [U2b] 终态 entry/manifest 面 = markFinalized 经真实 store 落 pi.appendEntry
|
|
341
|
+
//(archive 面),绑定 pi 注入真实 store。
|
|
342
|
+
h.store.realStore.setPi(h.pi ?? null);
|
|
332
343
|
h.store.disk.set(diskRecord.id, diskRecord);
|
|
333
344
|
// boot 分区候选(真实 collectRecords 的 running 投影形态)
|
|
334
345
|
h.store.collectResult.push({
|
|
@@ -341,37 +352,54 @@ describe("supervisorGiveUp 磁盘态(boot 重认领后看门狗到期)", ()
|
|
|
341
352
|
return h;
|
|
342
353
|
}
|
|
343
354
|
|
|
344
|
-
|
|
355
|
+
/** pi.appended 中 customType=subagent-record 的终态 entry 载荷。 */
|
|
356
|
+
function subagentEntries(pi: ReturnType<typeof makePi> | null): Array<Record<string, unknown>> {
|
|
357
|
+
return (pi?.appended ?? [])
|
|
358
|
+
.filter((a) => a.customType === "subagent-record")
|
|
359
|
+
.map((a) => a.data as Record<string, unknown>);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
it("markFinalized 归口:.state sidecar + subagent-record 终态 entry(closed/gc/endedAt/error)+ .alive release 一体落齐;磁盘态不发终止通知", async () => {
|
|
345
363
|
const diskRecord = makeDiskRecord(sessionFile);
|
|
346
364
|
const h = makeDiskBinding(diskRecord);
|
|
365
|
+
// 持有期声明在位(boot 重认领形态——磁盘 running record 携带写权声明)。
|
|
366
|
+
h.store.realStore.acquireWriteLease(sessionFile, diskRecord.id);
|
|
367
|
+
expect(fs.existsSync(`${sessionFile}.alive`)).toBe(true);
|
|
347
368
|
const supervisor = createRoundSupervisorForService(h.binding);
|
|
348
369
|
|
|
349
370
|
// 纳管经磁盘兜底视图(内存 Map 空)——boot 分区重认领形态
|
|
350
371
|
supervisor.bootPartition();
|
|
351
372
|
await vi.advanceTimersByTimeAsync(ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS + 1);
|
|
352
373
|
|
|
353
|
-
// sidecar:sessionFile 旁 .
|
|
354
|
-
expect(fs.readFileSync(`${sessionFile}.
|
|
374
|
+
// sidecar:sessionFile 旁 .state 内容 = {status:finalized, reason:"gc"};finalizeClosed 不适用(无内存 record)
|
|
375
|
+
expect(JSON.parse(fs.readFileSync(`${sessionFile}.state`, "utf-8"))).toEqual({
|
|
376
|
+
status: "finalized",
|
|
377
|
+
reason: "gc",
|
|
378
|
+
});
|
|
355
379
|
expect(h.finalizeClosed).not.toHaveBeenCalled();
|
|
356
380
|
|
|
357
|
-
// 终态 entry 落盘(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
expect(
|
|
381
|
+
// 终态 entry 落盘(markFinalized → archive → pi.appendEntry):磁盘 record + 终态语义位
|
|
382
|
+
const entries = subagentEntries(h.pi);
|
|
383
|
+
expect(entries).toHaveLength(1);
|
|
384
|
+
expect(entries[0]).toMatchObject({
|
|
361
385
|
id: "bg-disk",
|
|
362
386
|
status: "closed",
|
|
363
387
|
closedReason: "gc",
|
|
364
388
|
endedAt: expect.any(Number),
|
|
365
389
|
error: expect.stringContaining("safe to re-dispatch"),
|
|
366
390
|
});
|
|
367
|
-
expect(
|
|
391
|
+
expect(String(entries[0]!["error"])).not.toContain("superseded");
|
|
392
|
+
|
|
393
|
+
// [D3a release 出口①] 终态原语删写权声明——磁盘态放弃同样释放(残留声明会
|
|
394
|
+
// 误拦异宿主接管)。
|
|
395
|
+
expect(fs.existsSync(`${sessionFile}.alive`)).toBe(false);
|
|
368
396
|
|
|
369
397
|
// 磁盘态分支无终止通知(sent 仅 boot 重认领的 decision guidance 一条)
|
|
370
398
|
expect(h.pi?.sent).toHaveLength(1);
|
|
371
399
|
expect(h.pi?.sent.every((s) => !s.message.content.includes(TERMINATION_NOTICE_MARK))).toBe(true);
|
|
372
400
|
});
|
|
373
401
|
|
|
374
|
-
it("磁盘态无 sessionFile → 跳过 sidecar
|
|
402
|
+
it("磁盘态无 sessionFile → 跳过 sidecar,仍终态 entry(markFinalized 无锚点降级路径)", async () => {
|
|
375
403
|
const diskRecord = makeDiskRecord(undefined);
|
|
376
404
|
const h = makeDiskBinding(diskRecord);
|
|
377
405
|
const supervisor = createRoundSupervisorForService(h.binding);
|
|
@@ -379,19 +407,20 @@ describe("supervisorGiveUp 磁盘态(boot 重认领后看门狗到期)", ()
|
|
|
379
407
|
supervisor.bootPartition();
|
|
380
408
|
await vi.advanceTimersByTimeAsync(ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS + 1);
|
|
381
409
|
|
|
382
|
-
expect(fs.existsSync(`${sessionFile}.
|
|
383
|
-
|
|
384
|
-
expect(
|
|
410
|
+
expect(fs.existsSync(`${sessionFile}.state`)).toBe(false);
|
|
411
|
+
const entries = subagentEntries(h.pi);
|
|
412
|
+
expect(entries).toHaveLength(1);
|
|
413
|
+
expect(entries[0]).toMatchObject({ id: "bg-disk", status: "closed", closedReason: "gc" });
|
|
385
414
|
});
|
|
386
415
|
|
|
387
|
-
it("
|
|
416
|
+
it("entry 面抛错(pi.appendEntry 炸)→ best-effort 吞掉不向上抛,sidecar 照常落盘", async () => {
|
|
388
417
|
// 候选入 collectResult → boot 重认领(readopted)武装看门狗——没有这一步
|
|
389
418
|
// listCandidateRecords 返回空、giveUp 永不执行、entry catch 不可达(MF-R2-2)。
|
|
390
419
|
const diskRecord = makeDiskRecord(sessionFile);
|
|
391
420
|
const h = makeDiskBinding(diskRecord);
|
|
392
|
-
h.
|
|
421
|
+
(h.pi!.appendEntry as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
|
393
422
|
throw new Error("entry write exploded");
|
|
394
|
-
};
|
|
423
|
+
});
|
|
395
424
|
const supervisor = createRoundSupervisorForService(h.binding);
|
|
396
425
|
|
|
397
426
|
const { readopted } = supervisor.bootPartition();
|
|
@@ -400,39 +429,46 @@ describe("supervisorGiveUp 磁盘态(boot 重认领后看门狗到期)", ()
|
|
|
400
429
|
await vi.advanceTimersByTimeAsync(ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS + 1);
|
|
401
430
|
|
|
402
431
|
// sidecar 与终态 entry 是两段独立 best-effort:entry 抛错不回滚已落 sidecar
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
432
|
+
//(markFinalized 写序 = .state 先、archive/entry 后,D8)。
|
|
433
|
+
expect(JSON.parse(fs.readFileSync(`${sessionFile}.state`, "utf-8"))).toEqual({
|
|
434
|
+
status: "finalized",
|
|
435
|
+
reason: "gc",
|
|
436
|
+
});
|
|
437
|
+
expect(subagentEntries(h.pi)).toHaveLength(0);
|
|
406
438
|
});
|
|
407
439
|
|
|
408
|
-
it("
|
|
409
|
-
const diskRecord = makeDiskRecord(
|
|
440
|
+
it("writeFinalizedState 返回 false(重试耗尽)→ 零持久化副作用:无终态 entry、无 state-write-failed 之外的写入面、响亮 entry 上报", async () => {
|
|
441
|
+
const diskRecord = makeDiskRecord(sessionFile);
|
|
410
442
|
const h = makeDiskBinding(diskRecord);
|
|
411
443
|
const supervisor = createRoundSupervisorForService(h.binding);
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
444
|
+
// 模块替身注入失败返回(U1 后 writeFinalizedState 不抛——重试耗尽返回 false);
|
|
445
|
+
// finally 恢复委托真实实现。
|
|
446
|
+
const actual = await vi.importActual<typeof import("../state-marker.ts")>(
|
|
447
|
+
"../state-marker.ts",
|
|
415
448
|
);
|
|
416
|
-
const writeSpy = vi.mocked(
|
|
417
|
-
writeSpy.mockImplementation(() =>
|
|
418
|
-
throw new Error("sidecar write exploded");
|
|
419
|
-
});
|
|
449
|
+
const writeSpy = vi.mocked(stateMarker.writeFinalizedState);
|
|
450
|
+
writeSpy.mockImplementation(() => false);
|
|
420
451
|
try {
|
|
421
452
|
supervisor.bootPartition();
|
|
422
453
|
await vi.advanceTimersByTimeAsync(ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS + 1);
|
|
423
454
|
} finally {
|
|
424
|
-
writeSpy.mockImplementation(actual.
|
|
455
|
+
writeSpy.mockImplementation(actual.writeFinalizedState);
|
|
425
456
|
}
|
|
426
457
|
|
|
427
|
-
// 替身确实被 giveUp
|
|
458
|
+
// 替身确实被 giveUp 消费(否则用例假绿)
|
|
428
459
|
expect(writeSpy).toHaveBeenCalled();
|
|
429
|
-
|
|
430
|
-
expect(
|
|
431
|
-
expect(h.
|
|
460
|
+
// [§3.4] record 留 running:.state 未落 + 无终态 entry(零持久化副作用)
|
|
461
|
+
expect(fs.existsSync(`${sessionFile}.state`)).toBe(false);
|
|
462
|
+
expect(subagentEntries(h.pi)).toHaveLength(0);
|
|
463
|
+
// 响亮上报腿:subagent:state-write-failed entry(GUI 通知面,对齐 doFinalizeRecord)
|
|
464
|
+
const failNotices = (h.pi?.appended ?? []).filter((a) => a.customType === "subagent:state-write-failed");
|
|
465
|
+
expect(failNotices).toHaveLength(1);
|
|
466
|
+
expect(failNotices[0]?.data).toMatchObject({ id: diskRecord.id, status: "closed", closedReason: "gc" });
|
|
432
467
|
});
|
|
433
468
|
|
|
434
469
|
it("giveUp 执行时 record 已被 archive(内存移除 + 磁盘无 light)→ 双 miss 直接 return,零副作用", async () => {
|
|
435
470
|
const h = makeBinding({ sessionFile });
|
|
471
|
+
h.store.realStore.setPi(h.pi ?? null);
|
|
436
472
|
const rec = makeRecord();
|
|
437
473
|
h.store.memory.set("bg-1", rec);
|
|
438
474
|
// 视图检查(内存 running)之后、giveUp 磁盘兜底之前被 archive 的收尾竞争替身投影
|
|
@@ -451,8 +487,8 @@ describe("supervisorGiveUp 磁盘态(boot 重认领后看门狗到期)", ()
|
|
|
451
487
|
expect(h.finalizeClosed).not.toHaveBeenCalled();
|
|
452
488
|
expect(h.pi?.sent).toHaveLength(2); // 纳管期通知照常,终止通知不发(零通知放弃)
|
|
453
489
|
expect(h.pi?.sent.every((s) => !s.message.content.includes(TERMINATION_NOTICE_MARK))).toBe(true);
|
|
454
|
-
expect(h.
|
|
455
|
-
expect(fs.existsSync(`${sessionFile}.
|
|
490
|
+
expect(subagentEntries(h.pi)).toHaveLength(0);
|
|
491
|
+
expect(fs.existsSync(`${sessionFile}.state`)).toBe(false);
|
|
456
492
|
});
|
|
457
493
|
});
|
|
458
494
|
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
// 全部依赖经 RoundSupervisorBinding 惰性闭包注入(session 级状态运行时可变——
|
|
11
11
|
// 对齐 notify-host.ts createNotifyHost 的 deps 惰性求值先例)。
|
|
12
12
|
|
|
13
|
+
import { getLogger } from "../../core/logger.ts";
|
|
13
14
|
import { bestEffort } from "../best-effort.ts";
|
|
14
|
-
import { COLD_LOOKUP_SCAN_LIMIT } from "../cold-
|
|
15
|
-
import { tryTransition } from "../execution-record.ts";
|
|
16
|
-
import { writeFinalized } from "../finalized-marker.ts";
|
|
15
|
+
import { COLD_LOOKUP_SCAN_LIMIT } from "../cold-lookup.ts";
|
|
16
|
+
import { createRecord, tryTransition } from "../execution-record.ts";
|
|
17
17
|
import { hasLiveProcessHandle } from "../lifecycle-predicates.ts";
|
|
18
18
|
import { FileRunStore } from "../../orchestration/file-run-store.ts";
|
|
19
19
|
import { resolvePiWorkflowStateDir } from "../workflow-state-root.ts";
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
type SupervisorRecordView,
|
|
29
29
|
} from "./index.ts";
|
|
30
30
|
|
|
31
|
+
const logger = getLogger("subagents");
|
|
32
|
+
|
|
31
33
|
/** 绑定面(SubagentService 供给;全部惰性——见文件头注)。 */
|
|
32
34
|
export interface RoundSupervisorBinding {
|
|
33
35
|
getStore(): RecordStore;
|
|
@@ -63,6 +65,7 @@ function supervisorRecordView(binding: RoundSupervisorBinding, id: string): Supe
|
|
|
63
65
|
resumable: memory.resumable === true,
|
|
64
66
|
hasResult: memory.result !== undefined,
|
|
65
67
|
chatMode: memory.chatMode === true,
|
|
68
|
+
origin: memory.origin,
|
|
66
69
|
rootSessionId: memory.rootSessionId,
|
|
67
70
|
agent: memory.agent,
|
|
68
71
|
slug: memory.slug,
|
|
@@ -78,6 +81,7 @@ function supervisorRecordView(binding: RoundSupervisorBinding, id: string): Supe
|
|
|
78
81
|
resumable: disk.resumable === true,
|
|
79
82
|
hasResult: disk.result !== undefined,
|
|
80
83
|
chatMode: disk.chatMode === true,
|
|
84
|
+
origin: disk.origin,
|
|
81
85
|
rootSessionId: disk.rootSessionId,
|
|
82
86
|
agent: disk.agent,
|
|
83
87
|
slug: disk.slug,
|
|
@@ -86,7 +90,13 @@ function supervisorRecordView(binding: RoundSupervisorBinding, id: string): Supe
|
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
/** 本 root session 的 running record 候选(内存 ∪ 磁盘重建投影)。
|
|
93
|
+
/** 本 root session 的 running record 候选(内存 ∪ 磁盘重建投影)。
|
|
94
|
+
* [H2 W2] origin 透传——superseded 对账的 workflow 候选豁免判据(supervisor.evaluate
|
|
95
|
+
* 消费面,W1 已把 origin 投影进 SubagentRecord)。【双防注记 I2】本处的 collectRecords
|
|
96
|
+
* 不传 includeWorkflow(缺省过滤 workflow origin),而 supervisor.evaluate 的 superseded
|
|
97
|
+
* 对账另有 workflow 候选豁免判据——后者实为死判据:workflow 候选在本查询已结构性
|
|
98
|
+
* 豁免(缺省过滤),判据永不命中。刻意保留作防御纵深:若未来本查询改传 includeWorkflow
|
|
99
|
+
* 或 store 侧过滤语义变更,evaluate 侧判据立即接管,行为不回退。 */
|
|
90
100
|
function supervisorCandidates(binding: RoundSupervisorBinding): SupervisorCandidateRecord[] {
|
|
91
101
|
const rootFilter = binding.getSessionRootId() ?? undefined;
|
|
92
102
|
return binding
|
|
@@ -98,6 +108,7 @@ function supervisorCandidates(binding: RoundSupervisorBinding): SupervisorCandid
|
|
|
98
108
|
agent: r.agent,
|
|
99
109
|
slug: r.slug,
|
|
100
110
|
startedAt: r.startedAt,
|
|
111
|
+
origin: r.origin,
|
|
101
112
|
}));
|
|
102
113
|
}
|
|
103
114
|
|
|
@@ -109,9 +120,12 @@ function supervisorCandidates(binding: RoundSupervisorBinding): SupervisorCandid
|
|
|
109
120
|
* 替代通知已由监督器先行发出。boot 直断不经本函数(孤儿恢复层直断 + in-flight
|
|
110
121
|
* 的重启中断 error 语义由 finalizeOrphanRecord 落位,见 supervisor.bootPartition 头注)。
|
|
111
122
|
* - 磁盘态(内存无 + findLightById 命中,boot 重认领后看门狗到期的形态):终态
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
123
|
+
* 写面归口 store.markFinalized([U2b/G1 收口] `.state` writeSync + entry/archive +
|
|
124
|
+
* manifest + `.alive` release,D8 写序——原 store 外终态 sidecar 直写 + 终态
|
|
125
|
+
* entry 直写两段独立 best-effort 直写点消灭;`.state` 重试耗尽返回
|
|
126
|
+
* false = 零持久化副作用,record 留 running 由 boot 孤儿恢复终态化承接,§3.4)
|
|
127
|
+
* ——不走 finalizeRecord(无内存 record,archive/CAS 不适用),其注销由对账
|
|
128
|
+
* sweep(发射点⑤「record 终态 → 差集补发」)收口。
|
|
115
129
|
*/
|
|
116
130
|
async function supervisorGiveUp(
|
|
117
131
|
binding: RoundSupervisorBinding,
|
|
@@ -157,21 +171,57 @@ async function supervisorGiveUp(
|
|
|
157
171
|
// 磁盘态分支(boot 重认领后的放弃——record 不在内存)。
|
|
158
172
|
const disk = store.findLightById(recordId);
|
|
159
173
|
if (disk === undefined) return;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
// 终态投影 record 由磁盘 light 投影重建(createRecord 先例 = cold-lookup 的
|
|
175
|
+
// resurrectColdRecord:identity 域经 identity 入参、readonly engineHandle 经构造
|
|
176
|
+
// spread、运行态域随后补写——origin/parentRunId 不迁 [workflow origin 豁免 adopt
|
|
177
|
+
// 链,本分支不可达])。markFinalized 的 entry 面 = recordToSubagent 投影,磁盘
|
|
178
|
+
// 投影的 result/turns/totalTokens 等终态快照字段随重建保真。
|
|
179
|
+
const record: ExecutionRecord = {
|
|
180
|
+
...createRecord(recordId, {
|
|
181
|
+
agent: disk.agent,
|
|
182
|
+
model: disk.model,
|
|
183
|
+
thinkingLevel: disk.thinkingLevel,
|
|
184
|
+
mode: disk.mode,
|
|
185
|
+
task: disk.task,
|
|
186
|
+
slug: disk.slug,
|
|
187
|
+
startedAt: disk.startedAt,
|
|
188
|
+
rootSessionId: disk.rootSessionId,
|
|
189
|
+
parentRecordId: disk.parentRecordId,
|
|
190
|
+
depth: disk.depth,
|
|
191
|
+
chatMode: disk.chatMode === true,
|
|
192
|
+
engine: disk.engine,
|
|
193
|
+
engineFallback: disk.engineFallback,
|
|
194
|
+
collectMode: disk.collectMode,
|
|
195
|
+
}),
|
|
196
|
+
...(disk.engineHandle !== undefined ? { engineHandle: disk.engineHandle } : {}),
|
|
197
|
+
};
|
|
198
|
+
record.sessionFile = disk.sessionFile;
|
|
199
|
+
record.round = disk.round;
|
|
200
|
+
record.resumable = disk.resumable;
|
|
201
|
+
record.hadWorktree = disk.worktree === true;
|
|
202
|
+
record.result = disk.result;
|
|
203
|
+
record.turnCount = disk.turns;
|
|
204
|
+
record.totalTokens = disk.totalTokens;
|
|
205
|
+
record.error = errorText;
|
|
206
|
+
record.endedAt = Date.now();
|
|
207
|
+
// 防御:createRecord 产物恒 running,CAS 失败不可达(不终态化直接返回)。
|
|
208
|
+
if (!tryTransition(record, "closed", "gc")) return;
|
|
167
209
|
try {
|
|
168
|
-
store.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
error
|
|
174
|
-
|
|
210
|
+
const persisted = store.markFinalized(record, "gc");
|
|
211
|
+
if (!persisted) {
|
|
212
|
+
// [§3.4] `.state` 重试耗尽:store 层已 error 级留痕,此处补调用方语境 + GUI
|
|
213
|
+
// 通知面腿(对齐 doFinalizeRecord 同款接线)——record 留 running 形态,boot
|
|
214
|
+
// 孤儿恢复终态化承接。
|
|
215
|
+
logger.error(
|
|
216
|
+
`[subagents] round supervisor give-up terminal write failed (record=${recordId}); ` +
|
|
217
|
+
`record stays running on disk — boot orphan recovery will finalize it`,
|
|
218
|
+
);
|
|
219
|
+
binding.getPi()?.appendEntry?.("subagent:state-write-failed", {
|
|
220
|
+
id: recordId,
|
|
221
|
+
status: "closed",
|
|
222
|
+
closedReason: "gc",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
175
225
|
} catch (err) {
|
|
176
226
|
bestEffort(err, `round supervisor give-up entry (${recordId})`, "error");
|
|
177
227
|
}
|
|
@@ -120,6 +120,8 @@ export interface SupervisorRecordView {
|
|
|
120
120
|
/** 已有完成产出(record.result !== undefined)——SP-5 upgrade 等待态判据。 */
|
|
121
121
|
hasResult: boolean;
|
|
122
122
|
chatMode: boolean;
|
|
123
|
+
/** [H2 W2] 来源身份(adopt 链豁免域判据——classifySupervisorDomain 消费)。 */
|
|
124
|
+
origin?: "tool" | "workflow";
|
|
123
125
|
rootSessionId: string | undefined;
|
|
124
126
|
agent: string;
|
|
125
127
|
slug: string;
|
|
@@ -141,6 +143,8 @@ export interface SupervisorCandidateRecord {
|
|
|
141
143
|
agent: string;
|
|
142
144
|
slug: string;
|
|
143
145
|
startedAt: number;
|
|
146
|
+
/** [H2 W2] 来源身份(superseded 对账的 workflow 候选豁免判据)。 */
|
|
147
|
+
origin?: "tool" | "workflow";
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
/** RoundSupervisor 的依赖(SubagentService 注入;全部单测替身友好)。 */
|
|
@@ -231,9 +235,12 @@ export class RoundSupervisor {
|
|
|
231
235
|
for (const candidate of this.deps.listCandidateRecords()) {
|
|
232
236
|
const view = this.deps.getRecordView(candidate.id);
|
|
233
237
|
if (view === undefined || view.status !== "running") continue;
|
|
234
|
-
|
|
238
|
+
// [H2 W2] adopt 链豁免域门(conversation 豁免 + workflow origin 豁免):
|
|
239
|
+
// classifySupervisorDomain 不把 workflow record 归入可 adopt 域("run")——
|
|
240
|
+
// boot 重认领对 workflow 形态不生效(豁免落空面无「重认领后 2h 看门狗挂账」)。
|
|
241
|
+
if (classifySupervisorDomain(view) !== "run") continue;
|
|
235
242
|
// boot 分区只处理「孤儿恢复保留下来的 resumable 且无完成产出的形态」(W4 死亡
|
|
236
|
-
// 纳管态跨重启)——in-flight
|
|
243
|
+
// 纳管态跨重启)——in-flight 已被孤儿恢复直断(见头注),不再此处重复
|
|
237
244
|
// 终态化(双收尾防线);resumable 且 result 有值是 SP-5 完成态(已完成挂账,
|
|
238
245
|
// idle-gc 锚归档收口,无需监督)。谓词与 record-store 孤儿恢复保留分支同源
|
|
239
246
|
// (isBootReadoptable,domain.ts)。
|
|
@@ -314,16 +321,22 @@ export class RoundSupervisor {
|
|
|
314
321
|
return;
|
|
315
322
|
}
|
|
316
323
|
// 该唤醒 → 通知对账(送指引前查替代)。
|
|
324
|
+
// [H2 W2 / superseded 豁免] workflow origin 候选不参与替代判定——parallel 同
|
|
325
|
+
// slug/同 agent 并行是 workflow 常态而非替代关系(replacedNotice「was already
|
|
326
|
+
// replaced by your new task」会把并行任务误述为替代并错误终态化原任务)。
|
|
317
327
|
const windowMs = getSupervisorWatchdogMs();
|
|
318
328
|
const verdict = classifyReplacement(
|
|
319
329
|
{ id: view.id, rootSessionId: view.rootSessionId, agentName: view.agent, slug: view.slug },
|
|
320
|
-
this.deps
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
this.deps
|
|
331
|
+
.listCandidateRecords()
|
|
332
|
+
.filter((c) => c.origin !== "workflow")
|
|
333
|
+
.map((c) => ({
|
|
334
|
+
id: c.id,
|
|
335
|
+
rootSessionId: c.rootSessionId,
|
|
336
|
+
agentName: c.agent,
|
|
337
|
+
slug: c.slug,
|
|
338
|
+
startedAt: c.startedAt,
|
|
339
|
+
})),
|
|
327
340
|
windowMs,
|
|
328
341
|
this.deps.now(),
|
|
329
342
|
);
|
|
@@ -400,6 +413,7 @@ export class RoundSupervisor {
|
|
|
400
413
|
resumable: record.resumable === true,
|
|
401
414
|
hasResult: record.result !== undefined,
|
|
402
415
|
chatMode: record.chatMode === true,
|
|
416
|
+
origin: record.origin,
|
|
403
417
|
rootSessionId: record.rootSessionId,
|
|
404
418
|
agent: record.agent,
|
|
405
419
|
slug: record.slug,
|