@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
@@ -64,11 +64,12 @@ const BRIDGE_DENYLIST = new Set([
64
64
  // ---------------------------------------------------------------------------
65
65
  const KERNEL_SOURCE = `"use strict";
66
66
  const util = require("node:util");
67
+ const { AsyncLocalStorage } = require("node:async_hooks");
68
+ const executionScope = new AsyncLocalStorage();
67
69
  let buf = "";
68
70
  let seq = 0;
69
71
  const pending = new Map();
70
72
  const state = {};
71
- let inputData = {};
72
73
 
73
74
  function send(msg) { process.stdout.write(JSON.stringify(msg) + "\\n"); }
74
75
 
@@ -80,10 +81,12 @@ function render(value) {
80
81
  }
81
82
 
82
83
  function callTool(name, args, structured = false) {
84
+ const scope = executionScope.getStore();
85
+ if (!scope?.active) return Promise.reject(new Error("programma gia' concluso"));
83
86
  return new Promise((resolve, reject) => {
84
87
  seq += 1;
85
88
  pending.set(seq, { resolve, reject, structured });
86
- send({ t: "call", seq, name, args: args === undefined ? {} : args });
89
+ send({ t: "call", id: scope.id, seq, name, args: args === undefined ? {} : args });
87
90
  });
88
91
  }
89
92
 
@@ -92,7 +95,7 @@ function callTool(name, args, structured = false) {
92
95
  const tools = new Proxy({}, {
93
96
  get(_t, prop) {
94
97
  if (typeof prop !== "string" || prop === "then" || prop === "toJSON") return undefined;
95
- if (prop === "$data") return inputData;
98
+ if (prop === "$data") return executionScope.getStore()?.data ?? {};
96
99
  if (prop === "$result") return (name, args) => callTool(name, args, true);
97
100
  return (args) => callTool(prop, args);
98
101
  },
@@ -100,24 +103,31 @@ const tools = new Proxy({}, {
100
103
 
101
104
  const fakeConsole = {};
102
105
  for (const level of ["log", "info", "warn", "error", "debug", "trace"]) {
103
- fakeConsole[level] = (...parts) => send({
104
- t: "log",
105
- line: parts.map((p) => (typeof p === "string" ? p : render(p))).join(" "),
106
- });
106
+ fakeConsole[level] = (...parts) => {
107
+ const scope = executionScope.getStore();
108
+ if (scope?.active) send({
109
+ t: "log", id: scope.id,
110
+ line: parts.map((p) => (typeof p === "string" ? p : render(p))).join(" "),
111
+ });
112
+ };
107
113
  }
108
114
 
109
115
  async function execute(msg) {
110
- inputData = msg.data ?? {};
111
- const AsyncFunction = (async () => {}).constructor;
112
- try {
113
- // require/process oscurati: e' un invito a passare dai tool (che portano i
114
- // permessi), non un confine di sicurezza — il confine e' la conferma utente.
115
- const fn = new AsyncFunction("tools", "state", "console", "require", "process", msg.code);
116
- const value = await fn(tools, state, fakeConsole, undefined, undefined);
117
- send({ t: "done", id: msg.id, value: render(value) });
118
- } catch (err) {
119
- send({ t: "done", id: msg.id, error: err instanceof Error ? (err.stack || err.message) : String(err) });
120
- }
116
+ const scope = { id: msg.id, data: msg.data ?? {}, active: true };
117
+ return executionScope.run(scope, async () => {
118
+ const AsyncFunction = (async () => {}).constructor;
119
+ try {
120
+ // require/process oscurati: e' un invito a passare dai tool (che portano i
121
+ // permessi), non un confine di sicurezza — il confine e' la conferma utente.
122
+ const fn = new AsyncFunction("tools", "state", "console", "require", "process", msg.code);
123
+ const value = await fn(tools, state, fakeConsole, undefined, undefined);
124
+ send({ t: "done", id: msg.id, value: render(value) });
125
+ } catch (err) {
126
+ send({ t: "done", id: msg.id, error: err instanceof Error ? (err.stack || err.message) : String(err) });
127
+ } finally {
128
+ scope.active = false;
129
+ }
130
+ });
121
131
  }
122
132
 
123
133
  process.stdin.setEncoding("utf8");
@@ -255,7 +265,7 @@ class ProgramKernel {
255
265
  }
256
266
  async onMessage(msg) {
257
267
  const cur = this.current;
258
- if (!cur)
268
+ if (!cur || Number(msg["id"]) !== cur.id)
259
269
  return;
260
270
  if (msg["t"] === "log") {
261
271
  this.appendLog(cur, String(msg["line"] ?? ""));
@@ -307,8 +317,11 @@ class ProgramKernel {
307
317
  cur.logsTruncatedLines += 1;
308
318
  return;
309
319
  }
310
- cur.logs.push(line);
311
- cur.logChars += line.length + 1;
320
+ const remaining = Math.max(0, MAX_LOG_CHARS - cur.logChars - 1);
321
+ cur.logs.push(line.slice(0, remaining));
322
+ cur.logChars += Math.min(line.length, remaining) + 1;
323
+ if (line.length > remaining)
324
+ cur.logsTruncatedLines += 1;
312
325
  }
313
326
  send(msg) {
314
327
  try {
@@ -257,7 +257,7 @@ export const teamWaitTool = {
257
257
  const combacia = (m) => (!from || addressKey(m.from) === from) && (!contains || m.text.toLowerCase().includes(contains));
258
258
  const canale = bus(ctx);
259
259
  for (;;) {
260
- if (ctx.signal.aborted)
260
+ if (ctx.signal.aborted || ctx.waitInterrupted?.())
261
261
  return "attesa interrotta";
262
262
  const msgs = canale.inbox(id, me, combacia);
263
263
  if (msgs.length)
@@ -323,20 +323,25 @@ export const teamReportTool = {
323
323
  const richiesti = Number(input["timeout_s"]);
324
324
  const secondi = Number.isFinite(richiesti) ? Math.min(WAIT_MAX_S, Math.max(1, richiesti)) : WAIT_DEFAULT_S;
325
325
  const deadline = Date.now() + secondi * 1000;
326
+ // Keep the original target even if a newer team appears during the wait.
327
+ const selected = () => teamById(team.sessionId, team.id) ?? team;
326
328
  while (Date.now() < deadline) {
327
- if (ctx.signal.aborted)
328
- return reportResult("attesa interrotta; il lavoro del team non viene cancellato", currentTeam(ctx) ?? team);
329
- const current = requested ? teamById(ctx.sessionId ?? "", requested) : currentTeam(ctx);
329
+ if (ctx.signal.aborted || ctx.waitInterrupted?.())
330
+ return reportResult("attesa interrotta; il lavoro del team non viene cancellato", selected());
331
+ const current = selected();
330
332
  if (current?.done)
331
333
  return finished(current);
332
334
  try {
333
335
  await sleep(Math.min(250, Math.max(1, deadline - Date.now())), ctx.signal);
334
336
  }
335
337
  catch {
336
- return reportResult("attesa interrotta; il lavoro del team non viene cancellato", currentTeam(ctx) ?? team);
338
+ return reportResult("attesa interrotta; il lavoro del team non viene cancellato", selected());
337
339
  }
338
340
  }
339
- return reportResult(`non hanno ancora finito dopo ${secondi}s: ${pendingMembers(currentTeam(ctx) ?? team).join(", ")}. Richiama team_report piu' tardi.`, currentTeam(ctx) ?? team);
341
+ const current = selected();
342
+ if (current.done)
343
+ return finished(current);
344
+ return reportResult(`non hanno ancora finito dopo ${secondi}s: ${pendingMembers(current).join(", ")}. Richiama team_report piu' tardi.`, current);
340
345
  },
341
346
  };
