@turmind/halo 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1658,10 +1658,19 @@ function isAgentDisabled(agentId, workspaceRoot, disabledSet) {
1658
1658
  return disabledSet.has(`${scope}:${agentId}`);
1659
1659
  }
1660
1660
  async function loadAgentYaml(agentId, workspaceRoot) {
1661
+ const yamlPath = path6.join(agentSourceDir(agentId, workspaceRoot), "agent.yaml");
1661
1662
  try {
1662
- const content = await fs6.readFile(path6.join(agentSourceDir(agentId, workspaceRoot), "agent.yaml"), "utf-8");
1663
- return YAML4.parse(content);
1664
- } catch {
1663
+ const content = await fs6.readFile(yamlPath, "utf-8");
1664
+ const parsed = YAML4.parse(content);
1665
+ if (parsed === null || typeof parsed !== "object") {
1666
+ console.warn(`[AgentLoader] Failed to parse ${yamlPath}: empty or non-object YAML`);
1667
+ return null;
1668
+ }
1669
+ return parsed;
1670
+ } catch (err) {
1671
+ if (err.code === "ENOENT")
1672
+ return null;
1673
+ console.warn(`[AgentLoader] Failed to parse ${yamlPath}: ${err instanceof Error ? err.message : String(err)}`);
1665
1674
  return null;
1666
1675
  }
1667
1676
  }
@@ -2036,6 +2045,7 @@ __export(init_exports, {
2036
2045
  mergeAgentYaml: () => mergeAgentYaml,
2037
2046
  readInstalledOptionalSkills: () => readInstalledOptionalSkills,
2038
2047
  readSeedVersion: () => readSeedVersion,
2048
+ writeFileAtomic: () => writeFileAtomic,
2039
2049
  writeInstalledOptionalSkills: () => writeInstalledOptionalSkills
2040
2050
  });
2041
2051
  import fs7 from "node:fs";
@@ -2057,10 +2067,28 @@ function readSeedVersion(haloHome2) {
2057
2067
  return 0;
2058
2068
  }
2059
2069
  }
2070
+ function writeFileAtomic(filePath, content, mode) {
2071
+ fs7.mkdirSync(path8.dirname(filePath), { recursive: true });
2072
+ const tmpPath = `${filePath}.tmp`;
2073
+ fs7.writeFileSync(tmpPath, content, mode === void 0 ? { encoding: "utf-8" } : { encoding: "utf-8", mode });
2074
+ try {
2075
+ fs7.renameSync(tmpPath, filePath);
2076
+ } catch {
2077
+ fs7.writeFileSync(filePath, content, mode === void 0 ? { encoding: "utf-8" } : { encoding: "utf-8", mode });
2078
+ try {
2079
+ fs7.rmSync(tmpPath, { force: true });
2080
+ } catch {
2081
+ }
2082
+ }
2083
+ }
2060
2084
  function copyTemplate(srcAbs, dstAbs) {
2061
- fs7.mkdirSync(path8.dirname(dstAbs), { recursive: true });
2062
2085
  const content = fs7.readFileSync(srcAbs, "utf-8");
2063
- fs7.writeFileSync(dstAbs, content, "utf-8");
2086
+ try {
2087
+ if (fs7.readFileSync(dstAbs, "utf-8") === content)
2088
+ return;
2089
+ } catch {
2090
+ }
2091
+ writeFileAtomic(dstAbs, content);
2064
2092
  }
