@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
@@ -1,7 +1,7 @@
1
1
  import { runTurn } from "../core/agent.js";
2
2
  import { Session } from "../core/session.js";
3
- import { createUserInbox } from "../core/user-inbox.js";
4
- import { kingdomLive } from "./kingdom-live.js";
3
+ import { createWorkerInbox } from "./kingdom-live.js";
4
+ import { createWorkerSession } from "./worker-session.js";
5
5
  import { disjointAreas } from "./team-areas.js";
6
6
  import { fileActor, withFileActor } from "../core/file-coordination.js";
7
7
  import { launchGranular, granularConcurrency, formatGranularReport, } from "./granular-launcher.js";
@@ -114,7 +114,7 @@ OBIETTIVO_UNITA'
114
114
  }
115
115
  // Costruisce il TaskRunner che esegue una unita' come sessione di agente.
116
116
  export function makeAgentUnitRunner(opts) {
117
- const { harness, kingdomId, castleSlug, signal, confirm, cwd, isolated } = opts;
117
+ const { harness, kingdomId, castleSlug, confirm, cwd, isolated } = opts;
118
118
  const peers = opts.units.map((u) => u.id);
119
119
  const depth = opts.depth ?? 2;
120
120
  const maxDepth = maxTeamDepth(opts);
@@ -150,6 +150,8 @@ export function makeAgentUnitRunner(opts) {
150
150
  ...(cwd ? { cwd } : {}),
151
151
  };
152
152
  const self = `${castleSlug}/${unit.id}`;
153
+ let controller = new AbortController();
154
+ const signal = controller.signal;
153
155
  const spawnKingdom = depth < maxDepth ? async (specs) => {
154
156
  const units = nestedUnits(specs);
155
157
  if (!units.length)
@@ -188,6 +190,7 @@ export function makeAgentUnitRunner(opts) {
188
190
  };
189
191
  const report = await launchGranularKingdom({
190
192
  ...opts,
193
+ signal: controller.signal,
191
194
  harness: { ...harness, config: runConfig, tools: workerTools },
192
195
  castleSlug: self,
193
196
  units,
@@ -196,6 +199,9 @@ export function makeAgentUnitRunner(opts) {
196
199
  markCastle: undefined,
197
200
  onEvent: (id, event) => {
198
201
  const slug = `${self}/${id}`;
202
+ // Preserve raw child events for the host before the compact parent summary.
203
+ if (event.type !== "kingdom" && event.type !== "usage")
204
+ opts.onEvent?.(`${unit.id}/${id}`, event);
199
205
  if (event.type === "text") {
200
206
  const text = (textBuffers.get(slug) ?? "") + event.delta;
201
207
  textBuffers.set(slug, text);
@@ -216,13 +222,14 @@ export function makeAgentUnitRunner(opts) {
216
222
  } : undefined;
217
223
  // Anche il sotto-collaboratore ha la sua casella: la scheda laterale non
218
224
  // distingue i livelli, e nemmeno chi ci scrive dentro deve farlo.
219
- const inbox = createUserInbox();
220
- kingdomLive.register(kingdomId, self, inbox);
225
+ const binding = createWorkerInbox();
226
+ const inbox = binding.inbox;
221
227
  const deps = {
222
228
  provider: resolved.provider,
223
229
  tools: workerTools,
224
230
  session,
225
231
  config: runConfig,
232
+ env: harness.env,
226
233
  confirm,
227
234
  ask: opts.ask,
228
235
  host: undefined,
@@ -251,22 +258,22 @@ export function makeAgentUnitRunner(opts) {
251
258
  + (unit.assignment?.instructions ? `\nISTRUZIONI_AGGIUNTIVE\n${unit.assignment.instructions}\n` : "")
252
259
  + (ctx.prerequisites?.length ? "\nPREREQUISITI_COMPLETATI (risultati da verificare)\n"
253
260
  + ctx.prerequisites.map((result) => `[${result.id}] ${result.summary}`).join("\n").slice(0, 6000) + "\n" : "") + unit.goal;
261
+ const worker = createWorkerSession({ kingdomId, slug: self, sessionId: session.id, goal: unit.goal,
262
+ parentSignal: ctx.signal, controller, binding, deps, sink, retain: !isolated ? opts.retainSessions ?? false : false,
263
+ onController: next => { controller = next; } });
254
264
  try {
255
265
  sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
256
266
  slug: self, state: "spawned", goal: unit.goal, at: Date.now() });
257
- await runTurn(deps, input, sink, ctx.signal);
267
+ await runTurn(deps, input, sink, signal);
268
+ if (signal.aborted)
269
+ throw new Error("interrotta");
270
+ if (runError || !completed)
271
+ throw new Error(runError || "unita' terminata senza completamento verificato");
272
+ return { summary: summary.trim() };
258
273
  }
259
274
  finally {
260
- deps.fileCoordination?.hub.clearActor(deps.fileCoordination.actor.id);
261
- kingdomLive.release(kingdomId, self);
262
- sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
263
- slug: self, state: "despawned", at: Date.now() });
275
+ worker.finish();
264
276
  }
265
- if (ctx.signal.aborted)
266
- throw new Error("interrotta");
267
- if (runError || !completed)
268
- throw new Error(runError || "unita' terminata senza completamento verificato");
269
- return { summary: summary.trim() };
270
277
  };
271
278
  }
272
279
  // Endpoint completo: lancia le unita' granulari di un castello in parallelo.
@@ -19,6 +19,7 @@ export declare class KingdomBus {
19
19
  private seq;
20
20
  post(kingdomId: string, from: string, to: string | null, text: string, ts: number): KingdomMessage;
21
21
  inbox(kingdomId: string, me: string, filter?: (message: KingdomMessage) => boolean): KingdomMessage[];
22
+ peekInbox(kingdomId: string, me: string): readonly KingdomMessage[];
22
23
  history(kingdomId: string): readonly KingdomMessage[];
23
24
  clear(kingdomId: string): void;
24
25
  }
@@ -41,6 +41,9 @@ export class KingdomBus {
41
41
  return read.messages;
42
42
  }
43
43
  // Tutti i messaggi del regno (per il riepilogo/log), senza toccare i cursori.
44
+ peekInbox(kingdomId, me) {
45
+ return consumeInbox(this.boards.get(kingdomId) ?? [], me, this.cursors.get(`${kingdomId}::${me}`) ?? { last: 0 }).messages;
46
+ }
44
47
  history(kingdomId) {
45
48
  return this.boards.get(kingdomId) ?? [];
46
49
  }
@@ -8,6 +8,7 @@ export declare class PersistentKingdomChannel {
8
8
  constructor(sessionDir: string, opts?: KingdomChannelOptions);
9
9
  post(kingdomId: string, from: string, to: string | null, text: string, ts: number): KingdomMessage;
10
10
  inbox(kingdomId: string, me: string, filter?: (message: KingdomMessage) => boolean): KingdomMessage[];
11
+ peekInbox(kingdomId: string, me: string): readonly KingdomMessage[];
11
12
  history(kingdomId: string): readonly KingdomMessage[];
12
13
  clear(kingdomId: string): void;
13
14
  }
@@ -15,6 +16,7 @@ export declare function getKingdomChannel(sessionDir: string, opts?: KingdomChan
15
16
  export interface KingdomBusLike {
16
17
  post(kingdomId: string, from: string, to: string | null, text: string, ts: number): KingdomMessage;
17
18
  inbox(kingdomId: string, me: string, filter?: (message: KingdomMessage) => boolean): KingdomMessage[];
19
+ peekInbox(kingdomId: string, me: string): readonly KingdomMessage[];
18
20
  history(kingdomId: string): readonly KingdomMessage[];
19
21
  clear(kingdomId: string): void;
20
22
  }
@@ -127,6 +127,9 @@ export class PersistentKingdomChannel {
127
127
  });
128
128
  }
129
129
  // Tutti i messaggi del regno (per riepilogo/log), senza toccare i cursori.
130
+ peekInbox(kingdomId, me) {
131
+ return consumeInbox(readBoard(busFile(this.sessionDir, kingdomId)), me, readCursors(cursorsFile(this.sessionDir, kingdomId))[me] ?? { last: 0 }).messages;
132
+ }
130
133
  history(kingdomId) {
131
134
  return readBoard(busFile(this.sessionDir, kingdomId));
132
135
  }
@@ -1,9 +1,20 @@
1
- import type { UserInbox } from "../core/user-inbox.js";
2
- export interface LiveVassal {
1
+ import { type PendingUserMessage, type UserInbox } from "../core/user-inbox.js";
2
+ import type { ContentPart } from "../core/types.js";
3
+ export interface LiveVassalOptions {
4
+ readonly contextSource?: Pick<import("../core/agent.js").AgentDeps, "config" | "env" | "session" | "tools">;
5
+ readonly stop?: () => void;
6
+ readonly wake?: () => void;
7
+ readonly bindInbox?: (inbox: UserInbox) => void;
8
+ readonly close?: () => void;
9
+ }
10
+ export interface LiveVassal extends LiveVassalOptions {
3
11
  readonly kingdomId: string;
4
12
  readonly slug: string;
5
13
  readonly inbox: UserInbox;
6
14
  readonly since: number;
15
+ readonly stop?: () => void;
16
+ stopping?: boolean;
17
+ active?: boolean;
7
18
  }
8
19
  export interface DeliveryOutcome {
9
20
  readonly ok: boolean;
@@ -12,9 +23,14 @@ export interface DeliveryOutcome {
12
23
  }
13
24
  export declare class KingdomLive {
14
25
  private readonly vassals;
15
- register(kingdomId: string, slug: string, inbox: UserInbox, now?: number): void;
26
+ register(kingdomId: string, slug: string, inbox: UserInbox, now?: number, options?: LiveVassalOptions): void;
16
27
  release(kingdomId: string, slug: string): void;
17
28
  has(kingdomId: string, slug: string): boolean;
29
+ reachable(kingdomId: string, slug: string): boolean;
30
+ contextSource(kingdomId: string, slug: string): LiveVassalOptions["contextSource"];
31
+ setActive(kingdomId: string, slug: string, active: boolean): void;
32
+ bindInbox(kingdomId: string, slug: string, inbox: UserInbox): DeliveryOutcome;
33
+ wake(kingdomId: string, slug: string): DeliveryOutcome;
18
34
  /** Slug dei collaboratori che stanno lavorando adesso in questo regno. */
19
35
  slugs(kingdomId: string): readonly string[];
20
36
  /**
@@ -22,7 +38,19 @@ export declare class KingdomLive {
22
38
  * letto: ritorna l'id, cosi' l'host puo' dire "in attesa" e poi "letto"
23
39
  * quando arriva l'evento `user_message` di quel vassallo.
24
40
  */
25
- deliver(kingdomId: string, slug: string, text: string, messageId?: string): DeliveryOutcome;
41
+ deliver(kingdomId: string, slug: string, text: string, messageId?: string, parts?: readonly ContentPart[], immediate?: boolean): DeliveryOutcome;
42
+ /** Accoda la compattazione reale; runTurn la esegue al prossimo confine sicuro. */
43
+ compact(kingdomId: string, slug: string, instructions?: string): DeliveryOutcome;
44
+ /** Richiede l'arresto del solo worker. Il finally del runtime rilascia il registro. */
45
+ stop(kingdomId: string, slug: string): DeliveryOutcome;
26
46
  clear(kingdomId: string): void;
47
+ clearAll(): void;
27
48
  }
28
49
  export declare const kingdomLive: KingdomLive;
50
+ /** Casella stabile del loop, anche quando l'host collega la sua coda interattiva. */
51
+ export declare function createWorkerInbox(): {
52
+ inbox: UserInbox;
53
+ bind: (inbox: UserInbox) => void;
54
+ take: () => PendingUserMessage | undefined;
55
+ dispose: () => void;
56
+ };
@@ -8,54 +8,190 @@
8
8
  // chat principale), che l'agente svuota da solo al primo confine sicuro, subito
9
9
  // dopo gli esiti dei tool.
10
10
  //
11
- // Registro in-memory di processo, chiave `${kingdomId}::${slug}`. Vive quanto il
12
- // turno del vassallo: chi lo registra lo rilascia in un finally, altrimenti la
13
- // UI crederebbe di poter scrivere a un agente che non c'e' piu'.
11
+ // Registro in-memory di processo, chiave `${kingdomId}::${slug}`. Il runtime
12
+ // rilascia i worker effimeri al finally; gli host possono conservare sessioni
13
+ // idle con un vero controller di ripresa, fino a clear o abort del padre.
14
+ import { createUserInbox } from "../core/user-inbox.js";
14
15
  const key = (kingdomId, slug) => `${kingdomId}::${slug}`;
15
16
  export class KingdomLive {
16
17
  vassals = new Map();
17
- register(kingdomId, slug, inbox, now = Date.now()) {
18
+ register(kingdomId, slug, inbox, now = Date.now(), options = {}) {
18
19
  const id = String(kingdomId || "").trim();
19
20
  const who = String(slug || "").trim();
20
21
  if (!id || !who)
21
22
  return;
22
- this.vassals.set(key(id, who), { kingdomId: id, slug: who, inbox, since: now });
23
+ this.release(id, who);
24
+ this.vassals.set(key(id, who), { kingdomId: id, slug: who, inbox, since: now, active: true, ...options });
23
25
  }
24
26
  release(kingdomId, slug) {
25
- this.vassals.delete(key(String(kingdomId || "").trim(), String(slug || "").trim()));
27
+ const id = key(String(kingdomId || "").trim(), String(slug || "").trim());
28
+ const live = this.vassals.get(id);
29
+ this.vassals.delete(id);
30
+ live?.close?.();
26
31
  }
27
32
  has(kingdomId, slug) {
33
+ return Boolean(this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()))?.active);
34
+ }
35
+ reachable(kingdomId, slug) {
28
36
  return this.vassals.has(key(String(kingdomId || "").trim(), String(slug || "").trim()));
29
37
  }
38
+ contextSource(kingdomId, slug) {
39
+ return this.vassals.get(key(kingdomId, slug))?.contextSource;
40
+ }
41
+ setActive(kingdomId, slug, active) {
42
+ const live = this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()));
43
+ if (live) {
44
+ live.active = active;
45
+ live.stopping = false;
46
+ }
47
+ }
48
+ bindInbox(kingdomId, slug, inbox) {
49
+ const live = this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()));
50
+ if (!live)
51
+ return { ok: false, error: "collaboratore non piu' attivo" };
52
+ if (!live.bindInbox)
53
+ return { ok: false, error: "coda esterna non disponibile" };
54
+ try {
55
+ live.bindInbox(inbox);
56
+ return { ok: true };
57
+ }
58
+ catch (err) {
59
+ return { ok: false, error: err.message };
60
+ }
61
+ }
62
+ wake(kingdomId, slug) {
63
+ const live = this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()));
64
+ if (!live || live.stopping)
65
+ return { ok: false, error: "collaboratore non piu' attivo" };
66
+ try {
67
+ live.wake?.();
68
+ return { ok: true };
69
+ }
70
+ catch (err) {
71
+ return { ok: false, error: err.message };
72
+ }
73
+ }
30
74
  /** Slug dei collaboratori che stanno lavorando adesso in questo regno. */
