@wrongstack/core 0.303.0 → 0.305.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coordination/agents/index.js +530 -130
- package/dist/coordination/agents/project-agent-consolidation.d.ts +5 -0
- package/dist/coordination/agents/project-agent-directive-outcome.d.ts +57 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +26 -12
- package/dist/coordination/agents/project-agent-learning-policy.d.ts +22 -1
- package/dist/coordination/agents/project-agent-learning-structured.d.ts +46 -1
- package/dist/coordination/agents/project-agent-quarantine.d.ts +63 -0
- package/dist/coordination/agents/project-agent-skill-layer.d.ts +55 -10
- package/dist/coordination/agents/types.d.ts +10 -2
- package/dist/coordination/director-prompts.d.ts +19 -6
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/fleet.d.ts +0 -6
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +1579 -955
- package/dist/core/agent-types.d.ts +4 -2
- package/dist/core/agent.d.ts +1 -0
- package/dist/core/context.d.ts +15 -0
- package/dist/core/conversation-state.d.ts +14 -0
- package/dist/core/fallback-profile-manager.d.ts +70 -2
- package/dist/core/index.js +308 -108
- package/dist/core/system-prompt-blocks.d.ts +1 -1
- package/dist/core/system-prompt-builder.d.ts +13 -1
- package/dist/core/system-prompt-glossary.d.ts +73 -0
- package/dist/core/system-prompt-memory-skills.d.ts +2 -2
- package/dist/defaults/index.js +910 -693
- package/dist/execution/council-orchestrator.d.ts +3 -13
- package/dist/execution/index.js +211 -75
- package/dist/execution/one-shot-llm.d.ts +5 -0
- package/dist/hq/index.js +17 -7
- package/dist/hq/protocol/kanban.d.ts +21 -0
- package/dist/hq/protocol.js +5 -1
- package/dist/hq/redaction.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3482 -2538
- package/dist/infrastructure/index.js +247 -122
- package/dist/plugin/index.js +101 -3
- package/dist/registry/index.js +11 -0
- package/dist/registry/tool-registry.d.ts +8 -0
- package/dist/replay/hash.d.ts +9 -0
- package/dist/replay/index.js +14 -4
- package/dist/replay/replay-provider-runner.d.ts +31 -1
- package/dist/security/index.js +25 -20
- package/dist/security/secret-vault.d.ts +2 -0
- package/dist/session-catalog/index.js +62 -8
- package/dist/session-catalog/project-server.js +73 -13
- package/dist/session-catalog/protocol.d.ts +11 -4
- package/dist/session-catalog/store.d.ts +2 -2
- package/dist/storage/index.js +224 -67
- package/dist/storage/memory-consolidator.d.ts +4 -2
- package/dist/storage/session-resume-validation.d.ts +24 -0
- package/dist/storage/session-store/directory-scan.d.ts +5 -1
- package/dist/storage/session-store/fork-session.d.ts +13 -1
- package/dist/storage/session-store/load-cache.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +5 -0
- package/dist/storage/session-store.d.ts +18 -0
- package/dist/tools/index.js +174 -74
- package/dist/types/config/mcp-features.d.ts +31 -1
- package/dist/types/config/root.d.ts +12 -0
- package/dist/types/config/tools.d.ts +22 -0
- package/dist/types/default-config.d.ts +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/session.d.ts +9 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +214 -76
- package/dist/utils/project-state-guard.d.ts +21 -0
- package/dist/utils/session-scoped-path.d.ts +17 -0
- package/dist/utils/todos-format.d.ts +20 -0
- package/instructions/leader-after-task.md +3 -4
- package/instructions/system-lite.md +10 -13
- package/instructions/system-pro.md +18 -25
- package/instructions/system.md +18 -23
- package/package.json +3 -3
- package/skills/wrongstack-kanban/SKILL.md +95 -124
|
@@ -1264,8 +1264,8 @@ function defaultFormatTaskInput(task) {
|
|
|
1264
1264
|
}
|
|
1265
1265
|
|
|
1266
1266
|
// src/coordination/agents/agent-prompts.ts
|
|
1267
|
-
import { readFileSync as
|
|
1268
|
-
import * as
|
|
1267
|
+
import { readFileSync as readFileSync10, statSync as statSync2 } from "node:fs";
|
|
1268
|
+
import * as path12 from "node:path";
|
|
1269
1269
|
import { fileURLToPath } from "node:url";
|
|
1270
1270
|
|
|
1271
1271
|
// src/utils/wstack-paths.ts
|
|
@@ -1408,8 +1408,8 @@ function resolveWstackPaths(opts) {
|
|
|
1408
1408
|
}
|
|
1409
1409
|
|
|
1410
1410
|
// src/coordination/agents/project-agent-identity.ts
|
|
1411
|
-
import { existsSync as existsSync4, readFileSync as
|
|
1412
|
-
import * as
|
|
1411
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
1412
|
+
import * as path11 from "node:path";
|
|
1413
1413
|
|
|
1414
1414
|
// src/types/runtime-capability-manifest.ts
|
|
1415
1415
|
var PLAYWRIGHT_ALIASES = {
|
|
@@ -1901,8 +1901,8 @@ function loadProjectAgentConfig(role, projectRoot) {
|
|
|
1901
1901
|
}
|
|
1902
1902
|
|
|
1903
1903
|
// src/coordination/agents/project-agent-consolidation.ts
|
|
1904
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as
|
|
1905
|
-
import * as
|
|
1904
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
1905
|
+
import * as path6 from "node:path";
|
|
1906
1906
|
|
|
1907
1907
|
// src/coordination/agents/project-agent-learning-entries.ts
|
|
1908
1908
|
function tokenOverlap(a, b) {
|
|
@@ -2040,6 +2040,21 @@ function normalizeForComparison(text) {
|
|
|
2040
2040
|
}
|
|
2041
2041
|
|
|
2042
2042
|
// src/coordination/agents/project-agent-learning-structured.ts
|
|
2043
|
+
function directiveTrials(entry) {
|
|
2044
|
+
const count = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
|
|
2045
|
+
const applied = count(entry.applied);
|
|
2046
|
+
const wins = Math.min(applied, count(entry.wins));
|
|
2047
|
+
return { applied, wins, losses: applied - wins };
|
|
2048
|
+
}
|
|
2049
|
+
function directiveUtility(entry) {
|
|
2050
|
+
const { applied, wins } = directiveTrials(entry);
|
|
2051
|
+
return (wins + 1) / (applied + 2);
|
|
2052
|
+
}
|
|
2053
|
+
var DIRECTIVE_PROVEN_MIN_APPLIED = 5;
|
|
2054
|
+
var DIRECTIVE_PROVEN_MIN_UTILITY = 0.7;
|
|
2055
|
+
function isProvenDirective(entry) {
|
|
2056
|
+
return directiveTrials(entry).applied >= DIRECTIVE_PROVEN_MIN_APPLIED && directiveUtility(entry) >= DIRECTIVE_PROVEN_MIN_UTILITY;
|
|
2057
|
+
}
|
|
2043
2058
|
function parseLearnedEntryStamp(entry) {
|
|
2044
2059
|
const structuredMatch = entry.match(
|
|
2045
2060
|
/<!--\s*learned-stamp:\s*category=([\w-]+);\s*capturedAt=([^;]+?)\s*-->/
|
|
@@ -2158,6 +2173,10 @@ function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLear
|
|
|
2158
2173
|
const category = parseLearnedCategory(attributes["category"]) ?? "fact";
|
|
2159
2174
|
const capturedAt = attributes["capturedAt"] ?? "";
|
|
2160
2175
|
const skill = attributes["skill"];
|
|
2176
|
+
const trials = directiveTrials({
|
|
2177
|
+
applied: Number(attributes["applied"]),
|
|
2178
|
+
wins: Number(attributes["wins"])
|
|
2179
|
+
});
|
|
2161
2180
|
const start = (stamp.index ?? 0) + stamp[0].length;
|
|
2162
2181
|
const end = stamps[index + 1]?.index ?? raw.length;
|
|
2163
2182
|
const parsed = parseEntryBody(raw.slice(start, end));
|
|
@@ -2169,7 +2188,8 @@ function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLear
|
|
|
2169
2188
|
why: parsed.why || WHY_BY_CATEGORY[category],
|
|
2170
2189
|
how: parsed.how,
|
|
2171
2190
|
capturedAt,
|
|
2172
|
-
...skill ? { skill } : {}
|
|
2191
|
+
...skill ? { skill } : {},
|
|
2192
|
+
...trials.applied > 0 ? { applied: trials.applied, wins: trials.wins } : {}
|
|
2173
2193
|
});
|
|
2174
2194
|
}
|
|
2175
2195
|
if (structured.length === 0) {
|
|
@@ -2193,17 +2213,31 @@ function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLear
|
|
|
2193
2213
|
}
|
|
2194
2214
|
function mergeStructuredEntries(existing, fresh) {
|
|
2195
2215
|
const { what, why, how } = decomposeLearnedEntry(fresh.text, fresh.category);
|
|
2216
|
+
const key = directiveKey(fresh.text);
|
|
2217
|
+
const overlapping = existing.filter((entry) => tokenOverlap(entry.key, key) >= 0.55);
|
|
2218
|
+
const proven = overlapping.find(
|
|
2219
|
+
(entry) => entry.category === fresh.category && isProvenDirective(entry)
|
|
2220
|
+
);
|
|
2221
|
+
if (proven) return sortStructuredEntries([...existing]);
|
|
2222
|
+
const ancestor = overlapping.filter((entry) => entry.category === fresh.category).sort((a, b) => directiveTrials(b).applied - directiveTrials(a).applied)[0];
|
|
2223
|
+
const inherited = ancestor ? directiveTrials(ancestor) : { applied: 0, wins: 0 };
|
|
2196
2224
|
const freshEntry = {
|
|
2197
|
-
key
|
|
2225
|
+
key,
|
|
2198
2226
|
category: fresh.category,
|
|
2199
2227
|
what,
|
|
2200
2228
|
why,
|
|
2201
2229
|
how,
|
|
2202
2230
|
capturedAt: fresh.capturedAt,
|
|
2203
|
-
...fresh.skill ? { skill: fresh.skill } : {}
|
|
2231
|
+
...fresh.skill ? { skill: fresh.skill } : {},
|
|
2232
|
+
...inherited.applied > 0 ? { applied: inherited.applied, wins: inherited.wins } : {}
|
|
2204
2233
|
};
|
|
2205
|
-
const merged = existing.filter(
|
|
2234
|
+
const merged = existing.filter(
|
|
2235
|
+
(entry) => entry.category !== fresh.category || tokenOverlap(entry.key, key) < 0.55
|
|
2236
|
+
);
|
|
2206
2237
|
merged.push(freshEntry);
|
|
2238
|
+
return sortStructuredEntries(merged);
|
|
2239
|
+
}
|
|
2240
|
+
function sortStructuredEntries(merged) {
|
|
2207
2241
|
const order = {
|
|
2208
2242
|
warning: 0,
|
|
2209
2243
|
convention: 1,
|
|
@@ -2253,10 +2287,15 @@ function renderLearnedInstructions(role, entries, capturedAt) {
|
|
|
2253
2287
|
sections.push(SECTION_TITLE[category]);
|
|
2254
2288
|
sections.push("");
|
|
2255
2289
|
for (const entry of list) {
|
|
2290
|
+
const trials = directiveTrials(entry);
|
|
2256
2291
|
const attributes = [
|
|
2257
2292
|
`category=${entry.category}`,
|
|
2258
2293
|
`capturedAt=${entry.capturedAt}`,
|
|
2259
|
-
...entry.skill ? [`skill=${entry.skill}`] : []
|
|
2294
|
+
...entry.skill ? [`skill=${entry.skill}`] : [],
|
|
2295
|
+
// Zero counters are omitted so an entry with no track record renders
|
|
2296
|
+
// exactly as it did before this field existed — the parse→render
|
|
2297
|
+
// fixed point holds for every pre-existing buffer on disk.
|
|
2298
|
+
...trials.applied > 0 ? [`applied=${trials.applied}`, `wins=${trials.wins}`] : []
|
|
2260
2299
|
].join("; ");
|
|
2261
2300
|
sections.push(`<!-- learned-stamp: ${attributes} -->`);
|
|
2262
2301
|
sections.push(`- **${entry.what}**`);
|
|
@@ -2280,26 +2319,155 @@ function enforceLearnedBudget(entries, capturedAt, maxBytes, role = "agent") {
|
|
|
2280
2319
|
const kept = [...entries];
|
|
2281
2320
|
const dropped = [];
|
|
2282
2321
|
const size = (list) => Buffer.byteLength(renderLearnedInstructions(role, list, capturedAt), "utf8");
|
|
2322
|
+
const UTILITY_EPSILON = 1e-9;
|
|
2283
2323
|
while (kept.length > 1 && size(kept) > maxBytes) {
|
|
2284
2324
|
let victimIndex = 0;
|
|
2285
2325
|
for (let i = 1; i < kept.length; i++) {
|
|
2286
2326
|
const a = kept[i];
|
|
2287
2327
|
const b = kept[victimIndex];
|
|
2288
2328
|
const byPriority = DROP_PRIORITY[a.category] - DROP_PRIORITY[b.category];
|
|
2289
|
-
if (byPriority
|
|
2329
|
+
if (byPriority !== 0) {
|
|
2330
|
+
if (byPriority < 0) victimIndex = i;
|
|
2331
|
+
continue;
|
|
2332
|
+
}
|
|
2333
|
+
const byUtility = directiveUtility(a) - directiveUtility(b);
|
|
2334
|
+
if (Math.abs(byUtility) > UTILITY_EPSILON) {
|
|
2335
|
+
if (byUtility < 0) victimIndex = i;
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
if (a.capturedAt < b.capturedAt) victimIndex = i;
|
|
2290
2339
|
}
|
|
2291
2340
|
dropped.push(...kept.splice(victimIndex, 1));
|
|
2292
2341
|
}
|
|
2293
2342
|
return { kept, dropped };
|
|
2294
2343
|
}
|
|
2295
2344
|
|
|
2345
|
+
// src/coordination/agents/project-agent-quarantine.ts
|
|
2346
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2347
|
+
import * as path5 from "node:path";
|
|
2348
|
+
var QUARANTINE_MAX_BYTES = 64 * 1024;
|
|
2349
|
+
var RETIRED_MATCH_THRESHOLD = 0.55;
|
|
2350
|
+
function quarantinePath(role, projectRoot) {
|
|
2351
|
+
return path5.join(roleDir(role, projectRoot), "quarantine.md");
|
|
2352
|
+
}
|
|
2353
|
+
function readTextOrEmpty(filePath) {
|
|
2354
|
+
try {
|
|
2355
|
+
return readFileSync3(filePath, "utf8");
|
|
2356
|
+
} catch {
|
|
2357
|
+
return "";
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
function appendQuarantine(role, retired, at, projectRoot) {
|
|
2361
|
+
if (retired.length === 0) return;
|
|
2362
|
+
const filePath = quarantinePath(role, projectRoot);
|
|
2363
|
+
const existing = readTextOrEmpty(filePath);
|
|
2364
|
+
const header = existing ? "" : `# Retired directives for \`${role}\`
|
|
2365
|
+
|
|
2366
|
+
> Directives that were exercised repeatedly and kept correlating with
|
|
2367
|
+
> failure. They are no longer injected anywhere. Kept for audit \u2014 a directive
|
|
2368
|
+
> can be right about a project that has since changed.
|
|
2369
|
+
|
|
2370
|
+
`;
|
|
2371
|
+
const block = retired.map((entry) => {
|
|
2372
|
+
const { applied, wins, losses } = directiveTrials(entry);
|
|
2373
|
+
const attributes = [
|
|
2374
|
+
`at=${at}`,
|
|
2375
|
+
`category=${entry.category}`,
|
|
2376
|
+
...entry.skill ? [`skill=${entry.skill}`] : [],
|
|
2377
|
+
`applied=${applied}`,
|
|
2378
|
+
`wins=${wins}`
|
|
2379
|
+
].join("; ");
|
|
2380
|
+
return [
|
|
2381
|
+
`<!-- quarantined: ${attributes} -->`,
|
|
2382
|
+
`- **${entry.what}**`,
|
|
2383
|
+
` - *Record:* ${wins} succeeded / ${losses} failed of ${applied} applied`,
|
|
2384
|
+
""
|
|
2385
|
+
].join("\n");
|
|
2386
|
+
}).join("\n");
|
|
2387
|
+
let next = `${existing}${header}${block}`;
|
|
2388
|
+
if (Buffer.byteLength(next, "utf8") > QUARANTINE_MAX_BYTES) {
|
|
2389
|
+
const marker = "<!-- quarantined:";
|
|
2390
|
+
const overflow = Buffer.byteLength(next, "utf8") - QUARANTINE_MAX_BYTES;
|
|
2391
|
+
const cut = next.indexOf(marker, overflow);
|
|
2392
|
+
next = cut === -1 ? block : next.slice(cut);
|
|
2393
|
+
}
|
|
2394
|
+
writeTextAtomically(filePath, next);
|
|
2395
|
+
}
|
|
2396
|
+
function readQuarantinedDirectives(role, projectRoot) {
|
|
2397
|
+
const raw = readTextOrEmpty(quarantinePath(role, projectRoot));
|
|
2398
|
+
if (!raw) return [];
|
|
2399
|
+
const out = [];
|
|
2400
|
+
const stamps = [...raw.matchAll(/<!--\s*quarantined:\s*([^>]*?)\s*-->/g)];
|
|
2401
|
+
for (const [index, stamp] of stamps.entries()) {
|
|
2402
|
+
const attributes = {};
|
|
2403
|
+
for (const part of (stamp[1] ?? "").split(";")) {
|
|
2404
|
+
const eq = part.indexOf("=");
|
|
2405
|
+
if (eq === -1) continue;
|
|
2406
|
+
attributes[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
2407
|
+
}
|
|
2408
|
+
const start = (stamp.index ?? 0) + stamp[0].length;
|
|
2409
|
+
const end = stamps[index + 1]?.index ?? raw.length;
|
|
2410
|
+
const what = /^-\s+\*\*([\s\S]+?)\*\*\s*$/m.exec(raw.slice(start, end))?.[1]?.replace(/\s+/g, " ").trim();
|
|
2411
|
+
if (!what) continue;
|
|
2412
|
+
const skill = attributes["skill"];
|
|
2413
|
+
out.push({ what, ...skill ? { skill } : {} });
|
|
2414
|
+
}
|
|
2415
|
+
return out;
|
|
2416
|
+
}
|
|
2417
|
+
function bareClaim(line) {
|
|
2418
|
+
return line.replace(/^\s*[-*+]\s+/, "").replace(/^\s*\d+[.)]\s+/, "").replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
2419
|
+
}
|
|
2420
|
+
function indentOf(line) {
|
|
2421
|
+
return line.length - line.trimStart().length;
|
|
2422
|
+
}
|
|
2423
|
+
function scrubRetiredLines(text, retired) {
|
|
2424
|
+
if (!text.trim() || retired.length === 0) return text;
|
|
2425
|
+
const keys = retired.map((what) => normalizeForComparison(what));
|
|
2426
|
+
const lines = text.split("\n");
|
|
2427
|
+
const kept = [];
|
|
2428
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2429
|
+
const line = lines[i];
|
|
2430
|
+
const claim = bareClaim(line);
|
|
2431
|
+
if (claim.length >= MIN_INSTRUCTIVE_LENGTH && keys.some(
|
|
2432
|
+
(key) => tokenOverlap(key, normalizeForComparison(claim)) >= RETIRED_MATCH_THRESHOLD
|
|
2433
|
+
)) {
|
|
2434
|
+
const base = indentOf(line);
|
|
2435
|
+
while (i + 1 < lines.length) {
|
|
2436
|
+
const next = lines[i + 1];
|
|
2437
|
+
if (!next.trim() || indentOf(next) <= base) break;
|
|
2438
|
+
i++;
|
|
2439
|
+
}
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
kept.push(line);
|
|
2443
|
+
}
|
|
2444
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2445
|
+
}
|
|
2446
|
+
function hasDirectiveContent(text) {
|
|
2447
|
+
return text.split("\n").some((line) => {
|
|
2448
|
+
const trimmed = line.trimStart();
|
|
2449
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(">")) return false;
|
|
2450
|
+
if (/^-{3,}\s*$/.test(trimmed)) return false;
|
|
2451
|
+
if (/^\*[^*].*\*$/.test(trimmed)) return false;
|
|
2452
|
+
return bareClaim(line).length >= MIN_INSTRUCTIVE_LENGTH;
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
function retiredDirectivesToWarnAbout(role, currentDirectives, projectRoot, skill) {
|
|
2456
|
+
const currentKeys = currentDirectives.map((text) => normalizeForComparison(text));
|
|
2457
|
+
return readQuarantinedDirectives(role, projectRoot).filter((entry) => skill ? entry.skill === skill : true).map((entry) => entry.what).filter(
|
|
2458
|
+
(what) => !currentKeys.some(
|
|
2459
|
+
(key) => tokenOverlap(key, normalizeForComparison(what)) >= RETIRED_MATCH_THRESHOLD
|
|
2460
|
+
)
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2296
2464
|
// src/coordination/agents/project-agent-consolidation.ts
|
|
2297
2465
|
function learnedPath(role, projectRoot) {
|
|
2298
|
-
return
|
|
2466
|
+
return path6.join(roleDir(role, projectRoot), "learned.md");
|
|
2299
2467
|
}
|
|
2300
2468
|
function loadProjectAgentLearnedText(role, projectRoot) {
|
|
2301
2469
|
try {
|
|
2302
|
-
return
|
|
2470
|
+
return readFileSync4(learnedPath(role, projectRoot), "utf8").trim();
|
|
2303
2471
|
} catch {
|
|
2304
2472
|
return "";
|
|
2305
2473
|
}
|
|
@@ -2309,28 +2477,27 @@ function readRawLearnedEntries(role, projectRoot) {
|
|
|
2309
2477
|
return parseStructuredLearnedEntriesFromContent(raw, splitLearnedEntries(raw));
|
|
2310
2478
|
}
|
|
2311
2479
|
function consolidationPath(role, projectRoot) {
|
|
2312
|
-
return
|
|
2480
|
+
return path6.join(roleDir(role, projectRoot), "consolidated.md");
|
|
2481
|
+
}
|
|
2482
|
+
function consolidatedDocumentPath(role, projectRoot) {
|
|
2483
|
+
return consolidationPath(assertProjectAgentRole(role), projectRoot);
|
|
2313
2484
|
}
|
|
2314
2485
|
function consolidationMetaPath(role, projectRoot) {
|
|
2315
|
-
return
|
|
2486
|
+
return path6.join(roleDir(role, projectRoot), "consolidation.json");
|
|
2316
2487
|
}
|
|
2317
2488
|
function archivePath(role, at, projectRoot) {
|
|
2318
|
-
return
|
|
2319
|
-
roleDir(role, projectRoot),
|
|
2320
|
-
"archive",
|
|
2321
|
-
`learned-${at.replace(/[:.]/g, "-")}.md`
|
|
2322
|
-
);
|
|
2489
|
+
return path6.join(roleDir(role, projectRoot), "archive", `learned-${at.replace(/[:.]/g, "-")}.md`);
|
|
2323
2490
|
}
|
|
2324
2491
|
function loadProjectAgentConsolidated(role, projectRoot) {
|
|
2325
2492
|
try {
|
|
2326
|
-
return
|
|
2493
|
+
return readFileSync4(consolidationPath(role, projectRoot), "utf8").trim();
|
|
2327
2494
|
} catch {
|
|
2328
2495
|
return "";
|
|
2329
2496
|
}
|
|
2330
2497
|
}
|
|
2331
2498
|
function loadConsolidationMetadata(role, projectRoot) {
|
|
2332
2499
|
try {
|
|
2333
|
-
const raw =
|
|
2500
|
+
const raw = readFileSync4(consolidationMetaPath(role, projectRoot), "utf8");
|
|
2334
2501
|
const parsed = JSON.parse(raw);
|
|
2335
2502
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.consolidatedAt === "string" && typeof parsed.sourceEntryCount === "number") {
|
|
2336
2503
|
return parsed;
|
|
@@ -2401,10 +2568,13 @@ function clearProjectAgentConsolidated(role, projectRoot) {
|
|
|
2401
2568
|
function buildConsolidationInstruction(role, projectRoot) {
|
|
2402
2569
|
const normalizedRole = assertProjectAgentRole(role);
|
|
2403
2570
|
const entries = readRawLearnedEntries(normalizedRole, projectRoot);
|
|
2404
|
-
const rawEntries = entries.map(
|
|
2405
|
-
|
|
2406
|
-
Anchors: ${entry.how.split("\n").join(", ")}`
|
|
2407
|
-
|
|
2571
|
+
const rawEntries = entries.map((entry) => {
|
|
2572
|
+
const lines = [entry.what];
|
|
2573
|
+
if (entry.how) lines.push(` Anchors: ${entry.how.split("\n").join(", ")}`);
|
|
2574
|
+
const { applied, wins } = directiveTrials(entry);
|
|
2575
|
+
if (applied > 0) lines.push(` Track record: applied ${applied}\xD7, ${wins} succeeded`);
|
|
2576
|
+
return lines.join("\n");
|
|
2577
|
+
});
|
|
2408
2578
|
const existingConsolidation = loadProjectAgentConsolidated(normalizedRole, projectRoot);
|
|
2409
2579
|
const rawBody = rawEntries.map((entry, i) => `### Entry ${i + 1}
|
|
2410
2580
|
|
|
@@ -2424,6 +2594,7 @@ ${entry}`).join("\n\n");
|
|
|
2424
2594
|
"6. **Structured format.** Organize the content under clear markdown headings (##) by topic. Use bullet points for individual facts. Group by category (Conventions, Patterns, Warnings, Project Facts) when it helps scannability.",
|
|
2425
2595
|
"7. **Preserve actionable anchors.** Keep exact file paths, command names, package names, and configuration values that make a directive concrete and runnable.",
|
|
2426
2596
|
"8. **No filler.** Do not include meta-commentary about the consolidation process. The document should read as if it were always a single authoritative reference.",
|
|
2597
|
+
"9. **Trust the track record over your own judgement of plausibility.** An entry may carry `Track record: applied N\xD7, M succeeded` \u2014 completed tasks that exercised it, and how many of those succeeded. Keep the ones that keep working, even when they read as obvious. An entry applied several times with few successes has been tested and has failed the test: drop it, or narrow it to the condition under which it actually holds. Entries with no track record are unproven rather than bad \u2014 judge them on merit, but do not let one displace a proven entry.",
|
|
2427
2598
|
"",
|
|
2428
2599
|
rawEntries.length > 0 ? `## Raw learned entries (${rawEntries.length} total)
|
|
2429
2600
|
|
|
@@ -2437,6 +2608,21 @@ ${rawBody}` : "## Raw learned entries\n\n(No raw entries yet \u2014 return an em
|
|
|
2437
2608
|
${existingConsolidation}`
|
|
2438
2609
|
);
|
|
2439
2610
|
}
|
|
2611
|
+
const retired = retiredDirectivesToWarnAbout(
|
|
2612
|
+
normalizedRole,
|
|
2613
|
+
entries.map((entry) => entry.what),
|
|
2614
|
+
projectRoot
|
|
2615
|
+
);
|
|
2616
|
+
if (retired.length > 0) {
|
|
2617
|
+
sections.push(
|
|
2618
|
+
"",
|
|
2619
|
+
`## Retired \u2014 do not reinstate (${retired.length})
|
|
2620
|
+
|
|
2621
|
+
These were exercised repeatedly and kept correlating with failed tasks. If the existing document above still states any of them, drop it.
|
|
2622
|
+
|
|
2623
|
+
${retired.map((what) => `- ${what}`).join("\n")}`
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2440
2626
|
sections.push(
|
|
2441
2627
|
"",
|
|
2442
2628
|
"## Output",
|
|
@@ -2516,7 +2702,7 @@ var BUILT_IN_KNOWLEDGE_MANIFESTS = {
|
|
|
2516
2702
|
};
|
|
2517
2703
|
|
|
2518
2704
|
// src/coordination/agents/project-agent-learning-policy.ts
|
|
2519
|
-
import { readFileSync as
|
|
2705
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2520
2706
|
var DEFAULT_LEARNING_POLICY = {
|
|
2521
2707
|
enabled: true,
|
|
2522
2708
|
lifetimeCaptureCount: 0
|
|
@@ -2524,23 +2710,26 @@ var DEFAULT_LEARNING_POLICY = {
|
|
|
2524
2710
|
function loadProjectAgentLearningPolicy(role, projectRoot) {
|
|
2525
2711
|
try {
|
|
2526
2712
|
const parsed = JSON.parse(
|
|
2527
|
-
|
|
2713
|
+
readFileSync5(learningPolicyPath(role, projectRoot), "utf8")
|
|
2528
2714
|
);
|
|
2529
2715
|
return {
|
|
2530
2716
|
enabled: parsed.enabled !== false,
|
|
2531
2717
|
lifetimeCaptureCount: typeof parsed.lifetimeCaptureCount === "number" && Number.isInteger(parsed.lifetimeCaptureCount) && parsed.lifetimeCaptureCount >= 0 ? parsed.lifetimeCaptureCount : 0,
|
|
2532
2718
|
...typeof parsed.lastCaptureAt === "string" ? { lastCaptureAt: parsed.lastCaptureAt } : {},
|
|
2533
|
-
...parsed.lastCaptureSource === "automatic" || parsed.lastCaptureSource === "manual" || parsed.lastCaptureSource === "taught" ? { lastCaptureSource: parsed.lastCaptureSource } : {}
|
|
2719
|
+
...parsed.lastCaptureSource === "automatic" || parsed.lastCaptureSource === "manual" || parsed.lastCaptureSource === "taught" ? { lastCaptureSource: parsed.lastCaptureSource } : {},
|
|
2720
|
+
...typeof parsed.lastOptimizeAt === "string" ? { lastOptimizeAt: parsed.lastOptimizeAt } : {}
|
|
2534
2721
|
};
|
|
2535
2722
|
} catch {
|
|
2536
2723
|
return { ...DEFAULT_LEARNING_POLICY };
|
|
2537
2724
|
}
|
|
2538
2725
|
}
|
|
2539
2726
|
function updateProjectAgentLearningPolicy(role, patch, projectRoot) {
|
|
2540
|
-
const
|
|
2727
|
+
const defined = Object.fromEntries(
|
|
2728
|
+
Object.entries(patch).filter(([, value]) => value !== void 0)
|
|
2729
|
+
);
|
|
2541
2730
|
const updated = {
|
|
2542
|
-
...
|
|
2543
|
-
...
|
|
2731
|
+
...loadProjectAgentLearningPolicy(role, projectRoot),
|
|
2732
|
+
...defined
|
|
2544
2733
|
};
|
|
2545
2734
|
writeTextAtomically(
|
|
2546
2735
|
learningPolicyPath(role, projectRoot),
|
|
@@ -2549,14 +2738,20 @@ function updateProjectAgentLearningPolicy(role, patch, projectRoot) {
|
|
|
2549
2738
|
);
|
|
2550
2739
|
return updated;
|
|
2551
2740
|
}
|
|
2741
|
+
function recordProjectAgentOptimizePass(role, projectRoot, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2742
|
+
try {
|
|
2743
|
+
updateProjectAgentLearningPolicy(role, { lastOptimizeAt: at }, projectRoot);
|
|
2744
|
+
} catch {
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2552
2747
|
|
|
2553
2748
|
// src/coordination/agents/project-agent-skill-layer.ts
|
|
2554
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
2555
|
-
import * as
|
|
2749
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync3 } from "node:fs";
|
|
2750
|
+
import * as path8 from "node:path";
|
|
2556
2751
|
|
|
2557
2752
|
// src/coordination/agents/project-agent-profile.ts
|
|
2558
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as
|
|
2559
|
-
import * as
|
|
2753
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync6 } from "node:fs";
|
|
2754
|
+
import * as path7 from "node:path";
|
|
2560
2755
|
function slugifyProjectAgentRole(name) {
|
|
2561
2756
|
const slug = name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").slice(0, 96);
|
|
2562
2757
|
return assertProjectAgentRole(slug);
|
|
@@ -2564,7 +2759,7 @@ function slugifyProjectAgentRole(name) {
|
|
|
2564
2759
|
function loadProjectAgentProfile(role, projectRoot) {
|
|
2565
2760
|
try {
|
|
2566
2761
|
const parsed = JSON.parse(
|
|
2567
|
-
|
|
2762
|
+
readFileSync6(projectAgentProfilePath(role, projectRoot), "utf8")
|
|
2568
2763
|
);
|
|
2569
2764
|
const normalizedRole = assertProjectAgentRole(parsed.role ?? role);
|
|
2570
2765
|
const baseRole = assertProjectAgentRole(parsed.baseRole ?? "generic");
|
|
@@ -2635,7 +2830,7 @@ function createProjectAgent(input, projectRoot) {
|
|
|
2635
2830
|
""
|
|
2636
2831
|
].join("\n");
|
|
2637
2832
|
mkdirSync3(dir, { recursive: true });
|
|
2638
|
-
writeTextAtomically(
|
|
2833
|
+
writeTextAtomically(path7.join(dir, "identity.md"), identity);
|
|
2639
2834
|
writeTextAtomically(
|
|
2640
2835
|
projectAgentProfilePath(role, projectRoot),
|
|
2641
2836
|
`${JSON.stringify(profile, null, 2)}
|
|
@@ -2924,17 +3119,17 @@ function assertProjectSkillName(name) {
|
|
|
2924
3119
|
return normalized;
|
|
2925
3120
|
}
|
|
2926
3121
|
function projectSkillsDir(role, projectRoot) {
|
|
2927
|
-
return
|
|
3122
|
+
return path8.join(roleDir(role, projectRoot), "skills");
|
|
2928
3123
|
}
|
|
2929
3124
|
function projectSkillAugmentationPath(role, skill, projectRoot) {
|
|
2930
|
-
return
|
|
3125
|
+
return path8.join(projectSkillsDir(role, projectRoot), `${assertProjectSkillName(skill)}.md`);
|
|
2931
3126
|
}
|
|
2932
3127
|
function projectSkillAffinityPath(role, projectRoot) {
|
|
2933
|
-
return
|
|
3128
|
+
return path8.join(projectSkillsDir(role, projectRoot), "affinity.json");
|
|
2934
3129
|
}
|
|
2935
3130
|
function loadProjectSkillAugmentation(role, skill, projectRoot) {
|
|
2936
3131
|
try {
|
|
2937
|
-
return
|
|
3132
|
+
return readFileSync7(projectSkillAugmentationPath(role, skill, projectRoot), "utf8").trim();
|
|
2938
3133
|
} catch {
|
|
2939
3134
|
return "";
|
|
2940
3135
|
}
|
|
@@ -2943,13 +3138,32 @@ function saveProjectSkillAugmentation(role, skill, content, projectRoot) {
|
|
|
2943
3138
|
const filePath = projectSkillAugmentationPath(role, skill, projectRoot);
|
|
2944
3139
|
const trimmed = content.trim();
|
|
2945
3140
|
if (!trimmed) return filePath;
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
_(truncated at ${SKILL_AUGMENTATION_MAX_BYTES} bytes)_` : trimmed;
|
|
2949
|
-
writeTextAtomically(filePath, `${bounded}
|
|
3141
|
+
writeTextAtomically(filePath, `${boundAugmentation(trimmed)}
|
|
2950
3142
|
`);
|
|
2951
3143
|
return filePath;
|
|
2952
3144
|
}
|
|
3145
|
+
function boundAugmentation(text) {
|
|
3146
|
+
if (Buffer.byteLength(text, "utf8") <= SKILL_AUGMENTATION_MAX_BYTES) return text;
|
|
3147
|
+
const notice = `
|
|
3148
|
+
|
|
3149
|
+
_(truncated at ${SKILL_AUGMENTATION_MAX_BYTES} bytes)_`;
|
|
3150
|
+
const lines = text.split("\n");
|
|
3151
|
+
const title = lines[0] ?? "";
|
|
3152
|
+
let budget = SKILL_AUGMENTATION_MAX_BYTES - Buffer.byteLength(`${title}
|
|
3153
|
+
${notice}`, "utf8") - Buffer.byteLength("\u2026\n", "utf8");
|
|
3154
|
+
const tail = [];
|
|
3155
|
+
for (let i = lines.length - 1; i > 0; i--) {
|
|
3156
|
+
const cost = Buffer.byteLength(`${lines[i]}
|
|
3157
|
+
`, "utf8");
|
|
3158
|
+
if (cost > budget) break;
|
|
3159
|
+
tail.unshift(lines[i]);
|
|
3160
|
+
budget -= cost;
|
|
3161
|
+
}
|
|
3162
|
+
if (tail.length === 0) return `${title.slice(0, SKILL_AUGMENTATION_MAX_BYTES)}${notice}`;
|
|
3163
|
+
return `${title}
|
|
3164
|
+
\u2026
|
|
3165
|
+
${tail.join("\n").trimEnd()}${notice}`;
|
|
3166
|
+
}
|
|
2953
3167
|
function clearProjectSkillAugmentation(role, skill, projectRoot) {
|
|
2954
3168
|
const target = skill ? projectSkillAugmentationPath(role, skill, projectRoot) : projectSkillsDir(role, projectRoot);
|
|
2955
3169
|
try {
|
|
@@ -2964,7 +3178,25 @@ function listProjectSkillAugmentations(role, projectRoot) {
|
|
|
2964
3178
|
return [];
|
|
2965
3179
|
}
|
|
2966
3180
|
}
|
|
2967
|
-
function renderSkillAugmentation(role, skill, directives, updatedAt) {
|
|
3181
|
+
function renderSkillAugmentation(role, skill, directives, updatedAt, existing) {
|
|
3182
|
+
const priorBody = stripAugmentationFooter(existing ?? "");
|
|
3183
|
+
const priorLines = priorBody.split("\n").map((line) => normalizeForComparison(line.replace(/^[-*]\s+/, ""))).filter(Boolean);
|
|
3184
|
+
const fresh = directives.filter(
|
|
3185
|
+
(directive) => !priorLines.some((line) => tokenOverlap(line, normalizeForComparison(directive)) >= 0.55)
|
|
3186
|
+
);
|
|
3187
|
+
if (priorBody) {
|
|
3188
|
+
if (fresh.length === 0) return `${(existing ?? "").trim()}
|
|
3189
|
+
`;
|
|
3190
|
+
return [
|
|
3191
|
+
priorBody,
|
|
3192
|
+
"",
|
|
3193
|
+
...fresh.map((directive) => `- ${directive}`),
|
|
3194
|
+
"",
|
|
3195
|
+
"---",
|
|
3196
|
+
`*Distilled ${updatedAt} \xB7 ${fresh.length} new directive${fresh.length === 1 ? "" : "s"}*`,
|
|
3197
|
+
""
|
|
3198
|
+
].join("\n");
|
|
3199
|
+
}
|
|
2968
3200
|
return [
|
|
2969
3201
|
`# Project practice: \`${skill}\` (role: \`${role}\`)`,
|
|
2970
3202
|
"",
|
|
@@ -2978,7 +3210,10 @@ function renderSkillAugmentation(role, skill, directives, updatedAt) {
|
|
|
2978
3210
|
""
|
|
2979
3211
|
].join("\n");
|
|
2980
3212
|
}
|
|
2981
|
-
function
|
|
3213
|
+
function stripAugmentationFooter(text) {
|
|
3214
|
+
return text.replace(/\n*---\s*\n\*(?:Distilled|Truncated)[^\n]*\*\s*$/i, "").replace(/\n*_\(truncated at \d+ bytes\)_\s*$/i, "").trimEnd();
|
|
3215
|
+
}
|
|
3216
|
+
function buildSkillDistillInstruction(role, skill, directives, existing, retired = []) {
|
|
2982
3217
|
const sections = [
|
|
2983
3218
|
`You are refining the \`${skill}\` skill for the "${role}" agent **in this project**.`,
|
|
2984
3219
|
"",
|
|
@@ -2992,6 +3227,7 @@ function buildSkillDistillInstruction(role, skill, directives, existing) {
|
|
|
2992
3227
|
"4. **Merge, do not append.** Combine overlapping directives into one sharper rule. The output must be shorter than the input.",
|
|
2993
3228
|
"5. **Structure.** Short `##` sections when it helps (e.g. Commands, Conventions, Pitfalls); bullets otherwise.",
|
|
2994
3229
|
"6. **Budget.** Stay under 400 words. Drop the weakest directives rather than compressing everything into noise.",
|
|
3230
|
+
"7. **Weigh the track record.** Some directives carry one, shown as `[applied N\xD7, M ok]` \u2014 the number of completed tasks that exercised the directive and how many of those succeeded. Lead with the ones that keep working. A directive applied several times with few successes has been tested and found wanting: drop it, or state the narrower condition under which it actually holds. A directive with no record is unproven, not bad \u2014 keep it if it is sound, but do not let it displace a proven one.",
|
|
2995
3231
|
"",
|
|
2996
3232
|
`## Directives captured for \`${skill}\` (${directives.length})`,
|
|
2997
3233
|
"",
|
|
@@ -3000,6 +3236,16 @@ function buildSkillDistillInstruction(role, skill, directives, existing) {
|
|
|
3000
3236
|
if (existing?.trim()) {
|
|
3001
3237
|
sections.push("", "## Existing addendum (improve upon it)", "", existing.trim());
|
|
3002
3238
|
}
|
|
3239
|
+
if (retired.length > 0) {
|
|
3240
|
+
sections.push(
|
|
3241
|
+
"",
|
|
3242
|
+
`## Retired \u2014 do not reinstate (${retired.length})`,
|
|
3243
|
+
"",
|
|
3244
|
+
"These were exercised repeatedly and kept correlating with failed tasks. If the existing addendum still states any of them, drop it.",
|
|
3245
|
+
"",
|
|
3246
|
+
...retired.map((what) => `- ${what}`)
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3003
3249
|
sections.push(
|
|
3004
3250
|
"",
|
|
3005
3251
|
"## Output",
|
|
@@ -3025,7 +3271,7 @@ function loadSkillAffinity(role, projectRoot) {
|
|
|
3025
3271
|
const normalizedRole = assertProjectAgentRole(role);
|
|
3026
3272
|
try {
|
|
3027
3273
|
const parsed = JSON.parse(
|
|
3028
|
-
|
|
3274
|
+
readFileSync7(projectSkillAffinityPath(normalizedRole, projectRoot), "utf8")
|
|
3029
3275
|
);
|
|
3030
3276
|
const entries = {};
|
|
3031
3277
|
for (const [skill, entry] of Object.entries(parsed.entries ?? {})) {
|
|
@@ -3074,9 +3320,11 @@ function recordSkillLoad(role, skills, projectRoot) {
|
|
|
3074
3320
|
});
|
|
3075
3321
|
}
|
|
3076
3322
|
function recordSkillOutcome(role, skills, ok, projectRoot) {
|
|
3323
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3077
3324
|
mutateAffinity(role, skills, projectRoot, (entry) => {
|
|
3078
3325
|
if (ok) entry.succeeded += 1;
|
|
3079
3326
|
else entry.failed += 1;
|
|
3327
|
+
entry.lastUsedAt = now;
|
|
3080
3328
|
});
|
|
3081
3329
|
}
|
|
3082
3330
|
function recordSkillLearned(role, skill, projectRoot) {
|
|
@@ -3090,24 +3338,30 @@ function setSkillPinned(role, skill, pinned, projectRoot) {
|
|
|
3090
3338
|
else delete entry.pinned;
|
|
3091
3339
|
});
|
|
3092
3340
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
const
|
|
3098
|
-
|
|
3341
|
+
var SKILL_EVIDENCE_HALF_LIFE_DAYS = 30;
|
|
3342
|
+
function scoreSkillAffinity(entry, now = Date.now()) {
|
|
3343
|
+
const record = entry ?? EMPTY_ENTRY;
|
|
3344
|
+
if (record.pinned) return Number.POSITIVE_INFINITY;
|
|
3345
|
+
const outcomes = record.succeeded + record.failed;
|
|
3346
|
+
const successRate = (record.succeeded + 1) / (outcomes + 2);
|
|
3347
|
+
const ageDays = record.lastUsedAt ? (now - Date.parse(record.lastUsedAt)) / 864e5 : Number.NaN;
|
|
3348
|
+
const recency = Number.isFinite(ageDays) ? 0.5 ** (Math.max(0, ageDays) / SKILL_EVIDENCE_HALF_LIFE_DAYS) : 1;
|
|
3349
|
+
const evidence = (successRate - 0.5) * 4 * recency;
|
|
3350
|
+
const exploration = 1 / Math.sqrt(1 + record.loaded);
|
|
3351
|
+
return Math.log1p(record.learned) * 2 + evidence + exploration;
|
|
3099
3352
|
}
|
|
3100
3353
|
function rankRoleSkills(role, candidates, projectRoot, limit = DEFAULT_EAGER_SKILL_LIMIT) {
|
|
3101
3354
|
const unique = [...new Set(candidates.filter(isProjectSkillName))];
|
|
3102
3355
|
if (unique.length <= limit) return unique;
|
|
3103
3356
|
const affinity = loadSkillAffinity(role, projectRoot);
|
|
3104
3357
|
const augmented = new Set(listProjectSkillAugmentations(role, projectRoot));
|
|
3358
|
+
const now = Date.now();
|
|
3105
3359
|
return unique.map((skill, index) => ({
|
|
3106
3360
|
skill,
|
|
3107
3361
|
index,
|
|
3108
3362
|
// An existing addendum is itself evidence the project developed this
|
|
3109
3363
|
// skill, even when the affinity file was reset or hand-edited.
|
|
3110
|
-
score: scoreSkillAffinity(affinity.entries[skill]) + (augmented.has(skill) ? 1 : 0)
|
|
3364
|
+
score: scoreSkillAffinity(affinity.entries[skill], now) + (augmented.has(skill) ? 1 : 0)
|
|
3111
3365
|
})).sort((a, b) => b.score === a.score ? a.index - b.index : b.score - a.score).slice(0, limit).map((item) => item.skill);
|
|
3112
3366
|
}
|
|
3113
3367
|
function resolveRoleSkillCandidates(role, projectRoot) {
|
|
@@ -3177,6 +3431,13 @@ function isTextBlock(b) {
|
|
|
3177
3431
|
// src/coordination/agents/project-agent-optimizer.ts
|
|
3178
3432
|
var DEFAULT_MAX_TOKENS = 8e3;
|
|
3179
3433
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
3434
|
+
function describeDirective(entry) {
|
|
3435
|
+
const parts = [entry.what];
|
|
3436
|
+
if (entry.how) parts.push(`(anchors: ${entry.how.split("\n").join(", ")})`);
|
|
3437
|
+
const { applied, wins } = directiveTrials(entry);
|
|
3438
|
+
if (applied > 0) parts.push(`[applied ${applied}\xD7, ${wins} ok]`);
|
|
3439
|
+
return parts.join(" ");
|
|
3440
|
+
}
|
|
3180
3441
|
function unwrapWholeDocumentFence(text) {
|
|
3181
3442
|
const wholeDocFence = /^```(?:markdown|md)?[^\n]*\n([\s\S]*?)\n?```\s*$/i;
|
|
3182
3443
|
const inner = wholeDocFence.exec(text.trim())?.[1];
|
|
@@ -3223,13 +3484,14 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
3223
3484
|
for (const entry of entries) {
|
|
3224
3485
|
if (!entry.skill) continue;
|
|
3225
3486
|
const bucket = bySkill.get(entry.skill) ?? [];
|
|
3226
|
-
bucket.push(
|
|
3487
|
+
bucket.push(describeDirective(entry));
|
|
3227
3488
|
bySkill.set(entry.skill, bucket);
|
|
3228
3489
|
}
|
|
3229
3490
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3230
3491
|
const refreshed = [];
|
|
3231
3492
|
for (const [skill, directives] of bySkill) {
|
|
3232
|
-
|
|
3493
|
+
const existing = loadProjectSkillAugmentation(normalizedRole, skill, projectRoot);
|
|
3494
|
+
let body = renderSkillAugmentation(normalizedRole, skill, directives, at, existing);
|
|
3233
3495
|
if (options.llm) {
|
|
3234
3496
|
try {
|
|
3235
3497
|
const synthesized = await complete(
|
|
@@ -3239,7 +3501,8 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
3239
3501
|
normalizedRole,
|
|
3240
3502
|
skill,
|
|
3241
3503
|
directives,
|
|
3242
|
-
|
|
3504
|
+
existing,
|
|
3505
|
+
retiredDirectivesToWarnAbout(normalizedRole, directives, projectRoot, skill)
|
|
3243
3506
|
),
|
|
3244
3507
|
options
|
|
3245
3508
|
);
|
|
@@ -3252,6 +3515,7 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
3252
3515
|
}
|
|
3253
3516
|
const { instruction } = buildConsolidationInstruction(normalizedRole, projectRoot);
|
|
3254
3517
|
if (!options.llm) {
|
|
3518
|
+
recordProjectAgentOptimizePass(normalizedRole, projectRoot, at);
|
|
3255
3519
|
return { ...base, status: "no-llm", skills: refreshed, instruction };
|
|
3256
3520
|
}
|
|
3257
3521
|
let content;
|
|
@@ -3266,6 +3530,7 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
3266
3530
|
};
|
|
3267
3531
|
}
|
|
3268
3532
|
if (!content) {
|
|
3533
|
+
recordProjectAgentOptimizePass(normalizedRole, projectRoot);
|
|
3269
3534
|
return { ...base, status: "empty-synthesis", skills: refreshed, model: options.llm.model };
|
|
3270
3535
|
}
|
|
3271
3536
|
const prune = options.prune ?? true;
|
|
@@ -3284,6 +3549,7 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
3284
3549
|
error: error instanceof Error ? error.message : "failed to persist consolidation"
|
|
3285
3550
|
};
|
|
3286
3551
|
}
|
|
3552
|
+
recordProjectAgentOptimizePass(normalizedRole, projectRoot);
|
|
3287
3553
|
return {
|
|
3288
3554
|
...base,
|
|
3289
3555
|
status: "optimized",
|
|
@@ -3321,17 +3587,16 @@ function resolveAutoOptimizePolicy(overrides) {
|
|
|
3321
3587
|
function evaluateAutoOptimize(role, projectRoot, policy, now = Date.now()) {
|
|
3322
3588
|
if (!policy.enabled) return { eligible: false, reason: "disabled" };
|
|
3323
3589
|
const normalizedRole = assertProjectAgentRole(role);
|
|
3324
|
-
|
|
3590
|
+
const learningPolicy = loadProjectAgentLearningPolicy(normalizedRole, projectRoot);
|
|
3591
|
+
if (!learningPolicy.enabled) {
|
|
3325
3592
|
return { eligible: false, reason: "learning-paused" };
|
|
3326
3593
|
}
|
|
3327
3594
|
const entries = readRawLearnedEntries(normalizedRole, projectRoot);
|
|
3328
3595
|
if (entries.length < policy.minEntries) return { eligible: false, reason: "too-few-entries" };
|
|
3329
3596
|
const meta = loadConsolidationMetadata(normalizedRole, projectRoot);
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
return { eligible: false, reason: "cooling-down" };
|
|
3334
|
-
}
|
|
3597
|
+
const lastPassAt = [meta?.consolidatedAt, learningPolicy.lastOptimizeAt].map((stamp) => stamp ? Date.parse(stamp) : Number.NaN).filter((value) => Number.isFinite(value));
|
|
3598
|
+
if (lastPassAt.length > 0 && now - Math.max(...lastPassAt) < policy.minIntervalMs) {
|
|
3599
|
+
return { eligible: false, reason: "cooling-down" };
|
|
3335
3600
|
}
|
|
3336
3601
|
const bytes = entries.reduce(
|
|
3337
3602
|
(sum, entry) => sum + Buffer.byteLength(`${entry.what}${entry.why}${entry.how}`, "utf8"),
|
|
@@ -3472,26 +3737,130 @@ var LearningOptimizationScheduler = class {
|
|
|
3472
3737
|
}
|
|
3473
3738
|
};
|
|
3474
3739
|
|
|
3740
|
+
// src/coordination/agents/project-agent-directive-outcome.ts
|
|
3741
|
+
import * as path9 from "node:path";
|
|
3742
|
+
var DIRECTIVE_QUARANTINE_MIN_APPLIED = 8;
|
|
3743
|
+
var DIRECTIVE_QUARANTINE_MAX_UTILITY = 0.3;
|
|
3744
|
+
var MIN_ANCHOR_LENGTH = 6;
|
|
3745
|
+
var DISTINCTIVE_TOKEN_LENGTH = 6;
|
|
3746
|
+
var MIN_DISTINCTIVE_TOKENS = 5;
|
|
3747
|
+
var DISTINCTIVE_COVERAGE = 0.6;
|
|
3748
|
+
function learnedPath2(role, projectRoot) {
|
|
3749
|
+
return path9.join(roleDir(role, projectRoot), "learned.md");
|
|
3750
|
+
}
|
|
3751
|
+
function directiveAnchors(entry) {
|
|
3752
|
+
return entry.how.split("\n").map((line) => line.replace(/`/g, "").trim().toLowerCase()).filter((anchor) => anchor.length >= MIN_ANCHOR_LENGTH);
|
|
3753
|
+
}
|
|
3754
|
+
function distinctiveTokens(text) {
|
|
3755
|
+
return [
|
|
3756
|
+
...new Set(
|
|
3757
|
+
text.toLowerCase().replace(/[^\w\s./@-]/g, " ").split(/\s+/).map((token) => token.replace(/^[./@-]+/, "").replace(/[./@-]+$/, "")).filter((token) => token.length >= DISTINCTIVE_TOKEN_LENGTH)
|
|
3758
|
+
)
|
|
3759
|
+
];
|
|
3760
|
+
}
|
|
3761
|
+
function directiveWasApplied(entry, report) {
|
|
3762
|
+
const haystack = report.toLowerCase();
|
|
3763
|
+
if (!haystack) return false;
|
|
3764
|
+
const anchors = directiveAnchors(entry);
|
|
3765
|
+
if (anchors.length > 0) {
|
|
3766
|
+
return anchors.some((anchor) => haystack.includes(anchor));
|
|
3767
|
+
}
|
|
3768
|
+
const tokens = distinctiveTokens(entry.what);
|
|
3769
|
+
if (tokens.length < MIN_DISTINCTIVE_TOKENS) return false;
|
|
3770
|
+
const hits = tokens.filter((token) => haystack.includes(token)).length;
|
|
3771
|
+
return hits >= MIN_DISTINCTIVE_TOKENS && hits / tokens.length >= DISTINCTIVE_COVERAGE;
|
|
3772
|
+
}
|
|
3773
|
+
function recordDirectiveOutcomes(role, report, succeeded, projectRoot) {
|
|
3774
|
+
const normalizedRole = assertProjectAgentRole(role);
|
|
3775
|
+
const empty = { role: normalizedRole, attributed: 0, quarantined: [] };
|
|
3776
|
+
if (!report.trim()) return empty;
|
|
3777
|
+
const entries = readRawLearnedEntries(normalizedRole, projectRoot);
|
|
3778
|
+
if (entries.length === 0) return empty;
|
|
3779
|
+
const updated = [];
|
|
3780
|
+
const retired = [];
|
|
3781
|
+
let attributed = 0;
|
|
3782
|
+
for (const entry of entries) {
|
|
3783
|
+
if (!directiveWasApplied(entry, report)) {
|
|
3784
|
+
updated.push(entry);
|
|
3785
|
+
continue;
|
|
3786
|
+
}
|
|
3787
|
+
attributed++;
|
|
3788
|
+
const trials = directiveTrials(entry);
|
|
3789
|
+
const next = {
|
|
3790
|
+
...entry,
|
|
3791
|
+
applied: trials.applied + 1,
|
|
3792
|
+
wins: trials.wins + (succeeded ? 1 : 0)
|
|
3793
|
+
};
|
|
3794
|
+
if (directiveTrials(next).applied >= DIRECTIVE_QUARANTINE_MIN_APPLIED && directiveUtility(next) < DIRECTIVE_QUARANTINE_MAX_UTILITY) {
|
|
3795
|
+
retired.push(next);
|
|
3796
|
+
continue;
|
|
3797
|
+
}
|
|
3798
|
+
updated.push(next);
|
|
3799
|
+
}
|
|
3800
|
+
if (attributed === 0) return empty;
|
|
3801
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3802
|
+
const newest = updated.map((entry) => entry.capturedAt).filter(Boolean).sort().pop() ?? at;
|
|
3803
|
+
writeTextAtomically(
|
|
3804
|
+
learnedPath2(normalizedRole, projectRoot),
|
|
3805
|
+
renderLearnedInstructions(normalizedRole, updated, newest)
|
|
3806
|
+
);
|
|
3807
|
+
if (retired.length > 0) {
|
|
3808
|
+
appendQuarantine(normalizedRole, retired, at, projectRoot);
|
|
3809
|
+
scrubRetiredFromInjectedDocuments(normalizedRole, retired, projectRoot);
|
|
3810
|
+
}
|
|
3811
|
+
return {
|
|
3812
|
+
role: normalizedRole,
|
|
3813
|
+
attributed,
|
|
3814
|
+
quarantined: retired.map((entry) => entry.what)
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
function scrubRetiredFromInjectedDocuments(role, retired, projectRoot) {
|
|
3818
|
+
const texts = retired.map((entry) => entry.what);
|
|
3819
|
+
for (const skill of new Set(retired.map((entry) => entry.skill).filter(Boolean))) {
|
|
3820
|
+
try {
|
|
3821
|
+
const current = loadProjectSkillAugmentation(role, skill, projectRoot);
|
|
3822
|
+
if (!current) continue;
|
|
3823
|
+
const scrubbed = scrubRetiredLines(current, texts);
|
|
3824
|
+
if (scrubbed === current.trim()) continue;
|
|
3825
|
+
if (!hasDirectiveContent(scrubbed)) {
|
|
3826
|
+
clearProjectSkillAugmentation(role, skill, projectRoot);
|
|
3827
|
+
continue;
|
|
3828
|
+
}
|
|
3829
|
+
writeTextAtomically(projectSkillAugmentationPath(role, skill, projectRoot), `${scrubbed}
|
|
3830
|
+
`);
|
|
3831
|
+
} catch {
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
try {
|
|
3835
|
+
const consolidated = loadProjectAgentConsolidated(role, projectRoot);
|
|
3836
|
+
if (!consolidated) return;
|
|
3837
|
+
const scrubbed = scrubRetiredLines(consolidated, texts);
|
|
3838
|
+
if (scrubbed === consolidated.trim() || !hasDirectiveContent(scrubbed)) return;
|
|
3839
|
+
writeTextAtomically(consolidatedDocumentPath(role, projectRoot), scrubbed);
|
|
3840
|
+
} catch {
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3475
3844
|
// src/coordination/agents/project-agent-files.ts
|
|
3476
3845
|
import {
|
|
3477
3846
|
existsSync as existsSync3,
|
|
3478
3847
|
mkdirSync as mkdirSync4,
|
|
3479
3848
|
readdirSync as readdirSync3,
|
|
3480
|
-
readFileSync as
|
|
3849
|
+
readFileSync as readFileSync8,
|
|
3481
3850
|
rmSync as rmSync4
|
|
3482
3851
|
} from "node:fs";
|
|
3483
|
-
import * as
|
|
3852
|
+
import * as path10 from "node:path";
|
|
3484
3853
|
function updateProjectAgentLearned(role, content, projectRoot, mode = "append") {
|
|
3485
3854
|
const dir = roleDir(role, projectRoot);
|
|
3486
3855
|
mkdirSync4(dir, { recursive: true });
|
|
3487
|
-
const filePath =
|
|
3856
|
+
const filePath = path10.join(dir, "learned.md");
|
|
3488
3857
|
if (mode === "replace") {
|
|
3489
3858
|
writeTextAtomically(filePath, content);
|
|
3490
3859
|
return filePath;
|
|
3491
3860
|
}
|
|
3492
3861
|
const existing = (() => {
|
|
3493
3862
|
try {
|
|
3494
|
-
return
|
|
3863
|
+
return readFileSync8(filePath, "utf8");
|
|
3495
3864
|
} catch {
|
|
3496
3865
|
return "";
|
|
3497
3866
|
}
|
|
@@ -3519,7 +3888,7 @@ function updateProjectAgentLearned(role, content, projectRoot, mode = "append")
|
|
|
3519
3888
|
function updateProjectAgentIdentity(role, content, projectRoot) {
|
|
3520
3889
|
const dir = roleDir(role, projectRoot);
|
|
3521
3890
|
mkdirSync4(dir, { recursive: true });
|
|
3522
|
-
const filePath =
|
|
3891
|
+
const filePath = path10.join(dir, "identity.md");
|
|
3523
3892
|
writeTextAtomically(filePath, content);
|
|
3524
3893
|
return filePath;
|
|
3525
3894
|
}
|
|
@@ -3527,7 +3896,7 @@ function updateProjectAgentConfig(role, config, projectRoot) {
|
|
|
3527
3896
|
const validated = validateProjectAgentConfig(config);
|
|
3528
3897
|
const dir = roleDir(role, projectRoot);
|
|
3529
3898
|
mkdirSync4(dir, { recursive: true });
|
|
3530
|
-
const filePath =
|
|
3899
|
+
const filePath = path10.join(dir, "config.json");
|
|
3531
3900
|
writeTextAtomically(filePath, `${JSON.stringify(validated, null, 2)}
|
|
3532
3901
|
`);
|
|
3533
3902
|
return filePath;
|
|
@@ -3535,7 +3904,7 @@ function updateProjectAgentConfig(role, config, projectRoot) {
|
|
|
3535
3904
|
function updateProjectAgentKnowledge(role, manifest, projectRoot) {
|
|
3536
3905
|
const dir = roleDir(role, projectRoot);
|
|
3537
3906
|
mkdirSync4(dir, { recursive: true });
|
|
3538
|
-
const filePath =
|
|
3907
|
+
const filePath = path10.join(dir, "knowledge.json");
|
|
3539
3908
|
writeTextAtomically(filePath, `${JSON.stringify(manifest, null, 2)}
|
|
3540
3909
|
`);
|
|
3541
3910
|
return filePath;
|
|
@@ -3561,18 +3930,18 @@ function refreshProjectAgentIdentity(role, projectRoot) {
|
|
|
3561
3930
|
const dir = roleDir(role, projectRoot);
|
|
3562
3931
|
mkdirSync4(dir, { recursive: true });
|
|
3563
3932
|
for (const file of ["learned.md", "identity.md", "consolidated.md", "consolidation.json"]) {
|
|
3564
|
-
const fp =
|
|
3933
|
+
const fp = path10.join(dir, file);
|
|
3565
3934
|
try {
|
|
3566
3935
|
rmSync4(fp, { force: true });
|
|
3567
3936
|
} catch {
|
|
3568
3937
|
}
|
|
3569
3938
|
}
|
|
3570
3939
|
writeTextAtomically(
|
|
3571
|
-
|
|
3940
|
+
path10.join(dir, "learned.md"),
|
|
3572
3941
|
renderLearnedInstructions(role, [], (/* @__PURE__ */ new Date()).toISOString())
|
|
3573
3942
|
);
|
|
3574
3943
|
writeTextAtomically(
|
|
3575
|
-
|
|
3944
|
+
path10.join(dir, "identity.md"),
|
|
3576
3945
|
`# Project identity for ${role}
|
|
3577
3946
|
|
|
3578
3947
|
<!-- Describe how this agent should behave in the context of this project. -->
|
|
@@ -3585,7 +3954,7 @@ function listProjectAgentRoles(projectRoot) {
|
|
|
3585
3954
|
try {
|
|
3586
3955
|
return readdirSync3(dir).filter((name) => {
|
|
3587
3956
|
if (!isProjectAgentRoleName(name)) return false;
|
|
3588
|
-
const sub =
|
|
3957
|
+
const sub = path10.join(dir, name);
|
|
3589
3958
|
try {
|
|
3590
3959
|
return [
|
|
3591
3960
|
"learned.md",
|
|
@@ -3596,7 +3965,7 @@ function listProjectAgentRoles(projectRoot) {
|
|
|
3596
3965
|
"profile.json",
|
|
3597
3966
|
"consolidated.md",
|
|
3598
3967
|
"consolidation.json"
|
|
3599
|
-
].some((file) => existsSync3(
|
|
3968
|
+
].some((file) => existsSync3(path10.join(sub, file)));
|
|
3600
3969
|
} catch {
|
|
3601
3970
|
return false;
|
|
3602
3971
|
}
|
|
@@ -3663,25 +4032,25 @@ function createProjectAgentRoster(baseRoster, projectRoot) {
|
|
|
3663
4032
|
});
|
|
3664
4033
|
}
|
|
3665
4034
|
function loadProjectAgentIdentity(role, projectRoot) {
|
|
3666
|
-
const identityPath =
|
|
4035
|
+
const identityPath = path11.join(roleDir(role, projectRoot), "identity.md");
|
|
3667
4036
|
try {
|
|
3668
|
-
return
|
|
4037
|
+
return readFileSync9(identityPath, "utf8").trim();
|
|
3669
4038
|
} catch {
|
|
3670
4039
|
return "";
|
|
3671
4040
|
}
|
|
3672
4041
|
}
|
|
3673
4042
|
function loadProjectAgentLearned(role, projectRoot) {
|
|
3674
|
-
const
|
|
4043
|
+
const learnedPath3 = path11.join(roleDir(role, projectRoot), "learned.md");
|
|
3675
4044
|
try {
|
|
3676
|
-
return
|
|
4045
|
+
return readFileSync9(learnedPath3, "utf8").trim();
|
|
3677
4046
|
} catch {
|
|
3678
4047
|
return "";
|
|
3679
4048
|
}
|
|
3680
4049
|
}
|
|
3681
4050
|
function loadRoleKnowledgeManifest(role, projectRoot) {
|
|
3682
|
-
const projectPath =
|
|
4051
|
+
const projectPath = path11.join(roleDir(role, projectRoot), "knowledge.json");
|
|
3683
4052
|
try {
|
|
3684
|
-
const raw =
|
|
4053
|
+
const raw = readFileSync9(projectPath, "utf8");
|
|
3685
4054
|
return JSON.parse(raw);
|
|
3686
4055
|
} catch {
|
|
3687
4056
|
return BUILT_IN_KNOWLEDGE_MANIFESTS[role];
|
|
@@ -3822,7 +4191,7 @@ ${learnedContent}`);
|
|
|
3822
4191
|
}
|
|
3823
4192
|
}
|
|
3824
4193
|
const effectiveProjectRoot = projectRoot || process.cwd();
|
|
3825
|
-
const learnedFilePath =
|
|
4194
|
+
const learnedFilePath = path11.join(
|
|
3826
4195
|
effectiveProjectRoot,
|
|
3827
4196
|
".wrongstack",
|
|
3828
4197
|
"agents",
|
|
@@ -3849,6 +4218,8 @@ The file below stores **learning data for this project's "${role}" agent** \u201
|
|
|
3849
4218
|
- **Self-contained** \u2014 understandable without the surrounding session context.
|
|
3850
4219
|
- **Front-load concrete anchors** \u2014 commands in backticks, package names like \`@wrongstack/core\`, file paths like \`packages/core/src/.../foo.ts\`. The structured-list renderer extracts these as the "how" for the entry.
|
|
3851
4220
|
|
|
4221
|
+
**Your directives are scored against real outcomes.** After every task the runtime checks which stored directives were actually exercised \u2014 it matches their anchors against the report \u2014 and folds that task's success or failure into each one's record. A directive that keeps correlating with success outlives newer arrivals and survives rewording; one that has been exercised repeatedly and kept correlating with failure is retired and stops being injected. Two consequences for how you write them: anchors are what make a directive *measurable*, not just runnable, so an anchorless directive can never earn a record; and a directive you are unsure about costs nothing to write, because the loop will find out.
|
|
4222
|
+
|
|
3852
4223
|
**Tag the skill you are developing.** When a directive refines one of your skills, mark it: \`## LEARNED [skill: testing]\`. Tagged directives are distilled into that skill's project addendum, so the lesson arrives as part of the skill itself on every future run instead of as a loose fact. Untagged directives are routed automatically when the wording makes the target obvious, and stay role-level otherwise.
|
|
3853
4224
|
|
|
3854
4225
|
**Bad** (session log \u2014 rejected at capture time):
|
|
@@ -3914,7 +4285,7 @@ ${additions}
|
|
|
3914
4285
|
${contextEnd}`;
|
|
3915
4286
|
}
|
|
3916
4287
|
function resolveCaptureKey(role, projectRoot) {
|
|
3917
|
-
return `${
|
|
4288
|
+
return `${path11.resolve(projectRoot ?? process.cwd())}\0${assertProjectAgentRole(role)}`;
|
|
3918
4289
|
}
|
|
3919
4290
|
function canCaptureNewLearned(role, _existingSize, isManual, projectRoot) {
|
|
3920
4291
|
if (isManual) return void 0;
|
|
@@ -3932,13 +4303,13 @@ function canCaptureNewLearned(role, _existingSize, isManual, projectRoot) {
|
|
|
3932
4303
|
}
|
|
3933
4304
|
function getProjectAgentLearnStats(role, projectRoot) {
|
|
3934
4305
|
const dir = roleDir(role, projectRoot);
|
|
3935
|
-
const learnedPath_ =
|
|
3936
|
-
const identityPath_ =
|
|
3937
|
-
const configPath_ =
|
|
3938
|
-
const knowledgePath_ =
|
|
4306
|
+
const learnedPath_ = path11.join(dir, "learned.md");
|
|
4307
|
+
const identityPath_ = path11.join(dir, "identity.md");
|
|
4308
|
+
const configPath_ = path11.join(dir, "config.json");
|
|
4309
|
+
const knowledgePath_ = path11.join(dir, "knowledge.json");
|
|
3939
4310
|
let learnedText = "";
|
|
3940
4311
|
try {
|
|
3941
|
-
learnedText =
|
|
4312
|
+
learnedText = readFileSync9(learnedPath_, "utf8");
|
|
3942
4313
|
} catch {
|
|
3943
4314
|
}
|
|
3944
4315
|
const entries = parseStructuredLearnedEntries(role, projectRoot);
|
|
@@ -3952,12 +4323,18 @@ function getProjectAgentLearnStats(role, projectRoot) {
|
|
|
3952
4323
|
const lastTs = runtimeLastTs ?? (Number.isFinite(persistedLastTs) ? persistedLastTs : null);
|
|
3953
4324
|
const cooldownRemaining = lastTs ? Math.max(0, CAPTURE_COOLDOWN_MS - (Date.now() - lastTs)) : 0;
|
|
3954
4325
|
const freq = window.count;
|
|
4326
|
+
const trials = entries.map((entry) => directiveTrials(entry));
|
|
4327
|
+
const totalApplied = trials.reduce((sum, trial) => sum + trial.applied, 0);
|
|
4328
|
+
const totalWins = trials.reduce((sum, trial) => sum + trial.wins, 0);
|
|
3955
4329
|
return {
|
|
3956
4330
|
role,
|
|
3957
4331
|
exists,
|
|
3958
4332
|
entryCount: entries.length,
|
|
3959
4333
|
skills: skillAugmentations,
|
|
3960
4334
|
skilledEntryCount: entries.filter((entry) => entry.skill).length,
|
|
4335
|
+
appliedEntryCount: trials.filter((trial) => trial.applied > 0).length,
|
|
4336
|
+
deadEntryCount: trials.filter((trial) => trial.applied === 0).length,
|
|
4337
|
+
directiveHitRate: totalApplied > 0 ? totalWins / totalApplied : null,
|
|
3961
4338
|
totalBytes: Buffer.byteLength(learnedText, "utf8"),
|
|
3962
4339
|
lastCapture: lastTs ? new Date(lastTs).toISOString() : null,
|
|
3963
4340
|
lastCaptureTimestamp: lastTs,
|
|
@@ -4130,7 +4507,7 @@ function captureLearnedFromAgentOutputDetailed(output, role, projectRoot, isManu
|
|
|
4130
4507
|
normalizedRole
|
|
4131
4508
|
);
|
|
4132
4509
|
const newContent = renderLearnedInstructions(normalizedRole, budget.kept, nowIso);
|
|
4133
|
-
writeTextAtomically(
|
|
4510
|
+
writeTextAtomically(path11.join(roleDir(normalizedRole, projectRoot), "learned.md"), newContent);
|
|
4134
4511
|
recordCaptureAttempt(key, now.getTime());
|
|
4135
4512
|
for (const skill of new Set(routedSkills)) {
|
|
4136
4513
|
try {
|
|
@@ -4138,16 +4515,14 @@ function captureLearnedFromAgentOutputDetailed(output, role, projectRoot, isManu
|
|
|
4138
4515
|
} catch {
|
|
4139
4516
|
}
|
|
4140
4517
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
`${JSON.stringify(nextPolicy, null, 2)}
|
|
4150
|
-
`
|
|
4518
|
+
updateProjectAgentLearningPolicy(
|
|
4519
|
+
normalizedRole,
|
|
4520
|
+
{
|
|
4521
|
+
lifetimeCaptureCount: policy.lifetimeCaptureCount + captured,
|
|
4522
|
+
lastCaptureAt: now.toISOString(),
|
|
4523
|
+
lastCaptureSource: isManual ? "manual" : "automatic"
|
|
4524
|
+
},
|
|
4525
|
+
projectRoot
|
|
4151
4526
|
);
|
|
4152
4527
|
return {
|
|
4153
4528
|
role: normalizedRole,
|
|
@@ -4243,38 +4618,38 @@ function loadTechVersionPolicy() {
|
|
|
4243
4618
|
return techVersionPolicyBody;
|
|
4244
4619
|
}
|
|
4245
4620
|
function policyCandidateDirs() {
|
|
4246
|
-
const here =
|
|
4621
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
4247
4622
|
const profileInstructions = resolveWstackPaths({ projectRoot: process.cwd() }).globalInstructions;
|
|
4248
4623
|
return [
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4624
|
+
path12.resolve(here, "../../../../instructions/agents"),
|
|
4625
|
+
path12.resolve(here, "../../../instructions/agents"),
|
|
4626
|
+
path12.resolve(here, "../../instructions/agents"),
|
|
4627
|
+
path12.resolve(here, "../instructions/agents"),
|
|
4628
|
+
path12.resolve(here, "instructions/agents"),
|
|
4629
|
+
path12.join(profileInstructions, "agents"),
|
|
4255
4630
|
profileInstructions
|
|
4256
4631
|
].filter((dir, index, all) => all.indexOf(dir) === index);
|
|
4257
4632
|
}
|
|
4258
4633
|
function policyBody() {
|
|
4259
|
-
const here =
|
|
4634
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
4260
4635
|
const roots = [
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4636
|
+
path12.resolve(here, "_policy/tech-version.md"),
|
|
4637
|
+
path12.resolve(here, "../_policy/tech-version.md"),
|
|
4638
|
+
path12.resolve(here, "../../../instructions/agents/_policy/tech-version.md"),
|
|
4639
|
+
path12.resolve(here, "../../instructions/agents/_policy/tech-version.md"),
|
|
4640
|
+
path12.resolve(here, "../instructions/agents/_policy/tech-version.md"),
|
|
4641
|
+
path12.resolve(here, "../../../../instructions/agents/_policy/tech-version.md"),
|
|
4642
|
+
path12.resolve(here, "instructions/agents/_policy/tech-version.md")
|
|
4268
4643
|
];
|
|
4269
4644
|
for (const file of roots) {
|
|
4270
4645
|
try {
|
|
4271
|
-
return
|
|
4646
|
+
return readFileSync10(file, "utf8").trim();
|
|
4272
4647
|
} catch {
|
|
4273
4648
|
}
|
|
4274
4649
|
}
|
|
4275
4650
|
for (const dir of policyCandidateDirs()) {
|
|
4276
4651
|
try {
|
|
4277
|
-
return
|
|
4652
|
+
return readFileSync10(path12.join(dir, "_policy", "tech-version.md"), "utf8").trim();
|
|
4278
4653
|
} catch {
|
|
4279
4654
|
}
|
|
4280
4655
|
}
|
|
@@ -4293,7 +4668,7 @@ function agentPrompt(id) {
|
|
|
4293
4668
|
let resolved = "";
|
|
4294
4669
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
4295
4670
|
try {
|
|
4296
|
-
resolved =
|
|
4671
|
+
resolved = readFileSync10(path12.join(dir, fileName), "utf8").trim();
|
|
4297
4672
|
break;
|
|
4298
4673
|
} catch {
|
|
4299
4674
|
}
|
|
@@ -4319,16 +4694,16 @@ function agentPromptDirCandidates(envDir) {
|
|
|
4319
4694
|
const candKey = `${envDir}\0${profileInstructions}`;
|
|
4320
4695
|
const cached = candidateCache.get(candKey);
|
|
4321
4696
|
if (cached !== void 0) return cached;
|
|
4322
|
-
const here =
|
|
4697
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
4323
4698
|
const explicitDir = envDir || void 0;
|
|
4324
4699
|
const candidates = [
|
|
4325
|
-
...explicitDir ? [
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4700
|
+
...explicitDir ? [path12.resolve(explicitDir)] : [],
|
|
4701
|
+
path12.join(profileInstructions, "agents"),
|
|
4702
|
+
path12.resolve(here, "../../../../instructions/agents"),
|
|
4703
|
+
path12.resolve(here, "../../../instructions/agents"),
|
|
4704
|
+
path12.resolve(here, "../../instructions/agents"),
|
|
4705
|
+
path12.resolve(here, "../instructions/agents"),
|
|
4706
|
+
path12.resolve(here, "instructions/agents")
|
|
4332
4707
|
];
|
|
4333
4708
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
4334
4709
|
candidateCache.set(candKey, ordered);
|
|
@@ -4385,8 +4760,16 @@ var TOOLS = {
|
|
|
4385
4760
|
"test",
|
|
4386
4761
|
"mailbox"
|
|
4387
4762
|
],
|
|
4388
|
-
/**
|
|
4389
|
-
|
|
4763
|
+
/**
|
|
4764
|
+
* Version control.
|
|
4765
|
+
*
|
|
4766
|
+
* `mailbox` is in every preset on purpose: a subagent that hits a wall must
|
|
4767
|
+
* be able to say so. This was the one preset without it, which left the `git`
|
|
4768
|
+
* and `release` roles able to fail but not to ask — the two roles whose work
|
|
4769
|
+
* most often needs a decision from the leader (force-push, tag collision,
|
|
4770
|
+
* dirty tree) and least often has a safe default.
|
|
4771
|
+
*/
|
|
4772
|
+
vcs: ["read", "grep", "glob", "git", "diff", "mailbox"],
|
|
4390
4773
|
/** Dependency management + CVE audit. */
|
|
4391
4774
|
deps: ["read", "grep", "glob", "install", "outdated", "audit", "json", "mailbox"],
|
|
4392
4775
|
/** Documentation authoring. */
|
|
@@ -7817,26 +8200,26 @@ var BrainMonitor = class {
|
|
|
7817
8200
|
trackFileChurn(toolName, ok, input) {
|
|
7818
8201
|
if (!this.signals.fileChurn) return;
|
|
7819
8202
|
if (!ok || !this.fileEditTools.has(toolName.toLowerCase())) return;
|
|
7820
|
-
const
|
|
7821
|
-
if (!
|
|
8203
|
+
const path40 = editedPath(input);
|
|
8204
|
+
if (!path40) return;
|
|
7822
8205
|
const now = Date.now();
|
|
7823
|
-
const stamps = (this.editTimestamps.get(
|
|
8206
|
+
const stamps = (this.editTimestamps.get(path40) ?? []).filter(
|
|
7824
8207
|
(t) => now - t <= this.fileChurnWindowMs
|
|
7825
8208
|
);
|
|
7826
8209
|
stamps.push(now);
|
|
7827
8210
|
if (stamps.length >= this.fileChurnThreshold) {
|
|
7828
|
-
this.editTimestamps.delete(
|
|
8211
|
+
this.editTimestamps.delete(path40);
|
|
7829
8212
|
void this.engage("file_churn", {
|
|
7830
|
-
question: `The file "${
|
|
8213
|
+
question: `The file "${path40}" has been edited ${stamps.length} times within ${Math.round(this.fileChurnWindowMs / 6e4)} minutes \u2014 the agent may be oscillating (edit/revert loop) instead of converging. Should it be steered?`,
|
|
7831
8214
|
context: [
|
|
7832
|
-
`File: ${
|
|
8215
|
+
`File: ${path40}`,
|
|
7833
8216
|
`Edits in window: ${stamps.length}`,
|
|
7834
8217
|
`Window: ${Math.round(this.fileChurnWindowMs / 1e3)}s`
|
|
7835
8218
|
].join("\n")
|
|
7836
8219
|
});
|
|
7837
8220
|
return;
|
|
7838
8221
|
}
|
|
7839
|
-
this.editTimestamps.set(
|
|
8222
|
+
this.editTimestamps.set(path40, stamps);
|
|
7840
8223
|
}
|
|
7841
8224
|
async engage(kind, input) {
|
|
7842
8225
|
const last = this.lastEngagedAt.get(kind) ?? 0;
|
|
@@ -9416,7 +9799,7 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
9416
9799
|
|
|
9417
9800
|
// src/coordination/commit-safety.ts
|
|
9418
9801
|
import { spawn } from "node:child_process";
|
|
9419
|
-
import * as
|
|
9802
|
+
import * as path14 from "node:path";
|
|
9420
9803
|
|
|
9421
9804
|
// src/utils/child-env.ts
|
|
9422
9805
|
var ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9556,7 +9939,7 @@ function buildChildEnv(optsOrSessionId) {
|
|
|
9556
9939
|
|
|
9557
9940
|
// src/coordination/file-author-tracker.ts
|
|
9558
9941
|
import * as fs2 from "node:fs/promises";
|
|
9559
|
-
import * as
|
|
9942
|
+
import * as path13 from "node:path";
|
|
9560
9943
|
|
|
9561
9944
|
// src/utils/atomic-write.ts
|
|
9562
9945
|
import {
|
|
@@ -9579,7 +9962,7 @@ var withFileLock = primitives.withFileLock;
|
|
|
9579
9962
|
var DEFAULT_MAX_ENTRIES = 5e3;
|
|
9580
9963
|
var LOG_FILENAME = "file-authors.json";
|
|
9581
9964
|
function logPath(storageDir) {
|
|
9582
|
-
return
|
|
9965
|
+
return path13.join(storageDir, LOG_FILENAME);
|
|
9583
9966
|
}
|
|
9584
9967
|
async function loadLog(storageDir, projectRoot) {
|
|
9585
9968
|
try {
|
|
@@ -9647,7 +10030,7 @@ async function assessCommitSafety(opts) {
|
|
|
9647
10030
|
const foreignFiles = [];
|
|
9648
10031
|
const unverifiedFiles = [];
|
|
9649
10032
|
for (const rel of dirty) {
|
|
9650
|
-
const abs =
|
|
10033
|
+
const abs = path14.resolve(topLevel, rel);
|
|
9651
10034
|
const entry = authors.get(normPath(abs)) ?? authors.get(normPath(rel));
|
|
9652
10035
|
if (!entry) {
|
|
9653
10036
|
unverifiedFiles.push(rel);
|
|
@@ -9720,8 +10103,8 @@ async function loadAuthorMap(projectRoot, storageDirOverride) {
|
|
|
9720
10103
|
const map = /* @__PURE__ */ new Map();
|
|
9721
10104
|
for (const e of log.entries) {
|
|
9722
10105
|
map.set(normPath(e.filePath), e);
|
|
9723
|
-
if (!
|
|
9724
|
-
map.set(normPath(
|
|
10106
|
+
if (!path14.isAbsolute(e.filePath)) {
|
|
10107
|
+
map.set(normPath(path14.resolve(projectRoot, e.filePath)), e);
|
|
9725
10108
|
}
|
|
9726
10109
|
}
|
|
9727
10110
|
return map;
|
|
@@ -9793,7 +10176,7 @@ function runGit(args, cwd, signal) {
|
|
|
9793
10176
|
// src/coordination/delegate-tool.ts
|
|
9794
10177
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
9795
10178
|
import * as fsp3 from "node:fs/promises";
|
|
9796
|
-
import * as
|
|
10179
|
+
import * as path15 from "node:path";
|
|
9797
10180
|
|
|
9798
10181
|
// src/utils/safe-json.ts
|
|
9799
10182
|
function safeParse(input, maxBytes = 5e6) {
|
|
@@ -9851,6 +10234,14 @@ var SHADOW_AGENT = {
|
|
|
9851
10234
|
};
|
|
9852
10235
|
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
9853
10236
|
var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
|
|
10237
|
+
function withDispatchMetadata(definition) {
|
|
10238
|
+
const summary = definition.capability?.summary?.trim();
|
|
10239
|
+
if (!summary) return definition.config;
|
|
10240
|
+
return {
|
|
10241
|
+
...definition.config,
|
|
10242
|
+
dispatch: { summary, keywords: [...definition.capability.keywords ?? []] }
|
|
10243
|
+
};
|
|
10244
|
+
}
|
|
9854
10245
|
var FLEET_ROSTER = {
|
|
9855
10246
|
"audit-log": AUDIT_LOG_AGENT,
|
|
9856
10247
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
@@ -9860,7 +10251,7 @@ var FLEET_ROSTER = {
|
|
|
9860
10251
|
generic: GENERIC_AGENT,
|
|
9861
10252
|
"shadow-agent": SHADOW_AGENT,
|
|
9862
10253
|
...Object.fromEntries(
|
|
9863
|
-
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d
|
|
10254
|
+
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
|
|
9864
10255
|
)
|
|
9865
10256
|
};
|
|
9866
10257
|
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -10456,13 +10847,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
10456
10847
|
if (!opts.sessionsRoot) return void 0;
|
|
10457
10848
|
const candidates = [];
|
|
10458
10849
|
if (opts.directorRunId) {
|
|
10459
|
-
candidates.push(
|
|
10850
|
+
candidates.push(path15.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
10460
10851
|
} else {
|
|
10461
10852
|
try {
|
|
10462
10853
|
const entries = await fsp3.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
10463
10854
|
for (const entry of entries) {
|
|
10464
10855
|
if (entry.isDirectory()) {
|
|
10465
|
-
candidates.push(
|
|
10856
|
+
candidates.push(path15.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
10466
10857
|
}
|
|
10467
10858
|
}
|
|
10468
10859
|
} catch {
|
|
@@ -11011,7 +11402,7 @@ var DirectorStateCheckpoint = class {
|
|
|
11011
11402
|
|
|
11012
11403
|
// src/coordination/checkpoint-wiring.ts
|
|
11013
11404
|
import * as fsp5 from "node:fs/promises";
|
|
11014
|
-
import * as
|
|
11405
|
+
import * as path16 from "node:path";
|
|
11015
11406
|
async function appendSessionEvent(host, event) {
|
|
11016
11407
|
if (!host.sessionWriter) return;
|
|
11017
11408
|
try {
|
|
@@ -11059,7 +11450,7 @@ async function writeManifest(host) {
|
|
|
11059
11450
|
}),
|
|
11060
11451
|
usage: host.usage.snapshot()
|
|
11061
11452
|
};
|
|
11062
|
-
await fsp5.mkdir(
|
|
11453
|
+
await fsp5.mkdir(path16.dirname(host.manifestPath), { recursive: true });
|
|
11063
11454
|
await atomicWrite(host.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
11064
11455
|
return host.manifestPath;
|
|
11065
11456
|
}
|
|
@@ -11716,9 +12107,6 @@ import {
|
|
|
11716
12107
|
updateTaskAssignment
|
|
11717
12108
|
} from "@wrongstack/kanban";
|
|
11718
12109
|
|
|
11719
|
-
// src/coordination/director-kanban-queue-helpers.ts
|
|
11720
|
-
import { describeKanbanBoundary } from "@wrongstack/kanban";
|
|
11721
|
-
|
|
11722
12110
|
// src/coordination/director-input-helpers.ts
|
|
11723
12111
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
11724
12112
|
function stringArray2(value) {
|
|
@@ -11739,6 +12127,7 @@ function instantiateRosterConfig2(role, base) {
|
|
|
11739
12127
|
}
|
|
11740
12128
|
|
|
11741
12129
|
// src/coordination/director-kanban-queue-helpers.ts
|
|
12130
|
+
import { describeKanbanBoundary } from "@wrongstack/kanban";
|
|
11742
12131
|
function normalizeKanbanQueueInput(input) {
|
|
11743
12132
|
const raw = input ?? {};
|
|
11744
12133
|
return {
|
|
@@ -11907,8 +12296,8 @@ function ensureKanbanTool(tools) {
|
|
|
11907
12296
|
}
|
|
11908
12297
|
|
|
11909
12298
|
// src/utils/instruction-file.ts
|
|
11910
|
-
import { readFileSync as
|
|
11911
|
-
import * as
|
|
12299
|
+
import { readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
|
|
12300
|
+
import * as path17 from "node:path";
|
|
11912
12301
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11913
12302
|
var textCache = /* @__PURE__ */ new Map();
|
|
11914
12303
|
var rootCandidates;
|
|
@@ -11918,7 +12307,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
11918
12307
|
let resolved = "";
|
|
11919
12308
|
for (const root of instructionRootCandidates()) {
|
|
11920
12309
|
try {
|
|
11921
|
-
resolved =
|
|
12310
|
+
resolved = readFileSync11(path17.join(root, relativePath), "utf8").trimEnd();
|
|
11922
12311
|
break;
|
|
11923
12312
|
} catch {
|
|
11924
12313
|
}
|
|
@@ -11934,11 +12323,11 @@ function renderInstructionTemplate(template, values) {
|
|
|
11934
12323
|
}
|
|
11935
12324
|
function instructionRootCandidates() {
|
|
11936
12325
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
11937
|
-
const here =
|
|
12326
|
+
const here = path17.dirname(fileURLToPath2(import.meta.url));
|
|
11938
12327
|
const candidates = [
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
12328
|
+
path17.resolve(here, "../../instructions"),
|
|
12329
|
+
path17.resolve(here, "../instructions"),
|
|
12330
|
+
path17.resolve(here, "instructions")
|
|
11942
12331
|
];
|
|
11943
12332
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
|
|
11944
12333
|
return rootCandidates;
|
|
@@ -12073,378 +12462,336 @@ function makeLLMClassifier(complete2) {
|
|
|
12073
12462
|
};
|
|
12074
12463
|
}
|
|
12075
12464
|
|
|
12076
|
-
// src/coordination/director-
|
|
12465
|
+
// src/coordination/director-basic-tools.ts
|
|
12077
12466
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
12078
|
-
function
|
|
12467
|
+
function makeAssignTool(director) {
|
|
12468
|
+
const inputSchema = {
|
|
12469
|
+
type: "object",
|
|
12470
|
+
properties: {
|
|
12471
|
+
subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
|
|
12472
|
+
description: {
|
|
12473
|
+
type: "string",
|
|
12474
|
+
minLength: 1,
|
|
12475
|
+
description: "The task in natural language \u2014 what you want this subagent to do."
|
|
12476
|
+
},
|
|
12477
|
+
maxToolCalls: {
|
|
12478
|
+
type: "number",
|
|
12479
|
+
minimum: 1,
|
|
12480
|
+
description: "Optional per-task tool-call budget override."
|
|
12481
|
+
},
|
|
12482
|
+
timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
|
|
12483
|
+
},
|
|
12484
|
+
required: ["subagentId", "description"]
|
|
12485
|
+
};
|
|
12079
12486
|
return {
|
|
12080
|
-
name: "
|
|
12081
|
-
description: "
|
|
12082
|
-
usageHint: "Use after code-changing work. Provide implementerTaskIds when available. Add repairSubagentId to iterate fixes automatically. Verdict only passes when every enabled reviewer/verifier explicitly passes.",
|
|
12487
|
+
name: "assign_task",
|
|
12488
|
+
description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
|
|
12083
12489
|
permission: "auto",
|
|
12084
12490
|
mutating: false,
|
|
12085
12491
|
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
12492
|
+
inputSchema,
|
|
12493
|
+
async execute(input) {
|
|
12494
|
+
const i = input;
|
|
12495
|
+
const task = {
|
|
12496
|
+
id: randomUUID8(),
|
|
12497
|
+
description: i.description,
|
|
12498
|
+
subagentId: i.subagentId,
|
|
12499
|
+
maxToolCalls: i.maxToolCalls,
|
|
12500
|
+
timeoutMs: i.timeoutMs
|
|
12501
|
+
};
|
|
12502
|
+
const taskId = await director.assign(task);
|
|
12503
|
+
return { taskId, subagentId: i.subagentId };
|
|
12504
|
+
}
|
|
12505
|
+
};
|
|
12506
|
+
}
|
|
12507
|
+
function makeAwaitTasksTool(director) {
|
|
12508
|
+
return {
|
|
12509
|
+
name: "await_tasks",
|
|
12510
|
+
description: 'Block until one or more `taskId`s complete, then return their results. The subagents keep running in the background \u2014 only the leader\'s iteration pauses, which is the point: this is the correct tool to retrieve a result you started earlier with `assign_task`. mode:"all" (default) blocks until EVERY named task completes. mode:"any" returns as soon as AT LEAST ONE completes \u2014 use it for independent tasks so you can handle each finisher immediately (reassign work, spawn helpers) instead of idling on the slowest; call again with the returned `pending` ids to pick up the next finisher. The pattern "fan out via assign_task, then await_tasks({mode:\'any\'})" is the async replacement for serial `delegate` calls.',
|
|
12511
|
+
permission: "auto",
|
|
12512
|
+
mutating: false,
|
|
12513
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12086
12514
|
inputSchema: {
|
|
12087
12515
|
type: "object",
|
|
12088
12516
|
properties: {
|
|
12089
|
-
|
|
12090
|
-
type: "string",
|
|
12091
|
-
description: "Original implementation task or acceptance goal being gated."
|
|
12092
|
-
},
|
|
12093
|
-
implementerTaskIds: {
|
|
12517
|
+
taskIds: {
|
|
12094
12518
|
type: "array",
|
|
12095
12519
|
items: { type: "string" },
|
|
12096
|
-
description: "
|
|
12520
|
+
description: "One or more task ids returned by `assign_task`."
|
|
12097
12521
|
},
|
|
12098
|
-
|
|
12522
|
+
mode: {
|
|
12099
12523
|
type: "string",
|
|
12100
|
-
|
|
12524
|
+
enum: ["all", "any"],
|
|
12525
|
+
description: '"all" (default): block until every task completes. "any": return on the first completion with the rest listed as pending.'
|
|
12101
12526
|
},
|
|
12102
|
-
|
|
12527
|
+
timeoutMs: {
|
|
12103
12528
|
type: "number",
|
|
12104
|
-
minimum:
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
}
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12529
|
+
minimum: 1,
|
|
12530
|
+
description: 'mode:"any" only \u2014 return {timedOut:true, completed:[]} if nothing completes within this window instead of blocking.'
|
|
12531
|
+
}
|
|
12532
|
+
},
|
|
12533
|
+
required: ["taskIds"]
|
|
12534
|
+
},
|
|
12535
|
+
async execute(input) {
|
|
12536
|
+
const i = input;
|
|
12537
|
+
if (i.mode === "any") {
|
|
12538
|
+
const r = await director.awaitTasksAny(
|
|
12539
|
+
i.taskIds,
|
|
12540
|
+
i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : void 0
|
|
12541
|
+
);
|
|
12542
|
+
return {
|
|
12543
|
+
mode: "any",
|
|
12544
|
+
completed: r.completed,
|
|
12545
|
+
pending: r.pending,
|
|
12546
|
+
...r.timedOut ? { timedOut: true } : {},
|
|
12547
|
+
...r.pending.length > 0 ? {
|
|
12548
|
+
hint: 'Handle the completed results now. Re-call await_tasks with the pending ids (mode:"any") for the next finisher \u2014 or assign new work to the now-idle subagent first.'
|
|
12549
|
+
} : {}
|
|
12550
|
+
};
|
|
12551
|
+
}
|
|
12552
|
+
const results = await director.awaitTasks(i.taskIds);
|
|
12553
|
+
return { results };
|
|
12554
|
+
}
|
|
12555
|
+
};
|
|
12556
|
+
}
|
|
12557
|
+
function makeAskTool(director) {
|
|
12558
|
+
return {
|
|
12559
|
+
name: "ask_subagent",
|
|
12560
|
+
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
|
|
12561
|
+
permission: "auto",
|
|
12562
|
+
mutating: false,
|
|
12563
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12564
|
+
inputSchema: {
|
|
12565
|
+
type: "object",
|
|
12566
|
+
properties: {
|
|
12567
|
+
subagentId: {
|
|
12568
|
+
type: "string",
|
|
12569
|
+
minLength: 1,
|
|
12570
|
+
description: "Subagent to ask. Must be a previously spawned id."
|
|
12133
12571
|
},
|
|
12572
|
+
question: { type: "string", minLength: 1, description: "The question or instruction." },
|
|
12134
12573
|
timeoutMs: {
|
|
12135
12574
|
type: "number",
|
|
12136
12575
|
minimum: 1,
|
|
12137
|
-
description: "Optional
|
|
12138
|
-
},
|
|
12139
|
-
reviewerWorktree: {
|
|
12140
|
-
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
12141
|
-
description: "Reviewer worktree override. Default off because reviewer is read-only."
|
|
12142
|
-
},
|
|
12143
|
-
verifierWorktree: {
|
|
12144
|
-
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
12145
|
-
description: "Verifier worktree override. Default auto so test artifacts stay isolated when fleet policy wants it."
|
|
12576
|
+
description: "Optional timeout in ms (default 30s)."
|
|
12146
12577
|
}
|
|
12147
|
-
}
|
|
12578
|
+
},
|
|
12579
|
+
required: ["subagentId", "question"]
|
|
12148
12580
|
},
|
|
12149
12581
|
async execute(input) {
|
|
12150
|
-
const i =
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12582
|
+
const i = input;
|
|
12583
|
+
try {
|
|
12584
|
+
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
12585
|
+
const stored = director.largeAnswerStore.storeAnswer(answer);
|
|
12586
|
+
if (stored.inline) {
|
|
12587
|
+
return { ok: true, answer: stored.summary };
|
|
12588
|
+
}
|
|
12154
12589
|
return {
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12590
|
+
ok: true,
|
|
12591
|
+
answer: stored.summary,
|
|
12592
|
+
_answerKey: stored.key,
|
|
12593
|
+
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
12158
12594
|
};
|
|
12595
|
+
} catch (err) {
|
|
12596
|
+
return { ok: false, error: toErrorMessage(err) };
|
|
12159
12597
|
}
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
attempt,
|
|
12178
|
-
implementerResults,
|
|
12179
|
-
repairResults,
|
|
12180
|
-
priorAttempts: attempts
|
|
12181
|
-
}),
|
|
12182
|
-
timeoutMs: i.timeoutMs
|
|
12183
|
-
});
|
|
12184
|
-
gateTaskIds.push(taskId);
|
|
12185
|
-
taskRoleById.set(taskId, "verifier");
|
|
12186
|
-
}
|
|
12187
|
-
if (runReviewer) {
|
|
12188
|
-
const subagentId = await director.spawn(
|
|
12189
|
-
makeQualityGateSubagentConfig("reviewer", roster, i.reviewerWorktree ?? "off")
|
|
12190
|
-
);
|
|
12191
|
-
const taskId = await director.assign({
|
|
12192
|
-
id: randomUUID8(),
|
|
12193
|
-
subagentId,
|
|
12194
|
-
description: buildReviewerTask(i, {
|
|
12195
|
-
attempt,
|
|
12196
|
-
implementerResults,
|
|
12197
|
-
repairResults,
|
|
12198
|
-
priorAttempts: attempts
|
|
12199
|
-
}),
|
|
12200
|
-
timeoutMs: i.timeoutMs
|
|
12201
|
-
});
|
|
12202
|
-
gateTaskIds.push(taskId);
|
|
12203
|
-
taskRoleById.set(taskId, "reviewer");
|
|
12204
|
-
}
|
|
12205
|
-
const gateResults = await director.awaitTasks(gateTaskIds);
|
|
12206
|
-
const reports = gateResults.map((r) => assessRoleResult(taskRoleById.get(r.taskId), r));
|
|
12207
|
-
const assessment = assessQualityGate(reports);
|
|
12208
|
-
attempts.push({ attempt, reports, ...assessment });
|
|
12209
|
-
if (assessment.passed || !i.repairSubagentId || attempt > maxRepairAttempts) {
|
|
12210
|
-
return {
|
|
12211
|
-
verdict: assessment.verdict,
|
|
12212
|
-
passed: assessment.passed,
|
|
12213
|
-
attempts,
|
|
12214
|
-
repairAttemptsUsed: repairResults.length,
|
|
12215
|
-
implementerResults: implementerResults.map(summarizeTaskResult),
|
|
12216
|
-
nextAction: assessment.passed ? "accept" : i.repairSubagentId && attempt > maxRepairAttempts ? "manual_intervention_or_raise_repair_limit" : "inspect_failures"
|
|
12217
|
-
};
|
|
12218
|
-
}
|
|
12219
|
-
const repairTaskId = await director.assign({
|
|
12220
|
-
id: randomUUID8(),
|
|
12221
|
-
subagentId: i.repairSubagentId,
|
|
12222
|
-
description: buildRepairTask(i, attempts[attempts.length - 1], attempt),
|
|
12223
|
-
timeoutMs: i.timeoutMs
|
|
12224
|
-
});
|
|
12225
|
-
const [repairResult] = await director.awaitTasks([repairTaskId]);
|
|
12226
|
-
if (repairResult) repairResults.push(repairResult);
|
|
12227
|
-
if (repairResult?.status !== "success") {
|
|
12228
|
-
return {
|
|
12229
|
-
verdict: "fail",
|
|
12230
|
-
passed: false,
|
|
12231
|
-
attempts,
|
|
12232
|
-
repairAttemptsUsed: repairResults.length,
|
|
12233
|
-
repairResult: repairResult ? summarizeTaskResult(repairResult) : void 0,
|
|
12234
|
-
implementerResults: implementerResults.map(summarizeTaskResult),
|
|
12235
|
-
nextAction: "repair_failed"
|
|
12236
|
-
};
|
|
12598
|
+
}
|
|
12599
|
+
};
|
|
12600
|
+
}
|
|
12601
|
+
function makeAskResultTool(director) {
|
|
12602
|
+
return {
|
|
12603
|
+
name: "ask_result",
|
|
12604
|
+
description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
|
|
12605
|
+
permission: "auto",
|
|
12606
|
+
mutating: false,
|
|
12607
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12608
|
+
inputSchema: {
|
|
12609
|
+
type: "object",
|
|
12610
|
+
properties: {
|
|
12611
|
+
key: {
|
|
12612
|
+
type: "string",
|
|
12613
|
+
minLength: 1,
|
|
12614
|
+
description: "The `_answerKey` returned by `ask_subagent` for a large response."
|
|
12237
12615
|
}
|
|
12616
|
+
},
|
|
12617
|
+
required: ["key"]
|
|
12618
|
+
},
|
|
12619
|
+
async execute(input) {
|
|
12620
|
+
const i = input;
|
|
12621
|
+
const value = director.largeAnswerStore.retrieveAnswer(i.key);
|
|
12622
|
+
if (value === void 0) {
|
|
12623
|
+
return {
|
|
12624
|
+
ok: false,
|
|
12625
|
+
error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.`
|
|
12626
|
+
};
|
|
12238
12627
|
}
|
|
12628
|
+
return { ok: true, value };
|
|
12239
12629
|
}
|
|
12240
12630
|
};
|
|
12241
12631
|
}
|
|
12242
|
-
function
|
|
12243
|
-
const raw = input ?? {};
|
|
12632
|
+
function makeRollUpTool(director) {
|
|
12244
12633
|
return {
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12634
|
+
name: "roll_up",
|
|
12635
|
+
description: "Aggregate completed task results into a single formatted summary.",
|
|
12636
|
+
permission: "auto",
|
|
12637
|
+
mutating: false,
|
|
12638
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12639
|
+
inputSchema: {
|
|
12640
|
+
type: "object",
|
|
12641
|
+
properties: {
|
|
12642
|
+
taskIds: {
|
|
12643
|
+
type: "array",
|
|
12644
|
+
items: { type: "string" },
|
|
12645
|
+
description: "Completed task ids to aggregate."
|
|
12646
|
+
},
|
|
12647
|
+
style: {
|
|
12648
|
+
type: "string",
|
|
12649
|
+
enum: ["markdown", "json"],
|
|
12650
|
+
description: "Output flavor \u2014 markdown (default) or json."
|
|
12651
|
+
}
|
|
12652
|
+
},
|
|
12653
|
+
required: ["taskIds"]
|
|
12654
|
+
},
|
|
12655
|
+
async execute(input) {
|
|
12656
|
+
const i = input;
|
|
12657
|
+
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
12658
|
+
return { summary, count: i.taskIds.length };
|
|
12659
|
+
}
|
|
12258
12660
|
};
|
|
12259
12661
|
}
|
|
12260
|
-
function
|
|
12261
|
-
if (!Number.isFinite(value)) return 0;
|
|
12262
|
-
return Math.max(0, Math.min(5, Math.floor(value)));
|
|
12263
|
-
}
|
|
12264
|
-
function makeQualityGateSubagentConfig(role, roster, worktree) {
|
|
12265
|
-
const base = roster?.[role] ?? getAgentDefinition(role)?.config ?? { name: role, role };
|
|
12662
|
+
function makeTerminateTool(director) {
|
|
12266
12663
|
return {
|
|
12267
|
-
|
|
12268
|
-
|
|
12664
|
+
name: "terminate_subagent",
|
|
12665
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
12666
|
+
permission: "auto",
|
|
12667
|
+
mutating: true,
|
|
12668
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
12669
|
+
inputSchema: {
|
|
12670
|
+
type: "object",
|
|
12671
|
+
properties: { subagentId: { type: "string", description: "Subagent to abort." } },
|
|
12672
|
+
required: ["subagentId"]
|
|
12673
|
+
},
|
|
12674
|
+
async execute(input) {
|
|
12675
|
+
const i = input;
|
|
12676
|
+
await director.terminate(i.subagentId);
|
|
12677
|
+
return { ok: true };
|
|
12678
|
+
}
|
|
12269
12679
|
};
|
|
12270
12680
|
}
|
|
12271
|
-
function
|
|
12272
|
-
return [
|
|
12273
|
-
"Run the independent verification gate for this implementation.",
|
|
12274
|
-
"Return Markdown with `## Verdict` and make the first verdict word exactly `pass`, `fail`, or `blocked`.",
|
|
12275
|
-
"Do not edit code. Run the smallest meaningful command set and include exact failures.",
|
|
12276
|
-
"",
|
|
12277
|
-
`Gate attempt: ${state.attempt}`,
|
|
12278
|
-
input.task ? `Original task:
|
|
12279
|
-
${input.task}` : void 0,
|
|
12280
|
-
input.targets?.length ? `Targets:
|
|
12281
|
-
${input.targets.map((t) => `- ${t}`).join("\n")}` : void 0,
|
|
12282
|
-
input.commands?.length ? `Required or suggested commands:
|
|
12283
|
-
${input.commands.map((c) => `- ${c}`).join("\n")}` : void 0,
|
|
12284
|
-
input.expected ? `Expected behavior:
|
|
12285
|
-
${input.expected}` : void 0,
|
|
12286
|
-
input.evidence ? `Known evidence:
|
|
12287
|
-
${input.evidence}` : void 0,
|
|
12288
|
-
taskResultsBlock("Implementer results", state.implementerResults),
|
|
12289
|
-
taskResultsBlock("Repair results so far", state.repairResults),
|
|
12290
|
-
priorAttemptsBlock(state.priorAttempts)
|
|
12291
|
-
].filter((part) => !!part).join("\n\n");
|
|
12292
|
-
}
|
|
12293
|
-
function buildReviewerTask(input, state) {
|
|
12294
|
-
return [
|
|
12295
|
-
"Run independent code review for this implementation.",
|
|
12296
|
-
"Return Markdown with `## Verdict` and make the first verdict phrase exactly `approve`, `request changes`, or `needs verification`.",
|
|
12297
|
-
"Do not edit code. Treat missing proof, vague tests, and uncertainty as blocking until verifier evidence exists.",
|
|
12298
|
-
"",
|
|
12299
|
-
`Gate attempt: ${state.attempt}`,
|
|
12300
|
-
input.task ? `Original task:
|
|
12301
|
-
${input.task}` : void 0,
|
|
12302
|
-
input.targets?.length ? `Targets:
|
|
12303
|
-
${input.targets.map((t) => `- ${t}`).join("\n")}` : void 0,
|
|
12304
|
-
input.expected ? `Expected behavior:
|
|
12305
|
-
${input.expected}` : void 0,
|
|
12306
|
-
input.evidence ? `Known evidence:
|
|
12307
|
-
${input.evidence}` : void 0,
|
|
12308
|
-
taskResultsBlock("Implementer results", state.implementerResults),
|
|
12309
|
-
taskResultsBlock("Repair results so far", state.repairResults),
|
|
12310
|
-
priorAttemptsBlock(state.priorAttempts)
|
|
12311
|
-
].filter((part) => !!part).join("\n\n");
|
|
12312
|
-
}
|
|
12313
|
-
function buildRepairTask(input, attempt, attemptNumber) {
|
|
12314
|
-
return [
|
|
12315
|
-
`Repair the implementation after quality gate attempt ${attemptNumber} failed.`,
|
|
12316
|
-
"Address every must-fix item. Run relevant checks before returning.",
|
|
12317
|
-
"Do not claim done unless verifier/reviewer feedback is resolved.",
|
|
12318
|
-
"",
|
|
12319
|
-
input.task ? `Original task:
|
|
12320
|
-
${input.task}` : void 0,
|
|
12321
|
-
input.targets?.length ? `Targets:
|
|
12322
|
-
${input.targets.map((t) => `- ${t}`).join("\n")}` : void 0,
|
|
12323
|
-
input.commands?.length ? `Commands expected to pass:
|
|
12324
|
-
${input.commands.map((c) => `- ${c}`).join("\n")}` : void 0,
|
|
12325
|
-
input.expected ? `Expected behavior:
|
|
12326
|
-
${input.expected}` : void 0,
|
|
12327
|
-
attempt.mustFix.length ? `Must fix:
|
|
12328
|
-
${attempt.mustFix.map((f) => `- ${f}`).join("\n")}` : void 0,
|
|
12329
|
-
attempt.uncertaintyFlags.length ? `Uncertainty flags to resolve:
|
|
12330
|
-
${attempt.uncertaintyFlags.map((f) => `- ${f}`).join("\n")}` : void 0,
|
|
12331
|
-
`Reviewer/verifier reports:
|
|
12332
|
-
${attempt.reports.map((r) => `### ${r.role} (${r.verdict})
|
|
12333
|
-
${r.summary}`).join("\n\n")}`
|
|
12334
|
-
].filter((part) => !!part).join("\n\n");
|
|
12335
|
-
}
|
|
12336
|
-
function taskResultsBlock(title, results) {
|
|
12337
|
-
if (results.length === 0) return void 0;
|
|
12338
|
-
return `${title}:
|
|
12339
|
-
${results.map((r) => `### ${r.subagentId}/${r.taskId}
|
|
12340
|
-
${summarizeTaskResult(r).summary}`).join("\n\n")}`;
|
|
12341
|
-
}
|
|
12342
|
-
function priorAttemptsBlock(attempts) {
|
|
12343
|
-
if (attempts.length === 0) return void 0;
|
|
12344
|
-
return `Prior quality gate attempts:
|
|
12345
|
-
${attempts.map(
|
|
12346
|
-
(a) => `### Attempt ${a.attempt}
|
|
12347
|
-
${a.reports.map((r) => `- ${r.role}: ${r.verdict}${r.error ? ` (${r.error})` : ""}`).join("\n")}`
|
|
12348
|
-
).join("\n\n")}`;
|
|
12349
|
-
}
|
|
12350
|
-
function summarizeTaskResult(result) {
|
|
12351
|
-
const text = typeof result.result === "string" ? result.result : result.result !== void 0 ? JSON.stringify(result.result, null, 2) : "";
|
|
12352
|
-
const error = result.error ? `${result.error.kind}: ${result.error.message}` : void 0;
|
|
12681
|
+
function makeTerminateAllTool(director) {
|
|
12353
12682
|
return {
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12683
|
+
name: "terminate_all",
|
|
12684
|
+
description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
|
|
12685
|
+
permission: "auto",
|
|
12686
|
+
mutating: true,
|
|
12687
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
12688
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12689
|
+
async execute() {
|
|
12690
|
+
await director.terminateAll();
|
|
12691
|
+
return {
|
|
12692
|
+
ok: true,
|
|
12693
|
+
message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.`
|
|
12694
|
+
};
|
|
12695
|
+
}
|
|
12359
12696
|
};
|
|
12360
12697
|
}
|
|
12361
|
-
function
|
|
12362
|
-
const resolvedRole = role ?? (result.subagentId.includes("review") ? "reviewer" : "verifier");
|
|
12363
|
-
const summary = summarizeTaskResult(result);
|
|
12364
|
-
const text = summary.summary;
|
|
12365
|
-
const uncertaintyFlags = extractSection(text, "Uncertainty Flags");
|
|
12366
|
-
if (result.status !== "success") {
|
|
12367
|
-
return {
|
|
12368
|
-
role: resolvedRole,
|
|
12369
|
-
subagentId: result.subagentId,
|
|
12370
|
-
taskId: result.taskId,
|
|
12371
|
-
status: result.status,
|
|
12372
|
-
verdict: "fail",
|
|
12373
|
-
summary: text,
|
|
12374
|
-
uncertaintyFlags,
|
|
12375
|
-
error: summary.error ?? result.status
|
|
12376
|
-
};
|
|
12377
|
-
}
|
|
12698
|
+
function makeFleetTool(director) {
|
|
12378
12699
|
return {
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12700
|
+
name: "fleet",
|
|
12701
|
+
description: 'Fleet observation tool. Use `action` to select what you need: "status" \u2014 snapshot of all subagents + coordinator counts + pending tasks; "usage" \u2014 token + cost breakdown per subagent and totals; "health" \u2014 per-subagent budget pressure, last activity, and status; "session" \u2014 read a subagent\'s JSONL transcript (requires subagentId).',
|
|
12702
|
+
usageHint: 'action: "status" (default) | "usage" | "health" | "session".\nFor "session", pass subagentId (required) and optional tail (trailing JSONL lines).',
|
|
12703
|
+
permission: "auto",
|
|
12704
|
+
mutating: false,
|
|
12705
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12706
|
+
inputSchema: {
|
|
12707
|
+
type: "object",
|
|
12708
|
+
properties: {
|
|
12709
|
+
action: {
|
|
12710
|
+
type: "string",
|
|
12711
|
+
enum: ["status", "usage", "health", "session"],
|
|
12712
|
+
description: "Observation to retrieve (default: status)."
|
|
12713
|
+
},
|
|
12714
|
+
subagentId: {
|
|
12715
|
+
type: "string",
|
|
12716
|
+
description: 'Subagent id (required for action: "session").'
|
|
12717
|
+
},
|
|
12718
|
+
tail: {
|
|
12719
|
+
type: "number",
|
|
12720
|
+
description: 'Number of trailing JSONL lines (action: "session" only). Omit for the full transcript.'
|
|
12721
|
+
}
|
|
12722
|
+
}
|
|
12723
|
+
},
|
|
12724
|
+
async execute(input) {
|
|
12725
|
+
const i = input ?? {};
|
|
12726
|
+
const action = i.action ?? "status";
|
|
12727
|
+
switch (action) {
|
|
12728
|
+
case "status": {
|
|
12729
|
+
const base = director.status();
|
|
12730
|
+
const fm = director.fleetManager;
|
|
12731
|
+
const stats = fm?.getFleetStats();
|
|
12732
|
+
const fleetStatus = fm?.getFleetStatus();
|
|
12733
|
+
return {
|
|
12734
|
+
action: "status",
|
|
12735
|
+
subagents: base.subagents,
|
|
12736
|
+
coordinatorStats: stats ? {
|
|
12737
|
+
total: stats.total,
|
|
12738
|
+
running: stats.running,
|
|
12739
|
+
idle: stats.idle,
|
|
12740
|
+
stopped: stats.stopped
|
|
12741
|
+
} : void 0,
|
|
12742
|
+
pending: fleetStatus?.pending ?? [],
|
|
12743
|
+
usage: fm?.snapshot()
|
|
12744
|
+
};
|
|
12745
|
+
}
|
|
12746
|
+
case "usage": {
|
|
12747
|
+
return { action: "usage", ...director.snapshot() };
|
|
12748
|
+
}
|
|
12749
|
+
case "health": {
|
|
12750
|
+
const status = director.status();
|
|
12751
|
+
const snapshot = director.snapshot();
|
|
12752
|
+
const subagents = status.subagents ?? [];
|
|
12753
|
+
const perSubagent = snapshot.perSubagent ?? {};
|
|
12754
|
+
return {
|
|
12755
|
+
action: "health",
|
|
12756
|
+
subagents: subagents.map((s) => {
|
|
12757
|
+
const usage = perSubagent[s.id];
|
|
12758
|
+
return {
|
|
12759
|
+
id: s.id,
|
|
12760
|
+
status: s.status,
|
|
12761
|
+
lastEventAt: usage?.lastEventAt,
|
|
12762
|
+
budgetPressure: {
|
|
12763
|
+
iterations: usage?.iterations,
|
|
12764
|
+
toolCalls: usage?.toolCalls,
|
|
12765
|
+
costUsd: usage?.cost
|
|
12766
|
+
}
|
|
12767
|
+
};
|
|
12768
|
+
})
|
|
12769
|
+
};
|
|
12770
|
+
}
|
|
12771
|
+
case "session": {
|
|
12772
|
+
const subagentId = i.subagentId;
|
|
12773
|
+
if (!subagentId) {
|
|
12774
|
+
return {
|
|
12775
|
+
action: "session",
|
|
12776
|
+
error: 'fleet: subagentId is required for action: "session"'
|
|
12777
|
+
};
|
|
12778
|
+
}
|
|
12779
|
+
const result = await director.readSession(subagentId, i.tail);
|
|
12780
|
+
if (!result) {
|
|
12781
|
+
return {
|
|
12782
|
+
action: "session",
|
|
12783
|
+
error: `fleet: transcript unavailable for "${subagentId}". Is sessionsRoot configured?`
|
|
12784
|
+
};
|
|
12785
|
+
}
|
|
12786
|
+
return { action: "session", ...result };
|
|
12787
|
+
}
|
|
12788
|
+
default:
|
|
12789
|
+
return {
|
|
12790
|
+
error: `fleet: unknown action "${action}". Valid: status, usage, health, session.`
|
|
12791
|
+
};
|
|
12792
|
+
}
|
|
12421
12793
|
}
|
|
12422
|
-
|
|
12423
|
-
}
|
|
12424
|
-
if (/\b(fail|failed|blocked|red)\b/.test(verdictBlock)) return "fail";
|
|
12425
|
-
if (/\b(pass|passed|green|approve|approved)\b/.test(verdictBlock)) return "pass";
|
|
12426
|
-
if (sectionHasBlockingContent(text, "Failures")) return "fail";
|
|
12427
|
-
return "inconclusive";
|
|
12428
|
-
}
|
|
12429
|
-
function sectionHasBlockingContent(text, heading) {
|
|
12430
|
-
const section = extractSection(text, heading);
|
|
12431
|
-
if (!section) return false;
|
|
12432
|
-
return !/^\s*(none|n\/a|no\b|no issues|empty|\(none\))\s*\.?\s*$/i.test(section.trim());
|
|
12433
|
-
}
|
|
12434
|
-
function extractSection(text, heading) {
|
|
12435
|
-
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12436
|
-
const pattern = new RegExp(
|
|
12437
|
-
`(?:^|\\n)\\s*#{1,6}\\s*${escaped}\\s*\\n([\\s\\S]*?)(?=\\n\\s*#{1,6}\\s+|$)`,
|
|
12438
|
-
"i"
|
|
12439
|
-
);
|
|
12440
|
-
const match = text.match(pattern);
|
|
12441
|
-
const body = match?.[1]?.trim();
|
|
12442
|
-
return body ? body : void 0;
|
|
12443
|
-
}
|
|
12444
|
-
function excerpt(text, max) {
|
|
12445
|
-
if (text.length <= max) return text;
|
|
12446
|
-
return `${text.slice(0, max - 20).trimEnd()}
|
|
12447
|
-
...(truncated)`;
|
|
12794
|
+
};
|
|
12448
12795
|
}
|
|
12449
12796
|
|
|
12450
12797
|
// src/coordination/director-collab-tools.ts
|
|
@@ -12565,336 +12912,378 @@ function makeWorkCompleteTool(director) {
|
|
|
12565
12912
|
};
|
|
12566
12913
|
}
|
|
12567
12914
|
|
|
12568
|
-
// src/coordination/director-
|
|
12915
|
+
// src/coordination/director-quality-gate-tool.ts
|
|
12569
12916
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
12570
|
-
function
|
|
12571
|
-
const inputSchema = {
|
|
12572
|
-
type: "object",
|
|
12573
|
-
properties: {
|
|
12574
|
-
subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
|
|
12575
|
-
description: {
|
|
12576
|
-
type: "string",
|
|
12577
|
-
minLength: 1,
|
|
12578
|
-
description: "The task in natural language \u2014 what you want this subagent to do."
|
|
12579
|
-
},
|
|
12580
|
-
maxToolCalls: {
|
|
12581
|
-
type: "number",
|
|
12582
|
-
minimum: 1,
|
|
12583
|
-
description: "Optional per-task tool-call budget override."
|
|
12584
|
-
},
|
|
12585
|
-
timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
|
|
12586
|
-
},
|
|
12587
|
-
required: ["subagentId", "description"]
|
|
12588
|
-
};
|
|
12917
|
+
function makeQualityGateTool(director, roster) {
|
|
12589
12918
|
return {
|
|
12590
|
-
name: "
|
|
12591
|
-
description: "
|
|
12919
|
+
name: "quality_gate",
|
|
12920
|
+
description: "Run a first-class implementation quality gate. It can await implementer task ids, spawn independent verifier/reviewer agents, summarize their verdicts, and optionally send must-fix feedback back to an implementer until the gate passes or the repair-attempt limit is reached.",
|
|
12921
|
+
usageHint: "Use after code-changing work. Provide implementerTaskIds when available. Add repairSubagentId to iterate fixes automatically. Verdict only passes when every enabled reviewer/verifier explicitly passes.",
|
|
12592
12922
|
permission: "auto",
|
|
12593
12923
|
mutating: false,
|
|
12594
12924
|
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
12595
|
-
inputSchema,
|
|
12596
|
-
async execute(input) {
|
|
12597
|
-
const i = input;
|
|
12598
|
-
const task = {
|
|
12599
|
-
id: randomUUID9(),
|
|
12600
|
-
description: i.description,
|
|
12601
|
-
subagentId: i.subagentId,
|
|
12602
|
-
maxToolCalls: i.maxToolCalls,
|
|
12603
|
-
timeoutMs: i.timeoutMs
|
|
12604
|
-
};
|
|
12605
|
-
const taskId = await director.assign(task);
|
|
12606
|
-
return { taskId, subagentId: i.subagentId };
|
|
12607
|
-
}
|
|
12608
|
-
};
|
|
12609
|
-
}
|
|
12610
|
-
function makeAwaitTasksTool(director) {
|
|
12611
|
-
return {
|
|
12612
|
-
name: "await_tasks",
|
|
12613
|
-
description: 'Block until one or more `taskId`s complete, then return their results. The subagents keep running in the background \u2014 only the leader\'s iteration pauses, which is the point: this is the correct tool to retrieve a result you started earlier with `assign_task`. mode:"all" (default) blocks until EVERY named task completes. mode:"any" returns as soon as AT LEAST ONE completes \u2014 use it for independent tasks so you can handle each finisher immediately (reassign work, spawn helpers) instead of idling on the slowest; call again with the returned `pending` ids to pick up the next finisher. The pattern "fan out via assign_task, then await_tasks({mode:\'any\'})" is the async replacement for serial `delegate` calls.',
|
|
12614
|
-
permission: "auto",
|
|
12615
|
-
mutating: false,
|
|
12616
|
-
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12617
12925
|
inputSchema: {
|
|
12618
12926
|
type: "object",
|
|
12619
12927
|
properties: {
|
|
12620
|
-
|
|
12928
|
+
task: {
|
|
12929
|
+
type: "string",
|
|
12930
|
+
description: "Original implementation task or acceptance goal being gated."
|
|
12931
|
+
},
|
|
12932
|
+
implementerTaskIds: {
|
|
12621
12933
|
type: "array",
|
|
12622
12934
|
items: { type: "string" },
|
|
12623
|
-
description: "
|
|
12935
|
+
description: "Optional completed or in-flight implementer task ids to await and include as implementation evidence."
|
|
12624
12936
|
},
|
|
12625
|
-
|
|
12937
|
+
repairSubagentId: {
|
|
12626
12938
|
type: "string",
|
|
12627
|
-
|
|
12628
|
-
|
|
12939
|
+
description: "Optional implementer subagent id. When set and the gate fails, quality_gate assigns a repair task with reviewer/verifier feedback and reruns the gate."
|
|
12940
|
+
},
|
|
12941
|
+
maxRepairAttempts: {
|
|
12942
|
+
type: "number",
|
|
12943
|
+
minimum: 0,
|
|
12944
|
+
maximum: 5,
|
|
12945
|
+
description: "Maximum automatic repair iterations. Default: 2 when repairSubagentId is set, otherwise 0."
|
|
12946
|
+
},
|
|
12947
|
+
targets: {
|
|
12948
|
+
type: "array",
|
|
12949
|
+
items: { type: "string" },
|
|
12950
|
+
description: "Files, packages, or paths that reviewer/verifier should focus on."
|
|
12951
|
+
},
|
|
12952
|
+
commands: {
|
|
12953
|
+
type: "array",
|
|
12954
|
+
items: { type: "string" },
|
|
12955
|
+
description: 'Verification commands that should pass, e.g. ["pnpm --filter @wrongstack/core typecheck"].'
|
|
12956
|
+
},
|
|
12957
|
+
expected: {
|
|
12958
|
+
type: "string",
|
|
12959
|
+
description: "Expected behavior or acceptance criteria."
|
|
12960
|
+
},
|
|
12961
|
+
evidence: {
|
|
12962
|
+
type: "string",
|
|
12963
|
+
description: "Known implementation notes, diff summary, or commands already run."
|
|
12964
|
+
},
|
|
12965
|
+
reviewer: {
|
|
12966
|
+
type: "boolean",
|
|
12967
|
+
description: "Whether to run the reviewer lane. Default true."
|
|
12968
|
+
},
|
|
12969
|
+
verifier: {
|
|
12970
|
+
type: "boolean",
|
|
12971
|
+
description: "Whether to run the verifier lane. Default true."
|
|
12629
12972
|
},
|
|
12630
12973
|
timeoutMs: {
|
|
12631
12974
|
type: "number",
|
|
12632
12975
|
minimum: 1,
|
|
12633
|
-
description:
|
|
12976
|
+
description: "Optional per reviewer/verifier/repair task timeout."
|
|
12977
|
+
},
|
|
12978
|
+
reviewerWorktree: {
|
|
12979
|
+
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
12980
|
+
description: "Reviewer worktree override. Default off because reviewer is read-only."
|
|
12981
|
+
},
|
|
12982
|
+
verifierWorktree: {
|
|
12983
|
+
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
12984
|
+
description: "Verifier worktree override. Default auto so test artifacts stay isolated when fleet policy wants it."
|
|
12634
12985
|
}
|
|
12635
|
-
}
|
|
12636
|
-
required: ["taskIds"]
|
|
12986
|
+
}
|
|
12637
12987
|
},
|
|
12638
12988
|
async execute(input) {
|
|
12639
|
-
const i = input;
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : void 0
|
|
12644
|
-
);
|
|
12989
|
+
const i = normalizeQualityGateInput(input);
|
|
12990
|
+
const runReviewer = i.reviewer !== false;
|
|
12991
|
+
const runVerifier = i.verifier !== false;
|
|
12992
|
+
if (!runReviewer && !runVerifier) {
|
|
12645
12993
|
return {
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
...r.timedOut ? { timedOut: true } : {},
|
|
12650
|
-
...r.pending.length > 0 ? {
|
|
12651
|
-
hint: 'Handle the completed results now. Re-call await_tasks with the pending ids (mode:"any") for the next finisher \u2014 or assign new work to the now-idle subagent first.'
|
|
12652
|
-
} : {}
|
|
12994
|
+
verdict: "inconclusive",
|
|
12995
|
+
passed: false,
|
|
12996
|
+
error: "quality_gate requires reviewer, verifier, or both."
|
|
12653
12997
|
};
|
|
12654
12998
|
}
|
|
12655
|
-
const
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12999
|
+
const implementerResults = i.implementerTaskIds && i.implementerTaskIds.length > 0 ? await director.awaitTasks(i.implementerTaskIds) : [];
|
|
13000
|
+
const maxRepairAttempts = clampRepairAttempts(
|
|
13001
|
+
i.maxRepairAttempts ?? (i.repairSubagentId ? 2 : 0)
|
|
13002
|
+
);
|
|
13003
|
+
const repairResults = [];
|
|
13004
|
+
const attempts = [];
|
|
13005
|
+
for (let attempt = 1; ; attempt++) {
|
|
13006
|
+
const gateTaskIds = [];
|
|
13007
|
+
const taskRoleById = /* @__PURE__ */ new Map();
|
|
13008
|
+
if (runVerifier) {
|
|
13009
|
+
const subagentId = await director.spawn(
|
|
13010
|
+
makeQualityGateSubagentConfig("verifier", roster, i.verifierWorktree ?? "auto")
|
|
13011
|
+
);
|
|
13012
|
+
const taskId = await director.assign({
|
|
13013
|
+
id: randomUUID9(),
|
|
13014
|
+
subagentId,
|
|
13015
|
+
description: buildVerifierTask(i, {
|
|
13016
|
+
attempt,
|
|
13017
|
+
implementerResults,
|
|
13018
|
+
repairResults,
|
|
13019
|
+
priorAttempts: attempts
|
|
13020
|
+
}),
|
|
13021
|
+
timeoutMs: i.timeoutMs
|
|
13022
|
+
});
|
|
13023
|
+
gateTaskIds.push(taskId);
|
|
13024
|
+
taskRoleById.set(taskId, "verifier");
|
|
12680
13025
|
}
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
13026
|
+
if (runReviewer) {
|
|
13027
|
+
const subagentId = await director.spawn(
|
|
13028
|
+
makeQualityGateSubagentConfig("reviewer", roster, i.reviewerWorktree ?? "off")
|
|
13029
|
+
);
|
|
13030
|
+
const taskId = await director.assign({
|
|
13031
|
+
id: randomUUID9(),
|
|
13032
|
+
subagentId,
|
|
13033
|
+
description: buildReviewerTask(i, {
|
|
13034
|
+
attempt,
|
|
13035
|
+
implementerResults,
|
|
13036
|
+
repairResults,
|
|
13037
|
+
priorAttempts: attempts
|
|
13038
|
+
}),
|
|
13039
|
+
timeoutMs: i.timeoutMs
|
|
13040
|
+
});
|
|
13041
|
+
gateTaskIds.push(taskId);
|
|
13042
|
+
taskRoleById.set(taskId, "reviewer");
|
|
13043
|
+
}
|
|
13044
|
+
const gateResults = await director.awaitTasks(gateTaskIds);
|
|
13045
|
+
const reports = gateResults.map((r) => assessRoleResult(taskRoleById.get(r.taskId), r));
|
|
13046
|
+
const assessment = assessQualityGate(reports);
|
|
13047
|
+
attempts.push({ attempt, reports, ...assessment });
|
|
13048
|
+
if (assessment.passed || !i.repairSubagentId || attempt > maxRepairAttempts) {
|
|
13049
|
+
return {
|
|
13050
|
+
verdict: assessment.verdict,
|
|
13051
|
+
passed: assessment.passed,
|
|
13052
|
+
attempts,
|
|
13053
|
+
repairAttemptsUsed: repairResults.length,
|
|
13054
|
+
implementerResults: implementerResults.map(summarizeTaskResult),
|
|
13055
|
+
nextAction: assessment.passed ? "accept" : i.repairSubagentId && attempt > maxRepairAttempts ? "manual_intervention_or_raise_repair_limit" : "inspect_failures"
|
|
13056
|
+
};
|
|
13057
|
+
}
|
|
13058
|
+
const repairTaskId = await director.assign({
|
|
13059
|
+
id: randomUUID9(),
|
|
13060
|
+
subagentId: i.repairSubagentId,
|
|
13061
|
+
description: buildRepairTask(i, attempts[attempts.length - 1], attempt),
|
|
13062
|
+
timeoutMs: i.timeoutMs
|
|
13063
|
+
});
|
|
13064
|
+
const [repairResult] = await director.awaitTasks([repairTaskId]);
|
|
13065
|
+
if (repairResult) repairResults.push(repairResult);
|
|
13066
|
+
if (repairResult?.status !== "success") {
|
|
13067
|
+
return {
|
|
13068
|
+
verdict: "fail",
|
|
13069
|
+
passed: false,
|
|
13070
|
+
attempts,
|
|
13071
|
+
repairAttemptsUsed: repairResults.length,
|
|
13072
|
+
repairResult: repairResult ? summarizeTaskResult(repairResult) : void 0,
|
|
13073
|
+
implementerResults: implementerResults.map(summarizeTaskResult),
|
|
13074
|
+
nextAction: "repair_failed"
|
|
13075
|
+
};
|
|
12691
13076
|
}
|
|
12692
|
-
return {
|
|
12693
|
-
ok: true,
|
|
12694
|
-
answer: stored.summary,
|
|
12695
|
-
_answerKey: stored.key,
|
|
12696
|
-
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
12697
|
-
};
|
|
12698
|
-
} catch (err) {
|
|
12699
|
-
return { ok: false, error: toErrorMessage(err) };
|
|
12700
13077
|
}
|
|
12701
13078
|
}
|
|
12702
13079
|
};
|
|
12703
13080
|
}
|
|
12704
|
-
function
|
|
13081
|
+
function normalizeQualityGateInput(input) {
|
|
13082
|
+
const raw = input ?? {};
|
|
12705
13083
|
return {
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
},
|
|
12720
|
-
required: ["key"]
|
|
12721
|
-
},
|
|
12722
|
-
async execute(input) {
|
|
12723
|
-
const i = input;
|
|
12724
|
-
const value = director.largeAnswerStore.retrieveAnswer(i.key);
|
|
12725
|
-
if (value === void 0) {
|
|
12726
|
-
return {
|
|
12727
|
-
ok: false,
|
|
12728
|
-
error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.`
|
|
12729
|
-
};
|
|
12730
|
-
}
|
|
12731
|
-
return { ok: true, value };
|
|
12732
|
-
}
|
|
13084
|
+
task: typeof raw.task === "string" ? raw.task : void 0,
|
|
13085
|
+
implementerTaskIds: stringArray2(raw.implementerTaskIds),
|
|
13086
|
+
repairSubagentId: typeof raw.repairSubagentId === "string" && raw.repairSubagentId.trim() ? raw.repairSubagentId.trim() : void 0,
|
|
13087
|
+
maxRepairAttempts: typeof raw.maxRepairAttempts === "number" ? raw.maxRepairAttempts : void 0,
|
|
13088
|
+
targets: stringArray2(raw.targets),
|
|
13089
|
+
commands: stringArray2(raw.commands),
|
|
13090
|
+
expected: typeof raw.expected === "string" ? raw.expected : void 0,
|
|
13091
|
+
evidence: typeof raw.evidence === "string" ? raw.evidence : void 0,
|
|
13092
|
+
reviewer: typeof raw.reviewer === "boolean" ? raw.reviewer : void 0,
|
|
13093
|
+
verifier: typeof raw.verifier === "boolean" ? raw.verifier : void 0,
|
|
13094
|
+
timeoutMs: typeof raw.timeoutMs === "number" ? raw.timeoutMs : void 0,
|
|
13095
|
+
reviewerWorktree: normalizeWorktreeOverride(raw.reviewerWorktree),
|
|
13096
|
+
verifierWorktree: normalizeWorktreeOverride(raw.verifierWorktree)
|
|
12733
13097
|
};
|
|
12734
13098
|
}
|
|
12735
|
-
function
|
|
13099
|
+
function clampRepairAttempts(value) {
|
|
13100
|
+
if (!Number.isFinite(value)) return 0;
|
|
13101
|
+
return Math.max(0, Math.min(5, Math.floor(value)));
|
|
13102
|
+
}
|
|
13103
|
+
function makeQualityGateSubagentConfig(role, roster, worktree) {
|
|
13104
|
+
const base = roster?.[role] ?? getAgentDefinition(role)?.config ?? { name: role, role };
|
|
12736
13105
|
return {
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
permission: "auto",
|
|
12740
|
-
mutating: false,
|
|
12741
|
-
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12742
|
-
inputSchema: {
|
|
12743
|
-
type: "object",
|
|
12744
|
-
properties: {
|
|
12745
|
-
taskIds: {
|
|
12746
|
-
type: "array",
|
|
12747
|
-
items: { type: "string" },
|
|
12748
|
-
description: "Completed task ids to aggregate."
|
|
12749
|
-
},
|
|
12750
|
-
style: {
|
|
12751
|
-
type: "string",
|
|
12752
|
-
enum: ["markdown", "json"],
|
|
12753
|
-
description: "Output flavor \u2014 markdown (default) or json."
|
|
12754
|
-
}
|
|
12755
|
-
},
|
|
12756
|
-
required: ["taskIds"]
|
|
12757
|
-
},
|
|
12758
|
-
async execute(input) {
|
|
12759
|
-
const i = input;
|
|
12760
|
-
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
12761
|
-
return { summary, count: i.taskIds.length };
|
|
12762
|
-
}
|
|
13106
|
+
...instantiateRosterConfig2(role, base),
|
|
13107
|
+
worktree
|
|
12763
13108
|
};
|
|
12764
13109
|
}
|
|
12765
|
-
function
|
|
13110
|
+
function buildVerifierTask(input, state) {
|
|
13111
|
+
return [
|
|
13112
|
+
"Run the independent verification gate for this implementation.",
|
|
13113
|
+
"Return Markdown with `## Verdict` and make the first verdict word exactly `pass`, `fail`, or `blocked`.",
|
|
13114
|
+
"Do not edit code. Run the smallest meaningful command set and include exact failures.",
|
|
13115
|
+
"",
|
|
13116
|
+
`Gate attempt: ${state.attempt}`,
|
|
13117
|
+
input.task ? `Original task:
|
|
13118
|
+
${input.task}` : void 0,
|
|
13119
|
+
input.targets?.length ? `Targets:
|
|
13120
|
+
${input.targets.map((t) => `- ${t}`).join("\n")}` : void 0,
|
|
13121
|
+
input.commands?.length ? `Required or suggested commands:
|
|
13122
|
+
${input.commands.map((c) => `- ${c}`).join("\n")}` : void 0,
|
|
13123
|
+
input.expected ? `Expected behavior:
|
|
13124
|
+
${input.expected}` : void 0,
|
|
13125
|
+
input.evidence ? `Known evidence:
|
|
13126
|
+
${input.evidence}` : void 0,
|
|
13127
|
+
taskResultsBlock("Implementer results", state.implementerResults),
|
|
13128
|
+
taskResultsBlock("Repair results so far", state.repairResults),
|
|
13129
|
+
priorAttemptsBlock(state.priorAttempts)
|
|
13130
|
+
].filter((part) => !!part).join("\n\n");
|
|
13131
|
+
}
|
|
13132
|
+
function buildReviewerTask(input, state) {
|
|
13133
|
+
return [
|
|
13134
|
+
"Run independent code review for this implementation.",
|
|
13135
|
+
"Return Markdown with `## Verdict` and make the first verdict phrase exactly `approve`, `request changes`, or `needs verification`.",
|
|
13136
|
+
"Do not edit code. Treat missing proof, vague tests, and uncertainty as blocking until verifier evidence exists.",
|
|
13137
|
+
"",
|
|
13138
|
+
`Gate attempt: ${state.attempt}`,
|
|
13139
|
+
input.task ? `Original task:
|
|
13140
|
+
${input.task}` : void 0,
|
|
13141
|
+
input.targets?.length ? `Targets:
|
|
13142
|
+
${input.targets.map((t) => `- ${t}`).join("\n")}` : void 0,
|
|
13143
|
+
input.expected ? `Expected behavior:
|
|
13144
|
+
${input.expected}` : void 0,
|
|
13145
|
+
input.evidence ? `Known evidence:
|
|
13146
|
+
${input.evidence}` : void 0,
|
|
13147
|
+
taskResultsBlock("Implementer results", state.implementerResults),
|
|
13148
|
+
taskResultsBlock("Repair results so far", state.repairResults),
|
|
13149
|
+
priorAttemptsBlock(state.priorAttempts)
|
|
13150
|
+
].filter((part) => !!part).join("\n\n");
|
|
13151
|
+
}
|
|
13152
|
+
function buildRepairTask(input, attempt, attemptNumber) {
|
|
13153
|
+
return [
|
|
13154
|
+
`Repair the implementation after quality gate attempt ${attemptNumber} failed.`,
|
|
13155
|
+
"Address every must-fix item. Run relevant checks before returning.",
|
|
13156
|
+
"Do not claim done unless verifier/reviewer feedback is resolved.",
|
|
13157
|
+
"",
|
|
13158
|
+
input.task ? `Original task:
|
|
13159
|
+
${input.task}` : void 0,
|
|
13160
|
+
input.targets?.length ? `Targets:
|
|
13161
|
+
${input.targets.map((t) => `- ${t}`).join("\n")}` : void 0,
|
|
13162
|
+
input.commands?.length ? `Commands expected to pass:
|
|
13163
|
+
${input.commands.map((c) => `- ${c}`).join("\n")}` : void 0,
|
|
13164
|
+
input.expected ? `Expected behavior:
|
|
13165
|
+
${input.expected}` : void 0,
|
|
13166
|
+
attempt.mustFix.length ? `Must fix:
|
|
13167
|
+
${attempt.mustFix.map((f) => `- ${f}`).join("\n")}` : void 0,
|
|
13168
|
+
attempt.uncertaintyFlags.length ? `Uncertainty flags to resolve:
|
|
13169
|
+
${attempt.uncertaintyFlags.map((f) => `- ${f}`).join("\n")}` : void 0,
|
|
13170
|
+
`Reviewer/verifier reports:
|
|
13171
|
+
${attempt.reports.map((r) => `### ${r.role} (${r.verdict})
|
|
13172
|
+
${r.summary}`).join("\n\n")}`
|
|
13173
|
+
].filter((part) => !!part).join("\n\n");
|
|
13174
|
+
}
|
|
13175
|
+
function taskResultsBlock(title, results) {
|
|
13176
|
+
if (results.length === 0) return void 0;
|
|
13177
|
+
return `${title}:
|
|
13178
|
+
${results.map((r) => `### ${r.subagentId}/${r.taskId}
|
|
13179
|
+
${summarizeTaskResult(r).summary}`).join("\n\n")}`;
|
|
13180
|
+
}
|
|
13181
|
+
function priorAttemptsBlock(attempts) {
|
|
13182
|
+
if (attempts.length === 0) return void 0;
|
|
13183
|
+
return `Prior quality gate attempts:
|
|
13184
|
+
${attempts.map(
|
|
13185
|
+
(a) => `### Attempt ${a.attempt}
|
|
13186
|
+
${a.reports.map((r) => `- ${r.role}: ${r.verdict}${r.error ? ` (${r.error})` : ""}`).join("\n")}`
|
|
13187
|
+
).join("\n\n")}`;
|
|
13188
|
+
}
|
|
13189
|
+
function summarizeTaskResult(result) {
|
|
13190
|
+
const text = typeof result.result === "string" ? result.result : result.result !== void 0 ? JSON.stringify(result.result, null, 2) : "";
|
|
13191
|
+
const error = result.error ? `${result.error.kind}: ${result.error.message}` : void 0;
|
|
12766
13192
|
return {
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
inputSchema: {
|
|
12773
|
-
type: "object",
|
|
12774
|
-
properties: { subagentId: { type: "string", description: "Subagent to abort." } },
|
|
12775
|
-
required: ["subagentId"]
|
|
12776
|
-
},
|
|
12777
|
-
async execute(input) {
|
|
12778
|
-
const i = input;
|
|
12779
|
-
await director.terminate(i.subagentId);
|
|
12780
|
-
return { ok: true };
|
|
12781
|
-
}
|
|
13193
|
+
taskId: result.taskId,
|
|
13194
|
+
subagentId: result.subagentId,
|
|
13195
|
+
status: result.status,
|
|
13196
|
+
summary: excerpt(text || error || "(no output)", 4e3),
|
|
13197
|
+
error
|
|
12782
13198
|
};
|
|
12783
13199
|
}
|
|
12784
|
-
function
|
|
13200
|
+
function assessRoleResult(role, result) {
|
|
13201
|
+
const resolvedRole = role ?? (result.subagentId.includes("review") ? "reviewer" : "verifier");
|
|
13202
|
+
const summary = summarizeTaskResult(result);
|
|
13203
|
+
const text = summary.summary;
|
|
13204
|
+
const uncertaintyFlags = extractSection(text, "Uncertainty Flags");
|
|
13205
|
+
if (result.status !== "success") {
|
|
13206
|
+
return {
|
|
13207
|
+
role: resolvedRole,
|
|
13208
|
+
subagentId: result.subagentId,
|
|
13209
|
+
taskId: result.taskId,
|
|
13210
|
+
status: result.status,
|
|
13211
|
+
verdict: "fail",
|
|
13212
|
+
summary: text,
|
|
13213
|
+
uncertaintyFlags,
|
|
13214
|
+
error: summary.error ?? result.status
|
|
13215
|
+
};
|
|
13216
|
+
}
|
|
12785
13217
|
return {
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
13218
|
+
role: resolvedRole,
|
|
13219
|
+
subagentId: result.subagentId,
|
|
13220
|
+
taskId: result.taskId,
|
|
13221
|
+
status: result.status,
|
|
13222
|
+
verdict: parseQualityVerdict(resolvedRole, text),
|
|
13223
|
+
summary: text,
|
|
13224
|
+
uncertaintyFlags
|
|
13225
|
+
};
|
|
13226
|
+
}
|
|
13227
|
+
function assessQualityGate(reports) {
|
|
13228
|
+
const mustFix = [];
|
|
13229
|
+
const uncertaintyFlags = [];
|
|
13230
|
+
let hasFail = false;
|
|
13231
|
+
let hasInconclusive = false;
|
|
13232
|
+
for (const report of reports) {
|
|
13233
|
+
if (report.verdict === "fail") hasFail = true;
|
|
13234
|
+
if (report.verdict === "inconclusive") hasInconclusive = true;
|
|
13235
|
+
const blocking = extractSection(report.summary, "Must Fix") || extractSection(report.summary, "Failures") || extractSection(report.summary, "Verification Gaps");
|
|
13236
|
+
if (blocking) mustFix.push(`${report.role}: ${excerpt(blocking, 1e3)}`);
|
|
13237
|
+
if (report.uncertaintyFlags) {
|
|
13238
|
+
uncertaintyFlags.push(`${report.role}: ${excerpt(report.uncertaintyFlags, 1e3)}`);
|
|
13239
|
+
}
|
|
13240
|
+
if (report.error) mustFix.push(`${report.role}: ${report.error}`);
|
|
13241
|
+
}
|
|
13242
|
+
if (hasFail) return { verdict: "fail", passed: false, mustFix, uncertaintyFlags };
|
|
13243
|
+
if (hasInconclusive || reports.length === 0) {
|
|
13244
|
+
return { verdict: "inconclusive", passed: false, mustFix, uncertaintyFlags };
|
|
13245
|
+
}
|
|
13246
|
+
return { verdict: "pass", passed: true, mustFix, uncertaintyFlags };
|
|
13247
|
+
}
|
|
13248
|
+
function parseQualityVerdict(role, text) {
|
|
13249
|
+
const normalized = text.toLowerCase();
|
|
13250
|
+
const verdictBlock = normalized.match(/(?:^|\n)\s*(?:#+\s*)?verdict\b[^\n]*(?:\n|:|-)?([\s\S]{0,500})/)?.[0] ?? normalized.slice(0, 1e3);
|
|
13251
|
+
if (role === "reviewer") {
|
|
13252
|
+
if (/\b(request\s+changes|needs\s+verification|reject|rejected|fail|failed|blocked)\b/.test(
|
|
13253
|
+
verdictBlock
|
|
13254
|
+
)) {
|
|
13255
|
+
return "fail";
|
|
12798
13256
|
}
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
return {
|
|
12803
|
-
name: "fleet",
|
|
12804
|
-
description: 'Fleet observation tool. Use `action` to select what you need: "status" \u2014 snapshot of all subagents + coordinator counts + pending tasks; "usage" \u2014 token + cost breakdown per subagent and totals; "health" \u2014 per-subagent budget pressure, last activity, and status; "session" \u2014 read a subagent\'s JSONL transcript (requires subagentId).',
|
|
12805
|
-
usageHint: 'action: "status" (default) | "usage" | "health" | "session".\nFor "session", pass subagentId (required) and optional tail (trailing JSONL lines).',
|
|
12806
|
-
permission: "auto",
|
|
12807
|
-
mutating: false,
|
|
12808
|
-
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
12809
|
-
inputSchema: {
|
|
12810
|
-
type: "object",
|
|
12811
|
-
properties: {
|
|
12812
|
-
action: {
|
|
12813
|
-
type: "string",
|
|
12814
|
-
enum: ["status", "usage", "health", "session"],
|
|
12815
|
-
description: "Observation to retrieve (default: status)."
|
|
12816
|
-
},
|
|
12817
|
-
subagentId: {
|
|
12818
|
-
type: "string",
|
|
12819
|
-
description: 'Subagent id (required for action: "session").'
|
|
12820
|
-
},
|
|
12821
|
-
tail: {
|
|
12822
|
-
type: "number",
|
|
12823
|
-
description: 'Number of trailing JSONL lines (action: "session" only). Omit for the full transcript.'
|
|
12824
|
-
}
|
|
12825
|
-
}
|
|
12826
|
-
},
|
|
12827
|
-
async execute(input) {
|
|
12828
|
-
const i = input ?? {};
|
|
12829
|
-
const action = i.action ?? "status";
|
|
12830
|
-
switch (action) {
|
|
12831
|
-
case "status": {
|
|
12832
|
-
const base = director.status();
|
|
12833
|
-
const fm = director.fleetManager;
|
|
12834
|
-
const stats = fm?.getFleetStats();
|
|
12835
|
-
const fleetStatus = fm?.getFleetStatus();
|
|
12836
|
-
return {
|
|
12837
|
-
action: "status",
|
|
12838
|
-
subagents: base.subagents,
|
|
12839
|
-
coordinatorStats: stats ? {
|
|
12840
|
-
total: stats.total,
|
|
12841
|
-
running: stats.running,
|
|
12842
|
-
idle: stats.idle,
|
|
12843
|
-
stopped: stats.stopped
|
|
12844
|
-
} : void 0,
|
|
12845
|
-
pending: fleetStatus?.pending ?? [],
|
|
12846
|
-
usage: fm?.snapshot()
|
|
12847
|
-
};
|
|
12848
|
-
}
|
|
12849
|
-
case "usage": {
|
|
12850
|
-
return { action: "usage", ...director.snapshot() };
|
|
12851
|
-
}
|
|
12852
|
-
case "health": {
|
|
12853
|
-
const status = director.status();
|
|
12854
|
-
const snapshot = director.snapshot();
|
|
12855
|
-
const subagents = status.subagents ?? [];
|
|
12856
|
-
const perSubagent = snapshot.perSubagent ?? {};
|
|
12857
|
-
return {
|
|
12858
|
-
action: "health",
|
|
12859
|
-
subagents: subagents.map((s) => {
|
|
12860
|
-
const usage = perSubagent[s.id];
|
|
12861
|
-
return {
|
|
12862
|
-
id: s.id,
|
|
12863
|
-
status: s.status,
|
|
12864
|
-
lastEventAt: usage?.lastEventAt,
|
|
12865
|
-
budgetPressure: {
|
|
12866
|
-
iterations: usage?.iterations,
|
|
12867
|
-
toolCalls: usage?.toolCalls,
|
|
12868
|
-
costUsd: usage?.cost
|
|
12869
|
-
}
|
|
12870
|
-
};
|
|
12871
|
-
})
|
|
12872
|
-
};
|
|
12873
|
-
}
|
|
12874
|
-
case "session": {
|
|
12875
|
-
const subagentId = i.subagentId;
|
|
12876
|
-
if (!subagentId) {
|
|
12877
|
-
return {
|
|
12878
|
-
action: "session",
|
|
12879
|
-
error: 'fleet: subagentId is required for action: "session"'
|
|
12880
|
-
};
|
|
12881
|
-
}
|
|
12882
|
-
const result = await director.readSession(subagentId, i.tail);
|
|
12883
|
-
if (!result) {
|
|
12884
|
-
return {
|
|
12885
|
-
action: "session",
|
|
12886
|
-
error: `fleet: transcript unavailable for "${subagentId}". Is sessionsRoot configured?`
|
|
12887
|
-
};
|
|
12888
|
-
}
|
|
12889
|
-
return { action: "session", ...result };
|
|
12890
|
-
}
|
|
12891
|
-
default:
|
|
12892
|
-
return {
|
|
12893
|
-
error: `fleet: unknown action "${action}". Valid: status, usage, health, session.`
|
|
12894
|
-
};
|
|
12895
|
-
}
|
|
13257
|
+
if (/\b(approve|approved|pass|passed)\b/.test(verdictBlock)) return "pass";
|
|
13258
|
+
if (sectionHasBlockingContent(text, "Must Fix") || sectionHasBlockingContent(text, "Verification Gaps")) {
|
|
13259
|
+
return "fail";
|
|
12896
13260
|
}
|
|
12897
|
-
|
|
13261
|
+
return "inconclusive";
|
|
13262
|
+
}
|
|
13263
|
+
if (/\b(fail|failed|blocked|red)\b/.test(verdictBlock)) return "fail";
|
|
13264
|
+
if (/\b(pass|passed|green|approve|approved)\b/.test(verdictBlock)) return "pass";
|
|
13265
|
+
if (sectionHasBlockingContent(text, "Failures")) return "fail";
|
|
13266
|
+
return "inconclusive";
|
|
13267
|
+
}
|
|
13268
|
+
function sectionHasBlockingContent(text, heading) {
|
|
13269
|
+
const section = extractSection(text, heading);
|
|
13270
|
+
if (!section) return false;
|
|
13271
|
+
return !/^\s*(none|n\/a|no\b|no issues|empty|\(none\))\s*\.?\s*$/i.test(section.trim());
|
|
13272
|
+
}
|
|
13273
|
+
function extractSection(text, heading) {
|
|
13274
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13275
|
+
const pattern = new RegExp(
|
|
13276
|
+
`(?:^|\\n)\\s*#{1,6}\\s*${escaped}\\s*\\n([\\s\\S]*?)(?=\\n\\s*#{1,6}\\s+|$)`,
|
|
13277
|
+
"i"
|
|
13278
|
+
);
|
|
13279
|
+
const match = text.match(pattern);
|
|
13280
|
+
const body = match?.[1]?.trim();
|
|
13281
|
+
return body ? body : void 0;
|
|
13282
|
+
}
|
|
13283
|
+
function excerpt(text, max) {
|
|
13284
|
+
if (text.length <= max) return text;
|
|
13285
|
+
return `${text.slice(0, max - 20).trimEnd()}
|
|
13286
|
+
...(truncated)`;
|
|
12898
13287
|
}
|
|
12899
13288
|
|
|
12900
13289
|
// src/coordination/director-tools.ts
|
|
@@ -12929,13 +13318,13 @@ function makeSpawnTool(director, roster) {
|
|
|
12929
13318
|
const inputSchema = {
|
|
12930
13319
|
type: "object",
|
|
12931
13320
|
properties: {
|
|
12932
|
-
|
|
13321
|
+
description: {
|
|
12933
13322
|
type: "string",
|
|
12934
|
-
description: "
|
|
13323
|
+
description: "What the subagent has to do, in free form. PREFER THIS over `role`: the dispatcher scores it against the capability metadata of every agent in the roster and picks the specialist, which is more reliable than recalling a role id from a list of 77. Only reach for `role` when you are certain which one you want."
|
|
12935
13324
|
},
|
|
12936
|
-
|
|
13325
|
+
role: {
|
|
12937
13326
|
type: "string",
|
|
12938
|
-
description: "
|
|
13327
|
+
description: "Roster role id. When set, the spawn uses the matching config from the roster, ignores other fields, and SKIPS DISPATCH ENTIRELY \u2014 so a half-remembered id silently costs you the specialist. Prefer `description` unless the id is certain."
|
|
12939
13328
|
},
|
|
12940
13329
|
name: {
|
|
12941
13330
|
type: "string",
|
|
@@ -12981,7 +13370,7 @@ function makeSpawnTool(director, roster) {
|
|
|
12981
13370
|
return {
|
|
12982
13371
|
name: "spawn_subagent",
|
|
12983
13372
|
description: "Create a new subagent under this director (own LLM context, own budget). NON-BLOCKING: returns a `subagentId` immediately without triggering any model call. Pair with `assign_task` to send work and `await_tasks` to retrieve the result later \u2014 this is the async-delegation pattern that lets the leader keep working while the subagent runs in its own context.",
|
|
12984
|
-
usageHint: "Pass `
|
|
13373
|
+
usageHint: "Pass `description` (what the work is \u2014 dispatched to the best-matching specialist), or `role` when you are certain of the id, or `name` + `provider`/`model`. Returns `{ subagentId }`. The roster is deep and specialised: there is very likely an agent built for this exact job, so describe the work rather than defaulting to a generalist. Use this instead of `delegate` when you want to fan out to multiple subagents or keep the leader unblocked while work runs in parallel.",
|
|
12985
13374
|
permission: "auto",
|
|
12986
13375
|
mutating: false,
|
|
12987
13376
|
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
@@ -13485,8 +13874,9 @@ function composeDirectorPrompt(parts = {}) {
|
|
|
13485
13874
|
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
13486
13875
|
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
13487
13876
|
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
13488
|
-
sections.push(
|
|
13489
|
-
|
|
13877
|
+
sections.push(
|
|
13878
|
+
"Roles you can spawn. This roster is deep and specialised \u2014 before handing work to a generalist, look for the agent built for exactly this job, and prefer describing the work (`description`) over recalling an id (`role`) so the dispatcher can find it:\n" + parts.rosterSummary.trim()
|
|
13879
|
+
);
|
|
13490
13880
|
}
|
|
13491
13881
|
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
13492
13882
|
sections.push(parts.basePrompt.trim());
|
|
@@ -13523,37 +13913,47 @@ ${parts.skills.trim()}`);
|
|
|
13523
13913
|
}
|
|
13524
13914
|
return sections.join("\n\n");
|
|
13525
13915
|
}
|
|
13916
|
+
var ROSTER_SUMMARY_MAX_CHARS = 150;
|
|
13917
|
+
function nameAddsInformation(roleId, name) {
|
|
13918
|
+
const flatten = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
13919
|
+
return flatten(name) !== flatten(roleId);
|
|
13920
|
+
}
|
|
13526
13921
|
function rosterSummaryFromConfigs(roster) {
|
|
13527
13922
|
const lines = [];
|
|
13528
13923
|
for (const [roleId, cfg] of Object.entries(roster)) {
|
|
13529
13924
|
const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
|
|
13530
|
-
const
|
|
13925
|
+
const label = nameAddsInformation(roleId, cfg.name) ? ` (${cfg.name})` : "";
|
|
13926
|
+
const capability = cfg.dispatch?.summary?.trim() || // Fallback headline: a role prompt that opens with a markdown heading
|
|
13927
|
+
// would otherwise render as "# Generic Project Agent", which reads as
|
|
13928
|
+
// formatting rather than as a capability.
|
|
13929
|
+
(cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").replace(/^#+\s*/, "").trim() : "");
|
|
13930
|
+
const headline = capability.length > ROSTER_SUMMARY_MAX_CHARS ? `${capability.slice(0, ROSTER_SUMMARY_MAX_CHARS - 1).trimEnd()}\u2026` : capability;
|
|
13531
13931
|
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
13532
|
-
lines.push(`- ${roleId}
|
|
13932
|
+
lines.push(`- ${roleId}${label}${tag}${tail}`);
|
|
13533
13933
|
}
|
|
13534
13934
|
return lines.join("\n");
|
|
13535
13935
|
}
|
|
13536
13936
|
|
|
13537
13937
|
// src/coordination/director-session.ts
|
|
13538
13938
|
import * as fsp18 from "node:fs/promises";
|
|
13539
|
-
import * as
|
|
13939
|
+
import * as path30 from "node:path";
|
|
13540
13940
|
|
|
13541
13941
|
// src/storage/session-store.ts
|
|
13542
13942
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
13543
13943
|
import * as fsp17 from "node:fs/promises";
|
|
13544
|
-
import * as
|
|
13944
|
+
import * as path29 from "node:path";
|
|
13545
13945
|
|
|
13546
13946
|
// src/session-catalog/client.ts
|
|
13547
13947
|
import { spawn as spawn2 } from "node:child_process";
|
|
13548
13948
|
import * as fs3 from "node:fs";
|
|
13549
13949
|
import * as net from "node:net";
|
|
13550
|
-
import * as
|
|
13950
|
+
import * as path19 from "node:path";
|
|
13551
13951
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13552
13952
|
|
|
13553
13953
|
// src/session-catalog/endpoint.ts
|
|
13554
13954
|
import { createHash as createHash2 } from "node:crypto";
|
|
13555
13955
|
import * as os2 from "node:os";
|
|
13556
|
-
import * as
|
|
13956
|
+
import * as path18 from "node:path";
|
|
13557
13957
|
|
|
13558
13958
|
// src/session-catalog/protocol.ts
|
|
13559
13959
|
var SESSION_CATALOG_PROTOCOL_VERSION = 1;
|
|
@@ -13566,7 +13966,7 @@ function encodeSessionCatalogMessage(message) {
|
|
|
13566
13966
|
// src/session-catalog/endpoint.ts
|
|
13567
13967
|
var SESSION_CATALOG_METADATA_FILE = ".session-catalog-server.json";
|
|
13568
13968
|
function normalizedPath(value) {
|
|
13569
|
-
const resolved =
|
|
13969
|
+
const resolved = path18.resolve(value);
|
|
13570
13970
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
13571
13971
|
}
|
|
13572
13972
|
function sessionCatalogProjectServerKey(projectDir) {
|
|
@@ -13577,10 +13977,10 @@ function sessionCatalogProjectServerEndpoint(projectDir) {
|
|
|
13577
13977
|
if (process.platform === "win32") {
|
|
13578
13978
|
return `\\\\.\\pipe\\wrongstack-session-catalog-v${SESSION_CATALOG_PROTOCOL_VERSION}-${key}`;
|
|
13579
13979
|
}
|
|
13580
|
-
return
|
|
13980
|
+
return path18.join(os2.tmpdir(), `wssc-v${SESSION_CATALOG_PROTOCOL_VERSION}`, `${key}.sock`);
|
|
13581
13981
|
}
|
|
13582
13982
|
function sessionCatalogProjectServerMetadataPath(projectDir) {
|
|
13583
|
-
return
|
|
13983
|
+
return path18.join(projectDir, SESSION_CATALOG_METADATA_FILE);
|
|
13584
13984
|
}
|
|
13585
13985
|
|
|
13586
13986
|
// src/session-catalog/client.ts
|
|
@@ -13615,7 +14015,7 @@ function resolveSessionCatalogProjectServerUrl(moduleUrl = import.meta.url, exis
|
|
|
13615
14015
|
return availability.kind === "available" ? availability.url : null;
|
|
13616
14016
|
}
|
|
13617
14017
|
function normalize2(value) {
|
|
13618
|
-
const resolved =
|
|
14018
|
+
const resolved = path19.resolve(value);
|
|
13619
14019
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
13620
14020
|
}
|
|
13621
14021
|
function delay(ms) {
|
|
@@ -14060,18 +14460,32 @@ function ulid(seedTime = Date.now()) {
|
|
|
14060
14460
|
}
|
|
14061
14461
|
|
|
14062
14462
|
// src/utils/session-scoped-path.ts
|
|
14063
|
-
import * as
|
|
14463
|
+
import * as path20 from "node:path";
|
|
14064
14464
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
14065
14465
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
14066
14466
|
throw invalid(sessionId);
|
|
14067
14467
|
}
|
|
14068
|
-
const resolved =
|
|
14069
|
-
const rel =
|
|
14070
|
-
if (rel.startsWith("..") ||
|
|
14468
|
+
const resolved = path20.resolve(dir, `${sessionId}${suffix}`);
|
|
14469
|
+
const rel = path20.relative(path20.resolve(dir), resolved);
|
|
14470
|
+
if (rel.startsWith("..") || path20.isAbsolute(rel)) {
|
|
14071
14471
|
throw invalid(sessionId);
|
|
14072
14472
|
}
|
|
14073
14473
|
return resolved;
|
|
14074
14474
|
}
|
|
14475
|
+
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
14476
|
+
".replay.jsonl",
|
|
14477
|
+
".audit.jsonl",
|
|
14478
|
+
".annotations.jsonl"
|
|
14479
|
+
];
|
|
14480
|
+
var RESERVED_SESSION_JSONL_NAMES = /* @__PURE__ */ new Set([
|
|
14481
|
+
"_index.jsonl",
|
|
14482
|
+
"_mailbox.jsonl"
|
|
14483
|
+
]);
|
|
14484
|
+
function isSessionTranscriptFileName(name) {
|
|
14485
|
+
if (!name.endsWith(".jsonl")) return false;
|
|
14486
|
+
if (RESERVED_SESSION_JSONL_NAMES.has(name)) return false;
|
|
14487
|
+
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
14488
|
+
}
|
|
14075
14489
|
function invalid(sessionId) {
|
|
14076
14490
|
return new FsError({
|
|
14077
14491
|
message: `Invalid sessionId: ${sessionId}`,
|
|
@@ -14089,7 +14503,7 @@ function truncate3(s, max) {
|
|
|
14089
14503
|
// src/storage/file-session-writer.ts
|
|
14090
14504
|
import { closeSync, createReadStream as createReadStream2, fsyncSync, openSync, writeSync } from "node:fs";
|
|
14091
14505
|
import * as fsp7 from "node:fs/promises";
|
|
14092
|
-
import * as
|
|
14506
|
+
import * as path21 from "node:path";
|
|
14093
14507
|
import { createInterface as createInterface2 } from "node:readline";
|
|
14094
14508
|
|
|
14095
14509
|
// src/storage/session-workspace-checkpoints.ts
|
|
@@ -14355,7 +14769,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14355
14769
|
this.meta = meta;
|
|
14356
14770
|
this.events = events;
|
|
14357
14771
|
this.resumed = opts.resumed ?? false;
|
|
14358
|
-
this.manifestFile = opts.dir ?
|
|
14772
|
+
this.manifestFile = opts.dir ? path21.join(opts.dir, `${path21.basename(id)}.summary.json`) : "";
|
|
14359
14773
|
this.filePath = opts.filePath ?? "";
|
|
14360
14774
|
this.secretScrubber = opts.secretScrubber;
|
|
14361
14775
|
this.checkpointCas = opts.checkpointCas;
|
|
@@ -15247,7 +15661,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
15247
15661
|
import { spawn as spawn3 } from "node:child_process";
|
|
15248
15662
|
import { createHash as createHash3, randomUUID as randomUUID11 } from "node:crypto";
|
|
15249
15663
|
import * as fsp8 from "node:fs/promises";
|
|
15250
|
-
import * as
|
|
15664
|
+
import * as path22 from "node:path";
|
|
15251
15665
|
|
|
15252
15666
|
// src/storage/storage-concurrency.ts
|
|
15253
15667
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
@@ -15278,13 +15692,13 @@ function sha256(content) {
|
|
|
15278
15692
|
return createHash3("sha256").update(content).digest("hex");
|
|
15279
15693
|
}
|
|
15280
15694
|
function isInside(root, target) {
|
|
15281
|
-
const relative4 =
|
|
15282
|
-
return relative4 === "" || !relative4.startsWith("..") && !
|
|
15695
|
+
const relative4 = path22.relative(root, target);
|
|
15696
|
+
return relative4 === "" || !relative4.startsWith("..") && !path22.isAbsolute(relative4);
|
|
15283
15697
|
}
|
|
15284
15698
|
function normalizeRelative(input) {
|
|
15285
|
-
if (!input ||
|
|
15699
|
+
if (!input || path22.isAbsolute(input)) return null;
|
|
15286
15700
|
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
15287
|
-
const resolved =
|
|
15701
|
+
const resolved = path22.posix.normalize(normalized);
|
|
15288
15702
|
if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
|
|
15289
15703
|
return resolved;
|
|
15290
15704
|
}
|
|
@@ -15299,8 +15713,8 @@ var SessionCheckpointCas = class {
|
|
|
15299
15713
|
projectRoot;
|
|
15300
15714
|
runGit;
|
|
15301
15715
|
constructor(opts) {
|
|
15302
|
-
this.rootDir =
|
|
15303
|
-
this.projectRoot =
|
|
15716
|
+
this.rootDir = path22.resolve(opts.rootDir);
|
|
15717
|
+
this.projectRoot = path22.resolve(opts.projectRoot);
|
|
15304
15718
|
this.runGit = opts.runGit ?? defaultRunGit;
|
|
15305
15719
|
}
|
|
15306
15720
|
async capture(_sessionId, _promptIndex) {
|
|
@@ -15325,7 +15739,7 @@ var SessionCheckpointCas = class {
|
|
|
15325
15739
|
relativePaths,
|
|
15326
15740
|
CAPTURE_CONCURRENCY,
|
|
15327
15741
|
async (relative4) => {
|
|
15328
|
-
const absolute =
|
|
15742
|
+
const absolute = path22.resolve(this.projectRoot, ...relative4.split("/"));
|
|
15329
15743
|
if (!isInside(this.projectRoot, absolute)) {
|
|
15330
15744
|
unresolved.push({ path: relative4, reason: "path escapes project root" });
|
|
15331
15745
|
return null;
|
|
@@ -15334,8 +15748,8 @@ var SessionCheckpointCas = class {
|
|
|
15334
15748
|
const stat10 = await fsp8.lstat(absolute);
|
|
15335
15749
|
if (stat10.isSymbolicLink()) {
|
|
15336
15750
|
const linkTarget = await fsp8.readlink(absolute);
|
|
15337
|
-
const resolvedLink =
|
|
15338
|
-
if (
|
|
15751
|
+
const resolvedLink = path22.resolve(path22.dirname(absolute), linkTarget);
|
|
15752
|
+
if (path22.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
15339
15753
|
unresolved.push({
|
|
15340
15754
|
path: relative4,
|
|
15341
15755
|
reason: "symlink target escapes project root"
|
|
@@ -15397,7 +15811,7 @@ var SessionCheckpointCas = class {
|
|
|
15397
15811
|
};
|
|
15398
15812
|
}
|
|
15399
15813
|
async materialize(checkpoint, targetRoot) {
|
|
15400
|
-
const target =
|
|
15814
|
+
const target = path22.resolve(targetRoot);
|
|
15401
15815
|
if (target === this.projectRoot) {
|
|
15402
15816
|
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
15403
15817
|
}
|
|
@@ -15437,10 +15851,10 @@ var SessionCheckpointCas = class {
|
|
|
15437
15851
|
try {
|
|
15438
15852
|
const output = await this.safeOutputPath(target, realTarget, entry.path);
|
|
15439
15853
|
if (entry.state === "symlink") {
|
|
15440
|
-
if (
|
|
15854
|
+
if (path22.isAbsolute(entry.linkTarget)) {
|
|
15441
15855
|
throw new Error("absolute symlink target refused");
|
|
15442
15856
|
}
|
|
15443
|
-
const resolvedLink =
|
|
15857
|
+
const resolvedLink = path22.resolve(path22.dirname(output), entry.linkTarget);
|
|
15444
15858
|
if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
|
|
15445
15859
|
}
|
|
15446
15860
|
prepared.push({
|
|
@@ -15468,7 +15882,7 @@ var SessionCheckpointCas = class {
|
|
|
15468
15882
|
await fsp8.unlink(output).catch((err) => {
|
|
15469
15883
|
if (err.code !== "ENOENT") throw err;
|
|
15470
15884
|
});
|
|
15471
|
-
await fsp8.mkdir(
|
|
15885
|
+
await fsp8.mkdir(path22.dirname(output), { recursive: true });
|
|
15472
15886
|
await fsp8.symlink(entry.linkTarget, output);
|
|
15473
15887
|
writtenFiles.push(entry.path);
|
|
15474
15888
|
continue;
|
|
@@ -15485,15 +15899,15 @@ var SessionCheckpointCas = class {
|
|
|
15485
15899
|
}
|
|
15486
15900
|
objectPath(hash) {
|
|
15487
15901
|
if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);
|
|
15488
|
-
return
|
|
15902
|
+
return path22.join(this.rootDir, "objects", hash.slice(0, 2), hash.slice(2));
|
|
15489
15903
|
}
|
|
15490
15904
|
manifestPath(hash) {
|
|
15491
15905
|
if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);
|
|
15492
|
-
return
|
|
15906
|
+
return path22.join(this.rootDir, "manifests", `${hash}.json`);
|
|
15493
15907
|
}
|
|
15494
15908
|
async putBlob(hash, content) {
|
|
15495
15909
|
const target = this.objectPath(hash);
|
|
15496
|
-
await fsp8.mkdir(
|
|
15910
|
+
await fsp8.mkdir(path22.dirname(target), { recursive: true });
|
|
15497
15911
|
try {
|
|
15498
15912
|
const existing = await fsp8.readFile(target);
|
|
15499
15913
|
if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
|
|
@@ -15501,9 +15915,9 @@ var SessionCheckpointCas = class {
|
|
|
15501
15915
|
} catch (err) {
|
|
15502
15916
|
if (err.code !== "ENOENT") throw err;
|
|
15503
15917
|
}
|
|
15504
|
-
const temp =
|
|
15505
|
-
|
|
15506
|
-
`.${
|
|
15918
|
+
const temp = path22.join(
|
|
15919
|
+
path22.dirname(target),
|
|
15920
|
+
`.${path22.basename(target)}.${process.pid}.${randomUUID11()}.tmp`
|
|
15507
15921
|
);
|
|
15508
15922
|
let handle;
|
|
15509
15923
|
try {
|
|
@@ -15564,7 +15978,7 @@ var SessionCheckpointCas = class {
|
|
|
15564
15978
|
async safeOutputPath(target, realTarget, relative4) {
|
|
15565
15979
|
const normalized = normalizeRelative(relative4);
|
|
15566
15980
|
if (!normalized) throw new Error("invalid relative path");
|
|
15567
|
-
const output =
|
|
15981
|
+
const output = path22.resolve(target, ...normalized.split("/"));
|
|
15568
15982
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
15569
15983
|
let probe = output;
|
|
15570
15984
|
for (; ; ) {
|
|
@@ -15574,7 +15988,7 @@ var SessionCheckpointCas = class {
|
|
|
15574
15988
|
return output;
|
|
15575
15989
|
} catch (err) {
|
|
15576
15990
|
if (err.code !== "ENOENT") throw err;
|
|
15577
|
-
const parent =
|
|
15991
|
+
const parent = path22.dirname(probe);
|
|
15578
15992
|
if (parent === probe) throw err;
|
|
15579
15993
|
probe = parent;
|
|
15580
15994
|
}
|
|
@@ -15638,13 +16052,13 @@ function generateSessionId(startedAt, _model) {
|
|
|
15638
16052
|
}
|
|
15639
16053
|
|
|
15640
16054
|
// src/storage/session-id-resolver.ts
|
|
15641
|
-
import * as
|
|
16055
|
+
import * as path23 from "node:path";
|
|
15642
16056
|
function resolveSessionId(query, candidateIds) {
|
|
15643
16057
|
const normalized = query.trim();
|
|
15644
16058
|
if (!normalized) return { status: "missing", query: normalized };
|
|
15645
16059
|
const uniqueIds = [...new Set(candidateIds)];
|
|
15646
16060
|
if (uniqueIds.includes(normalized)) return { status: "resolved", id: normalized };
|
|
15647
|
-
const leaf = (id) =>
|
|
16061
|
+
const leaf = (id) => path23.posix.basename(id.replace(/\\/g, "/"));
|
|
15648
16062
|
const exactLeafMatches = uniqueIds.filter((id) => leaf(id) === normalized);
|
|
15649
16063
|
if (exactLeafMatches.length === 1) {
|
|
15650
16064
|
return { status: "resolved", id: exactLeafMatches[0] };
|
|
@@ -15695,13 +16109,13 @@ function scrubPersistedSessionSummary(summary, scrubber2) {
|
|
|
15695
16109
|
// src/storage/session-resume-validation.ts
|
|
15696
16110
|
import { createHash as createHash4 } from "node:crypto";
|
|
15697
16111
|
import * as fsp9 from "node:fs/promises";
|
|
15698
|
-
import * as
|
|
16112
|
+
import * as path24 from "node:path";
|
|
15699
16113
|
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
15700
16114
|
var VALIDATION_CONCURRENCY = 8;
|
|
15701
16115
|
var NOTICE_PATH_LIMIT = 20;
|
|
15702
16116
|
function isInside2(root, target) {
|
|
15703
|
-
const relative4 =
|
|
15704
|
-
return relative4 === "" || !relative4.startsWith("..") && !
|
|
16117
|
+
const relative4 = path24.relative(root, target);
|
|
16118
|
+
return relative4 === "" || !relative4.startsWith("..") && !path24.isAbsolute(relative4);
|
|
15705
16119
|
}
|
|
15706
16120
|
function errno(err) {
|
|
15707
16121
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -15712,7 +16126,7 @@ function latestObservations(events, projectRoot) {
|
|
|
15712
16126
|
if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
|
|
15713
16127
|
continue;
|
|
15714
16128
|
}
|
|
15715
|
-
const normalized =
|
|
16129
|
+
const normalized = path24.resolve(projectRoot, event.path);
|
|
15716
16130
|
latest.set(normalized, {
|
|
15717
16131
|
path: normalized,
|
|
15718
16132
|
hash: event.hash.toLowerCase(),
|
|
@@ -15770,7 +16184,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
15770
16184
|
}
|
|
15771
16185
|
}
|
|
15772
16186
|
async function validateResumeFileObservations(events, projectRoot) {
|
|
15773
|
-
const lexicalRoot =
|
|
16187
|
+
const lexicalRoot = path24.resolve(projectRoot);
|
|
15774
16188
|
const realRoot = await fsp9.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
15775
16189
|
const observations = latestObservations(events, lexicalRoot);
|
|
15776
16190
|
const results = await mapWithConcurrency(
|
|
@@ -15784,11 +16198,20 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
15784
16198
|
staleFiles: results.filter((entry) => entry !== null)
|
|
15785
16199
|
};
|
|
15786
16200
|
}
|
|
16201
|
+
var RESUME_NOTICE_HEADERS = [
|
|
16202
|
+
"[SESSION RESUME FILE VALIDATION]",
|
|
16203
|
+
"[SESSION RESUME INTERRUPTED WORK]"
|
|
16204
|
+
];
|
|
16205
|
+
function isResumeNoticeMessage(message) {
|
|
16206
|
+
if (message.role !== "system" || typeof message.content !== "string") return false;
|
|
16207
|
+
return RESUME_NOTICE_HEADERS.some((header) => message.content === header || message.content.startsWith(`${header}
|
|
16208
|
+
`));
|
|
16209
|
+
}
|
|
15787
16210
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
15788
16211
|
if (validation.staleFiles.length === 0) return null;
|
|
15789
|
-
const root =
|
|
16212
|
+
const root = path24.resolve(projectRoot);
|
|
15790
16213
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
15791
|
-
const relative4 =
|
|
16214
|
+
const relative4 = path24.relative(root, entry.path);
|
|
15792
16215
|
const display = isInside2(root, entry.path) ? relative4 || "." : entry.path;
|
|
15793
16216
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
15794
16217
|
});
|
|
@@ -15813,22 +16236,22 @@ function formatInterruptedToolNotice(pendingToolUseCount) {
|
|
|
15813
16236
|
|
|
15814
16237
|
// src/storage/session-store/delete-session-artifacts.ts
|
|
15815
16238
|
import * as fsp10 from "node:fs/promises";
|
|
15816
|
-
import * as
|
|
16239
|
+
import * as path26 from "node:path";
|
|
15817
16240
|
|
|
15818
16241
|
// src/storage/session-store/paths.ts
|
|
15819
|
-
import * as
|
|
16242
|
+
import * as path25 from "node:path";
|
|
15820
16243
|
function sessionPath(storeDir, id, ext) {
|
|
15821
16244
|
return sessionScopedPath(storeDir, id, ext);
|
|
15822
16245
|
}
|
|
15823
16246
|
function shardManifestPath(storeDir, shardKey) {
|
|
15824
|
-
return shardKey ?
|
|
16247
|
+
return shardKey ? path25.join(storeDir, shardKey, "_manifest.json") : path25.join(storeDir, "_manifest.json");
|
|
15825
16248
|
}
|
|
15826
16249
|
function shardKeyForSessionId(id) {
|
|
15827
|
-
const dirName =
|
|
16250
|
+
const dirName = path25.dirname(id);
|
|
15828
16251
|
return dirName === "." ? "" : dirName;
|
|
15829
16252
|
}
|
|
15830
16253
|
async function ensureShardDir(storeDir, id) {
|
|
15831
|
-
const dirPath =
|
|
16254
|
+
const dirPath = path25.dirname(sessionScopedPath(storeDir, id, ""));
|
|
15832
16255
|
await ensureDir(dirPath);
|
|
15833
16256
|
return dirPath;
|
|
15834
16257
|
}
|
|
@@ -15839,9 +16262,9 @@ async function deleteSessionArtifacts({
|
|
|
15839
16262
|
id,
|
|
15840
16263
|
jsonlPath
|
|
15841
16264
|
}) {
|
|
15842
|
-
const shardDir =
|
|
15843
|
-
const base =
|
|
15844
|
-
const sessDir =
|
|
16265
|
+
const shardDir = path26.dirname(jsonlPath);
|
|
16266
|
+
const base = path26.basename(id);
|
|
16267
|
+
const sessDir = path26.join(shardDir, base);
|
|
15845
16268
|
const deletions = [
|
|
15846
16269
|
fsp10.unlink(jsonlPath),
|
|
15847
16270
|
fsp10.unlink(sessionPath(rootDir, id, ".summary.json")),
|
|
@@ -15894,13 +16317,10 @@ async function assertSessionCanBeDeleted(sessionId, isSessionInUse) {
|
|
|
15894
16317
|
function shouldSkipSessionDirectoryEntry(name) {
|
|
15895
16318
|
return name.startsWith(".") && name !== ".wrongstack" || name === "shared" || name === "subagents" || name === "attachments";
|
|
15896
16319
|
}
|
|
15897
|
-
function isSessionJsonlFileName(name) {
|
|
15898
|
-
return name.endsWith(".jsonl") && name !== "_index.jsonl";
|
|
15899
|
-
}
|
|
15900
16320
|
|
|
15901
16321
|
// src/storage/session-store/directory-session-files.ts
|
|
15902
16322
|
import * as fsp11 from "node:fs/promises";
|
|
15903
|
-
import * as
|
|
16323
|
+
import * as path27 from "node:path";
|
|
15904
16324
|
function sessionIdForFile(prefix, name) {
|
|
15905
16325
|
const base = name.replace(/\.jsonl$/, "");
|
|
15906
16326
|
return prefix ? `${prefix}/${base}` : base;
|
|
@@ -15921,13 +16341,13 @@ async function collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
|
15921
16341
|
if (shouldSkipSessionDirectoryEntry(entry.name)) continue;
|
|
15922
16342
|
if (entry.isDirectory()) {
|
|
15923
16343
|
dirEntries.push(entry);
|
|
15924
|
-
} else if (entry.isFile() &&
|
|
15925
|
-
files.push({ id: sessionIdForFile(prefix, entry.name), filePath:
|
|
16344
|
+
} else if (entry.isFile() && isSessionTranscriptFileName(entry.name)) {
|
|
16345
|
+
files.push({ id: sessionIdForFile(prefix, entry.name), filePath: path27.join(dir, entry.name) });
|
|
15926
16346
|
}
|
|
15927
16347
|
}
|
|
15928
16348
|
const childFileArrays = await Promise.all(
|
|
15929
16349
|
dirEntries.map(
|
|
15930
|
-
(entry) => collectSessionFiles(
|
|
16350
|
+
(entry) => collectSessionFiles(path27.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
|
|
15931
16351
|
)
|
|
15932
16352
|
);
|
|
15933
16353
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -15945,13 +16365,13 @@ async function collectSessionIds(dir, prefix = "", depth = 0) {
|
|
|
15945
16365
|
if (shouldSkipSessionDirectoryEntry(entry.name)) continue;
|
|
15946
16366
|
if (entry.isDirectory()) {
|
|
15947
16367
|
dirEntries.push(entry);
|
|
15948
|
-
} else if (entry.isFile() &&
|
|
16368
|
+
} else if (entry.isFile() && isSessionTranscriptFileName(entry.name)) {
|
|
15949
16369
|
fileIds.push(sessionIdForFile(prefix, entry.name));
|
|
15950
16370
|
}
|
|
15951
16371
|
}
|
|
15952
16372
|
const childIdArrays = await Promise.all(
|
|
15953
16373
|
dirEntries.map(
|
|
15954
|
-
(entry) => collectSessionIds(
|
|
16374
|
+
(entry) => collectSessionIds(path27.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
|
|
15955
16375
|
)
|
|
15956
16376
|
);
|
|
15957
16377
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -16047,13 +16467,13 @@ function inheritsIntoFork(event) {
|
|
|
16047
16467
|
|
|
16048
16468
|
// src/storage/session-store/fork-session.ts
|
|
16049
16469
|
async function forkSession(host, id, opts = {}) {
|
|
16050
|
-
const
|
|
16051
|
-
let boundary =
|
|
16470
|
+
const parentEvents = await host.readRawEvents(id);
|
|
16471
|
+
let boundary = parentEvents.length - 1;
|
|
16052
16472
|
let targetCheckpoint;
|
|
16053
16473
|
if (opts.checkpointPromptIndex !== void 0) {
|
|
16054
16474
|
boundary = -1;
|
|
16055
|
-
for (let i = 0; i <
|
|
16056
|
-
const event =
|
|
16475
|
+
for (let i = 0; i < parentEvents.length; i++) {
|
|
16476
|
+
const event = parentEvents[i];
|
|
16057
16477
|
if (event?.type === "checkpoint" && event.promptIndex === opts.checkpointPromptIndex) {
|
|
16058
16478
|
boundary = i;
|
|
16059
16479
|
targetCheckpoint = event;
|
|
@@ -16063,15 +16483,18 @@ async function forkSession(host, id, opts = {}) {
|
|
|
16063
16483
|
throw new Error(`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`);
|
|
16064
16484
|
}
|
|
16065
16485
|
}
|
|
16066
|
-
const parentPrefix =
|
|
16486
|
+
const parentPrefix = parentEvents.slice(0, boundary + 1);
|
|
16067
16487
|
const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;
|
|
16068
16488
|
const checkpointHash = createHash5("sha256").update(parentPrefix.map((event) => JSON.stringify(event)).join("\n") + "\n", "utf8").digest("hex");
|
|
16069
16489
|
const inherited = parentPrefix.filter(inheritsIntoFork);
|
|
16490
|
+
const start = parentEvents.find(
|
|
16491
|
+
(event) => event.type === "session_start"
|
|
16492
|
+
);
|
|
16070
16493
|
const writer = await host.create({
|
|
16071
16494
|
id: "",
|
|
16072
|
-
title:
|
|
16073
|
-
model:
|
|
16074
|
-
provider:
|
|
16495
|
+
title: "",
|
|
16496
|
+
model: start?.model,
|
|
16497
|
+
provider: start?.provider
|
|
16075
16498
|
});
|
|
16076
16499
|
try {
|
|
16077
16500
|
await writer.append({
|
|
@@ -16177,6 +16600,17 @@ var SessionLoadCache = class {
|
|
|
16177
16600
|
this.entries.clear();
|
|
16178
16601
|
this.bytes = 0;
|
|
16179
16602
|
}
|
|
16603
|
+
/**
|
|
16604
|
+
* A hit hands back fresh `messages` / `events` arrays over the cached
|
|
16605
|
+
* contents.
|
|
16606
|
+
*
|
|
16607
|
+
* The entry outlives every caller, and callers treat what they get as their
|
|
16608
|
+
* own: `resume()` passes `messages` straight into a live conversation, and
|
|
16609
|
+
* anything walking `events` may splice it. Returning the cached arrays
|
|
16610
|
+
* themselves let one caller's edit rewrite what the next one loads. The
|
|
16611
|
+
* elements are still shared — copying them would defeat the cache — so
|
|
16612
|
+
* entries remain read-only *contents* behind private containers.
|
|
16613
|
+
*/
|
|
16180
16614
|
getFresh(id, stat10, full) {
|
|
16181
16615
|
const cached = this.entries.get(id);
|
|
16182
16616
|
if (!cached || cached.mtimeMs !== stat10.mtimeMs || cached.size !== stat10.size) {
|
|
@@ -16184,8 +16618,11 @@ var SessionLoadCache = class {
|
|
|
16184
16618
|
}
|
|
16185
16619
|
this.entries.delete(id);
|
|
16186
16620
|
this.entries.set(id, cached);
|
|
16187
|
-
|
|
16188
|
-
|
|
16621
|
+
return {
|
|
16622
|
+
...cached.data,
|
|
16623
|
+
messages: full ? [...cached.data.messages] : [],
|
|
16624
|
+
events: [...cached.data.events]
|
|
16625
|
+
};
|
|
16189
16626
|
}
|
|
16190
16627
|
set(id, stat10, data) {
|
|
16191
16628
|
this.delete(id);
|
|
@@ -16374,6 +16811,9 @@ function stripSnapshotPayload(event) {
|
|
|
16374
16811
|
event.messagesOmitted = event.messages.length;
|
|
16375
16812
|
event.messages = [];
|
|
16376
16813
|
}
|
|
16814
|
+
function isStrippedSnapshot(event) {
|
|
16815
|
+
return event.messages.length === 0 && typeof event.messagesOmitted === "number" && event.messagesOmitted > 0;
|
|
16816
|
+
}
|
|
16377
16817
|
function isSessionEventLike(value) {
|
|
16378
16818
|
return value !== null && typeof value === "object" && typeof value.type === "string" && typeof value.ts === "string";
|
|
16379
16819
|
}
|
|
@@ -16403,7 +16843,12 @@ function replaySessionEvent(params) {
|
|
|
16403
16843
|
emitDamaged(params, `Ignored malformed message_updated event at index ${ev.index}`);
|
|
16404
16844
|
}
|
|
16405
16845
|
} else if (ev.type === "messages_replaced" && ev.version === 1) {
|
|
16406
|
-
if (
|
|
16846
|
+
if (isStrippedSnapshot(ev)) {
|
|
16847
|
+
emitDamaged(
|
|
16848
|
+
params,
|
|
16849
|
+
`Ignored messages_replaced event whose payload was stripped before persistence (${String(ev.messagesOmitted)} messages)`
|
|
16850
|
+
);
|
|
16851
|
+
} else if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
16407
16852
|
exactJournalActive = true;
|
|
16408
16853
|
} else {
|
|
16409
16854
|
emitDamaged(params, "Ignored malformed messages_replaced event");
|
|
@@ -16419,9 +16864,16 @@ function replaySessionEvent(params) {
|
|
|
16419
16864
|
emitDamaged(params, `Ignored malformed messages_dropped event (count ${String(ev.count)})`);
|
|
16420
16865
|
}
|
|
16421
16866
|
} else if (ev.type === "context_snapshot") {
|
|
16422
|
-
if (
|
|
16867
|
+
if (isStrippedSnapshot(ev)) {
|
|
16868
|
+
emitDamaged(
|
|
16869
|
+
params,
|
|
16870
|
+
`Ignored context_snapshot event whose payload was stripped before persistence (${String(ev.messagesOmitted)} messages)`
|
|
16871
|
+
);
|
|
16872
|
+
} else if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
16423
16873
|
emitDamaged(params, "Ignored malformed context_snapshot event");
|
|
16424
16874
|
}
|
|
16875
|
+
} else if (ev.type === "messages_replaced" || ev.type === "message_appended" || ev.type === "message_updated" || ev.type === "messages_dropped") {
|
|
16876
|
+
emitDamaged(params, `Ignored ${ev.type} event with unsupported version`);
|
|
16425
16877
|
} else if (!exactJournalActive && ev.type === "user_input") {
|
|
16426
16878
|
openToolUses.clear();
|
|
16427
16879
|
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
@@ -16472,15 +16924,15 @@ function emitDamaged(params, detail) {
|
|
|
16472
16924
|
|
|
16473
16925
|
// src/storage/session-store/prune-helpers.ts
|
|
16474
16926
|
import * as fsp13 from "node:fs/promises";
|
|
16475
|
-
import * as
|
|
16927
|
+
import * as path28 from "node:path";
|
|
16476
16928
|
function isPrunableSessionJsonl(name) {
|
|
16477
|
-
return
|
|
16929
|
+
return isSessionTranscriptFileName(name);
|
|
16478
16930
|
}
|
|
16479
16931
|
async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
16480
16932
|
const cutoff = Date.now() - maxAgeDays * 864e5;
|
|
16481
16933
|
let deleted = 0;
|
|
16482
16934
|
const pruneFile = async (dir, name, prefix) => {
|
|
16483
|
-
const jsonlPath =
|
|
16935
|
+
const jsonlPath = path28.join(dir, name);
|
|
16484
16936
|
try {
|
|
16485
16937
|
const stat10 = await fsp13.stat(jsonlPath);
|
|
16486
16938
|
if (stat10.mtimeMs >= cutoff) return;
|
|
@@ -16499,7 +16951,7 @@ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
|
16499
16951
|
continue;
|
|
16500
16952
|
}
|
|
16501
16953
|
if (!entry.isDirectory()) continue;
|
|
16502
|
-
const dateDir =
|
|
16954
|
+
const dateDir = path28.join(storeDir, entry.name);
|
|
16503
16955
|
const files = await fsp13.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
16504
16956
|
for (const file of files) {
|
|
16505
16957
|
if (!file.isFile() || !isPrunableSessionJsonl(file.name)) continue;
|
|
@@ -16508,7 +16960,7 @@ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
|
16508
16960
|
}
|
|
16509
16961
|
for (const entry of entries) {
|
|
16510
16962
|
if (!entry.isDirectory()) continue;
|
|
16511
|
-
const dateDir =
|
|
16963
|
+
const dateDir = path28.join(storeDir, entry.name);
|
|
16512
16964
|
try {
|
|
16513
16965
|
const remaining = await fsp13.readdir(dateDir);
|
|
16514
16966
|
if (remaining.length === 0) {
|
|
@@ -16861,9 +17313,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16861
17313
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
16862
17314
|
constructor(opts) {
|
|
16863
17315
|
this.dir = opts.dir;
|
|
16864
|
-
this.projectRoot = opts.projectRoot ?
|
|
17316
|
+
this.projectRoot = opts.projectRoot ? path29.resolve(opts.projectRoot) : void 0;
|
|
16865
17317
|
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
16866
|
-
rootDir:
|
|
17318
|
+
rootDir: path29.join(this.dir, "_cas"),
|
|
16867
17319
|
projectRoot: this.projectRoot
|
|
16868
17320
|
}) : void 0;
|
|
16869
17321
|
this.events = opts.events;
|
|
@@ -16874,7 +17326,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16874
17326
|
this.onAppendBatch = opts.onAppendBatch;
|
|
16875
17327
|
const builtRuntime = import.meta.url.includes("/dist/");
|
|
16876
17328
|
this.catalogClient = this.projectRoot && (builtRuntime || process.env["WRONGSTACK_SESSION_CATALOG_FORCE"] === "1") && resolveSessionCatalogProjectServerUrl() ? new SessionCatalogProjectClient({
|
|
16877
|
-
projectDir:
|
|
17329
|
+
projectDir: path29.dirname(this.dir),
|
|
16878
17330
|
projectRoot: this.projectRoot
|
|
16879
17331
|
}) : void 0;
|
|
16880
17332
|
}
|
|
@@ -16906,7 +17358,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16906
17358
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
16907
17359
|
/** Absolute path to the session index file. */
|
|
16908
17360
|
get indexFile() {
|
|
16909
|
-
return
|
|
17361
|
+
return path29.join(this.dir, "_index.jsonl");
|
|
16910
17362
|
}
|
|
16911
17363
|
/** Join session ID to its absolute path within the store directory. */
|
|
16912
17364
|
sessionPath(id, ext) {
|
|
@@ -16997,6 +17449,21 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16997
17449
|
async fork(id, opts = {}) {
|
|
16998
17450
|
return forkSession(this, id, opts);
|
|
16999
17451
|
}
|
|
17452
|
+
/**
|
|
17453
|
+
* Implements {@link SessionForkHost.readRawEvents} — the parent stream a fork
|
|
17454
|
+
* inherits, unmodified.
|
|
17455
|
+
*
|
|
17456
|
+
* Deliberately NOT `load()`: that loader empties superseded snapshot payloads
|
|
17457
|
+
* in place and front-drops events past its retention budget, both of which
|
|
17458
|
+
* are correct for reconstructing a conversation and wrong for copying a
|
|
17459
|
+
* journal prefix into a child. Streaming with an accept-everything predicate
|
|
17460
|
+
* keeps the scrubbing contract (`searchEvents` scrubs each line the same way
|
|
17461
|
+
* `load()` does) without either transformation.
|
|
17462
|
+
*/
|
|
17463
|
+
async readRawEvents(id) {
|
|
17464
|
+
const hits = await this.searchEvents(id, () => true);
|
|
17465
|
+
return hits.map((hit) => hit.event);
|
|
17466
|
+
}
|
|
17000
17467
|
/**
|
|
17001
17468
|
* Capture the deterministic post-tool workspace identity through the store-owned CAS.
|
|
17002
17469
|
*/
|
|
@@ -17085,14 +17552,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17085
17552
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
17086
17553
|
});
|
|
17087
17554
|
}
|
|
17555
|
+
const carriedMessages = data.messages.filter((message) => !isResumeNoticeMessage(message));
|
|
17088
17556
|
const resumedData = {
|
|
17089
17557
|
...data,
|
|
17090
17558
|
...resumeValidation ? { resumeValidation } : {},
|
|
17091
|
-
|
|
17559
|
+
messages: [...carriedMessages, ...noticeMessages]
|
|
17092
17560
|
};
|
|
17093
17561
|
let handle;
|
|
17094
17562
|
try {
|
|
17095
|
-
handle = await
|
|
17563
|
+
handle = await openSessionForAppend(file);
|
|
17096
17564
|
} catch (err) {
|
|
17097
17565
|
emitSessionStoreError(this.events, canonicalId, file, "resume", toErrorMessage(err), false);
|
|
17098
17566
|
throw new Error(
|
|
@@ -17119,7 +17587,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17119
17587
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
17120
17588
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
17121
17589
|
// sessions root (where summaryFor() would never find it).
|
|
17122
|
-
dir:
|
|
17590
|
+
dir: path29.dirname(file),
|
|
17123
17591
|
filePath: file,
|
|
17124
17592
|
secretScrubber: this.secretScrubber,
|
|
17125
17593
|
checkpointCas: this.checkpointCas,
|
|
@@ -17271,10 +17739,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17271
17739
|
const limit = criteria.limit ?? 100;
|
|
17272
17740
|
if (this.catalogClient) {
|
|
17273
17741
|
const records = await this.catalogClient.call("list_catalog", {
|
|
17274
|
-
limit
|
|
17275
|
-
...criteria
|
|
17742
|
+
limit,
|
|
17743
|
+
...criteria
|
|
17276
17744
|
});
|
|
17277
|
-
return this.scrubSummaries(records)
|
|
17745
|
+
return this.scrubSummaries(records);
|
|
17278
17746
|
}
|
|
17279
17747
|
try {
|
|
17280
17748
|
const indexed = await this.readIndex();
|
|
@@ -17499,11 +17967,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17499
17967
|
return shardKeys;
|
|
17500
17968
|
}
|
|
17501
17969
|
async readOrBuildShardManifest(shardKey) {
|
|
17502
|
-
const cached = this.shardManifestCache.get(shardKey);
|
|
17503
|
-
if (cached) return cached;
|
|
17504
17970
|
const manifestPath = this.shardManifestPath(shardKey);
|
|
17971
|
+
const cached = await this.freshShardManifestCacheEntry(shardKey, manifestPath);
|
|
17972
|
+
if (cached) return cached;
|
|
17505
17973
|
return withFileLock(manifestPath, async () => {
|
|
17506
|
-
const lockedCached = this.
|
|
17974
|
+
const lockedCached = await this.freshShardManifestCacheEntry(shardKey, manifestPath);
|
|
17507
17975
|
if (lockedCached) return lockedCached;
|
|
17508
17976
|
const entry = await readOrBuildShardManifestEntry({
|
|
17509
17977
|
shardKey,
|
|
@@ -17514,12 +17982,40 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17514
17982
|
summaryHeaderFor: (ref) => this.summaryHeaderFor(ref),
|
|
17515
17983
|
summaryFor: (id) => this.summaryFor(id)
|
|
17516
17984
|
});
|
|
17517
|
-
|
|
17985
|
+
try {
|
|
17986
|
+
const stat10 = await fsp17.stat(manifestPath);
|
|
17987
|
+
this.shardManifestCache.set(shardKey, {
|
|
17988
|
+
entry,
|
|
17989
|
+
mtimeMs: stat10.mtimeMs,
|
|
17990
|
+
size: stat10.size,
|
|
17991
|
+
ino: stat10.ino
|
|
17992
|
+
});
|
|
17993
|
+
} catch {
|
|
17994
|
+
this.shardManifestCache.delete(shardKey);
|
|
17995
|
+
}
|
|
17518
17996
|
return entry;
|
|
17519
17997
|
});
|
|
17520
17998
|
}
|
|
17999
|
+
/**
|
|
18000
|
+
* Shard manifests are invalidated by other store processes via atomic
|
|
18001
|
+
* delete/rebuild. Validate the in-memory projection against the persisted
|
|
18002
|
+
* file so one long-lived process cannot retain another process's stale view.
|
|
18003
|
+
*/
|
|
18004
|
+
async freshShardManifestCacheEntry(shardKey, manifestPath) {
|
|
18005
|
+
const cached = this.shardManifestCache.get(shardKey);
|
|
18006
|
+
if (!cached) return void 0;
|
|
18007
|
+
try {
|
|
18008
|
+
const stat10 = await fsp17.stat(manifestPath);
|
|
18009
|
+
if (stat10.mtimeMs === cached.mtimeMs && stat10.size === cached.size && stat10.ino === cached.ino) {
|
|
18010
|
+
return cached.entry;
|
|
18011
|
+
}
|
|
18012
|
+
} catch {
|
|
18013
|
+
}
|
|
18014
|
+
this.shardManifestCache.delete(shardKey);
|
|
18015
|
+
return void 0;
|
|
18016
|
+
}
|
|
17521
18017
|
async collectSessionFilesInShard(shardKey) {
|
|
17522
|
-
const dir = shardKey ?
|
|
18018
|
+
const dir = shardKey ? path29.join(this.dir, shardKey) : this.dir;
|
|
17523
18019
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
17524
18020
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
17525
18021
|
}
|
|
@@ -17797,6 +18293,23 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
17797
18293
|
});
|
|
17798
18294
|
}
|
|
17799
18295
|
};
|
|
18296
|
+
async function openSessionForAppend(file) {
|
|
18297
|
+
const handle = await fsp17.open(file, "a+", 384);
|
|
18298
|
+
try {
|
|
18299
|
+
const stat10 = await handle.stat();
|
|
18300
|
+
if (stat10.size > 0) {
|
|
18301
|
+
const tail = Buffer.allocUnsafe(1);
|
|
18302
|
+
const { bytesRead } = await handle.read(tail, 0, 1, stat10.size - 1);
|
|
18303
|
+
if (bytesRead === 1 && tail[0] !== 10) {
|
|
18304
|
+
await handle.appendFile("\n", "utf8");
|
|
18305
|
+
}
|
|
18306
|
+
}
|
|
18307
|
+
return handle;
|
|
18308
|
+
} catch (err) {
|
|
18309
|
+
await handle.close().catch(() => void 0);
|
|
18310
|
+
throw err;
|
|
18311
|
+
}
|
|
18312
|
+
}
|
|
17800
18313
|
|
|
17801
18314
|
// src/coordination/director-session.ts
|
|
17802
18315
|
function makeDirectorSessionFactory(opts) {
|
|
@@ -17806,9 +18319,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
17806
18319
|
let dir;
|
|
17807
18320
|
if (opts.store) {
|
|
17808
18321
|
store = opts.store;
|
|
17809
|
-
dir = opts.sessionsRoot ?
|
|
18322
|
+
dir = opts.sessionsRoot ? path30.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
17810
18323
|
} else if (opts.sessionsRoot) {
|
|
17811
|
-
dir =
|
|
18324
|
+
dir = path30.join(opts.sessionsRoot, runId);
|
|
17812
18325
|
store = new DefaultSessionStore({ dir });
|
|
17813
18326
|
} else {
|
|
17814
18327
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -17832,7 +18345,7 @@ function makeDirectorSessionFactory(opts) {
|
|
|
17832
18345
|
}
|
|
17833
18346
|
async function readDirectorSubagentSession(args) {
|
|
17834
18347
|
if (!args.sessionsRoot) return null;
|
|
17835
|
-
const filePath =
|
|
18348
|
+
const filePath = path30.join(args.sessionsRoot, args.directorRunId, `${args.subagentId}.jsonl`);
|
|
17836
18349
|
let raw;
|
|
17837
18350
|
try {
|
|
17838
18351
|
raw = await fsp18.readFile(filePath, "utf8");
|
|
@@ -18286,12 +18799,97 @@ var FallbackProfileManager = class {
|
|
|
18286
18799
|
return this.resolveRefs([ref], exclude);
|
|
18287
18800
|
}
|
|
18288
18801
|
/**
|
|
18289
|
-
* Resolve every usable configured target as
|
|
18802
|
+
* Resolve every usable configured target as a bounded last-resort chain.
|
|
18290
18803
|
* Normal smart defaults stay bounded; callers append this only after the
|
|
18291
|
-
* preferred chain and only when automatic fallback is enabled.
|
|
18804
|
+
* preferred chain and only when automatic fallback is enabled. The cap
|
|
18805
|
+
* ({@link MAX_LAST_RESORT_CANDIDATES}) prevents a config with many providers
|
|
18806
|
+
* from producing a degenerate chain of doomed requests during a systemic
|
|
18807
|
+
* outage — by this point the smart default, bridge, and default profile
|
|
18808
|
+
* have already failed.
|
|
18292
18809
|
*/
|
|
18293
18810
|
resolveAllConfigured(exclude) {
|
|
18294
|
-
return this.smartDefault(exclude,
|
|
18811
|
+
return this.smartDefault(exclude, this.lastResortCap());
|
|
18812
|
+
}
|
|
18813
|
+
/**
|
|
18814
|
+
* Effective cap for the last-resort append. Reads the user-configurable
|
|
18815
|
+
* {@link Config.fallbackMaxLastResortCandidates} when set and valid;
|
|
18816
|
+
* otherwise falls back to the compiled-in default
|
|
18817
|
+
* {@link MAX_LAST_RESORT_CANDIDATES}.
|
|
18818
|
+
*/
|
|
18819
|
+
lastResortCap() {
|
|
18820
|
+
const configured = this.config.fallbackMaxLastResortCandidates;
|
|
18821
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured >= 0) {
|
|
18822
|
+
return Math.floor(configured);
|
|
18823
|
+
}
|
|
18824
|
+
return MAX_LAST_RESORT_CANDIDATES;
|
|
18825
|
+
}
|
|
18826
|
+
/**
|
|
18827
|
+
* Build the complete fallback candidate chain shared by the agent-loop
|
|
18828
|
+
* extension and the one-shot orchestrator. Centralizes the bridge → primary →
|
|
18829
|
+
* selected → default-profile → all-configured ladder and the fromExplicitSource
|
|
18830
|
+
* gate so both consumers produce identical ordering and depth semantics.
|
|
18831
|
+
*
|
|
18832
|
+
* Layering (each step deduped against all prior):
|
|
18833
|
+
* 1. Bridge (emergency continuity route).
|
|
18834
|
+
* 2. Configured primary, when the live context drifted from it.
|
|
18835
|
+
* 3. The selected chain (explicit refs → named profile → smart default),
|
|
18836
|
+
* via {@link resolveEffective}.
|
|
18837
|
+
* 4. The "default" profile — extra depth, ONLY when the chain was auto-derived.
|
|
18838
|
+
* 5. Every other configured provider — last resort, ONLY when the chain was
|
|
18839
|
+
* auto-derived AND `effectiveFallbackAuto` is true.
|
|
18840
|
+
*
|
|
18841
|
+
* Returns the empty chain when `closedWorld` is true and no explicit
|
|
18842
|
+
* refs/profile resolved — a model allowlist never leaks to unlisted models.
|
|
18843
|
+
*
|
|
18844
|
+
* @internal caller-aware options (primary, closedWorld) are accepted because
|
|
18845
|
+
* the agent loop has context the manager does not own; the resolution
|
|
18846
|
+
* pipeline itself is identical for both callers.
|
|
18847
|
+
*/
|
|
18848
|
+
resolveCandidates(current, opts = {}) {
|
|
18849
|
+
const configuredPrimary = opts.primary ?? {
|
|
18850
|
+
providerId: this.config.provider,
|
|
18851
|
+
model: this.config.model
|
|
18852
|
+
};
|
|
18853
|
+
const configFallbackAuto = this.config.fallbackAuto;
|
|
18854
|
+
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
18855
|
+
const explicitRefs = opts.fallbackModels ?? this.config.fallbackModels;
|
|
18856
|
+
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
18857
|
+
const profileUsable = opts.fallbackProfile !== void 0 && this.hasProfile(opts.fallbackProfile) && this.resolve(opts.fallbackProfile, { exclude: current }).length > 0;
|
|
18858
|
+
const fromExplicitSource = explicitUsable || profileUsable;
|
|
18859
|
+
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) : opts.fallbackProfile ? this.resolve(opts.fallbackProfile, { exclude: current }) : FREEZER_EMPTY : this.resolveEffective({
|
|
18860
|
+
fallbackModels: explicitRefs,
|
|
18861
|
+
fallbackProfile: opts.fallbackProfile,
|
|
18862
|
+
fallbackAuto: effectiveFallbackAuto,
|
|
18863
|
+
exclude: current
|
|
18864
|
+
});
|
|
18865
|
+
const candidates = [];
|
|
18866
|
+
if (opts.closedWorld) {
|
|
18867
|
+
candidates.push(...selectedChain);
|
|
18868
|
+
} else {
|
|
18869
|
+
candidates.push(...this.resolveBridge(current));
|
|
18870
|
+
if (!(configuredPrimary.providerId === current.providerId && configuredPrimary.model === current.model)) {
|
|
18871
|
+
candidates.push({
|
|
18872
|
+
providerId: configuredPrimary.providerId,
|
|
18873
|
+
model: configuredPrimary.model,
|
|
18874
|
+
providerSwitched: configuredPrimary.providerId !== current.providerId
|
|
18875
|
+
});
|
|
18876
|
+
}
|
|
18877
|
+
candidates.push(...selectedChain);
|
|
18878
|
+
if (!fromExplicitSource && effectiveFallbackAuto && opts.fallbackProfile !== "default") {
|
|
18879
|
+
candidates.push(...this.resolve("default", { exclude: current }));
|
|
18880
|
+
}
|
|
18881
|
+
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
18882
|
+
const cap = this.lastResortCap();
|
|
18883
|
+
if (cap > 0) {
|
|
18884
|
+
const usedKeys = new Set(
|
|
18885
|
+
candidates.map((c) => `${c.providerId}/${c.model}`)
|
|
18886
|
+
);
|
|
18887
|
+
const lastResort = this.smartDefault(current, Number.POSITIVE_INFINITY).filter((c) => !usedKeys.has(`${c.providerId}/${c.model}`)).slice(0, cap);
|
|
18888
|
+
candidates.push(...lastResort);
|
|
18889
|
+
}
|
|
18890
|
+
}
|
|
18891
|
+
}
|
|
18892
|
+
return dedupeChain(candidates, current);
|
|
18295
18893
|
}
|
|
18296
18894
|
// ── Provider availability (read-only) ──────────────────────────────────
|
|
18297
18895
|
checkProvider(providerId) {
|
|
@@ -18415,7 +19013,20 @@ var FallbackProfileManager = class {
|
|
|
18415
19013
|
);
|
|
18416
19014
|
}
|
|
18417
19015
|
};
|
|
19016
|
+
var MAX_LAST_RESORT_CANDIDATES = 12;
|
|
18418
19017
|
var FREEZER_EMPTY = Object.freeze([]);
|
|
19018
|
+
function dedupeChain(entries, current) {
|
|
19019
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19020
|
+
const currentKey = `${current.providerId}/${current.model}`;
|
|
19021
|
+
return Object.freeze(
|
|
19022
|
+
entries.filter((entry) => {
|
|
19023
|
+
const key = `${entry.providerId}/${entry.model}`;
|
|
19024
|
+
if (key === currentKey || seen.has(key)) return false;
|
|
19025
|
+
seen.add(key);
|
|
19026
|
+
return true;
|
|
19027
|
+
})
|
|
19028
|
+
);
|
|
19029
|
+
}
|
|
18419
19030
|
|
|
18420
19031
|
// src/core/fallback-model.ts
|
|
18421
19032
|
function fallbackProfileChain(config, profileName) {
|
|
@@ -21054,7 +21665,7 @@ var Director = class _Director {
|
|
|
21054
21665
|
// src/coordination/fleet-manager.ts
|
|
21055
21666
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
21056
21667
|
import * as fsp20 from "node:fs/promises";
|
|
21057
|
-
import * as
|
|
21668
|
+
import * as path31 from "node:path";
|
|
21058
21669
|
var FleetManager = class {
|
|
21059
21670
|
/** The fleet-wide event bus. */
|
|
21060
21671
|
fleet;
|
|
@@ -21387,7 +21998,7 @@ var FleetManager = class {
|
|
|
21387
21998
|
})),
|
|
21388
21999
|
usage: this.usage.snapshot()
|
|
21389
22000
|
};
|
|
21390
|
-
await fsp20.mkdir(
|
|
22001
|
+
await fsp20.mkdir(path31.dirname(this.manifestPath), { recursive: true });
|
|
21391
22002
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
21392
22003
|
return this.manifestPath;
|
|
21393
22004
|
}
|
|
@@ -21550,7 +22161,7 @@ var FleetManager = class {
|
|
|
21550
22161
|
};
|
|
21551
22162
|
|
|
21552
22163
|
// src/coordination/remote-mailbox.ts
|
|
21553
|
-
import * as
|
|
22164
|
+
import * as path35 from "node:path";
|
|
21554
22165
|
|
|
21555
22166
|
// src/coordination/mailbox-constants.ts
|
|
21556
22167
|
var HQ_MAILBOX_SNAPSHOT_MIN_INTERVAL_MS = 1e4;
|
|
@@ -21560,13 +22171,13 @@ var UNREAD_CHECK_MIN_INTERVAL_MS = 1e3;
|
|
|
21560
22171
|
import { spawn as spawn5 } from "node:child_process";
|
|
21561
22172
|
import * as fs4 from "node:fs";
|
|
21562
22173
|
import * as net2 from "node:net";
|
|
21563
|
-
import * as
|
|
22174
|
+
import * as path33 from "node:path";
|
|
21564
22175
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
21565
22176
|
|
|
21566
22177
|
// src/coordination/mailbox-project-server-endpoint.ts
|
|
21567
22178
|
import { createHash as createHash6 } from "node:crypto";
|
|
21568
22179
|
import * as os3 from "node:os";
|
|
21569
|
-
import * as
|
|
22180
|
+
import * as path32 from "node:path";
|
|
21570
22181
|
|
|
21571
22182
|
// src/coordination/mailbox-project-server-protocol.ts
|
|
21572
22183
|
var MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION = 4;
|
|
@@ -21599,7 +22210,7 @@ function encodeMailboxProjectServerMessage(message) {
|
|
|
21599
22210
|
// src/coordination/mailbox-project-server-endpoint.ts
|
|
21600
22211
|
var MAILBOX_PROJECT_SERVER_METADATA_FILE = ".mailbox-server.json";
|
|
21601
22212
|
function normalizeLocalPath(value) {
|
|
21602
|
-
const resolved =
|
|
22213
|
+
const resolved = path32.resolve(value);
|
|
21603
22214
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
21604
22215
|
}
|
|
21605
22216
|
function mailboxProjectServerKey(projectDir) {
|
|
@@ -21610,14 +22221,14 @@ function mailboxProjectServerEndpoint(projectDir) {
|
|
|
21610
22221
|
if (process.platform === "win32") {
|
|
21611
22222
|
return `\\\\.\\pipe\\wrongstack-mailbox-v${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
21612
22223
|
}
|
|
21613
|
-
return
|
|
22224
|
+
return path32.join(
|
|
21614
22225
|
os3.tmpdir(),
|
|
21615
22226
|
`wsmb-v${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}`,
|
|
21616
22227
|
`${key}.sock`
|
|
21617
22228
|
);
|
|
21618
22229
|
}
|
|
21619
22230
|
function mailboxProjectServerMetadataPath(projectDir) {
|
|
21620
|
-
return
|
|
22231
|
+
return path32.join(path32.resolve(projectDir), MAILBOX_PROJECT_SERVER_METADATA_FILE);
|
|
21621
22232
|
}
|
|
21622
22233
|
|
|
21623
22234
|
// src/coordination/mailbox-project-server-client.ts
|
|
@@ -21628,7 +22239,7 @@ var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
|
21628
22239
|
var AUTH_RETRY_DELAY_MS = 150;
|
|
21629
22240
|
var AUTH_RETRY_MAX_ATTEMPTS = 13;
|
|
21630
22241
|
function normalizePath(value) {
|
|
21631
|
-
const resolved =
|
|
22242
|
+
const resolved = path33.resolve(value);
|
|
21632
22243
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
21633
22244
|
}
|
|
21634
22245
|
function resolveProjectServerUrl() {
|
|
@@ -21656,7 +22267,7 @@ function isUnauthorizedMailboxError(error) {
|
|
|
21656
22267
|
var MailboxProjectServerConnection = class {
|
|
21657
22268
|
constructor(projectDir) {
|
|
21658
22269
|
this.projectDir = projectDir;
|
|
21659
|
-
this.projectDir =
|
|
22270
|
+
this.projectDir = path33.resolve(projectDir);
|
|
21660
22271
|
this.endpoint = mailboxProjectServerEndpoint(this.projectDir);
|
|
21661
22272
|
this.state = {
|
|
21662
22273
|
status: isMailboxProjectServerAvailable() ? "offline" : "unavailable",
|
|
@@ -22334,9 +22945,9 @@ var CredentialVerifyThrottle = class {
|
|
|
22334
22945
|
var credentialVerifyThrottle = new CredentialVerifyThrottle();
|
|
22335
22946
|
|
|
22336
22947
|
// src/coordination/global-mailbox-paths.ts
|
|
22337
|
-
import * as
|
|
22948
|
+
import * as path34 from "node:path";
|
|
22338
22949
|
function resolveProjectDir(projectRoot, globalRoot) {
|
|
22339
|
-
return
|
|
22950
|
+
return path34.join(globalRoot, "projects", projectSlug(projectRoot));
|
|
22340
22951
|
}
|
|
22341
22952
|
|
|
22342
22953
|
// src/coordination/sqlite-mailbox.ts
|
|
@@ -22386,8 +22997,8 @@ var RemoteMailbox = class {
|
|
|
22386
22997
|
hqPublisher,
|
|
22387
22998
|
eventEmitter
|
|
22388
22999
|
} : optionsOrProjectDir;
|
|
22389
|
-
this.projectDir =
|
|
22390
|
-
this.messagePath =
|
|
23000
|
+
this.projectDir = path35.resolve(options.projectDir);
|
|
23001
|
+
this.messagePath = path35.join(this.projectDir, SQLITE_MAILBOX_FILE);
|
|
22391
23002
|
this.registryPath = this.messagePath;
|
|
22392
23003
|
this.clientRegistryPath = this.messagePath;
|
|
22393
23004
|
this.events = options.events;
|
|
@@ -22633,7 +23244,7 @@ var RemoteMailbox = class {
|
|
|
22633
23244
|
this.hqEventPending.clear();
|
|
22634
23245
|
return;
|
|
22635
23246
|
}
|
|
22636
|
-
const mailboxId = `${
|
|
23247
|
+
const mailboxId = `${path35.basename(this.projectDir)}:mailbox`;
|
|
22637
23248
|
const inFlight = this.query({ includeDeleted: true, limit: 100 }).then((messages) => {
|
|
22638
23249
|
const message = messages.find((candidate) => candidate.id === event.messageId);
|
|
22639
23250
|
const action = event.type === "message.sent" ? "message.sent" : "message.updated";
|
|
@@ -22656,7 +23267,7 @@ var RemoteMailbox = class {
|
|
|
22656
23267
|
if (!publisher || !event.startsWith("mailbox.agent_") && !event.startsWith("mailbox.client_")) {
|
|
22657
23268
|
return;
|
|
22658
23269
|
}
|
|
22659
|
-
const mailboxId = `${
|
|
23270
|
+
const mailboxId = `${path35.basename(this.projectDir)}:mailbox`;
|
|
22660
23271
|
const record = typeof payload === "object" && payload !== null ? payload : {};
|
|
22661
23272
|
const agentId = typeof record["agentId"] === "string" ? record["agentId"] : void 0;
|
|
22662
23273
|
const action = event === "mailbox.agent_registered" ? "agent.registered" : event === "mailbox.agent_heartbeat" ? "agent.heartbeat" : event === "mailbox.agent_deregistered" ? "agent.deregistered" : void 0;
|
|
@@ -22679,7 +23290,7 @@ function createProjectMailbox(options) {
|
|
|
22679
23290
|
return new RemoteMailbox(options);
|
|
22680
23291
|
}
|
|
22681
23292
|
function getSharedProjectMailbox(projectDir, events, hqPublisher) {
|
|
22682
|
-
const key =
|
|
23293
|
+
const key = path35.resolve(projectDir);
|
|
22683
23294
|
let projectCache = sharedRemoteMailboxes.get(key);
|
|
22684
23295
|
if (!projectCache) {
|
|
22685
23296
|
projectCache = {
|
|
@@ -25047,16 +25658,16 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25047
25658
|
throw validationError(`routePath must not start with '?' (got ${JSON.stringify(routePath)})`);
|
|
25048
25659
|
}
|
|
25049
25660
|
const queryIndex = url.indexOf("?");
|
|
25050
|
-
const
|
|
25661
|
+
const path40 = queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
25051
25662
|
if (actor !== void 0) {
|
|
25052
|
-
const requiredCapability = requiredCredentialCapability(method,
|
|
25663
|
+
const requiredCapability = requiredCredentialCapability(method, path40);
|
|
25053
25664
|
if (requiredCapability === void 0) {
|
|
25054
25665
|
writeJson(response, 403, {
|
|
25055
|
-
error: { code: "FORBIDDEN", message: `credential access is not permitted for ${method} ${
|
|
25666
|
+
error: { code: "FORBIDDEN", message: `credential access is not permitted for ${method} ${path40}` }
|
|
25056
25667
|
});
|
|
25057
25668
|
return;
|
|
25058
25669
|
}
|
|
25059
|
-
if (
|
|
25670
|
+
if (path40 !== "/mailbox/send" && !hasMailboxCapability(actor, requiredCapability)) {
|
|
25060
25671
|
writeJson(response, 403, {
|
|
25061
25672
|
error: {
|
|
25062
25673
|
code: "FORBIDDEN",
|
|
@@ -25066,7 +25677,7 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25066
25677
|
return;
|
|
25067
25678
|
}
|
|
25068
25679
|
}
|
|
25069
|
-
if (method === "POST" &&
|
|
25680
|
+
if (method === "POST" && path40 === "/mailbox/send") {
|
|
25070
25681
|
const input = validateSend(await readJsonBody(request, maxBodyBytes), actor?.actorId);
|
|
25071
25682
|
if (actor !== void 0) {
|
|
25072
25683
|
const requiredCapability = requiredSendCapability(input.type);
|
|
@@ -25084,7 +25695,7 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25084
25695
|
writeJson(response, 201, await mailbox.send(input));
|
|
25085
25696
|
return;
|
|
25086
25697
|
}
|
|
25087
|
-
if (method === "POST" &&
|
|
25698
|
+
if (method === "POST" && path40 === "/mailbox/query") {
|
|
25088
25699
|
const queryContext = parseSinceMs(url, defaultMaxAgeMs);
|
|
25089
25700
|
if ("error" in queryContext) {
|
|
25090
25701
|
writeJson(response, 400, { error: queryContext.error });
|
|
@@ -25104,7 +25715,7 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25104
25715
|
writeJson(response, 200, { data: projected, count: projected.length });
|
|
25105
25716
|
return;
|
|
25106
25717
|
}
|
|
25107
|
-
if (method === "POST" &&
|
|
25718
|
+
if (method === "POST" && path40 === "/mailbox/check") {
|
|
25108
25719
|
const queryContext = parseSinceMs(url, defaultMaxAgeMs);
|
|
25109
25720
|
if ("error" in queryContext) {
|
|
25110
25721
|
writeJson(response, 400, { error: queryContext.error });
|
|
@@ -25138,7 +25749,7 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25138
25749
|
writeJson(response, 200, { data: projected, count: projected.length });
|
|
25139
25750
|
return;
|
|
25140
25751
|
}
|
|
25141
|
-
if (method === "POST" &&
|
|
25752
|
+
if (method === "POST" && path40 === "/mailbox/ack") {
|
|
25142
25753
|
const input = validateAck(await readJsonBody(request, maxBodyBytes), actor?.actorId);
|
|
25143
25754
|
if (actor !== void 0) {
|
|
25144
25755
|
input.readerId = actor.actorId;
|
|
@@ -25152,7 +25763,7 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25152
25763
|
writeJson(response, 200, { updated: projectedAck });
|
|
25153
25764
|
return;
|
|
25154
25765
|
}
|
|
25155
|
-
if (method === "POST" &&
|
|
25766
|
+
if (method === "POST" && path40 === "/mailbox/ack-many") {
|
|
25156
25767
|
const input = validateAckMany(await readJsonBody(request, maxBodyBytes), actor?.actorId);
|
|
25157
25768
|
if (actor !== void 0) {
|
|
25158
25769
|
const requestedIds = new Set(input.acks.map((ack) => ack.messageId));
|
|
@@ -25170,54 +25781,54 @@ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, me
|
|
|
25170
25781
|
writeJson(response, 200, { updated: projectedMany, count: projectedMany.length });
|
|
25171
25782
|
return;
|
|
25172
25783
|
}
|
|
25173
|
-
if (method === "POST" &&
|
|
25784
|
+
if (method === "POST" && path40 === "/mailbox/unread-count") {
|
|
25174
25785
|
const body = await readJsonBody(request, maxBodyBytes);
|
|
25175
25786
|
const count = actor === void 0 ? await mailbox.unreadCount(requireString2(body, "forAgentId")) : await unreadCountForActor(mailbox, actor);
|
|
25176
25787
|
writeJson(response, 200, { count });
|
|
25177
25788
|
return;
|
|
25178
25789
|
}
|
|
25179
|
-
if (method === "POST" &&
|
|
25790
|
+
if (method === "POST" && path40 === "/mailbox/agents/register") {
|
|
25180
25791
|
const input = validateAgentRegistration(await readJsonBody(request, maxBodyBytes), actor);
|
|
25181
25792
|
await mailbox.registerAgent(input);
|
|
25182
25793
|
writeJson(response, 200, { ok: true });
|
|
25183
25794
|
return;
|
|
25184
25795
|
}
|
|
25185
|
-
if (method === "POST" &&
|
|
25796
|
+
if (method === "POST" && path40 === "/mailbox/agents/heartbeat") {
|
|
25186
25797
|
const input = validateAgentHeartbeat(await readJsonBody(request, maxBodyBytes), actor?.actorId);
|
|
25187
25798
|
if (actor !== void 0) input.agentId = actor.actorId;
|
|
25188
25799
|
await mailbox.heartbeat(input);
|
|
25189
25800
|
writeJson(response, 200, { ok: true });
|
|
25190
25801
|
return;
|
|
25191
25802
|
}
|
|
25192
|
-
if (method === "POST" &&
|
|
25803
|
+
if (method === "POST" && path40 === "/mailbox/register-client") {
|
|
25193
25804
|
await mailbox.registerClient(
|
|
25194
25805
|
validateClientRegistration(await readJsonBody(request, maxBodyBytes))
|
|
25195
25806
|
);
|
|
25196
25807
|
writeJson(response, 200, { ok: true });
|
|
25197
25808
|
return;
|
|
25198
25809
|
}
|
|
25199
|
-
if (method === "POST" &&
|
|
25810
|
+
if (method === "POST" && path40 === "/mailbox/heartbeat") {
|
|
25200
25811
|
await mailbox.clientHeartbeat(
|
|
25201
25812
|
validateClientHeartbeat(await readJsonBody(request, maxBodyBytes))
|
|
25202
25813
|
);
|
|
25203
25814
|
writeJson(response, 200, { ok: true });
|
|
25204
25815
|
return;
|
|
25205
25816
|
}
|
|
25206
|
-
if (method === "POST" &&
|
|
25817
|
+
if (method === "POST" && path40 === "/mailbox/purge-clients") {
|
|
25207
25818
|
writeJson(response, 200, { ok: true, purged: await mailbox.purgeClients() });
|
|
25208
25819
|
return;
|
|
25209
25820
|
}
|
|
25210
|
-
if (method === "GET" &&
|
|
25821
|
+
if (method === "GET" && path40 === "/mailbox/agents") {
|
|
25211
25822
|
const agents = await mailbox.getAgentStatuses();
|
|
25212
25823
|
writeJson(response, 200, { data: agents, count: agents.length });
|
|
25213
25824
|
return;
|
|
25214
25825
|
}
|
|
25215
|
-
if (method === "GET" &&
|
|
25826
|
+
if (method === "GET" && path40 === "/mailbox/agents/online") {
|
|
25216
25827
|
const agents = await mailbox.getOnlineAgents();
|
|
25217
25828
|
writeJson(response, 200, { data: agents, count: agents.length });
|
|
25218
25829
|
return;
|
|
25219
25830
|
}
|
|
25220
|
-
if (method === "GET" &&
|
|
25831
|
+
if (method === "GET" && path40 === "/mailbox/events" && eventEmitter) {
|
|
25221
25832
|
const queryContext = parseSinceMs(url, defaultMaxAgeMs);
|
|
25222
25833
|
if ("error" in queryContext) {
|
|
25223
25834
|
writeJson(response, 400, { error: queryContext.error });
|
|
@@ -25326,25 +25937,25 @@ function requiredSendCapability(type) {
|
|
|
25326
25937
|
}
|
|
25327
25938
|
return "mail.send.informational";
|
|
25328
25939
|
}
|
|
25329
|
-
function requiredCredentialCapability(method,
|
|
25330
|
-
if (method === "POST" &&
|
|
25331
|
-
if (method === "POST" && (
|
|
25940
|
+
function requiredCredentialCapability(method, path40) {
|
|
25941
|
+
if (method === "POST" && path40 === "/mailbox/send") return "mail.send.informational";
|
|
25942
|
+
if (method === "POST" && (path40 === "/mailbox/query" || path40 === "/mailbox/check")) {
|
|
25332
25943
|
return "mail.read.self";
|
|
25333
25944
|
}
|
|
25334
|
-
if (method === "POST" && (
|
|
25945
|
+
if (method === "POST" && (path40 === "/mailbox/ack" || path40 === "/mailbox/ack-many")) {
|
|
25335
25946
|
return "mail.ack.self";
|
|
25336
25947
|
}
|
|
25337
|
-
if (method === "POST" &&
|
|
25338
|
-
if (method === "POST" &&
|
|
25948
|
+
if (method === "POST" && path40 === "/mailbox/unread-count") return "mail.read.self";
|
|
25949
|
+
if (method === "POST" && path40 === "/mailbox/agents/register") {
|
|
25339
25950
|
return "mail.presence.register.self";
|
|
25340
25951
|
}
|
|
25341
|
-
if (method === "POST" &&
|
|
25952
|
+
if (method === "POST" && path40 === "/mailbox/agents/heartbeat") {
|
|
25342
25953
|
return "mail.presence.heartbeat.self";
|
|
25343
25954
|
}
|
|
25344
|
-
if (method === "GET" && (
|
|
25955
|
+
if (method === "GET" && (path40 === "/mailbox/agents" || path40 === "/mailbox/agents/online")) {
|
|
25345
25956
|
return "mail.presence.read";
|
|
25346
25957
|
}
|
|
25347
|
-
if (method === "GET" &&
|
|
25958
|
+
if (method === "GET" && path40 === "/mailbox/events") return "mail.events.self";
|
|
25348
25959
|
return void 0;
|
|
25349
25960
|
}
|
|
25350
25961
|
function extractEventTimestamp(event) {
|
|
@@ -25547,11 +26158,11 @@ var NULL_FLEET_BUS = new FleetBus();
|
|
|
25547
26158
|
|
|
25548
26159
|
// src/coordination/package-author-tracker.ts
|
|
25549
26160
|
import * as fs5 from "node:fs/promises";
|
|
25550
|
-
import * as
|
|
26161
|
+
import * as path36 from "node:path";
|
|
25551
26162
|
var DEFAULT_MAX_ENTRIES2 = 1e4;
|
|
25552
26163
|
var LOG_FILENAME2 = "package-authors.json";
|
|
25553
26164
|
function logPath2(storageDir) {
|
|
25554
|
-
return
|
|
26165
|
+
return path36.join(storageDir, LOG_FILENAME2);
|
|
25555
26166
|
}
|
|
25556
26167
|
async function loadLog2(storageDir, projectRoot) {
|
|
25557
26168
|
try {
|
|
@@ -25573,7 +26184,7 @@ async function saveLog2(storageDir, log) {
|
|
|
25573
26184
|
`);
|
|
25574
26185
|
}
|
|
25575
26186
|
function detectEcosystem(manifestPath) {
|
|
25576
|
-
const name =
|
|
26187
|
+
const name = path36.win32.basename(manifestPath).toLowerCase();
|
|
25577
26188
|
if (name === "package.json") return "npm";
|
|
25578
26189
|
if (name === "go.mod") return "go";
|
|
25579
26190
|
if (name === "cargo.toml") return "cargo";
|
|
@@ -26456,12 +27067,12 @@ import { randomBytes as randomBytes2 } from "node:crypto";
|
|
|
26456
27067
|
import * as fsp21 from "node:fs/promises";
|
|
26457
27068
|
import { execFile } from "node:child_process";
|
|
26458
27069
|
import * as os4 from "node:os";
|
|
26459
|
-
import * as
|
|
27070
|
+
import * as path37 from "node:path";
|
|
26460
27071
|
var MAILBOX_BRIDGE_LOCK_FILENAME = ".mailbox-bridge.lock";
|
|
26461
27072
|
var MAILBOX_BRIDGE_TOKEN_FILENAME = ".mailbox.token";
|
|
26462
27073
|
async function acquireOrJoin(opts) {
|
|
26463
|
-
const lockPath =
|
|
26464
|
-
const tokenPath =
|
|
27074
|
+
const lockPath = path37.join(opts.projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
27075
|
+
const tokenPath = path37.join(opts.projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
26465
27076
|
const inspected = await readLockForInspection(lockPath);
|
|
26466
27077
|
if (inspected.kind === "live") {
|
|
26467
27078
|
const existing = inspected.lock;
|
|
@@ -26489,8 +27100,8 @@ async function acquireOrJoin(opts) {
|
|
|
26489
27100
|
return { kind: "acquired", lock: tentative, tokenPath };
|
|
26490
27101
|
}
|
|
26491
27102
|
async function finalize(projectDir, tentative, boundPort) {
|
|
26492
|
-
const lockPath =
|
|
26493
|
-
const tokenPath =
|
|
27103
|
+
const lockPath = path37.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
27104
|
+
const tokenPath = path37.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
26494
27105
|
const finalized = {
|
|
26495
27106
|
...tentative,
|
|
26496
27107
|
port: boundPort,
|
|
@@ -26501,8 +27112,8 @@ async function finalize(projectDir, tentative, boundPort) {
|
|
|
26501
27112
|
return finalized;
|
|
26502
27113
|
}
|
|
26503
27114
|
async function release(projectDir, generation) {
|
|
26504
|
-
const lockPath =
|
|
26505
|
-
const tokenPath =
|
|
27115
|
+
const lockPath = path37.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
27116
|
+
const tokenPath = path37.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
26506
27117
|
try {
|
|
26507
27118
|
const raw = await fsp21.readFile(lockPath, "utf-8");
|
|
26508
27119
|
const parsed = JSON.parse(raw);
|
|
@@ -26536,7 +27147,7 @@ async function readLockForInspection(lockPath) {
|
|
|
26536
27147
|
return { kind: "live", lock: parsed };
|
|
26537
27148
|
}
|
|
26538
27149
|
async function readLiveLock(projectDir) {
|
|
26539
|
-
const lockPath =
|
|
27150
|
+
const lockPath = path37.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
26540
27151
|
const result = await readLockForInspection(lockPath);
|
|
26541
27152
|
if (result.kind === "live") {
|
|
26542
27153
|
return { kind: "live", lock: result.lock };
|
|
@@ -26547,7 +27158,7 @@ async function readLiveLock(projectDir) {
|
|
|
26547
27158
|
return { kind: "absent" };
|
|
26548
27159
|
}
|
|
26549
27160
|
async function atomicWriteJson(targetPath, value) {
|
|
26550
|
-
const dir =
|
|
27161
|
+
const dir = path37.dirname(targetPath);
|
|
26551
27162
|
await fsp21.mkdir(dir, { recursive: true });
|
|
26552
27163
|
const tmp = `${targetPath}.tmp.${process.pid}.${randomBytes2(4).toString("hex")}`;
|
|
26553
27164
|
const body = JSON.stringify(value, null, 2) + "\n";
|
|
@@ -26710,8 +27321,8 @@ function extractManifestPath(msg) {
|
|
|
26710
27321
|
}
|
|
26711
27322
|
return void 0;
|
|
26712
27323
|
}
|
|
26713
|
-
function isManifestFile(
|
|
26714
|
-
const name = pathBasename(
|
|
27324
|
+
function isManifestFile(path40) {
|
|
27325
|
+
const name = pathBasename(path40).toLowerCase();
|
|
26715
27326
|
const manifests = [
|
|
26716
27327
|
"package.json",
|
|
26717
27328
|
"package-lock.json",
|
|
@@ -27005,7 +27616,7 @@ var AdaptiveConcurrencyController = class {
|
|
|
27005
27616
|
// src/coordination/agent-monitor.ts
|
|
27006
27617
|
import { createReadStream as createReadStream5 } from "node:fs";
|
|
27007
27618
|
import * as fs6 from "node:fs/promises";
|
|
27008
|
-
import * as
|
|
27619
|
+
import * as path38 from "node:path";
|
|
27009
27620
|
import { createInterface as createInterface5 } from "node:readline";
|
|
27010
27621
|
var AgentMonitorService = class _AgentMonitorService {
|
|
27011
27622
|
_fleetBus;
|
|
@@ -27109,7 +27720,7 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
27109
27720
|
return this.getAllSessions();
|
|
27110
27721
|
}
|
|
27111
27722
|
async _readTranscriptFile(subagentId) {
|
|
27112
|
-
const file =
|
|
27723
|
+
const file = path38.join(this._transcriptsDir, subagentId, "transcript.jsonl");
|
|
27113
27724
|
const accessible = await fs6.access(file).then(() => true).catch(() => false);
|
|
27114
27725
|
if (!accessible) return [];
|
|
27115
27726
|
const out = [];
|
|
@@ -27450,12 +28061,12 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
27450
28061
|
this._writeDrain = drain;
|
|
27451
28062
|
}
|
|
27452
28063
|
async _appendToFile(subagentId, line) {
|
|
27453
|
-
const dir =
|
|
28064
|
+
const dir = path38.join(this._transcriptsDir, subagentId);
|
|
27454
28065
|
if (!this._ensuredDirs.has(dir)) {
|
|
27455
28066
|
await fs6.mkdir(dir, { recursive: true });
|
|
27456
28067
|
this._ensuredDirs.add(dir);
|
|
27457
28068
|
}
|
|
27458
|
-
const filePath =
|
|
28069
|
+
const filePath = path38.join(dir, "transcript.jsonl");
|
|
27459
28070
|
try {
|
|
27460
28071
|
await fs6.appendFile(filePath, line, { encoding: "utf8", mode: SECRET_FILE_MODE });
|
|
27461
28072
|
} catch (error) {
|
|
@@ -27848,7 +28459,7 @@ import { randomUUID as randomUUID20 } from "node:crypto";
|
|
|
27848
28459
|
// src/coordination/knowledge-graph.ts
|
|
27849
28460
|
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
27850
28461
|
import * as fsp22 from "node:fs/promises";
|
|
27851
|
-
import * as
|
|
28462
|
+
import * as path39 from "node:path";
|
|
27852
28463
|
var DEFAULT_MAX_NODES = 2e3;
|
|
27853
28464
|
var MAX_SUBSCRIPTIONS = 1e3;
|
|
27854
28465
|
var MAX_PENDING_DELIVERIES_PER_SUBSCRIPTION = 1e3;
|
|
@@ -27883,8 +28494,8 @@ var KnowledgeGraph = class _KnowledgeGraph {
|
|
|
27883
28494
|
return this.index;
|
|
27884
28495
|
}
|
|
27885
28496
|
constructor(sessionDir, maxNodes = DEFAULT_MAX_NODES, compactEveryWrites = Math.max(1e3, maxNodes * 4)) {
|
|
27886
|
-
this.filePath =
|
|
27887
|
-
this.graphFilePath =
|
|
28497
|
+
this.filePath = path39.join(sessionDir, "_knowledge_graph");
|
|
28498
|
+
this.graphFilePath = path39.join(this.filePath, "graph.jsonl");
|
|
27888
28499
|
this.maxNodes = maxNodes;
|
|
27889
28500
|
this.compactEveryWrites = Math.max(1, Math.floor(compactEveryWrites));
|
|
27890
28501
|
}
|
|
@@ -30285,6 +30896,8 @@ export {
|
|
|
30285
30896
|
DEFAULT_SUBAGENT_BASELINE,
|
|
30286
30897
|
DELIVERY_AGENTS,
|
|
30287
30898
|
DEPENDENCY_FILE_PATTERNS,
|
|
30899
|
+
DIRECTIVE_QUARANTINE_MAX_UTILITY,
|
|
30900
|
+
DIRECTIVE_QUARANTINE_MIN_APPLIED,
|
|
30288
30901
|
DISCOVERY_AGENTS,
|
|
30289
30902
|
DOMAIN_AGENTS,
|
|
30290
30903
|
DefaultBrainArbiter,
|
|
@@ -30380,6 +30993,7 @@ export {
|
|
|
30380
30993
|
collabPauseMiddleware,
|
|
30381
30994
|
composeDirectorPrompt,
|
|
30382
30995
|
composeSubagentPrompt,
|
|
30996
|
+
consolidatedDocumentPath,
|
|
30383
30997
|
createAgentMonitorService,
|
|
30384
30998
|
createDelegateTool,
|
|
30385
30999
|
createLedgerGuardBrainArbiter,
|
|
@@ -30392,6 +31006,9 @@ export {
|
|
|
30392
31006
|
credentialVerifyThrottle,
|
|
30393
31007
|
detectEcosystem,
|
|
30394
31008
|
detectLearnedConflicts,
|
|
31009
|
+
directiveTrials,
|
|
31010
|
+
directiveUtility,
|
|
31011
|
+
directiveWasApplied,
|
|
30395
31012
|
dispatchAgent,
|
|
30396
31013
|
evaluateAutoOptimize,
|
|
30397
31014
|
finalize,
|
|
@@ -30409,6 +31026,7 @@ export {
|
|
|
30409
31026
|
isMailboxLeader,
|
|
30410
31027
|
isMailboxMessageVisibleTo,
|
|
30411
31028
|
isMailboxProjectServerAvailable,
|
|
31029
|
+
isProvenDirective,
|
|
30412
31030
|
isValidMatrixKey,
|
|
30413
31031
|
listProjectAgentLearnedEntries,
|
|
30414
31032
|
listProjectAgentRoles,
|
|
@@ -30456,10 +31074,13 @@ export {
|
|
|
30456
31074
|
parseMailboxQueryInput,
|
|
30457
31075
|
parseMailboxSendInput,
|
|
30458
31076
|
phaseForRole,
|
|
31077
|
+
quarantinePath,
|
|
30459
31078
|
rankRoleSkills,
|
|
30460
31079
|
readLiveLock,
|
|
31080
|
+
readQuarantinedDirectives,
|
|
30461
31081
|
readRawLearnedEntries,
|
|
30462
31082
|
readSubagentStructuredReport,
|
|
31083
|
+
recordDirectiveOutcomes,
|
|
30463
31084
|
recordFileAction,
|
|
30464
31085
|
recordPackageAction,
|
|
30465
31086
|
recordSkillLoad,
|
|
@@ -30482,6 +31103,7 @@ export {
|
|
|
30482
31103
|
resolveRoleSkillCandidates,
|
|
30483
31104
|
resolveSubagentModelTarget,
|
|
30484
31105
|
resolveSubagentWorktreeDecision,
|
|
31106
|
+
retiredDirectivesToWarnAbout,
|
|
30485
31107
|
roleNeedsIndependentReviewModel,
|
|
30486
31108
|
rosterSummaryFromConfigs,
|
|
30487
31109
|
routeDirectiveToSkill,
|
|
@@ -30489,6 +31111,8 @@ export {
|
|
|
30489
31111
|
saveProjectAgentConsolidated,
|
|
30490
31112
|
saveProjectSkillAugmentation,
|
|
30491
31113
|
scoreAgents,
|
|
31114
|
+
scoreSkillAffinity,
|
|
31115
|
+
scrubRetiredLines,
|
|
30492
31116
|
sessionRecipient,
|
|
30493
31117
|
setSkillPinned,
|
|
30494
31118
|
slugifyProjectAgentRole,
|