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.
- 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 +527 -75
- package/dist/ascii-video-animation.d.ts +2 -0
- package/dist/ascii-video-animation.js +243 -0
- package/dist/cli.js +195 -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 +12 -0
- package/dist/commands/mcp.js +96 -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 +18 -0
- package/dist/commands/serve.js +127 -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 +53 -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 +126 -0
- package/dist/kernel/federation/federation.js +335 -0
- package/dist/kernel/federation/index.d.ts +31 -0
- package/dist/kernel/federation/index.js +23 -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
package/dist/commands/run.js
CHANGED
|
@@ -1,83 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infinicode run — launch the infinicode TUI with the full kernel provider
|
|
3
|
+
* pool injected.
|
|
4
|
+
*
|
|
5
|
+
* The TUI is infinicode's SolidJS terminal UI (themes, markdown rendering,
|
|
6
|
+
* diff view, plugin slots, the infinicode gold theme + animated logo).
|
|
7
|
+
* infinicode builds the provider config from the saved kernel config
|
|
8
|
+
* (Ollama master + all cloud providers: Groq, OpenRouter, GitHub, NVIDIA,
|
|
9
|
+
* HF, Gemini) and injects it via OPENCODE_CONFIG_CONTENT so the TUI can
|
|
10
|
+
* use every provider — not just a single Ollama master.
|
|
11
|
+
*
|
|
12
|
+
* The kernel's router/recovery/verification do not run inside the TUI;
|
|
13
|
+
* the TUI is the Harness (prompts, workflows, personas). The kernel owns
|
|
14
|
+
* execution. For headless mission execution with full kernel routing,
|
|
15
|
+
* use `infinicode mission run`.
|
|
16
|
+
*/
|
|
1
17
|
import chalk from 'chalk';
|
|
2
18
|
import ora from 'ora';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
19
|
+
import { existsSync } from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { ASCII_VIDEO_FPS, ASCII_VIDEO_FRAMES } from '../ascii-video-animation.js';
|
|
23
|
+
import { getProviderPreset } from '../kernel/free-providers.js';
|
|
24
|
+
import { buildKernelFromConfig } from '../kernel/setup.js';
|
|
25
|
+
import { SilentLogger } from '../kernel/index.js';
|
|
26
|
+
import { openAICompatBaseURL } from '../kernel/provider-url.js';
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const PROMPT_ANIMATION_MAX_FRAMES = Math.min(ASCII_VIDEO_FRAMES.length, ASCII_VIDEO_FPS * 3);
|
|
29
|
+
function resolveInfinicodeCommand() {
|
|
30
|
+
const rootDir = resolve(__dirname, '..', '..');
|
|
31
|
+
const bundled = join(rootDir, 'bin', 'infinicode-tui.exe');
|
|
32
|
+
return existsSync(bundled) ? bundled : 'infinicode';
|
|
33
|
+
}
|
|
34
|
+
function fitPromptFrameToTerminal(frame) {
|
|
35
|
+
const columns = Math.max(1, (process.stdout.columns ?? 120) - 1);
|
|
36
|
+
const rows = Math.max(1, Math.min((process.stdout.rows ?? 40) - 8, 24));
|
|
37
|
+
const lines = frame.split('\n');
|
|
38
|
+
const maxWidth = Math.max(...lines.map(line => line.length), 0);
|
|
39
|
+
const startColumn = Math.max(0, Math.floor((maxWidth - columns) / 2));
|
|
40
|
+
const startRow = Math.max(0, Math.floor((lines.length - rows) / 2));
|
|
41
|
+
return lines
|
|
42
|
+
.slice(startRow, startRow + rows)
|
|
43
|
+
.map(line => line.slice(startColumn, startColumn + columns).padEnd(columns, ' '))
|
|
44
|
+
.join('\n');
|
|
45
|
+
}
|
|
46
|
+
function colorPromptFrame(frame) {
|
|
47
|
+
return chalk.hex('#fbbf24')(fitPromptFrameToTerminal(frame));
|
|
48
|
+
}
|
|
49
|
+
async function renderPromptAnimation() {
|
|
50
|
+
const firstFrame = ASCII_VIDEO_FRAMES[0] ?? '';
|
|
51
|
+
if (!firstFrame)
|
|
52
|
+
return;
|
|
53
|
+
if (!process.stdout.isTTY || process.env.CI) {
|
|
54
|
+
console.log(colorPromptFrame(firstFrame));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const frameDelayMs = Math.round(1000 / ASCII_VIDEO_FPS);
|
|
58
|
+
let renderedRows = 0;
|
|
59
|
+
process.stdout.write('\x1b[?25l\x1b[?7l');
|
|
60
|
+
try {
|
|
61
|
+
for (let index = 0; index < PROMPT_ANIMATION_MAX_FRAMES; index++) {
|
|
62
|
+
const output = colorPromptFrame(ASCII_VIDEO_FRAMES[index] ?? firstFrame);
|
|
63
|
+
const rows = output.split('\n').length;
|
|
64
|
+
if (renderedRows > 0) {
|
|
65
|
+
process.stdout.write(`\x1b[${renderedRows}F`);
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(`${output}\n`);
|
|
68
|
+
renderedRows = rows;
|
|
69
|
+
await new Promise(resolve => setTimeout(resolve, frameDelayMs));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
if (renderedRows > 0) {
|
|
74
|
+
process.stdout.write(`\x1b[${renderedRows}F\x1b[J`);
|
|
75
|
+
}
|
|
76
|
+
process.stdout.write('\x1b[0m\x1b[?7h\x1b[?25h');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if Ollama is reachable at the given URL.
|
|
81
|
+
*/
|
|
82
|
+
async function isOllamaReachable(url) {
|
|
6
83
|
try {
|
|
7
|
-
const response = await fetch(`${
|
|
8
|
-
signal: AbortSignal.timeout(5000)
|
|
84
|
+
const response = await fetch(`${url}/api/tags`, {
|
|
85
|
+
signal: AbortSignal.timeout(5000),
|
|
9
86
|
});
|
|
10
|
-
|
|
11
|
-
|
|
87
|
+
return response.ok;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the best reachable Ollama master URL (LAN → Tailscale fallback).
|
|
95
|
+
*/
|
|
96
|
+
async function resolveConfiguredMasterUrl(config) {
|
|
97
|
+
const masterUrl = config.get('masterUrl');
|
|
98
|
+
const tailscaleMasterUrl = config.get('tailscaleMasterUrl');
|
|
99
|
+
const candidates = [
|
|
100
|
+
masterUrl,
|
|
101
|
+
...(tailscaleMasterUrl && tailscaleMasterUrl !== masterUrl ? [tailscaleMasterUrl] : []),
|
|
102
|
+
];
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(`${candidate}/api/tags`, {
|
|
106
|
+
signal: AbortSignal.timeout(5000),
|
|
107
|
+
});
|
|
108
|
+
if (response.ok) {
|
|
109
|
+
if (candidate !== masterUrl) {
|
|
110
|
+
console.log(chalk.yellow(`LAN unavailable; using Tailscale master ${candidate}`));
|
|
111
|
+
}
|
|
112
|
+
return candidate;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// try next
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return masterUrl;
|
|
120
|
+
}
|
|
121
|
+
/** Build one opencode model entry, carrying the real context window so the TUI
|
|
122
|
+
* knows each model's limits (helps compaction). */
|
|
123
|
+
function modelEntry(m) {
|
|
124
|
+
const entry = { name: m.name ?? m.id };
|
|
125
|
+
// Only set a limit when we have a real context value (> the 8192 default) —
|
|
126
|
+
// a wrong-small limit would make opencode compact large models too early.
|
|
127
|
+
if (m.contextLength && m.contextLength > 8192) {
|
|
128
|
+
entry.limit = { context: m.contextLength, output: Math.min(16384, Math.max(2048, Math.floor(m.contextLength / 16))) };
|
|
129
|
+
}
|
|
130
|
+
return entry;
|
|
131
|
+
}
|
|
132
|
+
function modelMap(models) {
|
|
133
|
+
return Object.fromEntries(models.map(m => [m.id, modelEntry(m)]));
|
|
134
|
+
}
|
|
135
|
+
function buildProviderConfig(masterUrl, defaultModel, cloudProviders, masterReachable, modelsByProvider = {}) {
|
|
136
|
+
const providers = {};
|
|
137
|
+
// Prefer the provider's full fetched catalog; fall back to the curated preset.
|
|
138
|
+
const catalog = (id, fallback) => {
|
|
139
|
+
const fetched = modelsByProvider[id];
|
|
140
|
+
return fetched && fetched.length ? fetched : fallback;
|
|
141
|
+
};
|
|
142
|
+
// Only inject Ollama if the master is reachable
|
|
143
|
+
if (masterReachable) {
|
|
144
|
+
providers['ollama'] = {
|
|
145
|
+
npm: '@ai-sdk/openai-compatible',
|
|
146
|
+
name: 'Ollama',
|
|
147
|
+
options: {
|
|
148
|
+
baseURL: `${masterUrl}/v1`,
|
|
149
|
+
},
|
|
150
|
+
models: modelMap(catalog('ollama', [{ id: defaultModel, name: defaultModel, contextLength: 8192 }])),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
for (const cfg of cloudProviders) {
|
|
154
|
+
if (!cfg.enabled || !cfg.apiKey)
|
|
155
|
+
continue;
|
|
156
|
+
const preset = getProviderPreset(cfg.id);
|
|
157
|
+
const fallback = (preset?.knownModels ?? []).map(m => ({ id: m.id, name: m.name, contextLength: m.contextLength }));
|
|
158
|
+
const models = modelMap(catalog(cfg.id, fallback));
|
|
159
|
+
if (cfg.id === 'gemini') {
|
|
160
|
+
providers[cfg.id] = {
|
|
161
|
+
npm: '@ai-sdk/google',
|
|
162
|
+
name: cfg.name,
|
|
163
|
+
options: {
|
|
164
|
+
apiKey: cfg.apiKey,
|
|
165
|
+
},
|
|
166
|
+
models,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
providers[cfg.id] = {
|
|
171
|
+
npm: '@ai-sdk/openai-compatible',
|
|
172
|
+
name: cfg.name,
|
|
173
|
+
options: {
|
|
174
|
+
// Normalize so the AI SDK's `<base>/chat/completions` resolves
|
|
175
|
+
// correctly for every provider (e.g. Groq needs the /v1 segment).
|
|
176
|
+
baseURL: openAICompatBaseURL(cfg.baseURL),
|
|
177
|
+
apiKey: cfg.apiKey,
|
|
178
|
+
headers: cfg.headerName && cfg.headerName !== 'Authorization'
|
|
179
|
+
? { [cfg.headerName]: cfg.apiKey }
|
|
180
|
+
: undefined,
|
|
181
|
+
},
|
|
182
|
+
models,
|
|
183
|
+
};
|
|
12
184
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
185
|
+
}
|
|
186
|
+
return providers;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Pick the best default model: the pinned coding-worker model if set
|
|
190
|
+
* and its provider is healthy, otherwise the first cloud provider's
|
|
191
|
+
* recommended coding model, otherwise the Ollama default.
|
|
192
|
+
*/
|
|
193
|
+
async function pickDefaultModel(config, masterReachable) {
|
|
194
|
+
const workerModels = config.get('workerModels') ?? {};
|
|
195
|
+
const codingPin = workerModels['coding'];
|
|
196
|
+
// 1. Honor the coding-worker pin if its provider is available
|
|
197
|
+
if (codingPin) {
|
|
198
|
+
const cloudProviders = config.get('cloudProviders') ?? [];
|
|
199
|
+
const isCloud = cloudProviders.some(p => p.id === codingPin.providerId && p.enabled);
|
|
200
|
+
if (codingPin.providerId === 'ollama' && masterReachable) {
|
|
201
|
+
return { provider: codingPin.providerId, model: codingPin.modelId };
|
|
202
|
+
}
|
|
203
|
+
if (isCloud) {
|
|
204
|
+
return { provider: codingPin.providerId, model: codingPin.modelId };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// 2. If Ollama is reachable, use the Ollama default model
|
|
208
|
+
if (masterReachable) {
|
|
209
|
+
return { provider: 'ollama', model: config.get('defaultModel') };
|
|
210
|
+
}
|
|
211
|
+
// 3. Ollama down — pick the first enabled cloud provider's recommended coding model
|
|
212
|
+
const cloudProviders = config.get('cloudProviders') ?? [];
|
|
213
|
+
for (const cp of cloudProviders) {
|
|
214
|
+
if (!cp.enabled || !cp.apiKey)
|
|
215
|
+
continue;
|
|
216
|
+
const preset = getProviderPreset(cp.id);
|
|
217
|
+
const recs = preset?.recommended?.['coding'] ?? [];
|
|
218
|
+
if (recs.length > 0) {
|
|
219
|
+
return { provider: cp.id, model: recs[0] };
|
|
220
|
+
}
|
|
221
|
+
// fallback to first known model
|
|
222
|
+
const first = preset?.knownModels?.[0];
|
|
223
|
+
if (first) {
|
|
224
|
+
return { provider: cp.id, model: first.id };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 4. Nothing reachable — return Ollama default anyway (TUI will show error)
|
|
228
|
+
return { provider: 'ollama', model: config.get('defaultModel') };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* One kernel pass for the TUI launch: fetch each provider's FULL model catalog
|
|
232
|
+
* in parallel (so all models are exposed, incl. large ones like GLM-5.2 on HF
|
|
233
|
+
* and Nemotron-Ultra on NVIDIA), then route the default model under the policy.
|
|
234
|
+
*/
|
|
235
|
+
async function prepareRouting(config, policyName) {
|
|
236
|
+
const kernel = buildKernelFromConfig(config, { logger: new SilentLogger() });
|
|
237
|
+
const modelsByProvider = {};
|
|
238
|
+
try {
|
|
239
|
+
// Let background token-verified health checks run first.
|
|
240
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
241
|
+
// Fetch every provider's full catalog concurrently.
|
|
242
|
+
await Promise.all(kernel.providerManager.list().map(async (provider) => {
|
|
243
|
+
try {
|
|
244
|
+
const caps = await provider.capabilities();
|
|
245
|
+
modelsByProvider[provider.info.id] = caps.models;
|
|
20
246
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
247
|
+
catch {
|
|
248
|
+
modelsByProvider[provider.info.id] = [];
|
|
249
|
+
}
|
|
250
|
+
}));
|
|
251
|
+
const providers = (await kernel.providerManager.getHealthyProviders()).map(p => p.info);
|
|
252
|
+
const models = await kernel.providerManager.getAvailableModels();
|
|
253
|
+
let routed = null;
|
|
254
|
+
if (providers.length > 0 && models.length > 0) {
|
|
255
|
+
const ranked = kernel.router.rank({
|
|
256
|
+
capabilities: ['coding', 'reasoning'],
|
|
257
|
+
preferences: { reasoning: 95, speed: 80, contextLength: 32_000 },
|
|
258
|
+
policy: kernel.policies.get(policyName),
|
|
259
|
+
availableProviders: providers,
|
|
260
|
+
availableModels: models,
|
|
261
|
+
// Interactive sessions grow large; prefer a model that can accept a
|
|
262
|
+
// sizeable request so we don't default to a low-TPM free tier (e.g. Groq
|
|
263
|
+
// free ≈ 12k) that rejects big prompts mid-session.
|
|
264
|
+
estimatedRequestTokens: 16_000,
|
|
265
|
+
});
|
|
266
|
+
if (ranked[0])
|
|
267
|
+
routed = { providerId: ranked[0].providerId, modelId: ranked[0].modelId };
|
|
24
268
|
}
|
|
25
|
-
|
|
269
|
+
return { routed, modelsByProvider };
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return { routed: null, modelsByProvider };
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
kernel.destroy();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Ensure the routed model exists in the TUI's injected provider config. */
|
|
279
|
+
function ensureModelInjected(providers, providerId, modelId) {
|
|
280
|
+
const p = providers[providerId];
|
|
281
|
+
if (!p)
|
|
282
|
+
return;
|
|
283
|
+
p.models = p.models ?? {};
|
|
284
|
+
if (!p.models[modelId])
|
|
285
|
+
p.models[modelId] = { name: modelId };
|
|
286
|
+
}
|
|
287
|
+
export async function runAgent(_masterUrl, _model, _useTui, config) {
|
|
288
|
+
const masterUrl = await resolveConfiguredMasterUrl(config);
|
|
289
|
+
const masterReachable = await isOllamaReachable(masterUrl);
|
|
290
|
+
const defaultModel = config.get('defaultModel');
|
|
291
|
+
const cloudProviders = config.get('cloudProviders') ?? [];
|
|
292
|
+
const enabledCloud = cloudProviders.filter(p => p.enabled);
|
|
293
|
+
const policyName = config.get('policy') ?? 'balanced';
|
|
294
|
+
// One kernel pass: load every provider's full model catalog (in parallel) and
|
|
295
|
+
// route the default model under the policy.
|
|
296
|
+
const routeSpinner = ora('loading model catalog & routing...').start();
|
|
297
|
+
const { routed, modelsByProvider } = await prepareRouting(config, policyName);
|
|
298
|
+
routeSpinner.stop();
|
|
299
|
+
const providers = buildProviderConfig(masterUrl, defaultModel, cloudProviders, masterReachable, modelsByProvider);
|
|
300
|
+
let defaultProvider;
|
|
301
|
+
let pickedModel;
|
|
302
|
+
if (routed && providers[routed.providerId]) {
|
|
303
|
+
defaultProvider = routed.providerId;
|
|
304
|
+
pickedModel = routed.modelId;
|
|
305
|
+
ensureModelInjected(providers, routed.providerId, routed.modelId);
|
|
26
306
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
process.exit(1);
|
|
307
|
+
else {
|
|
308
|
+
const fb = await pickDefaultModel(config, masterReachable);
|
|
309
|
+
defaultProvider = fb.provider;
|
|
310
|
+
pickedModel = fb.model;
|
|
32
311
|
}
|
|
33
|
-
|
|
34
|
-
|
|
312
|
+
const modelRef = `${defaultProvider}/${pickedModel}`;
|
|
313
|
+
const providerCount = Object.keys(providers).length;
|
|
314
|
+
const modelCount = Object.values(providers).reduce((n, p) => n + Object.keys(p.models ?? {}).length, 0);
|
|
315
|
+
const policy = config.get('policy') ?? 'local-first';
|
|
316
|
+
const pinnedCount = Object.keys(config.get('workerModels') ?? {}).length;
|
|
317
|
+
const routingInfo = `AUTO ${policy} ${providerCount}P ${pinnedCount}pins`;
|
|
35
318
|
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
319
|
+
$schema: 'https://opencode.ai/config.json',
|
|
320
|
+
provider: providers,
|
|
321
|
+
model: modelRef,
|
|
322
|
+
small_model: modelRef,
|
|
323
|
+
enabled_providers: Object.keys(providers),
|
|
324
|
+
agent: {
|
|
325
|
+
build: {
|
|
326
|
+
model: modelRef,
|
|
327
|
+
description: `◆ ${routingInfo}`,
|
|
328
|
+
color: 'primary',
|
|
329
|
+
},
|
|
330
|
+
plan: {
|
|
331
|
+
model: modelRef,
|
|
332
|
+
description: `◆ ${routingInfo}`,
|
|
333
|
+
color: 'secondary',
|
|
334
|
+
},
|
|
48
335
|
},
|
|
49
|
-
agents: {
|
|
50
|
-
coder: { model: `local.${model}` }
|
|
51
|
-
},
|
|
52
|
-
tui: {
|
|
53
|
-
theme: config.get('theme') || 'infini'
|
|
54
|
-
}
|
|
55
336
|
});
|
|
56
|
-
console.log(chalk.dim(`\
|
|
57
|
-
console.log(chalk.dim(
|
|
58
|
-
|
|
337
|
+
console.log(chalk.dim(`\ninfinicode — launching TUI with ${chalk.cyan(String(providerCount))} provider(s), ${chalk.cyan(String(modelCount))} model(s)`));
|
|
338
|
+
console.log(chalk.dim(` default model: ${chalk.cyan(modelRef)}`));
|
|
339
|
+
console.log(chalk.dim(` policy: ${chalk.cyan(config.get('policy') ?? 'local-first')}`));
|
|
340
|
+
if (enabledCloud.length > 0) {
|
|
341
|
+
console.log(chalk.dim(` cloud: ${enabledCloud.map(p => p.id).join(', ')}`));
|
|
342
|
+
}
|
|
343
|
+
if (!masterReachable) {
|
|
344
|
+
console.log(chalk.yellow(` ⚠ Ollama not reachable — using cloud providers only`));
|
|
345
|
+
}
|
|
346
|
+
console.log(chalk.dim('-'.repeat(40)));
|
|
347
|
+
await renderPromptAnimation();
|
|
59
348
|
const { execa } = await import('execa');
|
|
349
|
+
const infinicodeCommand = resolveInfinicodeCommand();
|
|
60
350
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
await execa(
|
|
351
|
+
if (infinicodeCommand === 'infinicode') {
|
|
352
|
+
await execa('which', ['infinicode']).catch(() => execa('where', ['infinicode']));
|
|
353
|
+
}
|
|
354
|
+
await execa(infinicodeCommand, [], {
|
|
65
355
|
stdio: 'inherit',
|
|
66
|
-
env: process.env
|
|
356
|
+
env: process.env,
|
|
67
357
|
});
|
|
68
358
|
}
|
|
69
359
|
catch (e) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.log(chalk.
|
|
73
|
-
console.log(chalk.
|
|
74
|
-
console.log(chalk.dim('
|
|
75
|
-
console.log(chalk.cyan('
|
|
360
|
+
const err = e;
|
|
361
|
+
if (err.code === 'ENOENT') {
|
|
362
|
+
console.log(chalk.yellow('\ninfinicode TUI binary not found.'));
|
|
363
|
+
console.log(chalk.dim('\nThe TUI requires the infinicode binary in bin/ or on PATH.'));
|
|
364
|
+
console.log(chalk.dim('Build from source:'));
|
|
365
|
+
console.log(chalk.cyan(' pnpm build'));
|
|
76
366
|
console.log();
|
|
77
367
|
process.exit(1);
|
|
78
368
|
}
|
|
79
|
-
if (
|
|
80
|
-
process.exit(
|
|
369
|
+
if (err.exitCode) {
|
|
370
|
+
process.exit(err.exitCode);
|
|
81
371
|
}
|
|
82
372
|
throw e;
|
|
83
373
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type Conf from 'conf';
|
|
2
|
+
import type { InfinicodeConfig } from '../kernel/setup.js';
|
|
3
|
+
export interface ServeOptions {
|
|
4
|
+
role?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
port?: string;
|
|
7
|
+
host?: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
seed?: string[];
|
|
10
|
+
autoUpdate?: boolean;
|
|
11
|
+
hub?: boolean;
|
|
12
|
+
tailscale?: boolean;
|
|
13
|
+
tag?: string;
|
|
14
|
+
gateway?: string;
|
|
15
|
+
gatewayToken?: string;
|
|
16
|
+
gatewayPassword?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function serve(config: Conf<InfinicodeConfig>, opts: ServeOptions): Promise<void>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infinicode serve — run this device as a federation mesh node.
|
|
3
|
+
*
|
|
4
|
+
* infinicode serve start the mesh (uses config.federation)
|
|
5
|
+
* infinicode serve --role satellite --seed http://100.x.y.z:47913
|
|
6
|
+
* infinicode serve --hub --port 47913 --name workstation
|
|
7
|
+
*
|
|
8
|
+
* The node exposes an HTTP+SSE mesh endpoint, streams its activity + hardware
|
|
9
|
+
* telemetry to connected peers/harnesses, dispatches tasks across the mesh, and
|
|
10
|
+
* (as a satellite) inherits shared API/provider config from its hub.
|
|
11
|
+
*/
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { buildKernelFromConfig } from '../kernel/setup.js';
|
|
17
|
+
import { Federation, MoltfedConnector, loadOrCreateIdentity } from '../kernel/index.js';
|
|
18
|
+
function pkgVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
return JSON.parse(readFileSync(join(here, '..', '..', 'package.json'), 'utf8')).version ?? '0.0.0';
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function serve(config, opts) {
|
|
28
|
+
const fed = config.get('federation') ?? {};
|
|
29
|
+
const role = (opts.hub ? 'hub' : opts.role ?? fed.role ?? 'peer');
|
|
30
|
+
const port = opts.port ? parseInt(opts.port, 10) : fed.port ?? 47913;
|
|
31
|
+
const seeds = [...(fed.seeds ?? []), ...(opts.seed ?? [])];
|
|
32
|
+
const token = opts.token ?? fed.token;
|
|
33
|
+
const kernel = buildKernelFromConfig(config);
|
|
34
|
+
const identity = loadOrCreateIdentity({ role, displayName: opts.name ?? fed.displayName });
|
|
35
|
+
// Manifest inputs are read live from the kernel so the map shows real caps.
|
|
36
|
+
const describe = () => {
|
|
37
|
+
const defs = kernel.workerRuntime.listRegisteredDefinitions();
|
|
38
|
+
const caps = [...new Set(defs.flatMap(d => d.capabilities))];
|
|
39
|
+
return {
|
|
40
|
+
version: pkgVersion(),
|
|
41
|
+
capabilities: caps,
|
|
42
|
+
workers: defs.map(d => d.type),
|
|
43
|
+
commands: kernel.commands.list().map(c => c.name),
|
|
44
|
+
providers: kernel.providerManager.listInfos().map(p => p.id),
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
// Satellite: persist shared config locally so providers/keys survive a restart.
|
|
48
|
+
const applyConfig = (cfg) => {
|
|
49
|
+
if (cfg.cloudProviders)
|
|
50
|
+
config.set('cloudProviders', cfg.cloudProviders);
|
|
51
|
+
if (cfg.policy)
|
|
52
|
+
config.set('policy', cfg.policy);
|
|
53
|
+
if (cfg.defaultModel)
|
|
54
|
+
config.set('defaultModel', cfg.defaultModel);
|
|
55
|
+
console.log(chalk.green(` ✓ applied shared config (rev ${cfg.revision}) — restart to load new providers`));
|
|
56
|
+
};
|
|
57
|
+
const federation = new Federation({
|
|
58
|
+
kernel,
|
|
59
|
+
identity,
|
|
60
|
+
describe,
|
|
61
|
+
logger: kernel.logger,
|
|
62
|
+
options: {
|
|
63
|
+
version: pkgVersion(),
|
|
64
|
+
port,
|
|
65
|
+
host: opts.host ?? fed.host,
|
|
66
|
+
token,
|
|
67
|
+
heartbeatMs: (fed.heartbeatSec ?? 30) * 1000,
|
|
68
|
+
autoUpdate: opts.autoUpdate ?? fed.autoUpdate,
|
|
69
|
+
applyConfig,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
console.log(chalk.bold(`\n infinicode mesh node`));
|
|
73
|
+
console.log(chalk.dim(' ' + '-'.repeat(46)));
|
|
74
|
+
console.log(` node: ${chalk.cyan(identity.displayName)} ${chalk.dim('(' + identity.nodeId + ')')}`);
|
|
75
|
+
console.log(` role: ${chalk.yellow(role)}`);
|
|
76
|
+
console.log(` listen: ${chalk.cyan((opts.host ?? fed.host ?? '0.0.0.0') + ':' + port)}`);
|
|
77
|
+
if (token)
|
|
78
|
+
console.log(` auth: ${chalk.green('token required')}`);
|
|
79
|
+
console.log();
|
|
80
|
+
await federation.start();
|
|
81
|
+
// Connect to seeds and, as a satellite, pull shared config from the first hub.
|
|
82
|
+
for (const url of seeds) {
|
|
83
|
+
const peer = await federation.connect(url);
|
|
84
|
+
if (peer) {
|
|
85
|
+
console.log(chalk.green(` ✓ connected to ${peer.displayName} @ ${url}`));
|
|
86
|
+
if (role === 'satellite') {
|
|
87
|
+
const applied = await federation.pullConfig(peer.nodeId).catch(() => false);
|
|
88
|
+
if (applied)
|
|
89
|
+
console.log(chalk.green(` ✓ pulled shared config from ${peer.displayName}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Optional: auto-discover peers over Tailscale (no --seed needed).
|
|
94
|
+
if (opts.tailscale) {
|
|
95
|
+
const n = await federation.discoverAndConnect(opts.tag).catch(() => 0);
|
|
96
|
+
console.log(chalk.green(` ✓ tailscale discovery connected ${n} peer(s)`));
|
|
97
|
+
}
|
|
98
|
+
// Optional: log this node into an InfiniBot gateway so it renders live on the
|
|
99
|
+
// neural-mesh map + hardware panels (no InfiniBot changes needed).
|
|
100
|
+
let connector;
|
|
101
|
+
if (opts.gateway) {
|
|
102
|
+
connector = new MoltfedConnector({
|
|
103
|
+
gatewayUrl: opts.gateway,
|
|
104
|
+
token: opts.gatewayToken,
|
|
105
|
+
password: opts.gatewayPassword,
|
|
106
|
+
client: { id: identity.displayName, version: pkgVersion(), platform: 'node', mode: 'backend' },
|
|
107
|
+
logger: kernel.logger,
|
|
108
|
+
getNodeStatuses: () => federation.nodeStatuses(),
|
|
109
|
+
getHardware: () => federation.latestHardware(),
|
|
110
|
+
subscribeFrames: (cb) => federation.onFrames(cb),
|
|
111
|
+
});
|
|
112
|
+
connector.start();
|
|
113
|
+
console.log(chalk.green(` ✓ linking to InfiniBot gateway ${opts.gateway}`));
|
|
114
|
+
}
|
|
115
|
+
console.log(chalk.dim(`\n mesh online — Ctrl-C to stop. Peers: ${federation.peers().length}\n`));
|
|
116
|
+
const shutdown = async () => {
|
|
117
|
+
console.log(chalk.dim('\n stopping mesh…'));
|
|
118
|
+
connector?.stop();
|
|
119
|
+
await federation.stop();
|
|
120
|
+
kernel.destroy();
|
|
121
|
+
process.exit(0);
|
|
122
|
+
};
|
|
123
|
+
process.on('SIGINT', () => void shutdown());
|
|
124
|
+
process.on('SIGTERM', () => void shutdown());
|
|
125
|
+
// Keep the process alive.
|
|
126
|
+
await new Promise(() => { });
|
|
127
|
+
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type Conf from 'conf';
|
|
2
|
-
export declare function setupMaster(masterIp: string, port: string, model: string, config: Conf<any
|
|
2
|
+
export declare function setupMaster(masterIp: string, port: string, model: string, config: Conf<any>, tailscaleMasterIp?: string, tailscalePort?: string): Promise<void>;
|