@ametyst/cli 0.3.0 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1217 -894
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -112016,12 +112016,17 @@ var init_core = __esm({
|
|
|
112016
112016
|
// src/commands/init-claim.ts
|
|
112017
112017
|
var init_claim_exports = {};
|
|
112018
112018
|
__export(init_claim_exports, {
|
|
112019
|
+
CLAIM_HOT_RELOAD_NOTICE: () => CLAIM_HOT_RELOAD_NOTICE,
|
|
112019
112020
|
WRAP_KEY_BYTES: () => WRAP_KEY_BYTES,
|
|
112021
|
+
backupVaultFile: () => backupVaultFile,
|
|
112020
112022
|
claimErrorMessage: () => claimErrorMessage,
|
|
112021
112023
|
decryptBootstrapBlob: () => decryptBootstrapBlob,
|
|
112022
112024
|
exchangeClaimToken: () => exchangeClaimToken,
|
|
112023
|
-
parseClaimArgument: () => parseClaimArgument
|
|
112025
|
+
parseClaimArgument: () => parseClaimArgument,
|
|
112026
|
+
vaultBackupPath: () => vaultBackupPath,
|
|
112027
|
+
vaultBackupSuffix: () => vaultBackupSuffix
|
|
112024
112028
|
});
|
|
112029
|
+
import { chmodSync as chmodSync3, constants as fsConstants, copyFileSync as copyFileSync3, existsSync as existsSync7 } from "fs";
|
|
112025
112030
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
112026
112031
|
function claimErrorMessage(failure) {
|
|
112027
112032
|
switch (failure.kind) {
|
|
@@ -112157,13 +112162,32 @@ function decryptBootstrapBlob(wrapKey, blob) {
|
|
|
112157
112162
|
return null;
|
|
112158
112163
|
}
|
|
112159
112164
|
}
|
|
112160
|
-
|
|
112165
|
+
function vaultBackupSuffix(at) {
|
|
112166
|
+
return `${at.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z")}`;
|
|
112167
|
+
}
|
|
112168
|
+
function vaultBackupPath(vaultPath, at) {
|
|
112169
|
+
return `${vaultPath}.bak-${vaultBackupSuffix(at)}`;
|
|
112170
|
+
}
|
|
112171
|
+
function backupVaultFile(vaultPath, at = /* @__PURE__ */ new Date()) {
|
|
112172
|
+
if (!existsSync7(vaultPath)) return null;
|
|
112173
|
+
const target = vaultBackupPath(vaultPath, at);
|
|
112174
|
+
try {
|
|
112175
|
+
copyFileSync3(vaultPath, target, fsConstants.COPYFILE_EXCL);
|
|
112176
|
+
} catch (e) {
|
|
112177
|
+
if (e?.code === "EEXIST") return target;
|
|
112178
|
+
throw e;
|
|
112179
|
+
}
|
|
112180
|
+
chmodSync3(target, 384);
|
|
112181
|
+
return target;
|
|
112182
|
+
}
|
|
112183
|
+
var WRAP_KEY_BYTES, BASE64URL, CLAIM_HOT_RELOAD_NOTICE;
|
|
112161
112184
|
var init_init_claim = __esm({
|
|
112162
112185
|
"src/commands/init-claim.ts"() {
|
|
112163
112186
|
"use strict";
|
|
112164
112187
|
init_esm_shims();
|
|
112165
112188
|
WRAP_KEY_BYTES = 32;
|
|
112166
112189
|
BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
112190
|
+
CLAIM_HOT_RELOAD_NOTICE = "Running MCP servers pick up the new identity on their next call. If an agent host still shows the old workspace, restart it fully (Claude desktop: Cmd+Q).";
|
|
112167
112191
|
}
|
|
112168
112192
|
});
|
|
112169
112193
|
|
|
@@ -112173,12 +112197,12 @@ var init_version5 = __esm({
|
|
|
112173
112197
|
"src/version.ts"() {
|
|
112174
112198
|
"use strict";
|
|
112175
112199
|
init_esm_shims();
|
|
112176
|
-
CLI_VERSION = true ? "0.3.
|
|
112200
|
+
CLI_VERSION = true ? "0.3.4" : "0.0.0-dev";
|
|
112177
112201
|
}
|
|
112178
112202
|
});
|
|
112179
112203
|
|
|
112180
112204
|
// src/connections/catalog.ts
|
|
112181
|
-
import { existsSync as
|
|
112205
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
112182
112206
|
import { join as join6 } from "path";
|
|
112183
112207
|
function declaredVerbsHeader() {
|
|
112184
112208
|
return SUPPORTED_HTTP_METHODS.join(",");
|
|
@@ -112435,7 +112459,7 @@ function parseConnectorsResponseDetailed(body, onWarn = warnToStderr) {
|
|
|
112435
112459
|
function readCachedCatalogStatus() {
|
|
112436
112460
|
let raw;
|
|
112437
112461
|
try {
|
|
112438
|
-
if (!
|
|
112462
|
+
if (!existsSync8(CONNECTOR_CACHE_PATH)) return { state: "absent" };
|
|
112439
112463
|
raw = JSON.parse(readFileSync7(CONNECTOR_CACHE_PATH, "utf-8"));
|
|
112440
112464
|
} catch (e) {
|
|
112441
112465
|
return { state: "unreadable", reason: e instanceof Error ? e.message : String(e) };
|
|
@@ -112472,7 +112496,7 @@ function readMirrorRowsFromDisk() {
|
|
|
112472
112496
|
}
|
|
112473
112497
|
function readMirrorContainer() {
|
|
112474
112498
|
try {
|
|
112475
|
-
if (!
|
|
112499
|
+
if (!existsSync8(CONNECTOR_CACHE_PATH)) return { state: "absent" };
|
|
112476
112500
|
const raw = JSON.parse(readFileSync7(CONNECTOR_CACHE_PATH, "utf-8"));
|
|
112477
112501
|
if (!isRecord(raw)) {
|
|
112478
112502
|
return { state: "unreadable", reason: "the mirror's top level is not an object" };
|
|
@@ -112487,7 +112511,7 @@ function readMirrorContainer() {
|
|
|
112487
112511
|
}
|
|
112488
112512
|
function readMirrorFetchedAt() {
|
|
112489
112513
|
try {
|
|
112490
|
-
if (!
|
|
112514
|
+
if (!existsSync8(CONNECTOR_CACHE_PATH)) return "";
|
|
112491
112515
|
const raw = JSON.parse(readFileSync7(CONNECTOR_CACHE_PATH, "utf-8"));
|
|
112492
112516
|
if (!isRecord(raw)) return "";
|
|
112493
112517
|
return typeof raw.fetchedAt === "string" ? raw.fetchedAt : "";
|
|
@@ -113477,7 +113501,7 @@ var init_call2 = __esm({
|
|
|
113477
113501
|
});
|
|
113478
113502
|
|
|
113479
113503
|
// src/connections/engine-store.ts
|
|
113480
|
-
import { chmodSync as
|
|
113504
|
+
import { chmodSync as chmodSync4, closeSync as closeSync2, existsSync as existsSync11, mkdirSync as mkdirSync7, openSync as openSync2 } from "fs";
|
|
113481
113505
|
import { dirname as dirname7, join as join16 } from "path";
|
|
113482
113506
|
function connectionsDbPath() {
|
|
113483
113507
|
return join16(AMETYST_DIR, CONNECTIONS_DB_FILENAME);
|
|
@@ -113485,8 +113509,8 @@ function connectionsDbPath() {
|
|
|
113485
113509
|
function ensureOwnerOnlyFile(path2) {
|
|
113486
113510
|
if (path2 === IN_MEMORY_DB) return;
|
|
113487
113511
|
mkdirSync7(dirname7(path2), { recursive: true, mode: DIR_MODE });
|
|
113488
|
-
if (!
|
|
113489
|
-
|
|
113512
|
+
if (!existsSync11(path2)) closeSync2(openSync2(path2, "a", CONNECTIONS_DB_FILE_MODE));
|
|
113513
|
+
chmodSync4(path2, CONNECTIONS_DB_FILE_MODE);
|
|
113490
113514
|
}
|
|
113491
113515
|
function sqliteWasmDriver(db, raw) {
|
|
113492
113516
|
const connection = {
|
|
@@ -114884,6 +114908,7 @@ walletCommand.command("import").description("Import a pasted session private key
|
|
|
114884
114908
|
|
|
114885
114909
|
// src/commands/init.ts
|
|
114886
114910
|
init_config();
|
|
114911
|
+
init_paths();
|
|
114887
114912
|
init_core();
|
|
114888
114913
|
|
|
114889
114914
|
// src/mcp-server/policy-access.ts
|
|
@@ -115199,7 +115224,14 @@ confirm. No need to re-run init.`);
|
|
|
115199
115224
|
return false;
|
|
115200
115225
|
}
|
|
115201
115226
|
async function claimInit(claim, options) {
|
|
115202
|
-
const {
|
|
115227
|
+
const {
|
|
115228
|
+
parseClaimArgument: parseClaimArgument2,
|
|
115229
|
+
exchangeClaimToken: exchangeClaimToken2,
|
|
115230
|
+
decryptBootstrapBlob: decryptBootstrapBlob2,
|
|
115231
|
+
claimErrorMessage: claimErrorMessage2,
|
|
115232
|
+
backupVaultFile: backupVaultFile2,
|
|
115233
|
+
CLAIM_HOT_RELOAD_NOTICE: CLAIM_HOT_RELOAD_NOTICE2
|
|
115234
|
+
} = await Promise.resolve().then(() => (init_init_claim(), init_claim_exports));
|
|
115203
115235
|
const parsed = parseClaimArgument2(claim);
|
|
115204
115236
|
if (!parsed.ok) {
|
|
115205
115237
|
console.error(claimErrorMessage2(parsed.failure));
|
|
@@ -115226,11 +115258,21 @@ ${claimErrorMessage2({ kind: "decrypt" })}`);
|
|
|
115226
115258
|
}
|
|
115227
115259
|
await loginCommand({ apiKey: exchanged.result.apiKey, preseedAll: options.preseedAll });
|
|
115228
115260
|
const effective = applyWorkspacePassphraseStance(options);
|
|
115261
|
+
try {
|
|
115262
|
+
const backup = backupVaultFile2(VAULT_PATH);
|
|
115263
|
+
if (backup) console.log(`Previous wallet backed up to ${backup}`);
|
|
115264
|
+
} catch (e) {
|
|
115265
|
+
console.error(
|
|
115266
|
+
`Could not back up the existing wallet (${e instanceof Error ? e.message : String(e)}) \u2014 continuing with the claim.`
|
|
115267
|
+
);
|
|
115268
|
+
}
|
|
115229
115269
|
await walletImportCommand({
|
|
115230
115270
|
walletKey: privateKey,
|
|
115231
115271
|
force: effective.force,
|
|
115232
115272
|
noPassphrase: effective.noPassphrase
|
|
115233
115273
|
});
|
|
115274
|
+
console.log(`
|
|
115275
|
+
${CLAIM_HOT_RELOAD_NOTICE2}`);
|
|
115234
115276
|
return exchanged.result.apiKey;
|
|
115235
115277
|
}
|
|
115236
115278
|
async function initCommand(options = {}) {
|
|
@@ -116848,569 +116890,63 @@ init_local_state();
|
|
|
116848
116890
|
init_esm_shims();
|
|
116849
116891
|
init_paths();
|
|
116850
116892
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
116851
|
-
import { join as
|
|
116852
|
-
var TASK_DEFINITION_FILES = [
|
|
116853
|
-
["skill", "SKILL.md", "markdownBody"],
|
|
116854
|
-
["vision", "VISION.md", "visionMd"],
|
|
116855
|
-
["constraints", "CONSTRAINTS.md", "constraintsMd"],
|
|
116856
|
-
["readme", "README.md", "readmeMd"],
|
|
116857
|
-
["dashboardHtml", "dashboard.html", "dashboardHtml"],
|
|
116858
|
-
["dashboardManifest", "dashboard.manifest.json", "dashboardManifest"]
|
|
116859
|
-
];
|
|
116860
|
-
function materializeTask(task, runId) {
|
|
116861
|
-
const dir = loopFireDir(task.slug, runId);
|
|
116862
|
-
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
116863
|
-
const files = {};
|
|
116864
|
-
const skipped = [];
|
|
116865
|
-
for (const [label2, filename, field] of TASK_DEFINITION_FILES) {
|
|
116866
|
-
const body = task[field];
|
|
116867
|
-
if (typeof body === "string" && body.length > 0) {
|
|
116868
|
-
writeFileSync8(join7(dir, filename), body, { mode: 384 });
|
|
116869
|
-
files[label2] = filename;
|
|
116870
|
-
} else {
|
|
116871
|
-
skipped.push(label2);
|
|
116872
|
-
}
|
|
116873
|
-
}
|
|
116874
|
-
writeFileSync8(
|
|
116875
|
-
join7(dir, "STATUS.md"),
|
|
116876
|
-
`# STATUS \u2014 ${task.slug}
|
|
116893
|
+
import { join as join8 } from "path";
|
|
116877
116894
|
|
|
116878
|
-
|
|
116879
|
-
|
|
116880
|
-
|
|
116881
|
-
|
|
116882
|
-
`,
|
|
116883
|
-
{ mode: 384 }
|
|
116884
|
-
);
|
|
116885
|
-
files.status = "STATUS.md";
|
|
116886
|
-
return { dir, files, skipped };
|
|
116887
|
-
}
|
|
116895
|
+
// src/loops/state-docs.ts
|
|
116896
|
+
init_esm_shims();
|
|
116897
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
116898
|
+
import { join as join7 } from "path";
|
|
116888
116899
|
|
|
116889
|
-
// src/loops/
|
|
116900
|
+
// src/loops/shipback.ts
|
|
116890
116901
|
init_esm_shims();
|
|
116891
|
-
|
|
116892
|
-
|
|
116893
|
-
import { spawn as realSpawn } from "child_process";
|
|
116894
|
-
import { join as join8 } from "path";
|
|
116895
|
-
var DEFAULT_PORT = 4477;
|
|
116896
|
-
var DASHBOARD_PORT_ENV = "AMETYST_LOOP_DASHBOARD_PORT";
|
|
116897
|
-
var DASHBOARD_NO_OPEN_ENV = "AMETYST_DASHBOARD_NO_OPEN";
|
|
116898
|
-
var NO_DASHBOARD_MESSAGE = "no dashboard on this task \u2014 ask your agent to create one";
|
|
116899
|
-
var MAX_PORT_RETRIES = 20;
|
|
116900
|
-
var SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
116901
|
-
function parseManifestFiles(manifest) {
|
|
116902
|
-
if (typeof manifest !== "string" || !manifest.trim()) return [];
|
|
116903
|
-
let parsed;
|
|
116904
|
-
try {
|
|
116905
|
-
parsed = JSON.parse(manifest);
|
|
116906
|
-
} catch {
|
|
116907
|
-
return [];
|
|
116908
|
-
}
|
|
116909
|
-
const list2 = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.files) ? parsed.files : [];
|
|
116910
|
-
return list2.filter((f) => typeof f === "string" && SAFE_NAME.test(f));
|
|
116902
|
+
function scanHeadings(text) {
|
|
116903
|
+
return scanDoc(text).items;
|
|
116911
116904
|
}
|
|
116912
|
-
function
|
|
116913
|
-
const
|
|
116914
|
-
|
|
116905
|
+
function scanDoc(text) {
|
|
116906
|
+
const out = [];
|
|
116907
|
+
let offset = 0;
|
|
116908
|
+
let inFence = false;
|
|
116909
|
+
for (const line of text.split("\n")) {
|
|
116910
|
+
const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
116911
|
+
const trimmed = bare.trimStart();
|
|
116912
|
+
if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) inFence = !inFence;
|
|
116913
|
+
else if (!inFence) {
|
|
116914
|
+
const m = /^###\s+(.*)$/.exec(bare);
|
|
116915
|
+
if (m) out.push({ heading: m[1].trim(), start: offset });
|
|
116916
|
+
}
|
|
116917
|
+
offset += line.length + 1;
|
|
116918
|
+
}
|
|
116919
|
+
return { items: out, unbalanced: inFence };
|
|
116915
116920
|
}
|
|
116916
|
-
function
|
|
116917
|
-
const
|
|
116918
|
-
|
|
116919
|
-
|
|
116920
|
-
|
|
116921
|
-
|
|
116922
|
-
const cmd = platform === "darwin" ? "open" : platform === "linux" ? "xdg-open" : null;
|
|
116923
|
-
if (!cmd) return false;
|
|
116924
|
-
const spawn4 = deps.spawn ?? realSpawn;
|
|
116925
|
-
try {
|
|
116926
|
-
const child = spawn4(cmd, [url2], { stdio: "ignore", detached: true });
|
|
116927
|
-
child.once?.("error", () => {
|
|
116928
|
-
});
|
|
116929
|
-
child.unref?.();
|
|
116930
|
-
return true;
|
|
116931
|
-
} catch {
|
|
116932
|
-
return false;
|
|
116921
|
+
function headingsIgnoringFences(text) {
|
|
116922
|
+
const out = [];
|
|
116923
|
+
for (const line of text.split("\n")) {
|
|
116924
|
+
const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
116925
|
+
const m = /^ {0,3}###\s+(.*)$/.exec(bare);
|
|
116926
|
+
if (m) out.push(m[1].trim());
|
|
116933
116927
|
}
|
|
116928
|
+
return out;
|
|
116934
116929
|
}
|
|
116935
|
-
function
|
|
116936
|
-
|
|
116937
|
-
if (typeof html !== "string" || !html) return Promise.resolve(null);
|
|
116938
|
-
const fs = args.deps?.fs ?? realFs;
|
|
116939
|
-
const log = args.deps?.log ?? ((line) => console.error(line));
|
|
116940
|
-
const make = args.deps?.createServer ?? createServer2;
|
|
116941
|
-
const basePort = args.port ?? dashboardPort();
|
|
116942
|
-
const files = parseManifestFiles(args.loop.dashboardManifest);
|
|
116943
|
-
const handler = (req, res) => {
|
|
116944
|
-
try {
|
|
116945
|
-
const url2 = (req.url ?? "/").split("?")[0];
|
|
116946
|
-
if (req.method === "GET" && url2 === "/") {
|
|
116947
|
-
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
116948
|
-
res.end(html);
|
|
116949
|
-
return;
|
|
116950
|
-
}
|
|
116951
|
-
if (req.method === "GET" && url2 === "/data") {
|
|
116952
|
-
const data = {};
|
|
116953
|
-
for (const name of files) {
|
|
116954
|
-
try {
|
|
116955
|
-
const p = join8(args.loopDir, name);
|
|
116956
|
-
if (fs.existsSync(p)) data[name] = fs.readFileSync(p, "utf-8");
|
|
116957
|
-
} catch {
|
|
116958
|
-
}
|
|
116959
|
-
}
|
|
116960
|
-
const state = {};
|
|
116961
|
-
try {
|
|
116962
|
-
const p = join8(args.loopDir, ".state", "fires.jsonl");
|
|
116963
|
-
if (fs.existsSync(p)) state["fires.jsonl"] = fs.readFileSync(p, "utf-8");
|
|
116964
|
-
} catch {
|
|
116965
|
-
}
|
|
116966
|
-
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
116967
|
-
res.end(
|
|
116968
|
-
JSON.stringify({
|
|
116969
|
-
loop: args.loop.slug,
|
|
116970
|
-
files: data,
|
|
116971
|
-
state,
|
|
116972
|
-
mode: "live",
|
|
116973
|
-
asOf: (/* @__PURE__ */ new Date()).toISOString()
|
|
116974
|
-
})
|
|
116975
|
-
);
|
|
116976
|
-
return;
|
|
116977
|
-
}
|
|
116978
|
-
res.writeHead(404, { "content-type": "text/plain" });
|
|
116979
|
-
res.end("not found");
|
|
116980
|
-
} catch {
|
|
116981
|
-
try {
|
|
116982
|
-
res.writeHead(500);
|
|
116983
|
-
res.end();
|
|
116984
|
-
} catch {
|
|
116985
|
-
}
|
|
116986
|
-
}
|
|
116987
|
-
};
|
|
116988
|
-
const bind = (port) => new Promise((resolve) => {
|
|
116989
|
-
const server2 = make(handler);
|
|
116990
|
-
server2.once("error", (err) => {
|
|
116991
|
-
try {
|
|
116992
|
-
server2.close();
|
|
116993
|
-
} catch {
|
|
116994
|
-
}
|
|
116995
|
-
resolve({ ok: false, err });
|
|
116996
|
-
});
|
|
116997
|
-
server2.listen(port, "127.0.0.1", () => {
|
|
116998
|
-
server2.unref();
|
|
116999
|
-
resolve({
|
|
117000
|
-
ok: true,
|
|
117001
|
-
handle: {
|
|
117002
|
-
server: server2,
|
|
117003
|
-
port,
|
|
117004
|
-
close() {
|
|
117005
|
-
try {
|
|
117006
|
-
server2.close();
|
|
117007
|
-
server2.closeAllConnections?.();
|
|
117008
|
-
} catch {
|
|
117009
|
-
}
|
|
117010
|
-
}
|
|
117011
|
-
}
|
|
117012
|
-
});
|
|
117013
|
-
});
|
|
117014
|
-
});
|
|
117015
|
-
return (async () => {
|
|
117016
|
-
const lastPort = Math.min(basePort + MAX_PORT_RETRIES, 65535);
|
|
117017
|
-
for (let port = basePort; port <= lastPort; port++) {
|
|
117018
|
-
const attempt = await bind(port);
|
|
117019
|
-
if (attempt.ok) {
|
|
117020
|
-
log(` loop dashboard: http://localhost:${attempt.handle.port}`);
|
|
117021
|
-
return attempt.handle;
|
|
117022
|
-
}
|
|
117023
|
-
if (attempt.err.code !== "EADDRINUSE") {
|
|
117024
|
-
log(
|
|
117025
|
-
` (loop dashboard not started on :${port} \u2014 ${attempt.err.code ?? attempt.err.message}; run continues)`
|
|
117026
|
-
);
|
|
117027
|
-
return null;
|
|
117028
|
-
}
|
|
117029
|
-
}
|
|
117030
|
-
log(` (loop dashboard not started \u2014 :${basePort}-${lastPort} all in use; run continues)`);
|
|
117031
|
-
return null;
|
|
117032
|
-
})();
|
|
116930
|
+
function headings(text) {
|
|
116931
|
+
return scanHeadings(text).map((h) => h.heading);
|
|
117033
116932
|
}
|
|
117034
|
-
|
|
117035
|
-
|
|
117036
|
-
|
|
117037
|
-
|
|
117038
|
-
var FRONTMATTER_SCAN_LIMIT = 8192;
|
|
117039
|
-
function normalizeRunMode(raw) {
|
|
117040
|
-
if (typeof raw !== "string") return void 0;
|
|
117041
|
-
const v = raw.trim().toLowerCase();
|
|
117042
|
-
if (v === "headless") return "headless";
|
|
117043
|
-
if (v === "in-chat" || v === "inchat" || v === "in_chat") return "in-chat";
|
|
117044
|
-
return void 0;
|
|
116933
|
+
function countByHeading(list2) {
|
|
116934
|
+
const m = /* @__PURE__ */ new Map();
|
|
116935
|
+
for (const h of list2) m.set(h, (m.get(h) ?? 0) + 1);
|
|
116936
|
+
return m;
|
|
117045
116937
|
}
|
|
117046
|
-
function
|
|
117047
|
-
|
|
117048
|
-
|
|
117049
|
-
|
|
117050
|
-
|
|
117051
|
-
|
|
117052
|
-
|
|
116938
|
+
function sections(fragment) {
|
|
116939
|
+
const found = scanHeadings(fragment);
|
|
116940
|
+
if (found.length === 0) return { preamble: fragment, blocks: [] };
|
|
116941
|
+
const preamble = fragment.slice(0, found[0].start);
|
|
116942
|
+
const blocks = found.map((h, i) => ({
|
|
116943
|
+
heading: h.heading,
|
|
116944
|
+
text: fragment.slice(h.start, i + 1 < found.length ? found[i + 1].start : fragment.length)
|
|
116945
|
+
}));
|
|
116946
|
+
return { preamble, blocks };
|
|
117053
116947
|
}
|
|
117054
|
-
function
|
|
117055
|
-
|
|
117056
|
-
if (typeof raw === "string" && raw.trim() === "") return { kind: "omitted" };
|
|
117057
|
-
const mode2 = normalizeRunMode(raw);
|
|
117058
|
-
if (mode2) return { kind: "valid", mode: mode2 };
|
|
117059
|
-
return { kind: "invalid", provided: describeProvided(raw) };
|
|
117060
|
-
}
|
|
117061
|
-
function unquoteScalar(raw) {
|
|
117062
|
-
let v = raw.trim();
|
|
117063
|
-
const comment = v.match(/(?:^|\s)#.*$/);
|
|
117064
|
-
if (comment) v = v.slice(0, comment.index === 0 ? 0 : comment.index).trim();
|
|
117065
|
-
if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'"))) {
|
|
117066
|
-
v = v.slice(1, -1).trim();
|
|
117067
|
-
}
|
|
117068
|
-
return v;
|
|
117069
|
-
}
|
|
117070
|
-
function parseDefaultRunMode(body) {
|
|
117071
|
-
if (typeof body !== "string" || !body) return void 0;
|
|
117072
|
-
const head = body.replace(/^\uFEFF/, "").slice(0, FRONTMATTER_SCAN_LIMIT);
|
|
117073
|
-
const opener = head.match(/^---[ \t]*\r?\n/);
|
|
117074
|
-
if (!opener) return void 0;
|
|
117075
|
-
const rest = head.slice(opener[0].length);
|
|
117076
|
-
const closer = rest.search(/^(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/m);
|
|
117077
|
-
if (closer < 0) return void 0;
|
|
117078
|
-
const block = rest.slice(0, closer);
|
|
117079
|
-
const hit = block.match(/^defaultRunMode[ \t]*:[ \t]*(.*)$/m);
|
|
117080
|
-
if (!hit) return void 0;
|
|
117081
|
-
return normalizeRunMode(unquoteScalar(hit[1] ?? ""));
|
|
117082
|
-
}
|
|
117083
|
-
function resolveRunMode(explicit, body) {
|
|
117084
|
-
const arg = classifyRunModeArgument(explicit);
|
|
117085
|
-
if (arg.kind === "invalid") {
|
|
117086
|
-
return { ok: false, error: "invalid_mode", provided: arg.provided, accepted: ACCEPTED_RUN_MODES };
|
|
117087
|
-
}
|
|
117088
|
-
if (arg.kind === "valid") return { ok: true, mode: arg.mode, source: "explicit" };
|
|
117089
|
-
const fromBody = parseDefaultRunMode(body);
|
|
117090
|
-
if (fromBody) return { ok: true, mode: fromBody, source: "frontmatter" };
|
|
117091
|
-
return { ok: true, mode: "in-chat", source: "default" };
|
|
117092
|
-
}
|
|
117093
|
-
|
|
117094
|
-
// src/loops/estimate.ts
|
|
117095
|
-
init_esm_shims();
|
|
117096
|
-
function estimateBlastRadius(loop2) {
|
|
117097
|
-
const g = loop2.graphJson ?? {};
|
|
117098
|
-
const nodes = Array.isArray(g.nodes) ? g.nodes : [];
|
|
117099
|
-
const steps = nodes.length;
|
|
117100
|
-
const paidSteps = nodes.filter(
|
|
117101
|
-
(n) => n?.type === "spend" || n?.data?.paid === true || n?.paid === true
|
|
117102
|
-
).length;
|
|
117103
|
-
const costHints = nodes.map((n) => Number(n?.data?.estCostEur ?? n?.estCostEur)).filter((x) => !Number.isNaN(x));
|
|
117104
|
-
const estCostEur = costHints.length ? costHints.reduce((a, b) => a + b, 0) : null;
|
|
117105
|
-
return { steps, paidSteps, estCostEur };
|
|
117106
|
-
}
|
|
117107
|
-
|
|
117108
|
-
// src/loops/dashboard-template.ts
|
|
117109
|
-
init_esm_shims();
|
|
117110
|
-
var DEFAULT_DASHBOARD_FILES = [
|
|
117111
|
-
"VISION.md",
|
|
117112
|
-
"CONSTRAINTS.md",
|
|
117113
|
-
"QUEUE.md",
|
|
117114
|
-
"STATUS.md",
|
|
117115
|
-
"README.md",
|
|
117116
|
-
"rounds.jsonl"
|
|
117117
|
-
];
|
|
117118
|
-
function parseProcessSpec(manifest) {
|
|
117119
|
-
if (typeof manifest !== "string" || !manifest.trim()) return null;
|
|
117120
|
-
let parsed;
|
|
117121
|
-
try {
|
|
117122
|
-
parsed = JSON.parse(manifest);
|
|
117123
|
-
} catch {
|
|
117124
|
-
return null;
|
|
117125
|
-
}
|
|
117126
|
-
const raw = parsed?.processSpec;
|
|
117127
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
117128
|
-
const spec = raw;
|
|
117129
|
-
const stages = (Array.isArray(spec.stages) ? spec.stages : []).map((s) => {
|
|
117130
|
-
if (typeof s === "string" && s.trim()) return { label: s.trim() };
|
|
117131
|
-
if (s && typeof s === "object" && !Array.isArray(s)) {
|
|
117132
|
-
const o = s;
|
|
117133
|
-
if (typeof o.label === "string" && o.label.trim()) {
|
|
117134
|
-
return {
|
|
117135
|
-
label: o.label.trim(),
|
|
117136
|
-
...typeof o.id === "string" && o.id.trim() ? { id: o.id.trim() } : {},
|
|
117137
|
-
...typeof o.detail === "string" && o.detail.trim() ? { detail: o.detail.trim() } : {}
|
|
117138
|
-
};
|
|
117139
|
-
}
|
|
117140
|
-
}
|
|
117141
|
-
return null;
|
|
117142
|
-
}).filter((s) => s !== null);
|
|
117143
|
-
const strings = (v) => (Array.isArray(v) ? v : []).filter((x) => typeof x === "string" && x.trim() !== "");
|
|
117144
|
-
const out = {
|
|
117145
|
-
stages,
|
|
117146
|
-
inputs: strings(spec.inputs),
|
|
117147
|
-
outputs: strings(spec.outputs),
|
|
117148
|
-
...typeof spec.title === "string" && spec.title.trim() ? { title: spec.title.trim() } : {}
|
|
117149
|
-
};
|
|
117150
|
-
return stages.length || out.inputs.length || out.outputs.length ? out : null;
|
|
117151
|
-
}
|
|
117152
|
-
function defaultDashboardManifest() {
|
|
117153
|
-
return JSON.stringify({ files: [...DEFAULT_DASHBOARD_FILES] });
|
|
117154
|
-
}
|
|
117155
|
-
function embedJson(value2) {
|
|
117156
|
-
return JSON.stringify(value2).replace(/</g, "\\u003c");
|
|
117157
|
-
}
|
|
117158
|
-
function escapeHtml(s) {
|
|
117159
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
117160
|
-
}
|
|
117161
|
-
function renderLoopDashboardTemplate(args) {
|
|
117162
|
-
const title = escapeHtml(args.processSpec?.title ?? args.slug);
|
|
117163
|
-
const seed = embedJson({
|
|
117164
|
-
slug: args.slug,
|
|
117165
|
-
descriptionShort: args.descriptionShort ?? "",
|
|
117166
|
-
processSpec: args.processSpec ?? null
|
|
117167
|
-
});
|
|
117168
|
-
return `<!doctype html>
|
|
117169
|
-
<html lang="en">
|
|
117170
|
-
<head>
|
|
117171
|
-
<meta charset="utf-8">
|
|
117172
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
117173
|
-
<title>${title} \u2014 loop dashboard</title>
|
|
117174
|
-
<style>
|
|
117175
|
-
:root { --bg:#f7f7fb; --panel:#fff; --border:#e3e3ee; --muted:#6b6b80; --accent:#5b5bd6; --ok:#1a7f37; --bad:#b42318; }
|
|
117176
|
-
* { box-sizing: border-box; }
|
|
117177
|
-
body { margin:0; font:14px/1.5 -apple-system, "Segoe UI", Roboto, sans-serif; background:var(--bg); color:#1d1d2b; padding:24px; }
|
|
117178
|
-
h1 { font-size:20px; margin:0 0 4px; }
|
|
117179
|
-
h2 { font-size:14px; margin:24px 0 8px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }
|
|
117180
|
-
.sub { color:var(--muted); margin:0 0 16px; }
|
|
117181
|
-
.panel { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px; }
|
|
117182
|
-
.map { display:flex; flex-wrap:wrap; align-items:stretch; gap:8px; }
|
|
117183
|
-
.stage { background:var(--panel); border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:10px; padding:10px 14px; min-width:140px; flex:1; }
|
|
117184
|
-
.stage .label { font-weight:600; }
|
|
117185
|
-
.stage .detail { color:var(--muted); font-size:12px; margin-top:2px; }
|
|
117186
|
-
.arrow { align-self:center; color:var(--muted); }
|
|
117187
|
-
.io { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
117188
|
-
ul { margin:6px 0 0; padding-left:18px; }
|
|
117189
|
-
table { width:100%; border-collapse:collapse; background:var(--panel); border:1px solid var(--border); border-radius:12px; overflow:hidden; }
|
|
117190
|
-
th, td { text-align:left; padding:8px 12px; border-top:1px solid var(--border); font-size:13px; vertical-align:top; }
|
|
117191
|
-
thead th { border-top:none; background:var(--bg); color:var(--muted); font-weight:600; }
|
|
117192
|
-
.ok { color:var(--ok); } .bad { color:var(--bad); }
|
|
117193
|
-
details { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:10px 14px; margin-bottom:8px; }
|
|
117194
|
-
summary { cursor:pointer; font-weight:600; }
|
|
117195
|
-
pre { overflow-x:auto; font-size:12px; background:var(--bg); border-radius:8px; padding:10px; }
|
|
117196
|
-
.empty { color:var(--muted); font-style:italic; }
|
|
117197
|
-
#live { font-size:12px; color:var(--muted); float:right; }
|
|
117198
|
-
</style>
|
|
117199
|
-
</head>
|
|
117200
|
-
<body>
|
|
117201
|
-
<span id="live">loading\u2026</span>
|
|
117202
|
-
<h1 id="title"></h1>
|
|
117203
|
-
<p class="sub" id="desc"></p>
|
|
117204
|
-
|
|
117205
|
-
<h2>Process</h2>
|
|
117206
|
-
<div id="map" class="map panel"></div>
|
|
117207
|
-
|
|
117208
|
-
<h2>Inputs & outputs</h2>
|
|
117209
|
-
<div class="io">
|
|
117210
|
-
<div class="panel"><strong>Inputs</strong><ul id="inputs"></ul></div>
|
|
117211
|
-
<div class="panel"><strong>Outputs</strong><ul id="outputs"></ul></div>
|
|
117212
|
-
</div>
|
|
117213
|
-
|
|
117214
|
-
<h2>Rounds</h2>
|
|
117215
|
-
<div id="rounds"></div>
|
|
117216
|
-
|
|
117217
|
-
<h2>Files</h2>
|
|
117218
|
-
<div id="files"></div>
|
|
117219
|
-
|
|
117220
|
-
<script type="application/json" id="loop-seed">${seed}</script>
|
|
117221
|
-
<script>
|
|
117222
|
-
(function () {
|
|
117223
|
-
"use strict";
|
|
117224
|
-
var seed = JSON.parse(document.getElementById("loop-seed").textContent);
|
|
117225
|
-
document.getElementById("title").textContent = (seed.processSpec && seed.processSpec.title) || seed.slug;
|
|
117226
|
-
document.getElementById("desc").textContent = seed.descriptionShort || "";
|
|
117227
|
-
document.title = seed.slug + " \u2014 loop dashboard";
|
|
117228
|
-
|
|
117229
|
-
function el(tag, cls, text) {
|
|
117230
|
-
var e = document.createElement(tag);
|
|
117231
|
-
if (cls) e.className = cls;
|
|
117232
|
-
if (text !== undefined) e.textContent = text;
|
|
117233
|
-
return e;
|
|
117234
|
-
}
|
|
117235
|
-
|
|
117236
|
-
// \u2500\u2500 Process map + IO from the embedded spec (static \u2014 rendered once). \u2500\u2500
|
|
117237
|
-
var map = document.getElementById("map");
|
|
117238
|
-
var spec = seed.processSpec;
|
|
117239
|
-
if (spec && spec.stages && spec.stages.length) {
|
|
117240
|
-
spec.stages.forEach(function (s, i) {
|
|
117241
|
-
if (i > 0) map.appendChild(el("div", "arrow", "\\u2192"));
|
|
117242
|
-
var box = el("div", "stage");
|
|
117243
|
-
box.appendChild(el("div", "label", s.label));
|
|
117244
|
-
if (s.detail) box.appendChild(el("div", "detail", s.detail));
|
|
117245
|
-
map.appendChild(box);
|
|
117246
|
-
});
|
|
117247
|
-
} else {
|
|
117248
|
-
map.appendChild(el("span", "empty", "No process spec on this loop \\u2014 live files and rounds below."));
|
|
117249
|
-
}
|
|
117250
|
-
function fillList(id, items) {
|
|
117251
|
-
var ul = document.getElementById(id);
|
|
117252
|
-
ul.textContent = "";
|
|
117253
|
-
if (!items || !items.length) { ul.appendChild(el("li", "empty", "\\u2014")); return; }
|
|
117254
|
-
items.forEach(function (x) { ul.appendChild(el("li", null, x)); });
|
|
117255
|
-
}
|
|
117256
|
-
fillList("inputs", spec && spec.inputs);
|
|
117257
|
-
fillList("outputs", spec && spec.outputs);
|
|
117258
|
-
|
|
117259
|
-
// \u2500\u2500 Live data: rounds + files, refreshed from GET /data. \u2500\u2500
|
|
117260
|
-
function parseJsonl(text) {
|
|
117261
|
-
var rows = [];
|
|
117262
|
-
(text || "").split("\\n").forEach(function (line) {
|
|
117263
|
-
line = line.trim();
|
|
117264
|
-
if (!line) return;
|
|
117265
|
-
try { rows.push(JSON.parse(line)); } catch (e) { /* skip malformed line */ }
|
|
117266
|
-
});
|
|
117267
|
-
return rows;
|
|
117268
|
-
}
|
|
117269
|
-
|
|
117270
|
-
function renderRounds(fires, rounds) {
|
|
117271
|
-
var host = document.getElementById("rounds");
|
|
117272
|
-
host.textContent = "";
|
|
117273
|
-
if (!fires.length && !rounds.length) {
|
|
117274
|
-
var p = el("div", "panel"); p.appendChild(el("span", "empty", "No rounds yet \\u2014 this fills in as the loop runs."));
|
|
117275
|
-
host.appendChild(p);
|
|
117276
|
-
return;
|
|
117277
|
-
}
|
|
117278
|
-
var table = document.createElement("table");
|
|
117279
|
-
var thead = document.createElement("thead");
|
|
117280
|
-
var hr = document.createElement("tr");
|
|
117281
|
-
["when", "duration", "turns", "tokens", "exit", "round detail (rounds.jsonl)"].forEach(function (h) {
|
|
117282
|
-
hr.appendChild(el("th", null, h));
|
|
117283
|
-
});
|
|
117284
|
-
thead.appendChild(hr); table.appendChild(thead);
|
|
117285
|
-
var tbody = document.createElement("tbody");
|
|
117286
|
-
var n = Math.max(fires.length, rounds.length);
|
|
117287
|
-
for (var i = n - 1; i >= 0; i--) { // newest first
|
|
117288
|
-
var f = fires[i] || {};
|
|
117289
|
-
var r = rounds[i];
|
|
117290
|
-
var tr = document.createElement("tr");
|
|
117291
|
-
tr.appendChild(el("td", null, f.ts || (r && r.ts) || "\\u2014"));
|
|
117292
|
-
tr.appendChild(el("td", null, f.duration_s != null ? f.duration_s + "s" : "\\u2014"));
|
|
117293
|
-
tr.appendChild(el("td", null, f.turns != null ? String(f.turns) : "\\u2014"));
|
|
117294
|
-
tr.appendChild(el("td", null, f.tokens_total != null ? Number(f.tokens_total).toLocaleString() : "\\u2014"));
|
|
117295
|
-
tr.appendChild(el("td", f.exit === 0 ? "ok" : f.exit != null ? "bad" : null, f.exit != null ? String(f.exit) : "\\u2014"));
|
|
117296
|
-
var detail = el("td");
|
|
117297
|
-
if (f.launch_failed) { detail.textContent = "launch failed \\u2014 " + (f.reason || "unknown reason"); }
|
|
117298
|
-
else if (r) { var pre = document.createElement("pre"); pre.textContent = JSON.stringify(r, null, 1); detail.appendChild(pre); }
|
|
117299
|
-
else detail.textContent = "\\u2014";
|
|
117300
|
-
tr.appendChild(detail);
|
|
117301
|
-
tbody.appendChild(tr);
|
|
117302
|
-
}
|
|
117303
|
-
table.appendChild(tbody);
|
|
117304
|
-
host.appendChild(table);
|
|
117305
|
-
}
|
|
117306
|
-
|
|
117307
|
-
function renderFiles(files) {
|
|
117308
|
-
var host = document.getElementById("files");
|
|
117309
|
-
host.textContent = "";
|
|
117310
|
-
var names = Object.keys(files || {}).filter(function (n) { return n !== "rounds.jsonl"; });
|
|
117311
|
-
if (!names.length) { var p = el("div", "panel"); p.appendChild(el("span", "empty", "No files surfaced by the manifest.")); host.appendChild(p); return; }
|
|
117312
|
-
names.forEach(function (name) {
|
|
117313
|
-
var d = document.createElement("details");
|
|
117314
|
-
d.appendChild(el("summary", null, name));
|
|
117315
|
-
var pre = document.createElement("pre");
|
|
117316
|
-
pre.textContent = files[name];
|
|
117317
|
-
d.appendChild(pre);
|
|
117318
|
-
host.appendChild(d);
|
|
117319
|
-
});
|
|
117320
|
-
}
|
|
117321
|
-
|
|
117322
|
-
function refresh() {
|
|
117323
|
-
fetch("/data").then(function (res) { return res.json(); }).then(function (data) {
|
|
117324
|
-
var fires = parseJsonl(data.state && data.state["fires.jsonl"]);
|
|
117325
|
-
var rounds = parseJsonl(data.files && data.files["rounds.jsonl"]);
|
|
117326
|
-
renderRounds(fires, rounds);
|
|
117327
|
-
renderFiles(data.files);
|
|
117328
|
-
document.getElementById("live").textContent = "updated " + new Date().toLocaleTimeString();
|
|
117329
|
-
}).catch(function () {
|
|
117330
|
-
document.getElementById("live").textContent = "server stopped";
|
|
117331
|
-
});
|
|
117332
|
-
}
|
|
117333
|
-
refresh();
|
|
117334
|
-
setInterval(refresh, 5000);
|
|
117335
|
-
})();
|
|
117336
|
-
</script>
|
|
117337
|
-
</body>
|
|
117338
|
-
</html>
|
|
117339
|
-
`;
|
|
117340
|
-
}
|
|
117341
|
-
function injectDefaultDashboard(loop2) {
|
|
117342
|
-
const hasHtml = typeof loop2.dashboardHtml === "string" && loop2.dashboardHtml.trim() !== "";
|
|
117343
|
-
if (hasHtml) return;
|
|
117344
|
-
if (loop2.dashboardManifest && typeof loop2.dashboardManifest === "object") {
|
|
117345
|
-
try {
|
|
117346
|
-
loop2.dashboardManifest = JSON.stringify(loop2.dashboardManifest);
|
|
117347
|
-
} catch {
|
|
117348
|
-
}
|
|
117349
|
-
}
|
|
117350
|
-
const manifest = typeof loop2.dashboardManifest === "string" ? loop2.dashboardManifest : void 0;
|
|
117351
|
-
loop2.dashboardHtml = renderLoopDashboardTemplate({
|
|
117352
|
-
slug: typeof loop2.slug === "string" ? loop2.slug : "loop",
|
|
117353
|
-
descriptionShort: typeof loop2.descriptionShort === "string" ? loop2.descriptionShort : void 0,
|
|
117354
|
-
processSpec: parseProcessSpec(manifest)
|
|
117355
|
-
});
|
|
117356
|
-
if (!manifest || !manifest.trim()) loop2.dashboardManifest = defaultDashboardManifest();
|
|
117357
|
-
}
|
|
117358
|
-
|
|
117359
|
-
// src/loops/state-docs.ts
|
|
117360
|
-
init_esm_shims();
|
|
117361
|
-
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
117362
|
-
import { join as join9 } from "path";
|
|
117363
|
-
|
|
117364
|
-
// src/loops/shipback.ts
|
|
117365
|
-
init_esm_shims();
|
|
117366
|
-
function scanHeadings(text) {
|
|
117367
|
-
return scanDoc(text).items;
|
|
117368
|
-
}
|
|
117369
|
-
function scanDoc(text) {
|
|
117370
|
-
const out = [];
|
|
117371
|
-
let offset = 0;
|
|
117372
|
-
let inFence = false;
|
|
117373
|
-
for (const line of text.split("\n")) {
|
|
117374
|
-
const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
117375
|
-
const trimmed = bare.trimStart();
|
|
117376
|
-
if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) inFence = !inFence;
|
|
117377
|
-
else if (!inFence) {
|
|
117378
|
-
const m = /^###\s+(.*)$/.exec(bare);
|
|
117379
|
-
if (m) out.push({ heading: m[1].trim(), start: offset });
|
|
117380
|
-
}
|
|
117381
|
-
offset += line.length + 1;
|
|
117382
|
-
}
|
|
117383
|
-
return { items: out, unbalanced: inFence };
|
|
117384
|
-
}
|
|
117385
|
-
function headingsIgnoringFences(text) {
|
|
117386
|
-
const out = [];
|
|
117387
|
-
for (const line of text.split("\n")) {
|
|
117388
|
-
const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
117389
|
-
const m = /^ {0,3}###\s+(.*)$/.exec(bare);
|
|
117390
|
-
if (m) out.push(m[1].trim());
|
|
117391
|
-
}
|
|
117392
|
-
return out;
|
|
117393
|
-
}
|
|
117394
|
-
function headings(text) {
|
|
117395
|
-
return scanHeadings(text).map((h) => h.heading);
|
|
117396
|
-
}
|
|
117397
|
-
function countByHeading(list2) {
|
|
117398
|
-
const m = /* @__PURE__ */ new Map();
|
|
117399
|
-
for (const h of list2) m.set(h, (m.get(h) ?? 0) + 1);
|
|
117400
|
-
return m;
|
|
117401
|
-
}
|
|
117402
|
-
function sections(fragment) {
|
|
117403
|
-
const found = scanHeadings(fragment);
|
|
117404
|
-
if (found.length === 0) return { preamble: fragment, blocks: [] };
|
|
117405
|
-
const preamble = fragment.slice(0, found[0].start);
|
|
117406
|
-
const blocks = found.map((h, i) => ({
|
|
117407
|
-
heading: h.heading,
|
|
117408
|
-
text: fragment.slice(h.start, i + 1 < found.length ? found[i + 1].start : fragment.length)
|
|
117409
|
-
}));
|
|
117410
|
-
return { preamble, blocks };
|
|
117411
|
-
}
|
|
117412
|
-
function norm(s) {
|
|
117413
|
-
return s.replace(/\r\n/g, "\n").trimEnd();
|
|
116948
|
+
function norm(s) {
|
|
116949
|
+
return s.replace(/\r\n/g, "\n").trimEnd();
|
|
117414
116950
|
}
|
|
117415
116951
|
function mergeConstraints(boot, materialized, fresh) {
|
|
117416
116952
|
if (materialized === boot) return { next: void 0, added: [], addedBlocks: [], cleanAppend: true };
|
|
@@ -117503,357 +117039,898 @@ function isSafeKey(key) {
|
|
|
117503
117039
|
if (key.includes("/") || key.includes("\\") || key.includes("\0")) return false;
|
|
117504
117040
|
return true;
|
|
117505
117041
|
}
|
|
117506
|
-
function normalizeMemoryScope(scope) {
|
|
117507
|
-
switch (scope) {
|
|
117508
|
-
case "shared":
|
|
117509
|
-
case "workspace":
|
|
117510
|
-
return "shared";
|
|
117511
|
-
case "member":
|
|
117512
|
-
case "profile":
|
|
117513
|
-
return "member";
|
|
117514
|
-
default:
|
|
117515
|
-
return void 0;
|
|
117042
|
+
function normalizeMemoryScope(scope) {
|
|
117043
|
+
switch (scope) {
|
|
117044
|
+
case "shared":
|
|
117045
|
+
case "workspace":
|
|
117046
|
+
return "shared";
|
|
117047
|
+
case "member":
|
|
117048
|
+
case "profile":
|
|
117049
|
+
return "member";
|
|
117050
|
+
default:
|
|
117051
|
+
return void 0;
|
|
117052
|
+
}
|
|
117053
|
+
}
|
|
117054
|
+
function validateMemoryManifest(value2, reserved) {
|
|
117055
|
+
if (value2 === null) return { ok: true, manifest: null };
|
|
117056
|
+
if (Array.isArray(value2)) {
|
|
117057
|
+
return {
|
|
117058
|
+
ok: false,
|
|
117059
|
+
error: 'stateDocs is an ARRAY \u2014 that is the retired declaration shape. The memory manifest is an object: {"docs":[{"key":"\u2026","scope":"shared|member"}],"records":[{"kind":"run|decision|error|import|<free kind>","scope":"shared|member"}]}. Send "null" to clear, or the object form.'
|
|
117060
|
+
};
|
|
117061
|
+
}
|
|
117062
|
+
if (typeof value2 !== "object") {
|
|
117063
|
+
return { ok: false, error: "stateDocs must be a JSON object with `docs` and `records` arrays, or null." };
|
|
117064
|
+
}
|
|
117065
|
+
const obj = value2;
|
|
117066
|
+
const extra = Object.keys(obj).filter((k) => k !== "docs" && k !== "records");
|
|
117067
|
+
if (extra.length > 0) {
|
|
117068
|
+
return { ok: false, error: `stateDocs carries unknown propert${extra.length === 1 ? "y" : "ies"} ${extra.join(", ")} \u2014 only \`docs\` and \`records\` are allowed.` };
|
|
117069
|
+
}
|
|
117070
|
+
if (!Array.isArray(obj.docs) || !Array.isArray(obj.records)) {
|
|
117071
|
+
return { ok: false, error: "stateDocs needs BOTH `docs` and `records` arrays (either may be empty)." };
|
|
117072
|
+
}
|
|
117073
|
+
if (obj.docs.length > MEMORY_MANIFEST_MAX_DOCS) {
|
|
117074
|
+
return { ok: false, error: `stateDocs.docs holds ${obj.docs.length} entries \u2014 the maximum is ${MEMORY_MANIFEST_MAX_DOCS}.` };
|
|
117075
|
+
}
|
|
117076
|
+
if (obj.records.length > MEMORY_MANIFEST_MAX_RECORDS) {
|
|
117077
|
+
return { ok: false, error: `stateDocs.records holds ${obj.records.length} entries \u2014 the maximum is ${MEMORY_MANIFEST_MAX_RECORDS}.` };
|
|
117078
|
+
}
|
|
117079
|
+
const docs = [];
|
|
117080
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
117081
|
+
for (const [i, raw] of obj.docs.entries()) {
|
|
117082
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
117083
|
+
return { ok: false, error: `stateDocs.docs[${i}] must be an object {key, scope}.` };
|
|
117084
|
+
}
|
|
117085
|
+
const d = raw;
|
|
117086
|
+
const extraDoc = Object.keys(d).filter((k) => k !== "key" && k !== "scope");
|
|
117087
|
+
if (extraDoc.length > 0) {
|
|
117088
|
+
return { ok: false, error: `stateDocs.docs[${i}] carries unknown propert${extraDoc.length === 1 ? "y" : "ies"} ${extraDoc.join(", ")} \u2014 a doc is exactly {key, scope}.` };
|
|
117089
|
+
}
|
|
117090
|
+
if (typeof d.key !== "string" || !isSafeKey(d.key)) {
|
|
117091
|
+
return { ok: false, error: `stateDocs.docs[${i}].key must be a safe basename of 1..${KEY_MAX_LENGTH} chars (no \`/\`, \`\\\`, NUL, no leading dot).` };
|
|
117092
|
+
}
|
|
117093
|
+
if (isReservedManifestKey(d.key, reserved)) {
|
|
117094
|
+
return { ok: false, error: `stateDocs.docs[${i}].key "${d.key}" names a file the runner writes itself (${reserved.join(", ")}) \u2014 pick another key.` };
|
|
117095
|
+
}
|
|
117096
|
+
const lower = d.key.toLowerCase();
|
|
117097
|
+
if (seenKeys.has(lower)) {
|
|
117098
|
+
return { ok: false, error: `stateDocs.docs has "${d.key}" more than once (keys are unique case-insensitively).` };
|
|
117099
|
+
}
|
|
117100
|
+
seenKeys.add(lower);
|
|
117101
|
+
if (d.scope !== "shared" && d.scope !== "member") {
|
|
117102
|
+
return { ok: false, error: `stateDocs.docs[${i}].scope must be "shared" or "member".` };
|
|
117103
|
+
}
|
|
117104
|
+
docs.push({ key: d.key, scope: d.scope });
|
|
117105
|
+
}
|
|
117106
|
+
const records = [];
|
|
117107
|
+
const seenKinds = /* @__PURE__ */ new Set();
|
|
117108
|
+
for (const [i, raw] of obj.records.entries()) {
|
|
117109
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
117110
|
+
return { ok: false, error: `stateDocs.records[${i}] must be an object {kind, scope}.` };
|
|
117111
|
+
}
|
|
117112
|
+
const r = raw;
|
|
117113
|
+
const extraRec = Object.keys(r).filter((k) => k !== "kind" && k !== "scope");
|
|
117114
|
+
if (extraRec.length > 0) {
|
|
117115
|
+
return { ok: false, error: `stateDocs.records[${i}] carries unknown propert${extraRec.length === 1 ? "y" : "ies"} ${extraRec.join(", ")} \u2014 a record is exactly {kind, scope}.` };
|
|
117116
|
+
}
|
|
117117
|
+
if (!isRecordKindToken(r.kind)) {
|
|
117118
|
+
return {
|
|
117119
|
+
ok: false,
|
|
117120
|
+
error: `stateDocs.records[${i}].kind must be a token matching [a-z0-9-]{1,64} \u2014 a reserved kind (${RESERVED_RECORD_KINDS.join(", ")}) or a free kind of the task's own (e.g. "pbi").`
|
|
117121
|
+
};
|
|
117122
|
+
}
|
|
117123
|
+
if (seenKinds.has(r.kind)) {
|
|
117124
|
+
return { ok: false, error: `stateDocs.records declares "${r.kind}" more than once.` };
|
|
117125
|
+
}
|
|
117126
|
+
seenKinds.add(r.kind);
|
|
117127
|
+
if (r.scope !== "shared" && r.scope !== "member") {
|
|
117128
|
+
return { ok: false, error: `stateDocs.records[${i}].scope must be "shared" or "member".` };
|
|
117129
|
+
}
|
|
117130
|
+
records.push({ kind: r.kind, scope: r.scope });
|
|
117131
|
+
}
|
|
117132
|
+
return { ok: true, manifest: { docs, records } };
|
|
117133
|
+
}
|
|
117134
|
+
function normalizeMemoryManifest(manifest) {
|
|
117135
|
+
if (!manifest || typeof manifest !== "object") return null;
|
|
117136
|
+
const docs = [];
|
|
117137
|
+
for (const d of Array.isArray(manifest.docs) ? manifest.docs : []) {
|
|
117138
|
+
const scope = normalizeMemoryScope(d?.scope);
|
|
117139
|
+
if (typeof d?.key !== "string" || d.key.length === 0 || !scope) continue;
|
|
117140
|
+
docs.push({ key: d.key, scope });
|
|
117141
|
+
}
|
|
117142
|
+
const records = [];
|
|
117143
|
+
for (const r of Array.isArray(manifest.records) ? manifest.records : []) {
|
|
117144
|
+
const scope = normalizeMemoryScope(r?.scope);
|
|
117145
|
+
if (!isRecordKindToken(r?.kind) || !scope) continue;
|
|
117146
|
+
records.push({ kind: r.kind, scope });
|
|
117147
|
+
}
|
|
117148
|
+
return { docs, records };
|
|
117149
|
+
}
|
|
117150
|
+
async function readTaskManifest(sdk, apiKey, slug) {
|
|
117151
|
+
try {
|
|
117152
|
+
const got = await sdk.tasks.get(apiKey, slug);
|
|
117153
|
+
if (got?.status !== "ok") return void 0;
|
|
117154
|
+
return normalizeMemoryManifest(got.task?.stateDocs ?? null);
|
|
117155
|
+
} catch {
|
|
117156
|
+
return void 0;
|
|
117157
|
+
}
|
|
117158
|
+
}
|
|
117159
|
+
function declaredDocScope(manifest, key) {
|
|
117160
|
+
return manifest?.docs.find((d) => d.key.toLowerCase() === key.toLowerCase())?.scope;
|
|
117161
|
+
}
|
|
117162
|
+
function declaredRecordScope(manifest, kind) {
|
|
117163
|
+
return manifest?.records.find((r) => r.kind === kind)?.scope;
|
|
117164
|
+
}
|
|
117165
|
+
function declaredRecordKinds(manifest) {
|
|
117166
|
+
if (manifest === void 0) return void 0;
|
|
117167
|
+
return manifest?.records.map((r) => r.kind) ?? [];
|
|
117168
|
+
}
|
|
117169
|
+
function formatMemoryManifest(manifest) {
|
|
117170
|
+
const normalized = normalizeMemoryManifest(manifest);
|
|
117171
|
+
if (!normalized) return "(none)";
|
|
117172
|
+
const lines = [
|
|
117173
|
+
...normalized.docs.map((d) => `doc ${d.key} \xB7 ${d.scope}`),
|
|
117174
|
+
...normalized.records.map((r) => `record ${r.kind} \xB7 ${r.scope}`)
|
|
117175
|
+
];
|
|
117176
|
+
return lines.length ? lines.join("\n") : "(declared empty: no docs, no records)";
|
|
117177
|
+
}
|
|
117178
|
+
|
|
117179
|
+
// src/loops/state-docs.ts
|
|
117180
|
+
var RESERVED_FIRE_FILENAMES = [
|
|
117181
|
+
"SKILL.md",
|
|
117182
|
+
"VISION.md",
|
|
117183
|
+
"CONSTRAINTS.md",
|
|
117184
|
+
"README.md",
|
|
117185
|
+
"STATUS.md",
|
|
117186
|
+
"dashboard.html",
|
|
117187
|
+
"dashboard.manifest.json"
|
|
117188
|
+
];
|
|
117189
|
+
var KEY_EXTENSION = /\.[A-Za-z0-9]{1,8}$/;
|
|
117190
|
+
function filenameForKey(key) {
|
|
117191
|
+
return KEY_EXTENSION.test(key) ? key : `${key}.md`;
|
|
117192
|
+
}
|
|
117193
|
+
function isSafeBasename(filename) {
|
|
117194
|
+
if (filename.length === 0) return false;
|
|
117195
|
+
if (filename.startsWith(".")) return false;
|
|
117196
|
+
if (filename.includes("/") || filename.includes("\\")) return false;
|
|
117197
|
+
if (filename === ".." || filename.includes("\0")) return false;
|
|
117198
|
+
return true;
|
|
117199
|
+
}
|
|
117200
|
+
function isReservedName(filename) {
|
|
117201
|
+
return RESERVED_FIRE_FILENAMES.some((r) => r.toLowerCase() === filename.toLowerCase());
|
|
117202
|
+
}
|
|
117203
|
+
var DOC_LIST_LIMIT = 500;
|
|
117204
|
+
async function discoverMemoryDocs(sdk, apiKey, slug) {
|
|
117205
|
+
const out = { own: [], shared: [], failed: [], truncated: [] };
|
|
117206
|
+
for (const scope of ["member", "shared"]) {
|
|
117207
|
+
const res = await sdk.loops.memory.listDocs(apiKey, slug, { scope, limit: DOC_LIST_LIMIT });
|
|
117208
|
+
if (res.status !== "ok") {
|
|
117209
|
+
if (scope === "member" && isNotFound(res)) continue;
|
|
117210
|
+
out.failed.push(scope);
|
|
117211
|
+
continue;
|
|
117212
|
+
}
|
|
117213
|
+
if (res.items.length >= DOC_LIST_LIMIT) out.truncated.push(scope);
|
|
117214
|
+
if (scope === "member") out.own = res.items;
|
|
117215
|
+
else out.shared = res.items;
|
|
117516
117216
|
}
|
|
117217
|
+
return out;
|
|
117517
117218
|
}
|
|
117518
|
-
function
|
|
117519
|
-
|
|
117520
|
-
|
|
117521
|
-
|
|
117522
|
-
|
|
117523
|
-
|
|
117524
|
-
};
|
|
117219
|
+
function planStateDocs(stateDocs, discovered) {
|
|
117220
|
+
const manifest = normalizeMemoryManifest(stateDocs);
|
|
117221
|
+
const candidates = [];
|
|
117222
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
117223
|
+
for (const d of manifest?.docs ?? []) {
|
|
117224
|
+
claimed.add(d.key);
|
|
117225
|
+
candidates.push({ key: d.key, scope: d.scope, declared: true });
|
|
117525
117226
|
}
|
|
117526
|
-
|
|
117527
|
-
|
|
117227
|
+
for (const [rows, scope] of [
|
|
117228
|
+
[discovered.own, "member"],
|
|
117229
|
+
[discovered.shared, "shared"]
|
|
117230
|
+
]) {
|
|
117231
|
+
for (const m of rows) {
|
|
117232
|
+
if (claimed.has(m.key)) continue;
|
|
117233
|
+
claimed.add(m.key);
|
|
117234
|
+
candidates.push({ key: m.key, scope, declared: false });
|
|
117235
|
+
}
|
|
117528
117236
|
}
|
|
117529
|
-
const
|
|
117530
|
-
const
|
|
117531
|
-
|
|
117532
|
-
|
|
117237
|
+
const docs = [];
|
|
117238
|
+
const skipped = [];
|
|
117239
|
+
const taken = /* @__PURE__ */ new Map();
|
|
117240
|
+
for (const c of candidates) {
|
|
117241
|
+
const filename = filenameForKey(c.key);
|
|
117242
|
+
if (!isSafeBasename(filename)) {
|
|
117243
|
+
skipped.push({ key: c.key, reason: `'${filename}' is not a safe basename` });
|
|
117244
|
+
continue;
|
|
117245
|
+
}
|
|
117246
|
+
if (isReservedName(filename)) {
|
|
117247
|
+
skipped.push({
|
|
117248
|
+
key: c.key,
|
|
117249
|
+
reason: `'${filename}' is a file the wrapper writes itself \u2014 rename the key`
|
|
117250
|
+
});
|
|
117251
|
+
continue;
|
|
117252
|
+
}
|
|
117253
|
+
const owner = taken.get(filename.toLowerCase());
|
|
117254
|
+
if (owner !== void 0) {
|
|
117255
|
+
skipped.push({ key: c.key, reason: `'${filename}' is already taken by key '${owner}'` });
|
|
117256
|
+
continue;
|
|
117257
|
+
}
|
|
117258
|
+
taken.set(filename.toLowerCase(), c.key);
|
|
117259
|
+
docs.push({ key: c.key, filename, scope: c.scope, declared: c.declared });
|
|
117533
117260
|
}
|
|
117534
|
-
|
|
117535
|
-
|
|
117261
|
+
return { docs, skipped, manifest };
|
|
117262
|
+
}
|
|
117263
|
+
function describeSource(doc) {
|
|
117264
|
+
const from14 = doc.scope === "shared" ? "shared" : "own";
|
|
117265
|
+
const notes = [];
|
|
117266
|
+
if (doc.created) notes.push(doc.seeded ? "created, seeded from shared" : "created");
|
|
117267
|
+
if (!doc.declared) notes.push("not in manifest");
|
|
117268
|
+
return `${doc.filename} \u2190 ${from14}${notes.length ? ` (${notes.join(", ")})` : ""}`;
|
|
117269
|
+
}
|
|
117270
|
+
async function readScoped(sdk, apiKey, slug, key, scope) {
|
|
117271
|
+
const res = await sdk.loops.memory.getDoc(apiKey, slug, key, { scope });
|
|
117272
|
+
if (res.status === "ok") return { status: "ok", body: res.doc.content ?? "" };
|
|
117273
|
+
if (isNotFound(res)) return { status: "absent" };
|
|
117274
|
+
return { status: "failed" };
|
|
117275
|
+
}
|
|
117276
|
+
async function fetchStateDocs(sdk, apiKey, slug, docs) {
|
|
117277
|
+
const fetched = [];
|
|
117278
|
+
const failed = [];
|
|
117279
|
+
for (const doc of docs) {
|
|
117280
|
+
const read = await readScoped(sdk, apiKey, slug, doc.key, doc.scope);
|
|
117281
|
+
if (read.status === "ok") {
|
|
117282
|
+
fetched.push({ ...doc, body: read.body, created: false, seeded: false });
|
|
117283
|
+
continue;
|
|
117284
|
+
}
|
|
117285
|
+
if (read.status === "failed") {
|
|
117286
|
+
failed.push({ key: doc.key, reason: `could not read the ${doc.scope} row` });
|
|
117287
|
+
continue;
|
|
117288
|
+
}
|
|
117289
|
+
if (!doc.declared) {
|
|
117290
|
+
failed.push({ key: doc.key, reason: `the ${doc.scope} row vanished after the listing` });
|
|
117291
|
+
continue;
|
|
117292
|
+
}
|
|
117293
|
+
let seed = "";
|
|
117294
|
+
let seeded = false;
|
|
117295
|
+
if (doc.scope === "member") {
|
|
117296
|
+
const shared = await readScoped(sdk, apiKey, slug, doc.key, "shared");
|
|
117297
|
+
if (shared.status === "failed") {
|
|
117298
|
+
failed.push({ key: doc.key, reason: "could not read the shared row to seed the member row" });
|
|
117299
|
+
continue;
|
|
117300
|
+
}
|
|
117301
|
+
if (shared.status === "ok") {
|
|
117302
|
+
seed = shared.body;
|
|
117303
|
+
seeded = true;
|
|
117304
|
+
}
|
|
117305
|
+
}
|
|
117306
|
+
const put = await sdk.loops.memory.putDoc(apiKey, slug, doc.key, { content: seed, scope: doc.scope });
|
|
117307
|
+
if (put.status !== "ok") {
|
|
117308
|
+
failed.push({ key: doc.key, reason: `could not create the ${doc.scope} row (${put.error ?? "write refused"})` });
|
|
117309
|
+
continue;
|
|
117310
|
+
}
|
|
117311
|
+
fetched.push({ ...doc, body: seed, created: true, seeded });
|
|
117536
117312
|
}
|
|
117537
|
-
|
|
117538
|
-
|
|
117313
|
+
return { fetched, failed };
|
|
117314
|
+
}
|
|
117315
|
+
function isNotFound(res) {
|
|
117316
|
+
if (typeof res !== "object" || res === null) return false;
|
|
117317
|
+
return res.code === 404;
|
|
117318
|
+
}
|
|
117319
|
+
function docMergeRefusal(boot, fresh, next, cleanAppend) {
|
|
117320
|
+
if (next.trim() === "" && fresh.trim() !== "") {
|
|
117321
|
+
return "would TRUNCATE the stored document to empty while the record still holds content \u2014 an emptied file is not an instruction to erase the record";
|
|
117539
117322
|
}
|
|
117540
|
-
if (
|
|
117541
|
-
return
|
|
117323
|
+
if (!cleanAppend && fresh !== boot && headings(fresh).length === 0) {
|
|
117324
|
+
return "this fire rewrote the document rather than appending to it, the record MOVED under us, and the document has no headings for the shrink guard to count \u2014 so accepting this write would silently discard whatever the concurrent writer added";
|
|
117542
117325
|
}
|
|
117543
|
-
|
|
117544
|
-
|
|
117545
|
-
|
|
117546
|
-
|
|
117547
|
-
|
|
117326
|
+
return void 0;
|
|
117327
|
+
}
|
|
117328
|
+
async function shipBackStateDocs(sdk, apiKey, slug, dir, boot) {
|
|
117329
|
+
const outcomes = [];
|
|
117330
|
+
for (const doc of boot) {
|
|
117331
|
+
const path2 = join7(dir, doc.filename);
|
|
117332
|
+
if (!existsSync9(path2)) {
|
|
117333
|
+
outcomes.push({ key: doc.key, outcome: "unchanged" });
|
|
117334
|
+
continue;
|
|
117548
117335
|
}
|
|
117549
|
-
const
|
|
117550
|
-
const
|
|
117551
|
-
|
|
117552
|
-
|
|
117336
|
+
const materialized = readFileSync8(path2, "utf-8");
|
|
117337
|
+
const reread = await readScoped(sdk, apiKey, slug, doc.key, doc.scope);
|
|
117338
|
+
const fresh = reread.status === "ok" ? reread.body : reread.status === "absent" && doc.body === "" ? "" : void 0;
|
|
117339
|
+
if (fresh === void 0) {
|
|
117340
|
+
outcomes.push({
|
|
117341
|
+
key: doc.key,
|
|
117342
|
+
outcome: "failed",
|
|
117343
|
+
detail: `could not re-read the ${doc.scope} row; ${path2} kept for recovery`
|
|
117344
|
+
});
|
|
117345
|
+
continue;
|
|
117553
117346
|
}
|
|
117554
|
-
|
|
117555
|
-
|
|
117347
|
+
const merged = mergeConstraints(doc.body, materialized, fresh);
|
|
117348
|
+
if (merged.refusal) {
|
|
117349
|
+
outcomes.push({ key: doc.key, outcome: "refused", detail: merged.refusal });
|
|
117350
|
+
continue;
|
|
117556
117351
|
}
|
|
117557
|
-
if (
|
|
117558
|
-
|
|
117352
|
+
if (merged.next === void 0) {
|
|
117353
|
+
outcomes.push({ key: doc.key, outcome: "unchanged" });
|
|
117354
|
+
continue;
|
|
117559
117355
|
}
|
|
117560
|
-
const
|
|
117561
|
-
if (
|
|
117562
|
-
|
|
117356
|
+
const docRefusal = docMergeRefusal(doc.body, fresh, merged.next, merged.cleanAppend);
|
|
117357
|
+
if (docRefusal) {
|
|
117358
|
+
outcomes.push({
|
|
117359
|
+
key: doc.key,
|
|
117360
|
+
outcome: "refused",
|
|
117361
|
+
detail: `${docRefusal}; ${path2} kept for recovery`
|
|
117362
|
+
});
|
|
117363
|
+
continue;
|
|
117563
117364
|
}
|
|
117564
|
-
|
|
117565
|
-
|
|
117566
|
-
|
|
117365
|
+
const put = await sdk.loops.memory.putDoc(apiKey, slug, doc.key, {
|
|
117366
|
+
content: merged.next,
|
|
117367
|
+
scope: doc.scope
|
|
117368
|
+
});
|
|
117369
|
+
if (put.status !== "ok") {
|
|
117370
|
+
outcomes.push({
|
|
117371
|
+
key: doc.key,
|
|
117372
|
+
outcome: "failed",
|
|
117373
|
+
detail: `write to the ${doc.scope} row rejected; ${path2} kept for recovery`
|
|
117374
|
+
});
|
|
117375
|
+
continue;
|
|
117567
117376
|
}
|
|
117568
|
-
|
|
117377
|
+
const after = await sdk.loops.memory.getDoc(apiKey, slug, doc.key, { scope: doc.scope });
|
|
117378
|
+
if (after.status !== "ok") {
|
|
117379
|
+
outcomes.push({ key: doc.key, outcome: "shipped", detail: "could not verify it landed" });
|
|
117380
|
+
continue;
|
|
117381
|
+
}
|
|
117382
|
+
const afterText = after.doc.content ?? "";
|
|
117383
|
+
const missing = merged.addedBlocks.length > 0 ? verifyLanded(afterText, merged.addedBlocks) : afterText === merged.next ? [] : ["<rewrite did not survive>"];
|
|
117384
|
+
outcomes.push(
|
|
117385
|
+
missing.length === 0 ? { key: doc.key, outcome: "shipped" } : {
|
|
117386
|
+
key: doc.key,
|
|
117387
|
+
outcome: "failed",
|
|
117388
|
+
detail: `${missing.length} section(s) did not survive a concurrent write; ${path2} kept`
|
|
117389
|
+
}
|
|
117390
|
+
);
|
|
117569
117391
|
}
|
|
117570
|
-
|
|
117571
|
-
|
|
117572
|
-
|
|
117573
|
-
|
|
117574
|
-
|
|
117392
|
+
return outcomes;
|
|
117393
|
+
}
|
|
117394
|
+
|
|
117395
|
+
// src/mcp-server/materialize-task.ts
|
|
117396
|
+
var TASK_DEFINITION_FILES = [
|
|
117397
|
+
["skill", "SKILL.md", "markdownBody"],
|
|
117398
|
+
["vision", "VISION.md", "visionMd"],
|
|
117399
|
+
["constraints", "CONSTRAINTS.md", "constraintsMd"],
|
|
117400
|
+
["readme", "README.md", "readmeMd"],
|
|
117401
|
+
["dashboardHtml", "dashboard.html", "dashboardHtml"],
|
|
117402
|
+
["dashboardManifest", "dashboard.manifest.json", "dashboardManifest"]
|
|
117403
|
+
];
|
|
117404
|
+
function materializeTask(task, runId) {
|
|
117405
|
+
const dir = loopFireDir(task.slug, runId);
|
|
117406
|
+
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
117407
|
+
const files = {};
|
|
117408
|
+
const skipped = [];
|
|
117409
|
+
for (const [label2, filename, field] of TASK_DEFINITION_FILES) {
|
|
117410
|
+
const body = task[field];
|
|
117411
|
+
if (typeof body === "string" && body.length > 0) {
|
|
117412
|
+
writeFileSync8(join8(dir, filename), body, { mode: 384 });
|
|
117413
|
+
files[label2] = filename;
|
|
117414
|
+
} else {
|
|
117415
|
+
skipped.push(label2);
|
|
117575
117416
|
}
|
|
117576
|
-
|
|
117577
|
-
|
|
117578
|
-
|
|
117579
|
-
|
|
117417
|
+
}
|
|
117418
|
+
writeFileSync8(
|
|
117419
|
+
join8(dir, "STATUS.md"),
|
|
117420
|
+
`# STATUS \u2014 ${task.slug}
|
|
117421
|
+
|
|
117422
|
+
task_id: ${task.id}
|
|
117423
|
+
run_id: ${runId}
|
|
117424
|
+
started: pending
|
|
117425
|
+
queue: not started
|
|
117426
|
+
`,
|
|
117427
|
+
{ mode: 384 }
|
|
117428
|
+
);
|
|
117429
|
+
files.status = "STATUS.md";
|
|
117430
|
+
return { dir, files, skipped };
|
|
117431
|
+
}
|
|
117432
|
+
async function materializeMemoryDocs(sdk, apiKey, task, dir) {
|
|
117433
|
+
const files = {};
|
|
117434
|
+
const notes = [];
|
|
117435
|
+
try {
|
|
117436
|
+
const discovered = await discoverMemoryDocs(sdk, apiKey, task.slug);
|
|
117437
|
+
for (const scope of discovered.failed) {
|
|
117438
|
+
notes.push(`could not list the ${scope} memory docs \u2014 undeclared docs there are not materialized`);
|
|
117580
117439
|
}
|
|
117581
|
-
|
|
117582
|
-
|
|
117583
|
-
|
|
117584
|
-
error: `stateDocs.records[${i}].kind must be a token matching [a-z0-9-]{1,64} \u2014 a reserved kind (${RESERVED_RECORD_KINDS.join(", ")}) or a free kind of the task's own (e.g. "pbi").`
|
|
117585
|
-
};
|
|
117440
|
+
const plan = planStateDocs(task.stateDocs ?? null, discovered);
|
|
117441
|
+
for (const s of plan.skipped) {
|
|
117442
|
+
notes.push(`memory doc '${s.key}' not materialized: ${s.reason}`);
|
|
117586
117443
|
}
|
|
117587
|
-
if (
|
|
117588
|
-
|
|
117444
|
+
if (plan.docs.length === 0) return { files, notes };
|
|
117445
|
+
const { fetched, failed } = await fetchStateDocs(sdk, apiKey, task.slug, plan.docs);
|
|
117446
|
+
for (const f of failed) {
|
|
117447
|
+
notes.push(`memory doc '${f.key}' not materialized: ${f.reason}`);
|
|
117589
117448
|
}
|
|
117590
|
-
|
|
117591
|
-
|
|
117592
|
-
|
|
117449
|
+
for (const doc of fetched) {
|
|
117450
|
+
writeFileSync8(join8(dir, doc.filename), doc.body, { mode: 384 });
|
|
117451
|
+
files[`doc:${doc.key}`] = doc.filename;
|
|
117452
|
+
notes.push(describeSource(doc));
|
|
117593
117453
|
}
|
|
117594
|
-
|
|
117454
|
+
return { files, notes };
|
|
117455
|
+
} catch (err) {
|
|
117456
|
+
notes.push(
|
|
117457
|
+
`memory docs not materialized (${err instanceof Error ? err.message : String(err)}) \u2014 the run continues; read them with taskMemoryGet instead`
|
|
117458
|
+
);
|
|
117459
|
+
return { files, notes };
|
|
117595
117460
|
}
|
|
117596
|
-
return { ok: true, manifest: { docs, records } };
|
|
117597
117461
|
}
|
|
117598
|
-
|
|
117599
|
-
|
|
117600
|
-
|
|
117601
|
-
|
|
117602
|
-
|
|
117603
|
-
|
|
117604
|
-
|
|
117462
|
+
|
|
117463
|
+
// src/mcp-server/index.ts
|
|
117464
|
+
import { existsSync as existsSync12 } from "fs";
|
|
117465
|
+
|
|
117466
|
+
// src/loops/dashboard.ts
|
|
117467
|
+
init_esm_shims();
|
|
117468
|
+
import { createServer as createServer2 } from "http";
|
|
117469
|
+
import * as realFs from "fs";
|
|
117470
|
+
import { spawn as realSpawn } from "child_process";
|
|
117471
|
+
import { join as join9 } from "path";
|
|
117472
|
+
var DEFAULT_PORT = 4477;
|
|
117473
|
+
var DASHBOARD_PORT_ENV = "AMETYST_LOOP_DASHBOARD_PORT";
|
|
117474
|
+
var DASHBOARD_NO_OPEN_ENV = "AMETYST_DASHBOARD_NO_OPEN";
|
|
117475
|
+
var NO_DASHBOARD_MESSAGE = "no dashboard on this task \u2014 ask your agent to create one";
|
|
117476
|
+
var MAX_PORT_RETRIES = 20;
|
|
117477
|
+
var SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
117478
|
+
function parseManifestFiles(manifest) {
|
|
117479
|
+
if (typeof manifest !== "string" || !manifest.trim()) return [];
|
|
117480
|
+
let parsed;
|
|
117481
|
+
try {
|
|
117482
|
+
parsed = JSON.parse(manifest);
|
|
117483
|
+
} catch {
|
|
117484
|
+
return [];
|
|
117605
117485
|
}
|
|
117606
|
-
const
|
|
117607
|
-
|
|
117608
|
-
|
|
117609
|
-
|
|
117610
|
-
|
|
117486
|
+
const list2 = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.files) ? parsed.files : [];
|
|
117487
|
+
return list2.filter((f) => typeof f === "string" && SAFE_NAME.test(f));
|
|
117488
|
+
}
|
|
117489
|
+
function dashboardPort(env = process.env) {
|
|
117490
|
+
const raw = Number(env[DASHBOARD_PORT_ENV]);
|
|
117491
|
+
return Number.isInteger(raw) && raw > 0 && raw < 65536 ? raw : DEFAULT_PORT;
|
|
117492
|
+
}
|
|
117493
|
+
function openDashboardInBrowser(url2, deps = {}) {
|
|
117494
|
+
const env = deps.env ?? process.env;
|
|
117495
|
+
if (env[DASHBOARD_NO_OPEN_ENV] === "1") return false;
|
|
117496
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdout.isTTY);
|
|
117497
|
+
if (!isTTY) return false;
|
|
117498
|
+
const platform = deps.platform ?? process.platform;
|
|
117499
|
+
const cmd = platform === "darwin" ? "open" : platform === "linux" ? "xdg-open" : null;
|
|
117500
|
+
if (!cmd) return false;
|
|
117501
|
+
const spawn4 = deps.spawn ?? realSpawn;
|
|
117502
|
+
try {
|
|
117503
|
+
const child = spawn4(cmd, [url2], { stdio: "ignore", detached: true });
|
|
117504
|
+
child.once?.("error", () => {
|
|
117505
|
+
});
|
|
117506
|
+
child.unref?.();
|
|
117507
|
+
return true;
|
|
117508
|
+
} catch {
|
|
117509
|
+
return false;
|
|
117611
117510
|
}
|
|
117612
|
-
return { docs, records };
|
|
117613
117511
|
}
|
|
117614
|
-
|
|
117512
|
+
function startDashboardServer(args) {
|
|
117513
|
+
const html = args.loop.dashboardHtml;
|
|
117514
|
+
if (typeof html !== "string" || !html) return Promise.resolve(null);
|
|
117515
|
+
const fs = args.deps?.fs ?? realFs;
|
|
117516
|
+
const log = args.deps?.log ?? ((line) => console.error(line));
|
|
117517
|
+
const make = args.deps?.createServer ?? createServer2;
|
|
117518
|
+
const basePort = args.port ?? dashboardPort();
|
|
117519
|
+
const files = parseManifestFiles(args.loop.dashboardManifest);
|
|
117520
|
+
const handler = (req, res) => {
|
|
117521
|
+
try {
|
|
117522
|
+
const url2 = (req.url ?? "/").split("?")[0];
|
|
117523
|
+
if (req.method === "GET" && url2 === "/") {
|
|
117524
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
117525
|
+
res.end(html);
|
|
117526
|
+
return;
|
|
117527
|
+
}
|
|
117528
|
+
if (req.method === "GET" && url2 === "/data") {
|
|
117529
|
+
const data = {};
|
|
117530
|
+
for (const name of files) {
|
|
117531
|
+
try {
|
|
117532
|
+
const p = join9(args.loopDir, name);
|
|
117533
|
+
if (fs.existsSync(p)) data[name] = fs.readFileSync(p, "utf-8");
|
|
117534
|
+
} catch {
|
|
117535
|
+
}
|
|
117536
|
+
}
|
|
117537
|
+
const state = {};
|
|
117538
|
+
try {
|
|
117539
|
+
const p = join9(args.loopDir, ".state", "fires.jsonl");
|
|
117540
|
+
if (fs.existsSync(p)) state["fires.jsonl"] = fs.readFileSync(p, "utf-8");
|
|
117541
|
+
} catch {
|
|
117542
|
+
}
|
|
117543
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
117544
|
+
res.end(
|
|
117545
|
+
JSON.stringify({
|
|
117546
|
+
loop: args.loop.slug,
|
|
117547
|
+
files: data,
|
|
117548
|
+
state,
|
|
117549
|
+
mode: "live",
|
|
117550
|
+
asOf: (/* @__PURE__ */ new Date()).toISOString()
|
|
117551
|
+
})
|
|
117552
|
+
);
|
|
117553
|
+
return;
|
|
117554
|
+
}
|
|
117555
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
117556
|
+
res.end("not found");
|
|
117557
|
+
} catch {
|
|
117558
|
+
try {
|
|
117559
|
+
res.writeHead(500);
|
|
117560
|
+
res.end();
|
|
117561
|
+
} catch {
|
|
117562
|
+
}
|
|
117563
|
+
}
|
|
117564
|
+
};
|
|
117565
|
+
const bind = (port) => new Promise((resolve) => {
|
|
117566
|
+
const server2 = make(handler);
|
|
117567
|
+
server2.once("error", (err) => {
|
|
117568
|
+
try {
|
|
117569
|
+
server2.close();
|
|
117570
|
+
} catch {
|
|
117571
|
+
}
|
|
117572
|
+
resolve({ ok: false, err });
|
|
117573
|
+
});
|
|
117574
|
+
server2.listen(port, "127.0.0.1", () => {
|
|
117575
|
+
server2.unref();
|
|
117576
|
+
resolve({
|
|
117577
|
+
ok: true,
|
|
117578
|
+
handle: {
|
|
117579
|
+
server: server2,
|
|
117580
|
+
port,
|
|
117581
|
+
close() {
|
|
117582
|
+
try {
|
|
117583
|
+
server2.close();
|
|
117584
|
+
server2.closeAllConnections?.();
|
|
117585
|
+
} catch {
|
|
117586
|
+
}
|
|
117587
|
+
}
|
|
117588
|
+
}
|
|
117589
|
+
});
|
|
117590
|
+
});
|
|
117591
|
+
});
|
|
117592
|
+
return (async () => {
|
|
117593
|
+
const lastPort = Math.min(basePort + MAX_PORT_RETRIES, 65535);
|
|
117594
|
+
for (let port = basePort; port <= lastPort; port++) {
|
|
117595
|
+
const attempt = await bind(port);
|
|
117596
|
+
if (attempt.ok) {
|
|
117597
|
+
log(` loop dashboard: http://localhost:${attempt.handle.port}`);
|
|
117598
|
+
return attempt.handle;
|
|
117599
|
+
}
|
|
117600
|
+
if (attempt.err.code !== "EADDRINUSE") {
|
|
117601
|
+
log(
|
|
117602
|
+
` (loop dashboard not started on :${port} \u2014 ${attempt.err.code ?? attempt.err.message}; run continues)`
|
|
117603
|
+
);
|
|
117604
|
+
return null;
|
|
117605
|
+
}
|
|
117606
|
+
}
|
|
117607
|
+
log(` (loop dashboard not started \u2014 :${basePort}-${lastPort} all in use; run continues)`);
|
|
117608
|
+
return null;
|
|
117609
|
+
})();
|
|
117610
|
+
}
|
|
117611
|
+
|
|
117612
|
+
// src/mcp-server/task-run-mode.ts
|
|
117613
|
+
init_esm_shims();
|
|
117614
|
+
var ACCEPTED_RUN_MODES = ["in-chat", "headless"];
|
|
117615
|
+
var FRONTMATTER_SCAN_LIMIT = 8192;
|
|
117616
|
+
function normalizeRunMode(raw) {
|
|
117617
|
+
if (typeof raw !== "string") return void 0;
|
|
117618
|
+
const v = raw.trim().toLowerCase();
|
|
117619
|
+
if (v === "headless") return "headless";
|
|
117620
|
+
if (v === "in-chat" || v === "inchat" || v === "in_chat") return "in-chat";
|
|
117621
|
+
return void 0;
|
|
117622
|
+
}
|
|
117623
|
+
function describeProvided(raw) {
|
|
117624
|
+
if (typeof raw === "string") return raw.trim();
|
|
117615
117625
|
try {
|
|
117616
|
-
|
|
117617
|
-
if (got?.status !== "ok") return void 0;
|
|
117618
|
-
return normalizeMemoryManifest(got.task?.stateDocs ?? null);
|
|
117626
|
+
return JSON.stringify(raw) ?? String(raw);
|
|
117619
117627
|
} catch {
|
|
117620
|
-
return
|
|
117628
|
+
return String(raw);
|
|
117621
117629
|
}
|
|
117622
117630
|
}
|
|
117623
|
-
function
|
|
117624
|
-
|
|
117631
|
+
function classifyRunModeArgument(raw) {
|
|
117632
|
+
if (raw === void 0 || raw === null) return { kind: "omitted" };
|
|
117633
|
+
if (typeof raw === "string" && raw.trim() === "") return { kind: "omitted" };
|
|
117634
|
+
const mode2 = normalizeRunMode(raw);
|
|
117635
|
+
if (mode2) return { kind: "valid", mode: mode2 };
|
|
117636
|
+
return { kind: "invalid", provided: describeProvided(raw) };
|
|
117625
117637
|
}
|
|
117626
|
-
function
|
|
117627
|
-
|
|
117638
|
+
function unquoteScalar(raw) {
|
|
117639
|
+
let v = raw.trim();
|
|
117640
|
+
const comment = v.match(/(?:^|\s)#.*$/);
|
|
117641
|
+
if (comment) v = v.slice(0, comment.index === 0 ? 0 : comment.index).trim();
|
|
117642
|
+
if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'"))) {
|
|
117643
|
+
v = v.slice(1, -1).trim();
|
|
117644
|
+
}
|
|
117645
|
+
return v;
|
|
117628
117646
|
}
|
|
117629
|
-
function
|
|
117630
|
-
if (
|
|
117631
|
-
|
|
117647
|
+
function parseDefaultRunMode(body) {
|
|
117648
|
+
if (typeof body !== "string" || !body) return void 0;
|
|
117649
|
+
const head = body.replace(/^\uFEFF/, "").slice(0, FRONTMATTER_SCAN_LIMIT);
|
|
117650
|
+
const opener = head.match(/^---[ \t]*\r?\n/);
|
|
117651
|
+
if (!opener) return void 0;
|
|
117652
|
+
const rest = head.slice(opener[0].length);
|
|
117653
|
+
const closer = rest.search(/^(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/m);
|
|
117654
|
+
if (closer < 0) return void 0;
|
|
117655
|
+
const block = rest.slice(0, closer);
|
|
117656
|
+
const hit = block.match(/^defaultRunMode[ \t]*:[ \t]*(.*)$/m);
|
|
117657
|
+
if (!hit) return void 0;
|
|
117658
|
+
return normalizeRunMode(unquoteScalar(hit[1] ?? ""));
|
|
117659
|
+
}
|
|
117660
|
+
function resolveRunMode(explicit, body) {
|
|
117661
|
+
const arg = classifyRunModeArgument(explicit);
|
|
117662
|
+
if (arg.kind === "invalid") {
|
|
117663
|
+
return { ok: false, error: "invalid_mode", provided: arg.provided, accepted: ACCEPTED_RUN_MODES };
|
|
117664
|
+
}
|
|
117665
|
+
if (arg.kind === "valid") return { ok: true, mode: arg.mode, source: "explicit" };
|
|
117666
|
+
const fromBody = parseDefaultRunMode(body);
|
|
117667
|
+
if (fromBody) return { ok: true, mode: fromBody, source: "frontmatter" };
|
|
117668
|
+
return { ok: true, mode: "in-chat", source: "default" };
|
|
117632
117669
|
}
|
|
117633
|
-
|
|
117634
|
-
|
|
117635
|
-
|
|
117636
|
-
|
|
117637
|
-
|
|
117638
|
-
|
|
117639
|
-
|
|
117640
|
-
|
|
117670
|
+
|
|
117671
|
+
// src/loops/estimate.ts
|
|
117672
|
+
init_esm_shims();
|
|
117673
|
+
function estimateBlastRadius(loop2) {
|
|
117674
|
+
const g = loop2.graphJson ?? {};
|
|
117675
|
+
const nodes = Array.isArray(g.nodes) ? g.nodes : [];
|
|
117676
|
+
const steps = nodes.length;
|
|
117677
|
+
const paidSteps = nodes.filter(
|
|
117678
|
+
(n) => n?.type === "spend" || n?.data?.paid === true || n?.paid === true
|
|
117679
|
+
).length;
|
|
117680
|
+
const costHints = nodes.map((n) => Number(n?.data?.estCostEur ?? n?.estCostEur)).filter((x) => !Number.isNaN(x));
|
|
117681
|
+
const estCostEur = costHints.length ? costHints.reduce((a, b) => a + b, 0) : null;
|
|
117682
|
+
return { steps, paidSteps, estCostEur };
|
|
117641
117683
|
}
|
|
117642
117684
|
|
|
117643
|
-
// src/loops/
|
|
117644
|
-
|
|
117645
|
-
|
|
117685
|
+
// src/loops/dashboard-template.ts
|
|
117686
|
+
init_esm_shims();
|
|
117687
|
+
var DEFAULT_DASHBOARD_FILES = [
|
|
117646
117688
|
"VISION.md",
|
|
117647
117689
|
"CONSTRAINTS.md",
|
|
117648
|
-
"
|
|
117690
|
+
"QUEUE.md",
|
|
117649
117691
|
"STATUS.md",
|
|
117650
|
-
"
|
|
117651
|
-
"
|
|
117692
|
+
"README.md",
|
|
117693
|
+
"rounds.jsonl"
|
|
117652
117694
|
];
|
|
117653
|
-
|
|
117654
|
-
|
|
117655
|
-
|
|
117656
|
-
|
|
117657
|
-
|
|
117658
|
-
|
|
117659
|
-
|
|
117660
|
-
if (filename.includes("/") || filename.includes("\\")) return false;
|
|
117661
|
-
if (filename === ".." || filename.includes("\0")) return false;
|
|
117662
|
-
return true;
|
|
117663
|
-
}
|
|
117664
|
-
function isReservedName(filename) {
|
|
117665
|
-
return RESERVED_FIRE_FILENAMES.some((r) => r.toLowerCase() === filename.toLowerCase());
|
|
117666
|
-
}
|
|
117667
|
-
var DOC_LIST_LIMIT = 500;
|
|
117668
|
-
async function discoverMemoryDocs(sdk, apiKey, slug) {
|
|
117669
|
-
const out = { own: [], shared: [], failed: [], truncated: [] };
|
|
117670
|
-
for (const scope of ["member", "shared"]) {
|
|
117671
|
-
const res = await sdk.loops.memory.listDocs(apiKey, slug, { scope, limit: DOC_LIST_LIMIT });
|
|
117672
|
-
if (res.status !== "ok") {
|
|
117673
|
-
if (scope === "member" && isNotFound(res)) continue;
|
|
117674
|
-
out.failed.push(scope);
|
|
117675
|
-
continue;
|
|
117676
|
-
}
|
|
117677
|
-
if (res.items.length >= DOC_LIST_LIMIT) out.truncated.push(scope);
|
|
117678
|
-
if (scope === "member") out.own = res.items;
|
|
117679
|
-
else out.shared = res.items;
|
|
117680
|
-
}
|
|
117681
|
-
return out;
|
|
117682
|
-
}
|
|
117683
|
-
function planStateDocs(stateDocs, discovered) {
|
|
117684
|
-
const manifest = normalizeMemoryManifest(stateDocs);
|
|
117685
|
-
const candidates = [];
|
|
117686
|
-
const claimed = /* @__PURE__ */ new Set();
|
|
117687
|
-
for (const d of manifest?.docs ?? []) {
|
|
117688
|
-
claimed.add(d.key);
|
|
117689
|
-
candidates.push({ key: d.key, scope: d.scope, declared: true });
|
|
117690
|
-
}
|
|
117691
|
-
for (const [rows, scope] of [
|
|
117692
|
-
[discovered.own, "member"],
|
|
117693
|
-
[discovered.shared, "shared"]
|
|
117694
|
-
]) {
|
|
117695
|
-
for (const m of rows) {
|
|
117696
|
-
if (claimed.has(m.key)) continue;
|
|
117697
|
-
claimed.add(m.key);
|
|
117698
|
-
candidates.push({ key: m.key, scope, declared: false });
|
|
117699
|
-
}
|
|
117695
|
+
function parseProcessSpec(manifest) {
|
|
117696
|
+
if (typeof manifest !== "string" || !manifest.trim()) return null;
|
|
117697
|
+
let parsed;
|
|
117698
|
+
try {
|
|
117699
|
+
parsed = JSON.parse(manifest);
|
|
117700
|
+
} catch {
|
|
117701
|
+
return null;
|
|
117700
117702
|
}
|
|
117701
|
-
const
|
|
117702
|
-
|
|
117703
|
-
const
|
|
117704
|
-
|
|
117705
|
-
|
|
117706
|
-
if (!
|
|
117707
|
-
|
|
117708
|
-
|
|
117709
|
-
|
|
117710
|
-
|
|
117711
|
-
|
|
117712
|
-
|
|
117713
|
-
|
|
117714
|
-
}
|
|
117715
|
-
continue;
|
|
117716
|
-
}
|
|
117717
|
-
const owner = taken.get(filename.toLowerCase());
|
|
117718
|
-
if (owner !== void 0) {
|
|
117719
|
-
skipped.push({ key: c.key, reason: `'${filename}' is already taken by key '${owner}'` });
|
|
117720
|
-
continue;
|
|
117703
|
+
const raw = parsed?.processSpec;
|
|
117704
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
117705
|
+
const spec = raw;
|
|
117706
|
+
const stages = (Array.isArray(spec.stages) ? spec.stages : []).map((s) => {
|
|
117707
|
+
if (typeof s === "string" && s.trim()) return { label: s.trim() };
|
|
117708
|
+
if (s && typeof s === "object" && !Array.isArray(s)) {
|
|
117709
|
+
const o = s;
|
|
117710
|
+
if (typeof o.label === "string" && o.label.trim()) {
|
|
117711
|
+
return {
|
|
117712
|
+
label: o.label.trim(),
|
|
117713
|
+
...typeof o.id === "string" && o.id.trim() ? { id: o.id.trim() } : {},
|
|
117714
|
+
...typeof o.detail === "string" && o.detail.trim() ? { detail: o.detail.trim() } : {}
|
|
117715
|
+
};
|
|
117716
|
+
}
|
|
117721
117717
|
}
|
|
117722
|
-
|
|
117723
|
-
|
|
117724
|
-
|
|
117725
|
-
|
|
117718
|
+
return null;
|
|
117719
|
+
}).filter((s) => s !== null);
|
|
117720
|
+
const strings = (v) => (Array.isArray(v) ? v : []).filter((x) => typeof x === "string" && x.trim() !== "");
|
|
117721
|
+
const out = {
|
|
117722
|
+
stages,
|
|
117723
|
+
inputs: strings(spec.inputs),
|
|
117724
|
+
outputs: strings(spec.outputs),
|
|
117725
|
+
...typeof spec.title === "string" && spec.title.trim() ? { title: spec.title.trim() } : {}
|
|
117726
|
+
};
|
|
117727
|
+
return stages.length || out.inputs.length || out.outputs.length ? out : null;
|
|
117726
117728
|
}
|
|
117727
|
-
function
|
|
117728
|
-
|
|
117729
|
-
const notes = [];
|
|
117730
|
-
if (doc.created) notes.push(doc.seeded ? "created, seeded from shared" : "created");
|
|
117731
|
-
if (!doc.declared) notes.push("not in manifest");
|
|
117732
|
-
return `${doc.filename} \u2190 ${from14}${notes.length ? ` (${notes.join(", ")})` : ""}`;
|
|
117729
|
+
function defaultDashboardManifest() {
|
|
117730
|
+
return JSON.stringify({ files: [...DEFAULT_DASHBOARD_FILES] });
|
|
117733
117731
|
}
|
|
117734
|
-
|
|
117735
|
-
|
|
117736
|
-
if (res.status === "ok") return { status: "ok", body: res.doc.content ?? "" };
|
|
117737
|
-
if (isNotFound(res)) return { status: "absent" };
|
|
117738
|
-
return { status: "failed" };
|
|
117732
|
+
function embedJson(value2) {
|
|
117733
|
+
return JSON.stringify(value2).replace(/</g, "\\u003c");
|
|
117739
117734
|
}
|
|
117740
|
-
|
|
117741
|
-
|
|
117742
|
-
|
|
117743
|
-
|
|
117744
|
-
|
|
117745
|
-
|
|
117746
|
-
|
|
117747
|
-
|
|
117748
|
-
|
|
117749
|
-
|
|
117750
|
-
|
|
117751
|
-
|
|
117752
|
-
|
|
117753
|
-
|
|
117754
|
-
|
|
117755
|
-
|
|
117756
|
-
|
|
117757
|
-
|
|
117758
|
-
|
|
117759
|
-
|
|
117760
|
-
|
|
117761
|
-
|
|
117762
|
-
|
|
117763
|
-
|
|
117764
|
-
|
|
117765
|
-
|
|
117766
|
-
|
|
117767
|
-
|
|
117768
|
-
|
|
117769
|
-
|
|
117770
|
-
|
|
117771
|
-
|
|
117772
|
-
|
|
117773
|
-
|
|
117774
|
-
|
|
117775
|
-
|
|
117735
|
+
function escapeHtml(s) {
|
|
117736
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
117737
|
+
}
|
|
117738
|
+
function renderLoopDashboardTemplate(args) {
|
|
117739
|
+
const title = escapeHtml(args.processSpec?.title ?? args.slug);
|
|
117740
|
+
const seed = embedJson({
|
|
117741
|
+
slug: args.slug,
|
|
117742
|
+
descriptionShort: args.descriptionShort ?? "",
|
|
117743
|
+
processSpec: args.processSpec ?? null
|
|
117744
|
+
});
|
|
117745
|
+
return `<!doctype html>
|
|
117746
|
+
<html lang="en">
|
|
117747
|
+
<head>
|
|
117748
|
+
<meta charset="utf-8">
|
|
117749
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
117750
|
+
<title>${title} \u2014 loop dashboard</title>
|
|
117751
|
+
<style>
|
|
117752
|
+
:root { --bg:#f7f7fb; --panel:#fff; --border:#e3e3ee; --muted:#6b6b80; --accent:#5b5bd6; --ok:#1a7f37; --bad:#b42318; }
|
|
117753
|
+
* { box-sizing: border-box; }
|
|
117754
|
+
body { margin:0; font:14px/1.5 -apple-system, "Segoe UI", Roboto, sans-serif; background:var(--bg); color:#1d1d2b; padding:24px; }
|
|
117755
|
+
h1 { font-size:20px; margin:0 0 4px; }
|
|
117756
|
+
h2 { font-size:14px; margin:24px 0 8px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }
|
|
117757
|
+
.sub { color:var(--muted); margin:0 0 16px; }
|
|
117758
|
+
.panel { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px; }
|
|
117759
|
+
.map { display:flex; flex-wrap:wrap; align-items:stretch; gap:8px; }
|
|
117760
|
+
.stage { background:var(--panel); border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:10px; padding:10px 14px; min-width:140px; flex:1; }
|
|
117761
|
+
.stage .label { font-weight:600; }
|
|
117762
|
+
.stage .detail { color:var(--muted); font-size:12px; margin-top:2px; }
|
|
117763
|
+
.arrow { align-self:center; color:var(--muted); }
|
|
117764
|
+
.io { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
117765
|
+
ul { margin:6px 0 0; padding-left:18px; }
|
|
117766
|
+
table { width:100%; border-collapse:collapse; background:var(--panel); border:1px solid var(--border); border-radius:12px; overflow:hidden; }
|
|
117767
|
+
th, td { text-align:left; padding:8px 12px; border-top:1px solid var(--border); font-size:13px; vertical-align:top; }
|
|
117768
|
+
thead th { border-top:none; background:var(--bg); color:var(--muted); font-weight:600; }
|
|
117769
|
+
.ok { color:var(--ok); } .bad { color:var(--bad); }
|
|
117770
|
+
details { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:10px 14px; margin-bottom:8px; }
|
|
117771
|
+
summary { cursor:pointer; font-weight:600; }
|
|
117772
|
+
pre { overflow-x:auto; font-size:12px; background:var(--bg); border-radius:8px; padding:10px; }
|
|
117773
|
+
.empty { color:var(--muted); font-style:italic; }
|
|
117774
|
+
#live { font-size:12px; color:var(--muted); float:right; }
|
|
117775
|
+
</style>
|
|
117776
|
+
</head>
|
|
117777
|
+
<body>
|
|
117778
|
+
<span id="live">loading\u2026</span>
|
|
117779
|
+
<h1 id="title"></h1>
|
|
117780
|
+
<p class="sub" id="desc"></p>
|
|
117781
|
+
|
|
117782
|
+
<h2>Process</h2>
|
|
117783
|
+
<div id="map" class="map panel"></div>
|
|
117784
|
+
|
|
117785
|
+
<h2>Inputs & outputs</h2>
|
|
117786
|
+
<div class="io">
|
|
117787
|
+
<div class="panel"><strong>Inputs</strong><ul id="inputs"></ul></div>
|
|
117788
|
+
<div class="panel"><strong>Outputs</strong><ul id="outputs"></ul></div>
|
|
117789
|
+
</div>
|
|
117790
|
+
|
|
117791
|
+
<h2>Rounds</h2>
|
|
117792
|
+
<div id="rounds"></div>
|
|
117793
|
+
|
|
117794
|
+
<h2>Files</h2>
|
|
117795
|
+
<div id="files"></div>
|
|
117796
|
+
|
|
117797
|
+
<script type="application/json" id="loop-seed">${seed}</script>
|
|
117798
|
+
<script>
|
|
117799
|
+
(function () {
|
|
117800
|
+
"use strict";
|
|
117801
|
+
var seed = JSON.parse(document.getElementById("loop-seed").textContent);
|
|
117802
|
+
document.getElementById("title").textContent = (seed.processSpec && seed.processSpec.title) || seed.slug;
|
|
117803
|
+
document.getElementById("desc").textContent = seed.descriptionShort || "";
|
|
117804
|
+
document.title = seed.slug + " \u2014 loop dashboard";
|
|
117805
|
+
|
|
117806
|
+
function el(tag, cls, text) {
|
|
117807
|
+
var e = document.createElement(tag);
|
|
117808
|
+
if (cls) e.className = cls;
|
|
117809
|
+
if (text !== undefined) e.textContent = text;
|
|
117810
|
+
return e;
|
|
117776
117811
|
}
|
|
117777
|
-
|
|
117778
|
-
|
|
117779
|
-
|
|
117780
|
-
|
|
117781
|
-
|
|
117782
|
-
|
|
117783
|
-
|
|
117784
|
-
|
|
117785
|
-
|
|
117812
|
+
|
|
117813
|
+
// \u2500\u2500 Process map + IO from the embedded spec (static \u2014 rendered once). \u2500\u2500
|
|
117814
|
+
var map = document.getElementById("map");
|
|
117815
|
+
var spec = seed.processSpec;
|
|
117816
|
+
if (spec && spec.stages && spec.stages.length) {
|
|
117817
|
+
spec.stages.forEach(function (s, i) {
|
|
117818
|
+
if (i > 0) map.appendChild(el("div", "arrow", "\\u2192"));
|
|
117819
|
+
var box = el("div", "stage");
|
|
117820
|
+
box.appendChild(el("div", "label", s.label));
|
|
117821
|
+
if (s.detail) box.appendChild(el("div", "detail", s.detail));
|
|
117822
|
+
map.appendChild(box);
|
|
117823
|
+
});
|
|
117824
|
+
} else {
|
|
117825
|
+
map.appendChild(el("span", "empty", "No process spec on this loop \\u2014 live files and rounds below."));
|
|
117786
117826
|
}
|
|
117787
|
-
|
|
117788
|
-
|
|
117827
|
+
function fillList(id, items) {
|
|
117828
|
+
var ul = document.getElementById(id);
|
|
117829
|
+
ul.textContent = "";
|
|
117830
|
+
if (!items || !items.length) { ul.appendChild(el("li", "empty", "\\u2014")); return; }
|
|
117831
|
+
items.forEach(function (x) { ul.appendChild(el("li", null, x)); });
|
|
117789
117832
|
}
|
|
117790
|
-
|
|
117791
|
-
|
|
117792
|
-
|
|
117793
|
-
|
|
117794
|
-
|
|
117795
|
-
|
|
117796
|
-
|
|
117797
|
-
|
|
117798
|
-
|
|
117799
|
-
|
|
117800
|
-
|
|
117801
|
-
|
|
117802
|
-
|
|
117803
|
-
|
|
117804
|
-
|
|
117805
|
-
|
|
117806
|
-
|
|
117807
|
-
|
|
117808
|
-
|
|
117809
|
-
|
|
117810
|
-
|
|
117811
|
-
const merged = mergeConstraints(doc.body, materialized, fresh);
|
|
117812
|
-
if (merged.refusal) {
|
|
117813
|
-
outcomes.push({ key: doc.key, outcome: "refused", detail: merged.refusal });
|
|
117814
|
-
continue;
|
|
117815
|
-
}
|
|
117816
|
-
if (merged.next === void 0) {
|
|
117817
|
-
outcomes.push({ key: doc.key, outcome: "unchanged" });
|
|
117818
|
-
continue;
|
|
117819
|
-
}
|
|
117820
|
-
const docRefusal = docMergeRefusal(doc.body, fresh, merged.next, merged.cleanAppend);
|
|
117821
|
-
if (docRefusal) {
|
|
117822
|
-
outcomes.push({
|
|
117823
|
-
key: doc.key,
|
|
117824
|
-
outcome: "refused",
|
|
117825
|
-
detail: `${docRefusal}; ${path2} kept for recovery`
|
|
117826
|
-
});
|
|
117827
|
-
continue;
|
|
117833
|
+
fillList("inputs", spec && spec.inputs);
|
|
117834
|
+
fillList("outputs", spec && spec.outputs);
|
|
117835
|
+
|
|
117836
|
+
// \u2500\u2500 Live data: rounds + files, refreshed from GET /data. \u2500\u2500
|
|
117837
|
+
function parseJsonl(text) {
|
|
117838
|
+
var rows = [];
|
|
117839
|
+
(text || "").split("\\n").forEach(function (line) {
|
|
117840
|
+
line = line.trim();
|
|
117841
|
+
if (!line) return;
|
|
117842
|
+
try { rows.push(JSON.parse(line)); } catch (e) { /* skip malformed line */ }
|
|
117843
|
+
});
|
|
117844
|
+
return rows;
|
|
117845
|
+
}
|
|
117846
|
+
|
|
117847
|
+
function renderRounds(fires, rounds) {
|
|
117848
|
+
var host = document.getElementById("rounds");
|
|
117849
|
+
host.textContent = "";
|
|
117850
|
+
if (!fires.length && !rounds.length) {
|
|
117851
|
+
var p = el("div", "panel"); p.appendChild(el("span", "empty", "No rounds yet \\u2014 this fills in as the loop runs."));
|
|
117852
|
+
host.appendChild(p);
|
|
117853
|
+
return;
|
|
117828
117854
|
}
|
|
117829
|
-
|
|
117830
|
-
|
|
117831
|
-
|
|
117855
|
+
var table = document.createElement("table");
|
|
117856
|
+
var thead = document.createElement("thead");
|
|
117857
|
+
var hr = document.createElement("tr");
|
|
117858
|
+
["when", "duration", "turns", "tokens", "exit", "round detail (rounds.jsonl)"].forEach(function (h) {
|
|
117859
|
+
hr.appendChild(el("th", null, h));
|
|
117832
117860
|
});
|
|
117833
|
-
|
|
117834
|
-
|
|
117835
|
-
|
|
117836
|
-
|
|
117837
|
-
|
|
117838
|
-
|
|
117839
|
-
|
|
117861
|
+
thead.appendChild(hr); table.appendChild(thead);
|
|
117862
|
+
var tbody = document.createElement("tbody");
|
|
117863
|
+
var n = Math.max(fires.length, rounds.length);
|
|
117864
|
+
for (var i = n - 1; i >= 0; i--) { // newest first
|
|
117865
|
+
var f = fires[i] || {};
|
|
117866
|
+
var r = rounds[i];
|
|
117867
|
+
var tr = document.createElement("tr");
|
|
117868
|
+
tr.appendChild(el("td", null, f.ts || (r && r.ts) || "\\u2014"));
|
|
117869
|
+
tr.appendChild(el("td", null, f.duration_s != null ? f.duration_s + "s" : "\\u2014"));
|
|
117870
|
+
tr.appendChild(el("td", null, f.turns != null ? String(f.turns) : "\\u2014"));
|
|
117871
|
+
tr.appendChild(el("td", null, f.tokens_total != null ? Number(f.tokens_total).toLocaleString() : "\\u2014"));
|
|
117872
|
+
tr.appendChild(el("td", f.exit === 0 ? "ok" : f.exit != null ? "bad" : null, f.exit != null ? String(f.exit) : "\\u2014"));
|
|
117873
|
+
var detail = el("td");
|
|
117874
|
+
if (f.launch_failed) { detail.textContent = "launch failed \\u2014 " + (f.reason || "unknown reason"); }
|
|
117875
|
+
else if (r) { var pre = document.createElement("pre"); pre.textContent = JSON.stringify(r, null, 1); detail.appendChild(pre); }
|
|
117876
|
+
else detail.textContent = "\\u2014";
|
|
117877
|
+
tr.appendChild(detail);
|
|
117878
|
+
tbody.appendChild(tr);
|
|
117840
117879
|
}
|
|
117841
|
-
|
|
117842
|
-
|
|
117843
|
-
|
|
117844
|
-
|
|
117880
|
+
table.appendChild(tbody);
|
|
117881
|
+
host.appendChild(table);
|
|
117882
|
+
}
|
|
117883
|
+
|
|
117884
|
+
function renderFiles(files) {
|
|
117885
|
+
var host = document.getElementById("files");
|
|
117886
|
+
host.textContent = "";
|
|
117887
|
+
var names = Object.keys(files || {}).filter(function (n) { return n !== "rounds.jsonl"; });
|
|
117888
|
+
if (!names.length) { var p = el("div", "panel"); p.appendChild(el("span", "empty", "No files surfaced by the manifest.")); host.appendChild(p); return; }
|
|
117889
|
+
names.forEach(function (name) {
|
|
117890
|
+
var d = document.createElement("details");
|
|
117891
|
+
d.appendChild(el("summary", null, name));
|
|
117892
|
+
var pre = document.createElement("pre");
|
|
117893
|
+
pre.textContent = files[name];
|
|
117894
|
+
d.appendChild(pre);
|
|
117895
|
+
host.appendChild(d);
|
|
117896
|
+
});
|
|
117897
|
+
}
|
|
117898
|
+
|
|
117899
|
+
function refresh() {
|
|
117900
|
+
fetch("/data").then(function (res) { return res.json(); }).then(function (data) {
|
|
117901
|
+
var fires = parseJsonl(data.state && data.state["fires.jsonl"]);
|
|
117902
|
+
var rounds = parseJsonl(data.files && data.files["rounds.jsonl"]);
|
|
117903
|
+
renderRounds(fires, rounds);
|
|
117904
|
+
renderFiles(data.files);
|
|
117905
|
+
document.getElementById("live").textContent = "updated " + new Date().toLocaleTimeString();
|
|
117906
|
+
}).catch(function () {
|
|
117907
|
+
document.getElementById("live").textContent = "server stopped";
|
|
117908
|
+
});
|
|
117909
|
+
}
|
|
117910
|
+
refresh();
|
|
117911
|
+
setInterval(refresh, 5000);
|
|
117912
|
+
})();
|
|
117913
|
+
</script>
|
|
117914
|
+
</body>
|
|
117915
|
+
</html>
|
|
117916
|
+
`;
|
|
117917
|
+
}
|
|
117918
|
+
function injectDefaultDashboard(loop2) {
|
|
117919
|
+
const hasHtml = typeof loop2.dashboardHtml === "string" && loop2.dashboardHtml.trim() !== "";
|
|
117920
|
+
if (hasHtml) return;
|
|
117921
|
+
if (loop2.dashboardManifest && typeof loop2.dashboardManifest === "object") {
|
|
117922
|
+
try {
|
|
117923
|
+
loop2.dashboardManifest = JSON.stringify(loop2.dashboardManifest);
|
|
117924
|
+
} catch {
|
|
117845
117925
|
}
|
|
117846
|
-
const afterText = after.doc.content ?? "";
|
|
117847
|
-
const missing = merged.addedBlocks.length > 0 ? verifyLanded(afterText, merged.addedBlocks) : afterText === merged.next ? [] : ["<rewrite did not survive>"];
|
|
117848
|
-
outcomes.push(
|
|
117849
|
-
missing.length === 0 ? { key: doc.key, outcome: "shipped" } : {
|
|
117850
|
-
key: doc.key,
|
|
117851
|
-
outcome: "failed",
|
|
117852
|
-
detail: `${missing.length} section(s) did not survive a concurrent write; ${path2} kept`
|
|
117853
|
-
}
|
|
117854
|
-
);
|
|
117855
117926
|
}
|
|
117856
|
-
|
|
117927
|
+
const manifest = typeof loop2.dashboardManifest === "string" ? loop2.dashboardManifest : void 0;
|
|
117928
|
+
loop2.dashboardHtml = renderLoopDashboardTemplate({
|
|
117929
|
+
slug: typeof loop2.slug === "string" ? loop2.slug : "loop",
|
|
117930
|
+
descriptionShort: typeof loop2.descriptionShort === "string" ? loop2.descriptionShort : void 0,
|
|
117931
|
+
processSpec: parseProcessSpec(manifest)
|
|
117932
|
+
});
|
|
117933
|
+
if (!manifest || !manifest.trim()) loop2.dashboardManifest = defaultDashboardManifest();
|
|
117857
117934
|
}
|
|
117858
117935
|
|
|
117859
117936
|
// src/loops/memory-verbs.ts
|
|
@@ -118178,6 +118255,27 @@ async function taskMemoryArchiveVerb(rawSlug, opts) {
|
|
|
118178
118255
|
);
|
|
118179
118256
|
}
|
|
118180
118257
|
|
|
118258
|
+
// src/mcp-server/memory-scope.ts
|
|
118259
|
+
init_esm_shims();
|
|
118260
|
+
var MEMORY_SCOPE_EXPLANATION = "Global memory: everyone who uses this task (when it is shared) reads and writes the same copy \u2014 edits can conflict and build up over time. Per-member memory: each person who uses it gets their own copy. For a private task the difference doesn't matter. If unsure, choose per-member to avoid conflicts. Examples: a product PBI someone picks up and leaves half-done \u2192 per member; competitor signals the whole workspace should see \u2192 global.";
|
|
118261
|
+
function memorySummaryLine(manifest) {
|
|
118262
|
+
const normalized = normalizeMemoryManifest(manifest);
|
|
118263
|
+
const entries = normalized ? [...normalized.docs, ...normalized.records] : [];
|
|
118264
|
+
const global2 = entries.filter((e) => e.scope === "shared").length;
|
|
118265
|
+
const perMember = entries.filter((e) => e.scope === "member").length;
|
|
118266
|
+
if (global2 > 0 && perMember > 0) return `Memory: mixed \u2014 ${global2} global \xB7 ${perMember} per member`;
|
|
118267
|
+
if (global2 > 0) return "Memory: global";
|
|
118268
|
+
if (perMember > 0) return "Memory: per member";
|
|
118269
|
+
return "Memory: none";
|
|
118270
|
+
}
|
|
118271
|
+
function defaultMemoryManifest() {
|
|
118272
|
+
return { docs: [], records: [{ kind: "run", scope: "member" }] };
|
|
118273
|
+
}
|
|
118274
|
+
var MEMORY_DEFAULTED_SUFFIX = " (defaulted \u2014 no manifest declared)";
|
|
118275
|
+
function declaresNoMemory(manifest) {
|
|
118276
|
+
return memorySummaryLine(manifest ?? null) === "Memory: none";
|
|
118277
|
+
}
|
|
118278
|
+
|
|
118181
118279
|
// src/mcp-server/prompt-descriptors.ts
|
|
118182
118280
|
init_esm_shims();
|
|
118183
118281
|
|
|
@@ -118456,6 +118554,15 @@ function buildReceipt(input) {
|
|
|
118456
118554
|
if (typeof sent.draft === "boolean") {
|
|
118457
118555
|
out.push({ field: "draft", action: classify(sent.draft, prev ? prev.draft : void 0) });
|
|
118458
118556
|
}
|
|
118557
|
+
if (sent.stateDocs !== void 0) {
|
|
118558
|
+
const sentJson = JSON.stringify(sent.stateDocs ?? null);
|
|
118559
|
+
const prevHas = prev !== void 0 && prev.stateDocs !== void 0;
|
|
118560
|
+
const prevJson = prevHas ? JSON.stringify(prev.stateDocs ?? null) : void 0;
|
|
118561
|
+
const action = mode2 === "created" ? "set" : !previous || !previous.available ? "sent" : prevJson === void 0 ? "changed" : sentJson === prevJson ? "unchanged" : "changed";
|
|
118562
|
+
const rec = { field: "stateDocs", action, bytes: Buffer.byteLength(sentJson, "utf8") };
|
|
118563
|
+
if (prevJson !== void 0) rec.previousBytes = Buffer.byteLength(prevJson, "utf8");
|
|
118564
|
+
out.push(rec);
|
|
118565
|
+
}
|
|
118459
118566
|
return out;
|
|
118460
118567
|
}
|
|
118461
118568
|
function deriveChanges(input, receipt) {
|
|
@@ -118542,7 +118649,15 @@ function enforceResponseCap(payload) {
|
|
|
118542
118649
|
}
|
|
118543
118650
|
if (size6(out) > MAX_RESPONSE_CHARS && Array.isArray(out.receipt?.fields)) {
|
|
118544
118651
|
const r = out.receipt;
|
|
118545
|
-
out.receipt = {
|
|
118652
|
+
out.receipt = {
|
|
118653
|
+
fieldCount: r.fields.length,
|
|
118654
|
+
note: "receipt omitted \u2014 response cap",
|
|
118655
|
+
// WHERE IT LANDED SURVIVES THE CAP. It is two short strings, and it is the
|
|
118656
|
+
// one line on this receipt whose absence was an incident: shrinking the
|
|
118657
|
+
// response must not be how a caller stops being told which workspace it
|
|
118658
|
+
// just wrote to.
|
|
118659
|
+
...r.workspace !== void 0 ? { workspace: r.workspace } : {}
|
|
118660
|
+
};
|
|
118546
118661
|
}
|
|
118547
118662
|
if (size6(out) > MAX_RESPONSE_CHARS) {
|
|
118548
118663
|
out.summary = TRUNCATION_MARKER;
|
|
@@ -118553,7 +118668,7 @@ function enforceResponseCap(payload) {
|
|
|
118553
118668
|
|
|
118554
118669
|
// src/mcp-server/delegate-tools.ts
|
|
118555
118670
|
init_esm_shims();
|
|
118556
|
-
import { existsSync as
|
|
118671
|
+
import { existsSync as existsSync10, statSync as statSync2 } from "fs";
|
|
118557
118672
|
import { z as z3 } from "zod";
|
|
118558
118673
|
|
|
118559
118674
|
// src/delegate/jobs.ts
|
|
@@ -121047,7 +121162,7 @@ function registerDelegateTools(deps) {
|
|
|
121047
121162
|
const validated = validateStartInput(params, {
|
|
121048
121163
|
isDirectory: (path2) => {
|
|
121049
121164
|
try {
|
|
121050
|
-
return
|
|
121165
|
+
return existsSync10(path2) && statSync2(path2).isDirectory();
|
|
121051
121166
|
} catch {
|
|
121052
121167
|
return false;
|
|
121053
121168
|
}
|
|
@@ -121297,6 +121412,82 @@ function applyResolvedMethod(url2, method, inputs) {
|
|
|
121297
121412
|
|
|
121298
121413
|
// src/mcp-server/index.ts
|
|
121299
121414
|
init_plaintext_vault();
|
|
121415
|
+
init_paths();
|
|
121416
|
+
init_resolve();
|
|
121417
|
+
|
|
121418
|
+
// src/mcp-server/identity-reload.ts
|
|
121419
|
+
init_esm_shims();
|
|
121420
|
+
import { readFileSync as readFileSync12, statSync as statSync3 } from "fs";
|
|
121421
|
+
var NO_VAULT = "absent";
|
|
121422
|
+
var IDENTITY_CHECK_THROTTLE_MS = 1e3;
|
|
121423
|
+
function fingerprintVault(stat) {
|
|
121424
|
+
return stat ? `${stat.mtimeMs}:${stat.size}` : NO_VAULT;
|
|
121425
|
+
}
|
|
121426
|
+
function statVaultFile(path2) {
|
|
121427
|
+
try {
|
|
121428
|
+
const s = statSync3(path2);
|
|
121429
|
+
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
121430
|
+
} catch {
|
|
121431
|
+
return null;
|
|
121432
|
+
}
|
|
121433
|
+
}
|
|
121434
|
+
var IdentityWatch = class {
|
|
121435
|
+
vaultPath;
|
|
121436
|
+
now;
|
|
121437
|
+
throttleMs;
|
|
121438
|
+
stat;
|
|
121439
|
+
/** False for an env-supplied key: `poll` is then a guaranteed no-op. */
|
|
121440
|
+
armed;
|
|
121441
|
+
fingerprint;
|
|
121442
|
+
lastCheckedAt = null;
|
|
121443
|
+
constructor(options) {
|
|
121444
|
+
this.vaultPath = options.vaultPath;
|
|
121445
|
+
this.now = options.now ?? Date.now;
|
|
121446
|
+
this.throttleMs = options.throttleMs ?? IDENTITY_CHECK_THROTTLE_MS;
|
|
121447
|
+
this.stat = options.stat ?? statVaultFile;
|
|
121448
|
+
this.armed = options.source !== "env";
|
|
121449
|
+
this.fingerprint = fingerprintVault(this.stat(this.vaultPath));
|
|
121450
|
+
}
|
|
121451
|
+
/** The fingerprint this watch currently considers current. */
|
|
121452
|
+
currentFingerprint() {
|
|
121453
|
+
return this.fingerprint;
|
|
121454
|
+
}
|
|
121455
|
+
/**
|
|
121456
|
+
* One throttled `stat`. Returns the change when the vault file differs from
|
|
121457
|
+
* the fingerprint on record — and ADOPTS it in the same step, so a reload the
|
|
121458
|
+
* caller then fails to apply is not retried on every subsequent tool call.
|
|
121459
|
+
* Returns `null` when nothing changed, when the throttle window is still open,
|
|
121460
|
+
* or when the watch is not armed.
|
|
121461
|
+
*/
|
|
121462
|
+
poll() {
|
|
121463
|
+
if (!this.armed) return null;
|
|
121464
|
+
const at = this.now();
|
|
121465
|
+
if (this.lastCheckedAt !== null && at - this.lastCheckedAt < this.throttleMs) return null;
|
|
121466
|
+
this.lastCheckedAt = at;
|
|
121467
|
+
const next = fingerprintVault(this.stat(this.vaultPath));
|
|
121468
|
+
if (next === this.fingerprint) return null;
|
|
121469
|
+
const from14 = this.fingerprint;
|
|
121470
|
+
this.fingerprint = next;
|
|
121471
|
+
return { from: from14, to: next };
|
|
121472
|
+
}
|
|
121473
|
+
/**
|
|
121474
|
+
* The vault bytes this watch is pointed at, or `null` when there is none.
|
|
121475
|
+
*
|
|
121476
|
+
* Reading THROUGH the watch rather than through `loadVault()` is deliberate:
|
|
121477
|
+
* the freshness check and the reload must address the same file, and having
|
|
121478
|
+
* one owner of the path is what makes that true by construction instead of by
|
|
121479
|
+
* two constants agreeing.
|
|
121480
|
+
*/
|
|
121481
|
+
readVault() {
|
|
121482
|
+
try {
|
|
121483
|
+
return readFileSync12(this.vaultPath, "utf-8");
|
|
121484
|
+
} catch {
|
|
121485
|
+
return null;
|
|
121486
|
+
}
|
|
121487
|
+
}
|
|
121488
|
+
};
|
|
121489
|
+
|
|
121490
|
+
// src/mcp-server/index.ts
|
|
121300
121491
|
function buildDeclaredConnectorsSentence(catalog, unreadable) {
|
|
121301
121492
|
const read = catalog === void 0 ? { connectors: cachedCatalogOrEmpty(), unreadable: mirrorGap() } : { connectors: catalog, unreadable: unreadable ?? [] };
|
|
121302
121493
|
const gap = describeCatalogGap(read.unreadable);
|
|
@@ -121428,6 +121619,8 @@ var loopIndexCache = null;
|
|
|
121428
121619
|
var taskIndexCache = null;
|
|
121429
121620
|
var cardCounts = null;
|
|
121430
121621
|
var lastNudgeAtCall = null;
|
|
121622
|
+
var WORKSPACE_IDENTITY_TTL_MS = 6e4;
|
|
121623
|
+
var workspaceIdentityCache = null;
|
|
121431
121624
|
var sessionId = randomUUID4();
|
|
121432
121625
|
var mcpEventLogger = null;
|
|
121433
121626
|
var PROCESSED_APPROVAL_IDS_CAP = 50;
|
|
@@ -121524,6 +121717,83 @@ function maybeAppendCardNudge(res, toolName, toolCallIndex) {
|
|
|
121524
121717
|
if (next !== res) lastNudgeAtCall = toolCallIndex;
|
|
121525
121718
|
return next;
|
|
121526
121719
|
}
|
|
121720
|
+
async function resolveWorkspaceIdentity() {
|
|
121721
|
+
const apiKey = currentCredentials.apiKey;
|
|
121722
|
+
if (!apiKey?.trim()) return { companyName: null, employeeName: null };
|
|
121723
|
+
const now = Date.now();
|
|
121724
|
+
if (workspaceIdentityCache && now - workspaceIdentityCache.at < WORKSPACE_IDENTITY_TTL_MS) {
|
|
121725
|
+
return workspaceIdentityCache.value;
|
|
121726
|
+
}
|
|
121727
|
+
let value2 = { companyName: null, employeeName: null };
|
|
121728
|
+
try {
|
|
121729
|
+
const sdk = await getSDK();
|
|
121730
|
+
const me = await sdk.virtualWalletsManagers.getManagerMe(apiKey);
|
|
121731
|
+
value2 = {
|
|
121732
|
+
companyName: typeof me?.companyName === "string" ? me.companyName : null,
|
|
121733
|
+
employeeName: typeof me?.employeeName === "string" ? me.employeeName : null
|
|
121734
|
+
};
|
|
121735
|
+
} catch (err) {
|
|
121736
|
+
console.error(
|
|
121737
|
+
`\u26A0\uFE0F workspace identity read failed \u2014 reporting nulls: ${err instanceof Error ? err.message : String(err)}`
|
|
121738
|
+
);
|
|
121739
|
+
}
|
|
121740
|
+
workspaceIdentityCache = { at: now, value: value2 };
|
|
121741
|
+
return value2;
|
|
121742
|
+
}
|
|
121743
|
+
var identityWatch = null;
|
|
121744
|
+
function dropIdentityScopedCaches() {
|
|
121745
|
+
walletsCache = null;
|
|
121746
|
+
policiesCache = null;
|
|
121747
|
+
transactionsCache = null;
|
|
121748
|
+
allowlistCache = null;
|
|
121749
|
+
capabilityIndexCache = null;
|
|
121750
|
+
compoundIndexCache = null;
|
|
121751
|
+
loopIndexCache = null;
|
|
121752
|
+
taskIndexCache = null;
|
|
121753
|
+
cardCounts = null;
|
|
121754
|
+
servicesDiscovered = null;
|
|
121755
|
+
workspaceIdentityCache = null;
|
|
121756
|
+
endpointSessionCache.clear();
|
|
121757
|
+
}
|
|
121758
|
+
async function applyIdentityReload() {
|
|
121759
|
+
const watch = identityWatch;
|
|
121760
|
+
if (!watch) return;
|
|
121761
|
+
const { readVaultAddress: readVaultAddress2 } = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
121762
|
+
const vault = watch.readVault();
|
|
121763
|
+
const nextEoa = vault ? readVaultAddress2(vault) : "";
|
|
121764
|
+
const resolved = await resolveApiKey(cliConfig?.credentialStore ?? "keychain");
|
|
121765
|
+
const previousKey = currentCredentials.apiKey ?? "";
|
|
121766
|
+
const previousEoa = currentCredentials.eoaAddress ?? "";
|
|
121767
|
+
currentCredentials.walletKeystoreJson = vault ?? "";
|
|
121768
|
+
currentCredentials.eoaAddress = nextEoa;
|
|
121769
|
+
currentCredentials.signerAddress = nextEoa;
|
|
121770
|
+
if (resolved.apiKey) currentCredentials.apiKey = resolved.apiKey;
|
|
121771
|
+
refreshPlaintextVaultMode();
|
|
121772
|
+
clearWalletScopedCredentials(currentCredentials);
|
|
121773
|
+
delete currentCredentials.passphrase;
|
|
121774
|
+
delete currentCredentials.wrongPassphraseAttempts;
|
|
121775
|
+
delete currentCredentials.requiresNewPolicy;
|
|
121776
|
+
dropIdentityScopedCaches();
|
|
121777
|
+
console.error(
|
|
121778
|
+
`identity reloaded: ${maskApiKey(previousKey)} -> ${maskApiKey(currentCredentials.apiKey ?? "")}, eoa ${previousEoa || "(none)"} -> ${nextEoa || "(none)"}`
|
|
121779
|
+
);
|
|
121780
|
+
}
|
|
121781
|
+
async function ensureIdentityFresh() {
|
|
121782
|
+
let change = null;
|
|
121783
|
+
try {
|
|
121784
|
+
change = identityWatch?.poll() ?? null;
|
|
121785
|
+
} catch {
|
|
121786
|
+
return;
|
|
121787
|
+
}
|
|
121788
|
+
if (!change) return;
|
|
121789
|
+
try {
|
|
121790
|
+
await applyIdentityReload();
|
|
121791
|
+
} catch (err) {
|
|
121792
|
+
console.error(
|
|
121793
|
+
`\u26A0\uFE0F identity reload failed \u2014 continuing with the identity loaded at boot: ${err instanceof Error ? err.message : String(err)}`
|
|
121794
|
+
);
|
|
121795
|
+
}
|
|
121796
|
+
}
|
|
121527
121797
|
var __registerTool = server.tool.bind(server);
|
|
121528
121798
|
server.tool = (def, handler) => __registerTool(
|
|
121529
121799
|
def,
|
|
@@ -121534,6 +121804,7 @@ server.tool = (def, handler) => __registerTool(
|
|
|
121534
121804
|
// session, unconditionally. Only `severity: "block"` (an unsupported CLI)
|
|
121535
121805
|
// still refuses, and that refusal happens inside the gate.
|
|
121536
121806
|
runToolThroughInterstitialGate(def?.name, async () => {
|
|
121807
|
+
await ensureIdentityFresh();
|
|
121537
121808
|
const res = await handler(params);
|
|
121538
121809
|
maybeAutoShareOnToolCall();
|
|
121539
121810
|
const withNudge = maybeAppendCardNudge(res, def?.name, toolCallCount);
|
|
@@ -122248,6 +122519,7 @@ async function initializeCredentials(walletKeystoreJson, eoaAddress, config) {
|
|
|
122248
122519
|
currentCredentials.signerAddress = eoaAddress;
|
|
122249
122520
|
currentCredentials.eoaAddress = eoaAddress;
|
|
122250
122521
|
currentCredentials.apiKey = config.apiKey;
|
|
122522
|
+
identityWatch = new IdentityWatch({ vaultPath: VAULT_PATH, source: config.apiKeySource ?? null });
|
|
122251
122523
|
mcpEventLogger = new McpEventLogger(
|
|
122252
122524
|
() => getSDK(),
|
|
122253
122525
|
() => currentCredentials.apiKey,
|
|
@@ -123086,7 +123358,7 @@ server.tool(
|
|
|
123086
123358
|
server.tool(
|
|
123087
123359
|
{
|
|
123088
123360
|
name: "getWalletStatus",
|
|
123089
|
-
description: "Get wallet status: auth, balance, policy, allowlist, and the last 10 transactions (amount, merchant, timestamp, status). A transaction `amount` is a EUR display string like `\u20AC0.0126`, or the em dash `\u2014` when the row carries no usable amount (none recorded, or a value that is not a base-units integer) \u2014 `\u2014` means UNKNOWN, never zero. The unformatted base-units value is on `amountRaw`, which is null for exactly those rows.",
|
|
123361
|
+
description: "Get wallet status: auth, balance, policy, allowlist, WHICH WORKSPACE this server is acting in, and the last 10 transactions (amount, merchant, timestamp, status). `workspace` is `{companyName, employeeName}` \u2014 the Ametyst workspace every task, memory document and payment from this server lands in; both fields are null when the profile could not be read, which means UNKNOWN, never 'no workspace'. A transaction `amount` is a EUR display string like `\u20AC0.0126`, or the em dash `\u2014` when the row carries no usable amount (none recorded, or a value that is not a base-units integer) \u2014 `\u2014` means UNKNOWN, never zero. The unformatted base-units value is on `amountRaw`, which is null for exactly those rows.",
|
|
123090
123362
|
inputs: []
|
|
123091
123363
|
},
|
|
123092
123364
|
async () => {
|
|
@@ -123141,7 +123413,12 @@ server.tool(
|
|
|
123141
123413
|
status: currentCredentials.authorizationStatus || "none",
|
|
123142
123414
|
eoaAddress: currentCredentials.eoaAddress || null,
|
|
123143
123415
|
walletAddress: currentCredentials.walletAddress || null,
|
|
123144
|
-
kernelClientActive: !!currentCredentials.virtualWalletKernelAccountClient
|
|
123416
|
+
kernelClientActive: !!currentCredentials.virtualWalletKernelAccountClient,
|
|
123417
|
+
// WHICH WORKSPACE THIS IS. The 2026-08-31 incident produced writes into
|
|
123418
|
+
// the wrong workspace with nothing in any payload naming one — an agent
|
|
123419
|
+
// reading wallet status could not have told, and neither could a human
|
|
123420
|
+
// reading the transcript afterwards. Nulls when `me` could not be read.
|
|
123421
|
+
workspace: await resolveWorkspaceIdentity()
|
|
123145
123422
|
};
|
|
123146
123423
|
if (policiesCache && currentCredentials.policyId) {
|
|
123147
123424
|
const policy = policiesCache.find((p) => p.id === parseInt(currentCredentials.policyId, 10));
|
|
@@ -123597,6 +123874,7 @@ async function resolveTasksCore(params, flavor) {
|
|
|
123597
123874
|
if (slug && flavor.fetchManifest) {
|
|
123598
123875
|
const manifest = await flavor.fetchManifest(sdk, currentCredentials.apiKey, slug);
|
|
123599
123876
|
if (manifest) payload.stateDocs = manifest;
|
|
123877
|
+
if (manifest !== void 0) payload.memory = memorySummaryLine(manifest);
|
|
123600
123878
|
}
|
|
123601
123879
|
}
|
|
123602
123880
|
}
|
|
@@ -123642,6 +123920,7 @@ function taskMemoryBlock(slug, docKey) {
|
|
|
123642
123920
|
};
|
|
123643
123921
|
}
|
|
123644
123922
|
var liveDashboards = /* @__PURE__ */ new Map();
|
|
123923
|
+
var LIVE_DASHBOARD_WATCH_MS = 15e3;
|
|
123645
123924
|
function closeAllLiveDashboards() {
|
|
123646
123925
|
for (const h of liveDashboards.values()) {
|
|
123647
123926
|
try {
|
|
@@ -123669,6 +123948,22 @@ async function startLiveDashboard(entity, dir) {
|
|
|
123669
123948
|
});
|
|
123670
123949
|
if (!handle) return null;
|
|
123671
123950
|
liveDashboards.set(slug, handle);
|
|
123951
|
+
const watcher = setInterval(() => {
|
|
123952
|
+
if (liveDashboards.get(slug) !== handle) {
|
|
123953
|
+
clearInterval(watcher);
|
|
123954
|
+
return;
|
|
123955
|
+
}
|
|
123956
|
+
if (!existsSync12(dir)) {
|
|
123957
|
+
clearInterval(watcher);
|
|
123958
|
+
liveDashboards.delete(slug);
|
|
123959
|
+
try {
|
|
123960
|
+
handle.close();
|
|
123961
|
+
} catch {
|
|
123962
|
+
}
|
|
123963
|
+
console.error(`(live dashboard for ${slug} closed \u2014 run folder ${dir} is gone)`);
|
|
123964
|
+
}
|
|
123965
|
+
}, LIVE_DASHBOARD_WATCH_MS);
|
|
123966
|
+
watcher.unref?.();
|
|
123672
123967
|
const url2 = `http://localhost:${handle.port}`;
|
|
123673
123968
|
openDashboardInBrowser(url2, { isTTY: true });
|
|
123674
123969
|
return url2;
|
|
@@ -123733,6 +124028,9 @@ async function runTaskCore(params, flavor) {
|
|
|
123733
124028
|
}
|
|
123734
124029
|
const memoryDocKey = defaultDocKey(normalizeMemoryManifest(entity?.stateDocs ?? null));
|
|
123735
124030
|
const materialized = flavor.materialize(entity, randomUUID4());
|
|
124031
|
+
const docBoot = await materializeMemoryDocs(sdk, apiKey, entity, materialized.dir);
|
|
124032
|
+
for (const note of docBoot.notes) console.error(`(${entity.slug} memory docs: ${note})`);
|
|
124033
|
+
const runFiles = { ...materialized.files, ...docBoot.files };
|
|
123736
124034
|
const est = estimateBlastRadius(entity);
|
|
123737
124035
|
const shipBack = flavor.buildShipBack({ dir: materialized.dir, entity });
|
|
123738
124036
|
const dashboardUrl = await startLiveDashboard(entity, materialized.dir);
|
|
@@ -123744,14 +124042,14 @@ async function runTaskCore(params, flavor) {
|
|
|
123744
124042
|
dir: materialized.dir,
|
|
123745
124043
|
...dashboardUrl ? { dashboard: dashboardUrl } : {},
|
|
123746
124044
|
blastRadius: est,
|
|
123747
|
-
files:
|
|
124045
|
+
files: runFiles,
|
|
123748
124046
|
...materialized.skipped ? { skippedEmptyFiles: materialized.skipped } : {},
|
|
123749
124047
|
// The run's durable memory (loop-memory-primitive). `dir` dies with the run, so
|
|
123750
124048
|
// anything the NEXT run needs has to be written here instead. Named explicitly with
|
|
123751
124049
|
// the slug because the run context carries no task identity — which is exactly why
|
|
123752
124050
|
// the taskMemory* tools take an explicit `taskSlug`.
|
|
123753
124051
|
memory: taskMemoryBlock(entity.slug, memoryDocKey),
|
|
123754
|
-
directive: flavor.buildDirective({ dir: materialized.dir, slug: entity.slug, files:
|
|
124052
|
+
directive: flavor.buildDirective({ dir: materialized.dir, slug: entity.slug, files: runFiles, docKey: memoryDocKey }),
|
|
123755
124053
|
...shipBack ? { shipBack } : {}
|
|
123756
124054
|
}) }, { type: "text", text: dashboardLine }] };
|
|
123757
124055
|
} catch (error) {
|
|
@@ -124281,7 +124579,7 @@ async function upsertTaskCore(params, flavor) {
|
|
|
124281
124579
|
return { available: false, reason: err instanceof Error ? err.message : String(err) };
|
|
124282
124580
|
}
|
|
124283
124581
|
};
|
|
124284
|
-
const upsert = async (entity, sourcePaths = {}) => {
|
|
124582
|
+
const upsert = async (entity, sourcePaths = {}, memoryDefaulted = false) => {
|
|
124285
124583
|
if (!id && flavor.injectDashboardOnCreate) {
|
|
124286
124584
|
try {
|
|
124287
124585
|
injectDefaultDashboard(entity);
|
|
@@ -124293,6 +124591,7 @@ async function upsertTaskCore(params, flavor) {
|
|
|
124293
124591
|
if (res.status === "ok") {
|
|
124294
124592
|
void refreshDynamicPrompts();
|
|
124295
124593
|
const mode2 = id ? "modified" : "created";
|
|
124594
|
+
const effectiveManifest = "stateDocs" in entity ? entity.stateDocs : !id ? null : previous && previous.available ? previous.content.stateDocs ?? null : void 0;
|
|
124296
124595
|
const summary = buildLoopUpsertSummary({
|
|
124297
124596
|
mode: mode2,
|
|
124298
124597
|
sent: entity,
|
|
@@ -124303,7 +124602,26 @@ async function upsertTaskCore(params, flavor) {
|
|
|
124303
124602
|
success: true,
|
|
124304
124603
|
mode: mode2,
|
|
124305
124604
|
[flavor.responseKey]: projectLoopIdentity(flavor.pick(res)),
|
|
124306
|
-
|
|
124605
|
+
// WHERE IT LANDED, on the receipt itself. The 2026-08-31 incident put a
|
|
124606
|
+
// task into the wrong (admin) workspace and the success payload named
|
|
124607
|
+
// no workspace at all, so neither the agent nor the human reading the
|
|
124608
|
+
// transcript afterwards could tell. Nulls when `me` could not be read —
|
|
124609
|
+
// unknown, never a guess.
|
|
124610
|
+
receipt: {
|
|
124611
|
+
fields: summary.receipt,
|
|
124612
|
+
workspace: await resolveWorkspaceIdentity(),
|
|
124613
|
+
// WHERE THE TASK'S MEMORY LANDED, said in the user's vocabulary rather than left
|
|
124614
|
+
// implicit in the manifest bytes. `effectiveManifest` is what the task HAS after
|
|
124615
|
+
// this call: what this call sent, or — on a MODIFY that did not send one — the
|
|
124616
|
+
// stored manifest the diff already read. Never a fourth read.
|
|
124617
|
+
//
|
|
124618
|
+
// ⛔ THE FLOOR IS NAMED, NOT HIDDEN. When the default fired the line still reports
|
|
124619
|
+
// where the memory landed (`Memory: per member` — it really is per member), with a
|
|
124620
|
+
// suffix saying the author declared nothing. A receipt printing the bare line would
|
|
124621
|
+
// present the floor as an authored choice, which is the one thing this injection
|
|
124622
|
+
// must never be allowed to look like.
|
|
124623
|
+
...effectiveManifest !== void 0 ? { memory: memorySummaryLine(effectiveManifest) + (memoryDefaulted ? MEMORY_DEFAULTED_SUFFIX : "") } : {}
|
|
124624
|
+
},
|
|
124307
124625
|
summary: summary.text,
|
|
124308
124626
|
summaryTruncated: summary.truncated,
|
|
124309
124627
|
// ON CREATE ONLY. A task lands PRIVATE to the author, and the one step
|
|
@@ -124394,8 +124712,13 @@ async function upsertTaskCore(params, flavor) {
|
|
|
124394
124712
|
}
|
|
124395
124713
|
stateDocs = checked.manifest;
|
|
124396
124714
|
}
|
|
124715
|
+
let memoryDefaulted = false;
|
|
124716
|
+
if (!id && declaresNoMemory(stateDocs)) {
|
|
124717
|
+
stateDocs = defaultMemoryManifest();
|
|
124718
|
+
memoryDefaulted = true;
|
|
124719
|
+
}
|
|
124397
124720
|
const body = { ...resolvedMd };
|
|
124398
|
-
if (stateDocsProvided) body.stateDocs = stateDocs;
|
|
124721
|
+
if (stateDocsProvided || memoryDefaulted) body.stateDocs = stateDocs;
|
|
124399
124722
|
if (slug) body.slug = slug;
|
|
124400
124723
|
if (descriptionShort) body.descriptionShort = descriptionShort;
|
|
124401
124724
|
if (markdownBody) body.markdownBody = markdownBody;
|
|
@@ -124410,7 +124733,7 @@ async function upsertTaskCore(params, flavor) {
|
|
|
124410
124733
|
sourcePaths[mdKey] = params[fileKey].trim();
|
|
124411
124734
|
}
|
|
124412
124735
|
}
|
|
124413
|
-
return await upsert(body, sourcePaths);
|
|
124736
|
+
return await upsert(body, sourcePaths, memoryDefaulted);
|
|
124414
124737
|
}
|
|
124415
124738
|
const hasMessages = typeof params.messages === "string" && params.messages.trim();
|
|
124416
124739
|
const hasBrief = typeof params.brief === "string" && params.brief.trim();
|
|
@@ -124435,7 +124758,7 @@ async function upsertTaskCore(params, flavor) {
|
|
|
124435
124758
|
server.tool(
|
|
124436
124759
|
{
|
|
124437
124760
|
name: "createTask",
|
|
124438
|
-
description: "Create OR modify a workspace TASK (upsert). A task is the unified card \u2014 what used to be published as either a 'compound skill' or a 'loop' is ONE row now, so this ONE tool authors both: a task with only a `markdownBody` is what a compound was, a task that also carries visionMd/constraintsMd/readmeMd is what a loop was. Pass `id` to MODIFY, omit it to CREATE. MODIFY IS A NO-CLOBBER PATCH: send `id` plus ONLY the field(s) you want to change \u2014 any of markdownBody/visionMd/constraintsMd/readmeMd/dashboardHtml/dashboardManifest/stateDocs/descriptionShort/category/draft/graphJson \u2014 fields you don't send are preserved, so a constraints-only ship-back never has to resend the body. VERBATIM BODIES: for any LONG markdown field, WRITE it to a local file first and pass the matching *FilePath (`filePath` for the body, `visionFilePath`/`constraintsFilePath`/`readmeFilePath`/`dashboardHtmlFilePath`/`dashboardManifestFilePath`) INSTEAD of inlining it \u2014 the local MCP server reads the bytes from disk and pushes them verbatim (no arg-size limit, no drift). RESPONSE: a closing `summary` derived strictly from the published content (never invented) plus a compact `receipt` of per-field byte counts and source paths; the full bodies are NOT echoed back. NOT AN AUTHORING TOOL: it publishes what you give it. To create, publish or audit a task, run `task-architect` first \u2014 this tool is only the final upsert it performs. NOTE: if the user's intent is to publish/upload/import local skills to Ametyst, call getTask FIRST to fetch the `task-architect` task \u2014 it contains the publish procedure to follow before using this tool. MEMORY: `stateDocs` is the task's memory manifest \u2014 which memory docs / record kinds it owns and whether each is `shared` by the workspace or per-`member`; the runner creates the declared docs at boot and ships each back to its declared namespace. DASHBOARD: createTask does NOT inject a default monitoring page on create \u2014 pass `dashboardHtml` if the task wants one.
|
|
124761
|
+
description: "Create OR modify a workspace TASK (upsert). A task is the unified card \u2014 what used to be published as either a 'compound skill' or a 'loop' is ONE row now, so this ONE tool authors both: a task with only a `markdownBody` is what a compound was, a task that also carries visionMd/constraintsMd/readmeMd is what a loop was. Pass `id` to MODIFY, omit it to CREATE. MODIFY IS A NO-CLOBBER PATCH: send `id` plus ONLY the field(s) you want to change \u2014 any of markdownBody/visionMd/constraintsMd/readmeMd/dashboardHtml/dashboardManifest/stateDocs/descriptionShort/category/draft/graphJson \u2014 fields you don't send are preserved, so a constraints-only ship-back never has to resend the body. VERBATIM BODIES: for any LONG markdown field, WRITE it to a local file first and pass the matching *FilePath (`filePath` for the body, `visionFilePath`/`constraintsFilePath`/`readmeFilePath`/`dashboardHtmlFilePath`/`dashboardManifestFilePath`) INSTEAD of inlining it \u2014 the local MCP server reads the bytes from disk and pushes them verbatim (no arg-size limit, no drift). RESPONSE: a closing `summary` derived strictly from the published content (never invented) plus a compact `receipt` of per-field byte counts and source paths; the full bodies are NOT echoed back. NOT AN AUTHORING TOOL: it publishes what you give it. To create, publish or audit a task, run `task-architect` first \u2014 this tool is only the final upsert it performs. NOTE: if the user's intent is to publish/upload/import local skills to Ametyst, call getTask FIRST to fetch the `task-architect` task \u2014 it contains the publish procedure to follow before using this tool. MEMORY: `stateDocs` is the task's memory manifest \u2014 which memory docs / record kinds it owns and whether each is `shared` by the workspace or per-`member`; the runner creates the declared docs at boot and ships each back to its declared namespace. \u26D4 THE SCOPE CHOICE IS THE USER'S, NOT YOURS: BEFORE you create or modify a task that carries a memory manifest, EXPLAIN THE CHOICE TO THEM in your own words \u2014 never as `stateDocs` or `scope` \u2014 covering all of the following, and ASK them when their intent is ambiguous instead of deciding for them. Quote it verbatim if that is clearer; never contradict it: \"" + MEMORY_SCOPE_EXPLANATION + '" The response says which one the task ended up with, as a `memory` line on the receipt (`Memory: global` / `Memory: per member` / `Memory: mixed \u2014 N global \xB7 M per member` / `Memory: none`); getTask carries the same line. \u26D4 DECLARING THE MANIFEST IS EXPECTED ON EVERY TASK: a CREATE that declares none \u2014 or a declared-empty one \u2014 is given a floor rather than being born with no memory (the run diary, per member: `{"docs":[],"records":[{"kind":"run","scope":"member"}]}`), and the receipt says so with `(defaulted \u2014 no manifest declared)`, but that floor is a backstop and NOT a substitute for deriving the manifest this task actually needs and explaining the scope choice to the user first. DASHBOARD: createTask does NOT inject a default monitoring page on create \u2014 pass `dashboardHtml` if the task wants one.',
|
|
124439
124762
|
inputs: [
|
|
124440
124763
|
{ name: "slug", type: "string", required: false, description: "URL-safe unique slug for the task within the workspace. Required on CREATE." },
|
|
124441
124764
|
{ name: "descriptionShort", type: "string", required: false, description: "One-line description of what the task does. Required on CREATE." },
|
|
@@ -124451,7 +124774,7 @@ server.tool(
|
|
|
124451
124774
|
{ name: "dashboardHtmlFilePath", type: "string", required: false, description: "Path to a local file read verbatim as the dashboard HTML. Takes precedence over `dashboardHtml`." },
|
|
124452
124775
|
{ name: "dashboardManifest", type: "string", required: false, description: `JSON (as text) naming which materialized files the dashboard's /data endpoint surfaces (e.g. {"files":["STATUS.md"]}).` },
|
|
124453
124776
|
{ name: "dashboardManifestFilePath", type: "string", required: false, description: "Path to a local file read verbatim as the dashboard manifest. Takes precedence over `dashboardManifest`." },
|
|
124454
|
-
{ name: "stateDocs", type: "string", required: false, description: 'THE MEMORY MANIFEST \u2014 JSON TEXT: `{"docs":[{"key":"board","scope":"shared"}],"records":[{"kind":"run","scope":"member"}]}`. It declares WHICH memory documents and record kinds this task owns and WHERE each one lives: `scope` is `shared` (ONE row for the whole workspace \u2014 every member reads and writes the same document) or `member` (one row PER SEAT \u2014 each member has their own). `records[].kind` is a RESERVED diary kind (run | decision | error | import \u2014 always accepted by the server, declared or not; declaring one only fixes its scope) or a FREE item kind the task invents (a token [a-z0-9-]{1,64}, e.g. `pbi`, `test`, `needs-patrick` \u2014 keyed, versioned, closed with taskMemoryArchive; see the TASK MEMORY MODEL in your instructions). Both arrays are REQUIRED and may be empty (`{"docs":[],"records":[]}` = this task keeps no memory). \u26D4 A STRING, ALWAYS: send the JSON object as text, `"null"` to clear an existing manifest, and omit the field to leave it untouched; a bare object or a bare null is REJECTED by this tool\'s input schema before the call runs, and an EMPTY STRING is treated exactly like omitting the field. WHAT THE RUNNER DOES WITH IT: at boot every declared doc is read from its declared namespace and CREATED there (empty) if it does not exist yet \u2014 a `member` doc is seeded from the `shared` row of the same key when one exists \u2014 and materialized as `<key>.md`; at exit each changed doc is shipped back to the SAME namespace. A write through taskMemoryAppend to a declared key/kind needs no scope (the server resolves it and refuses a disagreeing one). RULES, enforced here AND by the server (the call fails and the task is neither created nor modified): `key` is a safe basename of 1..128 chars (no `/`, `\\`, no leading dot), unique case-insensitively, and NOT one of the seven files the runner writes itself (SKILL.md, VISION.md, CONSTRAINTS.md, README.md, STATUS.md, dashboard.html, dashboard.manifest.json \u2014 with or without `.md`); at most 64 docs and 16 records, one entry per kind. The manifest has NO archive section \u2014 an archived item is the same kind in the same namespace with archived: true. The retired array shape (`[{key, filename, \u2026}]`, including `"[]"`) is INVALID. \u26D4 DERIVE the manifest from what the task actually needs to remember between runs and who needs to see it \u2014 a board the whole team works is `shared`, a personal cursor is `member` \u2014 and explain it to the author IN THEIR WORDS ("everyone on the team sees the same to-do list"), never as `stateDocs` or `scope`.' },
|
|
124777
|
+
{ name: "stateDocs", type: "string", required: false, description: 'THE MEMORY MANIFEST \u2014 JSON TEXT: `{"docs":[{"key":"board","scope":"shared"}],"records":[{"kind":"run","scope":"member"}]}`. It declares WHICH memory documents and record kinds this task owns and WHERE each one lives: `scope` is `shared` (ONE row for the whole workspace \u2014 every member reads and writes the same document) or `member` (one row PER SEAT \u2014 each member has their own). `records[].kind` is a RESERVED diary kind (run | decision | error | import \u2014 always accepted by the server, declared or not; declaring one only fixes its scope) or a FREE item kind the task invents (a token [a-z0-9-]{1,64}, e.g. `pbi`, `test`, `needs-patrick` \u2014 keyed, versioned, closed with taskMemoryArchive; see the TASK MEMORY MODEL in your instructions). Both arrays are REQUIRED and may be empty (`{"docs":[],"records":[]}` = this task keeps no memory). \u26D4 A STRING, ALWAYS: send the JSON object as text, `"null"` to clear an existing manifest, and omit the field to leave it untouched; a bare object or a bare null is REJECTED by this tool\'s input schema before the call runs, and an EMPTY STRING is treated exactly like omitting the field. WHAT THE RUNNER DOES WITH IT: at boot every declared doc is read from its declared namespace and CREATED there (empty) if it does not exist yet \u2014 a `member` doc is seeded from the `shared` row of the same key when one exists \u2014 and materialized as `<key>.md`; at exit each changed doc is shipped back to the SAME namespace. A write through taskMemoryAppend to a declared key/kind needs no scope (the server resolves it and refuses a disagreeing one). RULES, enforced here AND by the server (the call fails and the task is neither created nor modified): `key` is a safe basename of 1..128 chars (no `/`, `\\`, no leading dot), unique case-insensitively, and NOT one of the seven files the runner writes itself (SKILL.md, VISION.md, CONSTRAINTS.md, README.md, STATUS.md, dashboard.html, dashboard.manifest.json \u2014 with or without `.md`); at most 64 docs and 16 records, one entry per kind. The manifest has NO archive section \u2014 an archived item is the same kind in the same namespace with archived: true. The retired array shape (`[{key, filename, \u2026}]`, including `"[]"`) is INVALID. \u26D4 DERIVE the manifest from what the task actually needs to remember between runs and who needs to see it \u2014 a board the whole team works is `shared`, a personal cursor is `member` \u2014 and explain it to the author IN THEIR WORDS ("everyone on the team sees the same to-do list"), never as `stateDocs` or `scope`. \u26D4 AND YOU DO THAT BEFORE THIS CALL, NOT AFTER \u2014 the choice is the user\'s: cover all of this with them, and ASK when their intent is ambiguous instead of deciding for them. "' + MEMORY_SCOPE_EXPLANATION + '"' },
|
|
124455
124778
|
{ name: "graphJson", type: "string", required: false, description: "JSON string of the canvas node graph (for re-edit). Defaults to {} on create." },
|
|
124456
124779
|
{ name: "draft", type: "boolean", required: false, description: "Whether the task is a draft (excluded from discovery). Defaults to true on create." },
|
|
124457
124780
|
{ name: "category", type: "string", required: false, description: "Free-text category for browsing/filtering (e.g. 'research'). Must be explicit \u2014 the tool asks rather than guessing." },
|
|
@@ -125809,14 +126132,14 @@ function killPreviousServeInstances(deps) {
|
|
|
125809
126132
|
|
|
125810
126133
|
// src/commands/autosync-skills.ts
|
|
125811
126134
|
init_esm_shims();
|
|
125812
|
-
import { existsSync as
|
|
126135
|
+
import { existsSync as existsSync14 } from "fs";
|
|
125813
126136
|
import { homedir as homedir12 } from "os";
|
|
125814
126137
|
import { join as join18 } from "path";
|
|
125815
126138
|
|
|
125816
126139
|
// src/compounds/sync-skills.ts
|
|
125817
126140
|
init_esm_shims();
|
|
125818
126141
|
init_paths();
|
|
125819
|
-
import { existsSync as
|
|
126142
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync13, rmSync, writeFileSync as writeFileSync10 } from "fs";
|
|
125820
126143
|
import { join as join17 } from "path";
|
|
125821
126144
|
var MANAGED_MARKER = "<!-- ametyst-managed: sync-skills -->";
|
|
125822
126145
|
var GITIGNORE_HEADER = "# ametyst-managed: sync-skills \u2014 pointer skills generated for this account; never commit them.";
|
|
@@ -125861,7 +126184,7 @@ ${taskRunSection(slug, kind)}`;
|
|
|
125861
126184
|
}
|
|
125862
126185
|
function isManaged(file) {
|
|
125863
126186
|
try {
|
|
125864
|
-
return
|
|
126187
|
+
return readFileSync13(file, "utf8").includes(MANAGED_MARKER);
|
|
125865
126188
|
} catch {
|
|
125866
126189
|
return false;
|
|
125867
126190
|
}
|
|
@@ -125875,8 +126198,8 @@ function buildGitignoreContent(managedSlugs) {
|
|
|
125875
126198
|
function maintainGitignore(root2) {
|
|
125876
126199
|
const file = join17(root2, ".gitignore");
|
|
125877
126200
|
const managed = listManagedDirs(root2);
|
|
125878
|
-
if (
|
|
125879
|
-
const current =
|
|
126201
|
+
if (existsSync13(file)) {
|
|
126202
|
+
const current = readFileSync13(file, "utf8");
|
|
125880
126203
|
const firstLine = current.split(/\r?\n/, 1)[0];
|
|
125881
126204
|
if (firstLine !== GITIGNORE_HEADER) {
|
|
125882
126205
|
console.error(
|
|
@@ -125939,7 +126262,7 @@ async function syncSkills(opts = {}) {
|
|
|
125939
126262
|
const skipped = [];
|
|
125940
126263
|
for (const [dir, item] of desired) {
|
|
125941
126264
|
const file = join17(dir, "SKILL.md");
|
|
125942
|
-
if (
|
|
126265
|
+
if (existsSync13(file) && !isManaged(file)) {
|
|
125943
126266
|
skipped.push(item.slug);
|
|
125944
126267
|
console.warn(`\u26A0\uFE0F sync-skills: skipping "${item.slug}" \u2014 an unmanaged skill already exists at ${file}`);
|
|
125945
126268
|
continue;
|
|
@@ -125967,7 +126290,7 @@ function isSkillsAutosyncEnabled(env = process.env) {
|
|
|
125967
126290
|
return !(raw === "0" || raw === "false" || raw === "off" || raw === "no");
|
|
125968
126291
|
}
|
|
125969
126292
|
function detectPresentTargets(deps = {}) {
|
|
125970
|
-
const exists = deps.existsSync ??
|
|
126293
|
+
const exists = deps.existsSync ?? existsSync14;
|
|
125971
126294
|
const cwd = deps.cwd ?? (() => process.cwd());
|
|
125972
126295
|
const home = deps.homedir ?? homedir12;
|
|
125973
126296
|
const targets = [];
|
|
@@ -126047,7 +126370,7 @@ async function serveCommand() {
|
|
|
126047
126370
|
console.error(
|
|
126048
126371
|
persisted ? isPlaintextVault(persisted) ? "Starting MCP server... (persisted UNENCRYPTED wallet found \u2014 always unlocked, no start_session needed)" : "Starting MCP server... (persisted wallet found \u2014 unlock with start_session)" : "Starting MCP server... (no saved wallet \u2014 one will be created on start_session)"
|
|
126049
126372
|
);
|
|
126050
|
-
await startMCPServer(keystore, eoa, { ...config, apiKey }, versionNotice, {
|
|
126373
|
+
await startMCPServer(keystore, eoa, { ...config, apiKey, apiKeySource: resolved.source ?? void 0 }, versionNotice, {
|
|
126051
126374
|
// Best-effort: materialize local `/`-command pointer skills for every published
|
|
126052
126375
|
// compound + loop so they're available without a manual `compound sync-skills`.
|
|
126053
126376
|
// Deferred from boot to the MCP initialize handshake so the sync is CLIENT-AWARE:
|
|
@@ -126986,7 +127309,7 @@ function recordFailedLaunch(args) {
|
|
|
126986
127309
|
init_esm_shims();
|
|
126987
127310
|
import { spawn as spawn2 } from "child_process";
|
|
126988
127311
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
126989
|
-
import { existsSync as
|
|
127312
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
126990
127313
|
import { dirname as dirname8, join as join23 } from "path";
|
|
126991
127314
|
|
|
126992
127315
|
// src/loops/claude-binary.ts
|
|
@@ -127178,7 +127501,7 @@ async function runLoop(loopId, opts = {}) {
|
|
|
127178
127501
|
`Loop ${loop2.slug}: ${est.steps} steps (${est.paidSteps} paid), est \u20AC${est.estCostEur ?? "?"} \u2014 ${capLabel}`
|
|
127179
127502
|
);
|
|
127180
127503
|
const statusPath = join23(dir, "STATUS.md");
|
|
127181
|
-
const statusBefore =
|
|
127504
|
+
const statusBefore = existsSync15(statusPath) ? readFileSync14(statusPath, "utf-8") : "";
|
|
127182
127505
|
writeFileSync12(
|
|
127183
127506
|
statusPath,
|
|
127184
127507
|
statusBefore.replace(
|
|
@@ -127238,7 +127561,7 @@ blast_radius: steps=${est.steps} paid=${est.paidSteps} estEur=${est.estCostEur ?
|
|
|
127238
127561
|
async function shipConstraints() {
|
|
127239
127562
|
try {
|
|
127240
127563
|
const constraintsPath = join23(dir, "CONSTRAINTS.md");
|
|
127241
|
-
const materializedConstraints =
|
|
127564
|
+
const materializedConstraints = existsSync15(constraintsPath) ? readFileSync14(constraintsPath, "utf-8") : void 0;
|
|
127242
127565
|
if (materializedConstraints === void 0) return;
|
|
127243
127566
|
const boot = loop2.constraintsMd ?? "";
|
|
127244
127567
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
@@ -127349,7 +127672,7 @@ blast_radius: steps=${est.steps} paid=${est.paidSteps} estEur=${est.estCostEur ?
|
|
|
127349
127672
|
startedAtEpochMs
|
|
127350
127673
|
});
|
|
127351
127674
|
}
|
|
127352
|
-
const statusAfter =
|
|
127675
|
+
const statusAfter = existsSync15(statusPath) ? readFileSync14(statusPath, "utf-8") : "";
|
|
127353
127676
|
const clean2 = exitCode === 0 && /(vision\s*done|queue\s*drained|status:\s*done)/i.test(statusAfter) && !/(brake|crash|crashed|errored)/i.test(statusAfter);
|
|
127354
127677
|
await shipBackConstraints();
|
|
127355
127678
|
process.off("SIGINT", onSignal);
|
|
@@ -127387,7 +127710,7 @@ async function showLoop(loopId) {
|
|
|
127387
127710
|
// src/loops/schedule.ts
|
|
127388
127711
|
init_esm_shims();
|
|
127389
127712
|
init_paths();
|
|
127390
|
-
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync11, rmSync as rmSync3, existsSync as
|
|
127713
|
+
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync11, rmSync as rmSync3, existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync15, accessSync as accessSync2, constants as constants2 } from "fs";
|
|
127391
127714
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
127392
127715
|
import { join as join24, dirname as dirname9 } from "path";
|
|
127393
127716
|
import { homedir as homedir14 } from "os";
|
|
@@ -127467,7 +127790,7 @@ function isLoaded(lbl) {
|
|
|
127467
127790
|
}
|
|
127468
127791
|
function readInteractiveDefaultModel(home) {
|
|
127469
127792
|
try {
|
|
127470
|
-
const raw =
|
|
127793
|
+
const raw = readFileSync15(join24(home, ".claude", "settings.json"), "utf-8");
|
|
127471
127794
|
const model = JSON.parse(String(raw)).model;
|
|
127472
127795
|
return typeof model === "string" && model.trim() ? model.trim() : void 0;
|
|
127473
127796
|
} catch {
|
|
@@ -127695,7 +128018,7 @@ function list(kind, opts = {}) {
|
|
|
127695
128018
|
const prefix = kind.labelPrefix;
|
|
127696
128019
|
if (platform === "darwin") {
|
|
127697
128020
|
const dir = join24(home, "Library", "LaunchAgents");
|
|
127698
|
-
if (!
|
|
128021
|
+
if (!existsSync16(dir)) return [];
|
|
127699
128022
|
return readdirSync5(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".plist")).map((f) => f.slice(prefix.length, -".plist".length));
|
|
127700
128023
|
}
|
|
127701
128024
|
return readCrontab().split("\n").filter((l) => l.includes(`# ${prefix}`)).map((l) => l.trimEnd().slice(l.trimEnd().lastIndexOf(prefix) + prefix.length));
|
|
@@ -127757,12 +128080,12 @@ function listEntries(kind, opts = {}) {
|
|
|
127757
128080
|
const prefix = kind.labelPrefix;
|
|
127758
128081
|
if (platform === "darwin") {
|
|
127759
128082
|
const dir = join24(home, "Library", "LaunchAgents");
|
|
127760
|
-
if (!
|
|
128083
|
+
if (!existsSync16(dir)) return [];
|
|
127761
128084
|
return readdirSync5(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".plist")).map((f) => {
|
|
127762
128085
|
const slug = f.slice(prefix.length, -".plist".length);
|
|
127763
128086
|
let envKeys = [];
|
|
127764
128087
|
try {
|
|
127765
|
-
envKeys = plistEnvKeys(String(
|
|
128088
|
+
envKeys = plistEnvKeys(String(readFileSync15(join24(dir, f), "utf-8")));
|
|
127766
128089
|
} catch {
|
|
127767
128090
|
envKeys = [];
|
|
127768
128091
|
}
|