@skein-code/cli 0.3.19 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/cli.js +520 -377
- package/dist/cli.js.map +1 -1
- package/docs/NEXT_STEPS.md +4 -2
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
7
|
-
import { basename as basename13, resolve as
|
|
7
|
+
import { basename as basename13, resolve as resolve24 } from "node:path";
|
|
8
8
|
import { Command, Option } from "commander";
|
|
9
9
|
import chalk4 from "chalk";
|
|
10
10
|
|
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.20",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -256,7 +256,8 @@ var package_default = {
|
|
|
256
256
|
"Prompt and timeline diagnostics expose the repository-first reuse ladder without blocking legitimate writes",
|
|
257
257
|
"Known tool changes refresh only their affected local-index paths before the next model turn",
|
|
258
258
|
"File ctime closes same-size and restored-mtime zero-hit freshness gaps without full-content scans",
|
|
259
|
-
"Repeated empty or unchanged search calls stop through the existing recovery circuit"
|
|
259
|
+
"Repeated empty or unchanged search calls stop through the existing recovery circuit",
|
|
260
|
+
"Git recency is an isolated, bounded tie-break with exact HEAD-bound cache invalidation and non-Git degradation"
|
|
260
261
|
]
|
|
261
262
|
},
|
|
262
263
|
bin: {
|
|
@@ -2611,9 +2612,9 @@ function countExplicitPaths(value) {
|
|
|
2611
2612
|
}
|
|
2612
2613
|
|
|
2613
2614
|
// src/context/local-index.ts
|
|
2614
|
-
import { createHash as
|
|
2615
|
-
import { lstat as
|
|
2616
|
-
import { basename as basename5, dirname as dirname7, extname as extname2, isAbsolute as
|
|
2615
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
2616
|
+
import { lstat as lstat9, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
2617
|
+
import { basename as basename5, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute4, join as join8, posix, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
|
|
2617
2618
|
import fg from "fast-glob";
|
|
2618
2619
|
import ts from "typescript";
|
|
2619
2620
|
import { z as z3 } from "zod";
|
|
@@ -3046,6 +3047,371 @@ function hash(value) {
|
|
|
3046
3047
|
return createHash5("sha256").update(value).digest("hex");
|
|
3047
3048
|
}
|
|
3048
3049
|
|
|
3050
|
+
// src/context/git-recency.ts
|
|
3051
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
3052
|
+
import { resolve as resolve9 } from "node:path";
|
|
3053
|
+
|
|
3054
|
+
// src/utils/process.ts
|
|
3055
|
+
import { spawn } from "node:child_process";
|
|
3056
|
+
import { constants as constants3 } from "node:fs";
|
|
3057
|
+
import { access as access2, lstat as lstat8, realpath as realpath6 } from "node:fs/promises";
|
|
3058
|
+
import { delimiter, isAbsolute as isAbsolute3, join as join7, resolve as resolve8 } from "node:path";
|
|
3059
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3060
|
+
async function resolveExecutableRuntime(command2, cwd, excludedRoots = []) {
|
|
3061
|
+
const realRoots = await Promise.all(excludedRoots.map(async (root) => {
|
|
3062
|
+
try {
|
|
3063
|
+
return await realpath6(root);
|
|
3064
|
+
} catch {
|
|
3065
|
+
return resolve8(root);
|
|
3066
|
+
}
|
|
3067
|
+
}));
|
|
3068
|
+
const pathEntries = (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean);
|
|
3069
|
+
const safeDirectories2 = [];
|
|
3070
|
+
let executable2;
|
|
3071
|
+
const explicit = isAbsolute3(command2) || command2.includes("/") || command2.includes("\\");
|
|
3072
|
+
const explicitPath = explicit ? resolve8(cwd, command2) : void 0;
|
|
3073
|
+
for (const entry of pathEntries) {
|
|
3074
|
+
let directory;
|
|
3075
|
+
try {
|
|
3076
|
+
directory = await realpath6(resolve8(cwd, entry));
|
|
3077
|
+
if (!(await lstat8(directory)).isDirectory()) continue;
|
|
3078
|
+
} catch {
|
|
3079
|
+
continue;
|
|
3080
|
+
}
|
|
3081
|
+
if (realRoots.some((root) => isInside(root, directory))) continue;
|
|
3082
|
+
let contaminated = false;
|
|
3083
|
+
if (!explicit) {
|
|
3084
|
+
for (const name of executableNames(command2)) {
|
|
3085
|
+
const candidate = join7(directory, name);
|
|
3086
|
+
const resolvedCandidate = await usableExecutable(candidate);
|
|
3087
|
+
if (!resolvedCandidate) continue;
|
|
3088
|
+
if (realRoots.some((root) => isInside(root, resolvedCandidate))) {
|
|
3089
|
+
contaminated = true;
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
executable2 ??= resolvedCandidate;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
if (!contaminated && !safeDirectories2.includes(directory)) safeDirectories2.push(directory);
|
|
3096
|
+
}
|
|
3097
|
+
if (explicitPath) executable2 = await usableExecutable(explicitPath);
|
|
3098
|
+
if (!executable2) return void 0;
|
|
3099
|
+
return { executable: executable2, path: safeDirectories2.join(delimiter) };
|
|
3100
|
+
}
|
|
3101
|
+
async function usableExecutable(candidate) {
|
|
3102
|
+
try {
|
|
3103
|
+
await access2(candidate, process.platform === "win32" ? constants3.F_OK : constants3.X_OK);
|
|
3104
|
+
const resolvedCandidate = await realpath6(candidate);
|
|
3105
|
+
return (await lstat8(resolvedCandidate)).isFile() ? resolvedCandidate : void 0;
|
|
3106
|
+
} catch {
|
|
3107
|
+
return void 0;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
function executableNames(command2) {
|
|
3111
|
+
if (process.platform !== "win32") return [command2];
|
|
3112
|
+
const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
|
3113
|
+
return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
|
|
3114
|
+
}
|
|
3115
|
+
function runProcess(command2, args, options) {
|
|
3116
|
+
return new Promise((resolve25, reject) => {
|
|
3117
|
+
const started = Date.now();
|
|
3118
|
+
const environment = options.inheritEnv === false ? {} : { ...process.env };
|
|
3119
|
+
for (const name of options.unsetEnv ?? []) delete environment[name];
|
|
3120
|
+
for (const name of Object.keys(environment)) {
|
|
3121
|
+
if (options.unsetEnvPrefixes?.some((prefix) => name.startsWith(prefix))) {
|
|
3122
|
+
delete environment[name];
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
Object.assign(environment, options.env);
|
|
3126
|
+
const child = spawn(command2, args, {
|
|
3127
|
+
cwd: options.cwd,
|
|
3128
|
+
env: environment,
|
|
3129
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3130
|
+
signal: options.signal
|
|
3131
|
+
});
|
|
3132
|
+
const maxBytes = options.maxOutputBytes ?? 1e6;
|
|
3133
|
+
let stdout = "";
|
|
3134
|
+
let stderr = "";
|
|
3135
|
+
let stdoutBytes = 0;
|
|
3136
|
+
let stderrBytes = 0;
|
|
3137
|
+
let stdoutObservedBytes = 0;
|
|
3138
|
+
let stderrObservedBytes = 0;
|
|
3139
|
+
let timedOut = false;
|
|
3140
|
+
let outputLimitReached = false;
|
|
3141
|
+
let callbackError;
|
|
3142
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
3143
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
3144
|
+
const stdoutCallbackDecoder = new StringDecoder("utf8");
|
|
3145
|
+
const stderrCallbackDecoder = new StringDecoder("utf8");
|
|
3146
|
+
const append = (decoder, chunk, usedBytes) => {
|
|
3147
|
+
if (usedBytes >= maxBytes) return { text: "", usedBytes };
|
|
3148
|
+
const selected = chunk.subarray(0, maxBytes - usedBytes);
|
|
3149
|
+
return { text: decoder.write(selected), usedBytes: usedBytes + selected.length };
|
|
3150
|
+
};
|
|
3151
|
+
const notify = (callback, decoder, chunk) => {
|
|
3152
|
+
if (!callback || callbackError) return;
|
|
3153
|
+
try {
|
|
3154
|
+
const decoded = decoder.write(chunk);
|
|
3155
|
+
if (decoded) callback(decoded);
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
callbackError = error;
|
|
3158
|
+
child.kill("SIGTERM");
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
child.stdout.on("data", (chunk) => {
|
|
3162
|
+
stdoutObservedBytes += chunk.length;
|
|
3163
|
+
if (options.stopOnOutputLimit && stdoutObservedBytes > maxBytes && !outputLimitReached) {
|
|
3164
|
+
outputLimitReached = true;
|
|
3165
|
+
child.kill("SIGTERM");
|
|
3166
|
+
}
|
|
3167
|
+
const appended = append(stdoutDecoder, chunk, stdoutBytes);
|
|
3168
|
+
stdout += appended.text;
|
|
3169
|
+
stdoutBytes = appended.usedBytes;
|
|
3170
|
+
notify(options.onStdout, stdoutCallbackDecoder, chunk);
|
|
3171
|
+
});
|
|
3172
|
+
child.stderr.on("data", (chunk) => {
|
|
3173
|
+
stderrObservedBytes += chunk.length;
|
|
3174
|
+
if (options.stopOnOutputLimit && stderrObservedBytes > maxBytes && !outputLimitReached) {
|
|
3175
|
+
outputLimitReached = true;
|
|
3176
|
+
child.kill("SIGTERM");
|
|
3177
|
+
}
|
|
3178
|
+
const appended = append(stderrDecoder, chunk, stderrBytes);
|
|
3179
|
+
stderr += appended.text;
|
|
3180
|
+
stderrBytes = appended.usedBytes;
|
|
3181
|
+
notify(options.onStderr, stderrCallbackDecoder, chunk);
|
|
3182
|
+
});
|
|
3183
|
+
child.on("error", reject);
|
|
3184
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
3185
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => {
|
|
3186
|
+
timedOut = true;
|
|
3187
|
+
child.kill("SIGTERM");
|
|
3188
|
+
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
3189
|
+
}, timeoutMs) : void 0;
|
|
3190
|
+
child.on("close", (code) => {
|
|
3191
|
+
if (timeout) clearTimeout(timeout);
|
|
3192
|
+
const stdoutTail = stdoutDecoder.end();
|
|
3193
|
+
const stderrTail = stderrDecoder.end();
|
|
3194
|
+
if (Buffer.byteLength(stdout) + Buffer.byteLength(stdoutTail) <= maxBytes) stdout += stdoutTail;
|
|
3195
|
+
if (Buffer.byteLength(stderr) + Buffer.byteLength(stderrTail) <= maxBytes) stderr += stderrTail;
|
|
3196
|
+
try {
|
|
3197
|
+
const stdoutCallbackTail = stdoutCallbackDecoder.end();
|
|
3198
|
+
const stderrCallbackTail = stderrCallbackDecoder.end();
|
|
3199
|
+
if (stdoutCallbackTail && options.onStdout && !callbackError) options.onStdout(stdoutCallbackTail);
|
|
3200
|
+
if (stderrCallbackTail && options.onStderr && !callbackError) options.onStderr(stderrCallbackTail);
|
|
3201
|
+
} catch (error) {
|
|
3202
|
+
callbackError = error;
|
|
3203
|
+
}
|
|
3204
|
+
if (callbackError) {
|
|
3205
|
+
reject(callbackError);
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
resolve25({
|
|
3209
|
+
command: [command2, ...args].join(" "),
|
|
3210
|
+
exitCode: code ?? (timedOut ? 124 : 1),
|
|
3211
|
+
stdout,
|
|
3212
|
+
stderr,
|
|
3213
|
+
timedOut,
|
|
3214
|
+
durationMs: Date.now() - started,
|
|
3215
|
+
stdoutBytes: stdoutObservedBytes,
|
|
3216
|
+
stderrBytes: stderrObservedBytes,
|
|
3217
|
+
stdoutTruncated: stdoutObservedBytes > stdoutBytes,
|
|
3218
|
+
stderrTruncated: stderrObservedBytes > stderrBytes
|
|
3219
|
+
});
|
|
3220
|
+
});
|
|
3221
|
+
if (options.stdin) child.stdin.end(options.stdin);
|
|
3222
|
+
else child.stdin.end();
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
function runShell(command2, cwd, options = {}) {
|
|
3226
|
+
const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
|
|
3227
|
+
const args = process.platform === "win32" ? ["/d", "/s", "/c", command2] : ["-lc", command2];
|
|
3228
|
+
return runProcess(shell, args, { ...options, cwd });
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
// src/utils/git.ts
|
|
3232
|
+
var unsafeInheritedGitEnvironment = [
|
|
3233
|
+
"GIT_DIR",
|
|
3234
|
+
"GIT_WORK_TREE",
|
|
3235
|
+
"GIT_COMMON_DIR",
|
|
3236
|
+
"GIT_INDEX_FILE",
|
|
3237
|
+
"GIT_OBJECT_DIRECTORY",
|
|
3238
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
3239
|
+
"GIT_NAMESPACE",
|
|
3240
|
+
"GIT_CEILING_DIRECTORIES",
|
|
3241
|
+
"GIT_DISCOVERY_ACROSS_FILESYSTEM",
|
|
3242
|
+
"GIT_EXTERNAL_DIFF",
|
|
3243
|
+
"GIT_DIFF_OPTS",
|
|
3244
|
+
"GIT_SSH",
|
|
3245
|
+
"GIT_SSH_COMMAND",
|
|
3246
|
+
"GIT_PROXY_COMMAND",
|
|
3247
|
+
"GIT_ASKPASS",
|
|
3248
|
+
"SSH_ASKPASS",
|
|
3249
|
+
"GIT_EXEC_PATH",
|
|
3250
|
+
"GIT_TEMPLATE_DIR",
|
|
3251
|
+
"GIT_CONFIG",
|
|
3252
|
+
"GIT_CONFIG_PARAMETERS",
|
|
3253
|
+
"GIT_QUARANTINE_PATH",
|
|
3254
|
+
"Path"
|
|
3255
|
+
];
|
|
3256
|
+
var isolatedGitEnvironment = {
|
|
3257
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
3258
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
3259
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
3260
|
+
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
3261
|
+
GIT_NO_REPLACE_OBJECTS: "1",
|
|
3262
|
+
GIT_PAGER: "cat",
|
|
3263
|
+
GIT_EDITOR: "true",
|
|
3264
|
+
GIT_SEQUENCE_EDITOR: "true",
|
|
3265
|
+
GIT_CONFIG_COUNT: "4",
|
|
3266
|
+
GIT_CONFIG_KEY_0: "core.hooksPath",
|
|
3267
|
+
GIT_CONFIG_VALUE_0: "/dev/null",
|
|
3268
|
+
GIT_CONFIG_KEY_1: "core.fsmonitor",
|
|
3269
|
+
GIT_CONFIG_VALUE_1: "false",
|
|
3270
|
+
GIT_CONFIG_KEY_2: "credential.helper",
|
|
3271
|
+
GIT_CONFIG_VALUE_2: "",
|
|
3272
|
+
GIT_CONFIG_KEY_3: "protocol.ext.allow",
|
|
3273
|
+
GIT_CONFIG_VALUE_3: "never"
|
|
3274
|
+
};
|
|
3275
|
+
function runIsolatedGit(runtime, args, cwd, options = {}) {
|
|
3276
|
+
return runProcess(runtime.executable, args, {
|
|
3277
|
+
cwd,
|
|
3278
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
3279
|
+
maxOutputBytes: options.maxOutputBytes ?? 2e6,
|
|
3280
|
+
...options.stopOnOutputLimit !== void 0 ? { stopOnOutputLimit: options.stopOnOutputLimit } : {},
|
|
3281
|
+
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
3282
|
+
unsetEnv: unsafeInheritedGitEnvironment,
|
|
3283
|
+
unsetEnvPrefixes: ["GIT_"],
|
|
3284
|
+
...options.stdin !== void 0 ? { stdin: options.stdin } : {},
|
|
3285
|
+
...options.signal ? { signal: options.signal } : {}
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
// src/context/git-recency.ts
|
|
3290
|
+
var MAX_COMMITS = 128;
|
|
3291
|
+
var MAX_OUTPUT_BYTES = 1e6;
|
|
3292
|
+
var TIMEOUT_MS = 2e3;
|
|
3293
|
+
var MAX_RECENCY_SCORE = 1e-3;
|
|
3294
|
+
var commitHashPattern = /^(?:[a-f\d]{40}|[a-f\d]{64})$/u;
|
|
3295
|
+
var GitRecencyCollector = class {
|
|
3296
|
+
roots;
|
|
3297
|
+
states = /* @__PURE__ */ new Map();
|
|
3298
|
+
constructor(roots) {
|
|
3299
|
+
this.roots = roots.map((root) => resolve9(root));
|
|
3300
|
+
}
|
|
3301
|
+
async collect() {
|
|
3302
|
+
const snapshots = await Promise.all(this.roots.map((root) => this.collectRoot(root)));
|
|
3303
|
+
const scores = /* @__PURE__ */ new Map();
|
|
3304
|
+
for (const snapshot of snapshots) {
|
|
3305
|
+
for (const [path, score] of snapshot.scores) {
|
|
3306
|
+
scores.set(path, Math.max(score, scores.get(path) ?? 0));
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return {
|
|
3310
|
+
generation: digest(snapshots.map(({ root, generation }) => `${root}\0${generation}`).join("\n")),
|
|
3311
|
+
scores
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
async collectRoot(root) {
|
|
3315
|
+
const state = this.states.get(root) ?? {
|
|
3316
|
+
runtime: void 0,
|
|
3317
|
+
runtimeResolved: false,
|
|
3318
|
+
head: void 0,
|
|
3319
|
+
snapshot: void 0
|
|
3320
|
+
};
|
|
3321
|
+
this.states.set(root, state);
|
|
3322
|
+
try {
|
|
3323
|
+
if (!state.runtimeResolved) {
|
|
3324
|
+
state.runtime = await resolveExecutableRuntime("git", root, this.roots);
|
|
3325
|
+
state.runtimeResolved = true;
|
|
3326
|
+
}
|
|
3327
|
+
if (!state.runtime) return unavailableSnapshot(root, "missing");
|
|
3328
|
+
const headResult = await runIsolatedGit(
|
|
3329
|
+
state.runtime,
|
|
3330
|
+
["rev-parse", "--verify", "HEAD"],
|
|
3331
|
+
root,
|
|
3332
|
+
{ timeoutMs: TIMEOUT_MS, maxOutputBytes: 256, stopOnOutputLimit: true }
|
|
3333
|
+
);
|
|
3334
|
+
const head = headResult.stdout.trim();
|
|
3335
|
+
if (headResult.exitCode !== 0 || headResult.timedOut || headResult.stdoutTruncated || headResult.stderrTruncated || !commitHashPattern.test(head)) {
|
|
3336
|
+
state.head = void 0;
|
|
3337
|
+
state.snapshot = unavailableSnapshot(root, "no-head");
|
|
3338
|
+
return state.snapshot;
|
|
3339
|
+
}
|
|
3340
|
+
if (state.head === head && state.snapshot) return state.snapshot;
|
|
3341
|
+
state.head = head;
|
|
3342
|
+
state.snapshot = await collectRootGitLog(root, state.runtime);
|
|
3343
|
+
return state.snapshot;
|
|
3344
|
+
} catch {
|
|
3345
|
+
state.head = void 0;
|
|
3346
|
+
state.snapshot = unavailableSnapshot(root, "error");
|
|
3347
|
+
return state.snapshot;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
};
|
|
3351
|
+
async function collectRootGitLog(root, runtime) {
|
|
3352
|
+
try {
|
|
3353
|
+
const result = await runIsolatedGit(runtime, [
|
|
3354
|
+
"--no-pager",
|
|
3355
|
+
"log",
|
|
3356
|
+
"--topo-order",
|
|
3357
|
+
`--max-count=${MAX_COMMITS}`,
|
|
3358
|
+
"--format=%x00%H",
|
|
3359
|
+
"--name-only",
|
|
3360
|
+
"-z",
|
|
3361
|
+
"--no-renames",
|
|
3362
|
+
"--no-ext-diff",
|
|
3363
|
+
"--no-textconv",
|
|
3364
|
+
"--relative",
|
|
3365
|
+
"--",
|
|
3366
|
+
"."
|
|
3367
|
+
], root, {
|
|
3368
|
+
timeoutMs: TIMEOUT_MS,
|
|
3369
|
+
maxOutputBytes: MAX_OUTPUT_BYTES,
|
|
3370
|
+
stopOnOutputLimit: true
|
|
3371
|
+
});
|
|
3372
|
+
if (result.exitCode !== 0 || result.timedOut || result.stdoutTruncated || result.stderrTruncated) {
|
|
3373
|
+
const reason = result.timedOut ? "timeout" : result.stdoutTruncated || result.stderrTruncated ? "output-limit" : `exit-${result.exitCode}`;
|
|
3374
|
+
return unavailableSnapshot(root, reason);
|
|
3375
|
+
}
|
|
3376
|
+
return {
|
|
3377
|
+
root,
|
|
3378
|
+
generation: digest(result.stdout),
|
|
3379
|
+
scores: parseGitLog(root, result.stdout)
|
|
3380
|
+
};
|
|
3381
|
+
} catch {
|
|
3382
|
+
return unavailableSnapshot(root, "error");
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
function parseGitLog(root, output2) {
|
|
3386
|
+
const scores = /* @__PURE__ */ new Map();
|
|
3387
|
+
const records = output2.split("\0");
|
|
3388
|
+
let commitRank = -1;
|
|
3389
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
3390
|
+
const record = records[index] ?? "";
|
|
3391
|
+
if (!record) {
|
|
3392
|
+
const commit = records[index + 1]?.trim() ?? "";
|
|
3393
|
+
if (commitHashPattern.test(commit)) {
|
|
3394
|
+
commitRank += 1;
|
|
3395
|
+
index += 1;
|
|
3396
|
+
}
|
|
3397
|
+
continue;
|
|
3398
|
+
}
|
|
3399
|
+
if (commitRank < 0) continue;
|
|
3400
|
+
const path = record.startsWith("\n") ? record.slice(1) : record;
|
|
3401
|
+
if (!path) continue;
|
|
3402
|
+
const absolutePath = resolve9(root, path);
|
|
3403
|
+
if (!isInside(root, absolutePath) || scores.has(absolutePath)) continue;
|
|
3404
|
+
scores.set(absolutePath, MAX_RECENCY_SCORE / (commitRank + 1));
|
|
3405
|
+
}
|
|
3406
|
+
return scores;
|
|
3407
|
+
}
|
|
3408
|
+
function unavailableSnapshot(root, reason) {
|
|
3409
|
+
return { root, generation: digest(`unavailable\0${reason}`), scores: /* @__PURE__ */ new Map() };
|
|
3410
|
+
}
|
|
3411
|
+
function digest(value) {
|
|
3412
|
+
return createHash6("sha256").update(value, "utf8").digest("hex").slice(0, 16);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3049
3415
|
// src/context/local-index.ts
|
|
3050
3416
|
var contentHashSchema = z3.string().regex(/^[a-f\d]{64}$/u);
|
|
3051
3417
|
var indexedChunkSchema = z3.object({
|
|
@@ -3111,14 +3477,16 @@ var MIN_STRUCTURAL_CHUNK_LINES = 12;
|
|
|
3111
3477
|
var LocalContextIndex = class {
|
|
3112
3478
|
constructor(roots) {
|
|
3113
3479
|
this.roots = roots;
|
|
3114
|
-
this.roots = roots.map((root) =>
|
|
3480
|
+
this.roots = roots.map((root) => resolve10(root));
|
|
3115
3481
|
this.workspace = new WorkspaceAccess(this.roots);
|
|
3116
|
-
this.
|
|
3482
|
+
this.gitRecency = new GitRecencyCollector(this.roots);
|
|
3483
|
+
this.indexPath = join8(resolveProjectNamespaceSync(this.roots[0] ?? process.cwd()).active, "index.json");
|
|
3117
3484
|
}
|
|
3118
3485
|
roots;
|
|
3119
3486
|
index;
|
|
3120
3487
|
workspace;
|
|
3121
3488
|
queryCache = /* @__PURE__ */ new Map();
|
|
3489
|
+
gitRecency;
|
|
3122
3490
|
queryCacheHits = 0;
|
|
3123
3491
|
queryCacheMisses = 0;
|
|
3124
3492
|
fingerprintCache;
|
|
@@ -3132,13 +3500,13 @@ var LocalContextIndex = class {
|
|
|
3132
3500
|
this.roots[0] ?? process.cwd(),
|
|
3133
3501
|
dirname7(this.indexPath)
|
|
3134
3502
|
);
|
|
3135
|
-
const info = await
|
|
3503
|
+
const info = await lstat9(this.indexPath);
|
|
3136
3504
|
if (!info.isFile() || info.isSymbolicLink()) return false;
|
|
3137
3505
|
const parsed = localIndexSchema.parse(JSON.parse(await readFile3(this.indexPath, "utf8")));
|
|
3138
3506
|
const files = [];
|
|
3139
3507
|
for (const file of parsed.files) {
|
|
3140
3508
|
try {
|
|
3141
|
-
if (!this.roots.includes(file.root) ||
|
|
3509
|
+
if (!this.roots.includes(file.root) || resolve10(file.root, file.path) !== file.absolutePath) continue;
|
|
3142
3510
|
const safe = await this.workspace.resolvePath(file.absolutePath, { expect: "file" });
|
|
3143
3511
|
if (safe !== file.absolutePath) continue;
|
|
3144
3512
|
const { ctimeMs, ...persistedFile } = file;
|
|
@@ -3215,19 +3583,21 @@ var LocalContextIndex = class {
|
|
|
3215
3583
|
await this.flushDirty();
|
|
3216
3584
|
await this.ensureCurrentIndex();
|
|
3217
3585
|
const limit = Math.max(1, Math.floor(topK));
|
|
3218
|
-
|
|
3219
|
-
|
|
3586
|
+
let recency = await this.gitRecency.collect();
|
|
3587
|
+
const cached = this.getCachedHits(query, limit, recency.generation);
|
|
3588
|
+
let hits = cached ?? this.rank(query, limit, recency.scores);
|
|
3220
3589
|
if (!await this.hitsAreCurrent(hits)) {
|
|
3221
3590
|
await this.buildWithOptions(void 0, true);
|
|
3222
|
-
|
|
3591
|
+
recency = await this.gitRecency.collect();
|
|
3592
|
+
hits = this.rank(query, limit, recency.scores);
|
|
3223
3593
|
if (!await this.hitsAreCurrent(hits)) return [];
|
|
3224
3594
|
}
|
|
3225
|
-
this.cacheHits(query, limit, hits);
|
|
3595
|
+
this.cacheHits(query, limit, recency.generation, hits);
|
|
3226
3596
|
return cloneHits(hits);
|
|
3227
3597
|
}
|
|
3228
3598
|
invalidate(paths) {
|
|
3229
3599
|
for (const path of paths) {
|
|
3230
|
-
const absolutePath =
|
|
3600
|
+
const absolutePath = isAbsolute4(path) ? resolve10(path) : resolve10(this.roots[0] ?? process.cwd(), path);
|
|
3231
3601
|
if (this.roots.some((root) => isWithinRoot(root, absolutePath))) {
|
|
3232
3602
|
this.dirtyPaths.add(absolutePath);
|
|
3233
3603
|
}
|
|
@@ -3424,6 +3794,7 @@ var LocalContextIndex = class {
|
|
|
3424
3794
|
);
|
|
3425
3795
|
await atomicWrite(this.indexPath, `${JSON.stringify(this.index)}
|
|
3426
3796
|
`, 384);
|
|
3797
|
+
await this.gitRecency.collect();
|
|
3427
3798
|
onProgress?.({ phase: "done", completed: files.length, total: files.length });
|
|
3428
3799
|
return {
|
|
3429
3800
|
files: files.length,
|
|
@@ -3517,7 +3888,7 @@ var LocalContextIndex = class {
|
|
|
3517
3888
|
followSymbolicLinks: false,
|
|
3518
3889
|
ignore: ignorePatterns
|
|
3519
3890
|
});
|
|
3520
|
-
for (const path of paths) discovered.push({ root, path, absolutePath:
|
|
3891
|
+
for (const path of paths) discovered.push({ root, path, absolutePath: resolve10(root, path) });
|
|
3521
3892
|
}
|
|
3522
3893
|
discovered.sort((left, right) => left.absolutePath.localeCompare(right.absolutePath));
|
|
3523
3894
|
return discovered;
|
|
@@ -3537,7 +3908,7 @@ var LocalContextIndex = class {
|
|
|
3537
3908
|
});
|
|
3538
3909
|
return matches.length === 1 ? { root, path, absolutePath } : void 0;
|
|
3539
3910
|
}
|
|
3540
|
-
rank(query, topK) {
|
|
3911
|
+
rank(query, topK, recencyScores) {
|
|
3541
3912
|
const index = this.index;
|
|
3542
3913
|
const files = index?.files ?? [];
|
|
3543
3914
|
const chunks = files.flatMap((file) => file.chunks);
|
|
@@ -3567,11 +3938,12 @@ var LocalContextIndex = class {
|
|
|
3567
3938
|
averageLength
|
|
3568
3939
|
);
|
|
3569
3940
|
const graph = graphBoosts.get(chunk.absolutePath)?.score ?? 0;
|
|
3570
|
-
const
|
|
3571
|
-
|
|
3572
|
-
|
|
3941
|
+
const recency = recencyScores.get(chunk.absolutePath) ?? 0;
|
|
3942
|
+
const score = lexical.bm25 + lexical.path + lexical.symbol + lexical.phrase + graph + recency;
|
|
3943
|
+
return { chunk, lexical, graph, recency, score };
|
|
3944
|
+
}).filter(({ lexical, graph }) => lexical.bm25 + lexical.path + lexical.symbol + lexical.phrase + graph > 0).sort((left, right) => right.score - left.score || left.chunk.absolutePath.localeCompare(right.chunk.absolutePath));
|
|
3573
3945
|
const covered = scored.filter(({ lexical, graph }) => graph > 0 || hasSufficientQueryCoverage(lexical.matchedTerms, coverageTerms));
|
|
3574
|
-
return (covered.length ? covered : scored).slice(0, topK).map(({ chunk, lexical, graph, score }) => {
|
|
3946
|
+
return (covered.length ? covered : scored).slice(0, topK).map(({ chunk, lexical, graph, recency, score }) => {
|
|
3575
3947
|
const file = filesByPath.get(chunk.absolutePath);
|
|
3576
3948
|
const breakdown = {
|
|
3577
3949
|
bm25: roundScore(lexical.bm25),
|
|
@@ -3579,6 +3951,7 @@ var LocalContextIndex = class {
|
|
|
3579
3951
|
symbol: roundScore(lexical.symbol),
|
|
3580
3952
|
phrase: roundScore(lexical.phrase),
|
|
3581
3953
|
graph: roundScore(graph),
|
|
3954
|
+
recency: roundScore(recency),
|
|
3582
3955
|
total: roundScore(score)
|
|
3583
3956
|
};
|
|
3584
3957
|
const provenance = {
|
|
@@ -3594,7 +3967,7 @@ var LocalContextIndex = class {
|
|
|
3594
3967
|
endLine: chunk.endLine,
|
|
3595
3968
|
content: chunk.content,
|
|
3596
3969
|
score,
|
|
3597
|
-
source: "local-bm25+path+symbol+graph",
|
|
3970
|
+
source: "local-bm25+path+symbol+graph+recency",
|
|
3598
3971
|
...chunk.symbol ? { symbol: chunk.symbol } : {},
|
|
3599
3972
|
provenance
|
|
3600
3973
|
};
|
|
@@ -3620,13 +3993,13 @@ var LocalContextIndex = class {
|
|
|
3620
3993
|
}
|
|
3621
3994
|
return true;
|
|
3622
3995
|
}
|
|
3623
|
-
getCachedHits(query, topK) {
|
|
3996
|
+
getCachedHits(query, topK, rankingGeneration) {
|
|
3624
3997
|
const generation = this.index?.generation;
|
|
3625
3998
|
if (!generation) {
|
|
3626
3999
|
this.queryCacheMisses += 1;
|
|
3627
4000
|
return void 0;
|
|
3628
4001
|
}
|
|
3629
|
-
const key = `${generation}\0${topK}\0${query}`;
|
|
4002
|
+
const key = `${generation}\0${rankingGeneration}\0${topK}\0${query}`;
|
|
3630
4003
|
const cached = this.queryCache.get(key);
|
|
3631
4004
|
if (!cached) {
|
|
3632
4005
|
this.queryCacheMisses += 1;
|
|
@@ -3637,11 +4010,11 @@ var LocalContextIndex = class {
|
|
|
3637
4010
|
this.queryCache.set(key, cached);
|
|
3638
4011
|
return cloneHits(cached);
|
|
3639
4012
|
}
|
|
3640
|
-
cacheHits(query, topK, hits) {
|
|
4013
|
+
cacheHits(query, topK, rankingGeneration, hits) {
|
|
3641
4014
|
if (!hits.length) return;
|
|
3642
4015
|
const generation = this.index?.generation;
|
|
3643
4016
|
if (!generation) return;
|
|
3644
|
-
const key = `${generation}\0${topK}\0${query}`;
|
|
4017
|
+
const key = `${generation}\0${rankingGeneration}\0${topK}\0${query}`;
|
|
3645
4018
|
this.queryCache.delete(key);
|
|
3646
4019
|
this.queryCache.set(key, cloneHits(hits));
|
|
3647
4020
|
while (this.queryCache.size > MAX_QUERY_CACHE_ENTRIES) {
|
|
@@ -3653,7 +4026,7 @@ var LocalContextIndex = class {
|
|
|
3653
4026
|
};
|
|
3654
4027
|
function isWithinRoot(root, path) {
|
|
3655
4028
|
const offset = relative5(root, path);
|
|
3656
|
-
return offset === "" || !offset.startsWith("..") && !
|
|
4029
|
+
return offset === "" || !offset.startsWith("..") && !isAbsolute4(offset);
|
|
3657
4030
|
}
|
|
3658
4031
|
function isIncludedSourcePath(path) {
|
|
3659
4032
|
const name = basename5(path);
|
|
@@ -3785,7 +4158,7 @@ function packContextHits(hits, roots, maxTokens, engine) {
|
|
|
3785
4158
|
const text = selected.map((hit) => {
|
|
3786
4159
|
const shownPath = workspaceAliasPath(hit.path, roots);
|
|
3787
4160
|
const symbol = hit.symbol ? ` symbol="${escapeAttribute(hit.symbol)}"` : "";
|
|
3788
|
-
const provenance = hit.provenance ? ` source="${escapeAttribute(hit.source)}" hash="${hit.provenance.contentHash.slice(0, 12)}" graph-score="${hit.provenance.score.graph.toFixed(3)}"` : "";
|
|
4161
|
+
const provenance = hit.provenance ? ` source="${escapeAttribute(hit.source)}" hash="${hit.provenance.contentHash.slice(0, 12)}" graph-score="${hit.provenance.score.graph.toFixed(3)}" recency-score="${hit.provenance.score.recency.toFixed(6)}"` : "";
|
|
3789
4162
|
return `<code path="${escapeAttribute(shownPath)}" lines="${hit.startLine}-${hit.endLine}" score="${hit.score.toFixed(3)}"${symbol}${provenance}>
|
|
3790
4163
|
${hit.content}
|
|
3791
4164
|
</code>`;
|
|
@@ -3833,10 +4206,10 @@ function reconstructIndexedContent(chunks) {
|
|
|
3833
4206
|
return lines.map((line) => line ?? "").join("\n");
|
|
3834
4207
|
}
|
|
3835
4208
|
function hashContent(content) {
|
|
3836
|
-
return
|
|
4209
|
+
return createHash7("sha256").update(content, "utf8").digest("hex");
|
|
3837
4210
|
}
|
|
3838
4211
|
function createGeneration(files) {
|
|
3839
|
-
return
|
|
4212
|
+
return createHash7("sha256").update(files.slice().sort((left, right) => left.absolutePath.localeCompare(right.absolutePath)).map((file) => `${file.absolutePath}\0${file.contentHash}`).join("\n"), "utf8").digest("hex").slice(0, 16);
|
|
3840
4213
|
}
|
|
3841
4214
|
function hasSubstantialOverlap(left, right) {
|
|
3842
4215
|
if (left.path !== right.path) return false;
|
|
@@ -4260,7 +4633,7 @@ function formatContextHits(hits, roots) {
|
|
|
4260
4633
|
const path = workspaceAliasPath(hit.path, roots);
|
|
4261
4634
|
const symbol = hit.symbol ? ` ${hit.symbol}` : "";
|
|
4262
4635
|
const score = hit.provenance?.score;
|
|
4263
|
-
const breakdown = score ? ` bm25=${score.bm25.toFixed(2)} path=${score.path.toFixed(2)} symbol=${score.symbol.toFixed(2)} graph=${score.graph.toFixed(2)}` : "";
|
|
4636
|
+
const breakdown = score ? ` bm25=${score.bm25.toFixed(2)} path=${score.path.toFixed(2)} symbol=${score.symbol.toFixed(2)} graph=${score.graph.toFixed(2)} recency=${score.recency.toFixed(6)}` : "";
|
|
4264
4637
|
const hash4 = hit.provenance?.contentHash.slice(0, 12);
|
|
4265
4638
|
return `[${hit.source} ${hit.score.toFixed(3)}${breakdown}${hash4 ? ` hash=${hash4}` : ""}]${symbol} ${path}:${hit.startLine}-${hit.endLine}`;
|
|
4266
4639
|
}).join("\n");
|
|
@@ -4501,7 +4874,7 @@ function toolTokens(messages) {
|
|
|
4501
4874
|
|
|
4502
4875
|
// src/context/mentions.ts
|
|
4503
4876
|
import { readFile as readFile4, stat as stat4 } from "node:fs/promises";
|
|
4504
|
-
import { isAbsolute as
|
|
4877
|
+
import { isAbsolute as isAbsolute5, resolve as resolve11 } from "node:path";
|
|
4505
4878
|
import fg2 from "fast-glob";
|
|
4506
4879
|
var defaultMentionIgnores = [
|
|
4507
4880
|
"**/.git/**",
|
|
@@ -4572,7 +4945,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4572
4945
|
ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
|
|
4573
4946
|
});
|
|
4574
4947
|
matched.push(...await safeMentionPaths(
|
|
4575
|
-
children.slice(0, 25).map((path) =>
|
|
4948
|
+
children.slice(0, 25).map((path) => resolve11(aliased, path)),
|
|
4576
4949
|
workspace
|
|
4577
4950
|
));
|
|
4578
4951
|
}
|
|
@@ -4580,7 +4953,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4580
4953
|
}
|
|
4581
4954
|
}
|
|
4582
4955
|
for (const root of matched.length ? [] : roots) {
|
|
4583
|
-
const direct =
|
|
4956
|
+
const direct = resolve11(root, clean2);
|
|
4584
4957
|
try {
|
|
4585
4958
|
const safeDirect = await workspace.resolvePath(direct);
|
|
4586
4959
|
const info = await stat4(safeDirect);
|
|
@@ -4594,7 +4967,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4594
4967
|
ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
|
|
4595
4968
|
});
|
|
4596
4969
|
matched.push(...await safeMentionPaths(
|
|
4597
|
-
children.slice(0, 25).map((path) =>
|
|
4970
|
+
children.slice(0, 25).map((path) => resolve11(safeDirect, path)),
|
|
4598
4971
|
workspace
|
|
4599
4972
|
));
|
|
4600
4973
|
}
|
|
@@ -4610,7 +4983,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4610
4983
|
ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
|
|
4611
4984
|
});
|
|
4612
4985
|
matched.push(...await safeMentionPaths(
|
|
4613
|
-
globbed.slice(0, 25).map((path) =>
|
|
4986
|
+
globbed.slice(0, 25).map((path) => resolve11(root, path)),
|
|
4614
4987
|
workspace
|
|
4615
4988
|
));
|
|
4616
4989
|
}
|
|
@@ -4676,7 +5049,7 @@ async function buildMentionPathIndex(roots, options = {}) {
|
|
|
4676
5049
|
})
|
|
4677
5050
|
})));
|
|
4678
5051
|
const paths = filesByRoot.flatMap(({ root, files }) => files.map(
|
|
4679
|
-
(path) => workspaceAliasPath(
|
|
5052
|
+
(path) => workspaceAliasPath(resolve11(root, path), normalizedRoots)
|
|
4680
5053
|
));
|
|
4681
5054
|
return new MentionPathIndex(normalizedRoots, paths);
|
|
4682
5055
|
}
|
|
@@ -4710,22 +5083,22 @@ function mapMentionCandidatePath(path, roots) {
|
|
|
4710
5083
|
const primary = normalizedRoots[0];
|
|
4711
5084
|
if (!primary || !path || path.includes("\0")) return void 0;
|
|
4712
5085
|
let candidate;
|
|
4713
|
-
if (
|
|
4714
|
-
candidate =
|
|
5086
|
+
if (isAbsolute5(path)) {
|
|
5087
|
+
candidate = resolve11(path);
|
|
4715
5088
|
} else {
|
|
4716
5089
|
const normalized = path.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
4717
5090
|
const [first = "", ...rest] = normalized.split("/");
|
|
4718
5091
|
const alias = first.toLocaleLowerCase();
|
|
4719
5092
|
const workspace = alias.match(/^workspace(\d+)$/);
|
|
4720
5093
|
if (alias === "main") {
|
|
4721
|
-
candidate =
|
|
5094
|
+
candidate = resolve11(primary, ...rest);
|
|
4722
5095
|
} else if (workspace) {
|
|
4723
5096
|
const index = Number(workspace[1]) - 1;
|
|
4724
5097
|
const root = Number.isSafeInteger(index) && index >= 1 ? normalizedRoots[index] : void 0;
|
|
4725
5098
|
if (!root) return void 0;
|
|
4726
|
-
candidate =
|
|
5099
|
+
candidate = resolve11(root, ...rest);
|
|
4727
5100
|
} else {
|
|
4728
|
-
candidate =
|
|
5101
|
+
candidate = resolve11(primary, normalized);
|
|
4729
5102
|
}
|
|
4730
5103
|
}
|
|
4731
5104
|
if (!normalizedRoots.some((root) => isInside(root, candidate))) return void 0;
|
|
@@ -4755,7 +5128,7 @@ function rankMention(path, needle) {
|
|
|
4755
5128
|
}
|
|
4756
5129
|
function normalizeRoots(roots) {
|
|
4757
5130
|
const values = typeof roots === "string" ? [roots] : roots;
|
|
4758
|
-
return [...new Set(values.map((root) =>
|
|
5131
|
+
return [...new Set(values.map((root) => resolve11(root)))];
|
|
4759
5132
|
}
|
|
4760
5133
|
function normalizeMentionCandidate(path) {
|
|
4761
5134
|
const normalized = path.replaceAll("\\", "/").normalize("NFC");
|
|
@@ -5421,9 +5794,9 @@ function createProvider(config) {
|
|
|
5421
5794
|
}
|
|
5422
5795
|
|
|
5423
5796
|
// src/checkpoint/store.ts
|
|
5424
|
-
import { createHash as
|
|
5425
|
-
import { lstat as
|
|
5426
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
5797
|
+
import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
|
|
5798
|
+
import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
5799
|
+
import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
|
|
5427
5800
|
import { z as z4 } from "zod";
|
|
5428
5801
|
var entrySchema = z4.object({
|
|
5429
5802
|
path: z4.string(),
|
|
@@ -5448,7 +5821,7 @@ var CheckpointStore = class {
|
|
|
5448
5821
|
constructor(workspace, directory) {
|
|
5449
5822
|
this.workspace = typeof workspace === "string" ? new WorkspaceAccess([workspace]) : workspace;
|
|
5450
5823
|
this.managedDirectory = directory === void 0;
|
|
5451
|
-
this.directory = directory ?
|
|
5824
|
+
this.directory = directory ? resolve12(directory) : join9(resolveProjectNamespaceSync(this.workspace.primaryRoot).active, "checkpoints");
|
|
5452
5825
|
}
|
|
5453
5826
|
async capture(sessionId, paths, options = {}) {
|
|
5454
5827
|
validateIdentifier(sessionId, "session");
|
|
@@ -5458,8 +5831,8 @@ var CheckpointStore = class {
|
|
|
5458
5831
|
}
|
|
5459
5832
|
async captureUnlocked(sessionId, unique3, options) {
|
|
5460
5833
|
const id = `${Date.now().toString(36)}-${randomUUID7()}`;
|
|
5461
|
-
const target =
|
|
5462
|
-
const blobDirectory =
|
|
5834
|
+
const target = join9(this.directory, sessionId, id);
|
|
5835
|
+
const blobDirectory = join9(target, "blobs");
|
|
5463
5836
|
await this.ensureDirectory();
|
|
5464
5837
|
await this.assertManagedPath(target);
|
|
5465
5838
|
await mkdir7(blobDirectory, { recursive: true });
|
|
@@ -5468,14 +5841,14 @@ var CheckpointStore = class {
|
|
|
5468
5841
|
for (const input2 of unique3) {
|
|
5469
5842
|
const path = await this.workspace.resolvePath(input2, { allowMissing: true });
|
|
5470
5843
|
try {
|
|
5471
|
-
const info = await
|
|
5844
|
+
const info = await lstat10(path);
|
|
5472
5845
|
if (info.isSymbolicLink()) throw new Error(`Cannot checkpoint a symbolic link: ${input2}`);
|
|
5473
5846
|
if (!info.isFile()) throw new Error(`Cannot checkpoint a non-file path: ${input2}`);
|
|
5474
5847
|
if (info.size > 25e6) {
|
|
5475
5848
|
throw new Error(`File is too large to checkpoint (${info.size} bytes): ${input2}`);
|
|
5476
5849
|
}
|
|
5477
|
-
const blob = `${
|
|
5478
|
-
await atomicWrite(
|
|
5850
|
+
const blob = `${createHash8("sha256").update(path).digest("hex")}.bin`;
|
|
5851
|
+
await atomicWrite(join9(blobDirectory, blob), await readFile5(path), 384);
|
|
5479
5852
|
entries.push({
|
|
5480
5853
|
path,
|
|
5481
5854
|
relativePath: relative6(this.workspace.primaryRoot, path),
|
|
@@ -5502,7 +5875,7 @@ var CheckpointStore = class {
|
|
|
5502
5875
|
entries
|
|
5503
5876
|
};
|
|
5504
5877
|
await atomicWrite(
|
|
5505
|
-
|
|
5878
|
+
join9(target, "manifest.json"),
|
|
5506
5879
|
`${JSON.stringify(manifest, null, 2)}
|
|
5507
5880
|
`,
|
|
5508
5881
|
384
|
|
@@ -5515,9 +5888,9 @@ var CheckpointStore = class {
|
|
|
5515
5888
|
if (!await this.directoryAvailable()) {
|
|
5516
5889
|
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
5517
5890
|
}
|
|
5518
|
-
const checkpointDirectory =
|
|
5891
|
+
const checkpointDirectory = join9(this.directory, sessionId, checkpointId);
|
|
5519
5892
|
await this.assertManagedPath(checkpointDirectory);
|
|
5520
|
-
const manifestPath =
|
|
5893
|
+
const manifestPath = join9(checkpointDirectory, "manifest.json");
|
|
5521
5894
|
await this.assertManagedFile(manifestPath);
|
|
5522
5895
|
const raw = await readFile5(manifestPath, "utf8");
|
|
5523
5896
|
const manifest = manifestSchema.parse(JSON.parse(raw));
|
|
@@ -5528,7 +5901,7 @@ var CheckpointStore = class {
|
|
|
5528
5901
|
}
|
|
5529
5902
|
async restore(sessionId, checkpointId) {
|
|
5530
5903
|
const manifest = await this.load(sessionId, checkpointId);
|
|
5531
|
-
const blobDirectory =
|
|
5904
|
+
const blobDirectory = join9(this.directory, sessionId, checkpointId, "blobs");
|
|
5532
5905
|
await this.assertManagedPath(blobDirectory);
|
|
5533
5906
|
const actions = [];
|
|
5534
5907
|
for (const entry of manifest.entries) {
|
|
@@ -5543,8 +5916,8 @@ var CheckpointStore = class {
|
|
|
5543
5916
|
if (!/^[a-f0-9]{64}\.bin$/.test(entry.blob) || basename6(entry.blob) !== entry.blob) {
|
|
5544
5917
|
throw new Error(`Checkpoint blob name is invalid for ${entry.relativePath}.`);
|
|
5545
5918
|
}
|
|
5546
|
-
const blobPath =
|
|
5547
|
-
const blobInfo = await
|
|
5919
|
+
const blobPath = join9(blobDirectory, entry.blob);
|
|
5920
|
+
const blobInfo = await lstat10(blobPath);
|
|
5548
5921
|
if (!blobInfo.isFile() || blobInfo.isSymbolicLink()) {
|
|
5549
5922
|
throw new Error(`Checkpoint blob is not a regular file for ${entry.relativePath}.`);
|
|
5550
5923
|
}
|
|
@@ -5585,7 +5958,7 @@ var CheckpointStore = class {
|
|
|
5585
5958
|
async list(sessionId) {
|
|
5586
5959
|
validateIdentifier(sessionId, "session");
|
|
5587
5960
|
if (!await this.directoryAvailable()) return [];
|
|
5588
|
-
const directory =
|
|
5961
|
+
const directory = join9(this.directory, sessionId);
|
|
5589
5962
|
let names;
|
|
5590
5963
|
try {
|
|
5591
5964
|
names = await readdir2(directory);
|
|
@@ -5624,7 +5997,7 @@ var CheckpointStore = class {
|
|
|
5624
5997
|
await assertNoSymlinkPath(this.workspace.primaryRoot, this.directory);
|
|
5625
5998
|
}
|
|
5626
5999
|
try {
|
|
5627
|
-
const info = await
|
|
6000
|
+
const info = await lstat10(this.directory);
|
|
5628
6001
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
5629
6002
|
throw new Error(`Checkpoint storage is not a regular directory: ${this.directory}`);
|
|
5630
6003
|
}
|
|
@@ -5643,7 +6016,7 @@ var CheckpointStore = class {
|
|
|
5643
6016
|
await this.assertManagedPath(dirname8(path));
|
|
5644
6017
|
if (!this.managedDirectory) return;
|
|
5645
6018
|
try {
|
|
5646
|
-
if ((await
|
|
6019
|
+
if ((await lstat10(path)).isSymbolicLink()) {
|
|
5647
6020
|
throw new Error(`Checkpoint path cannot contain a symbolic link: ${path}`);
|
|
5648
6021
|
}
|
|
5649
6022
|
} catch (error) {
|
|
@@ -5653,7 +6026,7 @@ var CheckpointStore = class {
|
|
|
5653
6026
|
};
|
|
5654
6027
|
async function readSnapshot(path) {
|
|
5655
6028
|
try {
|
|
5656
|
-
const info = await
|
|
6029
|
+
const info = await lstat10(path);
|
|
5657
6030
|
if (info.isSymbolicLink()) throw new Error(`Cannot restore over a symbolic link: ${path}`);
|
|
5658
6031
|
if (!info.isFile()) throw new Error(`Cannot restore over a non-file path: ${path}`);
|
|
5659
6032
|
return { content: await readFile5(path), mode: info.mode };
|
|
@@ -5678,174 +6051,6 @@ function validateIdentifier(value, kind) {
|
|
|
5678
6051
|
}
|
|
5679
6052
|
}
|
|
5680
6053
|
|
|
5681
|
-
// src/utils/process.ts
|
|
5682
|
-
import { spawn } from "node:child_process";
|
|
5683
|
-
import { constants as constants3 } from "node:fs";
|
|
5684
|
-
import { access as access2, lstat as lstat10, realpath as realpath6 } from "node:fs/promises";
|
|
5685
|
-
import { delimiter, isAbsolute as isAbsolute5, join as join9, resolve as resolve11 } from "node:path";
|
|
5686
|
-
import { StringDecoder } from "node:string_decoder";
|
|
5687
|
-
async function resolveExecutableRuntime(command2, cwd, excludedRoots = []) {
|
|
5688
|
-
const realRoots = await Promise.all(excludedRoots.map(async (root) => {
|
|
5689
|
-
try {
|
|
5690
|
-
return await realpath6(root);
|
|
5691
|
-
} catch {
|
|
5692
|
-
return resolve11(root);
|
|
5693
|
-
}
|
|
5694
|
-
}));
|
|
5695
|
-
const pathEntries = (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean);
|
|
5696
|
-
const safeDirectories2 = [];
|
|
5697
|
-
let executable2;
|
|
5698
|
-
const explicit = isAbsolute5(command2) || command2.includes("/") || command2.includes("\\");
|
|
5699
|
-
const explicitPath = explicit ? resolve11(cwd, command2) : void 0;
|
|
5700
|
-
for (const entry of pathEntries) {
|
|
5701
|
-
let directory;
|
|
5702
|
-
try {
|
|
5703
|
-
directory = await realpath6(resolve11(cwd, entry));
|
|
5704
|
-
if (!(await lstat10(directory)).isDirectory()) continue;
|
|
5705
|
-
} catch {
|
|
5706
|
-
continue;
|
|
5707
|
-
}
|
|
5708
|
-
if (realRoots.some((root) => isInside(root, directory))) continue;
|
|
5709
|
-
let contaminated = false;
|
|
5710
|
-
if (!explicit) {
|
|
5711
|
-
for (const name of executableNames(command2)) {
|
|
5712
|
-
const candidate = join9(directory, name);
|
|
5713
|
-
const resolvedCandidate = await usableExecutable(candidate);
|
|
5714
|
-
if (!resolvedCandidate) continue;
|
|
5715
|
-
if (realRoots.some((root) => isInside(root, resolvedCandidate))) {
|
|
5716
|
-
contaminated = true;
|
|
5717
|
-
continue;
|
|
5718
|
-
}
|
|
5719
|
-
executable2 ??= resolvedCandidate;
|
|
5720
|
-
}
|
|
5721
|
-
}
|
|
5722
|
-
if (!contaminated && !safeDirectories2.includes(directory)) safeDirectories2.push(directory);
|
|
5723
|
-
}
|
|
5724
|
-
if (explicitPath) executable2 = await usableExecutable(explicitPath);
|
|
5725
|
-
if (!executable2) return void 0;
|
|
5726
|
-
return { executable: executable2, path: safeDirectories2.join(delimiter) };
|
|
5727
|
-
}
|
|
5728
|
-
async function usableExecutable(candidate) {
|
|
5729
|
-
try {
|
|
5730
|
-
await access2(candidate, process.platform === "win32" ? constants3.F_OK : constants3.X_OK);
|
|
5731
|
-
const resolvedCandidate = await realpath6(candidate);
|
|
5732
|
-
return (await lstat10(resolvedCandidate)).isFile() ? resolvedCandidate : void 0;
|
|
5733
|
-
} catch {
|
|
5734
|
-
return void 0;
|
|
5735
|
-
}
|
|
5736
|
-
}
|
|
5737
|
-
function executableNames(command2) {
|
|
5738
|
-
if (process.platform !== "win32") return [command2];
|
|
5739
|
-
const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
|
5740
|
-
return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
|
|
5741
|
-
}
|
|
5742
|
-
function runProcess(command2, args, options) {
|
|
5743
|
-
return new Promise((resolve24, reject) => {
|
|
5744
|
-
const started = Date.now();
|
|
5745
|
-
const environment = options.inheritEnv === false ? {} : { ...process.env };
|
|
5746
|
-
for (const name of options.unsetEnv ?? []) delete environment[name];
|
|
5747
|
-
for (const name of Object.keys(environment)) {
|
|
5748
|
-
if (options.unsetEnvPrefixes?.some((prefix) => name.startsWith(prefix))) {
|
|
5749
|
-
delete environment[name];
|
|
5750
|
-
}
|
|
5751
|
-
}
|
|
5752
|
-
Object.assign(environment, options.env);
|
|
5753
|
-
const child = spawn(command2, args, {
|
|
5754
|
-
cwd: options.cwd,
|
|
5755
|
-
env: environment,
|
|
5756
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
5757
|
-
signal: options.signal
|
|
5758
|
-
});
|
|
5759
|
-
const maxBytes = options.maxOutputBytes ?? 1e6;
|
|
5760
|
-
let stdout = "";
|
|
5761
|
-
let stderr = "";
|
|
5762
|
-
let stdoutBytes = 0;
|
|
5763
|
-
let stderrBytes = 0;
|
|
5764
|
-
let stdoutObservedBytes = 0;
|
|
5765
|
-
let stderrObservedBytes = 0;
|
|
5766
|
-
let timedOut = false;
|
|
5767
|
-
let callbackError;
|
|
5768
|
-
const stdoutDecoder = new StringDecoder("utf8");
|
|
5769
|
-
const stderrDecoder = new StringDecoder("utf8");
|
|
5770
|
-
const stdoutCallbackDecoder = new StringDecoder("utf8");
|
|
5771
|
-
const stderrCallbackDecoder = new StringDecoder("utf8");
|
|
5772
|
-
const append = (decoder, chunk, usedBytes) => {
|
|
5773
|
-
if (usedBytes >= maxBytes) return { text: "", usedBytes };
|
|
5774
|
-
const selected = chunk.subarray(0, maxBytes - usedBytes);
|
|
5775
|
-
return { text: decoder.write(selected), usedBytes: usedBytes + selected.length };
|
|
5776
|
-
};
|
|
5777
|
-
const notify = (callback, decoder, chunk) => {
|
|
5778
|
-
if (!callback || callbackError) return;
|
|
5779
|
-
try {
|
|
5780
|
-
const decoded = decoder.write(chunk);
|
|
5781
|
-
if (decoded) callback(decoded);
|
|
5782
|
-
} catch (error) {
|
|
5783
|
-
callbackError = error;
|
|
5784
|
-
child.kill("SIGTERM");
|
|
5785
|
-
}
|
|
5786
|
-
};
|
|
5787
|
-
child.stdout.on("data", (chunk) => {
|
|
5788
|
-
stdoutObservedBytes += chunk.length;
|
|
5789
|
-
const appended = append(stdoutDecoder, chunk, stdoutBytes);
|
|
5790
|
-
stdout += appended.text;
|
|
5791
|
-
stdoutBytes = appended.usedBytes;
|
|
5792
|
-
notify(options.onStdout, stdoutCallbackDecoder, chunk);
|
|
5793
|
-
});
|
|
5794
|
-
child.stderr.on("data", (chunk) => {
|
|
5795
|
-
stderrObservedBytes += chunk.length;
|
|
5796
|
-
const appended = append(stderrDecoder, chunk, stderrBytes);
|
|
5797
|
-
stderr += appended.text;
|
|
5798
|
-
stderrBytes = appended.usedBytes;
|
|
5799
|
-
notify(options.onStderr, stderrCallbackDecoder, chunk);
|
|
5800
|
-
});
|
|
5801
|
-
child.on("error", reject);
|
|
5802
|
-
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
5803
|
-
const timeout = timeoutMs > 0 ? setTimeout(() => {
|
|
5804
|
-
timedOut = true;
|
|
5805
|
-
child.kill("SIGTERM");
|
|
5806
|
-
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
5807
|
-
}, timeoutMs) : void 0;
|
|
5808
|
-
child.on("close", (code) => {
|
|
5809
|
-
if (timeout) clearTimeout(timeout);
|
|
5810
|
-
const stdoutTail = stdoutDecoder.end();
|
|
5811
|
-
const stderrTail = stderrDecoder.end();
|
|
5812
|
-
if (Buffer.byteLength(stdout) + Buffer.byteLength(stdoutTail) <= maxBytes) stdout += stdoutTail;
|
|
5813
|
-
if (Buffer.byteLength(stderr) + Buffer.byteLength(stderrTail) <= maxBytes) stderr += stderrTail;
|
|
5814
|
-
try {
|
|
5815
|
-
const stdoutCallbackTail = stdoutCallbackDecoder.end();
|
|
5816
|
-
const stderrCallbackTail = stderrCallbackDecoder.end();
|
|
5817
|
-
if (stdoutCallbackTail && options.onStdout && !callbackError) options.onStdout(stdoutCallbackTail);
|
|
5818
|
-
if (stderrCallbackTail && options.onStderr && !callbackError) options.onStderr(stderrCallbackTail);
|
|
5819
|
-
} catch (error) {
|
|
5820
|
-
callbackError = error;
|
|
5821
|
-
}
|
|
5822
|
-
if (callbackError) {
|
|
5823
|
-
reject(callbackError);
|
|
5824
|
-
return;
|
|
5825
|
-
}
|
|
5826
|
-
resolve24({
|
|
5827
|
-
command: [command2, ...args].join(" "),
|
|
5828
|
-
exitCode: code ?? (timedOut ? 124 : 1),
|
|
5829
|
-
stdout,
|
|
5830
|
-
stderr,
|
|
5831
|
-
timedOut,
|
|
5832
|
-
durationMs: Date.now() - started,
|
|
5833
|
-
stdoutBytes: stdoutObservedBytes,
|
|
5834
|
-
stderrBytes: stderrObservedBytes,
|
|
5835
|
-
stdoutTruncated: stdoutObservedBytes > stdoutBytes,
|
|
5836
|
-
stderrTruncated: stderrObservedBytes > stderrBytes
|
|
5837
|
-
});
|
|
5838
|
-
});
|
|
5839
|
-
if (options.stdin) child.stdin.end(options.stdin);
|
|
5840
|
-
else child.stdin.end();
|
|
5841
|
-
});
|
|
5842
|
-
}
|
|
5843
|
-
function runShell(command2, cwd, options = {}) {
|
|
5844
|
-
const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
|
|
5845
|
-
const args = process.platform === "win32" ? ["/d", "/s", "/c", command2] : ["-lc", command2];
|
|
5846
|
-
return runProcess(shell, args, { ...options, cwd });
|
|
5847
|
-
}
|
|
5848
|
-
|
|
5849
6054
|
// src/hooks/runner.ts
|
|
5850
6055
|
var HookError = class extends Error {
|
|
5851
6056
|
constructor(message2, result) {
|
|
@@ -5915,7 +6120,7 @@ import {
|
|
|
5915
6120
|
stat as stat5,
|
|
5916
6121
|
unlink as unlink3
|
|
5917
6122
|
} from "node:fs/promises";
|
|
5918
|
-
import { basename as basename7, dirname as dirname9, join as join10, resolve as
|
|
6123
|
+
import { basename as basename7, dirname as dirname9, join as join10, resolve as resolve13 } from "node:path";
|
|
5919
6124
|
import { z as z5 } from "zod";
|
|
5920
6125
|
var sessionIdSchema = z5.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/);
|
|
5921
6126
|
var toolCallSchema = z5.object({
|
|
@@ -6174,9 +6379,9 @@ var SessionStore = class {
|
|
|
6174
6379
|
managedDirectory;
|
|
6175
6380
|
writes = Promise.resolve();
|
|
6176
6381
|
constructor(workspace, directory) {
|
|
6177
|
-
this.workspace =
|
|
6382
|
+
this.workspace = resolve13(workspace);
|
|
6178
6383
|
this.managedDirectory = directory === void 0;
|
|
6179
|
-
this.directory = directory ?
|
|
6384
|
+
this.directory = directory ? resolve13(directory) : join10(resolveProjectNamespaceSync(this.workspace).active, "sessions");
|
|
6180
6385
|
}
|
|
6181
6386
|
async create(options) {
|
|
6182
6387
|
const session = createSession({ ...options, workspace: this.workspace });
|
|
@@ -6185,7 +6390,7 @@ var SessionStore = class {
|
|
|
6185
6390
|
}
|
|
6186
6391
|
async save(session) {
|
|
6187
6392
|
validateId(session.id);
|
|
6188
|
-
if (
|
|
6393
|
+
if (resolve13(session.workspace) !== this.workspace) {
|
|
6189
6394
|
throw new Error("Session workspace does not match this store.");
|
|
6190
6395
|
}
|
|
6191
6396
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -6224,7 +6429,7 @@ var SessionStore = class {
|
|
|
6224
6429
|
const summaries = await Promise.all(
|
|
6225
6430
|
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => {
|
|
6226
6431
|
const session = await tryReadSession(join10(this.directory, entry.name));
|
|
6227
|
-
if (!session ||
|
|
6432
|
+
if (!session || resolve13(session.workspace) !== this.workspace) return;
|
|
6228
6433
|
return toSummary(session);
|
|
6229
6434
|
})
|
|
6230
6435
|
);
|
|
@@ -6295,7 +6500,7 @@ var SessionStore = class {
|
|
|
6295
6500
|
for (const candidate of candidates) {
|
|
6296
6501
|
await this.assertManagedFile(candidate.path);
|
|
6297
6502
|
const session = await tryReadSession(candidate.path);
|
|
6298
|
-
if (session?.id === id &&
|
|
6503
|
+
if (session?.id === id && resolve13(session.workspace) === this.workspace) return session;
|
|
6299
6504
|
}
|
|
6300
6505
|
return void 0;
|
|
6301
6506
|
}
|
|
@@ -6309,7 +6514,7 @@ var SessionStore = class {
|
|
|
6309
6514
|
}
|
|
6310
6515
|
}
|
|
6311
6516
|
assertWorkspace(session) {
|
|
6312
|
-
if (
|
|
6517
|
+
if (resolve13(session.workspace) !== this.workspace) {
|
|
6313
6518
|
throw new Error("Stored session belongs to a different workspace.");
|
|
6314
6519
|
}
|
|
6315
6520
|
return session;
|
|
@@ -6368,7 +6573,7 @@ function createSession(options) {
|
|
|
6368
6573
|
return {
|
|
6369
6574
|
id,
|
|
6370
6575
|
title: cleanTitle(options.title ?? "New session"),
|
|
6371
|
-
workspace:
|
|
6576
|
+
workspace: resolve13(options.workspace),
|
|
6372
6577
|
createdAt: now,
|
|
6373
6578
|
updatedAt: now,
|
|
6374
6579
|
model: options.model,
|
|
@@ -6423,9 +6628,9 @@ async function exists2(path) {
|
|
|
6423
6628
|
}
|
|
6424
6629
|
|
|
6425
6630
|
// src/session/tool-artifacts.ts
|
|
6426
|
-
import { createHash as
|
|
6631
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
6427
6632
|
import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
6428
|
-
import { join as join11, resolve as
|
|
6633
|
+
import { join as join11, resolve as resolve14 } from "node:path";
|
|
6429
6634
|
import { z as z6 } from "zod";
|
|
6430
6635
|
var MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
|
|
6431
6636
|
var MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
@@ -6455,9 +6660,9 @@ var ToolArtifactStore = class {
|
|
|
6455
6660
|
retentionMs;
|
|
6456
6661
|
writes = Promise.resolve();
|
|
6457
6662
|
constructor(workspace, options = {}) {
|
|
6458
|
-
this.workspace =
|
|
6663
|
+
this.workspace = resolve14(workspace);
|
|
6459
6664
|
this.managedDirectory = options.directory === void 0;
|
|
6460
|
-
this.directory = options.directory ?
|
|
6665
|
+
this.directory = options.directory ? resolve14(options.directory) : join11(resolveProjectNamespaceSync(this.workspace).active, "tool-artifacts");
|
|
6461
6666
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
6462
6667
|
this.maxArtifactBytes = options.maxArtifactBytes ?? MAX_ARTIFACT_BYTES;
|
|
6463
6668
|
this.maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
@@ -6514,7 +6719,7 @@ var ToolArtifactStore = class {
|
|
|
6514
6719
|
}
|
|
6515
6720
|
if (retainedBytes + storageBytes > this.maxTotalBytes) return { stored: false, reason: "total_limit" };
|
|
6516
6721
|
const path = this.pathFor(sessionId, toolCallId);
|
|
6517
|
-
await ensureWorkspaceStorageDirectory(this.workspace,
|
|
6722
|
+
await ensureWorkspaceStorageDirectory(this.workspace, resolve14(path, ".."), {
|
|
6518
6723
|
requireActiveNamespace: this.managedDirectory
|
|
6519
6724
|
});
|
|
6520
6725
|
await atomicWrite(path, `${JSON.stringify(artifact)}
|
|
@@ -6665,7 +6870,7 @@ var ToolArtifactStore = class {
|
|
|
6665
6870
|
}
|
|
6666
6871
|
}
|
|
6667
6872
|
async assertRegularFile(path) {
|
|
6668
|
-
await assertNoSymlinkPath(this.workspace,
|
|
6873
|
+
await assertNoSymlinkPath(this.workspace, resolve14(path, ".."));
|
|
6669
6874
|
try {
|
|
6670
6875
|
const info = await lstat12(path);
|
|
6671
6876
|
if (info.isSymbolicLink() || !info.isFile()) {
|
|
@@ -6702,7 +6907,7 @@ function reference(artifact) {
|
|
|
6702
6907
|
};
|
|
6703
6908
|
}
|
|
6704
6909
|
function hash2(value) {
|
|
6705
|
-
return
|
|
6910
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
6706
6911
|
}
|
|
6707
6912
|
function storedBytes(artifact) {
|
|
6708
6913
|
return Buffer.byteLength(JSON.stringify(artifact)) + 1;
|
|
@@ -7249,48 +7454,6 @@ var knownCommands = /* @__PURE__ */ new Set([
|
|
|
7249
7454
|
...networkCommands,
|
|
7250
7455
|
...workspaceMutationCommands
|
|
7251
7456
|
]);
|
|
7252
|
-
var unsafeInheritedGitEnvironment = [
|
|
7253
|
-
"GIT_DIR",
|
|
7254
|
-
"GIT_WORK_TREE",
|
|
7255
|
-
"GIT_COMMON_DIR",
|
|
7256
|
-
"GIT_INDEX_FILE",
|
|
7257
|
-
"GIT_OBJECT_DIRECTORY",
|
|
7258
|
-
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
7259
|
-
"GIT_NAMESPACE",
|
|
7260
|
-
"GIT_CEILING_DIRECTORIES",
|
|
7261
|
-
"GIT_DISCOVERY_ACROSS_FILESYSTEM",
|
|
7262
|
-
"GIT_EXTERNAL_DIFF",
|
|
7263
|
-
"GIT_DIFF_OPTS",
|
|
7264
|
-
"GIT_SSH",
|
|
7265
|
-
"GIT_SSH_COMMAND",
|
|
7266
|
-
"GIT_PROXY_COMMAND",
|
|
7267
|
-
"GIT_ASKPASS",
|
|
7268
|
-
"SSH_ASKPASS",
|
|
7269
|
-
"GIT_EXEC_PATH",
|
|
7270
|
-
"GIT_TEMPLATE_DIR",
|
|
7271
|
-
"GIT_CONFIG",
|
|
7272
|
-
"GIT_CONFIG_PARAMETERS",
|
|
7273
|
-
"GIT_QUARANTINE_PATH",
|
|
7274
|
-
"Path"
|
|
7275
|
-
];
|
|
7276
|
-
var isolatedGitEnvironment = {
|
|
7277
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
7278
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
7279
|
-
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
7280
|
-
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
7281
|
-
GIT_PAGER: "cat",
|
|
7282
|
-
GIT_EDITOR: "true",
|
|
7283
|
-
GIT_SEQUENCE_EDITOR: "true",
|
|
7284
|
-
GIT_CONFIG_COUNT: "4",
|
|
7285
|
-
GIT_CONFIG_KEY_0: "core.hooksPath",
|
|
7286
|
-
GIT_CONFIG_VALUE_0: "/dev/null",
|
|
7287
|
-
GIT_CONFIG_KEY_1: "core.fsmonitor",
|
|
7288
|
-
GIT_CONFIG_VALUE_1: "false",
|
|
7289
|
-
GIT_CONFIG_KEY_2: "credential.helper",
|
|
7290
|
-
GIT_CONFIG_VALUE_2: "",
|
|
7291
|
-
GIT_CONFIG_KEY_3: "protocol.ext.allow",
|
|
7292
|
-
GIT_CONFIG_VALUE_3: "never"
|
|
7293
|
-
};
|
|
7294
7457
|
var diffCommands = /* @__PURE__ */ new Set(["diff", "log", "show", "whatchanged"]);
|
|
7295
7458
|
var gitTool = {
|
|
7296
7459
|
definition: {
|
|
@@ -7338,13 +7501,9 @@ var gitTool = {
|
|
|
7338
7501
|
}
|
|
7339
7502
|
const runtime = await resolveExecutableRuntime("git", cwd, context.workspace.roots);
|
|
7340
7503
|
if (!runtime) throw new Error("Git executable was not found outside the configured workspace roots.");
|
|
7341
|
-
const status = await
|
|
7342
|
-
cwd,
|
|
7504
|
+
const status = await runIsolatedGit(runtime, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], cwd, {
|
|
7343
7505
|
timeoutMs: 15e3,
|
|
7344
|
-
maxOutputBytes: 2e6
|
|
7345
|
-
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
7346
|
-
unsetEnv: unsafeInheritedGitEnvironment,
|
|
7347
|
-
unsetEnvPrefixes: ["GIT_"]
|
|
7506
|
+
maxOutputBytes: 2e6
|
|
7348
7507
|
});
|
|
7349
7508
|
if (status.exitCode === 0) {
|
|
7350
7509
|
for (const record of status.stdout.split("\0")) {
|
|
@@ -7371,15 +7530,11 @@ var gitTool = {
|
|
|
7371
7530
|
const runtime = await resolveExecutableRuntime("git", cwd, context.workspace.roots);
|
|
7372
7531
|
if (!runtime) throw new Error("Git executable was not found outside the configured workspace roots.");
|
|
7373
7532
|
const before = worktreeTrackingCommands.has(command2) ? await captureGitState(runtime, cwd) : void 0;
|
|
7374
|
-
const result = await
|
|
7375
|
-
cwd,
|
|
7533
|
+
const result = await runIsolatedGit(runtime, protectedGitArguments(input2.args, command2), cwd, {
|
|
7376
7534
|
timeoutMs: input2.timeout_ms ?? 12e4,
|
|
7377
7535
|
maxOutputBytes: 2e6,
|
|
7378
7536
|
...input2.stdin !== void 0 ? { stdin: input2.stdin } : {},
|
|
7379
|
-
...context.signal ? { signal: context.signal } : {}
|
|
7380
|
-
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
7381
|
-
unsetEnv: unsafeInheritedGitEnvironment,
|
|
7382
|
-
unsetEnvPrefixes: ["GIT_"]
|
|
7537
|
+
...context.signal ? { signal: context.signal } : {}
|
|
7383
7538
|
});
|
|
7384
7539
|
const changedFiles = before ? await collectGitChanges(before, await captureGitState(runtime, cwd), runtime, cwd, context) : [];
|
|
7385
7540
|
return {
|
|
@@ -7457,18 +7612,6 @@ function parsePorcelainStatus(output2) {
|
|
|
7457
7612
|
}
|
|
7458
7613
|
return status;
|
|
7459
7614
|
}
|
|
7460
|
-
function runIsolatedGit(runtime, args, cwd, options = {}) {
|
|
7461
|
-
return runProcess(runtime.executable, args, {
|
|
7462
|
-
cwd,
|
|
7463
|
-
timeoutMs: options.timeoutMs ?? 15e3,
|
|
7464
|
-
maxOutputBytes: options.maxOutputBytes ?? 2e6,
|
|
7465
|
-
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
7466
|
-
unsetEnv: unsafeInheritedGitEnvironment,
|
|
7467
|
-
unsetEnvPrefixes: ["GIT_"],
|
|
7468
|
-
...options.stdin !== void 0 ? { stdin: options.stdin } : {},
|
|
7469
|
-
...options.signal ? { signal: options.signal } : {}
|
|
7470
|
-
});
|
|
7471
|
-
}
|
|
7472
7615
|
function validateGitArguments(args) {
|
|
7473
7616
|
for (const argument of args) {
|
|
7474
7617
|
if (argument.includes("\0") || argument.includes("\n") || argument.includes("\r")) {
|
|
@@ -7604,7 +7747,7 @@ function positionalArguments(args, command2) {
|
|
|
7604
7747
|
|
|
7605
7748
|
// src/tools/list.ts
|
|
7606
7749
|
import { lstat as lstat14 } from "node:fs/promises";
|
|
7607
|
-
import { relative as relative7, resolve as
|
|
7750
|
+
import { relative as relative7, resolve as resolve15 } from "node:path";
|
|
7608
7751
|
import fg3 from "fast-glob";
|
|
7609
7752
|
import { z as z9 } from "zod";
|
|
7610
7753
|
var inputSchema4 = z9.object({
|
|
@@ -7662,7 +7805,7 @@ var listFilesTool = {
|
|
|
7662
7805
|
for (const path of selected) {
|
|
7663
7806
|
let safePath;
|
|
7664
7807
|
try {
|
|
7665
|
-
safePath = await context.workspace.resolvePath(
|
|
7808
|
+
safePath = await context.workspace.resolvePath(resolve15(directory, path), { expect: "any" });
|
|
7666
7809
|
} catch {
|
|
7667
7810
|
continue;
|
|
7668
7811
|
}
|
|
@@ -7872,7 +8015,7 @@ function assertToolName(name) {
|
|
|
7872
8015
|
|
|
7873
8016
|
// src/tools/search.ts
|
|
7874
8017
|
import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
7875
|
-
import { resolve as
|
|
8018
|
+
import { resolve as resolve16 } from "node:path";
|
|
7876
8019
|
import fg4 from "fast-glob";
|
|
7877
8020
|
import { z as z12 } from "zod";
|
|
7878
8021
|
var inputSchema7 = z12.object({
|
|
@@ -7942,7 +8085,7 @@ var searchCodeTool = {
|
|
|
7942
8085
|
if (results.length >= maxResults) break;
|
|
7943
8086
|
let path;
|
|
7944
8087
|
try {
|
|
7945
|
-
path = await context.workspace.resolvePath(
|
|
8088
|
+
path = await context.workspace.resolvePath(resolve16(directory, file), { expect: "file" });
|
|
7946
8089
|
} catch {
|
|
7947
8090
|
skipped += 1;
|
|
7948
8091
|
continue;
|
|
@@ -8024,7 +8167,7 @@ ${hit.content}`
|
|
|
8024
8167
|
}
|
|
8025
8168
|
|
|
8026
8169
|
// src/tools/shell.ts
|
|
8027
|
-
import { createHash as
|
|
8170
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
8028
8171
|
import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
|
|
8029
8172
|
import { join as join13 } from "node:path";
|
|
8030
8173
|
import { z as z13 } from "zod";
|
|
@@ -8238,7 +8381,7 @@ async function captureWorkspaceSnapshot(roots) {
|
|
|
8238
8381
|
files.set(path, {
|
|
8239
8382
|
size: info.size,
|
|
8240
8383
|
mtimeMs: info.mtimeMs,
|
|
8241
|
-
hash:
|
|
8384
|
+
hash: createHash10("sha256").update(content).digest("hex")
|
|
8242
8385
|
});
|
|
8243
8386
|
} catch (error) {
|
|
8244
8387
|
if (error.code !== "ENOENT") complete = false;
|
|
@@ -8441,15 +8584,15 @@ function compact(value, limit) {
|
|
|
8441
8584
|
}
|
|
8442
8585
|
|
|
8443
8586
|
// src/agent/completion-gate.ts
|
|
8444
|
-
import { createHash as
|
|
8587
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
8445
8588
|
|
|
8446
8589
|
// src/tools/permissions.ts
|
|
8447
|
-
import { createHash as
|
|
8590
|
+
import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
|
|
8448
8591
|
var permissionScopeSecret = randomBytes(32);
|
|
8449
8592
|
function permissionKey(call, category) {
|
|
8450
8593
|
const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
|
|
8451
|
-
const
|
|
8452
|
-
return `${category}:${call.name}:${
|
|
8594
|
+
const digest2 = createHash11("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
|
|
8595
|
+
return `${category}:${call.name}:${digest2}`;
|
|
8453
8596
|
}
|
|
8454
8597
|
function permissionTarget(call) {
|
|
8455
8598
|
const command2 = commandForCall(call);
|
|
@@ -8642,7 +8785,7 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
|
|
|
8642
8785
|
kind,
|
|
8643
8786
|
ok: result.ok,
|
|
8644
8787
|
changeSequence,
|
|
8645
|
-
commandKey:
|
|
8788
|
+
commandKey: createHash12("sha256").update(normalized).digest("hex")
|
|
8646
8789
|
};
|
|
8647
8790
|
}
|
|
8648
8791
|
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = [], duplication) {
|
|
@@ -8826,7 +8969,7 @@ function verificationRequirementMet(requirement, checks) {
|
|
|
8826
8969
|
const normalized = normalizeCommand2(requirement);
|
|
8827
8970
|
const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
|
|
8828
8971
|
if (broad) return checks.length > 0;
|
|
8829
|
-
const commandKey =
|
|
8972
|
+
const commandKey = createHash12("sha256").update(normalized).digest("hex");
|
|
8830
8973
|
return checks.some((item) => item.commandKey === commandKey);
|
|
8831
8974
|
}
|
|
8832
8975
|
function acceptanceUnresolved(acceptance) {
|
|
@@ -9420,7 +9563,7 @@ function escapeAttribute3(value) {
|
|
|
9420
9563
|
}
|
|
9421
9564
|
|
|
9422
9565
|
// src/agent/tool-recovery.ts
|
|
9423
|
-
import { createHash as
|
|
9566
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
9424
9567
|
var RETRY_BUDGET = {
|
|
9425
9568
|
schema_input: 3,
|
|
9426
9569
|
unknown_tool: 2,
|
|
@@ -9492,7 +9635,7 @@ var ToolRecoveryController = class {
|
|
|
9492
9635
|
recordEvidence(call, result) {
|
|
9493
9636
|
if (call.name !== "search_code" || !result.ok) return void 0;
|
|
9494
9637
|
const callKey = callSignature(call);
|
|
9495
|
-
const fingerprint =
|
|
9638
|
+
const fingerprint = createHash13("sha256").update(`${result.content}\0${stableJson(result.metadata ?? {})}`).digest("hex");
|
|
9496
9639
|
const count = typeof result.metadata?.count === "number" ? result.metadata.count : void 0;
|
|
9497
9640
|
const current = this.evidence.get(callKey);
|
|
9498
9641
|
const repeated = current?.fingerprint === fingerprint;
|
|
@@ -9502,7 +9645,7 @@ var ToolRecoveryController = class {
|
|
|
9502
9645
|
status: count === 0 ? "empty" : repeated ? "repeated" : "new",
|
|
9503
9646
|
repeatCount: repeats,
|
|
9504
9647
|
stop: repeats >= 2,
|
|
9505
|
-
signature:
|
|
9648
|
+
signature: createHash13("sha256").update(`${callKey}\0${fingerprint}`).digest("hex")
|
|
9506
9649
|
};
|
|
9507
9650
|
}
|
|
9508
9651
|
receipt(call, failureClass, attempt, circuitOpen) {
|
|
@@ -9538,10 +9681,10 @@ function isRetryable(failureClass) {
|
|
|
9538
9681
|
return RETRY_BUDGET[failureClass] > 0;
|
|
9539
9682
|
}
|
|
9540
9683
|
function callSignature(call) {
|
|
9541
|
-
return
|
|
9684
|
+
return createHash13("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
|
|
9542
9685
|
}
|
|
9543
9686
|
function failureSignature(call, failureClass) {
|
|
9544
|
-
return
|
|
9687
|
+
return createHash13("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
|
|
9545
9688
|
}
|
|
9546
9689
|
function redact(value, key = "") {
|
|
9547
9690
|
if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
|
|
@@ -9889,7 +10032,7 @@ function escapeAttribute5(value) {
|
|
|
9889
10032
|
}
|
|
9890
10033
|
|
|
9891
10034
|
// src/agent/reuse-gate.ts
|
|
9892
|
-
import { createHash as
|
|
10035
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
9893
10036
|
import { readFile as readFile14, stat as stat10 } from "node:fs/promises";
|
|
9894
10037
|
import { basename as basename8, extname as extname3 } from "node:path";
|
|
9895
10038
|
var MAX_CANDIDATES = 5;
|
|
@@ -10098,14 +10241,14 @@ function unresolvedReceipt(input2, queryHash, targetPaths, trigger, detail) {
|
|
|
10098
10241
|
};
|
|
10099
10242
|
}
|
|
10100
10243
|
function hash3(value) {
|
|
10101
|
-
return
|
|
10244
|
+
return createHash14("sha256").update(value).digest("hex");
|
|
10102
10245
|
}
|
|
10103
10246
|
function roundScore2(value) {
|
|
10104
10247
|
return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : 0;
|
|
10105
10248
|
}
|
|
10106
10249
|
|
|
10107
10250
|
// src/agent/duplication-audit.ts
|
|
10108
|
-
import { createHash as
|
|
10251
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
10109
10252
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
10110
10253
|
var DEFAULT_NEAR_CLONE_THRESHOLD = 0.55;
|
|
10111
10254
|
var MAX_MATCHES = 8;
|
|
@@ -10280,7 +10423,7 @@ function round(value) {
|
|
|
10280
10423
|
return Math.round(value * 1e3) / 1e3;
|
|
10281
10424
|
}
|
|
10282
10425
|
function matchId(generation, changeSequence, changed, candidate, similarity) {
|
|
10283
|
-
return
|
|
10426
|
+
return createHash15("sha256").update([
|
|
10284
10427
|
generation,
|
|
10285
10428
|
String(changeSequence),
|
|
10286
10429
|
changed.path,
|
|
@@ -11698,9 +11841,9 @@ function numeric(...values) {
|
|
|
11698
11841
|
}
|
|
11699
11842
|
|
|
11700
11843
|
// src/agent/team-store.ts
|
|
11701
|
-
import { createHash as
|
|
11844
|
+
import { createHash as createHash16, randomUUID as randomUUID13 } from "node:crypto";
|
|
11702
11845
|
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
11703
|
-
import { join as join16, resolve as
|
|
11846
|
+
import { join as join16, resolve as resolve17 } from "node:path";
|
|
11704
11847
|
import { z as z18 } from "zod";
|
|
11705
11848
|
var runIdSchema = z18.string().uuid();
|
|
11706
11849
|
var hashSchema = z18.string().regex(/^[a-f0-9]{64}$/u);
|
|
@@ -11784,9 +11927,9 @@ var TeamRunStore = class {
|
|
|
11784
11927
|
managedDirectory;
|
|
11785
11928
|
writes = Promise.resolve();
|
|
11786
11929
|
constructor(workspace, directory) {
|
|
11787
|
-
this.workspace =
|
|
11930
|
+
this.workspace = resolve17(workspace);
|
|
11788
11931
|
this.managedDirectory = directory === void 0;
|
|
11789
|
-
this.directory = directory ?
|
|
11932
|
+
this.directory = directory ? resolve17(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
|
|
11790
11933
|
}
|
|
11791
11934
|
async create(input2) {
|
|
11792
11935
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -11873,7 +12016,7 @@ var TeamRunStore = class {
|
|
|
11873
12016
|
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
11874
12017
|
await this.assertRegularFile(path);
|
|
11875
12018
|
const manifest = manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
11876
|
-
if (manifest.id !== runId ||
|
|
12019
|
+
if (manifest.id !== runId || resolve17(manifest.workspace) !== this.workspace) {
|
|
11877
12020
|
throw new Error("Team run manifest identity does not match its location.");
|
|
11878
12021
|
}
|
|
11879
12022
|
if (verify) {
|
|
@@ -11956,7 +12099,7 @@ var TeamRunStore = class {
|
|
|
11956
12099
|
async writeArtifact(runId, content, truncate = true) {
|
|
11957
12100
|
const data = boundedArtifactText(content, 5e5, truncate);
|
|
11958
12101
|
const bytes = Buffer.byteLength(data);
|
|
11959
|
-
const sha256 =
|
|
12102
|
+
const sha256 = createHash16("sha256").update(data).digest("hex");
|
|
11960
12103
|
const directory = join16(this.runDirectory(runId), "blobs");
|
|
11961
12104
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
11962
12105
|
requireActiveNamespace: this.managedDirectory
|
|
@@ -11965,7 +12108,7 @@ var TeamRunStore = class {
|
|
|
11965
12108
|
try {
|
|
11966
12109
|
await this.assertRegularFile(path);
|
|
11967
12110
|
const existing = await readFile17(path);
|
|
11968
|
-
if (
|
|
12111
|
+
if (createHash16("sha256").update(existing).digest("hex") !== sha256) {
|
|
11969
12112
|
throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
|
|
11970
12113
|
}
|
|
11971
12114
|
} catch (error) {
|
|
@@ -11986,7 +12129,7 @@ var TeamRunStore = class {
|
|
|
11986
12129
|
const path = this.artifactPath(runId, artifact.sha256);
|
|
11987
12130
|
await this.assertRegularFile(path);
|
|
11988
12131
|
const data = await readFile17(path);
|
|
11989
|
-
const hash4 =
|
|
12132
|
+
const hash4 = createHash16("sha256").update(data).digest("hex");
|
|
11990
12133
|
if (hash4 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
|
|
11991
12134
|
throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
|
|
11992
12135
|
}
|
|
@@ -12004,7 +12147,7 @@ var TeamRunStore = class {
|
|
|
12004
12147
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
|
|
12005
12148
|
}
|
|
12006
12149
|
async assertRegularFile(path) {
|
|
12007
|
-
await assertNoSymlinkPath(this.workspace,
|
|
12150
|
+
await assertNoSymlinkPath(this.workspace, resolve17(path, ".."));
|
|
12008
12151
|
const info = await lstat18(path);
|
|
12009
12152
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Team run file is not a regular file: ${path}`);
|
|
12010
12153
|
}
|
|
@@ -12049,10 +12192,10 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
12049
12192
|
}
|
|
12050
12193
|
|
|
12051
12194
|
// src/agent/writer-lane.ts
|
|
12052
|
-
import { createHash as
|
|
12195
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
12053
12196
|
import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
|
|
12054
12197
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
12055
|
-
import { isAbsolute as isAbsolute6, join as join17, resolve as
|
|
12198
|
+
import { isAbsolute as isAbsolute6, join as join17, resolve as resolve18 } from "node:path";
|
|
12056
12199
|
var WriterLaneApplyError = class extends Error {
|
|
12057
12200
|
constructor(message2, attempted, cause) {
|
|
12058
12201
|
super(message2, cause === void 0 ? void 0 : { cause });
|
|
@@ -12065,8 +12208,8 @@ var WriterLane = class {
|
|
|
12065
12208
|
workspace;
|
|
12066
12209
|
constructor(workspace, workspaceRoots) {
|
|
12067
12210
|
this.workspace = new WorkspaceAccess([
|
|
12068
|
-
|
|
12069
|
-
...workspaceRoots.map((root) =>
|
|
12211
|
+
resolve18(workspace),
|
|
12212
|
+
...workspaceRoots.map((root) => resolve18(root))
|
|
12070
12213
|
]);
|
|
12071
12214
|
}
|
|
12072
12215
|
async createDraft(maxPatchBytes, operation, signal) {
|
|
@@ -12147,7 +12290,7 @@ var WriterLane = class {
|
|
|
12147
12290
|
return {
|
|
12148
12291
|
baseCommit,
|
|
12149
12292
|
patch,
|
|
12150
|
-
patchSha256:
|
|
12293
|
+
patchSha256: createHash17("sha256").update(patch).digest("hex"),
|
|
12151
12294
|
files,
|
|
12152
12295
|
worktreeCleaned,
|
|
12153
12296
|
value
|
|
@@ -12289,7 +12432,7 @@ var WriterLane = class {
|
|
|
12289
12432
|
if (topLevel.exitCode !== 0 || !topLevel.stdout.trim()) {
|
|
12290
12433
|
throw new Error("The primary workspace must be a Git repository root for writer lanes.");
|
|
12291
12434
|
}
|
|
12292
|
-
const discoveredRoot =
|
|
12435
|
+
const discoveredRoot = resolve18(topLevel.stdout.trim());
|
|
12293
12436
|
if (await realpath7(discoveredRoot) !== await realpath7(root)) {
|
|
12294
12437
|
throw new Error("The primary workspace must be the Git repository root for writer lanes.");
|
|
12295
12438
|
}
|
|
@@ -12298,7 +12441,7 @@ var WriterLane = class {
|
|
|
12298
12441
|
if (common.exitCode !== 0 || !common.stdout.trim()) {
|
|
12299
12442
|
throw new Error(`Unable to resolve the Git common directory: ${processDetail(common)}`);
|
|
12300
12443
|
}
|
|
12301
|
-
const commonDirectory = await realpath7(isAbsolute6(common.stdout.trim()) ? common.stdout.trim() :
|
|
12444
|
+
const commonDirectory = await realpath7(isAbsolute6(common.stdout.trim()) ? common.stdout.trim() : resolve18(repositoryRoot, common.stdout.trim()));
|
|
12302
12445
|
return { root: repositoryRoot, commonDirectory, runtime };
|
|
12303
12446
|
}
|
|
12304
12447
|
async head(repository) {
|
|
@@ -12310,7 +12453,7 @@ var WriterLane = class {
|
|
|
12310
12453
|
return value;
|
|
12311
12454
|
}
|
|
12312
12455
|
async cleanupWorktree(repository, worktree, added) {
|
|
12313
|
-
const physicalWorktree = await realpath7(worktree).catch(() =>
|
|
12456
|
+
const physicalWorktree = await realpath7(worktree).catch(() => resolve18(worktree));
|
|
12314
12457
|
if (added) {
|
|
12315
12458
|
await runIsolatedGit(repository.runtime, [
|
|
12316
12459
|
"worktree",
|
|
@@ -14236,7 +14379,7 @@ function formatResultDetail(result, paint = chalk2, glyphs = resolveCliGlyphs())
|
|
|
14236
14379
|
}
|
|
14237
14380
|
|
|
14238
14381
|
// src/cli/namespace-leases.ts
|
|
14239
|
-
import { resolve as
|
|
14382
|
+
import { resolve as resolve19 } from "node:path";
|
|
14240
14383
|
function cliNamespaceLeaseScopes(actionCommand) {
|
|
14241
14384
|
const names = commandNames(actionCommand);
|
|
14242
14385
|
const topLevel = names[1];
|
|
@@ -14258,7 +14401,7 @@ async function acquireCliNamespaceLeases(actionCommand) {
|
|
|
14258
14401
|
const scopes = cliNamespaceLeaseScopes(actionCommand);
|
|
14259
14402
|
const localOptions = actionCommand.opts();
|
|
14260
14403
|
const globalOptions = actionCommand.optsWithGlobals();
|
|
14261
|
-
const workspace =
|
|
14404
|
+
const workspace = resolve19(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
|
|
14262
14405
|
const targets = scopes.map((scope) => scope === "project" ? projectNamespacePaths(workspace).canonical : homeNamespacePaths().canonical);
|
|
14263
14406
|
const leases = [];
|
|
14264
14407
|
try {
|
|
@@ -14510,7 +14653,7 @@ function graphemes(value) {
|
|
|
14510
14653
|
|
|
14511
14654
|
// src/ui/theme.ts
|
|
14512
14655
|
import { lstat as lstat20, readdir as readdir8, readFile as readFile18 } from "node:fs/promises";
|
|
14513
|
-
import { basename as basename10, join as join19, resolve as
|
|
14656
|
+
import { basename as basename10, join as join19, resolve as resolve20 } from "node:path";
|
|
14514
14657
|
import React, { createContext, useContext } from "react";
|
|
14515
14658
|
function defineTheme(seed) {
|
|
14516
14659
|
return {
|
|
@@ -14632,7 +14775,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
14632
14775
|
userThemeNames.clear();
|
|
14633
14776
|
const loaded = [];
|
|
14634
14777
|
const errors = [];
|
|
14635
|
-
const resolvedDirectory =
|
|
14778
|
+
const resolvedDirectory = resolve20(directory);
|
|
14636
14779
|
let entries;
|
|
14637
14780
|
try {
|
|
14638
14781
|
entries = await readdir8(resolvedDirectory, { encoding: "utf8" });
|
|
@@ -17096,7 +17239,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17096
17239
|
return () => clearInterval(timer);
|
|
17097
17240
|
}, [busy]);
|
|
17098
17241
|
const requestPermission = useCallback((call, category) => {
|
|
17099
|
-
return new Promise((
|
|
17242
|
+
return new Promise((resolve25) => setPermission({ call, category, resolve: resolve25 }));
|
|
17100
17243
|
}, []);
|
|
17101
17244
|
const onEvent = useCallback((event) => {
|
|
17102
17245
|
switch (event.type) {
|
|
@@ -17932,8 +18075,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17932
18075
|
}, [append]);
|
|
17933
18076
|
function settlePermission(grant, stop = false) {
|
|
17934
18077
|
if (!permission) return;
|
|
17935
|
-
const { call, category, resolve:
|
|
17936
|
-
|
|
18078
|
+
const { call, category, resolve: resolve25 } = permission;
|
|
18079
|
+
resolve25(grant);
|
|
17937
18080
|
setPermission(void 0);
|
|
17938
18081
|
if (grant === "session") {
|
|
17939
18082
|
append({
|
|
@@ -19268,7 +19411,7 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
|
19268
19411
|
}
|
|
19269
19412
|
|
|
19270
19413
|
// src/runtime/extensions.ts
|
|
19271
|
-
import { resolve as
|
|
19414
|
+
import { resolve as resolve23 } from "node:path";
|
|
19272
19415
|
|
|
19273
19416
|
// src/mcp/manager.ts
|
|
19274
19417
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -19277,7 +19420,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
19277
19420
|
import stripAnsi5 from "strip-ansi";
|
|
19278
19421
|
|
|
19279
19422
|
// src/mcp/tool.ts
|
|
19280
|
-
import { createHash as
|
|
19423
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
19281
19424
|
import stripAnsi4 from "strip-ansi";
|
|
19282
19425
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
19283
19426
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
@@ -19448,7 +19591,7 @@ function fitToolName(name, identity2) {
|
|
|
19448
19591
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
19449
19592
|
}
|
|
19450
19593
|
function shortHash(value) {
|
|
19451
|
-
return
|
|
19594
|
+
return createHash18("sha256").update(value).digest("hex").slice(0, 8);
|
|
19452
19595
|
}
|
|
19453
19596
|
function sanitizeOutputText(value) {
|
|
19454
19597
|
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
@@ -19481,7 +19624,7 @@ function isRecord2(value) {
|
|
|
19481
19624
|
|
|
19482
19625
|
// src/mcp/validation.ts
|
|
19483
19626
|
import { stat as stat11, realpath as realpath8 } from "node:fs/promises";
|
|
19484
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
19627
|
+
import { isAbsolute as isAbsolute7, resolve as resolve21 } from "node:path";
|
|
19485
19628
|
var SERVER_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
19486
19629
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
19487
19630
|
var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
@@ -19553,9 +19696,9 @@ async function validateCwd(configured, options) {
|
|
|
19553
19696
|
if (configured !== void 0 && (configured.length > 4e3 || CONTROL_CHARACTERS.test(configured))) {
|
|
19554
19697
|
throw new Error("MCP working directory is invalid or too long");
|
|
19555
19698
|
}
|
|
19556
|
-
const defaultCwd =
|
|
19557
|
-
const candidate = configured ? isAbsolute7(configured) ?
|
|
19558
|
-
const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) =>
|
|
19699
|
+
const defaultCwd = resolve21(options.cwd ?? process.cwd());
|
|
19700
|
+
const candidate = configured ? isAbsolute7(configured) ? resolve21(configured) : resolve21(defaultCwd, configured) : defaultCwd;
|
|
19701
|
+
const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve21(root)) : [defaultCwd];
|
|
19559
19702
|
const resolvedCandidate = await realpath8(candidate).catch(() => {
|
|
19560
19703
|
throw new Error(`MCP working directory does not exist: ${candidate}`);
|
|
19561
19704
|
});
|
|
@@ -20209,7 +20352,7 @@ function scopeKey(scope, context) {
|
|
|
20209
20352
|
// src/skills/catalog.ts
|
|
20210
20353
|
import { lstat as lstat21, readFile as readFile20, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
|
|
20211
20354
|
import { homedir as homedir4 } from "node:os";
|
|
20212
|
-
import { basename as basename12, join as join21, resolve as
|
|
20355
|
+
import { basename as basename12, join as join21, resolve as resolve22 } from "node:path";
|
|
20213
20356
|
import { parse as parseYaml3 } from "yaml";
|
|
20214
20357
|
var SkillCatalog = class {
|
|
20215
20358
|
constructor(workspace, config) {
|
|
@@ -20278,14 +20421,14 @@ ${skill.content}
|
|
|
20278
20421
|
}
|
|
20279
20422
|
function discoveryLocations(workspace, configured) {
|
|
20280
20423
|
const home = homedir4();
|
|
20281
|
-
const workspaceRoot =
|
|
20424
|
+
const workspaceRoot = resolve22(workspace);
|
|
20282
20425
|
return [
|
|
20283
20426
|
{ path: join21(home, ".agents", "skills"), scope: "user", trusted: true },
|
|
20284
20427
|
{ path: join21(home, ".claude", "skills"), scope: "user", trusted: true },
|
|
20285
20428
|
{ path: join21(home, ".augment", "skills"), scope: "user", trusted: true },
|
|
20286
20429
|
{ path: join21(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
|
|
20287
20430
|
...configured.map((path) => {
|
|
20288
|
-
const resolved =
|
|
20431
|
+
const resolved = resolve22(workspaceRoot, path);
|
|
20289
20432
|
return {
|
|
20290
20433
|
path: resolved,
|
|
20291
20434
|
scope: "configured",
|
|
@@ -20314,7 +20457,7 @@ async function readMetadata(path) {
|
|
|
20314
20457
|
const { frontmatter } = splitFrontmatter(raw);
|
|
20315
20458
|
if (!frontmatter) return void 0;
|
|
20316
20459
|
const parsed = parseYaml3(frontmatter);
|
|
20317
|
-
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename12(
|
|
20460
|
+
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename12(resolve22(path, ".."));
|
|
20318
20461
|
const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
|
|
20319
20462
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
|
|
20320
20463
|
return void 0;
|
|
@@ -20335,7 +20478,7 @@ async function safeRead(path, maxBytes) {
|
|
|
20335
20478
|
try {
|
|
20336
20479
|
const info = await lstat21(path);
|
|
20337
20480
|
if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
|
|
20338
|
-
const parent =
|
|
20481
|
+
const parent = resolve22(path, "..");
|
|
20339
20482
|
const resolvedParent = await realpath9(parent);
|
|
20340
20483
|
const resolvedPath = await realpath9(path);
|
|
20341
20484
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
@@ -20520,7 +20663,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
20520
20663
|
delegation;
|
|
20521
20664
|
initialized = false;
|
|
20522
20665
|
static async create(config, registry, options = {}) {
|
|
20523
|
-
const runtime = new _ExtensionRuntime(config,
|
|
20666
|
+
const runtime = new _ExtensionRuntime(config, resolve23(config.workspaceRoots[0] ?? process.cwd()), options);
|
|
20524
20667
|
try {
|
|
20525
20668
|
await runtime.initialize(registry, options.signal);
|
|
20526
20669
|
return runtime;
|
|
@@ -20725,15 +20868,15 @@ function upgradeCommandOverride(env = process.env) {
|
|
|
20725
20868
|
return trimmed ? trimmed : void 0;
|
|
20726
20869
|
}
|
|
20727
20870
|
function runUpgrade(plan) {
|
|
20728
|
-
return new Promise((
|
|
20871
|
+
return new Promise((resolve25) => {
|
|
20729
20872
|
const child = spawn2(plan.command, plan.args, {
|
|
20730
20873
|
stdio: "inherit",
|
|
20731
20874
|
shell: plan.shell ?? false
|
|
20732
20875
|
});
|
|
20733
|
-
child.on("error", () =>
|
|
20876
|
+
child.on("error", () => resolve25({ ok: false, exitCode: 127, display: plan.display }));
|
|
20734
20877
|
child.on("close", (code) => {
|
|
20735
20878
|
const exitCode = code ?? 1;
|
|
20736
|
-
|
|
20879
|
+
resolve25({ ok: exitCode === 0, exitCode, display: plan.display });
|
|
20737
20880
|
});
|
|
20738
20881
|
});
|
|
20739
20882
|
}
|
|
@@ -20995,7 +21138,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
|
|
|
20995
21138
|
const store = new SessionStore(workspaceOption(options.workspace));
|
|
20996
21139
|
const session = await requireSessionSelector(store, id);
|
|
20997
21140
|
const markdown = sessionMarkdown(session);
|
|
20998
|
-
if (options.output) await writeFile3(
|
|
21141
|
+
if (options.output) await writeFile3(resolve24(options.output), markdown, "utf8");
|
|
20999
21142
|
else process.stdout.write(markdown);
|
|
21000
21143
|
});
|
|
21001
21144
|
var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
|
|
@@ -21372,7 +21515,7 @@ async function runChat(prompts, options) {
|
|
|
21372
21515
|
const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
|
|
21373
21516
|
const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
|
|
21374
21517
|
if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
|
|
21375
|
-
const workspace =
|
|
21518
|
+
const workspace = resolve24(options.workspace);
|
|
21376
21519
|
let config = await runtimeConfig(workspace, options);
|
|
21377
21520
|
let completedOnboarding = false;
|
|
21378
21521
|
if (!shouldPrint && needsFirstRunOnboarding(config)) {
|
|
@@ -21597,14 +21740,14 @@ async function runAgentSetup(options) {
|
|
|
21597
21740
|
`);
|
|
21598
21741
|
}
|
|
21599
21742
|
async function runtimeConfig(workspaceInput, options) {
|
|
21600
|
-
const workspace =
|
|
21743
|
+
const workspace = resolve24(workspaceInput);
|
|
21601
21744
|
const loaded = await loadConfig(workspace, options.config, {
|
|
21602
21745
|
trustProjectConfig: options.trustProjectConfig === true
|
|
21603
21746
|
});
|
|
21604
21747
|
const roots = [
|
|
21605
21748
|
workspace,
|
|
21606
21749
|
...loaded.workspaceRoots,
|
|
21607
|
-
...(options.addWorkspace ?? []).map((root) =>
|
|
21750
|
+
...(options.addWorkspace ?? []).map((root) => resolve24(workspace, root))
|
|
21608
21751
|
];
|
|
21609
21752
|
const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
|
|
21610
21753
|
return {
|
|
@@ -21760,7 +21903,7 @@ function collect(value, previous) {
|
|
|
21760
21903
|
}
|
|
21761
21904
|
function workspaceOption(value) {
|
|
21762
21905
|
const rootOptions = program.opts();
|
|
21763
|
-
return
|
|
21906
|
+
return resolve24(value ?? rootOptions.workspace ?? process.cwd());
|
|
21764
21907
|
}
|
|
21765
21908
|
function runtimeOptions(options) {
|
|
21766
21909
|
const root = program.opts();
|