@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/index.js
CHANGED
|
@@ -1,23 +1,109 @@
|
|
|
1
1
|
import * as crypto2 from 'crypto';
|
|
2
2
|
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
3
|
import * as fsp2 from 'fs/promises';
|
|
5
|
-
import { readFile, readdir,
|
|
4
|
+
import { readFile, readdir, stat, mkdir } from 'fs/promises';
|
|
6
5
|
import * as path6 from 'path';
|
|
7
6
|
import { join, extname, relative } from 'path';
|
|
7
|
+
import * as fs2 from 'fs';
|
|
8
8
|
import * as os4 from 'os';
|
|
9
|
+
import { execFile, spawn } from 'child_process';
|
|
10
|
+
import { promisify } from 'util';
|
|
9
11
|
import { EventEmitter } from 'events';
|
|
10
12
|
import { createGunzip } from 'zlib';
|
|
11
13
|
import { Readable } from 'stream';
|
|
12
14
|
import { pipeline } from 'stream/promises';
|
|
13
|
-
import { spawn } from 'child_process';
|
|
14
15
|
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
15
18
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
16
19
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
17
20
|
}) : x)(function(x) {
|
|
18
21
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
19
22
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
20
23
|
});
|
|
24
|
+
var __esm = (fn, res) => function __init() {
|
|
25
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
26
|
+
};
|
|
27
|
+
var __export = (target, all) => {
|
|
28
|
+
for (var name in all)
|
|
29
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/utils/atomic-write.ts
|
|
33
|
+
var atomic_write_exports = {};
|
|
34
|
+
__export(atomic_write_exports, {
|
|
35
|
+
atomicWrite: () => atomicWrite,
|
|
36
|
+
ensureDir: () => ensureDir
|
|
37
|
+
});
|
|
38
|
+
async function atomicWrite(targetPath, content, opts = {}) {
|
|
39
|
+
const dir = path6.dirname(targetPath);
|
|
40
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
41
|
+
const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
42
|
+
try {
|
|
43
|
+
if (typeof content === "string") {
|
|
44
|
+
await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
45
|
+
} else {
|
|
46
|
+
await fsp2.writeFile(tmp, content, { flag: "wx" });
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const fh = await fsp2.open(tmp, "r+");
|
|
50
|
+
try {
|
|
51
|
+
await fh.sync();
|
|
52
|
+
} finally {
|
|
53
|
+
await fh.close();
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
let mode;
|
|
58
|
+
try {
|
|
59
|
+
const stat8 = await fsp2.stat(targetPath);
|
|
60
|
+
mode = stat8.mode & 511;
|
|
61
|
+
} catch {
|
|
62
|
+
mode = opts.mode;
|
|
63
|
+
}
|
|
64
|
+
if (mode !== void 0) {
|
|
65
|
+
await fsp2.chmod(tmp, mode);
|
|
66
|
+
}
|
|
67
|
+
await renameWithRetry(tmp, targetPath);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
try {
|
|
70
|
+
await fsp2.unlink(tmp);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function ensureDir(dir) {
|
|
77
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
async function renameWithRetry(from, to) {
|
|
80
|
+
if (process.platform !== "win32") {
|
|
81
|
+
await fsp2.rename(from, to);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const delays = [10, 25, 60, 120, 250];
|
|
85
|
+
let lastErr;
|
|
86
|
+
for (let i = 0; i <= delays.length; i++) {
|
|
87
|
+
try {
|
|
88
|
+
await fsp2.rename(from, to);
|
|
89
|
+
return;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
lastErr = err;
|
|
92
|
+
const code = err?.code;
|
|
93
|
+
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
await new Promise((resolve5) => setTimeout(resolve5, delays[i]));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw lastErr;
|
|
100
|
+
}
|
|
101
|
+
var TRANSIENT_RENAME_CODES;
|
|
102
|
+
var init_atomic_write = __esm({
|
|
103
|
+
"src/utils/atomic-write.ts"() {
|
|
104
|
+
TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
21
107
|
|
|
22
108
|
// src/types/errors.ts
|
|
23
109
|
var WrongStackError = class extends Error {
|
|
@@ -823,6 +909,7 @@ function providerStatusToCode(status, type) {
|
|
|
823
909
|
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
824
910
|
|
|
825
911
|
// src/security/secret-vault.ts
|
|
912
|
+
init_atomic_write();
|
|
826
913
|
var KEY_BYTES = 32;
|
|
827
914
|
var IV_BYTES = 12;
|
|
828
915
|
var TAG_BYTES = 16;
|
|
@@ -867,7 +954,7 @@ var DefaultSecretVault = class {
|
|
|
867
954
|
loadOrCreateKey() {
|
|
868
955
|
if (this.key) return this.key;
|
|
869
956
|
try {
|
|
870
|
-
const buf =
|
|
957
|
+
const buf = fs2.readFileSync(this.keyFile);
|
|
871
958
|
if (buf.length !== KEY_BYTES) {
|
|
872
959
|
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
873
960
|
}
|
|
@@ -876,13 +963,13 @@ var DefaultSecretVault = class {
|
|
|
876
963
|
} catch (err) {
|
|
877
964
|
if (err.code !== "ENOENT") throw err;
|
|
878
965
|
}
|
|
879
|
-
|
|
966
|
+
fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
|
|
880
967
|
const key = randomBytes(KEY_BYTES);
|
|
881
968
|
try {
|
|
882
|
-
|
|
969
|
+
fs2.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
883
970
|
} catch (err) {
|
|
884
971
|
if (err.code !== "EEXIST") throw err;
|
|
885
|
-
const buf =
|
|
972
|
+
const buf = fs2.readFileSync(this.keyFile);
|
|
886
973
|
if (buf.length !== KEY_BYTES) {
|
|
887
974
|
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
888
975
|
}
|
|
@@ -944,7 +1031,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
944
1031
|
const merged = deepMerge(current, patch ?? {});
|
|
945
1032
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
946
1033
|
await fsp2.mkdir(path6.dirname(configPath), { recursive: true });
|
|
947
|
-
await
|
|
1034
|
+
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
948
1035
|
try {
|
|
949
1036
|
await fsp2.chmod(configPath, 384);
|
|
950
1037
|
} catch {
|
|
@@ -966,7 +1053,7 @@ async function migratePlaintextSecrets(configPath, vault) {
|
|
|
966
1053
|
const counter = { n: 0 };
|
|
967
1054
|
const migrated = walkCount(parsed, vault, counter);
|
|
968
1055
|
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
969
|
-
await
|
|
1056
|
+
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
970
1057
|
try {
|
|
971
1058
|
await fsp2.chmod(configPath, 384);
|
|
972
1059
|
} catch {
|
|
@@ -1076,7 +1163,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1076
1163
|
this.pretty = opts.pretty ?? true;
|
|
1077
1164
|
if (this.file) {
|
|
1078
1165
|
try {
|
|
1079
|
-
|
|
1166
|
+
fs2.mkdirSync(path6.dirname(this.file), { recursive: true });
|
|
1080
1167
|
} catch {
|
|
1081
1168
|
}
|
|
1082
1169
|
}
|
|
@@ -1115,7 +1202,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1115
1202
|
}
|
|
1116
1203
|
if (this.file) {
|
|
1117
1204
|
try {
|
|
1118
|
-
|
|
1205
|
+
fs2.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
1119
1206
|
`);
|
|
1120
1207
|
} catch {
|
|
1121
1208
|
}
|
|
@@ -1597,7 +1684,7 @@ var DefaultPathResolver = class {
|
|
|
1597
1684
|
while (dir !== root) {
|
|
1598
1685
|
for (const marker of PROJECT_MARKERS) {
|
|
1599
1686
|
try {
|
|
1600
|
-
|
|
1687
|
+
fs2.accessSync(path6.join(dir, marker));
|
|
1601
1688
|
return dir;
|
|
1602
1689
|
} catch {
|
|
1603
1690
|
}
|
|
@@ -1612,7 +1699,7 @@ var DefaultPathResolver = class {
|
|
|
1612
1699
|
const abs = path6.isAbsolute(input) ? input : path6.resolve(this.cwd, input);
|
|
1613
1700
|
let real;
|
|
1614
1701
|
try {
|
|
1615
|
-
real =
|
|
1702
|
+
real = fs2.realpathSync(abs);
|
|
1616
1703
|
} catch {
|
|
1617
1704
|
real = path6.normalize(abs);
|
|
1618
1705
|
}
|
|
@@ -1666,7 +1753,7 @@ function buildRecoveryStrategies(opts) {
|
|
|
1666
1753
|
async attempt(err) {
|
|
1667
1754
|
if (!(err instanceof ProviderError) || err.status !== 429) return null;
|
|
1668
1755
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
1669
|
-
const delay = Math.
|
|
1756
|
+
const delay = Math.min(6e4, Math.max(1e3, delayMs));
|
|
1670
1757
|
await new Promise((r) => setTimeout(r, delay));
|
|
1671
1758
|
return { action: "retry", reason: "rate_limit_backoff" };
|
|
1672
1759
|
}
|
|
@@ -1819,15 +1906,15 @@ var PATTERNS = [
|
|
|
1819
1906
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
1820
1907
|
{
|
|
1821
1908
|
type: "bearer_token",
|
|
1822
|
-
//
|
|
1823
|
-
//
|
|
1824
|
-
regex: /(
|
|
1909
|
+
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
1910
|
+
// backtracking risk on adversarial input. Bounded at 512 chars.
|
|
1911
|
+
regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,512}=*(?:$|[^A-Za-z0-9_.~+/-])/g
|
|
1825
1912
|
},
|
|
1826
1913
|
{
|
|
1827
1914
|
type: "high_entropy_env",
|
|
1828
|
-
//
|
|
1829
|
-
//
|
|
1830
|
-
regex:
|
|
1915
|
+
// Anchored with alternation instead of lookbehind to avoid backtracking.
|
|
1916
|
+
// Value bounded at 512 chars.
|
|
1917
|
+
regex: /(?:^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?:\s|$)/g
|
|
1831
1918
|
}
|
|
1832
1919
|
];
|
|
1833
1920
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
@@ -1879,72 +1966,9 @@ var DefaultSecretScrubber = class {
|
|
|
1879
1966
|
return visit(obj);
|
|
1880
1967
|
}
|
|
1881
1968
|
};
|
|
1882
|
-
async function atomicWrite(targetPath, content, opts = {}) {
|
|
1883
|
-
const dir = path6.dirname(targetPath);
|
|
1884
|
-
await fsp2.mkdir(dir, { recursive: true });
|
|
1885
|
-
const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
1886
|
-
try {
|
|
1887
|
-
if (typeof content === "string") {
|
|
1888
|
-
await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
1889
|
-
} else {
|
|
1890
|
-
await fsp2.writeFile(tmp, content, { flag: "wx" });
|
|
1891
|
-
}
|
|
1892
|
-
try {
|
|
1893
|
-
const fh = await fsp2.open(tmp, "r+");
|
|
1894
|
-
try {
|
|
1895
|
-
await fh.sync();
|
|
1896
|
-
} finally {
|
|
1897
|
-
await fh.close();
|
|
1898
|
-
}
|
|
1899
|
-
} catch {
|
|
1900
|
-
}
|
|
1901
|
-
let mode;
|
|
1902
|
-
try {
|
|
1903
|
-
const stat8 = await fsp2.stat(targetPath);
|
|
1904
|
-
mode = stat8.mode & 511;
|
|
1905
|
-
} catch {
|
|
1906
|
-
mode = opts.mode;
|
|
1907
|
-
}
|
|
1908
|
-
if (mode !== void 0) {
|
|
1909
|
-
await fsp2.chmod(tmp, mode);
|
|
1910
|
-
}
|
|
1911
|
-
await renameWithRetry(tmp, targetPath);
|
|
1912
|
-
} catch (err) {
|
|
1913
|
-
try {
|
|
1914
|
-
await fsp2.unlink(tmp);
|
|
1915
|
-
} catch {
|
|
1916
|
-
}
|
|
1917
|
-
throw err;
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
async function ensureDir(dir) {
|
|
1921
|
-
await fsp2.mkdir(dir, { recursive: true });
|
|
1922
|
-
}
|
|
1923
|
-
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
1924
|
-
async function renameWithRetry(from, to) {
|
|
1925
|
-
if (process.platform !== "win32") {
|
|
1926
|
-
await fsp2.rename(from, to);
|
|
1927
|
-
return;
|
|
1928
|
-
}
|
|
1929
|
-
const delays = [10, 25, 60, 120, 250];
|
|
1930
|
-
let lastErr;
|
|
1931
|
-
for (let i = 0; i <= delays.length; i++) {
|
|
1932
|
-
try {
|
|
1933
|
-
await fsp2.rename(from, to);
|
|
1934
|
-
return;
|
|
1935
|
-
} catch (err) {
|
|
1936
|
-
lastErr = err;
|
|
1937
|
-
const code = err?.code;
|
|
1938
|
-
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
1939
|
-
throw err;
|
|
1940
|
-
}
|
|
1941
|
-
await new Promise((resolve5) => setTimeout(resolve5, delays[i]));
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
throw lastErr;
|
|
1945
|
-
}
|
|
1946
1969
|
|
|
1947
1970
|
// src/models/models-registry.ts
|
|
1971
|
+
init_atomic_write();
|
|
1948
1972
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
1949
1973
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
1950
1974
|
var FAMILY_BY_NPM = {
|
|
@@ -2910,13 +2934,17 @@ var ToolExecutor = class {
|
|
|
2910
2934
|
const ctrl = new AbortController();
|
|
2911
2935
|
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
2912
2936
|
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
2937
|
+
let cleanupCalled = false;
|
|
2938
|
+
let caught = false;
|
|
2913
2939
|
try {
|
|
2914
2940
|
if (typeof tool.executeStream === "function") {
|
|
2915
2941
|
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
2916
2942
|
}
|
|
2917
2943
|
return await tool.execute(input, ctx, { signal: combined });
|
|
2918
2944
|
} catch (err) {
|
|
2945
|
+
caught = true;
|
|
2919
2946
|
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
2947
|
+
cleanupCalled = true;
|
|
2920
2948
|
try {
|
|
2921
2949
|
await tool.cleanup(input, ctx);
|
|
2922
2950
|
} catch {
|
|
@@ -2925,6 +2953,16 @@ var ToolExecutor = class {
|
|
|
2925
2953
|
throw err;
|
|
2926
2954
|
} finally {
|
|
2927
2955
|
clearTimeout(timer);
|
|
2956
|
+
if (combined.aborted && !caught) {
|
|
2957
|
+
if (!cleanupCalled && typeof tool.cleanup === "function") {
|
|
2958
|
+
try {
|
|
2959
|
+
await tool.cleanup(input, ctx);
|
|
2960
|
+
} catch {
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
|
|
2964
|
+
throw reason;
|
|
2965
|
+
}
|
|
2928
2966
|
}
|
|
2929
2967
|
}
|
|
2930
2968
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -2982,7 +3020,8 @@ var ToolExecutor = class {
|
|
|
2982
3020
|
if (subjectKey) {
|
|
2983
3021
|
const v = obj[subjectKey];
|
|
2984
3022
|
if (typeof v === "string") {
|
|
2985
|
-
|
|
3023
|
+
const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
3024
|
+
return isPathKey ? normalizePath(v) : escapeGlob(v);
|
|
2986
3025
|
}
|
|
2987
3026
|
}
|
|
2988
3027
|
if (toolName === "bash" && typeof obj.command === "string") {
|
|
@@ -3297,6 +3336,9 @@ function renderPlainText(meta, events) {
|
|
|
3297
3336
|
return lines.join("\n");
|
|
3298
3337
|
}
|
|
3299
3338
|
|
|
3339
|
+
// src/utils/index.ts
|
|
3340
|
+
init_atomic_write();
|
|
3341
|
+
|
|
3300
3342
|
// src/utils/safe-json.ts
|
|
3301
3343
|
function safeParse(input, maxBytes = 5e6) {
|
|
3302
3344
|
if (input.length > maxBytes) {
|
|
@@ -3725,6 +3767,11 @@ function looksSecret(name) {
|
|
|
3725
3767
|
function buildChildEnv(optsOrSessionId) {
|
|
3726
3768
|
const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
|
|
3727
3769
|
const passthrough = process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
|
|
3770
|
+
if (passthrough && !process.env["CI"]) {
|
|
3771
|
+
console.warn(
|
|
3772
|
+
"[WrongStack] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
|
|
3773
|
+
);
|
|
3774
|
+
}
|
|
3728
3775
|
const out = {};
|
|
3729
3776
|
for (const [k, v] of Object.entries(process.env)) {
|
|
3730
3777
|
if (v === void 0) continue;
|
|
@@ -3755,11 +3802,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
3755
3802
|
walk2(value, schema, "", errors);
|
|
3756
3803
|
return { ok: errors.length === 0, errors };
|
|
3757
3804
|
}
|
|
3758
|
-
function walk2(value, schema,
|
|
3805
|
+
function walk2(value, schema, path26, errors) {
|
|
3759
3806
|
if (schema.enum !== void 0) {
|
|
3760
3807
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
3761
3808
|
errors.push({
|
|
3762
|
-
path:
|
|
3809
|
+
path: path26 || "<root>",
|
|
3763
3810
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
3764
3811
|
});
|
|
3765
3812
|
return;
|
|
@@ -3768,7 +3815,7 @@ function walk2(value, schema, path25, errors) {
|
|
|
3768
3815
|
if (typeof schema.type === "string") {
|
|
3769
3816
|
if (!checkType(value, schema.type)) {
|
|
3770
3817
|
errors.push({
|
|
3771
|
-
path:
|
|
3818
|
+
path: path26 || "<root>",
|
|
3772
3819
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
3773
3820
|
});
|
|
3774
3821
|
return;
|
|
@@ -3778,19 +3825,19 @@ function walk2(value, schema, path25, errors) {
|
|
|
3778
3825
|
const obj = value;
|
|
3779
3826
|
for (const req of schema.required ?? []) {
|
|
3780
3827
|
if (!(req in obj)) {
|
|
3781
|
-
errors.push({ path: joinPath(
|
|
3828
|
+
errors.push({ path: joinPath(path26, req), message: "required property missing" });
|
|
3782
3829
|
}
|
|
3783
3830
|
}
|
|
3784
3831
|
if (schema.properties) {
|
|
3785
3832
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
3786
3833
|
if (key in obj) {
|
|
3787
|
-
walk2(obj[key], subSchema, joinPath(
|
|
3834
|
+
walk2(obj[key], subSchema, joinPath(path26, key), errors);
|
|
3788
3835
|
}
|
|
3789
3836
|
}
|
|
3790
3837
|
}
|
|
3791
3838
|
}
|
|
3792
3839
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
3793
|
-
value.forEach((item, i) => walk2(item, schema.items, `${
|
|
3840
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path26}[${i}]`, errors));
|
|
3794
3841
|
}
|
|
3795
3842
|
}
|
|
3796
3843
|
function checkType(value, type) {
|
|
@@ -3842,6 +3889,9 @@ function deepEqual(a, b) {
|
|
|
3842
3889
|
}
|
|
3843
3890
|
return false;
|
|
3844
3891
|
}
|
|
3892
|
+
|
|
3893
|
+
// src/storage/session-store.ts
|
|
3894
|
+
init_atomic_write();
|
|
3845
3895
|
var DefaultSessionStore = class {
|
|
3846
3896
|
dir;
|
|
3847
3897
|
events;
|
|
@@ -3885,19 +3935,25 @@ var DefaultSessionStore = class {
|
|
|
3885
3935
|
{ cause: err }
|
|
3886
3936
|
);
|
|
3887
3937
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
handle,
|
|
3891
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
3892
|
-
{
|
|
3938
|
+
try {
|
|
3939
|
+
const writer = new FileSessionWriter(
|
|
3893
3940
|
id,
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3941
|
+
handle,
|
|
3942
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
3943
|
+
{
|
|
3944
|
+
id,
|
|
3945
|
+
model: data.metadata.model,
|
|
3946
|
+
provider: data.metadata.provider
|
|
3947
|
+
},
|
|
3948
|
+
this.events,
|
|
3949
|
+
{ resumed: true, dir: this.dir, filePath: file }
|
|
3950
|
+
);
|
|
3951
|
+
return { writer, data };
|
|
3952
|
+
} catch (err) {
|
|
3953
|
+
await handle.close().catch(() => {
|
|
3954
|
+
});
|
|
3955
|
+
throw err;
|
|
3956
|
+
}
|
|
3901
3957
|
}
|
|
3902
3958
|
async load(id) {
|
|
3903
3959
|
const file = path6.join(this.dir, `${id}.jsonl`);
|
|
@@ -3943,7 +3999,7 @@ var DefaultSessionStore = class {
|
|
|
3943
3999
|
const full = path6.join(this.dir, `${id}.jsonl`);
|
|
3944
4000
|
const stat8 = await fsp2.stat(full);
|
|
3945
4001
|
const summary = await this.summarize(id, stat8.mtime.toISOString());
|
|
3946
|
-
await
|
|
4002
|
+
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
3947
4003
|
console.warn(
|
|
3948
4004
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
3949
4005
|
err instanceof Error ? err.message : String(err)
|
|
@@ -4171,7 +4227,7 @@ var FileSessionWriter = class {
|
|
|
4171
4227
|
this.closed = true;
|
|
4172
4228
|
if (this.manifestFile) {
|
|
4173
4229
|
try {
|
|
4174
|
-
await
|
|
4230
|
+
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
4175
4231
|
} catch {
|
|
4176
4232
|
}
|
|
4177
4233
|
}
|
|
@@ -4267,6 +4323,9 @@ function userInputTitle(content) {
|
|
|
4267
4323
|
const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
4268
4324
|
return (text || "(non-text input)").slice(0, 60);
|
|
4269
4325
|
}
|
|
4326
|
+
|
|
4327
|
+
// src/storage/queue-store.ts
|
|
4328
|
+
init_atomic_write();
|
|
4270
4329
|
var QueueStore = class {
|
|
4271
4330
|
file;
|
|
4272
4331
|
constructor(opts) {
|
|
@@ -4317,6 +4376,9 @@ function isPersistedQueueItem(v) {
|
|
|
4317
4376
|
const o = v;
|
|
4318
4377
|
return typeof o["displayText"] === "string" && Array.isArray(o["blocks"]);
|
|
4319
4378
|
}
|
|
4379
|
+
|
|
4380
|
+
// src/storage/attachment-store.ts
|
|
4381
|
+
init_atomic_write();
|
|
4320
4382
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
4321
4383
|
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
|
|
4322
4384
|
var DefaultAttachmentStore = class {
|
|
@@ -4338,7 +4400,9 @@ var DefaultAttachmentStore = class {
|
|
|
4338
4400
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
4339
4401
|
await fsp2.mkdir(this.spoolDir, { recursive: true });
|
|
4340
4402
|
spooledPath = path6.join(this.spoolDir, `${id}.bin`);
|
|
4341
|
-
await
|
|
4403
|
+
await atomicWrite(spooledPath, input.data, {
|
|
4404
|
+
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
4405
|
+
});
|
|
4342
4406
|
data = void 0;
|
|
4343
4407
|
}
|
|
4344
4408
|
const att = {
|
|
@@ -4437,6 +4501,9 @@ function mergeAdjacentText(blocks) {
|
|
|
4437
4501
|
}
|
|
4438
4502
|
return out;
|
|
4439
4503
|
}
|
|
4504
|
+
|
|
4505
|
+
// src/storage/memory-store.ts
|
|
4506
|
+
init_atomic_write();
|
|
4440
4507
|
var MAX_BYTES_TOTAL = 32e3;
|
|
4441
4508
|
var DefaultMemoryStore = class {
|
|
4442
4509
|
files;
|
|
@@ -4449,12 +4516,13 @@ var DefaultMemoryStore = class {
|
|
|
4449
4516
|
* issue order. Different scopes still proceed in parallel.
|
|
4450
4517
|
*
|
|
4451
4518
|
* The chain tracks only the last pending write. If a write fails, its
|
|
4452
|
-
* error is caught and swallowed
|
|
4453
|
-
*
|
|
4454
|
-
*
|
|
4455
|
-
* backup whose worst case is losing a memory consolidation pass.
|
|
4519
|
+
* error is caught and swallowed so the chain stays alive for subsequent
|
|
4520
|
+
* calls. The error is stored in `writeErrors` so callers can learn about
|
|
4521
|
+
* it on the next read operation.
|
|
4456
4522
|
*/
|
|
4457
4523
|
writeChain = /* @__PURE__ */ new Map();
|
|
4524
|
+
/** Last write error per scope — surfaced as warnings on the next readAll(). */
|
|
4525
|
+
writeErrors = /* @__PURE__ */ new Map();
|
|
4458
4526
|
constructor(opts) {
|
|
4459
4527
|
this.files = {
|
|
4460
4528
|
"project-agents": opts.paths.inProjectAgentsFile,
|
|
@@ -4464,10 +4532,16 @@ var DefaultMemoryStore = class {
|
|
|
4464
4532
|
}
|
|
4465
4533
|
async runSerialized(scope, work) {
|
|
4466
4534
|
const prior = this.writeChain.get(scope) ?? Promise.resolve();
|
|
4535
|
+
prior.catch((err) => {
|
|
4536
|
+
this.writeErrors.set(scope, err);
|
|
4537
|
+
});
|
|
4467
4538
|
const next = prior.catch(() => void 0).then(work);
|
|
4468
4539
|
this.writeChain.set(scope, next);
|
|
4469
4540
|
try {
|
|
4470
4541
|
return await next;
|
|
4542
|
+
} catch (err) {
|
|
4543
|
+
this.writeErrors.set(scope, err);
|
|
4544
|
+
throw err;
|
|
4471
4545
|
} finally {
|
|
4472
4546
|
if (this.writeChain.get(scope) === next) {
|
|
4473
4547
|
this.writeChain.delete(scope);
|
|
@@ -4477,6 +4551,10 @@ var DefaultMemoryStore = class {
|
|
|
4477
4551
|
async readAll() {
|
|
4478
4552
|
const parts = [];
|
|
4479
4553
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
4554
|
+
const writeErr = this.writeErrors.get(scope);
|
|
4555
|
+
if (writeErr) {
|
|
4556
|
+
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
|
|
4557
|
+
}
|
|
4480
4558
|
const body = await this.read(scope);
|
|
4481
4559
|
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
4482
4560
|
|
|
@@ -4969,6 +5047,9 @@ function runConfigMigrations(input, targetVersion, migrations) {
|
|
|
4969
5047
|
return { config: current, applied, shouldPersist };
|
|
4970
5048
|
}
|
|
4971
5049
|
var DEFAULT_CONFIG_MIGRATIONS = [];
|
|
5050
|
+
|
|
5051
|
+
// src/storage/recovery-lock.ts
|
|
5052
|
+
init_atomic_write();
|
|
4972
5053
|
var LOCK_FILE = "active.json";
|
|
4973
5054
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
4974
5055
|
var RecoveryLock = class {
|
|
@@ -5189,6 +5270,9 @@ var SessionAnalyzer = class {
|
|
|
5189
5270
|
return last - first;
|
|
5190
5271
|
}
|
|
5191
5272
|
};
|
|
5273
|
+
|
|
5274
|
+
// src/storage/todos-checkpoint.ts
|
|
5275
|
+
init_atomic_write();
|
|
5192
5276
|
async function loadTodosCheckpoint(filePath) {
|
|
5193
5277
|
let raw;
|
|
5194
5278
|
try {
|
|
@@ -5257,6 +5341,9 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
|
5257
5341
|
}
|
|
5258
5342
|
};
|
|
5259
5343
|
}
|
|
5344
|
+
|
|
5345
|
+
// src/storage/plan-store.ts
|
|
5346
|
+
init_atomic_write();
|
|
5260
5347
|
async function loadPlan(filePath) {
|
|
5261
5348
|
let raw;
|
|
5262
5349
|
try {
|
|
@@ -5491,6 +5578,9 @@ ${cat}:`);
|
|
|
5491
5578
|
}
|
|
5492
5579
|
return lines.join("\n");
|
|
5493
5580
|
}
|
|
5581
|
+
|
|
5582
|
+
// src/storage/director-state.ts
|
|
5583
|
+
init_atomic_write();
|
|
5494
5584
|
async function loadDirectorState(filePath) {
|
|
5495
5585
|
let raw;
|
|
5496
5586
|
try {
|
|
@@ -5669,6 +5759,9 @@ var DirectorStateCheckpoint = class {
|
|
|
5669
5759
|
}
|
|
5670
5760
|
}
|
|
5671
5761
|
};
|
|
5762
|
+
|
|
5763
|
+
// src/security/permission-policy.ts
|
|
5764
|
+
init_atomic_write();
|
|
5672
5765
|
var DefaultPermissionPolicy = class {
|
|
5673
5766
|
policy = {};
|
|
5674
5767
|
loaded = false;
|
|
@@ -6199,17 +6292,23 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
6199
6292
|
handleTextDelta(state, ev.text);
|
|
6200
6293
|
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
6201
6294
|
break;
|
|
6202
|
-
case "tool_use_start":
|
|
6203
|
-
|
|
6204
|
-
|
|
6295
|
+
case "tool_use_start": {
|
|
6296
|
+
const idVal = ev.id;
|
|
6297
|
+
const nameVal = ev.name;
|
|
6298
|
+
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
6299
|
+
const emittedPayload = { ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
6300
|
+
events.emit("provider.tool_use_start", emittedPayload);
|
|
6205
6301
|
break;
|
|
6302
|
+
}
|
|
6206
6303
|
case "tool_use_input_delta":
|
|
6207
6304
|
handleToolUseInputDelta(state, ev);
|
|
6208
6305
|
break;
|
|
6209
|
-
case "tool_use_stop":
|
|
6306
|
+
case "tool_use_stop": {
|
|
6307
|
+
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
6210
6308
|
handleToolUseStop(state, ev);
|
|
6211
|
-
events.emit("provider.tool_use_stop", { ctx, id: ev.id });
|
|
6309
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
6212
6310
|
break;
|
|
6311
|
+
}
|
|
6213
6312
|
case "thinking_start":
|
|
6214
6313
|
handleThinkingStart(state, ev);
|
|
6215
6314
|
break;
|
|
@@ -7164,31 +7263,794 @@ var AutonomousRunner = class {
|
|
|
7164
7263
|
this.opts.onDone?.(timeoutResult);
|
|
7165
7264
|
return timeoutResult;
|
|
7166
7265
|
}
|
|
7167
|
-
this.stopped = true;
|
|
7168
|
-
const failedResult = {
|
|
7169
|
-
status: "failed",
|
|
7170
|
-
error: toWrongStackError(e),
|
|
7171
|
-
iterations: this.iterations,
|
|
7172
|
-
toolCalls: this.toolCalls,
|
|
7173
|
-
reason: e instanceof Error ? e.message : String(e)
|
|
7266
|
+
this.stopped = true;
|
|
7267
|
+
const failedResult = {
|
|
7268
|
+
status: "failed",
|
|
7269
|
+
error: toWrongStackError(e),
|
|
7270
|
+
iterations: this.iterations,
|
|
7271
|
+
toolCalls: this.toolCalls,
|
|
7272
|
+
reason: e instanceof Error ? e.message : String(e)
|
|
7273
|
+
};
|
|
7274
|
+
this.opts.onDone?.(failedResult);
|
|
7275
|
+
return failedResult;
|
|
7276
|
+
} finally {
|
|
7277
|
+
clearTimeout(timeout);
|
|
7278
|
+
}
|
|
7279
|
+
}
|
|
7280
|
+
return {
|
|
7281
|
+
status: "aborted",
|
|
7282
|
+
iterations: this.iterations,
|
|
7283
|
+
toolCalls: this.toolCalls,
|
|
7284
|
+
reason: "stopped externally"
|
|
7285
|
+
};
|
|
7286
|
+
}
|
|
7287
|
+
stop() {
|
|
7288
|
+
this.stopped = true;
|
|
7289
|
+
}
|
|
7290
|
+
};
|
|
7291
|
+
|
|
7292
|
+
// src/storage/goal-store.ts
|
|
7293
|
+
init_atomic_write();
|
|
7294
|
+
var MAX_JOURNAL_ENTRIES = 500;
|
|
7295
|
+
function goalFilePath(projectRoot) {
|
|
7296
|
+
return path6.join(projectRoot, ".wrongstack", "goal.json");
|
|
7297
|
+
}
|
|
7298
|
+
async function loadGoal(filePath) {
|
|
7299
|
+
let raw;
|
|
7300
|
+
try {
|
|
7301
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
7302
|
+
} catch {
|
|
7303
|
+
return null;
|
|
7304
|
+
}
|
|
7305
|
+
try {
|
|
7306
|
+
const parsed = JSON.parse(raw);
|
|
7307
|
+
if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
|
|
7308
|
+
return null;
|
|
7309
|
+
}
|
|
7310
|
+
return parsed;
|
|
7311
|
+
} catch {
|
|
7312
|
+
return null;
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
async function saveGoal(filePath, goal) {
|
|
7316
|
+
await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
|
|
7317
|
+
}
|
|
7318
|
+
function emptyGoal(goal) {
|
|
7319
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7320
|
+
return {
|
|
7321
|
+
version: 1,
|
|
7322
|
+
goal,
|
|
7323
|
+
setAt: now,
|
|
7324
|
+
lastActivityAt: now,
|
|
7325
|
+
iterations: 0,
|
|
7326
|
+
engineState: "idle",
|
|
7327
|
+
goalState: "active",
|
|
7328
|
+
todoAttempts: {},
|
|
7329
|
+
journal: []
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7332
|
+
function appendJournal(goal, entry) {
|
|
7333
|
+
const iteration = goal.iterations + 1;
|
|
7334
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
7335
|
+
const full = { ...entry, iteration, at };
|
|
7336
|
+
const journal = [...goal.journal, full];
|
|
7337
|
+
const trimmed = journal.length > MAX_JOURNAL_ENTRIES ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES) : journal;
|
|
7338
|
+
return {
|
|
7339
|
+
...goal,
|
|
7340
|
+
iterations: iteration,
|
|
7341
|
+
lastActivityAt: at,
|
|
7342
|
+
journal: trimmed
|
|
7343
|
+
};
|
|
7344
|
+
}
|
|
7345
|
+
function summarizeUsage(goal) {
|
|
7346
|
+
let totalCostUsd = 0;
|
|
7347
|
+
let totalInputTokens = 0;
|
|
7348
|
+
let totalOutputTokens = 0;
|
|
7349
|
+
let iterationsWithUsage = 0;
|
|
7350
|
+
for (const e of goal.journal) {
|
|
7351
|
+
if (typeof e.costUsd === "number") totalCostUsd += e.costUsd;
|
|
7352
|
+
if (e.tokens) {
|
|
7353
|
+
totalInputTokens += e.tokens.input;
|
|
7354
|
+
totalOutputTokens += e.tokens.output;
|
|
7355
|
+
}
|
|
7356
|
+
if (typeof e.costUsd === "number" || e.tokens) iterationsWithUsage++;
|
|
7357
|
+
}
|
|
7358
|
+
return { totalCostUsd, totalInputTokens, totalOutputTokens, iterationsWithUsage };
|
|
7359
|
+
}
|
|
7360
|
+
function formatGoal(goal, journalLimit = 10) {
|
|
7361
|
+
const lines = [];
|
|
7362
|
+
lines.push(`Goal: ${goal.goal}`);
|
|
7363
|
+
lines.push(`Set: ${goal.setAt}`);
|
|
7364
|
+
lines.push(`Last activity: ${goal.lastActivityAt}`);
|
|
7365
|
+
lines.push(`Iterations: ${goal.iterations}`);
|
|
7366
|
+
lines.push(`Mission: ${goal.goalState ?? "active"}`);
|
|
7367
|
+
lines.push(`Engine: ${goal.engineState}`);
|
|
7368
|
+
const usage = summarizeUsage(goal);
|
|
7369
|
+
if (usage.iterationsWithUsage > 0) {
|
|
7370
|
+
lines.push(
|
|
7371
|
+
`Spent: $${usage.totalCostUsd.toFixed(4)} (in ${usage.totalInputTokens} / out ${usage.totalOutputTokens} tokens across ${usage.iterationsWithUsage} iterations)`
|
|
7372
|
+
);
|
|
7373
|
+
}
|
|
7374
|
+
if (goal.journal.length > 0) {
|
|
7375
|
+
lines.push("");
|
|
7376
|
+
lines.push(`Recent journal (last ${Math.min(journalLimit, goal.journal.length)}):`);
|
|
7377
|
+
const tail = goal.journal.slice(-journalLimit);
|
|
7378
|
+
for (const e of tail) {
|
|
7379
|
+
const mark = e.status === "success" ? "\u2713" : e.status === "failure" ? "\u2717" : e.status === "aborted" ? "\u2298" : "\xB7";
|
|
7380
|
+
const note = e.note ? ` \u2014 ${e.note}` : "";
|
|
7381
|
+
const cost = typeof e.costUsd === "number" ? ` ($${e.costUsd.toFixed(4)})` : "";
|
|
7382
|
+
lines.push(` #${e.iteration} ${mark} [${e.source}] ${e.task}${cost}${note}`);
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
return lines.join("\n");
|
|
7386
|
+
}
|
|
7387
|
+
|
|
7388
|
+
// src/execution/eternal-autonomy.ts
|
|
7389
|
+
var execFileP = promisify(execFile);
|
|
7390
|
+
var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
|
|
7391
|
+
var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
7392
|
+
var EternalAutonomyEngine = class {
|
|
7393
|
+
constructor(opts) {
|
|
7394
|
+
this.opts = opts;
|
|
7395
|
+
this.goalPath = goalFilePath(opts.projectRoot);
|
|
7396
|
+
}
|
|
7397
|
+
opts;
|
|
7398
|
+
state = "idle";
|
|
7399
|
+
stopRequested = false;
|
|
7400
|
+
consecutiveFailures = 0;
|
|
7401
|
+
consecutiveBrainstormDone = 0;
|
|
7402
|
+
/**
|
|
7403
|
+
* Count of consecutive transient (recoverable) provider failures. Drives
|
|
7404
|
+
* the exponential backoff between iterations. Reset on the first
|
|
7405
|
+
* successful iteration so a single bad afternoon doesn't permanently
|
|
7406
|
+
* slow the loop down.
|
|
7407
|
+
*/
|
|
7408
|
+
consecutiveTransientRetries = 0;
|
|
7409
|
+
currentCtrl = null;
|
|
7410
|
+
iterationsSinceCompact = 0;
|
|
7411
|
+
goalPath;
|
|
7412
|
+
/** Current engine state — readable for UIs. */
|
|
7413
|
+
get currentState() {
|
|
7414
|
+
return this.state;
|
|
7415
|
+
}
|
|
7416
|
+
/** Synchronously request stop. Resolves once the running iteration aborts. */
|
|
7417
|
+
stop() {
|
|
7418
|
+
this.stopRequested = true;
|
|
7419
|
+
this.currentCtrl?.abort();
|
|
7420
|
+
void this.persistEngineState("stopped").catch(() => {
|
|
7421
|
+
});
|
|
7422
|
+
this.state = "stopped";
|
|
7423
|
+
}
|
|
7424
|
+
/**
|
|
7425
|
+
* Mark the engine as 'running' on disk + reset stop state so a new
|
|
7426
|
+
* batch of `runOneIteration()` calls can proceed. Called by the REPL
|
|
7427
|
+
* when the user invokes `/autonomy eternal`. Idempotent.
|
|
7428
|
+
*/
|
|
7429
|
+
async prime() {
|
|
7430
|
+
this.stopRequested = false;
|
|
7431
|
+
this.state = "running";
|
|
7432
|
+
await this.persistEngineState("running").catch(() => {
|
|
7433
|
+
});
|
|
7434
|
+
}
|
|
7435
|
+
/**
|
|
7436
|
+
* Main loop. Returns when stop() is called or the goal file is removed.
|
|
7437
|
+
* Does NOT throw — every iteration is wrapped to keep the loop alive.
|
|
7438
|
+
*/
|
|
7439
|
+
async run() {
|
|
7440
|
+
this.state = "running";
|
|
7441
|
+
await this.persistEngineState("running");
|
|
7442
|
+
try {
|
|
7443
|
+
while (!this.stopRequested) {
|
|
7444
|
+
let iterationOk = false;
|
|
7445
|
+
try {
|
|
7446
|
+
iterationOk = await this.runOneIteration();
|
|
7447
|
+
} catch (err) {
|
|
7448
|
+
this.consecutiveFailures++;
|
|
7449
|
+
this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
|
|
7450
|
+
await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
|
|
7451
|
+
}
|
|
7452
|
+
if (iterationOk) {
|
|
7453
|
+
this.consecutiveFailures = 0;
|
|
7454
|
+
}
|
|
7455
|
+
if (this.stopRequested) break;
|
|
7456
|
+
await sleep(this.opts.cycleGapMs ?? 1e3);
|
|
7457
|
+
}
|
|
7458
|
+
} finally {
|
|
7459
|
+
this.state = "stopped";
|
|
7460
|
+
await this.persistEngineState("stopped").catch(() => {
|
|
7461
|
+
});
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
/**
|
|
7465
|
+
* Execute a single sense-decide-execute-reflect cycle.
|
|
7466
|
+
* Returns true on success, false on handled failure / no-op.
|
|
7467
|
+
*
|
|
7468
|
+
* Exposed publicly so the REPL can pace iterations from its main loop
|
|
7469
|
+
* — running the engine and the REPL as a single sequential consumer of
|
|
7470
|
+
* `agent.run()` avoids race conditions on the shared Context.
|
|
7471
|
+
*/
|
|
7472
|
+
async runOneIteration() {
|
|
7473
|
+
const goal = await loadGoal(this.goalPath);
|
|
7474
|
+
if (!goal) {
|
|
7475
|
+
this.stopRequested = true;
|
|
7476
|
+
return false;
|
|
7477
|
+
}
|
|
7478
|
+
const missionState = goal.goalState ?? "active";
|
|
7479
|
+
if (missionState !== "active") {
|
|
7480
|
+
this.stopRequested = true;
|
|
7481
|
+
return false;
|
|
7482
|
+
}
|
|
7483
|
+
const action = await this.decide(goal);
|
|
7484
|
+
if (!action) {
|
|
7485
|
+
if (!this.stopRequested) {
|
|
7486
|
+
await sleep(5e3);
|
|
7487
|
+
}
|
|
7488
|
+
return false;
|
|
7489
|
+
}
|
|
7490
|
+
const ctrl = new AbortController();
|
|
7491
|
+
this.currentCtrl = ctrl;
|
|
7492
|
+
const timer = setTimeout(
|
|
7493
|
+
() => ctrl.abort(),
|
|
7494
|
+
this.opts.iterationTimeoutMs ?? 5 * 6e4
|
|
7495
|
+
);
|
|
7496
|
+
let status = "success";
|
|
7497
|
+
let note;
|
|
7498
|
+
let finalText = "";
|
|
7499
|
+
let isTransientFailure = false;
|
|
7500
|
+
const tc = this.opts.agent.ctx?.tokenCounter;
|
|
7501
|
+
const beforeUsage = tc?.total?.();
|
|
7502
|
+
const beforeCost = tc?.estimateCost?.().total;
|
|
7503
|
+
try {
|
|
7504
|
+
const result = await this.opts.agent.run(
|
|
7505
|
+
[{ type: "text", text: action.directive }],
|
|
7506
|
+
{
|
|
7507
|
+
signal: ctrl.signal,
|
|
7508
|
+
// Enable per-call autonomous continuation so the agent can chain
|
|
7509
|
+
// multiple internal tool/response cycles end-to-end on one
|
|
7510
|
+
// directive instead of returning to the engine after a single
|
|
7511
|
+
// round-trip. The model uses `[continue]` / `[done]` markers
|
|
7512
|
+
// (or the `continue_to_next_iteration` tool) to control the
|
|
7513
|
+
// inner loop. Without this flag the engine produced shallow
|
|
7514
|
+
// iterations and almost never let a real task finish.
|
|
7515
|
+
autonomousContinue: true,
|
|
7516
|
+
// Cap the inner loop so a runaway agent.run can't burn through
|
|
7517
|
+
// the iteration timeout — the engine's own outer loop is the
|
|
7518
|
+
// long-running thing, each tick should be bounded.
|
|
7519
|
+
maxIterations: this.opts.iterationMaxAgentSteps ?? 50
|
|
7520
|
+
}
|
|
7521
|
+
);
|
|
7522
|
+
if (result.status === "aborted") {
|
|
7523
|
+
status = "aborted";
|
|
7524
|
+
note = "stopped by user";
|
|
7525
|
+
} else if (result.status === "failed") {
|
|
7526
|
+
status = "failure";
|
|
7527
|
+
note = result.error?.describe?.() ?? "agent run failed";
|
|
7528
|
+
isTransientFailure = result.error?.recoverable === true;
|
|
7529
|
+
} else if (result.status === "max_iterations") {
|
|
7530
|
+
status = "failure";
|
|
7531
|
+
note = `max iterations (${result.iterations})`;
|
|
7532
|
+
} else {
|
|
7533
|
+
status = "success";
|
|
7534
|
+
finalText = result.finalText ?? "";
|
|
7535
|
+
const tail = finalText.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
7536
|
+
if (tail) note = tail;
|
|
7537
|
+
}
|
|
7538
|
+
} catch (err) {
|
|
7539
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
|
|
7540
|
+
status = isAbort ? "aborted" : "failure";
|
|
7541
|
+
note = err instanceof Error ? err.message : String(err);
|
|
7542
|
+
if (!isAbort && typeof err?.recoverable === "boolean") {
|
|
7543
|
+
isTransientFailure = err.recoverable;
|
|
7544
|
+
}
|
|
7545
|
+
} finally {
|
|
7546
|
+
clearTimeout(timer);
|
|
7547
|
+
this.currentCtrl = null;
|
|
7548
|
+
}
|
|
7549
|
+
if (action.source === "todo" && action.todoId && status !== "success") {
|
|
7550
|
+
await this.bumpTodoAttempt(action.todoId);
|
|
7551
|
+
}
|
|
7552
|
+
const afterUsage = tc?.total?.();
|
|
7553
|
+
const afterCost = tc?.estimateCost?.().total;
|
|
7554
|
+
const tokens = beforeUsage && afterUsage ? {
|
|
7555
|
+
input: Math.max(0, afterUsage.input - beforeUsage.input),
|
|
7556
|
+
output: Math.max(0, afterUsage.output - beforeUsage.output)
|
|
7557
|
+
} : void 0;
|
|
7558
|
+
const costUsd = typeof beforeCost === "number" && typeof afterCost === "number" ? Math.max(0, afterCost - beforeCost) : void 0;
|
|
7559
|
+
await this.appendIterationEntry({
|
|
7560
|
+
source: action.source,
|
|
7561
|
+
task: action.task,
|
|
7562
|
+
status,
|
|
7563
|
+
note,
|
|
7564
|
+
tokens,
|
|
7565
|
+
costUsd
|
|
7566
|
+
});
|
|
7567
|
+
let iterationIndex = 0;
|
|
7568
|
+
try {
|
|
7569
|
+
const reloaded = await loadGoal(this.goalPath);
|
|
7570
|
+
iterationIndex = reloaded?.iterations ?? 0;
|
|
7571
|
+
} catch {
|
|
7572
|
+
}
|
|
7573
|
+
this.opts.onIteration?.({
|
|
7574
|
+
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
7575
|
+
iteration: iterationIndex,
|
|
7576
|
+
source: action.source,
|
|
7577
|
+
task: action.task,
|
|
7578
|
+
status,
|
|
7579
|
+
note,
|
|
7580
|
+
tokens,
|
|
7581
|
+
costUsd
|
|
7582
|
+
});
|
|
7583
|
+
if (status === "failure") {
|
|
7584
|
+
if (isTransientFailure) {
|
|
7585
|
+
this.consecutiveTransientRetries++;
|
|
7586
|
+
const delay = this.computeTransientBackoffMs();
|
|
7587
|
+
if (delay > 0) {
|
|
7588
|
+
await this.sleepInterruptible(delay);
|
|
7589
|
+
}
|
|
7590
|
+
return false;
|
|
7591
|
+
}
|
|
7592
|
+
this.consecutiveFailures++;
|
|
7593
|
+
return false;
|
|
7594
|
+
}
|
|
7595
|
+
if (status === "aborted") {
|
|
7596
|
+
if (this.stopRequested) return false;
|
|
7597
|
+
this.consecutiveFailures++;
|
|
7598
|
+
return false;
|
|
7599
|
+
}
|
|
7600
|
+
this.consecutiveTransientRetries = 0;
|
|
7601
|
+
if (GOAL_COMPLETE_MARKER.test(finalText)) {
|
|
7602
|
+
await this.markGoalCompleted(action, finalText);
|
|
7603
|
+
this.stopRequested = true;
|
|
7604
|
+
return true;
|
|
7605
|
+
}
|
|
7606
|
+
this.iterationsSinceCompact++;
|
|
7607
|
+
await this.maybeCompact().catch((err) => {
|
|
7608
|
+
this.opts.onError?.(
|
|
7609
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
7610
|
+
this.consecutiveFailures
|
|
7611
|
+
);
|
|
7612
|
+
});
|
|
7613
|
+
return true;
|
|
7614
|
+
}
|
|
7615
|
+
/**
|
|
7616
|
+
* Run compaction when either trigger fires:
|
|
7617
|
+
* - We've done >= compactEveryNIterations since the last compact.
|
|
7618
|
+
* - Current request tokens exceed aggressiveCompactRatio * maxContext.
|
|
7619
|
+
*
|
|
7620
|
+
* The second check uses *aggressive* mode to free more headroom; the
|
|
7621
|
+
* cadence check uses non-aggressive (cheaper).
|
|
7622
|
+
*/
|
|
7623
|
+
async maybeCompact() {
|
|
7624
|
+
const compactor = this.opts.compactor;
|
|
7625
|
+
if (!compactor) return;
|
|
7626
|
+
const ctx = this.opts.agent.ctx;
|
|
7627
|
+
if (!ctx) return;
|
|
7628
|
+
const cadence = this.opts.compactEveryNIterations ?? 25;
|
|
7629
|
+
const threshold = this.opts.aggressiveCompactRatio ?? 0.85;
|
|
7630
|
+
const maxCtx = this.opts.maxContextTokens;
|
|
7631
|
+
let aggressive = false;
|
|
7632
|
+
let shouldRun = false;
|
|
7633
|
+
if (this.iterationsSinceCompact >= cadence) {
|
|
7634
|
+
shouldRun = true;
|
|
7635
|
+
}
|
|
7636
|
+
if (maxCtx && maxCtx > 0) {
|
|
7637
|
+
const used = ctx.tokenCounter?.currentRequestTokens?.();
|
|
7638
|
+
if (used) {
|
|
7639
|
+
const total = used.input + used.cacheRead;
|
|
7640
|
+
if (total / maxCtx >= threshold) {
|
|
7641
|
+
shouldRun = true;
|
|
7642
|
+
aggressive = true;
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
if (!shouldRun) return;
|
|
7647
|
+
const report = await compactor.compact(ctx, { aggressive });
|
|
7648
|
+
this.iterationsSinceCompact = 0;
|
|
7649
|
+
const saved = report.before - report.after;
|
|
7650
|
+
await this.appendIterationEntry({
|
|
7651
|
+
source: "manual",
|
|
7652
|
+
task: `compaction (${aggressive ? "aggressive" : "cadence"})`,
|
|
7653
|
+
status: "success",
|
|
7654
|
+
note: `saved ~${saved} tokens (${report.before}\u2192${report.after})`
|
|
7655
|
+
});
|
|
7656
|
+
}
|
|
7657
|
+
/**
|
|
7658
|
+
* Hybrid idea source.
|
|
7659
|
+
* 1. Pending todos on the agent's context.
|
|
7660
|
+
* 2. Dirty git working tree → propose a "review and finish this" task.
|
|
7661
|
+
* 3. Otherwise: brainstorm via the LLM against the goal.
|
|
7662
|
+
*
|
|
7663
|
+
* After failureBudget consecutive failures, force brainstorm so the
|
|
7664
|
+
* engine doesn't loop on the same broken todo or stuck git state.
|
|
7665
|
+
*/
|
|
7666
|
+
async decide(goal) {
|
|
7667
|
+
const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
|
|
7668
|
+
if (!forceBrainstorm) {
|
|
7669
|
+
const todo = this.pickPendingTodo(goal);
|
|
7670
|
+
if (todo) {
|
|
7671
|
+
return {
|
|
7672
|
+
source: "todo",
|
|
7673
|
+
task: todo.content,
|
|
7674
|
+
todoId: todo.id,
|
|
7675
|
+
directive: this.buildDirective(goal, "todo", todo.content)
|
|
7174
7676
|
};
|
|
7175
|
-
this.opts.onDone?.(failedResult);
|
|
7176
|
-
return failedResult;
|
|
7177
|
-
} finally {
|
|
7178
|
-
clearTimeout(timeout);
|
|
7179
7677
|
}
|
|
7678
|
+
const gitTask = await this.pickGitTask();
|
|
7679
|
+
if (gitTask) {
|
|
7680
|
+
return {
|
|
7681
|
+
source: "git",
|
|
7682
|
+
task: gitTask,
|
|
7683
|
+
directive: this.buildDirective(goal, "git", gitTask)
|
|
7684
|
+
};
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
const brainstormed = await this.brainstormTask(goal);
|
|
7688
|
+
if (brainstormed === BRAINSTORM_DONE) {
|
|
7689
|
+
this.consecutiveBrainstormDone++;
|
|
7690
|
+
const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
|
|
7691
|
+
if (this.consecutiveBrainstormDone >= threshold) {
|
|
7692
|
+
await this.markGoalCompleted(
|
|
7693
|
+
{ source: "brainstorm", task: "no further work", directive: "" },
|
|
7694
|
+
`brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
|
|
7695
|
+
);
|
|
7696
|
+
this.stopRequested = true;
|
|
7697
|
+
}
|
|
7698
|
+
return null;
|
|
7180
7699
|
}
|
|
7700
|
+
if (!brainstormed) return null;
|
|
7701
|
+
this.consecutiveBrainstormDone = 0;
|
|
7181
7702
|
return {
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
reason: "stopped externally"
|
|
7703
|
+
source: "brainstorm",
|
|
7704
|
+
task: brainstormed,
|
|
7705
|
+
directive: this.buildDirective(goal, "brainstorm", brainstormed)
|
|
7186
7706
|
};
|
|
7187
7707
|
}
|
|
7188
|
-
|
|
7189
|
-
|
|
7708
|
+
pickPendingTodo(goal) {
|
|
7709
|
+
const todos = this.opts.agent.ctx.todos;
|
|
7710
|
+
if (!Array.isArray(todos)) return null;
|
|
7711
|
+
const attempts = goal.todoAttempts ?? {};
|
|
7712
|
+
const ceiling = this.opts.todoMaxAttempts ?? 3;
|
|
7713
|
+
for (const t2 of todos) {
|
|
7714
|
+
if (t2.status !== "pending") continue;
|
|
7715
|
+
const used = attempts[t2.id] ?? 0;
|
|
7716
|
+
if (used >= ceiling) continue;
|
|
7717
|
+
return t2;
|
|
7718
|
+
}
|
|
7719
|
+
return null;
|
|
7720
|
+
}
|
|
7721
|
+
async pickGitTask() {
|
|
7722
|
+
let out;
|
|
7723
|
+
try {
|
|
7724
|
+
out = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
|
|
7725
|
+
} catch {
|
|
7726
|
+
return null;
|
|
7727
|
+
}
|
|
7728
|
+
const dirty = out.trim();
|
|
7729
|
+
if (!dirty) return null;
|
|
7730
|
+
const lines = dirty.split("\n").slice(0, 8);
|
|
7731
|
+
const preview = lines.join(", ");
|
|
7732
|
+
return `Inspect the dirty working tree and either finish the in-progress work or revert it. Files: ${preview}`;
|
|
7733
|
+
}
|
|
7734
|
+
async readGitStatus() {
|
|
7735
|
+
const { stdout } = await execFileP("git", ["status", "--porcelain"], {
|
|
7736
|
+
cwd: this.opts.projectRoot,
|
|
7737
|
+
timeout: 5e3
|
|
7738
|
+
});
|
|
7739
|
+
return stdout;
|
|
7740
|
+
}
|
|
7741
|
+
async brainstormTask(goal) {
|
|
7742
|
+
const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
|
|
7743
|
+
const directive = [
|
|
7744
|
+
"You are deciding the next action in an autonomous loop pursuing a long-running goal.",
|
|
7745
|
+
"",
|
|
7746
|
+
`Goal: ${goal.goal}`,
|
|
7747
|
+
"",
|
|
7748
|
+
lastFew ? `Recent iterations:
|
|
7749
|
+
${lastFew}` : "No prior iterations yet.",
|
|
7750
|
+
"",
|
|
7751
|
+
"Output ONE concrete, immediately-actionable task that advances the goal.",
|
|
7752
|
+
"Constraints:",
|
|
7753
|
+
"- One sentence, imperative form, under 200 chars.",
|
|
7754
|
+
"- No preamble, no explanation, no markdown \u2014 just the task line.",
|
|
7755
|
+
"- If recent iterations show repeated failures on the same target, pivot.",
|
|
7756
|
+
"- If the goal appears fully accomplished AND you can name a concrete",
|
|
7757
|
+
" artifact / test / output that proves it, output exactly: DONE",
|
|
7758
|
+
"- Be conservative with DONE: if the recent journal contains failures",
|
|
7759
|
+
" or aborted entries, the goal is almost certainly NOT done."
|
|
7760
|
+
].join("\n");
|
|
7761
|
+
try {
|
|
7762
|
+
const ctrl = new AbortController();
|
|
7763
|
+
const timer = setTimeout(() => ctrl.abort(), 6e4);
|
|
7764
|
+
try {
|
|
7765
|
+
const result = await this.opts.agent.run(
|
|
7766
|
+
[{ type: "text", text: directive }],
|
|
7767
|
+
{ signal: ctrl.signal, maxIterations: 1 }
|
|
7768
|
+
);
|
|
7769
|
+
if (result.status !== "done") return null;
|
|
7770
|
+
const text = (result.finalText ?? "").trim();
|
|
7771
|
+
if (!text) return null;
|
|
7772
|
+
if (/^DONE\.?$/i.test(text)) return BRAINSTORM_DONE;
|
|
7773
|
+
const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
|
|
7774
|
+
if (!firstLine) return null;
|
|
7775
|
+
if (/^DONE\.?$/i.test(firstLine)) return BRAINSTORM_DONE;
|
|
7776
|
+
return firstLine.slice(0, 240);
|
|
7777
|
+
} finally {
|
|
7778
|
+
clearTimeout(timer);
|
|
7779
|
+
}
|
|
7780
|
+
} catch {
|
|
7781
|
+
return null;
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
buildDirective(goal, source, task) {
|
|
7785
|
+
const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
|
|
7786
|
+
return [
|
|
7787
|
+
"\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 iteration directive \u2550\u2550\u2550",
|
|
7788
|
+
"",
|
|
7789
|
+
`Mission: ${goal.goal}`,
|
|
7790
|
+
`Iteration: #${goal.iterations + 1}`,
|
|
7791
|
+
`Source: ${source}`,
|
|
7792
|
+
`Task: ${task}`,
|
|
7793
|
+
"",
|
|
7794
|
+
recentJournal ? `Recent journal (last 5):
|
|
7795
|
+
${recentJournal}` : "No prior iterations.",
|
|
7796
|
+
"",
|
|
7797
|
+
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
7798
|
+
"You are inside a long-running autonomous loop. Each iteration you",
|
|
7799
|
+
"execute ONE concrete task that advances the Mission. No user is",
|
|
7800
|
+
"available to clarify \u2014 make defensible decisions and move forward.",
|
|
7801
|
+
"",
|
|
7802
|
+
"1. EXECUTE END-TO-END",
|
|
7803
|
+
" \u2022 Use multiple tool calls freely. Emit `[continue]` on its own line",
|
|
7804
|
+
" to chain to the next internal step without returning.",
|
|
7805
|
+
" \u2022 When this iteration's Task is finished (real artifact / passing",
|
|
7806
|
+
" test / applied diff / clean output), emit `[done]` on its own line.",
|
|
7807
|
+
" \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
|
|
7808
|
+
" approaches before giving up. YOLO is active; no confirmations.",
|
|
7809
|
+
"",
|
|
7810
|
+
"2. UPDATE TODO STATE (when Source is `todo`)",
|
|
7811
|
+
" \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
|
|
7812
|
+
" \u2022 Mark it `completed` on success, with a one-line outcome note.",
|
|
7813
|
+
" \u2022 If you cannot make progress after 2 distinct attempts, mark it",
|
|
7814
|
+
" `cancelled` with the obstacle. The loop will skip it next time.",
|
|
7815
|
+
"",
|
|
7816
|
+
"3. MISSION-COMPLETE PROTOCOL",
|
|
7817
|
+
" \u2022 If \u2014 and ONLY if \u2014 the OVERALL Mission (not just this Task) is",
|
|
7818
|
+
" verifiably accomplished, emit on its own line:",
|
|
7819
|
+
" [GOAL_COMPLETE]",
|
|
7820
|
+
" followed by a one-paragraph verification recipe (artifact path,",
|
|
7821
|
+
" test command, or 10-second reproduction). This halts the loop.",
|
|
7822
|
+
" \u2022 NEVER emit [GOAL_COMPLETE] on optimism, partial progress, or",
|
|
7823
|
+
' "looks fine". Required: a concrete artifact that proves it AND',
|
|
7824
|
+
" no recent journal failures contradicting completion.",
|
|
7825
|
+
" \u2022 If unsure, emit `[done]` instead and let the next iteration",
|
|
7826
|
+
" decide. The loop is patient; false completion is not.",
|
|
7827
|
+
"",
|
|
7828
|
+
"4. NO INTERACTIVITY",
|
|
7829
|
+
" \u2022 Do not ask questions, do not request confirmation, do not propose",
|
|
7830
|
+
" options. Pick the best path and execute. The user is asleep."
|
|
7831
|
+
].join("\n");
|
|
7832
|
+
}
|
|
7833
|
+
/**
|
|
7834
|
+
* Exponential backoff for transient provider errors. `2^N * base`
|
|
7835
|
+
* capped at `transientBackoffMaxMs`. Zero base disables backoff.
|
|
7836
|
+
* Public-private to keep `runOneIteration` readable; the value is
|
|
7837
|
+
* recomputed each call from the current retry count, so callers
|
|
7838
|
+
* don't have to track state.
|
|
7839
|
+
*/
|
|
7840
|
+
computeTransientBackoffMs() {
|
|
7841
|
+
const base = this.opts.transientBackoffBaseMs ?? 2e3;
|
|
7842
|
+
const cap = this.opts.transientBackoffMaxMs ?? 6e4;
|
|
7843
|
+
if (base <= 0) return 0;
|
|
7844
|
+
const exponent = Math.max(0, this.consecutiveTransientRetries - 1);
|
|
7845
|
+
return Math.min(cap, base * Math.pow(2, exponent));
|
|
7846
|
+
}
|
|
7847
|
+
/**
|
|
7848
|
+
* Sleep that wakes early if `stopRequested` flips. Polls every 250 ms
|
|
7849
|
+
* so SIGINT / `/autonomy stop` can land in the middle of a long
|
|
7850
|
+
* backoff instead of waiting up to a minute for the timer.
|
|
7851
|
+
*/
|
|
7852
|
+
async sleepInterruptible(totalMs) {
|
|
7853
|
+
const step = 250;
|
|
7854
|
+
let remaining = totalMs;
|
|
7855
|
+
while (remaining > 0 && !this.stopRequested) {
|
|
7856
|
+
const chunk = Math.min(step, remaining);
|
|
7857
|
+
await sleep(chunk);
|
|
7858
|
+
remaining -= chunk;
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
async appendIterationEntry(entry) {
|
|
7862
|
+
const current = await loadGoal(this.goalPath);
|
|
7863
|
+
if (!current) {
|
|
7864
|
+
return;
|
|
7865
|
+
}
|
|
7866
|
+
const updated = appendJournal(current, entry);
|
|
7867
|
+
await saveGoal(this.goalPath, updated);
|
|
7868
|
+
}
|
|
7869
|
+
/**
|
|
7870
|
+
* Persistent per-todo failure counter. Skipped silently when the goal
|
|
7871
|
+
* file has been removed (graceful clear). Each non-success iteration
|
|
7872
|
+
* against a todo source bumps the counter by 1; `pickPendingTodo` reads
|
|
7873
|
+
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
7874
|
+
*/
|
|
7875
|
+
async bumpTodoAttempt(todoId) {
|
|
7876
|
+
const current = await loadGoal(this.goalPath);
|
|
7877
|
+
if (!current) return;
|
|
7878
|
+
const attempts = { ...current.todoAttempts ?? {} };
|
|
7879
|
+
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
7880
|
+
await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
|
|
7881
|
+
}
|
|
7882
|
+
/**
|
|
7883
|
+
* Flip the mission to `completed` and journal it. Called from two
|
|
7884
|
+
* paths: (a) `[GOAL_COMPLETE]` marker in a successful iteration's
|
|
7885
|
+
* finalText, (b) `brainstorm` returning DONE consecutively past the
|
|
7886
|
+
* configured threshold. Idempotent — re-entry is a no-op once the
|
|
7887
|
+
* goal is already `completed`.
|
|
7888
|
+
*/
|
|
7889
|
+
async markGoalCompleted(action, note) {
|
|
7890
|
+
const current = await loadGoal(this.goalPath);
|
|
7891
|
+
if (!current) return;
|
|
7892
|
+
if (current.goalState === "completed") return;
|
|
7893
|
+
const withFlag = { ...current, goalState: "completed" };
|
|
7894
|
+
const withEntry = appendJournal(withFlag, {
|
|
7895
|
+
source: action.source,
|
|
7896
|
+
task: `MISSION COMPLETE \u2014 ${action.task}`.slice(0, 240),
|
|
7897
|
+
status: "success",
|
|
7898
|
+
note: note.slice(0, 240)
|
|
7899
|
+
});
|
|
7900
|
+
await saveGoal(this.goalPath, withEntry);
|
|
7901
|
+
}
|
|
7902
|
+
async appendFailure(task, note) {
|
|
7903
|
+
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
7904
|
+
}
|
|
7905
|
+
async persistEngineState(state) {
|
|
7906
|
+
const current = await loadGoal(this.goalPath);
|
|
7907
|
+
if (!current) return;
|
|
7908
|
+
if (current.engineState === state) return;
|
|
7909
|
+
await saveGoal(this.goalPath, { ...current, engineState: state });
|
|
7190
7910
|
}
|
|
7191
7911
|
};
|
|
7912
|
+
function sleep(ms) {
|
|
7913
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
7914
|
+
}
|
|
7915
|
+
|
|
7916
|
+
// src/execution/autonomy-prompt-contributor.ts
|
|
7917
|
+
function makeAutonomyPromptContributor(opts) {
|
|
7918
|
+
return async (ctx) => {
|
|
7919
|
+
if (ctx.subagent) return [];
|
|
7920
|
+
if (!opts.enabled()) return [];
|
|
7921
|
+
let goal;
|
|
7922
|
+
try {
|
|
7923
|
+
goal = await loadGoal(opts.goalPath);
|
|
7924
|
+
} catch {
|
|
7925
|
+
return [];
|
|
7926
|
+
}
|
|
7927
|
+
if (!goal) return [];
|
|
7928
|
+
const missionState = goal.goalState ?? "active";
|
|
7929
|
+
if (missionState !== "active") return [];
|
|
7930
|
+
const tailSize = opts.journalTailSize ?? 5;
|
|
7931
|
+
const journalTail = goal.journal.slice(-tailSize).map((e) => {
|
|
7932
|
+
const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
|
|
7933
|
+
return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
|
|
7934
|
+
});
|
|
7935
|
+
const text = [
|
|
7936
|
+
"## ETERNAL AUTONOMY \u2014 active mission",
|
|
7937
|
+
"",
|
|
7938
|
+
"You are inside a long-running autonomous loop. The user is asleep",
|
|
7939
|
+
"and is not available to confirm decisions. Each turn you receive a",
|
|
7940
|
+
"directive describing one concrete sub-task that advances the mission.",
|
|
7941
|
+
"",
|
|
7942
|
+
`Mission: ${goal.goal}`,
|
|
7943
|
+
`Iteration: #${goal.iterations}`,
|
|
7944
|
+
journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
|
|
7945
|
+
${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
|
|
7946
|
+
"",
|
|
7947
|
+
"### Loop control markers",
|
|
7948
|
+
"Emit these on their own line in your final text \u2014 case-insensitive,",
|
|
7949
|
+
"whitespace-tolerant, but they must occupy the entire line:",
|
|
7950
|
+
"- `[continue]` \u2014 chain to the next internal step without returning.",
|
|
7951
|
+
"- `[done]` \u2014 the current sub-task is finished; return to the engine.",
|
|
7952
|
+
"- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
|
|
7953
|
+
" verifiably done. Must be followed by a one-paragraph verification",
|
|
7954
|
+
" recipe (artifact path, test command, or 10-second reproduction).",
|
|
7955
|
+
" The engine halts on this marker \u2014 false positives waste real",
|
|
7956
|
+
" human time. If unsure, emit `[done]` and let the next iteration",
|
|
7957
|
+
" decide.",
|
|
7958
|
+
"",
|
|
7959
|
+
"### Operating principles",
|
|
7960
|
+
"- YOLO is active. Do NOT ask for confirmation, do NOT propose",
|
|
7961
|
+
" options. Pick the best path and execute it.",
|
|
7962
|
+
"- Use tools freely; multiple calls per turn are normal and expected.",
|
|
7963
|
+
"- When working on a todo, mark it `in_progress` via the todos tool",
|
|
7964
|
+
" before tool work and `completed` (or `cancelled` with a reason)",
|
|
7965
|
+
" when done. The loop reads todo state between iterations.",
|
|
7966
|
+
"- If an approach fails twice in a row, pivot. Don't grind on the",
|
|
7967
|
+
" same wall \u2014 try a different angle, file a cancel on the todo, or",
|
|
7968
|
+
" surface the obstacle via `[done]` and let the next iteration",
|
|
7969
|
+
" re-plan."
|
|
7970
|
+
].join("\n");
|
|
7971
|
+
return [
|
|
7972
|
+
{
|
|
7973
|
+
type: "text",
|
|
7974
|
+
text,
|
|
7975
|
+
cache_control: { type: "ephemeral" }
|
|
7976
|
+
}
|
|
7977
|
+
];
|
|
7978
|
+
};
|
|
7979
|
+
}
|
|
7980
|
+
|
|
7981
|
+
// src/execution/goal-preamble.ts
|
|
7982
|
+
function buildGoalPreamble(goal) {
|
|
7983
|
+
return [
|
|
7984
|
+
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
7985
|
+
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
7986
|
+
"",
|
|
7987
|
+
"YOUR GOAL:",
|
|
7988
|
+
"---",
|
|
7989
|
+
goal,
|
|
7990
|
+
"---",
|
|
7991
|
+
"",
|
|
7992
|
+
"AUTHORITY YOU HAVE:",
|
|
7993
|
+
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
7994
|
+
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
7995
|
+
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
7996
|
+
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
7997
|
+
" batch work, specialist model for domain code.",
|
|
7998
|
+
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
7999
|
+
" The Agent loop auto-extends every 100 iterations forever.",
|
|
8000
|
+
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
8001
|
+
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
8002
|
+
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
8003
|
+
" to stick with the first plan you proposed.",
|
|
8004
|
+
"",
|
|
8005
|
+
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
8006
|
+
"- You can name a concrete artifact (a passing test, a written file at",
|
|
8007
|
+
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
8008
|
+
" a clean grep that previously had matches).",
|
|
8009
|
+
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
8010
|
+
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
8011
|
+
' believe this fixes it", "the changes appear correct".',
|
|
8012
|
+
"",
|
|
8013
|
+
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
8014
|
+
"- An error message you didn't recover from.",
|
|
8015
|
+
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
8016
|
+
" without questioning the search.",
|
|
8017
|
+
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
8018
|
+
' want X." Those are hedges. The user already told you to finish the',
|
|
8019
|
+
" goal \u2014 just do it.",
|
|
8020
|
+
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
8021
|
+
" done, not done.",
|
|
8022
|
+
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
8023
|
+
" respond to with a fresh attempt (different role, different model,",
|
|
8024
|
+
" tighter prompt).",
|
|
8025
|
+
"",
|
|
8026
|
+
"PERSISTENCE PROTOCOL:",
|
|
8027
|
+
"- If blocked, try at least 3 different angles before reporting the",
|
|
8028
|
+
" problem to the user. Different tool inputs, different subagent",
|
|
8029
|
+
" roles, different providers, different decomposition of the task.",
|
|
8030
|
+
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
8031
|
+
" not just report the failure back.",
|
|
8032
|
+
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
8033
|
+
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
8034
|
+
" final answer.",
|
|
8035
|
+
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
8036
|
+
" full delegated task.",
|
|
8037
|
+
"",
|
|
8038
|
+
"REPORTING:",
|
|
8039
|
+
"- Stream short progress notes between major actions so the user can",
|
|
8040
|
+
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
8041
|
+
" text \u2014 but also do not narrate every tool call.",
|
|
8042
|
+
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
8043
|
+
" subagents can read.",
|
|
8044
|
+
"- Final response must include: (a) what was accomplished, (b) how",
|
|
8045
|
+
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
8046
|
+
" should know about).",
|
|
8047
|
+
"",
|
|
8048
|
+
"BEGIN.]"
|
|
8049
|
+
].join("\n");
|
|
8050
|
+
}
|
|
8051
|
+
|
|
8052
|
+
// src/coordination/director.ts
|
|
8053
|
+
init_atomic_write();
|
|
7192
8054
|
|
|
7193
8055
|
// src/coordination/director-prompts.ts
|
|
7194
8056
|
var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
|
|
@@ -7497,7 +8359,7 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
7497
8359
|
this.decision = decision;
|
|
7498
8360
|
}
|
|
7499
8361
|
};
|
|
7500
|
-
var SubagentBudget = class {
|
|
8362
|
+
var SubagentBudget = class _SubagentBudget {
|
|
7501
8363
|
limits;
|
|
7502
8364
|
iterations = 0;
|
|
7503
8365
|
toolCalls = 0;
|
|
@@ -7506,6 +8368,26 @@ var SubagentBudget = class {
|
|
|
7506
8368
|
costUsd = 0;
|
|
7507
8369
|
startTime = null;
|
|
7508
8370
|
_onThreshold;
|
|
8371
|
+
/**
|
|
8372
|
+
* Tracks which budget kinds currently have an extension request
|
|
8373
|
+
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
8374
|
+
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
8375
|
+
* after the limit is reached spawns a fresh decision Promise (until
|
|
8376
|
+
* the first one lands and patches limits), flooding the FleetBus
|
|
8377
|
+
* with redundant threshold events. Cleared in `checkLimitAsync`'s
|
|
8378
|
+
* `finally`.
|
|
8379
|
+
*/
|
|
8380
|
+
pendingExtensions = /* @__PURE__ */ new Set();
|
|
8381
|
+
/**
|
|
8382
|
+
* Hard cap on how long `checkLimitAsync` waits for the coordinator to
|
|
8383
|
+
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
8384
|
+
* or hung listener (Director not built / event filter detached mid-run)
|
|
8385
|
+
* leaves the budget over-limit and never enforces anything, since
|
|
8386
|
+
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
8387
|
+
* Hardcoded for now — most fleets set their own per-task timeout that
|
|
8388
|
+
* dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
|
|
8389
|
+
*/
|
|
8390
|
+
static DECISION_TIMEOUT_MS = 3e4;
|
|
7509
8391
|
/**
|
|
7510
8392
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
7511
8393
|
* Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
|
|
@@ -7548,56 +8430,92 @@ var SubagentBudget = class {
|
|
|
7548
8430
|
if (kind === "timeout" || !this._onThreshold) {
|
|
7549
8431
|
throw new BudgetExceededError(kind, limit, used);
|
|
7550
8432
|
}
|
|
7551
|
-
|
|
8433
|
+
const bus = this._events;
|
|
8434
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
8435
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
8436
|
+
}
|
|
8437
|
+
if (this.pendingExtensions.has(kind)) return;
|
|
8438
|
+
this.pendingExtensions.add(kind);
|
|
8439
|
+
const decision = this.negotiateExtension(kind, used, limit);
|
|
8440
|
+
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
7552
8441
|
}
|
|
7553
8442
|
/**
|
|
7554
|
-
*
|
|
7555
|
-
*
|
|
7556
|
-
*
|
|
8443
|
+
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
8444
|
+
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
8445
|
+
* patched in-place; the runner should not abort). Always releases the
|
|
8446
|
+
* `pendingExtensions` slot in `finally`.
|
|
8447
|
+
*
|
|
8448
|
+
* The 'continue' return from a sync handler is treated as
|
|
8449
|
+
* `{ extend: {} }` — keep going without patching, next overrun will
|
|
8450
|
+
* fire a fresh signal.
|
|
7557
8451
|
*/
|
|
7558
|
-
async
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
8452
|
+
async negotiateExtension(kind, used, limit) {
|
|
8453
|
+
try {
|
|
8454
|
+
const result = this._onThreshold({
|
|
8455
|
+
kind,
|
|
8456
|
+
used,
|
|
8457
|
+
limit,
|
|
8458
|
+
// Inject a requestDecision helper the handler can call to emit the
|
|
8459
|
+
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
8460
|
+
// A hard fallback timer guarantees the promise eventually resolves
|
|
8461
|
+
// even if no listener responds — without it, an absent/detached
|
|
8462
|
+
// Director would leave the budget permanently in "asking" state.
|
|
8463
|
+
requestDecision: () => {
|
|
8464
|
+
const bus = this._events;
|
|
8465
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
8466
|
+
return Promise.resolve("stop");
|
|
8467
|
+
}
|
|
8468
|
+
return new Promise((resolve5) => {
|
|
8469
|
+
let resolved = false;
|
|
8470
|
+
const respond = (d) => {
|
|
8471
|
+
if (resolved) return;
|
|
8472
|
+
resolved = true;
|
|
8473
|
+
resolve5(d);
|
|
8474
|
+
};
|
|
8475
|
+
const fallback = setTimeout(
|
|
8476
|
+
() => respond("stop"),
|
|
8477
|
+
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
8478
|
+
);
|
|
8479
|
+
bus.emit("budget.threshold_reached", {
|
|
8480
|
+
kind,
|
|
8481
|
+
used,
|
|
8482
|
+
limit,
|
|
8483
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
8484
|
+
extend: (extra) => {
|
|
8485
|
+
clearTimeout(fallback);
|
|
8486
|
+
respond({ extend: extra });
|
|
8487
|
+
},
|
|
8488
|
+
deny: () => {
|
|
8489
|
+
clearTimeout(fallback);
|
|
8490
|
+
respond("stop");
|
|
8491
|
+
}
|
|
8492
|
+
});
|
|
7575
8493
|
});
|
|
7576
|
-
}
|
|
8494
|
+
}
|
|
8495
|
+
});
|
|
8496
|
+
if (result === "throw") return "stop";
|
|
8497
|
+
if (result === "continue") return { extend: {} };
|
|
8498
|
+
const decision = await result;
|
|
8499
|
+
if (decision === "stop") return "stop";
|
|
8500
|
+
const ext = decision.extend;
|
|
8501
|
+
if (ext.maxIterations !== void 0) {
|
|
8502
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
7577
8503
|
}
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
if (ext.maxToolCalls !== void 0) {
|
|
7594
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
7595
|
-
}
|
|
7596
|
-
if (ext.maxTokens !== void 0) {
|
|
7597
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
7598
|
-
}
|
|
7599
|
-
if (ext.maxCostUsd !== void 0) {
|
|
7600
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
8504
|
+
if (ext.maxToolCalls !== void 0) {
|
|
8505
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
8506
|
+
}
|
|
8507
|
+
if (ext.maxTokens !== void 0) {
|
|
8508
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
8509
|
+
}
|
|
8510
|
+
if (ext.maxCostUsd !== void 0) {
|
|
8511
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
8512
|
+
}
|
|
8513
|
+
if (ext.timeoutMs !== void 0) {
|
|
8514
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
8515
|
+
}
|
|
8516
|
+
return decision;
|
|
8517
|
+
} finally {
|
|
8518
|
+
this.pendingExtensions.delete(kind);
|
|
7601
8519
|
}
|
|
7602
8520
|
}
|
|
7603
8521
|
recordIteration() {
|
|
@@ -7880,6 +8798,19 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
7880
8798
|
setRunner(runner) {
|
|
7881
8799
|
this.runner = runner;
|
|
7882
8800
|
}
|
|
8801
|
+
/**
|
|
8802
|
+
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
8803
|
+
* preempt running tasks — already-dispatched subagents finish their
|
|
8804
|
+
* current task; only future dispatches respect the new cap. Raising
|
|
8805
|
+
* immediately tries to fill the freed slots from the pending queue.
|
|
8806
|
+
*/
|
|
8807
|
+
setMaxConcurrent(n) {
|
|
8808
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
8809
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
8810
|
+
}
|
|
8811
|
+
this.config.maxConcurrent = Math.floor(n);
|
|
8812
|
+
this.tryDispatchNext();
|
|
8813
|
+
}
|
|
7883
8814
|
async spawn(subagent) {
|
|
7884
8815
|
const id = subagent.id || randomUUID();
|
|
7885
8816
|
if (this.subagents.has(id)) {
|
|
@@ -8139,14 +9070,65 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
8139
9070
|
this.recordCompletion(result);
|
|
8140
9071
|
}
|
|
8141
9072
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
8142
|
-
const
|
|
8143
|
-
if (
|
|
9073
|
+
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
9074
|
+
if (initialTimeoutMs === void 0) return runner(task, ctx);
|
|
9075
|
+
const start = Date.now();
|
|
8144
9076
|
let timer = null;
|
|
8145
9077
|
const timeoutPromise = new Promise((_, reject) => {
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
9078
|
+
const armFor = (ms) => {
|
|
9079
|
+
if (timer) clearTimeout(timer);
|
|
9080
|
+
timer = setTimeout(async () => {
|
|
9081
|
+
const elapsed = Date.now() - start;
|
|
9082
|
+
const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
9083
|
+
if (!budget.onThreshold) {
|
|
9084
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9085
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9086
|
+
return;
|
|
9087
|
+
}
|
|
9088
|
+
try {
|
|
9089
|
+
const result = budget.onThreshold({
|
|
9090
|
+
kind: "timeout",
|
|
9091
|
+
used: elapsed,
|
|
9092
|
+
limit,
|
|
9093
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
9094
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
9095
|
+
kind: "timeout",
|
|
9096
|
+
used: elapsed,
|
|
9097
|
+
limit,
|
|
9098
|
+
timeoutMs: 3e4,
|
|
9099
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
9100
|
+
deny: () => resolveDecision("stop")
|
|
9101
|
+
});
|
|
9102
|
+
})
|
|
9103
|
+
});
|
|
9104
|
+
const decision = typeof result === "string" ? result : await result;
|
|
9105
|
+
if (decision === "continue") {
|
|
9106
|
+
armFor(Math.max(1e3, limit));
|
|
9107
|
+
return;
|
|
9108
|
+
}
|
|
9109
|
+
if (decision === "throw" || decision === "stop") {
|
|
9110
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9111
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9112
|
+
return;
|
|
9113
|
+
}
|
|
9114
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
9115
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
9116
|
+
const newLimit = decision.extend.timeoutMs;
|
|
9117
|
+
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
9118
|
+
armFor(remaining);
|
|
9119
|
+
return;
|
|
9120
|
+
}
|
|
9121
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9122
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9123
|
+
} catch (err) {
|
|
9124
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9125
|
+
reject(
|
|
9126
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
9127
|
+
);
|
|
9128
|
+
}
|
|
9129
|
+
}, ms);
|
|
9130
|
+
};
|
|
9131
|
+
armFor(initialTimeoutMs);
|
|
8150
9132
|
});
|
|
8151
9133
|
try {
|
|
8152
9134
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -8685,7 +9667,9 @@ var Director = class {
|
|
|
8685
9667
|
}, opts.checkpointDebounceMs ?? 250) : null;
|
|
8686
9668
|
this.fleetManager = opts.fleetManager;
|
|
8687
9669
|
if (this.sharedScratchpadPath) {
|
|
8688
|
-
void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
9670
|
+
void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
9671
|
+
(err) => this.logShutdownError("shared_scratchpad_mkdir", err)
|
|
9672
|
+
);
|
|
8689
9673
|
}
|
|
8690
9674
|
this.transport = new InMemoryBridgeTransport();
|
|
8691
9675
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -8755,7 +9739,7 @@ var Director = class {
|
|
|
8755
9739
|
return;
|
|
8756
9740
|
}
|
|
8757
9741
|
extendCounts.set(guardKey, prior + 1);
|
|
8758
|
-
|
|
9742
|
+
setImmediate(() => {
|
|
8759
9743
|
const extra = {};
|
|
8760
9744
|
switch (payload.kind) {
|
|
8761
9745
|
case "iterations":
|
|
@@ -8770,9 +9754,12 @@ var Director = class {
|
|
|
8770
9754
|
case "cost":
|
|
8771
9755
|
extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
|
|
8772
9756
|
break;
|
|
9757
|
+
case "timeout":
|
|
9758
|
+
extra.timeoutMs = Math.min(Math.ceil(payload.limit * 1.5), 60 * 6e4);
|
|
9759
|
+
break;
|
|
8773
9760
|
}
|
|
8774
9761
|
payload.extend(extra);
|
|
8775
|
-
}
|
|
9762
|
+
});
|
|
8776
9763
|
});
|
|
8777
9764
|
}
|
|
8778
9765
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
@@ -8791,7 +9778,9 @@ var Director = class {
|
|
|
8791
9778
|
if (this.manifestTimer) return;
|
|
8792
9779
|
this.manifestTimer = setTimeout(() => {
|
|
8793
9780
|
this.manifestTimer = null;
|
|
8794
|
-
void this.writeManifest().catch(
|
|
9781
|
+
void this.writeManifest().catch(
|
|
9782
|
+
(err) => this.logShutdownError("manifest_write_debounced", err)
|
|
9783
|
+
);
|
|
8795
9784
|
}, this.manifestDebounceMs);
|
|
8796
9785
|
}
|
|
8797
9786
|
/**
|
|
@@ -8982,7 +9971,7 @@ var Director = class {
|
|
|
8982
9971
|
usage: this.usage.snapshot()
|
|
8983
9972
|
};
|
|
8984
9973
|
await fsp2.mkdir(path6.dirname(this.manifestPath), { recursive: true });
|
|
8985
|
-
await
|
|
9974
|
+
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
8986
9975
|
return this.manifestPath;
|
|
8987
9976
|
}
|
|
8988
9977
|
/**
|
|
@@ -9511,9 +10500,11 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
9511
10500
|
candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
9512
10501
|
} else {
|
|
9513
10502
|
try {
|
|
9514
|
-
const
|
|
9515
|
-
for (const
|
|
9516
|
-
|
|
10503
|
+
const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
10504
|
+
for (const entry of entries) {
|
|
10505
|
+
if (entry.isDirectory()) {
|
|
10506
|
+
candidates.push(path6.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
10507
|
+
}
|
|
9517
10508
|
}
|
|
9518
10509
|
} catch {
|
|
9519
10510
|
return void 0;
|
|
@@ -9563,6 +10554,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
9563
10554
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
9564
10555
|
const aborter = new AbortController();
|
|
9565
10556
|
ctx.budget._events = events;
|
|
10557
|
+
ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
|
|
9566
10558
|
let budgetError = null;
|
|
9567
10559
|
const onBudgetError = (err) => {
|
|
9568
10560
|
if (err instanceof BudgetThresholdSignal) {
|
|
@@ -9711,6 +10703,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
9711
10703
|
|
|
9712
10704
|
// src/coordination/null-fleet-bus.ts
|
|
9713
10705
|
var NULL_FLEET_BUS = new FleetBus();
|
|
10706
|
+
|
|
10707
|
+
// src/models/mode-store.ts
|
|
10708
|
+
init_atomic_write();
|
|
9714
10709
|
var DefaultModeStore = class {
|
|
9715
10710
|
activeModeId = null;
|
|
9716
10711
|
modes;
|
|
@@ -9769,10 +10764,9 @@ var DefaultModeStore = class {
|
|
|
9769
10764
|
try {
|
|
9770
10765
|
await fsp2.mkdir(this.configDir, { recursive: true });
|
|
9771
10766
|
const configPath = path6.join(this.configDir, "mode.json");
|
|
9772
|
-
await
|
|
10767
|
+
await atomicWrite(
|
|
9773
10768
|
configPath,
|
|
9774
|
-
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
9775
|
-
"utf8"
|
|
10769
|
+
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
9776
10770
|
);
|
|
9777
10771
|
} catch {
|
|
9778
10772
|
}
|
|
@@ -10626,6 +11620,9 @@ var SpecDrivenDev = class {
|
|
|
10626
11620
|
}));
|
|
10627
11621
|
}
|
|
10628
11622
|
};
|
|
11623
|
+
|
|
11624
|
+
// src/sdd/spec-store.ts
|
|
11625
|
+
init_atomic_write();
|
|
10629
11626
|
var SpecStore = class {
|
|
10630
11627
|
baseDir;
|
|
10631
11628
|
indexPath;
|
|
@@ -10735,6 +11732,9 @@ var SpecStore = class {
|
|
|
10735
11732
|
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
10736
11733
|
}
|
|
10737
11734
|
};
|
|
11735
|
+
|
|
11736
|
+
// src/sdd/task-graph-store.ts
|
|
11737
|
+
init_atomic_write();
|
|
10738
11738
|
function graphToJSON(graph) {
|
|
10739
11739
|
const serialisable = {
|
|
10740
11740
|
...graph,
|
|
@@ -11036,10 +12036,11 @@ var AISpecBuilder = class {
|
|
|
11036
12036
|
async saveSession() {
|
|
11037
12037
|
if (!this.sessionPath) return;
|
|
11038
12038
|
try {
|
|
11039
|
-
const
|
|
11040
|
-
const
|
|
11041
|
-
await
|
|
11042
|
-
await
|
|
12039
|
+
const fsp16 = await import('fs/promises');
|
|
12040
|
+
const path26 = await import('path');
|
|
12041
|
+
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
12042
|
+
await fsp16.mkdir(path26.dirname(this.sessionPath), { recursive: true });
|
|
12043
|
+
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
11043
12044
|
} catch {
|
|
11044
12045
|
}
|
|
11045
12046
|
}
|
|
@@ -11047,8 +12048,8 @@ var AISpecBuilder = class {
|
|
|
11047
12048
|
async loadSession() {
|
|
11048
12049
|
if (!this.sessionPath) return false;
|
|
11049
12050
|
try {
|
|
11050
|
-
const
|
|
11051
|
-
const raw = await
|
|
12051
|
+
const fsp16 = await import('fs/promises');
|
|
12052
|
+
const raw = await fsp16.readFile(this.sessionPath, "utf8");
|
|
11052
12053
|
const loaded = JSON.parse(raw);
|
|
11053
12054
|
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
11054
12055
|
this.session = loaded;
|
|
@@ -11062,14 +12063,21 @@ var AISpecBuilder = class {
|
|
|
11062
12063
|
async deleteSession() {
|
|
11063
12064
|
if (!this.sessionPath) return;
|
|
11064
12065
|
try {
|
|
11065
|
-
const
|
|
11066
|
-
await
|
|
12066
|
+
const fsp16 = await import('fs/promises');
|
|
12067
|
+
await fsp16.unlink(this.sessionPath);
|
|
11067
12068
|
} catch {
|
|
11068
12069
|
}
|
|
11069
12070
|
}
|
|
11070
|
-
/** Auto-save helper — calls saveSession() but never throws.
|
|
12071
|
+
/** Auto-save helper — calls saveSession() but never throws.
|
|
12072
|
+
* Failures are surfaced via process.emitWarning so a persistent
|
|
12073
|
+
* ENOSPC / EACCES doesn't silently strand session edits in memory. */
|
|
11071
12074
|
autoSave() {
|
|
11072
|
-
this.saveSession().catch(() => {
|
|
12075
|
+
this.saveSession().catch((err) => {
|
|
12076
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
12077
|
+
process.emitWarning(
|
|
12078
|
+
`SpecBuilder autoSave failed: ${detail}`,
|
|
12079
|
+
"SpecBuilderWarning"
|
|
12080
|
+
);
|
|
11073
12081
|
});
|
|
11074
12082
|
}
|
|
11075
12083
|
// ── Session Lifecycle ─────────────────────────────────────────────────────
|
|
@@ -11741,15 +12749,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
|
|
|
11741
12749
|
maxId = id;
|
|
11742
12750
|
}
|
|
11743
12751
|
}
|
|
11744
|
-
const
|
|
12752
|
+
const path26 = [];
|
|
11745
12753
|
let current = maxId;
|
|
11746
12754
|
const visited = /* @__PURE__ */ new Set();
|
|
11747
12755
|
while (current && !visited.has(current)) {
|
|
11748
12756
|
visited.add(current);
|
|
11749
|
-
|
|
12757
|
+
path26.unshift(current);
|
|
11750
12758
|
current = prev.get(current) ?? null;
|
|
11751
12759
|
}
|
|
11752
|
-
return
|
|
12760
|
+
return path26;
|
|
11753
12761
|
}
|
|
11754
12762
|
function computeParallelGroups(graph, blockedByMap) {
|
|
11755
12763
|
const groups = [];
|
|
@@ -12459,7 +13467,7 @@ async function startMetricsServer(opts) {
|
|
|
12459
13467
|
const tls = opts.tls;
|
|
12460
13468
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
12461
13469
|
const host = opts.host ?? "127.0.0.1";
|
|
12462
|
-
const
|
|
13470
|
+
const path26 = opts.path ?? "/metrics";
|
|
12463
13471
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
12464
13472
|
const healthRegistry = opts.healthRegistry;
|
|
12465
13473
|
const listener = (req, res) => {
|
|
@@ -12469,7 +13477,7 @@ async function startMetricsServer(opts) {
|
|
|
12469
13477
|
return;
|
|
12470
13478
|
}
|
|
12471
13479
|
const url = req.url.split("?")[0];
|
|
12472
|
-
if (url ===
|
|
13480
|
+
if (url === path26) {
|
|
12473
13481
|
let body;
|
|
12474
13482
|
try {
|
|
12475
13483
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -12533,7 +13541,7 @@ async function startMetricsServer(opts) {
|
|
|
12533
13541
|
const protocol = useHttps ? "https" : "http";
|
|
12534
13542
|
return {
|
|
12535
13543
|
port: boundPort,
|
|
12536
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
13544
|
+
url: `${protocol}://${host}:${boundPort}${path26}`,
|
|
12537
13545
|
close: () => new Promise((resolve5, reject) => {
|
|
12538
13546
|
server.close((err) => err ? reject(err) : resolve5());
|
|
12539
13547
|
})
|
|
@@ -13260,6 +14268,9 @@ function stripTopDir(p) {
|
|
|
13260
14268
|
if (idx === -1) return "";
|
|
13261
14269
|
return p.slice(idx + 1);
|
|
13262
14270
|
}
|
|
14271
|
+
|
|
14272
|
+
// src/skills/manifest-store.ts
|
|
14273
|
+
init_atomic_write();
|
|
13263
14274
|
var SkillManifestStore = class {
|
|
13264
14275
|
manifestPath;
|
|
13265
14276
|
cache;
|
|
@@ -13284,7 +14295,7 @@ var SkillManifestStore = class {
|
|
|
13284
14295
|
async write(data) {
|
|
13285
14296
|
const dir = path6.dirname(this.manifestPath);
|
|
13286
14297
|
await fsp2.mkdir(dir, { recursive: true });
|
|
13287
|
-
await
|
|
14298
|
+
await atomicWrite(this.manifestPath, JSON.stringify(data, null, 2) + "\n");
|
|
13288
14299
|
this.cache = data;
|
|
13289
14300
|
}
|
|
13290
14301
|
async addEntry(entry) {
|
|
@@ -13615,6 +14626,9 @@ async function collectFiles(dir, baseDir) {
|
|
|
13615
14626
|
}
|
|
13616
14627
|
return results;
|
|
13617
14628
|
}
|
|
14629
|
+
|
|
14630
|
+
// src/storage/session-rewinder.ts
|
|
14631
|
+
init_atomic_write();
|
|
13618
14632
|
var DefaultSessionRewinder = class {
|
|
13619
14633
|
constructor(sessionsDir) {
|
|
13620
14634
|
this.sessionsDir = sessionsDir;
|
|
@@ -13748,7 +14762,7 @@ async function revertSnapshots(snapshots) {
|
|
|
13748
14762
|
try {
|
|
13749
14763
|
if (file.action === "deleted") {
|
|
13750
14764
|
if (file.before !== null) {
|
|
13751
|
-
await
|
|
14765
|
+
await atomicWrite(file.path, file.before, { mode: 420 });
|
|
13752
14766
|
revertedFiles.push(file.path);
|
|
13753
14767
|
}
|
|
13754
14768
|
} else if (file.action === "created") {
|
|
@@ -13756,7 +14770,7 @@ async function revertSnapshots(snapshots) {
|
|
|
13756
14770
|
revertedFiles.push(file.path);
|
|
13757
14771
|
} else if (file.action === "modified") {
|
|
13758
14772
|
if (file.before !== null) {
|
|
13759
|
-
await
|
|
14773
|
+
await atomicWrite(file.path, file.before, { mode: 420 });
|
|
13760
14774
|
revertedFiles.push(file.path);
|
|
13761
14775
|
}
|
|
13762
14776
|
}
|
|
@@ -14542,10 +15556,10 @@ var SecurityScanner = class {
|
|
|
14542
15556
|
for (const pattern of patterns) {
|
|
14543
15557
|
if (!this.matchesCategory(pattern)) continue;
|
|
14544
15558
|
for (const regex of pattern.patterns) {
|
|
14545
|
-
regex.lastIndex = 0;
|
|
14546
15559
|
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
14547
15560
|
const line = lines[lineNum];
|
|
14548
15561
|
if (!line) continue;
|
|
15562
|
+
regex.lastIndex = 0;
|
|
14549
15563
|
if (regex.test(line)) {
|
|
14550
15564
|
if (this.isFalsePositive(line, pattern.falsePositiveMarkers)) {
|
|
14551
15565
|
continue;
|
|
@@ -14607,6 +15621,9 @@ var SecurityScanner = class {
|
|
|
14607
15621
|
}
|
|
14608
15622
|
};
|
|
14609
15623
|
var defaultSecurityScanner = new SecurityScanner();
|
|
15624
|
+
|
|
15625
|
+
// src/security-scanner/report-generator.ts
|
|
15626
|
+
init_atomic_write();
|
|
14610
15627
|
var DEFAULT_REPORT_OPTIONS = {
|
|
14611
15628
|
outputDir: "security-reports",
|
|
14612
15629
|
format: "markdown",
|
|
@@ -14634,7 +15651,7 @@ var ReportGenerator = class {
|
|
|
14634
15651
|
default:
|
|
14635
15652
|
content = this.generateMarkdown(scanResult);
|
|
14636
15653
|
}
|
|
14637
|
-
await
|
|
15654
|
+
await atomicWrite(filepath, content);
|
|
14638
15655
|
return filepath;
|
|
14639
15656
|
}
|
|
14640
15657
|
async ensureOutputDir() {
|
|
@@ -14826,6 +15843,9 @@ var ReportGenerator = class {
|
|
|
14826
15843
|
}
|
|
14827
15844
|
};
|
|
14828
15845
|
var defaultReportGenerator = new ReportGenerator();
|
|
15846
|
+
|
|
15847
|
+
// src/security-scanner/gitignore-updater.ts
|
|
15848
|
+
init_atomic_write();
|
|
14829
15849
|
var DEFAULT_OPTIONS2 = {
|
|
14830
15850
|
gitignorePath: ".gitignore",
|
|
14831
15851
|
entries: ["security-reports/", "security-reports/*"]
|
|
@@ -14852,12 +15872,12 @@ var GitignoreUpdater = class {
|
|
|
14852
15872
|
}
|
|
14853
15873
|
if (added.length > 0) {
|
|
14854
15874
|
const newContent = [...lines].filter(Boolean).join("\n") + "\n";
|
|
14855
|
-
await
|
|
15875
|
+
await atomicWrite(this.options.gitignorePath, newContent);
|
|
14856
15876
|
}
|
|
14857
15877
|
} catch (err) {
|
|
14858
15878
|
if (err.code === "ENOENT") {
|
|
14859
15879
|
const content = this.options.entries.join("\n") + "\n";
|
|
14860
|
-
await
|
|
15880
|
+
await atomicWrite(this.options.gitignorePath, content);
|
|
14861
15881
|
added.push(...this.options.entries);
|
|
14862
15882
|
} else {
|
|
14863
15883
|
errors.push(`Failed to update .gitignore: ${err}`);
|
|
@@ -14876,6 +15896,9 @@ var GitignoreUpdater = class {
|
|
|
14876
15896
|
}
|
|
14877
15897
|
};
|
|
14878
15898
|
var defaultGitignoreUpdater = new GitignoreUpdater();
|
|
15899
|
+
|
|
15900
|
+
// src/security-scanner/orchestrator.ts
|
|
15901
|
+
init_atomic_write();
|
|
14879
15902
|
var NETWORK_ERR_RE2 = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
|
|
14880
15903
|
var SecurityScannerOrchestrator = class {
|
|
14881
15904
|
constructor(retryPolicy, errorHandler) {
|
|
@@ -14921,7 +15944,7 @@ var SecurityScannerOrchestrator = class {
|
|
|
14921
15944
|
*/
|
|
14922
15945
|
async run(ctx, options) {
|
|
14923
15946
|
const { projectRoot, reportOptions, skipGitignore, model: explicitModel } = options;
|
|
14924
|
-
const provider = "provider" in ctx ? ctx.provider : ctx;
|
|
15947
|
+
const provider = "provider" in ctx && ctx.provider ? ctx.provider : ctx;
|
|
14925
15948
|
const model = explicitModel ?? ("model" in ctx ? ctx.model : void 0);
|
|
14926
15949
|
const detectionResult = await this.detector.detect(projectRoot);
|
|
14927
15950
|
if (detectionResult.detectedStacks.length === 0) {
|
|
@@ -15260,7 +16283,7 @@ Be specific about the vulnerabilities found and how to fix them.`;
|
|
|
15260
16283
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
15261
16284
|
const filename = `security-report-${timestamp}.${format}`;
|
|
15262
16285
|
const filepath = join(outputDir, filename);
|
|
15263
|
-
await
|
|
16286
|
+
await atomicWrite(filepath, content);
|
|
15264
16287
|
return filepath;
|
|
15265
16288
|
}
|
|
15266
16289
|
/**
|
|
@@ -15434,11 +16457,21 @@ Show a previous security report.
|
|
|
15434
16457
|
}
|
|
15435
16458
|
};
|
|
15436
16459
|
}
|
|
16460
|
+
function getProviderFromContext(ctx) {
|
|
16461
|
+
if (ctx.provider && typeof ctx.provider.complete === "function") {
|
|
16462
|
+
return { provider: ctx.provider, model: ctx.model };
|
|
16463
|
+
}
|
|
16464
|
+
return null;
|
|
16465
|
+
}
|
|
15437
16466
|
async function handleScan(args, ctx) {
|
|
15438
16467
|
const options = parseArgs(args);
|
|
15439
16468
|
const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
|
|
15440
16469
|
try {
|
|
15441
|
-
const
|
|
16470
|
+
const providerInfo = getProviderFromContext(ctx);
|
|
16471
|
+
if (!providerInfo) {
|
|
16472
|
+
return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
|
|
16473
|
+
}
|
|
16474
|
+
const result = await defaultOrchestrator.run(providerInfo, {
|
|
15442
16475
|
projectRoot,
|
|
15443
16476
|
scanOptions: {
|
|
15444
16477
|
depth: options.depth || "standard",
|
|
@@ -15487,7 +16520,11 @@ async function handleScan(args, ctx) {
|
|
|
15487
16520
|
async function handleAudit(ctx) {
|
|
15488
16521
|
const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
|
|
15489
16522
|
try {
|
|
15490
|
-
const
|
|
16523
|
+
const providerInfo = getProviderFromContext(ctx);
|
|
16524
|
+
if (!providerInfo) {
|
|
16525
|
+
return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
|
|
16526
|
+
}
|
|
16527
|
+
const result = await defaultOrchestrator.run(providerInfo, {
|
|
15491
16528
|
projectRoot,
|
|
15492
16529
|
reportOptions: { format: "markdown" }
|
|
15493
16530
|
});
|
|
@@ -15543,16 +16580,16 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
15543
16580
|
}
|
|
15544
16581
|
const index = parseInt(reportId, 10) - 1;
|
|
15545
16582
|
if (!isNaN(index) && reports[index]) {
|
|
15546
|
-
const { readFile:
|
|
15547
|
-
const content = await
|
|
16583
|
+
const { readFile: readFile30 } = await import('fs/promises');
|
|
16584
|
+
const content = await readFile30(join(reportsDir, reports[index]), "utf-8");
|
|
15548
16585
|
return { message: `# Security Report
|
|
15549
16586
|
|
|
15550
16587
|
${content}` };
|
|
15551
16588
|
}
|
|
15552
16589
|
const match = reports.find((r) => r.includes(reportId));
|
|
15553
16590
|
if (match) {
|
|
15554
|
-
const { readFile:
|
|
15555
|
-
const content = await
|
|
16591
|
+
const { readFile: readFile30 } = await import('fs/promises');
|
|
16592
|
+
const content = await readFile30(join(reportsDir, match), "utf-8");
|
|
15556
16593
|
return { message: `# Security Report
|
|
15557
16594
|
|
|
15558
16595
|
${content}` };
|
|
@@ -15602,6 +16639,9 @@ function getHelpMessage() {
|
|
|
15602
16639
|
`;
|
|
15603
16640
|
}
|
|
15604
16641
|
var securitySlashCommand = createSecuritySlashCommand();
|
|
16642
|
+
|
|
16643
|
+
// src/coordination/fleet-manager.ts
|
|
16644
|
+
init_atomic_write();
|
|
15605
16645
|
var FleetManager = class {
|
|
15606
16646
|
/** The fleet-wide event bus. */
|
|
15607
16647
|
fleet;
|
|
@@ -15749,7 +16789,7 @@ var FleetManager = class {
|
|
|
15749
16789
|
usage: this.usage.snapshot()
|
|
15750
16790
|
};
|
|
15751
16791
|
await fsp2.mkdir(path6.dirname(this.manifestPath), { recursive: true });
|
|
15752
|
-
await
|
|
16792
|
+
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
15753
16793
|
return this.manifestPath;
|
|
15754
16794
|
}
|
|
15755
16795
|
/**
|
|
@@ -15769,7 +16809,13 @@ var FleetManager = class {
|
|
|
15769
16809
|
if (this.manifestTimer) return;
|
|
15770
16810
|
this.manifestTimer = setTimeout(() => {
|
|
15771
16811
|
this.manifestTimer = null;
|
|
15772
|
-
void this.writeManifest().catch(() =>
|
|
16812
|
+
void this.writeManifest().catch((err) => {
|
|
16813
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
16814
|
+
process.emitWarning(
|
|
16815
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
16816
|
+
"FleetManagerWarning"
|
|
16817
|
+
);
|
|
16818
|
+
});
|
|
15773
16819
|
}, this.manifestDebounceMs);
|
|
15774
16820
|
}
|
|
15775
16821
|
/** Best-effort session event writer. Swallows failures. */
|
|
@@ -15798,6 +16844,226 @@ var FleetManager = class {
|
|
|
15798
16844
|
return { pending, live: [] };
|
|
15799
16845
|
}
|
|
15800
16846
|
};
|
|
16847
|
+
function createMcpControlTool(opts) {
|
|
16848
|
+
const { getConfig, configPath, registry } = opts;
|
|
16849
|
+
const inputSchema = {
|
|
16850
|
+
type: "object",
|
|
16851
|
+
properties: {
|
|
16852
|
+
action: {
|
|
16853
|
+
type: "string",
|
|
16854
|
+
enum: ["list", "search", "enable", "disable", "restart"],
|
|
16855
|
+
description: "The management action to perform."
|
|
16856
|
+
},
|
|
16857
|
+
/** Filter for `search`. Matches server name or description case-insensitively. */
|
|
16858
|
+
query: {
|
|
16859
|
+
type: "string",
|
|
16860
|
+
description: "Search term for `search` action. Matches server name or description."
|
|
16861
|
+
},
|
|
16862
|
+
/** Target server name for `enable`, `disable`, `restart`. */
|
|
16863
|
+
server: {
|
|
16864
|
+
type: "string",
|
|
16865
|
+
description: 'Server name (e.g. "github", "filesystem", "brave-search").'
|
|
16866
|
+
}
|
|
16867
|
+
},
|
|
16868
|
+
required: ["action"]
|
|
16869
|
+
};
|
|
16870
|
+
return {
|
|
16871
|
+
name: "mcp_control",
|
|
16872
|
+
description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers.",
|
|
16873
|
+
category: "mcp",
|
|
16874
|
+
permission: "auto",
|
|
16875
|
+
mutating: false,
|
|
16876
|
+
riskTier: "standard",
|
|
16877
|
+
inputSchema,
|
|
16878
|
+
async execute(raw, ctx) {
|
|
16879
|
+
const input = raw;
|
|
16880
|
+
return mcpControlDispatch(input, { getConfig, configPath, registry });
|
|
16881
|
+
}
|
|
16882
|
+
};
|
|
16883
|
+
}
|
|
16884
|
+
async function mcpControlDispatch(input, deps) {
|
|
16885
|
+
const { action, query, server } = input;
|
|
16886
|
+
switch (action) {
|
|
16887
|
+
case "list":
|
|
16888
|
+
return renderList(deps);
|
|
16889
|
+
case "search":
|
|
16890
|
+
return renderSearch(query ?? "", deps);
|
|
16891
|
+
case "enable":
|
|
16892
|
+
return runEnable(server, deps);
|
|
16893
|
+
case "disable":
|
|
16894
|
+
return runDisable(server, deps);
|
|
16895
|
+
case "restart":
|
|
16896
|
+
return runRestart(server, deps);
|
|
16897
|
+
default:
|
|
16898
|
+
return `Unknown action "${action}". Use one of: list, search, enable, disable, restart.`;
|
|
16899
|
+
}
|
|
16900
|
+
}
|
|
16901
|
+
function renderList(deps) {
|
|
16902
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16903
|
+
const live = deps.registry.describe();
|
|
16904
|
+
if (Object.keys(configured).length === 0) {
|
|
16905
|
+
return [
|
|
16906
|
+
"No MCP servers configured.",
|
|
16907
|
+
' Use `mcp_control({ action: "search" })` to see available presets,',
|
|
16908
|
+
' then `mcp_control({ action: "enable", server: "<name>" })` to add one.'
|
|
16909
|
+
].join("\n");
|
|
16910
|
+
}
|
|
16911
|
+
const lines = [];
|
|
16912
|
+
const liveMap = new Map(live.map((s) => [s.name, s]));
|
|
16913
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
16914
|
+
const liveInfo = liveMap.get(name);
|
|
16915
|
+
const toolCount = liveInfo ? ` (${liveInfo.toolCount} tools)` : "";
|
|
16916
|
+
const stateStr = liveInfo ? badge(liveInfo.state) : dim("\u25CB not loaded");
|
|
16917
|
+
const enabled = cfg.enabled === false ? `${dim("disabled")} ` : `${green("\u25CF enabled")} `;
|
|
16918
|
+
lines.push(` ${bold(name)} ${enabled}${stateStr}${toolCount}`);
|
|
16919
|
+
if (cfg.description) lines.push(` ${dim(cfg.description)}`);
|
|
16920
|
+
}
|
|
16921
|
+
lines.push("");
|
|
16922
|
+
lines.push(dim(' Use `mcp_control({ action: "search", query: "<keyword>" })` to find servers.'));
|
|
16923
|
+
lines.push(dim(' Use `mcp_control({ action: "enable", server: "<name>" })` to start a server.'));
|
|
16924
|
+
return lines.join("\n");
|
|
16925
|
+
}
|
|
16926
|
+
function renderSearch(query, deps) {
|
|
16927
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16928
|
+
const all = allServers();
|
|
16929
|
+
const q = query.toLowerCase();
|
|
16930
|
+
const configuredNames = new Set(Object.keys(configured));
|
|
16931
|
+
const configuredEntries = Object.entries(configured).filter(
|
|
16932
|
+
([name, cfg]) => name.toLowerCase().includes(q) || (cfg.description ?? "").toLowerCase().includes(q)
|
|
16933
|
+
);
|
|
16934
|
+
const unconfiguredEntries = Object.entries(all).filter(([name]) => !configuredNames.has(name)).filter(
|
|
16935
|
+
([name, cfg]) => name.toLowerCase().includes(q) || (cfg.description ?? "").toLowerCase().includes(q)
|
|
16936
|
+
);
|
|
16937
|
+
const lines = [];
|
|
16938
|
+
if (configuredEntries.length > 0) {
|
|
16939
|
+
lines.push(bold('Configured servers matching "') + query + '":');
|
|
16940
|
+
for (const [name, cfg] of configuredEntries) {
|
|
16941
|
+
lines.push(` ${bold(name)} ${cfg.description ?? cfg.transport}`);
|
|
16942
|
+
}
|
|
16943
|
+
lines.push("");
|
|
16944
|
+
}
|
|
16945
|
+
if (unconfiguredEntries.length > 0) {
|
|
16946
|
+
lines.push(bold('Available presets matching "') + query + '":');
|
|
16947
|
+
for (const [name, cfg] of unconfiguredEntries) {
|
|
16948
|
+
const warn = cfg.permission === "deny" ? red(" \u26A0 confirm required") : "";
|
|
16949
|
+
lines.push(` ${bold(name)} ${cfg.description ?? cfg.transport}${warn}`);
|
|
16950
|
+
}
|
|
16951
|
+
lines.push("");
|
|
16952
|
+
}
|
|
16953
|
+
if (configuredEntries.length === 0 && unconfiguredEntries.length === 0) {
|
|
16954
|
+
return `No servers match "${query}". Try a shorter keyword or \`mcp_control({ action: "list" })\`.`;
|
|
16955
|
+
}
|
|
16956
|
+
const total = configuredEntries.length + unconfiguredEntries.length;
|
|
16957
|
+
lines.push(dim(` ${total} server${total !== 1 ? "s" : ""} shown. Run \`enable\` on one to activate it.`));
|
|
16958
|
+
return lines.join("\n");
|
|
16959
|
+
}
|
|
16960
|
+
async function runEnable(name, deps) {
|
|
16961
|
+
if (!name) return '`server` is required for enable. Example: { action: "enable", server: "github" }';
|
|
16962
|
+
const all = allServers();
|
|
16963
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16964
|
+
const cfg = configured[name] ?? all[name];
|
|
16965
|
+
if (!cfg) {
|
|
16966
|
+
const known = Object.keys(all).join(", ");
|
|
16967
|
+
return `Unknown server "${name}". Available presets: ${known}`;
|
|
16968
|
+
}
|
|
16969
|
+
const full = await readConfig(deps.configPath);
|
|
16970
|
+
const mcpServers = {
|
|
16971
|
+
...full.mcpServers ?? {}
|
|
16972
|
+
};
|
|
16973
|
+
mcpServers[name] = { ...cfg, enabled: true };
|
|
16974
|
+
full.mcpServers = mcpServers;
|
|
16975
|
+
await writeConfig(deps.configPath, full);
|
|
16976
|
+
try {
|
|
16977
|
+
const live = deps.registry.describe().find((s) => s.name === name);
|
|
16978
|
+
if (live && live.state === "connected") {
|
|
16979
|
+
return `${green("\u25CF")} Server "${name}" is already running (${live.toolCount} tools registered).`;
|
|
16980
|
+
}
|
|
16981
|
+
await deps.registry.start({ ...cfg, enabled: true });
|
|
16982
|
+
const updated = deps.registry.describe().find((s) => s.name === name);
|
|
16983
|
+
return `${green("\u2713 Enabled and started")} "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
|
|
16984
|
+
} catch (err) {
|
|
16985
|
+
return `${red("\u2717 Failed to start")} "${name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
16986
|
+
}
|
|
16987
|
+
}
|
|
16988
|
+
async function runDisable(name, deps) {
|
|
16989
|
+
if (!name) return '`server` is required for disable. Example: { action: "disable", server: "github" }';
|
|
16990
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16991
|
+
if (!configured[name]) {
|
|
16992
|
+
return `Server "${name}" is not in config. Add it with \`mcp_control({ action: "enable", server: "${name}" })\`.`;
|
|
16993
|
+
}
|
|
16994
|
+
const full = await readConfig(deps.configPath);
|
|
16995
|
+
const mcpServers = {
|
|
16996
|
+
...full.mcpServers ?? {}
|
|
16997
|
+
};
|
|
16998
|
+
const existing = mcpServers[name];
|
|
16999
|
+
mcpServers[name] = { ...existing, enabled: false };
|
|
17000
|
+
full.mcpServers = mcpServers;
|
|
17001
|
+
await writeConfig(deps.configPath, full);
|
|
17002
|
+
try {
|
|
17003
|
+
await deps.registry.stop(name);
|
|
17004
|
+
return `${yellow("\u25CB Disabled")} "${name}". It will not be started on next boot.`;
|
|
17005
|
+
} catch {
|
|
17006
|
+
return `${yellow("\u25CB Disabled")} "${name}" (it was not running). Config updated.`;
|
|
17007
|
+
}
|
|
17008
|
+
}
|
|
17009
|
+
async function runRestart(name, deps) {
|
|
17010
|
+
if (!name) return '`server` is required for restart. Example: { action: "restart", server: "github" }';
|
|
17011
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
17012
|
+
if (!configured[name]) {
|
|
17013
|
+
return `Server "${name}" is not configured. Use \`mcp_control({ action: "enable", server: "${name}" })\` first.`;
|
|
17014
|
+
}
|
|
17015
|
+
try {
|
|
17016
|
+
await deps.registry.restart(name);
|
|
17017
|
+
const updated = deps.registry.describe().find((s) => s.name === name);
|
|
17018
|
+
return `${green("\u2713 Restarted")} "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
|
|
17019
|
+
} catch (err) {
|
|
17020
|
+
return `${red("\u2717 Restart failed")} for "${name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
17021
|
+
}
|
|
17022
|
+
}
|
|
17023
|
+
async function readConfig(p) {
|
|
17024
|
+
try {
|
|
17025
|
+
return JSON.parse(await fsp2.readFile(p, "utf8"));
|
|
17026
|
+
} catch {
|
|
17027
|
+
return {};
|
|
17028
|
+
}
|
|
17029
|
+
}
|
|
17030
|
+
async function writeConfig(p, cfg) {
|
|
17031
|
+
const raw = JSON.stringify(cfg, null, 2);
|
|
17032
|
+
const tmp = p + ".tmp";
|
|
17033
|
+
await fsp2.writeFile(tmp, raw, "utf8");
|
|
17034
|
+
await fsp2.rename(tmp, p);
|
|
17035
|
+
}
|
|
17036
|
+
function bold(s) {
|
|
17037
|
+
return `\x1B[1m${s}\x1B[0m`;
|
|
17038
|
+
}
|
|
17039
|
+
function dim(s) {
|
|
17040
|
+
return `\x1B[2m${s}\x1B[0m`;
|
|
17041
|
+
}
|
|
17042
|
+
function green(s) {
|
|
17043
|
+
return `\x1B[32m${s}\x1B[0m`;
|
|
17044
|
+
}
|
|
17045
|
+
function yellow(s) {
|
|
17046
|
+
return `\x1B[33m${s}\x1B[0m`;
|
|
17047
|
+
}
|
|
17048
|
+
function red(s) {
|
|
17049
|
+
return `\x1B[31m${s}\x1B[0m`;
|
|
17050
|
+
}
|
|
17051
|
+
function badge(state) {
|
|
17052
|
+
switch (state) {
|
|
17053
|
+
case "connected":
|
|
17054
|
+
return green("\u25CF connected");
|
|
17055
|
+
case "connecting":
|
|
17056
|
+
return `\x1B[36m\u25D0 connecting\x1B[0m`;
|
|
17057
|
+
case "reconnecting":
|
|
17058
|
+
return `\x1B[36m\u25D1 reconnecting\x1B[0m`;
|
|
17059
|
+
case "disconnected":
|
|
17060
|
+
return dim("\u25CB disconnected");
|
|
17061
|
+
case "failed":
|
|
17062
|
+
return red("\u2717 failed");
|
|
17063
|
+
default:
|
|
17064
|
+
return dim(state);
|
|
17065
|
+
}
|
|
17066
|
+
}
|
|
15801
17067
|
|
|
15802
17068
|
// src/extension/registry.ts
|
|
15803
17069
|
var ExtensionRegistry = class {
|
|
@@ -15827,7 +17093,8 @@ var ExtensionRegistry = class {
|
|
|
15827
17093
|
*/
|
|
15828
17094
|
async buildSystemPromptContributions(ctx) {
|
|
15829
17095
|
const blocks = [];
|
|
15830
|
-
|
|
17096
|
+
const snapshot = [...this.promptContributors];
|
|
17097
|
+
for (const c of snapshot) {
|
|
15831
17098
|
try {
|
|
15832
17099
|
const contributed = await c(ctx);
|
|
15833
17100
|
blocks.push(...contributed);
|
|
@@ -16149,16 +17416,7 @@ var Agent = class {
|
|
|
16149
17416
|
this.tracer = init.tracer;
|
|
16150
17417
|
this.extensions = init.extensions ?? new ExtensionRegistry();
|
|
16151
17418
|
this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
|
|
16152
|
-
this.toolExecutor =
|
|
16153
|
-
permissionPolicy: init.permissionPolicy ?? this.permission,
|
|
16154
|
-
secretScrubber: this.scrubber,
|
|
16155
|
-
renderer: this.renderer,
|
|
16156
|
-
events: this.events,
|
|
16157
|
-
confirmAwaiter: init.confirmAwaiter,
|
|
16158
|
-
iterationTimeoutMs: this.iterationTimeoutMs,
|
|
16159
|
-
perIterationOutputCapBytes: this.perIterationOutputCapBytes,
|
|
16160
|
-
tracer: this.tracer
|
|
16161
|
-
});
|
|
17419
|
+
this.toolExecutor = init.toolExecutor;
|
|
16162
17420
|
}
|
|
16163
17421
|
get logger() {
|
|
16164
17422
|
return this.container.resolve(TOKENS.Logger);
|
|
@@ -16268,6 +17526,7 @@ var Agent = class {
|
|
|
16268
17526
|
let effectiveLimit = opts.maxIterations ?? this.maxIterations;
|
|
16269
17527
|
const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
|
|
16270
17528
|
let recoveryRetries = 0;
|
|
17529
|
+
const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
|
|
16271
17530
|
const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
|
|
16272
17531
|
const baseRunner = diRunner ? (ctx, req) => diRunner.run({
|
|
16273
17532
|
provider: ctx.provider,
|
|
@@ -16294,7 +17553,7 @@ var Agent = class {
|
|
|
16294
17553
|
if (controller.signal.aborted) {
|
|
16295
17554
|
return { status: "aborted", iterations };
|
|
16296
17555
|
}
|
|
16297
|
-
if (
|
|
17556
|
+
if (autonomousContinue) {
|
|
16298
17557
|
consumeAutonomousContinue(this.ctx);
|
|
16299
17558
|
}
|
|
16300
17559
|
const limitCheck = await this.checkIterationLimit(
|
|
@@ -16373,18 +17632,18 @@ var Agent = class {
|
|
|
16373
17632
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
16374
17633
|
if (toolUses.length === 0) {
|
|
16375
17634
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
16376
|
-
if (
|
|
17635
|
+
if (autonomousContinue && responseResult.directive === "continue") {
|
|
16377
17636
|
await this.compactContextIfNeeded();
|
|
16378
17637
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
16379
17638
|
continue;
|
|
16380
17639
|
}
|
|
16381
|
-
if (
|
|
17640
|
+
if (autonomousContinue && responseResult.directive === "stop") {
|
|
16382
17641
|
return { status: "done", iterations, finalText };
|
|
16383
17642
|
}
|
|
16384
17643
|
return { status: "done", iterations, finalText };
|
|
16385
17644
|
}
|
|
16386
17645
|
await this.executeTools(toolUses);
|
|
16387
|
-
if (
|
|
17646
|
+
if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
|
|
16388
17647
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
16389
17648
|
await this.compactContextIfNeeded();
|
|
16390
17649
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
@@ -16393,10 +17652,10 @@ var Agent = class {
|
|
|
16393
17652
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
16394
17653
|
await this.compactContextIfNeeded();
|
|
16395
17654
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
16396
|
-
if (
|
|
17655
|
+
if (autonomousContinue && responseResult.directive === "continue") {
|
|
16397
17656
|
continue;
|
|
16398
17657
|
}
|
|
16399
|
-
if (
|
|
17658
|
+
if (autonomousContinue && responseResult.directive === "stop") {
|
|
16400
17659
|
return { status: "done", iterations, finalText };
|
|
16401
17660
|
}
|
|
16402
17661
|
}
|
|
@@ -16486,7 +17745,7 @@ var Agent = class {
|
|
|
16486
17745
|
}
|
|
16487
17746
|
}
|
|
16488
17747
|
let directive = "none";
|
|
16489
|
-
if (
|
|
17748
|
+
if (finalText) {
|
|
16490
17749
|
directive = parseContinueDirective(finalText);
|
|
16491
17750
|
}
|
|
16492
17751
|
return { finalText, aborted: false, done: false, directive };
|
|
@@ -18082,6 +19341,6 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
|
|
|
18082
19341
|
});
|
|
18083
19342
|
}
|
|
18084
19343
|
|
|
18085
|
-
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeContinueToNextIterationTool, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
19344
|
+
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMcpControlTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeContinueToNextIterationTool, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
18086
19345
|
//# sourceMappingURL=index.js.map
|
|
18087
19346
|
//# sourceMappingURL=index.js.map
|