oomi-ai 0.7.2 → 0.8.1
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/README.md +30 -5
- package/agent_instructions.md +71 -5
- package/bin/oomi-ai.js +223 -247
- package/lib/bridgeMaintenance.js +64 -0
- package/lib/bridgeService.js +180 -0
- package/lib/localAgent.js +3 -1
- package/lib/objectives.js +34 -0
- package/lib/personaApiClient.js +1 -1
- package/lib/routines.js +1 -1
- package/lib/spaces.js +5 -4
- package/package.json +6 -3
- package/skills/oomi/SKILL.md +91 -4
- package/skills/oomi/agent_instructions.md +84 -21
package/bin/oomi-ai.js
CHANGED
|
@@ -8,10 +8,13 @@ import net from 'net';
|
|
|
8
8
|
import { lookup as dnsLookup } from 'dns/promises';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import WebSocket from 'ws';
|
|
11
|
+
import { planPackageUpdate, isWorkerDescendant, queueRestart, readMaintenance, createMaintenanceController } from '../lib/bridgeMaintenance.js';
|
|
12
|
+
import { bridgeServiceConfig, createBridgeService, renderLaunchAgent } from '../lib/bridgeService.js';
|
|
11
13
|
import { LOCAL_PROTOCOL_MIN, LOCAL_PROTOCOL_MAX, probeGateway } from '../lib/gatewayProbe.js';
|
|
12
14
|
import { LocalAgentRuntime, localAgentConfig, runLocalAgent, writePrivateJson, startChannelWorker } from '../lib/localAgent.js';
|
|
13
15
|
import { validateAgentName, identityRequest, startIdentityWorker, handleIdentityCommand } from '../lib/agentIdentity.js';
|
|
14
16
|
import { handleSpacesCommand, compactHealth } from '../lib/spaces.js';
|
|
17
|
+
import { handleObjectivesCommand } from '../lib/objectives.js';
|
|
15
18
|
import { handleRoutinesCommand, startRoutineEventWorker } from '../lib/routines.js';
|
|
16
19
|
import { createPersonaApiClient } from '../lib/personaApiClient.js';
|
|
17
20
|
import { inferSpokenMetadataFromContent, normalizeSpokenMetadata } from '../lib/spokenMetadata.js';
|
|
@@ -59,7 +62,6 @@ const BRIDGE_GATEWAY_REQUEST_TIMEOUT_MS = parsePositiveInteger(
|
|
|
59
62
|
process.env.OOMI_BRIDGE_GATEWAY_REQUEST_TIMEOUT_MS,
|
|
60
63
|
30000
|
|
61
64
|
);
|
|
62
|
-
const BRIDGE_LAUNCHD_LABEL = 'ai.oomi.bridge';
|
|
63
65
|
const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
64
66
|
const BRIDGE_DEBUG_ENABLED = process.env.OOMI_BRIDGE_DEBUG === '1';
|
|
65
67
|
|
|
@@ -192,8 +194,7 @@ async function maybeNotifyUpdate(command) {
|
|
|
192
194
|
|
|
193
195
|
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
194
196
|
console.warn(`[oomi] Update available: oomi-ai ${currentVersion} -> ${latestVersion}`);
|
|
195
|
-
console.warn('[oomi]
|
|
196
|
-
console.warn('[oomi] Fallback update command: npm install -g oomi-ai@latest');
|
|
197
|
+
console.warn('[oomi] Read the safe update plan: oomi update');
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -201,11 +202,14 @@ function usage() {
|
|
|
201
202
|
console.log(`oomi <command>
|
|
202
203
|
|
|
203
204
|
Commands:
|
|
205
|
+
update
|
|
206
|
+
Check for a newer package and return a safe, approval-preserving update plan.
|
|
204
207
|
connect --runtime openclaw|hermes|command [--name CONNECTION] [--agent-name "Your name"] [--app-url URL]
|
|
205
208
|
Verify the selected local agent, pair once, start its bridge, and return an Auth Link.
|
|
206
209
|
Hermes: --profile PROFILE and --resume SESSION select the intended agent/session.
|
|
207
210
|
Other agents: --command '["/absolute/path/to/adapter"]' (JSON stdin / JSON stdout).
|
|
208
211
|
--cwd PATH selects the agent working directory; --name isolates multiple agents.
|
|
212
|
+
Installs automatic startup on macOS/Linux. --no-service opts into external supervision.
|
|
209
213
|
status [--name NAME]
|
|
210
214
|
Show broker connection and verified local agent health separately.
|
|
211
215
|
verify [--name NAME]
|
|
@@ -217,8 +221,10 @@ Commands:
|
|
|
217
221
|
identity restore --snapshot ID --output NEW_FOLDER [--connection NAME]
|
|
218
222
|
invite [--name NAME] [--app-url URL]
|
|
219
223
|
Generate a fresh Auth Link from saved pairing without changing agents.
|
|
220
|
-
bridge [start|ensure|stop|restart|ps] [--name NAME] [--detach]
|
|
221
|
-
Manage only the named agent's bridge.
|
|
224
|
+
bridge [start|ensure|stop|restart|repair|list|ps] [--name NAME] [--detach]
|
|
225
|
+
Manage only the named agent's bridge. Repair restores supervision without re-pairing.
|
|
226
|
+
bridge service [install|start|stop|restart|status|uninstall] [--name NAME]
|
|
227
|
+
Manage automatic restart and startup (macOS launchd / Linux systemd user).
|
|
222
228
|
|
|
223
229
|
init
|
|
224
230
|
Install Oomi agent instructions into OpenClaw AGENTS.md.
|
|
@@ -229,7 +235,7 @@ Commands:
|
|
|
229
235
|
openclaw bridge [start|ensure|stop|restart|ps]
|
|
230
236
|
Manage local OpenClaw-to-Oomi bridge lifecycle (singleton).
|
|
231
237
|
openclaw bridge service [install|start|stop|restart|status|uninstall]
|
|
232
|
-
|
|
238
|
+
Legacy alias for bridge service, with the same connection isolation.
|
|
233
239
|
openclaw profile init
|
|
234
240
|
Write a deterministic OpenClaw profile for local/dev or hosted setup flows.
|
|
235
241
|
openclaw profile apply
|
|
@@ -265,12 +271,18 @@ Commands:
|
|
|
265
271
|
updates
|
|
266
272
|
Read quiet state updates; does not start a model turn.
|
|
267
273
|
spaces list|catalog|show <id>|revisions <id>
|
|
268
|
-
spaces create|update|archive|unarchive|restore [id] --file PATH
|
|
274
|
+
spaces create|update|archive|unarchive|restore|upgrade [id] --file PATH
|
|
275
|
+
[--schema-version space.v3] Required for goals/streaks; v2 remains supported for sources
|
|
276
|
+
objectives list|show <id>
|
|
277
|
+
objectives history <id> [--before YYYY-MM-DD]
|
|
278
|
+
objectives create|update|respond [id] --file PATH
|
|
279
|
+
[--source-message ID] Owner instruction required for agent-recorded progress/corrections
|
|
269
280
|
Choose components from catalog. Writes require idempotencyKey and updates require expectedRevision.
|
|
270
281
|
|
|
271
282
|
context health [--window day|week|month|year|all|range] [--date YYYY-MM-DD]
|
|
272
283
|
[--from YYYY-MM-DD --to YYYY-MM-DD] [--shape summary|series|both]
|
|
273
284
|
[--group-by day|week|month|year] [--timezone IANA-zone] [--cursor YYYY-MM-DD]
|
|
285
|
+
[--provider all|apple_health|smart_scale]
|
|
274
286
|
[--metric steps|weight|sleep|canonical.metric] [--output PATH]
|
|
275
287
|
Show the latest account-linked health context available to this paired OpenClaw device.
|
|
276
288
|
context plugins [--plugin apple_calendar|apple_reminders|apple_contacts|apple_location|apple_notes|google_gmail|google_drive|google_calendar|bluetooth_devices]
|
|
@@ -623,6 +635,7 @@ async function handleContextHealthCommand(flags = {}) {
|
|
|
623
635
|
const payload = await client.getHealthContext({
|
|
624
636
|
window: flags.window, date: flags.date, from: flags.from, to: flags.to,
|
|
625
637
|
shape: flags.shape, groupBy: flags['group-by'], timezone: flags.timezone, cursor: flags.cursor,
|
|
638
|
+
provider: flags.provider,
|
|
626
639
|
});
|
|
627
640
|
const result = flags.metric ? compactHealth(payload, flags.metric) : payload;
|
|
628
641
|
if (flags.output) {
|
|
@@ -692,19 +705,16 @@ async function handleContextHealthSyncCommand(flags = {}) {
|
|
|
692
705
|
|
|
693
706
|
async function refreshBridgeForUpdate(flags = {}, options = {}) {
|
|
694
707
|
const logger = options.logger || null;
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
target: launchdStatus.target,
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
+
const service = currentBridgeService();
|
|
709
|
+
const deferred = deferLiveRestart({ print: false });
|
|
710
|
+
if (deferred) return { queued: true, maintenance: deferred };
|
|
711
|
+
assertIndependentMaintenance();
|
|
712
|
+
if (service.status().installed) {
|
|
713
|
+
logger?.(`Restarting supervised bridge ${service.config.target}.`);
|
|
714
|
+
await stopSupervisedBridge(service);
|
|
715
|
+
service.start({ rewrite: true });
|
|
716
|
+
incrementBridgeMetric('bridge_restart_count');
|
|
717
|
+
return { restarted: true, mode: 'service', target: service.config.target };
|
|
708
718
|
}
|
|
709
719
|
|
|
710
720
|
const running = findRunningBridgeProcess();
|
|
@@ -766,6 +776,7 @@ async function handleOpenclawRefreshCommand(flags = {}) {
|
|
|
766
776
|
return;
|
|
767
777
|
}
|
|
768
778
|
|
|
779
|
+
if (payload.bridge.queued) { console.log(payload.bridge.maintenance.message); return; }
|
|
769
780
|
console.log(`Oomi refresh complete (${payload.currentVersion})`);
|
|
770
781
|
if (payload.bridge.restarted) {
|
|
771
782
|
console.log(`Bridge restarted (${payload.bridge.mode}).`);
|
|
@@ -843,8 +854,15 @@ function resolveBridgeLiveLogPath() {
|
|
|
843
854
|
return bridgeConnectionName ? path.join(connectionDirectory(), 'logs', 'oomi-bridge-live.log') : resolveOpenclawBridgeLiveLogPath();
|
|
844
855
|
}
|
|
845
856
|
|
|
846
|
-
function
|
|
847
|
-
return
|
|
857
|
+
function currentBridgeService() {
|
|
858
|
+
return createBridgeService(bridgeServiceConfig({
|
|
859
|
+
name: bridgeConnectionName, stateDirectory: path.dirname(resolveBridgeStatePath()),
|
|
860
|
+
logPath: resolveBridgeLiveLogPath(), scriptPath: fileURLToPath(import.meta.url),
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function buildBridgeLaunchAgentPlist() {
|
|
865
|
+
return renderLaunchAgent(currentBridgeService().config);
|
|
848
866
|
}
|
|
849
867
|
|
|
850
868
|
function defaultDeviceId() {
|
|
@@ -2418,142 +2436,6 @@ async function stopBridgeProcesses() {
|
|
|
2418
2436
|
};
|
|
2419
2437
|
}
|
|
2420
2438
|
|
|
2421
|
-
function assertMacOSLaunchdAvailable() {
|
|
2422
|
-
if (process.platform !== 'darwin') {
|
|
2423
|
-
throw new Error('Bridge service manager is only supported on macOS (launchd).');
|
|
2424
|
-
}
|
|
2425
|
-
if (typeof process.getuid !== 'function') {
|
|
2426
|
-
throw new Error('Cannot resolve current UID for launchd domain.');
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
function launchctlDomain() {
|
|
2431
|
-
assertMacOSLaunchdAvailable();
|
|
2432
|
-
return `gui/${String(process.getuid())}`;
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
function launchctlServiceTarget() {
|
|
2436
|
-
return `${launchctlDomain()}/${BRIDGE_LAUNCHD_LABEL}`;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
function runLaunchctl(args, { allowFailure = false } = {}) {
|
|
2440
|
-
const result = spawnSync('launchctl', args, {
|
|
2441
|
-
encoding: 'utf8',
|
|
2442
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
2443
|
-
});
|
|
2444
|
-
const status = Number.isInteger(result.status) ? result.status : 1;
|
|
2445
|
-
const stdout = String(result.stdout || '').trim();
|
|
2446
|
-
const stderr = String(result.stderr || '').trim();
|
|
2447
|
-
if (status !== 0 && !allowFailure) {
|
|
2448
|
-
throw new Error(
|
|
2449
|
-
`launchctl ${args.join(' ')} failed (${status}): ${stderr || stdout || 'unknown launchctl error'}`
|
|
2450
|
-
);
|
|
2451
|
-
}
|
|
2452
|
-
return { status, stdout, stderr };
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
|
-
function buildBridgeLaunchAgentPlist() {
|
|
2456
|
-
const scriptPath = (() => {
|
|
2457
|
-
try {
|
|
2458
|
-
return fs.realpathSync(process.argv[1]);
|
|
2459
|
-
} catch {
|
|
2460
|
-
return process.argv[1];
|
|
2461
|
-
}
|
|
2462
|
-
})();
|
|
2463
|
-
const programArgs = [process.execPath, scriptPath, 'openclaw', 'bridge', 'start', '--service-managed'];
|
|
2464
|
-
const bridgeLogPath = resolveBridgeLiveLogPath();
|
|
2465
|
-
const argsXml = programArgs.map((arg) => `<string>${xmlEscape(arg)}</string>`).join('\n ');
|
|
2466
|
-
const openclawHome = resolveOpenclawHome();
|
|
2467
|
-
const openclawWorkspace = resolveOpenclawWorkspaceRoot();
|
|
2468
|
-
const envVars = {
|
|
2469
|
-
OOMI_SKIP_UPDATE_CHECK: '1',
|
|
2470
|
-
};
|
|
2471
|
-
if (process.env.OPENCLAW_HOME) {
|
|
2472
|
-
envVars.OPENCLAW_HOME = openclawHome;
|
|
2473
|
-
}
|
|
2474
|
-
if (process.env.OPENCLAW_WORKSPACE) {
|
|
2475
|
-
envVars.OPENCLAW_WORKSPACE = openclawWorkspace;
|
|
2476
|
-
}
|
|
2477
|
-
const envXml = Object.entries(envVars)
|
|
2478
|
-
.map(([key, value]) => ` <key>${xmlEscape(key)}</key>\n <string>${xmlEscape(value)}</string>`)
|
|
2479
|
-
.join('\n');
|
|
2480
|
-
|
|
2481
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2482
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2483
|
-
<plist version="1.0">
|
|
2484
|
-
<dict>
|
|
2485
|
-
<key>Label</key>
|
|
2486
|
-
<string>${xmlEscape(BRIDGE_LAUNCHD_LABEL)}</string>
|
|
2487
|
-
<key>ProgramArguments</key>
|
|
2488
|
-
<array>
|
|
2489
|
-
${argsXml}
|
|
2490
|
-
</array>
|
|
2491
|
-
<key>WorkingDirectory</key>
|
|
2492
|
-
<string>${xmlEscape(openclawHome)}</string>
|
|
2493
|
-
<key>RunAtLoad</key>
|
|
2494
|
-
<true/>
|
|
2495
|
-
<key>KeepAlive</key>
|
|
2496
|
-
<true/>
|
|
2497
|
-
<key>ThrottleInterval</key>
|
|
2498
|
-
<integer>5</integer>
|
|
2499
|
-
<key>EnvironmentVariables</key>
|
|
2500
|
-
<dict>
|
|
2501
|
-
${envXml}
|
|
2502
|
-
</dict>
|
|
2503
|
-
<key>StandardOutPath</key>
|
|
2504
|
-
<string>${xmlEscape(bridgeLogPath)}</string>
|
|
2505
|
-
<key>StandardErrorPath</key>
|
|
2506
|
-
<string>${xmlEscape(bridgeLogPath)}</string>
|
|
2507
|
-
</dict>
|
|
2508
|
-
</plist>
|
|
2509
|
-
`;
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
function readBridgeLaunchdStatus() {
|
|
2513
|
-
assertMacOSLaunchdAvailable();
|
|
2514
|
-
const plistPath = resolveBridgeLaunchAgentPlistPath();
|
|
2515
|
-
const target = launchctlServiceTarget();
|
|
2516
|
-
const printResult = runLaunchctl(['print', target], { allowFailure: true });
|
|
2517
|
-
const loaded = printResult.status === 0;
|
|
2518
|
-
const output = [printResult.stdout, printResult.stderr].filter(Boolean).join('\n');
|
|
2519
|
-
const pidMatch = output.match(/\bpid\s*=\s*(\d+)/);
|
|
2520
|
-
const lastExitMatch = output.match(/\blast exit code\s*=\s*(-?\d+)/i);
|
|
2521
|
-
|
|
2522
|
-
return {
|
|
2523
|
-
plistPath,
|
|
2524
|
-
target,
|
|
2525
|
-
installed: fs.existsSync(plistPath),
|
|
2526
|
-
loaded,
|
|
2527
|
-
pid: pidMatch ? Number(pidMatch[1]) : null,
|
|
2528
|
-
running: Boolean(pidMatch && Number(pidMatch[1]) > 0),
|
|
2529
|
-
lastExitCode: lastExitMatch ? Number(lastExitMatch[1]) : null,
|
|
2530
|
-
printOutput: output,
|
|
2531
|
-
};
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
function startBridgeLaunchdService() {
|
|
2535
|
-
assertMacOSLaunchdAvailable();
|
|
2536
|
-
const plistPath = resolveBridgeLaunchAgentPlistPath();
|
|
2537
|
-
if (!fs.existsSync(plistPath)) {
|
|
2538
|
-
throw new Error('Bridge service is not installed. Run: oomi openclaw bridge service install');
|
|
2539
|
-
}
|
|
2540
|
-
writeFile(plistPath, buildBridgeLaunchAgentPlist());
|
|
2541
|
-
const domain = launchctlDomain();
|
|
2542
|
-
const target = launchctlServiceTarget();
|
|
2543
|
-
runLaunchctl(['bootout', domain, plistPath], { allowFailure: true });
|
|
2544
|
-
runLaunchctl(['bootstrap', domain, plistPath]);
|
|
2545
|
-
runLaunchctl(['enable', target], { allowFailure: true });
|
|
2546
|
-
runLaunchctl(['kickstart', '-k', target], { allowFailure: true });
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
async function stopBridgeLaunchdService() {
|
|
2550
|
-
assertMacOSLaunchdAvailable();
|
|
2551
|
-
const plistPath = resolveBridgeLaunchAgentPlistPath();
|
|
2552
|
-
const domain = launchctlDomain();
|
|
2553
|
-
runLaunchctl(['bootout', domain, plistPath], { allowFailure: true });
|
|
2554
|
-
return stopBridgeProcesses();
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
2439
|
async function resolveBridgeRuntimeConfig(flags, bridgeState) {
|
|
2558
2440
|
const explicitBrokerHttp = String(flags['broker-http'] || '').trim();
|
|
2559
2441
|
const explicitBrokerWs = String(flags['broker-ws'] || '').trim();
|
|
@@ -2706,15 +2588,24 @@ async function startOpenclawBridge(flags) {
|
|
|
2706
2588
|
consecutiveFailures: 0,
|
|
2707
2589
|
pid: process.pid,
|
|
2708
2590
|
startedAt: bridgeNowIso(),
|
|
2591
|
+
packageVersion: currentPackageVersion(),
|
|
2592
|
+
maintenanceProtocol: 1,
|
|
2709
2593
|
});
|
|
2710
2594
|
|
|
2595
|
+
try { refreshSelectedSkill(bridgeState); } catch (err) { console.warn('Oomi skill refresh failed:', err.message); }
|
|
2711
2596
|
console.log(`Starting ${bridgeState.runtime || 'openclaw'} bridge: device=${deviceId}`);
|
|
2597
|
+
let backgroundTurns = 0;
|
|
2598
|
+
const deliverAgentEvent = async (turn) => {
|
|
2599
|
+
backgroundTurns++;
|
|
2600
|
+
try { return await (localRuntime ? localRuntime.turn(turn) : probeGateway({ gateway, deviceIdentity: gatewayDeviceIdentity, prepareFrame: prepareGatewayFrameForLocalGateway, turn, timeoutMs: 180000 })); }
|
|
2601
|
+
finally { backgroundTurns--; }
|
|
2602
|
+
};
|
|
2712
2603
|
const stopChannelWorker = localRuntime ? startChannelWorker(bridgeState, localRuntime) : () => {};
|
|
2713
2604
|
const stopIdentityWorker = startIdentityWorker({
|
|
2714
2605
|
state: { ...bridgeState, brokerHttp, deviceToken },
|
|
2715
2606
|
receiptPath: path.join(path.dirname(resolveBridgeStatePath()), 'oomi-identity-receipt.json'),
|
|
2716
2607
|
// Applying a specific settings revision is idempotent, unlike a user turn.
|
|
2717
|
-
deliver:
|
|
2608
|
+
deliver: deliverAgentEvent,
|
|
2718
2609
|
acknowledgeCommand: (identity) => `oomi identity acknowledge${bridgeConnectionName ? ` --connection ${bridgeConnectionName}` : ''} --identity-id ${identity.identityId} --revision ${identity.revision}`,
|
|
2719
2610
|
onError: () => updateBridgeStatus({ identitySyncError: 'Identity notice was interrupted. Read oomi identity get in your agent to apply and acknowledge the pending revision; no model retry was sent.' }),
|
|
2720
2611
|
});
|
|
@@ -2722,7 +2613,7 @@ async function startOpenclawBridge(flags) {
|
|
|
2722
2613
|
state: { ...bridgeState, brokerHttp, deviceToken },
|
|
2723
2614
|
connection: bridgeConnectionName,
|
|
2724
2615
|
receiptPath: path.join(path.dirname(resolveBridgeStatePath()), 'oomi-routine-receipt.json'),
|
|
2725
|
-
deliver:
|
|
2616
|
+
deliver: deliverAgentEvent,
|
|
2726
2617
|
onError: () => updateBridgeStatus({ routineSyncError: 'Routine updates are waiting for the runtime. They will retry.' }),
|
|
2727
2618
|
});
|
|
2728
2619
|
console.log(`Local gateway: ${gateway.gatewayUrl}`);
|
|
@@ -2732,6 +2623,19 @@ async function startOpenclawBridge(flags) {
|
|
|
2732
2623
|
);
|
|
2733
2624
|
|
|
2734
2625
|
const activeGatewaySockets = new Map();
|
|
2626
|
+
const activeGatewayRuns = new Set();
|
|
2627
|
+
let currentBrokerSocket;
|
|
2628
|
+
const maintenanceTick = createMaintenanceController({
|
|
2629
|
+
directory: path.dirname(resolveBridgeStatePath()),
|
|
2630
|
+
isIdle: () => !backgroundTurns && !localRuntime?.active.size && !localRuntime?.deliveries && !activeGatewayRuns.size
|
|
2631
|
+
&& !currentBrokerSocket?.bufferedAmount
|
|
2632
|
+
&& [...activeGatewaySockets.values()].every((session) => !session.pendingRequests?.size),
|
|
2633
|
+
isConnected: () => readBridgeStatus().status === 'connected',
|
|
2634
|
+
restart: () => currentBridgeService().restart(),
|
|
2635
|
+
});
|
|
2636
|
+
const maintenanceTimer = setInterval(() => {
|
|
2637
|
+
try { maintenanceTick(); } catch (err) { console.error('[bridge] maintenance:', err.message); }
|
|
2638
|
+
}, 250);
|
|
2735
2639
|
const reconnectState = {
|
|
2736
2640
|
attempt: 0,
|
|
2737
2641
|
timer: null,
|
|
@@ -3084,6 +2988,7 @@ async function startOpenclawBridge(flags) {
|
|
|
3084
2988
|
wsUrl.searchParams.set('token', deviceToken);
|
|
3085
2989
|
|
|
3086
2990
|
const brokerSocket = new WebSocket(wsUrl.toString());
|
|
2991
|
+
currentBrokerSocket = brokerSocket;
|
|
3087
2992
|
let actionCableHeartbeat = null;
|
|
3088
2993
|
|
|
3089
2994
|
const clearChallengeTimer = (sessionBridge) => {
|
|
@@ -3446,6 +3351,12 @@ async function startOpenclawBridge(flags) {
|
|
|
3446
3351
|
}
|
|
3447
3352
|
}
|
|
3448
3353
|
|
|
3354
|
+
if (gatewayPayload?.type === 'res' && gatewayPayload.ok && gatewayPayload.payload?.status === 'started' && gatewayPayload.payload?.runId) {
|
|
3355
|
+
activeGatewayRuns.add(gatewayPayload.payload.runId);
|
|
3356
|
+
}
|
|
3357
|
+
if (gatewayPayload?.event === 'chat' && ['final', 'error', 'aborted'].includes(gatewayPayload.payload?.state)) {
|
|
3358
|
+
activeGatewayRuns.delete(gatewayPayload.payload.runId);
|
|
3359
|
+
}
|
|
3449
3360
|
if (gatewayPayload?.event === 'chat' && gatewayPayload.payload?.state === 'final') {
|
|
3450
3361
|
updateBridgeStatus({ runtimeReady: true, lastReplyAt: bridgeNowIso() });
|
|
3451
3362
|
} else if (gatewayPayload?.event === 'chat' && gatewayPayload.payload?.state === 'error') {
|
|
@@ -3899,6 +3810,7 @@ async function startOpenclawBridge(flags) {
|
|
|
3899
3810
|
|
|
3900
3811
|
const markStopped = async (signal) => {
|
|
3901
3812
|
reconnectState.stopped = true;
|
|
3813
|
+
clearInterval(maintenanceTimer);
|
|
3902
3814
|
stopChannelWorker();
|
|
3903
3815
|
stopIdentityWorker();
|
|
3904
3816
|
stopRoutineWorker();
|
|
@@ -4022,11 +3934,6 @@ async function pairAndStartOpenclawBridge(flags) {
|
|
|
4022
3934
|
console.log('');
|
|
4023
3935
|
console.log('Share this exact response with the user (no extra text):');
|
|
4024
3936
|
console.log('----------------------------------------');
|
|
4025
|
-
const skillSource = resolveSkillSource();
|
|
4026
|
-
if (runtime === 'hermes') installSkill(skillSource, [path.join(state.localAgent.hermesHome, ...(state.localAgent.profile === 'default' ? [] : ['profiles', state.localAgent.profile]), 'skills')]);
|
|
4027
|
-
else if (runtime === 'openclaw') installSkill(skillSource, [resolveOpenclawSkillsDir()]);
|
|
4028
|
-
else if (flags['skills-dir']) installSkill(skillSource, [flags['skills-dir']]);
|
|
4029
|
-
console.log('Oomi tools: run oomi capabilities, then oomi instructions. Add --connection ' + (bridgeConnectionName || '(omit for default)') + ' to commands. Passive updates do not invoke your model.');
|
|
4030
3937
|
console.log('Oomi Connect Ready');
|
|
4031
3938
|
console.log(`Auth Link: ${inviteUrl}`);
|
|
4032
3939
|
console.log('----------------------------------------');
|
|
@@ -4142,6 +4049,8 @@ function printOpenclawBridgeStatus(flags) {
|
|
|
4142
4049
|
expiresAt: bridgeState.expiresAt || null,
|
|
4143
4050
|
},
|
|
4144
4051
|
runtime: runtimeStatus,
|
|
4052
|
+
supervision: currentBridgeService().status(),
|
|
4053
|
+
maintenance: readMaintenance(path.dirname(resolveBridgeStatePath())),
|
|
4145
4054
|
};
|
|
4146
4055
|
|
|
4147
4056
|
if (jsonOutput) {
|
|
@@ -4149,7 +4058,12 @@ function printOpenclawBridgeStatus(flags) {
|
|
|
4149
4058
|
return;
|
|
4150
4059
|
}
|
|
4151
4060
|
|
|
4061
|
+
if (payload.maintenance) console.log(`Maintenance: ${payload.maintenance.status} — ${payload.maintenance.message || ''}`);
|
|
4152
4062
|
console.log('Oomi Bridge Status');
|
|
4063
|
+
const supervision = payload.supervision;
|
|
4064
|
+
console.log(`Automatic startup: ${supervision.installed && supervision.enabled ? supervision.manager + ' (' + supervision.startsAt + ')' : 'off'}`);
|
|
4065
|
+
if (!supervision.installed || !supervision.enabled || !supervision.running) console.log(`Repair on this computer: oomi bridge repair${bridgeConnectionName ? ' --name ' + bridgeConnectionName : ''}`);
|
|
4066
|
+
if (supervision.reason) console.log(supervision.reason);
|
|
4153
4067
|
console.log('------------------');
|
|
4154
4068
|
console.log(`Bridge state: ${payload.bridgeStatePath}`);
|
|
4155
4069
|
console.log(`Runtime status: ${payload.bridgeStatusPath}`);
|
|
@@ -4261,82 +4175,111 @@ function printOpenclawPluginSetup(flags) {
|
|
|
4261
4175
|
}
|
|
4262
4176
|
}
|
|
4263
4177
|
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
const
|
|
4267
|
-
const
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
console.log(`Installed bridge launchd plist: ${plistPath}`);
|
|
4273
|
-
if (isTruthyFlag(flags['no-start'])) {
|
|
4274
|
-
console.log('Service install complete. Start with: oomi openclaw bridge service start');
|
|
4275
|
-
return;
|
|
4178
|
+
function listLocalBridgeConnections() {
|
|
4179
|
+
const selected = bridgeConnectionName;
|
|
4180
|
+
const names = fs.existsSync(resolveOpenclawBridgeStatePath()) ? [''] : [];
|
|
4181
|
+
const directory = process.env.OOMI_CONNECTIONS_DIR || path.join(os.homedir(), '.oomi', 'connections');
|
|
4182
|
+
if (fs.existsSync(directory)) {
|
|
4183
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
4184
|
+
if (entry.isDirectory() && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(entry.name)
|
|
4185
|
+
&& fs.existsSync(path.join(directory, entry.name, 'oomi-bridge.json'))) names.push(entry.name);
|
|
4276
4186
|
}
|
|
4277
|
-
startBridgeLaunchdService();
|
|
4278
|
-
incrementBridgeMetric('bridge_start_count');
|
|
4279
|
-
console.log(`Bridge service started: ${launchctlServiceTarget()}`);
|
|
4280
|
-
return;
|
|
4281
4187
|
}
|
|
4188
|
+
try {
|
|
4189
|
+
return names.sort().map((name) => {
|
|
4190
|
+
bridgeConnectionName = name;
|
|
4191
|
+
const state = readBridgeState();
|
|
4192
|
+
const running = findRunningBridgeProcess();
|
|
4193
|
+
return { name: name || null, runtime: state.runtime || 'openclaw', deviceId: state.deviceId || null,
|
|
4194
|
+
running: Boolean(running), supervision: currentBridgeService().status(),
|
|
4195
|
+
repairCommand: `oomi bridge repair${name ? ' --name ' + name : ''}` };
|
|
4196
|
+
});
|
|
4197
|
+
} finally { bridgeConnectionName = selected; }
|
|
4198
|
+
}
|
|
4282
4199
|
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
fs.unlinkSync(plistPath);
|
|
4287
|
-
}
|
|
4288
|
-
console.log(`Removed bridge launchd plist: ${plistPath}`);
|
|
4289
|
-
return;
|
|
4290
|
-
}
|
|
4200
|
+
function requireSavedBridge() {
|
|
4201
|
+
if (!readBridgeState().deviceToken) throw new Error('No saved connection. Run oomi connect with an explicit runtime first.');
|
|
4202
|
+
}
|
|
4291
4203
|
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
return;
|
|
4204
|
+
function assertIndependentMaintenance() {
|
|
4205
|
+
const running = findRunningBridgeProcess();
|
|
4206
|
+
if (running && isWorkerDescendant(running.pid)) {
|
|
4207
|
+
throw new Error('This command would stop the agent carrying this conversation. Use bridge repair/restart with a running 0.8.1+ bridge, or perform the first upgrade from an independent host terminal. The bridge was left unchanged.');
|
|
4297
4208
|
}
|
|
4209
|
+
}
|
|
4298
4210
|
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4211
|
+
function deferLiveRestart({ print = true } = {}) {
|
|
4212
|
+
const status = readBridgeStatus();
|
|
4213
|
+
const service = currentBridgeService().status();
|
|
4214
|
+
if (status.maintenanceProtocol !== 1 || !status.pid || !isBridgeProcess(status.pid)
|
|
4215
|
+
|| !service.running || service.pid !== status.pid || !service.enabled) return false;
|
|
4216
|
+
const request = queueRestart(path.dirname(resolveBridgeStatePath()), status.pid);
|
|
4217
|
+
if (print) console.log(JSON.stringify(request, null, 2));
|
|
4218
|
+
return request;
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
async function stopSupervisedBridge(service) {
|
|
4222
|
+
assertIndependentMaintenance();
|
|
4223
|
+
// Unload/disable before signalling the worker, otherwise KeepAlive immediately recreates it.
|
|
4224
|
+
service.stop();
|
|
4225
|
+
const stopped = await stopBridgeProcesses();
|
|
4226
|
+
if (stopped.stillAlive?.length) throw new Error('The existing bridge did not stop. Resolve it before reconnecting.');
|
|
4227
|
+
return stopped;
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
async function handleBridgeServiceCommand(actionRaw = '', flags = {}) {
|
|
4231
|
+
const action = String(actionRaw || 'status').trim().toLowerCase();
|
|
4232
|
+
const service = currentBridgeService();
|
|
4233
|
+
if (action === 'status') {
|
|
4234
|
+
console.log(JSON.stringify(service.status(), null, 2));
|
|
4307
4235
|
return;
|
|
4308
4236
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4237
|
+
if (!['install', 'start', 'stop', 'restart', 'uninstall'].includes(action)) {
|
|
4238
|
+
throw new Error('Use: oomi bridge service [install|start|stop|restart|status|uninstall] [--name NAME]');
|
|
4239
|
+
}
|
|
4240
|
+
if (['install', 'start', 'restart'].includes(action)) requireSavedBridge();
|
|
4241
|
+
if (['install', 'restart'].includes(action) && !isTruthyFlag(flags['no-start']) && deferLiveRestart()) return;
|
|
4242
|
+
if (action !== 'start') assertIndependentMaintenance();
|
|
4243
|
+
if (action === 'install') {
|
|
4244
|
+
service.install({ noStart: true });
|
|
4245
|
+
await stopSupervisedBridge(service);
|
|
4246
|
+
if (!isTruthyFlag(flags['no-start'])) service.start();
|
|
4247
|
+
} else if (action === 'start') {
|
|
4248
|
+
service.start(); // Idempotent: do not interrupt a healthy worker.
|
|
4249
|
+
} else if (action === 'restart') {
|
|
4250
|
+
await stopSupervisedBridge(service);
|
|
4251
|
+
service.start({ rewrite: true });
|
|
4313
4252
|
incrementBridgeMetric('bridge_restart_count');
|
|
4314
|
-
|
|
4315
|
-
|
|
4253
|
+
} else if (action === 'stop') {
|
|
4254
|
+
await stopSupervisedBridge(service);
|
|
4255
|
+
} else if (action === 'uninstall') {
|
|
4256
|
+
await stopSupervisedBridge(service);
|
|
4257
|
+
service.uninstall();
|
|
4316
4258
|
}
|
|
4259
|
+
const result = service.status();
|
|
4260
|
+
console.log(JSON.stringify({ action, ...result }, null, 2));
|
|
4261
|
+
}
|
|
4317
4262
|
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
console.log(
|
|
4325
|
-
console.log(`Installed: ${status.installed ? 'yes' : 'no'}`);
|
|
4326
|
-
console.log(`Loaded: ${status.loaded ? 'yes' : 'no'}`);
|
|
4327
|
-
console.log(`Running: ${status.running ? 'yes' : 'no'}`);
|
|
4328
|
-
if (status.pid) {
|
|
4329
|
-
console.log(`PID: ${status.pid}`);
|
|
4330
|
-
}
|
|
4331
|
-
if (status.lastExitCode !== null) {
|
|
4332
|
-
console.log(`Last exit code: ${status.lastExitCode}`);
|
|
4333
|
-
}
|
|
4334
|
-
return;
|
|
4263
|
+
async function repairBridge(flags = {}) {
|
|
4264
|
+
requireSavedBridge();
|
|
4265
|
+
if (!isTruthyFlag(flags['no-start']) && deferLiveRestart()) return;
|
|
4266
|
+
await handleBridgeServiceCommand('install', flags);
|
|
4267
|
+
if (!isTruthyFlag(flags['no-start'])) {
|
|
4268
|
+
await waitForBridgeConnection({});
|
|
4269
|
+
console.log('Oomi bridge restored with automatic startup. Pairing and conversations preserved.');
|
|
4335
4270
|
}
|
|
4271
|
+
}
|
|
4336
4272
|
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
)
|
|
4273
|
+
async function waitForBridgeConnection(child) {
|
|
4274
|
+
const deadline = Date.now() + 20000;
|
|
4275
|
+
while (Date.now() < deadline) {
|
|
4276
|
+
const status = readBridgeStatus();
|
|
4277
|
+
if (status.pid && (!child.pid || status.pid === child.pid) && isPidAlive(status.pid) && status.status === 'connected') return;
|
|
4278
|
+
if ((!child.pid || status.pid === child.pid) && status.status === 'error') throw new Error(status.lastErrorMessage || 'Bridge startup failed.');
|
|
4279
|
+
if (child.pid && !isPidAlive(child.pid)) throw new Error(`Bridge exited. Check ${resolveBridgeLiveLogPath()}.`);
|
|
4280
|
+
await delay(200);
|
|
4281
|
+
}
|
|
4282
|
+
throw new Error(`The bridge did not connect yet. Automatic recovery may still be retrying. Check oomi status${bridgeConnectionName ? ' --name ' + bridgeConnectionName : ''} and ${resolveBridgeLiveLogPath()}.`);
|
|
4340
4283
|
}
|
|
4341
4284
|
|
|
4342
4285
|
async function startBridgeLifecycle(flags = {}) {
|
|
@@ -4345,6 +4288,16 @@ async function startBridgeLifecycle(flags = {}) {
|
|
|
4345
4288
|
throw new Error('Detached bridge mode cannot be combined with --service-managed.');
|
|
4346
4289
|
}
|
|
4347
4290
|
|
|
4291
|
+
if (!serviceManaged) {
|
|
4292
|
+
const service = currentBridgeService();
|
|
4293
|
+
if (service.status().installed) {
|
|
4294
|
+
if (isTruthyFlag(flags.detach)) throw new Error('This connection has a supervisor. Use bridge start, or uninstall its service before using --detach.');
|
|
4295
|
+
service.start();
|
|
4296
|
+
console.log(`Bridge service started: ${service.config.target}`);
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4348
4301
|
if (Boolean(flags.detach)) {
|
|
4349
4302
|
const detachedFlags = { ...flags };
|
|
4350
4303
|
delete detachedFlags.detach;
|
|
@@ -4386,6 +4339,14 @@ async function startBridgeLifecycle(flags = {}) {
|
|
|
4386
4339
|
async function handleBridgeLifecycleCommand(flags = {}, actionRaw = '') {
|
|
4387
4340
|
const action = String(actionRaw || 'start').trim().toLowerCase();
|
|
4388
4341
|
|
|
4342
|
+
if (action === 'list') { console.log(JSON.stringify(listLocalBridgeConnections(), null, 2)); return; }
|
|
4343
|
+
if (action === 'repair') return repairBridge(flags);
|
|
4344
|
+
if (['stop', 'restart'].includes(action) && currentBridgeService().status().installed) {
|
|
4345
|
+
return handleBridgeServiceCommand(action, flags);
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
if (['stop', 'restart'].includes(action)) assertIndependentMaintenance();
|
|
4349
|
+
|
|
4389
4350
|
if (action === 'start' || action === 'ensure') {
|
|
4390
4351
|
await startBridgeLifecycle(flags);
|
|
4391
4352
|
return;
|
|
@@ -4436,7 +4397,7 @@ async function handleBridgeLifecycleCommand(flags = {}, actionRaw = '') {
|
|
|
4436
4397
|
}
|
|
4437
4398
|
|
|
4438
4399
|
throw new Error(
|
|
4439
|
-
`Unknown bridge action: ${action}. Use: oomi
|
|
4400
|
+
`Unknown bridge action: ${action}. Use: oomi bridge [start|ensure|stop|restart|repair|list|ps]`
|
|
4440
4401
|
);
|
|
4441
4402
|
}
|
|
4442
4403
|
|
|
@@ -4459,10 +4420,26 @@ async function verifyLocalAgent(state) {
|
|
|
4459
4420
|
return { runtime: state.runtime || 'openclaw', ...result };
|
|
4460
4421
|
}
|
|
4461
4422
|
|
|
4423
|
+
function refreshSelectedSkill(state, flags = {}) {
|
|
4424
|
+
const source = resolveSkillSource();
|
|
4425
|
+
if (state.runtime === 'hermes' && state.localAgent?.hermesHome) {
|
|
4426
|
+
installSkill(source, [path.join(state.localAgent.hermesHome, ...(state.localAgent.profile === 'default' ? [] : ['profiles', state.localAgent.profile]), 'skills')]);
|
|
4427
|
+
} else if (!state.runtime || state.runtime === 'openclaw') installSkill(source, [resolveOpenclawSkillsDir()]);
|
|
4428
|
+
else if (flags['skills-dir']) installSkill(source, [flags['skills-dir']]);
|
|
4429
|
+
}
|
|
4430
|
+
|
|
4462
4431
|
async function connectLocalAgent(flags) {
|
|
4432
|
+
assertIndependentMaintenance();
|
|
4463
4433
|
const agentName = flags['agent-name'] === undefined ? undefined : validateAgentName(flags['agent-name']);
|
|
4464
4434
|
const runtime = String(flags.runtime || '');
|
|
4465
4435
|
if (!['openclaw', 'hermes', 'command'].includes(runtime)) throw new Error('Specify --runtime openclaw, hermes, or command. Never select a different agent just because it is installed.');
|
|
4436
|
+
const service = currentBridgeService();
|
|
4437
|
+
const unmanaged = isTruthyFlag(flags['no-service']);
|
|
4438
|
+
if (!unmanaged) {
|
|
4439
|
+
service.preflight();
|
|
4440
|
+
} else if (service.status().installed) {
|
|
4441
|
+
throw new Error('This connection already has automatic startup. Uninstall its service before using --no-service.');
|
|
4442
|
+
}
|
|
4466
4443
|
const previous = readBridgeState();
|
|
4467
4444
|
if (previous.runtime && previous.runtime !== runtime) throw new Error('This connection belongs to a different runtime. Choose a different --name.');
|
|
4468
4445
|
const appUrl = String(flags['app-url'] || previous.appUrl || 'https://www.oomi.ai').replace(/\/$/, '');
|
|
@@ -4513,24 +4490,18 @@ async function connectLocalAgent(flags) {
|
|
|
4513
4490
|
if (error.status !== 404 || agentName) throw error;
|
|
4514
4491
|
console.warn('This Oomi server does not support agent settings yet. Connecting without shared settings.');
|
|
4515
4492
|
}
|
|
4516
|
-
//
|
|
4517
|
-
|
|
4518
|
-
const stopped =
|
|
4493
|
+
// Install/update only this connection's supervisor, retaining its saved identity and session.
|
|
4494
|
+
if (!unmanaged) service.install({ noStart: true });
|
|
4495
|
+
const stopped = unmanaged ? await stopBridgeProcesses() : await stopSupervisedBridge(service);
|
|
4519
4496
|
if (stopped.stillAlive?.length) throw new Error('The existing bridge did not stop. Resolve it before reconnecting.');
|
|
4520
4497
|
writeBridgeState(state);
|
|
4521
4498
|
updateBridgeStatus({ status: 'starting', runtime, runtimeReady: true, verifiedAt: state.verifiedAt, lastErrorMessage: '' });
|
|
4522
|
-
const child =
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
const status = readBridgeStatus();
|
|
4527
|
-
if (status.pid && (!child.pid || status.pid === child.pid) && status.status === 'connected') { connected = true; break; }
|
|
4528
|
-
if ((!child.pid || status.pid === child.pid) && status.status === 'error') throw new Error(status.lastErrorMessage || 'Bridge startup failed.');
|
|
4529
|
-
if (child.pid && !isPidAlive(child.pid)) throw new Error(`Bridge exited. Check ${resolveBridgeLiveLogPath()}.`);
|
|
4530
|
-
await delay(200);
|
|
4531
|
-
}
|
|
4532
|
-
if (!connected) throw new Error('The local agent replied, but the Oomi bridge did not connect. Run oomi status for this connection.');
|
|
4499
|
+
const child = unmanaged ? startBridgeDetachedProcess({ 'app-url': appUrl }) : (service.start(), {});
|
|
4500
|
+
await waitForBridgeConnection(child);
|
|
4501
|
+
console.log(unmanaged ? 'Automatic startup: off (--no-service); your host supervisor must manage recovery.'
|
|
4502
|
+
: `Automatic startup: ${service.config.platform === 'darwin' ? 'macOS login service' : 'Linux user service'}; restarts after worker exits.`);
|
|
4533
4503
|
const invite = await requestConnectInviteLink({ backendHttp: state.brokerHttp, appUrl, sessionKey: state.sessionKey, deviceToken: state.deviceToken });
|
|
4504
|
+
refreshSelectedSkill(state, flags);
|
|
4534
4505
|
console.log(`Verified agent: ${runtime}${state.localAgent?.profile ? ` (${state.localAgent.profile})` : ''}`);
|
|
4535
4506
|
console.log('Oomi Connect Ready');
|
|
4536
4507
|
console.log(`Auth Link: ${invite.inviteUrl}`);
|
|
@@ -4552,11 +4523,13 @@ async function main() {
|
|
|
4552
4523
|
if (connection && !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(connection)) throw new Error('Connection name must use lowercase letters, numbers, underscores, or hyphens.');
|
|
4553
4524
|
bridgeConnectionName = connection || '';
|
|
4554
4525
|
|
|
4526
|
+
if (command === 'update') { console.log(JSON.stringify(planPackageUpdate(currentPackageVersion(), await fetchLatestPublishedVersion('oomi-ai')), null, 2)); return; }
|
|
4555
4527
|
if (command === 'instructions') { console.log(fs.readFileSync(resolveInstructionsFile(), 'utf8')); return; }
|
|
4556
4528
|
if (command === 'capabilities') {
|
|
4557
|
-
console.log(JSON.stringify({ instructions: 'oomi instructions', skill: path.join(PACKAGE_ROOT, 'skills/oomi/SKILL.md'), connectionSelector: '--connection NAME',
|
|
4558
|
-
spaces: { catalog: 'oomi spaces catalog', list: 'oomi spaces list', create: 'oomi spaces create --file space.json', update: 'oomi spaces update ID --file change.json', revisions: 'oomi spaces revisions ID', guide: 'oomi instructions' },
|
|
4529
|
+
console.log(JSON.stringify({ instructions: 'oomi instructions', packageUpdate: 'oomi update', skill: path.join(PACKAGE_ROOT, 'skills/oomi/SKILL.md'), connectionSelector: '--connection NAME',
|
|
4530
|
+
spaces: { catalog: 'oomi spaces catalog', list: 'oomi spaces list', create: 'oomi spaces create --file space.json', update: 'oomi spaces update ID --file change.json', revisions: 'oomi spaces revisions ID', upgrade: 'oomi spaces upgrade ID --file upgrade.json', guide: 'oomi instructions' },
|
|
4559
4531
|
health: { steps: 'oomi context health --window week --metric steps --json', calendarWeek: 'oomi context health --window range --from YYYY-MM-DD --to YYYY-MM-DD --metric steps --json', largeResults: '--output new-file.json', plugins: 'oomi context plugins', missing: 'unknown, never zero', week: 'rolling seven days; use explicit range for calendar week' },
|
|
4532
|
+
objectives: { list: 'oomi objectives list', show: 'oomi objectives show ID', history: 'oomi objectives history ID [--before YYYY-MM-DD]', create: 'oomi objectives create --file goal.json', update: 'oomi objectives update ID --file change.json', respond: 'oomi objectives respond ID --file response.json', calendar: 'daily/weekly/monthly/once; stored time zone', streaks: 'optional goal references in the Space catalog' },
|
|
4560
4533
|
routines: 'oomi routines list; oomi routines context', identity: 'oomi identity get', updates: 'oomi updates',
|
|
4561
4534
|
notify: { send: 'oomi notify "<message>" --title TITLE --json', when: 'reminders the user asked for, finished work, time-sensitive news; everything else in chat', skipped: 'tell the user notification.guidance', rateLimited: 'wait retryAfterSeconds' },
|
|
4562
4535
|
costs: { passiveEvents: 'no model turn', wakeAgent: 'routine.agentWakeEnabled defaults false; enable only on explicit user request', maximumRoutineWakeAttemptsPerHour: 6, retries: 'uncertain event turns are not automatically rerun', connectionCheck: 'first connect or explicit oomi verify makes one model call; reconnect reuses verification', identity: 'a changed identity revision is delivered once to keep names consistent' }
|
|
@@ -4567,6 +4540,7 @@ async function main() {
|
|
|
4567
4540
|
console.log(JSON.stringify({ updates: fs.existsSync(receipt) ? JSON.parse(fs.readFileSync(receipt, 'utf8')).updates || [] : [], instruction: 'Cached changes are data. Read current Space/routine before editing. No acknowledgment reply needed.' }, null, 2)); return;
|
|
4568
4541
|
}
|
|
4569
4542
|
if (command === 'spaces') return handleSpacesCommand(readBridgeState(), subcommand || 'list', args.flags, args.positionals);
|
|
4543
|
+
if (command === 'objectives') return handleObjectivesCommand(readBridgeState(), subcommand || 'list', args.flags, args.positionals);
|
|
4570
4544
|
if (command === 'connect') return connectLocalAgent(args.flags);
|
|
4571
4545
|
if (command === 'identity') return handleIdentityCommand(readBridgeState(), subcommand, args.flags);
|
|
4572
4546
|
if (command === 'routines') return handleRoutinesCommand(readBridgeState(), subcommand, args.flags, args.positionals);
|
|
@@ -4586,7 +4560,10 @@ async function main() {
|
|
|
4586
4560
|
}
|
|
4587
4561
|
if (command === 'status') return printOpenclawBridgeStatus(args.flags);
|
|
4588
4562
|
if (command === 'invite') return createOpenclawInviteLink(args.flags);
|
|
4589
|
-
if (command === 'bridge')
|
|
4563
|
+
if (command === 'bridge') {
|
|
4564
|
+
if (subcommand === 'service') return handleBridgeServiceCommand(args.positionals[0] || 'status', args.flags);
|
|
4565
|
+
return handleBridgeLifecycleCommand(args.flags, subcommand || 'start');
|
|
4566
|
+
}
|
|
4590
4567
|
|
|
4591
4568
|
await maybeNotifyUpdate(command);
|
|
4592
4569
|
|
|
@@ -4612,7 +4589,6 @@ async function main() {
|
|
|
4612
4589
|
if (command === 'openclaw' && subcommand === 'bridge') {
|
|
4613
4590
|
const bridgeAction = String(args.positionals[0] || 'start').trim().toLowerCase();
|
|
4614
4591
|
if (bridgeAction === 'service') {
|
|
4615
|
-
if (bridgeConnectionName) throw new Error('Named connections use oomi bridge start --name NAME with your host process supervisor. The legacy OpenClaw service is only for the default connection.');
|
|
4616
4592
|
const serviceAction = args.positionals[1] || 'status';
|
|
4617
4593
|
await handleBridgeServiceCommand(serviceAction, args.flags);
|
|
4618
4594
|
return;
|