@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/README.md +5 -5
- package/admin-out/404.html +1 -1
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +1 -1
- package/bundled-docs/design/agent.md +12 -2
- package/bundled-docs/design/session.md +9 -0
- package/bundled-docs/design/ws.md +4 -0
- package/bundled-docs/dev/add-model-provider.md +17 -9
- package/bundled-docs/dev/api.md +4 -0
- package/bundled-docs/dev/deploy.md +4 -8
- package/bundled-docs/dev/env.md +9 -4
- package/bundled-docs/dev/tools.md +4 -3
- package/bundled-docs/guide/cli.md +1 -0
- package/dist/index.js +189 -120
- package/package.json +1 -1
- package/templates/models/anthropic.yaml +1 -0
- package/templates/models/aws-bedrock-claude-invoke.yaml +7 -0
- package/templates/models/aws-bedrock-mantle.yaml +51 -11
- package/templates/models/deepseek.yaml +2 -0
- package/templates/models/doubao.yaml +4 -0
- package/templates/models/hunyuan.yaml +1 -0
- package/templates/models/kimi.yaml +49 -13
- package/templates/models/mimo-token-plan-china.yaml +2 -0
- package/templates/models/minimax.yaml +37 -5
- package/templates/models/openai.yaml +1 -0
- package/templates/models/qwen.yaml +2 -0
- /package/admin-out/_next/static/{TmICbj6oGkbeEcs8tFjd3 → gzvax-Lg7K157j0rQJHje}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{TmICbj6oGkbeEcs8tFjd3 → gzvax-Lg7K157j0rQJHje}/_ssgManifest.js +0 -0
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
5164
|
-
|
|
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
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
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
|
|
6153
|
-
const trimmed = (json ?? "").trim();
|
|
6154
|
-
if (trimmed === "")
|
|
6155
|
-
return {};
|
|
6245
|
+
function safeParse6(json) {
|
|
6156
6246
|
try {
|
|
6157
|
-
return JSON.parse(
|
|
6247
|
+
return JSON.parse(json || "{}");
|
|
6158
6248
|
} catch {
|
|
6159
|
-
return
|
|
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
|
-
|
|
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
|
|
6231
|
-
|
|
6232
|
-
|
|
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
|
|
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("~/") ?
|
|
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 =
|
|
6947
|
+
let prefix = path15.resolve(filePath);
|
|
6862
6948
|
const tail = [];
|
|
6863
6949
|
while (!existsSync(prefix)) {
|
|
6864
|
-
const parent =
|
|
6950
|
+
const parent = path15.dirname(prefix);
|
|
6865
6951
|
if (parent === prefix)
|
|
6866
6952
|
break;
|
|
6867
|
-
tail.unshift(
|
|
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 ?
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
7118
|
-
GLOBAL_DIR =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
28476
|
+
var VERSION = "1.0.0";
|
|
28408
28477
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
28409
28478
|
|
|
28410
28479
|
Commands:
|
package/package.json
CHANGED
|
@@ -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
|