@skein-code/cli 0.3.9 → 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 +2060 -657
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +31 -2
- package/docs/NEXT_STEPS.md +11 -8
- 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,
|
|
@@ -3279,7 +3279,7 @@ function formatContextHits(hits, roots) {
|
|
|
3279
3279
|
}
|
|
3280
3280
|
|
|
3281
3281
|
// src/agent/runner.ts
|
|
3282
|
-
import { randomUUID as
|
|
3282
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
3283
3283
|
|
|
3284
3284
|
// src/context/manager.ts
|
|
3285
3285
|
var RECENT_TURN_RESERVE = 3;
|
|
@@ -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"]),
|
|
@@ -4974,6 +4990,19 @@ var lastRunSchema = z5.object({
|
|
|
4974
4990
|
checks: z5.array(verificationEvidenceSchema),
|
|
4975
4991
|
detail: z5.string(),
|
|
4976
4992
|
mutationTracking: z5.enum(["complete", "unknown"]).optional(),
|
|
4993
|
+
acceptance: z5.object({
|
|
4994
|
+
state: z5.enum(["draft", "active", "satisfied", "blocked"]),
|
|
4995
|
+
total: z5.number().int().nonnegative(),
|
|
4996
|
+
satisfied: z5.number().int().nonnegative(),
|
|
4997
|
+
pending: z5.number().int().nonnegative(),
|
|
4998
|
+
blocked: z5.number().int().nonnegative(),
|
|
4999
|
+
missingVerification: z5.array(z5.string()),
|
|
5000
|
+
unresolved: z5.array(z5.object({
|
|
5001
|
+
id: z5.string(),
|
|
5002
|
+
description: z5.string(),
|
|
5003
|
+
status: z5.enum(["pending", "satisfied", "blocked"])
|
|
5004
|
+
}).strict())
|
|
5005
|
+
}).strict().optional(),
|
|
4977
5006
|
reason: z5.string(),
|
|
4978
5007
|
finishedAt: z5.string()
|
|
4979
5008
|
}).strict();
|
|
@@ -4986,6 +5015,27 @@ var workingMemorySchema = z5.object({
|
|
|
4986
5015
|
relevantFiles: z5.array(z5.string()),
|
|
4987
5016
|
lastUpdatedAt: z5.string()
|
|
4988
5017
|
}).strict();
|
|
5018
|
+
var taskContractCriterionSchema = z5.object({
|
|
5019
|
+
id: z5.string().min(1).max(128),
|
|
5020
|
+
description: z5.string().min(1).max(2e3),
|
|
5021
|
+
required: z5.boolean(),
|
|
5022
|
+
status: z5.enum(["pending", "satisfied", "blocked"]),
|
|
5023
|
+
evidenceRefs: z5.array(z5.string().min(1).max(256)).max(64),
|
|
5024
|
+
note: z5.string().max(2e3).optional()
|
|
5025
|
+
}).strict();
|
|
5026
|
+
var taskContractSchema = z5.object({
|
|
5027
|
+
version: z5.literal(1),
|
|
5028
|
+
state: z5.enum(["draft", "active", "satisfied", "blocked"]),
|
|
5029
|
+
objective: z5.string().min(1).max(2e4),
|
|
5030
|
+
scope: z5.array(z5.string().min(1).max(2e3)).max(64),
|
|
5031
|
+
constraints: z5.array(z5.string().min(1).max(2e3)).max(64),
|
|
5032
|
+
nonGoals: z5.array(z5.string().min(1).max(2e3)).max(64),
|
|
5033
|
+
acceptanceCriteria: z5.array(taskContractCriterionSchema).min(1).max(64),
|
|
5034
|
+
verificationRequirements: z5.array(z5.string().min(1).max(2e3)).max(64),
|
|
5035
|
+
createdAt: z5.string(),
|
|
5036
|
+
updatedAt: z5.string(),
|
|
5037
|
+
auditBoundaryId: z5.string().min(1).max(128).optional()
|
|
5038
|
+
}).strict();
|
|
4989
5039
|
var sessionSchema = z5.object({
|
|
4990
5040
|
id: sessionIdSchema,
|
|
4991
5041
|
title: z5.string(),
|
|
@@ -5003,6 +5053,8 @@ var sessionSchema = z5.object({
|
|
|
5003
5053
|
compactedThroughMessageId: z5.string().optional(),
|
|
5004
5054
|
workingMemory: workingMemorySchema.optional(),
|
|
5005
5055
|
contextSources: z5.array(contextSourceSchema).max(64).optional(),
|
|
5056
|
+
toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
|
|
5057
|
+
taskContract: taskContractSchema.optional(),
|
|
5006
5058
|
lastRun: lastRunSchema.optional(),
|
|
5007
5059
|
usage: z5.object({
|
|
5008
5060
|
inputTokens: z5.number().nonnegative(),
|
|
@@ -5263,13 +5315,390 @@ async function exists2(path) {
|
|
|
5263
5315
|
}
|
|
5264
5316
|
}
|
|
5265
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
|
+
|
|
5266
5695
|
// src/tools/apply-patch.ts
|
|
5267
|
-
import { lstat as
|
|
5696
|
+
import { lstat as lstat13, readFile as readFile8, unlink as unlink4 } from "node:fs/promises";
|
|
5268
5697
|
import { applyPatch as applyUnifiedPatch, parsePatch } from "diff";
|
|
5269
|
-
import { z as
|
|
5270
|
-
var inputSchema2 =
|
|
5271
|
-
patch:
|
|
5272
|
-
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()
|
|
5273
5702
|
}).strict();
|
|
5274
5703
|
var applyPatchTool = {
|
|
5275
5704
|
definition: {
|
|
@@ -5525,10 +5954,10 @@ function findSequence(haystack, needle, hint, minimum) {
|
|
|
5525
5954
|
}
|
|
5526
5955
|
async function readSnapshot2(path) {
|
|
5527
5956
|
try {
|
|
5528
|
-
const info = await
|
|
5957
|
+
const info = await lstat13(path);
|
|
5529
5958
|
if (info.isSymbolicLink()) throw new Error(`Refusing to patch a symbolic link: ${path}`);
|
|
5530
5959
|
if (!info.isFile()) throw new Error(`Patch target is not a regular file: ${path}`);
|
|
5531
|
-
return { before: await
|
|
5960
|
+
return { before: await readFile8(path), mode: info.mode };
|
|
5532
5961
|
} catch (error) {
|
|
5533
5962
|
if (error.code === "ENOENT") return { before: null };
|
|
5534
5963
|
throw error;
|
|
@@ -5577,13 +6006,13 @@ function buffersEqual(left, right) {
|
|
|
5577
6006
|
}
|
|
5578
6007
|
|
|
5579
6008
|
// src/tools/git.ts
|
|
5580
|
-
import { join as
|
|
5581
|
-
import { z as
|
|
5582
|
-
var inputSchema3 =
|
|
5583
|
-
args:
|
|
5584
|
-
cwd:
|
|
5585
|
-
timeout_ms:
|
|
5586
|
-
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()
|
|
5587
6016
|
}).strict();
|
|
5588
6017
|
var networkCommands = /* @__PURE__ */ new Set([
|
|
5589
6018
|
"clone",
|
|
@@ -5796,7 +6225,7 @@ var gitTool = {
|
|
|
5796
6225
|
for (const candidate of explicit) {
|
|
5797
6226
|
if (!candidate || candidate.startsWith("-")) continue;
|
|
5798
6227
|
try {
|
|
5799
|
-
paths.add(await context.workspace.resolvePath(
|
|
6228
|
+
paths.add(await context.workspace.resolvePath(join12(cwd, candidate), { allowMissing: true }));
|
|
5800
6229
|
} catch {
|
|
5801
6230
|
}
|
|
5802
6231
|
}
|
|
@@ -5817,7 +6246,7 @@ var gitTool = {
|
|
|
5817
6246
|
const candidates = candidate.includes(" -> ") ? candidate.split(" -> ") : [candidate];
|
|
5818
6247
|
for (const value of candidates) {
|
|
5819
6248
|
try {
|
|
5820
|
-
paths.add(await context.workspace.resolvePath(
|
|
6249
|
+
paths.add(await context.workspace.resolvePath(join12(cwd, value), { allowMissing: true }));
|
|
5821
6250
|
} catch {
|
|
5822
6251
|
}
|
|
5823
6252
|
}
|
|
@@ -5898,7 +6327,7 @@ async function collectGitChanges(before, after, runtime, cwd, context) {
|
|
|
5898
6327
|
const paths = [];
|
|
5899
6328
|
for (const candidate of [...candidates].slice(0, 2e3)) {
|
|
5900
6329
|
try {
|
|
5901
|
-
paths.push(await context.workspace.resolvePath(
|
|
6330
|
+
paths.push(await context.workspace.resolvePath(join12(cwd, candidate), { allowMissing: true }));
|
|
5902
6331
|
} catch {
|
|
5903
6332
|
}
|
|
5904
6333
|
}
|
|
@@ -6014,7 +6443,7 @@ async function validateGitWorkspaceArguments(args, command2, cwd, context) {
|
|
|
6014
6443
|
if (/^(?:[a-z]+:\/\/|[a-z]+@[^:]+:)/i.test(candidate)) continue;
|
|
6015
6444
|
if (command2 === "clone" && index === 0 && !candidate) continue;
|
|
6016
6445
|
await context.workspace.resolvePath(
|
|
6017
|
-
candidate.startsWith("/") ? candidate :
|
|
6446
|
+
candidate.startsWith("/") ? candidate : join12(cwd, candidate),
|
|
6018
6447
|
{ allowMissing: true }
|
|
6019
6448
|
);
|
|
6020
6449
|
}
|
|
@@ -6067,17 +6496,17 @@ function positionalArguments(args, command2) {
|
|
|
6067
6496
|
}
|
|
6068
6497
|
|
|
6069
6498
|
// src/tools/list.ts
|
|
6070
|
-
import { lstat as
|
|
6071
|
-
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";
|
|
6072
6501
|
import fg3 from "fast-glob";
|
|
6073
|
-
import { z as
|
|
6074
|
-
var inputSchema4 =
|
|
6075
|
-
path:
|
|
6076
|
-
pattern:
|
|
6077
|
-
depth:
|
|
6078
|
-
include_hidden:
|
|
6079
|
-
include_directories:
|
|
6080
|
-
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()
|
|
6081
6510
|
}).strict();
|
|
6082
6511
|
var ignored = [
|
|
6083
6512
|
"**/.git/**",
|
|
@@ -6126,11 +6555,11 @@ var listFilesTool = {
|
|
|
6126
6555
|
for (const path of selected) {
|
|
6127
6556
|
let safePath;
|
|
6128
6557
|
try {
|
|
6129
|
-
safePath = await context.workspace.resolvePath(
|
|
6558
|
+
safePath = await context.workspace.resolvePath(resolve14(directory, path), { expect: "any" });
|
|
6130
6559
|
} catch {
|
|
6131
6560
|
continue;
|
|
6132
6561
|
}
|
|
6133
|
-
const info = await
|
|
6562
|
+
const info = await lstat14(safePath);
|
|
6134
6563
|
rendered.push(`${info.isDirectory() ? "d" : "f"} ${relative6(directory, safePath)}${info.isDirectory() ? "/" : ""}`);
|
|
6135
6564
|
}
|
|
6136
6565
|
const base = workspaceAliasPath(directory, context.workspace.roots);
|
|
@@ -6147,14 +6576,14 @@ var listFilesTool = {
|
|
|
6147
6576
|
};
|
|
6148
6577
|
|
|
6149
6578
|
// src/tools/read.ts
|
|
6150
|
-
import { readFile as
|
|
6151
|
-
import { z as
|
|
6152
|
-
var inputSchema5 =
|
|
6153
|
-
path:
|
|
6154
|
-
start_line:
|
|
6155
|
-
end_line:
|
|
6156
|
-
line_numbers:
|
|
6157
|
-
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()
|
|
6158
6587
|
}).strict();
|
|
6159
6588
|
var readFileTool = {
|
|
6160
6589
|
definition: {
|
|
@@ -6177,7 +6606,7 @@ var readFileTool = {
|
|
|
6177
6606
|
if (info.size > 1e7) {
|
|
6178
6607
|
throw new Error(`File is too large to read safely (${info.size} bytes).`);
|
|
6179
6608
|
}
|
|
6180
|
-
const buffer = await
|
|
6609
|
+
const buffer = await readFile9(path);
|
|
6181
6610
|
if (looksBinary(buffer)) throw new Error("Binary files cannot be read with read_file.");
|
|
6182
6611
|
const raw = buffer.toString("utf8");
|
|
6183
6612
|
const lines = raw.split("\n");
|
|
@@ -6221,6 +6650,73 @@ function truncateUtf8(input2, maxBytes) {
|
|
|
6221
6650
|
return buffer.subarray(0, end).toString("utf8");
|
|
6222
6651
|
}
|
|
6223
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
|
+
|
|
6224
6720
|
// src/tools/registry.ts
|
|
6225
6721
|
var ToolRegistry = class {
|
|
6226
6722
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -6268,19 +6764,19 @@ function assertToolName(name) {
|
|
|
6268
6764
|
}
|
|
6269
6765
|
|
|
6270
6766
|
// src/tools/search.ts
|
|
6271
|
-
import { readFile as
|
|
6272
|
-
import { resolve as
|
|
6767
|
+
import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
6768
|
+
import { resolve as resolve15 } from "node:path";
|
|
6273
6769
|
import fg4 from "fast-glob";
|
|
6274
|
-
import { z as
|
|
6275
|
-
var
|
|
6276
|
-
query:
|
|
6277
|
-
path:
|
|
6278
|
-
pattern:
|
|
6279
|
-
mode:
|
|
6280
|
-
literal:
|
|
6281
|
-
case_sensitive:
|
|
6282
|
-
context_lines:
|
|
6283
|
-
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()
|
|
6284
6780
|
}).strict();
|
|
6285
6781
|
var ignore = [
|
|
6286
6782
|
"**/.git/**",
|
|
@@ -6315,7 +6811,7 @@ var searchCodeTool = {
|
|
|
6315
6811
|
}, ["query"])
|
|
6316
6812
|
},
|
|
6317
6813
|
async execute(arguments_, context) {
|
|
6318
|
-
const input2 =
|
|
6814
|
+
const input2 = inputSchema7.parse(arguments_);
|
|
6319
6815
|
const directory = await context.workspace.resolveDirectory(input2.path ?? ".");
|
|
6320
6816
|
if ((input2.mode ?? "text") === "ranked") {
|
|
6321
6817
|
if (!context.contextEngine) throw new Error("Local ranked retrieval is unavailable.");
|
|
@@ -6339,7 +6835,7 @@ var searchCodeTool = {
|
|
|
6339
6835
|
if (results.length >= maxResults) break;
|
|
6340
6836
|
let path;
|
|
6341
6837
|
try {
|
|
6342
|
-
path = await context.workspace.resolvePath(
|
|
6838
|
+
path = await context.workspace.resolvePath(resolve15(directory, file), { expect: "file" });
|
|
6343
6839
|
} catch {
|
|
6344
6840
|
skipped += 1;
|
|
6345
6841
|
continue;
|
|
@@ -6349,7 +6845,7 @@ var searchCodeTool = {
|
|
|
6349
6845
|
skipped += 1;
|
|
6350
6846
|
continue;
|
|
6351
6847
|
}
|
|
6352
|
-
const buffer = await
|
|
6848
|
+
const buffer = await readFile10(path);
|
|
6353
6849
|
if (buffer.subarray(0, 8192).includes(0)) {
|
|
6354
6850
|
skipped += 1;
|
|
6355
6851
|
continue;
|
|
@@ -6421,17 +6917,17 @@ ${hit.content}`
|
|
|
6421
6917
|
}
|
|
6422
6918
|
|
|
6423
6919
|
// src/tools/shell.ts
|
|
6424
|
-
import { createHash as
|
|
6425
|
-
import { lstat as
|
|
6426
|
-
import { join as
|
|
6427
|
-
import { z as
|
|
6428
|
-
var
|
|
6429
|
-
command:
|
|
6430
|
-
cwd:
|
|
6431
|
-
timeout_ms:
|
|
6432
|
-
max_output_bytes:
|
|
6433
|
-
env:
|
|
6434
|
-
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()
|
|
6435
6931
|
}).strict();
|
|
6436
6932
|
var shellTool = {
|
|
6437
6933
|
definition: {
|
|
@@ -6448,7 +6944,7 @@ var shellTool = {
|
|
|
6448
6944
|
}, ["command"])
|
|
6449
6945
|
},
|
|
6450
6946
|
permissionCategories(arguments_) {
|
|
6451
|
-
const input2 =
|
|
6947
|
+
const input2 = inputSchema8.parse(arguments_);
|
|
6452
6948
|
validateEnvironment(input2.env);
|
|
6453
6949
|
return [
|
|
6454
6950
|
"shell",
|
|
@@ -6458,12 +6954,12 @@ var shellTool = {
|
|
|
6458
6954
|
];
|
|
6459
6955
|
},
|
|
6460
6956
|
async affectedPaths(arguments_, context) {
|
|
6461
|
-
const input2 =
|
|
6957
|
+
const input2 = inputSchema8.parse(arguments_);
|
|
6462
6958
|
if (!appearsToModifyWorkspace(input2.command)) return [];
|
|
6463
6959
|
return collectAffectedPaths(input2.command, input2.cwd ?? ".", context);
|
|
6464
6960
|
},
|
|
6465
6961
|
async execute(arguments_, context) {
|
|
6466
|
-
const input2 =
|
|
6962
|
+
const input2 = inputSchema8.parse(arguments_);
|
|
6467
6963
|
validateEnvironment(input2.env);
|
|
6468
6964
|
const cwd = await context.workspace.resolveDirectory(input2.cwd ?? ".");
|
|
6469
6965
|
const candidates = appearsToModifyWorkspace(input2.command) ? await collectAffectedPaths(input2.command, input2.cwd ?? ".", context) : [];
|
|
@@ -6503,7 +6999,8 @@ Execution interrupted: ${error instanceof Error ? error.message : String(error)}
|
|
|
6503
6999
|
result.stdout ? `stdout:
|
|
6504
7000
|
${result.stdout}` : "",
|
|
6505
7001
|
result.stderr ? `stderr:
|
|
6506
|
-
${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.` : ""
|
|
6507
7004
|
].filter(Boolean);
|
|
6508
7005
|
return {
|
|
6509
7006
|
ok: result.exitCode === 0 && !result.timedOut,
|
|
@@ -6513,6 +7010,9 @@ ${result.stderr}` : ""
|
|
|
6513
7010
|
exitCode: result.exitCode,
|
|
6514
7011
|
timedOut: result.timedOut,
|
|
6515
7012
|
durationMs: result.durationMs,
|
|
7013
|
+
stdoutBytes: result.stdoutBytes,
|
|
7014
|
+
stderrBytes: result.stderrBytes,
|
|
7015
|
+
sourceTruncated: result.stdoutTruncated || result.stderrTruncated,
|
|
6516
7016
|
changeTracking: candidates.length ? "targeted" : beforeWorkspace && afterWorkspace && beforeWorkspace.complete && afterWorkspace.complete ? "workspace-snapshot" : beforeWorkspace ? "unresolved" : "read-only"
|
|
6517
7017
|
},
|
|
6518
7018
|
...changedFiles.length ? { changedFiles } : {}
|
|
@@ -6576,7 +7076,7 @@ async function collectAffectedPaths(command2, cwdInput, context) {
|
|
|
6576
7076
|
allowMissing: true
|
|
6577
7077
|
});
|
|
6578
7078
|
try {
|
|
6579
|
-
if ((await
|
|
7079
|
+
if ((await lstat15(path)).isDirectory()) continue;
|
|
6580
7080
|
} catch (error) {
|
|
6581
7081
|
if (error.code !== "ENOENT") throw error;
|
|
6582
7082
|
}
|
|
@@ -6604,7 +7104,7 @@ async function changedPaths(paths, before) {
|
|
|
6604
7104
|
}
|
|
6605
7105
|
async function snapshotPath(path) {
|
|
6606
7106
|
try {
|
|
6607
|
-
const info = await
|
|
7107
|
+
const info = await lstat15(path);
|
|
6608
7108
|
return { exists: true, size: info.size, mtimeMs: info.mtimeMs };
|
|
6609
7109
|
} catch (error) {
|
|
6610
7110
|
if (error.code === "ENOENT") return { exists: false };
|
|
@@ -6620,18 +7120,18 @@ async function captureWorkspaceSnapshot(roots) {
|
|
|
6620
7120
|
if (!discovered.complete) complete = false;
|
|
6621
7121
|
for (const path of discovered.files) {
|
|
6622
7122
|
try {
|
|
6623
|
-
const info = await
|
|
7123
|
+
const info = await lstat15(path);
|
|
6624
7124
|
if (!info.isFile() || info.isSymbolicLink()) continue;
|
|
6625
7125
|
if (files.size >= MAX_SNAPSHOT_FILES || info.size > MAX_SNAPSHOT_FILE_BYTES || totalBytes + info.size > MAX_SNAPSHOT_TOTAL_BYTES) {
|
|
6626
7126
|
complete = false;
|
|
6627
7127
|
continue;
|
|
6628
7128
|
}
|
|
6629
|
-
const content = await
|
|
7129
|
+
const content = await readFile11(path);
|
|
6630
7130
|
totalBytes += content.length;
|
|
6631
7131
|
files.set(path, {
|
|
6632
7132
|
size: info.size,
|
|
6633
7133
|
mtimeMs: info.mtimeMs,
|
|
6634
|
-
hash:
|
|
7134
|
+
hash: createHash8("sha256").update(content).digest("hex")
|
|
6635
7135
|
});
|
|
6636
7136
|
} catch (error) {
|
|
6637
7137
|
if (error.code !== "ENOENT") complete = false;
|
|
@@ -6652,14 +7152,14 @@ async function discoverSnapshotFiles(root) {
|
|
|
6652
7152
|
const directory = pending.pop();
|
|
6653
7153
|
if (!directory) break;
|
|
6654
7154
|
try {
|
|
6655
|
-
const entries = await
|
|
7155
|
+
const entries = await readdir5(directory, { withFileTypes: true, encoding: "utf8" });
|
|
6656
7156
|
for (const entry of entries) {
|
|
6657
7157
|
if (ignored2.has(entry.name) || /^\.skein\.(?:migrating|rollback)-/u.test(entry.name)) continue;
|
|
6658
7158
|
if (entry.isSymbolicLink()) {
|
|
6659
7159
|
complete = false;
|
|
6660
7160
|
continue;
|
|
6661
7161
|
}
|
|
6662
|
-
const path =
|
|
7162
|
+
const path = join13(directory, entry.name);
|
|
6663
7163
|
if (entry.isDirectory()) pending.push(path);
|
|
6664
7164
|
else if (entry.isFile()) files.push(path);
|
|
6665
7165
|
}
|
|
@@ -6673,18 +7173,18 @@ async function discoverSnapshotFiles(root) {
|
|
|
6673
7173
|
|
|
6674
7174
|
// src/tools/task.ts
|
|
6675
7175
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
6676
|
-
import { z as
|
|
6677
|
-
var taskSchema2 =
|
|
6678
|
-
id:
|
|
6679
|
-
title:
|
|
6680
|
-
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"])
|
|
6681
7181
|
}).strict();
|
|
6682
|
-
var
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
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()
|
|
6688
7188
|
]);
|
|
6689
7189
|
var taskTool = {
|
|
6690
7190
|
definition: {
|
|
@@ -6715,7 +7215,7 @@ var taskTool = {
|
|
|
6715
7215
|
}
|
|
6716
7216
|
},
|
|
6717
7217
|
async execute(arguments_, context) {
|
|
6718
|
-
const input2 =
|
|
7218
|
+
const input2 = inputSchema9.parse(arguments_);
|
|
6719
7219
|
const tasks = context.session.tasks;
|
|
6720
7220
|
switch (input2.action) {
|
|
6721
7221
|
case "list":
|
|
@@ -6751,126 +7251,97 @@ var taskTool = {
|
|
|
6751
7251
|
}
|
|
6752
7252
|
};
|
|
6753
7253
|
|
|
6754
|
-
// src/tools/
|
|
6755
|
-
import {
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
]);
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
"add_constraint",
|
|
6778
|
-
"add_decision",
|
|
6779
|
-
"add_question",
|
|
6780
|
-
"resolve_question",
|
|
6781
|
-
"add_file",
|
|
6782
|
-
"clear"
|
|
6783
|
-
] },
|
|
6784
|
-
value: { type: "string", description: "Text for a goal, focus, constraint, decision, or question." },
|
|
6785
|
-
path: { type: "string", description: "Workspace-relative relevant file path." },
|
|
6786
|
-
field: { type: "string", enum: ["constraints", "decisions", "openQuestions", "relevantFiles"] }
|
|
6787
|
-
}, ["action"])
|
|
6788
|
-
},
|
|
6789
|
-
async execute(arguments_, context) {
|
|
6790
|
-
const input2 = inputSchema9.parse(arguments_);
|
|
6791
|
-
const memory = context.session.workingMemory ?? emptyWorkingMemory2();
|
|
6792
|
-
switch (input2.action) {
|
|
6793
|
-
case "show":
|
|
6794
|
-
break;
|
|
6795
|
-
case "set_goal":
|
|
6796
|
-
memory.goal = clean(input2.value);
|
|
6797
|
-
break;
|
|
6798
|
-
case "set_focus":
|
|
6799
|
-
memory.focus = clean(input2.value);
|
|
6800
|
-
break;
|
|
6801
|
-
case "add_constraint":
|
|
6802
|
-
pushBounded2(memory.constraints, input2.value, 12);
|
|
6803
|
-
break;
|
|
6804
|
-
case "add_decision":
|
|
6805
|
-
pushBounded2(memory.decisions, input2.value, 12);
|
|
6806
|
-
break;
|
|
6807
|
-
case "add_question":
|
|
6808
|
-
pushBounded2(memory.openQuestions, input2.value, 12);
|
|
6809
|
-
break;
|
|
6810
|
-
case "resolve_question":
|
|
6811
|
-
removeMatching(memory.openQuestions, input2.value);
|
|
6812
|
-
break;
|
|
6813
|
-
case "add_file": {
|
|
6814
|
-
const safe = await context.workspace.resolvePath(input2.path, { expect: "any" });
|
|
6815
|
-
pushBounded2(memory.relevantFiles, safe, 24);
|
|
6816
|
-
break;
|
|
6817
|
-
}
|
|
6818
|
-
case "clear":
|
|
6819
|
-
memory[input2.field] = [];
|
|
6820
|
-
break;
|
|
6821
|
-
}
|
|
6822
|
-
memory.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6823
|
-
context.session.workingMemory = memory;
|
|
6824
|
-
return { content: render(memory), metadata: { workingMemory: { ...memory } } };
|
|
6825
|
-
}
|
|
6826
|
-
};
|
|
6827
|
-
function emptyWorkingMemory2() {
|
|
7254
|
+
// src/tools/task-contract.ts
|
|
7255
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
7256
|
+
import { z as z15 } from "zod";
|
|
7257
|
+
|
|
7258
|
+
// src/agent/task-contract.ts
|
|
7259
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
7260
|
+
var EXECUTABLE_INTENTS = /* @__PURE__ */ new Set(["debug", "refactor", "test", "implement"]);
|
|
7261
|
+
function shouldUseTaskContract(request, intent, existing) {
|
|
7262
|
+
if (existing && existing.state !== "satisfied") return true;
|
|
7263
|
+
if (!EXECUTABLE_INTENTS.has(intent)) return false;
|
|
7264
|
+
const value = request.trim();
|
|
7265
|
+
if (value.length < 180) return false;
|
|
7266
|
+
const requirements = value.split(/(?:\n+|[;;。]\s*|\b(?:and|then|also|plus)\b|以及|并且|同时|还要|另外)/iu).filter((item) => item.trim().length >= 12);
|
|
7267
|
+
const complexitySignals = [
|
|
7268
|
+
/\b(?:refactor|migrate|redesign|architecture|end[- ]to[- ]end|across|multiple)\b/iu,
|
|
7269
|
+
/重构|迁移|架构|完整|全链路|多个|跨模块|兼容|同时/iu,
|
|
7270
|
+
/(?:^|\n)\s*(?:[-*]|\d+[.)、])\s+/mu
|
|
7271
|
+
].filter((pattern) => pattern.test(value)).length;
|
|
7272
|
+
return requirements.length >= 3 || complexitySignals >= 2 || value.length >= 500;
|
|
7273
|
+
}
|
|
7274
|
+
function createDraftTaskContract(request, auditBoundaryId) {
|
|
7275
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7276
|
+
const objective = compact(request, 2e3);
|
|
6828
7277
|
return {
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
7278
|
+
version: 1,
|
|
7279
|
+
state: "draft",
|
|
7280
|
+
objective,
|
|
7281
|
+
scope: ["Configured workspace roots and the files required by the objective."],
|
|
7282
|
+
constraints: [
|
|
7283
|
+
"Preserve unrelated user changes.",
|
|
7284
|
+
"Use successful tool evidence for acceptance claims."
|
|
7285
|
+
],
|
|
7286
|
+
nonGoals: [],
|
|
7287
|
+
acceptanceCriteria: [
|
|
7288
|
+
criterion("requested-outcome", `Implement the requested outcome: ${compact(request, 500)}`),
|
|
7289
|
+
criterion("verification", "Run the smallest relevant deterministic verification after the final mutation.")
|
|
7290
|
+
],
|
|
7291
|
+
verificationRequirements: [
|
|
7292
|
+
"Record at least one successful test, typecheck, lint, build, check, or diff check after the final mutation."
|
|
7293
|
+
],
|
|
7294
|
+
createdAt: now,
|
|
7295
|
+
updatedAt: now,
|
|
7296
|
+
...auditBoundaryId ? { auditBoundaryId } : {}
|
|
6836
7297
|
};
|
|
6837
7298
|
}
|
|
6838
|
-
function
|
|
6839
|
-
const
|
|
6840
|
-
const
|
|
6841
|
-
if (
|
|
6842
|
-
|
|
6843
|
-
|
|
7299
|
+
function successfulContractEvidence(session) {
|
|
7300
|
+
const refs = /* @__PURE__ */ new Set();
|
|
7301
|
+
const contract = session.taskContract;
|
|
7302
|
+
if (!contract) return refs;
|
|
7303
|
+
for (const event of eventsSinceContract(session.audit ?? [], contract)) {
|
|
7304
|
+
if (event.type !== "tool" || event.outcome !== "success") continue;
|
|
7305
|
+
if (event.tool === "task_contract" || event.tool === "task" || event.tool === "working_memory") continue;
|
|
7306
|
+
refs.add(event.id);
|
|
7307
|
+
refs.add(event.toolCallId);
|
|
7308
|
+
}
|
|
7309
|
+
return refs;
|
|
6844
7310
|
}
|
|
6845
|
-
function
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
if (candidate.toLocaleLowerCase() === query || candidate.toLocaleLowerCase().includes(query)) {
|
|
6850
|
-
values.splice(index, 1);
|
|
6851
|
-
}
|
|
7311
|
+
function eventsSinceContract(audit, contract) {
|
|
7312
|
+
if (contract.auditBoundaryId) {
|
|
7313
|
+
const boundary = audit.findIndex((event) => event.id === contract.auditBoundaryId);
|
|
7314
|
+
if (boundary >= 0) return audit.slice(boundary + 1);
|
|
6852
7315
|
}
|
|
7316
|
+
return audit.filter((event) => event.createdAt >= contract.createdAt);
|
|
6853
7317
|
}
|
|
6854
|
-
function
|
|
6855
|
-
|
|
7318
|
+
function refreshTaskContractState(contract) {
|
|
7319
|
+
const required = contract.acceptanceCriteria.filter((item) => item.required);
|
|
7320
|
+
contract.state = required.some((item) => item.status === "blocked") ? "blocked" : required.length > 0 && required.every((item) => item.status === "satisfied") ? "satisfied" : "active";
|
|
7321
|
+
contract.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6856
7322
|
}
|
|
6857
|
-
function
|
|
6858
|
-
return
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
7323
|
+
function criterion(id, description) {
|
|
7324
|
+
return {
|
|
7325
|
+
id: `${id}-${randomUUID10().slice(0, 8)}`,
|
|
7326
|
+
description,
|
|
7327
|
+
required: true,
|
|
7328
|
+
status: "pending",
|
|
7329
|
+
evidenceRefs: []
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7332
|
+
function compact(value, limit) {
|
|
7333
|
+
return value.trim().replace(/\s+/gu, " ").slice(0, limit);
|
|
6866
7334
|
}
|
|
6867
7335
|
|
|
7336
|
+
// src/agent/completion-gate.ts
|
|
7337
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
7338
|
+
|
|
6868
7339
|
// src/tools/permissions.ts
|
|
6869
|
-
import { createHash as
|
|
7340
|
+
import { createHash as createHash9, createHmac, randomBytes } from "node:crypto";
|
|
6870
7341
|
var permissionScopeSecret = randomBytes(32);
|
|
6871
7342
|
function permissionKey(call, category) {
|
|
6872
7343
|
const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
|
|
6873
|
-
const digest =
|
|
7344
|
+
const digest = createHash9("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
|
|
6874
7345
|
return `${category}:${call.name}:${digest}`;
|
|
6875
7346
|
}
|
|
6876
7347
|
function permissionTarget(call) {
|
|
@@ -7048,10 +7519,513 @@ function escapeRegExp(value) {
|
|
|
7048
7519
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7049
7520
|
}
|
|
7050
7521
|
|
|
7522
|
+
// src/agent/completion-gate.ts
|
|
7523
|
+
function captureVerification(call, result, changeSequence, configuredCommands) {
|
|
7524
|
+
if (call.name !== "shell" && call.name !== "git") return void 0;
|
|
7525
|
+
const command2 = commandForCall(call);
|
|
7526
|
+
if (!command2) return void 0;
|
|
7527
|
+
const normalized = normalizeCommand2(command2);
|
|
7528
|
+
const configured = new Set(configuredCommands.map(normalizeCommand2));
|
|
7529
|
+
const kind = configured.has(normalized) ? "configured" : classifyVerificationCommand(normalized);
|
|
7530
|
+
if (!kind) return void 0;
|
|
7531
|
+
return {
|
|
7532
|
+
toolCallId: call.id,
|
|
7533
|
+
tool: call.name,
|
|
7534
|
+
command: redactCommand(command2),
|
|
7535
|
+
kind,
|
|
7536
|
+
ok: result.ok,
|
|
7537
|
+
changeSequence,
|
|
7538
|
+
commandKey: createHash10("sha256").update(normalized).digest("hex")
|
|
7539
|
+
};
|
|
7540
|
+
}
|
|
7541
|
+
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = []) {
|
|
7542
|
+
const files = [...new Set(changedFiles)];
|
|
7543
|
+
const acceptance = taskContract ? buildAcceptance(taskContract, audit, evidence, currentChangeSequence) : void 0;
|
|
7544
|
+
if (mutationTracking === "unknown") {
|
|
7545
|
+
return {
|
|
7546
|
+
status: "unverified",
|
|
7547
|
+
changedFiles: files,
|
|
7548
|
+
checks: [],
|
|
7549
|
+
detail: files.length ? `Workspace changes were observed, but a dynamic shell command prevented complete mutation tracking for ${fileCount(files.length)}.` : "A dynamic shell command may have changed workspace files, but reliable mutation tracking was unavailable.",
|
|
7550
|
+
mutationTracking,
|
|
7551
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {}
|
|
7552
|
+
};
|
|
7553
|
+
}
|
|
7554
|
+
if (!files.length) {
|
|
7555
|
+
if (acceptance && acceptanceUnresolved(acceptance)) {
|
|
7556
|
+
return {
|
|
7557
|
+
status: "unverified",
|
|
7558
|
+
changedFiles: [],
|
|
7559
|
+
checks: [],
|
|
7560
|
+
detail: acceptanceDetail(acceptance),
|
|
7561
|
+
acceptance: acceptanceForCompletion(acceptance, "unverified")
|
|
7562
|
+
};
|
|
7563
|
+
}
|
|
7564
|
+
return {
|
|
7565
|
+
status: "no_changes",
|
|
7566
|
+
changedFiles: [],
|
|
7567
|
+
checks: [],
|
|
7568
|
+
detail: "No workspace files changed in this run.",
|
|
7569
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "no_changes") } : {}
|
|
7570
|
+
};
|
|
7571
|
+
}
|
|
7572
|
+
const latestByCommand = /* @__PURE__ */ new Map();
|
|
7573
|
+
for (const item of evidence) {
|
|
7574
|
+
if (item.changeSequence === currentChangeSequence) {
|
|
7575
|
+
latestByCommand.set(item.commandKey, item);
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
const checks = [...latestByCommand.values()].map(publicEvidence);
|
|
7579
|
+
if (!checks.length) {
|
|
7580
|
+
return {
|
|
7581
|
+
status: "unverified",
|
|
7582
|
+
changedFiles: files,
|
|
7583
|
+
checks,
|
|
7584
|
+
detail: `No successful verification was recorded after the last change to ${fileCount(files.length)}.`,
|
|
7585
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {}
|
|
7586
|
+
};
|
|
7587
|
+
}
|
|
7588
|
+
const failures = checks.filter((check) => !check.ok);
|
|
7589
|
+
if (failures.length) {
|
|
7590
|
+
return {
|
|
7591
|
+
status: "verification_failed",
|
|
7592
|
+
changedFiles: files,
|
|
7593
|
+
checks,
|
|
7594
|
+
detail: `${failures.length} of ${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} failed.`,
|
|
7595
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verification_failed") } : {}
|
|
7596
|
+
};
|
|
7597
|
+
}
|
|
7598
|
+
if (acceptance && acceptanceUnresolved(acceptance)) {
|
|
7599
|
+
return {
|
|
7600
|
+
status: "unverified",
|
|
7601
|
+
changedFiles: files,
|
|
7602
|
+
checks,
|
|
7603
|
+
detail: acceptanceDetail(acceptance),
|
|
7604
|
+
acceptance: acceptanceForCompletion(acceptance, "unverified")
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
return {
|
|
7608
|
+
status: "verified",
|
|
7609
|
+
changedFiles: files,
|
|
7610
|
+
checks,
|
|
7611
|
+
detail: `${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} passed for ${fileCount(files.length)}.`,
|
|
7612
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verified") } : {}
|
|
7613
|
+
};
|
|
7614
|
+
}
|
|
7615
|
+
function completionRecoveryDirective(completion) {
|
|
7616
|
+
if (completion.acceptance && acceptanceUnresolved(completion.acceptance)) {
|
|
7617
|
+
const unresolved = completion.acceptance.unresolved.map((item) => `- [${item.status}] ${item.id}: ${item.description}`).join("\n");
|
|
7618
|
+
const failed = completion.checks.filter((check) => !check.ok).map((check) => `- ${check.command} (tool call ${check.toolCallId})`).join("\n");
|
|
7619
|
+
return `<runtime-completion-gate status="acceptance_unresolved" authorization="none">
|
|
7620
|
+
The run cannot be marked complete while required Task Contract criteria remain unresolved:
|
|
7621
|
+
${unresolved || "- Acceptance criteria are satisfied."}${completion.acceptance.missingVerification.length ? `
|
|
7622
|
+
Missing required verification:
|
|
7623
|
+
${completion.acceptance.missingVerification.map((item) => `- ${item}`).join("\n")}` : ""}${failed ? `
|
|
7624
|
+
Current failed checks:
|
|
7625
|
+
${failed}` : ""}
|
|
7626
|
+
Complete or explicitly block only these criteria. Mark a criterion satisfied through task_contract only with successful tool audit evidence or a successful tool-call id. Run the smallest missing verification after the final mutation. Do not repeat the final summary or claim acceptance from prose alone.
|
|
7627
|
+
</runtime-completion-gate>`;
|
|
7628
|
+
}
|
|
7629
|
+
if (completion.status === "verification_failed") {
|
|
7630
|
+
const failed = completion.checks.filter((check) => !check.ok).map((check) => `- ${check.command} (tool call ${check.toolCallId})`).join("\n");
|
|
7631
|
+
return `<runtime-completion-gate status="verification_failed" authorization="none">
|
|
7632
|
+
The run cannot be marked complete because current verification failed:
|
|
7633
|
+
${failed}
|
|
7634
|
+
Inspect the recorded tool output, correct the underlying problem, and rerun the smallest relevant check. Do not repeat the final summary or claim success without a new successful tool result. If the failure cannot be resolved safely, state the exact blocker and leave the result unverified.
|
|
7635
|
+
</runtime-completion-gate>`;
|
|
7636
|
+
}
|
|
7637
|
+
const changeSummary = completion.mutationTracking === "unknown" ? "A dynamic shell command could not be mapped to a complete set of workspace changes." : `The run changed ${fileCount(completion.changedFiles.length)}, but no successful verification command was recorded after the last change.`;
|
|
7638
|
+
return `<runtime-completion-gate status="unverified" authorization="none">
|
|
7639
|
+
${changeSummary}
|
|
7640
|
+
Run the smallest relevant test, typecheck, lint, build, or git diff --check now. Do not repeat the final summary or claim a check passed without a successful tool result. If verification cannot be run safely, state the exact reason and leave the result unverified.
|
|
7641
|
+
</runtime-completion-gate>`;
|
|
7642
|
+
}
|
|
7643
|
+
function buildAcceptance(contract, audit, evidence, currentChangeSequence) {
|
|
7644
|
+
const contractEvents = eventsSinceContract(audit, contract);
|
|
7645
|
+
const successfulEvents = contractEvents.filter(
|
|
7646
|
+
(event) => event.type === "tool" && event.outcome === "success" && event.tool !== "task_contract" && event.tool !== "task" && event.tool !== "working_memory"
|
|
7647
|
+
);
|
|
7648
|
+
const latestMutationIndex = contractEvents.reduce((latest, event, index) => {
|
|
7649
|
+
const changedFiles = event.metadata?.changedFiles;
|
|
7650
|
+
return Array.isArray(changedFiles) && changedFiles.length > 0 ? index : latest;
|
|
7651
|
+
}, -1);
|
|
7652
|
+
const successfulRefs = /* @__PURE__ */ new Map();
|
|
7653
|
+
for (const event of successfulEvents) {
|
|
7654
|
+
const index = contractEvents.indexOf(event);
|
|
7655
|
+
successfulRefs.set(event.id, { event, index });
|
|
7656
|
+
successfulRefs.set(event.toolCallId, { event, index });
|
|
7657
|
+
}
|
|
7658
|
+
const required = contract.acceptanceCriteria.filter((item) => item.required);
|
|
7659
|
+
const normalized = required.map((item) => {
|
|
7660
|
+
const evidenceValid = item.evidenceRefs.some((ref) => {
|
|
7661
|
+
const event = successfulRefs.get(ref);
|
|
7662
|
+
return event !== void 0 && event.index >= latestMutationIndex;
|
|
7663
|
+
});
|
|
7664
|
+
const status = item.status === "satisfied" && !evidenceValid ? "pending" : item.status;
|
|
7665
|
+
return { id: item.id, description: item.description, status };
|
|
7666
|
+
});
|
|
7667
|
+
const satisfied = normalized.filter((item) => item.status === "satisfied").length;
|
|
7668
|
+
const pending = normalized.filter((item) => item.status === "pending").length;
|
|
7669
|
+
const blocked = normalized.filter((item) => item.status === "blocked").length;
|
|
7670
|
+
const currentChecks = evidence.filter((item) => item.changeSequence === currentChangeSequence && item.ok);
|
|
7671
|
+
const requirements = contract.verificationRequirements.length ? contract.verificationRequirements : ["Record at least one successful test, typecheck, lint, build, check, or diff check after the final mutation."];
|
|
7672
|
+
const missingVerification = requirements.filter(
|
|
7673
|
+
(requirement) => !verificationRequirementMet(requirement, currentChecks)
|
|
7674
|
+
);
|
|
7675
|
+
return {
|
|
7676
|
+
state: contract.state === "draft" ? "draft" : blocked > 0 ? "blocked" : pending > 0 || missingVerification.length > 0 ? "active" : "satisfied",
|
|
7677
|
+
total: normalized.length,
|
|
7678
|
+
satisfied,
|
|
7679
|
+
pending,
|
|
7680
|
+
blocked,
|
|
7681
|
+
missingVerification,
|
|
7682
|
+
unresolved: normalized.filter((item) => item.status !== "satisfied")
|
|
7683
|
+
};
|
|
7684
|
+
}
|
|
7685
|
+
function acceptanceDetail(acceptance) {
|
|
7686
|
+
const parts = [
|
|
7687
|
+
acceptance.pending ? `${acceptance.pending} pending` : "",
|
|
7688
|
+
acceptance.blocked ? `${acceptance.blocked} blocked` : "",
|
|
7689
|
+
acceptance.missingVerification.length ? `${acceptance.missingVerification.length} verification requirements missing` : ""
|
|
7690
|
+
].filter(Boolean).join(" and ");
|
|
7691
|
+
const unresolved = acceptance.pending + acceptance.blocked;
|
|
7692
|
+
return `Task Contract acceptance is unresolved: ${parts} required ${unresolved === 1 ? "criterion" : "criteria"}.`;
|
|
7693
|
+
}
|
|
7694
|
+
function verificationRequirementMet(requirement, checks) {
|
|
7695
|
+
const normalized = normalizeCommand2(requirement);
|
|
7696
|
+
const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
|
|
7697
|
+
if (broad) return checks.length > 0;
|
|
7698
|
+
const commandKey = createHash10("sha256").update(normalized).digest("hex");
|
|
7699
|
+
return checks.some((item) => item.commandKey === commandKey);
|
|
7700
|
+
}
|
|
7701
|
+
function acceptanceUnresolved(acceptance) {
|
|
7702
|
+
return acceptance.pending > 0 || acceptance.blocked > 0 || acceptance.missingVerification.length > 0;
|
|
7703
|
+
}
|
|
7704
|
+
function acceptanceForCompletion(acceptance, status) {
|
|
7705
|
+
if (acceptance.state === "draft" || acceptance.state === "blocked" || status === "verified" || status === "no_changes") return acceptance;
|
|
7706
|
+
return acceptance.state === "active" ? acceptance : { ...acceptance, state: "active" };
|
|
7707
|
+
}
|
|
7708
|
+
function classifyVerificationCommand(command2) {
|
|
7709
|
+
const normalized = normalizeCommand2(command2).toLocaleLowerCase();
|
|
7710
|
+
const segments = normalized.split(/\s*(?:&&|\|\||;)\s*/u).filter(Boolean);
|
|
7711
|
+
if (segments.length > 1) {
|
|
7712
|
+
const kinds = segments.map(classifySingleVerificationCommand);
|
|
7713
|
+
if (kinds.every((kind) => kind !== void 0)) {
|
|
7714
|
+
return kinds.every((kind) => kind === kinds[0]) ? kinds[0] : "check";
|
|
7715
|
+
}
|
|
7716
|
+
return void 0;
|
|
7717
|
+
}
|
|
7718
|
+
return classifySingleVerificationCommand(normalized);
|
|
7719
|
+
}
|
|
7720
|
+
function classifySingleVerificationCommand(command2) {
|
|
7721
|
+
const value = command2.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s]+)\s+)+/u, "");
|
|
7722
|
+
if (/^git\s+diff\b.*(?:^|\s)--check(?:\s|$)/u.test(value)) return "diff";
|
|
7723
|
+
if (/^(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test(?::[^\s]+)?|test|vitest|jest)(?:\s|$)/u.test(value) || /^(?:npx|pnpx|bunx)\s+(?:vitest|jest)(?:\s|$)/u.test(value) || /^(?:python(?:\d+(?:\.\d+)*)?\s+-m\s+)?pytest(?:\s|$)/u.test(value) || /^(?:cargo|go|dotnet|mvn|mvnw|gradle|gradlew)\s+(?:test|verify)(?:\s|$)/u.test(value) || /^(?:make|just|task)\s+(?:test|verify)(?:\s|$)/u.test(value) || /^node\s+--test(?:\s|$)/u.test(value)) return "test";
|
|
7724
|
+
if (/^(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:typecheck|type-check|check:types)(?:\s|$)/u.test(value) || /^(?:npx|pnpx|bunx)\s+(?:tsc|pyright|mypy)(?:\s|$)/u.test(value) || /^(?:tsc|pyright|mypy)(?:\s|$)/u.test(value) || /^cargo\s+check(?:\s|$)/u.test(value)) return "typecheck";
|
|
7725
|
+
if (/^(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?lint(?:\s|$)/u.test(value) || /^(?:npx|pnpx|bunx)\s+(?:eslint|biome|ruff)(?:\s|$)/u.test(value) || /^(?:eslint|biome\s+check|ruff\s+check|cargo\s+clippy)(?:\s|$)/u.test(value)) return "lint";
|
|
7726
|
+
if (/^(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:build|compile)(?:\s|$)/u.test(value) || /^(?:cargo|go|dotnet|mvn|mvnw|gradle|gradlew)\s+build(?:\s|$)/u.test(value) || /^(?:make|just|task)\s+(?:build|compile)(?:\s|$)/u.test(value)) return "build";
|
|
7727
|
+
if (/^(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?check(?:\s|$)/u.test(value) || /^(?:make|just|task|gradle|gradlew)\s+check(?:\s|$)/u.test(value)) return "check";
|
|
7728
|
+
return void 0;
|
|
7729
|
+
}
|
|
7730
|
+
function publicEvidence(item) {
|
|
7731
|
+
return {
|
|
7732
|
+
toolCallId: item.toolCallId,
|
|
7733
|
+
tool: item.tool,
|
|
7734
|
+
command: item.command,
|
|
7735
|
+
kind: item.kind,
|
|
7736
|
+
ok: item.ok
|
|
7737
|
+
};
|
|
7738
|
+
}
|
|
7739
|
+
function normalizeCommand2(command2) {
|
|
7740
|
+
return command2.trim().replace(/[\t ]+/gu, " ");
|
|
7741
|
+
}
|
|
7742
|
+
function redactCommand(command2) {
|
|
7743
|
+
return command2.replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\b((?:api[_-]?key|access[_-]?token|authorization|password|secret|token))\s*=\s*([^\s]+)/giu, "$1=[redacted]").replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/gu, "[redacted-secret]").trim().slice(0, 2e3);
|
|
7744
|
+
}
|
|
7745
|
+
function fileCount(count) {
|
|
7746
|
+
return `${count} workspace ${count === 1 ? "file" : "files"}`;
|
|
7747
|
+
}
|
|
7748
|
+
|
|
7749
|
+
// src/tools/task-contract.ts
|
|
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()
|
|
7754
|
+
}).strict();
|
|
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)
|
|
7765
|
+
}).strict(),
|
|
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()
|
|
7773
|
+
}).strict()
|
|
7774
|
+
]);
|
|
7775
|
+
var taskContractTool = {
|
|
7776
|
+
definition: {
|
|
7777
|
+
name: "task_contract",
|
|
7778
|
+
description: "Define and update the durable acceptance contract for a complex executable request. Satisfied criteria require successful tool audit evidence IDs or tool-call IDs.",
|
|
7779
|
+
category: "read",
|
|
7780
|
+
inputSchema: {
|
|
7781
|
+
type: "object",
|
|
7782
|
+
properties: {
|
|
7783
|
+
action: { type: "string", enum: ["show", "replace", "activate", "update_criterion"] },
|
|
7784
|
+
objective: { type: "string" },
|
|
7785
|
+
scope: { type: "array", items: { type: "string" } },
|
|
7786
|
+
constraints: { type: "array", items: { type: "string" } },
|
|
7787
|
+
non_goals: { type: "array", items: { type: "string" } },
|
|
7788
|
+
acceptance_criteria: { type: "array", items: {
|
|
7789
|
+
type: "object",
|
|
7790
|
+
properties: {
|
|
7791
|
+
id: { type: "string" },
|
|
7792
|
+
description: { type: "string" },
|
|
7793
|
+
required: { type: "boolean" }
|
|
7794
|
+
},
|
|
7795
|
+
required: ["description"],
|
|
7796
|
+
additionalProperties: false
|
|
7797
|
+
} },
|
|
7798
|
+
verification_requirements: {
|
|
7799
|
+
type: "array",
|
|
7800
|
+
minItems: 1,
|
|
7801
|
+
items: { type: "string" },
|
|
7802
|
+
description: "Required verification commands or the standard any-successful-check requirement."
|
|
7803
|
+
},
|
|
7804
|
+
id: { type: "string" },
|
|
7805
|
+
status: { type: "string", enum: ["pending", "satisfied", "blocked"] },
|
|
7806
|
+
evidence_refs: { type: "array", items: { type: "string" } },
|
|
7807
|
+
note: { type: "string" }
|
|
7808
|
+
},
|
|
7809
|
+
required: ["action"],
|
|
7810
|
+
additionalProperties: false
|
|
7811
|
+
}
|
|
7812
|
+
},
|
|
7813
|
+
async execute(arguments_, context) {
|
|
7814
|
+
const input2 = inputSchema10.parse(arguments_);
|
|
7815
|
+
let contract = context.session.taskContract;
|
|
7816
|
+
if (!contract) throw new Error("No Task Contract is active for this session.");
|
|
7817
|
+
if (input2.action === "replace") {
|
|
7818
|
+
if (contract.state !== "draft") {
|
|
7819
|
+
throw new Error("Only a draft Task Contract can be replaced.");
|
|
7820
|
+
}
|
|
7821
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7822
|
+
const criteria = input2.acceptance_criteria.map((item) => ({
|
|
7823
|
+
id: item.id ?? `criterion-${randomUUID11().slice(0, 8)}`,
|
|
7824
|
+
description: item.description,
|
|
7825
|
+
required: item.required ?? true,
|
|
7826
|
+
status: "pending",
|
|
7827
|
+
evidenceRefs: []
|
|
7828
|
+
}));
|
|
7829
|
+
if (new Set(criteria.map((item) => item.id)).size !== criteria.length) {
|
|
7830
|
+
throw new Error("Task Contract criterion ids must be unique.");
|
|
7831
|
+
}
|
|
7832
|
+
if (!criteria.some((item) => item.required)) {
|
|
7833
|
+
throw new Error("Task Contract requires at least one required acceptance criterion.");
|
|
7834
|
+
}
|
|
7835
|
+
for (const existing of contract.acceptanceCriteria.filter((item) => item.required)) {
|
|
7836
|
+
const retained = criteria.find((item) => item.id === existing.id);
|
|
7837
|
+
if (!retained || !retained.required || retained.description !== existing.description) {
|
|
7838
|
+
throw new Error(`Required criterion cannot be removed or weakened: ${existing.id}`);
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
for (const existing of contract.verificationRequirements) {
|
|
7842
|
+
if (!input2.verification_requirements.includes(existing)) {
|
|
7843
|
+
throw new Error(`Verification requirement cannot be removed: ${existing}`);
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
for (const requirement of input2.verification_requirements) {
|
|
7847
|
+
validateVerificationRequirement(requirement);
|
|
7848
|
+
}
|
|
7849
|
+
Object.assign(contract, {
|
|
7850
|
+
version: 1,
|
|
7851
|
+
state: "active",
|
|
7852
|
+
objective: input2.objective,
|
|
7853
|
+
scope: input2.scope,
|
|
7854
|
+
constraints: input2.constraints,
|
|
7855
|
+
nonGoals: input2.non_goals,
|
|
7856
|
+
acceptanceCriteria: criteria,
|
|
7857
|
+
verificationRequirements: input2.verification_requirements,
|
|
7858
|
+
createdAt: contract.createdAt,
|
|
7859
|
+
updatedAt: now,
|
|
7860
|
+
...contract.auditBoundaryId ? { auditBoundaryId: contract.auditBoundaryId } : {}
|
|
7861
|
+
});
|
|
7862
|
+
} else if (input2.action === "activate") {
|
|
7863
|
+
refreshTaskContractState(contract);
|
|
7864
|
+
} else if (input2.action === "update_criterion") {
|
|
7865
|
+
if (contract.state === "draft") {
|
|
7866
|
+
throw new Error("Activate the draft Task Contract before updating acceptance criteria.");
|
|
7867
|
+
}
|
|
7868
|
+
const criterion2 = contract.acceptanceCriteria.find((item) => item.id === input2.id);
|
|
7869
|
+
if (!criterion2) throw new Error(`Unknown Task Contract criterion id: ${input2.id}`);
|
|
7870
|
+
const refs = [...new Set(input2.evidence_refs ?? [])];
|
|
7871
|
+
if (input2.status === "satisfied") {
|
|
7872
|
+
if (!refs.length) throw new Error("Satisfied criteria require at least one evidence_refs entry.");
|
|
7873
|
+
const valid = successfulContractEvidence(context.session);
|
|
7874
|
+
const invalid = refs.filter((ref) => !valid.has(ref));
|
|
7875
|
+
if (invalid.length) {
|
|
7876
|
+
throw new Error(`Unknown or unsuccessful evidence refs: ${invalid.join(", ")}`);
|
|
7877
|
+
}
|
|
7878
|
+
}
|
|
7879
|
+
criterion2.status = input2.status;
|
|
7880
|
+
criterion2.evidenceRefs = input2.status === "satisfied" ? refs : [];
|
|
7881
|
+
if (input2.note === void 0) delete criterion2.note;
|
|
7882
|
+
else criterion2.note = input2.note;
|
|
7883
|
+
refreshTaskContractState(contract);
|
|
7884
|
+
}
|
|
7885
|
+
return {
|
|
7886
|
+
content: formatContract(contract),
|
|
7887
|
+
metadata: { taskContract: structuredClone(contract) }
|
|
7888
|
+
};
|
|
7889
|
+
}
|
|
7890
|
+
};
|
|
7891
|
+
function formatContract(contract) {
|
|
7892
|
+
const satisfied = contract.acceptanceCriteria.filter((item) => item.status === "satisfied").length;
|
|
7893
|
+
const criteria = contract.acceptanceCriteria.map(
|
|
7894
|
+
(item) => `- [${item.status}] ${item.id}: ${item.description}`
|
|
7895
|
+
).join("\n");
|
|
7896
|
+
return `Task Contract: ${contract.state} (${satisfied}/${contract.acceptanceCriteria.length} satisfied)
|
|
7897
|
+
Objective: ${contract.objective}
|
|
7898
|
+
${criteria}`;
|
|
7899
|
+
}
|
|
7900
|
+
function validateVerificationRequirement(requirement) {
|
|
7901
|
+
if (/^Record at least one successful (.+) after the final mutation\.?$/iu.test(requirement)) return;
|
|
7902
|
+
if (/\b(?:api[_-]?key|authorization|password|secret|token)\s*=/iu.test(requirement)) {
|
|
7903
|
+
throw new Error("Verification requirements cannot contain credentials.");
|
|
7904
|
+
}
|
|
7905
|
+
if (!classifyVerificationCommand(requirement)) {
|
|
7906
|
+
throw new Error(`Verification requirement is not a recognized deterministic check: ${requirement}`);
|
|
7907
|
+
}
|
|
7908
|
+
}
|
|
7909
|
+
|
|
7910
|
+
// src/tools/working-memory.ts
|
|
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()
|
|
7922
|
+
]);
|
|
7923
|
+
var workingMemoryTool = {
|
|
7924
|
+
definition: {
|
|
7925
|
+
name: "working_memory",
|
|
7926
|
+
description: "Read or update short-term thread state: goal, focus, constraints, decisions, open questions, and relevant files. This state is temporary and is not durable memory.",
|
|
7927
|
+
category: "read",
|
|
7928
|
+
inputSchema: jsonSchema({
|
|
7929
|
+
action: { type: "string", enum: [
|
|
7930
|
+
"show",
|
|
7931
|
+
"set_goal",
|
|
7932
|
+
"set_focus",
|
|
7933
|
+
"add_constraint",
|
|
7934
|
+
"add_decision",
|
|
7935
|
+
"add_question",
|
|
7936
|
+
"resolve_question",
|
|
7937
|
+
"add_file",
|
|
7938
|
+
"clear"
|
|
7939
|
+
] },
|
|
7940
|
+
value: { type: "string", description: "Text for a goal, focus, constraint, decision, or question." },
|
|
7941
|
+
path: { type: "string", description: "Workspace-relative relevant file path." },
|
|
7942
|
+
field: { type: "string", enum: ["constraints", "decisions", "openQuestions", "relevantFiles"] }
|
|
7943
|
+
}, ["action"])
|
|
7944
|
+
},
|
|
7945
|
+
async execute(arguments_, context) {
|
|
7946
|
+
const input2 = inputSchema11.parse(arguments_);
|
|
7947
|
+
const memory = context.session.workingMemory ?? emptyWorkingMemory2();
|
|
7948
|
+
switch (input2.action) {
|
|
7949
|
+
case "show":
|
|
7950
|
+
break;
|
|
7951
|
+
case "set_goal":
|
|
7952
|
+
memory.goal = clean(input2.value);
|
|
7953
|
+
break;
|
|
7954
|
+
case "set_focus":
|
|
7955
|
+
memory.focus = clean(input2.value);
|
|
7956
|
+
break;
|
|
7957
|
+
case "add_constraint":
|
|
7958
|
+
pushBounded2(memory.constraints, input2.value, 12);
|
|
7959
|
+
break;
|
|
7960
|
+
case "add_decision":
|
|
7961
|
+
pushBounded2(memory.decisions, input2.value, 12);
|
|
7962
|
+
break;
|
|
7963
|
+
case "add_question":
|
|
7964
|
+
pushBounded2(memory.openQuestions, input2.value, 12);
|
|
7965
|
+
break;
|
|
7966
|
+
case "resolve_question":
|
|
7967
|
+
removeMatching(memory.openQuestions, input2.value);
|
|
7968
|
+
break;
|
|
7969
|
+
case "add_file": {
|
|
7970
|
+
const safe = await context.workspace.resolvePath(input2.path, { expect: "any" });
|
|
7971
|
+
pushBounded2(memory.relevantFiles, safe, 24);
|
|
7972
|
+
break;
|
|
7973
|
+
}
|
|
7974
|
+
case "clear":
|
|
7975
|
+
memory[input2.field] = [];
|
|
7976
|
+
break;
|
|
7977
|
+
}
|
|
7978
|
+
memory.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7979
|
+
context.session.workingMemory = memory;
|
|
7980
|
+
return { content: render(memory), metadata: { workingMemory: { ...memory } } };
|
|
7981
|
+
}
|
|
7982
|
+
};
|
|
7983
|
+
function emptyWorkingMemory2() {
|
|
7984
|
+
return {
|
|
7985
|
+
goal: "",
|
|
7986
|
+
focus: "",
|
|
7987
|
+
constraints: [],
|
|
7988
|
+
decisions: [],
|
|
7989
|
+
openQuestions: [],
|
|
7990
|
+
relevantFiles: [],
|
|
7991
|
+
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7992
|
+
};
|
|
7993
|
+
}
|
|
7994
|
+
function pushBounded2(values, value, limit) {
|
|
7995
|
+
const normalized = clean(value);
|
|
7996
|
+
const existing = values.indexOf(normalized);
|
|
7997
|
+
if (existing >= 0) values.splice(existing, 1);
|
|
7998
|
+
values.push(normalized);
|
|
7999
|
+
if (values.length > limit) values.splice(0, values.length - limit);
|
|
8000
|
+
}
|
|
8001
|
+
function removeMatching(values, value) {
|
|
8002
|
+
const query = value.toLocaleLowerCase().trim();
|
|
8003
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
8004
|
+
const candidate = values[index];
|
|
8005
|
+
if (candidate.toLocaleLowerCase() === query || candidate.toLocaleLowerCase().includes(query)) {
|
|
8006
|
+
values.splice(index, 1);
|
|
8007
|
+
}
|
|
8008
|
+
}
|
|
8009
|
+
}
|
|
8010
|
+
function clean(value) {
|
|
8011
|
+
return value.trim().replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/g, "[redacted-secret]").replace(/\b((?:api[_-]?key|access[_-]?token|auth(?:orization)?|password|secret))\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/\s+/g, " ").slice(0, 1e3);
|
|
8012
|
+
}
|
|
8013
|
+
function render(memory) {
|
|
8014
|
+
return [
|
|
8015
|
+
`Goal: ${memory.goal || "(none)"}`,
|
|
8016
|
+
`Focus: ${memory.focus || "(none)"}`,
|
|
8017
|
+
`Constraints: ${memory.constraints.length ? memory.constraints.join(" \xB7 ") : "(none)"}`,
|
|
8018
|
+
`Decisions: ${memory.decisions.length ? memory.decisions.join(" \xB7 ") : "(none)"}`,
|
|
8019
|
+
`Open questions: ${memory.openQuestions.length ? memory.openQuestions.join(" \xB7 ") : "(none)"}`,
|
|
8020
|
+
`Relevant files: ${memory.relevantFiles.length ? memory.relevantFiles.join(" \xB7 ") : "(none)"}`
|
|
8021
|
+
].join("\n");
|
|
8022
|
+
}
|
|
8023
|
+
|
|
7051
8024
|
// src/tools/index.ts
|
|
7052
8025
|
function createDefaultToolRegistry(_options = {}) {
|
|
7053
8026
|
return new ToolRegistry([
|
|
7054
8027
|
readFileTool,
|
|
8028
|
+
readToolArtifactTool,
|
|
7055
8029
|
listFilesTool,
|
|
7056
8030
|
searchCodeTool,
|
|
7057
8031
|
writeFileTool,
|
|
@@ -7059,6 +8033,7 @@ function createDefaultToolRegistry(_options = {}) {
|
|
|
7059
8033
|
shellTool,
|
|
7060
8034
|
gitTool,
|
|
7061
8035
|
taskTool,
|
|
8036
|
+
taskContractTool,
|
|
7062
8037
|
workingMemoryTool
|
|
7063
8038
|
]);
|
|
7064
8039
|
}
|
|
@@ -7098,11 +8073,19 @@ ${workspaceRules}` : ""}`;
|
|
|
7098
8073
|
}
|
|
7099
8074
|
function buildSessionStatePrompt(session) {
|
|
7100
8075
|
const tasks = session.tasks.length ? session.tasks.map((task) => `- [${task.status}] ${task.title}`).join("\n") : "- No active plan.";
|
|
8076
|
+
const contract = session.taskContract && session.taskContract.state !== "satisfied" ? `
|
|
8077
|
+
|
|
8078
|
+
Task Contract (${session.taskContract.state}):
|
|
8079
|
+
Objective: ${session.taskContract.objective}
|
|
8080
|
+
${session.taskContract.acceptanceCriteria.map(
|
|
8081
|
+
(item) => `- [${item.status}] ${item.id}: ${item.description}`
|
|
8082
|
+
).join("\n")}
|
|
8083
|
+
Use task_contract to activate or update acceptance. A satisfied criterion requires successful tool evidence.` : "";
|
|
7101
8084
|
return `<session-state scope="session" authorization="none">
|
|
7102
8085
|
This is mutable execution state, not a permission grant or higher-priority instruction.
|
|
7103
8086
|
|
|
7104
8087
|
Current saved plan:
|
|
7105
|
-
${tasks}
|
|
8088
|
+
${tasks}${contract}
|
|
7106
8089
|
</session-state>`;
|
|
7107
8090
|
}
|
|
7108
8091
|
function buildRetrievedContext(packed, mentions, primaryRoot, roots = [primaryRoot]) {
|
|
@@ -7170,136 +8153,328 @@ function escapeAttribute3(value) {
|
|
|
7170
8153
|
})[character] ?? character);
|
|
7171
8154
|
}
|
|
7172
8155
|
|
|
7173
|
-
// src/agent/
|
|
7174
|
-
import { createHash as
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
8156
|
+
// src/agent/tool-recovery.ts
|
|
8157
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
8158
|
+
var RETRY_BUDGET = {
|
|
8159
|
+
schema_input: 3,
|
|
8160
|
+
unknown_tool: 2,
|
|
8161
|
+
permission_denied: 0,
|
|
8162
|
+
command_exit: 3,
|
|
8163
|
+
timeout: 2,
|
|
8164
|
+
cancelled: 0,
|
|
8165
|
+
hook: 1,
|
|
8166
|
+
execution: 3,
|
|
8167
|
+
contract_required: 2
|
|
8168
|
+
};
|
|
8169
|
+
var REPAIR_HINT = {
|
|
8170
|
+
schema_input: "Correct the arguments to match the tool schema.",
|
|
8171
|
+
unknown_tool: "Choose a tool exposed for this turn.",
|
|
8172
|
+
permission_denied: "Do not retry unless the user or configuration changes permission.",
|
|
8173
|
+
command_exit: "Inspect the exit output, change the command or fix the cause, then retry once.",
|
|
8174
|
+
timeout: "Narrow the operation or increase its allowed timeout.",
|
|
8175
|
+
cancelled: "Stop work and preserve the current state.",
|
|
8176
|
+
hook: "Fix the hook failure before relying on the tool result.",
|
|
8177
|
+
execution: "Use the error detail to change the inputs or approach.",
|
|
8178
|
+
contract_required: "Activate the Task Contract before any workspace mutation."
|
|
8179
|
+
};
|
|
8180
|
+
var ToolRecoveryController = class {
|
|
8181
|
+
signatures = /* @__PURE__ */ new Map();
|
|
8182
|
+
classFailures = /* @__PURE__ */ new Map();
|
|
8183
|
+
toolClasses = /* @__PURE__ */ new Map();
|
|
8184
|
+
preflight(call) {
|
|
8185
|
+
const callKey = callSignature(call);
|
|
8186
|
+
const signatureState = this.signatures.get(callKey);
|
|
8187
|
+
if (signatureState && (signatureState.failures >= 2 || !isRetryable(signatureState.failureClass))) {
|
|
8188
|
+
return this.receipt(
|
|
8189
|
+
call,
|
|
8190
|
+
signatureState.failureClass,
|
|
8191
|
+
signatureState.failures + 1,
|
|
8192
|
+
true
|
|
8193
|
+
);
|
|
8194
|
+
}
|
|
8195
|
+
const lastClass = this.toolClasses.get(call.name);
|
|
8196
|
+
if (lastClass && (this.classFailures.get(lastClass) ?? 0) >= RETRY_BUDGET[lastClass] && RETRY_BUDGET[lastClass] > 0) {
|
|
8197
|
+
return this.receipt(call, lastClass, (signatureState?.failures ?? 0) + 1, true);
|
|
8198
|
+
}
|
|
8199
|
+
return void 0;
|
|
7203
8200
|
}
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
8201
|
+
recordFailure(call, failureClass) {
|
|
8202
|
+
const callKey = callSignature(call);
|
|
8203
|
+
const current = this.signatures.get(callKey);
|
|
8204
|
+
const failures = current?.failureClass === failureClass ? current.failures + 1 : 1;
|
|
8205
|
+
this.signatures.set(callKey, { failureClass, failures });
|
|
8206
|
+
this.toolClasses.set(call.name, failureClass);
|
|
8207
|
+
this.classFailures.set(failureClass, (this.classFailures.get(failureClass) ?? 0) + 1);
|
|
8208
|
+
return this.receipt(
|
|
8209
|
+
call,
|
|
8210
|
+
failureClass,
|
|
8211
|
+
failures,
|
|
8212
|
+
failures >= 2 || !isRetryable(failureClass)
|
|
8213
|
+
);
|
|
7211
8214
|
}
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
latestByCommand.set(item.commandKey, item);
|
|
7216
|
-
}
|
|
8215
|
+
recordSuccess(call) {
|
|
8216
|
+
this.signatures.delete(callSignature(call));
|
|
8217
|
+
this.toolClasses.delete(call.name);
|
|
7217
8218
|
}
|
|
7218
|
-
|
|
7219
|
-
|
|
8219
|
+
receipt(call, failureClass, attempt, circuitOpen) {
|
|
8220
|
+
const budget = RETRY_BUDGET[failureClass];
|
|
8221
|
+
const consumed = this.classFailures.get(failureClass) ?? 0;
|
|
7220
8222
|
return {
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
8223
|
+
class: failureClass,
|
|
8224
|
+
retryable: isRetryable(failureClass) && consumed < budget && !circuitOpen,
|
|
8225
|
+
repairHint: REPAIR_HINT[failureClass],
|
|
8226
|
+
attempt,
|
|
8227
|
+
remaining: Math.max(0, budget - consumed),
|
|
8228
|
+
circuitOpen,
|
|
8229
|
+
signature: failureSignature(call, failureClass)
|
|
7225
8230
|
};
|
|
7226
8231
|
}
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
8232
|
+
};
|
|
8233
|
+
function classifyToolFailure(result, fallback = "execution") {
|
|
8234
|
+
if (result.metadata?.failureClass && isFailureClass(result.metadata.failureClass)) {
|
|
8235
|
+
return result.metadata.failureClass;
|
|
8236
|
+
}
|
|
8237
|
+
if (result.metadata?.aborted === true) return "cancelled";
|
|
8238
|
+
if (result.metadata?.timedOut === true) return "timeout";
|
|
8239
|
+
if (result.metadata?.hookError) return "hook";
|
|
8240
|
+
if (typeof result.metadata?.exitCode === "number" && result.metadata.exitCode !== 0) {
|
|
8241
|
+
return "command_exit";
|
|
8242
|
+
}
|
|
8243
|
+
return fallback;
|
|
8244
|
+
}
|
|
8245
|
+
function formatFailureReceipt(receipt) {
|
|
8246
|
+
return `Failure: ${receipt.class}; attempt ${receipt.attempt}; ${receipt.remaining} retries remain; circuit ${receipt.circuitOpen ? "open" : "closed"}. Repair: ${receipt.repairHint}`;
|
|
8247
|
+
}
|
|
8248
|
+
function isRetryable(failureClass) {
|
|
8249
|
+
return RETRY_BUDGET[failureClass] > 0;
|
|
8250
|
+
}
|
|
8251
|
+
function callSignature(call) {
|
|
8252
|
+
return createHash11("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
|
|
8253
|
+
}
|
|
8254
|
+
function failureSignature(call, failureClass) {
|
|
8255
|
+
return createHash11("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
|
|
8256
|
+
}
|
|
8257
|
+
function redact(value, key = "") {
|
|
8258
|
+
if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
|
|
8259
|
+
if (Array.isArray(value)) return value.map((item) => redact(item));
|
|
8260
|
+
if (value && typeof value === "object") {
|
|
8261
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([name, item]) => [name, redact(item, name)]));
|
|
8262
|
+
}
|
|
8263
|
+
return value;
|
|
8264
|
+
}
|
|
8265
|
+
function stableJson(value) {
|
|
8266
|
+
return JSON.stringify(value) ?? String(value);
|
|
8267
|
+
}
|
|
8268
|
+
function isFailureClass(value) {
|
|
8269
|
+
return typeof value === "string" && Object.hasOwn(RETRY_BUDGET, value);
|
|
8270
|
+
}
|
|
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" };
|
|
7235
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);
|
|
7236
8319
|
return {
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
checks,
|
|
7240
|
-
detail: `${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} passed for ${fileCount(files.length)}.`
|
|
8320
|
+
content: formatReceipt(options, metadata, preview),
|
|
8321
|
+
metadata
|
|
7241
8322
|
};
|
|
7242
8323
|
}
|
|
7243
|
-
function
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
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);
|
|
7251
8337
|
}
|
|
7252
|
-
|
|
7253
|
-
return `<runtime-completion-gate status="unverified" authorization="none">
|
|
7254
|
-
${changeSummary}
|
|
7255
|
-
Run the smallest relevant test, typecheck, lint, build, or git diff --check now. Do not repeat the final summary or claim a check passed without a successful tool result. If verification cannot be run safely, state the exact reason and leave the result unverified.
|
|
7256
|
-
</runtime-completion-gate>`;
|
|
8338
|
+
return Math.max(1, Math.ceil(tokens2));
|
|
7257
8339
|
}
|
|
7258
|
-
function
|
|
7259
|
-
const
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
}
|
|
7266
|
-
|
|
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;
|
|
7267
8410
|
}
|
|
7268
|
-
return
|
|
8411
|
+
return value.slice(0, end);
|
|
7269
8412
|
}
|
|
7270
|
-
function
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
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);
|
|
7279
8454
|
}
|
|
7280
|
-
function
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
command: item.command,
|
|
7285
|
-
kind: item.kind,
|
|
7286
|
-
ok: item.ok
|
|
7287
|
-
};
|
|
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;
|
|
7288
8459
|
}
|
|
7289
|
-
function
|
|
7290
|
-
|
|
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;
|
|
7291
8463
|
}
|
|
7292
|
-
function
|
|
7293
|
-
return
|
|
8464
|
+
function clamp2(value, minimum, maximum) {
|
|
8465
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
7294
8466
|
}
|
|
7295
|
-
function
|
|
7296
|
-
|
|
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}`;
|
|
7297
8472
|
}
|
|
7298
8473
|
|
|
7299
8474
|
// src/agent/rules.ts
|
|
7300
8475
|
import { existsSync as existsSync2 } from "node:fs";
|
|
7301
|
-
import { join as
|
|
7302
|
-
import { lstat as
|
|
8476
|
+
import { join as join14 } from "node:path";
|
|
8477
|
+
import { lstat as lstat16, readFile as readFile12 } from "node:fs/promises";
|
|
7303
8478
|
var workspaceRulePaths = [
|
|
7304
8479
|
"AGENTS.md",
|
|
7305
8480
|
"CLAUDE.md",
|
|
@@ -7308,14 +8483,14 @@ var workspaceRulePaths = [
|
|
|
7308
8483
|
];
|
|
7309
8484
|
async function discoverWorkspaceRules(workspace, maxChars = 12e4) {
|
|
7310
8485
|
const workspaceAccess = new WorkspaceAccess([workspace]);
|
|
7311
|
-
const activeProjectRules =
|
|
8486
|
+
const activeProjectRules = join14(resolveProjectNamespaceSync(workspace).active, "rules.md");
|
|
7312
8487
|
const projectCandidates = [
|
|
7313
|
-
...workspaceRulePaths.slice(0, 3).map((path) =>
|
|
8488
|
+
...workspaceRulePaths.slice(0, 3).map((path) => join14(workspace, path)),
|
|
7314
8489
|
activeProjectRules,
|
|
7315
|
-
...workspaceRulePaths.slice(3).map((path) =>
|
|
8490
|
+
...workspaceRulePaths.slice(3).map((path) => join14(workspace, path))
|
|
7316
8491
|
];
|
|
7317
8492
|
const candidates = [
|
|
7318
|
-
{ path:
|
|
8493
|
+
{ path: join14(resolveHomeNamespace(), "rules.md"), scope: "user" },
|
|
7319
8494
|
...projectCandidates.map((path) => ({
|
|
7320
8495
|
path,
|
|
7321
8496
|
scope: "workspace"
|
|
@@ -7326,13 +8501,13 @@ async function discoverWorkspaceRules(workspace, maxChars = 12e4) {
|
|
|
7326
8501
|
for (const candidate of candidates) {
|
|
7327
8502
|
if (remaining <= 0 || !existsSync2(candidate.path)) continue;
|
|
7328
8503
|
try {
|
|
7329
|
-
const info = await
|
|
8504
|
+
const info = await lstat16(candidate.path);
|
|
7330
8505
|
if (!info.isFile() || info.size > 1e6) continue;
|
|
7331
8506
|
if (candidate.scope === "workspace") {
|
|
7332
8507
|
const safePath = await workspaceAccess.resolvePath(candidate.path, { expect: "file" });
|
|
7333
8508
|
if (safePath !== candidate.path) continue;
|
|
7334
8509
|
}
|
|
7335
|
-
const raw = await
|
|
8510
|
+
const raw = await readFile12(candidate.path, "utf8");
|
|
7336
8511
|
if (raw.includes("\0")) continue;
|
|
7337
8512
|
const content = raw.slice(0, remaining);
|
|
7338
8513
|
rules.push({
|
|
@@ -7364,7 +8539,7 @@ function escapeAttribute4(value) {
|
|
|
7364
8539
|
}
|
|
7365
8540
|
|
|
7366
8541
|
// src/context/context-sources.ts
|
|
7367
|
-
import { readFile as
|
|
8542
|
+
import { readFile as readFile13, stat as stat9 } from "node:fs/promises";
|
|
7368
8543
|
var MAX_SOURCE_CHARS = 6e4;
|
|
7369
8544
|
var MAX_PINNED_CHARS = 16e4;
|
|
7370
8545
|
var MAX_CONTEXT_SOURCES = 32;
|
|
@@ -7378,7 +8553,7 @@ async function pinContextSource(session, workspace, requested) {
|
|
|
7378
8553
|
const resolved = await workspace.resolvePath(requested, { expect: "file" });
|
|
7379
8554
|
const info = await stat9(resolved);
|
|
7380
8555
|
const alias = workspace.display(resolved);
|
|
7381
|
-
const tokens2 = estimateTokens3((await
|
|
8556
|
+
const tokens2 = estimateTokens3((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
|
|
7382
8557
|
const list2 = sources(session);
|
|
7383
8558
|
const existing = list2.find((source2) => source2.path === alias);
|
|
7384
8559
|
if (existing) {
|
|
@@ -7428,7 +8603,7 @@ async function resolvePinnedContent(session, workspace) {
|
|
|
7428
8603
|
if (remaining <= 0) break;
|
|
7429
8604
|
try {
|
|
7430
8605
|
const safe = await workspace.resolvePath(source.path, { expect: "file" });
|
|
7431
|
-
const raw = await
|
|
8606
|
+
const raw = await readFile13(safe, "utf8");
|
|
7432
8607
|
const capped = raw.slice(0, Math.min(MAX_SOURCE_CHARS, remaining));
|
|
7433
8608
|
source.tokens = estimateTokens3(capped);
|
|
7434
8609
|
resolved.push({
|
|
@@ -7476,6 +8651,7 @@ var AgentRunner = class {
|
|
|
7476
8651
|
contextEngine;
|
|
7477
8652
|
tools;
|
|
7478
8653
|
sessionStore;
|
|
8654
|
+
toolArtifactStore;
|
|
7479
8655
|
checkpointStore;
|
|
7480
8656
|
workspace;
|
|
7481
8657
|
hooks;
|
|
@@ -7497,6 +8673,7 @@ var AgentRunner = class {
|
|
|
7497
8673
|
contextEngine: this.contextEngine
|
|
7498
8674
|
});
|
|
7499
8675
|
this.sessionStore = options.sessionStore ?? new SessionStore(this.workspace.primaryRoot);
|
|
8676
|
+
this.toolArtifactStore = options.toolArtifactStore ?? new ToolArtifactStore(this.workspace.primaryRoot);
|
|
7500
8677
|
this.checkpointStore = options.checkpointStore ?? new CheckpointStore(this.workspace);
|
|
7501
8678
|
this.hooks = new HookRunner(options.config.hooks, this.workspace);
|
|
7502
8679
|
this.contextManager = options.contextManager ?? new ContextManager(options.config);
|
|
@@ -7538,6 +8715,8 @@ var AgentRunner = class {
|
|
|
7538
8715
|
const changeSequenceAtStart = this.changeSequence;
|
|
7539
8716
|
const runChangedFiles = /* @__PURE__ */ new Set();
|
|
7540
8717
|
const verificationEvidence = [];
|
|
8718
|
+
const toolRecovery = new ToolRecoveryController();
|
|
8719
|
+
let activeRunContract = this.session.taskContract;
|
|
7541
8720
|
let mutationTracking = "complete";
|
|
7542
8721
|
let completionRecoveryAttempted = false;
|
|
7543
8722
|
const recordExecution = (call, result) => {
|
|
@@ -7556,12 +8735,21 @@ var AgentRunner = class {
|
|
|
7556
8735
|
);
|
|
7557
8736
|
if (evidence) verificationEvidence.push(evidence);
|
|
7558
8737
|
};
|
|
7559
|
-
const completionReport = () =>
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
8738
|
+
const completionReport = () => {
|
|
8739
|
+
const completion = buildRunCompletion(
|
|
8740
|
+
runChangedFiles,
|
|
8741
|
+
verificationEvidence,
|
|
8742
|
+
this.changeSequence,
|
|
8743
|
+
mutationTracking,
|
|
8744
|
+
activeRunContract,
|
|
8745
|
+
this.session.audit ?? []
|
|
8746
|
+
);
|
|
8747
|
+
if (completion.acceptance && activeRunContract && activeRunContract.state !== "draft") {
|
|
8748
|
+
activeRunContract.state = completion.acceptance.state;
|
|
8749
|
+
activeRunContract.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8750
|
+
}
|
|
8751
|
+
return completion;
|
|
8752
|
+
};
|
|
7565
8753
|
const finishRun = async (reason, completion = completionReport()) => {
|
|
7566
8754
|
this.session.lastRun = {
|
|
7567
8755
|
...completion,
|
|
@@ -7569,11 +8757,16 @@ var AgentRunner = class {
|
|
|
7569
8757
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7570
8758
|
};
|
|
7571
8759
|
await this.persist();
|
|
8760
|
+
const finalContract = activeRunContract && structuredClone(activeRunContract);
|
|
8761
|
+
if (finalContract && finalContract.state !== "satisfied") {
|
|
8762
|
+
await emit({ type: "contract", contract: finalContract });
|
|
8763
|
+
}
|
|
7572
8764
|
await emit({ type: "done", reason, completion });
|
|
7573
8765
|
return this.session;
|
|
7574
8766
|
};
|
|
7575
8767
|
try {
|
|
7576
8768
|
throwIfAborted(options.signal);
|
|
8769
|
+
await this.reconcileToolArtifacts();
|
|
7577
8770
|
if (this.session.messages.length === 0 && this.session.title === "New session") {
|
|
7578
8771
|
this.session.title = titleFromInput(request);
|
|
7579
8772
|
}
|
|
@@ -7613,10 +8806,22 @@ var AgentRunner = class {
|
|
|
7613
8806
|
const turnDirective = buildTurnDirective(request, {
|
|
7614
8807
|
agents: Boolean(this.config.agents?.enabled)
|
|
7615
8808
|
});
|
|
8809
|
+
const contractEnabled = shouldUseTaskContract(
|
|
8810
|
+
request,
|
|
8811
|
+
turnDirective.intent,
|
|
8812
|
+
this.session.taskContract
|
|
8813
|
+
);
|
|
8814
|
+
if (contractEnabled && (!this.session.taskContract || this.session.taskContract.state === "satisfied")) {
|
|
8815
|
+
this.session.taskContract = createDraftTaskContract(request, this.session.audit?.at(-1)?.id);
|
|
8816
|
+
await this.persist();
|
|
8817
|
+
await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
|
|
8818
|
+
}
|
|
8819
|
+
activeRunContract = contractEnabled ? this.session.taskContract : void 0;
|
|
7616
8820
|
const promptSections = [
|
|
7617
8821
|
`intent:${turnDirective.intent}`,
|
|
7618
8822
|
...workspaceRules ? ["rules"] : [],
|
|
7619
8823
|
...this.session.workingMemory ? ["working-memory"] : [],
|
|
8824
|
+
...contractEnabled ? ["task-contract"] : [],
|
|
7620
8825
|
...this.session.contextSummary ? ["session-summary"] : [],
|
|
7621
8826
|
...!trivialTurn ? [`context:${packed.engine}`] : [],
|
|
7622
8827
|
...packed.text ? [`code:${packed.engine}`] : [],
|
|
@@ -7668,9 +8873,13 @@ var AgentRunner = class {
|
|
|
7668
8873
|
contextBudget
|
|
7669
8874
|
);
|
|
7670
8875
|
const availableTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
|
|
7671
|
-
const
|
|
7672
|
-
|
|
8876
|
+
const visibleTools = visibleToolDefinitions(
|
|
8877
|
+
this.tools,
|
|
8878
|
+
options.askMode === true,
|
|
8879
|
+
contractEnabled,
|
|
8880
|
+
this.hasReadableToolArtifact()
|
|
7673
8881
|
);
|
|
8882
|
+
const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
|
|
7674
8883
|
if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
|
|
7675
8884
|
return finishRun("token_budget");
|
|
7676
8885
|
}
|
|
@@ -7678,8 +8887,7 @@ var AgentRunner = class {
|
|
|
7678
8887
|
this.config.model.maxTokens ?? 8192,
|
|
7679
8888
|
availableTokens - estimatedInputTokens
|
|
7680
8889
|
));
|
|
7681
|
-
const
|
|
7682
|
-
const assistantId = randomUUID10();
|
|
8890
|
+
const assistantId = randomUUID12();
|
|
7683
8891
|
const response = await this.completeModel(
|
|
7684
8892
|
messages,
|
|
7685
8893
|
visibleTools,
|
|
@@ -7722,9 +8930,17 @@ var AgentRunner = class {
|
|
|
7722
8930
|
return finishRun("token_budget");
|
|
7723
8931
|
}
|
|
7724
8932
|
if (response.toolCalls.length) {
|
|
8933
|
+
const visibleToolNames = new Set(visibleTools.map((tool) => tool.name));
|
|
7725
8934
|
for (const call of response.toolCalls) {
|
|
7726
8935
|
throwIfAborted(options.signal);
|
|
7727
|
-
const result = await this.executeTool(
|
|
8936
|
+
const result = await this.executeTool(
|
|
8937
|
+
call,
|
|
8938
|
+
options,
|
|
8939
|
+
emit,
|
|
8940
|
+
toolRecovery,
|
|
8941
|
+
visibleToolNames
|
|
8942
|
+
);
|
|
8943
|
+
if (call.name === "task_contract") activeRunContract = this.session.taskContract;
|
|
7728
8944
|
recordExecution(call, result);
|
|
7729
8945
|
this.session.messages.push(message("tool", result.content, {
|
|
7730
8946
|
toolCallId: result.toolCallId,
|
|
@@ -7753,8 +8969,11 @@ Review these results, correct any failures if needed, then provide the final ans
|
|
|
7753
8969
|
continue;
|
|
7754
8970
|
}
|
|
7755
8971
|
const completion = completionReport();
|
|
7756
|
-
if (this.config.agent.autoVerify && (completion.status === "unverified" || completion.status === "verification_failed") && !completionRecoveryAttempted && turn < maxTurns) {
|
|
8972
|
+
if ((this.config.agent.autoVerify || Boolean(completion.acceptance)) && (completion.status === "unverified" || completion.status === "verification_failed") && !completionRecoveryAttempted && turn < maxTurns) {
|
|
7757
8973
|
completionRecoveryAttempted = true;
|
|
8974
|
+
if (activeRunContract) {
|
|
8975
|
+
await emit({ type: "contract", contract: structuredClone(activeRunContract) });
|
|
8976
|
+
}
|
|
7758
8977
|
this.session.messages.push(message("user", completionRecoveryDirective(completion)));
|
|
7759
8978
|
await this.persist();
|
|
7760
8979
|
await this.runAfterTurnHook(turn, [], options.signal);
|
|
@@ -7822,10 +9041,29 @@ ${input2}`
|
|
|
7822
9041
|
if (final) return final;
|
|
7823
9042
|
return { content, toolCalls: [] };
|
|
7824
9043
|
}
|
|
7825
|
-
async executeTool(call, options, emit) {
|
|
9044
|
+
async executeTool(call, options, emit, recovery = new ToolRecoveryController(), visibleToolNames) {
|
|
9045
|
+
const preflight = recovery.preflight(call);
|
|
9046
|
+
if (preflight) {
|
|
9047
|
+
const result = await this.protectToolResult(
|
|
9048
|
+
failedResult(call, "Tool call rejected by the recovery circuit.", preflight)
|
|
9049
|
+
);
|
|
9050
|
+
this.recordToolResult(result);
|
|
9051
|
+
await emit({ type: "tool_result", result });
|
|
9052
|
+
return result;
|
|
9053
|
+
}
|
|
9054
|
+
if (visibleToolNames && !visibleToolNames.has(call.name)) {
|
|
9055
|
+
const receipt = recovery.recordFailure(call, "unknown_tool");
|
|
9056
|
+
const result = await this.protectToolResult(
|
|
9057
|
+
failedResult(call, `Tool is not exposed for this turn: ${call.name}`, receipt)
|
|
9058
|
+
);
|
|
9059
|
+
this.recordToolResult(result);
|
|
9060
|
+
await emit({ type: "tool_result", result });
|
|
9061
|
+
return result;
|
|
9062
|
+
}
|
|
7826
9063
|
const tool = this.tools.get(call.name);
|
|
7827
9064
|
if (!tool) {
|
|
7828
|
-
const
|
|
9065
|
+
const receipt = recovery.recordFailure(call, "unknown_tool");
|
|
9066
|
+
const result = await this.protectToolResult(failedResult(call, `Unknown tool: ${call.name}`, receipt));
|
|
7829
9067
|
this.recordToolResult(result);
|
|
7830
9068
|
await emit({ type: "tool_result", result });
|
|
7831
9069
|
return result;
|
|
@@ -7836,7 +9074,18 @@ ${input2}`
|
|
|
7836
9074
|
tool.permissionCategories?.(call.arguments) ?? [tool.definition.category]
|
|
7837
9075
|
);
|
|
7838
9076
|
} catch (error) {
|
|
7839
|
-
const
|
|
9077
|
+
const failureClass = classifyThrownToolFailure(error, options.signal);
|
|
9078
|
+
const receipt = recovery.recordFailure(call, failureClass);
|
|
9079
|
+
const result = await this.protectToolResult(failedResult(call, formatToolError(error), receipt));
|
|
9080
|
+
this.recordToolResult(result, tool.definition.category);
|
|
9081
|
+
await emit({ type: "tool_result", result });
|
|
9082
|
+
return result;
|
|
9083
|
+
}
|
|
9084
|
+
if (categories.some((category) => category !== "read") && this.session.taskContract?.state === "draft") {
|
|
9085
|
+
const receipt = recovery.recordFailure(call, "contract_required");
|
|
9086
|
+
const result = await this.protectToolResult(
|
|
9087
|
+
failedResult(call, "Potentially mutating work is paused until the draft Task Contract is activated.", receipt)
|
|
9088
|
+
);
|
|
7840
9089
|
this.recordToolResult(result, tool.definition.category);
|
|
7841
9090
|
await emit({ type: "tool_result", result });
|
|
7842
9091
|
return result;
|
|
@@ -7844,7 +9093,10 @@ ${input2}`
|
|
|
7844
9093
|
for (const category of categories) {
|
|
7845
9094
|
const allowed = await this.authorize(call, category, options, emit);
|
|
7846
9095
|
if (!allowed) {
|
|
7847
|
-
const
|
|
9096
|
+
const receipt = recovery.recordFailure(call, "permission_denied");
|
|
9097
|
+
const result = await this.protectToolResult(
|
|
9098
|
+
failedResult(call, `Permission denied for ${category} operation.`, receipt)
|
|
9099
|
+
);
|
|
7848
9100
|
this.recordToolResult(result, category);
|
|
7849
9101
|
await emit({ type: "tool_result", result });
|
|
7850
9102
|
return result;
|
|
@@ -7858,6 +9110,7 @@ ${input2}`
|
|
|
7858
9110
|
workspace: this.workspace,
|
|
7859
9111
|
session: this.session,
|
|
7860
9112
|
contextEngine: this.contextEngine,
|
|
9113
|
+
toolArtifactStore: this.toolArtifactStore,
|
|
7861
9114
|
emit,
|
|
7862
9115
|
...options.signal ? { signal: options.signal } : {}
|
|
7863
9116
|
};
|
|
@@ -7891,30 +9144,48 @@ ${input2}`
|
|
|
7891
9144
|
} catch (error) {
|
|
7892
9145
|
afterHookError = toError(error);
|
|
7893
9146
|
}
|
|
7894
|
-
|
|
7895
|
-
toolCallId: call.id,
|
|
7896
|
-
name: call.name,
|
|
7897
|
-
ok: execution.ok !== false && !afterHookError,
|
|
7898
|
-
content: truncateToolOutput(afterHookError ? `${execution.content}
|
|
9147
|
+
let completeContent = afterHookError ? `${execution.content}
|
|
7899
9148
|
|
|
7900
|
-
Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : execution.content
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
}
|
|
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 } : {}
|
|
7908
9156
|
};
|
|
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 });
|
|
9160
|
+
const receipt = recovery.recordFailure(call, failureClass);
|
|
9161
|
+
completeContent = `${formatFailureReceipt(receipt)}
|
|
9162
|
+
${completeContent}`;
|
|
9163
|
+
metadata.failure = receipt;
|
|
9164
|
+
} else {
|
|
9165
|
+
recovery.recordSuccess(call);
|
|
9166
|
+
}
|
|
9167
|
+
const result = await this.protectToolResult({
|
|
9168
|
+
toolCallId: call.id,
|
|
9169
|
+
name: call.name,
|
|
9170
|
+
ok,
|
|
9171
|
+
content: completeContent,
|
|
9172
|
+
metadata
|
|
9173
|
+
});
|
|
7909
9174
|
this.contextManager.recordTool(this.session, call, result);
|
|
7910
9175
|
if (JSON.stringify(this.session.tasks) !== tasksBefore || call.name === "task") {
|
|
7911
9176
|
await emit({ type: "tasks", tasks: this.session.tasks.map((task) => ({ ...task })) });
|
|
7912
9177
|
}
|
|
9178
|
+
if (call.name === "task_contract" && this.session.taskContract) {
|
|
9179
|
+
await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
|
|
9180
|
+
}
|
|
7913
9181
|
this.recordToolResult(result, tool.definition.category);
|
|
7914
9182
|
await emit({ type: "tool_result", result });
|
|
7915
9183
|
return result;
|
|
7916
9184
|
} catch (error) {
|
|
7917
|
-
const
|
|
9185
|
+
const normalized = toError(error);
|
|
9186
|
+
const failureClass = classifyThrownToolFailure(normalized, options.signal);
|
|
9187
|
+
const receipt = recovery.recordFailure(call, failureClass);
|
|
9188
|
+
const result = await this.protectToolResult(failedResult(call, formatToolError(error), receipt));
|
|
7918
9189
|
this.recordToolResult(result, tool.definition.category);
|
|
7919
9190
|
await emit({ type: "tool_result", result });
|
|
7920
9191
|
return result;
|
|
@@ -7983,7 +9254,7 @@ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : executio
|
|
|
7983
9254
|
}
|
|
7984
9255
|
appendAudit(event) {
|
|
7985
9256
|
const audit = this.session.audit ?? (this.session.audit = []);
|
|
7986
|
-
audit.push({ id:
|
|
9257
|
+
audit.push({ id: randomUUID12(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
7987
9258
|
if (audit.length > 5e3) audit.splice(0, audit.length - 5e3);
|
|
7988
9259
|
}
|
|
7989
9260
|
async acceptChangedFiles(paths) {
|
|
@@ -8004,7 +9275,7 @@ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : executio
|
|
|
8004
9275
|
const results = [];
|
|
8005
9276
|
for (const command2 of this.config.agent.verifyCommands) {
|
|
8006
9277
|
const call = {
|
|
8007
|
-
id: `verify-${
|
|
9278
|
+
id: `verify-${randomUUID12()}`,
|
|
8008
9279
|
name: "shell",
|
|
8009
9280
|
arguments: { command: command2, cwd: this.workspace.primaryRoot }
|
|
8010
9281
|
};
|
|
@@ -8068,10 +9339,67 @@ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : executio
|
|
|
8068
9339
|
persist() {
|
|
8069
9340
|
return this.persistSession ? this.sessionStore.save(this.session) : Promise.resolve();
|
|
8070
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
|
+
}
|
|
8071
9399
|
};
|
|
8072
9400
|
function message(role, content, extra = {}) {
|
|
8073
9401
|
return {
|
|
8074
|
-
id:
|
|
9402
|
+
id: randomUUID12(),
|
|
8075
9403
|
role,
|
|
8076
9404
|
content,
|
|
8077
9405
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8142,24 +9470,33 @@ function estimateResponseTokens(response) {
|
|
|
8142
9470
|
function uniqueCategories(categories) {
|
|
8143
9471
|
return [...new Set(categories)];
|
|
8144
9472
|
}
|
|
8145
|
-
function failedResult(call, content) {
|
|
9473
|
+
function failedResult(call, content, failure) {
|
|
8146
9474
|
return {
|
|
8147
9475
|
toolCallId: call.id,
|
|
8148
9476
|
name: call.name,
|
|
8149
9477
|
ok: false,
|
|
8150
|
-
content:
|
|
9478
|
+
content: failure ? `${formatFailureReceipt(failure)}
|
|
9479
|
+
${content}` : content,
|
|
9480
|
+
...failure ? { metadata: { failure } } : {}
|
|
8151
9481
|
};
|
|
8152
9482
|
}
|
|
8153
|
-
function
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
9483
|
+
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable) {
|
|
9484
|
+
return tools.definitions().filter(
|
|
9485
|
+
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact")
|
|
9486
|
+
);
|
|
8157
9487
|
}
|
|
8158
9488
|
function formatToolError(error) {
|
|
8159
9489
|
const normalized = toError(error);
|
|
8160
9490
|
if (normalized.name === "ZodError") return `Invalid tool arguments: ${normalized.message}`;
|
|
8161
9491
|
return normalized.message;
|
|
8162
9492
|
}
|
|
9493
|
+
function classifyThrownToolFailure(error, signal) {
|
|
9494
|
+
const normalized = toError(error);
|
|
9495
|
+
if (isAbortError(normalized) || signal?.aborted) return "cancelled";
|
|
9496
|
+
if (normalized.name === "HookError") return "hook";
|
|
9497
|
+
if (normalized.name === "ZodError" || normalized.name === "ToolInputError") return "schema_input";
|
|
9498
|
+
return "execution";
|
|
9499
|
+
}
|
|
8163
9500
|
function titleFromInput(input2) {
|
|
8164
9501
|
return input2.trim().replace(/\s+/g, " ").slice(0, 80) || "New session";
|
|
8165
9502
|
}
|
|
@@ -8180,9 +9517,9 @@ async function safeEmit(emit, event) {
|
|
|
8180
9517
|
}
|
|
8181
9518
|
|
|
8182
9519
|
// src/agent/profiles.ts
|
|
8183
|
-
import { lstat as
|
|
9520
|
+
import { lstat as lstat17, readFile as readFile14, readdir as readdir6 } from "node:fs/promises";
|
|
8184
9521
|
import { homedir as homedir3 } from "node:os";
|
|
8185
|
-
import { basename as basename8, join as
|
|
9522
|
+
import { basename as basename8, join as join15 } from "node:path";
|
|
8186
9523
|
import { parse as parseYaml2 } from "yaml";
|
|
8187
9524
|
var builtInProfiles = [
|
|
8188
9525
|
{
|
|
@@ -8274,14 +9611,14 @@ var AgentProfileCatalog = class {
|
|
|
8274
9611
|
profiles = new Map(builtInProfiles.map((profile) => [profile.name, profile]));
|
|
8275
9612
|
async discover() {
|
|
8276
9613
|
const locations = [
|
|
8277
|
-
{ path:
|
|
8278
|
-
{ path:
|
|
8279
|
-
{ path:
|
|
8280
|
-
{ 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" }
|
|
8281
9618
|
];
|
|
8282
9619
|
for (const location of locations) {
|
|
8283
9620
|
for (const file of await markdownFiles(location.path)) {
|
|
8284
|
-
const profile = await readProfile(
|
|
9621
|
+
const profile = await readProfile(join15(location.path, file), location.source);
|
|
8285
9622
|
if (profile) this.profiles.set(profile.name, profile);
|
|
8286
9623
|
}
|
|
8287
9624
|
}
|
|
@@ -8297,18 +9634,18 @@ var AgentProfileCatalog = class {
|
|
|
8297
9634
|
};
|
|
8298
9635
|
async function markdownFiles(directory) {
|
|
8299
9636
|
try {
|
|
8300
|
-
const info = await
|
|
9637
|
+
const info = await lstat17(directory);
|
|
8301
9638
|
if (!info.isDirectory() || info.isSymbolicLink()) return [];
|
|
8302
|
-
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);
|
|
8303
9640
|
} catch {
|
|
8304
9641
|
return [];
|
|
8305
9642
|
}
|
|
8306
9643
|
}
|
|
8307
9644
|
async function readProfile(path, source) {
|
|
8308
9645
|
try {
|
|
8309
|
-
const info = await
|
|
9646
|
+
const info = await lstat17(path);
|
|
8310
9647
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 1e5) return void 0;
|
|
8311
|
-
const raw = await
|
|
9648
|
+
const raw = await readFile14(path, "utf8");
|
|
8312
9649
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
8313
9650
|
const frontmatter = match ? parseYaml2(match[1] ?? "") : {};
|
|
8314
9651
|
const prompt = (match ? raw.slice(match[0].length) : raw).trim();
|
|
@@ -8335,9 +9672,9 @@ function integer(value, fallback, min, max) {
|
|
|
8335
9672
|
}
|
|
8336
9673
|
|
|
8337
9674
|
// src/agent/delegation.ts
|
|
8338
|
-
import { randomUUID as
|
|
8339
|
-
import { join as
|
|
8340
|
-
import { z as
|
|
9675
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
9676
|
+
import { join as join18 } from "node:path";
|
|
9677
|
+
import { z as z18 } from "zod";
|
|
8341
9678
|
|
|
8342
9679
|
// src/agent/external-runtime.ts
|
|
8343
9680
|
async function runExternalAgent(request) {
|
|
@@ -8464,101 +9801,101 @@ function numeric(...values) {
|
|
|
8464
9801
|
}
|
|
8465
9802
|
|
|
8466
9803
|
// src/agent/team-store.ts
|
|
8467
|
-
import { createHash as
|
|
8468
|
-
import { lstat as
|
|
8469
|
-
import { join as
|
|
8470
|
-
import { z as
|
|
8471
|
-
var runIdSchema =
|
|
8472
|
-
var hashSchema =
|
|
8473
|
-
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({
|
|
8474
9811
|
sha256: hashSchema,
|
|
8475
|
-
bytes:
|
|
9812
|
+
bytes: z17.number().int().nonnegative().max(5e5)
|
|
8476
9813
|
}).strict();
|
|
8477
|
-
var phaseSchema =
|
|
8478
|
-
var agentRecordSchema =
|
|
8479
|
-
id:
|
|
8480
|
-
profile:
|
|
8481
|
-
provider:
|
|
8482
|
-
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(),
|
|
8483
9820
|
phase: phaseSchema,
|
|
8484
|
-
ok:
|
|
8485
|
-
createdAt:
|
|
8486
|
-
startedAt:
|
|
8487
|
-
endedAt:
|
|
8488
|
-
durationMs:
|
|
8489
|
-
toolCalls:
|
|
8490
|
-
usage:
|
|
8491
|
-
inputTokens:
|
|
8492
|
-
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()
|
|
8493
9830
|
}).strict().optional(),
|
|
8494
|
-
report:
|
|
9831
|
+
report: artifactSchema2
|
|
8495
9832
|
}).strict();
|
|
8496
|
-
var messageRecordSchema =
|
|
8497
|
-
id:
|
|
8498
|
-
from:
|
|
8499
|
-
to:
|
|
8500
|
-
createdAt:
|
|
8501
|
-
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
|
|
8502
9839
|
}).strict();
|
|
8503
|
-
var writerIntegrationSchema =
|
|
8504
|
-
status:
|
|
8505
|
-
checkedAt:
|
|
8506
|
-
detail:
|
|
8507
|
-
checkpoint:
|
|
8508
|
-
sessionId:
|
|
8509
|
-
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()
|
|
8510
9847
|
}).strict().optional(),
|
|
8511
|
-
integratedAt:
|
|
9848
|
+
integratedAt: z17.string().optional()
|
|
8512
9849
|
}).strict();
|
|
8513
|
-
var writerLaneSchema =
|
|
8514
|
-
profile:
|
|
8515
|
-
reviewer:
|
|
8516
|
-
baseCommit:
|
|
8517
|
-
outcome:
|
|
8518
|
-
patch:
|
|
8519
|
-
files:
|
|
8520
|
-
worktreeCleaned:
|
|
8521
|
-
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(),
|
|
8522
9859
|
integration: writerIntegrationSchema.optional()
|
|
8523
9860
|
}).strict();
|
|
8524
9861
|
var manifestFields = {
|
|
8525
9862
|
id: runIdSchema,
|
|
8526
|
-
workspace:
|
|
8527
|
-
objective:
|
|
8528
|
-
reviewer:
|
|
8529
|
-
createdAt:
|
|
8530
|
-
updatedAt:
|
|
8531
|
-
status:
|
|
8532
|
-
maxReviewRounds:
|
|
8533
|
-
reviewRounds:
|
|
8534
|
-
agents:
|
|
8535
|
-
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)
|
|
8536
9873
|
};
|
|
8537
|
-
var manifestV1Schema =
|
|
8538
|
-
version:
|
|
9874
|
+
var manifestV1Schema = z17.object({
|
|
9875
|
+
version: z17.literal(1),
|
|
8539
9876
|
...manifestFields
|
|
8540
9877
|
}).strict();
|
|
8541
|
-
var manifestV2Schema =
|
|
8542
|
-
version:
|
|
9878
|
+
var manifestV2Schema = z17.object({
|
|
9879
|
+
version: z17.literal(2),
|
|
8543
9880
|
...manifestFields,
|
|
8544
9881
|
writer: writerLaneSchema.optional()
|
|
8545
9882
|
}).strict();
|
|
8546
|
-
var manifestSchema2 =
|
|
9883
|
+
var manifestSchema2 = z17.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
|
|
8547
9884
|
var TeamRunStore = class {
|
|
8548
9885
|
workspace;
|
|
8549
9886
|
directory;
|
|
8550
9887
|
managedDirectory;
|
|
8551
9888
|
writes = Promise.resolve();
|
|
8552
9889
|
constructor(workspace, directory) {
|
|
8553
|
-
this.workspace =
|
|
9890
|
+
this.workspace = resolve16(workspace);
|
|
8554
9891
|
this.managedDirectory = directory === void 0;
|
|
8555
|
-
this.directory = directory ?
|
|
9892
|
+
this.directory = directory ? resolve16(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
|
|
8556
9893
|
}
|
|
8557
9894
|
async create(input2) {
|
|
8558
9895
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8559
9896
|
const manifest = manifestSchema2.parse({
|
|
8560
9897
|
version: 2,
|
|
8561
|
-
id:
|
|
9898
|
+
id: randomUUID13(),
|
|
8562
9899
|
workspace: this.workspace,
|
|
8563
9900
|
objective: input2.objective,
|
|
8564
9901
|
reviewer: input2.reviewer,
|
|
@@ -8636,10 +9973,10 @@ var TeamRunStore = class {
|
|
|
8636
9973
|
runIdSchema.parse(runId);
|
|
8637
9974
|
await this.writes;
|
|
8638
9975
|
await this.assertRunDirectory(runId);
|
|
8639
|
-
const path =
|
|
9976
|
+
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
8640
9977
|
await this.assertRegularFile(path);
|
|
8641
|
-
const manifest = manifestSchema2.parse(JSON.parse(await
|
|
8642
|
-
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) {
|
|
8643
9980
|
throw new Error("Team run manifest identity does not match its location.");
|
|
8644
9981
|
}
|
|
8645
9982
|
if (verify) {
|
|
@@ -8653,13 +9990,13 @@ var TeamRunStore = class {
|
|
|
8653
9990
|
}
|
|
8654
9991
|
async readArtifact(runId, artifact) {
|
|
8655
9992
|
await this.verifyArtifact(runId, artifact);
|
|
8656
|
-
return
|
|
9993
|
+
return readFile15(this.artifactPath(runId, artifact.sha256), "utf8");
|
|
8657
9994
|
}
|
|
8658
9995
|
async list() {
|
|
8659
9996
|
await this.writes;
|
|
8660
9997
|
try {
|
|
8661
9998
|
await assertNoSymlinkPath(this.workspace, this.directory);
|
|
8662
|
-
const entries = await
|
|
9999
|
+
const entries = await readdir7(this.directory, { withFileTypes: true });
|
|
8663
10000
|
const summaries = [];
|
|
8664
10001
|
for (const entry of entries) {
|
|
8665
10002
|
if (!entry.isDirectory() || entry.isSymbolicLink() || !runIdSchema.safeParse(entry.name).success) continue;
|
|
@@ -8682,9 +10019,9 @@ var TeamRunStore = class {
|
|
|
8682
10019
|
const directory = this.runDirectory(runId);
|
|
8683
10020
|
await assertNoSymlinkPath(this.workspace, directory);
|
|
8684
10021
|
try {
|
|
8685
|
-
const info = await
|
|
10022
|
+
const info = await lstat18(directory);
|
|
8686
10023
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
|
|
8687
|
-
await
|
|
10024
|
+
await rm3(directory, { recursive: true });
|
|
8688
10025
|
return true;
|
|
8689
10026
|
} catch (error) {
|
|
8690
10027
|
if (error.code === "ENOENT") return false;
|
|
@@ -8707,31 +10044,31 @@ var TeamRunStore = class {
|
|
|
8707
10044
|
}
|
|
8708
10045
|
async loadUnlocked(runId) {
|
|
8709
10046
|
await this.assertRunDirectory(runId);
|
|
8710
|
-
const path =
|
|
10047
|
+
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
8711
10048
|
await this.assertRegularFile(path);
|
|
8712
|
-
return manifestSchema2.parse(JSON.parse(await
|
|
10049
|
+
return manifestSchema2.parse(JSON.parse(await readFile15(path, "utf8")));
|
|
8713
10050
|
}
|
|
8714
10051
|
async writeManifest(manifest) {
|
|
8715
10052
|
const directory = this.runDirectory(manifest.id);
|
|
8716
10053
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
8717
10054
|
requireActiveNamespace: this.managedDirectory
|
|
8718
10055
|
});
|
|
8719
|
-
await atomicWrite(
|
|
10056
|
+
await atomicWrite(join16(directory, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
8720
10057
|
`, 384);
|
|
8721
10058
|
}
|
|
8722
10059
|
async writeArtifact(runId, content, truncate = true) {
|
|
8723
10060
|
const data = boundedArtifactText(content, 5e5, truncate);
|
|
8724
10061
|
const bytes = Buffer.byteLength(data);
|
|
8725
|
-
const sha256 =
|
|
8726
|
-
const directory =
|
|
10062
|
+
const sha256 = createHash12("sha256").update(data).digest("hex");
|
|
10063
|
+
const directory = join16(this.runDirectory(runId), "blobs");
|
|
8727
10064
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
8728
10065
|
requireActiveNamespace: this.managedDirectory
|
|
8729
10066
|
});
|
|
8730
|
-
const path =
|
|
10067
|
+
const path = join16(directory, `${sha256}.txt`);
|
|
8731
10068
|
try {
|
|
8732
10069
|
await this.assertRegularFile(path);
|
|
8733
|
-
const existing = await
|
|
8734
|
-
if (
|
|
10070
|
+
const existing = await readFile15(path);
|
|
10071
|
+
if (createHash12("sha256").update(existing).digest("hex") !== sha256) {
|
|
8735
10072
|
throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
|
|
8736
10073
|
}
|
|
8737
10074
|
} catch (error) {
|
|
@@ -8751,27 +10088,27 @@ var TeamRunStore = class {
|
|
|
8751
10088
|
hashSchema.parse(artifact.sha256);
|
|
8752
10089
|
const path = this.artifactPath(runId, artifact.sha256);
|
|
8753
10090
|
await this.assertRegularFile(path);
|
|
8754
|
-
const data = await
|
|
8755
|
-
const
|
|
8756
|
-
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) {
|
|
8757
10094
|
throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
|
|
8758
10095
|
}
|
|
8759
10096
|
}
|
|
8760
10097
|
artifactPath(runId, sha256) {
|
|
8761
|
-
return
|
|
10098
|
+
return join16(this.runDirectory(runId), "blobs", `${sha256}.txt`);
|
|
8762
10099
|
}
|
|
8763
10100
|
runDirectory(runId) {
|
|
8764
|
-
return
|
|
10101
|
+
return join16(this.directory, runId);
|
|
8765
10102
|
}
|
|
8766
10103
|
async assertRunDirectory(runId) {
|
|
8767
10104
|
const directory = this.runDirectory(runId);
|
|
8768
10105
|
await assertNoSymlinkPath(this.workspace, directory);
|
|
8769
|
-
const info = await
|
|
10106
|
+
const info = await lstat18(directory);
|
|
8770
10107
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
|
|
8771
10108
|
}
|
|
8772
10109
|
async assertRegularFile(path) {
|
|
8773
|
-
await assertNoSymlinkPath(this.workspace,
|
|
8774
|
-
const info = await
|
|
10110
|
+
await assertNoSymlinkPath(this.workspace, resolve16(path, ".."));
|
|
10111
|
+
const info = await lstat18(path);
|
|
8775
10112
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Team run file is not a regular file: ${path}`);
|
|
8776
10113
|
}
|
|
8777
10114
|
};
|
|
@@ -8815,10 +10152,10 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
8815
10152
|
}
|
|
8816
10153
|
|
|
8817
10154
|
// src/agent/writer-lane.ts
|
|
8818
|
-
import { createHash as
|
|
8819
|
-
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";
|
|
8820
10157
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8821
|
-
import { isAbsolute as isAbsolute5, join as
|
|
10158
|
+
import { isAbsolute as isAbsolute5, join as join17, resolve as resolve17 } from "node:path";
|
|
8822
10159
|
var WriterLaneApplyError = class extends Error {
|
|
8823
10160
|
constructor(message2, attempted, cause) {
|
|
8824
10161
|
super(message2, cause === void 0 ? void 0 : { cause });
|
|
@@ -8831,18 +10168,18 @@ var WriterLane = class {
|
|
|
8831
10168
|
workspace;
|
|
8832
10169
|
constructor(workspace, workspaceRoots) {
|
|
8833
10170
|
this.workspace = new WorkspaceAccess([
|
|
8834
|
-
|
|
8835
|
-
...workspaceRoots.map((root) =>
|
|
10171
|
+
resolve17(workspace),
|
|
10172
|
+
...workspaceRoots.map((root) => resolve17(root))
|
|
8836
10173
|
]);
|
|
8837
10174
|
}
|
|
8838
10175
|
async createDraft(maxPatchBytes, operation, signal) {
|
|
8839
10176
|
const repository = await this.repository();
|
|
8840
10177
|
const baseCommit = await this.head(repository);
|
|
8841
10178
|
const lease = await acquireNamespaceLease(
|
|
8842
|
-
|
|
10179
|
+
join17(repository.commonDirectory, "skein-writer-lane"),
|
|
8843
10180
|
"exclusive"
|
|
8844
10181
|
);
|
|
8845
|
-
const worktree = await mkdtemp(
|
|
10182
|
+
const worktree = await mkdtemp(join17(tmpdir2(), "skein-writer-"));
|
|
8846
10183
|
let added = false;
|
|
8847
10184
|
let value;
|
|
8848
10185
|
let patch = "";
|
|
@@ -8913,7 +10250,7 @@ var WriterLane = class {
|
|
|
8913
10250
|
return {
|
|
8914
10251
|
baseCommit,
|
|
8915
10252
|
patch,
|
|
8916
|
-
patchSha256:
|
|
10253
|
+
patchSha256: createHash13("sha256").update(patch).digest("hex"),
|
|
8917
10254
|
files,
|
|
8918
10255
|
worktreeCleaned,
|
|
8919
10256
|
value
|
|
@@ -8932,7 +10269,7 @@ var WriterLane = class {
|
|
|
8932
10269
|
try {
|
|
8933
10270
|
const repository = await this.repository();
|
|
8934
10271
|
const lease = await acquireNamespaceLease(
|
|
8935
|
-
|
|
10272
|
+
join17(repository.commonDirectory, "skein-writer-lane"),
|
|
8936
10273
|
"exclusive"
|
|
8937
10274
|
);
|
|
8938
10275
|
try {
|
|
@@ -9040,7 +10377,7 @@ var WriterLane = class {
|
|
|
9040
10377
|
if (isAbsolute5(file) || file === ".git" || file.startsWith(".git/") || file.includes("\0")) {
|
|
9041
10378
|
throw new Error(`Writer patch contains an unsafe path: ${file}`);
|
|
9042
10379
|
}
|
|
9043
|
-
await this.workspace.resolvePath(
|
|
10380
|
+
await this.workspace.resolvePath(join17(repository.root, file), { allowMissing: true });
|
|
9044
10381
|
}
|
|
9045
10382
|
if (expectedFiles && !sameSet(files, expectedFiles)) {
|
|
9046
10383
|
throw new Error("Writer patch paths do not match the persisted file manifest.");
|
|
@@ -9055,7 +10392,7 @@ var WriterLane = class {
|
|
|
9055
10392
|
if (topLevel.exitCode !== 0 || !topLevel.stdout.trim()) {
|
|
9056
10393
|
throw new Error("The primary workspace must be a Git repository root for writer lanes.");
|
|
9057
10394
|
}
|
|
9058
|
-
const discoveredRoot =
|
|
10395
|
+
const discoveredRoot = resolve17(topLevel.stdout.trim());
|
|
9059
10396
|
if (await realpath7(discoveredRoot) !== await realpath7(root)) {
|
|
9060
10397
|
throw new Error("The primary workspace must be the Git repository root for writer lanes.");
|
|
9061
10398
|
}
|
|
@@ -9064,7 +10401,7 @@ var WriterLane = class {
|
|
|
9064
10401
|
if (common.exitCode !== 0 || !common.stdout.trim()) {
|
|
9065
10402
|
throw new Error(`Unable to resolve the Git common directory: ${processDetail(common)}`);
|
|
9066
10403
|
}
|
|
9067
|
-
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()));
|
|
9068
10405
|
return { root: repositoryRoot, commonDirectory, runtime };
|
|
9069
10406
|
}
|
|
9070
10407
|
async head(repository) {
|
|
@@ -9076,7 +10413,7 @@ var WriterLane = class {
|
|
|
9076
10413
|
return value;
|
|
9077
10414
|
}
|
|
9078
10415
|
async cleanupWorktree(repository, worktree, added) {
|
|
9079
|
-
const physicalWorktree = await realpath7(worktree).catch(() =>
|
|
10416
|
+
const physicalWorktree = await realpath7(worktree).catch(() => resolve17(worktree));
|
|
9080
10417
|
if (added) {
|
|
9081
10418
|
await runIsolatedGit(repository.runtime, [
|
|
9082
10419
|
"worktree",
|
|
@@ -9085,7 +10422,7 @@ var WriterLane = class {
|
|
|
9085
10422
|
worktree
|
|
9086
10423
|
], repository.root, { timeoutMs: 6e4 }).catch(() => void 0);
|
|
9087
10424
|
}
|
|
9088
|
-
await
|
|
10425
|
+
await rm4(worktree, { recursive: true, force: true }).catch(() => void 0);
|
|
9089
10426
|
await runIsolatedGit(repository.runtime, [
|
|
9090
10427
|
"worktree",
|
|
9091
10428
|
"prune",
|
|
@@ -9134,7 +10471,7 @@ function processDetail(result) {
|
|
|
9134
10471
|
}
|
|
9135
10472
|
async function pathExists2(path) {
|
|
9136
10473
|
try {
|
|
9137
|
-
await
|
|
10474
|
+
await lstat19(path);
|
|
9138
10475
|
return true;
|
|
9139
10476
|
} catch (error) {
|
|
9140
10477
|
if (error.code === "ENOENT") return false;
|
|
@@ -9143,14 +10480,14 @@ async function pathExists2(path) {
|
|
|
9143
10480
|
}
|
|
9144
10481
|
|
|
9145
10482
|
// src/agent/delegation.ts
|
|
9146
|
-
var writerRunInputSchema =
|
|
9147
|
-
task:
|
|
9148
|
-
profile:
|
|
9149
|
-
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()
|
|
9150
10487
|
}).strict();
|
|
9151
|
-
var writerIntegrateInputSchema =
|
|
9152
|
-
run_id:
|
|
9153
|
-
patch_sha256:
|
|
10488
|
+
var writerIntegrateInputSchema = z18.object({
|
|
10489
|
+
run_id: z18.string().uuid(),
|
|
10490
|
+
patch_sha256: z18.string().regex(/^[a-f0-9]{64}$/u)
|
|
9154
10491
|
}).strict();
|
|
9155
10492
|
var DelegationManager = class {
|
|
9156
10493
|
constructor(options) {
|
|
@@ -9211,10 +10548,10 @@ var DelegationManager = class {
|
|
|
9211
10548
|
},
|
|
9212
10549
|
async execute(arguments_, context) {
|
|
9213
10550
|
if (!manager.team.enabled) return { ok: false, content: "Multi-agent delegation is disabled." };
|
|
9214
|
-
const input2 =
|
|
9215
|
-
tasks:
|
|
9216
|
-
profile:
|
|
9217
|
-
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)
|
|
9218
10555
|
})).min(1).max(manager.team.maxDelegations)
|
|
9219
10556
|
}).parse(arguments_);
|
|
9220
10557
|
const tasks = input2.tasks.map((task) => ({
|
|
@@ -9260,13 +10597,13 @@ var DelegationManager = class {
|
|
|
9260
10597
|
},
|
|
9261
10598
|
async execute(arguments_, context) {
|
|
9262
10599
|
if (!manager.team.enabled) return { ok: false, content: "Multi-agent teams are disabled." };
|
|
9263
|
-
const input2 =
|
|
9264
|
-
objective:
|
|
9265
|
-
tasks:
|
|
9266
|
-
profile:
|
|
9267
|
-
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)
|
|
9268
10605
|
})).min(1).max(manager.team.maxDelegations),
|
|
9269
|
-
reviewer:
|
|
10606
|
+
reviewer: z18.string().max(64).optional()
|
|
9270
10607
|
}).parse(arguments_);
|
|
9271
10608
|
const tasks = input2.tasks.map((task) => ({
|
|
9272
10609
|
profile: task.profile ?? manager.team.defaultProfile,
|
|
@@ -9329,7 +10666,7 @@ var DelegationManager = class {
|
|
|
9329
10666
|
const plan = await manager.loadWriterPlan(input2.run_id, input2.patch_sha256);
|
|
9330
10667
|
const files = await manager.writerLane.inspectPatch(plan.patch, plan.writer.files);
|
|
9331
10668
|
return Promise.all(files.map(
|
|
9332
|
-
(file) => context.workspace.resolvePath(
|
|
10669
|
+
(file) => context.workspace.resolvePath(join18(context.workspace.primaryRoot, file), { allowMissing: true })
|
|
9333
10670
|
));
|
|
9334
10671
|
},
|
|
9335
10672
|
async execute(arguments_, context) {
|
|
@@ -9364,7 +10701,7 @@ var DelegationManager = class {
|
|
|
9364
10701
|
maxReviewRounds: 0
|
|
9365
10702
|
});
|
|
9366
10703
|
await emit?.({ type: "team_start", id: board.id, objective: task });
|
|
9367
|
-
const writerId =
|
|
10704
|
+
const writerId = randomUUID14();
|
|
9368
10705
|
await emit?.({ type: "agent_queued", id: writerId, profile: profile.name, task, phase: "write" });
|
|
9369
10706
|
const draft = await this.writerLane.createDraft(
|
|
9370
10707
|
Math.min(this.team.maxWriterPatchBytes ?? 6e4, 12e4),
|
|
@@ -9516,7 +10853,7 @@ ${review.summary}`,
|
|
|
9516
10853
|
const plan = await this.loadWriterPlan(runId, patchSha256);
|
|
9517
10854
|
const files = await this.writerLane.inspectPatch(plan.patch, plan.writer.files);
|
|
9518
10855
|
const paths = await Promise.all(files.map(
|
|
9519
|
-
(file) => context.workspace.resolvePath(
|
|
10856
|
+
(file) => context.workspace.resolvePath(join18(context.workspace.primaryRoot, file), { allowMissing: true })
|
|
9520
10857
|
));
|
|
9521
10858
|
const checkpointStore = new CheckpointStore(context.workspace);
|
|
9522
10859
|
let checkpointId = context.checkpointId;
|
|
@@ -9734,7 +11071,7 @@ You are the only writer inside a disposable worktree. Make only the bounded requ
|
|
|
9734
11071
|
const reviewer = reviewerOverride ?? this.team.reviewerProfile ?? "reviewer";
|
|
9735
11072
|
const rounds = Math.max(0, this.team.maxReviewRounds ?? 1);
|
|
9736
11073
|
const board = await this.teamStore?.create({ objective, reviewer, maxReviewRounds: rounds });
|
|
9737
|
-
const runId = board?.id ??
|
|
11074
|
+
const runId = board?.id ?? randomUUID14();
|
|
9738
11075
|
await emit?.({ type: "team_start", id: runId, objective });
|
|
9739
11076
|
try {
|
|
9740
11077
|
let results = await this.runBatch(board?.id, tasks, "work", emit, signal);
|
|
@@ -9792,7 +11129,7 @@ ${review.summary}`,
|
|
|
9792
11129
|
}
|
|
9793
11130
|
}
|
|
9794
11131
|
async runBatch(runId, tasks, phase, emit, signal) {
|
|
9795
|
-
const scheduled = tasks.map((task) => ({ id:
|
|
11132
|
+
const scheduled = tasks.map((task) => ({ id: randomUUID14(), task }));
|
|
9796
11133
|
for (const item of scheduled) {
|
|
9797
11134
|
await emit?.({ type: "agent_queued", id: item.id, profile: item.task.profile, task: item.task.task, phase });
|
|
9798
11135
|
}
|
|
@@ -9901,12 +11238,12 @@ Start the response with exactly VERDICT: ACCEPT when the evidence is sufficient,
|
|
|
9901
11238
|
});
|
|
9902
11239
|
}
|
|
9903
11240
|
async peerMessage(runId, from, to, content, emit) {
|
|
9904
|
-
const id =
|
|
11241
|
+
const id = randomUUID14();
|
|
9905
11242
|
if (runId) await this.teamStore?.recordMessage(runId, { id, from, to, content });
|
|
9906
11243
|
await emit?.({ type: "agent_message", id, from, to, content });
|
|
9907
11244
|
}
|
|
9908
11245
|
async runOne(task, phase, emit, signal, retryOf, scheduledId) {
|
|
9909
|
-
const id = scheduledId ??
|
|
11246
|
+
const id = scheduledId ?? randomUUID14();
|
|
9910
11247
|
const profile = this.options.profiles.get(task.profile);
|
|
9911
11248
|
const configuredRoute = this.team.routes?.[task.profile];
|
|
9912
11249
|
const budgetMode = configuredRoute?.budgetMode ?? this.team.budgetMode ?? "observe";
|
|
@@ -10818,11 +12155,11 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
10818
12155
|
`);
|
|
10819
12156
|
}
|
|
10820
12157
|
printText(event) {
|
|
10821
|
-
const { quiet, compact } = this.options;
|
|
12158
|
+
const { quiet, compact: compact2 } = this.options;
|
|
10822
12159
|
if (quiet) return;
|
|
10823
12160
|
switch (event.type) {
|
|
10824
12161
|
case "thinking":
|
|
10825
|
-
if (!
|
|
12162
|
+
if (!compact2) process.stderr.write(this.paint.dim(`${this.glyphs.meta} reasoning ${this.glyphs.separator} turn ${event.turn}
|
|
10826
12163
|
`));
|
|
10827
12164
|
break;
|
|
10828
12165
|
case "context":
|
|
@@ -10832,7 +12169,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
10832
12169
|
));
|
|
10833
12170
|
break;
|
|
10834
12171
|
case "prompt":
|
|
10835
|
-
if (!
|
|
12172
|
+
if (!compact2) {
|
|
10836
12173
|
process.stderr.write(this.paint.dim(
|
|
10837
12174
|
`${this.glyphs.meta} prompt ${this.glyphs.separator} ${event.intent} ${this.glyphs.separator} ${event.sections.join(" + ")} ${this.glyphs.separator} ~${event.estimatedTokens} tokens
|
|
10838
12175
|
`
|
|
@@ -10867,12 +12204,21 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
10867
12204
|
}
|
|
10868
12205
|
break;
|
|
10869
12206
|
case "tasks":
|
|
10870
|
-
if (!
|
|
12207
|
+
if (!compact2) {
|
|
10871
12208
|
const completed = event.tasks.filter((task) => task.status === "completed").length;
|
|
10872
12209
|
process.stderr.write(this.paint.dim(`${this.glyphs.meta} plan ${this.glyphs.separator} ${completed}/${event.tasks.length} complete
|
|
10873
12210
|
`));
|
|
10874
12211
|
}
|
|
10875
12212
|
break;
|
|
12213
|
+
case "contract": {
|
|
12214
|
+
const required = event.contract.acceptanceCriteria.filter((item) => item.required);
|
|
12215
|
+
const satisfied = required.filter((item) => item.status === "satisfied").length;
|
|
12216
|
+
process.stderr.write(this.paint.dim(
|
|
12217
|
+
`${this.glyphs.meta} contract ${this.glyphs.separator} ${event.contract.state} ${this.glyphs.separator} ${satisfied}/${required.length} accepted
|
|
12218
|
+
`
|
|
12219
|
+
));
|
|
12220
|
+
break;
|
|
12221
|
+
}
|
|
10876
12222
|
case "writer_lane":
|
|
10877
12223
|
process.stderr.write(
|
|
10878
12224
|
`${event.status === "ready" || event.status === "integrated" ? this.paint.green(this.glyphs.success) : this.paint.red(this.glyphs.error)} writer ${event.id.slice(0, 8)} ${this.glyphs.separator} ${event.status} ${this.glyphs.separator} ${event.detail}
|
|
@@ -10954,6 +12300,7 @@ function sessionSummary(session) {
|
|
|
10954
12300
|
createdAt: session.createdAt,
|
|
10955
12301
|
updatedAt: session.updatedAt,
|
|
10956
12302
|
tasks: session.tasks,
|
|
12303
|
+
...session.taskContract ? { taskContract: session.taskContract } : {},
|
|
10957
12304
|
changedFiles: session.changedFiles,
|
|
10958
12305
|
...session.lastRun ? { lastRun: session.lastRun } : {},
|
|
10959
12306
|
usage: session.usage
|
|
@@ -10981,7 +12328,7 @@ function formatResultDetail(result, paint = chalk2, glyphs = resolveCliGlyphs())
|
|
|
10981
12328
|
}
|
|
10982
12329
|
|
|
10983
12330
|
// src/cli/namespace-leases.ts
|
|
10984
|
-
import { resolve as
|
|
12331
|
+
import { resolve as resolve18 } from "node:path";
|
|
10985
12332
|
function cliNamespaceLeaseScopes(actionCommand) {
|
|
10986
12333
|
const names = commandNames(actionCommand);
|
|
10987
12334
|
const topLevel = names[1];
|
|
@@ -11003,7 +12350,7 @@ async function acquireCliNamespaceLeases(actionCommand) {
|
|
|
11003
12350
|
const scopes = cliNamespaceLeaseScopes(actionCommand);
|
|
11004
12351
|
const localOptions = actionCommand.opts();
|
|
11005
12352
|
const globalOptions = actionCommand.optsWithGlobals();
|
|
11006
|
-
const workspace =
|
|
12353
|
+
const workspace = resolve18(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
|
|
11007
12354
|
const targets = scopes.map((scope) => scope === "project" ? projectNamespacePaths(workspace).canonical : homeNamespacePaths().canonical);
|
|
11008
12355
|
const leases = [];
|
|
11009
12356
|
try {
|
|
@@ -11157,11 +12504,11 @@ function command(name, description, usage, aliases) {
|
|
|
11157
12504
|
|
|
11158
12505
|
// src/ui/text.ts
|
|
11159
12506
|
import stringWidth from "string-width";
|
|
11160
|
-
import
|
|
12507
|
+
import stripAnsi2 from "strip-ansi";
|
|
11161
12508
|
var controlCharacters = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
|
|
11162
12509
|
var escapeSequences = /[[\]][0-9;?=>!]*[ -/]*[@-~]|[[\]][?=>][0-9;?=>!]*[a-zA-Z~]/gu;
|
|
11163
12510
|
function sanitizeTerminalText(value) {
|
|
11164
|
-
return
|
|
12511
|
+
return stripAnsi2(value).replace(escapeSequences, "").replace(/\r\n?/g, "\n").replace(controlCharacters, "");
|
|
11165
12512
|
}
|
|
11166
12513
|
function terminalEllipsis() {
|
|
11167
12514
|
return process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii" ? "..." : "\u2026";
|
|
@@ -11254,8 +12601,8 @@ function graphemes(value) {
|
|
|
11254
12601
|
}
|
|
11255
12602
|
|
|
11256
12603
|
// src/ui/theme.ts
|
|
11257
|
-
import { lstat as
|
|
11258
|
-
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";
|
|
11259
12606
|
import React, { createContext, useContext } from "react";
|
|
11260
12607
|
function defineTheme(seed) {
|
|
11261
12608
|
return {
|
|
@@ -11377,10 +12724,10 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
11377
12724
|
userThemeNames.clear();
|
|
11378
12725
|
const loaded = [];
|
|
11379
12726
|
const errors = [];
|
|
11380
|
-
const resolvedDirectory =
|
|
12727
|
+
const resolvedDirectory = resolve19(directory);
|
|
11381
12728
|
let entries;
|
|
11382
12729
|
try {
|
|
11383
|
-
entries = await
|
|
12730
|
+
entries = await readdir8(resolvedDirectory, { encoding: "utf8" });
|
|
11384
12731
|
} catch (error) {
|
|
11385
12732
|
if (error.code === "ENOENT") {
|
|
11386
12733
|
return { directory: resolvedDirectory, loaded, errors };
|
|
@@ -11389,13 +12736,13 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
11389
12736
|
}
|
|
11390
12737
|
for (const entry of entries) {
|
|
11391
12738
|
if (!entry.endsWith(".json")) continue;
|
|
11392
|
-
const path =
|
|
12739
|
+
const path = join19(resolvedDirectory, entry);
|
|
11393
12740
|
try {
|
|
11394
|
-
const info = await
|
|
12741
|
+
const info = await lstat20(path);
|
|
11395
12742
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 64e3) {
|
|
11396
12743
|
throw new Error("must be a regular JSON file smaller than 64 KB");
|
|
11397
12744
|
}
|
|
11398
|
-
const parsed = JSON.parse(await
|
|
12745
|
+
const parsed = JSON.parse(await readFile16(path, "utf8"));
|
|
11399
12746
|
const name = themeName(parsed, basename9(entry, ".json"));
|
|
11400
12747
|
if (builtInThemeNames.has(name)) throw new Error(`cannot replace built-in theme \`${name}\``);
|
|
11401
12748
|
themes[name] = defineTheme(themeSeed(parsed, name));
|
|
@@ -11408,7 +12755,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
11408
12755
|
return { directory: resolvedDirectory, loaded, errors };
|
|
11409
12756
|
}
|
|
11410
12757
|
function userThemeDirectory(environment = process.env) {
|
|
11411
|
-
return environment.SKEIN_THEME_DIR ?? environment.MOSAIC_THEME_DIR ??
|
|
12758
|
+
return environment.SKEIN_THEME_DIR ?? environment.MOSAIC_THEME_DIR ?? join19(resolveHomeNamespace(environment), "themes");
|
|
11412
12759
|
}
|
|
11413
12760
|
var defaultTheme = themes.graphite;
|
|
11414
12761
|
var palette = {
|
|
@@ -11644,7 +12991,7 @@ function ToolGlyph({ state, glyphs }) {
|
|
|
11644
12991
|
if (state === "cancelled") return /* @__PURE__ */ jsx(Text, { color: theme.warning, children: glyphs.warning });
|
|
11645
12992
|
return /* @__PURE__ */ jsx(Text, { color: theme.error, children: glyphs.error });
|
|
11646
12993
|
}
|
|
11647
|
-
function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = false, expandedToolId, compact = false }) {
|
|
12994
|
+
function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = false, expandedToolId, compact: compact2 = false }) {
|
|
11648
12995
|
const theme = useTheme();
|
|
11649
12996
|
const glyphs = resolveGlyphs(glyphMode);
|
|
11650
12997
|
if (!items.length) {
|
|
@@ -11652,13 +12999,13 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
11652
12999
|
}
|
|
11653
13000
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
|
|
11654
13001
|
if (item.kind === "user") {
|
|
11655
|
-
return /* @__PURE__ */ jsxs(Box, { marginBottom:
|
|
13002
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: compact2 || item.clipped ? 0 : 1, children: [
|
|
11656
13003
|
/* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: glyphs.prompt }) }),
|
|
11657
13004
|
/* @__PURE__ */ jsx(Text, { bold: true, color: theme.textStrong, wrap: "wrap", children: sanitizeTerminalText(item.text) })
|
|
11658
13005
|
] }, item.id);
|
|
11659
13006
|
}
|
|
11660
13007
|
if (item.kind === "assistant") {
|
|
11661
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom:
|
|
13008
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: compact2 || item.clipped ? 0 : 1, children: [
|
|
11662
13009
|
/* @__PURE__ */ jsxs(Text, { bold: true, color: theme.accent, children: [
|
|
11663
13010
|
glyphs.brand,
|
|
11664
13011
|
" ",
|
|
@@ -11674,7 +13021,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
11674
13021
|
}
|
|
11675
13022
|
if (item.kind === "context") {
|
|
11676
13023
|
const spans = item.spans ?? [];
|
|
11677
|
-
const spanLimit =
|
|
13024
|
+
const spanLimit = compact2 ? 2 : 3;
|
|
11678
13025
|
const innerWidth = Math.max(1, safeWidth(width) - 2);
|
|
11679
13026
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
11680
13027
|
/* @__PURE__ */ jsx(
|
|
@@ -11725,7 +13072,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
11725
13072
|
const duration = item.durationMs !== void 0 ? formatDuration(item.durationMs) : "";
|
|
11726
13073
|
const detailText = [detail, duration].filter(Boolean).join(" ");
|
|
11727
13074
|
const expanded = Boolean(item.output) && (showToolOutput || expandedToolId === item.id);
|
|
11728
|
-
const verbose = expanded && item.output ? limitTerminalText(item.output,
|
|
13075
|
+
const verbose = expanded && item.output ? limitTerminalText(item.output, compact2 ? 24 : 80) : void 0;
|
|
11729
13076
|
const disclosure = item.output ? expanded ? glyphs.expanded : glyphs.collapsed : "";
|
|
11730
13077
|
const disclosureWidth = disclosure ? displayWidth(disclosure) + 1 : 0;
|
|
11731
13078
|
const nameLimit = Math.max(1, Math.min(rowWidth - 2 - disclosureWidth, rowWidth < 64 ? rowWidth - 2 - disclosureWidth : 28));
|
|
@@ -11859,7 +13206,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
11859
13206
|
}
|
|
11860
13207
|
if (item.kind === "list") return /* @__PURE__ */ jsx(ListPanel, { title: item.title, entries: item.entries, width, glyphMode }, item.id);
|
|
11861
13208
|
if (item.kind === "context-inspector") {
|
|
11862
|
-
return /* @__PURE__ */ jsx(ContextInspector, { status: item.status, working: item.working, summary: item.summary, width, compact, glyphMode }, item.id);
|
|
13209
|
+
return /* @__PURE__ */ jsx(ContextInspector, { status: item.status, working: item.working, summary: item.summary, width, compact: compact2, glyphMode }, item.id);
|
|
11863
13210
|
}
|
|
11864
13211
|
if (item.kind === "theme") return /* @__PURE__ */ jsx(ThemePreview, { name: item.name, width, glyphs }, item.id);
|
|
11865
13212
|
if (item.kind === "banner") {
|
|
@@ -12069,7 +13416,7 @@ function TaskRail({ tasks, width = 80, glyphMode = "auto", maxItems }) {
|
|
|
12069
13416
|
] }) : null
|
|
12070
13417
|
] });
|
|
12071
13418
|
}
|
|
12072
|
-
function PermissionCard({ call, category, width = 80, glyphMode = "auto", workspace, compact = false }) {
|
|
13419
|
+
function PermissionCard({ call, category, width = 80, glyphMode = "auto", workspace, compact: compact2 = false }) {
|
|
12073
13420
|
const theme = useTheme();
|
|
12074
13421
|
const glyphs = resolveGlyphs(glyphMode);
|
|
12075
13422
|
const summary = permissionSummary(call);
|
|
@@ -12106,7 +13453,7 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
|
|
|
12106
13453
|
rowWidth >= 64 ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts, width: innerWidth, separator: ` ${glyphs.separator} `, separatorColor: theme.border }) }) : rowWidth >= 28 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
|
|
12107
13454
|
/* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
|
|
12108
13455
|
/* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
|
|
12109
|
-
] }) :
|
|
13456
|
+
] }) : compact2 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
|
|
12110
13457
|
/* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
|
|
12111
13458
|
/* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
|
|
12112
13459
|
] }) : /* @__PURE__ */ jsx(Box, { paddingLeft: 2, flexDirection: "column", children: shortcuts.map((part) => part.color ? /* @__PURE__ */ jsx(Text, { color: part.color, children: truncateDisplay(part.text, innerWidth) }, part.text) : /* @__PURE__ */ jsx(Text, { children: truncateDisplay(part.text, innerWidth) }, part.text)) })
|
|
@@ -12367,7 +13714,7 @@ function MeterBar({ segments, total, width, glyphs }) {
|
|
|
12367
13714
|
remainder ? /* @__PURE__ */ jsx(Text, { color: theme.border, children: glyphs.meterEmpty.repeat(remainder) }) : null
|
|
12368
13715
|
] });
|
|
12369
13716
|
}
|
|
12370
|
-
function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact = false, minimal = false, glyphMode = "auto" }) {
|
|
13717
|
+
function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact: compact2 = false, minimal = false, glyphMode = "auto" }) {
|
|
12371
13718
|
const theme = useTheme();
|
|
12372
13719
|
const glyphs = resolveGlyphs(glyphMode);
|
|
12373
13720
|
if (minimal) {
|
|
@@ -12387,10 +13734,10 @@ function ContextInspector({ status, working, summary, width, memory, connections
|
|
|
12387
13734
|
{ label: "summary", detail: summary ? `~${formatTokens(status.summaryTokens)} tokens ${glyphs.separator} ${status.compactedMessages} compacted` : "not created" },
|
|
12388
13735
|
{ label: "long-term", detail: memory ?? `retrieved by relevance ${glyphs.separator} untrusted context` }
|
|
12389
13736
|
];
|
|
12390
|
-
if (!
|
|
12391
|
-
if (!
|
|
12392
|
-
if (!
|
|
12393
|
-
if (!
|
|
13737
|
+
if (!compact2 && working?.constraints.length) entries.push({ label: `constraints ${working.constraints.length}`, detail: working.constraints.slice(0, 2).join(` ${glyphs.separator} `) });
|
|
13738
|
+
if (!compact2 && working?.decisions.length) entries.push({ label: `decisions ${working.decisions.length}`, detail: working.decisions.slice(0, 2).join(` ${glyphs.separator} `) });
|
|
13739
|
+
if (!compact2 && working?.openQuestions.length) entries.push({ label: `open ${working.openQuestions.length}`, detail: working.openQuestions.slice(0, 2).join(` ${glyphs.separator} `), tone: "warning" });
|
|
13740
|
+
if (!compact2 && working?.relevantFiles.length) entries.push({ label: "relevant files", detail: working.relevantFiles.map((file) => compactDisplayPath(sanitizeInlineTerminalText(file), 28)).join(` ${glyphs.separator} `) });
|
|
12394
13741
|
if (sources2?.length) {
|
|
12395
13742
|
const pinned = sources2.filter((source) => source.state === "pinned");
|
|
12396
13743
|
const muted = sources2.filter((source) => source.state === "muted");
|
|
@@ -12511,8 +13858,8 @@ function Banner({ model, engine, workspace, version, width, glyphs }) {
|
|
|
12511
13858
|
function UpdateNotice({ current, latest, command: command2, highlights, width, glyphs }) {
|
|
12512
13859
|
const theme = useTheme();
|
|
12513
13860
|
const availableWidth = safeWidth(width);
|
|
12514
|
-
const
|
|
12515
|
-
const parts =
|
|
13861
|
+
const compact2 = availableWidth < 48;
|
|
13862
|
+
const parts = compact2 ? [
|
|
12516
13863
|
{ text: `${glyphs.up} `, color: theme.accent, bold: true },
|
|
12517
13864
|
{ text: `v${current}`, color: theme.dim, bold: false },
|
|
12518
13865
|
{ text: ` ${glyphs.arrow} `, color: theme.muted, bold: false },
|
|
@@ -12533,7 +13880,7 @@ function UpdateNotice({ current, latest, command: command2, highlights, width, g
|
|
|
12533
13880
|
const bullets = bulletWidth > 0 ? (highlights ?? []).map((line) => truncateDisplay(sanitizeInlineTerminalText(line), bulletWidth)) : [];
|
|
12534
13881
|
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [
|
|
12535
13882
|
/* @__PURE__ */ jsx(Box, { children: truncated ? /* @__PURE__ */ jsx(Text, { color: theme.muted, children: rendered }) : parts.map((part, index) => /* @__PURE__ */ jsx(Text, { color: part.color, bold: part.bold, children: part.text }, index)) }),
|
|
12536
|
-
|
|
13883
|
+
compact2 ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(` run ${command2}`, availableWidth) }) : null,
|
|
12537
13884
|
bullets.map((line, index) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: `${bulletPrefix}${line}` }, index))
|
|
12538
13885
|
] });
|
|
12539
13886
|
}
|
|
@@ -12635,9 +13982,9 @@ function safeWidth(width) {
|
|
|
12635
13982
|
}
|
|
12636
13983
|
|
|
12637
13984
|
// src/utils/update-check.ts
|
|
12638
|
-
import { mkdir as mkdir9, readFile as
|
|
12639
|
-
import { join as
|
|
12640
|
-
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";
|
|
12641
13988
|
var PACKAGE_NAME = "@skein-code/cli";
|
|
12642
13989
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
12643
13990
|
var CACHE_FILE = "update-check.json";
|
|
@@ -12652,7 +13999,7 @@ function sanitizeHighlights(value) {
|
|
|
12652
13999
|
const cleaned = [];
|
|
12653
14000
|
for (const entry of value) {
|
|
12654
14001
|
if (typeof entry !== "string") continue;
|
|
12655
|
-
const flattened =
|
|
14002
|
+
const flattened = stripAnsi3(entry).replace(BIDI_CONTROLS, "").replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
12656
14003
|
if (!flattened || flattened.length > MAX_HIGHLIGHT_LENGTH) continue;
|
|
12657
14004
|
cleaned.push(flattened);
|
|
12658
14005
|
if (cleaned.length >= MAX_HIGHLIGHTS) break;
|
|
@@ -12716,7 +14063,7 @@ function compareVersions(a, b) {
|
|
|
12716
14063
|
return 0;
|
|
12717
14064
|
}
|
|
12718
14065
|
function updateCachePath(env = process.env) {
|
|
12719
|
-
return
|
|
14066
|
+
return join20(resolveHomeNamespace(env), CACHE_FILE);
|
|
12720
14067
|
}
|
|
12721
14068
|
function upgradeCommand() {
|
|
12722
14069
|
return `npm i -g ${PACKAGE_NAME}`;
|
|
@@ -12733,7 +14080,7 @@ function noticeIfNewer(latest, current, highlights) {
|
|
|
12733
14080
|
}
|
|
12734
14081
|
async function readUpdateCache(env = process.env) {
|
|
12735
14082
|
try {
|
|
12736
|
-
const raw = await
|
|
14083
|
+
const raw = await readFile17(updateCachePath(env), "utf8");
|
|
12737
14084
|
const parsed = JSON.parse(raw);
|
|
12738
14085
|
if (typeof parsed?.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && parsed.checkedAt >= 0 && (parsed.latest === null || typeof parsed.latest === "string" && parseVersion(parsed.latest))) {
|
|
12739
14086
|
const latest = typeof parsed.latest === "string" ? parsed.latest : null;
|
|
@@ -13441,9 +14788,9 @@ function tailText(value, width, maxRows) {
|
|
|
13441
14788
|
return `${marker}
|
|
13442
14789
|
${selected.join("\n")}`;
|
|
13443
14790
|
}
|
|
13444
|
-
function estimateTimelineItemRows(item, { width, compact = false, showToolOutput = false, expandedToolId }) {
|
|
14791
|
+
function estimateTimelineItemRows(item, { width, compact: compact2 = false, showToolOutput = false, expandedToolId }) {
|
|
13445
14792
|
const rowWidth = Math.max(1, Math.floor(width));
|
|
13446
|
-
const gap =
|
|
14793
|
+
const gap = compact2 ? 0 : 1;
|
|
13447
14794
|
if (item.kind === "user") return wrappedRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
|
|
13448
14795
|
if (item.kind === "assistant") {
|
|
13449
14796
|
return 1 + richTextRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
|
|
@@ -13455,7 +14802,7 @@ function estimateTimelineItemRows(item, { width, compact = false, showToolOutput
|
|
|
13455
14802
|
const detail = item.errorDetail || item.detail;
|
|
13456
14803
|
const detailRows = narrow && detail ? 1 : 0;
|
|
13457
14804
|
const metaRows = item.meta ? 1 : 0;
|
|
13458
|
-
const outputRows = (showToolOutput || item.id === expandedToolId) && item.output ? Math.min(
|
|
14805
|
+
const outputRows = (showToolOutput || item.id === expandedToolId) && item.output ? Math.min(compact2 ? 25 : 81, richTextRows(item.output, Math.max(1, rowWidth - 2))) : 0;
|
|
13459
14806
|
return 1 + detailRows + metaRows + outputRows;
|
|
13460
14807
|
}
|
|
13461
14808
|
if (item.kind === "list") {
|
|
@@ -13469,7 +14816,7 @@ function estimateTimelineItemRows(item, { width, compact = false, showToolOutput
|
|
|
13469
14816
|
if (item.kind === "theme") return 3;
|
|
13470
14817
|
if (item.kind === "context") {
|
|
13471
14818
|
const metaRows = rowWidth < 64 ? 2 : 1;
|
|
13472
|
-
const spanLimit =
|
|
14819
|
+
const spanLimit = compact2 ? 2 : 3;
|
|
13473
14820
|
const spanCount = Math.min(item.spans?.length ?? 0, spanLimit);
|
|
13474
14821
|
const moreRow = (item.spans?.length ?? 0) > spanLimit ? 1 : 0;
|
|
13475
14822
|
const degradationRows = item.degradation ? metaRows : 0;
|
|
@@ -13567,6 +14914,21 @@ function finalizeAssistant(items, id, content) {
|
|
|
13567
14914
|
function endStreamingAssistants(items) {
|
|
13568
14915
|
return items.map((item) => item.kind === "assistant" && item.streaming ? { ...item, streaming: false } : item);
|
|
13569
14916
|
}
|
|
14917
|
+
function updateContractProgress(items, contract, separator = " | ") {
|
|
14918
|
+
const id = "task-contract-progress";
|
|
14919
|
+
if (contract.state === "satisfied") return items.filter((item) => item.id !== id);
|
|
14920
|
+
const required = contract.acceptanceCriteria.filter((item) => item.required);
|
|
14921
|
+
const satisfied = required.filter((item) => item.status === "satisfied").length;
|
|
14922
|
+
const next = {
|
|
14923
|
+
id,
|
|
14924
|
+
kind: "notice",
|
|
14925
|
+
tone: contract.state === "blocked" ? "error" : "info",
|
|
14926
|
+
text: `Contract ${contract.state}${separator}${satisfied}/${required.length} accepted`
|
|
14927
|
+
};
|
|
14928
|
+
const index = items.findIndex((item) => item.id === id);
|
|
14929
|
+
if (index < 0) return [...items, next].slice(-500);
|
|
14930
|
+
return items.map((item, itemIndex) => itemIndex === index ? next : item);
|
|
14931
|
+
}
|
|
13570
14932
|
function updateAgent(items, event) {
|
|
13571
14933
|
const found = items.some((item) => item.kind === "agent" && item.id === event.id);
|
|
13572
14934
|
if (!found) {
|
|
@@ -13659,7 +15021,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
13659
15021
|
const colorEnabled = config.ui.color && !process.env.NO_COLOR;
|
|
13660
15022
|
const [theme, setTheme] = useState2(() => resolveThemeWithColor(config.ui.theme, colorEnabled));
|
|
13661
15023
|
const [themeCatalogRevision, setThemeCatalogRevision] = useState2(0);
|
|
13662
|
-
const [
|
|
15024
|
+
const [compact2, setCompact] = useState2(config.ui.compact);
|
|
13663
15025
|
const [interactionMode, setInteractionMode] = useState2(planMode ? "plan" : askMode ? "ask" : "build");
|
|
13664
15026
|
const [input2, setInput] = useState2("");
|
|
13665
15027
|
const [busy, setBusy] = useState2(false);
|
|
@@ -13799,7 +15161,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
13799
15161
|
return () => clearInterval(timer);
|
|
13800
15162
|
}, [busy]);
|
|
13801
15163
|
const requestPermission = useCallback((call, category) => {
|
|
13802
|
-
return new Promise((
|
|
15164
|
+
return new Promise((resolve24) => setPermission({ call, category, resolve: resolve24 }));
|
|
13803
15165
|
}, []);
|
|
13804
15166
|
const onEvent = useCallback((event) => {
|
|
13805
15167
|
switch (event.type) {
|
|
@@ -13859,6 +15221,11 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
13859
15221
|
case "tasks":
|
|
13860
15222
|
setTasks(event.tasks.map((task) => ({ ...task })));
|
|
13861
15223
|
break;
|
|
15224
|
+
case "contract": {
|
|
15225
|
+
setTimeline((items) => updateContractProgress(items, event.contract, separator));
|
|
15226
|
+
refreshSession();
|
|
15227
|
+
break;
|
|
15228
|
+
}
|
|
13862
15229
|
case "skill":
|
|
13863
15230
|
append({ id: nextId(), kind: "skill", name: event.name, description: event.description });
|
|
13864
15231
|
break;
|
|
@@ -14351,7 +15718,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
14351
15718
|
}
|
|
14352
15719
|
if (command2 === "density") {
|
|
14353
15720
|
const normalized = argument.toLocaleLowerCase();
|
|
14354
|
-
const next = normalized === "compact" ? true : normalized === "comfortable" || normalized === "normal" ? false : normalized === "" || normalized === "toggle" ? !
|
|
15721
|
+
const next = normalized === "compact" ? true : normalized === "comfortable" || normalized === "normal" ? false : normalized === "" || normalized === "toggle" ? !compact2 : void 0;
|
|
14355
15722
|
if (next === void 0) throw new Error("Usage: /density [compact|comfortable]");
|
|
14356
15723
|
setCompact(next);
|
|
14357
15724
|
await saveUiPreference({ compact: next });
|
|
@@ -14444,7 +15811,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
14444
15811
|
})));
|
|
14445
15812
|
return true;
|
|
14446
15813
|
}
|
|
14447
|
-
}, [append, appendList,
|
|
15814
|
+
}, [append, appendList, compact2, config, ellipsis, exit, extensions, interactionMode, refreshSession, requestPermission, runner, separator, showToolOutput, tasks, theme, workflows]);
|
|
14448
15815
|
const submit = useCallback(async (raw, mode = "normal") => {
|
|
14449
15816
|
const trimmed = raw.trim();
|
|
14450
15817
|
if (!trimmed) return;
|
|
@@ -14618,8 +15985,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
14618
15985
|
}, [append]);
|
|
14619
15986
|
function settlePermission(grant, stop = false) {
|
|
14620
15987
|
if (!permission) return;
|
|
14621
|
-
const { call, category, resolve:
|
|
14622
|
-
|
|
15988
|
+
const { call, category, resolve: resolve24 } = permission;
|
|
15989
|
+
resolve24(grant);
|
|
14623
15990
|
setPermission(void 0);
|
|
14624
15991
|
if (grant === "session") {
|
|
14625
15992
|
append({
|
|
@@ -14808,7 +16175,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
14808
16175
|
const tokenTotal = session.usage.inputTokens + session.usage.outputTokens;
|
|
14809
16176
|
const contextStatus = runner.getContextStatus();
|
|
14810
16177
|
const frame = spinnerFrames()[frameIndex % spinnerFrames().length];
|
|
14811
|
-
const compactUi =
|
|
16178
|
+
const compactUi = compact2 || terminalHeight < 28;
|
|
14812
16179
|
const constrainedHeight = terminalHeight < 18;
|
|
14813
16180
|
const compactComposer = terminalHeight < 18;
|
|
14814
16181
|
const minimalInspector = terminalHeight < 22;
|
|
@@ -15014,6 +16381,16 @@ function initialTimeline(session, banner, setupProblem) {
|
|
|
15014
16381
|
if (!items.length) {
|
|
15015
16382
|
items.push({ id: nextId(), kind: "banner", model: banner.model, engine: banner.engine, workspace: banner.workspace, version: banner.version });
|
|
15016
16383
|
}
|
|
16384
|
+
if (session.taskContract && session.taskContract.state !== "satisfied") {
|
|
16385
|
+
const required = session.taskContract.acceptanceCriteria.filter((item) => item.required);
|
|
16386
|
+
const satisfied = required.filter((item) => item.status === "satisfied").length;
|
|
16387
|
+
items.push({
|
|
16388
|
+
id: "task-contract-progress",
|
|
16389
|
+
kind: "notice",
|
|
16390
|
+
tone: session.taskContract.state === "blocked" ? "error" : "info",
|
|
16391
|
+
text: `Contract ${session.taskContract.state} | ${satisfied}/${required.length} accepted`
|
|
16392
|
+
});
|
|
16393
|
+
}
|
|
15017
16394
|
if (setupProblem && items.length <= 1) items.push({ id: nextId(), kind: "notice", tone: "error", text: setupProblem });
|
|
15018
16395
|
return items;
|
|
15019
16396
|
}
|
|
@@ -15105,17 +16482,17 @@ function composerAttachments(value) {
|
|
|
15105
16482
|
const paths = [...value.matchAll(/(?:^|\s)@([^\s]+)/g)].map((match) => match[1]).filter((path) => Boolean(path));
|
|
15106
16483
|
return [...new Set(paths)].slice(-3);
|
|
15107
16484
|
}
|
|
15108
|
-
function permissionRows(width, hasCwd,
|
|
16485
|
+
function permissionRows(width, hasCwd, compact2) {
|
|
15109
16486
|
const content = 3 + (hasCwd ? 1 : 0);
|
|
15110
16487
|
if (width >= 64) return content + 2;
|
|
15111
16488
|
if (width >= 28) return content + 3;
|
|
15112
|
-
if (
|
|
16489
|
+
if (compact2) return content + 3;
|
|
15113
16490
|
return content + 5;
|
|
15114
16491
|
}
|
|
15115
|
-
function contextInspectorRows(session,
|
|
16492
|
+
function contextInspectorRows(session, compact2, width, minimal) {
|
|
15116
16493
|
if (minimal) return 2;
|
|
15117
16494
|
const working = session.workingMemory;
|
|
15118
|
-
const entries = 5 + (
|
|
16495
|
+
const entries = 5 + (compact2 ? 0 : (working?.constraints.length ? 1 : 0) + (working?.decisions.length ? 1 : 0) + (working?.openQuestions.length ? 1 : 0) + (working?.relevantFiles.length ? 1 : 0));
|
|
15119
16496
|
return 2 + entries * (width < 52 ? 2 : 1);
|
|
15120
16497
|
}
|
|
15121
16498
|
function contextInspectorStatus(status) {
|
|
@@ -15167,14 +16544,14 @@ function WorkspacePreparationView({
|
|
|
15167
16544
|
const theme = useTheme();
|
|
15168
16545
|
const safeWidth2 = Math.max(1, Math.floor(width));
|
|
15169
16546
|
const innerWidth = Math.max(1, safeWidth2 - (safeWidth2 >= 24 ? 4 : 0));
|
|
15170
|
-
const
|
|
16547
|
+
const compact2 = safeWidth2 < 48;
|
|
15171
16548
|
const constrained = height < 14;
|
|
15172
16549
|
const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
|
|
15173
16550
|
const spinner = (ascii ? [".", "o", "O", "o"] : ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"])[frame % (ascii ? 4 : 6)];
|
|
15174
16551
|
const separator = ascii ? "|" : "\xB7";
|
|
15175
16552
|
const brand = ascii ? "*" : PRODUCT_MARK;
|
|
15176
16553
|
const phase = readiness ? "ready" : error ? "error" : progress.phase;
|
|
15177
|
-
const phaseLabel = preparationLabel(phase, progress, readiness,
|
|
16554
|
+
const phaseLabel = preparationLabel(phase, progress, readiness, compact2);
|
|
15178
16555
|
const detail = preparationDetail(phase, progress, readiness, error);
|
|
15179
16556
|
const modelLine = `model ${sanitizeTerminalText(model)}`;
|
|
15180
16557
|
const workspaceLine = `workspace ${compactDisplayPath(sanitizeTerminalText(workspace), Math.max(1, innerWidth - 10))}`;
|
|
@@ -15186,14 +16563,14 @@ function WorkspacePreparationView({
|
|
|
15186
16563
|
" ",
|
|
15187
16564
|
PRODUCT_NAME.toUpperCase()
|
|
15188
16565
|
] }),
|
|
15189
|
-
!
|
|
16566
|
+
!compact2 && !constrained ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
|
|
15190
16567
|
" ",
|
|
15191
16568
|
separator,
|
|
15192
16569
|
" LOCAL CONTEXT"
|
|
15193
16570
|
] }) : null
|
|
15194
16571
|
] }),
|
|
15195
16572
|
!constrained ? /* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: truncateDisplay("Ground the workspace before the first request.", innerWidth) }) : null,
|
|
15196
|
-
!constrained && !
|
|
16573
|
+
!constrained && !compact2 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(`${modelLine} ${separator} ${workspaceLine}`, innerWidth) }) : !constrained ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
15197
16574
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(modelLine, innerWidth) }),
|
|
15198
16575
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) })
|
|
15199
16576
|
] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) }),
|
|
@@ -15207,10 +16584,10 @@ function WorkspacePreparationView({
|
|
|
15207
16584
|
step2.glyph,
|
|
15208
16585
|
" "
|
|
15209
16586
|
] }),
|
|
15210
|
-
/* @__PURE__ */ jsx4(Text4, { bold: step2.state === "active", color: step2.state === "pending" ? theme.dim : theme.textStrong, children: truncateDisplay(step2.label,
|
|
16587
|
+
/* @__PURE__ */ jsx4(Text4, { bold: step2.state === "active", color: step2.state === "pending" ? theme.dim : theme.textStrong, children: truncateDisplay(step2.label, compact2 ? 8 : 10) }),
|
|
15211
16588
|
/* @__PURE__ */ jsxs4(Text4, { color: step2.state === "active" ? theme.muted : theme.dim, children: [
|
|
15212
16589
|
" ",
|
|
15213
|
-
truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (
|
|
16590
|
+
truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (compact2 ? 12 : 14)))
|
|
15214
16591
|
] })
|
|
15215
16592
|
] }, step2.id)) }),
|
|
15216
16593
|
/* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
|
|
@@ -15351,9 +16728,9 @@ async function runWorkspacePreparation(engine, config, options = {}) {
|
|
|
15351
16728
|
await instance.waitUntilExit();
|
|
15352
16729
|
return result ?? { status: "cancelled" };
|
|
15353
16730
|
}
|
|
15354
|
-
function preparationLabel(phase, progress, readiness,
|
|
16731
|
+
function preparationLabel(phase, progress, readiness, compact2 = false) {
|
|
15355
16732
|
if (phase === "ready") {
|
|
15356
|
-
if (
|
|
16733
|
+
if (compact2) return "Index verified";
|
|
15357
16734
|
return readiness?.rebuilt ? "Workspace index created and verified" : "Workspace index verified";
|
|
15358
16735
|
}
|
|
15359
16736
|
if (phase === "error") return "Workspace preparation failed";
|
|
@@ -15757,7 +17134,7 @@ function OnboardingFlow({ initialConfig, saveConfig, onFinish }) {
|
|
|
15757
17134
|
}, [finish, saveConfig, state]);
|
|
15758
17135
|
return /* @__PURE__ */ jsx5(OnboardingScreen, { state, dispatch, width, compact: compactHeight });
|
|
15759
17136
|
}
|
|
15760
|
-
function OnboardingScreen({ state, dispatch, width, compact = false }) {
|
|
17137
|
+
function OnboardingScreen({ state, dispatch, width, compact: compact2 = false }) {
|
|
15761
17138
|
const theme = useTheme();
|
|
15762
17139
|
const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
|
|
15763
17140
|
const marker = ascii ? ">" : "\u203A";
|
|
@@ -15774,14 +17151,14 @@ function OnboardingScreen({ state, dispatch, width, compact = false }) {
|
|
|
15774
17151
|
] }),
|
|
15775
17152
|
/* @__PURE__ */ jsx5(Text6, { color: theme.border, children: truncateDisplay(stageDivider(ascii, headerWidth), headerWidth) }),
|
|
15776
17153
|
/* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(stage.name, headerWidth) }),
|
|
15777
|
-
!
|
|
17154
|
+
!compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
15778
17155
|
/* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: truncateDisplay(titleForStep(state.step), headerWidth) }),
|
|
15779
|
-
!
|
|
17156
|
+
!compact2 ? /* @__PURE__ */ jsx5(Text6, { color: theme.muted, wrap: "wrap", children: descriptionForStep(state) }) : null,
|
|
15780
17157
|
summary ? /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(summary, headerWidth) }) : null,
|
|
15781
|
-
!
|
|
15782
|
-
state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact }) : null,
|
|
15783
|
-
state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact }) : null,
|
|
15784
|
-
state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact }) : null,
|
|
17158
|
+
!compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
17159
|
+
state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
|
|
17160
|
+
state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
|
|
17161
|
+
state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
|
|
15785
17162
|
inputField ? /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
|
|
15786
17163
|
/* @__PURE__ */ jsxs5(Box4, { children: [
|
|
15787
17164
|
/* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: inputField.label }),
|
|
@@ -15809,18 +17186,18 @@ function OnboardingScreen({ state, dispatch, width, compact = false }) {
|
|
|
15809
17186
|
"! ",
|
|
15810
17187
|
truncateDisplay(state.error, Math.max(1, headerWidth - 2))
|
|
15811
17188
|
] }) : null,
|
|
15812
|
-
!
|
|
17189
|
+
!compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
15813
17190
|
/* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: footerForStep(state, headerWidth) })
|
|
15814
17191
|
] });
|
|
15815
17192
|
}
|
|
15816
|
-
function OptionList({ options, selected, marker, width, compact }) {
|
|
17193
|
+
function OptionList({ options, selected, marker, width, compact: compact2 }) {
|
|
15817
17194
|
const theme = useTheme();
|
|
15818
17195
|
return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((option, index) => {
|
|
15819
17196
|
const active = index === selected;
|
|
15820
17197
|
const prefix = active ? `${marker} ` : " ";
|
|
15821
17198
|
const available = Math.max(1, width - displayWidth(prefix) - (active ? 2 : 0));
|
|
15822
17199
|
const label = `${prefix}${truncateDisplay(option.label, available)}`;
|
|
15823
|
-
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginBottom:
|
|
17200
|
+
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginBottom: compact2 || index === options.length - 1 ? 0 : 1, children: [
|
|
15824
17201
|
/* @__PURE__ */ jsx5(
|
|
15825
17202
|
Text6,
|
|
15826
17203
|
{
|
|
@@ -15830,7 +17207,7 @@ function OptionList({ options, selected, marker, width, compact }) {
|
|
|
15830
17207
|
children: active ? padDisplay(label, width) : label
|
|
15831
17208
|
}
|
|
15832
17209
|
),
|
|
15833
|
-
!
|
|
17210
|
+
!compact2 && width >= 36 || active ? /* @__PURE__ */ jsx5(Box4, { marginLeft: 2, width: Math.max(1, width - 2), children: /* @__PURE__ */ jsx5(Text6, { color: active ? theme.muted : theme.dim, wrap: "wrap", children: option.detail }) }) : null
|
|
15834
17211
|
] }, option.label);
|
|
15835
17212
|
}) });
|
|
15836
17213
|
}
|
|
@@ -15944,24 +17321,24 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
|
15944
17321
|
}
|
|
15945
17322
|
|
|
15946
17323
|
// src/runtime/extensions.ts
|
|
15947
|
-
import { resolve as
|
|
17324
|
+
import { resolve as resolve22 } from "node:path";
|
|
15948
17325
|
|
|
15949
17326
|
// src/mcp/manager.ts
|
|
15950
17327
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
15951
17328
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
15952
17329
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
15953
|
-
import
|
|
17330
|
+
import stripAnsi5 from "strip-ansi";
|
|
15954
17331
|
|
|
15955
17332
|
// src/mcp/tool.ts
|
|
15956
|
-
import { createHash as
|
|
15957
|
-
import
|
|
17333
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
17334
|
+
import stripAnsi4 from "strip-ansi";
|
|
15958
17335
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
15959
17336
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
15960
|
-
var
|
|
17337
|
+
var MAX_RESULT_BYTES = 5 * 1024 * 1024;
|
|
15961
17338
|
var MAX_SCHEMA_BYTES = 1e5;
|
|
15962
17339
|
function createMcpToolAdapter(options) {
|
|
15963
17340
|
const { remoteTool } = options;
|
|
15964
|
-
const
|
|
17341
|
+
const inputSchema12 = copyInputSchema(remoteTool.inputSchema);
|
|
15965
17342
|
return {
|
|
15966
17343
|
definition: {
|
|
15967
17344
|
name: options.exposedName,
|
|
@@ -15969,7 +17346,7 @@ function createMcpToolAdapter(options) {
|
|
|
15969
17346
|
// MCP servers are an external trust boundary. Read-only annotations are
|
|
15970
17347
|
// hints from that server and must not lower the local permission level.
|
|
15971
17348
|
category: "network",
|
|
15972
|
-
inputSchema:
|
|
17349
|
+
inputSchema: inputSchema12
|
|
15973
17350
|
},
|
|
15974
17351
|
permissionCategories: () => ["network"],
|
|
15975
17352
|
async execute(arguments_, context) {
|
|
@@ -15989,6 +17366,8 @@ function createMcpToolAdapter(options) {
|
|
|
15989
17366
|
metadata: {
|
|
15990
17367
|
mcpServer: options.serverName,
|
|
15991
17368
|
mcpTool: remoteTool.name,
|
|
17369
|
+
sourceBytes: normalized.sourceBytes,
|
|
17370
|
+
sourceTruncated: normalized.sourceTruncated,
|
|
15992
17371
|
...normalized.isError ? { mcpError: true } : {}
|
|
15993
17372
|
}
|
|
15994
17373
|
};
|
|
@@ -16018,8 +17397,8 @@ function isUsableRemoteTool(tool) {
|
|
|
16018
17397
|
}
|
|
16019
17398
|
}
|
|
16020
17399
|
function describeTool(serverName, tool) {
|
|
16021
|
-
const label =
|
|
16022
|
-
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;
|
|
16023
17402
|
return description ? `[MCP ${serverName}/${label}] ${description}` : `Call the ${label} tool provided by the ${serverName} MCP server.`;
|
|
16024
17403
|
}
|
|
16025
17404
|
function copyInputSchema(schema) {
|
|
@@ -16042,7 +17421,7 @@ function assertArguments(arguments_) {
|
|
|
16042
17421
|
}
|
|
16043
17422
|
function normalizeCallResult(result) {
|
|
16044
17423
|
if (!isRecord2(result)) {
|
|
16045
|
-
return
|
|
17424
|
+
return boundResult(safeJson(result), false);
|
|
16046
17425
|
}
|
|
16047
17426
|
const isError = result.isError === true;
|
|
16048
17427
|
const sections = [];
|
|
@@ -16058,7 +17437,22 @@ ${safeJson(result.structuredContent)}`);
|
|
|
16058
17437
|
${safeJson(result.toolResult)}`);
|
|
16059
17438
|
}
|
|
16060
17439
|
const content = sections.filter(Boolean).join("\n\n") || (isError ? "The MCP tool reported an error." : "The MCP tool completed without output.");
|
|
16061
|
-
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 };
|
|
16062
17456
|
}
|
|
16063
17457
|
function formatContentBlock(block) {
|
|
16064
17458
|
if (!isRecord2(block) || typeof block.type !== "string") return safeJson(block);
|
|
@@ -16106,15 +17500,10 @@ function fitToolName(name, identity) {
|
|
|
16106
17500
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
16107
17501
|
}
|
|
16108
17502
|
function shortHash(value) {
|
|
16109
|
-
return
|
|
16110
|
-
}
|
|
16111
|
-
function truncateResult(content) {
|
|
16112
|
-
if (content.length <= MAX_RESULT_LENGTH) return content;
|
|
16113
|
-
return `${content.slice(0, MAX_RESULT_LENGTH)}
|
|
16114
|
-
... MCP result truncated`;
|
|
17503
|
+
return createHash14("sha256").update(value).digest("hex").slice(0, 8);
|
|
16115
17504
|
}
|
|
16116
17505
|
function sanitizeOutputText(value) {
|
|
16117
|
-
return
|
|
17506
|
+
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
16118
17507
|
}
|
|
16119
17508
|
function sanitizeInlineText(value) {
|
|
16120
17509
|
return sanitizeOutputText(value).replace(/[\r\n\t]+/g, " ").trim().slice(0, 2e3);
|
|
@@ -16126,13 +17515,25 @@ function safeJson(value) {
|
|
|
16126
17515
|
return String(value);
|
|
16127
17516
|
}
|
|
16128
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
|
+
}
|
|
16129
17530
|
function isRecord2(value) {
|
|
16130
17531
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16131
17532
|
}
|
|
16132
17533
|
|
|
16133
17534
|
// src/mcp/validation.ts
|
|
16134
17535
|
import { stat as stat10, realpath as realpath8 } from "node:fs/promises";
|
|
16135
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
17536
|
+
import { isAbsolute as isAbsolute6, resolve as resolve20 } from "node:path";
|
|
16136
17537
|
var SERVER_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
16137
17538
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16138
17539
|
var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
@@ -16204,9 +17605,9 @@ async function validateCwd(configured, options) {
|
|
|
16204
17605
|
if (configured !== void 0 && (configured.length > 4e3 || CONTROL_CHARACTERS.test(configured))) {
|
|
16205
17606
|
throw new Error("MCP working directory is invalid or too long");
|
|
16206
17607
|
}
|
|
16207
|
-
const defaultCwd =
|
|
16208
|
-
const candidate = configured ? isAbsolute6(configured) ?
|
|
16209
|
-
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];
|
|
16210
17611
|
const resolvedCandidate = await realpath8(candidate).catch(() => {
|
|
16211
17612
|
throw new Error(`MCP working directory does not exist: ${candidate}`);
|
|
16212
17613
|
});
|
|
@@ -16544,7 +17945,7 @@ var McpManager = class {
|
|
|
16544
17945
|
stderr: "pipe"
|
|
16545
17946
|
});
|
|
16546
17947
|
transport.stderr?.on("data", (chunk) => {
|
|
16547
|
-
const text =
|
|
17948
|
+
const text = stripAnsi5(chunk.toString()).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
|
|
16548
17949
|
if (text) this.options.logger?.(`MCP ${name} stderr`, { text: text.slice(0, 2e3) });
|
|
16549
17950
|
});
|
|
16550
17951
|
return transport;
|
|
@@ -16703,7 +18104,7 @@ function errorMessage2(error) {
|
|
|
16703
18104
|
return sanitizeStatusText(message2) || "Unknown MCP error";
|
|
16704
18105
|
}
|
|
16705
18106
|
function sanitizeStatusText(value) {
|
|
16706
|
-
return
|
|
18107
|
+
return stripAnsi5(value).replace(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 1e3);
|
|
16707
18108
|
}
|
|
16708
18109
|
function timeoutError(timeoutMs) {
|
|
16709
18110
|
const error = new Error(`MCP request timed out after ${timeoutMs} ms`);
|
|
@@ -16729,9 +18130,9 @@ async function closeTransportQuietly(transport) {
|
|
|
16729
18130
|
}
|
|
16730
18131
|
|
|
16731
18132
|
// src/memory/tools.ts
|
|
16732
|
-
import { z as
|
|
16733
|
-
var scopeSchema =
|
|
16734
|
-
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"]);
|
|
16735
18136
|
function createMemoryTools(store) {
|
|
16736
18137
|
return [
|
|
16737
18138
|
{
|
|
@@ -16746,10 +18147,10 @@ function createMemoryTools(store) {
|
|
|
16746
18147
|
}, ["query"])
|
|
16747
18148
|
},
|
|
16748
18149
|
async execute(arguments_, context) {
|
|
16749
|
-
const input2 =
|
|
16750
|
-
query:
|
|
18150
|
+
const input2 = z19.object({
|
|
18151
|
+
query: z19.string().max(4e3),
|
|
16751
18152
|
scope: scopeSchema.optional(),
|
|
16752
|
-
limit:
|
|
18153
|
+
limit: z19.number().int().min(1).max(20).optional()
|
|
16753
18154
|
}).parse(arguments_);
|
|
16754
18155
|
const scopes = input2.scope ? [{ scope: input2.scope, scopeKey: scopeKey(input2.scope, context) }] : availableScopes(context);
|
|
16755
18156
|
const records = store.search(input2.query, { scopes, limit: input2.limit ?? 8 });
|
|
@@ -16781,17 +18182,17 @@ ${record.content}`
|
|
|
16781
18182
|
}, ["content", "rationale"])
|
|
16782
18183
|
},
|
|
16783
18184
|
async execute(arguments_, context) {
|
|
16784
|
-
const input2 =
|
|
16785
|
-
content:
|
|
16786
|
-
rationale:
|
|
18185
|
+
const input2 = z19.object({
|
|
18186
|
+
content: z19.string().min(1).max(12e3),
|
|
18187
|
+
rationale: z19.string().min(1).max(1e3),
|
|
16787
18188
|
scope: scopeSchema.optional(),
|
|
16788
18189
|
kind: kindSchema.optional(),
|
|
16789
|
-
tags:
|
|
16790
|
-
importance:
|
|
16791
|
-
confidence:
|
|
16792
|
-
agent:
|
|
16793
|
-
revision:
|
|
16794
|
-
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()
|
|
16795
18196
|
}).strict().parse(arguments_);
|
|
16796
18197
|
const scope = input2.scope ?? "workspace";
|
|
16797
18198
|
const candidate = store.propose({
|
|
@@ -16829,9 +18230,9 @@ ${record.content}`
|
|
|
16829
18230
|
}, ["id"])
|
|
16830
18231
|
},
|
|
16831
18232
|
async execute(arguments_) {
|
|
16832
|
-
const input2 =
|
|
16833
|
-
id:
|
|
16834
|
-
permanent:
|
|
18233
|
+
const input2 = z19.object({
|
|
18234
|
+
id: z19.string().uuid(),
|
|
18235
|
+
permanent: z19.boolean().optional()
|
|
16835
18236
|
}).parse(arguments_);
|
|
16836
18237
|
const changed = input2.permanent ? store.remove(input2.id) : store.archive(input2.id);
|
|
16837
18238
|
return {
|
|
@@ -16858,9 +18259,9 @@ function scopeKey(scope, context) {
|
|
|
16858
18259
|
}
|
|
16859
18260
|
|
|
16860
18261
|
// src/skills/catalog.ts
|
|
16861
|
-
import { lstat as
|
|
18262
|
+
import { lstat as lstat21, readFile as readFile18, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
|
|
16862
18263
|
import { homedir as homedir4 } from "node:os";
|
|
16863
|
-
import { basename as basename11, join as
|
|
18264
|
+
import { basename as basename11, join as join21, resolve as resolve21 } from "node:path";
|
|
16864
18265
|
import { parse as parseYaml3 } from "yaml";
|
|
16865
18266
|
var SkillCatalog = class {
|
|
16866
18267
|
constructor(workspace, config) {
|
|
@@ -16880,7 +18281,7 @@ var SkillCatalog = class {
|
|
|
16880
18281
|
for (const location of locations) {
|
|
16881
18282
|
const entries = await safeDirectories(location.path);
|
|
16882
18283
|
for (const entry of entries) {
|
|
16883
|
-
const skillPath =
|
|
18284
|
+
const skillPath = join21(location.path, entry, "SKILL.md");
|
|
16884
18285
|
const metadata = await readMetadata(skillPath);
|
|
16885
18286
|
if (!metadata) continue;
|
|
16886
18287
|
const descriptor = {
|
|
@@ -16929,31 +18330,31 @@ ${skill.content}
|
|
|
16929
18330
|
}
|
|
16930
18331
|
function discoveryLocations(workspace, configured) {
|
|
16931
18332
|
const home = homedir4();
|
|
16932
|
-
const workspaceRoot =
|
|
18333
|
+
const workspaceRoot = resolve21(workspace);
|
|
16933
18334
|
return [
|
|
16934
|
-
{ path:
|
|
16935
|
-
{ path:
|
|
16936
|
-
{ path:
|
|
16937
|
-
{ 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 },
|
|
16938
18339
|
...configured.map((path) => {
|
|
16939
|
-
const resolved =
|
|
18340
|
+
const resolved = resolve21(workspaceRoot, path);
|
|
16940
18341
|
return {
|
|
16941
18342
|
path: resolved,
|
|
16942
18343
|
scope: "configured",
|
|
16943
18344
|
trusted: !isInside(workspaceRoot, resolved)
|
|
16944
18345
|
};
|
|
16945
18346
|
}),
|
|
16946
|
-
{ path:
|
|
16947
|
-
{ path:
|
|
16948
|
-
{ path:
|
|
16949
|
-
{ 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 }
|
|
16950
18351
|
];
|
|
16951
18352
|
}
|
|
16952
18353
|
async function safeDirectories(path) {
|
|
16953
18354
|
try {
|
|
16954
|
-
const info = await
|
|
18355
|
+
const info = await lstat21(path);
|
|
16955
18356
|
if (!info.isDirectory() || info.isSymbolicLink()) return [];
|
|
16956
|
-
const entries = await
|
|
18357
|
+
const entries = await readdir9(path, { withFileTypes: true });
|
|
16957
18358
|
return entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => entry.name).slice(0, 500);
|
|
16958
18359
|
} catch {
|
|
16959
18360
|
return [];
|
|
@@ -16965,7 +18366,7 @@ async function readMetadata(path) {
|
|
|
16965
18366
|
const { frontmatter } = splitFrontmatter(raw);
|
|
16966
18367
|
if (!frontmatter) return void 0;
|
|
16967
18368
|
const parsed = parseYaml3(frontmatter);
|
|
16968
|
-
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename11(
|
|
18369
|
+
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename11(resolve21(path, ".."));
|
|
16969
18370
|
const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
|
|
16970
18371
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
|
|
16971
18372
|
return void 0;
|
|
@@ -16984,13 +18385,13 @@ async function readSkill(path, maxChars) {
|
|
|
16984
18385
|
}
|
|
16985
18386
|
async function safeRead(path, maxBytes) {
|
|
16986
18387
|
try {
|
|
16987
|
-
const info = await
|
|
18388
|
+
const info = await lstat21(path);
|
|
16988
18389
|
if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
|
|
16989
|
-
const parent =
|
|
18390
|
+
const parent = resolve21(path, "..");
|
|
16990
18391
|
const resolvedParent = await realpath9(parent);
|
|
16991
18392
|
const resolvedPath = await realpath9(path);
|
|
16992
18393
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
16993
|
-
return await
|
|
18394
|
+
return await readFile18(path, "utf8");
|
|
16994
18395
|
} catch {
|
|
16995
18396
|
return void 0;
|
|
16996
18397
|
}
|
|
@@ -17035,7 +18436,7 @@ function escapeAttribute6(value) {
|
|
|
17035
18436
|
}
|
|
17036
18437
|
|
|
17037
18438
|
// src/workflows/catalog.ts
|
|
17038
|
-
import { z as
|
|
18439
|
+
import { z as z20 } from "zod";
|
|
17039
18440
|
var builtInWorkflows = [
|
|
17040
18441
|
{
|
|
17041
18442
|
name: "implement",
|
|
@@ -17126,7 +18527,7 @@ function createWorkflowTool(catalog) {
|
|
|
17126
18527
|
}, ["name", "task"])
|
|
17127
18528
|
},
|
|
17128
18529
|
async execute(arguments_) {
|
|
17129
|
-
const input2 =
|
|
18530
|
+
const input2 = z20.object({ name: z20.string(), task: z20.string().min(1).max(2e4) }).parse(arguments_);
|
|
17130
18531
|
const workflow = catalog.get(input2.name);
|
|
17131
18532
|
if (!workflow) return { ok: false, content: `Unknown workflow: ${input2.name}` };
|
|
17132
18533
|
return {
|
|
@@ -17171,7 +18572,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
17171
18572
|
delegation;
|
|
17172
18573
|
initialized = false;
|
|
17173
18574
|
static async create(config, registry, options = {}) {
|
|
17174
|
-
const runtime = new _ExtensionRuntime(config,
|
|
18575
|
+
const runtime = new _ExtensionRuntime(config, resolve22(config.workspaceRoots[0] ?? process.cwd()), options);
|
|
17175
18576
|
try {
|
|
17176
18577
|
await runtime.initialize(registry, options.signal);
|
|
17177
18578
|
return runtime;
|
|
@@ -17376,15 +18777,15 @@ function upgradeCommandOverride(env = process.env) {
|
|
|
17376
18777
|
return trimmed ? trimmed : void 0;
|
|
17377
18778
|
}
|
|
17378
18779
|
function runUpgrade(plan) {
|
|
17379
|
-
return new Promise((
|
|
18780
|
+
return new Promise((resolve24) => {
|
|
17380
18781
|
const child = spawn2(plan.command, plan.args, {
|
|
17381
18782
|
stdio: "inherit",
|
|
17382
18783
|
shell: plan.shell ?? false
|
|
17383
18784
|
});
|
|
17384
|
-
child.on("error", () =>
|
|
18785
|
+
child.on("error", () => resolve24({ ok: false, exitCode: 127, display: plan.display }));
|
|
17385
18786
|
child.on("close", (code) => {
|
|
17386
18787
|
const exitCode = code ?? 1;
|
|
17387
|
-
|
|
18788
|
+
resolve24({ ok: exitCode === 0, exitCode, display: plan.display });
|
|
17388
18789
|
});
|
|
17389
18790
|
});
|
|
17390
18791
|
}
|
|
@@ -17633,9 +19034,11 @@ sessionCommand.command("show <id>").description("Show a saved session transcript
|
|
|
17633
19034
|
else process.stdout.write(sessionMarkdown(session));
|
|
17634
19035
|
});
|
|
17635
19036
|
sessionCommand.command("delete <id>").description("Delete a saved session").option("-w, --workspace <path>", "workspace root").option("--yes", "skip confirmation").action(async (id, options) => {
|
|
17636
|
-
const
|
|
19037
|
+
const workspace = workspaceOption(options.workspace);
|
|
19038
|
+
const store = new SessionStore(workspace);
|
|
17637
19039
|
const session = await requireSessionSelector(store, id);
|
|
17638
19040
|
if (!options.yes && !await confirm(`Delete session ${session.id.slice(0, 8)}?`)) return;
|
|
19041
|
+
await new ToolArtifactStore(workspace).removeSession(session.id);
|
|
17639
19042
|
await store.remove(session.id);
|
|
17640
19043
|
process.stdout.write(`Deleted ${session.id}
|
|
17641
19044
|
`);
|
|
@@ -17644,7 +19047,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
|
|
|
17644
19047
|
const store = new SessionStore(workspaceOption(options.workspace));
|
|
17645
19048
|
const session = await requireSessionSelector(store, id);
|
|
17646
19049
|
const markdown = sessionMarkdown(session);
|
|
17647
|
-
if (options.output) await writeFile3(
|
|
19050
|
+
if (options.output) await writeFile3(resolve23(options.output), markdown, "utf8");
|
|
17648
19051
|
else process.stdout.write(markdown);
|
|
17649
19052
|
});
|
|
17650
19053
|
var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
|
|
@@ -18021,7 +19424,7 @@ async function runChat(prompts, options) {
|
|
|
18021
19424
|
const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
|
|
18022
19425
|
const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
|
|
18023
19426
|
if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
|
|
18024
|
-
const workspace =
|
|
19427
|
+
const workspace = resolve23(options.workspace);
|
|
18025
19428
|
let config = await runtimeConfig(workspace, options);
|
|
18026
19429
|
let completedOnboarding = false;
|
|
18027
19430
|
if (!shouldPrint && needsFirstRunOnboarding(config)) {
|
|
@@ -18246,14 +19649,14 @@ async function runAgentSetup(options) {
|
|
|
18246
19649
|
`);
|
|
18247
19650
|
}
|
|
18248
19651
|
async function runtimeConfig(workspaceInput, options) {
|
|
18249
|
-
const workspace =
|
|
19652
|
+
const workspace = resolve23(workspaceInput);
|
|
18250
19653
|
const loaded = await loadConfig(workspace, options.config, {
|
|
18251
19654
|
trustProjectConfig: options.trustProjectConfig === true
|
|
18252
19655
|
});
|
|
18253
19656
|
const roots = [
|
|
18254
19657
|
workspace,
|
|
18255
19658
|
...loaded.workspaceRoots,
|
|
18256
|
-
...(options.addWorkspace ?? []).map((root) =>
|
|
19659
|
+
...(options.addWorkspace ?? []).map((root) => resolve23(workspace, root))
|
|
18257
19660
|
];
|
|
18258
19661
|
const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
|
|
18259
19662
|
return {
|
|
@@ -18409,7 +19812,7 @@ function collect(value, previous) {
|
|
|
18409
19812
|
}
|
|
18410
19813
|
function workspaceOption(value) {
|
|
18411
19814
|
const rootOptions = program.opts();
|
|
18412
|
-
return
|
|
19815
|
+
return resolve23(value ?? rootOptions.workspace ?? process.cwd());
|
|
18413
19816
|
}
|
|
18414
19817
|
function runtimeOptions(options) {
|
|
18415
19818
|
const root = program.opts();
|