31
75
  slugs(kingdomId) {
32
76
  const id = String(kingdomId || "").trim();
33
- return [...this.vassals.values()].filter((v) => v.kingdomId === id).map((v) => v.slug);
77
+ return [...this.vassals.values()].filter((v) => v.kingdomId === id && v.active).map((v) => v.slug);
34
78
  }
35
79
  /**
36
80
  * Consegna un messaggio dell'utente a un collaboratore. Non attende che sia
37
81
  * letto: ritorna l'id, cosi' l'host puo' dire "in attesa" e poi "letto"
38
82
  * quando arriva l'evento `user_message` di quel vassallo.
39
83
  */
40
- deliver(kingdomId, slug, text, messageId) {
84
+ deliver(kingdomId, slug, text, messageId, parts, immediate = false) {
41
85
  const body = String(text ?? "").trim();
42
- if (!body)
86
+ const content = parts ?? body;
87
+ if (typeof content === "string" ? !content : !content.some((part) => part.type !== "text" || part.text.trim())) {
43
88
  return { ok: false, error: "messaggio vuoto" };
89
+ }
44
90
  const live = this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()));
45
- if (!live)
91
+ if (!live || live.stopping)
92
+ return { ok: false, error: "collaboratore non piu' attivo" };
93
+ try {
94
+ const id = live.inbox.push(content, messageId);
95
+ if (immediate)
96
+ live.inbox.promote(id);
97
+ live.wake?.();
98
+ return { ok: true, messageId: id };
99
+ }
100
+ catch (err) {
101
+ return { ok: false, error: err.message };
102
+ }
103
+ }
104
+ /** Accoda la compattazione reale; runTurn la esegue al prossimo confine sicuro. */
105
+ compact(kingdomId, slug, instructions) {
106
+ const live = this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()));
107
+ if (!live || live.stopping)
46
108
  return { ok: false, error: "collaboratore non piu' attivo" };
