@turmind/halo 1.1.0 → 1.1.2

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);
@@ -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
  }
@@ -5062,7 +5094,7 @@ var init_ui_log_builder = __esm({
5062
5094
  import fsSync5 from "node:fs";
5063
5095
  import path19 from "node:path";
5064
5096
  import { eq as eq3 } from "drizzle-orm";
5065
- var SessionUIStore;
5097
+ var UI_STATE_IDLE_TTL_MS, UI_STATE_SWEEP_PERIOD_MS, SessionUIStore;
5066
5098
  var init_session_ui_store = __esm({
5067
5099
  "packages/server/dist/agents/session-ui-store.js"() {
5068
5100
  "use strict";
@@ -5071,6 +5103,8 @@ var init_session_ui_store = __esm({
5071
5103
  init_session_store();
5072
5104
  init_session_archive();
5073
5105
  init_ui_log_builder();
5106
+ UI_STATE_IDLE_TTL_MS = 10 * 6e4;
5107
+ UI_STATE_SWEEP_PERIOD_MS = 6e4;
5074
5108
  SessionUIStore = class {
5075
5109
  host;
5076
5110
  db;
@@ -5090,9 +5124,13 @@ var init_session_ui_store = __esm({
5090
5124
  uiStateProjectPaths = /* @__PURE__ */ new Map();
5091
5125
  /** Debounced persist timers — prevents flooding disk with writes during rapid tool loops */
5092
5126
  persistTimers = /* @__PURE__ */ new Map();
5127
+ /** Last activity (event reduced / view built) per uiStates key — the idle
5128
+ * sweep's eviction clock. Invariant: every uiStates key has an entry. */
5129
+ uiStateTouched = /* @__PURE__ */ new Map();
5093
5130
  constructor(host) {
5094
5131
  this.host = host;
5095
5132
  this.db = host.getDb();
5133
+ setInterval(() => this.sweepIdleUIStates(), UI_STATE_SWEEP_PERIOD_MS).unref();
5096
5134
  }
5097
5135
  // ── Event routing ──────────────────────────────────────────────────
5098
5136
  /** Store global event handler (backward compat — Phase 2 only) */
@@ -5229,6 +5267,7 @@ var init_session_ui_store = __esm({
5229
5267
  * from the on-disk messages so the view is complete.
5230
5268
  */
5231
5269
  ensureUIState(rootId) {
5270
+ this.uiStateTouched.set(rootId, Date.now());
5232
5271
  const existing = this.uiStates.get(rootId);
5233
5272
  if (existing)
5234
5273
  return existing;
@@ -5449,10 +5488,48 @@ var init_session_ui_store = __esm({
5449
5488
  /**
5450
5489
  * Drop the in-memory UIState for a session (e.g. after stopSession / clear).
5451
5490
  * The disk file stays. This is a no-op if not loaded.
5491
+ *
5492
+ * A pending debounced persist is flushed BEFORE eviction: dropping first
5493
+ * would let a later ensureUIState rehydrate from a disk file that's missing
5494
+ * the last batch, and the next persist of that shorter log would overwrite
5495
+ * the orphaned timer's eventual write — messages lost. Must not be called
5496
+ * while the session tree is mid-turn (callers gate on that): flushing then
5497
+ * would snapshot a temp in-flight assistant message that the turn's own
5498
+ * flush would later duplicate.
5452
5499
  */
5453
5500
  dropUIState(sessionId) {
5501
+ const state = this.uiStates.get(sessionId);
5502
+ if (state && this.persistTimers.has(sessionId))
5503
+ this.flushPersist(sessionId, state);
5454
5504
  this.uiStates.delete(sessionId);
5455
5505
  this.uiStateProjectPaths.delete(sessionId);
5506
+ this.uiStateTouched.delete(sessionId);
5507
+ }
5508
+ /**
5509
+ * Evict UIStates whose tree has been idle past the TTL. This is THE eviction
5510
+ * point for the common leak (channel / cron / admin-viewed sessions that just
5511
+ * stop getting traffic) — archive and delete only cover explicit user action.
5512
+ *
5513
+ * Safety gates, in order:
5514
+ * - tree activity: an in-flight turn/compact anywhere in the tree keeps the
5515
+ * root's state (it holds the live sub buffers + streaming state);
5516
+ * - pending debounced persist counts as activity-in-progress? No — drop
5517
+ * flushes it first (see dropUIState), so a just-debounced write is safe.
5518
+ *
5519
+ * Re-entry cost after eviction: one loadSessionMessages disk read on the next
5520
+ * event/view — turn-start cadence at worst (ensureSession already does a
5521
+ * full-file read there anyway), never per-event.
5522
+ */
5523
+ sweepIdleUIStates() {
5524
+ const cutoff = Date.now() - UI_STATE_IDLE_TTL_MS;
5525
+ for (const [rootId, touched] of this.uiStateTouched) {
5526
+ if (touched > cutoff)
5527
+ continue;
5528
+ if (this.host.hasActiveWorkInTree(rootId))
5529
+ continue;
5530
+ this.dropUIState(rootId);
5531
+ console.debug(`[SessionUIStore] evicted idle UIState ${rootId}`);
5532
+ }
5456
5533
  }
5457
5534
  // ── Operations the manager's session-lifecycle methods delegate here ──
5458
5535
  // These exist so the manager never reaches into the maps above directly.
@@ -5493,6 +5570,7 @@ var init_session_ui_store = __esm({
5493
5570
  purge(id) {
5494
5571
  this.uiStates.delete(id);
5495
5572
  this.uiStateProjectPaths.delete(id);
5573
+ this.uiStateTouched.delete(id);
5496
5574
  const timer = this.persistTimers.get(id);
5497
5575
  if (timer) {
5498
5576
  clearTimeout(timer);
@@ -9075,13 +9153,20 @@ var init_session_agent_builder = __esm({
9075
9153
  });
9076
9154
  return { agent, yamlConfig, contextConfig, modelId, systemPrompt, thinkingEffort, meta, draftReset };
9077
9155
  }
9078
- /** 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. */
9079
9160
  validateAgentModelConfig(agentId, yamlConfig) {
9080
- const modelId = yamlConfig?.model?.id;
9081
- const endpoint = yamlConfig?.model?.endpoint;
9082
- 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;
9083
9168
  if (!modelId || !endpoint || !providerId) {
9084
- 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.`);
9085
9170
  }
9086
9171
  return { modelId, endpoint, providerId };
9087
9172
  }
@@ -11416,6 +11501,19 @@ ${result.summary}`, agentName: "user", report: true, taskId });
11416
11501
  }
11417
11502
  return false;
11418
11503
  }
11504
+ /** Any in-flight turn or compact anywhere in a root's session tree (root or
11505
+ * `root>…` descendants). SessionUIStoreHost member — the UI store's idle
11506
+ * sweep gates eviction on this, because a root's UIState carries its running
11507
+ * subs' live buffers even while the root itself is between turns. */
11508
+ hasActiveWorkInTree(rootId) {
11509
+ for (const [id, session] of this.sessions) {
11510
+ if (id !== rootId && !id.startsWith(`${rootId}>`))
11511
+ continue;
11512
+ if (session.promise !== null || session.isCompacting)
11513
+ return true;
11514
+ }
11515
+ return false;
11516
+ }
11419
11517
  /** Get context config for a session */
11420
11518
  async getContextConfig(sessionId) {
11421
11519
  const session = this.sessions.get(sessionId);
@@ -13985,6 +14083,41 @@ var init_setup_prompts = __esm({
13985
14083
  }
13986
14084
  });
13987
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
+
13988
14121
  // packages/cli/src/render-md.ts
13989
14122
  import { marked } from "marked";
13990
14123
  import { markedTerminal } from "marked-terminal";
@@ -14001,8 +14134,8 @@ var init_render_md = __esm({
14001
14134
  });
14002
14135
 
14003
14136
  // packages/cli/src/resolve-refs.ts
14004
- import fs27 from "node:fs";
14005
- import path33 from "node:path";
14137
+ import fs28 from "node:fs";
14138
+ import path34 from "node:path";
14006
14139
  function resolveRefs(input, workspace) {
14007
14140
  const images = [];
14008
14141
  const attachments = [];
@@ -14015,11 +14148,11 @@ function resolveRefs(input, workspace) {
14015
14148
  lastIdx = match.index + match[0].length;
14016
14149
  const kind = match[1];
14017
14150
  const rawPath = match[2] ?? match[3];
14018
- const resolved = path33.isAbsolute(rawPath) ? rawPath : path33.resolve(workspace, rawPath);
14019
- const ext = path33.extname(resolved).toLowerCase();
14151
+ const resolved = path34.isAbsolute(rawPath) ? rawPath : path34.resolve(workspace, rawPath);
14152
+ const ext = path34.extname(resolved).toLowerCase();
14020
14153
  let stat = null;
14021
14154
  try {
14022
- stat = fs27.statSync(resolved);
14155
+ stat = fs28.statSync(resolved);
14023
14156
  } catch {
14024
14157
  }
14025
14158
  if (!stat) {
@@ -14037,18 +14170,18 @@ function resolveRefs(input, workspace) {
14037
14170
  warnings.push(`${rawPath}: image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_IMAGE_BYTES / 1024 / 1024}MB), skipped`);
14038
14171
  continue;
14039
14172
  }
14040
- const data = fs27.readFileSync(resolved).toString("base64");
14173
+ const data = fs28.readFileSync(resolved).toString("base64");
14041
14174
  images.push({ data, mimeType: imageMimeFromExt(ext) ?? "application/octet-stream" });
14042
14175
  } else {
14043
- const relPath = path33.relative(workspace, resolved);
14176
+ const relPath = path34.relative(workspace, resolved);
14044
14177
  if (stat.size > MAX_FILE_BYTES) {
14045
14178
  const buf = Buffer.alloc(MAX_FILE_BYTES);
14046
- const fd = fs27.openSync(resolved, "r");
14179
+ const fd = fs28.openSync(resolved, "r");
14047
14180
  let n = 0;
14048
14181
  try {
14049
- n = fs27.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
14182
+ n = fs28.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
14050
14183
  } finally {
14051
- fs27.closeSync(fd);
14184
+ fs28.closeSync(fd);
14052
14185
  }
14053
14186
  const content = buf.subarray(0, n).toString("utf-8").replace(/\uFFFD+$/, "");
14054
14187
  parts.push(`
@@ -14059,7 +14192,7 @@ ${content}
14059
14192
  `);
14060
14193
  warnings.push(`${rawPath}: truncated (${(stat.size / 1024).toFixed(0)}KB, max ${MAX_FILE_BYTES / 1024}KB)`);
14061
14194
  } else {
14062
- const content = fs27.readFileSync(resolved, "utf-8");
14195
+ const content = fs28.readFileSync(resolved, "utf-8");
14063
14196
  parts.push(`
14064
14197
  <file path="${relPath}">
14065
14198
  ${content}
@@ -14331,12 +14464,12 @@ var init_streaming = __esm({
14331
14464
  });
14332
14465
 
14333
14466
  // packages/cli/src/tui/history.ts
14334
- import fs28 from "node:fs";
14335
- import path34 from "node:path";
14467
+ import fs29 from "node:fs";
14468
+ import path35 from "node:path";
14336
14469
  import os2 from "node:os";
14337
14470
  function loadHistory() {
14338
14471
  try {
14339
- const arr = JSON.parse(fs28.readFileSync(HISTORY_FILE, "utf8"));
14472
+ const arr = JSON.parse(fs29.readFileSync(HISTORY_FILE, "utf8"));
14340
14473
  if (Array.isArray(arr)) {
14341
14474
  return arr.filter((x) => typeof x === "string").slice(-MAX_ENTRIES);
14342
14475
  }
@@ -14349,8 +14482,8 @@ function appendHistory(entry) {
14349
14482
  const arr = loadHistory();
14350
14483
  if (arr[arr.length - 1] === entry) return;
14351
14484
  arr.push(entry);
14352
- fs28.mkdirSync(path34.dirname(HISTORY_FILE), { recursive: true });
14353
- 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)));
14354
14487
  } catch {
14355
14488
  }
14356
14489
  }
@@ -14358,7 +14491,7 @@ var HISTORY_FILE, MAX_ENTRIES;
14358
14491
  var init_history = __esm({
14359
14492
  "packages/cli/src/tui/history.ts"() {
14360
14493
  "use strict";
14361
- HISTORY_FILE = path34.join(os2.homedir(), ".halo", "global", "tui-history.json");
14494
+ HISTORY_FILE = path35.join(os2.homedir(), ".halo", "global", "tui-history.json");
14362
14495
  MAX_ENTRIES = 100;
14363
14496
  }
14364
14497
  });
@@ -14469,8 +14602,8 @@ var init_path_suggest = __esm({
14469
14602
  });
14470
14603
 
14471
14604
  // packages/cli/src/tui/path-suggest-engine.ts
14472
- import fs29 from "node:fs";
14473
- import path35 from "node:path";
14605
+ import fs30 from "node:fs";
14606
+ import path36 from "node:path";
14474
14607
  function detectActiveRef(value) {
14475
14608
  const reExplicit = /@(file|image|scope)\s+(?:"([^"]*)|(\S*))$/;
14476
14609
  const mExplicit = value.match(reExplicit);
@@ -14499,20 +14632,20 @@ function resolveDirAndPrefix(partial, workspace) {
14499
14632
  if (partial === "") {
14500
14633
  return { absDir: workspace, displayDir: ".", prefix: "" };
14501
14634
  }
14502
- const absBase = path35.isAbsolute(partial) ? partial : path35.join(workspace, partial);
14635
+ const absBase = path36.isAbsolute(partial) ? partial : path36.join(workspace, partial);
14503
14636
  if (partial.endsWith("/")) {
14504
14637
  return { absDir: absBase, displayDir: partial.replace(/\/$/, "") || "/", prefix: "" };
14505
14638
  }
14506
- const dirAbs = path35.dirname(absBase);
14507
- const prefix = path35.basename(absBase);
14508
- const dirRel = path35.dirname(partial);
14639
+ const dirAbs = path36.dirname(absBase);
14640
+ const prefix = path36.basename(absBase);
14641
+ const dirRel = path36.dirname(partial);
14509
14642
  return { absDir: dirAbs, displayDir: dirRel === "." ? "." : dirRel, prefix };
14510
14643
  }
14511
14644
  function scanCandidates(partial, workspace, kind) {
14512
14645
  const { absDir, displayDir, prefix } = resolveDirAndPrefix(partial, workspace);
14513
14646
  let entries;
14514
14647
  try {
14515
- entries = fs29.readdirSync(absDir, { withFileTypes: true });
14648
+ entries = fs30.readdirSync(absDir, { withFileTypes: true });
14516
14649
  } catch {
14517
14650
  return { items: [], cwdLabel: displayDir };
14518
14651
  }
@@ -14521,17 +14654,17 @@ function scanCandidates(partial, workspace, kind) {
14521
14654
  if (e.name.startsWith(".")) continue;
14522
14655
  if (prefix && !e.name.toLowerCase().startsWith(prefix.toLowerCase())) continue;
14523
14656
  const isDir = e.isDirectory();
14524
- const ext = path35.extname(e.name).toLowerCase();
14657
+ const ext = path36.extname(e.name).toLowerCase();
14525
14658
  const isImage = IMAGE_EXTS.has(ext);
14526
14659
  if (kind === "image" && !isDir && !isImage) continue;
14527
14660
  if (kind === "scope" && !isDir) continue;
14528
14661
  let insertPath;
14529
- if (path35.isAbsolute(partial)) {
14530
- insertPath = path35.join(absDir, e.name);
14662
+ if (path36.isAbsolute(partial)) {
14663
+ insertPath = path36.join(absDir, e.name);
14531
14664
  } else if (partial.endsWith("/") || partial === "") {
14532
14665
  insertPath = partial + e.name;
14533
14666
  } else {
14534
- const dirRel = path35.dirname(partial);
14667
+ const dirRel = path36.dirname(partial);
14535
14668
  insertPath = (dirRel === "." ? "" : dirRel + "/") + e.name;
14536
14669
  }
14537
14670
  items.push({ name: e.name, insertPath, isDir, isImage });
@@ -14550,8 +14683,8 @@ function applyPathPick(value, ref, pick) {
14550
14683
  const before = value.slice(0, markerStart);
14551
14684
  const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
14552
14685
  const needsQuote = pick.insertPath.includes(" ");
14553
- const path65 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
14554
- return `${before}@${kind} ${path65}`;
14686
+ const path66 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
14687
+ return `${before}@${kind} ${path66}`;
14555
14688
  }
14556
14689
  var init_path_suggest_engine = __esm({
14557
14690
  "packages/cli/src/tui/path-suggest-engine.ts"() {
@@ -16024,15 +16157,15 @@ var init_cli = __esm({
16024
16157
  });
16025
16158
 
16026
16159
  // packages/server/dist/channels/shared/media.js
16027
- import path36 from "node:path";
16160
+ import path37 from "node:path";
16028
16161
  import os3 from "node:os";
16029
16162
  function tempDir() {
16030
- return path36.resolve(os3.tmpdir());
16163
+ return path37.resolve(os3.tmpdir());
16031
16164
  }
16032
16165
  function isInTempDir(filePath) {
16033
- const resolved = path36.resolve(filePath);
16166
+ const resolved = path37.resolve(filePath);
16034
16167
  const tmp = tempDir();
16035
- return resolved === tmp || resolved.startsWith(tmp + path36.sep);
16168
+ return resolved === tmp || resolved.startsWith(tmp + path37.sep);
16036
16169
  }
16037
16170
  function extractMediaPaths(text5) {
16038
16171
  const mediaPaths = [];
@@ -16048,12 +16181,12 @@ function extractMediaMessage(text5) {
16048
16181
  return { text: stripped.replace(/\n{3,}/g, "\n\n").trim(), mediaPaths };
16049
16182
  }
16050
16183
  function isMediaPathAllowed(filePath, workspacePath) {
16051
- const resolved = path36.resolve(filePath);
16052
- const ws = path36.resolve(workspacePath);
16053
- 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);
16054
16187
  }
16055
16188
  function classifyMedia(filePath) {
16056
- const ext = path36.extname(filePath).toLowerCase();
16189
+ const ext = path37.extname(filePath).toLowerCase();
16057
16190
  if (IMAGE_EXTS.has(ext))
16058
16191
  return "image";
16059
16192
  if (VIDEO_EXTS.has(ext))
@@ -16074,13 +16207,13 @@ var init_media = __esm({
16074
16207
  });
16075
16208
 
16076
16209
  // packages/server/dist/routes/workspace-path.js
16077
- import fs30 from "node:fs/promises";
16210
+ import fs31 from "node:fs/promises";
16078
16211
  import { existsSync as existsSync2, realpathSync as realpathSync3 } from "node:fs";
16079
- import path37 from "node:path";
16212
+ import path38 from "node:path";
16080
16213
  async function resolveProjectPath(projectId) {
16081
- if (path37.isAbsolute(projectId)) {
16214
+ if (path38.isAbsolute(projectId)) {
16082
16215
  try {
16083
- await fs30.access(projectId);
16216
+ await fs31.access(projectId);
16084
16217
  return projectId;
16085
16218
  } catch {
16086
16219
  return null;
@@ -16089,13 +16222,13 @@ async function resolveProjectPath(projectId) {
16089
16222
  return null;
16090
16223
  }
16091
16224
  function realpathBounded2(filePath) {
16092
- let prefix = path37.resolve(filePath);
16225
+ let prefix = path38.resolve(filePath);
16093
16226
  const tail = [];
16094
16227
  while (!existsSync2(prefix)) {
16095
- const parent = path37.dirname(prefix);
16228
+ const parent = path38.dirname(prefix);
16096
16229
  if (parent === prefix)
16097
16230
  break;
16098
- tail.unshift(path37.basename(prefix));
16231
+ tail.unshift(path38.basename(prefix));
16099
16232
  prefix = parent;
16100
16233
  }
16101
16234
  let realPrefix;
@@ -16104,12 +16237,12 @@ function realpathBounded2(filePath) {
16104
16237
  } catch {
16105
16238
  realPrefix = prefix;
16106
16239
  }
16107
- return tail.length > 0 ? path37.join(realPrefix, ...tail) : realPrefix;
16240
+ return tail.length > 0 ? path38.join(realPrefix, ...tail) : realPrefix;
16108
16241
  }
16109
16242
  function validatePath(filePath, projectPath) {
16110
- const resolved = realpathBounded2(path37.resolve(projectPath, filePath));
16243
+ const resolved = realpathBounded2(path38.resolve(projectPath, filePath));
16111
16244
  const proj = realpathBounded2(projectPath);
16112
- return resolved === proj || resolved.startsWith(proj + path37.sep);
16245
+ return resolved === proj || resolved.startsWith(proj + path38.sep);
16113
16246
  }
16114
16247
  function isSafeIdSegment(id) {
16115
16248
  if (id === "." || id === "..")
@@ -16124,10 +16257,10 @@ var init_workspace_path = __esm({
16124
16257
 
16125
16258
  // packages/server/dist/routes/files.js
16126
16259
  import { Hono } from "hono";
16127
- import fs31 from "node:fs/promises";
16260
+ import fs32 from "node:fs/promises";
16128
16261
  import { createReadStream } from "node:fs";
16129
16262
  import { Readable } from "node:stream";
16130
- import path38 from "node:path";
16263
+ import path39 from "node:path";
16131
16264
  import { homedir as homedir17 } from "node:os";
16132
16265
  function createFileRoutes() {
16133
16266
  const app2 = new Hono();
@@ -16144,7 +16277,7 @@ function createFileRoutes() {
16144
16277
  }
16145
16278
  async function dirHasChildren(dirPath) {
16146
16279
  try {
16147
- const entries = await fs31.readdir(dirPath, { withFileTypes: true });
16280
+ const entries = await fs32.readdir(dirPath, { withFileTypes: true });
16148
16281
  for (const entry of entries) {
16149
16282
  if (!isSkippedName(entry.name))
16150
16283
  return true;
@@ -16157,7 +16290,7 @@ function createFileRoutes() {
16157
16290
  async function listDir(dirPath, basePath) {
16158
16291
  let entries;
16159
16292
  try {
16160
- entries = await fs31.readdir(dirPath, { withFileTypes: true });
16293
+ entries = await fs32.readdir(dirPath, { withFileTypes: true });
16161
16294
  } catch {
16162
16295
  return [];
16163
16296
  }
@@ -16172,8 +16305,8 @@ function createFileRoutes() {
16172
16305
  for (const entry of sorted) {
16173
16306
  if (isSkippedName(entry.name))
16174
16307
  continue;
16175
- const fullPath = path38.join(dirPath, entry.name);
16176
- 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("/");
16177
16310
  if (entry.isDirectory()) {
16178
16311
  nodes.push({
16179
16312
  name: entry.name,
@@ -16205,9 +16338,9 @@ function createFileRoutes() {
16205
16338
  if (relPath && !validatePath(relPath, projectPath)) {
16206
16339
  return c.json({ error: "Path traversal not allowed" }, 403);
16207
16340
  }
16208
- const targetPath = relPath ? path38.resolve(projectPath, relPath) : projectPath;
16341
+ const targetPath = relPath ? path39.resolve(projectPath, relPath) : projectPath;
16209
16342
  try {
16210
- const stat = await fs31.stat(targetPath);
16343
+ const stat = await fs32.stat(targetPath);
16211
16344
  if (!stat.isDirectory()) {
16212
16345
  return c.json({ error: "Path is not a directory" }, 400);
16213
16346
  }
@@ -16217,7 +16350,7 @@ function createFileRoutes() {
16217
16350
  const tree = await listDir(targetPath, projectPath);
16218
16351
  return c.json({
16219
16352
  projectId,
16220
- root: path38.basename(projectPath),
16353
+ root: path39.basename(projectPath),
16221
16354
  path: relPath,
16222
16355
  tree
16223
16356
  });
@@ -16250,7 +16383,7 @@ function createFileRoutes() {
16250
16383
  return;
16251
16384
  let entries;
16252
16385
  try {
16253
- entries = await fs31.readdir(dirPath, { withFileTypes: true });
16386
+ entries = await fs32.readdir(dirPath, { withFileTypes: true });
16254
16387
  } catch {
16255
16388
  return;
16256
16389
  }
@@ -16260,11 +16393,11 @@ function createFileRoutes() {
16260
16393
  if (isSkippedName(entry.name))
16261
16394
  continue;
16262
16395
  scanned++;
16263
- const fullPath = path38.join(dirPath, entry.name);
16396
+ const fullPath = path39.join(dirPath, entry.name);
16264
16397
  if (entry.isDirectory()) {
16265
16398
  if (dirsOnly) {
16266
16399
  const name = entry.name.toLowerCase();
16267
- const rel = path38.relative(projectRoot, fullPath);
16400
+ const rel = path39.relative(projectRoot, fullPath);
16268
16401
  if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
16269
16402
  matches.push({ name: entry.name, path: rel });
16270
16403
  }
@@ -16272,7 +16405,7 @@ function createFileRoutes() {
16272
16405
  await walk(fullPath);
16273
16406
  } else if (entry.isFile() && !dirsOnly) {
16274
16407
  const name = entry.name.toLowerCase();
16275
- const rel = path38.relative(projectRoot, fullPath);
16408
+ const rel = path39.relative(projectRoot, fullPath);
16276
16409
  if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
16277
16410
  matches.push({ name: entry.name, path: rel });
16278
16411
  }
@@ -16316,15 +16449,15 @@ function createFileRoutes() {
16316
16449
  }
16317
16450
  const uploaded = [];
16318
16451
  for (const file of files) {
16319
- const relPath = targetDir ? path38.join(targetDir, file.name) : file.name;
16452
+ const relPath = targetDir ? path39.join(targetDir, file.name) : file.name;
16320
16453
  if (!validatePath(relPath, projectPath)) {
16321
16454
  continue;
16322
16455
  }
16323
- const destPath = path38.resolve(projectPath, relPath);
16324
- const destDir = path38.dirname(destPath);
16325
- 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 });
16326
16459
  const buffer = Buffer.from(await file.arrayBuffer());
16327
- await fs31.writeFile(destPath, buffer);
16460
+ await fs32.writeFile(destPath, buffer);
16328
16461
  uploaded.push({
16329
16462
  name: file.name,
16330
16463
  path: relPath,
@@ -16352,9 +16485,9 @@ function createFileRoutes() {
16352
16485
  if (!validatePath(filePath, projectPath)) {
16353
16486
  return c.json({ error: "Path traversal not allowed" }, 403);
16354
16487
  }
16355
- const absolutePath = path38.resolve(projectPath, filePath);
16356
- await fs31.writeFile(absolutePath, content, "utf-8");
16357
- 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);
16358
16491
  return c.json({ ok: true, path: filePath, modifiedAt: stat.mtimeMs });
16359
16492
  } catch (err) {
16360
16493
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16376,14 +16509,14 @@ function createFileRoutes() {
16376
16509
  if (!validatePath(filePath, projectPath)) {
16377
16510
  return c.json({ error: "Path traversal not allowed" }, 403);
16378
16511
  }
16379
- const absolutePath = path38.resolve(projectPath, filePath);
16380
- await fs31.mkdir(path38.dirname(absolutePath), { recursive: true });
16512
+ const absolutePath = path39.resolve(projectPath, filePath);
16513
+ await fs32.mkdir(path39.dirname(absolutePath), { recursive: true });
16381
16514
  try {
16382
- await fs31.access(absolutePath);
16515
+ await fs32.access(absolutePath);
16383
16516
  return c.json({ error: "File already exists" }, 409);
16384
16517
  } catch {
16385
16518
  }
16386
- await fs31.writeFile(absolutePath, "", "utf-8");
16519
+ await fs32.writeFile(absolutePath, "", "utf-8");
16387
16520
  return c.json({ ok: true, path: filePath });
16388
16521
  } catch (err) {
16389
16522
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16405,8 +16538,8 @@ function createFileRoutes() {
16405
16538
  if (!validatePath(dirPath, projectPath)) {
16406
16539
  return c.json({ error: "Path traversal not allowed" }, 403);
16407
16540
  }
16408
- const absolutePath = path38.resolve(projectPath, dirPath);
16409
- await fs31.mkdir(absolutePath, { recursive: true });
16541
+ const absolutePath = path39.resolve(projectPath, dirPath);
16542
+ await fs32.mkdir(absolutePath, { recursive: true });
16410
16543
  return c.json({ ok: true, path: dirPath });
16411
16544
  } catch (err) {
16412
16545
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16428,10 +16561,10 @@ function createFileRoutes() {
16428
16561
  if (!validatePath(oldPath, projectPath) || !validatePath(newPath, projectPath)) {
16429
16562
  return c.json({ error: "Path traversal not allowed" }, 403);
16430
16563
  }
16431
- const absOld = path38.resolve(projectPath, oldPath);
16432
- const absNew = path38.resolve(projectPath, newPath);
16433
- await fs31.mkdir(path38.dirname(absNew), { recursive: true });
16434
- 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);
16435
16568
  return c.json({ ok: true, oldPath, newPath });
16436
16569
  } catch (err) {
16437
16570
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16453,11 +16586,11 @@ function createFileRoutes() {
16453
16586
  if (!validatePath(filePath, projectPath)) {
16454
16587
  return c.json({ error: "Path traversal not allowed" }, 403);
16455
16588
  }
16456
- const absolutePath = path38.resolve(projectPath, filePath);
16457
- if (absolutePath === path38.resolve(projectPath)) {
16589
+ const absolutePath = path39.resolve(projectPath, filePath);
16590
+ if (absolutePath === path39.resolve(projectPath)) {
16458
16591
  return c.json({ error: "Cannot delete project root" }, 403);
16459
16592
  }
16460
- await fs31.rm(absolutePath, { recursive: true, force: true });
16593
+ await fs32.rm(absolutePath, { recursive: true, force: true });
16461
16594
  return c.json({ ok: true, path: filePath });
16462
16595
  } catch (err) {
16463
16596
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -16491,16 +16624,16 @@ function createFileRoutes() {
16491
16624
  if (!projectPath) {
16492
16625
  return c.json({ error: "Project not found" }, 404);
16493
16626
  }
16494
- const isTmp = path38.isAbsolute(filePath) && isInTempDir(filePath);
16627
+ const isTmp = path39.isAbsolute(filePath) && isInTempDir(filePath);
16495
16628
  if (!isTmp && !validatePath(filePath, projectPath)) {
16496
16629
  return c.json({ error: "Path traversal not allowed" }, 403);
16497
16630
  }
16498
- const absolutePath = isTmp ? path38.resolve(filePath) : path38.resolve(projectPath, filePath);
16499
- const stat = await fs31.stat(absolutePath);
16631
+ const absolutePath = isTmp ? path39.resolve(filePath) : path39.resolve(projectPath, filePath);
16632
+ const stat = await fs32.stat(absolutePath);
16500
16633
  if (stat.isDirectory()) {
16501
16634
  return c.json({ error: "Cannot download a directory" }, 400);
16502
16635
  }
16503
- const fileName = path38.basename(filePath);
16636
+ const fileName = path39.basename(filePath);
16504
16637
  const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
16505
16638
  const mime = inline ? imageMimeFromExt(ext) ?? MIME_MAP[ext] ?? "application/octet-stream" : "application/octet-stream";
16506
16639
  const disposition = inline ? `inline; filename="${encodeURIComponent(fileName)}"` : `attachment; filename="${encodeURIComponent(fileName)}"`;
@@ -16559,8 +16692,8 @@ function createFileRoutes() {
16559
16692
  return c.json({ error: "Project not found" }, 404);
16560
16693
  if (!validatePath(filePath, projectPath))
16561
16694
  return c.json({ error: "Path traversal not allowed" }, 403);
16562
- const absolutePath = path38.resolve(projectPath, filePath);
16563
- const stat = await fs31.stat(absolutePath);
16695
+ const absolutePath = path39.resolve(projectPath, filePath);
16696
+ const stat = await fs32.stat(absolutePath);
16564
16697
  return c.json({ path: filePath, modifiedAt: stat.mtimeMs, createdAt: stat.birthtimeMs, size: stat.size });
16565
16698
  } catch (err) {
16566
16699
  if (err.code === "ENOENT")
@@ -16582,9 +16715,9 @@ function createFileRoutes() {
16582
16715
  if (!validatePath(filePath, projectPath)) {
16583
16716
  return c.json({ error: "Path traversal not allowed" }, 403);
16584
16717
  }
16585
- const absolutePath = path38.resolve(projectPath, filePath);
16718
+ const absolutePath = path39.resolve(projectPath, filePath);
16586
16719
  try {
16587
- const stat = await fs31.stat(absolutePath);
16720
+ const stat = await fs32.stat(absolutePath);
16588
16721
  if (stat.isDirectory()) {
16589
16722
  return c.json({ error: "Path is a directory, use /files/tree instead" }, 400);
16590
16723
  }
@@ -16592,7 +16725,7 @@ function createFileRoutes() {
16592
16725
  if (stat.size > maxSize) {
16593
16726
  return c.json({ error: "File too large (max 10MB)" }, 413);
16594
16727
  }
16595
- const content = await fs31.readFile(absolutePath, "utf-8");
16728
+ const content = await fs32.readFile(absolutePath, "utf-8");
16596
16729
  return c.json({
16597
16730
  path: filePath,
16598
16731
  content,
@@ -16619,16 +16752,16 @@ function createFileRoutes() {
16619
16752
  const body = await c.req.json();
16620
16753
  if (!body.path)
16621
16754
  return c.json({ error: "path required" }, 400);
16622
- const resolved = path38.resolve(body.path);
16755
+ const resolved = path39.resolve(body.path);
16623
16756
  let stat;
16624
16757
  try {
16625
- stat = await fs31.stat(resolved);
16758
+ stat = await fs32.stat(resolved);
16626
16759
  } catch {
16627
16760
  return c.json({ error: "path not found" }, 404);
16628
16761
  }
16629
16762
  if (!stat.isDirectory())
16630
16763
  return c.json({ error: "not a directory" }, 400);
16631
- if (resolved === path38.parse(resolved).root) {
16764
+ if (resolved === path39.parse(resolved).root) {
16632
16765
  return c.json({ error: "filesystem root cannot be a workspace" }, 400);
16633
16766
  }
16634
16767
  const { ensureWorkspaceHalo: ensureWorkspaceHalo2 } = await Promise.resolve().then(() => (init_init(), init_exports));
@@ -16637,10 +16770,10 @@ function createFileRoutes() {
16637
16770
  });
16638
16771
  app2.get("/fs/exists", async (c) => {
16639
16772
  const target = c.req.query("path");
16640
- if (!target || !path38.isAbsolute(target))
16773
+ if (!target || !path39.isAbsolute(target))
16641
16774
  return c.json({ exists: false, reason: "absolute path required" });
16642
16775
  try {
16643
- const stat = await fs31.stat(target);
16776
+ const stat = await fs32.stat(target);
16644
16777
  return c.json({ exists: true, isDirectory: stat.isDirectory() });
16645
16778
  } catch {
16646
16779
  return c.json({ exists: false });
@@ -16648,13 +16781,13 @@ function createFileRoutes() {
16648
16781
  });
16649
16782
  app2.get("/fs/browse", async (c) => {
16650
16783
  const target = c.req.query("path") ?? homedir17();
16651
- if (!path38.isAbsolute(target))
16784
+ if (!path39.isAbsolute(target))
16652
16785
  return c.json({ error: "absolute path required" }, 400);
16653
16786
  try {
16654
- const resolved = path38.resolve(target);
16655
- const entries = await fs31.readdir(resolved, { withFileTypes: true });
16656
- 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));
16657
- 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 });
16658
16791
  } catch (err) {
16659
16792
  return c.json({ error: err.message }, 404);
16660
16793
  }
@@ -16672,9 +16805,9 @@ var init_files = __esm({
16672
16805
 
16673
16806
  // packages/server/dist/routes/data-preview.js
16674
16807
  import { Hono as Hono2 } from "hono";
16675
- import fs32 from "node:fs/promises";
16808
+ import fs33 from "node:fs/promises";
16676
16809
  import { createReadStream as createReadStream2 } from "node:fs";
16677
- import path39 from "node:path";
16810
+ import path40 from "node:path";
16678
16811
  import Database3 from "better-sqlite3";
16679
16812
  import { asyncBufferFromFile, parquetMetadataAsync, parquetSchema, parquetReadObjects } from "hyparquet";
16680
16813
  import { compressors } from "hyparquet-compressors";
@@ -16705,7 +16838,7 @@ function quoteIdent(name) {
16705
16838
  return `"${name.replaceAll('"', '""')}"`;
16706
16839
  }
16707
16840
  async function sniffDelimiter(absolutePath) {
16708
- const fh = await fs32.open(absolutePath, "r");
16841
+ const fh = await fs33.open(absolutePath, "r");
16709
16842
  let head;
16710
16843
  try {
16711
16844
  const buf = Buffer.alloc(65536);
@@ -16789,9 +16922,9 @@ function createDataPreviewRoutes() {
16789
16922
  return { error: "Project not found", status: 404 };
16790
16923
  if (!validatePath(filePath, projectPath))
16791
16924
  return { error: "Path traversal not allowed", status: 403 };
16792
- const absolutePath = path39.resolve(projectPath, filePath);
16925
+ const absolutePath = path40.resolve(projectPath, filePath);
16793
16926
  try {
16794
- const stat = await fs32.stat(absolutePath);
16927
+ const stat = await fs33.stat(absolutePath);
16795
16928
  if (stat.isDirectory())
16796
16929
  return { error: "Path is a directory", status: 400 };
16797
16930
  } catch {
@@ -16997,15 +17130,15 @@ var init_data_preview = __esm({
16997
17130
  });
16998
17131
 
16999
17132
  // packages/server/dist/git-credentials.js
17000
- import fs33 from "node:fs";
17001
- import path40 from "node:path";
17133
+ import fs34 from "node:fs";
17134
+ import path41 from "node:path";
17002
17135
  import { homedir as homedir18 } from "node:os";
17003
- import { spawnSync } from "node:child_process";
17136
+ import { spawnSync as spawnSync2 } from "node:child_process";
17004
17137
  function writeGitCredentialsFile(host, username, token) {
17005
17138
  const line = `https://${encodeURIComponent(username)}:${encodeURIComponent(token)}@${host}`;
17006
17139
  let lines = [];
17007
- if (fs33.existsSync(GIT_CREDENTIALS_PATH)) {
17008
- 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() !== "");
17009
17142
  }
17010
17143
  const kept = lines.filter((l) => {
17011
17144
  try {
@@ -17015,17 +17148,17 @@ function writeGitCredentialsFile(host, username, token) {
17015
17148
  }
17016
17149
  });
17017
17150
  kept.push(line);
17018
- 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 });
17019
17152
  try {
17020
- fs33.chmodSync(GIT_CREDENTIALS_PATH, 384);
17153
+ fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
17021
17154
  } catch {
17022
17155
  }
17023
17156
  }
17024
17157
  function ensureCredentialHelper() {
17025
- 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" });
17026
17159
  const helper = (current.stdout ?? "").trim();
17027
17160
  if (!helper) {
17028
- spawnSync("git", ["config", "--global", "credential.helper", "store"], { encoding: "utf-8" });
17161
+ spawnSync2("git", ["config", "--global", "credential.helper", "store"], { encoding: "utf-8" });
17029
17162
  }
17030
17163
  }
17031
17164
  function saveGitCredentials({ host, username, token }) {
@@ -17034,9 +17167,9 @@ function saveGitCredentials({ host, username, token }) {
17034
17167
  console.log(`[GitCredentials] saved for host ${host}`);
17035
17168
  }
17036
17169
  function listGitCredentials() {
17037
- if (!fs33.existsSync(GIT_CREDENTIALS_PATH))
17170
+ if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
17038
17171
  return [];
17039
- 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() !== "");
17040
17173
  const byHost = /* @__PURE__ */ new Map();
17041
17174
  for (const line of lines) {
17042
17175
  try {
@@ -17050,9 +17183,9 @@ function listGitCredentials() {
17050
17183
  return [...byHost.values()];
17051
17184
  }
17052
17185
  function deleteGitCredential(host) {
17053
- if (!fs33.existsSync(GIT_CREDENTIALS_PATH))
17186
+ if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
17054
17187
  return;
17055
- 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() !== "");
17056
17189
  const kept = lines.filter((l) => {
17057
17190
  try {
17058
17191
  return new URL(l).host !== host;
@@ -17062,9 +17195,9 @@ function deleteGitCredential(host) {
17062
17195
  });
17063
17196
  if (kept.length === lines.length)
17064
17197
  return;
17065
- 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 });
17066
17199
  try {
17067
- fs33.chmodSync(GIT_CREDENTIALS_PATH, 384);
17200
+ fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
17068
17201
  } catch {
17069
17202
  }
17070
17203
  console.log(`[GitCredentials] removed for host ${host}`);
@@ -17073,15 +17206,15 @@ var GIT_CREDENTIALS_PATH;
17073
17206
  var init_git_credentials = __esm({
17074
17207
  "packages/server/dist/git-credentials.js"() {
17075
17208
  "use strict";
17076
- GIT_CREDENTIALS_PATH = path40.join(homedir18(), ".git-credentials");
17209
+ GIT_CREDENTIALS_PATH = path41.join(homedir18(), ".git-credentials");
17077
17210
  }
17078
17211
  });
17079
17212
 
17080
17213
  // packages/server/dist/git-ssh.js
17081
- import fs34 from "node:fs";
17214
+ import fs35 from "node:fs";
17082
17215
  import os4 from "node:os";
17083
- import path41 from "node:path";
17084
- import { spawnSync as spawnSync2 } from "node:child_process";
17216
+ import path42 from "node:path";
17217
+ import { spawnSync as spawnSync3 } from "node:child_process";
17085
17218
  function looksLikePrivateKey(name, abs) {
17086
17219
  if (name.endsWith(".pub"))
17087
17220
  return false;
@@ -17089,13 +17222,13 @@ function looksLikePrivateKey(name, abs) {
17089
17222
  return false;
17090
17223
  let firstLine = "";
17091
17224
  try {
17092
- const fd = fs34.openSync(abs, "r");
17225
+ const fd = fs35.openSync(abs, "r");
17093
17226
  try {
17094
17227
  const buf = Buffer.alloc(64);
17095
- const n = fs34.readSync(fd, buf, 0, 64, 0);
17228
+ const n = fs35.readSync(fd, buf, 0, 64, 0);
17096
17229
  firstLine = buf.toString("utf-8", 0, n).split("\n")[0];
17097
17230
  } finally {
17098
- fs34.closeSync(fd);
17231
+ fs35.closeSync(fd);
17099
17232
  }
17100
17233
  } catch {
17101
17234
  return false;
@@ -17103,21 +17236,21 @@ function looksLikePrivateKey(name, abs) {
17103
17236
  return firstLine.includes("PRIVATE KEY-----");
17104
17237
  }
17105
17238
  function isKeyEncrypted(abs) {
17106
- 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" });
17107
17240
  return r.status !== 0;
17108
17241
  }
17109
17242
  function listSshKeys() {
17110
17243
  let entries = [];
17111
17244
  try {
17112
- entries = fs34.readdirSync(SSH_DIR);
17245
+ entries = fs35.readdirSync(SSH_DIR);
17113
17246
  } catch {
17114
17247
  return [];
17115
17248
  }
17116
17249
  const keys = [];
17117
17250
  for (const name of entries) {
17118
- const abs = path41.join(SSH_DIR, name);
17251
+ const abs = path42.join(SSH_DIR, name);
17119
17252
  try {
17120
- if (!fs34.statSync(abs).isFile())
17253
+ if (!fs35.statSync(abs).isFile())
17121
17254
  continue;
17122
17255
  } catch {
17123
17256
  continue;
@@ -17131,7 +17264,7 @@ function listSshKeys() {
17131
17264
  function getSshAgentStatus() {
17132
17265
  if (!process.env.SSH_AUTH_SOCK)
17133
17266
  return { agentRunning: false, loadedKeys: [] };
17134
- const r = spawnSync2("ssh-add", ["-l"], { encoding: "utf-8" });
17267
+ const r = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
17135
17268
  if (r.status === 2)
17136
17269
  return { agentRunning: false, loadedKeys: [] };
17137
17270
  const loadedKeys = (r.stdout ?? "").split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => l !== "The agent has no identities.");
@@ -17142,10 +17275,10 @@ function unlockSshKey(keyPath, passphrase) {
17142
17275
  return { ok: false, error: "ssh-agent is not running" };
17143
17276
  let dir = "";
17144
17277
  try {
17145
- dir = fs34.mkdtempSync(path41.join(os4.tmpdir(), "halo-askpass-"));
17146
- const askpass = path41.join(dir, "askpass.sh");
17147
- fs34.writeFileSync(askpass, ASKPASS_SCRIPT, { mode: 448 });
17148
- 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], {
17149
17282
  encoding: "utf-8",
17150
17283
  // stdin from /dev/null so ssh-add can never fall back to a tty prompt and block.
17151
17284
  stdio: ["ignore", "pipe", "pipe"],
@@ -17160,10 +17293,10 @@ function unlockSshKey(keyPath, passphrase) {
17160
17293
  }
17161
17294
  });
17162
17295
  if (r.status === 0) {
17163
- console.log(`[GitSsh] unlocked key ${path41.basename(keyPath)}`);
17296
+ console.log(`[GitSsh] unlocked key ${path42.basename(keyPath)}`);
17164
17297
  return { ok: true };
17165
17298
  }
17166
- 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"})`);
17167
17300
  if (r.status === 2)
17168
17301
  return { ok: false, error: "ssh-agent is not reachable" };
17169
17302
  return { ok: false, error: "Incorrect passphrase, or the key could not be unlocked" };
@@ -17173,19 +17306,19 @@ function unlockSshKey(keyPath, passphrase) {
17173
17306
  return { ok: false, error: "Failed to unlock key" };
17174
17307
  } finally {
17175
17308
  if (dir)
17176
- fs34.rmSync(dir, { recursive: true, force: true });
17309
+ fs35.rmSync(dir, { recursive: true, force: true });
17177
17310
  }
17178
17311
  }
17179
17312
  function ensureSshAgent() {
17180
17313
  try {
17181
17314
  if (process.env.SSH_AUTH_SOCK) {
17182
- const probe = spawnSync2("ssh-add", ["-l"], { encoding: "utf-8" });
17315
+ const probe = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
17183
17316
  if (probe.status !== 2) {
17184
17317
  console.log(`[GitSsh] using existing agent at ${process.env.SSH_AUTH_SOCK}`);
17185
17318
  return;
17186
17319
  }
17187
17320
  }
17188
- const r = spawnSync2("ssh-agent", ["-s"], { encoding: "utf-8" });
17321
+ const r = spawnSync3("ssh-agent", ["-s"], { encoding: "utf-8" });
17189
17322
  if (r.status !== 0) {
17190
17323
  console.log("[GitSsh] ssh-agent unavailable; SSH push/pull will need a manually-started agent");
17191
17324
  return;
@@ -17230,19 +17363,19 @@ function parseRemote(url) {
17230
17363
  }
17231
17364
  }
17232
17365
  function switchRemoteProtocol(projectRoot, to) {
17233
- 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" });
17234
17367
  if (cur.status !== 0)
17235
17368
  throw new Error((cur.stderr ?? "no origin remote").trim());
17236
17369
  const { host, repoPath } = parseRemote((cur.stdout ?? "").trim());
17237
17370
  const next = to === "ssh" ? `git@${host}:${repoPath}` : `https://${host}/${repoPath}`;
17238
- 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" });
17239
17372
  if (set.status !== 0)
17240
17373
  throw new Error((set.stderr ?? "failed to set remote url").trim());
17241
17374
  console.log(`[GitSsh] switched origin to ${to}`);
17242
17375
  return next;
17243
17376
  }
17244
17377
  function getRemoteProtocol(projectRoot) {
17245
- 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" });
17246
17379
  if (cur.status !== 0)
17247
17380
  return { url: "", protocol: "other" };
17248
17381
  const url = (cur.stdout ?? "").trim();
@@ -17256,7 +17389,7 @@ var SSH_DIR, NON_KEY_NAMES, ownedAgentPid, ASKPASS_SCRIPT;
17256
17389
  var init_git_ssh = __esm({
17257
17390
  "packages/server/dist/git-ssh.js"() {
17258
17391
  "use strict";
17259
- SSH_DIR = path41.join(os4.homedir(), ".ssh");
17392
+ SSH_DIR = path42.join(os4.homedir(), ".ssh");
17260
17393
  NON_KEY_NAMES = /* @__PURE__ */ new Set(["known_hosts", "known_hosts2", "authorized_keys", "config"]);
17261
17394
  ownedAgentPid = null;
17262
17395
  ASKPASS_SCRIPT = `#!/bin/sh
@@ -17270,7 +17403,7 @@ printf '%s\\n' "$HALO_SSH_PASSPHRASE"
17270
17403
  // packages/server/dist/routes/git.js
17271
17404
  import { Hono as Hono3 } from "hono";
17272
17405
  import os5 from "node:os";
17273
- import path42 from "node:path";
17406
+ import path43 from "node:path";
17274
17407
  function notifyGitChanged(workspacePath) {
17275
17408
  broadcastToWorkspace(workspacePath, { type: "file:changed", path: ".git", action: "change" });
17276
17409
  }
@@ -17587,9 +17720,9 @@ function createGitRoutes() {
17587
17720
  if (!keyPath || typeof passphrase !== "string") {
17588
17721
  return c.json({ error: "keyPath and passphrase are required" }, 400);
17589
17722
  }
17590
- const sshDir = path42.join(os5.homedir(), ".ssh");
17591
- const resolved = path42.resolve(keyPath);
17592
- 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)) {
17593
17726
  return c.json({ error: "keyPath must be inside ~/.ssh" }, 400);
17594
17727
  }
17595
17728
  return c.json(unlockSshKey(resolved, passphrase));
@@ -17665,8 +17798,8 @@ var init_mtime_cache = __esm({
17665
17798
 
17666
17799
  // packages/server/dist/routes/agent-configs.js
17667
17800
  import { Hono as Hono4 } from "hono";
17668
- import fs35 from "node:fs/promises";
17669
- import path43 from "node:path";
17801
+ import fs36 from "node:fs/promises";
17802
+ import path44 from "node:path";
17670
17803
  import { homedir as homedir19 } from "node:os";
17671
17804
  import YAML10 from "yaml";
17672
17805
  function getAvailableTools() {
@@ -17677,6 +17810,17 @@ function getAvailableTools() {
17677
17810
  _cachedTools = [...tools, draft].map((t2) => ({ name: t2.name, description: t2.description }));
17678
17811
  return _cachedTools;
17679
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
+ }
17680
17824
  function parseAgentYaml(content) {
17681
17825
  try {
17682
17826
  const data = YAML10.parse(content);
@@ -17762,29 +17906,29 @@ function defaultAgentYamlTemplate() {
17762
17906
  }, { lineWidth: 120 });
17763
17907
  }
17764
17908
  async function ensureDir2(dir) {
17765
- await fs35.mkdir(dir, { recursive: true });
17909
+ await fs36.mkdir(dir, { recursive: true });
17766
17910
  }
17767
17911
  async function scanAgentsDir(dir, scope) {
17768
17912
  const agents = [];
17769
17913
  let names;
17770
17914
  try {
17771
- names = await fs35.readdir(dir);
17915
+ names = await fs36.readdir(dir);
17772
17916
  } catch {
17773
17917
  return agents;
17774
17918
  }
17775
17919
  for (const entryName of names) {
17776
- const agentDir = path43.join(dir, entryName);
17920
+ const agentDir = path44.join(dir, entryName);
17777
17921
  try {
17778
- const stat = await fs35.stat(agentDir);
17922
+ const stat = await fs36.stat(agentDir);
17779
17923
  if (!stat.isDirectory())
17780
17924
  continue;
17781
17925
  } catch {
17782
17926
  continue;
17783
17927
  }
17784
- const yamlPath = path43.join(agentDir, "agent.yaml");
17928
+ const yamlPath = path44.join(agentDir, "agent.yaml");
17785
17929
  try {
17786
- const yamlStat = await fs35.stat(yamlPath);
17787
- 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")));
17788
17932
  const { name, description, model, priority, tools, skills, internal } = parsed;
17789
17933
  agents.push({
17790
17934
  id: entryName,
@@ -17805,13 +17949,13 @@ async function scanAgentsDir(dir, scope) {
17805
17949
  return agents;
17806
17950
  }
17807
17951
  async function ensureDefaultAgent(dir) {
17808
- const defaultDir = path43.join(dir, "default");
17809
- const yamlPath = path43.join(defaultDir, "agent.yaml");
17952
+ const defaultDir = path44.join(dir, "default");
17953
+ const yamlPath = path44.join(defaultDir, "agent.yaml");
17810
17954
  try {
17811
- await fs35.access(yamlPath);
17955
+ await fs36.access(yamlPath);
17812
17956
  } catch {
17813
17957
  await ensureDir2(defaultDir);
17814
- await fs35.writeFile(yamlPath, defaultAgentYamlTemplate(), "utf-8");
17958
+ await fs36.writeFile(yamlPath, defaultAgentYamlTemplate(), "utf-8");
17815
17959
  }
17816
17960
  }
17817
17961
  function createAgentConfigRoutes() {
@@ -17829,7 +17973,7 @@ function createAgentConfigRoutes() {
17829
17973
  const projectId = c.req.query("projectId");
17830
17974
  let workspaceAgents = [];
17831
17975
  if (projectId) {
17832
- const wsAgentsDir = path43.join(projectId, ".halo", "agents");
17976
+ const wsAgentsDir = path44.join(projectId, ".halo", "agents");
17833
17977
  workspaceAgents = await scanAgentsDir(wsAgentsDir, "workspace");
17834
17978
  }
17835
17979
  const wsIds = new Set(workspaceAgents.map((a) => a.id));
@@ -17861,33 +18005,33 @@ function createAgentConfigRoutes() {
17861
18005
  if (scope === "workspace") {
17862
18006
  if (!body.projectId)
17863
18007
  return c.json({ error: "projectId required for workspace agents" }, 400);
17864
- baseDir = path43.join(body.projectId, ".halo", "agents");
18008
+ baseDir = path44.join(body.projectId, ".halo", "agents");
17865
18009
  } else {
17866
18010
  baseDir = GLOBAL_AGENTS_DIR4;
17867
18011
  }
17868
- const agentDir = path43.join(baseDir, id);
18012
+ const agentDir = path44.join(baseDir, id);
17869
18013
  try {
17870
- await fs35.access(agentDir);
18014
+ await fs36.access(agentDir);
17871
18015
  return c.json({ error: "Agent already exists" }, 409);
17872
18016
  } catch {
17873
18017
  }
17874
18018
  let conflictScope = null;
17875
18019
  if (scope === "workspace") {
17876
18020
  try {
17877
- await fs35.access(path43.join(GLOBAL_AGENTS_DIR4, id));
18021
+ await fs36.access(path44.join(GLOBAL_AGENTS_DIR4, id));
17878
18022
  conflictScope = "global";
17879
18023
  } catch {
17880
18024
  }
17881
18025
  } else if (body.projectId) {
17882
18026
  try {
17883
- await fs35.access(path43.join(body.projectId, ".halo", "agents", id));
18027
+ await fs36.access(path44.join(body.projectId, ".halo", "agents", id));
17884
18028
  conflictScope = "workspace";
17885
18029
  } catch {
17886
18030
  }
17887
18031
  }
17888
18032
  await ensureDir2(agentDir);
17889
- await fs35.writeFile(path43.join(agentDir, "agent.yaml"), defaultAgentYaml(body.name, body.description ?? ""), "utf-8");
17890
- 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}
17891
18035
 
17892
18036
  ${body.description ?? ""}
17893
18037
  `, "utf-8");
@@ -17901,13 +18045,13 @@ ${body.description ?? ""}
17901
18045
  const projectId = c.req.query("projectId");
17902
18046
  let agentDir;
17903
18047
  if (scope === "workspace" && projectId) {
17904
- agentDir = path43.join(projectId, ".halo", "agents", id);
18048
+ agentDir = path44.join(projectId, ".halo", "agents", id);
17905
18049
  } else {
17906
- agentDir = path43.join(GLOBAL_AGENTS_DIR4, id);
18050
+ agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
17907
18051
  }
17908
- const yamlPath = path43.join(agentDir, "agent.yaml");
18052
+ const yamlPath = path44.join(agentDir, "agent.yaml");
17909
18053
  try {
17910
- const content = await fs35.readFile(yamlPath, "utf-8");
18054
+ const content = await fs36.readFile(yamlPath, "utf-8");
17911
18055
  return c.json({ yaml: content });
17912
18056
  } catch {
17913
18057
  return c.json({ error: "Agent not found" }, 404);
@@ -17924,17 +18068,17 @@ ${body.description ?? ""}
17924
18068
  const scope = body.scope ?? "global";
17925
18069
  let agentDir;
17926
18070
  if (scope === "workspace" && body.projectId) {
17927
- agentDir = path43.join(body.projectId, ".halo", "agents", id);
18071
+ agentDir = path44.join(body.projectId, ".halo", "agents", id);
17928
18072
  } else {
17929
- agentDir = path43.join(GLOBAL_AGENTS_DIR4, id);
18073
+ agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
17930
18074
  }
17931
- const yamlPath = path43.join(agentDir, "agent.yaml");
18075
+ const yamlPath = path44.join(agentDir, "agent.yaml");
17932
18076
  try {
17933
- await fs35.access(agentDir);
18077
+ await fs36.access(agentDir);
17934
18078
  } catch {
17935
18079
  return c.json({ error: "Agent not found" }, 404);
17936
18080
  }
17937
- await fs35.writeFile(yamlPath, body.yaml, "utf-8");
18081
+ await writeFileAtomic2(yamlPath, body.yaml);
17938
18082
  const { name, description, model } = parseAgentYaml(body.yaml);
17939
18083
  return c.json({ agent: { id, name, description, model, path: agentDir, scope } });
17940
18084
  });
@@ -17946,12 +18090,12 @@ ${body.description ?? ""}
17946
18090
  const projectId = c.req.query("projectId");
17947
18091
  let agentDir;
17948
18092
  if (scope === "workspace" && projectId) {
17949
- agentDir = path43.join(projectId, ".halo", "agents", id);
18093
+ agentDir = path44.join(projectId, ".halo", "agents", id);
17950
18094
  } else {
17951
- agentDir = path43.join(GLOBAL_AGENTS_DIR4, id);
18095
+ agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
17952
18096
  }
17953
18097
  try {
17954
- await fs35.access(agentDir);
18098
+ await fs36.access(agentDir);
17955
18099
  } catch {
17956
18100
  return c.json({ error: "Agent not found" }, 404);
17957
18101
  }
@@ -17961,7 +18105,7 @@ ${body.description ?? ""}
17961
18105
  return c.json({ error: "Cannot delete the last global agent. At least one global agent must remain." }, 400);
17962
18106
  }
17963
18107
  }
17964
- await fs35.rm(agentDir, { recursive: true, force: true });
18108
+ await fs36.rm(agentDir, { recursive: true, force: true });
17965
18109
  return c.json({ ok: true });
17966
18110
  });
17967
18111
  app2.patch("/agent-configs/:id/toggle", async (c) => {
@@ -17993,7 +18137,7 @@ ${body.description ?? ""}
17993
18137
  if (!filePath)
17994
18138
  return c.json({ content: "", exists: false, readOnly: !isMdWritable(fileType) });
17995
18139
  try {
17996
- const content = await fs35.readFile(filePath, "utf-8");
18140
+ const content = await fs36.readFile(filePath, "utf-8");
17997
18141
  return c.json({ content, exists: true, path: filePath, readOnly: !isMdWritable(fileType) });
17998
18142
  } catch {
17999
18143
  return c.json({ content: "", exists: false, path: filePath, readOnly: !isMdWritable(fileType) });
@@ -18026,7 +18170,7 @@ ${body.description ?? ""}
18026
18170
  continue;
18027
18171
  }
18028
18172
  try {
18029
- const content = await fs35.readFile(filePath, "utf-8");
18173
+ const content = await fs36.readFile(filePath, "utf-8");
18030
18174
  result[ft] = { content, exists: true, path: filePath, readOnly: !isMdWritable(ft) };
18031
18175
  } catch {
18032
18176
  result[ft] = { content: "", exists: false, path: filePath, readOnly: !isMdWritable(ft) };
@@ -18036,10 +18180,10 @@ ${body.description ?? ""}
18036
18180
  });
18037
18181
  function getSessionsDir(agentId, _source, projectId) {
18038
18182
  if (agentId.startsWith("__") && agentId.endsWith("__")) {
18039
- return path43.join(homedir19(), ".halo", "global", "internal-sessions", agentId);
18183
+ return path44.join(homedir19(), ".halo", "global", "internal-sessions", agentId);
18040
18184
  }
18041
- const base = projectId ? path43.join(projectId, ".halo") : path43.join(homedir19(), ".halo");
18042
- 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);
18043
18187
  }
18044
18188
  app2.get("/agent-configs/:id/sessions", async (c) => {
18045
18189
  const agentId = c.req.param("id");
@@ -18050,7 +18194,7 @@ ${body.description ?? ""}
18050
18194
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18051
18195
  let files;
18052
18196
  try {
18053
- files = await fs35.readdir(dir);
18197
+ files = await fs36.readdir(dir);
18054
18198
  } catch {
18055
18199
  return c.json({ sessions: [] });
18056
18200
  }
@@ -18059,7 +18203,7 @@ ${body.description ?? ""}
18059
18203
  if (!f.endsWith(".json"))
18060
18204
  continue;
18061
18205
  try {
18062
- const raw = await fs35.readFile(path43.join(dir, f), "utf-8");
18206
+ const raw = await fs36.readFile(path44.join(dir, f), "utf-8");
18063
18207
  const session = JSON.parse(raw);
18064
18208
  sessions.push({
18065
18209
  id: session.id,
@@ -18088,9 +18232,9 @@ ${body.description ?? ""}
18088
18232
  const projectId = c.req.query("projectId");
18089
18233
  const source = c.req.query("source") || "test-chat";
18090
18234
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18091
- const filePath = path43.join(dir, `${sessionId}.json`);
18235
+ const filePath = path44.join(dir, `${sessionId}.json`);
18092
18236
  try {
18093
- const raw = await fs35.readFile(filePath, "utf-8");
18237
+ const raw = await fs36.readFile(filePath, "utf-8");
18094
18238
  return c.json({ session: JSON.parse(raw) });
18095
18239
  } catch {
18096
18240
  return c.json({ error: "Session not found" }, 404);
@@ -18124,7 +18268,7 @@ ${body.description ?? ""}
18124
18268
  agentSnapshot: body.agentSnapshot ?? {},
18125
18269
  messages: messages2
18126
18270
  };
18127
- 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");
18128
18272
  return c.json({ id: sessionId, session: { ...session, messages: void 0 } });
18129
18273
  });
18130
18274
  app2.delete("/agent-configs/:id/sessions", async (c) => {
@@ -18136,11 +18280,11 @@ ${body.description ?? ""}
18136
18280
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18137
18281
  let deleted = 0;
18138
18282
  try {
18139
- const files = await fs35.readdir(dir);
18283
+ const files = await fs36.readdir(dir);
18140
18284
  for (const f of files) {
18141
18285
  if (!f.endsWith(".json"))
18142
18286
  continue;
18143
- await fs35.rm(path43.join(dir, f));
18287
+ await fs36.rm(path44.join(dir, f));
18144
18288
  deleted++;
18145
18289
  }
18146
18290
  } catch {
@@ -18157,7 +18301,7 @@ ${body.description ?? ""}
18157
18301
  const source = c.req.query("source") || "test-chat";
18158
18302
  const dir = getSessionsDir(agentId, source, projectId || void 0);
18159
18303
  try {
18160
- await fs35.rm(path43.join(dir, `${sessionId}.json`));
18304
+ await fs36.rm(path44.join(dir, `${sessionId}.json`));
18161
18305
  return c.json({ ok: true });
18162
18306
  } catch {
18163
18307
  return c.json({ error: "Session not found" }, 404);
@@ -18177,7 +18321,7 @@ var init_agent_configs = __esm({
18177
18321
  init_workspace_path();
18178
18322
  init_mtime_cache();
18179
18323
  _cachedTools = null;
18180
- GLOBAL_AGENTS_DIR4 = path43.join(homedir19(), ".halo", "global", "agents");
18324
+ GLOBAL_AGENTS_DIR4 = path44.join(homedir19(), ".halo", "global", "agents");
18181
18325
  FALLBACK_SCAFFOLD_PROVIDER = "aws-bedrock-claude-invoke";
18182
18326
  _yamlCache = createMtimeCache();
18183
18327
  }
@@ -18185,8 +18329,8 @@ var init_agent_configs = __esm({
18185
18329
 
18186
18330
  // packages/server/dist/routes/skills.js
18187
18331
  import { Hono as Hono5 } from "hono";
18188
- import fs36 from "node:fs/promises";
18189
- import path44 from "node:path";
18332
+ import fs37 from "node:fs/promises";
18333
+ import path45 from "node:path";
18190
18334
  import { homedir as homedir20 } from "node:os";
18191
18335
  import YAML11 from "yaml";
18192
18336
  function defaultSkillSettings() {
@@ -18205,20 +18349,20 @@ function defaultSkillSettings() {
18205
18349
  }
18206
18350
  async function readYamlFile3(filePath) {
18207
18351
  try {
18208
- const content = await fs36.readFile(filePath, "utf-8");
18352
+ const content = await fs37.readFile(filePath, "utf-8");
18209
18353
  return YAML11.parse(content) ?? {};
18210
18354
  } catch {
18211
18355
  try {
18212
18356
  const jsonPath = filePath.replace(/\.yaml$/, ".json");
18213
- return JSON.parse(await fs36.readFile(jsonPath, "utf-8"));
18357
+ return JSON.parse(await fs37.readFile(jsonPath, "utf-8"));
18214
18358
  } catch {
18215
18359
  return {};
18216
18360
  }
18217
18361
  }
18218
18362
  }
18219
18363
  async function writeYamlFile(filePath, data) {
18220
- await fs36.mkdir(path44.dirname(filePath), { recursive: true });
18221
- 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");
18222
18366
  }
18223
18367
  async function addSkillSettings(settingsPath, skillId) {
18224
18368
  const settings = await readYamlFile3(settingsPath);
@@ -18241,7 +18385,7 @@ async function removeSkillSettings(settingsPath, skillId) {
18241
18385
  }
18242
18386
  }
18243
18387
  async function ensureDir3(dir) {
18244
- await fs36.mkdir(dir, { recursive: true });
18388
+ await fs37.mkdir(dir, { recursive: true });
18245
18389
  }
18246
18390
  function toSkillMd(name, description) {
18247
18391
  return `---
@@ -18258,32 +18402,32 @@ async function scanSkillsDir(dir, scope) {
18258
18402
  const skills = [];
18259
18403
  let names;
18260
18404
  try {
18261
- names = await fs36.readdir(dir);
18405
+ names = await fs37.readdir(dir);
18262
18406
  } catch {
18263
18407
  return skills;
18264
18408
  }
18265
18409
  for (const entryName of names) {
18266
- const skillDir = path44.join(dir, entryName);
18410
+ const skillDir = path45.join(dir, entryName);
18267
18411
  try {
18268
- const stat = await fs36.stat(skillDir);
18412
+ const stat = await fs37.stat(skillDir);
18269
18413
  if (!stat.isDirectory())
18270
18414
  continue;
18271
18415
  } catch {
18272
18416
  continue;
18273
18417
  }
18274
- const skillMdPath = path44.join(skillDir, "SKILL.md");
18418
+ const skillMdPath = path45.join(skillDir, "SKILL.md");
18275
18419
  try {
18276
- const mdStat = await fs36.stat(skillMdPath);
18420
+ const mdStat = await fs37.stat(skillMdPath);
18277
18421
  let meta = _skillMdCache.get(skillMdPath, mdStat.mtimeMs);
18278
18422
  if (!meta) {
18279
- const { name, description, command } = parseSkillFrontmatter(await fs36.readFile(skillMdPath, "utf-8"));
18423
+ const { name, description, command } = parseSkillFrontmatter(await fs37.readFile(skillMdPath, "utf-8"));
18280
18424
  meta = _skillMdCache.set(skillMdPath, mdStat.mtimeMs, { name: name || entryName, description: description ?? "", command });
18281
18425
  }
18282
18426
  skills.push({ id: entryName, name: meta.name, description: meta.description, path: skillDir, scope, command: meta.command });
18283
18427
  } catch {
18284
- const yamlPath = path44.join(skillDir, "skill.yaml");
18428
+ const yamlPath = path45.join(skillDir, "skill.yaml");
18285
18429
  try {
18286
- const yamlContent = await fs36.readFile(yamlPath, "utf-8");
18430
+ const yamlContent = await fs37.readFile(yamlPath, "utf-8");
18287
18431
  const m = yamlContent.match(/^name:\s*(.+)$/m);
18288
18432
  const d = yamlContent.match(/^description:\s*(.+)$/m);
18289
18433
  skills.push({ id: entryName, name: m?.[1]?.trim() || entryName, description: d?.[1]?.trim() || "", path: skillDir, scope });
@@ -18302,7 +18446,7 @@ function createSkillRoutes() {
18302
18446
  const projectId = c.req.query("projectId");
18303
18447
  let workspaceSkills = [];
18304
18448
  if (projectId) {
18305
- const wsSkillsDir = path44.join(projectId, ".halo", "skills");
18449
+ const wsSkillsDir = path45.join(projectId, ".halo", "skills");
18306
18450
  workspaceSkills = await scanSkillsDir(wsSkillsDir, "workspace");
18307
18451
  }
18308
18452
  const wsIds = new Set(workspaceSkills.map((s) => s.id));
@@ -18325,19 +18469,19 @@ function createSkillRoutes() {
18325
18469
  if (scope === "workspace") {
18326
18470
  if (!body.projectId)
18327
18471
  return c.json({ error: "projectId required for workspace skills" }, 400);
18328
- baseDir = path44.join(body.projectId, ".halo", "skills");
18472
+ baseDir = path45.join(body.projectId, ".halo", "skills");
18329
18473
  } else {
18330
18474
  baseDir = GLOBAL_SKILLS_DIR2;
18331
18475
  }
18332
- const skillDir = path44.join(baseDir, id);
18476
+ const skillDir = path45.join(baseDir, id);
18333
18477
  try {
18334
- await fs36.access(skillDir);
18478
+ await fs37.access(skillDir);
18335
18479
  return c.json({ error: "Skill already exists" }, 409);
18336
18480
  } catch {
18337
18481
  }
18338
18482
  await ensureDir3(skillDir);
18339
- await fs36.writeFile(path44.join(skillDir, "SKILL.md"), toSkillMd(body.name, body.description ?? ""), "utf-8");
18340
- 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;
18341
18485
  await addSkillSettings(settingsPath, id);
18342
18486
  const skill = { id, name: body.name, description: body.description ?? "", path: skillDir, scope };
18343
18487
  return c.json({ skill }, 201);
@@ -18350,17 +18494,17 @@ function createSkillRoutes() {
18350
18494
  const projectId = c.req.query("projectId");
18351
18495
  let skillDir;
18352
18496
  if (scope === "workspace" && projectId) {
18353
- skillDir = path44.join(projectId, ".halo", "skills", id);
18497
+ skillDir = path45.join(projectId, ".halo", "skills", id);
18354
18498
  } else {
18355
- skillDir = path44.join(GLOBAL_SKILLS_DIR2, id);
18499
+ skillDir = path45.join(GLOBAL_SKILLS_DIR2, id);
18356
18500
  }
18357
18501
  try {
18358
- await fs36.access(skillDir);
18502
+ await fs37.access(skillDir);
18359
18503
  } catch {
18360
18504
  return c.json({ error: "Skill not found" }, 404);
18361
18505
  }
18362
- await fs36.rm(skillDir, { recursive: true, force: true });
18363
- 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;
18364
18508
  await removeSkillSettings(settingsPath, id);
18365
18509
  return c.json({ ok: true });
18366
18510
  });
@@ -18384,30 +18528,30 @@ var init_skills = __esm({
18384
18528
  init_db();
18385
18529
  init_workspace_path();
18386
18530
  init_mtime_cache();
18387
- GLOBAL_SETTINGS_PATH = path44.join(homedir20(), ".halo", "secrets", "settings.yaml");
18531
+ GLOBAL_SETTINGS_PATH = path45.join(homedir20(), ".halo", "secrets", "settings.yaml");
18388
18532
  _skillMdCache = createMtimeCache();
18389
18533
  }
18390
18534
  });
18391
18535
 
18392
18536
  // packages/server/dist/routes/settings.js
18393
18537
  import { Hono as Hono6 } from "hono";
18394
- import fs37 from "node:fs/promises";
18395
- import path45 from "node:path";
18538
+ import fs38 from "node:fs/promises";
18539
+ import path46 from "node:path";
18396
18540
  import { homedir as homedir21 } from "node:os";
18397
18541
  import YAML12 from "yaml";
18398
18542
  async function readSettingsFile(filePath) {
18399
18543
  try {
18400
- const content = await fs37.readFile(filePath, "utf-8");
18544
+ const content = await fs38.readFile(filePath, "utf-8");
18401
18545
  return YAML12.parse(content) ?? {};
18402
18546
  } catch {
18403
18547
  return {};
18404
18548
  }
18405
18549
  }
18406
18550
  async function writeSettingsFile(filePath, data) {
18407
- await fs37.mkdir(path45.dirname(filePath), { recursive: true });
18408
- 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 });
18409
18553
  try {
18410
- await fs37.chmod(filePath, 384);
18554
+ await fs38.chmod(filePath, 384);
18411
18555
  } catch {
18412
18556
  }
18413
18557
  }
@@ -18521,8 +18665,8 @@ function detectOrphans(tree, declared) {
18521
18665
  if (!block || typeof block !== "object")
18522
18666
  continue;
18523
18667
  for (const k of Object.keys(block)) {
18524
- const path65 = `${namespaceId}.${kindWord}.${k}`;
18525
- if (!declaredMap.has(path65)) {
18668
+ const path66 = `${namespaceId}.${kindWord}.${k}`;
18669
+ if (!declaredMap.has(path66)) {
18526
18670
  out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
18527
18671
  }
18528
18672
  }
@@ -18537,7 +18681,7 @@ function createSettingsRoutes() {
18537
18681
  const globalTree = await readSettingsFile(GLOBAL_SETTINGS_PATH2);
18538
18682
  let workspaceTree = null;
18539
18683
  if (projectId) {
18540
- workspaceTree = await readSettingsFile(path45.join(projectId, ".halo", "settings.yaml"));
18684
+ workspaceTree = await readSettingsFile(path46.join(projectId, ".halo", "settings.yaml"));
18541
18685
  }
18542
18686
  const declared = loadSettingsSchema(projectId ?? void 0);
18543
18687
  const sections = resolveSections(declared, globalTree, workspaceTree);
@@ -18587,7 +18731,7 @@ function createSettingsRoutes() {
18587
18731
  const offending = rejectGlobalOnlyAtWorkspace("workspace", flattenDotted(body.settings));
18588
18732
  if (offending)
18589
18733
  return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
18590
- const wsPath = path45.join(projectPath, ".halo", "settings.yaml");
18734
+ const wsPath = path46.join(projectPath, ".halo", "settings.yaml");
18591
18735
  await writeSettingsFile(wsPath, body.settings);
18592
18736
  } else {
18593
18737
  await writeSettingsFile(GLOBAL_SETTINGS_PATH2, body.settings);
@@ -18607,7 +18751,7 @@ function createSettingsRoutes() {
18607
18751
  const offending = rejectGlobalOnlyAtWorkspace("workspace", [body.key]);
18608
18752
  if (offending)
18609
18753
  return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
18610
- filePath = path45.join(projectPath, ".halo", "settings.yaml");
18754
+ filePath = path46.join(projectPath, ".halo", "settings.yaml");
18611
18755
  } else {
18612
18756
  filePath = GLOBAL_SETTINGS_PATH2;
18613
18757
  }
@@ -18636,7 +18780,7 @@ function createSettingsRoutes() {
18636
18780
  const projectPath = await resolveProjectPath(body.projectId);
18637
18781
  if (!projectPath)
18638
18782
  return c.json({ error: "Project not found" }, 404);
18639
- filePath = path45.join(projectPath, ".halo", "settings.yaml");
18783
+ filePath = path46.join(projectPath, ".halo", "settings.yaml");
18640
18784
  } else {
18641
18785
  filePath = GLOBAL_SETTINGS_PATH2;
18642
18786
  }
@@ -18662,39 +18806,39 @@ var init_settings = __esm({
18662
18806
  "use strict";
18663
18807
  init_settings_schema();
18664
18808
  init_workspace_path();
18665
- GLOBAL_SETTINGS_PATH2 = path45.join(homedir21(), ".halo", "secrets", "settings.yaml");
18809
+ GLOBAL_SETTINGS_PATH2 = path46.join(homedir21(), ".halo", "secrets", "settings.yaml");
18666
18810
  _changeListeners = [];
18667
18811
  }
18668
18812
  });
18669
18813
 
18670
18814
  // packages/server/dist/evolution/archive.js
18671
- import fs38 from "node:fs";
18672
- import path46 from "node:path";
18815
+ import fs39 from "node:fs";
18816
+ import path47 from "node:path";
18673
18817
  import { pipeline } from "node:stream/promises";
18674
18818
  import JSZip from "jszip";
18675
18819
  import { eq as eq7, and as and5, isNull as isNull4, lte, isNotNull as isNotNull3, lt as lt2 } from "drizzle-orm";
18676
18820
  async function zipDir(srcDir, outZip) {
18677
- if (!fs38.existsSync(srcDir))
18821
+ if (!fs39.existsSync(srcDir))
18678
18822
  return false;
18679
- fs38.mkdirSync(path46.dirname(outZip), { recursive: true });
18823
+ fs39.mkdirSync(path47.dirname(outZip), { recursive: true });
18680
18824
  const zip = new JSZip();
18681
18825
  const walk = (dir, rel) => {
18682
- const entries = fs38.readdirSync(dir, { withFileTypes: true });
18826
+ const entries = fs39.readdirSync(dir, { withFileTypes: true });
18683
18827
  if (entries.length === 0 && rel) {
18684
18828
  zip.folder(rel);
18685
18829
  return;
18686
18830
  }
18687
18831
  for (const e of entries) {
18688
- const abs = path46.join(dir, e.name);
18832
+ const abs = path47.join(dir, e.name);
18689
18833
  const childRel = rel ? `${rel}/${e.name}` : e.name;
18690
18834
  if (e.isDirectory()) {
18691
18835
  walk(abs, childRel);
18692
18836
  } else if (e.isFile()) {
18693
- zip.file(childRel, fs38.readFileSync(abs), { date: fs38.statSync(abs).mtime });
18837
+ zip.file(childRel, fs39.readFileSync(abs), { date: fs39.statSync(abs).mtime });
18694
18838
  } else if (e.isSymbolicLink()) {
18695
18839
  try {
18696
- if (fs38.statSync(abs).isFile())
18697
- zip.file(childRel, fs38.readFileSync(abs));
18840
+ if (fs39.statSync(abs).isFile())
18841
+ zip.file(childRel, fs39.readFileSync(abs));
18698
18842
  } catch {
18699
18843
  }
18700
18844
  }
@@ -18702,7 +18846,7 @@ async function zipDir(srcDir, outZip) {
18702
18846
  };
18703
18847
  try {
18704
18848
  walk(srcDir, "");
18705
- 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));
18706
18850
  return true;
18707
18851
  } catch (err) {
18708
18852
  rmFile(outZip);
@@ -18712,20 +18856,20 @@ async function zipDir(srcDir, outZip) {
18712
18856
  }
18713
18857
  function rmDir(dir) {
18714
18858
  try {
18715
- fs38.rmSync(dir, { recursive: true, force: true });
18859
+ fs39.rmSync(dir, { recursive: true, force: true });
18716
18860
  } catch {
18717
18861
  }
18718
18862
  }
18719
18863
  function rmFile(p) {
18720
18864
  try {
18721
- fs38.rmSync(p, { force: true });
18865
+ fs39.rmSync(p, { force: true });
18722
18866
  } catch {
18723
18867
  }
18724
18868
  }
18725
18869
  async function archiveRun(workspacePath, runId, errors) {
18726
18870
  const runDir = wsEvoRunDir(workspacePath, runId);
18727
18871
  const outZip = wsEvoArchivedRunZip(workspacePath, runId);
18728
- if (!fs38.existsSync(runDir)) {
18872
+ if (!fs39.existsSync(runDir)) {
18729
18873
  return true;
18730
18874
  }
18731
18875
  if (!await zipDir(runDir, outZip)) {
@@ -18738,7 +18882,7 @@ async function archiveRun(workspacePath, runId, errors) {
18738
18882
  async function archiveApply(workspacePath, applyId, errors) {
18739
18883
  const applyDir = wsEvoApplyDir(workspacePath, applyId);
18740
18884
  const outZip = wsEvoArchivedApplyZip(workspacePath, applyId);
18741
- if (!fs38.existsSync(applyDir))
18885
+ if (!fs39.existsSync(applyDir))
18742
18886
  return true;
18743
18887
  if (!await zipDir(applyDir, outZip)) {
18744
18888
  errors.push(`zip failed for apply ${applyId}`);
@@ -18865,8 +19009,8 @@ var init_raw_sqlite = __esm({
18865
19009
  // packages/server/dist/routes/evolution.js
18866
19010
  import { Hono as Hono7 } from "hono";
18867
19011
  import { eq as eq8, desc as desc2, lt as lt3, and as and6, or, sql } from "drizzle-orm";
18868
- import fs39 from "node:fs/promises";
18869
- import path47 from "node:path";
19012
+ import fs40 from "node:fs/promises";
19013
+ import path48 from "node:path";
18870
19014
  function createEvolutionRoutes() {
18871
19015
  const router = new Hono7();
18872
19016
  router.get("/evolution/runs", async (c) => {
@@ -18931,33 +19075,33 @@ function createEvolutionRoutes() {
18931
19075
  const dir = wsEvoRunDir(row.workspacePath, id);
18932
19076
  let patchMd = null;
18933
19077
  try {
18934
- patchMd = await fs39.readFile(path47.join(dir, "patch.md"), "utf-8");
19078
+ patchMd = await fs40.readFile(path48.join(dir, "patch.md"), "utf-8");
18935
19079
  } catch {
18936
19080
  }
18937
19081
  let scoreJson = null;
18938
19082
  try {
18939
- 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");
18940
19084
  scoreJson = JSON.parse(raw);
18941
19085
  } catch {
18942
19086
  }
18943
19087
  let wrapperLog = null;
18944
19088
  try {
18945
- wrapperLog = await fs39.readFile(evoWrapperLogFile(id), "utf-8");
19089
+ wrapperLog = await fs40.readFile(evoWrapperLogFile(id), "utf-8");
18946
19090
  } catch {
18947
19091
  }
18948
19092
  let subCliLog = null;
18949
19093
  try {
18950
- 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");
18951
19095
  } catch {
18952
19096
  }
18953
19097
  let skipReasonMd = null;
18954
19098
  try {
18955
- skipReasonMd = await fs39.readFile(path47.join(dir, ".skip.md"), "utf-8");
19099
+ skipReasonMd = await fs40.readFile(path48.join(dir, ".skip.md"), "utf-8");
18956
19100
  } catch {
18957
19101
  }
18958
19102
  let snapshotSummary = null;
18959
19103
  try {
18960
- 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");
18961
19105
  const parsed = JSON.parse(raw);
18962
19106
  const msgs = parsed.rawMessages ?? [];
18963
19107
  const firstUser = msgs.find((m) => m.role === "user");
@@ -19445,12 +19589,12 @@ var init_session_archive2 = __esm({
19445
19589
  import Database4 from "better-sqlite3";
19446
19590
  import { drizzle as drizzle3 } from "drizzle-orm/better-sqlite3";
19447
19591
  import { sqliteTable as sqliteTable3, text as text3, integer as integer3 } from "drizzle-orm/sqlite-core";
19448
- import path48 from "node:path";
19449
- import fs40 from "node:fs";
19592
+ import path49 from "node:path";
19593
+ import fs41 from "node:fs";
19450
19594
  function createChannelDb(secretsDir) {
19451
- const channelsDir = path48.join(secretsDir, "channels");
19452
- fs40.mkdirSync(channelsDir, { recursive: true });
19453
- 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");
19454
19598
  const sqlite = new Database4(dbPath);
19455
19599
  sqlite.pragma("journal_mode = WAL");
19456
19600
  sqlite.exec(CREATE_SQL2);
@@ -19500,11 +19644,11 @@ CREATE INDEX IF NOT EXISTS idx_channel_accounts_type ON channel_accounts(channel
19500
19644
  });
19501
19645
 
19502
19646
  // packages/server/dist/channels/shared/accounts.js
19503
- import fs41 from "node:fs";
19504
- import path49 from "node:path";
19647
+ import fs42 from "node:fs";
19648
+ import path50 from "node:path";
19505
19649
  import { eq as eq10, and as and7 } from "drizzle-orm";
19506
19650
  function resolveAccountWorkspace(account) {
19507
- if (!fs41.existsSync(account.workspacePath))
19651
+ if (!fs42.existsSync(account.workspacePath))
19508
19652
  return null;
19509
19653
  ensureWorkspaceHalo(account.workspacePath);
19510
19654
  return account.workspacePath;
@@ -19517,9 +19661,9 @@ function accessLevelError(value, allowed) {
19517
19661
  return `accessLevel must be one of: ${allowed.join(", ")}`;
19518
19662
  }
19519
19663
  function validateWorkspaceBody(workspacePath) {
19520
- if (!path49.isAbsolute(workspacePath))
19664
+ if (!path50.isAbsolute(workspacePath))
19521
19665
  return "workspacePath must be absolute";
19522
- if (!fs41.existsSync(workspacePath))
19666
+ if (!fs42.existsSync(workspacePath))
19523
19667
  return "workspace path not found";
19524
19668
  ensureWorkspaceHalo(workspacePath);
19525
19669
  return null;
@@ -19819,22 +19963,22 @@ var init_session_types = __esm({
19819
19963
 
19820
19964
  // packages/server/dist/routes/halo-city.js
19821
19965
  import { Hono as Hono10 } from "hono";
19822
- import fs42 from "node:fs";
19823
- import path50 from "node:path";
19966
+ import fs43 from "node:fs";
19967
+ import path51 from "node:path";
19824
19968
  import Database5 from "better-sqlite3";
19825
19969
  function scanSkills2(dir) {
19826
19970
  let names;
19827
19971
  try {
19828
- names = fs42.readdirSync(dir);
19972
+ names = fs43.readdirSync(dir);
19829
19973
  } catch {
19830
19974
  return [];
19831
19975
  }
19832
19976
  const out = [];
19833
19977
  for (const entry of names) {
19834
- const skillMd = path50.join(dir, entry, "SKILL.md");
19978
+ const skillMd = path51.join(dir, entry, "SKILL.md");
19835
19979
  let stat;
19836
19980
  try {
19837
- stat = fs42.statSync(skillMd);
19981
+ stat = fs43.statSync(skillMd);
19838
19982
  if (!stat.isFile())
19839
19983
  continue;
19840
19984
  } catch {
@@ -19843,7 +19987,7 @@ function scanSkills2(dir) {
19843
19987
  let meta = _skillCache.get(skillMd, stat.mtimeMs);
19844
19988
  if (!meta) {
19845
19989
  try {
19846
- const { name, description, command } = parseSkillFrontmatter(fs42.readFileSync(skillMd, "utf-8"));
19990
+ const { name, description, command } = parseSkillFrontmatter(fs43.readFileSync(skillMd, "utf-8"));
19847
19991
  meta = _skillCache.set(skillMd, stat.mtimeMs, { id: entry, name: name || entry, description: description ?? "", command });
19848
19992
  } catch {
19849
19993
  continue;
@@ -19929,10 +20073,10 @@ function snapshotWorkspace(sm, wsPath, label) {
19929
20073
  messageCount: meta.messageCount
19930
20074
  };
19931
20075
  });
19932
- const wsSkills = scanSkills2(path50.join(wsPath, ".halo", "skills"));
20076
+ const wsSkills = scanSkills2(path51.join(wsPath, ".halo", "skills"));
19933
20077
  return {
19934
20078
  path: wsPath,
19935
- key: path50.basename(wsPath) || wsPath,
20079
+ key: path51.basename(wsPath) || wsPath,
19936
20080
  label,
19937
20081
  sessions,
19938
20082
  counts,
@@ -19943,15 +20087,15 @@ function snapshotWorkspace(sm, wsPath, label) {
19943
20087
  function roReader(rawPath) {
19944
20088
  let wsPath;
19945
20089
  try {
19946
- wsPath = fs42.realpathSync(rawPath);
20090
+ wsPath = fs43.realpathSync(rawPath);
19947
20091
  } catch {
19948
20092
  return null;
19949
20093
  }
19950
20094
  const cached = _roReaders.get(wsPath);
19951
20095
  if (cached)
19952
20096
  return cached;
19953
- const dbPath = path50.join(wsPath, ".halo", "halo.db");
19954
- if (!fs42.existsSync(dbPath))
20097
+ const dbPath = path51.join(wsPath, ".halo", "halo.db");
20098
+ if (!fs43.existsSync(dbPath))
19955
20099
  return null;
19956
20100
  try {
19957
20101
  const db = new Database5(dbPath, { readonly: true, fileMustExist: true });
@@ -19979,7 +20123,7 @@ function dropRoReader(rawPath) {
19979
20123
  return;
19980
20124
  let wsPath = rawPath;
19981
20125
  try {
19982
- wsPath = fs42.realpathSync(rawPath);
20126
+ wsPath = fs43.realpathSync(rawPath);
19983
20127
  } catch {
19984
20128
  }
19985
20129
  const r = _roReaders.get(wsPath);
@@ -20027,12 +20171,12 @@ function snapshotWorkspaceReadonly(wsPath, label) {
20027
20171
  });
20028
20172
  return {
20029
20173
  path: wsPath,
20030
- key: path50.basename(wsPath) || wsPath,
20174
+ key: path51.basename(wsPath) || wsPath,
20031
20175
  label,
20032
20176
  sessions,
20033
20177
  counts,
20034
20178
  totalSessions: counts.total,
20035
- skills: scanSkills2(path50.join(wsPath, ".halo", "skills"))
20179
+ skills: scanSkills2(path51.join(wsPath, ".halo", "skills"))
20036
20180
  };
20037
20181
  }
20038
20182
  function readonlySessionCounts(wsPath, limit) {
@@ -20076,7 +20220,7 @@ function workspaceBirth(wsPath) {
20076
20220
  let b = _birthCache.get(wsPath);
20077
20221
  if (b === void 0) {
20078
20222
  try {
20079
- b = fs42.statSync(wsPath).birthtimeMs || fs42.statSync(wsPath).ctimeMs;
20223
+ b = fs43.statSync(wsPath).birthtimeMs || fs43.statSync(wsPath).ctimeMs;
20080
20224
  } catch {
20081
20225
  b = 0;
20082
20226
  }
@@ -20088,18 +20232,18 @@ function discoverWorkspaces(registry2) {
20088
20232
  const out = /* @__PURE__ */ new Map();
20089
20233
  const add = (p, label) => {
20090
20234
  try {
20091
- const resolved = fs42.realpathSync(p);
20235
+ const resolved = fs43.realpathSync(p);
20092
20236
  if (!out.has(resolved))
20093
20237
  out.set(resolved, label);
20094
20238
  } catch {
20095
20239
  }
20096
20240
  };
20097
20241
  for (const { workspacePath } of registry2.list())
20098
- add(workspacePath, path50.basename(workspacePath));
20242
+ add(workspacePath, path51.basename(workspacePath));
20099
20243
  try {
20100
20244
  const rows = getChannelDb().select().from(channelAccounts).all();
20101
20245
  for (const row of rows)
20102
- add(row.workspacePath, path50.basename(row.workspacePath));
20246
+ add(row.workspacePath, path51.basename(row.workspacePath));
20103
20247
  } catch {
20104
20248
  }
20105
20249
  return out;
@@ -20118,7 +20262,7 @@ function createShowRoutes(registry2) {
20118
20262
  return a.response;
20119
20263
  const { account } = a;
20120
20264
  const globalView = account.accessLevel === "full" || account.accessLevel === "observer";
20121
- 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)]]);
20122
20266
  const workspaces = [];
20123
20267
  for (const [wsPath, label] of targets) {
20124
20268
  try {
@@ -20152,7 +20296,7 @@ function createShowRoutes(registry2) {
20152
20296
  if (!globalView) {
20153
20297
  let allowed = false;
20154
20298
  try {
20155
- allowed = fs42.realpathSync(wsPath) === fs42.realpathSync(account.workspacePath);
20299
+ allowed = fs43.realpathSync(wsPath) === fs43.realpathSync(account.workspacePath);
20156
20300
  } catch {
20157
20301
  }
20158
20302
  if (!allowed)
@@ -20346,14 +20490,14 @@ var init_commands3 = __esm({
20346
20490
 
20347
20491
  // packages/server/dist/routes/agentcore.js
20348
20492
  import { Hono as Hono13 } from "hono";
20349
- import path51 from "node:path";
20350
- import fs43 from "node:fs";
20493
+ import path52 from "node:path";
20494
+ import fs44 from "node:fs";
20351
20495
  function haloSessionId(runtimeSessionId) {
20352
20496
  return `agentcore_${runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80)}`;
20353
20497
  }
20354
20498
  function userWorkspace(base, runtimeSessionId) {
20355
- const dir = path51.join(base, "users", runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80));
20356
- fs43.mkdirSync(dir, { recursive: true });
20499
+ const dir = path52.join(base, "users", runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80));
20500
+ fs44.mkdirSync(dir, { recursive: true });
20357
20501
  return dir;
20358
20502
  }
20359
20503
  async function ensureSession(sm, workspace, sessionId) {
@@ -20610,8 +20754,8 @@ var init_agentcore = __esm({
20610
20754
  });
20611
20755
 
20612
20756
  // packages/server/dist/ws/file-watcher.js
20613
- import fs44 from "node:fs";
20614
- import path52 from "node:path";
20757
+ import fs45 from "node:fs";
20758
+ import path53 from "node:path";
20615
20759
  function enqueueNativeOp(op) {
20616
20760
  const result = nativeOpChain.then(op);
20617
20761
  nativeOpChain = result.catch(() => {
@@ -20619,12 +20763,12 @@ function enqueueNativeOp(op) {
20619
20763
  return result;
20620
20764
  }
20621
20765
  function isUnwatchablePath(absPath) {
20622
- const norm = path52.resolve(absPath);
20623
- if (norm === "/" || norm === path52.parse(norm).root)
20766
+ const norm = path53.resolve(absPath);
20767
+ if (norm === "/" || norm === path53.parse(norm).root)
20624
20768
  return "filesystem root";
20625
20769
  const banned = ["/System", "/Library", "/private", "/usr", "/bin", "/sbin", "/etc"];
20626
20770
  for (const b of banned) {
20627
- if (norm === b || norm.startsWith(b + path52.sep))
20771
+ if (norm === b || norm.startsWith(b + path53.sep))
20628
20772
  return `system path ${b}`;
20629
20773
  }
20630
20774
  return null;
@@ -20720,7 +20864,7 @@ var init_file_watcher = __esm({
20720
20864
  }
20721
20865
  let root = workspaceRoot;
20722
20866
  try {
20723
- root = fs44.realpathSync(workspaceRoot);
20867
+ root = fs45.realpathSync(workspaceRoot);
20724
20868
  } catch {
20725
20869
  }
20726
20870
  if (this.subscription && this.workspaceRoot === root)
@@ -20764,10 +20908,10 @@ var init_file_watcher = __esm({
20764
20908
  const root = this.workspaceRoot;
20765
20909
  if (!root)
20766
20910
  return;
20767
- const raw = path52.relative(root, absPath);
20911
+ const raw = path53.relative(root, absPath);
20768
20912
  if (!raw || raw.startsWith(".."))
20769
20913
  return;
20770
- const rel = raw.split(path52.sep).join("/");
20914
+ const rel = raw.split(path53.sep).join("/");
20771
20915
  if (IS_WIN32 && rel.split("/").some((seg) => IGNORED_SEGMENT_SET.has(seg)))
20772
20916
  return;
20773
20917
  let action;
@@ -20778,7 +20922,7 @@ var init_file_watcher = __esm({
20778
20922
  } else {
20779
20923
  let isDir = false;
20780
20924
  try {
20781
- isDir = fs44.statSync(absPath).isDirectory();
20925
+ isDir = fs45.statSync(absPath).isDirectory();
20782
20926
  } catch {
20783
20927
  }
20784
20928
  action = isDir ? "addDir" : "add";
@@ -20854,8 +20998,8 @@ var init_file_watcher = __esm({
20854
20998
  });
20855
20999
 
20856
21000
  // packages/server/dist/ws/git-dir-watcher.js
20857
- import fs45 from "node:fs";
20858
- import path53 from "node:path";
21001
+ import fs46 from "node:fs";
21002
+ import path54 from "node:path";
20859
21003
  var GitDirWatcher;
20860
21004
  var init_git_dir_watcher = __esm({
20861
21005
  "packages/server/dist/ws/git-dir-watcher.js"() {
@@ -20879,8 +21023,8 @@ var init_git_dir_watcher = __esm({
20879
21023
  return;
20880
21024
  this.stop();
20881
21025
  this.workspaceRoot = workspaceRoot;
20882
- const gitDir = path53.join(workspaceRoot, ".git");
20883
- if (fs45.existsSync(gitDir)) {
21026
+ const gitDir = path54.join(workspaceRoot, ".git");
21027
+ if (fs46.existsSync(gitDir)) {
20884
21028
  this.watchGitDir(gitDir);
20885
21029
  } else {
20886
21030
  this.watchParentForGit(workspaceRoot);
@@ -20889,7 +21033,7 @@ var init_git_dir_watcher = __esm({
20889
21033
  /** Watch the `.git` dir for index/HEAD changes (steady state). */
20890
21034
  watchGitDir(gitDir) {
20891
21035
  try {
20892
- this.watcher = fs45.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
21036
+ this.watcher = fs46.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
20893
21037
  this.handleGitEvent(filename);
20894
21038
  });
20895
21039
  this.watcher.on("error", (err) => {
@@ -20908,7 +21052,7 @@ var init_git_dir_watcher = __esm({
20908
21052
  * Exists solely during the non-repo window; upgraded away on first `.git`. */
20909
21053
  watchParentForGit(workspaceRoot) {
20910
21054
  try {
20911
- this.watcher = fs45.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
21055
+ this.watcher = fs46.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
20912
21056
  this.handleParentEvent(filename);
20913
21057
  });
20914
21058
  this.watcher.on("error", (err) => {
@@ -20944,8 +21088,8 @@ var init_git_dir_watcher = __esm({
20944
21088
  const root = this.workspaceRoot;
20945
21089
  if (!root)
20946
21090
  return;
20947
- const gitDir = path53.join(root, ".git");
20948
- if (!fs45.existsSync(gitDir))
21091
+ const gitDir = path54.join(root, ".git");
21092
+ if (!fs46.existsSync(gitDir))
20949
21093
  return;
20950
21094
  this.closeWatcher();
20951
21095
  this.watchGitDir(gitDir);
@@ -21261,7 +21405,7 @@ var init_terminal_manager = __esm({
21261
21405
  });
21262
21406
 
21263
21407
  // packages/server/dist/ws/handler.js
21264
- import path54 from "node:path";
21408
+ import path55 from "node:path";
21265
21409
  function setupWebSocketHandler(deps) {
21266
21410
  const { wss: wss2, registry: registry2 } = deps;
21267
21411
  const clients = /* @__PURE__ */ new Set();
@@ -21283,7 +21427,7 @@ function setupWebSocketHandler(deps) {
21283
21427
  ackedChatIds.add(id);
21284
21428
  }
21285
21429
  function resolveProjectPath2(projectId) {
21286
- if (path54.isAbsolute(projectId))
21430
+ if (path55.isAbsolute(projectId))
21287
21431
  return projectId;
21288
21432
  return null;
21289
21433
  }
@@ -21909,11 +22053,11 @@ var init_handler = __esm({
21909
22053
  import Database6 from "better-sqlite3";
21910
22054
  import { drizzle as drizzle4 } from "drizzle-orm/better-sqlite3";
21911
22055
  import { sqliteTable as sqliteTable4, text as text4, integer as integer4 } from "drizzle-orm/sqlite-core";
21912
- import path55 from "node:path";
21913
- import fs46 from "node:fs";
22056
+ import path56 from "node:path";
22057
+ import fs47 from "node:fs";
21914
22058
  function createCronDb(globalDir2) {
21915
- fs46.mkdirSync(globalDir2, { recursive: true });
21916
- const dbPath = path55.join(globalDir2, "cron.db");
22059
+ fs47.mkdirSync(globalDir2, { recursive: true });
22060
+ const dbPath = path56.join(globalDir2, "cron.db");
21917
22061
  const sqlite = new Database6(dbPath);
21918
22062
  sqlite.pragma("journal_mode = WAL");
21919
22063
  sqlite.exec(CREATE_SQL3);
@@ -22111,8 +22255,8 @@ var init_child_env = __esm({
22111
22255
 
22112
22256
  // packages/server/dist/cron/runner.js
22113
22257
  import { spawn as spawn3, execFileSync } from "node:child_process";
22114
- import fs47 from "node:fs";
22115
- import path56 from "node:path";
22258
+ import fs48 from "node:fs";
22259
+ import path57 from "node:path";
22116
22260
  import { homedir as homedir23 } from "node:os";
22117
22261
  import { Cron } from "croner";
22118
22262
  import { eq as eq11, lt as lt4 } from "drizzle-orm";
@@ -22183,10 +22327,10 @@ function isCronCliProcess(pid, jobId) {
22183
22327
  }
22184
22328
  }
22185
22329
  function logsDir() {
22186
- return path56.join(homedir23(), ".halo", "global", "logs", "cron");
22330
+ return path57.join(homedir23(), ".halo", "global", "logs", "cron");
22187
22331
  }
22188
22332
  function logPathFor(runId) {
22189
- return path56.join(logsDir(), `${runId}.log`);
22333
+ return path57.join(logsDir(), `${runId}.log`);
22190
22334
  }
22191
22335
  function newRunId() {
22192
22336
  return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
@@ -22237,7 +22381,7 @@ function sweepOrphanRuns(opts) {
22237
22381
  }
22238
22382
  }
22239
22383
  function startCronDaemon() {
22240
- fs47.mkdirSync(logsDir(), { recursive: true });
22384
+ fs48.mkdirSync(logsDir(), { recursive: true });
22241
22385
  sweepOrphanRuns();
22242
22386
  reloadAll();
22243
22387
  _daemonTimers.push(setInterval(pruneOldLogs, DAY_MS2));
@@ -22394,7 +22538,7 @@ async function runJob(jobId, triggerKind) {
22394
22538
  const runId = newRunId();
22395
22539
  const startedAt = Date.now();
22396
22540
  const logPath = logPathFor(runId);
22397
- fs47.mkdirSync(logsDir(), { recursive: true });
22541
+ fs48.mkdirSync(logsDir(), { recursive: true });
22398
22542
  if (_inflight.has(jobId)) {
22399
22543
  db.insert(cronRuns).values({
22400
22544
  id: runId,
@@ -22429,7 +22573,7 @@ async function runJob(jobId, triggerKind) {
22429
22573
  }).run();
22430
22574
  broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
22431
22575
  const targets = parseTargets(job.targets);
22432
- if (!fs47.existsSync(job.workspacePath)) {
22576
+ if (!fs48.existsSync(job.workspacePath)) {
22433
22577
  finalize(runId, jobId, {
22434
22578
  status: "failed",
22435
22579
  output: null,
@@ -22443,7 +22587,7 @@ async function runJob(jobId, triggerKind) {
22443
22587
  const sessionId = `cron-${job.id}`;
22444
22588
  const args = ["cli", "-a", job.agentId, "-s", sessionId, "-w", job.workspacePath];
22445
22589
  const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
22446
- const teeStream = fs47.createWriteStream(logPath, { flags: "a" });
22590
+ const teeStream = fs48.createWriteStream(logPath, { flags: "a" });
22447
22591
  teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
22448
22592
  `);
22449
22593
  teeStream.write(`agent=${job.agentId} workspace=${job.workspacePath} schedule=${job.schedule}
@@ -22615,15 +22759,15 @@ function pruneOldRuns(jobId) {
22615
22759
  }
22616
22760
  function pruneOldLogs() {
22617
22761
  const dir = logsDir();
22618
- if (!fs47.existsSync(dir))
22762
+ if (!fs48.existsSync(dir))
22619
22763
  return;
22620
22764
  const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
22621
- for (const name of fs47.readdirSync(dir)) {
22622
- const p = path56.join(dir, name);
22765
+ for (const name of fs48.readdirSync(dir)) {
22766
+ const p = path57.join(dir, name);
22623
22767
  try {
22624
- const stat = fs47.statSync(p);
22768
+ const stat = fs48.statSync(p);
22625
22769
  if (stat.mtimeMs < cutoff)
22626
- fs47.rmSync(p, { force: true });
22770
+ fs48.rmSync(p, { force: true });
22627
22771
  } catch {
22628
22772
  }
22629
22773
  }
@@ -22656,7 +22800,7 @@ var init_runner = __esm({
22656
22800
  // packages/server/dist/routes/cron.js
22657
22801
  import { Hono as Hono14 } from "hono";
22658
22802
  import { eq as eq12, desc as desc3, lt as lt5, and as and8 } from "drizzle-orm";
22659
- import fs48 from "node:fs/promises";
22803
+ import fs49 from "node:fs/promises";
22660
22804
  import { Cron as Cron2 } from "croner";
22661
22805
  function newJobId() {
22662
22806
  return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -22852,7 +22996,7 @@ function createCronRoutes() {
22852
22996
  return c.json({ log: null });
22853
22997
  let log2 = null;
22854
22998
  try {
22855
- log2 = await fs48.readFile(row.logPath, "utf-8");
22999
+ log2 = await fs49.readFile(row.logPath, "utf-8");
22856
23000
  } catch {
22857
23001
  }
22858
23002
  return c.json({ log: log2 });
@@ -23154,7 +23298,7 @@ var init_ticker = __esm({
23154
23298
  });
23155
23299
 
23156
23300
  // packages/server/dist/evolution/spawn.js
23157
- import path57 from "node:path";
23301
+ import path58 from "node:path";
23158
23302
  import { spawn as spawn4 } from "node:child_process";
23159
23303
  import { fileURLToPath as fileURLToPath2 } from "node:url";
23160
23304
  var __dirname2, WRAPPER_PATH, realEvoSpawner;
@@ -23162,8 +23306,8 @@ var init_spawn = __esm({
23162
23306
  "packages/server/dist/evolution/spawn.js"() {
23163
23307
  "use strict";
23164
23308
  init_child_env();
23165
- __dirname2 = path57.dirname(fileURLToPath2(import.meta.url));
23166
- WRAPPER_PATH = path57.resolve(__dirname2, "evo-wrapper.js");
23309
+ __dirname2 = path58.dirname(fileURLToPath2(import.meta.url));
23310
+ WRAPPER_PATH = path58.resolve(__dirname2, "evo-wrapper.js");
23167
23311
  realEvoSpawner = (mode, id) => {
23168
23312
  const child = spawn4(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
23169
23313
  detached: true,
@@ -23950,7 +24094,7 @@ var init_handler2 = __esm({
23950
24094
  // packages/server/dist/routes/telegram.js
23951
24095
  import { Hono as Hono15 } from "hono";
23952
24096
  import { Bot as Bot2 } from "grammy";
23953
- import fs49 from "node:fs";
24097
+ import fs50 from "node:fs";
23954
24098
  function createTelegramRoutes(deps) {
23955
24099
  const { db, channel } = deps;
23956
24100
  const app2 = new Hono15();
@@ -23960,7 +24104,7 @@ function createTelegramRoutes(deps) {
23960
24104
  accountId: acc.accountId,
23961
24105
  botUsername: acc.botUsername,
23962
24106
  workspacePath: acc.workspacePath,
23963
- workspaceMissing: !fs49.existsSync(acc.workspacePath),
24107
+ workspaceMissing: !fs50.existsSync(acc.workspacePath),
23964
24108
  label: acc.label,
23965
24109
  enabled: acc.enabled,
23966
24110
  accessLevel: acc.accessLevel,
@@ -24684,8 +24828,8 @@ var init_cdn = __esm({
24684
24828
  });
24685
24829
 
24686
24830
  // packages/server/dist/channels/wechat/send-media.js
24687
- import fs50 from "node:fs/promises";
24688
- import path58 from "node:path";
24831
+ import fs51 from "node:fs/promises";
24832
+ import path59 from "node:path";
24689
24833
  function inferKind(filePath) {
24690
24834
  const cls = classifyMedia(filePath);
24691
24835
  if (cls === "image")
@@ -24697,7 +24841,7 @@ function inferKind(filePath) {
24697
24841
  async function sendMediaFile(params) {
24698
24842
  const { baseUrl, token, toUserId, contextToken, filePath } = params;
24699
24843
  const kind = params.kind ?? inferKind(filePath);
24700
- const plaintext = await fs50.readFile(filePath);
24844
+ const plaintext = await fs51.readFile(filePath);
24701
24845
  const prep = prepareUpload(plaintext);
24702
24846
  console.log(`[wechat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
24703
24847
  const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
@@ -24735,7 +24879,7 @@ async function sendMediaFile(params) {
24735
24879
  aesKeyBase64,
24736
24880
  fileSizeCiphertext: prep.filesize,
24737
24881
  fileSize: prep.rawsize,
24738
- fileName: path58.basename(filePath)
24882
+ fileName: path59.basename(filePath)
24739
24883
  });
24740
24884
  const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
24741
24885
  const body = {
@@ -24898,8 +25042,8 @@ var init_login = __esm({
24898
25042
  });
24899
25043
 
24900
25044
  // packages/server/dist/channels/wechat/handler.js
24901
- import fs51 from "node:fs";
24902
- import path59 from "node:path";
25045
+ import fs52 from "node:fs";
25046
+ import path60 from "node:path";
24903
25047
  import QRCode from "qrcode";
24904
25048
  function wxRoute(fromUserId, contextToken) {
24905
25049
  return (prev) => ({
@@ -25288,7 +25432,7 @@ async function handleSlashCommand(args) {
25288
25432
  await reply(login.message);
25289
25433
  return true;
25290
25434
  }
25291
- const tmpPath = path59.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
25435
+ const tmpPath = path60.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
25292
25436
  await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
25293
25437
  await sendMediaFile({
25294
25438
  baseUrl: account.baseUrl,
@@ -25297,7 +25441,7 @@ async function handleSlashCommand(args) {
25297
25441
  contextToken,
25298
25442
  filePath: tmpPath
25299
25443
  });
25300
- await fs51.promises.unlink(tmpPath).catch(() => {
25444
+ await fs52.promises.unlink(tmpPath).catch(() => {
25301
25445
  });
25302
25446
  await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
25303
25447
  waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
@@ -25377,8 +25521,8 @@ var init_handler3 = __esm({
25377
25521
 
25378
25522
  // packages/server/dist/routes/wechat.js
25379
25523
  import { Hono as Hono16 } from "hono";
25380
- import path60 from "node:path";
25381
- import fs52 from "node:fs";
25524
+ import path61 from "node:path";
25525
+ import fs53 from "node:fs";
25382
25526
  function createWechatRoutes(deps) {
25383
25527
  const { db, channel } = deps;
25384
25528
  const app2 = new Hono16();
@@ -25387,7 +25531,7 @@ function createWechatRoutes(deps) {
25387
25531
  return {
25388
25532
  ...stripSecrets(acc),
25389
25533
  workspacePath: acc.workspacePath,
25390
- workspaceMissing: !fs52.existsSync(acc.workspacePath)
25534
+ workspaceMissing: !fs53.existsSync(acc.workspacePath)
25391
25535
  };
25392
25536
  });
25393
25537
  return c.json({ accounts });
@@ -25403,7 +25547,7 @@ function createWechatRoutes(deps) {
25403
25547
  return c.json({ error: "sessionKey required" }, 400);
25404
25548
  if (!body.workspacePath)
25405
25549
  return c.json({ error: "workspacePath required" }, 400);
25406
- if (!path60.isAbsolute(body.workspacePath))
25550
+ if (!path61.isAbsolute(body.workspacePath))
25407
25551
  return c.json({ error: "workspacePath must be absolute" }, 400);
25408
25552
  const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
25409
25553
  if (levelError)
@@ -25607,14 +25751,14 @@ function createWebChannel(deps) {
25607
25751
  return findActiveSessionId(sm, accountId, prefix, activeOverrides2, "full") ?? void 0;
25608
25752
  }
25609
25753
  function resolveWorkspace(account, override) {
25610
- let path65 = account.workspacePath;
25754
+ let path66 = account.workspacePath;
25611
25755
  if (override && override !== account.workspacePath) {
25612
25756
  if (account.accessLevel !== "full") {
25613
25757
  return { ok: false, error: "workspace override requires a full-access token" };
25614
25758
  }
25615
- path65 = override;
25759
+ path66 = override;
25616
25760
  }
25617
- const resolved = resolveAccountWorkspace({ ...account, workspacePath: path65 });
25761
+ const resolved = resolveAccountWorkspace({ ...account, workspacePath: path66 });
25618
25762
  if (!resolved)
25619
25763
  return { ok: false, error: "workspace not found" };
25620
25764
  return { ok: true, path: resolved };
@@ -25997,7 +26141,7 @@ var init_handler4 = __esm({
25997
26141
  import { Hono as Hono17 } from "hono";
25998
26142
  import { streamSSE } from "hono/streaming";
25999
26143
  import crypto4 from "node:crypto";
26000
- import fs53 from "node:fs";
26144
+ import fs54 from "node:fs";
26001
26145
  function createWebRoutes(deps) {
26002
26146
  const { db, channel } = deps;
26003
26147
  const app2 = new Hono17();
@@ -26007,7 +26151,7 @@ function createWebRoutes(deps) {
26007
26151
  accountId: acc.accountId,
26008
26152
  token: acc.token,
26009
26153
  workspacePath: acc.workspacePath,
26010
- workspaceMissing: !fs53.existsSync(acc.workspacePath),
26154
+ workspaceMissing: !fs54.existsSync(acc.workspacePath),
26011
26155
  label: acc.label,
26012
26156
  enabled: acc.enabled,
26013
26157
  accessLevel: acc.accessLevel,
@@ -26153,27 +26297,27 @@ function createWebRoutes(deps) {
26153
26297
  const filePath = c.req.query("path");
26154
26298
  if (!filePath)
26155
26299
  return c.json({ error: "path required" }, 400);
26156
- const path65 = await import("node:path");
26157
- const resolved = path65.default.resolve(account.workspacePath, filePath);
26158
- const root = path65.default.resolve(account.workspacePath);
26159
- if (resolved !== root && !resolved.startsWith(root + path65.default.sep)) {
26300
+ const path66 = await import("node:path");
26301
+ const resolved = path66.default.resolve(account.workspacePath, filePath);
26302
+ const root = path66.default.resolve(account.workspacePath);
26303
+ if (resolved !== root && !resolved.startsWith(root + path66.default.sep)) {
26160
26304
  return c.json({ error: "path traversal not allowed" }, 403);
26161
26305
  }
26162
26306
  let real;
26163
26307
  let realRoot;
26164
26308
  try {
26165
- real = fs53.realpathSync(resolved);
26166
- realRoot = fs53.realpathSync(root);
26309
+ real = fs54.realpathSync(resolved);
26310
+ realRoot = fs54.realpathSync(root);
26167
26311
  } catch {
26168
26312
  return c.json({ error: "file not found" }, 404);
26169
26313
  }
26170
- if (real !== realRoot && !real.startsWith(realRoot + path65.default.sep)) {
26314
+ if (real !== realRoot && !real.startsWith(realRoot + path66.default.sep)) {
26171
26315
  return c.json({ error: "path traversal not allowed" }, 403);
26172
26316
  }
26173
- const stat = fs53.statSync(real);
26317
+ const stat = fs54.statSync(real);
26174
26318
  if (stat.isDirectory())
26175
26319
  return c.json({ error: "cannot serve directory" }, 400);
26176
- const ext = path65.default.extname(real).toLowerCase();
26320
+ const ext = path66.default.extname(real).toLowerCase();
26177
26321
  const nonImageMimes = {
26178
26322
  ".mp4": "video/mp4",
26179
26323
  ".webm": "video/webm",
@@ -26181,8 +26325,8 @@ function createWebRoutes(deps) {
26181
26325
  ".txt": "text/plain"
26182
26326
  };
26183
26327
  const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
26184
- const fileName = path65.default.basename(resolved);
26185
- const data = fs53.readFileSync(real);
26328
+ const fileName = path66.default.basename(resolved);
26329
+ const data = fs54.readFileSync(real);
26186
26330
  c.header("content-type", contentType);
26187
26331
  c.header("content-disposition", `inline; filename="${fileName}"`);
26188
26332
  return c.body(data);
@@ -26523,10 +26667,10 @@ async function authTest(botToken) {
26523
26667
  return slackPost("auth.test", botToken, {});
26524
26668
  }
26525
26669
  async function uploadFile(args) {
26526
- const fs58 = await import("node:fs");
26527
- const path65 = await import("node:path");
26528
- const filename = args.filename ?? path65.basename(args.filePath);
26529
- const stat = fs58.statSync(args.filePath);
26670
+ const fs59 = await import("node:fs");
26671
+ const path66 = await import("node:path");
26672
+ const filename = args.filename ?? path66.basename(args.filePath);
26673
+ const stat = fs59.statSync(args.filePath);
26530
26674
  if (!stat.isFile())
26531
26675
  throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
26532
26676
  const length = stat.size;
@@ -26544,7 +26688,7 @@ async function uploadFile(args) {
26544
26688
  if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
26545
26689
  throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
26546
26690
  }
26547
- const buf = fs58.readFileSync(args.filePath);
26691
+ const buf = fs59.readFileSync(args.filePath);
26548
26692
  const step2 = await fetch(step1Parsed.upload_url, {
26549
26693
  method: "POST",
26550
26694
  body: buf
@@ -26689,7 +26833,7 @@ var init_api2 = __esm({
26689
26833
  });
26690
26834
 
26691
26835
  // packages/server/dist/channels/slack/handler.js
26692
- import path61 from "node:path";
26836
+ import path62 from "node:path";
26693
26837
  import { WebSocket } from "ws";
26694
26838
  function slashToBang(text5, commandNames) {
26695
26839
  if (commandNames.length === 0)
@@ -26794,7 +26938,7 @@ function startSlackChannel(deps) {
26794
26938
  const route = bridge.getRoute(sessionId);
26795
26939
  if (!account || !route)
26796
26940
  return;
26797
- const resolved = path61.resolve(filePath);
26941
+ const resolved = path62.resolve(filePath);
26798
26942
  if (!isMediaPathAllowed(resolved, account.workspacePath)) {
26799
26943
  console.log(`[slack] sendMedia blocked: ${filePath} not under workspace`);
26800
26944
  return;
@@ -26813,7 +26957,7 @@ function startSlackChannel(deps) {
26813
26957
  channel: route.channelId,
26814
26958
  threadTs: route.replyTs,
26815
26959
  text: t("handler.upload_failed", getLang(account), {
26816
- name: path61.basename(filePath),
26960
+ name: path62.basename(filePath),
26817
26961
  error: err instanceof Error ? err.message : String(err)
26818
26962
  })
26819
26963
  }).catch(() => {
@@ -27070,7 +27214,7 @@ var init_handler5 = __esm({
27070
27214
 
27071
27215
  // packages/server/dist/routes/slack.js
27072
27216
  import { Hono as Hono18 } from "hono";
27073
- import fs54 from "node:fs";
27217
+ import fs55 from "node:fs";
27074
27218
  function createSlackRoutes(deps) {
27075
27219
  const { db, channel } = deps;
27076
27220
  const app2 = new Hono18();
@@ -27093,7 +27237,7 @@ function createSlackRoutes(deps) {
27093
27237
  botUserId: a.botUserId,
27094
27238
  teamId: a.teamId,
27095
27239
  workspacePath: a.workspacePath,
27096
- workspaceMissing: !fs54.existsSync(a.workspacePath),
27240
+ workspaceMissing: !fs55.existsSync(a.workspacePath),
27097
27241
  label: a.label,
27098
27242
  enabled: a.enabled,
27099
27243
  accessLevel: a.accessLevel,
@@ -27625,10 +27769,10 @@ async function replyMessage(args) {
27625
27769
  return { message_id: parsed.data?.message_id ?? "" };
27626
27770
  }
27627
27771
  async function uploadImage(args) {
27628
- const fs58 = await import("node:fs");
27629
- const path65 = await import("node:path");
27630
- const buf = fs58.readFileSync(args.filePath);
27631
- const filename = path65.basename(args.filePath);
27772
+ const fs59 = await import("node:fs");
27773
+ const path66 = await import("node:path");
27774
+ const buf = fs59.readFileSync(args.filePath);
27775
+ const filename = path66.basename(args.filePath);
27632
27776
  const send = async (token2) => {
27633
27777
  const form = new FormData();
27634
27778
  form.append("image_type", "message");
@@ -27659,10 +27803,10 @@ async function uploadImage(args) {
27659
27803
  return { imageKey: parsed.data.image_key };
27660
27804
  }
27661
27805
  async function uploadFile2(args) {
27662
- const fs58 = await import("node:fs");
27663
- const path65 = await import("node:path");
27664
- const buf = fs58.readFileSync(args.filePath);
27665
- const filename = args.filename ?? path65.basename(args.filePath);
27806
+ const fs59 = await import("node:fs");
27807
+ const path66 = await import("node:path");
27808
+ const buf = fs59.readFileSync(args.filePath);
27809
+ const filename = args.filename ?? path66.basename(args.filePath);
27666
27810
  const send = async (token2) => {
27667
27811
  const form = new FormData();
27668
27812
  form.append("file_type", args.fileType);
@@ -27768,7 +27912,7 @@ var init_api3 = __esm({
27768
27912
  });
27769
27913
 
27770
27914
  // packages/server/dist/channels/feishu/handler.js
27771
- import path62 from "node:path";
27915
+ import path63 from "node:path";
27772
27916
  import * as Lark from "@larksuiteoapi/node-sdk";
27773
27917
  function buildSessionPrefixForThread2(chatId, rootId) {
27774
27918
  return sessionPrefix("feishu", `${chatId}:${rootId}`);
@@ -27891,7 +28035,7 @@ function startFeishuChannel(deps) {
27891
28035
  const route = bridge.getRoute(sessionId);
27892
28036
  if (!account || !route)
27893
28037
  return;
27894
- const resolved = path62.resolve(filePath);
28038
+ const resolved = path63.resolve(filePath);
27895
28039
  if (!isMediaPathAllowed(resolved, account.workspacePath)) {
27896
28040
  console.log(`[feishu] sendMedia blocked: ${filePath} not under workspace`);
27897
28041
  return;
@@ -27904,7 +28048,7 @@ function startFeishuChannel(deps) {
27904
28048
  account,
27905
28049
  ...route,
27906
28050
  text: t("handler.upload_failed", getLang(account), {
27907
- name: path62.basename(filePath),
28051
+ name: path63.basename(filePath),
27908
28052
  error: err instanceof Error ? err.message : String(err)
27909
28053
  })
27910
28054
  }).catch(() => {
@@ -28068,7 +28212,7 @@ async function replyToInbound(args) {
28068
28212
  async function sendFeishuMedia(args) {
28069
28213
  const { account, inboundMessageId, isP2P, chatId, filePath } = args;
28070
28214
  const cls = classifyMedia(filePath);
28071
- const ext = path62.extname(filePath).toLowerCase();
28215
+ const ext = path63.extname(filePath).toLowerCase();
28072
28216
  if (cls === "image") {
28073
28217
  const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
28074
28218
  await replyToInboundRaw({
@@ -28194,7 +28338,7 @@ var init_handler6 = __esm({
28194
28338
 
28195
28339
  // packages/server/dist/routes/feishu.js
28196
28340
  import { Hono as Hono19 } from "hono";
28197
- import fs55 from "node:fs";
28341
+ import fs56 from "node:fs";
28198
28342
  function createFeishuRoutes(deps) {
28199
28343
  const { db, channel } = deps;
28200
28344
  const app2 = new Hono19();
@@ -28218,7 +28362,7 @@ function createFeishuRoutes(deps) {
28218
28362
  botOpenId: a.botOpenId,
28219
28363
  hasEncryptKey: !!a.encryptKey,
28220
28364
  workspacePath: a.workspacePath,
28221
- workspaceMissing: !fs55.existsSync(a.workspacePath),
28365
+ workspaceMissing: !fs56.existsSync(a.workspacePath),
28222
28366
  label: a.label,
28223
28367
  enabled: a.enabled,
28224
28368
  accessLevel: a.accessLevel,
@@ -28606,14 +28750,14 @@ function passwordStrengthError(pw) {
28606
28750
  }
28607
28751
  function authMiddleware() {
28608
28752
  return async (c, next) => {
28609
- const path65 = c.req.path;
28610
- if (PUBLIC_PATHS.some((p) => path65 === p || path65.startsWith(p + "?"))) {
28753
+ const path66 = c.req.path;
28754
+ if (PUBLIC_PATHS.some((p) => path66 === p || path66.startsWith(p + "?"))) {
28611
28755
  return next();
28612
28756
  }
28613
28757
  const token = getCookie(c, COOKIE_NAME);
28614
28758
  const payload = validateToken(token);
28615
28759
  if (!payload) {
28616
- if (!path65.startsWith("/api/")) {
28760
+ if (!path66.startsWith("/api/")) {
28617
28761
  return next();
28618
28762
  }
28619
28763
  return c.json({ error: "Unauthorized" }, 401);
@@ -28648,20 +28792,21 @@ var init_auth = __esm({
28648
28792
  var dist_exports = {};
28649
28793
  import { Hono as Hono21 } from "hono";
28650
28794
  import { cors } from "hono/cors";
28795
+ import { compress } from "hono/compress";
28651
28796
  import { serve } from "@hono/node-server";
28652
28797
  import { serveStatic } from "@hono/node-server/serve-static";
28653
28798
  import { WebSocketServer } from "ws";
28654
- import path63 from "node:path";
28655
- import fs56 from "node:fs";
28656
- import { spawnSync as spawnSync3 } from "node:child_process";
28799
+ import path64 from "node:path";
28800
+ import fs57 from "node:fs";
28801
+ import { spawnSync as spawnSync4 } from "node:child_process";
28657
28802
  import { homedir as homedir24 } from "node:os";
28658
28803
  import { fileURLToPath as fileURLToPath3 } from "node:url";
28659
28804
  function findProjectRoot() {
28660
28805
  let dir = process.cwd();
28661
28806
  for (let i = 0; i < 32; i++) {
28662
- if (fs56.existsSync(path63.join(dir, "pnpm-workspace.yaml")))
28807
+ if (fs57.existsSync(path64.join(dir, "pnpm-workspace.yaml")))
28663
28808
  return dir;
28664
- const parent = path63.dirname(dir);
28809
+ const parent = path64.dirname(dir);
28665
28810
  if (parent === dir)
28666
28811
  break;
28667
28812
  dir = parent;
@@ -28670,15 +28815,15 @@ function findProjectRoot() {
28670
28815
  return process.cwd();
28671
28816
  }
28672
28817
  function acquireSingleInstanceLock(lockFile) {
28673
- fs56.mkdirSync(path63.dirname(lockFile), { recursive: true });
28674
- const fd = fs56.openSync(lockFile, fs56.constants.O_RDWR | fs56.constants.O_CREAT, 420);
28675
- const result = spawnSync3("flock", ["-n", "3"], {
28818
+ fs57.mkdirSync(path64.dirname(lockFile), { recursive: true });
28819
+ const fd = fs57.openSync(lockFile, fs57.constants.O_RDWR | fs57.constants.O_CREAT, 420);
28820
+ const result = spawnSync4("flock", ["-n", "3"], {
28676
28821
  stdio: ["ignore", "ignore", "ignore", fd]
28677
28822
  });
28678
28823
  if (result.error) {
28679
28824
  const content = readLockPid(fd);
28680
28825
  if (content > 0 && isProcessAlive(content)) {
28681
- fs56.closeSync(fd);
28826
+ fs57.closeSync(fd);
28682
28827
  console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
28683
28828
  process.exit(1);
28684
28829
  }
@@ -28687,19 +28832,19 @@ function acquireSingleInstanceLock(lockFile) {
28687
28832
  if (content > 0 && !isProcessAlive(content)) {
28688
28833
  console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
28689
28834
  } else {
28690
- fs56.closeSync(fd);
28835
+ fs57.closeSync(fd);
28691
28836
  console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
28692
28837
  process.exit(1);
28693
28838
  }
28694
28839
  }
28695
- fs56.ftruncateSync(fd);
28696
- fs56.writeSync(fd, String(process.pid));
28697
- fs56.fsyncSync(fd);
28840
+ fs57.ftruncateSync(fd);
28841
+ fs57.writeSync(fd, String(process.pid));
28842
+ fs57.fsyncSync(fd);
28698
28843
  }
28699
28844
  function readLockPid(fd) {
28700
28845
  try {
28701
28846
  const buf = Buffer.alloc(32);
28702
- const n = fs56.readSync(fd, buf, 0, 32, 0);
28847
+ const n = fs57.readSync(fd, buf, 0, 32, 0);
28703
28848
  return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
28704
28849
  } catch {
28705
28850
  return 0;
@@ -28708,7 +28853,7 @@ function readLockPid(fd) {
28708
28853
  function isProcessAlive(pid) {
28709
28854
  if (process.platform === "win32") {
28710
28855
  try {
28711
- const out = spawnSync3("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
28856
+ const out = spawnSync4("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
28712
28857
  encoding: "utf-8",
28713
28858
  windowsHide: true
28714
28859
  });
@@ -28727,16 +28872,16 @@ function isProcessAlive(pid) {
28727
28872
  }
28728
28873
  }
28729
28874
  function readGitSha() {
28730
- if (!fs56.existsSync(path63.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs56.existsSync(path63.join(PROJECT_ROOT, ".git")))
28875
+ if (!fs57.existsSync(path64.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs57.existsSync(path64.join(PROJECT_ROOT, ".git")))
28731
28876
  return null;
28732
28877
  try {
28733
- const rev = spawnSync3("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
28878
+ const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
28734
28879
  if (rev.status !== 0)
28735
28880
  return null;
28736
28881
  const sha = rev.stdout.trim();
28737
28882
  if (!sha)
28738
28883
  return null;
28739
- const dirty = spawnSync3("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
28884
+ const dirty = spawnSync4("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
28740
28885
  return dirty ? `${sha}-dirty` : sha;
28741
28886
  } catch {
28742
28887
  return null;
@@ -28744,14 +28889,14 @@ function readGitSha() {
28744
28889
  }
28745
28890
  function resolveFrontendDir() {
28746
28891
  if (process.env.HALO_FRONTEND_DIR)
28747
- return path63.resolve(process.env.HALO_FRONTEND_DIR);
28748
- const bundled = path63.resolve(__dirname3, "..", "admin-out");
28749
- if (fs56.existsSync(path63.join(bundled, "index.html")))
28892
+ return path64.resolve(process.env.HALO_FRONTEND_DIR);
28893
+ const bundled = path64.resolve(__dirname3, "..", "admin-out");
28894
+ if (fs57.existsSync(path64.join(bundled, "index.html")))
28750
28895
  return bundled;
28751
- const sibling = path63.resolve(__dirname3, "..", "..", "admin", "out");
28752
- if (fs56.existsSync(path63.join(sibling, "index.html")))
28896
+ const sibling = path64.resolve(__dirname3, "..", "..", "admin", "out");
28897
+ if (fs57.existsSync(path64.join(sibling, "index.html")))
28753
28898
  return sibling;
28754
- return path63.resolve(PROJECT_ROOT, "packages", "admin", "out");
28899
+ return path64.resolve(PROJECT_ROOT, "packages", "admin", "out");
28755
28900
  }
28756
28901
  async function gracefulShutdown(signal) {
28757
28902
  console.log(`[Server] Received ${signal}, shutting down gracefully...`);
@@ -28807,13 +28952,13 @@ var init_dist2 = __esm({
28807
28952
  init_sandbox();
28808
28953
  init_init();
28809
28954
  init_git_ssh();
28810
- __dirname3 = path63.dirname(fileURLToPath3(import.meta.url));
28955
+ __dirname3 = path64.dirname(fileURLToPath3(import.meta.url));
28811
28956
  PORT = config.server.port;
28812
- HALO_VERSION = "1.1.0";
28957
+ HALO_VERSION = "1.1.2";
28813
28958
  PROJECT_ROOT = findProjectRoot();
28814
- HALO_HOME4 = path63.join(homedir24(), ".halo");
28959
+ HALO_HOME4 = path64.join(homedir24(), ".halo");
28815
28960
  GIT_SHA = readGitSha();
28816
- if (!fs56.existsSync(path63.join(HALO_HOME4, "global", ".template-version"))) {
28961
+ if (!fs57.existsSync(path64.join(HALO_HOME4, "global", ".template-version"))) {
28817
28962
  process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
28818
28963
  process.exit(1);
28819
28964
  }
@@ -28834,7 +28979,7 @@ var init_dist2 = __esm({
28834
28979
  process.exit(1);
28835
28980
  }
28836
28981
  if (!AGENTCORE)
28837
- acquireSingleInstanceLock(path63.join(HALO_HOME4, "global", "server.lock"));
28982
+ acquireSingleInstanceLock(path64.join(HALO_HOME4, "global", "server.lock"));
28838
28983
  {
28839
28984
  const declared = new Set(commandRegistry.listDescriptors().filter((d) => d.source === "builtin").map((d) => d.slashName));
28840
28985
  const dispatched = new Set(DISPATCH_COMMANDS);
@@ -28873,6 +29018,7 @@ var init_dist2 = __esm({
28873
29018
  maxAge: 86400,
28874
29019
  credentials: allowlist.length > 0
28875
29020
  }));
29021
+ app.use("/*", compress({ threshold: 1024 }));
28876
29022
  app.use("/api/*", authMiddleware());
28877
29023
  authRoutes = createAuthRoutes();
28878
29024
  app.route("/api", authRoutes);
@@ -28902,10 +29048,10 @@ var init_dist2 = __esm({
28902
29048
  app.route("/api", evolutionRoutes);
28903
29049
  cronRoutes = createCronRoutes();
28904
29050
  app.route("/api", cronRoutes);
28905
- channelDb = createChannelDb(path63.join(HALO_HOME4, "secrets"));
29051
+ channelDb = createChannelDb(path64.join(HALO_HOME4, "secrets"));
28906
29052
  setChannelDb(channelDb);
28907
- setEvoDb(createEvoDb(path63.join(HALO_HOME4, "global")));
28908
- setCronDb(createCronDb(path63.join(HALO_HOME4, "global")));
29053
+ setEvoDb(createEvoDb(path64.join(HALO_HOME4, "global")));
29054
+ setCronDb(createCronDb(path64.join(HALO_HOME4, "global")));
28909
29055
  setEvoSpawner(realEvoSpawner);
28910
29056
  if (!AGENTCORE) {
28911
29057
  startEvoTicker();
@@ -28931,11 +29077,11 @@ var init_dist2 = __esm({
28931
29077
  console.log(`[Server] AgentCore runtime mode \u2014 workspace: ${config.server.agentcoreWorkspace}`);
28932
29078
  }
28933
29079
  FRONTEND_DIR = resolveFrontendDir();
28934
- app.use("/*", serveStatic({ root: path63.relative(process.cwd(), FRONTEND_DIR) }));
29080
+ app.use("/*", serveStatic({ root: path64.relative(process.cwd(), FRONTEND_DIR) }));
28935
29081
  app.get("/*", (c) => {
28936
- const indexPath = path63.join(FRONTEND_DIR, "index.html");
29082
+ const indexPath = path64.join(FRONTEND_DIR, "index.html");
28937
29083
  try {
28938
- const html = fs56.readFileSync(indexPath, "utf-8");
29084
+ const html = fs57.readFileSync(indexPath, "utf-8");
28939
29085
  return c.html(html);
28940
29086
  } catch {
28941
29087
  return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
@@ -29706,11 +29852,11 @@ process.emitWarning = (warning, ...args) => {
29706
29852
 
29707
29853
  // packages/cli/src/index.ts
29708
29854
  init_server_supervisor();
29709
- import path64 from "node:path";
29710
- import fs57 from "node:fs";
29855
+ import path65 from "node:path";
29856
+ import fs58 from "node:fs";
29711
29857
  import { homedir as homedir25 } from "node:os";
29712
29858
  import { parseArgs as parseArgs2 } from "node:util";
29713
- var VERSION = "1.1.0";
29859
+ var VERSION = "1.1.2";
29714
29860
  var HELP_TOP = `Usage: halo <command> [options]
29715
29861
 
29716
29862
  Commands:
@@ -29967,8 +30113,8 @@ async function cmdSetup(argv = []) {
29967
30113
  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));
29968
30114
  const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
29969
30115
  const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
29970
- const haloHome2 = path64.join(homedir25(), ".halo");
29971
- const globalDir2 = path64.join(haloHome2, "global");
30116
+ const haloHome2 = path65.join(homedir25(), ".halo");
30117
+ const globalDir2 = path65.join(haloHome2, "global");
29972
30118
  ensureHaloHome2(haloHome2);
29973
30119
  process.stderr.write(`[setup] ${haloHome2}/ ready
29974
30120
  `);
@@ -30271,10 +30417,10 @@ async function cmdUpgrade() {
30271
30417
  );
30272
30418
  process.exit(1);
30273
30419
  }
30274
- const { spawnSync: spawnSync4 } = await import("node:child_process");
30420
+ const { spawnSync: spawnSync5 } = await import("node:child_process");
30275
30421
  process.stderr.write(`Checking npm for the latest ${PKG}\u2026
30276
30422
  `);
30277
- const view = spawnSync4("npm", ["view", PKG, "version"], { encoding: "utf-8" });
30423
+ const view = spawnSync5("npm", ["view", PKG, "version"], { encoding: "utf-8" });
30278
30424
  if (view.error || view.status !== 0) {
30279
30425
  const err = view.error?.message ?? view.stderr?.trim() ?? `exit ${view.status}`;
30280
30426
  process.stderr.write(`halo: \`npm view\` failed \u2014 ${err}
@@ -30292,10 +30438,40 @@ async function cmdUpgrade() {
30292
30438
  `);
30293
30439
  return;
30294
30440
  }
30441
+ const { ALLOW_SCRIPTS: ALLOW_SCRIPTS2, verifyNativeModules: verifyNativeModules2 } = await Promise.resolve().then(() => (init_upgrade_verify(), upgrade_verify_exports));
30295
30442
  process.stderr.write(`Upgrading ${installed} \u2192 ${latest}\u2026
30296
30443
  `);
30297
- const install = spawnSync4("npm", ["install", "-g", `${PKG}@latest`], { stdio: "inherit" });
30444
+ const install = spawnSync5(
30445
+ "npm",
30446
+ ["install", "-g", `--allow-scripts=${ALLOW_SCRIPTS2}`, `${PKG}@latest`],
30447
+ { stdio: "inherit" }
30448
+ );
30298
30449
  if (install.status === 0) {
30450
+ const npmRoot = spawnSync5("npm", ["root", "-g"], {
30451
+ encoding: "utf-8",
30452
+ shell: process.platform === "win32"
30453
+ });
30454
+ const rootDir = npmRoot.status === 0 ? npmRoot.stdout.trim() : "";
30455
+ if (!rootDir) {
30456
+ process.stderr.write(`halo: \`npm root -g\` failed \u2014 cannot verify the new install
30457
+ `);
30458
+ process.exit(1);
30459
+ }
30460
+ const broken = verifyNativeModules2(path65.join(rootDir, PKG));
30461
+ if (broken.length > 0) {
30462
+ process.stderr.write(
30463
+ `
30464
+ halo: upgrade installed, but native module(s) failed to load: ${broken.join(", ")}
30465
+ This usually means npm blocked the module's install scripts (npm 12+ allowScripts policy \u2014 check \`npm -v\`).
30466
+
30467
+ Fix \u2014 reinstall with the scripts allowed:
30468
+ sudo npm install -g --allow-scripts=${ALLOW_SCRIPTS2} ${PKG}@latest
30469
+ Or persist the allowance first, then reinstall:
30470
+ npm config set allow-scripts=${ALLOW_SCRIPTS2} --location=user
30471
+ `
30472
+ );
30473
+ process.exit(1);
30474
+ }
30299
30475
  process.stderr.write(
30300
30476
  `
30301
30477
  Upgraded to ${latest}. Run \`halo server restart\` (or your service manager's restart) to pick up the new templates.
@@ -30312,7 +30488,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
30312
30488
  async function cmdAgents(flags) {
30313
30489
  const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
30314
30490
  await initRuntime2();
30315
- const workspace = path64.resolve(flags.workspace ?? process.cwd());
30491
+ const workspace = path65.resolve(flags.workspace ?? process.cwd());
30316
30492
  const agents = await listAgents2(workspace);
30317
30493
  if (agents.length === 0) {
30318
30494
  process.stderr.write("No agents found.\n");
@@ -30328,7 +30504,7 @@ async function cmdAgents(flags) {
30328
30504
  async function cmdSessions(flags) {
30329
30505
  const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
30330
30506
  await initRuntime2();
30331
- const workspace = path64.resolve(flags.workspace ?? process.cwd());
30507
+ const workspace = path65.resolve(flags.workspace ?? process.cwd());
30332
30508
  const sessions = listSessions2(workspace);
30333
30509
  if (sessions.length === 0) {
30334
30510
  process.stderr.write("No sessions found.\n");
@@ -30441,11 +30617,11 @@ ${stdinContent}` : stdinContent;
30441
30617
  harness.destroy();
30442
30618
  }
30443
30619
  }
30444
- var SERVER_LOCK_PATH = path64.join(homedir25(), ".halo", "global", "server.lock");
30445
- var SERVER_LOG_PATH = path64.join(homedir25(), ".halo", "logs", "server.log");
30620
+ var SERVER_LOCK_PATH = path65.join(homedir25(), ".halo", "global", "server.lock");
30621
+ var SERVER_LOG_PATH = path65.join(homedir25(), ".halo", "logs", "server.log");
30446
30622
  function readServerPid() {
30447
30623
  try {
30448
- const txt = fs57.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
30624
+ const txt = fs58.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
30449
30625
  const n = parseInt(txt, 10);
30450
30626
  return Number.isFinite(n) && n > 0 ? n : null;
30451
30627
  } catch {
@@ -30532,8 +30708,8 @@ async function cmdServerStart(argv, _unused) {
30532
30708
  return;
30533
30709
  }
30534
30710
  const { spawn: spawn5 } = await import("node:child_process");
30535
- fs57.mkdirSync(path64.dirname(SERVER_LOG_PATH), { recursive: true });
30536
- const out = fs57.openSync(SERVER_LOG_PATH, "a");
30711
+ fs58.mkdirSync(path65.dirname(SERVER_LOG_PATH), { recursive: true });
30712
+ const out = fs58.openSync(SERVER_LOG_PATH, "a");
30537
30713
  const child = spawn5(process.argv[0], [process.argv[1], "server", "start"], {
30538
30714
  detached: true,
30539
30715
  stdio: ["ignore", out, out],
@@ -30634,7 +30810,7 @@ async function cmdServerLogs(argv) {
30634
30810
  lines: { type: "string", short: "n" }
30635
30811
  }
30636
30812
  });
30637
- if (!fs57.existsSync(SERVER_LOG_PATH)) {
30813
+ if (!fs58.existsSync(SERVER_LOG_PATH)) {
30638
30814
  process.stderr.write(`No log file at ${SERVER_LOG_PATH}
30639
30815
  `);
30640
30816
  return;
@@ -30652,7 +30828,7 @@ function exitMissingSetup() {
30652
30828
  process.exit(1);
30653
30829
  }
30654
30830
  function isHaloHomeReady() {
30655
- return fs57.existsSync(path64.join(homedir25(), ".halo", "global", ".template-version"));
30831
+ return fs58.existsSync(path65.join(homedir25(), ".halo", "global", ".template-version"));
30656
30832
  }
30657
30833
  async function main2() {
30658
30834
  const argv = process.argv.slice(2);