342
347
  export const teamTools = [
@@ -3,7 +3,8 @@ import { stdin, stdout } from "node:process";
3
3
  import { tr } from "./i18n.js";
4
4
  import { initializeLanguage, onboardingLanguage } from "./language-preference.js";
5
5
  import { leggiOnboarding, salvaOnboarding, serveTermini, TESTI, consensoAffermativo, TERMINI_VERSIONE, LINK_TERMINI, LINK_PRIVACY } from "./onboarding.js";
6
- import { decisioneCancello, tokenVascend, statoSessione, valutaLicenzaLocale, rimuoviTokenVascend, salvaTokenVascend, rinnovaProvaOffline } from "./vascend-login.js";
6
+ import { decisioneCancello, statoSessione, valutaLicenzaLocale, rimuoviTokenVascend, rinnovaProvaOffline } from "./vascend-login.js";
7
+ import { rimuoviSessione, salvaSessione, tokenAgente } from "./vascend-id.js";
7
8
  import { startWebLogin, waitForWebLogin, WebLoginError } from "./web-login.js";
8
9
  import { showWebLoginScreen } from "./web-login-screen.js";
9
10
  import { CliCancelled } from "../core/cli-cancelled.js";
@@ -14,12 +15,16 @@ export async function enterAccount(env, theme) {
14
15
  // requires a verified session and current account permissions.
15
16
  if (env["TALOS_NO_LOGIN"] === "1")
16
17
  return "";
17
- const saved = tokenVascend(env);
18
- const session = saved ? await statoSessione(env, saved) : null;
18
+ // Token per l'agente dalla sessione Vascend ID, rinnovato se serve. Il vecchio
19
+ // token del backend agentico non vale piu': chi lo aveva rifa' l'accesso.
20
+ const esito = await tokenAgente(env);
21
+ const saved = esito.token;
22
+ const session = saved ? await statoSessione(env, saved)
23
+ : esito.errore === "rete" ? { stato: "irraggiungibile" } : null;
19
24
  if (!stdin.isTTY || !stdout.isTTY || env["TALOS_HOST_PROFILE"]?.toLowerCase() === "horizon")
20
25
  return "";
21
26
  const offline = session?.stato === "irraggiungibile" ? valutaLicenzaLocale(env) : null;
22
- const decision = decisioneCancello(saved, session, offline);
27
+ const decision = decisioneCancello(saved || (esito.errore === "rete" ? "offline" : ""), session, offline);
23
28
  let tier = decision.tier;
24
29
  let token = saved;
25
30
  const note = () => tr("Account connesso", "Account connected") +
@@ -38,18 +43,18 @@ export async function enterAccount(env, theme) {
38
43
  const write = (text) => { stdout.write(` ${text}\n`); };
39
44
  try {
40
45
  if (decision.azione === "login") {
41
- if (saved)
42
- rimuoviTokenVascend(env);
46
+ rimuoviSessione(env);
47
+ rimuoviTokenVascend(env);
43
48
  stdout.write(`\n ${tr("Preparazione dell'accesso a Talos...", "Preparing Talos sign-in...")}`);
44
49
  const grant = await startWebLogin(env, language, {}, abort.signal)
45
50
  .finally(() => { stdout.write(env.TERM === "dumb" ? "\n" : "\r\x1b[2K"); });
46
51
  const finish = showWebLoginScreen(grant, abort, { env, theme });
47
52
  try {
48
- const account = await waitForWebLogin(env, grant, {}, abort.signal);
49
- if (!salvaTokenVascend(env, account.token))
53
+ const account = await waitForWebLogin(env, grant, language, {}, abort.signal);
54
+ if (!salvaSessione(env, account))
50
55
  throw new Error(TESTI[language].tokenNonSalvabile);
51
56
  token = account.token;
52
- tier = account.tier;
57
+ tier = undefined;
53
58
  finish(true);
54
59
  write(TESTI[language].benvenuto(account.email));
55
60
  }
@@ -0,0 +1,2 @@
1
+ /** Translate display labels without changing saved IDs or team addresses. */
2
+ export declare function agentLabel(actor: string): string;
@@ -0,0 +1,5 @@
1
+ import { tr } from "./i18n.js";
2
+ /** Translate display labels without changing saved IDs or team addresses. */
3
+ export function agentLabel(actor) {
4
+ return ["main", "orchestratore", "orchestrator"].includes(actor) ? tr("principale", "main") : actor;
5
+ }
@@ -1,21 +1,31 @@
1
- import type { AgentSessions } from "./agent-sessions.js";
1
+ import type { AgentSession, AgentSessions } from "./agent-sessions.js";
2
2
  import type { QueueStyle } from "./queue-screen.js";
3
3
  import type { ScreenKey, TerminalScreen } from "./terminal-screen.js";
4
4
  export declare function sessionDuration(ms: number): string;
5
5
  export declare class AgentSessionsScreen implements TerminalScreen {
6
6
  private readonly store;
7
7
  private readonly style;
8
+ private readonly onDelete?;
8
9
  private selected;
9
10
  private active;
10
11
  private offset;
11
12
  private pageSize;
12
13
  private maxOffset;
13
14
  private follow;
15
+ private readonly transcript;
14
16
  focused: boolean;
15
- constructor(store: AgentSessions, style?: QueueStyle);
17
+ private deleteConfirmation?;
18
+ constructor(store: AgentSessions, style?: QueueStyle, onDelete?: ((session: AgentSession) => {
19
+ ok: boolean;
20
+ error?: string;
21
+ }) | undefined);
22
+ private deleteSelected;
23
+ get session(): AgentSession;
24
+ conversation(width: number, height: number): readonly string[];
16
25
  get selectedId(): string;
17
26
  get activeId(): string;
18
27
  get available(): boolean;
28
+ returnToMain(): void;
19
29
  sync(): void;
20
30
  private select;
21
31
  key(_text: string, key: ScreenKey): "close" | void;
@@ -1,6 +1,8 @@
1
1
  import { tr } from "./i18n.js";
2
2
  import { PLAIN_QUEUE_STYLE } from "./queue-screen.js";
3
- import { fitLine, oneLine, terminalWidth, wrapText } from "./terminal-text.js";
3
+ import { fitLine, oneLine, terminalWidth } from "./terminal-text.js";
4
+ import { SessionTranscriptLayout } from "./session-transcript-layout.js";
5
+ import { agentLabel } from "./agent-label.js";
4
6
  export function sessionDuration(ms) {
5
7
  const seconds = Math.floor(Math.max(0, ms) / 1000);
6
8
  return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
@@ -17,32 +19,69 @@ function metrics(session) {
17
19
  export class AgentSessionsScreen {
18
20
  store;
19
21
  style;
22
+ onDelete;
20
23
  selected = "main";
21
24
  active = "main";
22
25
  offset = 0;
23
26
  pageSize = 1;
24
27
  maxOffset = 0;
25
28
  follow = true;
29
+ transcript = new SessionTranscriptLayout();
26
30
  focused = false;
27
- constructor(store, style = PLAIN_QUEUE_STYLE) {
31
+ deleteConfirmation;
32
+ constructor(store, style = PLAIN_QUEUE_STYLE, onDelete) {
28
33
  this.store = store;
29
34
  this.style = style;
35
+ this.onDelete = onDelete;
36
+ }
37
+ deleteSelected() {
38
+ const session = this.store.get(this.selectedId);
39
+ if (!session || session.id === "main" || !this.onDelete)
40
+ return;
41
+ if (session.running && (this.deleteConfirmation?.id !== session.id || this.deleteConfirmation.until < Date.now())) {
42
+ this.deleteConfirmation = { id: session.id, until: Date.now() + 3000 };
43
+ return;
44
+ }
45
+ this.deleteConfirmation = undefined;
46
+ const result = this.onDelete(session);
47
+ if (result.ok) {
48
+ this.store.delete(session.id);
49
+ this.sync();
50
+ }
51
+ else
52
+ this.store.notify(session.id, result.error ?? tr("Eliminazione non riuscita", "Deletion failed"));
53
+ }
54
+ get session() { return this.store.get(this.activeId); }
55
+ conversation(width, height) {
56
+ const session = this.session;
57
+ const lines = this.store.conversations.layout(session.id, width)
58
+ ?? this.transcript.lines(session.id, [session.goal, ...session.transcript], Math.max(2, width - 2));
59
+ this.pageSize = Math.max(1, height);
60
+ this.maxOffset = Math.max(0, lines.length - this.pageSize);
61
+ this.offset = this.follow ? this.maxOffset : Math.min(this.offset, this.maxOffset);
62
+ return lines.slice(this.offset, this.offset + this.pageSize);
30
63
  }
31
64
  get selectedId() { this.sync(); return this.selected; }
32
65
  get activeId() { this.sync(); return this.active; }
33
66
  get available() { return this.store.hasAgents; }
67
+ returnToMain() {
68
+ this.active = "main";
69
+ this.selected = "main";
70
+ this.focused = false;
71
+ this.follow = true;
72
+ this.offset = 0;
73
+ }
34
74
  sync() {
35
- if (!this.store.get(this.active)?.live) {
36
- this.active = "main";
37
- this.selected = "main";
38
- this.focused = false;
39
- this.follow = true;
40
- this.offset = 0;
41
- }
75
+ this.transcript.retain(new Set(this.store.list().map(s => s.id)));
76
+ if (!this.store.get(this.active)?.live)
77
+ this.returnToMain();
42
78
  if (!this.store.get(this.selected)?.live)
43
79
  this.selected = this.active;
44
80
  if (!this.available)
45
81
  this.focused = false;
82
+ if (this.deleteConfirmation && (!this.focused || this.deleteConfirmation.id !== this.selected || this.deleteConfirmation.until < Date.now())) {
83
+ this.deleteConfirmation = undefined;
84
+ }
46
85
  }
47
86
  select(delta) {
48
87
  this.sync();
@@ -53,6 +92,11 @@ export class AgentSessionsScreen {
53
92
  }
54
93
  key(_text, key) {
55
94
  this.sync();
95
+ if (key.name === "delete" && this.focused) {
96
+ this.deleteSelected();
97
+ return;
98
+ }
99
+ this.deleteConfirmation = undefined;
56
100
  if (key.name === "up" || key.name === "down")
57
101
  this.select(key.name === "up" ? -1 : 1);
58
102
  else if (key.name === "return" || key.name === "enter") {
@@ -88,14 +132,17 @@ export class AgentSessionsScreen {
88
132
  const capacity = Math.max(1, maxRows - 1);
89
133
  const index = Math.max(0, sessions.findIndex(s => s.id === this.selected));
90
134
  const start = Math.max(0, Math.min(index - Math.floor(capacity / 2), sessions.length - capacity));
135
+ const deletion = this.onDelete && this.selected !== "main"
136
+ ? this.deleteConfirmation ? tr("[Canc: conferma stop ed elimina]", "[Del: confirm stop and delete]")
137
+ : tr("[Canc: elimina sessione]", "[Del: delete session]") : "";
91
138
  const help = this.focused
92
- ? tr("↑↓ seleziona · Invio apri · main: sessione principale", "↑↓ select · Enter open · main: primary session")
139
+ ? `${deletion ? deletion + " · " : ""}${tr("↑↓ seleziona · Invio apri", "↑↓ select · Enter open")}`
93
140
  : tr("↓ sessioni · Alt+A seleziona", "↓ sessions · Alt+A select");
94
141
  const range = sessions.length > capacity ? ` [${start + 1}–${Math.min(sessions.length, start + capacity)}/${sessions.length}]` : "";
95
142
  const rows = [this.style.muted(fitLine(` ${help}${range}`, width - 1))];
96
143
  for (const s of sessions.slice(start, start + capacity)) {
97
144
  const indicator = this.focused && s.id === this.selected ? ">" : " ";
98
- const name = ` ${indicator} ${s.id === this.active ? "*" : " "} ${oneLine(s.actor)}`;
145
+ const name = ` ${indicator} ${s.id === this.active ? "*" : " "} ${oneLine(agentLabel(s.actor))}`;
99
146
  const tail = ` ${metrics(s)}`;
100
147
  const room = Math.max(1, width - 1 - terminalWidth(tail));
101
148
  const activity = s.activity ? ` ${oneLine(s.activity)}` : "";
@@ -110,16 +157,15 @@ export class AgentSessionsScreen {
110
157
  this.sync();
111
158
  const h = Math.max(1, height), w = Math.max(1, width - 2);
112
159
  const session = this.store.get(this.active);
113
- const heading = this.style.heading(fitLine(` ${tr("Sessione", "Session")} · ${oneLine(session.actor)} · ${metrics(session)}`, width - 1));
160
+ const heading = this.style.heading(fitLine(` ${tr("Sessione", "Session")} · ${oneLine(agentLabel(session.actor))} · ${metrics(session)}`, width - 1));
114
161
  const list = this.preview(width, Math.min(6, Math.max(2, Math.floor(h / 3))));
115
- const help = this.style.muted(fitLine(tr("↑↓ seleziona · Invio apri · seleziona main per tornare · PgSu/PgGiù output", "↑↓ select · Enter open · select main to return · PgUp/PgDn output"), width - 1));
162
+ const help = this.style.muted(fitLine(tr("↑↓ seleziona · Invio apri · seleziona principale per tornare · PgSu/PgGiù output", "↑↓ select · Enter open · select main to return · PgUp/PgDn output"), width - 1));
116
163
  const head = [heading, ...list, ""];
117
164
  const usage = session.usageKnown
118
165
  ? `Input: ${tokenCount(session.inputTokens)} · Output: ${tokenCount(session.outputTokens)}`
119
166
  : tr("Token non ancora comunicati dal provider.", "Tokens not yet reported by the provider.");
120
- const text = [session.goal, usage, ...session.transcript].filter(Boolean).join("\n\n")
121
- || tr("In attesa di attività…", "Waiting for activity…");
122
- const lines = wrapText(text, w, true);
167
+ const blocks = [session.goal, usage, ...session.transcript].filter(Boolean);
168
+ const lines = this.transcript.lines(session.id, blocks.length ? blocks : [tr("In attesa di attività…", "Waiting for activity…")], w);
123
169
  this.pageSize = Math.max(1, h - head.length - 1);
124
170
  this.maxOffset = Math.max(0, lines.length - this.pageSize);
125
171
  this.offset = this.follow ? this.maxOffset : Math.min(this.offset, this.maxOffset);
@@ -1,6 +1,13 @@
1
+ import type { ContextLedger } from "../core/context-ledger.js";
2
+ import { WorkerTranscripts } from "./worker-transcripts.js";
1
3
  import type { AgentEvent } from "../core/types.js";
2
4
  import type { SessionUsageRecord } from "../core/session-usage.js";
3
5
  export interface AgentSession {
6
+ readonly kingdomId?: string;
7
+ readonly context?: ContextLedger;
8
+ readonly compacting?: boolean;
9
+ readonly compactingAt?: number;
10
+ readonly notice?: string;
4
11
  readonly id: string;
5
12
  readonly actor: string;
6
13
  readonly goal: string;
@@ -16,10 +23,14 @@ export interface AgentSession {
16
23
  readonly transcript: readonly string[];
17
24
  }
18
25
  export declare class AgentSessions {
26
+ readonly conversations: WorkerTranscripts;
19
27
  private sessions;
20
28
  private actors;
21
29
  private seenUsage;
30
+ private deleted;
22
31
  private listeners;
32
+ private messageListeners;
33
+ onMessage(listener: (id: string, messageId: string) => void): () => void;
23
34
  subscribe(listener: () => void): () => void;
24
35
  private changed;
25
36
  reset(): void;
@@ -31,6 +42,9 @@ export declare class AgentSessions {
31
42
  get(id: string, now?: number): AgentSession | undefined;
32
43
  get hasAgents(): boolean;
33
44
  private snapshot;
45
+ /** Remove only the live UI session, never files or persisted team reports. */
46
+ delete(id: string): boolean;
47
+ notify(id: string, notice: string): void;
34
48
  begin(goal: string, at?: number): void;
35
49
  finish(at?: number): void;
36
50
  private stop;
@@ -1,4 +1,6 @@
1
+ import { WorkerTranscripts } from "./worker-transcripts.js";
1
2
  import { tr } from "./i18n.js";
3
+ import { runtimeError } from "./runtime-error.js";
2
4
  import { oneLine, plainText } from "./terminal-text.js";
3
5
  const EMPTY = { input: 0, output: 0 };
4
6
  const MAIN = "main";
@@ -14,10 +16,17 @@ function fresh(id, actor, goal = "") {
14
16
  // Session IDs come from the worker lifecycle, not from a tool call or a label.
15
17
  // A finished response does not imply that its worker has been despawned.
16
18
  export class AgentSessions {
19
+ conversations = new WorkerTranscripts();
17
20
  sessions = new Map([[MAIN, fresh(MAIN, MAIN)]]);
18
21
  actors = new Map();
19
22
  seenUsage = new Set();
23
+ deleted = new Set();
20
24
  listeners = new Set();
25
+ messageListeners = new Set();
26
+ onMessage(listener) {
27
+ this.messageListeners.add(listener);
28
+ return () => { this.messageListeners.delete(listener); };
29
+ }
21
30
  subscribe(listener) {
22
31
  this.listeners.add(listener);
23
32
  return () => { this.listeners.delete(listener); };
@@ -25,9 +34,11 @@ export class AgentSessions {
25
34
  changed() { for (const listener of this.listeners)
26
35
  listener(); }
27
36
  reset() {
37
+ this.conversations.reset();
28
38
  this.sessions = new Map([[MAIN, fresh(MAIN, MAIN)]]);
29
39
  this.actors.clear();
30
40
  this.seenUsage.clear();
41
+ this.deleted.clear();
31
42
  this.changed();
32
43
  }
33
44
  restoreMain(history) {
@@ -49,11 +60,31 @@ export class AgentSessions {
49
60
  get hasAgents() { return [...this.sessions.values()].some(s => s.id !== MAIN && s.live); }
50
61
  snapshot(s, now) {
51
62
  const usage = s.ledgerSeen ? s.ledger : s.fallback;
52
- return { id: s.id, actor: s.actor, goal: s.goal, activity: s.activity, live: s.live, running: s.running,
63
+ return { kingdomId: s.kingdomId, context: s.context, compacting: s.compacting, compactingAt: s.compactingAt, notice: s.notice, id: s.id, actor: s.actor, goal: s.goal, activity: s.activity, live: s.live, running: s.running,
53
64
  startedAt: s.startedAt, elapsedMs: s.elapsedMs + (s.startedAt === undefined ? 0 : Math.max(0, now - s.startedAt)),
54
65
  inputTokens: usage.input, outputTokens: usage.output, usageKnown: s.usageKnown, usageMissing: s.usageMissing,
55
66
  transcript: s.streaming ? [...s.transcript, s.streaming] : s.transcript };
56
67
  }
68
+ /** Remove only the live UI session, never files or persisted team reports. */
69
+ delete(id) {
70
+ if (id === MAIN || !this.sessions.has(id))
71
+ return false;
72
+ this.deleted.add(id);
73
+ this.sessions.delete(id);
74
+ this.conversations.delete(id);
75
+ for (const [actor, sessionId] of this.actors)
76
+ if (sessionId === id)
77
+ this.actors.delete(actor);
78
+ this.changed();
79
+ return true;
80
+ }
81
+ notify(id, notice) {
82
+ const session = this.sessions.get(id);
83
+ if (session) {
84
+ this.sessions.set(id, { ...session, notice });
85
+ this.changed();
86
+ }
87
+ }
57
88
  begin(goal, at = Date.now()) {
58
89
  const s = this.sessions.get(MAIN);
59
90
  this.sessions.set(MAIN, { ...s, goal, startedAt: s.startedAt ?? at, running: true,
@@ -86,24 +117,34 @@ export class AgentSessions {
86
117
  }
87
118
  record(event, at = Date.now(), actor = "Talos") {
88
119
  if (event.type === "kingdom" && event.phase === "session") {
120
+ if (this.deleted.has(event.sessionId))
121
+ return;
122
+ if (event.state === "despawned")
123
+ this.conversations.delete(event.sessionId);
89
124
  if (event.state === "spawned") {
90
125
  if (!this.sessions.has(event.sessionId)) {
91
126
  this.sessions.set(event.sessionId, { ...fresh(event.sessionId, event.slug, event.goal),
92
- startedAt: event.at, running: true, activity: tr("Avvio", "Starting") });
127
+ kingdomId: event.kingdomId, startedAt: event.at, running: true, activity: tr("Avvio", "Starting") });
93
128
  }
94
129
  this.actors.set(event.slug, event.sessionId);
130
+ const session = this.sessions.get(event.sessionId);
131
+ this.sessions.set(event.sessionId, { ...session, live: true, running: true, startedAt: session.startedAt ?? event.at });
95
132
  }
96
133
  else
97
- this.stop(event.sessionId, event.at, true);
134
+ this.stop(event.sessionId, event.at, event.state === "despawned");
98
135
  // Retain a small completed history, without dropping any live worker.
99
136
  const completed = [...this.sessions.values()].filter(s => !s.live);
100
- for (const s of completed.slice(0, Math.max(0, completed.length - 32)))
137
+ for (const s of completed.slice(0, Math.max(0, completed.length - 32))) {
101
138
  this.sessions.delete(s.id);
139
+ this.conversations.delete(s.id);
140
+ }
102
141
  this.changed();
103
142
  return;
104
143
  }
105
144
  let id = actor === "Talos" ? MAIN : this.actors.get(actor);
106
145
  if (event.type === "usage") {
146
+ if (this.deleted.has(event.record.sessionId))
147
+ return;
107
148
  id = this.sessions.has(event.record.sessionId) ? event.record.sessionId
108
149
  : event.record.actor ? this.actors.get(event.record.actor) : id;
109
150
  }
@@ -131,9 +172,21 @@ export class AgentSessions {
131
172
  s = { ...s, fallback: event.usage ? add(s.fallback, tokens(event.usage)) : s.fallback,
132
173
  usageKnown: s.usageKnown || !!event.usage };
133
174
  }
175
+ else if (event.type === "context_window")
176
+ s = { ...s, context: event.ledger };
177
+ else if (event.type === "compacting")
178
+ s = { ...s, compacting: true, compactingAt: at };
179
+ else if (event.type === "compaction_end")
180
+ s = { ...s, compacting: false };
181
+ else if (event.type === "compacted")
182
+ s = { ...s, compacting: false,
183
+ context: s.context ? { ...s.context, tokens: event.tokensAfter, fraction: event.tokensAfter / s.context.window, pressure: event.tokensAfter / s.context.compactAt } : undefined };
134
184
  else if (event.type === "status")
135
185
  s = { ...s, activity: oneLine(event.label) };
136
186
  else if (event.type === "turn_start")
187
+ s = { ...s, running: true, live: true,
188
+ startedAt: s.startedAt ?? at, notice: undefined, activity: tr("Elaborazione", "Thinking") };
189
+ else if (event.type === "reasoning")
137
190
  s = { ...s, activity: tr("Elaborazione", "Thinking") };
138
191
  else if (event.type === "text")
139
192
  s = { ...this.log(s, event.delta, true), activity: tr("Risposta in corso", "Writing") };
@@ -148,9 +201,9 @@ export class AgentSessions {
148
201
  else if (event.type === "user_message")
149
202
  s = this.log(s, event.text);
150
203
  else if (event.type === "error")
151
- s = { ...this.log(s, event.message), activity: oneLine(event.message) };
204
+ s = { ...this.log(s, runtimeError(event.message)), activity: oneLine(runtimeError(event.message)) };
152
205
  else if (event.type === "done")
153
- s = { ...s, activity: tr("Concluso", "Finished") };
206
+ s = { ...s, compacting: false, activity: tr("Concluso", "Finished") };
154
207
  else if (event.type === "kingdom") {
155
208
  if (event.phase === "usage")
156
209
  s = { ...s, fallback: { input: count(event.inputTokens), output: count(event.outputTokens) }, usageKnown: true };
@@ -169,6 +222,14 @@ export class AgentSessions {
169
222
  }
170
223
  else
171
224
  return;
225
+ if (id !== MAIN && s.live) {
226
+ this.conversations.record(id, event);
227
+ if (event.type === "user_message") {
228
+ s = { ...s, notice: tr("Messaggio preso in carico", "Message received") };
229
+ for (const listener of this.messageListeners)
230
+ listener(id, event.id);
231
+ }
232
+ }
172
233
  this.sessions.set(id, s);
173
234
  this.changed();
174
235
  }