109
+ if (!live.inbox.requestCompact)
110
+ return { ok: false, error: "compattazione non disponibile per questo collaboratore" };
47
111
  try {
48
- return { ok: true, messageId: live.inbox.push(body, messageId) };
112
+ live.inbox.requestCompact(instructions);
113
+ live.wake?.();
114
+ return { ok: true };
49
115
  }
50
116
  catch (err) {
51
117
  return { ok: false, error: err.message };
52
118
  }
53
119
  }
120
+ /** Richiede l'arresto del solo worker. Il finally del runtime rilascia il registro. */
121
+ stop(kingdomId, slug) {
122
+ const live = this.vassals.get(key(String(kingdomId || "").trim(), String(slug || "").trim()));
123
+ if (!live || live.stopping)
124
+ return { ok: false, error: "collaboratore non piu' attivo" };
125
+ if (!live.active)
126
+ return { ok: false, error: "collaboratore gia' inattivo" };
127
+ if (!live.stop)
128
+ return { ok: false, error: "arresto non disponibile per questo collaboratore" };
129
+ live.stopping = true;
130
+ try {
131
+ live.stop();
132
+ return { ok: true };
133
+ }
134
+ catch (err) {
135
+ live.stopping = false;
136
+ return { ok: false, error: err.message };
137
+ }
138
+ }
54
139
  clear(kingdomId) {
55
140
  const id = String(kingdomId || "").trim();
56
- for (const [k, v] of [...this.vassals])
141
+ for (const v of [...this.vassals.values()])
57
142
  if (v.kingdomId === id)
58
- this.vassals.delete(k);
143
+ this.release(v.kingdomId, v.slug);
144
+ }
145
+ clearAll() {
146
+ for (const v of [...this.vassals.values()])
147
+ this.release(v.kingdomId, v.slug);
59
148
  }
