pi-shadow-mind 0.1.15 → 0.1.16
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/DESIGN.md +5 -3
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/index.js +175 -67
- package/dist/index.js.map +4 -4
- package/package.json +13 -1
package/DESIGN.md
CHANGED
|
@@ -340,9 +340,11 @@ Main 在会话中切换模型后,后续 heartbeat 直接依据新模型重新
|
|
|
340
340
|
|
|
341
341
|
配置了 `final_response` 的 Shadow 在 Main 发出非空最终文字,并进入 `agent_settled` 状态后参与调度。该模式仍应用 `enabled` 和 `active_for_models`,但绕过 `heartbeat_probability` 与 `activation_probability`,所有匹配项都必须获得一次运行机会。
|
|
342
342
|
|
|
343
|
-
最终回复检查使用当时的完整净化轨迹快照。`max_parallel_shadows` 仍是硬并发上限;没有空闲槽位或同一 Shadow
|
|
343
|
+
最终回复检查使用当时的完整净化轨迹快照。`max_parallel_shadows` 仍是硬并发上限;没有空闲槽位或同一 Shadow 正在运行时,检查进入专用队列,槽位释放后继续执行。旧 epoch 的运行释放槽位时同样继续泵送当前队列,避免跨 epoch 的异步收尾把检查永久卡住。
|
|
344
344
|
|
|
345
|
-
|
|
345
|
+
每次最终回复检查拥有独立、递增的 review generation。独立的 completion-review 协调器统一拥有 generation、等待队列、运行完成计数和报告聚合,Runtime 只转发宿主生命周期事件与 Shadow 启停通知。同一 generation 的报告先暂存,所有匹配 Shadow 都进入终态后才合并为一次 `shadow-report`;因此较慢的同批检查不会在 Main 已开始修订后单独 steer。新的最终回复、新用户输入、其他报告触发的修订或 Session 关闭都会使旧 generation 失效,并清空尚未启动的项目;仍在收尾的旧运行只能记录 lifetime usage,不能再投递报告。异步刷新配置和 Registry 时还会同时校验启动请求与 epoch,防止等待 I/O 的旧最终回复在新任务中恢复调度。
|
|
346
|
+
|
|
347
|
+
检查结果沿用普通 `shadow-report` 的 steer/follow-up 机制。因此 Main 根据聚合报告修正并再次给出最终回复后,可以再次触发新一代完成检查;Shadow 没有发现时保持沉默,循环自然结束。
|
|
346
348
|
|
|
347
349
|
## 6. Shadow 的输出与介入
|
|
348
350
|
|
|
@@ -362,7 +364,7 @@ Shadow 返回
|
|
|
362
364
|
└── 丢弃旧结果
|
|
363
365
|
```
|
|
364
366
|
|
|
365
|
-
|
|
367
|
+
Heartbeat Shadow 不会因为 Main 给出最终回复而立即终止,迟到结果仍可在下一条用户消息到来前触发补充回复。Final-response Shadow 则按 review generation 聚合,只有整批检查结束后才允许触发一次补充回复。
|
|
366
368
|
|
|
367
369
|
每个用户任务拥有递增的 `epoch`:
|
|
368
370
|
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ This Shadow is read-only. It reviews the implementation in parallel and reports
|
|
|
72
72
|
|
|
73
73
|
Each Shadow chooses one or both activation triggers with `trigger`. The default is `[heartbeat]`: after a main-agent `turn_end` that completed at least one tool call, the extension evaluates the global heartbeat probability, then eligible Shadows roll independently using `activation_probability`. Pure text-only conversation turns are skipped.
|
|
74
74
|
|
|
75
|
-
Use `trigger: [final_response]` for completion review. It activates after the main agent has emitted its final text
|
|
75
|
+
Use `trigger: [final_response]` for completion review. It activates after the main agent has emitted its final text and bypasses both heartbeat and activation probability. All checks for that final response finish before their findings are sent together through one `shadow-report` follow-up, so a slow sibling cannot leak into a revised answer. `trigger: [heartbeat, final_response]` enables both modes. `max_parallel_shadows` remains the concurrency limit; excess final-response checks are queued rather than skipped.
|
|
76
76
|
|
|
77
77
|
Each activation starts a fresh temporary session. It inherits the main agent's unchanged system prompt but receives only a sanitized plain-text trajectory: assistant thinking is removed, while tool calls retain compact, deterministic result summaries.
|
|
78
78
|
|
|
@@ -83,7 +83,7 @@ Shadow definitions are ordinary Markdown files. They can be created and adjusted
|
|
|
83
83
|
## Installation
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
pi install npm:pi-shadow-mind
|
|
86
|
+
pi install npm:pi-shadow-mind
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
On the first session start, the extension creates:
|
package/README.zh-CN.md
CHANGED
|
@@ -69,7 +69,7 @@ tools: [read, grep]
|
|
|
69
69
|
|
|
70
70
|
每个 Shadow 可以通过 `trigger` 选择一种或两种激活方式。默认值是 `[heartbeat]`:主 Agent 的一次 `turn_end` 只有在该轮至少完成过一个工具调用时,扩展才进行全局 heartbeat 概率判断,符合条件的 Shadow 再按照各自的 `activation_probability` 独立抽选。纯文本对话轮次不会触发 heartbeat。
|
|
71
71
|
|
|
72
|
-
使用 `trigger: [final_response]` 可以进行完成后审查。它在主 Agent 发出最终文字并完全 settled 后激活,不受 heartbeat 和 `activation_probability`
|
|
72
|
+
使用 `trigger: [final_response]` 可以进行完成后审查。它在主 Agent 发出最终文字并完全 settled 后激活,不受 heartbeat 和 `activation_probability` 影响。同一最终回复的全部检查结束后,发现会合并为一次 `shadow-report` follow-up,较慢的同批检查不会介入已经修订的回复。`trigger: [heartbeat, final_response]` 会同时启用两种模式。`max_parallel_shadows` 仍然限制并发数;超出并发槽位的最终回复检查会排队,而不会被跳过。
|
|
73
73
|
|
|
74
74
|
每次激活都会创建一个全新的临时 Session。它继承主 Agent 原封不动的 system prompt,但只接收净化后的文本轨迹:思考内容会被移除,工具调用后仅保留简洁、确定性的结果概述。
|
|
75
75
|
|
|
@@ -80,7 +80,7 @@ Shadow 定义只是普通 Markdown 文件,可以由用户创建和调整,也
|
|
|
80
80
|
## 安装
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
pi install npm:pi-shadow-mind
|
|
83
|
+
pi install npm:pi-shadow-mind
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
首次启动 Session 时,扩展会创建:
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,121 @@ import {
|
|
|
12
12
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { Text } from "@earendil-works/pi-tui";
|
|
14
14
|
|
|
15
|
+
// src/final-response-queue.ts
|
|
16
|
+
var FinalResponseQueue = class {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
pending = [];
|
|
21
|
+
enqueue(items) {
|
|
22
|
+
this.pending.push(...items);
|
|
23
|
+
this.pump();
|
|
24
|
+
}
|
|
25
|
+
slotAvailable() {
|
|
26
|
+
this.pump();
|
|
27
|
+
}
|
|
28
|
+
clear() {
|
|
29
|
+
this.pending.length = 0;
|
|
30
|
+
}
|
|
31
|
+
pump() {
|
|
32
|
+
while (this.options.activeCount() < this.options.maxParallel() && this.pending.length > 0) {
|
|
33
|
+
const epoch = this.options.currentEpoch();
|
|
34
|
+
const activeShadowIds = this.options.activeShadowIds();
|
|
35
|
+
const index = this.pending.findIndex(
|
|
36
|
+
(item2) => item2.epoch !== epoch || !activeShadowIds.has(item2.shadowId)
|
|
37
|
+
);
|
|
38
|
+
if (index < 0) return;
|
|
39
|
+
const [item] = this.pending.splice(index, 1);
|
|
40
|
+
if (!item || item.epoch !== epoch) continue;
|
|
41
|
+
this.options.launch(item);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/completion-review.ts
|
|
47
|
+
var CompletionReview = class {
|
|
48
|
+
constructor(host) {
|
|
49
|
+
this.host = host;
|
|
50
|
+
this.queue = new FinalResponseQueue({
|
|
51
|
+
currentEpoch: () => this.host.currentEpoch(),
|
|
52
|
+
maxParallel: () => this.host.maxParallel(),
|
|
53
|
+
activeCount: () => this.host.activeCount(),
|
|
54
|
+
activeShadowIds: () => this.host.activeShadowIds(),
|
|
55
|
+
launch: ({ job, run }) => host.launch(job, run)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
requestId = 0;
|
|
59
|
+
state;
|
|
60
|
+
requestByRun = /* @__PURE__ */ new WeakMap();
|
|
61
|
+
queue;
|
|
62
|
+
begin(epoch) {
|
|
63
|
+
this.invalidate();
|
|
64
|
+
return { id: this.requestId, epoch };
|
|
65
|
+
}
|
|
66
|
+
isCurrent(request) {
|
|
67
|
+
return request.id === this.requestId && request.epoch === this.host.currentEpoch();
|
|
68
|
+
}
|
|
69
|
+
schedule(request, jobs) {
|
|
70
|
+
if (!this.isCurrent(request) || this.state?.requestId === request.id || jobs.some((job) => job.epoch !== request.epoch)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (jobs.length === 0) {
|
|
74
|
+
this.retire(request.id);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
this.state = {
|
|
78
|
+
requestId: request.id,
|
|
79
|
+
epoch: request.epoch,
|
|
80
|
+
remaining: jobs.length,
|
|
81
|
+
reports: []
|
|
82
|
+
};
|
|
83
|
+
this.queue.enqueue(
|
|
84
|
+
jobs.map((job) => {
|
|
85
|
+
const run = {
|
|
86
|
+
accept: (report) => this.accept(request.id, report)
|
|
87
|
+
};
|
|
88
|
+
this.requestByRun.set(run, request.id);
|
|
89
|
+
return { epoch: job.epoch, shadowId: job.shadowId, job, run };
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
slotAvailable() {
|
|
95
|
+
this.queue.slotAvailable();
|
|
96
|
+
}
|
|
97
|
+
complete(run) {
|
|
98
|
+
if (!run) return;
|
|
99
|
+
const requestId = this.requestByRun.get(run);
|
|
100
|
+
this.requestByRun.delete(run);
|
|
101
|
+
if (requestId === void 0 || this.state?.requestId !== requestId) return;
|
|
102
|
+
this.state.remaining -= 1;
|
|
103
|
+
if (this.state.remaining > 0) return;
|
|
104
|
+
const reports = this.state.reports;
|
|
105
|
+
this.retire(requestId);
|
|
106
|
+
if (reports.length > 0) this.host.deliver(reports);
|
|
107
|
+
}
|
|
108
|
+
cancel(request) {
|
|
109
|
+
if (this.isCurrent(request)) this.retire(request.id);
|
|
110
|
+
}
|
|
111
|
+
invalidate() {
|
|
112
|
+
this.requestId += 1;
|
|
113
|
+
this.state = void 0;
|
|
114
|
+
this.queue.clear();
|
|
115
|
+
}
|
|
116
|
+
retire(requestId) {
|
|
117
|
+
if (requestId !== this.requestId) return;
|
|
118
|
+
this.requestId += 1;
|
|
119
|
+
this.state = void 0;
|
|
120
|
+
this.queue.clear();
|
|
121
|
+
}
|
|
122
|
+
accept(requestId, report) {
|
|
123
|
+
if (this.state?.requestId !== requestId || report.epoch !== this.state.epoch) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.state.reports.push(report);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
15
130
|
// src/config.ts
|
|
16
131
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
17
132
|
import { dirname, join } from "node:path";
|
|
@@ -7007,37 +7122,6 @@ function definedOnly(value) {
|
|
|
7007
7122
|
);
|
|
7008
7123
|
}
|
|
7009
7124
|
|
|
7010
|
-
// src/final-response-queue.ts
|
|
7011
|
-
var FinalResponseQueue = class {
|
|
7012
|
-
constructor(options) {
|
|
7013
|
-
this.options = options;
|
|
7014
|
-
}
|
|
7015
|
-
pending = [];
|
|
7016
|
-
enqueue(items) {
|
|
7017
|
-
this.pending.push(...items);
|
|
7018
|
-
this.pump();
|
|
7019
|
-
}
|
|
7020
|
-
slotAvailable() {
|
|
7021
|
-
this.pump();
|
|
7022
|
-
}
|
|
7023
|
-
clear() {
|
|
7024
|
-
this.pending.length = 0;
|
|
7025
|
-
}
|
|
7026
|
-
pump() {
|
|
7027
|
-
while (this.options.activeCount() < this.options.maxParallel() && this.pending.length > 0) {
|
|
7028
|
-
const epoch = this.options.currentEpoch();
|
|
7029
|
-
const activeShadowIds = this.options.activeShadowIds();
|
|
7030
|
-
const index = this.pending.findIndex(
|
|
7031
|
-
(item2) => item2.epoch !== epoch || !activeShadowIds.has(item2.shadowId)
|
|
7032
|
-
);
|
|
7033
|
-
if (index < 0) return;
|
|
7034
|
-
const [item] = this.pending.splice(index, 1);
|
|
7035
|
-
if (!item || item.epoch !== epoch) continue;
|
|
7036
|
-
this.options.launch(item);
|
|
7037
|
-
}
|
|
7038
|
-
}
|
|
7039
|
-
};
|
|
7040
|
-
|
|
7041
7125
|
// src/management-tools.ts
|
|
7042
7126
|
import { Type } from "typebox";
|
|
7043
7127
|
var ID = Type.String({ pattern: "^[a-z0-9][a-z0-9_-]*$" });
|
|
@@ -8280,7 +8364,10 @@ var ShadowMindRuntime = class {
|
|
|
8280
8364
|
this.pi = pi;
|
|
8281
8365
|
this.batcher = new ReportBatcher(
|
|
8282
8366
|
this.configStore.current.resultBatchWindowMs,
|
|
8283
|
-
(reports) =>
|
|
8367
|
+
(reports) => {
|
|
8368
|
+
this.completionReview.invalidate();
|
|
8369
|
+
void this.deliverReports(reports);
|
|
8370
|
+
}
|
|
8284
8371
|
);
|
|
8285
8372
|
}
|
|
8286
8373
|
agentDir = getAgentDir();
|
|
@@ -8293,22 +8380,14 @@ var ShadowMindRuntime = class {
|
|
|
8293
8380
|
runner = new ShadowRunner();
|
|
8294
8381
|
usageStore = new UsageStore(this.agentDir);
|
|
8295
8382
|
active = /* @__PURE__ */ new Map();
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
pending.shadow,
|
|
8305
|
-
pending.mainModel,
|
|
8306
|
-
pending.fullModelId,
|
|
8307
|
-
pending.context,
|
|
8308
|
-
pending.availableTools
|
|
8309
|
-
)
|
|
8310
|
-
}
|
|
8311
|
-
);
|
|
8383
|
+
completionReview = new CompletionReview({
|
|
8384
|
+
currentEpoch: () => this.epoch,
|
|
8385
|
+
maxParallel: () => this.configStore.current.maxParallelShadows,
|
|
8386
|
+
activeCount: () => this.active.size,
|
|
8387
|
+
activeShadowIds: () => new Set([...this.active.values()].map(({ shadow }) => shadow.id)),
|
|
8388
|
+
launch: (pending, review) => this.launchShadow({ ...pending, completionReview: review }),
|
|
8389
|
+
deliver: (reports) => void this.deliverReports(reports)
|
|
8390
|
+
});
|
|
8312
8391
|
recentEvents = [];
|
|
8313
8392
|
recentRuns = [];
|
|
8314
8393
|
batcher;
|
|
@@ -8458,7 +8537,7 @@ var ShadowMindRuntime = class {
|
|
|
8458
8537
|
);
|
|
8459
8538
|
}
|
|
8460
8539
|
async onHeartbeat(ctx) {
|
|
8461
|
-
await this.refresh(ctx);
|
|
8540
|
+
const snapshot = await this.refresh(ctx);
|
|
8462
8541
|
if (this.paused || !ctx.model) {
|
|
8463
8542
|
this.record("heartbeat-skipped", {
|
|
8464
8543
|
reason: this.paused ? "paused" : "no-model",
|
|
@@ -8466,7 +8545,6 @@ var ShadowMindRuntime = class {
|
|
|
8466
8545
|
});
|
|
8467
8546
|
return;
|
|
8468
8547
|
}
|
|
8469
|
-
const snapshot = await this.registry.load();
|
|
8470
8548
|
const fullModelId = `${ctx.model.provider}/${ctx.model.id}`;
|
|
8471
8549
|
const decision = decideHeartbeat({
|
|
8472
8550
|
heartbeatProbability: this.configStore.current.heartbeatProbability,
|
|
@@ -8501,25 +8579,30 @@ var ShadowMindRuntime = class {
|
|
|
8501
8579
|
this.pi.getAllTools().map((tool2) => tool2.name)
|
|
8502
8580
|
);
|
|
8503
8581
|
for (const { shadow } of decision.activated) {
|
|
8504
|
-
this.launchShadow(
|
|
8582
|
+
this.launchShadow({
|
|
8505
8583
|
ctx,
|
|
8506
8584
|
shadow,
|
|
8507
|
-
ctx.model,
|
|
8585
|
+
mainModel: ctx.model,
|
|
8508
8586
|
fullModelId,
|
|
8509
8587
|
context,
|
|
8510
8588
|
availableTools
|
|
8511
|
-
);
|
|
8589
|
+
});
|
|
8512
8590
|
}
|
|
8513
8591
|
}
|
|
8514
8592
|
async onFinalResponse(ctx) {
|
|
8515
|
-
|
|
8593
|
+
const request = this.completionReview.begin(this.epoch);
|
|
8594
|
+
const snapshot = await this.refresh(ctx);
|
|
8595
|
+
if (!this.completionReview.isCurrent(request)) {
|
|
8596
|
+
this.record("final-response-skipped", { reason: "superseded" });
|
|
8597
|
+
return;
|
|
8598
|
+
}
|
|
8516
8599
|
if (this.paused || !ctx.model) {
|
|
8600
|
+
this.completionReview.cancel(request);
|
|
8517
8601
|
this.record("final-response-skipped", {
|
|
8518
8602
|
reason: this.paused ? "paused" : "no-model"
|
|
8519
8603
|
});
|
|
8520
8604
|
return;
|
|
8521
8605
|
}
|
|
8522
|
-
const snapshot = await this.registry.load();
|
|
8523
8606
|
const mainModel = ctx.model;
|
|
8524
8607
|
const fullModelId = `${mainModel.provider}/${mainModel.id}`;
|
|
8525
8608
|
const decision = decideFinalResponse({
|
|
@@ -8532,7 +8615,10 @@ var ShadowMindRuntime = class {
|
|
|
8532
8615
|
...decision.modelFiltered.length ? { modelFiltered: decision.modelFiltered } : {},
|
|
8533
8616
|
...decision.runningExcluded.length ? { runningExcluded: decision.runningExcluded } : {}
|
|
8534
8617
|
});
|
|
8535
|
-
if (!decision.activated.length)
|
|
8618
|
+
if (!decision.activated.length) {
|
|
8619
|
+
this.completionReview.schedule(request, []);
|
|
8620
|
+
return;
|
|
8621
|
+
}
|
|
8536
8622
|
const context = buildSessionContext(
|
|
8537
8623
|
ctx.sessionManager.getEntries(),
|
|
8538
8624
|
ctx.sessionManager.getLeafId()
|
|
@@ -8540,9 +8626,10 @@ var ShadowMindRuntime = class {
|
|
|
8540
8626
|
const availableTools = new Set(
|
|
8541
8627
|
this.pi.getAllTools().map((tool2) => tool2.name)
|
|
8542
8628
|
);
|
|
8543
|
-
this.
|
|
8629
|
+
this.completionReview.schedule(
|
|
8630
|
+
request,
|
|
8544
8631
|
decision.activated.map(({ shadow }) => ({
|
|
8545
|
-
epoch:
|
|
8632
|
+
epoch: request.epoch,
|
|
8546
8633
|
shadowId: shadow.id,
|
|
8547
8634
|
ctx,
|
|
8548
8635
|
shadow,
|
|
@@ -8553,11 +8640,22 @@ var ShadowMindRuntime = class {
|
|
|
8553
8640
|
}))
|
|
8554
8641
|
);
|
|
8555
8642
|
}
|
|
8556
|
-
launchShadow(
|
|
8643
|
+
launchShadow(options) {
|
|
8644
|
+
const {
|
|
8645
|
+
ctx,
|
|
8646
|
+
shadow,
|
|
8647
|
+
mainModel,
|
|
8648
|
+
fullModelId,
|
|
8649
|
+
context,
|
|
8650
|
+
availableTools,
|
|
8651
|
+
completionReview
|
|
8652
|
+
} = options;
|
|
8557
8653
|
const runId = randomUUID3();
|
|
8558
|
-
const runEpoch = this.epoch;
|
|
8654
|
+
const runEpoch = options.epoch ?? this.epoch;
|
|
8559
8655
|
const { tools, missing } = resolveShadowTools(shadow.tools, availableTools);
|
|
8560
|
-
|
|
8656
|
+
const activeRun = { shadow, epoch: runEpoch };
|
|
8657
|
+
if (completionReview) activeRun.completionReview = completionReview;
|
|
8658
|
+
this.active.set(runId, activeRun);
|
|
8561
8659
|
this.record("run-start", {
|
|
8562
8660
|
runId,
|
|
8563
8661
|
shadowId: shadow.id,
|
|
@@ -8573,30 +8671,39 @@ var ShadowMindRuntime = class {
|
|
|
8573
8671
|
cwd: ctx.cwd,
|
|
8574
8672
|
agentDir: this.agentDir,
|
|
8575
8673
|
mainSystemPrompt: ctx.getSystemPrompt(),
|
|
8576
|
-
// SAFETY: buildSessionContext returns Pi
|
|
8674
|
+
// SAFETY: buildSessionContext returns Pi messages; ShadowRunner treats them as read-only records.
|
|
8577
8675
|
messages: context.messages,
|
|
8578
8676
|
mainModel,
|
|
8579
8677
|
tools,
|
|
8580
8678
|
resolveModel: (id) => resolveModel(ctx, id),
|
|
8581
8679
|
modelAuthOk: (model) => ctx.modelRegistry.hasConfiguredAuth(model) || ctx.modelRegistry.isUsingOAuth(model),
|
|
8582
8680
|
mainThinkingLevel: ctx.thinkingLevel,
|
|
8583
|
-
onReport: (report) =>
|
|
8681
|
+
onReport: (report) => {
|
|
8682
|
+
if (completionReview) completionReview.accept(report);
|
|
8683
|
+
else this.acceptReport(report);
|
|
8684
|
+
}
|
|
8584
8685
|
}).then((result) => this.handleRunEnd(runId, shadow, result)).catch((error) => {
|
|
8686
|
+
const activeRun2 = this.active.get(runId);
|
|
8585
8687
|
this.active.delete(runId);
|
|
8688
|
+
this.completionReview.slotAvailable();
|
|
8689
|
+
this.completionReview.complete(activeRun2?.completionReview);
|
|
8586
8690
|
this.record("run-end", {
|
|
8587
8691
|
runId,
|
|
8588
8692
|
shadowId: shadow.id,
|
|
8589
8693
|
reason: "error",
|
|
8590
8694
|
error: error instanceof Error ? error.message : String(error)
|
|
8591
8695
|
});
|
|
8592
|
-
this.finalResponseQueue.slotAvailable();
|
|
8593
8696
|
});
|
|
8594
8697
|
}
|
|
8595
8698
|
handleRunEnd(runId, shadow, result) {
|
|
8596
8699
|
const activeRun = this.active.get(runId);
|
|
8597
8700
|
this.active.delete(runId);
|
|
8701
|
+
this.completionReview.slotAvailable();
|
|
8598
8702
|
const persisted = this.usageStore.add(result.usage);
|
|
8599
|
-
if (activeRun?.epoch !== this.epoch)
|
|
8703
|
+
if (activeRun?.epoch !== this.epoch) {
|
|
8704
|
+
this.completionReview.complete(activeRun?.completionReview);
|
|
8705
|
+
return;
|
|
8706
|
+
}
|
|
8600
8707
|
this.sessionUsage = addUsage(this.sessionUsage, result.usage);
|
|
8601
8708
|
this.recentRuns.push({
|
|
8602
8709
|
shadowName: shadow.name,
|
|
@@ -8607,7 +8714,7 @@ var ShadowMindRuntime = class {
|
|
|
8607
8714
|
this.record("run-end", { runId, shadowId: shadow.id, ...result });
|
|
8608
8715
|
if (this.latestContext && this.sessionLifetime.isActive)
|
|
8609
8716
|
this.updateStatus(this.latestContext);
|
|
8610
|
-
this.
|
|
8717
|
+
this.completionReview.complete(activeRun.completionReview);
|
|
8611
8718
|
void persisted.then(() => {
|
|
8612
8719
|
if (this.latestContext && this.sessionLifetime.isActive)
|
|
8613
8720
|
this.updateStatus(this.latestContext);
|
|
@@ -8655,10 +8762,11 @@ var ShadowMindRuntime = class {
|
|
|
8655
8762
|
)
|
|
8656
8763
|
];
|
|
8657
8764
|
this.updateStatus(ctx);
|
|
8765
|
+
return registry;
|
|
8658
8766
|
}
|
|
8659
8767
|
abortAll(reason) {
|
|
8660
8768
|
this.runner.abortAll();
|
|
8661
|
-
this.
|
|
8769
|
+
this.completionReview.invalidate();
|
|
8662
8770
|
this.batcher.clear();
|
|
8663
8771
|
this.record("runs-aborted", { reason, count: this.active.size });
|
|
8664
8772
|
}
|