pi-shadow-mind 0.1.18 → 0.1.20
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-Evolution.md +47 -25
- package/DESIGN.md +13 -7
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/index.js +363 -40
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,11 +6,8 @@ var __export = (target, all) => {
|
|
|
6
6
|
|
|
7
7
|
// src/runtime.ts
|
|
8
8
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
9
|
-
import {
|
|
10
|
-
buildSessionContext
|
|
11
|
-
} from "@earendil-works/pi-coding-agent";
|
|
12
9
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
10
|
+
import { Text as Text2 } from "@earendil-works/pi-tui";
|
|
14
11
|
|
|
15
12
|
// src/final-response-queue.ts
|
|
16
13
|
var FinalResponseQueue = class {
|
|
@@ -145,6 +142,12 @@ function isNonEmptyString(value) {
|
|
|
145
142
|
function isStringArray(value) {
|
|
146
143
|
return Array.isArray(value) && value.every((item) => isNonEmptyString(item));
|
|
147
144
|
}
|
|
145
|
+
function cleanStringArray(value, fallback, name) {
|
|
146
|
+
if (value === void 0) return [...fallback];
|
|
147
|
+
if (!isStringArray(value))
|
|
148
|
+
throw new Error(`${name} must be an array of non-empty strings`);
|
|
149
|
+
return [...new Set(value.map((item) => item.trim()))];
|
|
150
|
+
}
|
|
148
151
|
function isThinkingLevel(value) {
|
|
149
152
|
return typeof value === "string" && THINKING_LEVELS.has(value);
|
|
150
153
|
}
|
|
@@ -152,6 +155,7 @@ function isThinkingLevel(value) {
|
|
|
152
155
|
// src/config.ts
|
|
153
156
|
var DEFAULT_CONFIG = {
|
|
154
157
|
heartbeatProbability: 1 / 3,
|
|
158
|
+
heartbeatTools: [],
|
|
155
159
|
maxParallelShadows: 2,
|
|
156
160
|
defaultShadowTimeoutSeconds: 300,
|
|
157
161
|
headlessDrainTimeoutSeconds: 120,
|
|
@@ -164,6 +168,7 @@ function parseConfig(input) {
|
|
|
164
168
|
}
|
|
165
169
|
const value = input;
|
|
166
170
|
const probability = numberInRange(value.heartbeat_probability, 0, 1, DEFAULT_CONFIG.heartbeatProbability, "heartbeat_probability");
|
|
171
|
+
const heartbeatTools = cleanStringArray(value.heartbeat_tools, DEFAULT_CONFIG.heartbeatTools, "heartbeat_tools");
|
|
167
172
|
const parallel = positiveInteger(value.max_parallel_shadows, DEFAULT_CONFIG.maxParallelShadows, "max_parallel_shadows");
|
|
168
173
|
const timeout = positiveNumber(value.default_shadow_timeout_seconds, DEFAULT_CONFIG.defaultShadowTimeoutSeconds, "default_shadow_timeout_seconds");
|
|
169
174
|
const drainTimeout = positiveNumber(value.headless_drain_timeout_seconds, DEFAULT_CONFIG.headlessDrainTimeoutSeconds, "headless_drain_timeout_seconds");
|
|
@@ -176,6 +181,7 @@ function parseConfig(input) {
|
|
|
176
181
|
}
|
|
177
182
|
return {
|
|
178
183
|
heartbeatProbability: probability,
|
|
184
|
+
heartbeatTools,
|
|
179
185
|
maxParallelShadows: parallel,
|
|
180
186
|
defaultShadowTimeoutSeconds: timeout,
|
|
181
187
|
headlessDrainTimeoutSeconds: drainTimeout,
|
|
@@ -188,6 +194,7 @@ function parseConfig(input) {
|
|
|
188
194
|
function serializeConfig(config) {
|
|
189
195
|
return `${JSON.stringify({
|
|
190
196
|
heartbeat_probability: config.heartbeatProbability,
|
|
197
|
+
...config.heartbeatTools && config.heartbeatTools.length ? { heartbeat_tools: config.heartbeatTools } : {},
|
|
191
198
|
max_parallel_shadows: config.maxParallelShadows,
|
|
192
199
|
default_shadow_timeout_seconds: config.defaultShadowTimeoutSeconds,
|
|
193
200
|
headless_drain_timeout_seconds: config.headlessDrainTimeoutSeconds,
|
|
@@ -6968,7 +6975,7 @@ function parseShadowMarkdown(source, filePath) {
|
|
|
6968
6975
|
"activation_probability"
|
|
6969
6976
|
),
|
|
6970
6977
|
trigger: triggerArray(value.trigger, ["heartbeat"], "trigger"),
|
|
6971
|
-
activeForModels:
|
|
6978
|
+
activeForModels: cleanStringArray(
|
|
6972
6979
|
value.active_for_models,
|
|
6973
6980
|
["*"],
|
|
6974
6981
|
"active_for_models"
|
|
@@ -6979,7 +6986,12 @@ function parseShadowMarkdown(source, filePath) {
|
|
|
6979
6986
|
value.timeout_seconds,
|
|
6980
6987
|
"timeout_seconds"
|
|
6981
6988
|
),
|
|
6982
|
-
|
|
6989
|
+
activationTools: cleanStringArray(
|
|
6990
|
+
value.activation_tools,
|
|
6991
|
+
[],
|
|
6992
|
+
"activation_tools"
|
|
6993
|
+
),
|
|
6994
|
+
tools: cleanStringArray(value.tools, [], "tools"),
|
|
6983
6995
|
prompt,
|
|
6984
6996
|
filePath
|
|
6985
6997
|
};
|
|
@@ -7010,15 +7022,9 @@ function optionalPositiveNumber(value, name) {
|
|
|
7010
7022
|
throw new Error(`${name} must be positive`);
|
|
7011
7023
|
return value;
|
|
7012
7024
|
}
|
|
7013
|
-
function stringArray(value, fallback, name) {
|
|
7014
|
-
if (value === void 0) return [...fallback];
|
|
7015
|
-
if (!isStringArray(value))
|
|
7016
|
-
throw new Error(`${name} must be an array of non-empty strings`);
|
|
7017
|
-
return [...new Set(value.map((item) => item.trim()))];
|
|
7018
|
-
}
|
|
7019
7025
|
function triggerArray(value, fallback, name) {
|
|
7020
7026
|
const values = typeof value === "string" ? [value] : value;
|
|
7021
|
-
const items =
|
|
7027
|
+
const items = cleanStringArray(values, fallback, name);
|
|
7022
7028
|
const allowed = new Set(SHADOW_TRIGGERS);
|
|
7023
7029
|
const invalid = items.filter((item) => !allowed.has(item));
|
|
7024
7030
|
if (invalid.length)
|
|
@@ -7104,6 +7110,7 @@ function serializeShadow(shadow) {
|
|
|
7104
7110
|
...shadow.runWithModel ? { run_with_model: shadow.runWithModel } : {},
|
|
7105
7111
|
...shadow.thinkingLevel ? { thinking_level: shadow.thinkingLevel } : {},
|
|
7106
7112
|
...shadow.timeoutSeconds !== void 0 ? { timeout_seconds: shadow.timeoutSeconds } : {},
|
|
7113
|
+
...shadow.activationTools && shadow.activationTools.length ? { activation_tools: shadow.activationTools } : {},
|
|
7107
7114
|
tools: shadow.tools ?? []
|
|
7108
7115
|
};
|
|
7109
7116
|
return `---
|
|
@@ -7114,7 +7121,8 @@ ${(shadow.prompt ?? "").trim()}
|
|
|
7114
7121
|
`;
|
|
7115
7122
|
}
|
|
7116
7123
|
function describeShadow(shadow) {
|
|
7117
|
-
|
|
7124
|
+
const actTools = shadow.activationTools.length ? ` act_tools=${shadow.activationTools.join(",")}` : "";
|
|
7125
|
+
return `${shadow.enabled ? "enabled" : "disabled"} ${shadow.id} (${shadow.name}) p=${shadow.activationProbability} trigger=${shadow.trigger.join(",")} models=${shadow.activeForModels.join(",")}${actTools} tools=${shadow.tools.join(",") || "default"} file=${basename2(shadow.filePath)}`;
|
|
7118
7126
|
}
|
|
7119
7127
|
function definedOnly(value) {
|
|
7120
7128
|
return Object.fromEntries(
|
|
@@ -7149,6 +7157,7 @@ var SHADOW_FIELDS = {
|
|
|
7149
7157
|
run_with_model: Type.Optional(Type.String()),
|
|
7150
7158
|
thinking_level: Type.Optional(THINKING),
|
|
7151
7159
|
timeout_seconds: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
|
|
7160
|
+
activation_tools: Type.Optional(Type.Array(Type.String())),
|
|
7152
7161
|
tools: Type.Optional(Type.Array(Type.String())),
|
|
7153
7162
|
prompt: Type.Optional(Type.String())
|
|
7154
7163
|
};
|
|
@@ -7270,6 +7279,7 @@ function configWriteTool(store, getConfig) {
|
|
|
7270
7279
|
heartbeat_probability: Type.Optional(
|
|
7271
7280
|
Type.Number({ minimum: 0, maximum: 1 })
|
|
7272
7281
|
),
|
|
7282
|
+
heartbeat_tools: Type.Optional(Type.Array(Type.String())),
|
|
7273
7283
|
max_parallel_shadows: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
7274
7284
|
default_shadow_timeout_seconds: Type.Optional(
|
|
7275
7285
|
Type.Number({ exclusiveMinimum: 0 })
|
|
@@ -7289,6 +7299,7 @@ function configWriteTool(store, getConfig) {
|
|
|
7289
7299
|
const raw = params;
|
|
7290
7300
|
const next = parseConfig({
|
|
7291
7301
|
heartbeat_probability: raw.heartbeat_probability ?? current.heartbeatProbability,
|
|
7302
|
+
heartbeat_tools: raw.heartbeat_tools ?? current.heartbeatTools,
|
|
7292
7303
|
max_parallel_shadows: raw.max_parallel_shadows ?? current.maxParallelShadows,
|
|
7293
7304
|
default_shadow_timeout_seconds: raw.default_shadow_timeout_seconds ?? current.defaultShadowTimeoutSeconds,
|
|
7294
7305
|
headless_drain_timeout_seconds: raw.headless_drain_timeout_seconds ?? current.headlessDrainTimeoutSeconds,
|
|
@@ -7336,6 +7347,7 @@ function toPatch(raw) {
|
|
|
7336
7347
|
runWithModel: raw.run_with_model,
|
|
7337
7348
|
thinkingLevel: raw.thinking_level,
|
|
7338
7349
|
timeoutSeconds: raw.timeout_seconds,
|
|
7350
|
+
activationTools: raw.activation_tools,
|
|
7339
7351
|
tools: raw.tools,
|
|
7340
7352
|
prompt: raw.prompt
|
|
7341
7353
|
};
|
|
@@ -7385,6 +7397,147 @@ function formatReportBatch(reports) {
|
|
|
7385
7397
|
${report.content}`).join("\n\n");
|
|
7386
7398
|
}
|
|
7387
7399
|
|
|
7400
|
+
// src/report-history.ts
|
|
7401
|
+
var ReportHistory = class {
|
|
7402
|
+
constructor(limit = 5) {
|
|
7403
|
+
this.limit = limit;
|
|
7404
|
+
if (!Number.isInteger(limit) || limit < 0) throw new Error("Report limit must be a non-negative integer");
|
|
7405
|
+
}
|
|
7406
|
+
reports = [];
|
|
7407
|
+
add(reports, deliveredAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
7408
|
+
for (const report of reports) {
|
|
7409
|
+
this.reports = this.reports.filter(({ runId }) => runId !== report.runId);
|
|
7410
|
+
this.reports.push({ ...report, deliveredAt });
|
|
7411
|
+
if (this.reports.length > this.limit) this.reports.shift();
|
|
7412
|
+
}
|
|
7413
|
+
}
|
|
7414
|
+
/** Newest delivery first; callers receive an isolated snapshot. */
|
|
7415
|
+
list() {
|
|
7416
|
+
return this.reports.map((report) => ({ ...report })).reverse();
|
|
7417
|
+
}
|
|
7418
|
+
clear() {
|
|
7419
|
+
this.reports = [];
|
|
7420
|
+
}
|
|
7421
|
+
};
|
|
7422
|
+
|
|
7423
|
+
// src/report-viewer.ts
|
|
7424
|
+
import { matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
7425
|
+
var ReportViewer = class {
|
|
7426
|
+
constructor(reports, theme, host) {
|
|
7427
|
+
this.theme = theme;
|
|
7428
|
+
this.host = host;
|
|
7429
|
+
this.body = new Text(reports.map(
|
|
7430
|
+
(report) => `${report.shadowName} \xB7 ${report.deliveredAt}
|
|
7431
|
+
run ${report.runId}
|
|
7432
|
+
${report.content.replace(/\r\n?/g, "\n")}`
|
|
7433
|
+
).join("\n\n"), 0, 0);
|
|
7434
|
+
this.actions = [
|
|
7435
|
+
["escape", () => host.close()],
|
|
7436
|
+
["up", () => this.scroll(-1)],
|
|
7437
|
+
["down", () => this.scroll(1)],
|
|
7438
|
+
["pageUp", () => this.scroll(-this.pageSize)],
|
|
7439
|
+
["pageDown", () => this.scroll(this.pageSize)],
|
|
7440
|
+
["home", () => {
|
|
7441
|
+
this.offset = 0;
|
|
7442
|
+
}],
|
|
7443
|
+
["end", () => {
|
|
7444
|
+
this.offset = this.maxOffset();
|
|
7445
|
+
}]
|
|
7446
|
+
];
|
|
7447
|
+
}
|
|
7448
|
+
body;
|
|
7449
|
+
offset = 0;
|
|
7450
|
+
contentHeight = 0;
|
|
7451
|
+
pageSize = 1;
|
|
7452
|
+
actions;
|
|
7453
|
+
render(width) {
|
|
7454
|
+
const height = Math.max(1, Math.min(Math.floor(this.host.rows() * 0.8), this.host.rows() - 2));
|
|
7455
|
+
this.pageSize = Math.max(1, height - 2);
|
|
7456
|
+
const lines = this.body.render(Math.max(1, width));
|
|
7457
|
+
this.contentHeight = lines.length;
|
|
7458
|
+
this.offset = Math.min(this.offset, this.maxOffset());
|
|
7459
|
+
const page = lines.slice(this.offset, this.offset + this.pageSize);
|
|
7460
|
+
if (height < 3) return page.slice(0, height).map((line) => truncateToWidth(line, width));
|
|
7461
|
+
return [
|
|
7462
|
+
this.theme.fg("accent", truncateToWidth("Recent Shadow reports \xB7 newest first", width)),
|
|
7463
|
+
...page,
|
|
7464
|
+
this.theme.fg("dim", truncateToWidth(
|
|
7465
|
+
`Esc close \xB7 \u2191\u2193 PgUp/PgDn Home/End \xB7 ${this.offset + 1}\u2013${Math.min(this.offset + this.pageSize, lines.length)}/${lines.length}`,
|
|
7466
|
+
width
|
|
7467
|
+
))
|
|
7468
|
+
];
|
|
7469
|
+
}
|
|
7470
|
+
handleInput(data) {
|
|
7471
|
+
const action = this.actions.find(([key]) => matchesKey(data, key));
|
|
7472
|
+
if (!action) return;
|
|
7473
|
+
action[1]();
|
|
7474
|
+
this.host.requestRender();
|
|
7475
|
+
}
|
|
7476
|
+
invalidate() {
|
|
7477
|
+
this.body.invalidate();
|
|
7478
|
+
}
|
|
7479
|
+
maxOffset() {
|
|
7480
|
+
return Math.max(0, this.contentHeight - this.pageSize);
|
|
7481
|
+
}
|
|
7482
|
+
scroll(lines) {
|
|
7483
|
+
this.offset = Math.max(0, Math.min(this.maxOffset(), this.offset + lines));
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
7486
|
+
|
|
7487
|
+
// src/report-browser.ts
|
|
7488
|
+
var ReportBrowser = class {
|
|
7489
|
+
history = new ReportHistory();
|
|
7490
|
+
closeViewer;
|
|
7491
|
+
add(reports) {
|
|
7492
|
+
this.history.add(reports);
|
|
7493
|
+
}
|
|
7494
|
+
reset() {
|
|
7495
|
+
this.closeViewer?.();
|
|
7496
|
+
this.history.clear();
|
|
7497
|
+
}
|
|
7498
|
+
async handleCommand(command, ctx) {
|
|
7499
|
+
const [name, ...args] = command.split(/\s+/);
|
|
7500
|
+
if (name !== "reports") return false;
|
|
7501
|
+
if (args.length && args.join(" ") !== "hide") {
|
|
7502
|
+
ctx.ui.notify("Usage: /shadow reports [hide]", "warning");
|
|
7503
|
+
return true;
|
|
7504
|
+
}
|
|
7505
|
+
if (args[0] === "hide" || this.closeViewer) {
|
|
7506
|
+
this.closeViewer?.();
|
|
7507
|
+
return true;
|
|
7508
|
+
}
|
|
7509
|
+
if (ctx.mode !== "tui") {
|
|
7510
|
+
if (ctx.hasUI) ctx.ui.notify("The report viewer requires TUI mode. Delivered reports remain in the conversation.", "warning");
|
|
7511
|
+
return true;
|
|
7512
|
+
}
|
|
7513
|
+
const reports = this.history.list();
|
|
7514
|
+
if (!reports.length) {
|
|
7515
|
+
ctx.ui.notify("No reports delivered in this session yet.", "info");
|
|
7516
|
+
return true;
|
|
7517
|
+
}
|
|
7518
|
+
const view = { close: () => {
|
|
7519
|
+
} };
|
|
7520
|
+
try {
|
|
7521
|
+
await ctx.ui.custom((tui, theme, _keys, done) => {
|
|
7522
|
+
view.close = () => {
|
|
7523
|
+
if (this.closeViewer !== view.close) return;
|
|
7524
|
+
this.closeViewer = void 0;
|
|
7525
|
+
done();
|
|
7526
|
+
};
|
|
7527
|
+
this.closeViewer = view.close;
|
|
7528
|
+
return new ReportViewer(reports, theme, {
|
|
7529
|
+
rows: () => tui.terminal.rows,
|
|
7530
|
+
requestRender: () => tui.requestRender(),
|
|
7531
|
+
close: view.close
|
|
7532
|
+
});
|
|
7533
|
+
}, { overlay: true, overlayOptions: { width: "90%", maxHeight: "80%", margin: 1 } });
|
|
7534
|
+
} finally {
|
|
7535
|
+
if (this.closeViewer === view.close) this.closeViewer = void 0;
|
|
7536
|
+
}
|
|
7537
|
+
return true;
|
|
7538
|
+
}
|
|
7539
|
+
};
|
|
7540
|
+
|
|
7388
7541
|
// src/random.ts
|
|
7389
7542
|
function createRandom(seed) {
|
|
7390
7543
|
if (seed === void 0) return Math.random;
|
|
@@ -7398,9 +7551,143 @@ function createRandom(seed) {
|
|
|
7398
7551
|
};
|
|
7399
7552
|
}
|
|
7400
7553
|
|
|
7554
|
+
// src/acp-projection.ts
|
|
7555
|
+
import {
|
|
7556
|
+
buildContextEntries,
|
|
7557
|
+
buildSessionContext,
|
|
7558
|
+
sessionEntryToContextMessages
|
|
7559
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7560
|
+
|
|
7561
|
+
// src/acp-state.ts
|
|
7562
|
+
import { readFileSync } from "node:fs";
|
|
7563
|
+
function isRecord(value) {
|
|
7564
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7565
|
+
}
|
|
7566
|
+
function readAcpBlocks(sessionFile) {
|
|
7567
|
+
if (!sessionFile) return [];
|
|
7568
|
+
try {
|
|
7569
|
+
const state = JSON.parse(readFileSync(`${sessionFile}.acp.json`, "utf8"));
|
|
7570
|
+
if (!isRecord(state) || !Array.isArray(state.blocks)) return [];
|
|
7571
|
+
const blocks = [];
|
|
7572
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7573
|
+
for (const block of state.blocks) {
|
|
7574
|
+
if (!isRecord(block)) return [];
|
|
7575
|
+
if (block.active === false) continue;
|
|
7576
|
+
if (block.active !== true || typeof block.blockId !== "string" || !block.blockId || typeof block.summary !== "string" || !block.summary.trim() || !Array.isArray(block.effectiveMessageIds) || !block.effectiveMessageIds.every((id) => typeof id === "string" && id.length > 0) || seen.has(block.blockId)) return [];
|
|
7577
|
+
seen.add(block.blockId);
|
|
7578
|
+
blocks.push({
|
|
7579
|
+
blockId: block.blockId,
|
|
7580
|
+
summary: block.summary,
|
|
7581
|
+
effectiveMessageIds: [...block.effectiveMessageIds]
|
|
7582
|
+
});
|
|
7583
|
+
}
|
|
7584
|
+
return blocks;
|
|
7585
|
+
} catch {
|
|
7586
|
+
return [];
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7590
|
+
// src/acp-projection.ts
|
|
7591
|
+
function buildShadowSessionContext(entries, leafId, sessionFile) {
|
|
7592
|
+
const context = buildSessionContext(entries, leafId);
|
|
7593
|
+
const blocks = readAcpBlocks(sessionFile);
|
|
7594
|
+
if (!blocks.length) return context;
|
|
7595
|
+
const visible = buildContextEntries(entries, leafId).flatMap(
|
|
7596
|
+
(entry) => sessionEntryToContextMessages(entry).map((message) => ({ id: entry.id, message }))
|
|
7597
|
+
);
|
|
7598
|
+
return { ...context, messages: projectMessages(visible, blocks) };
|
|
7599
|
+
}
|
|
7600
|
+
function toolCalls(message) {
|
|
7601
|
+
return message.role === "assistant" ? message.content.filter((part) => part.type === "toolCall") : [];
|
|
7602
|
+
}
|
|
7603
|
+
function coverageIds({ id, message }) {
|
|
7604
|
+
const calls = toolCalls(message);
|
|
7605
|
+
return calls.length > 1 ? calls.map((call) => `${id}#${call.id}`) : [id];
|
|
7606
|
+
}
|
|
7607
|
+
function summaryMessage(block) {
|
|
7608
|
+
return {
|
|
7609
|
+
role: "compactionSummary",
|
|
7610
|
+
summary: `[ACP block ${block.blockId} \u2014 ${block.effectiveMessageIds.length} folded messages]
|
|
7611
|
+
${block.summary}`,
|
|
7612
|
+
tokensBefore: 0,
|
|
7613
|
+
timestamp: 0
|
|
7614
|
+
};
|
|
7615
|
+
}
|
|
7616
|
+
function projectMessages(visible, blocks) {
|
|
7617
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
7618
|
+
visible.forEach((item, index) => {
|
|
7619
|
+
for (const id of coverageIds(item)) indexById.set(id, index);
|
|
7620
|
+
});
|
|
7621
|
+
const covered = /* @__PURE__ */ new Set();
|
|
7622
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
7623
|
+
for (const block of blocks) {
|
|
7624
|
+
const positions = block.effectiveMessageIds.flatMap((id) => {
|
|
7625
|
+
const index = indexById.get(id);
|
|
7626
|
+
return index === void 0 ? [] : [index];
|
|
7627
|
+
});
|
|
7628
|
+
if (!positions.length) continue;
|
|
7629
|
+
const first = positions.reduce((left, right) => Math.min(left, right));
|
|
7630
|
+
anchors.set(first, [...anchors.get(first) ?? [], block]);
|
|
7631
|
+
for (const id of block.effectiveMessageIds) covered.add(id);
|
|
7632
|
+
}
|
|
7633
|
+
if (!covered.size) return visible.map(({ message }) => message);
|
|
7634
|
+
const firstUser = visible.findIndex(
|
|
7635
|
+
({ message }) => message.role === "user" || message.role === "custom"
|
|
7636
|
+
);
|
|
7637
|
+
const messages = [];
|
|
7638
|
+
visible.forEach((item, index) => {
|
|
7639
|
+
for (const block of anchors.get(index) ?? []) messages.push(summaryMessage(block));
|
|
7640
|
+
const retained = index === firstUser ? item.message : retainUncovered(item, covered);
|
|
7641
|
+
if (retained) messages.push(retained);
|
|
7642
|
+
});
|
|
7643
|
+
return removeOrphanedTools(messages);
|
|
7644
|
+
}
|
|
7645
|
+
function retainUncovered(item, covered) {
|
|
7646
|
+
const ids = coverageIds(item);
|
|
7647
|
+
if (!ids.some((id) => covered.has(id))) return item.message;
|
|
7648
|
+
if (ids.every((id) => covered.has(id))) return void 0;
|
|
7649
|
+
const message = item.message;
|
|
7650
|
+
if (message.role !== "assistant") return message;
|
|
7651
|
+
return {
|
|
7652
|
+
...message,
|
|
7653
|
+
content: message.content.filter(
|
|
7654
|
+
(part) => part.type !== "toolCall" || !covered.has(`${item.id}#${part.id}`)
|
|
7655
|
+
)
|
|
7656
|
+
};
|
|
7657
|
+
}
|
|
7658
|
+
function removeOrphanedTools(messages) {
|
|
7659
|
+
const resultIds = new Set(messages.flatMap(
|
|
7660
|
+
(message) => message.role === "toolResult" ? [message.toolCallId] : []
|
|
7661
|
+
));
|
|
7662
|
+
const paired = messages.flatMap((message) => {
|
|
7663
|
+
const calls = toolCalls(message);
|
|
7664
|
+
if (!calls.length || message.role !== "assistant") return [message];
|
|
7665
|
+
const retained = calls.filter((call) => call.name === "compress" || resultIds.has(call.id));
|
|
7666
|
+
if (!retained.length) return [];
|
|
7667
|
+
const ids = new Set(retained.map((call) => call.id));
|
|
7668
|
+
return [{ ...message, content: message.content.filter((part) => part.type !== "toolCall" || ids.has(part.id)) }];
|
|
7669
|
+
});
|
|
7670
|
+
const callIds = new Set(paired.flatMap((message) => toolCalls(message).map((call) => call.id)));
|
|
7671
|
+
return paired.filter((message) => message.role !== "toolResult" || callIds.has(message.toolCallId));
|
|
7672
|
+
}
|
|
7673
|
+
|
|
7401
7674
|
// src/scheduler.ts
|
|
7402
|
-
function
|
|
7403
|
-
|
|
7675
|
+
function extractToolNames(toolResults) {
|
|
7676
|
+
const names = /* @__PURE__ */ new Set();
|
|
7677
|
+
for (const item of toolResults) {
|
|
7678
|
+
if (!item || typeof item !== "object") continue;
|
|
7679
|
+
const name = item.toolName;
|
|
7680
|
+
if (typeof name === "string" && name.trim().length > 0) {
|
|
7681
|
+
names.add(name.trim());
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
return names;
|
|
7685
|
+
}
|
|
7686
|
+
function shouldEvaluateHeartbeat(toolsOrResults, heartbeatTools) {
|
|
7687
|
+
const toolNames = toolsOrResults instanceof Set ? toolsOrResults : extractToolNames(toolsOrResults);
|
|
7688
|
+
if (toolNames.size === 0) return false;
|
|
7689
|
+
if (!heartbeatTools || heartbeatTools.length === 0) return true;
|
|
7690
|
+
return heartbeatTools.some((tool2) => toolNames.has(tool2));
|
|
7404
7691
|
}
|
|
7405
7692
|
function shouldEvaluateFinalResponse(messages) {
|
|
7406
7693
|
const message = messages.at(-1);
|
|
@@ -7423,11 +7710,13 @@ function decideHeartbeat(options) {
|
|
|
7423
7710
|
activated: [],
|
|
7424
7711
|
candidates: [],
|
|
7425
7712
|
modelFiltered: [],
|
|
7426
|
-
runningExcluded: []
|
|
7713
|
+
runningExcluded: [],
|
|
7714
|
+
toolFiltered: []
|
|
7427
7715
|
};
|
|
7428
7716
|
}
|
|
7429
7717
|
const modelFiltered = [];
|
|
7430
7718
|
const runningExcluded = [];
|
|
7719
|
+
const toolFiltered = [];
|
|
7431
7720
|
const rolls = options.shadows.filter((shadow) => {
|
|
7432
7721
|
if (!shadow.enabled || !hasTrigger(shadow, "heartbeat")) return false;
|
|
7433
7722
|
if (options.activeShadowIds.has(shadow.id)) {
|
|
@@ -7438,6 +7727,10 @@ function decideHeartbeat(options) {
|
|
|
7438
7727
|
modelFiltered.push(shadow.id);
|
|
7439
7728
|
return false;
|
|
7440
7729
|
}
|
|
7730
|
+
if (!matchesActivationTools(shadow, options.executedTools)) {
|
|
7731
|
+
toolFiltered.push(shadow.id);
|
|
7732
|
+
return false;
|
|
7733
|
+
}
|
|
7441
7734
|
return true;
|
|
7442
7735
|
}).map((shadow) => ({ shadow, roll: random() }));
|
|
7443
7736
|
const hits = rolls.filter(
|
|
@@ -7458,7 +7751,8 @@ function decideHeartbeat(options) {
|
|
|
7458
7751
|
selected: selectedIds.has(shadow.id)
|
|
7459
7752
|
})),
|
|
7460
7753
|
modelFiltered,
|
|
7461
|
-
runningExcluded
|
|
7754
|
+
runningExcluded,
|
|
7755
|
+
toolFiltered
|
|
7462
7756
|
};
|
|
7463
7757
|
}
|
|
7464
7758
|
function decideFinalResponse(options) {
|
|
@@ -7486,6 +7780,14 @@ function decideFinalResponse(options) {
|
|
|
7486
7780
|
function matchesModel(shadow, fullModelId) {
|
|
7487
7781
|
return shadow.activeForModels.includes("*") || shadow.activeForModels.includes(fullModelId);
|
|
7488
7782
|
}
|
|
7783
|
+
function matchesActivationTools(shadow, executedTools) {
|
|
7784
|
+
if (shadow.activationTools.length === 0) {
|
|
7785
|
+
return true;
|
|
7786
|
+
}
|
|
7787
|
+
if (!executedTools) return false;
|
|
7788
|
+
const executedSet = executedTools instanceof Set ? executedTools : new Set(executedTools);
|
|
7789
|
+
return shadow.activationTools.some((tool2) => executedSet.has(tool2));
|
|
7790
|
+
}
|
|
7489
7791
|
function hasTrigger(shadow, trigger) {
|
|
7490
7792
|
return shadow.trigger.includes(trigger);
|
|
7491
7793
|
}
|
|
@@ -7716,7 +8018,7 @@ function serializeTrajectory(messages) {
|
|
|
7716
8018
|
continue;
|
|
7717
8019
|
}
|
|
7718
8020
|
if (message.role === "compactionSummary" || message.role === "branchSummary") {
|
|
7719
|
-
appendText(lines, "SUMMARY", message.content);
|
|
8021
|
+
appendText(lines, "SUMMARY", message.summary ?? message.content);
|
|
7720
8022
|
}
|
|
7721
8023
|
}
|
|
7722
8024
|
return `<main-agent-trajectory>
|
|
@@ -8390,6 +8692,7 @@ var ShadowMindRuntime = class {
|
|
|
8390
8692
|
});
|
|
8391
8693
|
recentEvents = [];
|
|
8392
8694
|
recentRuns = [];
|
|
8695
|
+
reports = new ReportBrowser();
|
|
8393
8696
|
batcher;
|
|
8394
8697
|
sessionLifetime = new SessionLifetime();
|
|
8395
8698
|
epoch = 0;
|
|
@@ -8419,6 +8722,7 @@ var ShadowMindRuntime = class {
|
|
|
8419
8722
|
this.completedWithFinalText = false;
|
|
8420
8723
|
this.sessionUsage = zeroUsage();
|
|
8421
8724
|
this.recentRuns.length = 0;
|
|
8725
|
+
this.reports.reset();
|
|
8422
8726
|
await this.configStore.initialize();
|
|
8423
8727
|
await this.usageStore.initialize();
|
|
8424
8728
|
this.random = createRandom(this.configStore.current.randomSeed);
|
|
@@ -8442,14 +8746,16 @@ var ShadowMindRuntime = class {
|
|
|
8442
8746
|
});
|
|
8443
8747
|
this.pi.on("turn_end", async (event, ctx) => {
|
|
8444
8748
|
this.latestContext = ctx;
|
|
8445
|
-
|
|
8749
|
+
const toolResults = event.toolResults ?? [];
|
|
8750
|
+
const executedTools = extractToolNames(toolResults);
|
|
8751
|
+
if (executedTools.size === 0) {
|
|
8446
8752
|
this.record("heartbeat-skipped", {
|
|
8447
8753
|
reason: "no-tool-activity",
|
|
8448
8754
|
modelCalls: this.modelCalls
|
|
8449
8755
|
});
|
|
8450
8756
|
return;
|
|
8451
8757
|
}
|
|
8452
|
-
await this.onHeartbeat(ctx);
|
|
8758
|
+
await this.onHeartbeat(ctx, executedTools);
|
|
8453
8759
|
});
|
|
8454
8760
|
this.pi.on("agent_end", (event, ctx) => {
|
|
8455
8761
|
this.latestContext = ctx;
|
|
@@ -8481,6 +8787,7 @@ var ShadowMindRuntime = class {
|
|
|
8481
8787
|
if (!result.settled) this.active.clear();
|
|
8482
8788
|
}
|
|
8483
8789
|
await this.usageStore.flush();
|
|
8790
|
+
this.reports.reset();
|
|
8484
8791
|
ctx.ui.setStatus("shadow-mind", void 0);
|
|
8485
8792
|
ctx.ui.setWidget("shadow-mind-panel", void 0);
|
|
8486
8793
|
this.sessionLifetime.deactivate();
|
|
@@ -8504,6 +8811,7 @@ var ShadowMindRuntime = class {
|
|
|
8504
8811
|
this.setPaused(!this.paused, ctx);
|
|
8505
8812
|
return;
|
|
8506
8813
|
}
|
|
8814
|
+
if (await this.reports.handleCommand(command, ctx)) return;
|
|
8507
8815
|
if (command === "status") {
|
|
8508
8816
|
await this.refresh(ctx);
|
|
8509
8817
|
ctx.ui.notify(
|
|
@@ -8520,24 +8828,34 @@ var ShadowMindRuntime = class {
|
|
|
8520
8828
|
this.updateStatus(ctx);
|
|
8521
8829
|
}
|
|
8522
8830
|
});
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8831
|
+
for (const shortcut of ["f6", "alt+s"]) {
|
|
8832
|
+
this.pi.registerShortcut(shortcut, {
|
|
8833
|
+
description: "Pause or resume Shadow Mind",
|
|
8834
|
+
handler: (ctx) => {
|
|
8835
|
+
this.latestContext = ctx;
|
|
8836
|
+
this.setPaused(!this.paused, ctx);
|
|
8837
|
+
}
|
|
8838
|
+
});
|
|
8839
|
+
}
|
|
8530
8840
|
this.pi.registerMessageRenderer(
|
|
8531
8841
|
"shadow-report",
|
|
8532
8842
|
(message, _options, theme) => {
|
|
8533
8843
|
const content = typeof message.content === "string" ? message.content : "Shadow report";
|
|
8534
8844
|
const prefix = theme.fg("accent", "\u{1F419} shadow \xB7 ");
|
|
8535
|
-
return new
|
|
8845
|
+
return new Text2(`${prefix}${content}`, 0, 0);
|
|
8536
8846
|
}
|
|
8537
8847
|
);
|
|
8538
8848
|
}
|
|
8539
|
-
async onHeartbeat(ctx) {
|
|
8849
|
+
async onHeartbeat(ctx, executedTools) {
|
|
8540
8850
|
const snapshot = await this.refresh(ctx);
|
|
8851
|
+
const config = this.configStore.current;
|
|
8852
|
+
if (!shouldEvaluateHeartbeat(executedTools, config.heartbeatTools)) {
|
|
8853
|
+
this.record("heartbeat-skipped", {
|
|
8854
|
+
reason: "tool-filtered",
|
|
8855
|
+
modelCalls: this.modelCalls
|
|
8856
|
+
});
|
|
8857
|
+
return;
|
|
8858
|
+
}
|
|
8541
8859
|
if (this.paused || !ctx.model) {
|
|
8542
8860
|
this.record("heartbeat-skipped", {
|
|
8543
8861
|
reason: this.paused ? "paused" : "no-model",
|
|
@@ -8547,17 +8865,18 @@ var ShadowMindRuntime = class {
|
|
|
8547
8865
|
}
|
|
8548
8866
|
const fullModelId = `${ctx.model.provider}/${ctx.model.id}`;
|
|
8549
8867
|
const decision = decideHeartbeat({
|
|
8550
|
-
heartbeatProbability:
|
|
8868
|
+
heartbeatProbability: config.heartbeatProbability,
|
|
8551
8869
|
availableSlots: Math.max(
|
|
8552
8870
|
0,
|
|
8553
|
-
|
|
8871
|
+
config.maxParallelShadows - this.active.size
|
|
8554
8872
|
),
|
|
8555
8873
|
shadows: snapshot.shadows,
|
|
8556
8874
|
activeShadowIds: new Set(
|
|
8557
8875
|
[...this.active.values()].map(({ shadow }) => shadow.id)
|
|
8558
8876
|
),
|
|
8559
8877
|
mainModelId: fullModelId,
|
|
8560
|
-
random: this.random
|
|
8878
|
+
random: this.random,
|
|
8879
|
+
executedTools
|
|
8561
8880
|
});
|
|
8562
8881
|
this.record("heartbeat", {
|
|
8563
8882
|
modelCalls: this.modelCalls,
|
|
@@ -8568,12 +8887,14 @@ var ShadowMindRuntime = class {
|
|
|
8568
8887
|
roll
|
|
8569
8888
|
})),
|
|
8570
8889
|
...decision.modelFiltered.length ? { modelFiltered: decision.modelFiltered } : {},
|
|
8571
|
-
...decision.runningExcluded.length ? { runningExcluded: decision.runningExcluded } : {}
|
|
8890
|
+
...decision.runningExcluded.length ? { runningExcluded: decision.runningExcluded } : {},
|
|
8891
|
+
...decision.toolFiltered.length ? { toolFiltered: decision.toolFiltered } : {}
|
|
8572
8892
|
});
|
|
8573
8893
|
if (!decision.activated.length) return;
|
|
8574
|
-
const context =
|
|
8894
|
+
const context = buildShadowSessionContext(
|
|
8575
8895
|
ctx.sessionManager.getEntries(),
|
|
8576
|
-
ctx.sessionManager.getLeafId()
|
|
8896
|
+
ctx.sessionManager.getLeafId(),
|
|
8897
|
+
ctx.sessionManager.getSessionFile()
|
|
8577
8898
|
);
|
|
8578
8899
|
const availableTools = new Set(
|
|
8579
8900
|
this.pi.getAllTools().map((tool2) => tool2.name)
|
|
@@ -8619,9 +8940,10 @@ var ShadowMindRuntime = class {
|
|
|
8619
8940
|
this.completionReview.schedule(request, []);
|
|
8620
8941
|
return;
|
|
8621
8942
|
}
|
|
8622
|
-
const context =
|
|
8943
|
+
const context = buildShadowSessionContext(
|
|
8623
8944
|
ctx.sessionManager.getEntries(),
|
|
8624
|
-
ctx.sessionManager.getLeafId()
|
|
8945
|
+
ctx.sessionManager.getLeafId(),
|
|
8946
|
+
ctx.sessionManager.getSessionFile()
|
|
8625
8947
|
);
|
|
8626
8948
|
const availableTools = new Set(
|
|
8627
8949
|
this.pi.getAllTools().map((tool2) => tool2.name)
|
|
@@ -8733,7 +9055,7 @@ var ShadowMindRuntime = class {
|
|
|
8733
9055
|
count: current.length
|
|
8734
9056
|
});
|
|
8735
9057
|
const idle = this.latestContext?.isIdle() ?? true;
|
|
8736
|
-
this.sessionLifetime.run(() => {
|
|
9058
|
+
const delivered = this.sessionLifetime.run(() => {
|
|
8737
9059
|
this.pi.sendMessage(
|
|
8738
9060
|
{
|
|
8739
9061
|
customType: "shadow-report",
|
|
@@ -8749,6 +9071,7 @@ var ShadowMindRuntime = class {
|
|
|
8749
9071
|
{ triggerTurn: true, deliverAs: idle ? "followUp" : "steer" }
|
|
8750
9072
|
);
|
|
8751
9073
|
});
|
|
9074
|
+
if (delivered) this.reports.add(current);
|
|
8752
9075
|
}
|
|
8753
9076
|
async refresh(ctx) {
|
|
8754
9077
|
const config = await this.configStore.reload();
|
|
@@ -8850,7 +9173,7 @@ var ShadowMindRuntime = class {
|
|
|
8850
9173
|
const detail = failed ? ` ${event.data?.error ?? event.data?.reason}` : "";
|
|
8851
9174
|
return `${new Date(event.at).toLocaleTimeString("en-GB", { hour12: false })} ${event.kind}${detail}`;
|
|
8852
9175
|
}),
|
|
8853
|
-
"Shortcut: Alt+S toggle \xB7 Commands: /shadow toggle | pause | resume | status | hide"
|
|
9176
|
+
"Shortcut: Alt+S toggle \xB7 Commands: /shadow toggle | pause | resume | status | hide | reports"
|
|
8854
9177
|
];
|
|
8855
9178
|
}
|
|
8856
9179
|
};
|