60
149
  }
61
150
  export const kingdomLive = new KingdomLive();
151
+ /** Casella stabile del loop, anche quando l'host collega la sua coda interattiva. */
152
+ export function createWorkerInbox() {
153
+ let taken = [];
154
+ let current = createUserInbox();
155
+ const controls = createUserInbox();
156
+ const subscriptions = new Map();
157
+ const inbox = {
158
+ push: (input, id) => current.push(input, id),
159
+ promote: id => current.promote(id),
160
+ get pending() { return taken.length + current.pending; },
161
+ get queued() { return taken.length + (current.queued ?? current.pending); },
162
+ drain: () => { const pending = [...taken, ...current.drain()]; taken = []; return pending; },
163
+ onImmediate(listener) {
164
+ subscriptions.set(listener, current.onImmediate?.(listener) ?? (() => { }));
165
+ return () => { subscriptions.get(listener)?.(); subscriptions.delete(listener); };
166
+ },
167
+ requestCompact: instructions => controls.requestCompact(instructions),
168
+ drainCompactions: () => controls.drainCompactions(),
169
+ get pendingCompactions() { return controls.pendingCompactions; },
170
+ };
171
+ return { inbox,
172
+ take() { taken.push(...current.drain()); return taken.shift(); },
173
+ bind(next) {
174
+ if (next === current || next === inbox)
175
+ return;
176
+ const pending = current.drain();
177
+ for (const off of subscriptions.values())
178
+ off();
179
+ current = next;
180
+ for (const message of pending) {
181
+ current.push(message.parts, message.id);
182
+ if (message.immediate)
183
+ current.promote(message.id);
184
+ }
185
+ for (const listener of subscriptions.keys())
186
+ subscriptions.set(listener, current.onImmediate?.(listener) ?? (() => { }));
187
+ },
188
+ dispose() {
189
+ for (const off of subscriptions.values())
190
+ off();
191
+ subscriptions.clear();
192
+ taken = [];
193
+ current = createUserInbox();
194
+ controls.drainCompactions?.();
195
+ },
196
+ };
197
+ }
@@ -1,8 +1,12 @@
1
- import type { EventSink, ToolContext } from "../core/types.js";
1
+ import type { AgentEvent, EventSink, ToolContext } from "../core/types.js";
2
2
  import type { Harness } from "../core/bootstrap.js";
