@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,4 +1,4 @@
1
- import type { EventSink, Message, Provider, ToolContext, ContentPart, ToolCallPart, SpawnAgentSpec } from "./types.js";
1
+ import type { AgentEvent, EventSink, Message, Provider, ToolContext, ContentPart, ToolCallPart, SpawnAgentSpec } from "./types.js";
2
2
  import type { ToolRegistry } from "../tools/registry.js";
3
3
  import type { Session } from "./session.js";
4
4
  import type { PiConfig } from "./config.js";
@@ -7,13 +7,26 @@ import { type UserInbox } from "./user-inbox.js";
7
7
  import { type ContextFabricResult } from "../vascend/context-fabric.js";
8
8
  import { type FileCoordinationContext } from "./file-coordination.js";
9
9
  import type { ExecutionTarget } from "./execution-target.js";
10
+ import type { StepPolicy } from "./step-policy.js";
10
11
  import { type WorkspaceSession } from "./workspace-sessions.js";
11
12
  export interface AgentDeps {
13
+ readonly fusionHarness?: import("./bootstrap.js").Harness;
14
+ /** Politica per passo (Fusion): guida effimera e tool ammessi nel passo. */
15
+ readonly stepPolicy?: StepPolicy;
16
+ /** Trasporto Jev dell'host per il canale client (sessione Vascend dell'host). */
17
+ readonly jevProxy?: import("../fusion/jev.js").JevProxy;
18
+ /** Full worker events for hosts with per-agent transcripts. */
19
+ readonly onWorkerEvent?: (actor: string, event: AgentEvent) => void;
12
20
  readonly provider: Provider;
13
21
  readonly tools: ToolRegistry;
14
22
  readonly session: Session;
15
23
  readonly config: PiConfig;
16
24
  readonly env?: NodeJS.ProcessEnv;
25
+ /** Correlazione di un follow-up gia' prelevato dall'inbox dall'host. */
26
+ readonly inputMessage?: {
27
+ readonly id: string;
28
+ readonly immediate?: boolean;
29
+ };
17
30
  /** Allowlist filesystem aggiuntiva, isolata al singolo runTurn. */
18
31
  readonly fsRoots?: readonly string[];
19
32
  readonly executionTarget?: ExecutionTarget;
@@ -25,8 +38,8 @@ export interface AgentDeps {
25
38
  * Viene marcata come non attendibile e non entra mai nella sessione.
26
39
  */
27
40
  readonly ephemeralContext?: string;
28
- readonly confirm: (action: string, detail: string) => Promise<boolean>;
29
- readonly ask?: (question: string, options?: readonly string[]) => Promise<string | null>;
41
+ readonly confirm: (action: string, detail: string, signal?: AbortSignal) => Promise<boolean>;
42
+ readonly ask?: (question: string, options?: readonly string[], signal?: AbortSignal) => Promise<string | null>;
30
43
  readonly spawnKingdom?: (castles: readonly SpawnAgentSpec[], opts?: {
31
44
  baseBranch?: string;
32
45
  detach?: boolean;
@@ -3,10 +3,11 @@ import { trackSessionUsage } from "./session-usage.js";
3
3
  import { nameSession } from "./session-naming.js";
4
4
  import { quotaResetAt } from "../providers/quota-limits.js";
5
5
  import { RequestBudget } from "./request-plan.js";
6
- import { projectToolResult, visibleOutputPages } from "./tool-artifacts.js";
6
+ import { maxInputFor, resolveMaxTokens } from "./model-limits.js";
7
+ import { projectToolResult, visibleOutputPages, boundHistoricalToolResults } from "./tool-artifacts.js";
7
8
  import { contextWindowFor, estimateMessagesTokens, estimateTokens, estimateToolDefinitionTokens, IMAGE_TOKEN_ESTIMATE, planCompaction, planCompactionMove, protectedExcerpts, repairInstructions, summarizeMessages, verifySummary, withProtectedExcerpts, } from "./compaction.js";
8
9
  import { RoomMemory, formatRoomRecord } from "../vascend/room-memory.js";
9
- import {} from "./context-ledger.js";
10
+ import { buildContextLedger } from "./context-ledger.js";
10
11
  import { applyMediaEviction, applyToolResultDemotion, mediaBudgetPolicy, planMediaEviction, planToolResultDemotion, slimMessageForSession, toolIndexPolicy, EMPTY_PLAN, } from "./tool-result-index.js";
11
12
  import { createToolExecutor } from "./tool-executor.js";
12
13
  import { ToolOutputStore } from "./tool-result-store.js";
@@ -26,6 +27,7 @@ import { DecisionStore, promoteDecisions, decisionToRoomRecord } from "../vascen
26
27
  import { ContractStore, promoteContractChanges, contractToRoomRecord } from "../vascend/contract-memory.js";
27
28
  import { VascendPlanStore } from "../vascend/plan-store.js";
28
29
  import { pendingMembers, teamForSession, waitForTeamChange } from "../vascend/team-session.js";
30
+ import { resolveKingdomBus } from "../vascend/kingdom-channel.js";
29
31
  import { getKingdomScribe } from "../vascend/kingdom-scribe.js";
30
32
  import { toolCompressOptions } from "./tool-compress.js";
31
33
  import { routeEffort, isRoutineBatch, effectiveToolName, effortRoutingEnabled } from "./effort.js";
@@ -39,14 +41,6 @@ import { RunTiming } from "./run-timing.js";
39
41
  import { queuedConfirmation } from "./permission-queue.js";
40
42
  import { workspaceMessageText, workspacePeersContext } from "./workspace-sessions.js";
41
43
  const CONTEXT_READ_TOOLS = new Set(["read_file", "glob", "grep", "list_dir", "recall", "output_page", "vascend_status", "ram_status", "file_activity", "workspace_sessions", "tool_search", "tool_list", "tool_describe", "bash_output"]);
42
- // Tetto di sicurezza ai turni di un singolo giro (anti-loop). Alto di default
43
- // (i goal Danilov multi-castello richiedono molti turni) e configurabile via
44
- // TALOS_MAX_TURNS. Al raggiungimento NON e' un errore fatale: il giro termina e
45
- // la validazione del regno sveglia l'agente sulle stanze ancora al buio.
46
- function maxTurns(env) {
47
- const n = Number(env["TALOS_MAX_TURNS"]);
48
- return Number.isFinite(n) && n > 0 ? n : 200;
49
- }
50
44
  // Quante volte auto-continuare un output troncato (stop=max_tokens) prima di
51
45
  // arrendersi e chiedere di spezzare il compito. Configurabile via
52
46
  // TALOS_MAX_AUTOCONTINUE. 0 = disabilita l'auto-continuazione.
@@ -153,9 +147,13 @@ function coerceParam(raw) {
153
147
  return raw.replace(/^\n/, "").replace(/\n$/, "");
154
148
  }
155
149
  // Esegue un giro completo: dato l'input utente, alterna chiamate al modello
156
- // ed esecuzione di tool finche' il modello non si ferma (o si raggiunge il
157
- // limite di turni / l'utente interrompe via signal).
150
+ // ed esecuzione di tool finche' il modello non si ferma, si verifica un errore
151
+ // o l'utente interrompe via signal. Nessun tetto al numero di turni.
158
152
  export async function runTurn(deps, userInput, sink, signal) {
153
+ if (deps.config.fusion && !deps.kingdom) {
154
+ const { runFusionTurn } = await import("../fusion/run.js");
155
+ return runFusionTurn(deps, userInput, sink, signal, runTurn);
156
+ }
159
157
  // Un collaboratore costruito copiando AgentDeps non assume l'identita'
160
158
  // della sessione principale: il team ha gia' il proprio canale dedicato.
161
159
  const channel = deps.workspaceSession?.owns(deps.config.cwd, deps.session.id, deps.executionTarget)
@@ -166,13 +164,23 @@ export async function runTurn(deps, userInput, sink, signal) {
166
164
  const timing = new RunTiming(deps.config.sessionDir, deps.session.id, deps.env ?? process.env);
167
165
  const releaseStorage = protectSessionStorage(deps.config.sessionDir, [deps.session.id, deps.config.roomStoreId, deps.config.planStoreId].filter((value) => Boolean(value)), deps.env ?? process.env, deps.config.cwd);
168
166
  let failed = false;
167
+ const namingController = new AbortController();
168
+ const abortNaming = () => namingController.abort();
169
+ signal.addEventListener("abort", abortNaming, { once: true });
170
+ if (signal.aborted)
171
+ abortNaming();
169
172
  const naming = (deps.env ?? process.env)["TALOS_AUTO_TITLE"] === "1" && !deps.kingdom
170
- ? nameSession(deps, text, sink, signal) : Promise.resolve();
173
+ ? nameSession(deps, text, sink, namingController.signal) : Promise.resolve();
171
174
  try {
172
175
  await runAgentLoop({ ...deps, workspaceSession: channel,
173
176
  provider: trackSessionUsage(timing.provider(deps.provider), deps.config.sessionDir, deps.session.id, sink),
174
177
  confirm: (action, detail) => timing.wait("permission", () => queuedConfirmation(deps.confirm, action, detail, signal)),
175
- }, userInput, event => { timing.event(event); sink(event); }, signal);
178
+ }, userInput, event => {
179
+ if (event.type === "status" && event.label.startsWith("Limite sessione di 5 ore raggiunto"))
180
+ abortNaming();
181
+ timing.event(event);
182
+ sink(event);
183
+ }, signal);
176
184
  }
177
185
  catch (error) {
178
186
  failed = true;
@@ -180,6 +188,7 @@ export async function runTurn(deps, userInput, sink, signal) {
180
188
  }
181
189
  finally {
182
190
  await naming;
191
+ signal.removeEventListener("abort", abortNaming);
183
192
  timing.finish(signal.aborted, failed);
184
193
  channel?.update({ state: "idle" });
185
194
  releaseStorage();
@@ -337,6 +346,10 @@ userInput, sink, signal) {
337
346
  systemBlocks.splice(stableBlockCount);
338
347
  turnBlocks.length = 0;
339
348
  turnBlockRecords.length = 0;
349
+ if (config.turnSystem) {
350
+ turnBlocks.push(config.turnSystem);
351
+ turnBlockRecords.push({ key: "host-instructions", label: "Istruzioni operative dell'host", text: config.turnSystem, cacheable: false });
352
+ }
340
353
  if (persistenceReminder)
341
354
  addTurnBlock(persistenceReminder, { key: "nudge", label: "Promemoria di persistenza" });
342
355
  // Il contesto dell'host non e' parte del system prompt ne' della cronologia:
@@ -359,10 +372,19 @@ userInput, sink, signal) {
359
372
  userText: latestUserText,
360
373
  inheritProjectFrame: sharedProjectMemory && sessionWasEmpty,
361
374
  });
362
- addTurnBlock(horizonHost ? renderHorizonFabricBlock(fabric) : fabric.systemBlock, {
363
- key: "fabric",
364
- label: "Frame di progetto (Context Fabric)",
365
- });
375
+ const inheritedFrame = fabric.frame.receipt?.inherited === true;
376
+ // Su una sessione vuota il frame appena creato deriva interamente dal
377
+ // messaggio corrente: reinviarlo come Fabric ripeterebbe lo stesso prompt
378
+ // in missione, contratto e revisioni. Il frame viene comunque persistito
379
+ // e sara' disponibile dal follow-up successivo.
380
+ const fabricBlock = horizonHost
381
+ ? renderHorizonFabricBlock(fabric)
382
+ : sessionWasEmpty && !inheritedFrame ? "" : fabric.systemBlock;
383
+ if (fabricBlock)
384
+ addTurnBlock(fabricBlock, {
385
+ key: "fabric",
386
+ label: "Frame di progetto (Context Fabric)",
387
+ });
366
388
  }
367
389
  catch {
368
390
  // Context Fabric e' un acceleratore, non un single point of failure: uno
@@ -469,12 +491,12 @@ userInput, sink, signal) {
469
491
  // dal turno) e fonde la config migliore nella baseline del progetto. Effetto
470
492
  // collaterale utile: lascia lo snapshot degli store caldo per il turno dopo.
471
493
  let memoryPacket = "";
472
- if (!contextBlock || horizonHost) {
494
+ if (!contextBlock || horizonHost || sessionWasEmpty) {
473
495
  memoryPacket = compileInput.snapshot.packet(12_000);
474
496
  }
475
497
  if (!contextBlock)
476
498
  contextBlock = memoryPacket;
477
- if (horizonHost && !shouldIncludeHorizonContextPack({
499
+ if (!shouldIncludeHorizonContextPack({
478
500
  sessionWasEmpty,
479
501
  inheritedFrame: fabric?.frame.receipt?.inherited === true,
480
502
  hasCompiledEvidence: Boolean(compiledContext?.report.cleaned.length),
@@ -519,6 +541,9 @@ userInput, sink, signal) {
519
541
  // Il blocco NON viene persistito nella sessione: la cronologia su disco resta
520
542
  // pulita e il prefisso inviato al modello resta byte-identico fra i turni.
521
543
  const turnMessage = session.append({ role: "user", content: userParts }).message;
544
+ if (deps.inputMessage)
545
+ sink({ type: "user_message", id: deps.inputMessage.id, text: userText,
546
+ at: Date.now(), ...(deps.inputMessage.immediate ? { immediate: true } : {}) });
522
547
  await host?.emit("agent_start", { input: userText });
523
548
  // Prende in carico i messaggi arrivati durante il turno: li appende come
524
549
  // messaggi utente veri (quindi entrano nel contesto del giro successivo) e
@@ -526,6 +551,7 @@ userInput, sink, signal) {
526
551
  // l'interfaccia mostra come "letto". Ritorna true se ne ha consumato almeno
527
552
  // uno, cosi' il chiamante sa che c'e' lavoro nuovo da servire.
528
553
  const receivedPeerMessages = new Set();
554
+ const notifiedTeamMessages = new Set();
529
555
  const consumeInbox = () => {
530
556
  const arrivals = deps.inbox?.drain() ?? [];
531
557
  if (arrivals.length) {
@@ -560,7 +586,6 @@ userInput, sink, signal) {
560
586
  }
561
587
  return arrivals.length > 0 || peerArrivals > 0;
562
588
  };
563
- const turnCap = maxTurns(env);
564
589
  const requestBudget = new RequestBudget();
565
590
  const toolCompress = toolCompressOptions(env);
566
591
  // Archivio dei tool result: gli output voluminosi vengono indicizzati appena
@@ -584,7 +609,7 @@ userInput, sink, signal) {
584
609
  // Effort routing: i turni di routine (digestione di letture) abbassano il
585
610
  // reasoning; il primo turno e quelli con scrittura/errore tengono l'effort pieno.
586
611
  const baseEffort = (config.effort ?? "");
587
- const effortRouting = effortRoutingEnabled(env);
612
+ const effortRouting = effortRoutingEnabled(env, provider.id);
588
613
  let routineBatch = false;
589
614
  // Auto-continuazione su output troncato: se il modello si ferma per
590
615
  // max_tokens (risposta tagliata al tetto), invece di chiudere la stanza con
@@ -595,6 +620,7 @@ userInput, sink, signal) {
595
620
  const completionRepairCap = maxCompletionRepairs(env);
596
621
  let emptyResponseRepairs = 0;
597
622
  let transientGuardContext = "";
623
+ let stepDirective = null;
598
624
  let planCompletionRepairs = 0;
599
625
  let planTouchedThisRun = false;
600
626
  let delegationObserved = false;
@@ -626,7 +652,28 @@ userInput, sink, signal) {
626
652
  ...openContractSymbols(deps),
627
653
  ].filter(Boolean)),
628
654
  ].slice(0, MAX_COMPACTION_ANCHORS);
629
- for (let turn = 0; turn < turnCap; turn += 1) {
655
+ for (let turn = 0;; turn += 1) {
656
+ if (signal.aborted) {
657
+ await archiveCurrentRoom();
658
+ sink({ type: "error", message: "interrotto" });
659
+ await host?.emit("agent_end", {});
660
+ return;
661
+ }
662
+ // I comandi manuali mutano la cronologia solo dopo un batch completo di
663
+ // tool, mai durante provider/tool concorrenti. Non diventano prompt finti.
664
+ const compactions = deps.inbox?.drainCompactions?.() ?? [];
665
+ for (const request of compactions) {
666
+ try {
667
+ const compacted = await compactNow(deps, signal, sink, request.instructions, compactionAnchors());
668
+ contextDirty ||= compacted;
669
+ sink({ type: "status", category: "context", label: compacted
670
+ ? "Contesto compattato; proseguo nella stessa sessione."
671
+ : "Nessuna compattazione necessaria; contesto conservato." });
672
+ }
673
+ catch (err) {
674
+ sink({ type: "status", category: "context", label: `Compattazione non riuscita: ${err.message}; contesto conservato.` });
675
+ }
676
+ }
630
677
  if (signal.aborted) {
631
678
  await archiveCurrentRoom();
632
679
  sink({ type: "error", message: "interrotto" });
@@ -658,7 +705,7 @@ userInput, sink, signal) {
658
705
  // persisted, including nested execute calls and updates from collaborators.
659
706
  if (completed.length && config.compaction.enabled && config.compaction.perRoom
660
707
  && policy.roomCompaction !== "never") {
661
- const limits = workingContextLimits(policy, contextWindowFor(config.model, config.contextWindow), config.compaction.reserveTokens);
708
+ const limits = workingContextLimits(policy, contextWindowFor(config.model, config.contextWindow), config.compaction.reserveTokens, resolveMaxTokens(config.maxTokens, config.model), maxInputFor(config.model));
662
709
  const activeTokens = contextTokens(deps, runtimeSystem) + estimateTokens(turnContext)
663
710
  - planDemotion(session.history()).freedTokens
664
711
  - planMediaEviction({ messages: session.history(), policy: mediaBudget }).images * IMAGE_TOKEN_ESTIMATE;
@@ -691,6 +738,7 @@ userInput, sink, signal) {
691
738
  // successivo la sessione espone soltanto il riferimento testuale. Senza
692
739
  // questa proiezione ogni nuova richiesta reinviava lo stesso base64 (e gli
693
740
  // SVG storici potevano far fallire Anthropic per dimensioni nominali >8k).
741
+ messages = boundHistoricalToolResults(messages, outputStore, toolCompress);
694
742
  messages = projectHistoricalPromptImages(messages, turnMessage);
695
743
  // Budget delle immagini: le schermate vecchie lasciano la finestra. Prima
696
744
  // della retrocessione del testo, perche' e' la voce piu' pesante di tutte
@@ -761,7 +809,10 @@ userInput, sink, signal) {
761
809
  : {}),
762
810
  };
763
811
  let streamedText = "";
764
- const requestTurnContext = [turnContext, scopeGuidance, transientGuardContext, peerContext].filter(Boolean).join("\n\n");
812
+ stepDirective = deps.stepPolicy
813
+ ? await deps.stepPolicy.beforeStep({ step: turn, tools: tools.names(), signal }).catch(() => null) : null;
814
+ const requestTurnContext = [turnContext, scopeGuidance, transientGuardContext, peerContext, stepDirective?.guidance]
815
+ .filter(Boolean).join("\n\n");
765
816
  // I guard interni valgono per una sola richiesta provider e non devono
766
817
  // entrare nella cronologia come messaggi utente.
767
818
  transientGuardContext = "";
@@ -784,6 +835,7 @@ userInput, sink, signal) {
784
835
  onActivity: (label) => sink({ type: "status", label }),
785
836
  onReasoning: (delta) => sink({ type: "reasoning", delta }),
786
837
  }, ledgerInput);
838
+ sessionContextFrames.set(session, { ...ledgerInput, messages: undefined, now: undefined });
787
839
  sink({ type: "context_window", ledger: requestPlan.ledger });
788
840
  if (!requestPlan.fits) {
789
841
  sink({ type: "error", message: "Il contesto supera la finestra del modello anche dopo la compattazione. La sessione resta recuperabile." });
@@ -909,12 +961,21 @@ userInput, sink, signal) {
909
961
  // Ultimo controllo della casella prima di chiudere: se l'utente ha scritto
910
962
  // mentre il modello stava componendo la risposta, il turno prosegue invece
911
963
  // di terminare e costringerlo a un giro nuovo.
912
- if (consumeInbox())
964
+ if (consumeInbox() || deps.inbox?.pendingCompactions)
913
965
  continue;
914
966
  const activeTeam = delegationObserved ? teamForSession(session.id) : null;
915
967
  if (activeTeam && !activeTeam.done) {
916
968
  sink({ type: "status", label: `team al lavoro: ${pendingMembers(activeTeam).join(", ")}; attendo avanzamento` });
917
- await waitForTeamChange(activeTeam, signal, () => Boolean(deps.inbox?.pending));
969
+ const teamBus = resolveKingdomBus(process.env, config.sessionDir);
970
+ await waitForTeamChange(activeTeam, signal, () => {
971
+ if (deps.inbox?.pending || deps.inbox?.pendingCompactions)
972
+ return true;
973
+ const unread = teamBus.peekInbox(activeTeam.id, deps.kingdom?.self || "orchestratore")
974
+ .filter(message => !notifiedTeamMessages.has(`${activeTeam.id}:${message.seq}`));
975
+ for (const message of unread)
976
+ notifiedTeamMessages.add(`${activeTeam.id}:${message.seq}`);
977
+ return unread.length > 0;
978
+ });
918
979
  transientGuardContext = "[TALOS_TEAM_GUARD]\nIl team avviato deve ancora essere raccolto e verificato. "
919
980
  + "Leggi team_report e team_inbox, rispondi ai blocchi e verifica i risultati prima della consegna. "
920
981
  + "Non dichiarare concluso il lavoro solo perche' l'ingaggio e' partito.";
@@ -1050,6 +1111,10 @@ userInput, sink, signal) {
1050
1111
  // a indice di stanze, cosi' resta sempre compatto. Il dettaglio si
1051
1112
  // recupera col tool recall.
1052
1113
  await archiveCurrentRoom();
1114
+ // L'archiviazione puo' attendere I/O: non perdere un invio o comando
1115
+ // arrivato dopo il primo controllo della finale e prima del done.
1116
+ if (consumeInbox() || deps.inbox?.pendingCompactions)
1117
+ continue;
1053
1118
  sink({ type: "done" });
1054
1119
  await host?.emit("agent_end", {});
1055
1120
  return;
@@ -1057,6 +1122,7 @@ userInput, sink, signal) {
1057
1122
  // Esegue i tool richiesti e raccoglie i risultati in un turno "tool".
1058
1123
  const ctx = {
1059
1124
  cwd: config.cwd,
1125
+ waitInterrupted: () => Boolean(deps.inbox?.pending || deps.inbox?.pendingCompactions),
1060
1126
  ...(deps.workspaceSession ? { workspaceSession: deps.workspaceSession } : {}),
1061
1127
  ...(deps.executionTarget ? { executionTarget: deps.executionTarget } : {}),
1062
1128
  ...(deps.fsRoots !== undefined ? { fsRoots: deps.fsRoots } : {}),
@@ -1085,6 +1151,9 @@ userInput, sink, signal) {
1085
1151
  const executeCall = createToolExecutor(ctx, {
1086
1152
  onCall: (call, parentId) => sink({ type: "tool_call", call, ...(parentId ? { parentId } : {}) }),
1087
1153
  before: async (call) => {
1154
+ if (stepDirective?.admit && !stepDirective.admit(call.name)) {
1155
+ return { block: true, reason: stepDirective.rejection ?? "tool non ammesso in questo passo" };
1156
+ }
1088
1157
  let blocked;
1089
1158
  for (const output of await host?.emit("tool_call", { toolName: call.name, input: { ...call.input } }) ?? []) {
1090
1159
  if (output.block)
@@ -1135,6 +1204,9 @@ userInput, sink, signal) {
1135
1204
  results.push(part);
1136
1205
  sink({ type: "tool_result", result: part });
1137
1206
  }
1207
+ deps.stepPolicy?.afterStep?.({ step: turn, final: false,
1208
+ tools: toolCalls.map(call => effectiveToolName(call.name, call.input)),
1209
+ errors: results.filter(result => result.isError).length });
1138
1210
  if (toolCalls.some((call, index) => PLAN_COMPLETION_TOOLS.has(effectiveToolName(call.name, call.input))
1139
1211
  && results[index]?.isError !== true)) {
1140
1212
  planTouchedThisRun = true;
@@ -1204,9 +1276,6 @@ Il piano ha ${collaborators.length} filoni `
1204
1276
  transientGuardContext = [transientGuardContext, hint].filter(Boolean).join("\n\n");
1205
1277
  }
1206
1278
  }
1207
- await host?.emit("agent_end", {});
1208
- await archiveCurrentRoom();
1209
- sink({ type: "error", message: `pausa al tetto di ${turnCap} turni per questo giro (TALOS_MAX_TURNS per alzarlo); la validazione del piano riprende le attivita rimaste` });
1210
1279
  }
1211
1280
  // Archivia la stanza appena illuminata: costruisce il record Danilov dei nodi
1212
1281
  // di questo turno e lo persiste nella memoria delle stanze. L'archiviazione e'
@@ -1278,7 +1347,7 @@ async function archiveRoom(deps, roomStartHead, sink, policy, signal, anchors) {
1278
1347
  const firstKeptId = head;
1279
1348
  if (policy.roomCompaction === "threshold") {
1280
1349
  const window = contextWindowFor(config.model, config.contextWindow);
1281
- const limits = workingContextLimits(policy, window, config.compaction.reserveTokens);
1350
+ const limits = workingContextLimits(policy, window, config.compaction.reserveTokens, resolveMaxTokens(config.maxTokens, config.model), maxInputFor(config.model));
1282
1351
  if (projectedContextTokens(deps) <= limits.checkpoint)
1283
1352
  return;
1284
1353
  await compactNow(deps, signal, sink, undefined, anchors);
@@ -1630,6 +1699,9 @@ function projectCompactionMessages(deps, messages) {
1630
1699
  // Compattazione gia' avviata e non ancora conclusa, per sessione. Single-flight:
1631
1700
  // due turni ravvicinati non devono riassumere due volte la stessa cronologia.
1632
1701
  const pendingCompaction = new WeakMap();
1702
+ // Ultima composizione runtime: una compattazione idle non deve misurare solo
1703
+ // la cronologia o usare l'usage cumulativo del provider come finestra attiva.
1704
+ const sessionContextFrames = new WeakMap();
1633
1705
  // Compatta se la soglia e' superata. No-op se disabilitata o non necessario.
1634
1706
  //
1635
1707
  // La compattazione e' il nodo piu' costoso del giro: una chiamata al modello
@@ -1650,7 +1722,7 @@ savedTokens = 0,
1650
1722
  system = deps.config.system) {
1651
1723
  const { compaction } = deps.config;
1652
1724
  const inFlight = pendingCompaction.get(deps.session);
1653
- const limits = workingContextLimits(contextPolicy(deps.env ?? process.env), contextWindowFor(deps.config.model, deps.config.contextWindow), compaction.reserveTokens);
1725
+ const limits = workingContextLimits(contextPolicy(deps.env ?? process.env), contextWindowFor(deps.config.model, deps.config.contextWindow), compaction.reserveTokens, resolveMaxTokens(deps.config.maxTokens, deps.config.model), maxInputFor(deps.config.model));
1654
1726
  const move = planCompactionMove({
1655
1727
  enabled: compaction.enabled,
1656
1728
  // Il pack compilato e il contesto IDE sono effimeri e quindi non vivono
@@ -1705,8 +1777,8 @@ export async function compactNow(deps, signal, sink, instructions, anchors = [])
1705
1777
  async function performCompaction(deps, signal, sink, instructions, anchors = []) {
1706
1778
  const { session, config } = deps;
1707
1779
  const provider = trackSessionUsage(deps.provider, config.sessionDir, session.id, sink);
1708
- const fullChain = session.nodeChain();
1709
1780
  const previousCompaction = session.lastCompaction;
1781
+ const fullChain = session.nodeChain(previousCompaction?.firstKeptId);
1710
1782
  const liveStart = previousCompaction
1711
1783
  ? fullChain.findIndex((node) => node.id === previousCompaction.firstKeptId)
1712
1784
  : 0;
@@ -1780,7 +1852,7 @@ async function performCompaction(deps, signal, sink, instructions, anchors = [])
1780
1852
  // A speculative summary may finish after new messages arrive. Keep that tail
1781
1853
  // intact, but reject a result for a branch that is no longer current.
1782
1854
  if (signal.aborted || session.lastCompaction !== previousCompaction
1783
- || !session.nodeChain().some(node => node.id === snapshotHead))
1855
+ || !session.nodeChain(snapshotHead ?? undefined).some(node => node.id === snapshotHead))
1784
1856
  return false;
1785
1857
  const checkpoint = compactionCheckpoint(checkpointTasks(config, session.id, deps.kingdom?.self), messages, plan.cutIndex);
1786
1858
  if (checkpoint)
@@ -1788,6 +1860,14 @@ async function performCompaction(deps, signal, sink, instructions, anchors = [])
1788
1860
  session.compact(summary, firstKept.id, tokensBefore);
1789
1861
  const tokensAfter = projectedContextTokens(deps);
1790
1862
  sink({ type: "compacted", tokensBefore, tokensAfter, reason: "summary", ...(anchors.length ? { anchors: [...anchors] } : {}) });
1863
+ const frame = sessionContextFrames.get(session);
1864
+ sink({ type: "context_window", ledger: buildContextLedger({
1865
+ model: config.model, contextWindow: config.contextWindow, reserveTokens: config.compaction.reserveTokens,
1866
+ workingContextTokens: contextPolicy(deps.env ?? process.env).workingContextTokens,
1867
+ system: config.system, ...frame, tools: deps.tools.defs(),
1868
+ messages: projectCompactionMessages(deps, session.history()), demoted: null,
1869
+ compaction: { ts: session.lastCompaction.ts, tokensBefore, summary },
1870
+ }) });
1791
1871
  return true;
1792
1872
  }
1793
1873
  finally {
@@ -13,14 +13,16 @@ export interface BgTask {
13
13
  }
14
14
  interface Foreground {
15
15
  readonly taskId: string;
16
+ readonly sessionId?: string;
16
17
  background(): void;
17
18
  }
18
19
  export declare class BackgroundManager {
19
20
  private readonly tasks;
20
21
  private readonly children;
21
22
  private readonly order;
23
+ private readonly completedOrder;
22
24
  private seq;
23
- private fg;
25
+ private readonly foregrounds;
24
26
  private readonly changeCbs;
25
27
  private readonly finishCbs;
26
28
  createTask(command: string): BgTask;
@@ -29,11 +31,16 @@ export declare class BackgroundManager {
29
31
  finishTask(id: string, status: BgStatus, exitCode?: number): void;
30
32
  setForeground(fg: Foreground): void;
31
33
  clearForeground(id?: string): void;
32
- hasForeground(): boolean;
34
+ private foreground;
35
+ hasForeground(sessionId?: string): boolean;
33
36
  markDetached(id: string): void;
34
- requestBackground(): BgTask | null;
37
+ requestBackground(sessionId?: string): BgTask | null;
35
38
  kill(id: string): boolean;
36
39
  get(id: string): BgTask | undefined;
40
+ waitForTask(id: string, signal: AbortSignal, options?: {
41
+ readonly timeoutMs?: number;
42
+ readonly interrupted?: () => boolean;
43
+ }): Promise<void>;
37
44
  list(): readonly BgTask[];
38
45
  running(): readonly BgTask[];
39
46
  onChange(cb: () => void): () => void;
@@ -25,8 +25,9 @@ export class BackgroundManager {
25
25
  tasks = new Map();
26
26
  children = new Map();
27
27
  order = [];
28
+ completedOrder = [];
28
29
  seq = 0;
29
- fg = null;
30
+ foregrounds = new Map();
30
31
  changeCbs = new Set();
31
32
  finishCbs = new Set();
32
33
  createTask(command) {
@@ -56,6 +57,7 @@ export class BackgroundManager {
56
57
  t.endedAt = Date.now();
57
58
  if (exitCode !== undefined)
58
59
  t.exitCode = exitCode;
60
+ this.completedOrder.push(id);
59
61
  this.children.delete(id);
60
62
  this.emitChange();
61
63
  for (const cb of this.finishCbs)
@@ -63,10 +65,17 @@ export class BackgroundManager {
63
65
  this.pruneHistory(id);
64
66
  }
65
67
  // Registra il comando in primo piano (chiamato dal tool bash all'avvio).
66
- setForeground(fg) { this.fg = fg; }
67
- clearForeground(id) { if (!id || this.fg?.taskId === id)
68
- this.fg = null; }
69
- hasForeground() { return this.fg !== null; }
68
+ setForeground(fg) { this.foregrounds.set(fg.taskId, fg); }
69
+ clearForeground(id) {
70
+ if (id)
71
+ this.foregrounds.delete(id);
72
+ else
73
+ this.foregrounds.clear();
74
+ }
75
+ foreground(sessionId) {
76
+ return [...this.foregrounds.values()].findLast(fg => sessionId === undefined || fg.sessionId === sessionId);
77
+ }
78
+ hasForeground(sessionId) { return !!this.foreground(sessionId); }
70
79
  markDetached(id) {
71
80
  const task = this.tasks.get(id);
72
81
  if (task && !task.detached) {
@@ -76,11 +85,11 @@ export class BackgroundManager {
76
85
  }
77
86
  // Ctrl+B: stacca il comando in primo piano. Ritorna il task o null se non c'e'
78
87
  // niente in primo piano.
79
- requestBackground() {
80
- const fg = this.fg;
88
+ requestBackground(sessionId) {
89
+ const fg = this.foreground(sessionId);
81
90
  if (!fg)
82
91
  return null;
83
- this.fg = null;
92
+ this.foregrounds.delete(fg.taskId);
84
93
  const t = this.tasks.get(fg.taskId);
85
94
  if (t)
86
95
  t.detached = true;
@@ -97,6 +106,41 @@ export class BackgroundManager {
97
106
  return true;
98
107
  }
99
108
  get(id) { return this.tasks.get(id); }
109
+ // Wait in the host, without asking the model to poll unchanged output.
110
+ // Keep the task reference alive even if completed history is pruned.
111
+ async waitForTask(id, signal, options = {}) {
112
+ signal.throwIfAborted();
113
+ const task = this.tasks.get(id);
114
+ if (!task || task.status !== "running" || options.interrupted?.())
115
+ return;
116
+ await new Promise((resolve, reject) => {
117
+ let timeout;
118
+ let interruptCheck;
119
+ const cleanup = () => {
120
+ unsubscribe();
121
+ signal.removeEventListener("abort", abort);
122
+ clearTimeout(timeout);
123
+ clearInterval(interruptCheck);
124
+ };
125
+ const finish = () => { cleanup(); resolve(); };
126
+ const abort = () => { cleanup(); reject(signal.reason); };
127
+ const unsubscribe = this.onFinish(completed => { if (completed.id === id)
128
+ finish(); });
129
+ signal.addEventListener("abort", abort, { once: true });
130
+ if (options.timeoutMs !== undefined)
131
+ timeout = setTimeout(finish, options.timeoutMs);
132
+ // Only check local user input; elapsed time/output never wakes the model.
133
+ if (options.interrupted)
134
+ interruptCheck = setInterval(() => {
135
+ if (options.interrupted?.())
136
+ finish();
137
+ }, 100);
138
+ if (signal.aborted)
139
+ abort();
140
+ else if (task.status !== "running")
141
+ finish();
142
+ });
143
+ }
100
144
  list() {
101
145
  // Ogni comando shell nasce come candidato al Ctrl+B, ma diventa davvero
102
146
  // un'attivita' in background soltanto quando l'utente lo stacca. Esporre
@@ -111,7 +155,7 @@ export class BackgroundManager {
111
155
  emitChange() { for (const cb of this.changeCbs)
112
156
  cb(); }
113
157
  pruneHistory(latestId) {
114
- const completedDetached = this.order.filter((id) => {
158
+ const completedDetached = this.completedOrder.filter((id) => {
115
159
  const task = this.tasks.get(id);
116
160
  return Boolean(task?.detached && task.status !== "running");
117
161
  });
@@ -134,6 +178,8 @@ export class BackgroundManager {
134
178
  }
135
179
  const retainedOrder = this.order.filter((id) => keep.has(id));
136
180
  this.order.splice(0, this.order.length, ...retainedOrder);
181
+ const retainedCompleted = this.completedOrder.filter(id => keep.has(id));
182
+ this.completedOrder.splice(0, this.completedOrder.length, ...retainedCompleted);
137
183
  }
138
184
  }
139
185
  // Istanza condivisa di processo.
@@ -278,6 +278,7 @@ export async function bootstrap(opts) {
278
278
  const runConfig = { ...config, model: resolved.modelId };
279
279
  return {
280
280
  provider: resolved.provider,
281
+ ...(config.fusion ? { fusionHarness: harness } : {}),
281
282
  tools,
282
283
  session,
283
284
  config: runConfig,
@@ -69,7 +69,7 @@ export function contextWindowFor(model, override = 0) {
69
69
  const m = model.toLowerCase();
70
70
  if (m.includes("1m"))
71
71
  return 1_000_000;
72
- if (m.includes("gpt-5.6") || m.includes("gpt-6-astra"))
72
+ if (m.includes("gpt-5.6") || m.includes("gpt-6"))
73
73
  return 1_050_000;
74
74
  if (/claude-(?:fable|opus|sonnet)-5(?:\b|[-.])/.test(m))
75
75
  return 1_000_000;
@@ -0,0 +1,5 @@
1
+ type ConfigObject = Record<string, unknown>;
2
+ export declare function readConfigObject(path: string): ConfigObject;
3
+ export declare function updateConfigObject(path: string, update: (current: ConfigObject) => ConfigObject): void;
4
+ export declare function resetInvalidConfig(path: string): string | null;
5
+ export {};