@turmind/halo 0.2.6 → 1.0.0

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
@@ -433,6 +433,7 @@ __export(config_exports, {
433
433
  reloadSandboxConfig: () => reloadSandboxConfig,
434
434
  resolveApiKey: () => resolveApiKey,
435
435
  resolveAwsCredentials: () => resolveAwsCredentials,
436
+ resolveContextWindow: () => resolveContextWindow,
436
437
  resolveMaxOutputTokens: () => resolveMaxOutputTokens,
437
438
  resolveThinkingMode: () => resolveThinkingMode,
438
439
  resolveVerbosity: () => resolveVerbosity
@@ -656,6 +657,10 @@ function resolveMaxOutputTokens(modelId) {
656
657
  const entry = findModelEntry(modelId);
657
658
  return entry?.maxOutputTokens ?? 16384;
658
659
  }
660
+ function resolveContextWindow(modelId) {
661
+ const entry = findModelEntry(modelId);
662
+ return entry?.contextWindow;
663
+ }
659
664
  function resolveApiKey(providerId, workspaceRoot) {
660
665
  return getServerSecret(providerId, "api_key", workspaceRoot) || void 0;
661
666
  }
@@ -2201,7 +2206,7 @@ var init_init = __esm({
2201
2206
  path6.resolve(__dirname, "..", "bundled-docs"),
2202
2207
  path6.resolve(__dirname, "..", "..", "..", ".halo", "docs")
2203
2208
  ];
2204
- TEMPLATE_VERSION = 44;
2209
+ TEMPLATE_VERSION = 47;
2205
2210
  VERSION_FILE = ".template-version";
2206
2211
  SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
2207
2212
  BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
@@ -3767,6 +3772,84 @@ var init_enqueue = __esm({
3767
3772
  }
3768
3773
  });
3769
3774
 
3775
+ // ../server/dist/channels/shared/media-store.js
3776
+ import fs13 from "node:fs/promises";
3777
+ import path13 from "node:path";
3778
+ import { randomBytes } from "node:crypto";
3779
+ function inferImageMime(buf) {
3780
+ if (buf.length >= 4 && buf[0] === 255 && buf[1] === 216)
3781
+ return "image/jpeg";
3782
+ if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71)
3783
+ return "image/png";
3784
+ if (buf.length >= 6 && buf[0] === 71 && buf[1] === 73 && buf[2] === 70)
3785
+ return "image/gif";
3786
+ if (buf.length >= 12 && buf.subarray(8, 12).toString("ascii") === "WEBP")
3787
+ return "image/webp";
3788
+ return "image/jpeg";
3789
+ }
3790
+ function inferExtFromBytes(buf) {
3791
+ if (buf.length >= 4 && buf[0] === 255 && buf[1] === 216)
3792
+ return ".jpg";
3793
+ if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71)
3794
+ return ".png";
3795
+ if (buf.length >= 6 && buf[0] === 71 && buf[1] === 73 && buf[2] === 70)
3796
+ return ".gif";
3797
+ if (buf.length >= 12 && buf.subarray(8, 12).toString("ascii") === "WEBP")
3798
+ return ".webp";
3799
+ if (buf.length >= 4 && buf.subarray(0, 4).toString("ascii") === "RIFF")
3800
+ return ".wav";
3801
+ if (buf.length >= 9 && buf.subarray(0, 9).toString("ascii") === "#!SILK_V3")
3802
+ return ".silk";
3803
+ if (buf.length >= 10 && buf[0] === 2 && buf.subarray(1, 10).toString("ascii") === "#!SILK_V3")
3804
+ return ".silk";
3805
+ return ".bin";
3806
+ }
3807
+ function sanitizeFilename(name) {
3808
+ return name.replace(/[^\w.-]+/g, "_").slice(0, 120) || "file";
3809
+ }
3810
+ async function saveInboundMedia(params) {
3811
+ const date = /* @__PURE__ */ new Date();
3812
+ const yyyy = date.getFullYear();
3813
+ const mm = String(date.getMonth() + 1).padStart(2, "0");
3814
+ const dd = String(date.getDate()).padStart(2, "0");
3815
+ const channel = params.channel ?? "weixin";
3816
+ const dir = path13.join(params.workspacePath, ".halo", "assets", channel, "inbound", params.accountId, `${yyyy}-${mm}-${dd}`);
3817
+ await fs13.mkdir(dir, { recursive: true });
3818
+ const ts = `${date.getHours().toString().padStart(2, "0")}${date.getMinutes().toString().padStart(2, "0")}${date.getSeconds().toString().padStart(2, "0")}`;
3819
+ const suffix = randomBytes(3).toString("hex");
3820
+ let filename;
3821
+ if (params.originalFilename) {
3822
+ filename = `${ts}_${suffix}_${sanitizeFilename(params.originalFilename)}`;
3823
+ } else {
3824
+ const ext = params.mimeType ? EXT_BY_MIME[params.mimeType] ?? inferExtFromBytes(params.buffer) : inferExtFromBytes(params.buffer);
3825
+ filename = `${params.kind}_${ts}_${suffix}${ext}`;
3826
+ }
3827
+ const fullPath = path13.join(dir, filename);
3828
+ await fs13.writeFile(fullPath, params.buffer);
3829
+ return fullPath;
3830
+ }
3831
+ var VISION_IMAGE_MIME_TYPES, EXT_BY_MIME;
3832
+ var init_media_store = __esm({
3833
+ "../server/dist/channels/shared/media-store.js"() {
3834
+ "use strict";
3835
+ VISION_IMAGE_MIME_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
3836
+ EXT_BY_MIME = {
3837
+ "image/jpeg": ".jpg",
3838
+ "image/png": ".png",
3839
+ "image/gif": ".gif",
3840
+ "image/webp": ".webp",
3841
+ "video/mp4": ".mp4",
3842
+ "audio/silk": ".silk",
3843
+ "audio/wav": ".wav",
3844
+ "audio/amr": ".amr",
3845
+ "audio/webm": ".webm",
3846
+ "audio/ogg": ".ogg",
3847
+ "audio/mp4": ".m4a",
3848
+ "application/pdf": ".pdf"
3849
+ };
3850
+ }
3851
+ });
3852
+
3770
3853
  // ../server/dist/sessions/ui-log-builder.js
