@vascend/talos 0.1.5 → 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 (224) hide show
  1. package/dist/core/agent.d.ts +16 -3
  2. package/dist/core/agent.js +114 -34
  3. package/dist/core/background.d.ts +10 -3
  4. package/dist/core/background.js +55 -9
  5. package/dist/core/bootstrap.js +1 -0
  6. package/dist/core/compaction.js +1 -1
  7. package/dist/core/config-storage.d.ts +5 -0
  8. package/dist/core/config-storage.js +83 -0
  9. package/dist/core/config.d.ts +11 -2
  10. package/dist/core/config.js +24 -8
  11. package/dist/core/context-ledger.js +2 -1
  12. package/dist/core/context-policy.d.ts +1 -1
  13. package/dist/core/context-policy.js +10 -7
  14. package/dist/core/credentials.js +29 -30
  15. package/dist/core/effort.d.ts +1 -1
  16. package/dist/core/effort.js +5 -1
  17. package/dist/core/execution-guidance.d.ts +1 -1
  18. package/dist/core/execution-guidance.js +2 -0
  19. package/dist/core/file-coordination.d.ts +3 -0
  20. package/dist/core/file-coordination.js +28 -0
  21. package/dist/core/file-lease-store.d.ts +3 -0
  22. package/dist/core/file-lease-store.js +28 -12
  23. package/dist/core/fusion.d.ts +24 -0
  24. package/dist/core/fusion.js +80 -0
  25. package/dist/core/model-limits.d.ts +1 -0
  26. package/dist/core/model-limits.js +5 -1
  27. package/dist/core/permission-queue.js +15 -15
  28. package/dist/core/request-plan.js +4 -3
  29. package/dist/core/session-naming.js +4 -0
  30. package/dist/core/session.d.ts +4 -2
  31. package/dist/core/session.js +13 -4
  32. package/dist/core/settings.d.ts +2 -0
  33. package/dist/core/settings.js +5 -16
  34. package/dist/core/step-policy.d.ts +18 -0
  35. package/dist/core/step-policy.js +6 -0
  36. package/dist/core/tool-artifacts.d.ts +2 -0
  37. package/dist/core/tool-artifacts.js +21 -3
  38. package/dist/core/tool-result-index.js +29 -33
  39. package/dist/core/tool-result-store.js +6 -5
  40. package/dist/core/types.d.ts +10 -3
  41. package/dist/core/user-inbox.d.ts +9 -0
  42. package/dist/core/user-inbox.js +22 -3
  43. package/dist/core/user-input.js +1 -1
  44. package/dist/fusion/autopilot-fast.d.ts +59 -0
  45. package/dist/fusion/autopilot-fast.js +213 -0
  46. package/dist/fusion/autopilot.d.ts +75 -0
  47. package/dist/fusion/autopilot.js +308 -0
  48. package/dist/fusion/focus.d.ts +30 -0
  49. package/dist/fusion/focus.js +122 -0
  50. package/dist/fusion/gate.d.ts +30 -0
  51. package/dist/fusion/gate.js +95 -0
  52. package/dist/fusion/jev.d.ts +71 -0
  53. package/dist/fusion/jev.js +164 -0
  54. package/dist/fusion/receipts.d.ts +25 -0
  55. package/dist/fusion/receipts.js +34 -0
  56. package/dist/fusion/route.d.ts +25 -0
  57. package/dist/fusion/route.js +84 -0
  58. package/dist/fusion/run.d.ts +4 -0
  59. package/dist/fusion/run.js +186 -0
  60. package/dist/horizon/engine.d.ts +20 -1
  61. package/dist/horizon/engine.js +57 -0
  62. package/dist/horizon/index.d.ts +2 -2
  63. package/dist/horizon/index.js +1 -1
  64. package/dist/horizon/types.d.ts +29 -0
  65. package/dist/index.js +12 -0
  66. package/dist/providers/anthropic-oauth.d.ts +8 -3
  67. package/dist/providers/anthropic-oauth.js +13 -10
  68. package/dist/providers/anthropic.d.ts +9 -2
  69. package/dist/providers/anthropic.js +64 -21
  70. package/dist/providers/cli-models.d.ts +30 -0
  71. package/dist/providers/cli-models.js +194 -0
  72. package/dist/providers/model-effort.js +1 -1
  73. package/dist/providers/openai-oauth.d.ts +8 -3
  74. package/dist/providers/openai-oauth.js +9 -3
  75. package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
  76. package/dist/providers/openrouter-manual-fallback.js +20 -20
  77. package/dist/providers/registry.js +3 -2
  78. package/dist/providers/retry.d.ts +3 -0
  79. package/dist/providers/retry.js +44 -1
  80. package/dist/remote/cifratura.d.ts +12 -0
  81. package/dist/remote/cifratura.js +66 -0
  82. package/dist/remote/index.d.ts +15 -0
  83. package/dist/remote/index.js +65 -0
  84. package/dist/remote/relay-client.d.ts +58 -0
  85. package/dist/remote/relay-client.js +315 -0
  86. package/dist/remote/sessione-remota.d.ts +60 -0
  87. package/dist/remote/sessione-remota.js +266 -0
  88. package/dist/sdk.d.ts +7 -2
  89. package/dist/sdk.js +3 -1
  90. package/dist/tools/builtin.js +41 -20
  91. package/dist/tools/capabilities.d.ts +3 -1
  92. package/dist/tools/capabilities.js +5 -2
  93. package/dist/tools/computer-use.js +2 -1
  94. package/dist/tools/pagination.js +1 -1
  95. package/dist/tools/program.js +34 -21
  96. package/dist/tools/team-tools.js +11 -6
  97. package/dist/tui/account-entry.js +14 -9
  98. package/dist/tui/agent-label.d.ts +2 -0
  99. package/dist/tui/agent-label.js +5 -0
  100. package/dist/tui/agent-sessions-screen.d.ts +12 -2
  101. package/dist/tui/agent-sessions-screen.js +62 -16
  102. package/dist/tui/agent-sessions.d.ts +14 -0
  103. package/dist/tui/agent-sessions.js +67 -6
  104. package/dist/tui/bottom-input.d.ts +46 -2
  105. package/dist/tui/bottom-input.js +358 -59
  106. package/dist/tui/branding.js +8 -4
  107. package/dist/tui/capability-lists.d.ts +10 -0
  108. package/dist/tui/capability-lists.js +41 -0
  109. package/dist/tui/command-output-registry.js +4 -0
  110. package/dist/tui/command-transcript.d.ts +1 -1
  111. package/dist/tui/command-transcript.js +17 -21
  112. package/dist/tui/commands.js +4 -1
  113. package/dist/tui/compaction-status.d.ts +7 -0
  114. package/dist/tui/compaction-status.js +39 -0
  115. package/dist/tui/config-recovery.d.ts +12 -0
  116. package/dist/tui/config-recovery.js +54 -0
  117. package/dist/tui/context-meter.d.ts +3 -0
  118. package/dist/tui/context-meter.js +35 -8
  119. package/dist/tui/diff.d.ts +9 -0
  120. package/dist/tui/diff.js +43 -19
  121. package/dist/tui/fusion-menu.d.ts +5 -0
  122. package/dist/tui/fusion-menu.js +89 -0
  123. package/dist/tui/i18n-catalog.js +312 -1
  124. package/dist/tui/live-command.d.ts +1 -0
  125. package/dist/tui/live-command.js +9 -0
  126. package/dist/tui/live-output.d.ts +1 -0
  127. package/dist/tui/live-output.js +11 -0
  128. package/dist/tui/model-details.js +13 -12
  129. package/dist/tui/model-picker.js +17 -7
  130. package/dist/tui/model-selection.js +19 -10
  131. package/dist/tui/native-scrollback.js +5 -0
  132. package/dist/tui/prompt-queue.d.ts +3 -0
  133. package/dist/tui/prompt-queue.js +19 -1
  134. package/dist/tui/provider-label.d.ts +2 -0
  135. package/dist/tui/provider-label.js +9 -0
  136. package/dist/tui/provider-menu.js +25 -24
  137. package/dist/tui/queue-screen.js +2 -1
  138. package/dist/tui/remote-command.d.ts +7 -0
  139. package/dist/tui/remote-command.js +79 -0
  140. package/dist/tui/repl.d.ts +8 -2
  141. package/dist/tui/repl.js +355 -311
  142. package/dist/tui/review-store.d.ts +1 -0
  143. package/dist/tui/review-store.js +28 -7
  144. package/dist/tui/review-team.js +4 -3
  145. package/dist/tui/runtime-error.d.ts +2 -0
  146. package/dist/tui/runtime-error.js +26 -0
  147. package/dist/tui/selector-mouse.d.ts +1 -0
  148. package/dist/tui/selector-mouse.js +1 -1
  149. package/dist/tui/selector.d.ts +7 -0
  150. package/dist/tui/selector.js +19 -2
  151. package/dist/tui/session-controls.d.ts +26 -0
  152. package/dist/tui/session-controls.js +65 -0
  153. package/dist/tui/session-naming-notice.js +5 -3
  154. package/dist/tui/session-replay.d.ts +1 -0
  155. package/dist/tui/session-replay.js +101 -32
  156. package/dist/tui/session-transcript-layout.d.ts +9 -0
  157. package/dist/tui/session-transcript-layout.js +41 -0
  158. package/dist/tui/startup.js +63 -33
  159. package/dist/tui/status-line.d.ts +1 -1
  160. package/dist/tui/status-line.js +20 -12
  161. package/dist/tui/talos-art-data.d.ts +1 -0
  162. package/dist/tui/talos-art-data.js +2 -0
  163. package/dist/tui/talos-art.d.ts +1 -0
  164. package/dist/tui/talos-art.js +9 -0
  165. package/dist/tui/task-labels.d.ts +1 -0
  166. package/dist/tui/task-labels.js +8 -0
  167. package/dist/tui/terminal-text.d.ts +2 -1
  168. package/dist/tui/terminal-text.js +28 -2
  169. package/dist/tui/theme.js +2 -2
  170. package/dist/tui/tool-presentation.d.ts +3 -0
  171. package/dist/tui/tool-presentation.js +98 -0
  172. package/dist/tui/tool-style.js +4 -0
  173. package/dist/tui/transcript-store.d.ts +25 -0
  174. package/dist/tui/transcript-store.js +151 -0
  175. package/dist/tui/usage-footer.d.ts +1 -1
  176. package/dist/tui/usage-footer.js +3 -1
  177. package/dist/tui/vascend-id.d.ts +49 -0
  178. package/dist/tui/vascend-id.js +321 -0
  179. package/dist/tui/web-login-screen.js +4 -3
  180. package/dist/tui/web-login.d.ts +7 -15
  181. package/dist/tui/web-login.js +38 -78
  182. package/dist/tui/worker-transcripts.d.ts +31 -0
  183. package/dist/tui/worker-transcripts.js +91 -0
  184. package/dist/vascend/candidate-collector.d.ts +6 -5
  185. package/dist/vascend/candidate-collector.js +44 -43
  186. package/dist/vascend/context-lint.js +32 -2
  187. package/dist/vascend/context-market-selection.js +12 -5
  188. package/dist/vascend/context-pack-render.js +10 -4
  189. package/dist/vascend/context-packet-mirrors.d.ts +8 -0
  190. package/dist/vascend/context-packet-mirrors.js +24 -0
  191. package/dist/vascend/context-plan-events.d.ts +4 -0
  192. package/dist/vascend/context-plan-events.js +44 -0
  193. package/dist/vascend/context-snapshot.js +4 -2
  194. package/dist/vascend/context-store-reader.d.ts +20 -0
  195. package/dist/vascend/context-store-reader.js +46 -0
  196. package/dist/vascend/file-lock.d.ts +1 -0
  197. package/dist/vascend/file-lock.js +26 -0
  198. package/dist/vascend/granular-kingdom.d.ts +1 -0
  199. package/dist/vascend/granular-kingdom.js +22 -15
  200. package/dist/vascend/kingdom-bus.d.ts +1 -0
  201. package/dist/vascend/kingdom-bus.js +3 -0
  202. package/dist/vascend/kingdom-channel.d.ts +2 -0
  203. package/dist/vascend/kingdom-channel.js +3 -0
  204. package/dist/vascend/kingdom-live.d.ts +32 -4
  205. package/dist/vascend/kingdom-live.js +149 -13
  206. package/dist/vascend/kingdom-run.d.ts +5 -1
  207. package/dist/vascend/kingdom-run.js +6 -2
  208. package/dist/vascend/kingdom.d.ts +3 -0
  209. package/dist/vascend/kingdom.js +20 -13
  210. package/dist/vascend/memory-packet.d.ts +2 -1
  211. package/dist/vascend/memory-packet.js +31 -44
  212. package/dist/vascend/plan-store.d.ts +3 -1
  213. package/dist/vascend/plan-store.js +6 -4
  214. package/dist/vascend/room-memory.d.ts +10 -1
  215. package/dist/vascend/room-memory.js +42 -2
  216. package/dist/vascend/runtime.d.ts +3 -11
  217. package/dist/vascend/runtime.js +11 -123
  218. package/dist/vascend/session-search.js +2 -0
  219. package/dist/vascend/team-session.js +3 -2
  220. package/dist/vascend/vision.d.ts +3 -1
  221. package/dist/vascend/vision.js +6 -4
  222. package/dist/vascend/worker-session.d.ts +20 -0
  223. package/dist/vascend/worker-session.js +83 -0
  224. package/package.json +1 -1
