pi-shadow-mind 0.1.22 → 0.1.23
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 +6 -0
- package/DESIGN.md +3 -1
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/index.js +109 -19
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/DESIGN-Evolution.md
CHANGED
|
@@ -67,3 +67,9 @@
|
|
|
67
67
|
- 发生:用户希望仅对特定主力模型(如 `gpt-6-astra`)关闭某 Shadow,而对其余模型保持启用。
|
|
68
68
|
- 分析:此前仅支持全量 `*` 或显式枚举白名单,针对特定模型禁用必须穷举所有其他合法模型,配置维护成本高;应支持以 `!` 开头的排除规则,支持模型短名与 glob 通配,并在纯排除配置时默认全量模型候选。
|
|
69
69
|
- 改变:`matchesModel` 区分正向与否定规则;命中任一排除规则立即过滤;纯排除列表默认继承 `*`;补充短名、通配、格式破坏与调度过滤回归。
|
|
70
|
+
|
|
71
|
+
## 2026-09-21 · Shadow 会话不加载按会话记录的扩展
|
|
72
|
+
|
|
73
|
+
- 发生:同时安装 Run 归档扩展 pi-experiencev2 时,用户归档的 1098 个 Run 里有 381 个(35%)来自 Shadow 会话,缺摘要率 21.0%,高于用户会话的 9.3%;摘要成功的 301 条也按 Main 的目标撰写,再因为 Shadow 只回 `NOT_RELEVANT` 判成任务未完成,在归档列表里与用户 Run 混排。Shadow 会话不走 CLI 的 `session_start` / `session_shutdown`,这类扩展的关闭路径也不触发,归档 `writers` 表残留 26 条记录。
|
|
74
|
+
- 分析:Shadow 的结论本来就通过 `report_to_main` 进入 Main 会话记录,381 次运行只有 8 次产出报告,单独归档价值接近零。整体关闭扩展不可行,provider 扩展要提供模型。因此按名单排除,默认排除 `pi-experiencev2`,写 `[]` 恢复原状。匹配只能基于扩展文件路径:`DefaultResourceLoader` 先调用 `extensionsOverride`,再调用 `applyExtensionSourceInfo`,回调拿到的 `sourceInfo` 还是 `source: "local"` / `origin: "top-level"` 的占位值,`baseDir` 指向文件所在目录而非包根。包名改从文件向上最近的 `package.json` 读取,遇到 `node_modules` 或 home 目录即停,`index.js` 这类入口文件名不作为标识,避免一个通用词排除掉所有打包扩展。
|
|
75
|
+
- 改变:新增 `excluded_extensions` 配置与 `src/extension-filter.ts`(`isSelfExtension` 一并迁入),`bootstrapSession` 的 `extensionsOverride` 同时应用两个判据。实测对照:`excluded_extensions: []` 时归档 2 条 Run 且残留 1 条 writer,默认值下归档 1 条、writer 0 条,同一次运行的 Shadow debug 日志与 usage 证明 Shadow 确实执行过。
|
package/DESIGN.md
CHANGED
|
@@ -47,6 +47,8 @@ Shadow 的 Markdown 定义持久存在,但运行实例不保留长期记忆。
|
|
|
47
47
|
|
|
48
48
|
每个实例同时快照激活时 Main 的当前工作目录,并在整个临时 AgentSession 中保持不变。Shadow 的工具都以该目录为 `cwd`;Main 后续切换目录只影响新激活的 Shadow。
|
|
49
49
|
|
|
50
|
+
临时 AgentSession 加载的扩展是 Main 的子集:去掉本插件自身,再去掉 `excluded_extensions` 列出的扩展。Shadow 需要 provider 扩展提供模型,因此不整体关闭扩展。排除项按扩展文件路径匹配,可用的标识是包名、包目录名、单文件扩展的文件名和绝对路径,都是精确匹配,不支持通配。
|
|
51
|
+
|
|
50
52
|
## 3. Shadow Mind 实体
|
|
51
53
|
|
|
52
54
|
每个 Shadow Mind 使用一个 Markdown 文件描述。用户可以创建和调整它,主 Agent 也可以通过插件工具创建和调整它。
|
|
@@ -65,7 +67,7 @@ Shadow 定义统一从全局目录加载:
|
|
|
65
67
|
|
|
66
68
|
该目录属于用户数据,不放入插件安装目录,也不随当前项目切换。插件级配置保存在 `config.json`;registry 只扫描目录顶层的 `.md` 文件,不读取 `config.json`,也不递归读取 `logs/`。
|
|
67
69
|
|
|
68
|
-
`config.json` 保存默认 Shadow 模型、`default_thinking_level`、`heartbeat_probability`、`heartbeat_tools`、`max_parallel_shadows`、`default_shadow_timeout_seconds`、`headless_drain_timeout_seconds`、`result_batch_window_ms` 和可选的 `random_seed` 等全局调度配置。`default_shadow_model` 省略时,插件使用激活时的当前 Main 模型;用户也可以配置一个固定默认模型。`default_thinking_level` 的内置默认值为 `low`。
|
|
70
|
+
`config.json` 保存默认 Shadow 模型、`default_thinking_level`、`heartbeat_probability`、`heartbeat_tools`、`max_parallel_shadows`、`default_shadow_timeout_seconds`、`headless_drain_timeout_seconds`、`result_batch_window_ms`、`excluded_extensions` 和可选的 `random_seed` 等全局调度配置。`excluded_extensions` 的内置默认值是 `["pi-experiencev2"]`,写成 `[]` 即加载 Main 的全部扩展。`default_shadow_model` 省略时,插件使用激活时的当前 Main 模型;用户也可以配置一个固定默认模型。`default_thinking_level` 的内置默认值为 `low`。
|
|
69
71
|
|
|
70
72
|
每次 heartbeat 判断或 final-response 调度前,插件检查并重新加载发生变化的 `config.json`。纯文本轮次不会进入 heartbeat,但 Main 的最终文字可以触发 final-response 检查。新配置只影响后续调度和新建实例;已经运行的 Shadow 继续使用启动时取得的配置快照。
|
|
71
73
|
|
package/README.md
CHANGED
|
@@ -97,6 +97,8 @@ On the first session start, the extension creates:
|
|
|
97
97
|
|
|
98
98
|
No default Shadow Mind is created. The global runtime timeout defaults to 300 seconds, and individual Shadows may override it with `timeout_seconds`.
|
|
99
99
|
|
|
100
|
+
A Shadow session loads the same extensions as the main session, minus this plugin and anything listed in `excluded_extensions`. Extensions that archive or record whole sessions would otherwise file every Shadow run as one more user session, so `pi-experiencev2` is excluded by default. An entry is an exact identifier, not a pattern: a package name (`pi-experiencev2`, `@scope/name`), a package directory name, a single-file extension's file name, or an absolute path. Set `"excluded_extensions": []` to load everything the main session loads. Shadows need their model provider, so keep provider extensions off this list.
|
|
101
|
+
|
|
100
102
|
Press `F6` or `Alt+S` to pause or resume Shadow Mind for the current session. On macOS, you may need `Fn+F6`; `Alt+S` corresponds to `Option+S` and depends on the terminal's Option key mapping. The paused footer reads `🐙 Paused` without a redundant zero count. Use `/shadow` to toggle the status panel, `/shadow status` for a summary, or `/shadow toggle`, `/shadow pause`, and `/shadow resume` for command-based control. In TUI mode, `/shadow reports` opens a bounded, scrollable viewer for the five most recently delivered reports, independent of the recent-run list. Use ↑/↓, PageUp/PageDown, or Home/End to scroll; Escape or `/shadow reports hide` closes it. The viewer shows a snapshot; reopen to see new deliveries. Report history is session-local and clears on session replacement or reload; persisted reports remain in the conversation. The status panel keeps its compact summary. Management tools can list, create, update, enable, disable, and delete Shadow Minds, as well as read or update the global configuration. Every write requires user confirmation.
|
|
101
103
|
|
|
102
104
|
## Using with DSH
|
package/README.zh-CN.md
CHANGED
|
@@ -94,6 +94,8 @@ pi install npm:pi-shadow-mind
|
|
|
94
94
|
|
|
95
95
|
扩展不会默认创建 Shadow Mind。全局默认运行超时为 300 秒,单个 Shadow 可以通过 `timeout_seconds` 覆盖。
|
|
96
96
|
|
|
97
|
+
Shadow 会话加载的扩展与 Main 相同,只去掉本插件和 `excluded_extensions` 里列出的扩展。按会话归档或记录的扩展会把每次 Shadow 运行也记成一条用户会话,因此默认排除 `pi-experiencev2`。每个条目是精确标识,不是通配:包名(`pi-experiencev2`、`@scope/name`)、包目录名、单文件扩展的文件名,或绝对路径。写 `"excluded_extensions": []` 就恢复加载 Main 的全部扩展。Shadow 需要自己的模型 provider,不要把 provider 扩展写进这个列表。
|
|
98
|
+
|
|
97
99
|
按 `F6` 或 `Alt+S` 可以暂停或恢复当前 Session 的 Shadow Mind。macOS 上可能需要按 `Fn+F6`;`Alt+S` 对应 `Option+S`,是否生效取决于终端的 Option 键映射。暂停时底部状态显示为 `🐙 Paused`,不再显示没有信息量的零计数。使用 `/shadow` 显示或隐藏状态面板,`/shadow status` 查看摘要,也可以通过 `/shadow toggle`、`/shadow pause` 和 `/shadow resume` 控制状态。TUI 模式下,`/shadow reports` 直接打开限高、可滚动的查看器,展示最近交付的 5 份报告,独立于最近运行列表。使用 ↑/↓、PageUp/PageDown 或 Home/End 滚动,Escape 或 `/shadow reports hide` 关闭。查看器显示打开时的快照,重新打开可查看新报告。报告历史仅保留在当前 Session 内存中,切换会话或 reload 时清空;已持久化的报告仍可在对话记录中查看。状态面板保持简短摘要。管理工具可以查询、创建、更新、启用、禁用和删除 Shadow Mind,以及读取或修改全局配置。所有写操作都需要用户确认。
|
|
98
100
|
|
|
99
101
|
## 在 DSH 中使用
|
package/dist/index.js
CHANGED
|
@@ -158,6 +158,7 @@ function isThinkingLevel(value) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// src/config.ts
|
|
161
|
+
var DEFAULT_EXCLUDED_EXTENSIONS = ["pi-experiencev2"];
|
|
161
162
|
var DEFAULT_CONFIG = {
|
|
162
163
|
heartbeatProbability: 1 / 3,
|
|
163
164
|
heartbeatTools: [],
|
|
@@ -165,6 +166,7 @@ var DEFAULT_CONFIG = {
|
|
|
165
166
|
defaultShadowTimeoutSeconds: 300,
|
|
166
167
|
headlessDrainTimeoutSeconds: 120,
|
|
167
168
|
resultBatchWindowMs: 400,
|
|
169
|
+
excludedExtensions: DEFAULT_EXCLUDED_EXTENSIONS,
|
|
168
170
|
defaultThinkingLevel: "low"
|
|
169
171
|
};
|
|
170
172
|
function parseConfig(input) {
|
|
@@ -178,6 +180,7 @@ function parseConfig(input) {
|
|
|
178
180
|
const timeout = positiveNumber(value.default_shadow_timeout_seconds, DEFAULT_CONFIG.defaultShadowTimeoutSeconds, "default_shadow_timeout_seconds");
|
|
179
181
|
const drainTimeout = positiveNumber(value.headless_drain_timeout_seconds, DEFAULT_CONFIG.headlessDrainTimeoutSeconds, "headless_drain_timeout_seconds");
|
|
180
182
|
const windowMs = nonNegativeInteger(value.result_batch_window_ms, DEFAULT_CONFIG.resultBatchWindowMs, "result_batch_window_ms");
|
|
183
|
+
const excludedExtensions = cleanStringArray(value.excluded_extensions, DEFAULT_CONFIG.excludedExtensions, "excluded_extensions");
|
|
181
184
|
const model = optionalNonEmptyString(value.default_shadow_model, "default_shadow_model");
|
|
182
185
|
const randomSeed = optionalSeed(value.random_seed);
|
|
183
186
|
const thinking = value.default_thinking_level ?? DEFAULT_CONFIG.defaultThinkingLevel;
|
|
@@ -191,6 +194,7 @@ function parseConfig(input) {
|
|
|
191
194
|
defaultShadowTimeoutSeconds: timeout,
|
|
192
195
|
headlessDrainTimeoutSeconds: drainTimeout,
|
|
193
196
|
resultBatchWindowMs: windowMs,
|
|
197
|
+
excludedExtensions,
|
|
194
198
|
defaultShadowModel: model,
|
|
195
199
|
defaultThinkingLevel: thinking,
|
|
196
200
|
randomSeed
|
|
@@ -204,6 +208,8 @@ function serializeConfig(config) {
|
|
|
204
208
|
default_shadow_timeout_seconds: config.defaultShadowTimeoutSeconds,
|
|
205
209
|
headless_drain_timeout_seconds: config.headlessDrainTimeoutSeconds,
|
|
206
210
|
result_batch_window_ms: config.resultBatchWindowMs,
|
|
211
|
+
// Always written, including `[]`: omitting it would silently restore the default.
|
|
212
|
+
excluded_extensions: config.excludedExtensions,
|
|
207
213
|
...config.defaultShadowModel ? { default_shadow_model: config.defaultShadowModel } : {},
|
|
208
214
|
default_thinking_level: config.defaultThinkingLevel,
|
|
209
215
|
...config.randomSeed !== void 0 ? { random_seed: config.randomSeed } : {}
|
|
@@ -7327,6 +7333,7 @@ function configWriteTool(store, getConfig) {
|
|
|
7327
7333
|
Type.Number({ exclusiveMinimum: 0 })
|
|
7328
7334
|
),
|
|
7329
7335
|
result_batch_window_ms: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
7336
|
+
excluded_extensions: Type.Optional(Type.Array(Type.String())),
|
|
7330
7337
|
default_shadow_model: Type.Optional(Type.String()),
|
|
7331
7338
|
default_thinking_level: Type.Optional(THINKING),
|
|
7332
7339
|
random_seed: Type.Optional(
|
|
@@ -7343,6 +7350,7 @@ function configWriteTool(store, getConfig) {
|
|
|
7343
7350
|
default_shadow_timeout_seconds: raw.default_shadow_timeout_seconds ?? current.defaultShadowTimeoutSeconds,
|
|
7344
7351
|
headless_drain_timeout_seconds: raw.headless_drain_timeout_seconds ?? current.headlessDrainTimeoutSeconds,
|
|
7345
7352
|
result_batch_window_ms: raw.result_batch_window_ms ?? current.resultBatchWindowMs,
|
|
7353
|
+
excluded_extensions: raw.excluded_extensions ?? current.excludedExtensions,
|
|
7346
7354
|
default_shadow_model: raw.default_shadow_model ?? current.defaultShadowModel,
|
|
7347
7355
|
default_thinking_level: raw.default_thinking_level ?? current.defaultThinkingLevel,
|
|
7348
7356
|
random_seed: raw.random_seed ?? current.randomSeed
|
|
@@ -7921,8 +7929,7 @@ function isStaleExtensionContextError(error) {
|
|
|
7921
7929
|
|
|
7922
7930
|
// src/shadow-runner.ts
|
|
7923
7931
|
import { mkdir as mkdir3 } from "node:fs/promises";
|
|
7924
|
-
import {
|
|
7925
|
-
import { basename as basename3, dirname as dirname2, join as join4, normalize, resolve } from "node:path";
|
|
7932
|
+
import { join as join5 } from "node:path";
|
|
7926
7933
|
import { Type as Type2 } from "typebox";
|
|
7927
7934
|
import {
|
|
7928
7935
|
DefaultResourceLoader,
|
|
@@ -8136,6 +8143,90 @@ function compactJson(value) {
|
|
|
8136
8143
|
}
|
|
8137
8144
|
}
|
|
8138
8145
|
|
|
8146
|
+
// src/extension-filter.ts
|
|
8147
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
8148
|
+
import { homedir } from "node:os";
|
|
8149
|
+
import { basename as basename3, dirname as dirname2, join as join4, normalize, resolve } from "node:path";
|
|
8150
|
+
import { fileURLToPath } from "node:url";
|
|
8151
|
+
var SELF_PATH = normalize(resolve(fileURLToPath(import.meta.url)));
|
|
8152
|
+
var SELF_DIRECTORY = dirname2(SELF_PATH);
|
|
8153
|
+
var SELF_ENTRIES = /* @__PURE__ */ new Set(["index.ts", "index.js"]);
|
|
8154
|
+
function isSelfExtension(candidate) {
|
|
8155
|
+
const normalized = normalize(resolve(candidate));
|
|
8156
|
+
if (normalized === SELF_PATH) return true;
|
|
8157
|
+
return dirname2(normalized) === SELF_DIRECTORY && SELF_ENTRIES.has(basename3(normalized));
|
|
8158
|
+
}
|
|
8159
|
+
var PACKAGE_LOOKUP_DEPTH = 4;
|
|
8160
|
+
var GENERIC_ENTRY_FILE = /^index\.[^.]+$/;
|
|
8161
|
+
var packageNameCache = /* @__PURE__ */ new Map();
|
|
8162
|
+
function extensionIdentifiers(resolvedPath) {
|
|
8163
|
+
const normalized = normalize(resolve(resolvedPath));
|
|
8164
|
+
const identifiers = /* @__PURE__ */ new Set([normalized]);
|
|
8165
|
+
const owner = findPackage(dirname2(normalized));
|
|
8166
|
+
if (owner) {
|
|
8167
|
+
identifiers.add(owner.name);
|
|
8168
|
+
identifiers.add(basename3(owner.directory));
|
|
8169
|
+
identifiers.add(owner.directory);
|
|
8170
|
+
}
|
|
8171
|
+
const fileName = basename3(normalized);
|
|
8172
|
+
if (!GENERIC_ENTRY_FILE.test(fileName)) {
|
|
8173
|
+
identifiers.add(fileName);
|
|
8174
|
+
identifiers.add(fileName.replace(/\.[^.]+$/, ""));
|
|
8175
|
+
}
|
|
8176
|
+
return identifiers;
|
|
8177
|
+
}
|
|
8178
|
+
function createExtensionExcluder(entries) {
|
|
8179
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
8180
|
+
for (const entry of entries) {
|
|
8181
|
+
const trimmed = entry.trim();
|
|
8182
|
+
if (!trimmed) continue;
|
|
8183
|
+
wanted.add(trimmed);
|
|
8184
|
+
wanted.add(stripScheme(trimmed));
|
|
8185
|
+
wanted.add(normalize(resolve(expandHome(trimmed))));
|
|
8186
|
+
}
|
|
8187
|
+
if (wanted.size === 0) return () => false;
|
|
8188
|
+
return (resolvedPath) => {
|
|
8189
|
+
for (const identifier of extensionIdentifiers(resolvedPath)) {
|
|
8190
|
+
if (wanted.has(identifier)) return true;
|
|
8191
|
+
}
|
|
8192
|
+
return false;
|
|
8193
|
+
};
|
|
8194
|
+
}
|
|
8195
|
+
function findPackage(directory) {
|
|
8196
|
+
if (packageNameCache.has(directory)) return packageNameCache.get(directory);
|
|
8197
|
+
const home = resolve(homedir());
|
|
8198
|
+
let current = directory;
|
|
8199
|
+
let found;
|
|
8200
|
+
for (let depth = 0; depth < PACKAGE_LOOKUP_DEPTH; depth += 1) {
|
|
8201
|
+
if (basename3(current) === "node_modules" || current === home) break;
|
|
8202
|
+
const name = readPackageName(current);
|
|
8203
|
+
if (name) {
|
|
8204
|
+
found = { name, directory: current };
|
|
8205
|
+
break;
|
|
8206
|
+
}
|
|
8207
|
+
const parent = dirname2(current);
|
|
8208
|
+
if (parent === current) break;
|
|
8209
|
+
current = parent;
|
|
8210
|
+
}
|
|
8211
|
+
packageNameCache.set(directory, found);
|
|
8212
|
+
return found;
|
|
8213
|
+
}
|
|
8214
|
+
function readPackageName(directory) {
|
|
8215
|
+
try {
|
|
8216
|
+
const parsed = JSON.parse(readFileSync2(join4(directory, "package.json"), "utf8"));
|
|
8217
|
+
const name = parsed.name;
|
|
8218
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
8219
|
+
} catch {
|
|
8220
|
+
return void 0;
|
|
8221
|
+
}
|
|
8222
|
+
}
|
|
8223
|
+
function stripScheme(source) {
|
|
8224
|
+
return source.replace(/^[a-z][a-z0-9+.-]+:/i, "");
|
|
8225
|
+
}
|
|
8226
|
+
function expandHome(entry) {
|
|
8227
|
+
return entry === "~" || entry.startsWith("~/") ? resolve(homedir(), entry.slice(2)) : entry;
|
|
8228
|
+
}
|
|
8229
|
+
|
|
8139
8230
|
// src/shadow-runner.ts
|
|
8140
8231
|
function resolveShadowTools(whitelist, available, defaults = DEFAULT_READ_TOOLS) {
|
|
8141
8232
|
const builtin = /* @__PURE__ */ new Set(["report_to_main"]);
|
|
@@ -8147,7 +8238,6 @@ function resolveShadowTools(whitelist, available, defaults = DEFAULT_READ_TOOLS)
|
|
|
8147
8238
|
}
|
|
8148
8239
|
return { tools, missing };
|
|
8149
8240
|
}
|
|
8150
|
-
var SELF_PATH = normalize(resolve(fileURLToPath(import.meta.url)));
|
|
8151
8241
|
function runReason(timedOut, reported, aborted, failed = false) {
|
|
8152
8242
|
if (timedOut) return "timeout";
|
|
8153
8243
|
if (reported) return "report";
|
|
@@ -8225,6 +8315,7 @@ var ShadowRunner = class {
|
|
|
8225
8315
|
async bootstrapSession(request, model, controller, state, sessionManager) {
|
|
8226
8316
|
const thinkingLevel = resolveRunThinkingLevel(model, request);
|
|
8227
8317
|
const settingsManager = SettingsManager.create(request.cwd, request.agentDir);
|
|
8318
|
+
const isExcluded = createExtensionExcluder(request.config.excludedExtensions);
|
|
8228
8319
|
const resourceLoader = new DefaultResourceLoader({
|
|
8229
8320
|
cwd: request.cwd,
|
|
8230
8321
|
agentDir: request.agentDir,
|
|
@@ -8236,9 +8327,14 @@ var ShadowRunner = class {
|
|
|
8236
8327
|
appendSystemPrompt: [],
|
|
8237
8328
|
agentsFilesOverride: () => ({ agentsFiles: [] }),
|
|
8238
8329
|
skillsOverride: (base) => ({ skills: [], diagnostics: base.diagnostics }),
|
|
8330
|
+
// Drop this plugin itself plus every extension the user excluded, so
|
|
8331
|
+
// session-scoped extensions (archivers, recorders) never treat an internal
|
|
8332
|
+
// Shadow run as one more user session.
|
|
8239
8333
|
extensionsOverride: (base) => ({
|
|
8240
8334
|
...base,
|
|
8241
|
-
extensions: base.extensions.filter(
|
|
8335
|
+
extensions: base.extensions.filter(
|
|
8336
|
+
(extension) => !isSelfExtension(extension.resolvedPath) && !isExcluded(extension.resolvedPath)
|
|
8337
|
+
)
|
|
8242
8338
|
})
|
|
8243
8339
|
});
|
|
8244
8340
|
await resourceLoader.reload();
|
|
@@ -8331,7 +8427,7 @@ function createReportTool(onReport) {
|
|
|
8331
8427
|
}
|
|
8332
8428
|
async function createShadowSessionManager(request) {
|
|
8333
8429
|
if (!request.shadow.debug) return SessionManager.inMemory(request.cwd);
|
|
8334
|
-
const directory =
|
|
8430
|
+
const directory = join5(request.agentDir, "shadow-minds", "logs", request.shadow.id);
|
|
8335
8431
|
await mkdir3(directory, { recursive: true });
|
|
8336
8432
|
const manager = SessionManager.create(request.cwd, directory);
|
|
8337
8433
|
manager.appendCustomEntry("shadow-mind-run", { runId: request.runId, epoch: request.epoch, shadowId: request.shadow.id });
|
|
@@ -8363,12 +8459,6 @@ function assertTrajectoryFits(model, messages) {
|
|
|
8363
8459
|
throw new Error(`trajectory ~${estimated} tokens exceeds ${model.provider}/${model.id} context window (${model.contextWindow})`);
|
|
8364
8460
|
}
|
|
8365
8461
|
}
|
|
8366
|
-
function isSelfExtension(candidate) {
|
|
8367
|
-
const normalized = normalize(resolve(candidate));
|
|
8368
|
-
if (normalized === SELF_PATH) return true;
|
|
8369
|
-
const sourceDirectory = normalize(resolve(dirname2(SELF_PATH)));
|
|
8370
|
-
return dirname2(normalized) === sourceDirectory && (basename3(normalized) === "index.ts" || basename3(normalized) === "index.js");
|
|
8371
|
-
}
|
|
8372
8462
|
|
|
8373
8463
|
// src/shutdown-drain.ts
|
|
8374
8464
|
async function waitForSettled(options) {
|
|
@@ -8394,12 +8484,12 @@ async function waitForSettled(options) {
|
|
|
8394
8484
|
// src/usage-store.ts
|
|
8395
8485
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8396
8486
|
import { mkdir as mkdir5, readFile as readFile5, rename as rename2, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
8397
|
-
import { dirname as dirname3, join as
|
|
8487
|
+
import { dirname as dirname3, join as join7 } from "node:path";
|
|
8398
8488
|
|
|
8399
8489
|
// src/usage-file-lock.ts
|
|
8400
8490
|
import { randomUUID } from "node:crypto";
|
|
8401
8491
|
import { mkdir as mkdir4, readFile as readFile4, rename, rm, stat as stat2, utimes, writeFile as writeFile3 } from "node:fs/promises";
|
|
8402
|
-
import { join as
|
|
8492
|
+
import { join as join6 } from "node:path";
|
|
8403
8493
|
var USAGE_LOCK_OWNER_FILE = "owner";
|
|
8404
8494
|
var USAGE_LOCK_OPERATION_DIRECTORY = ".operation";
|
|
8405
8495
|
async function acquireUsageFileLock(usagePath, timing = {}) {
|
|
@@ -8436,7 +8526,7 @@ function startLease(lockPath, owner, timing) {
|
|
|
8436
8526
|
return;
|
|
8437
8527
|
}
|
|
8438
8528
|
const now = /* @__PURE__ */ new Date();
|
|
8439
|
-
await utimes(
|
|
8529
|
+
await utimes(join6(lockPath, USAGE_LOCK_OWNER_FILE), now, now);
|
|
8440
8530
|
}).catch(() => void 0);
|
|
8441
8531
|
}, timing.heartbeatMs);
|
|
8442
8532
|
timer.unref();
|
|
@@ -8450,7 +8540,7 @@ function startLease(lockPath, owner, timing) {
|
|
|
8450
8540
|
async function recoverStaleUsageLock(lockPath, timing) {
|
|
8451
8541
|
const observed = await readUsageLockState(lockPath);
|
|
8452
8542
|
if (!observed || !isRecoverableUsageLock(observed, timing.staleMs)) return;
|
|
8453
|
-
const operationPath =
|
|
8543
|
+
const operationPath = join6(lockPath, USAGE_LOCK_OPERATION_DIRECTORY);
|
|
8454
8544
|
if (!await claimUsageLockOperation(operationPath, timing)) return;
|
|
8455
8545
|
try {
|
|
8456
8546
|
const current = await readUsageLockState(lockPath);
|
|
@@ -8468,7 +8558,7 @@ async function recoverStaleUsageLock(lockPath, timing) {
|
|
|
8468
8558
|
}
|
|
8469
8559
|
}
|
|
8470
8560
|
async function releaseUsageLock(lockPath, token, timing) {
|
|
8471
|
-
const operationPath =
|
|
8561
|
+
const operationPath = join6(lockPath, USAGE_LOCK_OPERATION_DIRECTORY);
|
|
8472
8562
|
while (true) {
|
|
8473
8563
|
const observed = await readUsageLockState(lockPath);
|
|
8474
8564
|
if (!observed || observed.owner?.token !== token) return;
|
|
@@ -8523,7 +8613,7 @@ async function claimUsageLockOperation(operationPath, timing) {
|
|
|
8523
8613
|
return false;
|
|
8524
8614
|
}
|
|
8525
8615
|
async function writeOwner(path, owner) {
|
|
8526
|
-
await writeFile3(
|
|
8616
|
+
await writeFile3(join6(path, USAGE_LOCK_OWNER_FILE), JSON.stringify(owner), {
|
|
8527
8617
|
encoding: "utf8",
|
|
8528
8618
|
flag: "wx"
|
|
8529
8619
|
});
|
|
@@ -8537,7 +8627,7 @@ async function readUsageLockState(lockPath) {
|
|
|
8537
8627
|
throw error;
|
|
8538
8628
|
}
|
|
8539
8629
|
try {
|
|
8540
|
-
const ownerPath =
|
|
8630
|
+
const ownerPath = join6(lockPath, USAGE_LOCK_OWNER_FILE);
|
|
8541
8631
|
const [raw, ownerFile] = await Promise.all([readFile4(ownerPath, "utf8"), stat2(ownerPath)]);
|
|
8542
8632
|
return {
|
|
8543
8633
|
owner: parseOwner(raw),
|
|
@@ -8595,7 +8685,7 @@ var UsageStore = class {
|
|
|
8595
8685
|
initialization;
|
|
8596
8686
|
pendingWrite = Promise.resolve();
|
|
8597
8687
|
constructor(agentDir) {
|
|
8598
|
-
this.usagePath =
|
|
8688
|
+
this.usagePath = join7(agentDir, "shadow-minds", "usage.json");
|
|
8599
8689
|
}
|
|
8600
8690
|
initialize() {
|
|
8601
8691
|
this.initialization ??= this.load();
|