@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
@@ -1,12 +1,14 @@
1
1
  import { tr } from "./i18n.js";
2
2
  import { stdin, stdout } from "node:process";
3
- import { pulseGlyph, pulseToolName } from "./tool-style.js";
4
3
  import { fitLine, plainText, terminalWidth, wrapText } from "./terminal-text.js";
5
4
  import { PromptQueue } from "./prompt-queue.js";
6
5
  import { QueueScreen, queuePreview } from "./queue-screen.js";
7
6
  import { createAttachmentHighlighter } from "./attachment-highlight.js";
8
7
  import { NativeScrollback } from "./native-scrollback.js";
9
8
  import { QueuedQuestions } from "./queued-questions.js";
9
+ import { AgentSessionsScreen } from "./agent-sessions-screen.js";
10
+ import { SessionNamingNotice } from "./session-naming-notice.js";
11
+ import { renderCompactionStatus, shimmerText } from "./compaction-status.js";
10
12
  const SYNC_ON = "\x1b[?2026h";
11
13
  const SYNC_OFF = "\x1b[?2026l";
12
14
  const ALT_ON = "\x1b[?1049h";
@@ -22,7 +24,6 @@ export const NATIVE_SCROLL_MODE = "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\
22
24
  // crudo nel buffer (parita' Claude Code).
23
25
  const PASTE_ON = "\x1b[?2004h";
24
26
  const PASTE_OFF = "\x1b[?2004l";
25
- const SPINNER = ["✶", "✸", "✺", "✹", "✷", "✦"];
26
27
  const MAX_LOG = 5000;
27
28
  const SCROLL_FRAME_MS = 16;
28
29
  const RESIZE_SETTLE_MS = 80;
