@turmind/halo 2.2.4 → 2.2.6

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.
Files changed (37) hide show
  1. package/README.md +2 -1
  2. package/admin-out/404.html +1 -1
  3. package/admin-out/_next/static/chunks/1989-3ab059aa2c368f46.js +1 -0
  4. package/admin-out/_next/static/chunks/4320-0fb65ed376b666a8.js +1 -0
  5. package/admin-out/_next/static/chunks/app/page-99eba94ecd0f8e81.js +1 -0
  6. package/admin-out/_next/static/css/7eb37d975d4027da.css +1 -0
  7. package/admin-out/index.html +1 -1
  8. package/admin-out/index.txt +5 -5
  9. package/bundled-docs/design/agent.md +0 -1
  10. package/bundled-docs/design/architecture.md +1 -1
  11. package/bundled-docs/design/canvas-extensions.md +2 -0
  12. package/bundled-docs/design/cron.md +2 -2
  13. package/bundled-docs/design/feishu.md +10 -0
  14. package/bundled-docs/design/storage.md +4 -2
  15. package/bundled-docs/design/stt-local.md +1 -1
  16. package/bundled-docs/design/ws.md +1 -1
  17. package/bundled-docs/dev/add-model-provider.md +18 -0
  18. package/bundled-docs/dev/api.md +7 -1
  19. package/bundled-docs/dev/deploy.md +26 -1
  20. package/bundled-docs/dev/desktop-packaging.md +3 -2
  21. package/bundled-docs/dev/env.md +1 -2
  22. package/bundled-docs/guide/agents.md +2 -2
  23. package/bundled-docs/guide/cli.md +2 -2
  24. package/bundled-docs/guide/skills.md +1 -1
  25. package/bundled-docs/requirements/explorer.md +10 -3
  26. package/bundled-docs/requirements/settings.md +24 -1
  27. package/dist/feishu-sdk.cjs +23128 -0
  28. package/dist/feishu-sdk.licenses.txt +1465 -0
  29. package/dist/index.js +418 -220
  30. package/package.json +1 -2
  31. package/templates/models/aws-bedrock-claude-invoke.yaml +62 -1
  32. package/admin-out/_next/static/chunks/1607-bf227fc5e4db45b7.js +0 -1
  33. package/admin-out/_next/static/chunks/1989-9d30cdb1a86e4beb.js +0 -1
  34. package/admin-out/_next/static/chunks/app/page-c9d174a5432b0007.js +0 -1
  35. package/admin-out/_next/static/css/87d4fc049f865389.css +0 -1
  36. /package/admin-out/_next/static/{vf5W8US5y8B2f1E6zJJw3 → 6I6qmeIcucaJczGu5oP3C}/_buildManifest.js +0 -0
  37. /package/admin-out/_next/static/{vf5W8US5y8B2f1E6zJJw3 → 6I6qmeIcucaJczGu5oP3C}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -2121,7 +2121,7 @@ var init_init = __esm({
2121
2121
  path9.resolve(__dirname, "..", "bundled-docs"),
2122
2122
  path9.resolve(__dirname, "..", "..", "..", ".halo", "docs")
2123
2123
  ];
2124
- TEMPLATE_VERSION = 123;
2124
+ TEMPLATE_VERSION = 124;
2125
2125
  VERSION_FILE = ".template-version";
2126
2126
  SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
2127
2127
  BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
@@ -9350,27 +9350,27 @@ var init_workspace = __esm({
9350
9350
  constructor(projectRoot) {
9351
9351
  this.projectRoot = projectRoot;
9352
9352
  }
9353
- async readFile(path98) {
9354
- const fullPath = this.validatePath(path98);
9353
+ async readFile(path99) {
9354
+ const fullPath = this.validatePath(path99);
9355
9355
  return readFile(fullPath, "utf-8");
9356
9356
  }
9357
- async writeFile(path98, content) {
9358
- const fullPath = this.validatePath(path98);
9357
+ async writeFile(path99, content) {
9358
+ const fullPath = this.validatePath(path99);
9359
9359
  const dir = resolve2(fullPath, "..");
9360
9360
  await mkdir(dir, { recursive: true });
9361
9361
  await writeFile(fullPath, content, "utf-8");
9362
9362
  }
9363
- validatePath(path98) {
9364
- const fullPath = isAbsolute(path98) ? resolve2(path98) : resolve2(this.projectRoot, path98);
9363
+ validatePath(path99) {
9364
+ const fullPath = isAbsolute(path99) ? resolve2(path99) : resolve2(this.projectRoot, path99);
9365
9365
  const root = resolve2(this.projectRoot);
9366
9366
  if (fullPath !== root && !fullPath.startsWith(root + sep)) {
9367
- throw new WorkspaceError(`Path "${path98}" is outside the workspace directory "${this.projectRoot}"`);
9367
+ throw new WorkspaceError(`Path "${path99}" is outside the workspace directory "${this.projectRoot}"`);
9368
9368
  }
9369
9369
  try {
9370
9370
  const real = realpathSync2(fullPath);
9371
9371
  const realRoot = realpathSync2(root);
9372
9372
  if (real !== realRoot && !real.startsWith(realRoot + sep)) {
9373
- throw new WorkspaceError(`Path "${path98}" is outside the workspace directory "${this.projectRoot}"`);
9373
+ throw new WorkspaceError(`Path "${path99}" is outside the workspace directory "${this.projectRoot}"`);
9374
9374
  }
9375
9375
  } catch (err) {
9376
9376
  if (err instanceof WorkspaceError)
@@ -9557,12 +9557,12 @@ var init_git_manager = __esm({
9557
9557
  const code = parts[0];
9558
9558
  const status = code[0];
9559
9559
  const isRenameOrCopy = status === "R" || status === "C";
9560
- const path98 = isRenameOrCopy ? parts[2] : parts[1];
9561
- if (!path98 || seen.has(path98))
9560
+ const path99 = isRenameOrCopy ? parts[2] : parts[1];
9561
+ if (!path99 || seen.has(path99))
9562
9562
  continue;
9563
- seen.add(path98);
9563
+ seen.add(path99);
9564
9564
  files.push({
9565
- path: path98,
9565
+ path: path99,
9566
9566
  status,
9567
9567
  from: isRenameOrCopy ? parts[1] : void 0
9568
9568
  });
@@ -9710,9 +9710,9 @@ var init_git_manager = __esm({
9710
9710
  * empty for an added file or the root commit) and `modified` is the version
9711
9711
  * at `commit` (empty for a deletion). The working-tree/staged path above is
9712
9712
  * left entirely untouched (commit === undefined preserves existing behavior). */
9713
- async getFileDiff(path98, staged, from, commit) {
9713
+ async getFileDiff(path99, staged, from, commit) {
9714
9714
  if (commit) {
9715
- const parentPath = from || path98;
9715
+ const parentPath = from || path99;
9716
9716
  let original2 = "";
9717
9717
  try {
9718
9718
  original2 = await this.git.show([`${commit}^:${parentPath}`]);
@@ -9721,13 +9721,13 @@ var init_git_manager = __esm({
9721
9721
  }
9722
9722
  let modified2 = "";
9723
9723
  try {
9724
- modified2 = await this.git.show([`${commit}:${path98}`]);
9724
+ modified2 = await this.git.show([`${commit}:${path99}`]);
9725
9725
  } catch {
9726
9726
  modified2 = "";
9727
9727
  }
9728
9728
  return { original: original2, modified: modified2 };
9729
9729
  }
9730
- const headPath = from || path98;
9730
+ const headPath = from || path99;
9731
9731
  let original = "";
9732
9732
  try {
9733
9733
  original = await this.git.show([`HEAD:${headPath}`]);
@@ -9737,13 +9737,13 @@ var init_git_manager = __esm({
9737
9737
  let modified = "";
9738
9738
  if (staged) {
9739
9739
  try {
9740
- modified = await this.git.show([`:${path98}`]);
9740
+ modified = await this.git.show([`:${path99}`]);
9741
9741
  } catch {
9742
9742
  modified = "";
9743
9743
  }
9744
9744
  } else {
9745
9745
  try {
9746
- modified = await this.workspace.readFile(path98);
9746
+ modified = await this.workspace.readFile(path99);
9747
9747
  } catch {
9748
9748
  modified = "";
9749
9749
  }
@@ -15847,7 +15847,7 @@ function createNodeHub(deps) {
15847
15847
  since: c.since
15848
15848
  }));
15849
15849
  const announce = () => {
15850
- broadcast({ type: "nodes:changed", online: online().map(({ accountId, root, shell, os: os12, since }) => ({ accountId, root, shell, os: os12, since })) });
15850
+ broadcast({ type: "nodes:changed", online: online().map(({ accountId, root, shell, os: os13, since }) => ({ accountId, root, shell, os: os13, since })) });
15851
15851
  };
15852
15852
  const settle = (conn, id, fn) => {
15853
15853
  const p = conn.pending.get(id);
@@ -16830,7 +16830,7 @@ async function initObservability(haloProcess = "server") {
16830
16830
  return true;
16831
16831
  }
16832
16832
  const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
16833
- providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "2.2.4", haloProcess);
16833
+ providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "2.2.6", haloProcess);
16834
16834
  console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, halo.process=${haloProcess}, capture_content=${config.observability.captureContent})`);
16835
16835
  return true;
16836
16836
  }
@@ -22018,8 +22018,8 @@ function applyPathPick(value, ref, pick) {
22018
22018
  const before = value.slice(0, markerStart);
22019
22019
  const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
22020
22020
  const needsQuote = pick.insertPath.includes(" ");
22021
- const path98 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
22022
- return `${before}@${kind} ${path98}`;
22021
+ const path99 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
22022
+ return `${before}@${kind} ${path99}`;
22023
22023
  }
22024
22024
  var init_path_suggest_engine = __esm({
22025
22025
  "src/tui/path-suggest-engine.ts"() {
@@ -23920,6 +23920,31 @@ async function planFolderZip(root, projectPath, opts) {
23920
23920
  const [rootReal, wsReal] = await Promise.all([fs53.realpath(root), fs53.realpath(projectPath)]);
23921
23921
  if (isBlockedPath(rootReal, wsReal))
23922
23922
  throw new FolderZipError(403, "Path is not accessible");
23923
+ const planner = createPlanner(wsReal, opts);
23924
+ await planner.walk(rootReal, "");
23925
+ return planner.plan;
23926
+ }
23927
+ async function planSelectionZip(rels, projectPath, opts) {
23928
+ const selected = new Set(rels);
23929
+ const kept = [...selected].filter((rel) => rel === "" || !rel.split("/").some((_, i, segs) => selected.has(segs.slice(0, i).join("/")))).sort();
23930
+ let common = kept[0].split("/").slice(0, -1);
23931
+ for (const rel of kept) {
23932
+ const segs = rel.split("/").slice(0, -1);
23933
+ let i = 0;
23934
+ while (i < common.length && common[i] === segs[i])
23935
+ i++;
23936
+ common = common.slice(0, i);
23937
+ }
23938
+ const parent = common.join("/");
23939
+ const wsReal = await fs53.realpath(projectPath);
23940
+ const planner = createPlanner(wsReal, opts);
23941
+ for (const rel of kept) {
23942
+ const real = rel ? path60.join(await fs53.realpath(path60.join(wsReal, path60.dirname(rel))), path60.basename(rel)) : wsReal;
23943
+ await planner.addSelected(real, parent ? rel.slice(parent.length + 1) : rel);
23944
+ }
23945
+ return { plan: planner.plan, parent };
23946
+ }
23947
+ function createPlanner(wsReal, opts) {
23923
23948
  const { includeIgnored, limits, signal } = opts;
23924
23949
  const deadline = Date.now() + limits.maxWalkMs;
23925
23950
  const plan = { items: [], files: 0, bytes: 0, excluded: new ExcludedLog() };
@@ -24012,8 +24037,31 @@ async function planFolderZip(root, projectPath, opts) {
24012
24037
  }
24013
24038
  return nonEmpty2;
24014
24039
  }
24015
- await walk(rootReal, "");
24016
- return plan;
24040
+ async function addSelected(real, rel) {
24041
+ signal?.throwIfAborted();
24042
+ if (Date.now() > deadline)
24043
+ throw walkTimeout();
24044
+ const name = path60.basename(real);
24045
+ const st = await fs53.lstat(real);
24046
+ if (rel.split("/").some((seg, i) => isZipUnsafeName(seg, i === 0))) {
24047
+ excluded.add("unreadable", `${rel}${st.isDirectory() ? "/" : ""} (name not valid in a zip)`);
24048
+ } else if (st.isSymbolicLink()) {
24049
+ await addSymlink(real, rel, name);
24050
+ } else if (st.isDirectory()) {
24051
+ if (isBlockedPath(real, wsReal))
24052
+ excluded.add("blocked", `${rel}/`);
24053
+ else if (!await walk(real, rel) && rel)
24054
+ plan.items.push({ rel, mtimeMs: st.mtimeMs });
24055
+ } else if (st.isFile()) {
24056
+ if (isEnvName(name) || isBlockedPath(real, wsReal))
24057
+ excluded.add("blocked", rel);
24058
+ else
24059
+ addFile(rel, real, st);
24060
+ } else {
24061
+ excluded.add("unreadable", `${rel} (not a regular file)`);
24062
+ }
24063
+ }
24064
+ return { plan, walk, addSelected };
24017
24065
  }
24018
24066
  function planSummary(plan) {
24019
24067
  return { files: plan.files, bytes: plan.bytes, excluded: { ...plan.excluded.counts }, ignoredNames: { ...plan.excluded.ignoredNames } };
@@ -24747,26 +24795,44 @@ function createFileRoutes(opts = {}) {
24747
24795
  }
24748
24796
  });
24749
24797
  app2.get("/files/download-folder", async (c) => {
24750
- const relPath = c.req.query("path") ?? "";
24798
+ const relPaths = c.req.queries("path") ?? [];
24799
+ const relPath = relPaths[0] ?? "";
24751
24800
  const projectId = c.req.query("projectId");
24752
24801
  if (!projectId)
24753
24802
  return c.json({ error: "projectId is required" }, 400);
24754
24803
  const signal = c.req.raw.signal;
24804
+ const planOpts = { includeIgnored: c.req.query("include") === "excluded", limits: folderZipLimits, signal };
24755
24805
  try {
24756
- const resolved = await resolveProjectFile(projectId, relPath);
24757
- if ("error" in resolved)
24758
- return c.json({ error: resolved.error }, resolved.status);
24759
- const { projectPath, absolutePath } = resolved;
24760
- if (!(await fs54.stat(absolutePath)).isDirectory())
24761
- return c.json({ error: "Not a directory" }, 400);
24762
- const plan = await planFolderZip(absolutePath, projectPath, {
24763
- includeIgnored: c.req.query("include") === "excluded",
24764
- limits: folderZipLimits,
24765
- signal
24766
- });
24806
+ let plan;
24807
+ let folderName;
24808
+ if (relPaths.length > 1) {
24809
+ let projectPath = "";
24810
+ const rels = [];
24811
+ for (const p of relPaths) {
24812
+ const resolved = await resolveProjectFile(projectId, p);
24813
+ if ("error" in resolved)
24814
+ return c.json({ error: resolved.error }, resolved.status);
24815
+ projectPath = resolved.projectPath;
24816
+ const rel = path61.relative(projectPath, resolved.absolutePath);
24817
+ if (rel === ".." || rel.startsWith(`..${path61.sep}`) || path61.isAbsolute(rel))
24818
+ return c.json({ error: "Path traversal not allowed" }, 403);
24819
+ rels.push(rel.split(path61.sep).join("/"));
24820
+ }
24821
+ const selection = await planSelectionZip(rels, projectPath, planOpts);
24822
+ plan = selection.plan;
24823
+ folderName = `${path61.basename(path61.resolve(projectPath, selection.parent)) || "workspace"}-selection`;
24824
+ } else {
24825
+ const resolved = await resolveProjectFile(projectId, relPath);
24826
+ if ("error" in resolved)
24827
+ return c.json({ error: resolved.error }, resolved.status);
24828
+ const { projectPath, absolutePath } = resolved;
24829
+ if (!(await fs54.stat(absolutePath)).isDirectory())
24830
+ return c.json({ error: "Not a directory" }, 400);
24831
+ plan = await planFolderZip(absolutePath, projectPath, planOpts);
24832
+ folderName = path61.basename(absolutePath) || "workspace";
24833
+ }
24767
24834
  if (c.req.query("estimate") === "1")
24768
24835
  return c.json(planSummary(plan));
24769
- const folderName = path61.basename(absolutePath) || "workspace";
24770
24836
  const fileName = `${folderName}.zip`;
24771
24837
  const asciiName = fileName.replace(/[^\x20-\x7e]|["\\]/g, "_");
24772
24838
  const encodedName = encodeURIComponent(fileName).replace(/['()*]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
@@ -26855,8 +26921,8 @@ function detectOrphans(tree, declared, kept) {
26855
26921
  if (!block || typeof block !== "object")
26856
26922
  continue;
26857
26923
  for (const k of Object.keys(block)) {
26858
- const path98 = `${namespaceId}.${kindWord}.${k}`;
26859
- if (!declaredMap.has(path98)) {
26924
+ const path99 = `${namespaceId}.${kindWord}.${k}`;
26925
+ if (!declaredMap.has(path99)) {
26860
26926
  out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
26861
26927
  }
26862
26928
  }
@@ -28460,17 +28526,17 @@ function passwordStrengthError(pw) {
28460
28526
  }
28461
28527
  function authMiddleware() {
28462
28528
  return async (c, next) => {
28463
- const path98 = c.req.path;
28464
- if (PUBLIC_PATHS.some((p) => path98 === p || path98.startsWith(p + "?"))) {
28529
+ const path99 = c.req.path;
28530
+ if (PUBLIC_PATHS.some((p) => path99 === p || path99.startsWith(p + "?"))) {
28465
28531
  return next();
28466
28532
  }
28467
- if (EXTENSION_ASSET_PATH.test(path98)) {
28533
+ if (EXTENSION_ASSET_PATH.test(path99)) {
28468
28534
  return next();
28469
28535
  }
28470
28536
  const token = getCookie(c, COOKIE_NAME);
28471
28537
  const payload = validateToken(token);
28472
28538
  if (!payload) {
28473
- if (!path98.startsWith("/api/")) {
28539
+ if (!path99.startsWith("/api/")) {
28474
28540
  return next();
28475
28541
  }
28476
28542
  return c.json({ error: "Unauthorized" }, 401);
@@ -28524,14 +28590,14 @@ function createWebChannel(deps) {
28524
28590
  return findActiveSessionId(sm, accountId, prefix, activeOverrides, "full") ?? void 0;
28525
28591
  }
28526
28592
  function resolveWorkspace(account, override, scaffold = true) {
28527
- let path98 = account.workspacePath;
28593
+ let path99 = account.workspacePath;
28528
28594
  if (override && override !== account.workspacePath) {
28529
28595
  if (account.accessLevel !== "full") {
28530
28596
  return { ok: false, error: "workspace override requires a full-access token" };
28531
28597
  }
28532
- path98 = override;
28598
+ path99 = override;
28533
28599
  }
28534
- const resolved = scaffold ? resolveAccountWorkspace({ ...account, workspacePath: path98 }) : fs64.existsSync(path98) ? path98 : null;
28600
+ const resolved = scaffold ? resolveAccountWorkspace({ ...account, workspacePath: path99 }) : fs64.existsSync(path99) ? path99 : null;
28535
28601
  if (!resolved)
28536
28602
  return { ok: false, error: "workspace not found" };
28537
28603
  return { ok: true, path: resolved };
@@ -32756,7 +32822,7 @@ function buildCard(workspace, interfaceUrl, caps) {
32756
32822
  return {
32757
32823
  name: raw.name,
32758
32824
  description: raw.description,
32759
- version: typeof raw.version === "string" ? raw.version : "2.2.4",
32825
+ version: typeof raw.version === "string" ? raw.version : "2.2.6",
32760
32826
  supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
32761
32827
  capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
32762
32828
  ...caps.tokenAuth === false ? {} : {
@@ -34238,7 +34304,7 @@ async function finishRun(runId, db4, session) {
34238
34304
  if (session.turnError) {
34239
34305
  status = "failed";
34240
34306
  failureReason = session.turnError.slice(0, 500);
34241
- } else if (!text6) {
34307
+ } else if (!answer) {
34242
34308
  status = "failed";
34243
34309
  failureReason = "run produced no output (nothing to dispatch)";
34244
34310
  }
@@ -34250,7 +34316,7 @@ async function finishRun(runId, db4, session) {
34250
34316
  const targets = job ? parseTargets(job.targets) : [];
34251
34317
  let dispatchResults = [];
34252
34318
  if (job && status === "succeeded" && targets.length > 0) {
34253
- dispatchResults = await dispatchToTargets(text6, targets, row.workspacePath ?? job.workspacePath);
34319
+ dispatchResults = await dispatchToTargets(answer, targets, row.workspacePath ?? job.workspacePath);
34254
34320
  if (dispatchResults.length > 0 && dispatchResults.every((r) => !r.ok)) {
34255
34321
  status = "failed";
34256
34322
  failureReason = `all ${dispatchResults.length} target(s) failed: ${dispatchResults.map((r) => `${r.channelType}:${r.error ?? "?"}`).join("; ")}`;
@@ -35780,11 +35846,15 @@ function haloBaseVersion(haloVersion) {
35780
35846
  return semver.valid(stripped) === stripped ? stripped : null;
35781
35847
  }
35782
35848
  function defaultHubBase(hubRepo) {
35849
+ const gh = githubHubRepo(hubRepo);
35850
+ return gh ? `https://raw.githubusercontent.com/${gh.owner}/${gh.repo}/main/extensions` : null;
35851
+ }
35852
+ function githubHubRepo(hubRepo) {
35783
35853
  let h = hubRepo.trim();
35784
35854
  if (h === "" || h.startsWith("{{"))
35785
35855
  h = DEFAULT_HUB_REPO;
35786
35856
  const m = /^(?:https:\/\/github\.com\/)?([A-Za-z0-9][A-Za-z0-9_.-]*)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/?$/.exec(h);
35787
- return m ? `https://raw.githubusercontent.com/${m[1]}/${m[2]}/main/extensions` : null;
35857
+ return m ? { owner: m[1], repo: m[2] } : null;
35788
35858
  }
35789
35859
  function updateJsonUrl(id, source, hubBase2) {
35790
35860
  if (source)
@@ -35924,7 +35994,7 @@ function createCoreChecker(opts) {
35924
35994
  }
35925
35995
  function getCoreStatus(force) {
35926
35996
  checker ??= createCoreChecker({
35927
- haloVersion: "2.2.4",
35997
+ haloVersion: "2.2.6",
35928
35998
  fetchLatest: fetchNpmLatest,
35929
35999
  onChange: (status) => broadcast({ type: "updates:changed", scope: "core", status })
35930
36000
  });
@@ -36092,7 +36162,7 @@ function hubBase() {
36092
36162
  }
36093
36163
  function serverChecker() {
36094
36164
  checker2 ??= createExtensionsChecker({
36095
- haloVersion: "2.2.4",
36165
+ haloVersion: "2.2.6",
36096
36166
  installed: () => {
36097
36167
  const s = getSnapshot();
36098
36168
  return [...s.extensions, ...s.disabled];
@@ -36427,6 +36497,97 @@ var init_extensions = __esm({
36427
36497
  }
36428
36498
  });
36429
36499
 
36500
+ // ../server/dist/models/hub-update.js
36501
+ import fs80 from "node:fs";
36502
+ import os11 from "node:os";
36503
+ import path86 from "node:path";
36504
+ import JSZip3 from "jszip";
36505
+ function pickModelsRelease(releases) {
36506
+ if (!Array.isArray(releases))
36507
+ return null;
36508
+ for (const r of releases) {
36509
+ if (typeof r?.tag_name !== "string" || !/^models-v\d/.test(r.tag_name) || r.draft || r.prerelease)
36510
+ continue;
36511
+ const assets = Array.isArray(r.assets) ? r.assets : [];
36512
+ const zip = assets.find((a) => typeof a?.name === "string" && a.name.endsWith(".zip") && typeof a.browser_download_url === "string");
36513
+ if (zip)
36514
+ return { tag: r.tag_name, url: zip.browser_download_url };
36515
+ }
36516
+ return null;
36517
+ }
36518
+ async function unpackYamls(zipBuf, dest) {
36519
+ let zip;
36520
+ try {
36521
+ zip = await JSZip3.loadAsync(zipBuf);
36522
+ } catch (err) {
36523
+ throw new ModelsHubError(`release asset is not a zip: ${err instanceof Error ? err.message : String(err)}`);
36524
+ }
36525
+ const seen = /* @__PURE__ */ new Set();
36526
+ for (const entry of Object.values(zip.files)) {
36527
+ if (entry.dir)
36528
+ continue;
36529
+ const name = path86.posix.basename(entry.name.replace(/\\/g, "/"));
36530
+ if (!name.endsWith(".yaml") || name.startsWith("."))
36531
+ continue;
36532
+ if (seen.has(name))
36533
+ throw new ModelsHubError(`zip holds ${name} twice`);
36534
+ seen.add(name);
36535
+ fs80.writeFileSync(path86.join(dest, name), await entry.async("nodebuffer"));
36536
+ }
36537
+ }
36538
+ async function updateModelsFromHub(hubRepo, opts = {}) {
36539
+ const gh = githubHubRepo(hubRepo);
36540
+ if (!gh)
36541
+ throw new ModelsHubError(`not a GitHub hub: ${hubRepo}`);
36542
+ const hub = `${gh.owner}/${gh.repo}`;
36543
+ const api = `https://api.github.com/repos/${hub}/releases?per_page=100`;
36544
+ const res = await httpsGet(api, { maxBytes: RELEASES_MAX_BYTES, timeoutMs: RELEASES_TIMEOUT_MS, headers: { accept: "application/vnd.github+json" } });
36545
+ if (res.status !== 200)
36546
+ throw new ModelsHubError(`GitHub release API answered HTTP ${res.status} for ${hub} \u2014 rate-limited, private or wrong repo?`);
36547
+ let releases;
36548
+ try {
36549
+ releases = JSON.parse(res.body.toString("utf-8"));
36550
+ } catch {
36551
+ throw new ModelsHubError(`GitHub release API sent invalid JSON for ${hub}`);
36552
+ }
36553
+ const picked = pickModelsRelease(releases);
36554
+ if (!picked)
36555
+ throw new ModelsHubError(`no models-v* release with a .zip asset in ${hub}`);
36556
+ const zip = await httpsGet(picked.url, { maxBytes: ZIP_MAX_BYTES, timeoutMs: ZIP_TIMEOUT_MS, idleTimeoutMs: ZIP_IDLE_TIMEOUT_MS });
36557
+ if (zip.status !== 200)
36558
+ throw new ModelsHubError(`download of ${picked.tag} failed: HTTP ${zip.status}`);
36559
+ const tmp = fs80.mkdtempSync(path86.join(os11.tmpdir(), "halo-models-"));
36560
+ try {
36561
+ await unpackYamls(zip.body, tmp);
36562
+ let plan;
36563
+ try {
36564
+ plan = planModelsInstall(tmp, opts.dirs);
36565
+ } catch (err) {
36566
+ throw new ModelsHubError(`${picked.tag}: ${err instanceof Error ? err.message.replace(tmp, "the release zip") : String(err)}`);
36567
+ }
36568
+ const written = plan.needsConfirm && !opts.confirm ? null : applyModelsInstall(plan);
36569
+ return { hub, release: picked.tag, plan, written, summary: written ? summaryLine(plan, written) : null };
36570
+ } finally {
36571
+ fs80.rmSync(tmp, { recursive: true, force: true });
36572
+ }
36573
+ }
36574
+ var ModelsHubError, RELEASES_MAX_BYTES, ZIP_MAX_BYTES, RELEASES_TIMEOUT_MS, ZIP_TIMEOUT_MS, ZIP_IDLE_TIMEOUT_MS;
36575
+ var init_hub_update = __esm({
36576
+ "../server/dist/models/hub-update.js"() {
36577
+ "use strict";
36578
+ init_https_get();
36579
+ init_update_json();
36580
+ init_install();
36581
+ ModelsHubError = class extends Error {
36582
+ };
36583
+ RELEASES_MAX_BYTES = 2 * 1024 * 1024;
36584
+ ZIP_MAX_BYTES = 10 * 1024 * 1024;
36585
+ RELEASES_TIMEOUT_MS = 15e3;
36586
+ ZIP_TIMEOUT_MS = 6e4;
36587
+ ZIP_IDLE_TIMEOUT_MS = 2e4;
36588
+ }
36589
+ });
36590
+
36430
36591
  // ../server/dist/routes/updates.js
36431
36592
  import { Hono as Hono22 } from "hono";
36432
36593
  function statusOf(scope, force) {
@@ -36447,20 +36608,55 @@ function createUpdateRoutes() {
36447
36608
  const status = statusOf(body?.scope, true);
36448
36609
  return status ? c.json(status) : c.json({ error: SCOPE_ERROR }, 400);
36449
36610
  });
36611
+ app2.post("/models/update", async (c) => {
36612
+ const body = await c.req.json().catch(() => null);
36613
+ const hubRepo = getServerParam("extension", "hub_repo");
36614
+ if (!githubHubRepo(hubRepo)) {
36615
+ return c.json({ error: `Sync latest models supports GitHub hubs only (extension hub_repo is ${hubRepo}) \u2014 use /extension models in a chat, which also handles Gitea / Forgejo / GitLab` }, 400);
36616
+ }
36617
+ if (syncingModels)
36618
+ return c.json({ error: "A model sync is already running" }, 409);
36619
+ syncingModels = true;
36620
+ try {
36621
+ const r = await updateModelsFromHub(hubRepo, { confirm: body?.confirm === true });
36622
+ if (r.written)
36623
+ console.log(`[Models] synced ${r.hub} ${r.release}: ${r.summary}`);
36624
+ return c.json({
36625
+ hub: r.hub,
36626
+ release: r.release,
36627
+ needsConfirm: r.written === null,
36628
+ written: r.written ?? [],
36629
+ summary: r.summary,
36630
+ // `raw` (the yaml text) stays server-side.
36631
+ entries: r.plan.entries.map(({ raw: _raw, ...e }) => e)
36632
+ });
36633
+ } catch (err) {
36634
+ const msg = err instanceof Error ? err.message : String(err);
36635
+ console.log(`[Models] sync from ${hubRepo || "the default hub"} failed: ${msg}`);
36636
+ return c.json({ error: msg }, err instanceof ModelsHubError || err instanceof HttpsGetError ? 502 : 500);
36637
+ } finally {
36638
+ syncingModels = false;
36639
+ }
36640
+ });
36450
36641
  return app2;
36451
36642
  }
36452
- var SCOPE_ERROR;
36643
+ var SCOPE_ERROR, syncingModels;
36453
36644
  var init_updates = __esm({
36454
36645
  "../server/dist/routes/updates.js"() {
36455
36646
  "use strict";
36647
+ init_config();
36456
36648
  init_core_check();
36457
36649
  init_extension_check();
36650
+ init_update_json();
36651
+ init_https_get();
36652
+ init_hub_update();
36458
36653
  SCOPE_ERROR = 'scope must be "core" or "extensions"';
36654
+ syncingModels = false;
36459
36655
  }
36460
36656
  });
36461
36657
 
36462
36658
  // ../server/dist/models/watcher.js
36463
- import fs80 from "node:fs";
36659
+ import fs81 from "node:fs";
36464
36660
  function snapshot2() {
36465
36661
  const { effective } = loadProviders();
36466
36662
  return {
@@ -36488,7 +36684,7 @@ function reloadAndBroadcast() {
36488
36684
  }
36489
36685
  function openWatcher2(root) {
36490
36686
  try {
36491
- watcher2 = fs80.watch(root, { persistent: false, recursive: false }, () => scheduleFlush2());
36687
+ watcher2 = fs81.watch(root, { persistent: false, recursive: false }, () => scheduleFlush2());
36492
36688
  watcher2.on("error", (err) => {
36493
36689
  console.warn(`[Models] watch error on ${root}: ${err.message} \u2014 retrying in 1s`);
36494
36690
  closeWatcher2();
@@ -36514,7 +36710,7 @@ function start2() {
36514
36710
  return;
36515
36711
  const root = globalHubModelsDir();
36516
36712
  try {
36517
- fs80.mkdirSync(root, { recursive: true });
36713
+ fs81.mkdirSync(root, { recursive: true });
36518
36714
  } catch (err) {
36519
36715
  console.warn(`[Models] cannot create ${root}: ${err instanceof Error ? err.message : String(err)}`);
36520
36716
  }
@@ -36549,8 +36745,8 @@ var init_watcher2 = __esm({
36549
36745
  });
36550
36746
 
36551
36747
  // ../server/dist/evolution/notify.js
36552
- import fs81 from "node:fs";
36553
- import path86 from "node:path";
36748
+ import fs82 from "node:fs";
36749
+ import path87 from "node:path";
36554
36750
  import { and as and18, eq as eq22, isNull as isNull7 } from "drizzle-orm";
36555
36751
  function setEvoSessionRegistry(r) {
36556
36752
  _registry4 = r;
@@ -36569,7 +36765,7 @@ function formatAutoAppliedNotice(input) {
36569
36765
  }
36570
36766
  function readJson(file) {
36571
36767
  try {
36572
- return JSON.parse(fs81.readFileSync(file, "utf-8"));
36768
+ return JSON.parse(fs82.readFileSync(file, "utf-8"));
36573
36769
  } catch {
36574
36770
  return null;
36575
36771
  }
@@ -36583,10 +36779,10 @@ function notifyAutoApplied(db4) {
36583
36779
  try {
36584
36780
  const runId = JSON.parse(a.sourceRunIds)[0];
36585
36781
  const run = runId ? db4.select().from(evolutionRuns).where(eq22(evolutionRuns.id, runId)).get() : void 0;
36586
- if (run && fs81.existsSync(path86.join(a.workspacePath, ".halo"))) {
36782
+ if (run && fs82.existsSync(path87.join(a.workspacePath, ".halo"))) {
36587
36783
  const sm = registry2.getOrCreate(a.workspacePath);
36588
36784
  if (sm.getSessionById(run.sourceSession)) {
36589
- const verdict2 = readJson(path86.join(wsEvoRunDir(a.workspacePath, run.id), AUTO_APPLY_FILE));
36785
+ const verdict2 = readJson(path87.join(wsEvoRunDir(a.workspacePath, run.id), AUTO_APPLY_FILE));
36590
36786
  sm.notifySystem(run.sourceSession, formatAutoAppliedNotice({
36591
36787
  files: readPublishedFiles(a.workspacePath, a.id) ?? [],
36592
36788
  avg: verdict2?.score?.avg ?? null,
@@ -36872,14 +37068,14 @@ var init_ticker = __esm({
36872
37068
  });
36873
37069
 
36874
37070
  // ../server/dist/evolution/spawn.js
36875
- import fs82 from "node:fs";
36876
- import path87 from "node:path";
37071
+ import fs83 from "node:fs";
37072
+ import path88 from "node:path";
36877
37073
  import { spawn as spawn5 } from "node:child_process";
36878
37074
  import { fileURLToPath as fileURLToPath2 } from "node:url";
36879
37075
  function spawnEvoWrapper(wrapperPath, mode, id) {
36880
- fs82.mkdirSync(evoLogsDir(), { recursive: true });
37076
+ fs83.mkdirSync(evoLogsDir(), { recursive: true });
36881
37077
  const logPath = mode === "run" ? evoWrapperLogFile(id) : evoApplyLogFile(id);
36882
- const logFd = fs82.openSync(logPath, "a");
37078
+ const logFd = fs83.openSync(logPath, "a");
36883
37079
  let child;
36884
37080
  try {
36885
37081
  child = spawn5(process.execPath, [wrapperPath, `--mode=${mode}`, `--id=${id}`], {
@@ -36892,7 +37088,7 @@ function spawnEvoWrapper(wrapperPath, mode, id) {
36892
37088
  windowsHide: true
36893
37089
  });
36894
37090
  } finally {
36895
- fs82.closeSync(logFd);
37091
+ fs83.closeSync(logFd);
36896
37092
  }
36897
37093
  child.once("exit", (code, signal) => {
36898
37094
  if (code === 0)
@@ -36909,8 +37105,8 @@ var init_spawn = __esm({
36909
37105
  "use strict";
36910
37106
  init_child_env();
36911
37107
  init_paths();
36912
- __dirname2 = path87.dirname(fileURLToPath2(import.meta.url));
36913
- WRAPPER_PATH = path87.resolve(__dirname2, "evo-wrapper.js");
37108
+ __dirname2 = path88.dirname(fileURLToPath2(import.meta.url));
37109
+ WRAPPER_PATH = path88.resolve(__dirname2, "evo-wrapper.js");
36914
37110
  realEvoSpawner = (mode, id) => {
36915
37111
  spawnEvoWrapper(WRAPPER_PATH, mode, id);
36916
37112
  };
@@ -37730,12 +37926,12 @@ var init_handler3 = __esm({
37730
37926
  });
37731
37927
 
37732
37928
  // ../server/dist/routes/channel-accounts.js
37733
- import fs83 from "node:fs";
37929
+ import fs84 from "node:fs";
37734
37930
  function accountListFields(a) {
37735
37931
  return {
37736
37932
  accountId: a.accountId,
37737
37933
  workspacePath: a.workspacePath,
37738
- workspaceMissing: !fs83.existsSync(a.workspacePath),
37934
+ workspaceMissing: !fs84.existsSync(a.workspacePath),
37739
37935
  label: a.label,
37740
37936
  enabled: a.enabled,
37741
37937
  accessLevel: a.accessLevel,
@@ -38377,8 +38573,8 @@ var init_cdn = __esm({
38377
38573
  });
38378
38574
 
38379
38575
  // ../server/dist/channels/wechat/send-media.js
38380
- import fs84 from "node:fs/promises";
38381
- import path88 from "node:path";
38576
+ import fs85 from "node:fs/promises";
38577
+ import path89 from "node:path";
38382
38578
  function inferKind(filePath) {
38383
38579
  const cls = classifyMedia(filePath);
38384
38580
  if (cls === "image")
@@ -38390,7 +38586,7 @@ function inferKind(filePath) {
38390
38586
  async function sendMediaFile(params) {
38391
38587
  const { baseUrl, token, toUserId, contextToken, contextTokenAt, filePath } = params;
38392
38588
  const kind = params.kind ?? inferKind(filePath);
38393
- const plaintext = await fs84.readFile(filePath);
38589
+ const plaintext = await fs85.readFile(filePath);
38394
38590
  const prep = prepareUpload(plaintext);
38395
38591
  console.log(`[WeChat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
38396
38592
  const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
@@ -38428,7 +38624,7 @@ async function sendMediaFile(params) {
38428
38624
  aesKeyBase64,
38429
38625
  fileSizeCiphertext: prep.filesize,
38430
38626
  fileSize: prep.rawsize,
38431
- fileName: path88.basename(filePath)
38627
+ fileName: path89.basename(filePath)
38432
38628
  });
38433
38629
  const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
38434
38630
  const body = {
@@ -38591,8 +38787,8 @@ var init_login = __esm({
38591
38787
  });
38592
38788
 
38593
38789
  // ../server/dist/channels/wechat/handler.js
38594
- import fs85 from "node:fs";
38595
- import path89 from "node:path";
38790
+ import fs86 from "node:fs";
38791
+ import path90 from "node:path";
38596
38792
  import QRCode from "qrcode";
38597
38793
  function wxRoute(fromUserId, contextToken, contextTokenAt) {
38598
38794
  return (prev) => {
@@ -39013,7 +39209,7 @@ async function handleSlashCommand(args) {
39013
39209
  await reply(login.message);
39014
39210
  return true;
39015
39211
  }
39016
- const tmpPath = path89.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
39212
+ const tmpPath = path90.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
39017
39213
  await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
39018
39214
  await sendMediaFile({
39019
39215
  baseUrl: account.baseUrl,
@@ -39022,7 +39218,7 @@ async function handleSlashCommand(args) {
39022
39218
  contextToken,
39023
39219
  filePath: tmpPath
39024
39220
  });
39025
- await fs85.promises.unlink(tmpPath).catch(() => {
39221
+ await fs86.promises.unlink(tmpPath).catch(() => {
39026
39222
  });
39027
39223
  await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
39028
39224
  waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
@@ -39102,8 +39298,8 @@ var init_handler4 = __esm({
39102
39298
 
39103
39299
  // ../server/dist/routes/wechat.js
39104
39300
  import { Hono as Hono24 } from "hono";
39105
- import path90 from "node:path";
39106
- import fs86 from "node:fs";
39301
+ import path91 from "node:path";
39302
+ import fs87 from "node:fs";
39107
39303
  function createWechatRoutes(deps) {
39108
39304
  const { db: db4, channel } = deps;
39109
39305
  const app2 = new Hono24();
@@ -39112,7 +39308,7 @@ function createWechatRoutes(deps) {
39112
39308
  return {
39113
39309
  ...stripSecrets(acc),
39114
39310
  workspacePath: acc.workspacePath,
39115
- workspaceMissing: !fs86.existsSync(acc.workspacePath)
39311
+ workspaceMissing: !fs87.existsSync(acc.workspacePath)
39116
39312
  };
39117
39313
  });
39118
39314
  return c.json({ accounts });
@@ -39128,7 +39324,7 @@ function createWechatRoutes(deps) {
39128
39324
  return c.json({ error: "sessionKey required" }, 400);
39129
39325
  if (!body.workspacePath)
39130
39326
  return c.json({ error: "workspacePath required" }, 400);
39131
- if (!path90.isAbsolute(body.workspacePath))
39327
+ if (!path91.isAbsolute(body.workspacePath))
39132
39328
  return c.json({ error: "workspacePath must be absolute" }, 400);
39133
39329
  const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
39134
39330
  if (levelError)
@@ -39341,7 +39537,7 @@ var init_descriptor2 = __esm({
39341
39537
  import { Hono as Hono25 } from "hono";
39342
39538
  import { streamSSE as streamSSE3 } from "hono/streaming";
39343
39539
  import crypto15 from "node:crypto";
39344
- import fs87 from "node:fs";
39540
+ import fs88 from "node:fs";
39345
39541
  function streamWithKeepalive(c, chunks) {
39346
39542
  return streamSSE3(c, async (stream) => {
39347
39543
  const timer2 = setInterval(() => {
@@ -39537,30 +39733,30 @@ function createWebRoutes(deps) {
39537
39733
  const filePath = c.req.query("path");
39538
39734
  if (!filePath)
39539
39735
  return c.json({ error: "path required" }, 400);
39540
- const path98 = await import("node:path");
39541
- const resolved = path98.default.resolve(account.workspacePath, filePath);
39542
- const root = path98.default.resolve(account.workspacePath);
39543
- if (resolved !== root && !resolved.startsWith(root + path98.default.sep)) {
39736
+ const path99 = await import("node:path");
39737
+ const resolved = path99.default.resolve(account.workspacePath, filePath);
39738
+ const root = path99.default.resolve(account.workspacePath);
39739
+ if (resolved !== root && !resolved.startsWith(root + path99.default.sep)) {
39544
39740
  return c.json({ error: "path traversal not allowed" }, 403);
39545
39741
  }
39546
39742
  let real;
39547
39743
  let realRoot;
39548
39744
  try {
39549
- real = fs87.realpathSync(resolved);
39550
- realRoot = fs87.realpathSync(root);
39745
+ real = fs88.realpathSync(resolved);
39746
+ realRoot = fs88.realpathSync(root);
39551
39747
  } catch {
39552
39748
  return c.json({ error: "file not found" }, 404);
39553
39749
  }
39554
- if (real !== realRoot && !real.startsWith(realRoot + path98.default.sep)) {
39750
+ if (real !== realRoot && !real.startsWith(realRoot + path99.default.sep)) {
39555
39751
  return c.json({ error: "path traversal not allowed" }, 403);
39556
39752
  }
39557
39753
  if (isHiddenWorkspacePath(real, realRoot)) {
39558
39754
  return c.json({ error: "path is not accessible" }, 403);
39559
39755
  }
39560
- const stat = fs87.statSync(real);
39756
+ const stat = fs88.statSync(real);
39561
39757
  if (stat.isDirectory())
39562
39758
  return c.json({ error: "cannot serve directory" }, 400);
39563
- const ext = path98.default.extname(real).toLowerCase();
39759
+ const ext = path99.default.extname(real).toLowerCase();
39564
39760
  const nonImageMimes = {
39565
39761
  ".mp4": "video/mp4",
39566
39762
  ".webm": "video/webm",
@@ -39568,8 +39764,8 @@ function createWebRoutes(deps) {
39568
39764
  ".txt": "text/plain"
39569
39765
  };
39570
39766
  const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
39571
- const fileName = path98.default.basename(resolved);
39572
- const data = fs87.readFileSync(real);
39767
+ const fileName = path99.default.basename(resolved);
39768
+ const data = fs88.readFileSync(real);
39573
39769
  c.header("content-type", contentType);
39574
39770
  c.header("content-disposition", `inline; filename="${fileName}"`);
39575
39771
  return c.body(data);
@@ -39791,10 +39987,10 @@ async function authTest(botToken) {
39791
39987
  return slackPost("auth.test", botToken, {});
39792
39988
  }
39793
39989
  async function uploadFile(args) {
39794
- const fs92 = await import("node:fs");
39795
- const path98 = await import("node:path");
39796
- const filename = args.filename ?? path98.basename(args.filePath);
39797
- const stat = fs92.statSync(args.filePath);
39990
+ const fs93 = await import("node:fs");
39991
+ const path99 = await import("node:path");
39992
+ const filename = args.filename ?? path99.basename(args.filePath);
39993
+ const stat = fs93.statSync(args.filePath);
39798
39994
  if (!stat.isFile())
39799
39995
  throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
39800
39996
  const length = stat.size;
@@ -39812,7 +40008,7 @@ async function uploadFile(args) {
39812
40008
  if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
39813
40009
  throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
39814
40010
  }
39815
- const buf = fs92.readFileSync(args.filePath);
40011
+ const buf = fs93.readFileSync(args.filePath);
39816
40012
  const step2 = await fetch(step1Parsed.upload_url, {
39817
40013
  method: "POST",
39818
40014
  body: buf
@@ -39957,7 +40153,7 @@ var init_api2 = __esm({
39957
40153
  });
39958
40154
 
39959
40155
  // ../server/dist/channels/slack/handler.js
39960
- import path91 from "node:path";
40156
+ import path92 from "node:path";
39961
40157
  import { WebSocket as WebSocket3 } from "ws";
39962
40158
  function slashToBang(text6, commandNames) {
39963
40159
  if (commandNames.length === 0)
@@ -40088,7 +40284,7 @@ function startSlackChannel(deps) {
40088
40284
  const route = bridge.getRoute(sessionId);
40089
40285
  if (!account || !route)
40090
40286
  return;
40091
- const resolved = path91.resolve(filePath);
40287
+ const resolved = path92.resolve(filePath);
40092
40288
  await sendMediaOrReport({
40093
40289
  filePath,
40094
40290
  account,
@@ -40732,10 +40928,10 @@ async function replyMessage(args) {
40732
40928
  return { message_id: parsed.data?.message_id ?? "" };
40733
40929
  }
40734
40930
  async function uploadImage(args) {
40735
- const fs92 = await import("node:fs");
40736
- const path98 = await import("node:path");
40737
- const buf = fs92.readFileSync(args.filePath);
40738
- const filename = path98.basename(args.filePath);
40931
+ const fs93 = await import("node:fs");
40932
+ const path99 = await import("node:path");
40933
+ const buf = fs93.readFileSync(args.filePath);
40934
+ const filename = path99.basename(args.filePath);
40739
40935
  const send2 = async (token) => {
40740
40936
  const form = new FormData();
40741
40937
  form.append("image_type", "message");
@@ -40751,10 +40947,10 @@ async function uploadImage(args) {
40751
40947
  return { imageKey: parsed.data.image_key };
40752
40948
  }
40753
40949
  async function uploadFile2(args) {
40754
- const fs92 = await import("node:fs");
40755
- const path98 = await import("node:path");
40756
- const buf = fs92.readFileSync(args.filePath);
40757
- const filename = args.filename ?? path98.basename(args.filePath);
40950
+ const fs93 = await import("node:fs");
40951
+ const path99 = await import("node:path");
40952
+ const buf = fs93.readFileSync(args.filePath);
40953
+ const filename = args.filename ?? path99.basename(args.filePath);
40758
40954
  const send2 = async (token) => {
40759
40955
  const form = new FormData();
40760
40956
  form.append("file_type", args.fileType);
@@ -40821,8 +41017,7 @@ var init_api3 = __esm({
40821
41017
  });
40822
41018
 
40823
41019
  // ../server/dist/channels/feishu/handler.js
40824
- import path92 from "node:path";
40825
- import * as Lark from "@larksuiteoapi/node-sdk";
41020
+ import path93 from "node:path";
40826
41021
  function buildSessionPrefixForThread2(chatId, rootId) {
40827
41022
  return sessionPrefix("feishu", `${chatId}:${rootId}`);
40828
41023
  }
@@ -41024,7 +41219,7 @@ function startFeishuChannel(deps) {
41024
41219
  const route = bridge.getRoute(sessionId);
41025
41220
  if (!account || !route)
41026
41221
  return;
41027
- const resolved = path92.resolve(filePath);
41222
+ const resolved = path93.resolve(filePath);
41028
41223
  await sendMediaOrReport({
41029
41224
  filePath,
41030
41225
  account,
@@ -41069,13 +41264,16 @@ function startFeishuChannel(deps) {
41069
41264
  const state = ensureState(accountId);
41070
41265
  if (state.stopped || state.wsClient)
41071
41266
  return;
41072
- const wsClient = new Lark.WSClient({
41267
+ const { WSClient: WSClient2, EventDispatcher, LoggerLevel } = await import("./feishu-sdk.cjs");
41268
+ if (state.stopped || state.wsClient)
41269
+ return;
41270
+ const wsClient = new WSClient2({
41073
41271
  appId: account.appId,
41074
41272
  appSecret: account.appSecret,
41075
- loggerLevel: Lark.LoggerLevel.warn
41273
+ loggerLevel: LoggerLevel.warn
41076
41274
  });
41077
41275
  state.wsClient = wsClient;
41078
- const dispatcher = new Lark.EventDispatcher({});
41276
+ const dispatcher = new EventDispatcher({});
41079
41277
  dispatcher.register({
41080
41278
  "im.message.receive_v1": async (data) => {
41081
41279
  dispatchEvent(accountId, data);
@@ -41191,7 +41389,7 @@ async function replyToInbound(args) {
41191
41389
  async function sendFeishuMedia(args) {
41192
41390
  const { account, inboundMessageId, isP2P, chatId, filePath } = args;
41193
41391
  const cls = classifyMedia(filePath);
41194
- const ext = path92.extname(filePath).toLowerCase();
41392
+ const ext = path93.extname(filePath).toLowerCase();
41195
41393
  if (cls === "image") {
41196
41394
  const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
41197
41395
  await replyToInboundRaw({
@@ -41586,9 +41784,9 @@ var init_event_adapter5 = __esm({
41586
41784
  });
41587
41785
 
41588
41786
  // ../server/dist/channels/wecom/handler.js
41589
- import path93 from "node:path";
41590
- import fs88 from "node:fs/promises";
41591
- import { WSClient as WSClient2, generateReqId } from "@wecom/aibot-node-sdk";
41787
+ import path94 from "node:path";
41788
+ import fs89 from "node:fs/promises";
41789
+ import { WSClient, generateReqId } from "@wecom/aibot-node-sdk";
41592
41790
  function pickConversation(body) {
41593
41791
  const userid = body.from?.userid ?? "unknown";
41594
41792
  if (body.chattype === "group" && body.chatid) {
@@ -41710,13 +41908,13 @@ async function replyText(wsClient, route, text6) {
41710
41908
  }
41711
41909
  async function sendWecomMedia(args) {
41712
41910
  const { wsClient, route, filePath } = args;
41713
- const ext = path93.extname(filePath).toLowerCase();
41911
+ const ext = path94.extname(filePath).toLowerCase();
41714
41912
  const cls = classifyMedia(filePath);
41715
41913
  const type = cls === "image" && [".png", ".jpg", ".jpeg", ".gif"].includes(ext) ? "image" : cls === "video" && ext === ".mp4" ? "video" : "file";
41716
- const buf = await fs88.readFile(filePath);
41914
+ const buf = await fs89.readFile(filePath);
41717
41915
  if (buf.length > MAX_WECOM_DOWNLOAD_BYTES)
41718
41916
  throw new Error("file exceeds 20MB");
41719
- const { media_id: mediaId } = await wsClient.uploadMedia(buf, { type, filename: path93.basename(filePath) });
41917
+ const { media_id: mediaId } = await wsClient.uploadMedia(buf, { type, filename: path94.basename(filePath) });
41720
41918
  if (route.reqId)
41721
41919
  await wsClient.replyMedia({ headers: { req_id: route.reqId } }, type, mediaId);
41722
41920
  else
@@ -41761,7 +41959,7 @@ function startWecomChannel(deps) {
41761
41959
  const wsClient = states.get(accountId)?.wsClient;
41762
41960
  if (!account || !route || !wsClient)
41763
41961
  return;
41764
- const resolved = path93.resolve(filePath);
41962
+ const resolved = path94.resolve(filePath);
41765
41963
  await sendMediaOrReport({
41766
41964
  filePath,
41767
41965
  account,
@@ -41820,7 +42018,7 @@ function startWecomChannel(deps) {
41820
42018
  const state = ensureState(accountId);
41821
42019
  if (state.stopped || state.wsClient)
41822
42020
  return;
41823
- const wsClient = new WSClient2({
42021
+ const wsClient = new WSClient({
41824
42022
  botId: account.botId,
41825
42023
  secret: account.secret,
41826
42024
  maxReconnectAttempts: -1,
@@ -42249,17 +42447,17 @@ import { cors } from "hono/cors";
42249
42447
  import { serve } from "@hono/node-server";
42250
42448
  import { serveStatic } from "@hono/node-server/serve-static";
42251
42449
  import { WebSocketServer as WebSocketServer5 } from "ws";
42252
- import path94 from "node:path";
42253
- import fs89 from "node:fs";
42450
+ import path95 from "node:path";
42451
+ import fs90 from "node:fs";
42254
42452
  import { spawnSync as spawnSync4 } from "node:child_process";
42255
42453
  import { homedir as homedir26 } from "node:os";
42256
42454
  import { fileURLToPath as fileURLToPath3 } from "node:url";
42257
42455
  function findProjectRoot() {
42258
42456
  let dir = process.cwd();
42259
42457
  for (let i = 0; i < 32; i++) {
42260
- if (fs89.existsSync(path94.join(dir, "pnpm-workspace.yaml")))
42458
+ if (fs90.existsSync(path95.join(dir, "pnpm-workspace.yaml")))
42261
42459
  return dir;
42262
- const parent = path94.dirname(dir);
42460
+ const parent = path95.dirname(dir);
42263
42461
  if (parent === dir)
42264
42462
  break;
42265
42463
  dir = parent;
@@ -42269,15 +42467,15 @@ function findProjectRoot() {
42269
42467
  return process.cwd();
42270
42468
  }
42271
42469
  function acquireSingleInstanceLock(lockFile) {
42272
- fs89.mkdirSync(path94.dirname(lockFile), { recursive: true });
42273
- const fd = fs89.openSync(lockFile, fs89.constants.O_RDWR | fs89.constants.O_CREAT, 420);
42470
+ fs90.mkdirSync(path95.dirname(lockFile), { recursive: true });
42471
+ const fd = fs90.openSync(lockFile, fs90.constants.O_RDWR | fs90.constants.O_CREAT, 420);
42274
42472
  const result = spawnSync4("flock", ["-n", "3"], {
42275
42473
  stdio: ["ignore", "ignore", "ignore", fd]
42276
42474
  });
42277
42475
  if (result.error) {
42278
42476
  const content = readLockPid(fd);
42279
42477
  if (content > 0 && isProcessAlive(content)) {
42280
- fs89.closeSync(fd);
42478
+ fs90.closeSync(fd);
42281
42479
  console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
42282
42480
  process.exit(1);
42283
42481
  }
@@ -42286,19 +42484,19 @@ function acquireSingleInstanceLock(lockFile) {
42286
42484
  if (content > 0 && !isProcessAlive(content)) {
42287
42485
  console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
42288
42486
  } else {
42289
- fs89.closeSync(fd);
42487
+ fs90.closeSync(fd);
42290
42488
  console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
42291
42489
  process.exit(1);
42292
42490
  }
42293
42491
  }
42294
- fs89.ftruncateSync(fd);
42295
- fs89.writeSync(fd, String(process.pid));
42296
- fs89.fsyncSync(fd);
42492
+ fs90.ftruncateSync(fd);
42493
+ fs90.writeSync(fd, String(process.pid));
42494
+ fs90.fsyncSync(fd);
42297
42495
  }
42298
42496
  function readLockPid(fd) {
42299
42497
  try {
42300
42498
  const buf = Buffer.alloc(32);
42301
- const n = fs89.readSync(fd, buf, 0, 32, 0);
42499
+ const n = fs90.readSync(fd, buf, 0, 32, 0);
42302
42500
  return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
42303
42501
  } catch {
42304
42502
  return 0;
@@ -42326,7 +42524,7 @@ function isProcessAlive(pid) {
42326
42524
  }
42327
42525
  }
42328
42526
  function readGitSha() {
42329
- if (!fs89.existsSync(path94.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs89.existsSync(path94.join(PROJECT_ROOT, ".git")))
42527
+ if (!fs90.existsSync(path95.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs90.existsSync(path95.join(PROJECT_ROOT, ".git")))
42330
42528
  return null;
42331
42529
  try {
42332
42530
  const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
@@ -42347,14 +42545,14 @@ function applySandboxPaths() {
42347
42545
  }
42348
42546
  function resolveFrontendDir() {
42349
42547
  if (process.env.HALO_FRONTEND_DIR)
42350
- return path94.resolve(process.env.HALO_FRONTEND_DIR);
42351
- const bundled = path94.resolve(__dirname3, "..", "admin-out");
42352
- if (fs89.existsSync(path94.join(bundled, "index.html")))
42548
+ return path95.resolve(process.env.HALO_FRONTEND_DIR);
42549
+ const bundled = path95.resolve(__dirname3, "..", "admin-out");
42550
+ if (fs90.existsSync(path95.join(bundled, "index.html")))
42353
42551
  return bundled;
42354
- const sibling = path94.resolve(__dirname3, "..", "..", "admin", "out");
42355
- if (fs89.existsSync(path94.join(sibling, "index.html")))
42552
+ const sibling = path95.resolve(__dirname3, "..", "..", "admin", "out");
42553
+ if (fs90.existsSync(path95.join(sibling, "index.html")))
42356
42554
  return sibling;
42357
- return path94.resolve(PROJECT_ROOT, "packages", "admin", "out");
42555
+ return path95.resolve(PROJECT_ROOT, "packages", "admin", "out");
42358
42556
  }
42359
42557
  async function gracefulShutdown(signal) {
42360
42558
  beginShutdown();
@@ -42461,13 +42659,13 @@ var init_dist2 = __esm({
42461
42659
  init_sandbox();
42462
42660
  init_init();
42463
42661
  init_git_ssh();
42464
- __dirname3 = path94.dirname(fileURLToPath3(import.meta.url));
42662
+ __dirname3 = path95.dirname(fileURLToPath3(import.meta.url));
42465
42663
  PORT = config.server.port;
42466
- HALO_VERSION = "2.2.4";
42664
+ HALO_VERSION = "2.2.6";
42467
42665
  PROJECT_ROOT = findProjectRoot();
42468
- HALO_HOME4 = path94.join(homedir26(), ".halo");
42666
+ HALO_HOME4 = path95.join(homedir26(), ".halo");
42469
42667
  GIT_SHA = readGitSha();
42470
- if (!fs89.existsSync(path94.join(HALO_HOME4, "global", ".template-version"))) {
42668
+ if (!fs90.existsSync(path95.join(HALO_HOME4, "global", ".template-version"))) {
42471
42669
  process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
42472
42670
  process.exit(1);
42473
42671
  }
@@ -42478,7 +42676,7 @@ var init_dist2 = __esm({
42478
42676
  process.exit(1);
42479
42677
  }
42480
42678
  if (!AGENTCORE)
42481
- acquireSingleInstanceLock(path94.join(HALO_HOME4, "global", "server.lock"));
42679
+ acquireSingleInstanceLock(path95.join(HALO_HOME4, "global", "server.lock"));
42482
42680
  commandRegistry.assertDispatchCommands(DISPATCH_COMMANDS);
42483
42681
  await initObservability();
42484
42682
  initLogger();
@@ -42549,14 +42747,14 @@ var init_dist2 = __esm({
42549
42747
  app.route("/api", createJupyterRoutes(jupyter));
42550
42748
  }
42551
42749
  start2();
42552
- channelDb = createChannelDb(path94.join(HALO_HOME4, "secrets"));
42750
+ channelDb = createChannelDb(path95.join(HALO_HOME4, "secrets"));
42553
42751
  setChannelDb(channelDb);
42554
42752
  nodeHub = AGENTCORE ? null : createNodeHub({ db: channelDb });
42555
42753
  setNodeHub(nodeHub);
42556
- setEvoDb(createEvoDb(path94.join(HALO_HOME4, "global")));
42557
- setCronDb(createCronDb(path94.join(HALO_HOME4, "global")));
42754
+ setEvoDb(createEvoDb(path95.join(HALO_HOME4, "global")));
42755
+ setCronDb(createCronDb(path95.join(HALO_HOME4, "global")));
42558
42756
  if (!AGENTCORE)
42559
- setRunsDb(createRunsDb(path94.join(HALO_HOME4, "global")));
42757
+ setRunsDb(createRunsDb(path95.join(HALO_HOME4, "global")));
42560
42758
  setEvoSpawner(realEvoSpawner);
42561
42759
  if (!AGENTCORE) {
42562
42760
  startEvoTicker();
@@ -42572,7 +42770,7 @@ var init_dist2 = __esm({
42572
42770
  setEvoSessionRegistry(registry);
42573
42771
  if (OWNS_RUNTIMES && !AGENTCORE) {
42574
42772
  for (const ws of listRunningWorkspaces()) {
42575
- if (!fs89.existsSync(path94.join(ws, ".halo")))
42773
+ if (!fs90.existsSync(path95.join(ws, ".halo")))
42576
42774
  continue;
42577
42775
  if (!claimWorkspaceRuntime(ws))
42578
42776
  continue;
@@ -42600,7 +42798,7 @@ var init_dist2 = __esm({
42600
42798
  startCronDaemon();
42601
42799
  }
42602
42800
  if (!AGENTCORE) {
42603
- setA2ADb(createA2ADb(path94.join(HALO_HOME4, "global")));
42801
+ setA2ADb(createA2ADb(path95.join(HALO_HOME4, "global")));
42604
42802
  setA2AOutboundRegistry(registry);
42605
42803
  app.route("/a2a", createA2ARoutes({ registry, ownsRuntimes: OWNS_RUNTIMES }));
42606
42804
  app.route("/", createA2APushRoutes());
@@ -42617,7 +42815,7 @@ var init_dist2 = __esm({
42617
42815
  FRONTEND_DIR = resolveFrontendDir();
42618
42816
  if (!AGENTCORE) {
42619
42817
  app.use("/*", serveStatic({
42620
- root: path94.relative(process.cwd(), FRONTEND_DIR),
42818
+ root: path95.relative(process.cwd(), FRONTEND_DIR),
42621
42819
  // Next's `/_next/static/` files are content-hashed (under a buildId dir),
42622
42820
  // so they can be cached forever. Only on a found file — the SPA fallback
42623
42821
  // below must stay uncached, or a missing chunk caches index.html as js.
@@ -42631,9 +42829,9 @@ var init_dist2 = __esm({
42631
42829
  app.get("/*", (c) => {
42632
42830
  if (c.req.path.startsWith("/_next/static/"))
42633
42831
  return c.notFound();
42634
- const indexPath = path94.join(FRONTEND_DIR, "index.html");
42832
+ const indexPath = path95.join(FRONTEND_DIR, "index.html");
42635
42833
  try {
42636
- const html = fs89.readFileSync(indexPath, "utf-8");
42834
+ const html = fs90.readFileSync(indexPath, "utf-8");
42637
42835
  return c.html(html);
42638
42836
  } catch {
42639
42837
  return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
@@ -42724,10 +42922,10 @@ __export(agentcore_front_exports, {
42724
42922
  workerEnv: () => workerEnv
42725
42923
  });
42726
42924
  import http3 from "node:http";
42727
- import fs90 from "node:fs";
42925
+ import fs91 from "node:fs";
42728
42926
  import fsp2 from "node:fs/promises";
42729
- import os11 from "node:os";
42730
- import path95 from "node:path";
42927
+ import os12 from "node:os";
42928
+ import path96 from "node:path";
42731
42929
  import crypto16 from "node:crypto";
42732
42930
  import { pipeline as pipeline2 } from "node:stream";
42733
42931
  import { execFile as execFile2, spawn as spawn6 } from "node:child_process";
@@ -42750,44 +42948,44 @@ function mountLayers(dir, mountinfo) {
42750
42948
  }).length;
42751
42949
  }
42752
42950
  function skipTemplateEntry(rel) {
42753
- const name = path95.basename(rel);
42754
- return rel === path95.join(".halo", "sessions") || /\.db(-wal|-shm|-journal)?$/.test(name) || name === "runtime.lock" || name.startsWith("agentcore.lease");
42951
+ const name = path96.basename(rel);
42952
+ return rel === path96.join(".halo", "sessions") || /\.db(-wal|-shm|-journal)?$/.test(name) || name === "runtime.lock" || name.startsWith("agentcore.lease");
42755
42953
  }
42756
42954
  function chownTree(dir, owner, sid) {
42757
42955
  try {
42758
- fs90.lchownSync(dir, owner.uid, owner.gid);
42759
- for (const rel of fs90.readdirSync(dir, { recursive: true }))
42760
- fs90.lchownSync(path95.join(dir, rel), owner.uid, owner.gid);
42956
+ fs91.lchownSync(dir, owner.uid, owner.gid);
42957
+ for (const rel of fs91.readdirSync(dir, { recursive: true }))
42958
+ fs91.lchownSync(path96.join(dir, rel), owner.uid, owner.gid);
42761
42959
  } catch (err) {
42762
42960
  console.warn(`[AgentCore] front: chown ${sid} to ${owner.uid}:${owner.gid} failed (${err.message}) \u2014 serving it as is`);
42763
42961
  }
42764
42962
  }
42765
42963
  function ensureSessionDir(base, sid, owner) {
42766
- const dir = path95.join(base, sid);
42767
- if (fs90.existsSync(dir))
42964
+ const dir = path96.join(base, sid);
42965
+ if (fs91.existsSync(dir))
42768
42966
  return false;
42769
- const tmp = path95.join(base, `.tmp-${crypto16.randomBytes(8).toString("hex")}`);
42770
- fs90.mkdirSync(tmp, { mode: 448 });
42967
+ const tmp = path96.join(base, `.tmp-${crypto16.randomBytes(8).toString("hex")}`);
42968
+ fs91.mkdirSync(tmp, { mode: 448 });
42771
42969
  try {
42772
- const template = path95.join(base, TEMPLATE_DIR);
42773
- if (fs90.statSync(template, { throwIfNoEntry: false })?.isDirectory()) {
42774
- fs90.cpSync(template, tmp, { recursive: true, verbatimSymlinks: true, filter: (src) => !skipTemplateEntry(path95.relative(template, src)) });
42970
+ const template = path96.join(base, TEMPLATE_DIR);
42971
+ if (fs91.statSync(template, { throwIfNoEntry: false })?.isDirectory()) {
42972
+ fs91.cpSync(template, tmp, { recursive: true, verbatimSymlinks: true, filter: (src) => !skipTemplateEntry(path96.relative(template, src)) });
42775
42973
  }
42776
42974
  if (owner)
42777
42975
  chownTree(tmp, owner, sid);
42778
- if (fs90.readdirSync(tmp).length === 0) {
42779
- fs90.rmdirSync(tmp);
42780
- fs90.mkdirSync(dir, { mode: 448 });
42976
+ if (fs91.readdirSync(tmp).length === 0) {
42977
+ fs91.rmdirSync(tmp);
42978
+ fs91.mkdirSync(dir, { mode: 448 });
42781
42979
  if (owner)
42782
42980
  chownTree(dir, owner, sid);
42783
42981
  return true;
42784
42982
  }
42785
- fs90.renameSync(tmp, dir);
42983
+ fs91.renameSync(tmp, dir);
42786
42984
  return true;
42787
42985
  } catch (err) {
42788
- fs90.rmSync(tmp, { recursive: true, force: true });
42986
+ fs91.rmSync(tmp, { recursive: true, force: true });
42789
42987
  const code = err.code;
42790
- if ((code === "EEXIST" || code === "ENOTEMPTY") && fs90.existsSync(dir))
42988
+ if ((code === "EEXIST" || code === "ENOTEMPTY") && fs91.existsSync(dir))
42791
42989
  return false;
42792
42990
  throw err;
42793
42991
  }
@@ -42831,7 +43029,7 @@ async function runViewSetup(base, sessionDir, deps = SYSTEM_SETUP) {
42831
43029
  const left = mountLayers(base, deps.mountinfo());
42832
43030
  if (left > 0)
42833
43031
  throw new Error(`view setup: ${base} still has ${left} mount(s) after ${MAX_UMOUNTS} \xD7 umount -l`);
42834
- if (fs90.existsSync(sessionDir))
43032
+ if (fs91.existsSync(sessionDir))
42835
43033
  throw new Error(`view setup: ${base} is still visible after umount -l`);
42836
43034
  }
42837
43035
  function workerEnv(src, front) {
@@ -42846,19 +43044,19 @@ function workerEnv(src, front) {
42846
43044
  return env;
42847
43045
  }
42848
43046
  async function copyMediaFile(p, root, dest, max = MAX_MEDIA_BYTES) {
42849
- if (!path95.isAbsolute(p))
43047
+ if (!path96.isAbsolute(p))
42850
43048
  return "not an absolute path";
42851
43049
  let pfd;
42852
43050
  try {
42853
- pfd = fs90.openSync(p, O_PATH);
43051
+ pfd = fs91.openSync(p, O_PATH);
42854
43052
  } catch (err) {
42855
43053
  return `cannot open (${err.code})`;
42856
43054
  }
42857
43055
  try {
42858
- const link = fs90.readlinkSync(`/proc/self/fd/${pfd}`);
43056
+ const link = fs91.readlinkSync(`/proc/self/fd/${pfd}`);
42859
43057
  if (!link.startsWith(`${root}/`))
42860
43058
  return `not under ${root}/`;
42861
- const st = fs90.fstatSync(pfd);
43059
+ const st = fs91.fstatSync(pfd);
42862
43060
  if (!st.isFile())
42863
43061
  return "not a regular file";
42864
43062
  if (st.size > max)
@@ -42885,7 +43083,7 @@ async function copyMediaFile(p, root, dest, max = MAX_MEDIA_BYTES) {
42885
43083
  await src.close();
42886
43084
  }
42887
43085
  } finally {
42888
- fs90.closeSync(pfd);
43086
+ fs91.closeSync(pfd);
42889
43087
  }
42890
43088
  }
42891
43089
  async function copyMediaLines(text6, root, dir, sid) {
@@ -42897,16 +43095,16 @@ async function copyMediaLines(text6, root, dir, sid) {
42897
43095
  out.push(line);
42898
43096
  continue;
42899
43097
  }
42900
- const sub = path95.join(dir, String(n++));
42901
- fs90.mkdirSync(sub);
42902
- const name = path95.basename(m[1]).replace(/[\x00-\x1f\x7f]/g, "_").replace(/^\.+/, "") || "file";
42903
- const dest = path95.join(sub, name);
43098
+ const sub = path96.join(dir, String(n++));
43099
+ fs91.mkdirSync(sub);
43100
+ const name = path96.basename(m[1]).replace(/[\x00-\x1f\x7f]/g, "_").replace(/^\.+/, "") || "file";
43101
+ const dest = path96.join(sub, name);
42904
43102
  const why = await copyMediaFile(m[1], root, dest).catch((err) => err.message);
42905
43103
  if (why === null) {
42906
43104
  out.push(`MEDIA:${dest}`);
42907
43105
  continue;
42908
43106
  }
42909
- fs90.rmSync(sub, { recursive: true, force: true });
43107
+ fs91.rmSync(sub, { recursive: true, force: true });
42910
43108
  console.warn(`[AgentCore] notify ${sid}: MEDIA dropped (${why}): ${JSON.stringify(m[1].slice(0, 300))}`);
42911
43109
  }
42912
43110
  return out.join("\n");
@@ -42950,7 +43148,7 @@ function readBody(req, max) {
42950
43148
  });
42951
43149
  }
42952
43150
  async function startAgentCoreFront(o) {
42953
- const base = fs90.realpathSync(o.base);
43151
+ const base = fs91.realpathSync(o.base);
42954
43152
  const workerPort = o.workerPort ?? WORKER_PORT;
42955
43153
  const token = crypto16.randomBytes(32).toString("hex");
42956
43154
  const isMounted = o.isMounted ?? ((b) => mountLayers(b, readMountinfo()) > 0);
@@ -43012,11 +43210,11 @@ async function startAgentCoreFront(o) {
43012
43210
  preparing++;
43013
43211
  let dir = "";
43014
43212
  const cleanup = () => {
43015
- fs90.rmSync(dir, { recursive: true, force: true });
43213
+ fs91.rmSync(dir, { recursive: true, force: true });
43016
43214
  settle();
43017
43215
  };
43018
43216
  try {
43019
- dir = fs90.mkdtempSync(path95.join(os11.tmpdir(), "halo-notify-"));
43217
+ dir = fs91.mkdtempSync(path96.join(os12.tmpdir(), "halo-notify-"));
43020
43218
  const text6 = await copyMediaLines(b.text, viewRoot, dir, b.session);
43021
43219
  runNotify(cmd, text6, { HALO_SESSION_ID: b.session, HALO_RUN_STATUS: b.status, HALO_WORKSPACE: dir }, dir, cleanup);
43022
43220
  } catch (err) {
@@ -43031,7 +43229,7 @@ async function startAgentCoreFront(o) {
43031
43229
  const activate = async (sid) => {
43032
43230
  if (!isMounted(base))
43033
43231
  throw Object.assign(new Error(`${base} is not a mount point (the EFS not mounted yet, or HALO_WORKSPACE is not its mount path) \u2014 refusing to serve the image's empty dir`), { status: 503 });
43034
- const sessionDir = path95.join(base, sid);
43232
+ const sessionDir = path96.join(base, sid);
43035
43233
  if (ensureSessionDir(base, sid, { uid: WORKER_UID, gid: WORKER_GID }))
43036
43234
  console.log(`[AgentCore] front: created ${sessionDir}`);
43037
43235
  try {
@@ -43186,7 +43384,7 @@ async function runAgentCoreFront(o) {
43186
43384
  throw new Error("HALO_AGENTCORE_SESSION_DIRS=1 needs root (bind mount, umount); the worker drops to an unprivileged uid itself");
43187
43385
  if (o.port === WORKER_PORT)
43188
43386
  throw new Error(`port ${WORKER_PORT} is the worker's loopback port \u2014 pick another HALO_PORT`);
43189
- const base = path95.resolve(o.base);
43387
+ const base = path96.resolve(o.base);
43190
43388
  const overlaps = (a, b) => a === b || a.startsWith(b === "/" ? "/" : `${b}/`);
43191
43389
  if (overlaps(WORKER_ROOT, base) || overlaps(base, WORKER_ROOT))
43192
43390
  throw new Error(`HALO_WORKSPACE (${base}) must not overlap ${WORKER_ROOT}`);
@@ -43233,7 +43431,7 @@ var init_agentcore_front = __esm({
43233
43431
  MAX_MEDIA_BYTES = 20 * 1024 * 1024;
43234
43432
  O_PATH = 2097152;
43235
43433
  execFileP = promisify3(execFile2);
43236
- readMountinfo = () => fs90.readFileSync("/proc/self/mountinfo", "utf8");
43434
+ readMountinfo = () => fs91.readFileSync("/proc/self/mountinfo", "utf8");
43237
43435
  SYSTEM_SETUP = {
43238
43436
  mountinfo: readMountinfo,
43239
43437
  exec: (argv) => execFileP(argv[0], argv.slice(1), { timeout: SETUP_TIMEOUT_MS })
@@ -43413,8 +43611,8 @@ var init_halo_client = __esm({
43413
43611
  }
43414
43612
  return await res.json();
43415
43613
  }
43416
- sessionUrl(path98, workspace, sessionId) {
43417
- const url = new URL(`${this.opts.baseUrl}${path98}`);
43614
+ sessionUrl(path99, workspace, sessionId) {
43615
+ const url = new URL(`${this.opts.baseUrl}${path99}`);
43418
43616
  url.searchParams.set("workspace", workspace);
43419
43617
  url.searchParams.set("sessionId", sessionId);
43420
43618
  return url;
@@ -43562,7 +43760,7 @@ var init_version = __esm({
43562
43760
  });
43563
43761
 
43564
43762
  // ../acp-adapter/dist/adapter.js
43565
- import path96 from "node:path";
43763
+ import path97 from "node:path";
43566
43764
  function composePrompt(blocks) {
43567
43765
  const segments = [];
43568
43766
  let run = "";
@@ -43755,7 +43953,7 @@ var init_adapter = __esm({
43755
43953
  if (cursor !== void 0 && !/^\d+$/.test(cursor))
43756
43954
  throw newError(`invalid cursor: ${cursor}`, -32602);
43757
43955
  const page = await this.client.listSessions(this.config.workspace, cursor === void 0 ? void 0 : Number(cursor));
43758
- if (p.cwd !== void 0 && path96.resolve(p.cwd) !== path96.resolve(page.workspace))
43956
+ if (p.cwd !== void 0 && path97.resolve(p.cwd) !== path97.resolve(page.workspace))
43759
43957
  return { sessions: [] };
43760
43958
  return {
43761
43959
  sessions: page.sessions.map((s) => ({
@@ -44369,8 +44567,8 @@ process.emitWarning = (warning, ...args) => {
44369
44567
 
44370
44568
  // src/index.ts
44371
44569
  init_server_supervisor();
44372
- import path97 from "node:path";
44373
- import fs91 from "node:fs";
44570
+ import path98 from "node:path";
44571
+ import fs92 from "node:fs";
44374
44572
  import { homedir as homedir27, userInfo } from "node:os";
44375
44573
  import { parseArgs as parseArgs2 } from "node:util";
44376
44574
 
@@ -44661,7 +44859,7 @@ function installUserUnit(opts, run = runCommand) {
44661
44859
  }
44662
44860
 
44663
44861
  // src/index.ts
44664
- var VERSION = "2.2.4";
44862
+ var VERSION = "2.2.6";
44665
44863
  var HELP_TOP = `Usage: halo <command> [options]
44666
44864
 
44667
44865
  Commands:
@@ -45028,8 +45226,8 @@ async function cmdSetup(argv = []) {
45028
45226
  const { buildBindProviderPlan: buildBindProviderPlan2, envKeyPlaceholders: envKeyPlaceholders2, KEEP_CURRENT: KEEP_CURRENT2, AWS_CHAIN_PROVIDER: AWS_CHAIN_PROVIDER2 } = await Promise.resolve().then(() => (init_setup_bind_provider(), setup_bind_provider_exports));
45029
45227
  const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
45030
45228
  const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
45031
- const haloHome2 = path97.join(homedir27(), ".halo");
45032
- const globalDir2 = path97.join(haloHome2, "global");
45229
+ const haloHome2 = path98.join(homedir27(), ".halo");
45230
+ const globalDir2 = path98.join(haloHome2, "global");
45033
45231
  ensureHaloHome2(haloHome2);
45034
45232
  process.stderr.write(`[setup] ${haloHome2}/ ready
45035
45233
  `);
@@ -45382,7 +45580,7 @@ async function cmdUpgrade() {
45382
45580
  `);
45383
45581
  process.exit(1);
45384
45582
  }
45385
- const broken = verifyNativeModules2(path97.join(rootDir, PKG));
45583
+ const broken = verifyNativeModules2(path98.join(rootDir, PKG));
45386
45584
  if (broken.length > 0) {
45387
45585
  process.stderr.write(
45388
45586
  `
@@ -45413,7 +45611,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
45413
45611
  async function cmdAgents(flags) {
45414
45612
  const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
45415
45613
  await initRuntime2();
45416
- const workspace = path97.resolve(flags.workspace ?? process.cwd());
45614
+ const workspace = path98.resolve(flags.workspace ?? process.cwd());
45417
45615
  const agents = await listAgents2(workspace);
45418
45616
  if (agents.length === 0) {
45419
45617
  process.stderr.write("No agents found.\n");
@@ -45436,12 +45634,12 @@ ${HELP_SESSIONS}`);
45436
45634
  process.exitCode = 1;
45437
45635
  return;
45438
45636
  }
45439
- await cmdSessionsMigrate(path97.resolve(flags.workspace ?? process.cwd()));
45637
+ await cmdSessionsMigrate(path98.resolve(flags.workspace ?? process.cwd()));
45440
45638
  return;
45441
45639
  }
45442
45640
  const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
45443
45641
  await initRuntime2();
45444
- const workspace = path97.resolve(flags.workspace ?? process.cwd());
45642
+ const workspace = path98.resolve(flags.workspace ?? process.cwd());
45445
45643
  const sessions = listSessions2(workspace);
45446
45644
  if (sessions.length === 0) {
45447
45645
  process.stderr.write("No sessions found.\n");
@@ -45455,7 +45653,7 @@ ${HELP_SESSIONS}`);
45455
45653
  }
45456
45654
  }
45457
45655
  async function cmdSessionsMigrate(workspace) {
45458
- if (!fs91.existsSync(path97.join(workspace, ".halo"))) {
45656
+ if (!fs92.existsSync(path98.join(workspace, ".halo"))) {
45459
45657
  process.stderr.write(`Not a Halo workspace (no .halo/): ${workspace}
45460
45658
  `);
45461
45659
  process.exitCode = 1;
@@ -45487,7 +45685,7 @@ async function cmdModels(argv) {
45487
45685
  process.exitCode = 1;
45488
45686
  return;
45489
45687
  }
45490
- const plan = mod.planModelsInstall(path97.resolve(dir));
45688
+ const plan = mod.planModelsInstall(path98.resolve(dir));
45491
45689
  for (const line of mod.formatPlan(plan)) process.stdout.write(`${line}
45492
45690
  `);
45493
45691
  if (plan.needsConfirm && !values.yes) {
@@ -45596,11 +45794,11 @@ ${stdinContent}` : stdinContent;
45596
45794
  harness.destroy();
45597
45795
  }
45598
45796
  }
45599
- var SERVER_LOCK_PATH = path97.join(homedir27(), ".halo", "global", "server.lock");
45600
- var SERVER_LOG_PATH = path97.join(homedir27(), ".halo", "logs", "server.log");
45797
+ var SERVER_LOCK_PATH = path98.join(homedir27(), ".halo", "global", "server.lock");
45798
+ var SERVER_LOG_PATH = path98.join(homedir27(), ".halo", "logs", "server.log");
45601
45799
  function readServerPid() {
45602
45800
  try {
45603
- const txt = fs91.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
45801
+ const txt = fs92.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
45604
45802
  const n = parseInt(txt, 10);
45605
45803
  return Number.isFinite(n) && n > 0 ? n : null;
45606
45804
  } catch {
@@ -45707,8 +45905,8 @@ async function cmdServerStart(argv, restart) {
45707
45905
  }
45708
45906
  if (plan.note) writeDim(plan.note);
45709
45907
  const { spawn: spawn7 } = await import("node:child_process");
45710
- fs91.mkdirSync(path97.dirname(SERVER_LOG_PATH), { recursive: true });
45711
- const out = fs91.openSync(SERVER_LOG_PATH, "a");
45908
+ fs92.mkdirSync(path98.dirname(SERVER_LOG_PATH), { recursive: true });
45909
+ const out = fs92.openSync(SERVER_LOG_PATH, "a");
45712
45910
  const child = spawn7(process.argv[0], [process.argv[1], "server", "start"], {
45713
45911
  detached: true,
45714
45912
  stdio: ["ignore", out, out],
@@ -45789,8 +45987,8 @@ ${HELP_AGENTCORE}`);
45789
45987
  process.exitCode = 1;
45790
45988
  return;
45791
45989
  }
45792
- const workspace = path97.resolve(raw);
45793
- if (!fs91.statSync(workspace, { throwIfNoEntry: false })?.isDirectory()) {
45990
+ const workspace = path98.resolve(raw);
45991
+ if (!fs92.statSync(workspace, { throwIfNoEntry: false })?.isDirectory()) {
45794
45992
  process.stderr.write(`Workspace is not a directory: ${workspace}
45795
45993
  `);
45796
45994
  process.exitCode = 1;
@@ -45901,7 +46099,7 @@ async function cmdServerLogs(argv) {
45901
46099
  lines: { type: "string", short: "n" }
45902
46100
  }
45903
46101
  });
45904
- if (!fs91.existsSync(SERVER_LOG_PATH)) {
46102
+ if (!fs92.existsSync(SERVER_LOG_PATH)) {
45905
46103
  process.stderr.write(`No log file at ${SERVER_LOG_PATH}
45906
46104
  `);
45907
46105
  return;
@@ -45919,7 +46117,7 @@ function exitMissingSetup() {
45919
46117
  process.exit(1);
45920
46118
  }
45921
46119
  function isHaloHomeReady() {
45922
- return fs91.existsSync(path97.join(homedir27(), ".halo", "global", ".template-version"));
46120
+ return fs92.existsSync(path98.join(homedir27(), ".halo", "global", ".template-version"));
45923
46121
  }
45924
46122
  async function main2() {
45925
46123
  const argv = process.argv.slice(2);