infinicode 1.0.0 → 2.1.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.
- package/.opencode/plugins/home-logo-animation.tsx +120 -0
- package/.opencode/plugins/routing-mode-display.tsx +135 -0
- package/.opencode/themes/infinibot-gold.json +222 -0
- package/.opencode/tui.json +8 -0
- package/README.md +564 -72
- package/dist/ascii-video-animation.d.ts +2 -0
- package/dist/ascii-video-animation.js +243 -0
- package/dist/cli.js +199 -50
- package/dist/commands/console.d.ts +6 -0
- package/dist/commands/console.js +111 -0
- package/dist/commands/kernel-setup.d.ts +3 -0
- package/dist/commands/kernel-setup.js +303 -0
- package/dist/commands/mcp.d.ts +14 -0
- package/dist/commands/mcp.js +100 -0
- package/dist/commands/mission.d.ts +16 -0
- package/dist/commands/mission.js +301 -0
- package/dist/commands/models.d.ts +3 -1
- package/dist/commands/models.js +111 -55
- package/dist/commands/providers.d.ts +6 -0
- package/dist/commands/providers.js +95 -0
- package/dist/commands/run.d.ts +2 -1
- package/dist/commands/run.js +349 -59
- package/dist/commands/serve.d.ts +20 -0
- package/dist/commands/serve.js +132 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +77 -44
- package/dist/commands/status.d.ts +2 -1
- package/dist/commands/status.js +46 -30
- package/dist/commands/workers.d.ts +5 -0
- package/dist/commands/workers.js +103 -0
- package/dist/kernel/autonomy/goal-loop.d.ts +49 -0
- package/dist/kernel/autonomy/goal-loop.js +113 -0
- package/dist/kernel/autonomy/index.d.ts +8 -0
- package/dist/kernel/autonomy/index.js +7 -0
- package/dist/kernel/browser/browser-controller.d.ts +57 -0
- package/dist/kernel/browser/browser-controller.js +175 -0
- package/dist/kernel/checkpoint-engine.d.ts +17 -0
- package/dist/kernel/checkpoint-engine.js +128 -0
- package/dist/kernel/command-system.d.ts +37 -0
- package/dist/kernel/command-system.js +239 -0
- package/dist/kernel/config-schema.d.ts +57 -0
- package/dist/kernel/config-schema.js +36 -0
- package/dist/kernel/event-bus.d.ts +19 -0
- package/dist/kernel/event-bus.js +65 -0
- package/dist/kernel/federation/auto-update.d.ts +36 -0
- package/dist/kernel/federation/auto-update.js +57 -0
- package/dist/kernel/federation/compute-router.d.ts +27 -0
- package/dist/kernel/federation/compute-router.js +44 -0
- package/dist/kernel/federation/config-sync.d.ts +33 -0
- package/dist/kernel/federation/config-sync.js +37 -0
- package/dist/kernel/federation/discovery.d.ts +11 -0
- package/dist/kernel/federation/discovery.js +44 -0
- package/dist/kernel/federation/event-bridge.d.ts +30 -0
- package/dist/kernel/federation/event-bridge.js +61 -0
- package/dist/kernel/federation/federation-plugin.d.ts +24 -0
- package/dist/kernel/federation/federation-plugin.js +35 -0
- package/dist/kernel/federation/federation.d.ts +135 -0
- package/dist/kernel/federation/federation.js +378 -0
- package/dist/kernel/federation/index.d.ts +33 -0
- package/dist/kernel/federation/index.js +24 -0
- package/dist/kernel/federation/lan-discovery.d.ts +43 -0
- package/dist/kernel/federation/lan-discovery.js +135 -0
- package/dist/kernel/federation/moltfed-adapter.d.ts +30 -0
- package/dist/kernel/federation/moltfed-adapter.js +52 -0
- package/dist/kernel/federation/moltfed-connector.d.ts +98 -0
- package/dist/kernel/federation/moltfed-connector.js +193 -0
- package/dist/kernel/federation/node-identity.d.ts +19 -0
- package/dist/kernel/federation/node-identity.js +86 -0
- package/dist/kernel/federation/peer-mesh.d.ts +46 -0
- package/dist/kernel/federation/peer-mesh.js +117 -0
- package/dist/kernel/federation/protocol.d.ts +20 -0
- package/dist/kernel/federation/protocol.js +61 -0
- package/dist/kernel/federation/role-context.d.ts +5 -0
- package/dist/kernel/federation/role-context.js +45 -0
- package/dist/kernel/federation/telemetry.d.ts +21 -0
- package/dist/kernel/federation/telemetry.js +138 -0
- package/dist/kernel/federation/transport-http.d.ts +44 -0
- package/dist/kernel/federation/transport-http.js +207 -0
- package/dist/kernel/federation/types.d.ts +212 -0
- package/dist/kernel/federation/types.js +3 -0
- package/dist/kernel/free-providers.d.ts +28 -0
- package/dist/kernel/free-providers.js +162 -0
- package/dist/kernel/frontend-scoring.d.ts +21 -0
- package/dist/kernel/frontend-scoring.js +125 -0
- package/dist/kernel/index.d.ts +63 -0
- package/dist/kernel/index.js +45 -0
- package/dist/kernel/kernel.d.ts +75 -0
- package/dist/kernel/kernel.js +223 -0
- package/dist/kernel/logger.d.ts +18 -0
- package/dist/kernel/logger.js +26 -0
- package/dist/kernel/mcp/index.d.ts +9 -0
- package/dist/kernel/mcp/index.js +8 -0
- package/dist/kernel/mcp/mcp-server.d.ts +27 -0
- package/dist/kernel/mcp/mcp-server.js +178 -0
- package/dist/kernel/mission-engine.d.ts +42 -0
- package/dist/kernel/mission-engine.js +160 -0
- package/dist/kernel/orchestrator.d.ts +51 -0
- package/dist/kernel/orchestrator.js +226 -0
- package/dist/kernel/plugin-manager.d.ts +28 -0
- package/dist/kernel/plugin-manager.js +76 -0
- package/dist/kernel/plugins/browser-plugin.d.ts +22 -0
- package/dist/kernel/plugins/browser-plugin.js +34 -0
- package/dist/kernel/plugins/dashboard-plugin.d.ts +17 -0
- package/dist/kernel/plugins/dashboard-plugin.js +72 -0
- package/dist/kernel/plugins/discord-plugin.d.ts +11 -0
- package/dist/kernel/plugins/discord-plugin.js +35 -0
- package/dist/kernel/plugins/metrics-plugin.d.ts +33 -0
- package/dist/kernel/plugins/metrics-plugin.js +86 -0
- package/dist/kernel/plugins/search-plugin.d.ts +17 -0
- package/dist/kernel/plugins/search-plugin.js +20 -0
- package/dist/kernel/plugins/slack-plugin.d.ts +11 -0
- package/dist/kernel/plugins/slack-plugin.js +35 -0
- package/dist/kernel/plugins/telegram-plugin.d.ts +20 -0
- package/dist/kernel/plugins/telegram-plugin.js +122 -0
- package/dist/kernel/policies.d.ts +33 -0
- package/dist/kernel/policies.js +105 -0
- package/dist/kernel/provider-discovery.d.ts +41 -0
- package/dist/kernel/provider-discovery.js +179 -0
- package/dist/kernel/provider-manager.d.ts +38 -0
- package/dist/kernel/provider-manager.js +206 -0
- package/dist/kernel/provider-url.d.ts +18 -0
- package/dist/kernel/provider-url.js +45 -0
- package/dist/kernel/providers/gemini-provider.d.ts +43 -0
- package/dist/kernel/providers/gemini-provider.js +203 -0
- package/dist/kernel/providers/ollama-provider.d.ts +44 -0
- package/dist/kernel/providers/ollama-provider.js +241 -0
- package/dist/kernel/providers/openai-compatible-provider.d.ts +43 -0
- package/dist/kernel/providers/openai-compatible-provider.js +233 -0
- package/dist/kernel/recovery-manager.d.ts +38 -0
- package/dist/kernel/recovery-manager.js +156 -0
- package/dist/kernel/router.d.ts +44 -0
- package/dist/kernel/router.js +222 -0
- package/dist/kernel/sample-missions.d.ts +11 -0
- package/dist/kernel/sample-missions.js +132 -0
- package/dist/kernel/scheduler.d.ts +30 -0
- package/dist/kernel/scheduler.js +147 -0
- package/dist/kernel/sdk/harness-sdk.d.ts +37 -0
- package/dist/kernel/sdk/harness-sdk.js +48 -0
- package/dist/kernel/sdk/plugin-sdk.d.ts +27 -0
- package/dist/kernel/sdk/plugin-sdk.js +40 -0
- package/dist/kernel/search/search-controller.d.ts +32 -0
- package/dist/kernel/search/search-controller.js +141 -0
- package/dist/kernel/setup.d.ts +13 -0
- package/dist/kernel/setup.js +59 -0
- package/dist/kernel/types.d.ts +479 -0
- package/dist/kernel/types.js +7 -0
- package/dist/kernel/verification-engine.d.ts +33 -0
- package/dist/kernel/verification-engine.js +287 -0
- package/dist/kernel/worker-runtime.d.ts +66 -0
- package/dist/kernel/worker-runtime.js +261 -0
- package/dist/kernel/workers/browser-worker.d.ts +6 -0
- package/dist/kernel/workers/browser-worker.js +92 -0
- package/dist/kernel/workers/builtin-workers.d.ts +20 -0
- package/dist/kernel/workers/builtin-workers.js +120 -0
- package/dist/kernel/workers/research-worker.d.ts +5 -0
- package/dist/kernel/workers/research-worker.js +90 -0
- package/dist/prompt-ascii-video-animation.d.ts +2 -0
- package/dist/prompt-ascii-video-animation.js +243 -0
- package/package.json +43 -9
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export class BrowserController {
|
|
2
|
+
playwright;
|
|
3
|
+
browser;
|
|
4
|
+
page;
|
|
5
|
+
mode = 'unavailable';
|
|
6
|
+
opts;
|
|
7
|
+
constructor(opts = {}) {
|
|
8
|
+
this.opts = { headless: true, browser: 'chromium', timeoutMs: 30_000, ...opts };
|
|
9
|
+
}
|
|
10
|
+
async init() {
|
|
11
|
+
try {
|
|
12
|
+
this.playwright = await import('playwright');
|
|
13
|
+
this.mode = 'playwright';
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
this.mode = 'fetch';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
getMode() {
|
|
20
|
+
return this.mode;
|
|
21
|
+
}
|
|
22
|
+
async runAction(action) {
|
|
23
|
+
if (this.mode === 'playwright' && this.playwright) {
|
|
24
|
+
return this.runPlaywright(action);
|
|
25
|
+
}
|
|
26
|
+
return this.runFetch(action);
|
|
27
|
+
}
|
|
28
|
+
async close() {
|
|
29
|
+
try {
|
|
30
|
+
await this.page?.close();
|
|
31
|
+
}
|
|
32
|
+
catch { /* best-effort */ }
|
|
33
|
+
try {
|
|
34
|
+
await this.browser?.close();
|
|
35
|
+
}
|
|
36
|
+
catch { /* best-effort */ }
|
|
37
|
+
this.page = undefined;
|
|
38
|
+
this.browser = undefined;
|
|
39
|
+
}
|
|
40
|
+
async ensurePage() {
|
|
41
|
+
if (!this.playwright)
|
|
42
|
+
throw new Error('playwright not loaded');
|
|
43
|
+
if (!this.browser) {
|
|
44
|
+
const launch = this.opts.browser === 'firefox' ? this.playwright.firefox.launch
|
|
45
|
+
: this.opts.browser === 'webkit' ? this.playwright.webkit.launch
|
|
46
|
+
: this.playwright.chromium.launch;
|
|
47
|
+
this.browser = await launch.call(this.playwright.chromium, { headless: this.opts.headless });
|
|
48
|
+
}
|
|
49
|
+
if (!this.page) {
|
|
50
|
+
const ctx = await this.browser.newContext();
|
|
51
|
+
this.page = await ctx.newPage();
|
|
52
|
+
this.page.setDefaultTimeout(this.opts.timeoutMs ?? 30_000);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async runPlaywright(action) {
|
|
56
|
+
await this.ensurePage();
|
|
57
|
+
const page = this.page;
|
|
58
|
+
try {
|
|
59
|
+
switch (action.type) {
|
|
60
|
+
case 'navigate': {
|
|
61
|
+
if (!action.url)
|
|
62
|
+
throw new Error('navigate requires url');
|
|
63
|
+
await page.goto(action.url, { waitUntil: 'domcontentloaded' });
|
|
64
|
+
return { ok: true, url: page.url(), title: await page.title() };
|
|
65
|
+
}
|
|
66
|
+
case 'click': {
|
|
67
|
+
if (!action.selector)
|
|
68
|
+
throw new Error('click requires selector');
|
|
69
|
+
await page.click(action.selector);
|
|
70
|
+
return { ok: true, url: page.url() };
|
|
71
|
+
}
|
|
72
|
+
case 'type': {
|
|
73
|
+
if (!action.selector || action.text === undefined)
|
|
74
|
+
throw new Error('type requires selector+text');
|
|
75
|
+
await page.fill(action.selector, action.text);
|
|
76
|
+
return { ok: true };
|
|
77
|
+
}
|
|
78
|
+
case 'extract': {
|
|
79
|
+
const text = await page.innerText('body');
|
|
80
|
+
return { ok: true, url: page.url(), title: await page.title(), text, markdown: htmlToMarkdown(await page.content()) };
|
|
81
|
+
}
|
|
82
|
+
case 'screenshot': {
|
|
83
|
+
const path = `${this.opts.screenshotDir ?? './.openkernel/screenshots'}/shot-${Date.now()}.png`;
|
|
84
|
+
await page.screenshot({ path, fullPage: true });
|
|
85
|
+
return { ok: true, screenshotPath: path };
|
|
86
|
+
}
|
|
87
|
+
case 'evaluate': {
|
|
88
|
+
if (!action.script)
|
|
89
|
+
throw new Error('evaluate requires script');
|
|
90
|
+
const result = await page.evaluate(action.script);
|
|
91
|
+
return { ok: true, text: typeof result === 'string' ? result : JSON.stringify(result) };
|
|
92
|
+
}
|
|
93
|
+
case 'wait': {
|
|
94
|
+
if (action.selector) {
|
|
95
|
+
await page.waitForSelector(action.selector, { timeout: action.ms ?? this.opts.timeoutMs });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await page.waitForTimeout(action.ms ?? 1000);
|
|
99
|
+
}
|
|
100
|
+
return { ok: true };
|
|
101
|
+
}
|
|
102
|
+
case 'scroll': {
|
|
103
|
+
await page.evaluate((y) => globalThis.scrollBy?.(0, y ?? 800), action.ms);
|
|
104
|
+
return { ok: true };
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
return { ok: false, error: `unknown action: ${action.type}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
112
|
+
return { ok: false, error: message };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async runFetch(action) {
|
|
116
|
+
if (action.type === 'navigate' && action.url) {
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(action.url, {
|
|
119
|
+
signal: AbortSignal.timeout(this.opts.timeoutMs ?? 30_000),
|
|
120
|
+
headers: { 'User-Agent': 'OpenKernel-Browser/1.0' },
|
|
121
|
+
});
|
|
122
|
+
if (!response.ok)
|
|
123
|
+
return { ok: false, url: action.url, error: `HTTP ${response.status}` };
|
|
124
|
+
const html = await response.text();
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
url: action.url,
|
|
128
|
+
title: extractTitle(html),
|
|
129
|
+
text: stripHtml(html),
|
|
130
|
+
html,
|
|
131
|
+
markdown: htmlToMarkdown(html),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
136
|
+
return { ok: false, error: message };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { ok: false, error: `fetch mode does not support action: ${action.type}` };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function htmlToMarkdown(html) {
|
|
143
|
+
return stripHtml(html)
|
|
144
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
145
|
+
.trim();
|
|
146
|
+
}
|
|
147
|
+
export function stripHtml(html) {
|
|
148
|
+
return html
|
|
149
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
150
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
151
|
+
.replace(/<[^>]+>/g, ' ')
|
|
152
|
+
.replace(/ /g, ' ')
|
|
153
|
+
.replace(/&/g, '&')
|
|
154
|
+
.replace(/</g, '<')
|
|
155
|
+
.replace(/>/g, '>')
|
|
156
|
+
.replace(/"/g, '"')
|
|
157
|
+
.replace(/'/g, "'")
|
|
158
|
+
.replace(/\s+/g, ' ')
|
|
159
|
+
.trim();
|
|
160
|
+
}
|
|
161
|
+
export function extractTitle(html) {
|
|
162
|
+
const match = html.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
163
|
+
return match ? match[1].trim() : '';
|
|
164
|
+
}
|
|
165
|
+
export function actionArtifacts(results) {
|
|
166
|
+
return results
|
|
167
|
+
.filter(r => r.text || r.screenshotPath)
|
|
168
|
+
.map((r, i) => ({
|
|
169
|
+
id: `browser-${i}`,
|
|
170
|
+
type: (r.screenshotPath ? 'image' : 'document'),
|
|
171
|
+
content: r.text ?? r.markdown,
|
|
172
|
+
path: r.screenshotPath,
|
|
173
|
+
mimeType: r.screenshotPath ? 'image/png' : 'text/markdown',
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Checkpoint, Mission, WorkerInstanceState } from './types.js';
|
|
2
|
+
import type { EventBus } from './event-bus.js';
|
|
3
|
+
import type { Logger } from './types.js';
|
|
4
|
+
export declare class CheckpointEngine {
|
|
5
|
+
private eventBus;
|
|
6
|
+
private logger;
|
|
7
|
+
private dir;
|
|
8
|
+
private maxPerMission;
|
|
9
|
+
constructor(eventBus: EventBus, logger: Logger, storageDir?: string);
|
|
10
|
+
setMaxPerMission(n: number): void;
|
|
11
|
+
save(mission: Mission, workerStates: WorkerInstanceState[], reason: Checkpoint['reason']): Promise<Checkpoint>;
|
|
12
|
+
private pruneMission;
|
|
13
|
+
load(missionId: string, checkpointId: string): Checkpoint | undefined;
|
|
14
|
+
latest(missionId: string): Checkpoint | undefined;
|
|
15
|
+
list(missionId: string): Checkpoint[];
|
|
16
|
+
purge(missionId: string): number;
|
|
17
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — Checkpoint Engine
|
|
3
|
+
*
|
|
4
|
+
* Stores: mission, objectives, current task, completed tasks, worker state,
|
|
5
|
+
* provider, memory, logs, artifacts.
|
|
6
|
+
* Supports: pause, resume, crash recovery, migration, long-running missions.
|
|
7
|
+
*/
|
|
8
|
+
import { nanoid } from 'nanoid';
|
|
9
|
+
import { mkdirSync, existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
export class CheckpointEngine {
|
|
12
|
+
eventBus;
|
|
13
|
+
logger;
|
|
14
|
+
dir;
|
|
15
|
+
maxPerMission = 20;
|
|
16
|
+
constructor(eventBus, logger, storageDir) {
|
|
17
|
+
this.eventBus = eventBus;
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
this.dir = resolve(storageDir ?? join(process.cwd(), '.openkernel', 'checkpoints'));
|
|
20
|
+
if (!existsSync(this.dir)) {
|
|
21
|
+
mkdirSync(this.dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
setMaxPerMission(n) {
|
|
25
|
+
this.maxPerMission = n;
|
|
26
|
+
}
|
|
27
|
+
async save(mission, workerStates, reason) {
|
|
28
|
+
// cap per-mission checkpoint files to prevent disk exhaustion
|
|
29
|
+
this.pruneMission(mission.id);
|
|
30
|
+
const id = nanoid(12);
|
|
31
|
+
const checkpoint = {
|
|
32
|
+
id,
|
|
33
|
+
missionId: mission.id,
|
|
34
|
+
mission: structuredClone(mission),
|
|
35
|
+
workerStates,
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
reason,
|
|
38
|
+
};
|
|
39
|
+
const file = join(this.dir, `${mission.id}_${id}.json`);
|
|
40
|
+
writeFileSync(file, JSON.stringify(checkpoint, null, 2), 'utf-8');
|
|
41
|
+
mission.checkpoints.push(id);
|
|
42
|
+
this.eventBus.emit({
|
|
43
|
+
type: 'CHECKPOINT',
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
missionId: mission.id,
|
|
46
|
+
data: { checkpointId: id, reason, file },
|
|
47
|
+
});
|
|
48
|
+
this.logger.info(`Checkpoint saved: ${id} (reason: ${reason})`);
|
|
49
|
+
return checkpoint;
|
|
50
|
+
}
|
|
51
|
+
pruneMission(missionId) {
|
|
52
|
+
if (!existsSync(this.dir))
|
|
53
|
+
return;
|
|
54
|
+
const prefix = `${missionId}_`;
|
|
55
|
+
const files = readdirSync(this.dir)
|
|
56
|
+
.filter(f => f.startsWith(prefix) && f.endsWith('.json'))
|
|
57
|
+
.sort();
|
|
58
|
+
while (files.length >= this.maxPerMission) {
|
|
59
|
+
const oldest = files.shift();
|
|
60
|
+
if (!oldest)
|
|
61
|
+
break;
|
|
62
|
+
try {
|
|
63
|
+
unlinkSync(join(this.dir, oldest));
|
|
64
|
+
}
|
|
65
|
+
catch { /* best-effort */ }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
load(missionId, checkpointId) {
|
|
69
|
+
const file = join(this.dir, `${missionId}_${checkpointId}.json`);
|
|
70
|
+
if (!existsSync(file))
|
|
71
|
+
return undefined;
|
|
72
|
+
try {
|
|
73
|
+
const raw = readFileSync(file, 'utf-8');
|
|
74
|
+
return JSON.parse(raw);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
this.logger.warn(`Failed to load checkpoint ${checkpointId}: ${err}`);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
latest(missionId) {
|
|
82
|
+
if (!existsSync(this.dir))
|
|
83
|
+
return undefined;
|
|
84
|
+
const prefix = `${missionId}_`;
|
|
85
|
+
const files = readdirSync(this.dir)
|
|
86
|
+
.filter(f => f.startsWith(prefix) && f.endsWith('.json'))
|
|
87
|
+
.sort();
|
|
88
|
+
if (files.length === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
const latestFile = files[files.length - 1];
|
|
91
|
+
try {
|
|
92
|
+
const raw = readFileSync(join(this.dir, latestFile), 'utf-8');
|
|
93
|
+
return JSON.parse(raw);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
list(missionId) {
|
|
100
|
+
if (!existsSync(this.dir))
|
|
101
|
+
return [];
|
|
102
|
+
const prefix = `${missionId}_`;
|
|
103
|
+
const files = readdirSync(this.dir)
|
|
104
|
+
.filter(f => f.startsWith(prefix) && f.endsWith('.json'))
|
|
105
|
+
.sort();
|
|
106
|
+
const checkpoints = [];
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
try {
|
|
109
|
+
const raw = readFileSync(join(this.dir, file), 'utf-8');
|
|
110
|
+
checkpoints.push(JSON.parse(raw));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// skip corrupt
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return checkpoints;
|
|
117
|
+
}
|
|
118
|
+
purge(missionId) {
|
|
119
|
+
if (!existsSync(this.dir))
|
|
120
|
+
return 0;
|
|
121
|
+
const prefix = `${missionId}_`;
|
|
122
|
+
const files = readdirSync(this.dir).filter(f => f.startsWith(prefix));
|
|
123
|
+
for (const f of files) {
|
|
124
|
+
unlinkSync(join(this.dir, f));
|
|
125
|
+
}
|
|
126
|
+
return files.length;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenKernel — Command System
|
|
3
|
+
*
|
|
4
|
+
* The canonical OpenKernel slash-command surface from the spec:
|
|
5
|
+
* /status /workers /logs /pause /resume /retry /models /providers
|
|
6
|
+
* /checkpoints /goal (+ /missions /cancel /help)
|
|
7
|
+
*
|
|
8
|
+
* One registry, reused everywhere: the CLI console REPL and notification
|
|
9
|
+
* plugins (Telegram/Discord/Slack) all dispatch through `kernel.command(...)`,
|
|
10
|
+
* so behavior is defined once. Each command returns a renderable `CommandResult`
|
|
11
|
+
* (text) so any surface can display it.
|
|
12
|
+
*/
|
|
13
|
+
import type { Kernel } from './kernel.js';
|
|
14
|
+
import type { CommandResult } from './types.js';
|
|
15
|
+
export interface CommandContext {
|
|
16
|
+
kernel: Kernel;
|
|
17
|
+
args: string[];
|
|
18
|
+
raw: string;
|
|
19
|
+
}
|
|
20
|
+
export interface KernelCommand {
|
|
21
|
+
name: string;
|
|
22
|
+
aliases?: string[];
|
|
23
|
+
description: string;
|
|
24
|
+
usage?: string;
|
|
25
|
+
run(ctx: CommandContext): Promise<CommandResult> | CommandResult;
|
|
26
|
+
}
|
|
27
|
+
export declare const DEFAULT_COMMANDS: KernelCommand[];
|
|
28
|
+
export declare class KernelCommandRegistry {
|
|
29
|
+
private kernel;
|
|
30
|
+
private commands;
|
|
31
|
+
private aliases;
|
|
32
|
+
constructor(kernel: Kernel);
|
|
33
|
+
register(cmd: KernelCommand): void;
|
|
34
|
+
list(): KernelCommand[];
|
|
35
|
+
resolve(name: string): KernelCommand | undefined;
|
|
36
|
+
execute(input: string): Promise<CommandResult>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
const ok = (text, data) => ({ ok: true, text, data });
|
|
2
|
+
const err = (text) => ({ ok: false, text });
|
|
3
|
+
/** Resolve a mission by id, "latest", or an optional status filter. */
|
|
4
|
+
function pickMission(kernel, arg, status) {
|
|
5
|
+
const missions = kernel.listMissions();
|
|
6
|
+
if (arg && arg !== 'latest')
|
|
7
|
+
return kernel.getMission(arg);
|
|
8
|
+
const pool = status ? missions.filter(m => m.status === status) : missions;
|
|
9
|
+
return pool[pool.length - 1];
|
|
10
|
+
}
|
|
11
|
+
export const DEFAULT_COMMANDS = [
|
|
12
|
+
{
|
|
13
|
+
name: 'help',
|
|
14
|
+
aliases: ['?', 'commands'],
|
|
15
|
+
description: 'List available commands',
|
|
16
|
+
run({ kernel }) {
|
|
17
|
+
const lines = kernel.commands.list().map(c => {
|
|
18
|
+
const alias = c.aliases?.length ? ` (/${c.aliases.join(', /')})` : '';
|
|
19
|
+
return ` /${c.name}${alias} — ${c.description}${c.usage ? ` [${c.usage}]` : ''}`;
|
|
20
|
+
});
|
|
21
|
+
return ok(`OpenKernel commands:\n${lines.join('\n')}`);
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'status',
|
|
26
|
+
description: 'Overall kernel status: providers, workers, missions',
|
|
27
|
+
run({ kernel }) {
|
|
28
|
+
const infos = kernel.providerManager.listInfos();
|
|
29
|
+
const healthy = infos.filter(p => p.healthy).length;
|
|
30
|
+
const active = kernel.workerRuntime.getActive().length;
|
|
31
|
+
const missions = kernel.listMissions();
|
|
32
|
+
const recent = missions.slice(-5).map(m => ` ${m.id} ${m.status} — ${m.name} (${m.tasks.filter(t => t.status === 'COMPLETED').length}/${m.tasks.length})`);
|
|
33
|
+
const head = `Providers ${healthy}/${infos.length} healthy · Workers active ${active} · Missions ${missions.length}`;
|
|
34
|
+
return ok(recent.length ? `${head}\n${recent.join('\n')}` : head);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'providers',
|
|
39
|
+
aliases: ['p'],
|
|
40
|
+
description: 'Provider health, latency, and per-request token limits',
|
|
41
|
+
run({ kernel }) {
|
|
42
|
+
const infos = kernel.providerManager.listInfos();
|
|
43
|
+
if (!infos.length)
|
|
44
|
+
return ok('No providers registered.');
|
|
45
|
+
const lines = infos.map(p => {
|
|
46
|
+
const tpm = p.maxRequestTokens ? `tpm=${p.maxRequestTokens}` : '';
|
|
47
|
+
const e = p.error ? ` err=${p.error.replace(/\s+/g, ' ').slice(0, 60)}` : '';
|
|
48
|
+
return `${p.healthy ? '●' : '○'} ${p.id.padEnd(10)} lat=${p.latencyMs ?? '?'}ms ${tpm}${e}`;
|
|
49
|
+
});
|
|
50
|
+
return ok(lines.join('\n'));
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'models',
|
|
55
|
+
aliases: ['m'],
|
|
56
|
+
description: 'Available models across providers (optional filter)',
|
|
57
|
+
usage: '/models [filter]',
|
|
58
|
+
async run({ kernel, args }) {
|
|
59
|
+
const filter = args.join(' ').toLowerCase();
|
|
60
|
+
const models = await kernel.providerManager.getAvailableModels();
|
|
61
|
+
const filtered = filter ? models.filter(m => `${m.providerId}/${m.id}`.toLowerCase().includes(filter)) : models;
|
|
62
|
+
if (!filtered.length)
|
|
63
|
+
return ok(filter ? `No models match "${filter}".` : 'No models available.');
|
|
64
|
+
const byProv = {};
|
|
65
|
+
for (const m of filtered)
|
|
66
|
+
(byProv[m.providerId] ??= []).push(m.id);
|
|
67
|
+
const lines = Object.entries(byProv).map(([p, ids]) => `${p} (${ids.length}): ${ids.slice(0, 12).join(', ')}${ids.length > 12 ? ' …' : ''}`);
|
|
68
|
+
return ok(`${filtered.length} model(s)\n${lines.join('\n')}`);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'workers',
|
|
73
|
+
aliases: ['w'],
|
|
74
|
+
description: 'Registered worker types and active worker instances',
|
|
75
|
+
run({ kernel }) {
|
|
76
|
+
const defs = kernel.workerRuntime.listRegisteredDefinitions();
|
|
77
|
+
const active = kernel.workerRuntime.getActive();
|
|
78
|
+
const activeStr = active.map(w => `${w.type}:${w.status}`).join(', ') || 'none';
|
|
79
|
+
return ok(`Registered (${defs.length}): ${defs.map(d => d.type).join(', ')}\nActive (${active.length}): ${activeStr}`);
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'logs',
|
|
84
|
+
aliases: ['log'],
|
|
85
|
+
description: 'Recent event-bus activity',
|
|
86
|
+
usage: '/logs [count]',
|
|
87
|
+
run({ kernel, args }) {
|
|
88
|
+
const n = Math.max(1, Math.min(100, parseInt(args[0] ?? '15', 10) || 15));
|
|
89
|
+
const hist = kernel.getEventHistory().slice(-n);
|
|
90
|
+
if (!hist.length)
|
|
91
|
+
return ok('No events yet.');
|
|
92
|
+
const lines = hist.map(e => {
|
|
93
|
+
const t = new Date(e.timestamp).toISOString().slice(11, 19);
|
|
94
|
+
return `${t} ${e.type}${e.missionId ? ' ' + e.missionId : ''}${e.taskId ? '/' + e.taskId : ''}`;
|
|
95
|
+
});
|
|
96
|
+
return ok(lines.join('\n'));
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'missions',
|
|
101
|
+
aliases: ['ls'],
|
|
102
|
+
description: 'List missions in this session',
|
|
103
|
+
run({ kernel }) {
|
|
104
|
+
const missions = kernel.listMissions();
|
|
105
|
+
if (!missions.length)
|
|
106
|
+
return ok('No missions this session.');
|
|
107
|
+
return ok(missions.map(m => `${m.id} ${m.status} — ${m.name} (${m.tasks.length} tasks)`).join('\n'));
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'checkpoints',
|
|
112
|
+
aliases: ['ckpt'],
|
|
113
|
+
description: 'Checkpoints per mission',
|
|
114
|
+
usage: '/checkpoints [missionId]',
|
|
115
|
+
run({ kernel, args }) {
|
|
116
|
+
const missions = args[0] ? [kernel.getMission(args[0])].filter(Boolean) : kernel.listMissions();
|
|
117
|
+
if (!missions.length)
|
|
118
|
+
return ok('No missions.');
|
|
119
|
+
return ok(missions.map(m => `${m.id}: ${m.checkpoints.length} checkpoint(s)`).join('\n'));
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'pause',
|
|
124
|
+
description: 'Pause a running mission',
|
|
125
|
+
usage: '/pause [missionId|latest]',
|
|
126
|
+
async run({ kernel, args }) {
|
|
127
|
+
const m = pickMission(kernel, args[0], 'RUNNING');
|
|
128
|
+
if (!m)
|
|
129
|
+
return err('No running mission to pause.');
|
|
130
|
+
await kernel.pause(m.id);
|
|
131
|
+
return ok(`Paused ${m.id}.`);
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'resume',
|
|
136
|
+
description: 'Resume a paused mission',
|
|
137
|
+
usage: '/resume [missionId|latest]',
|
|
138
|
+
async run({ kernel, args }) {
|
|
139
|
+
const m = pickMission(kernel, args[0], 'PAUSED');
|
|
140
|
+
if (!m)
|
|
141
|
+
return err('No paused mission to resume.');
|
|
142
|
+
const resumed = await kernel.resume(m.id);
|
|
143
|
+
return ok(`Resumed ${m.id} — now ${resumed.status}.`);
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'cancel',
|
|
148
|
+
description: 'Cancel a mission',
|
|
149
|
+
usage: '/cancel <missionId|latest>',
|
|
150
|
+
async run({ kernel, args }) {
|
|
151
|
+
const m = pickMission(kernel, args[0]);
|
|
152
|
+
if (!m)
|
|
153
|
+
return err('No mission to cancel.');
|
|
154
|
+
await kernel.cancel(m.id);
|
|
155
|
+
return ok(`Cancelled ${m.id}.`);
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'retry',
|
|
160
|
+
description: 'Re-run a mission\'s failed/skipped tasks',
|
|
161
|
+
usage: '/retry [missionId|latest]',
|
|
162
|
+
async run({ kernel, args }) {
|
|
163
|
+
const m = pickMission(kernel, args[0]);
|
|
164
|
+
if (!m)
|
|
165
|
+
return err('No mission to retry.');
|
|
166
|
+
const before = m.tasks.filter(t => t.status === 'FAILED' || t.status === 'SKIPPED').length;
|
|
167
|
+
if (before === 0)
|
|
168
|
+
return ok(`Mission ${m.id} has no failed tasks.`);
|
|
169
|
+
const result = await kernel.retry(m.id);
|
|
170
|
+
const done = result.tasks.filter(t => t.status === 'COMPLETED').length;
|
|
171
|
+
return ok(`Retried ${before} task(s) on ${m.id} — now ${result.status} (${done}/${result.tasks.length} complete).`);
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'goal',
|
|
176
|
+
aliases: ['g'],
|
|
177
|
+
description: 'Start and run a new mission from a goal',
|
|
178
|
+
usage: '/goal <text>',
|
|
179
|
+
async run({ kernel, args }) {
|
|
180
|
+
const goal = args.join(' ').trim();
|
|
181
|
+
if (!goal)
|
|
182
|
+
return err('Usage: /goal <text>');
|
|
183
|
+
const mission = await kernel.execute({
|
|
184
|
+
description: goal,
|
|
185
|
+
goal,
|
|
186
|
+
tasks: [{ description: goal, capabilities: ['reasoning', 'coding'], input: { prompt: goal } }],
|
|
187
|
+
});
|
|
188
|
+
const done = mission.tasks.filter(t => t.status === 'COMPLETED').length;
|
|
189
|
+
const preview = mission.tasks
|
|
190
|
+
.map(t => t.output?.content?.slice(0, 400))
|
|
191
|
+
.filter(Boolean)
|
|
192
|
+
.join('\n---\n');
|
|
193
|
+
return {
|
|
194
|
+
ok: mission.status === 'COMPLETED',
|
|
195
|
+
text: `Mission ${mission.id} ${mission.status} (${done}/${mission.tasks.length})${preview ? '\n' + preview : ''}`,
|
|
196
|
+
data: { missionId: mission.id },
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
export class KernelCommandRegistry {
|
|
202
|
+
kernel;
|
|
203
|
+
commands = new Map();
|
|
204
|
+
aliases = new Map();
|
|
205
|
+
constructor(kernel) {
|
|
206
|
+
this.kernel = kernel;
|
|
207
|
+
for (const cmd of DEFAULT_COMMANDS)
|
|
208
|
+
this.register(cmd);
|
|
209
|
+
}
|
|
210
|
+
register(cmd) {
|
|
211
|
+
this.commands.set(cmd.name, cmd);
|
|
212
|
+
for (const a of cmd.aliases ?? [])
|
|
213
|
+
this.aliases.set(a, cmd.name);
|
|
214
|
+
}
|
|
215
|
+
list() {
|
|
216
|
+
return [...this.commands.values()];
|
|
217
|
+
}
|
|
218
|
+
resolve(name) {
|
|
219
|
+
const key = name.toLowerCase();
|
|
220
|
+
return this.commands.get(key) ?? this.commands.get(this.aliases.get(key) ?? '');
|
|
221
|
+
}
|
|
222
|
+
async execute(input) {
|
|
223
|
+
const trimmed = (input ?? '').trim().replace(/^\//, '').trim();
|
|
224
|
+
if (!trimmed)
|
|
225
|
+
return this.resolve('help').run({ kernel: this.kernel, args: [], raw: input });
|
|
226
|
+
const parts = trimmed.split(/\s+/);
|
|
227
|
+
const name = parts[0];
|
|
228
|
+
const args = parts.slice(1);
|
|
229
|
+
const cmd = this.resolve(name);
|
|
230
|
+
if (!cmd)
|
|
231
|
+
return err(`Unknown command: /${name}. Try /help.`);
|
|
232
|
+
try {
|
|
233
|
+
return await cmd.run({ kernel: this.kernel, args, raw: input });
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
return err(`/${name} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infinicode — Extended config schema for OpenKernel.
|
|
3
|
+
*
|
|
4
|
+
* Adds worker model pinning + cloud provider credentials on top of the
|
|
5
|
+
* original Ollama master config.
|
|
6
|
+
*/
|
|
7
|
+
import type { WorkerType } from '../kernel/types.js';
|
|
8
|
+
export interface CloudProviderConfig {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
baseURL: string;
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
headerName?: string;
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface WorkerModelPref {
|
|
17
|
+
/** Provider+model pair, e.g. "ollama/qwen2.5-coder:14b" or "groq/llama-3.3-70b-versatile". */
|
|
18
|
+
providerId: string;
|
|
19
|
+
modelId: string;
|
|
20
|
+
}
|
|
21
|
+
/** Device-mesh / federation settings. */
|
|
22
|
+
export interface FederationConfig {
|
|
23
|
+
/** Enable the mesh on `infinicode serve`. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
/** This node's role in the mesh. */
|
|
26
|
+
role?: 'hub' | 'peer' | 'satellite' | 'relay';
|
|
27
|
+
/** Friendly node name (defaults to hostname). */
|
|
28
|
+
displayName?: string;
|
|
29
|
+
/** Mesh listen port (default 47913). */
|
|
30
|
+
port?: number;
|
|
31
|
+
host?: string;
|
|
32
|
+
/** Shared bearer token peers must present (app-layer auth). */
|
|
33
|
+
token?: string;
|
|
34
|
+
/** Base URLs of peers to connect to on start. */
|
|
35
|
+
seeds?: string[];
|
|
36
|
+
/** Seconds between peer heartbeats (default 30). */
|
|
37
|
+
heartbeatSec?: number;
|
|
38
|
+
/** Accept + apply auto-updates announced by a hub/relay. */
|
|
39
|
+
autoUpdate?: boolean;
|
|
40
|
+
/** Zero-config LAN discovery: broadcast + auto-connect peers on this subnet. */
|
|
41
|
+
lan?: boolean;
|
|
42
|
+
/** UDP discovery port for LAN discovery (default 47915). */
|
|
43
|
+
lanPort?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface InfinicodeConfig {
|
|
46
|
+
masterUrl: string;
|
|
47
|
+
tailscaleMasterUrl?: string;
|
|
48
|
+
defaultModel: string;
|
|
49
|
+
theme: string;
|
|
50
|
+
policy?: string;
|
|
51
|
+
workerModels?: Partial<Record<WorkerType, WorkerModelPref>>;
|
|
52
|
+
cloudProviders?: CloudProviderConfig[];
|
|
53
|
+
federation?: FederationConfig;
|
|
54
|
+
}
|
|
55
|
+
export declare const DEFAULT_CONFIG: InfinicodeConfig;
|
|
56
|
+
export declare const WORKER_TYPES_ORDER: WorkerType[];
|
|
57
|
+
export declare const WORKER_DESCRIPTIONS: Record<WorkerType, string>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const DEFAULT_CONFIG = {
|
|
2
|
+
masterUrl: 'http://localhost:11434',
|
|
3
|
+
defaultModel: 'qwen2.5-coder:14b',
|
|
4
|
+
theme: 'infini',
|
|
5
|
+
// Auto-routes across all providers by capability + benchmark. Use 'offline'
|
|
6
|
+
// to lock to local, or 'quality'/'fastest'/'free-first' to bias the router.
|
|
7
|
+
policy: 'balanced',
|
|
8
|
+
workerModels: {},
|
|
9
|
+
cloudProviders: [],
|
|
10
|
+
};
|
|
11
|
+
export const WORKER_TYPES_ORDER = [
|
|
12
|
+
'coding',
|
|
13
|
+
'frontend',
|
|
14
|
+
'research',
|
|
15
|
+
'architecture',
|
|
16
|
+
'review',
|
|
17
|
+
'documentation',
|
|
18
|
+
'translation',
|
|
19
|
+
'terminal',
|
|
20
|
+
'verification',
|
|
21
|
+
'vision',
|
|
22
|
+
'browser',
|
|
23
|
+
];
|
|
24
|
+
export const WORKER_DESCRIPTIONS = {
|
|
25
|
+
coding: 'Writes code — functions, implementations, fixes',
|
|
26
|
+
frontend: 'Builds production-grade UI (HTML/CSS/vanilla JS) — design systems, components, animation',
|
|
27
|
+
research: 'Searches, crawls, summarizes with citations',
|
|
28
|
+
architecture: 'Designs structure, modules, data flow, tradeoffs',
|
|
29
|
+
review: 'Critiques code, design, or plans',
|
|
30
|
+
documentation: 'Produces docs in Markdown',
|
|
31
|
+
translation: 'Translates content across languages',
|
|
32
|
+
terminal: 'Produces shell commands',
|
|
33
|
+
verification: 'Verifies tasks meet acceptance criteria',
|
|
34
|
+
vision: 'Analyzes images, OCR, visual content',
|
|
35
|
+
browser: 'Drives a browser via Playwright',
|
|
36
|
+
};
|