pi-shadow-mind 0.1.14 → 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 +27 -10
- package/README.md +16 -111
- package/README.zh-CN.md +110 -0
- package/dist/index.js +1147 -137
- package/dist/index.js.map +4 -4
- package/package.json +14 -1
package/dist/index.js
CHANGED
|
@@ -5,13 +5,128 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/runtime.ts
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
9
9
|
import {
|
|
10
10
|
buildSessionContext
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
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";
|
|
@@ -6760,6 +6875,12 @@ var browser_default = dist_exports;
|
|
|
6760
6875
|
// src/registry.ts
|
|
6761
6876
|
import { mkdir as mkdir2, readFile as readFile2, readdir, stat } from "node:fs/promises";
|
|
6762
6877
|
import { basename, extname, join as join2 } from "node:path";
|
|
6878
|
+
|
|
6879
|
+
// src/types.ts
|
|
6880
|
+
var DEFAULT_READ_TOOLS = ["read", "grep", "find", "ls"];
|
|
6881
|
+
var SHADOW_TRIGGERS = ["heartbeat", "final_response"];
|
|
6882
|
+
|
|
6883
|
+
// src/registry.ts
|
|
6763
6884
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
6764
6885
|
var ShadowRegistry = class {
|
|
6765
6886
|
directory;
|
|
@@ -6787,8 +6908,14 @@ var ShadowRegistry = class {
|
|
|
6787
6908
|
continue;
|
|
6788
6909
|
}
|
|
6789
6910
|
try {
|
|
6790
|
-
const definition = parseShadowMarkdown(
|
|
6791
|
-
|
|
6911
|
+
const definition = parseShadowMarkdown(
|
|
6912
|
+
await readFile2(filePath, "utf8"),
|
|
6913
|
+
filePath
|
|
6914
|
+
);
|
|
6915
|
+
this.cache.set(filePath, {
|
|
6916
|
+
mtimeMs: fileStat.mtimeMs,
|
|
6917
|
+
shadow: definition
|
|
6918
|
+
});
|
|
6792
6919
|
shadows.push(definition);
|
|
6793
6920
|
} catch (error) {
|
|
6794
6921
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6803,7 +6930,10 @@ var ShadowRegistry = class {
|
|
|
6803
6930
|
const ids = /* @__PURE__ */ new Set();
|
|
6804
6931
|
for (const shadow of shadows) {
|
|
6805
6932
|
if (ids.has(shadow.id)) {
|
|
6806
|
-
diagnostics.push({
|
|
6933
|
+
diagnostics.push({
|
|
6934
|
+
filePath: shadow.filePath,
|
|
6935
|
+
message: `duplicate shadow id: ${shadow.id}`
|
|
6936
|
+
});
|
|
6807
6937
|
} else {
|
|
6808
6938
|
ids.add(shadow.id);
|
|
6809
6939
|
unique.push(shadow);
|
|
@@ -6813,14 +6943,18 @@ var ShadowRegistry = class {
|
|
|
6813
6943
|
}
|
|
6814
6944
|
};
|
|
6815
6945
|
function parseShadowMarkdown(source, filePath) {
|
|
6816
|
-
const match = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)([\s\S]*)$/.exec(
|
|
6946
|
+
const match = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)([\s\S]*)$/.exec(
|
|
6947
|
+
source
|
|
6948
|
+
);
|
|
6817
6949
|
if (!match) throw new Error("missing YAML frontmatter");
|
|
6818
6950
|
const meta = browser_default.parse(match[1]);
|
|
6819
|
-
if (!meta || typeof meta !== "object" || Array.isArray(meta))
|
|
6951
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta))
|
|
6952
|
+
throw new Error("frontmatter must be an object");
|
|
6820
6953
|
const value = meta;
|
|
6821
6954
|
const fallbackId = basename(filePath, extname(filePath));
|
|
6822
6955
|
const id = stringValue(value.id, fallbackId, "id");
|
|
6823
|
-
if (!ID_PATTERN.test(id))
|
|
6956
|
+
if (!ID_PATTERN.test(id))
|
|
6957
|
+
throw new Error("id must match [a-z0-9][a-z0-9_-]*");
|
|
6824
6958
|
const prompt = match[2].trim();
|
|
6825
6959
|
if (!prompt) throw new Error("shadow prompt body is empty");
|
|
6826
6960
|
return {
|
|
@@ -6828,11 +6962,23 @@ function parseShadowMarkdown(source, filePath) {
|
|
|
6828
6962
|
name: stringValue(value.name, id, "name"),
|
|
6829
6963
|
enabled: booleanValue(value.enabled, true, "enabled"),
|
|
6830
6964
|
debug: booleanValue(value.debug, false, "debug"),
|
|
6831
|
-
activationProbability: probabilityValue(
|
|
6832
|
-
|
|
6965
|
+
activationProbability: probabilityValue(
|
|
6966
|
+
value.activation_probability,
|
|
6967
|
+
0.3,
|
|
6968
|
+
"activation_probability"
|
|
6969
|
+
),
|
|
6970
|
+
trigger: triggerArray(value.trigger, ["heartbeat"], "trigger"),
|
|
6971
|
+
activeForModels: stringArray(
|
|
6972
|
+
value.active_for_models,
|
|
6973
|
+
["*"],
|
|
6974
|
+
"active_for_models"
|
|
6975
|
+
),
|
|
6833
6976
|
runWithModel: optionalString(value.run_with_model, "run_with_model"),
|
|
6834
6977
|
thinkingLevel: optionalThinking(value.thinking_level),
|
|
6835
|
-
timeoutSeconds: optionalPositiveNumber(
|
|
6978
|
+
timeoutSeconds: optionalPositiveNumber(
|
|
6979
|
+
value.timeout_seconds,
|
|
6980
|
+
"timeout_seconds"
|
|
6981
|
+
),
|
|
6836
6982
|
tools: stringArray(value.tools, [], "tools"),
|
|
6837
6983
|
prompt,
|
|
6838
6984
|
filePath
|
|
@@ -6840,7 +6986,8 @@ function parseShadowMarkdown(source, filePath) {
|
|
|
6840
6986
|
}
|
|
6841
6987
|
function stringValue(value, fallback, name) {
|
|
6842
6988
|
if (value === void 0) return fallback;
|
|
6843
|
-
if (!isNonEmptyString(value))
|
|
6989
|
+
if (!isNonEmptyString(value))
|
|
6990
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
6844
6991
|
return value.trim();
|
|
6845
6992
|
}
|
|
6846
6993
|
function optionalString(value, name) {
|
|
@@ -6853,19 +7000,31 @@ function booleanValue(value, fallback, name) {
|
|
|
6853
7000
|
}
|
|
6854
7001
|
function probabilityValue(value, fallback, name) {
|
|
6855
7002
|
if (value === void 0) return fallback;
|
|
6856
|
-
if (!isFiniteNumber(value) || !inRange(value, 0, 1))
|
|
7003
|
+
if (!isFiniteNumber(value) || !inRange(value, 0, 1))
|
|
7004
|
+
throw new Error(`${name} must be between 0 and 1`);
|
|
6857
7005
|
return value;
|
|
6858
7006
|
}
|
|
6859
7007
|
function optionalPositiveNumber(value, name) {
|
|
6860
7008
|
if (value === void 0) return void 0;
|
|
6861
|
-
if (!isFiniteNumber(value) || value <= 0)
|
|
7009
|
+
if (!isFiniteNumber(value) || value <= 0)
|
|
7010
|
+
throw new Error(`${name} must be positive`);
|
|
6862
7011
|
return value;
|
|
6863
7012
|
}
|
|
6864
7013
|
function stringArray(value, fallback, name) {
|
|
6865
7014
|
if (value === void 0) return [...fallback];
|
|
6866
|
-
if (!isStringArray(value))
|
|
7015
|
+
if (!isStringArray(value))
|
|
7016
|
+
throw new Error(`${name} must be an array of non-empty strings`);
|
|
6867
7017
|
return [...new Set(value.map((item) => item.trim()))];
|
|
6868
7018
|
}
|
|
7019
|
+
function triggerArray(value, fallback, name) {
|
|
7020
|
+
const values = typeof value === "string" ? [value] : value;
|
|
7021
|
+
const items = stringArray(values, fallback, name);
|
|
7022
|
+
const allowed = new Set(SHADOW_TRIGGERS);
|
|
7023
|
+
const invalid = items.filter((item) => !allowed.has(item));
|
|
7024
|
+
if (invalid.length)
|
|
7025
|
+
throw new Error(`${name} contains invalid trigger: ${invalid.join(", ")}`);
|
|
7026
|
+
return items;
|
|
7027
|
+
}
|
|
6869
7028
|
function optionalThinking(value) {
|
|
6870
7029
|
if (value === void 0) return void 0;
|
|
6871
7030
|
if (!isThinkingLevel(value)) throw new Error("thinking_level is invalid");
|
|
@@ -6887,17 +7046,37 @@ var EntityStore = class {
|
|
|
6887
7046
|
return shadow;
|
|
6888
7047
|
}
|
|
6889
7048
|
async create(draft) {
|
|
6890
|
-
if ((await this.list()).some((shadow) => shadow.id === draft.id))
|
|
6891
|
-
|
|
7049
|
+
if ((await this.list()).some((shadow) => shadow.id === draft.id))
|
|
7050
|
+
throw new Error(`shadow already exists: ${draft.id}`);
|
|
7051
|
+
return this.writeParsed(
|
|
7052
|
+
join3(this.registry.directory, `${draft.id}.md`),
|
|
7053
|
+
draft,
|
|
7054
|
+
{ overwrite: false }
|
|
7055
|
+
);
|
|
6892
7056
|
}
|
|
6893
7057
|
async update(id, patch) {
|
|
6894
7058
|
const current = await this.get(id);
|
|
6895
|
-
return this.writeParsed(
|
|
7059
|
+
return this.writeParsed(
|
|
7060
|
+
current.filePath,
|
|
7061
|
+
{
|
|
7062
|
+
...current,
|
|
7063
|
+
...definedOnly(patch),
|
|
7064
|
+
id,
|
|
7065
|
+
prompt: patch.prompt ?? current.prompt
|
|
7066
|
+
},
|
|
7067
|
+
{ overwrite: true }
|
|
7068
|
+
);
|
|
6896
7069
|
}
|
|
6897
7070
|
async writeParsed(filePath, draft, options) {
|
|
6898
|
-
const source = serializeShadow({
|
|
7071
|
+
const source = serializeShadow({
|
|
7072
|
+
...draft,
|
|
7073
|
+
prompt: draft.prompt ?? "Describe this Shadow Mind's responsibility."
|
|
7074
|
+
});
|
|
6899
7075
|
const parsed = parseShadowMarkdown(source, filePath);
|
|
6900
|
-
await writeFile2(filePath, source, {
|
|
7076
|
+
await writeFile2(filePath, source, {
|
|
7077
|
+
encoding: "utf8",
|
|
7078
|
+
...options.overwrite ? {} : { flag: "wx" }
|
|
7079
|
+
});
|
|
6901
7080
|
return parsed;
|
|
6902
7081
|
}
|
|
6903
7082
|
async setEnabled(id, enabled) {
|
|
@@ -6920,6 +7099,7 @@ function serializeShadow(shadow) {
|
|
|
6920
7099
|
enabled: shadow.enabled ?? true,
|
|
6921
7100
|
debug: shadow.debug ?? false,
|
|
6922
7101
|
activation_probability: shadow.activationProbability ?? 0.3,
|
|
7102
|
+
trigger: shadow.trigger ?? ["heartbeat"],
|
|
6923
7103
|
active_for_models: shadow.activeForModels ?? ["*"],
|
|
6924
7104
|
...shadow.runWithModel ? { run_with_model: shadow.runWithModel } : {},
|
|
6925
7105
|
...shadow.thinkingLevel ? { thinking_level: shadow.thinkingLevel } : {},
|
|
@@ -6934,21 +7114,37 @@ ${(shadow.prompt ?? "").trim()}
|
|
|
6934
7114
|
`;
|
|
6935
7115
|
}
|
|
6936
7116
|
function describeShadow(shadow) {
|
|
6937
|
-
return `${shadow.enabled ? "enabled" : "disabled"} ${shadow.id} (${shadow.name}) p=${shadow.activationProbability} models=${shadow.activeForModels.join(",")} tools=${shadow.tools.join(",") || "default"} file=${basename2(shadow.filePath)}`;
|
|
7117
|
+
return `${shadow.enabled ? "enabled" : "disabled"} ${shadow.id} (${shadow.name}) p=${shadow.activationProbability} trigger=${shadow.trigger.join(",")} models=${shadow.activeForModels.join(",")} tools=${shadow.tools.join(",") || "default"} file=${basename2(shadow.filePath)}`;
|
|
6938
7118
|
}
|
|
6939
7119
|
function definedOnly(value) {
|
|
6940
|
-
return Object.fromEntries(
|
|
7120
|
+
return Object.fromEntries(
|
|
7121
|
+
Object.entries(value).filter(([, item]) => item !== void 0)
|
|
7122
|
+
);
|
|
6941
7123
|
}
|
|
6942
7124
|
|
|
6943
7125
|
// src/management-tools.ts
|
|
6944
7126
|
import { Type } from "typebox";
|
|
6945
7127
|
var ID = Type.String({ pattern: "^[a-z0-9][a-z0-9_-]*$" });
|
|
6946
|
-
var THINKING = Type.Union([
|
|
7128
|
+
var THINKING = Type.Union([
|
|
7129
|
+
Type.Literal("minimal"),
|
|
7130
|
+
Type.Literal("low"),
|
|
7131
|
+
Type.Literal("medium"),
|
|
7132
|
+
Type.Literal("high"),
|
|
7133
|
+
Type.Literal("xhigh"),
|
|
7134
|
+
Type.Literal("max")
|
|
7135
|
+
]);
|
|
7136
|
+
var TRIGGER = Type.Union([
|
|
7137
|
+
Type.Literal("heartbeat"),
|
|
7138
|
+
Type.Literal("final_response")
|
|
7139
|
+
]);
|
|
6947
7140
|
var SHADOW_FIELDS = {
|
|
6948
7141
|
name: Type.Optional(Type.String()),
|
|
6949
7142
|
enabled: Type.Optional(Type.Boolean()),
|
|
6950
7143
|
debug: Type.Optional(Type.Boolean()),
|
|
6951
|
-
activation_probability: Type.Optional(
|
|
7144
|
+
activation_probability: Type.Optional(
|
|
7145
|
+
Type.Number({ minimum: 0, maximum: 1 })
|
|
7146
|
+
),
|
|
7147
|
+
trigger: Type.Optional(Type.Array(TRIGGER)),
|
|
6952
7148
|
active_for_models: Type.Optional(Type.Array(Type.String())),
|
|
6953
7149
|
run_with_model: Type.Optional(Type.String()),
|
|
6954
7150
|
thinking_level: Type.Optional(THINKING),
|
|
@@ -6967,80 +7163,160 @@ function registerManagementTools(pi, store, getConfig) {
|
|
|
6967
7163
|
pi.registerTool(configWriteTool(store, getConfig));
|
|
6968
7164
|
}
|
|
6969
7165
|
function listTool(store) {
|
|
6970
|
-
return tool(
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
7166
|
+
return tool(
|
|
7167
|
+
"list_shadows",
|
|
7168
|
+
"List Shadows",
|
|
7169
|
+
"List all valid globally configured Shadow Minds.",
|
|
7170
|
+
Type.Object({}),
|
|
7171
|
+
async () => {
|
|
7172
|
+
const shadows = await store.list();
|
|
7173
|
+
return textResult(
|
|
7174
|
+
shadows.length ? shadows.map(describeShadow).join("\n") : "No Shadow Minds configured."
|
|
7175
|
+
);
|
|
7176
|
+
}
|
|
7177
|
+
);
|
|
6974
7178
|
}
|
|
6975
7179
|
function createTool(store) {
|
|
6976
|
-
return tool(
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7180
|
+
return tool(
|
|
7181
|
+
"create_shadow",
|
|
7182
|
+
"Create Shadow",
|
|
7183
|
+
"Create a global Shadow Mind definition after user confirmation.",
|
|
7184
|
+
Type.Object({ id: ID, ...SHADOW_FIELDS, prompt: Type.String() }),
|
|
7185
|
+
async (_id, params, _signal, _update, ctx) => {
|
|
7186
|
+
const raw = params;
|
|
7187
|
+
if (!await confirm(
|
|
7188
|
+
ctx,
|
|
7189
|
+
"Create Shadow Mind",
|
|
7190
|
+
`Create ${String(raw.id)} in the global registry?`
|
|
7191
|
+
))
|
|
7192
|
+
return textResult("Cancelled.");
|
|
7193
|
+
const shadow = await store.create(toDraft(raw));
|
|
7194
|
+
return textResult(`Created ${describeShadow(shadow)}`);
|
|
7195
|
+
}
|
|
7196
|
+
);
|
|
6982
7197
|
}
|
|
6983
7198
|
function updateTool(store) {
|
|
6984
|
-
return tool(
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7199
|
+
return tool(
|
|
7200
|
+
"update_shadow",
|
|
7201
|
+
"Update Shadow",
|
|
7202
|
+
"Update a global Shadow Mind definition after user confirmation. The id is immutable.",
|
|
7203
|
+
Type.Object({ id: ID, ...SHADOW_FIELDS }),
|
|
7204
|
+
async (_id, params, _signal, _update, ctx) => {
|
|
7205
|
+
const raw = params;
|
|
7206
|
+
if (!await confirm(
|
|
7207
|
+
ctx,
|
|
7208
|
+
"Update Shadow Mind",
|
|
7209
|
+
`Apply changes to ${String(raw.id)}?`
|
|
7210
|
+
))
|
|
7211
|
+
return textResult("Cancelled.");
|
|
7212
|
+
const shadow = await store.update(String(raw.id), toPatch(raw));
|
|
7213
|
+
return textResult(`Updated ${describeShadow(shadow)}`);
|
|
7214
|
+
}
|
|
7215
|
+
);
|
|
6990
7216
|
}
|
|
6991
7217
|
function toggleTool(store, enabled) {
|
|
6992
7218
|
const action = enabled ? "enable" : "disable";
|
|
6993
|
-
return tool(
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
7219
|
+
return tool(
|
|
7220
|
+
`${action}_shadow`,
|
|
7221
|
+
`${enabled ? "Enable" : "Disable"} Shadow`,
|
|
7222
|
+
`${enabled ? "Enable" : "Disable"} a global Shadow Mind after user confirmation.`,
|
|
7223
|
+
Type.Object({ id: ID }),
|
|
7224
|
+
async (_id, params, _signal, _update, ctx) => {
|
|
7225
|
+
const id = String(params.id);
|
|
7226
|
+
if (!await confirm(
|
|
7227
|
+
ctx,
|
|
7228
|
+
`${enabled ? "Enable" : "Disable"} Shadow Mind`,
|
|
7229
|
+
`${action} ${id}?`
|
|
7230
|
+
))
|
|
7231
|
+
return textResult("Cancelled.");
|
|
7232
|
+
return textResult(describeShadow(await store.setEnabled(id, enabled)));
|
|
7233
|
+
}
|
|
7234
|
+
);
|
|
6998
7235
|
}
|
|
6999
7236
|
function deleteTool(store) {
|
|
7000
|
-
return tool(
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7237
|
+
return tool(
|
|
7238
|
+
"delete_shadow",
|
|
7239
|
+
"Delete Shadow",
|
|
7240
|
+
"Delete a Shadow Mind definition after user confirmation. Debug logs are retained.",
|
|
7241
|
+
Type.Object({ id: ID }),
|
|
7242
|
+
async (_id, params, _signal, _update, ctx) => {
|
|
7243
|
+
const id = String(params.id);
|
|
7244
|
+
if (!await confirm(
|
|
7245
|
+
ctx,
|
|
7246
|
+
"Delete Shadow Mind",
|
|
7247
|
+
`Delete ${id}? Its debug logs will be kept.`
|
|
7248
|
+
))
|
|
7249
|
+
return textResult("Cancelled.");
|
|
7250
|
+
await store.delete(id);
|
|
7251
|
+
return textResult(`Deleted ${id}; logs were retained.`);
|
|
7252
|
+
}
|
|
7253
|
+
);
|
|
7006
7254
|
}
|
|
7007
7255
|
function configReadTool(store) {
|
|
7008
|
-
return tool(
|
|
7256
|
+
return tool(
|
|
7257
|
+
"get_shadow_config",
|
|
7258
|
+
"Get Shadow Config",
|
|
7259
|
+
"Read the global Shadow Mind runtime configuration.",
|
|
7260
|
+
Type.Object({}),
|
|
7261
|
+
async () => textResult(await store.readConfig())
|
|
7262
|
+
);
|
|
7009
7263
|
}
|
|
7010
7264
|
function configWriteTool(store, getConfig) {
|
|
7011
|
-
return tool(
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
})
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7265
|
+
return tool(
|
|
7266
|
+
"update_shadow_config",
|
|
7267
|
+
"Update Shadow Config",
|
|
7268
|
+
"Update global Shadow Mind runtime configuration after user confirmation.",
|
|
7269
|
+
Type.Object({
|
|
7270
|
+
heartbeat_probability: Type.Optional(
|
|
7271
|
+
Type.Number({ minimum: 0, maximum: 1 })
|
|
7272
|
+
),
|
|
7273
|
+
max_parallel_shadows: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
7274
|
+
default_shadow_timeout_seconds: Type.Optional(
|
|
7275
|
+
Type.Number({ exclusiveMinimum: 0 })
|
|
7276
|
+
),
|
|
7277
|
+
headless_drain_timeout_seconds: Type.Optional(
|
|
7278
|
+
Type.Number({ exclusiveMinimum: 0 })
|
|
7279
|
+
),
|
|
7280
|
+
result_batch_window_ms: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
7281
|
+
default_shadow_model: Type.Optional(Type.String()),
|
|
7282
|
+
default_thinking_level: Type.Optional(THINKING),
|
|
7283
|
+
random_seed: Type.Optional(
|
|
7284
|
+
Type.Integer({ minimum: 0, maximum: 4294967295 })
|
|
7285
|
+
)
|
|
7286
|
+
}),
|
|
7287
|
+
async (_id, params, _signal, _update, ctx) => {
|
|
7288
|
+
const current = getConfig();
|
|
7289
|
+
const raw = params;
|
|
7290
|
+
const next = parseConfig({
|
|
7291
|
+
heartbeat_probability: raw.heartbeat_probability ?? current.heartbeatProbability,
|
|
7292
|
+
max_parallel_shadows: raw.max_parallel_shadows ?? current.maxParallelShadows,
|
|
7293
|
+
default_shadow_timeout_seconds: raw.default_shadow_timeout_seconds ?? current.defaultShadowTimeoutSeconds,
|
|
7294
|
+
headless_drain_timeout_seconds: raw.headless_drain_timeout_seconds ?? current.headlessDrainTimeoutSeconds,
|
|
7295
|
+
result_batch_window_ms: raw.result_batch_window_ms ?? current.resultBatchWindowMs,
|
|
7296
|
+
default_shadow_model: raw.default_shadow_model ?? current.defaultShadowModel,
|
|
7297
|
+
default_thinking_level: raw.default_thinking_level ?? current.defaultThinkingLevel,
|
|
7298
|
+
random_seed: raw.random_seed ?? current.randomSeed
|
|
7299
|
+
});
|
|
7300
|
+
if (!await confirm(
|
|
7301
|
+
ctx,
|
|
7302
|
+
"Update Shadow Config",
|
|
7303
|
+
`Apply this config?
|
|
7304
|
+
${JSON.stringify(next, null, 2)}`
|
|
7305
|
+
))
|
|
7306
|
+
return textResult("Cancelled.");
|
|
7307
|
+
await store.writeConfig(next);
|
|
7308
|
+
return textResult("Shadow Mind config updated.");
|
|
7309
|
+
}
|
|
7310
|
+
);
|
|
7038
7311
|
}
|
|
7039
7312
|
function tool(name, label, description, parameters, execute) {
|
|
7040
7313
|
return { name, label, description, parameters, execute };
|
|
7041
7314
|
}
|
|
7042
7315
|
async function confirm(ctx, title, message) {
|
|
7043
|
-
if (!ctx.hasUI)
|
|
7316
|
+
if (!ctx.hasUI)
|
|
7317
|
+
throw new Error(
|
|
7318
|
+
"This write requires a UI confirmation, but no dialog-capable UI is available."
|
|
7319
|
+
);
|
|
7044
7320
|
return ctx.ui.confirm(title, message);
|
|
7045
7321
|
}
|
|
7046
7322
|
function textResult(text) {
|
|
@@ -7055,6 +7331,7 @@ function toPatch(raw) {
|
|
|
7055
7331
|
enabled: raw.enabled,
|
|
7056
7332
|
debug: raw.debug,
|
|
7057
7333
|
activationProbability: raw.activation_probability,
|
|
7334
|
+
trigger: raw.trigger,
|
|
7058
7335
|
activeForModels: raw.active_for_models,
|
|
7059
7336
|
runWithModel: raw.run_with_model,
|
|
7060
7337
|
thinkingLevel: raw.thinking_level,
|
|
@@ -7125,16 +7402,34 @@ function createRandom(seed) {
|
|
|
7125
7402
|
function shouldEvaluateHeartbeat(toolResults) {
|
|
7126
7403
|
return toolResults.length > 0;
|
|
7127
7404
|
}
|
|
7405
|
+
function shouldEvaluateFinalResponse(messages) {
|
|
7406
|
+
const message = messages.at(-1);
|
|
7407
|
+
if (!message || typeof message !== "object" || message.role !== "assistant")
|
|
7408
|
+
return false;
|
|
7409
|
+
const content = message.content;
|
|
7410
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
7411
|
+
return Array.isArray(content) && content.some((block) => {
|
|
7412
|
+
if (!block || typeof block !== "object") return false;
|
|
7413
|
+
const value = block;
|
|
7414
|
+
return value.type === "text" && typeof value.text === "string" && value.text.trim().length > 0;
|
|
7415
|
+
});
|
|
7416
|
+
}
|
|
7128
7417
|
function decideHeartbeat(options) {
|
|
7129
7418
|
const random = options.random ?? Math.random;
|
|
7130
7419
|
const heartbeatRoll = random();
|
|
7131
7420
|
if (heartbeatRoll >= options.heartbeatProbability || options.availableSlots <= 0) {
|
|
7132
|
-
return {
|
|
7421
|
+
return {
|
|
7422
|
+
heartbeatRoll,
|
|
7423
|
+
activated: [],
|
|
7424
|
+
candidates: [],
|
|
7425
|
+
modelFiltered: [],
|
|
7426
|
+
runningExcluded: []
|
|
7427
|
+
};
|
|
7133
7428
|
}
|
|
7134
7429
|
const modelFiltered = [];
|
|
7135
7430
|
const runningExcluded = [];
|
|
7136
7431
|
const rolls = options.shadows.filter((shadow) => {
|
|
7137
|
-
if (!shadow.enabled) return false;
|
|
7432
|
+
if (!shadow.enabled || !hasTrigger(shadow, "heartbeat")) return false;
|
|
7138
7433
|
if (options.activeShadowIds.has(shadow.id)) {
|
|
7139
7434
|
runningExcluded.push(shadow.id);
|
|
7140
7435
|
return false;
|
|
@@ -7145,20 +7440,55 @@ function decideHeartbeat(options) {
|
|
|
7145
7440
|
}
|
|
7146
7441
|
return true;
|
|
7147
7442
|
}).map((shadow) => ({ shadow, roll: random() }));
|
|
7148
|
-
const hits = rolls.filter(
|
|
7149
|
-
|
|
7443
|
+
const hits = rolls.filter(
|
|
7444
|
+
({ shadow, roll }) => roll < shadow.activationProbability
|
|
7445
|
+
);
|
|
7446
|
+
const selected = sample(
|
|
7447
|
+
hits,
|
|
7448
|
+
Math.min(options.availableSlots, hits.length),
|
|
7449
|
+
random
|
|
7450
|
+
);
|
|
7150
7451
|
const selectedIds = new Set(selected.map(({ shadow }) => shadow.id));
|
|
7151
7452
|
return {
|
|
7152
7453
|
heartbeatRoll,
|
|
7153
7454
|
activated: selected,
|
|
7154
|
-
candidates: rolls.map(({ shadow, roll }) => ({
|
|
7455
|
+
candidates: rolls.map(({ shadow, roll }) => ({
|
|
7456
|
+
shadowId: shadow.id,
|
|
7457
|
+
roll,
|
|
7458
|
+
selected: selectedIds.has(shadow.id)
|
|
7459
|
+
})),
|
|
7155
7460
|
modelFiltered,
|
|
7156
7461
|
runningExcluded
|
|
7157
7462
|
};
|
|
7158
7463
|
}
|
|
7464
|
+
function decideFinalResponse(options) {
|
|
7465
|
+
const modelFiltered = [];
|
|
7466
|
+
const candidates = options.shadows.filter((shadow) => {
|
|
7467
|
+
if (!shadow.enabled || !hasTrigger(shadow, "final_response"))
|
|
7468
|
+
return false;
|
|
7469
|
+
if (!matchesModel(shadow, options.mainModelId)) {
|
|
7470
|
+
modelFiltered.push(shadow.id);
|
|
7471
|
+
return false;
|
|
7472
|
+
}
|
|
7473
|
+
return true;
|
|
7474
|
+
}).map((shadow) => ({ shadow, roll: 0 }));
|
|
7475
|
+
return {
|
|
7476
|
+
activated: candidates,
|
|
7477
|
+
candidates: candidates.map(({ shadow, roll }) => ({
|
|
7478
|
+
shadowId: shadow.id,
|
|
7479
|
+
roll,
|
|
7480
|
+
selected: true
|
|
7481
|
+
})),
|
|
7482
|
+
modelFiltered,
|
|
7483
|
+
runningExcluded: []
|
|
7484
|
+
};
|
|
7485
|
+
}
|
|
7159
7486
|
function matchesModel(shadow, fullModelId) {
|
|
7160
7487
|
return shadow.activeForModels.includes("*") || shadow.activeForModels.includes(fullModelId);
|
|
7161
7488
|
}
|
|
7489
|
+
function hasTrigger(shadow, trigger) {
|
|
7490
|
+
return shadow.trigger.includes(trigger);
|
|
7491
|
+
}
|
|
7162
7492
|
function sample(values, count, random) {
|
|
7163
7493
|
const copy = [...values];
|
|
7164
7494
|
for (let index = copy.length - 1; index > 0; index -= 1) {
|
|
@@ -7209,8 +7539,70 @@ import {
|
|
|
7209
7539
|
createAgentSession
|
|
7210
7540
|
} from "@earendil-works/pi-coding-agent";
|
|
7211
7541
|
|
|
7212
|
-
// src/
|
|
7213
|
-
|
|
7542
|
+
// src/usage.ts
|
|
7543
|
+
function zeroUsage() {
|
|
7544
|
+
return {
|
|
7545
|
+
requests: 0,
|
|
7546
|
+
input: 0,
|
|
7547
|
+
output: 0,
|
|
7548
|
+
cacheRead: 0,
|
|
7549
|
+
cacheWrite: 0,
|
|
7550
|
+
totalTokens: 0,
|
|
7551
|
+
cost: {
|
|
7552
|
+
input: 0,
|
|
7553
|
+
output: 0,
|
|
7554
|
+
cacheRead: 0,
|
|
7555
|
+
cacheWrite: 0,
|
|
7556
|
+
total: 0
|
|
7557
|
+
}
|
|
7558
|
+
};
|
|
7559
|
+
}
|
|
7560
|
+
function addUsage(left, right) {
|
|
7561
|
+
return {
|
|
7562
|
+
requests: left.requests + right.requests,
|
|
7563
|
+
input: left.input + right.input,
|
|
7564
|
+
output: left.output + right.output,
|
|
7565
|
+
cacheRead: left.cacheRead + right.cacheRead,
|
|
7566
|
+
cacheWrite: left.cacheWrite + right.cacheWrite,
|
|
7567
|
+
totalTokens: left.totalTokens + right.totalTokens,
|
|
7568
|
+
cost: {
|
|
7569
|
+
input: left.cost.input + right.cost.input,
|
|
7570
|
+
output: left.cost.output + right.cost.output,
|
|
7571
|
+
cacheRead: left.cost.cacheRead + right.cost.cacheRead,
|
|
7572
|
+
cacheWrite: left.cost.cacheWrite + right.cost.cacheWrite,
|
|
7573
|
+
total: left.cost.total + right.cost.total
|
|
7574
|
+
}
|
|
7575
|
+
};
|
|
7576
|
+
}
|
|
7577
|
+
function formatUsageTokens(tokens) {
|
|
7578
|
+
if (tokens < 1e3) return tokens.toLocaleString("en-US");
|
|
7579
|
+
if (tokens < 999950) return `${formatCompact(tokens / 1e3)}k`;
|
|
7580
|
+
return `${formatCompact(tokens / 1e6)}m`;
|
|
7581
|
+
}
|
|
7582
|
+
function formatUsageCost(cost) {
|
|
7583
|
+
if (cost === 0) return "$0";
|
|
7584
|
+
if (cost >= 0.01) return `$${cost.toFixed(2)}`;
|
|
7585
|
+
if (cost >= 1e-3) return `$${cost.toFixed(3)}`;
|
|
7586
|
+
return `$${cost.toFixed(4)}`;
|
|
7587
|
+
}
|
|
7588
|
+
function formatUsageSummary(usage) {
|
|
7589
|
+
return `${usage.requests} requests \xB7 ${formatUsageTokens(usage.totalTokens)} tokens \xB7 API ${formatUsageCost(usage.cost.total)}`;
|
|
7590
|
+
}
|
|
7591
|
+
function formatUsageDetail(scope, usage) {
|
|
7592
|
+
return [
|
|
7593
|
+
`usage ${scope}`,
|
|
7594
|
+
`${usage.requests} requests`,
|
|
7595
|
+
`${formatUsageTokens(usage.totalTokens)} total`,
|
|
7596
|
+
`${formatUsageTokens(usage.input)} input`,
|
|
7597
|
+
`${formatUsageTokens(usage.output)} output`,
|
|
7598
|
+
`${formatUsageTokens(usage.cacheRead)} cache read`,
|
|
7599
|
+
`${formatUsageTokens(usage.cacheWrite)} cache write`,
|
|
7600
|
+
`API ${formatUsageCost(usage.cost.total)}`
|
|
7601
|
+
].join(" \xB7 ");
|
|
7602
|
+
}
|
|
7603
|
+
function formatCompact(value) {
|
|
7604
|
+
return Number(value.toFixed(1)).toString();
|
|
7605
|
+
}
|
|
7214
7606
|
|
|
7215
7607
|
// src/protocol.ts
|
|
7216
7608
|
var RUNTIME_PROTOCOL = `You are a Shadow Mind running beside the main agent.
|
|
@@ -7374,6 +7766,7 @@ function buildRunResult(options) {
|
|
|
7374
7766
|
const { reason, error, durationMs, session, baseMessageCount, missingTools, thinkingLevel } = options;
|
|
7375
7767
|
const ownMessages = session ? session.messages.slice(baseMessageCount) : [];
|
|
7376
7768
|
const metrics = toolMetrics(ownMessages);
|
|
7769
|
+
const usage = usageMetrics(ownMessages);
|
|
7377
7770
|
return {
|
|
7378
7771
|
reason,
|
|
7379
7772
|
...error !== void 0 ? { error } : {},
|
|
@@ -7381,6 +7774,7 @@ function buildRunResult(options) {
|
|
|
7381
7774
|
toolNames: session?.getActiveToolNames() ?? [],
|
|
7382
7775
|
missingTools,
|
|
7383
7776
|
...metrics,
|
|
7777
|
+
usage,
|
|
7384
7778
|
...thinkingLevel !== void 0 ? { thinkingLevel } : {},
|
|
7385
7779
|
sessionFile: session?.sessionFile
|
|
7386
7780
|
};
|
|
@@ -7455,6 +7849,7 @@ var ShadowRunner = class {
|
|
|
7455
7849
|
extensions: base.extensions.filter((extension) => !isSelfExtension(extension.resolvedPath))
|
|
7456
7850
|
})
|
|
7457
7851
|
});
|
|
7852
|
+
await resourceLoader.reload();
|
|
7458
7853
|
const reportTool = createReportTool((content) => {
|
|
7459
7854
|
if (state.reported || controller.signal.aborted) return;
|
|
7460
7855
|
state.reported = true;
|
|
@@ -7501,11 +7896,33 @@ function toolMetrics(messages) {
|
|
|
7501
7896
|
}
|
|
7502
7897
|
const toolStats = [...stats.values()].sort((a, b) => b.calls - a.calls);
|
|
7503
7898
|
return {
|
|
7504
|
-
toolCalls: toolStats.reduce((sum,
|
|
7505
|
-
toolFailures: toolStats.reduce((sum,
|
|
7899
|
+
toolCalls: toolStats.reduce((sum, stat3) => sum + stat3.calls, 0),
|
|
7900
|
+
toolFailures: toolStats.reduce((sum, stat3) => sum + stat3.failures, 0),
|
|
7506
7901
|
toolStats
|
|
7507
7902
|
};
|
|
7508
7903
|
}
|
|
7904
|
+
function usageMetrics(messages) {
|
|
7905
|
+
let aggregate = zeroUsage();
|
|
7906
|
+
for (const message of messages) {
|
|
7907
|
+
if (message.role !== "assistant" || !message.usage) continue;
|
|
7908
|
+
aggregate = addUsage(aggregate, {
|
|
7909
|
+
requests: 1,
|
|
7910
|
+
input: message.usage.input,
|
|
7911
|
+
output: message.usage.output,
|
|
7912
|
+
cacheRead: message.usage.cacheRead,
|
|
7913
|
+
cacheWrite: message.usage.cacheWrite,
|
|
7914
|
+
totalTokens: message.usage.totalTokens,
|
|
7915
|
+
cost: {
|
|
7916
|
+
input: message.usage.cost.input,
|
|
7917
|
+
output: message.usage.cost.output,
|
|
7918
|
+
cacheRead: message.usage.cost.cacheRead,
|
|
7919
|
+
cacheWrite: message.usage.cost.cacheWrite,
|
|
7920
|
+
total: message.usage.cost.total
|
|
7921
|
+
}
|
|
7922
|
+
});
|
|
7923
|
+
}
|
|
7924
|
+
return aggregate;
|
|
7925
|
+
}
|
|
7509
7926
|
function createReportTool(onReport) {
|
|
7510
7927
|
return {
|
|
7511
7928
|
name: "report_to_main",
|
|
@@ -7566,7 +7983,7 @@ async function waitForSettled(options) {
|
|
|
7566
7983
|
const pollMs = options.pollMs ?? 25;
|
|
7567
7984
|
const quietMs = options.quietMs ?? 50;
|
|
7568
7985
|
const now = options.now ?? Date.now;
|
|
7569
|
-
const
|
|
7986
|
+
const delay2 = options.delay ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
7570
7987
|
const started = now();
|
|
7571
7988
|
let settledSince;
|
|
7572
7989
|
while (now() - started < options.timeoutMs) {
|
|
@@ -7577,24 +7994,402 @@ async function waitForSettled(options) {
|
|
|
7577
7994
|
settledSince = void 0;
|
|
7578
7995
|
}
|
|
7579
7996
|
const remaining = options.timeoutMs - (now() - started);
|
|
7580
|
-
await
|
|
7997
|
+
await delay2(Math.min(pollMs, Math.max(1, remaining)));
|
|
7581
7998
|
}
|
|
7582
7999
|
return { settled: false, durationMs: now() - started };
|
|
7583
8000
|
}
|
|
7584
8001
|
|
|
8002
|
+
// src/usage-store.ts
|
|
8003
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8004
|
+
import { mkdir as mkdir5, readFile as readFile5, rename as rename2, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
8005
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
8006
|
+
|
|
8007
|
+
// src/usage-file-lock.ts
|
|
8008
|
+
import { randomUUID } from "node:crypto";
|
|
8009
|
+
import { mkdir as mkdir4, readFile as readFile4, rename, rm, stat as stat2, utimes, writeFile as writeFile3 } from "node:fs/promises";
|
|
8010
|
+
import { join as join5 } from "node:path";
|
|
8011
|
+
var USAGE_LOCK_OWNER_FILE = "owner";
|
|
8012
|
+
var USAGE_LOCK_OPERATION_DIRECTORY = ".operation";
|
|
8013
|
+
async function acquireUsageFileLock(usagePath, timing = {}) {
|
|
8014
|
+
const resolved = resolveTiming(timing);
|
|
8015
|
+
const lockPath = `${usagePath}.lock`;
|
|
8016
|
+
const owner = { token: randomUUID(), pid: process.pid };
|
|
8017
|
+
while (true) {
|
|
8018
|
+
try {
|
|
8019
|
+
await mkdir4(lockPath);
|
|
8020
|
+
} catch (error) {
|
|
8021
|
+
if (error.code !== "EEXIST") throw error;
|
|
8022
|
+
await recoverStaleUsageLock(lockPath, resolved);
|
|
8023
|
+
await delay(resolved.retryMs);
|
|
8024
|
+
continue;
|
|
8025
|
+
}
|
|
8026
|
+
try {
|
|
8027
|
+
await writeOwner(lockPath, owner);
|
|
8028
|
+
} catch (error) {
|
|
8029
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
|
|
8030
|
+
throw error;
|
|
8031
|
+
}
|
|
8032
|
+
return startLease(lockPath, owner, resolved);
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
function startLease(lockPath, owner, timing) {
|
|
8036
|
+
let stopped = false;
|
|
8037
|
+
let heartbeat = Promise.resolve();
|
|
8038
|
+
const timer = setInterval(() => {
|
|
8039
|
+
heartbeat = heartbeat.then(async () => {
|
|
8040
|
+
if (stopped) return;
|
|
8041
|
+
const current = await readUsageLockState(lockPath);
|
|
8042
|
+
if (!current || current.owner?.token !== owner.token) {
|
|
8043
|
+
stopped = true;
|
|
8044
|
+
return;
|
|
8045
|
+
}
|
|
8046
|
+
const now = /* @__PURE__ */ new Date();
|
|
8047
|
+
await utimes(join5(lockPath, USAGE_LOCK_OWNER_FILE), now, now);
|
|
8048
|
+
}).catch(() => void 0);
|
|
8049
|
+
}, timing.heartbeatMs);
|
|
8050
|
+
timer.unref();
|
|
8051
|
+
return async () => {
|
|
8052
|
+
stopped = true;
|
|
8053
|
+
clearInterval(timer);
|
|
8054
|
+
await heartbeat;
|
|
8055
|
+
await releaseUsageLock(lockPath, owner.token, timing);
|
|
8056
|
+
};
|
|
8057
|
+
}
|
|
8058
|
+
async function recoverStaleUsageLock(lockPath, timing) {
|
|
8059
|
+
const observed = await readUsageLockState(lockPath);
|
|
8060
|
+
if (!observed || !isRecoverableUsageLock(observed, timing.staleMs)) return;
|
|
8061
|
+
const operationPath = join5(lockPath, USAGE_LOCK_OPERATION_DIRECTORY);
|
|
8062
|
+
if (!await claimUsageLockOperation(operationPath, timing)) return;
|
|
8063
|
+
try {
|
|
8064
|
+
const current = await readUsageLockState(lockPath);
|
|
8065
|
+
if (!current || !sameUsageLock(current, observed) || !isRecoverableUsageLock(current, timing.staleMs)) return;
|
|
8066
|
+
const abandonedPath = `${lockPath}.${randomUUID()}.stale`;
|
|
8067
|
+
try {
|
|
8068
|
+
await rename(lockPath, abandonedPath);
|
|
8069
|
+
} catch (error) {
|
|
8070
|
+
if (error.code === "ENOENT") return;
|
|
8071
|
+
throw error;
|
|
8072
|
+
}
|
|
8073
|
+
await rm(abandonedPath, { recursive: true, force: true });
|
|
8074
|
+
} finally {
|
|
8075
|
+
await rm(operationPath, { recursive: true, force: true }).catch(() => void 0);
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
8078
|
+
async function releaseUsageLock(lockPath, token, timing) {
|
|
8079
|
+
const operationPath = join5(lockPath, USAGE_LOCK_OPERATION_DIRECTORY);
|
|
8080
|
+
while (true) {
|
|
8081
|
+
const observed = await readUsageLockState(lockPath);
|
|
8082
|
+
if (!observed || observed.owner?.token !== token) return;
|
|
8083
|
+
if (!await claimUsageLockOperation(operationPath, timing)) {
|
|
8084
|
+
await delay(timing.retryMs);
|
|
8085
|
+
continue;
|
|
8086
|
+
}
|
|
8087
|
+
try {
|
|
8088
|
+
const current = await readUsageLockState(lockPath);
|
|
8089
|
+
if (!current || current.owner?.token !== token) return;
|
|
8090
|
+
const releasedPath = `${lockPath}.${token}.released`;
|
|
8091
|
+
try {
|
|
8092
|
+
await rename(lockPath, releasedPath);
|
|
8093
|
+
} catch (error) {
|
|
8094
|
+
if (error.code === "ENOENT") continue;
|
|
8095
|
+
throw error;
|
|
8096
|
+
}
|
|
8097
|
+
await rm(releasedPath, { recursive: true, force: true });
|
|
8098
|
+
return;
|
|
8099
|
+
} finally {
|
|
8100
|
+
await rm(operationPath, { recursive: true, force: true }).catch(() => void 0);
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
async function claimUsageLockOperation(operationPath, timing) {
|
|
8105
|
+
const owner = { token: randomUUID(), pid: process.pid };
|
|
8106
|
+
try {
|
|
8107
|
+
await mkdir4(operationPath);
|
|
8108
|
+
try {
|
|
8109
|
+
await writeOwner(operationPath, owner);
|
|
8110
|
+
} catch (error) {
|
|
8111
|
+
await rm(operationPath, { recursive: true, force: true }).catch(() => void 0);
|
|
8112
|
+
throw error;
|
|
8113
|
+
}
|
|
8114
|
+
return true;
|
|
8115
|
+
} catch (error) {
|
|
8116
|
+
if (error.code !== "EEXIST") {
|
|
8117
|
+
if (error.code === "ENOENT") return false;
|
|
8118
|
+
throw error;
|
|
8119
|
+
}
|
|
8120
|
+
}
|
|
8121
|
+
const observed = await readUsageLockState(operationPath);
|
|
8122
|
+
if (!observed || !isRecoverableUsageLock(observed, timing.staleMs)) return false;
|
|
8123
|
+
const abandonedPath = `${operationPath}.${randomUUID()}.stale`;
|
|
8124
|
+
try {
|
|
8125
|
+
await rename(operationPath, abandonedPath);
|
|
8126
|
+
} catch (error) {
|
|
8127
|
+
if (error.code === "ENOENT") return false;
|
|
8128
|
+
throw error;
|
|
8129
|
+
}
|
|
8130
|
+
await rm(abandonedPath, { recursive: true, force: true });
|
|
8131
|
+
return false;
|
|
8132
|
+
}
|
|
8133
|
+
async function writeOwner(path, owner) {
|
|
8134
|
+
await writeFile3(join5(path, USAGE_LOCK_OWNER_FILE), JSON.stringify(owner), {
|
|
8135
|
+
encoding: "utf8",
|
|
8136
|
+
flag: "wx"
|
|
8137
|
+
});
|
|
8138
|
+
}
|
|
8139
|
+
async function readUsageLockState(lockPath) {
|
|
8140
|
+
let lock;
|
|
8141
|
+
try {
|
|
8142
|
+
lock = await stat2(lockPath);
|
|
8143
|
+
} catch (error) {
|
|
8144
|
+
if (error.code === "ENOENT") return void 0;
|
|
8145
|
+
throw error;
|
|
8146
|
+
}
|
|
8147
|
+
try {
|
|
8148
|
+
const ownerPath = join5(lockPath, USAGE_LOCK_OWNER_FILE);
|
|
8149
|
+
const [raw, ownerFile] = await Promise.all([readFile4(ownerPath, "utf8"), stat2(ownerPath)]);
|
|
8150
|
+
return {
|
|
8151
|
+
owner: parseOwner(raw),
|
|
8152
|
+
mtimeMs: ownerFile.mtimeMs,
|
|
8153
|
+
dev: lock.dev,
|
|
8154
|
+
ino: lock.ino
|
|
8155
|
+
};
|
|
8156
|
+
} catch (error) {
|
|
8157
|
+
if (error.code !== "ENOENT") throw error;
|
|
8158
|
+
return { mtimeMs: lock.mtimeMs, dev: lock.dev, ino: lock.ino };
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
function parseOwner(raw) {
|
|
8162
|
+
try {
|
|
8163
|
+
const value = JSON.parse(raw);
|
|
8164
|
+
if (typeof value.token !== "string" || !value.token || !Number.isSafeInteger(value.pid) || value.pid <= 0) return void 0;
|
|
8165
|
+
return { token: value.token, pid: value.pid };
|
|
8166
|
+
} catch {
|
|
8167
|
+
return void 0;
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
function isRecoverableUsageLock(lock, staleMs) {
|
|
8171
|
+
return Date.now() - lock.mtimeMs >= staleMs && !isProcessAlive(lock.owner?.pid);
|
|
8172
|
+
}
|
|
8173
|
+
function isProcessAlive(pid) {
|
|
8174
|
+
if (pid === void 0) return false;
|
|
8175
|
+
try {
|
|
8176
|
+
process.kill(pid, 0);
|
|
8177
|
+
return true;
|
|
8178
|
+
} catch (error) {
|
|
8179
|
+
return error.code !== "ESRCH";
|
|
8180
|
+
}
|
|
8181
|
+
}
|
|
8182
|
+
function sameUsageLock(left, right) {
|
|
8183
|
+
return left.owner !== void 0 && right.owner !== void 0 ? left.owner.token === right.owner.token : left.owner === right.owner && left.dev === right.dev && left.ino === right.ino;
|
|
8184
|
+
}
|
|
8185
|
+
function resolveTiming(timing) {
|
|
8186
|
+
return {
|
|
8187
|
+
retryMs: timing.retryMs ?? 25,
|
|
8188
|
+
staleMs: timing.staleMs ?? 3e4,
|
|
8189
|
+
heartbeatMs: timing.heartbeatMs ?? 1e4
|
|
8190
|
+
};
|
|
8191
|
+
}
|
|
8192
|
+
function delay(ms) {
|
|
8193
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
8194
|
+
}
|
|
8195
|
+
|
|
8196
|
+
// src/usage-store.ts
|
|
8197
|
+
var USAGE_DOCUMENT_VERSION = 1;
|
|
8198
|
+
var UsageStore = class {
|
|
8199
|
+
usagePath;
|
|
8200
|
+
lifetime = zeroUsage();
|
|
8201
|
+
pendingUsage = zeroUsage();
|
|
8202
|
+
lastError;
|
|
8203
|
+
initialization;
|
|
8204
|
+
pendingWrite = Promise.resolve();
|
|
8205
|
+
constructor(agentDir) {
|
|
8206
|
+
this.usagePath = join6(agentDir, "shadow-minds", "usage.json");
|
|
8207
|
+
}
|
|
8208
|
+
initialize() {
|
|
8209
|
+
this.initialization ??= this.load();
|
|
8210
|
+
return this.initialization;
|
|
8211
|
+
}
|
|
8212
|
+
get current() {
|
|
8213
|
+
return {
|
|
8214
|
+
...this.lifetime,
|
|
8215
|
+
cost: { ...this.lifetime.cost }
|
|
8216
|
+
};
|
|
8217
|
+
}
|
|
8218
|
+
get error() {
|
|
8219
|
+
return this.lastError;
|
|
8220
|
+
}
|
|
8221
|
+
add(usage) {
|
|
8222
|
+
this.lifetime = addUsage(this.lifetime, usage);
|
|
8223
|
+
this.pendingUsage = addUsage(this.pendingUsage, usage);
|
|
8224
|
+
return this.enqueueWrite();
|
|
8225
|
+
}
|
|
8226
|
+
async flush() {
|
|
8227
|
+
await this.pendingWrite;
|
|
8228
|
+
if (!isZeroUsage(this.pendingUsage)) await this.enqueueWrite();
|
|
8229
|
+
}
|
|
8230
|
+
enqueueWrite() {
|
|
8231
|
+
this.pendingWrite = this.pendingWrite.then(
|
|
8232
|
+
() => this.writePending(),
|
|
8233
|
+
() => this.writePending()
|
|
8234
|
+
).then(
|
|
8235
|
+
() => {
|
|
8236
|
+
this.lastError = void 0;
|
|
8237
|
+
},
|
|
8238
|
+
(error) => {
|
|
8239
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
8240
|
+
}
|
|
8241
|
+
);
|
|
8242
|
+
return this.pendingWrite;
|
|
8243
|
+
}
|
|
8244
|
+
async load() {
|
|
8245
|
+
try {
|
|
8246
|
+
const raw = await readFile5(this.usagePath, "utf8");
|
|
8247
|
+
this.lifetime = parseUsageDocument(JSON.parse(raw)).lifetime;
|
|
8248
|
+
this.lastError = void 0;
|
|
8249
|
+
} catch (error) {
|
|
8250
|
+
if (error.code === "ENOENT") {
|
|
8251
|
+
this.lifetime = zeroUsage();
|
|
8252
|
+
this.lastError = void 0;
|
|
8253
|
+
return;
|
|
8254
|
+
}
|
|
8255
|
+
this.lifetime = zeroUsage();
|
|
8256
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
8257
|
+
}
|
|
8258
|
+
}
|
|
8259
|
+
async writePending() {
|
|
8260
|
+
const delta = this.pendingUsage;
|
|
8261
|
+
if (isZeroUsage(delta)) return;
|
|
8262
|
+
await mkdir5(dirname3(this.usagePath), { recursive: true });
|
|
8263
|
+
const releaseLock = await acquireUsageFileLock(this.usagePath);
|
|
8264
|
+
try {
|
|
8265
|
+
const committed = addUsage(await readUsageLifetime(this.usagePath), delta);
|
|
8266
|
+
await writeUsageDocument(this.usagePath, committed);
|
|
8267
|
+
this.pendingUsage = subtractUsage(this.pendingUsage, delta);
|
|
8268
|
+
this.lifetime = addUsage(committed, this.pendingUsage);
|
|
8269
|
+
} finally {
|
|
8270
|
+
await releaseLock();
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
};
|
|
8274
|
+
function subtractUsage(left, right) {
|
|
8275
|
+
return {
|
|
8276
|
+
requests: left.requests - right.requests,
|
|
8277
|
+
input: left.input - right.input,
|
|
8278
|
+
output: left.output - right.output,
|
|
8279
|
+
cacheRead: left.cacheRead - right.cacheRead,
|
|
8280
|
+
cacheWrite: left.cacheWrite - right.cacheWrite,
|
|
8281
|
+
totalTokens: left.totalTokens - right.totalTokens,
|
|
8282
|
+
cost: {
|
|
8283
|
+
input: left.cost.input - right.cost.input,
|
|
8284
|
+
output: left.cost.output - right.cost.output,
|
|
8285
|
+
cacheRead: left.cost.cacheRead - right.cost.cacheRead,
|
|
8286
|
+
cacheWrite: left.cost.cacheWrite - right.cost.cacheWrite,
|
|
8287
|
+
total: left.cost.total - right.cost.total
|
|
8288
|
+
}
|
|
8289
|
+
};
|
|
8290
|
+
}
|
|
8291
|
+
function isZeroUsage(usage) {
|
|
8292
|
+
return usage.requests === 0 && usage.input === 0 && usage.output === 0 && usage.cacheRead === 0 && usage.cacheWrite === 0 && usage.totalTokens === 0 && usage.cost.input === 0 && usage.cost.output === 0 && usage.cost.cacheRead === 0 && usage.cost.cacheWrite === 0 && usage.cost.total === 0;
|
|
8293
|
+
}
|
|
8294
|
+
async function readUsageLifetime(usagePath) {
|
|
8295
|
+
try {
|
|
8296
|
+
const raw = await readFile5(usagePath, "utf8");
|
|
8297
|
+
return parseUsageDocument(JSON.parse(raw)).lifetime;
|
|
8298
|
+
} catch (error) {
|
|
8299
|
+
if (error.code === "ENOENT") return zeroUsage();
|
|
8300
|
+
throw error;
|
|
8301
|
+
}
|
|
8302
|
+
}
|
|
8303
|
+
async function writeUsageDocument(usagePath, lifetime) {
|
|
8304
|
+
const temporaryPath = `${usagePath}.${randomUUID2()}.tmp`;
|
|
8305
|
+
try {
|
|
8306
|
+
const document = { version: USAGE_DOCUMENT_VERSION, lifetime };
|
|
8307
|
+
await writeFile4(temporaryPath, `${JSON.stringify(document, null, 2)}
|
|
8308
|
+
`, "utf8");
|
|
8309
|
+
await rename2(temporaryPath, usagePath);
|
|
8310
|
+
} finally {
|
|
8311
|
+
await rm2(temporaryPath, { force: true }).catch(() => void 0);
|
|
8312
|
+
}
|
|
8313
|
+
}
|
|
8314
|
+
function parseUsageDocument(input) {
|
|
8315
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
8316
|
+
throw new Error("usage must be a JSON object");
|
|
8317
|
+
}
|
|
8318
|
+
const value = input;
|
|
8319
|
+
if (value.version !== USAGE_DOCUMENT_VERSION) throw new Error("usage version is unsupported");
|
|
8320
|
+
return { version: USAGE_DOCUMENT_VERSION, lifetime: parseUsage(value.lifetime, "lifetime") };
|
|
8321
|
+
}
|
|
8322
|
+
function parseUsage(input, name) {
|
|
8323
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
8324
|
+
throw new Error(`${name} must be an object`);
|
|
8325
|
+
}
|
|
8326
|
+
const value = input;
|
|
8327
|
+
const cost = value.cost;
|
|
8328
|
+
if (cost === null || typeof cost !== "object" || Array.isArray(cost)) {
|
|
8329
|
+
throw new Error(`${name}.cost must be an object`);
|
|
8330
|
+
}
|
|
8331
|
+
const costValue = cost;
|
|
8332
|
+
return {
|
|
8333
|
+
requests: nonNegativeInteger2(value.requests, `${name}.requests`),
|
|
8334
|
+
input: nonNegativeNumber(value.input, `${name}.input`),
|
|
8335
|
+
output: nonNegativeNumber(value.output, `${name}.output`),
|
|
8336
|
+
cacheRead: nonNegativeNumber(value.cacheRead, `${name}.cacheRead`),
|
|
8337
|
+
cacheWrite: nonNegativeNumber(value.cacheWrite, `${name}.cacheWrite`),
|
|
8338
|
+
totalTokens: nonNegativeNumber(value.totalTokens, `${name}.totalTokens`),
|
|
8339
|
+
cost: {
|
|
8340
|
+
input: nonNegativeNumber(costValue.input, `${name}.cost.input`),
|
|
8341
|
+
output: nonNegativeNumber(costValue.output, `${name}.cost.output`),
|
|
8342
|
+
cacheRead: nonNegativeNumber(costValue.cacheRead, `${name}.cost.cacheRead`),
|
|
8343
|
+
cacheWrite: nonNegativeNumber(costValue.cacheWrite, `${name}.cost.cacheWrite`),
|
|
8344
|
+
total: nonNegativeNumber(costValue.total, `${name}.cost.total`)
|
|
8345
|
+
}
|
|
8346
|
+
};
|
|
8347
|
+
}
|
|
8348
|
+
function nonNegativeNumber(value, name) {
|
|
8349
|
+
if (!isFiniteNumber(value) || value < 0) {
|
|
8350
|
+
throw new Error(`${name} must be a non-negative finite number`);
|
|
8351
|
+
}
|
|
8352
|
+
return value;
|
|
8353
|
+
}
|
|
8354
|
+
function nonNegativeInteger2(value, name) {
|
|
8355
|
+
const number = nonNegativeNumber(value, name);
|
|
8356
|
+
if (!Number.isSafeInteger(number)) throw new Error(`${name} must be a non-negative safe integer`);
|
|
8357
|
+
return number;
|
|
8358
|
+
}
|
|
8359
|
+
|
|
7585
8360
|
// src/runtime.ts
|
|
8361
|
+
var SESSION_TEARDOWN_TIMEOUT_MS = 1e3;
|
|
7586
8362
|
var ShadowMindRuntime = class {
|
|
7587
8363
|
constructor(pi) {
|
|
7588
8364
|
this.pi = pi;
|
|
7589
|
-
this.batcher = new ReportBatcher(
|
|
8365
|
+
this.batcher = new ReportBatcher(
|
|
8366
|
+
this.configStore.current.resultBatchWindowMs,
|
|
8367
|
+
(reports) => {
|
|
8368
|
+
this.completionReview.invalidate();
|
|
8369
|
+
void this.deliverReports(reports);
|
|
8370
|
+
}
|
|
8371
|
+
);
|
|
7590
8372
|
}
|
|
7591
8373
|
agentDir = getAgentDir();
|
|
7592
8374
|
configStore = new ConfigStore(this.agentDir);
|
|
7593
8375
|
registry = new ShadowRegistry(this.agentDir);
|
|
7594
|
-
entityStore = new EntityStore(
|
|
8376
|
+
entityStore = new EntityStore(
|
|
8377
|
+
this.registry,
|
|
8378
|
+
this.configStore.configPath
|
|
8379
|
+
);
|
|
7595
8380
|
runner = new ShadowRunner();
|
|
8381
|
+
usageStore = new UsageStore(this.agentDir);
|
|
7596
8382
|
active = /* @__PURE__ */ new Map();
|
|
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
|
+
});
|
|
7597
8391
|
recentEvents = [];
|
|
8392
|
+
recentRuns = [];
|
|
7598
8393
|
batcher;
|
|
7599
8394
|
sessionLifetime = new SessionLifetime();
|
|
7600
8395
|
epoch = 0;
|
|
@@ -7603,10 +8398,16 @@ var ShadowMindRuntime = class {
|
|
|
7603
8398
|
panelVisible = false;
|
|
7604
8399
|
latestContext;
|
|
7605
8400
|
diagnostics = [];
|
|
8401
|
+
sessionUsage = zeroUsage();
|
|
7606
8402
|
shadowCount = 0;
|
|
8403
|
+
completedWithFinalText = false;
|
|
7607
8404
|
random = Math.random;
|
|
7608
8405
|
register() {
|
|
7609
|
-
registerManagementTools(
|
|
8406
|
+
registerManagementTools(
|
|
8407
|
+
this.pi,
|
|
8408
|
+
this.entityStore,
|
|
8409
|
+
() => this.configStore.current
|
|
8410
|
+
);
|
|
7610
8411
|
this.registerEvents();
|
|
7611
8412
|
this.registerUi();
|
|
7612
8413
|
}
|
|
@@ -7615,16 +8416,23 @@ var ShadowMindRuntime = class {
|
|
|
7615
8416
|
this.sessionLifetime.activate();
|
|
7616
8417
|
this.latestContext = ctx;
|
|
7617
8418
|
this.modelCalls = 0;
|
|
8419
|
+
this.completedWithFinalText = false;
|
|
8420
|
+
this.sessionUsage = zeroUsage();
|
|
8421
|
+
this.recentRuns.length = 0;
|
|
7618
8422
|
await this.configStore.initialize();
|
|
8423
|
+
await this.usageStore.initialize();
|
|
7619
8424
|
this.random = createRandom(this.configStore.current.randomSeed);
|
|
7620
8425
|
await this.registry.initialize();
|
|
7621
8426
|
await this.refresh(ctx);
|
|
7622
|
-
this.record("session-config", {
|
|
8427
|
+
this.record("session-config", {
|
|
8428
|
+
randomSeed: this.configStore.current.randomSeed ?? "random"
|
|
8429
|
+
});
|
|
7623
8430
|
this.updateStatus(ctx);
|
|
7624
8431
|
});
|
|
7625
8432
|
this.pi.on("input", (event, ctx) => {
|
|
7626
8433
|
this.latestContext = ctx;
|
|
7627
8434
|
if (event.source === "extension") return;
|
|
8435
|
+
this.completedWithFinalText = false;
|
|
7628
8436
|
this.epoch += 1;
|
|
7629
8437
|
this.abortAll("new-user-input");
|
|
7630
8438
|
});
|
|
@@ -7635,11 +8443,29 @@ var ShadowMindRuntime = class {
|
|
|
7635
8443
|
this.pi.on("turn_end", async (event, ctx) => {
|
|
7636
8444
|
this.latestContext = ctx;
|
|
7637
8445
|
if (!shouldEvaluateHeartbeat(event.toolResults)) {
|
|
7638
|
-
this.record("heartbeat-skipped", {
|
|
8446
|
+
this.record("heartbeat-skipped", {
|
|
8447
|
+
reason: "no-tool-activity",
|
|
8448
|
+
modelCalls: this.modelCalls
|
|
8449
|
+
});
|
|
7639
8450
|
return;
|
|
7640
8451
|
}
|
|
7641
8452
|
await this.onHeartbeat(ctx);
|
|
7642
8453
|
});
|
|
8454
|
+
this.pi.on("agent_end", (event, ctx) => {
|
|
8455
|
+
this.latestContext = ctx;
|
|
8456
|
+
this.completedWithFinalText = shouldEvaluateFinalResponse(event.messages);
|
|
8457
|
+
});
|
|
8458
|
+
this.pi.on("agent_settled", async (_event, ctx) => {
|
|
8459
|
+
this.latestContext = ctx;
|
|
8460
|
+
if (!this.completedWithFinalText) {
|
|
8461
|
+
this.record("final-response-skipped", {
|
|
8462
|
+
reason: "no-final-assistant-text"
|
|
8463
|
+
});
|
|
8464
|
+
return;
|
|
8465
|
+
}
|
|
8466
|
+
this.completedWithFinalText = false;
|
|
8467
|
+
await this.onFinalResponse(ctx);
|
|
8468
|
+
});
|
|
7643
8469
|
this.pi.on("session_shutdown", async (event, ctx) => {
|
|
7644
8470
|
this.latestContext = ctx;
|
|
7645
8471
|
if (event.reason === "quit" && (ctx.mode === "print" || ctx.mode === "json")) {
|
|
@@ -7647,6 +8473,14 @@ var ShadowMindRuntime = class {
|
|
|
7647
8473
|
}
|
|
7648
8474
|
this.epoch += 1;
|
|
7649
8475
|
this.abortAll("session-shutdown");
|
|
8476
|
+
if (this.active.size > 0) {
|
|
8477
|
+
const result = await waitForSettled({
|
|
8478
|
+
timeoutMs: SESSION_TEARDOWN_TIMEOUT_MS,
|
|
8479
|
+
isSettled: () => this.active.size === 0
|
|
8480
|
+
});
|
|
8481
|
+
if (!result.settled) this.active.clear();
|
|
8482
|
+
}
|
|
8483
|
+
await this.usageStore.flush();
|
|
7650
8484
|
ctx.ui.setStatus("shadow-mind", void 0);
|
|
7651
8485
|
ctx.ui.setWidget("shadow-mind-panel", void 0);
|
|
7652
8486
|
this.sessionLifetime.deactivate();
|
|
@@ -7672,7 +8506,10 @@ var ShadowMindRuntime = class {
|
|
|
7672
8506
|
}
|
|
7673
8507
|
if (command === "status") {
|
|
7674
8508
|
await this.refresh(ctx);
|
|
7675
|
-
ctx.ui.notify(
|
|
8509
|
+
ctx.ui.notify(
|
|
8510
|
+
this.statusLines().join("\n"),
|
|
8511
|
+
this.diagnostics.length ? "warning" : "info"
|
|
8512
|
+
);
|
|
7676
8513
|
} else if (command === "hide") {
|
|
7677
8514
|
this.panelVisible = false;
|
|
7678
8515
|
ctx.ui.setWidget("shadow-mind-panel", void 0);
|
|
@@ -7690,25 +8527,35 @@ var ShadowMindRuntime = class {
|
|
|
7690
8527
|
this.setPaused(!this.paused, ctx);
|
|
7691
8528
|
}
|
|
7692
8529
|
});
|
|
7693
|
-
this.pi.registerMessageRenderer(
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
8530
|
+
this.pi.registerMessageRenderer(
|
|
8531
|
+
"shadow-report",
|
|
8532
|
+
(message, _options, theme) => {
|
|
8533
|
+
const content = typeof message.content === "string" ? message.content : "Shadow report";
|
|
8534
|
+
const prefix = theme.fg("accent", "\u{1F419} shadow \xB7 ");
|
|
8535
|
+
return new Text(`${prefix}${content}`, 0, 0);
|
|
8536
|
+
}
|
|
8537
|
+
);
|
|
7698
8538
|
}
|
|
7699
8539
|
async onHeartbeat(ctx) {
|
|
7700
|
-
await this.refresh(ctx);
|
|
8540
|
+
const snapshot = await this.refresh(ctx);
|
|
7701
8541
|
if (this.paused || !ctx.model) {
|
|
7702
|
-
this.record("heartbeat-skipped", {
|
|
8542
|
+
this.record("heartbeat-skipped", {
|
|
8543
|
+
reason: this.paused ? "paused" : "no-model",
|
|
8544
|
+
modelCalls: this.modelCalls
|
|
8545
|
+
});
|
|
7703
8546
|
return;
|
|
7704
8547
|
}
|
|
7705
|
-
const snapshot = await this.registry.load();
|
|
7706
8548
|
const fullModelId = `${ctx.model.provider}/${ctx.model.id}`;
|
|
7707
8549
|
const decision = decideHeartbeat({
|
|
7708
8550
|
heartbeatProbability: this.configStore.current.heartbeatProbability,
|
|
7709
|
-
availableSlots: Math.max(
|
|
8551
|
+
availableSlots: Math.max(
|
|
8552
|
+
0,
|
|
8553
|
+
this.configStore.current.maxParallelShadows - this.active.size
|
|
8554
|
+
),
|
|
7710
8555
|
shadows: snapshot.shadows,
|
|
7711
|
-
activeShadowIds: new Set(
|
|
8556
|
+
activeShadowIds: new Set(
|
|
8557
|
+
[...this.active.values()].map(({ shadow }) => shadow.id)
|
|
8558
|
+
),
|
|
7712
8559
|
mainModelId: fullModelId,
|
|
7713
8560
|
random: this.random
|
|
7714
8561
|
});
|
|
@@ -7716,22 +8563,99 @@ var ShadowMindRuntime = class {
|
|
|
7716
8563
|
modelCalls: this.modelCalls,
|
|
7717
8564
|
roll: decision.heartbeatRoll,
|
|
7718
8565
|
candidates: decision.candidates,
|
|
7719
|
-
activated: decision.activated.map(({ shadow, roll }) => ({
|
|
8566
|
+
activated: decision.activated.map(({ shadow, roll }) => ({
|
|
8567
|
+
id: shadow.id,
|
|
8568
|
+
roll
|
|
8569
|
+
})),
|
|
7720
8570
|
...decision.modelFiltered.length ? { modelFiltered: decision.modelFiltered } : {},
|
|
7721
8571
|
...decision.runningExcluded.length ? { runningExcluded: decision.runningExcluded } : {}
|
|
7722
8572
|
});
|
|
7723
8573
|
if (!decision.activated.length) return;
|
|
7724
|
-
const context = buildSessionContext(
|
|
7725
|
-
|
|
8574
|
+
const context = buildSessionContext(
|
|
8575
|
+
ctx.sessionManager.getEntries(),
|
|
8576
|
+
ctx.sessionManager.getLeafId()
|
|
8577
|
+
);
|
|
8578
|
+
const availableTools = new Set(
|
|
8579
|
+
this.pi.getAllTools().map((tool2) => tool2.name)
|
|
8580
|
+
);
|
|
7726
8581
|
for (const { shadow } of decision.activated) {
|
|
7727
|
-
this.launchShadow(
|
|
8582
|
+
this.launchShadow({
|
|
8583
|
+
ctx,
|
|
8584
|
+
shadow,
|
|
8585
|
+
mainModel: ctx.model,
|
|
8586
|
+
fullModelId,
|
|
8587
|
+
context,
|
|
8588
|
+
availableTools
|
|
8589
|
+
});
|
|
7728
8590
|
}
|
|
7729
8591
|
}
|
|
7730
|
-
|
|
7731
|
-
const
|
|
7732
|
-
const
|
|
8592
|
+
async onFinalResponse(ctx) {
|
|
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
|
+
}
|
|
8599
|
+
if (this.paused || !ctx.model) {
|
|
8600
|
+
this.completionReview.cancel(request);
|
|
8601
|
+
this.record("final-response-skipped", {
|
|
8602
|
+
reason: this.paused ? "paused" : "no-model"
|
|
8603
|
+
});
|
|
8604
|
+
return;
|
|
8605
|
+
}
|
|
8606
|
+
const mainModel = ctx.model;
|
|
8607
|
+
const fullModelId = `${mainModel.provider}/${mainModel.id}`;
|
|
8608
|
+
const decision = decideFinalResponse({
|
|
8609
|
+
shadows: snapshot.shadows,
|
|
8610
|
+
mainModelId: fullModelId
|
|
8611
|
+
});
|
|
8612
|
+
this.record("final-response", {
|
|
8613
|
+
candidates: decision.candidates,
|
|
8614
|
+
activated: decision.activated.map(({ shadow }) => shadow.id),
|
|
8615
|
+
...decision.modelFiltered.length ? { modelFiltered: decision.modelFiltered } : {},
|
|
8616
|
+
...decision.runningExcluded.length ? { runningExcluded: decision.runningExcluded } : {}
|
|
8617
|
+
});
|
|
8618
|
+
if (!decision.activated.length) {
|
|
8619
|
+
this.completionReview.schedule(request, []);
|
|
8620
|
+
return;
|
|
8621
|
+
}
|
|
8622
|
+
const context = buildSessionContext(
|
|
8623
|
+
ctx.sessionManager.getEntries(),
|
|
8624
|
+
ctx.sessionManager.getLeafId()
|
|
8625
|
+
);
|
|
8626
|
+
const availableTools = new Set(
|
|
8627
|
+
this.pi.getAllTools().map((tool2) => tool2.name)
|
|
8628
|
+
);
|
|
8629
|
+
this.completionReview.schedule(
|
|
8630
|
+
request,
|
|
8631
|
+
decision.activated.map(({ shadow }) => ({
|
|
8632
|
+
epoch: request.epoch,
|
|
8633
|
+
shadowId: shadow.id,
|
|
8634
|
+
ctx,
|
|
8635
|
+
shadow,
|
|
8636
|
+
mainModel,
|
|
8637
|
+
fullModelId,
|
|
8638
|
+
context,
|
|
8639
|
+
availableTools
|
|
8640
|
+
}))
|
|
8641
|
+
);
|
|
8642
|
+
}
|
|
8643
|
+
launchShadow(options) {
|
|
8644
|
+
const {
|
|
8645
|
+
ctx,
|
|
8646
|
+
shadow,
|
|
8647
|
+
mainModel,
|
|
8648
|
+
fullModelId,
|
|
8649
|
+
context,
|
|
8650
|
+
availableTools,
|
|
8651
|
+
completionReview
|
|
8652
|
+
} = options;
|
|
8653
|
+
const runId = randomUUID3();
|
|
8654
|
+
const runEpoch = options.epoch ?? this.epoch;
|
|
7733
8655
|
const { tools, missing } = resolveShadowTools(shadow.tools, availableTools);
|
|
7734
|
-
|
|
8656
|
+
const activeRun = { shadow, epoch: runEpoch };
|
|
8657
|
+
if (completionReview) activeRun.completionReview = completionReview;
|
|
8658
|
+
this.active.set(runId, activeRun);
|
|
7735
8659
|
this.record("run-start", {
|
|
7736
8660
|
runId,
|
|
7737
8661
|
shadowId: shadow.id,
|
|
@@ -7747,15 +8671,22 @@ var ShadowMindRuntime = class {
|
|
|
7747
8671
|
cwd: ctx.cwd,
|
|
7748
8672
|
agentDir: this.agentDir,
|
|
7749
8673
|
mainSystemPrompt: ctx.getSystemPrompt(),
|
|
8674
|
+
// SAFETY: buildSessionContext returns Pi messages; ShadowRunner treats them as read-only records.
|
|
7750
8675
|
messages: context.messages,
|
|
7751
8676
|
mainModel,
|
|
7752
8677
|
tools,
|
|
7753
8678
|
resolveModel: (id) => resolveModel(ctx, id),
|
|
7754
8679
|
modelAuthOk: (model) => ctx.modelRegistry.hasConfiguredAuth(model) || ctx.modelRegistry.isUsingOAuth(model),
|
|
7755
8680
|
mainThinkingLevel: ctx.thinkingLevel,
|
|
7756
|
-
onReport: (report) =>
|
|
8681
|
+
onReport: (report) => {
|
|
8682
|
+
if (completionReview) completionReview.accept(report);
|
|
8683
|
+
else this.acceptReport(report);
|
|
8684
|
+
}
|
|
7757
8685
|
}).then((result) => this.handleRunEnd(runId, shadow, result)).catch((error) => {
|
|
8686
|
+
const activeRun2 = this.active.get(runId);
|
|
7758
8687
|
this.active.delete(runId);
|
|
8688
|
+
this.completionReview.slotAvailable();
|
|
8689
|
+
this.completionReview.complete(activeRun2?.completionReview);
|
|
7759
8690
|
this.record("run-end", {
|
|
7760
8691
|
runId,
|
|
7761
8692
|
shadowId: shadow.id,
|
|
@@ -7765,9 +8696,29 @@ var ShadowMindRuntime = class {
|
|
|
7765
8696
|
});
|
|
7766
8697
|
}
|
|
7767
8698
|
handleRunEnd(runId, shadow, result) {
|
|
8699
|
+
const activeRun = this.active.get(runId);
|
|
7768
8700
|
this.active.delete(runId);
|
|
8701
|
+
this.completionReview.slotAvailable();
|
|
8702
|
+
const persisted = this.usageStore.add(result.usage);
|
|
8703
|
+
if (activeRun?.epoch !== this.epoch) {
|
|
8704
|
+
this.completionReview.complete(activeRun?.completionReview);
|
|
8705
|
+
return;
|
|
8706
|
+
}
|
|
8707
|
+
this.sessionUsage = addUsage(this.sessionUsage, result.usage);
|
|
8708
|
+
this.recentRuns.push({
|
|
8709
|
+
shadowName: shadow.name,
|
|
8710
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8711
|
+
result
|
|
8712
|
+
});
|
|
8713
|
+
if (this.recentRuns.length > 5) this.recentRuns.shift();
|
|
7769
8714
|
this.record("run-end", { runId, shadowId: shadow.id, ...result });
|
|
7770
|
-
if (this.latestContext && this.sessionLifetime.isActive)
|
|
8715
|
+
if (this.latestContext && this.sessionLifetime.isActive)
|
|
8716
|
+
this.updateStatus(this.latestContext);
|
|
8717
|
+
this.completionReview.complete(activeRun.completionReview);
|
|
8718
|
+
void persisted.then(() => {
|
|
8719
|
+
if (this.latestContext && this.sessionLifetime.isActive)
|
|
8720
|
+
this.updateStatus(this.latestContext);
|
|
8721
|
+
});
|
|
7771
8722
|
}
|
|
7772
8723
|
acceptReport(report) {
|
|
7773
8724
|
if (report.epoch !== this.epoch) return;
|
|
@@ -7777,15 +8728,26 @@ var ShadowMindRuntime = class {
|
|
|
7777
8728
|
const current = reports.filter((report) => report.epoch === this.epoch);
|
|
7778
8729
|
if (!current.length) return;
|
|
7779
8730
|
const content = formatReportBatch(current);
|
|
7780
|
-
this.record("report-delivered", {
|
|
8731
|
+
this.record("report-delivered", {
|
|
8732
|
+
runIds: current.map((report) => report.runId),
|
|
8733
|
+
count: current.length
|
|
8734
|
+
});
|
|
7781
8735
|
const idle = this.latestContext?.isIdle() ?? true;
|
|
7782
8736
|
this.sessionLifetime.run(() => {
|
|
7783
|
-
this.pi.sendMessage(
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
8737
|
+
this.pi.sendMessage(
|
|
8738
|
+
{
|
|
8739
|
+
customType: "shadow-report",
|
|
8740
|
+
content,
|
|
8741
|
+
display: true,
|
|
8742
|
+
details: {
|
|
8743
|
+
reports: current.map(({ shadowId, runId }) => ({
|
|
8744
|
+
shadowId,
|
|
8745
|
+
runId
|
|
8746
|
+
}))
|
|
8747
|
+
}
|
|
8748
|
+
},
|
|
8749
|
+
{ triggerTurn: true, deliverAs: idle ? "followUp" : "steer" }
|
|
8750
|
+
);
|
|
7789
8751
|
});
|
|
7790
8752
|
}
|
|
7791
8753
|
async refresh(ctx) {
|
|
@@ -7795,57 +8757,94 @@ var ShadowMindRuntime = class {
|
|
|
7795
8757
|
this.shadowCount = registry.shadows.length;
|
|
7796
8758
|
this.diagnostics = [
|
|
7797
8759
|
...config.error ? [`config: ${config.error}`] : [],
|
|
7798
|
-
...registry.diagnostics.map(
|
|
8760
|
+
...registry.diagnostics.map(
|
|
8761
|
+
(item) => `${item.filePath}: ${item.message}`
|
|
8762
|
+
)
|
|
7799
8763
|
];
|
|
7800
8764
|
this.updateStatus(ctx);
|
|
8765
|
+
return registry;
|
|
7801
8766
|
}
|
|
7802
8767
|
abortAll(reason) {
|
|
7803
8768
|
this.runner.abortAll();
|
|
8769
|
+
this.completionReview.invalidate();
|
|
7804
8770
|
this.batcher.clear();
|
|
7805
8771
|
this.record("runs-aborted", { reason, count: this.active.size });
|
|
7806
8772
|
}
|
|
7807
8773
|
async drainHeadless(ctx) {
|
|
7808
8774
|
if (this.active.size === 0 && !this.batcher.hasPending) return;
|
|
7809
8775
|
const timeoutMs = this.configStore.current.headlessDrainTimeoutSeconds * 1e3;
|
|
7810
|
-
this.record("headless-drain-start", {
|
|
8776
|
+
this.record("headless-drain-start", {
|
|
8777
|
+
timeoutMs,
|
|
8778
|
+
active: this.active.size
|
|
8779
|
+
});
|
|
7811
8780
|
const result = await waitForSettled({
|
|
7812
8781
|
timeoutMs,
|
|
7813
8782
|
isSettled: () => this.active.size === 0 && !this.batcher.hasPending && ctx.isIdle() && !ctx.hasPendingMessages()
|
|
7814
8783
|
});
|
|
7815
|
-
this.record(
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
8784
|
+
this.record(
|
|
8785
|
+
result.settled ? "headless-drain-complete" : "headless-drain-timeout",
|
|
8786
|
+
{
|
|
8787
|
+
durationMs: result.durationMs,
|
|
8788
|
+
active: this.active.size
|
|
8789
|
+
}
|
|
8790
|
+
);
|
|
7819
8791
|
if (!result.settled) this.abortAll("headless-drain-timeout");
|
|
7820
8792
|
}
|
|
7821
8793
|
record(kind, data) {
|
|
7822
|
-
const event = {
|
|
8794
|
+
const event = {
|
|
8795
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8796
|
+
kind,
|
|
8797
|
+
epoch: this.epoch,
|
|
8798
|
+
data
|
|
8799
|
+
};
|
|
7823
8800
|
this.recentEvents.push(event);
|
|
7824
8801
|
if (this.recentEvents.length > 20) this.recentEvents.shift();
|
|
7825
|
-
this.sessionLifetime.run(
|
|
8802
|
+
this.sessionLifetime.run(
|
|
8803
|
+
() => this.pi.appendEntry("shadow-mind-event", event)
|
|
8804
|
+
);
|
|
7826
8805
|
}
|
|
7827
8806
|
setPaused(paused, ctx) {
|
|
7828
8807
|
this.paused = paused;
|
|
7829
8808
|
if (paused) this.abortAll("paused");
|
|
7830
|
-
ctx.ui.notify(
|
|
8809
|
+
ctx.ui.notify(
|
|
8810
|
+
paused ? "Shadow Mind paused" : "Shadow Mind resumed",
|
|
8811
|
+
"info"
|
|
8812
|
+
);
|
|
7831
8813
|
this.updateStatus(ctx);
|
|
7832
8814
|
}
|
|
7833
8815
|
updateStatus(ctx) {
|
|
7834
8816
|
this.sessionLifetime.run(() => {
|
|
7835
|
-
const
|
|
7836
|
-
|
|
7837
|
-
|
|
8817
|
+
const diagnostics = this.usageStore.error ? [...this.diagnostics, `usage: ${this.usageStore.error}`] : this.diagnostics;
|
|
8818
|
+
const warning = diagnostics.length || this.hasRecentRunErrors() ? " !" : "";
|
|
8819
|
+
const usage = `${formatUsageTokens(this.sessionUsage.totalTokens)} \xB7 ${formatUsageCost(this.sessionUsage.cost.total)}`;
|
|
8820
|
+
ctx.ui.setStatus(
|
|
8821
|
+
"shadow-mind",
|
|
8822
|
+
this.paused ? `\u{1F419} Paused \xB7 ${usage}${warning}` : `\u{1F419} ${this.active.size} \xB7 ${usage}${warning}`
|
|
8823
|
+
);
|
|
8824
|
+
if (this.panelVisible)
|
|
8825
|
+
ctx.ui.setWidget("shadow-mind-panel", this.statusLines(), {
|
|
8826
|
+
placement: "aboveEditor"
|
|
8827
|
+
});
|
|
7838
8828
|
});
|
|
7839
8829
|
}
|
|
7840
8830
|
hasRecentRunErrors() {
|
|
7841
|
-
return this.recentEvents.slice(-3).some(
|
|
8831
|
+
return this.recentEvents.slice(-3).some(
|
|
8832
|
+
(event) => event.kind === "run-end" && (event.data?.reason === "error" || event.data?.reason === "timeout")
|
|
8833
|
+
);
|
|
7842
8834
|
}
|
|
7843
8835
|
statusLines() {
|
|
7844
8836
|
const config = this.configStore.current;
|
|
8837
|
+
const diagnostics = this.usageStore.error ? [...this.diagnostics, `usage: ${this.usageStore.error}`] : this.diagnostics;
|
|
7845
8838
|
return [
|
|
7846
8839
|
`\u{1F419} Shadow Mind \xB7 ${this.paused ? "paused" : "active"} \xB7 running ${this.active.size}/${config.maxParallelShadows}`,
|
|
7847
8840
|
`heartbeat ${formatNumber(config.heartbeatProbability)} \xB7 batch ${config.resultBatchWindowMs}ms \xB7 timeout ${config.defaultShadowTimeoutSeconds}s \xB7 drain ${config.headlessDrainTimeoutSeconds}s \xB7 thinking ${config.defaultThinkingLevel}`,
|
|
7848
8841
|
`definitions: ${this.shadowCount} valid \xB7 ${this.diagnostics.length} invalid`,
|
|
8842
|
+
formatUsageDetail("session", this.sessionUsage),
|
|
8843
|
+
`usage lifetime \xB7 ${formatUsageSummary(this.usageStore.current)}`,
|
|
8844
|
+
...this.recentRuns.slice(-3).map(
|
|
8845
|
+
({ shadowName, completedAt, result }) => `recent run \xB7 ${formatCompletedAt(completedAt)} \xB7 ${shadowName} \xB7 ${result.reason} \xB7 ${formatUsageSummary(result.usage)}`
|
|
8846
|
+
),
|
|
8847
|
+
...diagnostics.map((diagnostic) => `diagnostic: ${diagnostic}`),
|
|
7849
8848
|
...this.recentEvents.slice(-5).map((event) => {
|
|
7850
8849
|
const failed = event.kind === "run-end" && (event.data?.reason === "error" || event.data?.reason === "timeout");
|
|
7851
8850
|
const detail = failed ? ` ${event.data?.error ?? event.data?.reason}` : "";
|
|
@@ -7858,11 +8857,22 @@ var ShadowMindRuntime = class {
|
|
|
7858
8857
|
function resolveModel(ctx, fullId) {
|
|
7859
8858
|
const separator = fullId.indexOf("/");
|
|
7860
8859
|
if (separator <= 0 || separator === fullId.length - 1) return void 0;
|
|
7861
|
-
return ctx.modelRegistry.find(
|
|
8860
|
+
return ctx.modelRegistry.find(
|
|
8861
|
+
fullId.slice(0, separator),
|
|
8862
|
+
fullId.slice(separator + 1)
|
|
8863
|
+
);
|
|
7862
8864
|
}
|
|
7863
8865
|
function formatNumber(value) {
|
|
7864
8866
|
return Number(value.toFixed(3)).toString();
|
|
7865
8867
|
}
|
|
8868
|
+
function formatCompletedAt(completedAt) {
|
|
8869
|
+
return new Date(completedAt).toLocaleTimeString("en-GB", {
|
|
8870
|
+
hour: "2-digit",
|
|
8871
|
+
minute: "2-digit",
|
|
8872
|
+
second: "2-digit",
|
|
8873
|
+
hourCycle: "h23"
|
|
8874
|
+
});
|
|
8875
|
+
}
|
|
7866
8876
|
|
|
7867
8877
|
// src/index.ts
|
|
7868
8878
|
function shadowMindExtension(pi) {
|