infinicode 2.3.2 → 2.3.3

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 (39) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/commands/mcp.js +2 -0
  3. package/dist/commands/serve.d.ts +1 -0
  4. package/dist/commands/serve.js +36 -1
  5. package/dist/kernel/capability-map.d.ts +18 -0
  6. package/dist/kernel/capability-map.js +84 -0
  7. package/dist/kernel/checkpoint-engine.js +9 -0
  8. package/dist/kernel/command-system.d.ts +3 -0
  9. package/dist/kernel/command-system.js +315 -10
  10. package/dist/kernel/config-schema.d.ts +11 -0
  11. package/dist/kernel/config-schema.js +1 -0
  12. package/dist/kernel/federation/event-bridge.js +3 -1
  13. package/dist/kernel/federation/federation.d.ts +10 -0
  14. package/dist/kernel/federation/federation.js +28 -9
  15. package/dist/kernel/free-providers.d.ts +23 -0
  16. package/dist/kernel/free-providers.js +73 -1
  17. package/dist/kernel/index.d.ts +4 -0
  18. package/dist/kernel/index.js +3 -0
  19. package/dist/kernel/kernel.d.ts +12 -0
  20. package/dist/kernel/kernel.js +29 -2
  21. package/dist/kernel/mission-engine.js +14 -0
  22. package/dist/kernel/orchestrator.d.ts +3 -0
  23. package/dist/kernel/orchestrator.js +74 -2
  24. package/dist/kernel/planner.d.ts +42 -0
  25. package/dist/kernel/planner.js +174 -0
  26. package/dist/kernel/recovery-manager.d.ts +9 -0
  27. package/dist/kernel/recovery-manager.js +64 -3
  28. package/dist/kernel/router.d.ts +12 -1
  29. package/dist/kernel/router.js +36 -1
  30. package/dist/kernel/scheduler.d.ts +17 -0
  31. package/dist/kernel/scheduler.js +97 -1
  32. package/dist/kernel/setup.d.ts +9 -0
  33. package/dist/kernel/setup.js +38 -0
  34. package/dist/kernel/types.d.ts +65 -2
  35. package/dist/kernel/verification-engine.js +83 -27
  36. package/dist/kernel/verification-exec.d.ts +14 -0
  37. package/dist/kernel/verification-exec.js +65 -0
  38. package/dist/kernel/worker-runtime.js +3 -2
  39. package/package.json +1 -1
@@ -23,6 +23,9 @@ export interface DispatchTask {
23
23
  prompt: string;
24
24
  role?: string;
25
25
  context?: Record<string, unknown>;
26
+ /** When true, the receiving node auto-plans the goal into phases (a phased
27
+ * build) instead of running it as one atomic task. */
28
+ autoPlan?: boolean;
26
29
  }
