@mrciphersmith/keryx 0.2.56 → 0.2.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +676 -558
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -14422,101 +14422,66 @@ var init_slate_course = __esm(() => {
|
|
|
14422
14422
|
unbound = { state: "unbound" };
|
|
14423
14423
|
});
|
|
14424
14424
|
|
|
14425
|
-
// src/sac/
|
|
14426
|
-
import {
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14425
|
+
// src/sac/trusted-wrap-up.ts
|
|
14426
|
+
import { createHash as createHash8, randomUUID as randomUUID6 } from "crypto";
|
|
14427
|
+
function createTrustedWrapUpAuthority(input2) {
|
|
14428
|
+
const now = input2.now ?? (() => new Date);
|
|
14429
|
+
return Object.freeze({
|
|
14430
|
+
async issue(request) {
|
|
14431
|
+
const resolved = await input2.resolveExplicitWrapUp(request);
|
|
14432
|
+
if (!resolved.summary.trim() || resolved.evidence.length === 0 || new Date(resolved.expiresAt).getTime() <= now().getTime())
|
|
14433
|
+
throw new Error("invalid trusted wrap-up issuance");
|
|
14434
|
+
const provenance = Object.freeze({ id: `wrapup-${randomUUID6().replace(/-/g, "").slice(0, 16)}`, source: request.source, sourceRef: request.sourceRef, sourceRevision: resolved.sourceRevision, workspaceId: resolved.workspaceId, actorSubject: request.actor.subject, summaryDigest: digest(resolved.summary), evidence: resolved.evidence.map((item) => ({ ...item })), issuedAt: now().toISOString(), expiresAt: resolved.expiresAt });
|
|
14435
|
+
issued.add(provenance);
|
|
14436
|
+
return provenance;
|
|
14437
|
+
},
|
|
14438
|
+
verify(provenance, request) {
|
|
14439
|
+
if (!issued.has(provenance))
|
|
14440
|
+
return "untrusted";
|
|
14441
|
+
if (consumed.has(provenance))
|
|
14442
|
+
return "replayed";
|
|
14443
|
+
if (new Date(provenance.expiresAt).getTime() <= now().getTime())
|
|
14444
|
+
return "expired";
|
|
14445
|
+
if (provenance.workspaceId !== request.workspaceId || provenance.actorSubject !== request.actor.subject)
|
|
14446
|
+
return "mismatch";
|
|
14447
|
+
return "ok";
|
|
14448
|
+
},
|
|
14449
|
+
consume(provenance, request) {
|
|
14450
|
+
const result = this.verify(provenance, request);
|
|
14451
|
+
if (result === "ok")
|
|
14452
|
+
consumed.add(provenance);
|
|
14453
|
+
return result;
|
|
14448
14454
|
}
|
|
14449
|
-
|
|
14450
|
-
if (!name)
|
|
14451
|
-
throw new Error("safe source path has no final component");
|
|
14452
|
-
const file = openAt(libc, parent, name, fileFlags);
|
|
14453
|
-
opened.push(file);
|
|
14454
|
-
return readAll(libc, file);
|
|
14455
|
-
} finally {
|
|
14456
|
-
for (const fd of opened.reverse())
|
|
14457
|
-
libc.symbols.close(fd);
|
|
14458
|
-
libc.close();
|
|
14459
|
-
}
|
|
14460
|
-
}
|
|
14461
|
-
function loadPosixLibrary() {
|
|
14462
|
-
for (const candidate of libcCandidates) {
|
|
14463
|
-
try {
|
|
14464
|
-
return dlopen(candidate, {
|
|
14465
|
-
openat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32, FFIType.i32], returns: FFIType.i32 },
|
|
14466
|
-
read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
|
|
14467
|
-
close: { args: [FFIType.i32], returns: FFIType.i32 }
|
|
14468
|
-
});
|
|
14469
|
-
} catch {}
|
|
14470
|
-
}
|
|
14471
|
-
return;
|
|
14472
|
-
}
|
|
14473
|
-
function openAt(libc, directoryFd, component, flags) {
|
|
14474
|
-
const name = Buffer.from(`${component}\x00`);
|
|
14475
|
-
const fd = libc.symbols.openat(directoryFd, name, flags, 0);
|
|
14476
|
-
if (fd < 0)
|
|
14477
|
-
throw new Error("safe source descriptor open failed");
|
|
14478
|
-
return fd;
|
|
14455
|
+
});
|
|
14479
14456
|
}
|
|
14480
|
-
function
|
|
14481
|
-
|
|
14482
|
-
for (;; ) {
|
|
14483
|
-
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
14484
|
-
const read = libc.symbols.read(fd, ptr(chunk), chunk.length);
|
|
14485
|
-
if (read < 0n)
|
|
14486
|
-
throw new Error("safe source descriptor read failed");
|
|
14487
|
-
if (read === 0n)
|
|
14488
|
-
return Buffer.concat(chunks);
|
|
14489
|
-
const length = Number(read);
|
|
14490
|
-
if (!Number.isSafeInteger(length) || length > chunk.length)
|
|
14491
|
-
throw new Error("safe source descriptor read returned an invalid length");
|
|
14492
|
-
chunks.push(chunk.subarray(0, length));
|
|
14493
|
-
}
|
|
14457
|
+
function digest(value) {
|
|
14458
|
+
return createHash8("sha256").update(value).digest("hex");
|
|
14494
14459
|
}
|
|
14495
|
-
var
|
|
14496
|
-
var
|
|
14497
|
-
|
|
14498
|
-
|
|
14460
|
+
var issued, consumed;
|
|
14461
|
+
var init_trusted_wrap_up = __esm(() => {
|
|
14462
|
+
issued = new WeakSet;
|
|
14463
|
+
consumed = new WeakSet;
|
|
14499
14464
|
});
|
|
14500
14465
|
|
|
14501
14466
|
// src/sac/index.ts
|
|
14502
14467
|
import { access as access2, readFile as readFile33, realpath as realpath2 } from "fs/promises";
|
|
14503
|
-
import { createHash as
|
|
14504
|
-
import
|
|
14468
|
+
import { createHash as createHash9 } from "crypto";
|
|
14469
|
+
import path71 from "path";
|
|
14505
14470
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14506
14471
|
async function resolveSacNormativeSchemaPath(fileName, searchFrom = [fileURLToPath4(new URL(".", import.meta.url)), process.cwd()]) {
|
|
14507
14472
|
const seen = new Set;
|
|
14508
14473
|
for (const start of searchFrom) {
|
|
14509
|
-
let dir =
|
|
14474
|
+
let dir = path71.resolve(start);
|
|
14510
14475
|
for (let i = 0;i < 10; i++) {
|
|
14511
14476
|
if (seen.has(dir))
|
|
14512
14477
|
break;
|
|
14513
14478
|
seen.add(dir);
|
|
14514
|
-
const candidate =
|
|
14479
|
+
const candidate = path71.join(dir, NORMATIVE_SCHEMA_DIR, fileName);
|
|
14515
14480
|
try {
|
|
14516
14481
|
await access2(candidate);
|
|
14517
14482
|
return candidate;
|
|
14518
14483
|
} catch {}
|
|
14519
|
-
const parent =
|
|
14484
|
+
const parent = path71.dirname(dir);
|
|
14520
14485
|
if (parent === dir)
|
|
14521
14486
|
break;
|
|
14522
14487
|
dir = parent;
|
|
@@ -15178,15 +15143,15 @@ async function validateSacContract(input2) {
|
|
|
15178
15143
|
return { valid: errors.length === 0, errors };
|
|
15179
15144
|
}
|
|
15180
15145
|
function hashSacRecord(value) {
|
|
15181
|
-
return
|
|
15146
|
+
return createHash9("sha256").update(typeof value === "string" ? value : JSON.stringify(value)).digest("hex");
|
|
15182
15147
|
}
|
|
15183
15148
|
async function resolveWorkspaceReference(input2) {
|
|
15184
|
-
if (!workspacePathPattern.test(input2.uri) ||
|
|
15149
|
+
if (!workspacePathPattern.test(input2.uri) || path71.isAbsolute(input2.uri) || /^[a-z][a-z0-9+.-]*:/i.test(input2.uri) || input2.uri.includes("\\"))
|
|
15185
15150
|
throw new SacReferenceError("unsafe workspace reference");
|
|
15186
|
-
const lexicalRoot =
|
|
15151
|
+
const lexicalRoot = path71.resolve(input2.workspaceRoot);
|
|
15187
15152
|
const root = await realpath2(lexicalRoot);
|
|
15188
|
-
const candidate =
|
|
15189
|
-
if (candidate !== lexicalRoot && !candidate.startsWith(`${lexicalRoot}${
|
|
15153
|
+
const candidate = path71.resolve(lexicalRoot, input2.uri.slice(2));
|
|
15154
|
+
if (candidate !== lexicalRoot && !candidate.startsWith(`${lexicalRoot}${path71.sep}`))
|
|
15190
15155
|
throw new SacReferenceError("workspace reference escapes root");
|
|
15191
15156
|
let resolved;
|
|
15192
15157
|
try {
|
|
@@ -15194,7 +15159,7 @@ async function resolveWorkspaceReference(input2) {
|
|
|
15194
15159
|
} catch {
|
|
15195
15160
|
throw new SacReferenceError("workspace reference is not resolvable");
|
|
15196
15161
|
}
|
|
15197
|
-
if (resolved !== root && !resolved.startsWith(`${root}${
|
|
15162
|
+
if (resolved !== root && !resolved.startsWith(`${root}${path71.sep}`))
|
|
15198
15163
|
throw new SacReferenceError("workspace reference escapes root after realpath");
|
|
15199
15164
|
return candidate;
|
|
15200
15165
|
}
|
|
@@ -15261,7 +15226,7 @@ var init_sac = __esm(() => {
|
|
|
15261
15226
|
"review-decision": "review-decision.schema.json"
|
|
15262
15227
|
};
|
|
15263
15228
|
normativeSchemas = new Map;
|
|
15264
|
-
NORMATIVE_SCHEMA_DIR =
|
|
15229
|
+
NORMATIVE_SCHEMA_DIR = path71.join("docs", "requirements", "shared-agent-context", "schemas");
|
|
15265
15230
|
SacReferenceError = class SacReferenceError extends Error {
|
|
15266
15231
|
code = "unsafe_workspace_reference";
|
|
15267
15232
|
};
|
|
@@ -15269,9 +15234,85 @@ var init_sac = __esm(() => {
|
|
|
15269
15234
|
roleRank = { viewer: 1, editor: 2, owner: 3 };
|
|
15270
15235
|
});
|
|
15271
15236
|
|
|
15237
|
+
// src/sac/secure-resource-read.ts
|
|
15238
|
+
import { constants } from "fs";
|
|
15239
|
+
import path72 from "path";
|
|
15240
|
+
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
15241
|
+
function readWorkspaceFileNoFollow(workspaceRoot, absolutePath) {
|
|
15242
|
+
const relativePath = path72.relative(workspaceRoot, absolutePath);
|
|
15243
|
+
const components = relativePath.split(path72.sep);
|
|
15244
|
+
if (!relativePath || path72.isAbsolute(relativePath) || components.some((component) => !component || component === "." || component === "..")) {
|
|
15245
|
+
throw new Error("safe source path is not workspace-relative");
|
|
15246
|
+
}
|
|
15247
|
+
const libc = loadPosixLibrary();
|
|
15248
|
+
if (!libc || !Number.isInteger(constants.O_DIRECTORY) || !Number.isInteger(constants.O_NOFOLLOW)) {
|
|
15249
|
+
throw new Error("safe descriptor source reads are unavailable on this platform");
|
|
15250
|
+
}
|
|
15251
|
+
const directoryFlags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
|
|
15252
|
+
const fileFlags = constants.O_RDONLY | constants.O_NOFOLLOW;
|
|
15253
|
+
const opened = [];
|
|
15254
|
+
try {
|
|
15255
|
+
let parent = openAt(libc, atFdcwd, workspaceRoot, directoryFlags);
|
|
15256
|
+
opened.push(parent);
|
|
15257
|
+
for (const component of components.slice(0, -1)) {
|
|
15258
|
+
parent = openAt(libc, parent, component, directoryFlags);
|
|
15259
|
+
opened.push(parent);
|
|
15260
|
+
}
|
|
15261
|
+
const name = components.at(-1);
|
|
15262
|
+
if (!name)
|
|
15263
|
+
throw new Error("safe source path has no final component");
|
|
15264
|
+
const file = openAt(libc, parent, name, fileFlags);
|
|
15265
|
+
opened.push(file);
|
|
15266
|
+
return readAll(libc, file);
|
|
15267
|
+
} finally {
|
|
15268
|
+
for (const fd of opened.reverse())
|
|
15269
|
+
libc.symbols.close(fd);
|
|
15270
|
+
libc.close();
|
|
15271
|
+
}
|
|
15272
|
+
}
|
|
15273
|
+
function loadPosixLibrary() {
|
|
15274
|
+
for (const candidate of libcCandidates) {
|
|
15275
|
+
try {
|
|
15276
|
+
return dlopen(candidate, {
|
|
15277
|
+
openat: { args: [FFIType.i32, FFIType.cstring, FFIType.i32, FFIType.i32], returns: FFIType.i32 },
|
|
15278
|
+
read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
|
|
15279
|
+
close: { args: [FFIType.i32], returns: FFIType.i32 }
|
|
15280
|
+
});
|
|
15281
|
+
} catch {}
|
|
15282
|
+
}
|
|
15283
|
+
return;
|
|
15284
|
+
}
|
|
15285
|
+
function openAt(libc, directoryFd, component, flags) {
|
|
15286
|
+
const name = Buffer.from(`${component}\x00`);
|
|
15287
|
+
const fd = libc.symbols.openat(directoryFd, name, flags, 0);
|
|
15288
|
+
if (fd < 0)
|
|
15289
|
+
throw new Error("safe source descriptor open failed");
|
|
15290
|
+
return fd;
|
|
15291
|
+
}
|
|
15292
|
+
function readAll(libc, fd) {
|
|
15293
|
+
const chunks = [];
|
|
15294
|
+
for (;; ) {
|
|
15295
|
+
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
15296
|
+
const read = libc.symbols.read(fd, ptr(chunk), chunk.length);
|
|
15297
|
+
if (read < 0n)
|
|
15298
|
+
throw new Error("safe source descriptor read failed");
|
|
15299
|
+
if (read === 0n)
|
|
15300
|
+
return Buffer.concat(chunks);
|
|
15301
|
+
const length = Number(read);
|
|
15302
|
+
if (!Number.isSafeInteger(length) || length > chunk.length)
|
|
15303
|
+
throw new Error("safe source descriptor read returned an invalid length");
|
|
15304
|
+
chunks.push(chunk.subarray(0, length));
|
|
15305
|
+
}
|
|
15306
|
+
}
|
|
15307
|
+
var atFdcwd, libcCandidates;
|
|
15308
|
+
var init_secure_resource_read = __esm(() => {
|
|
15309
|
+
atFdcwd = process.platform === "darwin" ? -2 : -100;
|
|
15310
|
+
libcCandidates = process.platform === "darwin" ? ["/usr/lib/libSystem.B.dylib"] : process.platform === "linux" ? ["/lib/aarch64-linux-gnu/libc.so.6", "/lib/x86_64-linux-gnu/libc.so.6", "/lib64/libc.so.6"] : [];
|
|
15311
|
+
});
|
|
15312
|
+
|
|
15272
15313
|
// src/sac/workspace-service.ts
|
|
15273
15314
|
import { mkdir as mkdir27, readdir as readdir9, readFile as readFile34 } from "fs/promises";
|
|
15274
|
-
import { randomUUID as
|
|
15315
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
15275
15316
|
import path73 from "path";
|
|
15276
15317
|
|
|
15277
15318
|
class WorkspaceService {
|
|
@@ -15579,7 +15620,7 @@ function localWorkspaceAuthorizationServer(subject = `user:local-${process.getui
|
|
|
15579
15620
|
return createSacAuthorizationServer({ authenticateRequest: async () => ({ subject, authenticationMethod: "local-os", roleRevision: "local-os-v1" }) });
|
|
15580
15621
|
}
|
|
15581
15622
|
function newWorkspaceId() {
|
|
15582
|
-
return `workspace-${
|
|
15623
|
+
return `workspace-${randomUUID7().replace(/-/g, "").slice(0, 16)}`;
|
|
15583
15624
|
}
|
|
15584
15625
|
async function resolveWorkspaceForActor(cwd, workspaceId) {
|
|
15585
15626
|
const service = new WorkspaceService({
|
|
@@ -15588,7 +15629,7 @@ async function resolveWorkspaceForActor(cwd, workspaceId) {
|
|
|
15588
15629
|
strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
|
|
15589
15630
|
});
|
|
15590
15631
|
try {
|
|
15591
|
-
const manifest = await service.show({ request: undefined, requestCorrelationId:
|
|
15632
|
+
const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID7(), workspaceId });
|
|
15592
15633
|
return { ok: true, manifest };
|
|
15593
15634
|
} catch (error2) {
|
|
15594
15635
|
if (error2 instanceof WorkspaceServiceError) {
|
|
@@ -15614,47 +15655,6 @@ var init_workspace_service = __esm(() => {
|
|
|
15614
15655
|
};
|
|
15615
15656
|
});
|
|
15616
15657
|
|
|
15617
|
-
// src/sac/trusted-wrap-up.ts
|
|
15618
|
-
import { createHash as createHash9, randomUUID as randomUUID7 } from "crypto";
|
|
15619
|
-
function createTrustedWrapUpAuthority(input2) {
|
|
15620
|
-
const now = input2.now ?? (() => new Date);
|
|
15621
|
-
return Object.freeze({
|
|
15622
|
-
async issue(request) {
|
|
15623
|
-
const resolved = await input2.resolveExplicitWrapUp(request);
|
|
15624
|
-
if (!resolved.summary.trim() || resolved.evidence.length === 0 || new Date(resolved.expiresAt).getTime() <= now().getTime())
|
|
15625
|
-
throw new Error("invalid trusted wrap-up issuance");
|
|
15626
|
-
const provenance = Object.freeze({ id: `wrapup-${randomUUID7().replace(/-/g, "").slice(0, 16)}`, source: request.source, sourceRef: request.sourceRef, sourceRevision: resolved.sourceRevision, workspaceId: resolved.workspaceId, actorSubject: request.actor.subject, summaryDigest: digest(resolved.summary), evidence: resolved.evidence.map((item) => ({ ...item })), issuedAt: now().toISOString(), expiresAt: resolved.expiresAt });
|
|
15627
|
-
issued.add(provenance);
|
|
15628
|
-
return provenance;
|
|
15629
|
-
},
|
|
15630
|
-
verify(provenance, request) {
|
|
15631
|
-
if (!issued.has(provenance))
|
|
15632
|
-
return "untrusted";
|
|
15633
|
-
if (consumed.has(provenance))
|
|
15634
|
-
return "replayed";
|
|
15635
|
-
if (new Date(provenance.expiresAt).getTime() <= now().getTime())
|
|
15636
|
-
return "expired";
|
|
15637
|
-
if (provenance.workspaceId !== request.workspaceId || provenance.actorSubject !== request.actor.subject)
|
|
15638
|
-
return "mismatch";
|
|
15639
|
-
return "ok";
|
|
15640
|
-
},
|
|
15641
|
-
consume(provenance, request) {
|
|
15642
|
-
const result = this.verify(provenance, request);
|
|
15643
|
-
if (result === "ok")
|
|
15644
|
-
consumed.add(provenance);
|
|
15645
|
-
return result;
|
|
15646
|
-
}
|
|
15647
|
-
});
|
|
15648
|
-
}
|
|
15649
|
-
function digest(value) {
|
|
15650
|
-
return createHash9("sha256").update(value).digest("hex");
|
|
15651
|
-
}
|
|
15652
|
-
var issued, consumed;
|
|
15653
|
-
var init_trusted_wrap_up = __esm(() => {
|
|
15654
|
-
issued = new WeakSet;
|
|
15655
|
-
consumed = new WeakSet;
|
|
15656
|
-
});
|
|
15657
|
-
|
|
15658
15658
|
// src/sac/guarded-owner-writer.ts
|
|
15659
15659
|
import { createHash as createHash10 } from "crypto";
|
|
15660
15660
|
function createGuardedOwnerWriter(input2) {
|
|
@@ -16208,290 +16208,16 @@ var init_store3 = __esm(() => {
|
|
|
16208
16208
|
};
|
|
16209
16209
|
});
|
|
16210
16210
|
|
|
16211
|
-
// src/sac/machine-wrap-up.ts
|
|
16212
|
-
import { createHash as createHash11, randomUUID as randomUUID9 } from "crypto";
|
|
16213
|
-
import { execFile } from "child_process";
|
|
16214
|
-
import { mkdir as mkdir28 } from "fs/promises";
|
|
16215
|
-
import path75 from "path";
|
|
16216
|
-
import { promisify } from "util";
|
|
16217
|
-
function describeSource(source) {
|
|
16218
|
-
return source === "parent" ? "parent" : `child:${source.childDispatchId}`;
|
|
16219
|
-
}
|
|
16220
|
-
function dedupedAttributedSeeds(slate) {
|
|
16221
|
-
const seen = new Set;
|
|
16222
|
-
const result = [];
|
|
16223
|
-
const take = (seeds, source) => {
|
|
16224
|
-
for (const seed of dedupeSeeds(seeds)) {
|
|
16225
|
-
const key = seed.text.trim();
|
|
16226
|
-
if (seen.has(key))
|
|
16227
|
-
continue;
|
|
16228
|
-
seen.add(key);
|
|
16229
|
-
result.push({ text: seed.text, kind: seed.kind ?? "follow-up", source });
|
|
16230
|
-
}
|
|
16231
|
-
};
|
|
16232
|
-
take(slate.seeds, "parent");
|
|
16233
|
-
const childDispatches = slate.childDispatches ?? {};
|
|
16234
|
-
for (const [dispatchId, dispatch] of Object.entries(childDispatches)) {
|
|
16235
|
-
take(dispatch.seeds, { childDispatchId: dispatchId });
|
|
16236
|
-
}
|
|
16237
|
-
return result;
|
|
16238
|
-
}
|
|
16239
|
-
function groupSeedsByKind(slate) {
|
|
16240
|
-
const map = new Map;
|
|
16241
|
-
for (const seed of dedupedAttributedSeeds(slate)) {
|
|
16242
|
-
const bucket = map.get(seed.kind);
|
|
16243
|
-
if (bucket)
|
|
16244
|
-
bucket.push(seed);
|
|
16245
|
-
else
|
|
16246
|
-
map.set(seed.kind, [seed]);
|
|
16247
|
-
}
|
|
16248
|
-
return map;
|
|
16249
|
-
}
|
|
16250
|
-
function sha256(value) {
|
|
16251
|
-
return createHash11("sha256").update(value).digest("hex");
|
|
16252
|
-
}
|
|
16253
|
-
async function gitDiff(cwd) {
|
|
16254
|
-
try {
|
|
16255
|
-
const { stdout: stdout2 } = await execFileAsync("git", ["diff"], { cwd, maxBuffer: 16 * 1024 * 1024 });
|
|
16256
|
-
return stdout2;
|
|
16257
|
-
} catch {
|
|
16258
|
-
return "";
|
|
16259
|
-
}
|
|
16260
|
-
}
|
|
16261
|
-
function diffStatLine(diffText) {
|
|
16262
|
-
if (diffText.trim().length === 0)
|
|
16263
|
-
return "no working-tree changes";
|
|
16264
|
-
const added = (diffText.match(/^\+(?!\+\+)/gm) ?? []).length;
|
|
16265
|
-
const removed = (diffText.match(/^-(?!--)/gm) ?? []).length;
|
|
16266
|
-
return `working-tree diff: +${added}/-${removed} line(s)`;
|
|
16267
|
-
}
|
|
16268
|
-
function courseStatusLine(course) {
|
|
16269
|
-
if (course.state !== "bound")
|
|
16270
|
-
return "flow: unbound";
|
|
16271
|
-
return `flow ${course.flowRef.uri} snapshot=${course.flowRef.snapshot} completed=${course.completed.length} next=${course.next.length} blocked=${course.blocked.length}`;
|
|
16272
|
-
}
|
|
16273
|
-
function mechanicalSummary(diffText, course) {
|
|
16274
|
-
return `Mechanical wrap-up summary (model turn unavailable or timed out):
|
|
16275
|
-
${diffStatLine(diffText)}
|
|
16276
|
-
${courseStatusLine(course)}`;
|
|
16277
|
-
}
|
|
16278
|
-
async function resolveMachineWrapUp(input2) {
|
|
16279
|
-
const now = input2.now ?? (() => new Date);
|
|
16280
|
-
const diffText = await gitDiff(input2.cwd);
|
|
16281
|
-
const course = await readCourse(input2.cwd, input2.slate.course.flowRef);
|
|
16282
|
-
const seedsForKind = dedupedAttributedSeeds(input2.slate).filter((seed) => seed.kind === input2.kind);
|
|
16283
|
-
const flowSnapshotJson = `${JSON.stringify(course, null, 2)}
|
|
16284
|
-
`;
|
|
16285
|
-
const seedsJson = `${JSON.stringify(seedsForKind.map((seed) => ({ text: seed.text, source: describeSource(seed.source) })), null, 2)}
|
|
16286
|
-
`;
|
|
16287
|
-
const sourceRevision = sha256([diffText, flowSnapshotJson, seedsJson].join("\x00"));
|
|
16288
|
-
const shortHash = sourceRevision.slice(0, 16);
|
|
16289
|
-
const system = "Summarize ONLY the machine evidence provided below \u2014 a git diff, a Flow snapshot, and the Seeds captured " + "this session for one proposal kind. Never invent facts that are not present in the evidence.";
|
|
16290
|
-
const user = `--- git diff ---
|
|
16291
|
-
${diffText.length > 0 ? diffText : "(no working-tree changes)"}
|
|
16292
|
-
|
|
16293
|
-
` + `--- flow snapshot ---
|
|
16294
|
-
${flowSnapshotJson}
|
|
16295
|
-
` + `--- seeds (${input2.kind}) ---
|
|
16296
|
-
${seedsJson}`;
|
|
16297
|
-
let modelResult;
|
|
16298
|
-
const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS;
|
|
16299
|
-
const turn = runModelTurn({
|
|
16300
|
-
system,
|
|
16301
|
-
user,
|
|
16302
|
-
requestId: `machine-wrap-up-${shortHash}`,
|
|
16303
|
-
...input2.env !== undefined ? { env: input2.env } : {},
|
|
16304
|
-
...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {}
|
|
16305
|
-
}).then((result) => {
|
|
16306
|
-
modelResult = result;
|
|
16307
|
-
return "done";
|
|
16308
|
-
});
|
|
16309
|
-
let timer;
|
|
16310
|
-
const expired = new Promise((resolve) => {
|
|
16311
|
-
timer = setTimeout(() => resolve("timeout"), modelTurnTimeoutMs);
|
|
16312
|
-
});
|
|
16313
|
-
let raceOutcome;
|
|
16314
|
-
try {
|
|
16315
|
-
raceOutcome = await Promise.race([turn, expired]);
|
|
16316
|
-
} finally {
|
|
16317
|
-
if (timer !== undefined)
|
|
16318
|
-
clearTimeout(timer);
|
|
16319
|
-
}
|
|
16320
|
-
let summary;
|
|
16321
|
-
if (raceOutcome === "timeout") {
|
|
16322
|
-
turn.catch(() => {});
|
|
16323
|
-
summary = mechanicalSummary(diffText, course);
|
|
16324
|
-
} else {
|
|
16325
|
-
const result = modelResult;
|
|
16326
|
-
if (result.text.trim().length === 0 && !result.credentialAvailable) {
|
|
16327
|
-
return { ok: false, code: "no_credential" };
|
|
16328
|
-
}
|
|
16329
|
-
summary = result.text.trim().length > 0 ? result.text.trim() : mechanicalSummary(diffText, course);
|
|
16330
|
-
}
|
|
16331
|
-
const evidenceDir = path75.join(input2.cwd, ".metaproject", "workspaces", input2.workspaceId, "machine-evidence");
|
|
16332
|
-
await mkdir28(evidenceDir, { recursive: true });
|
|
16333
|
-
const diffFile = `${input2.kind}.${shortHash}.diff.txt`;
|
|
16334
|
-
const flowFile = `${input2.kind}.${shortHash}.flow.json`;
|
|
16335
|
-
const seedsFile = `${input2.kind}.${shortHash}.seeds.json`;
|
|
16336
|
-
await writeFileAtomic(path75.join(evidenceDir, diffFile), diffText);
|
|
16337
|
-
await writeFileAtomic(path75.join(evidenceDir, flowFile), flowSnapshotJson);
|
|
16338
|
-
await writeFileAtomic(path75.join(evidenceDir, seedsFile), seedsJson);
|
|
16339
|
-
const observedAt = now().toISOString();
|
|
16340
|
-
const relBase = `./.metaproject/workspaces/${input2.workspaceId}/machine-evidence`;
|
|
16341
|
-
const evidence = [
|
|
16342
|
-
{ kind: "diff", uri: `${relBase}/${diffFile}`, revision: sha256(diffText), observedAt },
|
|
16343
|
-
{ kind: "flow", uri: `${relBase}/${flowFile}`, revision: sha256(flowSnapshotJson), observedAt },
|
|
16344
|
-
{ kind: "seeds", uri: `${relBase}/${seedsFile}`, revision: sha256(seedsJson), observedAt }
|
|
16345
|
-
];
|
|
16346
|
-
return {
|
|
16347
|
-
ok: true,
|
|
16348
|
-
resolution: {
|
|
16349
|
-
workspaceId: input2.workspaceId,
|
|
16350
|
-
sourceRevision,
|
|
16351
|
-
summary,
|
|
16352
|
-
evidence,
|
|
16353
|
-
expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS).toISOString()
|
|
16354
|
-
}
|
|
16355
|
-
};
|
|
16356
|
-
}
|
|
16357
|
-
async function writeUnboundCandidateArtifact(dir, trigger, now, grouped, nonEmptyKinds) {
|
|
16358
|
-
const archiveDir = path75.join(dir, "slate-archive");
|
|
16359
|
-
await mkdir28(archiveDir, { recursive: true });
|
|
16360
|
-
const nowIso2 = now().toISOString();
|
|
16361
|
-
const filename = `${nowIso2.replace(/[:.]/g, "-")}-unbound-candidate.json`;
|
|
16362
|
-
const content = {
|
|
16363
|
-
recordType: "unbound-candidate",
|
|
16364
|
-
trigger,
|
|
16365
|
-
generatedAt: nowIso2,
|
|
16366
|
-
groups: nonEmptyKinds.map((kind) => ({
|
|
16367
|
-
kind,
|
|
16368
|
-
seeds: (grouped.get(kind) ?? []).map((seed) => ({ text: seed.text, source: describeSource(seed.source) }))
|
|
16369
|
-
}))
|
|
16370
|
-
};
|
|
16371
|
-
await writeFileAtomic(path75.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
|
|
16372
|
-
`);
|
|
16373
|
-
}
|
|
16374
|
-
async function writeWrapUpOutcomeArtifact(dir, trigger, now, groups) {
|
|
16375
|
-
try {
|
|
16376
|
-
const archiveDir = path75.join(dir, "slate-archive");
|
|
16377
|
-
await mkdir28(archiveDir, { recursive: true });
|
|
16378
|
-
const nowIso2 = now().toISOString();
|
|
16379
|
-
const filename = `${nowIso2.replace(/[:.]/g, "-")}-wrap-up-outcome.json`;
|
|
16380
|
-
const content = { recordType: "wrap-up-outcome", trigger, generatedAt: nowIso2, groups };
|
|
16381
|
-
await writeFileAtomic(path75.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
|
|
16382
|
-
`);
|
|
16383
|
-
} catch {}
|
|
16384
|
-
}
|
|
16385
|
-
async function proposeOneGroup(params) {
|
|
16386
|
-
const wrapUpSource = params.wrapUpSource ?? "flow";
|
|
16387
|
-
try {
|
|
16388
|
-
const resolved = await resolveMachineWrapUp({
|
|
16389
|
-
cwd: params.cwd,
|
|
16390
|
-
workspaceId: params.workspaceId,
|
|
16391
|
-
slate: params.slate,
|
|
16392
|
-
kind: params.kind,
|
|
16393
|
-
now: params.now,
|
|
16394
|
-
...params.env !== undefined ? { env: params.env } : {},
|
|
16395
|
-
...params.providerFactory !== undefined ? { providerFactory: params.providerFactory } : {},
|
|
16396
|
-
...params.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: params.modelTurnTimeoutMs } : {}
|
|
16397
|
-
});
|
|
16398
|
-
if (!resolved.ok)
|
|
16399
|
-
return { kind: params.kind, outcome: "no_credential" };
|
|
16400
|
-
const flowEvidence = resolved.resolution.evidence.find((item) => item.kind === "flow");
|
|
16401
|
-
const sourceRef = (flowEvidence ?? resolved.resolution.evidence[0]).uri;
|
|
16402
|
-
const flowRef = params.slate.course.flowRef ?? "";
|
|
16403
|
-
const dedupHash = sha256(`${params.workspaceId}:${flowRef}:${resolved.resolution.sourceRevision}:${params.kind}`);
|
|
16404
|
-
const proposalId = `wrapup-${dedupHash.slice(0, 32)}`;
|
|
16405
|
-
const wrapUpAuthority = createTrustedWrapUpAuthority({
|
|
16406
|
-
now: params.now,
|
|
16407
|
-
resolveExplicitWrapUp: async (request) => {
|
|
16408
|
-
if (request.source !== wrapUpSource) {
|
|
16409
|
-
throw new Error(`machine-wrap-up only resolves "${wrapUpSource}" wrap-ups, got "${request.source}"`);
|
|
16410
|
-
}
|
|
16411
|
-
return resolved.resolution;
|
|
16412
|
-
}
|
|
16413
|
-
});
|
|
16414
|
-
const { service, authorizationServer } = createHarnessProposalLifecycleService(params.cwd, {
|
|
16415
|
-
workspaceId: params.workspaceId,
|
|
16416
|
-
now: params.now
|
|
16417
|
-
});
|
|
16418
|
-
const requestCorrelationId = randomUUID9();
|
|
16419
|
-
const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
|
|
16420
|
-
if (!actor)
|
|
16421
|
-
throw new Error("trusted ActorContext is required for a machine wrap-up propose");
|
|
16422
|
-
const provenance = await wrapUpAuthority.issue({ actor, source: wrapUpSource, sourceRef });
|
|
16423
|
-
try {
|
|
16424
|
-
const proposal = await service.create({
|
|
16425
|
-
request: undefined,
|
|
16426
|
-
requestCorrelationId,
|
|
16427
|
-
workspaceId: params.workspaceId,
|
|
16428
|
-
id: proposalId,
|
|
16429
|
-
proposalRevision: "1",
|
|
16430
|
-
kind: params.kind,
|
|
16431
|
-
wrapUp: provenance
|
|
16432
|
-
});
|
|
16433
|
-
return { kind: params.kind, outcome: "proposed", proposalId: proposal.id };
|
|
16434
|
-
} catch (error2) {
|
|
16435
|
-
if (error2 instanceof ProposalLifecycleError && error2.code === "conflict") {
|
|
16436
|
-
return { kind: params.kind, outcome: "conflict" };
|
|
16437
|
-
}
|
|
16438
|
-
throw error2;
|
|
16439
|
-
}
|
|
16440
|
-
} catch (error2) {
|
|
16441
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
16442
|
-
return { kind: params.kind, outcome: "error", message };
|
|
16443
|
-
}
|
|
16444
|
-
}
|
|
16445
|
-
async function runWrapUp(input2) {
|
|
16446
|
-
const now = input2.now ?? (() => new Date);
|
|
16447
|
-
const grouped = groupSeedsByKind(input2.slate);
|
|
16448
|
-
const nonEmptyKinds = [...grouped.keys()].filter((kind) => (grouped.get(kind)?.length ?? 0) > 0);
|
|
16449
|
-
if (nonEmptyKinds.length === 0) {
|
|
16450
|
-
return { groups: [] };
|
|
16451
|
-
}
|
|
16452
|
-
if (input2.slate.workspaceId === undefined) {
|
|
16453
|
-
await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
|
|
16454
|
-
const groups2 = nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" }));
|
|
16455
|
-
await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups2);
|
|
16456
|
-
return { groups: groups2 };
|
|
16457
|
-
}
|
|
16458
|
-
const workspaceId = input2.slate.workspaceId;
|
|
16459
|
-
const groups = await Promise.all(nonEmptyKinds.map((kind) => proposeOneGroup({
|
|
16460
|
-
cwd: input2.cwd,
|
|
16461
|
-
workspaceId,
|
|
16462
|
-
slate: input2.slate,
|
|
16463
|
-
kind,
|
|
16464
|
-
now,
|
|
16465
|
-
...input2.wrapUpSource !== undefined ? { wrapUpSource: input2.wrapUpSource } : {},
|
|
16466
|
-
...input2.env !== undefined ? { env: input2.env } : {},
|
|
16467
|
-
...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
|
|
16468
|
-
...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
|
|
16469
|
-
})));
|
|
16470
|
-
await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups);
|
|
16471
|
-
return { groups };
|
|
16472
|
-
}
|
|
16473
|
-
var execFileAsync, WRAP_UP_TTL_MS, DEFAULT_MODEL_TURN_TIMEOUT_MS = 30000;
|
|
16474
|
-
var init_machine_wrap_up = __esm(() => {
|
|
16475
|
-
init_fs();
|
|
16476
|
-
init_slate();
|
|
16477
|
-
init_slate_course();
|
|
16478
|
-
init_trusted_wrap_up();
|
|
16479
|
-
init_proposal_lifecycle();
|
|
16480
|
-
init_single_turn();
|
|
16481
|
-
execFileAsync = promisify(execFile);
|
|
16482
|
-
WRAP_UP_TTL_MS = 60 * 60 * 1000;
|
|
16483
|
-
});
|
|
16484
|
-
|
|
16485
16211
|
// src/sac/session-wrap-up.ts
|
|
16486
|
-
import { createHash as
|
|
16487
|
-
import { mkdir as
|
|
16488
|
-
import
|
|
16212
|
+
import { createHash as createHash11 } from "crypto";
|
|
16213
|
+
import { mkdir as mkdir28, writeFile as writeFile27 } from "fs/promises";
|
|
16214
|
+
import path75 from "path";
|
|
16489
16215
|
function sessionEvidenceRef(workspaceId, sessionId) {
|
|
16490
16216
|
return `./.metaproject/workspaces/${workspaceId}/session-evidence/${sessionId}.md`;
|
|
16491
16217
|
}
|
|
16492
16218
|
async function resolveSessionWrapUp(input2) {
|
|
16493
16219
|
const now = input2.now ?? (() => new Date);
|
|
16494
|
-
const sessionId =
|
|
16220
|
+
const sessionId = path75.posix.basename(input2.sourceRef, ".md");
|
|
16495
16221
|
const summary = findSession(input2.cwd, sessionId);
|
|
16496
16222
|
if (summary === undefined || input2.sourceRef !== sessionEvidenceRef(input2.workspaceId, summary.id)) {
|
|
16497
16223
|
throw new SessionWrapUpError("session_not_found", `no session matching "${input2.sourceRef}" in this project \u2014 use \`keryx sessions list\``);
|
|
@@ -16508,9 +16234,9 @@ async function resolveSessionWrapUp(input2) {
|
|
|
16508
16234
|
throw new SessionWrapUpError("session_unreadable", `session "${summary.id}" could not be read: ${cause.message}`);
|
|
16509
16235
|
}
|
|
16510
16236
|
const relPath = sessionEvidenceRef(input2.workspaceId, summary.id).slice(2);
|
|
16511
|
-
const evidenceDir =
|
|
16512
|
-
await
|
|
16513
|
-
await writeFile27(
|
|
16237
|
+
const evidenceDir = path75.dirname(relPath);
|
|
16238
|
+
await mkdir28(path75.join(input2.cwd, evidenceDir), { recursive: true });
|
|
16239
|
+
await writeFile27(path75.join(input2.cwd, relPath), markdown, "utf8");
|
|
16514
16240
|
const slate = await readSessionSlate(input2.cwd, summary.id);
|
|
16515
16241
|
const diffText = await gitDiff(input2.cwd);
|
|
16516
16242
|
const course = await readCourse(input2.cwd, slate?.course.flowRef);
|
|
@@ -16530,30 +16256,30 @@ async function resolveSessionWrapUp(input2) {
|
|
|
16530
16256
|
""
|
|
16531
16257
|
].join(`
|
|
16532
16258
|
`);
|
|
16533
|
-
const wrapUpRelPath =
|
|
16534
|
-
const diffRelPath =
|
|
16535
|
-
await writeFile27(
|
|
16536
|
-
await writeFile27(
|
|
16259
|
+
const wrapUpRelPath = path75.join(evidenceDir, `${summary.id}.wrap-up.md`);
|
|
16260
|
+
const diffRelPath = path75.join(evidenceDir, `${summary.id}.diff.txt`);
|
|
16261
|
+
await writeFile27(path75.join(input2.cwd, wrapUpRelPath), wrapUpMarkdown, "utf8");
|
|
16262
|
+
await writeFile27(path75.join(input2.cwd, diffRelPath), diffText, "utf8");
|
|
16537
16263
|
const observedAt = now().toISOString();
|
|
16538
16264
|
const evidence = [
|
|
16539
|
-
{ kind: "wrap-up", uri: `./${wrapUpRelPath}`, revision:
|
|
16540
|
-
{ kind: "diff", uri: `./${diffRelPath}`, revision:
|
|
16541
|
-
{ kind: "session", uri: `./${relPath}`, revision:
|
|
16265
|
+
{ kind: "wrap-up", uri: `./${wrapUpRelPath}`, revision: createHash11("sha256").update(wrapUpMarkdown).digest("hex"), observedAt },
|
|
16266
|
+
{ kind: "diff", uri: `./${diffRelPath}`, revision: createHash11("sha256").update(diffText).digest("hex"), observedAt },
|
|
16267
|
+
{ kind: "session", uri: `./${relPath}`, revision: createHash11("sha256").update(markdown).digest("hex"), observedAt }
|
|
16542
16268
|
];
|
|
16543
16269
|
return {
|
|
16544
16270
|
workspaceId: input2.workspaceId,
|
|
16545
16271
|
sourceRevision: summary.updatedAt,
|
|
16546
16272
|
summary: `Session "${summary.title}" (${summary.archiveMessageCount} messages${summary.model ? `, ${summary.provider ?? "?"}/${summary.model}` : ""})`,
|
|
16547
16273
|
evidence,
|
|
16548
|
-
expiresAt: new Date(now().getTime() +
|
|
16274
|
+
expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS).toISOString()
|
|
16549
16275
|
};
|
|
16550
16276
|
}
|
|
16551
|
-
var
|
|
16277
|
+
var WRAP_UP_TTL_MS, SessionWrapUpError;
|
|
16552
16278
|
var init_session_wrap_up = __esm(() => {
|
|
16553
16279
|
init_store3();
|
|
16554
16280
|
init_machine_wrap_up();
|
|
16555
16281
|
init_slate_course();
|
|
16556
|
-
|
|
16282
|
+
WRAP_UP_TTL_MS = 60 * 60 * 1000;
|
|
16557
16283
|
SessionWrapUpError = class SessionWrapUpError extends Error {
|
|
16558
16284
|
code;
|
|
16559
16285
|
constructor(code, message) {
|
|
@@ -16564,17 +16290,17 @@ var init_session_wrap_up = __esm(() => {
|
|
|
16564
16290
|
});
|
|
16565
16291
|
|
|
16566
16292
|
// src/sac/review-confirm-token.ts
|
|
16567
|
-
import { createHash as
|
|
16293
|
+
import { createHash as createHash12, randomBytes as randomBytes2 } from "crypto";
|
|
16568
16294
|
import { readFile as readFile35 } from "fs/promises";
|
|
16569
|
-
import
|
|
16295
|
+
import path76 from "path";
|
|
16570
16296
|
function confirmTokenPath(cwd, workspaceId, proposalId) {
|
|
16571
|
-
return
|
|
16297
|
+
return path76.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.confirm-token.json`);
|
|
16572
16298
|
}
|
|
16573
16299
|
function confirmReceiptPath(cwd, workspaceId, proposalId, idempotencyKey) {
|
|
16574
|
-
return
|
|
16300
|
+
return path76.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${sha256(idempotencyKey)}.confirm-receipt.json`);
|
|
16575
16301
|
}
|
|
16576
|
-
function
|
|
16577
|
-
return
|
|
16302
|
+
function sha256(text) {
|
|
16303
|
+
return createHash12("sha256").update(text).digest("hex");
|
|
16578
16304
|
}
|
|
16579
16305
|
async function mintConfirmToken(cwd, workspaceId, proposalId, deps = {}) {
|
|
16580
16306
|
const now = deps.now ?? (() => new Date);
|
|
@@ -16585,7 +16311,7 @@ async function mintConfirmToken(cwd, workspaceId, proposalId, deps = {}) {
|
|
|
16585
16311
|
const file = confirmTokenPath(cwd, workspaceId, proposalId);
|
|
16586
16312
|
const stored = {
|
|
16587
16313
|
schemaVersion: 1,
|
|
16588
|
-
hash:
|
|
16314
|
+
hash: sha256(token),
|
|
16589
16315
|
workspaceId,
|
|
16590
16316
|
proposalId,
|
|
16591
16317
|
mintedAt: mintedAt.toISOString(),
|
|
@@ -16612,7 +16338,7 @@ async function consumeConfirmToken(cwd, workspaceId, proposalId, idempotencyKey,
|
|
|
16612
16338
|
} catch (error2) {
|
|
16613
16339
|
return { ok: false, reason: isNotFound(error2) ? "token_required" : "token_invalid" };
|
|
16614
16340
|
}
|
|
16615
|
-
if (stored.usedAt !== undefined || new Date(stored.expiresAt).getTime() <= now().getTime() || stored.hash !==
|
|
16341
|
+
if (stored.usedAt !== undefined || new Date(stored.expiresAt).getTime() <= now().getTime() || stored.hash !== sha256(token) || stored.workspaceId !== workspaceId || stored.proposalId !== proposalId) {
|
|
16616
16342
|
return { ok: false, reason: "token_invalid" };
|
|
16617
16343
|
}
|
|
16618
16344
|
const consumed2 = { ...stored, usedAt: now().toISOString() };
|
|
@@ -16699,17 +16425,17 @@ function round2(value) {
|
|
|
16699
16425
|
var init_dedup = () => {};
|
|
16700
16426
|
|
|
16701
16427
|
// src/sac/proposal-evidence.ts
|
|
16702
|
-
import { createHash as
|
|
16428
|
+
import { createHash as createHash13 } from "crypto";
|
|
16703
16429
|
import { readFile as readFile36 } from "fs/promises";
|
|
16704
|
-
import
|
|
16430
|
+
import path77 from "path";
|
|
16705
16431
|
function proposalPath(cwd, workspaceId, proposalId) {
|
|
16706
|
-
return
|
|
16432
|
+
return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
|
|
16707
16433
|
}
|
|
16708
16434
|
function ownerReceiptPath(cwd, owner, workspaceId, idempotencyKey) {
|
|
16709
|
-
return
|
|
16435
|
+
return path77.join(cwd, ".metaproject", "workspaces", workspaceId, `${owner}-write-receipts`, `${idempotencyKey}.json`);
|
|
16710
16436
|
}
|
|
16711
16437
|
function proposalNotePath(cwd, workspaceId, proposalId) {
|
|
16712
|
-
return
|
|
16438
|
+
return path77.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.note.txt`);
|
|
16713
16439
|
}
|
|
16714
16440
|
async function readVerifiedProposalEvidence(cwd, workspaceId, proposalId) {
|
|
16715
16441
|
let proposal;
|
|
@@ -16723,11 +16449,11 @@ async function readVerifiedProposalEvidence(cwd, workspaceId, proposalId) {
|
|
|
16723
16449
|
return { ok: false, code: "no_evidence_to_write" };
|
|
16724
16450
|
let content;
|
|
16725
16451
|
try {
|
|
16726
|
-
content = await readFile36(
|
|
16452
|
+
content = await readFile36(path77.join(cwd, evidence.uri), "utf8");
|
|
16727
16453
|
} catch {
|
|
16728
16454
|
return { ok: false, code: "evidence_file_unreadable" };
|
|
16729
16455
|
}
|
|
16730
|
-
if (
|
|
16456
|
+
if (createHash13("sha256").update(content).digest("hex") !== evidence.revision) {
|
|
16731
16457
|
return { ok: false, code: "evidence_revision_mismatch" };
|
|
16732
16458
|
}
|
|
16733
16459
|
return { proposal, evidence, content };
|
|
@@ -16741,8 +16467,8 @@ var init_proposal_evidence = () => {};
|
|
|
16741
16467
|
|
|
16742
16468
|
// src/sac/decision-dedup.ts
|
|
16743
16469
|
import { readFile as readFile37, readdir as readdir10 } from "fs/promises";
|
|
16744
|
-
import { randomUUID as
|
|
16745
|
-
import
|
|
16470
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
16471
|
+
import path78 from "path";
|
|
16746
16472
|
function asMemoryStatus(value) {
|
|
16747
16473
|
return MEMORY_STATUSES.includes(value) ? value : "draft";
|
|
16748
16474
|
}
|
|
@@ -16769,14 +16495,14 @@ function extractHeaderField(content, field3) {
|
|
|
16769
16495
|
async function resolveWorkspaceModule(cwd, workspaceId) {
|
|
16770
16496
|
try {
|
|
16771
16497
|
const service = new WorkspaceService({ workspaceRoot: cwd, authorizationServer: localWorkspaceAuthorizationServer(), strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" } });
|
|
16772
|
-
const manifest = await service.show({ request: undefined, requestCorrelationId:
|
|
16498
|
+
const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID9(), workspaceId });
|
|
16773
16499
|
return manifest.resources.find((r) => r.kind === "component")?.uri ?? null;
|
|
16774
16500
|
} catch {
|
|
16775
16501
|
return null;
|
|
16776
16502
|
}
|
|
16777
16503
|
}
|
|
16778
16504
|
async function collectWikiDecisionEntries(cwd) {
|
|
16779
|
-
const dir =
|
|
16505
|
+
const dir = path78.join(cwd, ".metaproject", "wiki", "decisions");
|
|
16780
16506
|
let files;
|
|
16781
16507
|
try {
|
|
16782
16508
|
files = (await readdir10(dir)).filter((name) => name.endsWith(".md"));
|
|
@@ -16785,12 +16511,12 @@ async function collectWikiDecisionEntries(cwd) {
|
|
|
16785
16511
|
}
|
|
16786
16512
|
const entries = [];
|
|
16787
16513
|
for (const file of files) {
|
|
16788
|
-
const absolutePath =
|
|
16514
|
+
const absolutePath = path78.join(dir, file);
|
|
16789
16515
|
try {
|
|
16790
16516
|
const content = await readFile37(absolutePath, "utf8");
|
|
16791
16517
|
entries.push({
|
|
16792
16518
|
absolutePath,
|
|
16793
|
-
relativePath:
|
|
16519
|
+
relativePath: path78.posix.join("decisions", file),
|
|
16794
16520
|
type: extractHeaderField(content, "Type") ?? "decision",
|
|
16795
16521
|
title: extractTitle(content),
|
|
16796
16522
|
version: extractHeaderField(content, "Version") ?? null,
|
|
@@ -16909,13 +16635,13 @@ var init_decision_dedup = __esm(() => {
|
|
|
16909
16635
|
});
|
|
16910
16636
|
|
|
16911
16637
|
// src/memory/write.ts
|
|
16912
|
-
import { mkdir as
|
|
16913
|
-
import
|
|
16638
|
+
import { mkdir as mkdir29, open, readFile as readFile38, rename as rename3, rm as rm5, writeFile as writeFile28 } from "fs/promises";
|
|
16639
|
+
import path79 from "path";
|
|
16914
16640
|
async function resolveCanonicalEntryPath(cwd, relativePath) {
|
|
16915
|
-
const root =
|
|
16916
|
-
const absolutePath =
|
|
16917
|
-
const normalized = toPosix2(
|
|
16918
|
-
if (!normalized || normalized.startsWith("../") ||
|
|
16641
|
+
const root = path79.resolve(memoryRoot(cwd));
|
|
16642
|
+
const absolutePath = path79.resolve(root, relativePath);
|
|
16643
|
+
const normalized = toPosix2(path79.relative(root, absolutePath));
|
|
16644
|
+
if (!normalized || normalized.startsWith("../") || path79.isAbsolute(normalized) || !normalized.endsWith(".md")) {
|
|
16919
16645
|
return null;
|
|
16920
16646
|
}
|
|
16921
16647
|
return { absolutePath, relativePath: normalized };
|
|
@@ -17010,9 +16736,9 @@ function validateNextEntry(relativePath, content) {
|
|
|
17010
16736
|
return null;
|
|
17011
16737
|
}
|
|
17012
16738
|
async function replaceAtomically(target, content) {
|
|
17013
|
-
const dir =
|
|
17014
|
-
await
|
|
17015
|
-
const tmp =
|
|
16739
|
+
const dir = path79.dirname(target);
|
|
16740
|
+
await mkdir29(dir, { recursive: true });
|
|
16741
|
+
const tmp = path79.join(dir, `.${path79.basename(target)}.keryx-tmp-${process.pid}-${Math.random().toString(16).slice(2)}`);
|
|
17016
16742
|
try {
|
|
17017
16743
|
await writeFile28(tmp, content, "utf8");
|
|
17018
16744
|
const file = await open(tmp, "r");
|
|
@@ -17041,14 +16767,14 @@ function asPair(result, prior) {
|
|
|
17041
16767
|
return { status: "skipped", paths: [...prior, result.path], warnings: result.warnings, reason: result.reason };
|
|
17042
16768
|
return { status: "error", paths: [...prior, result.path], warnings: result.warnings, error: result.error };
|
|
17043
16769
|
}
|
|
17044
|
-
function persistenceError(
|
|
17045
|
-
return { status: "error", path:
|
|
16770
|
+
function persistenceError(path80, cause, warnings) {
|
|
16771
|
+
return { status: "error", path: path80, warnings, error: { code: "persistence-failed", message: message(cause) } };
|
|
17046
16772
|
}
|
|
17047
16773
|
function header(content, name) {
|
|
17048
16774
|
return content.match(new RegExp(`^${name}:\\s*(.+)$`, "mi"))?.[1]?.trim() ?? null;
|
|
17049
16775
|
}
|
|
17050
16776
|
function toPosix2(value) {
|
|
17051
|
-
return value.split(
|
|
16777
|
+
return value.split(path79.sep).join("/");
|
|
17052
16778
|
}
|
|
17053
16779
|
function message(cause) {
|
|
17054
16780
|
return cause instanceof Error ? cause.message : String(cause);
|
|
@@ -17060,8 +16786,8 @@ var init_write = __esm(() => {
|
|
|
17060
16786
|
});
|
|
17061
16787
|
|
|
17062
16788
|
// src/sac/memory-owner-writer.ts
|
|
17063
|
-
import { mkdir as
|
|
17064
|
-
import
|
|
16789
|
+
import { mkdir as mkdir30, readFile as readFile39, writeFile as writeFile29 } from "fs/promises";
|
|
16790
|
+
import path80 from "path";
|
|
17065
16791
|
function renderWrapUpMemoryEntry(input2) {
|
|
17066
16792
|
const note2 = input2.note?.trim();
|
|
17067
16793
|
return `# ${input2.title}
|
|
@@ -17146,7 +16872,7 @@ function createRealMemoryOwnerWriter(cwd, opts) {
|
|
|
17146
16872
|
targetRef: `./memory/${result.path}`,
|
|
17147
16873
|
completedAt: now().toISOString()
|
|
17148
16874
|
};
|
|
17149
|
-
await
|
|
16875
|
+
await mkdir30(path80.dirname(ownerReceiptPath(cwd, "memory", intent.workspaceId, intent.idempotencyKey)), { recursive: true });
|
|
17150
16876
|
await writeFile29(ownerReceiptPath(cwd, "memory", intent.workspaceId, intent.idempotencyKey), `${JSON.stringify(receipt, null, 2)}
|
|
17151
16877
|
`, "utf8");
|
|
17152
16878
|
return receipt;
|
|
@@ -17161,7 +16887,7 @@ var init_memory_owner_writer = __esm(() => {
|
|
|
17161
16887
|
|
|
17162
16888
|
// src/sac/wiki-owner-writer.ts
|
|
17163
16889
|
import { readFile as readFile40 } from "fs/promises";
|
|
17164
|
-
import
|
|
16890
|
+
import path81 from "path";
|
|
17165
16891
|
function renderWrapUpDecisionPage(input2) {
|
|
17166
16892
|
const note2 = input2.note?.trim();
|
|
17167
16893
|
return `# ${input2.title}
|
|
@@ -17202,7 +16928,7 @@ export is the source of truth for what actually happened.
|
|
|
17202
16928
|
`;
|
|
17203
16929
|
}
|
|
17204
16930
|
function wikiPageRelativePath(proposalId) {
|
|
17205
|
-
return
|
|
16931
|
+
return path81.posix.join("decisions", `sac-${proposalId}.md`);
|
|
17206
16932
|
}
|
|
17207
16933
|
function createRealWikiOwnerWriter(cwd, opts) {
|
|
17208
16934
|
const now = opts?.now ?? (() => new Date);
|
|
@@ -17239,7 +16965,7 @@ function createRealWikiOwnerWriter(cwd, opts) {
|
|
|
17239
16965
|
const guard = await guardOutput({ cwd, content, target: "wiki", source: "tool-output", path: `wiki/${relativePath}` });
|
|
17240
16966
|
if (!guard.allowed)
|
|
17241
16967
|
return { ok: false, code: `security_gate_${guard.reason ?? "blocked"}` };
|
|
17242
|
-
await writeFileAtomic(
|
|
16968
|
+
await writeFileAtomic(path81.join(cwd, ".metaproject", "wiki", relativePath), content);
|
|
17243
16969
|
const receipt = {
|
|
17244
16970
|
receiptRef: `./wiki/${relativePath.replace(/\.md$/, "")}.receipt.json`,
|
|
17245
16971
|
targetRef: `./wiki/${relativePath}`,
|
|
@@ -17258,23 +16984,23 @@ var init_wiki_owner_writer = __esm(() => {
|
|
|
17258
16984
|
});
|
|
17259
16985
|
|
|
17260
16986
|
// src/gdskills/project-skills.ts
|
|
17261
|
-
import { mkdir as
|
|
17262
|
-
import
|
|
16987
|
+
import { mkdir as mkdir31, readFile as readFile41, stat as stat4 } from "fs/promises";
|
|
16988
|
+
import path82 from "path";
|
|
17263
16989
|
async function createProjectSkill(projectRoot, options) {
|
|
17264
|
-
const metaprojectRoot =
|
|
16990
|
+
const metaprojectRoot = path82.join(projectRoot, ".metaproject");
|
|
17265
16991
|
if (!await pathExists(metaprojectRoot)) {
|
|
17266
16992
|
throw new Error("Metaproject is not initialized. Run: keryx init");
|
|
17267
16993
|
}
|
|
17268
16994
|
const moduleName = slugify3(options.module ?? inferModule(options.target));
|
|
17269
16995
|
const skillName = slugify3(options.name ?? inferSkillName(options.target));
|
|
17270
16996
|
const format = options.format ?? "auto";
|
|
17271
|
-
const packageRoot =
|
|
17272
|
-
const relativeSkillPath = toPosix(
|
|
16997
|
+
const packageRoot = path82.join(metaprojectRoot, "project-skills", moduleName, skillName);
|
|
16998
|
+
const relativeSkillPath = toPosix(path82.relative(projectRoot, packageRoot));
|
|
17273
16999
|
const evidence = await collectEvidence(projectRoot, options.target);
|
|
17274
17000
|
const warnings = collectWarnings(evidence, format);
|
|
17275
17001
|
const files = filesForPackage(packageRoot, format);
|
|
17276
17002
|
if (!options.dryRun) {
|
|
17277
|
-
await withFileLock2(
|
|
17003
|
+
await withFileLock2(path82.join(metaprojectRoot, "data", "gdskills", "project-skills.lock"), async () => {
|
|
17278
17004
|
await writeProjectSkillPackage({
|
|
17279
17005
|
projectRoot,
|
|
17280
17006
|
packageRoot,
|
|
@@ -17301,7 +17027,7 @@ async function createProjectSkill(projectRoot, options) {
|
|
|
17301
17027
|
name: skillName,
|
|
17302
17028
|
target: options.target,
|
|
17303
17029
|
skillPath: relativeSkillPath,
|
|
17304
|
-
files: files.map((filePath) => toPosix(
|
|
17030
|
+
files: files.map((filePath) => toPosix(path82.relative(projectRoot, filePath))),
|
|
17305
17031
|
warnings,
|
|
17306
17032
|
dryRun: options.dryRun === true
|
|
17307
17033
|
};
|
|
@@ -17323,31 +17049,31 @@ async function writeProjectSkillPackage({
|
|
|
17323
17049
|
}) {
|
|
17324
17050
|
const packageFormat = format === "single" ? "single" : "package";
|
|
17325
17051
|
const skillContent = renderProjectSkill({ moduleName, skillName, target, evidence, packageFormat });
|
|
17326
|
-
const relativeSkillMdPath = toPosix(
|
|
17052
|
+
const relativeSkillMdPath = toPosix(path82.join(path82.relative(projectRoot, packageRoot), "SKILL.md"));
|
|
17327
17053
|
const guard = await guardOutput({ cwd: projectRoot, content: skillContent, target: "skill", source: "generated", path: relativeSkillMdPath });
|
|
17328
17054
|
if (!guard.allowed) {
|
|
17329
17055
|
throw new Error(`Project skill blocked by the security gate: ${guard.reason ?? "policy violation"}`);
|
|
17330
17056
|
}
|
|
17331
|
-
await
|
|
17332
|
-
const skillPath =
|
|
17057
|
+
await mkdir31(packageRoot, { recursive: true });
|
|
17058
|
+
const skillPath = path82.join(packageRoot, "SKILL.md");
|
|
17333
17059
|
await writeFileAtomic(skillPath, skillContent);
|
|
17334
|
-
const changelogPath =
|
|
17060
|
+
const changelogPath = path82.join(packageRoot, "skill-changelog.md");
|
|
17335
17061
|
if (!await pathExists(changelogPath)) {
|
|
17336
17062
|
await writeFileAtomic(changelogPath, renderSkillChangelog({ moduleName, skillName, target }));
|
|
17337
17063
|
}
|
|
17338
17064
|
if (packageFormat === "package") {
|
|
17339
|
-
await
|
|
17340
|
-
await
|
|
17341
|
-
await writeFileAtomic(
|
|
17342
|
-
await writeFileAtomic(
|
|
17343
|
-
await writeFileAtomic(
|
|
17065
|
+
await mkdir31(path82.join(packageRoot, "references"), { recursive: true });
|
|
17066
|
+
await mkdir31(path82.join(packageRoot, "templates"), { recursive: true });
|
|
17067
|
+
await writeFileAtomic(path82.join(packageRoot, "references", "context.md"), renderReferenceContext({ moduleName, skillName, target, evidence }));
|
|
17068
|
+
await writeFileAtomic(path82.join(packageRoot, "templates", "README.md"), renderTemplatesReadme({ moduleName, skillName }));
|
|
17069
|
+
await writeFileAtomic(path82.join(packageRoot, "verification.md"), renderVerification({ moduleName, skillName, evidence }));
|
|
17344
17070
|
}
|
|
17345
17071
|
}
|
|
17346
17072
|
async function collectEvidence(projectRoot, target) {
|
|
17347
|
-
const absoluteTarget =
|
|
17073
|
+
const absoluteTarget = path82.resolve(projectRoot, target);
|
|
17348
17074
|
const targetExists = await pathExists(absoluteTarget);
|
|
17349
17075
|
const targetKind = await classifyTarget(absoluteTarget, targetExists);
|
|
17350
|
-
const maybeRelativeTarget = targetExists ? toPosix(
|
|
17076
|
+
const maybeRelativeTarget = targetExists ? toPosix(path82.relative(projectRoot, absoluteTarget)) : undefined;
|
|
17351
17077
|
const graphArtifacts = await existingRelativePaths(projectRoot, [
|
|
17352
17078
|
".metaproject/data/gdgraph/artifacts/summary.md",
|
|
17353
17079
|
".metaproject/data/gdgraph/artifacts/module-map.json"
|
|
@@ -17377,7 +17103,7 @@ async function classifyTarget(absoluteTarget, targetExists) {
|
|
|
17377
17103
|
async function existingRelativePaths(projectRoot, candidates) {
|
|
17378
17104
|
const existing = [];
|
|
17379
17105
|
for (const candidate of candidates) {
|
|
17380
|
-
if (await pathExists(
|
|
17106
|
+
if (await pathExists(path82.join(projectRoot, candidate))) {
|
|
17381
17107
|
existing.push(candidate);
|
|
17382
17108
|
}
|
|
17383
17109
|
}
|
|
@@ -17404,17 +17130,17 @@ function collectWarnings(evidence, format) {
|
|
|
17404
17130
|
}
|
|
17405
17131
|
function filesForPackage(packageRoot, format) {
|
|
17406
17132
|
const base = [
|
|
17407
|
-
|
|
17408
|
-
|
|
17133
|
+
path82.join(packageRoot, "SKILL.md"),
|
|
17134
|
+
path82.join(packageRoot, "skill-changelog.md")
|
|
17409
17135
|
];
|
|
17410
17136
|
if (format === "single") {
|
|
17411
17137
|
return base;
|
|
17412
17138
|
}
|
|
17413
17139
|
return [
|
|
17414
17140
|
...base,
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17141
|
+
path82.join(packageRoot, "verification.md"),
|
|
17142
|
+
path82.join(packageRoot, "references", "context.md"),
|
|
17143
|
+
path82.join(packageRoot, "templates", "README.md")
|
|
17418
17144
|
];
|
|
17419
17145
|
}
|
|
17420
17146
|
function renderProjectSkill({
|
|
@@ -17633,7 +17359,7 @@ keryx skills verify ${moduleName}/${skillName}
|
|
|
17633
17359
|
`;
|
|
17634
17360
|
}
|
|
17635
17361
|
async function updateManifest(projectRoot, entry) {
|
|
17636
|
-
const manifestPath =
|
|
17362
|
+
const manifestPath = path82.join(projectRoot, ".metaproject", "metaproject.json");
|
|
17637
17363
|
const manifest = await readJsonFileOr(manifestPath, {});
|
|
17638
17364
|
manifest.modules ??= {};
|
|
17639
17365
|
manifest.modules.gdskills ??= {};
|
|
@@ -17647,8 +17373,8 @@ async function updateManifest(projectRoot, entry) {
|
|
|
17647
17373
|
`);
|
|
17648
17374
|
}
|
|
17649
17375
|
async function updateSkillsCatalog(projectRoot) {
|
|
17650
|
-
const manifestPath =
|
|
17651
|
-
const catalogPath =
|
|
17376
|
+
const manifestPath = path82.join(projectRoot, ".metaproject", "metaproject.json");
|
|
17377
|
+
const catalogPath = path82.join(projectRoot, ".metaproject", "skills", "catalog.md");
|
|
17652
17378
|
const manifest = await readJsonFileOr(manifestPath, {});
|
|
17653
17379
|
const registry = manifest.modules?.gdskills?.projectSkillRegistry ?? [];
|
|
17654
17380
|
const rows = registry.length > 0 ? registry.map((entry) => `| ${entry.module} | ${entry.name} | \`${entry.target}\` | ${entry.path}/SKILL.md |`).join(`
|
|
@@ -17688,7 +17414,7 @@ function inferModule(target) {
|
|
|
17688
17414
|
}
|
|
17689
17415
|
function inferSkillName(target) {
|
|
17690
17416
|
const normalized = target.trim().replace(/[#:]+/g, "/");
|
|
17691
|
-
const base =
|
|
17417
|
+
const base = path82.basename(normalized).replace(/\.[^.]+$/, "");
|
|
17692
17418
|
return base || "entity";
|
|
17693
17419
|
}
|
|
17694
17420
|
function slugify3(value) {
|
|
@@ -17766,9 +17492,9 @@ var init_skill_owner_writer = __esm(() => {
|
|
|
17766
17492
|
});
|
|
17767
17493
|
|
|
17768
17494
|
// src/sac/proposal-lifecycle.ts
|
|
17769
|
-
import { createHash as
|
|
17770
|
-
import { appendFile as appendFile3, mkdir as
|
|
17771
|
-
import
|
|
17495
|
+
import { createHash as createHash14, randomUUID as randomUUID10 } from "crypto";
|
|
17496
|
+
import { appendFile as appendFile3, mkdir as mkdir32, readdir as readdir11, readFile as readFile43 } from "fs/promises";
|
|
17497
|
+
import path83 from "path";
|
|
17772
17498
|
|
|
17773
17499
|
class ProposalLifecycleService {
|
|
17774
17500
|
options;
|
|
@@ -17776,7 +17502,7 @@ class ProposalLifecycleService {
|
|
|
17776
17502
|
now;
|
|
17777
17503
|
constructor(options) {
|
|
17778
17504
|
this.options = options;
|
|
17779
|
-
this.root =
|
|
17505
|
+
this.root = path83.resolve(options.workspaceRoot);
|
|
17780
17506
|
this.now = options.now ?? (() => new Date);
|
|
17781
17507
|
}
|
|
17782
17508
|
async create(input2) {
|
|
@@ -17795,7 +17521,7 @@ class ProposalLifecycleService {
|
|
|
17795
17521
|
if (manifest.status === "archived")
|
|
17796
17522
|
throw new ProposalLifecycleError("guard_denied", "workspace is archived");
|
|
17797
17523
|
const file = this.proposalPath(workspaceId, proposal.id);
|
|
17798
|
-
await
|
|
17524
|
+
await mkdir32(path83.dirname(file), { recursive: true, mode: 448 });
|
|
17799
17525
|
return withFileLock2(`${file}.lock`, async () => {
|
|
17800
17526
|
const consume = this.options.wrapUpAuthority.consume(input2.wrapUp, { actor, workspaceId });
|
|
17801
17527
|
if (consume !== "ok")
|
|
@@ -17823,7 +17549,7 @@ class ProposalLifecycleService {
|
|
|
17823
17549
|
return this.options.workspaces.withAuthorizedActor({ actorContext: actor, workspaceId: input2.workspaceId, action: "review", execute: async (manifest) => {
|
|
17824
17550
|
const proposal = await this.loadProposal(input2.workspaceId, input2.proposalId);
|
|
17825
17551
|
const ledger = this.ledgerPath(input2.workspaceId);
|
|
17826
|
-
await
|
|
17552
|
+
await mkdir32(path83.dirname(ledger), { recursive: true, mode: 448 });
|
|
17827
17553
|
return withFileLock2(`${ledger}.lock`, async () => {
|
|
17828
17554
|
const records = (await this.records(ledger)).filter((record) => record.proposalId === proposal.id);
|
|
17829
17555
|
const events = records.filter((record) => record.recordType === "proposal-transition");
|
|
@@ -17864,7 +17590,7 @@ class ProposalLifecycleService {
|
|
|
17864
17590
|
} });
|
|
17865
17591
|
}
|
|
17866
17592
|
async listProposedProposals(workspaceId) {
|
|
17867
|
-
const proposalsDir =
|
|
17593
|
+
const proposalsDir = path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals");
|
|
17868
17594
|
let entries;
|
|
17869
17595
|
try {
|
|
17870
17596
|
entries = (await readdir11(proposalsDir)).filter((name) => name.endsWith(".json"));
|
|
@@ -17876,7 +17602,7 @@ class ProposalLifecycleService {
|
|
|
17876
17602
|
const proposals = [];
|
|
17877
17603
|
for (const entry of entries) {
|
|
17878
17604
|
try {
|
|
17879
|
-
const parsed = JSON.parse(await readFile43(
|
|
17605
|
+
const parsed = JSON.parse(await readFile43(path83.join(proposalsDir, entry), "utf8"));
|
|
17880
17606
|
if (parsed.recordType === "proposal-created")
|
|
17881
17607
|
proposals.push(parsed);
|
|
17882
17608
|
} catch {}
|
|
@@ -17935,7 +17661,7 @@ class ProposalLifecycleService {
|
|
|
17935
17661
|
}
|
|
17936
17662
|
async transition(input2) {
|
|
17937
17663
|
const previous = input2.records.at(-1);
|
|
17938
|
-
const base = { schemaVersion: "1.0", recordType: "proposal-transition", eventId: `event-${
|
|
17664
|
+
const base = { schemaVersion: "1.0", recordType: "proposal-transition", eventId: `event-${randomUUID10().replace(/-/g, "").slice(0, 16)}`, proposalId: input2.proposal.id, proposalRevision: input2.proposal.proposalRevision, correlationId: input2.input.requestCorrelationId, workspaceId: input2.proposal.workspaceId, sequence: input2.records.length + 1, priorEventHash: previous ? recordHash(previous) : hash("GENESIS"), fromStatus: "proposed", toStatus: input2.outcome, occurredAt: this.timestamp(), idempotencyKey: input2.input.idempotencyKey };
|
|
17939
17665
|
if (input2.outcome === "accepted") {
|
|
17940
17666
|
const write = input2.targetWrite;
|
|
17941
17667
|
if (!write?.ok)
|
|
@@ -17982,7 +17708,7 @@ class ProposalLifecycleService {
|
|
|
17982
17708
|
if (!isNotFound(error2))
|
|
17983
17709
|
throw error2;
|
|
17984
17710
|
}
|
|
17985
|
-
const intent = { schemaVersion: "1.0", recordType: "proposal-write-intent", intentId: `intent-${
|
|
17711
|
+
const intent = { schemaVersion: "1.0", recordType: "proposal-write-intent", intentId: `intent-${randomUUID10().replace(/-/g, "").slice(0, 16)}`, proposalId: proposal.id, proposalRevision: proposal.proposalRevision, correlationId: input2.requestCorrelationId, workspaceId: proposal.workspaceId, sequence: records.length + 1, priorEventHash: records.length ? recordHash(records.at(-1)) : hash("GENESIS"), idempotencyKey: input2.idempotencyKey, reviewer: { subject: actor.subject, authority: reviewerAuthority, trustedPrincipalRef: "./principals/local" }, approvalRef, security: { gate: "pass", policyRef: this.options.policyRef, policyRevision }, evidence: proposal.evidence, createdAt: this.timestamp() };
|
|
17986
17712
|
await this.validateRecord(intent);
|
|
17987
17713
|
await this.writeImmutable(this.intentPath(proposal.workspaceId, proposal.id, input2.idempotencyKey), intent);
|
|
17988
17714
|
await appendFile3(ledger, `${JSON.stringify(intent)}
|
|
@@ -18050,7 +17776,7 @@ class ProposalLifecycleService {
|
|
|
18050
17776
|
throw new ProposalLifecycleError("invalid_proposal", validation.errors.map((error2) => error2.code).join(","));
|
|
18051
17777
|
}
|
|
18052
17778
|
async reviewDecision(proposal, actor, reviewerAuthority, input2, decision, targetWrite, policyRevision) {
|
|
18053
|
-
const base = { schemaVersion: "1.0", id: `review-${
|
|
17779
|
+
const base = { schemaVersion: "1.0", id: `review-${randomUUID10().replace(/-/g, "").slice(0, 16)}`, proposalId: proposal.id, proposalRevision: proposal.proposalRevision, correlationId: input2.requestCorrelationId, workspaceId: proposal.workspaceId, decision, reviewer: { subject: actor.subject, authority: reviewerAuthority, trustedPrincipalRef: "./principals/local" }, decidedAt: this.timestamp(), idempotencyKey: input2.idempotencyKey };
|
|
18054
17780
|
if (decision === "accepted" && targetWrite?.ok)
|
|
18055
17781
|
Object.assign(base, { security: { gate: "pass", policyRef: this.options.policyRef, policyRevision }, freshness: { state: "fresh", verifiedAt: this.timestamp(), evidenceRevision: proposal.evidence[0].revision }, targetWrite: { receiptRef: targetWrite.receipt.receiptRef, targetRef: targetWrite.receipt.targetRef, completedAt: targetWrite.receipt.completedAt } });
|
|
18056
17782
|
else
|
|
@@ -18082,28 +17808,28 @@ class ProposalLifecycleService {
|
|
|
18082
17808
|
}
|
|
18083
17809
|
}
|
|
18084
17810
|
proposalPath(workspaceId, proposalId) {
|
|
18085
|
-
return
|
|
17811
|
+
return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.json`);
|
|
18086
17812
|
}
|
|
18087
17813
|
decisionRef(proposalId, key) {
|
|
18088
17814
|
return `./proposals/${proposalId}.${hash(key)}.decision.json`;
|
|
18089
17815
|
}
|
|
18090
17816
|
decisionPath(workspaceId, proposalId, key) {
|
|
18091
|
-
return
|
|
17817
|
+
return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.decision.json`);
|
|
18092
17818
|
}
|
|
18093
17819
|
approvalRef(proposalId, key) {
|
|
18094
17820
|
return `./proposals/${proposalId}.${hash(key)}.approval.json`;
|
|
18095
17821
|
}
|
|
18096
17822
|
approvalPath(workspaceId, proposalId, key) {
|
|
18097
|
-
return
|
|
17823
|
+
return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.approval.json`);
|
|
18098
17824
|
}
|
|
18099
17825
|
writeResultPath(workspaceId, proposalId, key) {
|
|
18100
|
-
return
|
|
17826
|
+
return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.write-result.json`);
|
|
18101
17827
|
}
|
|
18102
17828
|
intentRef(proposalId, key) {
|
|
18103
17829
|
return `./proposals/${proposalId}.${hash(key)}.write-intent.json`;
|
|
18104
17830
|
}
|
|
18105
17831
|
intentPath(workspaceId, proposalId, key) {
|
|
18106
|
-
return
|
|
17832
|
+
return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${hash(key)}.write-intent.json`);
|
|
18107
17833
|
}
|
|
18108
17834
|
async loadWriteResult(workspaceId, proposalId, key) {
|
|
18109
17835
|
try {
|
|
@@ -18130,14 +17856,14 @@ class ProposalLifecycleService {
|
|
|
18130
17856
|
}
|
|
18131
17857
|
}
|
|
18132
17858
|
ledgerPath(workspaceId) {
|
|
18133
|
-
return
|
|
17859
|
+
return path83.join(this.root, ".metaproject", "workspaces", workspaceId, "activity.jsonl");
|
|
18134
17860
|
}
|
|
18135
17861
|
timestamp() {
|
|
18136
17862
|
return this.now().toISOString();
|
|
18137
17863
|
}
|
|
18138
17864
|
}
|
|
18139
17865
|
function hash(value) {
|
|
18140
|
-
return
|
|
17866
|
+
return createHash14("sha256").update(value).digest("hex");
|
|
18141
17867
|
}
|
|
18142
17868
|
function recordHash(value) {
|
|
18143
17869
|
return hash(JSON.stringify(value));
|
|
@@ -18235,9 +17961,9 @@ var init_proposal_lifecycle = __esm(() => {
|
|
|
18235
17961
|
});
|
|
18236
17962
|
|
|
18237
17963
|
// src/harness/tool/builtin/workspace-lifecycle-tool.ts
|
|
18238
|
-
import { randomUUID as
|
|
17964
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
18239
17965
|
import { writeFile as writeFile30 } from "fs/promises";
|
|
18240
|
-
import
|
|
17966
|
+
import path84 from "path";
|
|
18241
17967
|
function service(cwd) {
|
|
18242
17968
|
return new WorkspaceService({
|
|
18243
17969
|
workspaceRoot: cwd,
|
|
@@ -18248,11 +17974,11 @@ function service(cwd) {
|
|
|
18248
17974
|
function errorOutput(prefix, cause) {
|
|
18249
17975
|
return { output: `${prefix}: ${cause instanceof Error ? cause.message : String(cause)}`, isError: true };
|
|
18250
17976
|
}
|
|
18251
|
-
function workspaceCreateTool(cwd) {
|
|
17977
|
+
function workspaceCreateTool(cwd, getSessionDir) {
|
|
18252
17978
|
return {
|
|
18253
17979
|
definition: {
|
|
18254
17980
|
name: "workspace_create",
|
|
18255
|
-
description: "Create a new Shared Agent Context (SAC) workspace to bind this session's work to. Call workspace_list FIRST and only create when no existing workspace already fits the current topic \u2014 a workspace is meant to persist and accumulate context across sessions, not to be created per-session. Input: { title: string, component?: string }. `component` is an optional workspace-relative path this workspace is scoped to.",
|
|
17981
|
+
description: "Create a new Shared Agent Context (SAC) workspace to bind this session's work to. Call workspace_list FIRST and only create when no existing workspace already fits the current topic \u2014 a workspace is meant to persist and accumulate context across sessions, not to be created per-session. The created workspace is BOUND to this session's slate (its workspaceId is written to the slate), so wrap-up can propose into it. Input: { title: string, component?: string }. `component` is an optional workspace-relative path this workspace is scoped to.",
|
|
18256
17982
|
inputSchema: {
|
|
18257
17983
|
type: "object",
|
|
18258
17984
|
properties: { title: { type: "string" }, component: { type: "string" } },
|
|
@@ -18269,11 +17995,23 @@ function workspaceCreateTool(cwd) {
|
|
|
18269
17995
|
try {
|
|
18270
17996
|
const workspace = await service(cwd).create({
|
|
18271
17997
|
request: undefined,
|
|
18272
|
-
requestCorrelationId:
|
|
17998
|
+
requestCorrelationId: randomUUID11(),
|
|
18273
17999
|
id: newWorkspaceId(),
|
|
18274
18000
|
title,
|
|
18275
18001
|
...component ? { component: { kind: "component", uri: component } } : {}
|
|
18276
18002
|
});
|
|
18003
|
+
const dir = getSessionDir?.();
|
|
18004
|
+
if (dir !== undefined) {
|
|
18005
|
+
try {
|
|
18006
|
+
await writeSlate(dir, (prev) => ({
|
|
18007
|
+
anchors: prev?.anchors ?? { root: "", touched: [] },
|
|
18008
|
+
course: prev?.course ?? {},
|
|
18009
|
+
seeds: prev?.seeds ?? [],
|
|
18010
|
+
...prev !== undefined ? { workspaceId: prev.workspaceId } : {},
|
|
18011
|
+
workspaceId: workspace.id
|
|
18012
|
+
}));
|
|
18013
|
+
} catch {}
|
|
18014
|
+
}
|
|
18277
18015
|
return { output: JSON.stringify(workspace, null, 2), isError: false };
|
|
18278
18016
|
} catch (cause) {
|
|
18279
18017
|
return errorOutput("workspace_create failed", cause);
|
|
@@ -18296,7 +18034,7 @@ function workspaceListTool(cwd) {
|
|
|
18296
18034
|
invoke: async (input2) => {
|
|
18297
18035
|
const includeArchived = input2.includeArchived === true;
|
|
18298
18036
|
try {
|
|
18299
|
-
const workspaces = await service(cwd).list({ request: undefined, requestCorrelationId:
|
|
18037
|
+
const workspaces = await service(cwd).list({ request: undefined, requestCorrelationId: randomUUID11(), includeArchived });
|
|
18300
18038
|
return { output: JSON.stringify(workspaces, null, 2), isError: false };
|
|
18301
18039
|
} catch (cause) {
|
|
18302
18040
|
return errorOutput("workspace_list failed", cause);
|
|
@@ -18322,7 +18060,7 @@ function workspaceShowTool(cwd) {
|
|
|
18322
18060
|
if (workspaceId.length === 0)
|
|
18323
18061
|
return { output: "workspace_show requires a non-empty 'workspaceId'", isError: true };
|
|
18324
18062
|
try {
|
|
18325
|
-
const workspace = await service(cwd).show({ request: undefined, requestCorrelationId:
|
|
18063
|
+
const workspace = await service(cwd).show({ request: undefined, requestCorrelationId: randomUUID11(), workspaceId });
|
|
18326
18064
|
return { output: JSON.stringify(workspace, null, 2), isError: false };
|
|
18327
18065
|
} catch (cause) {
|
|
18328
18066
|
return errorOutput("workspace_show failed", cause);
|
|
@@ -18358,7 +18096,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
|
|
|
18358
18096
|
return { output: `workspace_propose: unrecognized 'kind' \u2014 expected one of: ${PROPOSAL_KINDS.join(", ")}`, isError: true };
|
|
18359
18097
|
}
|
|
18360
18098
|
const explicitSessionId = typeof input2.sessionId === "string" && input2.sessionId.length > 0 ? input2.sessionId : undefined;
|
|
18361
|
-
const sessionRef = explicitSessionId ??
|
|
18099
|
+
const sessionRef = explicitSessionId ?? path84.basename(getSessionDir() ?? "");
|
|
18362
18100
|
if (sessionRef.length === 0) {
|
|
18363
18101
|
return { output: "workspace_propose: no 'sessionId' given and no active session in this run", isError: true };
|
|
18364
18102
|
}
|
|
@@ -18367,7 +18105,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
|
|
|
18367
18105
|
if (!session)
|
|
18368
18106
|
return { output: `workspace_propose: no session matching "${sessionRef}" in this project`, isError: true };
|
|
18369
18107
|
const { service: lifecycle, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...note2 ? { note: note2 } : {} });
|
|
18370
|
-
const requestCorrelationId =
|
|
18108
|
+
const requestCorrelationId = randomUUID11();
|
|
18371
18109
|
const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
|
|
18372
18110
|
if (!actor)
|
|
18373
18111
|
return { output: "workspace_propose: trusted ActorContext is required", isError: true };
|
|
@@ -18376,7 +18114,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
|
|
|
18376
18114
|
request: undefined,
|
|
18377
18115
|
requestCorrelationId,
|
|
18378
18116
|
workspaceId,
|
|
18379
|
-
id: `proposal-${
|
|
18117
|
+
id: `proposal-${randomUUID11().replace(/-/g, "").slice(0, 16)}`,
|
|
18380
18118
|
proposalRevision: "1",
|
|
18381
18119
|
kind,
|
|
18382
18120
|
wrapUp
|
|
@@ -18396,6 +18134,7 @@ var init_workspace_lifecycle_tool = __esm(() => {
|
|
|
18396
18134
|
init_proposal_lifecycle();
|
|
18397
18135
|
init_proposal_evidence();
|
|
18398
18136
|
init_session_wrap_up();
|
|
18137
|
+
init_slate();
|
|
18399
18138
|
init_store3();
|
|
18400
18139
|
PROPOSAL_KINDS = ["decision", "wiki-update", "memory-entry", "follow-up", "contract-change", "risk"];
|
|
18401
18140
|
});
|
|
@@ -18445,7 +18184,7 @@ ${topicHint}
|
|
|
18445
18184
|
${existing.map((w) => `${w.id}: ${w.title}`).join(`
|
|
18446
18185
|
`)}`;
|
|
18447
18186
|
let modelResult;
|
|
18448
|
-
const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ??
|
|
18187
|
+
const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS;
|
|
18449
18188
|
const turn = runModelTurn({
|
|
18450
18189
|
system,
|
|
18451
18190
|
user,
|
|
@@ -18490,13 +18229,315 @@ ${existing.map((w) => `${w.id}: ${w.title}`).join(`
|
|
|
18490
18229
|
const { id } = JSON.parse(created.output);
|
|
18491
18230
|
return { ok: true, workspaceId: id, action: "created" };
|
|
18492
18231
|
}
|
|
18493
|
-
var
|
|
18232
|
+
var DEFAULT_MODEL_TURN_TIMEOUT_MS = 15000, TOPIC_HINT_MAX_LENGTH = 200;
|
|
18494
18233
|
var init_workspace_resolve = __esm(() => {
|
|
18495
18234
|
init_redact();
|
|
18496
18235
|
init_workspace_lifecycle_tool();
|
|
18497
18236
|
init_single_turn();
|
|
18498
18237
|
});
|
|
18499
18238
|
|
|
18239
|
+
// src/sac/machine-wrap-up.ts
|
|
18240
|
+
import { createHash as createHash15, randomUUID as randomUUID12 } from "crypto";
|
|
18241
|
+
import { execFile } from "child_process";
|
|
18242
|
+
import { mkdir as mkdir33 } from "fs/promises";
|
|
18243
|
+
import path85 from "path";
|
|
18244
|
+
import { promisify } from "util";
|
|
18245
|
+
function describeSource(source) {
|
|
18246
|
+
return source === "parent" ? "parent" : `child:${source.childDispatchId}`;
|
|
18247
|
+
}
|
|
18248
|
+
function dedupedAttributedSeeds(slate) {
|
|
18249
|
+
const seen = new Set;
|
|
18250
|
+
const result = [];
|
|
18251
|
+
const take = (seeds, source) => {
|
|
18252
|
+
for (const seed of dedupeSeeds(seeds)) {
|
|
18253
|
+
const key = seed.text.trim();
|
|
18254
|
+
if (seen.has(key))
|
|
18255
|
+
continue;
|
|
18256
|
+
seen.add(key);
|
|
18257
|
+
result.push({ text: seed.text, kind: seed.kind ?? "follow-up", source });
|
|
18258
|
+
}
|
|
18259
|
+
};
|
|
18260
|
+
take(slate.seeds, "parent");
|
|
18261
|
+
const childDispatches = slate.childDispatches ?? {};
|
|
18262
|
+
for (const [dispatchId, dispatch] of Object.entries(childDispatches)) {
|
|
18263
|
+
take(dispatch.seeds, { childDispatchId: dispatchId });
|
|
18264
|
+
}
|
|
18265
|
+
return result;
|
|
18266
|
+
}
|
|
18267
|
+
function groupSeedsByKind(slate) {
|
|
18268
|
+
const map = new Map;
|
|
18269
|
+
for (const seed of dedupedAttributedSeeds(slate)) {
|
|
18270
|
+
const bucket = map.get(seed.kind);
|
|
18271
|
+
if (bucket)
|
|
18272
|
+
bucket.push(seed);
|
|
18273
|
+
else
|
|
18274
|
+
map.set(seed.kind, [seed]);
|
|
18275
|
+
}
|
|
18276
|
+
return map;
|
|
18277
|
+
}
|
|
18278
|
+
function sha2562(value) {
|
|
18279
|
+
return createHash15("sha256").update(value).digest("hex");
|
|
18280
|
+
}
|
|
18281
|
+
async function gitDiff(cwd) {
|
|
18282
|
+
try {
|
|
18283
|
+
const { stdout: stdout2 } = await execFileAsync("git", ["diff"], { cwd, maxBuffer: 16 * 1024 * 1024 });
|
|
18284
|
+
return stdout2;
|
|
18285
|
+
} catch {
|
|
18286
|
+
return "";
|
|
18287
|
+
}
|
|
18288
|
+
}
|
|
18289
|
+
function diffStatLine(diffText) {
|
|
18290
|
+
if (diffText.trim().length === 0)
|
|
18291
|
+
return "no working-tree changes";
|
|
18292
|
+
const added = (diffText.match(/^\+(?!\+\+)/gm) ?? []).length;
|
|
18293
|
+
const removed = (diffText.match(/^-(?!--)/gm) ?? []).length;
|
|
18294
|
+
return `working-tree diff: +${added}/-${removed} line(s)`;
|
|
18295
|
+
}
|
|
18296
|
+
function courseStatusLine(course) {
|
|
18297
|
+
if (course.state !== "bound")
|
|
18298
|
+
return "flow: unbound";
|
|
18299
|
+
return `flow ${course.flowRef.uri} snapshot=${course.flowRef.snapshot} completed=${course.completed.length} next=${course.next.length} blocked=${course.blocked.length}`;
|
|
18300
|
+
}
|
|
18301
|
+
function mechanicalSummary(diffText, course) {
|
|
18302
|
+
return `Mechanical wrap-up summary (model turn unavailable or timed out):
|
|
18303
|
+
${diffStatLine(diffText)}
|
|
18304
|
+
${courseStatusLine(course)}`;
|
|
18305
|
+
}
|
|
18306
|
+
async function resolveMachineWrapUp(input2) {
|
|
18307
|
+
const now = input2.now ?? (() => new Date);
|
|
18308
|
+
const diffText = await gitDiff(input2.cwd);
|
|
18309
|
+
const course = await readCourse(input2.cwd, input2.slate.course.flowRef);
|
|
18310
|
+
const seedsForKind = dedupedAttributedSeeds(input2.slate).filter((seed) => seed.kind === input2.kind);
|
|
18311
|
+
const flowSnapshotJson = `${JSON.stringify(course, null, 2)}
|
|
18312
|
+
`;
|
|
18313
|
+
const seedsJson = `${JSON.stringify(seedsForKind.map((seed) => ({ text: seed.text, source: describeSource(seed.source) })), null, 2)}
|
|
18314
|
+
`;
|
|
18315
|
+
const sourceRevision = sha2562([diffText, flowSnapshotJson, seedsJson].join("\x00"));
|
|
18316
|
+
const shortHash = sourceRevision.slice(0, 16);
|
|
18317
|
+
const system = "Summarize ONLY the machine evidence provided below \u2014 a git diff, a Flow snapshot, and the Seeds captured " + "this session for one proposal kind. Never invent facts that are not present in the evidence.";
|
|
18318
|
+
const user = `--- git diff ---
|
|
18319
|
+
${diffText.length > 0 ? diffText : "(no working-tree changes)"}
|
|
18320
|
+
|
|
18321
|
+
` + `--- flow snapshot ---
|
|
18322
|
+
${flowSnapshotJson}
|
|
18323
|
+
` + `--- seeds (${input2.kind}) ---
|
|
18324
|
+
${seedsJson}`;
|
|
18325
|
+
let modelResult;
|
|
18326
|
+
const modelTurnTimeoutMs = input2.modelTurnTimeoutMs ?? DEFAULT_MODEL_TURN_TIMEOUT_MS2;
|
|
18327
|
+
const turn = runModelTurn({
|
|
18328
|
+
system,
|
|
18329
|
+
user,
|
|
18330
|
+
requestId: `machine-wrap-up-${shortHash}`,
|
|
18331
|
+
...input2.env !== undefined ? { env: input2.env } : {},
|
|
18332
|
+
...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {}
|
|
18333
|
+
}).then((result) => {
|
|
18334
|
+
modelResult = result;
|
|
18335
|
+
return "done";
|
|
18336
|
+
});
|
|
18337
|
+
let timer;
|
|
18338
|
+
const expired = new Promise((resolve) => {
|
|
18339
|
+
timer = setTimeout(() => resolve("timeout"), modelTurnTimeoutMs);
|
|
18340
|
+
});
|
|
18341
|
+
let raceOutcome;
|
|
18342
|
+
try {
|
|
18343
|
+
raceOutcome = await Promise.race([turn, expired]);
|
|
18344
|
+
} finally {
|
|
18345
|
+
if (timer !== undefined)
|
|
18346
|
+
clearTimeout(timer);
|
|
18347
|
+
}
|
|
18348
|
+
let summary;
|
|
18349
|
+
if (raceOutcome === "timeout") {
|
|
18350
|
+
turn.catch(() => {});
|
|
18351
|
+
summary = mechanicalSummary(diffText, course);
|
|
18352
|
+
} else {
|
|
18353
|
+
const result = modelResult;
|
|
18354
|
+
if (result.text.trim().length === 0 && !result.credentialAvailable) {
|
|
18355
|
+
return { ok: false, code: "no_credential" };
|
|
18356
|
+
}
|
|
18357
|
+
summary = result.text.trim().length > 0 ? result.text.trim() : mechanicalSummary(diffText, course);
|
|
18358
|
+
}
|
|
18359
|
+
const evidenceDir = path85.join(input2.cwd, ".metaproject", "workspaces", input2.workspaceId, "machine-evidence");
|
|
18360
|
+
await mkdir33(evidenceDir, { recursive: true });
|
|
18361
|
+
const diffFile = `${input2.kind}.${shortHash}.diff.txt`;
|
|
18362
|
+
const flowFile = `${input2.kind}.${shortHash}.flow.json`;
|
|
18363
|
+
const seedsFile = `${input2.kind}.${shortHash}.seeds.json`;
|
|
18364
|
+
await writeFileAtomic(path85.join(evidenceDir, diffFile), diffText);
|
|
18365
|
+
await writeFileAtomic(path85.join(evidenceDir, flowFile), flowSnapshotJson);
|
|
18366
|
+
await writeFileAtomic(path85.join(evidenceDir, seedsFile), seedsJson);
|
|
18367
|
+
const observedAt = now().toISOString();
|
|
18368
|
+
const relBase = `./.metaproject/workspaces/${input2.workspaceId}/machine-evidence`;
|
|
18369
|
+
const evidence = [
|
|
18370
|
+
{ kind: "diff", uri: `${relBase}/${diffFile}`, revision: sha2562(diffText), observedAt },
|
|
18371
|
+
{ kind: "flow", uri: `${relBase}/${flowFile}`, revision: sha2562(flowSnapshotJson), observedAt },
|
|
18372
|
+
{ kind: "seeds", uri: `${relBase}/${seedsFile}`, revision: sha2562(seedsJson), observedAt }
|
|
18373
|
+
];
|
|
18374
|
+
return {
|
|
18375
|
+
ok: true,
|
|
18376
|
+
resolution: {
|
|
18377
|
+
workspaceId: input2.workspaceId,
|
|
18378
|
+
sourceRevision,
|
|
18379
|
+
summary,
|
|
18380
|
+
evidence,
|
|
18381
|
+
expiresAt: new Date(now().getTime() + WRAP_UP_TTL_MS2).toISOString()
|
|
18382
|
+
}
|
|
18383
|
+
};
|
|
18384
|
+
}
|
|
18385
|
+
async function writeUnboundCandidateArtifact(dir, trigger, now, grouped, nonEmptyKinds) {
|
|
18386
|
+
const archiveDir = path85.join(dir, "slate-archive");
|
|
18387
|
+
await mkdir33(archiveDir, { recursive: true });
|
|
18388
|
+
const nowIso2 = now().toISOString();
|
|
18389
|
+
const filename = `${nowIso2.replace(/[:.]/g, "-")}-unbound-candidate.json`;
|
|
18390
|
+
const content = {
|
|
18391
|
+
recordType: "unbound-candidate",
|
|
18392
|
+
trigger,
|
|
18393
|
+
generatedAt: nowIso2,
|
|
18394
|
+
groups: nonEmptyKinds.map((kind) => ({
|
|
18395
|
+
kind,
|
|
18396
|
+
seeds: (grouped.get(kind) ?? []).map((seed) => ({ text: seed.text, source: describeSource(seed.source) }))
|
|
18397
|
+
}))
|
|
18398
|
+
};
|
|
18399
|
+
await writeFileAtomic(path85.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
|
|
18400
|
+
`);
|
|
18401
|
+
}
|
|
18402
|
+
async function writeWrapUpOutcomeArtifact(dir, trigger, now, groups) {
|
|
18403
|
+
try {
|
|
18404
|
+
const archiveDir = path85.join(dir, "slate-archive");
|
|
18405
|
+
await mkdir33(archiveDir, { recursive: true });
|
|
18406
|
+
const nowIso2 = now().toISOString();
|
|
18407
|
+
const filename = `${nowIso2.replace(/[:.]/g, "-")}-wrap-up-outcome.json`;
|
|
18408
|
+
const content = { recordType: "wrap-up-outcome", trigger, generatedAt: nowIso2, groups };
|
|
18409
|
+
await writeFileAtomic(path85.join(archiveDir, filename), `${JSON.stringify(content, null, 2)}
|
|
18410
|
+
`);
|
|
18411
|
+
} catch {}
|
|
18412
|
+
}
|
|
18413
|
+
async function proposeOneGroup(params) {
|
|
18414
|
+
const wrapUpSource = params.wrapUpSource ?? "flow";
|
|
18415
|
+
try {
|
|
18416
|
+
const resolved = await resolveMachineWrapUp({
|
|
18417
|
+
cwd: params.cwd,
|
|
18418
|
+
workspaceId: params.workspaceId,
|
|
18419
|
+
slate: params.slate,
|
|
18420
|
+
kind: params.kind,
|
|
18421
|
+
now: params.now,
|
|
18422
|
+
...params.env !== undefined ? { env: params.env } : {},
|
|
18423
|
+
...params.providerFactory !== undefined ? { providerFactory: params.providerFactory } : {},
|
|
18424
|
+
...params.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: params.modelTurnTimeoutMs } : {}
|
|
18425
|
+
});
|
|
18426
|
+
if (!resolved.ok)
|
|
18427
|
+
return { kind: params.kind, outcome: "no_credential" };
|
|
18428
|
+
const flowEvidence = resolved.resolution.evidence.find((item) => item.kind === "flow");
|
|
18429
|
+
const sourceRef = (flowEvidence ?? resolved.resolution.evidence[0]).uri;
|
|
18430
|
+
const flowRef = params.slate.course.flowRef ?? "";
|
|
18431
|
+
const dedupHash = sha2562(`${params.workspaceId}:${flowRef}:${resolved.resolution.sourceRevision}:${params.kind}`);
|
|
18432
|
+
const proposalId = `wrapup-${dedupHash.slice(0, 32)}`;
|
|
18433
|
+
const wrapUpAuthority = createTrustedWrapUpAuthority({
|
|
18434
|
+
now: params.now,
|
|
18435
|
+
resolveExplicitWrapUp: async (request) => {
|
|
18436
|
+
if (request.source !== wrapUpSource) {
|
|
18437
|
+
throw new Error(`machine-wrap-up only resolves "${wrapUpSource}" wrap-ups, got "${request.source}"`);
|
|
18438
|
+
}
|
|
18439
|
+
return resolved.resolution;
|
|
18440
|
+
}
|
|
18441
|
+
});
|
|
18442
|
+
const { service: service2, authorizationServer } = createHarnessProposalLifecycleService(params.cwd, {
|
|
18443
|
+
workspaceId: params.workspaceId,
|
|
18444
|
+
now: params.now
|
|
18445
|
+
});
|
|
18446
|
+
const requestCorrelationId = randomUUID12();
|
|
18447
|
+
const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
|
|
18448
|
+
if (!actor)
|
|
18449
|
+
throw new Error("trusted ActorContext is required for a machine wrap-up propose");
|
|
18450
|
+
const provenance = await wrapUpAuthority.issue({ actor, source: wrapUpSource, sourceRef });
|
|
18451
|
+
try {
|
|
18452
|
+
const proposal = await service2.create({
|
|
18453
|
+
request: undefined,
|
|
18454
|
+
requestCorrelationId,
|
|
18455
|
+
workspaceId: params.workspaceId,
|
|
18456
|
+
id: proposalId,
|
|
18457
|
+
proposalRevision: "1",
|
|
18458
|
+
kind: params.kind,
|
|
18459
|
+
wrapUp: provenance
|
|
18460
|
+
});
|
|
18461
|
+
return { kind: params.kind, outcome: "proposed", proposalId: proposal.id };
|
|
18462
|
+
} catch (error2) {
|
|
18463
|
+
if (error2 instanceof ProposalLifecycleError && error2.code === "conflict") {
|
|
18464
|
+
return { kind: params.kind, outcome: "conflict" };
|
|
18465
|
+
}
|
|
18466
|
+
throw error2;
|
|
18467
|
+
}
|
|
18468
|
+
} catch (error2) {
|
|
18469
|
+
const message2 = error2 instanceof Error ? error2.message : String(error2);
|
|
18470
|
+
return { kind: params.kind, outcome: "error", message: message2 };
|
|
18471
|
+
}
|
|
18472
|
+
}
|
|
18473
|
+
async function runWrapUp(input2) {
|
|
18474
|
+
const now = input2.now ?? (() => new Date);
|
|
18475
|
+
const grouped = groupSeedsByKind(input2.slate);
|
|
18476
|
+
const nonEmptyKinds = [...grouped.keys()].filter((kind) => (grouped.get(kind)?.length ?? 0) > 0);
|
|
18477
|
+
if (nonEmptyKinds.length === 0) {
|
|
18478
|
+
return { groups: [] };
|
|
18479
|
+
}
|
|
18480
|
+
let workspaceId = input2.slate.workspaceId;
|
|
18481
|
+
if (workspaceId === undefined && input2.wrapUpSource === "external-slate") {
|
|
18482
|
+
await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
|
|
18483
|
+
const groups2 = nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" }));
|
|
18484
|
+
await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups2);
|
|
18485
|
+
return { groups: groups2 };
|
|
18486
|
+
}
|
|
18487
|
+
if (workspaceId === undefined) {
|
|
18488
|
+
const topicHint = dedupedAttributedSeeds(input2.slate).map((seed) => seed.text).join("; ").trim().slice(0, 2000);
|
|
18489
|
+
const resolver = input2.resolveWorkspace ?? resolveOrCreateWorkspace;
|
|
18490
|
+
const resolved = await resolver({
|
|
18491
|
+
cwd: input2.cwd,
|
|
18492
|
+
topicHint: topicHint.length > 0 ? topicHint : "Untitled session wrap-up",
|
|
18493
|
+
...input2.env !== undefined ? { env: input2.env } : {},
|
|
18494
|
+
...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
|
|
18495
|
+
...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
|
|
18496
|
+
});
|
|
18497
|
+
if (!resolved.ok) {
|
|
18498
|
+
await writeUnboundCandidateArtifact(input2.dir, input2.trigger, now, grouped, nonEmptyKinds);
|
|
18499
|
+
const groups2 = nonEmptyKinds.map((kind) => ({ kind, outcome: "unbound-candidate" }));
|
|
18500
|
+
await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups2);
|
|
18501
|
+
return { groups: groups2 };
|
|
18502
|
+
}
|
|
18503
|
+
workspaceId = resolved.workspaceId;
|
|
18504
|
+
const boundWorkspaceId = resolved.workspaceId;
|
|
18505
|
+
try {
|
|
18506
|
+
await writeSlate(input2.dir, (prev) => ({
|
|
18507
|
+
anchors: prev?.anchors ?? { root: "", touched: [] },
|
|
18508
|
+
course: prev?.course ?? {},
|
|
18509
|
+
seeds: prev?.seeds ?? [],
|
|
18510
|
+
workspaceId: boundWorkspaceId
|
|
18511
|
+
}));
|
|
18512
|
+
} catch {}
|
|
18513
|
+
}
|
|
18514
|
+
const groups = await Promise.all(nonEmptyKinds.map((kind) => proposeOneGroup({
|
|
18515
|
+
cwd: input2.cwd,
|
|
18516
|
+
workspaceId,
|
|
18517
|
+
slate: input2.slate,
|
|
18518
|
+
kind,
|
|
18519
|
+
now,
|
|
18520
|
+
...input2.wrapUpSource !== undefined ? { wrapUpSource: input2.wrapUpSource } : {},
|
|
18521
|
+
...input2.env !== undefined ? { env: input2.env } : {},
|
|
18522
|
+
...input2.providerFactory !== undefined ? { providerFactory: input2.providerFactory } : {},
|
|
18523
|
+
...input2.modelTurnTimeoutMs !== undefined ? { modelTurnTimeoutMs: input2.modelTurnTimeoutMs } : {}
|
|
18524
|
+
})));
|
|
18525
|
+
await writeWrapUpOutcomeArtifact(input2.dir, input2.trigger, now, groups);
|
|
18526
|
+
return { groups };
|
|
18527
|
+
}
|
|
18528
|
+
var execFileAsync, WRAP_UP_TTL_MS2, DEFAULT_MODEL_TURN_TIMEOUT_MS2 = 30000;
|
|
18529
|
+
var init_machine_wrap_up = __esm(() => {
|
|
18530
|
+
init_fs();
|
|
18531
|
+
init_slate();
|
|
18532
|
+
init_slate_course();
|
|
18533
|
+
init_trusted_wrap_up();
|
|
18534
|
+
init_proposal_lifecycle();
|
|
18535
|
+
init_workspace_resolve();
|
|
18536
|
+
init_single_turn();
|
|
18537
|
+
execFileAsync = promisify(execFile);
|
|
18538
|
+
WRAP_UP_TTL_MS2 = 60 * 60 * 1000;
|
|
18539
|
+
});
|
|
18540
|
+
|
|
18500
18541
|
// src/session/slate-lifecycle.ts
|
|
18501
18542
|
import { execFile as execFile2 } from "child_process";
|
|
18502
18543
|
import { promisify as promisify2 } from "util";
|
|
@@ -18870,8 +18911,8 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
|
|
|
18870
18911
|
` + "- To find where a function/class/symbol is defined (or who calls it): call " + "**graph_symbol** with `{ name }` FIRST \u2014 it returns the exact file + line in one call. " + "read_file is capped at its first bytes only (see its own description) and cannot page " + "forward, so re-reading a large file to hunt for a symbol wastes calls without ever " + "reaching content past the cap; use graph_symbol (or search_code for a text pattern) " + `to get the location, THEN read_file only if you need surrounding context near it.
|
|
18871
18912
|
` + "- Prefer ONE correct shell_exec over many exploratory tool calls when the user asks " + `to run a known keryx workflow.
|
|
18872
18913
|
` + "- Tool-call budget: shell_exec, file-mutating shell, workspace_create/workspace_propose, and spawn_subagent " + "all share ONE small per-turn pool (distinct non-read actions), separate from the much larger read-tool pool. " + "search_code/graph_*/memory_search/read_wiki/wiki_*/test_related/health_status/flow_status/repomap/read_file/" + "list_dir do NOT touch it. Conserve the small pool: batch multiple shell steps into ONE call with `&&` instead " + "of issuing them one at a time, get a command's arguments right the first time instead of trying variants, and " + "for any check covered by a read tool above, use that tool instead of shelling out to the equivalent `keryx \u2026` " + `CLI command.
|
|
18873
|
-
` + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human review " + "
|
|
18874
|
-
` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace
|
|
18914
|
+
` + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
|
|
18915
|
+
` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace AND binds it to " + "this session's slate (wrap-up then proposes into it) \u2014 only when workspace_list found no " + "fitting one and the session has real, durable results worth persisting; a workspace is " + "meant to persist across sessions, so prefer an existing one over creating another for the " + "same topic, and do NOT create one for one-shot operational requests. **workspace_propose** with " + "`{ workspaceId, kind, sessionId?, note? }` (sessionId defaults to this session) proposes a decision/wiki-update/memory-entry/" + "follow-up/contract-change/risk from this session for later human review \u2014 it never accepts " + "anything by itself; accepting always requires a human running `keryx workspace review` at a " + `real terminal, never this tool.
|
|
18875
18916
|
` + "- When you need a decision, interview step, or clarification: use **ask_user** with " + "2\u20136 options `{ id, label, description, recommended? }` (mark one recommended). " + `Do not dump long prose questions without options.
|
|
18876
18917
|
` + "- For a focused independent subtask (investigate X, review Y, research Z): use " + "**spawn_subagent** with `{ task, mode?: 'read_only'|'general', label? }`. " + "Default mode is read_only (no shell). Prefer spawn for work that can finish " + `without your intermediate turns; do not spawn for trivial one-line answers.
|
|
18877
18918
|
|
|
@@ -19114,22 +19155,6 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
19114
19155
|
if (freshSlate !== undefined) {
|
|
19115
19156
|
history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project" });
|
|
19116
19157
|
io.onHistoryChange?.("tool");
|
|
19117
|
-
if (freshSlate.workspaceId === undefined) {
|
|
19118
|
-
const resolver = options.resolveWorkspace ?? resolveOrCreateWorkspace;
|
|
19119
|
-
const resolved = await resolver({
|
|
19120
|
-
cwd: options.slateSession.cwd,
|
|
19121
|
-
topicHint: userLine,
|
|
19122
|
-
provider: deps.providerId,
|
|
19123
|
-
model: deps.modelId
|
|
19124
|
-
});
|
|
19125
|
-
if (resolved.ok) {
|
|
19126
|
-
await writeSlate(options.slateSession.dir, (prev) => {
|
|
19127
|
-
if (!prev)
|
|
19128
|
-
throw new Error(`SLATE-16 bind: no open slate in ${options.slateSession.dir}`);
|
|
19129
|
-
return { ...prev, workspaceId: resolved.workspaceId };
|
|
19130
|
-
});
|
|
19131
|
-
}
|
|
19132
|
-
}
|
|
19133
19158
|
}
|
|
19134
19159
|
}
|
|
19135
19160
|
}
|
|
@@ -19710,7 +19735,6 @@ var init_agent = __esm(() => {
|
|
|
19710
19735
|
init_scheduler();
|
|
19711
19736
|
init_slate();
|
|
19712
19737
|
init_slate_course();
|
|
19713
|
-
init_workspace_resolve();
|
|
19714
19738
|
init_machine_wrap_up();
|
|
19715
19739
|
init_slate_lifecycle();
|
|
19716
19740
|
init_slate_terminal_state();
|
|
@@ -49832,7 +49856,7 @@ function buildInteractiveAgentTools(input2) {
|
|
|
49832
49856
|
applyPatchTool(input2.cwd),
|
|
49833
49857
|
workspaceOverviewTool(input2.cwd),
|
|
49834
49858
|
workspaceReadTool(input2.cwd),
|
|
49835
|
-
workspaceCreateTool(input2.cwd),
|
|
49859
|
+
workspaceCreateTool(input2.cwd, getSessionDir),
|
|
49836
49860
|
workspaceListTool(input2.cwd),
|
|
49837
49861
|
workspaceShowTool(input2.cwd),
|
|
49838
49862
|
workspaceProposeTool(input2.cwd, getSessionDir),
|
|
@@ -53121,7 +53145,6 @@ init_agent();
|
|
|
53121
53145
|
init_slate_lifecycle();
|
|
53122
53146
|
init_slate();
|
|
53123
53147
|
init_workspace_service();
|
|
53124
|
-
init_workspace_resolve();
|
|
53125
53148
|
init_service7();
|
|
53126
53149
|
init_store2();
|
|
53127
53150
|
init_fs();
|
|
@@ -53390,7 +53413,7 @@ async function runGoalVerifier(deps, goalText, cwd, slateSession, history, io, m
|
|
|
53390
53413
|
return parseVerifierVerdict(result.output);
|
|
53391
53414
|
}
|
|
53392
53415
|
async function runGoalCommand(params) {
|
|
53393
|
-
const { raw, cwd, io, deps, history, slateSession, mintAttemptId
|
|
53416
|
+
const { raw, cwd, io, deps, history, slateSession, mintAttemptId } = params;
|
|
53394
53417
|
const parsed = parseGoalArgs(raw);
|
|
53395
53418
|
if ("error" in parsed) {
|
|
53396
53419
|
systemLine(io, `/goal: ${parsed.error}
|
|
@@ -53424,19 +53447,6 @@ async function runGoalCommand(params) {
|
|
|
53424
53447
|
const base = prev ?? { anchors: { root: "", touched: [] }, course: {}, seeds: [] };
|
|
53425
53448
|
return { ...base, workspaceId };
|
|
53426
53449
|
});
|
|
53427
|
-
} else {
|
|
53428
|
-
const current = await readSlate(slateSession.dir);
|
|
53429
|
-
if (current !== undefined && current.workspaceId === undefined) {
|
|
53430
|
-
const resolver = resolveWorkspace ?? resolveOrCreateWorkspace;
|
|
53431
|
-
const resolved2 = await resolver({ cwd, topicHint: parsed.text, provider: deps.providerId, model: deps.modelId });
|
|
53432
|
-
if (resolved2.ok) {
|
|
53433
|
-
await writeSlate(slateSession.dir, (prev) => {
|
|
53434
|
-
if (!prev)
|
|
53435
|
-
throw new Error(`SLATE-16 bind: no open slate in ${slateSession.dir}`);
|
|
53436
|
-
return { ...prev, workspaceId: resolved2.workspaceId };
|
|
53437
|
-
});
|
|
53438
|
-
}
|
|
53439
|
-
}
|
|
53440
53450
|
}
|
|
53441
53451
|
if (parsed.auto !== undefined) {
|
|
53442
53452
|
const forCourse = await readSlate(slateSession.dir);
|
|
@@ -53535,7 +53545,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
53535
53545
|
// package.json
|
|
53536
53546
|
var package_default = {
|
|
53537
53547
|
name: "@mrciphersmith/keryx",
|
|
53538
|
-
version: "0.2.
|
|
53548
|
+
version: "0.2.57",
|
|
53539
53549
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
53540
53550
|
private: false,
|
|
53541
53551
|
publishConfig: {
|
|
@@ -54688,6 +54698,13 @@ function ensureHost(otui, chrome) {
|
|
|
54688
54698
|
}
|
|
54689
54699
|
const idx = state.tabs.findIndex((tab) => tab.id === state.active);
|
|
54690
54700
|
const onStrip = focused !== null && containsNode(state.tabStrip, focused);
|
|
54701
|
+
const claimArrow = (direction) => state.input?.onArrowKeys?.(key, direction) === true;
|
|
54702
|
+
if (key.name === "left" && claimArrow("left")) {
|
|
54703
|
+
return;
|
|
54704
|
+
}
|
|
54705
|
+
if (key.name === "right" && claimArrow("right")) {
|
|
54706
|
+
return;
|
|
54707
|
+
}
|
|
54691
54708
|
if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
|
|
54692
54709
|
const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
|
|
54693
54710
|
if (prev !== undefined) {
|
|
@@ -55959,10 +55976,9 @@ function openWorkspace(otui, chrome, options) {
|
|
|
55959
55976
|
var REVIEW_COMMAND = "/review";
|
|
55960
55977
|
var REVIEW_FOOTER = [
|
|
55961
55978
|
{ key: "[/]", label: "item" },
|
|
55962
|
-
{ key: "a
|
|
55963
|
-
{ key: "
|
|
55979
|
+
{ key: "\u2190/\u2192 a d", label: "accept/decline" },
|
|
55980
|
+
{ key: "enter y", label: "arm \u2192 confirm" },
|
|
55964
55981
|
{ key: "\u2191/\u2193", label: "scroll" },
|
|
55965
|
-
{ key: "\u2190/\u2192", label: "tabs" },
|
|
55966
55982
|
{ key: "esc", label: "close" }
|
|
55967
55983
|
];
|
|
55968
55984
|
function isReviewCommand(line) {
|
|
@@ -56146,14 +56162,58 @@ function paintLines3(otui, renderer, body, lines, width) {
|
|
|
56146
56162
|
parent.add(node);
|
|
56147
56163
|
return node;
|
|
56148
56164
|
}
|
|
56165
|
+
function paintActionButtons(otui, renderer, body, callbacks) {
|
|
56166
|
+
if (otui === undefined || otui === null || body === undefined || body === null) {
|
|
56167
|
+
return;
|
|
56168
|
+
}
|
|
56169
|
+
const parent = body;
|
|
56170
|
+
const boxCtor = otui.BoxRenderable;
|
|
56171
|
+
const textCtor = otui.TextRenderable;
|
|
56172
|
+
if (parent.add === undefined || boxCtor === undefined || textCtor === undefined) {
|
|
56173
|
+
return;
|
|
56174
|
+
}
|
|
56175
|
+
const BoxCtor = boxCtor;
|
|
56176
|
+
const TextCtor = textCtor;
|
|
56177
|
+
const addChild = (child) => parent.add?.(child);
|
|
56178
|
+
const theme = getTheme();
|
|
56179
|
+
const make = (label, id, color, onClick) => {
|
|
56180
|
+
const box = new BoxCtor(renderer, {
|
|
56181
|
+
id,
|
|
56182
|
+
flexShrink: 0,
|
|
56183
|
+
marginLeft: 1,
|
|
56184
|
+
paddingLeft: 1,
|
|
56185
|
+
paddingRight: 1,
|
|
56186
|
+
onMouseDown: (event) => {
|
|
56187
|
+
event.stopPropagation();
|
|
56188
|
+
onClick();
|
|
56189
|
+
}
|
|
56190
|
+
});
|
|
56191
|
+
const text = new TextCtor(renderer, { id: `${id}-t`, content: `[${label}]` });
|
|
56192
|
+
text.fg = color;
|
|
56193
|
+
box.add(text);
|
|
56194
|
+
addChild(box);
|
|
56195
|
+
const setActive = (active) => {
|
|
56196
|
+
box.backgroundColor = active ? theme.highlight : undefined;
|
|
56197
|
+
text.content = `[${label}]`;
|
|
56198
|
+
text.fg = color;
|
|
56199
|
+
};
|
|
56200
|
+
return { setActive };
|
|
56201
|
+
};
|
|
56202
|
+
return {
|
|
56203
|
+
accept: make("Accept", "review-accept", theme.ok, callbacks.onAccept),
|
|
56204
|
+
decline: make("Decline", "review-decline", theme.error, callbacks.onDecline)
|
|
56205
|
+
};
|
|
56206
|
+
}
|
|
56149
56207
|
function presentReview(openModal2, otui, chrome, options) {
|
|
56150
56208
|
const items = [...options.items];
|
|
56151
56209
|
let selected = 0;
|
|
56152
56210
|
let listScroll = 0;
|
|
56153
56211
|
let detailScroll = 0;
|
|
56154
56212
|
let status = { kind: "idle" };
|
|
56213
|
+
let focusedAction = "accept";
|
|
56155
56214
|
let listNode;
|
|
56156
56215
|
let detailNode;
|
|
56216
|
+
let actionButtons;
|
|
56157
56217
|
let unsubscribeKey;
|
|
56158
56218
|
const rendererHint = options.renderer ?? chrome?.renderer;
|
|
56159
56219
|
const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
|
|
@@ -56174,6 +56234,7 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
56174
56234
|
detailNode.content = windowLines3(detailLines(), detailScroll, bodyRows).join(`
|
|
56175
56235
|
`);
|
|
56176
56236
|
}
|
|
56237
|
+
updateButtons();
|
|
56177
56238
|
};
|
|
56178
56239
|
const moveSelection = (next) => {
|
|
56179
56240
|
if (items.length === 0) {
|
|
@@ -56189,6 +56250,22 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
56189
56250
|
paintSelection();
|
|
56190
56251
|
};
|
|
56191
56252
|
const handlerFor = (decision) => decision === "accept" ? options.acceptProposal : options.declineProposal;
|
|
56253
|
+
const updateButtons = () => {
|
|
56254
|
+
if (actionButtons === undefined) {
|
|
56255
|
+
return;
|
|
56256
|
+
}
|
|
56257
|
+
const onProposal = items[selected]?.type === "proposal" && status.kind !== "done";
|
|
56258
|
+
const highlighted = status.kind === "armed" ? status.decision : focusedAction;
|
|
56259
|
+
actionButtons.accept.setActive(onProposal && highlighted === "accept");
|
|
56260
|
+
actionButtons.decline.setActive(onProposal && highlighted === "decline");
|
|
56261
|
+
};
|
|
56262
|
+
const armDecision = (decision) => {
|
|
56263
|
+
if (status.kind === "running") {
|
|
56264
|
+
return;
|
|
56265
|
+
}
|
|
56266
|
+
status = items[selected]?.type === "proposal" && handlerFor(decision) !== undefined ? { kind: "armed", decision } : { kind: "unavailable", decision };
|
|
56267
|
+
paintSelection();
|
|
56268
|
+
};
|
|
56192
56269
|
const runDecision = (decision) => {
|
|
56193
56270
|
const item = items[selected];
|
|
56194
56271
|
const run = handlerFor(decision);
|
|
@@ -56213,16 +56290,43 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
56213
56290
|
],
|
|
56214
56291
|
initialTab: "list",
|
|
56215
56292
|
footer: REVIEW_FOOTER,
|
|
56293
|
+
onArrowKeys: (key, direction) => {
|
|
56294
|
+
if (items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done" && handle?.activeTab() === "detail") {
|
|
56295
|
+
focusedAction = direction === "left" ? "accept" : "decline";
|
|
56296
|
+
status = { kind: "idle" };
|
|
56297
|
+
paintSelection();
|
|
56298
|
+
return true;
|
|
56299
|
+
}
|
|
56300
|
+
return false;
|
|
56301
|
+
},
|
|
56216
56302
|
renderTab: (tabId, body, ctx) => {
|
|
56217
56303
|
const renderer = options.renderer ?? chrome?.renderer;
|
|
56218
56304
|
tabWidth = ctx?.width;
|
|
56219
56305
|
if (tabId === "list") {
|
|
56306
|
+
detailNode = undefined;
|
|
56307
|
+
actionButtons = undefined;
|
|
56220
56308
|
listScroll = scrollToReveal3(selected, listScroll, bodyRows);
|
|
56221
56309
|
listNode = paintLines3(otui, renderer, body, windowLines3(listLines(), listScroll, bodyRows));
|
|
56222
56310
|
return;
|
|
56223
56311
|
}
|
|
56312
|
+
listNode = undefined;
|
|
56224
56313
|
detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
|
|
56225
56314
|
detailNode = paintLines3(otui, renderer, body, windowLines3(detailLines(), detailScroll, bodyRows), tabWidth);
|
|
56315
|
+
if (items[selected]?.type === "proposal") {
|
|
56316
|
+
actionButtons = paintActionButtons(otui, renderer, body, {
|
|
56317
|
+
onAccept: () => {
|
|
56318
|
+
focusedAction = "accept";
|
|
56319
|
+
armDecision("accept");
|
|
56320
|
+
},
|
|
56321
|
+
onDecline: () => {
|
|
56322
|
+
focusedAction = "decline";
|
|
56323
|
+
armDecision("decline");
|
|
56324
|
+
}
|
|
56325
|
+
});
|
|
56326
|
+
updateButtons();
|
|
56327
|
+
} else {
|
|
56328
|
+
actionButtons = undefined;
|
|
56329
|
+
}
|
|
56226
56330
|
},
|
|
56227
56331
|
onClose: () => {
|
|
56228
56332
|
unsubscribeKey?.();
|
|
@@ -56239,7 +56343,7 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
56239
56343
|
}
|
|
56240
56344
|
const onDetail = handle.activeTab() === "detail";
|
|
56241
56345
|
if (onDetail && status.kind === "armed") {
|
|
56242
|
-
if (token === "y") {
|
|
56346
|
+
if (token === "y" || token === "return" || token === "enter") {
|
|
56243
56347
|
runDecision(status.decision);
|
|
56244
56348
|
} else {
|
|
56245
56349
|
status = { kind: "idle" };
|
|
@@ -56256,15 +56360,29 @@ function presentReview(openModal2, otui, chrome, options) {
|
|
|
56256
56360
|
return;
|
|
56257
56361
|
}
|
|
56258
56362
|
if (token === "return" || token === "enter") {
|
|
56363
|
+
if (onDetail && items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done") {
|
|
56364
|
+
if (status.kind === "armed") {
|
|
56365
|
+
runDecision(status.decision);
|
|
56366
|
+
} else {
|
|
56367
|
+
armDecision(focusedAction);
|
|
56368
|
+
}
|
|
56369
|
+
return;
|
|
56370
|
+
}
|
|
56259
56371
|
handle.setTab("detail");
|
|
56260
56372
|
return;
|
|
56261
56373
|
}
|
|
56262
|
-
if (onDetail && (token === "
|
|
56263
|
-
|
|
56264
|
-
status =
|
|
56374
|
+
if (onDetail && (token === "left" || token === "right") && status.kind !== "running" && status.kind !== "done") {
|
|
56375
|
+
focusedAction = token === "left" ? "accept" : "decline";
|
|
56376
|
+
status = { kind: "idle" };
|
|
56265
56377
|
paintSelection();
|
|
56266
56378
|
return;
|
|
56267
56379
|
}
|
|
56380
|
+
if (onDetail && (token === "a" || token === "d") && status.kind !== "running" && status.kind !== "done") {
|
|
56381
|
+
const decision = token === "a" ? "accept" : "decline";
|
|
56382
|
+
focusedAction = decision;
|
|
56383
|
+
armDecision(decision);
|
|
56384
|
+
return;
|
|
56385
|
+
}
|
|
56268
56386
|
if (token === "up" || token === "k") {
|
|
56269
56387
|
if (onDetail) {
|
|
56270
56388
|
detailScroll = clampScroll3(detailScroll - 1, detailLines().length, bodyRows);
|