@turmind/halo 1.6.5 → 1.6.7

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
@@ -2227,6 +2227,7 @@ __export(config_exports, {
2227
2227
  getServerParam: () => getServerParam,
2228
2228
  getServerSecret: () => getServerSecret,
2229
2229
  invalidateModelsRegistry: () => invalidateModelsRegistry,
2230
+ missingEnvRefs: () => missingEnvRefs,
2230
2231
  modelSupportsAudio: () => modelSupportsAudio,
2231
2232
  modelSupportsImage: () => modelSupportsImage,
2232
2233
  modelSupportsVideo: () => modelSupportsVideo,
@@ -2410,6 +2411,9 @@ function expandEnv(raw) {
2410
2411
  return "";
2411
2412
  return raw.replace(ENV_PATTERN2, (m, name) => process.env[name] ?? m);
2412
2413
  }
2414
+ function missingEnvRefs(raw) {
2415
+ return [...new Set([...raw.matchAll(ENV_PATTERN2)].map((m) => m[1]))].filter((name) => process.env[name] === void 0);
2416
+ }
2413
2417
  function getWorkspaceSettings(workspaceRoot) {
2414
2418
  const p = path11.join(workspaceRoot, ".halo", "settings.yaml");
2415
2419
  let mtimeMs = 0;
@@ -2900,6 +2904,8 @@ function classifyKind(msg, errName, httpStatus) {
2900
2904
  if (msg.includes("too many input tokens") || msg.includes("prompt_too_long") || msg.includes("ContextWindowOverflow") || msg.includes("Input is too long")) {
2901
2905
  return "context_overflow";
2902
2906
  }
2907
+ if (errName === "MissingEnvCredentialError")
2908
+ return "account";
2903
2909
  const isAccountError = httpStatus !== void 0 ? httpStatus === 401 || httpStatus === 402 || httpStatus === 403 : msg.includes("insufficient balance") || msg.includes("suspended") || msg.includes("invalid api key") || msg.includes("Invalid API Key") || msg.includes("Unauthorized") || msg.includes("authentication");
2904
2910
  if (isAccountError) {
2905
2911
  return "account";
@@ -2924,9 +2930,13 @@ function classifyKind(msg, errName, httpStatus) {
2924
2930
  }
2925
2931
  return "fatal";
2926
2932
  }
2933
+ var MissingEnvCredentialError;
2927
2934
  var init_model_error = __esm({
2928
2935
  "../server/dist/agents/model-error.js"() {
2929
2936
  "use strict";
2937
+ MissingEnvCredentialError = class extends Error {
2938
+ name = "MissingEnvCredentialError";
2939
+ };
2930
2940
  }
2931
2941
  });
2932
2942
 
@@ -4149,7 +4159,7 @@ var init_init = __esm({
4149
4159
  path14.resolve(__dirname, "..", "bundled-docs"),
4150
4160
  path14.resolve(__dirname, "..", "..", "..", ".halo", "docs")
4151
4161
  ];
4152
- TEMPLATE_VERSION = 102;
4162
+ TEMPLATE_VERSION = 105;
4153
4163
  VERSION_FILE = ".template-version";
4154
4164
  SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
4155
4165
  BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
@@ -13708,6 +13718,7 @@ var init_session_agent_builder = __esm({
13708
13718
  init_system_prompts();
13709
13719
  init_md_loader();
13710
13720
  init_config();
13721
+ init_model_error();
13711
13722
  init_agent_loader();
13712
13723
  init_db();
13713
13724
  init_skill_sources();
@@ -14023,6 +14034,16 @@ ${roster}`;
14023
14034
  apiKey,
14024
14035
  sessionId
14025
14036
  });
14037
+ const unresolved = [
14038
+ ...apiKey ? [{ key: "api_key", value: apiKey }] : [],
14039
+ ...awsCreds2 ? [{ key: "access_key_id", value: awsCreds2.accessKeyId }, { key: "secret_access_key", value: awsCreds2.secretAccessKey }] : []
14040
+ ].map((f) => ({ key: f.key, names: missingEnvRefs(f.value) })).filter((f) => f.names.length > 0);
14041
+ if (unresolved.length > 0) {
14042
+ const names = [...new Set(unresolved.flatMap((f) => f.names))];
14043
+ const message = `${providerId} ${unresolved.map((f) => `${f.key} uses ${f.names.map((n) => `<<${n}>>`).join(" ")}`).join(", ")}, but ${names.join(", ")} ${names.length > 1 ? "are" : "is"} not set in the Halo server's environment. Background servers (systemd, \`halo server start -d\`) and the desktop app opened from the Dock don't read ~/.bashrc / ~/.zshrc \u2014 enter the key in Settings, or add the variable to the server's environment and restart it (from a new terminal: \`halo server restart\`).`;
14044
+ const run = agent.run.bind(agent);
14045
+ agent.run = (input, options) => run(input, { ...options, beforeCallModel: () => Promise.reject(new MissingEnvCredentialError(message)) });
14046
+ }
14026
14047
  const thinkingEffort = thinkingConfig?.enabled ? thinkingConfig.effort ?? thinkingConfig.budget ?? (thinkingConfig.budget_tokens != null ? `${thinkingConfig.budget_tokens}` : "medium") : "off";
14027
14048
  return { agent, thinkingEffort, contextConfig };
14028
14049
  }
@@ -14303,7 +14324,7 @@ async function initObservability(haloProcess = "server") {
14303
14324
  return true;
14304
14325
  }
14305
14326
  const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
14306
- providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "1.6.5", haloProcess);
14327
+ providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "1.6.7", haloProcess);
14307
14328
  console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, halo.process=${haloProcess}, capture_content=${config.observability.captureContent})`);
14308
14329
  return true;
14309
14330
  }
@@ -23792,7 +23813,8 @@ function fieldFor(section, f, globalTree, workspaceTree) {
23792
23813
  value,
23793
23814
  hasValue: raw !== void 0,
23794
23815
  source,
23795
- inheritedFromGlobal
23816
+ inheritedFromGlobal,
23817
+ envMissing: raw === void 0 ? [] : missingEnvRefs(raw)
23796
23818
  };
23797
23819
  }
23798
23820
  function detectOrphans(tree, declared) {
@@ -23953,6 +23975,7 @@ var init_settings = __esm({
23953
23975
  "use strict";
23954
23976
  init_settings_schema();
23955
23977
  init_workspace_path();
23978
+ init_config();
23956
23979
  GLOBAL_SETTINGS_PATH2 = path64.join(homedir22(), ".halo", "secrets", "settings.yaml");
23957
23980
  _changeListeners = [];
23958
23981
  }
@@ -25556,10 +25579,27 @@ var init_session_types = __esm({
25556
25579
  }
25557
25580
  });
25558
25581
 
25582
+ // ../server/dist/routes/etag.js
25583
+ function etagMatches(header, etag) {
25584
+ if (!header)
25585
+ return false;
25586
+ const bare = etag.replace(/^W\//, "");
25587
+ return header.split(",").some((t2) => {
25588
+ const tag = t2.trim();
25589
+ return tag === "*" || tag.replace(/^W\//, "") === bare;
25590
+ });
25591
+ }
25592
+ var init_etag = __esm({
25593
+ "../server/dist/routes/etag.js"() {
25594
+ "use strict";
25595
+ }
25596
+ });
25597
+
25559
25598
  // ../server/dist/routes/halo-city.js
25560
25599
  import { Hono as Hono12 } from "hono";
25561
25600
  import fs59 from "node:fs";
25562
25601
  import path67 from "node:path";
25602
+ import crypto6 from "node:crypto";
25563
25603
  import Database7 from "better-sqlite3";
25564
25604
  function scanSkills2(dir) {
25565
25605
  let names;
@@ -25843,6 +25883,10 @@ function discoverWorkspaces(registry2) {
25843
25883
  }
25844
25884
  return out;
25845
25885
  }
25886
+ function stateEtag(accessLevel, skills, workspaces, uptime) {
25887
+ const json = JSON.stringify({ accessLevel, skills, workspaces, uptimeMin: Math.floor(uptime / 60) });
25888
+ return `W/"${crypto6.createHash("sha1").update(json).digest("base64url")}"`;
25889
+ }
25846
25890
  function createShowRoutes(registry2) {
25847
25891
  const app2 = new Hono12();
25848
25892
  function auth(c) {
@@ -25875,11 +25919,18 @@ function createShowRoutes(registry2) {
25875
25919
  }
25876
25920
  }
25877
25921
  workspaces.sort((x, y) => workspaceBirth(x.path) - workspaceBirth(y.path) || x.key.localeCompare(y.key));
25922
+ const uptime = process.uptime();
25923
+ const skills = scanSkills2(GLOBAL_SKILLS_DIR);
25924
+ const etag = stateEtag(a.accessLevel, skills, workspaces, uptime);
25925
+ c.header("ETag", etag);
25926
+ c.header("Cache-Control", "private, no-cache");
25927
+ if (etagMatches(c.req.header("if-none-match"), etag))
25928
+ return c.body(null, 304);
25878
25929
  return c.json({
25879
25930
  serverTime: Date.now(),
25880
- uptime: process.uptime(),
25931
+ uptime,
25881
25932
  accessLevel: a.accessLevel,
25882
- skills: scanSkills2(GLOBAL_SKILLS_DIR),
25933
+ skills,
25883
25934
  workspaces
25884
25935
  });
25885
25936
  });
@@ -25957,6 +26008,7 @@ var init_halo_city = __esm({
25957
26008
  init_session_store();
25958
26009
  init_session_types();
25959
26010
  init_mtime_cache();
26011
+ init_etag();
25960
26012
  SESSIONS_PER_WS = 80;
25961
26013
  _skillCache = createMtimeCache();
25962
26014
  _metaCache = /* @__PURE__ */ new Map();
@@ -28125,7 +28177,9 @@ function setupWebSocketHandler(deps) {
28125
28177
  sendJson(ws, { type: "error", error: "No workspace context for delete", sessionId: delSessionId });
28126
28178
  return;
28127
28179
  }
28128
- await sm.deleteSession(delSessionId);
28180
+ for (const id of await sm.deleteSession(delSessionId)) {
28181
+ await findAndDeleteSessionFile(id, sm.workspaceRoot);
28182
+ }
28129
28183
  client.subscriptions.get(delSessionId)?.();
28130
28184
  client.subscriptions.delete(delSessionId);
28131
28185
  sendJson(ws, { type: "session:deleted", sessionId: delSessionId });
@@ -28876,7 +28930,7 @@ CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
28876
28930
  // ../server/dist/a2a/exposure.js
28877
28931
  import fs65 from "node:fs";
28878
28932
  import path74 from "node:path";
28879
- import crypto6 from "node:crypto";
28933
+ import crypto7 from "node:crypto";
28880
28934
  function samePath(a, b, platform = process.platform) {
28881
28935
  if (platform !== "win32")
28882
28936
  return a === b;
@@ -28942,7 +28996,7 @@ function buildCard(workspace, interfaceUrl, caps) {
28942
28996
  return {
28943
28997
  name: raw.name,
28944
28998
  description: raw.description,
28945
- version: typeof raw.version === "string" ? raw.version : "1.6.5",
28999
+ version: typeof raw.version === "string" ? raw.version : "1.6.7",
28946
29000
  supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
28947
29001
  capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
28948
29002
  ...caps.tokenAuth === false ? {} : {
@@ -28958,7 +29012,7 @@ function buildCard(workspace, interfaceUrl, caps) {
28958
29012
  };
28959
29013
  }
28960
29014
  function etagOf(body) {
28961
- return `"${crypto6.createHash("sha1").update(body).digest("base64url")}"`;
29015
+ return `W/"${crypto7.createHash("sha1").update(body).digest("base64url")}"`;
28962
29016
  }
28963
29017
  var CARD_FILE, IO_MODES, baseCache, homeStrategies;
28964
29018
  var init_exposure = __esm({
@@ -29001,7 +29055,7 @@ var init_exposure = __esm({
29001
29055
  // ../server/dist/a2a/routes.js
29002
29056
  import { Hono as Hono16 } from "hono";
29003
29057
  import { streamSSE } from "hono/streaming";
29004
- import crypto7 from "node:crypto";
29058
+ import crypto8 from "node:crypto";
29005
29059
  function str(v) {
29006
29060
  return typeof v === "string" ? v : "";
29007
29061
  }
@@ -29111,7 +29165,7 @@ async function dispatchMessage(x, p) {
29111
29165
  }
29112
29166
  const newSession = !sessionId;
29113
29167
  if (newSession)
29114
- sessionId = `${sessionPrefixFor(x.caller)}${Date.now().toString(36)}${crypto7.randomBytes(3).toString("hex")}`;
29168
+ sessionId = `${sessionPrefixFor(x.caller)}${Date.now().toString(36)}${crypto8.randomBytes(3).toString("hex")}`;
29115
29169
  const followUp = task !== null;
29116
29170
  if (!task) {
29117
29171
  try {
@@ -29315,7 +29369,7 @@ function createA2ARoutes(deps) {
29315
29369
  const etag = etagOf(body);
29316
29370
  c.header("Cache-Control", "private, max-age=300");
29317
29371
  c.header("ETag", etag);
29318
- if (c.req.header("if-none-match") === etag)
29372
+ if (etagMatches(c.req.header("if-none-match"), etag))
29319
29373
  return c.body(null, 304);
29320
29374
  return c.body(body, 200, { "Content-Type": "application/json" });
29321
29375
  });
@@ -29390,6 +29444,7 @@ var init_routes = __esm({
29390
29444
  init_commands2();
29391
29445
  init_accounts();
29392
29446
  init_session_prefix();
29447
+ init_etag();
29393
29448
  init_url_policy();
29394
29449
  init_files();
29395
29450
  init_exposure();
@@ -29428,7 +29483,7 @@ var init_routes = __esm({
29428
29483
  import fs66 from "node:fs";
29429
29484
  import os10 from "node:os";
29430
29485
  import path75 from "node:path";
29431
- import crypto8 from "node:crypto";
29486
+ import crypto9 from "node:crypto";
29432
29487
  import { Hono as Hono17 } from "hono";
29433
29488
  function readLease(file) {
29434
29489
  let raw;
@@ -29611,7 +29666,7 @@ var init_agentcore = __esm({
29611
29666
  WorkspaceLease = class {
29612
29667
  file;
29613
29668
  hooks;
29614
- owner = crypto8.randomUUID();
29669
+ owner = crypto9.randomUUID();
29615
29670
  _held = false;
29616
29671
  timer = null;
29617
29672
  waitLogged = false;
@@ -32720,7 +32775,7 @@ var init_descriptor = __esm({
32720
32775
  });
32721
32776
 
32722
32777
  // ../server/dist/channels/wechat/api.js
32723
- import crypto9 from "node:crypto";
32778
+ import crypto10 from "node:crypto";
32724
32779
  function buildClientVersion(version) {
32725
32780
  const [ma, mi, pa] = version.split(".").map((p) => parseInt(p, 10));
32726
32781
  return (ma & 255) << 16 | (mi & 255) << 8 | pa & 255;
@@ -32729,7 +32784,7 @@ function buildBaseInfo() {
32729
32784
  return { channel_version: CHANNEL_VERSION };
32730
32785
  }
32731
32786
  function randomWechatUin() {
32732
- const n = crypto9.randomBytes(4).readUInt32BE(0);
32787
+ const n = crypto10.randomBytes(4).readUInt32BE(0);
32733
32788
  return Buffer.from(String(n), "utf-8").toString("base64");
32734
32789
  }
32735
32790
  function commonHeaders() {
@@ -34106,7 +34161,7 @@ var init_descriptor2 = __esm({
34106
34161
  // ../server/dist/routes/web.js
34107
34162
  import { Hono as Hono22 } from "hono";
34108
34163
  import { streamSSE as streamSSE2 } from "hono/streaming";
34109
- import crypto10 from "node:crypto";
34164
+ import crypto11 from "node:crypto";
34110
34165
  import fs77 from "node:fs";
34111
34166
  function streamWithKeepalive(c, chunks) {
34112
34167
  return streamSSE2(c, async (stream) => {
@@ -34144,8 +34199,8 @@ function createWebRoutes(deps) {
34144
34199
  const bodyError = accountBodyError(body, ACCOUNT_ACCESS_LEVELS);
34145
34200
  if (bodyError)
34146
34201
  return c.json({ error: bodyError }, 400);
34147
- const accountId = crypto10.randomUUID().slice(0, 8);
34148
- const token = crypto10.randomBytes(24).toString("base64url");
34202
+ const accountId = crypto11.randomUUID().slice(0, 8);
34203
+ const token = crypto11.randomBytes(24).toString("base64url");
34149
34204
  insertAccount2(db3, {
34150
34205
  accountId,
34151
34206
  token,
@@ -34483,7 +34538,7 @@ function formatForFeishu(input) {
34483
34538
  inFence = !inFence;
34484
34539
  continue;
34485
34540
  }
34486
- if (inFence) {
34541
+ if (inFence || /^MEDIA:/.test(raw)) {
34487
34542
  out.push(raw);
34488
34543
  continue;
34489
34544
  }
@@ -34508,16 +34563,16 @@ var init_markdown = __esm({
34508
34563
  });
34509
34564
 
34510
34565
  // ../server/dist/channels/slack/event-adapter.js
34511
- var HARD_CHARS, SlackResponder;
34566
+ var SLACK_TEXT_LIMIT, SlackResponder;
34512
34567
  var init_event_adapter3 = __esm({
34513
34568
  "../server/dist/channels/slack/event-adapter.js"() {
34514
34569
  "use strict";
34515
34570
  init_markdown();
34516
34571
  init_responder();
34517
- HARD_CHARS = 35e3;
34572
+ SLACK_TEXT_LIMIT = 35e3;
34518
34573
  SlackResponder = class extends ChunkedResponder {
34519
34574
  constructor(deps) {
34520
- super(deps, { limit: HARD_CHARS, logTag: "Slack", format: formatForSlack });
34575
+ super(deps, { limit: SLACK_TEXT_LIMIT, logTag: "Slack", format: formatForSlack });
34521
34576
  }
34522
34577
  };
34523
34578
  }
@@ -35253,18 +35308,17 @@ async function dispatch3(accountId, text6, explicitChatId, media) {
35253
35308
  const { channel, threadTs } = parseChatId(explicitChatId);
35254
35309
  const out = [];
35255
35310
  if (text6) {
35256
- try {
35257
- await postMessage({ botToken: acct.botToken, channel, threadTs, text: text6 });
35258
- out.push({ channelType: "slack", accountId, chatId: explicitChatId, ok: true });
35259
- } catch (err) {
35260
- out.push({
35261
- channelType: "slack",
35262
- accountId,
35263
- chatId: explicitChatId,
35264
- ok: false,
35265
- error: err instanceof Error ? err.message : String(err)
35266
- });
35311
+ const chunks = splitText(text6, SLACK_TEXT_LIMIT);
35312
+ let failed;
35313
+ for (const [i, chunk] of chunks.entries()) {
35314
+ try {
35315
+ await postMessage({ botToken: acct.botToken, channel, threadTs, text: formatForSlack(chunk) });
35316
+ } catch (err) {
35317
+ failed = `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`;
35318
+ break;
35319
+ }
35267
35320
  }
35321
+ out.push(failed ? { channelType: "slack", accountId, chatId: explicitChatId, ok: false, error: failed } : { channelType: "slack", accountId, chatId: explicitChatId, ok: true });
35268
35322
  }
35269
35323
  for (const filePath of media?.paths ?? []) {
35270
35324
  if (!isMediaPathAllowed(filePath, media.workspacePath)) {
@@ -35303,6 +35357,9 @@ var init_cron_dispatcher3 = __esm({
35303
35357
  init_channel_db();
35304
35358
  init_accounts5();
35305
35359
  init_api2();
35360
+ init_event_adapter3();
35361
+ init_chunk();
35362
+ init_markdown();
35306
35363
  init_media();
35307
35364
  init_dispatcher();
35308
35365
  }
@@ -35380,16 +35437,16 @@ var init_accounts6 = __esm({
35380
35437
  });
35381
35438
 
35382
35439
  // ../server/dist/channels/feishu/event-adapter.js
35383
- var HARD_CHARS2, FeishuResponder;
35440
+ var FEISHU_TEXT_LIMIT, FeishuResponder;
35384
35441
  var init_event_adapter4 = __esm({
35385
35442
  "../server/dist/channels/feishu/event-adapter.js"() {
35386
35443
  "use strict";
35387
35444
  init_markdown();
35388
35445
  init_responder();
35389
- HARD_CHARS2 = 4500;
35446
+ FEISHU_TEXT_LIMIT = 4500;
35390
35447
  FeishuResponder = class extends ChunkedResponder {
35391
35448
  constructor(deps) {
35392
- super(deps, { limit: HARD_CHARS2, logTag: "Feishu", format: formatForFeishu });
35449
+ super(deps, { limit: FEISHU_TEXT_LIMIT, logTag: "Feishu", format: formatForFeishu });
35393
35450
  }
35394
35451
  };
35395
35452
  }
@@ -36211,25 +36268,28 @@ async function dispatch4(accountId, text6, explicitChatId) {
36211
36268
  throw new Error('feishu cron target requires an explicit chatId (e.g. "oc_abcd1234"). Create the cron from inside a feishu chat to auto-pin, or pass --targets feishu:<accountId>:<chatId>.');
36212
36269
  }
36213
36270
  const { chatId } = parseChatId2(explicitChatId);
36214
- try {
36215
- await sendMessage3({
36216
- appId: acct.appId,
36217
- appSecret: acct.appSecret,
36218
- receiveIdType: "chat_id",
36219
- receiveId: chatId,
36220
- msgType: "text",
36221
- content: { text: text6 }
36222
- });
36223
- return [{ channelType: "feishu", accountId, chatId: explicitChatId, ok: true }];
36224
- } catch (err) {
36225
- return [{
36226
- channelType: "feishu",
36227
- accountId,
36228
- chatId: explicitChatId,
36229
- ok: false,
36230
- error: err instanceof Error ? err.message : String(err)
36231
- }];
36271
+ const chunks = splitText(text6, FEISHU_TEXT_LIMIT);
36272
+ for (const [i, chunk] of chunks.entries()) {
36273
+ try {
36274
+ await sendMessage3({
36275
+ appId: acct.appId,
36276
+ appSecret: acct.appSecret,
36277
+ receiveIdType: "chat_id",
36278
+ receiveId: chatId,
36279
+ msgType: "text",
36280
+ content: { text: formatForFeishu(chunk) }
36281
+ });
36282
+ } catch (err) {
36283
+ return [{
36284
+ channelType: "feishu",
36285
+ accountId,
36286
+ chatId: explicitChatId,
36287
+ ok: false,
36288
+ error: `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`
36289
+ }];
36290
+ }
36232
36291
  }
36292
+ return [{ channelType: "feishu", accountId, chatId: explicitChatId, ok: true }];
36233
36293
  }
36234
36294
  function listTargets4() {
36235
36295
  const cdb = getChannelDb();
@@ -36251,6 +36311,9 @@ var init_cron_dispatcher4 = __esm({
36251
36311
  init_channel_db();
36252
36312
  init_accounts6();
36253
36313
  init_api3();
36314
+ init_event_adapter4();
36315
+ init_chunk();
36316
+ init_markdown();
36254
36317
  init_dispatcher();
36255
36318
  }
36256
36319
  });
@@ -36324,15 +36387,15 @@ var init_accounts7 = __esm({
36324
36387
  });
36325
36388
 
36326
36389
  // ../server/dist/channels/wecom/event-adapter.js
36327
- var HARD_CHARS3, WecomResponder;
36390
+ var WECOM_TEXT_LIMIT, WecomResponder;
36328
36391
  var init_event_adapter5 = __esm({
36329
36392
  "../server/dist/channels/wecom/event-adapter.js"() {
36330
36393
  "use strict";
36331
36394
  init_responder();
36332
- HARD_CHARS3 = 5e3;
36395
+ WECOM_TEXT_LIMIT = 5e3;
36333
36396
  WecomResponder = class extends ChunkedResponder {
36334
36397
  constructor(deps) {
36335
- super(deps, { limit: HARD_CHARS3, logTag: "WeCom" });
36398
+ super(deps, { limit: WECOM_TEXT_LIMIT, logTag: "WeCom" });
36336
36399
  }
36337
36400
  };
36338
36401
  }
@@ -36838,18 +36901,21 @@ async function dispatch5(accountId, text6, explicitChatId) {
36838
36901
  if (!client) {
36839
36902
  return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: false, error: "wecom long-connect not active" }];
36840
36903
  }
36841
- try {
36842
- await client.sendMessage(explicitChatId, { msgtype: "markdown", markdown: { content: text6 } });
36843
- return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: true }];
36844
- } catch (err) {
36845
- return [{
36846
- channelType: "wecom",
36847
- accountId,
36848
- chatId: explicitChatId,
36849
- ok: false,
36850
- error: err instanceof Error ? err.message : String(err)
36851
- }];
36904
+ const chunks = splitText(text6, WECOM_TEXT_LIMIT);
36905
+ for (const [i, chunk] of chunks.entries()) {
36906
+ try {
36907
+ await client.sendMessage(explicitChatId, { msgtype: "markdown", markdown: { content: chunk } });
36908
+ } catch (err) {
36909
+ return [{
36910
+ channelType: "wecom",
36911
+ accountId,
36912
+ chatId: explicitChatId,
36913
+ ok: false,
36914
+ error: `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`
36915
+ }];
36916
+ }
36852
36917
  }
36918
+ return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: true }];
36853
36919
  }
36854
36920
  function listTargets5() {
36855
36921
  const cdb = getChannelDb();
@@ -36871,6 +36937,8 @@ var init_cron_dispatcher5 = __esm({
36871
36937
  init_channel_db();
36872
36938
  init_accounts7();
36873
36939
  init_handler7();
36940
+ init_event_adapter5();
36941
+ init_chunk();
36874
36942
  init_dispatcher();
36875
36943
  }
36876
36944
  });
@@ -36915,6 +36983,64 @@ var init_descriptors = __esm({
36915
36983
  }
36916
36984
  });
36917
36985
 
36986
+ // ../server/dist/middleware/compress.js
36987
+ import { compress, COMPRESSIBLE_CONTENT_TYPE_REGEX } from "hono/compress";
36988
+ function compressResponses(threshold) {
36989
+ const inner = compress({ threshold });
36990
+ return (c, next) => inner(c, async () => {
36991
+ await next();
36992
+ if (c.res.status === 304)
36993
+ varyAcceptEncoding(c);
36994
+ else
36995
+ await setProbedLength(c, threshold);
36996
+ });
36997
+ }
36998
+ function varyAcceptEncoding(c) {
36999
+ const vary = c.res.headers.get("Vary");
37000
+ if (vary === "*" || vary && VARY_ACCEPT_ENCODING_RE.test(vary))
37001
+ return;
37002
+ c.header("Vary", vary ? `${vary}, Accept-Encoding` : "Accept-Encoding");
37003
+ }
37004
+ async function setProbedLength(c, threshold) {
37005
+ const res = c.res;
37006
+ const type = res.headers.get("Content-Type");
37007
+ if (res.headers.has("Content-Length") || res.headers.has("Content-Encoding") || res.headers.has("Transfer-Encoding") || c.req.method === "HEAD" || res.status === 204 || res.status === 206 || !res.body || !type || !COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type))
37008
+ return;
37009
+ const reader = res.body.getReader();
37010
+ const head = [];
37011
+ let size = 0;
37012
+ while (size < threshold) {
37013
+ const { done, value } = await reader.read();
37014
+ if (done) {
37015
+ c.res = new Response(Buffer.concat(head, size), res);
37016
+ c.res.headers.set("Content-Length", String(size));
37017
+ return;
37018
+ }
37019
+ head.push(value);
37020
+ size += value.byteLength;
37021
+ }
37022
+ c.res = new Response(new ReadableStream({
37023
+ async pull(ctrl) {
37024
+ const chunk = head.shift();
37025
+ if (chunk)
37026
+ return ctrl.enqueue(chunk);
37027
+ const { done, value } = await reader.read();
37028
+ if (done)
37029
+ ctrl.close();
37030
+ else
37031
+ ctrl.enqueue(value);
37032
+ },
37033
+ cancel: (reason) => reader.cancel(reason)
37034
+ }), res);
37035
+ }
37036
+ var VARY_ACCEPT_ENCODING_RE;
37037
+ var init_compress = __esm({
37038
+ "../server/dist/middleware/compress.js"() {
37039
+ "use strict";
37040
+ VARY_ACCEPT_ENCODING_RE = /(?:^|,)\s*accept-encoding\s*(?:,|$)/i;
37041
+ }
37042
+ });
37043
+
36918
37044
  // ../server/dist/listen-banner.js
36919
37045
  function formatListenBanner(address, port, version) {
36920
37046
  if (address === "::" || address === "0.0.0.0") {
@@ -36936,7 +37062,6 @@ var init_listen_banner = __esm({
36936
37062
  var dist_exports = {};
36937
37063
  import { Hono as Hono26 } from "hono";
36938
37064
  import { cors } from "hono/cors";
36939
- import { compress } from "hono/compress";
36940
37065
  import { serve } from "@hono/node-server";
36941
37066
  import { serveStatic } from "@hono/node-server/serve-static";
36942
37067
  import { WebSocketServer as WebSocketServer4 } from "ws";
@@ -37128,6 +37253,7 @@ var init_dist2 = __esm({
37128
37253
  init_registry4();
37129
37254
  init_descriptors();
37130
37255
  init_auth();
37256
+ init_compress();
37131
37257
  init_logger();
37132
37258
  init_listen_banner();
37133
37259
  init_otel();
@@ -37137,7 +37263,7 @@ var init_dist2 = __esm({
37137
37263
  init_git_ssh();
37138
37264
  __dirname3 = path87.dirname(fileURLToPath3(import.meta.url));
37139
37265
  PORT = config.server.port;
37140
- HALO_VERSION = "1.6.5";
37266
+ HALO_VERSION = "1.6.7";
37141
37267
  PROJECT_ROOT = findProjectRoot();
37142
37268
  HALO_HOME4 = path87.join(homedir25(), ".halo");
37143
37269
  GIT_SHA = readGitSha();
@@ -37181,7 +37307,7 @@ var init_dist2 = __esm({
37181
37307
  maxAge: 86400,
37182
37308
  credentials: allowlist.length > 0
37183
37309
  }));
37184
- app.use("/*", compress({ threshold: 1024 }));
37310
+ app.use("/*", compressResponses(1024));
37185
37311
  jupyter = AGENTCORE ? null : createJupyterManager();
37186
37312
  if (!AGENTCORE) {
37187
37313
  app.use("/api/*", authMiddleware());
@@ -38747,7 +38873,7 @@ function installUserUnit(opts, run = runCommand) {
38747
38873
  }
38748
38874
 
38749
38875
  // src/index.ts
38750
- var VERSION = "1.6.5";
38876
+ var VERSION = "1.6.7";
38751
38877
  var HELP_TOP = `Usage: halo <command> [options]
38752
38878
 
38753
38879
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.6.5",
3
+ "version": "1.6.7",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  id: mimo-token-plan-china
2
2
  runtime: anthropic-messages
3
- revision: 2026101002
3
+ revision: 2026101401
4
4
  displayName: Xiaomi MiMo
5
5
  displayName_zh: 小米 MiMo
6
6
  description: Xiaomi MiMo via its Anthropic-compatible gateway — Token Plan (China / Singapore / Europe clusters) or pay-as-you-go. Supports 1M context window.
@@ -28,9 +28,8 @@ secrets:
28
28
  # gives `mimo-v2.6-pro` / `mimo-v2.6-flash` 1M context, 128K (131072) max
29
29
  # output, full-modal understanding (image input on both — V2.5 only had it on
30
30
  # the non-pro tier). `mimo-v2.6-pro-ultraspeed` is "contact us" only, not
31
- # listed. `mimo-v2.5-pro` / `mimo-v2.5` are deprecated 2026-10-21 10:00 CST —
32
- # kept at the tail (tagged) so agents still on them keep their registry
33
- # capabilities until the cutoff; drop both after that date. Thinking shape unchanged from V2.5 — Anthropic-protocol
31
+ # listed. `mimo-v2.5-pro` / `mimo-v2.5` (officially retired 2026-10-21) were
32
+ # dropped from this list on 2026-10-14. Thinking shape unchanged from V2.5 — Anthropic-protocol
34
33
  # `thinking.type` enabled/disabled, on by default; the docs say effort levels
35
34
  # are not differentiated yet, so the adaptive presets below are a UI knob the
36
35
  # gateway accepts, not a real intensity dial. Not live-probed (no valid key at
@@ -85,54 +84,3 @@ models:
85
84
  - { value: medium, label: Medium }
86
85
  - { value: high, label: High }
87
86
  - { value: max, label: Max }
88
-
89
- # --- deprecated 2026-10-21 10:00 CST (official notice) — remove after cutoff ---
90
- - id: mimo-v2.5-pro
91
- displayName: MiMo v2.5 Pro (deprecated 2026-10-21)
92
- maxOutputTokens: 131072
93
- contextWindow: 272000
94
- maxContextWindow: 1000000
95
- compressAt: 0.9
96
- capabilities:
97
- image: false
98
- video: false
99
- audio: false
100
- promptCaching:
101
- default: 1h
102
- ttlPresets:
103
- - { value: 5m, label: 5min }
104
- - { value: 1h, label: 1hour }
105
- thinking:
106
- mode: adaptive
107
- defaultEnabled: true
108
- default: high
109
- effortPresets:
110
- - { value: low, label: Low }
111
- - { value: medium, label: Medium }
112
- - { value: high, label: High }
113
- - { value: max, label: Max }
114
-
115
- - id: mimo-v2.5
116
- displayName: MiMo v2.5 (deprecated 2026-10-21)
117
- maxOutputTokens: 131072
118
- contextWindow: 272000
119
- maxContextWindow: 1000000
120
- compressAt: 0.9
121
- capabilities:
122
- image: true
123
- video: false
124
- audio: false
125
- promptCaching:
126
- default: 1h
127
- ttlPresets:
128
- - { value: 5m, label: 5min }
129
- - { value: 1h, label: 1hour }
130
- thinking:
131
- mode: adaptive
132
- defaultEnabled: true
133
- default: high
134
- effortPresets:
135
- - { value: low, label: Low }
136
- - { value: medium, label: Medium }
137
- - { value: high, label: High }
138
- - { value: max, label: Max }