@turmind/halo 2.2.2 → 2.2.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/README.md +2 -1
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/1989-9d30cdb1a86e4beb.js +1 -0
- package/admin-out/_next/static/chunks/4362.115405260299210d.js +1 -0
- package/admin-out/_next/static/chunks/{7613.fe24922a6a839656.js → 7073.4081ed209e6748bd.js} +1 -1
- package/admin-out/_next/static/chunks/{7073.9d5f9dd7a77827b7.js → 7613.9c1657bb3b52d1b5.js} +1 -1
- package/admin-out/_next/static/chunks/app/{page-10a4afac9cbc27a2.js → page-c9d174a5432b0007.js} +1 -1
- package/admin-out/_next/static/chunks/{webpack-9532ec7f1fa9a757.js → webpack-1970c5140e4019b7.js} +1 -1
- package/admin-out/_next/static/css/87d4fc049f865389.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/a2a.md +3 -1
- package/bundled-docs/design/architecture.md +1 -1
- package/bundled-docs/design/cron.md +1 -1
- package/bundled-docs/design/evolution.md +6 -6
- package/bundled-docs/design/node.md +18 -39
- package/bundled-docs/design/observability.md +2 -2
- package/bundled-docs/design/relay.md +1 -1
- package/bundled-docs/design/session.md +3 -3
- package/bundled-docs/design/storage.md +5 -2
- package/bundled-docs/design/stt-local.md +2 -2
- package/bundled-docs/design/ws.md +3 -3
- package/bundled-docs/dev/api.md +10 -9
- package/bundled-docs/dev/deploy.md +1 -1
- package/bundled-docs/dev/env.md +2 -1
- package/bundled-docs/dev/tools.md +10 -6
- package/bundled-docs/guide/README.md +1 -1
- package/bundled-docs/guide/cli.md +4 -31
- package/bundled-docs/guide/node.md +57 -36
- package/bundled-docs/requirements/command.md +1 -1
- package/bundled-docs/requirements/settings.md +39 -2
- package/dist/evo-wrapper.js +368 -268
- package/dist/index.js +2330 -2531
- package/package.json +2 -1
- package/templates/agents/default/agent.yaml +1 -1
- package/templates/models/aws-bedrock-openai.yaml +81 -7
- package/templates/skills/cron/SKILL.md +1 -3
- package/admin-out/_next/static/chunks/1989-3b2faa2731d973c4.js +0 -1
- package/admin-out/_next/static/chunks/4362.9d2ee4f9b6facc49.js +0 -1
- package/admin-out/_next/static/css/8fc43fce83e5ce5d.css +0 -1
- /package/admin-out/_next/static/{u9pg60rITCj73MBYeXsW1 → vf5W8US5y8B2f1E6zJJw3}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{u9pg60rITCj73MBYeXsW1 → vf5W8US5y8B2f1E6zJJw3}/_ssgManifest.js +0 -0
package/dist/evo-wrapper.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ../server/dist/evolution/evo-wrapper.js
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs12 from "node:fs";
|
|
3
|
+
import path14 from "node:path";
|
|
4
4
|
import { spawn as spawn2 } from "node:child_process";
|
|
5
|
-
import { homedir as
|
|
5
|
+
import { homedir as homedir7 } from "node:os";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
8
|
import { eq as eq3, and as and3, inArray as inArray2 } from "drizzle-orm";
|
|
@@ -237,41 +237,133 @@ function evoApplyLogFile(applyId) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// ../server/dist/config.js
|
|
240
|
+
import fs9 from "node:fs";
|
|
241
|
+
import path12 from "node:path";
|
|
242
|
+
import { homedir as homedir6 } from "node:os";
|
|
243
|
+
|
|
244
|
+
// ../server/dist/settings-schema.js
|
|
240
245
|
import fs8 from "node:fs";
|
|
241
246
|
import path11 from "node:path";
|
|
242
247
|
import { homedir as homedir5 } from "node:os";
|
|
243
248
|
import YAML5 from "yaml";
|
|
244
249
|
|
|
245
|
-
// ../server/dist/settings-schema.js
|
|
246
|
-
import fs7 from "node:fs";
|
|
247
|
-
import path10 from "node:path";
|
|
248
|
-
import { homedir as homedir4 } from "node:os";
|
|
249
|
-
import YAML4 from "yaml";
|
|
250
|
-
|
|
251
250
|
// ../server/dist/tools/sandbox.js
|
|
252
251
|
import { exec, execFile, spawn } from "node:child_process";
|
|
253
252
|
import { promisify } from "node:util";
|
|
253
|
+
import path4 from "node:path";
|
|
254
|
+
import { homedir as homedir3 } from "node:os";
|
|
255
|
+
|
|
256
|
+
// ../server/dist/settings-file.js
|
|
257
|
+
import fs2 from "node:fs";
|
|
254
258
|
import path3 from "node:path";
|
|
255
259
|
import { homedir as homedir2 } from "node:os";
|
|
260
|
+
import YAML from "yaml";
|
|
261
|
+
function loadYamlFile(filePath) {
|
|
262
|
+
try {
|
|
263
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
264
|
+
return YAML.parse(raw) ?? {};
|
|
265
|
+
} catch {
|
|
266
|
+
return {};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
var SETTINGS_PATH = path3.join(homedir2(), ".halo", "secrets", "settings.yaml");
|
|
270
|
+
var _settingsCache = null;
|
|
271
|
+
function getSettings() {
|
|
272
|
+
let mtimeMs = 0;
|
|
273
|
+
try {
|
|
274
|
+
mtimeMs = fs2.statSync(SETTINGS_PATH).mtimeMs;
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
if (!_settingsCache || _settingsCache.mtimeMs !== mtimeMs) {
|
|
278
|
+
_settingsCache = { mtimeMs, data: loadYamlFile(SETTINGS_PATH) };
|
|
279
|
+
}
|
|
280
|
+
return _settingsCache.data;
|
|
281
|
+
}
|
|
282
|
+
var ENV_PATTERN = /<<([A-Z_][A-Z0-9_]*)>>/g;
|
|
283
|
+
function expandEnv(raw) {
|
|
284
|
+
if (!raw)
|
|
285
|
+
return "";
|
|
286
|
+
return raw.replace(ENV_PATTERN, (m, name) => process.env[name] ?? m);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ../server/dist/outbound-proxy.js
|
|
290
|
+
var DEFAULT_NO_PROXY = ["localhost", "127.0.0.1", "::1"];
|
|
291
|
+
function checkProxyUrl(raw) {
|
|
292
|
+
let url;
|
|
293
|
+
try {
|
|
294
|
+
url = new URL(raw);
|
|
295
|
+
} catch {
|
|
296
|
+
return "not a valid URL";
|
|
297
|
+
}
|
|
298
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
299
|
+
return "scheme must be http:// or https://";
|
|
300
|
+
if (url.username || url.password)
|
|
301
|
+
return "credentials are not allowed in the URL \u2014 put them in Token (general.outbound_proxy.token)";
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
var _cache = null;
|
|
305
|
+
function outboundProxy() {
|
|
306
|
+
const settings = getSettings();
|
|
307
|
+
if (_cache?.settings !== settings)
|
|
308
|
+
_cache = { settings, proxy: resolve(settings) };
|
|
309
|
+
return _cache.proxy;
|
|
310
|
+
}
|
|
311
|
+
function resolve(settings) {
|
|
312
|
+
const general = settings.general;
|
|
313
|
+
const cfg = general?.outbound_proxy;
|
|
314
|
+
const enabled = ["true", "1"].includes(String(cfg?.enabled ?? "").trim().toLowerCase());
|
|
315
|
+
const raw = typeof cfg?.url === "string" ? cfg.url.trim() : "";
|
|
316
|
+
if (!enabled || !raw)
|
|
317
|
+
return null;
|
|
318
|
+
const bad = checkProxyUrl(raw);
|
|
319
|
+
if (bad) {
|
|
320
|
+
console.warn(`[OutboundProxy] general.outbound_proxy.url ignored (${bad}) \u2014 proxy off`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
const url = new URL(raw).origin;
|
|
324
|
+
const cred = cfg?.token == null ? "" : expandEnv(String(cfg.token)).trim();
|
|
325
|
+
const colon = cred.indexOf(":");
|
|
326
|
+
const user = colon < 0 ? cred : cred.slice(0, colon);
|
|
327
|
+
const pass = colon < 0 ? "" : cred.slice(colon + 1);
|
|
328
|
+
const userinfo = `${encodeURIComponent(user)}:${encodeURIComponent(pass)}`;
|
|
329
|
+
const basic = Buffer.from(`${user}:${pass}`).toString("base64");
|
|
330
|
+
const inherited = [process.env.NO_PROXY, process.env.no_proxy].flatMap((v) => (v ?? "").split(","));
|
|
331
|
+
const merged = [.../* @__PURE__ */ new Set([...DEFAULT_NO_PROXY, ...inherited.map((s) => s.trim()).filter(Boolean)])];
|
|
332
|
+
const noProxy = merged.includes("*") ? ["*"] : merged;
|
|
333
|
+
console.log(`[OutboundProxy] on: ${url}${cred ? " (with token)" : ""}`);
|
|
334
|
+
return {
|
|
335
|
+
url,
|
|
336
|
+
envUrl: cred ? url.replace("://", `://${userinfo}@`) : url,
|
|
337
|
+
authorization: cred ? `Basic ${basic}` : "",
|
|
338
|
+
noProxy,
|
|
339
|
+
credential: cred ? { raw: cred, plain: `${user}:${pass}`, userinfo, basic } : null
|
|
340
|
+
};
|
|
341
|
+
}
|
|
256
342
|
|
|
257
343
|
// ../server/dist/child-env.js
|
|
258
344
|
var settingsOtelEnvKeys = /* @__PURE__ */ new Set();
|
|
259
|
-
function cleanChildEnv() {
|
|
345
|
+
function cleanChildEnv(proxyEnv = outboundProxyEnv()) {
|
|
260
346
|
const env = { ...process.env };
|
|
261
347
|
delete env.HALO_PASSWORD;
|
|
262
348
|
delete env.HALO_JWT_SECRET;
|
|
263
349
|
delete env.HALO_NODE_TOKEN;
|
|
264
350
|
for (const key of settingsOtelEnvKeys)
|
|
265
351
|
delete env[key];
|
|
266
|
-
return env;
|
|
352
|
+
return Object.assign(env, proxyEnv);
|
|
353
|
+
}
|
|
354
|
+
function outboundProxyEnv(proxy = outboundProxy()) {
|
|
355
|
+
if (!proxy)
|
|
356
|
+
return {};
|
|
357
|
+
const noProxy = proxy.noProxy.join(",");
|
|
358
|
+
return { http_proxy: proxy.envUrl, https_proxy: proxy.envUrl, HTTP_PROXY: proxy.envUrl, HTTPS_PROXY: proxy.envUrl, no_proxy: noProxy, NO_PROXY: noProxy };
|
|
267
359
|
}
|
|
268
360
|
|
|
269
361
|
// ../server/dist/tools/sandbox.js
|
|
270
362
|
var execAsync = promisify(exec);
|
|
271
363
|
var execFileAsync = promisify(execFile);
|
|
272
|
-
var HOME =
|
|
273
|
-
var HALO_HOME =
|
|
274
|
-
var EMPTY_MASK_FILE =
|
|
364
|
+
var HOME = homedir3();
|
|
365
|
+
var HALO_HOME = path4.join(HOME, ".halo");
|
|
366
|
+
var EMPTY_MASK_FILE = path4.join(HALO_HOME, ".sandbox-empty");
|
|
275
367
|
var DEFAULT_HIDDEN_DIRS = [
|
|
276
368
|
"~/.halo/secrets",
|
|
277
369
|
"~/.aws",
|
|
@@ -313,9 +405,9 @@ var DEFAULT_HIDDEN_FILES = [
|
|
|
313
405
|
];
|
|
314
406
|
|
|
315
407
|
// ../server/dist/models/registry.js
|
|
316
|
-
import
|
|
317
|
-
import
|
|
318
|
-
import
|
|
408
|
+
import fs3 from "node:fs";
|
|
409
|
+
import path5 from "node:path";
|
|
410
|
+
import YAML2 from "yaml";
|
|
319
411
|
var MODEL_RUNTIME_NAMES = [
|
|
320
412
|
"anthropic-messages",
|
|
321
413
|
"openai-chat",
|
|
@@ -346,15 +438,15 @@ function logOnce(msg) {
|
|
|
346
438
|
function readProviderDir(dir, source) {
|
|
347
439
|
let names;
|
|
348
440
|
try {
|
|
349
|
-
names =
|
|
441
|
+
names = fs3.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".yaml")).map((e) => e.name).sort();
|
|
350
442
|
} catch {
|
|
351
443
|
return [];
|
|
352
444
|
}
|
|
353
445
|
const out = [];
|
|
354
446
|
for (const name of names) {
|
|
355
|
-
const file =
|
|
447
|
+
const file = path5.join(dir, name);
|
|
356
448
|
try {
|
|
357
|
-
const parsed =
|
|
449
|
+
const parsed = YAML2.parse(fs3.readFileSync(file, "utf-8"));
|
|
358
450
|
if (parsed && typeof parsed === "object" && typeof parsed.id === "string") {
|
|
359
451
|
out.push({
|
|
360
452
|
id: parsed.id,
|
|
@@ -402,34 +494,34 @@ function loadProviders(bundledDir = globalModelsDir(), hubDir = globalHubModelsD
|
|
|
402
494
|
}
|
|
403
495
|
|
|
404
496
|
// ../server/dist/extensions/registry.js
|
|
405
|
-
import
|
|
406
|
-
import
|
|
497
|
+
import fs5 from "node:fs";
|
|
498
|
+
import path8 from "node:path";
|
|
407
499
|
|
|
408
500
|
// ../server/dist/extensions/state.js
|
|
409
|
-
import
|
|
410
|
-
import
|
|
501
|
+
import fs4 from "node:fs";
|
|
502
|
+
import path7 from "node:path";
|
|
411
503
|
|
|
412
504
|
// ../server/dist/init.js
|
|
413
|
-
import
|
|
505
|
+
import path6 from "node:path";
|
|
414
506
|
import { fileURLToPath } from "node:url";
|
|
415
|
-
import
|
|
416
|
-
var __dirname =
|
|
417
|
-
var TEMPLATES_DIR =
|
|
507
|
+
import YAML3 from "yaml";
|
|
508
|
+
var __dirname = path6.dirname(fileURLToPath(import.meta.url));
|
|
509
|
+
var TEMPLATES_DIR = path6.resolve(__dirname, "..", "templates");
|
|
418
510
|
var DOCS_SRC_CANDIDATES = [
|
|
419
|
-
|
|
420
|
-
|
|
511
|
+
path6.resolve(__dirname, "..", "bundled-docs"),
|
|
512
|
+
path6.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
421
513
|
];
|
|
422
514
|
|
|
423
515
|
// ../server/dist/extensions/state.js
|
|
424
516
|
function extensionStatePath() {
|
|
425
|
-
return
|
|
517
|
+
return path7.join(globalExtensionsDir(), ".state.json");
|
|
426
518
|
}
|
|
427
519
|
var cache = null;
|
|
428
520
|
function load() {
|
|
429
521
|
const file = extensionStatePath();
|
|
430
522
|
let st;
|
|
431
523
|
try {
|
|
432
|
-
st =
|
|
524
|
+
st = fs4.statSync(file);
|
|
433
525
|
} catch {
|
|
434
526
|
return { states: {}, disabled: /* @__PURE__ */ new Set() };
|
|
435
527
|
}
|
|
@@ -438,7 +530,7 @@ function load() {
|
|
|
438
530
|
return cache;
|
|
439
531
|
let states = null;
|
|
440
532
|
try {
|
|
441
|
-
const raw = JSON.parse(
|
|
533
|
+
const raw = JSON.parse(fs4.readFileSync(file, "utf-8"));
|
|
442
534
|
if (typeof raw === "object" && raw !== null && !Array.isArray(raw))
|
|
443
535
|
states = raw;
|
|
444
536
|
} catch {
|
|
@@ -683,17 +775,17 @@ function parseManifest(raw, dirName, entryExists) {
|
|
|
683
775
|
...m.source !== void 0 ? { source: m.source } : {}
|
|
684
776
|
};
|
|
685
777
|
}
|
|
686
|
-
function readExtensionDir(dir, id =
|
|
778
|
+
function readExtensionDir(dir, id = path8.basename(dir)) {
|
|
687
779
|
let raw;
|
|
688
780
|
try {
|
|
689
|
-
raw = JSON.parse(
|
|
781
|
+
raw = JSON.parse(fs5.readFileSync(path8.join(dir, MANIFEST_FILE), "utf-8"));
|
|
690
782
|
} catch (err) {
|
|
691
783
|
const code = err.code;
|
|
692
784
|
return { id, error: code === "ENOENT" ? `${MANIFEST_FILE} missing` : `${MANIFEST_FILE} unreadable: ${err instanceof Error ? err.message : String(err)}` };
|
|
693
785
|
}
|
|
694
786
|
const parsed = parseManifest(raw, id, (entry) => {
|
|
695
787
|
try {
|
|
696
|
-
return
|
|
788
|
+
return fs5.statSync(path8.join(dir, entry)).isFile();
|
|
697
789
|
} catch {
|
|
698
790
|
return false;
|
|
699
791
|
}
|
|
@@ -702,7 +794,7 @@ function readExtensionDir(dir, id = path7.basename(dir)) {
|
|
|
702
794
|
return { id, error: parsed.error };
|
|
703
795
|
let installedAt = 0;
|
|
704
796
|
try {
|
|
705
|
-
installedAt = Math.round(
|
|
797
|
+
installedAt = Math.round(fs5.statSync(dir).mtimeMs);
|
|
706
798
|
} catch {
|
|
707
799
|
}
|
|
708
800
|
return { ...parsed, installedAt };
|
|
@@ -713,9 +805,9 @@ function getSnapshot() {
|
|
|
713
805
|
}
|
|
714
806
|
|
|
715
807
|
// ../server/dist/agents/skill-sources.js
|
|
716
|
-
import
|
|
717
|
-
import
|
|
718
|
-
import { homedir as
|
|
808
|
+
import fs6 from "node:fs";
|
|
809
|
+
import path9 from "node:path";
|
|
810
|
+
import { homedir as homedir4 } from "node:os";
|
|
719
811
|
|
|
720
812
|
// ../server/dist/routes/mtime-cache.js
|
|
721
813
|
function createMtimeCache() {
|
|
@@ -733,11 +825,11 @@ function createMtimeCache() {
|
|
|
733
825
|
}
|
|
734
826
|
|
|
735
827
|
// ../server/dist/agents/skill-sources.js
|
|
736
|
-
var GLOBAL_SKILLS_DIR =
|
|
828
|
+
var GLOBAL_SKILLS_DIR = path9.join(homedir4(), ".halo", "global", "skills");
|
|
737
829
|
var SKILL_ID_RE = /^[\w-]+$/;
|
|
738
830
|
function isFile(p) {
|
|
739
831
|
try {
|
|
740
|
-
return
|
|
832
|
+
return fs6.statSync(p).isFile();
|
|
741
833
|
} catch {
|
|
742
834
|
return false;
|
|
743
835
|
}
|
|
@@ -745,13 +837,13 @@ function isFile(p) {
|
|
|
745
837
|
function childDirs(parent) {
|
|
746
838
|
let names;
|
|
747
839
|
try {
|
|
748
|
-
names =
|
|
840
|
+
names = fs6.readdirSync(parent);
|
|
749
841
|
} catch {
|
|
750
842
|
return [];
|
|
751
843
|
}
|
|
752
844
|
return names.filter((n) => {
|
|
753
845
|
try {
|
|
754
|
-
return
|
|
846
|
+
return fs6.statSync(path9.join(parent, n)).isDirectory();
|
|
755
847
|
} catch {
|
|
756
848
|
return false;
|
|
757
849
|
}
|
|
@@ -766,7 +858,7 @@ function extensionSkillDirs() {
|
|
|
766
858
|
const root = extensionsRoot();
|
|
767
859
|
let extIds;
|
|
768
860
|
try {
|
|
769
|
-
extIds =
|
|
861
|
+
extIds = fs6.readdirSync(root);
|
|
770
862
|
} catch {
|
|
771
863
|
return [];
|
|
772
864
|
}
|
|
@@ -774,31 +866,31 @@ function extensionSkillDirs() {
|
|
|
774
866
|
const taken = /* @__PURE__ */ new Set();
|
|
775
867
|
let off;
|
|
776
868
|
for (const extId of extIds.filter(isExtensionId).sort()) {
|
|
777
|
-
const extDir =
|
|
778
|
-
const ids = childDirs(
|
|
869
|
+
const extDir = path9.join(root, extId);
|
|
870
|
+
const ids = childDirs(path9.join(extDir, "skills"));
|
|
779
871
|
if (ids.length === 0)
|
|
780
872
|
continue;
|
|
781
873
|
off ??= disabledExtensionIds();
|
|
782
874
|
if (off.has(extId))
|
|
783
875
|
continue;
|
|
784
|
-
const manifest =
|
|
876
|
+
const manifest = path9.join(extDir, MANIFEST_FILE);
|
|
785
877
|
let mtimeMs;
|
|
786
878
|
try {
|
|
787
|
-
mtimeMs =
|
|
879
|
+
mtimeMs = fs6.statSync(manifest).mtimeMs;
|
|
788
880
|
} catch {
|
|
789
881
|
continue;
|
|
790
882
|
}
|
|
791
883
|
let entry = _manifestEntry.get(manifest, mtimeMs);
|
|
792
884
|
if (entry === void 0) {
|
|
793
885
|
const ext = readExtensionDir(extDir);
|
|
794
|
-
entry = _manifestEntry.set(manifest, mtimeMs, "error" in ext ? null :
|
|
886
|
+
entry = _manifestEntry.set(manifest, mtimeMs, "error" in ext ? null : path9.join(extDir, ext.entry));
|
|
795
887
|
}
|
|
796
888
|
if (!entry || !isFile(entry))
|
|
797
889
|
continue;
|
|
798
890
|
const ignored = [];
|
|
799
891
|
for (const id of ids.sort()) {
|
|
800
|
-
const dir =
|
|
801
|
-
if (!
|
|
892
|
+
const dir = path9.join(extDir, "skills", id);
|
|
893
|
+
if (!fs6.existsSync(path9.join(dir, "SKILL.md")))
|
|
802
894
|
continue;
|
|
803
895
|
if (!isExtensionSkillId(extId, id) || taken.has(id)) {
|
|
804
896
|
ignored.push(id);
|
|
@@ -823,28 +915,28 @@ function listSkillDirs(workspaceRoot) {
|
|
|
823
915
|
seen.add(s.id);
|
|
824
916
|
};
|
|
825
917
|
if (workspaceRoot) {
|
|
826
|
-
const wsDir =
|
|
918
|
+
const wsDir = path9.join(workspaceRoot, ".halo", "skills");
|
|
827
919
|
for (const id of childDirs(wsDir))
|
|
828
|
-
add({ id, dir:
|
|
920
|
+
add({ id, dir: path9.join(wsDir, id), scope: "workspace" });
|
|
829
921
|
}
|
|
830
922
|
for (const id of childDirs(GLOBAL_SKILLS_DIR))
|
|
831
|
-
add({ id, dir:
|
|
923
|
+
add({ id, dir: path9.join(GLOBAL_SKILLS_DIR, id), scope: "global" });
|
|
832
924
|
for (const s of extensionSkillDirs())
|
|
833
925
|
add(s);
|
|
834
926
|
return out;
|
|
835
927
|
}
|
|
836
928
|
|
|
837
929
|
// ../server/dist/a2a/remotes.js
|
|
838
|
-
import
|
|
839
|
-
import
|
|
840
|
-
import
|
|
930
|
+
import fs7 from "node:fs";
|
|
931
|
+
import path10 from "node:path";
|
|
932
|
+
import YAML4 from "yaml";
|
|
841
933
|
function remotesFile() {
|
|
842
|
-
return
|
|
934
|
+
return path10.join(secretsDir(), "a2a-remotes.yaml");
|
|
843
935
|
}
|
|
844
936
|
function loadRemotes() {
|
|
845
937
|
let doc;
|
|
846
938
|
try {
|
|
847
|
-
doc =
|
|
939
|
+
doc = YAML4.parse(fs7.readFileSync(remotesFile(), "utf8")) ?? {};
|
|
848
940
|
} catch {
|
|
849
941
|
return {};
|
|
850
942
|
}
|
|
@@ -858,7 +950,7 @@ function loadRemotes() {
|
|
|
858
950
|
}
|
|
859
951
|
|
|
860
952
|
// ../server/dist/settings-schema.js
|
|
861
|
-
var GLOBAL_AGENTS_DIR =
|
|
953
|
+
var GLOBAL_AGENTS_DIR = path11.join(homedir5(), ".halo", "global", "agents");
|
|
862
954
|
var DEFAULT_A2A_URL_ALLOWLIST = "100.64.0.0/10,*.ts.net";
|
|
863
955
|
var DEFAULT_FILES_SUMMARY = DEFAULT_HIDDEN_FILES.filter((f) => !/-(wal|shm)$/.test(f)).join(", ");
|
|
864
956
|
function generalSection() {
|
|
@@ -869,9 +961,12 @@ function generalSection() {
|
|
|
869
961
|
displayName_zh: "\u5E38\u89C4",
|
|
870
962
|
description: "Server behavior parameters (session limits, compaction, sandbox, logging).",
|
|
871
963
|
description_zh: "\u670D\u52A1\u7AEF\u884C\u4E3A\u53C2\u6570\uFF08\u4F1A\u8BDD\u9650\u5236\u3001\u538B\u7F29\u3001\u6C99\u7BB1\u3001\u65E5\u5FD7\uFF09\u3002",
|
|
872
|
-
// Every general.* field is globalOnly
|
|
873
|
-
// settingsValue() from the global settings.yaml only
|
|
874
|
-
// layering
|
|
964
|
+
// Every general.* field is globalOnly — config.ts reads them via
|
|
965
|
+
// settingsValue() from the global settings.yaml only; per-workspace
|
|
966
|
+
// layering is for namespaced params/secrets (getServerSecret) — except
|
|
967
|
+
// evolution.level and evolution.auto_apply_threshold: how far evo may act
|
|
968
|
+
// on its own is a property of the workspace (an open one at L0, a private
|
|
969
|
+
// one at L2/L3), read per workspace by evoLevelFor / autoApplyThresholdFor.
|
|
875
970
|
fields: [
|
|
876
971
|
// system-wide language preference. Read by internal agents (evo, score)
|
|
877
972
|
// that don't load INSTRUCTIONS.md, and available to anything else that
|
|
@@ -917,11 +1012,18 @@ function generalSection() {
|
|
|
917
1012
|
// a workspace must not widen; public_url names this server to peers.
|
|
918
1013
|
{ key: "a2a.public_url", type: "string", globalOnly: true, description: "Origin peers use to reach this server for A2A (e.g. http://myhost.tailnet.ts.net:9527): the agent card's interface URL and the webhook URL given to remotes. Empty = derived from the request Host. Env HALO_A2A_PUBLIC_URL overrides.", description_zh: "\u5BF9\u7AEF\u8BBF\u95EE\u672C\u670D\u52A1\u7684 A2A \u6E90\u5730\u5740\uFF08\u5982 http://myhost.tailnet.ts.net:9527\uFF09\uFF1A\u7528\u4E8E agent card \u7684\u63A5\u53E3 URL \u548C\u4EA4\u7ED9\u8FDC\u7AEF\u7684 webhook URL\u3002\u7559\u7A7A\u5219\u6309\u8BF7\u6C42 Host \u63A8\u5BFC\u3002\u73AF\u5883\u53D8\u91CF HALO_A2A_PUBLIC_URL \u4F18\u5148\u3002", default: "" },
|
|
919
1014
|
{ key: "a2a.url_allowlist", type: "string", globalOnly: true, description: "Comma-separated CIDRs / host patterns A2A may reach besides public https (push webhooks, remote cards and RPC). Private / loopback ranges are refused unless listed; plain http is allowed only to listed hosts / ranges. Link-local (169.254.0.0/16, fe80::/10) is always refused.", description_zh: "A2A \u9664\u516C\u7F51 https \u4E4B\u5916\u5141\u8BB8\u8BBF\u95EE\u7684 CIDR / \u4E3B\u673A\u6A21\u5F0F\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u7528\u4E8E push webhook\u3001\u8FDC\u7AEF card \u548C RPC\uFF09\u3002\u79C1\u7F51 / \u56DE\u73AF\u5730\u5740\u5FC5\u987B\u5217\u51FA\u624D\u5141\u8BB8\uFF1B\u660E\u6587 http \u53EA\u5141\u8BB8\u5217\u51FA\u7684\u4E3B\u673A / \u7F51\u6BB5\u3002\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF08169.254.0.0/16\u3001fe80::/10\uFF09\u59CB\u7EC8\u62D2\u7EDD\u3002", default: DEFAULT_A2A_URL_ALLOWLIST },
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
|
|
923
|
-
{ key: "
|
|
924
|
-
{ key: "
|
|
1015
|
+
// Outbound proxy (outbound-proxy.ts). Global-only: a machine's way out,
|
|
1016
|
+
// not a workspace's. Read per spawn / request — no restart.
|
|
1017
|
+
{ key: "outbound_proxy.enabled", type: "boolean", globalOnly: true, description: "Route outbound traffic through the proxy below: shell_exec, newly opened Terminals, notebook kernels and evolution runs get http_proxy / https_proxy (and the upper-case forms) in their env, and web_fetch and A2A requests to remotes go through it. localhost / 127.0.0.1 / ::1 always connect directly. Off = Halo sets no proxy vars (any the server inherited still pass). Applies on the next command / request; a Terminal already open keeps its env.", description_zh: "\u8BA9\u51FA\u7AD9\u6D41\u91CF\u8D70\u4E0B\u9762\u7684\u4EE3\u7406\uFF1Ashell_exec\u3001\u65B0\u5F00\u7684 Terminal\u3001notebook \u5185\u6838\u548C evolution \u8FD0\u884C\u7684\u73AF\u5883\u53D8\u91CF\u91CC\u4F1A\u8BBE\u4E0A http_proxy / https_proxy\uFF08\u5927\u5199\u5F62\u5F0F\u4E5F\u4E00\u8D77\u8BBE\uFF09\uFF0Cweb_fetch \u548C\u53D1\u5F80 A2A \u8FDC\u7AEF\u7684\u8BF7\u6C42\u4E5F\u8D70\u4EE3\u7406\u3002localhost / 127.0.0.1 / ::1 \u59CB\u7EC8\u76F4\u8FDE\u3002\u5173\u95ED\u540E Halo \u4E0D\u8BBE\u4EFB\u4F55\u4EE3\u7406\u53D8\u91CF\uFF08\u670D\u52A1\u8FDB\u7A0B\u672C\u8EAB\u7EE7\u627F\u7684\u7167\u5E38\u4F20\u4E0B\u53BB\uFF09\u3002\u4E0B\u4E00\u6761\u547D\u4EE4 / \u8BF7\u6C42\u5373\u751F\u6548\uFF1B\u5DF2\u7ECF\u6253\u5F00\u7684 Terminal \u4FDD\u6301\u539F\u6765\u7684\u73AF\u5883\u3002", default: "false" },
|
|
1018
|
+
{ key: "outbound_proxy.url", type: "string", globalOnly: true, description: "Proxy address, http:// or https:// (e.g. http://127.0.0.1:12334 for a local Hiddify / Clash). No user:pass@ in the URL \u2014 put credentials in Token. Empty = proxy off even when enabled.", description_zh: "\u4EE3\u7406\u5730\u5740\uFF0Chttp:// \u6216 https://\uFF08\u5982\u672C\u5730 Hiddify / Clash \u7684 http://127.0.0.1:12334\uFF09\u3002URL \u91CC\u4E0D\u80FD\u5E26 user:pass@\uFF0C\u51ED\u8BC1\u586B\u5230 Token\u3002\u7559\u7A7A\u65F6\u5373\u4F7F\u6253\u5F00\u5F00\u5173\u4E5F\u4E0D\u8D70\u4EE3\u7406\u3002", default: "", placeholder: "http://127.0.0.1:12334" },
|
|
1019
|
+
{ key: "outbound_proxy.token", type: "string", secret: true, globalOnly: true, description: "Optional proxy credential: user:password, or a bare token (sent as the user name, empty password). Sent as Basic auth \u2014 in the proxy URL's user-info for child processes (the form curl / git / pip read), as Proxy-Authorization for web_fetch and A2A. Leave empty for a proxy without auth.", description_zh: "\u53EF\u9009\u7684\u4EE3\u7406\u51ED\u8BC1\uFF1Auser:password\uFF0C\u6216\u5355\u72EC\u4E00\u4E2A token\uFF08\u4F5C\u4E3A\u7528\u6237\u540D\uFF0C\u5BC6\u7801\u4E3A\u7A7A\uFF09\u3002\u4EE5 Basic \u8BA4\u8BC1\u53D1\u9001\u2014\u2014\u5B50\u8FDB\u7A0B\u653E\u5728\u4EE3\u7406 URL \u7684 user-info \u91CC\uFF08curl / git / pip \u90FD\u8BA4\u8FD9\u79CD\u5199\u6CD5\uFF09\uFF0Cweb_fetch \u548C A2A \u7528 Proxy-Authorization \u5934\u3002\u4EE3\u7406\u4E0D\u9700\u8981\u9274\u6743\u5C31\u7559\u7A7A\u3002", default: "" },
|
|
1020
|
+
// self-evolution (see plans/self-evolution.md). The level and the
|
|
1021
|
+
// auto-apply threshold are per workspace (read where a run's workspace is
|
|
1022
|
+
// known: the pre-compact trigger, the wrapper's auto-apply verdict and
|
|
1023
|
+
// recheck). The rest stays global-only: the ticker's slots, timeouts and
|
|
1024
|
+
// retries are server-wide, and pre-compact snapshots are a server switch.
|
|
1025
|
+
{ key: "evolution.level", type: "enum", options: ["L0", "L1", "L2", "L3"], description: "Self-evolution capability ladder. L0: manual only \u2014 drafts via /evo, manual approve/reject. L1: L0 plus automatic drafting on pre-compact. L2: L1 plus auto-applying prompt-only changes that score above evolution.auto_apply_threshold. L3: L2 plus auto-applying capability changes (agent.yaml tools / model, skill invocation). Auto-applied changes still pass the regression check and can be rolled back from the Evolution tab. Queued runs always execute regardless of level.", description_zh: "\u81EA\u6211\u8FDB\u5316\u7B49\u7EA7\u3002L0\uFF1A\u4EC5\u624B\u52A8\u2014\u2014\u901A\u8FC7 /evo \u8D77\u8349\uFF0C\u624B\u52A8 approve/reject\u3002L1\uFF1A\u5728 L0 \u57FA\u7840\u4E0A\uFF0C\u538B\u7F29\u524D\uFF08pre-compact\uFF09\u81EA\u52A8\u8D77\u8349\u3002L2\uFF1A\u5728 L1 \u57FA\u7840\u4E0A\uFF0C\u5BF9\u8BC4\u5206\u8D85\u8FC7 evolution.auto_apply_threshold \u7684\u7EAF\u63D0\u793A\u8BCD\u6539\u52A8\u81EA\u52A8\u5E94\u7528\u3002L3\uFF1A\u5728 L2 \u57FA\u7840\u4E0A\uFF0C\u5BF9\u80FD\u529B\u7C7B\u6539\u52A8\uFF08agent.yaml \u7684\u5DE5\u5177/\u6A21\u578B\u3001skill \u8C03\u7528\u65B9\u5F0F\uFF09\u4E5F\u81EA\u52A8\u5E94\u7528\u3002\u81EA\u52A8\u5E94\u7528\u7167\u6837\u8FC7\u56DE\u5F52\u68C0\u67E5\uFF0C\u53EF\u5728 Evolution \u9875\u56DE\u6EDA\u3002\u5DF2\u6392\u961F\u7684 run \u4E0D\u53D7\u7B49\u7EA7\u5F71\u54CD\uFF0C\u59CB\u7EC8\u6267\u884C\u3002", default: "L0" },
|
|
1026
|
+
{ key: "evolution.auto_apply_threshold", type: "int", min: 0, max: 100, description: "Minimum average score (0\u2013100) for auto-apply at L2/L3. Also required: confidence high, behavior \u2265 70, lint 100, all sandbox checks passed. Ignored at L0/L1.", description_zh: "L2/L3 \u81EA\u52A8\u5E94\u7528\u8981\u6C42\u7684\u6700\u4F4E\u5E73\u5747\u5206\uFF080\u2013100\uFF09\u3002\u53E6\u9700\u540C\u65F6\u6EE1\u8DB3\uFF1Aconfidence \u4E3A high\u3001behavior \u2265 70\u3001lint \u4E3A 100\u3001\u6C99\u7BB1\u68C0\u67E5\u5168\u90E8\u901A\u8FC7\u3002L0/L1 \u4E0B\u5FFD\u7565\u3002", default: "80" },
|
|
925
1027
|
{ key: "evolution.max_concurrent_run", type: "int", globalOnly: true, description: "Max concurrent evo run wrappers (drafting patches).", description_zh: "\u540C\u65F6\u8FD0\u884C\u7684 evo run wrapper \u4E0A\u9650\uFF08\u8D77\u8349 patch\uFF09\u3002", default: "1" },
|
|
926
1028
|
{ key: "evolution.max_concurrent_apply", type: "int", globalOnly: true, description: "Max concurrent evo apply wrappers (merging patches).", description_zh: "\u540C\u65F6\u8FD0\u884C\u7684 evo apply wrapper \u4E0A\u9650\uFF08\u5408\u5E76 patch\uFF09\u3002", default: "1" },
|
|
927
1029
|
{ key: "evolution.run_timeout_minutes", type: "int", globalOnly: true, description: "Heartbeat timeout for run wrappers. The wrapper writes a heartbeat every 60s for as long as it lives, so exceeding this means the wrapper process died \u2014 the row is requeued (or marked timeout past max_attempts). This is dead-wrapper detection latency, NOT a budget for slow models; the per-phase model budget is the wrapper-internal cap (draft 60min, other phases 45min).", description_zh: "run wrapper \u5FC3\u8DF3\u8D85\u65F6\uFF08\u5206\u949F\uFF09\u3002wrapper \u5B58\u6D3B\u671F\u95F4\u6BCF 60 \u79D2\u5199\u4E00\u6B21\u5FC3\u8DF3\uFF0C\u8D85\u65F6\u5373\u89C6\u4E3A wrapper \u8FDB\u7A0B\u5DF2\u6B7B\uFF0C\u4EFB\u52A1\u91CD\u6392\uFF08\u8D85\u8FC7 max_attempts \u5219\u6807 timeout\uFF09\u3002\u8FD9\u662F\u6B7B wrapper \u7684\u68C0\u6D4B\u5EF6\u8FDF\uFF0C\u4E0D\u662F\u6162\u6A21\u578B\u7684\u8FD0\u884C\u9884\u7B97\u2014\u2014\u5355\u9636\u6BB5\u6A21\u578B\u9884\u7B97\u662F wrapper \u5185\u90E8\u7684\u4E0A\u9650\uFF08\u8D77\u8349 60 \u5206\u949F\uFF0C\u5176\u4F59\u9636\u6BB5 45 \u5206\u949F\uFF09\u3002", default: "12" },
|
|
@@ -951,11 +1053,11 @@ var _yamlCache = createMtimeCache();
|
|
|
951
1053
|
var _skillNameCache = createMtimeCache();
|
|
952
1054
|
function readYamlFile(p) {
|
|
953
1055
|
try {
|
|
954
|
-
const { mtimeMs } =
|
|
1056
|
+
const { mtimeMs } = fs8.statSync(p);
|
|
955
1057
|
const hit = _yamlCache.get(p, mtimeMs);
|
|
956
1058
|
if (hit !== void 0)
|
|
957
1059
|
return hit;
|
|
958
|
-
const parsed =
|
|
1060
|
+
const parsed = YAML5.parse(fs8.readFileSync(p, "utf-8"));
|
|
959
1061
|
return _yamlCache.set(p, mtimeMs, parsed && typeof parsed === "object" ? parsed : null);
|
|
960
1062
|
} catch {
|
|
961
1063
|
return null;
|
|
@@ -1010,8 +1112,8 @@ function providerSections() {
|
|
|
1010
1112
|
return sections;
|
|
1011
1113
|
}
|
|
1012
1114
|
function buildSkillSection(skillsDir, skillId) {
|
|
1013
|
-
const configPath =
|
|
1014
|
-
if (!
|
|
1115
|
+
const configPath = path11.join(skillsDir, skillId, "config.yaml");
|
|
1116
|
+
if (!fs8.existsSync(configPath))
|
|
1015
1117
|
return null;
|
|
1016
1118
|
const parsed = readYamlFile(configPath);
|
|
1017
1119
|
if (!parsed)
|
|
@@ -1022,11 +1124,11 @@ function buildSkillSection(skillsDir, skillId) {
|
|
|
1022
1124
|
return null;
|
|
1023
1125
|
let displayName = skillId;
|
|
1024
1126
|
try {
|
|
1025
|
-
const mdPath =
|
|
1026
|
-
const { mtimeMs } =
|
|
1127
|
+
const mdPath = path11.join(skillsDir, skillId, "SKILL.md");
|
|
1128
|
+
const { mtimeMs } = fs8.statSync(mdPath);
|
|
1027
1129
|
let name = _skillNameCache.get(mdPath, mtimeMs);
|
|
1028
1130
|
if (name === void 0) {
|
|
1029
|
-
const m =
|
|
1131
|
+
const m = fs8.readFileSync(mdPath, "utf-8").match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1030
1132
|
const nameMatch = m?.[1].match(/^name:\s*(.+)$/m);
|
|
1031
1133
|
name = _skillNameCache.set(mdPath, mtimeMs, nameMatch ? nameMatch[1].trim() : null);
|
|
1032
1134
|
}
|
|
@@ -1047,7 +1149,7 @@ function buildSkillSection(skillsDir, skillId) {
|
|
|
1047
1149
|
function skillSections(workspaceRoot) {
|
|
1048
1150
|
const byId = /* @__PURE__ */ new Map();
|
|
1049
1151
|
for (const { id, dir } of listSkillDirs(workspaceRoot).reverse()) {
|
|
1050
|
-
const section = buildSkillSection(
|
|
1152
|
+
const section = buildSkillSection(path11.dirname(dir), id);
|
|
1051
1153
|
if (section)
|
|
1052
1154
|
byId.set(section.namespaceId, section);
|
|
1053
1155
|
}
|
|
@@ -1057,7 +1159,7 @@ function agentSections() {
|
|
|
1057
1159
|
const sections = [];
|
|
1058
1160
|
let entries = [];
|
|
1059
1161
|
try {
|
|
1060
|
-
entries =
|
|
1162
|
+
entries = fs8.readdirSync(GLOBAL_AGENTS_DIR, { withFileTypes: true });
|
|
1061
1163
|
} catch {
|
|
1062
1164
|
return sections;
|
|
1063
1165
|
}
|
|
@@ -1065,8 +1167,8 @@ function agentSections() {
|
|
|
1065
1167
|
if (!entry.isDirectory())
|
|
1066
1168
|
continue;
|
|
1067
1169
|
const agentId = entry.name;
|
|
1068
|
-
const configPath =
|
|
1069
|
-
if (!
|
|
1170
|
+
const configPath = path11.join(GLOBAL_AGENTS_DIR, agentId, "agent-config.yaml");
|
|
1171
|
+
if (!fs8.existsSync(configPath))
|
|
1070
1172
|
continue;
|
|
1071
1173
|
const parsed = readYamlFile(configPath);
|
|
1072
1174
|
if (!parsed)
|
|
@@ -1077,7 +1179,7 @@ function agentSections() {
|
|
|
1077
1179
|
continue;
|
|
1078
1180
|
let displayName = agentId;
|
|
1079
1181
|
try {
|
|
1080
|
-
const ay = readYamlFile(
|
|
1182
|
+
const ay = readYamlFile(path11.join(GLOBAL_AGENTS_DIR, agentId, "agent.yaml"));
|
|
1081
1183
|
if (ay && typeof ay.name === "string")
|
|
1082
1184
|
displayName = ay.name;
|
|
1083
1185
|
} catch {
|
|
@@ -1140,23 +1242,15 @@ function extensionSections() {
|
|
|
1140
1242
|
}
|
|
1141
1243
|
|
|
1142
1244
|
// ../server/dist/config.js
|
|
1143
|
-
var HALO_HOME2 =
|
|
1144
|
-
var HALO_GLOBAL_DIR =
|
|
1145
|
-
var HALO_SECRETS_DIR =
|
|
1146
|
-
|
|
1147
|
-
try {
|
|
1148
|
-
const raw = fs8.readFileSync(filePath, "utf-8");
|
|
1149
|
-
return YAML5.parse(raw) ?? {};
|
|
1150
|
-
} catch {
|
|
1151
|
-
return {};
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
var SYSTEM_CONFIG_PATH = path11.join(HALO_SECRETS_DIR, "config.yaml");
|
|
1245
|
+
var HALO_HOME2 = path12.join(homedir6(), ".halo");
|
|
1246
|
+
var HALO_GLOBAL_DIR = path12.join(HALO_HOME2, "global");
|
|
1247
|
+
var HALO_SECRETS_DIR = path12.join(HALO_HOME2, "secrets");
|
|
1248
|
+
var SYSTEM_CONFIG_PATH = path12.join(HALO_SECRETS_DIR, "config.yaml");
|
|
1155
1249
|
var _systemConfigCache = null;
|
|
1156
1250
|
function getSystemConfig() {
|
|
1157
1251
|
let mtimeMs = 0;
|
|
1158
1252
|
try {
|
|
1159
|
-
mtimeMs =
|
|
1253
|
+
mtimeMs = fs9.statSync(SYSTEM_CONFIG_PATH).mtimeMs;
|
|
1160
1254
|
} catch {
|
|
1161
1255
|
}
|
|
1162
1256
|
if (!_systemConfigCache || _systemConfigCache.mtimeMs !== mtimeMs) {
|
|
@@ -1164,19 +1258,6 @@ function getSystemConfig() {
|
|
|
1164
1258
|
}
|
|
1165
1259
|
return _systemConfigCache.data;
|
|
1166
1260
|
}
|
|
1167
|
-
var SETTINGS_PATH = path11.join(HALO_SECRETS_DIR, "settings.yaml");
|
|
1168
|
-
var _settingsCache = null;
|
|
1169
|
-
function getSettings() {
|
|
1170
|
-
let mtimeMs = 0;
|
|
1171
|
-
try {
|
|
1172
|
-
mtimeMs = fs8.statSync(SETTINGS_PATH).mtimeMs;
|
|
1173
|
-
} catch {
|
|
1174
|
-
}
|
|
1175
|
-
if (!_settingsCache || _settingsCache.mtimeMs !== mtimeMs) {
|
|
1176
|
-
_settingsCache = { mtimeMs, data: loadYamlFile(SETTINGS_PATH) };
|
|
1177
|
-
}
|
|
1178
|
-
return _settingsCache.data;
|
|
1179
|
-
}
|
|
1180
1261
|
function readYamlValue(tree, dotPath) {
|
|
1181
1262
|
const parts = dotPath.split(".");
|
|
1182
1263
|
let cur = tree;
|
|
@@ -1303,6 +1384,21 @@ function systemString(envKey, configPath, fallback) {
|
|
|
1303
1384
|
return cv;
|
|
1304
1385
|
return fallback;
|
|
1305
1386
|
}
|
|
1387
|
+
var _wsSettingsCache = /* @__PURE__ */ new Map();
|
|
1388
|
+
function getWorkspaceSettings(workspaceRoot) {
|
|
1389
|
+
const p = path12.join(workspaceRoot, ".halo", "settings.yaml");
|
|
1390
|
+
let mtimeMs = 0;
|
|
1391
|
+
try {
|
|
1392
|
+
mtimeMs = fs9.statSync(p).mtimeMs;
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
const hit = _wsSettingsCache.get(workspaceRoot);
|
|
1396
|
+
if (hit && hit.mtimeMs === mtimeMs)
|
|
1397
|
+
return hit.data;
|
|
1398
|
+
const entry = { mtimeMs, data: loadYamlFile(p) };
|
|
1399
|
+
_wsSettingsCache.set(workspaceRoot, entry);
|
|
1400
|
+
return entry.data;
|
|
1401
|
+
}
|
|
1306
1402
|
var EVO_LEVELS = ["L0", "L1", "L2", "L3"];
|
|
1307
1403
|
function parseEvoLevel(raw) {
|
|
1308
1404
|
return EVO_LEVELS.includes(raw) ? raw : "L0";
|
|
@@ -1310,6 +1406,19 @@ function parseEvoLevel(raw) {
|
|
|
1310
1406
|
function evoLevelRank(level) {
|
|
1311
1407
|
return EVO_LEVELS.indexOf(level);
|
|
1312
1408
|
}
|
|
1409
|
+
function generalValueFor(workspaceRoot, dotPath) {
|
|
1410
|
+
const ws = readYamlValue(getWorkspaceSettings(workspaceRoot), dotPath);
|
|
1411
|
+
if (ws != null && String(ws) !== "")
|
|
1412
|
+
return String(ws);
|
|
1413
|
+
return settingsValue(dotPath);
|
|
1414
|
+
}
|
|
1415
|
+
function evoLevelFor(workspaceRoot) {
|
|
1416
|
+
return parseEvoLevel(generalValueFor(workspaceRoot, "general.evolution.level") ?? schemaDefault("general.evolution.level", "L0"));
|
|
1417
|
+
}
|
|
1418
|
+
function autoApplyThresholdFor(workspaceRoot) {
|
|
1419
|
+
const n = parseInt(generalValueFor(workspaceRoot, "general.evolution.auto_apply_threshold") ?? "", 10);
|
|
1420
|
+
return isNaN(n) ? settingsInt("general.evolution.auto_apply_threshold", 80) : n;
|
|
1421
|
+
}
|
|
1313
1422
|
var config = {
|
|
1314
1423
|
// Goal entry points are retired, not the runtime. Internal/global-only, no UI:
|
|
1315
1424
|
// set general.goal_mode_enabled in ~/.halo/secrets/settings.yaml, then restart.
|
|
@@ -1487,7 +1596,7 @@ var config = {
|
|
|
1487
1596
|
a2a: {
|
|
1488
1597
|
/** Base dir workspaces are exposed relative to: env HALO_A2A_ROOT, else the user's home. */
|
|
1489
1598
|
get root() {
|
|
1490
|
-
return process.env.HALO_A2A_ROOT?.trim() ||
|
|
1599
|
+
return process.env.HALO_A2A_ROOT?.trim() || homedir6();
|
|
1491
1600
|
},
|
|
1492
1601
|
/** Server origin as peers see it (no trailing slash); '' = derive from the request. */
|
|
1493
1602
|
get publicUrl() {
|
|
@@ -1498,16 +1607,10 @@ var config = {
|
|
|
1498
1607
|
}
|
|
1499
1608
|
},
|
|
1500
1609
|
// Self-evolution (see plans/self-evolution.md). Getter-based so user edits
|
|
1501
|
-
// in settings.yaml take effect on the next ticker run without restart.
|
|
1610
|
+
// in settings.yaml take effect on the next ticker run without restart. The
|
|
1611
|
+
// level and auto-apply threshold are per workspace: evoLevelFor /
|
|
1612
|
+
// autoApplyThresholdFor.
|
|
1502
1613
|
evolution: {
|
|
1503
|
-
get level() {
|
|
1504
|
-
return parseEvoLevel(settingsStr("general.evolution.level", "L0"));
|
|
1505
|
-
},
|
|
1506
|
-
// Minimum average score for L2/L3 auto-apply (see evolution/auto-apply.ts
|
|
1507
|
-
// for the extra fixed floors).
|
|
1508
|
-
get autoApplyThreshold() {
|
|
1509
|
-
return settingsInt("general.evolution.auto_apply_threshold", 80);
|
|
1510
|
-
},
|
|
1511
1614
|
get maxConcurrentRun() {
|
|
1512
1615
|
return settingsInt("general.evolution.max_concurrent_run", 1);
|
|
1513
1616
|
},
|
|
@@ -1538,21 +1641,21 @@ var config = {
|
|
|
1538
1641
|
};
|
|
1539
1642
|
|
|
1540
1643
|
// ../server/dist/evolution/auto-apply.js
|
|
1541
|
-
import
|
|
1542
|
-
import
|
|
1644
|
+
import fs10 from "node:fs";
|
|
1645
|
+
import path13 from "node:path";
|
|
1543
1646
|
import YAML6 from "yaml";
|
|
1544
1647
|
var AUTO_APPLY_FILE = "auto-apply.json";
|
|
1545
1648
|
var OVERRIDE_DIRS = /* @__PURE__ */ new Set(["agents", "skills", "prompts"]);
|
|
1546
1649
|
function isDir(p) {
|
|
1547
1650
|
try {
|
|
1548
|
-
return
|
|
1651
|
+
return fs10.statSync(p).isDirectory();
|
|
1549
1652
|
} catch {
|
|
1550
1653
|
return false;
|
|
1551
1654
|
}
|
|
1552
1655
|
}
|
|
1553
1656
|
function readOrNull(p) {
|
|
1554
1657
|
try {
|
|
1555
|
-
return
|
|
1658
|
+
return fs10.readFileSync(p);
|
|
1556
1659
|
} catch {
|
|
1557
1660
|
return null;
|
|
1558
1661
|
}
|
|
@@ -1560,33 +1663,33 @@ function readOrNull(p) {
|
|
|
1560
1663
|
function walkFiles(root, rel, out = []) {
|
|
1561
1664
|
let st;
|
|
1562
1665
|
try {
|
|
1563
|
-
st =
|
|
1666
|
+
st = fs10.statSync(path13.join(root, rel));
|
|
1564
1667
|
} catch {
|
|
1565
1668
|
return out;
|
|
1566
1669
|
}
|
|
1567
1670
|
if (st.isFile())
|
|
1568
1671
|
out.push(rel);
|
|
1569
1672
|
else if (st.isDirectory()) {
|
|
1570
|
-
for (const e of
|
|
1571
|
-
walkFiles(root,
|
|
1673
|
+
for (const e of fs10.readdirSync(path13.join(root, rel)))
|
|
1674
|
+
walkFiles(root, path13.join(rel, e), out);
|
|
1572
1675
|
}
|
|
1573
1676
|
return out;
|
|
1574
1677
|
}
|
|
1575
|
-
var toPosix = (rel) => rel.split(
|
|
1678
|
+
var toPosix = (rel) => rel.split(path13.sep).join("/");
|
|
1576
1679
|
function isInternalAgentDir(dir) {
|
|
1577
|
-
const raw = readOrNull(
|
|
1680
|
+
const raw = readOrNull(path13.join(dir, "agent.yaml"));
|
|
1578
1681
|
return raw !== null && parseYamlLoose(raw.toString("utf-8"))?.internal === true;
|
|
1579
1682
|
}
|
|
1580
1683
|
function liveBaseline(dirs, rel) {
|
|
1581
|
-
const parts = rel.split(
|
|
1684
|
+
const parts = rel.split(path13.sep);
|
|
1582
1685
|
if (!OVERRIDE_DIRS.has(parts[0]) || parts.length < 3)
|
|
1583
|
-
return { file:
|
|
1584
|
-
const dirRel =
|
|
1585
|
-
const internal = (root) => parts[0] === "agents" && isInternalAgentDir(
|
|
1586
|
-
if (isDir(
|
|
1587
|
-
return { file:
|
|
1588
|
-
if (isDir(
|
|
1589
|
-
return { file:
|
|
1686
|
+
return { file: path13.join(dirs.mainHalo, rel), newDir: false };
|
|
1687
|
+
const dirRel = path13.join(parts[0], parts[1]);
|
|
1688
|
+
const internal = (root) => parts[0] === "agents" && isInternalAgentDir(path13.join(root, dirRel));
|
|
1689
|
+
if (isDir(path13.join(dirs.mainHalo, dirRel)))
|
|
1690
|
+
return { file: path13.join(dirs.mainHalo, rel), newDir: false, internalAgent: internal(dirs.mainHalo) };
|
|
1691
|
+
if (isDir(path13.join(dirs.globalHalo, dirRel)))
|
|
1692
|
+
return { file: path13.join(dirs.globalHalo, rel), newDir: false, globalDir: dirRel, internalAgent: internal(dirs.globalHalo) };
|
|
1590
1693
|
return { file: null, newDir: true };
|
|
1591
1694
|
}
|
|
1592
1695
|
function fileChange(rel, base, after) {
|
|
@@ -1608,15 +1711,15 @@ function publishChanges(dirs, files) {
|
|
|
1608
1711
|
const base = liveBaseline(dirs, rel);
|
|
1609
1712
|
if (base.globalDir)
|
|
1610
1713
|
globalBacked.add(base.globalDir);
|
|
1611
|
-
const c = fileChange(rel, base,
|
|
1714
|
+
const c = fileChange(rel, base, fs10.readFileSync(full));
|
|
1612
1715
|
if (c)
|
|
1613
1716
|
out.push(c);
|
|
1614
1717
|
}
|
|
1615
1718
|
for (const dirRel of globalBacked) {
|
|
1616
1719
|
for (const rel of walkFiles(dirs.globalHalo, dirRel)) {
|
|
1617
|
-
if (
|
|
1720
|
+
if (fs10.existsSync(path13.join(dirs.sandboxHalo, rel)))
|
|
1618
1721
|
continue;
|
|
1619
|
-
out.push({ rel: toPosix(rel), before:
|
|
1722
|
+
out.push({ rel: toPosix(rel), before: fs10.readFileSync(path13.join(dirs.globalHalo, rel), "utf-8"), after: null, newDir: false });
|
|
1620
1723
|
}
|
|
1621
1724
|
}
|
|
1622
1725
|
return out;
|
|
@@ -1631,21 +1734,21 @@ function collectChanges(dirs) {
|
|
|
1631
1734
|
const base = liveBaseline(dirs, rel);
|
|
1632
1735
|
if (base.globalDir)
|
|
1633
1736
|
globalBacked.add(base.globalDir);
|
|
1634
|
-
const c = fileChange(rel, base,
|
|
1737
|
+
const c = fileChange(rel, base, fs10.readFileSync(path13.join(dirs.sandboxHalo, rel)));
|
|
1635
1738
|
if (c)
|
|
1636
1739
|
out.push(c);
|
|
1637
1740
|
}
|
|
1638
1741
|
for (const rel of walkFiles(dirs.mainHalo, top)) {
|
|
1639
1742
|
if (seen.has(rel))
|
|
1640
1743
|
continue;
|
|
1641
|
-
out.push({ rel: toPosix(rel), before:
|
|
1744
|
+
out.push({ rel: toPosix(rel), before: fs10.readFileSync(path13.join(dirs.mainHalo, rel), "utf-8"), after: null, newDir: false });
|
|
1642
1745
|
}
|
|
1643
1746
|
}
|
|
1644
1747
|
for (const dirRel of globalBacked) {
|
|
1645
1748
|
for (const rel of walkFiles(dirs.globalHalo, dirRel)) {
|
|
1646
1749
|
if (seen.has(rel))
|
|
1647
1750
|
continue;
|
|
1648
|
-
out.push({ rel: toPosix(rel), before:
|
|
1751
|
+
out.push({ rel: toPosix(rel), before: fs10.readFileSync(path13.join(dirs.globalHalo, rel), "utf-8"), after: null, newDir: false });
|
|
1649
1752
|
}
|
|
1650
1753
|
}
|
|
1651
1754
|
return out;
|
|
@@ -1857,7 +1960,7 @@ function parseScore(raw) {
|
|
|
1857
1960
|
}
|
|
1858
1961
|
function readScore(file) {
|
|
1859
1962
|
try {
|
|
1860
|
-
return parseScore(JSON.parse(
|
|
1963
|
+
return parseScore(JSON.parse(fs10.readFileSync(file, "utf-8")));
|
|
1861
1964
|
} catch {
|
|
1862
1965
|
return null;
|
|
1863
1966
|
}
|
|
@@ -1877,11 +1980,8 @@ function categoryGate(level, cls) {
|
|
|
1877
1980
|
return null;
|
|
1878
1981
|
}
|
|
1879
1982
|
function sourceGate(sourceAccess) {
|
|
1880
|
-
if (sourceAccess === SOURCE_WEBHOOK)
|
|
1881
|
-
return "source_webhook";
|
|
1882
1983
|
return sourceAccess === "full" ? null : "source_access";
|
|
1883
1984
|
}
|
|
1884
|
-
var SOURCE_WEBHOOK = "webhook";
|
|
1885
1985
|
function scoreGate(score, threshold) {
|
|
1886
1986
|
if (!score)
|
|
1887
1987
|
return "score_missing";
|
|
@@ -1939,7 +2039,7 @@ function extractLargerObservation(md) {
|
|
|
1939
2039
|
}
|
|
1940
2040
|
function readObservation(file) {
|
|
1941
2041
|
try {
|
|
1942
|
-
return extractLargerObservation(
|
|
2042
|
+
return extractLargerObservation(fs10.readFileSync(file, "utf-8"));
|
|
1943
2043
|
} catch {
|
|
1944
2044
|
return null;
|
|
1945
2045
|
}
|
|
@@ -2001,11 +2101,11 @@ function failApply(db, applyId, input) {
|
|
|
2001
2101
|
}
|
|
2002
2102
|
|
|
2003
2103
|
// ../server/dist/evolution/rollback.js
|
|
2004
|
-
import
|
|
2104
|
+
import fs11 from "node:fs";
|
|
2005
2105
|
import { createHash } from "node:crypto";
|
|
2006
2106
|
import { and as and2, eq as eq2, inArray } from "drizzle-orm";
|
|
2007
2107
|
function sha256File(file) {
|
|
2008
|
-
return createHash("sha256").update(
|
|
2108
|
+
return createHash("sha256").update(fs11.readFileSync(file)).digest("hex");
|
|
2009
2109
|
}
|
|
2010
2110
|
|
|
2011
2111
|
// ../server/dist/evolution/evo-wrapper.js
|
|
@@ -2067,9 +2167,9 @@ function spawnProc(bin, args, logFd, teePath, stdinInput, timeoutSec) {
|
|
|
2067
2167
|
}
|
|
2068
2168
|
writeLog(logFd, `[wrapper] $ ${bin} ${loggedArgs.join(" ")}${stdinInput === void 0 ? " <last-arg-omitted>" : " <prompt-on-stdin>"}${timeoutSec ? ` (timeout ${timeoutSec}s)` : ""}
|
|
2069
2169
|
`);
|
|
2070
|
-
const tee = teePath ?
|
|
2170
|
+
const tee = teePath ? fs12.createWriteStream(teePath, { flags: "a" }) : null;
|
|
2071
2171
|
const [spawnBin, spawnArgs] = process.platform === "win32" && bin.endsWith(".cmd") ? ["cmd.exe", ["/c", bin, ...args]] : [bin, args];
|
|
2072
|
-
return new Promise((
|
|
2172
|
+
return new Promise((resolve2) => {
|
|
2073
2173
|
const child = spawn2(spawnBin, spawnArgs, {
|
|
2074
2174
|
// Pipe stdin only when we have input to write — the cli reads a prompt
|
|
2075
2175
|
// from stdin when it's not a TTY, which is how long briefs are delivered
|
|
@@ -2141,7 +2241,7 @@ function spawnProc(bin, args, logFd, teePath, stdinInput, timeoutSec) {
|
|
|
2141
2241
|
tee?.end();
|
|
2142
2242
|
if (stopping)
|
|
2143
2243
|
return;
|
|
2144
|
-
|
|
2244
|
+
resolve2({ exitCode, stdout, stderr });
|
|
2145
2245
|
});
|
|
2146
2246
|
child.on("error", (err) => {
|
|
2147
2247
|
liveProcs.delete(killTree);
|
|
@@ -2157,17 +2257,17 @@ function spawnProc(bin, args, logFd, teePath, stdinInput, timeoutSec) {
|
|
|
2157
2257
|
tee?.end();
|
|
2158
2258
|
if (stopping)
|
|
2159
2259
|
return;
|
|
2160
|
-
|
|
2260
|
+
resolve2({ exitCode: 1, stdout, stderr: stderr + `
|
|
2161
2261
|
[spawn error] ${err.message}` });
|
|
2162
2262
|
});
|
|
2163
2263
|
});
|
|
2164
2264
|
}
|
|
2165
2265
|
function subCliLogPath(runDir) {
|
|
2166
|
-
return
|
|
2266
|
+
return path14.join(runDir, "sub-cli.log");
|
|
2167
2267
|
}
|
|
2168
2268
|
function appendSubCliHeader(runDir, header) {
|
|
2169
2269
|
try {
|
|
2170
|
-
|
|
2270
|
+
fs12.appendFileSync(subCliLogPath(runDir), `
|
|
2171
2271
|
=== ${(/* @__PURE__ */ new Date()).toISOString()} ${header} ===
|
|
2172
2272
|
`);
|
|
2173
2273
|
} catch {
|
|
@@ -2201,10 +2301,10 @@ function buildLanguageClause(langHint, scope) {
|
|
|
2201
2301
|
];
|
|
2202
2302
|
}
|
|
2203
2303
|
function readPatchFrontmatter(runDir) {
|
|
2204
|
-
const patchPath =
|
|
2304
|
+
const patchPath = path14.join(runDir, "patch.md");
|
|
2205
2305
|
let raw;
|
|
2206
2306
|
try {
|
|
2207
|
-
raw =
|
|
2307
|
+
raw = fs12.readFileSync(patchPath, "utf-8");
|
|
2208
2308
|
} catch {
|
|
2209
2309
|
return null;
|
|
2210
2310
|
}
|
|
@@ -2234,14 +2334,14 @@ function extractTestScenario(fm) {
|
|
|
2234
2334
|
return { agentId: o.agentId, testMessage, originalMessage };
|
|
2235
2335
|
}
|
|
2236
2336
|
function readEvoContext(runDir, logFd) {
|
|
2237
|
-
const p =
|
|
2238
|
-
if (!
|
|
2337
|
+
const p = path14.join(runDir, "evo-context.json");
|
|
2338
|
+
if (!fs12.existsSync(p)) {
|
|
2239
2339
|
writeLog(logFd, `[brief] evo-context.json missing at ${p}
|
|
2240
2340
|
`);
|
|
2241
2341
|
return null;
|
|
2242
2342
|
}
|
|
2243
2343
|
try {
|
|
2244
|
-
return JSON.parse(
|
|
2344
|
+
return JSON.parse(fs12.readFileSync(p, "utf-8"));
|
|
2245
2345
|
} catch (err) {
|
|
2246
2346
|
writeLog(logFd, `[brief] evo-context.json parse failed: ${err instanceof Error ? err.message : String(err)}
|
|
2247
2347
|
`);
|
|
@@ -2249,12 +2349,12 @@ function readEvoContext(runDir, logFd) {
|
|
|
2249
2349
|
}
|
|
2250
2350
|
}
|
|
2251
2351
|
function dumpSnapshotImages(runDir, logFd) {
|
|
2252
|
-
const snapshotPath =
|
|
2253
|
-
if (!
|
|
2352
|
+
const snapshotPath = path14.join(runDir, "source-snapshot.json");
|
|
2353
|
+
if (!fs12.existsSync(snapshotPath))
|
|
2254
2354
|
return [];
|
|
2255
2355
|
let parsed;
|
|
2256
2356
|
try {
|
|
2257
|
-
parsed = JSON.parse(
|
|
2357
|
+
parsed = JSON.parse(fs12.readFileSync(snapshotPath, "utf-8"));
|
|
2258
2358
|
} catch {
|
|
2259
2359
|
writeLog(logFd, `[dumpSnapshotImages] snapshot parse failed
|
|
2260
2360
|
`);
|
|
@@ -2263,8 +2363,8 @@ function dumpSnapshotImages(runDir, logFd) {
|
|
|
2263
2363
|
const messages = Array.isArray(parsed.rawMessages) ? parsed.rawMessages : [];
|
|
2264
2364
|
if (messages.length === 0)
|
|
2265
2365
|
return [];
|
|
2266
|
-
const imagesDir =
|
|
2267
|
-
|
|
2366
|
+
const imagesDir = path14.join(runDir, "images");
|
|
2367
|
+
fs12.mkdirSync(imagesDir, { recursive: true });
|
|
2268
2368
|
const out = [];
|
|
2269
2369
|
const extByMime = {
|
|
2270
2370
|
"image/png": "png",
|
|
@@ -2303,10 +2403,10 @@ function dumpSnapshotImages(runDir, logFd) {
|
|
|
2303
2403
|
continue;
|
|
2304
2404
|
const ext = extByMime[mediaType] ?? "bin";
|
|
2305
2405
|
const fileName = c.subBlockIdx == null ? `${m}-${b}.${ext}` : `${m}-${b}-${c.subBlockIdx}.${ext}`;
|
|
2306
|
-
const fullPath =
|
|
2406
|
+
const fullPath = path14.join(imagesDir, fileName);
|
|
2307
2407
|
try {
|
|
2308
2408
|
const buf = Buffer.from(data, "base64");
|
|
2309
|
-
|
|
2409
|
+
fs12.writeFileSync(fullPath, buf);
|
|
2310
2410
|
out.push({
|
|
2311
2411
|
relPath: `images/${fileName}`,
|
|
2312
2412
|
msgIdx: m,
|
|
@@ -2393,15 +2493,15 @@ function renderEvoContextSections(ctx) {
|
|
|
2393
2493
|
lines.push("");
|
|
2394
2494
|
}
|
|
2395
2495
|
if (ctx.skills.length > 0) {
|
|
2396
|
-
const globalSkillsDir =
|
|
2496
|
+
const globalSkillsDir = path14.join(homedir7(), ".halo", "global", "skills");
|
|
2397
2497
|
lines.push("=== Skills in this workspace ===");
|
|
2398
2498
|
for (const s of ctx.skills)
|
|
2399
2499
|
lines.push(` ${s.id} [${s.scope}]`);
|
|
2400
2500
|
lines.push("");
|
|
2401
2501
|
lines.push("To read a skill's content, file_read its SKILL.md (you have full");
|
|
2402
2502
|
lines.push("file access \u2014 absolute paths resolve):");
|
|
2403
|
-
lines.push(` [workspace] \u2192 ${
|
|
2404
|
-
lines.push(` [global] / [builtin] \u2192 ${
|
|
2503
|
+
lines.push(` [workspace] \u2192 ${path14.join("<workspace>", ".halo", "skills", "<id>", "SKILL.md")}`);
|
|
2504
|
+
lines.push(` [global] / [builtin] \u2192 ${path14.join(globalSkillsDir, "<id>", "SKILL.md")}`);
|
|
2405
2505
|
lines.push("A [global]/[builtin] skill is NOT inside your sandbox \u2014 read it");
|
|
2406
2506
|
lines.push(`from ${globalSkillsDir} directly; don't glob the sandbox for it.`);
|
|
2407
2507
|
lines.push("Sibling resource files live next to SKILL.md in that same dir.");
|
|
@@ -2424,13 +2524,13 @@ function buildDraftBrief(args) {
|
|
|
2424
2524
|
];
|
|
2425
2525
|
if (args.userHint)
|
|
2426
2526
|
lines.push(`Reviewer hint: ${args.userHint}`);
|
|
2427
|
-
lines.push("", "The triggering agent's full message log lives on disk:", ` ${
|
|
2527
|
+
lines.push("", "The triggering agent's full message log lives on disk:", ` ${path14.join(args.runDir, "tool-flow.md")} \u2014 clipped, fast skim`, ` ${path14.join(args.runDir, "source-snapshot.json")} \u2014 full rawMessages`, "", "Read tool-flow.md first to understand what the user wanted, what the", "agent did, and where it diverged. Each tool_result is clipped to a", "~200-char peek (with the `is_error` flag when the tool failed) \u2014", "enough to see the *shape* of the flow without re-tokenizing large", "grep / shell outputs. Fall back to source-snapshot.json only for", "messages whose full tool result actually matters to your patch.", "", ...renderEvoContextSections(args.evoContext), ...renderImageManifest(args.images), "=== Non-text media note ===", "", "Inline images in source-snapshot.json are base64 \u2014 you can't read them", "directly. The `images/` dump", "(see manifest above) decodes them to disk so you can `view_image` if", "a specific image is load-bearing. Video, audio, and binary documents", "(PDF, etc.) are even more limited: at trigger time the source agent", "worked with them via shell tooling (ffmpeg, pdftotext, etc.), and your", "view is limited to the textual results those tools produced (visible", "inside source-snapshot.json as tool_result text).", "", "When the patch you have in mind requires understanding what's INSIDE", "the media file (a video's visual content, an audio's acoustic", "content, a PDF's layout), the dry-run test scenario can't reliably", "verify it \u2014 skipping the run is the right call. When the patch is", "about the shell-tooling pattern itself (how to call ffmpeg, how to", "grep its output, how to handle credential errors), it can be", "verified at the textual level and the run proceeds normally.", "", "=== Task ===", "", "Decide what change in the prompt surface above would help the next time", "a similar conversation comes up. Three shapes, in order of preference:", "rewrite an existing rule; tighten / reorganize what's there; or, only", "when neither applies, add a new rule.", "", `Sandbox path: ${evoSandboxDir(args.runDir)}`, "", `When you have a change worth making, write ${path14.join(args.runDir, "patch.md")}`, "(frontmatter + body) and one new file at", `${evoSandboxHaloDir(args.runDir)}/<target> with the`, "full new contents. AGENT.md describes the patch.md schema.", "", "When the conversation has no signal worth a patch (rule already covers,", "too short to learn from, agent did fine, depends on media content", `you can't see), write ${path14.join(args.runDir, ".skip.md")} with a`, "one or two sentence reason instead, and skip the rest.", "", ...buildLanguageClause(args.langHint, "all natural-language content (patch.md body, prose drafted into sandbox files, .skip.md body)"));
|
|
2428
2528
|
return lines.join("\n");
|
|
2429
2529
|
}
|
|
2430
2530
|
function buildFixBrief(args) {
|
|
2431
2531
|
let failLogText = "";
|
|
2432
2532
|
try {
|
|
2433
|
-
failLogText =
|
|
2533
|
+
failLogText = fs12.readFileSync(args.failLogPath, "utf-8");
|
|
2434
2534
|
} catch {
|
|
2435
2535
|
failLogText = `(failed to read ${args.failLogPath})`;
|
|
2436
2536
|
}
|
|
@@ -2445,7 +2545,7 @@ function buildFixBrief(args) {
|
|
|
2445
2545
|
`Fix attempt: ${args.attempt}/${args.maxAttempts}`,
|
|
2446
2546
|
"",
|
|
2447
2547
|
`An earlier draft pass produced patch.md + a sandbox at`,
|
|
2448
|
-
`${
|
|
2548
|
+
`${path14.join(args.runDir, "patch.md")} and`,
|
|
2449
2549
|
`${evoSandboxHaloDir(args.runDir)} respectively.`,
|
|
2450
2550
|
"The dry-run failed; the wrapper's failure log:",
|
|
2451
2551
|
"",
|
|
@@ -2466,16 +2566,16 @@ function buildFixBrief(args) {
|
|
|
2466
2566
|
function buildScoreBrief(args) {
|
|
2467
2567
|
function readOrEmpty(p, label) {
|
|
2468
2568
|
try {
|
|
2469
|
-
return
|
|
2569
|
+
return fs12.readFileSync(p, "utf-8");
|
|
2470
2570
|
} catch (err) {
|
|
2471
2571
|
writeLog(args.logFd, `[buildScoreBrief] ${label} read failed: ${err instanceof Error ? err.message : String(err)}
|
|
2472
2572
|
`);
|
|
2473
2573
|
return `(${label} unavailable)`;
|
|
2474
2574
|
}
|
|
2475
2575
|
}
|
|
2476
|
-
const patchMd = readOrEmpty(
|
|
2477
|
-
const dryRun = readOrEmpty(
|
|
2478
|
-
const meta = readOrEmpty(
|
|
2576
|
+
const patchMd = readOrEmpty(path14.join(args.runDir, "patch.md"), "patch.md");
|
|
2577
|
+
const dryRun = readOrEmpty(path14.join(args.runDir, "dry-run-output.txt"), "dry-run-output.txt");
|
|
2578
|
+
const meta = readOrEmpty(path14.join(args.runDir, "meta.json"), "meta.json");
|
|
2479
2579
|
return [
|
|
2480
2580
|
"You are running as the Evolution scorer.",
|
|
2481
2581
|
"",
|
|
@@ -2486,8 +2586,8 @@ function buildScoreBrief(args) {
|
|
|
2486
2586
|
"write score.json with the absolute path given below)",
|
|
2487
2587
|
"",
|
|
2488
2588
|
`The original conversation lives on disk:`,
|
|
2489
|
-
` ${
|
|
2490
|
-
` ${
|
|
2589
|
+
` ${path14.join(args.runDir, "tool-flow.md")} \u2014 clipped, fast skim`,
|
|
2590
|
+
` ${path14.join(args.runDir, "source-snapshot.json")} \u2014 full rawMessages, when needed`,
|
|
2491
2591
|
"Read tool-flow.md first to find the user turn matching",
|
|
2492
2592
|
"`testScenario.originalMessage` and the assistant reply that followed",
|
|
2493
2593
|
"it. Fall back to source-snapshot.json only when the full tool result",
|
|
@@ -2537,7 +2637,7 @@ function buildScoreBrief(args) {
|
|
|
2537
2637
|
"find the assistant turn that followed it in tool-flow.md (or",
|
|
2538
2638
|
"source-snapshot.json) \u2014 that's your baseline. Compare baseline to",
|
|
2539
2639
|
"dry-run-output.txt, rate lint / behavior / scope, write",
|
|
2540
|
-
`${
|
|
2640
|
+
`${path14.join(args.runDir, "score.json")} with the shape AGENT.md describes.`,
|
|
2541
2641
|
"",
|
|
2542
2642
|
...buildLanguageClause(args.langHint, 'the score.json "notes" field'),
|
|
2543
2643
|
'The numeric fields and "confidence" enum stay in their canonical form.'
|
|
@@ -2580,14 +2680,14 @@ async function phaseDraft(args) {
|
|
|
2580
2680
|
return outcome;
|
|
2581
2681
|
}
|
|
2582
2682
|
function judgeDraft(runDir, workspacePath, exitCode) {
|
|
2583
|
-
if (
|
|
2683
|
+
if (fs12.existsSync(path14.join(runDir, ".skip.md"))) {
|
|
2584
2684
|
return { outcome: "skipped", why: ".skip.md present \u2014 evo declared skip" };
|
|
2585
2685
|
}
|
|
2586
2686
|
if (exitCode !== 0) {
|
|
2587
2687
|
const timeout = exitCode === 124 ? " (timeout)" : "";
|
|
2588
2688
|
return { outcome: "failed", why: `drafter exited ${exitCode}${timeout} \u2014 draft interrupted, not trusted` };
|
|
2589
2689
|
}
|
|
2590
|
-
if (!
|
|
2690
|
+
if (!fs12.existsSync(path14.join(runDir, "patch.md"))) {
|
|
2591
2691
|
return { outcome: "failed", why: "patch.md missing" };
|
|
2592
2692
|
}
|
|
2593
2693
|
const target = readPatchFrontmatter(runDir)?.target;
|
|
@@ -2595,16 +2695,16 @@ function judgeDraft(runDir, workspacePath, exitCode) {
|
|
|
2595
2695
|
return { outcome: "drafted", why: "patch.md present, no target in frontmatter \u2014 target file not checked" };
|
|
2596
2696
|
}
|
|
2597
2697
|
const sandboxDir = evoSandboxDir(runDir);
|
|
2598
|
-
const rel =
|
|
2599
|
-
if (!rel.startsWith(".halo" +
|
|
2698
|
+
const rel = path14.relative(sandboxDir, path14.resolve(sandboxDir, target));
|
|
2699
|
+
if (!rel.startsWith(".halo" + path14.sep)) {
|
|
2600
2700
|
return { outcome: "failed", why: `target ${JSON.stringify(target)} does not resolve under sandbox/.halo/` };
|
|
2601
2701
|
}
|
|
2602
|
-
const sandboxFile =
|
|
2603
|
-
if (!
|
|
2702
|
+
const sandboxFile = path14.join(sandboxDir, rel);
|
|
2703
|
+
if (!fs12.statSync(sandboxFile, { throwIfNoEntry: false })?.isFile()) {
|
|
2604
2704
|
return { outcome: "failed", why: `target not written to sandbox: ${target}` };
|
|
2605
2705
|
}
|
|
2606
|
-
const wsFile =
|
|
2607
|
-
if (
|
|
2706
|
+
const wsFile = path14.join(workspacePath, rel);
|
|
2707
|
+
if (fs12.statSync(wsFile, { throwIfNoEntry: false })?.isFile() && fs12.readFileSync(wsFile).equals(fs12.readFileSync(sandboxFile))) {
|
|
2608
2708
|
return { outcome: "failed", why: `target unchanged from workspace: ${target}` };
|
|
2609
2709
|
}
|
|
2610
2710
|
return { outcome: "drafted", why: `patch.md + target ${target} present` };
|
|
@@ -2631,15 +2731,15 @@ async function phaseDryRun(args) {
|
|
|
2631
2731
|
}
|
|
2632
2732
|
async function tryDryRun(args, attemptIndex) {
|
|
2633
2733
|
const sandboxDir = evoSandboxDir(args.runDir);
|
|
2634
|
-
const outputPath =
|
|
2635
|
-
const failLogPath =
|
|
2734
|
+
const outputPath = path14.join(args.runDir, "dry-run-output.txt");
|
|
2735
|
+
const failLogPath = path14.join(args.runDir, `dry-run-fail-${attemptIndex}.log`);
|
|
2636
2736
|
const cli = resolveHaloCli();
|
|
2637
2737
|
const fm = readPatchFrontmatter(args.runDir);
|
|
2638
2738
|
const scenario = extractTestScenario(fm);
|
|
2639
2739
|
if (!scenario) {
|
|
2640
2740
|
writeLog(args.logFd, `[tryDryRun:${attemptIndex}] patch.md frontmatter missing testScenario.{agentId, testMessage}
|
|
2641
2741
|
`);
|
|
2642
|
-
|
|
2742
|
+
fs12.writeFileSync(failLogPath, "patch.md frontmatter missing testScenario.{agentId, testMessage} \u2014 wrapper cannot run dry-run without it.\n", "utf-8");
|
|
2643
2743
|
return { kind: "no-scenario", failLogPath };
|
|
2644
2744
|
}
|
|
2645
2745
|
writeLog(args.logFd, `[tryDryRun:${attemptIndex}] agent=${scenario.agentId} testMessage=${JSON.stringify(scenario.testMessage).slice(0, 200)}
|
|
@@ -2657,14 +2757,14 @@ async function tryDryRun(args, attemptIndex) {
|
|
|
2657
2757
|
const timedOut = result.exitCode === 124;
|
|
2658
2758
|
const ok = result.exitCode === 0 && result.stdout.trim().length > 0;
|
|
2659
2759
|
if (ok) {
|
|
2660
|
-
|
|
2760
|
+
fs12.writeFileSync(outputPath, result.stdout, "utf-8");
|
|
2661
2761
|
writeLog(args.logFd, `[tryDryRun:${attemptIndex}] success: wrote ${outputPath} (${result.stdout.length}B)
|
|
2662
2762
|
`);
|
|
2663
2763
|
return { kind: "ok" };
|
|
2664
2764
|
}
|
|
2665
2765
|
const stdoutTail = result.stdout.length > 4e3 ? "\u2026" + result.stdout.slice(-4e3) : result.stdout;
|
|
2666
2766
|
const stderrTail = result.stderr.length > 4e3 ? "\u2026" + result.stderr.slice(-4e3) : result.stderr;
|
|
2667
|
-
|
|
2767
|
+
fs12.writeFileSync(failLogPath, [
|
|
2668
2768
|
`Wrapper command: halo cli -a ${scenario.agentId} -n -w ${sandboxDir} <testMessage>`,
|
|
2669
2769
|
`Test message: ${scenario.testMessage}`,
|
|
2670
2770
|
`Exit code: ${result.exitCode}${timedOut ? " (timeout)" : ""}`,
|
|
@@ -2720,7 +2820,7 @@ async function phaseScore(args) {
|
|
|
2720
2820
|
const sandboxWs = evoSandboxDir(args.runDir);
|
|
2721
2821
|
buildEvoSandbox(args.workspacePath, args.runDir, args.logFd);
|
|
2722
2822
|
const cliArgs = ["cli", "-a", "__score__", "-n", "-w", sandboxWs];
|
|
2723
|
-
|
|
2823
|
+
fs12.rmSync(path14.join(args.runDir, "score.json"), { force: true });
|
|
2724
2824
|
appendSubCliHeader(args.runDir, "Phase C: __score__");
|
|
2725
2825
|
const result = await spawnProc(
|
|
2726
2826
|
resolveHaloCli(),
|
|
@@ -2741,7 +2841,7 @@ async function phaseScore(args) {
|
|
|
2741
2841
|
writeLog(args.logFd, `[phaseScore] stdout (last 1KB): ${result.stdout.slice(-1e3)}
|
|
2742
2842
|
`);
|
|
2743
2843
|
}
|
|
2744
|
-
const hasScore =
|
|
2844
|
+
const hasScore = fs12.existsSync(path14.join(args.runDir, "score.json"));
|
|
2745
2845
|
writeLog(args.logFd, `[phaseScore] score.json=${hasScore}
|
|
2746
2846
|
`);
|
|
2747
2847
|
return hasScore;
|
|
@@ -2755,7 +2855,7 @@ var RUN_INPUT_FILES = /* @__PURE__ */ new Set([
|
|
|
2755
2855
|
function resetRunArtifacts(runDir, logFd) {
|
|
2756
2856
|
let entries;
|
|
2757
2857
|
try {
|
|
2758
|
-
entries =
|
|
2858
|
+
entries = fs12.readdirSync(runDir);
|
|
2759
2859
|
} catch {
|
|
2760
2860
|
return;
|
|
2761
2861
|
}
|
|
@@ -2763,7 +2863,7 @@ function resetRunArtifacts(runDir, logFd) {
|
|
|
2763
2863
|
if (RUN_INPUT_FILES.has(entry))
|
|
2764
2864
|
continue;
|
|
2765
2865
|
try {
|
|
2766
|
-
|
|
2866
|
+
fs12.rmSync(path14.join(runDir, entry), { recursive: true, force: true });
|
|
2767
2867
|
} catch (err) {
|
|
2768
2868
|
writeLog(logFd, `[resetRunArtifacts] failed to remove ${entry}: ${err instanceof Error ? err.message : String(err)}
|
|
2769
2869
|
`);
|
|
@@ -2773,7 +2873,7 @@ function resetRunArtifacts(runDir, logFd) {
|
|
|
2773
2873
|
async function runMode(row, logFd) {
|
|
2774
2874
|
const { id } = row;
|
|
2775
2875
|
const runDir = wsEvoRunDir(row.workspacePath, id);
|
|
2776
|
-
|
|
2876
|
+
fs12.mkdirSync(runDir, { recursive: true });
|
|
2777
2877
|
resetRunArtifacts(runDir, logFd);
|
|
2778
2878
|
const hb = setInterval(() => heartbeat("run", id, row.attempts, logFd), 6e4);
|
|
2779
2879
|
const langHint = resolveLangHint();
|
|
@@ -2817,14 +2917,14 @@ async function runMode(row, logFd) {
|
|
|
2817
2917
|
}
|
|
2818
2918
|
}
|
|
2819
2919
|
function markObservation(id, attempts, runDir, outcome, logFd) {
|
|
2820
|
-
const has = outcome !== "failed" && readObservation(
|
|
2920
|
+
const has = outcome !== "failed" && readObservation(path14.join(runDir, outcome === "skipped" ? ".skip.md" : "patch.md")) !== null;
|
|
2821
2921
|
getEvoDb().update(evolutionRuns).set({ hasObservation: has }).where(ownsRun(id, attempts)).run();
|
|
2822
2922
|
if (has)
|
|
2823
2923
|
writeLog(logFd, `[wrapper] draft carries a Larger observation
|
|
2824
2924
|
`);
|
|
2825
2925
|
}
|
|
2826
2926
|
function maybeAutoApply(args) {
|
|
2827
|
-
const level =
|
|
2927
|
+
const level = evoLevelFor(args.workspacePath);
|
|
2828
2928
|
if (evoLevelRank(level) < 2)
|
|
2829
2929
|
return false;
|
|
2830
2930
|
let record;
|
|
@@ -2835,12 +2935,12 @@ function maybeAutoApply(args) {
|
|
|
2835
2935
|
sandboxHalo: evoSandboxHaloDir(args.runDir),
|
|
2836
2936
|
entries: PUBLISH_WHITELIST,
|
|
2837
2937
|
level,
|
|
2838
|
-
threshold:
|
|
2938
|
+
threshold: autoApplyThresholdFor(args.workspacePath),
|
|
2839
2939
|
sourceAccess: args.sourceAccess,
|
|
2840
|
-
score: readScore(
|
|
2841
|
-
observation: readObservation(
|
|
2940
|
+
score: readScore(path14.join(args.runDir, "score.json")),
|
|
2941
|
+
observation: readObservation(path14.join(args.runDir, "patch.md"))
|
|
2842
2942
|
});
|
|
2843
|
-
|
|
2943
|
+
fs12.writeFileSync(path14.join(args.runDir, AUTO_APPLY_FILE), JSON.stringify(record, null, 2), "utf-8");
|
|
2844
2944
|
} catch (err) {
|
|
2845
2945
|
writeLog(args.logFd, `[autoApply] evaluation failed, leaving the run for review: ${err instanceof Error ? err.message : String(err)}
|
|
2846
2946
|
`);
|
|
@@ -2938,14 +3038,14 @@ function loadApplyRow(id) {
|
|
|
2938
3038
|
function buildEvoSandbox(workspacePath, sandboxParent, logFd) {
|
|
2939
3039
|
const srcWs = wsHaloDir(workspacePath);
|
|
2940
3040
|
const dstWs = evoSandboxHaloDir(sandboxParent);
|
|
2941
|
-
|
|
3041
|
+
fs12.mkdirSync(dstWs, { recursive: true });
|
|
2942
3042
|
const whitelist = SANDBOX_WHITELIST;
|
|
2943
3043
|
for (const entry of whitelist) {
|
|
2944
|
-
const srcEntry =
|
|
2945
|
-
if (!
|
|
3044
|
+
const srcEntry = path14.join(srcWs, entry);
|
|
3045
|
+
if (!fs12.existsSync(srcEntry))
|
|
2946
3046
|
continue;
|
|
2947
|
-
const dstEntry =
|
|
2948
|
-
if (
|
|
3047
|
+
const dstEntry = path14.join(dstWs, entry);
|
|
3048
|
+
if (fs12.existsSync(dstEntry))
|
|
2949
3049
|
continue;
|
|
2950
3050
|
copyDereferenced(srcEntry, dstEntry);
|
|
2951
3051
|
}
|
|
@@ -2953,13 +3053,13 @@ function buildEvoSandbox(workspacePath, sandboxParent, logFd) {
|
|
|
2953
3053
|
`);
|
|
2954
3054
|
}
|
|
2955
3055
|
function copyDereferenced(src, dst) {
|
|
2956
|
-
if (
|
|
2957
|
-
|
|
2958
|
-
for (const entry of
|
|
2959
|
-
copyDereferenced(
|
|
3056
|
+
if (fs12.statSync(src).isDirectory()) {
|
|
3057
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
3058
|
+
for (const entry of fs12.readdirSync(src))
|
|
3059
|
+
copyDereferenced(path14.join(src, entry), path14.join(dst, entry));
|
|
2960
3060
|
return;
|
|
2961
3061
|
}
|
|
2962
|
-
|
|
3062
|
+
fs12.copyFileSync(src, dst);
|
|
2963
3063
|
}
|
|
2964
3064
|
function buildApplySandbox(workspacePath, applyDir, logFd) {
|
|
2965
3065
|
buildEvoSandbox(workspacePath, applyDir, logFd);
|
|
@@ -2987,11 +3087,11 @@ function buildApplyMergeBrief(args) {
|
|
|
2987
3087
|
"",
|
|
2988
3088
|
"Hard rules: only edit files under sandbox/.halo/ \u2014 main workspace",
|
|
2989
3089
|
"and ~/.halo/global/ are off-limits. Do NOT spawn halo cli. Do NOT",
|
|
2990
|
-
`write score.json. Write ${
|
|
3090
|
+
`write score.json. Write ${path14.join(args.applyDir, "apply.log")}`,
|
|
2991
3091
|
"with a summary of what you did.",
|
|
2992
3092
|
"",
|
|
2993
3093
|
"If the merge cannot proceed (irreconcilable patch conflicts, malformed",
|
|
2994
|
-
`input, missing sandbox), write ${
|
|
3094
|
+
`input, missing sandbox), write ${path14.join(args.applyDir, "ABORT.md")}`,
|
|
2995
3095
|
'with the diagnosis \u2014 the wrapper treats its presence as "abort this',
|
|
2996
3096
|
'apply" and fails the apply instead of publishing. You cannot signal',
|
|
2997
3097
|
"abort via exit code; ABORT.md is the only abort channel.",
|
|
@@ -3012,19 +3112,19 @@ function buildApplyScoreBrief(args) {
|
|
|
3012
3112
|
"The apply agent has merged this patch (and possibly others) into",
|
|
3013
3113
|
"an apply sandbox. The wrapper just ran the patch's testScenario",
|
|
3014
3114
|
"against the merged sandbox; stdout is in",
|
|
3015
|
-
`${
|
|
3115
|
+
`${path14.join(args.regressDir, "dry-run-output.txt")}.`,
|
|
3016
3116
|
"An empty dry-run-output.txt means the dry-run against the merged",
|
|
3017
3117
|
"sandbox failed \u2014 score lint 0 (that is the regression signal the",
|
|
3018
3118
|
`wrapper acts on); the failure detail is in`,
|
|
3019
|
-
`${
|
|
3119
|
+
`${path14.join(args.regressDir, "dry-run-fail.log")} when present.`,
|
|
3020
3120
|
"",
|
|
3021
3121
|
"Follow your AGENT.md procedure. Read:",
|
|
3022
3122
|
` patch.md \u2192 ${args.patchMdPath}`,
|
|
3023
|
-
` tool-flow.md \u2192 ${
|
|
3123
|
+
` tool-flow.md \u2192 ${path14.join(path14.dirname(args.patchMdPath), "tool-flow.md")} (baseline conversation)`,
|
|
3024
3124
|
` source-snapshot.json \u2192 ${args.snapshotPath}`,
|
|
3025
|
-
` dry-run output \u2192 ${
|
|
3125
|
+
` dry-run output \u2192 ${path14.join(args.regressDir, "dry-run-output.txt")}`,
|
|
3026
3126
|
"",
|
|
3027
|
-
`Write ${
|
|
3127
|
+
`Write ${path14.join(args.regressDir, "score.json")} \u2014 NOT the source`,
|
|
3028
3128
|
"run's score.json. Same format as run mode.",
|
|
3029
3129
|
"Do not modify any other file.",
|
|
3030
3130
|
...buildLanguageClause(args.langHint, 'the score.json "notes" field'),
|
|
@@ -3036,9 +3136,9 @@ async function phaseApplyMerge(ctx) {
|
|
|
3036
3136
|
=== Phase A': merge ===
|
|
3037
3137
|
`);
|
|
3038
3138
|
buildApplySandbox(ctx.workspacePath, ctx.applyDir, ctx.logFd);
|
|
3039
|
-
const abortPath =
|
|
3139
|
+
const abortPath = path14.join(ctx.applyDir, "ABORT.md");
|
|
3040
3140
|
try {
|
|
3041
|
-
|
|
3141
|
+
fs12.rmSync(abortPath, { force: true });
|
|
3042
3142
|
} catch {
|
|
3043
3143
|
}
|
|
3044
3144
|
const brief = buildApplyMergeBrief(ctx);
|
|
@@ -3057,10 +3157,10 @@ async function phaseApplyMerge(ctx) {
|
|
|
3057
3157
|
`);
|
|
3058
3158
|
return { ok: false, reason: `apply cli exited ${result.exitCode}` };
|
|
3059
3159
|
}
|
|
3060
|
-
if (
|
|
3160
|
+
if (fs12.existsSync(abortPath)) {
|
|
3061
3161
|
let abortText = "";
|
|
3062
3162
|
try {
|
|
3063
|
-
abortText =
|
|
3163
|
+
abortText = fs12.readFileSync(abortPath, "utf-8").trim();
|
|
3064
3164
|
} catch {
|
|
3065
3165
|
}
|
|
3066
3166
|
writeLog(ctx.logFd, `[phaseApplyMerge] ABORT.md present \u2014 agent aborted the merge:
|
|
@@ -3069,7 +3169,7 @@ ${abortText}
|
|
|
3069
3169
|
const firstLine = abortText.split("\n")[0]?.slice(0, 300) ?? "";
|
|
3070
3170
|
return { ok: false, reason: `apply agent aborted: ${firstLine || "see ABORT.md in apply dir"}` };
|
|
3071
3171
|
}
|
|
3072
|
-
const hasLog =
|
|
3172
|
+
const hasLog = fs12.existsSync(path14.join(ctx.applyDir, "apply.log"));
|
|
3073
3173
|
writeLog(ctx.logFd, `[phaseApplyMerge] apply.log=${hasLog}
|
|
3074
3174
|
`);
|
|
3075
3175
|
if (!hasLog)
|
|
@@ -3081,17 +3181,17 @@ async function phaseApplyRegress(ctx) {
|
|
|
3081
3181
|
=== Phase B': regress ===
|
|
3082
3182
|
`);
|
|
3083
3183
|
const sandboxDir = evoSandboxDir(ctx.applyDir);
|
|
3084
|
-
|
|
3184
|
+
fs12.mkdirSync(evoRegressDir(ctx.applyDir), { recursive: true });
|
|
3085
3185
|
const outcomes = [];
|
|
3086
3186
|
for (const runId of ctx.sourceRunIds) {
|
|
3087
3187
|
writeLog(ctx.logFd, `
|
|
3088
3188
|
--- regress for source run ${runId} ---
|
|
3089
3189
|
`);
|
|
3090
3190
|
const regressDir = evoRegressRunDir(ctx.applyDir, runId);
|
|
3091
|
-
|
|
3191
|
+
fs12.mkdirSync(regressDir, { recursive: true });
|
|
3092
3192
|
const runDir = wsEvoRunDir(ctx.workspacePath, runId);
|
|
3093
|
-
const patchMdPath =
|
|
3094
|
-
const snapshotPath =
|
|
3193
|
+
const patchMdPath = path14.join(runDir, "patch.md");
|
|
3194
|
+
const snapshotPath = path14.join(runDir, "source-snapshot.json");
|
|
3095
3195
|
const fm = readPatchFrontmatter(runDir);
|
|
3096
3196
|
const scenario = extractTestScenario(fm);
|
|
3097
3197
|
if (!scenario) {
|
|
@@ -3118,14 +3218,14 @@ async function phaseApplyRegress(ctx) {
|
|
|
3118
3218
|
// stdin, not argv
|
|
3119
3219
|
DRY_RUN_TIMEOUT_SEC
|
|
3120
3220
|
);
|
|
3121
|
-
const dryRunOut =
|
|
3221
|
+
const dryRunOut = path14.join(regressDir, "dry-run-output.txt");
|
|
3122
3222
|
if (dryRun.exitCode === 0 && dryRun.stdout.trim().length > 0) {
|
|
3123
|
-
|
|
3223
|
+
fs12.writeFileSync(dryRunOut, dryRun.stdout, "utf-8");
|
|
3124
3224
|
} else {
|
|
3125
3225
|
const tail = dryRun.stdout.length > 4e3 ? "\u2026" + dryRun.stdout.slice(-4e3) : dryRun.stdout;
|
|
3126
3226
|
const errTail = dryRun.stderr.length > 4e3 ? "\u2026" + dryRun.stderr.slice(-4e3) : dryRun.stderr;
|
|
3127
|
-
|
|
3128
|
-
|
|
3227
|
+
fs12.writeFileSync(dryRunOut, "", "utf-8");
|
|
3228
|
+
fs12.writeFileSync(path14.join(regressDir, "dry-run-fail.log"), [
|
|
3129
3229
|
`Wrapper command: halo cli -a ${scenario.agentId} -n -w ${sandboxDir} <testMessage>`,
|
|
3130
3230
|
`Test message: ${scenario.testMessage}`,
|
|
3131
3231
|
`Exit code: ${dryRun.exitCode}${dryRun.exitCode === 124 ? " (timeout)" : ""}`,
|
|
@@ -3146,7 +3246,7 @@ async function phaseApplyRegress(ctx) {
|
|
|
3146
3246
|
langHint: ctx.langHint
|
|
3147
3247
|
});
|
|
3148
3248
|
const scoreSandboxWs = evoSandboxDir(ctx.applyDir);
|
|
3149
|
-
|
|
3249
|
+
fs12.rmSync(path14.join(regressDir, "score.json"), { force: true });
|
|
3150
3250
|
const scoreRun = await spawnProc(
|
|
3151
3251
|
resolveHaloCli(),
|
|
3152
3252
|
["cli", "-a", "__score__", "-n", "-w", scoreSandboxWs],
|
|
@@ -3160,10 +3260,10 @@ async function phaseApplyRegress(ctx) {
|
|
|
3160
3260
|
writeLog(ctx.logFd, `[phaseApplyRegress:${runId}] scorer cli exited ${scoreRun.exitCode}
|
|
3161
3261
|
`);
|
|
3162
3262
|
}
|
|
3163
|
-
const scorePath =
|
|
3263
|
+
const scorePath = path14.join(regressDir, "score.json");
|
|
3164
3264
|
let scoreJson = null;
|
|
3165
3265
|
try {
|
|
3166
|
-
scoreJson = JSON.parse(
|
|
3266
|
+
scoreJson = JSON.parse(fs12.readFileSync(scorePath, "utf-8"));
|
|
3167
3267
|
} catch {
|
|
3168
3268
|
}
|
|
3169
3269
|
if (!scoreJson) {
|
|
@@ -3217,13 +3317,13 @@ function autoApplyRecheck(workspacePath, applyDir, changed) {
|
|
|
3217
3317
|
globalHalo: globalDir(),
|
|
3218
3318
|
sandboxHalo: evoSandboxHaloDir(applyDir)
|
|
3219
3319
|
}, changed));
|
|
3220
|
-
return categoryGate(
|
|
3320
|
+
return categoryGate(evoLevelFor(workspacePath), cls);
|
|
3221
3321
|
}
|
|
3222
3322
|
async function applyMode(id, logFd) {
|
|
3223
3323
|
const row = loadApplyRow(id);
|
|
3224
3324
|
const applyDir = wsEvoApplyDir(row.workspacePath, id);
|
|
3225
|
-
|
|
3226
|
-
|
|
3325
|
+
fs12.mkdirSync(applyDir, { recursive: true });
|
|
3326
|
+
fs12.writeFileSync(path14.join(applyDir, "meta.json"), JSON.stringify({
|
|
3227
3327
|
id,
|
|
3228
3328
|
sourceRunIds: row.sourceRunIds,
|
|
3229
3329
|
reviewerHint: row.reviewerHint,
|
|
@@ -3314,28 +3414,28 @@ async function phaseApplyPreflight(ctx, isResume) {
|
|
|
3314
3414
|
`);
|
|
3315
3415
|
const sandboxHalo = evoSandboxHaloDir(ctx.applyDir);
|
|
3316
3416
|
const mainHalo = wsHaloDir(ctx.workspacePath);
|
|
3317
|
-
if (!
|
|
3417
|
+
if (!fs12.existsSync(sandboxHalo))
|
|
3318
3418
|
return { ok: false, reason: "sandbox missing \u2014 phase 11 must run first" };
|
|
3319
3419
|
const whitelistTop = new Set(PUBLISH_WHITELIST);
|
|
3320
3420
|
const filesToConsider = [];
|
|
3321
|
-
for (const top of
|
|
3421
|
+
for (const top of fs12.readdirSync(sandboxHalo)) {
|
|
3322
3422
|
if (!whitelistTop.has(top))
|
|
3323
3423
|
continue;
|
|
3324
|
-
walkInto(
|
|
3424
|
+
walkInto(path14.join(sandboxHalo, top), top, filesToConsider);
|
|
3325
3425
|
}
|
|
3326
3426
|
const historyDir = wsEvoHistoryDir(ctx.workspacePath, ctx.applyId);
|
|
3327
3427
|
const changed = [];
|
|
3328
3428
|
for (const { rel, full } of filesToConsider) {
|
|
3329
|
-
const mainPath =
|
|
3429
|
+
const mainPath = path14.join(mainHalo, rel);
|
|
3330
3430
|
let existsInMain = false;
|
|
3331
3431
|
try {
|
|
3332
|
-
|
|
3432
|
+
fs12.accessSync(mainPath);
|
|
3333
3433
|
existsInMain = true;
|
|
3334
3434
|
} catch {
|
|
3335
3435
|
}
|
|
3336
3436
|
if (existsInMain) {
|
|
3337
|
-
const sandboxBuf =
|
|
3338
|
-
const mainBuf =
|
|
3437
|
+
const sandboxBuf = fs12.readFileSync(full);
|
|
3438
|
+
const mainBuf = fs12.readFileSync(mainPath);
|
|
3339
3439
|
if (sandboxBuf.equals(mainBuf))
|
|
3340
3440
|
continue;
|
|
3341
3441
|
}
|
|
@@ -3344,20 +3444,20 @@ async function phaseApplyPreflight(ctx, isResume) {
|
|
|
3344
3444
|
writeLog(ctx.logFd, `[preflight] ${changed.length} of ${filesToConsider.length} whitelisted files differ from main
|
|
3345
3445
|
`);
|
|
3346
3446
|
if (!isResume)
|
|
3347
|
-
|
|
3447
|
+
fs12.rmSync(historyDir, { recursive: true, force: true });
|
|
3348
3448
|
if (changed.length === 0) {
|
|
3349
3449
|
return { ok: true, result: { changed, historyDir, fileCount: 0 } };
|
|
3350
3450
|
}
|
|
3351
|
-
|
|
3352
|
-
const manifestPath =
|
|
3451
|
+
fs12.mkdirSync(historyDir, { recursive: true });
|
|
3452
|
+
const manifestPath = path14.join(historyDir, "MANIFEST.json");
|
|
3353
3453
|
if (!isResume) {
|
|
3354
3454
|
for (const { rel, existsInMain } of changed) {
|
|
3355
3455
|
if (!existsInMain)
|
|
3356
3456
|
continue;
|
|
3357
|
-
const mainPath =
|
|
3358
|
-
const histPath =
|
|
3359
|
-
|
|
3360
|
-
|
|
3457
|
+
const mainPath = path14.join(mainHalo, rel);
|
|
3458
|
+
const histPath = path14.join(historyDir, rel);
|
|
3459
|
+
fs12.mkdirSync(path14.dirname(histPath), { recursive: true });
|
|
3460
|
+
fs12.copyFileSync(mainPath, histPath);
|
|
3361
3461
|
}
|
|
3362
3462
|
const effective = new Set(publishChanges({ mainHalo, globalHalo: globalDir(), sandboxHalo }, changed).map((c) => c.rel));
|
|
3363
3463
|
const manifest = {
|
|
@@ -3367,13 +3467,13 @@ async function phaseApplyPreflight(ctx, isResume) {
|
|
|
3367
3467
|
files: changed.map((c) => ({
|
|
3368
3468
|
rel: c.rel,
|
|
3369
3469
|
kind: c.existsInMain ? "overwrite" : "new",
|
|
3370
|
-
before: c.existsInMain ? sha256File(
|
|
3470
|
+
before: c.existsInMain ? sha256File(path14.join(mainHalo, c.rel)) : null,
|
|
3371
3471
|
after: sha256File(c.full),
|
|
3372
|
-
...effective.has(c.rel.split(
|
|
3472
|
+
...effective.has(c.rel.split(path14.sep).join("/")) ? {} : { unchanged: true }
|
|
3373
3473
|
})),
|
|
3374
3474
|
createdDirs: dirsCreatedBy(mainHalo, changed.filter((c) => !c.existsInMain).map((c) => c.rel))
|
|
3375
3475
|
};
|
|
3376
|
-
|
|
3476
|
+
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
3377
3477
|
} else {
|
|
3378
3478
|
writeLog(ctx.logFd, `[preflight] resume path, skipping history backup (the pre-apply state is already preserved from the fresh claim's preflight)
|
|
3379
3479
|
`);
|
|
@@ -3388,9 +3488,9 @@ async function phaseApplyPublish(ctx, preflight) {
|
|
|
3388
3488
|
`);
|
|
3389
3489
|
const mainHalo = wsHaloDir(ctx.workspacePath);
|
|
3390
3490
|
for (const { rel, full } of preflight.changed) {
|
|
3391
|
-
const mainPath =
|
|
3392
|
-
|
|
3393
|
-
|
|
3491
|
+
const mainPath = path14.join(mainHalo, rel);
|
|
3492
|
+
fs12.mkdirSync(path14.dirname(mainPath), { recursive: true });
|
|
3493
|
+
fs12.copyFileSync(full, mainPath);
|
|
3394
3494
|
}
|
|
3395
3495
|
writeLog(ctx.logFd, `[publish] copied ${preflight.changed.length} files to ${mainHalo}
|
|
3396
3496
|
`);
|
|
@@ -3398,10 +3498,10 @@ async function phaseApplyPublish(ctx, preflight) {
|
|
|
3398
3498
|
function dirsCreatedBy(mainHalo, newFiles) {
|
|
3399
3499
|
const dirs = /* @__PURE__ */ new Set();
|
|
3400
3500
|
for (const rel of newFiles) {
|
|
3401
|
-
for (let d =
|
|
3501
|
+
for (let d = path14.dirname(rel); d !== "." && !fs12.existsSync(path14.join(mainHalo, d)); d = path14.dirname(d))
|
|
3402
3502
|
dirs.add(d);
|
|
3403
3503
|
}
|
|
3404
|
-
return [...dirs].sort((a, b) => b.split(
|
|
3504
|
+
return [...dirs].sort((a, b) => b.split(path14.sep).length - a.split(path14.sep).length || (a < b ? -1 : 1));
|
|
3405
3505
|
}
|
|
3406
3506
|
function ownsApply(id, status, attempts) {
|
|
3407
3507
|
return and3(eq3(evolutionApplies.id, id), eq3(evolutionApplies.status, status), eq3(evolutionApplies.attempts, attempts));
|
|
@@ -3427,15 +3527,15 @@ function releaseSyncing(applyId, attempts, reason, logFd) {
|
|
|
3427
3527
|
`);
|
|
3428
3528
|
}
|
|
3429
3529
|
function walkInto(absPath, relPath, out) {
|
|
3430
|
-
const stat =
|
|
3530
|
+
const stat = fs12.statSync(absPath);
|
|
3431
3531
|
if (stat.isFile()) {
|
|
3432
3532
|
out.push({ rel: relPath, full: absPath });
|
|
3433
3533
|
return;
|
|
3434
3534
|
}
|
|
3435
3535
|
if (!stat.isDirectory())
|
|
3436
3536
|
return;
|
|
3437
|
-
for (const entry of
|
|
3438
|
-
walkInto(
|
|
3537
|
+
for (const entry of fs12.readdirSync(absPath)) {
|
|
3538
|
+
walkInto(path14.join(absPath, entry), path14.join(relPath, entry), out);
|
|
3439
3539
|
}
|
|
3440
3540
|
}
|
|
3441
3541
|
function finalizeApplied(applyId, attempts, sourceRunIds, logFd) {
|
|
@@ -3454,19 +3554,19 @@ function finalizeApplied(applyId, attempts, sourceRunIds, logFd) {
|
|
|
3454
3554
|
}
|
|
3455
3555
|
function writeLog(fd, msg) {
|
|
3456
3556
|
try {
|
|
3457
|
-
|
|
3557
|
+
fs12.writeSync(fd, msg);
|
|
3458
3558
|
} catch {
|
|
3459
3559
|
}
|
|
3460
3560
|
}
|
|
3461
3561
|
function openLogFor(mode, id) {
|
|
3462
|
-
|
|
3562
|
+
fs12.mkdirSync(evoLogsDir(), { recursive: true });
|
|
3463
3563
|
const logPath = mode === "run" ? evoWrapperLogFile(id) : evoApplyLogFile(id);
|
|
3464
|
-
const fd =
|
|
3564
|
+
const fd = fs12.openSync(logPath, "a");
|
|
3465
3565
|
return { fd, logPath };
|
|
3466
3566
|
}
|
|
3467
3567
|
async function main() {
|
|
3468
3568
|
const { mode, id } = parseCli(process.argv.slice(2));
|
|
3469
|
-
setEvoDb(createEvoDb(
|
|
3569
|
+
setEvoDb(createEvoDb(path14.join(homedir7(), ".halo", "global")));
|
|
3470
3570
|
const { fd, logPath } = openLogFor(mode, id);
|
|
3471
3571
|
writeLog(fd, `
|
|
3472
3572
|
=== ${(/* @__PURE__ */ new Date()).toISOString()} wrapper start mode=${mode} id=${id} pid=${process.pid} ===
|
|
@@ -3494,7 +3594,7 @@ async function main() {
|
|
|
3494
3594
|
process.exit(1);
|
|
3495
3595
|
}
|
|
3496
3596
|
}
|
|
3497
|
-
if (process.argv[1] &&
|
|
3597
|
+
if (process.argv[1] && path14.resolve(process.argv[1]) === fileURLToPath2(import.meta.url)) {
|
|
3498
3598
|
main().catch((err) => {
|
|
3499
3599
|
process.stderr.write(`[wrapper] startup error: ${err instanceof Error ? err.message : String(err)}
|
|
3500
3600
|
`);
|