27
30
  export interface FederationOptions {
28
31
  version: string;
@@ -100,6 +103,13 @@ export declare class Federation {
100
103
  runId: string;
101
104
  record: RunRecord;
102
105
  } | null>;
106
+ /** Runs received + executing on THIS node (async handoff), newest last. */
107
+ localRuns(): RunRecord[];
108
+ /** Runs WE dispatched to peers → { runId, peerId } for monitoring. */
109
+ outboundRuns(): Array<{
110
+ runId: string;
111
+ peerId: string;
112
+ }>;
103
113
  /** Query a dispatched run's current record (from whichever peer holds it). */
104
114
  followRun(runId: string): Promise<{
105
115
  record: RunRecord;
@@ -174,15 +174,26 @@ export class Federation {
174
174
  const localRole = loadRole();
175
175
  const preamble = rolePreamble(localRole);
176
176
  const prompt = preamble ? `${preamble}\n\n${task.prompt}` : task.prompt;
177
- const mission = await this.deps.kernel.execute({
178
- description: task.description,
179
- goal: task.description,
180
- tasks: [{
181
- description: task.description,
182
- capabilities: task.capabilities.length ? task.capabilities : ['reasoning'],
183
- input: { prompt, context: { role: task.role ?? localRole?.role, architecture: localRole?.architecture, ...task.context } },
184
- }],
185
- });
177
+ // Auto-planned dispatch the remote kernel decomposes the goal into phases
178
+ // (architecture → implementation → verification …), each routed to its own
179
+ // model tier. The phase/task events stream back to the dispatcher for
180
+ // monitoring. Otherwise run it as a single atomic task.
181
+ const mission = task.autoPlan
182
+ ? await this.deps.kernel.execute({
183
+ description: task.description,
184
+ goal: task.prompt || task.description,
185
+ autoPlan: true,
186
+ metadata: { role: task.role ?? localRole?.role, architecture: localRole?.architecture, ...task.context },
187
+ })
188
+ : await this.deps.kernel.execute({
189
+ description: task.description,
190
+ goal: task.description,
191
+ tasks: [{
192
+ description: task.description,
193
+ capabilities: task.capabilities.length ? task.capabilities : ['reasoning'],
194
+ input: { prompt, context: { role: task.role ?? localRole?.role, architecture: localRole?.architecture, ...task.context } },
195
+ }],
196
+ });
186
197
  return {
187
198
  missionId: mission.id,
188
199
  status: mission.status,
@@ -238,6 +249,14 @@ export class Federation {
238
249
  }
239
250
  return { peerId: target.nodeId, runId, record };
240
251
  }
252
+ /** Runs received + executing on THIS node (async handoff), newest last. */
253
+ localRuns() {
254
+ return [...this.runs.values()];
255
+ }
256
+ /** Runs WE dispatched to peers → { runId, peerId } for monitoring. */
257
+ outboundRuns() {
258
+ return [...this.dispatchedRuns.entries()].map(([runId, peerId]) => ({ runId, peerId }));
259
+ }
241
260
  /** Query a dispatched run's current record (from whichever peer holds it). */
242
261
  async followRun(runId) {
243
262
  const local = this.runs.get(runId);
@@ -9,6 +9,11 @@ import type { ProviderModel, WorkerType } from './types.js';
9
9
  export interface FreeProviderPreset {
10
10
  id: string;
11
11
  name: string;
12
+ /**
13
+ * Provider kind. 'local' presets register as local (offline-eligible)
14
+ * providers and require no API key. Defaults to 'cloud' when omitted.
15
+ */
16
+ type?: 'local' | 'cloud';
12
17
  baseURL: string;
13
18
  headerName?: string;
14
19
  /** Where to get the API key. */
@@ -24,5 +29,23 @@ export interface FreeProviderPreset {
24
29
  notes?: string;
25
30
  }
26
31
  export declare const FREE_PROVIDERS: FreeProviderPreset[];
32
+ /**
33
+ * Local OpenAI-compatible servers the wizard can register. These speak the same
34
+ * /v1/chat/completions + /v1/models API as the cloud providers but run on the
35
+ * user's machine, so they need NO API key and register with type 'local' (which
36
+ * makes the router apply its localBonus and include them in 'offline' mode).
37
+ *
38
+ * knownModels are intentionally empty: every one of these servers exposes the
39
+ * currently loaded model(s) dynamically via /v1/models, so there is no fixed
40
+ * catalog to hard-code. Unreachable servers are marked unhealthy by the health
41
+ * check and simply skipped by the router.
42
+ */
43
+ export declare const LOCAL_PROVIDERS: FreeProviderPreset[];
27
44
  export declare function getProviderPreset(id: string): FreeProviderPreset | undefined;
45
+ /**
46
+ * Build ready-to-register config entries for every built-in local provider.
47
+ * Used to register LM Studio / vLLM / SGLang / llama.cpp out of the box when the
48
+ * user has not customized `config.localProviders`.
49
+ */
50
+ export declare function localProviderConfigs(): CloudProviderConfig[];
28
51
  export declare function toCloudProviderConfig(preset: FreeProviderPreset, apiKey: string): CloudProviderConfig;
@@ -147,8 +147,80 @@ export const FREE_PROVIDERS = [
147
147
  notes: 'Google Generative Language API. Free tier (15 RPM / 1500 RPD on Flash). Long context.',
148
148
  },
149
149
  ];
150
+ /**
151
+ * Local OpenAI-compatible servers the wizard can register. These speak the same
152
+ * /v1/chat/completions + /v1/models API as the cloud providers but run on the
153
+ * user's machine, so they need NO API key and register with type 'local' (which
154
+ * makes the router apply its localBonus and include them in 'offline' mode).
155
+ *
156
+ * knownModels are intentionally empty: every one of these servers exposes the
157
+ * currently loaded model(s) dynamically via /v1/models, so there is no fixed
158
+ * catalog to hard-code. Unreachable servers are marked unhealthy by the health
159
+ * check and simply skipped by the router.
160
+ */
161
+ export const LOCAL_PROVIDERS = [
162
+ {
163
+ id: 'lmstudio',
164
+ name: 'LM Studio',
165
+ type: 'local',
166
+ baseURL: 'http://localhost:1234/v1',
167
+ keyUrl: 'https://lmstudio.ai',
168
+ free: true,
169
+ recommended: {},
170
+ knownModels: [],
171
+ notes: 'LM Studio local server. Start it from the "Developer" tab. No API key required.',
172
+ },
173
+ {
174
+ id: 'vllm',
175
+ name: 'vLLM',
176
+ type: 'local',
177
+ baseURL: 'http://localhost:8000/v1',
178
+ keyUrl: 'https://docs.vllm.ai',
179
+ free: true,
180
+ recommended: {},
181
+ knownModels: [],
182
+ notes: 'vLLM OpenAI-compatible server (`vllm serve <model>`). No API key required.',
183
+ },
184
+ {
185
+ id: 'sglang',
186
+ name: 'SGLang',
187
+ type: 'local',
188
+ baseURL: 'http://localhost:30000/v1',
189
+ keyUrl: 'https://docs.sglang.ai',
190
+ free: true,
191
+ recommended: {},
192
+ knownModels: [],
193
+ notes: 'SGLang OpenAI-compatible server (`python -m sglang.launch_server`). No API key required.',
194
+ },
195
+ {
196
+ id: 'llamacpp',
197
+ name: 'llama.cpp',
198
+ type: 'local',
199
+ baseURL: 'http://localhost:8080/v1',
200
+ keyUrl: 'https://github.com/ggml-org/llama.cpp',
201
+ free: true,
202
+ recommended: {},
203
+ knownModels: [],
204
+ notes: 'llama.cpp server (`llama-server`). No API key required.',
205
+ },
206
+ ];
150
207
  export function getProviderPreset(id) {
151
- return FREE_PROVIDERS.find(p => p.id === id);
208
+ return FREE_PROVIDERS.find(p => p.id === id) ?? LOCAL_PROVIDERS.find(p => p.id === id);
209
+ }
210
+ /**
211
+ * Build ready-to-register config entries for every built-in local provider.
212
+ * Used to register LM Studio / vLLM / SGLang / llama.cpp out of the box when the
213
+ * user has not customized `config.localProviders`.
214
+ */
215
+ export function localProviderConfigs() {
216
+ return LOCAL_PROVIDERS.map((preset) => ({
217
+ id: preset.id,
218
+ name: preset.name,
219
+ type: 'local',
220
+ baseURL: preset.baseURL,
221
+ headerName: preset.headerName,
222
+ enabled: true,
223
+ }));
152
224
  }
153
225
  export function toCloudProviderConfig(preset, apiKey) {
154
226
  return {
@@ -32,6 +32,10 @@ export type { GeminiProviderOptions } from './providers/gemini-provider.js';
32
32
  export { ALL_BUILTIN_WORKERS, createResearchWorker, createCodingWorker, createArchitectureWorker, createReviewWorker, createDocumentationWorker, createTranslationWorker, createTerminalWorker, createVerificationWorker, createVisionWorker, createFrontendWorker, createBrowserWorker, } from './workers/builtin-workers.js';
33
33
  export { KernelCommandRegistry, DEFAULT_COMMANDS } from './command-system.js';
34
34
  export type { KernelCommand, CommandContext } from './command-system.js';
35
+ export { MissionPlanner } from './planner.js';
36
+ export type { MissionPlannerDeps, PlanOptions } from './planner.js';
37
+ export { normalizeCapabilities, CANONICAL_CAPABILITIES } from './capability-map.js';
38
+ export { phaseBenchmarkFloor, phaseLabel } from './scheduler.js';
35
39
  export * from './federation/index.js';
36
40
  export { createMcpServer, serveMcpStdio } from './mcp/index.js';
37
41
  export type { McpServerDeps } from './mcp/index.js';
@@ -24,6 +24,9 @@ export { OpenAICompatibleProvider } from './providers/openai-compatible-provider
24
24
  export { GeminiProvider } from './providers/gemini-provider.js';
25
25
  export { ALL_BUILTIN_WORKERS, createResearchWorker, createCodingWorker, createArchitectureWorker, createReviewWorker, createDocumentationWorker, createTranslationWorker, createTerminalWorker, createVerificationWorker, createVisionWorker, createFrontendWorker, createBrowserWorker, } from './workers/builtin-workers.js';
26
26
  export { KernelCommandRegistry, DEFAULT_COMMANDS } from './command-system.js';
27
+ export { MissionPlanner } from './planner.js';
28
+ export { normalizeCapabilities, CANONICAL_CAPABILITIES } from './capability-map.js';
29
+ export { phaseBenchmarkFloor, phaseLabel } from './scheduler.js';
27
30
  // Federation — neutral device mesh (peers, telemetry, config sync, MOLTFED interop)
28
31
  export * from './federation/index.js';
29
32
  // MCP — north-facing control surface (spawn/dispatch/role/voice/map over the mesh)
@@ -20,6 +20,8 @@ import { PolicyEngine } from './policies.js';
20
20
  import { VerificationEngine } from './verification-engine.js';
21
21
  import { RecoveryManager } from './recovery-manager.js';
22
22
  import { KernelCommandRegistry } from './command-system.js';
23
+ import { MissionPlanner } from './planner.js';
24
+ import type { Federation } from './federation/federation.js';
23
25
  import type { CommandResult, EventHandler, KernelEvent, KernelEventType, KernelLike, Logger, Mission, MissionInput, Plugin, Policy, ProviderInterface, WorkerDefinition, MissionStatus } from './types.js';
24
26
  export interface KernelOptions {
25
27
  logger?: Logger;
@@ -42,8 +44,18 @@ export declare class Kernel implements KernelLike {
42
44
  readonly logger: Logger;
43
45
  /** Canonical OpenKernel slash-command surface (/status, /workers, /goal …). */
44
46
  readonly commands: KernelCommandRegistry;
47
+ /** Decomposes a goal into phases when a mission is run with `autoPlan`. */
48
+ readonly planner: MissionPlanner;
49
+ /**
50
+ * The mesh node this kernel is part of, once `infinicode serve`/`mcp` wires it
51
+ * in via {@link attachFederation}. Undefined when running stand-alone. Node
52
+ * commands (/nodes, /node, /activity …) read it off the command context.
53
+ */
54
+ federation?: Federation;
45
55
  private missions;
46
56
  constructor(options?: KernelOptions);
57
+ /** Attach the federation mesh node so node/mesh slash-commands can reach it. */
58
+ attachFederation(federation: Federation): void;
47
59
  execute(input: MissionInput): Promise<Mission>;
48
60
  pause(missionId: string): Promise<void>;
49
61
  resume(missionId: string): Promise<Mission>;
@@ -22,6 +22,7 @@ import { RecoveryManager } from './recovery-manager.js';
22
22
  import { ConsoleLogger } from './logger.js';
23
23
  import { ALL_BUILTIN_WORKERS } from './workers/builtin-workers.js';
24
24
  import { KernelCommandRegistry } from './command-system.js';
25
+ import { MissionPlanner } from './planner.js';
25
26
  export class Kernel {
26
27
  eventBus;
27
28
  providerManager;
@@ -38,6 +39,14 @@ export class Kernel {
38
39
  logger;
39
40
  /** Canonical OpenKernel slash-command surface (/status, /workers, /goal …). */
40
41
  commands;
42
+ /** Decomposes a goal into phases when a mission is run with `autoPlan`. */
43
+ planner;
44
+ /**
45
+ * The mesh node this kernel is part of, once `infinicode serve`/`mcp` wires it
46
+ * in via {@link attachFederation}. Undefined when running stand-alone. Node
47
+ * commands (/nodes, /node, /activity …) read it off the command context.
48
+ */
49
+ federation;
41
50
  missions = new Map();
42
51
  constructor(options = {}) {
43
52
  this.logger = options.logger ?? new ConsoleLogger();
@@ -89,17 +98,35 @@ export class Kernel {
89
98
  });
90
99
  this.plugins = new PluginManager(this.eventBus, this.logger);
91
100
  this.commands = new KernelCommandRegistry(this);
101
+ this.planner = new MissionPlanner({
102
+ providerManager: this.providerManager,
103
+ router: this.router,
104
+ logger: this.logger,
105
+ });
92
106
  if (options.loadBuiltinWorkers ?? true) {
93
107
  for (const handler of ALL_BUILTIN_WORKERS) {
94
108
  this.workerRuntime.registerHandler(handler);
95
109
  }
96
110
  }
97
111
  }
112
+ /** Attach the federation mesh node so node/mesh slash-commands can reach it. */
113
+ attachFederation(federation) {
114
+ this.federation = federation;
115
+ }
98
116
  async execute(input) {
99
- const mission = this.missionEngine.create(input);
117
+ // Auto-planning: decompose a bare goal into phases before execution when
118
+ // asked and no tasks/phases were supplied. Each phase → its own objective +
119
+ // model tier (see MissionPlanner + scheduler.planPhases).
120
+ let effective = input;
121
+ if (input.autoPlan && !input.tasks?.length && !input.phases?.length) {
122
+ const policy = this.policies.resolveRef(input.policy).policy;
123
+ const phases = await this.planner.plan(input.goal, { description: input.description, policy });
124
+ effective = { ...input, phases };
125
+ }
126
+ const mission = this.missionEngine.create(effective);
100
127
  this.missions.set(mission.id, mission);
101
128
  try {
102
- await this.orchestrator.run(mission, input);
129
+ await this.orchestrator.run(mission, effective);
103
130
  }
104
131
  catch (err) {
105
132
  const message = err instanceof Error ? err.message : String(err);
@@ -38,6 +38,20 @@ export class MissionEngine {
38
38
  mission.tasks = tasks;
39
39
  this.transition(mission, 'READY');
40
40
  this.deps.logger.info(`Mission planned: ${mission.id} — ${tasks.length} task(s)`);
41
+ // Broadcast the phase breakdown so harnesses (InfiniBot mesh map) can render
42
+ // the plan + each phase's capabilities and model tier.
43
+ this.emit(mission, 'PLAN_CREATED', {
44
+ missionId: mission.id,
45
+ goal: mission.goal,
46
+ phases: objectives.map(o => ({
47
+ id: o.id,
48
+ phase: o.phase,
49
+ description: o.description,
50
+ capabilities: o.capabilities,
51
+ minBenchmark: o.minBenchmark,
52
+ taskCount: o.taskIds.length,
53
+ })),
54
+ });
41
55
  return mission;
42
56
  }
43
57
  start(mission) {
@@ -36,6 +36,9 @@ export declare class Orchestrator {
36
36
  private taskOverrides;
37
37
  /** Per-task forced worker type override set by the recovery manager. */
38
38
  private taskWorkerOverrides;
39
+ /** How many times each task has been replanned (bounds the replan loop). */
40
+ private taskReplans;
41
+ private maxReplans;
39
42
  constructor(deps: OrchestratorDeps);
40
43
  run(mission: Mission, input: MissionInput): Promise<Mission>;
41
44
  private runTasks;
@@ -7,6 +7,9 @@ export class Orchestrator {
7
7
  taskOverrides = new Map();
8
8
  /** Per-task forced worker type override set by the recovery manager. */
9
9
  taskWorkerOverrides = new Map();
10
+ /** How many times each task has been replanned (bounds the replan loop). */
11
+ taskReplans = new Map();
12
+ maxReplans = 2;
10
13
  constructor(deps) {
11
14
  this.deps = deps;
12
15
  }
@@ -68,6 +71,15 @@ export class Orchestrator {
68
71
  }
69
72
  }
70
73
  async executeTask(mission, task) {
74
+ // Per-phase model assignment: inherit this task's objective (phase) benchmark
75
+ // floor so demanding phases (architecture, verification) route to a more
76
+ // capable model tier than light phases — instead of one model for the whole
77
+ // mission. Escalation may later raise this floor further.
78
+ if (task.minBenchmark === undefined) {
79
+ const objective = mission.objectives.find(o => o.id === task.objectiveId);
80
+ if (objective?.minBenchmark)
81
+ task.minBenchmark = objective.minBenchmark;
82
+ }
71
83
  const overrideType = this.taskWorkerOverrides.get(task.id);
72
84
  let workerType = overrideType ?? this.deps.workerRuntime.matchWorkerType(task.capabilities);
73
85
  if (!workerType) {
@@ -90,7 +102,8 @@ export class Orchestrator {
90
102
  // Per-task verification via the Verification Engine pipeline.
91
103
  const vresult = await this.deps.verification.verify(mission, task, mission.policy.policy);
92
104
  if (!vresult.passed) {
93
- // Treat as a verification-failure recovery case.
105
+ // Count verification misses repeated misses escalate the model tier.
106
+ task.verificationFailures = (task.verificationFailures ?? 0) + 1;
94
107
  await this.handleRecovery(mission, task, `verification failed: ${vresult.summary}`, instance);
95
108
  }
96
109
  }
@@ -112,6 +125,7 @@ export class Orchestrator {
112
125
  policy: mission.policy.policy,
113
126
  currentProviderId: instance.providerId,
114
127
  currentModelId: instance.modelId,
128
+ verificationFailures: task.verificationFailures,
115
129
  });
116
130
  const maxAttempts = mission.policy.policy.retry?.maxAttempts ?? 3;
117
131
  switch (decision.action) {
@@ -144,6 +158,35 @@ export class Orchestrator {
144
158
  task.error = message;
145
159
  break;
146
160
  }
161
+ case 'escalate-model': {
162
+ // Climb the benchmark ladder to a more capable model and raise this
163
+ // task's routing floor so it keeps escalating on further failures.
164
+ if (decision.rotateToProviderId && decision.rotateToModelId) {
165
+ this.taskOverrides.set(task.id, {
166
+ providerId: decision.rotateToProviderId,
167
+ modelId: decision.rotateToModelId,
168
+ });
169
+ if (decision.escalateToBenchmark) {
170
+ task.minBenchmark = Math.max(task.minBenchmark ?? 0, decision.escalateToBenchmark);
171
+ }
172
+ this.deps.eventBus.emit({
173
+ type: 'MODEL_CHANGED',
174
+ timestamp: Date.now(),
175
+ missionId: mission.id,
176
+ taskId: task.id,
177
+ data: {
178
+ from: { providerId: instance.providerId, modelId: instance.modelId },
179
+ to: { providerId: decision.rotateToProviderId, modelId: decision.rotateToModelId },
180
+ reason: decision.reason,
181
+ escalated: true,
182
+ minBenchmark: task.minBenchmark,
183
+ },
184
+ });
185
+ }
186
+ task.status = 'PENDING';
187
+ task.error = message;
188
+ break;
189
+ }
147
190
  case 'spawn-different-worker': {
148
191
  if (decision.newWorkerType) {
149
192
  this.taskWorkerOverrides.set(task.id, decision.newWorkerType);
@@ -153,7 +196,36 @@ export class Orchestrator {
153
196
  task.error = message;
154
197
  break;
155
198
  }
156
- case 'replan':
199
+ case 'replan': {
200
+ // Real replan: checkpoint, then restart the task with a CLEAN slate —
201
+ // drop the provider/model + worker overrides so it re-routes from
202
+ // scratch, and reset the attempt/verification counters. Bounded by
203
+ // maxReplans so an unsatisfiable task can't loop forever.
204
+ const replans = (this.taskReplans.get(task.id) ?? 0) + 1;
205
+ this.taskReplans.set(task.id, replans);
206
+ await this.deps.checkpoints.save(mission, this.collectWorkerStates(mission.id), 'crash');
207
+ if (replans > this.maxReplans) {
208
+ task.status = 'FAILED';
209
+ task.error = `${message} (replan limit reached)`;
210
+ this.deps.eventBus.emit({
211
+ type: 'TASK_FAILED',
212
+ timestamp: Date.now(),
213
+ missionId: mission.id,
214
+ taskId: task.id,
215
+ workerId: instance.id,
216
+ data: { error: task.error, attempts: task.attempts, replans },
217
+ });
218
+ break;
219
+ }
220
+ this.taskOverrides.delete(task.id);
221
+ this.taskWorkerOverrides.delete(task.id);
222
+ task.verificationFailures = 0;
223
+ task.attempts = 0;
224
+ task.status = 'PENDING';
225
+ task.error = message;
226
+ this.deps.logger.info(`Task ${task.id}: replan #${replans} — fresh routing + counters`);
227
+ break;
228
+ }
157
229
  case 'checkpoint': {
158
230
  await this.deps.checkpoints.save(mission, this.collectWorkerStates(mission.id), 'crash');
159
231
  task.status = 'PENDING';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OpenKernel — Mission Planner
3
+ *
4
+ * Decomposes a one-line goal into a sequence of PHASES (each becomes an
5
+ * objective), so a large build spreads across the right models by aspect
6
+ * (architecture → implementation → verification) instead of one model
7
+ * one-shotting the whole thing. Each phase carries the capabilities it needs;
8
+ * the scheduler turns those into a per-phase model benchmark floor.
9
+ *
10
+ * LLM-driven when a capable provider is available, with a deterministic
11
+ * heuristic fallback so planning never blocks on model availability.
12
+ */
13
+ import type { Logger, MissionPhaseInput, Policy } from './types.js';
14
+ import type { ProviderManager } from './provider-manager.js';
15
+ import type { CapabilityRouter } from './router.js';
16
+ import { normalizeCapabilities } from './capability-map.js';
17
+ export interface MissionPlannerDeps {
18
+ providerManager: ProviderManager;
19
+ router: CapabilityRouter;
20
+ logger: Logger;
21
+ }
22
+ export interface PlanOptions {
23
+ description?: string;
24
+ policy?: Policy;
25
+ /** Max phases to request (default 6). */
26
+ maxPhases?: number;
27
+ }
28
+ export declare class MissionPlanner {
29
+ private deps;
30
+ constructor(deps: MissionPlannerDeps);
31
+ plan(goal: string, opts?: PlanOptions): Promise<MissionPhaseInput[]>;
32
+ private planWithLlm;
33
+ /** Extract + validate a phase array from a model's (possibly noisy) response. */
34
+ private parsePhases;
35
+ /**
36
+ * Deterministic fallback plan. Always yields buildable phases; adds a frontend
37
+ * phase for UI-shaped goals. Capabilities are natural — the scheduler
38
+ * normalizes them and derives each phase's model tier.
39
+ */
40
+ private heuristicPhases;
41
+ }
42
+ export { normalizeCapabilities };