@turmind/halo 0.2.3 → 0.2.5

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
@@ -608,7 +608,26 @@ function expandEnv(raw) {
608
608
  return "";
609
609
  return raw.replace(ENV_PATTERN, (m, name) => process.env[name] ?? m);
610
610
  }
611
- function getServerSecret(namespaceId, key) {
611
+ function getWorkspaceSettings(workspaceRoot) {
612
+ const p = path3.join(workspaceRoot, ".halo", "settings.yaml");
613
+ let mtimeMs = 0;
614
+ try {
615
+ mtimeMs = fs3.statSync(p).mtimeMs;
616
+ } catch {
617
+ }
618
+ const hit = _wsSettingsCache.get(workspaceRoot);
619
+ if (hit && hit.mtimeMs === mtimeMs)
620
+ return hit.data;
621
+ const entry = { mtimeMs, data: loadYamlFile(p) };
622
+ _wsSettingsCache.set(workspaceRoot, entry);
623
+ return entry.data;
624
+ }
625
+ function getServerSecret(namespaceId, key, workspaceRoot) {
626
+ if (workspaceRoot) {
627
+ const ws = readYamlValue(getWorkspaceSettings(workspaceRoot), `${namespaceId}.secrets.${key}`);
628
+ if (ws != null && String(ws) !== "")
629
+ return expandEnv(String(ws));
630
+ }
612
631
  return expandEnv(settingsValue(`${namespaceId}.secrets.${key}`));
613
632
  }
614
633
  function reloadSandboxConfig() {
@@ -637,13 +656,13 @@ function resolveMaxOutputTokens(modelId) {
637
656
  const entry = findModelEntry(modelId);
638
657
  return entry?.maxOutputTokens ?? 16384;
639
658
  }
640
- function resolveApiKey(providerId) {
641
- return getServerSecret(providerId, "api_key") || void 0;
659
+ function resolveApiKey(providerId, workspaceRoot) {
660
+ return getServerSecret(providerId, "api_key", workspaceRoot) || void 0;
642
661
  }
643
- function resolveAwsCredentials(providerId = "aws-bedrock-claude-invoke") {
662
+ function resolveAwsCredentials(providerId = "aws-bedrock-claude-invoke", workspaceRoot) {
644
663
  return {
645
- accessKeyId: getServerSecret(providerId, "access_key_id"),
646
- secretAccessKey: getServerSecret(providerId, "secret_access_key")
664
+ accessKeyId: getServerSecret(providerId, "access_key_id", workspaceRoot),
665
+ secretAccessKey: getServerSecret(providerId, "secret_access_key", workspaceRoot)
647
666
  };
648
667
  }
649
668
  function resolveThinkingMode(modelId) {
@@ -687,7 +706,7 @@ function modelSupportsAudio(modelId, override) {
687
706
  function getModelsRegistry() {
688
707
  return modelsRegistry();
689
708
  }
690
- var HALO_HOME2, HALO_GLOBAL_DIR, HALO_SECRETS_DIR, _modelsRegistryCache, _systemConfig, SETTINGS_PATH, _settingsCache, _schemaDefaultsCache, ENV_PATTERN, config, HIDDEN_DIRS_DEFAULT, HIDDEN_FILES_DEFAULT;
709
+ var HALO_HOME2, HALO_GLOBAL_DIR, HALO_SECRETS_DIR, _modelsRegistryCache, _systemConfig, SETTINGS_PATH, _settingsCache, _schemaDefaultsCache, ENV_PATTERN, _wsSettingsCache, config, HIDDEN_DIRS_DEFAULT, HIDDEN_FILES_DEFAULT;
691
710
  var init_config = __esm({
692
711
  "packages/server/dist/config.js"() {
693
712
  "use strict";
@@ -701,6 +720,7 @@ var init_config = __esm({
701
720
  _settingsCache = null;
702
721
  _schemaDefaultsCache = null;
703
722
  ENV_PATTERN = /<<([A-Z_][A-Z0-9_]*)>>/g;
723
+ _wsSettingsCache = /* @__PURE__ */ new Map();
704
724
  config = {
705
725
  server: {
706
726
  port: systemInt("HALO_PORT", "server.port", 9527),
@@ -1841,6 +1861,7 @@ __export(init_exports, {
1841
1861
  ensureHaloHome: () => ensureHaloHome,
1842
1862
  ensureWorkspaceHalo: () => ensureWorkspaceHalo,
1843
1863
  listOptionalSkillIds: () => listOptionalSkillIds,
1864
+ mergeAgentYaml: () => mergeAgentYaml,
1844
1865
  readInstalledOptionalSkills: () => readInstalledOptionalSkills,
1845
1866
  readSeedVersion: () => readSeedVersion,
1846
1867
  writeInstalledOptionalSkills: () => writeInstalledOptionalSkills
@@ -1888,6 +1909,10 @@ function mergeAgentYaml(srcAbs, dstAbs) {
1888
1909
  if (userModel != null) {
1889
1910
  templateDoc.set("model", userModel);
1890
1911
  }
1912
+ const userContext = userDoc.get("context");
1913
+ if (userContext != null) {
1914
+ templateDoc.set("context", userContext);
1915
+ }
1891
1916
  fs6.writeFileSync(dstAbs, templateDoc.toString(), "utf-8");
1892
1917
  } catch (err) {
1893
1918
  console.log(`[Init] mergeAgentYaml fallback (force-copy) for ${dstAbs}: ${err instanceof Error ? err.message : String(err)}`);
@@ -7878,9 +7903,9 @@ do delegate, say so in one line and keep going.`;
7878
7903
  const promptCaching = rawCaching === "1h" ? "1h" : rawCaching === "5m" ? "5m" : rawCaching ? true : void 0;
7879
7904
  const thinkingConfig = yamlConfig?.model?.thinking;
7880
7905
  const thinkingMode = resolveThinkingMode(modelId);
7881
- const aws = resolveAwsCredentials(providerId);
7906
+ const aws = resolveAwsCredentials(providerId, this.host.workspaceRoot);
7882
7907
  const awsCreds = aws.accessKeyId && aws.secretAccessKey ? aws : void 0;
7883
- const apiKey = resolveApiKey(providerId);
7908
+ const apiKey = resolveApiKey(providerId, this.host.workspaceRoot);
7884
7909
  const agent = createModelRuntime(providerId, {
7885
7910
  modelId,
7886
7911
  endpoint,
@@ -18769,6 +18794,12 @@ var init_agentcore = __esm({
18769
18794
  // packages/server/dist/ws/file-watcher.js
18770
18795
  import fs41 from "node:fs";
18771
18796
  import path45 from "node:path";
18797
+ function enqueueNativeOp(op) {
18798
+ const result = nativeOpChain.then(op);
18799
+ nativeOpChain = result.catch(() => {
18800
+ });
18801
+ return result;
18802
+ }
18772
18803
  function isUnwatchablePath(absPath) {
18773
18804
  const norm = path45.resolve(absPath);
18774
18805
  if (norm === "/" || norm === path45.parse(norm).root)
@@ -18780,7 +18811,7 @@ function isUnwatchablePath(absPath) {
18780
18811
  }
18781
18812
  return null;
18782
18813
  }
18783
- var IGNORED_SEGMENTS, IGNORE_GLOBS, WorkspaceWatcher;
18814
+ var IGNORED_SEGMENTS, IS_WIN32, IGNORED_SEGMENT_SET, NATIVE_IGNORE, nativeOpChain, WorkspaceWatcher;
18784
18815
  var init_file_watcher = __esm({
18785
18816
  "packages/server/dist/ws/file-watcher.js"() {
18786
18817
  "use strict";
@@ -18844,7 +18875,10 @@ var init_file_watcher = __esm({
18844
18875
  "System Volume Information",
18845
18876
  "AppData"
18846
18877
  ];
18847
- IGNORE_GLOBS = IGNORED_SEGMENTS.flatMap((seg) => [seg, `**/${seg}/**`]);
18878
+ IS_WIN32 = process.platform === "win32";
18879
+ IGNORED_SEGMENT_SET = new Set(IGNORED_SEGMENTS);
18880
+ NATIVE_IGNORE = IS_WIN32 ? IGNORED_SEGMENTS : IGNORED_SEGMENTS.flatMap((seg) => [seg, `**/${seg}/**`]);
18881
+ nativeOpChain = Promise.resolve();
18848
18882
  WorkspaceWatcher = class {
18849
18883
  subscription = null;
18850
18884
  workspaceRoot = null;
@@ -18852,33 +18886,55 @@ var init_file_watcher = __esm({
18852
18886
  /** Debounce: batch rapid changes into a single notification per path */
18853
18887
  pending = /* @__PURE__ */ new Map();
18854
18888
  flushTimer = null;
18889
+ /** Bumped by every start()/stop(). A start() whose subscribe resolves after
18890
+ * a newer start()/stop() has bumped the epoch is stale: assigning its
18891
+ * subscription would overwrite (and leak) the current one, so it tears its
18892
+ * own subscription down instead. */
18893
+ startEpoch = 0;
18855
18894
  setCallback(cb) {
18856
18895
  this.callback = cb;
18857
18896
  }
18858
18897
  async start(workspaceRoot) {
18859
- if (this.subscription && this.workspaceRoot === workspaceRoot)
18860
- return;
18861
- await this.stop();
18862
18898
  const reject = isUnwatchablePath(workspaceRoot);
18863
18899
  if (reject) {
18864
18900
  console.warn(`[FileWatcher] refusing to watch ${workspaceRoot} (${reject}) \u2014 Explorer live-refresh disabled for this workspace`);
18865
18901
  return;
18866
18902
  }
18867
- this.workspaceRoot = workspaceRoot;
18903
+ let root = workspaceRoot;
18904
+ try {
18905
+ root = fs41.realpathSync(workspaceRoot);
18906
+ } catch {
18907
+ }
18908
+ if (this.subscription && this.workspaceRoot === root)
18909
+ return;
18910
+ await this.stop();
18911
+ const epoch = ++this.startEpoch;
18912
+ this.workspaceRoot = root;
18868
18913
  try {
18869
18914
  const { default: watcher } = await import("@parcel/watcher");
18870
- this.subscription = await watcher.subscribe(workspaceRoot, (err, events) => {
18915
+ const sub = await enqueueNativeOp(() => watcher.subscribe(root, (err, events) => {
18871
18916
  if (err) {
18872
- console.warn(`[FileWatcher] watcher error on ${workspaceRoot}: ${err.message}`);
18917
+ console.warn(`[FileWatcher] watcher error on ${root}: ${err.message}`);
18873
18918
  return;
18874
18919
  }
18875
18920
  for (const e of events)
18876
18921
  this.handleEvent(e.type, e.path);
18877
- }, { ignore: IGNORE_GLOBS });
18922
+ }, { ignore: NATIVE_IGNORE }));
18923
+ if (this.startEpoch !== epoch) {
18924
+ void enqueueNativeOp(() => sub.unsubscribe()).catch((err) => {
18925
+ console.warn(`[FileWatcher] failed to unsubscribe superseded watch on ${root}: ${err instanceof Error ? err.message : String(err)}`);
18926
+ });
18927
+ return;
18928
+ }
18929
+ this.subscription = sub;
18930
+ console.debug(`[FileWatcher] watching ${root}`);
18878
18931
  } catch (err) {
18879
- console.warn(`[FileWatcher] failed to watch ${workspaceRoot}: ${err instanceof Error ? err.message : String(err)}`);
18880
- this.subscription = null;
18881
- this.workspaceRoot = null;
18932
+ const detail = err instanceof Error ? err.stack ?? err.message : String(err);
18933
+ console.warn(`[FileWatcher] failed to watch ${root} \u2014 Explorer live-refresh disabled: ${detail}`);
18934
+ if (this.startEpoch === epoch) {
18935
+ this.subscription = null;
18936
+ this.workspaceRoot = null;
18937
+ }
18882
18938
  }
18883
18939
  }
18884
18940
  /** Map a parcel event to our add/change/unlink/addDir/unlinkDir shape and
@@ -18894,6 +18950,8 @@ var init_file_watcher = __esm({
18894
18950
  if (!raw || raw.startsWith(".."))
18895
18951
  return;
18896
18952
  const rel = raw.split(path45.sep).join("/");
18953
+ if (IS_WIN32 && rel.split("/").some((seg) => IGNORED_SEGMENT_SET.has(seg)))
18954
+ return;
18897
18955
  let action;
18898
18956
  if (type === "delete") {
18899
18957
  action = "unlink";
@@ -18938,6 +18996,7 @@ var init_file_watcher = __esm({
18938
18996
  this.pending.set(rel, { path: rel, action });
18939
18997
  }
18940
18998
  async stop() {
18999
+ this.startEpoch++;
18941
19000
  if (this.flushTimer)
18942
19001
  clearTimeout(this.flushTimer);
18943
19002
  this.flushTimer = null;
@@ -18947,11 +19006,16 @@ var init_file_watcher = __esm({
18947
19006
  this.pending.clear();
18948
19007
  if (!sub)
18949
19008
  return;
18950
- await Promise.race([
18951
- sub.unsubscribe().catch(() => {
18952
- }),
18953
- new Promise((resolve2) => setTimeout(resolve2, 2e3))
19009
+ const settled = enqueueNativeOp(() => sub.unsubscribe()).then(() => true, (err) => {
19010
+ console.warn(`[FileWatcher] unsubscribe failed: ${err instanceof Error ? err.message : String(err)}`);
19011
+ return true;
19012
+ });
19013
+ const done = await Promise.race([
19014
+ settled,
19015
+ new Promise((resolve2) => setTimeout(() => resolve2(false), 2e3))
18954
19016
  ]);
19017
+ if (!done)
19018
+ console.warn("[FileWatcher] unsubscribe still pending after 2s \u2014 continuing; next subscribe will wait for it");
18955
19019
  }
18956
19020
  scheduleFlush() {
18957
19021
  if (this.flushTimer)
@@ -19010,6 +19074,11 @@ var init_git_dir_watcher = __esm({
19010
19074
  this.watcher = fs42.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
19011
19075
  this.handleGitEvent(filename);
19012
19076
  });
19077
+ this.watcher.on("error", (err) => {
19078
+ console.warn(`[GitDirWatcher] watch error on ${gitDir}: ${err.message} \u2014 git auto-refresh disabled`);
19079
+ this.closeWatcher();
19080
+ this.mode = null;
19081
+ });
19013
19082
  this.mode = "git";
19014
19083
  } catch (err) {
19015
19084
  console.warn(`[GitDirWatcher] failed to watch ${gitDir}: ${err instanceof Error ? err.message : String(err)}`);
@@ -19024,6 +19093,11 @@ var init_git_dir_watcher = __esm({
19024
19093
  this.watcher = fs42.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
19025
19094
  this.handleParentEvent(filename);
19026
19095
  });
19096
+ this.watcher.on("error", (err) => {
19097
+ console.warn(`[GitDirWatcher] watch error on ${workspaceRoot}: ${err.message} \u2014 git auto-refresh disabled`);
19098
+ this.closeWatcher();
19099
+ this.mode = null;
19100
+ });
19027
19101
  this.mode = "parent";
19028
19102
  } catch (err) {
19029
19103
  console.warn(`[GitDirWatcher] failed to watch ${workspaceRoot}: ${err instanceof Error ? err.message : String(err)}`);
@@ -19663,6 +19737,8 @@ function setupWebSocketHandler(deps) {
19663
19737
  client2.unsubscribeEvents?.();
19664
19738
  client2.sessionId = await sm.createSession(agentId, null, "Explorer chat", void 0, msg.sessionId);
19665
19739
  client2.unsubscribeEvents = sm.registerEventListener(client2.sessionId, createEventListener(client2));
19740
+ const ctxConfig = await sm.getContextConfig(client2.sessionId);
19741
+ sendJson(ws, { type: "state:snapshot", snapshot: { activePlan: null, agents: [], recentMessages: [], sessionId: client2.sessionId, maxContextTokens: ctxConfig.maxTokens, agentId } });
19666
19742
  } else if (client2.sessionId !== msg.sessionId) {
19667
19743
  client2.unsubscribeEvents?.();
19668
19744
  client2.sessionId = msg.sessionId;
@@ -26311,6 +26387,22 @@ function isProcessAlive(pid) {
26311
26387
  return false;
26312
26388
  }
26313
26389
  }
26390
+ function readGitSha() {
26391
+ if (!fs53.existsSync(path61.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs53.existsSync(path61.join(PROJECT_ROOT, ".git")))
26392
+ return null;
26393
+ try {
26394
+ const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
26395
+ if (rev.status !== 0)
26396
+ return null;
26397
+ const sha = rev.stdout.trim();
26398
+ if (!sha)
26399
+ return null;
26400
+ const dirty = spawnSync4("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
26401
+ return dirty ? `${sha}-dirty` : sha;
26402
+ } catch {
26403
+ return null;
26404
+ }
26405
+ }
26314
26406
  function resolveFrontendDir() {
26315
26407
  if (process.env.HALO_FRONTEND_DIR)
26316
26408
  return path61.resolve(process.env.HALO_FRONTEND_DIR);
@@ -26337,7 +26429,7 @@ async function gracefulShutdown(signal) {
26337
26429
  console.log("[Server] Shutdown complete");
26338
26430
  process.exit(0);
26339
26431
  }
26340
- var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, AGENTCORE, app, allowlist, authRoutes, fileRoutes, gitRoutes, agentConfigRoutes, skillRoutes, settingsRoutes, evolutionRoutes, cronRoutes, channelDb, registry, sessionRoutes, showRoutes, metricsRoutes, commandRoutes, FRONTEND_DIR, server, wss;
26432
+ var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, GIT_SHA, AGENTCORE, app, allowlist, authRoutes, fileRoutes, gitRoutes, agentConfigRoutes, skillRoutes, settingsRoutes, evolutionRoutes, cronRoutes, channelDb, registry, sessionRoutes, showRoutes, metricsRoutes, commandRoutes, FRONTEND_DIR, server, wss;
26341
26433
  var init_dist2 = __esm({
26342
26434
  async "packages/server/dist/index.js"() {
26343
26435
  "use strict";
@@ -26375,9 +26467,10 @@ var init_dist2 = __esm({
26375
26467
  init_git_ssh();
26376
26468
  __dirname3 = path61.dirname(fileURLToPath3(import.meta.url));
26377
26469
  PORT = config.server.port;
26378
- HALO_VERSION = "0.2.3";
26470
+ HALO_VERSION = "0.2.5";
26379
26471
  PROJECT_ROOT = findProjectRoot();
26380
26472
  HALO_HOME4 = path61.join(homedir24(), ".halo");
26473
+ GIT_SHA = readGitSha();
26381
26474
  if (!fs53.existsSync(path61.join(HALO_HOME4, "global", ".template-version"))) {
26382
26475
  process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
26383
26476
  process.exit(1);
@@ -26447,7 +26540,8 @@ var init_dist2 = __esm({
26447
26540
  timestamp: Date.now(),
26448
26541
  uptime: process.uptime(),
26449
26542
  engine: "agent",
26450
- version: HALO_VERSION
26543
+ version: HALO_VERSION,
26544
+ gitSha: GIT_SHA
26451
26545
  });
26452
26546
  });
26453
26547
  fileRoutes = createFileRoutes();
@@ -27268,7 +27362,7 @@ import path62 from "node:path";
27268
27362
  import fs54 from "node:fs";
27269
27363
  import { homedir as homedir25 } from "node:os";
27270
27364
  import { parseArgs as parseArgs2 } from "node:util";
27271
- var VERSION = "0.2.3";
27365
+ var VERSION = "0.2.5";
27272
27366
  var HELP_TOP = `Usage: halo <command> [options]
27273
27367
 
27274
27368
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",