oomi-ai 0.8.0 → 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 +16 -3
- package/agent_instructions.md +15 -2
- package/bin/oomi-ai.js +77 -10
- package/lib/bridgeMaintenance.js +64 -0
- package/lib/bridgeService.js +9 -2
- package/lib/localAgent.js +3 -1
- package/package.json +4 -3
- package/skills/oomi/SKILL.md +15 -2
- package/skills/oomi/agent_instructions.md +15 -2
package/README.md
CHANGED
|
@@ -48,9 +48,22 @@ This package exposes connection, bridge, voice, and persona app state APIs. It d
|
|
|
48
48
|
|
|
49
49
|
Connect the agent the user is speaking to. Identify your runtime and profile first. Never choose OpenClaw merely because it is installed on the same machine as Hermes.
|
|
50
50
|
|
|
51
|
+
## Updating Oomi safely
|
|
52
|
+
|
|
53
|
+
When the user asks you to update Oomi, read `oomi instructions` first; it supersedes copied setup/restart recipes. Keep the same connection on the same computer.
|
|
54
|
+
|
|
55
|
+
1. Run `oomi update --connection NAME` (omit the selector for default OpenClaw). This only checks versions. If already current, report that and stop: do not reinstall or restart "to be sure."
|
|
56
|
+
2. If an update exists, run the returned exact-version npm install command **alone**, through the runtime's normal approval flow. Do not append redirections, status checks or restarts. If installation is denied, fails, or cannot be verified, leave the bridge alone and explain the approval/error. Never disable security checks, retry through another executor, or enable blanket unattended approvals.
|
|
57
|
+
3. After successful installation, check `npm list -g oomi-ai --depth=0` and `oomi update` separately. Only then run `oomi bridge repair --connection NAME`.
|
|
58
|
+
4. A running 0.8.1+ supervised bridge queues its restart until active replies finish and queued phone replies are saved. If the result is `queued`, finish your reply now; do not wait for your own turn to end, poll in a loop, or claim completion. `oomi status --connection NAME --json` subsequently reports maintenance `completed`/`failed` and the running `packageVersion`. Pairing and conversations stay intact.
|
|
59
|
+
|
|
60
|
+
For packages before 0.8.1 (no `oomi update` command), read the published guide, check `npm list -g oomi-ai --depth=0` and `npm view oomi-ai version` in separate commands. If equal, stop. Otherwise install the exact returned version through normal approvals. The first restart of an older or unsupervised bridge must happen from an independent host terminal/agent session, outside the Oomi conversation it would terminate. New CLI commands refuse to stop their own older host. After that bootstrap, in-chat restarts use the deferred path.
|
|
61
|
+
|
|
62
|
+
Hermes single-query mode has no interactive approval surface. If its package/security check requires a human, tell the user to approve/run the exact install in an approval-capable host session, then continue there. A blocked install is not a reason to restart. Oomi does not override host permissions. `bridge repair` is also available for an actual connection fault when no package update is needed.
|
|
63
|
+
|
|
51
64
|
## Standard Connect Flow
|
|
52
65
|
|
|
53
|
-
|
|
66
|
+
For a new installation (for an existing installation, use the safe update flow below):
|
|
54
67
|
|
|
55
68
|
```bash
|
|
56
69
|
npm install -g oomi-ai@latest
|
|
@@ -129,7 +142,7 @@ oomi invite --name hermes --app-url https://www.oomi.ai
|
|
|
129
142
|
|
|
130
143
|
Do not pair again to refresh an expired Auth Link. Re-running `connect` reuses saved pairing and verification; only the first connection or an explicit `oomi verify` sends a model probe. A different command, profile, or working directory requires a new connection name, so one agent cannot silently replace another. Named connections store private state and logs under `~/.oomi/connections/<name>`; default OpenClaw keeps `~/.openclaw`. Since 0.8.0, `connect` installs a separate automatic-start service for each connection: macOS launchd or Linux systemd user. It restarts an exited worker and starts at the next login/user session. Linux servers can start at boot and remain running after logout when the host administrator enables user lingering (`loginctl enable-linger <user>`); `oomi bridge service status --name hermes` reports whether this is enabled. WSL needs systemd and a running WSL instance; native Windows does not yet have an automatic service installer. A sleeping, powered-off, or disconnected computer remains unavailable.
|
|
131
144
|
|
|
132
|
-
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To
|
|
145
|
+
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To update an existing connection on **each computer**, follow Updating Oomi safely above. Repair preserves pairing and conversations. On a running 0.8.1+ supervised bridge it queues a safe restart and reports pending work; on a stopped bridge it installs/starts supervision and waits for the broker connection. It does not send a model probe or switch agents. Do not copy connection credentials between computers.
|
|
133
146
|
|
|
134
147
|
Use `oomi bridge service status --name hermes --json` to inspect startup protection. `oomi bridge stop --name hermes` disables automatic startup and stops only that connection; `oomi bridge start --name hermes` enables it again. `oomi bridge service uninstall --name hermes` removes startup management without deleting saved state. The legacy `oomi openclaw bridge service` command remains supported.
|
|
135
148
|
|
|
@@ -333,7 +346,7 @@ STT works but the assistant does not reply:
|
|
|
333
346
|
|
|
334
347
|
Bridge keeps using an old package after update:
|
|
335
348
|
|
|
336
|
-
- Action:
|
|
349
|
+
- Action: follow Updating Oomi safely above, then inspect `oomi status --json` for running version and maintenance result.
|
|
337
350
|
- If still stale: run `oomi bridge repair` for default OpenClaw, or add the existing `--name` for a named connection.
|
|
338
351
|
- On macOS service installs: run `oomi openclaw bridge service restart`.
|
|
339
352
|
- Confirm with `oomi openclaw bridge ps`.
|
package/agent_instructions.md
CHANGED
|
@@ -101,9 +101,22 @@ Owner-originated identity changes are a deliberate exception: one notice per ide
|
|
|
101
101
|
|
|
102
102
|
The legacy persona-app commands below remain compatibility tools; use Spaces for new agent-composed personal views.
|
|
103
103
|
|
|
104
|
+
## Updating Oomi safely
|
|
105
|
+
|
|
106
|
+
When the user asks you to update Oomi, read `oomi instructions` first; it supersedes copied setup/restart recipes. Keep the same connection on the same computer.
|
|
107
|
+
|
|
108
|
+
1. Run `oomi update --connection NAME` (omit the selector for default OpenClaw). This only checks versions. If already current, report that and stop: do not reinstall or restart "to be sure."
|
|
109
|
+
2. If an update exists, run the returned exact-version npm install command **alone**, through the runtime's normal approval flow. Do not append redirections, status checks or restarts. If installation is denied, fails, or cannot be verified, leave the bridge alone and explain the approval/error. Never disable security checks, retry through another executor, or enable blanket unattended approvals.
|
|
110
|
+
3. After successful installation, check `npm list -g oomi-ai --depth=0` and `oomi update` separately. Only then run `oomi bridge repair --connection NAME`.
|
|
111
|
+
4. A running 0.8.1+ supervised bridge queues its restart until active replies finish and queued phone replies are saved. If the result is `queued`, finish your reply now; do not wait for your own turn to end, poll in a loop, or claim completion. `oomi status --connection NAME --json` subsequently reports maintenance `completed`/`failed` and the running `packageVersion`. Pairing and conversations stay intact.
|
|
112
|
+
|
|
113
|
+
For packages before 0.8.1 (no `oomi update` command), read the published guide, check `npm list -g oomi-ai --depth=0` and `npm view oomi-ai version` in separate commands. If equal, stop. Otherwise install the exact returned version through normal approvals. The first restart of an older or unsupervised bridge must happen from an independent host terminal/agent session, outside the Oomi conversation it would terminate. New CLI commands refuse to stop their own older host. After that bootstrap, in-chat restarts use the deferred path.
|
|
114
|
+
|
|
115
|
+
Hermes single-query mode has no interactive approval surface. If its package/security check requires a human, tell the user to approve/run the exact install in an approval-capable host session, then continue there. A blocked install is not a reason to restart. Oomi does not override host permissions. `bridge repair` is also available for an actual connection fault when no package update is needed.
|
|
116
|
+
|
|
104
117
|
## Standard Connect Flow
|
|
105
118
|
|
|
106
|
-
|
|
119
|
+
For a new installation (for an existing installation, use the safe update flow below):
|
|
107
120
|
|
|
108
121
|
```bash
|
|
109
122
|
npm install -g oomi-ai@latest
|
|
@@ -182,7 +195,7 @@ oomi invite --name hermes --app-url https://www.oomi.ai
|
|
|
182
195
|
|
|
183
196
|
Do not pair again to refresh an expired Auth Link. Re-running `connect` reuses saved pairing and verification; only the first connection or an explicit `oomi verify` sends a model probe. A different command, profile, or working directory requires a new connection name, so one agent cannot silently replace another. Named connections store private state and logs under `~/.oomi/connections/<name>`; default OpenClaw keeps `~/.openclaw`. Since 0.8.0, `connect` installs a separate automatic-start service for each connection: macOS launchd or Linux systemd user. It restarts an exited worker and starts at the next login/user session. Linux servers can start at boot and remain running after logout when the host administrator enables user lingering (`loginctl enable-linger <user>`); `oomi bridge service status --name hermes` reports whether this is enabled. WSL needs systemd and a running WSL instance; native Windows does not yet have an automatic service installer. A sleeping, powered-off, or disconnected computer remains unavailable.
|
|
184
197
|
|
|
185
|
-
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To
|
|
198
|
+
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To update an existing connection on **each computer**, follow Updating Oomi safely above. Repair preserves pairing and conversations. On a running 0.8.1+ supervised bridge it queues a safe restart and reports pending work; on a stopped bridge it installs/starts supervision and waits for the broker connection. It does not send a model probe or switch agents. Do not copy connection credentials between computers.
|
|
186
199
|
|
|
187
200
|
Use `oomi bridge service status --name hermes --json` to inspect startup protection. `oomi bridge stop --name hermes` disables automatic startup and stops only that connection; `oomi bridge start --name hermes` enables it again. `oomi bridge service uninstall --name hermes` removes startup management without deleting saved state. The legacy `oomi openclaw bridge service` command remains supported.
|
|
188
201
|
|
package/bin/oomi-ai.js
CHANGED
|
@@ -8,6 +8,7 @@ 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';
|
|
11
12
|
import { bridgeServiceConfig, createBridgeService, renderLaunchAgent } from '../lib/bridgeService.js';
|
|
12
13
|
import { LOCAL_PROTOCOL_MIN, LOCAL_PROTOCOL_MAX, probeGateway } from '../lib/gatewayProbe.js';
|
|
13
14
|
import { LocalAgentRuntime, localAgentConfig, runLocalAgent, writePrivateJson, startChannelWorker } from '../lib/localAgent.js';
|
|
@@ -193,8 +194,7 @@ async function maybeNotifyUpdate(command) {
|
|
|
193
194
|
|
|
194
195
|
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
195
196
|
console.warn(`[oomi] Update available: oomi-ai ${currentVersion} -> ${latestVersion}`);
|
|
196
|
-
console.warn('[oomi]
|
|
197
|
-
console.warn('[oomi] Fallback update command: npm install -g oomi-ai@latest');
|
|
197
|
+
console.warn('[oomi] Read the safe update plan: oomi update');
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -202,6 +202,8 @@ function usage() {
|
|
|
202
202
|
console.log(`oomi <command>
|
|
203
203
|
|
|
204
204
|
Commands:
|
|
205
|
+
update
|
|
206
|
+
Check for a newer package and return a safe, approval-preserving update plan.
|
|
205
207
|
connect --runtime openclaw|hermes|command [--name CONNECTION] [--agent-name "Your name"] [--app-url URL]
|
|
206
208
|
Verify the selected local agent, pair once, start its bridge, and return an Auth Link.
|
|
207
209
|
Hermes: --profile PROFILE and --resume SESSION select the intended agent/session.
|
|
@@ -704,6 +706,9 @@ async function handleContextHealthSyncCommand(flags = {}) {
|
|
|
704
706
|
async function refreshBridgeForUpdate(flags = {}, options = {}) {
|
|
705
707
|
const logger = options.logger || null;
|
|
706
708
|
const service = currentBridgeService();
|
|
709
|
+
const deferred = deferLiveRestart({ print: false });
|
|
710
|
+
if (deferred) return { queued: true, maintenance: deferred };
|
|
711
|
+
assertIndependentMaintenance();
|
|
707
712
|
if (service.status().installed) {
|
|
708
713
|
logger?.(`Restarting supervised bridge ${service.config.target}.`);
|
|
709
714
|
await stopSupervisedBridge(service);
|
|
@@ -771,6 +776,7 @@ async function handleOpenclawRefreshCommand(flags = {}) {
|
|
|
771
776
|
return;
|
|
772
777
|
}
|
|
773
778
|
|
|
779
|
+
if (payload.bridge.queued) { console.log(payload.bridge.maintenance.message); return; }
|
|
774
780
|
console.log(`Oomi refresh complete (${payload.currentVersion})`);
|
|
775
781
|
if (payload.bridge.restarted) {
|
|
776
782
|
console.log(`Bridge restarted (${payload.bridge.mode}).`);
|
|
@@ -2582,15 +2588,24 @@ async function startOpenclawBridge(flags) {
|
|
|
2582
2588
|
consecutiveFailures: 0,
|
|
2583
2589
|
pid: process.pid,
|
|
2584
2590
|
startedAt: bridgeNowIso(),
|
|
2591
|
+
packageVersion: currentPackageVersion(),
|
|
2592
|
+
maintenanceProtocol: 1,
|
|
2585
2593
|
});
|
|
2586
2594
|
|
|
2595
|
+
try { refreshSelectedSkill(bridgeState); } catch (err) { console.warn('Oomi skill refresh failed:', err.message); }
|
|
2587
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
|
+
};
|
|
2588
2603
|
const stopChannelWorker = localRuntime ? startChannelWorker(bridgeState, localRuntime) : () => {};
|
|
2589
2604
|
const stopIdentityWorker = startIdentityWorker({
|
|
2590
2605
|
state: { ...bridgeState, brokerHttp, deviceToken },
|
|
2591
2606
|
receiptPath: path.join(path.dirname(resolveBridgeStatePath()), 'oomi-identity-receipt.json'),
|
|
2592
2607
|
// Applying a specific settings revision is idempotent, unlike a user turn.
|
|
2593
|
-
deliver:
|
|
2608
|
+
deliver: deliverAgentEvent,
|
|
2594
2609
|
acknowledgeCommand: (identity) => `oomi identity acknowledge${bridgeConnectionName ? ` --connection ${bridgeConnectionName}` : ''} --identity-id ${identity.identityId} --revision ${identity.revision}`,
|
|
2595
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.' }),
|
|
2596
2611
|
});
|
|
@@ -2598,7 +2613,7 @@ async function startOpenclawBridge(flags) {
|
|
|
2598
2613
|
state: { ...bridgeState, brokerHttp, deviceToken },
|
|
2599
2614
|
connection: bridgeConnectionName,
|
|
2600
2615
|
receiptPath: path.join(path.dirname(resolveBridgeStatePath()), 'oomi-routine-receipt.json'),
|
|
2601
|
-
deliver:
|
|
2616
|
+
deliver: deliverAgentEvent,
|
|
2602
2617
|
onError: () => updateBridgeStatus({ routineSyncError: 'Routine updates are waiting for the runtime. They will retry.' }),
|
|
2603
2618
|
});
|
|
2604
2619
|
console.log(`Local gateway: ${gateway.gatewayUrl}`);
|
|
@@ -2608,6 +2623,19 @@ async function startOpenclawBridge(flags) {
|
|
|
2608
2623
|
);
|
|
2609
2624
|
|
|
2610
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);
|
|
2611
2639
|
const reconnectState = {
|
|
2612
2640
|
attempt: 0,
|
|
2613
2641
|
timer: null,
|
|
@@ -2960,6 +2988,7 @@ async function startOpenclawBridge(flags) {
|
|
|
2960
2988
|
wsUrl.searchParams.set('token', deviceToken);
|
|
2961
2989
|
|
|
2962
2990
|
const brokerSocket = new WebSocket(wsUrl.toString());
|
|
2991
|
+
currentBrokerSocket = brokerSocket;
|
|
2963
2992
|
let actionCableHeartbeat = null;
|
|
2964
2993
|
|
|
2965
2994
|
const clearChallengeTimer = (sessionBridge) => {
|
|
@@ -3322,6 +3351,12 @@ async function startOpenclawBridge(flags) {
|
|
|
3322
3351
|
}
|
|
3323
3352
|
}
|
|
3324
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
|
+
}
|
|
3325
3360
|
if (gatewayPayload?.event === 'chat' && gatewayPayload.payload?.state === 'final') {
|
|
3326
3361
|
updateBridgeStatus({ runtimeReady: true, lastReplyAt: bridgeNowIso() });
|
|
3327
3362
|
} else if (gatewayPayload?.event === 'chat' && gatewayPayload.payload?.state === 'error') {
|
|
@@ -3775,6 +3810,7 @@ async function startOpenclawBridge(flags) {
|
|
|
3775
3810
|
|
|
3776
3811
|
const markStopped = async (signal) => {
|
|
3777
3812
|
reconnectState.stopped = true;
|
|
3813
|
+
clearInterval(maintenanceTimer);
|
|
3778
3814
|
stopChannelWorker();
|
|
3779
3815
|
stopIdentityWorker();
|
|
3780
3816
|
stopRoutineWorker();
|
|
@@ -3898,11 +3934,6 @@ async function pairAndStartOpenclawBridge(flags) {
|
|
|
3898
3934
|
console.log('');
|
|
3899
3935
|
console.log('Share this exact response with the user (no extra text):');
|
|
3900
3936
|
console.log('----------------------------------------');
|
|
3901
|
-
const skillSource = resolveSkillSource();
|
|
3902
|
-
if (runtime === 'hermes') installSkill(skillSource, [path.join(state.localAgent.hermesHome, ...(state.localAgent.profile === 'default' ? [] : ['profiles', state.localAgent.profile]), 'skills')]);
|
|
3903
|
-
else if (runtime === 'openclaw') installSkill(skillSource, [resolveOpenclawSkillsDir()]);
|
|
3904
|
-
else if (flags['skills-dir']) installSkill(skillSource, [flags['skills-dir']]);
|
|
3905
|
-
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.');
|
|
3906
3937
|
console.log('Oomi Connect Ready');
|
|
3907
3938
|
console.log(`Auth Link: ${inviteUrl}`);
|
|
3908
3939
|
console.log('----------------------------------------');
|
|
@@ -4019,6 +4050,7 @@ function printOpenclawBridgeStatus(flags) {
|
|
|
4019
4050
|
},
|
|
4020
4051
|
runtime: runtimeStatus,
|
|
4021
4052
|
supervision: currentBridgeService().status(),
|
|
4053
|
+
maintenance: readMaintenance(path.dirname(resolveBridgeStatePath())),
|
|
4022
4054
|
};
|
|
4023
4055
|
|
|
4024
4056
|
if (jsonOutput) {
|
|
@@ -4026,6 +4058,7 @@ function printOpenclawBridgeStatus(flags) {
|
|
|
4026
4058
|
return;
|
|
4027
4059
|
}
|
|
4028
4060
|
|
|
4061
|
+
if (payload.maintenance) console.log(`Maintenance: ${payload.maintenance.status} — ${payload.maintenance.message || ''}`);
|
|
4029
4062
|
console.log('Oomi Bridge Status');
|
|
4030
4063
|
const supervision = payload.supervision;
|
|
4031
4064
|
console.log(`Automatic startup: ${supervision.installed && supervision.enabled ? supervision.manager + ' (' + supervision.startsAt + ')' : 'off'}`);
|
|
@@ -4168,7 +4201,25 @@ function requireSavedBridge() {
|
|
|
4168
4201
|
if (!readBridgeState().deviceToken) throw new Error('No saved connection. Run oomi connect with an explicit runtime first.');
|
|
4169
4202
|
}
|
|
4170
4203
|
|
|
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.');
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
|
|
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
|
+
|
|
4171
4221
|
async function stopSupervisedBridge(service) {
|
|
4222
|
+
assertIndependentMaintenance();
|
|
4172
4223
|
// Unload/disable before signalling the worker, otherwise KeepAlive immediately recreates it.
|
|
4173
4224
|
service.stop();
|
|
4174
4225
|
const stopped = await stopBridgeProcesses();
|
|
@@ -4187,6 +4238,8 @@ async function handleBridgeServiceCommand(actionRaw = '', flags = {}) {
|
|
|
4187
4238
|
throw new Error('Use: oomi bridge service [install|start|stop|restart|status|uninstall] [--name NAME]');
|
|
4188
4239
|
}
|
|
4189
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();
|
|
4190
4243
|
if (action === 'install') {
|
|
4191
4244
|
service.install({ noStart: true });
|
|
4192
4245
|
await stopSupervisedBridge(service);
|
|
@@ -4209,6 +4262,7 @@ async function handleBridgeServiceCommand(actionRaw = '', flags = {}) {
|
|
|
4209
4262
|
|
|
4210
4263
|
async function repairBridge(flags = {}) {
|
|
4211
4264
|
requireSavedBridge();
|
|
4265
|
+
if (!isTruthyFlag(flags['no-start']) && deferLiveRestart()) return;
|
|
4212
4266
|
await handleBridgeServiceCommand('install', flags);
|
|
4213
4267
|
if (!isTruthyFlag(flags['no-start'])) {
|
|
4214
4268
|
await waitForBridgeConnection({});
|
|
@@ -4291,6 +4345,8 @@ async function handleBridgeLifecycleCommand(flags = {}, actionRaw = '') {
|
|
|
4291
4345
|
return handleBridgeServiceCommand(action, flags);
|
|
4292
4346
|
}
|
|
4293
4347
|
|
|
4348
|
+
if (['stop', 'restart'].includes(action)) assertIndependentMaintenance();
|
|
4349
|
+
|
|
4294
4350
|
if (action === 'start' || action === 'ensure') {
|
|
4295
4351
|
await startBridgeLifecycle(flags);
|
|
4296
4352
|
return;
|
|
@@ -4364,7 +4420,16 @@ async function verifyLocalAgent(state) {
|
|
|
4364
4420
|
return { runtime: state.runtime || 'openclaw', ...result };
|
|
4365
4421
|
}
|
|
4366
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
|
+
|
|
4367
4431
|
async function connectLocalAgent(flags) {
|
|
4432
|
+
assertIndependentMaintenance();
|
|
4368
4433
|
const agentName = flags['agent-name'] === undefined ? undefined : validateAgentName(flags['agent-name']);
|
|
4369
4434
|
const runtime = String(flags.runtime || '');
|
|
4370
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,6 +4501,7 @@ async function connectLocalAgent(flags) {
|
|
|
4436
4501
|
console.log(unmanaged ? 'Automatic startup: off (--no-service); your host supervisor must manage recovery.'
|
|
4437
4502
|
: `Automatic startup: ${service.config.platform === 'darwin' ? 'macOS login service' : 'Linux user service'}; restarts after worker exits.`);
|
|
4438
4503
|
const invite = await requestConnectInviteLink({ backendHttp: state.brokerHttp, appUrl, sessionKey: state.sessionKey, deviceToken: state.deviceToken });
|
|
4504
|
+
refreshSelectedSkill(state, flags);
|
|
4439
4505
|
console.log(`Verified agent: ${runtime}${state.localAgent?.profile ? ` (${state.localAgent.profile})` : ''}`);
|
|
4440
4506
|
console.log('Oomi Connect Ready');
|
|
4441
4507
|
console.log(`Auth Link: ${invite.inviteUrl}`);
|
|
@@ -4457,9 +4523,10 @@ async function main() {
|
|
|
4457
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.');
|
|
4458
4524
|
bridgeConnectionName = connection || '';
|
|
4459
4525
|
|
|
4526
|
+
if (command === 'update') { console.log(JSON.stringify(planPackageUpdate(currentPackageVersion(), await fetchLatestPublishedVersion('oomi-ai')), null, 2)); return; }
|
|
4460
4527
|
if (command === 'instructions') { console.log(fs.readFileSync(resolveInstructionsFile(), 'utf8')); return; }
|
|
4461
4528
|
if (command === 'capabilities') {
|
|
4462
|
-
console.log(JSON.stringify({ instructions: 'oomi instructions', skill: path.join(PACKAGE_ROOT, 'skills/oomi/SKILL.md'), connectionSelector: '--connection NAME',
|
|
4529
|
+
console.log(JSON.stringify({ instructions: 'oomi instructions', packageUpdate: 'oomi update', skill: path.join(PACKAGE_ROOT, 'skills/oomi/SKILL.md'), connectionSelector: '--connection NAME',
|
|
4463
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' },
|
|
4464
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' },
|
|
4465
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' },
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { readJson, writePrivateJson } from './localAgent.js';
|
|
5
|
+
|
|
6
|
+
export const maintenancePath = (directory) => path.join(directory, 'oomi-bridge-maintenance.json');
|
|
7
|
+
export const readMaintenance = (directory) => readJson(maintenancePath(directory), null);
|
|
8
|
+
const pending = (request) => ['queued', 'restarting'].includes(request?.status);
|
|
9
|
+
|
|
10
|
+
// This is a plan, never an installer: the runtime keeps its normal package approval gate.
|
|
11
|
+
export function planPackageUpdate(current, latest) {
|
|
12
|
+
const valid = (v) => typeof v === 'string' && /^\d+\.\d+\.\d+$/.test(v);
|
|
13
|
+
if (!valid(current) || !valid(latest)) throw new Error('Could not verify the published version. Leave the package and bridge unchanged; try again later.');
|
|
14
|
+
const a = current.split('.').map(Number), b = latest.split('.').map(Number);
|
|
15
|
+
const difference = b.map((v, i) => v - a[i]).find((v) => v !== 0) || 0;
|
|
16
|
+
return { currentVersion: current, latestVersion: latest, status: difference > 0 ? 'update_available' : 'up_to_date',
|
|
17
|
+
...(difference > 0 ? { installCommand: `npm install -g oomi-ai@${latest}`,
|
|
18
|
+
instruction: 'Run this command alone through your normal approval flow. If blocked or unsuccessful, stop and report the approval needed; do not restart or change security settings. After success, verify the installed version, then run oomi bridge repair for this connection.' }
|
|
19
|
+
: { instruction: 'Already current. Do not reinstall or restart. Check oomi status only if the user also reports a connection problem.' }) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isWorkerDescendant(workerPid, { pid = process.pid, run = spawnSync } = {}) {
|
|
23
|
+
if (!Number.isInteger(workerPid) || workerPid <= 0) return false;
|
|
24
|
+
const result = run('ps', ['-axo', 'pid=,ppid='], { encoding: 'utf8', timeout: 5000 });
|
|
25
|
+
if (result.status !== 0) throw new Error('Cannot establish whether this command would stop its own agent. Run maintenance from an independent host terminal.');
|
|
26
|
+
const parents = new Map(String(result.stdout).trim().split('\n').map((line) => line.trim().split(/\s+/).map(Number)));
|
|
27
|
+
const visited = new Set();
|
|
28
|
+
while (pid > 1 && !visited.has(pid)) {
|
|
29
|
+
if (pid === workerPid) return true;
|
|
30
|
+
visited.add(pid); pid = parents.get(pid);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function queueRestart(directory, workerPid, now = Date.now()) {
|
|
36
|
+
const previous = readMaintenance(directory);
|
|
37
|
+
if (pending(previous) && previous.workerPid === workerPid && previous.expiresAt > now) return previous;
|
|
38
|
+
const request = { id: randomUUID(), status: 'queued', action: 'restart', workerPid, requestedAt: now, expiresAt: now + 300000,
|
|
39
|
+
message: 'Restart queued. Finish this reply; the bridge will restart after active replies are delivered. Check oomi status afterward; queued is not completed.' };
|
|
40
|
+
writePrivateJson(maintenancePath(directory), request);
|
|
41
|
+
return request;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// One host supervisor owns the replacement. Never disable/unload the service from its own job.
|
|
45
|
+
export function createMaintenanceController({ directory, pid = process.pid, isIdle, isConnected, restart, now = Date.now }) {
|
|
46
|
+
let idleSince = null;
|
|
47
|
+
return () => {
|
|
48
|
+
const request = readMaintenance(directory);
|
|
49
|
+
if (!pending(request)) { idleSince = null; return; }
|
|
50
|
+
const finish = (status, message) => writePrivateJson(maintenancePath(directory), { ...request, status, message, finishedAt: now() });
|
|
51
|
+
if (request.workerPid !== pid) {
|
|
52
|
+
if (isConnected()) finish('completed', 'Bridge restarted and reconnected.');
|
|
53
|
+
else if (now() > request.expiresAt) finish('failed', 'The replacement bridge has not reconnected. Check oomi status and the host connection.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (now() > request.expiresAt) { finish('failed', 'Restart expired while waiting for replies; no further restart will be attempted.'); return; }
|
|
57
|
+
if (request.status !== 'queued') return;
|
|
58
|
+
if (!isIdle()) { idleSince = null; return; }
|
|
59
|
+
idleSince ??= now();
|
|
60
|
+
if (now() - idleSince < 1000) return;
|
|
61
|
+
writePrivateJson(maintenancePath(directory), { ...request, status: 'restarting' });
|
|
62
|
+
try { restart(); } catch (err) { finish('failed', err.message); }
|
|
63
|
+
};
|
|
64
|
+
}
|
package/lib/bridgeService.js
CHANGED
|
@@ -18,7 +18,7 @@ export function bridgeServiceConfig({ name = '', stateDirectory, logPath, script
|
|
|
18
18
|
if (name && !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(name)) throw new Error('Invalid connection name.');
|
|
19
19
|
const label = `ai.oomi.bridge${name ? '.' + name : ''}`;
|
|
20
20
|
const directory = path.resolve(stateDirectory);
|
|
21
|
-
const environment = { OOMI_SKIP_UPDATE_CHECK: '1', OOMI_CONNECTION: name, PATH: env.PATH || '/usr/local/bin:/usr/bin:/bin',
|
|
21
|
+
const environment = { HOME: home, OOMI_SKIP_UPDATE_CHECK: '1', OOMI_CONNECTION: name, PATH: env.PATH || '/usr/local/bin:/usr/bin:/bin',
|
|
22
22
|
// Always pin the state root. A login service must not resolve another connection from its environment.
|
|
23
23
|
OPENCLAW_HOME: path.resolve(env.OPENCLAW_HOME || path.join(home, '.openclaw')) };
|
|
24
24
|
if (name) environment.OOMI_CONNECTIONS_DIR = path.dirname(directory);
|
|
@@ -158,6 +158,13 @@ export function createBridgeService(config, { run = spawnSync } = {}) {
|
|
|
158
158
|
else if (!current.running) invoke(['kickstart', config.target]);
|
|
159
159
|
} else invoke(['enable', '--now', config.target]);
|
|
160
160
|
}
|
|
161
|
+
function restart() {
|
|
162
|
+
preflight();
|
|
163
|
+
const current = status();
|
|
164
|
+
if (!current.installed || !current.loaded || !current.enabled) throw new Error('Restart requires an enabled, loaded service. Repair it from an independent host terminal.');
|
|
165
|
+
// Submit one supervisor-owned replacement, so killing the caller cannot strand the service.
|
|
166
|
+
invoke(mac ? ['kickstart', '-k', config.target] : ['--no-block', 'restart', config.target]);
|
|
167
|
+
}
|
|
161
168
|
function install({ noStart = false } = {}) {
|
|
162
169
|
assertAvailable();
|
|
163
170
|
writeDefinition();
|
|
@@ -169,5 +176,5 @@ export function createBridgeService(config, { run = spawnSync } = {}) {
|
|
|
169
176
|
if (fs.existsSync(config.file)) fs.unlinkSync(config.file);
|
|
170
177
|
if (!mac) invoke(['daemon-reload']);
|
|
171
178
|
}
|
|
172
|
-
return { config, availability, preflight, status, install, start, stop, uninstall };
|
|
179
|
+
return { config, availability, preflight, status, install, start, stop, restart, uninstall };
|
|
173
180
|
}
|
package/lib/localAgent.js
CHANGED
|
@@ -165,6 +165,7 @@ export class LocalAgentRuntime {
|
|
|
165
165
|
this.onHealth = onHealth;
|
|
166
166
|
this.queue = Promise.resolve();
|
|
167
167
|
this.active = new Map();
|
|
168
|
+
this.deliveries = 0;
|
|
168
169
|
}
|
|
169
170
|
file(sessionKey) { return path.join(this.directory, 'conversations', createHash('sha256').update(sessionKey).digest('hex') + '.json'); }
|
|
170
171
|
history(sessionKey) { return readJson(this.file(sessionKey), { messages: [], receipts: {} }); }
|
|
@@ -269,6 +270,7 @@ export function startChannelWorker(state, runtime) {
|
|
|
269
270
|
return response.json();
|
|
270
271
|
};
|
|
271
272
|
const poll = async () => {
|
|
273
|
+
runtime.deliveries++;
|
|
272
274
|
try {
|
|
273
275
|
const { messages = [] } = await post('poll', { limit: 1 });
|
|
274
276
|
for (const message of messages) {
|
|
@@ -286,7 +288,7 @@ export function startChannelWorker(state, runtime) {
|
|
|
286
288
|
}
|
|
287
289
|
} catch (err) {
|
|
288
290
|
runtime.onHealth({ status: 'degraded', lastErrorMessage: err.message });
|
|
289
|
-
} finally { if (!stopped) timer = setTimeout(poll, 3000); }
|
|
291
|
+
} finally { runtime.deliveries--; if (!stopped) timer = setTimeout(poll, 3000); }
|
|
290
292
|
};
|
|
291
293
|
timer = setTimeout(poll, 0);
|
|
292
294
|
return () => { stopped = true; clearTimeout(timer); for (const entry of runtime.active.values()) entry.controller.abort(); };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oomi-ai",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Connect OpenClaw, Hermes, and other local agents to Oomi with verified runtime adapters",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oomi": "bin/oomi-ai.js"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"url": "https://github.com/crispcode-io/oomi/issues"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
|
-
"check": "node --check bin/oomi-ai.js && node --check lib/bridgeService.js",
|
|
52
|
+
"check": "node --check bin/oomi-ai.js && node --check lib/bridgeService.js && node --check lib/bridgeMaintenance.js",
|
|
53
53
|
"test": "node --test test/*.test.mjs"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"lib/routines.js",
|
|
78
78
|
"lib/spaces.js",
|
|
79
79
|
"lib/objectives.js",
|
|
80
|
-
"lib/bridgeService.js"
|
|
80
|
+
"lib/bridgeService.js",
|
|
81
|
+
"lib/bridgeMaintenance.js"
|
|
81
82
|
]
|
|
82
83
|
}
|
package/skills/oomi/SKILL.md
CHANGED
|
@@ -127,9 +127,22 @@ Owner-originated identity changes are a deliberate exception: one notice per ide
|
|
|
127
127
|
|
|
128
128
|
The legacy persona-app commands below remain compatibility tools; use Spaces for new agent-composed personal views.
|
|
129
129
|
|
|
130
|
+
## Updating Oomi safely
|
|
131
|
+
|
|
132
|
+
When the user asks you to update Oomi, read `oomi instructions` first; it supersedes copied setup/restart recipes. Keep the same connection on the same computer.
|
|
133
|
+
|
|
134
|
+
1. Run `oomi update --connection NAME` (omit the selector for default OpenClaw). This only checks versions. If already current, report that and stop: do not reinstall or restart "to be sure."
|
|
135
|
+
2. If an update exists, run the returned exact-version npm install command **alone**, through the runtime's normal approval flow. Do not append redirections, status checks or restarts. If installation is denied, fails, or cannot be verified, leave the bridge alone and explain the approval/error. Never disable security checks, retry through another executor, or enable blanket unattended approvals.
|
|
136
|
+
3. After successful installation, check `npm list -g oomi-ai --depth=0` and `oomi update` separately. Only then run `oomi bridge repair --connection NAME`.
|
|
137
|
+
4. A running 0.8.1+ supervised bridge queues its restart until active replies finish and queued phone replies are saved. If the result is `queued`, finish your reply now; do not wait for your own turn to end, poll in a loop, or claim completion. `oomi status --connection NAME --json` subsequently reports maintenance `completed`/`failed` and the running `packageVersion`. Pairing and conversations stay intact.
|
|
138
|
+
|
|
139
|
+
For packages before 0.8.1 (no `oomi update` command), read the published guide, check `npm list -g oomi-ai --depth=0` and `npm view oomi-ai version` in separate commands. If equal, stop. Otherwise install the exact returned version through normal approvals. The first restart of an older or unsupervised bridge must happen from an independent host terminal/agent session, outside the Oomi conversation it would terminate. New CLI commands refuse to stop their own older host. After that bootstrap, in-chat restarts use the deferred path.
|
|
140
|
+
|
|
141
|
+
Hermes single-query mode has no interactive approval surface. If its package/security check requires a human, tell the user to approve/run the exact install in an approval-capable host session, then continue there. A blocked install is not a reason to restart. Oomi does not override host permissions. `bridge repair` is also available for an actual connection fault when no package update is needed.
|
|
142
|
+
|
|
130
143
|
## Standard Connect Flow
|
|
131
144
|
|
|
132
|
-
|
|
145
|
+
For a new installation (for an existing installation, use the safe update flow below):
|
|
133
146
|
|
|
134
147
|
```bash
|
|
135
148
|
npm install -g oomi-ai@latest
|
|
@@ -208,7 +221,7 @@ oomi invite --name hermes --app-url https://www.oomi.ai
|
|
|
208
221
|
|
|
209
222
|
Do not pair again to refresh an expired Auth Link. Re-running `connect` reuses saved pairing and verification; only the first connection or an explicit `oomi verify` sends a model probe. A different command, profile, or working directory requires a new connection name, so one agent cannot silently replace another. Named connections store private state and logs under `~/.oomi/connections/<name>`; default OpenClaw keeps `~/.openclaw`. Since 0.8.0, `connect` installs a separate automatic-start service for each connection: macOS launchd or Linux systemd user. It restarts an exited worker and starts at the next login/user session. Linux servers can start at boot and remain running after logout when the host administrator enables user lingering (`loginctl enable-linger <user>`); `oomi bridge service status --name hermes` reports whether this is enabled. WSL needs systemd and a running WSL instance; native Windows does not yet have an automatic service installer. A sleeping, powered-off, or disconnected computer remains unavailable.
|
|
210
223
|
|
|
211
|
-
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To
|
|
224
|
+
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To update an existing connection on **each computer**, follow Updating Oomi safely above. Repair preserves pairing and conversations. On a running 0.8.1+ supervised bridge it queues a safe restart and reports pending work; on a stopped bridge it installs/starts supervision and waits for the broker connection. It does not send a model probe or switch agents. Do not copy connection credentials between computers.
|
|
212
225
|
|
|
213
226
|
Use `oomi bridge service status --name hermes --json` to inspect startup protection. `oomi bridge stop --name hermes` disables automatic startup and stops only that connection; `oomi bridge start --name hermes` enables it again. `oomi bridge service uninstall --name hermes` removes startup management without deleting saved state. The legacy `oomi openclaw bridge service` command remains supported.
|
|
214
227
|
|
|
@@ -101,9 +101,22 @@ Owner-originated identity changes are a deliberate exception: one notice per ide
|
|
|
101
101
|
|
|
102
102
|
The legacy persona-app commands below remain compatibility tools; use Spaces for new agent-composed personal views.
|
|
103
103
|
|
|
104
|
+
## Updating Oomi safely
|
|
105
|
+
|
|
106
|
+
When the user asks you to update Oomi, read `oomi instructions` first; it supersedes copied setup/restart recipes. Keep the same connection on the same computer.
|
|
107
|
+
|
|
108
|
+
1. Run `oomi update --connection NAME` (omit the selector for default OpenClaw). This only checks versions. If already current, report that and stop: do not reinstall or restart "to be sure."
|
|
109
|
+
2. If an update exists, run the returned exact-version npm install command **alone**, through the runtime's normal approval flow. Do not append redirections, status checks or restarts. If installation is denied, fails, or cannot be verified, leave the bridge alone and explain the approval/error. Never disable security checks, retry through another executor, or enable blanket unattended approvals.
|
|
110
|
+
3. After successful installation, check `npm list -g oomi-ai --depth=0` and `oomi update` separately. Only then run `oomi bridge repair --connection NAME`.
|
|
111
|
+
4. A running 0.8.1+ supervised bridge queues its restart until active replies finish and queued phone replies are saved. If the result is `queued`, finish your reply now; do not wait for your own turn to end, poll in a loop, or claim completion. `oomi status --connection NAME --json` subsequently reports maintenance `completed`/`failed` and the running `packageVersion`. Pairing and conversations stay intact.
|
|
112
|
+
|
|
113
|
+
For packages before 0.8.1 (no `oomi update` command), read the published guide, check `npm list -g oomi-ai --depth=0` and `npm view oomi-ai version` in separate commands. If equal, stop. Otherwise install the exact returned version through normal approvals. The first restart of an older or unsupervised bridge must happen from an independent host terminal/agent session, outside the Oomi conversation it would terminate. New CLI commands refuse to stop their own older host. After that bootstrap, in-chat restarts use the deferred path.
|
|
114
|
+
|
|
115
|
+
Hermes single-query mode has no interactive approval surface. If its package/security check requires a human, tell the user to approve/run the exact install in an approval-capable host session, then continue there. A blocked install is not a reason to restart. Oomi does not override host permissions. `bridge repair` is also available for an actual connection fault when no package update is needed.
|
|
116
|
+
|
|
104
117
|
## Standard Connect Flow
|
|
105
118
|
|
|
106
|
-
|
|
119
|
+
For a new installation (for an existing installation, use the safe update flow below):
|
|
107
120
|
|
|
108
121
|
```bash
|
|
109
122
|
npm install -g oomi-ai@latest
|
|
@@ -182,7 +195,7 @@ oomi invite --name hermes --app-url https://www.oomi.ai
|
|
|
182
195
|
|
|
183
196
|
Do not pair again to refresh an expired Auth Link. Re-running `connect` reuses saved pairing and verification; only the first connection or an explicit `oomi verify` sends a model probe. A different command, profile, or working directory requires a new connection name, so one agent cannot silently replace another. Named connections store private state and logs under `~/.oomi/connections/<name>`; default OpenClaw keeps `~/.openclaw`. Since 0.8.0, `connect` installs a separate automatic-start service for each connection: macOS launchd or Linux systemd user. It restarts an exited worker and starts at the next login/user session. Linux servers can start at boot and remain running after logout when the host administrator enables user lingering (`loginctl enable-linger <user>`); `oomi bridge service status --name hermes` reports whether this is enabled. WSL needs systemd and a running WSL instance; native Windows does not yet have an automatic service installer. A sleeping, powered-off, or disconnected computer remains unavailable.
|
|
184
197
|
|
|
185
|
-
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To
|
|
198
|
+
Run `oomi bridge list` to find saved connection names and their startup protection on this computer. To update an existing connection on **each computer**, follow Updating Oomi safely above. Repair preserves pairing and conversations. On a running 0.8.1+ supervised bridge it queues a safe restart and reports pending work; on a stopped bridge it installs/starts supervision and waits for the broker connection. It does not send a model probe or switch agents. Do not copy connection credentials between computers.
|
|
186
199
|
|
|
187
200
|
Use `oomi bridge service status --name hermes --json` to inspect startup protection. `oomi bridge stop --name hermes` disables automatic startup and stops only that connection; `oomi bridge start --name hermes` enables it again. `oomi bridge service uninstall --name hermes` removes startup management without deleting saved state. The legacy `oomi openclaw bridge service` command remains supported.
|
|
188
201
|
|