flavor-code 1.4.0-beta.1 → 1.4.0-beta.4
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/loop.d.ts +7 -2
- package/dist/{app-C5ZQUOKJ.js → app-UWIEIWBQ.js} +7 -7
- package/dist/{chunk-FOTMP4ZS.js → chunk-4RR5VVX4.js} +8 -2
- package/dist/{chunk-6MNOZ2TX.js → chunk-DCNUBQCH.js} +1376 -641
- package/dist/{chunk-ODRWR4KK.js → chunk-IQAZ42TM.js} +1 -1
- package/dist/{chunk-URUVFECH.js → chunk-KQMKY7C5.js} +8 -4
- package/dist/chunk-LKQTBL3R.js +107 -0
- package/dist/{chunk-3WCDZ6ZL.js → chunk-ZJCAQXA7.js} +2 -1
- package/dist/{claude-ink-LP32NSX5.js → claude-ink-ECZ6QF3P.js} +2 -2
- package/dist/cli-main.js +22 -15
- package/dist/cli.js +24 -5
- package/dist/config/schema.d.ts +4 -0
- package/dist/context/manager.d.ts +9 -0
- package/dist/desktop/main.js +1770 -949
- package/dist/desktop-renderer/assets/{index-CqRIr-hz.js → index-C0t1nGih.js} +2 -2
- package/dist/desktop-renderer/assets/{interactive-terminal-C4o7MNPO.js → interactive-terminal-BJqe96mZ.js} +1 -1
- package/dist/desktop-renderer/index.html +1 -1
- package/dist/git/service.d.ts +3 -3
- package/dist/git/tools.d.ts +1 -1
- package/dist/goal/classifier.d.ts +2 -0
- package/dist/goal/orchestrator.d.ts +18 -0
- package/dist/goal/planner.d.ts +2 -0
- package/dist/goal/store.d.ts +2 -0
- package/dist/goal/types.d.ts +8 -0
- package/dist/harness/journal.d.ts +2 -0
- package/dist/harness/local.d.ts +2 -0
- package/dist/init/project.d.ts +0 -6
- package/dist/jobs/registry.d.ts +7 -1
- package/dist/{load-V2UIYYQI.js → load-F4AYLCF6.js} +1 -1
- package/dist/loop/orchestrator.d.ts +18 -0
- package/dist/loop/types.d.ts +7 -0
- package/dist/production.d.ts +17 -0
- package/dist/sdk/index.js +5 -5
- package/dist/session/tree.d.ts +2 -0
- package/dist/tools/lsp.d.ts +13 -10
- package/dist/tools/runtime.d.ts +2 -1
- package/dist/tools/search.d.ts +1 -0
- package/dist/tools/types.d.ts +1 -1
- package/dist/ui/session.d.ts +9 -0
- package/dist/utils/abort.d.ts +4 -0
- package/dist/utils/execFileNoThrow.d.ts +1 -0
- package/dist/utils/heap.d.ts +58 -0
- package/dist/utils/memory-restart.d.ts +38 -3
- package/dist/utils/process-tree.d.ts +2 -0
- package/dist/utils/rotation-census.d.ts +36 -0
- package/package.json +2 -2
- package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +7 -7
- package/dist/astgraph/db.mjs +0 -237
- package/dist/astgraph/extract.mjs +0 -239
- package/dist/astgraph/flavor-plugin.json +0 -25
- package/dist/astgraph/grammars.mjs +0 -68
- package/dist/astgraph/index.js +0 -301
- package/dist/astgraph/indexer.mjs +0 -175
- package/dist/astgraph/query.mjs +0 -165
- package/dist/astgraph/resolve.mjs +0 -93
- package/dist/astgraph/vendor/tree-sitter-javascript.wasm +0 -0
- package/dist/astgraph/vendor/tree-sitter-tsx.wasm +0 -0
- package/dist/astgraph/vendor/tree-sitter-typescript.wasm +0 -0
- package/dist/astgraph/vendor/tree-sitter.js +0 -3980
- package/dist/astgraph/vendor/tree-sitter.wasm +0 -0
- package/dist/astgraph/vendor/zod/LICENSE +0 -21
- package/dist/astgraph/vendor/zod/index.js +0 -4
- package/dist/astgraph/vendor/zod/locales/index.js +0 -1
- package/dist/astgraph/vendor/zod/locales/package.json +0 -7
- package/dist/astgraph/vendor/zod/package.json +0 -135
- package/dist/astgraph/vendor/zod/v4/classic/checks.js +0 -1
- package/dist/astgraph/vendor/zod/v4/classic/coerce.js +0 -17
- package/dist/astgraph/vendor/zod/v4/classic/compat.js +0 -31
- package/dist/astgraph/vendor/zod/v4/classic/errors.js +0 -48
- package/dist/astgraph/vendor/zod/v4/classic/external.js +0 -20
- package/dist/astgraph/vendor/zod/v4/classic/from-json-schema.js +0 -599
- package/dist/astgraph/vendor/zod/v4/classic/index.js +0 -4
- package/dist/astgraph/vendor/zod/v4/classic/iso.js +0 -30
- package/dist/astgraph/vendor/zod/v4/classic/package.json +0 -7
- package/dist/astgraph/vendor/zod/v4/classic/parse.js +0 -15
- package/dist/astgraph/vendor/zod/v4/classic/schemas.js +0 -1395
- package/dist/astgraph/vendor/zod/v4/core/api.js +0 -1087
- package/dist/astgraph/vendor/zod/v4/core/checks.js +0 -575
- package/dist/astgraph/vendor/zod/v4/core/core.js +0 -78
- package/dist/astgraph/vendor/zod/v4/core/doc.js +0 -35
- package/dist/astgraph/vendor/zod/v4/core/errors.js +0 -185
- package/dist/astgraph/vendor/zod/v4/core/index.js +0 -16
- package/dist/astgraph/vendor/zod/v4/core/json-schema-generator.js +0 -95
- package/dist/astgraph/vendor/zod/v4/core/json-schema-processors.js +0 -601
- package/dist/astgraph/vendor/zod/v4/core/json-schema.js +0 -1
- package/dist/astgraph/vendor/zod/v4/core/package.json +0 -7
- package/dist/astgraph/vendor/zod/v4/core/parse.js +0 -93
- package/dist/astgraph/vendor/zod/v4/core/regexes.js +0 -139
- package/dist/astgraph/vendor/zod/v4/core/registries.js +0 -51
- package/dist/astgraph/vendor/zod/v4/core/schemas.js +0 -2239
- package/dist/astgraph/vendor/zod/v4/core/standard-schema.js +0 -1
- package/dist/astgraph/vendor/zod/v4/core/to-json-schema.js +0 -448
- package/dist/astgraph/vendor/zod/v4/core/util.js +0 -674
- package/dist/astgraph/vendor/zod/v4/core/versions.js +0 -5
- package/dist/astgraph/vendor/zod/v4/index.js +0 -3
- package/dist/astgraph/vendor/zod/v4/locales/ar.js +0 -106
- package/dist/astgraph/vendor/zod/v4/locales/az.js +0 -105
- package/dist/astgraph/vendor/zod/v4/locales/be.js +0 -156
- package/dist/astgraph/vendor/zod/v4/locales/bg.js +0 -120
- package/dist/astgraph/vendor/zod/v4/locales/ca.js +0 -107
- package/dist/astgraph/vendor/zod/v4/locales/cs.js +0 -111
- package/dist/astgraph/vendor/zod/v4/locales/da.js +0 -115
- package/dist/astgraph/vendor/zod/v4/locales/de.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/el.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/en.js +0 -113
- package/dist/astgraph/vendor/zod/v4/locales/eo.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/es.js +0 -132
- package/dist/astgraph/vendor/zod/v4/locales/fa.js +0 -114
- package/dist/astgraph/vendor/zod/v4/locales/fi.js +0 -112
- package/dist/astgraph/vendor/zod/v4/locales/fr-CA.js +0 -107
- package/dist/astgraph/vendor/zod/v4/locales/fr.js +0 -125
- package/dist/astgraph/vendor/zod/v4/locales/he.js +0 -214
- package/dist/astgraph/vendor/zod/v4/locales/hr.js +0 -122
- package/dist/astgraph/vendor/zod/v4/locales/hu.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/hy.js +0 -147
- package/dist/astgraph/vendor/zod/v4/locales/id.js +0 -106
- package/dist/astgraph/vendor/zod/v4/locales/index.js +0 -52
- package/dist/astgraph/vendor/zod/v4/locales/is.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/it.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/ja.js +0 -107
- package/dist/astgraph/vendor/zod/v4/locales/ka.js +0 -112
- package/dist/astgraph/vendor/zod/v4/locales/kh.js +0 -5
- package/dist/astgraph/vendor/zod/v4/locales/km.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/ko.js +0 -111
- package/dist/astgraph/vendor/zod/v4/locales/lt.js +0 -203
- package/dist/astgraph/vendor/zod/v4/locales/mk.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/ms.js +0 -107
- package/dist/astgraph/vendor/zod/v4/locales/nl.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/no.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/ota.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/package.json +0 -7
- package/dist/astgraph/vendor/zod/v4/locales/pl.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/ps.js +0 -114
- package/dist/astgraph/vendor/zod/v4/locales/pt.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/ro.js +0 -119
- package/dist/astgraph/vendor/zod/v4/locales/ru.js +0 -156
- package/dist/astgraph/vendor/zod/v4/locales/sl.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/sv.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/ta.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/th.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/tr.js +0 -105
- package/dist/astgraph/vendor/zod/v4/locales/ua.js +0 -5
- package/dist/astgraph/vendor/zod/v4/locales/uk.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/ur.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/uz.js +0 -110
- package/dist/astgraph/vendor/zod/v4/locales/vi.js +0 -108
- package/dist/astgraph/vendor/zod/v4/locales/yo.js +0 -107
- package/dist/astgraph/vendor/zod/v4/locales/zh-CN.js +0 -109
- package/dist/astgraph/vendor/zod/v4/locales/zh-TW.js +0 -107
- package/dist/astgraph/vendor/zod/v4/mini/checks.js +0 -1
- package/dist/astgraph/vendor/zod/v4/mini/coerce.js +0 -22
- package/dist/astgraph/vendor/zod/v4/mini/external.js +0 -14
- package/dist/astgraph/vendor/zod/v4/mini/index.js +0 -3
- package/dist/astgraph/vendor/zod/v4/mini/iso.js +0 -34
- package/dist/astgraph/vendor/zod/v4/mini/package.json +0 -7
- package/dist/astgraph/vendor/zod/v4/mini/parse.js +0 -1
- package/dist/astgraph/vendor/zod/v4/mini/schemas.js +0 -961
- package/dist/astgraph/vendor/zod/v4/package.json +0 -7
- package/dist/chunk-CIVPKHF4.js +0 -53
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
MEMORY_RESTART_EXIT_CODE,
|
|
5
|
+
consumedMemoryRestartMarker,
|
|
6
|
+
markMemoryRotation,
|
|
5
7
|
memoryRestartMarkerPath,
|
|
8
|
+
memoryRotationActive,
|
|
6
9
|
nextMemoryRestartMarker,
|
|
7
|
-
parseMemoryRestartMarker
|
|
8
|
-
|
|
10
|
+
parseMemoryRestartMarker,
|
|
11
|
+
pendingContinuation,
|
|
12
|
+
rotationCooldownActive
|
|
13
|
+
} from "./chunk-LKQTBL3R.js";
|
|
9
14
|
import {
|
|
10
15
|
PERMISSION_MODES,
|
|
11
16
|
PermissionModeSchema,
|
|
@@ -18,7 +23,7 @@ import {
|
|
|
18
23
|
redactConfig,
|
|
19
24
|
setProjectMcpServerDisabled,
|
|
20
25
|
updateProtectedFile
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-KQMKY7C5.js";
|
|
22
27
|
import {
|
|
23
28
|
AuditLogger,
|
|
24
29
|
appendUsageLog,
|
|
@@ -32,7 +37,7 @@ import {
|
|
|
32
37
|
resolveLanguage,
|
|
33
38
|
setUsageSession,
|
|
34
39
|
usageLogPath
|
|
35
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-ZJCAQXA7.js";
|
|
36
41
|
|
|
37
42
|
// src/models/types.ts
|
|
38
43
|
function modelContentText(content) {
|
|
@@ -75,12 +80,12 @@ function normalizeProviderError(error) {
|
|
|
75
80
|
|
|
76
81
|
// src/utils/async.ts
|
|
77
82
|
function withTimeout(promise, ms) {
|
|
78
|
-
return new Promise((
|
|
83
|
+
return new Promise((resolve32, reject) => {
|
|
79
84
|
let settled = false;
|
|
80
85
|
const timer = setTimeout(() => {
|
|
81
86
|
if (settled) return;
|
|
82
87
|
settled = true;
|
|
83
|
-
|
|
88
|
+
resolve32({ timedOut: true, value: void 0 });
|
|
84
89
|
}, ms);
|
|
85
90
|
timer.unref?.();
|
|
86
91
|
promise.then(
|
|
@@ -88,7 +93,7 @@ function withTimeout(promise, ms) {
|
|
|
88
93
|
if (!settled) {
|
|
89
94
|
settled = true;
|
|
90
95
|
clearTimeout(timer);
|
|
91
|
-
|
|
96
|
+
resolve32({ timedOut: false, value });
|
|
92
97
|
}
|
|
93
98
|
},
|
|
94
99
|
(error) => {
|
|
@@ -103,13 +108,13 @@ function withTimeout(promise, ms) {
|
|
|
103
108
|
}
|
|
104
109
|
function awaitWithSignal(promise, signal) {
|
|
105
110
|
signal.throwIfAborted();
|
|
106
|
-
return new Promise((
|
|
111
|
+
return new Promise((resolve32, reject) => {
|
|
107
112
|
const onAbort = () => reject(signal.reason);
|
|
108
113
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
109
114
|
promise.then(
|
|
110
115
|
(value) => {
|
|
111
116
|
signal.removeEventListener("abort", onAbort);
|
|
112
|
-
|
|
117
|
+
resolve32(value);
|
|
113
118
|
},
|
|
114
119
|
(error) => {
|
|
115
120
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -572,9 +577,7 @@ function redactSecrets(input, secrets) {
|
|
|
572
577
|
|
|
573
578
|
// src/init/project.ts
|
|
574
579
|
import { lstat, mkdir, readdir, readFile, realpath, writeFile } from "fs/promises";
|
|
575
|
-
import {
|
|
576
|
-
import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join, relative as relative2, sep as sep2 } from "path";
|
|
577
|
-
import { fileURLToPath } from "url";
|
|
580
|
+
import { basename, isAbsolute as isAbsolute2, join, relative as relative2, sep as sep2 } from "path";
|
|
578
581
|
var GENERATED_START = "<!-- flavor-code:start -->";
|
|
579
582
|
var GENERATED_END = "<!-- flavor-code:end -->";
|
|
580
583
|
var MAX_MANIFEST_BYTES = 1e6;
|
|
@@ -1025,29 +1028,8 @@ async function createExampleFlavorConfig(cwd) {
|
|
|
1025
1028
|
};
|
|
1026
1029
|
await writeFile(configPath, JSON.stringify(example, null, 2) + "\n");
|
|
1027
1030
|
}
|
|
1028
|
-
var ASTGRAPH_SRC_DIR = join(dirname2(fileURLToPath(import.meta.url)), "astgraph");
|
|
1029
|
-
async function copyAstgraphPlugin(cwd) {
|
|
1030
|
-
const pluginDir = join(cwd, ".flavor", "plugins", "astgraph");
|
|
1031
|
-
try {
|
|
1032
|
-
await lstat(pluginDir);
|
|
1033
|
-
return;
|
|
1034
|
-
} catch (error) {
|
|
1035
|
-
if (!isMissingPathError(error)) throw error;
|
|
1036
|
-
}
|
|
1037
|
-
copyDirectory(ASTGRAPH_SRC_DIR, pluginDir);
|
|
1038
|
-
}
|
|
1039
|
-
function copyDirectory(from, to) {
|
|
1040
|
-
mkdirSync(to, { recursive: true, mode: 448 });
|
|
1041
|
-
for (const entry2 of readdirSync(from, { withFileTypes: true })) {
|
|
1042
|
-
const source = join(from, entry2.name);
|
|
1043
|
-
const target = join(to, entry2.name);
|
|
1044
|
-
if (entry2.isDirectory()) copyDirectory(source, target);
|
|
1045
|
-
else if (entry2.isFile()) copyFileSync(source, target);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
1031
|
async function initializeFlavor(cwd) {
|
|
1049
1032
|
await ensureFlavorDirectories(cwd);
|
|
1050
|
-
await copyAstgraphPlugin(cwd);
|
|
1051
1033
|
await createExampleFlavorConfig(cwd);
|
|
1052
1034
|
const facts = await inspectProject(cwd);
|
|
1053
1035
|
const path = join(cwd, "FLAVOR.md");
|
|
@@ -1069,12 +1051,12 @@ async function initializeFlavor(cwd) {
|
|
|
1069
1051
|
|
|
1070
1052
|
// src/utils/version.ts
|
|
1071
1053
|
import { readFileSync } from "fs";
|
|
1072
|
-
import { dirname as
|
|
1073
|
-
import { fileURLToPath
|
|
1054
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1055
|
+
import { fileURLToPath } from "url";
|
|
1074
1056
|
var cached;
|
|
1075
1057
|
function packageVersion() {
|
|
1076
1058
|
if (cached !== void 0) return cached;
|
|
1077
|
-
let directory =
|
|
1059
|
+
let directory = dirname2(fileURLToPath(import.meta.url));
|
|
1078
1060
|
for (; ; ) {
|
|
1079
1061
|
try {
|
|
1080
1062
|
const manifest = JSON.parse(readFileSync(join2(directory, "package.json"), "utf8"));
|
|
@@ -1084,7 +1066,7 @@ function packageVersion() {
|
|
|
1084
1066
|
}
|
|
1085
1067
|
} catch {
|
|
1086
1068
|
}
|
|
1087
|
-
const parent =
|
|
1069
|
+
const parent = dirname2(directory);
|
|
1088
1070
|
if (parent === directory) break;
|
|
1089
1071
|
directory = parent;
|
|
1090
1072
|
}
|
|
@@ -1879,7 +1861,7 @@ function addText(turn, text, onNewLine = false) {
|
|
|
1879
1861
|
import { createHash, randomUUID } from "crypto";
|
|
1880
1862
|
import { execFile } from "child_process";
|
|
1881
1863
|
import { lstat as lstat2, mkdir as mkdir2, open, readFile as readFile2, readdir as readdir2, rename, rm, writeFile as writeFile2 } from "fs/promises";
|
|
1882
|
-
import { dirname as
|
|
1864
|
+
import { dirname as dirname3, join as join3, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
1883
1865
|
var EXCLUDED = /* @__PURE__ */ new Set([".git", ".flavor", "node_modules", "dist", "build", "release", ".worktrees"]);
|
|
1884
1866
|
var MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
1885
1867
|
var MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
@@ -1965,7 +1947,7 @@ var WorkspaceCheckpointStore = class {
|
|
|
1965
1947
|
for (const file of checkpoint.files) {
|
|
1966
1948
|
const target = contained(this.#workspace, file.path);
|
|
1967
1949
|
const object = objects.get(file.digest);
|
|
1968
|
-
await mkdir2(
|
|
1950
|
+
await mkdir2(dirname3(target), { recursive: true });
|
|
1969
1951
|
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
1970
1952
|
await writeFile2(temporary, object, { mode: file.mode });
|
|
1971
1953
|
await rename(temporary, target);
|
|
@@ -2037,7 +2019,7 @@ function contained(workspace, path) {
|
|
|
2037
2019
|
return absolute;
|
|
2038
2020
|
}
|
|
2039
2021
|
async function atomicJson(path, value) {
|
|
2040
|
-
await mkdir2(
|
|
2022
|
+
await mkdir2(dirname3(path), { recursive: true });
|
|
2041
2023
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
2042
2024
|
await writeFile2(temporary, `${JSON.stringify(value)}
|
|
2043
2025
|
`, { encoding: "utf8", mode: 384 });
|
|
@@ -2050,7 +2032,7 @@ function isCode(error, code) {
|
|
|
2050
2032
|
// src/session/tree.ts
|
|
2051
2033
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2052
2034
|
import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
2053
|
-
import { dirname as
|
|
2035
|
+
import { dirname as dirname4, join as join4, resolve as resolve3 } from "path";
|
|
2054
2036
|
var MAX_SESSION_HISTORY_NODES = 100;
|
|
2055
2037
|
var MAX_SESSION_HISTORY_CONTEXT_CHARS = 2e6;
|
|
2056
2038
|
var SessionHistory = class _SessionHistory {
|
|
@@ -2084,6 +2066,10 @@ var SessionHistory = class _SessionHistory {
|
|
|
2084
2066
|
get leafId() {
|
|
2085
2067
|
return this.#leafId;
|
|
2086
2068
|
}
|
|
2069
|
+
/** Cheap retained-node count for the rotation census (no cloning). */
|
|
2070
|
+
get nodeCount() {
|
|
2071
|
+
return this.#nodes.length;
|
|
2072
|
+
}
|
|
2087
2073
|
tree() {
|
|
2088
2074
|
return this.#nodes.map((node) => structuredClone(node));
|
|
2089
2075
|
}
|
|
@@ -2155,7 +2141,7 @@ var SessionHistory = class _SessionHistory {
|
|
|
2155
2141
|
leafId: this.#leafId,
|
|
2156
2142
|
nodes: this.#nodes
|
|
2157
2143
|
};
|
|
2158
|
-
await mkdir3(
|
|
2144
|
+
await mkdir3(dirname4(this.#path), { recursive: true });
|
|
2159
2145
|
const temporary = `${this.#path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
2160
2146
|
await writeFile3(temporary, `${JSON.stringify(document)}
|
|
2161
2147
|
`, { encoding: "utf8", mode: 384 });
|
|
@@ -2201,7 +2187,7 @@ function isCode2(error, code) {
|
|
|
2201
2187
|
// src/tools/search.ts
|
|
2202
2188
|
import { spawn } from "child_process";
|
|
2203
2189
|
import { open as open2, opendir, stat } from "fs/promises";
|
|
2204
|
-
import {
|
|
2190
|
+
import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve4, sep as sep4 } from "path";
|
|
2205
2191
|
import { rgPath as bundledRgPath } from "@vscode/ripgrep";
|
|
2206
2192
|
import createIgnore from "ignore";
|
|
2207
2193
|
import { z } from "zod";
|
|
@@ -2292,8 +2278,9 @@ var GlobInput = z.object({
|
|
|
2292
2278
|
limit: z.coerce.number().int().positive().max(1e5).nullable().optional()
|
|
2293
2279
|
});
|
|
2294
2280
|
var GrepInput = z.object({
|
|
2295
|
-
pattern: z.string().min(1),
|
|
2296
|
-
|
|
2281
|
+
pattern: z.string().min(1).describe("Regular expression without / delimiters, or literal text when fixedStrings=true."),
|
|
2282
|
+
fixedStrings: BooleanInput.describe("Match literal text, useful for Markdown links and paths containing regex punctuation.").nullable().optional(),
|
|
2283
|
+
path: z.string().min(1).describe("Workspace-relative file or directory. A file restricts the search to that file. Use glob for multiple filenames and JSON null for the workspace root.").nullable().optional(),
|
|
2297
2284
|
glob: z.string().min(1).nullable().optional(),
|
|
2298
2285
|
fileType: z.enum(FILE_TYPES).nullable().optional(),
|
|
2299
2286
|
includeIgnored: BooleanInput.describe("Include files excluded by .gitignore or .ignore. Git metadata remains excluded.").nullable().optional(),
|
|
@@ -2342,7 +2329,7 @@ function createGrepTool(workspace, options = {}) {
|
|
|
2342
2329
|
const resources = searchResources(options);
|
|
2343
2330
|
return {
|
|
2344
2331
|
name: "Grep",
|
|
2345
|
-
description: `Search workspace text with a regular expression. fileType must be one of: ${FILE_TYPES.join(", ")}.`,
|
|
2332
|
+
description: `Search workspace text with a regular expression or fixedStrings=true for literal text. Zero matches is a successful search, not a path error. fileType must be one of: ${FILE_TYPES.join(", ")}.`,
|
|
2346
2333
|
inputSchema: GrepInput,
|
|
2347
2334
|
paths: (input) => [scope(root, searchPath(input.path))],
|
|
2348
2335
|
summarize: (input) => {
|
|
@@ -2350,10 +2337,10 @@ function createGrepTool(workspace, options = {}) {
|
|
|
2350
2337
|
const where = path ? ` in ${path}` : "";
|
|
2351
2338
|
const kind = input.fileType ? ` [${input.fileType}]` : "";
|
|
2352
2339
|
const ignored2 = input.includeIgnored === true ? " (including ignored files)" : "";
|
|
2353
|
-
return `pattern:
|
|
2340
|
+
return `pattern: ${input.fixedStrings === true ? JSON.stringify(input.pattern) : `/${input.pattern}/`}${kind}${where}${ignored2}`;
|
|
2354
2341
|
},
|
|
2355
2342
|
execute: async (input, signal) => {
|
|
2356
|
-
const expression = new RegExp(input.pattern);
|
|
2343
|
+
const expression = new RegExp(input.fixedStrings === true ? input.pattern.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&") : input.pattern);
|
|
2357
2344
|
const limit = input.limit ?? options.defaultLimit ?? DEFAULT_RESULT_LIMIT;
|
|
2358
2345
|
const context = input.context ?? 0;
|
|
2359
2346
|
const start = await resolveGrepPath(root, searchPath(input.path));
|
|
@@ -2404,10 +2391,12 @@ async function rgGrep(root, start, input, context, signal, executable, argsPrefi
|
|
|
2404
2391
|
}
|
|
2405
2392
|
const args = ["--json", "--line-number", "--column", "--sort", "path", "--hidden", "--glob", "!.git", "--glob", "!.git/**", "--max-filesize", String(maxFileBytes), "--context", String(context)];
|
|
2406
2393
|
if (input.includeIgnored === true) args.push("--no-ignore");
|
|
2394
|
+
if (input.fixedStrings === true) args.push("--fixed-strings");
|
|
2407
2395
|
if (input.glob !== void 0 && input.glob !== null) args.push("--glob", input.glob);
|
|
2408
2396
|
else for (const extension of typeExtensions ?? []) args.push("--glob", `*${extension}`);
|
|
2409
2397
|
args.push("--regexp", input.pattern, "--", relative4(root, start) || ".");
|
|
2410
2398
|
const matches = [];
|
|
2399
|
+
const matcher = input.glob == null ? void 0 : globRegex(input.glob);
|
|
2411
2400
|
const ignoreLayers = input.includeIgnored === true ? [] : await collectIgnoreLayers(root, start, signal, resources, ignoreBudget);
|
|
2412
2401
|
const parser = delimitedParser(10, (record) => {
|
|
2413
2402
|
if (record.length === 0) return true;
|
|
@@ -2420,6 +2409,7 @@ async function rgGrep(root, start, input, context, signal, executable, argsPrefi
|
|
|
2420
2409
|
const lineWithEnding = tryDecodeUtf8(lineValue);
|
|
2421
2410
|
if (decodedPath === void 0 || lineWithEnding === void 0) return true;
|
|
2422
2411
|
const path = normalizePath(decodedPath).replace(/^\.\//, "");
|
|
2412
|
+
if (matcher !== void 0 && !matcher.test(path)) return true;
|
|
2423
2413
|
if (typeExtensions !== void 0 && !typeExtensions.some((extension) => path.endsWith(extension))) return true;
|
|
2424
2414
|
if (ignored(path, false, ignoreLayers)) return true;
|
|
2425
2415
|
const text = lineWithEnding.replace(/\r?\n$/, "");
|
|
@@ -2544,7 +2534,12 @@ async function nodeFiles(root, start, signal, accept, limit, resources, ignoreBu
|
|
|
2544
2534
|
}
|
|
2545
2535
|
ignores.length = ruleCount;
|
|
2546
2536
|
}
|
|
2547
|
-
await
|
|
2537
|
+
if ((await stat(start)).isFile()) {
|
|
2538
|
+
const path = normalizedRelative(root, start);
|
|
2539
|
+
if (!isGitMetadataPath(root, start) && (includeIgnored || !ignored(path, false, ignores)) && accept(path)) output.push(path);
|
|
2540
|
+
} else {
|
|
2541
|
+
await walk(start);
|
|
2542
|
+
}
|
|
2548
2543
|
return output;
|
|
2549
2544
|
}
|
|
2550
2545
|
async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget) {
|
|
@@ -2574,7 +2569,7 @@ async function collectIgnoreLayers(root, start, signal, resources, ignoreBudget)
|
|
|
2574
2569
|
if (!ignored(path, true, layers)) await walk(absolute);
|
|
2575
2570
|
}
|
|
2576
2571
|
}
|
|
2577
|
-
await walk(start);
|
|
2572
|
+
if (!(await stat(start)).isFile()) await walk(start);
|
|
2578
2573
|
return layers;
|
|
2579
2574
|
}
|
|
2580
2575
|
async function loadIgnoreFiles(directory, base, rules, budget, signal) {
|
|
@@ -2694,8 +2689,12 @@ async function resolveGrepPath(root, path = ".") {
|
|
|
2694
2689
|
const candidate = scope(root, path);
|
|
2695
2690
|
try {
|
|
2696
2691
|
const info = await stat(candidate);
|
|
2697
|
-
if (info.isFile())
|
|
2698
|
-
} catch {
|
|
2692
|
+
if (!info.isFile() && !info.isDirectory()) throw new Error("Search path must be a file or directory");
|
|
2693
|
+
} catch (error) {
|
|
2694
|
+
if (error.code === "ENOENT") {
|
|
2695
|
+
throw new Error(`Search path does not exist: ${path}. Use Glob to find the file; put filename patterns in glob, not path.`);
|
|
2696
|
+
}
|
|
2697
|
+
throw error;
|
|
2699
2698
|
}
|
|
2700
2699
|
return candidate;
|
|
2701
2700
|
}
|
|
@@ -3645,6 +3644,7 @@ var FlavorSession = class {
|
|
|
3645
3644
|
#pendingCoWorkStarts = [];
|
|
3646
3645
|
#activeSteeringDurableIds = /* @__PURE__ */ new Set();
|
|
3647
3646
|
#durableResumeStarted = false;
|
|
3647
|
+
#continuationResumeStarted = false;
|
|
3648
3648
|
#permissionBaseline;
|
|
3649
3649
|
#activeSubmission;
|
|
3650
3650
|
constructor(services) {
|
|
@@ -3665,6 +3665,10 @@ var FlavorSession = class {
|
|
|
3665
3665
|
queueSnapshot() {
|
|
3666
3666
|
return this.#queue.snapshot();
|
|
3667
3667
|
}
|
|
3668
|
+
/** True when start() admitted a persisted /loop or /goal continuation. */
|
|
3669
|
+
get rotationContinuationResumed() {
|
|
3670
|
+
return this.#continuationResumeStarted;
|
|
3671
|
+
}
|
|
3668
3672
|
clearQueue() {
|
|
3669
3673
|
const snapshot4 = this.#queue.clear();
|
|
3670
3674
|
for (const kind of ["steer", "followUp"]) {
|
|
@@ -3814,6 +3818,7 @@ var FlavorSession = class {
|
|
|
3814
3818
|
if (decision.additionalContext !== void 0) await this.#services.addContext?.(decision.additionalContext);
|
|
3815
3819
|
this.#started = true;
|
|
3816
3820
|
this.#resumeDurableQueue();
|
|
3821
|
+
this.#resumeRotationContinuation();
|
|
3817
3822
|
});
|
|
3818
3823
|
return this.#startPromise;
|
|
3819
3824
|
}
|
|
@@ -3896,7 +3901,8 @@ var FlavorSession = class {
|
|
|
3896
3901
|
skillNames,
|
|
3897
3902
|
this.#services.managedToolCommands().map(({ name }) => name)
|
|
3898
3903
|
);
|
|
3899
|
-
if (
|
|
3904
|
+
if (submission.continuation !== void 0) await this.#runContinuation(submission.continuation, controller.signal);
|
|
3905
|
+
else if (command !== null) await this.#dispatch(command, controller.signal);
|
|
3900
3906
|
else for await (const event of this.#services.run(prompt, controller.signal, {
|
|
3901
3907
|
getSteeringMessages: () => this.#drainMessages("steer", true).map((item) => item.text),
|
|
3902
3908
|
...decision.additionalContext === void 0 ? {} : { additionalContext: decision.additionalContext },
|
|
@@ -4154,6 +4160,33 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
4154
4160
|
});
|
|
4155
4161
|
}
|
|
4156
4162
|
}
|
|
4163
|
+
/**
|
|
4164
|
+
* After a heap rotation the relaunched process inherits the same session and
|
|
4165
|
+
* a marker continuation naming the /loop or /goal that was in flight. Resume
|
|
4166
|
+
* it once here so a multi-day autonomous run survives the rotation instead of
|
|
4167
|
+
* silently stopping at the boundary.
|
|
4168
|
+
*/
|
|
4169
|
+
#resumeRotationContinuation() {
|
|
4170
|
+
if (this.#continuationResumeStarted || this.#closed) return;
|
|
4171
|
+
const continuation = this.#services.continuation?.();
|
|
4172
|
+
if (continuation === void 0) return;
|
|
4173
|
+
const supported = continuation.kind === "loop" ? this.#services.resumeLoop !== void 0 : this.#services.resumeGoal !== void 0;
|
|
4174
|
+
if (!supported) return;
|
|
4175
|
+
this.#continuationResumeStarted = true;
|
|
4176
|
+
const displayText = `Resuming the interrupted ${continuation.kind} ${continuation.id} after a heap rotation.`;
|
|
4177
|
+
this.#notice(displayText);
|
|
4178
|
+
this.#enqueueNormalizedSubmission({ text: displayText, displayText, continuation }).catch((error) => {
|
|
4179
|
+
this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
async #runContinuation(continuation, signal) {
|
|
4183
|
+
const stream = continuation.kind === "loop" ? this.#services.resumeLoop?.(continuation.id, signal) : this.#services.resumeGoal?.(continuation.id, signal);
|
|
4184
|
+
if (stream === void 0) {
|
|
4185
|
+
this.#notice(`The runtime cannot resume the interrupted ${continuation.kind} ${continuation.id}; start it again with /${continuation.kind}.`);
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
for await (const event of stream) this.#services.output(event);
|
|
4189
|
+
}
|
|
4157
4190
|
#ackDurable(submission) {
|
|
4158
4191
|
if (submission.durableId === void 0) return;
|
|
4159
4192
|
this.#services.durableQueue?.ack(submission.durableId);
|
|
@@ -4200,9 +4233,9 @@ Address this now (for example run the required verification or fix), then finish
|
|
|
4200
4233
|
}
|
|
4201
4234
|
#waitForPlanningIdle(key) {
|
|
4202
4235
|
if (!this.#activePlanning.has(key)) return Promise.resolve();
|
|
4203
|
-
return new Promise((
|
|
4236
|
+
return new Promise((resolve32) => {
|
|
4204
4237
|
const check = () => {
|
|
4205
|
-
if (!this.#activePlanning.has(key))
|
|
4238
|
+
if (!this.#activePlanning.has(key)) resolve32();
|
|
4206
4239
|
else this.#planningIdleWaiters.add(check);
|
|
4207
4240
|
};
|
|
4208
4241
|
this.#planningIdleWaiters.add(check);
|
|
@@ -4321,8 +4354,8 @@ async function fetchLatestVersion(options = {}) {
|
|
|
4321
4354
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
4322
4355
|
const fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
|
|
4323
4356
|
let timer;
|
|
4324
|
-
const timeout = new Promise((
|
|
4325
|
-
timer = setTimeout(() =>
|
|
4357
|
+
const timeout = new Promise((resolve32) => {
|
|
4358
|
+
timer = setTimeout(() => resolve32(void 0), timeoutMs);
|
|
4326
4359
|
timer.unref?.();
|
|
4327
4360
|
});
|
|
4328
4361
|
try {
|
|
@@ -4447,7 +4480,7 @@ var PluginManifestSchema = z4.object({
|
|
|
4447
4480
|
}).strict();
|
|
4448
4481
|
|
|
4449
4482
|
// src/tools/shell.ts
|
|
4450
|
-
import { spawn as
|
|
4483
|
+
import { spawn as spawn3 } from "child_process";
|
|
4451
4484
|
import { existsSync as existsSync3 } from "fs";
|
|
4452
4485
|
import { basename as basename2, delimiter as delimiter2, isAbsolute as isAbsolute5, join as join5, relative as relative5, resolve as resolve6, sep as sep5 } from "path";
|
|
4453
4486
|
import { z as z6 } from "zod";
|
|
@@ -4490,7 +4523,7 @@ function resolveWindowsCommand(command, cwd, env) {
|
|
|
4490
4523
|
...pathValue.split(";").filter(Boolean).map((entry2) => resolve5(unquote(entry2), command))
|
|
4491
4524
|
];
|
|
4492
4525
|
for (const base of bases) {
|
|
4493
|
-
const candidates = extname(base).length > 0 ? [base] : [
|
|
4526
|
+
const candidates = extname(base).length > 0 ? [base] : [...pathExt.map((extension) => `${base}${extension}`), base];
|
|
4494
4527
|
for (const candidate of candidates) {
|
|
4495
4528
|
if (isRegularFile(candidate)) return resolve5(candidate);
|
|
4496
4529
|
}
|
|
@@ -4568,7 +4601,7 @@ function createJobTools(registry) {
|
|
|
4568
4601
|
inputSchema: IdInput,
|
|
4569
4602
|
paths: () => [],
|
|
4570
4603
|
execute: async (input, _signal, context) => {
|
|
4571
|
-
registry.kill(input.id, owner(context));
|
|
4604
|
+
await registry.kill(input.id, owner(context));
|
|
4572
4605
|
return registry.read(input.id, owner(context));
|
|
4573
4606
|
},
|
|
4574
4607
|
presentResult: (output) => jobPresentation("kill", output)
|
|
@@ -4608,11 +4641,51 @@ function owner(context) {
|
|
|
4608
4641
|
return context?.ownerId ?? context?.agent ?? "main";
|
|
4609
4642
|
}
|
|
4610
4643
|
|
|
4644
|
+
// src/utils/process-tree.ts
|
|
4645
|
+
import { spawn as spawn2 } from "child_process";
|
|
4646
|
+
async function terminateProcessTree(pid) {
|
|
4647
|
+
if (pid === void 0) return;
|
|
4648
|
+
if (process.platform === "win32") {
|
|
4649
|
+
await new Promise((resolve32) => {
|
|
4650
|
+
const child = spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], { shell: false, windowsHide: true });
|
|
4651
|
+
const finish = () => {
|
|
4652
|
+
clearTimeout(timer);
|
|
4653
|
+
resolve32();
|
|
4654
|
+
};
|
|
4655
|
+
const timer = setTimeout(finish, 5e3);
|
|
4656
|
+
timer.unref();
|
|
4657
|
+
child.once("error", finish);
|
|
4658
|
+
child.once("close", finish);
|
|
4659
|
+
});
|
|
4660
|
+
try {
|
|
4661
|
+
process.kill(pid);
|
|
4662
|
+
} catch {
|
|
4663
|
+
}
|
|
4664
|
+
} else {
|
|
4665
|
+
try {
|
|
4666
|
+
process.kill(-pid, "SIGTERM");
|
|
4667
|
+
} catch {
|
|
4668
|
+
try {
|
|
4669
|
+
process.kill(pid, "SIGTERM");
|
|
4670
|
+
} catch {
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
await new Promise((resolve32) => setTimeout(resolve32, 250));
|
|
4674
|
+
try {
|
|
4675
|
+
process.kill(-pid, "SIGKILL");
|
|
4676
|
+
} catch {
|
|
4677
|
+
try {
|
|
4678
|
+
process.kill(pid, "SIGKILL");
|
|
4679
|
+
} catch {
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4611
4685
|
// src/tools/shell.ts
|
|
4612
4686
|
var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
|
|
4613
4687
|
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 6e4;
|
|
4614
4688
|
var ELLIPSIS = "\u2026";
|
|
4615
|
-
var TERMINATION_GRACE_MS = 250;
|
|
4616
4689
|
var TERMINATION_FAILURE_MS = 5e3;
|
|
4617
4690
|
var STREAM_CLOSE_GRACE_MS = 500;
|
|
4618
4691
|
var ShellInput = z6.object({
|
|
@@ -4631,12 +4704,6 @@ function normalizeShellCommand(input) {
|
|
|
4631
4704
|
if ((executable === "cmd" || executable === "cmd.exe") && /^\/[ck]$/iu.test(args[0] ?? "")) {
|
|
4632
4705
|
return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
|
|
4633
4706
|
}
|
|
4634
|
-
if ((executable === "pwsh" || executable === "pwsh.exe" || executable === "powershell" || executable === "powershell.exe") && /^(?:-c|-command)$/iu.test(args[0] ?? "") && args.length > 1) {
|
|
4635
|
-
return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
|
|
4636
|
-
}
|
|
4637
|
-
if (/^(?:sh|bash|zsh|fish)$/iu.test(executable) && args[0] === "-c" && args[1] !== void 0) {
|
|
4638
|
-
return { command, args: [args[0], unquoteToken(args[1]), ...args.slice(2)] };
|
|
4639
|
-
}
|
|
4640
4707
|
if (!/\s/u.test(command) || containsShellSyntax(command)) return { command, args };
|
|
4641
4708
|
const tokens = tokenizeCommandLine(command);
|
|
4642
4709
|
if (tokens.length <= 1) return { command, args };
|
|
@@ -4754,7 +4821,8 @@ function shellInvocation(input, cwd, environment) {
|
|
|
4754
4821
|
const payload = serializeCmdPayload(input.args.slice(commandFlag + 1));
|
|
4755
4822
|
return {
|
|
4756
4823
|
command: resolved,
|
|
4757
|
-
args: [...input.args.slice(0, commandFlag + 1), `chcp 65001>nul & ${payload}`]
|
|
4824
|
+
args: [...input.args.slice(0, commandFlag + 1), `chcp 65001>nul & ${payload}`],
|
|
4825
|
+
windowsVerbatimArguments: true
|
|
4758
4826
|
};
|
|
4759
4827
|
}
|
|
4760
4828
|
}
|
|
@@ -4770,7 +4838,8 @@ function shellInvocation(input, cwd, environment) {
|
|
|
4770
4838
|
if (shell.kind === "posix") return { command: shell.command, args: ["-c", script] };
|
|
4771
4839
|
if (shell.kind === "cmd") return {
|
|
4772
4840
|
command: shell.command,
|
|
4773
|
-
args: ["/d", "/s", "/c", `chcp 65001>nul & ${script}`]
|
|
4841
|
+
args: ["/d", "/s", "/c", `chcp 65001>nul & ${script}`],
|
|
4842
|
+
windowsVerbatimArguments: true
|
|
4774
4843
|
};
|
|
4775
4844
|
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
4776
4845
|
return {
|
|
@@ -4793,8 +4862,8 @@ function executionEnvironmentCommand(environment, command, args) {
|
|
|
4793
4862
|
function structuredShellScript(shell, command, args) {
|
|
4794
4863
|
if (shell.kind === "posix") return [command, ...args].map(posixQuote).join(" ");
|
|
4795
4864
|
if (shell.kind === "cmd") return serializeCmdCommand([command, ...args]);
|
|
4796
|
-
const invocation = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}`;
|
|
4797
|
-
return `$ErrorActionPreference='Stop'; try { ${invocation}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 } } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 127 }`;
|
|
4865
|
+
const invocation = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map((arg) => /^-[A-Za-z][A-Za-z0-9-]*$/u.test(arg) ? arg : psQuote(arg)).join(" ")}` : ""}`;
|
|
4866
|
+
return `$ErrorActionPreference='Stop'; try { ${invocation}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 } } catch { [Console]::Error.WriteLine($_.Exception.Message); if ($_.Exception -is [System.Management.Automation.CommandNotFoundException]) { exit 127 } else { exit 1 } }`;
|
|
4798
4867
|
}
|
|
4799
4868
|
function posixQuote(value) {
|
|
4800
4869
|
return `'${value.replace(/'/gu, `'"'"'`)}'`;
|
|
@@ -4827,7 +4896,7 @@ ${result.stderr}`;
|
|
|
4827
4896
|
return {
|
|
4828
4897
|
kind: "command-not-found",
|
|
4829
4898
|
message: `Executable "${command}" was not found by the selected runtime shell.`,
|
|
4830
|
-
hint: "Use a command available on PATH, or pass an existing executable path without surrounding quotes."
|
|
4899
|
+
hint: "Use a command available on PATH, or pass an existing executable path without surrounding quotes. For an unavailable PowerShell cmdlet, check Get-Command and its module in the selected shell; do not keep retrying the same command."
|
|
4831
4900
|
};
|
|
4832
4901
|
}
|
|
4833
4902
|
if (code === "EACCES" || /(?:permission denied|access is denied|拒绝访问)/iu.test(detail)) {
|
|
@@ -4836,11 +4905,11 @@ ${result.stderr}`;
|
|
|
4836
4905
|
if (/(?:no such file or directory|cannot find (?:the )?path|system cannot find|系统找不到指定的路径)/iu.test(detail)) {
|
|
4837
4906
|
return { kind: "path-not-found", message: "The command referenced a path that does not exist." };
|
|
4838
4907
|
}
|
|
4839
|
-
if (/(?:syntax
|
|
4908
|
+
if (/(?:syntax\s*error|unexpected token|parsererror|unterminated (?:regexp|string)|语法错误)/iu.test(detail)) {
|
|
4840
4909
|
return {
|
|
4841
4910
|
kind: "shell-syntax",
|
|
4842
|
-
message: "The
|
|
4843
|
-
hint:
|
|
4911
|
+
message: "The command or interpreter rejected the syntax.",
|
|
4912
|
+
hint: 'Pass normal arguments through args. For JavaScript use command="node", args=["-e", "<complete script>"] without an extra shell or surrounding quotes. Legacy Windows PowerShell can strip quotes when forwarding native arguments.'
|
|
4844
4913
|
};
|
|
4845
4914
|
}
|
|
4846
4915
|
return {
|
|
@@ -4862,7 +4931,7 @@ function createShellTool(workspace, options = {}) {
|
|
|
4862
4931
|
}
|
|
4863
4932
|
return {
|
|
4864
4933
|
name: "Shell",
|
|
4865
|
-
description:
|
|
4934
|
+
description: 'Run a bounded command reliably inside the workspace. Pass a bare executable in command and one logical value per args entry, without adding surrounding shell quotes. For Node scripts use command="node", args=["-e", "<complete JavaScript>"]; do not wrap native commands in cmd /c or PowerShell -Command unless shell syntax is needed, since legacy PowerShell can strip embedded quotes. Use Grep for text searches and Read/Edit for file changes. stdout/stderr are returned verbatim and failures include a structured diagnostic. background=true returns a managed job id.',
|
|
4866
4935
|
inputSchema: ShellInput,
|
|
4867
4936
|
paths: (input) => [workingDirectory(root, input.cwd ?? void 0)],
|
|
4868
4937
|
summarize: (input) => [input.command, ...input.args].join(" "),
|
|
@@ -4962,7 +5031,7 @@ async function executeShell(root, input, cancellation, maxBytes, environment = p
|
|
|
4962
5031
|
return;
|
|
4963
5032
|
}
|
|
4964
5033
|
const invocation = shellInvocation(input, cwd, environment);
|
|
4965
|
-
const child =
|
|
5034
|
+
const child = spawn3(invocation.command, invocation.args, {
|
|
4966
5035
|
cwd,
|
|
4967
5036
|
shell: false,
|
|
4968
5037
|
windowsHide: true,
|
|
@@ -4989,7 +5058,7 @@ async function executeShell(root, input, cancellation, maxBytes, environment = p
|
|
|
4989
5058
|
void finishResolve(observedExitCode ?? child.exitCode, observedSignal ?? child.signalCode);
|
|
4990
5059
|
return;
|
|
4991
5060
|
}
|
|
4992
|
-
termination =
|
|
5061
|
+
termination = terminateProcessTree(child.pid);
|
|
4993
5062
|
terminalTimer = setTimeout(() => finishReject(new Error(`Process did not close after ${reason} termination`)), TERMINATION_FAILURE_MS);
|
|
4994
5063
|
terminalTimer.unref();
|
|
4995
5064
|
};
|
|
@@ -5149,52 +5218,6 @@ function workingDirectory(root, cwd = ".") {
|
|
|
5149
5218
|
if (delta === ".." || delta.startsWith(`..${sep5}`) || isAbsolute5(delta)) throw new Error("Working directory is outside the workspace");
|
|
5150
5219
|
return candidate;
|
|
5151
5220
|
}
|
|
5152
|
-
async function terminateTree(pid) {
|
|
5153
|
-
if (pid === void 0) return;
|
|
5154
|
-
if (process.platform === "win32") {
|
|
5155
|
-
await waitForProcess(spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], { shell: false, windowsHide: true }));
|
|
5156
|
-
try {
|
|
5157
|
-
process.kill(pid);
|
|
5158
|
-
} catch {
|
|
5159
|
-
}
|
|
5160
|
-
} else {
|
|
5161
|
-
try {
|
|
5162
|
-
process.kill(-pid, "SIGTERM");
|
|
5163
|
-
} catch {
|
|
5164
|
-
try {
|
|
5165
|
-
process.kill(pid, "SIGTERM");
|
|
5166
|
-
} catch {
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
await delay(TERMINATION_GRACE_MS);
|
|
5170
|
-
try {
|
|
5171
|
-
process.kill(-pid, "SIGKILL");
|
|
5172
|
-
} catch {
|
|
5173
|
-
try {
|
|
5174
|
-
process.kill(pid, "SIGKILL");
|
|
5175
|
-
} catch {
|
|
5176
|
-
}
|
|
5177
|
-
}
|
|
5178
|
-
}
|
|
5179
|
-
}
|
|
5180
|
-
function waitForProcess(child) {
|
|
5181
|
-
return new Promise((resolvePromise) => {
|
|
5182
|
-
let settled = false;
|
|
5183
|
-
const finish = () => {
|
|
5184
|
-
if (settled) return;
|
|
5185
|
-
settled = true;
|
|
5186
|
-
clearTimeout(timer);
|
|
5187
|
-
resolvePromise();
|
|
5188
|
-
};
|
|
5189
|
-
const timer = setTimeout(finish, TERMINATION_FAILURE_MS);
|
|
5190
|
-
timer.unref();
|
|
5191
|
-
child.once("error", finish);
|
|
5192
|
-
child.once("close", finish);
|
|
5193
|
-
});
|
|
5194
|
-
}
|
|
5195
|
-
function delay(milliseconds) {
|
|
5196
|
-
return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
5197
|
-
}
|
|
5198
5221
|
function decodeOutput(buffer) {
|
|
5199
5222
|
const encoding = outputEncoding(buffer);
|
|
5200
5223
|
return new TextDecoder(encoding).decode(buffer);
|
|
@@ -5478,7 +5501,7 @@ function errorMessage(error) {
|
|
|
5478
5501
|
}
|
|
5479
5502
|
|
|
5480
5503
|
// src/utils/crash-guard.ts
|
|
5481
|
-
import { mkdirSync
|
|
5504
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
5482
5505
|
import { join as join7 } from "path";
|
|
5483
5506
|
var installed = false;
|
|
5484
5507
|
var crashing = false;
|
|
@@ -5518,7 +5541,7 @@ function installCrashGuard(options = {}) {
|
|
|
5518
5541
|
const writeCrashLog = (kind, detail) => {
|
|
5519
5542
|
try {
|
|
5520
5543
|
const dir = crashLogDir();
|
|
5521
|
-
|
|
5544
|
+
mkdirSync(dir, { recursive: true });
|
|
5522
5545
|
const path = join7(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
5523
5546
|
writeFileSync(path, [
|
|
5524
5547
|
`kind: ${kind}`,
|
|
@@ -5573,7 +5596,7 @@ ${reason.stack ?? ""}`;
|
|
|
5573
5596
|
}
|
|
5574
5597
|
|
|
5575
5598
|
// src/execution/docker.ts
|
|
5576
|
-
import { spawn as
|
|
5599
|
+
import { spawn as spawn4 } from "child_process";
|
|
5577
5600
|
import { relative as relative7, resolve as resolve8, sep as sep7, win32 } from "path";
|
|
5578
5601
|
function buildDockerInvocation(options) {
|
|
5579
5602
|
const workspace = options.workspace;
|
|
@@ -5632,7 +5655,7 @@ function runDockerInvocation(invocation, signal, timeoutMs, maxOutputBytes = DEF
|
|
|
5632
5655
|
resolvePromise({ exitCode: null, signal: null, stdout: "", stderr: "", terminationReason: "cancelled" });
|
|
5633
5656
|
return;
|
|
5634
5657
|
}
|
|
5635
|
-
const child =
|
|
5658
|
+
const child = spawn4(invocation.command, invocation.args, { windowsHide: true, shell: false });
|
|
5636
5659
|
const stdout = new BoundedUtf8Output(maxOutputBytes);
|
|
5637
5660
|
const stderr = new BoundedUtf8Output(maxOutputBytes);
|
|
5638
5661
|
let reason = null;
|
|
@@ -5741,13 +5764,13 @@ import { createHash as createHash3 } from "crypto";
|
|
|
5741
5764
|
import { Socket as Socket2 } from "net";
|
|
5742
5765
|
import { tmpdir } from "os";
|
|
5743
5766
|
import { join as join9 } from "path";
|
|
5744
|
-
import { spawn as
|
|
5767
|
+
import { spawn as spawn5 } from "child_process";
|
|
5745
5768
|
|
|
5746
5769
|
// src/pals/broker.ts
|
|
5747
5770
|
import { createHash as createHash2, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
|
|
5748
5771
|
import { chmod as chmod2, lstat as lstat4, mkdir as mkdir6, unlink as unlink3 } from "fs/promises";
|
|
5749
5772
|
import { createServer, Socket } from "net";
|
|
5750
|
-
import { dirname as
|
|
5773
|
+
import { dirname as dirname6 } from "path";
|
|
5751
5774
|
|
|
5752
5775
|
// src/pals/auth.ts
|
|
5753
5776
|
import { randomBytes, randomUUID as randomUUID3 } from "crypto";
|
|
@@ -5798,7 +5821,7 @@ function validatePalAuthToken(token) {
|
|
|
5798
5821
|
// src/pals/lifecycle.ts
|
|
5799
5822
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5800
5823
|
import { mkdir as mkdir5, open as open4, readFile as readFile6, rename as rename3, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
5801
|
-
import { dirname as
|
|
5824
|
+
import { dirname as dirname5 } from "path";
|
|
5802
5825
|
function defaultProcessAlive(pid) {
|
|
5803
5826
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
5804
5827
|
try {
|
|
@@ -5819,7 +5842,7 @@ function parseMetadata(value) {
|
|
|
5819
5842
|
}
|
|
5820
5843
|
async function acquirePalFileLock(options) {
|
|
5821
5844
|
if (await options.endpointLive()) return void 0;
|
|
5822
|
-
await mkdir5(
|
|
5845
|
+
await mkdir5(dirname5(options.path), { recursive: true, mode: 448 });
|
|
5823
5846
|
const pid = options.pid ?? process.pid;
|
|
5824
5847
|
const now = options.now ?? Date.now;
|
|
5825
5848
|
const nonce = randomUUID4();
|
|
@@ -5883,12 +5906,12 @@ var BrokerStateError = class extends Error {
|
|
|
5883
5906
|
};
|
|
5884
5907
|
var WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
5885
5908
|
function canConnect(address) {
|
|
5886
|
-
return new Promise((
|
|
5909
|
+
return new Promise((resolve32) => {
|
|
5887
5910
|
const socket = new Socket();
|
|
5888
5911
|
const finish = (connected) => {
|
|
5889
5912
|
socket.removeAllListeners();
|
|
5890
5913
|
socket.destroy();
|
|
5891
|
-
|
|
5914
|
+
resolve32(connected);
|
|
5892
5915
|
};
|
|
5893
5916
|
socket.once("connect", () => finish(true));
|
|
5894
5917
|
socket.once("error", () => finish(false));
|
|
@@ -5937,7 +5960,7 @@ var PalBrokerServer = class {
|
|
|
5937
5960
|
this.authToken ??= await loadOrCreatePalAuthToken(this.authHome === void 0 ? {} : { home: this.authHome });
|
|
5938
5961
|
try {
|
|
5939
5962
|
await this.prepareAddress();
|
|
5940
|
-
await new Promise((
|
|
5963
|
+
await new Promise((resolve32, reject) => {
|
|
5941
5964
|
const onError = (error) => {
|
|
5942
5965
|
this.server.off("listening", onListening);
|
|
5943
5966
|
reject(error);
|
|
@@ -5946,7 +5969,7 @@ var PalBrokerServer = class {
|
|
|
5946
5969
|
this.server.off("error", onError);
|
|
5947
5970
|
this.started = true;
|
|
5948
5971
|
this.ownsAddress = true;
|
|
5949
|
-
|
|
5972
|
+
resolve32();
|
|
5950
5973
|
};
|
|
5951
5974
|
this.server.once("error", onError);
|
|
5952
5975
|
this.server.once("listening", onListening);
|
|
@@ -5974,7 +5997,7 @@ var PalBrokerServer = class {
|
|
|
5974
5997
|
const ownsAddress = this.ownsAddress;
|
|
5975
5998
|
for (const connection of this.connections) connection.socket.destroy();
|
|
5976
5999
|
if (this.started) {
|
|
5977
|
-
await new Promise((
|
|
6000
|
+
await new Promise((resolve32) => this.server.close(() => resolve32()));
|
|
5978
6001
|
this.started = false;
|
|
5979
6002
|
}
|
|
5980
6003
|
this.ownsAddress = false;
|
|
@@ -5983,7 +6006,7 @@ var PalBrokerServer = class {
|
|
|
5983
6006
|
}
|
|
5984
6007
|
async prepareAddress() {
|
|
5985
6008
|
if (isWindowsPipeAddress(this.address, this.platform)) return;
|
|
5986
|
-
const parent =
|
|
6009
|
+
const parent = dirname6(this.address);
|
|
5987
6010
|
await mkdir6(parent, { recursive: true, mode: 448 });
|
|
5988
6011
|
await chmod2(parent, 448);
|
|
5989
6012
|
this.endpointLock = await acquirePalFileLock({
|
|
@@ -5993,8 +6016,8 @@ var PalBrokerServer = class {
|
|
|
5993
6016
|
});
|
|
5994
6017
|
if (this.endpointLock === void 0) throw new Error(`Pals endpoint '${this.address}' is active or owned`);
|
|
5995
6018
|
try {
|
|
5996
|
-
const
|
|
5997
|
-
if (!
|
|
6019
|
+
const stat12 = await lstat4(this.address);
|
|
6020
|
+
if (!stat12.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
|
|
5998
6021
|
await unlink3(this.address);
|
|
5999
6022
|
} catch (error) {
|
|
6000
6023
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -6007,8 +6030,8 @@ var PalBrokerServer = class {
|
|
|
6007
6030
|
}
|
|
6008
6031
|
async unlinkKnownSocket() {
|
|
6009
6032
|
try {
|
|
6010
|
-
const
|
|
6011
|
-
if (
|
|
6033
|
+
const stat12 = await lstat4(this.address);
|
|
6034
|
+
if (stat12.isSocket()) await unlink3(this.address);
|
|
6012
6035
|
} catch (error) {
|
|
6013
6036
|
if (error.code !== "ENOENT") throw error;
|
|
6014
6037
|
}
|
|
@@ -6642,16 +6665,16 @@ var PalBrokerState = class {
|
|
|
6642
6665
|
|
|
6643
6666
|
// src/pals/broker-cli.ts
|
|
6644
6667
|
var startupAttempts = /* @__PURE__ */ new Map();
|
|
6645
|
-
function
|
|
6646
|
-
return new Promise((
|
|
6668
|
+
function delay(milliseconds) {
|
|
6669
|
+
return new Promise((resolve32) => setTimeout(resolve32, milliseconds));
|
|
6647
6670
|
}
|
|
6648
6671
|
function canConnect2(address) {
|
|
6649
|
-
return new Promise((
|
|
6672
|
+
return new Promise((resolve32) => {
|
|
6650
6673
|
const socket = new Socket2();
|
|
6651
6674
|
const finish = (connected) => {
|
|
6652
6675
|
socket.removeAllListeners();
|
|
6653
6676
|
socket.destroy();
|
|
6654
|
-
|
|
6677
|
+
resolve32(connected);
|
|
6655
6678
|
};
|
|
6656
6679
|
socket.once("connect", () => finish(true));
|
|
6657
6680
|
socket.once("error", () => finish(false));
|
|
@@ -6674,7 +6697,7 @@ async function spawnPalBrokerProcess(options) {
|
|
|
6674
6697
|
const lock = await acquirePalFileLock({ path: lockPath, endpointLive: () => canConnect2(address) });
|
|
6675
6698
|
if (lock === void 0) return;
|
|
6676
6699
|
try {
|
|
6677
|
-
const child =
|
|
6700
|
+
const child = spawn5(options.executable, [...options.args], {
|
|
6678
6701
|
detached: true,
|
|
6679
6702
|
stdio: "ignore",
|
|
6680
6703
|
windowsHide: true,
|
|
@@ -6682,7 +6705,7 @@ async function spawnPalBrokerProcess(options) {
|
|
|
6682
6705
|
});
|
|
6683
6706
|
child.unref();
|
|
6684
6707
|
const deadline = Date.now() + 5e3;
|
|
6685
|
-
while (Date.now() < deadline && !await canConnect2(address)) await
|
|
6708
|
+
while (Date.now() < deadline && !await canConnect2(address)) await delay(25);
|
|
6686
6709
|
if (!await canConnect2(address)) throw new Error(`Pals broker process did not become ready at '${address}'`);
|
|
6687
6710
|
} finally {
|
|
6688
6711
|
await lock.release();
|
|
@@ -6694,8 +6717,8 @@ async function runPalBroker(options) {
|
|
|
6694
6717
|
const sweepIntervalMs = options.sweepIntervalMs ?? 5e3;
|
|
6695
6718
|
const idleShutdownMs = options.idleShutdownMs ?? 3e4;
|
|
6696
6719
|
let resolveClosed;
|
|
6697
|
-
const closed = new Promise((
|
|
6698
|
-
resolveClosed =
|
|
6720
|
+
const closed = new Promise((resolve32) => {
|
|
6721
|
+
resolveClosed = resolve32;
|
|
6699
6722
|
});
|
|
6700
6723
|
let closing;
|
|
6701
6724
|
const timer = setInterval(() => {
|
|
@@ -6729,17 +6752,17 @@ async function ensureOnce(options) {
|
|
|
6729
6752
|
const deadline = Date.now() + timeoutMs;
|
|
6730
6753
|
do {
|
|
6731
6754
|
if (await canConnect2(options.address)) return;
|
|
6732
|
-
await
|
|
6755
|
+
await delay(retryDelayMs);
|
|
6733
6756
|
} while (Date.now() < deadline);
|
|
6734
6757
|
throw new Error(`Timed out waiting for pals broker at '${options.address}'`);
|
|
6735
6758
|
}
|
|
6736
6759
|
|
|
6737
6760
|
// src/production.ts
|
|
6738
|
-
import { readFile as
|
|
6739
|
-
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as
|
|
6761
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
6762
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
6740
6763
|
import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
|
|
6741
6764
|
import { homedir as homedir3, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
6742
|
-
import { basename as basename7, dirname as
|
|
6765
|
+
import { basename as basename7, dirname as dirname17, join as join30, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
|
|
6743
6766
|
|
|
6744
6767
|
// src/agent/subagents.ts
|
|
6745
6768
|
import { z as z9 } from "zod";
|
|
@@ -7159,7 +7182,7 @@ var TaskPlanSchema = z10.object({
|
|
|
7159
7182
|
}
|
|
7160
7183
|
dependencies.add(dependency);
|
|
7161
7184
|
}
|
|
7162
|
-
if (task.status === "completed") {
|
|
7185
|
+
if (task.status === "completed" || task.status === "in_progress") {
|
|
7163
7186
|
const incomplete = task.dependencies.find((dependency) => plan.tasks.find((candidate) => candidate.id === dependency)?.status !== "completed");
|
|
7164
7187
|
if (incomplete !== void 0) {
|
|
7165
7188
|
context.addIssue({
|
|
@@ -7188,7 +7211,7 @@ function updatePlanTask(plan, input) {
|
|
|
7188
7211
|
if (!isValidTransition(previousStatus, update.status)) {
|
|
7189
7212
|
throw new Error(`Invalid task transition for ${update.taskId}: ${previousStatus} -> ${update.status}`);
|
|
7190
7213
|
}
|
|
7191
|
-
if (update.status === "completed") {
|
|
7214
|
+
if (update.status === "completed" || update.status === "in_progress") {
|
|
7192
7215
|
const incomplete = current.tasks[index].dependencies.find((dependency) => current.tasks.find((task) => task.id === dependency)?.status !== "completed");
|
|
7193
7216
|
if (incomplete !== void 0) throw new Error(`Task ${update.taskId} has an incomplete dependency: ${incomplete}`);
|
|
7194
7217
|
}
|
|
@@ -7213,6 +7236,7 @@ function isValidTransition(from, to) {
|
|
|
7213
7236
|
if (from === "in_progress") {
|
|
7214
7237
|
return to === "completed" || to === "failed" || to === "blocked" || to === "cancelled";
|
|
7215
7238
|
}
|
|
7239
|
+
if (from === "blocked" || from === "failed") return to === "pending" || to === "in_progress" || to === "cancelled";
|
|
7216
7240
|
return false;
|
|
7217
7241
|
}
|
|
7218
7242
|
function normalizeAbandonedPlan(plan) {
|
|
@@ -7696,6 +7720,18 @@ var ContextManager = class _ContextManager {
|
|
|
7696
7720
|
estimatedTokens() {
|
|
7697
7721
|
return estimateMessageTokens(this.messagesForModel());
|
|
7698
7722
|
}
|
|
7723
|
+
/** Retained sizes for the rotation census: what this context holds right now. */
|
|
7724
|
+
census() {
|
|
7725
|
+
const chars = (messages) => messages.reduce((sum, entry2) => sum + modelContentText(entry2.content).length, 0);
|
|
7726
|
+
return {
|
|
7727
|
+
messages: this.#messages.length,
|
|
7728
|
+
messageChars: chars(this.#messages),
|
|
7729
|
+
visibilityEntries: this.#visibilityLog.length,
|
|
7730
|
+
visibilityChars: this.#visibilityLog.reduce((sum, record) => sum + record.content.length, 0),
|
|
7731
|
+
transientEntries: this.#activeTransientSystem.size,
|
|
7732
|
+
transientChars: chars([...this.#activeTransientSystem.values()])
|
|
7733
|
+
};
|
|
7734
|
+
}
|
|
7699
7735
|
needsCompaction() {
|
|
7700
7736
|
if (modelVisibleText(this.messagesForModel()).length >= this.#compactAtChars) return true;
|
|
7701
7737
|
return calculateContextPressure(this.#currentTokenUsage(), this.#compaction).shouldAutoCompact;
|
|
@@ -8085,7 +8121,7 @@ function providerValidMessages(input) {
|
|
|
8085
8121
|
// src/context/workspace-instructions.ts
|
|
8086
8122
|
import { createHash as createHash5 } from "crypto";
|
|
8087
8123
|
import { lstat as lstat5, readFile as readFile7, realpath as realpath2 } from "fs/promises";
|
|
8088
|
-
import { dirname as
|
|
8124
|
+
import { dirname as dirname7, isAbsolute as isAbsolute7, relative as relative8, resolve as resolve9, sep as sep8 } from "path";
|
|
8089
8125
|
var NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
8090
8126
|
var ROOT_NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
8091
8127
|
var WorkspaceInstructions = class {
|
|
@@ -8113,7 +8149,7 @@ var WorkspaceInstructions = class {
|
|
|
8113
8149
|
const depth = depthOf(this.#root, current);
|
|
8114
8150
|
directories.set(current, depth);
|
|
8115
8151
|
if (current === this.#root) break;
|
|
8116
|
-
current =
|
|
8152
|
+
current = dirname7(current);
|
|
8117
8153
|
}
|
|
8118
8154
|
}
|
|
8119
8155
|
const ordered = [...directories].map(([path, depth]) => ({ path, depth })).sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
|
|
@@ -8187,9 +8223,9 @@ ${document.content}
|
|
|
8187
8223
|
};
|
|
8188
8224
|
async function directoryForPath(path) {
|
|
8189
8225
|
try {
|
|
8190
|
-
return (await lstat5(path)).isDirectory() ? path :
|
|
8226
|
+
return (await lstat5(path)).isDirectory() ? path : dirname7(path);
|
|
8191
8227
|
} catch {
|
|
8192
|
-
return
|
|
8228
|
+
return dirname7(path);
|
|
8193
8229
|
}
|
|
8194
8230
|
}
|
|
8195
8231
|
function depthOf(root, path) {
|
|
@@ -8264,8 +8300,75 @@ function reportProgress(callback, percentage) {
|
|
|
8264
8300
|
}
|
|
8265
8301
|
}
|
|
8266
8302
|
|
|
8267
|
-
// src/
|
|
8303
|
+
// src/utils/heap.ts
|
|
8268
8304
|
import { getHeapStatistics } from "v8";
|
|
8305
|
+
import { totalmem } from "os";
|
|
8306
|
+
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
8307
|
+
var MEMORY_ROTATION_HEAP_RATIO = 0.6;
|
|
8308
|
+
var MEMORY_PRESSURE_RSS_RATIO = 0.75;
|
|
8309
|
+
var MEMORY_ROTATION_RSS_RATIO = 0.6;
|
|
8310
|
+
var ROTATION_PRECHECK_RATIO = 0.45;
|
|
8311
|
+
function readHeap() {
|
|
8312
|
+
const heapLimit = getHeapStatistics().heap_size_limit;
|
|
8313
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8314
|
+
return { heapUsed, heapLimit, ratio: heapLimit > 0 ? heapUsed / heapLimit : 0 };
|
|
8315
|
+
}
|
|
8316
|
+
function forceFullGc() {
|
|
8317
|
+
const gc = globalThis.gc;
|
|
8318
|
+
if (typeof gc !== "function") return false;
|
|
8319
|
+
gc();
|
|
8320
|
+
gc();
|
|
8321
|
+
return true;
|
|
8322
|
+
}
|
|
8323
|
+
function ratioOf(heapLimit, heapUsed) {
|
|
8324
|
+
return heapLimit > 0 ? heapUsed / heapLimit : 0;
|
|
8325
|
+
}
|
|
8326
|
+
function effectiveMemoryLimit() {
|
|
8327
|
+
const physical = totalmem();
|
|
8328
|
+
const constrained = process.constrainedMemory?.() ?? 0;
|
|
8329
|
+
return constrained > 0 ? Math.min(physical, constrained) : physical;
|
|
8330
|
+
}
|
|
8331
|
+
function withRss(reading, gcVerified) {
|
|
8332
|
+
const usage = process.memoryUsage();
|
|
8333
|
+
const memoryLimit = effectiveMemoryLimit();
|
|
8334
|
+
return {
|
|
8335
|
+
...reading,
|
|
8336
|
+
gcVerified,
|
|
8337
|
+
rss: usage.rss,
|
|
8338
|
+
memoryLimit,
|
|
8339
|
+
rssRatio: ratioOf(memoryLimit, usage.rss),
|
|
8340
|
+
external: usage.external,
|
|
8341
|
+
arrayBuffers: usage.arrayBuffers
|
|
8342
|
+
};
|
|
8343
|
+
}
|
|
8344
|
+
function verifiedHeapPressure(threshold) {
|
|
8345
|
+
const raw = readHeap();
|
|
8346
|
+
const before = withRss(raw, false);
|
|
8347
|
+
const rssThreshold = threshold >= MEMORY_PRESSURE_HEAP_RATIO ? MEMORY_PRESSURE_RSS_RATIO : threshold;
|
|
8348
|
+
if ((raw.heapLimit <= 0 || raw.ratio < threshold) && (before.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
8349
|
+
if (!forceFullGc()) return before;
|
|
8350
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8351
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
8352
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
8353
|
+
if (ratio < threshold && (verified.rssRatio ?? 0) < rssThreshold) return void 0;
|
|
8354
|
+
return verified;
|
|
8355
|
+
}
|
|
8356
|
+
function heapRotationNeeded() {
|
|
8357
|
+
const raw = readHeap();
|
|
8358
|
+
const before = withRss(raw, false);
|
|
8359
|
+
if ((raw.heapLimit <= 0 || raw.ratio < ROTATION_PRECHECK_RATIO) && (before.rssRatio ?? 0) < ROTATION_PRECHECK_RATIO) return void 0;
|
|
8360
|
+
if (!forceFullGc()) {
|
|
8361
|
+
return raw.ratio >= MEMORY_PRESSURE_HEAP_RATIO || (before.rssRatio ?? 0) >= MEMORY_PRESSURE_RSS_RATIO ? before : void 0;
|
|
8362
|
+
}
|
|
8363
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
8364
|
+
const ratio = ratioOf(raw.heapLimit, heapUsed);
|
|
8365
|
+
const verified = withRss({ heapUsed, heapLimit: raw.heapLimit, ratio, verifiedRatio: ratio }, true);
|
|
8366
|
+
if (ratio < MEMORY_ROTATION_HEAP_RATIO && (verified.rssRatio ?? 0) < MEMORY_ROTATION_RSS_RATIO) return void 0;
|
|
8367
|
+
return verified;
|
|
8368
|
+
}
|
|
8369
|
+
function heapSnapshotWarranted(_reading) {
|
|
8370
|
+
return process.env.FLAVOR_HEAP_SNAPSHOT_ON_ROTATE === "1";
|
|
8371
|
+
}
|
|
8269
8372
|
|
|
8270
8373
|
// src/models/structured.ts
|
|
8271
8374
|
import { z as z11 } from "zod";
|
|
@@ -8288,8 +8391,8 @@ function withStructuredOutput(options) {
|
|
|
8288
8391
|
if (retry.backoffMs.length < retry.maxRetries) {
|
|
8289
8392
|
throw new Error("Structured output backoffMs must provide one delay per retry");
|
|
8290
8393
|
}
|
|
8291
|
-
for (const
|
|
8292
|
-
if (!Number.isSafeInteger(
|
|
8394
|
+
for (const delay2 of retry.backoffMs.slice(0, retry.maxRetries)) {
|
|
8395
|
+
if (!Number.isSafeInteger(delay2) || delay2 < 0) {
|
|
8293
8396
|
throw new Error("Structured output retry delays must be non-negative integers");
|
|
8294
8397
|
}
|
|
8295
8398
|
}
|
|
@@ -8650,10 +8753,10 @@ function sanitize(message2, secrets) {
|
|
|
8650
8753
|
}
|
|
8651
8754
|
function waitForRetry(delayMs, signal) {
|
|
8652
8755
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
8653
|
-
return new Promise((
|
|
8756
|
+
return new Promise((resolve32, reject) => {
|
|
8654
8757
|
const timer = setTimeout(() => {
|
|
8655
8758
|
signal?.removeEventListener("abort", onAbort);
|
|
8656
|
-
|
|
8759
|
+
resolve32();
|
|
8657
8760
|
}, delayMs);
|
|
8658
8761
|
const onAbort = () => {
|
|
8659
8762
|
clearTimeout(timer);
|
|
@@ -8684,7 +8787,6 @@ function envMaxIterations() {
|
|
|
8684
8787
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
8685
8788
|
return parsed;
|
|
8686
8789
|
}
|
|
8687
|
-
var MEMORY_PRESSURE_HEAP_RATIO = 0.8;
|
|
8688
8790
|
function memoryPressureError(heapUsed, heapLimit) {
|
|
8689
8791
|
if (heapLimit <= 0 || heapUsed < heapLimit * MEMORY_PRESSURE_HEAP_RATIO) return void 0;
|
|
8690
8792
|
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
@@ -8694,7 +8796,12 @@ function memoryPressureError(heapUsed, heapLimit) {
|
|
|
8694
8796
|
};
|
|
8695
8797
|
}
|
|
8696
8798
|
function currentMemoryPressure() {
|
|
8697
|
-
|
|
8799
|
+
const reading = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
8800
|
+
if (reading === void 0) return void 0;
|
|
8801
|
+
return memoryPressureError(reading.heapUsed, reading.heapLimit) ?? {
|
|
8802
|
+
code: "memory_pressure",
|
|
8803
|
+
message: `Process RSS reached ${Math.round((reading.rssRatio ?? 0) * 100)}% of the available memory limit; stopping before system OOM`
|
|
8804
|
+
};
|
|
8698
8805
|
}
|
|
8699
8806
|
var AgentLoop = class {
|
|
8700
8807
|
#options;
|
|
@@ -8724,6 +8831,15 @@ var AgentLoop = class {
|
|
|
8724
8831
|
get iterationLimitMode() {
|
|
8725
8832
|
return this.#iterationLimitMode;
|
|
8726
8833
|
}
|
|
8834
|
+
get maxIterations() {
|
|
8835
|
+
return this.#options.maxIterations;
|
|
8836
|
+
}
|
|
8837
|
+
get softLimitFactor() {
|
|
8838
|
+
return this.#options.softLimitFactor;
|
|
8839
|
+
}
|
|
8840
|
+
get extendIterations() {
|
|
8841
|
+
return this.#options.extendIterations;
|
|
8842
|
+
}
|
|
8727
8843
|
setModel(modelId) {
|
|
8728
8844
|
this.#options.registry.get(modelId);
|
|
8729
8845
|
this.#options.modelId = modelId;
|
|
@@ -9291,10 +9407,10 @@ function serializeToolInput(input) {
|
|
|
9291
9407
|
}
|
|
9292
9408
|
function waitForRetry2(delayMs, signal) {
|
|
9293
9409
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
9294
|
-
return new Promise((
|
|
9410
|
+
return new Promise((resolve32, reject) => {
|
|
9295
9411
|
const timer = setTimeout(() => {
|
|
9296
9412
|
signal?.removeEventListener("abort", onAbort);
|
|
9297
|
-
|
|
9413
|
+
resolve32();
|
|
9298
9414
|
}, delayMs);
|
|
9299
9415
|
const onAbort = () => {
|
|
9300
9416
|
clearTimeout(timer);
|
|
@@ -9504,8 +9620,9 @@ var ToolRuntime = class {
|
|
|
9504
9620
|
}
|
|
9505
9621
|
}
|
|
9506
9622
|
async execute(call, context) {
|
|
9623
|
+
if (this.#disposed) return { ok: false, error: { code: "runtime_disposed", message: "ToolRuntime is disposed" } };
|
|
9507
9624
|
const definition = this.#tools.get(call.name);
|
|
9508
|
-
const journalId = definition === void 0 || this.#journal === void 0 ? void 0 : this.#journal.start(call.name, call.input, definition.retrySafe === true || definition.readOnly === true);
|
|
9625
|
+
const journalId = definition === void 0 || this.#journal === void 0 ? void 0 : this.#journal.start(call.name, call.input, definition.retrySafe === true || definition.readOnly === true || getToolCategory(call.name) === "read");
|
|
9509
9626
|
try {
|
|
9510
9627
|
const result = await this.#execute(call, context);
|
|
9511
9628
|
if (journalId !== void 0) this.#journal?.complete(journalId, result);
|
|
@@ -9532,6 +9649,10 @@ var ToolRuntime = class {
|
|
|
9532
9649
|
if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
|
|
9533
9650
|
const signal = context.signal ?? new AbortController().signal;
|
|
9534
9651
|
const toolCallId = call.id ?? randomUUID7();
|
|
9652
|
+
if (tool.agents !== void 0 && !tool.agents.includes(context.agent)) {
|
|
9653
|
+
return this.#fail(toolCallId, tool.name, call.input, context.agent, "permission_denied", `Tool ${tool.name} is not available to ${context.agent} agents`);
|
|
9654
|
+
}
|
|
9655
|
+
if (signal.aborted) return this.#fail(toolCallId, tool.name, call.input, context.agent, "cancelled", message(signal.reason));
|
|
9535
9656
|
const validation = this.normalize(call);
|
|
9536
9657
|
if (!validation.ok) {
|
|
9537
9658
|
return this.#fail(toolCallId, call.name, call.input, context.agent, validation.error.code, validation.error.message);
|
|
@@ -9639,17 +9760,37 @@ var ToolRuntime = class {
|
|
|
9639
9760
|
} catch (error) {
|
|
9640
9761
|
return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_output", message(error));
|
|
9641
9762
|
}
|
|
9642
|
-
const
|
|
9643
|
-
|
|
9644
|
-
|
|
9763
|
+
const warnings = [];
|
|
9764
|
+
let presentation = getToolPresentation(rawOutput);
|
|
9765
|
+
try {
|
|
9766
|
+
presentation = tool.presentResult?.(rawOutput, input) ?? presentation;
|
|
9767
|
+
} catch (error) {
|
|
9768
|
+
warnings.push(`Tool succeeded; result presentation failed: ${message(error)}`);
|
|
9769
|
+
}
|
|
9770
|
+
let rendered;
|
|
9771
|
+
try {
|
|
9772
|
+
rendered = tool.renderForModel?.(rawOutput, input);
|
|
9773
|
+
} catch (error) {
|
|
9774
|
+
warnings.push(`Tool succeeded; result rendering failed: ${message(error)}`);
|
|
9775
|
+
}
|
|
9645
9776
|
const output = rendered === void 0 ? await this.#limitOutput(tool.name, rawOutput) : rawOutput;
|
|
9646
9777
|
const content = rendered === void 0 ? void 0 : await this.#limitText(tool.name, rendered);
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
}
|
|
9778
|
+
let additionalContext = [];
|
|
9779
|
+
try {
|
|
9780
|
+
additionalContext = await this.#afterSuccess?.(tool.name, tool.paths(input), input, rawOutput, context) ?? [];
|
|
9781
|
+
} catch (error) {
|
|
9782
|
+
warnings.push(`Tool succeeded; context update failed: ${message(error)}`);
|
|
9783
|
+
}
|
|
9784
|
+
try {
|
|
9785
|
+
await this.#hooks.emit({
|
|
9786
|
+
version: 1,
|
|
9787
|
+
type: "PostToolUse",
|
|
9788
|
+
payload: { toolCallId, tool: tool.name, input, agent: context.agent, output }
|
|
9789
|
+
});
|
|
9790
|
+
} catch (error) {
|
|
9791
|
+
warnings.push(`Tool succeeded; PostToolUse hook failed: ${message(error)}`);
|
|
9792
|
+
}
|
|
9793
|
+
additionalContext = [...additionalContext, ...warnings];
|
|
9653
9794
|
return {
|
|
9654
9795
|
ok: true,
|
|
9655
9796
|
output,
|
|
@@ -9680,17 +9821,23 @@ var ToolRuntime = class {
|
|
|
9680
9821
|
const preview = headTailPreview(serialized, previewChars);
|
|
9681
9822
|
this.#turnOutputChars += preview.length;
|
|
9682
9823
|
const directory = join10(this.#workspace, ".flavor", "tool-results");
|
|
9683
|
-
await mkdir7(directory, { recursive: true });
|
|
9684
9824
|
const filename2 = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7()}-${safeFilename(tool)}.txt`;
|
|
9685
9825
|
const savedTo = join10(directory, filename2);
|
|
9686
|
-
|
|
9826
|
+
let storage;
|
|
9827
|
+
try {
|
|
9828
|
+
await mkdir7(directory, { recursive: true });
|
|
9829
|
+
await writeFile4(savedTo, serialized, "utf8");
|
|
9830
|
+
storage = { savedTo };
|
|
9831
|
+
} catch (error) {
|
|
9832
|
+
storage = { storageError: `Tool succeeded, but the full output could not be saved: ${message(error)}` };
|
|
9833
|
+
}
|
|
9687
9834
|
return {
|
|
9688
9835
|
truncated: true,
|
|
9689
9836
|
reason,
|
|
9690
9837
|
preview,
|
|
9691
9838
|
originalChars: serialized.length,
|
|
9692
9839
|
previewChars: preview.length,
|
|
9693
|
-
|
|
9840
|
+
...storage
|
|
9694
9841
|
};
|
|
9695
9842
|
}
|
|
9696
9843
|
callPresentation(call) {
|
|
@@ -9891,6 +10038,8 @@ var LocalHarness = class {
|
|
|
9891
10038
|
agent,
|
|
9892
10039
|
ownerId,
|
|
9893
10040
|
...maxIterations === void 0 ? {} : { maxIterations },
|
|
10041
|
+
...this.#options.softLimitFactor === void 0 ? {} : { softLimitFactor: this.#options.softLimitFactor },
|
|
10042
|
+
...this.#options.extendIterations === void 0 ? {} : { extendIterations: this.#options.extendIterations },
|
|
9894
10043
|
...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
|
|
9895
10044
|
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {},
|
|
9896
10045
|
...this.#options.modelJournal === void 0 ? {} : { modelJournal: this.#options.modelJournal }
|
|
@@ -9920,7 +10069,7 @@ function toModelTool(tool) {
|
|
|
9920
10069
|
|
|
9921
10070
|
// src/harness/journal.ts
|
|
9922
10071
|
import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
|
|
9923
|
-
import { closeSync, constants as constants3, existsSync as existsSync4, fsyncSync, mkdirSync as
|
|
10072
|
+
import { closeSync, constants as constants3, existsSync as existsSync4, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, renameSync, statSync as statSync2, truncateSync, unlinkSync, writeSync } from "fs";
|
|
9924
10073
|
import { join as join11, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
|
|
9925
10074
|
import { z as z14 } from "zod";
|
|
9926
10075
|
|
|
@@ -10013,7 +10162,7 @@ var HarnessJournal = class {
|
|
|
10013
10162
|
const workspace = resolve11(options.workspace);
|
|
10014
10163
|
const directory = join11(workspace, ".flavor", "sessions");
|
|
10015
10164
|
assertSessionId(options.sessionId);
|
|
10016
|
-
|
|
10165
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
10017
10166
|
assertWithin(workspace, directory);
|
|
10018
10167
|
const canonicalWorkspace = realpathSync2.native(workspace);
|
|
10019
10168
|
const canonicalDirectory = realpathSync2.native(directory);
|
|
@@ -10030,6 +10179,10 @@ var HarnessJournal = class {
|
|
|
10030
10179
|
get records() {
|
|
10031
10180
|
return this.#records.map((record) => structuredClone(record));
|
|
10032
10181
|
}
|
|
10182
|
+
/** Retained record count for the rotation census. */
|
|
10183
|
+
get recordCount() {
|
|
10184
|
+
return this.#records.length;
|
|
10185
|
+
}
|
|
10033
10186
|
admitQueue(kind, payload) {
|
|
10034
10187
|
QueueKindSchema.parse(kind);
|
|
10035
10188
|
const id = randomUUID8();
|
|
@@ -10306,19 +10459,38 @@ function jsonSafeRecord(value) {
|
|
|
10306
10459
|
return JSON.parse(json);
|
|
10307
10460
|
}
|
|
10308
10461
|
function hashJson(value) {
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
const { hash: _hash, ...unsigned } = record;
|
|
10313
|
-
return hashJson(unsigned);
|
|
10462
|
+
const hash = createHash6("sha256");
|
|
10463
|
+
stableHashInto(hash, value);
|
|
10464
|
+
return hash.digest("hex");
|
|
10314
10465
|
}
|
|
10315
|
-
function
|
|
10316
|
-
if (Array.isArray(value))
|
|
10466
|
+
function stableHashInto(hash, value) {
|
|
10467
|
+
if (Array.isArray(value)) {
|
|
10468
|
+
hash.update("[");
|
|
10469
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
10470
|
+
if (index > 0) hash.update(",");
|
|
10471
|
+
stableHashInto(hash, value[index]);
|
|
10472
|
+
}
|
|
10473
|
+
hash.update("]");
|
|
10474
|
+
return;
|
|
10475
|
+
}
|
|
10317
10476
|
if (typeof value === "object" && value !== null) {
|
|
10318
10477
|
const object = value;
|
|
10319
|
-
|
|
10478
|
+
hash.update("{");
|
|
10479
|
+
const keys = Object.keys(object).sort();
|
|
10480
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
10481
|
+
const key = keys[index];
|
|
10482
|
+
if (index > 0) hash.update(",");
|
|
10483
|
+
hash.update(`${JSON.stringify(key)}:`);
|
|
10484
|
+
stableHashInto(hash, object[key]);
|
|
10485
|
+
}
|
|
10486
|
+
hash.update("}");
|
|
10487
|
+
return;
|
|
10320
10488
|
}
|
|
10321
|
-
|
|
10489
|
+
hash.update(JSON.stringify(value) ?? String(value));
|
|
10490
|
+
}
|
|
10491
|
+
function recordHash(record) {
|
|
10492
|
+
const { hash: _hash, ...unsigned } = record;
|
|
10493
|
+
return hashJson(unsigned);
|
|
10322
10494
|
}
|
|
10323
10495
|
function assertSessionId(value) {
|
|
10324
10496
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) throw new Error("Invalid session id");
|
|
@@ -10332,7 +10504,7 @@ function isWithin2(root, target) {
|
|
|
10332
10504
|
}
|
|
10333
10505
|
|
|
10334
10506
|
// src/hooks/bus.ts
|
|
10335
|
-
import { spawn as
|
|
10507
|
+
import { spawn as spawn6 } from "child_process";
|
|
10336
10508
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10337
10509
|
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
10338
10510
|
var DEFAULT_MAX_OUTPUT_BYTES2 = 1048576;
|
|
@@ -10439,10 +10611,10 @@ function decoratePayload(payload, envelope) {
|
|
|
10439
10611
|
return envelope === void 0 ? payload : { ...payload, ...envelope };
|
|
10440
10612
|
}
|
|
10441
10613
|
function runShellHandler(descriptor, event, signal) {
|
|
10442
|
-
return new Promise((
|
|
10614
|
+
return new Promise((resolve32, reject) => {
|
|
10443
10615
|
const env = { ...process.env, ...descriptor.env };
|
|
10444
10616
|
const invocation = prepareSpawnInvocation(descriptor.command, descriptor.args ?? [], { env });
|
|
10445
|
-
const child =
|
|
10617
|
+
const child = spawn6(invocation.command, invocation.args, {
|
|
10446
10618
|
env,
|
|
10447
10619
|
shell: false,
|
|
10448
10620
|
detached: process.platform !== "win32",
|
|
@@ -10459,7 +10631,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
10459
10631
|
if (settled) return;
|
|
10460
10632
|
settled = true;
|
|
10461
10633
|
signal.removeEventListener("abort", onAbort);
|
|
10462
|
-
|
|
10634
|
+
terminateProcessTree2(child);
|
|
10463
10635
|
reject(error);
|
|
10464
10636
|
};
|
|
10465
10637
|
const append = (stream, chunk) => {
|
|
@@ -10485,7 +10657,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
10485
10657
|
return;
|
|
10486
10658
|
}
|
|
10487
10659
|
try {
|
|
10488
|
-
|
|
10660
|
+
resolve32(HookDecisionSchema.parse(JSON.parse(stdout)));
|
|
10489
10661
|
} catch (error) {
|
|
10490
10662
|
reject(error);
|
|
10491
10663
|
}
|
|
@@ -10493,10 +10665,10 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
10493
10665
|
child.stdin.end(JSON.stringify(event));
|
|
10494
10666
|
});
|
|
10495
10667
|
}
|
|
10496
|
-
function
|
|
10668
|
+
function terminateProcessTree2(child) {
|
|
10497
10669
|
if (child.pid === void 0) return;
|
|
10498
10670
|
if (process.platform === "win32") {
|
|
10499
|
-
const killer =
|
|
10671
|
+
const killer = spawn6("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
|
|
10500
10672
|
stdio: "ignore",
|
|
10501
10673
|
windowsHide: true
|
|
10502
10674
|
});
|
|
@@ -10682,14 +10854,14 @@ async function createIslandControlServer(options) {
|
|
|
10682
10854
|
void dispatch(line, socket, options, token);
|
|
10683
10855
|
});
|
|
10684
10856
|
});
|
|
10685
|
-
await new Promise((
|
|
10857
|
+
await new Promise((resolve32, reject) => {
|
|
10686
10858
|
const onError = (error) => {
|
|
10687
10859
|
server.removeListener("listening", onListening);
|
|
10688
10860
|
reject(error);
|
|
10689
10861
|
};
|
|
10690
10862
|
const onListening = () => {
|
|
10691
10863
|
server.removeListener("error", onError);
|
|
10692
|
-
|
|
10864
|
+
resolve32();
|
|
10693
10865
|
};
|
|
10694
10866
|
server.once("error", onError);
|
|
10695
10867
|
server.once("listening", onListening);
|
|
@@ -10760,17 +10932,17 @@ function write(socket, payload) {
|
|
|
10760
10932
|
}
|
|
10761
10933
|
}
|
|
10762
10934
|
function closeServer(server) {
|
|
10763
|
-
return new Promise((
|
|
10935
|
+
return new Promise((resolve32) => {
|
|
10764
10936
|
try {
|
|
10765
|
-
server.close(() =>
|
|
10937
|
+
server.close(() => resolve32());
|
|
10766
10938
|
} catch {
|
|
10767
|
-
|
|
10939
|
+
resolve32();
|
|
10768
10940
|
}
|
|
10769
10941
|
});
|
|
10770
10942
|
}
|
|
10771
10943
|
|
|
10772
10944
|
// src/evolve/service.ts
|
|
10773
|
-
import { spawn as
|
|
10945
|
+
import { spawn as spawn7 } from "child_process";
|
|
10774
10946
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
10775
10947
|
import { join as join15 } from "path";
|
|
10776
10948
|
import { z as z16 } from "zod";
|
|
@@ -10790,7 +10962,7 @@ import { z as z15 } from "zod";
|
|
|
10790
10962
|
|
|
10791
10963
|
// src/plugins/worker-host.ts
|
|
10792
10964
|
import { realpath as realpath3 } from "fs/promises";
|
|
10793
|
-
import { dirname as
|
|
10965
|
+
import { dirname as dirname8 } from "path";
|
|
10794
10966
|
import { Worker } from "worker_threads";
|
|
10795
10967
|
var DEFAULT_ACTIVATION_TIMEOUT_MS = 1e4;
|
|
10796
10968
|
var DEFAULT_INVOCATION_TIMEOUT_MS = 3e4;
|
|
@@ -10798,7 +10970,7 @@ var DEFAULT_MAX_OLD_SPACE_MB = 128;
|
|
|
10798
10970
|
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
10799
10971
|
async function startPluginSandbox(options) {
|
|
10800
10972
|
const entryPath = await realpath3(options.entryPath);
|
|
10801
|
-
const pluginRoot = await realpath3(options.pluginRoot ??
|
|
10973
|
+
const pluginRoot = await realpath3(options.pluginRoot ?? dirname8(entryPath));
|
|
10802
10974
|
const activationTimeoutMs = options.activationTimeoutMs ?? DEFAULT_ACTIVATION_TIMEOUT_MS;
|
|
10803
10975
|
const invocationTimeoutMs = options.invocationTimeoutMs ?? DEFAULT_INVOCATION_TIMEOUT_MS;
|
|
10804
10976
|
let configJson;
|
|
@@ -11618,7 +11790,7 @@ async function revertFixPlugin(workspace, name) {
|
|
|
11618
11790
|
// src/evolve/store.ts
|
|
11619
11791
|
import { createHash as createHash9 } from "crypto";
|
|
11620
11792
|
import { appendFile, mkdir as mkdir9, readFile as readFile9, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
11621
|
-
import { dirname as
|
|
11793
|
+
import { dirname as dirname9, join as join14 } from "path";
|
|
11622
11794
|
function normalizeError(message2) {
|
|
11623
11795
|
return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
|
|
11624
11796
|
}
|
|
@@ -11647,7 +11819,7 @@ async function readJsonLines(file) {
|
|
|
11647
11819
|
}
|
|
11648
11820
|
}
|
|
11649
11821
|
async function writeJsonLines(file, records) {
|
|
11650
|
-
await mkdir9(
|
|
11822
|
+
await mkdir9(dirname9(file), { recursive: true, mode: 448 });
|
|
11651
11823
|
const body = records.map((record) => JSON.stringify(record)).join("\n");
|
|
11652
11824
|
await writeFile7(file, body.length > 0 ? `${body}
|
|
11653
11825
|
` : "", { encoding: "utf8", mode: 384 });
|
|
@@ -11675,7 +11847,7 @@ async function readRules(file) {
|
|
|
11675
11847
|
}
|
|
11676
11848
|
}
|
|
11677
11849
|
async function writeRules(file, rules) {
|
|
11678
|
-
await mkdir9(
|
|
11850
|
+
await mkdir9(dirname9(file), { recursive: true, mode: 448 });
|
|
11679
11851
|
await writeFile7(file, JSON.stringify(rules, null, 2), { encoding: "utf8", mode: 384 });
|
|
11680
11852
|
}
|
|
11681
11853
|
var EvolveStore = class {
|
|
@@ -11915,9 +12087,9 @@ var USAGE = [
|
|
|
11915
12087
|
" clear reset signals, done markers, and verified markers"
|
|
11916
12088
|
].join("\n");
|
|
11917
12089
|
function runCommand(command, cwd, timeoutMs) {
|
|
11918
|
-
return new Promise((
|
|
12090
|
+
return new Promise((resolve32) => {
|
|
11919
12091
|
const isWindows = process.platform === "win32";
|
|
11920
|
-
const child =
|
|
12092
|
+
const child = spawn7(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
|
|
11921
12093
|
cwd,
|
|
11922
12094
|
windowsHide: true
|
|
11923
12095
|
});
|
|
@@ -11925,7 +12097,7 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
11925
12097
|
let stderr = "";
|
|
11926
12098
|
const timer = setTimeout(() => {
|
|
11927
12099
|
child.kill();
|
|
11928
|
-
|
|
12100
|
+
resolve32({ ok: false, stdout, stderr: `${stderr}
|
|
11929
12101
|
[killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
|
|
11930
12102
|
}, timeoutMs);
|
|
11931
12103
|
child.stdout.on("data", (chunk) => {
|
|
@@ -11936,12 +12108,12 @@ function runCommand(command, cwd, timeoutMs) {
|
|
|
11936
12108
|
});
|
|
11937
12109
|
child.on("error", (error) => {
|
|
11938
12110
|
clearTimeout(timer);
|
|
11939
|
-
|
|
12111
|
+
resolve32({ ok: false, stdout, stderr: `${stderr}
|
|
11940
12112
|
${error.message}`, code: "spawn" });
|
|
11941
12113
|
});
|
|
11942
12114
|
child.on("close", (code) => {
|
|
11943
12115
|
clearTimeout(timer);
|
|
11944
|
-
|
|
12116
|
+
resolve32({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
|
|
11945
12117
|
});
|
|
11946
12118
|
});
|
|
11947
12119
|
}
|
|
@@ -12312,10 +12484,12 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
12312
12484
|
name: "evolve_improve",
|
|
12313
12485
|
description: "Implement a fix for one repeated tool failure. Default kind=plugin scaffolds the fix-<tool>/ plugin dir and writes PLAN.md with instructions for implementing, verifying, reloading, and testing it. kind=prompt_rule instead stores a concise guardrail rule (from `implementation`) that is injected into future system prompts and closes the suggestion. Use when the model proposes a concrete fix for a suggestion in the system prompt.",
|
|
12314
12486
|
inputSchema,
|
|
12315
|
-
paths: () => [],
|
|
12316
|
-
execute: async (input) => {
|
|
12487
|
+
paths: (input) => [join15(workspace, ".flavor", "evolve"), ...input.kind === "prompt_rule" ? [] : [join15(workspace, ".flavor", "plugins")]],
|
|
12488
|
+
execute: async (input, signal) => {
|
|
12489
|
+
signal.throwIfAborted();
|
|
12317
12490
|
const { suggestionId, implementation, kind } = input;
|
|
12318
12491
|
const suggestions = await openWithTrends(100);
|
|
12492
|
+
signal.throwIfAborted();
|
|
12319
12493
|
const suggestion = suggestions.find((item) => item.id === suggestionId);
|
|
12320
12494
|
if (suggestion === void 0) throw new Error(`No open suggestion with id "${suggestionId}".`);
|
|
12321
12495
|
if (kind === "prompt_rule") {
|
|
@@ -12432,6 +12606,14 @@ var LoopStateSchema = z17.object({
|
|
|
12432
12606
|
approvals: z17.array(LoopApprovalSchema)
|
|
12433
12607
|
}).strict(),
|
|
12434
12608
|
cycles: z17.array(LoopCycleEvidenceSchema),
|
|
12609
|
+
/** Completed worker awaiting host verification; avoids replaying tool work after rotation. */
|
|
12610
|
+
pendingCycle: z17.object({
|
|
12611
|
+
cycle: z17.number().int().positive(),
|
|
12612
|
+
startedAt: z17.string().datetime({ offset: true }),
|
|
12613
|
+
inputTokens: z17.number().int().nonnegative(),
|
|
12614
|
+
outputTokens: z17.number().int().nonnegative(),
|
|
12615
|
+
workerText: z17.string().max(16e3)
|
|
12616
|
+
}).strict().nullable().optional(),
|
|
12435
12617
|
terminalReason: z17.string().optional()
|
|
12436
12618
|
}).strict();
|
|
12437
12619
|
var LoopEventSchema = z17.object({
|
|
@@ -12520,15 +12702,45 @@ var LoopOrchestrator = class {
|
|
|
12520
12702
|
tokenCheckpoint: this.#options.config.maxTokens,
|
|
12521
12703
|
approvals: []
|
|
12522
12704
|
},
|
|
12523
|
-
cycles: []
|
|
12705
|
+
cycles: [],
|
|
12706
|
+
pendingCycle: null
|
|
12524
12707
|
});
|
|
12525
12708
|
await this.#options.persistence.create(state);
|
|
12709
|
+
yield* this.#execute(state, request.goal, request.signal);
|
|
12710
|
+
}
|
|
12711
|
+
/**
|
|
12712
|
+
* Continue a loop whose process rotated onto a fresh heap. The persisted
|
|
12713
|
+
* state decides where to pick up; an interrupted cycle simply runs again.
|
|
12714
|
+
*/
|
|
12715
|
+
async *resume(request) {
|
|
12716
|
+
const persistence = this.#options.persistence;
|
|
12717
|
+
if (persistence.load === void 0) throw new Error("Loop persistence does not support resume");
|
|
12718
|
+
let state;
|
|
12526
12719
|
try {
|
|
12720
|
+
state = await persistence.load(request.loopId);
|
|
12721
|
+
} catch (error) {
|
|
12722
|
+
yield { type: "loop-terminal", loopId: request.loopId, status: "failed", reason: message(error) };
|
|
12723
|
+
return;
|
|
12724
|
+
}
|
|
12725
|
+
if (state.status !== "running") {
|
|
12726
|
+
const status = state.status;
|
|
12727
|
+
yield { type: "loop-terminal", loopId: state.loopId, status, reason: state.terminalReason ?? "The loop is not running." };
|
|
12728
|
+
return;
|
|
12729
|
+
}
|
|
12730
|
+
yield* this.#execute(state, state.goal, request.signal);
|
|
12731
|
+
}
|
|
12732
|
+
async *#execute(initialState, goal, signal) {
|
|
12733
|
+
const now = this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
12734
|
+
const loopId = initialState.loopId;
|
|
12735
|
+
let state = initialState;
|
|
12736
|
+
try {
|
|
12737
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12738
|
+
if (memoryRotationActive()) return;
|
|
12527
12739
|
const workspaceResolution = await this.#options.prepareWorkspace({
|
|
12528
12740
|
root: this.#options.workspace,
|
|
12529
12741
|
loopId,
|
|
12530
|
-
goal
|
|
12531
|
-
signal
|
|
12742
|
+
goal,
|
|
12743
|
+
signal
|
|
12532
12744
|
});
|
|
12533
12745
|
if (workspaceResolution.kind === "needs_human") {
|
|
12534
12746
|
state = await this.#terminal(state, "needs_human", workspaceResolution.reason, now());
|
|
@@ -12546,67 +12758,87 @@ var LoopOrchestrator = class {
|
|
|
12546
12758
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
12547
12759
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
12548
12760
|
};
|
|
12549
|
-
|
|
12761
|
+
const lastCompletedCycle = state.cycles.at(-1);
|
|
12762
|
+
let previousVerification = lastCompletedCycle?.verification ?? (plan.commands.length === 0 ? {
|
|
12550
12763
|
passed: false,
|
|
12551
12764
|
commands: [],
|
|
12552
12765
|
summary: `${plan.needsHumanReason ?? "No deterministic verification command was found."} Inspect the project and establish a meaningful project-native deterministic check; never add a trivial pass-through verifier.`
|
|
12553
|
-
} : void 0;
|
|
12554
|
-
let previousFailureSignature
|
|
12555
|
-
|
|
12766
|
+
} : void 0);
|
|
12767
|
+
let previousFailureSignature = lastCompletedCycle === void 0 ? void 0 : `${lastCompletedCycle.workspaceFingerprint}
|
|
12768
|
+
${lastCompletedCycle.verification.summary}`;
|
|
12769
|
+
let repeatedFailures = previousFailureSignature === void 0 ? 0 : countTrailingFailureSignature(state.cycles, previousFailureSignature);
|
|
12556
12770
|
while (state.status === "running") {
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
const
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
}
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
goal: request.goal,
|
|
12576
|
-
cycle,
|
|
12577
|
-
workspace: executionWorkspace.root,
|
|
12578
|
-
prompt,
|
|
12579
|
-
signal: request.signal
|
|
12580
|
-
})) {
|
|
12581
|
-
if (event.type === "usage") {
|
|
12582
|
-
cycleInputTokens += event.inputTokens;
|
|
12583
|
-
cycleOutputTokens += event.outputTokens;
|
|
12584
|
-
}
|
|
12585
|
-
if (event.type === "error") workerError = event.error.message;
|
|
12586
|
-
if (event.type === "text") workerText += event.text;
|
|
12587
|
-
if (this.#options.hallucinationGuard !== void 0) {
|
|
12588
|
-
if (event.type === "tool-start") {
|
|
12589
|
-
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
12590
|
-
} else if (event.type === "tool-end") {
|
|
12591
|
-
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
12771
|
+
signal.throwIfAborted();
|
|
12772
|
+
await this.#options.onCycleBoundary?.(loopId);
|
|
12773
|
+
if (memoryRotationActive()) return;
|
|
12774
|
+
const pendingCycle = state.pendingCycle;
|
|
12775
|
+
const cycle = pendingCycle?.cycle ?? state.budget.cyclesUsed + 1;
|
|
12776
|
+
const startedAt = pendingCycle?.startedAt ?? now();
|
|
12777
|
+
let cycleInputTokens = pendingCycle?.inputTokens ?? 0;
|
|
12778
|
+
let cycleOutputTokens = pendingCycle?.outputTokens ?? 0;
|
|
12779
|
+
let workerText = pendingCycle?.workerText ?? "";
|
|
12780
|
+
if (pendingCycle === void 0 || pendingCycle === null) {
|
|
12781
|
+
await this.#options.persistence.append({ version: 1, type: "cycle_started", timestamp: startedAt, loopId, payload: { cycle } });
|
|
12782
|
+
yield { type: "loop-cycle-start", loopId, cycle };
|
|
12783
|
+
const prompt = buildLoopCyclePrompt({
|
|
12784
|
+
goal,
|
|
12785
|
+
cycle,
|
|
12786
|
+
...previousVerification === void 0 ? {} : {
|
|
12787
|
+
memory: `Previous host verification: ${previousVerification.summary}`,
|
|
12788
|
+
verification: previousVerification
|
|
12592
12789
|
}
|
|
12790
|
+
});
|
|
12791
|
+
let workerError;
|
|
12792
|
+
let workerMemoryPressure = false;
|
|
12793
|
+
for await (const event of this.#options.runWorker({
|
|
12794
|
+
goal,
|
|
12795
|
+
cycle,
|
|
12796
|
+
workspace: executionWorkspace.root,
|
|
12797
|
+
prompt,
|
|
12798
|
+
signal
|
|
12799
|
+
})) {
|
|
12800
|
+
if (event.type === "usage") {
|
|
12801
|
+
cycleInputTokens += event.inputTokens;
|
|
12802
|
+
cycleOutputTokens += event.outputTokens;
|
|
12803
|
+
}
|
|
12804
|
+
if (event.type === "error") {
|
|
12805
|
+
workerError = event.error.message;
|
|
12806
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
12807
|
+
}
|
|
12808
|
+
if (event.type === "text") workerText = `${workerText}${event.text}`.slice(-16e3);
|
|
12809
|
+
if (this.#options.hallucinationGuard !== void 0) {
|
|
12810
|
+
if (event.type === "tool-start") {
|
|
12811
|
+
this.#options.hallucinationGuard.recordToolCall(event.name, event.input, event.id);
|
|
12812
|
+
} else if (event.type === "tool-end") {
|
|
12813
|
+
this.#options.hallucinationGuard.recordToolResult(event.name, event.result, event.id);
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12816
|
+
yield { type: "worker-event", event };
|
|
12817
|
+
}
|
|
12818
|
+
if (workerMemoryPressure) return;
|
|
12819
|
+
if (workerError !== void 0) {
|
|
12820
|
+
state = LoopStateSchema.parse({
|
|
12821
|
+
...state,
|
|
12822
|
+
budget: {
|
|
12823
|
+
...state.budget,
|
|
12824
|
+
cyclesUsed: cycle,
|
|
12825
|
+
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
12826
|
+
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
12827
|
+
}
|
|
12828
|
+
});
|
|
12829
|
+
state = await this.#terminal(state, "failed", workerError, now());
|
|
12830
|
+
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
12831
|
+
return;
|
|
12593
12832
|
}
|
|
12594
|
-
yield { type: "worker-event", event };
|
|
12595
|
-
}
|
|
12596
|
-
if (workerError !== void 0) {
|
|
12597
12833
|
state = LoopStateSchema.parse({
|
|
12598
12834
|
...state,
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
cyclesUsed: cycle,
|
|
12602
|
-
inputTokens: state.budget.inputTokens + cycleInputTokens,
|
|
12603
|
-
outputTokens: state.budget.outputTokens + cycleOutputTokens
|
|
12604
|
-
}
|
|
12835
|
+
pendingCycle: { cycle, startedAt, inputTokens: cycleInputTokens, outputTokens: cycleOutputTokens, workerText },
|
|
12836
|
+
updatedAt: now()
|
|
12605
12837
|
});
|
|
12606
|
-
|
|
12607
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: workerError };
|
|
12608
|
-
return;
|
|
12838
|
+
await this.#options.persistence.save(state);
|
|
12609
12839
|
}
|
|
12840
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12841
|
+
if (memoryRotationActive()) return;
|
|
12610
12842
|
if (plan.commands.length === 0) {
|
|
12611
12843
|
plan = await this.#options.inferVerification(executionWorkspace.root);
|
|
12612
12844
|
if (plan.commands.length > 0) {
|
|
@@ -12626,8 +12858,28 @@ var LoopOrchestrator = class {
|
|
|
12626
12858
|
passed: false,
|
|
12627
12859
|
commands: [],
|
|
12628
12860
|
summary: plan.needsHumanReason ?? "No deterministic verification command was found after the discovery cycle."
|
|
12629
|
-
} : await this.#options.runVerifier(plan, executionWorkspace.root,
|
|
12861
|
+
} : await this.#options.runVerifier(plan, executionWorkspace.root, signal);
|
|
12862
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12863
|
+
if (memoryRotationActive()) return;
|
|
12630
12864
|
const fingerprint2 = await this.#options.fingerprint(executionWorkspace.root);
|
|
12865
|
+
let guardWarnings = [];
|
|
12866
|
+
if (evidence.passed && this.#options.hallucinationGuard !== void 0) {
|
|
12867
|
+
try {
|
|
12868
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12869
|
+
if (memoryRotationActive()) return;
|
|
12870
|
+
const report = await this.#options.hallucinationGuard.evaluate(goal, workerText);
|
|
12871
|
+
await this.#options.onMemoryCheckpoint?.(loopId);
|
|
12872
|
+
if (memoryRotationActive()) return;
|
|
12873
|
+
if (!report.passed) {
|
|
12874
|
+
const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
|
|
12875
|
+
state = await this.#terminal(state, "failed", guardReason, now());
|
|
12876
|
+
yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
|
|
12877
|
+
return;
|
|
12878
|
+
}
|
|
12879
|
+
guardWarnings = report.warnings;
|
|
12880
|
+
} catch {
|
|
12881
|
+
}
|
|
12882
|
+
}
|
|
12631
12883
|
const completedAt = now();
|
|
12632
12884
|
state = LoopStateSchema.parse({
|
|
12633
12885
|
...state,
|
|
@@ -12646,7 +12898,8 @@ var LoopOrchestrator = class {
|
|
|
12646
12898
|
outputTokens: cycleOutputTokens,
|
|
12647
12899
|
workspaceFingerprint: fingerprint2,
|
|
12648
12900
|
verification: evidence
|
|
12649
|
-
}]
|
|
12901
|
+
}],
|
|
12902
|
+
pendingCycle: null
|
|
12650
12903
|
});
|
|
12651
12904
|
await this.#options.persistence.save(state);
|
|
12652
12905
|
await this.#options.persistence.append({
|
|
@@ -12664,20 +12917,6 @@ var LoopOrchestrator = class {
|
|
|
12664
12917
|
return;
|
|
12665
12918
|
}
|
|
12666
12919
|
if (evidence.passed) {
|
|
12667
|
-
let guardWarnings = [];
|
|
12668
|
-
if (this.#options.hallucinationGuard !== void 0) {
|
|
12669
|
-
try {
|
|
12670
|
-
const report = await this.#options.hallucinationGuard.evaluate(request.goal, workerText);
|
|
12671
|
-
if (!report.passed) {
|
|
12672
|
-
const guardReason = report.blockingReasons.join("; ") || "Hallucination guard blocked completion.";
|
|
12673
|
-
state = await this.#terminal(state, "failed", guardReason, now());
|
|
12674
|
-
yield { type: "loop-terminal", loopId, status: "failed", reason: guardReason };
|
|
12675
|
-
return;
|
|
12676
|
-
}
|
|
12677
|
-
guardWarnings = report.warnings;
|
|
12678
|
-
} catch {
|
|
12679
|
-
}
|
|
12680
|
-
}
|
|
12681
12920
|
const reason = [evidence.summary, ...guardWarnings].filter(Boolean).join("; ");
|
|
12682
12921
|
state = await this.#terminal(state, "succeeded", reason, now());
|
|
12683
12922
|
yield { type: "loop-terminal", loopId, status: "succeeded", reason };
|
|
@@ -12703,7 +12942,7 @@ ${evidence.summary}`;
|
|
|
12703
12942
|
cycleCheckpoint: state.budget.cycleCheckpoint,
|
|
12704
12943
|
tokenCheckpoint: state.budget.tokenCheckpoint
|
|
12705
12944
|
};
|
|
12706
|
-
const confirmation = await this.#options.confirmBudget(state, decision.dimensions,
|
|
12945
|
+
const confirmation = await this.#options.confirmBudget(state, decision.dimensions, signal);
|
|
12707
12946
|
if (confirmation === "unavailable") {
|
|
12708
12947
|
const reason = "Loop budget extension requires user confirmation.";
|
|
12709
12948
|
state = await this.#terminal(state, "needs_human", reason, now());
|
|
@@ -12730,8 +12969,9 @@ ${evidence.summary}`;
|
|
|
12730
12969
|
}
|
|
12731
12970
|
}
|
|
12732
12971
|
} catch (error) {
|
|
12733
|
-
|
|
12734
|
-
const
|
|
12972
|
+
if (memoryRotationActive()) return;
|
|
12973
|
+
const status = signal.aborted ? "cancelled" : "failed";
|
|
12974
|
+
const reason = signal.aborted ? "Loop cancelled by user." : message(error);
|
|
12735
12975
|
state = await this.#terminal(state, status, reason, now());
|
|
12736
12976
|
yield { type: "loop-terminal", loopId, status, reason };
|
|
12737
12977
|
}
|
|
@@ -12749,6 +12989,16 @@ ${evidence.summary}`;
|
|
|
12749
12989
|
return state;
|
|
12750
12990
|
}
|
|
12751
12991
|
};
|
|
12992
|
+
function countTrailingFailureSignature(cycles, signature) {
|
|
12993
|
+
let count = 0;
|
|
12994
|
+
for (let index = cycles.length - 1; index >= 0; index -= 1) {
|
|
12995
|
+
const cycle = cycles[index];
|
|
12996
|
+
if (`${cycle.workspaceFingerprint}
|
|
12997
|
+
${cycle.verification.summary}` !== signature) break;
|
|
12998
|
+
count += 1;
|
|
12999
|
+
}
|
|
13000
|
+
return count;
|
|
13001
|
+
}
|
|
12752
13002
|
|
|
12753
13003
|
// src/goal/orchestrator.ts
|
|
12754
13004
|
import { createHash as createHash11 } from "crypto";
|
|
@@ -12816,6 +13066,11 @@ var GoalStateSchema = z18.object({
|
|
|
12816
13066
|
planPath: z18.string().nullable(),
|
|
12817
13067
|
verifyRounds: z18.number().int().min(0),
|
|
12818
13068
|
workerRounds: z18.number().int().min(0),
|
|
13069
|
+
/** Worker output checkpoint used to resume verification without rerunning tools. */
|
|
13070
|
+
pendingVerification: z18.object({
|
|
13071
|
+
round: z18.number().int().positive(),
|
|
13072
|
+
finalResponse: z18.string().max(16e3)
|
|
13073
|
+
}).strict().nullable().optional(),
|
|
12819
13074
|
lastGaps: z18.array(GapSchema),
|
|
12820
13075
|
gapFingerprint: z18.string(),
|
|
12821
13076
|
stallStreak: z18.number().int().min(0),
|
|
@@ -12861,6 +13116,7 @@ ${objective}`
|
|
|
12861
13116
|
].join("\n");
|
|
12862
13117
|
}
|
|
12863
13118
|
async function runPlanner(options) {
|
|
13119
|
+
await options.onProgress?.();
|
|
12864
13120
|
const { adapter, model } = options.registry.get(options.modelId);
|
|
12865
13121
|
const prompt = buildPlannerPrompt(options.objective);
|
|
12866
13122
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -12872,6 +13128,7 @@ async function runPlanner(options) {
|
|
|
12872
13128
|
tools: [],
|
|
12873
13129
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
12874
13130
|
})) {
|
|
13131
|
+
await options.onProgress?.();
|
|
12875
13132
|
if (event.type === "text") text += event.text;
|
|
12876
13133
|
else if (event.type === "error") throw event.error;
|
|
12877
13134
|
else if (event.type === "done") {
|
|
@@ -12978,7 +13235,7 @@ async function runClassifier(evidence, plan, options) {
|
|
|
12978
13235
|
const skepticResults = await Promise.all(
|
|
12979
13236
|
Array.from(
|
|
12980
13237
|
{ length: n },
|
|
12981
|
-
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal)
|
|
13238
|
+
(_, i) => runOneSkeptic(evidence, plan, i + 1, adapter, model, options.signal, options.onProgress)
|
|
12982
13239
|
)
|
|
12983
13240
|
);
|
|
12984
13241
|
const verdicts = [];
|
|
@@ -12995,7 +13252,8 @@ async function runClassifier(evidence, plan, options) {
|
|
|
12995
13252
|
}
|
|
12996
13253
|
return aggregate(verdicts);
|
|
12997
13254
|
}
|
|
12998
|
-
async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
13255
|
+
async function runOneSkeptic(evidence, plan, index, adapter, model, signal, onProgress) {
|
|
13256
|
+
await onProgress?.();
|
|
12999
13257
|
const prompt = buildSkepticPrompt(evidence, plan, index);
|
|
13000
13258
|
const messages = [{ role: "user", content: prompt }];
|
|
13001
13259
|
let text = "";
|
|
@@ -13005,6 +13263,7 @@ async function runOneSkeptic(evidence, plan, index, adapter, model, signal) {
|
|
|
13005
13263
|
tools: [],
|
|
13006
13264
|
...signal === void 0 ? {} : { signal }
|
|
13007
13265
|
})) {
|
|
13266
|
+
await onProgress?.();
|
|
13008
13267
|
if (event.type === "text") text += event.text;
|
|
13009
13268
|
else if (event.type === "error") throw event.error;
|
|
13010
13269
|
else if (event.type === "done") break;
|
|
@@ -13159,7 +13418,7 @@ var GoalOrchestrator = class {
|
|
|
13159
13418
|
async *run(request) {
|
|
13160
13419
|
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13161
13420
|
const createdAt = timestamp2();
|
|
13162
|
-
|
|
13421
|
+
const state = {
|
|
13163
13422
|
id: this.#options.idFactory?.() ?? `goal-${Date.now().toString(36)}`,
|
|
13164
13423
|
objective: request.goal,
|
|
13165
13424
|
phase: "planning",
|
|
@@ -13168,6 +13427,7 @@ var GoalOrchestrator = class {
|
|
|
13168
13427
|
planPath: null,
|
|
13169
13428
|
verifyRounds: 0,
|
|
13170
13429
|
workerRounds: 0,
|
|
13430
|
+
pendingVerification: null,
|
|
13171
13431
|
lastGaps: [],
|
|
13172
13432
|
gapFingerprint: "",
|
|
13173
13433
|
stallStreak: 0,
|
|
@@ -13176,87 +13436,147 @@ var GoalOrchestrator = class {
|
|
|
13176
13436
|
createdAt,
|
|
13177
13437
|
updatedAt: createdAt
|
|
13178
13438
|
};
|
|
13439
|
+
yield* this.#execute(state, request.signal);
|
|
13440
|
+
}
|
|
13441
|
+
/**
|
|
13442
|
+
* Continue a goal whose process rotated onto a fresh heap. The persisted
|
|
13443
|
+
* state decides where to pick up; an interrupted round simply runs again.
|
|
13444
|
+
*/
|
|
13445
|
+
async *resume(request) {
|
|
13446
|
+
const persistence = this.#options.persistence;
|
|
13447
|
+
if (persistence === void 0 || persistence.load === void 0) {
|
|
13448
|
+
throw new Error("Goal persistence does not support resume");
|
|
13449
|
+
}
|
|
13450
|
+
let state;
|
|
13451
|
+
try {
|
|
13452
|
+
state = await persistence.load(request.goalId);
|
|
13453
|
+
} catch (error) {
|
|
13454
|
+
yield { type: "goal-failed", reason: message(error) };
|
|
13455
|
+
return;
|
|
13456
|
+
}
|
|
13457
|
+
if (state.status !== "active" && state.status !== "not_achieved") {
|
|
13458
|
+
yield { type: "goal-failed", reason: `Goal "${state.id}" is ${state.status}; it cannot be resumed.` };
|
|
13459
|
+
return;
|
|
13460
|
+
}
|
|
13461
|
+
yield { type: "goal-resumed", goalId: state.id, round: resumeStartRound(state) };
|
|
13462
|
+
yield* this.#execute(state, request.signal);
|
|
13463
|
+
}
|
|
13464
|
+
async *#execute(initialState, signal) {
|
|
13465
|
+
const timestamp2 = () => this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13466
|
+
let state = initialState;
|
|
13179
13467
|
const persistState = async (patch) => {
|
|
13180
13468
|
state = { ...state, ...patch, updatedAt: timestamp2() };
|
|
13181
13469
|
await this.#options.persistence?.save(state);
|
|
13182
13470
|
};
|
|
13471
|
+
const memoryCheckpoint = async () => {
|
|
13472
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13473
|
+
if (memoryRotationActive()) throw new Error("Heap rotation requested");
|
|
13474
|
+
};
|
|
13183
13475
|
await persistState();
|
|
13184
13476
|
const workspace = this.#options.workspace;
|
|
13185
13477
|
let plan;
|
|
13186
13478
|
let planPath;
|
|
13187
|
-
|
|
13188
|
-
request.signal.throwIfAborted();
|
|
13189
|
-
plan = await runPlanner({
|
|
13190
|
-
registry: this.#options.registry,
|
|
13191
|
-
modelId: this.#options.plannerModelId,
|
|
13192
|
-
objective: request.goal,
|
|
13193
|
-
signal: request.signal
|
|
13194
|
-
});
|
|
13195
|
-
planPath = await writePlanFile(workspace, plan);
|
|
13196
|
-
await persistState({ plan, planPath, contractHash: contractHash(request.goal, plan) });
|
|
13197
|
-
yield { type: "goal-plan-created", plan, planPath };
|
|
13198
|
-
} catch (error) {
|
|
13199
|
-
const reason = `Goal planning failed: ${message(error)}`;
|
|
13200
|
-
await persistState({ phase: "complete", status: "failed" });
|
|
13201
|
-
yield { type: "goal-plan-failed", reason };
|
|
13202
|
-
yield { type: "goal-failed", reason };
|
|
13203
|
-
return;
|
|
13204
|
-
}
|
|
13205
|
-
let priorGaps = [];
|
|
13206
|
-
let priorFingerprint = "";
|
|
13207
|
-
let stallStreak = 0;
|
|
13208
|
-
for (let round = 1; round <= this.#options.maxRounds; round++) {
|
|
13209
|
-
request.signal.throwIfAborted();
|
|
13210
|
-
const workerPrompt = buildWorkerPrompt(request.goal, plan, priorGaps, round);
|
|
13211
|
-
await persistState({
|
|
13212
|
-
phase: "executing",
|
|
13213
|
-
status: "active",
|
|
13214
|
-
workerRounds: round,
|
|
13215
|
-
lastGaps: priorGaps,
|
|
13216
|
-
gapFingerprint: priorFingerprint,
|
|
13217
|
-
stallStreak
|
|
13218
|
-
});
|
|
13219
|
-
yield { type: "goal-worker-start", round };
|
|
13220
|
-
let finalResponse = "";
|
|
13221
|
-
let workerError;
|
|
13479
|
+
if (state.plan === null) {
|
|
13222
13480
|
try {
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
signal
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
break;
|
|
13236
|
-
}
|
|
13237
|
-
}
|
|
13481
|
+
signal.throwIfAborted();
|
|
13482
|
+
await memoryCheckpoint();
|
|
13483
|
+
plan = await runPlanner({
|
|
13484
|
+
registry: this.#options.registry,
|
|
13485
|
+
modelId: this.#options.plannerModelId,
|
|
13486
|
+
objective: state.objective,
|
|
13487
|
+
signal,
|
|
13488
|
+
onProgress: memoryCheckpoint
|
|
13489
|
+
});
|
|
13490
|
+
planPath = await writePlanFile(workspace, plan);
|
|
13491
|
+
await persistState({ phase: "executing", plan, planPath, contractHash: contractHash(state.objective, plan) });
|
|
13492
|
+
yield { type: "goal-plan-created", plan, planPath };
|
|
13238
13493
|
} catch (error) {
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
if (workerError !== void 0) {
|
|
13242
|
-
const reason = `Worker error in round ${round}: ${workerError}`;
|
|
13494
|
+
if (memoryRotationActive()) return;
|
|
13495
|
+
const reason = `Goal planning failed: ${message(error)}`;
|
|
13243
13496
|
await persistState({ phase: "complete", status: "failed" });
|
|
13497
|
+
yield { type: "goal-plan-failed", reason };
|
|
13244
13498
|
yield { type: "goal-failed", reason };
|
|
13245
13499
|
return;
|
|
13246
13500
|
}
|
|
13247
|
-
|
|
13501
|
+
} else {
|
|
13502
|
+
plan = state.plan;
|
|
13503
|
+
planPath = state.planPath ?? await writePlanFile(workspace, plan);
|
|
13504
|
+
await persistState({ phase: "executing", status: "active", ...state.planPath === null ? { planPath } : {} });
|
|
13505
|
+
}
|
|
13506
|
+
let priorGaps = state.lastGaps;
|
|
13507
|
+
let priorFingerprint = state.gapFingerprint;
|
|
13508
|
+
let stallStreak = state.stallStreak;
|
|
13509
|
+
const startRound = resumeStartRound(state);
|
|
13510
|
+
for (let round = startRound; round <= this.#options.maxRounds; round++) {
|
|
13511
|
+
signal.throwIfAborted();
|
|
13512
|
+
await this.#options.onRoundBoundary?.(state.id);
|
|
13513
|
+
if (memoryRotationActive()) return;
|
|
13514
|
+
const pendingVerification = state.pendingVerification?.round === round ? state.pendingVerification : void 0;
|
|
13515
|
+
let finalResponse = pendingVerification?.finalResponse ?? "";
|
|
13516
|
+
if (pendingVerification === void 0) {
|
|
13517
|
+
const workerPrompt = buildWorkerPrompt(state.objective, plan, priorGaps, round);
|
|
13518
|
+
await persistState({
|
|
13519
|
+
phase: "executing",
|
|
13520
|
+
status: "active",
|
|
13521
|
+
workerRounds: round,
|
|
13522
|
+
pendingVerification: null,
|
|
13523
|
+
lastGaps: priorGaps,
|
|
13524
|
+
gapFingerprint: priorFingerprint,
|
|
13525
|
+
stallStreak
|
|
13526
|
+
});
|
|
13527
|
+
yield { type: "goal-worker-start", round };
|
|
13528
|
+
let workerError;
|
|
13529
|
+
let workerMemoryPressure = false;
|
|
13530
|
+
try {
|
|
13531
|
+
for await (const event of this.#options.runWorker({
|
|
13532
|
+
goal: state.objective,
|
|
13533
|
+
round,
|
|
13534
|
+
workspace,
|
|
13535
|
+
prompt: workerPrompt,
|
|
13536
|
+
priorGaps: formatPriorGaps(priorGaps),
|
|
13537
|
+
signal
|
|
13538
|
+
})) {
|
|
13539
|
+
yield { type: "goal-worker-event", round, event };
|
|
13540
|
+
if (event.type === "text") finalResponse = `${finalResponse}${event.text}`.slice(-16e3);
|
|
13541
|
+
if (event.type === "error") {
|
|
13542
|
+
workerError = event.error.message;
|
|
13543
|
+
if (event.error.code === "memory_pressure") workerMemoryPressure = true;
|
|
13544
|
+
break;
|
|
13545
|
+
}
|
|
13546
|
+
}
|
|
13547
|
+
} catch (error) {
|
|
13548
|
+
if (memoryRotationActive()) return;
|
|
13549
|
+
workerError = message(error);
|
|
13550
|
+
}
|
|
13551
|
+
if (workerMemoryPressure) return;
|
|
13552
|
+
if (workerError !== void 0) {
|
|
13553
|
+
const reason = `Worker error in round ${round}: ${workerError}`;
|
|
13554
|
+
await persistState({ phase: "complete", status: "failed" });
|
|
13555
|
+
yield { type: "goal-failed", reason };
|
|
13556
|
+
return;
|
|
13557
|
+
}
|
|
13558
|
+
await persistState({
|
|
13559
|
+
phase: "verifying",
|
|
13560
|
+
status: "active",
|
|
13561
|
+
pendingVerification: { round, finalResponse }
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13248
13564
|
yield { type: "goal-verification-start", round };
|
|
13249
|
-
|
|
13565
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13566
|
+
if (memoryRotationActive()) return;
|
|
13567
|
+
const hostVerification = await this.#options.verifyHost?.(signal);
|
|
13568
|
+
await this.#options.onMemoryCheckpoint?.(state.id);
|
|
13569
|
+
if (memoryRotationActive()) return;
|
|
13250
13570
|
const evidence = await collectEvidence(
|
|
13251
13571
|
workspace,
|
|
13252
|
-
|
|
13572
|
+
state.objective,
|
|
13253
13573
|
finalResponse.slice(-4e3),
|
|
13254
13574
|
// Last 4000 chars of response
|
|
13255
13575
|
formatPriorGaps(priorGaps),
|
|
13256
13576
|
state.contractHash,
|
|
13257
13577
|
hostVerification
|
|
13258
13578
|
);
|
|
13259
|
-
await persistState({ evidenceRounds: [...state.evidenceRounds, {
|
|
13579
|
+
await persistState({ evidenceRounds: [...state.evidenceRounds.filter((item) => item.round !== round), {
|
|
13260
13580
|
round,
|
|
13261
13581
|
workspaceDiffHash: evidence.workspaceDiffHash,
|
|
13262
13582
|
...hostVerification === void 0 ? {} : { hostVerification }
|
|
@@ -13280,14 +13600,17 @@ var GoalOrchestrator = class {
|
|
|
13280
13600
|
modelId: this.#options.classifierModelId,
|
|
13281
13601
|
skepticCount: this.#options.skepticCount,
|
|
13282
13602
|
workspace,
|
|
13283
|
-
signal
|
|
13603
|
+
signal,
|
|
13604
|
+
onProgress: memoryCheckpoint
|
|
13284
13605
|
});
|
|
13285
13606
|
} catch (error) {
|
|
13607
|
+
if (memoryRotationActive()) return;
|
|
13286
13608
|
outcome = {
|
|
13287
13609
|
type: "blocked",
|
|
13288
13610
|
reason: `Classifier infrastructure error: ${message(error)}`
|
|
13289
13611
|
};
|
|
13290
13612
|
}
|
|
13613
|
+
await persistState({ verifyRounds: round, pendingVerification: null });
|
|
13291
13614
|
yield {
|
|
13292
13615
|
type: "goal-verdict",
|
|
13293
13616
|
round,
|
|
@@ -13347,6 +13670,12 @@ var GoalOrchestrator = class {
|
|
|
13347
13670
|
function contractHash(objective, plan) {
|
|
13348
13671
|
return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
|
|
13349
13672
|
}
|
|
13673
|
+
function resumeStartRound(state) {
|
|
13674
|
+
if (state.pendingVerification !== void 0 && state.pendingVerification !== null) {
|
|
13675
|
+
return state.pendingVerification.round;
|
|
13676
|
+
}
|
|
13677
|
+
return state.verifyRounds >= state.workerRounds ? state.workerRounds + 1 : Math.max(state.workerRounds, 1);
|
|
13678
|
+
}
|
|
13350
13679
|
async function writePlanFile(workspace, plan) {
|
|
13351
13680
|
const dir = join17(workspace, ".flavor");
|
|
13352
13681
|
const planPath = join17(dir, "goal-plan.md");
|
|
@@ -13426,8 +13755,8 @@ function formatPriorGaps(gaps) {
|
|
|
13426
13755
|
// src/goal/store.ts
|
|
13427
13756
|
import { constants as constants4 } from "fs";
|
|
13428
13757
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
13429
|
-
import { lstat as lstat7, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
|
|
13430
|
-
import { dirname as
|
|
13758
|
+
import { lstat as lstat7, mkdir as mkdir11, open as open6, readFile as readFile11, rename as rename4, rm as rm4, stat as stat6 } from "fs/promises";
|
|
13759
|
+
import { dirname as dirname10, join as join18, relative as relative11, resolve as resolve14, sep as sep11 } from "path";
|
|
13431
13760
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
13432
13761
|
var GoalStore = class {
|
|
13433
13762
|
#workspace;
|
|
@@ -13440,7 +13769,7 @@ var GoalStore = class {
|
|
|
13440
13769
|
}
|
|
13441
13770
|
async save(input) {
|
|
13442
13771
|
const state = GoalStateSchema.parse(input);
|
|
13443
|
-
await assertNoSymlink(this.#workspace,
|
|
13772
|
+
await assertNoSymlink(this.#workspace, dirname10(this.#root));
|
|
13444
13773
|
await mkdir11(this.#root, { recursive: true, mode: 448 });
|
|
13445
13774
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
13446
13775
|
const target = join18(this.#root, `${state.id}.json`);
|
|
@@ -13463,6 +13792,28 @@ var GoalStore = class {
|
|
|
13463
13792
|
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
13464
13793
|
}
|
|
13465
13794
|
}
|
|
13795
|
+
/** Reload a persisted goal so a rotated process can resume it. */
|
|
13796
|
+
async load(goalId) {
|
|
13797
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(goalId)) {
|
|
13798
|
+
throw new Error(`Goal "${goalId}" was not found`);
|
|
13799
|
+
}
|
|
13800
|
+
await assertNoSymlink(this.#workspace, dirname10(this.#root));
|
|
13801
|
+
await assertNoSymlink(this.#workspace, this.#root);
|
|
13802
|
+
const target = join18(this.#root, `${goalId}.json`);
|
|
13803
|
+
try {
|
|
13804
|
+
const metadata = await stat6(target);
|
|
13805
|
+
if (!metadata.isFile() || metadata.size > this.#maxBytes) {
|
|
13806
|
+
throw new Error("Goal state is not a bounded regular file");
|
|
13807
|
+
}
|
|
13808
|
+
const raw = await readFile11(target, "utf8");
|
|
13809
|
+
return GoalStateSchema.parse(JSON.parse(raw));
|
|
13810
|
+
} catch (error) {
|
|
13811
|
+
if (isCode3(error, "ENOENT")) throw new Error(`Goal "${goalId}" was not found`);
|
|
13812
|
+
const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
|
|
13813
|
+
await rename4(target, quarantine).catch(() => void 0);
|
|
13814
|
+
throw new Error(`Goal "${goalId}" is corrupt and was quarantined`);
|
|
13815
|
+
}
|
|
13816
|
+
}
|
|
13466
13817
|
};
|
|
13467
13818
|
async function assertNoSymlink(root, target) {
|
|
13468
13819
|
let cursor = resolve14(target);
|
|
@@ -13474,7 +13825,7 @@ async function assertNoSymlink(root, target) {
|
|
|
13474
13825
|
const chain = [];
|
|
13475
13826
|
while (cursor !== stop) {
|
|
13476
13827
|
chain.push(cursor);
|
|
13477
|
-
cursor =
|
|
13828
|
+
cursor = dirname10(cursor);
|
|
13478
13829
|
}
|
|
13479
13830
|
for (const path of chain.reverse()) {
|
|
13480
13831
|
try {
|
|
@@ -13489,7 +13840,7 @@ function isCode3(error, code) {
|
|
|
13489
13840
|
}
|
|
13490
13841
|
|
|
13491
13842
|
// src/loop/isolation.ts
|
|
13492
|
-
import { appendFile as appendFile2, mkdir as mkdir12, readFile as
|
|
13843
|
+
import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile12 } from "fs/promises";
|
|
13493
13844
|
import { join as join19, resolve as resolve15 } from "path";
|
|
13494
13845
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
13495
13846
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
@@ -13549,7 +13900,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
13549
13900
|
await mkdir12(infoDirectory, { recursive: true });
|
|
13550
13901
|
let existing = "";
|
|
13551
13902
|
try {
|
|
13552
|
-
existing = await
|
|
13903
|
+
existing = await readFile12(excludePath, "utf8");
|
|
13553
13904
|
} catch (error) {
|
|
13554
13905
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
13555
13906
|
}
|
|
@@ -13560,8 +13911,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
13560
13911
|
|
|
13561
13912
|
// src/loop/store.ts
|
|
13562
13913
|
import { constants as constants5 } from "fs";
|
|
13563
|
-
import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as
|
|
13564
|
-
import { dirname as
|
|
13914
|
+
import { lstat as lstat8, mkdir as mkdir13, open as open7, readFile as readFile13, realpath as realpath5, rename as rename5, rm as rm5, stat as stat7 } from "fs/promises";
|
|
13915
|
+
import { dirname as dirname11, join as join20, relative as relative12, resolve as resolve16, sep as sep12 } from "path";
|
|
13565
13916
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
13566
13917
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
13567
13918
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -13611,9 +13962,9 @@ var LoopStore = class {
|
|
|
13611
13962
|
const directory = await this.#safeExisting(loopId);
|
|
13612
13963
|
const target = join20(directory, "state.json");
|
|
13613
13964
|
try {
|
|
13614
|
-
const metadata = await
|
|
13965
|
+
const metadata = await stat7(target);
|
|
13615
13966
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
13616
|
-
const raw = await
|
|
13967
|
+
const raw = await readFile13(target, "utf8");
|
|
13617
13968
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
13618
13969
|
await this.#assertWorkspace(state.workspace);
|
|
13619
13970
|
return state;
|
|
@@ -13643,7 +13994,7 @@ var LoopStore = class {
|
|
|
13643
13994
|
}
|
|
13644
13995
|
async #prepare(loopId) {
|
|
13645
13996
|
validateId(loopId);
|
|
13646
|
-
await assertNoSymlink2(this.#workspace,
|
|
13997
|
+
await assertNoSymlink2(this.#workspace, dirname11(this.#root));
|
|
13647
13998
|
await mkdir13(this.#root, { recursive: true, mode: 448 });
|
|
13648
13999
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
13649
14000
|
const directory = join20(this.#root, loopId);
|
|
@@ -13676,7 +14027,7 @@ async function assertNoSymlink2(root, target) {
|
|
|
13676
14027
|
const chain = [];
|
|
13677
14028
|
while (cursor !== stop) {
|
|
13678
14029
|
chain.push(cursor);
|
|
13679
|
-
cursor =
|
|
14030
|
+
cursor = dirname11(cursor);
|
|
13680
14031
|
}
|
|
13681
14032
|
for (const path of chain.reverse()) {
|
|
13682
14033
|
try {
|
|
@@ -13702,7 +14053,7 @@ function isCode4(error, code) {
|
|
|
13702
14053
|
}
|
|
13703
14054
|
|
|
13704
14055
|
// src/loop/verifier.ts
|
|
13705
|
-
import { readFile as
|
|
14056
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
13706
14057
|
import { join as join21 } from "path";
|
|
13707
14058
|
var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
|
|
13708
14059
|
async function inferVerificationPlan(workspace) {
|
|
@@ -13781,7 +14132,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
13781
14132
|
}
|
|
13782
14133
|
async function optionalText(path) {
|
|
13783
14134
|
try {
|
|
13784
|
-
return await
|
|
14135
|
+
return await readFile14(path, "utf8");
|
|
13785
14136
|
} catch (error) {
|
|
13786
14137
|
if (isCode5(error, "ENOENT")) return void 0;
|
|
13787
14138
|
throw error;
|
|
@@ -13802,7 +14153,7 @@ function isCode5(error, code) {
|
|
|
13802
14153
|
|
|
13803
14154
|
// src/models/anthropic.ts
|
|
13804
14155
|
import Anthropic from "@anthropic-ai/sdk";
|
|
13805
|
-
import { readFile as
|
|
14156
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
13806
14157
|
|
|
13807
14158
|
// src/utils/json.ts
|
|
13808
14159
|
function safeParseJson(input) {
|
|
@@ -14039,7 +14390,7 @@ var AnthropicModelAdapter = class {
|
|
|
14039
14390
|
source: {
|
|
14040
14391
|
type: "base64",
|
|
14041
14392
|
media_type: block.mediaType,
|
|
14042
|
-
data: (await
|
|
14393
|
+
data: (await readFile15(block.source.path)).toString("base64")
|
|
14043
14394
|
}
|
|
14044
14395
|
}));
|
|
14045
14396
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -14259,7 +14610,7 @@ function resolveCacheProfile(input) {
|
|
|
14259
14610
|
|
|
14260
14611
|
// src/models/openai.ts
|
|
14261
14612
|
import OpenAI from "openai";
|
|
14262
|
-
import { readFile as
|
|
14613
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
14263
14614
|
async function toInput(message2) {
|
|
14264
14615
|
if (message2.role === "tool") {
|
|
14265
14616
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -14271,7 +14622,7 @@ async function toInput(message2) {
|
|
|
14271
14622
|
}
|
|
14272
14623
|
const content = typeof message2.content === "string" ? message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
|
|
14273
14624
|
type: "input_image",
|
|
14274
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
14625
|
+
image_url: `data:${block.mediaType};base64,${(await readFile16(block.source.path)).toString("base64")}`,
|
|
14275
14626
|
detail: "auto"
|
|
14276
14627
|
}));
|
|
14277
14628
|
return [
|
|
@@ -14908,8 +15259,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14908
15259
|
async #startCallbackServer(expectedState, signal) {
|
|
14909
15260
|
let serverResolve;
|
|
14910
15261
|
let serverReject;
|
|
14911
|
-
const codePromise = new Promise((
|
|
14912
|
-
serverResolve =
|
|
15262
|
+
const codePromise = new Promise((resolve32, reject) => {
|
|
15263
|
+
serverResolve = resolve32;
|
|
14913
15264
|
serverReject = reject;
|
|
14914
15265
|
});
|
|
14915
15266
|
const server = createServer3((req, res) => {
|
|
@@ -14943,7 +15294,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14943
15294
|
);
|
|
14944
15295
|
serverResolve(code);
|
|
14945
15296
|
});
|
|
14946
|
-
return new Promise((
|
|
15297
|
+
return new Promise((resolve32, reject) => {
|
|
14947
15298
|
server.once("error", (error) => {
|
|
14948
15299
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
14949
15300
|
});
|
|
@@ -14960,7 +15311,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
14960
15311
|
signal.addEventListener("abort", () => {
|
|
14961
15312
|
serverReject(new Error("OAuth flow aborted"));
|
|
14962
15313
|
}, { once: true });
|
|
14963
|
-
|
|
15314
|
+
resolve32({ redirectUri, promise: codePromise });
|
|
14964
15315
|
});
|
|
14965
15316
|
});
|
|
14966
15317
|
}
|
|
@@ -15042,7 +15393,7 @@ function escapeHtml(text) {
|
|
|
15042
15393
|
}
|
|
15043
15394
|
|
|
15044
15395
|
// src/auth/store.ts
|
|
15045
|
-
import { dirname as
|
|
15396
|
+
import { dirname as dirname12 } from "path";
|
|
15046
15397
|
async function retainOnlyCredentials(store, keepCredentialId) {
|
|
15047
15398
|
const tokens = await store.load();
|
|
15048
15399
|
const retained = Object.fromEntries(Object.entries(tokens).filter(([id]) => id === keepCredentialId));
|
|
@@ -15053,7 +15404,7 @@ function createFileTokenStore(filePath) {
|
|
|
15053
15404
|
const store = {
|
|
15054
15405
|
tokens: {},
|
|
15055
15406
|
async load() {
|
|
15056
|
-
const key = await loadOrCreateConfigKey(
|
|
15407
|
+
const key = await loadOrCreateConfigKey(dirname12(filePath));
|
|
15057
15408
|
const result = await readRecoverableFile(filePath, (raw) => parseTokens(decryptDocument(raw, key)));
|
|
15058
15409
|
const tokens = result?.value ?? {};
|
|
15059
15410
|
baseline = { ...tokens };
|
|
@@ -15061,7 +15412,7 @@ function createFileTokenStore(filePath) {
|
|
|
15061
15412
|
return tokens;
|
|
15062
15413
|
},
|
|
15063
15414
|
async save(tokens) {
|
|
15064
|
-
const key = await loadOrCreateConfigKey(
|
|
15415
|
+
const key = await loadOrCreateConfigKey(dirname12(filePath));
|
|
15065
15416
|
const removals = Object.keys(baseline).filter((providerId) => !(providerId in tokens));
|
|
15066
15417
|
const upserts = Object.fromEntries(Object.entries(tokens).filter(
|
|
15067
15418
|
([providerId, token]) => !sameToken(baseline[providerId], token)
|
|
@@ -15102,7 +15453,7 @@ function isStoredToken(value) {
|
|
|
15102
15453
|
}
|
|
15103
15454
|
|
|
15104
15455
|
// src/permissions/policy.ts
|
|
15105
|
-
import { readFile as
|
|
15456
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15106
15457
|
import { join as join22, resolve as resolve18 } from "path";
|
|
15107
15458
|
import { z as z20 } from "zod";
|
|
15108
15459
|
var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
|
|
@@ -15148,7 +15499,7 @@ async function loadPermissionPolicy(options) {
|
|
|
15148
15499
|
for (const source of sources) {
|
|
15149
15500
|
let raw;
|
|
15150
15501
|
try {
|
|
15151
|
-
raw = await
|
|
15502
|
+
raw = await readFile17(source.path, "utf8");
|
|
15152
15503
|
} catch (error) {
|
|
15153
15504
|
if (isCode6(error, "ENOENT")) continue;
|
|
15154
15505
|
throw error;
|
|
@@ -15890,10 +16241,14 @@ function createSkillResourceTool(registry) {
|
|
|
15890
16241
|
name: "SkillResource",
|
|
15891
16242
|
description: "Read a bounded resource explicitly referenced by a discovered skill; scripts are returned as data and never executed",
|
|
15892
16243
|
inputSchema: SkillResourceInput,
|
|
16244
|
+
readOnly: true,
|
|
15893
16245
|
paths: () => [],
|
|
15894
16246
|
execute: async (input, signal) => {
|
|
15895
16247
|
signal.throwIfAborted();
|
|
15896
|
-
const
|
|
16248
|
+
const requested = input.skill.trim();
|
|
16249
|
+
const alias = requested.includes(":") ? requested.slice(requested.lastIndexOf(":") + 1) : requested;
|
|
16250
|
+
const discovered = await registry.discover();
|
|
16251
|
+
const skill = discovered.find((candidate) => candidate.name === requested) ?? discovered.find((candidate) => candidate.name === alias);
|
|
15897
16252
|
if (skill === void 0) throw new Error(`Unknown skill: ${input.skill}`);
|
|
15898
16253
|
const capability = await registry.resolveResource(skill, input.reference);
|
|
15899
16254
|
const content = await registry.readResource(capability);
|
|
@@ -15910,8 +16265,8 @@ function createSkillResourceTool(registry) {
|
|
|
15910
16265
|
|
|
15911
16266
|
// src/session/store.ts
|
|
15912
16267
|
import { constants as constants7 } from "fs";
|
|
15913
|
-
import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as
|
|
15914
|
-
import { basename as basename4, dirname as
|
|
16268
|
+
import { lstat as lstat10, mkdir as mkdir14, open as open8, readFile as readFile18, readdir as readdir7, realpath as realpath7, rename as rename6, rm as rm6, stat as stat8 } from "fs/promises";
|
|
16269
|
+
import { basename as basename4, dirname as dirname13, join as join23, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
|
|
15915
16270
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15916
16271
|
import { z as z22 } from "zod";
|
|
15917
16272
|
var SESSION_VERSION = 4;
|
|
@@ -16108,7 +16463,7 @@ var SessionStore = class {
|
|
|
16108
16463
|
release: async () => {
|
|
16109
16464
|
if (released) return;
|
|
16110
16465
|
try {
|
|
16111
|
-
if (await
|
|
16466
|
+
if (await readFile18(path, "utf8") === token) await rm6(path, { force: true });
|
|
16112
16467
|
} catch (error) {
|
|
16113
16468
|
if (!isCode7(error, "ENOENT")) throw error;
|
|
16114
16469
|
}
|
|
@@ -16265,7 +16620,7 @@ var SessionStore = class {
|
|
|
16265
16620
|
}
|
|
16266
16621
|
}
|
|
16267
16622
|
async #prepareDirectory() {
|
|
16268
|
-
await assertNoSymlink3(this.#workspace,
|
|
16623
|
+
await assertNoSymlink3(this.#workspace, dirname13(this.#sessions));
|
|
16269
16624
|
await mkdir14(this.#sessions, { recursive: true, mode: 448 });
|
|
16270
16625
|
await this.#assertSafeExistingDirectory();
|
|
16271
16626
|
}
|
|
@@ -16302,14 +16657,14 @@ function storedSessionVersion(raw) {
|
|
|
16302
16657
|
}
|
|
16303
16658
|
async function reclaimStaleSessionLock(path) {
|
|
16304
16659
|
try {
|
|
16305
|
-
const token = await
|
|
16306
|
-
const metadata = await
|
|
16660
|
+
const token = await readFile18(path, "utf8");
|
|
16661
|
+
const metadata = await stat8(path);
|
|
16307
16662
|
const owner2 = parseSessionLockOwner(token);
|
|
16308
16663
|
if (owner2 !== void 0 && isProcessAlive(owner2)) return owner2;
|
|
16309
16664
|
if (owner2 === void 0 && Date.now() - metadata.mtimeMs <= INVALID_SESSION_LOCK_STALE_MS) {
|
|
16310
16665
|
throw new Error(`Session writer lock ${path} is incomplete or invalid; retry after the owning process exits`);
|
|
16311
16666
|
}
|
|
16312
|
-
if (await
|
|
16667
|
+
if (await readFile18(path, "utf8") !== token) return void 0;
|
|
16313
16668
|
const stale = `${path}.stale-${process.pid}-${randomUUID13()}`;
|
|
16314
16669
|
try {
|
|
16315
16670
|
await rename6(path, stale);
|
|
@@ -16342,10 +16697,10 @@ function isProcessAlive(pid) {
|
|
|
16342
16697
|
}
|
|
16343
16698
|
}
|
|
16344
16699
|
async function boundedRead(path, maxBytes) {
|
|
16345
|
-
const metadata = await
|
|
16700
|
+
const metadata = await stat8(path);
|
|
16346
16701
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
16347
16702
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16348
|
-
const value = await
|
|
16703
|
+
const value = await readFile18(path);
|
|
16349
16704
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
16350
16705
|
return value.toString("utf8");
|
|
16351
16706
|
}
|
|
@@ -16493,7 +16848,7 @@ async function assertNoSymlink3(root, target) {
|
|
|
16493
16848
|
const chain = [];
|
|
16494
16849
|
while (cursor !== stop) {
|
|
16495
16850
|
chain.push(cursor);
|
|
16496
|
-
cursor =
|
|
16851
|
+
cursor = dirname13(cursor);
|
|
16497
16852
|
}
|
|
16498
16853
|
for (const path of chain.reverse()) {
|
|
16499
16854
|
try {
|
|
@@ -16535,11 +16890,70 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
16535
16890
|
return output;
|
|
16536
16891
|
}
|
|
16537
16892
|
|
|
16893
|
+
// src/utils/rotation-census.ts
|
|
16894
|
+
import { mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16895
|
+
import { join as join24 } from "path";
|
|
16896
|
+
import { writeHeapSnapshot } from "v8";
|
|
16897
|
+
var CENSUS_PREFIX = "rotation-census-";
|
|
16898
|
+
var SNAPSHOT_PREFIX = "heap-";
|
|
16899
|
+
var KEEP_CENSUS_FILES = 5;
|
|
16900
|
+
var KEEP_SNAPSHOT_FILES = 2;
|
|
16901
|
+
function censusHeapBlock(reading) {
|
|
16902
|
+
const usage = process.memoryUsage();
|
|
16903
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
16904
|
+
return {
|
|
16905
|
+
heapUsedMb: mb(reading.heapUsed),
|
|
16906
|
+
heapLimitMb: mb(reading.heapLimit),
|
|
16907
|
+
rawRatio: Number(reading.ratio.toFixed(3)),
|
|
16908
|
+
...reading.verifiedRatio === void 0 ? {} : { verifiedRatio: Number(reading.verifiedRatio.toFixed(3)) },
|
|
16909
|
+
gcVerified: reading.gcVerified,
|
|
16910
|
+
rssMb: mb(usage.rss),
|
|
16911
|
+
externalMb: mb(usage.external),
|
|
16912
|
+
arrayBuffersMb: mb(usage.arrayBuffers),
|
|
16913
|
+
...reading.memoryLimit === void 0 ? {} : { memoryLimitMb: mb(reading.memoryLimit) },
|
|
16914
|
+
...reading.rssRatio === void 0 ? {} : { rssRatio: Number(reading.rssRatio.toFixed(3)) }
|
|
16915
|
+
};
|
|
16916
|
+
}
|
|
16917
|
+
function writeRotationCensus(workspace, census) {
|
|
16918
|
+
try {
|
|
16919
|
+
const directory = join24(workspace, ".flavor", "tmp");
|
|
16920
|
+
mkdirSync3(directory, { recursive: true });
|
|
16921
|
+
const path = join24(directory, `${CENSUS_PREFIX}${Date.now()}.json`);
|
|
16922
|
+
writeFileSync2(path, `${JSON.stringify(census, null, 2)}
|
|
16923
|
+
`, { encoding: "utf8", mode: 384 });
|
|
16924
|
+
prune(directory, CENSUS_PREFIX, ".json", KEEP_CENSUS_FILES);
|
|
16925
|
+
return path;
|
|
16926
|
+
} catch {
|
|
16927
|
+
return void 0;
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16930
|
+
function writeRotationHeapSnapshot(workspace) {
|
|
16931
|
+
try {
|
|
16932
|
+
const directory = join24(workspace, ".flavor", "tmp");
|
|
16933
|
+
mkdirSync3(directory, { recursive: true });
|
|
16934
|
+
const path = join24(directory, `${SNAPSHOT_PREFIX}${Date.now()}.heapsnapshot`);
|
|
16935
|
+
writeHeapSnapshot(path);
|
|
16936
|
+
prune(directory, SNAPSHOT_PREFIX, ".heapsnapshot", KEEP_SNAPSHOT_FILES);
|
|
16937
|
+
return path;
|
|
16938
|
+
} catch {
|
|
16939
|
+
return void 0;
|
|
16940
|
+
}
|
|
16941
|
+
}
|
|
16942
|
+
function prune(directory, prefix, suffix, keep) {
|
|
16943
|
+
const stale = readdirSync(directory).filter((name) => name.startsWith(prefix) && name.endsWith(suffix)).sort().slice(0, -keep);
|
|
16944
|
+
for (const name of stale) {
|
|
16945
|
+
try {
|
|
16946
|
+
unlinkSync2(join24(directory, name));
|
|
16947
|
+
} catch {
|
|
16948
|
+
}
|
|
16949
|
+
}
|
|
16950
|
+
}
|
|
16951
|
+
|
|
16538
16952
|
// src/sleep/organizer.ts
|
|
16539
16953
|
import { constants as constants8 } from "fs";
|
|
16540
16954
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
16541
16955
|
import { lstat as lstat11, mkdir as mkdir15, open as open9, readdir as readdir8, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
|
|
16542
|
-
import { isAbsolute as isAbsolute10, join as
|
|
16956
|
+
import { isAbsolute as isAbsolute10, join as join25, relative as relative15, resolve as resolve21, sep as sep15 } from "path";
|
|
16543
16957
|
import { z as z23 } from "zod";
|
|
16544
16958
|
var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
16545
16959
|
var ReviewSchema = z23.object({
|
|
@@ -16594,7 +17008,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16594
17008
|
#generate;
|
|
16595
17009
|
constructor(options) {
|
|
16596
17010
|
this.#workspace = resolve21(options.workspace);
|
|
16597
|
-
this.#sleepDirectory =
|
|
17011
|
+
this.#sleepDirectory = join25(this.#workspace, ".flavor", "sleep");
|
|
16598
17012
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
16599
17013
|
this.#generate = options.generate;
|
|
16600
17014
|
}
|
|
@@ -16603,7 +17017,7 @@ var ProjectSleepOrganizer = class {
|
|
|
16603
17017
|
signal.throwIfAborted();
|
|
16604
17018
|
await this.#prepareDirectory();
|
|
16605
17019
|
if (await this.#hasReport(date)) return { status: "exists", date };
|
|
16606
|
-
const lockPath =
|
|
17020
|
+
const lockPath = join25(this.#sleepDirectory, `.${date}.lock`);
|
|
16607
17021
|
let lock;
|
|
16608
17022
|
try {
|
|
16609
17023
|
lock = await open9(lockPath, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -16638,7 +17052,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16638
17052
|
}
|
|
16639
17053
|
if (review === void 0) throw lastError;
|
|
16640
17054
|
const filename2 = `${date}-${filenameSummary(review.title)}.md`;
|
|
16641
|
-
const path =
|
|
17055
|
+
const path = join25(this.#sleepDirectory, filename2);
|
|
16642
17056
|
const markdown = renderSleepReport(date, review, documents);
|
|
16643
17057
|
await this.#atomicWrite(path, markdown);
|
|
16644
17058
|
return { status: "written", date, path, sessionCount: documents.length };
|
|
@@ -16680,7 +17094,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16680
17094
|
});
|
|
16681
17095
|
}
|
|
16682
17096
|
async #prepareDirectory() {
|
|
16683
|
-
const flavorDirectory =
|
|
17097
|
+
const flavorDirectory = join25(this.#workspace, ".flavor");
|
|
16684
17098
|
await assertNotSymlink(flavorDirectory);
|
|
16685
17099
|
await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
|
|
16686
17100
|
await assertNotSymlink(this.#sleepDirectory);
|
|
@@ -16694,7 +17108,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
16694
17108
|
return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
|
|
16695
17109
|
}
|
|
16696
17110
|
async #atomicWrite(path, content) {
|
|
16697
|
-
const temporary =
|
|
17111
|
+
const temporary = join25(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
|
|
16698
17112
|
let handle;
|
|
16699
17113
|
try {
|
|
16700
17114
|
handle = await open9(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
|
|
@@ -16767,14 +17181,14 @@ var ProjectSleepScheduler = class {
|
|
|
16767
17181
|
const next = new Date(now);
|
|
16768
17182
|
next.setDate(next.getDate() + 1);
|
|
16769
17183
|
next.setHours(0, 0, 0, 0);
|
|
16770
|
-
const
|
|
17184
|
+
const delay2 = Math.max(1, next.getTime() - now.getTime());
|
|
16771
17185
|
const targetDate = localDateKey(now);
|
|
16772
17186
|
this.#dailyTimer = setTimeout(() => {
|
|
16773
17187
|
this.#dailyTimer = void 0;
|
|
16774
17188
|
if (this.#disposed) return;
|
|
16775
17189
|
this.#enqueue(targetDate);
|
|
16776
17190
|
this.#scheduleNextMidnight();
|
|
16777
|
-
},
|
|
17191
|
+
}, delay2);
|
|
16778
17192
|
this.#dailyTimer.unref?.();
|
|
16779
17193
|
}
|
|
16780
17194
|
#enqueue(date) {
|
|
@@ -17068,8 +17482,8 @@ function computeSessionStats(documents) {
|
|
|
17068
17482
|
|
|
17069
17483
|
// src/tools/files.ts
|
|
17070
17484
|
import { constants as constants9 } from "fs";
|
|
17071
|
-
import { mkdir as mkdir16, open as open10, readFile as
|
|
17072
|
-
import { dirname as
|
|
17485
|
+
import { mkdir as mkdir16, open as open10, readFile as readFile19, realpath as realpath9, rename as rename8, rm as rm8, stat as stat9 } from "fs/promises";
|
|
17486
|
+
import { dirname as dirname14, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve22, sep as sep16 } from "path";
|
|
17073
17487
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
17074
17488
|
import { z as z24 } from "zod";
|
|
17075
17489
|
|
|
@@ -17187,8 +17601,12 @@ var ReadInput2 = z24.object({
|
|
|
17187
17601
|
force: z24.union([z24.boolean(), z24.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
|
|
17188
17602
|
});
|
|
17189
17603
|
var WriteInput = z24.object({ path: z24.string().min(1), content: z24.string() });
|
|
17190
|
-
var EditInput = z24.object({
|
|
17191
|
-
|
|
17604
|
+
var EditInput = z24.object({
|
|
17605
|
+
path: z24.string().min(1),
|
|
17606
|
+
oldText: z24.string().min(1).describe("Copy exact current file text from Read, without displayed line numbers or diff prefixes. Include enough context for one unique match."),
|
|
17607
|
+
newText: z24.string()
|
|
17608
|
+
});
|
|
17609
|
+
var ApplyPatchInput = z24.object({ patch: z24.string().min(1).describe("One-file unified diff with --- a/path, +++ b/path and @@ hunk headers. Use /dev/null as the old path to create a file.") });
|
|
17192
17610
|
var FileObservationStore = class {
|
|
17193
17611
|
#versions = /* @__PURE__ */ new Map();
|
|
17194
17612
|
get(path) {
|
|
@@ -17217,7 +17635,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
17217
17635
|
execute: async (input, signal) => {
|
|
17218
17636
|
abortIfNeeded(signal);
|
|
17219
17637
|
const path = await guard.existing(input.path);
|
|
17220
|
-
const info = await
|
|
17638
|
+
const info = await stat9(path);
|
|
17221
17639
|
const maxBytes = input.maxBytes ?? MAX_READ_BYTES;
|
|
17222
17640
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > MAX_READ_BYTES) {
|
|
17223
17641
|
throw new Error(`maxBytes must be a positive integer no greater than ${MAX_READ_BYTES}`);
|
|
@@ -17231,9 +17649,9 @@ function createReadTool(workspace, options = {}) {
|
|
|
17231
17649
|
const windowBytes = rangeRequested ? Math.max(maxBytes, Math.min(info.size, MAX_READ_BYTES)) : maxBytes;
|
|
17232
17650
|
const contents = await readBounded2(path, windowBytes, signal, openFile2);
|
|
17233
17651
|
if (isBinary(contents)) throw new Error("Cannot read binary file as text");
|
|
17234
|
-
const textEnd = utf8SafeEnd(contents, windowBytes);
|
|
17652
|
+
const textEnd = contents.length > windowBytes ? utf8SafeEnd(contents, windowBytes) : contents.length;
|
|
17235
17653
|
const truncated = contents.length > windowBytes;
|
|
17236
|
-
const text = contents.subarray(0, textEnd)
|
|
17654
|
+
const text = decodeFileText(contents.subarray(0, textEnd)).replaceAll("\r\n", "\n");
|
|
17237
17655
|
options.observations?.set(path, versionOfStat(info));
|
|
17238
17656
|
const lines = text.split("\n");
|
|
17239
17657
|
if (lines.at(-1) === "") lines.pop();
|
|
@@ -17309,7 +17727,7 @@ function createEditTool(workspace, options = {}) {
|
|
|
17309
17727
|
const guard = createPathGuard(workspace);
|
|
17310
17728
|
return {
|
|
17311
17729
|
name: "Edit",
|
|
17312
|
-
description: "Replace one unique exact text match",
|
|
17730
|
+
description: "Replace one unique exact text match, accepting LF or CRLF input. Read the file before editing; after a mismatch, re-read with force=true and copy the current text instead of retrying the same oldText.",
|
|
17313
17731
|
inputSchema: EditInput,
|
|
17314
17732
|
paths: (input) => [guard.lexical(input.path)],
|
|
17315
17733
|
execute: async (input, signal) => {
|
|
@@ -17319,14 +17737,14 @@ function createEditTool(workspace, options = {}) {
|
|
|
17319
17737
|
const expected = await expectedMutationVersion(path, options.observations);
|
|
17320
17738
|
const contents = await readText(path);
|
|
17321
17739
|
const hasCRLF = contents.includes("\r\n");
|
|
17322
|
-
const norm = (s) =>
|
|
17740
|
+
const norm = (s) => s.replace(/\r\n/g, "\n");
|
|
17323
17741
|
const contentsLF = norm(contents);
|
|
17324
17742
|
const oldTextLF = norm(input.oldText);
|
|
17325
17743
|
const newTextLF = norm(input.newText);
|
|
17326
17744
|
const first = contentsLF.indexOf(oldTextLF);
|
|
17327
|
-
const second = first < 0 ? -1 : contentsLF.indexOf(oldTextLF, first +
|
|
17745
|
+
const second = first < 0 ? -1 : contentsLF.indexOf(oldTextLF, first + 1);
|
|
17328
17746
|
if (first < 0 || second >= 0) {
|
|
17329
|
-
const diagnosis = buildEditDiagnosis(contentsLF, oldTextLF,
|
|
17747
|
+
const diagnosis = buildEditDiagnosis(contentsLF, oldTextLF, second);
|
|
17330
17748
|
throw new Error(diagnosis);
|
|
17331
17749
|
}
|
|
17332
17750
|
const updatedLF = contentsLF.slice(0, first) + newTextLF + contentsLF.slice(first + oldTextLF.length);
|
|
@@ -17346,7 +17764,7 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
17346
17764
|
const guard = createPathGuard(workspace);
|
|
17347
17765
|
return {
|
|
17348
17766
|
name: "ApplyPatch",
|
|
17349
|
-
description: "Apply a workspace-limited unified diff, relocating hunks only by unique exact context",
|
|
17767
|
+
description: "Apply a single-file workspace-limited unified diff, relocating hunks only by unique exact context. Submit separate calls for multiple files.",
|
|
17350
17768
|
inputSchema: ApplyPatchInput,
|
|
17351
17769
|
paths: (input) => parsePatch(input.patch).map((file) => guard.lexical(file.path)),
|
|
17352
17770
|
execute: async (input, signal) => {
|
|
@@ -17411,7 +17829,7 @@ function createPathGuard(workspace) {
|
|
|
17411
17829
|
break;
|
|
17412
17830
|
} catch (error) {
|
|
17413
17831
|
if (!isMissing3(error)) throw error;
|
|
17414
|
-
const parent =
|
|
17832
|
+
const parent = dirname14(ancestor);
|
|
17415
17833
|
if (parent === ancestor) throw error;
|
|
17416
17834
|
ancestor = parent;
|
|
17417
17835
|
}
|
|
@@ -17422,12 +17840,19 @@ function createPathGuard(workspace) {
|
|
|
17422
17840
|
}
|
|
17423
17841
|
async function atomicWrite(path, content, signal, expectedVersion) {
|
|
17424
17842
|
abortIfNeeded(signal);
|
|
17425
|
-
await mkdir16(
|
|
17843
|
+
await mkdir16(dirname14(path), { recursive: true });
|
|
17426
17844
|
const temporary = `${path}.${randomUUID15()}.tmp`;
|
|
17845
|
+
let mode;
|
|
17846
|
+
try {
|
|
17847
|
+
mode = (await stat9(path)).mode & 511;
|
|
17848
|
+
} catch (error) {
|
|
17849
|
+
if (!isMissing3(error)) throw error;
|
|
17850
|
+
}
|
|
17427
17851
|
try {
|
|
17428
17852
|
const handle = await open10(temporary, constants9.O_CREAT | constants9.O_EXCL | constants9.O_WRONLY, 384);
|
|
17429
17853
|
try {
|
|
17430
17854
|
await handle.writeFile(content, "utf8");
|
|
17855
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
17431
17856
|
await handle.sync();
|
|
17432
17857
|
} finally {
|
|
17433
17858
|
await handle.close();
|
|
@@ -17442,9 +17867,16 @@ async function atomicWrite(path, content, signal, expectedVersion) {
|
|
|
17442
17867
|
}
|
|
17443
17868
|
}
|
|
17444
17869
|
async function readText(path) {
|
|
17445
|
-
const contents = await
|
|
17870
|
+
const contents = await readFile19(path);
|
|
17446
17871
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
17447
|
-
return contents
|
|
17872
|
+
return decodeFileText(contents);
|
|
17873
|
+
}
|
|
17874
|
+
function decodeFileText(contents) {
|
|
17875
|
+
try {
|
|
17876
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(contents);
|
|
17877
|
+
} catch {
|
|
17878
|
+
throw new Error("File is not valid UTF-8 text. Convert its encoding before using Read/Edit/ApplyPatch to avoid corrupting its contents.");
|
|
17879
|
+
}
|
|
17448
17880
|
}
|
|
17449
17881
|
var ABSENT_VERSION = "absent";
|
|
17450
17882
|
async function expectedMutationVersion(path, observations) {
|
|
@@ -17461,7 +17893,7 @@ async function assertMutationVersion(path, expected, observations) {
|
|
|
17461
17893
|
}
|
|
17462
17894
|
async function fileVersion(path) {
|
|
17463
17895
|
try {
|
|
17464
|
-
return versionOfStat(await
|
|
17896
|
+
return versionOfStat(await stat9(path));
|
|
17465
17897
|
} catch (error) {
|
|
17466
17898
|
if (isMissing3(error)) return ABSENT_VERSION;
|
|
17467
17899
|
throw error;
|
|
@@ -17472,7 +17904,7 @@ function versionOfStat(info) {
|
|
|
17472
17904
|
}
|
|
17473
17905
|
async function readOptionalPresentationText(path) {
|
|
17474
17906
|
try {
|
|
17475
|
-
const contents = await
|
|
17907
|
+
const contents = await readFile19(path);
|
|
17476
17908
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
17477
17909
|
} catch (error) {
|
|
17478
17910
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -17654,11 +18086,12 @@ function patchPath(header) {
|
|
|
17654
18086
|
const raw = header.split(" ", 1)[0].trim();
|
|
17655
18087
|
return raw.startsWith("a/") || raw.startsWith("b/") ? raw.slice(2) : raw;
|
|
17656
18088
|
}
|
|
17657
|
-
function buildEditDiagnosis(contentsLF, oldTextLF,
|
|
18089
|
+
function buildEditDiagnosis(contentsLF, oldTextLF, second) {
|
|
17658
18090
|
let diagnosis = "oldText must match exactly once";
|
|
17659
18091
|
if (second >= 0) {
|
|
17660
|
-
|
|
17661
|
-
|
|
18092
|
+
let count = 0;
|
|
18093
|
+
for (let index = contentsLF.indexOf(oldTextLF); index >= 0; index = contentsLF.indexOf(oldTextLF, index + 1)) count += 1;
|
|
18094
|
+
diagnosis += ` \u2014 matched ${count} times in the file. No changes made. Include more surrounding text to select one occurrence.`;
|
|
17662
18095
|
return diagnosis;
|
|
17663
18096
|
}
|
|
17664
18097
|
const oldLines = oldTextLF.split("\n");
|
|
@@ -17694,7 +18127,8 @@ ${snippet}
|
|
|
17694
18127
|
First lines of file:
|
|
17695
18128
|
${fileSnippet}`;
|
|
17696
18129
|
}
|
|
17697
|
-
return diagnosis
|
|
18130
|
+
return `${diagnosis}
|
|
18131
|
+
No changes made. Use Read with force=true to retrieve current text, or Grep with fixedStrings=true to locate it. Copy oldText exactly without line-number prefixes; do not repeat the same failed edit.`;
|
|
17698
18132
|
}
|
|
17699
18133
|
function applyHunks(original, hunks) {
|
|
17700
18134
|
const hasCRLF = original.includes("\r\n");
|
|
@@ -17785,6 +18219,19 @@ import { isIP } from "net";
|
|
|
17785
18219
|
import { request as httpRequest } from "http";
|
|
17786
18220
|
import { request as httpsRequest } from "https";
|
|
17787
18221
|
import { z as z25 } from "zod";
|
|
18222
|
+
|
|
18223
|
+
// src/utils/abort.ts
|
|
18224
|
+
function waitWithSignal(operation, signal) {
|
|
18225
|
+
if (signal === void 0) return operation;
|
|
18226
|
+
return new Promise((resolve32, reject) => {
|
|
18227
|
+
const abort2 = () => reject(signal.reason);
|
|
18228
|
+
if (signal.aborted) abort2();
|
|
18229
|
+
else signal.addEventListener("abort", abort2, { once: true });
|
|
18230
|
+
operation.then(resolve32, reject).finally(() => signal.removeEventListener("abort", abort2));
|
|
18231
|
+
});
|
|
18232
|
+
}
|
|
18233
|
+
|
|
18234
|
+
// src/tools/web.ts
|
|
17788
18235
|
var WebFetchInput = z25.object({
|
|
17789
18236
|
url: z25.string().url().max(8192),
|
|
17790
18237
|
timeoutMs: z25.coerce.number().int().positive().max(12e4).optional(),
|
|
@@ -17830,7 +18277,12 @@ function createWebSearchTool(provider = createDefaultWebSearchProvider()) {
|
|
|
17830
18277
|
summarize: (input) => input.query,
|
|
17831
18278
|
presentCall: (input) => ({ kind: "web", title: `Searching: ${input.query}` }),
|
|
17832
18279
|
permissions: () => ({ paths: [] }),
|
|
17833
|
-
execute: async (input, signal) =>
|
|
18280
|
+
execute: async (input, signal) => {
|
|
18281
|
+
signal.throwIfAborted();
|
|
18282
|
+
const maxResults = input.maxResults ?? 8;
|
|
18283
|
+
const results = await waitWithSignal(provider.search(input.query, maxResults, signal), signal);
|
|
18284
|
+
return { query: input.query, results: [...results].slice(0, maxResults) };
|
|
18285
|
+
},
|
|
17834
18286
|
renderForModel: (output) => output.results.length === 0 ? `No web results found for: ${output.query}` : output.results.map((item, index) => `${index + 1}. ${item.title}
|
|
17835
18287
|
${item.url}
|
|
17836
18288
|
${item.snippet}`).join("\n\n"),
|
|
@@ -17875,10 +18327,14 @@ var MultiSourceWebSearchProvider = class {
|
|
|
17875
18327
|
}
|
|
17876
18328
|
};
|
|
17877
18329
|
async function fetchPublic(initialUrl, options) {
|
|
18330
|
+
options.signal.throwIfAborted();
|
|
18331
|
+
const deadline = AbortSignal.timeout(options.timeoutMs);
|
|
18332
|
+
options = { ...options, signal: AbortSignal.any([options.signal, deadline]) };
|
|
17878
18333
|
let current = new URL(initialUrl);
|
|
17879
18334
|
for (let redirects = 0; redirects <= 5; redirects += 1) {
|
|
18335
|
+
options.signal.throwIfAborted();
|
|
17880
18336
|
validateUrl(current);
|
|
17881
|
-
const addresses = await lookup(normalizedHostname(current.hostname), { all: true, verbatim: true });
|
|
18337
|
+
const addresses = await waitWithSignal(lookup(normalizedHostname(current.hostname), { all: true, verbatim: true }), options.signal);
|
|
17882
18338
|
if (addresses.length === 0) throw new Error("Host did not resolve");
|
|
17883
18339
|
if (addresses.some((entry2) => isBlockedResolvedAddress(entry2.address, current.hostname))) {
|
|
17884
18340
|
throw new Error("Blocked private or special network destination");
|
|
@@ -17890,7 +18346,13 @@ async function fetchPublic(initialUrl, options) {
|
|
|
17890
18346
|
continue;
|
|
17891
18347
|
}
|
|
17892
18348
|
const contentType = response.contentType;
|
|
17893
|
-
|
|
18349
|
+
let decoder;
|
|
18350
|
+
try {
|
|
18351
|
+
decoder = new TextDecoder(charset(contentType), { fatal: false });
|
|
18352
|
+
} catch {
|
|
18353
|
+
decoder = new TextDecoder("utf-8");
|
|
18354
|
+
}
|
|
18355
|
+
const decoded = decoder.decode(response.body);
|
|
17894
18356
|
const content = !options.rawHtml && /text\/html|application\/xhtml\+xml/i.test(contentType) ? htmlToText(decoded) : decoded;
|
|
17895
18357
|
return { url: current.href, status: response.status, contentType, content, truncated: response.truncated };
|
|
17896
18358
|
}
|
|
@@ -17925,8 +18387,8 @@ async function requestAddress(url, address, options) {
|
|
|
17925
18387
|
const status = response.statusCode ?? 0;
|
|
17926
18388
|
const location = response.headers.location;
|
|
17927
18389
|
if (location !== void 0 && [301, 302, 303, 307, 308].includes(status)) {
|
|
17928
|
-
response.resume();
|
|
17929
18390
|
finish({ status, contentType: "", body: new Uint8Array(), truncated: false, redirect: location });
|
|
18391
|
+
response.destroy();
|
|
17930
18392
|
return;
|
|
17931
18393
|
}
|
|
17932
18394
|
const chunks = [];
|
|
@@ -17942,7 +18404,11 @@ async function requestAddress(url, address, options) {
|
|
|
17942
18404
|
const accepted = chunk.subarray(0, options.maxBytes - bytes);
|
|
17943
18405
|
chunks.push(accepted);
|
|
17944
18406
|
bytes += accepted.length;
|
|
17945
|
-
if (accepted.length < chunk.length)
|
|
18407
|
+
if (accepted.length < chunk.length) {
|
|
18408
|
+
truncated = true;
|
|
18409
|
+
finish({ status, contentType: String(response.headers["content-type"] ?? "application/octet-stream"), body: Buffer.concat(chunks), truncated });
|
|
18410
|
+
response.destroy();
|
|
18411
|
+
}
|
|
17946
18412
|
});
|
|
17947
18413
|
response.once("end", () => finish({
|
|
17948
18414
|
status,
|
|
@@ -18088,11 +18554,11 @@ function charset(contentType) {
|
|
|
18088
18554
|
}
|
|
18089
18555
|
|
|
18090
18556
|
// src/tools/lsp.ts
|
|
18091
|
-
import { spawn as
|
|
18557
|
+
import { spawn as spawn8 } from "child_process";
|
|
18092
18558
|
import { existsSync as existsSync5 } from "fs";
|
|
18093
|
-
import { access as access2, constants as constants10, readFile as
|
|
18094
|
-
import { basename as basename5, dirname as
|
|
18095
|
-
import { fileURLToPath as
|
|
18559
|
+
import { access as access2, constants as constants10, readFile as readFile20 } from "fs/promises";
|
|
18560
|
+
import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute12, relative as relative17, resolve as resolve23, sep as sep17 } from "path";
|
|
18561
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
|
|
18096
18562
|
import { z as z26 } from "zod";
|
|
18097
18563
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
18098
18564
|
var LspConnection = class {
|
|
@@ -18104,12 +18570,14 @@ var LspConnection = class {
|
|
|
18104
18570
|
#nextId = 1;
|
|
18105
18571
|
#disposed = false;
|
|
18106
18572
|
#initError;
|
|
18573
|
+
#shutdownPromise;
|
|
18107
18574
|
constructor(command, args, cwd) {
|
|
18108
18575
|
const invocation = prepareSpawnInvocation(command, args, { cwd });
|
|
18109
|
-
this.#process =
|
|
18576
|
+
this.#process = spawn8(invocation.command, invocation.args, {
|
|
18110
18577
|
cwd,
|
|
18111
18578
|
stdio: ["pipe", "pipe", "pipe"],
|
|
18112
18579
|
windowsHide: true,
|
|
18580
|
+
detached: process.platform !== "win32",
|
|
18113
18581
|
...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
|
18114
18582
|
});
|
|
18115
18583
|
this.#process.on("error", (err) => {
|
|
@@ -18118,9 +18586,15 @@ var LspConnection = class {
|
|
|
18118
18586
|
});
|
|
18119
18587
|
this.#process.once("exit", (code, signal) => {
|
|
18120
18588
|
const reason = signal !== null ? `LSP server "${command}" exited with signal ${signal}` : `LSP server "${command}" exited with code ${code}`;
|
|
18121
|
-
this.#
|
|
18589
|
+
this.#initError = new Error(reason);
|
|
18590
|
+
this.#rejectAll(this.#initError);
|
|
18122
18591
|
});
|
|
18123
18592
|
this.#process.stdout.on("data", (chunk) => this.#buffer.feed(chunk, this.#onMessage));
|
|
18593
|
+
this.#process.stderr.resume();
|
|
18594
|
+
this.#process.stdin.on("error", (error) => {
|
|
18595
|
+
this.#initError = error;
|
|
18596
|
+
this.#rejectAll(error);
|
|
18597
|
+
});
|
|
18124
18598
|
}
|
|
18125
18599
|
async initialize(rootUri) {
|
|
18126
18600
|
if (this.#initError !== void 0) throw this.#initError;
|
|
@@ -18142,67 +18616,84 @@ var LspConnection = class {
|
|
|
18142
18616
|
if (initResult === void 0) throw new Error("LSP initialize returned no capabilities");
|
|
18143
18617
|
this.#notify("initialized", {});
|
|
18144
18618
|
}
|
|
18145
|
-
async findReferences(uri, line, character) {
|
|
18619
|
+
async findReferences(uri, line, character, signal) {
|
|
18620
|
+
signal?.throwIfAborted();
|
|
18146
18621
|
await this.#syncDocument(uri);
|
|
18147
18622
|
const result = await this.#request("textDocument/references", {
|
|
18148
18623
|
textDocument: { uri },
|
|
18149
18624
|
position: { line, character },
|
|
18150
18625
|
context: { includeDeclaration: false }
|
|
18151
|
-
});
|
|
18626
|
+
}, signal);
|
|
18152
18627
|
if (result === null || result === void 0) return [];
|
|
18153
18628
|
if (!Array.isArray(result)) return [];
|
|
18154
18629
|
return result.filter(isLocation);
|
|
18155
18630
|
}
|
|
18156
|
-
async hover(uri, line, character) {
|
|
18631
|
+
async hover(uri, line, character, signal) {
|
|
18632
|
+
signal?.throwIfAborted();
|
|
18157
18633
|
await this.#syncDocument(uri);
|
|
18158
18634
|
const result = await this.#request("textDocument/hover", {
|
|
18159
18635
|
textDocument: { uri },
|
|
18160
18636
|
position: { line, character }
|
|
18161
|
-
});
|
|
18637
|
+
}, signal);
|
|
18162
18638
|
if (result === null || result === void 0) return null;
|
|
18163
18639
|
if (typeof result === "object" && "contents" in result) {
|
|
18164
18640
|
return result;
|
|
18165
18641
|
}
|
|
18166
18642
|
return null;
|
|
18167
18643
|
}
|
|
18168
|
-
async diagnostics(uri) {
|
|
18644
|
+
async diagnostics(uri, signal) {
|
|
18645
|
+
signal?.throwIfAborted();
|
|
18169
18646
|
await this.#syncDocument(uri);
|
|
18170
18647
|
const result = await this.#request("textDocument/diagnostic", {
|
|
18171
18648
|
textDocument: { uri }
|
|
18172
|
-
});
|
|
18649
|
+
}, signal);
|
|
18173
18650
|
if (result === null || result === void 0) return [];
|
|
18174
18651
|
const items = result?.items ?? result;
|
|
18175
18652
|
if (!Array.isArray(items)) return [];
|
|
18176
18653
|
return items.filter(isDiagnostic);
|
|
18177
18654
|
}
|
|
18178
|
-
|
|
18179
|
-
if (this.#
|
|
18655
|
+
shutdown() {
|
|
18656
|
+
if (this.#shutdownPromise !== void 0) return this.#shutdownPromise;
|
|
18180
18657
|
this.#disposed = true;
|
|
18181
|
-
try {
|
|
18182
|
-
await this.#request("shutdown", null);
|
|
18183
|
-
} catch {
|
|
18184
|
-
}
|
|
18185
|
-
this.#notify("exit", null);
|
|
18186
|
-
this.#process.kill();
|
|
18187
18658
|
this.#rejectAll(new Error("LSP connection disposed"));
|
|
18659
|
+
this.#shutdownPromise = (async () => {
|
|
18660
|
+
await terminateProcessTree(this.#process.pid);
|
|
18661
|
+
this.#process.stdin.destroy();
|
|
18662
|
+
this.#process.stdout.destroy();
|
|
18663
|
+
this.#process.stderr.destroy();
|
|
18664
|
+
})();
|
|
18665
|
+
return this.#shutdownPromise;
|
|
18188
18666
|
}
|
|
18189
18667
|
// -- JSON-RPC core -------------------------------------------------------
|
|
18190
|
-
async #request(method, params) {
|
|
18668
|
+
async #request(method, params, signal) {
|
|
18191
18669
|
if (this.#disposed) throw new Error("LSP connection disposed");
|
|
18670
|
+
if (this.#initError !== void 0) throw this.#initError;
|
|
18671
|
+
signal?.throwIfAborted();
|
|
18192
18672
|
const id = this.#nextId++;
|
|
18193
18673
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18674
|
+
let timer;
|
|
18675
|
+
let onAbort;
|
|
18676
|
+
const promise = new Promise((resolve32, reject) => {
|
|
18677
|
+
this.#pending.set(id, { resolve: resolve32, reject });
|
|
18678
|
+
timer = setTimeout(() => reject(new Error(`LSP request "${method}" timed out after ${LSP_REQUEST_TIMEOUT_MS}ms`)), LSP_REQUEST_TIMEOUT_MS);
|
|
18679
|
+
timer.unref();
|
|
18680
|
+
onAbort = () => {
|
|
18681
|
+
try {
|
|
18682
|
+
this.#notify("$/cancelRequest", { id });
|
|
18683
|
+
} catch {
|
|
18201
18684
|
}
|
|
18202
|
-
|
|
18685
|
+
reject(signal?.reason);
|
|
18686
|
+
};
|
|
18687
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
18203
18688
|
});
|
|
18204
|
-
|
|
18205
|
-
|
|
18689
|
+
try {
|
|
18690
|
+
this.#send(request);
|
|
18691
|
+
return await promise;
|
|
18692
|
+
} finally {
|
|
18693
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
18694
|
+
if (onAbort !== void 0) signal?.removeEventListener("abort", onAbort);
|
|
18695
|
+
this.#pending.delete(id);
|
|
18696
|
+
}
|
|
18206
18697
|
}
|
|
18207
18698
|
#notify(method, params) {
|
|
18208
18699
|
const notification = { jsonrpc: "2.0", method, params };
|
|
@@ -18222,6 +18713,7 @@ var LspConnection = class {
|
|
|
18222
18713
|
} catch {
|
|
18223
18714
|
return;
|
|
18224
18715
|
}
|
|
18716
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
18225
18717
|
if ("method" in parsed) {
|
|
18226
18718
|
if ("id" in parsed) this.#handleServerRequest(parsed);
|
|
18227
18719
|
return;
|
|
@@ -18257,8 +18749,8 @@ var LspConnection = class {
|
|
|
18257
18749
|
async #syncDocumentNow(uri) {
|
|
18258
18750
|
const url = new URL(uri);
|
|
18259
18751
|
if (url.protocol !== "file:") return;
|
|
18260
|
-
const filePath =
|
|
18261
|
-
const text = await
|
|
18752
|
+
const filePath = fileURLToPath2(url);
|
|
18753
|
+
const text = await readFile20(filePath, "utf8");
|
|
18262
18754
|
const current = this.#documents.get(uri);
|
|
18263
18755
|
if (current === void 0) {
|
|
18264
18756
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -18310,7 +18802,7 @@ var LspBuffer = class {
|
|
|
18310
18802
|
}
|
|
18311
18803
|
};
|
|
18312
18804
|
var TYPESCRIPT_CLI = resolve23(
|
|
18313
|
-
|
|
18805
|
+
dirname15(fileURLToPath2(import.meta.resolve("typescript/package.json"))),
|
|
18314
18806
|
"lib",
|
|
18315
18807
|
"tsc.js"
|
|
18316
18808
|
);
|
|
@@ -18345,6 +18837,8 @@ var KNOWN_SERVERS = [
|
|
|
18345
18837
|
}
|
|
18346
18838
|
];
|
|
18347
18839
|
var RealLspManager = class {
|
|
18840
|
+
#disposed = false;
|
|
18841
|
+
#disposal;
|
|
18348
18842
|
#workspace;
|
|
18349
18843
|
#connections = /* @__PURE__ */ new Map();
|
|
18350
18844
|
#pendingStarts = /* @__PURE__ */ new Map();
|
|
@@ -18356,27 +18850,32 @@ var RealLspManager = class {
|
|
|
18356
18850
|
this.#onStatus = options.onStatus;
|
|
18357
18851
|
}
|
|
18358
18852
|
dispose() {
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18853
|
+
if (this.#disposal !== void 0) return this.#disposal;
|
|
18854
|
+
this.#disposed = true;
|
|
18855
|
+
const shutdowns = [...this.#connections.values()].map((connection) => connection.shutdown());
|
|
18856
|
+
shutdowns.push(...[...this.#pendingStarts.values()].map(async (start) => {
|
|
18857
|
+
const connection = await start;
|
|
18858
|
+
await connection.shutdown();
|
|
18859
|
+
}));
|
|
18363
18860
|
this.#connections.clear();
|
|
18364
18861
|
this.#pendingStarts.clear();
|
|
18862
|
+
this.#disposal = Promise.allSettled(shutdowns).then(() => void 0);
|
|
18863
|
+
return this.#disposal;
|
|
18365
18864
|
}
|
|
18366
|
-
async findReferences(uri, line, character) {
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
return connection.findReferences(uri, line, character);
|
|
18865
|
+
async findReferences(uri, line, character, signal) {
|
|
18866
|
+
signal?.throwIfAborted();
|
|
18867
|
+
const connection = await waitWithSignal(this.#getConnection(uri), signal);
|
|
18868
|
+
return connection.findReferences(uri, line, character, signal);
|
|
18370
18869
|
}
|
|
18371
|
-
async hover(uri, line, character) {
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
return connection.hover(uri, line, character);
|
|
18870
|
+
async hover(uri, line, character, signal) {
|
|
18871
|
+
signal?.throwIfAborted();
|
|
18872
|
+
const connection = await waitWithSignal(this.#getConnection(uri), signal);
|
|
18873
|
+
return connection.hover(uri, line, character, signal);
|
|
18375
18874
|
}
|
|
18376
|
-
async diagnostics(uri) {
|
|
18377
|
-
|
|
18378
|
-
|
|
18379
|
-
return connection.diagnostics(uri);
|
|
18875
|
+
async diagnostics(uri, signal) {
|
|
18876
|
+
signal?.throwIfAborted();
|
|
18877
|
+
const connection = await waitWithSignal(this.#getConnection(uri), signal);
|
|
18878
|
+
return connection.diagnostics(uri, signal);
|
|
18380
18879
|
}
|
|
18381
18880
|
#languageForUri(uri) {
|
|
18382
18881
|
try {
|
|
@@ -18392,20 +18891,25 @@ var RealLspManager = class {
|
|
|
18392
18891
|
return void 0;
|
|
18393
18892
|
}
|
|
18394
18893
|
async #getConnection(uri) {
|
|
18894
|
+
if (this.#disposed) throw new Error("LSP manager is disposed");
|
|
18395
18895
|
const language = this.#languageForUri(uri);
|
|
18396
|
-
if (language === void 0)
|
|
18896
|
+
if (language === void 0) throw new Error(`No language server configured for ${uri}; use Read/Grep or the project compiler`);
|
|
18397
18897
|
const existing = this.#connections.get(language);
|
|
18398
18898
|
if (existing !== void 0) return existing;
|
|
18399
18899
|
const pending = this.#pendingStarts.get(language);
|
|
18400
18900
|
if (pending !== void 0) return pending;
|
|
18401
18901
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
18402
|
-
if (config === void 0)
|
|
18902
|
+
if (config === void 0) throw new Error(`No language server configured for ${language}`);
|
|
18403
18903
|
const hasRoot = config.rootFiles.some((file) => existsSync5(resolve23(this.#workspace, file)));
|
|
18404
|
-
if (!hasRoot)
|
|
18904
|
+
if (!hasRoot) throw new Error(`Cannot start ${language} language server: workspace needs ${config.rootFiles.join(" or ")}`);
|
|
18405
18905
|
const startPromise = this.#startServer(config);
|
|
18406
18906
|
this.#pendingStarts.set(language, startPromise);
|
|
18407
18907
|
try {
|
|
18408
18908
|
const connection = await startPromise;
|
|
18909
|
+
if (this.#disposed) {
|
|
18910
|
+
await connection.shutdown();
|
|
18911
|
+
throw new Error("LSP manager is disposed");
|
|
18912
|
+
}
|
|
18409
18913
|
this.#connections.set(language, connection);
|
|
18410
18914
|
return connection;
|
|
18411
18915
|
} finally {
|
|
@@ -18423,6 +18927,7 @@ var RealLspManager = class {
|
|
|
18423
18927
|
await connection.initialize(rootUri);
|
|
18424
18928
|
this.#onStatus?.(`${config.language} Language Server ready (${config.command})`);
|
|
18425
18929
|
} catch (error) {
|
|
18930
|
+
await connection.shutdown();
|
|
18426
18931
|
this.#onStatus?.(`${config.language} Language Server failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
18427
18932
|
throw error;
|
|
18428
18933
|
}
|
|
@@ -18431,13 +18936,13 @@ var RealLspManager = class {
|
|
|
18431
18936
|
};
|
|
18432
18937
|
var LspFindRefsInput = z26.object({
|
|
18433
18938
|
file: z26.string().min(1),
|
|
18434
|
-
line: z26.coerce.number().int().nonnegative(),
|
|
18435
|
-
character: z26.coerce.number().int().nonnegative()
|
|
18939
|
+
line: z26.coerce.number().int().nonnegative().describe("Zero-based line number (first line is 0)."),
|
|
18940
|
+
character: z26.coerce.number().int().nonnegative().describe("Zero-based UTF-16 character offset.")
|
|
18436
18941
|
});
|
|
18437
18942
|
var LspHoverInput = z26.object({
|
|
18438
18943
|
file: z26.string().min(1),
|
|
18439
|
-
line: z26.coerce.number().int().nonnegative(),
|
|
18440
|
-
character: z26.coerce.number().int().nonnegative()
|
|
18944
|
+
line: z26.coerce.number().int().nonnegative().describe("Zero-based line number (first line is 0)."),
|
|
18945
|
+
character: z26.coerce.number().int().nonnegative().describe("Zero-based UTF-16 character offset.")
|
|
18441
18946
|
});
|
|
18442
18947
|
var LspDiagnosticsInput = z26.object({
|
|
18443
18948
|
file: z26.string().min(1)
|
|
@@ -18463,10 +18968,11 @@ function createLspTools(workspace, options = {}) {
|
|
|
18463
18968
|
inputSchema: LspFindRefsInput,
|
|
18464
18969
|
paths: (input) => [guard(input.file)],
|
|
18465
18970
|
summarize: (input) => `${basename5(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
|
|
18466
|
-
execute: async (input,
|
|
18971
|
+
execute: async (input, signal) => {
|
|
18972
|
+
signal.throwIfAborted();
|
|
18467
18973
|
const path = guard(input.file);
|
|
18468
18974
|
const uri = toUri(path);
|
|
18469
|
-
const refs = await manager.findReferences(uri, input.line, input.character);
|
|
18975
|
+
const refs = await waitWithSignal(manager.findReferences(uri, input.line, input.character, signal), signal);
|
|
18470
18976
|
if (refs.length === 0) return `No references found at ${input.file}:${input.line}:${input.character}`;
|
|
18471
18977
|
return refs.map((loc) => {
|
|
18472
18978
|
const filePath = uriToPath(loc.uri);
|
|
@@ -18480,12 +18986,14 @@ function createLspTools(workspace, options = {}) {
|
|
|
18480
18986
|
inputSchema: LspHoverInput,
|
|
18481
18987
|
paths: (input) => [guard(input.file)],
|
|
18482
18988
|
summarize: (input) => `${basename5(input.file)}:${input.line}:${input.character}${langTag(input.file)}`,
|
|
18483
|
-
execute: async (input,
|
|
18989
|
+
execute: async (input, signal) => {
|
|
18990
|
+
signal.throwIfAborted();
|
|
18484
18991
|
const path = guard(input.file);
|
|
18485
18992
|
const uri = toUri(path);
|
|
18486
|
-
const info = await manager.hover(uri, input.line, input.character);
|
|
18993
|
+
const info = await waitWithSignal(manager.hover(uri, input.line, input.character, signal), signal);
|
|
18487
18994
|
if (info === null) return `No hover information at ${input.file}:${input.line}:${input.character}`;
|
|
18488
|
-
const
|
|
18995
|
+
const parts = Array.isArray(info.contents) ? info.contents : [info.contents];
|
|
18996
|
+
const contents = parts.map((part) => typeof part === "string" ? part : part.value).join("\n\n");
|
|
18489
18997
|
return contents;
|
|
18490
18998
|
}
|
|
18491
18999
|
};
|
|
@@ -18495,10 +19003,11 @@ function createLspTools(workspace, options = {}) {
|
|
|
18495
19003
|
inputSchema: LspDiagnosticsInput,
|
|
18496
19004
|
paths: (input) => [guard(input.file)],
|
|
18497
19005
|
summarize: (input) => `${basename5(input.file)}${langTag(input.file)}`,
|
|
18498
|
-
execute: async (input,
|
|
19006
|
+
execute: async (input, signal) => {
|
|
19007
|
+
signal.throwIfAborted();
|
|
18499
19008
|
const path = guard(input.file);
|
|
18500
19009
|
const uri = toUri(path);
|
|
18501
|
-
const diags = await manager.diagnostics(uri);
|
|
19010
|
+
const diags = await waitWithSignal(manager.diagnostics(uri, signal), signal);
|
|
18502
19011
|
if (diags.length === 0) return `No diagnostics for ${input.file}`;
|
|
18503
19012
|
return diags.map((d) => {
|
|
18504
19013
|
const severity = severityLabel(d.severity);
|
|
@@ -18517,6 +19026,14 @@ function languageIdForPath(filePath) {
|
|
|
18517
19026
|
return "typescript";
|
|
18518
19027
|
case ".cts":
|
|
18519
19028
|
return "typescript";
|
|
19029
|
+
case ".py":
|
|
19030
|
+
case ".pyi":
|
|
19031
|
+
case ".pyx":
|
|
19032
|
+
return "python";
|
|
19033
|
+
case ".rs":
|
|
19034
|
+
return "rust";
|
|
19035
|
+
case ".go":
|
|
19036
|
+
return "go";
|
|
18520
19037
|
default:
|
|
18521
19038
|
return "typescript";
|
|
18522
19039
|
}
|
|
@@ -18528,7 +19045,7 @@ function isWithin7(root, candidate) {
|
|
|
18528
19045
|
function uriToPath(uri) {
|
|
18529
19046
|
try {
|
|
18530
19047
|
const url = new URL(uri);
|
|
18531
|
-
if (url.protocol === "file:") return
|
|
19048
|
+
if (url.protocol === "file:") return fileURLToPath2(url);
|
|
18532
19049
|
} catch {
|
|
18533
19050
|
}
|
|
18534
19051
|
return uri;
|
|
@@ -18555,12 +19072,14 @@ function severityLabel(severity) {
|
|
|
18555
19072
|
}
|
|
18556
19073
|
|
|
18557
19074
|
// src/git/service.ts
|
|
18558
|
-
async function git2(workspace, args, timeoutMs = 3e4) {
|
|
18559
|
-
|
|
19075
|
+
async function git2(workspace, args, timeoutMs = 3e4, signal) {
|
|
19076
|
+
signal?.throwIfAborted();
|
|
19077
|
+
const result = await execFileNoThrow("git", ["-C", workspace, ...args], { timeout: timeoutMs, useCwd: false, ...signal === void 0 ? {} : { signal } });
|
|
19078
|
+
signal?.throwIfAborted();
|
|
18560
19079
|
return { ok: result.code === 0, stdout: result.stdout, stderr: result.stderr, code: result.code };
|
|
18561
19080
|
}
|
|
18562
|
-
async function isGitRepository(workspace) {
|
|
18563
|
-
const result = await git2(workspace, ["rev-parse", "--is-inside-work-tree"], 2e3);
|
|
19081
|
+
async function isGitRepository(workspace, signal) {
|
|
19082
|
+
const result = await git2(workspace, ["rev-parse", "--is-inside-work-tree"], 2e3, signal);
|
|
18564
19083
|
return result.ok && result.stdout.trim() === "true";
|
|
18565
19084
|
}
|
|
18566
19085
|
function gitFailure(result, action) {
|
|
@@ -18597,19 +19116,19 @@ async function changeSummary(workspace) {
|
|
|
18597
19116
|
};
|
|
18598
19117
|
}
|
|
18599
19118
|
async function stagedDiff(workspace, maxChars = 96e3) {
|
|
18600
|
-
const
|
|
19119
|
+
const stat12 = await git2(workspace, ["diff", "--staged", "--stat"]);
|
|
18601
19120
|
const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
|
|
18602
19121
|
if (!diff.ok) throw gitFailure(diff, "diff --staged");
|
|
18603
19122
|
const text = diff.stdout;
|
|
18604
19123
|
return {
|
|
18605
19124
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
18606
19125
|
\u2026[diff truncated]` : text,
|
|
18607
|
-
stat:
|
|
19126
|
+
stat: stat12.stdout.trim(),
|
|
18608
19127
|
truncated: text.length > maxChars
|
|
18609
19128
|
};
|
|
18610
19129
|
}
|
|
18611
19130
|
async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
18612
|
-
const
|
|
19131
|
+
const stat12 = await git2(workspace, ["diff", "HEAD", "--stat"]);
|
|
18613
19132
|
const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
|
|
18614
19133
|
if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
|
|
18615
19134
|
const summary2 = await changeSummary(workspace);
|
|
@@ -18617,7 +19136,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
|
18617
19136
|
return {
|
|
18618
19137
|
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
18619
19138
|
\u2026[diff truncated]` : text,
|
|
18620
|
-
stat:
|
|
19139
|
+
stat: stat12.stdout.trim(),
|
|
18621
19140
|
untracked: summary2.untrackedFiles
|
|
18622
19141
|
};
|
|
18623
19142
|
}
|
|
@@ -18638,16 +19157,21 @@ async function commit(workspace, commitMessage) {
|
|
|
18638
19157
|
const head = await git2(workspace, ["log", "-1", "--pretty=%h %s"]);
|
|
18639
19158
|
return head.stdout.trim() || "committed";
|
|
18640
19159
|
}
|
|
18641
|
-
async function fileHistory(workspace, path, limit) {
|
|
19160
|
+
async function fileHistory(workspace, path, limit, signal) {
|
|
18642
19161
|
const args = [
|
|
19162
|
+
"--literal-pathspecs",
|
|
18643
19163
|
"log",
|
|
18644
19164
|
`--max-count=${limit}`,
|
|
18645
19165
|
"--date=short",
|
|
18646
19166
|
"--pretty=format:%h%x1f%ad%x1f%an%x1f%s",
|
|
18647
19167
|
...path === void 0 ? [] : ["--follow", "--", path]
|
|
18648
19168
|
];
|
|
18649
|
-
const result = await git2(workspace, args, 15e3);
|
|
18650
|
-
if (!result.ok)
|
|
19169
|
+
const result = await git2(workspace, args, 15e3, signal);
|
|
19170
|
+
if (!result.ok) {
|
|
19171
|
+
const head = await git2(workspace, ["rev-parse", "--verify", "HEAD"], 2e3, signal);
|
|
19172
|
+
if (!head.ok && await isGitRepository(workspace, signal)) return [];
|
|
19173
|
+
throw gitFailure(result, "log");
|
|
19174
|
+
}
|
|
18651
19175
|
return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => {
|
|
18652
19176
|
const [hash = "", date = "", author = "", ...subject] = line.split("");
|
|
18653
19177
|
return { hash, date, author, subject: subject.join("") };
|
|
@@ -18909,17 +19433,17 @@ ${explanation}` : "";
|
|
|
18909
19433
|
}
|
|
18910
19434
|
|
|
18911
19435
|
// src/desktop/astgraph-service.ts
|
|
18912
|
-
import { stat as
|
|
19436
|
+
import { stat as stat10 } from "fs/promises";
|
|
18913
19437
|
import { createRequire as nodeCreateRequire } from "module";
|
|
18914
|
-
import { join as
|
|
19438
|
+
import { join as join26, resolve as resolve24 } from "path";
|
|
18915
19439
|
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
18916
19440
|
var DesktopAstGraphService = class {
|
|
18917
19441
|
#path;
|
|
18918
19442
|
constructor(workspace) {
|
|
18919
|
-
this.#path =
|
|
19443
|
+
this.#path = join26(resolve24(workspace), ".flavor", "astgraph", "index.db");
|
|
18920
19444
|
}
|
|
18921
19445
|
async status() {
|
|
18922
|
-
if ((await
|
|
19446
|
+
if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
18923
19447
|
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
18924
19448
|
}
|
|
18925
19449
|
return this.#read((db) => {
|
|
@@ -18986,24 +19510,33 @@ function mapNode(value) {
|
|
|
18986
19510
|
|
|
18987
19511
|
// src/git/tools.ts
|
|
18988
19512
|
import { z as z28 } from "zod";
|
|
19513
|
+
import { isAbsolute as isAbsolute13, relative as relative18, resolve as resolve25, sep as sep18 } from "path";
|
|
18989
19514
|
var GitHistoryInput = z28.object({
|
|
18990
|
-
path: z28.string().describe("
|
|
18991
|
-
limit: z28.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
|
|
19515
|
+
path: z28.string().min(1).describe("Workspace-relative or absolute file path; omit for the whole repository history").optional(),
|
|
19516
|
+
limit: z28.coerce.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
|
|
18992
19517
|
});
|
|
18993
19518
|
function createGitHistoryTool(workspace) {
|
|
19519
|
+
const root = resolve25(workspace);
|
|
19520
|
+
const file = (path) => {
|
|
19521
|
+
const absolute = resolve25(root, path);
|
|
19522
|
+
const delta = relative18(root, absolute);
|
|
19523
|
+
if (delta === ".." || delta.startsWith(`..${sep18}`) || isAbsolute13(delta)) throw new Error("GitHistory path is outside the workspace");
|
|
19524
|
+
return absolute;
|
|
19525
|
+
};
|
|
18994
19526
|
return {
|
|
18995
19527
|
name: "GitHistory",
|
|
18996
19528
|
description: "Show git commit history for the repository or for one file (follows renames). Use to understand when and why code changed.",
|
|
18997
19529
|
inputSchema: GitHistoryInput,
|
|
18998
19530
|
readOnly: true,
|
|
18999
|
-
paths: (input) => input.path === void 0 ? [] : [input.path],
|
|
19531
|
+
paths: (input) => input.path === void 0 ? [root] : [file(input.path)],
|
|
19000
19532
|
summarize: (input) => input.path ?? "repository",
|
|
19001
19533
|
renderForModel: (output) => output,
|
|
19002
19534
|
async execute(input, signal) {
|
|
19003
19535
|
signal.throwIfAborted();
|
|
19004
|
-
|
|
19536
|
+
const path = input.path === void 0 ? void 0 : relative18(root, file(input.path));
|
|
19537
|
+
if (!await isGitRepository(root, signal)) throw new Error("Not a git repository");
|
|
19005
19538
|
const limit = input.limit ?? 20;
|
|
19006
|
-
const entries = await fileHistory(
|
|
19539
|
+
const entries = await fileHistory(root, path, limit, signal);
|
|
19007
19540
|
if (entries.length === 0) {
|
|
19008
19541
|
return input.path === void 0 ? "No commits found." : `No commits found for ${input.path} (the file may be untracked).`;
|
|
19009
19542
|
}
|
|
@@ -19066,7 +19599,8 @@ var QuestionBridge = class {
|
|
|
19066
19599
|
}
|
|
19067
19600
|
async #askRelayed(questions, signal) {
|
|
19068
19601
|
try {
|
|
19069
|
-
const answers = await this.#relay(questions, signal);
|
|
19602
|
+
const answers = await waitWithSignal(this.#relay(questions, signal), signal);
|
|
19603
|
+
signal.throwIfAborted();
|
|
19070
19604
|
if (answers !== void 0) {
|
|
19071
19605
|
this.#active = false;
|
|
19072
19606
|
return answers;
|
|
@@ -19078,10 +19612,15 @@ var QuestionBridge = class {
|
|
|
19078
19612
|
}
|
|
19079
19613
|
}
|
|
19080
19614
|
#askLocally(questions, signal) {
|
|
19081
|
-
|
|
19615
|
+
if (signal.aborted) {
|
|
19616
|
+
this.#active = false;
|
|
19617
|
+
return Promise.reject(signal.reason);
|
|
19618
|
+
}
|
|
19619
|
+
return new Promise((resolve32, reject) => {
|
|
19082
19620
|
const onAbort = () => {
|
|
19083
19621
|
this.#pending = void 0;
|
|
19084
19622
|
this.#active = false;
|
|
19623
|
+
this.#onChange?.();
|
|
19085
19624
|
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
|
|
19086
19625
|
};
|
|
19087
19626
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -19092,7 +19631,7 @@ var QuestionBridge = class {
|
|
|
19092
19631
|
this.#pending = void 0;
|
|
19093
19632
|
this.#active = false;
|
|
19094
19633
|
this.#onChange?.();
|
|
19095
|
-
|
|
19634
|
+
resolve32(answers);
|
|
19096
19635
|
},
|
|
19097
19636
|
reject: (error) => {
|
|
19098
19637
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -19184,8 +19723,8 @@ function createTodoWriteTool() {
|
|
|
19184
19723
|
|
|
19185
19724
|
// src/tools/managed.ts
|
|
19186
19725
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
19187
|
-
import { link as link2, lstat as lstat12, mkdir as mkdir17, readFile as
|
|
19188
|
-
import { dirname as
|
|
19726
|
+
import { link as link2, lstat as lstat12, mkdir as mkdir17, readFile as readFile21, readdir as readdir9, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
|
|
19727
|
+
import { dirname as dirname16, join as join27, resolve as resolve26 } from "path";
|
|
19189
19728
|
import { z as z31 } from "zod";
|
|
19190
19729
|
var MANAGED_TOOL_VERSION = 1;
|
|
19191
19730
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -19227,9 +19766,9 @@ var ManagedToolStore = class {
|
|
|
19227
19766
|
#entries = /* @__PURE__ */ new Map();
|
|
19228
19767
|
#diagnostics = [];
|
|
19229
19768
|
constructor(options) {
|
|
19230
|
-
this.#workspace =
|
|
19231
|
-
this.#projectRoot =
|
|
19232
|
-
this.#globalRoot =
|
|
19769
|
+
this.#workspace = resolve26(options.workspace);
|
|
19770
|
+
this.#projectRoot = join27(this.#workspace, ".flavor", "tools");
|
|
19771
|
+
this.#globalRoot = join27(resolve26(options.home), ".flavor-code", "tools");
|
|
19233
19772
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19234
19773
|
}
|
|
19235
19774
|
get diagnostics() {
|
|
@@ -19276,7 +19815,7 @@ var ManagedToolStore = class {
|
|
|
19276
19815
|
version: MANAGED_TOOL_VERSION,
|
|
19277
19816
|
createdAt: this.#now()
|
|
19278
19817
|
});
|
|
19279
|
-
const path =
|
|
19818
|
+
const path = join27(this.directory(input.scope), filename(input.name));
|
|
19280
19819
|
const definition = compileDefinition(record, input.scope, this.#workspace);
|
|
19281
19820
|
await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
|
|
19282
19821
|
`);
|
|
@@ -19301,7 +19840,7 @@ var ManagedToolStore = class {
|
|
|
19301
19840
|
if (!parsed.success) return [];
|
|
19302
19841
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
19303
19842
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
19304
|
-
return [
|
|
19843
|
+
return [join27(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
19305
19844
|
}
|
|
19306
19845
|
async #loadDirectory(scope2) {
|
|
19307
19846
|
const root = this.directory(scope2);
|
|
@@ -19314,12 +19853,12 @@ var ManagedToolStore = class {
|
|
|
19314
19853
|
return;
|
|
19315
19854
|
}
|
|
19316
19855
|
for (const name of names) {
|
|
19317
|
-
const path =
|
|
19856
|
+
const path = join27(root, name);
|
|
19318
19857
|
try {
|
|
19319
19858
|
const info = await lstat12(path);
|
|
19320
19859
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
19321
19860
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
19322
|
-
const record = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
19861
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile21(path, "utf8")));
|
|
19323
19862
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
19324
19863
|
const key = scopedKey(scope2, record.name);
|
|
19325
19864
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -19469,7 +20008,7 @@ ${sourceUrl}`
|
|
|
19469
20008
|
}
|
|
19470
20009
|
}
|
|
19471
20010
|
async function writeExclusive(path, content) {
|
|
19472
|
-
await mkdir17(
|
|
20011
|
+
await mkdir17(dirname16(path), { recursive: true });
|
|
19473
20012
|
const temporary = `${path}.${randomUUID16()}.tmp`;
|
|
19474
20013
|
try {
|
|
19475
20014
|
await writeFile10(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
@@ -20232,8 +20771,8 @@ ${total}`;
|
|
|
20232
20771
|
|
|
20233
20772
|
// src/memory/store.ts
|
|
20234
20773
|
import { createHash as createHash16 } from "crypto";
|
|
20235
|
-
import { readFile as
|
|
20236
|
-
import { resolve as
|
|
20774
|
+
import { readFile as readFile22, rm as rm9 } from "fs/promises";
|
|
20775
|
+
import { resolve as resolve27, join as join28, relative as relative19, sep as sep19 } from "path";
|
|
20237
20776
|
|
|
20238
20777
|
// src/memory/similarity.ts
|
|
20239
20778
|
function normalizeForSimilarity(value) {
|
|
@@ -20335,10 +20874,10 @@ var MemoryStore = class {
|
|
|
20335
20874
|
constructor(options) {
|
|
20336
20875
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
20337
20876
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
20338
|
-
this.workspace =
|
|
20339
|
-
this.#memoryRoot =
|
|
20340
|
-
this.path =
|
|
20341
|
-
this.#behaviorPath =
|
|
20877
|
+
this.workspace = resolve27(options.workspace);
|
|
20878
|
+
this.#memoryRoot = join28(this.workspace, ".flavor", "memory");
|
|
20879
|
+
this.path = join28(this.#memoryRoot, "MEMORY.md");
|
|
20880
|
+
this.#behaviorPath = join28(this.#memoryRoot, "behavior.json");
|
|
20342
20881
|
this.#maxEntries = options.maxEntries;
|
|
20343
20882
|
this.#maxEntryChars = options.maxEntryChars;
|
|
20344
20883
|
}
|
|
@@ -20556,14 +21095,14 @@ var MemoryStore = class {
|
|
|
20556
21095
|
});
|
|
20557
21096
|
}
|
|
20558
21097
|
async #readTaskItem(reference) {
|
|
20559
|
-
const document = decodeTask(await
|
|
21098
|
+
const document = decodeTask(await readFile22(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
20560
21099
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
20561
21100
|
}
|
|
20562
21101
|
#resolveContentPath(contentPath) {
|
|
20563
21102
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
20564
|
-
const path =
|
|
20565
|
-
const rel =
|
|
20566
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
21103
|
+
const path = resolve27(this.#memoryRoot, ...contentPath.split("/"));
|
|
21104
|
+
const rel = relative19(this.#memoryRoot, path);
|
|
21105
|
+
if (rel === ".." || rel.startsWith(`..${sep19}`)) throw new Error("Memory content path escapes the memory directory");
|
|
20567
21106
|
return path;
|
|
20568
21107
|
}
|
|
20569
21108
|
async #updateIndex(update) {
|
|
@@ -21032,8 +21571,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
21032
21571
|
}
|
|
21033
21572
|
|
|
21034
21573
|
// src/ide/client.ts
|
|
21035
|
-
import { readFile as
|
|
21036
|
-
import { basename as basename6, join as
|
|
21574
|
+
import { readFile as readFile23, readdir as readdir10, stat as stat11 } from "fs/promises";
|
|
21575
|
+
import { basename as basename6, join as join29, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
|
|
21037
21576
|
var FlavorIdeClient = class {
|
|
21038
21577
|
#workspace;
|
|
21039
21578
|
#home;
|
|
@@ -21044,8 +21583,8 @@ var FlavorIdeClient = class {
|
|
|
21044
21583
|
#sessionId;
|
|
21045
21584
|
#eventTail = Promise.resolve();
|
|
21046
21585
|
constructor(options) {
|
|
21047
|
-
this.#workspace =
|
|
21048
|
-
this.#home =
|
|
21586
|
+
this.#workspace = resolve28(options.workspace);
|
|
21587
|
+
this.#home = resolve28(options.home);
|
|
21049
21588
|
this.#environment = options.environment ?? process.env;
|
|
21050
21589
|
this.#fetch = options.fetch ?? fetch;
|
|
21051
21590
|
}
|
|
@@ -21129,7 +21668,7 @@ var FlavorIdeClient = class {
|
|
|
21129
21668
|
};
|
|
21130
21669
|
if (await this.#healthy(configured)) return configured;
|
|
21131
21670
|
}
|
|
21132
|
-
const directory =
|
|
21671
|
+
const directory = join29(this.#home, ".flavor-code", "ide");
|
|
21133
21672
|
let entries;
|
|
21134
21673
|
try {
|
|
21135
21674
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -21137,9 +21676,9 @@ var FlavorIdeClient = class {
|
|
|
21137
21676
|
return void 0;
|
|
21138
21677
|
}
|
|
21139
21678
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
21140
|
-
const path =
|
|
21679
|
+
const path = join29(directory, name);
|
|
21141
21680
|
try {
|
|
21142
|
-
const [contents, info] = await Promise.all([
|
|
21681
|
+
const [contents, info] = await Promise.all([readFile23(path, "utf8"), stat11(path)]);
|
|
21143
21682
|
const lock = parseLockfile(contents);
|
|
21144
21683
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
21145
21684
|
} catch {
|
|
@@ -21243,21 +21782,22 @@ function parsePort(value) {
|
|
|
21243
21782
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
21244
21783
|
}
|
|
21245
21784
|
function isWithin8(parent, child) {
|
|
21246
|
-
const base = normalize(
|
|
21247
|
-
const target = normalize(
|
|
21248
|
-
return target === base || target.startsWith(`${base}${
|
|
21785
|
+
const base = normalize(resolve28(parent));
|
|
21786
|
+
const target = normalize(resolve28(child));
|
|
21787
|
+
return target === base || target.startsWith(`${base}${sep20}`);
|
|
21249
21788
|
}
|
|
21250
21789
|
function normalize(path) {
|
|
21251
21790
|
const normalized = path.normalize("NFC");
|
|
21252
21791
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
21253
21792
|
}
|
|
21254
21793
|
function relativeDisplay(workspace, filePath) {
|
|
21255
|
-
const path =
|
|
21794
|
+
const path = relative20(workspace, filePath).replaceAll("\\", "/");
|
|
21256
21795
|
return path && !path.startsWith("../") ? path : basename6(filePath);
|
|
21257
21796
|
}
|
|
21258
21797
|
|
|
21259
21798
|
// src/jobs/registry.ts
|
|
21260
21799
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
21800
|
+
var OUTPUT_SLICE_SLACK_CHARS = 64 * 1024;
|
|
21261
21801
|
var JobRegistry = class {
|
|
21262
21802
|
#jobs = /* @__PURE__ */ new Map();
|
|
21263
21803
|
#maxOutputChars;
|
|
@@ -21271,11 +21811,12 @@ var JobRegistry = class {
|
|
|
21271
21811
|
}
|
|
21272
21812
|
create(input) {
|
|
21273
21813
|
this.#pruneCompleted();
|
|
21814
|
+
if (this.#jobs.size >= this.#maxJobs) throw new Error(`Background job limit (${this.#maxJobs}) reached; wait for or cancel an existing job first`);
|
|
21274
21815
|
const id = `job-${randomUUID17().slice(0, 12)}`;
|
|
21275
21816
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21276
21817
|
let settle;
|
|
21277
|
-
const done = new Promise((
|
|
21278
|
-
settle =
|
|
21818
|
+
const done = new Promise((resolve32) => {
|
|
21819
|
+
settle = resolve32;
|
|
21279
21820
|
});
|
|
21280
21821
|
const record = {
|
|
21281
21822
|
id,
|
|
@@ -21285,7 +21826,8 @@ var JobRegistry = class {
|
|
|
21285
21826
|
updatedAt: now,
|
|
21286
21827
|
outputChars: 0,
|
|
21287
21828
|
truncated: false,
|
|
21288
|
-
|
|
21829
|
+
chunks: [],
|
|
21830
|
+
windowChars: 0,
|
|
21289
21831
|
outputStart: 0,
|
|
21290
21832
|
settle,
|
|
21291
21833
|
done
|
|
@@ -21296,11 +21838,16 @@ var JobRegistry = class {
|
|
|
21296
21838
|
id,
|
|
21297
21839
|
append: (text) => {
|
|
21298
21840
|
if (record.state !== "running" || text === "") return;
|
|
21299
|
-
record.
|
|
21841
|
+
record.chunks.push(text);
|
|
21842
|
+
record.windowChars += text.length;
|
|
21300
21843
|
record.outputChars += text.length;
|
|
21301
|
-
if (record.
|
|
21302
|
-
|
|
21303
|
-
|
|
21844
|
+
if (record.outputChars > this.#maxOutputChars) record.truncated = true;
|
|
21845
|
+
if (record.windowChars > this.#maxOutputChars + OUTPUT_SLICE_SLACK_CHARS) {
|
|
21846
|
+
const joined = record.chunks.join("");
|
|
21847
|
+
const kept = joined.slice(joined.length - this.#maxOutputChars);
|
|
21848
|
+
const removed = joined.length - kept.length;
|
|
21849
|
+
record.chunks = [kept];
|
|
21850
|
+
record.windowChars = kept.length;
|
|
21304
21851
|
record.outputStart += removed;
|
|
21305
21852
|
record.truncated = true;
|
|
21306
21853
|
}
|
|
@@ -21309,7 +21856,7 @@ var JobRegistry = class {
|
|
|
21309
21856
|
},
|
|
21310
21857
|
complete: (result = {}) => this.#finish(
|
|
21311
21858
|
record,
|
|
21312
|
-
result.exitCode !== void 0 && result.exitCode !== null && result.exitCode !== 0 ? "failed" : "completed",
|
|
21859
|
+
result.error !== void 0 || result.exitCode !== void 0 && result.exitCode !== null && result.exitCode !== 0 ? "failed" : "completed",
|
|
21313
21860
|
result
|
|
21314
21861
|
),
|
|
21315
21862
|
fail: (error) => this.#finish(record, "failed", { error: error instanceof Error ? error.message : String(error) }),
|
|
@@ -21321,25 +21868,40 @@ var JobRegistry = class {
|
|
|
21321
21868
|
}
|
|
21322
21869
|
read(id, owner2, cursor = 0) {
|
|
21323
21870
|
const job = this.#owned(id, owner2);
|
|
21324
|
-
const
|
|
21325
|
-
|
|
21871
|
+
const window = job.chunks.join("");
|
|
21872
|
+
const dropped = Math.max(0, window.length - this.#maxOutputChars);
|
|
21873
|
+
const effective = Math.max(cursor, job.outputStart + dropped);
|
|
21874
|
+
return { ...snapshot2(job), output: window.slice(effective - job.outputStart), cursor: job.outputStart + window.length };
|
|
21875
|
+
}
|
|
21876
|
+
/** Retained sizes for the rotation census. */
|
|
21877
|
+
census() {
|
|
21878
|
+
let windowChars = 0;
|
|
21879
|
+
let running = 0;
|
|
21880
|
+
for (const job of this.#jobs.values()) {
|
|
21881
|
+
windowChars += job.windowChars;
|
|
21882
|
+
if (job.state === "running") running += 1;
|
|
21883
|
+
}
|
|
21884
|
+
return { jobs: this.#jobs.size, running, windowChars };
|
|
21326
21885
|
}
|
|
21327
21886
|
async wait(id, owner2, signal) {
|
|
21887
|
+
signal?.throwIfAborted();
|
|
21328
21888
|
const job = this.#owned(id, owner2);
|
|
21329
21889
|
if (job.state !== "running") return snapshot2(job);
|
|
21330
|
-
|
|
21331
|
-
return Promise.race([job.done, new Promise((_resolve, reject) => {
|
|
21332
|
-
if (signal.aborted) {
|
|
21333
|
-
reject(signal.reason);
|
|
21334
|
-
return;
|
|
21335
|
-
}
|
|
21336
|
-
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
21337
|
-
})]);
|
|
21890
|
+
return waitWithSignal(job.done, signal);
|
|
21338
21891
|
}
|
|
21339
21892
|
kill(id, owner2) {
|
|
21340
21893
|
const job = this.#owned(id, owner2);
|
|
21341
21894
|
if (job.state !== "running") return;
|
|
21342
|
-
|
|
21895
|
+
try {
|
|
21896
|
+
const cancellation = job.cancel?.();
|
|
21897
|
+
if (cancellation !== void 0) return cancellation.then(
|
|
21898
|
+
() => this.#finish(job, "cancelled", {}),
|
|
21899
|
+
(error) => this.#finish(job, "failed", { error: `Cancellation failed: ${error instanceof Error ? error.message : String(error)}` })
|
|
21900
|
+
);
|
|
21901
|
+
} catch (error) {
|
|
21902
|
+
this.#finish(job, "failed", { error: `Cancellation failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
21903
|
+
return;
|
|
21904
|
+
}
|
|
21343
21905
|
this.#finish(job, "cancelled", {});
|
|
21344
21906
|
}
|
|
21345
21907
|
subscribe(listener) {
|
|
@@ -21399,7 +21961,7 @@ function snapshot2(job) {
|
|
|
21399
21961
|
// src/terminal/service.ts
|
|
21400
21962
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
21401
21963
|
import { createRequire as nodeCreateRequire2 } from "module";
|
|
21402
|
-
import { isAbsolute as
|
|
21964
|
+
import { isAbsolute as isAbsolute14, relative as relative21, resolve as resolve29, sep as sep21 } from "path";
|
|
21403
21965
|
var TerminalService = class {
|
|
21404
21966
|
#root;
|
|
21405
21967
|
#factory;
|
|
@@ -21407,10 +21969,11 @@ var TerminalService = class {
|
|
|
21407
21969
|
#maxOutputChars;
|
|
21408
21970
|
#sessions = /* @__PURE__ */ new Map();
|
|
21409
21971
|
constructor(workspace, options = {}) {
|
|
21410
|
-
this.#root =
|
|
21972
|
+
this.#root = resolve29(workspace);
|
|
21411
21973
|
this.#factory = options.factory ?? defaultFactory;
|
|
21412
21974
|
this.#jobs = options.jobs;
|
|
21413
21975
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
21976
|
+
if (!Number.isSafeInteger(this.#maxOutputChars) || this.#maxOutputChars <= 0) throw new Error("maxOutputChars must be a positive integer");
|
|
21414
21977
|
}
|
|
21415
21978
|
open(input) {
|
|
21416
21979
|
const cwd = workspacePath(this.#root, input.cwd ?? ".");
|
|
@@ -21437,7 +22000,13 @@ var TerminalService = class {
|
|
|
21437
22000
|
truncated: false,
|
|
21438
22001
|
disposables: []
|
|
21439
22002
|
};
|
|
21440
|
-
|
|
22003
|
+
let job;
|
|
22004
|
+
try {
|
|
22005
|
+
job = this.#jobs?.create({ kind: "terminal", owner: input.owner, label: shell, cancel: () => this.close(id, input.owner) });
|
|
22006
|
+
} catch (error) {
|
|
22007
|
+
pty.kill();
|
|
22008
|
+
throw error;
|
|
22009
|
+
}
|
|
21441
22010
|
if (job !== void 0) {
|
|
21442
22011
|
jobId = job.id;
|
|
21443
22012
|
record.jobId = job.id;
|
|
@@ -21463,10 +22032,10 @@ var TerminalService = class {
|
|
|
21463
22032
|
return { ...snapshot3(record), ...jobId === void 0 ? {} : { jobId } };
|
|
21464
22033
|
}
|
|
21465
22034
|
write(id, owner2, data2) {
|
|
21466
|
-
this.#
|
|
22035
|
+
this.#running(id, owner2).pty.write(data2);
|
|
21467
22036
|
}
|
|
21468
22037
|
resize(id, owner2, columns, rows) {
|
|
21469
|
-
this.#
|
|
22038
|
+
this.#running(id, owner2).pty.resize(columns, rows);
|
|
21470
22039
|
}
|
|
21471
22040
|
read(id, owner2, cursor = 0) {
|
|
21472
22041
|
const record = this.#owned(id, owner2);
|
|
@@ -21497,6 +22066,11 @@ var TerminalService = class {
|
|
|
21497
22066
|
if (record.owner !== owner2) throw new Error("Terminal owner mismatch");
|
|
21498
22067
|
return record;
|
|
21499
22068
|
}
|
|
22069
|
+
#running(id, owner2) {
|
|
22070
|
+
const record = this.#owned(id, owner2);
|
|
22071
|
+
if (record.state !== "running") throw new Error(`Terminal ${id} is ${record.state}; use TerminalOpen to start a new session`);
|
|
22072
|
+
return record;
|
|
22073
|
+
}
|
|
21500
22074
|
};
|
|
21501
22075
|
function snapshot3(record) {
|
|
21502
22076
|
return {
|
|
@@ -21525,15 +22099,15 @@ function cleanEnvironment(environment) {
|
|
|
21525
22099
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
21526
22100
|
}
|
|
21527
22101
|
function workspacePath(root, input) {
|
|
21528
|
-
const path =
|
|
21529
|
-
const delta =
|
|
21530
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
22102
|
+
const path = resolve29(root, input);
|
|
22103
|
+
const delta = relative21(root, path);
|
|
22104
|
+
if (delta === ".." || delta.startsWith(`..${sep21}`) || isAbsolute14(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
21531
22105
|
return path;
|
|
21532
22106
|
}
|
|
21533
22107
|
|
|
21534
22108
|
// src/tools/terminal.ts
|
|
21535
22109
|
import { z as z33 } from "zod";
|
|
21536
|
-
import { resolve as
|
|
22110
|
+
import { resolve as resolve30 } from "path";
|
|
21537
22111
|
var Id = z33.string().regex(/^term-[a-f0-9-]+$/);
|
|
21538
22112
|
var OpenInput = z33.object({ cwd: z33.string().optional(), shell: z33.string().optional(), args: z33.array(z33.string()).optional(), columns: z33.coerce.number().int().min(10).max(500).optional(), rows: z33.coerce.number().int().min(2).max(300).optional() });
|
|
21539
22113
|
var OptionalBoolean = z33.union([z33.boolean(), z33.string().refine((value) => value === "true" || value === "false")]);
|
|
@@ -21547,8 +22121,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
21547
22121
|
name: "TerminalOpen",
|
|
21548
22122
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
21549
22123
|
inputSchema: OpenInput,
|
|
21550
|
-
paths: (input) => [
|
|
21551
|
-
permissions: (input) => ({ paths: [
|
|
22124
|
+
paths: (input) => [resolve30(workspace, input.cwd ?? ".")],
|
|
22125
|
+
permissions: (input) => ({ paths: [resolve30(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve30(workspace, input.cwd ?? ".") }),
|
|
21552
22126
|
execute: async (input, _signal, context) => terminals.open({
|
|
21553
22127
|
owner: owner(context),
|
|
21554
22128
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -21577,7 +22151,7 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
21577
22151
|
inputSchema: ReadInput3,
|
|
21578
22152
|
paths: () => [],
|
|
21579
22153
|
execute: async (input, _signal, context) => terminals.read(input.id, owner(context), input.cursor ?? 0),
|
|
21580
|
-
renderForModel: (output) =>
|
|
22154
|
+
renderForModel: (output) => JSON.stringify(output),
|
|
21581
22155
|
presentResult: (output) => {
|
|
21582
22156
|
const result = output;
|
|
21583
22157
|
return {
|
|
@@ -21774,8 +22348,8 @@ var PalClient = class {
|
|
|
21774
22348
|
let disconnectTimer;
|
|
21775
22349
|
await Promise.race([
|
|
21776
22350
|
this.request({ version: 1, type: "disconnect", requestId: randomUUID19() }).catch(() => void 0),
|
|
21777
|
-
new Promise((
|
|
21778
|
-
disconnectTimer = setTimeout(
|
|
22351
|
+
new Promise((resolve32) => {
|
|
22352
|
+
disconnectTimer = setTimeout(resolve32, Math.min(this.requestTimeoutMs, 250));
|
|
21779
22353
|
})
|
|
21780
22354
|
]);
|
|
21781
22355
|
if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
|
|
@@ -21821,7 +22395,7 @@ var PalClient = class {
|
|
|
21821
22395
|
return presence;
|
|
21822
22396
|
}
|
|
21823
22397
|
openSocket() {
|
|
21824
|
-
return new Promise((
|
|
22398
|
+
return new Promise((resolve32, reject) => {
|
|
21825
22399
|
let settled = false;
|
|
21826
22400
|
const rejectConnection = (error) => {
|
|
21827
22401
|
if (settled) return;
|
|
@@ -21844,7 +22418,7 @@ var PalClient = class {
|
|
|
21844
22418
|
settled = true;
|
|
21845
22419
|
if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
|
|
21846
22420
|
socket.off("error", onInitialError);
|
|
21847
|
-
|
|
22421
|
+
resolve32();
|
|
21848
22422
|
};
|
|
21849
22423
|
const onInitialError = (error) => {
|
|
21850
22424
|
socket.off("connect", onConnect);
|
|
@@ -21875,12 +22449,12 @@ var PalClient = class {
|
|
|
21875
22449
|
if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
|
|
21876
22450
|
return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
|
|
21877
22451
|
}
|
|
21878
|
-
return new Promise((
|
|
22452
|
+
return new Promise((resolve32, reject) => {
|
|
21879
22453
|
const timer = setTimeout(() => {
|
|
21880
22454
|
this.pending.delete(request.requestId);
|
|
21881
22455
|
reject(new Error(`Pal request '${request.type}' timed out`));
|
|
21882
22456
|
}, this.requestTimeoutMs);
|
|
21883
|
-
this.pending.set(request.requestId, { resolve:
|
|
22457
|
+
this.pending.set(request.requestId, { resolve: resolve32, reject, timer });
|
|
21884
22458
|
let frame;
|
|
21885
22459
|
try {
|
|
21886
22460
|
frame = encodeControlFrame(BrokerRequestSchema.parse(request));
|
|
@@ -21984,12 +22558,12 @@ var PalClient = class {
|
|
|
21984
22558
|
}
|
|
21985
22559
|
scheduleReconnect() {
|
|
21986
22560
|
if (this.reconnectTimer !== void 0 || this.closed) return;
|
|
21987
|
-
const
|
|
22561
|
+
const delay2 = this.reconnectDelayMs;
|
|
21988
22562
|
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, this.reconnectMaxDelayMs);
|
|
21989
22563
|
this.reconnectTimer = setTimeout(() => {
|
|
21990
22564
|
this.reconnectTimer = void 0;
|
|
21991
22565
|
void this.start().catch(() => this.scheduleReconnect());
|
|
21992
|
-
},
|
|
22566
|
+
}, delay2);
|
|
21993
22567
|
this.reconnectTimer.unref?.();
|
|
21994
22568
|
}
|
|
21995
22569
|
};
|
|
@@ -22091,6 +22665,7 @@ function createPalsTools(client, options) {
|
|
|
22091
22665
|
execute: async (input, signal) => {
|
|
22092
22666
|
signal.throwIfAborted();
|
|
22093
22667
|
const recipientId = await assertSendTarget(client, input.target, input.coWorkId);
|
|
22668
|
+
signal.throwIfAborted();
|
|
22094
22669
|
return client.sendChat(recipientId, shareGuard.protect(input.message));
|
|
22095
22670
|
}
|
|
22096
22671
|
},
|
|
@@ -22114,6 +22689,7 @@ function createPalsTools(client, options) {
|
|
|
22114
22689
|
execute: async (input, signal) => {
|
|
22115
22690
|
signal.throwIfAborted();
|
|
22116
22691
|
assertRequiredParticipant(await client.coWorkStatus(input.plan.coWorkId), options.selfId);
|
|
22692
|
+
signal.throwIfAborted();
|
|
22117
22693
|
const plan = CoWorkPlanSchema.parse({
|
|
22118
22694
|
...input.plan,
|
|
22119
22695
|
goal: shareGuard.protect(input.plan.goal),
|
|
@@ -22130,11 +22706,13 @@ function createPalsTools(client, options) {
|
|
|
22130
22706
|
execute: async (input, signal) => {
|
|
22131
22707
|
signal.throwIfAborted();
|
|
22132
22708
|
let state = await assertCurrentToken(client, input);
|
|
22709
|
+
signal.throwIfAborted();
|
|
22133
22710
|
assertRequiredParticipant(state, options.selfId);
|
|
22134
22711
|
if (!state.planAcceptedParticipantIds.includes(options.selfId)) {
|
|
22135
22712
|
state = await client.coWorkAction({ type: "cowork-plan-accept", ...input });
|
|
22136
22713
|
}
|
|
22137
22714
|
if (state.readyParticipantIds.includes(options.selfId) || state.phase === "running") return state;
|
|
22715
|
+
signal.throwIfAborted();
|
|
22138
22716
|
return client.coWorkAction({ type: "cowork-ready", ...input });
|
|
22139
22717
|
}
|
|
22140
22718
|
},
|
|
@@ -22148,6 +22726,7 @@ function createPalsTools(client, options) {
|
|
|
22148
22726
|
execute: async (input, signal) => {
|
|
22149
22727
|
signal.throwIfAborted();
|
|
22150
22728
|
const recipientId = await assertSendTarget(client, input.target, input.coWorkId);
|
|
22729
|
+
signal.throwIfAborted();
|
|
22151
22730
|
return client.sendChat(recipientId, `[co-work ${input.coWorkId} progress] ${shareGuard.protect(input.detail)}`);
|
|
22152
22731
|
}
|
|
22153
22732
|
},
|
|
@@ -22161,6 +22740,7 @@ function createPalsTools(client, options) {
|
|
|
22161
22740
|
execute: async (input, signal) => {
|
|
22162
22741
|
signal.throwIfAborted();
|
|
22163
22742
|
const state = await assertCurrentToken(client, input);
|
|
22743
|
+
signal.throwIfAborted();
|
|
22164
22744
|
assertRequiredParticipant(state, options.selfId);
|
|
22165
22745
|
if (state.phase !== "running" || !state.acceptedParticipantIds.includes(options.selfId) || !state.readyParticipantIds.includes(options.selfId)) {
|
|
22166
22746
|
throw new Error("A required participant must be accepted, ready, and running before reporting completion");
|
|
@@ -22178,6 +22758,7 @@ function createPalsTools(client, options) {
|
|
|
22178
22758
|
execute: async (input, signal) => {
|
|
22179
22759
|
signal.throwIfAborted();
|
|
22180
22760
|
const state = await assertCurrentToken(client, input);
|
|
22761
|
+
signal.throwIfAborted();
|
|
22181
22762
|
assertRequiredParticipant(state, options.selfId);
|
|
22182
22763
|
if (state.integrationOwnerId !== options.selfId) throw new Error("Only the broker-designated integration owner can finalize integration");
|
|
22183
22764
|
if (state.phase !== "verifying") throw new Error("Co-work is not ready for integration verification");
|
|
@@ -22284,8 +22865,8 @@ var ApprovalBridge = class {
|
|
|
22284
22865
|
}
|
|
22285
22866
|
};
|
|
22286
22867
|
async function createProductionRuntime(options) {
|
|
22287
|
-
const workspace =
|
|
22288
|
-
const home =
|
|
22868
|
+
const workspace = resolve31(options.workspace ?? process.cwd());
|
|
22869
|
+
const home = resolve31(options.home ?? homedir3());
|
|
22289
22870
|
const environment = options.environment ?? process.env;
|
|
22290
22871
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
22291
22872
|
const ideReady = ide.initialize();
|
|
@@ -22459,6 +23040,7 @@ async function createProductionRuntime(options) {
|
|
|
22459
23040
|
const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
|
|
22460
23041
|
const jobs = new JobRegistry();
|
|
22461
23042
|
const terminals = new TerminalService(workspace, { jobs });
|
|
23043
|
+
const lspManager = new RealLspManager({ workspace, onStatus: (message2) => emitOutput({ type: "notice", message: message2 }) });
|
|
22462
23044
|
const observations = new FileObservationStore();
|
|
22463
23045
|
const workspaceInstructions = new WorkspaceInstructions(workspace);
|
|
22464
23046
|
const fileMutationOptions = {
|
|
@@ -22482,9 +23064,7 @@ async function createProductionRuntime(options) {
|
|
|
22482
23064
|
createWebSearchTool(),
|
|
22483
23065
|
...createJobTools(jobs),
|
|
22484
23066
|
...createTerminalTools(terminals, workspace),
|
|
22485
|
-
...createLspTools(workspace, {
|
|
22486
|
-
onStatus: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
22487
|
-
}),
|
|
23067
|
+
...createLspTools(workspace, { manager: lspManager }),
|
|
22488
23068
|
...options.approvalPolicy === "deny" ? [] : [createAskUserQuestionTool(askUserQuestionHandler)],
|
|
22489
23069
|
createTaskOutputTool(),
|
|
22490
23070
|
createTodoWriteTool()
|
|
@@ -22529,8 +23109,8 @@ async function createProductionRuntime(options) {
|
|
|
22529
23109
|
const registeredProviders = registration.registered;
|
|
22530
23110
|
let effectiveLlm = registration.effectiveLlm;
|
|
22531
23111
|
const pluginHost = new PluginHost({
|
|
22532
|
-
globalPluginDirs: [
|
|
22533
|
-
projectPluginDirs: [
|
|
23112
|
+
globalPluginDirs: [join30(home, ".flavor-code", "plugins")],
|
|
23113
|
+
projectPluginDirs: [join30(workspace, ".flavor", "plugins")],
|
|
22534
23114
|
config,
|
|
22535
23115
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
22536
23116
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -22639,7 +23219,7 @@ async function createProductionRuntime(options) {
|
|
|
22639
23219
|
const address = palSocketAddress({
|
|
22640
23220
|
platform: process.platform,
|
|
22641
23221
|
userScope: home,
|
|
22642
|
-
runtimeDir:
|
|
23222
|
+
runtimeDir: join30(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
22643
23223
|
});
|
|
22644
23224
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
22645
23225
|
instanceId: collaboration.instanceId,
|
|
@@ -22750,8 +23330,8 @@ async function createProductionRuntime(options) {
|
|
|
22750
23330
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
22751
23331
|
});
|
|
22752
23332
|
const skills = new SkillRegistry({
|
|
22753
|
-
globalRoots: [
|
|
22754
|
-
projectRoots: [
|
|
23333
|
+
globalRoots: [join30(home, ".flavor-code", "skills")],
|
|
23334
|
+
projectRoots: [join30(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
22755
23335
|
authorizeResource: async () => true,
|
|
22756
23336
|
disabledNames: config.skills.disabled
|
|
22757
23337
|
});
|
|
@@ -22759,7 +23339,7 @@ async function createProductionRuntime(options) {
|
|
|
22759
23339
|
skillsReady.catch(() => void 0);
|
|
22760
23340
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
22761
23341
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
22762
|
-
const flavor = await optionalText2(
|
|
23342
|
+
const flavor = await optionalText2(join30(workspace, "FLAVOR.md"));
|
|
22763
23343
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
22764
23344
|
let memoryContext;
|
|
22765
23345
|
if (memoryStore !== void 0) {
|
|
@@ -23021,6 +23601,8 @@ async function createProductionRuntime(options) {
|
|
|
23021
23601
|
permissionPolicy,
|
|
23022
23602
|
maxIterationsMain: config.maxIterations.main,
|
|
23023
23603
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
23604
|
+
softLimitFactor: config.maxIterations.softLimitFactor,
|
|
23605
|
+
extendIterations: config.maxIterations.extendBy,
|
|
23024
23606
|
hasActiveProgress,
|
|
23025
23607
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
23026
23608
|
toolJournal: {
|
|
@@ -23342,7 +23924,7 @@ async function createProductionRuntime(options) {
|
|
|
23342
23924
|
evolveService.toolDefinition(),
|
|
23343
23925
|
...mcpTools
|
|
23344
23926
|
];
|
|
23345
|
-
const loopFlavor = await optionalText2(
|
|
23927
|
+
const loopFlavor = await optionalText2(join30(input.workspace, "FLAVOR.md"));
|
|
23346
23928
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
23347
23929
|
const loopEnvironment = createPromptEnvironment({
|
|
23348
23930
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -23412,6 +23994,8 @@ async function createProductionRuntime(options) {
|
|
|
23412
23994
|
permissionPolicy,
|
|
23413
23995
|
maxIterationsMain: config.maxIterations.main,
|
|
23414
23996
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
23997
|
+
softLimitFactor: config.maxIterations.softLimitFactor,
|
|
23998
|
+
extendIterations: config.maxIterations.extendBy,
|
|
23415
23999
|
loopMode: true,
|
|
23416
24000
|
afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
23417
24001
|
toolJournal: {
|
|
@@ -23448,12 +24032,83 @@ async function createProductionRuntime(options) {
|
|
|
23448
24032
|
await loopExecutionEnvironment?.dispose();
|
|
23449
24033
|
}
|
|
23450
24034
|
};
|
|
24035
|
+
let activeLongTask;
|
|
24036
|
+
const readMemoryRestartMarker = () => {
|
|
24037
|
+
try {
|
|
24038
|
+
return parseMemoryRestartMarker(readFileSync3(memoryRestartMarkerPath(workspace), "utf8"));
|
|
24039
|
+
} catch {
|
|
24040
|
+
return void 0;
|
|
24041
|
+
}
|
|
24042
|
+
};
|
|
24043
|
+
const currentPressureReading = () => verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO) ?? { ...readHeap(), gcVerified: false };
|
|
24044
|
+
const collectRotationHolders = () => {
|
|
24045
|
+
const holders = {};
|
|
24046
|
+
try {
|
|
24047
|
+
const context = harness.main.context.census();
|
|
24048
|
+
holders.context = { entries: context.messages, chars: context.messageChars };
|
|
24049
|
+
holders.contextVisibility = { entries: context.visibilityEntries, chars: context.visibilityChars };
|
|
24050
|
+
holders.contextTransient = { entries: context.transientEntries, chars: context.transientChars };
|
|
24051
|
+
} catch {
|
|
24052
|
+
}
|
|
24053
|
+
try {
|
|
24054
|
+
holders.journal = { entries: harnessJournal.recordCount };
|
|
24055
|
+
} catch {
|
|
24056
|
+
}
|
|
24057
|
+
try {
|
|
24058
|
+
const jobCensus = jobs.census();
|
|
24059
|
+
holders.jobs = { entries: jobCensus.jobs, chars: jobCensus.windowChars };
|
|
24060
|
+
} catch {
|
|
24061
|
+
}
|
|
24062
|
+
try {
|
|
24063
|
+
holders.sessionTree = { entries: sessionHistory.nodeCount };
|
|
24064
|
+
} catch {
|
|
24065
|
+
}
|
|
24066
|
+
return holders;
|
|
24067
|
+
};
|
|
24068
|
+
const rotateForHeap = (continuation, reading) => {
|
|
24069
|
+
requestMemoryRestart(workspace, sessionId, emitOutput, {
|
|
24070
|
+
...continuation === void 0 ? {} : { continuation },
|
|
24071
|
+
...reading === void 0 ? {} : { reading },
|
|
24072
|
+
holders: collectRotationHolders()
|
|
24073
|
+
}, options.onMemoryRestartRequested);
|
|
24074
|
+
};
|
|
24075
|
+
const maybeRotateAtBoundary = (continuation) => {
|
|
24076
|
+
activeLongTask = continuation;
|
|
24077
|
+
const pressure = verifiedHeapPressure(MEMORY_PRESSURE_HEAP_RATIO);
|
|
24078
|
+
if (pressure !== void 0) {
|
|
24079
|
+
rotateForHeap(continuation, pressure);
|
|
24080
|
+
return;
|
|
24081
|
+
}
|
|
24082
|
+
const reading = heapRotationNeeded();
|
|
24083
|
+
if (reading === void 0) return;
|
|
24084
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
24085
|
+
rotateForHeap(continuation, reading);
|
|
24086
|
+
};
|
|
24087
|
+
const maybeRotateAtTurnBoundary = () => {
|
|
24088
|
+
const reading = heapRotationNeeded();
|
|
24089
|
+
if (reading === void 0) return;
|
|
24090
|
+
if (rotationCooldownActive(readMemoryRestartMarker(), sessionId, /* @__PURE__ */ new Date())) return;
|
|
24091
|
+
rotateForHeap(void 0, reading);
|
|
24092
|
+
};
|
|
24093
|
+
const consumePendingContinuation = () => {
|
|
24094
|
+
const marker = readMemoryRestartMarker();
|
|
24095
|
+
const continuation = pendingContinuation(marker, sessionId, /* @__PURE__ */ new Date());
|
|
24096
|
+
if (continuation === void 0 || marker === void 0) return void 0;
|
|
24097
|
+
try {
|
|
24098
|
+
const markerPath = memoryRestartMarkerPath(workspace);
|
|
24099
|
+
writeMarkerAtomically(markerPath, consumedMemoryRestartMarker(marker));
|
|
24100
|
+
} catch {
|
|
24101
|
+
}
|
|
24102
|
+
return continuation;
|
|
24103
|
+
};
|
|
23451
24104
|
const loopStore = new LoopStore({ workspace });
|
|
23452
24105
|
const loopOrchestrator = new LoopOrchestrator({
|
|
23453
24106
|
workspace,
|
|
23454
24107
|
config: config.loop,
|
|
23455
24108
|
persistence: loopStore,
|
|
23456
24109
|
hallucinationGuard,
|
|
24110
|
+
onCycleBoundary: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
24111
|
+
onMemoryCheckpoint: (loopId) => maybeRotateAtBoundary({ kind: "loop", id: loopId }),
|
|
23457
24112
|
prepareWorkspace: (input) => prepareLoopWorkspace(input),
|
|
23458
24113
|
inferVerification: inferVerificationPlan,
|
|
23459
24114
|
runWorker: ({ workspace: executionWorkspace, prompt, signal }) => runLoopWorker({ workspace: executionWorkspace, prompt, signal }),
|
|
@@ -23490,9 +24145,11 @@ async function createProductionRuntime(options) {
|
|
|
23490
24145
|
plannerModelId: mainModel,
|
|
23491
24146
|
classifierModelId: mainModel,
|
|
23492
24147
|
skepticCount: 3,
|
|
23493
|
-
maxRounds:
|
|
23494
|
-
maxStallStreak:
|
|
24148
|
+
maxRounds: config.goal.maxRounds,
|
|
24149
|
+
maxStallStreak: config.goal.maxStallStreak,
|
|
23495
24150
|
persistence: goalStore,
|
|
24151
|
+
onRoundBoundary: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
24152
|
+
onMemoryCheckpoint: (goalId) => maybeRotateAtBoundary({ kind: "goal", id: goalId }),
|
|
23496
24153
|
verifyHost: async (signal) => runVerificationPlan(
|
|
23497
24154
|
await inferVerificationPlan(workspace),
|
|
23498
24155
|
workspace,
|
|
@@ -23587,30 +24244,49 @@ async function createProductionRuntime(options) {
|
|
|
23587
24244
|
contextEpoch: harness.main.context.snapshot().epoch?.id ?? "legacy"
|
|
23588
24245
|
});
|
|
23589
24246
|
const turnId = harnessJournal.startTurn(turnConfig, { prompt, initialUserMessage: runOptions?.initialUserMessage });
|
|
23590
|
-
return
|
|
23591
|
-
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
24247
|
+
return monitorTurnMemory(
|
|
24248
|
+
durableTurn(persistAfter(runMain(
|
|
24249
|
+
harness,
|
|
24250
|
+
skills,
|
|
24251
|
+
prompt,
|
|
24252
|
+
signal,
|
|
24253
|
+
selectedModels.mainError,
|
|
24254
|
+
memoryStore === void 0 || !memoryHasRoutableEntries && userMemoryContext === void 0 ? void 0 : {
|
|
24255
|
+
store: memoryStore,
|
|
24256
|
+
taskId: memoryLifecycle.taskId ?? sessionId,
|
|
24257
|
+
topK: config.memory.retrievalTopK,
|
|
24258
|
+
maxChars: config.memory.maxPromptChars
|
|
24259
|
+
},
|
|
24260
|
+
runOptions?.getSteeringMessages,
|
|
24261
|
+
runOptions?.initialUserMessage,
|
|
24262
|
+
runOptions?.additionalContext,
|
|
24263
|
+
ide
|
|
24264
|
+
), persist), harnessJournal, turnId, turnConfig),
|
|
24265
|
+
() => rotateForHeap(void 0, currentPressureReading()),
|
|
24266
|
+
() => maybeRotateAtTurnBoundary()
|
|
24267
|
+
);
|
|
23607
24268
|
},
|
|
23608
24269
|
runSkill: (skill, prompt, signal) => persistAfter(
|
|
23609
24270
|
runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
|
|
23610
24271
|
persist
|
|
23611
24272
|
),
|
|
23612
|
-
runLoop: (goal, signal) =>
|
|
23613
|
-
|
|
24273
|
+
runLoop: (goal, signal) => monitorLongTaskRestart(
|
|
24274
|
+
runLoopSession(loopOrchestrator, hooks, goal, signal),
|
|
24275
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
24276
|
+
),
|
|
24277
|
+
runGoal: (goal, signal) => monitorLongTaskRestart(
|
|
24278
|
+
persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
|
|
24279
|
+
() => rotateForHeap(activeLongTask, currentPressureReading())
|
|
24280
|
+
),
|
|
24281
|
+
resumeLoop: (loopId, signal) => monitorLongTaskRestart(
|
|
24282
|
+
runResumeLoopSession(loopOrchestrator, hooks, loopId, signal),
|
|
24283
|
+
() => rotateForHeap(activeLongTask ?? { kind: "loop", id: loopId }, currentPressureReading())
|
|
24284
|
+
),
|
|
24285
|
+
resumeGoal: (goalId, signal) => monitorLongTaskRestart(
|
|
24286
|
+
persistEach(runResumeGoalSession(goalOrchestrator, goalId, signal), persist),
|
|
24287
|
+
() => rotateForHeap(activeLongTask ?? { kind: "goal", id: goalId }, currentPressureReading())
|
|
24288
|
+
),
|
|
24289
|
+
continuation: () => consumePendingContinuation(),
|
|
23614
24290
|
mcp: async (command, signal) => {
|
|
23615
24291
|
signal.throwIfAborted();
|
|
23616
24292
|
const manager = await mcpReady;
|
|
@@ -23701,10 +24377,10 @@ async function createProductionRuntime(options) {
|
|
|
23701
24377
|
explain: (query, focus, signal, onText) => runExplain({
|
|
23702
24378
|
graph: new DesktopAstGraphService(workspace),
|
|
23703
24379
|
readFile: async (path) => {
|
|
23704
|
-
const root =
|
|
23705
|
-
const abs =
|
|
23706
|
-
if (abs !== root && !abs.startsWith(root +
|
|
23707
|
-
return
|
|
24380
|
+
const root = resolve31(workspace);
|
|
24381
|
+
const abs = resolve31(root, path);
|
|
24382
|
+
if (abs !== root && !abs.startsWith(root + sep22)) throw new Error(`path outside workspace: ${path}`);
|
|
24383
|
+
return readFile24(abs, "utf8");
|
|
23708
24384
|
},
|
|
23709
24385
|
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
23710
24386
|
questions,
|
|
@@ -23716,7 +24392,7 @@ async function createProductionRuntime(options) {
|
|
|
23716
24392
|
}, query, focus, signal),
|
|
23717
24393
|
audit: async (toolFilter) => {
|
|
23718
24394
|
try {
|
|
23719
|
-
const raw = await
|
|
24395
|
+
const raw = await readFile24(auditLogger.path, "utf8");
|
|
23720
24396
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
23721
24397
|
const entries = lines.map((line) => {
|
|
23722
24398
|
try {
|
|
@@ -23756,7 +24432,7 @@ ${summary2}`;
|
|
|
23756
24432
|
doctor: async () => formatDoctorReport(await runDoctor({ workspace, home, environment })),
|
|
23757
24433
|
usage: async () => {
|
|
23758
24434
|
try {
|
|
23759
|
-
const raw = await
|
|
24435
|
+
const raw = await readFile24(usageLogPath(), "utf8");
|
|
23760
24436
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
23761
24437
|
} catch (error) {
|
|
23762
24438
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -23909,7 +24585,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23909
24585
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
23910
24586
|
}
|
|
23911
24587
|
try {
|
|
23912
|
-
const tokenStore = createFileTokenStore(
|
|
24588
|
+
const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
23913
24589
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
23914
24590
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
23915
24591
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -23956,7 +24632,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
23956
24632
|
}
|
|
23957
24633
|
},
|
|
23958
24634
|
async logout() {
|
|
23959
|
-
const tokenStore = createFileTokenStore(
|
|
24635
|
+
const tokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
23960
24636
|
const tokens = await tokenStore.load();
|
|
23961
24637
|
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
23962
24638
|
if (!hadCredentials) {
|
|
@@ -24055,6 +24731,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
24055
24731
|
if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
|
|
24056
24732
|
if (islandControlServer !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "island-control", islandControlServer.close());
|
|
24057
24733
|
terminals.dispose();
|
|
24734
|
+
await boundedStep(stepTimeoutMs, diagnostics, "language-servers", lspManager.dispose());
|
|
24058
24735
|
await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
|
|
24059
24736
|
auditLogger.close();
|
|
24060
24737
|
evolveService.dispose();
|
|
@@ -24095,6 +24772,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
24095
24772
|
if (sleepScheduler !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "sleep-scheduler", sleepScheduler.dispose());
|
|
24096
24773
|
if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
|
|
24097
24774
|
terminals.dispose();
|
|
24775
|
+
await boundedStep(stepTimeoutMs, diagnostics, "language-servers", lspManager.dispose());
|
|
24098
24776
|
await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
|
|
24099
24777
|
unsubscribeCollaboration?.();
|
|
24100
24778
|
pendingCollaborationEvents.length = 0;
|
|
@@ -24147,10 +24825,16 @@ function formatMcpReconnect(server) {
|
|
|
24147
24825
|
return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
|
|
24148
24826
|
}
|
|
24149
24827
|
async function* runLoopSession(orchestrator, hooks, goal, signal) {
|
|
24828
|
+
yield* loopSessionEvents(orchestrator.run({ goal, signal }), hooks);
|
|
24829
|
+
}
|
|
24830
|
+
async function* runResumeLoopSession(orchestrator, hooks, loopId, signal) {
|
|
24831
|
+
yield* loopSessionEvents(orchestrator.resume({ loopId, signal }), hooks);
|
|
24832
|
+
}
|
|
24833
|
+
async function* loopSessionEvents(events, hooks) {
|
|
24150
24834
|
let totalInputTokens = 0;
|
|
24151
24835
|
let totalOutputTokens = 0;
|
|
24152
24836
|
let latestVerification;
|
|
24153
|
-
for await (const event of
|
|
24837
|
+
for await (const event of events) {
|
|
24154
24838
|
if (event.type === "worker-event") {
|
|
24155
24839
|
if (event.event.type === "usage") {
|
|
24156
24840
|
totalInputTokens += event.event.inputTokens;
|
|
@@ -24248,13 +24932,13 @@ async function workspaceFingerprint(workspace) {
|
|
|
24248
24932
|
let remaining = 5 * 1024 * 1024;
|
|
24249
24933
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
24250
24934
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
24251
|
-
const path =
|
|
24252
|
-
const relativePath =
|
|
24253
|
-
if (relativePath === ".." || relativePath.startsWith(`..${
|
|
24935
|
+
const path = resolve31(workspace, name);
|
|
24936
|
+
const relativePath = relative22(resolve31(workspace), path);
|
|
24937
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep22}`)) continue;
|
|
24254
24938
|
hash.update(name);
|
|
24255
24939
|
if (remaining <= 0) continue;
|
|
24256
24940
|
try {
|
|
24257
|
-
const content = await
|
|
24941
|
+
const content = await readFile24(path);
|
|
24258
24942
|
const slice = content.subarray(0, remaining);
|
|
24259
24943
|
hash.update(slice);
|
|
24260
24944
|
remaining -= slice.length;
|
|
@@ -24366,7 +25050,16 @@ async function* durableTurn(source, journal, turnId, config) {
|
|
|
24366
25050
|
}
|
|
24367
25051
|
}
|
|
24368
25052
|
}
|
|
24369
|
-
async function*
|
|
25053
|
+
async function* monitorTurnMemory(source, onPressure, onBoundary) {
|
|
25054
|
+
let pressure = false;
|
|
25055
|
+
for await (const event of source) {
|
|
25056
|
+
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
25057
|
+
yield event;
|
|
25058
|
+
}
|
|
25059
|
+
if (pressure) onPressure();
|
|
25060
|
+
else onBoundary();
|
|
25061
|
+
}
|
|
25062
|
+
async function* monitorLongTaskRestart(source, onRestartNeeded) {
|
|
24370
25063
|
let pressure = false;
|
|
24371
25064
|
for await (const event of source) {
|
|
24372
25065
|
if (event.type === "error" && event.error.code === "memory_pressure") pressure = true;
|
|
@@ -24374,7 +25067,10 @@ async function* monitorMemoryRestart(source, onRestartNeeded) {
|
|
|
24374
25067
|
}
|
|
24375
25068
|
if (pressure) onRestartNeeded();
|
|
24376
25069
|
}
|
|
24377
|
-
function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
25070
|
+
function requestMemoryRestart(workspace, sessionId, emitOutput, rotation, onRestartRequested) {
|
|
25071
|
+
if (memoryRotationActive()) return;
|
|
25072
|
+
markMemoryRotation();
|
|
25073
|
+
const continuation = rotation?.continuation;
|
|
24378
25074
|
try {
|
|
24379
25075
|
const markerPath = memoryRestartMarkerPath(workspace);
|
|
24380
25076
|
let previous;
|
|
@@ -24382,14 +25078,43 @@ function requestMemoryRestart(workspace, sessionId, emitOutput) {
|
|
|
24382
25078
|
previous = parseMemoryRestartMarker(readFileSync3(markerPath, "utf8"));
|
|
24383
25079
|
} catch {
|
|
24384
25080
|
}
|
|
24385
|
-
|
|
24386
|
-
writeFileSync2(markerPath, `${JSON.stringify(nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date()))}
|
|
24387
|
-
`, "utf8");
|
|
25081
|
+
writeMarkerAtomically(markerPath, nextMemoryRestartMarker(previous, sessionId, /* @__PURE__ */ new Date(), continuation));
|
|
24388
25082
|
} catch {
|
|
24389
25083
|
}
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
25084
|
+
if (rotation?.reading !== void 0) {
|
|
25085
|
+
const census = {
|
|
25086
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25087
|
+
reason: continuation === void 0 ? "memory-pressure" : `heap-rotation:${continuation.kind}`,
|
|
25088
|
+
sessionId,
|
|
25089
|
+
heap: censusHeapBlock(rotation.reading),
|
|
25090
|
+
holders: rotation.holders ?? {}
|
|
25091
|
+
};
|
|
25092
|
+
writeRotationCensus(workspace, census);
|
|
25093
|
+
if (heapSnapshotWarranted(rotation.reading)) writeRotationHeapSnapshot(workspace);
|
|
25094
|
+
}
|
|
25095
|
+
emitOutput({
|
|
25096
|
+
type: "notice",
|
|
25097
|
+
message: continuation === void 0 ? "Memory watermark reached; the session is saved and Flavor is restarting with a fresh heap." : `Memory watermark reached; the session and the running ${continuation.kind} are saved, and Flavor is restarting with a fresh heap to continue it.`
|
|
25098
|
+
});
|
|
25099
|
+
if (onRestartRequested !== void 0) onRestartRequested(sessionId);
|
|
25100
|
+
else {
|
|
25101
|
+
process.exitCode = MEMORY_RESTART_EXIT_CODE;
|
|
25102
|
+
emitOutput({ type: "exit" });
|
|
25103
|
+
}
|
|
25104
|
+
}
|
|
25105
|
+
function writeMarkerAtomically(path, marker) {
|
|
25106
|
+
mkdirSync4(dirname17(path), { recursive: true });
|
|
25107
|
+
const temporary = `${path}.${process.pid}.${randomUUID20()}.tmp`;
|
|
25108
|
+
try {
|
|
25109
|
+
writeFileSync3(temporary, `${JSON.stringify(marker)}
|
|
25110
|
+
`, { encoding: "utf8", mode: 384 });
|
|
25111
|
+
renameSync2(temporary, path);
|
|
25112
|
+
} finally {
|
|
25113
|
+
try {
|
|
25114
|
+
unlinkSync3(temporary);
|
|
25115
|
+
} catch {
|
|
25116
|
+
}
|
|
25117
|
+
}
|
|
24393
25118
|
}
|
|
24394
25119
|
async function runGitCommit(deps, hint, signal) {
|
|
24395
25120
|
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
@@ -24484,7 +25209,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
24484
25209
|
const configured = { ...providers };
|
|
24485
25210
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
24486
25211
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
24487
|
-
const oauthTokenStore = createFileTokenStore(
|
|
25212
|
+
const oauthTokenStore = createFileTokenStore(join30(home, ".flavor-code", "auth.json"));
|
|
24488
25213
|
const registered = [];
|
|
24489
25214
|
let effectiveLlm;
|
|
24490
25215
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -24753,7 +25478,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
24753
25478
|
}
|
|
24754
25479
|
async function optionalText2(path) {
|
|
24755
25480
|
try {
|
|
24756
|
-
return await
|
|
25481
|
+
return await readFile24(path, "utf8");
|
|
24757
25482
|
} catch (error) {
|
|
24758
25483
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
24759
25484
|
throw error;
|
|
@@ -24838,7 +25563,17 @@ function attachCleanupError(primary, cleanup) {
|
|
|
24838
25563
|
}
|
|
24839
25564
|
}
|
|
24840
25565
|
async function* runGoalSession(orchestrator, goal, signal) {
|
|
24841
|
-
|
|
25566
|
+
yield* goalSessionEvents(orchestrator.run({ goal, signal }));
|
|
25567
|
+
}
|
|
25568
|
+
async function* runResumeGoalSession(orchestrator, goalId, signal) {
|
|
25569
|
+
yield* goalSessionEvents(orchestrator.resume({ goalId, signal }));
|
|
25570
|
+
}
|
|
25571
|
+
async function* goalSessionEvents(events) {
|
|
25572
|
+
for await (const event of events) {
|
|
25573
|
+
if (event.type === "goal-resumed") {
|
|
25574
|
+
yield { type: "notice", message: `Resuming goal ${event.goalId} at round ${event.round} after a heap rotation.` };
|
|
25575
|
+
continue;
|
|
25576
|
+
}
|
|
24842
25577
|
if (event.type === "goal-plan-created") {
|
|
24843
25578
|
yield { type: "notice", message: `Goal plan created (${event.plan.kind}) with ${event.plan.criteria.length} acceptance criteria.` };
|
|
24844
25579
|
yield { type: "notice", message: `Plan file: ${event.planPath}` };
|