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.
@@ -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.3';
19
+ const VERSION = '2.3.4';
20
20
  const config = new Conf({
21
21
  projectName: 'infinicode',
22
22
  defaults: DEFAULT_CONFIG,
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.3.3",
3
+ "version": "2.3.4",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",