@wrongstack/core 0.5.7 → 0.6.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/{agent-bridge-BKNiE1VH.d.ts → agent-bridge-DaCvA_uK.d.ts} +1 -1
- package/dist/coordination/index.d.ts +5 -5
- package/dist/coordination/index.js +218 -78
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +10 -9
- package/dist/defaults/index.js +1127 -180
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-DPQKFX7W.d.ts → events-CzkeaVVl.d.ts} +8 -2
- package/dist/execution/index.d.ts +281 -5
- package/dist/execution/index.js +785 -3
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +2 -2
- package/dist/extension/index.js +2 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/goal-store-DVCfj7Ff.d.ts +95 -0
- package/dist/{index-j2WyAyML.d.ts → index-BkKLQjea.d.ts} +34 -2
- package/dist/{index-Bf9Bpkdc.d.ts → index-i9rPR53g.d.ts} +11 -4
- package/dist/index.d.ts +51 -16
- package/dist/index.js +1506 -247
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/models/index.js +2 -3
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-Cpp7FXUl.d.ts → multi-agent-MfI6dmv-.d.ts} +30 -5
- package/dist/observability/index.d.ts +1 -1
- package/dist/{path-resolver--g_hKJ7V.d.ts → path-resolver-CWINz5XR.d.ts} +1 -1
- package/dist/{plan-templates-Bveo2W8n.d.ts → plan-templates-Bne1pB4d.d.ts} +6 -5
- package/dist/{provider-runner-hl4Il3xS.d.ts → provider-runner-CZYIzeBp.d.ts} +1 -1
- package/dist/sdd/index.d.ts +5 -3
- package/dist/sdd/index.js +102 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.js +82 -82
- package/dist/security/index.js.map +1 -1
- package/dist/skills/index.js +102 -38
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +3 -2
- package/dist/storage/index.js +134 -22
- package/dist/storage/index.js.map +1 -1
- package/dist/{tool-executor-DbAFkHdP.d.ts → tool-executor-40Q6shR-.d.ts} +1 -1
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +116 -103
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
package/dist/defaults/index.js
CHANGED
|
@@ -1,17 +1,104 @@
|
|
|
1
|
-
import * as fs5 from 'fs';
|
|
2
|
-
import * as path3 from 'path';
|
|
3
1
|
import * as crypto2 from 'crypto';
|
|
4
2
|
import { randomBytes, randomUUID, createCipheriv, createDecipheriv } from 'crypto';
|
|
5
3
|
import * as fsp from 'fs/promises';
|
|
4
|
+
import * as path3 from 'path';
|
|
5
|
+
import * as fs5 from 'fs';
|
|
6
6
|
import * as os from 'os';
|
|
7
|
+
import { execFile } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
7
9
|
import { EventEmitter } from 'events';
|
|
8
10
|
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
13
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
14
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
15
|
}) : x)(function(x) {
|
|
12
16
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
17
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
18
|
});
|
|
19
|
+
var __esm = (fn, res) => function __init() {
|
|
20
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
21
|
+
};
|
|
22
|
+
var __export = (target, all) => {
|
|
23
|
+
for (var name in all)
|
|
24
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/utils/atomic-write.ts
|
|
28
|
+
var atomic_write_exports = {};
|
|
29
|
+
__export(atomic_write_exports, {
|
|
30
|
+
atomicWrite: () => atomicWrite,
|
|
31
|
+
ensureDir: () => ensureDir
|
|
32
|
+
});
|
|
33
|
+
async function atomicWrite(targetPath, content, opts = {}) {
|
|
34
|
+
const dir = path3.dirname(targetPath);
|
|
35
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
36
|
+
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
37
|
+
try {
|
|
38
|
+
if (typeof content === "string") {
|
|
39
|
+
await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
40
|
+
} else {
|
|
41
|
+
await fsp.writeFile(tmp, content, { flag: "wx" });
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const fh = await fsp.open(tmp, "r+");
|
|
45
|
+
try {
|
|
46
|
+
await fh.sync();
|
|
47
|
+
} finally {
|
|
48
|
+
await fh.close();
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
let mode;
|
|
53
|
+
try {
|
|
54
|
+
const stat4 = await fsp.stat(targetPath);
|
|
55
|
+
mode = stat4.mode & 511;
|
|
56
|
+
} catch {
|
|
57
|
+
mode = opts.mode;
|
|
58
|
+
}
|
|
59
|
+
if (mode !== void 0) {
|
|
60
|
+
await fsp.chmod(tmp, mode);
|
|
61
|
+
}
|
|
62
|
+
await renameWithRetry(tmp, targetPath);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
try {
|
|
65
|
+
await fsp.unlink(tmp);
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function ensureDir(dir) {
|
|
72
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
async function renameWithRetry(from, to) {
|
|
75
|
+
if (process.platform !== "win32") {
|
|
76
|
+
await fsp.rename(from, to);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const delays = [10, 25, 60, 120, 250];
|
|
80
|
+
let lastErr;
|
|
81
|
+
for (let i = 0; i <= delays.length; i++) {
|
|
82
|
+
try {
|
|
83
|
+
await fsp.rename(from, to);
|
|
84
|
+
return;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
lastErr = err;
|
|
87
|
+
const code = err?.code;
|
|
88
|
+
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw lastErr;
|
|
95
|
+
}
|
|
96
|
+
var TRANSIENT_RENAME_CODES;
|
|
97
|
+
var init_atomic_write = __esm({
|
|
98
|
+
"src/utils/atomic-write.ts"() {
|
|
99
|
+
TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
15
102
|
|
|
16
103
|
// src/utils/color.ts
|
|
17
104
|
var isColorTty = () => {
|
|
@@ -132,70 +219,9 @@ function formatCtx(ctx) {
|
|
|
132
219
|
return color.dim(String(ctx));
|
|
133
220
|
}
|
|
134
221
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
139
|
-
try {
|
|
140
|
-
if (typeof content === "string") {
|
|
141
|
-
await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
142
|
-
} else {
|
|
143
|
-
await fsp.writeFile(tmp, content, { flag: "wx" });
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
const fh = await fsp.open(tmp, "r+");
|
|
147
|
-
try {
|
|
148
|
-
await fh.sync();
|
|
149
|
-
} finally {
|
|
150
|
-
await fh.close();
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
153
|
-
}
|
|
154
|
-
let mode;
|
|
155
|
-
try {
|
|
156
|
-
const stat4 = await fsp.stat(targetPath);
|
|
157
|
-
mode = stat4.mode & 511;
|
|
158
|
-
} catch {
|
|
159
|
-
mode = opts.mode;
|
|
160
|
-
}
|
|
161
|
-
if (mode !== void 0) {
|
|
162
|
-
await fsp.chmod(tmp, mode);
|
|
163
|
-
}
|
|
164
|
-
await renameWithRetry(tmp, targetPath);
|
|
165
|
-
} catch (err) {
|
|
166
|
-
try {
|
|
167
|
-
await fsp.unlink(tmp);
|
|
168
|
-
} catch {
|
|
169
|
-
}
|
|
170
|
-
throw err;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
async function ensureDir(dir) {
|
|
174
|
-
await fsp.mkdir(dir, { recursive: true });
|
|
175
|
-
}
|
|
176
|
-
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
177
|
-
async function renameWithRetry(from, to) {
|
|
178
|
-
if (process.platform !== "win32") {
|
|
179
|
-
await fsp.rename(from, to);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const delays = [10, 25, 60, 120, 250];
|
|
183
|
-
let lastErr;
|
|
184
|
-
for (let i = 0; i <= delays.length; i++) {
|
|
185
|
-
try {
|
|
186
|
-
await fsp.rename(from, to);
|
|
187
|
-
return;
|
|
188
|
-
} catch (err) {
|
|
189
|
-
lastErr = err;
|
|
190
|
-
const code = err?.code;
|
|
191
|
-
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
192
|
-
throw err;
|
|
193
|
-
}
|
|
194
|
-
await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
throw lastErr;
|
|
198
|
-
}
|
|
222
|
+
|
|
223
|
+
// src/storage/session-store.ts
|
|
224
|
+
init_atomic_write();
|
|
199
225
|
|
|
200
226
|
// src/utils/message-invariants.ts
|
|
201
227
|
function repairToolUseAdjacency(messages) {
|
|
@@ -333,19 +359,25 @@ var DefaultSessionStore = class {
|
|
|
333
359
|
{ cause: err }
|
|
334
360
|
);
|
|
335
361
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
handle,
|
|
339
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
340
|
-
{
|
|
362
|
+
try {
|
|
363
|
+
const writer = new FileSessionWriter(
|
|
341
364
|
id,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
365
|
+
handle,
|
|
366
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
367
|
+
{
|
|
368
|
+
id,
|
|
369
|
+
model: data.metadata.model,
|
|
370
|
+
provider: data.metadata.provider
|
|
371
|
+
},
|
|
372
|
+
this.events,
|
|
373
|
+
{ resumed: true, dir: this.dir, filePath: file }
|
|
374
|
+
);
|
|
375
|
+
return { writer, data };
|
|
376
|
+
} catch (err) {
|
|
377
|
+
await handle.close().catch(() => {
|
|
378
|
+
});
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
349
381
|
}
|
|
350
382
|
async load(id) {
|
|
351
383
|
const file = path3.join(this.dir, `${id}.jsonl`);
|
|
@@ -391,7 +423,7 @@ var DefaultSessionStore = class {
|
|
|
391
423
|
const full = path3.join(this.dir, `${id}.jsonl`);
|
|
392
424
|
const stat4 = await fsp.stat(full);
|
|
393
425
|
const summary = await this.summarize(id, stat4.mtime.toISOString());
|
|
394
|
-
await
|
|
426
|
+
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
395
427
|
console.warn(
|
|
396
428
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
397
429
|
err instanceof Error ? err.message : String(err)
|
|
@@ -619,7 +651,7 @@ var FileSessionWriter = class {
|
|
|
619
651
|
this.closed = true;
|
|
620
652
|
if (this.manifestFile) {
|
|
621
653
|
try {
|
|
622
|
-
await
|
|
654
|
+
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
623
655
|
} catch {
|
|
624
656
|
}
|
|
625
657
|
}
|
|
@@ -715,6 +747,9 @@ function userInputTitle(content) {
|
|
|
715
747
|
const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
716
748
|
return (text || "(non-text input)").slice(0, 60);
|
|
717
749
|
}
|
|
750
|
+
|
|
751
|
+
// src/storage/queue-store.ts
|
|
752
|
+
init_atomic_write();
|
|
718
753
|
var QueueStore = class {
|
|
719
754
|
file;
|
|
720
755
|
constructor(opts) {
|
|
@@ -765,6 +800,9 @@ function isPersistedQueueItem(v) {
|
|
|
765
800
|
const o = v;
|
|
766
801
|
return typeof o["displayText"] === "string" && Array.isArray(o["blocks"]);
|
|
767
802
|
}
|
|
803
|
+
|
|
804
|
+
// src/storage/attachment-store.ts
|
|
805
|
+
init_atomic_write();
|
|
768
806
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
769
807
|
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
|
|
770
808
|
var DefaultAttachmentStore = class {
|
|
@@ -786,7 +824,9 @@ var DefaultAttachmentStore = class {
|
|
|
786
824
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
787
825
|
await fsp.mkdir(this.spoolDir, { recursive: true });
|
|
788
826
|
spooledPath = path3.join(this.spoolDir, `${id}.bin`);
|
|
789
|
-
await
|
|
827
|
+
await atomicWrite(spooledPath, input.data, {
|
|
828
|
+
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
829
|
+
});
|
|
790
830
|
data = void 0;
|
|
791
831
|
}
|
|
792
832
|
const att = {
|
|
@@ -885,6 +925,9 @@ function mergeAdjacentText(blocks) {
|
|
|
885
925
|
}
|
|
886
926
|
return out;
|
|
887
927
|
}
|
|
928
|
+
|
|
929
|
+
// src/storage/memory-store.ts
|
|
930
|
+
init_atomic_write();
|
|
888
931
|
var MAX_BYTES_TOTAL = 32e3;
|
|
889
932
|
var DefaultMemoryStore = class {
|
|
890
933
|
files;
|
|
@@ -897,12 +940,13 @@ var DefaultMemoryStore = class {
|
|
|
897
940
|
* issue order. Different scopes still proceed in parallel.
|
|
898
941
|
*
|
|
899
942
|
* The chain tracks only the last pending write. If a write fails, its
|
|
900
|
-
* error is caught and swallowed
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
* backup whose worst case is losing a memory consolidation pass.
|
|
943
|
+
* error is caught and swallowed so the chain stays alive for subsequent
|
|
944
|
+
* calls. The error is stored in `writeErrors` so callers can learn about
|
|
945
|
+
* it on the next read operation.
|
|
904
946
|
*/
|
|
905
947
|
writeChain = /* @__PURE__ */ new Map();
|
|
948
|
+
/** Last write error per scope — surfaced as warnings on the next readAll(). */
|
|
949
|
+
writeErrors = /* @__PURE__ */ new Map();
|
|
906
950
|
constructor(opts) {
|
|
907
951
|
this.files = {
|
|
908
952
|
"project-agents": opts.paths.inProjectAgentsFile,
|
|
@@ -912,10 +956,16 @@ var DefaultMemoryStore = class {
|
|
|
912
956
|
}
|
|
913
957
|
async runSerialized(scope, work) {
|
|
914
958
|
const prior = this.writeChain.get(scope) ?? Promise.resolve();
|
|
959
|
+
prior.catch((err) => {
|
|
960
|
+
this.writeErrors.set(scope, err);
|
|
961
|
+
});
|
|
915
962
|
const next = prior.catch(() => void 0).then(work);
|
|
916
963
|
this.writeChain.set(scope, next);
|
|
917
964
|
try {
|
|
918
965
|
return await next;
|
|
966
|
+
} catch (err) {
|
|
967
|
+
this.writeErrors.set(scope, err);
|
|
968
|
+
throw err;
|
|
919
969
|
} finally {
|
|
920
970
|
if (this.writeChain.get(scope) === next) {
|
|
921
971
|
this.writeChain.delete(scope);
|
|
@@ -925,6 +975,10 @@ var DefaultMemoryStore = class {
|
|
|
925
975
|
async readAll() {
|
|
926
976
|
const parts = [];
|
|
927
977
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
978
|
+
const writeErr = this.writeErrors.get(scope);
|
|
979
|
+
if (writeErr) {
|
|
980
|
+
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
|
|
981
|
+
}
|
|
928
982
|
const body = await this.read(scope);
|
|
929
983
|
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
930
984
|
|
|
@@ -1483,6 +1537,9 @@ function runConfigMigrations(input, targetVersion, migrations) {
|
|
|
1483
1537
|
return { config: current, applied, shouldPersist };
|
|
1484
1538
|
}
|
|
1485
1539
|
var DEFAULT_CONFIG_MIGRATIONS = [];
|
|
1540
|
+
|
|
1541
|
+
// src/storage/recovery-lock.ts
|
|
1542
|
+
init_atomic_write();
|
|
1486
1543
|
var LOCK_FILE = "active.json";
|
|
1487
1544
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1488
1545
|
var RecoveryLock = class {
|
|
@@ -1999,6 +2056,9 @@ var SessionAnalyzer = class {
|
|
|
1999
2056
|
return last - first;
|
|
2000
2057
|
}
|
|
2001
2058
|
};
|
|
2059
|
+
|
|
2060
|
+
// src/storage/todos-checkpoint.ts
|
|
2061
|
+
init_atomic_write();
|
|
2002
2062
|
async function loadTodosCheckpoint(filePath) {
|
|
2003
2063
|
let raw;
|
|
2004
2064
|
try {
|
|
@@ -2067,6 +2127,9 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
|
2067
2127
|
}
|
|
2068
2128
|
};
|
|
2069
2129
|
}
|
|
2130
|
+
|
|
2131
|
+
// src/storage/plan-store.ts
|
|
2132
|
+
init_atomic_write();
|
|
2070
2133
|
async function loadPlan(filePath) {
|
|
2071
2134
|
let raw;
|
|
2072
2135
|
try {
|
|
@@ -2301,6 +2364,9 @@ ${cat}:`);
|
|
|
2301
2364
|
}
|
|
2302
2365
|
return lines.join("\n");
|
|
2303
2366
|
}
|
|
2367
|
+
|
|
2368
|
+
// src/storage/director-state.ts
|
|
2369
|
+
init_atomic_write();
|
|
2304
2370
|
async function loadDirectorState(filePath) {
|
|
2305
2371
|
let raw;
|
|
2306
2372
|
try {
|
|
@@ -2523,15 +2589,15 @@ var PATTERNS = [
|
|
|
2523
2589
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
2524
2590
|
{
|
|
2525
2591
|
type: "bearer_token",
|
|
2526
|
-
//
|
|
2527
|
-
//
|
|
2528
|
-
regex: /(
|
|
2592
|
+
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
2593
|
+
// backtracking risk on adversarial input. Bounded at 512 chars.
|
|
2594
|
+
regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,512}=*(?:$|[^A-Za-z0-9_.~+/-])/g
|
|
2529
2595
|
},
|
|
2530
2596
|
{
|
|
2531
2597
|
type: "high_entropy_env",
|
|
2532
|
-
//
|
|
2533
|
-
//
|
|
2534
|
-
regex:
|
|
2598
|
+
// Anchored with alternation instead of lookbehind to avoid backtracking.
|
|
2599
|
+
// Value bounded at 512 chars.
|
|
2600
|
+
regex: /(?:^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?:\s|$)/g
|
|
2535
2601
|
}
|
|
2536
2602
|
];
|
|
2537
2603
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
@@ -2588,6 +2654,7 @@ var DefaultSecretScrubber = class {
|
|
|
2588
2654
|
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
2589
2655
|
|
|
2590
2656
|
// src/security/secret-vault.ts
|
|
2657
|
+
init_atomic_write();
|
|
2591
2658
|
var KEY_BYTES = 32;
|
|
2592
2659
|
var IV_BYTES = 12;
|
|
2593
2660
|
var TAG_BYTES = 16;
|
|
@@ -2709,7 +2776,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
2709
2776
|
const merged = deepMerge2(current, patch ?? {});
|
|
2710
2777
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
2711
2778
|
await fsp.mkdir(path3.dirname(configPath), { recursive: true });
|
|
2712
|
-
await
|
|
2779
|
+
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
2713
2780
|
try {
|
|
2714
2781
|
await fsp.chmod(configPath, 384);
|
|
2715
2782
|
} catch {
|
|
@@ -2731,7 +2798,7 @@ async function migratePlaintextSecrets(configPath, vault) {
|
|
|
2731
2798
|
const counter = { n: 0 };
|
|
2732
2799
|
const migrated = walkCount(parsed, vault, counter);
|
|
2733
2800
|
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
2734
|
-
await
|
|
2801
|
+
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
2735
2802
|
try {
|
|
2736
2803
|
await fsp.chmod(configPath, 384);
|
|
2737
2804
|
} catch {
|
|
@@ -2780,6 +2847,9 @@ function deepMerge2(a, b) {
|
|
|
2780
2847
|
return out;
|
|
2781
2848
|
}
|
|
2782
2849
|
|
|
2850
|
+
// src/security/permission-policy.ts
|
|
2851
|
+
init_atomic_write();
|
|
2852
|
+
|
|
2783
2853
|
// src/utils/glob-match.ts
|
|
2784
2854
|
function escapeRegex(s) {
|
|
2785
2855
|
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
@@ -3279,7 +3349,7 @@ function buildRecoveryStrategies(opts) {
|
|
|
3279
3349
|
async attempt(err) {
|
|
3280
3350
|
if (!(err instanceof ProviderError) || err.status !== 429) return null;
|
|
3281
3351
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
3282
|
-
const delay = Math.
|
|
3352
|
+
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
3283
3353
|
await new Promise((r) => setTimeout(r, delay));
|
|
3284
3354
|
return { action: "retry", reason: "rate_limit_backoff" };
|
|
3285
3355
|
}
|
|
@@ -3621,17 +3691,23 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
3621
3691
|
handleTextDelta(state, ev.text);
|
|
3622
3692
|
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
3623
3693
|
break;
|
|
3624
|
-
case "tool_use_start":
|
|
3625
|
-
|
|
3626
|
-
|
|
3694
|
+
case "tool_use_start": {
|
|
3695
|
+
const idVal = ev.id;
|
|
3696
|
+
const nameVal = ev.name;
|
|
3697
|
+
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
3698
|
+
const emittedPayload = { ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
3699
|
+
events.emit("provider.tool_use_start", emittedPayload);
|
|
3627
3700
|
break;
|
|
3701
|
+
}
|
|
3628
3702
|
case "tool_use_input_delta":
|
|
3629
3703
|
handleToolUseInputDelta(state, ev);
|
|
3630
3704
|
break;
|
|
3631
|
-
case "tool_use_stop":
|
|
3705
|
+
case "tool_use_stop": {
|
|
3706
|
+
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
3632
3707
|
handleToolUseStop(state, ev);
|
|
3633
|
-
events.emit("provider.tool_use_stop", { ctx, id: ev.id });
|
|
3708
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
3634
3709
|
break;
|
|
3710
|
+
}
|
|
3635
3711
|
case "thinking_start":
|
|
3636
3712
|
handleThinkingStart(state, ev);
|
|
3637
3713
|
break;
|
|
@@ -4901,13 +4977,17 @@ var ToolExecutor = class {
|
|
|
4901
4977
|
const ctrl = new AbortController();
|
|
4902
4978
|
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
4903
4979
|
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
4980
|
+
let cleanupCalled = false;
|
|
4981
|
+
let caught = false;
|
|
4904
4982
|
try {
|
|
4905
4983
|
if (typeof tool.executeStream === "function") {
|
|
4906
4984
|
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
4907
4985
|
}
|
|
4908
4986
|
return await tool.execute(input, ctx, { signal: combined });
|
|
4909
4987
|
} catch (err) {
|
|
4988
|
+
caught = true;
|
|
4910
4989
|
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
4990
|
+
cleanupCalled = true;
|
|
4911
4991
|
try {
|
|
4912
4992
|
await tool.cleanup(input, ctx);
|
|
4913
4993
|
} catch {
|
|
@@ -4916,6 +4996,16 @@ var ToolExecutor = class {
|
|
|
4916
4996
|
throw err;
|
|
4917
4997
|
} finally {
|
|
4918
4998
|
clearTimeout(timer);
|
|
4999
|
+
if (combined.aborted && !caught) {
|
|
5000
|
+
if (!cleanupCalled && typeof tool.cleanup === "function") {
|
|
5001
|
+
try {
|
|
5002
|
+
await tool.cleanup(input, ctx);
|
|
5003
|
+
} catch {
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
|
|
5007
|
+
throw reason;
|
|
5008
|
+
}
|
|
4919
5009
|
}
|
|
4920
5010
|
}
|
|
4921
5011
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -4973,7 +5063,8 @@ var ToolExecutor = class {
|
|
|
4973
5063
|
if (subjectKey) {
|
|
4974
5064
|
const v = obj[subjectKey];
|
|
4975
5065
|
if (typeof v === "string") {
|
|
4976
|
-
|
|
5066
|
+
const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
5067
|
+
return isPathKey ? normalizePath(v) : escapeGlob(v);
|
|
4977
5068
|
}
|
|
4978
5069
|
}
|
|
4979
5070
|
if (toolName === "bash" && typeof obj.command === "string") {
|
|
@@ -5157,6 +5248,713 @@ var AutonomousRunner = class {
|
|
|
5157
5248
|
}
|
|
5158
5249
|
};
|
|
5159
5250
|
|
|
5251
|
+
// src/storage/goal-store.ts
|
|
5252
|
+
init_atomic_write();
|
|
5253
|
+
var MAX_JOURNAL_ENTRIES = 500;
|
|
5254
|
+
function goalFilePath(projectRoot) {
|
|
5255
|
+
return path3.join(projectRoot, ".wrongstack", "goal.json");
|
|
5256
|
+
}
|
|
5257
|
+
async function loadGoal(filePath) {
|
|
5258
|
+
let raw;
|
|
5259
|
+
try {
|
|
5260
|
+
raw = await fsp.readFile(filePath, "utf8");
|
|
5261
|
+
} catch {
|
|
5262
|
+
return null;
|
|
5263
|
+
}
|
|
5264
|
+
try {
|
|
5265
|
+
const parsed = JSON.parse(raw);
|
|
5266
|
+
if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
|
|
5267
|
+
return null;
|
|
5268
|
+
}
|
|
5269
|
+
return parsed;
|
|
5270
|
+
} catch {
|
|
5271
|
+
return null;
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
async function saveGoal(filePath, goal) {
|
|
5275
|
+
await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
|
|
5276
|
+
}
|
|
5277
|
+
function appendJournal(goal, entry) {
|
|
5278
|
+
const iteration = goal.iterations + 1;
|
|
5279
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5280
|
+
const full = { ...entry, iteration, at };
|
|
5281
|
+
const journal = [...goal.journal, full];
|
|
5282
|
+
const trimmed = journal.length > MAX_JOURNAL_ENTRIES ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES) : journal;
|
|
5283
|
+
return {
|
|
5284
|
+
...goal,
|
|
5285
|
+
iterations: iteration,
|
|
5286
|
+
lastActivityAt: at,
|
|
5287
|
+
journal: trimmed
|
|
5288
|
+
};
|
|
5289
|
+
}
|
|
5290
|
+
|
|
5291
|
+
// src/execution/eternal-autonomy.ts
|
|
5292
|
+
var execFileP = promisify(execFile);
|
|
5293
|
+
var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
|
|
5294
|
+
var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
5295
|
+
var EternalAutonomyEngine = class {
|
|
5296
|
+
constructor(opts) {
|
|
5297
|
+
this.opts = opts;
|
|
5298
|
+
this.goalPath = goalFilePath(opts.projectRoot);
|
|
5299
|
+
}
|
|
5300
|
+
opts;
|
|
5301
|
+
state = "idle";
|
|
5302
|
+
stopRequested = false;
|
|
5303
|
+
consecutiveFailures = 0;
|
|
5304
|
+
consecutiveBrainstormDone = 0;
|
|
5305
|
+
/**
|
|
5306
|
+
* Count of consecutive transient (recoverable) provider failures. Drives
|
|
5307
|
+
* the exponential backoff between iterations. Reset on the first
|
|
5308
|
+
* successful iteration so a single bad afternoon doesn't permanently
|
|
5309
|
+
* slow the loop down.
|
|
5310
|
+
*/
|
|
5311
|
+
consecutiveTransientRetries = 0;
|
|
5312
|
+
currentCtrl = null;
|
|
5313
|
+
iterationsSinceCompact = 0;
|
|
5314
|
+
goalPath;
|
|
5315
|
+
/** Current engine state — readable for UIs. */
|
|
5316
|
+
get currentState() {
|
|
5317
|
+
return this.state;
|
|
5318
|
+
}
|
|
5319
|
+
/** Synchronously request stop. Resolves once the running iteration aborts. */
|
|
5320
|
+
stop() {
|
|
5321
|
+
this.stopRequested = true;
|
|
5322
|
+
this.currentCtrl?.abort();
|
|
5323
|
+
void this.persistEngineState("stopped").catch(() => {
|
|
5324
|
+
});
|
|
5325
|
+
this.state = "stopped";
|
|
5326
|
+
}
|
|
5327
|
+
/**
|
|
5328
|
+
* Mark the engine as 'running' on disk + reset stop state so a new
|
|
5329
|
+
* batch of `runOneIteration()` calls can proceed. Called by the REPL
|
|
5330
|
+
* when the user invokes `/autonomy eternal`. Idempotent.
|
|
5331
|
+
*/
|
|
5332
|
+
async prime() {
|
|
5333
|
+
this.stopRequested = false;
|
|
5334
|
+
this.state = "running";
|
|
5335
|
+
await this.persistEngineState("running").catch(() => {
|
|
5336
|
+
});
|
|
5337
|
+
}
|
|
5338
|
+
/**
|
|
5339
|
+
* Main loop. Returns when stop() is called or the goal file is removed.
|
|
5340
|
+
* Does NOT throw — every iteration is wrapped to keep the loop alive.
|
|
5341
|
+
*/
|
|
5342
|
+
async run() {
|
|
5343
|
+
this.state = "running";
|
|
5344
|
+
await this.persistEngineState("running");
|
|
5345
|
+
try {
|
|
5346
|
+
while (!this.stopRequested) {
|
|
5347
|
+
let iterationOk = false;
|
|
5348
|
+
try {
|
|
5349
|
+
iterationOk = await this.runOneIteration();
|
|
5350
|
+
} catch (err) {
|
|
5351
|
+
this.consecutiveFailures++;
|
|
5352
|
+
this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
|
|
5353
|
+
await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
|
|
5354
|
+
}
|
|
5355
|
+
if (iterationOk) {
|
|
5356
|
+
this.consecutiveFailures = 0;
|
|
5357
|
+
}
|
|
5358
|
+
if (this.stopRequested) break;
|
|
5359
|
+
await sleep(this.opts.cycleGapMs ?? 1e3);
|
|
5360
|
+
}
|
|
5361
|
+
} finally {
|
|
5362
|
+
this.state = "stopped";
|
|
5363
|
+
await this.persistEngineState("stopped").catch(() => {
|
|
5364
|
+
});
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Execute a single sense-decide-execute-reflect cycle.
|
|
5369
|
+
* Returns true on success, false on handled failure / no-op.
|
|
5370
|
+
*
|
|
5371
|
+
* Exposed publicly so the REPL can pace iterations from its main loop
|
|
5372
|
+
* — running the engine and the REPL as a single sequential consumer of
|
|
5373
|
+
* `agent.run()` avoids race conditions on the shared Context.
|
|
5374
|
+
*/
|
|
5375
|
+
async runOneIteration() {
|
|
5376
|
+
const goal = await loadGoal(this.goalPath);
|
|
5377
|
+
if (!goal) {
|
|
5378
|
+
this.stopRequested = true;
|
|
5379
|
+
return false;
|
|
5380
|
+
}
|
|
5381
|
+
const missionState = goal.goalState ?? "active";
|
|
5382
|
+
if (missionState !== "active") {
|
|
5383
|
+
this.stopRequested = true;
|
|
5384
|
+
return false;
|
|
5385
|
+
}
|
|
5386
|
+
const action = await this.decide(goal);
|
|
5387
|
+
if (!action) {
|
|
5388
|
+
if (!this.stopRequested) {
|
|
5389
|
+
await sleep(5e3);
|
|
5390
|
+
}
|
|
5391
|
+
return false;
|
|
5392
|
+
}
|
|
5393
|
+
const ctrl = new AbortController();
|
|
5394
|
+
this.currentCtrl = ctrl;
|
|
5395
|
+
const timer = setTimeout(
|
|
5396
|
+
() => ctrl.abort(),
|
|
5397
|
+
this.opts.iterationTimeoutMs ?? 5 * 6e4
|
|
5398
|
+
);
|
|
5399
|
+
let status = "success";
|
|
5400
|
+
let note;
|
|
5401
|
+
let finalText = "";
|
|
5402
|
+
let isTransientFailure = false;
|
|
5403
|
+
const tc = this.opts.agent.ctx?.tokenCounter;
|
|
5404
|
+
const beforeUsage = tc?.total?.();
|
|
5405
|
+
const beforeCost = tc?.estimateCost?.().total;
|
|
5406
|
+
try {
|
|
5407
|
+
const result = await this.opts.agent.run(
|
|
5408
|
+
[{ type: "text", text: action.directive }],
|
|
5409
|
+
{
|
|
5410
|
+
signal: ctrl.signal,
|
|
5411
|
+
// Enable per-call autonomous continuation so the agent can chain
|
|
5412
|
+
// multiple internal tool/response cycles end-to-end on one
|
|
5413
|
+
// directive instead of returning to the engine after a single
|
|
5414
|
+
// round-trip. The model uses `[continue]` / `[done]` markers
|
|
5415
|
+
// (or the `continue_to_next_iteration` tool) to control the
|
|
5416
|
+
// inner loop. Without this flag the engine produced shallow
|
|
5417
|
+
// iterations and almost never let a real task finish.
|
|
5418
|
+
autonomousContinue: true,
|
|
5419
|
+
// Cap the inner loop so a runaway agent.run can't burn through
|
|
5420
|
+
// the iteration timeout — the engine's own outer loop is the
|
|
5421
|
+
// long-running thing, each tick should be bounded.
|
|
5422
|
+
maxIterations: this.opts.iterationMaxAgentSteps ?? 50
|
|
5423
|
+
}
|
|
5424
|
+
);
|
|
5425
|
+
if (result.status === "aborted") {
|
|
5426
|
+
status = "aborted";
|
|
5427
|
+
note = "stopped by user";
|
|
5428
|
+
} else if (result.status === "failed") {
|
|
5429
|
+
status = "failure";
|
|
5430
|
+
note = result.error?.describe?.() ?? "agent run failed";
|
|
5431
|
+
isTransientFailure = result.error?.recoverable === true;
|
|
5432
|
+
} else if (result.status === "max_iterations") {
|
|
5433
|
+
status = "failure";
|
|
5434
|
+
note = `max iterations (${result.iterations})`;
|
|
5435
|
+
} else {
|
|
5436
|
+
status = "success";
|
|
5437
|
+
finalText = result.finalText ?? "";
|
|
5438
|
+
const tail = finalText.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
5439
|
+
if (tail) note = tail;
|
|
5440
|
+
}
|
|
5441
|
+
} catch (err) {
|
|
5442
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
|
|
5443
|
+
status = isAbort ? "aborted" : "failure";
|
|
5444
|
+
note = err instanceof Error ? err.message : String(err);
|
|
5445
|
+
if (!isAbort && typeof err?.recoverable === "boolean") {
|
|
5446
|
+
isTransientFailure = err.recoverable;
|
|
5447
|
+
}
|
|
5448
|
+
} finally {
|
|
5449
|
+
clearTimeout(timer);
|
|
5450
|
+
this.currentCtrl = null;
|
|
5451
|
+
}
|
|
5452
|
+
if (action.source === "todo" && action.todoId && status !== "success") {
|
|
5453
|
+
await this.bumpTodoAttempt(action.todoId);
|
|
5454
|
+
}
|
|
5455
|
+
const afterUsage = tc?.total?.();
|
|
5456
|
+
const afterCost = tc?.estimateCost?.().total;
|
|
5457
|
+
const tokens = beforeUsage && afterUsage ? {
|
|
5458
|
+
input: Math.max(0, afterUsage.input - beforeUsage.input),
|
|
5459
|
+
output: Math.max(0, afterUsage.output - beforeUsage.output)
|
|
5460
|
+
} : void 0;
|
|
5461
|
+
const costUsd = typeof beforeCost === "number" && typeof afterCost === "number" ? Math.max(0, afterCost - beforeCost) : void 0;
|
|
5462
|
+
await this.appendIterationEntry({
|
|
5463
|
+
source: action.source,
|
|
5464
|
+
task: action.task,
|
|
5465
|
+
status,
|
|
5466
|
+
note,
|
|
5467
|
+
tokens,
|
|
5468
|
+
costUsd
|
|
5469
|
+
});
|
|
5470
|
+
let iterationIndex = 0;
|
|
5471
|
+
try {
|
|
5472
|
+
const reloaded = await loadGoal(this.goalPath);
|
|
5473
|
+
iterationIndex = reloaded?.iterations ?? 0;
|
|
5474
|
+
} catch {
|
|
5475
|
+
}
|
|
5476
|
+
this.opts.onIteration?.({
|
|
5477
|
+
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
5478
|
+
iteration: iterationIndex,
|
|
5479
|
+
source: action.source,
|
|
5480
|
+
task: action.task,
|
|
5481
|
+
status,
|
|
5482
|
+
note,
|
|
5483
|
+
tokens,
|
|
5484
|
+
costUsd
|
|
5485
|
+
});
|
|
5486
|
+
if (status === "failure") {
|
|
5487
|
+
if (isTransientFailure) {
|
|
5488
|
+
this.consecutiveTransientRetries++;
|
|
5489
|
+
const delay = this.computeTransientBackoffMs();
|
|
5490
|
+
if (delay > 0) {
|
|
5491
|
+
await this.sleepInterruptible(delay);
|
|
5492
|
+
}
|
|
5493
|
+
return false;
|
|
5494
|
+
}
|
|
5495
|
+
this.consecutiveFailures++;
|
|
5496
|
+
return false;
|
|
5497
|
+
}
|
|
5498
|
+
if (status === "aborted") {
|
|
5499
|
+
if (this.stopRequested) return false;
|
|
5500
|
+
this.consecutiveFailures++;
|
|
5501
|
+
return false;
|
|
5502
|
+
}
|
|
5503
|
+
this.consecutiveTransientRetries = 0;
|
|
5504
|
+
if (GOAL_COMPLETE_MARKER.test(finalText)) {
|
|
5505
|
+
await this.markGoalCompleted(action, finalText);
|
|
5506
|
+
this.stopRequested = true;
|
|
5507
|
+
return true;
|
|
5508
|
+
}
|
|
5509
|
+
this.iterationsSinceCompact++;
|
|
5510
|
+
await this.maybeCompact().catch((err) => {
|
|
5511
|
+
this.opts.onError?.(
|
|
5512
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
5513
|
+
this.consecutiveFailures
|
|
5514
|
+
);
|
|
5515
|
+
});
|
|
5516
|
+
return true;
|
|
5517
|
+
}
|
|
5518
|
+
/**
|
|
5519
|
+
* Run compaction when either trigger fires:
|
|
5520
|
+
* - We've done >= compactEveryNIterations since the last compact.
|
|
5521
|
+
* - Current request tokens exceed aggressiveCompactRatio * maxContext.
|
|
5522
|
+
*
|
|
5523
|
+
* The second check uses *aggressive* mode to free more headroom; the
|
|
5524
|
+
* cadence check uses non-aggressive (cheaper).
|
|
5525
|
+
*/
|
|
5526
|
+
async maybeCompact() {
|
|
5527
|
+
const compactor = this.opts.compactor;
|
|
5528
|
+
if (!compactor) return;
|
|
5529
|
+
const ctx = this.opts.agent.ctx;
|
|
5530
|
+
if (!ctx) return;
|
|
5531
|
+
const cadence = this.opts.compactEveryNIterations ?? 25;
|
|
5532
|
+
const threshold = this.opts.aggressiveCompactRatio ?? 0.85;
|
|
5533
|
+
const maxCtx = this.opts.maxContextTokens;
|
|
5534
|
+
let aggressive = false;
|
|
5535
|
+
let shouldRun = false;
|
|
5536
|
+
if (this.iterationsSinceCompact >= cadence) {
|
|
5537
|
+
shouldRun = true;
|
|
5538
|
+
}
|
|
5539
|
+
if (maxCtx && maxCtx > 0) {
|
|
5540
|
+
const used = ctx.tokenCounter?.currentRequestTokens?.();
|
|
5541
|
+
if (used) {
|
|
5542
|
+
const total = used.input + used.cacheRead;
|
|
5543
|
+
if (total / maxCtx >= threshold) {
|
|
5544
|
+
shouldRun = true;
|
|
5545
|
+
aggressive = true;
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
if (!shouldRun) return;
|
|
5550
|
+
const report = await compactor.compact(ctx, { aggressive });
|
|
5551
|
+
this.iterationsSinceCompact = 0;
|
|
5552
|
+
const saved = report.before - report.after;
|
|
5553
|
+
await this.appendIterationEntry({
|
|
5554
|
+
source: "manual",
|
|
5555
|
+
task: `compaction (${aggressive ? "aggressive" : "cadence"})`,
|
|
5556
|
+
status: "success",
|
|
5557
|
+
note: `saved ~${saved} tokens (${report.before}\u2192${report.after})`
|
|
5558
|
+
});
|
|
5559
|
+
}
|
|
5560
|
+
/**
|
|
5561
|
+
* Hybrid idea source.
|
|
5562
|
+
* 1. Pending todos on the agent's context.
|
|
5563
|
+
* 2. Dirty git working tree → propose a "review and finish this" task.
|
|
5564
|
+
* 3. Otherwise: brainstorm via the LLM against the goal.
|
|
5565
|
+
*
|
|
5566
|
+
* After failureBudget consecutive failures, force brainstorm so the
|
|
5567
|
+
* engine doesn't loop on the same broken todo or stuck git state.
|
|
5568
|
+
*/
|
|
5569
|
+
async decide(goal) {
|
|
5570
|
+
const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
|
|
5571
|
+
if (!forceBrainstorm) {
|
|
5572
|
+
const todo = this.pickPendingTodo(goal);
|
|
5573
|
+
if (todo) {
|
|
5574
|
+
return {
|
|
5575
|
+
source: "todo",
|
|
5576
|
+
task: todo.content,
|
|
5577
|
+
todoId: todo.id,
|
|
5578
|
+
directive: this.buildDirective(goal, "todo", todo.content)
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
const gitTask = await this.pickGitTask();
|
|
5582
|
+
if (gitTask) {
|
|
5583
|
+
return {
|
|
5584
|
+
source: "git",
|
|
5585
|
+
task: gitTask,
|
|
5586
|
+
directive: this.buildDirective(goal, "git", gitTask)
|
|
5587
|
+
};
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
const brainstormed = await this.brainstormTask(goal);
|
|
5591
|
+
if (brainstormed === BRAINSTORM_DONE) {
|
|
5592
|
+
this.consecutiveBrainstormDone++;
|
|
5593
|
+
const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
|
|
5594
|
+
if (this.consecutiveBrainstormDone >= threshold) {
|
|
5595
|
+
await this.markGoalCompleted(
|
|
5596
|
+
{ source: "brainstorm", task: "no further work", directive: "" },
|
|
5597
|
+
`brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
|
|
5598
|
+
);
|
|
5599
|
+
this.stopRequested = true;
|
|
5600
|
+
}
|
|
5601
|
+
return null;
|
|
5602
|
+
}
|
|
5603
|
+
if (!brainstormed) return null;
|
|
5604
|
+
this.consecutiveBrainstormDone = 0;
|
|
5605
|
+
return {
|
|
5606
|
+
source: "brainstorm",
|
|
5607
|
+
task: brainstormed,
|
|
5608
|
+
directive: this.buildDirective(goal, "brainstorm", brainstormed)
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
pickPendingTodo(goal) {
|
|
5612
|
+
const todos = this.opts.agent.ctx.todos;
|
|
5613
|
+
if (!Array.isArray(todos)) return null;
|
|
5614
|
+
const attempts = goal.todoAttempts ?? {};
|
|
5615
|
+
const ceiling = this.opts.todoMaxAttempts ?? 3;
|
|
5616
|
+
for (const t of todos) {
|
|
5617
|
+
if (t.status !== "pending") continue;
|
|
5618
|
+
const used = attempts[t.id] ?? 0;
|
|
5619
|
+
if (used >= ceiling) continue;
|
|
5620
|
+
return t;
|
|
5621
|
+
}
|
|
5622
|
+
return null;
|
|
5623
|
+
}
|
|
5624
|
+
async pickGitTask() {
|
|
5625
|
+
let out;
|
|
5626
|
+
try {
|
|
5627
|
+
out = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
|
|
5628
|
+
} catch {
|
|
5629
|
+
return null;
|
|
5630
|
+
}
|
|
5631
|
+
const dirty = out.trim();
|
|
5632
|
+
if (!dirty) return null;
|
|
5633
|
+
const lines = dirty.split("\n").slice(0, 8);
|
|
5634
|
+
const preview = lines.join(", ");
|
|
5635
|
+
return `Inspect the dirty working tree and either finish the in-progress work or revert it. Files: ${preview}`;
|
|
5636
|
+
}
|
|
5637
|
+
async readGitStatus() {
|
|
5638
|
+
const { stdout } = await execFileP("git", ["status", "--porcelain"], {
|
|
5639
|
+
cwd: this.opts.projectRoot,
|
|
5640
|
+
timeout: 5e3
|
|
5641
|
+
});
|
|
5642
|
+
return stdout;
|
|
5643
|
+
}
|
|
5644
|
+
async brainstormTask(goal) {
|
|
5645
|
+
const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
|
|
5646
|
+
const directive = [
|
|
5647
|
+
"You are deciding the next action in an autonomous loop pursuing a long-running goal.",
|
|
5648
|
+
"",
|
|
5649
|
+
`Goal: ${goal.goal}`,
|
|
5650
|
+
"",
|
|
5651
|
+
lastFew ? `Recent iterations:
|
|
5652
|
+
${lastFew}` : "No prior iterations yet.",
|
|
5653
|
+
"",
|
|
5654
|
+
"Output ONE concrete, immediately-actionable task that advances the goal.",
|
|
5655
|
+
"Constraints:",
|
|
5656
|
+
"- One sentence, imperative form, under 200 chars.",
|
|
5657
|
+
"- No preamble, no explanation, no markdown \u2014 just the task line.",
|
|
5658
|
+
"- If recent iterations show repeated failures on the same target, pivot.",
|
|
5659
|
+
"- If the goal appears fully accomplished AND you can name a concrete",
|
|
5660
|
+
" artifact / test / output that proves it, output exactly: DONE",
|
|
5661
|
+
"- Be conservative with DONE: if the recent journal contains failures",
|
|
5662
|
+
" or aborted entries, the goal is almost certainly NOT done."
|
|
5663
|
+
].join("\n");
|
|
5664
|
+
try {
|
|
5665
|
+
const ctrl = new AbortController();
|
|
5666
|
+
const timer = setTimeout(() => ctrl.abort(), 6e4);
|
|
5667
|
+
try {
|
|
5668
|
+
const result = await this.opts.agent.run(
|
|
5669
|
+
[{ type: "text", text: directive }],
|
|
5670
|
+
{ signal: ctrl.signal, maxIterations: 1 }
|
|
5671
|
+
);
|
|
5672
|
+
if (result.status !== "done") return null;
|
|
5673
|
+
const text = (result.finalText ?? "").trim();
|
|
5674
|
+
if (!text) return null;
|
|
5675
|
+
if (/^DONE\.?$/i.test(text)) return BRAINSTORM_DONE;
|
|
5676
|
+
const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
|
|
5677
|
+
if (!firstLine) return null;
|
|
5678
|
+
if (/^DONE\.?$/i.test(firstLine)) return BRAINSTORM_DONE;
|
|
5679
|
+
return firstLine.slice(0, 240);
|
|
5680
|
+
} finally {
|
|
5681
|
+
clearTimeout(timer);
|
|
5682
|
+
}
|
|
5683
|
+
} catch {
|
|
5684
|
+
return null;
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
buildDirective(goal, source, task) {
|
|
5688
|
+
const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
|
|
5689
|
+
return [
|
|
5690
|
+
"\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 iteration directive \u2550\u2550\u2550",
|
|
5691
|
+
"",
|
|
5692
|
+
`Mission: ${goal.goal}`,
|
|
5693
|
+
`Iteration: #${goal.iterations + 1}`,
|
|
5694
|
+
`Source: ${source}`,
|
|
5695
|
+
`Task: ${task}`,
|
|
5696
|
+
"",
|
|
5697
|
+
recentJournal ? `Recent journal (last 5):
|
|
5698
|
+
${recentJournal}` : "No prior iterations.",
|
|
5699
|
+
"",
|
|
5700
|
+
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
5701
|
+
"You are inside a long-running autonomous loop. Each iteration you",
|
|
5702
|
+
"execute ONE concrete task that advances the Mission. No user is",
|
|
5703
|
+
"available to clarify \u2014 make defensible decisions and move forward.",
|
|
5704
|
+
"",
|
|
5705
|
+
"1. EXECUTE END-TO-END",
|
|
5706
|
+
" \u2022 Use multiple tool calls freely. Emit `[continue]` on its own line",
|
|
5707
|
+
" to chain to the next internal step without returning.",
|
|
5708
|
+
" \u2022 When this iteration's Task is finished (real artifact / passing",
|
|
5709
|
+
" test / applied diff / clean output), emit `[done]` on its own line.",
|
|
5710
|
+
" \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
|
|
5711
|
+
" approaches before giving up. YOLO is active; no confirmations.",
|
|
5712
|
+
"",
|
|
5713
|
+
"2. UPDATE TODO STATE (when Source is `todo`)",
|
|
5714
|
+
" \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
|
|
5715
|
+
" \u2022 Mark it `completed` on success, with a one-line outcome note.",
|
|
5716
|
+
" \u2022 If you cannot make progress after 2 distinct attempts, mark it",
|
|
5717
|
+
" `cancelled` with the obstacle. The loop will skip it next time.",
|
|
5718
|
+
"",
|
|
5719
|
+
"3. MISSION-COMPLETE PROTOCOL",
|
|
5720
|
+
" \u2022 If \u2014 and ONLY if \u2014 the OVERALL Mission (not just this Task) is",
|
|
5721
|
+
" verifiably accomplished, emit on its own line:",
|
|
5722
|
+
" [GOAL_COMPLETE]",
|
|
5723
|
+
" followed by a one-paragraph verification recipe (artifact path,",
|
|
5724
|
+
" test command, or 10-second reproduction). This halts the loop.",
|
|
5725
|
+
" \u2022 NEVER emit [GOAL_COMPLETE] on optimism, partial progress, or",
|
|
5726
|
+
' "looks fine". Required: a concrete artifact that proves it AND',
|
|
5727
|
+
" no recent journal failures contradicting completion.",
|
|
5728
|
+
" \u2022 If unsure, emit `[done]` instead and let the next iteration",
|
|
5729
|
+
" decide. The loop is patient; false completion is not.",
|
|
5730
|
+
"",
|
|
5731
|
+
"4. NO INTERACTIVITY",
|
|
5732
|
+
" \u2022 Do not ask questions, do not request confirmation, do not propose",
|
|
5733
|
+
" options. Pick the best path and execute. The user is asleep."
|
|
5734
|
+
].join("\n");
|
|
5735
|
+
}
|
|
5736
|
+
/**
|
|
5737
|
+
* Exponential backoff for transient provider errors. `2^N * base`
|
|
5738
|
+
* capped at `transientBackoffMaxMs`. Zero base disables backoff.
|
|
5739
|
+
* Public-private to keep `runOneIteration` readable; the value is
|
|
5740
|
+
* recomputed each call from the current retry count, so callers
|
|
5741
|
+
* don't have to track state.
|
|
5742
|
+
*/
|
|
5743
|
+
computeTransientBackoffMs() {
|
|
5744
|
+
const base = this.opts.transientBackoffBaseMs ?? 2e3;
|
|
5745
|
+
const cap = this.opts.transientBackoffMaxMs ?? 6e4;
|
|
5746
|
+
if (base <= 0) return 0;
|
|
5747
|
+
const exponent = Math.max(0, this.consecutiveTransientRetries - 1);
|
|
5748
|
+
return Math.min(cap, base * Math.pow(2, exponent));
|
|
5749
|
+
}
|
|
5750
|
+
/**
|
|
5751
|
+
* Sleep that wakes early if `stopRequested` flips. Polls every 250 ms
|
|
5752
|
+
* so SIGINT / `/autonomy stop` can land in the middle of a long
|
|
5753
|
+
* backoff instead of waiting up to a minute for the timer.
|
|
5754
|
+
*/
|
|
5755
|
+
async sleepInterruptible(totalMs) {
|
|
5756
|
+
const step = 250;
|
|
5757
|
+
let remaining = totalMs;
|
|
5758
|
+
while (remaining > 0 && !this.stopRequested) {
|
|
5759
|
+
const chunk = Math.min(step, remaining);
|
|
5760
|
+
await sleep(chunk);
|
|
5761
|
+
remaining -= chunk;
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
async appendIterationEntry(entry) {
|
|
5765
|
+
const current = await loadGoal(this.goalPath);
|
|
5766
|
+
if (!current) {
|
|
5767
|
+
return;
|
|
5768
|
+
}
|
|
5769
|
+
const updated = appendJournal(current, entry);
|
|
5770
|
+
await saveGoal(this.goalPath, updated);
|
|
5771
|
+
}
|
|
5772
|
+
/**
|
|
5773
|
+
* Persistent per-todo failure counter. Skipped silently when the goal
|
|
5774
|
+
* file has been removed (graceful clear). Each non-success iteration
|
|
5775
|
+
* against a todo source bumps the counter by 1; `pickPendingTodo` reads
|
|
5776
|
+
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
5777
|
+
*/
|
|
5778
|
+
async bumpTodoAttempt(todoId) {
|
|
5779
|
+
const current = await loadGoal(this.goalPath);
|
|
5780
|
+
if (!current) return;
|
|
5781
|
+
const attempts = { ...current.todoAttempts ?? {} };
|
|
5782
|
+
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
5783
|
+
await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
|
|
5784
|
+
}
|
|
5785
|
+
/**
|
|
5786
|
+
* Flip the mission to `completed` and journal it. Called from two
|
|
5787
|
+
* paths: (a) `[GOAL_COMPLETE]` marker in a successful iteration's
|
|
5788
|
+
* finalText, (b) `brainstorm` returning DONE consecutively past the
|
|
5789
|
+
* configured threshold. Idempotent — re-entry is a no-op once the
|
|
5790
|
+
* goal is already `completed`.
|
|
5791
|
+
*/
|
|
5792
|
+
async markGoalCompleted(action, note) {
|
|
5793
|
+
const current = await loadGoal(this.goalPath);
|
|
5794
|
+
if (!current) return;
|
|
5795
|
+
if (current.goalState === "completed") return;
|
|
5796
|
+
const withFlag = { ...current, goalState: "completed" };
|
|
5797
|
+
const withEntry = appendJournal(withFlag, {
|
|
5798
|
+
source: action.source,
|
|
5799
|
+
task: `MISSION COMPLETE \u2014 ${action.task}`.slice(0, 240),
|
|
5800
|
+
status: "success",
|
|
5801
|
+
note: note.slice(0, 240)
|
|
5802
|
+
});
|
|
5803
|
+
await saveGoal(this.goalPath, withEntry);
|
|
5804
|
+
}
|
|
5805
|
+
async appendFailure(task, note) {
|
|
5806
|
+
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
5807
|
+
}
|
|
5808
|
+
async persistEngineState(state) {
|
|
5809
|
+
const current = await loadGoal(this.goalPath);
|
|
5810
|
+
if (!current) return;
|
|
5811
|
+
if (current.engineState === state) return;
|
|
5812
|
+
await saveGoal(this.goalPath, { ...current, engineState: state });
|
|
5813
|
+
}
|
|
5814
|
+
};
|
|
5815
|
+
function sleep(ms) {
|
|
5816
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5819
|
+
// src/execution/autonomy-prompt-contributor.ts
|
|
5820
|
+
function makeAutonomyPromptContributor(opts) {
|
|
5821
|
+
return async (ctx) => {
|
|
5822
|
+
if (ctx.subagent) return [];
|
|
5823
|
+
if (!opts.enabled()) return [];
|
|
5824
|
+
let goal;
|
|
5825
|
+
try {
|
|
5826
|
+
goal = await loadGoal(opts.goalPath);
|
|
5827
|
+
} catch {
|
|
5828
|
+
return [];
|
|
5829
|
+
}
|
|
5830
|
+
if (!goal) return [];
|
|
5831
|
+
const missionState = goal.goalState ?? "active";
|
|
5832
|
+
if (missionState !== "active") return [];
|
|
5833
|
+
const tailSize = opts.journalTailSize ?? 5;
|
|
5834
|
+
const journalTail = goal.journal.slice(-tailSize).map((e) => {
|
|
5835
|
+
const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
|
|
5836
|
+
return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
|
|
5837
|
+
});
|
|
5838
|
+
const text = [
|
|
5839
|
+
"## ETERNAL AUTONOMY \u2014 active mission",
|
|
5840
|
+
"",
|
|
5841
|
+
"You are inside a long-running autonomous loop. The user is asleep",
|
|
5842
|
+
"and is not available to confirm decisions. Each turn you receive a",
|
|
5843
|
+
"directive describing one concrete sub-task that advances the mission.",
|
|
5844
|
+
"",
|
|
5845
|
+
`Mission: ${goal.goal}`,
|
|
5846
|
+
`Iteration: #${goal.iterations}`,
|
|
5847
|
+
journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
|
|
5848
|
+
${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
|
|
5849
|
+
"",
|
|
5850
|
+
"### Loop control markers",
|
|
5851
|
+
"Emit these on their own line in your final text \u2014 case-insensitive,",
|
|
5852
|
+
"whitespace-tolerant, but they must occupy the entire line:",
|
|
5853
|
+
"- `[continue]` \u2014 chain to the next internal step without returning.",
|
|
5854
|
+
"- `[done]` \u2014 the current sub-task is finished; return to the engine.",
|
|
5855
|
+
"- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
|
|
5856
|
+
" verifiably done. Must be followed by a one-paragraph verification",
|
|
5857
|
+
" recipe (artifact path, test command, or 10-second reproduction).",
|
|
5858
|
+
" The engine halts on this marker \u2014 false positives waste real",
|
|
5859
|
+
" human time. If unsure, emit `[done]` and let the next iteration",
|
|
5860
|
+
" decide.",
|
|
5861
|
+
"",
|
|
5862
|
+
"### Operating principles",
|
|
5863
|
+
"- YOLO is active. Do NOT ask for confirmation, do NOT propose",
|
|
5864
|
+
" options. Pick the best path and execute it.",
|
|
5865
|
+
"- Use tools freely; multiple calls per turn are normal and expected.",
|
|
5866
|
+
"- When working on a todo, mark it `in_progress` via the todos tool",
|
|
5867
|
+
" before tool work and `completed` (or `cancelled` with a reason)",
|
|
5868
|
+
" when done. The loop reads todo state between iterations.",
|
|
5869
|
+
"- If an approach fails twice in a row, pivot. Don't grind on the",
|
|
5870
|
+
" same wall \u2014 try a different angle, file a cancel on the todo, or",
|
|
5871
|
+
" surface the obstacle via `[done]` and let the next iteration",
|
|
5872
|
+
" re-plan."
|
|
5873
|
+
].join("\n");
|
|
5874
|
+
return [
|
|
5875
|
+
{
|
|
5876
|
+
type: "text",
|
|
5877
|
+
text,
|
|
5878
|
+
cache_control: { type: "ephemeral" }
|
|
5879
|
+
}
|
|
5880
|
+
];
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5884
|
+
// src/execution/goal-preamble.ts
|
|
5885
|
+
function buildGoalPreamble(goal) {
|
|
5886
|
+
return [
|
|
5887
|
+
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
5888
|
+
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
5889
|
+
"",
|
|
5890
|
+
"YOUR GOAL:",
|
|
5891
|
+
"---",
|
|
5892
|
+
goal,
|
|
5893
|
+
"---",
|
|
5894
|
+
"",
|
|
5895
|
+
"AUTHORITY YOU HAVE:",
|
|
5896
|
+
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
5897
|
+
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
5898
|
+
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
5899
|
+
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
5900
|
+
" batch work, specialist model for domain code.",
|
|
5901
|
+
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
5902
|
+
" The Agent loop auto-extends every 100 iterations forever.",
|
|
5903
|
+
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
5904
|
+
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
5905
|
+
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
5906
|
+
" to stick with the first plan you proposed.",
|
|
5907
|
+
"",
|
|
5908
|
+
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
5909
|
+
"- You can name a concrete artifact (a passing test, a written file at",
|
|
5910
|
+
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
5911
|
+
" a clean grep that previously had matches).",
|
|
5912
|
+
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
5913
|
+
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
5914
|
+
' believe this fixes it", "the changes appear correct".',
|
|
5915
|
+
"",
|
|
5916
|
+
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
5917
|
+
"- An error message you didn't recover from.",
|
|
5918
|
+
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
5919
|
+
" without questioning the search.",
|
|
5920
|
+
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
5921
|
+
' want X." Those are hedges. The user already told you to finish the',
|
|
5922
|
+
" goal \u2014 just do it.",
|
|
5923
|
+
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
5924
|
+
" done, not done.",
|
|
5925
|
+
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
5926
|
+
" respond to with a fresh attempt (different role, different model,",
|
|
5927
|
+
" tighter prompt).",
|
|
5928
|
+
"",
|
|
5929
|
+
"PERSISTENCE PROTOCOL:",
|
|
5930
|
+
"- If blocked, try at least 3 different angles before reporting the",
|
|
5931
|
+
" problem to the user. Different tool inputs, different subagent",
|
|
5932
|
+
" roles, different providers, different decomposition of the task.",
|
|
5933
|
+
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
5934
|
+
" not just report the failure back.",
|
|
5935
|
+
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
5936
|
+
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
5937
|
+
" final answer.",
|
|
5938
|
+
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
5939
|
+
" full delegated task.",
|
|
5940
|
+
"",
|
|
5941
|
+
"REPORTING:",
|
|
5942
|
+
"- Stream short progress notes between major actions so the user can",
|
|
5943
|
+
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
5944
|
+
" text \u2014 but also do not narrate every tool call.",
|
|
5945
|
+
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
5946
|
+
" subagents can read.",
|
|
5947
|
+
"- Final response must include: (a) what was accomplished, (b) how",
|
|
5948
|
+
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
5949
|
+
" should know about).",
|
|
5950
|
+
"",
|
|
5951
|
+
"BEGIN.]"
|
|
5952
|
+
].join("\n");
|
|
5953
|
+
}
|
|
5954
|
+
|
|
5955
|
+
// src/coordination/director.ts
|
|
5956
|
+
init_atomic_write();
|
|
5957
|
+
|
|
5160
5958
|
// src/coordination/in-memory-transport.ts
|
|
5161
5959
|
var InMemoryBridgeTransport = class {
|
|
5162
5960
|
subs = /* @__PURE__ */ new Map();
|
|
@@ -5610,7 +6408,7 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
5610
6408
|
this.decision = decision;
|
|
5611
6409
|
}
|
|
5612
6410
|
};
|
|
5613
|
-
var SubagentBudget = class {
|
|
6411
|
+
var SubagentBudget = class _SubagentBudget {
|
|
5614
6412
|
limits;
|
|
5615
6413
|
iterations = 0;
|
|
5616
6414
|
toolCalls = 0;
|
|
@@ -5619,6 +6417,26 @@ var SubagentBudget = class {
|
|
|
5619
6417
|
costUsd = 0;
|
|
5620
6418
|
startTime = null;
|
|
5621
6419
|
_onThreshold;
|
|
6420
|
+
/**
|
|
6421
|
+
* Tracks which budget kinds currently have an extension request
|
|
6422
|
+
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
6423
|
+
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
6424
|
+
* after the limit is reached spawns a fresh decision Promise (until
|
|
6425
|
+
* the first one lands and patches limits), flooding the FleetBus
|
|
6426
|
+
* with redundant threshold events. Cleared in `checkLimitAsync`'s
|
|
6427
|
+
* `finally`.
|
|
6428
|
+
*/
|
|
6429
|
+
pendingExtensions = /* @__PURE__ */ new Set();
|
|
6430
|
+
/**
|
|
6431
|
+
* Hard cap on how long `checkLimitAsync` waits for the coordinator to
|
|
6432
|
+
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
6433
|
+
* or hung listener (Director not built / event filter detached mid-run)
|
|
6434
|
+
* leaves the budget over-limit and never enforces anything, since
|
|
6435
|
+
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
6436
|
+
* Hardcoded for now — most fleets set their own per-task timeout that
|
|
6437
|
+
* dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
|
|
6438
|
+
*/
|
|
6439
|
+
static DECISION_TIMEOUT_MS = 3e4;
|
|
5622
6440
|
/**
|
|
5623
6441
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
5624
6442
|
* Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
|
|
@@ -5661,56 +6479,92 @@ var SubagentBudget = class {
|
|
|
5661
6479
|
if (kind === "timeout" || !this._onThreshold) {
|
|
5662
6480
|
throw new BudgetExceededError(kind, limit, used);
|
|
5663
6481
|
}
|
|
5664
|
-
|
|
6482
|
+
const bus = this._events;
|
|
6483
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
6484
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
6485
|
+
}
|
|
6486
|
+
if (this.pendingExtensions.has(kind)) return;
|
|
6487
|
+
this.pendingExtensions.add(kind);
|
|
6488
|
+
const decision = this.negotiateExtension(kind, used, limit);
|
|
6489
|
+
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
5665
6490
|
}
|
|
5666
6491
|
/**
|
|
5667
|
-
*
|
|
5668
|
-
*
|
|
5669
|
-
*
|
|
6492
|
+
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
6493
|
+
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
6494
|
+
* patched in-place; the runner should not abort). Always releases the
|
|
6495
|
+
* `pendingExtensions` slot in `finally`.
|
|
6496
|
+
*
|
|
6497
|
+
* The 'continue' return from a sync handler is treated as
|
|
6498
|
+
* `{ extend: {} }` — keep going without patching, next overrun will
|
|
6499
|
+
* fire a fresh signal.
|
|
5670
6500
|
*/
|
|
5671
|
-
async
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
6501
|
+
async negotiateExtension(kind, used, limit) {
|
|
6502
|
+
try {
|
|
6503
|
+
const result = this._onThreshold({
|
|
6504
|
+
kind,
|
|
6505
|
+
used,
|
|
6506
|
+
limit,
|
|
6507
|
+
// Inject a requestDecision helper the handler can call to emit the
|
|
6508
|
+
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
6509
|
+
// A hard fallback timer guarantees the promise eventually resolves
|
|
6510
|
+
// even if no listener responds — without it, an absent/detached
|
|
6511
|
+
// Director would leave the budget permanently in "asking" state.
|
|
6512
|
+
requestDecision: () => {
|
|
6513
|
+
const bus = this._events;
|
|
6514
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
6515
|
+
return Promise.resolve("stop");
|
|
6516
|
+
}
|
|
6517
|
+
return new Promise((resolve2) => {
|
|
6518
|
+
let resolved = false;
|
|
6519
|
+
const respond = (d) => {
|
|
6520
|
+
if (resolved) return;
|
|
6521
|
+
resolved = true;
|
|
6522
|
+
resolve2(d);
|
|
6523
|
+
};
|
|
6524
|
+
const fallback = setTimeout(
|
|
6525
|
+
() => respond("stop"),
|
|
6526
|
+
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
6527
|
+
);
|
|
6528
|
+
bus.emit("budget.threshold_reached", {
|
|
6529
|
+
kind,
|
|
6530
|
+
used,
|
|
6531
|
+
limit,
|
|
6532
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
6533
|
+
extend: (extra) => {
|
|
6534
|
+
clearTimeout(fallback);
|
|
6535
|
+
respond({ extend: extra });
|
|
6536
|
+
},
|
|
6537
|
+
deny: () => {
|
|
6538
|
+
clearTimeout(fallback);
|
|
6539
|
+
respond("stop");
|
|
6540
|
+
}
|
|
6541
|
+
});
|
|
5688
6542
|
});
|
|
5689
|
-
}
|
|
6543
|
+
}
|
|
6544
|
+
});
|
|
6545
|
+
if (result === "throw") return "stop";
|
|
6546
|
+
if (result === "continue") return { extend: {} };
|
|
6547
|
+
const decision = await result;
|
|
6548
|
+
if (decision === "stop") return "stop";
|
|
6549
|
+
const ext = decision.extend;
|
|
6550
|
+
if (ext.maxIterations !== void 0) {
|
|
6551
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
5690
6552
|
}
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
if (ext.maxToolCalls !== void 0) {
|
|
5707
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
5708
|
-
}
|
|
5709
|
-
if (ext.maxTokens !== void 0) {
|
|
5710
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
5711
|
-
}
|
|
5712
|
-
if (ext.maxCostUsd !== void 0) {
|
|
5713
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
6553
|
+
if (ext.maxToolCalls !== void 0) {
|
|
6554
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
6555
|
+
}
|
|
6556
|
+
if (ext.maxTokens !== void 0) {
|
|
6557
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
6558
|
+
}
|
|
6559
|
+
if (ext.maxCostUsd !== void 0) {
|
|
6560
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
6561
|
+
}
|
|
6562
|
+
if (ext.timeoutMs !== void 0) {
|
|
6563
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
6564
|
+
}
|
|
6565
|
+
return decision;
|
|
6566
|
+
} finally {
|
|
6567
|
+
this.pendingExtensions.delete(kind);
|
|
5714
6568
|
}
|
|
5715
6569
|
}
|
|
5716
6570
|
recordIteration() {
|
|
@@ -5993,6 +6847,19 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5993
6847
|
setRunner(runner) {
|
|
5994
6848
|
this.runner = runner;
|
|
5995
6849
|
}
|
|
6850
|
+
/**
|
|
6851
|
+
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
6852
|
+
* preempt running tasks — already-dispatched subagents finish their
|
|
6853
|
+
* current task; only future dispatches respect the new cap. Raising
|
|
6854
|
+
* immediately tries to fill the freed slots from the pending queue.
|
|
6855
|
+
*/
|
|
6856
|
+
setMaxConcurrent(n) {
|
|
6857
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
6858
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
6859
|
+
}
|
|
6860
|
+
this.config.maxConcurrent = Math.floor(n);
|
|
6861
|
+
this.tryDispatchNext();
|
|
6862
|
+
}
|
|
5996
6863
|
async spawn(subagent) {
|
|
5997
6864
|
const id = subagent.id || randomUUID();
|
|
5998
6865
|
if (this.subagents.has(id)) {
|
|
@@ -6252,14 +7119,65 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
6252
7119
|
this.recordCompletion(result);
|
|
6253
7120
|
}
|
|
6254
7121
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
6255
|
-
const
|
|
6256
|
-
if (
|
|
7122
|
+
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
7123
|
+
if (initialTimeoutMs === void 0) return runner(task, ctx);
|
|
7124
|
+
const start = Date.now();
|
|
6257
7125
|
let timer = null;
|
|
6258
7126
|
const timeoutPromise = new Promise((_, reject) => {
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
7127
|
+
const armFor = (ms) => {
|
|
7128
|
+
if (timer) clearTimeout(timer);
|
|
7129
|
+
timer = setTimeout(async () => {
|
|
7130
|
+
const elapsed = Date.now() - start;
|
|
7131
|
+
const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
7132
|
+
if (!budget.onThreshold) {
|
|
7133
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7134
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7135
|
+
return;
|
|
7136
|
+
}
|
|
7137
|
+
try {
|
|
7138
|
+
const result = budget.onThreshold({
|
|
7139
|
+
kind: "timeout",
|
|
7140
|
+
used: elapsed,
|
|
7141
|
+
limit,
|
|
7142
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
7143
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
7144
|
+
kind: "timeout",
|
|
7145
|
+
used: elapsed,
|
|
7146
|
+
limit,
|
|
7147
|
+
timeoutMs: 3e4,
|
|
7148
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
7149
|
+
deny: () => resolveDecision("stop")
|
|
7150
|
+
});
|
|
7151
|
+
})
|
|
7152
|
+
});
|
|
7153
|
+
const decision = typeof result === "string" ? result : await result;
|
|
7154
|
+
if (decision === "continue") {
|
|
7155
|
+
armFor(Math.max(1e3, limit));
|
|
7156
|
+
return;
|
|
7157
|
+
}
|
|
7158
|
+
if (decision === "throw" || decision === "stop") {
|
|
7159
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7160
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7161
|
+
return;
|
|
7162
|
+
}
|
|
7163
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
7164
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
7165
|
+
const newLimit = decision.extend.timeoutMs;
|
|
7166
|
+
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
7167
|
+
armFor(remaining);
|
|
7168
|
+
return;
|
|
7169
|
+
}
|
|
7170
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7171
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7172
|
+
} catch (err) {
|
|
7173
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7174
|
+
reject(
|
|
7175
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
7176
|
+
);
|
|
7177
|
+
}
|
|
7178
|
+
}, ms);
|
|
7179
|
+
};
|
|
7180
|
+
armFor(initialTimeoutMs);
|
|
6263
7181
|
});
|
|
6264
7182
|
try {
|
|
6265
7183
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -6798,7 +7716,9 @@ var Director = class {
|
|
|
6798
7716
|
}, opts.checkpointDebounceMs ?? 250) : null;
|
|
6799
7717
|
this.fleetManager = opts.fleetManager;
|
|
6800
7718
|
if (this.sharedScratchpadPath) {
|
|
6801
|
-
void fsp.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
7719
|
+
void fsp.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
7720
|
+
(err) => this.logShutdownError("shared_scratchpad_mkdir", err)
|
|
7721
|
+
);
|
|
6802
7722
|
}
|
|
6803
7723
|
this.transport = new InMemoryBridgeTransport();
|
|
6804
7724
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -6868,7 +7788,7 @@ var Director = class {
|
|
|
6868
7788
|
return;
|
|
6869
7789
|
}
|
|
6870
7790
|
extendCounts.set(guardKey, prior + 1);
|
|
6871
|
-
|
|
7791
|
+
setImmediate(() => {
|
|
6872
7792
|
const extra = {};
|
|
6873
7793
|
switch (payload.kind) {
|
|
6874
7794
|
case "iterations":
|
|
@@ -6883,9 +7803,12 @@ var Director = class {
|
|
|
6883
7803
|
case "cost":
|
|
6884
7804
|
extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
|
|
6885
7805
|
break;
|
|
7806
|
+
case "timeout":
|
|
7807
|
+
extra.timeoutMs = Math.min(Math.ceil(payload.limit * 1.5), 60 * 6e4);
|
|
7808
|
+
break;
|
|
6886
7809
|
}
|
|
6887
7810
|
payload.extend(extra);
|
|
6888
|
-
}
|
|
7811
|
+
});
|
|
6889
7812
|
});
|
|
6890
7813
|
}
|
|
6891
7814
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
@@ -6904,7 +7827,9 @@ var Director = class {
|
|
|
6904
7827
|
if (this.manifestTimer) return;
|
|
6905
7828
|
this.manifestTimer = setTimeout(() => {
|
|
6906
7829
|
this.manifestTimer = null;
|
|
6907
|
-
void this.writeManifest().catch(
|
|
7830
|
+
void this.writeManifest().catch(
|
|
7831
|
+
(err) => this.logShutdownError("manifest_write_debounced", err)
|
|
7832
|
+
);
|
|
6908
7833
|
}, this.manifestDebounceMs);
|
|
6909
7834
|
}
|
|
6910
7835
|
/**
|
|
@@ -7095,7 +8020,7 @@ var Director = class {
|
|
|
7095
8020
|
usage: this.usage.snapshot()
|
|
7096
8021
|
};
|
|
7097
8022
|
await fsp.mkdir(path3.dirname(this.manifestPath), { recursive: true });
|
|
7098
|
-
await
|
|
8023
|
+
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
7099
8024
|
return this.manifestPath;
|
|
7100
8025
|
}
|
|
7101
8026
|
/**
|
|
@@ -7624,9 +8549,11 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
7624
8549
|
candidates.push(path3.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
7625
8550
|
} else {
|
|
7626
8551
|
try {
|
|
7627
|
-
const
|
|
7628
|
-
for (const
|
|
7629
|
-
|
|
8552
|
+
const entries = await fsp.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
8553
|
+
for (const entry of entries) {
|
|
8554
|
+
if (entry.isDirectory()) {
|
|
8555
|
+
candidates.push(path3.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
8556
|
+
}
|
|
7630
8557
|
}
|
|
7631
8558
|
} catch {
|
|
7632
8559
|
return void 0;
|
|
@@ -7676,6 +8603,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
7676
8603
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
7677
8604
|
const aborter = new AbortController();
|
|
7678
8605
|
ctx.budget._events = events;
|
|
8606
|
+
ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
|
|
7679
8607
|
let budgetError = null;
|
|
7680
8608
|
const onBudgetError = (err) => {
|
|
7681
8609
|
if (err instanceof BudgetThresholdSignal) {
|
|
@@ -7824,6 +8752,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
7824
8752
|
|
|
7825
8753
|
// src/coordination/null-fleet-bus.ts
|
|
7826
8754
|
var NULL_FLEET_BUS = new FleetBus();
|
|
8755
|
+
|
|
8756
|
+
// src/models/models-registry.ts
|
|
8757
|
+
init_atomic_write();
|
|
7827
8758
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
7828
8759
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
7829
8760
|
var FAMILY_BY_NPM = {
|
|
@@ -7992,6 +8923,9 @@ var DefaultModelsRegistry = class {
|
|
|
7992
8923
|
}
|
|
7993
8924
|
};
|
|
7994
8925
|
|
|
8926
|
+
// src/models/mode-store.ts
|
|
8927
|
+
init_atomic_write();
|
|
8928
|
+
|
|
7995
8929
|
// src/types/mode.ts
|
|
7996
8930
|
var DEFAULT_MODES = [
|
|
7997
8931
|
{
|
|
@@ -8292,10 +9226,9 @@ var DefaultModeStore = class {
|
|
|
8292
9226
|
try {
|
|
8293
9227
|
await fsp.mkdir(this.configDir, { recursive: true });
|
|
8294
9228
|
const configPath = path3.join(this.configDir, "mode.json");
|
|
8295
|
-
await
|
|
9229
|
+
await atomicWrite(
|
|
8296
9230
|
configPath,
|
|
8297
|
-
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
8298
|
-
"utf8"
|
|
9231
|
+
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
8299
9232
|
);
|
|
8300
9233
|
} catch {
|
|
8301
9234
|
}
|
|
@@ -9219,6 +10152,9 @@ var SpecDrivenDev = class {
|
|
|
9219
10152
|
}));
|
|
9220
10153
|
}
|
|
9221
10154
|
};
|
|
10155
|
+
|
|
10156
|
+
// src/sdd/spec-store.ts
|
|
10157
|
+
init_atomic_write();
|
|
9222
10158
|
var SpecStore = class {
|
|
9223
10159
|
baseDir;
|
|
9224
10160
|
indexPath;
|
|
@@ -9328,6 +10264,9 @@ var SpecStore = class {
|
|
|
9328
10264
|
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
9329
10265
|
}
|
|
9330
10266
|
};
|
|
10267
|
+
|
|
10268
|
+
// src/sdd/task-graph-store.ts
|
|
10269
|
+
init_atomic_write();
|
|
9331
10270
|
function graphToJSON(graph) {
|
|
9332
10271
|
const serialisable = {
|
|
9333
10272
|
...graph,
|
|
@@ -9629,10 +10568,11 @@ var AISpecBuilder = class {
|
|
|
9629
10568
|
async saveSession() {
|
|
9630
10569
|
if (!this.sessionPath) return;
|
|
9631
10570
|
try {
|
|
9632
|
-
const
|
|
9633
|
-
const
|
|
9634
|
-
await
|
|
9635
|
-
await
|
|
10571
|
+
const fsp14 = await import('fs/promises');
|
|
10572
|
+
const path18 = await import('path');
|
|
10573
|
+
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
10574
|
+
await fsp14.mkdir(path18.dirname(this.sessionPath), { recursive: true });
|
|
10575
|
+
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
9636
10576
|
} catch {
|
|
9637
10577
|
}
|
|
9638
10578
|
}
|
|
@@ -9640,8 +10580,8 @@ var AISpecBuilder = class {
|
|
|
9640
10580
|
async loadSession() {
|
|
9641
10581
|
if (!this.sessionPath) return false;
|
|
9642
10582
|
try {
|
|
9643
|
-
const
|
|
9644
|
-
const raw = await
|
|
10583
|
+
const fsp14 = await import('fs/promises');
|
|
10584
|
+
const raw = await fsp14.readFile(this.sessionPath, "utf8");
|
|
9645
10585
|
const loaded = JSON.parse(raw);
|
|
9646
10586
|
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
9647
10587
|
this.session = loaded;
|
|
@@ -9655,14 +10595,21 @@ var AISpecBuilder = class {
|
|
|
9655
10595
|
async deleteSession() {
|
|
9656
10596
|
if (!this.sessionPath) return;
|
|
9657
10597
|
try {
|
|
9658
|
-
const
|
|
9659
|
-
await
|
|
10598
|
+
const fsp14 = await import('fs/promises');
|
|
10599
|
+
await fsp14.unlink(this.sessionPath);
|
|
9660
10600
|
} catch {
|
|
9661
10601
|
}
|
|
9662
10602
|
}
|
|
9663
|
-
/** Auto-save helper — calls saveSession() but never throws.
|
|
10603
|
+
/** Auto-save helper — calls saveSession() but never throws.
|
|
10604
|
+
* Failures are surfaced via process.emitWarning so a persistent
|
|
10605
|
+
* ENOSPC / EACCES doesn't silently strand session edits in memory. */
|
|
9664
10606
|
autoSave() {
|
|
9665
|
-
this.saveSession().catch(() => {
|
|
10607
|
+
this.saveSession().catch((err) => {
|
|
10608
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
10609
|
+
process.emitWarning(
|
|
10610
|
+
`SpecBuilder autoSave failed: ${detail}`,
|
|
10611
|
+
"SpecBuilderWarning"
|
|
10612
|
+
);
|
|
9666
10613
|
});
|
|
9667
10614
|
}
|
|
9668
10615
|
// ── Session Lifecycle ─────────────────────────────────────────────────────
|
|
@@ -10334,15 +11281,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
|
|
|
10334
11281
|
maxId = id;
|
|
10335
11282
|
}
|
|
10336
11283
|
}
|
|
10337
|
-
const
|
|
11284
|
+
const path18 = [];
|
|
10338
11285
|
let current = maxId;
|
|
10339
11286
|
const visited = /* @__PURE__ */ new Set();
|
|
10340
11287
|
while (current && !visited.has(current)) {
|
|
10341
11288
|
visited.add(current);
|
|
10342
|
-
|
|
11289
|
+
path18.unshift(current);
|
|
10343
11290
|
current = prev.get(current) ?? null;
|
|
10344
11291
|
}
|
|
10345
|
-
return
|
|
11292
|
+
return path18;
|
|
10346
11293
|
}
|
|
10347
11294
|
function computeParallelGroups(graph, blockedByMap) {
|
|
10348
11295
|
const groups = [];
|
|
@@ -11052,7 +11999,7 @@ async function startMetricsServer(opts) {
|
|
|
11052
11999
|
const tls = opts.tls;
|
|
11053
12000
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
11054
12001
|
const host = opts.host ?? "127.0.0.1";
|
|
11055
|
-
const
|
|
12002
|
+
const path18 = opts.path ?? "/metrics";
|
|
11056
12003
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
11057
12004
|
const healthRegistry = opts.healthRegistry;
|
|
11058
12005
|
const listener = (req, res) => {
|
|
@@ -11062,7 +12009,7 @@ async function startMetricsServer(opts) {
|
|
|
11062
12009
|
return;
|
|
11063
12010
|
}
|
|
11064
12011
|
const url = req.url.split("?")[0];
|
|
11065
|
-
if (url ===
|
|
12012
|
+
if (url === path18) {
|
|
11066
12013
|
let body;
|
|
11067
12014
|
try {
|
|
11068
12015
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -11126,7 +12073,7 @@ async function startMetricsServer(opts) {
|
|
|
11126
12073
|
const protocol = useHttps ? "https" : "http";
|
|
11127
12074
|
return {
|
|
11128
12075
|
port: boundPort,
|
|
11129
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
12076
|
+
url: `${protocol}://${host}:${boundPort}${path18}`,
|
|
11130
12077
|
close: () => new Promise((resolve2, reject) => {
|
|
11131
12078
|
server.close((err) => err ? reject(err) : resolve2());
|
|
11132
12079
|
})
|
|
@@ -11743,6 +12690,6 @@ var allServers = () => ({
|
|
|
11743
12690
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
11744
12691
|
});
|
|
11745
12692
|
|
|
11746
|
-
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
12693
|
+
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
11747
12694
|
//# sourceMappingURL=index.js.map
|
|
11748
12695
|
//# sourceMappingURL=index.js.map
|