@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/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
- for (const tu of toolUseBlocks) {
1631
- if (options?.cancelSignal?.aborted)
1632
- return;
1633
- const toolDef = this.toolMap.get(tu.name);
1634
- if (toolDef?.forceEndTurn)
1635
- shouldEndTurn = true;
1636
- const startTime = Date.now();
1637
- let resultContent;
1638
- let resultText;
1639
- let isError = false;
1640
- if (!toolDef) {
1641
- resultContent = `${TOOL_ERROR_MARKER}
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: ${err instanceof Error ? err.message : String(err)}`;
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
- const uiCap = config.limits.toolResultUiMax;
1663
- const resultTextFull = resultText.length > uiCap ? resultText.slice(0, uiCap) + `
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
- const cap2 = tu.name === "activate_skill" ? Infinity : config.limits.toolResultMax;
1667
- const truncationNote = (origLen) => `
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
- if (typeof resultContent === "string" && resultContent.length > cap2) {
1671
- const orig = resultContent.length;
1672
- resultContent = resultContent.slice(0, cap2) + truncationNote(orig);
1673
- resultText = resultContent;
1674
- } else if (Array.isArray(resultContent)) {
1675
- let mutated = false;
1676
- resultContent = resultContent.map((b) => {
1677
- if (b.type === "text" && b.text.length > cap2) {
1678
- mutated = true;
1679
- return { type: "text", text: b.text.slice(0, cap2) + truncationNote(b.text.length) };
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 = 59;
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
- return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
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(/bedrock-mantle\.([a-z0-9-]+)\.api\.aws/);
8925
- return m?.[1] ?? "us-east-2";
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-2.api.aws/openai/v1",
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.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 input = this.buildInput(merged + suffix, images.length > 0 ? images : void 0, session.supportsImage);
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, the flag is harmless (no live turn) and
13852
- * endCompact drains the queue. */
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.interruptRequested = true;
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.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.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turmind/halo",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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). Astra is served from the us-west-2 endpoint only.
5
- description_zh: 通过 Bedrock Mantle 端点调用 OpenAI GPT-6 Astra 与 GPT-5.6(Sol / Terra / Luna)(仅 OpenAI Responses API,不支持 Converse/InvokeModel)。Astra 仅 us-west-2 端点提供。
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-13, both directions 404
8
- # on the wrong endpoint): us-east-2 serves every 5.6 model; us-west-2 serves
9
- # GPT-6 Astra + Terra / Luna but NOT Sol. No model is on both endpoints
10
- # except Terra / Luna, so the endpoint is a per-agent pick — the default
11
- # stays us-east-2 (existing Sol configs), Astra users select the us-west-2
12
- # preset in the agent form.
13
- defaultEndpoint: https://bedrock-mantle.us-east-2.api.aws/openai/v1
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-5.6-sol
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