2065
2093
  function writeIfMissing(filePath, content, mode) {
2066
2094
  if (fs7.existsSync(filePath))
@@ -2075,7 +2103,8 @@ function mergeAgentYaml(srcAbs, dstAbs) {
2075
2103
  return;
2076
2104
  }
2077
2105
  try {
2078
- const userDoc = YAML5.parseDocument(fs7.readFileSync(dstAbs, "utf-8"));
2106
+ const userRaw = fs7.readFileSync(dstAbs, "utf-8");
2107
+ const userDoc = YAML5.parseDocument(userRaw);
2079
2108
  const templateDoc = YAML5.parseDocument(fs7.readFileSync(srcAbs, "utf-8"));
2080
2109
  const userModel = userDoc.get("model");
2081
2110
  if (userModel != null) {
@@ -2085,7 +2114,10 @@ function mergeAgentYaml(srcAbs, dstAbs) {
2085
2114
  if (userContext != null) {
2086
2115
  templateDoc.set("context", userContext);
2087
2116
  }
2088
- fs7.writeFileSync(dstAbs, templateDoc.toString(), "utf-8");
2117
+ const merged = templateDoc.toString();
2118
+ if (merged === userRaw)
2119
+ return;
2120
+ writeFileAtomic(dstAbs, merged);
2089
2121
  } catch (err) {
2090
2122
  console.log(`[Init] mergeAgentYaml fallback (force-copy) for ${dstAbs}: ${err instanceof Error ? err.message : String(err)}`);
2091
2123
  copyTemplate(srcAbs, dstAbs);
@@ -2368,7 +2400,7 @@ var init_init = __esm({
2368
2400
  path8.resolve(__dirname, "..", "bundled-docs"),
2369
2401
  path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
2370
2402
  ];
2371
- TEMPLATE_VERSION = 49;
2403
+ TEMPLATE_VERSION = 50;
2372
2404
  VERSION_FILE = ".template-version";
2373
2405
  SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
2374
2406
  BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
@@ -4128,27 +4160,27 @@ var init_workspace = __esm({
4128
4160
  constructor(projectRoot) {
4129
4161
  this.projectRoot = projectRoot;
4130
4162
  }
4131
- async readFile(path65) {
4132
- const fullPath = this.validatePath(path65);
4163
+ async readFile(path66) {
4164
+ const fullPath = this.validatePath(path66);
4133
4165
  return readFile(fullPath, "utf-8");
4134
4166
  }
4135
- async writeFile(path65, content) {
4136
- const fullPath = this.validatePath(path65);
4167
+ async writeFile(path66, content) {
4168
+ const fullPath = this.validatePath(path66);
4137
4169
  const dir = resolve(fullPath, "..");
4138
4170
  await mkdir(dir, { recursive: true });
4139
4171
  await writeFile(fullPath, content, "utf-8");
4140
4172
  }
4141
- validatePath(path65) {
4142
- const fullPath = isAbsolute(path65) ? resolve(path65) : resolve(this.projectRoot, path65);
4173
+ validatePath(path66) {
4174
+ const fullPath = isAbsolute(path66) ? resolve(path66) : resolve(this.projectRoot, path66);
4143
4175
  const root = resolve(this.projectRoot);
4144
4176
  if (fullPath !== root && !fullPath.startsWith(root + sep)) {
4145
- throw new WorkspaceError(`Path "${path65}" is outside the workspace directory "${this.projectRoot}"`);
4177
+ throw new WorkspaceError(`Path "${path66}" is outside the workspace directory "${this.projectRoot}"`);
4146
4178
  }
4147
4179
  try {
4148
4180
  const real = realpathSync(fullPath);
4149
4181
  const realRoot = realpathSync(root);
4150
4182
  if (real !== realRoot && !real.startsWith(realRoot + sep)) {
4151
- throw new WorkspaceError(`Path "${path65}" is outside the workspace directory "${this.projectRoot}"`);
4183
+ throw new WorkspaceError(`Path "${path66}" is outside the workspace directory "${this.projectRoot}"`);
4152
4184
  }
4153
4185
  } catch (err) {
4154
4186
  if (err instanceof WorkspaceError)
@@ -4326,12 +4358,12 @@ var init_git_manager = __esm({
4326
4358
  const code = parts[0];
4327
4359
  const status = code[0];
4328
4360
  const isRenameOrCopy = status === "R" || status === "C";
4329
- const path65 = isRenameOrCopy ? parts[2] : parts[1];
4330
- if (!path65 || seen.has(path65))
4361
+ const path66 = isRenameOrCopy ? parts[2] : parts[1];
4362
+ if (!path66 || seen.has(path66))
4331
4363
  continue;
4332
- seen.add(path65);
4364
+ seen.add(path66);
4333
4365
  files.push({
4334
- path: path65,
4366
+ path: path66,
4335
4367
  status,
4336
4368
  from: isRenameOrCopy ? parts[1] : void 0
4337
4369
  });
@@ -4479,9 +4511,9 @@ var init_git_manager = __esm({
4479
4511
  * empty for an added file or the root commit) and `modified` is the version
4480
4512
  * at `commit` (empty for a deletion). The working-tree/staged path above is
4481
4513
  * left entirely untouched (commit === undefined preserves existing behavior). */
4482
- async getFileDiff(path65, staged, from, commit) {
4514
+ async getFileDiff(path66, staged, from, commit) {
4483
4515
  if (commit) {
4484
- const parentPath = from || path65;
4516
+ const parentPath = from || path66;
4485
4517
  let original2 = "";
4486
4518
  try {
4487
4519
  original2 = await this.git.show([`${commit}^:${parentPath}`]);
@@ -4490,13 +4522,13 @@ var init_git_manager = __esm({
4490
4522
  }
4491
4523
  let modified2 = "";
4492
4524
  try {
4493
- modified2 = await this.git.show([`${commit}:${path65}`]);
4525
+ modified2 = await this.git.show([`${commit}:${path66}`]);
4494
4526
  } catch {
4495
4527
  modified2 = "";
4496
4528
  }
4497
4529
  return { original: original2, modified: modified2 };
4498
4530
  }
4499
- const headPath = from || path65;
4531
+ const headPath = from || path66;
4500
4532
  let original = "";
4501
4533
  try {
4502
4534
  original = await this.git.show([`HEAD:${headPath}`]);
@@ -4506,13 +4538,13 @@ var init_git_manager = __esm({
4506
4538
  let modified = "";
4507
4539
  if (staged) {
4508
4540
  try {
4509
- modified = await this.git.show([`:${path65}`]);
4541
+ modified = await this.git.show([`:${path66}`]);
4510
4542
  } catch {
4511
4543
  modified = "";
4512
4544
  }
4513
4545
  } else {
4514
4546
  try {
4515
- modified = await this.workspace.readFile(path65);
4547
+ modified = await this.workspace.readFile(path66);
4516
4548
  } catch {
4517
4549
  modified = "";
4518
4550
  }
@@ -9121,13 +9153,20 @@ var init_session_agent_builder = __esm({
9121
9153
  });
9122
9154
  return { agent, yamlConfig, contextConfig, modelId, systemPrompt, thinkingEffort, meta, draftReset };
9123
9155
  }
9124
- /** Throw with a clear message if agent.yaml is missing the model triple. */
9156
+ /** Throw with a clear message if agent.yaml is missing the model triple.
9157
+ * Includes the resolved yaml path and distinguishes "file unreadable"
9158
+ * (yamlConfig null — read/parse failed, see loadAgentYaml) from "config
9159
+ * genuinely incomplete", so the error is self-diagnosing. */
9125
9160
  validateAgentModelConfig(agentId, yamlConfig) {
9126
- const modelId = yamlConfig?.model?.id;
9127
- const endpoint = yamlConfig?.model?.endpoint;
9128
- const providerId = yamlConfig?.model?.provider;
9161
+ const yamlPath = path23.join(agentSourceDir(agentId, this.host.workspaceRoot), "agent.yaml");
9162
+ if (yamlConfig === null) {
9163
+ throw new Error(`[SessionManager] Agent "${agentId}": agent.yaml missing or unreadable at ${yamlPath}.`);
9164
+ }
9165
+ const modelId = yamlConfig.model?.id;
9166
+ const endpoint = yamlConfig.model?.endpoint;
9167
+ const providerId = yamlConfig.model?.provider;
9129
9168
  if (!modelId || !endpoint || !providerId) {
9130
- throw new Error(`[SessionManager] Agent "${agentId}" is missing model config. agent.yaml must specify model.provider, model.id and model.endpoint.`);
9169
+ throw new Error(`[SessionManager] Agent "${agentId}" is missing model config in ${yamlPath}. agent.yaml must specify model.provider, model.id and model.endpoint.`);
9131
9170
  }
9132
9171
  return { modelId, endpoint, providerId };
9133
9172
  }
@@ -14044,6 +14083,41 @@ var init_setup_prompts = __esm({
14044
14083
  }
14045
14084
  });
14046
14085
 
14086
+ // packages/cli/src/upgrade-verify.ts
14087
+ var upgrade_verify_exports = {};
14088
+ __export(upgrade_verify_exports, {
14089
+ ALLOW_SCRIPTS: () => ALLOW_SCRIPTS,
14090
+ NATIVE_MODULE_SMOKES: () => NATIVE_MODULE_SMOKES,
14091
+ verifyNativeModules: () => verifyNativeModules
14092
+ });
14093
+ import path33 from "node:path";
14094
+ import fs27 from "node:fs";
14095
+ import { spawnSync } from "node:child_process";
14096
+ function verifyNativeModules(pkgRoot) {
14097
+ const broken = [];
14098
+ for (const { name, smoke } of NATIVE_MODULE_SMOKES) {
14099
+ const modDir = path33.join(pkgRoot, "node_modules", name);
14100
+ if (!fs27.existsSync(modDir)) {
14101
+ broken.push(name);
14102
+ continue;
14103
+ }
14104
+ const probe = spawnSync(process.execPath, ["-e", smoke, modDir], { encoding: "utf-8" });
14105
+ if (probe.error || probe.status !== 0) broken.push(name);
14106
+ }
14107
+ return broken;
14108
+ }
14109
+ var NATIVE_MODULE_SMOKES, ALLOW_SCRIPTS;
14110
+ var init_upgrade_verify = __esm({
14111
+ "packages/cli/src/upgrade-verify.ts"() {
14112
+ "use strict";
14113
+ NATIVE_MODULE_SMOKES = [
14114
+ { name: "better-sqlite3", smoke: "new (require(process.argv[1]))(':memory:')" },
14115
+ { name: "node-pty", smoke: "require(process.argv[1])" }
14116
+ ];
14117
+ ALLOW_SCRIPTS = "better-sqlite3";
14118
+ }
14119
+ });
14120
+
14047
14121
  // packages/cli/src/render-md.ts
14048
14122
  import { marked } from "marked";
14049
14123
  import { markedTerminal } from "marked-terminal";
@@ -14060,8 +14134,8 @@ var init_render_md = __esm({
14060
14134
  });
14061
14135
 
14062
14136
  // packages/cli/src/resolve-refs.ts
14063
- import fs27 from "node:fs";
14064
- import path33 from "node:path";
14137
+ import fs28 from "node:fs";
14138
+ import path34 from "node:path";
14065
14139
  function resolveRefs(input, workspace) {
14066
14140
  const images = [];
14067
14141
  const attachments = [];
@@ -14074,11 +14148,11 @@ function resolveRefs(input, workspace) {
14074
14148
  lastIdx = match.index + match[0].length;
14075
14149
  const kind = match[1];
14076
14150
  const rawPath = match[2] ?? match[3];
14077
- const resolved = path33.isAbsolute(rawPath) ? rawPath : path33.resolve(workspace, rawPath);
14078
- const ext = path33.extname(resolved).toLowerCase();
14151
+ const resolved = path34.isAbsolute(rawPath) ? rawPath : path34.resolve(workspace, rawPath);
14152
+ const ext = path34.extname(resolved).toLowerCase();
14079
14153
  let stat = null;
14080
14154
  try {
14081
- stat = fs27.statSync(resolved);
14155
+ stat = fs28.statSync(resolved);
14082
14156
  } catch {
14083
14157
  }
14084
14158
  if (!stat) {
@@ -14096,18 +14170,18 @@ function resolveRefs(input, workspace) {
14096
14170
  warnings.push(`${rawPath}: image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_IMAGE_BYTES / 1024 / 1024}MB), skipped`);
14097
14171
  continue;
14098
14172
  }
14099
- const data = fs27.readFileSync(resolved).toString("base64");
14173
+ const data = fs28.readFileSync(resolved).toString("base64");
14100
14174
  images.push({ data, mimeType: imageMimeFromExt(ext) ?? "application/octet-stream" });
14101
14175
  } else {
14102
- const relPath = path33.relative(workspace, resolved);
14176
+ const relPath = path34.relative(workspace, resolved);
14103
14177
  if (stat.size > MAX_FILE_BYTES) {
14104
14178
  const buf = Buffer.alloc(MAX_FILE_BYTES);
14105
- const fd = fs27.openSync(resolved, "r");
14179
+ const fd = fs28.openSync(resolved, "r");
14106
14180
  let n = 0;
14107
14181
  try {
14108
- n = fs27.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
14182
+ n = fs28.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
14109
14183
  } finally {
14110
- fs27.closeSync(fd);
14184
+ fs28.closeSync(fd);
14111
14185
  }
14112
14186
  const content = buf.subarray(0, n).toString("utf-8").replace(/\uFFFD+$/, "");
14113
14187
  parts.push(`
@@ -14118,7 +14192,7 @@ ${content}
14118
14192
  `);
14119
14193
  warnings.push(`${rawPath}: truncated (${(stat.size / 1024).toFixed(0)}KB, max ${MAX_FILE_BYTES / 1024}KB)`);
14120
14194
  } else {
14121
- const content = fs27.readFileSync(resolved, "utf-8");
14195
+ const content = fs28.readFileSync(resolved, "utf-8");
14122
14196
  parts.push(`
14123
14197
  <file path="${relPath}">
14124
14198
  ${content}
@@ -14390,12 +14464,12 @@ var init_streaming = __esm({
14390
14464
  });
14391
14465
 
14392
14466
  // packages/cli/src/tui/history.ts
14393
- import fs28 from "node:fs";
14394
- import path34 from "node:path";
14467
+ import fs29 from "node:fs";
14468
+ import path35 from "node:path";
14395
14469
  import os2 from "node:os";
14396
14470
  function loadHistory() {
14397
14471
  try {
14398
- const arr = JSON.parse(fs28.readFileSync(HISTORY_FILE, "utf8"));
14472
+ const arr = JSON.parse(fs29.readFileSync(HISTORY_FILE, "utf8"));
14399
14473
  if (Array.isArray(arr)) {
14400
14474
  return arr.filter((x) => typeof x === "string").slice(-MAX_ENTRIES);
14401
14475
  }
@@ -14408,8 +14482,8 @@ function appendHistory(entry) {
14408
14482
  const arr = loadHistory();
14409
14483
  if (arr[arr.length - 1] === entry) return;
14410
14484
  arr.push(entry);
14411
- fs28.mkdirSync(path34.dirname(HISTORY_FILE), { recursive: true });
14412
- fs28.writeFileSync(HISTORY_FILE, JSON.stringify(arr.slice(-MAX_ENTRIES)));
14485
+ fs29.mkdirSync(path35.dirname(HISTORY_FILE), { recursive: true });
14486
+ fs29.writeFileSync(HISTORY_FILE, JSON.stringify(arr.slice(-MAX_ENTRIES)));
14413
14487
  } catch {
14414
14488
  }
14415
14489
  }
@@ -14417,7 +14491,7 @@ var HISTORY_FILE, MAX_ENTRIES;
14417
14491
  var init_history = __esm({
14418
14492
  "packages/cli/src/tui/history.ts"() {
14419
14493
  "use strict";
14420
- HISTORY_FILE = path34.join(os2.homedir(), ".halo", "global", "tui-history.json");
14494
+ HISTORY_FILE = path35.join(os2.homedir(), ".halo", "global", "tui-history.json");
14421
14495
  MAX_ENTRIES = 100;
14422
14496
  }
14423
14497
  });
@@ -14528,8 +14602,8 @@ var init_path_suggest = __esm({
14528
14602
  });
14529
14603
 
14530
14604
  // packages/cli/src/tui/path-suggest-engine.ts
14531
- import fs29 from "node:fs";
14532
- import path35 from "node:path";
14605
+ import fs30 from "node:fs";
14606
+ import path36 from "node:path";
14533
14607
  function detectActiveRef(value) {
14534
14608
  const reExplicit = /@(file|image|scope)\s+(?:"([^"]*)|(\S*))$/;
14535
14609
  const mExplicit = value.match(reExplicit);
@@ -14558,20 +14632,20 @@ function resolveDirAndPrefix(partial, workspace) {
14558
14632
  if (partial === "") {
14559
14633
  return { absDir: workspace, displayDir: ".", prefix: "" };
14560
14634
  }
14561
- const absBase = path35.isAbsolute(partial) ? partial : path35.join(workspace, partial);
14635
+ const absBase = path36.isAbsolute(partial) ? partial : path36.join(workspace, partial);
14562
14636
  if (partial.endsWith("/")) {
14563
14637
  return { absDir: absBase, displayDir: partial.replace(/\/$/, "") || "/", prefix: "" };
14564
14638
  }
14565
- const dirAbs = path35.dirname(absBase);
14566
- const prefix = path35.basename(absBase);
14567
- const dirRel = path35.dirname(partial);
14639
+ const dirAbs = path36.dirname(absBase);
14640
+ const prefix = path36.basename(absBase);
14641
+ const dirRel = path36.dirname(partial);
14568
14642
  return { absDir: dirAbs, displayDir: dirRel === "." ? "." : dirRel, prefix };
14569
14643
  }
14570
14644
  function scanCandidates(partial, workspace, kind) {
14571
14645
  const { absDir, displayDir, prefix } = resolveDirAndPrefix(partial, workspace);
14572
14646
  let entries;
14573
14647
  try {
14574
- entries = fs29.readdirSync(absDir, { withFileTypes: true });
14648
+ entries = fs30.readdirSync(absDir, { withFileTypes: true });
14575
14649
  } catch {
14576
14650
  return { items: [], cwdLabel: displayDir };
14577
14651
  }
@@ -14580,17 +14654,17 @@ function scanCandidates(partial, workspace, kind) {
14580
14654
  if (e.name.startsWith(".")) continue;
14581
14655
  if (prefix && !e.name.toLowerCase().startsWith(prefix.toLowerCase())) continue;
14582
14656
  const isDir = e.isDirectory();
14583
- const ext = path35.extname(e.name).toLowerCase();
14657
+ const ext = path36.extname(e.name).toLowerCase();
14584
14658
  const isImage = IMAGE_EXTS.has(ext);
14585
14659
  if (kind === "image" && !isDir && !isImage) continue;
14586
14660
  if (kind === "scope" && !isDir) continue;
14587
14661
  let insertPath;
14588
- if (path35.isAbsolute(partial)) {
14589
- insertPath = path35.join(absDir, e.name);
14662
+ if (path36.isAbsolute(partial)) {
14663
+ insertPath = path36.join(absDir, e.name);
14590
14664
  } else if (partial.endsWith("/") || partial === "") {
14591
14665
  insertPath = partial + e.name;
14592
14666
  } else {
14593
- const dirRel = path35.dirname(partial);
14667
+ const dirRel = path36.dirname(partial);
14594
14668
  insertPath = (dirRel === "." ? "" : dirRel + "/") + e.name;
14595
14669
  }
14596
14670
  items.push({ name: e.name, insertPath, isDir, isImage });
@@ -14609,8 +14683,8 @@ function applyPathPick(value, ref, pick) {
14609
14683
  const before = value.slice(0, markerStart);
14610
14684
  const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
14611
14685
  const needsQuote = pick.insertPath.includes(" ");
14612
- const path65 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
14613
- return `${before}@${kind} ${path65}`;
14686
+ const path66 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
14687
+ return `${before}@${kind} ${path66}`;
14614
14688
  }
14615
14689
  var init_path_suggest_engine = __esm({
14616
14690
  "packages/cli/src/tui/path-suggest-engine.ts"() {
@@ -16083,15 +16157,15 @@ var init_cli = __esm({
16083
16157
  });
16084
16158
 
16085
16159
  // packages/server/dist/channels/shared/media.js
16086
- import path36 from "node:path";
16160
+ import path37 from "node:path";
16087
16161
  import os3 from "node:os";
16088
16162
  function tempDir() {
16089
- return path36.resolve(os3.tmpdir());
16163
+ return path37.resolve(os3.tmpdir());
16090
16164
  }
16091
16165
  function isInTempDir(filePath) {
16092
- const resolved = path36.resolve(filePath);
16166
+ const resolved = path37.resolve(filePath);
16093
16167
  const tmp = tempDir();
16094
- return resolved === tmp || resolved.startsWith(tmp + path36.sep);
16168
+ return resolved === tmp || resolved.startsWith(tmp + path37.sep);
16095
16169
  }
16096
16170
  function extractMediaPaths(text5) {
16097
16171
  const mediaPaths = [];
@@ -16107,12 +16181,12 @@ function extractMediaMessage(text5) {
16107
16181
  return { text: stripped.replace(/\n{3,}/g, "\n\n").trim(), mediaPaths };
16108
16182
  }
16109
16183
  function isMediaPathAllowed(filePath, workspacePath) {
16110
- const resolved = path36.resolve(filePath);
16111
- const ws = path36.resolve(workspacePath);
16112
- return resolved === ws || resolved.startsWith(ws + path36.sep) || isInTempDir(resolved);
16184
+ const resolved = path37.resolve(filePath);
16185
+ const ws = path37.resolve(workspacePath);
16186
+ return resolved === ws || resolved.startsWith(ws + path37.sep) || isInTempDir(resolved);
16113
16187
  }
16114
16188
  function classifyMedia(filePath) {
16115
- const ext = path36.extname(filePath).toLowerCase();
16189
+ const ext = path37.extname(filePath).toLowerCase();
16116
16190
  if (IMAGE_EXTS.has(ext))
16117
16191
  return "image";
16118
16192
  if (VIDEO_EXTS.has(ext))
@@ -16133,13 +16207,13 @@ var init_media = __esm({
16133
16207
  });
16134
16208
 
16135
16209
  // packages/server/dist/routes/workspace-path.js
16136
- import fs30 from "node:fs/promises";
16210
+ import fs31 from "node:fs/promises";
16137
16211
  import { existsSync as existsSync2, realpathSync as realpathSync3 } from "node:fs";
16138
- import path37 from "node:path";
16212
+ import path38 from "node:path";
16139
16213
  async function resolveProjectPath(projectId) {
16140
- if (path37.isAbsolute(projectId)) {
16214
+ if (path38.isAbsolute(projectId)) {
16141
16215
  try {
16142
- await fs30.access(projectId);
16216
+ await fs31.access(projectId);
16143
16217
  return projectId;
16144
16218
  } catch {
16145
16219
  return null;
@@ -16148,13 +16222,13 @@ async function resolveProjectPath(projectId) {
16148
16222
  return null;
16149
16223
  }
16150
16224
  function realpathBounded2(filePath) {
16151
- let prefix = path37.resolve(filePath);
16225
+ let prefix = path38.resolve(filePath);
16152
16226
  const tail = [];
16153
16227
  while (!existsSync2(prefix)) {
16154
- const parent = path37.dirname(prefix);
16228
+ const parent = path38.dirname(prefix);
16155
16229
  if (parent === prefix)
16156
16230
  break;
16157
- tail.unshift(path37.basename(prefix));
16231
+ tail.unshift(path38.basename(prefix));
16158
16232
  prefix = parent;
16159
16233
  }
16160
16234
  let realPrefix;
@@ -16163,12 +16237,12 @@ function realpathBounded2(filePath) {
16163
16237
  } catch {
16164
16238
  realPrefix = prefix;
16165
16239
  }
16166
- return tail.length > 0 ? path37.join(realPrefix, ...tail) : realPrefix;
16240
+ return tail.length > 0 ? path38.join(realPrefix, ...tail) : realPrefix;
16167
16241
  }
16168
16242
  function validatePath(filePath, projectPath) {
16169
- const resolved = realpathBounded2(path37.resolve(projectPath, filePath));
16243
+ const resolved = realpathBounded2(path38.resolve(projectPath, filePath));
16170
16244
  const proj = realpathBounded2(projectPath);
16171
- return resolved === proj || resolved.startsWith(proj + path37.sep);
16245
+ return resolved === proj || resolved.startsWith(proj + path38.sep);
16172
16246
  }
16173
16247
  function isSafeIdSegment(id) {
16174
16248
  if (id === "." || id === "..")
@@ -16183,10 +16257,10 @@ var init_workspace_path = __esm({
16183
16257
 
16184
16258
  // packages/server/dist/routes/files.js
16185
16259
  import { Hono } from "hono";
16186
- import fs31 from "node:fs/promises";
16260
+ import fs32 from "node:fs/promises";
16187
16261
  import { createReadStream } from "node:fs";
16188
16262
  import { Readable } from "node:stream";
16189
- import path38 from "node:path";
16263
+ import path39 from "node:path";
16190
16264
  import { homedir as homedir17 } from "node:os";
16191
16265
  function createFileRoutes() {
16192
16266
  const app2 = new Hono();
@@ -16203,7 +16277,7 @@ function createFileRoutes() {
16203
16277
  }
16204
16278
  async function dirHasChildren(dirPath) {
16205
16279
  try {
16206
- const entries = await fs31.readdir(dirPath, { withFileTypes: true });
16280
+ const entries = await fs32.readdir(dirPath, { withFileTypes: true });
16207
16281
  for (const entry of entries) {
16208
16282
  if (!isSkippedName(entry.name))
16209
16283
  return true;
@@ -16216,7 +16290,7 @@ function createFileRoutes() {
16216
16290
  async function listDir(dirPath, basePath) {
16217
16291
  let entries;
16218
16292
  try {
16219
- entries = await fs31.readdir(dirPath, { withFileTypes: true });
16293
+ entries = await fs32.readdir(dirPath, { withFileTypes: true });
16220
16294
  } catch {
16221
16295
  return [];
16222
16296
  }
@@ -16231,8 +16305,8 @@ function createFileRoutes() {
16231
16305
  for (const entry of sorted) {
16232
16306
  if (isSkippedName(entry.name))
16233
16307
  continue;
16234
- const fullPath = path38.join(dirPath, entry.name);
16235
- const relativePath = path38.relative(basePath, fullPath).split(path38.sep).join("/");
16308
+ const fullPath = path39.join(dirPath, entry.name);
16309
+ const relativePath = path39.relative(basePath, fullPath).split(path39.sep).join("/");
16236
16310
  if (entry.isDirectory()) {
16237
16311
  nodes.push({
16238
16312
  name: entry.name,
@@ -16264,9 +16338,9 @@ function createFileRoutes() {
16264
16338
  if (relPath && !validatePath(relPath, projectPath)) {
16265
16339
  return c.json({ error: "Path traversal not allowed" }, 403);
16266
16340
  }
16267
- const targetPath = relPath ? path38.resolve(projectPath, relPath) : projectPath;
16341
+ const targetPath = relPath ? path39.resolve(projectPath, relPath) : projectPath;
16268
16342
  try {
16269
- const stat = await fs31.stat(targetPath);
16343
+ const stat = await fs32.stat(targetPath);
16270
16344
  if (!stat.isDirectory()) {
16271
16345
  return c.json({ error: "Path is not a directory" }, 400);
16272
16346
  }
@@ -16276,7 +16350,7 @@ function createFileRoutes() {
16276
16350
  const tree = await listDir(targetPath, projectPath);
16277
16351
  return c.json({
16278
16352
  projectId,
16279
- root: path38.basename(projectPath),
16353
+ root: path39.basename(projectPath),
16280
16354
  path: relPath,
16281
16355
  tree
16282
16356
  });
@@ -16309,7 +16383,7 @@ function createFileRoutes() {
16309
16383
  return;
16310
16384
  let entries;
16311
16385
  try {
16312
- entries = await fs31.readdir(dirPath, { withFileTypes: true });
16386
+ entries = await fs32.readdir(dirPath, { withFileTypes: true });
16313
16387
  } catch {
16314
16388
  return;
16315
16389
  }
@@ -16319,11 +16393,11 @@ function createFileRoutes() {
16319
16393
  if (isSkippedName(entry.name))
16320
16394
  continue;
16321
16395
  scanned++;
16322
- const fullPath = path38.join(dirPath, entry.name);
16396
+ const fullPath = path39.join(dirPath, entry.name);
16323
16397
  if (entry.isDirectory()) {
16324
16398
  if (dirsOnly) {
16325
16399
  const name = entry.name.toLowerCase();
16326
- const rel = path38.relative(projectRoot, fullPath);
16400
+ const rel = path39.relative(projectRoot, fullPath);
16327
16401
  if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
16328
16402
  matches.push({ name: entry.name, path: rel });
16329
16403
  }
@@ -16331,7 +16405,7 @@ function createFileRoutes() {
16331
16405
  await walk(fullPath);
16332
16406
  } else if (entry.isFile() && !dirsOnly) {
16333
16407
  const name = entry.name.toLowerCase();
16334
- const rel = path38.relative(projectRoot, fullPath);
16408
+ const rel = path39.relative(projectRoot, fullPath);
16335
16409
  if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
16336
16410
  matches.push({ name: entry.name, path: rel });
16337
16411
  }
@@ -16375,15 +16449,15 @@ function createFileRoutes() {
16375
16449
  }
16376
16450
  const uploaded = [];
16377
16451
  for (const file of files) {
16378
- const relPath = targetDir ? path38.join(targetDir, file.name) : file.name;
16452
+ const relPath = targetDir ? path39.join(targetDir, file.name) : file.name;
16379
16453
  if (!validatePath(relPath, projectPath)) {
16380
16454
  continue;
16381
16455
  }
16382
- const destPath = path38.resolve(projectPath, relPath);
16383
- const destDir = path38.dirname(destPath);
16384
- await fs31.mkdir(destDir, { recursive: true });
16456
+ const destPath = path39.resolve(projectPath, relPath);
16457
+ const destDir = path39.dirname(destPath);
16458
+ await fs32.mkdir(destDir, { recursive: true });
16385
16459
  const buffer = Buffer.from(await file.arrayBuffer());
16386
- await fs31.writeFile(destPath, buffer);
16460
+ await fs32.writeFile(destPath, buffer);
16387
16461
  uploaded.push({
16388
16462
  name: file.name,
16389
16463
  path: relPath,
@@ -16411,9 +16485,9 @@ function createFileRoutes() {
16411
16485
  if (!validatePath(filePath, projectPath)) {
16412
16486
  return c.json({ error: "Path traversal not allowed" }, 403);
16413
16487
  }
16414
- const absolutePath = path38.resolve(projectPath, filePath);
16415
- await fs31.writeFile(absolutePath, content, "utf-8");
16416
- const stat = await fs31.stat(absolutePath);
16488
+ const absolutePath = path39.resolve(projectPath, filePath);
16489
+ await fs32.writeFile(absolutePath, content, "utf-8");
16490
+ const stat = await fs32.stat(absolutePath);
16417
16491
  return c.json({ ok: true, path: filePath, modifiedAt: stat.mtimeMs });
16418
16492
  } catch (err) {
16419
16493
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16435,14 +16509,14 @@ function createFileRoutes() {
16435
16509
  if (!validatePath(filePath, projectPath)) {
16436
16510
  return c.json({ error: "Path traversal not allowed" }, 403);
16437
16511
  }
16438
- const absolutePath = path38.resolve(projectPath, filePath);
16439
- await fs31.mkdir(path38.dirname(absolutePath), { recursive: true });
16512
+ const absolutePath = path39.resolve(projectPath, filePath);
16513
+ await fs32.mkdir(path39.dirname(absolutePath), { recursive: true });
16440
16514
  try {
16441
- await fs31.access(absolutePath);
16515
+ await fs32.access(absolutePath);
16442
16516
  return c.json({ error: "File already exists" }, 409);
16443
16517
  } catch {
16444
16518
  }
16445
- await fs31.writeFile(absolutePath, "", "utf-8");
16519
+ await fs32.writeFile(absolutePath, "", "utf-8");
16446
16520
  return c.json({ ok: true, path: filePath });
16447
16521
  } catch (err) {
16448
16522
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16464,8 +16538,8 @@ function createFileRoutes() {
16464
16538
  if (!validatePath(dirPath, projectPath)) {
16465
16539
  return c.json({ error: "Path traversal not allowed" }, 403);
16466
16540
  }
16467
- const absolutePath = path38.resolve(projectPath, dirPath);
16468
- await fs31.mkdir(absolutePath, { recursive: true });
16541
+ const absolutePath = path39.resolve(projectPath, dirPath);
16542
+ await fs32.mkdir(absolutePath, { recursive: true });
16469
16543
  return c.json({ ok: true, path: dirPath });
16470
16544
  } catch (err) {
16471
16545
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16487,10 +16561,10 @@ function createFileRoutes() {
16487
16561
  if (!validatePath(oldPath, projectPath) || !validatePath(newPath, projectPath)) {
16488
16562
  return c.json({ error: "Path traversal not allowed" }, 403);
16489
16563
  }
16490
- const absOld = path38.resolve(projectPath, oldPath);
16491
- const absNew = path38.resolve(projectPath, newPath);
16492
- await fs31.mkdir(path38.dirname(absNew), { recursive: true });
16493
- await fs31.rename(absOld, absNew);
16564
+ const absOld = path39.resolve(projectPath, oldPath);
16565
+ const absNew = path39.resolve(projectPath, newPath);
16566
+ await fs32.mkdir(path39.dirname(absNew), { recursive: true });
16567
+ await fs32.rename(absOld, absNew);
16494
16568
  return c.json({ ok: true, oldPath, newPath });
16495
16569
  } catch (err) {
16496
16570
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16512,11 +16586,11 @@ function createFileRoutes() {
16512
16586
  if (!validatePath(filePath, projectPath)) {
16513
16587
  return c.json({ error: "Path traversal not allowed" }, 403);
16514
16588
  }
16515
- const absolutePath = path38.resolve(projectPath, filePath);
16516
- if (absolutePath === path38.resolve(projectPath)) {
16589
+ const absolutePath = path39.resolve(projectPath, filePath);
16590
+ if (absolutePath === path39.resolve(projectPath)) {
16517
16591
  return c.json({ error: "Cannot delete project root" }, 403);
16518
16592
  }
16519
- await fs31.rm(absolutePath, { recursive: true, force: true });
16593
+ await fs32.rm(absolutePath, { recursive: true, force: true });
16520
16594
  return c.json({ ok: true, path: filePath });
16521
16595
  } catch (err) {
16522
16596
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16550,16 +16624,16 @@ function createFileRoutes() {
16550
16624
  if (!projectPath) {
16551
16625
  return c.json({ error: "Project not found" }, 404);
16552
16626
  }
16553
- const isTmp = path38.isAbsolute(filePath) && isInTempDir(filePath);
16627
+ const isTmp = path39.isAbsolute(filePath) && isInTempDir(filePath);
16554
16628
  if (!isTmp && !validatePath(filePath, projectPath)) {
16555
16629
  return c.json({ error: "Path traversal not allowed" }, 403);
16556
16630
  }
16557
- const absolutePath = isTmp ? path38.resolve(filePath) : path38.resolve(projectPath, filePath);
16558
- const stat = await fs31.stat(absolutePath);
16631
+ const absolutePath = isTmp ? path39.resolve(filePath) : path39.resolve(projectPath, filePath);
16632
+ const stat = await fs32.stat(absolutePath);
16559
16633
  if (stat.isDirectory()) {
16560
16634
  return c.json({ error: "Cannot download a directory" }, 400);
16561
16635
  }
16562
- const fileName = path38.basename(filePath);
16636
+ const fileName = path39.basename(filePath);
16563
16637
  const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
16564
16638
  const mime = inline ? imageMimeFromExt(ext) ?? MIME_MAP[ext] ?? "application/octet-stream" : "application/octet-stream";
16565
16639
  const disposition = inline ? `inline; filename="${encodeURIComponent(fileName)}"` : `attachment; filename="${encodeURIComponent(fileName)}"`;
@@ -16618,8 +16692,8 @@ function createFileRoutes() {
16618
16692
  return c.json({ error: "Project not found" }, 404);
16619
16693
  if (!validatePath(filePath, projectPath))
16620
16694
  return c.json({ error: "Path traversal not allowed" }, 403);
16621
- const absolutePath = path38.resolve(projectPath, filePath);
16622
- const stat = await fs31.stat(absolutePath);
16695
+ const absolutePath = path39.resolve(projectPath, filePath);
16696
+ const stat = await fs32.stat(absolutePath);
16623
16697
  return c.json({ path: filePath, modifiedAt: stat.mtimeMs, createdAt: stat.birthtimeMs, size: stat.size });
16624
16698
  } catch (err) {
16625
16699
  if (err.code === "ENOENT")
@@ -16641,9 +16715,9 @@ function createFileRoutes() {
16641
16715
  if (!validatePath(filePath, projectPath)) {
16642
16716
  return c.json({ error: "Path traversal not allowed" }, 403);
16643
16717
  }
16644
- const absolutePath = path38.resolve(projectPath, filePath);
16718
+ const absolutePath = path39.resolve(projectPath, filePath);
16645
16719
  try {
16646
- const stat = await fs31.stat(absolutePath);
16720
+ const stat = await fs32.stat(absolutePath);
16647
16721
  if (stat.isDirectory()) {
16648
16722
  return c.json({ error: "Path is a directory, use /files/tree instead" }, 400);
16649
16723
  }
@@ -16651,7 +16725,7 @@ function createFileRoutes() {
16651
16725
  if (stat.size > maxSize) {
16652
16726
  return c.json({ error: "File too large (max 10MB)" }, 413);
16653
16727
  }
16654
- const content = await fs31.readFile(absolutePath, "utf-8");
16728
+ const content = await fs32.readFile(absolutePath, "utf-8");
16655
16729
  return c.json({
16656
16730
  path: filePath,
16657
16731
  content,
@@ -16678,16 +16752,16 @@ function createFileRoutes() {
16678
16752
  const body = await c.req.json();
16679
16753
  if (!body.path)
16680
16754
  return c.json({ error: "path required" }, 400);
16681
- const resolved = path38.resolve(body.path);
16755
+ const resolved = path39.resolve(body.path);
16682
16756
  let stat;
16683
16757
  try {
16684
- stat = await fs31.stat(resolved);
16758
+ stat = await fs32.stat(resolved);
16685
16759
  } catch {
16686
16760
  return c.json({ error: "path not found" }, 404);
16687
16761
  }
16688
16762
  if (!stat.isDirectory())
16689
16763
  return c.json({ error: "not a directory" }, 400);
16690
- if (resolved === path38.parse(resolved).root) {
16764
+ if (resolved === path39.parse(resolved).root) {
16691
16765
  return c.json({ error: "filesystem root cannot be a workspace" }, 400);
16692
16766
  }
16693
16767
  const { ensureWorkspaceHalo: ensureWorkspaceHalo2 } = await Promise.resolve().then(() => (init_init(), init_exports));
@@ -16696,10 +16770,10 @@ function createFileRoutes() {
16696
16770
  });
16697
16771
  app2.get("/fs/exists", async (c) => {
16698
16772
  const target = c.req.query("path");
16699
- if (!target || !path38.isAbsolute(target))
16773
+ if (!target || !path39.isAbsolute(target))
16700
16774
  return c.json({ exists: false, reason: "absolute path required" });
16701
16775
  try {
16702
- const stat = await fs31.stat(target);
16776
+ const stat = await fs32.stat(target);
16703
16777
  return c.json({ exists: true, isDirectory: stat.isDirectory() });
16704
16778
  } catch {
16705
16779
  return c.json({ exists: false });
@@ -16707,13 +16781,13 @@ function createFileRoutes() {
16707
16781
  });
16708
16782
  app2.get("/fs/browse", async (c) => {
16709
16783
  const target = c.req.query("path") ?? homedir17();
16710
- if (!path38.isAbsolute(target))
16784
+ if (!path39.isAbsolute(target))
16711
16785
  return c.json({ error: "absolute path required" }, 400);
16712
16786
  try {
16713
- const resolved = path38.resolve(target);
16714
- const entries = await fs31.readdir(resolved, { withFileTypes: true });
16715
- const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: path38.join(resolved, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
16716
- return c.json({ path: resolved, parent: path38.dirname(resolved), entries: dirs });
16787
+ const resolved = path39.resolve(target);
16788
+ const entries = await fs32.readdir(resolved, { withFileTypes: true });
16789
+ const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: path39.join(resolved, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
16790
+ return c.json({ path: resolved, parent: path39.dirname(resolved), entries: dirs });
16717
16791
  } catch (err) {
16718
16792
  return c.json({ error: err.message }, 404);
16719
16793
  }
@@ -16731,9 +16805,9 @@ var init_files = __esm({
16731
16805
 
16732
16806
  // packages/server/dist/routes/data-preview.js
16733
16807
  import { Hono as Hono2 } from "hono";
16734
- import fs32 from "node:fs/promises";
16808
+ import fs33 from "node:fs/promises";
16735
16809
  import { createReadStream as createReadStream2 } from "node:fs";
16736
- import path39 from "node:path";
16810
+ import path40 from "node:path";
16737
16811
  import Database3 from "better-sqlite3";
16738
16812
  import { asyncBufferFromFile, parquetMetadataAsync, parquetSchema, parquetReadObjects } from "hyparquet";
16739
16813
  import { compressors } from "hyparquet-compressors";
@@ -16764,7 +16838,7 @@ function quoteIdent(name) {
16764
16838
  return `"${name.replaceAll('"', '""')}"`;
16765
16839
  }
16766
16840
  async function sniffDelimiter(absolutePath) {
16767
- const fh = await fs32.open(absolutePath, "r");
16841
+ const fh = await fs33.open(absolutePath, "r");
16768
16842
  let head;
16769
16843
  try {
16770
16844
  const buf = Buffer.alloc(65536);
@@ -16848,9 +16922,9 @@ function createDataPreviewRoutes() {
16848
16922
  return { error: "Project not found", status: 404 };
16849
16923
  if (!validatePath(filePath, projectPath))
16850
16924
  return { error: "Path traversal not allowed", status: 403 };
16851
- const absolutePath = path39.resolve(projectPath, filePath);
16925
+ const absolutePath = path40.resolve(projectPath, filePath);
16852
16926
  try {
16853
- const stat = await fs32.stat(absolutePath);
16927
+ const stat = await fs33.stat(absolutePath);
16854
16928
  if (stat.isDirectory())
16855
16929
  return { error: "Path is a directory", status: 400 };
16856
16930
  } catch {
@@ -17056,15 +17130,15 @@ var init_data_preview = __esm({
17056
17130
  });
17057
17131
 
17058
17132
  // packages/server/dist/git-credentials.js
17059
- import fs33 from "node:fs";
17060
- import path40 from "node:path";
17133
+ import fs34 from "node:fs";
17134
+ import path41 from "node:path";
17061
17135
  import { homedir as homedir18 } from "node:os";
17062
- import { spawnSync } from "node:child_process";
17136
+ import { spawnSync as spawnSync2 } from "node:child_process";
17063
17137
  function writeGitCredentialsFile(host, username, token) {
17064
17138
  const line = `https://${encodeURIComponent(username)}:${encodeURIComponent(token)}@${host}`;
17065
17139
  let lines = [];
17066
- if (fs33.existsSync(GIT_CREDENTIALS_PATH)) {
17067
- lines = fs33.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
17140
+ if (fs34.existsSync(GIT_CREDENTIALS_PATH)) {
17141
+ lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
17068
17142
  }
17069
17143
  const kept = lines.filter((l) => {
17070
17144
  try {
@@ -17074,17 +17148,17 @@ function writeGitCredentialsFile(host, username, token) {
17074
17148
  }
17075
17149
  });
17076
17150
  kept.push(line);
17077
- fs33.writeFileSync(GIT_CREDENTIALS_PATH, kept.join("\n") + "\n", { encoding: "utf-8", mode: 384 });
17151
+ fs34.writeFileSync(GIT_CREDENTIALS_PATH, kept.join("\n") + "\n", { encoding: "utf-8", mode: 384 });
17078
17152
  try {
17079
- fs33.chmodSync(GIT_CREDENTIALS_PATH, 384);
17153
+ fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
17080
17154
  } catch {
17081
17155
  }
17082
17156
  }
17083
17157
  function ensureCredentialHelper() {
17084
- const current = spawnSync("git", ["config", "--global", "--get", "credential.helper"], { encoding: "utf-8" });
17158
+ const current = spawnSync2("git", ["config", "--global", "--get", "credential.helper"], { encoding: "utf-8" });
17085
17159
  const helper = (current.stdout ?? "").trim();
17086
17160
  if (!helper) {
17087
- spawnSync("git", ["config", "--global", "credential.helper", "store"], { encoding: "utf-8" });
17161
+ spawnSync2("git", ["config", "--global", "credential.helper", "store"], { encoding: "utf-8" });
17088
17162
  }
17089
17163
  }
17090
17164
  function saveGitCredentials({ host, username, token }) {
@@ -17093,9 +17167,9 @@ function saveGitCredentials({ host, username, token }) {
17093
17167
  console.log(`[GitCredentials] saved for host ${host}`);
17094
17168
  }
17095
17169
  function listGitCredentials() {
17096
- if (!fs33.existsSync(GIT_CREDENTIALS_PATH))
17170
+ if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
17097
17171
  return [];
17098
- const lines = fs33.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
17172
+ const lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
17099
17173
  const byHost = /* @__PURE__ */ new Map();
17100
17174
  for (const line of lines) {
17101
17175
  try {
@@ -17109,9 +17183,9 @@ function listGitCredentials() {
17109
17183
  return [...byHost.values()];
17110
17184
  }
17111
17185
  function deleteGitCredential(host) {
17112
- if (!fs33.existsSync(GIT_CREDENTIALS_PATH))
17186
+ if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
17113
17187
  return;
17114
- const lines = fs33.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
17188
+ const lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
17115
17189
  const kept = lines.filter((l) => {
17116
17190
  try {
17117
17191
  return new URL(l).host !== host;
@@ -17121,9 +17195,9 @@ function deleteGitCredential(host) {
17121
17195
  });
17122
17196
  if (kept.length === lines.length)
17123
17197
  return;
17124
- fs33.writeFileSync(GIT_CREDENTIALS_PATH, kept.length ? kept.join("\n") + "\n" : "", { encoding: "utf-8", mode: 384 });
17198
+ fs34.writeFileSync(GIT_CREDENTIALS_PATH, kept.length ? kept.join("\n") + "\n" : "", { encoding: "utf-8", mode: 384 });
17125
17199
  try {
17126
- fs33.chmodSync(GIT_CREDENTIALS_PATH, 384);
17200
+ fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
17127
17201
  } catch {
17128
17202
  }
17129
17203
  console.log(`[GitCredentials] removed for host ${host}`);
@@ -17132,15 +17206,15 @@ var GIT_CREDENTIALS_PATH;
17132
17206
  var init_git_credentials = __esm({
17133
17207
  "packages/server/dist/git-credentials.js"() {
17134
17208
  "use strict";
17135
- GIT_CREDENTIALS_PATH = path40.join(homedir18(), ".git-credentials");
17209
+ GIT_CREDENTIALS_PATH = path41.join(homedir18(), ".git-credentials");
17136
17210
  }
17137
17211
  });
17138
17212
 
17139
17213
  // packages/server/dist/git-ssh.js
17140
- import fs34 from "node:fs";
17214
+ import fs35 from "node:fs";
17141
17215
  import os4 from "node:os";
17142
- import path41 from "node:path";
17143
- import { spawnSync as spawnSync2 } from "node:child_process";
17216
+ import path42 from "node:path";
17217
+ import { spawnSync as spawnSync3 } from "node:child_process";
17144
17218
  function looksLikePrivateKey(name, abs) {
17145
17219
  if (name.endsWith(".pub"))
17146
17220
  return false;
@@ -17148,13 +17222,13 @@ function looksLikePrivateKey(name, abs) {
17148
17222
  return false;
17149
17223
  let firstLine = "";
17150
17224
  try {
17151
- const fd = fs34.openSync(abs, "r");
17225
+ const fd = fs35.openSync(abs, "r");
17152
17226
  try {
17153
17227
  const buf = Buffer.alloc(64);
17154
- const n = fs34.readSync(fd, buf, 0, 64, 0);
17228
+ const n = fs35.readSync(fd, buf, 0, 64, 0);
17155
17229
  firstLine = buf.toString("utf-8", 0, n).split("\n")[0];
17156
17230
  } finally {
17157
- fs34.closeSync(fd);
17231
+ fs35.closeSync(fd);
17158
17232
  }
17159
17233
  } catch {
17160
17234
  return false;
@@ -17162,21 +17236,21 @@ function looksLikePrivateKey(name, abs) {
17162
17236
  return firstLine.includes("PRIVATE KEY-----");
17163
17237
  }
17164
17238
  function isKeyEncrypted(abs) {
17165
- const r = spawnSync2("ssh-keygen", ["-y", "-P", "", "-f", abs], { encoding: "utf-8" });
17239
+ const r = spawnSync3("ssh-keygen", ["-y", "-P", "", "-f", abs], { encoding: "utf-8" });
17166
17240
  return r.status !== 0;
17167
17241
  }
17168
17242
  function listSshKeys() {
17169
17243
  let entries = [];
17170
17244
  try {
17171
- entries = fs34.readdirSync(SSH_DIR);
17245
+ entries = fs35.readdirSync(SSH_DIR);
17172
17246
  } catch {
17173
17247
  return [];
17174
17248
  }
17175
17249
  const keys = [];
17176
17250
  for (const name of entries) {
17177
- const abs = path41.join(SSH_DIR, name);
17251
+ const abs = path42.join(SSH_DIR, name);
17178
17252
  try {
17179
- if (!fs34.statSync(abs).isFile())
17253
+ if (!fs35.statSync(abs).isFile())
17180
17254
  continue;
17181
17255
  } catch {
17182
17256
  continue;
@@ -17190,7 +17264,7 @@ function listSshKeys() {
17190
17264
  function getSshAgentStatus() {
17191
17265
  if (!process.env.SSH_AUTH_SOCK)
17192
17266
  return { agentRunning: false, loadedKeys: [] };
17193
- const r = spawnSync2("ssh-add", ["-l"], { encoding: "utf-8" });
17267
+ const r = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
17194
17268
  if (r.status === 2)
17195
17269
  return { agentRunning: false, loadedKeys: [] };
17196
17270
  const loadedKeys = (r.stdout ?? "").split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => l !== "The agent has no identities.");
@@ -17201,10 +17275,10 @@ function unlockSshKey(keyPath, passphrase) {
17201
17275
  return { ok: false, error: "ssh-agent is not running" };
17202
17276
  let dir = "";
17203
17277
  try {
17204
- dir = fs34.mkdtempSync(path41.join(os4.tmpdir(), "halo-askpass-"));
17205
- const askpass = path41.join(dir, "askpass.sh");
17206
- fs34.writeFileSync(askpass, ASKPASS_SCRIPT, { mode: 448 });
17207
- const r = spawnSync2("ssh-add", [keyPath], {
17278
+ dir = fs35.mkdtempSync(path42.join(os4.tmpdir(), "halo-askpass-"));
17279
+ const askpass = path42.join(dir, "askpass.sh");
17280
+ fs35.writeFileSync(askpass, ASKPASS_SCRIPT, { mode: 448 });
17281
+ const r = spawnSync3("ssh-add", [keyPath], {
17208
17282
  encoding: "utf-8",
17209
17283
  // stdin from /dev/null so ssh-add can never fall back to a tty prompt and block.
17210
17284
  stdio: ["ignore", "pipe", "pipe"],
@@ -17219,10 +17293,10 @@ function unlockSshKey(keyPath, passphrase) {
17219
17293
  }
17220
17294
  });
17221
17295
  if (r.status === 0) {
17222
- console.log(`[GitSsh] unlocked key ${path41.basename(keyPath)}`);
17296
+ console.log(`[GitSsh] unlocked key ${path42.basename(keyPath)}`);
17223
17297
  return { ok: true };
17224
17298
  }
17225
- console.log(`[GitSsh] unlock failed for ${path41.basename(keyPath)} (exit ${r.status ?? "killed"})`);
17299
+ console.log(`[GitSsh] unlock failed for ${path42.basename(keyPath)} (exit ${r.status ?? "killed"})`);
17226
17300
  if (r.status === 2)
17227
17301
  return { ok: false, error: "ssh-agent is not reachable" };
17228
17302
  return { ok: false, error: "Incorrect passphrase, or the key could not be unlocked" };
@@ -17232,19 +17306,19 @@ function unlockSshKey(keyPath, passphrase) {
17232
17306
  return { ok: false, error: "Failed to unlock key" };
17233
17307
  } finally {
17234
17308
  if (dir)
17235
- fs34.rmSync(dir, { recursive: true, force: true });
17309
+ fs35.rmSync(dir, { recursive: true, force: true });
17236
17310
  }
17237
17311
  }
17238
17312
  function ensureSshAgent() {
17239
17313
  try {
17240
17314
  if (process.env.SSH_AUTH_SOCK) {
17241
- const probe = spawnSync2("ssh-add", ["-l"], { encoding: "utf-8" });
17315
+ const probe = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
17242
17316
  if (probe.status !== 2) {
17243
17317
  console.log(`[GitSsh] using existing agent at ${process.env.SSH_AUTH_SOCK}`);
17244
17318
  return;
17245
17319
  }
17246
17320
  }
17247
- const r = spawnSync2("ssh-agent", ["-s"], { encoding: "utf-8" });
17321
+ const r = spawnSync3("ssh-agent", ["-s"], { encoding: "utf-8" });
17248
17322
  if (r.status !== 0) {
17249
17323
  console.log("[GitSsh] ssh-agent unavailable; SSH push/pull will need a manually-started agent");
17250
17324
  return;
@@ -17289,19 +17363,19 @@ function parseRemote(url) {
17289
17363
  }
17290
17364
  }
17291
17365
  function switchRemoteProtocol(projectRoot, to) {
17292
- const cur = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
17366
+ const cur = spawnSync3("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
17293
17367
  if (cur.status !== 0)
17294
17368
  throw new Error((cur.stderr ?? "no origin remote").trim());
17295
17369
  const { host, repoPath } = parseRemote((cur.stdout ?? "").trim());
17296
17370
  const next = to === "ssh" ? `git@${host}:${repoPath}` : `https://${host}/${repoPath}`;
17297
- const set = spawnSync2("git", ["-C", projectRoot, "remote", "set-url", "origin", next], { encoding: "utf-8" });
17371
+ const set = spawnSync3("git", ["-C", projectRoot, "remote", "set-url", "origin", next], { encoding: "utf-8" });
17298
17372
  if (set.status !== 0)
17299
17373
  throw new Error((set.stderr ?? "failed to set remote url").trim());
17300
17374
  console.log(`[GitSsh] switched origin to ${to}`);
17301
17375
  return next;
17302
17376
  }
17303
17377
  function getRemoteProtocol(projectRoot) {
17304
- const cur = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
17378
+ const cur = spawnSync3("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
17305
17379
  if (cur.status !== 0)
17306
17380
  return { url: "", protocol: "other" };
17307
17381
  const url = (cur.stdout ?? "").trim();
@@ -17315,7 +17389,7 @@ var SSH_DIR, NON_KEY_NAMES, ownedAgentPid, ASKPASS_SCRIPT;
17315
17389
  var init_git_ssh = __esm({
17316
17390
  "packages/server/dist/git-ssh.js"() {
17317
17391
  "use strict";
17318
- SSH_DIR = path41.join(os4.homedir(), ".ssh");
17392
+ SSH_DIR = path42.join(os4.homedir(), ".ssh");
17319
17393
  NON_KEY_NAMES = /* @__PURE__ */ new Set(["known_hosts", "known_hosts2", "authorized_keys", "config"]);
17320
17394
  ownedAgentPid = null;
17321
17395
  ASKPASS_SCRIPT = `#!/bin/sh
@@ -17329,7 +17403,7 @@ printf '%s\\n' "$HALO_SSH_PASSPHRASE"
17329
17403
  // packages/server/dist/routes/git.js
17330
17404
  import { Hono as Hono3 } from "hono";
17331
17405
  import os5 from "node:os";
17332
- import path42 from "node:path";
17406
+ import path43 from "node:path";
17333
17407
  function notifyGitChanged(workspacePath) {
17334
17408
  broadcastToWorkspace(workspacePath, { type: "file:changed", path: ".git", action: "change" });
17335
17409
  }
@@ -17646,9 +17720,9 @@ function createGitRoutes() {
17646
17720
  if (!keyPath || typeof passphrase !== "string") {
17647
17721
  return c.json({ error: "keyPath and passphrase are required" }, 400);
17648
17722
  }
17649
- const sshDir = path42.join(os5.homedir(), ".ssh");
17650
- const resolved = path42.resolve(keyPath);
17651
- if (path42.dirname(resolved) !== path42.resolve(sshDir)) {
17723
+ const sshDir = path43.join(os5.homedir(), ".ssh");
17724
+ const resolved = path43.resolve(keyPath);
17725
+ if (path43.dirname(resolved) !== path43.resolve(sshDir)) {
17652
17726
  return c.json({ error: "keyPath must be inside ~/.ssh" }, 400);
17653
17727
  }
17654
17728
  return c.json(unlockSshKey(resolved, passphrase));
@@ -17724,8 +17798,8 @@ var init_mtime_cache = __esm({
17724
17798
 
17725
17799
  // packages/server/dist/routes/agent-configs.js
17726
17800
  import { Hono as Hono4 } from "hono";
17727
- import fs35 from "node:fs/promises";
17728
- import path43 from "node:path";
17801
+ import fs36 from "node:fs/promises";
17802
+ import path44 from "node:path";
17729
17803
  import { homedir as homedir19 } from "node:os";
17730
17804
  import YAML10 from "yaml";
17731
17805
  function getAvailableTools() {
@@ -17736,6 +17810,17 @@ function getAvailableTools() {
17736
17810
  _cachedTools = [...tools, draft].map((t2) => ({ name: t2.name, description: t2.description }));
17737
17811
  return _cachedTools;
17738
17812
  }
17813
+ async function writeFileAtomic2(filePath, content) {
17814
+ const tmpPath = `${filePath}.tmp`;
17815
+ await fs36.writeFile(tmpPath, content, "utf-8");
17816
+ try {
17817
+ await fs36.rename(tmpPath, filePath);
17818
+ } catch {
17819
+ await fs36.writeFile(filePath, content, "utf-8");
17820
+ await fs36.rm(tmpPath, { force: true }).catch(() => {
17821
+ });
17822
+ }
17823
+ }
17739
17824
  function parseAgentYaml(content) {
17740
17825
  try {
17741
17826
  const data = YAML10.parse(content);
@@ -17821,29 +17906,29 @@ function defaultAgentYamlTemplate() {
17821
17906
  }, { lineWidth: 120 });
17822
17907
  }
17823
17908
  async function ensureDir2(dir) {
17824
- await fs35.mkdir(dir, { recursive: true });
17909
+ await fs36.mkdir(dir, { recursive: true });
17825
17910
  }
17826
17911
  async function scanAgentsDir(dir, scope) {
17827
17912
  const agents = [];
17828
17913
  let names;
17829
17914
  try {
17830
- names = await fs35.readdir(dir);
17915
+ names = await fs36.readdir(dir);
17831
17916
  } catch {
17832
17917
  return agents;
17833
17918
  }
17834
17919
  for (const entryName of names) {
17835
- const agentDir = path43.join(dir, entryName);
17920
+ const agentDir = path44.join(dir, entryName);
17836
17921
  try {
17837
- const stat = await fs35.stat(agentDir);
17922
+ const stat = await fs36.stat(agentDir);
17838
17923
  if (!stat.isDirectory())
17839
17924
  continue;
17840
17925
  } catch {
17841
17926
  continue;
17842
17927
  }
17843
- const yamlPath = path43.join(agentDir, "agent.yaml");
17928
+ const yamlPath = path44.join(agentDir, "agent.yaml");
17844
17929
  try {
17845
- const yamlStat = await fs35.stat(yamlPath);
17846
- const parsed = _yamlCache.get(yamlPath, yamlStat.mtimeMs) ?? _yamlCache.set(yamlPath, yamlStat.mtimeMs, parseAgentYaml(await fs35.readFile(yamlPath, "utf-8")));
17930
+ const yamlStat = await fs36.stat(yamlPath);
17931
+ const parsed = _yamlCache.get(yamlPath, yamlStat.mtimeMs) ?? _yamlCache.set(yamlPath, yamlStat.mtimeMs, parseAgentYaml(await fs36.readFile(yamlPath, "utf-8")));
17847
17932
  const { name, description, model, priority, tools, skills, internal } = parsed;
17848
17933
  agents.push({
17849
17934
  id: entryName,
@@ -17864,13 +17949,13 @@ async function scanAgentsDir(dir, scope) {
17864
17949
  return agents;
17865
17950
  }
17866
17951
  async function ensureDefaultAgent(dir) {
17867
- const defaultDir = path43.join(dir, "default");
17868
- const yamlPath = path43.join(defaultDir, "agent.yaml");
17952
+ const defaultDir = path44.join(dir, "default");
17953
+ const yamlPath = path44.join(defaultDir, "agent.yaml");
17869
17954
  try {
17870
- await fs35.access(yamlPath);
17955
+ await fs36.access(yamlPath);
17871
17956
  } catch {
17872
17957
  await ensureDir2(defaultDir);
17873
- await fs35.writeFile(yamlPath, defaultAgentYamlTemplate(), "utf-8");
17958
+ await fs36.writeFile(yamlPath, defaultAgentYamlTemplate(), "utf-8");
17874
17959
  }
17875
17960
  }
17876
17961
  function createAgentConfigRoutes() {
@@ -17888,7 +17973,7 @@ function createAgentConfigRoutes() {
17888
17973
  const projectId = c.req.query("projectId");
17889
17974
  let workspaceAgents = [];
17890
17975
  if (projectId) {
17891
- const wsAgentsDir = path43.join(projectId, ".halo", "agents");
17976
+ const wsAgentsDir = path44.join(projectId, ".halo", "agents");
17892
17977
  workspaceAgents = await scanAgentsDir(wsAgentsDir, "workspace");
17893
17978
  }
17894
17979
  const wsIds = new Set(workspaceAgents.map((a) => a.id));
@@ -17920,33 +18005,33 @@ function createAgentConfigRoutes() {
17920
18005
  if (scope === "workspace") {
17921
18006
  if (!body.projectId)
17922
18007
  return c.json({ error: "projectId required for workspace agents" }, 400);
17923
- baseDir = path43.join(body.projectId, ".halo", "agents");
18008
+ baseDir = path44.join(body.projectId, ".halo", "agents");
17924
18009
  } else {
17925
18010
  baseDir = GLOBAL_AGENTS_DIR4;
17926
18011
  }
17927
- const agentDir = path43.join(baseDir, id);
18012
+ const agentDir = path44.join(baseDir, id);
17928
18013
  try {
17929
- await fs35.access(agentDir);
18014
+ await fs36.access(agentDir);
17930
18015
  return c.json({ error: "Agent already exists" }, 409);
17931
18016
  } catch {
17932
18017
  }
17933
18018
  let conflictScope = null;
17934
18019
  if (scope === "workspace") {
17935
18020
  try {
17936
- await fs35.access(path43.join(GLOBAL_AGENTS_DIR4, id));
18021
+ await fs36.access(path44.join(GLOBAL_AGENTS_DIR4, id));
17937
18022
  conflictScope = "global";
17938
18023
  } catch {
17939
18024
  }
17940
18025
  } else if (body.projectId) {
17941
18026
  try {
17942
- await fs35.access(path43.join(body.projectId, ".halo", "agents", id));
18027
+ await fs36.access(path44.join(body.projectId, ".halo", "agents", id));
17943
18028
  conflictScope = "workspace";
17944
18029
  } catch {
17945
18030
  }
17946
18031
  }
17947
18032
  await ensureDir2(agentDir);
17948
- await fs35.writeFile(path43.join(agentDir, "agent.yaml"), defaultAgentYaml(body.name, body.description ?? ""), "utf-8");
17949
- await fs35.writeFile(path43.join(agentDir, "AGENT.md"), `# ${body.name}
18033
+ await fs36.writeFile(path44.join(agentDir, "agent.yaml"), defaultAgentYaml(body.name, body.description ?? ""), "utf-8");
18034
+ await fs36.writeFile(path44.join(agentDir, "AGENT.md"), `# ${body.name}
17950
18035
 
17951
18036
  ${body.description ?? ""}
17952
18037
  `, "utf-8");
@@ -17960,13 +18045,13 @@ ${body.description ?? ""}
17960
18045
  const projectId = c.req.query("projectId");
17961
18046
  let agentDir;
17962
18047
  if (scope === "workspace" && projectId) {
17963
- agentDir = path43.join(projectId, ".halo", "agents", id);
18048
+ agentDir = path44.join(projectId, ".halo", "agents", id);
17964
18049
  } else {
17965
- agentDir = path43.join(GLOBAL_AGENTS_DIR4, id);
18050
+ agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
17966
18051
  }
17967
- const yamlPath = path43.join(agentDir, "agent.yaml");
18052
+ const yamlPath = path44.join(agentDir, "agent.yaml");
17968
18053
  try {
17969
- const content = await fs35.readFile(yamlPath, "utf-8");
18054
+ const content = await fs36.readFile(yamlPath, "utf-8");
17970
18055
  return c.json({ yaml: content });
17971
18056
  } catch {
17972
18057
  return c.json({ error: "Agent not found" }, 404);
@@ -17983,17 +18068,17 @@ ${body.description ?? ""}
17983
18068
  const scope = body.scope ?? "global";
17984
18069
  let agentDir;
17985
18070
  if (scope === "workspace" && body.projectId) {
17986
- agentDir = path43.join(body.projectId, ".halo", "agents", id);
18071
+ agentDir = path44.join(body.projectId, ".halo", "agents", id);
17987
18072
  } else {
17988
- agentDir = path43.join(GLOBAL_AGENTS_DIR4, id);
18073
+ agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
17989
18074
  }
17990
- const yamlPath = path43.join(agentDir, "agent.yaml");
18075
+ const yamlPath = path44.join(agentDir, "agent.yaml");
17991
18076
  try {
17992
- await fs35.access(agentDir);
18077
+ await fs36.access(agentDir);
17993
18078
  } catch {
17994
18079
  return c.json({ error: "Agent not found" }, 404);
17995
18080
  }
17996
- await fs35.writeFile(yamlPath, body.yaml, "utf-8");
18081
+ await writeFileAtomic2(yamlPath, body.yaml);
17997
18082
  const { name, description, model } = parseAgentYaml(body.yaml);
17998
18083
  return c.json({ agent: { id, name, description, model, path: agentDir, scope } });
17999
18084
  });
@@ -18005,12 +18090,12 @@ ${body.description ?? ""}
18005
18090
  const projectId = c.req.query("projectId");
18006
18091
  let agentDir;
18007
18092
  if (scope === "workspace" && projectId) {
18008
- agentDir = path43.join(projectId, ".halo", "agents", id);
18093
+ agentDir = path44.join(projectId, ".halo", "agents", id);
18009
18094
  } else {
18010
- agentDir = path43.join(GLOBAL_AGENTS_DIR4, id);
18095
+ agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
18011
18096
  }
18012
18097
  try {
18013
- await fs35.access(agentDir);
18098
+ await fs36.access(agentDir);
18014
18099
  } catch {
18015
18100
  return c.json({ error: "Agent not found" }, 404);
18016
18101
  }
@@ -18020,7 +18105,7 @@ ${body.description ?? ""}
18020
18105
  return c.json({ error: "Cannot delete the last global agent. At least one global agent must remain." }, 400);
18021
18106
  }
18022
18107
  }
18023
- await fs35.rm(agentDir, { recursive: true, force: true });
18108
+ await fs36.rm(agentDir, { recursive: true, force: true });
18024
18109
  return c.json({ ok: true });
18025
18110
  });
18026
18111
  app2.patch("/agent-configs/:id/toggle", async (c) => {
@@ -18052,7 +18137,7 @@ ${body.description ?? ""}
18052
18137
  if (!filePath)
18053
18138
  return c.json({ content: "", exists: false, readOnly: !isMdWritable(fileType) });
18054
18139
  try {
18055
- const content = await fs35.readFile(filePath, "utf-8");
18140
+ const content = await fs36.readFile(filePath, "utf-8");
18056
18141
  return c.json({ content, exists: true, path: filePath, readOnly: !isMdWritable(fileType) });
18057
18142
  } catch {
18058
18143
  return c.json({ content: "", exists: false, path: filePath, readOnly: !isMdWritable(fileType) });
@@ -18085,7 +18170,7 @@ ${body.description ?? ""}
18085
18170
  continue;
18086
18171
  }
18087
18172
  try {
18088
- const content = await fs35.readFile(filePath, "utf-8");
18173
+ const content = await fs36.readFile(filePath, "utf-8");
18089
18174
  result[ft] = { content, exists: true, path: filePath, readOnly: !isMdWritable(ft) };
18090
18175
  } catch {
18091
18176
  result[ft] = { content: "", exists: false, path: filePath, readOnly: !isMdWritable(ft) };
@@ -18095,10 +18180,10 @@ ${body.description ?? ""}
18095
18180
  });
18096
18181
  function getSessionsDir(agentId, _source, projectId) {
18097
18182
  if (agentId.startsWith("__") && agentId.endsWith("__")) {
18098
- return path43.join(homedir19(), ".halo", "global", "internal-sessions", agentId);
18183
+ return path44.join(homedir19(), ".halo", "global", "internal-sessions", agentId);
18099
18184
  }
18100
- const base = projectId ? path43.join(projectId, ".halo") : path43.join(homedir19(), ".halo");
18101
- return path43.join(base, "sessions", agentId);
18185
+ const base = projectId ? path44.join(projectId, ".halo") : path44.join(homedir19(), ".halo");
18186
+ return path44.join(base, "sessions", agentId);
18102
18187
  }
18103
18188
  app2.get("/agent-configs/:id/sessions", async (c) => {
18104
18189
  const agentId = c.req.param("id");
@@ -18109,7 +18194,7 @@ ${body.description ?? ""}
18109
18194
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18110
18195
  let files;
18111
18196
  try {
18112
- files = await fs35.readdir(dir);
18197
+ files = await fs36.readdir(dir);
18113
18198
  } catch {
18114
18199
  return c.json({ sessions: [] });
18115
18200
  }
@@ -18118,7 +18203,7 @@ ${body.description ?? ""}
18118
18203
  if (!f.endsWith(".json"))
18119
18204
  continue;
18120
18205
  try {
18121
- const raw = await fs35.readFile(path43.join(dir, f), "utf-8");
18206
+ const raw = await fs36.readFile(path44.join(dir, f), "utf-8");
18122
18207
  const session = JSON.parse(raw);
18123
18208
  sessions.push({
18124
18209
  id: session.id,
@@ -18147,9 +18232,9 @@ ${body.description ?? ""}
18147
18232
  const projectId = c.req.query("projectId");
18148
18233
  const source = c.req.query("source") || "test-chat";
18149
18234
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18150
- const filePath = path43.join(dir, `${sessionId}.json`);
18235
+ const filePath = path44.join(dir, `${sessionId}.json`);
18151
18236
  try {
18152
- const raw = await fs35.readFile(filePath, "utf-8");
18237
+ const raw = await fs36.readFile(filePath, "utf-8");
18153
18238
  return c.json({ session: JSON.parse(raw) });
18154
18239
  } catch {
18155
18240
  return c.json({ error: "Session not found" }, 404);
@@ -18183,7 +18268,7 @@ ${body.description ?? ""}
18183
18268
  agentSnapshot: body.agentSnapshot ?? {},
18184
18269
  messages: messages2
18185
18270
  };
18186
- await fs35.writeFile(path43.join(dir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf-8");
18271
+ await fs36.writeFile(path44.join(dir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf-8");
18187
18272
  return c.json({ id: sessionId, session: { ...session, messages: void 0 } });
18188
18273
  });
18189
18274
  app2.delete("/agent-configs/:id/sessions", async (c) => {
@@ -18195,11 +18280,11 @@ ${body.description ?? ""}
18195
18280
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18196
18281
  let deleted = 0;
18197
18282
  try {
18198
- const files = await fs35.readdir(dir);
18283
+ const files = await fs36.readdir(dir);
18199
18284
  for (const f of files) {
18200
18285
  if (!f.endsWith(".json"))
18201
18286
  continue;
18202
- await fs35.rm(path43.join(dir, f));
18287
+ await fs36.rm(path44.join(dir, f));
18203
18288
  deleted++;
18204
18289
  }
18205
18290
  } catch {
@@ -18216,7 +18301,7 @@ ${body.description ?? ""}
18216
18301
  const source = c.req.query("source") || "test-chat";
18217
18302
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18218
18303
  try {
18219
- await fs35.rm(path43.join(dir, `${sessionId}.json`));
18304
+ await fs36.rm(path44.join(dir, `${sessionId}.json`));
18220
18305
  return c.json({ ok: true });
18221
18306
  } catch {
18222
18307
  return c.json({ error: "Session not found" }, 404);
@@ -18236,7 +18321,7 @@ var init_agent_configs = __esm({
18236
18321
  init_workspace_path();
18237
18322
  init_mtime_cache();
18238
18323
  _cachedTools = null;
18239
- GLOBAL_AGENTS_DIR4 = path43.join(homedir19(), ".halo", "global", "agents");
18324
+ GLOBAL_AGENTS_DIR4 = path44.join(homedir19(), ".halo", "global", "agents");
18240
18325
  FALLBACK_SCAFFOLD_PROVIDER = "aws-bedrock-claude-invoke";
18241
18326
  _yamlCache = createMtimeCache();
18242
18327
  }
@@ -18244,8 +18329,8 @@ var init_agent_configs = __esm({
18244
18329
 
18245
18330
  // packages/server/dist/routes/skills.js
18246
18331
  import { Hono as Hono5 } from "hono";
18247
- import fs36 from "node:fs/promises";
18248
- import path44 from "node:path";
18332
+ import fs37 from "node:fs/promises";
18333
+ import path45 from "node:path";
18249
18334
  import { homedir as homedir20 } from "node:os";
18250
18335
  import YAML11 from "yaml";
18251
18336
  function defaultSkillSettings() {
@@ -18264,20 +18349,20 @@ function defaultSkillSettings() {
18264
18349
  }
18265
18350
  async function readYamlFile3(filePath) {
18266
18351
  try {
18267
- const content = await fs36.readFile(filePath, "utf-8");
18352
+ const content = await fs37.readFile(filePath, "utf-8");
18268
18353
  return YAML11.parse(content) ?? {};
18269
18354
  } catch {
18270
18355
  try {
18271
18356
  const jsonPath = filePath.replace(/\.yaml$/, ".json");
18272
- return JSON.parse(await fs36.readFile(jsonPath, "utf-8"));
18357
+ return JSON.parse(await fs37.readFile(jsonPath, "utf-8"));
18273
18358
  } catch {
18274
18359
  return {};
18275
18360
  }
18276
18361
  }
18277
18362
  }
18278
18363
  async function writeYamlFile(filePath, data) {
18279
- await fs36.mkdir(path44.dirname(filePath), { recursive: true });
18280
- await fs36.writeFile(filePath, YAML11.stringify(data, { lineWidth: 120 }), "utf-8");
18364
+ await fs37.mkdir(path45.dirname(filePath), { recursive: true });
18365
+ await fs37.writeFile(filePath, YAML11.stringify(data, { lineWidth: 120 }), "utf-8");
18281
18366
  }
18282
18367
  async function addSkillSettings(settingsPath, skillId) {
18283
18368
  const settings = await readYamlFile3(settingsPath);
@@ -18300,7 +18385,7 @@ async function removeSkillSettings(settingsPath, skillId) {
18300
18385
  }
18301
18386
  }
18302
18387
  async function ensureDir3(dir) {
18303
- await fs36.mkdir(dir, { recursive: true });
18388
+ await fs37.mkdir(dir, { recursive: true });
18304
18389
  }
18305
18390
  function toSkillMd(name, description) {
18306
18391
  return `---
@@ -18317,32 +18402,32 @@ async function scanSkillsDir(dir, scope) {
18317
18402
  const skills = [];
18318
18403
  let names;
18319
18404
  try {
18320
- names = await fs36.readdir(dir);
18405
+ names = await fs37.readdir(dir);
18321
18406
  } catch {
18322
18407
  return skills;
18323
18408
  }
18324
18409
  for (const entryName of names) {
18325
- const skillDir = path44.join(dir, entryName);
18410
+ const skillDir = path45.join(dir, entryName);
18326
18411
  try {
18327
- const stat = await fs36.stat(skillDir);
18412
+ const stat = await fs37.stat(skillDir);
18328
18413
  if (!stat.isDirectory())
18329
18414
  continue;
18330
18415
  } catch {
18331
18416
  continue;
18332
18417
  }
18333
- const skillMdPath = path44.join(skillDir, "SKILL.md");
18418
+ const skillMdPath = path45.join(skillDir, "SKILL.md");
18334
18419
  try {
18335
- const mdStat = await fs36.stat(skillMdPath);
18420
+ const mdStat = await fs37.stat(skillMdPath);
18336
18421
  let meta = _skillMdCache.get(skillMdPath, mdStat.mtimeMs);
18337
18422
  if (!meta) {
18338
- const { name, description, command } = parseSkillFrontmatter(await fs36.readFile(skillMdPath, "utf-8"));
18423
+ const { name, description, command } = parseSkillFrontmatter(await fs37.readFile(skillMdPath, "utf-8"));
18339
18424
  meta = _skillMdCache.set(skillMdPath, mdStat.mtimeMs, { name: name || entryName, description: description ?? "", command });
18340
18425
  }
18341
18426
  skills.push({ id: entryName, name: meta.name, description: meta.description, path: skillDir, scope, command: meta.command });
18342
18427
  } catch {
18343
- const yamlPath = path44.join(skillDir, "skill.yaml");
18428
+ const yamlPath = path45.join(skillDir, "skill.yaml");
18344
18429
  try {
18345
- const yamlContent = await fs36.readFile(yamlPath, "utf-8");
18430
+ const yamlContent = await fs37.readFile(yamlPath, "utf-8");
18346
18431
  const m = yamlContent.match(/^name:\s*(.+)$/m);
18347
18432
  const d = yamlContent.match(/^description:\s*(.+)$/m);
18348
18433
  skills.push({ id: entryName, name: m?.[1]?.trim() || entryName, description: d?.[1]?.trim() || "", path: skillDir, scope });
@@ -18361,7 +18446,7 @@ function createSkillRoutes() {
18361
18446
  const projectId = c.req.query("projectId");
18362
18447
  let workspaceSkills = [];
18363
18448
  if (projectId) {
18364
- const wsSkillsDir = path44.join(projectId, ".halo", "skills");
18449
+ const wsSkillsDir = path45.join(projectId, ".halo", "skills");
18365
18450
  workspaceSkills = await scanSkillsDir(wsSkillsDir, "workspace");
18366
18451
  }
18367
18452
  const wsIds = new Set(workspaceSkills.map((s) => s.id));
@@ -18384,19 +18469,19 @@ function createSkillRoutes() {
18384
18469
  if (scope === "workspace") {
18385
18470
  if (!body.projectId)
18386
18471
  return c.json({ error: "projectId required for workspace skills" }, 400);
18387
- baseDir = path44.join(body.projectId, ".halo", "skills");
18472
+ baseDir = path45.join(body.projectId, ".halo", "skills");
18388
18473
  } else {
18389
18474
  baseDir = GLOBAL_SKILLS_DIR2;
18390
18475
  }
18391
- const skillDir = path44.join(baseDir, id);
18476
+ const skillDir = path45.join(baseDir, id);
18392
18477
  try {
18393
- await fs36.access(skillDir);
18478
+ await fs37.access(skillDir);
18394
18479
  return c.json({ error: "Skill already exists" }, 409);
18395
18480
  } catch {
18396
18481
  }
18397
18482
  await ensureDir3(skillDir);
18398
- await fs36.writeFile(path44.join(skillDir, "SKILL.md"), toSkillMd(body.name, body.description ?? ""), "utf-8");
18399
- const settingsPath = scope === "workspace" && body.projectId ? path44.join(body.projectId, ".halo", "settings.yaml") : GLOBAL_SETTINGS_PATH;
18483
+ await fs37.writeFile(path45.join(skillDir, "SKILL.md"), toSkillMd(body.name, body.description ?? ""), "utf-8");
18484
+ const settingsPath = scope === "workspace" && body.projectId ? path45.join(body.projectId, ".halo", "settings.yaml") : GLOBAL_SETTINGS_PATH;
18400
18485
  await addSkillSettings(settingsPath, id);
18401
18486
  const skill = { id, name: body.name, description: body.description ?? "", path: skillDir, scope };
18402
18487
  return c.json({ skill }, 201);
@@ -18409,17 +18494,17 @@ function createSkillRoutes() {
18409
18494
  const projectId = c.req.query("projectId");
18410
18495
  let skillDir;
18411
18496
  if (scope === "workspace" && projectId) {
18412
- skillDir = path44.join(projectId, ".halo", "skills", id);
18497
+ skillDir = path45.join(projectId, ".halo", "skills", id);
18413
18498
  } else {
18414
- skillDir = path44.join(GLOBAL_SKILLS_DIR2, id);
18499
+ skillDir = path45.join(GLOBAL_SKILLS_DIR2, id);
18415
18500
  }
18416
18501
  try {
18417
- await fs36.access(skillDir);
18502
+ await fs37.access(skillDir);
18418
18503
  } catch {
18419
18504
  return c.json({ error: "Skill not found" }, 404);
18420
18505
  }
18421
- await fs36.rm(skillDir, { recursive: true, force: true });
18422
- const settingsPath = scope === "workspace" && projectId ? path44.join(projectId, ".halo", "settings.yaml") : GLOBAL_SETTINGS_PATH;
18506
+ await fs37.rm(skillDir, { recursive: true, force: true });
18507
+ const settingsPath = scope === "workspace" && projectId ? path45.join(projectId, ".halo", "settings.yaml") : GLOBAL_SETTINGS_PATH;
18423
18508
  await removeSkillSettings(settingsPath, id);
18424
18509
  return c.json({ ok: true });
18425
18510
  });
@@ -18443,30 +18528,30 @@ var init_skills = __esm({
18443
18528
  init_db();
18444
18529
  init_workspace_path();
18445
18530
  init_mtime_cache();
18446
- GLOBAL_SETTINGS_PATH = path44.join(homedir20(), ".halo", "secrets", "settings.yaml");
18531
+ GLOBAL_SETTINGS_PATH = path45.join(homedir20(), ".halo", "secrets", "settings.yaml");
18447
18532
  _skillMdCache = createMtimeCache();
18448
18533
  }
18449
18534
  });
18450
18535
 
18451
18536
  // packages/server/dist/routes/settings.js
18452
18537
  import { Hono as Hono6 } from "hono";
18453
- import fs37 from "node:fs/promises";
18454
- import path45 from "node:path";
18538
+ import fs38 from "node:fs/promises";
18539
+ import path46 from "node:path";
18455
18540
  import { homedir as homedir21 } from "node:os";
18456
18541
  import YAML12 from "yaml";
18457
18542
  async function readSettingsFile(filePath) {
18458
18543
  try {
18459
- const content = await fs37.readFile(filePath, "utf-8");
18544
+ const content = await fs38.readFile(filePath, "utf-8");
18460
18545
  return YAML12.parse(content) ?? {};
18461
18546
  } catch {
18462
18547
  return {};
18463
18548
  }
18464
18549
  }
18465
18550
  async function writeSettingsFile(filePath, data) {
18466
- await fs37.mkdir(path45.dirname(filePath), { recursive: true });
18467
- await fs37.writeFile(filePath, YAML12.stringify(data, { lineWidth: 120 }), { encoding: "utf-8", mode: 384 });
18551
+ await fs38.mkdir(path46.dirname(filePath), { recursive: true });
18552
+ await fs38.writeFile(filePath, YAML12.stringify(data, { lineWidth: 120 }), { encoding: "utf-8", mode: 384 });
18468
18553
  try {
18469
- await fs37.chmod(filePath, 384);
18554
+ await fs38.chmod(filePath, 384);
18470
18555
  } catch {
18471
18556
  }
18472
18557
  }
@@ -18580,8 +18665,8 @@ function detectOrphans(tree, declared) {
18580
18665
  if (!block || typeof block !== "object")
18581
18666
  continue;
18582
18667
  for (const k of Object.keys(block)) {
18583
- const path65 = `${namespaceId}.${kindWord}.${k}`;
18584
- if (!declaredMap.has(path65)) {
18668
+ const path66 = `${namespaceId}.${kindWord}.${k}`;
18669
+ if (!declaredMap.has(path66)) {
18585
18670
  out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
18586
18671
  }
18587
18672
  }
@@ -18596,7 +18681,7 @@ function createSettingsRoutes() {
18596
18681
  const globalTree = await readSettingsFile(GLOBAL_SETTINGS_PATH2);
18597
18682
  let workspaceTree = null;
18598
18683
  if (projectId) {
18599
- workspaceTree = await readSettingsFile(path45.join(projectId, ".halo", "settings.yaml"));
18684
+ workspaceTree = await readSettingsFile(path46.join(projectId, ".halo", "settings.yaml"));
18600
18685
  }
18601
18686
  const declared = loadSettingsSchema(projectId ?? void 0);
18602
18687
  const sections = resolveSections(declared, globalTree, workspaceTree);
@@ -18646,7 +18731,7 @@ function createSettingsRoutes() {
18646
18731
  const offending = rejectGlobalOnlyAtWorkspace("workspace", flattenDotted(body.settings));
18647
18732
  if (offending)
18648
18733
  return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
18649
- const wsPath = path45.join(projectPath, ".halo", "settings.yaml");
18734
+ const wsPath = path46.join(projectPath, ".halo", "settings.yaml");
18650
18735
  await writeSettingsFile(wsPath, body.settings);
18651
18736
  } else {
18652
18737
  await writeSettingsFile(GLOBAL_SETTINGS_PATH2, body.settings);
@@ -18666,7 +18751,7 @@ function createSettingsRoutes() {
18666
18751
  const offending = rejectGlobalOnlyAtWorkspace("workspace", [body.key]);
18667
18752
  if (offending)
18668
18753
  return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
18669
- filePath = path45.join(projectPath, ".halo", "settings.yaml");
18754
+ filePath = path46.join(projectPath, ".halo", "settings.yaml");
18670
18755
  } else {
18671
18756
  filePath = GLOBAL_SETTINGS_PATH2;
18672
18757
  }
@@ -18695,7 +18780,7 @@ function createSettingsRoutes() {
18695
18780
  const projectPath = await resolveProjectPath(body.projectId);
18696
18781
  if (!projectPath)
18697
18782
  return c.json({ error: "Project not found" }, 404);
18698
- filePath = path45.join(projectPath, ".halo", "settings.yaml");
18783
+ filePath = path46.join(projectPath, ".halo", "settings.yaml");
18699
18784
  } else {
18700
18785
  filePath = GLOBAL_SETTINGS_PATH2;
18701
18786
  }
@@ -18721,39 +18806,39 @@ var init_settings = __esm({
18721
18806
  "use strict";
18722
18807
  init_settings_schema();
18723
18808
  init_workspace_path();
18724
- GLOBAL_SETTINGS_PATH2 = path45.join(homedir21(), ".halo", "secrets", "settings.yaml");
18809
+ GLOBAL_SETTINGS_PATH2 = path46.join(homedir21(), ".halo", "secrets", "settings.yaml");
18725
18810
  _changeListeners = [];
18726
18811
  }
18727
18812
  });
18728
18813
 
18729
18814
  // packages/server/dist/evolution/archive.js
18730
- import fs38 from "node:fs";
18731
- import path46 from "node:path";
18815
+ import fs39 from "node:fs";
18816
+ import path47 from "node:path";
18732
18817
  import { pipeline } from "node:stream/promises";
18733
18818
  import JSZip from "jszip";
18734
18819
  import { eq as eq7, and as and5, isNull as isNull4, lte, isNotNull as isNotNull3, lt as lt2 } from "drizzle-orm";
18735
18820
  async function zipDir(srcDir, outZip) {
18736
- if (!fs38.existsSync(srcDir))
18821
+ if (!fs39.existsSync(srcDir))
18737
18822
  return false;
18738
- fs38.mkdirSync(path46.dirname(outZip), { recursive: true });
18823
+ fs39.mkdirSync(path47.dirname(outZip), { recursive: true });
18739
18824
  const zip = new JSZip();
18740
18825
  const walk = (dir, rel) => {
18741
- const entries = fs38.readdirSync(dir, { withFileTypes: true });
18826
+ const entries = fs39.readdirSync(dir, { withFileTypes: true });
18742
18827
  if (entries.length === 0 && rel) {
18743
18828
  zip.folder(rel);
18744
18829
  return;
18745
18830
  }
18746
18831
  for (const e of entries) {
18747
- const abs = path46.join(dir, e.name);
18832
+ const abs = path47.join(dir, e.name);
18748
18833
  const childRel = rel ? `${rel}/${e.name}` : e.name;
18749
18834
  if (e.isDirectory()) {
18750
18835
  walk(abs, childRel);
18751
18836
  } else if (e.isFile()) {
18752
- zip.file(childRel, fs38.readFileSync(abs), { date: fs38.statSync(abs).mtime });
18837
+ zip.file(childRel, fs39.readFileSync(abs), { date: fs39.statSync(abs).mtime });
18753
18838
  } else if (e.isSymbolicLink()) {
18754
18839
  try {
18755
- if (fs38.statSync(abs).isFile())
18756
- zip.file(childRel, fs38.readFileSync(abs));
18840
+ if (fs39.statSync(abs).isFile())
18841
+ zip.file(childRel, fs39.readFileSync(abs));
18757
18842
  } catch {
18758
18843
  }
18759
18844
  }
@@ -18761,7 +18846,7 @@ async function zipDir(srcDir, outZip) {
18761
18846
  };
18762
18847
  try {
18763
18848
  walk(srcDir, "");
18764
- await pipeline(zip.generateNodeStream({ type: "nodebuffer", streamFiles: true, compression: "DEFLATE" }), fs38.createWriteStream(outZip));
18849
+ await pipeline(zip.generateNodeStream({ type: "nodebuffer", streamFiles: true, compression: "DEFLATE" }), fs39.createWriteStream(outZip));
18765
18850
  return true;
18766
18851
  } catch (err) {
18767
18852
  rmFile(outZip);
@@ -18771,20 +18856,20 @@ async function zipDir(srcDir, outZip) {
18771
18856
  }
18772
18857
  function rmDir(dir) {
18773
18858
  try {
18774
- fs38.rmSync(dir, { recursive: true, force: true });
18859
+ fs39.rmSync(dir, { recursive: true, force: true });
18775
18860
  } catch {
18776
18861
  }
18777
18862
  }
18778
18863
  function rmFile(p) {
18779
18864
  try {
18780
- fs38.rmSync(p, { force: true });
18865
+ fs39.rmSync(p, { force: true });
18781
18866
  } catch {
18782
18867
  }
18783
18868
  }
18784
18869
  async function archiveRun(workspacePath, runId, errors) {
18785
18870
  const runDir = wsEvoRunDir(workspacePath, runId);
18786
18871
  const outZip = wsEvoArchivedRunZip(workspacePath, runId);
18787
- if (!fs38.existsSync(runDir)) {
18872
+ if (!fs39.existsSync(runDir)) {
18788
18873
  return true;
18789
18874
  }
18790
18875
  if (!await zipDir(runDir, outZip)) {
@@ -18797,7 +18882,7 @@ async function archiveRun(workspacePath, runId, errors) {
18797
18882
  async function archiveApply(workspacePath, applyId, errors) {
18798
18883
  const applyDir = wsEvoApplyDir(workspacePath, applyId);
18799
18884
  const outZip = wsEvoArchivedApplyZip(workspacePath, applyId);
18800
- if (!fs38.existsSync(applyDir))
18885
+ if (!fs39.existsSync(applyDir))
18801
18886
  return true;
18802
18887
  if (!await zipDir(applyDir, outZip)) {
18803
18888
  errors.push(`zip failed for apply ${applyId}`);
@@ -18924,8 +19009,8 @@ var init_raw_sqlite = __esm({
18924
19009
  // packages/server/dist/routes/evolution.js
18925
19010
  import { Hono as Hono7 } from "hono";
18926
19011
  import { eq as eq8, desc as desc2, lt as lt3, and as and6, or, sql } from "drizzle-orm";
18927
- import fs39 from "node:fs/promises";
18928
- import path47 from "node:path";
19012
+ import fs40 from "node:fs/promises";
19013
+ import path48 from "node:path";
18929
19014
  function createEvolutionRoutes() {
18930
19015
  const router = new Hono7();
18931
19016
  router.get("/evolution/runs", async (c) => {
@@ -18990,33 +19075,33 @@ function createEvolutionRoutes() {
18990
19075
  const dir = wsEvoRunDir(row.workspacePath, id);
18991
19076
  let patchMd = null;
18992
19077
  try {
18993
- patchMd = await fs39.readFile(path47.join(dir, "patch.md"), "utf-8");
19078
+ patchMd = await fs40.readFile(path48.join(dir, "patch.md"), "utf-8");
18994
19079
  } catch {
18995
19080
  }
18996
19081
  let scoreJson = null;
18997
19082
  try {
18998
- const raw = await fs39.readFile(path47.join(dir, "score.json"), "utf-8");
19083
+ const raw = await fs40.readFile(path48.join(dir, "score.json"), "utf-8");
18999
19084
  scoreJson = JSON.parse(raw);
19000
19085
  } catch {
19001
19086
  }
19002
19087
  let wrapperLog = null;
19003
19088
  try {
19004
- wrapperLog = await fs39.readFile(evoWrapperLogFile(id), "utf-8");
19089
+ wrapperLog = await fs40.readFile(evoWrapperLogFile(id), "utf-8");
19005
19090
  } catch {
19006
19091
  }
19007
19092
  let subCliLog = null;
19008
19093
  try {
19009
- subCliLog = await fs39.readFile(path47.join(dir, "sub-cli.log"), "utf-8");
19094
+ subCliLog = await fs40.readFile(path48.join(dir, "sub-cli.log"), "utf-8");
19010
19095
  } catch {
19011
19096
  }
19012
19097
  let skipReasonMd = null;
19013
19098
  try {
19014
- skipReasonMd = await fs39.readFile(path47.join(dir, ".skip.md"), "utf-8");
19099
+ skipReasonMd = await fs40.readFile(path48.join(dir, ".skip.md"), "utf-8");
19015
19100
  } catch {
19016
19101
  }
19017
19102
  let snapshotSummary = null;
19018
19103
  try {
19019
- const raw = await fs39.readFile(path47.join(dir, "source-snapshot.json"), "utf-8");
19104
+ const raw = await fs40.readFile(path48.join(dir, "source-snapshot.json"), "utf-8");
19020
19105
  const parsed = JSON.parse(raw);
19021
19106
  const msgs = parsed.rawMessages ?? [];
19022
19107
  const firstUser = msgs.find((m) => m.role === "user");
@@ -19504,12 +19589,12 @@ var init_session_archive2 = __esm({
19504
19589
  import Database4 from "better-sqlite3";
19505
19590
  import { drizzle as drizzle3 } from "drizzle-orm/better-sqlite3";
19506
19591
  import { sqliteTable as sqliteTable3, text as text3, integer as integer3 } from "drizzle-orm/sqlite-core";
19507
- import path48 from "node:path";
19508
- import fs40 from "node:fs";
19592
+ import path49 from "node:path";
19593
+ import fs41 from "node:fs";
19509
19594
  function createChannelDb(secretsDir) {
19510
- const channelsDir = path48.join(secretsDir, "channels");
19511
- fs40.mkdirSync(channelsDir, { recursive: true });
19512
- const dbPath = path48.join(channelsDir, "channels.db");
19595
+ const channelsDir = path49.join(secretsDir, "channels");
19596
+ fs41.mkdirSync(channelsDir, { recursive: true });
19597
+ const dbPath = path49.join(channelsDir, "channels.db");
19513
19598
  const sqlite = new Database4(dbPath);
19514
19599
  sqlite.pragma("journal_mode = WAL");
19515
19600
  sqlite.exec(CREATE_SQL2);
@@ -19559,11 +19644,11 @@ CREATE INDEX IF NOT EXISTS idx_channel_accounts_type ON channel_accounts(channel
19559
19644
  });
19560
19645
 
19561
19646
  // packages/server/dist/channels/shared/accounts.js
19562
- import fs41 from "node:fs";
19563
- import path49 from "node:path";
19647
+ import fs42 from "node:fs";
19648
+ import path50 from "node:path";
19564
19649
  import { eq as eq10, and as and7 } from "drizzle-orm";
19565
19650
  function resolveAccountWorkspace(account) {
19566
- if (!fs41.existsSync(account.workspacePath))
19651
+ if (!fs42.existsSync(account.workspacePath))
19567
19652
  return null;
19568
19653
  ensureWorkspaceHalo(account.workspacePath);
19569
19654
  return account.workspacePath;
@@ -19576,9 +19661,9 @@ function accessLevelError(value, allowed) {
19576
19661
  return `accessLevel must be one of: ${allowed.join(", ")}`;
19577
19662
  }
19578
19663
  function validateWorkspaceBody(workspacePath) {
19579
- if (!path49.isAbsolute(workspacePath))
19664
+ if (!path50.isAbsolute(workspacePath))
19580
19665
  return "workspacePath must be absolute";
19581
- if (!fs41.existsSync(workspacePath))
19666
+ if (!fs42.existsSync(workspacePath))
19582
19667
  return "workspace path not found";
19583
19668
  ensureWorkspaceHalo(workspacePath);
19584
19669
  return null;
@@ -19878,22 +19963,22 @@ var init_session_types = __esm({
19878
19963
 
19879
19964
  // packages/server/dist/routes/halo-city.js
19880
19965
  import { Hono as Hono10 } from "hono";
19881
- import fs42 from "node:fs";
19882
- import path50 from "node:path";
19966
+ import fs43 from "node:fs";
19967
+ import path51 from "node:path";
19883
19968
  import Database5 from "better-sqlite3";
19884
19969
  function scanSkills2(dir) {
19885
19970
  let names;
19886
19971
  try {
19887
- names = fs42.readdirSync(dir);
19972
+ names = fs43.readdirSync(dir);
19888
19973
  } catch {
19889
19974
  return [];
19890
19975
  }
19891
19976
  const out = [];
19892
19977
  for (const entry of names) {
19893
- const skillMd = path50.join(dir, entry, "SKILL.md");
19978
+ const skillMd = path51.join(dir, entry, "SKILL.md");
19894
19979
  let stat;
19895
19980
  try {
19896
- stat = fs42.statSync(skillMd);
19981
+ stat = fs43.statSync(skillMd);
19897
19982
  if (!stat.isFile())
19898
19983
  continue;
19899
19984
  } catch {
@@ -19902,7 +19987,7 @@ function scanSkills2(dir) {
19902
19987
  let meta = _skillCache.get(skillMd, stat.mtimeMs);
19903
19988
  if (!meta) {
19904
19989
  try {
19905
- const { name, description, command } = parseSkillFrontmatter(fs42.readFileSync(skillMd, "utf-8"));
19990
+ const { name, description, command } = parseSkillFrontmatter(fs43.readFileSync(skillMd, "utf-8"));
19906
19991
  meta = _skillCache.set(skillMd, stat.mtimeMs, { id: entry, name: name || entry, description: description ?? "", command });
19907
19992
  } catch {
19908
19993
  continue;
@@ -19988,10 +20073,10 @@ function snapshotWorkspace(sm, wsPath, label) {
19988
20073
  messageCount: meta.messageCount
19989
20074
  };
19990
20075
  });
19991
- const wsSkills = scanSkills2(path50.join(wsPath, ".halo", "skills"));
20076
+ const wsSkills = scanSkills2(path51.join(wsPath, ".halo", "skills"));
19992
20077
  return {
19993
20078
  path: wsPath,
19994
- key: path50.basename(wsPath) || wsPath,
20079
+ key: path51.basename(wsPath) || wsPath,
19995
20080
  label,
19996
20081
  sessions,
19997
20082
  counts,
@@ -20002,15 +20087,15 @@ function snapshotWorkspace(sm, wsPath, label) {
20002
20087
  function roReader(rawPath) {
20003
20088
  let wsPath;
20004
20089
  try {
20005
- wsPath = fs42.realpathSync(rawPath);
20090
+ wsPath = fs43.realpathSync(rawPath);
20006
20091
  } catch {
20007
20092
  return null;
20008
20093
  }
20009
20094
  const cached = _roReaders.get(wsPath);
20010
20095
  if (cached)
20011
20096
  return cached;
20012
- const dbPath = path50.join(wsPath, ".halo", "halo.db");
20013
- if (!fs42.existsSync(dbPath))
20097
+ const dbPath = path51.join(wsPath, ".halo", "halo.db");
20098
+ if (!fs43.existsSync(dbPath))
20014
20099
  return null;
20015
20100
  try {
20016
20101
  const db = new Database5(dbPath, { readonly: true, fileMustExist: true });
@@ -20038,7 +20123,7 @@ function dropRoReader(rawPath) {
20038
20123
  return;
20039
20124
  let wsPath = rawPath;
20040
20125
  try {
20041
- wsPath = fs42.realpathSync(rawPath);
20126
+ wsPath = fs43.realpathSync(rawPath);
20042
20127
  } catch {
20043
20128
  }
20044
20129
  const r = _roReaders.get(wsPath);
@@ -20086,12 +20171,12 @@ function snapshotWorkspaceReadonly(wsPath, label) {
20086
20171
  });
20087
20172
  return {
20088
20173
  path: wsPath,
20089
- key: path50.basename(wsPath) || wsPath,
20174
+ key: path51.basename(wsPath) || wsPath,
20090
20175
  label,
20091
20176
  sessions,
20092
20177
  counts,
20093
20178
  totalSessions: counts.total,
20094
- skills: scanSkills2(path50.join(wsPath, ".halo", "skills"))
20179
+ skills: scanSkills2(path51.join(wsPath, ".halo", "skills"))
20095
20180
  };
20096
20181
  }
20097
20182
  function readonlySessionCounts(wsPath, limit) {
@@ -20135,7 +20220,7 @@ function workspaceBirth(wsPath) {
20135
20220
  let b = _birthCache.get(wsPath);
20136
20221
  if (b === void 0) {
20137
20222
  try {
20138
- b = fs42.statSync(wsPath).birthtimeMs || fs42.statSync(wsPath).ctimeMs;
20223
+ b = fs43.statSync(wsPath).birthtimeMs || fs43.statSync(wsPath).ctimeMs;
20139
20224
  } catch {
20140
20225
  b = 0;
20141
20226
  }
@@ -20147,18 +20232,18 @@ function discoverWorkspaces(registry2) {
20147
20232
  const out = /* @__PURE__ */ new Map();
20148
20233
  const add = (p, label) => {
20149
20234
  try {
20150
- const resolved = fs42.realpathSync(p);
20235
+ const resolved = fs43.realpathSync(p);
20151
20236
  if (!out.has(resolved))
20152
20237
  out.set(resolved, label);
20153
20238
  } catch {
20154
20239
  }
20155
20240
  };
20156
20241
  for (const { workspacePath } of registry2.list())
20157
- add(workspacePath, path50.basename(workspacePath));
20242
+ add(workspacePath, path51.basename(workspacePath));
20158
20243
  try {
20159
20244
  const rows = getChannelDb().select().from(channelAccounts).all();
20160
20245
  for (const row of rows)
20161
- add(row.workspacePath, path50.basename(row.workspacePath));
20246
+ add(row.workspacePath, path51.basename(row.workspacePath));
20162
20247
  } catch {
20163
20248
  }
20164
20249
  return out;
@@ -20177,7 +20262,7 @@ function createShowRoutes(registry2) {
20177
20262
  return a.response;
20178
20263
  const { account } = a;
20179
20264
  const globalView = account.accessLevel === "full" || account.accessLevel === "observer";
20180
- const targets = globalView ? discoverWorkspaces(registry2) : /* @__PURE__ */ new Map([[account.workspacePath, path50.basename(account.workspacePath)]]);
20265
+ const targets = globalView ? discoverWorkspaces(registry2) : /* @__PURE__ */ new Map([[account.workspacePath, path51.basename(account.workspacePath)]]);
20181
20266
  const workspaces = [];
20182
20267
  for (const [wsPath, label] of targets) {
20183
20268
  try {
@@ -20211,7 +20296,7 @@ function createShowRoutes(registry2) {
20211
20296
  if (!globalView) {
20212
20297
  let allowed = false;
20213
20298
  try {
20214
- allowed = fs42.realpathSync(wsPath) === fs42.realpathSync(account.workspacePath);
20299
+ allowed = fs43.realpathSync(wsPath) === fs43.realpathSync(account.workspacePath);
20215
20300
  } catch {
20216
20301
  }
20217
20302
  if (!allowed)
@@ -20405,16 +20490,77 @@ var init_commands3 = __esm({
20405
20490
 
20406
20491
  // packages/server/dist/routes/agentcore.js
20407
20492
  import { Hono as Hono13 } from "hono";
20408
- import path51 from "node:path";
20409
- import fs43 from "node:fs";
20493
+ import path52 from "node:path";
20494
+ import fs44 from "node:fs";
20495
+ function sanitizeRuntimeId(runtimeSessionId) {
20496
+ return runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80);
20497
+ }
20410
20498
  function haloSessionId(runtimeSessionId) {
20411
- return `agentcore_${runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80)}`;
20499
+ return `agentcore_${sanitizeRuntimeId(runtimeSessionId)}`;
20412
20500
  }
20413
20501
  function userWorkspace(base, runtimeSessionId) {
20414
- const dir = path51.join(base, "users", runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80));
20415
- fs43.mkdirSync(dir, { recursive: true });
20502
+ const dir = path52.join(base, "users", sanitizeRuntimeId(runtimeSessionId));
20503
+ fs44.mkdirSync(dir, { recursive: true });
20416
20504
  return dir;
20417
20505
  }
20506
+ function acceptChunk(uploads, msg) {
20507
+ const uploadId = msg.uploadId;
20508
+ const seq = msg.seq;
20509
+ const total = msg.total;
20510
+ const mimeType = msg.mimeType;
20511
+ const data = msg.data;
20512
+ if (typeof uploadId !== "string" || !uploadId || uploadId.length > 64)
20513
+ return "image_chunk: uploadId must be a non-empty string (\u226464 chars)";
20514
+ if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 0)
20515
+ return "image_chunk: seq must be a non-negative integer";
20516
+ if (typeof total !== "number" || !Number.isInteger(total) || total < 1 || total > MAX_CHUNKS_PER_UPLOAD)
20517
+ return `image_chunk: total must be 1..${MAX_CHUNKS_PER_UPLOAD}`;
20518
+ if (typeof mimeType !== "string" || typeof data !== "string" || !data)
20519
+ return "image_chunk: string mimeType and non-empty string data required";
20520
+ if (seq >= total)
20521
+ return "image_chunk: seq out of range";
20522
+ let up = uploads.get(uploadId);
20523
+ if (!up) {
20524
+ if (uploads.size >= MAX_PENDING_UPLOADS)
20525
+ return `too many pending uploads (max ${MAX_PENDING_UPLOADS} per connection)`;
20526
+ up = { chunks: new Array(total), received: 0, mimeType, size: 0 };
20527
+ uploads.set(uploadId, up);
20528
+ }
20529
+ if (up.chunks.length !== total)
20530
+ return "image_chunk: total mismatch across chunks of one uploadId";
20531
+ if (up.chunks[seq] !== void 0)
20532
+ return null;
20533
+ if (up.size + data.length > MAX_IMAGE_BASE64_LENGTH) {
20534
+ uploads.delete(uploadId);
20535
+ return "image too large (max 8MB base64 per image)";
20536
+ }
20537
+ up.chunks[seq] = data;
20538
+ up.received++;
20539
+ up.size += data.length;
20540
+ return null;
20541
+ }
20542
+ function claimImageRefs(uploads, raw) {
20543
+ if (raw === void 0)
20544
+ return [];
20545
+ if (!Array.isArray(raw))
20546
+ return { error: "imageRefs must be an array" };
20547
+ if (raw.length > MAX_IMAGES_PER_MESSAGE)
20548
+ return { error: `too many images (max ${MAX_IMAGES_PER_MESSAGE} per message)` };
20549
+ const images = [];
20550
+ for (const ref of raw) {
20551
+ if (typeof ref !== "string")
20552
+ return { error: "imageRefs entries must be strings" };
20553
+ const up = uploads.get(ref);
20554
+ if (!up)
20555
+ return { error: `unknown uploadId: ${ref}` };
20556
+ if (up.received !== up.chunks.length)
20557
+ return { error: `upload ${ref} incomplete (${up.received}/${up.chunks.length} chunks)` };
20558
+ images.push({ data: up.chunks.join(""), mimeType: up.mimeType });
20559
+ }
20560
+ for (const ref of raw)
20561
+ uploads.delete(ref);
20562
+ return images;
20563
+ }
20418
20564
  async function ensureSession(sm, workspace, sessionId) {
20419
20565
  if (sm.getSessionById(sessionId))
20420
20566
  return;
@@ -20553,6 +20699,7 @@ function setupAgentCoreWebSocket(deps) {
20553
20699
  ready ??= ensureSession(sm, userWs, sessionId).then(() => attachListener());
20554
20700
  return ready;
20555
20701
  };
20702
+ const uploads = /* @__PURE__ */ new Map();
20556
20703
  ws.on("message", (raw) => {
20557
20704
  let msg;
20558
20705
  try {
@@ -20578,8 +20725,20 @@ function setupAgentCoreWebSocket(deps) {
20578
20725
  });
20579
20726
  return;
20580
20727
  }
20728
+ if (msg.type === "image_chunk") {
20729
+ const err = acceptChunk(uploads, msg);
20730
+ if (err)
20731
+ send({ type: "error", error: err });
20732
+ return;
20733
+ }
20581
20734
  const text5 = typeof msg.inputText === "string" ? msg.inputText.trim() : "";
20582
- if (!text5) {
20735
+ const claimed = claimImageRefs(uploads, msg.imageRefs);
20736
+ if (!Array.isArray(claimed)) {
20737
+ send({ type: "error", error: claimed.error });
20738
+ return;
20739
+ }
20740
+ const images = claimed;
20741
+ if (!text5 && images.length === 0) {
20583
20742
  return;
20584
20743
  }
20585
20744
  if (text5.startsWith("/")) {
@@ -20637,10 +20796,29 @@ function setupAgentCoreWebSocket(deps) {
20637
20796
  void (async () => {
20638
20797
  try {
20639
20798
  await ensureOnce();
20640
- sm.appendUserMessage(sessionId, text5);
20799
+ const notes = [];
20800
+ for (const img of images) {
20801
+ try {
20802
+ const savedPath = await saveInboundMedia({
20803
+ workspacePath: userWs,
20804
+ accountId: sanitizeRuntimeId(runtimeSessionId),
20805
+ channel: "agentcore",
20806
+ buffer: Buffer.from(img.data, "base64"),
20807
+ kind: "image",
20808
+ mimeType: img.mimeType
20809
+ });
20810
+ notes.push(`[\u56FE\u7247\u5DF2\u4FDD\u5B58: ${savedPath}]`);
20811
+ } catch (err) {
20812
+ console.log(`[AgentCore] image save failed: ${err instanceof Error ? err.message : String(err)}`);
20813
+ notes.push("[\u56FE\u7247]");
20814
+ }
20815
+ }
20816
+ const fullText = notes.length > 0 ? text5 ? `${text5}
20817
+ ${notes.join("\n")}` : notes.join("\n") : text5;
20818
+ sm.appendUserMessage(sessionId, fullText);
20641
20819
  const result = await sm.sendUserMessage(sessionId, `[channel: agentcore]
20642
20820
 
20643
- ${text5}`);
20821
+ ${fullText}`, images.length > 0 ? images : void 0);
20644
20822
  if (result === "queued")
20645
20823
  send({ type: "queued" });
20646
20824
  } catch (err) {
@@ -20651,26 +20829,32 @@ ${text5}`);
20651
20829
  ws.on("close", () => {
20652
20830
  unsubscribe?.();
20653
20831
  unsubscribe = null;
20832
+ uploads.clear();
20654
20833
  activeOverrides.delete(runtimeSessionId);
20655
20834
  });
20656
20835
  console.log(`[AgentCore] ws connected \u2014 session ${sessionId}`);
20657
20836
  });
20658
20837
  }
20659
- var SESSION_HEADER, activeOverrides;
20838
+ var SESSION_HEADER, MAX_IMAGES_PER_MESSAGE, MAX_IMAGE_BASE64_LENGTH, MAX_PENDING_UPLOADS, MAX_CHUNKS_PER_UPLOAD, activeOverrides;
20660
20839
  var init_agentcore = __esm({
20661
20840
  "packages/server/dist/routes/agentcore.js"() {
20662
20841
  "use strict";
20663
20842
  init_commands2();
20664
20843
  init_ui_log_builder();
20665
20844
  init_session_types();
20845
+ init_media_store();
20666
20846
  SESSION_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id";
20847
+ MAX_IMAGES_PER_MESSAGE = 4;
20848
+ MAX_IMAGE_BASE64_LENGTH = 8 * 1024 * 1024;
20849
+ MAX_PENDING_UPLOADS = 4;
20850
+ MAX_CHUNKS_PER_UPLOAD = 256;
20667
20851
  activeOverrides = /* @__PURE__ */ new Map();
20668
20852
  }
20669
20853
  });
20670
20854
 
20671
20855
  // packages/server/dist/ws/file-watcher.js
20672
- import fs44 from "node:fs";
20673
- import path52 from "node:path";
20856
+ import fs45 from "node:fs";
20857
+ import path53 from "node:path";
20674
20858
  function enqueueNativeOp(op) {
20675
20859
  const result = nativeOpChain.then(op);
20676
20860
  nativeOpChain = result.catch(() => {
@@ -20678,12 +20862,12 @@ function enqueueNativeOp(op) {
20678
20862
  return result;
20679
20863
  }
20680
20864
  function isUnwatchablePath(absPath) {
20681
- const norm = path52.resolve(absPath);
20682
- if (norm === "/" || norm === path52.parse(norm).root)
20865
+ const norm = path53.resolve(absPath);
20866
+ if (norm === "/" || norm === path53.parse(norm).root)
20683
20867
  return "filesystem root";
20684
20868
  const banned = ["/System", "/Library", "/private", "/usr", "/bin", "/sbin", "/etc"];
20685
20869
  for (const b of banned) {
20686
- if (norm === b || norm.startsWith(b + path52.sep))
20870
+ if (norm === b || norm.startsWith(b + path53.sep))
20687
20871
  return `system path ${b}`;
20688
20872
  }
20689
20873
  return null;
@@ -20779,7 +20963,7 @@ var init_file_watcher = __esm({
20779
20963
  }
20780
20964
  let root = workspaceRoot;
20781
20965
  try {
20782
- root = fs44.realpathSync(workspaceRoot);
20966
+ root = fs45.realpathSync(workspaceRoot);
20783
20967
  } catch {
20784
20968
  }
20785
20969
  if (this.subscription && this.workspaceRoot === root)
@@ -20823,10 +21007,10 @@ var init_file_watcher = __esm({
20823
21007
  const root = this.workspaceRoot;
20824
21008
  if (!root)
20825
21009
  return;
20826
- const raw = path52.relative(root, absPath);
21010
+ const raw = path53.relative(root, absPath);
20827
21011
  if (!raw || raw.startsWith(".."))
20828
21012
  return;
20829
- const rel = raw.split(path52.sep).join("/");
21013
+ const rel = raw.split(path53.sep).join("/");
20830
21014
  if (IS_WIN32 && rel.split("/").some((seg) => IGNORED_SEGMENT_SET.has(seg)))
20831
21015
  return;
20832
21016
  let action;
@@ -20837,7 +21021,7 @@ var init_file_watcher = __esm({
20837
21021
  } else {
20838
21022
  let isDir = false;
20839
21023
  try {
20840
- isDir = fs44.statSync(absPath).isDirectory();
21024
+ isDir = fs45.statSync(absPath).isDirectory();
20841
21025
  } catch {
20842
21026
  }
20843
21027
  action = isDir ? "addDir" : "add";
@@ -20913,8 +21097,8 @@ var init_file_watcher = __esm({
20913
21097
  });
20914
21098
 
20915
21099
  // packages/server/dist/ws/git-dir-watcher.js
20916
- import fs45 from "node:fs";
20917
- import path53 from "node:path";
21100
+ import fs46 from "node:fs";
21101
+ import path54 from "node:path";
20918
21102
  var GitDirWatcher;
20919
21103
  var init_git_dir_watcher = __esm({
20920
21104
  "packages/server/dist/ws/git-dir-watcher.js"() {
@@ -20938,8 +21122,8 @@ var init_git_dir_watcher = __esm({
20938
21122
  return;
20939
21123
  this.stop();
20940
21124
  this.workspaceRoot = workspaceRoot;
20941
- const gitDir = path53.join(workspaceRoot, ".git");
20942
- if (fs45.existsSync(gitDir)) {
21125
+ const gitDir = path54.join(workspaceRoot, ".git");
21126
+ if (fs46.existsSync(gitDir)) {
20943
21127
  this.watchGitDir(gitDir);
20944
21128
  } else {
20945
21129
  this.watchParentForGit(workspaceRoot);
@@ -20948,7 +21132,7 @@ var init_git_dir_watcher = __esm({
20948
21132
  /** Watch the `.git` dir for index/HEAD changes (steady state). */
20949
21133
  watchGitDir(gitDir) {
20950
21134
  try {
20951
- this.watcher = fs45.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
21135
+ this.watcher = fs46.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
20952
21136
  this.handleGitEvent(filename);
20953
21137
  });
20954
21138
  this.watcher.on("error", (err) => {
@@ -20967,7 +21151,7 @@ var init_git_dir_watcher = __esm({
20967
21151
  * Exists solely during the non-repo window; upgraded away on first `.git`. */
20968
21152
  watchParentForGit(workspaceRoot) {
20969
21153
  try {
20970
- this.watcher = fs45.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
21154
+ this.watcher = fs46.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
20971
21155
  this.handleParentEvent(filename);
20972
21156
  });
20973
21157
  this.watcher.on("error", (err) => {
@@ -21003,8 +21187,8 @@ var init_git_dir_watcher = __esm({
21003
21187
  const root = this.workspaceRoot;
21004
21188
  if (!root)
21005
21189
  return;
21006
- const gitDir = path53.join(root, ".git");
21007
- if (!fs45.existsSync(gitDir))
21190
+ const gitDir = path54.join(root, ".git");
21191
+ if (!fs46.existsSync(gitDir))
21008
21192
  return;
21009
21193
  this.closeWatcher();
21010
21194
  this.watchGitDir(gitDir);
@@ -21320,7 +21504,7 @@ var init_terminal_manager = __esm({
21320
21504
  });
21321
21505
 
21322
21506
  // packages/server/dist/ws/handler.js
21323
- import path54 from "node:path";
21507
+ import path55 from "node:path";
21324
21508
  function setupWebSocketHandler(deps) {
21325
21509
  const { wss: wss2, registry: registry2 } = deps;
21326
21510
  const clients = /* @__PURE__ */ new Set();
@@ -21342,7 +21526,7 @@ function setupWebSocketHandler(deps) {
21342
21526
  ackedChatIds.add(id);
21343
21527
  }
21344
21528
  function resolveProjectPath2(projectId) {
21345
- if (path54.isAbsolute(projectId))
21529
+ if (path55.isAbsolute(projectId))
21346
21530
  return projectId;
21347
21531
  return null;
21348
21532
  }
@@ -21968,11 +22152,11 @@ var init_handler = __esm({
21968
22152
  import Database6 from "better-sqlite3";
21969
22153
  import { drizzle as drizzle4 } from "drizzle-orm/better-sqlite3";
21970
22154
  import { sqliteTable as sqliteTable4, text as text4, integer as integer4 } from "drizzle-orm/sqlite-core";
21971
- import path55 from "node:path";
21972
- import fs46 from "node:fs";
22155
+ import path56 from "node:path";
22156
+ import fs47 from "node:fs";
21973
22157
  function createCronDb(globalDir2) {
21974
- fs46.mkdirSync(globalDir2, { recursive: true });
21975
- const dbPath = path55.join(globalDir2, "cron.db");
22158
+ fs47.mkdirSync(globalDir2, { recursive: true });
22159
+ const dbPath = path56.join(globalDir2, "cron.db");
21976
22160
  const sqlite = new Database6(dbPath);
21977
22161
  sqlite.pragma("journal_mode = WAL");
21978
22162
  sqlite.exec(CREATE_SQL3);
@@ -21980,6 +22164,9 @@ function createCronDb(globalDir2) {
21980
22164
  if (!cols.some((c) => c.name === "run_at")) {
21981
22165
  sqlite.exec(`ALTER TABLE cron_jobs ADD COLUMN run_at INTEGER`);
21982
22166
  }
22167
+ if (!cols.some((c) => c.name === "timeout_sec")) {
22168
+ sqlite.exec(`ALTER TABLE cron_jobs ADD COLUMN timeout_sec INTEGER`);
22169
+ }
21983
22170
  const runCols = sqlite.prepare(`PRAGMA table_info(cron_runs)`).all();
21984
22171
  if (!runCols.some((c) => c.name === "pid")) {
21985
22172
  sqlite.exec(`ALTER TABLE cron_runs ADD COLUMN pid INTEGER`);
@@ -22020,6 +22207,10 @@ var init_cron_db = __esm({
22020
22207
  runAt: integer4("run_at"),
22021
22208
  /** IANA timezone name; null = host timezone. */
22022
22209
  timezone: text4("timezone"),
22210
+ /** Max seconds a single cron-fired cli may run before the runner kills
22211
+ * it. NULL = default 3600 (runner.ts CLI_TIMEOUT_SEC). Range 60–21600,
22212
+ * enforced at the write points (REST routes / skill helper). */
22213
+ timeoutSec: integer4("timeout_sec"),
22023
22214
  /** JSON array of `{channelType, accountId}` records. Cron output (final
22024
22215
  * assistant text) is dispatched to each one. Empty array = log only. */
22025
22216
  targets: text4("targets").notNull().default("[]"),
@@ -22069,6 +22260,7 @@ CREATE TABLE IF NOT EXISTS cron_jobs (
22069
22260
  schedule TEXT NOT NULL,
22070
22261
  run_at INTEGER,
22071
22262
  timezone TEXT,
22263
+ timeout_sec INTEGER,
22072
22264
  targets TEXT NOT NULL DEFAULT '[]',
22073
22265
  enabled INTEGER NOT NULL DEFAULT 1,
22074
22266
  last_run_status TEXT,
@@ -22170,8 +22362,8 @@ var init_child_env = __esm({
22170
22362
 
22171
22363
  // packages/server/dist/cron/runner.js
22172
22364
  import { spawn as spawn3, execFileSync } from "node:child_process";
22173
- import fs47 from "node:fs";
22174
- import path56 from "node:path";
22365
+ import fs48 from "node:fs";
22366
+ import path57 from "node:path";
22175
22367
  import { homedir as homedir23 } from "node:os";
22176
22368
  import { Cron } from "croner";
22177
22369
  import { eq as eq11, lt as lt4 } from "drizzle-orm";
@@ -22242,10 +22434,10 @@ function isCronCliProcess(pid, jobId) {
22242
22434
  }
22243
22435
  }
22244
22436
  function logsDir() {
22245
- return path56.join(homedir23(), ".halo", "global", "logs", "cron");
22437
+ return path57.join(homedir23(), ".halo", "global", "logs", "cron");
22246
22438
  }
22247
22439
  function logPathFor(runId) {
22248
- return path56.join(logsDir(), `${runId}.log`);
22440
+ return path57.join(logsDir(), `${runId}.log`);
22249
22441
  }
22250
22442
  function newRunId() {
22251
22443
  return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
@@ -22296,7 +22488,7 @@ function sweepOrphanRuns(opts) {
22296
22488
  }
22297
22489
  }
22298
22490
  function startCronDaemon() {
22299
- fs47.mkdirSync(logsDir(), { recursive: true });
22491
+ fs48.mkdirSync(logsDir(), { recursive: true });
22300
22492
  sweepOrphanRuns();
22301
22493
  reloadAll();
22302
22494
  _daemonTimers.push(setInterval(pruneOldLogs, DAY_MS2));
@@ -22453,7 +22645,7 @@ async function runJob(jobId, triggerKind) {
22453
22645
  const runId = newRunId();
22454
22646
  const startedAt = Date.now();
22455
22647
  const logPath = logPathFor(runId);
22456
- fs47.mkdirSync(logsDir(), { recursive: true });
22648
+ fs48.mkdirSync(logsDir(), { recursive: true });
22457
22649
  if (_inflight.has(jobId)) {
22458
22650
  db.insert(cronRuns).values({
22459
22651
  id: runId,
@@ -22488,7 +22680,7 @@ async function runJob(jobId, triggerKind) {
22488
22680
  }).run();
22489
22681
  broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
22490
22682
  const targets = parseTargets(job.targets);
22491
- if (!fs47.existsSync(job.workspacePath)) {
22683
+ if (!fs48.existsSync(job.workspacePath)) {
22492
22684
  finalize(runId, jobId, {
22493
22685
  status: "failed",
22494
22686
  output: null,
@@ -22502,7 +22694,8 @@ async function runJob(jobId, triggerKind) {
22502
22694
  const sessionId = `cron-${job.id}`;
22503
22695
  const args = ["cli", "-a", job.agentId, "-s", sessionId, "-w", job.workspacePath];
22504
22696
  const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
22505
- const teeStream = fs47.createWriteStream(logPath, { flags: "a" });
22697
+ const timeoutSec = job.timeoutSec ?? CLI_TIMEOUT_SEC;
22698
+ const teeStream = fs48.createWriteStream(logPath, { flags: "a" });
22506
22699
  teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
22507
22700
  `);
22508
22701
  teeStream.write(`agent=${job.agentId} workspace=${job.workspacePath} schedule=${job.schedule}
@@ -22532,7 +22725,7 @@ async function runJob(jobId, triggerKind) {
22532
22725
  const killTimer = setTimeout(() => {
22533
22726
  timedOut = true;
22534
22727
  teeStream.write(`
22535
- [runner] timeout ${CLI_TIMEOUT_SEC}s \u2014 SIGTERM, grace ${KILL_GRACE_SEC}s
22728
+ [runner] timeout ${timeoutSec}s \u2014 SIGTERM, grace ${KILL_GRACE_SEC}s
22536
22729
  `);
22537
22730
  try {
22538
22731
  child.kill("SIGTERM");
@@ -22544,7 +22737,7 @@ async function runJob(jobId, triggerKind) {
22544
22737
  if (child.pid)
22545
22738
  killTreeHard(child.pid);
22546
22739
  }, KILL_GRACE_SEC * 1e3);
22547
- }, CLI_TIMEOUT_SEC * 1e3);
22740
+ }, timeoutSec * 1e3);
22548
22741
  child.stdout?.on("data", (chunk) => {
22549
22742
  stdout += chunk.toString("utf-8");
22550
22743
  teeStream.write(chunk);
@@ -22580,7 +22773,7 @@ async function runJob(jobId, triggerKind) {
22580
22773
  let failureReason = null;
22581
22774
  if (result.exitCode === 124) {
22582
22775
  status = "timeout";
22583
- failureReason = `cli exceeded ${CLI_TIMEOUT_SEC}s timeout`;
22776
+ failureReason = `cli exceeded ${timeoutSec}s timeout`;
22584
22777
  } else if (result.exitCode !== 0) {
22585
22778
  status = "failed";
22586
22779
  failureReason = stderr.slice(-500).trim() || `cli exited ${result.exitCode}`;
@@ -22674,15 +22867,15 @@ function pruneOldRuns(jobId) {
22674
22867
  }
22675
22868
  function pruneOldLogs() {
22676
22869
  const dir = logsDir();
22677
- if (!fs47.existsSync(dir))
22870
+ if (!fs48.existsSync(dir))
22678
22871
  return;
22679
22872
  const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
22680
- for (const name of fs47.readdirSync(dir)) {
22681
- const p = path56.join(dir, name);
22873
+ for (const name of fs48.readdirSync(dir)) {
22874
+ const p = path57.join(dir, name);
22682
22875
  try {
22683
- const stat = fs47.statSync(p);
22876
+ const stat = fs48.statSync(p);
22684
22877
  if (stat.mtimeMs < cutoff)
22685
- fs47.rmSync(p, { force: true });
22878
+ fs48.rmSync(p, { force: true });
22686
22879
  } catch {
22687
22880
  }
22688
22881
  }
@@ -22715,7 +22908,7 @@ var init_runner = __esm({
22715
22908
  // packages/server/dist/routes/cron.js
22716
22909
  import { Hono as Hono14 } from "hono";
22717
22910
  import { eq as eq12, desc as desc3, lt as lt5, and as and8 } from "drizzle-orm";
22718
- import fs48 from "node:fs/promises";
22911
+ import fs49 from "node:fs/promises";
22719
22912
  import { Cron as Cron2 } from "croner";
22720
22913
  function newJobId() {
22721
22914
  return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -22728,6 +22921,15 @@ function validateSchedule(expr) {
22728
22921
  return err instanceof Error ? err.message : String(err);
22729
22922
  }
22730
22923
  }
22924
+ function validateTimeoutSec(v) {
22925
+ if (typeof v !== "number" || !Number.isInteger(v)) {
22926
+ return "timeoutSec must be an integer (seconds)";
22927
+ }
22928
+ if (v < TIMEOUT_SEC_MIN || v > TIMEOUT_SEC_MAX) {
22929
+ return `timeoutSec must be between ${TIMEOUT_SEC_MIN} and ${TIMEOUT_SEC_MAX}`;
22930
+ }
22931
+ return null;
22932
+ }
22731
22933
  function createCronRoutes() {
22732
22934
  const router = new Hono14();
22733
22935
  router.get("/cron/jobs", async (c) => {
@@ -22771,6 +22973,11 @@ function createCronRoutes() {
22771
22973
  if (hasRunAt && body.runAt <= Date.now()) {
22772
22974
  return c.json({ error: "runAt must be in the future" }, 400);
22773
22975
  }
22976
+ if (body.timeoutSec !== void 0 && body.timeoutSec !== null) {
22977
+ const err = validateTimeoutSec(body.timeoutSec);
22978
+ if (err)
22979
+ return c.json({ error: err }, 400);
22980
+ }
22774
22981
  const id = newJobId();
22775
22982
  const now = Date.now();
22776
22983
  getCronDb().insert(cronJobs).values({
@@ -22782,6 +22989,7 @@ function createCronRoutes() {
22782
22989
  schedule: hasSchedule ? body.schedule : "",
22783
22990
  runAt: hasRunAt ? body.runAt : null,
22784
22991
  timezone: body.timezone ?? null,
22992
+ timeoutSec: body.timeoutSec ?? null,
22785
22993
  targets: JSON.stringify(body.targets ?? []),
22786
22994
  enabled: body.enabled === false ? 0 : 1,
22787
22995
  lastRunStatus: null,
@@ -22818,6 +23026,11 @@ function createCronRoutes() {
22818
23026
  if (body.runAt <= Date.now())
22819
23027
  return c.json({ error: "runAt must be in the future" }, 400);
22820
23028
  }
23029
+ if (body.timeoutSec !== void 0 && body.timeoutSec !== null) {
23030
+ const err = validateTimeoutSec(body.timeoutSec);
23031
+ if (err)
23032
+ return c.json({ error: err }, 400);
23033
+ }
22821
23034
  const hasSchedule = typeof body.schedule === "string" && body.schedule.trim().length > 0;
22822
23035
  const hasRunAt = typeof body.runAt === "number";
22823
23036
  if (hasSchedule && hasRunAt) {
@@ -22845,6 +23058,8 @@ function createCronRoutes() {
22845
23058
  patch.runAt = nextRunAt;
22846
23059
  if (body.timezone !== void 0)
22847
23060
  patch.timezone = body.timezone || null;
23061
+ if (body.timeoutSec !== void 0)
23062
+ patch.timeoutSec = body.timeoutSec;
22848
23063
  if (body.targets !== void 0)
22849
23064
  patch.targets = JSON.stringify(body.targets);
22850
23065
  if (body.enabled !== void 0)
@@ -22911,7 +23126,7 @@ function createCronRoutes() {
22911
23126
  return c.json({ log: null });
22912
23127
  let log2 = null;
22913
23128
  try {
22914
- log2 = await fs48.readFile(row.logPath, "utf-8");
23129
+ log2 = await fs49.readFile(row.logPath, "utf-8");
22915
23130
  } catch {
22916
23131
  }
22917
23132
  return c.json({ log: log2 });
@@ -22960,6 +23175,7 @@ function computeNextRun(j) {
22960
23175
  return null;
22961
23176
  }
22962
23177
  }
23178
+ var TIMEOUT_SEC_MIN, TIMEOUT_SEC_MAX;
22963
23179
  var init_cron = __esm({
22964
23180
  "packages/server/dist/routes/cron.js"() {
22965
23181
  "use strict";
@@ -22967,6 +23183,8 @@ var init_cron = __esm({
22967
23183
  init_runner();
22968
23184
  init_dispatcher();
22969
23185
  init_broadcast();
23186
+ TIMEOUT_SEC_MIN = 60;
23187
+ TIMEOUT_SEC_MAX = 21600;
22970
23188
  }
22971
23189
  });
22972
23190
 
@@ -23213,7 +23431,7 @@ var init_ticker = __esm({
23213
23431
  });
23214
23432
 
23215
23433
  // packages/server/dist/evolution/spawn.js
23216
- import path57 from "node:path";
23434
+ import path58 from "node:path";
23217
23435
  import { spawn as spawn4 } from "node:child_process";
23218
23436
  import { fileURLToPath as fileURLToPath2 } from "node:url";
23219
23437
  var __dirname2, WRAPPER_PATH, realEvoSpawner;
@@ -23221,8 +23439,8 @@ var init_spawn = __esm({
23221
23439
  "packages/server/dist/evolution/spawn.js"() {
23222
23440
  "use strict";
23223
23441
  init_child_env();
23224
- __dirname2 = path57.dirname(fileURLToPath2(import.meta.url));
23225
- WRAPPER_PATH = path57.resolve(__dirname2, "evo-wrapper.js");
23442
+ __dirname2 = path58.dirname(fileURLToPath2(import.meta.url));
23443
+ WRAPPER_PATH = path58.resolve(__dirname2, "evo-wrapper.js");
23226
23444
  realEvoSpawner = (mode, id) => {
23227
23445
  const child = spawn4(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
23228
23446
  detached: true,
@@ -24009,7 +24227,7 @@ var init_handler2 = __esm({
24009
24227
  // packages/server/dist/routes/telegram.js
24010
24228
  import { Hono as Hono15 } from "hono";
24011
24229
  import { Bot as Bot2 } from "grammy";
24012
- import fs49 from "node:fs";
24230
+ import fs50 from "node:fs";
24013
24231
  function createTelegramRoutes(deps) {
24014
24232
  const { db, channel } = deps;
24015
24233
  const app2 = new Hono15();
@@ -24019,7 +24237,7 @@ function createTelegramRoutes(deps) {
24019
24237
  accountId: acc.accountId,
24020
24238
  botUsername: acc.botUsername,
24021
24239
  workspacePath: acc.workspacePath,
24022
- workspaceMissing: !fs49.existsSync(acc.workspacePath),
24240
+ workspaceMissing: !fs50.existsSync(acc.workspacePath),
24023
24241
  label: acc.label,
24024
24242
  enabled: acc.enabled,
24025
24243
  accessLevel: acc.accessLevel,
@@ -24743,8 +24961,8 @@ var init_cdn = __esm({
24743
24961
  });
24744
24962
 
24745
24963
  // packages/server/dist/channels/wechat/send-media.js
24746
- import fs50 from "node:fs/promises";
24747
- import path58 from "node:path";
24964
+ import fs51 from "node:fs/promises";
24965
+ import path59 from "node:path";
24748
24966
  function inferKind(filePath) {
24749
24967
  const cls = classifyMedia(filePath);
24750
24968
  if (cls === "image")
@@ -24756,7 +24974,7 @@ function inferKind(filePath) {
24756
24974
  async function sendMediaFile(params) {
24757
24975
  const { baseUrl, token, toUserId, contextToken, filePath } = params;
24758
24976
  const kind = params.kind ?? inferKind(filePath);
24759
- const plaintext = await fs50.readFile(filePath);
24977
+ const plaintext = await fs51.readFile(filePath);
24760
24978
  const prep = prepareUpload(plaintext);
24761
24979
  console.log(`[wechat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
24762
24980
  const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
@@ -24794,7 +25012,7 @@ async function sendMediaFile(params) {
24794
25012
  aesKeyBase64,
24795
25013
  fileSizeCiphertext: prep.filesize,
24796
25014
  fileSize: prep.rawsize,
24797
- fileName: path58.basename(filePath)
25015
+ fileName: path59.basename(filePath)
24798
25016
  });
24799
25017
  const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
24800
25018
  const body = {
@@ -24957,8 +25175,8 @@ var init_login = __esm({
24957
25175
  });
24958
25176
 
24959
25177
  // packages/server/dist/channels/wechat/handler.js
24960
- import fs51 from "node:fs";
24961
- import path59 from "node:path";
25178
+ import fs52 from "node:fs";
25179
+ import path60 from "node:path";
24962
25180
  import QRCode from "qrcode";
24963
25181
  function wxRoute(fromUserId, contextToken) {
24964
25182
  return (prev) => ({
@@ -25347,7 +25565,7 @@ async function handleSlashCommand(args) {
25347
25565
  await reply(login.message);
25348
25566
  return true;
25349
25567
  }
25350
- const tmpPath = path59.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
25568
+ const tmpPath = path60.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
25351
25569
  await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
25352
25570
  await sendMediaFile({
25353
25571
  baseUrl: account.baseUrl,
@@ -25356,7 +25574,7 @@ async function handleSlashCommand(args) {
25356
25574
  contextToken,
25357
25575
  filePath: tmpPath
25358
25576
  });
25359
- await fs51.promises.unlink(tmpPath).catch(() => {
25577
+ await fs52.promises.unlink(tmpPath).catch(() => {
25360
25578
  });
25361
25579
  await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
25362
25580
  waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
@@ -25436,8 +25654,8 @@ var init_handler3 = __esm({
25436
25654
 
25437
25655
  // packages/server/dist/routes/wechat.js
25438
25656
  import { Hono as Hono16 } from "hono";
25439
- import path60 from "node:path";
25440
- import fs52 from "node:fs";
25657
+ import path61 from "node:path";
25658
+ import fs53 from "node:fs";
25441
25659
  function createWechatRoutes(deps) {
25442
25660
  const { db, channel } = deps;
25443
25661
  const app2 = new Hono16();
@@ -25446,7 +25664,7 @@ function createWechatRoutes(deps) {
25446
25664
  return {
25447
25665
  ...stripSecrets(acc),
25448
25666
  workspacePath: acc.workspacePath,
25449
- workspaceMissing: !fs52.existsSync(acc.workspacePath)
25667
+ workspaceMissing: !fs53.existsSync(acc.workspacePath)
25450
25668
  };
25451
25669
  });
25452
25670
  return c.json({ accounts });
@@ -25462,7 +25680,7 @@ function createWechatRoutes(deps) {
25462
25680
  return c.json({ error: "sessionKey required" }, 400);
25463
25681
  if (!body.workspacePath)
25464
25682
  return c.json({ error: "workspacePath required" }, 400);
25465
- if (!path60.isAbsolute(body.workspacePath))
25683
+ if (!path61.isAbsolute(body.workspacePath))
25466
25684
  return c.json({ error: "workspacePath must be absolute" }, 400);
25467
25685
  const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
25468
25686
  if (levelError)
@@ -25666,14 +25884,14 @@ function createWebChannel(deps) {
25666
25884
  return findActiveSessionId(sm, accountId, prefix, activeOverrides2, "full") ?? void 0;
25667
25885
  }
25668
25886
  function resolveWorkspace(account, override) {
25669
- let path65 = account.workspacePath;
25887
+ let path66 = account.workspacePath;
25670
25888
  if (override && override !== account.workspacePath) {
25671
25889
  if (account.accessLevel !== "full") {
25672
25890
  return { ok: false, error: "workspace override requires a full-access token" };
25673
25891
  }
25674
- path65 = override;
25892
+ path66 = override;
25675
25893
  }
25676
- const resolved = resolveAccountWorkspace({ ...account, workspacePath: path65 });
25894
+ const resolved = resolveAccountWorkspace({ ...account, workspacePath: path66 });
25677
25895
  if (!resolved)
25678
25896
  return { ok: false, error: "workspace not found" };
25679
25897
  return { ok: true, path: resolved };
@@ -26056,7 +26274,7 @@ var init_handler4 = __esm({
26056
26274
  import { Hono as Hono17 } from "hono";
26057
26275
  import { streamSSE } from "hono/streaming";
26058
26276
  import crypto4 from "node:crypto";
26059
- import fs53 from "node:fs";
26277
+ import fs54 from "node:fs";
26060
26278
  function createWebRoutes(deps) {
26061
26279
  const { db, channel } = deps;
26062
26280
  const app2 = new Hono17();
@@ -26066,7 +26284,7 @@ function createWebRoutes(deps) {
26066
26284
  accountId: acc.accountId,
26067
26285
  token: acc.token,
26068
26286
  workspacePath: acc.workspacePath,
26069
- workspaceMissing: !fs53.existsSync(acc.workspacePath),
26287
+ workspaceMissing: !fs54.existsSync(acc.workspacePath),
26070
26288
  label: acc.label,
26071
26289
  enabled: acc.enabled,
26072
26290
  accessLevel: acc.accessLevel,
@@ -26212,27 +26430,27 @@ function createWebRoutes(deps) {
26212
26430
  const filePath = c.req.query("path");
26213
26431
  if (!filePath)
26214
26432
  return c.json({ error: "path required" }, 400);
26215
- const path65 = await import("node:path");
26216
- const resolved = path65.default.resolve(account.workspacePath, filePath);
26217
- const root = path65.default.resolve(account.workspacePath);
26218
- if (resolved !== root && !resolved.startsWith(root + path65.default.sep)) {
26433
+ const path66 = await import("node:path");
26434
+ const resolved = path66.default.resolve(account.workspacePath, filePath);
26435
+ const root = path66.default.resolve(account.workspacePath);
26436
+ if (resolved !== root && !resolved.startsWith(root + path66.default.sep)) {
26219
26437
  return c.json({ error: "path traversal not allowed" }, 403);
26220
26438
  }
26221
26439
  let real;
26222
26440
  let realRoot;
26223
26441
  try {
26224
- real = fs53.realpathSync(resolved);
26225
- realRoot = fs53.realpathSync(root);
26442
+ real = fs54.realpathSync(resolved);
26443
+ realRoot = fs54.realpathSync(root);
26226
26444
  } catch {
26227
26445
  return c.json({ error: "file not found" }, 404);
26228
26446
  }
26229
- if (real !== realRoot && !real.startsWith(realRoot + path65.default.sep)) {
26447
+ if (real !== realRoot && !real.startsWith(realRoot + path66.default.sep)) {
26230
26448
  return c.json({ error: "path traversal not allowed" }, 403);
26231
26449
  }
26232
- const stat = fs53.statSync(real);
26450
+ const stat = fs54.statSync(real);
26233
26451
  if (stat.isDirectory())
26234
26452
  return c.json({ error: "cannot serve directory" }, 400);
26235
- const ext = path65.default.extname(real).toLowerCase();
26453
+ const ext = path66.default.extname(real).toLowerCase();
26236
26454
  const nonImageMimes = {
26237
26455
  ".mp4": "video/mp4",
26238
26456
  ".webm": "video/webm",
@@ -26240,8 +26458,8 @@ function createWebRoutes(deps) {
26240
26458
  ".txt": "text/plain"
26241
26459
  };
26242
26460
  const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
26243
- const fileName = path65.default.basename(resolved);
26244
- const data = fs53.readFileSync(real);
26461
+ const fileName = path66.default.basename(resolved);
26462
+ const data = fs54.readFileSync(real);
26245
26463
  c.header("content-type", contentType);
26246
26464
  c.header("content-disposition", `inline; filename="${fileName}"`);
26247
26465
  return c.body(data);
@@ -26582,10 +26800,10 @@ async function authTest(botToken) {
26582
26800
  return slackPost("auth.test", botToken, {});
26583
26801
  }
26584
26802
  async function uploadFile(args) {
26585
- const fs58 = await import("node:fs");
26586
- const path65 = await import("node:path");
26587
- const filename = args.filename ?? path65.basename(args.filePath);
26588
- const stat = fs58.statSync(args.filePath);
26803
+ const fs59 = await import("node:fs");
26804
+ const path66 = await import("node:path");
26805
+ const filename = args.filename ?? path66.basename(args.filePath);
26806
+ const stat = fs59.statSync(args.filePath);
26589
26807
  if (!stat.isFile())
26590
26808
  throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
26591
26809
  const length = stat.size;
@@ -26603,7 +26821,7 @@ async function uploadFile(args) {
26603
26821
  if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
26604
26822
  throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
26605
26823
  }
26606
- const buf = fs58.readFileSync(args.filePath);
26824
+ const buf = fs59.readFileSync(args.filePath);
26607
26825
  const step2 = await fetch(step1Parsed.upload_url, {
26608
26826
  method: "POST",
26609
26827
  body: buf
@@ -26748,7 +26966,7 @@ var init_api2 = __esm({
26748
26966
  });
26749
26967
 
26750
26968
  // packages/server/dist/channels/slack/handler.js
26751
- import path61 from "node:path";
26969
+ import path62 from "node:path";
26752
26970
  import { WebSocket } from "ws";
26753
26971
  function slashToBang(text5, commandNames) {
26754
26972
  if (commandNames.length === 0)
@@ -26853,7 +27071,7 @@ function startSlackChannel(deps) {
26853
27071
  const route = bridge.getRoute(sessionId);
26854
27072
  if (!account || !route)
26855
27073
  return;
26856
- const resolved = path61.resolve(filePath);
27074
+ const resolved = path62.resolve(filePath);
26857
27075
  if (!isMediaPathAllowed(resolved, account.workspacePath)) {
26858
27076
  console.log(`[slack] sendMedia blocked: ${filePath} not under workspace`);
26859
27077
  return;
@@ -26872,7 +27090,7 @@ function startSlackChannel(deps) {
26872
27090
  channel: route.channelId,
26873
27091
  threadTs: route.replyTs,
26874
27092
  text: t("handler.upload_failed", getLang(account), {
26875
- name: path61.basename(filePath),
27093
+ name: path62.basename(filePath),
26876
27094
  error: err instanceof Error ? err.message : String(err)
26877
27095
  })
26878
27096
  }).catch(() => {
@@ -27129,7 +27347,7 @@ var init_handler5 = __esm({
27129
27347
 
27130
27348
  // packages/server/dist/routes/slack.js
27131
27349
  import { Hono as Hono18 } from "hono";
27132
- import fs54 from "node:fs";
27350
+ import fs55 from "node:fs";
27133
27351
  function createSlackRoutes(deps) {
27134
27352
  const { db, channel } = deps;
27135
27353
  const app2 = new Hono18();
@@ -27152,7 +27370,7 @@ function createSlackRoutes(deps) {
27152
27370
  botUserId: a.botUserId,
27153
27371
  teamId: a.teamId,
27154
27372
  workspacePath: a.workspacePath,
27155
- workspaceMissing: !fs54.existsSync(a.workspacePath),
27373
+ workspaceMissing: !fs55.existsSync(a.workspacePath),
27156
27374
  label: a.label,
27157
27375
  enabled: a.enabled,
27158
27376
  accessLevel: a.accessLevel,
@@ -27684,10 +27902,10 @@ async function replyMessage(args) {
27684
27902
  return { message_id: parsed.data?.message_id ?? "" };
27685
27903
  }
27686
27904
  async function uploadImage(args) {
27687
- const fs58 = await import("node:fs");
27688
- const path65 = await import("node:path");
27689
- const buf = fs58.readFileSync(args.filePath);
27690
- const filename = path65.basename(args.filePath);
27905
+ const fs59 = await import("node:fs");
27906
+ const path66 = await import("node:path");
27907
+ const buf = fs59.readFileSync(args.filePath);
27908
+ const filename = path66.basename(args.filePath);
27691
27909
  const send = async (token2) => {
27692
27910
  const form = new FormData();
27693
27911
  form.append("image_type", "message");
@@ -27718,10 +27936,10 @@ async function uploadImage(args) {
27718
27936
  return { imageKey: parsed.data.image_key };
27719
27937
  }
27720
27938
  async function uploadFile2(args) {
27721
- const fs58 = await import("node:fs");
27722
- const path65 = await import("node:path");
27723
- const buf = fs58.readFileSync(args.filePath);
27724
- const filename = args.filename ?? path65.basename(args.filePath);
27939
+ const fs59 = await import("node:fs");
27940
+ const path66 = await import("node:path");
27941
+ const buf = fs59.readFileSync(args.filePath);
27942
+ const filename = args.filename ?? path66.basename(args.filePath);
27725
27943
  const send = async (token2) => {
27726
27944
  const form = new FormData();
27727
27945
  form.append("file_type", args.fileType);
@@ -27827,7 +28045,7 @@ var init_api3 = __esm({
27827
28045
  });
27828
28046
 
27829
28047
  // packages/server/dist/channels/feishu/handler.js
27830
- import path62 from "node:path";
28048
+ import path63 from "node:path";
27831
28049
  import * as Lark from "@larksuiteoapi/node-sdk";
27832
28050
  function buildSessionPrefixForThread2(chatId, rootId) {
27833
28051
  return sessionPrefix("feishu", `${chatId}:${rootId}`);
@@ -27950,7 +28168,7 @@ function startFeishuChannel(deps) {
27950
28168
  const route = bridge.getRoute(sessionId);
27951
28169
  if (!account || !route)
27952
28170
  return;
27953
- const resolved = path62.resolve(filePath);
28171
+ const resolved = path63.resolve(filePath);
27954
28172
  if (!isMediaPathAllowed(resolved, account.workspacePath)) {
27955
28173
  console.log(`[feishu] sendMedia blocked: ${filePath} not under workspace`);
27956
28174
  return;
@@ -27963,7 +28181,7 @@ function startFeishuChannel(deps) {
27963
28181
  account,
27964
28182
  ...route,
27965
28183
  text: t("handler.upload_failed", getLang(account), {
27966
- name: path62.basename(filePath),
28184
+ name: path63.basename(filePath),
27967
28185
  error: err instanceof Error ? err.message : String(err)
27968
28186
  })
27969
28187
  }).catch(() => {
@@ -28127,7 +28345,7 @@ async function replyToInbound(args) {
28127
28345
  async function sendFeishuMedia(args) {
28128
28346
  const { account, inboundMessageId, isP2P, chatId, filePath } = args;
28129
28347
  const cls = classifyMedia(filePath);
28130
- const ext = path62.extname(filePath).toLowerCase();
28348
+ const ext = path63.extname(filePath).toLowerCase();
28131
28349
  if (cls === "image") {
28132
28350
  const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
28133
28351
  await replyToInboundRaw({
@@ -28253,7 +28471,7 @@ var init_handler6 = __esm({
28253
28471
 
28254
28472
  // packages/server/dist/routes/feishu.js
28255
28473
  import { Hono as Hono19 } from "hono";
28256
- import fs55 from "node:fs";
28474
+ import fs56 from "node:fs";
28257
28475
  function createFeishuRoutes(deps) {
28258
28476
  const { db, channel } = deps;
28259
28477
  const app2 = new Hono19();
@@ -28277,7 +28495,7 @@ function createFeishuRoutes(deps) {
28277
28495
  botOpenId: a.botOpenId,
28278
28496
  hasEncryptKey: !!a.encryptKey,
28279
28497
  workspacePath: a.workspacePath,
28280
- workspaceMissing: !fs55.existsSync(a.workspacePath),
28498
+ workspaceMissing: !fs56.existsSync(a.workspacePath),
28281
28499
  label: a.label,
28282
28500
  enabled: a.enabled,
28283
28501
  accessLevel: a.accessLevel,
@@ -28665,14 +28883,14 @@ function passwordStrengthError(pw) {
28665
28883
  }
28666
28884
  function authMiddleware() {
28667
28885
  return async (c, next) => {
28668
- const path65 = c.req.path;
28669
- if (PUBLIC_PATHS.some((p) => path65 === p || path65.startsWith(p + "?"))) {
28886
+ const path66 = c.req.path;
28887
+ if (PUBLIC_PATHS.some((p) => path66 === p || path66.startsWith(p + "?"))) {
28670
28888
  return next();
28671
28889
  }
28672
28890
  const token = getCookie(c, COOKIE_NAME);
28673
28891
  const payload = validateToken(token);
28674
28892
  if (!payload) {
28675
- if (!path65.startsWith("/api/")) {
28893
+ if (!path66.startsWith("/api/")) {
28676
28894
  return next();
28677
28895
  }
28678
28896
  return c.json({ error: "Unauthorized" }, 401);
@@ -28711,17 +28929,17 @@ import { compress } from "hono/compress";
28711
28929
  import { serve } from "@hono/node-server";
28712
28930
  import { serveStatic } from "@hono/node-server/serve-static";
28713
28931
  import { WebSocketServer } from "ws";
28714
- import path63 from "node:path";
28715
- import fs56 from "node:fs";
28716
- import { spawnSync as spawnSync3 } from "node:child_process";
28932
+ import path64 from "node:path";
28933
+ import fs57 from "node:fs";
28934
+ import { spawnSync as spawnSync4 } from "node:child_process";
28717
28935
  import { homedir as homedir24 } from "node:os";
28718
28936
  import { fileURLToPath as fileURLToPath3 } from "node:url";
28719
28937
  function findProjectRoot() {
28720
28938
  let dir = process.cwd();
28721
28939
  for (let i = 0; i < 32; i++) {
28722
- if (fs56.existsSync(path63.join(dir, "pnpm-workspace.yaml")))
28940
+ if (fs57.existsSync(path64.join(dir, "pnpm-workspace.yaml")))
28723
28941
  return dir;
28724
- const parent = path63.dirname(dir);
28942
+ const parent = path64.dirname(dir);
28725
28943
  if (parent === dir)
28726
28944
  break;
28727
28945
  dir = parent;
@@ -28730,15 +28948,15 @@ function findProjectRoot() {
28730
28948
  return process.cwd();
28731
28949
  }
28732
28950
  function acquireSingleInstanceLock(lockFile) {
28733
- fs56.mkdirSync(path63.dirname(lockFile), { recursive: true });
28734
- const fd = fs56.openSync(lockFile, fs56.constants.O_RDWR | fs56.constants.O_CREAT, 420);
28735
- const result = spawnSync3("flock", ["-n", "3"], {
28951
+ fs57.mkdirSync(path64.dirname(lockFile), { recursive: true });
28952
+ const fd = fs57.openSync(lockFile, fs57.constants.O_RDWR | fs57.constants.O_CREAT, 420);
28953
+ const result = spawnSync4("flock", ["-n", "3"], {
28736
28954
  stdio: ["ignore", "ignore", "ignore", fd]
28737
28955
  });
28738
28956
  if (result.error) {
28739
28957
  const content = readLockPid(fd);
28740
28958
  if (content > 0 && isProcessAlive(content)) {
28741
- fs56.closeSync(fd);
28959
+ fs57.closeSync(fd);
28742
28960
  console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
28743
28961
  process.exit(1);
28744
28962
  }
@@ -28747,19 +28965,19 @@ function acquireSingleInstanceLock(lockFile) {
28747
28965
  if (content > 0 && !isProcessAlive(content)) {
28748
28966
  console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
28749
28967
  } else {
28750
- fs56.closeSync(fd);
28968
+ fs57.closeSync(fd);
28751
28969
  console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
28752
28970
  process.exit(1);
28753
28971
  }
28754
28972
  }
28755
- fs56.ftruncateSync(fd);
28756
- fs56.writeSync(fd, String(process.pid));
28757
- fs56.fsyncSync(fd);
28973
+ fs57.ftruncateSync(fd);
28974
+ fs57.writeSync(fd, String(process.pid));
28975
+ fs57.fsyncSync(fd);
28758
28976
  }
28759
28977
  function readLockPid(fd) {
28760
28978
  try {
28761
28979
  const buf = Buffer.alloc(32);
28762
- const n = fs56.readSync(fd, buf, 0, 32, 0);
28980
+ const n = fs57.readSync(fd, buf, 0, 32, 0);
28763
28981
  return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
28764
28982
  } catch {
28765
28983
  return 0;
@@ -28768,7 +28986,7 @@ function readLockPid(fd) {
28768
28986
  function isProcessAlive(pid) {
28769
28987
  if (process.platform === "win32") {
28770
28988
  try {
28771
- const out = spawnSync3("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
28989
+ const out = spawnSync4("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
28772
28990
  encoding: "utf-8",
28773
28991
  windowsHide: true
28774
28992
  });
@@ -28787,16 +29005,16 @@ function isProcessAlive(pid) {
28787
29005
  }
28788
29006
  }
28789
29007
  function readGitSha() {
28790
- if (!fs56.existsSync(path63.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs56.existsSync(path63.join(PROJECT_ROOT, ".git")))
29008
+ if (!fs57.existsSync(path64.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs57.existsSync(path64.join(PROJECT_ROOT, ".git")))
28791
29009
  return null;
28792
29010
  try {
28793
- const rev = spawnSync3("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
29011
+ const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
28794
29012
  if (rev.status !== 0)
28795
29013
  return null;
28796
29014
  const sha = rev.stdout.trim();
28797
29015
  if (!sha)
28798
29016
  return null;
28799
- const dirty = spawnSync3("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
29017
+ const dirty = spawnSync4("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
28800
29018
  return dirty ? `${sha}-dirty` : sha;
28801
29019
  } catch {
28802
29020
  return null;
@@ -28804,14 +29022,14 @@ function readGitSha() {
28804
29022
  }
28805
29023
  function resolveFrontendDir() {
28806
29024
  if (process.env.HALO_FRONTEND_DIR)
28807
- return path63.resolve(process.env.HALO_FRONTEND_DIR);
28808
- const bundled = path63.resolve(__dirname3, "..", "admin-out");
28809
- if (fs56.existsSync(path63.join(bundled, "index.html")))
29025
+ return path64.resolve(process.env.HALO_FRONTEND_DIR);
29026
+ const bundled = path64.resolve(__dirname3, "..", "admin-out");
29027
+ if (fs57.existsSync(path64.join(bundled, "index.html")))
28810
29028
  return bundled;
28811
- const sibling = path63.resolve(__dirname3, "..", "..", "admin", "out");
28812
- if (fs56.existsSync(path63.join(sibling, "index.html")))
29029
+ const sibling = path64.resolve(__dirname3, "..", "..", "admin", "out");
29030
+ if (fs57.existsSync(path64.join(sibling, "index.html")))
28813
29031
  return sibling;
28814
- return path63.resolve(PROJECT_ROOT, "packages", "admin", "out");
29032
+ return path64.resolve(PROJECT_ROOT, "packages", "admin", "out");
28815
29033
  }
28816
29034
  async function gracefulShutdown(signal) {
28817
29035
  console.log(`[Server] Received ${signal}, shutting down gracefully...`);
@@ -28867,13 +29085,13 @@ var init_dist2 = __esm({
28867
29085
  init_sandbox();
28868
29086
  init_init();
28869
29087
  init_git_ssh();
28870
- __dirname3 = path63.dirname(fileURLToPath3(import.meta.url));
29088
+ __dirname3 = path64.dirname(fileURLToPath3(import.meta.url));
28871
29089
  PORT = config.server.port;
28872
- HALO_VERSION = "1.1.1";
29090
+ HALO_VERSION = "1.1.3";
28873
29091
  PROJECT_ROOT = findProjectRoot();
28874
- HALO_HOME4 = path63.join(homedir24(), ".halo");
29092
+ HALO_HOME4 = path64.join(homedir24(), ".halo");
28875
29093
  GIT_SHA = readGitSha();
28876
- if (!fs56.existsSync(path63.join(HALO_HOME4, "global", ".template-version"))) {
29094
+ if (!fs57.existsSync(path64.join(HALO_HOME4, "global", ".template-version"))) {
28877
29095
  process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
28878
29096
  process.exit(1);
28879
29097
  }
@@ -28894,7 +29112,7 @@ var init_dist2 = __esm({
28894
29112
  process.exit(1);
28895
29113
  }
28896
29114
  if (!AGENTCORE)
28897
- acquireSingleInstanceLock(path63.join(HALO_HOME4, "global", "server.lock"));
29115
+ acquireSingleInstanceLock(path64.join(HALO_HOME4, "global", "server.lock"));
28898
29116
  {
28899
29117
  const declared = new Set(commandRegistry.listDescriptors().filter((d) => d.source === "builtin").map((d) => d.slashName));
28900
29118
  const dispatched = new Set(DISPATCH_COMMANDS);
@@ -28963,10 +29181,10 @@ var init_dist2 = __esm({
28963
29181
  app.route("/api", evolutionRoutes);
28964
29182
  cronRoutes = createCronRoutes();
28965
29183
  app.route("/api", cronRoutes);
28966
- channelDb = createChannelDb(path63.join(HALO_HOME4, "secrets"));
29184
+ channelDb = createChannelDb(path64.join(HALO_HOME4, "secrets"));
28967
29185
  setChannelDb(channelDb);
28968
- setEvoDb(createEvoDb(path63.join(HALO_HOME4, "global")));
28969
- setCronDb(createCronDb(path63.join(HALO_HOME4, "global")));
29186
+ setEvoDb(createEvoDb(path64.join(HALO_HOME4, "global")));
29187
+ setCronDb(createCronDb(path64.join(HALO_HOME4, "global")));
28970
29188
  setEvoSpawner(realEvoSpawner);
28971
29189
  if (!AGENTCORE) {
28972
29190
  startEvoTicker();
@@ -28992,11 +29210,11 @@ var init_dist2 = __esm({
28992
29210
  console.log(`[Server] AgentCore runtime mode \u2014 workspace: ${config.server.agentcoreWorkspace}`);
28993
29211
  }
28994
29212
  FRONTEND_DIR = resolveFrontendDir();
28995
- app.use("/*", serveStatic({ root: path63.relative(process.cwd(), FRONTEND_DIR) }));
29213
+ app.use("/*", serveStatic({ root: path64.relative(process.cwd(), FRONTEND_DIR) }));
28996
29214
  app.get("/*", (c) => {
28997
- const indexPath = path63.join(FRONTEND_DIR, "index.html");
29215
+ const indexPath = path64.join(FRONTEND_DIR, "index.html");
28998
29216
  try {
28999
- const html = fs56.readFileSync(indexPath, "utf-8");
29217
+ const html = fs57.readFileSync(indexPath, "utf-8");
29000
29218
  return c.html(html);
29001
29219
  } catch {
29002
29220
  return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
@@ -29767,11 +29985,11 @@ process.emitWarning = (warning, ...args) => {
29767
29985
 
29768
29986
  // packages/cli/src/index.ts
29769
29987
  init_server_supervisor();
29770
- import path64 from "node:path";
29771
- import fs57 from "node:fs";
29988
+ import path65 from "node:path";
29989
+ import fs58 from "node:fs";
29772
29990
  import { homedir as homedir25 } from "node:os";
29773
29991
  import { parseArgs as parseArgs2 } from "node:util";
29774
- var VERSION = "1.1.1";
29992
+ var VERSION = "1.1.3";
29775
29993
  var HELP_TOP = `Usage: halo <command> [options]
29776
29994
 
29777
29995
  Commands:
@@ -30028,8 +30246,8 @@ async function cmdSetup(argv = []) {
30028
30246
  const { buildBindProviderPlan: buildBindProviderPlan2, KEEP_CURRENT: KEEP_CURRENT2, AWS_CHAIN_PROVIDER: AWS_CHAIN_PROVIDER2 } = await Promise.resolve().then(() => (init_setup_bind_provider(), setup_bind_provider_exports));
30029
30247
  const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
30030
30248
  const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
30031
- const haloHome2 = path64.join(homedir25(), ".halo");
30032
- const globalDir2 = path64.join(haloHome2, "global");
30249
+ const haloHome2 = path65.join(homedir25(), ".halo");
30250
+ const globalDir2 = path65.join(haloHome2, "global");
30033
30251
  ensureHaloHome2(haloHome2);
30034
30252
  process.stderr.write(`[setup] ${haloHome2}/ ready
30035
30253
  `);
@@ -30332,10 +30550,10 @@ async function cmdUpgrade() {
30332
30550
  );
30333
30551
  process.exit(1);
30334
30552
  }
30335
- const { spawnSync: spawnSync4 } = await import("node:child_process");
30553
+ const { spawnSync: spawnSync5 } = await import("node:child_process");
30336
30554
  process.stderr.write(`Checking npm for the latest ${PKG}\u2026
30337
30555
  `);
30338
- const view = spawnSync4("npm", ["view", PKG, "version"], { encoding: "utf-8" });
30556
+ const view = spawnSync5("npm", ["view", PKG, "version"], { encoding: "utf-8" });
30339
30557
  if (view.error || view.status !== 0) {
30340
30558
  const err = view.error?.message ?? view.stderr?.trim() ?? `exit ${view.status}`;
30341
30559
  process.stderr.write(`halo: \`npm view\` failed \u2014 ${err}
@@ -30353,10 +30571,40 @@ async function cmdUpgrade() {
30353
30571
  `);
30354
30572
  return;
30355
30573
  }
30574
+ const { ALLOW_SCRIPTS: ALLOW_SCRIPTS2, verifyNativeModules: verifyNativeModules2 } = await Promise.resolve().then(() => (init_upgrade_verify(), upgrade_verify_exports));
30356
30575
  process.stderr.write(`Upgrading ${installed} \u2192 ${latest}\u2026
30357
30576
  `);
30358
- const install = spawnSync4("npm", ["install", "-g", `${PKG}@latest`], { stdio: "inherit" });
30577
+ const install = spawnSync5(
30578
+ "npm",
30579
+ ["install", "-g", `--allow-scripts=${ALLOW_SCRIPTS2}`, `${PKG}@latest`],
30580
+ { stdio: "inherit" }
30581
+ );
30359
30582
  if (install.status === 0) {
30583
+ const npmRoot = spawnSync5("npm", ["root", "-g"], {
30584
+ encoding: "utf-8",
30585
+ shell: process.platform === "win32"
30586
+ });
30587
+ const rootDir = npmRoot.status === 0 ? npmRoot.stdout.trim() : "";
30588
+ if (!rootDir) {
30589
+ process.stderr.write(`halo: \`npm root -g\` failed \u2014 cannot verify the new install
30590
+ `);
30591
+ process.exit(1);
30592
+ }
30593
+ const broken = verifyNativeModules2(path65.join(rootDir, PKG));
30594
+ if (broken.length > 0) {
30595
+ process.stderr.write(
30596
+ `
30597
+ halo: upgrade installed, but native module(s) failed to load: ${broken.join(", ")}
30598
+ This usually means npm blocked the module's install scripts (npm 12+ allowScripts policy \u2014 check \`npm -v\`).
30599
+
30600
+ Fix \u2014 reinstall with the scripts allowed:
30601
+ sudo npm install -g --allow-scripts=${ALLOW_SCRIPTS2} ${PKG}@latest
30602
+ Or persist the allowance first, then reinstall:
30603
+ npm config set allow-scripts=${ALLOW_SCRIPTS2} --location=user
30604
+ `
30605
+ );
30606
+ process.exit(1);
30607
+ }
30360
30608
  process.stderr.write(
30361
30609
  `
30362
30610
  Upgraded to ${latest}. Run \`halo server restart\` (or your service manager's restart) to pick up the new templates.
@@ -30373,7 +30621,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
30373
30621
  async function cmdAgents(flags) {
30374
30622
  const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
30375
30623
  await initRuntime2();
30376
- const workspace = path64.resolve(flags.workspace ?? process.cwd());
30624
+ const workspace = path65.resolve(flags.workspace ?? process.cwd());
30377
30625
  const agents = await listAgents2(workspace);
30378
30626
  if (agents.length === 0) {
30379
30627
  process.stderr.write("No agents found.\n");
@@ -30389,7 +30637,7 @@ async function cmdAgents(flags) {
30389
30637
  async function cmdSessions(flags) {
30390
30638
  const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
30391
30639
  await initRuntime2();
30392
- const workspace = path64.resolve(flags.workspace ?? process.cwd());
30640
+ const workspace = path65.resolve(flags.workspace ?? process.cwd());
30393
30641
  const sessions = listSessions2(workspace);
30394
30642
  if (sessions.length === 0) {
30395
30643
  process.stderr.write("No sessions found.\n");
@@ -30502,11 +30750,11 @@ ${stdinContent}` : stdinContent;
30502
30750
  harness.destroy();
30503
30751
  }
30504
30752
  }
30505
- var SERVER_LOCK_PATH = path64.join(homedir25(), ".halo", "global", "server.lock");
30506
- var SERVER_LOG_PATH = path64.join(homedir25(), ".halo", "logs", "server.log");
30753
+ var SERVER_LOCK_PATH = path65.join(homedir25(), ".halo", "global", "server.lock");
30754
+ var SERVER_LOG_PATH = path65.join(homedir25(), ".halo", "logs", "server.log");
30507
30755
  function readServerPid() {
30508
30756
  try {
30509
- const txt = fs57.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
30757
+ const txt = fs58.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
30510
30758
  const n = parseInt(txt, 10);
30511
30759
  return Number.isFinite(n) && n > 0 ? n : null;
30512
30760
  } catch {
@@ -30593,8 +30841,8 @@ async function cmdServerStart(argv, _unused) {
30593
30841
  return;
30594
30842
  }
30595
30843
  const { spawn: spawn5 } = await import("node:child_process");
30596
- fs57.mkdirSync(path64.dirname(SERVER_LOG_PATH), { recursive: true });
30597
- const out = fs57.openSync(SERVER_LOG_PATH, "a");
30844
+ fs58.mkdirSync(path65.dirname(SERVER_LOG_PATH), { recursive: true });
30845
+ const out = fs58.openSync(SERVER_LOG_PATH, "a");
30598
30846
  const child = spawn5(process.argv[0], [process.argv[1], "server", "start"], {
30599
30847
  detached: true,
30600
30848
  stdio: ["ignore", out, out],
@@ -30695,7 +30943,7 @@ async function cmdServerLogs(argv) {
30695
30943
  lines: { type: "string", short: "n" }
30696
30944
  }
30697
30945
  });
30698
- if (!fs57.existsSync(SERVER_LOG_PATH)) {
30946
+ if (!fs58.existsSync(SERVER_LOG_PATH)) {
30699
30947
  process.stderr.write(`No log file at ${SERVER_LOG_PATH}
30700
30948
  `);
30701
30949
  return;
@@ -30713,7 +30961,7 @@ function exitMissingSetup() {
30713
30961
  process.exit(1);
30714
30962
  }
30715
30963
  function isHaloHomeReady() {
30716
- return fs57.existsSync(path64.join(homedir25(), ".halo", "global", ".template-version"));
30964
+ return fs58.existsSync(path65.join(homedir25(), ".halo", "global", ".template-version"));
30717
30965
  }
30718
30966
  async function main2() {
30719
30967
  const argv = process.argv.slice(2);