@turmind/halo 1.3.3 → 1.3.4
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 +6 -3
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/989-e660d0dd799950f2.js +1 -0
- package/admin-out/_next/static/chunks/app/page-18ab83dbdcc3897c.js +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +4 -4
- package/bundled-docs/design/agent.md +2 -0
- package/bundled-docs/design/session.md +12 -2
- package/bundled-docs/dev/add-model-provider.md +4 -3
- package/bundled-docs/dev/tools.md +10 -2
- package/dist/index.js +166 -78
- package/package.json +1 -1
- package/templates/models/aws-bedrock-claude-invoke.yaml +29 -0
- package/templates/models/aws-bedrock-mantle.yaml +90 -10
- package/templates/models/aws-bedrock-openai.yaml +313 -0
- package/templates/models/deepseek.yaml +12 -7
- package/templates/models/mimo-token-plan-china.yaml +63 -5
- package/admin-out/_next/static/chunks/989-056ee2aecccce015.js +0 -1
- package/admin-out/_next/static/chunks/app/page-5ed9b114aa1a8c10.js +0 -1
- /package/admin-out/_next/static/{zi3QS64PlTtv0VfsmIYwZ → D2iJsfaULMDk0HYCHji6W}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{zi3QS64PlTtv0VfsmIYwZ → D2iJsfaULMDk0HYCHji6W}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -1627,83 +1627,90 @@ var init_agent_loop = __esm({
|
|
|
1627
1627
|
return;
|
|
1628
1628
|
const toolResults = [];
|
|
1629
1629
|
let shouldEndTurn = false;
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
Error: unknown tool "${tu.name}"`;
|
|
1643
|
-
resultText = resultContent;
|
|
1644
|
-
isError = true;
|
|
1645
|
-
} else {
|
|
1646
|
-
try {
|
|
1647
|
-
const raw = await toolDef.callback(tu.input, options?.cancelSignal);
|
|
1648
|
-
if (typeof raw === "string") {
|
|
1649
|
-
resultContent = raw;
|
|
1650
|
-
resultText = raw;
|
|
1651
|
-
} else {
|
|
1652
|
-
resultContent = raw;
|
|
1653
|
-
resultText = raw.map((b) => b.type === "text" ? b.text : `[image ${b.source.media_type}]`).join("\n");
|
|
1654
|
-
}
|
|
1655
|
-
} catch (err) {
|
|
1630
|
+
try {
|
|
1631
|
+
for (const tu of toolUseBlocks) {
|
|
1632
|
+
if (options?.cancelSignal?.aborted)
|
|
1633
|
+
return;
|
|
1634
|
+
const toolDef = this.toolMap.get(tu.name);
|
|
1635
|
+
if (toolDef?.forceEndTurn)
|
|
1636
|
+
shouldEndTurn = true;
|
|
1637
|
+
const startTime = Date.now();
|
|
1638
|
+
let resultContent;
|
|
1639
|
+
let resultText;
|
|
1640
|
+
let isError = false;
|
|
1641
|
+
if (!toolDef) {
|
|
1656
1642
|
resultContent = `${TOOL_ERROR_MARKER}
|
|
1657
|
-
Error: ${
|
|
1643
|
+
Error: unknown tool "${tu.name}"`;
|
|
1658
1644
|
resultText = resultContent;
|
|
1659
1645
|
isError = true;
|
|
1646
|
+
} else {
|
|
1647
|
+
try {
|
|
1648
|
+
const raw = await toolDef.callback(tu.input, options?.cancelSignal);
|
|
1649
|
+
if (typeof raw === "string") {
|
|
1650
|
+
resultContent = raw;
|
|
1651
|
+
resultText = raw;
|
|
1652
|
+
} else {
|
|
1653
|
+
resultContent = raw;
|
|
1654
|
+
resultText = raw.map((b) => b.type === "text" ? b.text : `[image ${b.source.media_type}]`).join("\n");
|
|
1655
|
+
}
|
|
1656
|
+
} catch (err) {
|
|
1657
|
+
resultContent = `${TOOL_ERROR_MARKER}
|
|
1658
|
+
Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
1659
|
+
resultText = resultContent;
|
|
1660
|
+
isError = true;
|
|
1661
|
+
}
|
|
1660
1662
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1663
|
+
if (options?.cancelSignal?.aborted)
|
|
1664
|
+
return;
|
|
1665
|
+
const uiCap = config.limits.toolResultUiMax;
|
|
1666
|
+
const resultTextFull = resultText.length > uiCap ? resultText.slice(0, uiCap) + `
|
|
1664
1667
|
|
|
1665
1668
|
[Content truncated: ${resultText.length} chars total, showing first ${uiCap}. Use file_read for the complete content.]` : resultText;
|
|
1666
|
-
|
|
1667
|
-
|
|
1669
|
+
const cap2 = tu.name === "activate_skill" ? Infinity : config.limits.toolResultMax;
|
|
1670
|
+
const truncationNote = (origLen) => `
|
|
1668
1671
|
|
|
1669
1672
|
[Content truncated: ${origLen} chars total, showing first ${cap2}. Re-run with narrower scope (e.g. grep / file_read with offset+limit) to see specific sections.]`;
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1673
|
+
if (typeof resultContent === "string" && resultContent.length > cap2) {
|
|
1674
|
+
const orig = resultContent.length;
|
|
1675
|
+
resultContent = resultContent.slice(0, cap2) + truncationNote(orig);
|
|
1676
|
+
resultText = resultContent;
|
|
1677
|
+
} else if (Array.isArray(resultContent)) {
|
|
1678
|
+
let mutated = false;
|
|
1679
|
+
resultContent = resultContent.map((b) => {
|
|
1680
|
+
if (b.type === "text" && b.text.length > cap2) {
|
|
1681
|
+
mutated = true;
|
|
1682
|
+
return { type: "text", text: b.text.slice(0, cap2) + truncationNote(b.text.length) };
|
|
1683
|
+
}
|
|
1684
|
+
return b;
|
|
1685
|
+
});
|
|
1686
|
+
if (mutated) {
|
|
1687
|
+
resultText = resultContent.map((b) => b.type === "text" ? b.text : `[image ${b.source.media_type}]`).join("\n");
|
|
1680
1688
|
}
|
|
1681
|
-
return b;
|
|
1682
|
-
});
|
|
1683
|
-
if (mutated) {
|
|
1684
|
-
resultText = resultContent.map((b) => b.type === "text" ? b.text : `[image ${b.source.media_type}]`).join("\n");
|
|
1685
1689
|
}
|
|
1690
|
+
const durationMs = Date.now() - startTime;
|
|
1691
|
+
toolResults.push({
|
|
1692
|
+
type: "tool_result",
|
|
1693
|
+
tool_use_id: tu.id,
|
|
1694
|
+
content: resultContent,
|
|
1695
|
+
// truncated — LLM-facing only
|
|
1696
|
+
...isError ? { is_error: true } : {}
|
|
1697
|
+
});
|
|
1698
|
+
yield {
|
|
1699
|
+
type: "tool_result",
|
|
1700
|
+
toolName: tu.name,
|
|
1701
|
+
toolUseId: tu.id,
|
|
1702
|
+
toolResult: resultText,
|
|
1703
|
+
// truncated (LLM cap applied)
|
|
1704
|
+
toolResultFull: resultTextFull,
|
|
1705
|
+
// full — for UI display
|
|
1706
|
+
durationMs
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
} finally {
|
|
1710
|
+
if (toolResults.length > 0) {
|
|
1711
|
+
this.messages.push({ role: "user", content: toolResults });
|
|
1686
1712
|
}
|
|
1687
|
-
const durationMs = Date.now() - startTime;
|
|
1688
|
-
toolResults.push({
|
|
1689
|
-
type: "tool_result",
|
|
1690
|
-
tool_use_id: tu.id,
|
|
1691
|
-
content: resultContent,
|
|
1692
|
-
// truncated — LLM-facing only
|
|
1693
|
-
...isError ? { is_error: true } : {}
|
|
1694
|
-
});
|
|
1695
|
-
yield {
|
|
1696
|
-
type: "tool_result",
|
|
1697
|
-
toolName: tu.name,
|
|
1698
|
-
toolUseId: tu.id,
|
|
1699
|
-
toolResult: resultText,
|
|
1700
|
-
// truncated (LLM cap applied)
|
|
1701
|
-
toolResultFull: resultTextFull,
|
|
1702
|
-
// full — for UI display
|
|
1703
|
-
durationMs
|
|
1704
|
-
};
|
|
1705
1713
|
}
|
|
1706
|
-
this.messages.push({ role: "user", content: toolResults });
|
|
1707
1714
|
if (shouldEndTurn) {
|
|
1708
1715
|
yield { type: "stop", stopReason: "end_turn" };
|
|
1709
1716
|
return;
|
|
@@ -2490,7 +2497,7 @@ var init_init = __esm({
|
|
|
2490
2497
|
path8.resolve(__dirname, "..", "bundled-docs"),
|
|
2491
2498
|
path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
2492
2499
|
];
|
|
2493
|
-
TEMPLATE_VERSION =
|
|
2500
|
+
TEMPLATE_VERSION = 60;
|
|
2494
2501
|
VERSION_FILE = ".template-version";
|
|
2495
2502
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
2496
2503
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -2932,6 +2939,23 @@ ${skillInfo}` : ""
|
|
|
2932
2939
|
queryAgentTool
|
|
2933
2940
|
];
|
|
2934
2941
|
}
|
|
2942
|
+
function buildContinueTaskTool(sm, sessionId) {
|
|
2943
|
+
return {
|
|
2944
|
+
name: "continue_task",
|
|
2945
|
+
description: "Call this when your turn was started by an interruption (a user or parent message arrived while you were working) and the task you were doing is NOT finished yet. After your current reply ends, the session automatically resumes the interrupted task. Do NOT call it if the task is done, if the user told you to stop, or in a normal (uninterrupted) turn \u2014 it does nothing there. The flag lasts one turn: if you get interrupted again before resuming, call it again.",
|
|
2946
|
+
inputSchema: { type: "object", properties: {} },
|
|
2947
|
+
callback: async () => {
|
|
2948
|
+
switch (sm.requestSelfKick(sessionId)) {
|
|
2949
|
+
case "set":
|
|
2950
|
+
return JSON.stringify({ code: 0, message: "Continue flag set. Finish your current reply \u2014 the interrupted task resumes automatically right after." });
|
|
2951
|
+
case "not_interrupted":
|
|
2952
|
+
return JSON.stringify({ code: 0, message: "This turn was not started by an interruption \u2014 nothing to resume. Just keep working normally; do not call this again this turn." });
|
|
2953
|
+
case "no_turn":
|
|
2954
|
+
return JSON.stringify({ code: 1, error: "Turn already ended (stopped) \u2014 flag not set." });
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2935
2959
|
var init_session_tools = __esm({
|
|
2936
2960
|
"packages/server/dist/agents/session-tools.js"() {
|
|
2937
2961
|
"use strict";
|
|
@@ -7841,10 +7865,25 @@ var init_deepseek_agent = __esm({
|
|
|
7841
7865
|
}
|
|
7842
7866
|
return results;
|
|
7843
7867
|
}
|
|
7868
|
+
/** Block arrays become OpenAI content parts — text as-is, image blocks as
|
|
7869
|
+
* `image_url` data URLs (same shape as Kimi). Only `deepseek-flash`
|
|
7870
|
+
* actually sees images — the registry has `image: false` on v4-pro, so the
|
|
7871
|
+
* session manager strips them before they reach here for that model. */
|
|
7844
7872
|
convertUserContent(content) {
|
|
7845
7873
|
if (typeof content === "string")
|
|
7846
7874
|
return content;
|
|
7847
|
-
|
|
7875
|
+
const parts = [];
|
|
7876
|
+
for (const block of content) {
|
|
7877
|
+
if (block.type === "text") {
|
|
7878
|
+
parts.push({ type: "text", text: block.text });
|
|
7879
|
+
} else if (block.type === "image") {
|
|
7880
|
+
parts.push({
|
|
7881
|
+
type: "image_url",
|
|
7882
|
+
image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
|
|
7883
|
+
});
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
return parts;
|
|
7848
7887
|
}
|
|
7849
7888
|
convertAssistantMessage(msg) {
|
|
7850
7889
|
const results = [];
|
|
@@ -8921,8 +8960,8 @@ import { SignatureV4 } from "@smithy/signature-v4";
|
|
|
8921
8960
|
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
8922
8961
|
import { defaultProvider } from "@aws-sdk/credential-provider-node";
|
|
8923
8962
|
function extractRegionFromHost(hostname) {
|
|
8924
|
-
const m = hostname.match(
|
|
8925
|
-
return m?.[1] ?? "us-east-
|
|
8963
|
+
const m = hostname.match(/^bedrock-(?:mantle|runtime)\.([a-z0-9-]+)\./);
|
|
8964
|
+
return m?.[1] ?? "us-east-1";
|
|
8926
8965
|
}
|
|
8927
8966
|
function safeParse6(json) {
|
|
8928
8967
|
try {
|
|
@@ -9371,10 +9410,15 @@ function createModelRuntime(providerId, cfg) {
|
|
|
9371
9410
|
maxTokens: cfg.maxTokens,
|
|
9372
9411
|
thinking: cfg.thinking
|
|
9373
9412
|
});
|
|
9413
|
+
// aws-bedrock-openai is the same OpenAI Responses wire format on the
|
|
9414
|
+
// bedrock-runtime host (/openai/v1, cross-Region profile ids only) —
|
|
9415
|
+
// Grok 4.6 / Kimi K3 live there and not (or not everywhere) on Mantle.
|
|
9416
|
+
// See aws-bedrock-openai.yaml.
|
|
9374
9417
|
case "aws-bedrock-mantle":
|
|
9418
|
+
case "aws-bedrock-openai":
|
|
9375
9419
|
return new MantleAgent({
|
|
9376
9420
|
modelId: cfg.modelId,
|
|
9377
|
-
endpoint: cfg.endpoint ?? "https://bedrock-mantle.us-east-
|
|
9421
|
+
endpoint: cfg.endpoint ?? (providerId === "aws-bedrock-openai" ? "https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1" : "https://bedrock-mantle.us-east-1.api.aws/openai/v1"),
|
|
9378
9422
|
// No bearer token → MantleAgent falls back to SigV4 IAM auth using
|
|
9379
9423
|
// these creds (or the SDK default chain when also empty).
|
|
9380
9424
|
apiKey: cfg.apiKey ?? "",
|
|
@@ -10960,6 +11004,7 @@ var init_session_agent_builder = __esm({
|
|
|
10960
11004
|
const nameSet = new Set(yamlConfig?.tools ?? []);
|
|
10961
11005
|
const delegates = canDelegate(yamlConfig);
|
|
10962
11006
|
const sessionTools = delegates ? this.host.createSessionTools(sessionId) : agentId === GOAL_AGENT_ID ? this.host.createGoalTools(sessionId) : [];
|
|
11007
|
+
sessionTools.push(this.host.createContinueTaskTool(sessionId));
|
|
10963
11008
|
let draftReset = null;
|
|
10964
11009
|
if (nameSet.has("draft")) {
|
|
10965
11010
|
const { tool, reset } = createDraftTool();
|
|
@@ -11442,7 +11487,7 @@ async function initObservability() {
|
|
|
11442
11487
|
return true;
|
|
11443
11488
|
}
|
|
11444
11489
|
const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
|
|
11445
|
-
providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.3.
|
|
11490
|
+
providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.3.4");
|
|
11446
11491
|
console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, capture_content=${config.observability.captureContent})`);
|
|
11447
11492
|
return true;
|
|
11448
11493
|
}
|
|
@@ -11816,7 +11861,7 @@ function generateSessionId() {
|
|
|
11816
11861
|
const rand = Math.random().toString(36).slice(2, 8);
|
|
11817
11862
|
return `sid_${ts}_${rand}`;
|
|
11818
11863
|
}
|
|
11819
|
-
var SessionManager;
|
|
11864
|
+
var CONTINUE_TASK_KICK, SessionManager;
|
|
11820
11865
|
var init_session_manager = __esm({
|
|
11821
11866
|
"packages/server/dist/agents/session-manager.js"() {
|
|
11822
11867
|
"use strict";
|
|
@@ -11847,6 +11892,7 @@ var init_session_manager = __esm({
|
|
|
11847
11892
|
init_session_skill_commands();
|
|
11848
11893
|
init_session_state_store();
|
|
11849
11894
|
init_genai_spans();
|
|
11895
|
+
CONTINUE_TASK_KICK = `[System] You called continue_task: the task interrupted earlier is not finished. Resume it now from where it stopped. Any tool call marked "[tool execution interrupted \u2014 no result]" did not run \u2014 re-issue it if you still need the result (this overrides the marker's do-not-retry note \u2014 you explicitly asked to continue).`;
|
|
11850
11896
|
SessionManager = class _SessionManager {
|
|
11851
11897
|
// The fields below are marked `internal:` because they're consumed by
|
|
11852
11898
|
// helpers in this directory (session-tools.ts). Treat them as
|
|
@@ -12063,6 +12109,11 @@ var init_session_manager = __esm({
|
|
|
12063
12109
|
createSessionTools(sessionId) {
|
|
12064
12110
|
return buildSessionTools(this, sessionId);
|
|
12065
12111
|
}
|
|
12112
|
+
/** Built-in continue_task tool (every agent, no yaml opt-in) — see
|
|
12113
|
+
* session-agent-builder / requestSelfKick / drainQueue. */
|
|
12114
|
+
createContinueTaskTool(sessionId) {
|
|
12115
|
+
return buildContinueTaskTool(this, sessionId);
|
|
12116
|
+
}
|
|
12066
12117
|
/** Goal-mode tool set — injected only for the `goal` agent (see
|
|
12067
12118
|
* session-agent-builder / goal-mode.ts). */
|
|
12068
12119
|
createGoalTools(sessionId) {
|
|
@@ -12104,6 +12155,8 @@ var init_session_manager = __esm({
|
|
|
12104
12155
|
warnedToolHashes: /* @__PURE__ */ new Set(),
|
|
12105
12156
|
turnStartTime: 0,
|
|
12106
12157
|
interruptRequested: false,
|
|
12158
|
+
selfKick: false,
|
|
12159
|
+
resumedAfterInterrupt: false,
|
|
12107
12160
|
isCompacting: false,
|
|
12108
12161
|
compactAbortController: null,
|
|
12109
12162
|
compactedThisTurn: false,
|
|
@@ -12707,6 +12760,8 @@ var init_session_manager = __esm({
|
|
|
12707
12760
|
session.toolCallLog = [];
|
|
12708
12761
|
session.warnedToolHashes.clear();
|
|
12709
12762
|
session.interruptRequested = false;
|
|
12763
|
+
session.resumedAfterInterrupt = false;
|
|
12764
|
+
session.selfKick = false;
|
|
12710
12765
|
result = await this.runAgentTurn(session, message);
|
|
12711
12766
|
console.debug(`[SessionManager] runSession ${sessionId} first turn done \u2014 result: ${result.slice(0, 150)}`);
|
|
12712
12767
|
}
|
|
@@ -12847,6 +12902,9 @@ ${list}`;
|
|
|
12847
12902
|
async drainQueue(session) {
|
|
12848
12903
|
while (session.messageQueue.length > 0) {
|
|
12849
12904
|
const batch = session.messageQueue.splice(0);
|
|
12905
|
+
session.resumedAfterInterrupt = session.interruptRequested;
|
|
12906
|
+
const kickFlagReset = session.selfKick;
|
|
12907
|
+
session.selfKick = false;
|
|
12850
12908
|
session.interruptRequested = false;
|
|
12851
12909
|
session.toolCallLog = [];
|
|
12852
12910
|
session.warnedToolHashes.clear();
|
|
@@ -12861,7 +12919,8 @@ ${q.text}` : q.text).join("\n\n");
|
|
|
12861
12919
|
if (suffix && session.parentId === null) {
|
|
12862
12920
|
this.emitEvent(session.id, { type: "system", text: suffix.trim(), agentName: session.agentName });
|
|
12863
12921
|
}
|
|
12864
|
-
const
|
|
12922
|
+
const kickNote = kickFlagReset ? "\n\n[System] The continue_task flag you set last turn was reset by this interrupt. If the interrupted task is still unfinished after you answer, call continue_task again." : "";
|
|
12923
|
+
const input = this.buildInput(merged + suffix + kickNote, images.length > 0 ? images : void 0, session.supportsImage);
|
|
12865
12924
|
try {
|
|
12866
12925
|
await this.runAgentTurn(session, input);
|
|
12867
12926
|
} catch (err) {
|
|
@@ -12869,6 +12928,15 @@ ${q.text}` : q.text).join("\n\n");
|
|
|
12869
12928
|
console.error(`[SessionManager] Drain run error for ${session.id}: ${errMsg}`);
|
|
12870
12929
|
throw err;
|
|
12871
12930
|
}
|
|
12931
|
+
if (session.messageQueue.length === 0) {
|
|
12932
|
+
if (session.selfKick && !session.interruptRequested) {
|
|
12933
|
+
session.messageQueue.push({ text: CONTINUE_TASK_KICK });
|
|
12934
|
+
const taskId = session.parentId !== null ? session.id : void 0;
|
|
12935
|
+
this.emitEvent(session.id, { type: "user", text: CONTINUE_TASK_KICK, agentName: "user", report: true, taskId });
|
|
12936
|
+
console.debug(`[SessionManager] continue_task kick for ${session.id}`);
|
|
12937
|
+
}
|
|
12938
|
+
session.selfKick = false;
|
|
12939
|
+
}
|
|
12872
12940
|
if (session.parentId === null && session.messageQueue.length > 0) {
|
|
12873
12941
|
this.emitEvent(session.id, { type: "complete", batchBoundary: true });
|
|
12874
12942
|
}
|
|
@@ -12944,6 +13012,18 @@ ${q.text}` : q.text).join("\n\n");
|
|
|
12944
13012
|
this.markPendingToolCallsInterrupted(sessionId);
|
|
12945
13013
|
console.debug(`[SessionManager] Interrupted session ${sessionId}`);
|
|
12946
13014
|
}
|
|
13015
|
+
/** continue_task tool backend. Arms the one-turn selfKick flag that drainQueue
|
|
13016
|
+
* turns into a synthetic resume turn — but only in a turn that itself started
|
|
13017
|
+
* after an interrupt (resumedAfterInterrupt), so kicks ≤ interrupts. */
|
|
13018
|
+
requestSelfKick(sessionId) {
|
|
13019
|
+
const session = this.sessions.get(sessionId);
|
|
13020
|
+
if (!session || session.abortController === null)
|
|
13021
|
+
return "no_turn";
|
|
13022
|
+
if (!session.resumedAfterInterrupt)
|
|
13023
|
+
return "not_interrupted";
|
|
13024
|
+
session.selfKick = true;
|
|
13025
|
+
return "set";
|
|
13026
|
+
}
|
|
12947
13027
|
/** Append text as a user turn to a session's agent.messages, coalescing into
|
|
12948
13028
|
* a trailing user message if one exists. Two invariants drive this:
|
|
12949
13029
|
* 1. Anthropic rejects consecutive same-role messages, and the next
|
|
@@ -12981,6 +13061,7 @@ ${q.text}` : q.text);
|
|
|
12981
13061
|
}
|
|
12982
13062
|
session.messageQueue = [];
|
|
12983
13063
|
session.interruptRequested = false;
|
|
13064
|
+
session.selfKick = false;
|
|
12984
13065
|
if (session.abortController) {
|
|
12985
13066
|
session.abortController.abort(abortReason("stop"));
|
|
12986
13067
|
session.abortController = null;
|
|
@@ -13702,6 +13783,7 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
13702
13783
|
session.abortController.abort(abortReason("delete"));
|
|
13703
13784
|
session.abortController = null;
|
|
13704
13785
|
}
|
|
13786
|
+
session.selfKick = false;
|
|
13705
13787
|
if (session.promise) {
|
|
13706
13788
|
try {
|
|
13707
13789
|
await session.promise;
|
|
@@ -13718,6 +13800,7 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
13718
13800
|
childSession.abortController.abort(abortReason("delete"));
|
|
13719
13801
|
childSession.abortController = null;
|
|
13720
13802
|
}
|
|
13803
|
+
childSession.selfKick = false;
|
|
13721
13804
|
this.sessions.delete(id);
|
|
13722
13805
|
}
|
|
13723
13806
|
}
|
|
@@ -13762,6 +13845,7 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
13762
13845
|
session.abortController.abort(abortReason("archive"));
|
|
13763
13846
|
session.abortController = null;
|
|
13764
13847
|
}
|
|
13848
|
+
session.selfKick = false;
|
|
13765
13849
|
if (session.promise) {
|
|
13766
13850
|
try {
|
|
13767
13851
|
await session.promise;
|
|
@@ -13848,8 +13932,10 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
13848
13932
|
* message), and requests a SOFT interrupt so a busy turn yields after its
|
|
13849
13933
|
* current tool — same effect as sendUserMessage's busy branch, minus the
|
|
13850
13934
|
* idle/run path (the caller already established the session is busy or
|
|
13851
|
-
* compacting). When compacting
|
|
13852
|
-
*
|
|
13935
|
+
* compacting). When compacting there is no live turn to interrupt, so the
|
|
13936
|
+
* flag is NOT set (mirrors sendUserMessage's compacting branch): endCompact's
|
|
13937
|
+
* drain would snapshot it into resumedAfterInterrupt and let continue_task
|
|
13938
|
+
* arm a kick in a turn that interrupted nothing. endCompact drains the queue. */
|
|
13853
13939
|
async enqueueUserMessage(sessionId, text6, images) {
|
|
13854
13940
|
const session = this.sessions.get(sessionId);
|
|
13855
13941
|
if (!session)
|
|
@@ -13859,7 +13945,8 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
13859
13945
|
this.emitEvent(sessionId, { type: "system", text: `\u26A0 ${w}` });
|
|
13860
13946
|
}
|
|
13861
13947
|
session.messageQueue.push({ text: scoped.text, images });
|
|
13862
|
-
session.
|
|
13948
|
+
if (!session.isCompacting)
|
|
13949
|
+
session.interruptRequested = true;
|
|
13863
13950
|
console.debug(`[SessionManager] User message enqueued for ${sessionId} (${session.messageQueue.length} in queue)`);
|
|
13864
13951
|
}
|
|
13865
13952
|
/** Hard stop — abort immediately (for explicit user stop button).
|
|
@@ -13880,6 +13967,7 @@ ${q.text}` : q.text);
|
|
|
13880
13967
|
}
|
|
13881
13968
|
session.messageQueue = [];
|
|
13882
13969
|
session.interruptRequested = false;
|
|
13970
|
+
session.selfKick = false;
|
|
13883
13971
|
if (session.abortController) {
|
|
13884
13972
|
session.abortController.abort();
|
|
13885
13973
|
session.abortController = null;
|
|
@@ -31078,7 +31166,7 @@ var init_dist2 = __esm({
|
|
|
31078
31166
|
init_git_ssh();
|
|
31079
31167
|
__dirname3 = path68.dirname(fileURLToPath3(import.meta.url));
|
|
31080
31168
|
PORT = config.server.port;
|
|
31081
|
-
HALO_VERSION = "1.3.
|
|
31169
|
+
HALO_VERSION = "1.3.4";
|
|
31082
31170
|
PROJECT_ROOT = findProjectRoot();
|
|
31083
31171
|
HALO_HOME4 = path68.join(homedir24(), ".halo");
|
|
31084
31172
|
GIT_SHA = readGitSha();
|
|
@@ -32013,7 +32101,7 @@ import path69 from "node:path";
|
|
|
32013
32101
|
import fs63 from "node:fs";
|
|
32014
32102
|
import { homedir as homedir25 } from "node:os";
|
|
32015
32103
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
32016
|
-
var VERSION = "1.3.
|
|
32104
|
+
var VERSION = "1.3.4";
|
|
32017
32105
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
32018
32106
|
|
|
32019
32107
|
Commands:
|
package/package.json
CHANGED
|
@@ -245,6 +245,35 @@ models:
|
|
|
245
245
|
- { value: xhigh, label: XHigh }
|
|
246
246
|
- { value: max, label: Max }
|
|
247
247
|
|
|
248
|
+
- id: global.anthropic.claude-opus-5-5
|
|
249
|
+
displayName: Claude Opus 5.5
|
|
250
|
+
maxOutputTokens: 128000
|
|
251
|
+
contextWindow: 1000000
|
|
252
|
+
capabilities:
|
|
253
|
+
image: true
|
|
254
|
+
video: false
|
|
255
|
+
audio: false
|
|
256
|
+
promptCaching:
|
|
257
|
+
default: 1h
|
|
258
|
+
ttlPresets:
|
|
259
|
+
- { value: 5m, label: 5min }
|
|
260
|
+
- { value: 1h, label: 1hour }
|
|
261
|
+
thinking:
|
|
262
|
+
# Opus 5.5 (Bedrock GA 2026-09-22). Live-probed 2026-09-24 on invoke:
|
|
263
|
+
# invalid-value probe lists low / medium / high / xhigh / max;
|
|
264
|
+
# max_tokens ceiling 128000; smoke call (xhigh) returned normally.
|
|
265
|
+
# Model card: 1M context, adaptive thinking always-on (can't be
|
|
266
|
+
# disabled), image input.
|
|
267
|
+
mode: adaptive
|
|
268
|
+
defaultEnabled: true
|
|
269
|
+
default: high # flagship — high by default; xhigh/max on demand
|
|
270
|
+
effortPresets:
|
|
271
|
+
- { value: low, label: Low }
|
|
272
|
+
- { value: medium, label: Medium }
|
|
273
|
+
- { value: high, label: High }
|
|
274
|
+
- { value: xhigh, label: XHigh }
|
|
275
|
+
- { value: max, label: Max }
|
|
276
|
+
|
|
248
277
|
- id: global.anthropic.claude-haiku-4-5-20251001-v1:0
|
|
249
278
|
displayName: Claude Haiku 4.5
|
|
250
279
|
maxOutputTokens: 64000
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
id: aws-bedrock-mantle
|
|
2
2
|
displayName: AWS Bedrock Mantle (OpenAI GPT-6 / 5.6)
|
|
3
3
|
displayName_zh: AWS Bedrock Mantle(OpenAI GPT-6 / 5.6)
|
|
4
|
-
description: OpenAI GPT-6 Astra and GPT-5.6 (Sol / Terra / Luna) on Amazon Bedrock via the bedrock-mantle endpoint (OpenAI Responses API only — not Converse/InvokeModel).
|
|
5
|
-
description_zh: 通过 Bedrock Mantle 端点调用 OpenAI GPT-6
|
|
4
|
+
description: OpenAI GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna) on Amazon Bedrock via the bedrock-mantle endpoint (OpenAI Responses API only — not Converse/InvokeModel). Region-split — GPT-6 Sol / Luna are on us-east-1 only, Astra on us-west-2 only.
|
|
5
|
+
description_zh: 通过 Bedrock Mantle 端点调用 OpenAI GPT-6(Astra / Sol / Luna)与 GPT-5.6(Sol / Terra / Luna)(仅 OpenAI Responses API,不支持 Converse/InvokeModel)。按区域分布:GPT-6 Sol / Luna 仅 us-east-1,Astra 仅 us-west-2。
|
|
6
6
|
# Mantle is a distinct endpoint from bedrock-runtime; the OpenAI surface lives
|
|
7
|
-
# under /openai/v1. Region split (live-probed 2026-09-
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
7
|
+
# under /openai/v1. Region split (live-probed 2026-09-24, 404 on the wrong
|
|
8
|
+
# endpoint):
|
|
9
|
+
#
|
|
10
|
+
# us-east-1 us-east-2 us-west-2
|
|
11
|
+
# gpt-6-astra - - ✓
|
|
12
|
+
# gpt-6-sol ✓ - -
|
|
13
|
+
# gpt-6-luna ✓ - -
|
|
14
|
+
# gpt-5.6-sol ✓ ✓ -
|
|
15
|
+
# gpt-5.6-terra ✓ ✓ ✓
|
|
16
|
+
# gpt-5.6-luna ✓ ✓ ✓
|
|
17
|
+
#
|
|
18
|
+
# us-east-1 serves everything but Astra, so it is the default; existing
|
|
19
|
+
# us-east-2 configs (5.6-era) keep working; Astra users pick us-west-2 in the
|
|
20
|
+
# agent form. The endpoint is a per-agent pick.
|
|
21
|
+
#
|
|
22
|
+
# The same six models are also in aws-bedrock-openai.yaml (bedrock-runtime
|
|
23
|
+
# host, `global.openai.*` ids, no region split) — AWS's recommended endpoint
|
|
24
|
+
# for new agents. Stay here only for Mantle-only features (server-side
|
|
25
|
+
# web_search, background inference), which the web-search skill relies on.
|
|
26
|
+
defaultEndpoint: https://bedrock-mantle.us-east-1.api.aws/openai/v1
|
|
14
27
|
endpointPresets:
|
|
28
|
+
- https://bedrock-mantle.us-east-1.api.aws/openai/v1
|
|
15
29
|
- https://bedrock-mantle.us-east-2.api.aws/openai/v1
|
|
16
30
|
- https://bedrock-mantle.us-west-2.api.aws/openai/v1
|
|
17
31
|
|
|
18
|
-
defaultModelId: openai.gpt-
|
|
32
|
+
defaultModelId: openai.gpt-6-sol
|
|
19
33
|
|
|
20
34
|
# Auth: a Bedrock long-term API key passed as a Bearer token. Generate at
|
|
21
35
|
# console → Bedrock → API keys → long-term. The default placeholder reads the
|
|
@@ -87,6 +101,72 @@ models:
|
|
|
87
101
|
- { value: medium, label: Medium }
|
|
88
102
|
- { value: high, label: High }
|
|
89
103
|
|
|
104
|
+
# GPT-6 Sol / Luna (Bedrock GA 2026-09-22) — us-east-1 endpoint ONLY (404 on
|
|
105
|
+
# us-east-2 / us-west-2). Same 1,050,000 context / 128,000 max output as
|
|
106
|
+
# Astra per the OpenAI model pages; live-probed 2026-09-24 on us-east-1 with
|
|
107
|
+
# invalid-value requests: reasoning.effort accepts none/low/medium/high/
|
|
108
|
+
# xhigh/max, text.verbosity accepts low/medium/high — identical surface, so
|
|
109
|
+
# MantleAgent needs no change. Sol ($2/$10) is the coding/agentic tier and
|
|
110
|
+
# the new default; Luna ($0.10/$0.50) is the high-volume tier. The 272K→2x
|
|
111
|
+
# input-price step noted on Astra applies here too.
|
|
112
|
+
- id: openai.gpt-6-sol
|
|
113
|
+
displayName: GPT-6 Sol
|
|
114
|
+
maxOutputTokens: 128000
|
|
115
|
+
contextWindow: 1050000
|
|
116
|
+
capabilities:
|
|
117
|
+
image: true
|
|
118
|
+
video: false
|
|
119
|
+
audio: false
|
|
120
|
+
promptCaching:
|
|
121
|
+
default: 5m
|
|
122
|
+
ttlPresets:
|
|
123
|
+
- { value: 5m, label: 5min }
|
|
124
|
+
thinking:
|
|
125
|
+
mode: adaptive
|
|
126
|
+
defaultEnabled: true
|
|
127
|
+
default: medium
|
|
128
|
+
effortPresets:
|
|
129
|
+
- { value: low, label: Low }
|
|
130
|
+
- { value: medium, label: Medium }
|
|
131
|
+
- { value: high, label: High }
|
|
132
|
+
- { value: xhigh, label: XHigh }
|
|
133
|
+
- { value: max, label: Max }
|
|
134
|
+
verbosity:
|
|
135
|
+
default: low
|
|
136
|
+
presets:
|
|
137
|
+
- { value: low, label: Low }
|
|
138
|
+
- { value: medium, label: Medium }
|
|
139
|
+
- { value: high, label: High }
|
|
140
|
+
|
|
141
|
+
- id: openai.gpt-6-luna
|
|
142
|
+
displayName: GPT-6 Luna
|
|
143
|
+
maxOutputTokens: 128000
|
|
144
|
+
contextWindow: 1050000
|
|
145
|
+
capabilities:
|
|
146
|
+
image: true
|
|
147
|
+
video: false
|
|
148
|
+
audio: false
|
|
149
|
+
promptCaching:
|
|
150
|
+
default: 5m
|
|
151
|
+
ttlPresets:
|
|
152
|
+
- { value: 5m, label: 5min }
|
|
153
|
+
thinking:
|
|
154
|
+
mode: adaptive
|
|
155
|
+
defaultEnabled: true
|
|
156
|
+
default: medium
|
|
157
|
+
effortPresets:
|
|
158
|
+
- { value: low, label: Low }
|
|
159
|
+
- { value: medium, label: Medium }
|
|
160
|
+
- { value: high, label: High }
|
|
161
|
+
- { value: xhigh, label: XHigh }
|
|
162
|
+
- { value: max, label: Max }
|
|
163
|
+
verbosity:
|
|
164
|
+
default: low
|
|
165
|
+
presets:
|
|
166
|
+
- { value: low, label: Low }
|
|
167
|
+
- { value: medium, label: Medium }
|
|
168
|
+
- { value: high, label: High }
|
|
169
|
+
|
|
90
170
|
# GPT-5.6 flagship.
|
|
91
171
|
- id: openai.gpt-5.6-sol
|
|
92
172
|
displayName: GPT-5.6 Sol
|