@skein-code/cli 0.3.10 → 0.3.11
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 +23 -3
- package/dist/cli.js +1187 -431
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +31 -2
- package/docs/NEXT_STEPS.md +11 -9
- package/docs/PRODUCT.md +7 -1
- package/docs/PRODUCT_BENCHMARK.md +2 -1
- package/package.json +4 -4
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 basename12, resolve as
|
|
7
|
+
import { basename as basename12, resolve as resolve23 } 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.11",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -236,9 +236,9 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
-
"
|
|
240
|
-
"
|
|
241
|
-
"
|
|
239
|
+
"Large tool results now use a language-aware dynamic token budget with status-preserving head and tail receipts",
|
|
240
|
+
"Complete captured output can be read back through redacted, expiring, session-scoped local artifacts",
|
|
241
|
+
"Shell and MCP source truncation is explicit, and session deletion removes retained output"
|
|
242
242
|
]
|
|
243
243
|
},
|
|
244
244
|
bin: {
|
|
@@ -990,16 +990,16 @@ async function hashRegularFile(path) {
|
|
|
990
990
|
try {
|
|
991
991
|
const info = await handle.stat();
|
|
992
992
|
if (!info.isFile()) throw new Error(`Namespace entry is not a regular file: ${path}`);
|
|
993
|
-
const
|
|
993
|
+
const hash2 = createHash2("sha256");
|
|
994
994
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
995
995
|
let position = 0;
|
|
996
996
|
while (true) {
|
|
997
997
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
998
998
|
if (!bytesRead) break;
|
|
999
|
-
|
|
999
|
+
hash2.update(buffer.subarray(0, bytesRead));
|
|
1000
1000
|
position += bytesRead;
|
|
1001
1001
|
}
|
|
1002
|
-
return { sha256:
|
|
1002
|
+
return { sha256: hash2.digest("hex"), size: position };
|
|
1003
1003
|
} finally {
|
|
1004
1004
|
await handle.close();
|
|
1005
1005
|
}
|
|
@@ -1200,18 +1200,18 @@ var MemoryStore = class {
|
|
|
1200
1200
|
input2.lastVerifiedAt ?? (explicit ? now : void 0),
|
|
1201
1201
|
"Memory verification time"
|
|
1202
1202
|
);
|
|
1203
|
-
const
|
|
1203
|
+
const hash2 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
|
|
1204
1204
|
database.exec("BEGIN IMMEDIATE");
|
|
1205
1205
|
try {
|
|
1206
1206
|
const existing = database.prepare(
|
|
1207
1207
|
"SELECT * FROM memories WHERE content_hash = ?"
|
|
1208
|
-
).get(
|
|
1208
|
+
).get(hash2);
|
|
1209
1209
|
const conflicting = conflictKey ? database.prepare(`
|
|
1210
1210
|
SELECT * FROM memories
|
|
1211
1211
|
WHERE scope = ? AND scope_key = ? AND conflict_key = ?
|
|
1212
1212
|
AND status = 'active' AND content_hash <> ?
|
|
1213
1213
|
ORDER BY updated_at DESC
|
|
1214
|
-
`).all(input2.scope, scopeKey2, conflictKey,
|
|
1214
|
+
`).all(input2.scope, scopeKey2, conflictKey, hash2) : [];
|
|
1215
1215
|
const supersedesId = normalizeOptional(input2.supersedesId, 80) ?? conflicting[0]?.id;
|
|
1216
1216
|
let id;
|
|
1217
1217
|
if (existing) {
|
|
@@ -1256,7 +1256,7 @@ var MemoryStore = class {
|
|
|
1256
1256
|
importance,
|
|
1257
1257
|
confidence,
|
|
1258
1258
|
source,
|
|
1259
|
-
|
|
1259
|
+
hash2,
|
|
1260
1260
|
now,
|
|
1261
1261
|
now,
|
|
1262
1262
|
now,
|
|
@@ -1359,10 +1359,10 @@ var MemoryStore = class {
|
|
|
1359
1359
|
const conflictKey = normalizeOptional(input2.conflictKey, 240);
|
|
1360
1360
|
const candidateDefaultExpiry = !input2.expiresAt ? new Date(Date.now() + MEMORY_CANDIDATE_TTL_MS).toISOString() : void 0;
|
|
1361
1361
|
const expiresAt = normalizeTimestamp(input2.expiresAt ?? candidateDefaultExpiry, "Memory expiration");
|
|
1362
|
-
const
|
|
1362
|
+
const hash2 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
|
|
1363
1363
|
const existing = database.prepare(
|
|
1364
1364
|
"SELECT * FROM memory_candidates WHERE content_hash = ?"
|
|
1365
|
-
).get(
|
|
1365
|
+
).get(hash2);
|
|
1366
1366
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1367
1367
|
if (existing) {
|
|
1368
1368
|
if (existing.status === "approved" && existing.approved_memory_id) {
|
|
@@ -1409,7 +1409,7 @@ var MemoryStore = class {
|
|
|
1409
1409
|
confidence,
|
|
1410
1410
|
source,
|
|
1411
1411
|
rationale,
|
|
1412
|
-
|
|
1412
|
+
hash2,
|
|
1413
1413
|
now,
|
|
1414
1414
|
now,
|
|
1415
1415
|
revision ?? null,
|
|
@@ -4755,7 +4755,7 @@ function executableNames(command2) {
|
|
|
4755
4755
|
return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
|
|
4756
4756
|
}
|
|
4757
4757
|
function runProcess(command2, args, options) {
|
|
4758
|
-
return new Promise((
|
|
4758
|
+
return new Promise((resolve24, reject) => {
|
|
4759
4759
|
const started = Date.now();
|
|
4760
4760
|
const environment = options.inheritEnv === false ? {} : { ...process.env };
|
|
4761
4761
|
for (const name of options.unsetEnv ?? []) delete environment[name];
|
|
@@ -4776,6 +4776,8 @@ function runProcess(command2, args, options) {
|
|
|
4776
4776
|
let stderr = "";
|
|
4777
4777
|
let stdoutBytes = 0;
|
|
4778
4778
|
let stderrBytes = 0;
|
|
4779
|
+
let stdoutObservedBytes = 0;
|
|
4780
|
+
let stderrObservedBytes = 0;
|
|
4779
4781
|
let timedOut = false;
|
|
4780
4782
|
let callbackError;
|
|
4781
4783
|
const stdoutDecoder = new StringDecoder("utf8");
|
|
@@ -4798,12 +4800,14 @@ function runProcess(command2, args, options) {
|
|
|
4798
4800
|
}
|
|
4799
4801
|
};
|
|
4800
4802
|
child.stdout.on("data", (chunk) => {
|
|
4803
|
+
stdoutObservedBytes += chunk.length;
|
|
4801
4804
|
const appended = append(stdoutDecoder, chunk, stdoutBytes);
|
|
4802
4805
|
stdout += appended.text;
|
|
4803
4806
|
stdoutBytes = appended.usedBytes;
|
|
4804
4807
|
notify(options.onStdout, stdoutCallbackDecoder, chunk);
|
|
4805
4808
|
});
|
|
4806
4809
|
child.stderr.on("data", (chunk) => {
|
|
4810
|
+
stderrObservedBytes += chunk.length;
|
|
4807
4811
|
const appended = append(stderrDecoder, chunk, stderrBytes);
|
|
4808
4812
|
stderr += appended.text;
|
|
4809
4813
|
stderrBytes = appended.usedBytes;
|
|
@@ -4834,13 +4838,17 @@ function runProcess(command2, args, options) {
|
|
|
4834
4838
|
reject(callbackError);
|
|
4835
4839
|
return;
|
|
4836
4840
|
}
|
|
4837
|
-
|
|
4841
|
+
resolve24({
|
|
4838
4842
|
command: [command2, ...args].join(" "),
|
|
4839
4843
|
exitCode: code ?? (timedOut ? 124 : 1),
|
|
4840
4844
|
stdout,
|
|
4841
4845
|
stderr,
|
|
4842
4846
|
timedOut,
|
|
4843
|
-
durationMs: Date.now() - started
|
|
4847
|
+
durationMs: Date.now() - started,
|
|
4848
|
+
stdoutBytes: stdoutObservedBytes,
|
|
4849
|
+
stderrBytes: stderrObservedBytes,
|
|
4850
|
+
stdoutTruncated: stdoutObservedBytes > stdoutBytes,
|
|
4851
|
+
stderrTruncated: stderrObservedBytes > stderrBytes
|
|
4844
4852
|
});
|
|
4845
4853
|
});
|
|
4846
4854
|
if (options.stdin) child.stdin.end(options.stdin);
|
|
@@ -4961,6 +4969,14 @@ var contextSourceSchema = z5.object({
|
|
|
4961
4969
|
tokens: z5.number().int().nonnegative(),
|
|
4962
4970
|
addedAt: z5.string()
|
|
4963
4971
|
}).strict();
|
|
4972
|
+
var toolArtifactSchema = z5.object({
|
|
4973
|
+
toolCallId: z5.string().min(1).max(512).refine((value) => !/[\u0000-\u001f\u007f-\u009f]/u.test(value)),
|
|
4974
|
+
sha256: z5.string().regex(/^[a-f0-9]{64}$/u),
|
|
4975
|
+
bytes: z5.number().int().nonnegative().max(5 * 1024 * 1024),
|
|
4976
|
+
createdAt: z5.string().datetime(),
|
|
4977
|
+
expiresAt: z5.string().datetime(),
|
|
4978
|
+
redacted: z5.boolean()
|
|
4979
|
+
}).strict();
|
|
4964
4980
|
var verificationEvidenceSchema = z5.object({
|
|
4965
4981
|
toolCallId: z5.string(),
|
|
4966
4982
|
tool: z5.enum(["shell", "git"]),
|
|
@@ -5037,6 +5053,7 @@ var sessionSchema = z5.object({
|
|
|
5037
5053
|
compactedThroughMessageId: z5.string().optional(),
|
|
5038
5054
|
workingMemory: workingMemorySchema.optional(),
|
|
5039
5055
|
contextSources: z5.array(contextSourceSchema).max(64).optional(),
|
|
5056
|
+
toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
|
|
5040
5057
|
taskContract: taskContractSchema.optional(),
|
|
5041
5058
|
lastRun: lastRunSchema.optional(),
|
|
5042
5059
|
usage: z5.object({
|
|
@@ -5298,13 +5315,390 @@ async function exists2(path) {
|
|
|
5298
5315
|
}
|
|
5299
5316
|
}
|
|
5300
5317
|
|
|
5318
|
+
// src/session/tool-artifacts.ts
|
|
5319
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
5320
|
+
import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
5321
|
+
import { join as join11, resolve as resolve13 } from "node:path";
|
|
5322
|
+
import { z as z6 } from "zod";
|
|
5323
|
+
var MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
|
|
5324
|
+
var MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
5325
|
+
var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5326
|
+
var sessionIdSchema2 = z6.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/);
|
|
5327
|
+
var toolCallIdSchema = z6.string().min(1).max(512).refine((value) => !/[\u0000-\u001f\u007f-\u009f]/u.test(value), {
|
|
5328
|
+
message: "Tool call id cannot contain control characters."
|
|
5329
|
+
});
|
|
5330
|
+
var artifactSchema = z6.object({
|
|
5331
|
+
version: z6.literal(1),
|
|
5332
|
+
sessionId: sessionIdSchema2,
|
|
5333
|
+
toolCallId: toolCallIdSchema,
|
|
5334
|
+
sha256: z6.string().regex(/^[a-f0-9]{64}$/u),
|
|
5335
|
+
bytes: z6.number().int().nonnegative().max(MAX_ARTIFACT_BYTES),
|
|
5336
|
+
createdAt: z6.string().datetime(),
|
|
5337
|
+
expiresAt: z6.string().datetime(),
|
|
5338
|
+
redacted: z6.boolean(),
|
|
5339
|
+
content: z6.string()
|
|
5340
|
+
}).strict();
|
|
5341
|
+
var ToolArtifactStore = class {
|
|
5342
|
+
workspace;
|
|
5343
|
+
directory;
|
|
5344
|
+
managedDirectory;
|
|
5345
|
+
now;
|
|
5346
|
+
maxArtifactBytes;
|
|
5347
|
+
maxTotalBytes;
|
|
5348
|
+
retentionMs;
|
|
5349
|
+
writes = Promise.resolve();
|
|
5350
|
+
constructor(workspace, options = {}) {
|
|
5351
|
+
this.workspace = resolve13(workspace);
|
|
5352
|
+
this.managedDirectory = options.directory === void 0;
|
|
5353
|
+
this.directory = options.directory ? resolve13(options.directory) : join11(resolveProjectNamespaceSync(this.workspace).active, "tool-artifacts");
|
|
5354
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
5355
|
+
this.maxArtifactBytes = options.maxArtifactBytes ?? MAX_ARTIFACT_BYTES;
|
|
5356
|
+
this.maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
5357
|
+
this.retentionMs = options.retentionMs ?? RETENTION_MS;
|
|
5358
|
+
if (!Number.isSafeInteger(this.maxArtifactBytes) || this.maxArtifactBytes < 1 || this.maxArtifactBytes > MAX_ARTIFACT_BYTES) {
|
|
5359
|
+
throw new Error(`maxArtifactBytes must be between 1 and ${MAX_ARTIFACT_BYTES}.`);
|
|
5360
|
+
}
|
|
5361
|
+
if (!Number.isSafeInteger(this.maxTotalBytes) || this.maxTotalBytes < this.maxArtifactBytes) {
|
|
5362
|
+
throw new Error("maxTotalBytes must be at least maxArtifactBytes.");
|
|
5363
|
+
}
|
|
5364
|
+
if (!Number.isSafeInteger(this.retentionMs) || this.retentionMs < 1) {
|
|
5365
|
+
throw new Error("retentionMs must be a positive integer.");
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
async archive(sessionId, toolCallId, content, options) {
|
|
5369
|
+
sessionIdSchema2.parse(sessionId);
|
|
5370
|
+
toolCallIdSchema.parse(toolCallId);
|
|
5371
|
+
const bytes = Buffer.byteLength(content);
|
|
5372
|
+
if (bytes > this.maxArtifactBytes) return { stored: false, reason: "too_large" };
|
|
5373
|
+
return this.enqueue(() => this.withManagedLease(async () => {
|
|
5374
|
+
await this.ensureDirectory();
|
|
5375
|
+
const now = this.now();
|
|
5376
|
+
const artifacts = await this.loadArtifacts();
|
|
5377
|
+
await this.removeExpired(artifacts, now);
|
|
5378
|
+
const active = artifacts.filter((artifact2) => artifact2.expiresAt > now.toISOString());
|
|
5379
|
+
const sha256 = hash(content);
|
|
5380
|
+
const existing = active.find((artifact2) => artifact2.sessionId === sessionId && artifact2.toolCallId === toolCallId);
|
|
5381
|
+
if (existing) {
|
|
5382
|
+
if (existing.sha256 === sha256 && existing.bytes === bytes && existing.redacted === options.redacted) {
|
|
5383
|
+
return { stored: true, artifact: reference(existing) };
|
|
5384
|
+
}
|
|
5385
|
+
return { stored: false, reason: "conflict" };
|
|
5386
|
+
}
|
|
5387
|
+
const artifact = {
|
|
5388
|
+
version: 1,
|
|
5389
|
+
sessionId,
|
|
5390
|
+
toolCallId,
|
|
5391
|
+
sha256,
|
|
5392
|
+
bytes,
|
|
5393
|
+
createdAt: now.toISOString(),
|
|
5394
|
+
expiresAt: new Date(now.getTime() + this.retentionMs).toISOString(),
|
|
5395
|
+
redacted: options.redacted,
|
|
5396
|
+
content
|
|
5397
|
+
};
|
|
5398
|
+
const storageBytes = storedBytes(artifact);
|
|
5399
|
+
let retainedBytes = active.reduce((total, retained) => total + storedBytes(retained), 0);
|
|
5400
|
+
const evictable = active.slice().sort(
|
|
5401
|
+
(left, right) => left.createdAt.localeCompare(right.createdAt)
|
|
5402
|
+
);
|
|
5403
|
+
while (retainedBytes + storageBytes > this.maxTotalBytes && evictable.length) {
|
|
5404
|
+
const oldest = evictable.shift();
|
|
5405
|
+
await this.removeArtifact(oldest);
|
|
5406
|
+
retainedBytes -= storedBytes(oldest);
|
|
5407
|
+
}
|
|
5408
|
+
if (retainedBytes + storageBytes > this.maxTotalBytes) return { stored: false, reason: "total_limit" };
|
|
5409
|
+
const path = this.pathFor(sessionId, toolCallId);
|
|
5410
|
+
await ensureWorkspaceStorageDirectory(this.workspace, resolve13(path, ".."), {
|
|
5411
|
+
requireActiveNamespace: this.managedDirectory
|
|
5412
|
+
});
|
|
5413
|
+
await atomicWrite(path, `${JSON.stringify(artifact)}
|
|
5414
|
+
`, 384);
|
|
5415
|
+
return { stored: true, artifact: reference(artifact) };
|
|
5416
|
+
}));
|
|
5417
|
+
}
|
|
5418
|
+
async read(sessionId, toolCallId, options = {}) {
|
|
5419
|
+
sessionIdSchema2.parse(sessionId);
|
|
5420
|
+
toolCallIdSchema.parse(toolCallId);
|
|
5421
|
+
const startLine = options.startLine ?? 1;
|
|
5422
|
+
const startByte = options.startByte;
|
|
5423
|
+
const maxLines = options.maxLines ?? 200;
|
|
5424
|
+
const maxBytes = options.maxBytes ?? 3e3;
|
|
5425
|
+
if (startByte !== void 0 && options.startLine !== void 0) {
|
|
5426
|
+
throw new Error("Use either startLine or startByte, not both.");
|
|
5427
|
+
}
|
|
5428
|
+
if (!Number.isInteger(startLine) || startLine < 1) throw new Error("startLine must be a positive integer.");
|
|
5429
|
+
if (startByte !== void 0 && (!Number.isInteger(startByte) || startByte < 0)) {
|
|
5430
|
+
throw new Error("startByte must be a non-negative integer.");
|
|
5431
|
+
}
|
|
5432
|
+
if (!Number.isInteger(maxLines) || maxLines < 1 || maxLines > 1e3) {
|
|
5433
|
+
throw new Error("maxLines must be between 1 and 1000.");
|
|
5434
|
+
}
|
|
5435
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 256 || maxBytes > 32e3) {
|
|
5436
|
+
throw new Error("maxBytes must be between 256 and 32000.");
|
|
5437
|
+
}
|
|
5438
|
+
await this.writes;
|
|
5439
|
+
return this.withManagedLease(async () => {
|
|
5440
|
+
const artifact = await this.readArtifact(sessionId, toolCallId);
|
|
5441
|
+
const now = this.now();
|
|
5442
|
+
if (artifact.expiresAt <= now.toISOString()) {
|
|
5443
|
+
await this.removeArtifact(artifact);
|
|
5444
|
+
throw new Error("The retained tool output has expired. Re-run the tool if it is still needed.");
|
|
5445
|
+
}
|
|
5446
|
+
const page = startByte === void 0 ? readLinePage(artifact.content, startLine, maxLines, maxBytes) : readBytePage(artifact.content, startByte, maxBytes);
|
|
5447
|
+
return {
|
|
5448
|
+
...reference(artifact),
|
|
5449
|
+
...page
|
|
5450
|
+
};
|
|
5451
|
+
});
|
|
5452
|
+
}
|
|
5453
|
+
/** Remove expired output and return the still-valid receipts for one session. */
|
|
5454
|
+
async prune(sessionId) {
|
|
5455
|
+
sessionIdSchema2.parse(sessionId);
|
|
5456
|
+
return this.enqueue(() => this.withManagedLease(async () => {
|
|
5457
|
+
const artifacts = await this.loadArtifacts();
|
|
5458
|
+
const now = this.now();
|
|
5459
|
+
await this.removeExpired(artifacts, now);
|
|
5460
|
+
return artifacts.filter((artifact) => artifact.sessionId === sessionId && artifact.expiresAt > now.toISOString()).sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map(reference);
|
|
5461
|
+
}));
|
|
5462
|
+
}
|
|
5463
|
+
/** Delete every retained result owned by a removed session. */
|
|
5464
|
+
async removeSession(sessionId) {
|
|
5465
|
+
sessionIdSchema2.parse(sessionId);
|
|
5466
|
+
await this.enqueue(() => this.withManagedLease(async () => {
|
|
5467
|
+
if (!await this.directoryAvailable()) return;
|
|
5468
|
+
const directory = this.sessionDirectoryFor(sessionId);
|
|
5469
|
+
try {
|
|
5470
|
+
const info = await lstat12(directory);
|
|
5471
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
5472
|
+
throw new Error(`Tool artifact session storage is not a regular directory: ${directory}`);
|
|
5473
|
+
}
|
|
5474
|
+
await assertNoSymlinkPath(this.workspace, directory);
|
|
5475
|
+
} catch (error) {
|
|
5476
|
+
if (error.code === "ENOENT") return;
|
|
5477
|
+
throw error;
|
|
5478
|
+
}
|
|
5479
|
+
await rm2(directory, { recursive: true, force: true });
|
|
5480
|
+
}));
|
|
5481
|
+
}
|
|
5482
|
+
async readArtifact(sessionId, toolCallId) {
|
|
5483
|
+
const path = this.pathFor(sessionId, toolCallId);
|
|
5484
|
+
await this.assertRegularFile(path);
|
|
5485
|
+
let artifact;
|
|
5486
|
+
try {
|
|
5487
|
+
artifact = artifactSchema.parse(JSON.parse(await readFile7(path, "utf8")));
|
|
5488
|
+
} catch {
|
|
5489
|
+
throw new Error("Retained tool output is unreadable or corrupt.");
|
|
5490
|
+
}
|
|
5491
|
+
if (artifact.sessionId !== sessionId || artifact.toolCallId !== toolCallId) {
|
|
5492
|
+
throw new Error("Retained tool output does not belong to this session.");
|
|
5493
|
+
}
|
|
5494
|
+
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash(artifact.content)) {
|
|
5495
|
+
throw new Error("Retained tool output failed its integrity check.");
|
|
5496
|
+
}
|
|
5497
|
+
return artifact;
|
|
5498
|
+
}
|
|
5499
|
+
async loadArtifacts() {
|
|
5500
|
+
if (!await this.directoryAvailable()) return [];
|
|
5501
|
+
const artifacts = [];
|
|
5502
|
+
for (const entry of await readdir4(this.directory, { withFileTypes: true })) {
|
|
5503
|
+
const sessionDirectory = join11(this.directory, entry.name);
|
|
5504
|
+
if (entry.isSymbolicLink()) throw new Error(`Tool artifact storage contains a symbolic link: ${sessionDirectory}`);
|
|
5505
|
+
if (!entry.isDirectory() || !/^[a-f0-9]{64}$/u.test(entry.name)) continue;
|
|
5506
|
+
await assertNoSymlinkPath(this.workspace, sessionDirectory);
|
|
5507
|
+
for (const file of await readdir4(sessionDirectory, { withFileTypes: true })) {
|
|
5508
|
+
const path = join11(sessionDirectory, file.name);
|
|
5509
|
+
if (file.isSymbolicLink()) throw new Error(`Tool artifact storage contains a symbolic link: ${path}`);
|
|
5510
|
+
if (!file.isFile() || !/^[a-f0-9]{64}\.json$/u.test(file.name)) continue;
|
|
5511
|
+
await this.assertRegularFile(path);
|
|
5512
|
+
try {
|
|
5513
|
+
const artifact = artifactSchema.parse(JSON.parse(await readFile7(path, "utf8")));
|
|
5514
|
+
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash(artifact.content)) {
|
|
5515
|
+
throw new Error("integrity");
|
|
5516
|
+
}
|
|
5517
|
+
if (sessionDirectory !== this.sessionDirectoryFor(artifact.sessionId) || path !== this.pathFor(artifact.sessionId, artifact.toolCallId)) {
|
|
5518
|
+
throw new Error("identity");
|
|
5519
|
+
}
|
|
5520
|
+
artifacts.push(artifact);
|
|
5521
|
+
} catch {
|
|
5522
|
+
throw new Error(`Tool artifact is unreadable or corrupt: ${path}`);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
return artifacts;
|
|
5527
|
+
}
|
|
5528
|
+
async removeExpired(artifacts, now) {
|
|
5529
|
+
for (const artifact of artifacts) {
|
|
5530
|
+
if (artifact.expiresAt <= now.toISOString()) await this.removeArtifact(artifact);
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
async removeArtifact(artifact) {
|
|
5534
|
+
await rm2(this.pathFor(artifact.sessionId, artifact.toolCallId), { force: true });
|
|
5535
|
+
}
|
|
5536
|
+
pathFor(sessionId, toolCallId) {
|
|
5537
|
+
return join11(this.sessionDirectoryFor(sessionId), `${hash(`call\0${toolCallId}`)}.json`);
|
|
5538
|
+
}
|
|
5539
|
+
sessionDirectoryFor(sessionId) {
|
|
5540
|
+
return join11(this.directory, hash(`session\0${sessionId}`));
|
|
5541
|
+
}
|
|
5542
|
+
async ensureDirectory() {
|
|
5543
|
+
await ensureWorkspaceStorageDirectory(this.workspace, this.directory, {
|
|
5544
|
+
requireActiveNamespace: this.managedDirectory
|
|
5545
|
+
});
|
|
5546
|
+
}
|
|
5547
|
+
async directoryAvailable() {
|
|
5548
|
+
if (this.managedDirectory) await assertNoSymlinkPath(this.workspace, this.directory);
|
|
5549
|
+
try {
|
|
5550
|
+
const info = await lstat12(this.directory);
|
|
5551
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
5552
|
+
throw new Error(`Tool artifact storage is not a regular directory: ${this.directory}`);
|
|
5553
|
+
}
|
|
5554
|
+
return true;
|
|
5555
|
+
} catch (error) {
|
|
5556
|
+
if (error.code === "ENOENT") return false;
|
|
5557
|
+
throw error;
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
async assertRegularFile(path) {
|
|
5561
|
+
await assertNoSymlinkPath(this.workspace, resolve13(path, ".."));
|
|
5562
|
+
try {
|
|
5563
|
+
const info = await lstat12(path);
|
|
5564
|
+
if (info.isSymbolicLink() || !info.isFile()) {
|
|
5565
|
+
throw new Error(`Tool artifact cannot be a symbolic link or non-regular file: ${path}`);
|
|
5566
|
+
}
|
|
5567
|
+
} catch (error) {
|
|
5568
|
+
if (error.code === "ENOENT") {
|
|
5569
|
+
throw new Error("No retained tool output matches this tool call in the current session.");
|
|
5570
|
+
}
|
|
5571
|
+
throw error;
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
async withManagedLease(operation) {
|
|
5575
|
+
if (!this.managedDirectory) return operation();
|
|
5576
|
+
return withNamespaceLease(projectNamespacePaths(this.workspace).canonical, "shared", async () => {
|
|
5577
|
+
assertActiveProjectNamespacePath(this.workspace, this.directory);
|
|
5578
|
+
return operation();
|
|
5579
|
+
});
|
|
5580
|
+
}
|
|
5581
|
+
async enqueue(operation) {
|
|
5582
|
+
const next = this.writes.then(operation);
|
|
5583
|
+
this.writes = next.then(() => void 0, () => void 0);
|
|
5584
|
+
return next;
|
|
5585
|
+
}
|
|
5586
|
+
};
|
|
5587
|
+
function reference(artifact) {
|
|
5588
|
+
return {
|
|
5589
|
+
toolCallId: artifact.toolCallId,
|
|
5590
|
+
sha256: artifact.sha256,
|
|
5591
|
+
bytes: artifact.bytes,
|
|
5592
|
+
createdAt: artifact.createdAt,
|
|
5593
|
+
expiresAt: artifact.expiresAt,
|
|
5594
|
+
redacted: artifact.redacted
|
|
5595
|
+
};
|
|
5596
|
+
}
|
|
5597
|
+
function hash(value) {
|
|
5598
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
5599
|
+
}
|
|
5600
|
+
function storedBytes(artifact) {
|
|
5601
|
+
return Buffer.byteLength(JSON.stringify(artifact)) + 1;
|
|
5602
|
+
}
|
|
5603
|
+
function readLinePage(content, startLine, maxLines, maxBytes) {
|
|
5604
|
+
const ranges = lineRanges(content);
|
|
5605
|
+
const startIndex = startLine - 1;
|
|
5606
|
+
if (startIndex >= ranges.length) {
|
|
5607
|
+
const totalBytes = Buffer.byteLength(content);
|
|
5608
|
+
return {
|
|
5609
|
+
content: "",
|
|
5610
|
+
startLine,
|
|
5611
|
+
endLine: startLine - 1,
|
|
5612
|
+
totalLines: ranges.length,
|
|
5613
|
+
startByte: totalBytes,
|
|
5614
|
+
endByte: totalBytes,
|
|
5615
|
+
hasMore: false
|
|
5616
|
+
};
|
|
5617
|
+
}
|
|
5618
|
+
const endIndex = Math.min(ranges.length - 1, startIndex + maxLines - 1);
|
|
5619
|
+
const startCharacter = ranges[startIndex]?.start ?? content.length;
|
|
5620
|
+
const endCharacter = ranges[endIndex]?.end ?? content.length;
|
|
5621
|
+
const startByte = Buffer.byteLength(content.slice(0, startCharacter));
|
|
5622
|
+
const selected = Buffer.from(content.slice(startCharacter, endCharacter));
|
|
5623
|
+
if (selected.length > maxBytes) {
|
|
5624
|
+
const page = sliceUtf8Buffer(selected, 0, maxBytes);
|
|
5625
|
+
const pageContent = page.content;
|
|
5626
|
+
return {
|
|
5627
|
+
content: pageContent,
|
|
5628
|
+
startLine,
|
|
5629
|
+
endLine: startLine + countNewlines(pageContent),
|
|
5630
|
+
totalLines: ranges.length,
|
|
5631
|
+
startByte,
|
|
5632
|
+
endByte: startByte + page.bytes,
|
|
5633
|
+
hasMore: true,
|
|
5634
|
+
nextStartByte: startByte + page.bytes
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
5637
|
+
const endLine = endIndex + 1;
|
|
5638
|
+
const hasMore = endLine < ranges.length;
|
|
5639
|
+
return {
|
|
5640
|
+
content: selected.toString("utf8"),
|
|
5641
|
+
startLine,
|
|
5642
|
+
endLine,
|
|
5643
|
+
totalLines: ranges.length,
|
|
5644
|
+
startByte,
|
|
5645
|
+
endByte: startByte + selected.length,
|
|
5646
|
+
hasMore,
|
|
5647
|
+
...hasMore ? { nextStartLine: endLine + 1 } : {}
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5650
|
+
function readBytePage(content, requestedStart, maxBytes) {
|
|
5651
|
+
const buffer = Buffer.from(content);
|
|
5652
|
+
const startByte = Math.min(requestedStart, buffer.length);
|
|
5653
|
+
if (startByte < buffer.length && isUtf8Continuation(buffer[startByte] ?? 0)) {
|
|
5654
|
+
throw new Error("startByte must be an exact UTF-8 boundary shown by a previous page.");
|
|
5655
|
+
}
|
|
5656
|
+
const page = sliceUtf8Buffer(buffer, startByte, maxBytes);
|
|
5657
|
+
const prefix = buffer.subarray(0, startByte).toString("utf8");
|
|
5658
|
+
const startLine = countNewlines(prefix) + 1;
|
|
5659
|
+
const endByte = startByte + page.bytes;
|
|
5660
|
+
const hasMore = endByte < buffer.length;
|
|
5661
|
+
return {
|
|
5662
|
+
content: page.content,
|
|
5663
|
+
startLine,
|
|
5664
|
+
endLine: startLine + countNewlines(page.content),
|
|
5665
|
+
totalLines: countNewlines(content) + 1,
|
|
5666
|
+
startByte,
|
|
5667
|
+
endByte,
|
|
5668
|
+
hasMore,
|
|
5669
|
+
...hasMore ? { nextStartByte: endByte } : {}
|
|
5670
|
+
};
|
|
5671
|
+
}
|
|
5672
|
+
function sliceUtf8Buffer(buffer, start, maxBytes) {
|
|
5673
|
+
let end = Math.min(buffer.length, start + maxBytes);
|
|
5674
|
+
while (end > start && isUtf8Continuation(buffer[end] ?? 0)) end -= 1;
|
|
5675
|
+
return { content: buffer.subarray(start, end).toString("utf8"), bytes: end - start };
|
|
5676
|
+
}
|
|
5677
|
+
function lineRanges(content) {
|
|
5678
|
+
const ranges = [];
|
|
5679
|
+
const expression = /\r?\n/gu;
|
|
5680
|
+
let start = 0;
|
|
5681
|
+
for (const match of content.matchAll(expression)) {
|
|
5682
|
+
ranges.push({ start, end: match.index });
|
|
5683
|
+
start = match.index + match[0].length;
|
|
5684
|
+
}
|
|
5685
|
+
ranges.push({ start, end: content.length });
|
|
5686
|
+
return ranges;
|
|
5687
|
+
}
|
|
5688
|
+
function countNewlines(value) {
|
|
5689
|
+
return value.match(/\n/gu)?.length ?? 0;
|
|
5690
|
+
}
|
|
5691
|
+
function isUtf8Continuation(byte) {
|
|
5692
|
+
return byte >= 128 && byte < 192;
|
|
5693
|
+
}
|
|
5694
|
+
|
|
5301
5695
|
// src/tools/apply-patch.ts
|
|
5302
|
-
import { lstat as
|
|
5696
|
+
import { lstat as lstat13, readFile as readFile8, unlink as unlink4 } from "node:fs/promises";
|
|
5303
5697
|
import { applyPatch as applyUnifiedPatch, parsePatch } from "diff";
|
|
5304
|
-
import { z as
|
|
5305
|
-
var inputSchema2 =
|
|
5306
|
-
patch:
|
|
5307
|
-
dry_run:
|
|
5698
|
+
import { z as z7 } from "zod";
|
|
5699
|
+
var inputSchema2 = z7.object({
|
|
5700
|
+
patch: z7.string().min(1).max(1e7),
|
|
5701
|
+
dry_run: z7.boolean().optional()
|
|
5308
5702
|
}).strict();
|
|
5309
5703
|
var applyPatchTool = {
|
|
5310
5704
|
definition: {
|
|
@@ -5560,10 +5954,10 @@ function findSequence(haystack, needle, hint, minimum) {
|
|
|
5560
5954
|
}
|
|
5561
5955
|
async function readSnapshot2(path) {
|
|
5562
5956
|
try {
|
|
5563
|
-
const info = await
|
|
5957
|
+
const info = await lstat13(path);
|
|
5564
5958
|
if (info.isSymbolicLink()) throw new Error(`Refusing to patch a symbolic link: ${path}`);
|
|
5565
5959
|
if (!info.isFile()) throw new Error(`Patch target is not a regular file: ${path}`);
|
|
5566
|
-
return { before: await
|
|
5960
|
+
return { before: await readFile8(path), mode: info.mode };
|
|
5567
5961
|
} catch (error) {
|
|
5568
5962
|
if (error.code === "ENOENT") return { before: null };
|
|
5569
5963
|
throw error;
|
|
@@ -5612,13 +6006,13 @@ function buffersEqual(left, right) {
|
|
|
5612
6006
|
}
|
|
5613
6007
|
|
|
5614
6008
|
// src/tools/git.ts
|
|
5615
|
-
import { join as
|
|
5616
|
-
import { z as
|
|
5617
|
-
var inputSchema3 =
|
|
5618
|
-
args:
|
|
5619
|
-
cwd:
|
|
5620
|
-
timeout_ms:
|
|
5621
|
-
stdin:
|
|
6009
|
+
import { join as join12 } from "node:path";
|
|
6010
|
+
import { z as z8 } from "zod";
|
|
6011
|
+
var inputSchema3 = z8.object({
|
|
6012
|
+
args: z8.array(z8.string().max(1e4)).min(1).max(200),
|
|
6013
|
+
cwd: z8.string().min(1).optional(),
|
|
6014
|
+
timeout_ms: z8.number().int().min(100).max(6e5).optional(),
|
|
6015
|
+
stdin: z8.string().max(5e6).optional()
|
|
5622
6016
|
}).strict();
|
|
5623
6017
|
var networkCommands = /* @__PURE__ */ new Set([
|
|
5624
6018
|
"clone",
|
|
@@ -5831,7 +6225,7 @@ var gitTool = {
|
|
|
5831
6225
|
for (const candidate of explicit) {
|
|
5832
6226
|
if (!candidate || candidate.startsWith("-")) continue;
|
|
5833
6227
|
try {
|
|
5834
|
-
paths.add(await context.workspace.resolvePath(
|
|
6228
|
+
paths.add(await context.workspace.resolvePath(join12(cwd, candidate), { allowMissing: true }));
|
|
5835
6229
|
} catch {
|
|
5836
6230
|
}
|
|
5837
6231
|
}
|
|
@@ -5852,7 +6246,7 @@ var gitTool = {
|
|
|
5852
6246
|
const candidates = candidate.includes(" -> ") ? candidate.split(" -> ") : [candidate];
|
|
5853
6247
|
for (const value of candidates) {
|
|
5854
6248
|
try {
|
|
5855
|
-
paths.add(await context.workspace.resolvePath(
|
|
6249
|
+
paths.add(await context.workspace.resolvePath(join12(cwd, value), { allowMissing: true }));
|
|
5856
6250
|
} catch {
|
|
5857
6251
|
}
|
|
5858
6252
|
}
|
|
@@ -5933,7 +6327,7 @@ async function collectGitChanges(before, after, runtime, cwd, context) {
|
|
|
5933
6327
|
const paths = [];
|
|
5934
6328
|
for (const candidate of [...candidates].slice(0, 2e3)) {
|
|
5935
6329
|
try {
|
|
5936
|
-
paths.push(await context.workspace.resolvePath(
|
|
6330
|
+
paths.push(await context.workspace.resolvePath(join12(cwd, candidate), { allowMissing: true }));
|
|
5937
6331
|
} catch {
|
|
5938
6332
|
}
|
|
5939
6333
|
}
|
|
@@ -6049,7 +6443,7 @@ async function validateGitWorkspaceArguments(args, command2, cwd, context) {
|
|
|
6049
6443
|
if (/^(?:[a-z]+:\/\/|[a-z]+@[^:]+:)/i.test(candidate)) continue;
|
|
6050
6444
|
if (command2 === "clone" && index === 0 && !candidate) continue;
|
|
6051
6445
|
await context.workspace.resolvePath(
|
|
6052
|
-
candidate.startsWith("/") ? candidate :
|
|
6446
|
+
candidate.startsWith("/") ? candidate : join12(cwd, candidate),
|
|
6053
6447
|
{ allowMissing: true }
|
|
6054
6448
|
);
|
|
6055
6449
|
}
|
|
@@ -6102,17 +6496,17 @@ function positionalArguments(args, command2) {
|
|
|
6102
6496
|
}
|
|
6103
6497
|
|
|
6104
6498
|
// src/tools/list.ts
|
|
6105
|
-
import { lstat as
|
|
6106
|
-
import { relative as relative6, resolve as
|
|
6499
|
+
import { lstat as lstat14 } from "node:fs/promises";
|
|
6500
|
+
import { relative as relative6, resolve as resolve14 } from "node:path";
|
|
6107
6501
|
import fg3 from "fast-glob";
|
|
6108
|
-
import { z as
|
|
6109
|
-
var inputSchema4 =
|
|
6110
|
-
path:
|
|
6111
|
-
pattern:
|
|
6112
|
-
depth:
|
|
6113
|
-
include_hidden:
|
|
6114
|
-
include_directories:
|
|
6115
|
-
limit:
|
|
6502
|
+
import { z as z9 } from "zod";
|
|
6503
|
+
var inputSchema4 = z9.object({
|
|
6504
|
+
path: z9.string().min(1).optional(),
|
|
6505
|
+
pattern: z9.string().min(1).optional(),
|
|
6506
|
+
depth: z9.number().int().min(1).max(20).optional(),
|
|
6507
|
+
include_hidden: z9.boolean().optional(),
|
|
6508
|
+
include_directories: z9.boolean().optional(),
|
|
6509
|
+
limit: z9.number().int().min(1).max(5e3).optional()
|
|
6116
6510
|
}).strict();
|
|
6117
6511
|
var ignored = [
|
|
6118
6512
|
"**/.git/**",
|
|
@@ -6161,11 +6555,11 @@ var listFilesTool = {
|
|
|
6161
6555
|
for (const path of selected) {
|
|
6162
6556
|
let safePath;
|
|
6163
6557
|
try {
|
|
6164
|
-
safePath = await context.workspace.resolvePath(
|
|
6558
|
+
safePath = await context.workspace.resolvePath(resolve14(directory, path), { expect: "any" });
|
|
6165
6559
|
} catch {
|
|
6166
6560
|
continue;
|
|
6167
6561
|
}
|
|
6168
|
-
const info = await
|
|
6562
|
+
const info = await lstat14(safePath);
|
|
6169
6563
|
rendered.push(`${info.isDirectory() ? "d" : "f"} ${relative6(directory, safePath)}${info.isDirectory() ? "/" : ""}`);
|
|
6170
6564
|
}
|
|
6171
6565
|
const base = workspaceAliasPath(directory, context.workspace.roots);
|
|
@@ -6182,14 +6576,14 @@ var listFilesTool = {
|
|
|
6182
6576
|
};
|
|
6183
6577
|
|
|
6184
6578
|
// src/tools/read.ts
|
|
6185
|
-
import { readFile as
|
|
6186
|
-
import { z as
|
|
6187
|
-
var inputSchema5 =
|
|
6188
|
-
path:
|
|
6189
|
-
start_line:
|
|
6190
|
-
end_line:
|
|
6191
|
-
line_numbers:
|
|
6192
|
-
max_bytes:
|
|
6579
|
+
import { readFile as readFile9, stat as stat7 } from "node:fs/promises";
|
|
6580
|
+
import { z as z10 } from "zod";
|
|
6581
|
+
var inputSchema5 = z10.object({
|
|
6582
|
+
path: z10.string().min(1),
|
|
6583
|
+
start_line: z10.number().int().positive().optional(),
|
|
6584
|
+
end_line: z10.number().int().positive().optional(),
|
|
6585
|
+
line_numbers: z10.boolean().optional(),
|
|
6586
|
+
max_bytes: z10.number().int().positive().max(1e6).optional()
|
|
6193
6587
|
}).strict();
|
|
6194
6588
|
var readFileTool = {
|
|
6195
6589
|
definition: {
|
|
@@ -6212,7 +6606,7 @@ var readFileTool = {
|
|
|
6212
6606
|
if (info.size > 1e7) {
|
|
6213
6607
|
throw new Error(`File is too large to read safely (${info.size} bytes).`);
|
|
6214
6608
|
}
|
|
6215
|
-
const buffer = await
|
|
6609
|
+
const buffer = await readFile9(path);
|
|
6216
6610
|
if (looksBinary(buffer)) throw new Error("Binary files cannot be read with read_file.");
|
|
6217
6611
|
const raw = buffer.toString("utf8");
|
|
6218
6612
|
const lines = raw.split("\n");
|
|
@@ -6256,6 +6650,73 @@ function truncateUtf8(input2, maxBytes) {
|
|
|
6256
6650
|
return buffer.subarray(0, end).toString("utf8");
|
|
6257
6651
|
}
|
|
6258
6652
|
|
|
6653
|
+
// src/tools/read-artifact.ts
|
|
6654
|
+
import { z as z11 } from "zod";
|
|
6655
|
+
var inputSchema6 = z11.object({
|
|
6656
|
+
sha256: z11.string().regex(/^[a-f0-9]{64}$/u),
|
|
6657
|
+
start_line: z11.number().int().positive().optional(),
|
|
6658
|
+
start_byte: z11.number().int().nonnegative().optional(),
|
|
6659
|
+
max_lines: z11.number().int().min(1).max(1e3).optional(),
|
|
6660
|
+
max_bytes: z11.number().int().min(256).max(768).optional()
|
|
6661
|
+
}).strict().refine((input2) => input2.start_line === void 0 || input2.start_byte === void 0, {
|
|
6662
|
+
message: "Use either start_line or start_byte, not both."
|
|
6663
|
+
});
|
|
6664
|
+
var readToolArtifactTool = {
|
|
6665
|
+
definition: {
|
|
6666
|
+
name: "read_tool_artifact",
|
|
6667
|
+
description: "Read a bounded page of oversized tool output retained for this session. Use only the SHA-256 shown in a tool-output receipt.",
|
|
6668
|
+
category: "read",
|
|
6669
|
+
inputSchema: jsonSchema({
|
|
6670
|
+
sha256: { type: "string", description: "The exact 64-character SHA-256 from a retained-output receipt." },
|
|
6671
|
+
start_line: { type: "integer", minimum: 1, default: 1 },
|
|
6672
|
+
start_byte: { type: "integer", minimum: 0, description: "Exact UTF-8 continuation byte shown by a previous page." },
|
|
6673
|
+
max_lines: { type: "integer", minimum: 1, maximum: 1e3, default: 200 },
|
|
6674
|
+
max_bytes: { type: "integer", minimum: 256, maximum: 768, default: 768 }
|
|
6675
|
+
}, ["sha256"])
|
|
6676
|
+
},
|
|
6677
|
+
async execute(arguments_, context) {
|
|
6678
|
+
const input2 = inputSchema6.parse(arguments_);
|
|
6679
|
+
const artifact = context.session.toolArtifacts?.find(
|
|
6680
|
+
(candidate) => candidate.sha256 === input2.sha256 && Date.parse(candidate.expiresAt) > Date.now()
|
|
6681
|
+
);
|
|
6682
|
+
if (!artifact) {
|
|
6683
|
+
throw new Error("No retained tool output matches this tool call in the current session.");
|
|
6684
|
+
}
|
|
6685
|
+
const store = context.toolArtifactStore ?? new ToolArtifactStore(context.workspace.primaryRoot);
|
|
6686
|
+
const page = await store.read(context.session.id, artifact.toolCallId, {
|
|
6687
|
+
...input2.start_line !== void 0 ? { startLine: input2.start_line } : {},
|
|
6688
|
+
...input2.start_byte !== void 0 ? { startByte: input2.start_byte } : {},
|
|
6689
|
+
...input2.max_lines !== void 0 ? { maxLines: input2.max_lines } : {},
|
|
6690
|
+
maxBytes: input2.max_bytes ?? 768
|
|
6691
|
+
});
|
|
6692
|
+
if (page.sha256 !== artifact.sha256 || page.bytes !== artifact.bytes) {
|
|
6693
|
+
throw new Error("Retained tool output no longer matches the session receipt.");
|
|
6694
|
+
}
|
|
6695
|
+
const heading = `Retained output page: lines ${page.startLine}-${page.endLine} of ${page.totalLines}; bytes ${page.startByte}-${page.endByte} of ${page.bytes}`;
|
|
6696
|
+
const continuation = page.nextStartByte !== void 0 ? `continue with start_byte=${page.nextStartByte}` : page.nextStartLine !== void 0 ? `continue with start_line=${page.nextStartLine}` : "";
|
|
6697
|
+
return {
|
|
6698
|
+
content: `${heading}
|
|
6699
|
+
${page.content}${page.hasMore ? `
|
|
6700
|
+
\u2026 more retained output; ${continuation}` : ""}`,
|
|
6701
|
+
metadata: {
|
|
6702
|
+
artifact: {
|
|
6703
|
+
toolCallId: page.toolCallId,
|
|
6704
|
+
sha256: page.sha256,
|
|
6705
|
+
bytes: page.bytes,
|
|
6706
|
+
expiresAt: page.expiresAt,
|
|
6707
|
+
redacted: page.redacted
|
|
6708
|
+
},
|
|
6709
|
+
startLine: page.startLine,
|
|
6710
|
+
endLine: page.endLine,
|
|
6711
|
+
totalLines: page.totalLines,
|
|
6712
|
+
startByte: page.startByte,
|
|
6713
|
+
endByte: page.endByte,
|
|
6714
|
+
truncated: page.hasMore
|
|
6715
|
+
}
|
|
6716
|
+
};
|
|
6717
|
+
}
|
|
6718
|
+
};
|
|
6719
|
+
|
|
6259
6720
|
// src/tools/registry.ts
|
|
6260
6721
|
var ToolRegistry = class {
|
|
6261
6722
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -6303,19 +6764,19 @@ function assertToolName(name) {
|
|
|
6303
6764
|
}
|
|
6304
6765
|
|
|
6305
6766
|
// src/tools/search.ts
|
|
6306
|
-
import { readFile as
|
|
6307
|
-
import { resolve as
|
|
6767
|
+
import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
6768
|
+
import { resolve as resolve15 } from "node:path";
|
|
6308
6769
|
import fg4 from "fast-glob";
|
|
6309
|
-
import { z as
|
|
6310
|
-
var
|
|
6311
|
-
query:
|
|
6312
|
-
path:
|
|
6313
|
-
pattern:
|
|
6314
|
-
mode:
|
|
6315
|
-
literal:
|
|
6316
|
-
case_sensitive:
|
|
6317
|
-
context_lines:
|
|
6318
|
-
max_results:
|
|
6770
|
+
import { z as z12 } from "zod";
|
|
6771
|
+
var inputSchema7 = z12.object({
|
|
6772
|
+
query: z12.string().min(1).max(1e4),
|
|
6773
|
+
path: z12.string().min(1).optional(),
|
|
6774
|
+
pattern: z12.string().min(1).optional(),
|
|
6775
|
+
mode: z12.enum(["text", "ranked"]).optional(),
|
|
6776
|
+
literal: z12.boolean().optional(),
|
|
6777
|
+
case_sensitive: z12.boolean().optional(),
|
|
6778
|
+
context_lines: z12.number().int().min(0).max(20).optional(),
|
|
6779
|
+
max_results: z12.number().int().min(1).max(500).optional()
|
|
6319
6780
|
}).strict();
|
|
6320
6781
|
var ignore = [
|
|
6321
6782
|
"**/.git/**",
|
|
@@ -6350,7 +6811,7 @@ var searchCodeTool = {
|
|
|
6350
6811
|
}, ["query"])
|
|
6351
6812
|
},
|
|
6352
6813
|
async execute(arguments_, context) {
|
|
6353
|
-
const input2 =
|
|
6814
|
+
const input2 = inputSchema7.parse(arguments_);
|
|
6354
6815
|
const directory = await context.workspace.resolveDirectory(input2.path ?? ".");
|
|
6355
6816
|
if ((input2.mode ?? "text") === "ranked") {
|
|
6356
6817
|
if (!context.contextEngine) throw new Error("Local ranked retrieval is unavailable.");
|
|
@@ -6374,7 +6835,7 @@ var searchCodeTool = {
|
|
|
6374
6835
|
if (results.length >= maxResults) break;
|
|
6375
6836
|
let path;
|
|
6376
6837
|
try {
|
|
6377
|
-
path = await context.workspace.resolvePath(
|
|
6838
|
+
path = await context.workspace.resolvePath(resolve15(directory, file), { expect: "file" });
|
|
6378
6839
|
} catch {
|
|
6379
6840
|
skipped += 1;
|
|
6380
6841
|
continue;
|
|
@@ -6384,7 +6845,7 @@ var searchCodeTool = {
|
|
|
6384
6845
|
skipped += 1;
|
|
6385
6846
|
continue;
|
|
6386
6847
|
}
|
|
6387
|
-
const buffer = await
|
|
6848
|
+
const buffer = await readFile10(path);
|
|
6388
6849
|
if (buffer.subarray(0, 8192).includes(0)) {
|
|
6389
6850
|
skipped += 1;
|
|
6390
6851
|
continue;
|
|
@@ -6456,17 +6917,17 @@ ${hit.content}`
|
|
|
6456
6917
|
}
|
|
6457
6918
|
|
|
6458
6919
|
// src/tools/shell.ts
|
|
6459
|
-
import { createHash as
|
|
6460
|
-
import { lstat as
|
|
6461
|
-
import { join as
|
|
6462
|
-
import { z as
|
|
6463
|
-
var
|
|
6464
|
-
command:
|
|
6465
|
-
cwd:
|
|
6466
|
-
timeout_ms:
|
|
6467
|
-
max_output_bytes:
|
|
6468
|
-
env:
|
|
6469
|
-
stdin:
|
|
6920
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
6921
|
+
import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
|
|
6922
|
+
import { join as join13 } from "node:path";
|
|
6923
|
+
import { z as z13 } from "zod";
|
|
6924
|
+
var inputSchema8 = z13.object({
|
|
6925
|
+
command: z13.string().min(1).max(1e5),
|
|
6926
|
+
cwd: z13.string().min(1).optional(),
|
|
6927
|
+
timeout_ms: z13.number().int().min(100).max(6e5).optional(),
|
|
6928
|
+
max_output_bytes: z13.number().int().min(1e3).max(5e6).optional(),
|
|
6929
|
+
env: z13.record(z13.string(), z13.string()).optional(),
|
|
6930
|
+
stdin: z13.string().max(5e6).optional()
|
|
6470
6931
|
}).strict();
|
|
6471
6932
|
var shellTool = {
|
|
6472
6933
|
definition: {
|
|
@@ -6483,7 +6944,7 @@ var shellTool = {
|
|
|
6483
6944
|
}, ["command"])
|
|
6484
6945
|
},
|
|
6485
6946
|
permissionCategories(arguments_) {
|
|
6486
|
-
const input2 =
|
|
6947
|
+
const input2 = inputSchema8.parse(arguments_);
|
|
6487
6948
|
validateEnvironment(input2.env);
|
|
6488
6949
|
return [
|
|
6489
6950
|
"shell",
|
|
@@ -6493,12 +6954,12 @@ var shellTool = {
|
|
|
6493
6954
|
];
|
|
6494
6955
|
},
|
|
6495
6956
|
async affectedPaths(arguments_, context) {
|
|
6496
|
-
const input2 =
|
|
6957
|
+
const input2 = inputSchema8.parse(arguments_);
|
|
6497
6958
|
if (!appearsToModifyWorkspace(input2.command)) return [];
|
|
6498
6959
|
return collectAffectedPaths(input2.command, input2.cwd ?? ".", context);
|
|
6499
6960
|
},
|
|
6500
6961
|
async execute(arguments_, context) {
|
|
6501
|
-
const input2 =
|
|
6962
|
+
const input2 = inputSchema8.parse(arguments_);
|
|
6502
6963
|
validateEnvironment(input2.env);
|
|
6503
6964
|
const cwd = await context.workspace.resolveDirectory(input2.cwd ?? ".");
|
|
6504
6965
|
const candidates = appearsToModifyWorkspace(input2.command) ? await collectAffectedPaths(input2.command, input2.cwd ?? ".", context) : [];
|
|
@@ -6538,7 +6999,8 @@ Execution interrupted: ${error instanceof Error ? error.message : String(error)}
|
|
|
6538
6999
|
result.stdout ? `stdout:
|
|
6539
7000
|
${result.stdout}` : "",
|
|
6540
7001
|
result.stderr ? `stderr:
|
|
6541
|
-
${result.stderr}` : ""
|
|
7002
|
+
${result.stderr}` : "",
|
|
7003
|
+
result.stdoutTruncated || result.stderrTruncated ? `Source output exceeded the command capture limit (${result.stdoutBytes} stdout bytes, ${result.stderrBytes} stderr bytes observed). Rerun with max_output_bytes up to 5000000 or narrow the command; omitted source bytes are not recoverable from this result.` : ""
|
|
6542
7004
|
].filter(Boolean);
|
|
6543
7005
|
return {
|
|
6544
7006
|
ok: result.exitCode === 0 && !result.timedOut,
|
|
@@ -6548,6 +7010,9 @@ ${result.stderr}` : ""
|
|
|
6548
7010
|
exitCode: result.exitCode,
|
|
6549
7011
|
timedOut: result.timedOut,
|
|
6550
7012
|
durationMs: result.durationMs,
|
|
7013
|
+
stdoutBytes: result.stdoutBytes,
|
|
7014
|
+
stderrBytes: result.stderrBytes,
|
|
7015
|
+
sourceTruncated: result.stdoutTruncated || result.stderrTruncated,
|
|
6551
7016
|
changeTracking: candidates.length ? "targeted" : beforeWorkspace && afterWorkspace && beforeWorkspace.complete && afterWorkspace.complete ? "workspace-snapshot" : beforeWorkspace ? "unresolved" : "read-only"
|
|
6552
7017
|
},
|
|
6553
7018
|
...changedFiles.length ? { changedFiles } : {}
|
|
@@ -6611,7 +7076,7 @@ async function collectAffectedPaths(command2, cwdInput, context) {
|
|
|
6611
7076
|
allowMissing: true
|
|
6612
7077
|
});
|
|
6613
7078
|
try {
|
|
6614
|
-
if ((await
|
|
7079
|
+
if ((await lstat15(path)).isDirectory()) continue;
|
|
6615
7080
|
} catch (error) {
|
|
6616
7081
|
if (error.code !== "ENOENT") throw error;
|
|
6617
7082
|
}
|
|
@@ -6639,7 +7104,7 @@ async function changedPaths(paths, before) {
|
|
|
6639
7104
|
}
|
|
6640
7105
|
async function snapshotPath(path) {
|
|
6641
7106
|
try {
|
|
6642
|
-
const info = await
|
|
7107
|
+
const info = await lstat15(path);
|
|
6643
7108
|
return { exists: true, size: info.size, mtimeMs: info.mtimeMs };
|
|
6644
7109
|
} catch (error) {
|
|
6645
7110
|
if (error.code === "ENOENT") return { exists: false };
|
|
@@ -6655,18 +7120,18 @@ async function captureWorkspaceSnapshot(roots) {
|
|
|
6655
7120
|
if (!discovered.complete) complete = false;
|
|
6656
7121
|
for (const path of discovered.files) {
|
|
6657
7122
|
try {
|
|
6658
|
-
const info = await
|
|
7123
|
+
const info = await lstat15(path);
|
|
6659
7124
|
if (!info.isFile() || info.isSymbolicLink()) continue;
|
|
6660
7125
|
if (files.size >= MAX_SNAPSHOT_FILES || info.size > MAX_SNAPSHOT_FILE_BYTES || totalBytes + info.size > MAX_SNAPSHOT_TOTAL_BYTES) {
|
|
6661
7126
|
complete = false;
|
|
6662
7127
|
continue;
|
|
6663
7128
|
}
|
|
6664
|
-
const content = await
|
|
7129
|
+
const content = await readFile11(path);
|
|
6665
7130
|
totalBytes += content.length;
|
|
6666
7131
|
files.set(path, {
|
|
6667
7132
|
size: info.size,
|
|
6668
7133
|
mtimeMs: info.mtimeMs,
|
|
6669
|
-
hash:
|
|
7134
|
+
hash: createHash8("sha256").update(content).digest("hex")
|
|
6670
7135
|
});
|
|
6671
7136
|
} catch (error) {
|
|
6672
7137
|
if (error.code !== "ENOENT") complete = false;
|
|
@@ -6687,14 +7152,14 @@ async function discoverSnapshotFiles(root) {
|
|
|
6687
7152
|
const directory = pending.pop();
|
|
6688
7153
|
if (!directory) break;
|
|
6689
7154
|
try {
|
|
6690
|
-
const entries = await
|
|
7155
|
+
const entries = await readdir5(directory, { withFileTypes: true, encoding: "utf8" });
|
|
6691
7156
|
for (const entry of entries) {
|
|
6692
7157
|
if (ignored2.has(entry.name) || /^\.skein\.(?:migrating|rollback)-/u.test(entry.name)) continue;
|
|
6693
7158
|
if (entry.isSymbolicLink()) {
|
|
6694
7159
|
complete = false;
|
|
6695
7160
|
continue;
|
|
6696
7161
|
}
|
|
6697
|
-
const path =
|
|
7162
|
+
const path = join13(directory, entry.name);
|
|
6698
7163
|
if (entry.isDirectory()) pending.push(path);
|
|
6699
7164
|
else if (entry.isFile()) files.push(path);
|
|
6700
7165
|
}
|
|
@@ -6708,18 +7173,18 @@ async function discoverSnapshotFiles(root) {
|
|
|
6708
7173
|
|
|
6709
7174
|
// src/tools/task.ts
|
|
6710
7175
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
6711
|
-
import { z as
|
|
6712
|
-
var taskSchema2 =
|
|
6713
|
-
id:
|
|
6714
|
-
title:
|
|
6715
|
-
status:
|
|
7176
|
+
import { z as z14 } from "zod";
|
|
7177
|
+
var taskSchema2 = z14.object({
|
|
7178
|
+
id: z14.string().min(1).optional(),
|
|
7179
|
+
title: z14.string().min(1).max(500),
|
|
7180
|
+
status: z14.enum(["pending", "in_progress", "completed"])
|
|
6716
7181
|
}).strict();
|
|
6717
|
-
var
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
7182
|
+
var inputSchema9 = z14.discriminatedUnion("action", [
|
|
7183
|
+
z14.object({ action: z14.literal("list") }).strict(),
|
|
7184
|
+
z14.object({ action: z14.literal("add"), title: z14.string().min(1).max(500), status: z14.enum(["pending", "in_progress", "completed"]).optional() }).strict(),
|
|
7185
|
+
z14.object({ action: z14.literal("update"), id: z14.string().min(1), title: z14.string().min(1).max(500).optional(), status: z14.enum(["pending", "in_progress", "completed"]).optional() }).strict(),
|
|
7186
|
+
z14.object({ action: z14.literal("remove"), id: z14.string().min(1) }).strict(),
|
|
7187
|
+
z14.object({ action: z14.literal("replace"), tasks: z14.array(taskSchema2).max(100) }).strict()
|
|
6723
7188
|
]);
|
|
6724
7189
|
var taskTool = {
|
|
6725
7190
|
definition: {
|
|
@@ -6750,7 +7215,7 @@ var taskTool = {
|
|
|
6750
7215
|
}
|
|
6751
7216
|
},
|
|
6752
7217
|
async execute(arguments_, context) {
|
|
6753
|
-
const input2 =
|
|
7218
|
+
const input2 = inputSchema9.parse(arguments_);
|
|
6754
7219
|
const tasks = context.session.tasks;
|
|
6755
7220
|
switch (input2.action) {
|
|
6756
7221
|
case "list":
|
|
@@ -6788,7 +7253,7 @@ var taskTool = {
|
|
|
6788
7253
|
|
|
6789
7254
|
// src/tools/task-contract.ts
|
|
6790
7255
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
6791
|
-
import { z as
|
|
7256
|
+
import { z as z15 } from "zod";
|
|
6792
7257
|
|
|
6793
7258
|
// src/agent/task-contract.ts
|
|
6794
7259
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
@@ -6869,14 +7334,14 @@ function compact(value, limit) {
|
|
|
6869
7334
|
}
|
|
6870
7335
|
|
|
6871
7336
|
// src/agent/completion-gate.ts
|
|
6872
|
-
import { createHash as
|
|
7337
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
6873
7338
|
|
|
6874
7339
|
// src/tools/permissions.ts
|
|
6875
|
-
import { createHash as
|
|
7340
|
+
import { createHash as createHash9, createHmac, randomBytes } from "node:crypto";
|
|
6876
7341
|
var permissionScopeSecret = randomBytes(32);
|
|
6877
7342
|
function permissionKey(call, category) {
|
|
6878
7343
|
const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
|
|
6879
|
-
const digest =
|
|
7344
|
+
const digest = createHash9("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
|
|
6880
7345
|
return `${category}:${call.name}:${digest}`;
|
|
6881
7346
|
}
|
|
6882
7347
|
function permissionTarget(call) {
|
|
@@ -7070,7 +7535,7 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
|
|
|
7070
7535
|
kind,
|
|
7071
7536
|
ok: result.ok,
|
|
7072
7537
|
changeSequence,
|
|
7073
|
-
commandKey:
|
|
7538
|
+
commandKey: createHash10("sha256").update(normalized).digest("hex")
|
|
7074
7539
|
};
|
|
7075
7540
|
}
|
|
7076
7541
|
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = []) {
|
|
@@ -7230,7 +7695,7 @@ function verificationRequirementMet(requirement, checks) {
|
|
|
7230
7695
|
const normalized = normalizeCommand2(requirement);
|
|
7231
7696
|
const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
|
|
7232
7697
|
if (broad) return checks.length > 0;
|
|
7233
|
-
const commandKey =
|
|
7698
|
+
const commandKey = createHash10("sha256").update(normalized).digest("hex");
|
|
7234
7699
|
return checks.some((item) => item.commandKey === commandKey);
|
|
7235
7700
|
}
|
|
7236
7701
|
function acceptanceUnresolved(acceptance) {
|
|
@@ -7282,29 +7747,29 @@ function fileCount(count) {
|
|
|
7282
7747
|
}
|
|
7283
7748
|
|
|
7284
7749
|
// src/tools/task-contract.ts
|
|
7285
|
-
var criterionSchema =
|
|
7286
|
-
id:
|
|
7287
|
-
description:
|
|
7288
|
-
required:
|
|
7750
|
+
var criterionSchema = z15.object({
|
|
7751
|
+
id: z15.string().min(1).max(128).optional(),
|
|
7752
|
+
description: z15.string().min(1).max(2e3),
|
|
7753
|
+
required: z15.boolean().optional()
|
|
7289
7754
|
}).strict();
|
|
7290
|
-
var
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
action:
|
|
7294
|
-
objective:
|
|
7295
|
-
scope:
|
|
7296
|
-
constraints:
|
|
7297
|
-
non_goals:
|
|
7298
|
-
acceptance_criteria:
|
|
7299
|
-
verification_requirements:
|
|
7755
|
+
var inputSchema10 = z15.discriminatedUnion("action", [
|
|
7756
|
+
z15.object({ action: z15.literal("show") }).strict(),
|
|
7757
|
+
z15.object({
|
|
7758
|
+
action: z15.literal("replace"),
|
|
7759
|
+
objective: z15.string().min(1).max(2e4),
|
|
7760
|
+
scope: z15.array(z15.string().min(1).max(2e3)).max(64),
|
|
7761
|
+
constraints: z15.array(z15.string().min(1).max(2e3)).max(64),
|
|
7762
|
+
non_goals: z15.array(z15.string().min(1).max(2e3)).max(64),
|
|
7763
|
+
acceptance_criteria: z15.array(criterionSchema).min(1).max(64),
|
|
7764
|
+
verification_requirements: z15.array(z15.string().min(1).max(2e3)).min(1).max(64)
|
|
7300
7765
|
}).strict(),
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
action:
|
|
7304
|
-
id:
|
|
7305
|
-
status:
|
|
7306
|
-
evidence_refs:
|
|
7307
|
-
note:
|
|
7766
|
+
z15.object({ action: z15.literal("activate") }).strict(),
|
|
7767
|
+
z15.object({
|
|
7768
|
+
action: z15.literal("update_criterion"),
|
|
7769
|
+
id: z15.string().min(1).max(128),
|
|
7770
|
+
status: z15.enum(["pending", "satisfied", "blocked"]),
|
|
7771
|
+
evidence_refs: z15.array(z15.string().min(1).max(256)).max(64).optional(),
|
|
7772
|
+
note: z15.string().max(2e3).optional()
|
|
7308
7773
|
}).strict()
|
|
7309
7774
|
]);
|
|
7310
7775
|
var taskContractTool = {
|
|
@@ -7346,7 +7811,7 @@ var taskContractTool = {
|
|
|
7346
7811
|
}
|
|
7347
7812
|
},
|
|
7348
7813
|
async execute(arguments_, context) {
|
|
7349
|
-
const input2 =
|
|
7814
|
+
const input2 = inputSchema10.parse(arguments_);
|
|
7350
7815
|
let contract = context.session.taskContract;
|
|
7351
7816
|
if (!contract) throw new Error("No Task Contract is active for this session.");
|
|
7352
7817
|
if (input2.action === "replace") {
|
|
@@ -7443,17 +7908,17 @@ function validateVerificationRequirement(requirement) {
|
|
|
7443
7908
|
}
|
|
7444
7909
|
|
|
7445
7910
|
// src/tools/working-memory.ts
|
|
7446
|
-
import { z as
|
|
7447
|
-
var
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7911
|
+
import { z as z16 } from "zod";
|
|
7912
|
+
var inputSchema11 = z16.discriminatedUnion("action", [
|
|
7913
|
+
z16.object({ action: z16.literal("show") }).strict(),
|
|
7914
|
+
z16.object({ action: z16.literal("set_goal"), value: z16.string().min(1).max(1e3) }).strict(),
|
|
7915
|
+
z16.object({ action: z16.literal("set_focus"), value: z16.string().min(1).max(1e3) }).strict(),
|
|
7916
|
+
z16.object({ action: z16.literal("add_constraint"), value: z16.string().min(1).max(1e3) }).strict(),
|
|
7917
|
+
z16.object({ action: z16.literal("add_decision"), value: z16.string().min(1).max(1e3) }).strict(),
|
|
7918
|
+
z16.object({ action: z16.literal("add_question"), value: z16.string().min(1).max(1e3) }).strict(),
|
|
7919
|
+
z16.object({ action: z16.literal("resolve_question"), value: z16.string().min(1).max(1e3) }).strict(),
|
|
7920
|
+
z16.object({ action: z16.literal("add_file"), path: z16.string().min(1).max(4e3) }).strict(),
|
|
7921
|
+
z16.object({ action: z16.literal("clear"), field: z16.enum(["constraints", "decisions", "openQuestions", "relevantFiles"]) }).strict()
|
|
7457
7922
|
]);
|
|
7458
7923
|
var workingMemoryTool = {
|
|
7459
7924
|
definition: {
|
|
@@ -7478,7 +7943,7 @@ var workingMemoryTool = {
|
|
|
7478
7943
|
}, ["action"])
|
|
7479
7944
|
},
|
|
7480
7945
|
async execute(arguments_, context) {
|
|
7481
|
-
const input2 =
|
|
7946
|
+
const input2 = inputSchema11.parse(arguments_);
|
|
7482
7947
|
const memory = context.session.workingMemory ?? emptyWorkingMemory2();
|
|
7483
7948
|
switch (input2.action) {
|
|
7484
7949
|
case "show":
|
|
@@ -7560,6 +8025,7 @@ function render(memory) {
|
|
|
7560
8025
|
function createDefaultToolRegistry(_options = {}) {
|
|
7561
8026
|
return new ToolRegistry([
|
|
7562
8027
|
readFileTool,
|
|
8028
|
+
readToolArtifactTool,
|
|
7563
8029
|
listFilesTool,
|
|
7564
8030
|
searchCodeTool,
|
|
7565
8031
|
writeFileTool,
|
|
@@ -7688,7 +8154,7 @@ function escapeAttribute3(value) {
|
|
|
7688
8154
|
}
|
|
7689
8155
|
|
|
7690
8156
|
// src/agent/tool-recovery.ts
|
|
7691
|
-
import { createHash as
|
|
8157
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
7692
8158
|
var RETRY_BUDGET = {
|
|
7693
8159
|
schema_input: 3,
|
|
7694
8160
|
unknown_tool: 2,
|
|
@@ -7783,10 +8249,10 @@ function isRetryable(failureClass) {
|
|
|
7783
8249
|
return RETRY_BUDGET[failureClass] > 0;
|
|
7784
8250
|
}
|
|
7785
8251
|
function callSignature(call) {
|
|
7786
|
-
return
|
|
8252
|
+
return createHash11("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
|
|
7787
8253
|
}
|
|
7788
8254
|
function failureSignature(call, failureClass) {
|
|
7789
|
-
return
|
|
8255
|
+
return createHash11("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
|
|
7790
8256
|
}
|
|
7791
8257
|
function redact(value, key = "") {
|
|
7792
8258
|
if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
|
|
@@ -7803,10 +8269,212 @@ function isFailureClass(value) {
|
|
|
7803
8269
|
return typeof value === "string" && Object.hasOwn(RETRY_BUDGET, value);
|
|
7804
8270
|
}
|
|
7805
8271
|
|
|
8272
|
+
// src/agent/tool-output.ts
|
|
8273
|
+
import stripAnsi from "strip-ansi";
|
|
8274
|
+
var MIN_TOOL_OUTPUT_TOKENS = 1024;
|
|
8275
|
+
var MAX_TOOL_OUTPUT_TOKENS = 8192;
|
|
8276
|
+
async function protectToolOutput(options) {
|
|
8277
|
+
const safe = sanitizeToolOutput(options.content);
|
|
8278
|
+
const sanitized = redactToolOutput(safe.text);
|
|
8279
|
+
const estimatedTokens = estimateToolOutputTokens(sanitized.text);
|
|
8280
|
+
const budgetTokens = clamp2(options.budgetTokens, MIN_TOOL_OUTPUT_TOKENS, MAX_TOOL_OUTPUT_TOKENS);
|
|
8281
|
+
const base = {
|
|
8282
|
+
originalChars: options.content.length,
|
|
8283
|
+
originalBytes: Buffer.byteLength(options.content),
|
|
8284
|
+
estimatedTokens,
|
|
8285
|
+
budgetTokens,
|
|
8286
|
+
truncated: estimatedTokens > budgetTokens,
|
|
8287
|
+
redacted: sanitized.redacted,
|
|
8288
|
+
sanitized: safe.sanitized
|
|
8289
|
+
};
|
|
8290
|
+
if (estimatedTokens <= budgetTokens) return { content: sanitized.text, metadata: base };
|
|
8291
|
+
let archived;
|
|
8292
|
+
try {
|
|
8293
|
+
archived = await options.artifacts.archive(
|
|
8294
|
+
options.sessionId,
|
|
8295
|
+
options.toolCallId,
|
|
8296
|
+
sanitized.text,
|
|
8297
|
+
{ redacted: sanitized.redacted }
|
|
8298
|
+
);
|
|
8299
|
+
} catch {
|
|
8300
|
+
archived = { stored: false, reason: "storage_error" };
|
|
8301
|
+
}
|
|
8302
|
+
const artifact = archived.stored ? archived.artifact : void 0;
|
|
8303
|
+
const artifactUnavailable = archived.stored ? void 0 : archived.reason;
|
|
8304
|
+
const metadata = {
|
|
8305
|
+
...base,
|
|
8306
|
+
...artifact ? {
|
|
8307
|
+
artifact: {
|
|
8308
|
+
toolCallId: artifact.toolCallId,
|
|
8309
|
+
sha256: artifact.sha256,
|
|
8310
|
+
bytes: artifact.bytes,
|
|
8311
|
+
createdAt: artifact.createdAt,
|
|
8312
|
+
expiresAt: artifact.expiresAt
|
|
8313
|
+
}
|
|
8314
|
+
} : artifactUnavailable ? { artifactUnavailable } : {}
|
|
8315
|
+
};
|
|
8316
|
+
const receipt = formatReceipt(options, metadata, "");
|
|
8317
|
+
const previewBudget = Math.max(0, budgetTokens - estimateToolOutputTokens(receipt));
|
|
8318
|
+
const preview = boundedPreview(sanitized.text, previewBudget);
|
|
8319
|
+
return {
|
|
8320
|
+
content: formatReceipt(options, metadata, preview),
|
|
8321
|
+
metadata
|
|
8322
|
+
};
|
|
8323
|
+
}
|
|
8324
|
+
function dynamicToolOutputBudget(contextWindowTokens, activeContextTokens, remainingSessionTokens) {
|
|
8325
|
+
const contextHeadroom = Math.max(0, contextWindowTokens - activeContextTokens);
|
|
8326
|
+
const sessionHeadroom = Math.max(0, remainingSessionTokens);
|
|
8327
|
+
const budget = Math.min(
|
|
8328
|
+
Math.floor(contextHeadroom * 0.35),
|
|
8329
|
+
Math.floor(sessionHeadroom * 0.12)
|
|
8330
|
+
);
|
|
8331
|
+
return clamp2(budget, MIN_TOOL_OUTPUT_TOKENS, MAX_TOOL_OUTPUT_TOKENS);
|
|
8332
|
+
}
|
|
8333
|
+
function estimateToolOutputTokens(value) {
|
|
8334
|
+
let tokens2 = 0;
|
|
8335
|
+
for (const character of value) {
|
|
8336
|
+
tokens2 += tokenCost(character);
|
|
8337
|
+
}
|
|
8338
|
+
return Math.max(1, Math.ceil(tokens2));
|
|
8339
|
+
}
|
|
8340
|
+
function formatReceipt(options, metadata, preview) {
|
|
8341
|
+
const lines = [
|
|
8342
|
+
"[Oversized tool output bounded for the model transcript]",
|
|
8343
|
+
`tool: ${boundedInlineByTokens(options.tool, 64)}`,
|
|
8344
|
+
`tool-call-id: ${boundedInlineByTokens(options.toolCallId, 96)}`,
|
|
8345
|
+
`status: ${options.ok ? "success" : "failure"}`,
|
|
8346
|
+
`original-output: ${metadata.originalChars} chars, ${metadata.originalBytes} bytes, ~${metadata.estimatedTokens} tokens`,
|
|
8347
|
+
`model-budget: ${metadata.budgetTokens} tokens (head + tail preview)`,
|
|
8348
|
+
...metadata.redacted ? ["sensitive-values: redacted before display and retention"] : [],
|
|
8349
|
+
...preservedSignals(options.metadata),
|
|
8350
|
+
...metadata.artifact ? [
|
|
8351
|
+
`artifact: session-scoped; sha256 ${metadata.artifact.sha256}; ${metadata.artifact.bytes} bytes`,
|
|
8352
|
+
`read-back: read_tool_artifact({"sha256":"${metadata.artifact.sha256}","start_line":1,"max_lines":200}) before ${metadata.artifact.expiresAt}`
|
|
8353
|
+
] : [`artifact: unavailable (${metadata.artifactUnavailable ?? "unknown"}); rerun the tool with a narrower result if more detail is needed`],
|
|
8354
|
+
"preview:",
|
|
8355
|
+
preview
|
|
8356
|
+
];
|
|
8357
|
+
return lines.join("\n");
|
|
8358
|
+
}
|
|
8359
|
+
function preservedSignals(metadata) {
|
|
8360
|
+
const lines = [];
|
|
8361
|
+
if (typeof metadata.exitCode === "number") lines.push(`exit-code: ${metadata.exitCode}`);
|
|
8362
|
+
if (metadata.timedOut === true) lines.push("timed-out: true");
|
|
8363
|
+
if (metadata.sourceTruncated === true) {
|
|
8364
|
+
lines.push("source-truncated: true; the producing tool reached its capture limit before this firewall");
|
|
8365
|
+
}
|
|
8366
|
+
const changedFiles = metadata.changedFiles;
|
|
8367
|
+
if (Array.isArray(changedFiles)) {
|
|
8368
|
+
const allPaths = changedFiles.filter((path) => typeof path === "string");
|
|
8369
|
+
const paths = allPaths.slice(0, 3).map((path) => boundedInlineByTokens(path, 24));
|
|
8370
|
+
if (paths.length) {
|
|
8371
|
+
lines.push(`changed-files: ${paths.join(", ")}${allPaths.length > paths.length ? ` (+${allPaths.length - paths.length} more in metadata)` : ""}`);
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
const failure = metadata.failure;
|
|
8375
|
+
if (isFailureReceipt(failure)) {
|
|
8376
|
+
lines.push(`failure-class: ${failure.class}; attempt ${failure.attempt}; ${failure.remaining} retries remain`);
|
|
8377
|
+
}
|
|
8378
|
+
return lines;
|
|
8379
|
+
}
|
|
8380
|
+
function isFailureReceipt(value) {
|
|
8381
|
+
return typeof value === "object" && value !== null && typeof value.class === "string" && typeof value.attempt === "number" && typeof value.remaining === "number";
|
|
8382
|
+
}
|
|
8383
|
+
function redactToolOutput(value) {
|
|
8384
|
+
let redacted = false;
|
|
8385
|
+
const replace = (expression, replacement) => {
|
|
8386
|
+
expression.lastIndex = 0;
|
|
8387
|
+
if (!expression.test(value)) return;
|
|
8388
|
+
redacted = true;
|
|
8389
|
+
expression.lastIndex = 0;
|
|
8390
|
+
value = value.replace(expression, replacement);
|
|
8391
|
+
};
|
|
8392
|
+
replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/gu, "[redacted-secret]");
|
|
8393
|
+
replace(/\b(authorization\s*:\s*(?:bearer\s+|basic\s+)?)\S+/giu, "$1[redacted]");
|
|
8394
|
+
replace(/\b((?:api[_-]?key|access[_-]?token|authorization|cookie|password|client[_-]?secret)\s*[:=]\s*)(?:"[A-Za-z0-9+/_=-]{8,}"|'[A-Za-z0-9+/_=-]{8,}'|[A-Za-z0-9+/_=-]{8,})/giu, "$1[redacted]");
|
|
8395
|
+
replace(/(--(?:api[_-]?key|access[_-]?token|password|client[_-]?secret)(?:=|\s+))[A-Za-z0-9+/_=-]{8,}/giu, "$1[redacted]");
|
|
8396
|
+
return { text: value, redacted };
|
|
8397
|
+
}
|
|
8398
|
+
function sanitizeToolOutput(value) {
|
|
8399
|
+
const text = stripAnsi(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "");
|
|
8400
|
+
return { text, sanitized: text !== value };
|
|
8401
|
+
}
|
|
8402
|
+
function sliceStartByTokens(value, budget) {
|
|
8403
|
+
let used = 0;
|
|
8404
|
+
let end = 0;
|
|
8405
|
+
for (const character of value) {
|
|
8406
|
+
const cost = tokenCost(character);
|
|
8407
|
+
if (used + cost > budget) break;
|
|
8408
|
+
used += cost;
|
|
8409
|
+
end += character.length;
|
|
8410
|
+
}
|
|
8411
|
+
return value.slice(0, end);
|
|
8412
|
+
}
|
|
8413
|
+
function sliceEndByTokens(value, budget) {
|
|
8414
|
+
let used = 0;
|
|
8415
|
+
let start = value.length;
|
|
8416
|
+
while (start > 0) {
|
|
8417
|
+
let next = start - 1;
|
|
8418
|
+
const code = value.charCodeAt(next);
|
|
8419
|
+
if (code >= 56320 && code <= 57343 && next > 0) {
|
|
8420
|
+
const previous = value.charCodeAt(next - 1);
|
|
8421
|
+
if (previous >= 55296 && previous <= 56319) next -= 1;
|
|
8422
|
+
}
|
|
8423
|
+
const character = value.slice(next, start);
|
|
8424
|
+
const cost = tokenCost(character);
|
|
8425
|
+
if (used + cost > budget) break;
|
|
8426
|
+
used += cost;
|
|
8427
|
+
start = next;
|
|
8428
|
+
}
|
|
8429
|
+
return value.slice(start);
|
|
8430
|
+
}
|
|
8431
|
+
function boundedPreview(value, budget) {
|
|
8432
|
+
if (budget <= 0) return "";
|
|
8433
|
+
if (estimateToolOutputTokens(value) <= budget) return value;
|
|
8434
|
+
let low = 0;
|
|
8435
|
+
let high = budget;
|
|
8436
|
+
let best = "";
|
|
8437
|
+
while (low <= high) {
|
|
8438
|
+
const contentBudget = Math.floor((low + high) / 2);
|
|
8439
|
+
const headBudget = Math.ceil(contentBudget * 0.6);
|
|
8440
|
+
const tailBudget = Math.max(0, contentBudget - headBudget);
|
|
8441
|
+
const head = sliceStartByTokens(value, headBudget);
|
|
8442
|
+
const tail = sliceEndByTokens(value, tailBudget);
|
|
8443
|
+
const preview = `${head}
|
|
8444
|
+
\u2026 ${Math.max(0, value.length - head.length - tail.length)} chars omitted \u2026
|
|
8445
|
+
${tail}`;
|
|
8446
|
+
if (estimateToolOutputTokens(preview) <= budget) {
|
|
8447
|
+
best = preview;
|
|
8448
|
+
low = contentBudget + 1;
|
|
8449
|
+
} else {
|
|
8450
|
+
high = contentBudget - 1;
|
|
8451
|
+
}
|
|
8452
|
+
}
|
|
8453
|
+
return best || sliceStartByTokens(value, budget);
|
|
8454
|
+
}
|
|
8455
|
+
function tokenCost(character) {
|
|
8456
|
+
const code = character.codePointAt(0) ?? 0;
|
|
8457
|
+
if (isCjkCharacter(character) || code > 127) return 2;
|
|
8458
|
+
return /[A-Z0-9\p{P}\p{S}]/u.test(character) ? 0.5 : 0.25;
|
|
8459
|
+
}
|
|
8460
|
+
function isCjkCharacter(character) {
|
|
8461
|
+
const code = character.codePointAt(0) ?? 0;
|
|
8462
|
+
return code >= 11904 && code <= 40959 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 131072 && code <= 195103;
|
|
8463
|
+
}
|
|
8464
|
+
function clamp2(value, minimum, maximum) {
|
|
8465
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
8466
|
+
}
|
|
8467
|
+
function boundedInlineByTokens(value, maxTokens) {
|
|
8468
|
+
const normalized = value.replace(/[\r\n\t]+/gu, " ").trim();
|
|
8469
|
+
if (estimateToolOutputTokens(normalized) <= maxTokens) return normalized;
|
|
8470
|
+
const suffix = "\u2026";
|
|
8471
|
+
return `${sliceStartByTokens(normalized, Math.max(0, maxTokens - tokenCost(suffix)))}${suffix}`;
|
|
8472
|
+
}
|
|
8473
|
+
|
|
7806
8474
|
// src/agent/rules.ts
|
|
7807
8475
|
import { existsSync as existsSync2 } from "node:fs";
|
|
7808
|
-
import { join as
|
|
7809
|
-
import { lstat as
|
|
8476
|
+
import { join as join14 } from "node:path";
|
|
8477
|
+
import { lstat as lstat16, readFile as readFile12 } from "node:fs/promises";
|
|
7810
8478
|
var workspaceRulePaths = [
|
|
7811
8479
|
"AGENTS.md",
|
|
7812
8480
|
"CLAUDE.md",
|
|
@@ -7815,14 +8483,14 @@ var workspaceRulePaths = [
|
|
|
7815
8483
|
];
|
|
7816
8484
|
async function discoverWorkspaceRules(workspace, maxChars = 12e4) {
|
|
7817
8485
|
const workspaceAccess = new WorkspaceAccess([workspace]);
|
|
7818
|
-
const activeProjectRules =
|
|
8486
|
+
const activeProjectRules = join14(resolveProjectNamespaceSync(workspace).active, "rules.md");
|
|
7819
8487
|
const projectCandidates = [
|
|
7820
|
-
...workspaceRulePaths.slice(0, 3).map((path) =>
|
|
8488
|
+
...workspaceRulePaths.slice(0, 3).map((path) => join14(workspace, path)),
|
|
7821
8489
|
activeProjectRules,
|
|
7822
|
-
...workspaceRulePaths.slice(3).map((path) =>
|
|
8490
|
+
...workspaceRulePaths.slice(3).map((path) => join14(workspace, path))
|
|
7823
8491
|
];
|
|
7824
8492
|
const candidates = [
|
|
7825
|
-
{ path:
|
|
8493
|
+
{ path: join14(resolveHomeNamespace(), "rules.md"), scope: "user" },
|
|
7826
8494
|
...projectCandidates.map((path) => ({
|
|
7827
8495
|
path,
|
|
7828
8496
|
scope: "workspace"
|
|
@@ -7833,13 +8501,13 @@ async function discoverWorkspaceRules(workspace, maxChars = 12e4) {
|
|
|
7833
8501
|
for (const candidate of candidates) {
|
|
7834
8502
|
if (remaining <= 0 || !existsSync2(candidate.path)) continue;
|
|
7835
8503
|
try {
|
|
7836
|
-
const info = await
|
|
8504
|
+
const info = await lstat16(candidate.path);
|
|
7837
8505
|
if (!info.isFile() || info.size > 1e6) continue;
|
|
7838
8506
|
if (candidate.scope === "workspace") {
|
|
7839
8507
|
const safePath = await workspaceAccess.resolvePath(candidate.path, { expect: "file" });
|
|
7840
8508
|
if (safePath !== candidate.path) continue;
|
|
7841
8509
|
}
|
|
7842
|
-
const raw = await
|
|
8510
|
+
const raw = await readFile12(candidate.path, "utf8");
|
|
7843
8511
|
if (raw.includes("\0")) continue;
|
|
7844
8512
|
const content = raw.slice(0, remaining);
|
|
7845
8513
|
rules.push({
|
|
@@ -7871,7 +8539,7 @@ function escapeAttribute4(value) {
|
|
|
7871
8539
|
}
|
|
7872
8540
|
|
|
7873
8541
|
// src/context/context-sources.ts
|
|
7874
|
-
import { readFile as
|
|
8542
|
+
import { readFile as readFile13, stat as stat9 } from "node:fs/promises";
|
|
7875
8543
|
var MAX_SOURCE_CHARS = 6e4;
|
|
7876
8544
|
var MAX_PINNED_CHARS = 16e4;
|
|
7877
8545
|
var MAX_CONTEXT_SOURCES = 32;
|
|
@@ -7885,7 +8553,7 @@ async function pinContextSource(session, workspace, requested) {
|
|
|
7885
8553
|
const resolved = await workspace.resolvePath(requested, { expect: "file" });
|
|
7886
8554
|
const info = await stat9(resolved);
|
|
7887
8555
|
const alias = workspace.display(resolved);
|
|
7888
|
-
const tokens2 = estimateTokens3((await
|
|
8556
|
+
const tokens2 = estimateTokens3((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
|
|
7889
8557
|
const list2 = sources(session);
|
|
7890
8558
|
const existing = list2.find((source2) => source2.path === alias);
|
|
7891
8559
|
if (existing) {
|
|
@@ -7935,7 +8603,7 @@ async function resolvePinnedContent(session, workspace) {
|
|
|
7935
8603
|
if (remaining <= 0) break;
|
|
7936
8604
|
try {
|
|
7937
8605
|
const safe = await workspace.resolvePath(source.path, { expect: "file" });
|
|
7938
|
-
const raw = await
|
|
8606
|
+
const raw = await readFile13(safe, "utf8");
|
|
7939
8607
|
const capped = raw.slice(0, Math.min(MAX_SOURCE_CHARS, remaining));
|
|
7940
8608
|
source.tokens = estimateTokens3(capped);
|
|
7941
8609
|
resolved.push({
|
|
@@ -7983,6 +8651,7 @@ var AgentRunner = class {
|
|
|
7983
8651
|
contextEngine;
|
|
7984
8652
|
tools;
|
|
7985
8653
|
sessionStore;
|
|
8654
|
+
toolArtifactStore;
|
|
7986
8655
|
checkpointStore;
|
|
7987
8656
|
workspace;
|
|
7988
8657
|
hooks;
|
|
@@ -8004,6 +8673,7 @@ var AgentRunner = class {
|
|
|
8004
8673
|
contextEngine: this.contextEngine
|
|
8005
8674
|
});
|
|
8006
8675
|
this.sessionStore = options.sessionStore ?? new SessionStore(this.workspace.primaryRoot);
|
|
8676
|
+
this.toolArtifactStore = options.toolArtifactStore ?? new ToolArtifactStore(this.workspace.primaryRoot);
|
|
8007
8677
|
this.checkpointStore = options.checkpointStore ?? new CheckpointStore(this.workspace);
|
|
8008
8678
|
this.hooks = new HookRunner(options.config.hooks, this.workspace);
|
|
8009
8679
|
this.contextManager = options.contextManager ?? new ContextManager(options.config);
|
|
@@ -8096,6 +8766,7 @@ var AgentRunner = class {
|
|
|
8096
8766
|
};
|
|
8097
8767
|
try {
|
|
8098
8768
|
throwIfAborted(options.signal);
|
|
8769
|
+
await this.reconcileToolArtifacts();
|
|
8099
8770
|
if (this.session.messages.length === 0 && this.session.title === "New session") {
|
|
8100
8771
|
this.session.title = titleFromInput(request);
|
|
8101
8772
|
}
|
|
@@ -8202,9 +8873,13 @@ var AgentRunner = class {
|
|
|
8202
8873
|
contextBudget
|
|
8203
8874
|
);
|
|
8204
8875
|
const availableTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
|
|
8205
|
-
const
|
|
8206
|
-
|
|
8876
|
+
const visibleTools = visibleToolDefinitions(
|
|
8877
|
+
this.tools,
|
|
8878
|
+
options.askMode === true,
|
|
8879
|
+
contractEnabled,
|
|
8880
|
+
this.hasReadableToolArtifact()
|
|
8207
8881
|
);
|
|
8882
|
+
const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
|
|
8208
8883
|
if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
|
|
8209
8884
|
return finishRun("token_budget");
|
|
8210
8885
|
}
|
|
@@ -8212,7 +8887,6 @@ var AgentRunner = class {
|
|
|
8212
8887
|
this.config.model.maxTokens ?? 8192,
|
|
8213
8888
|
availableTokens - estimatedInputTokens
|
|
8214
8889
|
));
|
|
8215
|
-
const visibleTools = visibleToolDefinitions(this.tools, options.askMode === true, contractEnabled);
|
|
8216
8890
|
const assistantId = randomUUID12();
|
|
8217
8891
|
const response = await this.completeModel(
|
|
8218
8892
|
messages,
|
|
@@ -8370,17 +9044,17 @@ ${input2}`
|
|
|
8370
9044
|
async executeTool(call, options, emit, recovery = new ToolRecoveryController(), visibleToolNames) {
|
|
8371
9045
|
const preflight = recovery.preflight(call);
|
|
8372
9046
|
if (preflight) {
|
|
8373
|
-
const result =
|
|
9047
|
+
const result = await this.protectToolResult(
|
|
9048
|
+
failedResult(call, "Tool call rejected by the recovery circuit.", preflight)
|
|
9049
|
+
);
|
|
8374
9050
|
this.recordToolResult(result);
|
|
8375
9051
|
await emit({ type: "tool_result", result });
|
|
8376
9052
|
return result;
|
|
8377
9053
|
}
|
|
8378
9054
|
if (visibleToolNames && !visibleToolNames.has(call.name)) {
|
|
8379
9055
|
const receipt = recovery.recordFailure(call, "unknown_tool");
|
|
8380
|
-
const result =
|
|
8381
|
-
call,
|
|
8382
|
-
`Tool is not exposed for this turn: ${call.name}`,
|
|
8383
|
-
receipt
|
|
9056
|
+
const result = await this.protectToolResult(
|
|
9057
|
+
failedResult(call, `Tool is not exposed for this turn: ${call.name}`, receipt)
|
|
8384
9058
|
);
|
|
8385
9059
|
this.recordToolResult(result);
|
|
8386
9060
|
await emit({ type: "tool_result", result });
|
|
@@ -8389,7 +9063,7 @@ ${input2}`
|
|
|
8389
9063
|
const tool = this.tools.get(call.name);
|
|
8390
9064
|
if (!tool) {
|
|
8391
9065
|
const receipt = recovery.recordFailure(call, "unknown_tool");
|
|
8392
|
-
const result = failedResult(call, `Unknown tool: ${call.name}`, receipt);
|
|
9066
|
+
const result = await this.protectToolResult(failedResult(call, `Unknown tool: ${call.name}`, receipt));
|
|
8393
9067
|
this.recordToolResult(result);
|
|
8394
9068
|
await emit({ type: "tool_result", result });
|
|
8395
9069
|
return result;
|
|
@@ -8402,17 +9076,15 @@ ${input2}`
|
|
|
8402
9076
|
} catch (error) {
|
|
8403
9077
|
const failureClass = classifyThrownToolFailure(error, options.signal);
|
|
8404
9078
|
const receipt = recovery.recordFailure(call, failureClass);
|
|
8405
|
-
const result = failedResult(call, formatToolError(error), receipt);
|
|
9079
|
+
const result = await this.protectToolResult(failedResult(call, formatToolError(error), receipt));
|
|
8406
9080
|
this.recordToolResult(result, tool.definition.category);
|
|
8407
9081
|
await emit({ type: "tool_result", result });
|
|
8408
9082
|
return result;
|
|
8409
9083
|
}
|
|
8410
9084
|
if (categories.some((category) => category !== "read") && this.session.taskContract?.state === "draft") {
|
|
8411
9085
|
const receipt = recovery.recordFailure(call, "contract_required");
|
|
8412
|
-
const result =
|
|
8413
|
-
call,
|
|
8414
|
-
"Potentially mutating work is paused until the draft Task Contract is activated.",
|
|
8415
|
-
receipt
|
|
9086
|
+
const result = await this.protectToolResult(
|
|
9087
|
+
failedResult(call, "Potentially mutating work is paused until the draft Task Contract is activated.", receipt)
|
|
8416
9088
|
);
|
|
8417
9089
|
this.recordToolResult(result, tool.definition.category);
|
|
8418
9090
|
await emit({ type: "tool_result", result });
|
|
@@ -8422,7 +9094,9 @@ ${input2}`
|
|
|
8422
9094
|
const allowed = await this.authorize(call, category, options, emit);
|
|
8423
9095
|
if (!allowed) {
|
|
8424
9096
|
const receipt = recovery.recordFailure(call, "permission_denied");
|
|
8425
|
-
const result =
|
|
9097
|
+
const result = await this.protectToolResult(
|
|
9098
|
+
failedResult(call, `Permission denied for ${category} operation.`, receipt)
|
|
9099
|
+
);
|
|
8426
9100
|
this.recordToolResult(result, category);
|
|
8427
9101
|
await emit({ type: "tool_result", result });
|
|
8428
9102
|
return result;
|
|
@@ -8436,6 +9110,7 @@ ${input2}`
|
|
|
8436
9110
|
workspace: this.workspace,
|
|
8437
9111
|
session: this.session,
|
|
8438
9112
|
contextEngine: this.contextEngine,
|
|
9113
|
+
toolArtifactStore: this.toolArtifactStore,
|
|
8439
9114
|
emit,
|
|
8440
9115
|
...options.signal ? { signal: options.signal } : {}
|
|
8441
9116
|
};
|
|
@@ -8469,30 +9144,33 @@ ${input2}`
|
|
|
8469
9144
|
} catch (error) {
|
|
8470
9145
|
afterHookError = toError(error);
|
|
8471
9146
|
}
|
|
8472
|
-
|
|
8473
|
-
toolCallId: call.id,
|
|
8474
|
-
name: call.name,
|
|
8475
|
-
ok: execution.ok !== false && !afterHookError,
|
|
8476
|
-
content: truncateToolOutput(afterHookError ? `${execution.content}
|
|
9147
|
+
let completeContent = afterHookError ? `${execution.content}
|
|
8477
9148
|
|
|
8478
|
-
Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : execution.content
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
}
|
|
9149
|
+
Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : execution.content;
|
|
9150
|
+
const metadata = {
|
|
9151
|
+
...execution.metadata ?? {},
|
|
9152
|
+
...changedFiles.length ? { changedFiles } : {},
|
|
9153
|
+
...checkpointId ? { checkpointId } : {},
|
|
9154
|
+
...beforeHooks.length || afterHooks.length ? { hooks: { before: beforeHooks.length, after: afterHooks.length } } : {},
|
|
9155
|
+
...afterHookError ? { toolSucceeded: true, hookError: afterHookError.message } : {}
|
|
8486
9156
|
};
|
|
8487
|
-
|
|
8488
|
-
|
|
9157
|
+
const ok = execution.ok !== false && !afterHookError;
|
|
9158
|
+
if (!ok) {
|
|
9159
|
+
const failureClass = options.signal?.aborted ? "cancelled" : classifyToolFailure({ toolCallId: call.id, name: call.name, ok, content: completeContent, metadata });
|
|
8489
9160
|
const receipt = recovery.recordFailure(call, failureClass);
|
|
8490
|
-
|
|
8491
|
-
${
|
|
8492
|
-
|
|
9161
|
+
completeContent = `${formatFailureReceipt(receipt)}
|
|
9162
|
+
${completeContent}`;
|
|
9163
|
+
metadata.failure = receipt;
|
|
8493
9164
|
} else {
|
|
8494
9165
|
recovery.recordSuccess(call);
|
|
8495
9166
|
}
|
|
9167
|
+
const result = await this.protectToolResult({
|
|
9168
|
+
toolCallId: call.id,
|
|
9169
|
+
name: call.name,
|
|
9170
|
+
ok,
|
|
9171
|
+
content: completeContent,
|
|
9172
|
+
metadata
|
|
9173
|
+
});
|
|
8496
9174
|
this.contextManager.recordTool(this.session, call, result);
|
|
8497
9175
|
if (JSON.stringify(this.session.tasks) !== tasksBefore || call.name === "task") {
|
|
8498
9176
|
await emit({ type: "tasks", tasks: this.session.tasks.map((task) => ({ ...task })) });
|
|
@@ -8507,7 +9185,7 @@ ${result.content}`);
|
|
|
8507
9185
|
const normalized = toError(error);
|
|
8508
9186
|
const failureClass = classifyThrownToolFailure(normalized, options.signal);
|
|
8509
9187
|
const receipt = recovery.recordFailure(call, failureClass);
|
|
8510
|
-
const result = failedResult(call, formatToolError(error), receipt);
|
|
9188
|
+
const result = await this.protectToolResult(failedResult(call, formatToolError(error), receipt));
|
|
8511
9189
|
this.recordToolResult(result, tool.definition.category);
|
|
8512
9190
|
await emit({ type: "tool_result", result });
|
|
8513
9191
|
return result;
|
|
@@ -8661,6 +9339,63 @@ ${result.content}`);
|
|
|
8661
9339
|
persist() {
|
|
8662
9340
|
return this.persistSession ? this.sessionStore.save(this.session) : Promise.resolve();
|
|
8663
9341
|
}
|
|
9342
|
+
toolOutputBudget() {
|
|
9343
|
+
const contextWindowTokens = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
|
|
9344
|
+
const activeContextTokens = this.contextManager.status(this.session, contextWindowTokens).activeTokens;
|
|
9345
|
+
const remainingSessionTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
|
|
9346
|
+
return dynamicToolOutputBudget(contextWindowTokens, activeContextTokens, remainingSessionTokens);
|
|
9347
|
+
}
|
|
9348
|
+
async protectToolResult(result) {
|
|
9349
|
+
const metadata = { ...result.metadata ?? {} };
|
|
9350
|
+
const output2 = await protectToolOutput({
|
|
9351
|
+
content: result.content,
|
|
9352
|
+
sessionId: this.session.id,
|
|
9353
|
+
toolCallId: result.toolCallId,
|
|
9354
|
+
tool: result.name,
|
|
9355
|
+
ok: result.ok,
|
|
9356
|
+
budgetTokens: this.toolOutputBudget(),
|
|
9357
|
+
metadata,
|
|
9358
|
+
artifacts: this.toolArtifactStore
|
|
9359
|
+
});
|
|
9360
|
+
if (output2.metadata.artifact) this.registerToolArtifact({
|
|
9361
|
+
...output2.metadata.artifact,
|
|
9362
|
+
redacted: output2.metadata.redacted
|
|
9363
|
+
});
|
|
9364
|
+
return {
|
|
9365
|
+
...result,
|
|
9366
|
+
content: output2.content,
|
|
9367
|
+
...output2.metadata.truncated || output2.metadata.redacted || output2.metadata.sanitized || output2.metadata.artifact || output2.metadata.artifactUnavailable ? { metadata: { ...metadata, toolOutput: output2.metadata } } : Object.keys(metadata).length ? { metadata } : {}
|
|
9368
|
+
};
|
|
9369
|
+
}
|
|
9370
|
+
hasReadableToolArtifact() {
|
|
9371
|
+
const now = Date.now();
|
|
9372
|
+
return (this.session.toolArtifacts ?? []).some(
|
|
9373
|
+
(artifact) => Date.parse(artifact.expiresAt) > now
|
|
9374
|
+
);
|
|
9375
|
+
}
|
|
9376
|
+
registerToolArtifact(artifact) {
|
|
9377
|
+
const now = Date.now();
|
|
9378
|
+
const artifacts = (this.session.toolArtifacts ?? []).filter((candidate) => Date.parse(candidate.expiresAt) > now && candidate.toolCallId !== artifact.toolCallId);
|
|
9379
|
+
artifacts.push(artifact);
|
|
9380
|
+
artifacts.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
9381
|
+
if (artifacts.length > 200) artifacts.splice(0, artifacts.length - 200);
|
|
9382
|
+
this.session.toolArtifacts = artifacts;
|
|
9383
|
+
}
|
|
9384
|
+
async reconcileToolArtifacts() {
|
|
9385
|
+
let stored;
|
|
9386
|
+
try {
|
|
9387
|
+
stored = await this.toolArtifactStore.prune(this.session.id);
|
|
9388
|
+
} catch {
|
|
9389
|
+
this.session.toolArtifacts = [];
|
|
9390
|
+
return;
|
|
9391
|
+
}
|
|
9392
|
+
const valid = new Map(stored.map((artifact) => [artifact.toolCallId, artifact]));
|
|
9393
|
+
const reconciled = (this.session.toolArtifacts ?? []).filter((receipt) => {
|
|
9394
|
+
const artifact = valid.get(receipt.toolCallId);
|
|
9395
|
+
return artifact !== void 0 && artifact.sha256 === receipt.sha256 && artifact.bytes === receipt.bytes && artifact.createdAt === receipt.createdAt && artifact.expiresAt === receipt.expiresAt && artifact.redacted === receipt.redacted;
|
|
9396
|
+
});
|
|
9397
|
+
this.session.toolArtifacts = reconciled;
|
|
9398
|
+
}
|
|
8664
9399
|
};
|
|
8665
9400
|
function message(role, content, extra = {}) {
|
|
8666
9401
|
return {
|
|
@@ -8740,21 +9475,16 @@ function failedResult(call, content, failure) {
|
|
|
8740
9475
|
toolCallId: call.id,
|
|
8741
9476
|
name: call.name,
|
|
8742
9477
|
ok: false,
|
|
8743
|
-
content:
|
|
8744
|
-
${content}` : content
|
|
9478
|
+
content: failure ? `${formatFailureReceipt(failure)}
|
|
9479
|
+
${content}` : content,
|
|
8745
9480
|
...failure ? { metadata: { failure } } : {}
|
|
8746
9481
|
};
|
|
8747
9482
|
}
|
|
8748
|
-
function visibleToolDefinitions(tools, askMode, contractEnabled) {
|
|
9483
|
+
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable) {
|
|
8749
9484
|
return tools.definitions().filter(
|
|
8750
|
-
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract")
|
|
9485
|
+
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact")
|
|
8751
9486
|
);
|
|
8752
9487
|
}
|
|
8753
|
-
function truncateToolOutput(content) {
|
|
8754
|
-
const max = 8e4;
|
|
8755
|
-
return content.length <= max ? content : `${content.slice(0, max)}
|
|
8756
|
-
\u2026 tool output truncated`;
|
|
8757
|
-
}
|
|
8758
9488
|
function formatToolError(error) {
|
|
8759
9489
|
const normalized = toError(error);
|
|
8760
9490
|
if (normalized.name === "ZodError") return `Invalid tool arguments: ${normalized.message}`;
|
|
@@ -8787,9 +9517,9 @@ async function safeEmit(emit, event) {
|
|
|
8787
9517
|
}
|
|
8788
9518
|
|
|
8789
9519
|
// src/agent/profiles.ts
|
|
8790
|
-
import { lstat as
|
|
9520
|
+
import { lstat as lstat17, readFile as readFile14, readdir as readdir6 } from "node:fs/promises";
|
|
8791
9521
|
import { homedir as homedir3 } from "node:os";
|
|
8792
|
-
import { basename as basename8, join as
|
|
9522
|
+
import { basename as basename8, join as join15 } from "node:path";
|
|
8793
9523
|
import { parse as parseYaml2 } from "yaml";
|
|
8794
9524
|
var builtInProfiles = [
|
|
8795
9525
|
{
|
|
@@ -8881,14 +9611,14 @@ var AgentProfileCatalog = class {
|
|
|
8881
9611
|
profiles = new Map(builtInProfiles.map((profile) => [profile.name, profile]));
|
|
8882
9612
|
async discover() {
|
|
8883
9613
|
const locations = [
|
|
8884
|
-
{ path:
|
|
8885
|
-
{ path:
|
|
8886
|
-
{ path:
|
|
8887
|
-
{ path:
|
|
9614
|
+
{ path: join15(resolveHomeNamespace(), "agents"), source: "user" },
|
|
9615
|
+
{ path: join15(homedir3(), ".claude", "agents"), source: "user" },
|
|
9616
|
+
{ path: join15(resolveProjectNamespaceSync(this.workspace).active, "agents"), source: "workspace" },
|
|
9617
|
+
{ path: join15(this.workspace, ".claude", "agents"), source: "workspace" }
|
|
8888
9618
|
];
|
|
8889
9619
|
for (const location of locations) {
|
|
8890
9620
|
for (const file of await markdownFiles(location.path)) {
|
|
8891
|
-
const profile = await readProfile(
|
|
9621
|
+
const profile = await readProfile(join15(location.path, file), location.source);
|
|
8892
9622
|
if (profile) this.profiles.set(profile.name, profile);
|
|
8893
9623
|
}
|
|
8894
9624
|
}
|
|
@@ -8904,18 +9634,18 @@ var AgentProfileCatalog = class {
|
|
|
8904
9634
|
};
|
|
8905
9635
|
async function markdownFiles(directory) {
|
|
8906
9636
|
try {
|
|
8907
|
-
const info = await
|
|
9637
|
+
const info = await lstat17(directory);
|
|
8908
9638
|
if (!info.isDirectory() || info.isSymbolicLink()) return [];
|
|
8909
|
-
return (await
|
|
9639
|
+
return (await readdir6(directory, { withFileTypes: true })).filter((entry) => entry.isFile() && !entry.isSymbolicLink() && entry.name.endsWith(".md")).map((entry) => entry.name).slice(0, 200);
|
|
8910
9640
|
} catch {
|
|
8911
9641
|
return [];
|
|
8912
9642
|
}
|
|
8913
9643
|
}
|
|
8914
9644
|
async function readProfile(path, source) {
|
|
8915
9645
|
try {
|
|
8916
|
-
const info = await
|
|
9646
|
+
const info = await lstat17(path);
|
|
8917
9647
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 1e5) return void 0;
|
|
8918
|
-
const raw = await
|
|
9648
|
+
const raw = await readFile14(path, "utf8");
|
|
8919
9649
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
8920
9650
|
const frontmatter = match ? parseYaml2(match[1] ?? "") : {};
|
|
8921
9651
|
const prompt = (match ? raw.slice(match[0].length) : raw).trim();
|
|
@@ -8943,8 +9673,8 @@ function integer(value, fallback, min, max) {
|
|
|
8943
9673
|
|
|
8944
9674
|
// src/agent/delegation.ts
|
|
8945
9675
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
8946
|
-
import { join as
|
|
8947
|
-
import { z as
|
|
9676
|
+
import { join as join18 } from "node:path";
|
|
9677
|
+
import { z as z18 } from "zod";
|
|
8948
9678
|
|
|
8949
9679
|
// src/agent/external-runtime.ts
|
|
8950
9680
|
async function runExternalAgent(request) {
|
|
@@ -9071,95 +9801,95 @@ function numeric(...values) {
|
|
|
9071
9801
|
}
|
|
9072
9802
|
|
|
9073
9803
|
// src/agent/team-store.ts
|
|
9074
|
-
import { createHash as
|
|
9075
|
-
import { lstat as
|
|
9076
|
-
import { join as
|
|
9077
|
-
import { z as
|
|
9078
|
-
var runIdSchema =
|
|
9079
|
-
var hashSchema =
|
|
9080
|
-
var
|
|
9804
|
+
import { createHash as createHash12, randomUUID as randomUUID13 } from "node:crypto";
|
|
9805
|
+
import { lstat as lstat18, readFile as readFile15, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
9806
|
+
import { join as join16, resolve as resolve16 } from "node:path";
|
|
9807
|
+
import { z as z17 } from "zod";
|
|
9808
|
+
var runIdSchema = z17.string().uuid();
|
|
9809
|
+
var hashSchema = z17.string().regex(/^[a-f0-9]{64}$/u);
|
|
9810
|
+
var artifactSchema2 = z17.object({
|
|
9081
9811
|
sha256: hashSchema,
|
|
9082
|
-
bytes:
|
|
9812
|
+
bytes: z17.number().int().nonnegative().max(5e5)
|
|
9083
9813
|
}).strict();
|
|
9084
|
-
var phaseSchema =
|
|
9085
|
-
var agentRecordSchema =
|
|
9086
|
-
id:
|
|
9087
|
-
profile:
|
|
9088
|
-
provider:
|
|
9089
|
-
model:
|
|
9814
|
+
var phaseSchema = z17.enum(["work", "review", "revision", "write"]);
|
|
9815
|
+
var agentRecordSchema = z17.object({
|
|
9816
|
+
id: z17.string().uuid(),
|
|
9817
|
+
profile: z17.string(),
|
|
9818
|
+
provider: z17.string(),
|
|
9819
|
+
model: z17.string(),
|
|
9090
9820
|
phase: phaseSchema,
|
|
9091
|
-
ok:
|
|
9092
|
-
createdAt:
|
|
9093
|
-
startedAt:
|
|
9094
|
-
endedAt:
|
|
9095
|
-
durationMs:
|
|
9096
|
-
toolCalls:
|
|
9097
|
-
usage:
|
|
9098
|
-
inputTokens:
|
|
9099
|
-
outputTokens:
|
|
9821
|
+
ok: z17.boolean(),
|
|
9822
|
+
createdAt: z17.string(),
|
|
9823
|
+
startedAt: z17.string().optional(),
|
|
9824
|
+
endedAt: z17.string().optional(),
|
|
9825
|
+
durationMs: z17.number().int().nonnegative().optional(),
|
|
9826
|
+
toolCalls: z17.number().int().nonnegative().optional(),
|
|
9827
|
+
usage: z17.object({
|
|
9828
|
+
inputTokens: z17.number().int().nonnegative(),
|
|
9829
|
+
outputTokens: z17.number().int().nonnegative()
|
|
9100
9830
|
}).strict().optional(),
|
|
9101
|
-
report:
|
|
9831
|
+
report: artifactSchema2
|
|
9102
9832
|
}).strict();
|
|
9103
|
-
var messageRecordSchema =
|
|
9104
|
-
id:
|
|
9105
|
-
from:
|
|
9106
|
-
to:
|
|
9107
|
-
createdAt:
|
|
9108
|
-
content:
|
|
9833
|
+
var messageRecordSchema = z17.object({
|
|
9834
|
+
id: z17.string().uuid(),
|
|
9835
|
+
from: z17.string(),
|
|
9836
|
+
to: z17.string(),
|
|
9837
|
+
createdAt: z17.string(),
|
|
9838
|
+
content: artifactSchema2
|
|
9109
9839
|
}).strict();
|
|
9110
|
-
var writerIntegrationSchema =
|
|
9111
|
-
status:
|
|
9112
|
-
checkedAt:
|
|
9113
|
-
detail:
|
|
9114
|
-
checkpoint:
|
|
9115
|
-
sessionId:
|
|
9116
|
-
checkpointId:
|
|
9840
|
+
var writerIntegrationSchema = z17.object({
|
|
9841
|
+
status: z17.enum(["ready", "conflict", "integrated"]),
|
|
9842
|
+
checkedAt: z17.string(),
|
|
9843
|
+
detail: z17.string().max(2e4),
|
|
9844
|
+
checkpoint: z17.object({
|
|
9845
|
+
sessionId: z17.string(),
|
|
9846
|
+
checkpointId: z17.string()
|
|
9117
9847
|
}).strict().optional(),
|
|
9118
|
-
integratedAt:
|
|
9848
|
+
integratedAt: z17.string().optional()
|
|
9119
9849
|
}).strict();
|
|
9120
|
-
var writerLaneSchema =
|
|
9121
|
-
profile:
|
|
9122
|
-
reviewer:
|
|
9123
|
-
baseCommit:
|
|
9124
|
-
outcome:
|
|
9125
|
-
patch:
|
|
9126
|
-
files:
|
|
9127
|
-
worktreeCleaned:
|
|
9128
|
-
review:
|
|
9850
|
+
var writerLaneSchema = z17.object({
|
|
9851
|
+
profile: z17.string(),
|
|
9852
|
+
reviewer: z17.string(),
|
|
9853
|
+
baseCommit: z17.string().regex(/^[a-f0-9]{40,64}$/u),
|
|
9854
|
+
outcome: z17.enum(["accepted", "rejected", "failed", "cancelled"]),
|
|
9855
|
+
patch: artifactSchema2,
|
|
9856
|
+
files: z17.array(z17.string().min(1).max(4e3)).max(2e3),
|
|
9857
|
+
worktreeCleaned: z17.boolean(),
|
|
9858
|
+
review: artifactSchema2.optional(),
|
|
9129
9859
|
integration: writerIntegrationSchema.optional()
|
|
9130
9860
|
}).strict();
|
|
9131
9861
|
var manifestFields = {
|
|
9132
9862
|
id: runIdSchema,
|
|
9133
|
-
workspace:
|
|
9134
|
-
objective:
|
|
9135
|
-
reviewer:
|
|
9136
|
-
createdAt:
|
|
9137
|
-
updatedAt:
|
|
9138
|
-
status:
|
|
9139
|
-
maxReviewRounds:
|
|
9140
|
-
reviewRounds:
|
|
9141
|
-
agents:
|
|
9142
|
-
messages:
|
|
9863
|
+
workspace: z17.string(),
|
|
9864
|
+
objective: z17.string().max(3e4),
|
|
9865
|
+
reviewer: z17.string(),
|
|
9866
|
+
createdAt: z17.string(),
|
|
9867
|
+
updatedAt: z17.string(),
|
|
9868
|
+
status: z17.enum(["running", "accepted", "rejected", "failed"]),
|
|
9869
|
+
maxReviewRounds: z17.number().int().min(0).max(3),
|
|
9870
|
+
reviewRounds: z17.number().int().min(0).max(3),
|
|
9871
|
+
agents: z17.array(agentRecordSchema).max(256),
|
|
9872
|
+
messages: z17.array(messageRecordSchema).max(512)
|
|
9143
9873
|
};
|
|
9144
|
-
var manifestV1Schema =
|
|
9145
|
-
version:
|
|
9874
|
+
var manifestV1Schema = z17.object({
|
|
9875
|
+
version: z17.literal(1),
|
|
9146
9876
|
...manifestFields
|
|
9147
9877
|
}).strict();
|
|
9148
|
-
var manifestV2Schema =
|
|
9149
|
-
version:
|
|
9878
|
+
var manifestV2Schema = z17.object({
|
|
9879
|
+
version: z17.literal(2),
|
|
9150
9880
|
...manifestFields,
|
|
9151
9881
|
writer: writerLaneSchema.optional()
|
|
9152
9882
|
}).strict();
|
|
9153
|
-
var manifestSchema2 =
|
|
9883
|
+
var manifestSchema2 = z17.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
|
|
9154
9884
|
var TeamRunStore = class {
|
|
9155
9885
|
workspace;
|
|
9156
9886
|
directory;
|
|
9157
9887
|
managedDirectory;
|
|
9158
9888
|
writes = Promise.resolve();
|
|
9159
9889
|
constructor(workspace, directory) {
|
|
9160
|
-
this.workspace =
|
|
9890
|
+
this.workspace = resolve16(workspace);
|
|
9161
9891
|
this.managedDirectory = directory === void 0;
|
|
9162
|
-
this.directory = directory ?
|
|
9892
|
+
this.directory = directory ? resolve16(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
|
|
9163
9893
|
}
|
|
9164
9894
|
async create(input2) {
|
|
9165
9895
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9243,10 +9973,10 @@ var TeamRunStore = class {
|
|
|
9243
9973
|
runIdSchema.parse(runId);
|
|
9244
9974
|
await this.writes;
|
|
9245
9975
|
await this.assertRunDirectory(runId);
|
|
9246
|
-
const path =
|
|
9976
|
+
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
9247
9977
|
await this.assertRegularFile(path);
|
|
9248
|
-
const manifest = manifestSchema2.parse(JSON.parse(await
|
|
9249
|
-
if (manifest.id !== runId ||
|
|
9978
|
+
const manifest = manifestSchema2.parse(JSON.parse(await readFile15(path, "utf8")));
|
|
9979
|
+
if (manifest.id !== runId || resolve16(manifest.workspace) !== this.workspace) {
|
|
9250
9980
|
throw new Error("Team run manifest identity does not match its location.");
|
|
9251
9981
|
}
|
|
9252
9982
|
if (verify) {
|
|
@@ -9260,13 +9990,13 @@ var TeamRunStore = class {
|
|
|
9260
9990
|
}
|
|
9261
9991
|
async readArtifact(runId, artifact) {
|
|
9262
9992
|
await this.verifyArtifact(runId, artifact);
|
|
9263
|
-
return
|
|
9993
|
+
return readFile15(this.artifactPath(runId, artifact.sha256), "utf8");
|
|
9264
9994
|
}
|
|
9265
9995
|
async list() {
|
|
9266
9996
|
await this.writes;
|
|
9267
9997
|
try {
|
|
9268
9998
|
await assertNoSymlinkPath(this.workspace, this.directory);
|
|
9269
|
-
const entries = await
|
|
9999
|
+
const entries = await readdir7(this.directory, { withFileTypes: true });
|
|
9270
10000
|
const summaries = [];
|
|
9271
10001
|
for (const entry of entries) {
|
|
9272
10002
|
if (!entry.isDirectory() || entry.isSymbolicLink() || !runIdSchema.safeParse(entry.name).success) continue;
|
|
@@ -9289,9 +10019,9 @@ var TeamRunStore = class {
|
|
|
9289
10019
|
const directory = this.runDirectory(runId);
|
|
9290
10020
|
await assertNoSymlinkPath(this.workspace, directory);
|
|
9291
10021
|
try {
|
|
9292
|
-
const info = await
|
|
10022
|
+
const info = await lstat18(directory);
|
|
9293
10023
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
|
|
9294
|
-
await
|
|
10024
|
+
await rm3(directory, { recursive: true });
|
|
9295
10025
|
return true;
|
|
9296
10026
|
} catch (error) {
|
|
9297
10027
|
if (error.code === "ENOENT") return false;
|
|
@@ -9314,31 +10044,31 @@ var TeamRunStore = class {
|
|
|
9314
10044
|
}
|
|
9315
10045
|
async loadUnlocked(runId) {
|
|
9316
10046
|
await this.assertRunDirectory(runId);
|
|
9317
|
-
const path =
|
|
10047
|
+
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
9318
10048
|
await this.assertRegularFile(path);
|
|
9319
|
-
return manifestSchema2.parse(JSON.parse(await
|
|
10049
|
+
return manifestSchema2.parse(JSON.parse(await readFile15(path, "utf8")));
|
|
9320
10050
|
}
|
|
9321
10051
|
async writeManifest(manifest) {
|
|
9322
10052
|
const directory = this.runDirectory(manifest.id);
|
|
9323
10053
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
9324
10054
|
requireActiveNamespace: this.managedDirectory
|
|
9325
10055
|
});
|
|
9326
|
-
await atomicWrite(
|
|
10056
|
+
await atomicWrite(join16(directory, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
9327
10057
|
`, 384);
|
|
9328
10058
|
}
|
|
9329
10059
|
async writeArtifact(runId, content, truncate = true) {
|
|
9330
10060
|
const data = boundedArtifactText(content, 5e5, truncate);
|
|
9331
10061
|
const bytes = Buffer.byteLength(data);
|
|
9332
|
-
const sha256 =
|
|
9333
|
-
const directory =
|
|
10062
|
+
const sha256 = createHash12("sha256").update(data).digest("hex");
|
|
10063
|
+
const directory = join16(this.runDirectory(runId), "blobs");
|
|
9334
10064
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
9335
10065
|
requireActiveNamespace: this.managedDirectory
|
|
9336
10066
|
});
|
|
9337
|
-
const path =
|
|
10067
|
+
const path = join16(directory, `${sha256}.txt`);
|
|
9338
10068
|
try {
|
|
9339
10069
|
await this.assertRegularFile(path);
|
|
9340
|
-
const existing = await
|
|
9341
|
-
if (
|
|
10070
|
+
const existing = await readFile15(path);
|
|
10071
|
+
if (createHash12("sha256").update(existing).digest("hex") !== sha256) {
|
|
9342
10072
|
throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
|
|
9343
10073
|
}
|
|
9344
10074
|
} catch (error) {
|
|
@@ -9358,27 +10088,27 @@ var TeamRunStore = class {
|
|
|
9358
10088
|
hashSchema.parse(artifact.sha256);
|
|
9359
10089
|
const path = this.artifactPath(runId, artifact.sha256);
|
|
9360
10090
|
await this.assertRegularFile(path);
|
|
9361
|
-
const data = await
|
|
9362
|
-
const
|
|
9363
|
-
if (
|
|
10091
|
+
const data = await readFile15(path);
|
|
10092
|
+
const hash2 = createHash12("sha256").update(data).digest("hex");
|
|
10093
|
+
if (hash2 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
|
|
9364
10094
|
throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
|
|
9365
10095
|
}
|
|
9366
10096
|
}
|
|
9367
10097
|
artifactPath(runId, sha256) {
|
|
9368
|
-
return
|
|
10098
|
+
return join16(this.runDirectory(runId), "blobs", `${sha256}.txt`);
|
|
9369
10099
|
}
|
|
9370
10100
|
runDirectory(runId) {
|
|
9371
|
-
return
|
|
10101
|
+
return join16(this.directory, runId);
|
|
9372
10102
|
}
|
|
9373
10103
|
async assertRunDirectory(runId) {
|
|
9374
10104
|
const directory = this.runDirectory(runId);
|
|
9375
10105
|
await assertNoSymlinkPath(this.workspace, directory);
|
|
9376
|
-
const info = await
|
|
10106
|
+
const info = await lstat18(directory);
|
|
9377
10107
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
|
|
9378
10108
|
}
|
|
9379
10109
|
async assertRegularFile(path) {
|
|
9380
|
-
await assertNoSymlinkPath(this.workspace,
|
|
9381
|
-
const info = await
|
|
10110
|
+
await assertNoSymlinkPath(this.workspace, resolve16(path, ".."));
|
|
10111
|
+
const info = await lstat18(path);
|
|
9382
10112
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Team run file is not a regular file: ${path}`);
|
|
9383
10113
|
}
|
|
9384
10114
|
};
|
|
@@ -9422,10 +10152,10 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
9422
10152
|
}
|
|
9423
10153
|
|
|
9424
10154
|
// src/agent/writer-lane.ts
|
|
9425
|
-
import { createHash as
|
|
9426
|
-
import { lstat as
|
|
10155
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
10156
|
+
import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
|
|
9427
10157
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
9428
|
-
import { isAbsolute as isAbsolute5, join as
|
|
10158
|
+
import { isAbsolute as isAbsolute5, join as join17, resolve as resolve17 } from "node:path";
|
|
9429
10159
|
var WriterLaneApplyError = class extends Error {
|
|
9430
10160
|
constructor(message2, attempted, cause) {
|
|
9431
10161
|
super(message2, cause === void 0 ? void 0 : { cause });
|
|
@@ -9438,18 +10168,18 @@ var WriterLane = class {
|
|
|
9438
10168
|
workspace;
|
|
9439
10169
|
constructor(workspace, workspaceRoots) {
|
|
9440
10170
|
this.workspace = new WorkspaceAccess([
|
|
9441
|
-
|
|
9442
|
-
...workspaceRoots.map((root) =>
|
|
10171
|
+
resolve17(workspace),
|
|
10172
|
+
...workspaceRoots.map((root) => resolve17(root))
|
|
9443
10173
|
]);
|
|
9444
10174
|
}
|
|
9445
10175
|
async createDraft(maxPatchBytes, operation, signal) {
|
|
9446
10176
|
const repository = await this.repository();
|
|
9447
10177
|
const baseCommit = await this.head(repository);
|
|
9448
10178
|
const lease = await acquireNamespaceLease(
|
|
9449
|
-
|
|
10179
|
+
join17(repository.commonDirectory, "skein-writer-lane"),
|
|
9450
10180
|
"exclusive"
|
|
9451
10181
|
);
|
|
9452
|
-
const worktree = await mkdtemp(
|
|
10182
|
+
const worktree = await mkdtemp(join17(tmpdir2(), "skein-writer-"));
|
|
9453
10183
|
let added = false;
|
|
9454
10184
|
let value;
|
|
9455
10185
|
let patch = "";
|
|
@@ -9520,7 +10250,7 @@ var WriterLane = class {
|
|
|
9520
10250
|
return {
|
|
9521
10251
|
baseCommit,
|
|
9522
10252
|
patch,
|
|
9523
|
-
patchSha256:
|
|
10253
|
+
patchSha256: createHash13("sha256").update(patch).digest("hex"),
|
|
9524
10254
|
files,
|
|
9525
10255
|
worktreeCleaned,
|
|
9526
10256
|
value
|
|
@@ -9539,7 +10269,7 @@ var WriterLane = class {
|
|
|
9539
10269
|
try {
|
|
9540
10270
|
const repository = await this.repository();
|
|
9541
10271
|
const lease = await acquireNamespaceLease(
|
|
9542
|
-
|
|
10272
|
+
join17(repository.commonDirectory, "skein-writer-lane"),
|
|
9543
10273
|
"exclusive"
|
|
9544
10274
|
);
|
|
9545
10275
|
try {
|
|
@@ -9647,7 +10377,7 @@ var WriterLane = class {
|
|
|
9647
10377
|
if (isAbsolute5(file) || file === ".git" || file.startsWith(".git/") || file.includes("\0")) {
|
|
9648
10378
|
throw new Error(`Writer patch contains an unsafe path: ${file}`);
|
|
9649
10379
|
}
|
|
9650
|
-
await this.workspace.resolvePath(
|
|
10380
|
+
await this.workspace.resolvePath(join17(repository.root, file), { allowMissing: true });
|
|
9651
10381
|
}
|
|
9652
10382
|
if (expectedFiles && !sameSet(files, expectedFiles)) {
|
|
9653
10383
|
throw new Error("Writer patch paths do not match the persisted file manifest.");
|
|
@@ -9662,7 +10392,7 @@ var WriterLane = class {
|
|
|
9662
10392
|
if (topLevel.exitCode !== 0 || !topLevel.stdout.trim()) {
|
|
9663
10393
|
throw new Error("The primary workspace must be a Git repository root for writer lanes.");
|
|
9664
10394
|
}
|
|
9665
|
-
const discoveredRoot =
|
|
10395
|
+
const discoveredRoot = resolve17(topLevel.stdout.trim());
|
|
9666
10396
|
if (await realpath7(discoveredRoot) !== await realpath7(root)) {
|
|
9667
10397
|
throw new Error("The primary workspace must be the Git repository root for writer lanes.");
|
|
9668
10398
|
}
|
|
@@ -9671,7 +10401,7 @@ var WriterLane = class {
|
|
|
9671
10401
|
if (common.exitCode !== 0 || !common.stdout.trim()) {
|
|
9672
10402
|
throw new Error(`Unable to resolve the Git common directory: ${processDetail(common)}`);
|
|
9673
10403
|
}
|
|
9674
|
-
const commonDirectory = await realpath7(isAbsolute5(common.stdout.trim()) ? common.stdout.trim() :
|
|
10404
|
+
const commonDirectory = await realpath7(isAbsolute5(common.stdout.trim()) ? common.stdout.trim() : resolve17(repositoryRoot, common.stdout.trim()));
|
|
9675
10405
|
return { root: repositoryRoot, commonDirectory, runtime };
|
|
9676
10406
|
}
|
|
9677
10407
|
async head(repository) {
|
|
@@ -9683,7 +10413,7 @@ var WriterLane = class {
|
|
|
9683
10413
|
return value;
|
|
9684
10414
|
}
|
|
9685
10415
|
async cleanupWorktree(repository, worktree, added) {
|
|
9686
|
-
const physicalWorktree = await realpath7(worktree).catch(() =>
|
|
10416
|
+
const physicalWorktree = await realpath7(worktree).catch(() => resolve17(worktree));
|
|
9687
10417
|
if (added) {
|
|
9688
10418
|
await runIsolatedGit(repository.runtime, [
|
|
9689
10419
|
"worktree",
|
|
@@ -9692,7 +10422,7 @@ var WriterLane = class {
|
|
|
9692
10422
|
worktree
|
|
9693
10423
|
], repository.root, { timeoutMs: 6e4 }).catch(() => void 0);
|
|
9694
10424
|
}
|
|
9695
|
-
await
|
|
10425
|
+
await rm4(worktree, { recursive: true, force: true }).catch(() => void 0);
|
|
9696
10426
|
await runIsolatedGit(repository.runtime, [
|
|
9697
10427
|
"worktree",
|
|
9698
10428
|
"prune",
|
|
@@ -9741,7 +10471,7 @@ function processDetail(result) {
|
|
|
9741
10471
|
}
|
|
9742
10472
|
async function pathExists2(path) {
|
|
9743
10473
|
try {
|
|
9744
|
-
await
|
|
10474
|
+
await lstat19(path);
|
|
9745
10475
|
return true;
|
|
9746
10476
|
} catch (error) {
|
|
9747
10477
|
if (error.code === "ENOENT") return false;
|
|
@@ -9750,14 +10480,14 @@ async function pathExists2(path) {
|
|
|
9750
10480
|
}
|
|
9751
10481
|
|
|
9752
10482
|
// src/agent/delegation.ts
|
|
9753
|
-
var writerRunInputSchema =
|
|
9754
|
-
task:
|
|
9755
|
-
profile:
|
|
9756
|
-
reviewer:
|
|
10483
|
+
var writerRunInputSchema = z18.object({
|
|
10484
|
+
task: z18.string().min(1).max(2e4),
|
|
10485
|
+
profile: z18.string().max(64).optional(),
|
|
10486
|
+
reviewer: z18.string().max(64).optional()
|
|
9757
10487
|
}).strict();
|
|
9758
|
-
var writerIntegrateInputSchema =
|
|
9759
|
-
run_id:
|
|
9760
|
-
patch_sha256:
|
|
10488
|
+
var writerIntegrateInputSchema = z18.object({
|
|
10489
|
+
run_id: z18.string().uuid(),
|
|
10490
|
+
patch_sha256: z18.string().regex(/^[a-f0-9]{64}$/u)
|
|
9761
10491
|
}).strict();
|
|
9762
10492
|
var DelegationManager = class {
|
|
9763
10493
|
constructor(options) {
|
|
@@ -9818,10 +10548,10 @@ var DelegationManager = class {
|
|
|
9818
10548
|
},
|
|
9819
10549
|
async execute(arguments_, context) {
|
|
9820
10550
|
if (!manager.team.enabled) return { ok: false, content: "Multi-agent delegation is disabled." };
|
|
9821
|
-
const input2 =
|
|
9822
|
-
tasks:
|
|
9823
|
-
profile:
|
|
9824
|
-
task:
|
|
10551
|
+
const input2 = z18.object({
|
|
10552
|
+
tasks: z18.array(z18.object({
|
|
10553
|
+
profile: z18.string().max(64).optional(),
|
|
10554
|
+
task: z18.string().min(1).max(2e4)
|
|
9825
10555
|
})).min(1).max(manager.team.maxDelegations)
|
|
9826
10556
|
}).parse(arguments_);
|
|
9827
10557
|
const tasks = input2.tasks.map((task) => ({
|
|
@@ -9867,13 +10597,13 @@ var DelegationManager = class {
|
|
|
9867
10597
|
},
|
|
9868
10598
|
async execute(arguments_, context) {
|
|
9869
10599
|
if (!manager.team.enabled) return { ok: false, content: "Multi-agent teams are disabled." };
|
|
9870
|
-
const input2 =
|
|
9871
|
-
objective:
|
|
9872
|
-
tasks:
|
|
9873
|
-
profile:
|
|
9874
|
-
task:
|
|
10600
|
+
const input2 = z18.object({
|
|
10601
|
+
objective: z18.string().min(1).max(3e4),
|
|
10602
|
+
tasks: z18.array(z18.object({
|
|
10603
|
+
profile: z18.string().max(64).optional(),
|
|
10604
|
+
task: z18.string().min(1).max(2e4)
|
|
9875
10605
|
})).min(1).max(manager.team.maxDelegations),
|
|
9876
|
-
reviewer:
|
|
10606
|
+
reviewer: z18.string().max(64).optional()
|
|
9877
10607
|
}).parse(arguments_);
|
|
9878
10608
|
const tasks = input2.tasks.map((task) => ({
|
|
9879
10609
|
profile: task.profile ?? manager.team.defaultProfile,
|
|
@@ -9936,7 +10666,7 @@ var DelegationManager = class {
|
|
|
9936
10666
|
const plan = await manager.loadWriterPlan(input2.run_id, input2.patch_sha256);
|
|
9937
10667
|
const files = await manager.writerLane.inspectPatch(plan.patch, plan.writer.files);
|
|
9938
10668
|
return Promise.all(files.map(
|
|
9939
|
-
(file) => context.workspace.resolvePath(
|
|
10669
|
+
(file) => context.workspace.resolvePath(join18(context.workspace.primaryRoot, file), { allowMissing: true })
|
|
9940
10670
|
));
|
|
9941
10671
|
},
|
|
9942
10672
|
async execute(arguments_, context) {
|
|
@@ -10123,7 +10853,7 @@ ${review.summary}`,
|
|
|
10123
10853
|
const plan = await this.loadWriterPlan(runId, patchSha256);
|
|
10124
10854
|
const files = await this.writerLane.inspectPatch(plan.patch, plan.writer.files);
|
|
10125
10855
|
const paths = await Promise.all(files.map(
|
|
10126
|
-
(file) => context.workspace.resolvePath(
|
|
10856
|
+
(file) => context.workspace.resolvePath(join18(context.workspace.primaryRoot, file), { allowMissing: true })
|
|
10127
10857
|
));
|
|
10128
10858
|
const checkpointStore = new CheckpointStore(context.workspace);
|
|
10129
10859
|
let checkpointId = context.checkpointId;
|
|
@@ -11598,7 +12328,7 @@ function formatResultDetail(result, paint = chalk2, glyphs = resolveCliGlyphs())
|
|
|
11598
12328
|
}
|
|
11599
12329
|
|
|
11600
12330
|
// src/cli/namespace-leases.ts
|
|
11601
|
-
import { resolve as
|
|
12331
|
+
import { resolve as resolve18 } from "node:path";
|
|
11602
12332
|
function cliNamespaceLeaseScopes(actionCommand) {
|
|
11603
12333
|
const names = commandNames(actionCommand);
|
|
11604
12334
|
const topLevel = names[1];
|
|
@@ -11620,7 +12350,7 @@ async function acquireCliNamespaceLeases(actionCommand) {
|
|
|
11620
12350
|
const scopes = cliNamespaceLeaseScopes(actionCommand);
|
|
11621
12351
|
const localOptions = actionCommand.opts();
|
|
11622
12352
|
const globalOptions = actionCommand.optsWithGlobals();
|
|
11623
|
-
const workspace =
|
|
12353
|
+
const workspace = resolve18(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
|
|
11624
12354
|
const targets = scopes.map((scope) => scope === "project" ? projectNamespacePaths(workspace).canonical : homeNamespacePaths().canonical);
|
|
11625
12355
|
const leases = [];
|
|
11626
12356
|
try {
|
|
@@ -11774,11 +12504,11 @@ function command(name, description, usage, aliases) {
|
|
|
11774
12504
|
|
|
11775
12505
|
// src/ui/text.ts
|
|
11776
12506
|
import stringWidth from "string-width";
|
|
11777
|
-
import
|
|
12507
|
+
import stripAnsi2 from "strip-ansi";
|
|
11778
12508
|
var controlCharacters = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
|
|
11779
12509
|
var escapeSequences = /[[\]][0-9;?=>!]*[ -/]*[@-~]|[[\]][?=>][0-9;?=>!]*[a-zA-Z~]/gu;
|
|
11780
12510
|
function sanitizeTerminalText(value) {
|
|
11781
|
-
return
|
|
12511
|
+
return stripAnsi2(value).replace(escapeSequences, "").replace(/\r\n?/g, "\n").replace(controlCharacters, "");
|
|
11782
12512
|
}
|
|
11783
12513
|
function terminalEllipsis() {
|
|
11784
12514
|
return process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii" ? "..." : "\u2026";
|
|
@@ -11871,8 +12601,8 @@ function graphemes(value) {
|
|
|
11871
12601
|
}
|
|
11872
12602
|
|
|
11873
12603
|
// src/ui/theme.ts
|
|
11874
|
-
import { lstat as
|
|
11875
|
-
import { basename as basename9, join as
|
|
12604
|
+
import { lstat as lstat20, readdir as readdir8, readFile as readFile16 } from "node:fs/promises";
|
|
12605
|
+
import { basename as basename9, join as join19, resolve as resolve19 } from "node:path";
|
|
11876
12606
|
import React, { createContext, useContext } from "react";
|
|
11877
12607
|
function defineTheme(seed) {
|
|
11878
12608
|
return {
|
|
@@ -11994,10 +12724,10 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
11994
12724
|
userThemeNames.clear();
|
|
11995
12725
|
const loaded = [];
|
|
11996
12726
|
const errors = [];
|
|
11997
|
-
const resolvedDirectory =
|
|
12727
|
+
const resolvedDirectory = resolve19(directory);
|
|
11998
12728
|
let entries;
|
|
11999
12729
|
try {
|
|
12000
|
-
entries = await
|
|
12730
|
+
entries = await readdir8(resolvedDirectory, { encoding: "utf8" });
|
|
12001
12731
|
} catch (error) {
|
|
12002
12732
|
if (error.code === "ENOENT") {
|
|
12003
12733
|
return { directory: resolvedDirectory, loaded, errors };
|
|
@@ -12006,13 +12736,13 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
12006
12736
|
}
|
|
12007
12737
|
for (const entry of entries) {
|
|
12008
12738
|
if (!entry.endsWith(".json")) continue;
|
|
12009
|
-
const path =
|
|
12739
|
+
const path = join19(resolvedDirectory, entry);
|
|
12010
12740
|
try {
|
|
12011
|
-
const info = await
|
|
12741
|
+
const info = await lstat20(path);
|
|
12012
12742
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 64e3) {
|
|
12013
12743
|
throw new Error("must be a regular JSON file smaller than 64 KB");
|
|
12014
12744
|
}
|
|
12015
|
-
const parsed = JSON.parse(await
|
|
12745
|
+
const parsed = JSON.parse(await readFile16(path, "utf8"));
|
|
12016
12746
|
const name = themeName(parsed, basename9(entry, ".json"));
|
|
12017
12747
|
if (builtInThemeNames.has(name)) throw new Error(`cannot replace built-in theme \`${name}\``);
|
|
12018
12748
|
themes[name] = defineTheme(themeSeed(parsed, name));
|
|
@@ -12025,7 +12755,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
12025
12755
|
return { directory: resolvedDirectory, loaded, errors };
|
|
12026
12756
|
}
|
|
12027
12757
|
function userThemeDirectory(environment = process.env) {
|
|
12028
|
-
return environment.SKEIN_THEME_DIR ?? environment.MOSAIC_THEME_DIR ??
|
|
12758
|
+
return environment.SKEIN_THEME_DIR ?? environment.MOSAIC_THEME_DIR ?? join19(resolveHomeNamespace(environment), "themes");
|
|
12029
12759
|
}
|
|
12030
12760
|
var defaultTheme = themes.graphite;
|
|
12031
12761
|
var palette = {
|
|
@@ -13252,9 +13982,9 @@ function safeWidth(width) {
|
|
|
13252
13982
|
}
|
|
13253
13983
|
|
|
13254
13984
|
// src/utils/update-check.ts
|
|
13255
|
-
import { mkdir as mkdir9, readFile as
|
|
13256
|
-
import { join as
|
|
13257
|
-
import
|
|
13985
|
+
import { mkdir as mkdir9, readFile as readFile17, writeFile as writeFile2 } from "node:fs/promises";
|
|
13986
|
+
import { join as join20 } from "node:path";
|
|
13987
|
+
import stripAnsi3 from "strip-ansi";
|
|
13258
13988
|
var PACKAGE_NAME = "@skein-code/cli";
|
|
13259
13989
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
13260
13990
|
var CACHE_FILE = "update-check.json";
|
|
@@ -13269,7 +13999,7 @@ function sanitizeHighlights(value) {
|
|
|
13269
13999
|
const cleaned = [];
|
|
13270
14000
|
for (const entry of value) {
|
|
13271
14001
|
if (typeof entry !== "string") continue;
|
|
13272
|
-
const flattened =
|
|
14002
|
+
const flattened = stripAnsi3(entry).replace(BIDI_CONTROLS, "").replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
13273
14003
|
if (!flattened || flattened.length > MAX_HIGHLIGHT_LENGTH) continue;
|
|
13274
14004
|
cleaned.push(flattened);
|
|
13275
14005
|
if (cleaned.length >= MAX_HIGHLIGHTS) break;
|
|
@@ -13333,7 +14063,7 @@ function compareVersions(a, b) {
|
|
|
13333
14063
|
return 0;
|
|
13334
14064
|
}
|
|
13335
14065
|
function updateCachePath(env = process.env) {
|
|
13336
|
-
return
|
|
14066
|
+
return join20(resolveHomeNamespace(env), CACHE_FILE);
|
|
13337
14067
|
}
|
|
13338
14068
|
function upgradeCommand() {
|
|
13339
14069
|
return `npm i -g ${PACKAGE_NAME}`;
|
|
@@ -13350,7 +14080,7 @@ function noticeIfNewer(latest, current, highlights) {
|
|
|
13350
14080
|
}
|
|
13351
14081
|
async function readUpdateCache(env = process.env) {
|
|
13352
14082
|
try {
|
|
13353
|
-
const raw = await
|
|
14083
|
+
const raw = await readFile17(updateCachePath(env), "utf8");
|
|
13354
14084
|
const parsed = JSON.parse(raw);
|
|
13355
14085
|
if (typeof parsed?.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && parsed.checkedAt >= 0 && (parsed.latest === null || typeof parsed.latest === "string" && parseVersion(parsed.latest))) {
|
|
13356
14086
|
const latest = typeof parsed.latest === "string" ? parsed.latest : null;
|
|
@@ -14431,7 +15161,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
14431
15161
|
return () => clearInterval(timer);
|
|
14432
15162
|
}, [busy]);
|
|
14433
15163
|
const requestPermission = useCallback((call, category) => {
|
|
14434
|
-
return new Promise((
|
|
15164
|
+
return new Promise((resolve24) => setPermission({ call, category, resolve: resolve24 }));
|
|
14435
15165
|
}, []);
|
|
14436
15166
|
const onEvent = useCallback((event) => {
|
|
14437
15167
|
switch (event.type) {
|
|
@@ -15255,8 +15985,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
15255
15985
|
}, [append]);
|
|
15256
15986
|
function settlePermission(grant, stop = false) {
|
|
15257
15987
|
if (!permission) return;
|
|
15258
|
-
const { call, category, resolve:
|
|
15259
|
-
|
|
15988
|
+
const { call, category, resolve: resolve24 } = permission;
|
|
15989
|
+
resolve24(grant);
|
|
15260
15990
|
setPermission(void 0);
|
|
15261
15991
|
if (grant === "session") {
|
|
15262
15992
|
append({
|
|
@@ -16591,24 +17321,24 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
|
16591
17321
|
}
|
|
16592
17322
|
|
|
16593
17323
|
// src/runtime/extensions.ts
|
|
16594
|
-
import { resolve as
|
|
17324
|
+
import { resolve as resolve22 } from "node:path";
|
|
16595
17325
|
|
|
16596
17326
|
// src/mcp/manager.ts
|
|
16597
17327
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
16598
17328
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
16599
17329
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
16600
|
-
import
|
|
17330
|
+
import stripAnsi5 from "strip-ansi";
|
|
16601
17331
|
|
|
16602
17332
|
// src/mcp/tool.ts
|
|
16603
|
-
import { createHash as
|
|
16604
|
-
import
|
|
17333
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
17334
|
+
import stripAnsi4 from "strip-ansi";
|
|
16605
17335
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
16606
17336
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
16607
|
-
var
|
|
17337
|
+
var MAX_RESULT_BYTES = 5 * 1024 * 1024;
|
|
16608
17338
|
var MAX_SCHEMA_BYTES = 1e5;
|
|
16609
17339
|
function createMcpToolAdapter(options) {
|
|
16610
17340
|
const { remoteTool } = options;
|
|
16611
|
-
const
|
|
17341
|
+
const inputSchema12 = copyInputSchema(remoteTool.inputSchema);
|
|
16612
17342
|
return {
|
|
16613
17343
|
definition: {
|
|
16614
17344
|
name: options.exposedName,
|
|
@@ -16616,7 +17346,7 @@ function createMcpToolAdapter(options) {
|
|
|
16616
17346
|
// MCP servers are an external trust boundary. Read-only annotations are
|
|
16617
17347
|
// hints from that server and must not lower the local permission level.
|
|
16618
17348
|
category: "network",
|
|
16619
|
-
inputSchema:
|
|
17349
|
+
inputSchema: inputSchema12
|
|
16620
17350
|
},
|
|
16621
17351
|
permissionCategories: () => ["network"],
|
|
16622
17352
|
async execute(arguments_, context) {
|
|
@@ -16636,6 +17366,8 @@ function createMcpToolAdapter(options) {
|
|
|
16636
17366
|
metadata: {
|
|
16637
17367
|
mcpServer: options.serverName,
|
|
16638
17368
|
mcpTool: remoteTool.name,
|
|
17369
|
+
sourceBytes: normalized.sourceBytes,
|
|
17370
|
+
sourceTruncated: normalized.sourceTruncated,
|
|
16639
17371
|
...normalized.isError ? { mcpError: true } : {}
|
|
16640
17372
|
}
|
|
16641
17373
|
};
|
|
@@ -16665,8 +17397,8 @@ function isUsableRemoteTool(tool) {
|
|
|
16665
17397
|
}
|
|
16666
17398
|
}
|
|
16667
17399
|
function describeTool(serverName, tool) {
|
|
16668
|
-
const label =
|
|
16669
|
-
const description = tool.description ?
|
|
17400
|
+
const label = stripAnsi4(tool.title?.trim() || tool.name).replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 200);
|
|
17401
|
+
const description = tool.description ? stripAnsi4(tool.description).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim().slice(0, MAX_DESCRIPTION_LENGTH) : void 0;
|
|
16670
17402
|
return description ? `[MCP ${serverName}/${label}] ${description}` : `Call the ${label} tool provided by the ${serverName} MCP server.`;
|
|
16671
17403
|
}
|
|
16672
17404
|
function copyInputSchema(schema) {
|
|
@@ -16689,7 +17421,7 @@ function assertArguments(arguments_) {
|
|
|
16689
17421
|
}
|
|
16690
17422
|
function normalizeCallResult(result) {
|
|
16691
17423
|
if (!isRecord2(result)) {
|
|
16692
|
-
return
|
|
17424
|
+
return boundResult(safeJson(result), false);
|
|
16693
17425
|
}
|
|
16694
17426
|
const isError = result.isError === true;
|
|
16695
17427
|
const sections = [];
|
|
@@ -16705,7 +17437,22 @@ ${safeJson(result.structuredContent)}`);
|
|
|
16705
17437
|
${safeJson(result.toolResult)}`);
|
|
16706
17438
|
}
|
|
16707
17439
|
const content = sections.filter(Boolean).join("\n\n") || (isError ? "The MCP tool reported an error." : "The MCP tool completed without output.");
|
|
16708
|
-
return
|
|
17440
|
+
return boundResult(content, isError);
|
|
17441
|
+
}
|
|
17442
|
+
function boundResult(content, isError) {
|
|
17443
|
+
const sourceBytes = Buffer.byteLength(content);
|
|
17444
|
+
if (sourceBytes <= MAX_RESULT_BYTES) {
|
|
17445
|
+
return { content, isError, sourceBytes, sourceTruncated: false };
|
|
17446
|
+
}
|
|
17447
|
+
const marker = `
|
|
17448
|
+
... ${sourceBytes - MAX_RESULT_BYTES} or more source bytes omitted by the MCP safety limit ...
|
|
17449
|
+
`;
|
|
17450
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
17451
|
+
const available = MAX_RESULT_BYTES - markerBytes;
|
|
17452
|
+
const headBytes = Math.floor(available * 0.6);
|
|
17453
|
+
const tailBytes = available - headBytes;
|
|
17454
|
+
const bounded = `${sliceUtf8Start(content, headBytes)}${marker}${sliceUtf8End(content, tailBytes)}`;
|
|
17455
|
+
return { content: bounded, isError, sourceBytes, sourceTruncated: true };
|
|
16709
17456
|
}
|
|
16710
17457
|
function formatContentBlock(block) {
|
|
16711
17458
|
if (!isRecord2(block) || typeof block.type !== "string") return safeJson(block);
|
|
@@ -16753,15 +17500,10 @@ function fitToolName(name, identity) {
|
|
|
16753
17500
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
16754
17501
|
}
|
|
16755
17502
|
function shortHash(value) {
|
|
16756
|
-
return
|
|
16757
|
-
}
|
|
16758
|
-
function truncateResult(content) {
|
|
16759
|
-
if (content.length <= MAX_RESULT_LENGTH) return content;
|
|
16760
|
-
return `${content.slice(0, MAX_RESULT_LENGTH)}
|
|
16761
|
-
... MCP result truncated`;
|
|
17503
|
+
return createHash14("sha256").update(value).digest("hex").slice(0, 8);
|
|
16762
17504
|
}
|
|
16763
17505
|
function sanitizeOutputText(value) {
|
|
16764
|
-
return
|
|
17506
|
+
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
16765
17507
|
}
|
|
16766
17508
|
function sanitizeInlineText(value) {
|
|
16767
17509
|
return sanitizeOutputText(value).replace(/[\r\n\t]+/g, " ").trim().slice(0, 2e3);
|
|
@@ -16773,13 +17515,25 @@ function safeJson(value) {
|
|
|
16773
17515
|
return String(value);
|
|
16774
17516
|
}
|
|
16775
17517
|
}
|
|
17518
|
+
function sliceUtf8Start(value, maxBytes) {
|
|
17519
|
+
const buffer = Buffer.from(value);
|
|
17520
|
+
let end = Math.min(buffer.length, maxBytes);
|
|
17521
|
+
while (end > 0 && (buffer[end] ?? 0) >= 128 && (buffer[end] ?? 0) < 192) end -= 1;
|
|
17522
|
+
return buffer.subarray(0, end).toString("utf8");
|
|
17523
|
+
}
|
|
17524
|
+
function sliceUtf8End(value, maxBytes) {
|
|
17525
|
+
const buffer = Buffer.from(value);
|
|
17526
|
+
let start = Math.max(0, buffer.length - maxBytes);
|
|
17527
|
+
while (start < buffer.length && (buffer[start] ?? 0) >= 128 && (buffer[start] ?? 0) < 192) start += 1;
|
|
17528
|
+
return buffer.subarray(start).toString("utf8");
|
|
17529
|
+
}
|
|
16776
17530
|
function isRecord2(value) {
|
|
16777
17531
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16778
17532
|
}
|
|
16779
17533
|
|
|
16780
17534
|
// src/mcp/validation.ts
|
|
16781
17535
|
import { stat as stat10, realpath as realpath8 } from "node:fs/promises";
|
|
16782
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
17536
|
+
import { isAbsolute as isAbsolute6, resolve as resolve20 } from "node:path";
|
|
16783
17537
|
var SERVER_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
16784
17538
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16785
17539
|
var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
@@ -16851,9 +17605,9 @@ async function validateCwd(configured, options) {
|
|
|
16851
17605
|
if (configured !== void 0 && (configured.length > 4e3 || CONTROL_CHARACTERS.test(configured))) {
|
|
16852
17606
|
throw new Error("MCP working directory is invalid or too long");
|
|
16853
17607
|
}
|
|
16854
|
-
const defaultCwd =
|
|
16855
|
-
const candidate = configured ? isAbsolute6(configured) ?
|
|
16856
|
-
const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) =>
|
|
17608
|
+
const defaultCwd = resolve20(options.cwd ?? process.cwd());
|
|
17609
|
+
const candidate = configured ? isAbsolute6(configured) ? resolve20(configured) : resolve20(defaultCwd, configured) : defaultCwd;
|
|
17610
|
+
const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve20(root)) : [defaultCwd];
|
|
16857
17611
|
const resolvedCandidate = await realpath8(candidate).catch(() => {
|
|
16858
17612
|
throw new Error(`MCP working directory does not exist: ${candidate}`);
|
|
16859
17613
|
});
|
|
@@ -17191,7 +17945,7 @@ var McpManager = class {
|
|
|
17191
17945
|
stderr: "pipe"
|
|
17192
17946
|
});
|
|
17193
17947
|
transport.stderr?.on("data", (chunk) => {
|
|
17194
|
-
const text =
|
|
17948
|
+
const text = stripAnsi5(chunk.toString()).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
|
|
17195
17949
|
if (text) this.options.logger?.(`MCP ${name} stderr`, { text: text.slice(0, 2e3) });
|
|
17196
17950
|
});
|
|
17197
17951
|
return transport;
|
|
@@ -17350,7 +18104,7 @@ function errorMessage2(error) {
|
|
|
17350
18104
|
return sanitizeStatusText(message2) || "Unknown MCP error";
|
|
17351
18105
|
}
|
|
17352
18106
|
function sanitizeStatusText(value) {
|
|
17353
|
-
return
|
|
18107
|
+
return stripAnsi5(value).replace(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 1e3);
|
|
17354
18108
|
}
|
|
17355
18109
|
function timeoutError(timeoutMs) {
|
|
17356
18110
|
const error = new Error(`MCP request timed out after ${timeoutMs} ms`);
|
|
@@ -17376,9 +18130,9 @@ async function closeTransportQuietly(transport) {
|
|
|
17376
18130
|
}
|
|
17377
18131
|
|
|
17378
18132
|
// src/memory/tools.ts
|
|
17379
|
-
import { z as
|
|
17380
|
-
var scopeSchema =
|
|
17381
|
-
var kindSchema =
|
|
18133
|
+
import { z as z19 } from "zod";
|
|
18134
|
+
var scopeSchema = z19.enum(["user", "workspace", "session", "agent"]);
|
|
18135
|
+
var kindSchema = z19.enum(["semantic", "episodic", "procedural"]);
|
|
17382
18136
|
function createMemoryTools(store) {
|
|
17383
18137
|
return [
|
|
17384
18138
|
{
|
|
@@ -17393,10 +18147,10 @@ function createMemoryTools(store) {
|
|
|
17393
18147
|
}, ["query"])
|
|
17394
18148
|
},
|
|
17395
18149
|
async execute(arguments_, context) {
|
|
17396
|
-
const input2 =
|
|
17397
|
-
query:
|
|
18150
|
+
const input2 = z19.object({
|
|
18151
|
+
query: z19.string().max(4e3),
|
|
17398
18152
|
scope: scopeSchema.optional(),
|
|
17399
|
-
limit:
|
|
18153
|
+
limit: z19.number().int().min(1).max(20).optional()
|
|
17400
18154
|
}).parse(arguments_);
|
|
17401
18155
|
const scopes = input2.scope ? [{ scope: input2.scope, scopeKey: scopeKey(input2.scope, context) }] : availableScopes(context);
|
|
17402
18156
|
const records = store.search(input2.query, { scopes, limit: input2.limit ?? 8 });
|
|
@@ -17428,17 +18182,17 @@ ${record.content}`
|
|
|
17428
18182
|
}, ["content", "rationale"])
|
|
17429
18183
|
},
|
|
17430
18184
|
async execute(arguments_, context) {
|
|
17431
|
-
const input2 =
|
|
17432
|
-
content:
|
|
17433
|
-
rationale:
|
|
18185
|
+
const input2 = z19.object({
|
|
18186
|
+
content: z19.string().min(1).max(12e3),
|
|
18187
|
+
rationale: z19.string().min(1).max(1e3),
|
|
17434
18188
|
scope: scopeSchema.optional(),
|
|
17435
18189
|
kind: kindSchema.optional(),
|
|
17436
|
-
tags:
|
|
17437
|
-
importance:
|
|
17438
|
-
confidence:
|
|
17439
|
-
agent:
|
|
17440
|
-
revision:
|
|
17441
|
-
conflictKey:
|
|
18190
|
+
tags: z19.array(z19.string()).max(24).optional(),
|
|
18191
|
+
importance: z19.number().min(0).max(1).optional(),
|
|
18192
|
+
confidence: z19.number().min(0).max(1).optional(),
|
|
18193
|
+
agent: z19.string().max(64).optional(),
|
|
18194
|
+
revision: z19.string().max(240).optional(),
|
|
18195
|
+
conflictKey: z19.string().max(240).optional()
|
|
17442
18196
|
}).strict().parse(arguments_);
|
|
17443
18197
|
const scope = input2.scope ?? "workspace";
|
|
17444
18198
|
const candidate = store.propose({
|
|
@@ -17476,9 +18230,9 @@ ${record.content}`
|
|
|
17476
18230
|
}, ["id"])
|
|
17477
18231
|
},
|
|
17478
18232
|
async execute(arguments_) {
|
|
17479
|
-
const input2 =
|
|
17480
|
-
id:
|
|
17481
|
-
permanent:
|
|
18233
|
+
const input2 = z19.object({
|
|
18234
|
+
id: z19.string().uuid(),
|
|
18235
|
+
permanent: z19.boolean().optional()
|
|
17482
18236
|
}).parse(arguments_);
|
|
17483
18237
|
const changed = input2.permanent ? store.remove(input2.id) : store.archive(input2.id);
|
|
17484
18238
|
return {
|
|
@@ -17505,9 +18259,9 @@ function scopeKey(scope, context) {
|
|
|
17505
18259
|
}
|
|
17506
18260
|
|
|
17507
18261
|
// src/skills/catalog.ts
|
|
17508
|
-
import { lstat as
|
|
18262
|
+
import { lstat as lstat21, readFile as readFile18, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
|
|
17509
18263
|
import { homedir as homedir4 } from "node:os";
|
|
17510
|
-
import { basename as basename11, join as
|
|
18264
|
+
import { basename as basename11, join as join21, resolve as resolve21 } from "node:path";
|
|
17511
18265
|
import { parse as parseYaml3 } from "yaml";
|
|
17512
18266
|
var SkillCatalog = class {
|
|
17513
18267
|
constructor(workspace, config) {
|
|
@@ -17527,7 +18281,7 @@ var SkillCatalog = class {
|
|
|
17527
18281
|
for (const location of locations) {
|
|
17528
18282
|
const entries = await safeDirectories(location.path);
|
|
17529
18283
|
for (const entry of entries) {
|
|
17530
|
-
const skillPath =
|
|
18284
|
+
const skillPath = join21(location.path, entry, "SKILL.md");
|
|
17531
18285
|
const metadata = await readMetadata(skillPath);
|
|
17532
18286
|
if (!metadata) continue;
|
|
17533
18287
|
const descriptor = {
|
|
@@ -17576,31 +18330,31 @@ ${skill.content}
|
|
|
17576
18330
|
}
|
|
17577
18331
|
function discoveryLocations(workspace, configured) {
|
|
17578
18332
|
const home = homedir4();
|
|
17579
|
-
const workspaceRoot =
|
|
18333
|
+
const workspaceRoot = resolve21(workspace);
|
|
17580
18334
|
return [
|
|
17581
|
-
{ path:
|
|
17582
|
-
{ path:
|
|
17583
|
-
{ path:
|
|
17584
|
-
{ path:
|
|
18335
|
+
{ path: join21(home, ".agents", "skills"), scope: "user", trusted: true },
|
|
18336
|
+
{ path: join21(home, ".claude", "skills"), scope: "user", trusted: true },
|
|
18337
|
+
{ path: join21(home, ".augment", "skills"), scope: "user", trusted: true },
|
|
18338
|
+
{ path: join21(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
|
|
17585
18339
|
...configured.map((path) => {
|
|
17586
|
-
const resolved =
|
|
18340
|
+
const resolved = resolve21(workspaceRoot, path);
|
|
17587
18341
|
return {
|
|
17588
18342
|
path: resolved,
|
|
17589
18343
|
scope: "configured",
|
|
17590
18344
|
trusted: !isInside(workspaceRoot, resolved)
|
|
17591
18345
|
};
|
|
17592
18346
|
}),
|
|
17593
|
-
{ path:
|
|
17594
|
-
{ path:
|
|
17595
|
-
{ path:
|
|
17596
|
-
{ path:
|
|
18347
|
+
{ path: join21(workspace, ".agents", "skills"), scope: "workspace", trusted: false },
|
|
18348
|
+
{ path: join21(workspace, ".claude", "skills"), scope: "workspace", trusted: false },
|
|
18349
|
+
{ path: join21(workspace, ".augment", "skills"), scope: "workspace", trusted: false },
|
|
18350
|
+
{ path: join21(resolveProjectNamespaceSync(workspaceRoot).active, "skills"), scope: "workspace", trusted: false }
|
|
17597
18351
|
];
|
|
17598
18352
|
}
|
|
17599
18353
|
async function safeDirectories(path) {
|
|
17600
18354
|
try {
|
|
17601
|
-
const info = await
|
|
18355
|
+
const info = await lstat21(path);
|
|
17602
18356
|
if (!info.isDirectory() || info.isSymbolicLink()) return [];
|
|
17603
|
-
const entries = await
|
|
18357
|
+
const entries = await readdir9(path, { withFileTypes: true });
|
|
17604
18358
|
return entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => entry.name).slice(0, 500);
|
|
17605
18359
|
} catch {
|
|
17606
18360
|
return [];
|
|
@@ -17612,7 +18366,7 @@ async function readMetadata(path) {
|
|
|
17612
18366
|
const { frontmatter } = splitFrontmatter(raw);
|
|
17613
18367
|
if (!frontmatter) return void 0;
|
|
17614
18368
|
const parsed = parseYaml3(frontmatter);
|
|
17615
|
-
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename11(
|
|
18369
|
+
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename11(resolve21(path, ".."));
|
|
17616
18370
|
const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
|
|
17617
18371
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
|
|
17618
18372
|
return void 0;
|
|
@@ -17631,13 +18385,13 @@ async function readSkill(path, maxChars) {
|
|
|
17631
18385
|
}
|
|
17632
18386
|
async function safeRead(path, maxBytes) {
|
|
17633
18387
|
try {
|
|
17634
|
-
const info = await
|
|
18388
|
+
const info = await lstat21(path);
|
|
17635
18389
|
if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
|
|
17636
|
-
const parent =
|
|
18390
|
+
const parent = resolve21(path, "..");
|
|
17637
18391
|
const resolvedParent = await realpath9(parent);
|
|
17638
18392
|
const resolvedPath = await realpath9(path);
|
|
17639
18393
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
17640
|
-
return await
|
|
18394
|
+
return await readFile18(path, "utf8");
|
|
17641
18395
|
} catch {
|
|
17642
18396
|
return void 0;
|
|
17643
18397
|
}
|
|
@@ -17682,7 +18436,7 @@ function escapeAttribute6(value) {
|
|
|
17682
18436
|
}
|
|
17683
18437
|
|
|
17684
18438
|
// src/workflows/catalog.ts
|
|
17685
|
-
import { z as
|
|
18439
|
+
import { z as z20 } from "zod";
|
|
17686
18440
|
var builtInWorkflows = [
|
|
17687
18441
|
{
|
|
17688
18442
|
name: "implement",
|
|
@@ -17773,7 +18527,7 @@ function createWorkflowTool(catalog) {
|
|
|
17773
18527
|
}, ["name", "task"])
|
|
17774
18528
|
},
|
|
17775
18529
|
async execute(arguments_) {
|
|
17776
|
-
const input2 =
|
|
18530
|
+
const input2 = z20.object({ name: z20.string(), task: z20.string().min(1).max(2e4) }).parse(arguments_);
|
|
17777
18531
|
const workflow = catalog.get(input2.name);
|
|
17778
18532
|
if (!workflow) return { ok: false, content: `Unknown workflow: ${input2.name}` };
|
|
17779
18533
|
return {
|
|
@@ -17818,7 +18572,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
17818
18572
|
delegation;
|
|
17819
18573
|
initialized = false;
|
|
17820
18574
|
static async create(config, registry, options = {}) {
|
|
17821
|
-
const runtime = new _ExtensionRuntime(config,
|
|
18575
|
+
const runtime = new _ExtensionRuntime(config, resolve22(config.workspaceRoots[0] ?? process.cwd()), options);
|
|
17822
18576
|
try {
|
|
17823
18577
|
await runtime.initialize(registry, options.signal);
|
|
17824
18578
|
return runtime;
|
|
@@ -18023,15 +18777,15 @@ function upgradeCommandOverride(env = process.env) {
|
|
|
18023
18777
|
return trimmed ? trimmed : void 0;
|
|
18024
18778
|
}
|
|
18025
18779
|
function runUpgrade(plan) {
|
|
18026
|
-
return new Promise((
|
|
18780
|
+
return new Promise((resolve24) => {
|
|
18027
18781
|
const child = spawn2(plan.command, plan.args, {
|
|
18028
18782
|
stdio: "inherit",
|
|
18029
18783
|
shell: plan.shell ?? false
|
|
18030
18784
|
});
|
|
18031
|
-
child.on("error", () =>
|
|
18785
|
+
child.on("error", () => resolve24({ ok: false, exitCode: 127, display: plan.display }));
|
|
18032
18786
|
child.on("close", (code) => {
|
|
18033
18787
|
const exitCode = code ?? 1;
|
|
18034
|
-
|
|
18788
|
+
resolve24({ ok: exitCode === 0, exitCode, display: plan.display });
|
|
18035
18789
|
});
|
|
18036
18790
|
});
|
|
18037
18791
|
}
|
|
@@ -18280,9 +19034,11 @@ sessionCommand.command("show <id>").description("Show a saved session transcript
|
|
|
18280
19034
|
else process.stdout.write(sessionMarkdown(session));
|
|
18281
19035
|
});
|
|
18282
19036
|
sessionCommand.command("delete <id>").description("Delete a saved session").option("-w, --workspace <path>", "workspace root").option("--yes", "skip confirmation").action(async (id, options) => {
|
|
18283
|
-
const
|
|
19037
|
+
const workspace = workspaceOption(options.workspace);
|
|
19038
|
+
const store = new SessionStore(workspace);
|
|
18284
19039
|
const session = await requireSessionSelector(store, id);
|
|
18285
19040
|
if (!options.yes && !await confirm(`Delete session ${session.id.slice(0, 8)}?`)) return;
|
|
19041
|
+
await new ToolArtifactStore(workspace).removeSession(session.id);
|
|
18286
19042
|
await store.remove(session.id);
|
|
18287
19043
|
process.stdout.write(`Deleted ${session.id}
|
|
18288
19044
|
`);
|
|
@@ -18291,7 +19047,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
|
|
|
18291
19047
|
const store = new SessionStore(workspaceOption(options.workspace));
|
|
18292
19048
|
const session = await requireSessionSelector(store, id);
|
|
18293
19049
|
const markdown = sessionMarkdown(session);
|
|
18294
|
-
if (options.output) await writeFile3(
|
|
19050
|
+
if (options.output) await writeFile3(resolve23(options.output), markdown, "utf8");
|
|
18295
19051
|
else process.stdout.write(markdown);
|
|
18296
19052
|
});
|
|
18297
19053
|
var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
|
|
@@ -18668,7 +19424,7 @@ async function runChat(prompts, options) {
|
|
|
18668
19424
|
const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
|
|
18669
19425
|
const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
|
|
18670
19426
|
if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
|
|
18671
|
-
const workspace =
|
|
19427
|
+
const workspace = resolve23(options.workspace);
|
|
18672
19428
|
let config = await runtimeConfig(workspace, options);
|
|
18673
19429
|
let completedOnboarding = false;
|
|
18674
19430
|
if (!shouldPrint && needsFirstRunOnboarding(config)) {
|
|
@@ -18893,14 +19649,14 @@ async function runAgentSetup(options) {
|
|
|
18893
19649
|
`);
|
|
18894
19650
|
}
|
|
18895
19651
|
async function runtimeConfig(workspaceInput, options) {
|
|
18896
|
-
const workspace =
|
|
19652
|
+
const workspace = resolve23(workspaceInput);
|
|
18897
19653
|
const loaded = await loadConfig(workspace, options.config, {
|
|
18898
19654
|
trustProjectConfig: options.trustProjectConfig === true
|
|
18899
19655
|
});
|
|
18900
19656
|
const roots = [
|
|
18901
19657
|
workspace,
|
|
18902
19658
|
...loaded.workspaceRoots,
|
|
18903
|
-
...(options.addWorkspace ?? []).map((root) =>
|
|
19659
|
+
...(options.addWorkspace ?? []).map((root) => resolve23(workspace, root))
|
|
18904
19660
|
];
|
|
18905
19661
|
const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
|
|
18906
19662
|
return {
|
|
@@ -19056,7 +19812,7 @@ function collect(value, previous) {
|
|
|
19056
19812
|
}
|
|
19057
19813
|
function workspaceOption(value) {
|
|
19058
19814
|
const rootOptions = program.opts();
|
|
19059
|
-
return
|
|
19815
|
+
return resolve23(value ?? rootOptions.workspace ?? process.cwd());
|
|
19060
19816
|
}
|
|
19061
19817
|
function runtimeOptions(options) {
|
|
19062
19818
|
const root = program.opts();
|