@@ -35,6 +35,7 @@ export declare class ReviewStore {
35
35
  private retainedChars;
36
36
  private callIndex;
37
37
  private actorText;
38
+ private rawActors;
38
39
  private sequence;
39
40
  private run;
40
41
  private _revision;
@@ -1,5 +1,8 @@
1
1
  import { oneLine } from "./terminal-text.js";
2
2
  import { AgentSessions } from "./agent-sessions.js";
3
+ import { agentLabel } from "./agent-label.js";
4
+ import { tr } from "./i18n.js";
5
+ import { runtimeError } from "./runtime-error.js";
3
6
  const LABELS = {
4
7
  read_file: "Lettura", list_dir: "Cartella", grep: "Ricerca", glob: "Ricerca file",
5
8
  write_file: "Scrittura", edit_file: "Modifica", bash: "Comando", bash_bg: "Comando in background",
@@ -9,7 +12,9 @@ const LABELS = {
9
12
  vascend_addrooms: "Nuove attività", vascend_status: "Lista attività",
10
13
  kingdom_send: "Messaggio al team", kingdom_inbox: "Messaggi del team", kingdom_wait: "Attesa collaboratori",
11
14
  };
12
- export function reviewTitle(name) { return LABELS[name] ?? name; }
15
+ export function reviewTitle(name) {
16
+ return name === "bash_output" ? tr("Processo in background", "Background process") : LABELS[name] ?? name;
17
+ }
13
18
  function textSize(value, seen = new Set()) {
14
19
  if (typeof value === "string")
15
20
  return value.length;
@@ -34,6 +39,7 @@ export class ReviewStore {
34
39
  retainedChars = 0;
35
40
  callIndex = new Map();
36
41
  actorText = new Map();
42
+ rawActors = new Set();
37
43
  sequence = 0;
38
44
  run = 0;
39
45
  _revision = 0;
@@ -48,7 +54,17 @@ export class ReviewStore {
48
54
  get revision() { return this._revision; }
49
55
  get evicted() { return this._evicted; }
50
56
  get running() { return this._running || [...this.items.values()].some(e => e.state === "running"); }
51
- get label() { return !this._running && this.running ? "Collaboratori in esecuzione" : this._label; }
57
+ get label() {
58
+ if (!this._running && this.running)
59
+ return tr("Collaboratori in esecuzione", "Workers running");
60
+ switch (this._label) {
61
+ case "Pronto": return tr("Pronto", "Ready");
62
+ case "Avvio": return tr("Avvio", "Starting");
63
+ case "Interrotto": return tr("Interrotto", "Interrupted");
64
+ case "In attesa del prossimo messaggio": return tr("In attesa del prossimo messaggio", "Waiting for the next message");
65
+ default: return this._label;
66
+ }
67
+ }
52
68
  get goal() { return this._goal; }
53
69
  entries() { return [...this.items.values()]; }
54
70
  get(id) { return this.items.get(id); }
@@ -77,6 +93,7 @@ export class ReviewStore {
77
93
  this.retainedChars = 0;
78
94
  this.callIndex.clear();
79
95
  this.actorText.clear();
96
+ this.rawActors.clear();
80
97
  this.sequence = 0;
81
98
  this.run = 0;
82
99
  this._evicted = 0;
@@ -106,6 +123,10 @@ export class ReviewStore {
106
123
  this.put({ ...entry, change });
107
124
  }
108
125
  record(event, at = Date.now(), actor = "Talos") {
126
+ // Host summaries must not overwrite full raw tool output or duplicate messages.
127
+ if (event.type === "kingdom" && (event.phase === "tool" || event.phase === "say" || event.phase === "inbox")
128
+ && this.rawActors.has(event.slug))
129
+ return;
109
130
  this.sessions.record(event, at, actor);
110
131
  switch (event.type) {
111
132
  case "status":
@@ -152,7 +173,7 @@ export class ReviewStore {
152
173
  break;
153
174
  }
154
175
  case "error":
155
- this.notice("Errore", event.message, "error", at, actor);
176
+ this.notice(tr("Errore", "Error"), runtimeError(event.message), "error", at, actor);
156
177
  break;
157
178
  case "compacted":
158
179
  this.notice("Contesto riassunto", `Stima locale: ${event.tokensBefore} → ${event.tokensAfter} token.\n${event.anchors?.join("\n") ?? ""}`, "info", at);
@@ -169,6 +190,7 @@ export class ReviewStore {
169
190
  }
170
191
  }
171
192
  recordActor(actor, event) {
193
+ this.rawActors.add(actor);
172
194
  if (event.type === "status" || event.type === "text")
173
195
  this.sessions.record(event, Date.now(), actor);
174
196
  if (event.type === "status") {
@@ -193,12 +215,11 @@ export class ReviewStore {
193
215
  if (event.type === "turn_start" || event.type === "done" || event.type === "error") {
194
216
  this.record({ type: "kingdom", phase: "vassal", slug: actor,
195
217
  state: event.type === "turn_start" ? "active" : event.type === "done" ? "done" : "error",
196
- ...(event.type === "error" ? { label: event.message } : {}) });
218
+ ...(event.type === "error" ? { label: runtimeError(event.message) } : {}) });
197
219
  }
198
220
  if (event.type === "done" || event.type === "error")
199
221
  this.finishActor(actor);
200
- if (event.type !== "turn_start")
201
- this.record(event, Date.now(), actor);
222
+ this.record(event, Date.now(), actor);
202
223
  }
203
224
  recordKingdom(e, at) {
204
225
  if (e.phase === "start") {
@@ -249,7 +270,7 @@ export class ReviewStore {
249
270
  }
250
271
  else if (e.phase === "say" || e.phase === "note" || e.phase === "notice" || e.phase === "message" || e.phase === "inbox") {
251
272
  const actor = "slug" in e ? e.slug : "from" in e ? e.from : "Talos";
252
- this.notice(e.phase === "message" ? `${actor} → ${e.to}` : e.phase === "inbox" ? `Messaggio ${e.state === "read" ? "letto" : "in coda"}` : "Aggiornamento", e.text, "info", at, actor);
273
+ this.notice(e.phase === "message" ? `${agentLabel(actor)} → ${agentLabel(e.to)}` : e.phase === "inbox" ? `Messaggio ${e.state === "read" ? "letto" : "in coda"}` : "Aggiornamento", e.text, "info", at, actor);
253
274
  }
254
275
  else if (e.phase === "task") {
255
276
  const id = `task:${this.run}:${e.slug}:${e.workstreamId}:${e.taskRef}`;
@@ -1,3 +1,4 @@
1
+ import { agentLabel } from "./agent-label.js";
1
2
  // Proiezione del registro: una riga stabile per agente, non una per evento.
2
3
  export function teamEntries(store) {
3
4
  const groups = new Map();
@@ -19,9 +20,9 @@ export function teamEntries(store) {
19
20
  const waiting = state === "running" && status && /attes|conferm|permess|domand/i.test(status.target)
20
21
  && (status.startedAt ?? 0) >= (latest.endedAt ?? latest.startedAt ?? 0);
21
22
  const activity = (waiting ? status.target : "") || current || (state !== "running" ? lifecycle?.output : "") || `${latest.title}: ${latest.target}`;
22
- return { id: `agent:${actor}`, actor, kind: "team", title: actor,
23
- state, target: `${actor} · ${state === "running" ? "in corso" : state === "ok" ? "concluso" : state === "error" ? "errore" : state === "stopped" ? "interrotto" : lifecycle?.output?.startsWith("In coda") ? "in coda" : "stato non disponibile"} · ${activity}`,
24
- output: `Incarico: ${goal}\nRuolo: ${lifecycle?.role ?? "non disponibile"}\nRisponde a: ${lifecycle?.parent ?? "orchestratore"}\nAttività: ${activity}\n${active.length} tool attivi · ${files.size} file · ${problems.length} problemi\nEventi disponibili: ${entries.length}`,
23
+ return { id: `agent:${actor}`, actor, kind: "team", title: agentLabel(actor),
24
+ state, target: `${agentLabel(actor)} · ${state === "running" ? "in corso" : state === "ok" ? "concluso" : state === "error" ? "errore" : state === "stopped" ? "interrotto" : lifecycle?.output?.startsWith("In coda") ? "in coda" : "stato non disponibile"} · ${activity}`,
25
+ output: `Incarico: ${goal}\nRuolo: ${lifecycle?.role ?? "non disponibile"}\nRisponde a: ${agentLabel(lifecycle?.parent ?? "main")}\nAttività: ${activity}\n${active.length} tool attivi · ${files.size} file · ${problems.length} problemi\nEventi disponibili: ${entries.length}`,
25
26
  startedAt: lifecycle?.startedAt, endedAt: lifecycle?.endedAt };
26
27
  });
27
28
  }
@@ -0,0 +1,2 @@
1
+ export declare function isUserInterruption(message: string): boolean;
2
+ export declare function runtimeError(message: string): string;
@@ -0,0 +1,26 @@
1
+ import { tr } from "./i18n.js";
2
+ export function isUserInterruption(message) {
3
+ return /^(?:interrotto|interrupted|richiesta interrotta|request interrupted|the operation was aborted\.?|this operation was aborted\.?)$/i.test(message.trim());
4
+ }
5
+ // Localize Talos-owned runtime diagnostics at the display boundary, including
6
+ // events from existing sessions. Unknown provider errors retain their details.
7
+ export function runtimeError(message) {
8
+ switch (message) {
9
+ case "interrotto": return tr("interrotto", "Interrupted");
10
+ case "Il contesto supera la finestra del modello anche dopo la compattazione. La sessione resta recuperabile.": return tr("Il contesto supera la finestra del modello anche dopo la compattazione. La sessione resta recuperabile.", "The context exceeds the model window even after compaction. The session can still be recovered.");
11
+ case "Il provider ha interrotto il turno senza fornire dettagli.": return tr("Il provider ha interrotto il turno senza fornire dettagli.", "The provider interrupted the turn without providing details.");
12
+ case "Il provider ha terminato con tool_use senza fornire una tool call valida. Il task non e' stato completato; riprova mantenendo questa sessione.": return tr("Il provider ha terminato con tool_use senza fornire una tool call valida. Il task non e' stato completato; riprova mantenendo questa sessione.", "The provider ended with tool_use without a valid tool call. The task was not completed; retry in this session.");
13
+ case "Il provider ha chiuso il turno senza una risposta finale. Il task non e' stato dichiarato completato; riprova mantenendo questa sessione.": return tr("Il provider ha chiuso il turno senza una risposta finale. Il task non e' stato dichiarato completato; riprova mantenendo questa sessione.", "The provider ended the turn without a final response. The task was not marked complete; retry in this session.");
14
+ case "La delega richiesta non e' stata autorizzata; il task non viene dichiarato completato.": return tr("La delega richiesta non e' stata autorizzata; il task non viene dichiarato completato.", "The requested delegation was not authorized; the task is not marked complete.");
15
+ }
16
+ const turns = /^pausa al tetto di (\d+) turni per questo giro \(TALOS_MAX_TURNS per alzarlo\); la validazione del piano riprende le attivita rimaste$/.exec(message);
17
+ if (turns)
18
+ return tr("Pausa al limite di {count} turni per questo giro (TALOS_MAX_TURNS per alzarlo); la validazione del piano riprende le attività rimaste.", "Paused at the limit of {count} turns for this run (increase TALOS_MAX_TURNS to raise it); plan validation resumes the remaining tasks.", { count: turns[1] });
19
+ const output = /^Risposta incompleta: raggiunto il limite di output dopo (\d+) continuazion[ei]\. Il task non e' stato marcato completato; riprendi dalla stessa sessione\.$/.exec(message);
20
+ if (output)
21
+ return tr("Risposta incompleta: limite di output raggiunto. Continuazioni eseguite: {count}. Il task non è stato marcato completato; riprendi dalla stessa sessione.", "Incomplete response: output limit reached. Continuations used: {count}. The task was not marked complete; resume in the same session.", { count: output[1] });
22
+ const tasks = /^Task incompleto: restano (.+)\. Il turno e' stato fermato senza dichiarare un falso completamento; riprendilo nella stessa sessione\.$/.exec(message);
23
+ if (tasks)
24
+ return tr("Task incompleto: restano {tasks}. Il turno è stato fermato senza dichiarare un falso completamento; riprendilo nella stessa sessione.", "Incomplete task: {tasks} remain. The turn stopped without claiming completion; resume in the same session.", { tasks: tasks[1] });
25
+ return message;
26
+ }
@@ -22,5 +22,6 @@ export declare function selectorMouseIndex(o: {
22
22
  count: number;
23
23
  index: number;
24
24
  pageSize: number;
25
+ headerRows?: number;
25
26
  }): number | null;
26
27
  export {};
@@ -57,7 +57,7 @@ export class SelectorMouseInput {
57
57
  }
58
58
  export function selectorMouseIndex(o) {
59
59
  const top = o.rows - Math.min(o.renderedRows, o.rows - 1) + 1;
60
- const row = o.y - top - 1; // title occupies the first overlay row
60
+ const row = o.y - top - (o.headerRows ?? 1);
61
61
  if (o.x < 1 || o.x > o.columns + 1 || row < 0 || row >= Math.min(o.count, o.pageSize))
62
62
  return null;
63
63
  const start = Math.max(0, Math.min(o.index - Math.floor(o.pageSize / 2), o.count - o.pageSize));
@@ -7,6 +7,11 @@ export interface SelectOption<T> {
7
7
  readonly hint?: string;
8
8
  readonly value: T;
9
9
  readonly age?: string;
10
+ readonly modelInfo?: {
11
+ readonly context?: number;
12
+ readonly reasoning?: boolean;
13
+ readonly cost: Readonly<Record<string, number>>;
14
+ };
10
15
  }
11
16
  interface SelectorControl {
12
17
  pause(): void;
@@ -15,11 +20,13 @@ interface SelectorControl {
15
20
  setOverlay(lines: string[] | null): void;
16
21
  }
17
22
  interface SelectorOptions<T> {
23
+ readonly signal?: AbortSignal;
18
24
  readonly title: string;
19
25
  readonly hint?: string;
20
26
  readonly options: readonly SelectOption<T>[];
21
27
  readonly current?: T;
22
28
  readonly currentKey?: string;
29
+ readonly onCycleMode?: () => SelectOption<T> | undefined;
23
30
  readonly theme: TuiTheme;
24
31
  readonly wide?: boolean;
25
32
  readonly question?: boolean;
@@ -115,6 +115,8 @@ export function renderSelector(opts) {
115
115
  return lines;
116
116
  }
117
117
  export async function selectOption(opts) {
118
+ if (opts.signal?.aborted)
119
+ return null;
118
120
  if (!stdin.isTTY || !stdout.isTTY || typeof stdin.setRawMode !== "function") {
119
121
  opts.ctl.write(opts.theme.bold(`\n ${opts.title}\n`));
120
122
  if (opts.hint)
@@ -172,6 +174,7 @@ export async function selectOption(opts) {
172
174
  if (settled)
173
175
  return;
174
176
  settled = true;
177
+ opts.signal?.removeEventListener("abort", abort);
175
178
  stdin.off("data", onData);
176
179
  decoder.dispose();
177
180
  keys.off("keypress", onKeypress);
@@ -197,7 +200,8 @@ export async function selectOption(opts) {
197
200
  if (event.button !== 0 || details || opts.question)
198
201
  return;
199
202
  const hit = selectorMouseIndex({ y: event.y, x: event.x, rows: stdout.rows || 24,
200
- columns: selectorWidth(opts.terminalColumns(), opts.wide), renderedRows,
203
+ columns: opts.modelList ? Math.max(1, opts.terminalColumns() - 2) : selectorWidth(opts.terminalColumns(), opts.wide), renderedRows,
204
+ headerRows: opts.modelList ? 4 : 1,
201
205
  count: filtered.length, index, pageSize: pageSize() });
202
206
  if (hit === null)
203
207
  return;
@@ -212,6 +216,14 @@ export async function selectOption(opts) {
212
216
  const utf8 = new StringDecoder("utf8");
213
217
  const onData = (data) => decoder.push(typeof data === "string" ? data : utf8.write(data));
214
218
  const onKeypress = (chunk, key) => {
219
+ if (key.shift && key.name === "tab" && opts.onCycleMode) {
220
+ const selected = opts.onCycleMode();
221
+ if (selected)
222
+ cleanup(selected);
223
+ else
224
+ draw();
225
+ return;
226
+ }
215
227
  if (key.name === "return") {
216
228
  cleanup(filtered[index] ?? null);
217
229
  return;
@@ -295,12 +307,17 @@ export async function selectOption(opts) {
295
307
  else if (key.name === "q")
296
308
  cleanup(null);
297
309
  };
310
+ const abort = () => cleanup(null);
298
311
  opts.ctl.pause();
299
312
  stdin.setRawMode(true);
300
313
  stdin.resume();
301
314
  stdin.on("data", onData);
302
315
  keys.on("keypress", onKeypress);
303
316
  stdout.on("resize", draw);
304
- draw();
317
+ opts.signal?.addEventListener("abort", abort, { once: true });
318
+ if (opts.signal?.aborted)
319
+ abort();
320
+ else
321
+ draw();
305
322
  });
306
323
  }
@@ -0,0 +1,26 @@
1
+ import type { UserInbox } from "../core/user-inbox.js";
2
+ import type { ContentPart } from "../core/types.js";
3
+ import type { AgentSession } from "./agent-sessions.js";
4
+ import type { TuiTheme } from "./branding.js";
5
+ import { buildUsageFooter } from "./usage-footer.js";
6
+ export declare function workerFooter(session: AgentSession, width: number, theme: TuiTheme, usage?: Parameters<typeof buildUsageFooter>[2], providerId?: (model: string) => string): readonly string[];
7
+ export declare function deleteWorker(session: AgentSession): {
8
+ ok: boolean;
9
+ error?: string;
10
+ };
11
+ export declare function stopWorker(session: AgentSession): {
12
+ ok: boolean;
13
+ error?: string;
14
+ };
15
+ export declare function bindWorkerInbox(session: AgentSession, inbox: UserInbox): {
16
+ ok: boolean;
17
+ error?: string;
18
+ };
19
+ export declare function wakeWorker(session: AgentSession): {
20
+ ok: boolean;
21
+ error?: string;
22
+ };
23
+ export declare function sendWorker(session: AgentSession, text: string, parts: readonly ContentPart[], immediate: boolean): {
24
+ ok: boolean;
25
+ error?: string;
26
+ };
@@ -0,0 +1,65 @@
1
+ import { kingdomLive } from "../vascend/kingdom-live.js";
2
+ import { compactContextTokens } from "./context-bar.js";
3
+ import { buildUsageFooter } from "./usage-footer.js";
4
+ import { tr } from "./i18n.js";
5
+ import { fitLine } from "./terminal-text.js";
6
+ import { ContextMeter } from "./context-meter.js";
7
+ const meters = new WeakMap();
8
+ export function workerFooter(session, width, theme, usage = { limits: new Map(), incomplete: false, missing: 0, unsaved: false }, providerId = model => model.split("/")[0] ?? "") {
9
+ const source = kingdomLive.contextSource(session.kingdomId ?? "", session.actor);
10
+ let reading;
11
+ if (source) {
12
+ let entry = meters.get(source.session);
13
+ if (!entry) {
14
+ entry = { meter: new ContextMeter() };
15
+ meters.set(source.session, entry);
16
+ }
17
+ const input = { ...source, env: source.env ?? process.env };
18
+ if (session.context && entry.observed !== session.context) {
19
+ entry.meter.observe(input, session.context);
20
+ entry.observed = session.context;
21
+ }
22
+ reading = entry.meter.read(input);
23
+ }
24
+ const ctx = reading ?? session.context;
25
+ const model = source?.config.model ?? session.context?.model ?? session.actor;
26
+ const footer = buildUsageFooter({ model, cwd: source?.config.cwd ?? "", width,
27
+ effort: source?.config.effort, fast: source?.config.fast,
28
+ contextTokens: ctx?.tokens, contextWindow: ctx?.window, contextArchive: reading?.archived,
29
+ }, theme, usage, providerId(model), { order: [] });
30
+ return [...footer,
31
+ fitLine(theme.muted(` ${tr("Consumo cumulativo", "Cumulative usage")}: ${session.usageMissing ? "≥" : ""}${session.usageKnown
32
+ ? compactContextTokens(session.inputTokens + session.outputTokens) : "—"} token`), width - 1)];
33
+ }
34
+ // Release works for both active and idle workers; persisted history is kept.
35
+ export function deleteWorker(session) {
36
+ if (session.id === "main")
37
+ return { ok: false, error: tr("La sessione principale non si elimina", "Cannot delete the main session") };
38
+ if (session.kingdomId)
39
+ kingdomLive.release(session.kingdomId, session.actor);
40
+ return { ok: true };
41
+ }
42
+ export function stopWorker(session) {
43
+ if (!session.kingdomId)
44
+ return { ok: false, error: tr("Collaboratore non attivo", "Worker inactive") };
45
+ return kingdomLive.stop(session.kingdomId, session.actor);
46
+ }
47
+ export function bindWorkerInbox(session, inbox) {
48
+ return kingdomLive.bindInbox(session.kingdomId ?? "", session.actor, inbox);
49
+ }
50
+ export function wakeWorker(session) {
51
+ return kingdomLive.wake(session.kingdomId ?? "", session.actor);
52
+ }
53
+ // Session-scoped commands cannot silently fall through to the main session.
54
+ // Global display/preferences commands are still handled by the host explicitly.
55
+ export function sendWorker(session, text, parts, immediate) {
56
+ if (!session.kingdomId)
57
+ return { ok: false, error: tr("Collaboratore non attivo", "Worker inactive") };
58
+ const compact = /^\/compact(?:\s+([\s\S]*))?\s*$/.exec(text.trim());
59
+ if (compact)
60
+ return kingdomLive.compact(session.kingdomId, session.actor, compact[1]);
61
+ if (text.trimStart().startsWith("/"))
62
+ return { ok: false,
63
+ error: tr("Comando non disponibile per questa sessione", "Command unavailable for this session") };
64
+ return kingdomLive.deliver(session.kingdomId, session.actor, text, undefined, parts, immediate);
65
+ }
@@ -1,3 +1,4 @@
1
+ import { shimmerText } from "./compaction-status.js";
1
2
  // A short, non-scrolling transition from the first prompt to its saved title.
2
3
  export class SessionNamingNotice {
3
4
  refresh;
@@ -41,9 +42,10 @@ export class SessionNamingNotice {
41
42
  return [];
42
43
  if (!this.pending && elapsed >= 900)
43
44
  return [this.title];
44
- const frame = Math.floor(elapsed / 100) % 9;
45
- return Array.from({ length: 3 }, (_, row) => Array.from({ length: 3 }, (_, col) => row * 3 + col === frame ? "\u25cf" : "\u00b7").join(" ")
46
- + (row === 1 ? " Renaming" : ""));
45
+ // Una sola riga stabile: lo shimmer cambia soltanto luminosita', senza
46
+ // griglia 3x3, cubi o glyph che saltano tra un frame e l'altro.
47
+ const frame = Math.floor(elapsed / 100);
48
+ return [shimmerText("Renaming", frame)];
47
49
  }
48
50
  line(now = Date.now()) { return this.lines(now).join("\n"); }
49
51
  clear() {
@@ -7,4 +7,5 @@ export declare function replaySession(session: Session, control: {
7
7
  writeConversation?(text: string): void;
8
8
  replayConversation?(renderer: (columns: number) => string): void;
9
9
  }, theme: TuiTheme, columns: number): void;
10
+ export declare function visibleUserText(text: string): string;
10
11
  export declare function resumeReplay(session: Session, theme: TuiTheme, columns: number): string;
@@ -1,7 +1,11 @@
1
1
  import { renderAssistantSegment, renderUserMessage } from "./branding.js";
2
- import { commandSource, highlightCommand } from "./command-transcript.js";
3
- import { plainText, wrapText } from "./terminal-text.js";
2
+ import { commandOutput, commandPreview, commandResultState, commandSource } from "./command-transcript.js";
4
3
  import { tr } from "./i18n.js";
4
+ import { livePreview, liveResultSummary, backgroundResultSummary } from "./live-output.js";
5
+ import { mcpName, mcpOutput, mcpPreview } from "./mcp-transcript.js";
6
+ import { loadedSkill, skillPreview } from "./skill-transcript.js";
7
+ import { fitLine, terminalWidth } from "./terminal-text.js";
8
+ import { toolArg, toolLabel } from "./tool-presentation.js";
5
9
  // The model's history() is compacted; the visible conversation uses the
6
10
  // original nodes on the selected branch, including archived messages.
7
11
  export function sessionConversation(session) {
@@ -13,25 +17,51 @@ export function replaySession(session, control, theme, columns) {
13
17
  else
14
18
  (control.writeConversation ?? control.write).call(control, resumeReplay(session, theme, columns));
15
19
  }
20
+ function attachmentLabel(part) {
21
+ const source = part.source && !part.source.startsWith("data:") ? ` · ${part.source}` : "";
22
+ return `[${tr("immagine", "image")} · ${part.mime}${source}]`;
23
+ }
24
+ function genericToolHeader(call, theme, columns) {
25
+ const label = toolLabel(call.name);
26
+ const arg = livePreview(toolArg(call), columns - terminalWidth(label) - 9);
27
+ return fitLine(" " + theme.muted("◇ " + label) + " " + theme.dim(arg), Math.max(1, columns - 1));
28
+ }
29
+ export function visibleUserText(text) {
30
+ // Talos persists the exact request sent to the model. With the Vascend
31
+ // workflow that request contains an internal GOAL/MODE envelope, while the
32
+ // live chat shows only what the user typed. Resume must preserve that same
33
+ // presentation without changing the stored model history.
34
+ if (!text.includes("[VASCEND_GOAL]"))
35
+ return text;
36
+ const markers = [...text.matchAll(/(?:^|\r?\n)INPUT_UTENTE\r?\n/g)];
37
+ const marker = markers.at(-1);
38
+ if (marker?.index === undefined)
39
+ return text;
40
+ return text.slice(marker.index + marker[0].length).trim();
41
+ }
42
+ // A resumed session uses the same compact blocks as a live turn. No resume
43
+ // banner or restored-context footer is inserted into the conversation.
16
44
  export function resumeReplay(session, theme, columns) {
17
45
  const messages = sessionConversation(session);
18
- const width = Math.max(2, columns - 5);
19
46
  const out = [];
20
47
  const calls = new Map();
21
- const heading = (text, style = theme.dim) => {
22
- out.push("\n" + wrapText(plainText(text), width, true).map(row => " " + style(row)).join("\n") + "\n");
23
- };
24
- const literal = (text, style = theme.dim) => {
25
- out.push(wrapText(plainText(text), width, true).map(row => " " + style(row)).join("\n") + "\n");
26
- };
27
- const attachment = (part) => {
28
- const source = part.source && !part.source.startsWith("data:") ? ` · ${part.source}` : "";
29
- literal(`[${tr("immagine", "image")} · ${part.mime}${source}]`);
48
+ const backgroundPolls = new Map();
49
+ const results = new Map();
50
+ for (const message of messages) {
51
+ for (const part of message.content)
52
+ if (part.type === "tool_result" && !results.has(part.callId))
53
+ results.set(part.callId, part);
54
+ }
55
+ const attachment = (part, role) => {
56
+ const caption = attachmentLabel(part);
57
+ if (role === "user")
58
+ out.push(renderUserMessage(caption, theme, columns));
59
+ else
60
+ out.push(fitLine(theme.dark(" │ ") + theme.dark("⎿ ") + theme.dim(caption), Math.max(1, columns - 1)) + "\n");
30
61
  };
31
- heading(tr("ripresa sessione {name}({id}) · {count} messaggi", "Resumed session {name}({id}) · {count} messages", {
32
- name: session.name ? `"${session.name}" ` : "", id: session.id, count: messages.length,
33
- }), theme.bold);
34
62
  for (const message of messages) {
63
+ if (message.role === "system")
64
+ continue;
35
65
  let text = "";
36
66
  const flushText = () => {
37
67
  if (!text.trim()) {
@@ -39,13 +69,9 @@ export function resumeReplay(session, theme, columns) {
39
69
  return;
40
70
  }
41
71
  if (message.role === "user")
42
- out.push(renderUserMessage(text, theme, columns));
72
+ out.push(renderUserMessage(visibleUserText(text), theme, columns));
43
73
  else if (message.role === "assistant")
44
74
  out.push(renderAssistantSegment(text, theme, columns));
45
- else {
46
- heading(message.role === "system" ? tr("Sistema", "System") : tr("Strumento", "Tool"));
47
- literal(text);
48
- }
49
75
  text = "";
50
76
  };
51
77
  for (const part of message.content) {
@@ -54,25 +80,68 @@ export function resumeReplay(session, theme, columns) {
54
80
  continue;
55
81
  }
56
82
  flushText();
83
+ if (part.type === "image") {
84
+ attachment(part, message.role);
85
+ continue;
86
+ }
57
87
  if (part.type === "tool_call") {
58
88
  calls.set(part.id, part);
59
- heading(`${tr("Strumento", "Tool")} · ${part.name}`, theme.accent);
60
- const source = commandSource(part);
61
- literal(source ?? JSON.stringify(part.input, null, 2), source === undefined ? theme.dim : row => highlightCommand(row, theme));
89
+ if (part.name === "bash_output")
90
+ continue;
91
+ if (part.name === "vascend_mark")
92
+ continue;
93
+ const result = results.get(part.id);
94
+ const skill = loadedSkill(part);
95
+ if (skill) {
96
+ out.push("\n" + skillPreview(skill, theme, columns, result?.isError ? "error" : result ? "ok" : "running").join("\n") + "\n");
97
+ continue;
98
+ }
99
+ if (mcpName(part.name)) {
100
+ out.push("\n" + mcpPreview(part, theme, columns, result?.isError ? "error" : result ? "ok" : "stopped").join("\n") + "\n");
101
+ continue;
102
+ }
103
+ const source = part.name === "bash" ? commandSource(part) : undefined;
104
+ if (source !== undefined) {
105
+ out.push("\n" + commandPreview(source, theme, columns, result ? commandResultState(result.output, result.isError) : "stopped").lines.join("\n") + "\n");
106
+ continue;
107
+ }
108
+ out.push("\n" + genericToolHeader(part, theme, columns) + "\n");
109
+ continue;
62
110
  }
63
- else if (part.type === "tool_result") {
64
- const name = calls.get(part.callId)?.name ?? part.callId;
65
- heading(`${part.isError ? tr("Errore", "Error") : tr("Risultato", "Result")} · ${name}`, part.isError ? theme.red : theme.dim);
66
- literal(part.output || tr("(nessun output)", "(no output)"), part.isError ? theme.red : theme.dim);
111
+ if (part.type === "tool_result") {
112
+ const call = calls.get(part.callId);
113
+ if (call?.name === "bash_output") {
114
+ const id = String(call.input["id"] ?? call.id);
115
+ const snapshot = JSON.stringify([part.isError ?? false, part.output, part.media]);
116
+ if (backgroundPolls.get(id) === snapshot)
117
+ continue;
118
+ backgroundPolls.set(id, snapshot);
119
+ out.push("\n" + fitLine(" " + (part.isError ? theme.red("× ") : theme.muted("◇ "))
120
+ + theme.muted(toolLabel("bash_output")), Math.max(1, columns - 1)) + "\n");
121
+ }
122
+ if (call?.name === "vascend_mark")
123
+ continue;
124
+ const skill = call && loadedSkill(call);
125
+ if (skill) {
126
+ if (part.isError)
127
+ out.push(commandOutput(part.output, theme, columns, true).join("\n") + "\n");
128
+ }
129
+ else if (call && mcpName(call.name)) {
130
+ out.push(mcpOutput(part.output, part.isError, theme, columns).join("\n") + "\n");
131
+ }
132
+ else if (call?.name === "bash" && commandSource(call) !== undefined) {
133
+ out.push(commandOutput(part.output, theme, columns, Boolean(part.isError)).join("\n") + "\n");
134
+ }
135
+ else {
136
+ const summary = call?.name === "bash_output" ? backgroundResultSummary(part.output, columns) : liveResultSummary(part.output, null, columns);
137
+ out.push(fitLine(theme.dark(" └ ") + (part.isError ? theme.red("× ") : "")
138
+ + (part.isError ? theme.red(summary) : theme.dim(summary)), Math.max(1, columns - 1)) + "\n");
139
+ }
67
140
  for (const media of part.media ?? [])
68
- attachment(media);
69
- }
70
- else if (part.type === "image") {
71
- attachment(part);
141
+ attachment(media, "tool");
72
142
  }
73
143
  }
74
144
  flushText();
75
145
  }
76
- heading(tr("contesto ripreso: continua pure da qui", "Context restored: continue from here"));
77
146
  return out.join("");
78
147
  }
@@ -0,0 +1,9 @@
1
+ type Wrap = (text: string, width: number, wordWrap: boolean) => readonly string[];
2
+ export declare class SessionTranscriptLayout {
3
+ private readonly wrap;
4
+ private readonly sessions;
5
+ constructor(wrap?: Wrap);
6
+ retain(ids: ReadonlySet<string>): void;
7
+ lines(id: string, values: readonly string[], width: number): readonly string[];
8
+ }
9
+ export {};
@@ -0,0 +1,41 @@
1
+ import { wrapText } from "./terminal-text.js";
2
+ // Reflow only changed blocks (usually the streaming tail), not the complete
3
+ // worker transcript on every token, timer tick or selection change. This is a
4
+ // bounded rendering cache; the session store remains the source of truth.
5
+ export class SessionTranscriptLayout {
6
+ wrap;
7
+ sessions = new Map();
8
+ constructor(wrap = wrapText) {
9
+ this.wrap = wrap;
10
+ }
11
+ retain(ids) {
12
+ for (const id of this.sessions.keys())
13
+ if (!ids.has(id))
14
+ this.sessions.delete(id);
15
+ }
16
+ lines(id, values, width) {
17
+ const blocks = values.filter(Boolean);
18
+ const cached = this.sessions.get(id);
19
+ const previous = cached?.width === width ? cached : undefined;
20
+ if (previous && blocks.length === previous.blocks.length && blocks.every((text, i) => text === previous.blocks[i])) {
21
+ this.sessions.delete(id);
22
+ this.sessions.set(id, previous);
23
+ return previous.lines;
24
+ }
25
+ const wrapped = new Map();
26
+ const lines = [];
27
+ for (const text of blocks) {
28
+ const rows = wrapped.get(text) ?? previous?.wrapped.get(text) ?? this.wrap(text, width, true);
29
+ wrapped.set(text, rows);
30
+ if (lines.length)
31
+ lines.push("");
32
+ for (const row of rows)
33
+ lines.push(row);
34
+ }
35
+ this.sessions.delete(id);
36
+ this.sessions.set(id, { width, blocks, wrapped, lines });
37
+ if (this.sessions.size > 8)
38
+ this.sessions.delete(this.sessions.keys().next().value);
39
+ return lines;
40
+ }
41
+ }