3
3
  import { type CastleSpec, type VassalReport } from "./kingdom.js";
4
4
  import type { FileCoordinationContext } from "../core/file-coordination.js";
5
5
  export interface KingdomRunInput {
6
+ readonly kingdomId?: string;
7
+ readonly resumeSessions?: Readonly<Record<string, string>>;
8
+ readonly retainSessions?: boolean | "queued";
9
+ readonly onWorkerEvent?: (slug: string, event: AgentEvent) => void;
6
10
  readonly harness: Harness;
7
11
  readonly castles: readonly CastleSpec[];
8
12
  readonly signal: AbortSignal;
@@ -35,7 +35,7 @@ export async function runKingdomForHost(input) {
35
35
  // La sessione dura quanto la conversazione, uno swarm no. Un id riusato
36
36
  // faceva fondere nella UI il regno concluso con quello successivo.
37
37
  kingdomRunSequence = (kingdomRunSequence + 1) % 0xfffff;
38
- const kingdomId = `kw-${harness.session.id}-${Date.now().toString(36)}-${kingdomRunSequence.toString(36)}`;
38
+ const kingdomId = input.kingdomId ?? `kw-${harness.session.id}-${Date.now().toString(36)}-${kingdomRunSequence.toString(36)}`;
39
39
  const concurrency = warmConcurrency(env);
40
40
  const worktree = worktreeMode(env);
41
41
  // Bus pulito per il nuovo regno (in memoria e sul canale persistente).
@@ -89,6 +89,7 @@ export async function runKingdomForHost(input) {
89
89
  sink({ type: "kingdom", phase: "say", slug, text: buffered.slice(0, SAY_MAX), at: Date.now() });
90
90
  };
91
91
  const onEvent = (slug, event) => {
92
+ input.onWorkerEvent?.(slug, event);
92
93
  if (event.type === "usage") {
93
94
  sink({ ...event, record: { ...event.record, actor: event.record.actor ?? slug } });
94
95
  return;
@@ -312,6 +313,8 @@ export async function runKingdomForHost(input) {
312
313
  signal,
313
314
  confirm,
314
315
  ask: input.ask,
316
+ retainSessions: input.retainSessions,
317
+ resumeSessions: input.resumeSessions,
315
318
  concurrency,
316
319
  onEvent,
317
320
  onMemberState: (slug, status, report) => {
@@ -364,7 +367,8 @@ export async function runKingdomForHost(input) {
364
367
  // Ultime parole rimaste nel buffer di chi ha finito senza un confine di turno.
365
368
  for (const slug of [...saying.keys()])
366
369
  flushSay(slug);
367
- kingdomLive.clear(kingdomId);
370
+ if (!input.retainSessions)
371
+ kingdomLive.clear(kingdomId);
368
372
  for (const report of reports) {
369
373
  updateTeamMember(kingdomId, report.slug, { status: report.ok ? "completed" : "failed", summary: report.summary, error: report.error, artifacts: report.artifacts });
370
374
  sink({
@@ -45,6 +45,9 @@ export interface ConflictEntry {
45
45
  export declare function collectConflicts(reports: readonly VassalReport[]): ConflictEntry[];
46
46
  export declare function formatConflictList(conflicts: readonly ConflictEntry[], base?: string): string;
47
47
  export interface KingdomWarmOptions {
48
+ /** Explicit persisted worker sessions for hosts that resume a named worker. */
49
+ readonly resumeSessions?: Readonly<Record<string, string>>;
50
+ readonly retainSessions?: boolean | "queued";
48
51
  readonly harness: Harness;
49
52
  readonly kingdomId: string;
50
53
  readonly castles: readonly CastleSpec[];
@@ -4,8 +4,8 @@ import { existsSync } from "node:fs";
4
4
  import { resolve, sep } from "node:path";
5
5
  import { runTurn } from "../core/agent.js";
6
6
  import { Session } from "../core/session.js";
7
- import { createUserInbox } from "../core/user-inbox.js";
8
- import { kingdomLive } from "./kingdom-live.js";
7
+ import { createWorkerInbox } from "./kingdom-live.js";
8
+ import { createWorkerSession } from "./worker-session.js";
9
9
  import { fileActor, withFileActor } from "../core/file-coordination.js";
10
10
  import { RoomMemory } from "./room-memory.js";
11
11
  import { VascendPlanStore } from "./plan-store.js";
@@ -661,6 +661,7 @@ function subTeamSpawner(opts, castle, mandate, iso, planStoreId) {
661
661
  const report = await launchGranularKingdom({
662
662
  harness: opts.harness,
663
663
  kingdomId: opts.kingdomId,
664
+ retainSessions: opts.retainSessions,
664
665
  castleSlug: castle.slug,
665
666
  units,
666
667
  signal: opts.signal,
@@ -691,10 +692,14 @@ function subTeamSpawner(opts, castle, mandate, iso, planStoreId) {
691
692
  };
692
693
  }
693
694
  async function runVassal(opts, provider, modelId, displayModel, castle, peers, iso, prerequisites = []) {
694
- const { harness, kingdomId, signal, confirm, onEvent } = opts;
695
+ const { harness, kingdomId, signal: parentSignal, confirm, onEvent } = opts;
696
+ let controller = new AbortController();
697
+ const signal = controller.signal;
695
698
  // Sessione (conversazione) dedicata al vassallo; store memoria CONDIVISO del
696
699
  // regno (kingdomId) con id stanze namespaced sullo slug del castello.
697
- const session = Session.create(harness.config.sessionDir, `${kingdomId}-${castle.slug}`);
700
+ const resumeId = opts.resumeSessions?.[castle.slug];
701
+ const session = resumeId ? Session.load(harness.config.sessionDir, resumeId)
702
+ : Session.create(harness.config.sessionDir, `${kingdomId}-${castle.slug}`);
698
703
  const effort = ["low", "medium", "high", "max"].includes(String(castle.effort ?? "").toLowerCase())
699
704
  ? String(castle.effort).toLowerCase()
700
705
  : "";
@@ -719,8 +724,8 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
719
724
  // Casella del vassallo: l'utente puo' scrivergli mentre lavora dalla sua
720
725
  // scheda, e il messaggio entra nel contesto al primo confine sicuro come in
721
726
  // chat principale. Senza, l'unico modo di correggerlo era fermare il regno.
722
- const inbox = createUserInbox();
723
- kingdomLive.register(kingdomId, castle.slug, inbox);
727
+ const binding = createWorkerInbox();
728
+ const inbox = binding.inbox;
724
729
  const workerTools = harness.tools.scoped(castle.tools);
725
730
  for (const name of ["team_say", "team_inbox", "team_wait", "team_report", "vascend_status"])
726
731
  workerTools.promote(name);
@@ -729,6 +734,9 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
729
734
  tools: workerTools,
730
735
  session,
731
736
  config: runConfig,
737
+ env: harness.env,
738
+ fsRoots: harness.fsRoots,
739
+ executionTarget: harness.executionTarget,
732
740
  confirm,
733
741
  ask: opts.ask,
734
742
  host: undefined,
@@ -739,7 +747,8 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
739
747
  // Delega di secondo livello: un castello grande si gestisce formando un team
740
748
  // sulle proprie stanze. Prima questo campo mancava, e kingdoms_warm dentro
741
749
  // un vassallo rispondeva "non disponibile": la gerarchia era ferma a 1.
742
- spawnKingdom: subTeamSpawner({ ...opts, harness: { ...harness, config: runConfig, tools: workerTools } }, castle, mandate, iso, planStoreId),
750
+ spawnKingdom: (specs) => subTeamSpawner({ ...opts, signal: controller.signal,
751
+ harness: { ...harness, config: runConfig, tools: workerTools } }, castle, mandate, iso, planStoreId)(specs),
743
752
  // isolated abilita il git nel vassallo (worktree proprio); altrimenti il
744
753
  // bash tool blocca il git che muta (working dir condivisa).
745
754
  kingdom: { id: kingdomId, self: castle.slug, isolated: iso.isolated, peers },
@@ -772,6 +781,9 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
772
781
  completed = true;
773
782
  onEvent?.(castle.slug, event);
774
783
  };
784
+ const worker = createWorkerSession({ kingdomId, slug: castle.slug, sessionId: session.id, goal: castle.goal,
785
+ parentSignal, controller, binding, deps, sink, retain: !iso.isolated ? opts.retainSessions ?? false : false,
786
+ onController: next => { controller = next; } });
775
787
  try {
776
788
  const role = castle.role?.trim() || "collaboratore";
777
789
  sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
@@ -827,12 +839,7 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
827
839
  };
828
840
  }
829
841
  finally {
830
- deps.fileCoordination?.hub.clearActor(deps.fileCoordination.actor.id);
831
- // Il collaboratore non c'e' piu': la UI deve saperlo subito, altrimenti
832
- // continuerebbe a offrire un campo di scrittura che non consegna nulla.
833
- kingdomLive.release(kingdomId, castle.slug);
834
- sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
835
- slug: castle.slug, state: "despawned", at: Date.now() });
842
+ worker.finish();
836
843
  }
837
844
  }
838
845
  function mandateCompletion(sessionDir, planStoreId, mandate) {
@@ -1,3 +1,4 @@
1
+ import { ContextStoreReader } from "./context-store-reader.js";
1
2
  export interface MemoryPacketInput {
2
3
  readonly sessionDir: string;
3
4
  readonly scopeId: string;
@@ -9,5 +10,5 @@ export interface MemoryPacketInput {
9
10
  /** Preselected dependencies supplied by a coherent snapshot. */
10
11
  readonly projectScopes?: readonly string[];
11
12
  }
12
- export declare function buildMemoryPacket(input: MemoryPacketInput): string;
13
+ export declare function buildMemoryPacket(input: MemoryPacketInput, reader?: ContextStoreReader): string;
13
14
  export declare function clampMemoryPacket(value: string, max: number): string;