@@ -48,6 +49,8 @@ export class BottomInput {
48
49
  buffer = "";
49
50
  cursor = 0;
50
51
  history = [];
52
+ composerSession = "main";
53
+ sessionDrafts = new Map();
51
54
  histIdx = -1;
52
55
  draft = "";
53
56
  active = false;
@@ -55,9 +58,35 @@ export class BottomInput {
55
58
  ctrlCConfirmation = null;
56
59
  ctrlCTimer;
57
60
  running = false;
61
+ commandPending = false;
58
62
  questions = new QueuedQuestions(() => this.refresh());
59
63
  runAbort;
60
64
  queue;
65
+ workerQueues = new Map();
66
+ get composerQueue() {
67
+ if (this.composerSession === "main")
68
+ return this.queue;
69
+ let queue = this.workerQueues.get(this.composerSession);
70
+ if (!queue) {
71
+ queue = new PromptQueue(this.h.prepareQueuedInput, () => this.queueScrollRender());
72
+ this.workerQueues.set(this.composerSession, queue);
73
+ }
74
+ return queue;
75
+ }
76
+ ensureWorkerQueue() {
77
+ const session = this.agentSessions?.session;
78
+ if (!session || session.id === "main" || !this.h.bindSessionInbox)
79
+ return undefined;
80
+ const queue = this.workerQueues.get(session.id) ?? new PromptQueue(this.h.prepareQueuedInput, () => this.queueScrollRender());
81
+ // A footer/queue preview may have allocated it before the runtime was reachable.
82
+ const result = this.h.bindSessionInbox(session, queue.inbox);
83
+ if (!result.ok) {
84
+ this.h.review?.sessions.notify(session.id, result.error ?? "Coda non disponibile");
85
+ return undefined;
86
+ }
87
+ this.workerQueues.set(session.id, queue);
88
+ return queue;
89
+ }
61
90
  queueEditor = null;
62
91
  // Log della chat (righe gia' formattate) + scroll del viewport (0 = in fondo).
63
92
  log = [];
@@ -87,10 +116,17 @@ export class BottomInput {
87
116
  kingdomBoard = null;
88
117
  overlay = null;
89
118
  screen = null;
119
+ agentSessions;
120
+ sessionsUnsubscribe;
121
+ messagesUnsubscribe;
122
+ sessionsTimer;
90
123
  // Domanda a risposta libera (tool ask_user): quando attiva, l'editing va alla
91
124
  // risposta e Invio/Esc risolvono la promise invece di inviare un prompt.
92
125
  modal = null;
93
126
  statusLabel = null;
127
+ sessionLimitNotice = null;
128
+ compactionStartedAt = null;
129
+ compactionPreviousStatus = null;
94
130
  // Nome GREZZO del tool attualmente in corso (es. "read_file"): pilota colore e
95
131
  // glyph animati nella status-line (pulse abbinato alla categoria). null quando
96
132
  // l'attivita' non e' un singolo tool (es. "elaboro", ondata mista).
@@ -107,6 +143,8 @@ export class BottomInput {
107
143
  tokensOut = 0;
108
144
  frame = 0;
109
145
  spinnerTimer;
146
+ namingNotice = new SessionNamingNotice(() => { if (this.active)
147
+ this.render(); });
110
148
  lastLayout;
111
149
  liveStartRow = 1;
112
150
  paintedRows = [];
@@ -138,15 +176,25 @@ export class BottomInput {
138
176
  this.h = h;
139
177
  this.nativeScroll = h.nativeScroll !== false;
140
178
  this.queue = this.createQueue();
179
+ if (h.review)
180
+ this.agentSessions = new AgentSessionsScreen(h.review.sessions, h.queueStyle, h.onSessionDelete);
141
181
  }
142
182
  createQueue() {
143
- return new PromptQueue(this.h.prepareQueuedInput, () => this.render(), this.h.queuePersistence?.(), this.h.restoreQueuedInput);
183
+ return new PromptQueue(this.h.prepareQueuedInput, () => this.workerVisible ? this.queueScrollRender() : this.render(), this.h.queuePersistence?.(), this.h.restoreQueuedInput);
144
184
  }
145
185
  restoreQueue() {
146
186
  this.inputGeneration++;
147
187
  this.queue = this.createQueue();
188
+ this.workerQueues.clear();
189
+ this.sessionDrafts.clear();
190
+ this.composerSession = "main";
191
+ this.buffer = "";
192
+ this.cursor = 0;
193
+ this.history = [];
194
+ this.histIdx = -1;
195
+ this.agentSessions?.returnToMain();
148
196
  this.queueEditor = null;
149
- if (this.screen instanceof QueueScreen)
197
+ if (this.screen instanceof QueueScreen || this.screen === this.agentSessions)
150
198
  this.screen = null;
151
199
  this.render();
152
200
  }
@@ -155,7 +203,7 @@ export class BottomInput {
155
203
  acceptQueuedMessage(id) {
156
204
  const text = this.queue.acknowledge(id);
157
205
  if (text)
158
- this.history.push(text);
206
+ (this.composerSession === "main" ? this.history : this.sessionDrafts.get("main")?.history)?.push(text);
159
207
  return text;
160
208
  }
161
209
  pauseQueuedMessages() { if (this.queue.length) {
@@ -171,19 +219,21 @@ export class BottomInput {
171
219
  this.arrowTimer = undefined;
172
220
  }
173
221
  this.pendingArrows = [];
174
- this.queue.held = true;
175
- this.screen = new QueueScreen(this.queue, id => this.editQueuedMessage(id), this.h.queueStyle);
222
+ this.composerQueue.held = true;
223
+ this.screen = new QueueScreen(this.composerQueue, id => this.editQueuedMessage(id), this.h.queueStyle);
176
224
  this.render();
177
225
  }
178
226
  closeScreen() {
179
227
  if (this.screen instanceof QueueScreen)
180
- this.queue.held = false;
181
- this.screen = null;
228
+ this.composerQueue.held = false;
229
+ this.screen = this.agentSessions?.activeId !== "main" && this.agentSessions ? this.agentSessions : null;
230
+ if (this.composerSession !== "main" && this.agentSessions?.activeId !== "main" && !this.composerQueue.blocked)
231
+ this.h.wakeSession?.(this.agentSessions.session);
182
232
  this.startNextQueued();
183
233
  this.render();
184
234
  }
185
235
  editQueuedMessage(id) {
186
- const entry = this.queue.get(id);
236
+ const entry = this.composerQueue.get(id);
187
237
  if (!entry || !(this.screen instanceof QueueScreen))
188
238
  return;
189
239
  this.inputGeneration++;
@@ -200,7 +250,7 @@ export class BottomInput {
200
250
  return;
201
251
  if (save) {
202
252
  try {
203
- if (!this.queue.update(editor.id, this.buffer))
253
+ if (!this.composerQueue.update(editor.id, this.buffer))
204
254
  throw new Error("Il messaggio non è più in attesa.");
205
255
  }
206
256
  catch (error) {
@@ -213,7 +263,7 @@ export class BottomInput {
213
263
  this.histIdx = editor.draft.histIdx;
214
264
  this.queueEditor = null;
215
265
  this.screen = editor.screen;
216
- if (immediate && this.queue.sendNow(editor.id)) {
266
+ if (immediate && this.composerQueue.sendNow(editor.id)) {
217
267
  this.closeScreen();
218
268
  return;
219
269
  }
@@ -231,7 +281,48 @@ export class BottomInput {
231
281
  this.screen = this.h.createReviewScreen(section);
232
282
  this.render();
233
283
  }
234
- get screenVisible() { return this.screen !== null && !this.modal && !this.overlay; }
284
+ get screenVisible() { return this.screen !== null && this.screen !== this.agentSessions && !this.modal && !this.overlay; }
285
+ get workerVisible() { return !!this.agentSessions && this.screen === this.agentSessions; }
286
+ syncSessionComposer() {
287
+ const id = this.agentSessions?.activeId ?? "main";
288
+ const retired = this.composerSession !== "main" && !this.h.review?.sessions.get(this.composerSession)?.live;
289
+ if (retired) {
290
+ if (this.queueEditor) {
291
+ this.queueEditor = null;
292
+ this.inputGeneration++;
293
+ }
294
+ if (this.screen instanceof QueueScreen)
295
+ this.screen = null;
296
+ }
297
+ if (id === this.composerSession || this.modal || this.overlay || this.queueEditor)
298
+ return;
299
+ if (!retired)
300
+ this.sessionDrafts.set(this.composerSession, { buffer: this.buffer, cursor: this.cursor,
301
+ history: this.history, histIdx: this.histIdx, draft: this.draft });
302
+ const saved = this.sessionDrafts.get(id);
303
+ this.buffer = saved?.buffer ?? "";
304
+ this.cursor = saved?.cursor ?? 0;
305
+ this.history = saved?.history ?? [];
306
+ this.histIdx = saved?.histIdx ?? -1;
307
+ this.draft = saved?.draft ?? "";
308
+ this.composerSession = id;
309
+ if (id !== "main")
310
+ this.ensureWorkerQueue();
311
+ this.inputGeneration++;
312
+ this.cancelCtrlC();
313
+ }
314
+ showAgents() {
315
+ if (!this.agentSessions?.available || this.modal || this.overlay || this.queueEditor || this.queue.held)
316
+ return;
317
+ this.agentSessions.focused = true;
318
+ this.scrollOffset = 0;
319
+ this.render();
320
+ }
321
+ sessionArrow(name) {
322
+ return !!this.agentSessions?.available && !this.modal && !this.overlay && !this.queueEditor
323
+ && !this.questions.focused && this.scrollOffset === 0
324
+ && (this.agentSessions.focused || (!this.buffer && name === "down"));
325
+ }
235
326
  static supported() {
236
327
  return Boolean(stdout.isTTY && stdin.isTTY && typeof stdin.setRawMode === "function");
237
328
  }
@@ -239,6 +330,23 @@ export class BottomInput {
239
330
  if (!BottomInput.supported())
240
331
  return Promise.resolve();
241
332
  this.active = true;
333
+ // A burst of worker deltas must yield to stdin and timers before painting.
334
+ // Reuse the bounded frame scheduler; keyboard navigation still paints now.
335
+ this.sessionsUnsubscribe = this.h.review?.sessions.subscribe(() => this.queueScrollRender());
336
+ this.messagesUnsubscribe = this.h.review?.sessions.onMessage((id, messageId) => {
337
+ const text = this.workerQueues.get(id)?.acknowledge(messageId);
338
+ if (!text)
339
+ return;
340
+ if (this.composerSession === id)
341
+ this.history.push(text);
342
+ else
343
+ this.sessionDrafts.get(id)?.history.push(text);
344
+ });
345
+ this.sessionsTimer = setInterval(() => {
346
+ if (this.agentSessions?.available || this.screen === this.agentSessions)
347
+ this.refresh();
348
+ }, 500);
349
+ this.sessionsTimer.unref();
242
350
  this.alternateScreen = !this.nativeScroll;
243
351
  stdout.write(this.nativeScroll ? "\x1b[2J\x1b[H" + NATIVE_SCROLL_MODE + PASTE_ON
244
352
  : ALT_ON + "\x1b[2J\x1b[H" + NATIVE_SELECTION_MODE + PASTE_ON);
@@ -300,6 +408,7 @@ export class BottomInput {
300
408
  }
301
409
  // Forget the previous session's display and editor state before any replay.
302
410
  clearConversation() {
411
+ this.namingNotice.clear();
303
412
  this.questions.clear();
304
413
  this.inputGeneration++;
305
414
  this.buffer = "";
@@ -317,6 +426,7 @@ export class BottomInput {
317
426
  this.vassalOpen = false;
318
427
  this.kingdomBoard = null;
319
428
  this.statusLabel = null;
429
+ this.sessionLimitNotice = null;
320
430
  this.statusTool = null;
321
431
  this.roomStatus = null;
322
432
  this.tokensNew = this.tokensCache = this.tokensOut = 0;
@@ -391,7 +501,16 @@ export class BottomInput {
391
501
  this.render();
392
502
  }
393
503
  setStatus(label) {
504
+ if (label === null)
505
+ this.compactionStartedAt = null;
394
506
  this.statusLabel = label;
507
+ const limit = label?.match(/^Limite sessione di 5 ore raggiunto · reset alle (.+?) · riprendo automaticamente al reset · Esc per annullare$/);
508
+ this.sessionLimitNotice = limit ? {
509
+ model: this.h.currentModel?.(),
510
+ text: tr("Limite della sessione raggiunto · reset alle {time}\nRipresa automatica alle {clock} · Esc per annullare", "You've hit your session limit · resets {time}\nContinuing automatically at {clock} · esc to cancel", { time: limit[1], clock: limit[1].replace(/\s*\([^)]*\)$/, "") }),
511
+ } : null;
512
+ if (this.sessionLimitNotice)
513
+ this.namingNotice.clear();
395
514
  if (label !== null) {
396
515
  if (!this.spinnerTimer)
397
516
  this.spinnerTimer = setInterval(() => { this.frame += 1; this.render(); }, 140);
@@ -403,12 +522,61 @@ export class BottomInput {
403
522
  if (this.active)
404
523
  this.render();
405
524
  }
525
+ setCompacting(active) {
526
+ if (active) {
527
+ if (this.compactionStartedAt === null) {
528
+ this.compactionStartedAt = Date.now();
529
+ this.compactionPreviousStatus = this.statusLabel;
530
+ }
531
+ this.setStatus(this.statusLabel ?? "Compacting context");
532
+ return;
533
+ }
534
+ const previous = this.compactionPreviousStatus;
535
+ this.compactionStartedAt = null;
536
+ this.compactionPreviousStatus = null;
537
+ this.setStatus(previous);
538
+ }
539
+ showSessionNaming(title) {
540
+ this.namingNotice.show(title);
541
+ }
542
+ setSessionNaming(phase, title) {
543
+ if (this.sessionLimitNotice) {
544
+ this.namingNotice.clear();
545
+ return;
546
+ }
547
+ if (phase === "start")
548
+ this.namingNotice.start();
549
+ else
550
+ this.namingNotice.finish(title);
551
+ }
406
552
  getLogLine(index) { return this.log[index]; }
407
553
  // Stato leggibile dall'host per evitare una seconda telemetria statica mentre
408
554
  // la riga animata del run sta gia' mostrando token, tempo e coda.
409
555
  isBusy() {
410
556
  return this.running || this.statusLabel !== null;
411
557
  }
558
+ // Controllo remoto: un messaggio dal telefono entra come se fosse scritto
559
+ // qui (a riposo parte, durante un giro va in coda o, con `subito`, viene
560
+ // consegnato al primo confine sicuro). Mai un comando "/": dal telefono non
561
+ // si cambiano modello, permessi o account.
562
+ submitExternal(line, immediate = false) {
563
+ const text = line.trim();
564
+ if (!this.active || !text || text.startsWith("/"))
565
+ return false;
566
+ this.dispatch(text, immediate);
567
+ this.render();
568
+ return true;
569
+ }
570
+ // Ferma il lavoro della chat principale, come Esc.
571
+ requestStop() {
572
+ if (!this.running)
573
+ return false;
574
+ this.cancelCtrlC();
575
+ this.queue.paused = true;
576
+ this.runAbort?.abort();
577
+ this.render();
578
+ return true;
579
+ }
412
580
  // Nome del tool in corso per l'animazione della status-line. null = attivita'
413
581
  // generica (spinner neutro). Non forza un timer: lo gestisce gia' setStatus.
414
582
  setStatusTool(name) {
@@ -671,6 +839,7 @@ export class BottomInput {
671
839
  touchLog() {
672
840
  this.logRevision += 1;
673
841
  }
842
+ cyclePermissionMode() { this.h.onCycleMode?.(); }
674
843
  pause() {
675
844
  if (this.paused)
676
845
  return;
@@ -775,13 +944,25 @@ export class BottomInput {
775
944
  render(force = false) {
776
945
  if (!this.active || this.resizeTimer || (this.paused && !this.overlay))
777
946
  return;
947
+ this.agentSessions?.sync();
948
+ if (this.agentSessions && this.screen === this.agentSessions && this.agentSessions.activeId === "main")
949
+ this.screen = null;
778
950
  if (this.scrollRenderTimer) {
779
951
  clearTimeout(this.scrollRenderTimer);
780
952
  this.scrollRenderTimer = undefined;
781
953
  }
954
+ this.syncSessionComposer();
955
+ for (const [id, queue] of this.workerQueues)
956
+ if (!this.h.review?.sessions.get(id)?.live) {
957
+ queue.dispose();
958
+ this.workerQueues.delete(id);
959
+ }
960
+ for (const id of this.sessionDrafts.keys())
961
+ if (id !== "main" && !this.h.review?.sessions.get(id)?.live)
962
+ this.sessionDrafts.delete(id);
782
963
  const H = this.rows();
783
964
  const W = this.cols();
784
- const alternate = !this.nativeScroll || this.screenVisible || this.overlay !== null || this.vassalOpen || this.scrollOffset > 0;
965
+ const alternate = !this.nativeScroll || this.screenVisible || this.workerVisible || this.overlay !== null || this.vassalOpen || this.scrollOffset > 0;
785
966
  if (alternate !== this.alternateScreen) {
786
967
  stdout.write(alternate ? ALT_ON + "\x1b[2J\x1b[H" + NATIVE_SELECTION_MODE : ALT_OFF + NATIVE_SCROLL_MODE);
787
968
  this.alternateScreen = alternate;
@@ -790,8 +971,10 @@ export class BottomInput {
790
971
  force = true;
791
972
  }
792
973
  if (this.screenVisible) {
793
- const content = this.screen.render(W, H);
794
- const rows = Array.from({ length: H }, (_, i) => fitLine(content[i] ?? "", Math.max(1, W - 1)));
974
+ const warnings = (this.h.warningLines?.() ?? []).slice(0, Math.max(0, H - 3));
975
+ const bodyHeight = H - warnings.length;
976
+ const content = this.screen.render(W, bodyHeight);
977
+ const rows = Array.from({ length: H }, (_, i) => fitLine((i < bodyHeight ? content[i] : warnings[i - bodyHeight]) ?? "", Math.max(1, W - 1)));
795
978
  this.paint(rows, W, H, 1, 1, force, true);
796
979
  return;
797
980
  }
@@ -809,9 +992,14 @@ export class BottomInput {
809
992
  }
810
993
  const lay = this.liveContent();
811
994
  this.lastLayout = lay;
995
+ if (this.overlay && lay.lines.length >= H - 1) {
996
+ const frame = Array.from({ length: H }, (_, i) => fitLine(lay.lines[i] ?? "", W - 1));
997
+ this.paint(frame, W, H, Math.min(H, lay.cursorRow + 1), lay.cursorCol, force, true);
998
+ return;
999
+ }
812
1000
  if (this.nativeScroll && !alternate) {
813
1001
  this.renderedLog();
814
- const output = this.nativeOutput.render(this.renderedEntries, lay, W, H, false, this.h.startupLines?.() ?? this.h.headerLines?.() ?? []);
1002
+ const output = this.nativeOutput.render(this.renderedEntries, lay, W, H, false, this.h.startupLines?.() ?? this.h.headerLines?.() ?? [], chunk => { stdout.write(chunk); });
815
1003
  if (output)
816
1004
  stdout.write(output);
817
1005
  this.lastPaintAt = Date.now();
@@ -819,10 +1007,17 @@ export class BottomInput {
819
1007
  return;
820
1008
  }
821
1009
  const liveH = Math.min(lay.lines.length, H - 1);
822
- const requestedHeader = this.h.headerLines?.() ?? [];
1010
+ const requestedHeader = this.workerVisible ? [] : this.h.headerLines?.() ?? [];
823
1011
  const headerH = Math.min(requestedHeader.length, Math.max(0, H - liveH - 1));
824
1012
  const header = requestedHeader.slice(0, headerH);
825
1013
  const chatRows = Math.max(1, H - liveH - headerH);
1014
+ if (this.workerVisible && !this.overlay && !this.modal) {
1015
+ const visible = this.agentSessions.conversation(W, chatRows);
1016
+ const frame = [...header, ...Array.from({ length: chatRows }, (_, i) => visible[i] ?? ""), ...lay.lines.slice(0, liveH)];
1017
+ this.liveStartRow = headerH + chatRows + 1;
1018
+ this.paint(frame.map(line => fitLine(line, W - 1)), W, H, Math.min(H, this.liveStartRow + lay.cursorRow), lay.cursorCol, force);
1019
+ return;
1020
+ }
826
1021
  const rendered = this.renderedLog();
827
1022
  const total = rendered.length;
828
1023
  const maxOffset = Math.max(0, total - chatRows);
@@ -869,6 +1064,11 @@ export class BottomInput {
869
1064
  this.lastPaintAt = Date.now();
870
1065
  }
871
1066
  scrollBy(delta) {
1067
+ if (this.workerVisible) {
1068
+ this.agentSessions.scroll(-delta);
1069
+ this.queueScrollRender();
1070
+ return;
1071
+ }
872
1072
  const next = Math.max(0, this.scrollOffset + delta);
873
1073
  if (next === this.scrollOffset)
874
1074
  return;
@@ -892,20 +1092,55 @@ export class BottomInput {
892
1092
  layout() {
893
1093
  const cols = this.cols();
894
1094
  const lines = [];
895
- if (this.statusLabel !== null)
896
- lines.push(this.statusLine());
897
- if (this.statusLabel === null && this.roomStatus?.state === "failed") {
898
- const notice = this.roomSegment().replace(/\s+/g, " ").trim();
899
- lines.push(...wrapText(notice, Math.max(8, cols - 3)).slice(0, 3).map(line => ` ${line}`));
900
- lines.push(fitLine(tr(" Ctrl+R dettagli", " Ctrl+R details"), cols - 1));
1095
+ const worker = this.workerVisible ? this.agentSessions.session : undefined;
1096
+ const hasRunningStatus = worker ? worker.running : this.compactionStartedAt !== null || this.statusLabel !== null;
1097
+ if (worker) {
1098
+ const style = this.h.statusStyle ?? ((s) => s);
1099
+ lines.push(fitLine(style(` ${worker.actor} · ${worker.compacting ? tr("Compattazione in corso", "Compacting") : worker.activity}`), cols - 1));
1100
+ if (worker.notice)
1101
+ lines.push(fitLine(style(" " + worker.notice), cols - 1));
1102
+ if (worker.compacting)
1103
+ lines.push(...renderCompactionStatus({ frame: this.frame,
1104
+ elapsedSeconds: (Date.now() - (worker.compactingAt ?? Date.now())) / 1000, columns: cols, style: this.h.statusStyle }));
1105
+ }
1106
+ if (!worker) {
1107
+ if (this.compactionStartedAt !== null) {
1108
+ // A blank row plus a framed block keeps compaction separate from the
1109
+ // transcript. The renderer diffs rows, so the shimmer cannot flicker the
1110
+ // surrounding chat, context meter or composer.
1111
+ lines.push("");
1112
+ lines.push(...renderCompactionStatus({
1113
+ frame: this.frame,
1114
+ elapsedSeconds: (Date.now() - this.compactionStartedAt) / 1000,
1115
+ columns: cols,
1116
+ style: this.h.statusStyle,
1117
+ }));
1118
+ }
1119
+ else if (this.statusLabel !== null && !this.sessionLimitNotice)
1120
+ lines.push(this.statusLine());
1121
+ // L'attivita', il misuratore di contesto e il composer sono tre superfici
1122
+ // diverse: una riga vuota impedisce che si fondano visivamente durante i run.
1123
+ if (hasRunningStatus)
1124
+ lines.push("");
1125
+ if (this.statusLabel === null && this.roomStatus?.state === "failed") {
1126
+ const notice = this.roomSegment().replace(/\s+/g, " ").trim();
1127
+ lines.push(...wrapText(notice, Math.max(8, cols - 3)).slice(0, 3).map(line => ` ${line}`));
1128
+ lines.push(fitLine(tr(" Ctrl+R dettagli", " Ctrl+R details"), cols - 1));
1129
+ }
901
1130
  }
902
1131
  // La riga live descrive gia' il team; la board completa si consulta a parte.
903
- if (this.kingdomBoard && this.statusLabel === null)
1132
+ if (!worker && this.kingdomBoard && this.statusLabel === null)
904
1133
  lines.push(fitLine(this.kingdomBoard[0] ?? "", cols - 1));
905
- for (const f of this.h.footerLines())
1134
+ if (!this.modal && !this.queueEditor && !this.questions.focused) {
1135
+ lines.push(...(this.agentSessions?.preview(cols, Math.min(5, Math.max(2, this.rows() - 12))) ?? []));
1136
+ }
1137
+ const footerLines = worker ? this.h.sessionFooter?.(worker, cols) ?? [] : this.h.footerLines();
1138
+ for (const f of footerLines)
906
1139
  lines.push(f);
1140
+ if (hasRunningStatus && footerLines.length)
1141
+ lines.push("");
907
1142
  if (!this.modal && !this.queueEditor)
908
- lines.push(...queuePreview(this.queue, cols, this.h.queueStyle, !this.running));
1143
+ lines.push(...queuePreview(this.composerQueue, cols, this.h.queueStyle, worker ? !worker.running : !this.running));
909
1144
  const questionStart = lines.length;
910
1145
  const questions = !this.modal && !this.queueEditor
911
1146
  ? this.questions.render(cols, Math.max(5, this.rows() - lines.length - 6), this.h.queueStyle)
@@ -940,7 +1175,7 @@ export class BottomInput {
940
1175
  const inputCap = Math.max(1, Math.min(3, Math.floor(this.rows() / 4)));
941
1176
  const rowOffset = Math.max(0, Math.min(crow - Math.floor(inputCap / 2), rows.length - inputCap));
942
1177
  const visibleRows = rows.slice(rowOffset, rowOffset + inputCap);
943
- const padding = !this.modal && !this.queueEditor && this.rows() >= 16 && this.h.composerPadding?.();
1178
+ const padding = !worker && !this.modal && !this.queueEditor && this.rows() >= 16 && this.h.composerPadding?.();
944
1179
  const blank = " " + (this.h.composerStyle?.(" ".repeat(cell)) ?? " ".repeat(cell));
945
1180
  if (padding)
946
1181
  lines.push(blank);
@@ -959,10 +1194,17 @@ export class BottomInput {
959
1194
  const notice = ` ${this.roomStatus.label}`;
960
1195
  lines.push(fitLine(this.h.statusStyle?.(notice) ?? notice, cols - 1));
961
1196
  }
1197
+ if (this.sessionLimitNotice && this.sessionLimitNotice.model !== this.h.currentModel?.())
1198
+ this.sessionLimitNotice = null;
962
1199
  if (!this.modal) {
963
- for (const row of this.h.belowInputLines?.() ?? [])
1200
+ for (const naming of this.namingNotice.lines()) {
1201
+ lines.push(fitLine(" " + (this.h.statusStyle?.(naming) ?? naming), cols - 1));
1202
+ }
1203
+ for (const row of (worker ? this.h.sessionBelowInput?.(worker, cols) ?? [] : this.h.belowInputLines?.() ?? []))
964
1204
  lines.push(fitLine(row, cols - 1));
965
1205
  }
1206
+ for (const row of this.h.warningLines?.() ?? [])
1207
+ lines.push(fitLine(row, cols - 1));
966
1208
  const action = this.ctrlCConfirmation?.action;
967
1209
  const warning = action === "stop" ? tr("fermare il lavoro e mettere in pausa la coda", "stop work and pause the queue") : action === "clear" ? tr("svuotare la bozza", "clear the draft") : tr("uscire", "exit");
968
1210
  const hint = action
@@ -983,17 +1225,15 @@ export class BottomInput {
983
1225
  rowLengths: visibleRows.map((r) => r.length),
984
1226
  };
985
1227
  }
986
- // Segmento VIVO della stanza Danilov per la status-line: stato animato
987
- // (running pulsa col frame, done/failed con glyph fisso). Vuoto se nessuna.
1228
+ // Segmento VIVO della stanza Danilov. Durante un run resta testuale: un solo
1229
+ // shimmer viene applicato all'intera frase dalla status-line.
988
1230
  roomSegment() {
989
1231
  if (!this.roomStatus || this.roomStatus.state === "done")
990
1232
  return "";
991
1233
  const style = this.h.statusStyle ?? ((s) => s);
992
1234
  const { label, state } = this.roomStatus;
993
- if (state === "running") {
994
- const g = SPINNER[this.frame % SPINNER.length];
995
- return `${g} ${label}`;
996
- }
1235
+ if (state === "running")
1236
+ return label;
997
1237
  const glyph = state === "failed" ? "✗" : "✦";
998
1238
  return style(`${glyph} ${label}`);
999
1239
  }
@@ -1001,20 +1241,21 @@ export class BottomInput {
1001
1241
  const elapsed = Math.round((Date.now() - this.startedAt) / 1000);
1002
1242
  const style = this.h.statusStyle ?? ((s) => s);
1003
1243
  const label = this.statusLabel ?? tr("Elaborazione", "Working");
1004
- const glyph = this.statusTool ? pulseGlyph(this.statusTool, this.frame) : SPINNER[this.frame % SPINNER.length];
1005
- const name = this.statusTool ? pulseToolName(this.statusTool, label, this.frame) : style(label);
1006
- const meta = [elapsed >= 1 ? fmtElapsed(elapsed) : "", this.h.runningHint?.() ?? "", this.roomSegment()].filter(Boolean);
1007
- const right = style(this.cols() >= 64 && (!this.queue.length || this.modal || this.queueEditor)
1008
- ? tr("Alt+S subito · Esc ferma", "Alt+S send now · Esc stop") : tr("Esc ferma", "Esc stop"));
1244
+ const meta = [this.h.runningHint?.() ?? "", this.roomSegment()].filter(Boolean);
1245
+ const shortcut = this.cols() >= 100 && (!this.composerQueue.length || this.modal || this.queueEditor)
1246
+ ? tr("Alt+S subito · Esc ferma", "Alt+S send now · Esc stop") : tr("Esc ferma", "Esc stop");
1247
+ const right = style(`${fmtElapsed(elapsed).padStart(7)} · ${shortcut}`);
1009
1248
  const budget = Math.max(0, this.cols() - terminalWidth(right) - 5);
1010
- let left = ` ${glyph} ${name}`;
1249
+ let text = label;
1011
1250
  for (const segment of meta) {
1012
- const candidate = left + style(` · ${segment}`);
1013
- if (terminalWidth(candidate) > budget)
1251
+ const candidate = text + ` · ${plainText(segment)}`;
1252
+ if (terminalWidth(candidate) + 2 > budget)
1014
1253
  break;
1015
- left = candidate;
1254
+ text = candidate;
1016
1255
  }
1017
- left = fitLine(left, budget);
1256
+ // La geometria non cambia tra i frame: si muove soltanto una sottile banda
1257
+ // di luce sull'intero stato, evitando due o tre spinner concorrenti.
1258
+ let left = fitLine(` ${shimmerText(text, this.frame)}`, budget);
1018
1259
  return fitLine(left + " ".repeat(Math.max(2, this.cols() - 2 - terminalWidth(left) - terminalWidth(right))) + right, this.cols() - 1);
1019
1260
  }
1020
1261
  // ── input ─────────────────────────────────────────────────────────────────
@@ -1126,6 +1367,9 @@ export class BottomInput {
1126
1367
  this.scrollOffset = 0;
1127
1368
  this.render();
1128
1369
  }
1370
+ else if (this.workerVisible) {
1371
+ this.stopRun();
1372
+ }
1129
1373
  else if (this.running) {
1130
1374
  this.stopRun();
1131
1375
  }
@@ -1188,7 +1432,7 @@ export class BottomInput {
1188
1432
  }
1189
1433
  // Freccia su/giu' NUDA: disambigua scroll(touchpad) vs pressione (timer).
1190
1434
  if ((name === "up" || name === "down") && !ctrl && !meta && !shift && !this.screenVisible
1191
- && !this.questions.focused && (!this.nativeScroll || this.scrollOffset > 0)) {
1435
+ && !this.questions.focused && !this.sessionArrow(name) && (!this.nativeScroll || this.scrollOffset > 0)) {
1192
1436
  this.onArrow(name);
1193
1437
  return;
1194
1438
  }
@@ -1268,7 +1512,31 @@ export class BottomInput {
1268
1512
  this.showQueue();
1269
1513
  return;
1270
1514
  }
1271
- if (k.meta && k.name === "t" && !this.modal && !this.overlay && !this.queueEditor) {
1515
+ if (k.meta && k.name === "a") {
1516
+ this.showAgents();
1517
+ return;
1518
+ }
1519
+ if (!k.ctrl && !k.meta && !k.shift && this.sessionArrow(k.name ?? "") && (k.name === "up" || k.name === "down")) {
1520
+ this.agentSessions.key(s, k);
1521
+ this.render();
1522
+ return;
1523
+ }
1524
+ if (this.agentSessions?.focused && !this.modal && !this.overlay && !this.queueEditor) {
1525
+ if (k.name === "delete" && !k.ctrl && !k.meta && !k.shift) {
1526
+ this.agentSessions.key(s, k);
1527
+ this.render();
1528
+ return;
1529
+ }
1530
+ if (k.name === "return" || k.name === "enter") {
1531
+ this.agentSessions.key(s, k);
1532
+ this.screen = this.agentSessions.activeId !== "main" ? this.agentSessions : null;
1533
+ this.agentSessions.focused = this.agentSessions.activeId !== "main";
1534
+ this.render();
1535
+ return;
1536
+ }
1537
+ this.agentSessions.focused = false;
1538
+ }
1539
+ if (!this.workerVisible && k.meta && k.name === "t" && !this.modal && !this.overlay && !this.queueEditor) {
1272
1540
  if (this.queue.sendToTeam())
1273
1541
  this.startNextQueued();
1274
1542
  else
@@ -1286,6 +1554,10 @@ export class BottomInput {
1286
1554
  }
1287
1555
  if (k.ctrl && k.name === "c")
1288
1556
  return this.onCtrlC();
1557
+ if (k.ctrl && k.name === "g" && this.agentSessions?.available) {
1558
+ this.showAgents();
1559
+ return;
1560
+ }
1289
1561
  if (k.ctrl && k.name === "g") {
1290
1562
  // Con un regno attivo: Ctrl+G entra nel viewer e cicla i vassalli (poi esce).
1291
1563
  if (this.vassals && this.vassals.length) {
@@ -1305,7 +1577,15 @@ export class BottomInput {
1305
1577
  return;
1306
1578
  }
1307
1579
  if (k.ctrl && k.name === "b") {
1308
- this.h.onBackground?.();
1580
+ if (this.workerVisible) {
1581
+ const session = this.agentSessions.session;
1582
+ const result = this.h.onSessionBackground?.(session);
1583
+ this.h.review?.sessions.notify(session.id, result?.ok ? tr("Comando in background", "Command backgrounded")
1584
+ : result?.error ?? tr("Nessun comando in primo piano", "No foreground command"));
1585
+ this.render();
1586
+ }
1587
+ else
1588
+ this.h.onBackground?.();
1309
1589
  return;
1310
1590
  }
1311
1591
  // Ctrl+V (e Alt+V come alias): incolla un'immagine dalla clipboard di sistema
@@ -1322,6 +1602,11 @@ export class BottomInput {
1322
1602
  return;
1323
1603
  }
1324
1604
  // Scroll della CHAT (input resta fisso): PageUp/Down, Shift+frecce.
1605
+ if (this.workerVisible && k.ctrl && (k.name === "home" || k.name === "end")) {
1606
+ this.agentSessions.key("", { name: k.name });
1607
+ this.render();
1608
+ return;
1609
+ }
1325
1610
  if (k.name === "pageup")
1326
1611
  return this.scrollBy(this.pageStep());
1327
1612
  if (k.name === "pagedown")
@@ -1524,6 +1809,20 @@ export class BottomInput {
1524
1809
  this.edit(() => { this.cursor = Math.max(0, this.cursor - tw); });
1525
1810
  return;
1526
1811
  }
1812
+ // Sul composer vuoto la voce piu recente ancora in coda torna modificabile.
1813
+ // Un buffer gia compilato conserva invece la normale navigazione verticale
1814
+ // e della cronologia, senza perdere la bozza dell'utente.
1815
+ if (!this.buffer && this.histIdx === -1) {
1816
+ const queued = this.composerQueue.recallLast();
1817
+ if (queued) {
1818
+ this.inputGeneration += 1;
1819
+ this.buffer = queued.text;
1820
+ this.cursor = queued.text.length;
1821
+ this.draft = "";
1822
+ this.render();
1823
+ return;
1824
+ }
1825
+ }
1527
1826
  this.historyPrev();
1528
1827
  }
1529
1828
  onDown() {
@@ -1580,7 +1879,8 @@ export class BottomInput {
1580
1879
  this.resolveModal(null);
1581
1880
  return;
1582
1881
  }
1583
- const action = this.running ? "stop" : this.buffer ? "clear" : "quit";
1882
+ const action = this.workerVisible ? (this.agentSessions.session.running ? "stop" : this.buffer ? "clear" : "quit")
1883
+ : this.running ? "stop" : this.buffer ? "clear" : "quit";
1584
1884
  if (this.ctrlCConfirmation?.action === action && Date.now() < this.ctrlCConfirmation.until) {
1585
1885
  this.cancelCtrlC();
1586
1886
  if (action === "stop") {
@@ -1596,7 +1896,13 @@ export class BottomInput {
1596
1896
  this.render();
1597
1897
  return;
1598
1898
  }
1599
- this.quit();
1899
+ if (this.workerVisible) {
1900
+ this.agentSessions.returnToMain();
1901
+ this.screen = null;
1902
+ this.render();
1903
+ }
1904
+ else
1905
+ this.quit();
1600
1906
  return;
1601
1907
  }
1602
1908
  this.cancelCtrlC();
@@ -1613,11 +1919,18 @@ export class BottomInput {
1613
1919
  }
1614
1920
  stopRun() {
1615
1921
  this.cancelCtrlC();
1922
+ if (this.workerVisible) {
1923
+ const session = this.agentSessions.session;
1924
+ this.composerQueue.paused = true;
1925
+ const result = this.h.onSessionStop?.(session);
1926
+ this.h.review?.sessions.notify(session.id, result?.ok ? tr("Interruzione richiesta", "Stop requested") : result?.error ?? tr("Interruzione non disponibile", "Stop unavailable"));
1927
+ return;
1928
+ }
1616
1929
  const queued = this.queue.length;
1617
1930
  this.queue.paused = true;
1618
1931
  this.runAbort?.abort();
1619
1932
  if (queued)
1620
- this.setRoomStatus(tr("Lavoro interrotto. {queued} messaggi conservati in pausa · Alt+Q o /queue per gestirli.", "Work interrupted. {queued} messages paused · Alt+Q or /queue to manage.", { queued }), "failed");
1933
+ this.setRoomStatus(tr("Lavoro interrotto. {queued} messaggi conservati in pausa · Alt+Q o /queue per gestirli.", "Work interrupted. {queued} messages paused · Alt+Q or /queue to manage.", { queued }), "done");
1621
1934
  }
1622
1935
  // Domanda a risposta libera: l'editing finisce nel buffer, Invio risolve.
1623
1936
  queueQuestion(title, options, signal) {
@@ -1677,6 +1990,17 @@ export class BottomInput {
1677
1990
  this.cursor = 0;
1678
1991
  this.histIdx = -1;
1679
1992
  if (!line.trim()) {
1993
+ if (this.workerVisible) {
1994
+ const queue = this.composerQueue;
1995
+ const next = queue.items()[0];
1996
+ if (immediate && next)
1997
+ queue.sendNow(next.id);
1998
+ else
1999
+ queue.paused = false;
2000
+ this.h.wakeSession?.(this.agentSessions.session);
2001
+ this.render();
2002
+ return;
2003
+ }
1680
2004
  if (immediate) {
1681
2005
  const next = this.queue.items().find(entry => !entry.text.trimStart().startsWith("/"));
1682
2006
  if (next)
@@ -1692,21 +2016,61 @@ export class BottomInput {
1692
2016
  this.showQueue();
1693
2017
  return;
1694
2018
  }
2019
+ if (/^\/(?:agents|agenti)\s*$/i.test(line.trim())) {
2020
+ this.showAgents();
2021
+ return;
2022
+ }
1695
2023
  const review = /^\/(?:review|revisione)(?:\s+(\S+))?\s*$/i.exec(line.trim());
1696
2024
  if (review && this.h.createReviewScreen) {
1697
2025
  this.showReview(review[1]);
1698
2026
  return;
1699
2027
  }
2028
+ if (this.workerVisible) {
2029
+ const session = this.agentSessions.session;
2030
+ const queue = !line.trimStart().startsWith("/") ? this.ensureWorkerQueue() : undefined;
2031
+ if (queue) {
2032
+ try {
2033
+ queue.add(line, immediate ? "immediate" : "during-turn");
2034
+ if (!session.running)
2035
+ queue.paused = false;
2036
+ const result = this.h.wakeSession?.(session);
2037
+ if (result && !result.ok)
2038
+ this.h.review?.sessions.notify(session.id, result.error ?? "Invio non disponibile");
2039
+ }
2040
+ catch (error) {
2041
+ this.buffer = line;
2042
+ this.cursor = line.length;
2043
+ this.h.review?.sessions.notify(session.id, String(error));
2044
+ }
2045
+ this.render();
2046
+ return;
2047
+ }
2048
+ const result = this.h.onSessionSubmit?.(session, line, this.h.prepareQueuedInput?.(line) ?? [{ type: "text", text: line }], immediate);
2049
+ if (result?.ok)
2050
+ this.history.push(line);
2051
+ else {
2052
+ this.buffer = line;
2053
+ this.cursor = line.length;
2054
+ }
2055
+ this.h.review?.sessions.notify(session.id, result?.ok ? tr("Messaggio in attesa di consegna", "Message awaiting delivery")
2056
+ : result?.error ?? tr("Invio non disponibile", "Send unavailable"));
2057
+ this.render();
2058
+ return;
2059
+ }
1700
2060
  this.dispatch(line, immediate);
1701
2061
  }
1702
2062
  dispatch(line, immediate = false) {
2063
+ if (line.trimStart().startsWith("/") && (this.running || this.commandPending)) {
2064
+ this.runLiveCommand(line.trim());
2065
+ return;
2066
+ }
1703
2067
  // Una coda ripristinata in pausa non deve bloccare /new, /resume, /help
1704
2068
  // o /quit. A riposo i comandi controllano la UI senza riprendere la coda.
1705
2069
  if (!this.running && line.trimStart().startsWith("/")) {
1706
2070
  this.runLine(line);
1707
2071
  return;
1708
2072
  }
1709
- if (this.running || this.queue.length) {
2073
+ if (this.running || this.commandPending || this.queue.length) {
1710
2074
  try {
1711
2075
  this.queue.add(line, immediate ? "immediate" : "after-turn");
1712
2076
  // Aprire una chat non avvia lavoro da solo. Un nuovo Invio a riposo
@@ -1729,7 +2093,7 @@ export class BottomInput {
1729
2093
  this.runLine(line);
1730
2094
  }
1731
2095
  startNextQueued() {
1732
- if (!this.active || this.running || this.modal || this.overlay || this.queueEditor)
2096
+ if (!this.active || this.running || this.commandPending || this.modal || this.overlay || this.queueEditor)
1733
2097
  return;
1734
2098
  const next = this.queue.takeNext();
1735
2099
  if (next) {
@@ -1737,9 +2101,34 @@ export class BottomInput {
1737
2101
  this.runLine(next.text, true, next);
1738
2102
  }
1739
2103
  }
2104
+ runLiveCommand(line) {
2105
+ if (this.commandPending) {
2106
+ this.buffer = line;
2107
+ this.cursor = line.length;
2108
+ this.setRoomStatus(tr("Chiudi prima il comando aperto.", "Close the open command first."), "done");
2109
+ return;
2110
+ }
2111
+ if (!this.h.onCommandDuringRun) {
2112
+ this.setRoomStatus(tr("Comando non disponibile durante il lavoro. Riprova a fine turno.", "Command unavailable while working. Retry after the turn."), "done");
2113
+ return;
2114
+ }
2115
+ this.history.push(line);
2116
+ this.commandPending = true;
2117
+ // A UI command must not replace the agent's abort controller or reset its metrics.
2118
+ const signal = new AbortController().signal;
2119
+ Promise.resolve().then(() => this.h.onCommandDuringRun(line, signal))
2120
+ .catch(error => this.reportFailure(`${tr("Errore", "Error")}: ${error instanceof Error ? error.message : String(error)}`))
2121
+ .finally(() => {
2122
+ this.commandPending = false;
2123
+ if (!this.active)
2124
+ return;
2125
+ this.startNextQueued();
2126
+ this.render();
2127
+ });
2128
+ }
1740
2129
  runLine(line, fromQueue = false, queued) {
1741
2130
  this.setRoomStatus(null);
1742
- this.history.push(line);
2131
+ (this.composerSession === "main" ? this.history : this.sessionDrafts.get("main")?.history)?.push(line);
1743
2132
  this.running = true;
1744
2133
  this.startedAt = Date.now();
1745
2134
  this.tokensNew = 0;
@@ -1768,6 +2157,11 @@ export class BottomInput {
1768
2157
  if (!this.active)
1769
2158
  return;
1770
2159
  this.active = false;
2160
+ this.namingNotice.clear();
2161
+ this.sessionsUnsubscribe?.();
2162
+ this.messagesUnsubscribe?.();
2163
+ if (this.sessionsTimer)
2164
+ clearInterval(this.sessionsTimer);
1771
2165
  this.questions.clear();
1772
2166
  this.clearPendingEscape();
1773
2167
  this.cancelCtrlC();
@@ -1802,6 +2196,7 @@ export class BottomInput {
1802
2196
  if (this.nativeScroll)
1803
2197
  stdout.write(this.nativeOutput.release());
1804
2198
  stdout.write(PASTE_OFF + NATIVE_SCROLL_MODE + "\x1b[?25h");
2199
+ this.nativeOutput.dispose();
1805
2200
  if (typeof stdin.setRawMode === "function")
1806
2201
  stdin.setRawMode(false);
1807
2202
  this.h.onQuit();