@vascend/talos 0.1.4 → 0.1.6

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.
Files changed (251) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +37 -12
  3. package/dist/core/agent.d.ts +16 -3
  4. package/dist/core/agent.js +170 -70
  5. package/dist/core/background.d.ts +10 -3
  6. package/dist/core/background.js +55 -9
  7. package/dist/core/bootstrap.js +1 -0
  8. package/dist/core/compaction.js +1 -1
  9. package/dist/core/config-storage.d.ts +5 -0
  10. package/dist/core/config-storage.js +83 -0
  11. package/dist/core/config.d.ts +11 -2
  12. package/dist/core/config.js +24 -8
  13. package/dist/core/context-ledger.js +2 -1
  14. package/dist/core/context-policy.d.ts +1 -1
  15. package/dist/core/context-policy.js +10 -7
  16. package/dist/core/credentials.js +29 -30
  17. package/dist/core/effort.d.ts +1 -1
  18. package/dist/core/effort.js +5 -1
  19. package/dist/core/execution-guidance.d.ts +1 -1
  20. package/dist/core/execution-guidance.js +2 -0
  21. package/dist/core/file-coordination.d.ts +3 -0
  22. package/dist/core/file-coordination.js +28 -0
  23. package/dist/core/file-lease-store.d.ts +3 -0
  24. package/dist/core/file-lease-store.js +28 -12
  25. package/dist/core/fusion.d.ts +24 -0
  26. package/dist/core/fusion.js +80 -0
  27. package/dist/core/model-limits.d.ts +1 -0
  28. package/dist/core/model-limits.js +5 -1
  29. package/dist/core/permission-queue.js +15 -15
  30. package/dist/core/request-plan.js +4 -3
  31. package/dist/core/session-naming.d.ts +3 -0
  32. package/dist/core/session-naming.js +47 -0
  33. package/dist/core/session-usage.js +4 -1
  34. package/dist/core/session.d.ts +5 -2
  35. package/dist/core/session.js +22 -7
  36. package/dist/core/settings.d.ts +2 -0
  37. package/dist/core/settings.js +5 -16
  38. package/dist/core/step-policy.d.ts +18 -0
  39. package/dist/core/step-policy.js +6 -0
  40. package/dist/core/storage-health.d.ts +24 -0
  41. package/dist/core/storage-health.js +72 -0
  42. package/dist/core/tool-artifacts.d.ts +2 -0
  43. package/dist/core/tool-artifacts.js +21 -3
  44. package/dist/core/tool-result-index.js +29 -33
  45. package/dist/core/tool-result-store.js +6 -5
  46. package/dist/core/types.d.ts +26 -2
  47. package/dist/core/user-inbox.d.ts +9 -0
  48. package/dist/core/user-inbox.js +22 -3
  49. package/dist/core/user-input.js +1 -1
  50. package/dist/fusion/autopilot-fast.d.ts +59 -0
  51. package/dist/fusion/autopilot-fast.js +213 -0
  52. package/dist/fusion/autopilot.d.ts +75 -0
  53. package/dist/fusion/autopilot.js +308 -0
  54. package/dist/fusion/focus.d.ts +30 -0
  55. package/dist/fusion/focus.js +122 -0
  56. package/dist/fusion/gate.d.ts +30 -0
  57. package/dist/fusion/gate.js +95 -0
  58. package/dist/fusion/jev.d.ts +71 -0
  59. package/dist/fusion/jev.js +164 -0
  60. package/dist/fusion/receipts.d.ts +25 -0
  61. package/dist/fusion/receipts.js +34 -0
  62. package/dist/fusion/route.d.ts +25 -0
  63. package/dist/fusion/route.js +84 -0
  64. package/dist/fusion/run.d.ts +4 -0
  65. package/dist/fusion/run.js +186 -0
  66. package/dist/horizon/engine.d.ts +20 -1
  67. package/dist/horizon/engine.js +57 -0
  68. package/dist/horizon/index.d.ts +2 -2
  69. package/dist/horizon/index.js +1 -1
  70. package/dist/horizon/types.d.ts +29 -0
  71. package/dist/index.js +12 -0
  72. package/dist/providers/anthropic-oauth.d.ts +8 -3
  73. package/dist/providers/anthropic-oauth.js +13 -10
  74. package/dist/providers/anthropic.d.ts +9 -2
  75. package/dist/providers/anthropic.js +64 -21
  76. package/dist/providers/cli-models.d.ts +30 -0
  77. package/dist/providers/cli-models.js +194 -0
  78. package/dist/providers/model-effort.js +1 -1
  79. package/dist/providers/openai-oauth.d.ts +8 -3
  80. package/dist/providers/openai-oauth.js +9 -3
  81. package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
  82. package/dist/providers/openrouter-manual-fallback.js +20 -20
  83. package/dist/providers/registry.js +3 -2
  84. package/dist/providers/retry.d.ts +3 -0
  85. package/dist/providers/retry.js +44 -1
  86. package/dist/remote/cifratura.d.ts +12 -0
  87. package/dist/remote/cifratura.js +66 -0
  88. package/dist/remote/index.d.ts +15 -0
  89. package/dist/remote/index.js +65 -0
  90. package/dist/remote/relay-client.d.ts +58 -0
  91. package/dist/remote/relay-client.js +315 -0
  92. package/dist/remote/sessione-remota.d.ts +60 -0
  93. package/dist/remote/sessione-remota.js +266 -0
  94. package/dist/sdk.d.ts +7 -2
  95. package/dist/sdk.js +3 -1
  96. package/dist/tools/builtin.js +41 -20
  97. package/dist/tools/capabilities.d.ts +3 -1
  98. package/dist/tools/capabilities.js +5 -2
  99. package/dist/tools/computer-use.js +2 -1
  100. package/dist/tools/pagination.js +1 -1
  101. package/dist/tools/program.js +34 -21
  102. package/dist/tools/team-tools.js +11 -6
  103. package/dist/tui/account-entry.js +14 -9
  104. package/dist/tui/agent-label.d.ts +2 -0
  105. package/dist/tui/agent-label.js +5 -0
  106. package/dist/tui/agent-sessions-screen.d.ts +35 -0
  107. package/dist/tui/agent-sessions-screen.js +178 -0
  108. package/dist/tui/agent-sessions.d.ts +53 -0
  109. package/dist/tui/agent-sessions.js +236 -0
  110. package/dist/tui/bottom-input.d.ts +57 -2
  111. package/dist/tui/bottom-input.js +449 -54
  112. package/dist/tui/branding.js +8 -4
  113. package/dist/tui/capability-lists.d.ts +10 -0
  114. package/dist/tui/capability-lists.js +41 -0
  115. package/dist/tui/command-output-registry.js +4 -0
  116. package/dist/tui/command-transcript.d.ts +1 -1
  117. package/dist/tui/command-transcript.js +17 -21
  118. package/dist/tui/commands.js +6 -2
  119. package/dist/tui/compaction-status.d.ts +7 -0
  120. package/dist/tui/compaction-status.js +39 -0
  121. package/dist/tui/config-recovery.d.ts +12 -0
  122. package/dist/tui/config-recovery.js +54 -0
  123. package/dist/tui/context-meter.d.ts +3 -0
  124. package/dist/tui/context-meter.js +35 -8
  125. package/dist/tui/diff.d.ts +9 -0
  126. package/dist/tui/diff.js +43 -19
  127. package/dist/tui/fusion-menu.d.ts +5 -0
  128. package/dist/tui/fusion-menu.js +89 -0
  129. package/dist/tui/i18n-catalog.js +351 -2
  130. package/dist/tui/live-command.d.ts +1 -0
  131. package/dist/tui/live-command.js +9 -0
  132. package/dist/tui/live-output.d.ts +1 -0
  133. package/dist/tui/live-output.js +11 -0
  134. package/dist/tui/logo.js +3 -0
  135. package/dist/tui/model-details.js +13 -12
  136. package/dist/tui/model-picker.d.ts +2 -0
  137. package/dist/tui/model-picker.js +59 -0
  138. package/dist/tui/model-selection.js +19 -10
  139. package/dist/tui/native-scrollback.d.ts +2 -1
  140. package/dist/tui/native-scrollback.js +31 -6
  141. package/dist/tui/prompt-queue-store.d.ts +1 -0
  142. package/dist/tui/prompt-queue-store.js +4 -0
  143. package/dist/tui/prompt-queue.d.ts +3 -0
  144. package/dist/tui/prompt-queue.js +19 -1
  145. package/dist/tui/provider-label.d.ts +2 -0
  146. package/dist/tui/provider-label.js +9 -0
  147. package/dist/tui/provider-menu.js +25 -24
  148. package/dist/tui/queue-screen.js +2 -1
  149. package/dist/tui/queued-questions.js +2 -4
  150. package/dist/tui/quick-help.d.ts +1 -1
  151. package/dist/tui/quick-help.js +3 -0
  152. package/dist/tui/remote-command.d.ts +7 -0
  153. package/dist/tui/remote-command.js +79 -0
  154. package/dist/tui/repl.d.ts +11 -2
  155. package/dist/tui/repl.js +457 -327
  156. package/dist/tui/review-store.d.ts +8 -1
  157. package/dist/tui/review-store.js +64 -11
  158. package/dist/tui/review-team.js +4 -3
  159. package/dist/tui/runtime-error.d.ts +2 -0
  160. package/dist/tui/runtime-error.js +26 -0
  161. package/dist/tui/scrollback-archive.d.ts +16 -0
  162. package/dist/tui/scrollback-archive.js +90 -0
  163. package/dist/tui/selector-mouse.d.ts +1 -0
  164. package/dist/tui/selector-mouse.js +1 -1
  165. package/dist/tui/selector.d.ts +11 -0
  166. package/dist/tui/selector.js +28 -4
  167. package/dist/tui/session-controls.d.ts +26 -0
  168. package/dist/tui/session-controls.js +65 -0
  169. package/dist/tui/session-naming-notice.d.ts +14 -0
  170. package/dist/tui/session-naming-notice.js +59 -0
  171. package/dist/tui/session-picker.d.ts +2 -0
  172. package/dist/tui/session-picker.js +32 -0
  173. package/dist/tui/session-replay.d.ts +1 -0
  174. package/dist/tui/session-replay.js +101 -32
  175. package/dist/tui/session-transcript-layout.d.ts +9 -0
  176. package/dist/tui/session-transcript-layout.js +41 -0
  177. package/dist/tui/startup.js +63 -33
  178. package/dist/tui/status-line.d.ts +1 -1
  179. package/dist/tui/status-line.js +20 -12
  180. package/dist/tui/storage-warning.d.ts +2 -0
  181. package/dist/tui/storage-warning.js +35 -0
  182. package/dist/tui/talos-art-data.d.ts +1 -0
  183. package/dist/tui/talos-art-data.js +2 -0
  184. package/dist/tui/talos-art.d.ts +1 -0
  185. package/dist/tui/talos-art.js +9 -0
  186. package/dist/tui/task-labels.d.ts +1 -0
  187. package/dist/tui/task-labels.js +8 -0
  188. package/dist/tui/terminal-text.d.ts +2 -1
  189. package/dist/tui/terminal-text.js +28 -2
  190. package/dist/tui/theme.js +2 -2
  191. package/dist/tui/tool-presentation.d.ts +3 -0
  192. package/dist/tui/tool-presentation.js +98 -0
  193. package/dist/tui/tool-style.js +4 -0
  194. package/dist/tui/transcript-store.d.ts +25 -0
  195. package/dist/tui/transcript-store.js +151 -0
  196. package/dist/tui/usage-footer.d.ts +1 -1
  197. package/dist/tui/usage-footer.js +3 -1
  198. package/dist/tui/vascend-id.d.ts +49 -0
  199. package/dist/tui/vascend-id.js +321 -0
  200. package/dist/tui/web-login-screen.js +4 -3
  201. package/dist/tui/web-login.d.ts +7 -15
  202. package/dist/tui/web-login.js +38 -78
  203. package/dist/tui/worker-transcripts.d.ts +31 -0
  204. package/dist/tui/worker-transcripts.js +91 -0
  205. package/dist/vascend/candidate-collector.d.ts +6 -5
  206. package/dist/vascend/candidate-collector.js +44 -43
  207. package/dist/vascend/context-feedback-loop.js +2 -0
  208. package/dist/vascend/context-lint.js +32 -2
  209. package/dist/vascend/context-market-selection.js +12 -5
  210. package/dist/vascend/context-pack-render.js +10 -4
  211. package/dist/vascend/context-packet-mirrors.d.ts +8 -0
  212. package/dist/vascend/context-packet-mirrors.js +24 -0
  213. package/dist/vascend/context-plan-events.d.ts +4 -0
  214. package/dist/vascend/context-plan-events.js +44 -0
  215. package/dist/vascend/context-snapshot.d.ts +1 -0
  216. package/dist/vascend/context-snapshot.js +8 -5
  217. package/dist/vascend/context-store-reader.d.ts +20 -0
  218. package/dist/vascend/context-store-reader.js +46 -0
  219. package/dist/vascend/context-window-compiler.d.ts +1 -0
  220. package/dist/vascend/context-window-compiler.js +1 -0
  221. package/dist/vascend/file-lock.d.ts +1 -0
  222. package/dist/vascend/file-lock.js +26 -0
  223. package/dist/vascend/granular-kingdom.d.ts +1 -0
  224. package/dist/vascend/granular-kingdom.js +52 -17
  225. package/dist/vascend/kingdom-bus.d.ts +1 -0
  226. package/dist/vascend/kingdom-bus.js +3 -0
  227. package/dist/vascend/kingdom-channel.d.ts +2 -0
  228. package/dist/vascend/kingdom-channel.js +3 -0
  229. package/dist/vascend/kingdom-live.d.ts +32 -4
  230. package/dist/vascend/kingdom-live.js +149 -13
  231. package/dist/vascend/kingdom-run.d.ts +5 -1
  232. package/dist/vascend/kingdom-run.js +6 -2
  233. package/dist/vascend/kingdom.d.ts +3 -0
  234. package/dist/vascend/kingdom.js +22 -11
  235. package/dist/vascend/memory-packet.d.ts +2 -1
  236. package/dist/vascend/memory-packet.js +31 -44
  237. package/dist/vascend/memory-store.d.ts +1 -0
  238. package/dist/vascend/memory-store.js +12 -4
  239. package/dist/vascend/plan-store.d.ts +3 -1
  240. package/dist/vascend/plan-store.js +6 -4
  241. package/dist/vascend/room-memory.d.ts +13 -2
  242. package/dist/vascend/room-memory.js +48 -6
  243. package/dist/vascend/runtime.d.ts +3 -11
  244. package/dist/vascend/runtime.js +11 -123
  245. package/dist/vascend/session-search.js +2 -0
  246. package/dist/vascend/team-session.js +3 -2
  247. package/dist/vascend/vision.d.ts +3 -1
  248. package/dist/vascend/vision.js +6 -4
  249. package/dist/vascend/worker-session.d.ts +20 -0
  250. package/dist/vascend/worker-session.js +83 -0
  251. package/package.json +2 -2
