oomi-ai 0.7.2 → 0.8.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/README.md +15 -3
- package/agent_instructions.md +57 -4
- package/bin/oomi-ai.js +147 -238
- package/lib/bridgeService.js +173 -0
- 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 +5 -3
- package/skills/oomi/SKILL.md +77 -3
- package/skills/oomi/agent_instructions.md +70 -20
package/bin/oomi-ai.js
CHANGED
|
@@ -8,10 +8,12 @@ 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 { bridgeServiceConfig, createBridgeService, renderLaunchAgent } from '../lib/bridgeService.js';
|
|
11
12
|
import { LOCAL_PROTOCOL_MIN, LOCAL_PROTOCOL_MAX, probeGateway } from '../lib/gatewayProbe.js';
|
|
12
13
|
import { LocalAgentRuntime, localAgentConfig, runLocalAgent, writePrivateJson, startChannelWorker } from '../lib/localAgent.js';
|
|
13
14
|
import { validateAgentName, identityRequest, startIdentityWorker, handleIdentityCommand } from '../lib/agentIdentity.js';
|
|
14
15
|
import { handleSpacesCommand, compactHealth } from '../lib/spaces.js';
|
|
16
|
+
import { handleObjectivesCommand } from '../lib/objectives.js';
|
|
15
17
|
import { handleRoutinesCommand, startRoutineEventWorker } from '../lib/routines.js';
|
|
16
18
|
import { createPersonaApiClient } from '../lib/personaApiClient.js';
|
|
17
19
|
import { inferSpokenMetadataFromContent, normalizeSpokenMetadata } from '../lib/spokenMetadata.js';
|
|
@@ -59,7 +61,6 @@ const BRIDGE_GATEWAY_REQUEST_TIMEOUT_MS = parsePositiveInteger(
|
|
|
59
61
|
process.env.OOMI_BRIDGE_GATEWAY_REQUEST_TIMEOUT_MS,
|
|
60
62
|
30000
|
|
61
63
|
);
|
|
62
|
-
const BRIDGE_LAUNCHD_LABEL = 'ai.oomi.bridge';
|
|
63
64
|
const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
64
65
|
const BRIDGE_DEBUG_ENABLED = process.env.OOMI_BRIDGE_DEBUG === '1';
|
|
65
66
|
|
|
@@ -206,6 +207,7 @@ Commands:
|
|
|
206
207
|
Hermes: --profile PROFILE and --resume SESSION select the intended agent/session.
|
|
207
208
|
Other agents: --command '["/absolute/path/to/adapter"]' (JSON stdin / JSON stdout).
|
|
208
209
|
--cwd PATH selects the agent working directory; --name isolates multiple agents.
|
|
210
|
+
Installs automatic startup on macOS/Linux. --no-service opts into external supervision.
|
|
209
211
|
status [--name NAME]
|
|
210
212
|
Show broker connection and verified local agent health separately.
|
|
211
213
|
verify [--name NAME]
|
|
@@ -217,8 +219,10 @@ Commands:
|
|
|
217
219
|
identity restore --snapshot ID --output NEW_FOLDER [--connection NAME]
|
|
218
220
|
invite [--name NAME] [--app-url URL]
|
|
219
221
|
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.
|
|
222
|
+
bridge [start|ensure|stop|restart|repair|list|ps] [--name NAME] [--detach]
|
|
223
|
+
Manage only the named agent's bridge. Repair restores supervision without re-pairing.
|
|
224
|
+
bridge service [install|start|stop|restart|status|uninstall] [--name NAME]
|
|
225
|
+
Manage automatic restart and startup (macOS launchd / Linux systemd user).
|
|
222
226
|
|
|
223
227
|
init
|
|
224
228
|
Install Oomi agent instructions into OpenClaw AGENTS.md.
|
|
@@ -229,7 +233,7 @@ Commands:
|
|
|
229
233
|
openclaw bridge [start|ensure|stop|restart|ps]
|
|
230
234
|
Manage local OpenClaw-to-Oomi bridge lifecycle (singleton).
|
|
231
235
|
openclaw bridge service [install|start|stop|restart|status|uninstall]
|
|
232
|
-
|
|
236
|
+
Legacy alias for bridge service, with the same connection isolation.
|
|
233
237
|
openclaw profile init
|
|
234
238
|
Write a deterministic OpenClaw profile for local/dev or hosted setup flows.
|
|
235
239
|
openclaw profile apply
|
|
@@ -265,12 +269,18 @@ Commands:
|
|
|
265
269
|
updates
|
|
266
270
|
Read quiet state updates; does not start a model turn.
|
|
267
271
|
spaces list|catalog|show <id>|revisions <id>
|
|
268
|
-
spaces create|update|archive|unarchive|restore [id] --file PATH
|
|
272
|
+
spaces create|update|archive|unarchive|restore|upgrade [id] --file PATH
|
|
273
|
+
[--schema-version space.v3] Required for goals/streaks; v2 remains supported for sources
|
|
274
|
+
objectives list|show <id>
|
|
275
|
+
objectives history <id> [--before YYYY-MM-DD]
|
|
276
|
+
objectives create|update|respond [id] --file PATH
|
|
277
|
+
[--source-message ID] Owner instruction required for agent-recorded progress/corrections
|
|
269
278
|
Choose components from catalog. Writes require idempotencyKey and updates require expectedRevision.
|
|
270
279
|
|
|
271
280
|
context health [--window day|week|month|year|all|range] [--date YYYY-MM-DD]
|
|
272
281
|
[--from YYYY-MM-DD --to YYYY-MM-DD] [--shape summary|series|both]
|
|
273
282
|
[--group-by day|week|month|year] [--timezone IANA-zone] [--cursor YYYY-MM-DD]
|
|
283
|
+
[--provider all|apple_health|smart_scale]
|
|
274
284
|
[--metric steps|weight|sleep|canonical.metric] [--output PATH]
|
|
275
285
|
Show the latest account-linked health context available to this paired OpenClaw device.
|
|
276
286
|
context plugins [--plugin apple_calendar|apple_reminders|apple_contacts|apple_location|apple_notes|google_gmail|google_drive|google_calendar|bluetooth_devices]
|
|
@@ -623,6 +633,7 @@ async function handleContextHealthCommand(flags = {}) {
|
|
|
623
633
|
const payload = await client.getHealthContext({
|
|
624
634
|
window: flags.window, date: flags.date, from: flags.from, to: flags.to,
|
|
625
635
|
shape: flags.shape, groupBy: flags['group-by'], timezone: flags.timezone, cursor: flags.cursor,
|
|
636
|
+
provider: flags.provider,
|
|
626
637
|
});
|
|
627
638
|
const result = flags.metric ? compactHealth(payload, flags.metric) : payload;
|
|
628
639
|
if (flags.output) {
|
|
@@ -692,19 +703,13 @@ async function handleContextHealthSyncCommand(flags = {}) {
|
|
|
692
703
|
|
|
693
704
|
async function refreshBridgeForUpdate(flags = {}, options = {}) {
|
|
694
705
|
const logger = options.logger || null;
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
return {
|
|
703
|
-
restarted: true,
|
|
704
|
-
mode: 'service',
|
|
705
|
-
target: launchdStatus.target,
|
|
706
|
-
};
|
|
707
|
-
}
|
|
706
|
+
const service = currentBridgeService();
|
|
707
|
+
if (service.status().installed) {
|
|
708
|
+
logger?.(`Restarting supervised bridge ${service.config.target}.`);
|
|
709
|
+
await stopSupervisedBridge(service);
|
|
710
|
+
service.start({ rewrite: true });
|
|
711
|
+
incrementBridgeMetric('bridge_restart_count');
|
|
712
|
+
return { restarted: true, mode: 'service', target: service.config.target };
|
|
708
713
|
}
|
|
709
714
|
|
|
710
715
|
const running = findRunningBridgeProcess();
|
|
@@ -843,8 +848,15 @@ function resolveBridgeLiveLogPath() {
|
|
|
843
848
|
return bridgeConnectionName ? path.join(connectionDirectory(), 'logs', 'oomi-bridge-live.log') : resolveOpenclawBridgeLiveLogPath();
|
|
844
849
|
}
|
|
845
850
|
|
|
846
|
-
function
|
|
847
|
-
return
|
|
851
|
+
function currentBridgeService() {
|
|
852
|
+
return createBridgeService(bridgeServiceConfig({
|
|
853
|
+
name: bridgeConnectionName, stateDirectory: path.dirname(resolveBridgeStatePath()),
|
|
854
|
+
logPath: resolveBridgeLiveLogPath(), scriptPath: fileURLToPath(import.meta.url),
|
|
855
|
+
}));
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function buildBridgeLaunchAgentPlist() {
|
|
859
|
+
return renderLaunchAgent(currentBridgeService().config);
|
|
848
860
|
}
|
|
849
861
|
|
|
850
862
|
function defaultDeviceId() {
|
|
@@ -2418,142 +2430,6 @@ async function stopBridgeProcesses() {
|
|
|
2418
2430
|
};
|
|
2419
2431
|
}
|
|
2420
2432
|
|
|
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
2433
|
async function resolveBridgeRuntimeConfig(flags, bridgeState) {
|
|
2558
2434
|
const explicitBrokerHttp = String(flags['broker-http'] || '').trim();
|
|
2559
2435
|
const explicitBrokerWs = String(flags['broker-ws'] || '').trim();
|
|
@@ -4142,6 +4018,7 @@ function printOpenclawBridgeStatus(flags) {
|
|
|
4142
4018
|
expiresAt: bridgeState.expiresAt || null,
|
|
4143
4019
|
},
|
|
4144
4020
|
runtime: runtimeStatus,
|
|
4021
|
+
supervision: currentBridgeService().status(),
|
|
4145
4022
|
};
|
|
4146
4023
|
|
|
4147
4024
|
if (jsonOutput) {
|
|
@@ -4150,6 +4027,10 @@ function printOpenclawBridgeStatus(flags) {
|
|
|
4150
4027
|
}
|
|
4151
4028
|
|
|
4152
4029
|
console.log('Oomi Bridge Status');
|
|
4030
|
+
const supervision = payload.supervision;
|
|
4031
|
+
console.log(`Automatic startup: ${supervision.installed && supervision.enabled ? supervision.manager + ' (' + supervision.startsAt + ')' : 'off'}`);
|
|
4032
|
+
if (!supervision.installed || !supervision.enabled || !supervision.running) console.log(`Repair on this computer: oomi bridge repair${bridgeConnectionName ? ' --name ' + bridgeConnectionName : ''}`);
|
|
4033
|
+
if (supervision.reason) console.log(supervision.reason);
|
|
4153
4034
|
console.log('------------------');
|
|
4154
4035
|
console.log(`Bridge state: ${payload.bridgeStatePath}`);
|
|
4155
4036
|
console.log(`Runtime status: ${payload.bridgeStatusPath}`);
|
|
@@ -4261,82 +4142,90 @@ function printOpenclawPluginSetup(flags) {
|
|
|
4261
4142
|
}
|
|
4262
4143
|
}
|
|
4263
4144
|
|
|
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;
|
|
4145
|
+
function listLocalBridgeConnections() {
|
|
4146
|
+
const selected = bridgeConnectionName;
|
|
4147
|
+
const names = fs.existsSync(resolveOpenclawBridgeStatePath()) ? [''] : [];
|
|
4148
|
+
const directory = process.env.OOMI_CONNECTIONS_DIR || path.join(os.homedir(), '.oomi', 'connections');
|
|
4149
|
+
if (fs.existsSync(directory)) {
|
|
4150
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
4151
|
+
if (entry.isDirectory() && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(entry.name)
|
|
4152
|
+
&& fs.existsSync(path.join(directory, entry.name, 'oomi-bridge.json'))) names.push(entry.name);
|
|
4276
4153
|
}
|
|
4277
|
-
startBridgeLaunchdService();
|
|
4278
|
-
incrementBridgeMetric('bridge_start_count');
|
|
4279
|
-
console.log(`Bridge service started: ${launchctlServiceTarget()}`);
|
|
4280
|
-
return;
|
|
4281
4154
|
}
|
|
4155
|
+
try {
|
|
4156
|
+
return names.sort().map((name) => {
|
|
4157
|
+
bridgeConnectionName = name;
|
|
4158
|
+
const state = readBridgeState();
|
|
4159
|
+
const running = findRunningBridgeProcess();
|
|
4160
|
+
return { name: name || null, runtime: state.runtime || 'openclaw', deviceId: state.deviceId || null,
|
|
4161
|
+
running: Boolean(running), supervision: currentBridgeService().status(),
|
|
4162
|
+
repairCommand: `oomi bridge repair${name ? ' --name ' + name : ''}` };
|
|
4163
|
+
});
|
|
4164
|
+
} finally { bridgeConnectionName = selected; }
|
|
4165
|
+
}
|
|
4282
4166
|
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
fs.unlinkSync(plistPath);
|
|
4287
|
-
}
|
|
4288
|
-
console.log(`Removed bridge launchd plist: ${plistPath}`);
|
|
4289
|
-
return;
|
|
4290
|
-
}
|
|
4167
|
+
function requireSavedBridge() {
|
|
4168
|
+
if (!readBridgeState().deviceToken) throw new Error('No saved connection. Run oomi connect with an explicit runtime first.');
|
|
4169
|
+
}
|
|
4291
4170
|
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4171
|
+
async function stopSupervisedBridge(service) {
|
|
4172
|
+
// Unload/disable before signalling the worker, otherwise KeepAlive immediately recreates it.
|
|
4173
|
+
service.stop();
|
|
4174
|
+
const stopped = await stopBridgeProcesses();
|
|
4175
|
+
if (stopped.stillAlive?.length) throw new Error('The existing bridge did not stop. Resolve it before reconnecting.');
|
|
4176
|
+
return stopped;
|
|
4177
|
+
}
|
|
4298
4178
|
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
console.log('No bridge workers running.');
|
|
4305
|
-
}
|
|
4306
|
-
console.log(`Bridge service stopped: ${launchctlServiceTarget()}`);
|
|
4179
|
+
async function handleBridgeServiceCommand(actionRaw = '', flags = {}) {
|
|
4180
|
+
const action = String(actionRaw || 'status').trim().toLowerCase();
|
|
4181
|
+
const service = currentBridgeService();
|
|
4182
|
+
if (action === 'status') {
|
|
4183
|
+
console.log(JSON.stringify(service.status(), null, 2));
|
|
4307
4184
|
return;
|
|
4308
4185
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4186
|
+
if (!['install', 'start', 'stop', 'restart', 'uninstall'].includes(action)) {
|
|
4187
|
+
throw new Error('Use: oomi bridge service [install|start|stop|restart|status|uninstall] [--name NAME]');
|
|
4188
|
+
}
|
|
4189
|
+
if (['install', 'start', 'restart'].includes(action)) requireSavedBridge();
|
|
4190
|
+
if (action === 'install') {
|
|
4191
|
+
service.install({ noStart: true });
|
|
4192
|
+
await stopSupervisedBridge(service);
|
|
4193
|
+
if (!isTruthyFlag(flags['no-start'])) service.start();
|
|
4194
|
+
} else if (action === 'start') {
|
|
4195
|
+
service.start(); // Idempotent: do not interrupt a healthy worker.
|
|
4196
|
+
} else if (action === 'restart') {
|
|
4197
|
+
await stopSupervisedBridge(service);
|
|
4198
|
+
service.start({ rewrite: true });
|
|
4313
4199
|
incrementBridgeMetric('bridge_restart_count');
|
|
4314
|
-
|
|
4315
|
-
|
|
4200
|
+
} else if (action === 'stop') {
|
|
4201
|
+
await stopSupervisedBridge(service);
|
|
4202
|
+
} else if (action === 'uninstall') {
|
|
4203
|
+
await stopSupervisedBridge(service);
|
|
4204
|
+
service.uninstall();
|
|
4316
4205
|
}
|
|
4206
|
+
const result = service.status();
|
|
4207
|
+
console.log(JSON.stringify({ action, ...result }, null, 2));
|
|
4208
|
+
}
|
|
4317
4209
|
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
console.log(
|
|
4324
|
-
console.log(`Plist: ${status.plistPath}`);
|
|
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;
|
|
4210
|
+
async function repairBridge(flags = {}) {
|
|
4211
|
+
requireSavedBridge();
|
|
4212
|
+
await handleBridgeServiceCommand('install', flags);
|
|
4213
|
+
if (!isTruthyFlag(flags['no-start'])) {
|
|
4214
|
+
await waitForBridgeConnection({});
|
|
4215
|
+
console.log('Oomi bridge restored with automatic startup. Pairing and conversations preserved.');
|
|
4335
4216
|
}
|
|
4217
|
+
}
|
|
4336
4218
|
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
)
|
|
4219
|
+
async function waitForBridgeConnection(child) {
|
|
4220
|
+
const deadline = Date.now() + 20000;
|
|
4221
|
+
while (Date.now() < deadline) {
|
|
4222
|
+
const status = readBridgeStatus();
|
|
4223
|
+
if (status.pid && (!child.pid || status.pid === child.pid) && isPidAlive(status.pid) && status.status === 'connected') return;
|
|
4224
|
+
if ((!child.pid || status.pid === child.pid) && status.status === 'error') throw new Error(status.lastErrorMessage || 'Bridge startup failed.');
|
|
4225
|
+
if (child.pid && !isPidAlive(child.pid)) throw new Error(`Bridge exited. Check ${resolveBridgeLiveLogPath()}.`);
|
|
4226
|
+
await delay(200);
|
|
4227
|
+
}
|
|
4228
|
+
throw new Error(`The bridge did not connect yet. Automatic recovery may still be retrying. Check oomi status${bridgeConnectionName ? ' --name ' + bridgeConnectionName : ''} and ${resolveBridgeLiveLogPath()}.`);
|
|
4340
4229
|
}
|
|
4341
4230
|
|
|
4342
4231
|
async function startBridgeLifecycle(flags = {}) {
|
|
@@ -4345,6 +4234,16 @@ async function startBridgeLifecycle(flags = {}) {
|
|
|
4345
4234
|
throw new Error('Detached bridge mode cannot be combined with --service-managed.');
|
|
4346
4235
|
}
|
|
4347
4236
|
|
|
4237
|
+
if (!serviceManaged) {
|
|
4238
|
+
const service = currentBridgeService();
|
|
4239
|
+
if (service.status().installed) {
|
|
4240
|
+
if (isTruthyFlag(flags.detach)) throw new Error('This connection has a supervisor. Use bridge start, or uninstall its service before using --detach.');
|
|
4241
|
+
service.start();
|
|
4242
|
+
console.log(`Bridge service started: ${service.config.target}`);
|
|
4243
|
+
return;
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4348
4247
|
if (Boolean(flags.detach)) {
|
|
4349
4248
|
const detachedFlags = { ...flags };
|
|
4350
4249
|
delete detachedFlags.detach;
|
|
@@ -4386,6 +4285,12 @@ async function startBridgeLifecycle(flags = {}) {
|
|
|
4386
4285
|
async function handleBridgeLifecycleCommand(flags = {}, actionRaw = '') {
|
|
4387
4286
|
const action = String(actionRaw || 'start').trim().toLowerCase();
|
|
4388
4287
|
|
|
4288
|
+
if (action === 'list') { console.log(JSON.stringify(listLocalBridgeConnections(), null, 2)); return; }
|
|
4289
|
+
if (action === 'repair') return repairBridge(flags);
|
|
4290
|
+
if (['stop', 'restart'].includes(action) && currentBridgeService().status().installed) {
|
|
4291
|
+
return handleBridgeServiceCommand(action, flags);
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4389
4294
|
if (action === 'start' || action === 'ensure') {
|
|
4390
4295
|
await startBridgeLifecycle(flags);
|
|
4391
4296
|
return;
|
|
@@ -4436,7 +4341,7 @@ async function handleBridgeLifecycleCommand(flags = {}, actionRaw = '') {
|
|
|
4436
4341
|
}
|
|
4437
4342
|
|
|
4438
4343
|
throw new Error(
|
|
4439
|
-
`Unknown bridge action: ${action}. Use: oomi
|
|
4344
|
+
`Unknown bridge action: ${action}. Use: oomi bridge [start|ensure|stop|restart|repair|list|ps]`
|
|
4440
4345
|
);
|
|
4441
4346
|
}
|
|
4442
4347
|
|
|
@@ -4463,6 +4368,13 @@ async function connectLocalAgent(flags) {
|
|
|
4463
4368
|
const agentName = flags['agent-name'] === undefined ? undefined : validateAgentName(flags['agent-name']);
|
|
4464
4369
|
const runtime = String(flags.runtime || '');
|
|
4465
4370
|
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.');
|
|
4371
|
+
const service = currentBridgeService();
|
|
4372
|
+
const unmanaged = isTruthyFlag(flags['no-service']);
|
|
4373
|
+
if (!unmanaged) {
|
|
4374
|
+
service.preflight();
|
|
4375
|
+
} else if (service.status().installed) {
|
|
4376
|
+
throw new Error('This connection already has automatic startup. Uninstall its service before using --no-service.');
|
|
4377
|
+
}
|
|
4466
4378
|
const previous = readBridgeState();
|
|
4467
4379
|
if (previous.runtime && previous.runtime !== runtime) throw new Error('This connection belongs to a different runtime. Choose a different --name.');
|
|
4468
4380
|
const appUrl = String(flags['app-url'] || previous.appUrl || 'https://www.oomi.ai').replace(/\/$/, '');
|
|
@@ -4513,23 +4425,16 @@ async function connectLocalAgent(flags) {
|
|
|
4513
4425
|
if (error.status !== 404 || agentName) throw error;
|
|
4514
4426
|
console.warn('This Oomi server does not support agent settings yet. Connecting without shared settings.');
|
|
4515
4427
|
}
|
|
4516
|
-
//
|
|
4517
|
-
|
|
4518
|
-
const stopped =
|
|
4428
|
+
// Install/update only this connection's supervisor, retaining its saved identity and session.
|
|
4429
|
+
if (!unmanaged) service.install({ noStart: true });
|
|
4430
|
+
const stopped = unmanaged ? await stopBridgeProcesses() : await stopSupervisedBridge(service);
|
|
4519
4431
|
if (stopped.stillAlive?.length) throw new Error('The existing bridge did not stop. Resolve it before reconnecting.');
|
|
4520
4432
|
writeBridgeState(state);
|
|
4521
4433
|
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.');
|
|
4434
|
+
const child = unmanaged ? startBridgeDetachedProcess({ 'app-url': appUrl }) : (service.start(), {});
|
|
4435
|
+
await waitForBridgeConnection(child);
|
|
4436
|
+
console.log(unmanaged ? 'Automatic startup: off (--no-service); your host supervisor must manage recovery.'
|
|
4437
|
+
: `Automatic startup: ${service.config.platform === 'darwin' ? 'macOS login service' : 'Linux user service'}; restarts after worker exits.`);
|
|
4533
4438
|
const invite = await requestConnectInviteLink({ backendHttp: state.brokerHttp, appUrl, sessionKey: state.sessionKey, deviceToken: state.deviceToken });
|
|
4534
4439
|
console.log(`Verified agent: ${runtime}${state.localAgent?.profile ? ` (${state.localAgent.profile})` : ''}`);
|
|
4535
4440
|
console.log('Oomi Connect Ready');
|
|
@@ -4555,8 +4460,9 @@ async function main() {
|
|
|
4555
4460
|
if (command === 'instructions') { console.log(fs.readFileSync(resolveInstructionsFile(), 'utf8')); return; }
|
|
4556
4461
|
if (command === 'capabilities') {
|
|
4557
4462
|
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' },
|
|
4463
|
+
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
4464
|
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' },
|
|
4465
|
+
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
4466
|
routines: 'oomi routines list; oomi routines context', identity: 'oomi identity get', updates: 'oomi updates',
|
|
4561
4467
|
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
4468
|
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 +4473,7 @@ async function main() {
|
|
|
4567
4473
|
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
4474
|
}
|
|
4569
4475
|
if (command === 'spaces') return handleSpacesCommand(readBridgeState(), subcommand || 'list', args.flags, args.positionals);
|
|
4476
|
+
if (command === 'objectives') return handleObjectivesCommand(readBridgeState(), subcommand || 'list', args.flags, args.positionals);
|
|
4570
4477
|
if (command === 'connect') return connectLocalAgent(args.flags);
|
|
4571
4478
|
if (command === 'identity') return handleIdentityCommand(readBridgeState(), subcommand, args.flags);
|
|
4572
4479
|
if (command === 'routines') return handleRoutinesCommand(readBridgeState(), subcommand, args.flags, args.positionals);
|
|
@@ -4586,7 +4493,10 @@ async function main() {
|
|
|
4586
4493
|
}
|
|
4587
4494
|
if (command === 'status') return printOpenclawBridgeStatus(args.flags);
|
|
4588
4495
|
if (command === 'invite') return createOpenclawInviteLink(args.flags);
|
|
4589
|
-
if (command === 'bridge')
|
|
4496
|
+
if (command === 'bridge') {
|
|
4497
|
+
if (subcommand === 'service') return handleBridgeServiceCommand(args.positionals[0] || 'status', args.flags);
|
|
4498
|
+
return handleBridgeLifecycleCommand(args.flags, subcommand || 'start');
|
|
4499
|
+
}
|
|
4590
4500
|
|
|
4591
4501
|
await maybeNotifyUpdate(command);
|
|
4592
4502
|
|
|
@@ -4612,7 +4522,6 @@ async function main() {
|
|
|
4612
4522
|
if (command === 'openclaw' && subcommand === 'bridge') {
|
|
4613
4523
|
const bridgeAction = String(args.positionals[0] || 'start').trim().toLowerCase();
|
|
4614
4524
|
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
4525
|
const serviceAction = args.positionals[1] || 'status';
|
|
4617
4526
|
await handleBridgeServiceCommand(serviceAction, args.flags);
|
|
4618
4527
|
return;
|