threadnote 0.7.12 → 1.1.0
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/README.md +4 -0
- package/dist/mcp_server.cjs +3171 -273
- package/dist/threadnote.cjs +894 -356
- package/docs/index.html +5 -5
- package/docs/migration.md +1 -1
- package/docs/troubleshooting.md +10 -5
- package/manager/app.css +42 -1
- package/manager/app.js +144 -59
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3472,7 +3472,7 @@ var {
|
|
|
3472
3472
|
// src/constants.ts
|
|
3473
3473
|
var DEFAULT_HOST = "127.0.0.1";
|
|
3474
3474
|
var DEFAULT_PORT = 1933;
|
|
3475
|
-
var DEFAULT_OPENVIKING_VERSION = "0.3.
|
|
3475
|
+
var DEFAULT_OPENVIKING_VERSION = "0.3.24";
|
|
3476
3476
|
var DEFAULT_ACCOUNT = "local";
|
|
3477
3477
|
var DEFAULT_AGENT_ID = "threadnote";
|
|
3478
3478
|
var OPENVIKING_PACKAGE_NAME = "openviking[local-embed]";
|
|
@@ -3523,8 +3523,8 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3523
3523
|
];
|
|
3524
3524
|
|
|
3525
3525
|
// src/hooks.ts
|
|
3526
|
-
var
|
|
3527
|
-
var
|
|
3526
|
+
var import_promises8 = require("node:fs/promises");
|
|
3527
|
+
var import_node_path10 = require("node:path");
|
|
3528
3528
|
|
|
3529
3529
|
// src/mcp.ts
|
|
3530
3530
|
var import_node_fs2 = require("node:fs");
|
|
@@ -3616,6 +3616,41 @@ async function withSpinner(message, action) {
|
|
|
3616
3616
|
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3617
3617
|
}
|
|
3618
3618
|
}
|
|
3619
|
+
function startProgress(message) {
|
|
3620
|
+
if (import_node_process.stdout.isTTY !== true || process.env.CI !== void 0 || process.env.THREADNOTE_NO_SPINNER !== void 0) {
|
|
3621
|
+
console.log(message);
|
|
3622
|
+
return {
|
|
3623
|
+
update(nextMessage) {
|
|
3624
|
+
console.log(nextMessage);
|
|
3625
|
+
},
|
|
3626
|
+
stop() {
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
const frames = ["-", "\\", "|", "/"];
|
|
3632
|
+
let frameIndex = 0;
|
|
3633
|
+
let currentMessage = message;
|
|
3634
|
+
const render = () => {
|
|
3635
|
+
(0, import_node_readline.clearLine)(import_node_process.stdout, 0);
|
|
3636
|
+
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3637
|
+
import_node_process.stdout.write(`${muted(frames[frameIndex])} ${currentMessage}`);
|
|
3638
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
3639
|
+
};
|
|
3640
|
+
const timer = setInterval(render, 100);
|
|
3641
|
+
render();
|
|
3642
|
+
return {
|
|
3643
|
+
update(nextMessage) {
|
|
3644
|
+
currentMessage = nextMessage;
|
|
3645
|
+
render();
|
|
3646
|
+
},
|
|
3647
|
+
stop() {
|
|
3648
|
+
clearInterval(timer);
|
|
3649
|
+
(0, import_node_readline.clearLine)(import_node_process.stdout, 0);
|
|
3650
|
+
(0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3619
3654
|
|
|
3620
3655
|
// src/utils.ts
|
|
3621
3656
|
function isJsonObject(value) {
|
|
@@ -3753,15 +3788,11 @@ async function maybeRun(dryRun, executable, args, options = {}) {
|
|
|
3753
3788
|
}
|
|
3754
3789
|
async function runCommand(executable, args, options = {}) {
|
|
3755
3790
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
3756
|
-
const child = (0, import_node_child_process.spawn)(executable, args, { cwd: options.cwd });
|
|
3757
|
-
const stdoutChunks = [];
|
|
3758
|
-
const stderrChunks = [];
|
|
3759
3791
|
const maxOutputBytes = options.maxOutputBytes ?? defaultCommandMaxOutputBytes();
|
|
3760
|
-
let stdoutBytes = 0;
|
|
3761
|
-
let stderrBytes = 0;
|
|
3762
3792
|
let finished = false;
|
|
3763
|
-
let failureResult;
|
|
3764
3793
|
let killTimer;
|
|
3794
|
+
let killEscalationTimer;
|
|
3795
|
+
let failureMessage;
|
|
3765
3796
|
let sentTerminationSignal = false;
|
|
3766
3797
|
const timeoutMs = options.timeoutMs ?? defaultCommandTimeoutMs();
|
|
3767
3798
|
const finish = (result) => {
|
|
@@ -3772,22 +3803,47 @@ async function runCommand(executable, args, options = {}) {
|
|
|
3772
3803
|
if (killTimer) {
|
|
3773
3804
|
clearTimeout(killTimer);
|
|
3774
3805
|
}
|
|
3806
|
+
if (killEscalationTimer) {
|
|
3807
|
+
clearTimeout(killEscalationTimer);
|
|
3808
|
+
}
|
|
3775
3809
|
if (result.exitCode !== 0 && options.allowFailure !== true) {
|
|
3776
3810
|
rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
|
|
3777
3811
|
return;
|
|
3778
3812
|
}
|
|
3779
3813
|
resolvePromise(result);
|
|
3780
3814
|
};
|
|
3815
|
+
const child = (0, import_node_child_process.execFile)(
|
|
3816
|
+
executable,
|
|
3817
|
+
[...args],
|
|
3818
|
+
{
|
|
3819
|
+
cwd: options.cwd,
|
|
3820
|
+
encoding: "utf8",
|
|
3821
|
+
maxBuffer: maxOutputBytes
|
|
3822
|
+
},
|
|
3823
|
+
(err, stdout2, stderr) => {
|
|
3824
|
+
finish(
|
|
3825
|
+
commandResultFromExecFileCallback({
|
|
3826
|
+
args,
|
|
3827
|
+
err,
|
|
3828
|
+
executable,
|
|
3829
|
+
failureMessage,
|
|
3830
|
+
maxOutputBytes,
|
|
3831
|
+
stderr,
|
|
3832
|
+
stdout: stdout2
|
|
3833
|
+
})
|
|
3834
|
+
);
|
|
3835
|
+
}
|
|
3836
|
+
);
|
|
3781
3837
|
const failAndKill = (message) => {
|
|
3782
|
-
if (
|
|
3838
|
+
if (failureMessage) {
|
|
3783
3839
|
return;
|
|
3784
3840
|
}
|
|
3785
|
-
|
|
3841
|
+
failureMessage = message;
|
|
3786
3842
|
if (!sentTerminationSignal) {
|
|
3787
3843
|
sentTerminationSignal = true;
|
|
3788
3844
|
child.kill("SIGTERM");
|
|
3789
3845
|
}
|
|
3790
|
-
setTimeout(() => {
|
|
3846
|
+
killEscalationTimer = setTimeout(() => {
|
|
3791
3847
|
if (!finished) {
|
|
3792
3848
|
child.kill("SIGKILL");
|
|
3793
3849
|
}
|
|
@@ -3799,54 +3855,20 @@ async function runCommand(executable, args, options = {}) {
|
|
|
3799
3855
|
}, timeoutMs);
|
|
3800
3856
|
killTimer.unref?.();
|
|
3801
3857
|
}
|
|
3802
|
-
child.stdout.on("data", (chunk) => {
|
|
3803
|
-
if (failureResult) {
|
|
3804
|
-
return;
|
|
3805
|
-
}
|
|
3806
|
-
const text = String(chunk);
|
|
3807
|
-
stdoutBytes += Buffer.byteLength(text);
|
|
3808
|
-
if (stdoutBytes + stderrBytes > maxOutputBytes) {
|
|
3809
|
-
failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
|
|
3810
|
-
return;
|
|
3811
|
-
}
|
|
3812
|
-
stdoutChunks.push(text);
|
|
3813
|
-
});
|
|
3814
|
-
child.stderr.on("data", (chunk) => {
|
|
3815
|
-
if (failureResult) {
|
|
3816
|
-
return;
|
|
3817
|
-
}
|
|
3818
|
-
const text = String(chunk);
|
|
3819
|
-
stderrBytes += Buffer.byteLength(text);
|
|
3820
|
-
if (stdoutBytes + stderrBytes > maxOutputBytes) {
|
|
3821
|
-
failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
|
|
3822
|
-
return;
|
|
3823
|
-
}
|
|
3824
|
-
stderrChunks.push(text);
|
|
3825
|
-
});
|
|
3826
|
-
child.on("error", (err) => {
|
|
3827
|
-
if (finished) {
|
|
3828
|
-
return;
|
|
3829
|
-
}
|
|
3830
|
-
if (killTimer) {
|
|
3831
|
-
clearTimeout(killTimer);
|
|
3832
|
-
}
|
|
3833
|
-
if (options.allowFailure === true) {
|
|
3834
|
-
finish({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
|
|
3835
|
-
} else {
|
|
3836
|
-
finished = true;
|
|
3837
|
-
rejectPromise(err);
|
|
3838
|
-
}
|
|
3839
|
-
});
|
|
3840
|
-
child.on("close", (code) => {
|
|
3841
|
-
const result = failureResult ?? {
|
|
3842
|
-
exitCode: code ?? 1,
|
|
3843
|
-
stderr: stderrChunks.join(""),
|
|
3844
|
-
stdout: stdoutChunks.join("")
|
|
3845
|
-
};
|
|
3846
|
-
finish(result);
|
|
3847
|
-
});
|
|
3848
3858
|
});
|
|
3849
3859
|
}
|
|
3860
|
+
function commandResultFromExecFileCallback(params) {
|
|
3861
|
+
if (params.failureMessage) {
|
|
3862
|
+
return { exitCode: 124, stderr: params.failureMessage, stdout: params.stdout };
|
|
3863
|
+
}
|
|
3864
|
+
if (!params.err) {
|
|
3865
|
+
return { exitCode: 0, stderr: params.stderr, stdout: params.stdout };
|
|
3866
|
+
}
|
|
3867
|
+
const error = params.err;
|
|
3868
|
+
const exitCode = typeof error.code === "number" ? error.code : error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? 124 : 127;
|
|
3869
|
+
const stderr = error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? `${formatShellCommand(params.executable, params.args)} exceeded output limit of ${params.maxOutputBytes} bytes` : params.stderr || errorMessage(error);
|
|
3870
|
+
return { exitCode, stderr, stdout: params.stdout };
|
|
3871
|
+
}
|
|
3850
3872
|
function defaultCommandTimeoutMs() {
|
|
3851
3873
|
return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
|
|
3852
3874
|
}
|
|
@@ -4773,8 +4795,8 @@ function existsSyncDirectory(path) {
|
|
|
4773
4795
|
}
|
|
4774
4796
|
|
|
4775
4797
|
// src/memory.ts
|
|
4776
|
-
var
|
|
4777
|
-
var
|
|
4798
|
+
var import_promises6 = require("node:fs/promises");
|
|
4799
|
+
var import_node_path7 = require("node:path");
|
|
4778
4800
|
|
|
4779
4801
|
// src/manifest.ts
|
|
4780
4802
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -7472,8 +7494,311 @@ function readStringArray(object, key) {
|
|
|
7472
7494
|
return value;
|
|
7473
7495
|
}
|
|
7474
7496
|
|
|
7475
|
-
// src/
|
|
7497
|
+
// src/index_repair.ts
|
|
7498
|
+
var import_promises4 = require("node:fs/promises");
|
|
7499
|
+
var import_node_path4 = require("node:path");
|
|
7500
|
+
|
|
7501
|
+
// src/runtime.ts
|
|
7502
|
+
var import_node_fs3 = require("node:fs");
|
|
7503
|
+
var import_node_os3 = require("node:os");
|
|
7476
7504
|
var import_node_path3 = require("node:path");
|
|
7505
|
+
function getRuntimeConfig(program2, manifestOverride) {
|
|
7506
|
+
const options = program2.opts();
|
|
7507
|
+
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
7508
|
+
const manifestPath = expandPath(
|
|
7509
|
+
manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
|
|
7510
|
+
);
|
|
7511
|
+
return {
|
|
7512
|
+
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
7513
|
+
agentContextHome: threadnoteHome,
|
|
7514
|
+
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
7515
|
+
host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
|
|
7516
|
+
manifestPath,
|
|
7517
|
+
openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
|
|
7518
|
+
port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
|
|
7519
|
+
user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
|
|
7520
|
+
};
|
|
7521
|
+
}
|
|
7522
|
+
function defaultManifestPath(agentContextHome) {
|
|
7523
|
+
const userManifest = (0, import_node_path3.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
7524
|
+
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
7525
|
+
}
|
|
7526
|
+
function builtInExampleManifestPath() {
|
|
7527
|
+
return (0, import_node_path3.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
7528
|
+
}
|
|
7529
|
+
function openVikingHealthUrl(config) {
|
|
7530
|
+
return `http://${config.host}:${config.port}/health`;
|
|
7531
|
+
}
|
|
7532
|
+
function openVikingLogPath(config) {
|
|
7533
|
+
return (0, import_node_path3.join)(config.agentContextHome, "logs", "server.log");
|
|
7534
|
+
}
|
|
7535
|
+
function openVikingServerArgs(config) {
|
|
7536
|
+
return ["--config", (0, import_node_path3.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7537
|
+
}
|
|
7538
|
+
function withIdentity(config, args) {
|
|
7539
|
+
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
7540
|
+
}
|
|
7541
|
+
function renderTemplate(template, config, extras = {}) {
|
|
7542
|
+
let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
|
|
7543
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
7544
|
+
rendered = rendered.replaceAll(`{{${key}}}`, () => value);
|
|
7545
|
+
}
|
|
7546
|
+
return rendered;
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
// src/index_repair.ts
|
|
7550
|
+
var AUTO_REPAIR_STATE_FILE = "index-auto-repair.json";
|
|
7551
|
+
var AUTO_REPAIR_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
7552
|
+
var MAX_SCAN_DEPTH = 5;
|
|
7553
|
+
var MAX_REPAIR_TARGETS = 4;
|
|
7554
|
+
var MAINTENANCE_COLLAPSE_DEPTH = 3;
|
|
7555
|
+
var MAINTENANCE_MAX_REPAIR_TARGETS = 512;
|
|
7556
|
+
async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
7557
|
+
options.onProgress?.({ type: "scan-start" });
|
|
7558
|
+
const targets = await findStaleRecallIndexTargets(config, options);
|
|
7559
|
+
const repairTargets = targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS);
|
|
7560
|
+
options.onProgress?.({
|
|
7561
|
+
repairTargetCount: repairTargets.length,
|
|
7562
|
+
totalTargets: targets.length,
|
|
7563
|
+
type: "scan-complete"
|
|
7564
|
+
});
|
|
7565
|
+
if (targets.length === 0) {
|
|
7566
|
+
return { repairedUris: [], skippedRecentUris: [], warnings: [] };
|
|
7567
|
+
}
|
|
7568
|
+
const state = options.dryRun === true ? { entries: {}, version: 1 } : await readAutoRepairState(config);
|
|
7569
|
+
const now = Date.now();
|
|
7570
|
+
const repairedUris = [];
|
|
7571
|
+
const skippedRecentUris = [];
|
|
7572
|
+
const warnings = [];
|
|
7573
|
+
for (const [targetIndex, target] of repairTargets.entries()) {
|
|
7574
|
+
const progressBase = { index: targetIndex + 1, target, total: repairTargets.length };
|
|
7575
|
+
const previous = state.entries[target.uri];
|
|
7576
|
+
if (previous?.signature === target.signature && now - Date.parse(previous.repairedAt) < AUTO_REPAIR_TTL_MS && options.dryRun !== true && options.ignoreBackoff !== true) {
|
|
7577
|
+
options.onProgress?.({ ...progressBase, type: "repair-skip-recent" });
|
|
7578
|
+
skippedRecentUris.push(target.uri);
|
|
7579
|
+
continue;
|
|
7580
|
+
}
|
|
7581
|
+
if (options.dryRun === true) {
|
|
7582
|
+
options.onProgress?.({ ...progressBase, type: "repair-dry-run" });
|
|
7583
|
+
repairedUris.push(target.uri);
|
|
7584
|
+
continue;
|
|
7585
|
+
}
|
|
7586
|
+
options.onProgress?.({ ...progressBase, type: "repair-start" });
|
|
7587
|
+
const result = await runCommand(
|
|
7588
|
+
ov,
|
|
7589
|
+
withIdentity(config, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
7590
|
+
{ allowFailure: true }
|
|
7591
|
+
);
|
|
7592
|
+
if (result.exitCode === 0) {
|
|
7593
|
+
repairedUris.push(target.uri);
|
|
7594
|
+
state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
|
|
7595
|
+
} else {
|
|
7596
|
+
warnings.push(indexRepairWarning(target.uri, result));
|
|
7597
|
+
await options.onRepairFailure?.(target, result);
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
if (options.dryRun !== true && repairedUris.length > 0) {
|
|
7601
|
+
await writeAutoRepairState(config, state);
|
|
7602
|
+
}
|
|
7603
|
+
return { repairedUris, skippedRecentUris, warnings };
|
|
7604
|
+
}
|
|
7605
|
+
async function findStaleRecallIndexTargets(config, options = {}) {
|
|
7606
|
+
const roots = await scanRoots(config, options);
|
|
7607
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
7608
|
+
for (const root of roots) {
|
|
7609
|
+
if (!await exists(root.path)) {
|
|
7610
|
+
continue;
|
|
7611
|
+
}
|
|
7612
|
+
const sidecars = await staleSidecars(root.path, root.uri);
|
|
7613
|
+
if (options.collapseToRoots === true) {
|
|
7614
|
+
for (const sidecar of sidecars) {
|
|
7615
|
+
const uri = collapseStaleSidecarUri(root.uri, sidecar.relativePath, options.collapseDepth);
|
|
7616
|
+
const current = byUri.get(uri) ?? { parts: [], staleCount: 0, uri };
|
|
7617
|
+
current.parts.push(`${sidecar.relativePath}
|
|
7618
|
+
${sidecar.content}`);
|
|
7619
|
+
current.staleCount += 1;
|
|
7620
|
+
byUri.set(uri, current);
|
|
7621
|
+
}
|
|
7622
|
+
continue;
|
|
7623
|
+
}
|
|
7624
|
+
for (const sidecar of sidecars) {
|
|
7625
|
+
const current = byUri.get(sidecar.uri) ?? { parts: [], staleCount: 0, uri: sidecar.uri };
|
|
7626
|
+
current.parts.push(`${sidecar.relativePath}
|
|
7627
|
+
${sidecar.content}`);
|
|
7628
|
+
current.staleCount += 1;
|
|
7629
|
+
byUri.set(sidecar.uri, current);
|
|
7630
|
+
}
|
|
7631
|
+
}
|
|
7632
|
+
return [...byUri.values()].map((target) => ({
|
|
7633
|
+
signature: sha256([target.uri, ...target.parts.sort()].join("\n---\n")),
|
|
7634
|
+
staleCount: target.staleCount,
|
|
7635
|
+
uri: target.uri
|
|
7636
|
+
}));
|
|
7637
|
+
}
|
|
7638
|
+
function collapseStaleSidecarUri(rootUri, relativePath, depth = 0) {
|
|
7639
|
+
const normalizedRootUri = trimLocalRootUri(rootUri);
|
|
7640
|
+
if (depth <= 0) {
|
|
7641
|
+
return normalizedRootUri;
|
|
7642
|
+
}
|
|
7643
|
+
const parentParts = relativePath.split("/").slice(0, -1);
|
|
7644
|
+
const collapsedParts = parentParts.slice(0, depth);
|
|
7645
|
+
return collapsedParts.length === 0 ? normalizedRootUri : `${normalizedRootUri}/${collapsedParts.join("/")}`;
|
|
7646
|
+
}
|
|
7647
|
+
function formatRecallIndexRepairMessages(result, options = {}) {
|
|
7648
|
+
const messages = [];
|
|
7649
|
+
const maxUris = options.maxUris ?? result.repairedUris.length;
|
|
7650
|
+
for (const uri of result.repairedUris.slice(0, maxUris)) {
|
|
7651
|
+
messages.push(
|
|
7652
|
+
`${options.dryRun === true ? "Would auto-reindex stale recall scope" : "Auto-reindexed stale recall scope"}: ${uri}`
|
|
7653
|
+
);
|
|
7654
|
+
}
|
|
7655
|
+
const hiddenUriCount = result.repairedUris.length - maxUris;
|
|
7656
|
+
if (hiddenUriCount > 0) {
|
|
7657
|
+
messages.push(
|
|
7658
|
+
options.dryRun === true ? `Would auto-reindex ${hiddenUriCount} more stale recall scope(s).` : `Auto-reindexed ${hiddenUriCount} more stale recall scope(s).`
|
|
7659
|
+
);
|
|
7660
|
+
}
|
|
7661
|
+
for (const warning2 of result.warnings) {
|
|
7662
|
+
messages.push(`Auto-index repair warning: ${warning2}`);
|
|
7663
|
+
}
|
|
7664
|
+
return messages;
|
|
7665
|
+
}
|
|
7666
|
+
function filterStaleRecallSummaryRows(output2) {
|
|
7667
|
+
return output2.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
|
|
7668
|
+
}
|
|
7669
|
+
function isStaleRecallSummaryRow(line) {
|
|
7670
|
+
return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
|
|
7671
|
+
}
|
|
7672
|
+
async function scanRoots(config, options) {
|
|
7673
|
+
const accountRoot = (0, import_node_path4.join)(config.agentContextHome, "data", "viking", config.account);
|
|
7674
|
+
const roots = [
|
|
7675
|
+
{
|
|
7676
|
+
path: (0, import_node_path4.join)(accountRoot, "user", uriSegment(config.user), "memories"),
|
|
7677
|
+
uri: `viking://user/${uriSegment(config.user)}/memories`
|
|
7678
|
+
}
|
|
7679
|
+
];
|
|
7680
|
+
const query = options.query;
|
|
7681
|
+
if (query) {
|
|
7682
|
+
const project = await inferProjectFromQuery(config.manifestPath, query);
|
|
7683
|
+
const projectPath = project?.uri.startsWith("viking://resources/") ? (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")) : void 0;
|
|
7684
|
+
if (project && projectPath) {
|
|
7685
|
+
roots.push({ path: projectPath, uri: project.uri });
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
if (options.includeManifestResources === true) {
|
|
7689
|
+
roots.push(...await manifestResourceRoots(config, accountRoot));
|
|
7690
|
+
}
|
|
7691
|
+
const scanAgentSkills = options.includeAgentSkills === true || (query ? /\bskills?\b/.test(query.toLowerCase()) : false);
|
|
7692
|
+
if (scanAgentSkills) {
|
|
7693
|
+
roots.push({ path: (0, import_node_path4.join)(accountRoot, "resources", "agent-skills"), uri: "viking://resources/agent-skills" });
|
|
7694
|
+
}
|
|
7695
|
+
return dedupeRoots(roots);
|
|
7696
|
+
}
|
|
7697
|
+
async function manifestResourceRoots(config, accountRoot) {
|
|
7698
|
+
try {
|
|
7699
|
+
const manifest = await readSeedManifest(config.manifestPath);
|
|
7700
|
+
return manifest.projects.filter((project) => project.uri.startsWith("viking://resources/")).map((project) => ({
|
|
7701
|
+
path: (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")),
|
|
7702
|
+
uri: project.uri
|
|
7703
|
+
}));
|
|
7704
|
+
} catch (_err) {
|
|
7705
|
+
return [];
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
function dedupeRoots(roots) {
|
|
7709
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7710
|
+
const deduped = [];
|
|
7711
|
+
for (const root of roots) {
|
|
7712
|
+
if (seen.has(root.uri)) {
|
|
7713
|
+
continue;
|
|
7714
|
+
}
|
|
7715
|
+
seen.add(root.uri);
|
|
7716
|
+
deduped.push(root);
|
|
7717
|
+
}
|
|
7718
|
+
return deduped;
|
|
7719
|
+
}
|
|
7720
|
+
async function staleSidecars(rootPath, rootUri) {
|
|
7721
|
+
const results = [];
|
|
7722
|
+
async function visit(path, depth) {
|
|
7723
|
+
let entries;
|
|
7724
|
+
try {
|
|
7725
|
+
entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
|
|
7726
|
+
} catch (_err) {
|
|
7727
|
+
return;
|
|
7728
|
+
}
|
|
7729
|
+
for (const entry of entries) {
|
|
7730
|
+
const childPath = (0, import_node_path4.join)(path, entry.name);
|
|
7731
|
+
if (entry.isFile() && isSummarySidecar(entry.name)) {
|
|
7732
|
+
let content;
|
|
7733
|
+
try {
|
|
7734
|
+
content = await (0, import_promises4.readFile)(childPath, "utf8");
|
|
7735
|
+
} catch (_err) {
|
|
7736
|
+
continue;
|
|
7737
|
+
}
|
|
7738
|
+
if (!isStaleSummary(content)) {
|
|
7739
|
+
continue;
|
|
7740
|
+
}
|
|
7741
|
+
const parentPath = (0, import_node_path4.dirname)(childPath);
|
|
7742
|
+
const parentRelative = toPosixPath((0, import_node_path4.relative)(rootPath, parentPath));
|
|
7743
|
+
const relativePath = toPosixPath((0, import_node_path4.relative)(rootPath, childPath));
|
|
7744
|
+
results.push({
|
|
7745
|
+
content: content.trim(),
|
|
7746
|
+
relativePath,
|
|
7747
|
+
uri: parentRelative ? `${trimLocalRootUri(rootUri)}/${parentRelative}` : trimLocalRootUri(rootUri)
|
|
7748
|
+
});
|
|
7749
|
+
} else if (entry.isDirectory() && depth < MAX_SCAN_DEPTH) {
|
|
7750
|
+
await visit(childPath, depth + 1);
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
await visit(rootPath, 0);
|
|
7755
|
+
return results;
|
|
7756
|
+
}
|
|
7757
|
+
function isSummarySidecar(name) {
|
|
7758
|
+
return name === ".abstract.md" || name === ".overview.md";
|
|
7759
|
+
}
|
|
7760
|
+
function isStaleSummary(content) {
|
|
7761
|
+
return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
|
|
7762
|
+
}
|
|
7763
|
+
function trimLocalRootUri(uri) {
|
|
7764
|
+
return uri.endsWith("/") ? uri.slice(0, -1) : uri;
|
|
7765
|
+
}
|
|
7766
|
+
async function readAutoRepairState(config) {
|
|
7767
|
+
const raw = await readFileIfExists(autoRepairStatePath(config));
|
|
7768
|
+
if (!raw) {
|
|
7769
|
+
return { entries: {}, version: 1 };
|
|
7770
|
+
}
|
|
7771
|
+
try {
|
|
7772
|
+
const parsed = JSON.parse(raw);
|
|
7773
|
+
if (!isJsonObject(parsed) || parsed.version !== 1 || !isJsonObject(parsed.entries)) {
|
|
7774
|
+
return { entries: {}, version: 1 };
|
|
7775
|
+
}
|
|
7776
|
+
const entries = {};
|
|
7777
|
+
for (const [uri, entry] of Object.entries(parsed.entries)) {
|
|
7778
|
+
if (isJsonObject(entry) && typeof entry.signature === "string" && typeof entry.repairedAt === "string") {
|
|
7779
|
+
entries[uri] = { repairedAt: entry.repairedAt, signature: entry.signature };
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
return { entries, version: 1 };
|
|
7783
|
+
} catch (_err) {
|
|
7784
|
+
return { entries: {}, version: 1 };
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7787
|
+
async function writeAutoRepairState(config, state) {
|
|
7788
|
+
const path = autoRepairStatePath(config);
|
|
7789
|
+
await ensureDirectory((0, import_node_path4.dirname)(path), false);
|
|
7790
|
+
await (0, import_promises4.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
7791
|
+
`, { encoding: "utf8", mode: 384 });
|
|
7792
|
+
}
|
|
7793
|
+
function autoRepairStatePath(config) {
|
|
7794
|
+
return (0, import_node_path4.join)(config.agentContextHome, AUTO_REPAIR_STATE_FILE);
|
|
7795
|
+
}
|
|
7796
|
+
function indexRepairWarning(uri, result) {
|
|
7797
|
+
return `${uri}: ${result.stderr.trim() || result.stdout.trim() || "reindex failed"}`;
|
|
7798
|
+
}
|
|
7799
|
+
|
|
7800
|
+
// src/memory_hygiene.ts
|
|
7801
|
+
var import_node_path5 = require("node:path");
|
|
7477
7802
|
var HYGIENE_SOURCES_HEADING = "## Threadnote Hygiene Sources";
|
|
7478
7803
|
var STALE_HANDOFF_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
7479
7804
|
function parseMemoryDocument(uri, content) {
|
|
@@ -7766,7 +8091,7 @@ function branchFromBody(body) {
|
|
|
7766
8091
|
return branch?.split(/\s+/)[0]?.replace(/[.,;:]+$/g, "");
|
|
7767
8092
|
}
|
|
7768
8093
|
function topicFromUri(uri) {
|
|
7769
|
-
const name = (0,
|
|
8094
|
+
const name = (0, import_node_path5.basename)(uri).replace(/\.md$/, "");
|
|
7770
8095
|
return name.startsWith("threadnote-") ? void 0 : name;
|
|
7771
8096
|
}
|
|
7772
8097
|
function parseProjectFromUri(uri) {
|
|
@@ -7796,7 +8121,7 @@ function preferredKeepRecord(records, topic) {
|
|
|
7796
8121
|
}
|
|
7797
8122
|
function isStableRecord(record, topic) {
|
|
7798
8123
|
const recordTopic = topic ?? topicForRecord(record);
|
|
7799
|
-
return recordTopic !== void 0 && (0,
|
|
8124
|
+
return recordTopic !== void 0 && (0, import_node_path5.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
|
|
7800
8125
|
}
|
|
7801
8126
|
function sortedNewestFirst(records) {
|
|
7802
8127
|
return [...records].sort((left, right) => {
|
|
@@ -7907,58 +8232,10 @@ function formatPlanSection(title, lines) {
|
|
|
7907
8232
|
return [`${title}:`, ...lines.map((line) => `- ${line}`)].join("\n");
|
|
7908
8233
|
}
|
|
7909
8234
|
|
|
7910
|
-
// src/runtime.ts
|
|
7911
|
-
var import_node_fs3 = require("node:fs");
|
|
7912
|
-
var import_node_os3 = require("node:os");
|
|
7913
|
-
var import_node_path4 = require("node:path");
|
|
7914
|
-
function getRuntimeConfig(program2, manifestOverride) {
|
|
7915
|
-
const options = program2.opts();
|
|
7916
|
-
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
7917
|
-
const manifestPath = expandPath(
|
|
7918
|
-
manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
|
|
7919
|
-
);
|
|
7920
|
-
return {
|
|
7921
|
-
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
7922
|
-
agentContextHome: threadnoteHome,
|
|
7923
|
-
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
7924
|
-
host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
|
|
7925
|
-
manifestPath,
|
|
7926
|
-
openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
|
|
7927
|
-
port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
|
|
7928
|
-
user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
|
|
7929
|
-
};
|
|
7930
|
-
}
|
|
7931
|
-
function defaultManifestPath(agentContextHome) {
|
|
7932
|
-
const userManifest = (0, import_node_path4.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
7933
|
-
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
7934
|
-
}
|
|
7935
|
-
function builtInExampleManifestPath() {
|
|
7936
|
-
return (0, import_node_path4.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
7937
|
-
}
|
|
7938
|
-
function openVikingHealthUrl(config) {
|
|
7939
|
-
return `http://${config.host}:${config.port}/health`;
|
|
7940
|
-
}
|
|
7941
|
-
function openVikingLogPath(config) {
|
|
7942
|
-
return (0, import_node_path4.join)(config.agentContextHome, "logs", "server.log");
|
|
7943
|
-
}
|
|
7944
|
-
function openVikingServerArgs(config) {
|
|
7945
|
-
return ["--config", (0, import_node_path4.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7946
|
-
}
|
|
7947
|
-
function withIdentity(config, args) {
|
|
7948
|
-
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
7949
|
-
}
|
|
7950
|
-
function renderTemplate(template, config, extras = {}) {
|
|
7951
|
-
let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
|
|
7952
|
-
for (const [key, value] of Object.entries(extras)) {
|
|
7953
|
-
rendered = rendered.replaceAll(`{{${key}}}`, () => value);
|
|
7954
|
-
}
|
|
7955
|
-
return rendered;
|
|
7956
|
-
}
|
|
7957
|
-
|
|
7958
8235
|
// src/share.ts
|
|
7959
|
-
var
|
|
8236
|
+
var import_promises5 = require("node:fs/promises");
|
|
7960
8237
|
var import_node_os4 = require("node:os");
|
|
7961
|
-
var
|
|
8238
|
+
var import_node_path6 = require("node:path");
|
|
7962
8239
|
var TEAMS_FILE_VERSION = 1;
|
|
7963
8240
|
var SHARED_SEGMENT = "shared";
|
|
7964
8241
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
@@ -8027,8 +8304,8 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8027
8304
|
if (await exists(gitdir)) {
|
|
8028
8305
|
throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
|
|
8029
8306
|
}
|
|
8030
|
-
await ensureDirectory((0,
|
|
8031
|
-
await ensureDirectory((0,
|
|
8307
|
+
await ensureDirectory((0, import_node_path6.dirname)(worktree), dryRun);
|
|
8308
|
+
await ensureDirectory((0, import_node_path6.dirname)(gitdir), dryRun);
|
|
8032
8309
|
const git = await requiredExecutable("git");
|
|
8033
8310
|
await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
|
|
8034
8311
|
const newConfig = {
|
|
@@ -8059,7 +8336,7 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8059
8336
|
var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
|
|
8060
8337
|
var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
|
|
8061
8338
|
async function ensureSharedGitignore(worktree, git, push) {
|
|
8062
|
-
const gitignorePath = (0,
|
|
8339
|
+
const gitignorePath = (0, import_node_path6.join)(worktree, ".gitignore");
|
|
8063
8340
|
const existing = await readFileIfExists(gitignorePath) ?? "";
|
|
8064
8341
|
const lines = existing.split("\n").map((line) => line.trim());
|
|
8065
8342
|
const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
@@ -8078,7 +8355,7 @@ async function ensureSharedGitignore(worktree, git, push) {
|
|
|
8078
8355
|
segments.push(SHARED_GITIGNORE_HEADER, "\n");
|
|
8079
8356
|
}
|
|
8080
8357
|
segments.push(missingPatterns.join("\n"), "\n");
|
|
8081
|
-
await (0,
|
|
8358
|
+
await (0, import_promises5.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
|
|
8082
8359
|
console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
|
|
8083
8360
|
await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
|
|
8084
8361
|
const commitResult = await runCommand(
|
|
@@ -8160,7 +8437,7 @@ function autoShareState(config) {
|
|
|
8160
8437
|
return state;
|
|
8161
8438
|
}
|
|
8162
8439
|
function pendingReindexesPath(config) {
|
|
8163
|
-
return (0,
|
|
8440
|
+
return (0, import_node_path6.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
|
|
8164
8441
|
}
|
|
8165
8442
|
async function loadPendingReindexes(config, state) {
|
|
8166
8443
|
const raw = await readFileIfExists(pendingReindexesPath(config));
|
|
@@ -8197,18 +8474,18 @@ async function loadPendingReindexes(config, state) {
|
|
|
8197
8474
|
async function writePendingReindexes(config, state) {
|
|
8198
8475
|
const path = pendingReindexesPath(config);
|
|
8199
8476
|
if (state.pendingReindexes.size === 0) {
|
|
8200
|
-
await (0,
|
|
8477
|
+
await (0, import_promises5.rm)(path, { force: true });
|
|
8201
8478
|
return;
|
|
8202
8479
|
}
|
|
8203
8480
|
const contents = {
|
|
8204
8481
|
teams: Object.fromEntries(state.pendingReindexes),
|
|
8205
8482
|
version: 1
|
|
8206
8483
|
};
|
|
8207
|
-
await (0,
|
|
8484
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
|
|
8208
8485
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
8209
|
-
await (0,
|
|
8486
|
+
await (0, import_promises5.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
|
|
8210
8487
|
`, { encoding: "utf8", mode: 384 });
|
|
8211
|
-
await (0,
|
|
8488
|
+
await (0, import_promises5.rename)(tempPath, path);
|
|
8212
8489
|
}
|
|
8213
8490
|
async function refreshShareUpdateStateLocked(config, state, options) {
|
|
8214
8491
|
const now = Date.now();
|
|
@@ -8297,7 +8574,7 @@ async function runShareSync(config, options) {
|
|
|
8297
8574
|
if (dryRun) {
|
|
8298
8575
|
console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
|
|
8299
8576
|
} else if (pullResult && pullResult.exitCode !== 0) {
|
|
8300
|
-
if (await exists((0,
|
|
8577
|
+
if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
|
|
8301
8578
|
throw new Error(
|
|
8302
8579
|
`git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
|
|
8303
8580
|
Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
|
|
@@ -8359,7 +8636,7 @@ async function runShareSyncQuiet(config, state, options) {
|
|
|
8359
8636
|
const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], false);
|
|
8360
8637
|
const pullResult = await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
|
|
8361
8638
|
if (pullResult.exitCode !== 0) {
|
|
8362
|
-
if (await exists((0,
|
|
8639
|
+
if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
|
|
8363
8640
|
throw new Error(
|
|
8364
8641
|
`Automatic share sync hit git conflicts in ${worktree}. Resolve them in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then rerun recall/read.`
|
|
8365
8642
|
);
|
|
@@ -8598,8 +8875,8 @@ async function runShareRename(config, options) {
|
|
|
8598
8875
|
console.log(`Would write teams file: ${teamsFilePath(config)}`);
|
|
8599
8876
|
return;
|
|
8600
8877
|
}
|
|
8601
|
-
await (0,
|
|
8602
|
-
await (0,
|
|
8878
|
+
await (0, import_promises5.rename)(oldTeam.config.worktree, newWorktree);
|
|
8879
|
+
await (0, import_promises5.rename)(oldTeam.config.gitdir, newGitdir);
|
|
8603
8880
|
await writeTeamsFile(config, updatedFile);
|
|
8604
8881
|
const git = await requiredExecutable("git");
|
|
8605
8882
|
await runCommand(git, ["-C", newWorktree, "config", "core.worktree", newWorktree]);
|
|
@@ -8681,12 +8958,12 @@ async function preserveSharedMemoriesLocally(config, team, dryRun) {
|
|
|
8681
8958
|
const files = await walkMemoryFiles(team.worktree);
|
|
8682
8959
|
let preserved = 0;
|
|
8683
8960
|
for (const file of files) {
|
|
8684
|
-
const rel = (0,
|
|
8961
|
+
const rel = (0, import_node_path6.relative)(team.worktree, file).split(import_node_path6.sep).join("/");
|
|
8685
8962
|
if (!rel.startsWith("durable/")) {
|
|
8686
8963
|
continue;
|
|
8687
8964
|
}
|
|
8688
8965
|
const targetUri = `viking://user/${uriSegment(config.user)}/memories/${rel}`;
|
|
8689
|
-
const content = await (0,
|
|
8966
|
+
const content = await (0, import_promises5.readFile)(file, "utf8");
|
|
8690
8967
|
if (dryRun) {
|
|
8691
8968
|
console.log(`Would preserve ${rel} -> ${targetUri}`);
|
|
8692
8969
|
} else {
|
|
@@ -8725,10 +9002,10 @@ function normalizeTeamName(input2) {
|
|
|
8725
9002
|
return candidate;
|
|
8726
9003
|
}
|
|
8727
9004
|
function teamsFilePath(config) {
|
|
8728
|
-
return (0,
|
|
9005
|
+
return (0, import_node_path6.join)(config.agentContextHome, "share", "teams.json");
|
|
8729
9006
|
}
|
|
8730
9007
|
function teamWorktreePath(config, team) {
|
|
8731
|
-
return (0,
|
|
9008
|
+
return (0, import_node_path6.join)(
|
|
8732
9009
|
config.agentContextHome,
|
|
8733
9010
|
"data",
|
|
8734
9011
|
"viking",
|
|
@@ -8741,7 +9018,7 @@ function teamWorktreePath(config, team) {
|
|
|
8741
9018
|
);
|
|
8742
9019
|
}
|
|
8743
9020
|
function teamGitdirPath(config, team) {
|
|
8744
|
-
return (0,
|
|
9021
|
+
return (0, import_node_path6.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
8745
9022
|
}
|
|
8746
9023
|
async function readTeamsFile(config) {
|
|
8747
9024
|
const path = teamsFilePath(config);
|
|
@@ -8784,13 +9061,13 @@ async function readTeamsFile(config) {
|
|
|
8784
9061
|
}
|
|
8785
9062
|
async function writeTeamsFile(config, contents) {
|
|
8786
9063
|
const path = teamsFilePath(config);
|
|
8787
|
-
await (0,
|
|
9064
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
|
|
8788
9065
|
const serializable = {
|
|
8789
9066
|
defaultTeam: contents.defaultTeam,
|
|
8790
9067
|
teams: contents.teams,
|
|
8791
9068
|
version: contents.version
|
|
8792
9069
|
};
|
|
8793
|
-
await (0,
|
|
9070
|
+
await (0, import_promises5.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
|
|
8794
9071
|
`, { encoding: "utf8", mode: 384 });
|
|
8795
9072
|
}
|
|
8796
9073
|
async function resolveTeam(config, requested) {
|
|
@@ -8820,7 +9097,7 @@ async function assertWorktreeUsable(worktree) {
|
|
|
8820
9097
|
if (!await isDirectory(worktree)) {
|
|
8821
9098
|
throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
|
|
8822
9099
|
}
|
|
8823
|
-
const entries = await (0,
|
|
9100
|
+
const entries = await (0, import_promises5.readdir)(worktree);
|
|
8824
9101
|
if (entries.length > 0) {
|
|
8825
9102
|
const preview = entries.slice(0, 5).join(", ");
|
|
8826
9103
|
const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
|
|
@@ -8844,7 +9121,7 @@ async function walkMemoryFiles(root) {
|
|
|
8844
9121
|
async function visit(path, depth) {
|
|
8845
9122
|
let entries;
|
|
8846
9123
|
try {
|
|
8847
|
-
entries = await (0,
|
|
9124
|
+
entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
|
|
8848
9125
|
} catch (err) {
|
|
8849
9126
|
console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
|
|
8850
9127
|
return;
|
|
@@ -8853,7 +9130,7 @@ async function walkMemoryFiles(root) {
|
|
|
8853
9130
|
if (entry.name === ".git") {
|
|
8854
9131
|
continue;
|
|
8855
9132
|
}
|
|
8856
|
-
const full = (0,
|
|
9133
|
+
const full = (0, import_node_path6.join)(path, entry.name);
|
|
8857
9134
|
if (entry.isDirectory()) {
|
|
8858
9135
|
if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
|
|
8859
9136
|
continue;
|
|
@@ -8877,7 +9154,7 @@ async function walkMemoryFiles(root) {
|
|
|
8877
9154
|
return out;
|
|
8878
9155
|
}
|
|
8879
9156
|
function workfileToVikingUri(config, team, filePath) {
|
|
8880
|
-
const rel = (0,
|
|
9157
|
+
const rel = (0, import_node_path6.relative)(team.worktree, filePath).split(import_node_path6.sep).join("/");
|
|
8881
9158
|
return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
8882
9159
|
}
|
|
8883
9160
|
function isInSharedNamespace(config, uri) {
|
|
@@ -9025,13 +9302,14 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun, op
|
|
|
9025
9302
|
}
|
|
9026
9303
|
return;
|
|
9027
9304
|
}
|
|
9028
|
-
const stagingDir = await (0,
|
|
9029
|
-
const tempPath = (0,
|
|
9305
|
+
const stagingDir = await (0, import_promises5.mkdtemp)((0, import_node_path6.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
|
|
9306
|
+
const tempPath = (0, import_node_path6.join)(stagingDir, "body.txt");
|
|
9030
9307
|
try {
|
|
9031
|
-
await (0,
|
|
9308
|
+
await (0, import_promises5.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
9032
9309
|
await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode, options);
|
|
9310
|
+
await refreshMemoryIndex(config, ov, uri, options);
|
|
9033
9311
|
} finally {
|
|
9034
|
-
await (0,
|
|
9312
|
+
await (0, import_promises5.rm)(stagingDir, { force: true, recursive: true });
|
|
9035
9313
|
}
|
|
9036
9314
|
}
|
|
9037
9315
|
var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
|
|
@@ -9086,6 +9364,27 @@ async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode, opti
|
|
|
9086
9364
|
}
|
|
9087
9365
|
}
|
|
9088
9366
|
}
|
|
9367
|
+
async function refreshMemoryIndex(config, ov, uri, options = {}) {
|
|
9368
|
+
const result = await runCommand(
|
|
9369
|
+
ov,
|
|
9370
|
+
withIdentity(config, ["reindex", uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
9371
|
+
{ allowFailure: true }
|
|
9372
|
+
);
|
|
9373
|
+
if (result.exitCode === 0) {
|
|
9374
|
+
if (options.quiet !== true && result.stdout.trim()) {
|
|
9375
|
+
console.log(result.stdout.trim());
|
|
9376
|
+
}
|
|
9377
|
+
if (options.quiet !== true && result.stderr.trim()) {
|
|
9378
|
+
console.error(result.stderr.trim());
|
|
9379
|
+
}
|
|
9380
|
+
return;
|
|
9381
|
+
}
|
|
9382
|
+
if (options.quiet !== true) {
|
|
9383
|
+
console.error(
|
|
9384
|
+
`Memory stored, but index refresh failed for ${uri}: ${result.stderr.trim() || result.stdout.trim()}`
|
|
9385
|
+
);
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9089
9388
|
async function waitForOvQueue(ov, config, options = {}) {
|
|
9090
9389
|
const result = await runCommand(ov, withIdentity(config, ["wait", "--timeout", "120"]), { allowFailure: true });
|
|
9091
9390
|
if (options.quiet !== true && result.stdout.trim()) {
|
|
@@ -9106,7 +9405,7 @@ ${stdout2}`.toLowerCase();
|
|
|
9106
9405
|
return output2.includes("resource is busy") || output2.includes("resource is being processed");
|
|
9107
9406
|
}
|
|
9108
9407
|
async function ingestSingleFile(ov, config, uri, filePath, initialMode, options = {}) {
|
|
9109
|
-
const content = await (0,
|
|
9408
|
+
const content = await (0, import_promises5.readFile)(filePath, "utf8");
|
|
9110
9409
|
await writeMemoryFile(config, ov, uri, content, initialMode, false, options);
|
|
9111
9410
|
}
|
|
9112
9411
|
async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
|
|
@@ -9171,8 +9470,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9171
9470
|
const oldRel = entries[index + 1];
|
|
9172
9471
|
const newRel = entries[index + 2];
|
|
9173
9472
|
if (oldRel && newRel) {
|
|
9174
|
-
changes.push({ path: (0,
|
|
9175
|
-
changes.push({ path: (0,
|
|
9473
|
+
changes.push({ path: (0, import_node_path6.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
|
|
9474
|
+
changes.push({ path: (0, import_node_path6.join)(worktree, newRel), relativePath: newRel, status: "added" });
|
|
9176
9475
|
}
|
|
9177
9476
|
index += 3;
|
|
9178
9477
|
continue;
|
|
@@ -9180,7 +9479,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9180
9479
|
const rel = entries[index + 1];
|
|
9181
9480
|
if (rel) {
|
|
9182
9481
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
9183
|
-
changes.push({ path: (0,
|
|
9482
|
+
changes.push({ path: (0, import_node_path6.join)(worktree, rel), relativePath: rel, status });
|
|
9184
9483
|
}
|
|
9185
9484
|
index += 2;
|
|
9186
9485
|
}
|
|
@@ -9302,7 +9601,7 @@ async function runMigrateMemories(config, options) {
|
|
|
9302
9601
|
const candidates = await legacyMemoryCandidates(config, sourceAccounts);
|
|
9303
9602
|
const existingHashes = await existingDurableMemoryHashes(config);
|
|
9304
9603
|
const ov = await openVikingCliForMode(dryRun);
|
|
9305
|
-
const migrationPath = (0,
|
|
9604
|
+
const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "legacy-memory-migration.txt");
|
|
9306
9605
|
let duplicateCount = 0;
|
|
9307
9606
|
let migratedCount = 0;
|
|
9308
9607
|
let sensitiveCount = 0;
|
|
@@ -9338,8 +9637,8 @@ async function runMigrateMemories(config, options) {
|
|
|
9338
9637
|
}
|
|
9339
9638
|
console.log(`${dryRun ? "Would migrate" : "Migrating"} ${legacySourceLabel(candidate)} -> ${memoryUri}`);
|
|
9340
9639
|
if (!dryRun) {
|
|
9341
|
-
await (0,
|
|
9342
|
-
await (0,
|
|
9640
|
+
await (0, import_promises6.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
|
|
9641
|
+
await (0, import_promises6.chmod)(migrationPath, 384);
|
|
9343
9642
|
await writeDurableMemoryFile(ov, config, memoryUri, migrationPath, "create");
|
|
9344
9643
|
existingHashes.add(candidate.hash);
|
|
9345
9644
|
}
|
|
@@ -9347,7 +9646,7 @@ async function runMigrateMemories(config, options) {
|
|
|
9347
9646
|
}
|
|
9348
9647
|
} finally {
|
|
9349
9648
|
if (!dryRun) {
|
|
9350
|
-
await (0,
|
|
9649
|
+
await (0, import_promises6.rm)(migrationPath, { force: true });
|
|
9351
9650
|
}
|
|
9352
9651
|
}
|
|
9353
9652
|
console.log(
|
|
@@ -9365,7 +9664,7 @@ async function runMigrateLifecycle(config, options) {
|
|
|
9365
9664
|
const limit = options.limit ? parsePositiveInteger(options.limit, "lifecycle migration limit") : void 0;
|
|
9366
9665
|
const ov = await openVikingCliForMode(dryRun);
|
|
9367
9666
|
const candidates = await legacyLifecycleHandoffCandidates(config);
|
|
9368
|
-
const migrationPath = (0,
|
|
9667
|
+
const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
|
|
9369
9668
|
let existingCount = 0;
|
|
9370
9669
|
let migratedCount = 0;
|
|
9371
9670
|
let skippedCount = 0;
|
|
@@ -9386,8 +9685,8 @@ async function runMigrateLifecycle(config, options) {
|
|
|
9386
9685
|
existingCount += 1;
|
|
9387
9686
|
console.log(`Archived copy already exists; cleaning up legacy source: ${candidate.sourceUri}`);
|
|
9388
9687
|
} else {
|
|
9389
|
-
await (0,
|
|
9390
|
-
await (0,
|
|
9688
|
+
await (0, import_promises6.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
|
|
9689
|
+
await (0, import_promises6.chmod)(migrationPath, 384);
|
|
9391
9690
|
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, candidate.metadata));
|
|
9392
9691
|
await writeDurableMemoryFile(ov, config, destinationUri, migrationPath, "create");
|
|
9393
9692
|
}
|
|
@@ -9403,7 +9702,7 @@ async function runMigrateLifecycle(config, options) {
|
|
|
9403
9702
|
}
|
|
9404
9703
|
} finally {
|
|
9405
9704
|
if (!dryRun) {
|
|
9406
|
-
await (0,
|
|
9705
|
+
await (0, import_promises6.rm)(migrationPath, { force: true });
|
|
9407
9706
|
}
|
|
9408
9707
|
}
|
|
9409
9708
|
console.log(
|
|
@@ -9423,6 +9722,19 @@ async function runRecall(config, options) {
|
|
|
9423
9722
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
9424
9723
|
const query = await enrichRecallQueryWithWorkspaceContext(options.query);
|
|
9425
9724
|
const projectQuery = await enrichRecallQueryWithWorkspaceProjectContext(options.query);
|
|
9725
|
+
let indexRepairMessages;
|
|
9726
|
+
try {
|
|
9727
|
+
const indexRepair = await repairStaleRecallIndex(config, ov, {
|
|
9728
|
+
dryRun: options.dryRun === true,
|
|
9729
|
+
query: projectQuery
|
|
9730
|
+
});
|
|
9731
|
+
indexRepairMessages = formatRecallIndexRepairMessages(indexRepair, { dryRun: options.dryRun === true });
|
|
9732
|
+
} catch (err) {
|
|
9733
|
+
indexRepairMessages = [`Auto-index repair warning: ${err instanceof Error ? err.message : String(err)}`];
|
|
9734
|
+
}
|
|
9735
|
+
for (const message of indexRepairMessages) {
|
|
9736
|
+
console.log(message);
|
|
9737
|
+
}
|
|
9426
9738
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
9427
9739
|
const args = ["search", query];
|
|
9428
9740
|
if (inferredUri) {
|
|
@@ -9433,9 +9745,9 @@ async function runRecall(config, options) {
|
|
|
9433
9745
|
args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
|
|
9434
9746
|
}
|
|
9435
9747
|
const recallOutputs = [];
|
|
9436
|
-
const
|
|
9437
|
-
if (
|
|
9438
|
-
recallOutputs.push(
|
|
9748
|
+
const baseOutput = await runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
|
|
9749
|
+
if (baseOutput) {
|
|
9750
|
+
recallOutputs.push(baseOutput);
|
|
9439
9751
|
}
|
|
9440
9752
|
const seededOutput = await augmentRecallWithSeededResources(
|
|
9441
9753
|
config,
|
|
@@ -9474,8 +9786,20 @@ async function augmentRecallWithSeededResources(config, ov, options, inferredUri
|
|
|
9474
9786
|
}
|
|
9475
9787
|
console.log(`
|
|
9476
9788
|
Also searching seeded resources: ${projectResourceUri}`);
|
|
9477
|
-
|
|
9478
|
-
|
|
9789
|
+
return runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
|
|
9790
|
+
}
|
|
9791
|
+
async function runRecallSearch(config, ov, args, options) {
|
|
9792
|
+
const fullArgs = withIdentity(config, args);
|
|
9793
|
+
console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, fullArgs)}`);
|
|
9794
|
+
if (options.dryRun) {
|
|
9795
|
+
return void 0;
|
|
9796
|
+
}
|
|
9797
|
+
const result = await runCommand(ov, fullArgs);
|
|
9798
|
+
const output2 = filterStaleRecallSummaryRows([result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n"));
|
|
9799
|
+
if (output2) {
|
|
9800
|
+
console.log(output2);
|
|
9801
|
+
}
|
|
9802
|
+
return output2 || void 0;
|
|
9479
9803
|
}
|
|
9480
9804
|
async function runRead(config, uri, options) {
|
|
9481
9805
|
assertVikingUri(uri);
|
|
@@ -9541,15 +9865,15 @@ async function runCompact(config, options) {
|
|
|
9541
9865
|
return;
|
|
9542
9866
|
}
|
|
9543
9867
|
const ov = await openVikingCliForMode(false);
|
|
9544
|
-
const updatePath = (0,
|
|
9868
|
+
const updatePath = (0, import_node_path7.join)(config.agentContextHome, "compact-memory-update.txt");
|
|
9545
9869
|
try {
|
|
9546
9870
|
for (const action of plan.keepUpdates) {
|
|
9547
|
-
await (0,
|
|
9548
|
-
await (0,
|
|
9871
|
+
await (0, import_promises6.writeFile)(updatePath, action.content, { encoding: "utf8", mode: 384 });
|
|
9872
|
+
await (0, import_promises6.chmod)(updatePath, 384);
|
|
9549
9873
|
await writeDurableMemoryFile(ov, config, action.uri, updatePath, "replace");
|
|
9550
9874
|
}
|
|
9551
9875
|
} finally {
|
|
9552
|
-
await (0,
|
|
9876
|
+
await (0, import_promises6.rm)(updatePath, { force: true });
|
|
9553
9877
|
}
|
|
9554
9878
|
for (const action of plan.archives) {
|
|
9555
9879
|
await runArchive(config, action.uri, {
|
|
@@ -9605,7 +9929,7 @@ async function scopedCompactRecords(config, options) {
|
|
|
9605
9929
|
const uriDirectory = memoryUriDirectoryForCompact(config, kind, options.project);
|
|
9606
9930
|
let entries;
|
|
9607
9931
|
try {
|
|
9608
|
-
entries = await (0,
|
|
9932
|
+
entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
|
|
9609
9933
|
} catch (_err) {
|
|
9610
9934
|
continue;
|
|
9611
9935
|
}
|
|
@@ -9613,7 +9937,7 @@ async function scopedCompactRecords(config, options) {
|
|
|
9613
9937
|
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
9614
9938
|
continue;
|
|
9615
9939
|
}
|
|
9616
|
-
const content = await readTextIfExists((0,
|
|
9940
|
+
const content = await readTextIfExists((0, import_node_path7.join)(directory, entry.name));
|
|
9617
9941
|
if (!content) {
|
|
9618
9942
|
continue;
|
|
9619
9943
|
}
|
|
@@ -9651,11 +9975,11 @@ function localMemoryDirectoryForCompact(config, kind, project) {
|
|
|
9651
9975
|
const projectSegment = uriSegment(project);
|
|
9652
9976
|
switch (kind) {
|
|
9653
9977
|
case "durable":
|
|
9654
|
-
return (0,
|
|
9978
|
+
return (0, import_node_path7.join)(root, "durable", "projects", projectSegment);
|
|
9655
9979
|
case "handoff":
|
|
9656
|
-
return (0,
|
|
9980
|
+
return (0, import_node_path7.join)(root, "handoffs", "active", projectSegment);
|
|
9657
9981
|
case "incident":
|
|
9658
|
-
return (0,
|
|
9982
|
+
return (0, import_node_path7.join)(root, "incidents", "active", projectSegment);
|
|
9659
9983
|
}
|
|
9660
9984
|
}
|
|
9661
9985
|
function memoryUriDirectoryForCompact(config, kind, project) {
|
|
@@ -9675,11 +9999,11 @@ function localMemoryPathForUri(config, uri) {
|
|
|
9675
9999
|
if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
|
|
9676
10000
|
return void 0;
|
|
9677
10001
|
}
|
|
9678
|
-
const
|
|
9679
|
-
if (
|
|
10002
|
+
const relative5 = uri.slice(prefix.length);
|
|
10003
|
+
if (relative5.includes("..") || relative5.startsWith("/")) {
|
|
9680
10004
|
return void 0;
|
|
9681
10005
|
}
|
|
9682
|
-
return (0,
|
|
10006
|
+
return (0, import_node_path7.join)(localUserMemoriesRoot(config), ...relative5.split("/"));
|
|
9683
10007
|
}
|
|
9684
10008
|
async function runList(config, uri, options) {
|
|
9685
10009
|
assertVikingUri(uri);
|
|
@@ -9773,7 +10097,7 @@ async function runForget(config, uri, options) {
|
|
|
9773
10097
|
}
|
|
9774
10098
|
async function runExportPack(config, options) {
|
|
9775
10099
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
9776
|
-
const defaultPath = (0,
|
|
10100
|
+
const defaultPath = (0, import_node_path7.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
|
|
9777
10101
|
const outputPath = expandPath(options.path ?? defaultPath);
|
|
9778
10102
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, ["export", options.uri ?? "viking://", outputPath]));
|
|
9779
10103
|
}
|
|
@@ -9789,12 +10113,25 @@ async function runImportPack(config, options) {
|
|
|
9789
10113
|
);
|
|
9790
10114
|
}
|
|
9791
10115
|
async function inferRecallUri(config, query) {
|
|
9792
|
-
if (
|
|
10116
|
+
if (!hasAgentSkillCatalogIntent(query)) {
|
|
9793
10117
|
return void 0;
|
|
9794
10118
|
}
|
|
9795
10119
|
const project = await inferProjectFromQuery(config.manifestPath, query);
|
|
9796
10120
|
return project ? `viking://resources/agent-skills/repo-local-${uriSegment(project.name)}` : "viking://resources/agent-skills";
|
|
9797
10121
|
}
|
|
10122
|
+
function hasAgentSkillCatalogIntent(query) {
|
|
10123
|
+
const normalized = query.toLowerCase();
|
|
10124
|
+
if (!/\bskills?\b/.test(normalized)) {
|
|
10125
|
+
return false;
|
|
10126
|
+
}
|
|
10127
|
+
if (/\bseed[- ]skills?\b/.test(normalized) || /\bskills?\s+seed(?:ing)?\b/.test(normalized)) {
|
|
10128
|
+
return false;
|
|
10129
|
+
}
|
|
10130
|
+
if (/^\s*skills?\s*$/.test(normalized)) {
|
|
10131
|
+
return true;
|
|
10132
|
+
}
|
|
10133
|
+
return /\b(find|list|show|search|recall|use|choose|select)\b.{0,48}\bskills?\b/.test(normalized) || /\bskills?\b.{0,48}\b(for|to|that|which|about)\b/.test(normalized);
|
|
10134
|
+
}
|
|
9798
10135
|
async function printExactMemoryMatches(config, ov, query, options) {
|
|
9799
10136
|
const terms = exactRecallTerms(query);
|
|
9800
10137
|
if (terms.length === 0) {
|
|
@@ -9833,7 +10170,7 @@ async function storeMemory(config, options) {
|
|
|
9833
10170
|
await storeSharedMemoryReplacement(config, ov, options, options.replaceUri);
|
|
9834
10171
|
return;
|
|
9835
10172
|
}
|
|
9836
|
-
const memoryPath = (0,
|
|
10173
|
+
const memoryPath = (0, import_node_path7.join)(config.agentContextHome, "last-memory.txt");
|
|
9837
10174
|
const candidateMetadata = { ...options.metadata, supersedes: options.replaceUri };
|
|
9838
10175
|
const candidateMemory = formatMemoryDocument2(options.title, candidateMetadata, options.bodyText);
|
|
9839
10176
|
const memoryUri = memoryUriFor(config, candidateMemory, candidateMetadata);
|
|
@@ -9865,8 +10202,8 @@ async function storeMemory(config, options) {
|
|
|
9865
10202
|
}
|
|
9866
10203
|
return;
|
|
9867
10204
|
}
|
|
9868
|
-
await (0,
|
|
9869
|
-
await (0,
|
|
10205
|
+
await (0, import_promises6.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
|
|
10206
|
+
await (0, import_promises6.chmod)(memoryPath, 384);
|
|
9870
10207
|
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, finalMetadata));
|
|
9871
10208
|
await writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode);
|
|
9872
10209
|
console.log(`Stored memory: ${memoryUri}`);
|
|
@@ -9925,7 +10262,7 @@ async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
|
|
|
9925
10262
|
console.log(`Updated shared memory: ${targetUri}`);
|
|
9926
10263
|
}
|
|
9927
10264
|
async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode) {
|
|
9928
|
-
const content = await (0,
|
|
10265
|
+
const content = await (0, import_promises6.readFile)(memoryPath, "utf8");
|
|
9929
10266
|
await writeMemoryFile(config, ov, memoryUri, content, writeMode, false);
|
|
9930
10267
|
}
|
|
9931
10268
|
async function removeVikingResourceWithRetry(ov, config, uri) {
|
|
@@ -9982,10 +10319,10 @@ async function hasLegacyLifecycleHandoffCandidates(config) {
|
|
|
9982
10319
|
return (await legacyLifecycleHandoffCandidates(config, 1)).length > 0;
|
|
9983
10320
|
}
|
|
9984
10321
|
async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
9985
|
-
const eventsRoot = (0,
|
|
10322
|
+
const eventsRoot = (0, import_node_path7.join)(localUserMemoriesRoot(config), "events");
|
|
9986
10323
|
let entries;
|
|
9987
10324
|
try {
|
|
9988
|
-
entries = await (0,
|
|
10325
|
+
entries = await (0, import_promises6.readdir)(eventsRoot, { withFileTypes: true });
|
|
9989
10326
|
} catch (_err) {
|
|
9990
10327
|
return [];
|
|
9991
10328
|
}
|
|
@@ -9994,7 +10331,7 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
|
9994
10331
|
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
9995
10332
|
continue;
|
|
9996
10333
|
}
|
|
9997
|
-
const sourcePath = (0,
|
|
10334
|
+
const sourcePath = (0, import_node_path7.join)(eventsRoot, entry.name);
|
|
9998
10335
|
const original = await readTextIfExists(sourcePath);
|
|
9999
10336
|
if (!original || !isClearLegacyHandoffMemory(original) || sensitiveMemoryReason(original)) {
|
|
10000
10337
|
continue;
|
|
@@ -10135,7 +10472,7 @@ function inferLegacyProject(memory) {
|
|
|
10135
10472
|
return "general";
|
|
10136
10473
|
}
|
|
10137
10474
|
const trimmed = explicit.trim().replace(/[`.,;]+$/g, "");
|
|
10138
|
-
return trimmed.includes("/") ? (0,
|
|
10475
|
+
return trimmed.includes("/") ? (0, import_node_path7.basename)(trimmed) : trimmed;
|
|
10139
10476
|
}
|
|
10140
10477
|
function parseOptionalMemoryKind2(value) {
|
|
10141
10478
|
if (!value) {
|
|
@@ -10175,14 +10512,14 @@ async function legacySourceAccounts(config, options) {
|
|
|
10175
10512
|
async function legacyMemoryCandidates(config, sourceAccounts) {
|
|
10176
10513
|
const candidates = [];
|
|
10177
10514
|
for (const sourceAccount of sourceAccounts) {
|
|
10178
|
-
const sessionRoot = (0,
|
|
10515
|
+
const sessionRoot = (0, import_node_path7.join)(localVikingDataRoot(config), sourceAccount, "session");
|
|
10179
10516
|
for (const sourceSession of await childDirectoryNames(sessionRoot)) {
|
|
10180
|
-
const historyRoot = (0,
|
|
10517
|
+
const historyRoot = (0, import_node_path7.join)(sessionRoot, sourceSession, "history");
|
|
10181
10518
|
for (const sourceArchive of await childDirectoryNames(historyRoot)) {
|
|
10182
10519
|
if (!sourceArchive.startsWith("archive_")) {
|
|
10183
10520
|
continue;
|
|
10184
10521
|
}
|
|
10185
|
-
const sourcePath = (0,
|
|
10522
|
+
const sourcePath = (0, import_node_path7.join)(historyRoot, sourceArchive, "messages.jsonl");
|
|
10186
10523
|
for (const text of await legacyMemoryTexts(sourcePath)) {
|
|
10187
10524
|
candidates.push({
|
|
10188
10525
|
comparableHash: sha256(comparableMemoryText(text)),
|
|
@@ -10245,12 +10582,12 @@ async function existingDurableMemoryHashes(config) {
|
|
|
10245
10582
|
async function collectDurableMemoryHashes(root, hashes) {
|
|
10246
10583
|
let entries;
|
|
10247
10584
|
try {
|
|
10248
|
-
entries = await (0,
|
|
10585
|
+
entries = await (0, import_promises6.readdir)(root, { withFileTypes: true });
|
|
10249
10586
|
} catch (_err) {
|
|
10250
10587
|
return;
|
|
10251
10588
|
}
|
|
10252
10589
|
for (const entry of entries) {
|
|
10253
|
-
const path = (0,
|
|
10590
|
+
const path = (0, import_node_path7.join)(root, entry.name);
|
|
10254
10591
|
if (entry.isDirectory()) {
|
|
10255
10592
|
await collectDurableMemoryHashes(path, hashes);
|
|
10256
10593
|
continue;
|
|
@@ -10267,12 +10604,12 @@ async function collectDurableMemoryHashes(root, hashes) {
|
|
|
10267
10604
|
}
|
|
10268
10605
|
}
|
|
10269
10606
|
function isDurableMemoryPath(path) {
|
|
10270
|
-
return path.split(
|
|
10607
|
+
return path.split(import_node_path7.sep).includes("memories");
|
|
10271
10608
|
}
|
|
10272
10609
|
async function childDirectoryNames(path) {
|
|
10273
10610
|
let entries;
|
|
10274
10611
|
try {
|
|
10275
|
-
entries = await (0,
|
|
10612
|
+
entries = await (0, import_promises6.readdir)(path, { withFileTypes: true });
|
|
10276
10613
|
} catch (_err) {
|
|
10277
10614
|
return [];
|
|
10278
10615
|
}
|
|
@@ -10280,11 +10617,11 @@ async function childDirectoryNames(path) {
|
|
|
10280
10617
|
}
|
|
10281
10618
|
async function readTextIfExists(path) {
|
|
10282
10619
|
try {
|
|
10283
|
-
const pathStat = await (0,
|
|
10620
|
+
const pathStat = await (0, import_promises6.stat)(path);
|
|
10284
10621
|
if (!pathStat.isFile()) {
|
|
10285
10622
|
return void 0;
|
|
10286
10623
|
}
|
|
10287
|
-
return await (0,
|
|
10624
|
+
return await (0, import_promises6.readFile)(path, "utf8");
|
|
10288
10625
|
} catch (_err) {
|
|
10289
10626
|
return void 0;
|
|
10290
10627
|
}
|
|
@@ -10311,10 +10648,10 @@ function legacySourceLabel(candidate) {
|
|
|
10311
10648
|
return `${candidate.sourceAccount}/${candidate.sourceSession}/${candidate.sourceArchive}`;
|
|
10312
10649
|
}
|
|
10313
10650
|
function localVikingDataRoot(config) {
|
|
10314
|
-
return (0,
|
|
10651
|
+
return (0, import_node_path7.join)(config.agentContextHome, "data", "viking");
|
|
10315
10652
|
}
|
|
10316
10653
|
function localUserMemoriesRoot(config) {
|
|
10317
|
-
return (0,
|
|
10654
|
+
return (0, import_node_path7.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
|
|
10318
10655
|
}
|
|
10319
10656
|
function uniqueStrings(values) {
|
|
10320
10657
|
return [...new Set(values)].sort();
|
|
@@ -10330,7 +10667,7 @@ async function buildHandoff(options) {
|
|
|
10330
10667
|
const status = await gitValue(["status", "--short"], repoRoot) ?? "";
|
|
10331
10668
|
const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
|
|
10332
10669
|
const touchedFiles = await gitTouchedFiles(repoRoot);
|
|
10333
|
-
const repoName = await resolveRepoName(repoRoot) ?? (0,
|
|
10670
|
+
const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path7.basename)(repoRoot);
|
|
10334
10671
|
const topicBranch = branch && branch !== "unknown" ? branch : "current";
|
|
10335
10672
|
const metadata = {
|
|
10336
10673
|
kind: "handoff",
|
|
@@ -10390,8 +10727,8 @@ function formatBlock(value, emptyValue) {
|
|
|
10390
10727
|
|
|
10391
10728
|
// src/update-check.ts
|
|
10392
10729
|
var import_node_child_process2 = require("node:child_process");
|
|
10393
|
-
var
|
|
10394
|
-
var
|
|
10730
|
+
var import_promises7 = require("node:fs/promises");
|
|
10731
|
+
var import_node_path8 = require("node:path");
|
|
10395
10732
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10396
10733
|
var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
|
|
10397
10734
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
@@ -10458,7 +10795,7 @@ function isCacheFresh(cache) {
|
|
|
10458
10795
|
}
|
|
10459
10796
|
async function readUpdateCache(cachePath) {
|
|
10460
10797
|
try {
|
|
10461
|
-
const raw = await (0,
|
|
10798
|
+
const raw = await (0, import_promises7.readFile)(cachePath, "utf8");
|
|
10462
10799
|
const parsed = JSON.parse(raw);
|
|
10463
10800
|
if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
|
|
10464
10801
|
return void 0;
|
|
@@ -10470,8 +10807,8 @@ async function readUpdateCache(cachePath) {
|
|
|
10470
10807
|
}
|
|
10471
10808
|
async function writeUpdateCache(cachePath, contents) {
|
|
10472
10809
|
try {
|
|
10473
|
-
await (0,
|
|
10474
|
-
await (0,
|
|
10810
|
+
await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(cachePath), { recursive: true });
|
|
10811
|
+
await (0, import_promises7.writeFile)(cachePath, `${JSON.stringify(contents)}
|
|
10475
10812
|
`, { encoding: "utf8", mode: 384 });
|
|
10476
10813
|
} catch {
|
|
10477
10814
|
}
|
|
@@ -10494,14 +10831,14 @@ async function fetchLatestVersion() {
|
|
|
10494
10831
|
|
|
10495
10832
|
// src/version.ts
|
|
10496
10833
|
var import_node_fs4 = require("node:fs");
|
|
10497
|
-
var
|
|
10834
|
+
var import_node_path9 = require("node:path");
|
|
10498
10835
|
var cachedVersion;
|
|
10499
10836
|
function getThreadnoteVersion() {
|
|
10500
10837
|
if (cachedVersion !== void 0) {
|
|
10501
10838
|
return cachedVersion;
|
|
10502
10839
|
}
|
|
10503
10840
|
try {
|
|
10504
|
-
const packageJsonPath = (0,
|
|
10841
|
+
const packageJsonPath = (0, import_node_path9.join)(__dirname, "..", "package.json");
|
|
10505
10842
|
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
|
|
10506
10843
|
cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
|
|
10507
10844
|
} catch {
|
|
@@ -10543,7 +10880,7 @@ async function runHooksInstall(config, agent, options) {
|
|
|
10543
10880
|
}
|
|
10544
10881
|
async function runClaudeHooksInstall(options) {
|
|
10545
10882
|
const path = expandPath(CLAUDE_SETTINGS_PATH);
|
|
10546
|
-
const existingRaw = await exists(path) ? await (0,
|
|
10883
|
+
const existingRaw = await exists(path) ? await (0, import_promises8.readFile)(path, "utf8") : "{}";
|
|
10547
10884
|
const parsed = parseJsonConfigObject(existingRaw) ?? {};
|
|
10548
10885
|
const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
|
|
10549
10886
|
const before = JSON.stringify(parsed);
|
|
@@ -10560,11 +10897,11 @@ async function runClaudeHooksInstall(options) {
|
|
|
10560
10897
|
console.log("\nRe-run with --apply to actually modify the file.");
|
|
10561
10898
|
return;
|
|
10562
10899
|
}
|
|
10563
|
-
await (0,
|
|
10900
|
+
await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(path), { recursive: true });
|
|
10564
10901
|
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
10565
10902
|
`;
|
|
10566
|
-
await (0,
|
|
10567
|
-
await (0,
|
|
10903
|
+
await (0, import_promises8.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
|
|
10904
|
+
await (0, import_promises8.chmod)(path, 384);
|
|
10568
10905
|
console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
|
|
10569
10906
|
}
|
|
10570
10907
|
function withThreadnoteHooks(input2) {
|
|
@@ -10642,7 +10979,7 @@ async function hasManagedClaudeHooks() {
|
|
|
10642
10979
|
if (!await exists(path)) {
|
|
10643
10980
|
return false;
|
|
10644
10981
|
}
|
|
10645
|
-
const raw = await (0,
|
|
10982
|
+
const raw = await (0, import_promises8.readFile)(path, "utf8");
|
|
10646
10983
|
const parsed = parseJsonConfigObject(raw);
|
|
10647
10984
|
if (!parsed || !isJsonObject(parsed.hooks)) {
|
|
10648
10985
|
return false;
|
|
@@ -10704,7 +11041,7 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
10704
11041
|
async function emitUpdateBannerIfOutdated(config) {
|
|
10705
11042
|
try {
|
|
10706
11043
|
const result = await checkForThreadnoteUpdate({
|
|
10707
|
-
cachePath: (0,
|
|
11044
|
+
cachePath: (0, import_node_path10.join)(config.agentContextHome, ".update-state.json"),
|
|
10708
11045
|
currentVersion: getThreadnoteVersion()
|
|
10709
11046
|
});
|
|
10710
11047
|
if (!result || !result.outdated) {
|
|
@@ -10729,13 +11066,13 @@ async function emitUpdateBannerIfOutdated(config) {
|
|
|
10729
11066
|
}
|
|
10730
11067
|
|
|
10731
11068
|
// src/seeding.ts
|
|
10732
|
-
var
|
|
10733
|
-
var
|
|
11069
|
+
var import_promises9 = require("node:fs/promises");
|
|
11070
|
+
var import_node_path11 = require("node:path");
|
|
10734
11071
|
async function runSeed(config, options) {
|
|
10735
11072
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
10736
11073
|
const ignorePatterns = await loadIgnorePatterns();
|
|
10737
11074
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
10738
|
-
const statePath = (0,
|
|
11075
|
+
const statePath = (0, import_node_path11.join)(config.agentContextHome, SEED_STATE_FILE);
|
|
10739
11076
|
const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
|
|
10740
11077
|
const projects = filterProjects(manifest.projects, options.only);
|
|
10741
11078
|
let importedCount = 0;
|
|
@@ -10760,7 +11097,15 @@ async function runSeed(config, options) {
|
|
|
10760
11097
|
skippedCount += 1;
|
|
10761
11098
|
continue;
|
|
10762
11099
|
}
|
|
10763
|
-
const args = withIdentity(config, [
|
|
11100
|
+
const args = withIdentity(config, [
|
|
11101
|
+
"add-resource",
|
|
11102
|
+
importPath,
|
|
11103
|
+
"--to",
|
|
11104
|
+
candidate.destinationUri,
|
|
11105
|
+
"--reason",
|
|
11106
|
+
seedResourceReason(candidate),
|
|
11107
|
+
"--wait"
|
|
11108
|
+
]);
|
|
10764
11109
|
await maybeRun(options.dryRun === true, ov, args);
|
|
10765
11110
|
importedCount += 1;
|
|
10766
11111
|
if (fileStat && options.dryRun !== true) {
|
|
@@ -10790,7 +11135,7 @@ function filterProjects(projects, only) {
|
|
|
10790
11135
|
}
|
|
10791
11136
|
async function statSeedFile(path) {
|
|
10792
11137
|
try {
|
|
10793
|
-
const result = await (0,
|
|
11138
|
+
const result = await (0, import_promises9.stat)(path);
|
|
10794
11139
|
return { mtimeMs: result.mtimeMs, size: result.size };
|
|
10795
11140
|
} catch (_err) {
|
|
10796
11141
|
return void 0;
|
|
@@ -10798,7 +11143,7 @@ async function statSeedFile(path) {
|
|
|
10798
11143
|
}
|
|
10799
11144
|
async function readSeedState(path) {
|
|
10800
11145
|
try {
|
|
10801
|
-
const raw = await (0,
|
|
11146
|
+
const raw = await (0, import_promises9.readFile)(path, "utf8");
|
|
10802
11147
|
const parsed = JSON.parse(raw);
|
|
10803
11148
|
if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
|
|
10804
11149
|
return { files: {}, version: 1 };
|
|
@@ -10815,13 +11160,13 @@ async function readSeedState(path) {
|
|
|
10815
11160
|
}
|
|
10816
11161
|
}
|
|
10817
11162
|
async function writeSeedState(path, state) {
|
|
10818
|
-
await ensureDirectory((0,
|
|
10819
|
-
await (0,
|
|
11163
|
+
await ensureDirectory((0, import_node_path11.dirname)(path), false);
|
|
11164
|
+
await (0, import_promises9.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
10820
11165
|
`, { encoding: "utf8", mode: 384 });
|
|
10821
11166
|
}
|
|
10822
11167
|
async function runInitManifest(config, options) {
|
|
10823
11168
|
const manifestPath = expandPath(
|
|
10824
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
11169
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path11.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
10825
11170
|
);
|
|
10826
11171
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
10827
11172
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -10864,9 +11209,9 @@ async function runInitManifest(config, options) {
|
|
|
10864
11209
|
console.log(output2.trimEnd());
|
|
10865
11210
|
return;
|
|
10866
11211
|
}
|
|
10867
|
-
await ensureDirectory((0,
|
|
10868
|
-
await (0,
|
|
10869
|
-
await (0,
|
|
11212
|
+
await ensureDirectory((0, import_node_path11.dirname)(manifestPath), false);
|
|
11213
|
+
await (0, import_promises9.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
11214
|
+
await (0, import_promises9.chmod)(manifestPath, 384);
|
|
10870
11215
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
10871
11216
|
console.log("Seed with:");
|
|
10872
11217
|
console.log(" threadnote seed --dry-run");
|
|
@@ -10874,17 +11219,33 @@ async function runInitManifest(config, options) {
|
|
|
10874
11219
|
}
|
|
10875
11220
|
async function runSeedSkills(config, options) {
|
|
10876
11221
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
10877
|
-
const
|
|
11222
|
+
const catalogItems = await collectSkillCandidates(config);
|
|
10878
11223
|
const nativeMode = options.native === true;
|
|
10879
11224
|
console.log(
|
|
10880
11225
|
nativeMode ? "Skill seed mode: native OpenViking skills. This requires a working VLM config." : "Skill seed mode: resource catalog. Use --native only after configuring a working VLM provider."
|
|
10881
11226
|
);
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
11227
|
+
let skippedCount = 0;
|
|
11228
|
+
for (const skill of catalogItems) {
|
|
11229
|
+
console.log(`${skill.kind === "command" ? "Command" : "Skill"} ${skill.source}: ${skill.filePath}`);
|
|
11230
|
+
if (nativeMode && skill.kind === "command") {
|
|
11231
|
+
skippedCount += 1;
|
|
11232
|
+
console.log(`SKIP command in native skill mode: ${skill.filePath}`);
|
|
11233
|
+
continue;
|
|
11234
|
+
}
|
|
11235
|
+
const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : [
|
|
11236
|
+
"add-resource",
|
|
11237
|
+
skill.filePath,
|
|
11238
|
+
"--to",
|
|
11239
|
+
skillResourceUri(skill),
|
|
11240
|
+
"--reason",
|
|
11241
|
+
skillResourceReason(skill),
|
|
11242
|
+
"--wait"
|
|
11243
|
+
];
|
|
10885
11244
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
10886
11245
|
}
|
|
10887
|
-
console.log(
|
|
11246
|
+
console.log(
|
|
11247
|
+
`Skill seed complete: ${catalogItems.length - skippedCount} unique catalog item(s)${skippedCount > 0 ? `, ${skippedCount} skipped` : ""}.`
|
|
11248
|
+
);
|
|
10888
11249
|
}
|
|
10889
11250
|
async function resolveRepoRoot(repoInput) {
|
|
10890
11251
|
const inputPath = expandPath(repoInput);
|
|
@@ -10896,13 +11257,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
10896
11257
|
async function projectIdentity(path) {
|
|
10897
11258
|
const expanded = expandPath(path);
|
|
10898
11259
|
try {
|
|
10899
|
-
return await (0,
|
|
11260
|
+
return await (0, import_promises9.realpath)(expanded);
|
|
10900
11261
|
} catch (_err) {
|
|
10901
11262
|
return expanded;
|
|
10902
11263
|
}
|
|
10903
11264
|
}
|
|
10904
11265
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
10905
|
-
const baseName = uriSegment((0,
|
|
11266
|
+
const baseName = uriSegment((0, import_node_path11.basename)(repoRoot));
|
|
10906
11267
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
10907
11268
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
10908
11269
|
let name = baseName;
|
|
@@ -10924,7 +11285,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
10924
11285
|
for (const pattern of project.seed) {
|
|
10925
11286
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
10926
11287
|
for (const filePath of files) {
|
|
10927
|
-
const relativePath = toPosixPath((0,
|
|
11288
|
+
const relativePath = toPosixPath((0, import_node_path11.relative)(projectRoot, filePath));
|
|
10928
11289
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
10929
11290
|
continue;
|
|
10930
11291
|
}
|
|
@@ -10942,20 +11303,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
10942
11303
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
10943
11304
|
const normalizedPattern = toPosixPath(pattern);
|
|
10944
11305
|
if (!hasGlob(normalizedPattern)) {
|
|
10945
|
-
const filePath = (0,
|
|
11306
|
+
const filePath = (0, import_node_path11.join)(projectRoot, normalizedPattern);
|
|
10946
11307
|
return await isFile(filePath) ? [filePath] : [];
|
|
10947
11308
|
}
|
|
10948
11309
|
const globBase = getGlobBase(normalizedPattern);
|
|
10949
|
-
const basePath = (0,
|
|
11310
|
+
const basePath = (0, import_node_path11.join)(projectRoot, globBase);
|
|
10950
11311
|
if (!await exists(basePath)) {
|
|
10951
11312
|
return [];
|
|
10952
11313
|
}
|
|
10953
11314
|
const regex = globToRegExp(normalizedPattern);
|
|
10954
11315
|
const files = await walkFiles(basePath);
|
|
10955
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
11316
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path11.relative)(projectRoot, filePath))));
|
|
10956
11317
|
}
|
|
10957
11318
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
10958
|
-
const content = await (0,
|
|
11319
|
+
const content = await (0, import_promises9.readFile)(candidate.filePath, "utf8");
|
|
10959
11320
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
10960
11321
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
10961
11322
|
if (secretMatches.length > 0) {
|
|
@@ -10967,39 +11328,54 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
10967
11328
|
if (redactedContent === content) {
|
|
10968
11329
|
return candidate.filePath;
|
|
10969
11330
|
}
|
|
10970
|
-
const redactedPath = (0,
|
|
11331
|
+
const redactedPath = (0, import_node_path11.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
10971
11332
|
if (dryRun) {
|
|
10972
11333
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
10973
11334
|
return redactedPath;
|
|
10974
11335
|
}
|
|
10975
|
-
await ensureDirectory((0,
|
|
10976
|
-
await (0,
|
|
10977
|
-
await (0,
|
|
11336
|
+
await ensureDirectory((0, import_node_path11.dirname)(redactedPath), false);
|
|
11337
|
+
await (0, import_promises9.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
11338
|
+
await (0, import_promises9.chmod)(redactedPath, 384);
|
|
10978
11339
|
return redactedPath;
|
|
10979
11340
|
}
|
|
11341
|
+
function seedResourceReason(candidate) {
|
|
11342
|
+
return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
|
|
11343
|
+
}
|
|
11344
|
+
function skillResourceReason(skill) {
|
|
11345
|
+
return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path11.basename)(
|
|
11346
|
+
skill.filePath
|
|
11347
|
+
)}`;
|
|
11348
|
+
}
|
|
10980
11349
|
async function collectSkillCandidates(config) {
|
|
10981
11350
|
const sources = [
|
|
10982
|
-
{ pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
|
|
10983
|
-
{ pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
|
|
10984
|
-
{ pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" }
|
|
11351
|
+
{ kind: "skill", pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
|
|
11352
|
+
{ kind: "skill", pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
|
|
11353
|
+
{ kind: "skill", pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" },
|
|
11354
|
+
{ kind: "command", pattern: "~/.claude/commands/**/*.md", source: "claude-commands-global" }
|
|
10985
11355
|
];
|
|
10986
11356
|
try {
|
|
10987
11357
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
10988
11358
|
for (const project of manifest.projects) {
|
|
10989
11359
|
sources.push({
|
|
11360
|
+
kind: "skill",
|
|
10990
11361
|
pattern: `${project.path}/.claude/skills/**/SKILL.md`,
|
|
10991
11362
|
source: `repo-local:${project.name}`
|
|
10992
11363
|
});
|
|
11364
|
+
sources.push({
|
|
11365
|
+
kind: "command",
|
|
11366
|
+
pattern: `${project.path}/.claude/commands/**/*.md`,
|
|
11367
|
+
source: `repo-local:${project.name}:claude-commands`
|
|
11368
|
+
});
|
|
10993
11369
|
}
|
|
10994
11370
|
} catch (err) {
|
|
10995
|
-
console.log(`WARN cannot read manifest for repo-local skill discovery: ${errorMessage(err)}`);
|
|
11371
|
+
console.log(`WARN cannot read manifest for repo-local skill/command discovery: ${errorMessage(err)}`);
|
|
10996
11372
|
}
|
|
10997
11373
|
const seenHashes = /* @__PURE__ */ new Set();
|
|
10998
11374
|
const skills = [];
|
|
10999
11375
|
for (const source of sources) {
|
|
11000
11376
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
11001
11377
|
for (const filePath of files) {
|
|
11002
|
-
const content = await (0,
|
|
11378
|
+
const content = await (0, import_promises9.readFile)(filePath, "utf8");
|
|
11003
11379
|
const matches = detectSecretMatches(content);
|
|
11004
11380
|
if (matches.length > 0) {
|
|
11005
11381
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -11010,7 +11386,7 @@ async function collectSkillCandidates(config) {
|
|
|
11010
11386
|
continue;
|
|
11011
11387
|
}
|
|
11012
11388
|
seenHashes.add(hash);
|
|
11013
|
-
skills.push({ filePath, hash, source: source.source });
|
|
11389
|
+
skills.push({ filePath, hash, kind: source.kind, source: source.source });
|
|
11014
11390
|
}
|
|
11015
11391
|
}
|
|
11016
11392
|
return skills;
|
|
@@ -11030,10 +11406,19 @@ async function resolveAbsolutePattern(pattern) {
|
|
|
11030
11406
|
return files.filter((filePath) => regex.test(toPosixPath(filePath)));
|
|
11031
11407
|
}
|
|
11032
11408
|
function skillResourceUri(skill) {
|
|
11033
|
-
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${
|
|
11409
|
+
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
|
|
11410
|
+
}
|
|
11411
|
+
function skillResourceName(skill) {
|
|
11412
|
+
const fileName = (0, import_node_path11.basename)(skill.filePath);
|
|
11413
|
+
if (fileName.toLowerCase() === "skill.md") {
|
|
11414
|
+
return uriSegment((0, import_node_path11.basename)((0, import_node_path11.dirname)(skill.filePath)));
|
|
11415
|
+
}
|
|
11416
|
+
const extensionIndex = fileName.lastIndexOf(".");
|
|
11417
|
+
const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
|
|
11418
|
+
return uriSegment(stem);
|
|
11034
11419
|
}
|
|
11035
11420
|
async function loadIgnorePatterns() {
|
|
11036
|
-
const raw = await (0,
|
|
11421
|
+
const raw = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
11037
11422
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
11038
11423
|
}
|
|
11039
11424
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -11106,18 +11491,18 @@ function detectSecretMatches(content) {
|
|
|
11106
11491
|
// src/lifecycle.ts
|
|
11107
11492
|
var import_node_child_process3 = require("node:child_process");
|
|
11108
11493
|
var import_node_fs6 = require("node:fs");
|
|
11109
|
-
var
|
|
11494
|
+
var import_promises12 = require("node:fs/promises");
|
|
11110
11495
|
var import_node_os6 = require("node:os");
|
|
11111
|
-
var
|
|
11496
|
+
var import_node_path13 = require("node:path");
|
|
11112
11497
|
var import_node_process3 = require("node:process");
|
|
11113
|
-
var
|
|
11498
|
+
var import_promises13 = require("node:readline/promises");
|
|
11114
11499
|
|
|
11115
11500
|
// src/update.ts
|
|
11116
11501
|
var import_node_fs5 = require("node:fs");
|
|
11117
|
-
var
|
|
11502
|
+
var import_promises10 = require("node:fs/promises");
|
|
11118
11503
|
var import_node_os5 = require("node:os");
|
|
11119
|
-
var
|
|
11120
|
-
var
|
|
11504
|
+
var import_node_path12 = require("node:path");
|
|
11505
|
+
var import_promises11 = require("node:readline/promises");
|
|
11121
11506
|
var import_node_process2 = require("node:process");
|
|
11122
11507
|
|
|
11123
11508
|
// src/release_notes.ts
|
|
@@ -11286,7 +11671,9 @@ async function runUpdate(config, options) {
|
|
|
11286
11671
|
return;
|
|
11287
11672
|
}
|
|
11288
11673
|
const threadnoteCommand = await installedThreadnoteCommand(runtime);
|
|
11289
|
-
|
|
11674
|
+
console.log("");
|
|
11675
|
+
console.log("Repairing local Threadnote setup after package update.");
|
|
11676
|
+
await runThreadnoteSubcommand(options.dryRun === true, threadnoteCommand, ["repair", "--no-post-update"]);
|
|
11290
11677
|
if (options.postUpdate !== false) {
|
|
11291
11678
|
const postUpdateArgs = [
|
|
11292
11679
|
"post-update",
|
|
@@ -11296,15 +11683,7 @@ async function runUpdate(config, options) {
|
|
|
11296
11683
|
info2.latestVersion,
|
|
11297
11684
|
...options.yes === true ? ["--yes"] : []
|
|
11298
11685
|
];
|
|
11299
|
-
|
|
11300
|
-
await maybeRun(true, threadnoteCommand, postUpdateArgs);
|
|
11301
|
-
} else {
|
|
11302
|
-
console.log(`Running: ${formatShellCommand(threadnoteCommand, postUpdateArgs)}`);
|
|
11303
|
-
const postUpdateExitCode = await runInteractive(threadnoteCommand, postUpdateArgs);
|
|
11304
|
-
if (postUpdateExitCode !== 0) {
|
|
11305
|
-
throw new Error(`${formatShellCommand(threadnoteCommand, postUpdateArgs)} exited with ${postUpdateExitCode}.`);
|
|
11306
|
-
}
|
|
11307
|
-
}
|
|
11686
|
+
await runThreadnoteSubcommand(options.dryRun === true, threadnoteCommand, postUpdateArgs);
|
|
11308
11687
|
} else {
|
|
11309
11688
|
console.log("Skipping post-update migration prompts because --no-post-update was provided.");
|
|
11310
11689
|
}
|
|
@@ -11388,7 +11767,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11388
11767
|
}
|
|
11389
11768
|
console.log("");
|
|
11390
11769
|
console.log(`Upgrading OpenViking ${installedVersion} -> ${pinned} (pinned by Threadnote).`);
|
|
11391
|
-
console.log("Picks up upstream
|
|
11770
|
+
console.log("Picks up upstream CLI, resource-ingestion, and index reliability fixes.");
|
|
11392
11771
|
const wasRunning = await isOpenVikingHealthy(config);
|
|
11393
11772
|
const usingLaunchd = await isLaunchAgentInstalled();
|
|
11394
11773
|
const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
@@ -11406,9 +11785,11 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11406
11785
|
if (usingLaunchd) {
|
|
11407
11786
|
const launchAgentPath = launchAgentPlistPath();
|
|
11408
11787
|
await runCommand("launchctl", ["unload", launchAgentPath], { allowFailure: true });
|
|
11788
|
+
await waitForOpenVikingPortClosed(config, 15e3);
|
|
11409
11789
|
await runCommand("launchctl", ["load", launchAgentPath], { allowFailure: true });
|
|
11410
11790
|
} else {
|
|
11411
11791
|
await maybeRun(false, threadnoteCommand, ["stop"]);
|
|
11792
|
+
await waitForOpenVikingPortClosed(config, 15e3);
|
|
11412
11793
|
await maybeRun(false, threadnoteCommand, ["start"]);
|
|
11413
11794
|
}
|
|
11414
11795
|
const healthyAfter = await waitForOpenVikingHealthy(config, 1e4);
|
|
@@ -11419,6 +11800,17 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11419
11800
|
console.log("Check the server log or run: threadnote start");
|
|
11420
11801
|
}
|
|
11421
11802
|
}
|
|
11803
|
+
async function runThreadnoteSubcommand(dryRun, executable, args) {
|
|
11804
|
+
if (dryRun) {
|
|
11805
|
+
await maybeRun(true, executable, args);
|
|
11806
|
+
return;
|
|
11807
|
+
}
|
|
11808
|
+
console.log(`Running: ${formatShellCommand(executable, args)}`);
|
|
11809
|
+
const exitCode = await runInteractive(executable, args);
|
|
11810
|
+
if (exitCode !== 0) {
|
|
11811
|
+
throw new Error(`${formatShellCommand(executable, args)} exited with ${exitCode}.`);
|
|
11812
|
+
}
|
|
11813
|
+
}
|
|
11422
11814
|
function openVikingHealthEndpoint(config) {
|
|
11423
11815
|
return `http://${config.host}:${config.port}/health`;
|
|
11424
11816
|
}
|
|
@@ -11442,21 +11834,36 @@ async function waitForOpenVikingHealthy(config, timeoutMs) {
|
|
|
11442
11834
|
if (await isOpenVikingHealthy(config)) {
|
|
11443
11835
|
return true;
|
|
11444
11836
|
}
|
|
11445
|
-
await
|
|
11446
|
-
setTimeout(resolvePromise, 500);
|
|
11447
|
-
});
|
|
11837
|
+
await sleep(500);
|
|
11448
11838
|
}
|
|
11449
11839
|
return isOpenVikingHealthy(config);
|
|
11450
11840
|
}
|
|
11841
|
+
async function waitForOpenVikingPortClosed(config, timeoutMs) {
|
|
11842
|
+
console.log(`Waiting for OpenViking port ${config.host}:${config.port} to close before restart.`);
|
|
11843
|
+
const deadline = Date.now() + timeoutMs;
|
|
11844
|
+
while (Date.now() < deadline) {
|
|
11845
|
+
if (!await isTcpPortOpen(config.host, config.port, 300)) {
|
|
11846
|
+
return true;
|
|
11847
|
+
}
|
|
11848
|
+
await sleep(300);
|
|
11849
|
+
}
|
|
11850
|
+
if (!await isTcpPortOpen(config.host, config.port, 300)) {
|
|
11851
|
+
return true;
|
|
11852
|
+
}
|
|
11853
|
+
console.log(
|
|
11854
|
+
`Warning: OpenViking port ${config.host}:${config.port} is still in use after ${timeoutMs / 1e3}s; start may fail.`
|
|
11855
|
+
);
|
|
11856
|
+
return false;
|
|
11857
|
+
}
|
|
11451
11858
|
function launchAgentPlistPath() {
|
|
11452
|
-
return (0,
|
|
11859
|
+
return (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
|
|
11453
11860
|
}
|
|
11454
11861
|
async function isLaunchAgentInstalled() {
|
|
11455
11862
|
if (process.platform !== "darwin") {
|
|
11456
11863
|
return false;
|
|
11457
11864
|
}
|
|
11458
11865
|
try {
|
|
11459
|
-
await (0,
|
|
11866
|
+
await (0, import_promises10.access)(launchAgentPlistPath(), import_node_fs5.constants.F_OK);
|
|
11460
11867
|
return true;
|
|
11461
11868
|
} catch (_err) {
|
|
11462
11869
|
return false;
|
|
@@ -11485,7 +11892,7 @@ async function getUpdateInfo(config, options) {
|
|
|
11485
11892
|
};
|
|
11486
11893
|
}
|
|
11487
11894
|
async function currentPackageVersion() {
|
|
11488
|
-
const rawPackage = await (0,
|
|
11895
|
+
const rawPackage = await (0, import_promises10.readFile)((0, import_node_path12.join)(toolRoot(), "package.json"), "utf8");
|
|
11489
11896
|
const parsed = JSON.parse(rawPackage);
|
|
11490
11897
|
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
11491
11898
|
throw new Error("Could not read current threadnote package version.");
|
|
@@ -11535,11 +11942,11 @@ async function readFreshCache(config, registry) {
|
|
|
11535
11942
|
}
|
|
11536
11943
|
async function writeUpdateCache2(config, cache) {
|
|
11537
11944
|
await ensureDirectory(config.agentContextHome, false);
|
|
11538
|
-
await (0,
|
|
11945
|
+
await (0, import_promises10.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
11539
11946
|
`, { encoding: "utf8", mode: 384 });
|
|
11540
11947
|
}
|
|
11541
11948
|
function updateCachePath(config) {
|
|
11542
|
-
return (0,
|
|
11949
|
+
return (0, import_node_path12.join)(config.agentContextHome, "update-check.json");
|
|
11543
11950
|
}
|
|
11544
11951
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
11545
11952
|
const state = await readPostUpdateState(config);
|
|
@@ -11603,7 +12010,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
11603
12010
|
return applicable;
|
|
11604
12011
|
}
|
|
11605
12012
|
async function readPostUpdateMigrations() {
|
|
11606
|
-
const raw = await readFileIfExists((0,
|
|
12013
|
+
const raw = await readFileIfExists((0, import_node_path12.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
11607
12014
|
if (!raw) {
|
|
11608
12015
|
return [];
|
|
11609
12016
|
}
|
|
@@ -11642,7 +12049,7 @@ function printPostUpdateMigration(migration) {
|
|
|
11642
12049
|
}
|
|
11643
12050
|
}
|
|
11644
12051
|
async function confirmPostUpdateMigration(prompt, defaultYes = false) {
|
|
11645
|
-
const readline = (0,
|
|
12052
|
+
const readline = (0, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
11646
12053
|
try {
|
|
11647
12054
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
11648
12055
|
if (answer === "") {
|
|
@@ -11677,11 +12084,11 @@ async function readPostUpdateState(config) {
|
|
|
11677
12084
|
}
|
|
11678
12085
|
async function writePostUpdateState(config, state) {
|
|
11679
12086
|
await ensureDirectory(config.agentContextHome, false);
|
|
11680
|
-
await (0,
|
|
12087
|
+
await (0, import_promises10.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
11681
12088
|
`, { encoding: "utf8", mode: 384 });
|
|
11682
12089
|
}
|
|
11683
12090
|
function postUpdateStatePath(config) {
|
|
11684
|
-
return (0,
|
|
12091
|
+
return (0, import_node_path12.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
11685
12092
|
}
|
|
11686
12093
|
async function resolveUpdateRuntime(runtime) {
|
|
11687
12094
|
if (runtime !== "auto") {
|
|
@@ -11711,14 +12118,14 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
11711
12118
|
if (runtime === "npm") {
|
|
11712
12119
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
11713
12120
|
const prefix = result.stdout.trim();
|
|
11714
|
-
return prefix ? (0,
|
|
12121
|
+
return prefix ? (0, import_node_path12.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
11715
12122
|
}
|
|
11716
12123
|
if (runtime === "bun") {
|
|
11717
12124
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
11718
12125
|
const binDir = result.stdout.trim();
|
|
11719
|
-
return binDir ? (0,
|
|
12126
|
+
return binDir ? (0, import_node_path12.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
11720
12127
|
}
|
|
11721
|
-
return (0,
|
|
12128
|
+
return (0, import_node_path12.join)(process.env.DENO_INSTALL ?? (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
11722
12129
|
}
|
|
11723
12130
|
function updatePackageCommand(runtime, registry) {
|
|
11724
12131
|
if (runtime === "npm") {
|
|
@@ -11786,9 +12193,10 @@ async function collectDoctorChecks(config, options = {}) {
|
|
|
11786
12193
|
checks.push(await commandShimCheck());
|
|
11787
12194
|
checks.push(...await userAgentInstructionsChecks());
|
|
11788
12195
|
checks.push(await manifestCheck(config.manifestPath));
|
|
11789
|
-
checks.push(await
|
|
11790
|
-
checks.push(await fileCheck((0,
|
|
11791
|
-
checks.push(await fileCheck((0,
|
|
12196
|
+
checks.push(await recallIndexFreshnessCheck(config));
|
|
12197
|
+
checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
12198
|
+
checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
12199
|
+
checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
11792
12200
|
checks.push(await healthCheck(config));
|
|
11793
12201
|
return checks;
|
|
11794
12202
|
}
|
|
@@ -11796,9 +12204,9 @@ async function runInstall(config, options) {
|
|
|
11796
12204
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
11797
12205
|
const dryRun = options.dryRun === true;
|
|
11798
12206
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
11799
|
-
await ensureDirectory((0,
|
|
11800
|
-
await ensureDirectory((0,
|
|
11801
|
-
await ensureDirectory((0,
|
|
12207
|
+
await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "logs"), dryRun);
|
|
12208
|
+
await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "redacted"), dryRun);
|
|
12209
|
+
await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "mcp"), dryRun);
|
|
11802
12210
|
await installCommandShim(dryRun);
|
|
11803
12211
|
await installUserAgentInstructions(dryRun);
|
|
11804
12212
|
const serverPath = await findOpenVikingServer();
|
|
@@ -11835,18 +12243,19 @@ async function runInstall(config, options) {
|
|
|
11835
12243
|
}
|
|
11836
12244
|
await writeTemplateIfMissing({
|
|
11837
12245
|
config,
|
|
11838
|
-
destinationPath: (0,
|
|
12246
|
+
destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ov.conf"),
|
|
11839
12247
|
dryRun,
|
|
11840
12248
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
11841
|
-
templatePath: (0,
|
|
12249
|
+
templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
11842
12250
|
});
|
|
11843
12251
|
await writeTemplateIfMissing({
|
|
11844
12252
|
config,
|
|
11845
|
-
destinationPath: (0,
|
|
12253
|
+
destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ovcli.conf"),
|
|
11846
12254
|
dryRun,
|
|
11847
12255
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
11848
|
-
templatePath: (0,
|
|
12256
|
+
templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
11849
12257
|
});
|
|
12258
|
+
await configureOpenVikingCliLanguage(config, dryRun);
|
|
11850
12259
|
if (options.start !== false) {
|
|
11851
12260
|
const healthy = await repairServerHealth(config, dryRun);
|
|
11852
12261
|
if (!healthy && !dryRun) {
|
|
@@ -11873,6 +12282,7 @@ async function runRepair(config, options) {
|
|
|
11873
12282
|
} else {
|
|
11874
12283
|
console.log("Skipping server health repair because --no-start was provided.");
|
|
11875
12284
|
}
|
|
12285
|
+
await repairRecallIndex(config, dryRun);
|
|
11876
12286
|
const mcpClients = await resolveMcpClients(options.mcp ?? "available", "repair");
|
|
11877
12287
|
if (mcpClients.length === 0) {
|
|
11878
12288
|
console.log("Skipping MCP config repair.");
|
|
@@ -11899,7 +12309,7 @@ async function runUninstall(config, options) {
|
|
|
11899
12309
|
}
|
|
11900
12310
|
console.log("Uninstalling local Threadnote setup.");
|
|
11901
12311
|
await runStop(config, { dryRun });
|
|
11902
|
-
await removePathIfExists((0,
|
|
12312
|
+
await removePathIfExists((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
11903
12313
|
await removeLaunchAgent(dryRun);
|
|
11904
12314
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
11905
12315
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -11956,25 +12366,102 @@ async function repairManifest(config, dryRun) {
|
|
|
11956
12366
|
console.log(output2.trimEnd());
|
|
11957
12367
|
return;
|
|
11958
12368
|
}
|
|
11959
|
-
await ensureDirectory((0,
|
|
12369
|
+
await ensureDirectory((0, import_node_path13.dirname)(config.manifestPath), false);
|
|
11960
12370
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
11961
12371
|
if (currentContent !== void 0) {
|
|
11962
12372
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
11963
|
-
await (0,
|
|
11964
|
-
await (0,
|
|
12373
|
+
await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
12374
|
+
await (0, import_promises12.chmod)(backupPath, 384);
|
|
11965
12375
|
console.log(`Backup: ${backupPath}`);
|
|
11966
12376
|
}
|
|
11967
|
-
await (0,
|
|
11968
|
-
await (0,
|
|
12377
|
+
await (0, import_promises12.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
12378
|
+
await (0, import_promises12.chmod)(config.manifestPath, 384);
|
|
11969
12379
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
11970
12380
|
}
|
|
12381
|
+
async function repairRecallIndex(config, dryRun) {
|
|
12382
|
+
console.log("\nRepairing recall index freshness.");
|
|
12383
|
+
const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
|
|
12384
|
+
if (!ov) {
|
|
12385
|
+
console.log("Skipping recall index repair: neither ov nor openviking was found in PATH.");
|
|
12386
|
+
return;
|
|
12387
|
+
}
|
|
12388
|
+
const progress = startProgress("Scanning recall index freshness across memories and seeded resources.");
|
|
12389
|
+
try {
|
|
12390
|
+
const result = await repairStaleRecallIndex(config, ov, {
|
|
12391
|
+
collapseDepth: MAINTENANCE_COLLAPSE_DEPTH,
|
|
12392
|
+
collapseToRoots: true,
|
|
12393
|
+
dryRun,
|
|
12394
|
+
ignoreBackoff: true,
|
|
12395
|
+
includeAgentSkills: true,
|
|
12396
|
+
includeManifestResources: true,
|
|
12397
|
+
maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS,
|
|
12398
|
+
onRepairFailure: async () => {
|
|
12399
|
+
progress.update("A recall index reindex failed; checking OpenViking health before continuing.");
|
|
12400
|
+
await repairServerHealth(config, dryRun);
|
|
12401
|
+
},
|
|
12402
|
+
onProgress: (event) => {
|
|
12403
|
+
if (event.type === "scan-complete") {
|
|
12404
|
+
if (event.totalTargets === 0) {
|
|
12405
|
+
progress.update("No stale recall index scopes found.");
|
|
12406
|
+
} else {
|
|
12407
|
+
progress.update(
|
|
12408
|
+
`Found ${event.totalTargets} stale recall index scope(s); repairing ${event.repairTargetCount}.`
|
|
12409
|
+
);
|
|
12410
|
+
}
|
|
12411
|
+
} else if (event.type === "repair-start") {
|
|
12412
|
+
progress.update(
|
|
12413
|
+
`Reindexing ${event.index}/${event.total}: ${event.target.uri} (${event.target.staleCount} stale summaries).`
|
|
12414
|
+
);
|
|
12415
|
+
} else if (event.type === "repair-dry-run") {
|
|
12416
|
+
progress.update(
|
|
12417
|
+
`Planning reindex ${event.index}/${event.total}: ${event.target.uri} (${event.target.staleCount} stale summaries).`
|
|
12418
|
+
);
|
|
12419
|
+
} else if (event.type === "repair-skip-recent") {
|
|
12420
|
+
progress.update(`Skipping recently repaired scope ${event.index}/${event.total}: ${event.target.uri}.`);
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
});
|
|
12424
|
+
progress.stop();
|
|
12425
|
+
const messages = formatRecallIndexRepairMessages(result, { dryRun, maxUris: 20 });
|
|
12426
|
+
if (messages.length === 0) {
|
|
12427
|
+
console.log("Recall index freshness OK.");
|
|
12428
|
+
return;
|
|
12429
|
+
}
|
|
12430
|
+
for (const message of messages) {
|
|
12431
|
+
console.log(message);
|
|
12432
|
+
}
|
|
12433
|
+
} catch (err) {
|
|
12434
|
+
progress.stop();
|
|
12435
|
+
console.log(`WARN could not repair recall index freshness: ${errorMessage(err)}`);
|
|
12436
|
+
}
|
|
12437
|
+
}
|
|
12438
|
+
async function configureOpenVikingCliLanguage(config, dryRun) {
|
|
12439
|
+
const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
|
|
12440
|
+
if (!ov) {
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12443
|
+
const installedVersion = dryRun ? void 0 : await readOpenVikingCliVersion2(ov);
|
|
12444
|
+
const effectiveVersion = installedVersion ?? config.openVikingVersion;
|
|
12445
|
+
if (compareVersions(effectiveVersion, "0.3.23") < 0) {
|
|
12446
|
+
return;
|
|
12447
|
+
}
|
|
12448
|
+
await maybeRun(dryRun, ov, ["language", "en"], { allowFailure: true });
|
|
12449
|
+
}
|
|
12450
|
+
async function readOpenVikingCliVersion2(ov) {
|
|
12451
|
+
const result = await runCommand(ov, ["version"], { allowFailure: true });
|
|
12452
|
+
if (result.exitCode !== 0) {
|
|
12453
|
+
return void 0;
|
|
12454
|
+
}
|
|
12455
|
+
const match = result.stdout.match(/^\s*CLI:\s*(\S+)/m);
|
|
12456
|
+
return match ? match[1] : void 0;
|
|
12457
|
+
}
|
|
11971
12458
|
async function findOpenVikingServer() {
|
|
11972
12459
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
11973
12460
|
if (onPath) {
|
|
11974
12461
|
return onPath;
|
|
11975
12462
|
}
|
|
11976
12463
|
for (const candidateDir of await openVikingServerCandidateDirs()) {
|
|
11977
|
-
const candidate = (0,
|
|
12464
|
+
const candidate = (0, import_node_path13.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
|
|
11978
12465
|
if (await isExecutable(candidate)) {
|
|
11979
12466
|
return candidate;
|
|
11980
12467
|
}
|
|
@@ -12020,7 +12507,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
|
|
|
12020
12507
|
if (onPath) {
|
|
12021
12508
|
return;
|
|
12022
12509
|
}
|
|
12023
|
-
const binDir = (0,
|
|
12510
|
+
const binDir = (0, import_node_path13.dirname)(serverPath);
|
|
12024
12511
|
const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
|
|
12025
12512
|
console.log(
|
|
12026
12513
|
`Note: ${serverPath} is installed but ${binDir} is not on this shell's PATH. Add \`export PATH="${binDir}:$PATH"\` to ${rcHint} so other tools can find openviking-server.`
|
|
@@ -12064,7 +12551,7 @@ async function runStart(config, options) {
|
|
|
12064
12551
|
);
|
|
12065
12552
|
}
|
|
12066
12553
|
const logPath = openVikingLogPath(config);
|
|
12067
|
-
await ensureDirectory((0,
|
|
12554
|
+
await ensureDirectory((0, import_node_path13.dirname)(logPath), false);
|
|
12068
12555
|
if (options.foreground === true) {
|
|
12069
12556
|
const result = await runInteractive(server, args);
|
|
12070
12557
|
process.exitCode = result;
|
|
@@ -12073,7 +12560,7 @@ async function runStart(config, options) {
|
|
|
12073
12560
|
const logFd = (0, import_node_fs6.openSync)(logPath, "a");
|
|
12074
12561
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
12075
12562
|
child.unref();
|
|
12076
|
-
await (0,
|
|
12563
|
+
await (0, import_promises12.writeFile)((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
12077
12564
|
`, "utf8");
|
|
12078
12565
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
12079
12566
|
if (health) {
|
|
@@ -12106,7 +12593,7 @@ async function runStop(config, options) {
|
|
|
12106
12593
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
12107
12594
|
}
|
|
12108
12595
|
}
|
|
12109
|
-
const pidPath = (0,
|
|
12596
|
+
const pidPath = (0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid");
|
|
12110
12597
|
const pidText = await readFileIfExists(pidPath);
|
|
12111
12598
|
if (!pidText) {
|
|
12112
12599
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -12148,7 +12635,7 @@ async function openVikingServerCheck() {
|
|
|
12148
12635
|
}
|
|
12149
12636
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
12150
12637
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
12151
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
12638
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path13.dirname)(executable)} to PATH)`;
|
|
12152
12639
|
return {
|
|
12153
12640
|
name,
|
|
12154
12641
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -12217,7 +12704,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
12217
12704
|
};
|
|
12218
12705
|
}
|
|
12219
12706
|
async function commandShimCheck() {
|
|
12220
|
-
const shimPath = (0,
|
|
12707
|
+
const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
12221
12708
|
const content = await readFileIfExists(shimPath);
|
|
12222
12709
|
if (content === void 0) {
|
|
12223
12710
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -12283,11 +12770,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
12283
12770
|
async function siblingPythonForExecutable(executablePath) {
|
|
12284
12771
|
let resolvedPath;
|
|
12285
12772
|
try {
|
|
12286
|
-
resolvedPath = await (0,
|
|
12773
|
+
resolvedPath = await (0, import_promises12.realpath)(executablePath);
|
|
12287
12774
|
} catch (_err) {
|
|
12288
12775
|
return void 0;
|
|
12289
12776
|
}
|
|
12290
|
-
const pythonPath = (0,
|
|
12777
|
+
const pythonPath = (0, import_node_path13.join)((0, import_node_path13.dirname)(resolvedPath), "python");
|
|
12291
12778
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
12292
12779
|
}
|
|
12293
12780
|
async function manifestCheck(path) {
|
|
@@ -12298,6 +12785,29 @@ async function manifestCheck(path) {
|
|
|
12298
12785
|
return { name: "manifest", status: "fail", detail: errorMessage(err) };
|
|
12299
12786
|
}
|
|
12300
12787
|
}
|
|
12788
|
+
async function recallIndexFreshnessCheck(config) {
|
|
12789
|
+
try {
|
|
12790
|
+
const targets = await findStaleRecallIndexTargets(config, {
|
|
12791
|
+
collapseToRoots: true,
|
|
12792
|
+
includeAgentSkills: true,
|
|
12793
|
+
includeManifestResources: true
|
|
12794
|
+
});
|
|
12795
|
+
if (targets.length === 0) {
|
|
12796
|
+
return { name: "recall index freshness", status: "ok", detail: "no stale generated summaries found" };
|
|
12797
|
+
}
|
|
12798
|
+
const staleSummaryCount = targets.reduce((total, target) => total + target.staleCount, 0);
|
|
12799
|
+
const sampleUris = targets.slice(0, 3).map((target) => target.uri);
|
|
12800
|
+
const extraCount = targets.length - sampleUris.length;
|
|
12801
|
+
const sample = `${sampleUris.join(", ")}${extraCount > 0 ? `, +${extraCount} more` : ""}`;
|
|
12802
|
+
return {
|
|
12803
|
+
name: "recall index freshness",
|
|
12804
|
+
status: "warn",
|
|
12805
|
+
detail: `${staleSummaryCount} stale generated summary file(s) under ${targets.length} scope(s); run repair to reindex ${sample}`
|
|
12806
|
+
};
|
|
12807
|
+
} catch (err) {
|
|
12808
|
+
return { name: "recall index freshness", status: "warn", detail: errorMessage(err) };
|
|
12809
|
+
}
|
|
12810
|
+
}
|
|
12301
12811
|
async function fileCheck(path, label) {
|
|
12302
12812
|
return await exists(path) ? { name: label, status: "ok", detail: path } : { name: label, status: "fail", detail: `${path} missing` };
|
|
12303
12813
|
}
|
|
@@ -12358,7 +12868,7 @@ async function offerToInstallUv() {
|
|
|
12358
12868
|
);
|
|
12359
12869
|
return false;
|
|
12360
12870
|
}
|
|
12361
|
-
const readline = (0,
|
|
12871
|
+
const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
|
|
12362
12872
|
let answer;
|
|
12363
12873
|
try {
|
|
12364
12874
|
answer = (await readline.question(
|
|
@@ -12483,38 +12993,38 @@ function printInstallNextSteps(options) {
|
|
|
12483
12993
|
}
|
|
12484
12994
|
async function writeTemplateIfMissing(options) {
|
|
12485
12995
|
if (await exists(options.destinationPath)) {
|
|
12486
|
-
const currentContent = await (0,
|
|
12996
|
+
const currentContent = await (0, import_promises12.readFile)(options.destinationPath, "utf8");
|
|
12487
12997
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
12488
12998
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
12489
12999
|
return;
|
|
12490
13000
|
}
|
|
12491
|
-
const rendered2 = renderTemplate(await (0,
|
|
13001
|
+
const rendered2 = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
|
|
12492
13002
|
if (options.dryRun) {
|
|
12493
13003
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
12494
13004
|
return;
|
|
12495
13005
|
}
|
|
12496
13006
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
12497
|
-
await (0,
|
|
12498
|
-
await (0,
|
|
12499
|
-
await (0,
|
|
12500
|
-
await (0,
|
|
13007
|
+
await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
13008
|
+
await (0, import_promises12.chmod)(backupPath, 384);
|
|
13009
|
+
await (0, import_promises12.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
13010
|
+
await (0, import_promises12.chmod)(options.destinationPath, 384);
|
|
12501
13011
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
12502
13012
|
console.log(`Backup: ${backupPath}`);
|
|
12503
13013
|
return;
|
|
12504
13014
|
}
|
|
12505
|
-
const rendered = renderTemplate(await (0,
|
|
13015
|
+
const rendered = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
|
|
12506
13016
|
if (options.dryRun) {
|
|
12507
13017
|
console.log(`Would write ${options.destinationPath}`);
|
|
12508
13018
|
return;
|
|
12509
13019
|
}
|
|
12510
|
-
await ensureDirectory((0,
|
|
12511
|
-
await (0,
|
|
12512
|
-
await (0,
|
|
13020
|
+
await ensureDirectory((0, import_node_path13.dirname)(options.destinationPath), false);
|
|
13021
|
+
await (0, import_promises12.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
13022
|
+
await (0, import_promises12.chmod)(options.destinationPath, 384);
|
|
12513
13023
|
console.log(`Wrote ${options.destinationPath}`);
|
|
12514
13024
|
}
|
|
12515
13025
|
async function installCommandShim(dryRun) {
|
|
12516
13026
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
12517
|
-
const shimPath = (0,
|
|
13027
|
+
const shimPath = (0, import_node_path13.join)(binDir, "threadnote");
|
|
12518
13028
|
const existingContent = await readFileIfExists(shimPath);
|
|
12519
13029
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
12520
13030
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -12530,12 +13040,12 @@ async function installCommandShim(dryRun) {
|
|
|
12530
13040
|
return;
|
|
12531
13041
|
}
|
|
12532
13042
|
await ensureDirectory(binDir, false);
|
|
12533
|
-
await (0,
|
|
12534
|
-
await (0,
|
|
13043
|
+
await (0, import_promises12.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
13044
|
+
await (0, import_promises12.chmod)(shimPath, 493);
|
|
12535
13045
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
12536
13046
|
}
|
|
12537
13047
|
async function removeCommandShim(dryRun) {
|
|
12538
|
-
const shimPath = (0,
|
|
13048
|
+
const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
12539
13049
|
const content = await readFileIfExists(shimPath);
|
|
12540
13050
|
if (content === void 0) {
|
|
12541
13051
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -12569,8 +13079,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
12569
13079
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
12570
13080
|
continue;
|
|
12571
13081
|
}
|
|
12572
|
-
await ensureDirectory((0,
|
|
12573
|
-
await (0,
|
|
13082
|
+
await ensureDirectory((0, import_node_path13.dirname)(targetPath), false);
|
|
13083
|
+
await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
12574
13084
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
12575
13085
|
}
|
|
12576
13086
|
}
|
|
@@ -12607,7 +13117,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
12607
13117
|
console.log(`Would update ${targetPath}`);
|
|
12608
13118
|
continue;
|
|
12609
13119
|
}
|
|
12610
|
-
await (0,
|
|
13120
|
+
await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
12611
13121
|
console.log(`Updated ${targetPath}`);
|
|
12612
13122
|
}
|
|
12613
13123
|
}
|
|
@@ -12627,7 +13137,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
12627
13137
|
].join("\n");
|
|
12628
13138
|
}
|
|
12629
13139
|
async function renderUserAgentInstructionsBlock() {
|
|
12630
|
-
const instructions = (await (0,
|
|
13140
|
+
const instructions = (await (0, import_promises12.readFile)((0, import_node_path13.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
12631
13141
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
12632
13142
|
${instructions}
|
|
12633
13143
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -12711,9 +13221,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
12711
13221
|
`Cannot install LaunchAgent: ${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
|
|
12712
13222
|
);
|
|
12713
13223
|
}
|
|
12714
|
-
const source = (0,
|
|
13224
|
+
const source = (0, import_node_path13.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
12715
13225
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
12716
|
-
const rendered = renderTemplate(await (0,
|
|
13226
|
+
const rendered = renderTemplate(await (0, import_promises12.readFile)(source, "utf8"), config, {
|
|
12717
13227
|
OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
|
|
12718
13228
|
});
|
|
12719
13229
|
if (dryRun) {
|
|
@@ -12725,9 +13235,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
12725
13235
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
12726
13236
|
return;
|
|
12727
13237
|
}
|
|
12728
|
-
await ensureDirectory((0,
|
|
12729
|
-
await ensureDirectory((0,
|
|
12730
|
-
await (0,
|
|
13238
|
+
await ensureDirectory((0, import_node_path13.dirname)(destination), false);
|
|
13239
|
+
await ensureDirectory((0, import_node_path13.dirname)(openVikingLogPath(config)), false);
|
|
13240
|
+
await (0, import_promises12.writeFile)(destination, rendered, "utf8");
|
|
12731
13241
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
12732
13242
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
12733
13243
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -12790,7 +13300,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
12790
13300
|
if (typeof parsed.default_user !== "string") {
|
|
12791
13301
|
return false;
|
|
12792
13302
|
}
|
|
12793
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
13303
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path13.join)(config.agentContextHome, "data")) {
|
|
12794
13304
|
return false;
|
|
12795
13305
|
}
|
|
12796
13306
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -12852,9 +13362,9 @@ function printWhatsNew(whatsNew) {
|
|
|
12852
13362
|
// src/manager.ts
|
|
12853
13363
|
var import_node_http2 = require("node:http");
|
|
12854
13364
|
var import_node_crypto2 = require("node:crypto");
|
|
12855
|
-
var
|
|
13365
|
+
var import_promises14 = require("node:fs/promises");
|
|
12856
13366
|
var import_node_os7 = require("node:os");
|
|
12857
|
-
var
|
|
13367
|
+
var import_node_path14 = require("node:path");
|
|
12858
13368
|
var STATIC_FILES = {
|
|
12859
13369
|
"/": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
12860
13370
|
"/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
@@ -12897,24 +13407,46 @@ async function memoryTree(config) {
|
|
|
12897
13407
|
const root = localMemoriesRoot(config);
|
|
12898
13408
|
return readTree(config, root, `viking://user/${uriSegment(config.user)}/memories`, "");
|
|
12899
13409
|
}
|
|
13410
|
+
async function resourcesTree(config) {
|
|
13411
|
+
const root = localResourcesRoot(config);
|
|
13412
|
+
try {
|
|
13413
|
+
return await readTree(config, root, "viking://resources", "", {
|
|
13414
|
+
parseMemoryDocuments: false,
|
|
13415
|
+
rootName: "resources"
|
|
13416
|
+
});
|
|
13417
|
+
} catch (err) {
|
|
13418
|
+
if (isMissingPathError(err)) {
|
|
13419
|
+
return {
|
|
13420
|
+
children: [],
|
|
13421
|
+
isDir: true,
|
|
13422
|
+
isShared: false,
|
|
13423
|
+
isSystem: false,
|
|
13424
|
+
name: "resources",
|
|
13425
|
+
relativePath: "",
|
|
13426
|
+
uri: "viking://resources"
|
|
13427
|
+
};
|
|
13428
|
+
}
|
|
13429
|
+
throw err;
|
|
13430
|
+
}
|
|
13431
|
+
}
|
|
12900
13432
|
async function readManagedMemory(config, uri) {
|
|
12901
13433
|
assertVikingUri(uri);
|
|
12902
13434
|
const path = localPathForMemoryUri(config, uri);
|
|
12903
13435
|
if (!path) {
|
|
12904
13436
|
throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
|
|
12905
13437
|
}
|
|
12906
|
-
const [content, pathStat] = await Promise.all([(0,
|
|
12907
|
-
const relativePath = (0,
|
|
13438
|
+
const [content, pathStat] = await Promise.all([(0, import_promises14.readFile)(path, "utf8"), (0, import_promises14.stat)(path)]);
|
|
13439
|
+
const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path).split(import_node_path14.sep).join("/");
|
|
12908
13440
|
const record = parseMemoryDocument(uri, content);
|
|
12909
13441
|
return {
|
|
12910
13442
|
content,
|
|
12911
13443
|
node: {
|
|
12912
13444
|
isDir: false,
|
|
12913
13445
|
isShared: isInSharedNamespace(config, uri),
|
|
12914
|
-
isSystem: isSystemMemoryName(path.split(
|
|
13446
|
+
isSystem: isSystemMemoryName(path.split(import_node_path14.sep).at(-1) ?? ""),
|
|
12915
13447
|
metadata: record?.metadata,
|
|
12916
13448
|
modTime: pathStat.mtime.toISOString(),
|
|
12917
|
-
name: path.split(
|
|
13449
|
+
name: path.split(import_node_path14.sep).at(-1) ?? uri,
|
|
12918
13450
|
relativePath,
|
|
12919
13451
|
sharedTeam: sharedTeamNameForUri(config, uri),
|
|
12920
13452
|
size: pathStat.size,
|
|
@@ -12981,7 +13513,8 @@ async function handleRequest(context, request, response) {
|
|
|
12981
13513
|
return;
|
|
12982
13514
|
}
|
|
12983
13515
|
if (request.method === "GET" && url.pathname === "/api/tree") {
|
|
12984
|
-
|
|
13516
|
+
const [tree, resourceTree] = await Promise.all([memoryTree(context.config), resourcesTree(context.config)]);
|
|
13517
|
+
writeJson(response, 200, { resourcesTree: resourceTree, tree });
|
|
12985
13518
|
return;
|
|
12986
13519
|
}
|
|
12987
13520
|
if (request.method === "GET" && url.pathname === "/api/memory") {
|
|
@@ -13200,21 +13733,20 @@ async function handleRequest(context, request, response) {
|
|
|
13200
13733
|
}
|
|
13201
13734
|
async function serveStatic(context, url, response) {
|
|
13202
13735
|
const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
|
|
13203
|
-
const content = await (0,
|
|
13736
|
+
const content = await (0, import_promises14.readFile)((0, import_node_path14.join)(toolRoot(), file.root ?? "manager", file.path));
|
|
13204
13737
|
const headers = { "content-type": file.contentType };
|
|
13205
|
-
if (
|
|
13738
|
+
if (file.root !== "docs") {
|
|
13206
13739
|
headers["cache-control"] = "no-store";
|
|
13207
13740
|
}
|
|
13208
13741
|
response.writeHead(200, headers);
|
|
13209
13742
|
response.end(content);
|
|
13210
13743
|
}
|
|
13211
|
-
async function readTree(config, path, uri, relativePath) {
|
|
13212
|
-
const pathStat = await (0,
|
|
13213
|
-
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : "memories";
|
|
13744
|
+
async function readTree(config, path, uri, relativePath, options = {}) {
|
|
13745
|
+
const pathStat = await (0, import_promises14.stat)(path);
|
|
13746
|
+
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
|
|
13214
13747
|
const isDir = pathStat.isDirectory();
|
|
13215
13748
|
if (!isDir) {
|
|
13216
|
-
const
|
|
13217
|
-
const record = parseMemoryDocument(uri, content);
|
|
13749
|
+
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path, "utf8").catch(() => ""));
|
|
13218
13750
|
return {
|
|
13219
13751
|
isDir: false,
|
|
13220
13752
|
isShared: isInSharedNamespace(config, uri),
|
|
@@ -13228,13 +13760,13 @@ async function readTree(config, path, uri, relativePath) {
|
|
|
13228
13760
|
uri
|
|
13229
13761
|
};
|
|
13230
13762
|
}
|
|
13231
|
-
const entries = await (0,
|
|
13763
|
+
const entries = await (0, import_promises14.readdir)(path, { withFileTypes: true });
|
|
13232
13764
|
const children = await Promise.all(
|
|
13233
13765
|
entries.sort(
|
|
13234
13766
|
(left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
|
|
13235
13767
|
).map((entry) => {
|
|
13236
13768
|
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
13237
|
-
return readTree(config, (0,
|
|
13769
|
+
return readTree(config, (0, import_node_path14.join)(path, entry.name), `${uri}/${entry.name}`, childRelative, options);
|
|
13238
13770
|
})
|
|
13239
13771
|
);
|
|
13240
13772
|
return {
|
|
@@ -13408,27 +13940,27 @@ async function removeManagedFolder(config, uri) {
|
|
|
13408
13940
|
if (!path) {
|
|
13409
13941
|
throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
|
|
13410
13942
|
}
|
|
13411
|
-
const pathStat = await (0,
|
|
13943
|
+
const pathStat = await (0, import_promises14.stat)(path);
|
|
13412
13944
|
if (!pathStat.isDirectory()) {
|
|
13413
13945
|
throw new Error(`Not a folder: ${uri}`);
|
|
13414
13946
|
}
|
|
13415
|
-
const relativePath = (0,
|
|
13416
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
13947
|
+
const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
|
|
13948
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
|
|
13417
13949
|
throw new Error("Refusing to remove a folder outside the memories tree.");
|
|
13418
13950
|
}
|
|
13419
13951
|
const fileUris = await fileUrisUnderFolder(config, path);
|
|
13420
13952
|
for (const fileUri of fileUris) {
|
|
13421
13953
|
await runForget(config, fileUri, {});
|
|
13422
13954
|
}
|
|
13423
|
-
await (0,
|
|
13955
|
+
await (0, import_promises14.rm)(path, { force: true, recursive: true });
|
|
13424
13956
|
console.log(`Removed folder: ${uri}`);
|
|
13425
13957
|
console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
|
|
13426
13958
|
}
|
|
13427
13959
|
async function fileUrisUnderFolder(config, folderPath) {
|
|
13428
|
-
const entries = await (0,
|
|
13960
|
+
const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
|
|
13429
13961
|
const uris = [];
|
|
13430
13962
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
13431
|
-
const path = (0,
|
|
13963
|
+
const path = (0, import_node_path14.join)(folderPath, entry.name);
|
|
13432
13964
|
if (entry.isDirectory()) {
|
|
13433
13965
|
uris.push(...await fileUrisUnderFolder(config, path));
|
|
13434
13966
|
} else if (entry.isFile()) {
|
|
@@ -13526,11 +14058,11 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
13526
14058
|
throw new Error(`${agent} executable was not found.`);
|
|
13527
14059
|
}
|
|
13528
14060
|
const prompt = consolidationPrompt(sources);
|
|
13529
|
-
const stagingDir = await (0,
|
|
13530
|
-
const promptPath = (0,
|
|
14061
|
+
const stagingDir = await (0, import_promises14.mkdtemp)((0, import_node_path14.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
|
|
14062
|
+
const promptPath = (0, import_node_path14.join)(stagingDir, "prompt.txt");
|
|
13531
14063
|
try {
|
|
13532
|
-
await (0,
|
|
13533
|
-
await (0,
|
|
14064
|
+
await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
|
|
14065
|
+
await (0, import_promises14.chmod)(promptPath, 384);
|
|
13534
14066
|
const script = consolidationAgentScript(agent, executable);
|
|
13535
14067
|
const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
|
|
13536
14068
|
allowFailure: true,
|
|
@@ -13546,7 +14078,7 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
13546
14078
|
}
|
|
13547
14079
|
return draft;
|
|
13548
14080
|
} finally {
|
|
13549
|
-
await (0,
|
|
14081
|
+
await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
|
|
13550
14082
|
}
|
|
13551
14083
|
}
|
|
13552
14084
|
function consolidationAgentScript(agent, executable) {
|
|
@@ -13684,7 +14216,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
|
|
|
13684
14216
|
}
|
|
13685
14217
|
}
|
|
13686
14218
|
function localMemoriesRoot(config) {
|
|
13687
|
-
return (0,
|
|
14219
|
+
return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
|
|
14220
|
+
}
|
|
14221
|
+
function localResourcesRoot(config) {
|
|
14222
|
+
return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "resources");
|
|
13688
14223
|
}
|
|
13689
14224
|
function localPathForMemoryUri(config, uri) {
|
|
13690
14225
|
const prefix = `viking://user/${uriSegment(config.user)}/memories`;
|
|
@@ -13696,14 +14231,17 @@ function localPathForMemoryUri(config, uri) {
|
|
|
13696
14231
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
13697
14232
|
return void 0;
|
|
13698
14233
|
}
|
|
13699
|
-
return (0,
|
|
14234
|
+
return (0, import_node_path14.join)(localMemoriesRoot(config), ...segments);
|
|
14235
|
+
}
|
|
14236
|
+
function isMissingPathError(err) {
|
|
14237
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
13700
14238
|
}
|
|
13701
14239
|
function localPathToMemoryUri(config, path) {
|
|
13702
|
-
const relativePath = (0,
|
|
13703
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
14240
|
+
const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
|
|
14241
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
|
|
13704
14242
|
throw new Error(`Path is outside the memories tree: ${path}`);
|
|
13705
14243
|
}
|
|
13706
|
-
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(
|
|
14244
|
+
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path14.sep).join("/")}`;
|
|
13707
14245
|
}
|
|
13708
14246
|
async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
|
|
13709
14247
|
const prefix = "viking://";
|
|
@@ -13908,7 +14446,7 @@ async function main() {
|
|
|
13908
14446
|
program2.command("init-manifest").description("Create or update a per-developer seed manifest from one or more repo roots").option("--dry-run", "Print the manifest without writing it").option("--path <path>", "Manifest path; defaults to THREADNOTE_MANIFEST or ~/.openviking/seed-manifest.yaml").option("--replace", "Replace the manifest instead of merging with existing projects").option("--repo <path>", "Repo root to include; repeat for multiple repos", collectOption, []).action(async (options) => {
|
|
13909
14447
|
await runInitManifest(getRuntimeConfig(program2), options);
|
|
13910
14448
|
});
|
|
13911
|
-
program2.command("seed-skills").description("Seed Codex
|
|
14449
|
+
program2.command("seed-skills").description("Seed Codex/Claude skills and Claude command markdown files as a searchable catalog").option("--dry-run", "Print skill files and ov commands without importing").option("--manifest <path>", "Manifest path for repo-local skill discovery").option("--native", "Use native OpenViking skill ingestion; requires a working VLM config").action(async (options) => {
|
|
13912
14450
|
await runSeedSkills(getRuntimeConfig(program2, options.manifest), options);
|
|
13913
14451
|
});
|
|
13914
14452
|
program2.command("mcp-install").description("Install OpenViking MCP config for a supported agent").argument("<agent>", "codex, claude, cursor, or copilot").option("--apply", "Actually modify the selected agent config").option("--name <name>", "MCP server name", OPENVIKING_MCP_NAME).option("--native-http", "Install OpenViking native HTTP MCP endpoint instead of the local stdio adapter").option("--scope <scope>", "Claude MCP config scope: user, local, or project", parseClaudeMcpScope, "user").option("--url <url>", "OpenViking native HTTP MCP URL").option("--bearer-token-env-var <name>", "Environment variable containing the local API key").action(async (agent, options) => {
|