@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
@@ -8,6 +8,7 @@ import { NativeScrollback } from "./native-scrollback.js";
8
8
  import { QueuedQuestions } from "./queued-questions.js";
9
9
  import { AgentSessionsScreen } from "./agent-sessions-screen.js";
10
10
  import { SessionNamingNotice } from "./session-naming-notice.js";
11
+ import { renderCompactionStatus, shimmerText } from "./compaction-status.js";
11
12
  const SYNC_ON = "\x1b[?2026h";
12
13
  const SYNC_OFF = "\x1b[?2026l";
13
14
  const ALT_ON = "\x1b[?1049h";
@@ -23,7 +24,6 @@ export const NATIVE_SCROLL_MODE = "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\
23
24
  // crudo nel buffer (parita' Claude Code).
24
25
  const PASTE_ON = "\x1b[?2004h";
25
26
  const PASTE_OFF = "\x1b[?2004l";
26
- const SPINNER = ["✶", "✸", "✺", "✹", "✷", "✦"];
27
27
  const MAX_LOG = 5000;
28
28
  const SCROLL_FRAME_MS = 16;
29
29
  const RESIZE_SETTLE_MS = 80;
@@ -49,6 +49,8 @@ export class BottomInput {
49
49
  buffer = "";
50
50
  cursor = 0;
51
51
  history = [];
52
+ composerSession = "main";
53
+ sessionDrafts = new Map();
52
54
  histIdx = -1;
53
55
  draft = "";
54
56
  active = false;
@@ -56,9 +58,35 @@ export class BottomInput {
56
58
  ctrlCConfirmation = null;
57
59
  ctrlCTimer;
58
60
  running = false;
61
+ commandPending = false;
59
62
  questions = new QueuedQuestions(() => this.refresh());
60
63
  runAbort;
61
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
+ }
62
90
  queueEditor = null;
63
91
  // Log della chat (righe gia' formattate) + scroll del viewport (0 = in fondo).
64
92
  log = [];
@@ -90,12 +118,15 @@ export class BottomInput {
90
118
  screen = null;
91
119
  agentSessions;
92
120
  sessionsUnsubscribe;
121
+ messagesUnsubscribe;
93
122
  sessionsTimer;
94
123
  // Domanda a risposta libera (tool ask_user): quando attiva, l'editing va alla
95
124
  // risposta e Invio/Esc risolvono la promise invece di inviare un prompt.
96
125
  modal = null;
97
126
  statusLabel = null;
127
+ sessionLimitNotice = null;
98
128
  compactionStartedAt = null;
129
+ compactionPreviousStatus = null;
99
130
  // Nome GREZZO del tool attualmente in corso (es. "read_file"): pilota colore e
100
131
  // glyph animati nella status-line (pulse abbinato alla categoria). null quando
101
132
  // l'attivita' non e' un singolo tool (es. "elaboro", ondata mista).
@@ -146,16 +177,24 @@ export class BottomInput {
146
177
  this.nativeScroll = h.nativeScroll !== false;
147
178
  this.queue = this.createQueue();
148
179
  if (h.review)
149
- this.agentSessions = new AgentSessionsScreen(h.review.sessions, h.queueStyle);
180
+ this.agentSessions = new AgentSessionsScreen(h.review.sessions, h.queueStyle, h.onSessionDelete);
150
181
  }
151
182
  createQueue() {
152
- 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);
153
184
  }
154
185
  restoreQueue() {
155
186
  this.inputGeneration++;
156
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();
157
196
  this.queueEditor = null;
158
- if (this.screen instanceof QueueScreen)
197
+ if (this.screen instanceof QueueScreen || this.screen === this.agentSessions)
159
198
  this.screen = null;
160
199
  this.render();
161
200
  }
@@ -164,7 +203,7 @@ export class BottomInput {
164
203
  acceptQueuedMessage(id) {
165
204
  const text = this.queue.acknowledge(id);
166
205
  if (text)
167
- this.history.push(text);
206
+ (this.composerSession === "main" ? this.history : this.sessionDrafts.get("main")?.history)?.push(text);
168
207
  return text;
169
208
  }
170
209
  pauseQueuedMessages() { if (this.queue.length) {
@@ -180,19 +219,21 @@ export class BottomInput {
180
219
  this.arrowTimer = undefined;
181
220
  }
182
221
  this.pendingArrows = [];
183
- this.queue.held = true;
184
- 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);
185
224
  this.render();
186
225
  }
187
226
  closeScreen() {
188
227
  if (this.screen instanceof QueueScreen)
189
- this.queue.held = false;
190
- 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);
191
232
  this.startNextQueued();
192
233
  this.render();
193
234
  }
194
235
  editQueuedMessage(id) {
195
- const entry = this.queue.get(id);
236
+ const entry = this.composerQueue.get(id);
196
237
  if (!entry || !(this.screen instanceof QueueScreen))
197
238
  return;
198
239
  this.inputGeneration++;
@@ -209,7 +250,7 @@ export class BottomInput {
209
250
  return;
210
251
  if (save) {
211
252
  try {
212
- if (!this.queue.update(editor.id, this.buffer))
253
+ if (!this.composerQueue.update(editor.id, this.buffer))
213
254
  throw new Error("Il messaggio non è più in attesa.");
214
255
  }
215
256
  catch (error) {
@@ -222,7 +263,7 @@ export class BottomInput {
222
263
  this.histIdx = editor.draft.histIdx;
223
264
  this.queueEditor = null;
224
265
  this.screen = editor.screen;
225
- if (immediate && this.queue.sendNow(editor.id)) {
266
+ if (immediate && this.composerQueue.sendNow(editor.id)) {
226
267
  this.closeScreen();
227
268
  return;
228
269
  }
@@ -240,7 +281,36 @@ export class BottomInput {
240
281
  this.screen = this.h.createReviewScreen(section);
241
282
  this.render();
242
283
  }
243
- 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
+ }
244
314
  showAgents() {
245
315
  if (!this.agentSessions?.available || this.modal || this.overlay || this.queueEditor || this.queue.held)
246
316
  return;
@@ -260,7 +330,18 @@ export class BottomInput {
260
330
  if (!BottomInput.supported())
261
331
  return Promise.resolve();
262
332
  this.active = true;
263
- this.sessionsUnsubscribe = this.h.review?.sessions.subscribe(() => this.refresh());
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
+ });
264
345
  this.sessionsTimer = setInterval(() => {
265
346
  if (this.agentSessions?.available || this.screen === this.agentSessions)
266
347
  this.refresh();
@@ -345,6 +426,7 @@ export class BottomInput {
345
426
  this.vassalOpen = false;
346
427
  this.kingdomBoard = null;
347
428
  this.statusLabel = null;
429
+ this.sessionLimitNotice = null;
348
430
  this.statusTool = null;
349
431
  this.roomStatus = null;
350
432
  this.tokensNew = this.tokensCache = this.tokensOut = 0;
@@ -422,6 +504,13 @@ export class BottomInput {
422
504
  if (label === null)
423
505
  this.compactionStartedAt = null;
424
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();
425
514
  if (label !== null) {
426
515
  if (!this.spinnerTimer)
427
516
  this.spinnerTimer = setInterval(() => { this.frame += 1; this.render(); }, 140);
@@ -434,13 +523,27 @@ export class BottomInput {
434
523
  this.render();
435
524
  }
436
525
  setCompacting(active) {
437
- this.compactionStartedAt = active ? this.compactionStartedAt ?? Date.now() : null;
438
- this.setStatus(this.statusLabel ?? (active ? "Compacting context" : null));
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);
439
538
  }
440
539
  showSessionNaming(title) {
441
540
  this.namingNotice.show(title);
442
541
  }
443
542
  setSessionNaming(phase, title) {
543
+ if (this.sessionLimitNotice) {
544
+ this.namingNotice.clear();
545
+ return;
546
+ }
444
547
  if (phase === "start")
445
548
  this.namingNotice.start();
446
549
  else
@@ -452,6 +555,28 @@ export class BottomInput {
452
555
  isBusy() {
453
556
  return this.running || this.statusLabel !== null;
454
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
+ }
455
580
  // Nome del tool in corso per l'animazione della status-line. null = attivita'
456
581
  // generica (spinner neutro). Non forza un timer: lo gestisce gia' setStatus.
457
582
  setStatusTool(name) {
@@ -714,6 +839,7 @@ export class BottomInput {
714
839
  touchLog() {
715
840
  this.logRevision += 1;
716
841
  }
842
+ cyclePermissionMode() { this.h.onCycleMode?.(); }
717
843
  pause() {
718
844
  if (this.paused)
719
845
  return;
@@ -825,9 +951,18 @@ export class BottomInput {
825
951
  clearTimeout(this.scrollRenderTimer);
826
952
  this.scrollRenderTimer = undefined;
827
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);
828
963
  const H = this.rows();
829
964
  const W = this.cols();
830
- 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;
831
966
  if (alternate !== this.alternateScreen) {
832
967
  stdout.write(alternate ? ALT_ON + "\x1b[2J\x1b[H" + NATIVE_SELECTION_MODE : ALT_OFF + NATIVE_SCROLL_MODE);
833
968
  this.alternateScreen = alternate;
@@ -872,10 +1007,17 @@ export class BottomInput {
872
1007
  return;
873
1008
  }
874
1009
  const liveH = Math.min(lay.lines.length, H - 1);
875
- const requestedHeader = this.h.headerLines?.() ?? [];
1010
+ const requestedHeader = this.workerVisible ? [] : this.h.headerLines?.() ?? [];
876
1011
  const headerH = Math.min(requestedHeader.length, Math.max(0, H - liveH - 1));
877
1012
  const header = requestedHeader.slice(0, headerH);
878
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
+ }
879
1021
  const rendered = this.renderedLog();
880
1022
  const total = rendered.length;
881
1023
  const maxOffset = Math.max(0, total - chatRows);
@@ -922,6 +1064,11 @@ export class BottomInput {
922
1064
  this.lastPaintAt = Date.now();
923
1065
  }
924
1066
  scrollBy(delta) {
1067
+ if (this.workerVisible) {
1068
+ this.agentSessions.scroll(-delta);
1069
+ this.queueScrollRender();
1070
+ return;
1071
+ }
925
1072
  const next = Math.max(0, this.scrollOffset + delta);
926
1073
  if (next === this.scrollOffset)
927
1074
  return;
@@ -945,25 +1092,55 @@ export class BottomInput {
945
1092
  layout() {
946
1093
  const cols = this.cols();
947
1094
  const lines = [];
948
- if (this.statusLabel !== null)
949
- lines.push(this.statusLine());
950
- if (this.compactionStartedAt !== null)
951
- lines.push(fitLine(" Making room to continue…", cols - 1));
952
- if (this.statusLabel === null && this.roomStatus?.state === "failed") {
953
- const notice = this.roomSegment().replace(/\s+/g, " ").trim();
954
- lines.push(...wrapText(notice, Math.max(8, cols - 3)).slice(0, 3).map(line => ` ${line}`));
955
- 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
+ }
956
1130
  }
957
1131
  // La riga live descrive gia' il team; la board completa si consulta a parte.
958
- if (this.kingdomBoard && this.statusLabel === null)
1132
+ if (!worker && this.kingdomBoard && this.statusLabel === null)
959
1133
  lines.push(fitLine(this.kingdomBoard[0] ?? "", cols - 1));
960
1134
  if (!this.modal && !this.queueEditor && !this.questions.focused) {
961
1135
  lines.push(...(this.agentSessions?.preview(cols, Math.min(5, Math.max(2, this.rows() - 12))) ?? []));
962
1136
  }
963
- for (const f of this.h.footerLines())
1137
+ const footerLines = worker ? this.h.sessionFooter?.(worker, cols) ?? [] : this.h.footerLines();
1138
+ for (const f of footerLines)
964
1139
  lines.push(f);
1140
+ if (hasRunningStatus && footerLines.length)
1141
+ lines.push("");
965
1142
  if (!this.modal && !this.queueEditor)
966
- 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));
967
1144
  const questionStart = lines.length;
968
1145
  const questions = !this.modal && !this.queueEditor
969
1146
  ? this.questions.render(cols, Math.max(5, this.rows() - lines.length - 6), this.h.queueStyle)
@@ -998,7 +1175,7 @@ export class BottomInput {
998
1175
  const inputCap = Math.max(1, Math.min(3, Math.floor(this.rows() / 4)));
999
1176
  const rowOffset = Math.max(0, Math.min(crow - Math.floor(inputCap / 2), rows.length - inputCap));
1000
1177
  const visibleRows = rows.slice(rowOffset, rowOffset + inputCap);
1001
- 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?.();
1002
1179
  const blank = " " + (this.h.composerStyle?.(" ".repeat(cell)) ?? " ".repeat(cell));
1003
1180
  if (padding)
1004
1181
  lines.push(blank);
@@ -1017,11 +1194,13 @@ export class BottomInput {
1017
1194
  const notice = ` ${this.roomStatus.label}`;
1018
1195
  lines.push(fitLine(this.h.statusStyle?.(notice) ?? notice, cols - 1));
1019
1196
  }
1197
+ if (this.sessionLimitNotice && this.sessionLimitNotice.model !== this.h.currentModel?.())
1198
+ this.sessionLimitNotice = null;
1020
1199
  if (!this.modal) {
1021
1200
  for (const naming of this.namingNotice.lines()) {
1022
1201
  lines.push(fitLine(" " + (this.h.statusStyle?.(naming) ?? naming), cols - 1));
1023
1202
  }
1024
- for (const row of this.h.belowInputLines?.() ?? [])
1203
+ for (const row of (worker ? this.h.sessionBelowInput?.(worker, cols) ?? [] : this.h.belowInputLines?.() ?? []))
1025
1204
  lines.push(fitLine(row, cols - 1));
1026
1205
  }
1027
1206
  for (const row of this.h.warningLines?.() ?? [])
@@ -1046,43 +1225,37 @@ export class BottomInput {
1046
1225
  rowLengths: visibleRows.map((r) => r.length),
1047
1226
  };
1048
1227
  }
1049
- // Segmento VIVO della stanza Danilov per la status-line: stato animato
1050
- // (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.
1051
1230
  roomSegment() {
1052
1231
  if (!this.roomStatus || this.roomStatus.state === "done")
1053
1232
  return "";
1054
1233
  const style = this.h.statusStyle ?? ((s) => s);
1055
1234
  const { label, state } = this.roomStatus;
1056
- if (state === "running") {
1057
- const g = SPINNER[this.frame % SPINNER.length];
1058
- return `${g} ${label}`;
1059
- }
1235
+ if (state === "running")
1236
+ return label;
1060
1237
  const glyph = state === "failed" ? "✗" : "✦";
1061
1238
  return style(`${glyph} ${label}`);
1062
1239
  }
1063
1240
  statusLine() {
1064
- if (this.compactionStartedAt !== null) {
1065
- const seconds = Math.floor((Date.now() - this.compactionStartedAt) / 1000);
1066
- return fitLine(` ${SPINNER[this.frame % SPINNER.length]} Compacting context (${seconds}s · esc to interrupt)`, this.cols() - 1);
1067
- }
1068
1241
  const elapsed = Math.round((Date.now() - this.startedAt) / 1000);
1069
1242
  const style = this.h.statusStyle ?? ((s) => s);
1070
1243
  const label = this.statusLabel ?? tr("Elaborazione", "Working");
1071
- const glyph = style(SPINNER[this.frame % SPINNER.length]);
1072
- const name = style(label);
1073
1244
  const meta = [this.h.runningHint?.() ?? "", this.roomSegment()].filter(Boolean);
1074
- const shortcut = this.cols() >= 100 && (!this.queue.length || this.modal || this.queueEditor)
1245
+ const shortcut = this.cols() >= 100 && (!this.composerQueue.length || this.modal || this.queueEditor)
1075
1246
  ? tr("Alt+S subito · Esc ferma", "Alt+S send now · Esc stop") : tr("Esc ferma", "Esc stop");
1076
1247
  const right = style(`${fmtElapsed(elapsed).padStart(7)} · ${shortcut}`);
1077
1248
  const budget = Math.max(0, this.cols() - terminalWidth(right) - 5);
1078
- let left = ` ${glyph} ${name}`;
1249
+ let text = label;
1079
1250
  for (const segment of meta) {
1080
- const candidate = left + style(` · ${segment}`);
1081
- if (terminalWidth(candidate) > budget)
1251
+ const candidate = text + ` · ${plainText(segment)}`;
1252
+ if (terminalWidth(candidate) + 2 > budget)
1082
1253
  break;
1083
- left = candidate;
1254
+ text = candidate;
1084
1255
  }
1085
- 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);
1086
1259
  return fitLine(left + " ".repeat(Math.max(2, this.cols() - 2 - terminalWidth(left) - terminalWidth(right))) + right, this.cols() - 1);
1087
1260
  }
1088
1261
  // ── input ─────────────────────────────────────────────────────────────────
@@ -1194,6 +1367,9 @@ export class BottomInput {
1194
1367
  this.scrollOffset = 0;
1195
1368
  this.render();
1196
1369
  }
1370
+ else if (this.workerVisible) {
1371
+ this.stopRun();
1372
+ }
1197
1373
  else if (this.running) {
1198
1374
  this.stopRun();
1199
1375
  }
@@ -1346,16 +1522,21 @@ export class BottomInput {
1346
1522
  return;
1347
1523
  }
1348
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
+ }
1349
1530
  if (k.name === "return" || k.name === "enter") {
1350
1531
  this.agentSessions.key(s, k);
1351
- if (this.agentSessions.activeId !== "main")
1352
- this.screen = this.agentSessions;
1532
+ this.screen = this.agentSessions.activeId !== "main" ? this.agentSessions : null;
1533
+ this.agentSessions.focused = this.agentSessions.activeId !== "main";
1353
1534
  this.render();
1354
1535
  return;
1355
1536
  }
1356
1537
  this.agentSessions.focused = false;
1357
1538
  }
1358
- if (k.meta && k.name === "t" && !this.modal && !this.overlay && !this.queueEditor) {
1539
+ if (!this.workerVisible && k.meta && k.name === "t" && !this.modal && !this.overlay && !this.queueEditor) {
1359
1540
  if (this.queue.sendToTeam())
1360
1541
  this.startNextQueued();
1361
1542
  else
@@ -1373,6 +1554,10 @@ export class BottomInput {
1373
1554
  }
1374
1555
  if (k.ctrl && k.name === "c")
1375
1556
  return this.onCtrlC();
1557
+ if (k.ctrl && k.name === "g" && this.agentSessions?.available) {
1558
+ this.showAgents();
1559
+ return;
1560
+ }
1376
1561
  if (k.ctrl && k.name === "g") {
1377
1562
  // Con un regno attivo: Ctrl+G entra nel viewer e cicla i vassalli (poi esce).
1378
1563
  if (this.vassals && this.vassals.length) {
@@ -1392,7 +1577,15 @@ export class BottomInput {
1392
1577
  return;
1393
1578
  }
1394
1579
  if (k.ctrl && k.name === "b") {
1395
- 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?.();
1396
1589
  return;
1397
1590
  }
1398
1591
  // Ctrl+V (e Alt+V come alias): incolla un'immagine dalla clipboard di sistema
@@ -1409,6 +1602,11 @@ export class BottomInput {
1409
1602
  return;
1410
1603
  }
1411
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
+ }
1412
1610
  if (k.name === "pageup")
1413
1611
  return this.scrollBy(this.pageStep());
1414
1612
  if (k.name === "pagedown")
@@ -1611,6 +1809,20 @@ export class BottomInput {
1611
1809
  this.edit(() => { this.cursor = Math.max(0, this.cursor - tw); });
1612
1810
  return;
1613
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
+ }
1614
1826
  this.historyPrev();
1615
1827
  }
1616
1828
  onDown() {
@@ -1667,7 +1879,8 @@ export class BottomInput {
1667
1879
  this.resolveModal(null);
1668
1880
  return;
1669
1881
  }
1670
- 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";
1671
1884
  if (this.ctrlCConfirmation?.action === action && Date.now() < this.ctrlCConfirmation.until) {
1672
1885
  this.cancelCtrlC();
1673
1886
  if (action === "stop") {
@@ -1683,7 +1896,13 @@ export class BottomInput {
1683
1896
  this.render();
1684
1897
  return;
1685
1898
  }
1686
- this.quit();
1899
+ if (this.workerVisible) {
1900
+ this.agentSessions.returnToMain();
1901
+ this.screen = null;
1902
+ this.render();
1903
+ }
1904
+ else
1905
+ this.quit();
1687
1906
  return;
1688
1907
  }
1689
1908
  this.cancelCtrlC();
@@ -1700,11 +1919,18 @@ export class BottomInput {
1700
1919
  }
1701
1920
  stopRun() {
1702
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
+ }
1703
1929
  const queued = this.queue.length;
1704
1930
  this.queue.paused = true;
1705
1931
  this.runAbort?.abort();
1706
1932
  if (queued)
1707
- 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");
1708
1934
  }
1709
1935
  // Domanda a risposta libera: l'editing finisce nel buffer, Invio risolve.
1710
1936
  queueQuestion(title, options, signal) {
@@ -1764,6 +1990,17 @@ export class BottomInput {
1764
1990
  this.cursor = 0;
1765
1991
  this.histIdx = -1;
1766
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
+ }
1767
2004
  if (immediate) {
1768
2005
  const next = this.queue.items().find(entry => !entry.text.trimStart().startsWith("/"));
1769
2006
  if (next)
@@ -1788,16 +2025,52 @@ export class BottomInput {
1788
2025
  this.showReview(review[1]);
1789
2026
  return;
1790
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
+ }
1791
2060
  this.dispatch(line, immediate);
1792
2061
  }
1793
2062
  dispatch(line, immediate = false) {
2063
+ if (line.trimStart().startsWith("/") && (this.running || this.commandPending)) {
2064
+ this.runLiveCommand(line.trim());
2065
+ return;
2066
+ }
1794
2067
  // Una coda ripristinata in pausa non deve bloccare /new, /resume, /help
1795
2068
  // o /quit. A riposo i comandi controllano la UI senza riprendere la coda.
1796
2069
  if (!this.running && line.trimStart().startsWith("/")) {
1797
2070
  this.runLine(line);
1798
2071
  return;
1799
2072
  }
1800
- if (this.running || this.queue.length) {
2073
+ if (this.running || this.commandPending || this.queue.length) {
1801
2074
  try {
1802
2075
  this.queue.add(line, immediate ? "immediate" : "after-turn");
1803
2076
  // Aprire una chat non avvia lavoro da solo. Un nuovo Invio a riposo
@@ -1820,7 +2093,7 @@ export class BottomInput {
1820
2093
  this.runLine(line);
1821
2094
  }
1822
2095
  startNextQueued() {
1823
- 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)
1824
2097
  return;
1825
2098
  const next = this.queue.takeNext();
1826
2099
  if (next) {
@@ -1828,9 +2101,34 @@ export class BottomInput {
1828
2101
  this.runLine(next.text, true, next);
1829
2102
  }
1830
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
+ }
1831
2129
  runLine(line, fromQueue = false, queued) {
1832
2130
  this.setRoomStatus(null);
1833
- this.history.push(line);
2131
+ (this.composerSession === "main" ? this.history : this.sessionDrafts.get("main")?.history)?.push(line);
1834
2132
  this.running = true;
1835
2133
  this.startedAt = Date.now();
1836
2134
  this.tokensNew = 0;
@@ -1861,6 +2159,7 @@ export class BottomInput {
1861
2159
  this.active = false;
1862
2160
  this.namingNotice.clear();
1863
2161
  this.sessionsUnsubscribe?.();
2162
+ this.messagesUnsubscribe?.();
1864
2163
  if (this.sessionsTimer)
1865
2164
  clearInterval(this.sessionsTimer);
1866
2165
  this.questions.clear();