3771
3854
  import { randomUUID } from "node:crypto";
3772
3855
  function createEmptyUIState() {
@@ -4191,7 +4274,7 @@ var init_ui_log_builder = __esm({
4191
4274
 
4192
4275
  // ../server/dist/agents/session-ui-store.js
4193
4276
  import fsSync4 from "node:fs";
4194
- import path13 from "node:path";
4277
+ import path14 from "node:path";
4195
4278
  import { eq as eq3 } from "drizzle-orm";
4196
4279
  var SessionUIStore;
4197
4280
  var init_session_ui_store = __esm({
@@ -4371,7 +4454,7 @@ var init_session_ui_store = __esm({
4371
4454
  if (messages2.length > 0)
4372
4455
  state.messageLog = messages2;
4373
4456
  try {
4374
- const filePath = path13.join(getSessionDir(row.agentId, this.host.workspaceRoot), `${fileSegment(rootId)}.json`);
4457
+ const filePath = path14.join(getSessionDir(row.agentId, this.host.workspaceRoot), `${fileSegment(rootId)}.json`);
4375
4458
  const data = JSON.parse(fsSync4.readFileSync(filePath, "utf-8"));
4376
4459
  state.contextTokens = data.contextTokens ?? 0;
4377
4460
  state.outputTokens = data.totalOutputTokens ?? 0;
@@ -5160,8 +5243,14 @@ var init_kimi_agent = __esm({
5160
5243
  ...tools.length > 0 ? { tools } : {},
5161
5244
  prompt_cache_key: this.config.cacheKey ?? void 0
5162
5245
  };
5163
- if (this.config.thinking?.effort === "disabled") {
5164
- body.thinking = { type: "disabled" };
5246
+ if (this.config.modelId.startsWith("kimi-k3")) {
5247
+ if (this.config.thinking?.enabled) {
5248
+ body.reasoning_effort = "max";
5249
+ }
5250
+ } else {
5251
+ if (this.config.thinking?.effort === "disabled") {
5252
+ body.thinking = { type: "disabled" };
5253
+ }
5165
5254
  }
5166
5255
  const response = await fetch(url, {
5167
5256
  method: "POST",
@@ -5452,9 +5541,13 @@ var init_minimax_agent = __esm({
5452
5541
  body.messages = msgs;
5453
5542
  }
5454
5543
  if (this.config.thinking?.enabled && this.config.thinking.effort) {
5455
- const budget = this.config.thinkingBudgetTokens ?? effortToBudget2(this.config.thinking.effort, this.config.maxTokens);
5456
- const cappedBudget = typeof this.config.maxTokens === "number" && this.config.maxTokens > 0 ? Math.min(budget, Math.max(1024, Math.floor(this.config.maxTokens / 2))) : budget;
5457
- body.thinking = { type: "enabled", budget_tokens: cappedBudget };
5544
+ if (this.config.thinkingMode === "adaptive") {
5545
+ body.thinking = { type: "adaptive" };
5546
+ } else {
5547
+ const budget = this.config.thinkingBudgetTokens ?? effortToBudget2(this.config.thinking.effort, this.config.maxTokens);
5548
+ const cappedBudget = typeof this.config.maxTokens === "number" && this.config.maxTokens > 0 ? Math.min(budget, Math.max(1024, Math.floor(this.config.maxTokens / 2))) : budget;
5549
+ body.thinking = { type: "enabled", budget_tokens: cappedBudget };
5550
+ }
5458
5551
  }
5459
5552
  return body;
5460
5553
  }
@@ -6149,14 +6242,11 @@ function extractRegionFromHost(hostname) {
6149
6242
  const m = hostname.match(/bedrock-mantle\.([a-z0-9-]+)\.api\.aws/);
6150
6243
  return m?.[1] ?? "us-east-2";
6151
6244
  }
6152
- function strictParseOrNull(json) {
6153
- const trimmed = (json ?? "").trim();
6154
- if (trimmed === "")
6155
- return {};
6245
+ function safeParse6(json) {
6156
6246
  try {
6157
- return JSON.parse(trimmed);
6247
+ return JSON.parse(json || "{}");
6158
6248
  } catch {
6159
- return null;
6249
+ return {};
6160
6250
  }
6161
6251
  }
6162
6252
  var MantleAgent;
@@ -6208,15 +6298,12 @@ var init_mantle_agent = __esm({
6208
6298
  for (const item of output) {
6209
6299
  const itemType = item.type;
6210
6300
  if (itemType === "message") {
6211
- let itemText = "";
6212
6301
  const content = item.content ?? [];
6213
6302
  for (const part of content) {
6214
6303
  if (part.type === "output_text" && typeof part.text === "string") {
6215
- itemText += part.text;
6304
+ text5 += part.text;
6216
6305
  }
6217
6306
  }
6218
- if (itemText)
6219
- text5 = itemText;
6220
6307
  } else if (itemType === "reasoning") {
6221
6308
  const summary = item.summary ?? [];
6222
6309
  for (const part of summary) {
@@ -6227,11 +6314,9 @@ var init_mantle_agent = __esm({
6227
6314
  const callId = item.call_id ?? item.id ?? "";
6228
6315
  const name = item.name ?? "";
6229
6316
  const args = item.arguments ?? "{}";
6230
- const parsed = strictParseOrNull(args);
6231
- if (parsed === null)
6232
- continue;
6233
- toolCalls.push({ id: callId, name, input: parsed });
6234
- assistantBlocks.push({ type: "tool_use", id: callId, name, input: parsed });
6317
+ const input2 = safeParse6(args);
6318
+ toolCalls.push({ id: callId, name, input: input2 });
6319
+ assistantBlocks.push({ type: "tool_use", id: callId, name, input: input2 });
6235
6320
  }
6236
6321
  }
6237
6322
  if (output.length === 0 && !text5 && toolCalls.length === 0) {
@@ -6557,6 +6642,7 @@ function createModelRuntime(providerId, cfg) {
6557
6642
  maxTokens: cfg.maxTokens,
6558
6643
  promptCaching: cfg.promptCaching,
6559
6644
  thinking: cfg.thinking,
6645
+ thinkingMode: cfg.thinkingMode,
6560
6646
  thinkingBudgetTokens: cfg.thinkingBudgetTokens
6561
6647
  });
6562
6648
  case "qwen":
@@ -6662,7 +6748,7 @@ var init_model_runtime = __esm({
6662
6748
  // ../server/dist/tools/sandbox.js
6663
6749
  import { exec, execFile, spawn } from "node:child_process";
6664
6750
  import { promisify } from "node:util";
6665
- import path14 from "node:path";
6751
+ import path15 from "node:path";
6666
6752
  import { homedir as homedir8 } from "node:os";
6667
6753
  import { existsSync, realpathSync } from "node:fs";
6668
6754
  function decodeWinOutput(buf) {
@@ -6817,7 +6903,7 @@ async function initBwrapCheck() {
6817
6903
  return isBwrapAvailable();
6818
6904
  }
6819
6905
  function expandTilde(p) {
6820
- return p.startsWith("~/") ? path14.join(HOME, p.slice(2)) : p;
6906
+ return p.startsWith("~/") ? path15.join(HOME, p.slice(2)) : p;
6821
6907
  }
6822
6908
  function setSandboxHiddenPaths(dirs, files, writableDirs = []) {
6823
6909
  _hiddenDirs = dirs;
@@ -6858,13 +6944,13 @@ function buildBwrapArgs(opts) {
6858
6944
  return args;
6859
6945
  }
6860
6946
  function realpathBounded(filePath) {
6861
- let prefix = path14.resolve(filePath);
6947
+ let prefix = path15.resolve(filePath);
6862
6948
  const tail = [];
6863
6949
  while (!existsSync(prefix)) {
6864
- const parent = path14.dirname(prefix);
6950
+ const parent = path15.dirname(prefix);
6865
6951
  if (parent === prefix)
6866
6952
  break;
6867
- tail.unshift(path14.basename(prefix));
6953
+ tail.unshift(path15.basename(prefix));
6868
6954
  prefix = parent;
6869
6955
  }
6870
6956
  let realPrefix;
@@ -6873,12 +6959,12 @@ function realpathBounded(filePath) {
6873
6959
  } catch {
6874
6960
  realPrefix = prefix;
6875
6961
  }
6876
- return tail.length > 0 ? path14.join(realPrefix, ...tail) : realPrefix;
6962
+ return tail.length > 0 ? path15.join(realPrefix, ...tail) : realPrefix;
6877
6963
  }
6878
6964
  function assertPathAllowed(filePath, opts, write = false) {
6879
6965
  opts = normalizeOptsForPlatform(opts);
6880
6966
  if (opts.accessLevel === "full")
6881
- return path14.resolve(filePath);
6967
+ return path15.resolve(filePath);
6882
6968
  const resolved = realpathBounded(filePath);
6883
6969
  const wsRoot = realpathBounded(opts.workspaceRoot);
6884
6970
  if (resolved === wsRoot || resolved.startsWith(wsRoot + "/")) {
@@ -7044,7 +7130,7 @@ async function sandboxWriteFile(filePath, content, opts) {
7044
7130
  opts = normalizeOptsForPlatform(opts);
7045
7131
  if (opts.accessLevel === "full") {
7046
7132
  const fsP = await import("node:fs/promises");
7047
- await fsP.mkdir(path14.dirname(filePath), { recursive: true });
7133
+ await fsP.mkdir(path15.dirname(filePath), { recursive: true });
7048
7134
  await fsP.writeFile(filePath, content, "utf-8");
7049
7135
  return;
7050
7136
  }
@@ -7052,7 +7138,7 @@ async function sandboxWriteFile(filePath, content, opts) {
7052
7138
  if (!bwrapOk) {
7053
7139
  const safe = assertPathAllowed(filePath, opts, true);
7054
7140
  const fsP = await import("node:fs/promises");
7055
- await fsP.mkdir(path14.dirname(safe), { recursive: true });
7141
+ await fsP.mkdir(path15.dirname(safe), { recursive: true });
7056
7142
  await fsP.writeFile(safe, content, "utf-8");
7057
7143
  return;
7058
7144
  }
@@ -7114,8 +7200,8 @@ var init_sandbox = __esm({
7114
7200
  execAsync = promisify(exec);
7115
7201
  execFileAsync = promisify(execFile);
7116
7202
  HOME = homedir8();
7117
- HALO_HOME3 = path14.join(HOME, ".halo");
7118
- GLOBAL_DIR = path14.join(HALO_HOME3, "global");
7203
+ HALO_HOME3 = path15.join(HOME, ".halo");
7204
+ GLOBAL_DIR = path15.join(HALO_HOME3, "global");
7119
7205
  KILL_GRACE_MS = 2e3;
7120
7206
  _bwrapAvailable = null;
7121
7207
  DEFAULT_HIDDEN_DIRS = [
@@ -7156,83 +7242,6 @@ var init_sandbox = __esm({
7156
7242
  }
7157
7243
  });
7158
7244
 
7159
- // ../server/dist/channels/shared/media-store.js
7160
- import fs13 from "node:fs/promises";
7161
- import path15 from "node:path";
7162
- import { randomBytes } from "node:crypto";
7163
- function inferImageMime(buf) {
7164
- if (buf.length >= 4 && buf[0] === 255 && buf[1] === 216)
7165
- return "image/jpeg";
7166
- if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71)
7167
- return "image/png";
7168
- if (buf.length >= 6 && buf[0] === 71 && buf[1] === 73 && buf[2] === 70)
7169
- return "image/gif";
7170
- if (buf.length >= 12 && buf.subarray(8, 12).toString("ascii") === "WEBP")
7171
- return "image/webp";
7172
- return "image/jpeg";
7173
- }
7174
- function inferExtFromBytes(buf) {
7175
- if (buf.length >= 4 && buf[0] === 255 && buf[1] === 216)
7176
- return ".jpg";
7177
- if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71)
7178
- return ".png";
7179
- if (buf.length >= 6 && buf[0] === 71 && buf[1] === 73 && buf[2] === 70)
7180
- return ".gif";
7181
- if (buf.length >= 12 && buf.subarray(8, 12).toString("ascii") === "WEBP")
7182
- return ".webp";
7183
- if (buf.length >= 4 && buf.subarray(0, 4).toString("ascii") === "RIFF")
7184
- return ".wav";
7185
- if (buf.length >= 9 && buf.subarray(0, 9).toString("ascii") === "#!SILK_V3")
7186
- return ".silk";
7187
- if (buf.length >= 10 && buf[0] === 2 && buf.subarray(1, 10).toString("ascii") === "#!SILK_V3")
7188
- return ".silk";
7189
- return ".bin";
7190
- }
7191
- function sanitizeFilename(name) {
7192
- return name.replace(/[^\w.-]+/g, "_").slice(0, 120) || "file";
7193
- }
7194
- async function saveInboundMedia(params) {
7195
- const date = /* @__PURE__ */ new Date();
7196
- const yyyy = date.getFullYear();
7197
- const mm = String(date.getMonth() + 1).padStart(2, "0");
7198
- const dd = String(date.getDate()).padStart(2, "0");
7199
- const channel = params.channel ?? "weixin";
7200
- const dir = path15.join(params.workspacePath, ".halo", "assets", channel, "inbound", params.accountId, `${yyyy}-${mm}-${dd}`);
7201
- await fs13.mkdir(dir, { recursive: true });
7202
- const ts = `${date.getHours().toString().padStart(2, "0")}${date.getMinutes().toString().padStart(2, "0")}${date.getSeconds().toString().padStart(2, "0")}`;
7203
- const suffix = randomBytes(3).toString("hex");
7204
- let filename;
7205
- if (params.originalFilename) {
7206
- filename = `${ts}_${suffix}_${sanitizeFilename(params.originalFilename)}`;
7207
- } else {
7208
- const ext = params.mimeType ? EXT_BY_MIME[params.mimeType] ?? inferExtFromBytes(params.buffer) : inferExtFromBytes(params.buffer);
7209
- filename = `${params.kind}_${ts}_${suffix}${ext}`;
7210
- }
7211
- const fullPath = path15.join(dir, filename);
7212
- await fs13.writeFile(fullPath, params.buffer);
7213
- return fullPath;
7214
- }
7215
- var EXT_BY_MIME;
7216
- var init_media_store = __esm({
7217
- "../server/dist/channels/shared/media-store.js"() {
7218
- "use strict";
7219
- EXT_BY_MIME = {
7220
- "image/jpeg": ".jpg",
7221
- "image/png": ".png",
7222
- "image/gif": ".gif",
7223
- "image/webp": ".webp",
7224
- "video/mp4": ".mp4",
7225
- "audio/silk": ".silk",
7226
- "audio/wav": ".wav",
7227
- "audio/amr": ".amr",
7228
- "audio/webm": ".webm",
7229
- "audio/ogg": ".ogg",
7230
- "audio/mp4": ".m4a",
7231
- "application/pdf": ".pdf"
7232
- };
7233
- }
7234
- });
7235
-
7236
7245
  // ../server/dist/tools/workspace-tools.js
7237
7246
  import fs14 from "node:fs/promises";
7238
7247
  import path16 from "node:path";
@@ -8418,7 +8427,8 @@ do delegate, say so in one line and keep going.`;
8418
8427
  buildModelRuntime(args) {
8419
8428
  const { yamlConfig, modelId, endpoint, providerId, sessionId, systemPrompt, tools } = args;
8420
8429
  const contextConfig = {
8421
- maxTokens: yamlConfig?.context?.maxTokens ?? config.model.maxContextTokens,
8430
+ // Priority: explicit agent.yaml config > model registry contextWindow > global default (200K).
8431
+ maxTokens: yamlConfig?.context?.maxTokens ?? resolveContextWindow(modelId) ?? config.model.maxContextTokens,
8422
8432
  compressAt: yamlConfig?.context?.compressAt ?? config.model.compressAt
8423
8433
  };
8424
8434
  const rawCaching = yamlConfig?.model?.promptCaching;
@@ -9076,6 +9086,9 @@ function simpleHash(str) {
9076
9086
  function sleep(ms) {
9077
9087
  return new Promise((resolve2) => setTimeout(resolve2, ms));
9078
9088
  }
9089
+ function abortReason(reason) {
9090
+ return new DOMException(reason, "AbortError");
9091
+ }
9079
9092
  function estimateMessageTokens(messages2) {
9080
9093
  let chars = 0;
9081
9094
  for (const m of messages2) {
@@ -9105,6 +9118,34 @@ function isRawTurnStart(msg) {
9105
9118
  function stripImageMarkers(s) {
9106
9119
  return s.replace(/\[图片已保存: [^\]]*\]\n?/g, "").trim();
9107
9120
  }
9121
+ function replaceImageBlocks(messages2, reason) {
9122
+ let replaced = 0;
9123
+ const placeholder = () => ({ type: "text", text: `[image removed: ${reason}]` });
9124
+ for (const m of messages2) {
9125
+ if (!Array.isArray(m.content))
9126
+ continue;
9127
+ m.content = m.content.map((b) => {
9128
+ if (b.type === "image") {
9129
+ replaced++;
9130
+ return placeholder();
9131
+ }
9132
+ if (b.type === "tool_result" && Array.isArray(b.content)) {
9133
+ return {
9134
+ ...b,
9135
+ content: b.content.map((ib) => {
9136
+ if (ib.type === "image") {
9137
+ replaced++;
9138
+ return { type: "text", text: `[image removed: ${reason}]` };
9139
+ }
9140
+ return ib;
9141
+ })
9142
+ };
9143
+ }
9144
+ return b;
9145
+ });
9146
+ }
9147
+ return replaced;
9148
+ }
9108
9149
  function generateSessionId() {
9109
9150
  const ts = Date.now().toString(36);
9110
9151
  const rand = Math.random().toString(36).slice(2, 8);
@@ -9128,6 +9169,7 @@ var init_session_manager = __esm({
9128
9169
  init_workspace_runtime_lock();
9129
9170
  init_enqueue();
9130
9171
  init_session_store();
9172
+ init_media_store();
9131
9173
  init_ui_log_builder();
9132
9174
  init_session_ui_store();
9133
9175
  init_session_query_store();
@@ -9790,7 +9832,7 @@ var init_session_manager = __esm({
9790
9832
  this.processSessionEvent(session, event);
9791
9833
  if (session.interruptRequested && event.type === "tool_result") {
9792
9834
  console.debug(`[SessionManager] Graceful interrupt after tool result in ${session.id}`);
9793
- session.abortController?.abort("interrupt");
9835
+ session.abortController?.abort(abortReason("interrupt"));
9794
9836
  this.markPendingToolCallsInterrupted(session.id);
9795
9837
  }
9796
9838
  }
@@ -9877,6 +9919,20 @@ var init_session_manager = __esm({
9877
9919
  }
9878
9920
  this.emitEvent(session.id, { type: "system", text: "Conversation repair failed. Use /new to start a fresh session." });
9879
9921
  }
9922
+ if (httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500 && (msg.includes("Multimodal data is corrupted") || msg.includes("Could not process image"))) {
9923
+ const replaced = replaceImageBlocks(session.agent.messages, "rejected by model provider");
9924
+ if (replaced > 0) {
9925
+ if (Array.isArray(message)) {
9926
+ message = message.map((b) => b.type === "image" ? { type: "text", text: "[image removed: rejected by model provider]" } : b);
9927
+ }
9928
+ this.saveAgentState(session);
9929
+ console.warn(`[SessionManager] Session ${session.id} multimodal 4xx \u2014 replaced ${replaced} image block(s) with placeholders (attempt ${attempt + 1}/${maxRetries})`);
9930
+ if (attempt + 1 < maxRetries) {
9931
+ this.emitEvent(session.id, { type: "system", text: `Model rejected image data \u2014 removed ${replaced} image(s) from history, retrying...` });
9932
+ continue;
9933
+ }
9934
+ }
9935
+ }
9880
9936
  const errDetail = [errName, httpStatus ? `HTTP ${httpStatus}` : ""].filter(Boolean).join(" ");
9881
9937
  console.error(`[SessionManager] Session ${session.id} error (attempt ${attempt + 1}/${maxRetries})${errDetail ? ` [${errDetail}]` : ""}: ${msg}`);
9882
9938
  this.emitEvent(session.id, { type: "error", error: msg, agentName: session.agentName, taskId: session.parentId ? session.id : void 0 });
@@ -10113,7 +10169,7 @@ ${q.text}` : q.text).join("\n\n");
10113
10169
  if (!session?.abortController)
10114
10170
  return;
10115
10171
  session.interruptRequested = true;
10116
- session.abortController.abort("interrupt");
10172
+ session.abortController.abort(abortReason("interrupt"));
10117
10173
  session.abortController = null;
10118
10174
  this.markPendingToolCallsInterrupted(sessionId);
10119
10175
  console.debug(`[SessionManager] Interrupted session ${sessionId}`);
@@ -10164,7 +10220,7 @@ ${q.text}` : q.text);
10164
10220
  session.messageQueue = [];
10165
10221
  session.interruptRequested = false;
10166
10222
  if (session.abortController) {
10167
- session.abortController.abort("stop");
10223
+ session.abortController.abort(abortReason("stop"));
10168
10224
  session.abortController = null;
10169
10225
  this.markPendingToolCallsInterrupted(id);
10170
10226
  }
@@ -10338,8 +10394,18 @@ ${cleaned}` : cleaned;
10338
10394
  if (!supportsImage) {
10339
10395
  return text5 + "\n\n[\u6CE8\u610F\uFF1A\u7528\u6237\u53D1\u9001\u4E86\u56FE\u7247\uFF0C\u4F46\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u89C6\u89C9\u8F93\u5165\uFF0C\u56FE\u7247\u5DF2\u88AB\u5FFD\u7565]";
10340
10396
  }
10397
+ const accepted = images.filter((img) => VISION_IMAGE_MIME_TYPES.includes(img.mimeType));
10398
+ const dropped = images.length - accepted.length;
10399
+ if (dropped > 0) {
10400
+ console.warn(`[SessionManager] buildInput: dropped ${dropped} image(s) with unsupported media_type (${images.filter((i) => !VISION_IMAGE_MIME_TYPES.includes(i.mimeType)).map((i) => i.mimeType || "(empty)").join(", ")})`);
10401
+ text5 += `
10402
+
10403
+ [\u6CE8\u610F\uFF1A${dropped} \u5F20\u56FE\u7247\u683C\u5F0F\u4E0D\u53D7\u652F\u6301\uFF08\u4EC5\u652F\u6301 jpeg/png/gif/webp\uFF09\uFF0C\u5DF2\u88AB\u5FFD\u7565]`;
10404
+ }
10405
+ if (accepted.length === 0)
10406
+ return text5;
10341
10407
  const contentBlocks = [];
10342
- for (const img of images) {
10408
+ for (const img of accepted) {
10343
10409
  contentBlocks.push({
10344
10410
  type: "image",
10345
10411
  source: { type: "base64", media_type: img.mimeType, data: img.data }
@@ -10449,7 +10515,10 @@ ${result.summary}`, agentName: "user", report: true, taskId });
10449
10515
  return { maxTokens: config.model.maxContextTokens, compressAt: config.model.compressAt };
10450
10516
  const yamlConfig = await loadAgentYaml(row.agentId, this.workspaceRoot);
10451
10517
  return {
10452
- maxTokens: yamlConfig?.context?.maxTokens ?? config.model.maxContextTokens,
10518
+ // Same priority as buildModelRuntime: agent.yaml > model registry
10519
+ // contextWindow > global default — keeps the cold-session display in
10520
+ // sync with what actually takes effect on resume.
10521
+ maxTokens: yamlConfig?.context?.maxTokens ?? (yamlConfig?.model?.id ? resolveContextWindow(yamlConfig.model.id) : void 0) ?? config.model.maxContextTokens,
10453
10522
  compressAt: yamlConfig?.context?.compressAt ?? config.model.compressAt
10454
10523
  };
10455
10524
  }
@@ -24496,7 +24565,7 @@ function createWebChannel(deps) {
24496
24565
  resolve2 = null;
24497
24566
  }
24498
24567
  });
24499
- const imageTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
24568
+ const imageTypes = VISION_IMAGE_MIME_TYPES;
24500
24569
  const realImages = [];
24501
24570
  const savedPaths = [];
24502
24571
  if (images && images.length > 0) {
@@ -27509,7 +27578,7 @@ var init_dist2 = __esm({
27509
27578
  init_git_ssh();
27510
27579
  __dirname3 = path62.dirname(fileURLToPath3(import.meta.url));
27511
27580
  PORT = config.server.port;
27512
- HALO_VERSION = "0.2.6";
27581
+ HALO_VERSION = "1.0.0";
27513
27582
  PROJECT_ROOT = findProjectRoot();
27514
27583
  HALO_HOME4 = path62.join(homedir24(), ".halo");
27515
27584
  GIT_SHA = readGitSha();
@@ -28404,7 +28473,7 @@ import path63 from "node:path";
28404
28473
  import fs55 from "node:fs";
28405
28474
  import { homedir as homedir25 } from "node:os";
28406
28475
  import { parseArgs as parseArgs2 } from "node:util";
28407
- var VERSION = "0.2.6";
28476
+ var VERSION = "1.0.0";
28408
28477
  var HELP_TOP = `Usage: halo <command> [options]
28409
28478
 
28410
28479
  Commands:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "0.2.6",
3
+ "version": "1.0.0",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,6 +27,7 @@ models:
27
27
  - id: claude-sonnet-4-6
28
28
  displayName: Claude Sonnet 4.6 (placeholder)
29
29
  maxOutputTokens: 64000
30
+ contextWindow: 1000000
30
31
  capabilities:
31
32
  image: true
32
33
  video: false
@@ -38,6 +38,7 @@ models:
38
38
  - id: global.anthropic.claude-fable-5
39
39
  displayName: Claude Fable 5
40
40
  maxOutputTokens: 128000
41
+ contextWindow: 1000000
41
42
  capabilities:
42
43
  image: true
43
44
  video: false
@@ -65,6 +66,7 @@ models:
65
66
  - id: global.anthropic.claude-sonnet-4-6
66
67
  displayName: Claude Sonnet 4.6
67
68
  maxOutputTokens: 64000
69
+ contextWindow: 1000000
68
70
  capabilities:
69
71
  image: true
70
72
  video: false
@@ -91,6 +93,7 @@ models:
91
93
  - id: global.anthropic.claude-sonnet-5
92
94
  displayName: Claude Sonnet 5
93
95
  maxOutputTokens: 64000
96
+ contextWindow: 1000000
94
97
  capabilities:
95
98
  image: true
96
99
  video: false
@@ -117,6 +120,7 @@ models:
117
120
  - id: global.anthropic.claude-opus-4-6-v1
118
121
  displayName: Claude Opus 4.6
119
122
  maxOutputTokens: 128000
123
+ contextWindow: 1000000
120
124
  capabilities:
121
125
  image: true
122
126
  video: false
@@ -139,6 +143,7 @@ models:
139
143
  - id: global.anthropic.claude-opus-4-7
140
144
  displayName: Claude Opus 4.7
141
145
  maxOutputTokens: 128000
146
+ contextWindow: 1000000
142
147
  capabilities:
143
148
  image: true
144
149
  video: false
@@ -162,6 +167,7 @@ models:
162
167
  - id: global.anthropic.claude-opus-4-8
163
168
  displayName: Claude Opus 4.8
164
169
  maxOutputTokens: 128000
170
+ contextWindow: 1000000
165
171
  capabilities:
166
172
  image: true
167
173
  video: false
@@ -187,6 +193,7 @@ models:
187
193
  - id: global.anthropic.claude-haiku-4-5-20251001-v1:0
188
194
  displayName: Claude Haiku 4.5
189
195
  maxOutputTokens: 64000
196
+ contextWindow: 200000
190
197
  capabilities:
191
198
  image: true
192
199
  video: false