infinicode 1.0.0 → 2.0.0

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 (157) hide show
  1. package/.opencode/plugins/home-logo-animation.tsx +120 -0
  2. package/.opencode/plugins/routing-mode-display.tsx +135 -0
  3. package/.opencode/themes/infinibot-gold.json +222 -0
  4. package/.opencode/tui.json +8 -0
  5. package/README.md +527 -75
  6. package/dist/ascii-video-animation.d.ts +2 -0
  7. package/dist/ascii-video-animation.js +243 -0
  8. package/dist/cli.js +195 -50
  9. package/dist/commands/console.d.ts +6 -0
  10. package/dist/commands/console.js +111 -0
  11. package/dist/commands/kernel-setup.d.ts +3 -0
  12. package/dist/commands/kernel-setup.js +303 -0
  13. package/dist/commands/mcp.d.ts +12 -0
  14. package/dist/commands/mcp.js +96 -0
  15. package/dist/commands/mission.d.ts +16 -0
  16. package/dist/commands/mission.js +301 -0
  17. package/dist/commands/models.d.ts +3 -1
  18. package/dist/commands/models.js +111 -55
  19. package/dist/commands/providers.d.ts +6 -0
  20. package/dist/commands/providers.js +95 -0
  21. package/dist/commands/run.d.ts +2 -1
  22. package/dist/commands/run.js +349 -59
  23. package/dist/commands/serve.d.ts +18 -0
  24. package/dist/commands/serve.js +127 -0
  25. package/dist/commands/setup.d.ts +1 -1
  26. package/dist/commands/setup.js +77 -44
  27. package/dist/commands/status.d.ts +2 -1
  28. package/dist/commands/status.js +46 -30
  29. package/dist/commands/workers.d.ts +5 -0
  30. package/dist/commands/workers.js +103 -0
  31. package/dist/kernel/autonomy/goal-loop.d.ts +49 -0
  32. package/dist/kernel/autonomy/goal-loop.js +113 -0
  33. package/dist/kernel/autonomy/index.d.ts +8 -0
  34. package/dist/kernel/autonomy/index.js +7 -0
  35. package/dist/kernel/browser/browser-controller.d.ts +57 -0
  36. package/dist/kernel/browser/browser-controller.js +175 -0
  37. package/dist/kernel/checkpoint-engine.d.ts +17 -0
  38. package/dist/kernel/checkpoint-engine.js +128 -0
  39. package/dist/kernel/command-system.d.ts +37 -0
  40. package/dist/kernel/command-system.js +239 -0
  41. package/dist/kernel/config-schema.d.ts +53 -0
  42. package/dist/kernel/config-schema.js +36 -0
  43. package/dist/kernel/event-bus.d.ts +19 -0
  44. package/dist/kernel/event-bus.js +65 -0
  45. package/dist/kernel/federation/auto-update.d.ts +36 -0
  46. package/dist/kernel/federation/auto-update.js +57 -0
  47. package/dist/kernel/federation/compute-router.d.ts +27 -0
  48. package/dist/kernel/federation/compute-router.js +44 -0
  49. package/dist/kernel/federation/config-sync.d.ts +33 -0
  50. package/dist/kernel/federation/config-sync.js +37 -0
  51. package/dist/kernel/federation/discovery.d.ts +11 -0
  52. package/dist/kernel/federation/discovery.js +44 -0
  53. package/dist/kernel/federation/event-bridge.d.ts +30 -0
  54. package/dist/kernel/federation/event-bridge.js +61 -0
  55. package/dist/kernel/federation/federation-plugin.d.ts +24 -0
  56. package/dist/kernel/federation/federation-plugin.js +35 -0
  57. package/dist/kernel/federation/federation.d.ts +126 -0
  58. package/dist/kernel/federation/federation.js +335 -0
  59. package/dist/kernel/federation/index.d.ts +31 -0
  60. package/dist/kernel/federation/index.js +23 -0
  61. package/dist/kernel/federation/moltfed-adapter.d.ts +30 -0
  62. package/dist/kernel/federation/moltfed-adapter.js +52 -0
  63. package/dist/kernel/federation/moltfed-connector.d.ts +98 -0
  64. package/dist/kernel/federation/moltfed-connector.js +193 -0
  65. package/dist/kernel/federation/node-identity.d.ts +19 -0
  66. package/dist/kernel/federation/node-identity.js +86 -0
  67. package/dist/kernel/federation/peer-mesh.d.ts +46 -0
  68. package/dist/kernel/federation/peer-mesh.js +117 -0
  69. package/dist/kernel/federation/protocol.d.ts +20 -0
  70. package/dist/kernel/federation/protocol.js +61 -0
  71. package/dist/kernel/federation/role-context.d.ts +5 -0
  72. package/dist/kernel/federation/role-context.js +45 -0
  73. package/dist/kernel/federation/telemetry.d.ts +21 -0
  74. package/dist/kernel/federation/telemetry.js +138 -0
  75. package/dist/kernel/federation/transport-http.d.ts +44 -0
  76. package/dist/kernel/federation/transport-http.js +207 -0
  77. package/dist/kernel/federation/types.d.ts +212 -0
  78. package/dist/kernel/federation/types.js +3 -0
  79. package/dist/kernel/free-providers.d.ts +28 -0
  80. package/dist/kernel/free-providers.js +162 -0
  81. package/dist/kernel/frontend-scoring.d.ts +21 -0
  82. package/dist/kernel/frontend-scoring.js +125 -0
  83. package/dist/kernel/index.d.ts +63 -0
  84. package/dist/kernel/index.js +45 -0
  85. package/dist/kernel/kernel.d.ts +75 -0
  86. package/dist/kernel/kernel.js +223 -0
  87. package/dist/kernel/logger.d.ts +18 -0
  88. package/dist/kernel/logger.js +26 -0
  89. package/dist/kernel/mcp/index.d.ts +9 -0
  90. package/dist/kernel/mcp/index.js +8 -0
  91. package/dist/kernel/mcp/mcp-server.d.ts +27 -0
  92. package/dist/kernel/mcp/mcp-server.js +178 -0
  93. package/dist/kernel/mission-engine.d.ts +42 -0
  94. package/dist/kernel/mission-engine.js +160 -0
  95. package/dist/kernel/orchestrator.d.ts +51 -0
  96. package/dist/kernel/orchestrator.js +226 -0
  97. package/dist/kernel/plugin-manager.d.ts +28 -0
  98. package/dist/kernel/plugin-manager.js +76 -0
  99. package/dist/kernel/plugins/browser-plugin.d.ts +22 -0
  100. package/dist/kernel/plugins/browser-plugin.js +34 -0
  101. package/dist/kernel/plugins/dashboard-plugin.d.ts +17 -0
  102. package/dist/kernel/plugins/dashboard-plugin.js +72 -0
  103. package/dist/kernel/plugins/discord-plugin.d.ts +11 -0
  104. package/dist/kernel/plugins/discord-plugin.js +35 -0
  105. package/dist/kernel/plugins/metrics-plugin.d.ts +33 -0
  106. package/dist/kernel/plugins/metrics-plugin.js +86 -0
  107. package/dist/kernel/plugins/search-plugin.d.ts +17 -0
  108. package/dist/kernel/plugins/search-plugin.js +20 -0
  109. package/dist/kernel/plugins/slack-plugin.d.ts +11 -0
  110. package/dist/kernel/plugins/slack-plugin.js +35 -0
  111. package/dist/kernel/plugins/telegram-plugin.d.ts +20 -0
  112. package/dist/kernel/plugins/telegram-plugin.js +122 -0
  113. package/dist/kernel/policies.d.ts +33 -0
  114. package/dist/kernel/policies.js +105 -0
  115. package/dist/kernel/provider-discovery.d.ts +41 -0
  116. package/dist/kernel/provider-discovery.js +179 -0
  117. package/dist/kernel/provider-manager.d.ts +38 -0
  118. package/dist/kernel/provider-manager.js +206 -0
  119. package/dist/kernel/provider-url.d.ts +18 -0
  120. package/dist/kernel/provider-url.js +45 -0
  121. package/dist/kernel/providers/gemini-provider.d.ts +43 -0
  122. package/dist/kernel/providers/gemini-provider.js +203 -0
  123. package/dist/kernel/providers/ollama-provider.d.ts +44 -0
  124. package/dist/kernel/providers/ollama-provider.js +241 -0
  125. package/dist/kernel/providers/openai-compatible-provider.d.ts +43 -0
  126. package/dist/kernel/providers/openai-compatible-provider.js +233 -0
  127. package/dist/kernel/recovery-manager.d.ts +38 -0
  128. package/dist/kernel/recovery-manager.js +156 -0
  129. package/dist/kernel/router.d.ts +44 -0
  130. package/dist/kernel/router.js +222 -0
  131. package/dist/kernel/sample-missions.d.ts +11 -0
  132. package/dist/kernel/sample-missions.js +132 -0
  133. package/dist/kernel/scheduler.d.ts +30 -0
  134. package/dist/kernel/scheduler.js +147 -0
  135. package/dist/kernel/sdk/harness-sdk.d.ts +37 -0
  136. package/dist/kernel/sdk/harness-sdk.js +48 -0
  137. package/dist/kernel/sdk/plugin-sdk.d.ts +27 -0
  138. package/dist/kernel/sdk/plugin-sdk.js +40 -0
  139. package/dist/kernel/search/search-controller.d.ts +32 -0
  140. package/dist/kernel/search/search-controller.js +141 -0
  141. package/dist/kernel/setup.d.ts +13 -0
  142. package/dist/kernel/setup.js +59 -0
  143. package/dist/kernel/types.d.ts +479 -0
  144. package/dist/kernel/types.js +7 -0
  145. package/dist/kernel/verification-engine.d.ts +33 -0
  146. package/dist/kernel/verification-engine.js +287 -0
  147. package/dist/kernel/worker-runtime.d.ts +66 -0
  148. package/dist/kernel/worker-runtime.js +261 -0
  149. package/dist/kernel/workers/browser-worker.d.ts +6 -0
  150. package/dist/kernel/workers/browser-worker.js +92 -0
  151. package/dist/kernel/workers/builtin-workers.d.ts +20 -0
  152. package/dist/kernel/workers/builtin-workers.js +120 -0
  153. package/dist/kernel/workers/research-worker.d.ts +5 -0
  154. package/dist/kernel/workers/research-worker.js +90 -0
  155. package/dist/prompt-ascii-video-animation.d.ts +2 -0
  156. package/dist/prompt-ascii-video-animation.js +243 -0
  157. package/package.json +43 -9
