@wrongstack/core 0.289.0 → 0.291.1
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/boot.d.ts.map +1 -1
- package/dist/chronicle/domain-adapter.d.ts.map +1 -1
- package/dist/chronicle/index.d.ts +1 -1
- package/dist/chronicle/index.d.ts.map +1 -1
- package/dist/chronicle/index.js +964 -245
- package/dist/chronicle/index.js.map +3 -3
- package/dist/chronicle/journal.d.ts +35 -3
- package/dist/chronicle/journal.d.ts.map +1 -1
- package/dist/chronicle/query.d.ts +8 -5
- package/dist/chronicle/query.d.ts.map +1 -1
- package/dist/chronicle/tool-adapter.d.ts.map +1 -1
- package/dist/coordination/agent-subagent-runner.d.ts +6 -0
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +2 -1
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/collab-debug.d.ts +0 -9
- package/dist/coordination/collab-debug.d.ts.map +1 -1
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
- package/dist/coordination/dep-watcher.d.ts +2 -1
- package/dist/coordination/dep-watcher.d.ts.map +1 -1
- package/dist/coordination/director/director-collab.d.ts +1 -1
- package/dist/coordination/director/director-collab.d.ts.map +1 -1
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +19 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-event-validation.d.ts +0 -7
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/fleet-supervisor.d.ts +1 -1
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +0 -44
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -2
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +733 -111
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +2 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/large-answer-store.d.ts +0 -6
- package/dist/coordination/large-answer-store.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +35 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-tool.d.ts +1 -1
- package/dist/coordination/mailbox-tool.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +151 -0
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/provider-status-tracker.d.ts +27 -2
- package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-budget.d.ts +2 -1
- package/dist/coordination/subagent-budget.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +2 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/fallback-profile-manager.d.ts +1 -1
- package/dist/core/fallback-profile-manager.d.ts.map +1 -1
- package/dist/core/fleet-pulse.d.ts +2 -1
- package/dist/core/fleet-pulse.d.ts.map +1 -1
- package/dist/core/iteration-limit.d.ts +2 -1
- package/dist/core/iteration-limit.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +11 -1
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/model-availability-calendar.d.ts +27 -0
- package/dist/core/model-availability-calendar.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +2 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.js +1459 -560
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +3 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +8 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +2 -2
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.js +643 -134
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +0 -16
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts.map +1 -1
- package/dist/execution/retry-policy.d.ts +0 -13
- package/dist/execution/retry-policy.d.ts.map +1 -1
- package/dist/execution/skill-loader.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/index.js +39 -1
- package/dist/hq/index.js.map +3 -3
- package/dist/index.d.ts +32 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22263 -19999
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +59 -5
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/agent-events.d.ts +2 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -1
- package/dist/kernel/events/file-events.d.ts +46 -0
- package/dist/kernel/events/file-events.d.ts.map +1 -1
- package/dist/kernel/events/memory-events.d.ts +9 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/provider-events.d.ts +2 -1
- package/dist/kernel/events/provider-events.d.ts.map +1 -1
- package/dist/kernel/events/tool-events.d.ts +25 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/plugins/auto-review-plugin.d.ts +10 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
- package/dist/plugins/chimera-plugin.d.ts +5 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +317 -9
- package/dist/security/index.js.map +4 -4
- package/dist/security/kanban-boundary.d.ts +10 -0
- package/dist/security/kanban-boundary.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +8 -1
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/storage/config-loader.d.ts +15 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +627 -428
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +49 -4
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/memory-graph-backend.d.ts +11 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -1
- package/dist/storage/provider-config-watcher.d.ts +17 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-event-bridge.d.ts.map +1 -1
- package/dist/tools/fallback-manage-tools.d.ts.map +1 -1
- package/dist/tools/index.js +166 -26
- package/dist/tools/index.js.map +3 -3
- package/dist/types/config.d.ts +12 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/file-event-record.d.ts +68 -0
- package/dist/types/file-event-record.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/permission.d.ts +38 -0
- package/dist/types/permission.d.ts.map +1 -1
- package/dist/types/provider.d.ts +1 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/session.d.ts +27 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/utils/config-backup.d.ts +20 -0
- package/dist/utils/config-backup.d.ts.map +1 -0
- package/dist/utils/index.d.ts +12 -11
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +436 -290
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +12 -0
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.d.ts.map +1 -1
- package/dist/utils/term.d.ts +6 -0
- package/dist/utils/term.d.ts.map +1 -1
- package/dist/utils/tool-wire-compact.d.ts +9 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +21 -1
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/instructions/agents/ios.md +113 -0
- package/instructions/llm/memory-consolidator.md +4 -12
- package/instructions/system.md +4 -1
- package/package.json +3 -3
- package/skills/auto-review/SKILL.md +176 -0
- package/skills/mnemosyne/SKILL.md +156 -0
- package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
package/dist/defaults/index.js
CHANGED
|
@@ -36,8 +36,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
let mode;
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
40
|
-
mode =
|
|
39
|
+
const stat13 = await fs.stat(targetPath);
|
|
40
|
+
mode = stat13.mode & 511;
|
|
41
41
|
} catch {
|
|
42
42
|
mode = opts.mode;
|
|
43
43
|
}
|
|
@@ -90,8 +90,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
90
90
|
}
|
|
91
91
|
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
92
92
|
try {
|
|
93
|
-
const
|
|
94
|
-
if (Date.now() -
|
|
93
|
+
const stat13 = await fs.stat(lockPath);
|
|
94
|
+
if (Date.now() - stat13.mtimeMs > staleMs) {
|
|
95
95
|
await fs.unlink(lockPath);
|
|
96
96
|
continue;
|
|
97
97
|
}
|
|
@@ -127,13 +127,13 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
127
127
|
const parentDir = path.dirname(lockPath);
|
|
128
128
|
const lockName = path.basename(lockPath);
|
|
129
129
|
const intervalMs = Math.min(remainingMs, 100);
|
|
130
|
-
return new Promise((
|
|
130
|
+
return new Promise((resolve14) => {
|
|
131
131
|
let settled = false;
|
|
132
132
|
let watcher = null;
|
|
133
133
|
const timer = setTimeout(() => {
|
|
134
134
|
settled = true;
|
|
135
135
|
watcher?.close();
|
|
136
|
-
|
|
136
|
+
resolve14();
|
|
137
137
|
}, intervalMs);
|
|
138
138
|
try {
|
|
139
139
|
watcher = watchDir(parentDir, (eventType, filename) => {
|
|
@@ -142,14 +142,14 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
142
142
|
settled = true;
|
|
143
143
|
clearTimeout(timer);
|
|
144
144
|
watcher?.close();
|
|
145
|
-
|
|
145
|
+
resolve14();
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
} catch {
|
|
149
149
|
clearTimeout(timer);
|
|
150
150
|
if (!settled) {
|
|
151
151
|
settled = true;
|
|
152
|
-
setTimeout(
|
|
152
|
+
setTimeout(resolve14, Math.min(remainingMs, 25));
|
|
153
153
|
}
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
@@ -161,7 +161,7 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
161
161
|
settled = true;
|
|
162
162
|
clearTimeout(timer);
|
|
163
163
|
watcher?.close();
|
|
164
|
-
|
|
164
|
+
resolve14();
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
);
|
|
@@ -185,7 +185,7 @@ async function renameWithRetry(from, to) {
|
|
|
185
185
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
186
186
|
throw err;
|
|
187
187
|
}
|
|
188
|
-
await new Promise((
|
|
188
|
+
await new Promise((resolve14) => setTimeout(resolve14, delays[i]));
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
throw lastErr;
|
|
@@ -382,6 +382,37 @@ var color = {
|
|
|
382
382
|
bgGreen: wrap("42", "49")
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
// src/utils/config-backup.ts
|
|
386
|
+
import * as fs2 from "node:fs/promises";
|
|
387
|
+
import * as path2 from "node:path";
|
|
388
|
+
function configHistoryDir(globalRoot) {
|
|
389
|
+
return path2.join(globalRoot, "config-history");
|
|
390
|
+
}
|
|
391
|
+
function configSlug(absolutePath, globalRoot) {
|
|
392
|
+
const rel = path2.relative(globalRoot, absolutePath);
|
|
393
|
+
const normalized = rel.replace(/\\/g, "/").replace(/\.json$/i, "");
|
|
394
|
+
return normalized.replace(/\//g, "-");
|
|
395
|
+
}
|
|
396
|
+
async function backupConfigFile(filePath, paths) {
|
|
397
|
+
let currentContent;
|
|
398
|
+
try {
|
|
399
|
+
currentContent = await fs2.readFile(filePath, "utf8");
|
|
400
|
+
if (!currentContent.trim()) return;
|
|
401
|
+
} catch {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const now = /* @__PURE__ */ new Date();
|
|
405
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
406
|
+
const slug = configSlug(filePath, paths.globalRoot);
|
|
407
|
+
const backupDir = configHistoryDir(paths.globalRoot);
|
|
408
|
+
const backupFile = path2.join(backupDir, `${slug}-${ts}.json`);
|
|
409
|
+
try {
|
|
410
|
+
await fs2.mkdir(backupDir, { recursive: true });
|
|
411
|
+
await fs2.writeFile(backupFile, currentContent, { mode: 384, encoding: "utf8" });
|
|
412
|
+
} catch {
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
385
416
|
// src/utils/context-evidence.ts
|
|
386
417
|
var MAX_DIGEST_CHARS = 4e3;
|
|
387
418
|
function createContextEvidenceState() {
|
|
@@ -631,8 +662,8 @@ async function expandGlob(pattern) {
|
|
|
631
662
|
for (const e of entries) {
|
|
632
663
|
const full = `${dir}${SEP}${e}`;
|
|
633
664
|
try {
|
|
634
|
-
const
|
|
635
|
-
if (
|
|
665
|
+
const stat13 = await fsp.stat(full);
|
|
666
|
+
if (stat13.isDirectory()) await walk3(full, rest);
|
|
636
667
|
} catch {
|
|
637
668
|
}
|
|
638
669
|
}
|
|
@@ -649,8 +680,8 @@ async function expandGlob(pattern) {
|
|
|
649
680
|
if (entries.includes(seg)) {
|
|
650
681
|
const full = `${dir}${SEP}${seg}`;
|
|
651
682
|
try {
|
|
652
|
-
const
|
|
653
|
-
if (
|
|
683
|
+
const stat13 = await fsp.stat(full);
|
|
684
|
+
if (stat13.isDirectory()) await walk3(full, rest);
|
|
654
685
|
} catch {
|
|
655
686
|
}
|
|
656
687
|
}
|
|
@@ -841,10 +872,10 @@ function validateAgainstSchema(value, schema) {
|
|
|
841
872
|
return { ok: errors.length === 0, errors };
|
|
842
873
|
}
|
|
843
874
|
var MAX_SCHEMA_DEPTH = 64;
|
|
844
|
-
function walk(value, schema,
|
|
875
|
+
function walk(value, schema, path30, errors, depth) {
|
|
845
876
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
846
877
|
errors.push({
|
|
847
|
-
path:
|
|
878
|
+
path: path30 || "<root>",
|
|
848
879
|
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
849
880
|
});
|
|
850
881
|
return;
|
|
@@ -852,7 +883,7 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
852
883
|
if (schema.enum !== void 0) {
|
|
853
884
|
if (!enumIncludes(schema.enum, value)) {
|
|
854
885
|
errors.push({
|
|
855
|
-
path:
|
|
886
|
+
path: path30 || "<root>",
|
|
856
887
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
857
888
|
});
|
|
858
889
|
return;
|
|
@@ -861,7 +892,7 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
861
892
|
if (typeof schema.type === "string") {
|
|
862
893
|
if (!checkType(value, schema.type)) {
|
|
863
894
|
errors.push({
|
|
864
|
-
path:
|
|
895
|
+
path: path30 || "<root>",
|
|
865
896
|
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
866
897
|
});
|
|
867
898
|
return;
|
|
@@ -873,7 +904,7 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
873
904
|
if (!(req in obj)) {
|
|
874
905
|
const expected = schema.properties?.[req]?.type;
|
|
875
906
|
errors.push({
|
|
876
|
-
path: joinPath(
|
|
907
|
+
path: joinPath(path30, req),
|
|
877
908
|
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
878
909
|
});
|
|
879
910
|
}
|
|
@@ -881,14 +912,14 @@ function walk(value, schema, path28, errors, depth) {
|
|
|
881
912
|
if (schema.properties) {
|
|
882
913
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
883
914
|
if (key in obj) {
|
|
884
|
-
walk(obj[key], subSchema, joinPath(
|
|
915
|
+
walk(obj[key], subSchema, joinPath(path30, key), errors, depth + 1);
|
|
885
916
|
}
|
|
886
917
|
}
|
|
887
918
|
}
|
|
888
919
|
}
|
|
889
920
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
890
921
|
for (let i = 0; i < value.length; i++) {
|
|
891
|
-
walk(value[i], schema.items, `${
|
|
922
|
+
walk(value[i], schema.items, `${path30}[${i}]`, errors, depth + 1);
|
|
892
923
|
}
|
|
893
924
|
}
|
|
894
925
|
}
|
|
@@ -1168,9 +1199,23 @@ function mapContent(msg, fn) {
|
|
|
1168
1199
|
}
|
|
1169
1200
|
return { ...msg, content: next };
|
|
1170
1201
|
}
|
|
1202
|
+
function hasMeaningfulContent(content) {
|
|
1203
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
1204
|
+
for (const block of content) {
|
|
1205
|
+
if (block.type === "text") {
|
|
1206
|
+
if (block.text.trim().length > 0) return true;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (block.type === "thinking") {
|
|
1210
|
+
if (block.thinking.trim().length > 0 || block.signature) return true;
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
return true;
|
|
1214
|
+
}
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1171
1217
|
function isEmptyMessage(msg) {
|
|
1172
|
-
|
|
1173
|
-
return msg.content.length === 0;
|
|
1218
|
+
return !hasMeaningfulContent(msg.content);
|
|
1174
1219
|
}
|
|
1175
1220
|
|
|
1176
1221
|
// src/utils/regex-guard.ts
|
|
@@ -1211,7 +1256,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
1211
1256
|
|
|
1212
1257
|
// src/utils/safe-json.ts
|
|
1213
1258
|
function safeParse(input, maxBytes = 5e6) {
|
|
1214
|
-
if (input
|
|
1259
|
+
if (Buffer.byteLength(input, "utf8") > maxBytes) {
|
|
1215
1260
|
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
1216
1261
|
}
|
|
1217
1262
|
try {
|
|
@@ -1246,14 +1291,14 @@ function safeStringify(value, pretty = false) {
|
|
|
1246
1291
|
}
|
|
1247
1292
|
|
|
1248
1293
|
// src/utils/session-scoped-path.ts
|
|
1249
|
-
import * as
|
|
1294
|
+
import * as path3 from "node:path";
|
|
1250
1295
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
1251
1296
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
1252
1297
|
throw invalid(sessionId);
|
|
1253
1298
|
}
|
|
1254
|
-
const resolved =
|
|
1255
|
-
const rel =
|
|
1256
|
-
if (rel.startsWith("..") ||
|
|
1299
|
+
const resolved = path3.resolve(dir, `${sessionId}${suffix}`);
|
|
1300
|
+
const rel = path3.relative(path3.resolve(dir), resolved);
|
|
1301
|
+
if (rel.startsWith("..") || path3.isAbsolute(rel)) {
|
|
1257
1302
|
throw invalid(sessionId);
|
|
1258
1303
|
}
|
|
1259
1304
|
return resolved;
|
|
@@ -1267,84 +1312,21 @@ function invalid(sessionId) {
|
|
|
1267
1312
|
});
|
|
1268
1313
|
}
|
|
1269
1314
|
|
|
1315
|
+
// src/utils/sleep.ts
|
|
1316
|
+
function sleep(ms) {
|
|
1317
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1270
1320
|
// src/utils/slug.ts
|
|
1271
1321
|
function slugify(name, fallback = "prompt", maxLen = 64) {
|
|
1272
1322
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
|
|
1273
1323
|
}
|
|
1274
1324
|
|
|
1275
|
-
// src/utils/ulid.ts
|
|
1276
|
-
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1277
|
-
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
1278
|
-
var ENCODING_LEN = ENCODING.length;
|
|
1279
|
-
var TIME_LEN = 10;
|
|
1280
|
-
var RANDOM_LEN = 16;
|
|
1281
|
-
function encodeTime(now, len) {
|
|
1282
|
-
let mod;
|
|
1283
|
-
let str = "";
|
|
1284
|
-
for (let i = len - 1; i >= 0; i--) {
|
|
1285
|
-
mod = now % ENCODING_LEN;
|
|
1286
|
-
str = ENCODING[mod] + str;
|
|
1287
|
-
now = (now - mod) / ENCODING_LEN;
|
|
1288
|
-
}
|
|
1289
|
-
return str;
|
|
1290
|
-
}
|
|
1291
|
-
function encodeRandom(len) {
|
|
1292
|
-
const bytes = randomBytes2(len);
|
|
1293
|
-
let str = "";
|
|
1294
|
-
for (let i = 0; i < len; i++) {
|
|
1295
|
-
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
1296
|
-
}
|
|
1297
|
-
return str;
|
|
1298
|
-
}
|
|
1299
|
-
function ulid(seedTime = Date.now()) {
|
|
1300
|
-
return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
// src/utils/sleep.ts
|
|
1304
|
-
function sleep(ms) {
|
|
1305
|
-
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
1325
|
// src/utils/string.ts
|
|
1309
1326
|
function truncate(s, max) {
|
|
1310
1327
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1311
1328
|
}
|
|
1312
1329
|
|
|
1313
|
-
// src/utils/tool-subject.ts
|
|
1314
|
-
var GLOB_METACHARACTERS = /[*?[\]]/g;
|
|
1315
|
-
function escapeGlobSubject(value) {
|
|
1316
|
-
return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
|
|
1317
|
-
}
|
|
1318
|
-
function normalizePathSubject(value) {
|
|
1319
|
-
return escapeGlobSubject(value.replace(/\\/g, "/"));
|
|
1320
|
-
}
|
|
1321
|
-
function isPathSubjectKey(subjectKey) {
|
|
1322
|
-
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
1323
|
-
}
|
|
1324
|
-
function subjectForToolInput(toolName, input, subjectKey) {
|
|
1325
|
-
if (!input || typeof input !== "object") return void 0;
|
|
1326
|
-
const obj = input;
|
|
1327
|
-
if (subjectKey) {
|
|
1328
|
-
const value = obj[subjectKey];
|
|
1329
|
-
if (typeof value === "string") {
|
|
1330
|
-
return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
if (toolName === "bash" && typeof obj.command === "string") {
|
|
1334
|
-
return escapeGlobSubject(obj.command);
|
|
1335
|
-
}
|
|
1336
|
-
if (typeof obj.path === "string") {
|
|
1337
|
-
return normalizePathSubject(obj.path);
|
|
1338
|
-
}
|
|
1339
|
-
if (typeof obj.url === "string") {
|
|
1340
|
-
return escapeGlobSubject(obj.url);
|
|
1341
|
-
}
|
|
1342
|
-
if (typeof obj.name === "string") {
|
|
1343
|
-
return escapeGlobSubject(obj.name);
|
|
1344
|
-
}
|
|
1345
|
-
return void 0;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
1330
|
// src/utils/tool-wire-compact.ts
|
|
1349
1331
|
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
1350
1332
|
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
@@ -1361,9 +1343,11 @@ function compactToolDefinitionForWire(tool, opts = {}) {
|
|
|
1361
1343
|
tool.description ?? "",
|
|
1362
1344
|
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
1363
1345
|
),
|
|
1364
|
-
inputSchema:
|
|
1365
|
-
|
|
1366
|
-
|
|
1346
|
+
inputSchema: normalizeTopLevelToolSchema(
|
|
1347
|
+
compactSchemaDescriptions(
|
|
1348
|
+
tool.inputSchema,
|
|
1349
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
1350
|
+
)
|
|
1367
1351
|
)
|
|
1368
1352
|
};
|
|
1369
1353
|
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
@@ -1371,6 +1355,44 @@ function compactToolDefinitionForWire(tool, opts = {}) {
|
|
|
1371
1355
|
}
|
|
1372
1356
|
return compact;
|
|
1373
1357
|
}
|
|
1358
|
+
function normalizeTopLevelToolSchema(schema) {
|
|
1359
|
+
const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
|
|
1360
|
+
(entry) => Array.isArray(entry.branches)
|
|
1361
|
+
);
|
|
1362
|
+
if (combinators.length === 0) return schema;
|
|
1363
|
+
const out = { ...schema, type: "object" };
|
|
1364
|
+
delete out["oneOf"];
|
|
1365
|
+
delete out["anyOf"];
|
|
1366
|
+
delete out["allOf"];
|
|
1367
|
+
const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
|
|
1368
|
+
let required = stringSet(schema["required"]);
|
|
1369
|
+
for (const { keyword, branches } of combinators) {
|
|
1370
|
+
const objectBranches = branches.filter(isRecord);
|
|
1371
|
+
for (const branch of objectBranches) {
|
|
1372
|
+
if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
|
|
1373
|
+
}
|
|
1374
|
+
const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
|
|
1375
|
+
if (keyword === "allOf") {
|
|
1376
|
+
for (const fields of branchRequired) for (const field of fields) required.add(field);
|
|
1377
|
+
} else if (branchRequired.length > 0) {
|
|
1378
|
+
const common = new Set(
|
|
1379
|
+
[...branchRequired[0]].filter(
|
|
1380
|
+
(field) => branchRequired.slice(1).every((fields) => fields.has(field))
|
|
1381
|
+
)
|
|
1382
|
+
);
|
|
1383
|
+
required = /* @__PURE__ */ new Set([...required, ...common]);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
out["properties"] = properties;
|
|
1387
|
+
if (required.size > 0) out["required"] = [...required];
|
|
1388
|
+
else delete out["required"];
|
|
1389
|
+
return out;
|
|
1390
|
+
}
|
|
1391
|
+
function stringSet(value) {
|
|
1392
|
+
return new Set(
|
|
1393
|
+
Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1374
1396
|
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
1375
1397
|
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
1376
1398
|
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
@@ -2265,25 +2287,88 @@ function resolveToolResultRenderMode(modes, toolName) {
|
|
|
2265
2287
|
return normalizeToolResultRenderMode(modes?.[toolName]) ?? DEFAULT_TOOL_RESULT_RENDER_MODE;
|
|
2266
2288
|
}
|
|
2267
2289
|
|
|
2290
|
+
// src/utils/tool-subject.ts
|
|
2291
|
+
var GLOB_METACHARACTERS = /[*?[\]]/g;
|
|
2292
|
+
function escapeGlobSubject(value) {
|
|
2293
|
+
return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
|
|
2294
|
+
}
|
|
2295
|
+
function normalizePathSubject(value) {
|
|
2296
|
+
return escapeGlobSubject(value.replace(/\\/g, "/"));
|
|
2297
|
+
}
|
|
2298
|
+
function isPathSubjectKey(subjectKey) {
|
|
2299
|
+
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
2300
|
+
}
|
|
2301
|
+
function subjectForToolInput(toolName, input, subjectKey) {
|
|
2302
|
+
if (!input || typeof input !== "object") return void 0;
|
|
2303
|
+
const obj = input;
|
|
2304
|
+
if (subjectKey) {
|
|
2305
|
+
const value = obj[subjectKey];
|
|
2306
|
+
if (typeof value === "string") {
|
|
2307
|
+
return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
if (toolName === "bash" && typeof obj.command === "string") {
|
|
2311
|
+
return escapeGlobSubject(obj.command);
|
|
2312
|
+
}
|
|
2313
|
+
if (typeof obj.path === "string") {
|
|
2314
|
+
return normalizePathSubject(obj.path);
|
|
2315
|
+
}
|
|
2316
|
+
if (typeof obj.url === "string") {
|
|
2317
|
+
return escapeGlobSubject(obj.url);
|
|
2318
|
+
}
|
|
2319
|
+
if (typeof obj.name === "string") {
|
|
2320
|
+
return escapeGlobSubject(obj.name);
|
|
2321
|
+
}
|
|
2322
|
+
return void 0;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
// src/utils/ulid.ts
|
|
2326
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2327
|
+
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2328
|
+
var ENCODING_LEN = ENCODING.length;
|
|
2329
|
+
var TIME_LEN = 10;
|
|
2330
|
+
var RANDOM_LEN = 16;
|
|
2331
|
+
function encodeTime(now, len) {
|
|
2332
|
+
let mod;
|
|
2333
|
+
let str = "";
|
|
2334
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
2335
|
+
mod = now % ENCODING_LEN;
|
|
2336
|
+
str = ENCODING[mod] + str;
|
|
2337
|
+
now = (now - mod) / ENCODING_LEN;
|
|
2338
|
+
}
|
|
2339
|
+
return str;
|
|
2340
|
+
}
|
|
2341
|
+
function encodeRandom(len) {
|
|
2342
|
+
const bytes = randomBytes2(len);
|
|
2343
|
+
let str = "";
|
|
2344
|
+
for (let i = 0; i < len; i++) {
|
|
2345
|
+
str += ENCODING[bytes[i] % ENCODING_LEN];
|
|
2346
|
+
}
|
|
2347
|
+
return str;
|
|
2348
|
+
}
|
|
2349
|
+
function ulid(seedTime = Date.now()) {
|
|
2350
|
+
return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2268
2353
|
// src/utils/wstack-paths.ts
|
|
2269
2354
|
import { createHash } from "node:crypto";
|
|
2270
|
-
import * as
|
|
2355
|
+
import * as fs3 from "node:fs";
|
|
2271
2356
|
import * as os from "node:os";
|
|
2272
|
-
import * as
|
|
2357
|
+
import * as path4 from "node:path";
|
|
2273
2358
|
function canonicalProjectRoot(absRoot) {
|
|
2274
|
-
const checkoutRoot =
|
|
2275
|
-
const dotGit =
|
|
2359
|
+
const checkoutRoot = path4.resolve(absRoot);
|
|
2360
|
+
const dotGit = path4.join(checkoutRoot, ".git");
|
|
2276
2361
|
try {
|
|
2277
|
-
if (!
|
|
2278
|
-
const gitDirLine =
|
|
2362
|
+
if (!fs3.statSync(dotGit).isFile()) return checkoutRoot;
|
|
2363
|
+
const gitDirLine = fs3.readFileSync(dotGit, "utf8").trim();
|
|
2279
2364
|
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
2280
2365
|
if (!match?.[1]) return checkoutRoot;
|
|
2281
|
-
const gitDir =
|
|
2282
|
-
const commonDirFile =
|
|
2283
|
-
if (!
|
|
2284
|
-
const commonDir =
|
|
2285
|
-
if (
|
|
2286
|
-
return
|
|
2366
|
+
const gitDir = path4.resolve(checkoutRoot, match[1].trim());
|
|
2367
|
+
const commonDirFile = path4.join(gitDir, "commondir");
|
|
2368
|
+
if (!fs3.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
2369
|
+
const commonDir = path4.resolve(gitDir, fs3.readFileSync(commonDirFile, "utf8").trim());
|
|
2370
|
+
if (path4.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
2371
|
+
return path4.dirname(commonDir);
|
|
2287
2372
|
} catch {
|
|
2288
2373
|
return checkoutRoot;
|
|
2289
2374
|
}
|
|
@@ -2293,7 +2378,7 @@ function projectHash(absRoot) {
|
|
|
2293
2378
|
}
|
|
2294
2379
|
function projectSlug(absRoot) {
|
|
2295
2380
|
const identityRoot = canonicalProjectRoot(absRoot);
|
|
2296
|
-
const base = slugify2(
|
|
2381
|
+
const base = slugify2(path4.basename(identityRoot));
|
|
2297
2382
|
const hash2 = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
2298
2383
|
return `${base}-${hash2}`;
|
|
2299
2384
|
}
|
|
@@ -2302,61 +2387,83 @@ function slugify2(name) {
|
|
|
2302
2387
|
}
|
|
2303
2388
|
function wstackGlobalRoot() {
|
|
2304
2389
|
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
2305
|
-
if (fromEnv && fromEnv.trim().length > 0) return
|
|
2306
|
-
return
|
|
2390
|
+
if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
|
|
2391
|
+
return path4.join(os.homedir(), ".wrongstack");
|
|
2307
2392
|
}
|
|
2308
2393
|
function resolveWstackPaths(opts) {
|
|
2309
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ?
|
|
2394
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path4.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
2310
2395
|
const homeDir = opts.userHome ?? os.homedir();
|
|
2311
2396
|
const hash2 = projectHash(opts.projectRoot);
|
|
2312
2397
|
const slug = projectSlug(opts.projectRoot);
|
|
2313
|
-
const projectDir =
|
|
2398
|
+
const projectDir = path4.join(globalRoot, "projects", slug);
|
|
2314
2399
|
return {
|
|
2315
2400
|
globalRoot,
|
|
2316
2401
|
projectRoot: opts.projectRoot,
|
|
2317
2402
|
homeDir,
|
|
2318
2403
|
configDir: globalRoot,
|
|
2319
|
-
globalConfig:
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2404
|
+
globalConfig: path4.join(globalRoot, "config.json"),
|
|
2405
|
+
profilesDir: path4.join(globalRoot, "profiles"),
|
|
2406
|
+
profileConfig: (name) => {
|
|
2407
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2408
|
+
return path4.join(globalRoot, "profiles", safe || "default", "config.json");
|
|
2409
|
+
},
|
|
2410
|
+
profileStatuslineConfig: (name) => {
|
|
2411
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2412
|
+
return path4.join(globalRoot, "profiles", safe || "default", "statusline.json");
|
|
2413
|
+
},
|
|
2414
|
+
profileModeConfig: (name) => {
|
|
2415
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2416
|
+
return path4.join(globalRoot, "profiles", safe || "default", "mode.json");
|
|
2417
|
+
},
|
|
2418
|
+
profileProviderStatus: (name) => {
|
|
2419
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2420
|
+
return path4.join(globalRoot, "profiles", safe || "default", "provider-status.json");
|
|
2421
|
+
},
|
|
2422
|
+
profileUpdateCache: (name) => {
|
|
2423
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2424
|
+
return path4.join(globalRoot, "profiles", safe || "default", "update-cache.json");
|
|
2425
|
+
},
|
|
2426
|
+
secretsKey: path4.join(globalRoot, ".key"),
|
|
2427
|
+
globalMemory: path4.join(globalRoot, "memory.md"),
|
|
2428
|
+
globalSkills: path4.join(globalRoot, "skills"),
|
|
2429
|
+
globalClaudeSkills: path4.join(homeDir, ".claude", "skills"),
|
|
2430
|
+
globalDesignKits: path4.join(globalRoot, "design-kits"),
|
|
2431
|
+
globalPrompts: path4.join(globalRoot, "prompts"),
|
|
2432
|
+
globalInstructions: path4.join(globalRoot, "instructions"),
|
|
2433
|
+
promptUsage: path4.join(globalRoot, "prompt-usage.json"),
|
|
2434
|
+
cacheDir: path4.join(globalRoot, "cache"),
|
|
2435
|
+
modelsCache: path4.join(globalRoot, "cache", "models.dev.json"),
|
|
2436
|
+
modelsOverlayCache: path4.join(globalRoot, "cache", "models-overlay.json"),
|
|
2437
|
+
historyFile: path4.join(globalRoot, "history"),
|
|
2438
|
+
logFile: path4.join(globalRoot, "logs", "wrongstack.log"),
|
|
2333
2439
|
projectDir,
|
|
2334
|
-
projectCodebaseIndex:
|
|
2335
|
-
projectMemory:
|
|
2336
|
-
projectSessions:
|
|
2337
|
-
projectTrust:
|
|
2338
|
-
projectMeta:
|
|
2339
|
-
projectLocalConfig:
|
|
2340
|
-
inProjectConfig:
|
|
2341
|
-
inProjectAgentsFile:
|
|
2342
|
-
inProjectSkills:
|
|
2343
|
-
inProjectClaudeSkills:
|
|
2344
|
-
inProjectPrompts:
|
|
2345
|
-
inProjectInstructions:
|
|
2346
|
-
inProjectDesignKits:
|
|
2347
|
-
inProjectWorktrees:
|
|
2440
|
+
projectCodebaseIndex: path4.join(projectDir, "codebase-index"),
|
|
2441
|
+
projectMemory: path4.join(projectDir, "memory.md"),
|
|
2442
|
+
projectSessions: path4.join(projectDir, "sessions"),
|
|
2443
|
+
projectTrust: path4.join(projectDir, "trust.json"),
|
|
2444
|
+
projectMeta: path4.join(projectDir, "meta.json"),
|
|
2445
|
+
projectLocalConfig: path4.join(projectDir, "config.local.json"),
|
|
2446
|
+
inProjectConfig: path4.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
2447
|
+
inProjectAgentsFile: path4.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
2448
|
+
inProjectSkills: path4.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
2449
|
+
inProjectClaudeSkills: path4.join(opts.projectRoot, ".claude", "skills"),
|
|
2450
|
+
inProjectPrompts: path4.join(opts.projectRoot, ".wrongstack", "prompts"),
|
|
2451
|
+
inProjectInstructions: path4.join(opts.projectRoot, ".wrongstack", "instructions"),
|
|
2452
|
+
inProjectDesignKits: path4.join(opts.projectRoot, ".wrongstack", "design-kits"),
|
|
2453
|
+
inProjectWorktrees: path4.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
2348
2454
|
projectHash: hash2,
|
|
2349
2455
|
projectSlug: slug,
|
|
2350
|
-
projectGoal:
|
|
2351
|
-
projectInputHistory:
|
|
2352
|
-
projectSpecs:
|
|
2353
|
-
projectTaskGraphs:
|
|
2354
|
-
projectSddSession:
|
|
2355
|
-
projectPlan:
|
|
2356
|
-
projectAutophase:
|
|
2357
|
-
projectSddBoards:
|
|
2358
|
-
syncConfig:
|
|
2359
|
-
|
|
2456
|
+
projectGoal: path4.join(projectDir, "goal.json"),
|
|
2457
|
+
projectInputHistory: path4.join(projectDir, "input-history.json"),
|
|
2458
|
+
projectSpecs: path4.join(projectDir, "specs"),
|
|
2459
|
+
projectTaskGraphs: path4.join(projectDir, "task-graphs"),
|
|
2460
|
+
projectSddSession: path4.join(projectDir, "sdd-session.json"),
|
|
2461
|
+
projectPlan: path4.join(projectDir, "plan.json"),
|
|
2462
|
+
projectAutophase: path4.join(projectDir, "autophase"),
|
|
2463
|
+
projectSddBoards: path4.join(projectDir, "sdd-boards"),
|
|
2464
|
+
syncConfig: path4.join(globalRoot, "sync.json"),
|
|
2465
|
+
configHistoryDir: path4.join(globalRoot, "config-history"),
|
|
2466
|
+
projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
|
|
2360
2467
|
};
|
|
2361
2468
|
}
|
|
2362
2469
|
|
|
@@ -2649,7 +2756,7 @@ var InMemoryAgentBridge = class {
|
|
|
2649
2756
|
});
|
|
2650
2757
|
}
|
|
2651
2758
|
this.inflightGuards.add(correlationId);
|
|
2652
|
-
return new Promise((
|
|
2759
|
+
return new Promise((resolve14, reject) => {
|
|
2653
2760
|
const timer = setTimeout(() => {
|
|
2654
2761
|
this.inflightGuards.delete(correlationId);
|
|
2655
2762
|
this.pendingRequests.delete(correlationId);
|
|
@@ -2668,7 +2775,7 @@ var InMemoryAgentBridge = class {
|
|
|
2668
2775
|
return;
|
|
2669
2776
|
}
|
|
2670
2777
|
this.pendingRequests.set(correlationId, {
|
|
2671
|
-
resolve:
|
|
2778
|
+
resolve: resolve14,
|
|
2672
2779
|
reject,
|
|
2673
2780
|
timer
|
|
2674
2781
|
});
|
|
@@ -3017,13 +3124,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3017
3124
|
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
3018
3125
|
return Promise.resolve("stop");
|
|
3019
3126
|
}
|
|
3020
|
-
return new Promise((
|
|
3127
|
+
return new Promise((resolve14) => {
|
|
3021
3128
|
let resolved = false;
|
|
3022
3129
|
const respond = (d) => {
|
|
3023
3130
|
if (resolved) return;
|
|
3024
3131
|
resolved = true;
|
|
3025
3132
|
clearTimeout(fallback);
|
|
3026
|
-
|
|
3133
|
+
resolve14(d);
|
|
3027
3134
|
};
|
|
3028
3135
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
3029
3136
|
const sessionId = this.currentSessionId();
|
|
@@ -3348,8 +3455,34 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3348
3455
|
const taskStartedAt = Date.now();
|
|
3349
3456
|
const factoryResult = await opts.factory(ctx.config, task);
|
|
3350
3457
|
const { agent, events } = factoryResult;
|
|
3458
|
+
const taskContext = task.context ?? {};
|
|
3459
|
+
const hasExplicitTelemetryTask = typeof taskContext["telemetryTaskId"] === "string";
|
|
3460
|
+
const telemetryTaskId = hasExplicitTelemetryTask ? taskContext["telemetryTaskId"] : task.id;
|
|
3461
|
+
const telemetryRunId = typeof taskContext["telemetryRunId"] === "string" ? taskContext["telemetryRunId"] : void 0;
|
|
3462
|
+
const telemetryBoardId = typeof taskContext["telemetryBoardId"] === "string" ? taskContext["telemetryBoardId"] : void 0;
|
|
3351
3463
|
const agentContext = agent.ctx;
|
|
3352
|
-
if (agentContext?.meta)
|
|
3464
|
+
if (agentContext?.meta) {
|
|
3465
|
+
agentContext.meta["subagentTaskId"] = task.id;
|
|
3466
|
+
const kanbanContext = taskContext["kanban"];
|
|
3467
|
+
if (kanbanContext && typeof kanbanContext === "object") {
|
|
3468
|
+
agentContext.meta["kanban"] = kanbanContext;
|
|
3469
|
+
}
|
|
3470
|
+
if (hasExplicitTelemetryTask) {
|
|
3471
|
+
agentContext.meta["telemetryTaskId"] = telemetryTaskId;
|
|
3472
|
+
if (telemetryRunId) agentContext.meta["telemetryRunId"] = telemetryRunId;
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
if (hasExplicitTelemetryTask) {
|
|
3476
|
+
agentContext?.setCurrentKanbanTask?.(telemetryTaskId, telemetryBoardId);
|
|
3477
|
+
} else {
|
|
3478
|
+
const kanbanContext = taskContext["kanban"];
|
|
3479
|
+
if (kanbanContext && typeof kanbanContext === "object") {
|
|
3480
|
+
const kanban = kanbanContext;
|
|
3481
|
+
const boardId = typeof kanban["boardId"] === "string" ? kanban["boardId"] : void 0;
|
|
3482
|
+
const taskId = typeof kanban["taskId"] === "string" ? kanban["taskId"] : void 0;
|
|
3483
|
+
if (taskId) agentContext?.setCurrentKanbanTask?.(taskId, boardId);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3353
3486
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
3354
3487
|
const aborter = new AbortController();
|
|
3355
3488
|
ctx.budget._events = events;
|
|
@@ -3374,6 +3507,27 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3374
3507
|
const unsub = [];
|
|
3375
3508
|
unsub.push(
|
|
3376
3509
|
events.on("tool.executed", (e) => {
|
|
3510
|
+
if (hasExplicitTelemetryTask) {
|
|
3511
|
+
opts.hostEvents?.emit("subagent.tool_executed", {
|
|
3512
|
+
sessionId: e.sessionId,
|
|
3513
|
+
subagentId: ctx.subagentId,
|
|
3514
|
+
agentSessionId: e.sessionId,
|
|
3515
|
+
agentName: e.agentName ?? ctx.config.name,
|
|
3516
|
+
taskId: telemetryTaskId,
|
|
3517
|
+
runId: telemetryRunId,
|
|
3518
|
+
traceId: e.traceId,
|
|
3519
|
+
id: e.id,
|
|
3520
|
+
name: e.name,
|
|
3521
|
+
durationMs: e.durationMs,
|
|
3522
|
+
ok: e.ok,
|
|
3523
|
+
// Raw tool inputs may contain credentials or sensitive arguments.
|
|
3524
|
+
// Task telemetry crosses from the isolated worker bus to the shared
|
|
3525
|
+
// host bus, so expose only execution metadata at this boundary.
|
|
3526
|
+
outputBytes: e.outputBytes,
|
|
3527
|
+
outputTokens: e.outputTokens,
|
|
3528
|
+
outputLines: e.outputLines
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3377
3531
|
try {
|
|
3378
3532
|
ctx.budget.recordToolCall();
|
|
3379
3533
|
} catch (eb) {
|
|
@@ -3385,6 +3539,17 @@ function makeAgentSubagentRunner(opts) {
|
|
|
3385
3539
|
lastToolFailed = null;
|
|
3386
3540
|
}
|
|
3387
3541
|
}),
|
|
3542
|
+
events.on("file.event", (e) => {
|
|
3543
|
+
if (!hasExplicitTelemetryTask) return;
|
|
3544
|
+
if (e.runId === telemetryRunId && e.taskId === telemetryTaskId) return;
|
|
3545
|
+
opts.hostEvents?.emit("file.event", {
|
|
3546
|
+
...e,
|
|
3547
|
+
scope: "task",
|
|
3548
|
+
taskId: telemetryTaskId,
|
|
3549
|
+
boardId: telemetryBoardId,
|
|
3550
|
+
runId: telemetryRunId
|
|
3551
|
+
});
|
|
3552
|
+
}),
|
|
3388
3553
|
events.on("provider.response", (e) => {
|
|
3389
3554
|
try {
|
|
3390
3555
|
ctx.budget.recordUsage(e.usage);
|
|
@@ -3607,7 +3772,7 @@ var TOOLS = {
|
|
|
3607
3772
|
// src/coordination/agents/agent-prompts.ts
|
|
3608
3773
|
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
3609
3774
|
import * as os2 from "node:os";
|
|
3610
|
-
import * as
|
|
3775
|
+
import * as path5 from "node:path";
|
|
3611
3776
|
import { fileURLToPath } from "node:url";
|
|
3612
3777
|
var promptCache = /* @__PURE__ */ new Map();
|
|
3613
3778
|
var candidateCache = /* @__PURE__ */ new Map();
|
|
@@ -3620,7 +3785,7 @@ function agentPrompt(id) {
|
|
|
3620
3785
|
let resolved = "";
|
|
3621
3786
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
3622
3787
|
try {
|
|
3623
|
-
resolved = readFileSync2(
|
|
3788
|
+
resolved = readFileSync2(path5.join(dir, fileName), "utf8").trimEnd();
|
|
3624
3789
|
break;
|
|
3625
3790
|
} catch {
|
|
3626
3791
|
}
|
|
@@ -3629,20 +3794,20 @@ function agentPrompt(id) {
|
|
|
3629
3794
|
return resolved;
|
|
3630
3795
|
}
|
|
3631
3796
|
function agentPromptDirCandidates(envDir) {
|
|
3632
|
-
const globalRoot = process.env["WRONGSTACK_HOME"] ||
|
|
3797
|
+
const globalRoot = process.env["WRONGSTACK_HOME"] || path5.join(os2.homedir(), ".wrongstack");
|
|
3633
3798
|
const candKey = `${envDir}\0${globalRoot}`;
|
|
3634
3799
|
const cached = candidateCache.get(candKey);
|
|
3635
3800
|
if (cached !== void 0) return cached;
|
|
3636
|
-
const here =
|
|
3801
|
+
const here = path5.dirname(fileURLToPath(import.meta.url));
|
|
3637
3802
|
const explicitDir = envDir || void 0;
|
|
3638
3803
|
const candidates = [
|
|
3639
|
-
...explicitDir ? [
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3804
|
+
...explicitDir ? [path5.resolve(explicitDir)] : [],
|
|
3805
|
+
path5.join(globalRoot, "instructions", "agents"),
|
|
3806
|
+
path5.resolve(here, "../../../../instructions/agents"),
|
|
3807
|
+
path5.resolve(here, "../../../instructions/agents"),
|
|
3808
|
+
path5.resolve(here, "../../instructions/agents"),
|
|
3809
|
+
path5.resolve(here, "../instructions/agents"),
|
|
3810
|
+
path5.resolve(here, "instructions/agents")
|
|
3646
3811
|
];
|
|
3647
3812
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
3648
3813
|
candidateCache.set(candKey, ordered);
|
|
@@ -4678,6 +4843,40 @@ var DOMAIN_AGENTS = [
|
|
|
4678
4843
|
"information architecture"
|
|
4679
4844
|
]
|
|
4680
4845
|
}
|
|
4846
|
+
},
|
|
4847
|
+
{
|
|
4848
|
+
config: {
|
|
4849
|
+
id: "ios",
|
|
4850
|
+
name: "iOS",
|
|
4851
|
+
role: "ios",
|
|
4852
|
+
tools: [...TOOLS.build, "fetch"],
|
|
4853
|
+
prompt: agentPrompt("ios")
|
|
4854
|
+
},
|
|
4855
|
+
budget: HEAVY_BUDGET,
|
|
4856
|
+
capability: {
|
|
4857
|
+
phase: "domain",
|
|
4858
|
+
summary: "Apple-platform app development in Swift against the latest Xcode/iOS-SDK line: SwiftUI, UIKit, SwiftData, concurrency, accessibility, and App Store submission.",
|
|
4859
|
+
keywords: [
|
|
4860
|
+
"ios",
|
|
4861
|
+
"iphone",
|
|
4862
|
+
"ipad",
|
|
4863
|
+
"ipados",
|
|
4864
|
+
"watchos",
|
|
4865
|
+
"tvos",
|
|
4866
|
+
"visionos",
|
|
4867
|
+
"macos",
|
|
4868
|
+
"swift",
|
|
4869
|
+
"swiftui",
|
|
4870
|
+
"uikit",
|
|
4871
|
+
"xcode",
|
|
4872
|
+
"swiftdata",
|
|
4873
|
+
"app store",
|
|
4874
|
+
"app intents",
|
|
4875
|
+
"swift package manager",
|
|
4876
|
+
"cocoapods",
|
|
4877
|
+
"foundation models"
|
|
4878
|
+
]
|
|
4879
|
+
}
|
|
4681
4880
|
}
|
|
4682
4881
|
];
|
|
4683
4882
|
|
|
@@ -5211,7 +5410,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
5211
5410
|
|
|
5212
5411
|
// src/coordination/delegate-tool.ts
|
|
5213
5412
|
import * as fsp2 from "node:fs/promises";
|
|
5214
|
-
import * as
|
|
5413
|
+
import * as path6 from "node:path";
|
|
5215
5414
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5216
5415
|
|
|
5217
5416
|
// src/coordination/fleet.ts
|
|
@@ -5659,7 +5858,7 @@ function createDelegateTool(opts) {
|
|
|
5659
5858
|
};
|
|
5660
5859
|
}
|
|
5661
5860
|
async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
|
|
5662
|
-
return new Promise((
|
|
5861
|
+
return new Promise((resolve14) => {
|
|
5663
5862
|
let settled = false;
|
|
5664
5863
|
let timer;
|
|
5665
5864
|
let offAbort = () => {
|
|
@@ -5672,7 +5871,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
|
|
|
5672
5871
|
offIter();
|
|
5673
5872
|
offProgress();
|
|
5674
5873
|
offAbort();
|
|
5675
|
-
|
|
5874
|
+
resolve14(value);
|
|
5676
5875
|
};
|
|
5677
5876
|
const arm = () => {
|
|
5678
5877
|
if (timer) clearTimeout(timer);
|
|
@@ -5832,13 +6031,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5832
6031
|
if (!opts.sessionsRoot) return void 0;
|
|
5833
6032
|
const candidates = [];
|
|
5834
6033
|
if (opts.directorRunId) {
|
|
5835
|
-
candidates.push(
|
|
6034
|
+
candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
5836
6035
|
} else {
|
|
5837
6036
|
try {
|
|
5838
6037
|
const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
5839
6038
|
for (const entry of entries) {
|
|
5840
6039
|
if (entry.isDirectory()) {
|
|
5841
|
-
candidates.push(
|
|
6040
|
+
candidates.push(path6.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
5842
6041
|
}
|
|
5843
6042
|
}
|
|
5844
6043
|
} catch {
|
|
@@ -5885,7 +6084,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5885
6084
|
// src/coordination/director.ts
|
|
5886
6085
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
5887
6086
|
import * as fsp5 from "node:fs/promises";
|
|
5888
|
-
import * as
|
|
6087
|
+
import * as path8 from "node:path";
|
|
5889
6088
|
|
|
5890
6089
|
// src/storage/director-state.ts
|
|
5891
6090
|
import * as fsp3 from "node:fs/promises";
|
|
@@ -6368,7 +6567,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
6368
6567
|
}
|
|
6369
6568
|
for (const filePath of allFiles) {
|
|
6370
6569
|
try {
|
|
6371
|
-
const [content,
|
|
6570
|
+
const [content, stat13] = await Promise.all([
|
|
6372
6571
|
fsp4.readFile(filePath, "utf8"),
|
|
6373
6572
|
fsp4.stat(filePath)
|
|
6374
6573
|
]);
|
|
@@ -6378,8 +6577,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
6378
6577
|
path: filePath,
|
|
6379
6578
|
content,
|
|
6380
6579
|
language,
|
|
6381
|
-
snapshotMtimeMs:
|
|
6382
|
-
snapshotSizeBytes:
|
|
6580
|
+
snapshotMtimeMs: stat13.mtimeMs,
|
|
6581
|
+
snapshotSizeBytes: stat13.size
|
|
6383
6582
|
});
|
|
6384
6583
|
} catch {
|
|
6385
6584
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
@@ -6798,9 +6997,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
6798
6997
|
for (const file of this.snapshot.files) {
|
|
6799
6998
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
6800
6999
|
try {
|
|
6801
|
-
const
|
|
6802
|
-
const mtimeChanged = file.snapshotMtimeMs !== void 0 &&
|
|
6803
|
-
const sizeChanged = file.snapshotSizeBytes !== void 0 &&
|
|
7000
|
+
const stat13 = await fsp4.stat(file.path);
|
|
7001
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat13.mtimeMs > file.snapshotMtimeMs + 1;
|
|
7002
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat13.size !== file.snapshotSizeBytes;
|
|
6804
7003
|
if (mtimeChanged || sizeChanged) {
|
|
6805
7004
|
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
6806
7005
|
}
|
|
@@ -6955,7 +7154,7 @@ var DirectorBtwNotes = class {
|
|
|
6955
7154
|
|
|
6956
7155
|
// src/utils/instruction-file.ts
|
|
6957
7156
|
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
6958
|
-
import * as
|
|
7157
|
+
import * as path7 from "node:path";
|
|
6959
7158
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6960
7159
|
var textCache = /* @__PURE__ */ new Map();
|
|
6961
7160
|
var rootCandidates;
|
|
@@ -6965,7 +7164,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
6965
7164
|
let resolved = "";
|
|
6966
7165
|
for (const root of instructionRootCandidates()) {
|
|
6967
7166
|
try {
|
|
6968
|
-
resolved = readFileSync3(
|
|
7167
|
+
resolved = readFileSync3(path7.join(root, relativePath), "utf8").trimEnd();
|
|
6969
7168
|
break;
|
|
6970
7169
|
} catch {
|
|
6971
7170
|
}
|
|
@@ -6981,11 +7180,11 @@ function renderInstructionTemplate(template, values) {
|
|
|
6981
7180
|
}
|
|
6982
7181
|
function instructionRootCandidates() {
|
|
6983
7182
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
6984
|
-
const here =
|
|
7183
|
+
const here = path7.dirname(fileURLToPath2(import.meta.url));
|
|
6985
7184
|
const candidates = [
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
7185
|
+
path7.resolve(here, "../../instructions"),
|
|
7186
|
+
path7.resolve(here, "../instructions"),
|
|
7187
|
+
path7.resolve(here, "instructions")
|
|
6989
7188
|
];
|
|
6990
7189
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
|
|
6991
7190
|
return rootCandidates;
|
|
@@ -7063,6 +7262,8 @@ function rosterSummaryFromConfigs(roster) {
|
|
|
7063
7262
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
7064
7263
|
import {
|
|
7065
7264
|
claimReadyTask,
|
|
7265
|
+
describeKanbanBoundary,
|
|
7266
|
+
heartbeatTaskAssignment,
|
|
7066
7267
|
listReadyTasks,
|
|
7067
7268
|
updateTaskAssignment
|
|
7068
7269
|
} from "@wrongstack/kanban";
|
|
@@ -7817,13 +8018,19 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7817
8018
|
const projectRoot = ctx.projectRoot;
|
|
7818
8019
|
if (!projectRoot) return { error: "kanban_queue requires ctx.projectRoot." };
|
|
7819
8020
|
const maxTasks = Math.max(1, Math.min(20, Math.floor(i.maxTasks ?? 1)));
|
|
7820
|
-
const
|
|
8021
|
+
const baseLeaseTtlMs = Math.max(1e3, Math.floor(i.leaseTtlMs ?? 5 * 60 * 1e3));
|
|
8022
|
+
const leaseTtlMs = i.awaitCompletion === true && i.timeoutMs !== void 0 ? Math.max(baseLeaseTtlMs, i.timeoutMs + 6e4) : baseLeaseTtlMs;
|
|
8023
|
+
const heartbeatIntervalMs = Math.max(
|
|
8024
|
+
5e3,
|
|
8025
|
+
i.heartbeatIntervalMs ?? Math.floor(leaseTtlMs / 2)
|
|
8026
|
+
);
|
|
8027
|
+
const effectiveLeaseTtlMs = Math.max(leaseTtlMs, heartbeatIntervalMs * 2);
|
|
7821
8028
|
const claimedAt = nowIso();
|
|
7822
8029
|
const leaseSeeding = {
|
|
7823
8030
|
leaseId: randomUUID4(),
|
|
7824
8031
|
claimedAt,
|
|
7825
8032
|
heartbeatAt: claimedAt,
|
|
7826
|
-
leaseExpiresAt: new Date(Date.now() +
|
|
8033
|
+
leaseExpiresAt: new Date(Date.now() + effectiveLeaseTtlMs).toISOString()
|
|
7827
8034
|
};
|
|
7828
8035
|
const candidateTaskIds = i.taskId !== void 0 ? [i.taskId] : i.query ? (await listReadyTasks(projectRoot, {
|
|
7829
8036
|
...i.boardId !== void 0 ? { boardId: i.boardId } : {}
|
|
@@ -7831,9 +8038,11 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7831
8038
|
const dispatches = [];
|
|
7832
8039
|
const errors = [];
|
|
7833
8040
|
const resultFailures = [];
|
|
8041
|
+
const budgetRejectedTaskIds = /* @__PURE__ */ new Set();
|
|
7834
8042
|
for (let index = 0; index < maxTasks; index++) {
|
|
7835
8043
|
const candidateTaskId = candidateTaskIds?.[index];
|
|
7836
8044
|
if (candidateTaskIds && !candidateTaskId) break;
|
|
8045
|
+
if (candidateTaskId && budgetRejectedTaskIds.has(candidateTaskId)) continue;
|
|
7837
8046
|
const claim = await claimReadyTask(projectRoot, {
|
|
7838
8047
|
...i.boardId !== void 0 ? { boardId: i.boardId } : {},
|
|
7839
8048
|
...candidateTaskId !== void 0 ? { taskId: candidateTaskId } : {},
|
|
@@ -7858,14 +8067,22 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7858
8067
|
if (costCeiling !== void 0) {
|
|
7859
8068
|
const remaining = director.getRemainingBudgetUsd();
|
|
7860
8069
|
if (remaining !== void 0 && remaining < costCeiling) {
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
8070
|
+
budgetRejectedTaskIds.add(claim.task.id);
|
|
8071
|
+
const budgetError = `Cost ceiling ${costCeiling} exceeds remaining budget ${remaining.toFixed(4)}`;
|
|
8072
|
+
await updateTaskAssignment(
|
|
8073
|
+
projectRoot,
|
|
8074
|
+
claim.board.id,
|
|
8075
|
+
claim.task.id,
|
|
8076
|
+
{
|
|
8077
|
+
...claim.task.assignment ?? {},
|
|
8078
|
+
status: "failed",
|
|
8079
|
+
error: budgetError
|
|
8080
|
+
},
|
|
8081
|
+
{ expectedLeaseId: leaseSeeding.leaseId }
|
|
8082
|
+
);
|
|
7866
8083
|
errors.push({
|
|
7867
8084
|
taskId: claim.task.id,
|
|
7868
|
-
error:
|
|
8085
|
+
error: budgetError
|
|
7869
8086
|
});
|
|
7870
8087
|
continue;
|
|
7871
8088
|
}
|
|
@@ -7873,12 +8090,20 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7873
8090
|
try {
|
|
7874
8091
|
const config = buildKanbanSubagentConfig(claim.task, i, roster);
|
|
7875
8092
|
subagentId = await director.spawn(config);
|
|
7876
|
-
|
|
7877
|
-
|
|
8093
|
+
const dispatchTaskId = randomUUID4();
|
|
8094
|
+
const taskSpec = {
|
|
8095
|
+
id: dispatchTaskId,
|
|
7878
8096
|
subagentId,
|
|
7879
8097
|
description: buildKanbanFleetTaskPrompt(claim.board, claim.task, {
|
|
7880
|
-
|
|
7881
|
-
|
|
8098
|
+
// The worker must heartbeat against the lease it actually holds.
|
|
8099
|
+
// effectiveLeaseTtlMs is the real lease window (raised from the
|
|
8100
|
+
// requested TTL when needed so the first heartbeat always lands
|
|
8101
|
+
// before expiry), and the computed heartbeatIntervalMs is always
|
|
8102
|
+
// strictly shorter than that window. Passing the raw requested
|
|
8103
|
+
// values here would give the worker a 60s cadence against a
|
|
8104
|
+
// 10s lease, letting the lease expire before its first renewal.
|
|
8105
|
+
heartbeatIntervalMs,
|
|
8106
|
+
leaseTtlMs: effectiveLeaseTtlMs,
|
|
7882
8107
|
leaseId: leaseSeeding.leaseId,
|
|
7883
8108
|
leaseExpiresAt: leaseSeeding.leaseExpiresAt
|
|
7884
8109
|
}),
|
|
@@ -7889,16 +8114,51 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7889
8114
|
kanban: {
|
|
7890
8115
|
boardId: claim.board.id,
|
|
7891
8116
|
taskId: claim.task.id,
|
|
7892
|
-
origin: claim.task.origin
|
|
8117
|
+
origin: claim.task.origin,
|
|
8118
|
+
projectRoot
|
|
7893
8119
|
}
|
|
7894
8120
|
}
|
|
7895
|
-
}
|
|
7896
|
-
await updateTaskAssignment(
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
8121
|
+
};
|
|
8122
|
+
const dispatched = await updateTaskAssignment(
|
|
8123
|
+
projectRoot,
|
|
8124
|
+
claim.board.id,
|
|
8125
|
+
claim.task.id,
|
|
8126
|
+
{
|
|
8127
|
+
...claim.task.assignment ?? {},
|
|
8128
|
+
status: "running",
|
|
8129
|
+
subagentId,
|
|
8130
|
+
runTaskId: dispatchTaskId,
|
|
8131
|
+
// Renew the lease from the actual dispatch moment, not from claim
|
|
8132
|
+
// time. The claim-time lease starts before spawn + queueing, so
|
|
8133
|
+
// `timeoutMs + 60_000` can expire while the worker is still
|
|
8134
|
+
// queued or running, letting recover_stale reclaim and duplicate
|
|
8135
|
+
// the assignment (see auto-extend.ts: timeout extensions can push
|
|
8136
|
+
// execution well past the original timeoutMs). Both heartbeatAt
|
|
8137
|
+
// and leaseExpiresAt are stamped from now so the worker has a
|
|
8138
|
+
// full lease window from the moment it was actually dispatched.
|
|
8139
|
+
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8140
|
+
leaseExpiresAt: new Date(Date.now() + effectiveLeaseTtlMs).toISOString()
|
|
8141
|
+
},
|
|
8142
|
+
{ expectedLeaseId: leaseSeeding.leaseId }
|
|
8143
|
+
);
|
|
8144
|
+
if (dispatched === null) {
|
|
8145
|
+
try {
|
|
8146
|
+
await director.terminate(subagentId);
|
|
8147
|
+
} catch (cleanupErr) {
|
|
8148
|
+
errors.push({
|
|
8149
|
+
taskId: claim.task.id,
|
|
8150
|
+
error: `Lease lost during dispatch; orphan subagent ${subagentId} termination failed: ${toErrorMessage(cleanupErr)}`
|
|
8151
|
+
});
|
|
8152
|
+
continue;
|
|
8153
|
+
}
|
|
8154
|
+
errors.push({
|
|
8155
|
+
taskId: claim.task.id,
|
|
8156
|
+
error: `Lease lost during dispatch (reassigned by recovery); subagent ${subagentId} terminated.`
|
|
8157
|
+
});
|
|
8158
|
+
continue;
|
|
8159
|
+
}
|
|
8160
|
+
await director.assign(taskSpec);
|
|
8161
|
+
runTaskId = dispatchTaskId;
|
|
7902
8162
|
dispatches.push({
|
|
7903
8163
|
boardId: claim.board.id,
|
|
7904
8164
|
taskId: claim.task.id,
|
|
@@ -7917,28 +8177,97 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
7917
8177
|
message = `${message}; cleanup failed for spawned subagent ${subagentId}: ${toErrorMessage(cleanupErr)}`;
|
|
7918
8178
|
}
|
|
7919
8179
|
}
|
|
7920
|
-
await updateTaskAssignment(
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
8180
|
+
await updateTaskAssignment(
|
|
8181
|
+
projectRoot,
|
|
8182
|
+
claim.board.id,
|
|
8183
|
+
claim.task.id,
|
|
8184
|
+
{
|
|
8185
|
+
...claim.task.assignment ?? {},
|
|
8186
|
+
status: "failed",
|
|
8187
|
+
...subagentId !== void 0 ? { subagentId } : {},
|
|
8188
|
+
...runTaskId !== void 0 ? { runTaskId } : {},
|
|
8189
|
+
error: message
|
|
8190
|
+
},
|
|
8191
|
+
{ expectedLeaseId: leaseSeeding.leaseId }
|
|
8192
|
+
);
|
|
7927
8193
|
errors.push({ taskId: claim.task.id, error: message });
|
|
7928
8194
|
}
|
|
7929
8195
|
}
|
|
8196
|
+
if (!i.awaitCompletion && dispatches.length > 0 && typeof director.fleet?.subscribe === "function") {
|
|
8197
|
+
const ourLeaseId = leaseSeeding.leaseId;
|
|
8198
|
+
const disposers = /* @__PURE__ */ new Map();
|
|
8199
|
+
const renewalStartedAt = Date.now();
|
|
8200
|
+
const maxRenewalWindowMs = 24 * 60 * 6e4;
|
|
8201
|
+
const stopAll = () => {
|
|
8202
|
+
clearInterval(renewalTimer);
|
|
8203
|
+
for (const dispose of disposers.values()) dispose();
|
|
8204
|
+
disposers.clear();
|
|
8205
|
+
};
|
|
8206
|
+
const renewalTimer = setInterval(() => {
|
|
8207
|
+
if (disposers.size === 0 || Date.now() - renewalStartedAt > maxRenewalWindowMs) {
|
|
8208
|
+
stopAll();
|
|
8209
|
+
return;
|
|
8210
|
+
}
|
|
8211
|
+
const refreshedExpiry = new Date(Date.now() + effectiveLeaseTtlMs).toISOString();
|
|
8212
|
+
for (const dispatch of dispatches) {
|
|
8213
|
+
if (!disposers.has(dispatch.runTaskId)) continue;
|
|
8214
|
+
void heartbeatTaskAssignment(projectRoot, dispatch.boardId, dispatch.taskId, {
|
|
8215
|
+
leaseExpiresAt: refreshedExpiry,
|
|
8216
|
+
expectedLeaseId: ourLeaseId
|
|
8217
|
+
}).catch(() => {
|
|
8218
|
+
});
|
|
8219
|
+
}
|
|
8220
|
+
}, heartbeatIntervalMs);
|
|
8221
|
+
renewalTimer.unref?.();
|
|
8222
|
+
for (const dispatch of dispatches) {
|
|
8223
|
+
const dispose = director.fleet.subscribe(dispatch.subagentId, (event) => {
|
|
8224
|
+
if (event.type !== "subagent.completed" && event.type !== "subagent.stopped") return;
|
|
8225
|
+
if (event.type === "subagent.completed" && event.taskId !== dispatch.runTaskId) return;
|
|
8226
|
+
disposers.get(dispatch.runTaskId)?.();
|
|
8227
|
+
disposers.delete(dispatch.runTaskId);
|
|
8228
|
+
if (disposers.size === 0) clearInterval(renewalTimer);
|
|
8229
|
+
});
|
|
8230
|
+
disposers.set(dispatch.runTaskId, dispose);
|
|
8231
|
+
}
|
|
8232
|
+
}
|
|
7930
8233
|
let results;
|
|
7931
8234
|
if (i.awaitCompletion && dispatches.length > 0) {
|
|
7932
|
-
|
|
8235
|
+
const ourLeaseId = leaseSeeding.leaseId;
|
|
8236
|
+
const runTaskIds = new Set(dispatches.map((dispatch) => dispatch.runTaskId));
|
|
8237
|
+
const heartbeatTimer = setInterval(async () => {
|
|
8238
|
+
const refreshedExpiry = new Date(Date.now() + effectiveLeaseTtlMs).toISOString();
|
|
8239
|
+
for (const dispatch of dispatches) {
|
|
8240
|
+
if (!runTaskIds.has(dispatch.runTaskId)) continue;
|
|
8241
|
+
try {
|
|
8242
|
+
await heartbeatTaskAssignment(projectRoot, dispatch.boardId, dispatch.taskId, {
|
|
8243
|
+
leaseExpiresAt: refreshedExpiry,
|
|
8244
|
+
expectedLeaseId: ourLeaseId
|
|
8245
|
+
});
|
|
8246
|
+
} catch {
|
|
8247
|
+
}
|
|
8248
|
+
}
|
|
8249
|
+
}, heartbeatIntervalMs);
|
|
8250
|
+
try {
|
|
8251
|
+
results = await director.awaitTasks(dispatches.map((dispatch) => dispatch.runTaskId));
|
|
8252
|
+
} finally {
|
|
8253
|
+
clearInterval(heartbeatTimer);
|
|
8254
|
+
}
|
|
7933
8255
|
for (const result of results) {
|
|
7934
8256
|
const dispatch = dispatches.find((candidate) => candidate.runTaskId === result.taskId);
|
|
7935
8257
|
if (!dispatch) continue;
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
8258
|
+
runTaskIds.delete(dispatch.runTaskId);
|
|
8259
|
+
await updateTaskAssignment(
|
|
8260
|
+
projectRoot,
|
|
8261
|
+
dispatch.boardId,
|
|
8262
|
+
dispatch.taskId,
|
|
8263
|
+
{
|
|
8264
|
+
status: result.status === "success" ? "completed" : "failed",
|
|
8265
|
+
subagentId: result.subagentId,
|
|
8266
|
+
runTaskId: result.taskId,
|
|
8267
|
+
...result.status === "success" ? { lastResult: resultToText(result) } : { error: resultErrorText(result) }
|
|
8268
|
+
},
|
|
8269
|
+
{ expectedLeaseId: ourLeaseId }
|
|
8270
|
+
);
|
|
7942
8271
|
if (result.status !== "success") {
|
|
7943
8272
|
resultFailures.push({
|
|
7944
8273
|
taskId: dispatch.taskId,
|
|
@@ -8046,6 +8375,16 @@ function buildKanbanFleetTaskPrompt(board, task, lease) {
|
|
|
8046
8375
|
task.origin.phaseId ? `phaseId: ${task.origin.phaseId}` : "",
|
|
8047
8376
|
task.origin.taskId ? `sourceTaskId: ${task.origin.taskId}` : ""
|
|
8048
8377
|
].filter(Boolean).join("\n") : "";
|
|
8378
|
+
const boundaries = [
|
|
8379
|
+
board.boundary?.enabled ? `board: ${describeKanbanBoundary(board.boundary)}` : "",
|
|
8380
|
+
task.boundary?.enabled ? `task: ${describeKanbanBoundary(task.boundary)}` : "",
|
|
8381
|
+
...(board.boundary?.allow ?? []).map(
|
|
8382
|
+
(selector) => `board allow ${selector.access} ${selector.kind}:${selector.path}`
|
|
8383
|
+
),
|
|
8384
|
+
...(task.boundary?.allow ?? []).map(
|
|
8385
|
+
(selector) => `task allow ${selector.access} ${selector.kind}:${selector.path}`
|
|
8386
|
+
)
|
|
8387
|
+
].filter(Boolean);
|
|
8049
8388
|
return [
|
|
8050
8389
|
"You are processing a claimed WrongStack Kanban task.",
|
|
8051
8390
|
"",
|
|
@@ -8068,6 +8407,8 @@ ${checks}` : "",
|
|
|
8068
8407
|
metrics ? `Goal metrics:
|
|
8069
8408
|
${metrics}` : "",
|
|
8070
8409
|
task.labels?.length ? `Labels: ${task.labels.join(", ")}` : "",
|
|
8410
|
+
boundaries.length ? `BOUNDARY CONTRACT (enforced by the tool runtime; do not attempt to bypass):
|
|
8411
|
+
${boundaries.map((line) => `- ${line}`).join("\n")}` : "",
|
|
8071
8412
|
"Lease contract (Sprint 1):",
|
|
8072
8413
|
`- leaseId: ${lease.leaseId}`,
|
|
8073
8414
|
`- claimedAt: ${task.assignment?.claimedAt ?? "<unknown>"}`,
|
|
@@ -8083,10 +8424,21 @@ ${metrics}` : "",
|
|
|
8083
8424
|
"Work this task end-to-end. Treat the board as a live plan, not a frozen assignment snapshot.",
|
|
8084
8425
|
`Before material work and whenever evidence changes the plan, call kanban with action "get_board" and boardId "${board.id}" to reassess current tasks, dependencies, priorities, and peer changes.`,
|
|
8085
8426
|
"You may use kanban add_task, update_task, split_task, merge_tasks, move_task, delete_task, or dependency actions when reassessment shows the plan should change. Preserve traceability and do not keep obsolete work merely because it existed at dispatch time.",
|
|
8427
|
+
"",
|
|
8428
|
+
"FIELD MAINTENANCE \u2014 keep every task detail current:",
|
|
8429
|
+
'- Call "update_task" to refresh description, priority, type, labels, or estimatedHours as the work evolves.',
|
|
8430
|
+
'- Call "add_note" with author and content to record progress, decisions, or roadblock context.',
|
|
8431
|
+
'- Call "add_check" / "update_check" with description and status (pending/passed/failed) to track acceptance criteria.',
|
|
8432
|
+
'- Call "add_goal_metric" / "update_goal_metric" with name and current/target to report measurable progress.',
|
|
8433
|
+
'- Call "add_link" with url and type (issue/pr/doc/commit/design/file/url/other) to attach evidence.',
|
|
8434
|
+
'- Call "update_task" to set actualHours when you have a final time estimate.',
|
|
8435
|
+
"Do not leave stale or placeholder field values \u2014 the kanban board is the shared source of truth for every stakeholder.",
|
|
8436
|
+
"",
|
|
8437
|
+
"LEASE & LIFECYCLE:",
|
|
8086
8438
|
"Every successful board mutation updates shared pending work and notifies the owning session. Re-read the board after mutations and adapt to changes made by other agents; never assume your initial todo list remains authoritative.",
|
|
8087
|
-
`When you start, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}",
|
|
8088
|
-
`To stay alive in the queue, call kanban with action "heartbeat_assignment", boardId "${board.id}", taskId "${task.id}",
|
|
8089
|
-
`When you finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}",
|
|
8439
|
+
`When you start, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", assignmentStatus "running", and expectedLeaseId "${lease.leaseId}". Include subagentId and runTaskId when you have them. The expectedLeaseId fence makes the write a safe no-op if recover_stale already reassigned the task because your lease expired.`,
|
|
8440
|
+
`To stay alive in the queue, call kanban with action "heartbeat_assignment", boardId "${board.id}", taskId "${task.id}", heartbeatAt set to the current time, and expectedLeaseId "${lease.leaseId}". Cadence must be <= heartbeatIntervalMs and well before leaseExpiresAt. The expectedLeaseId fence makes the heartbeat a safe no-op if the lease was reassigned, so a stale worker cannot renew a successor's lease.`,
|
|
8441
|
+
`When you finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", assignmentStatus "completed" or "failed", and expectedLeaseId "${lease.leaseId}". Include lastResult or error. Populate every relevant detail field before the terminal mark_assignment so the card's final state is complete.`,
|
|
8090
8442
|
`If you cannot finish in time, call kanban with action "heartbeat_assignment" to extend the lease, or with action "release_task" to release so another worker can claim. Do NOT silently abandon the assignment.`,
|
|
8091
8443
|
'On failure the host may call kanban with action "recover_stale" (mode: retry/release/fail); respect its decisions and do not duplicate work in parallel.',
|
|
8092
8444
|
"When finished, report what changed, what you verified, and any remaining blockers."
|
|
@@ -8785,18 +9137,20 @@ function isTextBlock(b) {
|
|
|
8785
9137
|
// src/types/provider.ts
|
|
8786
9138
|
var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
|
|
8787
9139
|
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
9140
|
+
var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
|
|
8788
9141
|
function classifyProviderError(status, body, message) {
|
|
8789
9142
|
const type = body?.type;
|
|
9143
|
+
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
8790
9144
|
if (status === 0) return "network";
|
|
8791
9145
|
if (status === 408) return "timeout";
|
|
8792
9146
|
if (status === 599) return "stream_hang";
|
|
9147
|
+
if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
|
|
8793
9148
|
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
8794
9149
|
if (type === "overloaded_error" || status === 529) return "overloaded";
|
|
8795
9150
|
if (status >= 500) return "server";
|
|
8796
9151
|
if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
|
|
8797
9152
|
return "auth";
|
|
8798
9153
|
}
|
|
8799
|
-
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
8800
9154
|
if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
|
|
8801
9155
|
if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
|
|
8802
9156
|
return "context_overflow";
|
|
@@ -8809,6 +9163,7 @@ function isRetryableKind(kind) {
|
|
|
8809
9163
|
}
|
|
8810
9164
|
var RETRYABLE_BY_KIND = {
|
|
8811
9165
|
rate_limit: true,
|
|
9166
|
+
quota_exhausted: false,
|
|
8812
9167
|
overloaded: true,
|
|
8813
9168
|
server: true,
|
|
8814
9169
|
timeout: true,
|
|
@@ -8887,6 +9242,7 @@ var KIND_TO_CODE = {
|
|
|
8887
9242
|
network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
8888
9243
|
timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
8889
9244
|
rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
9245
|
+
quota_exhausted: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
8890
9246
|
auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
|
|
8891
9247
|
overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
|
|
8892
9248
|
context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
|
|
@@ -8986,6 +9342,7 @@ function classifySubagentError(err, hints = {}) {
|
|
|
8986
9342
|
function providerErrorToSubagentError(err, message, cause) {
|
|
8987
9343
|
switch (err.kind) {
|
|
8988
9344
|
case "rate_limit":
|
|
9345
|
+
case "quota_exhausted":
|
|
8989
9346
|
return {
|
|
8990
9347
|
kind: "provider_rate_limit",
|
|
8991
9348
|
message,
|
|
@@ -9393,7 +9750,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9393
9750
|
taskIds.map((id) => {
|
|
9394
9751
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
9395
9752
|
if (cached) return cached;
|
|
9396
|
-
return new Promise((
|
|
9753
|
+
return new Promise((resolve14, reject) => {
|
|
9397
9754
|
const timeout = setTimeout(() => {
|
|
9398
9755
|
this.off("task.completed", handler);
|
|
9399
9756
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -9402,7 +9759,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9402
9759
|
if (result.taskId === id) {
|
|
9403
9760
|
clearTimeout(timeout);
|
|
9404
9761
|
this.off("task.completed", handler);
|
|
9405
|
-
|
|
9762
|
+
resolve14(result);
|
|
9406
9763
|
}
|
|
9407
9764
|
};
|
|
9408
9765
|
this.on("task.completed", handler);
|
|
@@ -9427,13 +9784,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9427
9784
|
const done = new Set(completed.map((r) => r.taskId));
|
|
9428
9785
|
return { completed, pending: taskIds.filter((id) => !done.has(id)) };
|
|
9429
9786
|
}
|
|
9430
|
-
return new Promise((
|
|
9787
|
+
return new Promise((resolve14) => {
|
|
9431
9788
|
let timer;
|
|
9432
9789
|
const handler = ({ result }) => {
|
|
9433
9790
|
if (!ids.has(result.taskId)) return;
|
|
9434
9791
|
if (timer) clearTimeout(timer);
|
|
9435
9792
|
this.off("task.completed", handler);
|
|
9436
|
-
|
|
9793
|
+
resolve14({
|
|
9437
9794
|
completed: [result],
|
|
9438
9795
|
pending: taskIds.filter((id) => id !== result.taskId)
|
|
9439
9796
|
});
|
|
@@ -9441,7 +9798,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9441
9798
|
if (opts?.timeoutMs !== void 0) {
|
|
9442
9799
|
timer = setTimeout(() => {
|
|
9443
9800
|
this.off("task.completed", handler);
|
|
9444
|
-
|
|
9801
|
+
resolve14({ completed: [], pending: [...taskIds], timedOut: true });
|
|
9445
9802
|
}, opts.timeoutMs);
|
|
9446
9803
|
}
|
|
9447
9804
|
this.on("task.completed", handler);
|
|
@@ -9723,12 +10080,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9723
10080
|
}
|
|
9724
10081
|
return new Promise((resolveDecision) => {
|
|
9725
10082
|
let settled = false;
|
|
9726
|
-
const
|
|
10083
|
+
const resolve14 = (d) => {
|
|
9727
10084
|
if (settled) return;
|
|
9728
10085
|
settled = true;
|
|
9729
10086
|
resolveDecision(d);
|
|
9730
10087
|
};
|
|
9731
|
-
const fallback = setTimeout(() =>
|
|
10088
|
+
const fallback = setTimeout(() => resolve14("stop"), DECISION_TIMEOUT_MS);
|
|
9732
10089
|
const sessionId = this.currentSessionId();
|
|
9733
10090
|
budget._events?.emit("budget.threshold_reached", {
|
|
9734
10091
|
...sessionId ? { sessionId } : {},
|
|
@@ -9746,11 +10103,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
9746
10103
|
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
9747
10104
|
extend: (extra) => {
|
|
9748
10105
|
clearTimeout(fallback);
|
|
9749
|
-
queueMicrotask(() =>
|
|
10106
|
+
queueMicrotask(() => resolve14({ extend: extra }));
|
|
9750
10107
|
},
|
|
9751
10108
|
deny: () => {
|
|
9752
10109
|
clearTimeout(fallback);
|
|
9753
|
-
|
|
10110
|
+
resolve14("stop");
|
|
9754
10111
|
}
|
|
9755
10112
|
});
|
|
9756
10113
|
});
|
|
@@ -10375,6 +10732,10 @@ var Director = class _Director {
|
|
|
10375
10732
|
largeAnswerStore;
|
|
10376
10733
|
/** Shared provider/model status tracker, or undefined. */
|
|
10377
10734
|
statusTracker;
|
|
10735
|
+
/** Session/leader's provider id — absolute last-resort fallback for every spawn. */
|
|
10736
|
+
sessionProvider;
|
|
10737
|
+
/** Session/leader's model id — paired with sessionProvider above. */
|
|
10738
|
+
sessionModel;
|
|
10378
10739
|
constructor(opts) {
|
|
10379
10740
|
this.id = opts.config.coordinatorId || randomUUID6();
|
|
10380
10741
|
this.brain = opts.brain;
|
|
@@ -10415,6 +10776,8 @@ var Director = class _Director {
|
|
|
10415
10776
|
this.fleetManager = opts.fleetManager;
|
|
10416
10777
|
this.statusTracker = opts.statusTracker;
|
|
10417
10778
|
this.logger = opts.logger;
|
|
10779
|
+
this.sessionProvider = opts.sessionProvider;
|
|
10780
|
+
this.sessionModel = opts.sessionModel;
|
|
10418
10781
|
if (this.sharedScratchpadPath) {
|
|
10419
10782
|
void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
10420
10783
|
}
|
|
@@ -10923,20 +11286,31 @@ var Director = class _Director {
|
|
|
10923
11286
|
return result.subagentId;
|
|
10924
11287
|
}
|
|
10925
11288
|
resolveSpawnModel(config) {
|
|
11289
|
+
if (config.provider?.trim() === "") config.provider = void 0;
|
|
11290
|
+
if (config.model?.trim() === "") config.model = void 0;
|
|
10926
11291
|
if (!config.model && this.modelMatrix) {
|
|
10927
11292
|
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
10928
11293
|
const resolution = resolveModelMatrixResolution(matrix, config.role);
|
|
10929
11294
|
const entry = resolution?.source === "default" && roleNeedsIndependentReviewModel(config.role) ? void 0 : resolution?.entry;
|
|
10930
|
-
if (entry
|
|
10931
|
-
config.model = entry.model;
|
|
11295
|
+
if (entry) {
|
|
11296
|
+
if (entry.model) config.model = entry.model;
|
|
10932
11297
|
if (entry.provider) config.provider = entry.provider;
|
|
10933
11298
|
if (entry.fallbackProfile) config.fallbackProfile = entry.fallbackProfile;
|
|
10934
11299
|
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
10935
|
-
} else if (entry?.fallbackProfile) {
|
|
10936
|
-
config.fallbackProfile = entry.fallbackProfile;
|
|
10937
|
-
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
10938
11300
|
}
|
|
10939
11301
|
}
|
|
11302
|
+
if (!config.provider && this.sessionProvider) {
|
|
11303
|
+
config.provider = this.sessionProvider;
|
|
11304
|
+
this.logger?.info(
|
|
11305
|
+
`spawn: provider="${config.provider}" for role "${config.role ?? "?"}" fell back to session provider (matrix resolution left it undefined)`
|
|
11306
|
+
);
|
|
11307
|
+
}
|
|
11308
|
+
if (!config.model && this.sessionModel) {
|
|
11309
|
+
config.model = this.sessionModel;
|
|
11310
|
+
this.logger?.info(
|
|
11311
|
+
`spawn: model="${config.model}" for role "${config.role ?? "?"}" fell back to session model (matrix resolution left it undefined)`
|
|
11312
|
+
);
|
|
11313
|
+
}
|
|
10940
11314
|
if (this.statusTracker && config.provider && config.model) {
|
|
10941
11315
|
if (!this.statusTracker.isAvailable(config.provider, config.model)) {
|
|
10942
11316
|
this.logger?.warn(
|
|
@@ -11151,7 +11525,7 @@ var Director = class _Director {
|
|
|
11151
11525
|
})),
|
|
11152
11526
|
usage: this.usage.snapshot()
|
|
11153
11527
|
};
|
|
11154
|
-
await fsp5.mkdir(
|
|
11528
|
+
await fsp5.mkdir(path8.dirname(this.manifestPath), { recursive: true });
|
|
11155
11529
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
11156
11530
|
return this.manifestPath;
|
|
11157
11531
|
}
|
|
@@ -11346,11 +11720,11 @@ var Director = class _Director {
|
|
|
11346
11720
|
if (cached) return cached;
|
|
11347
11721
|
const existing = this.taskWaiters.get(id);
|
|
11348
11722
|
if (existing) return existing.promise;
|
|
11349
|
-
let
|
|
11723
|
+
let resolve14;
|
|
11350
11724
|
const promise = new Promise((res) => {
|
|
11351
|
-
|
|
11725
|
+
resolve14 = res;
|
|
11352
11726
|
});
|
|
11353
|
-
this.taskWaiters.set(id, { promise, resolve:
|
|
11727
|
+
this.taskWaiters.set(id, { promise, resolve: resolve14 });
|
|
11354
11728
|
return promise;
|
|
11355
11729
|
})
|
|
11356
11730
|
);
|
|
@@ -11379,10 +11753,10 @@ var Director = class _Director {
|
|
|
11379
11753
|
pending: taskIds.filter((id) => !done.has(id))
|
|
11380
11754
|
});
|
|
11381
11755
|
}
|
|
11382
|
-
return new Promise((
|
|
11756
|
+
return new Promise((resolve14) => {
|
|
11383
11757
|
const entry = {
|
|
11384
11758
|
ids: new Set(taskIds),
|
|
11385
|
-
resolve: (r) =>
|
|
11759
|
+
resolve: (r) => resolve14({
|
|
11386
11760
|
completed: [r],
|
|
11387
11761
|
pending: taskIds.filter((id) => id !== r.taskId)
|
|
11388
11762
|
})
|
|
@@ -11390,7 +11764,7 @@ var Director = class _Director {
|
|
|
11390
11764
|
if (opts?.timeoutMs !== void 0) {
|
|
11391
11765
|
entry.timer = setTimeout(() => {
|
|
11392
11766
|
this.anyWaiters.delete(entry);
|
|
11393
|
-
|
|
11767
|
+
resolve14({ completed: [], pending: [...taskIds], timedOut: true });
|
|
11394
11768
|
}, opts.timeoutMs);
|
|
11395
11769
|
}
|
|
11396
11770
|
this.anyWaiters.add(entry);
|
|
@@ -11555,7 +11929,7 @@ var Director = class _Director {
|
|
|
11555
11929
|
*/
|
|
11556
11930
|
async readSession(subagentId, tail) {
|
|
11557
11931
|
if (!this.sessionsRoot) return null;
|
|
11558
|
-
const filePath =
|
|
11932
|
+
const filePath = path8.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
11559
11933
|
let raw;
|
|
11560
11934
|
try {
|
|
11561
11935
|
raw = await fsp5.readFile(filePath, "utf8");
|
|
@@ -11709,19 +12083,19 @@ var Director = class _Director {
|
|
|
11709
12083
|
};
|
|
11710
12084
|
|
|
11711
12085
|
// src/coordination/director-session.ts
|
|
11712
|
-
import * as
|
|
12086
|
+
import * as path13 from "node:path";
|
|
11713
12087
|
|
|
11714
12088
|
// src/storage/session-store.ts
|
|
11715
12089
|
import { createHash as createHash4 } from "node:crypto";
|
|
11716
12090
|
import { createReadStream } from "node:fs";
|
|
11717
12091
|
import * as fsp9 from "node:fs/promises";
|
|
11718
|
-
import * as
|
|
12092
|
+
import * as path12 from "node:path";
|
|
11719
12093
|
import { createInterface } from "node:readline";
|
|
11720
12094
|
|
|
11721
12095
|
// src/storage/file-session-writer.ts
|
|
11722
12096
|
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
11723
12097
|
import * as fsp6 from "node:fs/promises";
|
|
11724
|
-
import * as
|
|
12098
|
+
import * as path9 from "node:path";
|
|
11725
12099
|
|
|
11726
12100
|
// src/storage/session-helpers.ts
|
|
11727
12101
|
function userInputTitle(content) {
|
|
@@ -11738,7 +12112,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
11738
12112
|
this.meta = meta;
|
|
11739
12113
|
this.events = events;
|
|
11740
12114
|
this.resumed = opts.resumed ?? false;
|
|
11741
|
-
this.manifestFile = opts.dir ?
|
|
12115
|
+
this.manifestFile = opts.dir ? path9.join(opts.dir, `${path9.basename(id)}.summary.json`) : "";
|
|
11742
12116
|
this.filePath = opts.filePath ?? "";
|
|
11743
12117
|
this.secretScrubber = opts.secretScrubber;
|
|
11744
12118
|
this.checkpointCas = opts.checkpointCas;
|
|
@@ -12520,7 +12894,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12520
12894
|
import { spawn } from "node:child_process";
|
|
12521
12895
|
import { createHash as createHash2, randomUUID as randomUUID7 } from "node:crypto";
|
|
12522
12896
|
import * as fsp7 from "node:fs/promises";
|
|
12523
|
-
import * as
|
|
12897
|
+
import * as path10 from "node:path";
|
|
12524
12898
|
|
|
12525
12899
|
// src/storage/storage-concurrency.ts
|
|
12526
12900
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
@@ -12551,29 +12925,29 @@ function sha256(content) {
|
|
|
12551
12925
|
return createHash2("sha256").update(content).digest("hex");
|
|
12552
12926
|
}
|
|
12553
12927
|
function isInside(root, target) {
|
|
12554
|
-
const
|
|
12555
|
-
return
|
|
12928
|
+
const relative6 = path10.relative(root, target);
|
|
12929
|
+
return relative6 === "" || !relative6.startsWith("..") && !path10.isAbsolute(relative6);
|
|
12556
12930
|
}
|
|
12557
12931
|
function normalizeRelative(input) {
|
|
12558
|
-
if (!input ||
|
|
12932
|
+
if (!input || path10.isAbsolute(input)) return null;
|
|
12559
12933
|
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
12560
|
-
const resolved =
|
|
12934
|
+
const resolved = path10.posix.normalize(normalized);
|
|
12561
12935
|
if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
|
|
12562
12936
|
return resolved;
|
|
12563
12937
|
}
|
|
12564
12938
|
function parseNulPaths(output) {
|
|
12565
12939
|
return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
|
|
12566
12940
|
}
|
|
12567
|
-
function isWrongStackWorktreePath(
|
|
12568
|
-
return
|
|
12941
|
+
function isWrongStackWorktreePath(relative6) {
|
|
12942
|
+
return relative6 === ".wrongstack/worktrees" || relative6.startsWith(".wrongstack/worktrees/");
|
|
12569
12943
|
}
|
|
12570
12944
|
var SessionCheckpointCas = class {
|
|
12571
12945
|
rootDir;
|
|
12572
12946
|
projectRoot;
|
|
12573
12947
|
runGit;
|
|
12574
12948
|
constructor(opts) {
|
|
12575
|
-
this.rootDir =
|
|
12576
|
-
this.projectRoot =
|
|
12949
|
+
this.rootDir = path10.resolve(opts.rootDir);
|
|
12950
|
+
this.projectRoot = path10.resolve(opts.projectRoot);
|
|
12577
12951
|
this.runGit = opts.runGit ?? defaultRunGit;
|
|
12578
12952
|
}
|
|
12579
12953
|
async capture(_sessionId, _promptIndex) {
|
|
@@ -12591,60 +12965,60 @@ var SessionCheckpointCas = class {
|
|
|
12591
12965
|
}
|
|
12592
12966
|
const relativePaths = [
|
|
12593
12967
|
.../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
|
|
12594
|
-
].filter((
|
|
12968
|
+
].filter((relative6) => !isWrongStackWorktreePath(relative6)).sort();
|
|
12595
12969
|
const unresolved = [];
|
|
12596
12970
|
let scheduledBytes = 0;
|
|
12597
12971
|
const captured = await mapWithConcurrency(
|
|
12598
12972
|
relativePaths,
|
|
12599
12973
|
CAPTURE_CONCURRENCY,
|
|
12600
|
-
async (
|
|
12601
|
-
const absolute =
|
|
12974
|
+
async (relative6) => {
|
|
12975
|
+
const absolute = path10.resolve(this.projectRoot, ...relative6.split("/"));
|
|
12602
12976
|
if (!isInside(this.projectRoot, absolute)) {
|
|
12603
|
-
unresolved.push({ path:
|
|
12977
|
+
unresolved.push({ path: relative6, reason: "path escapes project root" });
|
|
12604
12978
|
return null;
|
|
12605
12979
|
}
|
|
12606
12980
|
try {
|
|
12607
|
-
const
|
|
12608
|
-
if (
|
|
12981
|
+
const stat13 = await fsp7.lstat(absolute);
|
|
12982
|
+
if (stat13.isSymbolicLink()) {
|
|
12609
12983
|
const linkTarget = await fsp7.readlink(absolute);
|
|
12610
|
-
const resolvedLink =
|
|
12611
|
-
if (
|
|
12984
|
+
const resolvedLink = path10.resolve(path10.dirname(absolute), linkTarget);
|
|
12985
|
+
if (path10.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
12612
12986
|
unresolved.push({
|
|
12613
|
-
path:
|
|
12987
|
+
path: relative6,
|
|
12614
12988
|
reason: "symlink target escapes project root"
|
|
12615
12989
|
});
|
|
12616
12990
|
return null;
|
|
12617
12991
|
}
|
|
12618
|
-
return { path:
|
|
12992
|
+
return { path: relative6, state: "symlink", linkTarget };
|
|
12619
12993
|
}
|
|
12620
|
-
if (!
|
|
12621
|
-
unresolved.push({ path:
|
|
12994
|
+
if (!stat13.isFile()) {
|
|
12995
|
+
unresolved.push({ path: relative6, reason: "changed path is not a regular file" });
|
|
12622
12996
|
return null;
|
|
12623
12997
|
}
|
|
12624
|
-
if (
|
|
12998
|
+
if (stat13.size > MAX_BLOB_BYTES) {
|
|
12625
12999
|
unresolved.push({
|
|
12626
|
-
path:
|
|
13000
|
+
path: relative6,
|
|
12627
13001
|
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
12628
13002
|
});
|
|
12629
13003
|
return null;
|
|
12630
13004
|
}
|
|
12631
|
-
if (scheduledBytes +
|
|
13005
|
+
if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
|
|
12632
13006
|
unresolved.push({
|
|
12633
|
-
path:
|
|
13007
|
+
path: relative6,
|
|
12634
13008
|
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
12635
13009
|
});
|
|
12636
13010
|
return null;
|
|
12637
13011
|
}
|
|
12638
|
-
scheduledBytes +=
|
|
13012
|
+
scheduledBytes += stat13.size;
|
|
12639
13013
|
const content = await fsp7.readFile(absolute);
|
|
12640
13014
|
const blobHash = sha256(content);
|
|
12641
13015
|
await this.putBlob(blobHash, content);
|
|
12642
|
-
return { path:
|
|
13016
|
+
return { path: relative6, state: "file", blobHash, mode: stat13.mode & 511 };
|
|
12643
13017
|
} catch (err) {
|
|
12644
13018
|
if (err.code === "ENOENT") {
|
|
12645
|
-
return { path:
|
|
13019
|
+
return { path: relative6, state: "absent" };
|
|
12646
13020
|
}
|
|
12647
|
-
unresolved.push({ path:
|
|
13021
|
+
unresolved.push({ path: relative6, reason: toErrorMessage(err) });
|
|
12648
13022
|
return null;
|
|
12649
13023
|
}
|
|
12650
13024
|
}
|
|
@@ -12670,7 +13044,7 @@ var SessionCheckpointCas = class {
|
|
|
12670
13044
|
};
|
|
12671
13045
|
}
|
|
12672
13046
|
async materialize(checkpoint, targetRoot) {
|
|
12673
|
-
const target =
|
|
13047
|
+
const target = path10.resolve(targetRoot);
|
|
12674
13048
|
if (target === this.projectRoot) {
|
|
12675
13049
|
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
12676
13050
|
}
|
|
@@ -12710,10 +13084,10 @@ var SessionCheckpointCas = class {
|
|
|
12710
13084
|
try {
|
|
12711
13085
|
const output = await this.safeOutputPath(target, realTarget, entry.path);
|
|
12712
13086
|
if (entry.state === "symlink") {
|
|
12713
|
-
if (
|
|
13087
|
+
if (path10.isAbsolute(entry.linkTarget)) {
|
|
12714
13088
|
throw new Error("absolute symlink target refused");
|
|
12715
13089
|
}
|
|
12716
|
-
const resolvedLink =
|
|
13090
|
+
const resolvedLink = path10.resolve(path10.dirname(output), entry.linkTarget);
|
|
12717
13091
|
if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
|
|
12718
13092
|
}
|
|
12719
13093
|
prepared.push({
|
|
@@ -12741,7 +13115,7 @@ var SessionCheckpointCas = class {
|
|
|
12741
13115
|
await fsp7.unlink(output).catch((err) => {
|
|
12742
13116
|
if (err.code !== "ENOENT") throw err;
|
|
12743
13117
|
});
|
|
12744
|
-
await fsp7.mkdir(
|
|
13118
|
+
await fsp7.mkdir(path10.dirname(output), { recursive: true });
|
|
12745
13119
|
await fsp7.symlink(entry.linkTarget, output);
|
|
12746
13120
|
writtenFiles.push(entry.path);
|
|
12747
13121
|
continue;
|
|
@@ -12758,15 +13132,15 @@ var SessionCheckpointCas = class {
|
|
|
12758
13132
|
}
|
|
12759
13133
|
objectPath(hash2) {
|
|
12760
13134
|
if (!HASH_RE.test(hash2)) throw new Error(`Invalid CAS object hash: ${hash2}`);
|
|
12761
|
-
return
|
|
13135
|
+
return path10.join(this.rootDir, "objects", hash2.slice(0, 2), hash2.slice(2));
|
|
12762
13136
|
}
|
|
12763
13137
|
manifestPath(hash2) {
|
|
12764
13138
|
if (!HASH_RE.test(hash2)) throw new Error(`Invalid checkpoint manifest hash: ${hash2}`);
|
|
12765
|
-
return
|
|
13139
|
+
return path10.join(this.rootDir, "manifests", `${hash2}.json`);
|
|
12766
13140
|
}
|
|
12767
13141
|
async putBlob(hash2, content) {
|
|
12768
13142
|
const target = this.objectPath(hash2);
|
|
12769
|
-
await fsp7.mkdir(
|
|
13143
|
+
await fsp7.mkdir(path10.dirname(target), { recursive: true });
|
|
12770
13144
|
try {
|
|
12771
13145
|
const existing = await fsp7.readFile(target);
|
|
12772
13146
|
if (sha256(existing) !== hash2) throw new Error(`Corrupt CAS object collision: ${hash2}`);
|
|
@@ -12774,9 +13148,9 @@ var SessionCheckpointCas = class {
|
|
|
12774
13148
|
} catch (err) {
|
|
12775
13149
|
if (err.code !== "ENOENT") throw err;
|
|
12776
13150
|
}
|
|
12777
|
-
const temp =
|
|
12778
|
-
|
|
12779
|
-
`.${
|
|
13151
|
+
const temp = path10.join(
|
|
13152
|
+
path10.dirname(target),
|
|
13153
|
+
`.${path10.basename(target)}.${process.pid}.${randomUUID7()}.tmp`
|
|
12780
13154
|
);
|
|
12781
13155
|
let handle;
|
|
12782
13156
|
try {
|
|
@@ -12834,10 +13208,10 @@ var SessionCheckpointCas = class {
|
|
|
12834
13208
|
}
|
|
12835
13209
|
return parsed;
|
|
12836
13210
|
}
|
|
12837
|
-
async safeOutputPath(target, realTarget,
|
|
12838
|
-
const normalized = normalizeRelative(
|
|
13211
|
+
async safeOutputPath(target, realTarget, relative6) {
|
|
13212
|
+
const normalized = normalizeRelative(relative6);
|
|
12839
13213
|
if (!normalized) throw new Error("invalid relative path");
|
|
12840
|
-
const output =
|
|
13214
|
+
const output = path10.resolve(target, ...normalized.split("/"));
|
|
12841
13215
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
12842
13216
|
let probe = output;
|
|
12843
13217
|
for (; ; ) {
|
|
@@ -12847,7 +13221,7 @@ var SessionCheckpointCas = class {
|
|
|
12847
13221
|
return output;
|
|
12848
13222
|
} catch (err) {
|
|
12849
13223
|
if (err.code !== "ENOENT") throw err;
|
|
12850
|
-
const parent =
|
|
13224
|
+
const parent = path10.dirname(probe);
|
|
12851
13225
|
if (parent === probe) throw err;
|
|
12852
13226
|
probe = parent;
|
|
12853
13227
|
}
|
|
@@ -12855,7 +13229,7 @@ var SessionCheckpointCas = class {
|
|
|
12855
13229
|
}
|
|
12856
13230
|
};
|
|
12857
13231
|
function defaultRunGit(args, cwd) {
|
|
12858
|
-
return new Promise((
|
|
13232
|
+
return new Promise((resolve14) => {
|
|
12859
13233
|
const stdoutChunks = [];
|
|
12860
13234
|
const stderrChunks = [];
|
|
12861
13235
|
let stdoutBytes = 0;
|
|
@@ -12898,8 +13272,8 @@ function defaultRunGit(args, cwd) {
|
|
|
12898
13272
|
stdoutTruncated,
|
|
12899
13273
|
stderrTruncated
|
|
12900
13274
|
});
|
|
12901
|
-
child.on("error", (err) =>
|
|
12902
|
-
child.on("close", (code) =>
|
|
13275
|
+
child.on("error", (err) => resolve14(result(1, err.message)));
|
|
13276
|
+
child.on("close", (code) => resolve14(result(code ?? 1)));
|
|
12903
13277
|
});
|
|
12904
13278
|
}
|
|
12905
13279
|
|
|
@@ -12913,13 +13287,13 @@ function generateSessionId(startedAt, _model) {
|
|
|
12913
13287
|
// src/storage/session-resume-validation.ts
|
|
12914
13288
|
import { createHash as createHash3 } from "node:crypto";
|
|
12915
13289
|
import * as fsp8 from "node:fs/promises";
|
|
12916
|
-
import * as
|
|
13290
|
+
import * as path11 from "node:path";
|
|
12917
13291
|
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
12918
13292
|
var VALIDATION_CONCURRENCY = 8;
|
|
12919
13293
|
var NOTICE_PATH_LIMIT = 20;
|
|
12920
13294
|
function isInside2(root, target) {
|
|
12921
|
-
const
|
|
12922
|
-
return
|
|
13295
|
+
const relative6 = path11.relative(root, target);
|
|
13296
|
+
return relative6 === "" || !relative6.startsWith("..") && !path11.isAbsolute(relative6);
|
|
12923
13297
|
}
|
|
12924
13298
|
function errno(err) {
|
|
12925
13299
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -12930,7 +13304,7 @@ function latestObservations(events, projectRoot) {
|
|
|
12930
13304
|
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)) {
|
|
12931
13305
|
continue;
|
|
12932
13306
|
}
|
|
12933
|
-
const normalized =
|
|
13307
|
+
const normalized = path11.resolve(projectRoot, event.path);
|
|
12934
13308
|
latest.set(normalized, {
|
|
12935
13309
|
path: normalized,
|
|
12936
13310
|
hash: event.hash.toLowerCase(),
|
|
@@ -12967,11 +13341,11 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
12967
13341
|
};
|
|
12968
13342
|
}
|
|
12969
13343
|
try {
|
|
12970
|
-
const
|
|
12971
|
-
if (!
|
|
13344
|
+
const stat13 = await fsp8.stat(realFile);
|
|
13345
|
+
if (!stat13.isFile()) {
|
|
12972
13346
|
return { ...base, status: "unreadable", detail: "Path is no longer a regular file." };
|
|
12973
13347
|
}
|
|
12974
|
-
if (
|
|
13348
|
+
if (stat13.size > MAX_REVALIDATE_BYTES) {
|
|
12975
13349
|
return {
|
|
12976
13350
|
...base,
|
|
12977
13351
|
status: "unreadable",
|
|
@@ -12988,7 +13362,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
12988
13362
|
}
|
|
12989
13363
|
}
|
|
12990
13364
|
async function validateResumeFileObservations(events, projectRoot) {
|
|
12991
|
-
const lexicalRoot =
|
|
13365
|
+
const lexicalRoot = path11.resolve(projectRoot);
|
|
12992
13366
|
const realRoot = await fsp8.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
12993
13367
|
const observations = latestObservations(events, lexicalRoot);
|
|
12994
13368
|
const results = await mapWithConcurrency(
|
|
@@ -13004,10 +13378,10 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
13004
13378
|
}
|
|
13005
13379
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
13006
13380
|
if (validation.staleFiles.length === 0) return null;
|
|
13007
|
-
const root =
|
|
13381
|
+
const root = path11.resolve(projectRoot);
|
|
13008
13382
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
13009
|
-
const
|
|
13010
|
-
const display = isInside2(root, entry.path) ?
|
|
13383
|
+
const relative6 = path11.relative(root, entry.path);
|
|
13384
|
+
const display = isInside2(root, entry.path) ? relative6 || "." : entry.path;
|
|
13011
13385
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
13012
13386
|
});
|
|
13013
13387
|
const omitted = validation.staleFiles.length - shown.length;
|
|
@@ -13140,9 +13514,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13140
13514
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
13141
13515
|
constructor(opts) {
|
|
13142
13516
|
this.dir = opts.dir;
|
|
13143
|
-
this.projectRoot = opts.projectRoot ?
|
|
13517
|
+
this.projectRoot = opts.projectRoot ? path12.resolve(opts.projectRoot) : void 0;
|
|
13144
13518
|
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
13145
|
-
rootDir:
|
|
13519
|
+
rootDir: path12.join(this.dir, "_cas"),
|
|
13146
13520
|
projectRoot: this.projectRoot
|
|
13147
13521
|
}) : void 0;
|
|
13148
13522
|
this.events = opts.events;
|
|
@@ -13208,17 +13582,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13208
13582
|
}
|
|
13209
13583
|
/** Absolute path to the session index file. */
|
|
13210
13584
|
get indexFile() {
|
|
13211
|
-
return
|
|
13585
|
+
return path12.join(this.dir, "_index.jsonl");
|
|
13212
13586
|
}
|
|
13213
13587
|
/** Join session ID to its absolute path within the store directory. */
|
|
13214
13588
|
sessionPath(id, ext) {
|
|
13215
13589
|
return sessionScopedPath(this.dir, id, ext);
|
|
13216
13590
|
}
|
|
13217
13591
|
shardManifestPath(shardKey) {
|
|
13218
|
-
return shardKey ?
|
|
13592
|
+
return shardKey ? path12.join(this.dir, shardKey, "_manifest.json") : path12.join(this.dir, "_manifest.json");
|
|
13219
13593
|
}
|
|
13220
13594
|
shardKeyForSessionId(id) {
|
|
13221
|
-
const dirName =
|
|
13595
|
+
const dirName = path12.dirname(id);
|
|
13222
13596
|
return dirName === "." ? "" : dirName;
|
|
13223
13597
|
}
|
|
13224
13598
|
invalidateShardManifestBySessionId(id) {
|
|
@@ -13230,7 +13604,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13230
13604
|
* subdirectory so sessions group naturally by day.
|
|
13231
13605
|
*/
|
|
13232
13606
|
async ensureShardDir(id) {
|
|
13233
|
-
const dirPath =
|
|
13607
|
+
const dirPath = path12.dirname(sessionScopedPath(this.dir, id, ""));
|
|
13234
13608
|
await ensureDir(dirPath);
|
|
13235
13609
|
return dirPath;
|
|
13236
13610
|
}
|
|
@@ -13394,7 +13768,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13394
13768
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
13395
13769
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
13396
13770
|
// sessions root (where summaryFor() would never find it).
|
|
13397
|
-
dir:
|
|
13771
|
+
dir: path12.dirname(file),
|
|
13398
13772
|
filePath: file,
|
|
13399
13773
|
secretScrubber: this.secretScrubber,
|
|
13400
13774
|
checkpointCas: this.checkpointCas,
|
|
@@ -13442,9 +13816,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13442
13816
|
let cacheHit = false;
|
|
13443
13817
|
try {
|
|
13444
13818
|
const s = await fsp9.stat(file);
|
|
13445
|
-
const
|
|
13819
|
+
const stat13 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
13446
13820
|
const cached = this._loadCache.get(id);
|
|
13447
|
-
if (cached && cached.mtimeMs ===
|
|
13821
|
+
if (cached && cached.mtimeMs === stat13.mtimeMs && cached.size === stat13.size) {
|
|
13448
13822
|
cacheHit = true;
|
|
13449
13823
|
this._loadCache.delete(id);
|
|
13450
13824
|
this._loadCache.set(id, cached);
|
|
@@ -13633,7 +14007,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13633
14007
|
this._loadCache.delete(oldest);
|
|
13634
14008
|
}
|
|
13635
14009
|
}
|
|
13636
|
-
this._loadCache.set(id, { mtimeMs:
|
|
14010
|
+
this._loadCache.set(id, { mtimeMs: stat13.mtimeMs, size: stat13.size, data });
|
|
13637
14011
|
}
|
|
13638
14012
|
return data;
|
|
13639
14013
|
} catch (err) {
|
|
@@ -13682,14 +14056,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13682
14056
|
const limit = opts?.limit;
|
|
13683
14057
|
const signal = opts?.signal;
|
|
13684
14058
|
const out = [];
|
|
13685
|
-
let
|
|
14059
|
+
let stat13;
|
|
13686
14060
|
try {
|
|
13687
|
-
|
|
14061
|
+
stat13 = await fsp9.stat(file);
|
|
13688
14062
|
} catch (err) {
|
|
13689
14063
|
if (err.code === "ENOENT") return [];
|
|
13690
14064
|
throw err;
|
|
13691
14065
|
}
|
|
13692
|
-
if (
|
|
14066
|
+
if (stat13.size === 0) return [];
|
|
13693
14067
|
let fh;
|
|
13694
14068
|
try {
|
|
13695
14069
|
fh = await fsp9.open(file, "r");
|
|
@@ -13880,15 +14254,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13880
14254
|
* Returns empty array when the index doesn't exist or is corrupt.
|
|
13881
14255
|
*/
|
|
13882
14256
|
async readIndex() {
|
|
13883
|
-
let
|
|
14257
|
+
let stat13;
|
|
13884
14258
|
try {
|
|
13885
14259
|
const s = await fsp9.stat(this.indexFile);
|
|
13886
|
-
|
|
14260
|
+
stat13 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
13887
14261
|
} catch {
|
|
13888
14262
|
this._indexCache = null;
|
|
13889
14263
|
return [];
|
|
13890
14264
|
}
|
|
13891
|
-
if (this._indexCache !== null && this._indexCache.mtimeMs ===
|
|
14265
|
+
if (this._indexCache !== null && this._indexCache.mtimeMs === stat13.mtimeMs && this._indexCache.size === stat13.size) {
|
|
13892
14266
|
return [...this._indexCache.summaries];
|
|
13893
14267
|
}
|
|
13894
14268
|
let raw;
|
|
@@ -13921,7 +14295,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13921
14295
|
if (a.startedAt > b.startedAt) return -1;
|
|
13922
14296
|
return a.id.localeCompare(b.id);
|
|
13923
14297
|
});
|
|
13924
|
-
this._indexCache = { ...
|
|
14298
|
+
this._indexCache = { ...stat13, summaries };
|
|
13925
14299
|
return [...summaries];
|
|
13926
14300
|
}
|
|
13927
14301
|
/**
|
|
@@ -14015,7 +14389,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14015
14389
|
return entry;
|
|
14016
14390
|
}
|
|
14017
14391
|
async collectSessionFilesInShard(shardKey) {
|
|
14018
|
-
const dir = shardKey ?
|
|
14392
|
+
const dir = shardKey ? path12.join(this.dir, shardKey) : this.dir;
|
|
14019
14393
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
14020
14394
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
14021
14395
|
}
|
|
@@ -14038,13 +14412,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14038
14412
|
if (entry.name === "_index.jsonl") continue;
|
|
14039
14413
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
14040
14414
|
const id = prefix ? `${prefix}/${base}` : base;
|
|
14041
|
-
files.push({ id, filePath:
|
|
14415
|
+
files.push({ id, filePath: path12.join(dir, entry.name) });
|
|
14042
14416
|
}
|
|
14043
14417
|
}
|
|
14044
14418
|
const childFileArrays = await Promise.all(
|
|
14045
14419
|
dirEntries.map((entry) => {
|
|
14046
14420
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
14047
|
-
return this.collectSessionFiles(
|
|
14421
|
+
return this.collectSessionFiles(path12.join(dir, entry.name), childPrefix, depth + 1);
|
|
14048
14422
|
})
|
|
14049
14423
|
);
|
|
14050
14424
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -14077,7 +14451,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14077
14451
|
const childIdArrays = await Promise.all(
|
|
14078
14452
|
dirEntries.map((entry) => {
|
|
14079
14453
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
14080
|
-
return this.collectSessionIds(
|
|
14454
|
+
return this.collectSessionIds(path12.join(dir, entry.name), childPrefix, depth + 1);
|
|
14081
14455
|
})
|
|
14082
14456
|
);
|
|
14083
14457
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -14091,8 +14465,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14091
14465
|
if (fromManifest) return fromManifest;
|
|
14092
14466
|
try {
|
|
14093
14467
|
const full = this.sessionPath(id, ".jsonl");
|
|
14094
|
-
const
|
|
14095
|
-
const summary = await this.summarize(id,
|
|
14468
|
+
const stat13 = await fsp9.stat(full);
|
|
14469
|
+
const summary = await this.summarize(id, stat13.mtime.toISOString());
|
|
14096
14470
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
14097
14471
|
const msg = toErrorMessage(err);
|
|
14098
14472
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
@@ -14133,18 +14507,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14133
14507
|
async summaryHeaderFor(ref) {
|
|
14134
14508
|
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
14135
14509
|
try {
|
|
14136
|
-
const
|
|
14137
|
-
if (!
|
|
14510
|
+
const stat13 = await fsp9.stat(ref.filePath);
|
|
14511
|
+
if (!stat13.isFile()) {
|
|
14138
14512
|
return {
|
|
14139
14513
|
id: ref.id,
|
|
14140
14514
|
title: "(damaged)",
|
|
14141
|
-
startedAt:
|
|
14515
|
+
startedAt: stat13.mtime.toISOString(),
|
|
14142
14516
|
model: "unknown",
|
|
14143
14517
|
provider: "unknown",
|
|
14144
14518
|
tokenTotal: 0
|
|
14145
14519
|
};
|
|
14146
14520
|
}
|
|
14147
|
-
mtime =
|
|
14521
|
+
mtime = stat13.mtime.toISOString();
|
|
14148
14522
|
} catch {
|
|
14149
14523
|
return null;
|
|
14150
14524
|
}
|
|
@@ -14192,9 +14566,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14192
14566
|
async deleteSession(id) {
|
|
14193
14567
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
14194
14568
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
14195
|
-
const shardDir =
|
|
14196
|
-
const base =
|
|
14197
|
-
const sessDir =
|
|
14569
|
+
const shardDir = path12.dirname(jsonlPath);
|
|
14570
|
+
const base = path12.basename(id);
|
|
14571
|
+
const sessDir = path12.join(shardDir, base);
|
|
14198
14572
|
const deletions = [
|
|
14199
14573
|
fsp9.unlink(jsonlPath),
|
|
14200
14574
|
fsp9.unlink(summaryPath),
|
|
@@ -14239,7 +14613,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14239
14613
|
*/
|
|
14240
14614
|
async readActiveSessionId() {
|
|
14241
14615
|
try {
|
|
14242
|
-
const raw = await fsp9.readFile(
|
|
14616
|
+
const raw = await fsp9.readFile(path12.join(this.dir, "active.json"), "utf8");
|
|
14243
14617
|
const active = JSON.parse(raw);
|
|
14244
14618
|
return active.sessionId ?? null;
|
|
14245
14619
|
} catch {
|
|
@@ -14302,10 +14676,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14302
14676
|
const activeSessionId = await this.readActiveSessionId();
|
|
14303
14677
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
14304
14678
|
const pruneFile = async (dir, name, prefix) => {
|
|
14305
|
-
const jsonlPath =
|
|
14679
|
+
const jsonlPath = path12.join(dir, name);
|
|
14306
14680
|
try {
|
|
14307
|
-
const
|
|
14308
|
-
if (
|
|
14681
|
+
const stat13 = await fsp9.stat(jsonlPath);
|
|
14682
|
+
if (stat13.mtimeMs >= cutoff) return;
|
|
14309
14683
|
} catch {
|
|
14310
14684
|
return;
|
|
14311
14685
|
}
|
|
@@ -14322,7 +14696,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14322
14696
|
continue;
|
|
14323
14697
|
}
|
|
14324
14698
|
if (!entry.isDirectory()) continue;
|
|
14325
|
-
const dateDir =
|
|
14699
|
+
const dateDir = path12.join(this.dir, entry.name);
|
|
14326
14700
|
const files = await fsp9.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
14327
14701
|
for (const file of files) {
|
|
14328
14702
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
@@ -14334,7 +14708,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14334
14708
|
}
|
|
14335
14709
|
for (const entry of entries) {
|
|
14336
14710
|
if (!entry.isDirectory()) continue;
|
|
14337
|
-
const dateDir =
|
|
14711
|
+
const dateDir = path12.join(this.dir, entry.name);
|
|
14338
14712
|
try {
|
|
14339
14713
|
const remaining = await fsp9.readdir(dateDir);
|
|
14340
14714
|
if (remaining.length === 0) {
|
|
@@ -14465,9 +14839,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
14465
14839
|
let dir;
|
|
14466
14840
|
if (opts.store) {
|
|
14467
14841
|
store = opts.store;
|
|
14468
|
-
dir = opts.sessionsRoot ?
|
|
14842
|
+
dir = opts.sessionsRoot ? path13.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
14469
14843
|
} else if (opts.sessionsRoot) {
|
|
14470
|
-
dir =
|
|
14844
|
+
dir = path13.join(opts.sessionsRoot, runId);
|
|
14471
14845
|
store = new DefaultSessionStore({ dir });
|
|
14472
14846
|
} else {
|
|
14473
14847
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -15813,8 +16187,8 @@ function isDesignStack(v) {
|
|
|
15813
16187
|
|
|
15814
16188
|
// src/execution/design-kit-loader.ts
|
|
15815
16189
|
import { existsSync } from "node:fs";
|
|
15816
|
-
import * as
|
|
15817
|
-
import * as
|
|
16190
|
+
import * as fs4 from "node:fs/promises";
|
|
16191
|
+
import * as path14 from "node:path";
|
|
15818
16192
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
15819
16193
|
var KIT_FILE = "KIT.md";
|
|
15820
16194
|
var TOKENS_FILE = "tokens.json";
|
|
@@ -15921,15 +16295,15 @@ var DefaultDesignKitLoader = class {
|
|
|
15921
16295
|
for (const { dir, source } of this.dirs) {
|
|
15922
16296
|
let entries;
|
|
15923
16297
|
try {
|
|
15924
|
-
entries = await
|
|
16298
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
15925
16299
|
} catch {
|
|
15926
16300
|
continue;
|
|
15927
16301
|
}
|
|
15928
16302
|
for (const e of entries) {
|
|
15929
16303
|
if (!e.isDirectory()) continue;
|
|
15930
|
-
const kitFile =
|
|
16304
|
+
const kitFile = path14.join(dir, e.name, KIT_FILE);
|
|
15931
16305
|
try {
|
|
15932
|
-
const raw = await
|
|
16306
|
+
const raw = await fs4.readFile(kitFile, "utf8");
|
|
15933
16307
|
const fm = parseKitFrontmatter(raw);
|
|
15934
16308
|
const id = fm.id ?? e.name;
|
|
15935
16309
|
if (!fm.name) continue;
|
|
@@ -15990,7 +16364,7 @@ var DefaultDesignKitLoader = class {
|
|
|
15990
16364
|
if (cached !== void 0) return cached;
|
|
15991
16365
|
const m = await this.find(id);
|
|
15992
16366
|
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
15993
|
-
const raw = await
|
|
16367
|
+
const raw = await fs4.readFile(m.path, "utf8");
|
|
15994
16368
|
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
15995
16369
|
this.bodyCache.set(key, body);
|
|
15996
16370
|
return body;
|
|
@@ -16001,9 +16375,9 @@ var DefaultDesignKitLoader = class {
|
|
|
16001
16375
|
const m = await this.find(id);
|
|
16002
16376
|
let tokens;
|
|
16003
16377
|
if (m) {
|
|
16004
|
-
const tokensPath =
|
|
16378
|
+
const tokensPath = path14.join(path14.dirname(m.path), TOKENS_FILE);
|
|
16005
16379
|
try {
|
|
16006
|
-
const raw = await
|
|
16380
|
+
const raw = await fs4.readFile(tokensPath, "utf8");
|
|
16007
16381
|
const parsed = JSON.parse(raw);
|
|
16008
16382
|
tokens = parsed;
|
|
16009
16383
|
} catch {
|
|
@@ -16028,12 +16402,12 @@ var DefaultDesignKitLoader = class {
|
|
|
16028
16402
|
};
|
|
16029
16403
|
function resolveBundledDesignKitsDir() {
|
|
16030
16404
|
try {
|
|
16031
|
-
const here =
|
|
16405
|
+
const here = path14.dirname(fileURLToPath3(import.meta.url));
|
|
16032
16406
|
const candidates = [
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16407
|
+
path14.join(here, "design-kits"),
|
|
16408
|
+
path14.join(here, "..", "design-kits"),
|
|
16409
|
+
path14.join(here, "..", "..", "design-kits"),
|
|
16410
|
+
path14.join(here, "..", "..", "..", "design-kits")
|
|
16037
16411
|
];
|
|
16038
16412
|
for (const c of candidates) {
|
|
16039
16413
|
if (existsSync(c)) return c;
|
|
@@ -16061,11 +16435,11 @@ function _resetDesignKitLoaderMemo() {
|
|
|
16061
16435
|
|
|
16062
16436
|
// src/execution/design-project-store.ts
|
|
16063
16437
|
import { existsSync as existsSync2 } from "node:fs";
|
|
16064
|
-
import * as
|
|
16065
|
-
import * as
|
|
16438
|
+
import * as fs5 from "node:fs/promises";
|
|
16439
|
+
import * as path15 from "node:path";
|
|
16066
16440
|
var DESIGN_DIR = ".design";
|
|
16067
16441
|
function designProjectDir(projectRoot) {
|
|
16068
|
-
return
|
|
16442
|
+
return path15.join(projectRoot, DESIGN_DIR);
|
|
16069
16443
|
}
|
|
16070
16444
|
var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
|
|
16071
16445
|
var rulesCache = /* @__PURE__ */ new Map();
|
|
@@ -16074,7 +16448,7 @@ async function loadProjectDesignRules(projectRoot) {
|
|
|
16074
16448
|
let rules;
|
|
16075
16449
|
for (const name of RULE_FILES) {
|
|
16076
16450
|
try {
|
|
16077
|
-
const txt = await
|
|
16451
|
+
const txt = await fs5.readFile(path15.join(designProjectDir(projectRoot), name), "utf8");
|
|
16078
16452
|
if (txt.trim()) {
|
|
16079
16453
|
rules = txt.trim();
|
|
16080
16454
|
break;
|
|
@@ -16095,7 +16469,7 @@ function parseOverrides(value) {
|
|
|
16095
16469
|
}
|
|
16096
16470
|
async function loadActiveKit(projectRoot) {
|
|
16097
16471
|
try {
|
|
16098
|
-
const raw = await
|
|
16472
|
+
const raw = await fs5.readFile(path15.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
16099
16473
|
const parsed = JSON.parse(raw);
|
|
16100
16474
|
if (parsed && typeof parsed.kit === "string") {
|
|
16101
16475
|
return {
|
|
@@ -16128,11 +16502,11 @@ function applyTokenOverrides(tokens, overrides) {
|
|
|
16128
16502
|
}
|
|
16129
16503
|
async function ensureDesignDir(projectRoot) {
|
|
16130
16504
|
const dir = designProjectDir(projectRoot);
|
|
16131
|
-
await
|
|
16132
|
-
const gi =
|
|
16505
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
16506
|
+
const gi = path15.join(dir, ".gitignore");
|
|
16133
16507
|
if (!existsSync2(gi)) {
|
|
16134
16508
|
try {
|
|
16135
|
-
await
|
|
16509
|
+
await fs5.writeFile(gi, "*\n");
|
|
16136
16510
|
} catch {
|
|
16137
16511
|
}
|
|
16138
16512
|
}
|
|
@@ -16143,11 +16517,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
|
|
|
16143
16517
|
const dir = await ensureDesignDir(projectRoot);
|
|
16144
16518
|
const record = { kit, stack: stack ?? null };
|
|
16145
16519
|
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
16146
|
-
await
|
|
16520
|
+
await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
16147
16521
|
`);
|
|
16148
16522
|
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
16149
16523
|
`;
|
|
16150
|
-
await
|
|
16524
|
+
await fs5.appendFile(path15.join(dir, "decisions.md"), line);
|
|
16151
16525
|
} catch {
|
|
16152
16526
|
}
|
|
16153
16527
|
}
|
|
@@ -16163,11 +16537,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
16163
16537
|
const dir = await ensureDesignDir(projectRoot);
|
|
16164
16538
|
const record = { kit: active.kit, stack: active.stack ?? null };
|
|
16165
16539
|
if (Object.keys(merged).length > 0) record.overrides = merged;
|
|
16166
|
-
await
|
|
16540
|
+
await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
16167
16541
|
`);
|
|
16168
16542
|
const keys = Object.keys(patch).join(",");
|
|
16169
|
-
await
|
|
16170
|
-
|
|
16543
|
+
await fs5.appendFile(
|
|
16544
|
+
path15.join(dir, "decisions.md"),
|
|
16171
16545
|
`- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
|
|
16172
16546
|
`
|
|
16173
16547
|
);
|
|
@@ -16177,7 +16551,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
16177
16551
|
}
|
|
16178
16552
|
async function clearPersistedActiveKit(projectRoot) {
|
|
16179
16553
|
try {
|
|
16180
|
-
await
|
|
16554
|
+
await fs5.rm(path15.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
16181
16555
|
} catch {
|
|
16182
16556
|
}
|
|
16183
16557
|
}
|
|
@@ -16293,12 +16667,12 @@ function verifyFiles(tokens, files) {
|
|
|
16293
16667
|
const violations = [];
|
|
16294
16668
|
let onPalette = 0;
|
|
16295
16669
|
let offPalette = 0;
|
|
16296
|
-
for (const { path:
|
|
16670
|
+
for (const { path: path30, text } of files) {
|
|
16297
16671
|
const lines = text.split("\n");
|
|
16298
16672
|
lines.forEach((lineText, i) => {
|
|
16299
16673
|
const lineNo = i + 1;
|
|
16300
16674
|
const flag = (snippet, reason) => {
|
|
16301
|
-
violations.push({ file:
|
|
16675
|
+
violations.push({ file: path30, line: lineNo, snippet: snippet.slice(0, 80), reason });
|
|
16302
16676
|
};
|
|
16303
16677
|
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
16304
16678
|
re.lastIndex = 0;
|
|
@@ -16350,14 +16724,14 @@ var SKIP_DIR = /* @__PURE__ */ new Set([
|
|
|
16350
16724
|
".design"
|
|
16351
16725
|
]);
|
|
16352
16726
|
async function walkUiFiles(root, max = 200) {
|
|
16353
|
-
const { default:
|
|
16727
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
16354
16728
|
const { default: nodePath } = await import("node:path");
|
|
16355
16729
|
const found = [];
|
|
16356
16730
|
async function rec(dir, depth) {
|
|
16357
16731
|
if (found.length >= max || depth > 8) return;
|
|
16358
16732
|
let entries;
|
|
16359
16733
|
try {
|
|
16360
|
-
entries = await
|
|
16734
|
+
entries = await fs16.readdir(dir, { withFileTypes: true });
|
|
16361
16735
|
} catch {
|
|
16362
16736
|
return;
|
|
16363
16737
|
}
|
|
@@ -16375,13 +16749,13 @@ async function walkUiFiles(root, max = 200) {
|
|
|
16375
16749
|
return found;
|
|
16376
16750
|
}
|
|
16377
16751
|
async function runDesignVerify(projectRoot, tokens, explicitFiles) {
|
|
16378
|
-
const { default:
|
|
16752
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
16379
16753
|
const { default: nodePath } = await import("node:path");
|
|
16380
16754
|
const abs = explicitFiles && explicitFiles.length > 0 ? explicitFiles.map((f) => nodePath.isAbsolute(f) ? f : nodePath.join(projectRoot, f)) : await walkUiFiles(projectRoot);
|
|
16381
16755
|
const files = [];
|
|
16382
16756
|
for (const a of abs) {
|
|
16383
16757
|
try {
|
|
16384
|
-
files.push({ path: nodePath.relative(projectRoot, a), text: await
|
|
16758
|
+
files.push({ path: nodePath.relative(projectRoot, a), text: await fs16.readFile(a, "utf8") });
|
|
16385
16759
|
} catch {
|
|
16386
16760
|
}
|
|
16387
16761
|
}
|
|
@@ -16512,10 +16886,10 @@ function makeDesignVerifyToolCallMiddleware() {
|
|
|
16512
16886
|
const p = typeof input?.path === "string" ? input.path : "";
|
|
16513
16887
|
if (!p || !detectFrontendFile(p)) return out;
|
|
16514
16888
|
const ctx = out.ctx;
|
|
16515
|
-
const { default:
|
|
16889
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
16516
16890
|
const { default: nodePath } = await import("node:path");
|
|
16517
16891
|
const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
|
|
16518
|
-
const text = await
|
|
16892
|
+
const text = await fs16.readFile(abs, "utf8").catch(() => "");
|
|
16519
16893
|
if (!text) return out;
|
|
16520
16894
|
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
16521
16895
|
const rawTokens = await loader.readTokens(state.activeKit);
|
|
@@ -16807,16 +17181,20 @@ function buildRecoveryStrategies(opts) {
|
|
|
16807
17181
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
16808
17182
|
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
16809
17183
|
if (ctx?.signal) {
|
|
16810
|
-
await new Promise((
|
|
17184
|
+
await new Promise((resolve14) => {
|
|
16811
17185
|
if (ctx.signal.aborted) {
|
|
16812
|
-
|
|
17186
|
+
resolve14();
|
|
16813
17187
|
return;
|
|
16814
17188
|
}
|
|
16815
|
-
const timer = setTimeout(
|
|
16816
|
-
ctx.signal.addEventListener(
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
17189
|
+
const timer = setTimeout(resolve14, delay);
|
|
17190
|
+
ctx.signal.addEventListener(
|
|
17191
|
+
"abort",
|
|
17192
|
+
() => {
|
|
17193
|
+
clearTimeout(timer);
|
|
17194
|
+
resolve14();
|
|
17195
|
+
},
|
|
17196
|
+
{ once: true }
|
|
17197
|
+
);
|
|
16820
17198
|
});
|
|
16821
17199
|
} else {
|
|
16822
17200
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -16907,6 +17285,7 @@ function buildRecoveryStrategies(opts) {
|
|
|
16907
17285
|
var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
|
|
16908
17286
|
var HANDLER_KIND_BY_PROVIDER_KIND = {
|
|
16909
17287
|
rate_limit: "rate_limit",
|
|
17288
|
+
quota_exhausted: "rate_limit",
|
|
16910
17289
|
overloaded: "overloaded",
|
|
16911
17290
|
server: "server",
|
|
16912
17291
|
stream_hang: "server",
|
|
@@ -18801,8 +19180,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
18801
19180
|
});
|
|
18802
19181
|
await Promise.race([
|
|
18803
19182
|
drainPromise,
|
|
18804
|
-
new Promise((
|
|
18805
|
-
drainTimer = setTimeout(
|
|
19183
|
+
new Promise((resolve14) => {
|
|
19184
|
+
drainTimer = setTimeout(resolve14, STREAM_DRAIN_TIMEOUT_MS);
|
|
18806
19185
|
})
|
|
18807
19186
|
]);
|
|
18808
19187
|
} finally {
|
|
@@ -19097,7 +19476,7 @@ async function runProviderWithRetry(opts) {
|
|
|
19097
19476
|
status: isProviderErr ? err.status : 0,
|
|
19098
19477
|
description
|
|
19099
19478
|
});
|
|
19100
|
-
await new Promise((
|
|
19479
|
+
await new Promise((resolve14, reject) => {
|
|
19101
19480
|
let settled = false;
|
|
19102
19481
|
const cleanup = () => {
|
|
19103
19482
|
clearTimeout(t);
|
|
@@ -19113,7 +19492,7 @@ async function runProviderWithRetry(opts) {
|
|
|
19113
19492
|
if (settled) return;
|
|
19114
19493
|
settled = true;
|
|
19115
19494
|
cleanup();
|
|
19116
|
-
|
|
19495
|
+
resolve14();
|
|
19117
19496
|
}, delay);
|
|
19118
19497
|
if (signal.aborted) {
|
|
19119
19498
|
onAbort();
|
|
@@ -19141,6 +19520,7 @@ import { randomInt } from "node:crypto";
|
|
|
19141
19520
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
19142
19521
|
var MAX_ATTEMPTS_BY_KIND = {
|
|
19143
19522
|
rate_limit: 5,
|
|
19523
|
+
quota_exhausted: 0,
|
|
19144
19524
|
stream_hang: 5,
|
|
19145
19525
|
// transient, worth retrying aggressively
|
|
19146
19526
|
overloaded: 3,
|
|
@@ -19605,8 +19985,8 @@ Summarize the following message range:`;
|
|
|
19605
19985
|
};
|
|
19606
19986
|
|
|
19607
19987
|
// src/execution/skill-loader.ts
|
|
19608
|
-
import * as
|
|
19609
|
-
import * as
|
|
19988
|
+
import * as fs6 from "node:fs/promises";
|
|
19989
|
+
import * as path16 from "node:path";
|
|
19610
19990
|
|
|
19611
19991
|
// src/skills/foreign-sources.ts
|
|
19612
19992
|
var FOREIGN_SKILL_TOOLS = [
|
|
@@ -19836,7 +20216,7 @@ async function entryIsDirectory(dir, entry) {
|
|
|
19836
20216
|
if (entry.isDirectory()) return true;
|
|
19837
20217
|
if (entry.isSymbolicLink()) {
|
|
19838
20218
|
try {
|
|
19839
|
-
return (await
|
|
20219
|
+
return (await fs6.stat(path16.join(dir, entry.name))).isDirectory();
|
|
19840
20220
|
} catch {
|
|
19841
20221
|
return false;
|
|
19842
20222
|
}
|
|
@@ -19857,7 +20237,7 @@ var DefaultSkillLoader = class {
|
|
|
19857
20237
|
for (const tool of FOREIGN_SKILL_TOOLS) {
|
|
19858
20238
|
if (!foreignIds.includes(tool.id)) continue;
|
|
19859
20239
|
dirs.push({
|
|
19860
|
-
dir:
|
|
20240
|
+
dir: path16.join(root, "." + tool.id, tool.subdir),
|
|
19861
20241
|
source: "foreign",
|
|
19862
20242
|
originTool: tool.id
|
|
19863
20243
|
});
|
|
@@ -19879,12 +20259,14 @@ var DefaultSkillLoader = class {
|
|
|
19879
20259
|
const seen = /* @__PURE__ */ new Set();
|
|
19880
20260
|
for (const { dir, source, originTool } of this.dirs) {
|
|
19881
20261
|
try {
|
|
19882
|
-
const entries = await
|
|
20262
|
+
const entries = (await fs6.readdir(dir, { withFileTypes: true })).sort(
|
|
20263
|
+
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
20264
|
+
);
|
|
19883
20265
|
for (const e of entries) {
|
|
19884
20266
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
19885
|
-
const skillFile =
|
|
20267
|
+
const skillFile = path16.join(dir, e.name, "SKILL.md");
|
|
19886
20268
|
try {
|
|
19887
|
-
const raw = await
|
|
20269
|
+
const raw = await fs6.readFile(skillFile, "utf8");
|
|
19888
20270
|
const fm = parseSkillFrontmatter(raw);
|
|
19889
20271
|
if (!fm.name || !fm.description) continue;
|
|
19890
20272
|
if (!isValidSkillNameFormat(fm.name)) continue;
|
|
@@ -19956,7 +20338,7 @@ var DefaultSkillLoader = class {
|
|
|
19956
20338
|
if (cached !== void 0) return cached;
|
|
19957
20339
|
const m = await this.find(name);
|
|
19958
20340
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
19959
|
-
const body = await
|
|
20341
|
+
const body = await fs6.readFile(m.path, "utf8");
|
|
19960
20342
|
this.bodyCache.set(key, body);
|
|
19961
20343
|
return body;
|
|
19962
20344
|
}
|
|
@@ -19966,12 +20348,12 @@ var DefaultSkillLoader = class {
|
|
|
19966
20348
|
if (cached !== void 0) return cached;
|
|
19967
20349
|
const m = await this.find(name);
|
|
19968
20350
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
19969
|
-
const savePath =
|
|
20351
|
+
const savePath = path16.join(path16.dirname(m.path), "SKILL.save.md");
|
|
19970
20352
|
let result;
|
|
19971
20353
|
try {
|
|
19972
|
-
result = await
|
|
20354
|
+
result = await fs6.readFile(savePath, "utf8");
|
|
19973
20355
|
} catch {
|
|
19974
|
-
const full = await
|
|
20356
|
+
const full = await fs6.readFile(m.path, "utf8");
|
|
19975
20357
|
const body = stripFrontmatter2(full);
|
|
19976
20358
|
const compact = compactSkillBody(body);
|
|
19977
20359
|
if (compact) {
|
|
@@ -19999,12 +20381,12 @@ function parseDescriptionFromText(desc) {
|
|
|
19999
20381
|
}
|
|
20000
20382
|
|
|
20001
20383
|
// src/execution/prompt-loader.ts
|
|
20002
|
-
import * as
|
|
20003
|
-
import * as
|
|
20384
|
+
import * as fs8 from "node:fs/promises";
|
|
20385
|
+
import * as path18 from "node:path";
|
|
20004
20386
|
|
|
20005
20387
|
// src/storage/prompt-store.ts
|
|
20006
|
-
import * as
|
|
20007
|
-
import * as
|
|
20388
|
+
import * as fs7 from "node:fs/promises";
|
|
20389
|
+
import * as path17 from "node:path";
|
|
20008
20390
|
var SCHEMA_VERSION = 2;
|
|
20009
20391
|
function migratePromptEntry(raw) {
|
|
20010
20392
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -20065,12 +20447,12 @@ var DefaultPromptStore = class {
|
|
|
20065
20447
|
await ensureDir(this.dir);
|
|
20066
20448
|
const entries = [];
|
|
20067
20449
|
try {
|
|
20068
|
-
const files = await
|
|
20450
|
+
const files = await fs7.readdir(this.dir);
|
|
20069
20451
|
for (const file of files) {
|
|
20070
20452
|
if (!file.endsWith(".json")) continue;
|
|
20071
20453
|
try {
|
|
20072
20454
|
const raw = JSON.parse(
|
|
20073
|
-
await
|
|
20455
|
+
await fs7.readFile(path17.join(this.dir, file), "utf8")
|
|
20074
20456
|
);
|
|
20075
20457
|
const migrated = migratePromptEntry(raw.entry);
|
|
20076
20458
|
if (migrated) entries.push(migrated);
|
|
@@ -20084,9 +20466,9 @@ var DefaultPromptStore = class {
|
|
|
20084
20466
|
);
|
|
20085
20467
|
}
|
|
20086
20468
|
async get(id) {
|
|
20087
|
-
const file =
|
|
20469
|
+
const file = path17.join(this.dir, `${id}.json`);
|
|
20088
20470
|
try {
|
|
20089
|
-
const raw = JSON.parse(await
|
|
20471
|
+
const raw = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
20090
20472
|
return migratePromptEntry(raw.entry);
|
|
20091
20473
|
} catch {
|
|
20092
20474
|
return null;
|
|
@@ -20094,14 +20476,14 @@ var DefaultPromptStore = class {
|
|
|
20094
20476
|
}
|
|
20095
20477
|
async save(entry) {
|
|
20096
20478
|
await ensureDir(this.dir);
|
|
20097
|
-
const file =
|
|
20479
|
+
const file = path17.join(this.dir, `${entry.id}.json`);
|
|
20098
20480
|
const raw = { version: SCHEMA_VERSION, entry };
|
|
20099
20481
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
20100
20482
|
}
|
|
20101
20483
|
async delete(id) {
|
|
20102
|
-
const file =
|
|
20484
|
+
const file = path17.join(this.dir, `${id}.json`);
|
|
20103
20485
|
try {
|
|
20104
|
-
await
|
|
20486
|
+
await fs7.unlink(file);
|
|
20105
20487
|
return true;
|
|
20106
20488
|
} catch {
|
|
20107
20489
|
return false;
|
|
@@ -20188,7 +20570,7 @@ var DefaultPromptLoader = class {
|
|
|
20188
20570
|
constructor(opts) {
|
|
20189
20571
|
this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
|
|
20190
20572
|
this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
|
|
20191
|
-
this.builtinDir = opts.bundledDir ?
|
|
20573
|
+
this.builtinDir = opts.bundledDir ? path18.join(opts.bundledDir, "prompts") : void 0;
|
|
20192
20574
|
}
|
|
20193
20575
|
async list() {
|
|
20194
20576
|
if (this.cache) return this.cache;
|
|
@@ -20301,7 +20683,7 @@ var DefaultPromptLoader = class {
|
|
|
20301
20683
|
const files = await walkJson(dir);
|
|
20302
20684
|
for (const file of files) {
|
|
20303
20685
|
try {
|
|
20304
|
-
const parsed = JSON.parse(await
|
|
20686
|
+
const parsed = JSON.parse(await fs8.readFile(file, "utf8"));
|
|
20305
20687
|
const migrated = migratePromptEntry(parsed);
|
|
20306
20688
|
if (migrated) out.push({ ...migrated, source: "builtin" });
|
|
20307
20689
|
} catch {
|
|
@@ -20315,12 +20697,12 @@ async function walkJson(dir) {
|
|
|
20315
20697
|
const out = [];
|
|
20316
20698
|
let entries;
|
|
20317
20699
|
try {
|
|
20318
|
-
entries = await
|
|
20700
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
20319
20701
|
} catch {
|
|
20320
20702
|
return out;
|
|
20321
20703
|
}
|
|
20322
20704
|
for (const e of entries) {
|
|
20323
|
-
const full =
|
|
20705
|
+
const full = path18.join(dir, e.name);
|
|
20324
20706
|
if (e.isDirectory()) {
|
|
20325
20707
|
out.push(...await walkJson(full));
|
|
20326
20708
|
} else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
|
|
@@ -20506,7 +20888,7 @@ function readPolicy(ctx) {
|
|
|
20506
20888
|
}
|
|
20507
20889
|
|
|
20508
20890
|
// src/execution/tool-executor.ts
|
|
20509
|
-
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
20891
|
+
import { createHash as createHash6, randomUUID as randomUUID11 } from "node:crypto";
|
|
20510
20892
|
|
|
20511
20893
|
// src/observability/process-telemetry.ts
|
|
20512
20894
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
@@ -20517,8 +20899,167 @@ function runWithProcessTelemetry(context, run) {
|
|
|
20517
20899
|
|
|
20518
20900
|
// src/execution/tool-executor.ts
|
|
20519
20901
|
import { isDeepStrictEqual } from "node:util";
|
|
20520
|
-
import * as
|
|
20521
|
-
import * as
|
|
20902
|
+
import * as fs9 from "node:fs/promises";
|
|
20903
|
+
import * as path20 from "node:path";
|
|
20904
|
+
|
|
20905
|
+
// src/security/kanban-boundary.ts
|
|
20906
|
+
import { realpath as realpath3 } from "node:fs/promises";
|
|
20907
|
+
import * as path19 from "node:path";
|
|
20908
|
+
import {
|
|
20909
|
+
evaluateKanbanBoundaryOpaque,
|
|
20910
|
+
evaluateKanbanBoundaryPath,
|
|
20911
|
+
readBoard,
|
|
20912
|
+
resolveKanbanBoundaryLayers
|
|
20913
|
+
} from "@wrongstack/kanban";
|
|
20914
|
+
var PATH_KEYS = /* @__PURE__ */ new Set([
|
|
20915
|
+
"path",
|
|
20916
|
+
"paths",
|
|
20917
|
+
"file",
|
|
20918
|
+
"files",
|
|
20919
|
+
"directory",
|
|
20920
|
+
"dir",
|
|
20921
|
+
"cwd",
|
|
20922
|
+
"root",
|
|
20923
|
+
"baseDir",
|
|
20924
|
+
"out",
|
|
20925
|
+
"outputPath",
|
|
20926
|
+
"sourcePath",
|
|
20927
|
+
"targetPath",
|
|
20928
|
+
"destinationPath",
|
|
20929
|
+
"fromPath",
|
|
20930
|
+
"toPath",
|
|
20931
|
+
"worktreePath"
|
|
20932
|
+
]);
|
|
20933
|
+
var TOOL_PATH_KEYS = {
|
|
20934
|
+
// `target` is an identifier in tools such as plan/task/document, but a
|
|
20935
|
+
// filesystem target in language_info. Keep ambiguous names tool-specific.
|
|
20936
|
+
language_info: ["target"]
|
|
20937
|
+
};
|
|
20938
|
+
async function evaluateToolKanbanBoundary(tool, input, ctx) {
|
|
20939
|
+
const identity = resolveKanbanIdentity(ctx);
|
|
20940
|
+
if (!identity.boardId) return { decision: "allow" };
|
|
20941
|
+
const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
|
|
20942
|
+
if (!board) return { decision: "allow" };
|
|
20943
|
+
const task = identity.taskId ? board.tasks.find((candidate) => candidate.id === identity.taskId) : void 0;
|
|
20944
|
+
const layers = resolveKanbanBoundaryLayers(board, task);
|
|
20945
|
+
if (layers.length === 0) return { decision: "allow" };
|
|
20946
|
+
const caps = tool.capabilities ?? [];
|
|
20947
|
+
const access2 = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
|
|
20948
|
+
const shellLike = caps.some((capability) => capability.startsWith("shell."));
|
|
20949
|
+
const candidates = access2 ? await extractCandidatePaths(tool.name, input, ctx) : [];
|
|
20950
|
+
let evaluation;
|
|
20951
|
+
if (shellLike || access2 && candidates.length === 0) {
|
|
20952
|
+
evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
|
|
20953
|
+
} else if (access2) {
|
|
20954
|
+
const results = candidates.map(
|
|
20955
|
+
(candidate) => evaluateKanbanBoundaryPath(layers, candidate, access2)
|
|
20956
|
+
);
|
|
20957
|
+
evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
|
|
20958
|
+
} else {
|
|
20959
|
+
evaluation = { decision: "allow" };
|
|
20960
|
+
}
|
|
20961
|
+
return {
|
|
20962
|
+
...evaluation,
|
|
20963
|
+
boardId: board.id,
|
|
20964
|
+
...task ? { taskId: task.id } : {}
|
|
20965
|
+
};
|
|
20966
|
+
}
|
|
20967
|
+
function resolveKanbanIdentity(ctx) {
|
|
20968
|
+
const metaKanban = ctx.meta["kanban"];
|
|
20969
|
+
const record = metaKanban && typeof metaKanban === "object" ? metaKanban : void 0;
|
|
20970
|
+
const boardId = ctx.currentKanbanBoardId ?? (typeof record?.["boardId"] === "string" ? record["boardId"] : void 0);
|
|
20971
|
+
const taskId = ctx.currentKanbanTaskId ?? (typeof record?.["taskId"] === "string" ? record["taskId"] : void 0);
|
|
20972
|
+
const policyRoot = typeof record?.["projectRoot"] === "string" ? record["projectRoot"] : void 0;
|
|
20973
|
+
return {
|
|
20974
|
+
...boardId ? { boardId } : {},
|
|
20975
|
+
...taskId ? { taskId } : {},
|
|
20976
|
+
...policyRoot ? { policyRoot } : {}
|
|
20977
|
+
};
|
|
20978
|
+
}
|
|
20979
|
+
async function extractCandidatePaths(toolName, input, ctx) {
|
|
20980
|
+
if (toolName === "patch" && typeof input["patch"] === "string") {
|
|
20981
|
+
const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
|
|
20982
|
+
const directory = path19.isAbsolute(directoryInput) ? directoryInput : path19.resolve(ctx.workingDir, directoryInput);
|
|
20983
|
+
const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
|
|
20984
|
+
const targets = extractPatchTargets(input["patch"], strip).map(
|
|
20985
|
+
(target) => relativeToProject(path19.resolve(directory, target), ctx.projectRoot)
|
|
20986
|
+
);
|
|
20987
|
+
return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
|
|
20988
|
+
}
|
|
20989
|
+
const values = [];
|
|
20990
|
+
const pathKeys = new Set(PATH_KEYS);
|
|
20991
|
+
for (const key of TOOL_PATH_KEYS[toolName] ?? []) pathKeys.add(key);
|
|
20992
|
+
collectPathValues(input, values, pathKeys);
|
|
20993
|
+
if (toolName === "scaffold" && typeof input["name"] === "string") {
|
|
20994
|
+
const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
|
|
20995
|
+
values.push(path19.join(cwd, input["name"]));
|
|
20996
|
+
}
|
|
20997
|
+
const candidates = [
|
|
20998
|
+
...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
|
|
20999
|
+
];
|
|
21000
|
+
return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
|
|
21001
|
+
}
|
|
21002
|
+
async function canonicalizeCandidatePath(candidate, ctx) {
|
|
21003
|
+
if (path19.isAbsolute(candidate)) return candidate;
|
|
21004
|
+
const absolute = path19.resolve(ctx.projectRoot, candidate);
|
|
21005
|
+
const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
21006
|
+
let probe = absolute;
|
|
21007
|
+
const missingSegments = [];
|
|
21008
|
+
while (true) {
|
|
21009
|
+
try {
|
|
21010
|
+
const canonical = path19.join(await realpath3(probe), ...missingSegments);
|
|
21011
|
+
return relativeToProject(canonical, canonicalRoot);
|
|
21012
|
+
} catch (cause) {
|
|
21013
|
+
const code = cause.code;
|
|
21014
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
|
|
21015
|
+
const parent = path19.dirname(probe);
|
|
21016
|
+
if (parent === probe) return absolute;
|
|
21017
|
+
missingSegments.unshift(path19.basename(probe));
|
|
21018
|
+
probe = parent;
|
|
21019
|
+
}
|
|
21020
|
+
}
|
|
21021
|
+
}
|
|
21022
|
+
function collectPathValues(value, output, pathKeys, key) {
|
|
21023
|
+
if (typeof value === "string") {
|
|
21024
|
+
if (key && pathKeys.has(key)) output.push(value);
|
|
21025
|
+
return;
|
|
21026
|
+
}
|
|
21027
|
+
if (Array.isArray(value)) {
|
|
21028
|
+
for (const item of value) collectPathValues(item, output, pathKeys, key);
|
|
21029
|
+
return;
|
|
21030
|
+
}
|
|
21031
|
+
if (!value || typeof value !== "object") return;
|
|
21032
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
21033
|
+
collectPathValues(child, output, pathKeys, childKey);
|
|
21034
|
+
}
|
|
21035
|
+
}
|
|
21036
|
+
function splitPathList(value) {
|
|
21037
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
21038
|
+
}
|
|
21039
|
+
function resolveInputPath(value, ctx) {
|
|
21040
|
+
const absolute = path19.isAbsolute(value) ? value : path19.resolve(ctx.workingDir, value);
|
|
21041
|
+
return relativeToProject(absolute, ctx.projectRoot);
|
|
21042
|
+
}
|
|
21043
|
+
function relativeToProject(absolute, projectRoot) {
|
|
21044
|
+
const relative6 = path19.relative(projectRoot, absolute).replace(/\\/g, "/");
|
|
21045
|
+
return relative6.startsWith("../") || path19.isAbsolute(relative6) ? absolute : relative6 || ".";
|
|
21046
|
+
}
|
|
21047
|
+
function extractPatchTargets(patchText, strip) {
|
|
21048
|
+
const targets = [];
|
|
21049
|
+
for (const match of patchText.matchAll(/^\+\+\+\s+([^\t\r\n]+)/gm)) {
|
|
21050
|
+
const raw = match[1]?.trim();
|
|
21051
|
+
if (!raw || raw === "/dev/null") continue;
|
|
21052
|
+
const parts = raw.replace(/\\/g, "/").split("/").filter((part) => part && part !== ".");
|
|
21053
|
+
if (parts.length > strip) targets.push(parts.slice(strip).join("/"));
|
|
21054
|
+
}
|
|
21055
|
+
return targets;
|
|
21056
|
+
}
|
|
21057
|
+
function stringValue(value) {
|
|
21058
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
21059
|
+
}
|
|
21060
|
+
function numericValue(value) {
|
|
21061
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
21062
|
+
}
|
|
20522
21063
|
|
|
20523
21064
|
// src/types/tool-executor.ts
|
|
20524
21065
|
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
@@ -20739,35 +21280,68 @@ ${errorDetails}`,
|
|
|
20739
21280
|
return { result, tool, durationMs: Date.now() - start };
|
|
20740
21281
|
}
|
|
20741
21282
|
}
|
|
21283
|
+
const boundary = await evaluateToolKanbanBoundary(tool, use.input, ctx);
|
|
21284
|
+
if (boundary.decision === "block") {
|
|
21285
|
+
const result = {
|
|
21286
|
+
type: "tool_result",
|
|
21287
|
+
tool_use_id: use.id,
|
|
21288
|
+
content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
|
|
21289
|
+
is_error: true
|
|
21290
|
+
};
|
|
21291
|
+
budget = this.budgetForString(result.content, budget);
|
|
21292
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
21293
|
+
}
|
|
20742
21294
|
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
20743
21295
|
let effectivePermission = decision.permission;
|
|
20744
21296
|
const policy = this.opts.permissionPolicy;
|
|
20745
21297
|
const yolo = policy.getYolo?.() === true;
|
|
20746
21298
|
const authoritativeAuto = decision.source === "yolo";
|
|
20747
|
-
|
|
21299
|
+
const capabilityDowngraded = toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto;
|
|
21300
|
+
if (capabilityDowngraded) {
|
|
21301
|
+
effectivePermission = "confirm";
|
|
21302
|
+
}
|
|
21303
|
+
if (boundary.decision === "confirm" && effectivePermission !== "deny") {
|
|
20748
21304
|
effectivePermission = "confirm";
|
|
20749
21305
|
}
|
|
21306
|
+
this.opts.events?.emit("permission.evaluated", {
|
|
21307
|
+
sessionId: ctx.session.id,
|
|
21308
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21309
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
21310
|
+
name: tool.name,
|
|
21311
|
+
id: use.id,
|
|
21312
|
+
inputHash: hashPermissionInput(use.input, this.opts.secretScrubber),
|
|
21313
|
+
policyDecision: decision.permission,
|
|
21314
|
+
effectiveDecision: effectivePermission,
|
|
21315
|
+
decisionSource: decision.source,
|
|
21316
|
+
...decision.reason ? { reason: decision.reason } : {},
|
|
21317
|
+
...decision.riskTier ?? tool.riskTier ? { riskTier: decision.riskTier ?? tool.riskTier } : {},
|
|
21318
|
+
yoloEnabled: yolo,
|
|
21319
|
+
boundaryDecision: boundary.decision,
|
|
21320
|
+
...boundary.reason ? { boundaryReason: boundary.reason } : {},
|
|
21321
|
+
capabilityDowngraded
|
|
21322
|
+
});
|
|
20750
21323
|
if (effectivePermission === "deny") {
|
|
20751
21324
|
const result = this.deniedResult(use, decision.reason);
|
|
20752
21325
|
budget = this.budgetForString(result.content, budget);
|
|
20753
21326
|
return { result, tool, durationMs: Date.now() - start };
|
|
20754
21327
|
}
|
|
20755
21328
|
if (effectivePermission === "confirm") {
|
|
21329
|
+
const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
20756
21330
|
if (this.opts.confirmAwaiter) {
|
|
20757
21331
|
const awaiter = this.opts.confirmAwaiter;
|
|
20758
21332
|
const choice = await new Promise(
|
|
20759
|
-
(
|
|
21333
|
+
(resolve14, reject) => {
|
|
20760
21334
|
const signal = ctx.signal;
|
|
20761
|
-
const onAbort = () =>
|
|
21335
|
+
const onAbort = () => resolve14("abort");
|
|
20762
21336
|
if (signal.aborted) {
|
|
20763
|
-
|
|
21337
|
+
resolve14("abort");
|
|
20764
21338
|
return;
|
|
20765
21339
|
}
|
|
20766
21340
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
20767
|
-
awaiter(tool, use.input, use.id,
|
|
21341
|
+
awaiter(tool, use.input, use.id, suggestedPattern).then(
|
|
20768
21342
|
(c) => {
|
|
20769
21343
|
signal.removeEventListener("abort", onAbort);
|
|
20770
|
-
|
|
21344
|
+
resolve14(c);
|
|
20771
21345
|
},
|
|
20772
21346
|
(e) => {
|
|
20773
21347
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -20787,7 +21361,6 @@ ${errorDetails}`,
|
|
|
20787
21361
|
return { result, tool, durationMs: Date.now() - start };
|
|
20788
21362
|
}
|
|
20789
21363
|
} else {
|
|
20790
|
-
const suggestedPattern = subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
20791
21364
|
const pending = {
|
|
20792
21365
|
type: "tool_confirm_pending",
|
|
20793
21366
|
toolUseId: use.id,
|
|
@@ -20795,7 +21368,8 @@ ${errorDetails}`,
|
|
|
20795
21368
|
input: use.input,
|
|
20796
21369
|
suggestedPattern,
|
|
20797
21370
|
decisionSource: decision.source,
|
|
20798
|
-
riskTier: decision.riskTier ?? tool.riskTier
|
|
21371
|
+
riskTier: decision.riskTier ?? tool.riskTier,
|
|
21372
|
+
...boundary.decision === "confirm" && boundary.reason ? { boundaryReason: boundary.reason } : {}
|
|
20799
21373
|
};
|
|
20800
21374
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
20801
21375
|
}
|
|
@@ -20809,6 +21383,17 @@ ${errorDetails}`,
|
|
|
20809
21383
|
"tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
|
|
20810
21384
|
});
|
|
20811
21385
|
try {
|
|
21386
|
+
const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
|
|
21387
|
+
const caps = tool.capabilities ?? [];
|
|
21388
|
+
const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
|
|
21389
|
+
const absPath = hasFileCapability && typeof inputPath === "string" ? path20.isAbsolute(inputPath) ? inputPath : path20.resolve(ctx.projectRoot, inputPath) : void 0;
|
|
21390
|
+
let writeTargetExisted;
|
|
21391
|
+
if (tool.name === "write" && caps.includes("fs.write") && absPath) {
|
|
21392
|
+
writeTargetExisted = await fs9.stat(absPath).then(
|
|
21393
|
+
(stat13) => stat13.isFile(),
|
|
21394
|
+
(error2) => error2.code === "ENOENT" ? false : void 0
|
|
21395
|
+
);
|
|
21396
|
+
}
|
|
20812
21397
|
const producedText = await this.produceToolOutput(tool, use, ctx, budget);
|
|
20813
21398
|
let { block: result, bytes } = this.settleToolOutput(tool, use, producedText, budget);
|
|
20814
21399
|
budget -= bytes;
|
|
@@ -20837,6 +21422,35 @@ ${post.additionalContext}`;
|
|
|
20837
21422
|
span?.setAttribute("tool.is_error", !!result.is_error);
|
|
20838
21423
|
span?.setAttribute("tool.output_bytes", outputChars);
|
|
20839
21424
|
this.logToolSuccess(ctx, use, tool.name, Date.now() - start, outputChars);
|
|
21425
|
+
if (!result.is_error && typeof inputPath === "string" && absPath) {
|
|
21426
|
+
const operation = tool.name === "read" ? "read" : caps.includes("fs.write") && tool.name === "write" ? writeTargetExisted === false ? "create" : "update" : caps.includes("fs.write") ? "update" : "read";
|
|
21427
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
21428
|
+
ctx.recordFileEvent?.({
|
|
21429
|
+
operation,
|
|
21430
|
+
filePath: inputPath,
|
|
21431
|
+
absPath,
|
|
21432
|
+
toolName: tool.name,
|
|
21433
|
+
toolUseId: use.id,
|
|
21434
|
+
durationMs: Date.now() - start
|
|
21435
|
+
});
|
|
21436
|
+
this.opts.events?.emit("file.event", {
|
|
21437
|
+
operation,
|
|
21438
|
+
filePath: inputPath,
|
|
21439
|
+
absPath,
|
|
21440
|
+
sessionId: ctx.session.id,
|
|
21441
|
+
agentId: ctx.agentId,
|
|
21442
|
+
agentName: ctx.agentName,
|
|
21443
|
+
provider: typeof ctx.provider === "object" ? ctx.provider.id : String(ctx.provider),
|
|
21444
|
+
model: ctx.model,
|
|
21445
|
+
toolName: tool.name,
|
|
21446
|
+
toolUseId: use.id,
|
|
21447
|
+
scope: ctx.currentKanbanTaskId ? "task" : "session",
|
|
21448
|
+
taskId: ctx.currentKanbanTaskId,
|
|
21449
|
+
boardId: ctx.currentKanbanBoardId,
|
|
21450
|
+
timestamp: ts,
|
|
21451
|
+
durationMs: Date.now() - start
|
|
21452
|
+
});
|
|
21453
|
+
}
|
|
20840
21454
|
return { result, tool, durationMs: Date.now() - start };
|
|
20841
21455
|
} catch (err) {
|
|
20842
21456
|
if (isWrongStackError(err)) {
|
|
@@ -21065,7 +21679,7 @@ ${post.additionalContext}`;
|
|
|
21065
21679
|
toolPromise.catch(() => {
|
|
21066
21680
|
});
|
|
21067
21681
|
try {
|
|
21068
|
-
output = await new Promise((
|
|
21682
|
+
output = await new Promise((resolve14, reject) => {
|
|
21069
21683
|
const onAbort = () => {
|
|
21070
21684
|
setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
|
|
21071
21685
|
};
|
|
@@ -21073,7 +21687,7 @@ ${post.additionalContext}`;
|
|
|
21073
21687
|
toolPromise.then(
|
|
21074
21688
|
(v) => {
|
|
21075
21689
|
combined.removeEventListener("abort", onAbort);
|
|
21076
|
-
|
|
21690
|
+
resolve14(v);
|
|
21077
21691
|
},
|
|
21078
21692
|
(e) => {
|
|
21079
21693
|
combined.removeEventListener("abort", onAbort);
|
|
@@ -21364,12 +21978,12 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
21364
21978
|
return content;
|
|
21365
21979
|
}
|
|
21366
21980
|
try {
|
|
21367
|
-
const dir =
|
|
21368
|
-
await
|
|
21981
|
+
const dir = path20.join(wstackGlobalRoot(), "tool-output");
|
|
21982
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
21369
21983
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
21370
21984
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
21371
|
-
const filePath =
|
|
21372
|
-
await
|
|
21985
|
+
const filePath = path20.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
|
|
21986
|
+
await fs9.writeFile(filePath, content, "utf8");
|
|
21373
21987
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
21374
21988
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
21375
21989
|
const previewBytes = Math.min(
|
|
@@ -21387,6 +22001,15 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
|
|
|
21387
22001
|
return content;
|
|
21388
22002
|
}
|
|
21389
22003
|
}
|
|
22004
|
+
function hashPermissionInput(input, scrubber) {
|
|
22005
|
+
let serialized;
|
|
22006
|
+
try {
|
|
22007
|
+
serialized = JSON.stringify(input) ?? "";
|
|
22008
|
+
} catch {
|
|
22009
|
+
serialized = String(input);
|
|
22010
|
+
}
|
|
22011
|
+
return createHash6("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
|
|
22012
|
+
}
|
|
21390
22013
|
function sliceUtf8Prefix(text, maxBytes) {
|
|
21391
22014
|
if (maxBytes <= 0) return "";
|
|
21392
22015
|
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
@@ -21680,7 +22303,7 @@ var contextManagerTool = createContextManagerTool();
|
|
|
21680
22303
|
|
|
21681
22304
|
// src/infrastructure/logger.ts
|
|
21682
22305
|
import * as fsp11 from "node:fs/promises";
|
|
21683
|
-
import * as
|
|
22306
|
+
import * as path21 from "node:path";
|
|
21684
22307
|
var LEVEL_RANK2 = {
|
|
21685
22308
|
error: 0,
|
|
21686
22309
|
warn: 1,
|
|
@@ -21744,7 +22367,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
21744
22367
|
this.stderr = opts.stderr !== false;
|
|
21745
22368
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
21746
22369
|
if (this.file) {
|
|
21747
|
-
const dir =
|
|
22370
|
+
const dir = path21.dirname(this.file);
|
|
21748
22371
|
this._tail = this._tail.then(async () => {
|
|
21749
22372
|
await fsp11.mkdir(dir, { recursive: true });
|
|
21750
22373
|
}).catch(() => void 0);
|
|
@@ -22065,28 +22688,28 @@ function codexModelMeta(id) {
|
|
|
22065
22688
|
}
|
|
22066
22689
|
|
|
22067
22690
|
// src/models/mode-store.ts
|
|
22068
|
-
import * as
|
|
22069
|
-
import * as
|
|
22691
|
+
import * as fs10 from "node:fs/promises";
|
|
22692
|
+
import * as path23 from "node:path";
|
|
22070
22693
|
|
|
22071
22694
|
// src/types/mode-prompts.ts
|
|
22072
22695
|
import { readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
22073
|
-
import * as
|
|
22696
|
+
import * as path22 from "node:path";
|
|
22074
22697
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
22075
22698
|
function modePrompt(id) {
|
|
22076
22699
|
for (const dir of modePromptDirCandidates()) {
|
|
22077
22700
|
try {
|
|
22078
|
-
return readFileSync4(
|
|
22701
|
+
return readFileSync4(path22.join(dir, `${id}.md`), "utf8").trimEnd();
|
|
22079
22702
|
} catch {
|
|
22080
22703
|
}
|
|
22081
22704
|
}
|
|
22082
22705
|
return "";
|
|
22083
22706
|
}
|
|
22084
22707
|
function modePromptDirCandidates() {
|
|
22085
|
-
const here =
|
|
22708
|
+
const here = path22.dirname(fileURLToPath4(import.meta.url));
|
|
22086
22709
|
const candidates = [
|
|
22087
|
-
|
|
22088
|
-
|
|
22089
|
-
|
|
22710
|
+
path22.resolve(here, "../../instructions/modes"),
|
|
22711
|
+
path22.resolve(here, "../instructions/modes"),
|
|
22712
|
+
path22.resolve(here, "instructions/modes")
|
|
22090
22713
|
];
|
|
22091
22714
|
return candidates.sort((a, b) => Number(!isDirectory3(a)) - Number(!isDirectory3(b)));
|
|
22092
22715
|
}
|
|
@@ -22318,8 +22941,8 @@ var DefaultModeStore = class {
|
|
|
22318
22941
|
}
|
|
22319
22942
|
async loadActiveMode() {
|
|
22320
22943
|
try {
|
|
22321
|
-
const configPath =
|
|
22322
|
-
const content = await
|
|
22944
|
+
const configPath = path23.join(this.configDir, "mode.json");
|
|
22945
|
+
const content = await fs10.readFile(configPath, "utf8");
|
|
22323
22946
|
const data = JSON.parse(content);
|
|
22324
22947
|
this.activeModeId = data.activeMode ?? null;
|
|
22325
22948
|
} catch {
|
|
@@ -22328,8 +22951,8 @@ var DefaultModeStore = class {
|
|
|
22328
22951
|
}
|
|
22329
22952
|
async saveActiveMode() {
|
|
22330
22953
|
try {
|
|
22331
|
-
await
|
|
22332
|
-
const configPath =
|
|
22954
|
+
await fs10.mkdir(this.configDir, { recursive: true });
|
|
22955
|
+
const configPath = path23.join(this.configDir, "mode.json");
|
|
22333
22956
|
await atomicWrite(
|
|
22334
22957
|
configPath,
|
|
22335
22958
|
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
@@ -22341,14 +22964,14 @@ var DefaultModeStore = class {
|
|
|
22341
22964
|
async function loadProjectModes(modesDir) {
|
|
22342
22965
|
const modes = [];
|
|
22343
22966
|
try {
|
|
22344
|
-
const entries = await
|
|
22967
|
+
const entries = await fs10.readdir(modesDir);
|
|
22345
22968
|
for (const entry of entries) {
|
|
22346
22969
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
22347
|
-
const filePath =
|
|
22348
|
-
const
|
|
22349
|
-
if (!
|
|
22350
|
-
const content = await
|
|
22351
|
-
const id =
|
|
22970
|
+
const filePath = path23.join(modesDir, entry);
|
|
22971
|
+
const stat13 = await fs10.stat(filePath);
|
|
22972
|
+
if (!stat13.isFile()) continue;
|
|
22973
|
+
const content = await fs10.readFile(filePath, "utf8");
|
|
22974
|
+
const id = path23.basename(entry, path23.extname(entry));
|
|
22352
22975
|
modes.push({
|
|
22353
22976
|
id,
|
|
22354
22977
|
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
@@ -22364,8 +22987,8 @@ async function loadProjectModes(modesDir) {
|
|
|
22364
22987
|
async function loadUserModes(modesDir) {
|
|
22365
22988
|
const modes = [];
|
|
22366
22989
|
try {
|
|
22367
|
-
const manifestPath =
|
|
22368
|
-
const content = await
|
|
22990
|
+
const manifestPath = path23.join(modesDir, "modes.json");
|
|
22991
|
+
const content = await fs10.readFile(manifestPath, "utf8");
|
|
22369
22992
|
const manifest = JSON.parse(content);
|
|
22370
22993
|
for (const mode of manifest.modes) {
|
|
22371
22994
|
modes.push(mode);
|
|
@@ -22376,8 +22999,8 @@ async function loadUserModes(modesDir) {
|
|
|
22376
22999
|
}
|
|
22377
23000
|
|
|
22378
23001
|
// src/models/models-registry.ts
|
|
22379
|
-
import * as
|
|
22380
|
-
import * as
|
|
23002
|
+
import * as fs11 from "node:fs/promises";
|
|
23003
|
+
import * as path24 from "node:path";
|
|
22381
23004
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
22382
23005
|
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
22383
23006
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -22453,7 +23076,7 @@ var DefaultModelsRegistry = class {
|
|
|
22453
23076
|
this.overlay = opts.overlay;
|
|
22454
23077
|
this.overlayUrl = opts.overlayUrl;
|
|
22455
23078
|
this.overlayFile = opts.overlayFile;
|
|
22456
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
23079
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path24.join(path24.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
22457
23080
|
}
|
|
22458
23081
|
async load(opts = {}) {
|
|
22459
23082
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -22618,7 +23241,7 @@ var DefaultModelsRegistry = class {
|
|
|
22618
23241
|
async readOverlayFile() {
|
|
22619
23242
|
if (!this.overlayFile) return void 0;
|
|
22620
23243
|
try {
|
|
22621
|
-
const raw = await
|
|
23244
|
+
const raw = await fs11.readFile(this.overlayFile, "utf8");
|
|
22622
23245
|
return JSON.parse(raw);
|
|
22623
23246
|
} catch {
|
|
22624
23247
|
return void 0;
|
|
@@ -22697,7 +23320,7 @@ var DefaultModelsRegistry = class {
|
|
|
22697
23320
|
}
|
|
22698
23321
|
async readCacheAt(file) {
|
|
22699
23322
|
try {
|
|
22700
|
-
const raw = await
|
|
23323
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
22701
23324
|
return JSON.parse(raw);
|
|
22702
23325
|
} catch {
|
|
22703
23326
|
return void 0;
|
|
@@ -22705,7 +23328,7 @@ var DefaultModelsRegistry = class {
|
|
|
22705
23328
|
}
|
|
22706
23329
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
22707
23330
|
cacheLocation() {
|
|
22708
|
-
return
|
|
23331
|
+
return path24.resolve(this.cacheFile);
|
|
22709
23332
|
}
|
|
22710
23333
|
};
|
|
22711
23334
|
function formatAge(seconds) {
|
|
@@ -22943,9 +23566,9 @@ var DefaultHealthRegistry = class {
|
|
|
22943
23566
|
}
|
|
22944
23567
|
async runOne(check) {
|
|
22945
23568
|
let timer = null;
|
|
22946
|
-
const timeout = new Promise((
|
|
23569
|
+
const timeout = new Promise((resolve14) => {
|
|
22947
23570
|
timer = setTimeout(
|
|
22948
|
-
() =>
|
|
23571
|
+
() => resolve14({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
22949
23572
|
this.timeoutMs
|
|
22950
23573
|
);
|
|
22951
23574
|
});
|
|
@@ -23129,7 +23752,7 @@ async function startMetricsServer(opts) {
|
|
|
23129
23752
|
const tls = opts.tls;
|
|
23130
23753
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
23131
23754
|
const host = opts.host ?? "127.0.0.1";
|
|
23132
|
-
const
|
|
23755
|
+
const path30 = opts.path ?? "/metrics";
|
|
23133
23756
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
23134
23757
|
const healthRegistry = opts.healthRegistry;
|
|
23135
23758
|
const listener = (req, res) => {
|
|
@@ -23139,7 +23762,7 @@ async function startMetricsServer(opts) {
|
|
|
23139
23762
|
return;
|
|
23140
23763
|
}
|
|
23141
23764
|
const url = req.url.split("?")[0];
|
|
23142
|
-
if (url ===
|
|
23765
|
+
if (url === path30) {
|
|
23143
23766
|
let body;
|
|
23144
23767
|
try {
|
|
23145
23768
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -23185,14 +23808,14 @@ async function startMetricsServer(opts) {
|
|
|
23185
23808
|
const { createServer } = await import("node:http");
|
|
23186
23809
|
server = createServer(listener);
|
|
23187
23810
|
}
|
|
23188
|
-
await new Promise((
|
|
23811
|
+
await new Promise((resolve14, reject) => {
|
|
23189
23812
|
const onError = (err) => {
|
|
23190
23813
|
server.off("listening", onListening);
|
|
23191
23814
|
reject(err);
|
|
23192
23815
|
};
|
|
23193
23816
|
const onListening = () => {
|
|
23194
23817
|
server.off("error", onError);
|
|
23195
|
-
|
|
23818
|
+
resolve14();
|
|
23196
23819
|
};
|
|
23197
23820
|
server.once("error", onError);
|
|
23198
23821
|
server.once("listening", onListening);
|
|
@@ -23203,9 +23826,9 @@ async function startMetricsServer(opts) {
|
|
|
23203
23826
|
const protocol = useHttps ? "https" : "http";
|
|
23204
23827
|
return {
|
|
23205
23828
|
port: boundPort,
|
|
23206
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
23207
|
-
close: () => new Promise((
|
|
23208
|
-
server.close((err) => err ? reject(err) :
|
|
23829
|
+
url: `${protocol}://${host}:${boundPort}${path30}`,
|
|
23830
|
+
close: () => new Promise((resolve14, reject) => {
|
|
23831
|
+
server.close((err) => err ? reject(err) : resolve14());
|
|
23209
23832
|
})
|
|
23210
23833
|
};
|
|
23211
23834
|
}
|
|
@@ -23475,7 +24098,7 @@ function startOtlpTraceExporter(opts) {
|
|
|
23475
24098
|
}
|
|
23476
24099
|
|
|
23477
24100
|
// src/security/permission-policy.ts
|
|
23478
|
-
import * as
|
|
24101
|
+
import * as fs12 from "node:fs/promises";
|
|
23479
24102
|
|
|
23480
24103
|
// src/security/permission-policy-schema.ts
|
|
23481
24104
|
var TRUST_POLICY_LIMITS = Object.freeze({
|
|
@@ -23520,19 +24143,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
23520
24143
|
function isRecord5(value) {
|
|
23521
24144
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23522
24145
|
}
|
|
23523
|
-
function error(diagnostics, code,
|
|
23524
|
-
diagnostics.push({ severity: "error", code, path:
|
|
24146
|
+
function error(diagnostics, code, path30, message) {
|
|
24147
|
+
diagnostics.push({ severity: "error", code, path: path30, message });
|
|
23525
24148
|
}
|
|
23526
|
-
function validatePatterns(value,
|
|
24149
|
+
function validatePatterns(value, path30, diagnostics) {
|
|
23527
24150
|
if (!Array.isArray(value)) {
|
|
23528
|
-
error(diagnostics, "invalid_pattern_list",
|
|
24151
|
+
error(diagnostics, "invalid_pattern_list", path30, "must be an array of strings");
|
|
23529
24152
|
return void 0;
|
|
23530
24153
|
}
|
|
23531
24154
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
23532
24155
|
error(
|
|
23533
24156
|
diagnostics,
|
|
23534
24157
|
"too_many_patterns",
|
|
23535
|
-
|
|
24158
|
+
path30,
|
|
23536
24159
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
23537
24160
|
);
|
|
23538
24161
|
return void 0;
|
|
@@ -23540,7 +24163,7 @@ function validatePatterns(value, path28, diagnostics) {
|
|
|
23540
24163
|
const patterns = [];
|
|
23541
24164
|
const seen = /* @__PURE__ */ new Set();
|
|
23542
24165
|
for (const [index, pattern] of value.entries()) {
|
|
23543
|
-
const itemPath = `${
|
|
24166
|
+
const itemPath = `${path30}[${index}]`;
|
|
23544
24167
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
23545
24168
|
error(
|
|
23546
24169
|
diagnostics,
|
|
@@ -23652,8 +24275,8 @@ var LruCache = class {
|
|
|
23652
24275
|
this.capacity = capacity;
|
|
23653
24276
|
}
|
|
23654
24277
|
get(key) {
|
|
24278
|
+
if (!this.store.has(key)) return void 0;
|
|
23655
24279
|
const value = this.store.get(key);
|
|
23656
|
-
if (value === void 0) return void 0;
|
|
23657
24280
|
this.store.delete(key);
|
|
23658
24281
|
this.store.set(key, value);
|
|
23659
24282
|
return value;
|
|
@@ -23864,7 +24487,7 @@ var DefaultPermissionPolicy = class {
|
|
|
23864
24487
|
this.policyDiagnostics = [];
|
|
23865
24488
|
this.policyInvalid = false;
|
|
23866
24489
|
try {
|
|
23867
|
-
const raw = await
|
|
24490
|
+
const raw = await fs12.readFile(this.trustFile, "utf8");
|
|
23868
24491
|
const parsed = safeParse(raw);
|
|
23869
24492
|
if (!parsed.ok) {
|
|
23870
24493
|
this.policy = {};
|
|
@@ -24114,6 +24737,136 @@ var DefaultPermissionPolicy = class {
|
|
|
24114
24737
|
this.sessionAllowed.set(`${rule.tool}::${rule.pattern}`, true);
|
|
24115
24738
|
this._evalCache.clear();
|
|
24116
24739
|
}
|
|
24740
|
+
/**
|
|
24741
|
+
* Side-effect-free permission evaluation trace. Mirrors `evaluate()`
|
|
24742
|
+
* step-by-step without prompting the user, writing to trust files, or
|
|
24743
|
+
* mutating session state. Returns the full ordered trace and winner.
|
|
24744
|
+
*/
|
|
24745
|
+
async explain(tool, input, ctx) {
|
|
24746
|
+
if (!this.loaded) await this.reload();
|
|
24747
|
+
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
24748
|
+
const steps = [];
|
|
24749
|
+
let winnerIndex = -1;
|
|
24750
|
+
const add = (rule, matched, decision, source, detail) => {
|
|
24751
|
+
steps.push({ rule, matched, decision, source, detail });
|
|
24752
|
+
};
|
|
24753
|
+
if (this.policyInvalid) {
|
|
24754
|
+
add("policy invalid", true, "deny", "deny", "trust policy is invalid; all tools are denied");
|
|
24755
|
+
winnerIndex = 0;
|
|
24756
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "trust policy is invalid" } };
|
|
24757
|
+
}
|
|
24758
|
+
add("policy valid", false, "auto", "default", "trust policy loaded successfully");
|
|
24759
|
+
const namespaceEntry = this.findNamespaceEntry(tool.name);
|
|
24760
|
+
const entry = this.policy[tool.name] ?? namespaceEntry;
|
|
24761
|
+
const namespaceSource = namespaceEntry ? `wildcard entry matched (${Object.keys(this.policy).find((k) => k.includes("*") && matchGlob(k, tool.name))})` : "no namespace match";
|
|
24762
|
+
const cacheKey = `${tool.name}::${subject ?? tool.name}`;
|
|
24763
|
+
if (this.sessionDenied.has(cacheKey)) {
|
|
24764
|
+
add("session soft deny", true, "deny", "deny", 'user pressed "no" earlier in this session \u2014 blocked until reload');
|
|
24765
|
+
winnerIndex = steps.length - 1;
|
|
24766
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" } };
|
|
24767
|
+
}
|
|
24768
|
+
add("session soft deny", false, "deny", "deny", "no session-level soft deny for this tool+subject");
|
|
24769
|
+
if (this.sessionAllowed.has(cacheKey)) {
|
|
24770
|
+
add("session soft allow", true, "auto", "trust", 'user pressed "yes" in current session \u2014 one-shot auto-approve (consumed)');
|
|
24771
|
+
winnerIndex = steps.length - 1;
|
|
24772
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "session one-shot allow (user pressed yes)" } };
|
|
24773
|
+
}
|
|
24774
|
+
add("session soft allow", false, "auto", "trust", "no session-level one-shot allow for this tool+subject");
|
|
24775
|
+
if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
|
|
24776
|
+
add("trust deny", true, "deny", "deny", `subject "${subject}" matched a deny pattern in trust file`);
|
|
24777
|
+
winnerIndex = steps.length - 1;
|
|
24778
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "matched deny pattern" } };
|
|
24779
|
+
}
|
|
24780
|
+
add("trust deny", false, "deny", "deny", `no deny pattern matched (namespace: ${namespaceSource})`);
|
|
24781
|
+
if (tool.permission === "deny") {
|
|
24782
|
+
add("tool default deny", true, "deny", "default", `tool "${tool.name}" has permission: deny by default`);
|
|
24783
|
+
winnerIndex = steps.length - 1;
|
|
24784
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "default", reason: "tool default deny" } };
|
|
24785
|
+
}
|
|
24786
|
+
add("tool default deny", false, "deny", "default", `tool "${tool.name}" has permission: "${tool.permission}"`);
|
|
24787
|
+
if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
|
|
24788
|
+
add("trust allow", true, "auto", "trust", `subject "${subject}" matched an allow pattern in trust file`);
|
|
24789
|
+
winnerIndex = steps.length - 1;
|
|
24790
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "matched allow pattern" } };
|
|
24791
|
+
}
|
|
24792
|
+
add("trust allow", false, "auto", "trust", `no allow pattern matched (namespace: ${namespaceSource})`);
|
|
24793
|
+
if (entry?.auto) {
|
|
24794
|
+
add("trust auto", true, "auto", "trust", `trust file has auto: true for "${tool.name}"`);
|
|
24795
|
+
winnerIndex = steps.length - 1;
|
|
24796
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "trust auto" } };
|
|
24797
|
+
}
|
|
24798
|
+
add("trust auto", false, "auto", "trust", `no auto flag for "${tool.name}" in trust file`);
|
|
24799
|
+
if (!this.yolo && this.isSensitiveReadCall(tool, input)) {
|
|
24800
|
+
const hasDelegate2 = this.promptDelegate !== void 0;
|
|
24801
|
+
add(
|
|
24802
|
+
"sensitive read",
|
|
24803
|
+
true,
|
|
24804
|
+
hasDelegate2 ? "confirm" : "confirm",
|
|
24805
|
+
"default",
|
|
24806
|
+
hasDelegate2 ? "sensitive file read detected \u2014 would prompt user for approval" : "sensitive file read detected \u2014 returns confirm (no prompt delegate)"
|
|
24807
|
+
);
|
|
24808
|
+
winnerIndex = steps.length - 1;
|
|
24809
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "confirm", source: "default", riskTier: "standard", reason: "sensitive file read needs explicit approval" } };
|
|
24810
|
+
}
|
|
24811
|
+
add("sensitive read", false, "confirm", "default", "not a sensitive read call (or YOLO bypasses this check)");
|
|
24812
|
+
if (this.yolo) {
|
|
24813
|
+
add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
|
|
24814
|
+
winnerIndex = steps.length - 1;
|
|
24815
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "yolo" } };
|
|
24816
|
+
}
|
|
24817
|
+
add("yolo", false, "auto", "yolo", "YOLO mode is not active");
|
|
24818
|
+
if (tool.name === "write" && subject) {
|
|
24819
|
+
const hasRead = ctx.hasRead(subject);
|
|
24820
|
+
add(
|
|
24821
|
+
"write smart bypass",
|
|
24822
|
+
hasRead,
|
|
24823
|
+
"auto",
|
|
24824
|
+
"context",
|
|
24825
|
+
hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
|
|
24826
|
+
);
|
|
24827
|
+
if (hasRead) {
|
|
24828
|
+
winnerIndex = steps.length - 1;
|
|
24829
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "context", reason: "file already read in this session" } };
|
|
24830
|
+
}
|
|
24831
|
+
} else {
|
|
24832
|
+
add("write smart bypass", false, "auto", "context", 'tool is not "write" or has no subject \u2014 bypass does not apply');
|
|
24833
|
+
}
|
|
24834
|
+
const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
|
|
24835
|
+
const hasShellCap = hasCapability(tool, [
|
|
24836
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
24837
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
24838
|
+
ToolCapabilities.SHELL_EXEC
|
|
24839
|
+
]);
|
|
24840
|
+
const hasInstallCap = hasCapability(tool, ToolCapabilities.PACKAGE_INSTALL);
|
|
24841
|
+
const hasConfigCap = hasCapability(tool, ToolCapabilities.CONFIG_MUTATE);
|
|
24842
|
+
const hasSubagentCap = hasCapability(tool, ToolCapabilities.SUBAGENT_SPAWN);
|
|
24843
|
+
const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
|
|
24844
|
+
if (tool.permission === "auto" && !isMutating) {
|
|
24845
|
+
add("safe default auto", true, "auto", "default", `tool "${tool.name}" has auto permission and is not mutating \u2014 safe to auto-approve`);
|
|
24846
|
+
winnerIndex = steps.length - 1;
|
|
24847
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "default" } };
|
|
24848
|
+
}
|
|
24849
|
+
add(
|
|
24850
|
+
"mutating default confirm",
|
|
24851
|
+
true,
|
|
24852
|
+
"confirm",
|
|
24853
|
+
"default",
|
|
24854
|
+
isMutating ? `tool "${tool.name}" is mutating (default auto not enough) \u2014 needs confirmation` : `tool "${tool.name}" has "${tool.permission}" permission \u2014 needs confirmation`
|
|
24855
|
+
);
|
|
24856
|
+
winnerIndex = steps.length - 1;
|
|
24857
|
+
const hasDelegate = this.promptDelegate !== void 0;
|
|
24858
|
+
return {
|
|
24859
|
+
toolName: tool.name,
|
|
24860
|
+
subject,
|
|
24861
|
+
steps,
|
|
24862
|
+
winnerIndex,
|
|
24863
|
+
decision: {
|
|
24864
|
+
permission: "confirm",
|
|
24865
|
+
source: "default",
|
|
24866
|
+
...hasDelegate ? { reason: "would prompt user via delegate" } : {}
|
|
24867
|
+
}
|
|
24868
|
+
};
|
|
24869
|
+
}
|
|
24117
24870
|
findNamespaceEntry(toolName) {
|
|
24118
24871
|
for (const { pattern, value } of this.wildcardEntries) {
|
|
24119
24872
|
if (matchGlob(pattern, toolName)) return value;
|
|
@@ -24159,6 +24912,24 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
24159
24912
|
}
|
|
24160
24913
|
allowOnce() {
|
|
24161
24914
|
}
|
|
24915
|
+
async explain(tool) {
|
|
24916
|
+
const decision = await this.evaluate(tool);
|
|
24917
|
+
return {
|
|
24918
|
+
toolName: tool.name,
|
|
24919
|
+
subject: null,
|
|
24920
|
+
steps: [
|
|
24921
|
+
{
|
|
24922
|
+
rule: "subagent auto",
|
|
24923
|
+
matched: decision.permission === "auto",
|
|
24924
|
+
decision: decision.permission,
|
|
24925
|
+
source: decision.source,
|
|
24926
|
+
detail: decision.reason ?? `subagent policy: ${decision.permission}`
|
|
24927
|
+
}
|
|
24928
|
+
],
|
|
24929
|
+
winnerIndex: 0,
|
|
24930
|
+
decision
|
|
24931
|
+
};
|
|
24932
|
+
}
|
|
24162
24933
|
async reload() {
|
|
24163
24934
|
}
|
|
24164
24935
|
};
|
|
@@ -24359,9 +25130,9 @@ var DefaultSecretScrubber = class {
|
|
|
24359
25130
|
|
|
24360
25131
|
// src/security/secret-vault.ts
|
|
24361
25132
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, scryptSync } from "node:crypto";
|
|
24362
|
-
import * as
|
|
25133
|
+
import * as fs13 from "node:fs";
|
|
24363
25134
|
import * as fsp12 from "node:fs/promises";
|
|
24364
|
-
import * as
|
|
25135
|
+
import * as path25 from "node:path";
|
|
24365
25136
|
|
|
24366
25137
|
// src/types/secret-vault.ts
|
|
24367
25138
|
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
@@ -24501,8 +25272,8 @@ function checkKeyFilePermissions(keyFile, opts) {
|
|
|
24501
25272
|
if (process.platform === "win32") return;
|
|
24502
25273
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
24503
25274
|
try {
|
|
24504
|
-
const
|
|
24505
|
-
const actualMode =
|
|
25275
|
+
const stat13 = fs13.statSync(keyFile);
|
|
25276
|
+
const actualMode = stat13.mode & 511;
|
|
24506
25277
|
if (actualMode !== KEY_FILE_MODE) {
|
|
24507
25278
|
warn(
|
|
24508
25279
|
`Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`
|
|
@@ -24598,16 +25369,16 @@ var DefaultSecretVault = class {
|
|
|
24598
25369
|
const oldVersion = this._keyVersion;
|
|
24599
25370
|
const newKey = randomBytes4(KEY_BYTES);
|
|
24600
25371
|
const newVersion = oldVersion + 1;
|
|
24601
|
-
|
|
25372
|
+
fs13.mkdirSync(path25.dirname(this.keyFile), { recursive: true });
|
|
24602
25373
|
const passphrase = getVaultPassphrase();
|
|
24603
25374
|
if (passphrase) {
|
|
24604
|
-
|
|
25375
|
+
fs13.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
|
|
24605
25376
|
} else {
|
|
24606
25377
|
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
24607
25378
|
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
24608
25379
|
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
24609
25380
|
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
24610
|
-
|
|
25381
|
+
fs13.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
24611
25382
|
}
|
|
24612
25383
|
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
24613
25384
|
this.key = newKey;
|
|
@@ -24625,7 +25396,7 @@ var DefaultSecretVault = class {
|
|
|
24625
25396
|
const passphrase = getVaultPassphrase();
|
|
24626
25397
|
if (!passphrase || !this.key) return;
|
|
24627
25398
|
try {
|
|
24628
|
-
|
|
25399
|
+
fs13.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
|
|
24629
25400
|
mode: 384
|
|
24630
25401
|
});
|
|
24631
25402
|
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
@@ -24635,7 +25406,7 @@ var DefaultSecretVault = class {
|
|
|
24635
25406
|
loadOrCreateKey() {
|
|
24636
25407
|
if (this.key) return this.key;
|
|
24637
25408
|
try {
|
|
24638
|
-
const buf =
|
|
25409
|
+
const buf = fs13.readFileSync(this.keyFile);
|
|
24639
25410
|
if (isWrappedKeyFile(buf)) {
|
|
24640
25411
|
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
24641
25412
|
this.key = key2;
|
|
@@ -24682,15 +25453,15 @@ var DefaultSecretVault = class {
|
|
|
24682
25453
|
} catch (err) {
|
|
24683
25454
|
if (err.code !== "ENOENT") throw err;
|
|
24684
25455
|
}
|
|
24685
|
-
|
|
25456
|
+
fs13.mkdirSync(path25.dirname(this.keyFile), { recursive: true });
|
|
24686
25457
|
const key = randomBytes4(KEY_BYTES);
|
|
24687
25458
|
const passphrase = getVaultPassphrase();
|
|
24688
25459
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
24689
25460
|
try {
|
|
24690
|
-
|
|
25461
|
+
fs13.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
|
|
24691
25462
|
} catch (err) {
|
|
24692
25463
|
if (err.code !== "EEXIST") throw err;
|
|
24693
|
-
const buf =
|
|
25464
|
+
const buf = fs13.readFileSync(this.keyFile);
|
|
24694
25465
|
if (isWrappedKeyFile(buf)) {
|
|
24695
25466
|
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
24696
25467
|
this.key = winnerKey;
|
|
@@ -24740,7 +25511,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
24740
25511
|
}
|
|
24741
25512
|
const merged = deepMerge(current, patch ?? {});
|
|
24742
25513
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
24743
|
-
await fsp12.mkdir(
|
|
25514
|
+
await fsp12.mkdir(path25.dirname(configPath), { recursive: true });
|
|
24744
25515
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
24745
25516
|
await restrictFilePermissions(configPath);
|
|
24746
25517
|
}
|
|
@@ -24824,7 +25595,7 @@ function walkCount(node, vault, counter) {
|
|
|
24824
25595
|
// src/storage/attachment-store.ts
|
|
24825
25596
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
24826
25597
|
import * as fsp13 from "node:fs/promises";
|
|
24827
|
-
import * as
|
|
25598
|
+
import * as path26 from "node:path";
|
|
24828
25599
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
24829
25600
|
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
|
|
24830
25601
|
var DefaultAttachmentStore = class {
|
|
@@ -24845,7 +25616,7 @@ var DefaultAttachmentStore = class {
|
|
|
24845
25616
|
let data = input.data;
|
|
24846
25617
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
24847
25618
|
await fsp13.mkdir(this.spoolDir, { recursive: true });
|
|
24848
|
-
spooledPath =
|
|
25619
|
+
spooledPath = path26.join(this.spoolDir, `${id}.bin`);
|
|
24849
25620
|
await atomicWrite(spooledPath, input.data, {
|
|
24850
25621
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
24851
25622
|
});
|
|
@@ -24964,8 +25735,8 @@ function mergeAdjacentText(blocks) {
|
|
|
24964
25735
|
}
|
|
24965
25736
|
|
|
24966
25737
|
// src/storage/config-loader.ts
|
|
24967
|
-
import * as
|
|
24968
|
-
import * as
|
|
25738
|
+
import * as fs14 from "node:fs/promises";
|
|
25739
|
+
import * as path27 from "node:path";
|
|
24969
25740
|
|
|
24970
25741
|
// src/types/config.ts
|
|
24971
25742
|
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
@@ -25225,6 +25996,7 @@ var defaultIndexing = {
|
|
|
25225
25996
|
};
|
|
25226
25997
|
var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
25227
25998
|
"version",
|
|
25999
|
+
"activeProfile",
|
|
25228
26000
|
"model",
|
|
25229
26001
|
"cwd",
|
|
25230
26002
|
"context",
|
|
@@ -25247,6 +26019,7 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
25247
26019
|
"fallbackProfiles",
|
|
25248
26020
|
"favoriteModels",
|
|
25249
26021
|
"favoriteModelsOnly",
|
|
26022
|
+
"modelAvailabilitySchedule",
|
|
25250
26023
|
"fallbackAuto",
|
|
25251
26024
|
"models",
|
|
25252
26025
|
"modelMatrix",
|
|
@@ -25282,6 +26055,7 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
25282
26055
|
];
|
|
25283
26056
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
25284
26057
|
"version",
|
|
26058
|
+
"activeProfile",
|
|
25285
26059
|
"provider",
|
|
25286
26060
|
"model",
|
|
25287
26061
|
"apiKey",
|
|
@@ -25298,6 +26072,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
25298
26072
|
"fallbackProfiles",
|
|
25299
26073
|
"favoriteModels",
|
|
25300
26074
|
"favoriteModelsOnly",
|
|
26075
|
+
"modelAvailabilitySchedule",
|
|
25301
26076
|
"fallbackAuto",
|
|
25302
26077
|
"hooks",
|
|
25303
26078
|
"plugins",
|
|
@@ -25445,8 +26220,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
25445
26220
|
return out;
|
|
25446
26221
|
}
|
|
25447
26222
|
function samePath(a, b) {
|
|
25448
|
-
let ra =
|
|
25449
|
-
let rb =
|
|
26223
|
+
let ra = path27.resolve(a);
|
|
26224
|
+
let rb = path27.resolve(b);
|
|
25450
26225
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
25451
26226
|
ra = ra.toLowerCase();
|
|
25452
26227
|
rb = rb.toLowerCase();
|
|
@@ -25468,7 +26243,7 @@ function deepMerge2(base, patch) {
|
|
|
25468
26243
|
opts
|
|
25469
26244
|
);
|
|
25470
26245
|
}
|
|
25471
|
-
var DefaultConfigLoader = class {
|
|
26246
|
+
var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
25472
26247
|
paths;
|
|
25473
26248
|
strict;
|
|
25474
26249
|
vault;
|
|
@@ -25502,12 +26277,17 @@ var DefaultConfigLoader = class {
|
|
|
25502
26277
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
25503
26278
|
await this.ensureGlobalDefaults();
|
|
25504
26279
|
const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
|
|
25505
|
-
const [
|
|
26280
|
+
const [bootstrap, local, inProject] = await Promise.all([
|
|
25506
26281
|
this.readJson(this.paths.globalConfig),
|
|
25507
26282
|
this.readJson(this.paths.projectLocalConfig),
|
|
25508
26283
|
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
25509
26284
|
]);
|
|
25510
|
-
cfg = deepMerge2(cfg,
|
|
26285
|
+
cfg = deepMerge2(cfg, bootstrap);
|
|
26286
|
+
const profileName = bootstrap.activeProfile ?? "default";
|
|
26287
|
+
const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
|
|
26288
|
+
if (Object.keys(profileCfg).length > 0) {
|
|
26289
|
+
cfg = deepMerge2(cfg, profileCfg);
|
|
26290
|
+
}
|
|
25511
26291
|
cfg = deepMerge2(cfg, local);
|
|
25512
26292
|
cfg = deepMerge2(
|
|
25513
26293
|
cfg,
|
|
@@ -25533,10 +26313,11 @@ var DefaultConfigLoader = class {
|
|
|
25533
26313
|
cfg = deepMerge2(cfg, patch);
|
|
25534
26314
|
}
|
|
25535
26315
|
} catch (err) {
|
|
25536
|
-
this.logWarn(
|
|
25537
|
-
|
|
25538
|
-
|
|
25539
|
-
|
|
26316
|
+
this.logWarn("Config source load failed", {
|
|
26317
|
+
event: "config.source_load_failed",
|
|
26318
|
+
source: src.name,
|
|
26319
|
+
message: toErrorMessage(err)
|
|
26320
|
+
});
|
|
25540
26321
|
}
|
|
25541
26322
|
}
|
|
25542
26323
|
if (opts.cliFlags) {
|
|
@@ -25569,14 +26350,20 @@ var DefaultConfigLoader = class {
|
|
|
25569
26350
|
}
|
|
25570
26351
|
return Object.freeze(cfg);
|
|
25571
26352
|
}
|
|
26353
|
+
/** Check whether a config object contains only the two bootstrap keys. */
|
|
26354
|
+
static isBootstrapOnly(config) {
|
|
26355
|
+
const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
26356
|
+
return Object.keys(config).every((k) => BOOTSTRAP_KEYS.has(k));
|
|
26357
|
+
}
|
|
25572
26358
|
async ensureGlobalDefaults() {
|
|
25573
26359
|
const fp = this.paths.globalConfig;
|
|
25574
26360
|
const t0 = Date.now();
|
|
25575
26361
|
try {
|
|
25576
26362
|
await withFileLock(fp, async () => {
|
|
25577
26363
|
let parsed;
|
|
26364
|
+
let fileExisted = true;
|
|
25578
26365
|
try {
|
|
25579
|
-
const raw = await
|
|
26366
|
+
const raw = await fs14.readFile(fp, "utf8");
|
|
25580
26367
|
const result = safeParse(raw);
|
|
25581
26368
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
25582
26369
|
return;
|
|
@@ -25595,29 +26382,46 @@ var DefaultConfigLoader = class {
|
|
|
25595
26382
|
durationMs: Date.now() - t0,
|
|
25596
26383
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25597
26384
|
});
|
|
25598
|
-
this.logWarn(
|
|
25599
|
-
|
|
25600
|
-
|
|
25601
|
-
|
|
26385
|
+
this.logWarn("Config defaults read failed", {
|
|
26386
|
+
event: "config.defaults_read_failed",
|
|
26387
|
+
path: fp,
|
|
26388
|
+
message: toErrorMessage(err)
|
|
26389
|
+
});
|
|
25602
26390
|
return;
|
|
25603
26391
|
}
|
|
26392
|
+
fileExisted = false;
|
|
25604
26393
|
parsed = {};
|
|
25605
26394
|
}
|
|
25606
|
-
const
|
|
25607
|
-
|
|
25608
|
-
|
|
25609
|
-
|
|
25610
|
-
|
|
25611
|
-
|
|
25612
|
-
|
|
25613
|
-
|
|
25614
|
-
|
|
25615
|
-
|
|
25616
|
-
|
|
25617
|
-
|
|
25618
|
-
|
|
25619
|
-
|
|
25620
|
-
|
|
26395
|
+
const profileName = parsed.activeProfile ?? "default";
|
|
26396
|
+
const profileFp = this.paths.profileConfig(profileName);
|
|
26397
|
+
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
26398
|
+
const bootstrap = {
|
|
26399
|
+
version: 1,
|
|
26400
|
+
activeProfile: profileName
|
|
26401
|
+
};
|
|
26402
|
+
let needsBootstrapWrite = false;
|
|
26403
|
+
if (parsed.version !== 1) {
|
|
26404
|
+
needsBootstrapWrite = true;
|
|
26405
|
+
}
|
|
26406
|
+
if (parsed.activeProfile === void 0) {
|
|
26407
|
+
needsBootstrapWrite = true;
|
|
26408
|
+
}
|
|
26409
|
+
if (!_DefaultConfigLoader.isBootstrapOnly(parsed)) {
|
|
26410
|
+
needsBootstrapWrite = true;
|
|
26411
|
+
}
|
|
26412
|
+
if (needsBootstrapWrite) {
|
|
26413
|
+
await backupConfigFile(fp, this.paths);
|
|
26414
|
+
await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
|
|
26415
|
+
this.events?.emit("storage.write", {
|
|
26416
|
+
sessionId: "~config~",
|
|
26417
|
+
store: "config",
|
|
26418
|
+
filePath: fp,
|
|
26419
|
+
operation: "ensure_defaults",
|
|
26420
|
+
outcome: "success",
|
|
26421
|
+
durationMs: Date.now() - t0,
|
|
26422
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26423
|
+
});
|
|
26424
|
+
}
|
|
25621
26425
|
});
|
|
25622
26426
|
} catch (err) {
|
|
25623
26427
|
this.events?.emit("storage.error", {
|
|
@@ -25631,11 +26435,104 @@ var DefaultConfigLoader = class {
|
|
|
25631
26435
|
durationMs: Date.now() - t0,
|
|
25632
26436
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25633
26437
|
});
|
|
25634
|
-
this.logWarn(
|
|
25635
|
-
|
|
25636
|
-
|
|
25637
|
-
|
|
26438
|
+
this.logWarn("Config defaults write failed", {
|
|
26439
|
+
event: "config.defaults_write_failed",
|
|
26440
|
+
path: fp,
|
|
26441
|
+
message: toErrorMessage(err)
|
|
26442
|
+
});
|
|
26443
|
+
}
|
|
26444
|
+
}
|
|
26445
|
+
/**
|
|
26446
|
+
* Ensure a profile config file exists at the given path.
|
|
26447
|
+
* On first boot: migrate content from the old flat global config, or seed
|
|
26448
|
+
* with behavior defaults. Subsequent boots: fill any missing keys from
|
|
26449
|
+
* BEHAVIOR_DEFAULTS (keeping user settings intact).
|
|
26450
|
+
*
|
|
26451
|
+
* CRITICAL SAFETY NET: when the profile already exists but the old global
|
|
26452
|
+
* config has extra non-bootstrap keys (because WebUI/CLI persistence functions
|
|
26453
|
+
* mistakenly wrote settings to the root config), those keys are merged into
|
|
26454
|
+
* the profile so they are NOT silently destroyed by the subsequent bootstrap
|
|
26455
|
+
* trim. This prevents the "config keeps getting emptied" bug.
|
|
26456
|
+
*/
|
|
26457
|
+
async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
|
|
26458
|
+
const t0 = Date.now();
|
|
26459
|
+
let parsed;
|
|
26460
|
+
let existed = true;
|
|
26461
|
+
try {
|
|
26462
|
+
const raw = await fs14.readFile(profileFp, "utf8");
|
|
26463
|
+
const result = safeParse(raw);
|
|
26464
|
+
if (!result.ok || !isPlainRecord(result.value)) {
|
|
26465
|
+
this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
|
|
26466
|
+
event: "config.profile_parse_failed",
|
|
26467
|
+
path: profileFp
|
|
26468
|
+
});
|
|
26469
|
+
parsed = {};
|
|
26470
|
+
} else {
|
|
26471
|
+
parsed = result.value;
|
|
26472
|
+
}
|
|
26473
|
+
} catch (err) {
|
|
26474
|
+
if (err.code !== "ENOENT") {
|
|
26475
|
+
this.logWarn("Profile config read failed", {
|
|
26476
|
+
event: "config.profile_read_failed",
|
|
26477
|
+
path: profileFp,
|
|
26478
|
+
message: toErrorMessage(err)
|
|
26479
|
+
});
|
|
26480
|
+
return;
|
|
26481
|
+
}
|
|
26482
|
+
existed = false;
|
|
26483
|
+
parsed = {};
|
|
26484
|
+
}
|
|
26485
|
+
const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
|
|
26486
|
+
let seed;
|
|
26487
|
+
if (!profileHasContent && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
|
|
26488
|
+
seed = { ...oldGlobalParsed };
|
|
26489
|
+
delete seed["activeProfile"];
|
|
26490
|
+
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
26491
|
+
seed = filled.value;
|
|
26492
|
+
} else {
|
|
26493
|
+
if (existed && !_DefaultConfigLoader.isBootstrapOnly(oldGlobalParsed)) {
|
|
26494
|
+
const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
26495
|
+
let merged = false;
|
|
26496
|
+
for (const [k, v] of Object.entries(oldGlobalParsed)) {
|
|
26497
|
+
if (!BOOTSTRAP_KEYS.has(k) && !(k in parsed)) {
|
|
26498
|
+
parsed[k] = v;
|
|
26499
|
+
merged = true;
|
|
26500
|
+
}
|
|
26501
|
+
}
|
|
26502
|
+
if (merged) {
|
|
26503
|
+
const filled2 = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
26504
|
+
if (filled2.changed) {
|
|
26505
|
+
seed = filled2.value;
|
|
26506
|
+
await backupConfigFile(profileFp, this.paths);
|
|
26507
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
26508
|
+
this.events?.emit("storage.write", {
|
|
26509
|
+
sessionId: "~config~",
|
|
26510
|
+
store: "config",
|
|
26511
|
+
filePath: profileFp,
|
|
26512
|
+
operation: "ensure_profile_defaults",
|
|
26513
|
+
outcome: "success",
|
|
26514
|
+
durationMs: Date.now() - t0,
|
|
26515
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26516
|
+
});
|
|
26517
|
+
return;
|
|
26518
|
+
}
|
|
26519
|
+
}
|
|
26520
|
+
}
|
|
26521
|
+
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
26522
|
+
if (!filled.changed) return;
|
|
26523
|
+
seed = filled.value;
|
|
25638
26524
|
}
|
|
26525
|
+
await backupConfigFile(profileFp, this.paths);
|
|
26526
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
26527
|
+
this.events?.emit("storage.write", {
|
|
26528
|
+
sessionId: "~config~",
|
|
26529
|
+
store: "config",
|
|
26530
|
+
filePath: profileFp,
|
|
26531
|
+
operation: "ensure_profile_defaults",
|
|
26532
|
+
outcome: "success",
|
|
26533
|
+
durationMs: Date.now() - t0,
|
|
26534
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26535
|
+
});
|
|
25639
26536
|
}
|
|
25640
26537
|
/**
|
|
25641
26538
|
* Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
|
|
@@ -25685,7 +26582,7 @@ var DefaultConfigLoader = class {
|
|
|
25685
26582
|
const fp = this.paths.syncConfig;
|
|
25686
26583
|
const t0 = Date.now();
|
|
25687
26584
|
try {
|
|
25688
|
-
const raw = await
|
|
26585
|
+
const raw = await fs14.readFile(fp, "utf8");
|
|
25689
26586
|
const parsed = safeParse(raw);
|
|
25690
26587
|
if (!parsed.ok || !parsed.value) {
|
|
25691
26588
|
this.events?.emit("storage.read", {
|
|
@@ -25736,10 +26633,10 @@ var DefaultConfigLoader = class {
|
|
|
25736
26633
|
error: storageErrorString(err),
|
|
25737
26634
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25738
26635
|
});
|
|
25739
|
-
this.logWarn(
|
|
25740
|
-
|
|
25741
|
-
|
|
25742
|
-
);
|
|
26636
|
+
this.logWarn("Config sync load failed", {
|
|
26637
|
+
event: "config.sync_load_failed",
|
|
26638
|
+
message: toErrorMessage(err)
|
|
26639
|
+
});
|
|
25743
26640
|
return null;
|
|
25744
26641
|
}
|
|
25745
26642
|
}
|
|
@@ -25747,8 +26644,8 @@ var DefaultConfigLoader = class {
|
|
|
25747
26644
|
const t0 = Date.now();
|
|
25748
26645
|
let mtimeMs = null;
|
|
25749
26646
|
try {
|
|
25750
|
-
const
|
|
25751
|
-
mtimeMs =
|
|
26647
|
+
const stat13 = await fs14.stat(file);
|
|
26648
|
+
mtimeMs = stat13.mtimeMs;
|
|
25752
26649
|
const cached = this.jsonCache.get(file);
|
|
25753
26650
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
25754
26651
|
return structuredClone(cached.value);
|
|
@@ -25768,15 +26665,16 @@ var DefaultConfigLoader = class {
|
|
|
25768
26665
|
error: storageErrorString(err),
|
|
25769
26666
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25770
26667
|
});
|
|
25771
|
-
this.logWarn(
|
|
25772
|
-
|
|
25773
|
-
|
|
25774
|
-
|
|
26668
|
+
this.logWarn("Config read failed (stat)", {
|
|
26669
|
+
event: "config.read_failed",
|
|
26670
|
+
path: file,
|
|
26671
|
+
message: toErrorMessage(err)
|
|
26672
|
+
});
|
|
25775
26673
|
return {};
|
|
25776
26674
|
}
|
|
25777
26675
|
let raw;
|
|
25778
26676
|
try {
|
|
25779
|
-
raw = await
|
|
26677
|
+
raw = await fs14.readFile(file, "utf8");
|
|
25780
26678
|
} catch (err) {
|
|
25781
26679
|
if (err.code !== "ENOENT") {
|
|
25782
26680
|
this.events?.emit("storage.read", {
|
|
@@ -25789,10 +26687,11 @@ var DefaultConfigLoader = class {
|
|
|
25789
26687
|
error: storageErrorString(err),
|
|
25790
26688
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25791
26689
|
});
|
|
25792
|
-
this.logWarn(
|
|
25793
|
-
|
|
25794
|
-
|
|
25795
|
-
|
|
26690
|
+
this.logWarn("Config read failed (read)", {
|
|
26691
|
+
event: "config.read_failed",
|
|
26692
|
+
path: file,
|
|
26693
|
+
message: toErrorMessage(err)
|
|
26694
|
+
});
|
|
25796
26695
|
}
|
|
25797
26696
|
this.jsonCache.set(file, { mtimeMs: null, value: {} });
|
|
25798
26697
|
return {};
|
|
@@ -25809,10 +26708,10 @@ var DefaultConfigLoader = class {
|
|
|
25809
26708
|
error: "parse error or empty file",
|
|
25810
26709
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
25811
26710
|
});
|
|
25812
|
-
this.logWarn(
|
|
25813
|
-
|
|
25814
|
-
|
|
25815
|
-
);
|
|
26711
|
+
this.logWarn("Config parse failed \u2014 falling back to defaults", {
|
|
26712
|
+
event: "config.parse_failed",
|
|
26713
|
+
path: file
|
|
26714
|
+
});
|
|
25816
26715
|
return {};
|
|
25817
26716
|
}
|
|
25818
26717
|
this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });
|
|
@@ -26306,7 +27205,7 @@ ${cat}:`);
|
|
|
26306
27205
|
|
|
26307
27206
|
// src/storage/queue-store.ts
|
|
26308
27207
|
import * as fsp15 from "node:fs/promises";
|
|
26309
|
-
import * as
|
|
27208
|
+
import * as path28 from "node:path";
|
|
26310
27209
|
var QueueStore = class {
|
|
26311
27210
|
file;
|
|
26312
27211
|
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
@@ -26315,7 +27214,7 @@ var QueueStore = class {
|
|
|
26315
27214
|
traceId;
|
|
26316
27215
|
logger;
|
|
26317
27216
|
constructor(opts) {
|
|
26318
|
-
this.file =
|
|
27217
|
+
this.file = path28.join(opts.dir, "queue.json");
|
|
26319
27218
|
this.events = opts.events;
|
|
26320
27219
|
this.traceId = opts.traceId;
|
|
26321
27220
|
this.logger = opts.logger;
|
|
@@ -26473,7 +27372,7 @@ function isPersistedQueueItem(v) {
|
|
|
26473
27372
|
// src/storage/recovery-lock.ts
|
|
26474
27373
|
import * as fsp16 from "node:fs/promises";
|
|
26475
27374
|
import * as os3 from "node:os";
|
|
26476
|
-
import * as
|
|
27375
|
+
import * as path29 from "node:path";
|
|
26477
27376
|
var LOCK_FILE = "active.json";
|
|
26478
27377
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
26479
27378
|
var RecoveryLock = class {
|
|
@@ -26484,7 +27383,7 @@ var RecoveryLock = class {
|
|
|
26484
27383
|
sessionStore;
|
|
26485
27384
|
probe;
|
|
26486
27385
|
constructor(opts) {
|
|
26487
|
-
this.file =
|
|
27386
|
+
this.file = path29.join(opts.dir, LOCK_FILE);
|
|
26488
27387
|
this.pid = opts.pid ?? process.pid;
|
|
26489
27388
|
this.hostname = opts.hostname ?? os3.hostname();
|
|
26490
27389
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -26545,7 +27444,7 @@ var RecoveryLock = class {
|
|
|
26545
27444
|
* null return before calling this.
|
|
26546
27445
|
*/
|
|
26547
27446
|
async write(sessionId) {
|
|
26548
|
-
await ensureDir(
|
|
27447
|
+
await ensureDir(path29.dirname(this.file));
|
|
26549
27448
|
const lock = {
|
|
26550
27449
|
v: 1,
|
|
26551
27450
|
sessionId,
|
|
@@ -26844,7 +27743,7 @@ function resolveSessionLoggingConfig(cfg) {
|
|
|
26844
27743
|
}
|
|
26845
27744
|
|
|
26846
27745
|
// src/storage/session-reader.ts
|
|
26847
|
-
import * as
|
|
27746
|
+
import * as fs15 from "node:fs/promises";
|
|
26848
27747
|
var DefaultSessionReader = class _DefaultSessionReader {
|
|
26849
27748
|
store;
|
|
26850
27749
|
eventCache = /* @__PURE__ */ new Map();
|
|
@@ -26862,8 +27761,8 @@ var DefaultSessionReader = class _DefaultSessionReader {
|
|
|
26862
27761
|
const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
|
|
26863
27762
|
let mtimeMs = null;
|
|
26864
27763
|
try {
|
|
26865
|
-
const
|
|
26866
|
-
mtimeMs =
|
|
27764
|
+
const stat13 = await fs15.stat(sessionPath);
|
|
27765
|
+
mtimeMs = stat13.mtimeMs;
|
|
26867
27766
|
} catch {
|
|
26868
27767
|
this.eventCache.delete(sessionId);
|
|
26869
27768
|
this.eventCacheMtimes.delete(sessionId);
|