@turmind/halo 2.2.3 → 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/index.html +1 -1
- package/admin-out/index.txt +4 -4
- package/bundled-docs/design/a2a.md +3 -1
- package/bundled-docs/design/architecture.md +1 -1
- 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 +2 -2
- package/bundled-docs/design/storage.md +4 -1
- package/bundled-docs/design/ws.md +2 -2
- package/bundled-docs/dev/api.md +4 -3
- 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 +1 -30
- package/bundled-docs/guide/node.md +57 -36
- package/bundled-docs/requirements/settings.md +38 -1
- package/dist/evo-wrapper.js +323 -247
- package/dist/index.js +1727 -1962
- package/package.json +2 -1
- package/templates/agents/default/agent.yaml +1 -1
- package/templates/models/aws-bedrock-openai.yaml +81 -7
- 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/{bbPEaP5uh2KvLzSHGU2CO → vf5W8US5y8B2f1E6zJJw3}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{bbPEaP5uh2KvLzSHGU2CO → 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() {
|
|
@@ -920,6 +1012,11 @@ function generalSection() {
|
|
|
920
1012
|
// a workspace must not widen; public_url names this server to peers.
|
|
921
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: "" },
|
|
922
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 },
|
|
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: "" },
|
|
923
1020
|
// self-evolution (see plans/self-evolution.md). The level and the
|
|
924
1021
|
// auto-apply threshold are per workspace (read where a run's workspace is
|
|
925
1022
|
// known: the pre-compact trigger, the wrapper's auto-apply verdict and
|
|
@@ -956,11 +1053,11 @@ var _yamlCache = createMtimeCache();
|
|
|
956
1053
|
var _skillNameCache = createMtimeCache();
|
|
957
1054
|
function readYamlFile(p) {
|
|
958
1055
|
try {
|
|
959
|
-
const { mtimeMs } =
|
|
1056
|
+
const { mtimeMs } = fs8.statSync(p);
|
|
960
1057
|
const hit = _yamlCache.get(p, mtimeMs);
|
|
961
1058
|
if (hit !== void 0)
|
|
962
1059
|
return hit;
|
|
963
|
-
const parsed =
|
|
1060
|
+
const parsed = YAML5.parse(fs8.readFileSync(p, "utf-8"));
|
|
964
1061
|
return _yamlCache.set(p, mtimeMs, parsed && typeof parsed === "object" ? parsed : null);
|
|
965
1062
|
} catch {
|
|
966
1063
|
return null;
|
|
@@ -1015,8 +1112,8 @@ function providerSections() {
|
|
|
1015
1112
|
return sections;
|
|
1016
1113
|
}
|
|
1017
1114
|
function buildSkillSection(skillsDir, skillId) {
|
|
1018
|
-
const configPath =
|
|
1019
|
-
if (!
|
|
1115
|
+
const configPath = path11.join(skillsDir, skillId, "config.yaml");
|
|
1116
|
+
if (!fs8.existsSync(configPath))
|
|
1020
1117
|
return null;
|
|
1021
1118
|
const parsed = readYamlFile(configPath);
|
|
1022
1119
|
if (!parsed)
|
|
@@ -1027,11 +1124,11 @@ function buildSkillSection(skillsDir, skillId) {
|
|
|
1027
1124
|
return null;
|
|
1028
1125
|
let displayName = skillId;
|
|
1029
1126
|
try {
|
|
1030
|
-
const mdPath =
|
|
1031
|
-
const { mtimeMs } =
|
|
1127
|
+
const mdPath = path11.join(skillsDir, skillId, "SKILL.md");
|
|
1128
|
+
const { mtimeMs } = fs8.statSync(mdPath);
|
|
1032
1129
|
let name = _skillNameCache.get(mdPath, mtimeMs);
|
|
1033
1130
|
if (name === void 0) {
|
|
1034
|
-
const m =
|
|
1131
|
+
const m = fs8.readFileSync(mdPath, "utf-8").match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1035
1132
|
const nameMatch = m?.[1].match(/^name:\s*(.+)$/m);
|
|
1036
1133
|
name = _skillNameCache.set(mdPath, mtimeMs, nameMatch ? nameMatch[1].trim() : null);
|
|
1037
1134
|
}
|
|
@@ -1052,7 +1149,7 @@ function buildSkillSection(skillsDir, skillId) {
|
|
|
1052
1149
|
function skillSections(workspaceRoot) {
|
|
1053
1150
|
const byId = /* @__PURE__ */ new Map();
|
|
1054
1151
|
for (const { id, dir } of listSkillDirs(workspaceRoot).reverse()) {
|
|
1055
|
-
const section = buildSkillSection(
|
|
1152
|
+
const section = buildSkillSection(path11.dirname(dir), id);
|
|
1056
1153
|
if (section)
|
|
1057
1154
|
byId.set(section.namespaceId, section);
|
|
1058
1155
|
}
|
|
@@ -1062,7 +1159,7 @@ function agentSections() {
|
|
|
1062
1159
|
const sections = [];
|
|
1063
1160
|
let entries = [];
|
|
1064
1161
|
try {
|
|
1065
|
-
entries =
|
|
1162
|
+
entries = fs8.readdirSync(GLOBAL_AGENTS_DIR, { withFileTypes: true });
|
|
1066
1163
|
} catch {
|
|
1067
1164
|
return sections;
|
|
1068
1165
|
}
|
|
@@ -1070,8 +1167,8 @@ function agentSections() {
|
|
|
1070
1167
|
if (!entry.isDirectory())
|
|
1071
1168
|
continue;
|
|
1072
1169
|
const agentId = entry.name;
|
|
1073
|
-
const configPath =
|
|
1074
|
-
if (!
|
|
1170
|
+
const configPath = path11.join(GLOBAL_AGENTS_DIR, agentId, "agent-config.yaml");
|
|
1171
|
+
if (!fs8.existsSync(configPath))
|
|
1075
1172
|
continue;
|
|
1076
1173
|
const parsed = readYamlFile(configPath);
|
|
1077
1174
|
if (!parsed)
|
|
@@ -1082,7 +1179,7 @@ function agentSections() {
|
|
|
1082
1179
|
continue;
|
|
1083
1180
|
let displayName = agentId;
|
|
1084
1181
|
try {
|
|
1085
|
-
const ay = readYamlFile(
|
|
1182
|
+
const ay = readYamlFile(path11.join(GLOBAL_AGENTS_DIR, agentId, "agent.yaml"));
|
|
1086
1183
|
if (ay && typeof ay.name === "string")
|
|
1087
1184
|
displayName = ay.name;
|
|
1088
1185
|
} catch {
|
|
@@ -1145,23 +1242,15 @@ function extensionSections() {
|
|
|
1145
1242
|
}
|
|
1146
1243
|
|
|
1147
1244
|
// ../server/dist/config.js
|
|
1148
|
-
var HALO_HOME2 =
|
|
1149
|
-
var HALO_GLOBAL_DIR =
|
|
1150
|
-
var HALO_SECRETS_DIR =
|
|
1151
|
-
|
|
1152
|
-
try {
|
|
1153
|
-
const raw = fs8.readFileSync(filePath, "utf-8");
|
|
1154
|
-
return YAML5.parse(raw) ?? {};
|
|
1155
|
-
} catch {
|
|
1156
|
-
return {};
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
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");
|
|
1160
1249
|
var _systemConfigCache = null;
|
|
1161
1250
|
function getSystemConfig() {
|
|
1162
1251
|
let mtimeMs = 0;
|
|
1163
1252
|
try {
|
|
1164
|
-
mtimeMs =
|
|
1253
|
+
mtimeMs = fs9.statSync(SYSTEM_CONFIG_PATH).mtimeMs;
|
|
1165
1254
|
} catch {
|
|
1166
1255
|
}
|
|
1167
1256
|
if (!_systemConfigCache || _systemConfigCache.mtimeMs !== mtimeMs) {
|
|
@@ -1169,19 +1258,6 @@ function getSystemConfig() {
|
|
|
1169
1258
|
}
|
|
1170
1259
|
return _systemConfigCache.data;
|
|
1171
1260
|
}
|
|
1172
|
-
var SETTINGS_PATH = path11.join(HALO_SECRETS_DIR, "settings.yaml");
|
|
1173
|
-
var _settingsCache = null;
|
|
1174
|
-
function getSettings() {
|
|
1175
|
-
let mtimeMs = 0;
|
|
1176
|
-
try {
|
|
1177
|
-
mtimeMs = fs8.statSync(SETTINGS_PATH).mtimeMs;
|
|
1178
|
-
} catch {
|
|
1179
|
-
}
|
|
1180
|
-
if (!_settingsCache || _settingsCache.mtimeMs !== mtimeMs) {
|
|
1181
|
-
_settingsCache = { mtimeMs, data: loadYamlFile(SETTINGS_PATH) };
|
|
1182
|
-
}
|
|
1183
|
-
return _settingsCache.data;
|
|
1184
|
-
}
|
|
1185
1261
|
function readYamlValue(tree, dotPath) {
|
|
1186
1262
|
const parts = dotPath.split(".");
|
|
1187
1263
|
let cur = tree;
|
|
@@ -1310,10 +1386,10 @@ function systemString(envKey, configPath, fallback) {
|
|
|
1310
1386
|
}
|
|
1311
1387
|
var _wsSettingsCache = /* @__PURE__ */ new Map();
|
|
1312
1388
|
function getWorkspaceSettings(workspaceRoot) {
|
|
1313
|
-
const p =
|
|
1389
|
+
const p = path12.join(workspaceRoot, ".halo", "settings.yaml");
|
|
1314
1390
|
let mtimeMs = 0;
|
|
1315
1391
|
try {
|
|
1316
|
-
mtimeMs =
|
|
1392
|
+
mtimeMs = fs9.statSync(p).mtimeMs;
|
|
1317
1393
|
} catch {
|
|
1318
1394
|
}
|
|
1319
1395
|
const hit = _wsSettingsCache.get(workspaceRoot);
|
|
@@ -1520,7 +1596,7 @@ var config = {
|
|
|
1520
1596
|
a2a: {
|
|
1521
1597
|
/** Base dir workspaces are exposed relative to: env HALO_A2A_ROOT, else the user's home. */
|
|
1522
1598
|
get root() {
|
|
1523
|
-
return process.env.HALO_A2A_ROOT?.trim() ||
|
|
1599
|
+
return process.env.HALO_A2A_ROOT?.trim() || homedir6();
|
|
1524
1600
|
},
|
|
1525
1601
|
/** Server origin as peers see it (no trailing slash); '' = derive from the request. */
|
|
1526
1602
|
get publicUrl() {
|
|
@@ -1565,21 +1641,21 @@ var config = {
|
|
|
1565
1641
|
};
|
|
1566
1642
|
|
|
1567
1643
|
// ../server/dist/evolution/auto-apply.js
|
|
1568
|
-
import
|
|
1569
|
-
import
|
|
1644
|
+
import fs10 from "node:fs";
|
|
1645
|
+
import path13 from "node:path";
|
|
1570
1646
|
import YAML6 from "yaml";
|
|
1571
1647
|
var AUTO_APPLY_FILE = "auto-apply.json";
|
|
1572
1648
|
var OVERRIDE_DIRS = /* @__PURE__ */ new Set(["agents", "skills", "prompts"]);
|
|
1573
1649
|
function isDir(p) {
|
|
1574
1650
|
try {
|
|
1575
|
-
return
|
|
1651
|
+
return fs10.statSync(p).isDirectory();
|
|
1576
1652
|
} catch {
|
|
1577
1653
|
return false;
|
|
1578
1654
|
}
|
|
1579
1655
|
}
|
|
1580
1656
|
function readOrNull(p) {
|
|
1581
1657
|
try {
|
|
1582
|
-
return
|
|
1658
|
+
return fs10.readFileSync(p);
|
|
1583
1659
|
} catch {
|
|
1584
1660
|
return null;
|
|
1585
1661
|
}
|
|
@@ -1587,33 +1663,33 @@ function readOrNull(p) {
|
|
|
1587
1663
|
function walkFiles(root, rel, out = []) {
|
|
1588
1664
|
let st;
|
|
1589
1665
|
try {
|
|
1590
|
-
st =
|
|
1666
|
+
st = fs10.statSync(path13.join(root, rel));
|
|
1591
1667
|
} catch {
|
|
1592
1668
|
return out;
|
|
1593
1669
|
}
|
|
1594
1670
|
if (st.isFile())
|
|
1595
1671
|
out.push(rel);
|
|
1596
1672
|
else if (st.isDirectory()) {
|
|
1597
|
-
for (const e of
|
|
1598
|
-
walkFiles(root,
|
|
1673
|
+
for (const e of fs10.readdirSync(path13.join(root, rel)))
|
|
1674
|
+
walkFiles(root, path13.join(rel, e), out);
|
|
1599
1675
|
}
|
|
1600
1676
|
return out;
|
|
1601
1677
|
}
|
|
1602
|
-
var toPosix = (rel) => rel.split(
|
|
1678
|
+
var toPosix = (rel) => rel.split(path13.sep).join("/");
|
|
1603
1679
|
function isInternalAgentDir(dir) {
|
|
1604
|
-
const raw = readOrNull(
|
|
1680
|
+
const raw = readOrNull(path13.join(dir, "agent.yaml"));
|
|
1605
1681
|
return raw !== null && parseYamlLoose(raw.toString("utf-8"))?.internal === true;
|
|
1606
1682
|
}
|
|
1607
1683
|
function liveBaseline(dirs, rel) {
|
|
1608
|
-
const parts = rel.split(
|
|
1684
|
+
const parts = rel.split(path13.sep);
|
|
1609
1685
|
if (!OVERRIDE_DIRS.has(parts[0]) || parts.length < 3)
|
|
1610
|
-
return { file:
|
|
1611
|
-
const dirRel =
|
|
1612
|
-
const internal = (root) => parts[0] === "agents" && isInternalAgentDir(
|
|
1613
|
-
if (isDir(
|
|
1614
|
-
return { file:
|
|
1615
|
-
if (isDir(
|
|
1616
|
-
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) };
|
|
1617
1693
|
return { file: null, newDir: true };
|
|
1618
1694
|
}
|
|
1619
1695
|
function fileChange(rel, base, after) {
|
|
@@ -1635,15 +1711,15 @@ function publishChanges(dirs, files) {
|
|
|
1635
1711
|
const base = liveBaseline(dirs, rel);
|
|
1636
1712
|
if (base.globalDir)
|
|
1637
1713
|
globalBacked.add(base.globalDir);
|
|
1638
|
-
const c = fileChange(rel, base,
|
|
1714
|
+
const c = fileChange(rel, base, fs10.readFileSync(full));
|
|
1639
1715
|
if (c)
|
|
1640
1716
|
out.push(c);
|
|
1641
1717
|
}
|
|
1642
1718
|
for (const dirRel of globalBacked) {
|
|
1643
1719
|
for (const rel of walkFiles(dirs.globalHalo, dirRel)) {
|
|
1644
|
-
if (
|
|
1720
|
+
if (fs10.existsSync(path13.join(dirs.sandboxHalo, rel)))
|
|
1645
1721
|
continue;
|
|
1646
|
-
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 });
|
|
1647
1723
|
}
|
|
1648
1724
|
}
|
|
1649
1725
|
return out;
|
|
@@ -1658,21 +1734,21 @@ function collectChanges(dirs) {
|
|
|
1658
1734
|
const base = liveBaseline(dirs, rel);
|
|
1659
1735
|
if (base.globalDir)
|
|
1660
1736
|
globalBacked.add(base.globalDir);
|
|
1661
|
-
const c = fileChange(rel, base,
|
|
1737
|
+
const c = fileChange(rel, base, fs10.readFileSync(path13.join(dirs.sandboxHalo, rel)));
|
|
1662
1738
|
if (c)
|
|
1663
1739
|
out.push(c);
|
|
1664
1740
|
}
|
|
1665
1741
|
for (const rel of walkFiles(dirs.mainHalo, top)) {
|
|
1666
1742
|
if (seen.has(rel))
|
|
1667
1743
|
continue;
|
|
1668
|
-
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 });
|
|
1669
1745
|
}
|
|
1670
1746
|
}
|
|
1671
1747
|
for (const dirRel of globalBacked) {
|
|
1672
1748
|
for (const rel of walkFiles(dirs.globalHalo, dirRel)) {
|
|
1673
1749
|
if (seen.has(rel))
|
|
1674
1750
|
continue;
|
|
1675
|
-
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 });
|
|
1676
1752
|
}
|
|
1677
1753
|
}
|
|
1678
1754
|
return out;
|
|
@@ -1884,7 +1960,7 @@ function parseScore(raw) {
|
|
|
1884
1960
|
}
|
|
1885
1961
|
function readScore(file) {
|
|
1886
1962
|
try {
|
|
1887
|
-
return parseScore(JSON.parse(
|
|
1963
|
+
return parseScore(JSON.parse(fs10.readFileSync(file, "utf-8")));
|
|
1888
1964
|
} catch {
|
|
1889
1965
|
return null;
|
|
1890
1966
|
}
|
|
@@ -1963,7 +2039,7 @@ function extractLargerObservation(md) {
|
|
|
1963
2039
|
}
|
|
1964
2040
|
function readObservation(file) {
|
|
1965
2041
|
try {
|
|
1966
|
-
return extractLargerObservation(
|
|
2042
|
+
return extractLargerObservation(fs10.readFileSync(file, "utf-8"));
|
|
1967
2043
|
} catch {
|
|
1968
2044
|
return null;
|
|
1969
2045
|
}
|
|
@@ -2025,11 +2101,11 @@ function failApply(db, applyId, input) {
|
|
|
2025
2101
|
}
|
|
2026
2102
|
|
|
2027
2103
|
// ../server/dist/evolution/rollback.js
|
|
2028
|
-
import
|
|
2104
|
+
import fs11 from "node:fs";
|
|
2029
2105
|
import { createHash } from "node:crypto";
|
|
2030
2106
|
import { and as and2, eq as eq2, inArray } from "drizzle-orm";
|
|
2031
2107
|
function sha256File(file) {
|
|
2032
|
-
return createHash("sha256").update(
|
|
2108
|
+
return createHash("sha256").update(fs11.readFileSync(file)).digest("hex");
|
|
2033
2109
|
}
|
|
2034
2110
|
|
|
2035
2111
|
// ../server/dist/evolution/evo-wrapper.js
|
|
@@ -2091,9 +2167,9 @@ function spawnProc(bin, args, logFd, teePath, stdinInput, timeoutSec) {
|
|
|
2091
2167
|
}
|
|
2092
2168
|
writeLog(logFd, `[wrapper] $ ${bin} ${loggedArgs.join(" ")}${stdinInput === void 0 ? " <last-arg-omitted>" : " <prompt-on-stdin>"}${timeoutSec ? ` (timeout ${timeoutSec}s)` : ""}
|
|
2093
2169
|
`);
|
|
2094
|
-
const tee = teePath ?
|
|
2170
|
+
const tee = teePath ? fs12.createWriteStream(teePath, { flags: "a" }) : null;
|
|
2095
2171
|
const [spawnBin, spawnArgs] = process.platform === "win32" && bin.endsWith(".cmd") ? ["cmd.exe", ["/c", bin, ...args]] : [bin, args];
|
|
2096
|
-
return new Promise((
|
|
2172
|
+
return new Promise((resolve2) => {
|
|
2097
2173
|
const child = spawn2(spawnBin, spawnArgs, {
|
|
2098
2174
|
// Pipe stdin only when we have input to write — the cli reads a prompt
|
|
2099
2175
|
// from stdin when it's not a TTY, which is how long briefs are delivered
|
|
@@ -2165,7 +2241,7 @@ function spawnProc(bin, args, logFd, teePath, stdinInput, timeoutSec) {
|
|
|
2165
2241
|
tee?.end();
|
|
2166
2242
|
if (stopping)
|
|
2167
2243
|
return;
|
|
2168
|
-
|
|
2244
|
+
resolve2({ exitCode, stdout, stderr });
|
|
2169
2245
|
});
|
|
2170
2246
|
child.on("error", (err) => {
|
|
2171
2247
|
liveProcs.delete(killTree);
|
|
@@ -2181,17 +2257,17 @@ function spawnProc(bin, args, logFd, teePath, stdinInput, timeoutSec) {
|
|
|
2181
2257
|
tee?.end();
|
|
2182
2258
|
if (stopping)
|
|
2183
2259
|
return;
|
|
2184
|
-
|
|
2260
|
+
resolve2({ exitCode: 1, stdout, stderr: stderr + `
|
|
2185
2261
|
[spawn error] ${err.message}` });
|
|
2186
2262
|
});
|
|
2187
2263
|
});
|
|
2188
2264
|
}
|
|
2189
2265
|
function subCliLogPath(runDir) {
|
|
2190
|
-
return
|
|
2266
|
+
return path14.join(runDir, "sub-cli.log");
|
|
2191
2267
|
}
|
|
2192
2268
|
function appendSubCliHeader(runDir, header) {
|
|
2193
2269
|
try {
|
|
2194
|
-
|
|
2270
|
+
fs12.appendFileSync(subCliLogPath(runDir), `
|
|
2195
2271
|
=== ${(/* @__PURE__ */ new Date()).toISOString()} ${header} ===
|
|
2196
2272
|
`);
|
|
2197
2273
|
} catch {
|
|
@@ -2225,10 +2301,10 @@ function buildLanguageClause(langHint, scope) {
|
|
|
2225
2301
|
];
|
|
2226
2302
|
}
|
|
2227
2303
|
function readPatchFrontmatter(runDir) {
|
|
2228
|
-
const patchPath =
|
|
2304
|
+
const patchPath = path14.join(runDir, "patch.md");
|
|
2229
2305
|
let raw;
|
|
2230
2306
|
try {
|
|
2231
|
-
raw =
|
|
2307
|
+
raw = fs12.readFileSync(patchPath, "utf-8");
|
|
2232
2308
|
} catch {
|
|
2233
2309
|
return null;
|
|
2234
2310
|
}
|
|
@@ -2258,14 +2334,14 @@ function extractTestScenario(fm) {
|
|
|
2258
2334
|
return { agentId: o.agentId, testMessage, originalMessage };
|
|
2259
2335
|
}
|
|
2260
2336
|
function readEvoContext(runDir, logFd) {
|
|
2261
|
-
const p =
|
|
2262
|
-
if (!
|
|
2337
|
+
const p = path14.join(runDir, "evo-context.json");
|
|
2338
|
+
if (!fs12.existsSync(p)) {
|
|
2263
2339
|
writeLog(logFd, `[brief] evo-context.json missing at ${p}
|
|
2264
2340
|
`);
|
|
2265
2341
|
return null;
|
|
2266
2342
|
}
|
|
2267
2343
|
try {
|
|
2268
|
-
return JSON.parse(
|
|
2344
|
+
return JSON.parse(fs12.readFileSync(p, "utf-8"));
|
|
2269
2345
|
} catch (err) {
|
|
2270
2346
|
writeLog(logFd, `[brief] evo-context.json parse failed: ${err instanceof Error ? err.message : String(err)}
|
|
2271
2347
|
`);
|
|
@@ -2273,12 +2349,12 @@ function readEvoContext(runDir, logFd) {
|
|
|
2273
2349
|
}
|
|
2274
2350
|
}
|
|
2275
2351
|
function dumpSnapshotImages(runDir, logFd) {
|
|
2276
|
-
const snapshotPath =
|
|
2277
|
-
if (!
|
|
2352
|
+
const snapshotPath = path14.join(runDir, "source-snapshot.json");
|
|
2353
|
+
if (!fs12.existsSync(snapshotPath))
|
|
2278
2354
|
return [];
|
|
2279
2355
|
let parsed;
|
|
2280
2356
|
try {
|
|
2281
|
-
parsed = JSON.parse(
|
|
2357
|
+
parsed = JSON.parse(fs12.readFileSync(snapshotPath, "utf-8"));
|
|
2282
2358
|
} catch {
|
|
2283
2359
|
writeLog(logFd, `[dumpSnapshotImages] snapshot parse failed
|
|
2284
2360
|
`);
|
|
@@ -2287,8 +2363,8 @@ function dumpSnapshotImages(runDir, logFd) {
|
|
|
2287
2363
|
const messages = Array.isArray(parsed.rawMessages) ? parsed.rawMessages : [];
|
|
2288
2364
|
if (messages.length === 0)
|
|
2289
2365
|
return [];
|
|
2290
|
-
const imagesDir =
|
|
2291
|
-
|
|
2366
|
+
const imagesDir = path14.join(runDir, "images");
|
|
2367
|
+
fs12.mkdirSync(imagesDir, { recursive: true });
|
|
2292
2368
|
const out = [];
|
|
2293
2369
|
const extByMime = {
|
|
2294
2370
|
"image/png": "png",
|
|
@@ -2327,10 +2403,10 @@ function dumpSnapshotImages(runDir, logFd) {
|
|
|
2327
2403
|
continue;
|
|
2328
2404
|
const ext = extByMime[mediaType] ?? "bin";
|
|
2329
2405
|
const fileName = c.subBlockIdx == null ? `${m}-${b}.${ext}` : `${m}-${b}-${c.subBlockIdx}.${ext}`;
|
|
2330
|
-
const fullPath =
|
|
2406
|
+
const fullPath = path14.join(imagesDir, fileName);
|
|
2331
2407
|
try {
|
|
2332
2408
|
const buf = Buffer.from(data, "base64");
|
|
2333
|
-
|
|
2409
|
+
fs12.writeFileSync(fullPath, buf);
|
|
2334
2410
|
out.push({
|
|
2335
2411
|
relPath: `images/${fileName}`,
|
|
2336
2412
|
msgIdx: m,
|
|
@@ -2417,15 +2493,15 @@ function renderEvoContextSections(ctx) {
|
|
|
2417
2493
|
lines.push("");
|
|
2418
2494
|
}
|
|
2419
2495
|
if (ctx.skills.length > 0) {
|
|
2420
|
-
const globalSkillsDir =
|
|
2496
|
+
const globalSkillsDir = path14.join(homedir7(), ".halo", "global", "skills");
|
|
2421
2497
|
lines.push("=== Skills in this workspace ===");
|
|
2422
2498
|
for (const s of ctx.skills)
|
|
2423
2499
|
lines.push(` ${s.id} [${s.scope}]`);
|
|
2424
2500
|
lines.push("");
|
|
2425
2501
|
lines.push("To read a skill's content, file_read its SKILL.md (you have full");
|
|
2426
2502
|
lines.push("file access \u2014 absolute paths resolve):");
|
|
2427
|
-
lines.push(` [workspace] \u2192 ${
|
|
2428
|
-
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")}`);
|
|
2429
2505
|
lines.push("A [global]/[builtin] skill is NOT inside your sandbox \u2014 read it");
|
|
2430
2506
|
lines.push(`from ${globalSkillsDir} directly; don't glob the sandbox for it.`);
|
|
2431
2507
|
lines.push("Sibling resource files live next to SKILL.md in that same dir.");
|
|
@@ -2448,13 +2524,13 @@ function buildDraftBrief(args) {
|
|
|
2448
2524
|
];
|
|
2449
2525
|
if (args.userHint)
|
|
2450
2526
|
lines.push(`Reviewer hint: ${args.userHint}`);
|
|
2451
|
-
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)"));
|
|
2452
2528
|
return lines.join("\n");
|
|
2453
2529
|
}
|
|
2454
2530
|
function buildFixBrief(args) {
|
|
2455
2531
|
let failLogText = "";
|
|
2456
2532
|
try {
|
|
2457
|
-
failLogText =
|
|
2533
|
+
failLogText = fs12.readFileSync(args.failLogPath, "utf-8");
|
|
2458
2534
|
} catch {
|
|
2459
2535
|
failLogText = `(failed to read ${args.failLogPath})`;
|
|
2460
2536
|
}
|
|
@@ -2469,7 +2545,7 @@ function buildFixBrief(args) {
|
|
|
2469
2545
|
`Fix attempt: ${args.attempt}/${args.maxAttempts}`,
|
|
2470
2546
|
"",
|
|
2471
2547
|
`An earlier draft pass produced patch.md + a sandbox at`,
|
|
2472
|
-
`${
|
|
2548
|
+
`${path14.join(args.runDir, "patch.md")} and`,
|
|
2473
2549
|
`${evoSandboxHaloDir(args.runDir)} respectively.`,
|
|
2474
2550
|
"The dry-run failed; the wrapper's failure log:",
|
|
2475
2551
|
"",
|
|
@@ -2490,16 +2566,16 @@ function buildFixBrief(args) {
|
|
|
2490
2566
|
function buildScoreBrief(args) {
|
|
2491
2567
|
function readOrEmpty(p, label) {
|
|
2492
2568
|
try {
|
|
2493
|
-
return
|
|
2569
|
+
return fs12.readFileSync(p, "utf-8");
|
|
2494
2570
|
} catch (err) {
|
|
2495
2571
|
writeLog(args.logFd, `[buildScoreBrief] ${label} read failed: ${err instanceof Error ? err.message : String(err)}
|
|
2496
2572
|
`);
|
|
2497
2573
|
return `(${label} unavailable)`;
|
|
2498
2574
|
}
|
|
2499
2575
|
}
|
|
2500
|
-
const patchMd = readOrEmpty(
|
|
2501
|
-
const dryRun = readOrEmpty(
|
|
2502
|
-
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");
|
|
2503
2579
|
return [
|
|
2504
2580
|
"You are running as the Evolution scorer.",
|
|
2505
2581
|
"",
|
|
@@ -2510,8 +2586,8 @@ function buildScoreBrief(args) {
|
|
|
2510
2586
|
"write score.json with the absolute path given below)",
|
|
2511
2587
|
"",
|
|
2512
2588
|
`The original conversation lives on disk:`,
|
|
2513
|
-
` ${
|
|
2514
|
-
` ${
|
|
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`,
|
|
2515
2591
|
"Read tool-flow.md first to find the user turn matching",
|
|
2516
2592
|
"`testScenario.originalMessage` and the assistant reply that followed",
|
|
2517
2593
|
"it. Fall back to source-snapshot.json only when the full tool result",
|
|
@@ -2561,7 +2637,7 @@ function buildScoreBrief(args) {
|
|
|
2561
2637
|
"find the assistant turn that followed it in tool-flow.md (or",
|
|
2562
2638
|
"source-snapshot.json) \u2014 that's your baseline. Compare baseline to",
|
|
2563
2639
|
"dry-run-output.txt, rate lint / behavior / scope, write",
|
|
2564
|
-
`${
|
|
2640
|
+
`${path14.join(args.runDir, "score.json")} with the shape AGENT.md describes.`,
|
|
2565
2641
|
"",
|
|
2566
2642
|
...buildLanguageClause(args.langHint, 'the score.json "notes" field'),
|
|
2567
2643
|
'The numeric fields and "confidence" enum stay in their canonical form.'
|
|
@@ -2604,14 +2680,14 @@ async function phaseDraft(args) {
|
|
|
2604
2680
|
return outcome;
|
|
2605
2681
|
}
|
|
2606
2682
|
function judgeDraft(runDir, workspacePath, exitCode) {
|
|
2607
|
-
if (
|
|
2683
|
+
if (fs12.existsSync(path14.join(runDir, ".skip.md"))) {
|
|
2608
2684
|
return { outcome: "skipped", why: ".skip.md present \u2014 evo declared skip" };
|
|
2609
2685
|
}
|
|
2610
2686
|
if (exitCode !== 0) {
|
|
2611
2687
|
const timeout = exitCode === 124 ? " (timeout)" : "";
|
|
2612
2688
|
return { outcome: "failed", why: `drafter exited ${exitCode}${timeout} \u2014 draft interrupted, not trusted` };
|
|
2613
2689
|
}
|
|
2614
|
-
if (!
|
|
2690
|
+
if (!fs12.existsSync(path14.join(runDir, "patch.md"))) {
|
|
2615
2691
|
return { outcome: "failed", why: "patch.md missing" };
|
|
2616
2692
|
}
|
|
2617
2693
|
const target = readPatchFrontmatter(runDir)?.target;
|
|
@@ -2619,16 +2695,16 @@ function judgeDraft(runDir, workspacePath, exitCode) {
|
|
|
2619
2695
|
return { outcome: "drafted", why: "patch.md present, no target in frontmatter \u2014 target file not checked" };
|
|
2620
2696
|
}
|
|
2621
2697
|
const sandboxDir = evoSandboxDir(runDir);
|
|
2622
|
-
const rel =
|
|
2623
|
-
if (!rel.startsWith(".halo" +
|
|
2698
|
+
const rel = path14.relative(sandboxDir, path14.resolve(sandboxDir, target));
|
|
2699
|
+
if (!rel.startsWith(".halo" + path14.sep)) {
|
|
2624
2700
|
return { outcome: "failed", why: `target ${JSON.stringify(target)} does not resolve under sandbox/.halo/` };
|
|
2625
2701
|
}
|
|
2626
|
-
const sandboxFile =
|
|
2627
|
-
if (!
|
|
2702
|
+
const sandboxFile = path14.join(sandboxDir, rel);
|
|
2703
|
+
if (!fs12.statSync(sandboxFile, { throwIfNoEntry: false })?.isFile()) {
|
|
2628
2704
|
return { outcome: "failed", why: `target not written to sandbox: ${target}` };
|
|
2629
2705
|
}
|
|
2630
|
-
const wsFile =
|
|
2631
|
-
if (
|
|
2706
|
+
const wsFile = path14.join(workspacePath, rel);
|
|
2707
|
+
if (fs12.statSync(wsFile, { throwIfNoEntry: false })?.isFile() && fs12.readFileSync(wsFile).equals(fs12.readFileSync(sandboxFile))) {
|
|
2632
2708
|
return { outcome: "failed", why: `target unchanged from workspace: ${target}` };
|
|
2633
2709
|
}
|
|
2634
2710
|
return { outcome: "drafted", why: `patch.md + target ${target} present` };
|
|
@@ -2655,15 +2731,15 @@ async function phaseDryRun(args) {
|
|
|
2655
2731
|
}
|
|
2656
2732
|
async function tryDryRun(args, attemptIndex) {
|
|
2657
2733
|
const sandboxDir = evoSandboxDir(args.runDir);
|
|
2658
|
-
const outputPath =
|
|
2659
|
-
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`);
|
|
2660
2736
|
const cli = resolveHaloCli();
|
|
2661
2737
|
const fm = readPatchFrontmatter(args.runDir);
|
|
2662
2738
|
const scenario = extractTestScenario(fm);
|
|
2663
2739
|
if (!scenario) {
|
|
2664
2740
|
writeLog(args.logFd, `[tryDryRun:${attemptIndex}] patch.md frontmatter missing testScenario.{agentId, testMessage}
|
|
2665
2741
|
`);
|
|
2666
|
-
|
|
2742
|
+
fs12.writeFileSync(failLogPath, "patch.md frontmatter missing testScenario.{agentId, testMessage} \u2014 wrapper cannot run dry-run without it.\n", "utf-8");
|
|
2667
2743
|
return { kind: "no-scenario", failLogPath };
|
|
2668
2744
|
}
|
|
2669
2745
|
writeLog(args.logFd, `[tryDryRun:${attemptIndex}] agent=${scenario.agentId} testMessage=${JSON.stringify(scenario.testMessage).slice(0, 200)}
|
|
@@ -2681,14 +2757,14 @@ async function tryDryRun(args, attemptIndex) {
|
|
|
2681
2757
|
const timedOut = result.exitCode === 124;
|
|
2682
2758
|
const ok = result.exitCode === 0 && result.stdout.trim().length > 0;
|
|
2683
2759
|
if (ok) {
|
|
2684
|
-
|
|
2760
|
+
fs12.writeFileSync(outputPath, result.stdout, "utf-8");
|
|
2685
2761
|
writeLog(args.logFd, `[tryDryRun:${attemptIndex}] success: wrote ${outputPath} (${result.stdout.length}B)
|
|
2686
2762
|
`);
|
|
2687
2763
|
return { kind: "ok" };
|
|
2688
2764
|
}
|
|
2689
2765
|
const stdoutTail = result.stdout.length > 4e3 ? "\u2026" + result.stdout.slice(-4e3) : result.stdout;
|
|
2690
2766
|
const stderrTail = result.stderr.length > 4e3 ? "\u2026" + result.stderr.slice(-4e3) : result.stderr;
|
|
2691
|
-
|
|
2767
|
+
fs12.writeFileSync(failLogPath, [
|
|
2692
2768
|
`Wrapper command: halo cli -a ${scenario.agentId} -n -w ${sandboxDir} <testMessage>`,
|
|
2693
2769
|
`Test message: ${scenario.testMessage}`,
|
|
2694
2770
|
`Exit code: ${result.exitCode}${timedOut ? " (timeout)" : ""}`,
|
|
@@ -2744,7 +2820,7 @@ async function phaseScore(args) {
|
|
|
2744
2820
|
const sandboxWs = evoSandboxDir(args.runDir);
|
|
2745
2821
|
buildEvoSandbox(args.workspacePath, args.runDir, args.logFd);
|
|
2746
2822
|
const cliArgs = ["cli", "-a", "__score__", "-n", "-w", sandboxWs];
|
|
2747
|
-
|
|
2823
|
+
fs12.rmSync(path14.join(args.runDir, "score.json"), { force: true });
|
|
2748
2824
|
appendSubCliHeader(args.runDir, "Phase C: __score__");
|
|
2749
2825
|
const result = await spawnProc(
|
|
2750
2826
|
resolveHaloCli(),
|
|
@@ -2765,7 +2841,7 @@ async function phaseScore(args) {
|
|
|
2765
2841
|
writeLog(args.logFd, `[phaseScore] stdout (last 1KB): ${result.stdout.slice(-1e3)}
|
|
2766
2842
|
`);
|
|
2767
2843
|
}
|
|
2768
|
-
const hasScore =
|
|
2844
|
+
const hasScore = fs12.existsSync(path14.join(args.runDir, "score.json"));
|
|
2769
2845
|
writeLog(args.logFd, `[phaseScore] score.json=${hasScore}
|
|
2770
2846
|
`);
|
|
2771
2847
|
return hasScore;
|
|
@@ -2779,7 +2855,7 @@ var RUN_INPUT_FILES = /* @__PURE__ */ new Set([
|
|
|
2779
2855
|
function resetRunArtifacts(runDir, logFd) {
|
|
2780
2856
|
let entries;
|
|
2781
2857
|
try {
|
|
2782
|
-
entries =
|
|
2858
|
+
entries = fs12.readdirSync(runDir);
|
|
2783
2859
|
} catch {
|
|
2784
2860
|
return;
|
|
2785
2861
|
}
|
|
@@ -2787,7 +2863,7 @@ function resetRunArtifacts(runDir, logFd) {
|
|
|
2787
2863
|
if (RUN_INPUT_FILES.has(entry))
|
|
2788
2864
|
continue;
|
|
2789
2865
|
try {
|
|
2790
|
-
|
|
2866
|
+
fs12.rmSync(path14.join(runDir, entry), { recursive: true, force: true });
|
|
2791
2867
|
} catch (err) {
|
|
2792
2868
|
writeLog(logFd, `[resetRunArtifacts] failed to remove ${entry}: ${err instanceof Error ? err.message : String(err)}
|
|
2793
2869
|
`);
|
|
@@ -2797,7 +2873,7 @@ function resetRunArtifacts(runDir, logFd) {
|
|
|
2797
2873
|
async function runMode(row, logFd) {
|
|
2798
2874
|
const { id } = row;
|
|
2799
2875
|
const runDir = wsEvoRunDir(row.workspacePath, id);
|
|
2800
|
-
|
|
2876
|
+
fs12.mkdirSync(runDir, { recursive: true });
|
|
2801
2877
|
resetRunArtifacts(runDir, logFd);
|
|
2802
2878
|
const hb = setInterval(() => heartbeat("run", id, row.attempts, logFd), 6e4);
|
|
2803
2879
|
const langHint = resolveLangHint();
|
|
@@ -2841,7 +2917,7 @@ async function runMode(row, logFd) {
|
|
|
2841
2917
|
}
|
|
2842
2918
|
}
|
|
2843
2919
|
function markObservation(id, attempts, runDir, outcome, logFd) {
|
|
2844
|
-
const has = outcome !== "failed" && readObservation(
|
|
2920
|
+
const has = outcome !== "failed" && readObservation(path14.join(runDir, outcome === "skipped" ? ".skip.md" : "patch.md")) !== null;
|
|
2845
2921
|
getEvoDb().update(evolutionRuns).set({ hasObservation: has }).where(ownsRun(id, attempts)).run();
|
|
2846
2922
|
if (has)
|
|
2847
2923
|
writeLog(logFd, `[wrapper] draft carries a Larger observation
|
|
@@ -2861,10 +2937,10 @@ function maybeAutoApply(args) {
|
|
|
2861
2937
|
level,
|
|
2862
2938
|
threshold: autoApplyThresholdFor(args.workspacePath),
|
|
2863
2939
|
sourceAccess: args.sourceAccess,
|
|
2864
|
-
score: readScore(
|
|
2865
|
-
observation: readObservation(
|
|
2940
|
+
score: readScore(path14.join(args.runDir, "score.json")),
|
|
2941
|
+
observation: readObservation(path14.join(args.runDir, "patch.md"))
|
|
2866
2942
|
});
|
|
2867
|
-
|
|
2943
|
+
fs12.writeFileSync(path14.join(args.runDir, AUTO_APPLY_FILE), JSON.stringify(record, null, 2), "utf-8");
|
|
2868
2944
|
} catch (err) {
|
|
2869
2945
|
writeLog(args.logFd, `[autoApply] evaluation failed, leaving the run for review: ${err instanceof Error ? err.message : String(err)}
|
|
2870
2946
|
`);
|
|
@@ -2962,14 +3038,14 @@ function loadApplyRow(id) {
|
|
|
2962
3038
|
function buildEvoSandbox(workspacePath, sandboxParent, logFd) {
|
|
2963
3039
|
const srcWs = wsHaloDir(workspacePath);
|
|
2964
3040
|
const dstWs = evoSandboxHaloDir(sandboxParent);
|
|
2965
|
-
|
|
3041
|
+
fs12.mkdirSync(dstWs, { recursive: true });
|
|
2966
3042
|
const whitelist = SANDBOX_WHITELIST;
|
|
2967
3043
|
for (const entry of whitelist) {
|
|
2968
|
-
const srcEntry =
|
|
2969
|
-
if (!
|
|
3044
|
+
const srcEntry = path14.join(srcWs, entry);
|
|
3045
|
+
if (!fs12.existsSync(srcEntry))
|
|
2970
3046
|
continue;
|
|
2971
|
-
const dstEntry =
|
|
2972
|
-
if (
|
|
3047
|
+
const dstEntry = path14.join(dstWs, entry);
|
|
3048
|
+
if (fs12.existsSync(dstEntry))
|
|
2973
3049
|
continue;
|
|
2974
3050
|
copyDereferenced(srcEntry, dstEntry);
|
|
2975
3051
|
}
|
|
@@ -2977,13 +3053,13 @@ function buildEvoSandbox(workspacePath, sandboxParent, logFd) {
|
|
|
2977
3053
|
`);
|
|
2978
3054
|
}
|
|
2979
3055
|
function copyDereferenced(src, dst) {
|
|
2980
|
-
if (
|
|
2981
|
-
|
|
2982
|
-
for (const entry of
|
|
2983
|
-
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));
|
|
2984
3060
|
return;
|
|
2985
3061
|
}
|
|
2986
|
-
|
|
3062
|
+
fs12.copyFileSync(src, dst);
|
|
2987
3063
|
}
|
|
2988
3064
|
function buildApplySandbox(workspacePath, applyDir, logFd) {
|
|
2989
3065
|
buildEvoSandbox(workspacePath, applyDir, logFd);
|
|
@@ -3011,11 +3087,11 @@ function buildApplyMergeBrief(args) {
|
|
|
3011
3087
|
"",
|
|
3012
3088
|
"Hard rules: only edit files under sandbox/.halo/ \u2014 main workspace",
|
|
3013
3089
|
"and ~/.halo/global/ are off-limits. Do NOT spawn halo cli. Do NOT",
|
|
3014
|
-
`write score.json. Write ${
|
|
3090
|
+
`write score.json. Write ${path14.join(args.applyDir, "apply.log")}`,
|
|
3015
3091
|
"with a summary of what you did.",
|
|
3016
3092
|
"",
|
|
3017
3093
|
"If the merge cannot proceed (irreconcilable patch conflicts, malformed",
|
|
3018
|
-
`input, missing sandbox), write ${
|
|
3094
|
+
`input, missing sandbox), write ${path14.join(args.applyDir, "ABORT.md")}`,
|
|
3019
3095
|
'with the diagnosis \u2014 the wrapper treats its presence as "abort this',
|
|
3020
3096
|
'apply" and fails the apply instead of publishing. You cannot signal',
|
|
3021
3097
|
"abort via exit code; ABORT.md is the only abort channel.",
|
|
@@ -3036,19 +3112,19 @@ function buildApplyScoreBrief(args) {
|
|
|
3036
3112
|
"The apply agent has merged this patch (and possibly others) into",
|
|
3037
3113
|
"an apply sandbox. The wrapper just ran the patch's testScenario",
|
|
3038
3114
|
"against the merged sandbox; stdout is in",
|
|
3039
|
-
`${
|
|
3115
|
+
`${path14.join(args.regressDir, "dry-run-output.txt")}.`,
|
|
3040
3116
|
"An empty dry-run-output.txt means the dry-run against the merged",
|
|
3041
3117
|
"sandbox failed \u2014 score lint 0 (that is the regression signal the",
|
|
3042
3118
|
`wrapper acts on); the failure detail is in`,
|
|
3043
|
-
`${
|
|
3119
|
+
`${path14.join(args.regressDir, "dry-run-fail.log")} when present.`,
|
|
3044
3120
|
"",
|
|
3045
3121
|
"Follow your AGENT.md procedure. Read:",
|
|
3046
3122
|
` patch.md \u2192 ${args.patchMdPath}`,
|
|
3047
|
-
` tool-flow.md \u2192 ${
|
|
3123
|
+
` tool-flow.md \u2192 ${path14.join(path14.dirname(args.patchMdPath), "tool-flow.md")} (baseline conversation)`,
|
|
3048
3124
|
` source-snapshot.json \u2192 ${args.snapshotPath}`,
|
|
3049
|
-
` dry-run output \u2192 ${
|
|
3125
|
+
` dry-run output \u2192 ${path14.join(args.regressDir, "dry-run-output.txt")}`,
|
|
3050
3126
|
"",
|
|
3051
|
-
`Write ${
|
|
3127
|
+
`Write ${path14.join(args.regressDir, "score.json")} \u2014 NOT the source`,
|
|
3052
3128
|
"run's score.json. Same format as run mode.",
|
|
3053
3129
|
"Do not modify any other file.",
|
|
3054
3130
|
...buildLanguageClause(args.langHint, 'the score.json "notes" field'),
|
|
@@ -3060,9 +3136,9 @@ async function phaseApplyMerge(ctx) {
|
|
|
3060
3136
|
=== Phase A': merge ===
|
|
3061
3137
|
`);
|
|
3062
3138
|
buildApplySandbox(ctx.workspacePath, ctx.applyDir, ctx.logFd);
|
|
3063
|
-
const abortPath =
|
|
3139
|
+
const abortPath = path14.join(ctx.applyDir, "ABORT.md");
|
|
3064
3140
|
try {
|
|
3065
|
-
|
|
3141
|
+
fs12.rmSync(abortPath, { force: true });
|
|
3066
3142
|
} catch {
|
|
3067
3143
|
}
|
|
3068
3144
|
const brief = buildApplyMergeBrief(ctx);
|
|
@@ -3081,10 +3157,10 @@ async function phaseApplyMerge(ctx) {
|
|
|
3081
3157
|
`);
|
|
3082
3158
|
return { ok: false, reason: `apply cli exited ${result.exitCode}` };
|
|
3083
3159
|
}
|
|
3084
|
-
if (
|
|
3160
|
+
if (fs12.existsSync(abortPath)) {
|
|
3085
3161
|
let abortText = "";
|
|
3086
3162
|
try {
|
|
3087
|
-
abortText =
|
|
3163
|
+
abortText = fs12.readFileSync(abortPath, "utf-8").trim();
|
|
3088
3164
|
} catch {
|
|
3089
3165
|
}
|
|
3090
3166
|
writeLog(ctx.logFd, `[phaseApplyMerge] ABORT.md present \u2014 agent aborted the merge:
|
|
@@ -3093,7 +3169,7 @@ ${abortText}
|
|
|
3093
3169
|
const firstLine = abortText.split("\n")[0]?.slice(0, 300) ?? "";
|
|
3094
3170
|
return { ok: false, reason: `apply agent aborted: ${firstLine || "see ABORT.md in apply dir"}` };
|
|
3095
3171
|
}
|
|
3096
|
-
const hasLog =
|
|
3172
|
+
const hasLog = fs12.existsSync(path14.join(ctx.applyDir, "apply.log"));
|
|
3097
3173
|
writeLog(ctx.logFd, `[phaseApplyMerge] apply.log=${hasLog}
|
|
3098
3174
|
`);
|
|
3099
3175
|
if (!hasLog)
|
|
@@ -3105,17 +3181,17 @@ async function phaseApplyRegress(ctx) {
|
|
|
3105
3181
|
=== Phase B': regress ===
|
|
3106
3182
|
`);
|
|
3107
3183
|
const sandboxDir = evoSandboxDir(ctx.applyDir);
|
|
3108
|
-
|
|
3184
|
+
fs12.mkdirSync(evoRegressDir(ctx.applyDir), { recursive: true });
|
|
3109
3185
|
const outcomes = [];
|
|
3110
3186
|
for (const runId of ctx.sourceRunIds) {
|
|
3111
3187
|
writeLog(ctx.logFd, `
|
|
3112
3188
|
--- regress for source run ${runId} ---
|
|
3113
3189
|
`);
|
|
3114
3190
|
const regressDir = evoRegressRunDir(ctx.applyDir, runId);
|
|
3115
|
-
|
|
3191
|
+
fs12.mkdirSync(regressDir, { recursive: true });
|
|
3116
3192
|
const runDir = wsEvoRunDir(ctx.workspacePath, runId);
|
|
3117
|
-
const patchMdPath =
|
|
3118
|
-
const snapshotPath =
|
|
3193
|
+
const patchMdPath = path14.join(runDir, "patch.md");
|
|
3194
|
+
const snapshotPath = path14.join(runDir, "source-snapshot.json");
|
|
3119
3195
|
const fm = readPatchFrontmatter(runDir);
|
|
3120
3196
|
const scenario = extractTestScenario(fm);
|
|
3121
3197
|
if (!scenario) {
|
|
@@ -3142,14 +3218,14 @@ async function phaseApplyRegress(ctx) {
|
|
|
3142
3218
|
// stdin, not argv
|
|
3143
3219
|
DRY_RUN_TIMEOUT_SEC
|
|
3144
3220
|
);
|
|
3145
|
-
const dryRunOut =
|
|
3221
|
+
const dryRunOut = path14.join(regressDir, "dry-run-output.txt");
|
|
3146
3222
|
if (dryRun.exitCode === 0 && dryRun.stdout.trim().length > 0) {
|
|
3147
|
-
|
|
3223
|
+
fs12.writeFileSync(dryRunOut, dryRun.stdout, "utf-8");
|
|
3148
3224
|
} else {
|
|
3149
3225
|
const tail = dryRun.stdout.length > 4e3 ? "\u2026" + dryRun.stdout.slice(-4e3) : dryRun.stdout;
|
|
3150
3226
|
const errTail = dryRun.stderr.length > 4e3 ? "\u2026" + dryRun.stderr.slice(-4e3) : dryRun.stderr;
|
|
3151
|
-
|
|
3152
|
-
|
|
3227
|
+
fs12.writeFileSync(dryRunOut, "", "utf-8");
|
|
3228
|
+
fs12.writeFileSync(path14.join(regressDir, "dry-run-fail.log"), [
|
|
3153
3229
|
`Wrapper command: halo cli -a ${scenario.agentId} -n -w ${sandboxDir} <testMessage>`,
|
|
3154
3230
|
`Test message: ${scenario.testMessage}`,
|
|
3155
3231
|
`Exit code: ${dryRun.exitCode}${dryRun.exitCode === 124 ? " (timeout)" : ""}`,
|
|
@@ -3170,7 +3246,7 @@ async function phaseApplyRegress(ctx) {
|
|
|
3170
3246
|
langHint: ctx.langHint
|
|
3171
3247
|
});
|
|
3172
3248
|
const scoreSandboxWs = evoSandboxDir(ctx.applyDir);
|
|
3173
|
-
|
|
3249
|
+
fs12.rmSync(path14.join(regressDir, "score.json"), { force: true });
|
|
3174
3250
|
const scoreRun = await spawnProc(
|
|
3175
3251
|
resolveHaloCli(),
|
|
3176
3252
|
["cli", "-a", "__score__", "-n", "-w", scoreSandboxWs],
|
|
@@ -3184,10 +3260,10 @@ async function phaseApplyRegress(ctx) {
|
|
|
3184
3260
|
writeLog(ctx.logFd, `[phaseApplyRegress:${runId}] scorer cli exited ${scoreRun.exitCode}
|
|
3185
3261
|
`);
|
|
3186
3262
|
}
|
|
3187
|
-
const scorePath =
|
|
3263
|
+
const scorePath = path14.join(regressDir, "score.json");
|
|
3188
3264
|
let scoreJson = null;
|
|
3189
3265
|
try {
|
|
3190
|
-
scoreJson = JSON.parse(
|
|
3266
|
+
scoreJson = JSON.parse(fs12.readFileSync(scorePath, "utf-8"));
|
|
3191
3267
|
} catch {
|
|
3192
3268
|
}
|
|
3193
3269
|
if (!scoreJson) {
|
|
@@ -3246,8 +3322,8 @@ function autoApplyRecheck(workspacePath, applyDir, changed) {
|
|
|
3246
3322
|
async function applyMode(id, logFd) {
|
|
3247
3323
|
const row = loadApplyRow(id);
|
|
3248
3324
|
const applyDir = wsEvoApplyDir(row.workspacePath, id);
|
|
3249
|
-
|
|
3250
|
-
|
|
3325
|
+
fs12.mkdirSync(applyDir, { recursive: true });
|
|
3326
|
+
fs12.writeFileSync(path14.join(applyDir, "meta.json"), JSON.stringify({
|
|
3251
3327
|
id,
|
|
3252
3328
|
sourceRunIds: row.sourceRunIds,
|
|
3253
3329
|
reviewerHint: row.reviewerHint,
|
|
@@ -3338,28 +3414,28 @@ async function phaseApplyPreflight(ctx, isResume) {
|
|
|
3338
3414
|
`);
|
|
3339
3415
|
const sandboxHalo = evoSandboxHaloDir(ctx.applyDir);
|
|
3340
3416
|
const mainHalo = wsHaloDir(ctx.workspacePath);
|
|
3341
|
-
if (!
|
|
3417
|
+
if (!fs12.existsSync(sandboxHalo))
|
|
3342
3418
|
return { ok: false, reason: "sandbox missing \u2014 phase 11 must run first" };
|
|
3343
3419
|
const whitelistTop = new Set(PUBLISH_WHITELIST);
|
|
3344
3420
|
const filesToConsider = [];
|
|
3345
|
-
for (const top of
|
|
3421
|
+
for (const top of fs12.readdirSync(sandboxHalo)) {
|
|
3346
3422
|
if (!whitelistTop.has(top))
|
|
3347
3423
|
continue;
|
|
3348
|
-
walkInto(
|
|
3424
|
+
walkInto(path14.join(sandboxHalo, top), top, filesToConsider);
|
|
3349
3425
|
}
|
|
3350
3426
|
const historyDir = wsEvoHistoryDir(ctx.workspacePath, ctx.applyId);
|
|
3351
3427
|
const changed = [];
|
|
3352
3428
|
for (const { rel, full } of filesToConsider) {
|
|
3353
|
-
const mainPath =
|
|
3429
|
+
const mainPath = path14.join(mainHalo, rel);
|
|
3354
3430
|
let existsInMain = false;
|
|
3355
3431
|
try {
|
|
3356
|
-
|
|
3432
|
+
fs12.accessSync(mainPath);
|
|
3357
3433
|
existsInMain = true;
|
|
3358
3434
|
} catch {
|
|
3359
3435
|
}
|
|
3360
3436
|
if (existsInMain) {
|
|
3361
|
-
const sandboxBuf =
|
|
3362
|
-
const mainBuf =
|
|
3437
|
+
const sandboxBuf = fs12.readFileSync(full);
|
|
3438
|
+
const mainBuf = fs12.readFileSync(mainPath);
|
|
3363
3439
|
if (sandboxBuf.equals(mainBuf))
|
|
3364
3440
|
continue;
|
|
3365
3441
|
}
|
|
@@ -3368,20 +3444,20 @@ async function phaseApplyPreflight(ctx, isResume) {
|
|
|
3368
3444
|
writeLog(ctx.logFd, `[preflight] ${changed.length} of ${filesToConsider.length} whitelisted files differ from main
|
|
3369
3445
|
`);
|
|
3370
3446
|
if (!isResume)
|
|
3371
|
-
|
|
3447
|
+
fs12.rmSync(historyDir, { recursive: true, force: true });
|
|
3372
3448
|
if (changed.length === 0) {
|
|
3373
3449
|
return { ok: true, result: { changed, historyDir, fileCount: 0 } };
|
|
3374
3450
|
}
|
|
3375
|
-
|
|
3376
|
-
const manifestPath =
|
|
3451
|
+
fs12.mkdirSync(historyDir, { recursive: true });
|
|
3452
|
+
const manifestPath = path14.join(historyDir, "MANIFEST.json");
|
|
3377
3453
|
if (!isResume) {
|
|
3378
3454
|
for (const { rel, existsInMain } of changed) {
|
|
3379
3455
|
if (!existsInMain)
|
|
3380
3456
|
continue;
|
|
3381
|
-
const mainPath =
|
|
3382
|
-
const histPath =
|
|
3383
|
-
|
|
3384
|
-
|
|
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);
|
|
3385
3461
|
}
|
|
3386
3462
|
const effective = new Set(publishChanges({ mainHalo, globalHalo: globalDir(), sandboxHalo }, changed).map((c) => c.rel));
|
|
3387
3463
|
const manifest = {
|
|
@@ -3391,13 +3467,13 @@ async function phaseApplyPreflight(ctx, isResume) {
|
|
|
3391
3467
|
files: changed.map((c) => ({
|
|
3392
3468
|
rel: c.rel,
|
|
3393
3469
|
kind: c.existsInMain ? "overwrite" : "new",
|
|
3394
|
-
before: c.existsInMain ? sha256File(
|
|
3470
|
+
before: c.existsInMain ? sha256File(path14.join(mainHalo, c.rel)) : null,
|
|
3395
3471
|
after: sha256File(c.full),
|
|
3396
|
-
...effective.has(c.rel.split(
|
|
3472
|
+
...effective.has(c.rel.split(path14.sep).join("/")) ? {} : { unchanged: true }
|
|
3397
3473
|
})),
|
|
3398
3474
|
createdDirs: dirsCreatedBy(mainHalo, changed.filter((c) => !c.existsInMain).map((c) => c.rel))
|
|
3399
3475
|
};
|
|
3400
|
-
|
|
3476
|
+
fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
3401
3477
|
} else {
|
|
3402
3478
|
writeLog(ctx.logFd, `[preflight] resume path, skipping history backup (the pre-apply state is already preserved from the fresh claim's preflight)
|
|
3403
3479
|
`);
|
|
@@ -3412,9 +3488,9 @@ async function phaseApplyPublish(ctx, preflight) {
|
|
|
3412
3488
|
`);
|
|
3413
3489
|
const mainHalo = wsHaloDir(ctx.workspacePath);
|
|
3414
3490
|
for (const { rel, full } of preflight.changed) {
|
|
3415
|
-
const mainPath =
|
|
3416
|
-
|
|
3417
|
-
|
|
3491
|
+
const mainPath = path14.join(mainHalo, rel);
|
|
3492
|
+
fs12.mkdirSync(path14.dirname(mainPath), { recursive: true });
|
|
3493
|
+
fs12.copyFileSync(full, mainPath);
|
|
3418
3494
|
}
|
|
3419
3495
|
writeLog(ctx.logFd, `[publish] copied ${preflight.changed.length} files to ${mainHalo}
|
|
3420
3496
|
`);
|
|
@@ -3422,10 +3498,10 @@ async function phaseApplyPublish(ctx, preflight) {
|
|
|
3422
3498
|
function dirsCreatedBy(mainHalo, newFiles) {
|
|
3423
3499
|
const dirs = /* @__PURE__ */ new Set();
|
|
3424
3500
|
for (const rel of newFiles) {
|
|
3425
|
-
for (let d =
|
|
3501
|
+
for (let d = path14.dirname(rel); d !== "." && !fs12.existsSync(path14.join(mainHalo, d)); d = path14.dirname(d))
|
|
3426
3502
|
dirs.add(d);
|
|
3427
3503
|
}
|
|
3428
|
-
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));
|
|
3429
3505
|
}
|
|
3430
3506
|
function ownsApply(id, status, attempts) {
|
|
3431
3507
|
return and3(eq3(evolutionApplies.id, id), eq3(evolutionApplies.status, status), eq3(evolutionApplies.attempts, attempts));
|
|
@@ -3451,15 +3527,15 @@ function releaseSyncing(applyId, attempts, reason, logFd) {
|
|
|
3451
3527
|
`);
|
|
3452
3528
|
}
|
|
3453
3529
|
function walkInto(absPath, relPath, out) {
|
|
3454
|
-
const stat =
|
|
3530
|
+
const stat = fs12.statSync(absPath);
|
|
3455
3531
|
if (stat.isFile()) {
|
|
3456
3532
|
out.push({ rel: relPath, full: absPath });
|
|
3457
3533
|
return;
|
|
3458
3534
|
}
|
|
3459
3535
|
if (!stat.isDirectory())
|
|
3460
3536
|
return;
|
|
3461
|
-
for (const entry of
|
|
3462
|
-
walkInto(
|
|
3537
|
+
for (const entry of fs12.readdirSync(absPath)) {
|
|
3538
|
+
walkInto(path14.join(absPath, entry), path14.join(relPath, entry), out);
|
|
3463
3539
|
}
|
|
3464
3540
|
}
|
|
3465
3541
|
function finalizeApplied(applyId, attempts, sourceRunIds, logFd) {
|
|
@@ -3478,19 +3554,19 @@ function finalizeApplied(applyId, attempts, sourceRunIds, logFd) {
|
|
|
3478
3554
|
}
|
|
3479
3555
|
function writeLog(fd, msg) {
|
|
3480
3556
|
try {
|
|
3481
|
-
|
|
3557
|
+
fs12.writeSync(fd, msg);
|
|
3482
3558
|
} catch {
|
|
3483
3559
|
}
|
|
3484
3560
|
}
|
|
3485
3561
|
function openLogFor(mode, id) {
|
|
3486
|
-
|
|
3562
|
+
fs12.mkdirSync(evoLogsDir(), { recursive: true });
|
|
3487
3563
|
const logPath = mode === "run" ? evoWrapperLogFile(id) : evoApplyLogFile(id);
|
|
3488
|
-
const fd =
|
|
3564
|
+
const fd = fs12.openSync(logPath, "a");
|
|
3489
3565
|
return { fd, logPath };
|
|
3490
3566
|
}
|
|
3491
3567
|
async function main() {
|
|
3492
3568
|
const { mode, id } = parseCli(process.argv.slice(2));
|
|
3493
|
-
setEvoDb(createEvoDb(
|
|
3569
|
+
setEvoDb(createEvoDb(path14.join(homedir7(), ".halo", "global")));
|
|
3494
3570
|
const { fd, logPath } = openLogFor(mode, id);
|
|
3495
3571
|
writeLog(fd, `
|
|
3496
3572
|
=== ${(/* @__PURE__ */ new Date()).toISOString()} wrapper start mode=${mode} id=${id} pid=${process.pid} ===
|
|
@@ -3518,7 +3594,7 @@ async function main() {
|
|
|
3518
3594
|
process.exit(1);
|
|
3519
3595
|
}
|
|
3520
3596
|
}
|
|
3521
|
-
if (process.argv[1] &&
|
|
3597
|
+
if (process.argv[1] && path14.resolve(process.argv[1]) === fileURLToPath2(import.meta.url)) {
|
|
3522
3598
|
main().catch((err) => {
|
|
3523
3599
|
process.stderr.write(`[wrapper] startup error: ${err instanceof Error ? err.message : String(err)}
|
|
3524
3600
|
`);
|