infinicode 2.3.3 → 2.3.4
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/mesh-commands.tsx +139 -0
- package/dist/cli.js +1 -1
- package/dist/commands/run.js +100 -3
- package/dist/kernel/federation/federation.js +1 -0
- package/dist/kernel/federation/transport-http.d.ts +3 -0
- package/dist/kernel/federation/transport-http.js +25 -0
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
/**
|
|
3
|
+
* infinicode — mesh command palette
|
|
4
|
+
*
|
|
5
|
+
* Adds the OpenKernel mesh/kernel slash-commands natively to the TUI's `/`
|
|
6
|
+
* command palette. Each command is dispatched to a LOCAL mesh node over HTTP
|
|
7
|
+
* (POST /fed/command → kernel.command()), so the same command surface exposed by
|
|
8
|
+
* `infinicode serve`'s console and the CLI is available right inside the TUI.
|
|
9
|
+
*
|
|
10
|
+
* The local node is started (or reused) by `infinicode run`, which injects:
|
|
11
|
+
* INFINICODE_MESH_URL e.g. http://127.0.0.1:47913
|
|
12
|
+
* INFINICODE_MESH_TOKEN optional bearer token
|
|
13
|
+
*
|
|
14
|
+
* Argless commands (/nodes, /running, /activity, /whoami, /mesh-help) run
|
|
15
|
+
* immediately and show output in an alert. Arg-taking commands (/node, /build,
|
|
16
|
+
* /follow, /dispatch, /goal, /mission, /role) open a prompt first.
|
|
17
|
+
*/
|
|
18
|
+
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
19
|
+
import { createBindingLookup } from "@opentui/keymap/extras"
|
|
20
|
+
|
|
21
|
+
type CommandResult = { ok?: boolean; text?: string; data?: unknown }
|
|
22
|
+
|
|
23
|
+
const meshUrl = () => (process.env.INFINICODE_MESH_URL ?? "http://127.0.0.1:47913").replace(/\/+$/, "")
|
|
24
|
+
const meshToken = () => process.env.INFINICODE_MESH_TOKEN
|
|
25
|
+
|
|
26
|
+
/** POST a slash-command to the local mesh node and return its CommandResult. */
|
|
27
|
+
async function send(input: string): Promise<CommandResult> {
|
|
28
|
+
const token = meshToken()
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(`${meshUrl()}/fed/command`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({ input }),
|
|
37
|
+
signal: AbortSignal.timeout(30_000),
|
|
38
|
+
})
|
|
39
|
+
if (!res.ok) return { ok: false, text: `mesh error ${res.status} — is a node running? (infinicode serve)` }
|
|
40
|
+
return (await res.json()) as CommandResult
|
|
41
|
+
} catch (e) {
|
|
42
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
43
|
+
return { ok: false, text: `no local mesh node reachable at ${meshUrl()} (${msg})` }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Render a CommandResult in a scrollable alert dialog. */
|
|
48
|
+
function show(api: TuiPluginApi, title: string, res: CommandResult): void {
|
|
49
|
+
const DialogAlert = api.ui.DialogAlert
|
|
50
|
+
const message = (res.text && res.text.trim()) || (res.ok ? "(no output)" : "command failed")
|
|
51
|
+
api.ui.dialog.setSize("large")
|
|
52
|
+
api.ui.dialog.replace(() => (
|
|
53
|
+
<DialogAlert title={title} message={message} onConfirm={() => api.ui.dialog.clear()} />
|
|
54
|
+
))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Run a fixed slash-command now and show the result. */
|
|
58
|
+
function runNow(api: TuiPluginApi, title: string, input: string): void {
|
|
59
|
+
api.ui.toast({ variant: "info", title: "mesh", message: `${input} …`, duration: 1200 })
|
|
60
|
+
void send(input).then((res) => show(api, title, res))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Open a prompt, then dispatch `<slash> <entered args>`. */
|
|
64
|
+
function runWithArgs(
|
|
65
|
+
api: TuiPluginApi,
|
|
66
|
+
title: string,
|
|
67
|
+
slash: string,
|
|
68
|
+
placeholder: string,
|
|
69
|
+
): void {
|
|
70
|
+
const DialogPrompt = api.ui.DialogPrompt
|
|
71
|
+
api.ui.dialog.setSize("medium")
|
|
72
|
+
api.ui.dialog.replace(() => (
|
|
73
|
+
<DialogPrompt
|
|
74
|
+
title={title}
|
|
75
|
+
value=""
|
|
76
|
+
placeholder={placeholder}
|
|
77
|
+
onConfirm={(args: string) => {
|
|
78
|
+
api.ui.dialog.clear()
|
|
79
|
+
const input = args && args.trim() ? `${slash} ${args.trim()}` : slash
|
|
80
|
+
api.ui.toast({ variant: "info", title: "mesh", message: `${input} …`, duration: 1200 })
|
|
81
|
+
void send(input).then((res) => show(api, title, res))
|
|
82
|
+
}}
|
|
83
|
+
onCancel={() => api.ui.dialog.clear()}
|
|
84
|
+
/>
|
|
85
|
+
))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface Spec {
|
|
89
|
+
name: string
|
|
90
|
+
slashName: string
|
|
91
|
+
title: string
|
|
92
|
+
/** if set, prompt for args first with this placeholder; else run immediately */
|
|
93
|
+
arg?: string
|
|
94
|
+
/** the underlying kernel slash-command (defaults to `/${slashName}`) */
|
|
95
|
+
cmd?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const SPECS: Spec[] = [
|
|
99
|
+
{ name: "mesh_nodes", slashName: "nodes", title: "Mesh nodes" },
|
|
100
|
+
{ name: "mesh_running", slashName: "running", title: "Running tasks" },
|
|
101
|
+
{ name: "mesh_activity", slashName: "activity", title: "Mesh activity" },
|
|
102
|
+
{ name: "mesh_whoami", slashName: "whoami", title: "This node" },
|
|
103
|
+
{ name: "mesh_help", slashName: "mesh-help", title: "Mesh commands", cmd: "/help" },
|
|
104
|
+
{ name: "mesh_node", slashName: "node", title: "Node control", arg: "<node> <message>", cmd: "/node" },
|
|
105
|
+
{ name: "mesh_build", slashName: "build", title: "Phased build", arg: "<node> <goal>", cmd: "/build" },
|
|
106
|
+
{ name: "mesh_dispatch", slashName: "dispatch", title: "Dispatch task", arg: "<node> <task>", cmd: "/dispatch" },
|
|
107
|
+
{ name: "mesh_follow", slashName: "follow", title: "Follow run", arg: "<run-id>", cmd: "/follow" },
|
|
108
|
+
{ name: "mesh_goal", slashName: "goal", title: "New goal", arg: "<goal description>", cmd: "/goal" },
|
|
109
|
+
{ name: "mesh_mission", slashName: "mission", title: "Mission", arg: "<mission description>", cmd: "/mission" },
|
|
110
|
+
{ name: "mesh_role", slashName: "role", title: "Set role", arg: "<role>", cmd: "/role" },
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const tui: TuiPlugin = async (api, options) => {
|
|
114
|
+
if (options?.enabled === false) return
|
|
115
|
+
const keys = createBindingLookup({})
|
|
116
|
+
|
|
117
|
+
api.keymap.registerLayer({
|
|
118
|
+
commands: SPECS.map((s) => ({
|
|
119
|
+
name: s.name,
|
|
120
|
+
title: s.title,
|
|
121
|
+
category: "Mesh",
|
|
122
|
+
namespace: "palette" as const,
|
|
123
|
+
slashName: s.slashName,
|
|
124
|
+
run() {
|
|
125
|
+
const cmd = s.cmd ?? `/${s.slashName}`
|
|
126
|
+
if (s.arg) runWithArgs(api, s.title, cmd, s.arg)
|
|
127
|
+
else runNow(api, s.title, cmd)
|
|
128
|
+
},
|
|
129
|
+
})),
|
|
130
|
+
bindings: keys.gather("mesh.global", SPECS.map((s) => s.name)),
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
135
|
+
id: "infinicode-mesh-commands",
|
|
136
|
+
tui,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default plugin
|
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { serve } from './commands/serve.js';
|
|
|
16
16
|
import { mcpCommand } from './commands/mcp.js';
|
|
17
17
|
import { installTui } from './commands/install-tui.js';
|
|
18
18
|
import { DEFAULT_CONFIG } from './kernel/config-schema.js';
|
|
19
|
-
const VERSION = '2.3.
|
|
19
|
+
const VERSION = '2.3.4';
|
|
20
20
|
const config = new Conf({
|
|
21
21
|
projectName: 'infinicode',
|
|
22
22
|
defaults: DEFAULT_CONFIG,
|
package/dist/commands/run.js
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import chalk from 'chalk';
|
|
18
18
|
import ora from 'ora';
|
|
19
|
-
import { existsSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { dirname, join, resolve } from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
22
|
import { createRequire } from 'node:module';
|
|
23
23
|
import { ASCII_VIDEO_FPS, ASCII_VIDEO_FRAMES } from '../ascii-video-animation.js';
|
|
24
24
|
import { getProviderPreset } from '../kernel/free-providers.js';
|
|
25
25
|
import { buildKernelFromConfig } from '../kernel/setup.js';
|
|
26
|
-
import { SilentLogger } from '../kernel/index.js';
|
|
26
|
+
import { SilentLogger, Federation, loadOrCreateIdentity } from '../kernel/index.js';
|
|
27
27
|
import { openAICompatBaseURL } from '../kernel/provider-url.js';
|
|
28
28
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
29
|
const PROMPT_ANIMATION_MAX_FRAMES = Math.min(ASCII_VIDEO_FRAMES.length, ASCII_VIDEO_FPS * 3);
|
|
@@ -309,6 +309,81 @@ function ensureModelInjected(providers, providerId, modelId) {
|
|
|
309
309
|
if (!p.models[modelId])
|
|
310
310
|
p.models[modelId] = { name: modelId };
|
|
311
311
|
}
|
|
312
|
+
function runPkgVersion() {
|
|
313
|
+
try {
|
|
314
|
+
return JSON.parse(readFileSync(join(resolve(__dirname, '..', '..'), 'package.json'), 'utf8')).version ?? '0.0.0';
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return '0.0.0';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/** Is a mesh node already listening at this URL? (e.g. a separate `infinicode serve`). */
|
|
321
|
+
async function meshAlive(url, token) {
|
|
322
|
+
try {
|
|
323
|
+
const res = await fetch(`${url}/fed/manifest`, {
|
|
324
|
+
headers: token ? { authorization: `Bearer ${token}` } : undefined,
|
|
325
|
+
signal: AbortSignal.timeout(1500),
|
|
326
|
+
});
|
|
327
|
+
return res.ok;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Make the TUI a mesh participant so the in-TUI slash commands (/nodes, /build,
|
|
335
|
+
* /activity …) have a local node to talk to. If a node is already running on the
|
|
336
|
+
* configured port (a separate `infinicode serve`), point the TUI at THAT one;
|
|
337
|
+
* otherwise start an in-process node for the session. Best-effort: on any
|
|
338
|
+
* failure the TUI still launches, just without the live mesh palette.
|
|
339
|
+
*/
|
|
340
|
+
async function startLocalMeshNode(config) {
|
|
341
|
+
if (process.env.INFINICODE_NO_MESH)
|
|
342
|
+
return null;
|
|
343
|
+
const fed = config.get('federation') ?? {};
|
|
344
|
+
const port = fed.port ?? 47913;
|
|
345
|
+
const token = fed.token;
|
|
346
|
+
const url = `http://127.0.0.1:${port}`;
|
|
347
|
+
try {
|
|
348
|
+
// Reuse an already-running node (don't double-bind the port).
|
|
349
|
+
if (await meshAlive(url, token))
|
|
350
|
+
return { url, token, stop: async () => { } };
|
|
351
|
+
const kernel = buildKernelFromConfig(config, { logger: new SilentLogger() });
|
|
352
|
+
const role = (fed.role ?? 'peer');
|
|
353
|
+
const identity = loadOrCreateIdentity({ role, displayName: fed.displayName });
|
|
354
|
+
const version = runPkgVersion();
|
|
355
|
+
const describe = () => {
|
|
356
|
+
const defs = kernel.workerRuntime.listRegisteredDefinitions();
|
|
357
|
+
return {
|
|
358
|
+
version,
|
|
359
|
+
capabilities: [...new Set(defs.flatMap(d => d.capabilities))],
|
|
360
|
+
workers: defs.map(d => d.type),
|
|
361
|
+
commands: kernel.commands.list().map(c => c.name),
|
|
362
|
+
providers: kernel.providerManager.listInfos().map(p => p.id),
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
const federation = new Federation({
|
|
366
|
+
kernel, identity, describe, logger: new SilentLogger(),
|
|
367
|
+
options: {
|
|
368
|
+
version, port, host: fed.host, token, seeds: fed.seeds,
|
|
369
|
+
autoPullConfig: role === 'satellite',
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
kernel.attachFederation(federation);
|
|
373
|
+
await federation.start();
|
|
374
|
+
if (fed.lan)
|
|
375
|
+
federation.startLanDiscovery(undefined, fed.lanPort);
|
|
376
|
+
for (const s of fed.seeds ?? [])
|
|
377
|
+
await federation.connect(s).catch(() => undefined);
|
|
378
|
+
return {
|
|
379
|
+
url, token,
|
|
380
|
+
stop: async () => { await federation.stop(); kernel.destroy(); },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
312
387
|
export async function runAgent(_masterUrl, _model, _useTui, config) {
|
|
313
388
|
const masterUrl = await resolveConfiguredMasterUrl(config);
|
|
314
389
|
const masterReachable = await isOllamaReachable(masterUrl);
|
|
@@ -316,6 +391,15 @@ export async function runAgent(_masterUrl, _model, _useTui, config) {
|
|
|
316
391
|
const cloudProviders = config.get('cloudProviders') ?? [];
|
|
317
392
|
const enabledCloud = cloudProviders.filter(p => p.enabled);
|
|
318
393
|
const policyName = config.get('policy') ?? 'balanced';
|
|
394
|
+
// Make this TUI session a mesh participant so the in-TUI slash commands
|
|
395
|
+
// (/nodes, /build, /activity, …) have a local node to talk to. Reuses a
|
|
396
|
+
// running `infinicode serve` if one is up, else starts an in-process node.
|
|
397
|
+
const mesh = await startLocalMeshNode(config);
|
|
398
|
+
if (mesh) {
|
|
399
|
+
process.env.INFINICODE_MESH_URL = mesh.url;
|
|
400
|
+
if (mesh.token)
|
|
401
|
+
process.env.INFINICODE_MESH_TOKEN = mesh.token;
|
|
402
|
+
}
|
|
319
403
|
// One kernel pass: load every provider's full model catalog (in parallel) and
|
|
320
404
|
// route the default model under the policy.
|
|
321
405
|
const routeSpinner = ora('loading model catalog & routing...').start();
|
|
@@ -340,12 +424,23 @@ export async function runAgent(_masterUrl, _model, _useTui, config) {
|
|
|
340
424
|
const policy = config.get('policy') ?? 'local-first';
|
|
341
425
|
const pinnedCount = Object.keys(config.get('workerModels') ?? {}).length;
|
|
342
426
|
const routingInfo = `AUTO ${policy} ${providerCount}P ${pinnedCount}pins`;
|
|
427
|
+
// Runtime TUI plugin that adds the mesh slash-commands (/nodes, /build, …)
|
|
428
|
+
// to the `/` palette. Shipped in the package at .opencode/plugins; loaded via
|
|
429
|
+
// a file:// URL so it resolves regardless of the TUI's cwd. Only injected when
|
|
430
|
+
// a local mesh node is available (else the commands would have nothing to hit).
|
|
431
|
+
const pluginPaths = [];
|
|
432
|
+
if (mesh) {
|
|
433
|
+
const pluginFile = join(resolve(__dirname, '..', '..'), '.opencode', 'plugins', 'mesh-commands.tsx');
|
|
434
|
+
if (existsSync(pluginFile))
|
|
435
|
+
pluginPaths.push(pathToFileURL(pluginFile).href);
|
|
436
|
+
}
|
|
343
437
|
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
|
344
438
|
$schema: 'https://opencode.ai/config.json',
|
|
345
439
|
provider: providers,
|
|
346
440
|
model: modelRef,
|
|
347
441
|
small_model: modelRef,
|
|
348
442
|
enabled_providers: Object.keys(providers),
|
|
443
|
+
...(pluginPaths.length > 0 ? { plugin: pluginPaths } : {}),
|
|
349
444
|
agent: {
|
|
350
445
|
build: {
|
|
351
446
|
model: modelRef,
|
|
@@ -392,10 +487,12 @@ export async function runAgent(_masterUrl, _model, _useTui, config) {
|
|
|
392
487
|
});
|
|
393
488
|
}
|
|
394
489
|
catch (e) {
|
|
490
|
+
await mesh?.stop().catch(() => undefined);
|
|
395
491
|
const err = e;
|
|
396
492
|
if (err.exitCode) {
|
|
397
493
|
process.exit(err.exitCode);
|
|
398
494
|
}
|
|
399
495
|
throw e;
|
|
400
496
|
}
|
|
497
|
+
await mesh?.stop().catch(() => undefined);
|
|
401
498
|
}
|
|
@@ -61,6 +61,7 @@ export class Federation {
|
|
|
61
61
|
logger,
|
|
62
62
|
getManifest: () => buildManifest(identity, this.deps.describe()),
|
|
63
63
|
getNodes: () => this.nodeStatuses(),
|
|
64
|
+
runCommand: (input) => this.deps.kernel.command(input),
|
|
64
65
|
onRpc: (env, remote) => this.onRpc(env, remote),
|
|
65
66
|
});
|
|
66
67
|
await this.server.start();
|
|
@@ -9,6 +9,9 @@ export interface MeshServerOptions {
|
|
|
9
9
|
getManifest: () => NodeManifest;
|
|
10
10
|
/** Optional: full mesh view (self + peers) for the GET /fed/nodes probe. */
|
|
11
11
|
getNodes?: () => unknown;
|
|
12
|
+
/** Optional: run a kernel slash-command on this node (POST /fed/command).
|
|
13
|
+
* Powers the in-TUI mesh command palette. */
|
|
14
|
+
runCommand?: (input: string) => Promise<unknown>;
|
|
12
15
|
onRpc: RpcHandler;
|
|
13
16
|
logger: Logger;
|
|
14
17
|
}
|
|
@@ -78,6 +78,31 @@ export class MeshServer {
|
|
|
78
78
|
this.openStream(req, res);
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
+
if (req.method === 'POST' && url.startsWith('/fed/command')) {
|
|
82
|
+
if (!this.opts.runCommand) {
|
|
83
|
+
res.writeHead(404).end('no command handler');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.readBody(req)
|
|
87
|
+
.then(async (body) => {
|
|
88
|
+
let input = '';
|
|
89
|
+
try {
|
|
90
|
+
input = JSON.parse(body).input ?? '';
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
res.writeHead(400).end('bad json');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const result = await this.opts.runCommand(input);
|
|
97
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
98
|
+
res.end(JSON.stringify(result ?? { ok: false, text: 'no result' }));
|
|
99
|
+
})
|
|
100
|
+
.catch(err => {
|
|
101
|
+
this.opts.logger.warn(`[federation] command error: ${err instanceof Error ? err.message : String(err)}`);
|
|
102
|
+
res.writeHead(500).end('command failed');
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
81
106
|
if (req.method === 'POST' && url.startsWith('/fed/rpc')) {
|
|
82
107
|
this.readBody(req)
|
|
83
108
|
.then(async (body) => {
|
package/package.json
CHANGED