@@ -0,0 +1,287 @@
1
+ import { scoreFrontend } from './frontend-scoring.js';
2
+ const PIPELINE_ORDER = [
3
+ 'objective',
4
+ 'compile',
5
+ 'tests',
6
+ 'lint',
7
+ 'browser',
8
+ 'expected-output',
9
+ 'quality',
10
+ 'llm-judge',
11
+ ];
12
+ export class VerificationEngine {
13
+ deps;
14
+ verifiers = new Map();
15
+ constructor(deps) {
16
+ this.deps = deps;
17
+ this.registerBuiltins();
18
+ }
19
+ registerVerifier(verifier) {
20
+ const list = this.verifiers.get(verifier.stage) ?? [];
21
+ list.push(verifier);
22
+ this.verifiers.set(verifier.stage, list);
23
+ }
24
+ async verify(mission, task, policy) {
25
+ if (!task.output) {
26
+ return this.fail('No output to verify', []);
27
+ }
28
+ const ctx = {
29
+ mission,
30
+ task,
31
+ output: task.output,
32
+ strict: policy.verification?.strict ?? false,
33
+ workspace: mission.workspace,
34
+ };
35
+ const checks = [];
36
+ for (const stage of PIPELINE_ORDER) {
37
+ const list = this.verifiers.get(stage) ?? [];
38
+ for (const verifier of list) {
39
+ if (!verifier.enabled(ctx))
40
+ continue;
41
+ const start = Date.now();
42
+ try {
43
+ const check = await verifier.run(ctx);
44
+ check.durationMs = Date.now() - start;
45
+ checks.push(check);
46
+ this.deps.logger.info(`Verify[${stage}] ${task.id}: ${check.passed ? 'PASS' : 'FAIL'} — ${check.message}`);
47
+ if (!check.passed && ctx.strict && stage !== 'llm-judge') {
48
+ return this.finalize(checks, task);
49
+ }
50
+ }
51
+ catch (err) {
52
+ const message = err instanceof Error ? err.message : String(err);
53
+ checks.push({ name: stage, passed: false, message: `verifier error: ${message}`, durationMs: Date.now() - start });
54
+ }
55
+ }
56
+ }
57
+ return this.finalize(checks, task);
58
+ }
59
+ finalize(checks, task) {
60
+ const passed = checks.length > 0 && checks.every(c => c.passed);
61
+ const score = checks.length > 0
62
+ ? checks.filter(c => c.passed).length / checks.length
63
+ : 0;
64
+ const summary = passed
65
+ ? `All ${checks.length} check(s) passed`
66
+ : `${checks.filter(c => !c.passed).length}/${checks.length} check(s) failed`;
67
+ const result = { passed, checks, score, summary };
68
+ this.deps.eventBus.emit({
69
+ type: passed ? 'VERIFY_SUCCESS' : 'VERIFY_FAILED',
70
+ timestamp: Date.now(),
71
+ missionId: task.missionId,
72
+ taskId: task.id,
73
+ data: result,
74
+ });
75
+ return result;
76
+ }
77
+ fail(message, checks) {
78
+ return { passed: false, checks, score: 0, summary: message };
79
+ }
80
+ registerBuiltins() {
81
+ this.registerVerifier(objectiveVerifier());
82
+ this.registerVerifier(compileVerifier());
83
+ this.registerVerifier(testsVerifier());
84
+ this.registerVerifier(lintVerifier());
85
+ this.registerVerifier(browserVerifier());
86
+ this.registerVerifier(expectedOutputVerifier());
87
+ this.registerVerifier(frontendQualityVerifier(this.deps.logger));
88
+ this.registerVerifier(this.llmJudgeVerifier());
89
+ }
90
+ llmJudgeVerifier() {
91
+ const self = this;
92
+ return {
93
+ stage: 'llm-judge',
94
+ enabled: () => true,
95
+ async run(ctx) {
96
+ const verdict = await self.runLlmJudge(ctx);
97
+ return verdict;
98
+ },
99
+ };
100
+ }
101
+ async runLlmJudge(ctx) {
102
+ const provider = await this.pickJudgeProvider();
103
+ if (!provider) {
104
+ return {
105
+ name: 'llm-judge',
106
+ passed: true,
107
+ message: 'no judge provider available; skipping LLM judge',
108
+ };
109
+ }
110
+ const caps = await provider.capabilities();
111
+ const model = caps.models[0];
112
+ if (!model) {
113
+ return { name: 'llm-judge', passed: true, message: 'no judge model available; skipping' };
114
+ }
115
+ const request = {
116
+ model: model.id,
117
+ systemPrompt: 'You are a verification judge. Given a task, its goal, and the produced output, decide whether the output satisfies the goal. Reply with a single line: PASS or FAIL, followed by a one-sentence reason.',
118
+ prompt: `Task: ${ctx.task.description}\nGoal: ${ctx.mission.goal}\nOutput:\n${ctx.output.content}\n\nVerdict:`,
119
+ maxTokens: 256,
120
+ temperature: 0,
121
+ };
122
+ try {
123
+ const result = await provider.complete(request);
124
+ const text = result.content.trim();
125
+ const passed = /^(pass|yes|true|✓)/i.test(text);
126
+ return {
127
+ name: 'llm-judge',
128
+ passed,
129
+ message: text.split('\n')[0].slice(0, 200),
130
+ };
131
+ }
132
+ catch (err) {
133
+ const message = err instanceof Error ? err.message : String(err);
134
+ return { name: 'llm-judge', passed: false, message: `judge error: ${message}` };
135
+ }
136
+ }
137
+ async pickJudgeProvider() {
138
+ const healthy = await this.deps.providerManager.getHealthyProviders();
139
+ return healthy[0];
140
+ }
141
+ }
142
+ function objectiveVerifier() {
143
+ return {
144
+ stage: 'objective',
145
+ enabled: ctx => ctx.mission.objectives.length > 0,
146
+ async run(ctx) {
147
+ const objective = ctx.mission.objectives.find(o => o.id === ctx.task.objectiveId);
148
+ if (!objective) {
149
+ return { name: 'objective', passed: true, message: 'no objective bound' };
150
+ }
151
+ const hasContent = ctx.output.content.trim().length > 0;
152
+ const criteria = objective.acceptanceCriteria;
153
+ if (criteria.length === 0) {
154
+ return {
155
+ name: 'objective',
156
+ passed: hasContent,
157
+ message: hasContent ? 'output present' : 'empty output',
158
+ };
159
+ }
160
+ const passed = hasContent;
161
+ return {
162
+ name: 'objective',
163
+ passed,
164
+ message: passed
165
+ ? `${criteria.length} acceptance criteria, output present`
166
+ : 'empty output for objective',
167
+ };
168
+ },
169
+ };
170
+ }
171
+ function compileVerifier() {
172
+ return {
173
+ stage: 'compile',
174
+ enabled: ctx => hasArtifact(ctx.output, 'code') || hasArtifact(ctx.output, 'file'),
175
+ async run(ctx) {
176
+ const codeArtifacts = ctx.output.artifacts?.filter(a => a.type === 'code' || a.type === 'file') ?? [];
177
+ if (codeArtifacts.length === 0) {
178
+ return { name: 'compile', passed: true, message: 'no code artifacts' };
179
+ }
180
+ const hasSyntax = codeArtifacts.every(a => (a.content ?? '').trim().length > 0);
181
+ return {
182
+ name: 'compile',
183
+ passed: hasSyntax,
184
+ message: hasSyntax ? `${codeArtifacts.length} artifact(s) non-empty` : 'empty code artifact',
185
+ };
186
+ },
187
+ };
188
+ }
189
+ function testsVerifier() {
190
+ return {
191
+ stage: 'tests',
192
+ enabled: () => false,
193
+ async run() {
194
+ return { name: 'tests', passed: true, message: 'test runner not configured' };
195
+ },
196
+ };
197
+ }
198
+ function lintVerifier() {
199
+ return {
200
+ stage: 'lint',
201
+ enabled: () => false,
202
+ async run() {
203
+ return { name: 'lint', passed: true, message: 'linter not configured' };
204
+ },
205
+ };
206
+ }
207
+ function browserVerifier() {
208
+ return {
209
+ stage: 'browser',
210
+ enabled: () => false,
211
+ async run() {
212
+ return { name: 'browser', passed: true, message: 'browser tests not configured' };
213
+ },
214
+ };
215
+ }
216
+ function expectedOutputVerifier() {
217
+ return {
218
+ stage: 'expected-output',
219
+ enabled: ctx => {
220
+ const expected = ctx.task.input.context?.expectedOutput;
221
+ return typeof expected === 'string' && expected.length > 0;
222
+ },
223
+ async run(ctx) {
224
+ const expected = String(ctx.task.input.context?.expectedOutput ?? '');
225
+ const actual = ctx.output.content.trim();
226
+ const passed = actual.includes(expected.trim());
227
+ return {
228
+ name: 'expected-output',
229
+ passed,
230
+ message: passed ? 'matches expected output' : 'does not match expected output',
231
+ };
232
+ },
233
+ };
234
+ }
235
+ /**
236
+ * Frontend quality scorer. Enabled for frontend tasks (capability `frontend`
237
+ * or a `frontendAcceptance` request in context). Scores the produced code
238
+ * against the acceptance rubric; on a miss it writes targeted feedback back
239
+ * onto the task so the recovery loop's next attempt can fix the specific gaps.
240
+ */
241
+ function frontendQualityVerifier(logger) {
242
+ return {
243
+ stage: 'quality',
244
+ enabled(ctx) {
245
+ return (ctx.task.capabilities.includes('frontend') ||
246
+ ctx.task.input.context?.frontendAcceptance !== undefined);
247
+ },
248
+ async run(ctx) {
249
+ const acceptance = ctx.task.input.context?.frontendAcceptance;
250
+ const code = collectCode(ctx.output);
251
+ const result = scoreFrontend(code, acceptance);
252
+ const failing = result.items
253
+ .filter(i => i.earned < i.weight)
254
+ .sort((a, b) => (b.weight - b.earned) - (a.weight - a.earned))
255
+ .slice(0, 4)
256
+ .map(i => `${i.name} ${i.earned}/${i.weight}`)
257
+ .join(', ');
258
+ if (!result.passed) {
259
+ // Feed specific fixes into the task so the next attempt improves.
260
+ const context = (ctx.task.input.context ?? {});
261
+ context.qualityFeedback =
262
+ `Previous attempt scored ${result.score}/100 (need ${result.threshold}). ` +
263
+ `Fix these before anything else:\n- ${result.feedback.join('\n- ')}`;
264
+ ctx.task.input.context = context;
265
+ logger.warn(`Frontend quality ${ctx.task.id}: ${result.score}/${result.threshold} — weakest: ${failing}`);
266
+ }
267
+ return {
268
+ name: 'frontend-quality',
269
+ passed: result.passed,
270
+ message: `score ${result.score}/100 (bar ${result.threshold})` + (failing ? ` — weakest: ${failing}` : ''),
271
+ };
272
+ },
273
+ };
274
+ }
275
+ /** Concatenate the task output text and any code/file artifacts for scoring. */
276
+ function collectCode(output) {
277
+ const parts = [output.content ?? ''];
278
+ for (const a of output.artifacts ?? []) {
279
+ if ((a.type === 'code' || a.type === 'file' || a.type === 'document') && a.content) {
280
+ parts.push(a.content);
281
+ }
282
+ }
283
+ return parts.join('\n');
284
+ }
285
+ function hasArtifact(output, type) {
286
+ return output.artifacts?.some(a => a.type === type) ?? false;
287
+ }
@@ -0,0 +1,66 @@
1
+ import type { Capability, Logger, Policy, ProviderInterface, ProviderModel, Task, TaskOutput, WorkerDefinition, WorkerInstance, WorkerPreferences, WorkerType } from './types.js';
2
+ import type { EventBus } from './event-bus.js';
3
+ import type { CapabilityRouter } from './router.js';
4
+ import type { ProviderManager } from './provider-manager.js';
5
+ import type { PolicyEngine } from './policies.js';
6
+ export interface WorkerContext {
7
+ eventBus: EventBus;
8
+ router: CapabilityRouter;
9
+ providerManager: ProviderManager;
10
+ policies: PolicyEngine;
11
+ logger: Logger;
12
+ }
13
+ export type WorkerExecuteFn = (task: Task, provider: ProviderInterface, model: ProviderModel, ctx: WorkerContext) => Promise<TaskOutput>;
14
+ export interface WorkerHandler {
15
+ type: WorkerType;
16
+ capabilities: Capability[];
17
+ preferences: WorkerPreferences;
18
+ execute: WorkerExecuteFn;
19
+ verify?: (task: Task, output: TaskOutput) => Promise<boolean>;
20
+ }
21
+ export declare class WorkerRuntime {
22
+ private ctx;
23
+ private handlers;
24
+ private active;
25
+ private registered;
26
+ /** Default policy used when a mission doesn't specify one. */
27
+ private defaultPolicyName;
28
+ /** Per-worker-type model pins set by the host application. */
29
+ private workerPins;
30
+ constructor(ctx: WorkerContext);
31
+ /** Override the default policy used for routing. */
32
+ setDefaultPolicy(name: Policy['name']): void;
33
+ /** Set a model pin for a worker type — overrides the router when healthy. */
34
+ setWorkerPin(type: WorkerType, providerId: string, modelId: string): void;
35
+ /** Bulk-set worker pins. */
36
+ setWorkerPins(pins: Map<WorkerType, {
37
+ providerId: string;
38
+ modelId: string;
39
+ }>): void;
40
+ /** Clear all pins. */
41
+ clearWorkerPins(): void;
42
+ registerWorker(definition: WorkerDefinition): void;
43
+ registerHandler(handler: WorkerHandler): void;
44
+ getHandler(type: WorkerType): WorkerHandler | undefined;
45
+ listWorkerTypes(): WorkerType[];
46
+ listRegisteredDefinitions(): WorkerDefinition[];
47
+ matchWorkerType(capabilities: Capability[]): WorkerType | undefined;
48
+ private capabilityOverlap;
49
+ spawn(type: WorkerType, missionId: string, taskId: string, capabilities: Capability[]): Promise<WorkerInstance>;
50
+ execute(instance: WorkerInstance, task: Task, override?: {
51
+ providerId: string;
52
+ modelId: string;
53
+ }): Promise<TaskOutput>;
54
+ destroy(instance: WorkerInstance): Promise<void>;
55
+ getActive(): WorkerInstance[];
56
+ getInstance(id: string): WorkerInstance | undefined;
57
+ /**
58
+ * Build a routing plan: the chosen model plus the ranked alternatives the
59
+ * router considered. Honors worker pins (a hard lock), otherwise defers to
60
+ * the policy-driven capability router.
61
+ */
62
+ private planRoute;
63
+ private lockedPlan;
64
+ private emitRouting;
65
+ private emit;
66
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * OpenKernel — Worker Runtime
3
+ *
4
+ * Workers are disposable capability containers. Not personalities.
5
+ * Lifecycle: Spawn → Initialize → Execute → Verify → Publish → Destroy
6
+ */
7
+ import { nanoid } from 'nanoid';
8
+ /** Rough token estimate of a task's request (prompt + context), ~4 chars/token. */
9
+ function estimateRequestTokens(task) {
10
+ const ctx = task.input.context ? JSON.stringify(task.input.context) : '';
11
+ const chars = (task.input.prompt?.length ?? 0) + ctx.length;
12
+ return Math.ceil(chars / 4) + 500; // headroom for worker/system framing
13
+ }
14
+ export class WorkerRuntime {
15
+ ctx;
16
+ handlers = new Map();
17
+ active = new Map();
18
+ registered = new Map();
19
+ /** Default policy used when a mission doesn't specify one. */
20
+ defaultPolicyName = 'balanced';
21
+ /** Per-worker-type model pins set by the host application. */
22
+ workerPins = new Map();
23
+ constructor(ctx) {
24
+ this.ctx = ctx;
25
+ }
26
+ /** Override the default policy used for routing. */
27
+ setDefaultPolicy(name) {
28
+ this.defaultPolicyName = name;
29
+ }
30
+ /** Set a model pin for a worker type — overrides the router when healthy. */
31
+ setWorkerPin(type, providerId, modelId) {
32
+ this.workerPins.set(type, { providerId, modelId });
33
+ }
34
+ /** Bulk-set worker pins. */
35
+ setWorkerPins(pins) {
36
+ this.workerPins = new Map(pins);
37
+ }
38
+ /** Clear all pins. */
39
+ clearWorkerPins() {
40
+ this.workerPins.clear();
41
+ }
42
+ registerWorker(definition) {
43
+ this.registered.set(definition.type, definition);
44
+ this.ctx.logger.info(`Worker type registered: ${definition.type}`);
45
+ }
46
+ registerHandler(handler) {
47
+ this.handlers.set(handler.type, handler);
48
+ const def = {
49
+ type: handler.type,
50
+ capabilities: handler.capabilities,
51
+ preferences: handler.preferences,
52
+ };
53
+ this.registered.set(handler.type, def);
54
+ }
55
+ getHandler(type) {
56
+ return this.handlers.get(type);
57
+ }
58
+ listWorkerTypes() {
59
+ return [...this.handlers.keys()];
60
+ }
61
+ listRegisteredDefinitions() {
62
+ return [...this.registered.values()];
63
+ }
64
+ matchWorkerType(capabilities) {
65
+ let best;
66
+ let bestScore = 0;
67
+ for (const [type, def] of this.registered) {
68
+ const score = this.capabilityOverlap(def.capabilities, capabilities);
69
+ if (score > bestScore) {
70
+ bestScore = score;
71
+ best = type;
72
+ }
73
+ }
74
+ return best;
75
+ }
76
+ capabilityOverlap(workerCaps, taskCaps) {
77
+ if (taskCaps.length === 0)
78
+ return 0;
79
+ let matched = 0;
80
+ for (const cap of taskCaps) {
81
+ if (workerCaps.includes(cap))
82
+ matched++;
83
+ }
84
+ return matched / taskCaps.length;
85
+ }
86
+ async spawn(type, missionId, taskId, capabilities) {
87
+ const id = nanoid(10);
88
+ const def = this.registered.get(type);
89
+ const instance = {
90
+ id,
91
+ type,
92
+ capabilities: def?.capabilities ?? capabilities,
93
+ preferences: def?.preferences ?? {},
94
+ status: 'SPAWNED',
95
+ missionId,
96
+ taskId,
97
+ createdAt: Date.now(),
98
+ };
99
+ this.active.set(id, instance);
100
+ this.emit('WORKER_SPAWNED', missionId, { workerId: id, type }, taskId);
101
+ this.ctx.logger.info(`Worker spawned: ${id} (${type}) for mission ${missionId}`);
102
+ return instance;
103
+ }
104
+ async execute(instance, task, override) {
105
+ const handler = this.handlers.get(instance.type);
106
+ if (!handler) {
107
+ throw new Error(`No handler registered for worker type: ${instance.type}`);
108
+ }
109
+ instance.status = 'INITIALIZED';
110
+ const plan = override
111
+ ? this.lockedPlan(override, 'recovery override')
112
+ : await this.planRoute(task.capabilities, handler.preferences, instance.type, estimateRequestTokens(task));
113
+ // Surface the orchestrator's choice (ranked candidates + winner) so the
114
+ // CLI / TUI can animate and highlight it.
115
+ this.emitRouting(instance, task, plan);
116
+ const routing = plan.chosen;
117
+ if (!routing) {
118
+ throw new Error(`No provider available for capabilities: ${task.capabilities.join(', ')}`);
119
+ }
120
+ const provider = this.ctx.providerManager.get(routing.providerId);
121
+ if (!provider)
122
+ throw new Error(`Provider not found: ${routing.providerId}`);
123
+ const caps = await provider.capabilities();
124
+ const model = caps.models.find(m => m.id === routing.modelId);
125
+ if (!model)
126
+ throw new Error(`Model not found: ${routing.modelId}`);
127
+ instance.providerId = provider.info.id;
128
+ instance.modelId = model.id;
129
+ instance.status = 'EXECUTING';
130
+ instance.startedAt = Date.now();
131
+ task.assignedWorkerId = instance.id;
132
+ task.providerId = provider.info.id;
133
+ task.modelId = model.id;
134
+ task.status = 'RUNNING';
135
+ task.startedAt = Date.now();
136
+ this.emit('TASK_STARTED', instance.missionId, { taskId: task.id, workerId: instance.id, providerId: provider.info.id, modelId: model.id }, task.id, instance.id);
137
+ this.emit('MODEL_CHANGED', instance.missionId, { taskId: task.id, providerId: provider.info.id, modelId: model.id, previousModelId: undefined }, task.id, instance.id);
138
+ try {
139
+ const output = await handler.execute(task, provider, model, this.ctx);
140
+ this.ctx.providerManager.recordSuccess(provider.info.id);
141
+ instance.result = output;
142
+ instance.status = 'COMPLETED';
143
+ instance.completedAt = Date.now();
144
+ task.output = output;
145
+ task.status = 'COMPLETED';
146
+ task.completedAt = Date.now();
147
+ this.emit('TASK_COMPLETED', instance.missionId, { taskId: task.id, workerId: instance.id, output }, task.id, instance.id);
148
+ if (handler.verify) {
149
+ const verified = await handler.verify(task, output);
150
+ if (!verified) {
151
+ this.ctx.logger.warn(`Worker verification failed for task ${task.id}`);
152
+ this.emit('VERIFY_FAILED', instance.missionId, { taskId: task.id }, task.id, instance.id);
153
+ }
154
+ else {
155
+ this.emit('VERIFY_SUCCESS', instance.missionId, { taskId: task.id }, task.id, instance.id);
156
+ }
157
+ }
158
+ return output;
159
+ }
160
+ catch (err) {
161
+ const message = err instanceof Error ? err.message : String(err);
162
+ if (/429|rate.?limit|too many requests/i.test(message)) {
163
+ this.ctx.providerManager.record429(provider.info.id);
164
+ }
165
+ else {
166
+ this.ctx.providerManager.recordFailure(provider.info.id);
167
+ }
168
+ instance.error = message;
169
+ instance.status = 'FAILED';
170
+ task.status = 'FAILED';
171
+ task.error = message;
172
+ this.emit('TASK_FAILED', instance.missionId, { taskId: task.id, workerId: instance.id, error: message }, task.id, instance.id);
173
+ throw err;
174
+ }
175
+ }
176
+ async destroy(instance) {
177
+ instance.status = 'DESTROYED';
178
+ this.active.delete(instance.id);
179
+ this.emit('WORKER_EXITED', instance.missionId, { workerId: instance.id, type: instance.type }, instance.taskId, instance.id);
180
+ }
181
+ getActive() {
182
+ return [...this.active.values()];
183
+ }
184
+ getInstance(id) {
185
+ return this.active.get(id);
186
+ }
187
+ /**
188
+ * Build a routing plan: the chosen model plus the ranked alternatives the
189
+ * router considered. Honors worker pins (a hard lock), otherwise defers to
190
+ * the policy-driven capability router.
191
+ */
192
+ async planRoute(capabilities, preferences, workerType, estimatedRequestTokens) {
193
+ const providers = (await this.ctx.providerManager.getHealthyProviders()).map(p => p.info);
194
+ const models = await this.ctx.providerManager.getAvailableModels();
195
+ const policy = this.ctx.policies.get(this.defaultPolicyName);
196
+ const mode = policy.routing?.mode ?? 'balanced';
197
+ // 1. Honor a pinned model for this worker type if it's healthy → locked.
198
+ if (workerType) {
199
+ const pin = this.workerPins.get(workerType);
200
+ if (pin) {
201
+ const provider = providers.find(p => p.id === pin.providerId);
202
+ const model = models.find(m => m.id === pin.modelId && m.providerId === pin.providerId);
203
+ if (provider && model) {
204
+ return this.lockedPlan(pin, `pinned for ${workerType}`, provider.name, provider.type, mode);
205
+ }
206
+ this.ctx.logger.warn(`Worker pin for ${workerType} (${pin.providerId}/${pin.modelId}) not available; routing normally`);
207
+ }
208
+ }
209
+ // 2. Policy-driven router — rank all candidates, keep the top slice for display.
210
+ const ranked = this.ctx.router.rank({
211
+ capabilities,
212
+ preferences,
213
+ policy,
214
+ availableProviders: providers,
215
+ availableModels: models,
216
+ estimatedRequestTokens,
217
+ });
218
+ if (ranked.length === 0) {
219
+ return { chosen: null, candidates: [], locked: false, mode };
220
+ }
221
+ const candidates = ranked.slice(0, 6).map(r => ({
222
+ providerId: r.providerId,
223
+ modelId: r.modelId,
224
+ score: r.score,
225
+ reason: r.reason,
226
+ providerName: r.providerInfo.name,
227
+ providerType: r.providerInfo.type,
228
+ benchmarkScore: r.benchmarkScore,
229
+ }));
230
+ return { chosen: candidates[0], candidates, locked: false, mode };
231
+ }
232
+ lockedPlan(target, reason, providerName, providerType, mode = 'locked') {
233
+ const info = this.ctx.providerManager.get(target.providerId)?.info;
234
+ const candidate = {
235
+ providerId: target.providerId,
236
+ modelId: target.modelId,
237
+ score: 1000,
238
+ reason,
239
+ providerName: providerName ?? info?.name ?? target.providerId,
240
+ providerType: providerType ?? info?.type ?? 'cloud',
241
+ benchmarkScore: 0,
242
+ };
243
+ return { chosen: candidate, candidates: [candidate], locked: true, mode };
244
+ }
245
+ emitRouting(instance, task, plan) {
246
+ if (!plan.chosen)
247
+ return;
248
+ const data = {
249
+ taskId: task.id,
250
+ workerType: instance.type,
251
+ mode: plan.mode,
252
+ locked: plan.locked,
253
+ chosen: plan.chosen,
254
+ candidates: plan.candidates,
255
+ };
256
+ this.emit('ROUTING_DECISION', instance.missionId, data, task.id, instance.id);
257
+ }
258
+ emit(type, missionId, data, taskId, workerId) {
259
+ this.ctx.eventBus.emit({ type, timestamp: Date.now(), missionId, taskId, workerId, data });
260
+ }
261
+ }
@@ -0,0 +1,6 @@
1
+ import type { WorkerHandler } from '../worker-runtime.js';
2
+ import { BrowserController, htmlToMarkdown } from '../browser/browser-controller.js';
3
+ export declare function setBrowserController(controller: BrowserController): void;
4
+ export declare function setBrowserControllerOptions(options: Record<string, unknown>): void;
5
+ export declare function createBrowserWorker(): WorkerHandler;
6
+ export { htmlToMarkdown };