@@ -9,7 +9,9 @@ import { runKingdomForHost } from "../vascend/kingdom-run.js";
9
9
  import { FileCoordinationHub, fileActor } from "../core/file-coordination.js";
10
10
  import { listSessions, sessionFilePath } from "../core/session.js";
11
11
  import { defaultRegistry, resolveStartupModel } from "../providers/registry.js";
12
+ import { refreshCliModels } from "../providers/cli-models.js";
12
13
  import { listVibeModes, parseVibeMode, VIBE_MODE_IDS } from "../core/vibe-modes.js";
14
+ import { fusionAvailability, fusionUnavailableMessage, parseFusion } from "../core/fusion.js";
13
15
  import { contextProjectKey, loadContextFrame, renderContextFrame, resetContextFrame, updateContextPins, } from "../vascend/context-fabric.js";
14
16
  import { AsyncQueue } from "./async-queue.js";
15
17
  import { MCP_APP_MIME_TYPE, MCP_PROTOCOL_VERSION, MCP_UI_EXTENSION_ID } from "../mcp/protocol.js";
@@ -32,6 +34,7 @@ export const HORIZON_CAPABILITIES = Object.freeze({
32
34
  abort: true,
33
35
  inputs: Object.freeze(["text", "content-parts"]),
34
36
  ephemeralContext: true,
37
+ turnSystem: true,
35
38
  // L'host puo' consegnare messaggi a turno in corso: entrano al primo
36
39
  // confine sicuro e l'evento `user_message` ne annuncia la presa in carico.
37
40
  midTurnInput: true,
@@ -48,6 +51,12 @@ export const HORIZON_CAPABILITIES = Object.freeze({
48
51
  receipts: true,
49
52
  }),
50
53
  vibe: Object.freeze({ routing: Object.freeze(["auto", "explicit"]), modes: Object.freeze([...VIBE_MODE_IDS]) }),
54
+ fusion: Object.freeze({
55
+ decider: "jev",
56
+ decisions: Object.freeze(["route", "focus", "gate"]),
57
+ gateModes: Object.freeze(["guard", "auto"]),
58
+ plans: "paid",
59
+ }),
51
60
  permissions: Object.freeze({ default: "deny", hostGate: true }),
52
61
  fileCoordination: Object.freeze({
53
62
  modes: Object.freeze(["viewing", "editing", "writing"]),
@@ -62,6 +71,15 @@ export const HORIZON_CAPABILITIES = Object.freeze({
62
71
  apps: Object.freeze({ extension: MCP_UI_EXTENSION_ID, mimeType: MCP_APP_MIME_TYPE }),
63
72
  }),
64
73
  });
74
+ export class HorizonFusionUnavailableError extends Error {
75
+ reason;
76
+ code = "HORIZON_FUSION_UNAVAILABLE";
77
+ constructor(reason, message) {
78
+ super(message);
79
+ this.reason = reason;
80
+ this.name = "HorizonFusionUnavailableError";
81
+ }
82
+ }
65
83
  export class HorizonStateError extends Error {
66
84
  code = "HORIZON_INVALID_STATE";
67
85
  constructor(message) {
@@ -191,6 +209,15 @@ export class HorizonEngine {
191
209
  const effective = resolveStartupModel(providers, requested).model;
192
210
  return modelCatalog(providers, effective);
193
211
  }
212
+ /**
213
+ * Come {@link inspectModels}, ma prima rilegge i modelli dai CLI ufficiali
214
+ * installati (claude, codex) quando sono cambiati: un Claude Code aggiornato
215
+ * espone subito i suoi modelli nuovi.
216
+ */
217
+ static async inspectModelsLive(options = {}) {
218
+ await refreshCliModels(options.env ?? process.env);
219
+ return HorizonEngine.inspectModels(options);
220
+ }
194
221
  get state() {
195
222
  return this.stateValue;
196
223
  }
@@ -257,6 +284,32 @@ export class HorizonEngine {
257
284
  this.harness.config = { ...this.harness.config, vibeMode: selection };
258
285
  return this.vibe();
259
286
  }
287
+ /**
288
+ * Stato Fusion. `tier` e' il piano della sessione Vascend dell'host, se lo
289
+ * conosce: serve solo a decidere cosa mostrare, il backend lo riverifica.
290
+ */
291
+ fusion(tier) {
292
+ this.assertOpen();
293
+ const availability = fusionAvailability(this.harness.env, tier, Boolean(this.options.fusionProxy));
294
+ return {
295
+ config: this.harness.config.fusion ?? null,
296
+ available: availability.ok,
297
+ channel: availability.channel,
298
+ ...(availability.ok ? {} : { reason: availability.reason, message: fusionUnavailableMessage(availability.reason) }),
299
+ };
300
+ }
301
+ /** Attiva (true o configurazione) o spegne (null/false) Fusion per i turni successivi. */
302
+ setFusion(value, tier) {
303
+ this.assertMutable();
304
+ const config = parseFusion(value) ?? null;
305
+ if (config) {
306
+ const availability = fusionAvailability(this.harness.env, tier, Boolean(this.options.fusionProxy));
307
+ if (!availability.ok)
308
+ throw new HorizonFusionUnavailableError(availability.reason, fusionUnavailableMessage(availability.reason));
309
+ }
310
+ this.harness.config = { ...this.harness.config, fusion: config };
311
+ return this.fusion(tier);
312
+ }
260
313
  contextStatus() {
261
314
  this.assertOpen();
262
315
  return this.contextSnapshot(this.currentFrame());
@@ -369,6 +422,7 @@ export class HorizonEngine {
369
422
  ...base,
370
423
  inbox,
371
424
  spawnKingdom,
425
+ ...(this.options.fusionProxy ? { jevProxy: this.options.fusionProxy } : {}),
372
426
  ...(this.options.fileCoordination ? { fileCoordination: this.options.fileCoordination } : {}),
373
427
  ...(options.fsRoots !== undefined ? { fsRoots: options.fsRoots } : {}),
374
428
  ...(ephemeralContext !== undefined ? { ephemeralContext } : {}),
@@ -490,6 +544,7 @@ export class HorizonEngine {
490
544
  messages: this.harness.session.history().length,
491
545
  model: this.harness.config.model,
492
546
  vibeMode: this.harness.config.vibeMode,
547
+ fusion: this.harness.config.fusion ?? null,
493
548
  };
494
549
  }
495
550
  currentFrame() {
@@ -535,11 +590,13 @@ function errorMessage(value) {
535
590
  return value instanceof Error ? value.message : String(value);
536
591
  }
537
592
  function modelDescriptor(provider, modelId) {
593
+ const known = provider.modelLabels?.get(modelId);
538
594
  return {
539
595
  id: `${provider.id}/${modelId}`,
540
596
  modelId,
541
597
  providerId: provider.id,
542
598
  providerLabel: provider.label ?? provider.id,
599
+ ...(known ? { label: known.label, ...(known.description ? { description: known.description } : {}) } : {}),
543
600
  };
544
601
  }
545
602
  const DEFAULT_MODEL = "anthropic/claude-sonnet-4-6";
@@ -1,4 +1,4 @@
1
- export { HORIZON_CAPABILITIES, HorizonContextUnavailableError, HorizonEngine, HorizonSessionNotFoundError, HorizonStateError, routeWorkerModels, } from "./engine.js";
1
+ export { HORIZON_CAPABILITIES, HorizonContextUnavailableError, HorizonFusionUnavailableError, HorizonEngine, HorizonSessionNotFoundError, HorizonStateError, routeWorkerModels, } from "./engine.js";
2
2
  export { HorizonMainTeam, needsGuideArbitration } from "./main-team.js";
3
- export type { HorizonCapabilityDescriptor, HorizonContextStatus, HorizonEngineOptions, HorizonEngineState, HorizonModelCatalog, HorizonModelDescriptor, HorizonModelInspectionOptions, HorizonProviderDescriptor, HorizonRuntime, HorizonSessionListItem, HorizonSessionSnapshot, HorizonTurn, HorizonTurnInput, HorizonTurnOptions, HorizonTurnResult, HorizonTurnStatus, HorizonVibeStatus, } from "./types.js";
3
+ export type { HorizonCapabilityDescriptor, HorizonContextStatus, HorizonEngineOptions, HorizonEngineState, HorizonModelCatalog, HorizonModelDescriptor, HorizonModelInspectionOptions, HorizonProviderDescriptor, HorizonRuntime, HorizonSessionListItem, HorizonSessionSnapshot, HorizonTurn, HorizonTurnInput, HorizonTurnOptions, HorizonTurnResult, HorizonTurnStatus, HorizonVibeStatus, HorizonFusionStatus, } from "./types.js";
4
4
  export type { HorizonGuideCooperation, HorizonGuideDecision, HorizonGuideDecisionRecord, HorizonGuideDecisionRequest, HorizonGuidePlan, HorizonGuideTask, HorizonGuideVerdict, HorizonMainExchange, HorizonMainRef, HorizonMainTeamConfig, HorizonMainTeamEvent, HorizonMainTeamResult, HorizonMainTeamTurn, HorizonTeamRef, HorizonAgentPurpose, } from "./main-team.js";
@@ -1,2 +1,2 @@
1
- export { HORIZON_CAPABILITIES, HorizonContextUnavailableError, HorizonEngine, HorizonSessionNotFoundError, HorizonStateError, routeWorkerModels, } from "./engine.js";
1
+ export { HORIZON_CAPABILITIES, HorizonContextUnavailableError, HorizonFusionUnavailableError, HorizonEngine, HorizonSessionNotFoundError, HorizonStateError, routeWorkerModels, } from "./engine.js";
2
2
  export { HorizonMainTeam, needsGuideArbitration } from "./main-team.js";
@@ -3,6 +3,7 @@ import type { Harness } from "../core/bootstrap.js";
3
3
  import type { ContextLease, ContextReceipt } from "../vascend/context-fabric.js";
4
4
  import type { AgentEvent, ContentPart, ModelTurn } from "../core/types.js";
5
5
  import type { VibeModeId, VibeModeSelection, VibeModeSpec } from "../core/vibe-modes.js";
6
+ import type { FusionConfig, FusionGateMode } from "../core/fusion.js";
6
7
  import type { FileCoordinationContext } from "../core/file-coordination.js";
7
8
  import type { ExecutionTarget } from "../core/execution-target.js";
8
9
  /** Lifecycle osservabile del facade headless. */
@@ -20,6 +21,11 @@ export interface HorizonEngineOptions {
20
21
  readonly env?: NodeJS.ProcessEnv;
21
22
  /** Evidenza host non persistente applicata ai turni, salvo override locale. */
22
23
  readonly ephemeralContext?: string;
24
+ /**
25
+ * Trasporto Jev per Fusion sul canale client: l'host inoltra il corpo
26
+ * all'endpoint Fusion di Vascend con la propria sessione.
27
+ */
28
+ readonly fusionProxy?: import("../fusion/jev.js").JevProxy;
23
29
  /** Permission gate dell'host. In assenza, Horizon nega ogni azione. */
24
30
  readonly confirm?: AgentDeps["confirm"];
25
31
  /** Canale headless per le domande del modello. In assenza, non viene esposto. */
@@ -105,6 +111,7 @@ export interface HorizonSessionSnapshot {
105
111
  readonly messages: number;
106
112
  readonly model: string;
107
113
  readonly vibeMode: VibeModeSelection;
114
+ readonly fusion: FusionConfig | null;
108
115
  }
109
116
  export interface HorizonSessionListItem {
110
117
  readonly id: string;
@@ -122,6 +129,9 @@ export interface HorizonModelDescriptor {
122
129
  readonly modelId: string;
123
130
  readonly providerId: string;
124
131
  readonly providerLabel: string;
132
+ /** Nome del modello secondo il provider (es. "Opus 5.5"), quando noto. */
133
+ readonly label?: string;
134
+ readonly description?: string;
125
135
  }
126
136
  /** Provider attivo nel registro Talos, senza credenziali o dettagli sensibili. */
127
137
  export interface HorizonProviderDescriptor {
@@ -174,6 +184,7 @@ export interface HorizonCapabilityDescriptor {
174
184
  readonly abort: true;
175
185
  readonly inputs: readonly ["text", "content-parts"];
176
186
  readonly ephemeralContext: true;
187
+ readonly turnSystem: true;
177
188
  };
178
189
  readonly mainTeam: {
179
190
  readonly agents: readonly [1, 2];
@@ -190,6 +201,12 @@ export interface HorizonCapabilityDescriptor {
190
201
  readonly routing: readonly ["auto", "explicit"];
191
202
  readonly modes: readonly VibeModeId[];
192
203
  };
204
+ readonly fusion: {
205
+ readonly decider: "jev";
206
+ readonly decisions: readonly ["route", "focus", "gate"];
207
+ readonly gateModes: readonly FusionGateMode[];
208
+ readonly plans: "paid";
209
+ };
193
210
  readonly permissions: {
194
211
  readonly default: "deny";
195
212
  readonly hostGate: true;
@@ -227,6 +244,18 @@ export interface HorizonRuntime {
227
244
  }): Promise<Harness>;
228
245
  runTurn(deps: AgentDeps, input: HorizonTurnInput, sink: (event: AgentEvent) => void, signal: AbortSignal): Promise<void>;
229
246
  }
247
+ /**
248
+ * Fusion per l'host: configurazione attiva e disponibilita' su questa
249
+ * installazione. `available: false` spiega perche' (chiave dev mancante,
250
+ * login assente, piano free): l'host mostra la voce ma non la attiva.
251
+ */
252
+ export interface HorizonFusionStatus {
253
+ readonly config: FusionConfig | null;
254
+ readonly available: boolean;
255
+ readonly channel: "dev" | "client";
256
+ readonly reason?: "missing_openrouter" | "missing_login" | "free_plan";
257
+ readonly message?: string;
258
+ }
230
259
  export interface HorizonVibeStatus {
231
260
  readonly selected: VibeModeSelection;
232
261
  readonly modes: readonly VibeModeSpec[];
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { startRepl } from "./tui/repl.js";
10
10
  import { startRpc } from "./rpc/server.js";
11
11
  import { proxyUrl } from "./providers/registry.js";
12
12
  import { CliArgumentError, cliHelp, parseCliArgs } from "./core/cli-args.js";
13
+ import { InvalidConfigError } from "./core/config.js";
13
14
  function readStdin() {
14
15
  try {
15
16
  return readFileSync(0, "utf8").trim();
@@ -102,6 +103,11 @@ async function main() {
102
103
  process.exitCode = doctorExitCode(report);
103
104
  return;
104
105
  }
106
+ const { prepareStartupConfig } = await import("./tui/config-recovery.js");
107
+ if (!await prepareStartupConfig({ cwd: args.cwd, model: args.model, env }, args.mode === "tui" && Boolean(process.stdin.isTTY && process.stdout.isTTY))) {
108
+ process.exitCode = 2;
109
+ return;
110
+ }
105
111
  const trustPrompt = args.mode === "tui" && !args.approve && !args.noApprove
106
112
  ? (resources) => askTrust(resources)
107
113
  : undefined;
@@ -230,6 +236,12 @@ main().catch((err) => {
230
236
  process.stdout.write(`\n ${err.message}\n\n`, () => process.exit(130));
231
237
  return;
232
238
  }
239
+ if (err instanceof InvalidConfigError) {
240
+ process.stderr.write(`errore: ${err.message}\n` +
241
+ `Correggi o ripristina il file JSON, quindi riavvia Talos. Il login non è stato avviato.\n`);
242
+ process.exitCode = 2;
243
+ return;
244
+ }
233
245
  const report = writeCrashReport(err, "main");
234
246
  process.stderr.write(`fatale: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
235
247
  if (report)
@@ -1,3 +1,4 @@
1
+ import type { CliModel } from "./cli-models.js";
1
2
  import type { CompletionRequest, ModelTurn, Provider } from "../core/types.js";
2
3
  import { type OAuthCreds } from "./oauth-store.js";
3
4
  export declare class AnthropicOAuthProvider implements Provider {
@@ -5,9 +6,13 @@ export declare class AnthropicOAuthProvider implements Provider {
5
6
  private readonly env;
6
7
  private readonly now;
7
8
  readonly id = "claude-code";
8
- readonly label = "Claude Code (abbonamento)";
9
- readonly models: readonly ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5-20251001", "claude-opus-4-8", "claude-sonnet-4-6"];
10
- constructor(creds: OAuthCreds, env: NodeJS.ProcessEnv, now?: () => number);
9
+ readonly label = "Claude Code";
10
+ readonly models: readonly string[];
11
+ readonly modelLabels?: ReadonlyMap<string, {
12
+ readonly label: string;
13
+ readonly description?: string;
14
+ }>;
15
+ constructor(creds: OAuthCreds, env: NodeJS.ProcessEnv, now?: () => number, cliModels?: readonly CliModel[]);
11
16
  private ensureToken;
12
17
  complete(req: CompletionRequest): Promise<ModelTurn>;
13
18
  }
@@ -1,6 +1,6 @@
1
1
  import { messaggioErroreTurno } from "../core/image-limits.js";
2
2
  import { planLimitsFromHeaders } from "./plan-limits.js";
3
- import { toApiMessages, parseStream, applyMessageCache, wants1m, CONTEXT_1M_BETA } from "./anthropic.js";
3
+ import { claudePrompt, claudeCacheTtl, parseStream, wants1m, CONTEXT_1M_BETA } from "./anthropic.js";
4
4
  import { anthropicEffort } from "./effort.js";
5
5
  import { resolveMaxTokens } from "../core/model-limits.js";
6
6
  import { ANTHROPIC_MODELS } from "./catalog.js";
@@ -28,12 +28,18 @@ export class AnthropicOAuthProvider {
28
28
  env;
29
29
  now;
30
30
  id = "claude-code";
31
- label = "Claude Code (abbonamento)";
32
- models = ANTHROPIC_MODELS;
33
- constructor(creds, env, now = () => Date.now()) {
31
+ label = "Claude Code";
32
+ models;
33
+ modelLabels;
34
+ constructor(creds, env, now = () => Date.now(),
35
+ // Modelli del CLI ufficiale installato; senza, il catalogo statico.
36
+ cliModels) {
34
37
  this.creds = creds;
35
38
  this.env = env;
36
39
  this.now = now;
40
+ this.models = cliModels?.length ? cliModels.map((model) => model.id) : ANTHROPIC_MODELS;
41
+ if (cliModels?.length)
42
+ this.modelLabels = new Map(cliModels.map((model) => [model.id, model]));
37
43
  }
38
44
  async ensureToken(signal) {
39
45
  if (!isExpired(this.creds, this.now()) || !this.creds.refreshToken) {
@@ -74,18 +80,15 @@ export class AnthropicOAuthProvider {
74
80
  stopReason: "error",
75
81
  };
76
82
  }
83
+ const prompt = claudePrompt(req, claudeCacheTtl(this.env));
77
84
  const body = {
78
85
  model: req.model,
79
86
  max_tokens: resolveMaxTokens(req.maxTokens, `anthropic/${req.model}`),
80
87
  system: [
81
88
  { type: "text", text: CLAUDE_CODE_SYSTEM },
82
- { type: "text", text: req.system, cache_control: { type: "ephemeral" } },
83
- ...(req.turnContext ? [{
84
- type: "text",
85
- text: `[HORIZON_TURN_CONTEXT]\n${req.turnContext}\n[/HORIZON_TURN_CONTEXT]`,
86
- }] : []),
89
+ ...prompt.system,
87
90
  ],
88
- messages: applyMessageCache(toApiMessages(req)),
91
+ messages: prompt.messages,
89
92
  stream: true,
90
93
  tools: req.tools.map((t) => ({
91
94
  name: t.name,
@@ -11,8 +11,15 @@ interface ApiMessage {
11
11
  }
12
12
  export declare function toApiMessages(req: CompletionRequest): unknown[];
13
13
  export declare function normalizeToolPairing(messages: ApiMessage[]): ApiMessage[];
14
- export declare function withSystemCache(system: string, turnContext?: string): unknown[];
15
- export declare function applyMessageCache(messages: unknown[]): unknown[];
14
+ export type CacheTtl = "5m" | "1h";
15
+ export declare function claudeCacheTtl(env: NodeJS.ProcessEnv): CacheTtl;
16
+ export declare function supportsConversationSystem(model: string): boolean;
17
+ export declare function withSystemCache(system: string, turnContext?: string, ttl?: CacheTtl): unknown[];
18
+ export declare function applyMessageCache(messages: unknown[], ttl?: CacheTtl): unknown[];
19
+ export declare function claudePrompt(req: CompletionRequest, ttl?: CacheTtl): {
20
+ system: unknown[];
21
+ messages: unknown[];
22
+ };
16
23
  export declare const CONTEXT_1M_BETA = "context-1m-2025-08-07";
17
24
  export declare function wants1m(model: string, env: NodeJS.ProcessEnv): boolean;
18
25
  export declare class AnthropicProvider implements Provider {
@@ -163,12 +163,34 @@ export function normalizeToolPairing(messages) {
163
163
  }
164
164
  return out;
165
165
  }
166
- // Applica i breakpoint di cache (prompt caching Anthropic): marca l'ultimo
167
- // blocco di system e l'ultimo blocco dell'ultimo messaggio con cache_control
168
- // ephemeral. Riduce i token fatturati sui turni successivi.
169
- export function withSystemCache(system, turnContext = "") {
166
+ // Un'ora come Claude Code, per abbonamento e API key: ogni lettura rinnova la
167
+ // scadenza, quindi le sessioni con pause oltre i 5 minuti non riscrivono tutto
168
+ // il prefisso. TALOS_CLAUDE_CACHE_TTL=5m torna alla durata breve.
169
+ export function claudeCacheTtl(env) {
170
+ return env["TALOS_CLAUDE_CACHE_TTL"] === "5m" ? "5m" : "1h";
171
+ }
172
+ const CACHE_TTL_MS = { "5m": 300_000, "1h": 3_600_000 };
173
+ // Durata applicata dal provider alla cache di questa risposta: la dice la
174
+ // suddivisione di cache_creation per TTL. Una lettura rinnova la durata gia'
175
+ // scritta, che resta quella dell'ultima scrittura nota.
176
+ function reportedCacheTtlMs(usage) {
177
+ const creation = usage["cache_creation"];
178
+ if (Number(creation?.["ephemeral_1h_input_tokens"] ?? 0) > 0)
179
+ return CACHE_TTL_MS["1h"];
180
+ if (Number(creation?.["ephemeral_5m_input_tokens"] ?? 0) > 0)
181
+ return CACHE_TTL_MS["5m"];
182
+ return undefined;
183
+ }
184
+ function cacheControl(ttl) {
185
+ return { type: "ephemeral", ...(ttl === "1h" ? { ttl } : {}) };
186
+ }
187
+ // Only documented models accept authoritative system messages in the history.
188
+ export function supportsConversationSystem(model) {
189
+ return /^claude-(?:opus-(?:5(?:-\d)?|4-8)|(?:fable|mythos)-5(?:-\d)?)(?:-\d{8})?$/.test(model);
190
+ }
191
+ export function withSystemCache(system, turnContext = "", ttl = "5m") {
170
192
  return [
171
- { type: "text", text: system, cache_control: { type: "ephemeral" } },
193
+ { type: "text", text: system, cache_control: cacheControl(ttl) },
172
194
  ...(turnContext ? [{
173
195
  type: "text",
174
196
  text: `[HORIZON_TURN_CONTEXT]\n${turnContext}\n[/HORIZON_TURN_CONTEXT]`,
@@ -181,20 +203,39 @@ export function withSystemCache(system, turnContext = "") {
181
203
  // finisce col turno precedente resta cacheato e viene riconosciuto al turno
182
204
  // dopo. Insieme al breakpoint su system restano 3 marcatori, sotto il tetto di
183
205
  // 4 dell'API.
184
- export function applyMessageCache(messages) {
185
- markCacheBreakpoint(messages[messages.length - 1]);
186
- if (messages.length > 1)
187
- markCacheBreakpoint(messages[messages.length - 2]);
188
- return messages;
206
+ export function applyMessageCache(messages, ttl = "5m") {
207
+ // Provider projections must not mutate a caller's history.
208
+ const copy = messages.map((message) => {
209
+ const m = message;
210
+ return { ...m, ...(Array.isArray(m.content) ? { content: m.content.map(b => ({ ...b })) } : {}) };
211
+ });
212
+ markCacheBreakpoint(copy[copy.length - 1], ttl);
213
+ if (copy.length > 1)
214
+ markCacheBreakpoint(copy[copy.length - 2], ttl);
215
+ return copy;
189
216
  }
190
- function markCacheBreakpoint(message) {
217
+ function markCacheBreakpoint(message, ttl) {
191
218
  const target = message;
192
219
  if (!target || !Array.isArray(target.content) || !target.content.length)
193
220
  return;
194
221
  const blocks = target.content;
195
222
  const lastBlock = blocks[blocks.length - 1];
196
223
  if (lastBlock)
197
- lastBlock["cache_control"] = { type: "ephemeral" };
224
+ lastBlock["cache_control"] = cacheControl(ttl);
225
+ }
226
+ export function claudePrompt(req, ttl = "5m") {
227
+ const inline = supportsConversationSystem(req.model);
228
+ const messages = toApiMessages(req);
229
+ if (inline && req.turnContext)
230
+ messages.push({ role: "system", content: [{
231
+ type: "text", text: `[HORIZON_TURN_CONTEXT]\n${req.turnContext}\n[/HORIZON_TURN_CONTEXT]`,
232
+ }] });
233
+ // Cache the history immediately BEFORE the volatile tail as well as the tail.
234
+ // On the next request only the tail is replaced; all prior history still matches.
235
+ return {
236
+ system: withSystemCache(req.system, inline ? "" : req.turnContext, ttl),
237
+ messages: applyMessageCache(messages, ttl),
238
+ };
198
239
  }
199
240
  import { anthropicEffort } from "./effort.js";
200
241
  import { resolveMaxTokens } from "../core/model-limits.js";
@@ -233,8 +274,7 @@ export class AnthropicProvider {
233
274
  const body = {
234
275
  model: req.model,
235
276
  max_tokens: resolveMaxTokens(req.maxTokens, `anthropic/${req.model}`),
236
- system: withSystemCache(req.system, req.turnContext),
237
- messages: applyMessageCache(toApiMessages(req)),
277
+ ...claudePrompt(req, claudeCacheTtl(process.env)),
238
278
  stream: true,
239
279
  tools: req.tools.map((t) => ({
240
280
  name: t.name,
@@ -331,7 +371,7 @@ export async function parseStream(body, onDelta, onActivity, onReasoning, onTool
331
371
  stopReason ??= "end";
332
372
  sawTerminal = true;
333
373
  }
334
- const u = readUsage(evt);
374
+ const u = readUsage(evt, usage);
335
375
  if (u)
336
376
  usage = u;
337
377
  };
@@ -511,16 +551,19 @@ function readProviderError(evt) {
511
551
  const message = String(raw?.["message"] ?? evt["message"] ?? "errore streaming senza dettagli");
512
552
  return `Errore provider Anthropic (${kind}): ${message}${errorTimingDetail(raw)}${errorTimingDetail(evt)}`;
513
553
  }
514
- function readUsage(evt) {
515
- const usage = evt["usage"];
554
+ function readUsage(evt, previous) {
555
+ const message = evt["message"];
556
+ const usage = (evt["usage"] ?? message?.["usage"]);
516
557
  if (!usage)
517
558
  return undefined;
518
559
  const cacheRead = usage["cache_read_input_tokens"];
519
560
  const cacheWrite = usage["cache_creation_input_tokens"];
561
+ const cacheTtlMs = reportedCacheTtlMs(usage) ?? previous?.cacheTtlMs;
520
562
  return {
521
- inputTokens: Number(usage["input_tokens"] ?? 0),
522
- outputTokens: Number(usage["output_tokens"] ?? 0),
523
- ...(cacheRead !== undefined ? { cacheReadTokens: Number(cacheRead) } : {}),
524
- ...(cacheWrite !== undefined ? { cacheWriteTokens: Number(cacheWrite) } : {}),
563
+ inputTokens: Number(usage["input_tokens"] ?? previous?.inputTokens ?? 0),
564
+ outputTokens: Number(usage["output_tokens"] ?? previous?.outputTokens ?? 0),
565
+ ...((cacheRead ?? previous?.cacheReadTokens) !== undefined ? { cacheReadTokens: Number(cacheRead ?? previous?.cacheReadTokens) } : {}),
566
+ ...((cacheWrite ?? previous?.cacheWriteTokens) !== undefined ? { cacheWriteTokens: Number(cacheWrite ?? previous?.cacheWriteTokens) } : {}),
567
+ ...(cacheTtlMs !== undefined ? { cacheTtlMs } : {}),
525
568
  };
526
569
  }
@@ -0,0 +1,30 @@
1
+ export type CliName = "claude" | "codex";
2
+ export interface CliModel {
3
+ readonly id: string;
4
+ readonly label: string;
5
+ readonly description?: string;
6
+ }
7
+ /** Primo CLI nel PATH con l'impronta del binario reale (symlink risolti), o null se non installato. */
8
+ export declare function findCli(name: CliName, env: NodeJS.ProcessEnv): {
9
+ readonly executable: string;
10
+ readonly stamp: string;
11
+ } | null;
12
+ /** Modelli in cache per il CLI installato ora, o undefined se assenti o superati da un aggiornamento. */
13
+ export declare function cachedCliModels(name: CliName, env: NodeJS.ProcessEnv): readonly CliModel[] | undefined;
14
+ /**
15
+ * Righe di `initialize` del CLI Claude Code. Gli alias (default, opus, sonnet)
16
+ * diventano il modello versionato che eseguono: una voce per modello reale,
17
+ * nell'ordine del CLI.
18
+ */
19
+ export declare function claudeModelsFromInitialize(models: unknown): CliModel[];
20
+ /** Righe di `model/list` dell'app-server Codex, senza i modelli nascosti. */
21
+ export declare function codexModelsFromList(data: unknown): CliModel[];
22
+ /**
23
+ * Rilegge i modelli dai CLI installati la cui cache manca o e' superata, e la
24
+ * salva. Un CLI assente, lento o in errore lascia la cache com'e': il catalogo
25
+ * ripiega su quello statico.
26
+ */
27
+ export declare function refreshCliModels(env: NodeJS.ProcessEnv, options?: {
28
+ readonly timeoutMs?: number;
29
+ readonly force?: boolean;
30
+ }): Promise<void>;