infinicode 2.3.6 → 2.4.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/bin/robopark.js +2 -0
- package/dist/cli.js +22 -1
- package/dist/commands/maintain.d.ts +29 -0
- package/dist/commands/maintain.js +186 -0
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/robopark.d.ts +6 -0
- package/dist/commands/robopark.js +450 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +152 -11
- package/dist/kernel/agents/backends/cli-backend.d.ts +4 -0
- package/dist/kernel/agents/backends/cli-backend.js +13 -5
- package/dist/kernel/agents/backends/detect.d.ts +12 -0
- package/dist/kernel/agents/backends/detect.js +102 -2
- package/dist/kernel/agents/backends/profiles.d.ts +67 -0
- package/dist/kernel/agents/backends/profiles.js +48 -0
- package/dist/kernel/agents/backends/registry.d.ts +2 -1
- package/dist/kernel/agents/backends/registry.js +39 -5
- package/dist/kernel/agents/backends/rotation.d.ts +51 -0
- package/dist/kernel/agents/backends/rotation.js +107 -0
- package/dist/kernel/agents/backends/types.d.ts +3 -1
- package/dist/kernel/federation/dashboard-html.d.ts +13 -0
- package/dist/kernel/federation/dashboard-html.js +371 -0
- package/dist/kernel/federation/federation.d.ts +58 -1
- package/dist/kernel/federation/federation.js +111 -23
- package/dist/kernel/federation/moltfed-adapter.js +1 -0
- package/dist/kernel/federation/telemetry.d.ts +1 -1
- package/dist/kernel/federation/telemetry.js +2 -1
- package/dist/kernel/federation/transport-http.d.ts +17 -1
- package/dist/kernel/federation/transport-http.js +65 -2
- package/dist/kernel/federation/types.d.ts +20 -0
- package/dist/kernel/mcp/mcp-server.js +23 -7
- package/dist/robopark-cli.d.ts +2 -0
- package/dist/robopark-cli.js +23 -0
- package/package.json +6 -3
package/bin/robopark.js
ADDED
package/dist/cli.js
CHANGED
|
@@ -14,10 +14,12 @@ import { providersList, providersAdd, providersRemove, providersTest } from './c
|
|
|
14
14
|
import { consoleRepl, consoleRun } from './commands/console.js';
|
|
15
15
|
import { serve } from './commands/serve.js';
|
|
16
16
|
import { mcpCommand } from './commands/mcp.js';
|
|
17
|
+
import { maintain } from './commands/maintain.js';
|
|
17
18
|
import { meshInstall, meshUninstall } from './commands/mesh-install.js';
|
|
18
19
|
import { installTui } from './commands/install-tui.js';
|
|
20
|
+
import { attachRoboparkSubcommands } from './commands/robopark.js';
|
|
19
21
|
import { DEFAULT_CONFIG } from './kernel/config-schema.js';
|
|
20
|
-
const VERSION = '2.
|
|
22
|
+
const VERSION = '2.4.0';
|
|
21
23
|
const config = new Conf({
|
|
22
24
|
projectName: 'infinicode',
|
|
23
25
|
defaults: DEFAULT_CONFIG,
|
|
@@ -293,12 +295,31 @@ program
|
|
|
293
295
|
.option('--lan-port <port>', 'UDP discovery port for --lan (default 47915)')
|
|
294
296
|
.option('--tag <tag>', 'only discover peers carrying this tag (Tailscale ACL tag or LAN beacon tag)')
|
|
295
297
|
.option('--console', 'interactive slash-command console on the node (/nodes, /build, /activity …); on by default in a TTY')
|
|
298
|
+
.option('--dashboard', 'serve the RoboPark control web UI at http://<host>:<port>/ (fleet map, live activity, actions)')
|
|
296
299
|
.option('--gateway <url>', 'link to an InfiniBot gateway (ws://host:18789) — appear on its neural-mesh map')
|
|
297
300
|
.option('--gateway-token <token>', 'auth token for the InfiniBot gateway')
|
|
298
301
|
.option('--gateway-password <password>', 'auth password for the InfiniBot gateway')
|
|
299
302
|
.action(async (opts) => {
|
|
300
303
|
await serve(config, opts);
|
|
301
304
|
});
|
|
305
|
+
// ── RoboPark: fleet control overlay (also shipped as the `robopark` bin) ────
|
|
306
|
+
const roboparkCmd = program
|
|
307
|
+
.command('robopark')
|
|
308
|
+
.alias('rp')
|
|
309
|
+
.description('RoboPark fleet control — setup, fleet status, run, apply, dashboard (rides the mesh)');
|
|
310
|
+
attachRoboparkSubcommands(roboparkCmd, config);
|
|
311
|
+
// ── OpenKernel: one-shot scheduled maintenance ─────────────────────────────
|
|
312
|
+
program
|
|
313
|
+
.command('maintain <task-prompt>')
|
|
314
|
+
.description('Run one maintenance task and exit — meant for OS cron/Task Scheduler, not a daemon')
|
|
315
|
+
.option('--name <name>', 'node name for the report (defaults to this node\'s role, or "infinicode-node")')
|
|
316
|
+
.option('--task-name <name>', 'short label for the report (defaults to a slice of the task prompt)')
|
|
317
|
+
.option('--gateway <url>', 'InfiniBot gateway to push the report to (ws://host:18789)')
|
|
318
|
+
.option('--gateway-token <token>', 'auth token for the InfiniBot gateway')
|
|
319
|
+
.option('--gateway-password <password>', 'auth password for the InfiniBot gateway')
|
|
320
|
+
.action(async (taskPrompt, opts) => {
|
|
321
|
+
await maintain(config, taskPrompt, opts);
|
|
322
|
+
});
|
|
302
323
|
// ── infinicode: mesh — one-command MCP registration (no hand-edited JSON) ──
|
|
303
324
|
const meshCmd = program
|
|
304
325
|
.command('mesh')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type Conf from 'conf';
|
|
2
|
+
import type { InfinicodeConfig } from '../kernel/setup.js';
|
|
3
|
+
import type { MaintenanceReport, Kernel, RoleContext } from '../kernel/index.js';
|
|
4
|
+
export interface MaintainOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
gateway?: string;
|
|
7
|
+
gatewayToken?: string;
|
|
8
|
+
gatewayPassword?: string;
|
|
9
|
+
taskName?: string;
|
|
10
|
+
}
|
|
11
|
+
interface CachedTask {
|
|
12
|
+
taskPrompt: string;
|
|
13
|
+
taskName?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function loadLastMaintenanceTask(storageDir?: string): CachedTask | null;
|
|
16
|
+
/**
|
|
17
|
+
* Pure execution: run one maintenance task on an ALREADY-BUILT kernel and
|
|
18
|
+
* return the report — no push, no process.exit. Shared by the standalone
|
|
19
|
+
* `infinicode maintain` CLI command (builds its own short-lived kernel) and
|
|
20
|
+
* `infinicode serve`'s inbound-command poller (reuses the node's live
|
|
21
|
+
* kernel, since that process is already running one).
|
|
22
|
+
*/
|
|
23
|
+
export declare function runMaintenanceTask(kernel: Kernel, taskPrompt: string, opts: {
|
|
24
|
+
nodeId: string;
|
|
25
|
+
role: RoleContext | null;
|
|
26
|
+
taskName?: string;
|
|
27
|
+
}): Promise<MaintenanceReport>;
|
|
28
|
+
export declare function maintain(config: Conf<InfinicodeConfig>, taskPrompt: string, opts: MaintainOptions): Promise<void>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infinicode maintain — run one scheduled maintenance task and report the
|
|
3
|
+
* result, then exit. Meant to be invoked by OS-level cron / Task Scheduler on
|
|
4
|
+
* a monthly (or any) cadence — NOT a daemon. This deliberately does not reuse
|
|
5
|
+
* the autonomy GoalLoop: GoalLoop is in-memory/setInterval-based and resets on
|
|
6
|
+
* every process restart, which is wrong for a calendar cadence that must
|
|
7
|
+
* survive reboots and land on real dates. The OS scheduler already does that
|
|
8
|
+
* correctly; this command just needs to run once, cleanly, and report home.
|
|
9
|
+
*
|
|
10
|
+
* infinicode maintain "apply OS security patches, restart if required" \
|
|
11
|
+
* --name db-primary --gateway ws://100.x.y.z:18789 --gateway-token secret
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { buildKernelFromConfig } from '../kernel/setup.js';
|
|
17
|
+
import { loadRole, rolePreamble, loadOrCreateIdentity, MoltfedConnector, SilentLogger } from '../kernel/index.js';
|
|
18
|
+
function lastTaskFile(storageDir = join(process.cwd(), '.openkernel')) {
|
|
19
|
+
return join(storageDir, 'last-maintenance-task.json');
|
|
20
|
+
}
|
|
21
|
+
/** Cache the task text this device just ran LOCALLY — this is the only
|
|
22
|
+
* thing an on-demand "run now" trigger is allowed to replay (see
|
|
23
|
+
* loadLastMaintenanceTask). Never populated from anything network-supplied. */
|
|
24
|
+
function saveLastMaintenanceTask(task, storageDir) {
|
|
25
|
+
const dir = storageDir ?? join(process.cwd(), '.openkernel');
|
|
26
|
+
try {
|
|
27
|
+
if (!existsSync(dir))
|
|
28
|
+
mkdirSync(dir, { recursive: true });
|
|
29
|
+
writeFileSync(lastTaskFile(dir), JSON.stringify(task, null, 2));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// non-fatal — a re-trigger will just report "nothing cached yet"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function loadLastMaintenanceTask(storageDir) {
|
|
36
|
+
const file = lastTaskFile(storageDir);
|
|
37
|
+
if (!existsSync(file))
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pure execution: run one maintenance task on an ALREADY-BUILT kernel and
|
|
48
|
+
* return the report — no push, no process.exit. Shared by the standalone
|
|
49
|
+
* `infinicode maintain` CLI command (builds its own short-lived kernel) and
|
|
50
|
+
* `infinicode serve`'s inbound-command poller (reuses the node's live
|
|
51
|
+
* kernel, since that process is already running one).
|
|
52
|
+
*/
|
|
53
|
+
export async function runMaintenanceTask(kernel, taskPrompt, opts) {
|
|
54
|
+
const { nodeId, role } = opts;
|
|
55
|
+
const preamble = rolePreamble(role);
|
|
56
|
+
const prompt = preamble ? `${preamble}\n\n${taskPrompt}` : taskPrompt;
|
|
57
|
+
const taskName = opts.taskName ?? taskPrompt.slice(0, 60);
|
|
58
|
+
const startedAt = Date.now();
|
|
59
|
+
let ok = true;
|
|
60
|
+
let summary = '';
|
|
61
|
+
const errors = [];
|
|
62
|
+
try {
|
|
63
|
+
const mission = await kernel.execute({
|
|
64
|
+
description: taskName,
|
|
65
|
+
goal: taskPrompt,
|
|
66
|
+
tasks: [{
|
|
67
|
+
description: taskName,
|
|
68
|
+
capabilities: ['reasoning'],
|
|
69
|
+
input: { prompt, context: { proactive: true, maintenance: true, role: role?.role } },
|
|
70
|
+
}],
|
|
71
|
+
});
|
|
72
|
+
ok = mission.status === 'COMPLETED';
|
|
73
|
+
if (mission.error)
|
|
74
|
+
errors.push(mission.error);
|
|
75
|
+
for (const task of mission.tasks) {
|
|
76
|
+
if (task.error)
|
|
77
|
+
errors.push(task.error);
|
|
78
|
+
}
|
|
79
|
+
summary = mission.tasks
|
|
80
|
+
.map(t => t.output?.content)
|
|
81
|
+
.filter((c) => Boolean(c))
|
|
82
|
+
.join('\n\n')
|
|
83
|
+
.slice(0, 4000) || (ok ? 'completed with no textual output' : 'mission did not complete');
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
ok = false;
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
errors.push(message);
|
|
89
|
+
summary = `threw before completion: ${message}`;
|
|
90
|
+
}
|
|
91
|
+
const finishedAt = Date.now();
|
|
92
|
+
return {
|
|
93
|
+
nodeId,
|
|
94
|
+
role: role?.role ?? nodeId,
|
|
95
|
+
site: role?.site,
|
|
96
|
+
taskName,
|
|
97
|
+
startedAt,
|
|
98
|
+
finishedAt,
|
|
99
|
+
durationMs: finishedAt - startedAt,
|
|
100
|
+
ok,
|
|
101
|
+
summary,
|
|
102
|
+
errors: errors.length ? errors : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Open a MoltfedConnector just long enough to push one report, then close it.
|
|
106
|
+
* Does not start the periodic hw/status timers — this is a one-shot push, not
|
|
107
|
+
* a live mesh presence. */
|
|
108
|
+
async function pushReportOnce(gatewayUrl, report, opts) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
let settled = false;
|
|
111
|
+
const finish = (ok) => {
|
|
112
|
+
if (settled)
|
|
113
|
+
return;
|
|
114
|
+
settled = true;
|
|
115
|
+
connector.stop();
|
|
116
|
+
resolve(ok);
|
|
117
|
+
};
|
|
118
|
+
const connector = new MoltfedConnector({
|
|
119
|
+
gatewayUrl,
|
|
120
|
+
token: opts.token,
|
|
121
|
+
password: opts.password,
|
|
122
|
+
role: opts.role,
|
|
123
|
+
client: { id: report.nodeId, version: '1', platform: 'node', mode: 'maintenance' },
|
|
124
|
+
logger: new SilentLogger(),
|
|
125
|
+
// Not used for a one-shot push, but required by the connector's shape.
|
|
126
|
+
getNodeStatuses: () => [],
|
|
127
|
+
getHardware: () => undefined,
|
|
128
|
+
subscribeFrames: () => () => { },
|
|
129
|
+
});
|
|
130
|
+
// Poll for handshake completion, then fire the report once (connect() is
|
|
131
|
+
// async/event-driven internally with no public "connected" callback).
|
|
132
|
+
const poll = setInterval(() => {
|
|
133
|
+
if (connector.isConnected) {
|
|
134
|
+
clearInterval(poll);
|
|
135
|
+
connector.request('infinicode.push.report', report);
|
|
136
|
+
// Give the WS a beat to actually flush before closing.
|
|
137
|
+
setTimeout(() => finish(true), 250);
|
|
138
|
+
}
|
|
139
|
+
}, 100);
|
|
140
|
+
connector.start();
|
|
141
|
+
setTimeout(() => { clearInterval(poll); finish(false); }, 8000);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export async function maintain(config, taskPrompt, opts) {
|
|
145
|
+
const role = loadRole();
|
|
146
|
+
// Reuses the SAME identity `infinicode serve` on this device already
|
|
147
|
+
// persisted (.openkernel/node-identity.json) — so this report's nodeId
|
|
148
|
+
// matches the nodeId that device's nodes.status push uses, instead of the
|
|
149
|
+
// two diverging into separate fleet records for the same physical device.
|
|
150
|
+
const identity = loadOrCreateIdentity({ displayName: opts.name ?? role?.role });
|
|
151
|
+
const nodeId = identity.nodeId;
|
|
152
|
+
const taskName = opts.taskName ?? taskPrompt.slice(0, 60);
|
|
153
|
+
// Cache this as "the task this device runs" — the ONLY thing an on-demand
|
|
154
|
+
// "run maintenance now" trigger from the Fleet UI is allowed to replay
|
|
155
|
+
// later (see loadLastMaintenanceTask). Locally sourced only; a network
|
|
156
|
+
// trigger never carries or supplies task content.
|
|
157
|
+
saveLastMaintenanceTask({ taskPrompt, taskName });
|
|
158
|
+
console.log(chalk.bold(`\n infinicode maintain — ${chalk.cyan(taskName)}`));
|
|
159
|
+
console.log(chalk.dim(` node: ${identity.displayName} (${nodeId})${role ? ` · role: ${role.role}` : ''}`));
|
|
160
|
+
const kernel = buildKernelFromConfig(config, { logger: new SilentLogger() });
|
|
161
|
+
let report;
|
|
162
|
+
try {
|
|
163
|
+
report = await runMaintenanceTask(kernel, taskPrompt, { nodeId, role, taskName });
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
kernel.destroy();
|
|
167
|
+
}
|
|
168
|
+
const ok = report.ok;
|
|
169
|
+
console.log(ok ? chalk.green(` ✓ completed in ${report.durationMs}ms`) : chalk.red(` ✗ failed in ${report.durationMs}ms`));
|
|
170
|
+
if (report.errors?.length)
|
|
171
|
+
console.log(chalk.red(` ${report.errors.join('\n ')}`));
|
|
172
|
+
if (opts.gateway) {
|
|
173
|
+
const pushed = await pushReportOnce(opts.gateway, report, {
|
|
174
|
+
token: opts.gatewayToken,
|
|
175
|
+
password: opts.gatewayPassword,
|
|
176
|
+
role: role?.role ?? 'operator',
|
|
177
|
+
});
|
|
178
|
+
console.log(pushed
|
|
179
|
+
? chalk.green(` ✓ report pushed to ${opts.gateway}`)
|
|
180
|
+
: chalk.yellow(` ⚠ could not confirm report delivery to ${opts.gateway} (handshake timed out)`));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log(chalk.dim(' (no --gateway given — report was not pushed anywhere)'));
|
|
184
|
+
}
|
|
185
|
+
process.exit(ok ? 0 : 1);
|
|
186
|
+
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -70,7 +70,7 @@ export async function mcpCommand(config, opts) {
|
|
|
70
70
|
identity,
|
|
71
71
|
describe,
|
|
72
72
|
logger: stderrLogger,
|
|
73
|
-
options: { version, port, token, autoUpdate: fed.autoUpdate, applyConfig, autoPullConfig: role === 'satellite' },
|
|
73
|
+
options: { version, port, token, autoUpdate: fed.autoUpdate, applyConfig, autoPullConfig: role === 'satellite', coder: config.get('coder') },
|
|
74
74
|
});
|
|
75
75
|
// Let the kernel's slash-command surface reach the mesh (/nodes, /node, …).
|
|
76
76
|
kernel.attachFederation(federation);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type Conf from 'conf';
|
|
3
|
+
import type { InfinicodeConfig } from '../kernel/config-schema.js';
|
|
4
|
+
/** Attach the robopark verbs to a Commander command (the `robopark` program in
|
|
5
|
+
* the standalone bin, or the `infinicode robopark` group). */
|
|
6
|
+
export declare function attachRoboparkSubcommands(cmd: Command, config: Conf<InfinicodeConfig>): void;
|