@phnx-labs/agents-cli 1.22.117 → 1.22.118
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/CHANGELOG.md +235 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -1
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup.js +9 -9
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +6 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +0 -11
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/harness-auth-capabilities.js +0 -1
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.js +9 -7
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +12 -1
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +5 -10
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-client.ts — the ONE process client through which agents-cli talks to
|
|
3
|
+
* the standalone `browser` CLI (@phnx-labs/browser-cli, PHNX-4101).
|
|
4
|
+
*
|
|
5
|
+
* This is the agents-owned half of the browser extraction, and it is
|
|
6
|
+
* deliberately small. agents-cli no longer carries the CDP/BiDi/Arc drivers, a
|
|
7
|
+
* browser IPC service, a chrome-data/profile store, a network-capture pipeline,
|
|
8
|
+
* or the remote SSH driving loop — the standalone engine owns all of it, exactly
|
|
9
|
+
* as `secrets` took the keychain engine (PHNX-3989), `computer` took the
|
|
10
|
+
* accessibility engine (PHNX-4075) and `sessions` took the transcript engine
|
|
11
|
+
* (PHNX-4012). What stays here is what only the fleet CLI can know: which device
|
|
12
|
+
* a `--device` name resolves to, whether this machine consents to being driven,
|
|
13
|
+
* who the acting session is, and where an action must be recorded.
|
|
14
|
+
*
|
|
15
|
+
* THERE IS NO FALLBACK. A missing executable throws `BROWSER_BIN_MISSING` with
|
|
16
|
+
* install guidance (DIST-1) rather than silently driving a bundled engine —
|
|
17
|
+
* agents-cli has none to drive, and a fallback would re-couple the two release
|
|
18
|
+
* trains this extraction exists to separate.
|
|
19
|
+
*
|
|
20
|
+
* Transport — inherited-fd passthrough, not request/response (matches
|
|
21
|
+
* browser-cli's `bin/browser.cjs` launcher, which forwards fds 3-16):
|
|
22
|
+
*
|
|
23
|
+
* - stdio 0/1/2 are INHERITED. The engine owns the user's terminal: its
|
|
24
|
+
* stdout is the command's stdout, its `--json` is the command's `--json`,
|
|
25
|
+
* its prompts reach a real tty. agents-cli never re-formats engine output,
|
|
26
|
+
* which is what keeps the surface honest as the engine evolves.
|
|
27
|
+
* - fd 3 (`BROWSER_CONTEXT_FD`) carries ONE JSON object — the consumer context
|
|
28
|
+
* built by `lib/browser/context.ts` — written and closed immediately, so the
|
|
29
|
+
* engine reads to EOF and proceeds. The document is read once, before command
|
|
30
|
+
* parsing; a malformed one is a hard error, an absent one the standalone case.
|
|
31
|
+
* - fd 4 (`BROWSER_EVENTS_FD`) carries NDJSON action events back: one JSON
|
|
32
|
+
* object per line, each an action the engine actually performed. agents-cli
|
|
33
|
+
* turns those into feed events and `sessions --browser` history
|
|
34
|
+
* (`lib/browser/record.ts`). The engine may emit none; it must never block
|
|
35
|
+
* on this pipe.
|
|
36
|
+
*
|
|
37
|
+
* Both fds are anonymous pipes on the child's side, the same shape
|
|
38
|
+
* `computer-client.ts` and `secrets-client.ts` settled on after a named FIFO
|
|
39
|
+
* wedged macOS reads. The context is pushed rather than pulled so the engine
|
|
40
|
+
* needs no callback into agents-cli — one direction each way, no reentrancy.
|
|
41
|
+
*/
|
|
42
|
+
import { spawn } from 'node:child_process';
|
|
43
|
+
import { realpathSync, existsSync } from 'node:fs';
|
|
44
|
+
import * as path from 'node:path';
|
|
45
|
+
import { findInPath } from './agent-spec/agents.js';
|
|
46
|
+
const INSTALL_HINT = 'npm i -g @phnx-labs/browser-cli';
|
|
47
|
+
/** fd the engine reads its one-shot JSON context from. */
|
|
48
|
+
export const BROWSER_CONTEXT_FD = 3;
|
|
49
|
+
/** fd the engine writes NDJSON action events to. */
|
|
50
|
+
export const BROWSER_EVENTS_FD = 4;
|
|
51
|
+
export class BrowserClientError extends Error {
|
|
52
|
+
code;
|
|
53
|
+
constructor(code, message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.name = 'BrowserClientError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function isBrowserClientError(err) {
|
|
60
|
+
return err instanceof BrowserClientError;
|
|
61
|
+
}
|
|
62
|
+
let cachedBin;
|
|
63
|
+
function browserEntrypoint(bin) {
|
|
64
|
+
if (!/\.(cmd|ps1)$/i.test(bin))
|
|
65
|
+
return bin;
|
|
66
|
+
const launcher = path.join(path.dirname(bin), 'node_modules', '@phnx-labs', 'browser-cli', 'bin', 'browser.cjs');
|
|
67
|
+
return existsSync(launcher) ? launcher : bin;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Accept only the real standalone `browser` executable, never agents-cli's own
|
|
71
|
+
* `browser` command shim (`exec "$AGENTS_BIN" browser`, `installations/shims.ts`)
|
|
72
|
+
* — resolving that would recurse into this process. `findInPath` already skips
|
|
73
|
+
* `~/.agents/.cache/shims`; this guard is the second line, mirroring
|
|
74
|
+
* `isStandaloneComputer` (agents-cli never shipped a `dist/browser.js` bin, but
|
|
75
|
+
* the check stays symmetric and future-proof against one).
|
|
76
|
+
*/
|
|
77
|
+
export function isStandaloneBrowser(bin) {
|
|
78
|
+
let real = browserEntrypoint(bin);
|
|
79
|
+
try {
|
|
80
|
+
real = realpathSync(real);
|
|
81
|
+
}
|
|
82
|
+
catch { /* spawn reports missing explicit paths */ }
|
|
83
|
+
return !/\.(cmd|ps1)$/i.test(real) && !real.endsWith(path.join('dist', 'browser.js'));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the standalone executable. `BROWSER_BIN` wins so a dev build can be
|
|
87
|
+
* driven without touching PATH.
|
|
88
|
+
*
|
|
89
|
+
* Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — see
|
|
90
|
+
* `isStandaloneBrowser` for why that skip is load-bearing.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveBrowserBin() {
|
|
93
|
+
if (cachedBin)
|
|
94
|
+
return cachedBin;
|
|
95
|
+
const explicit = process.env.BROWSER_BIN?.trim();
|
|
96
|
+
const resolved = explicit && explicit.length > 0 ? (isStandaloneBrowser(explicit) ? explicit : null) : findInPath('browser', { accept: isStandaloneBrowser });
|
|
97
|
+
if (!resolved) {
|
|
98
|
+
throw new BrowserClientError('BROWSER_BIN_MISSING', 'The standalone `browser` CLI was not found. Install it with:\n' +
|
|
99
|
+
` ${INSTALL_HINT}\n` +
|
|
100
|
+
'or run `agents setup browser` / `agents clis install browser`, or point $BROWSER_BIN at its executable.');
|
|
101
|
+
}
|
|
102
|
+
cachedBin = browserEntrypoint(resolved);
|
|
103
|
+
return cachedBin;
|
|
104
|
+
}
|
|
105
|
+
/** True when the standalone `browser` CLI is resolvable — the readiness signal
|
|
106
|
+
* for `agents setup` without a spawn. Never throws. */
|
|
107
|
+
export function browserInstalled() {
|
|
108
|
+
try {
|
|
109
|
+
resolveBrowserBin();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** A `.js`/`.cjs`/`.mjs` bin is run through this runtime; a real executable is exec'd directly. */
|
|
117
|
+
export function invocation(bin) {
|
|
118
|
+
if (/\.[mc]?js$/.test(bin))
|
|
119
|
+
return { command: process.execPath, prefix: [bin] };
|
|
120
|
+
return { command: bin, prefix: [] };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Split a growing buffer into complete NDJSON lines. Pure so the framing rules —
|
|
124
|
+
* blank lines skipped, a non-JSON line dropped rather than crashing the CLI, a
|
|
125
|
+
* trailing partial line carried forward — are unit-testable without a spawn.
|
|
126
|
+
*
|
|
127
|
+
* A malformed line is dropped, not thrown: these events are telemetry riding
|
|
128
|
+
* alongside a user-visible action that already happened. Failing the command
|
|
129
|
+
* because its receipt was unreadable would be strictly worse than losing the
|
|
130
|
+
* receipt. The action itself already failed loud on its own channel if it failed.
|
|
131
|
+
*/
|
|
132
|
+
export function parseEventLines(buffer) {
|
|
133
|
+
const events = [];
|
|
134
|
+
const parts = buffer.split('\n');
|
|
135
|
+
const rest = parts.pop() ?? '';
|
|
136
|
+
for (const line of parts) {
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
if (!trimmed)
|
|
139
|
+
continue;
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(trimmed);
|
|
142
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.command === 'string') {
|
|
143
|
+
events.push(parsed);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Unreadable receipt — see above.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { events, rest };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Run the standalone engine with the consumer context on fd 3 and the action
|
|
154
|
+
* event stream on fd 4. Resolves with the engine's exit code; the caller
|
|
155
|
+
* propagates it so `agents browser` exits exactly as the engine did.
|
|
156
|
+
*
|
|
157
|
+
* Throws `BROWSER_BIN_MISSING` when the standalone is not installed. Every other
|
|
158
|
+
* failure is the engine's own, reported on the inherited stderr.
|
|
159
|
+
*/
|
|
160
|
+
export async function runBrowser(opts) {
|
|
161
|
+
const bin = resolveBrowserBin();
|
|
162
|
+
const { command, prefix } = invocation(bin);
|
|
163
|
+
const child = spawn(command, [...prefix, ...opts.argv], {
|
|
164
|
+
stdio: ['inherit', opts.capture ? 'pipe' : 'inherit', 'inherit', 'pipe', 'pipe'],
|
|
165
|
+
env: {
|
|
166
|
+
...process.env,
|
|
167
|
+
BROWSER_CONTEXT_FD: String(BROWSER_CONTEXT_FD),
|
|
168
|
+
BROWSER_EVENTS_FD: String(BROWSER_EVENTS_FD),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const contextPipe = child.stdio[BROWSER_CONTEXT_FD];
|
|
172
|
+
const eventsPipe = child.stdio[BROWSER_EVENTS_FD];
|
|
173
|
+
if (contextPipe) {
|
|
174
|
+
// An engine that exits before reading the context (bad argv, `--help`)
|
|
175
|
+
// closes fd 3 and we get EPIPE. That is a normal race, not a failure.
|
|
176
|
+
contextPipe.on('error', () => { });
|
|
177
|
+
contextPipe.end(JSON.stringify(opts.context) + '\n');
|
|
178
|
+
}
|
|
179
|
+
let stdout = '';
|
|
180
|
+
if (opts.capture && child.stdout) {
|
|
181
|
+
child.stdout.setEncoding('utf-8');
|
|
182
|
+
child.stdout.on('data', (chunk) => {
|
|
183
|
+
stdout += chunk;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
let pending = '';
|
|
187
|
+
const drained = new Promise((resolve) => {
|
|
188
|
+
if (!eventsPipe)
|
|
189
|
+
return resolve();
|
|
190
|
+
eventsPipe.setEncoding('utf-8');
|
|
191
|
+
eventsPipe.on('data', (chunk) => {
|
|
192
|
+
const { events, rest } = parseEventLines(pending + chunk);
|
|
193
|
+
pending = rest;
|
|
194
|
+
for (const event of events)
|
|
195
|
+
opts.onEvent?.(event);
|
|
196
|
+
});
|
|
197
|
+
eventsPipe.on('error', () => resolve());
|
|
198
|
+
eventsPipe.on('end', () => {
|
|
199
|
+
// A final line with no trailing newline still counts.
|
|
200
|
+
const { events } = parseEventLines(pending.endsWith('\n') ? pending : pending + '\n');
|
|
201
|
+
pending = '';
|
|
202
|
+
for (const event of events)
|
|
203
|
+
opts.onEvent?.(event);
|
|
204
|
+
resolve();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
208
|
+
child.on('error', (err) => {
|
|
209
|
+
reject(new BrowserClientError('BROWSER_SPAWN_FAILED', `Could not run \`${bin}\`: ${err.message}`));
|
|
210
|
+
});
|
|
211
|
+
child.on('close', (code, signal) => {
|
|
212
|
+
// A signalled child has no exit code; report the conventional 128+n so
|
|
213
|
+
// callers and shells see a non-zero status instead of a false success.
|
|
214
|
+
if (code == null)
|
|
215
|
+
return resolve(signal ? 128 + (osSignalNumber(signal) ?? 0) : 1);
|
|
216
|
+
resolve(code);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
await drained;
|
|
220
|
+
return { exitCode, stdout };
|
|
221
|
+
}
|
|
222
|
+
/** Signal name → number for the 128+n exit convention. Only the ones a tunnelled
|
|
223
|
+
* or interrupted engine realistically dies on; anything else contributes 0 and
|
|
224
|
+
* still yields a non-zero status. */
|
|
225
|
+
function osSignalNumber(signal) {
|
|
226
|
+
const table = {
|
|
227
|
+
SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGPIPE: 13, SIGTERM: 15,
|
|
228
|
+
};
|
|
229
|
+
return table[signal];
|
|
230
|
+
}
|
|
231
|
+
/** Test seam: drop the memoized bin so PATH fixtures can re-resolve. */
|
|
232
|
+
export function _resetBrowserClientForTest() {
|
|
233
|
+
cachedBin = undefined;
|
|
234
|
+
}
|
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import type { Meta } from '../types.js';
|
|
2
|
-
|
|
3
|
-
export type OwnerSinkReason = 'rush-not-on-path' | 'rush-signed-out';
|
|
2
|
+
export type OwnerSinkReason = 'imessage-not-macos' | 'slack-no-token' | 'channel-unsupported';
|
|
4
3
|
export interface OwnerSinkStatus {
|
|
5
|
-
/** Owner delivery is configured for this fleet (humans.yaml / notify.owner).
|
|
6
|
-
* When false, no finding is emitted — an un-opted-in box is not "broken". */
|
|
7
4
|
configured: boolean;
|
|
8
|
-
/** This box can actually deliver an owner notification right now. */
|
|
9
5
|
reachable: boolean;
|
|
10
|
-
/** Resolved owner channel (e.g. `imessage`). */
|
|
11
6
|
channel?: string;
|
|
12
|
-
/** Resolved transport after `notify.transports` mapping (usually === channel). */
|
|
13
7
|
transport?: string;
|
|
14
|
-
/** Set only when `reachable` is false. */
|
|
15
8
|
reason?: OwnerSinkReason;
|
|
16
9
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Probe whether THIS box can deliver an owner notification right now. Returns
|
|
19
|
-
* `configured:false` (and the caller emits no finding) when the fleet has no
|
|
20
|
-
* owner channel configured — an un-opted-in box is not broken. When configured,
|
|
21
|
-
* reports whether the resolved transport can actually deliver from here.
|
|
22
|
-
*
|
|
23
|
-
* Only the concrete rush-backed failure the lane hits is reported as unreachable;
|
|
24
|
-
* non-rush transports (desktop / mailbox / …) deliver locally and are treated as
|
|
25
|
-
* reachable rather than probed, so this never invents a critical it cannot back.
|
|
26
|
-
*/
|
|
27
10
|
export declare function probeOwnerSink(meta: Meta): Promise<OwnerSinkStatus>;
|
|
@@ -2,99 +2,46 @@
|
|
|
2
2
|
* Owner-delivery-sink reachability probe (RUSH-2262).
|
|
3
3
|
*
|
|
4
4
|
* The feed/notify owner-delivery lane (`agents send --to owner`, `agents feed post
|
|
5
|
-
* --level important` / `--blocked`) reaches the owner
|
|
6
|
-
*
|
|
7
|
-
* from a context that BOTH finds the `rush` CLI on PATH and can read its
|
|
8
|
-
* keychain-bound session. So a headless Linux fleet box (no rush) or a non-GUI
|
|
9
|
-
* SSH session on a mac (login keychain locked) structurally cannot escalate —
|
|
10
|
-
* and the failure is silent until a block is filed, surfacing only as the
|
|
11
|
-
* after-the-fact `owner failed: …` line. `agents doctor` had no signal for it,
|
|
12
|
-
* which is exactly the gap RUSH-2258 / RUSH-2262 flagged.
|
|
5
|
+
* --level important` / `--blocked`) reaches the owner through the channel
|
|
6
|
+
* providers registered for each configured channel in humans.yaml.
|
|
13
7
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
8
|
+
* Transport capabilities differ by channel and platform:
|
|
9
|
+
* - **imessage:** macOS only (osascript → Messages.app). Linux boxes rely on
|
|
10
|
+
* the peer-forward in owner-forward.ts to reach a macOS peer.
|
|
11
|
+
* - **slack:** requires a `SLACK_BOT_TOKEN` in env or the `webhooks` secrets
|
|
12
|
+
* bundle. Platform-independent.
|
|
13
|
+
* - **telegram / discord:** requires the Rush daemon (removed); only
|
|
14
|
+
* openclaw-telegram is available as a Telegram alternative.
|
|
20
15
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* token is a keychain item, NOT `~/.rush/user.yaml` (RUSH-2262), so this never
|
|
26
|
-
* reads that file — checking it is the mistake that made a signed-in box look
|
|
27
|
-
* signed out.
|
|
28
|
-
*
|
|
29
|
-
* `agents send --to owner --dry-run` is NOT this probe: it short-circuits before the
|
|
30
|
-
* `which rush` preflight (`providers/rush.ts`), so it reports `ok:true` on a box
|
|
31
|
-
* with no rush at all. Resolvability (does the envelope build?) and reachability
|
|
32
|
-
* (can this box actually deliver?) are different questions; this answers the
|
|
33
|
-
* second.
|
|
16
|
+
* `agents send --to owner --dry-run` is NOT this probe: dry-run short-circuits
|
|
17
|
+
* before the capability check. Resolvability (does the envelope build?) and
|
|
18
|
+
* reachability (can this box actually deliver?) are different questions; this
|
|
19
|
+
* answers the second.
|
|
34
20
|
*/
|
|
35
|
-
import {
|
|
36
|
-
import { promisify } from 'util';
|
|
21
|
+
import { platform } from 'os';
|
|
37
22
|
import { readOwnerDest } from './send.js';
|
|
38
|
-
import { RUSH_CHANNELS } from './providers/rush.js';
|
|
39
|
-
const execFileAsync = promisify(execFile);
|
|
40
|
-
async function rushOnPath() {
|
|
41
|
-
try {
|
|
42
|
-
await execFileAsync('which', ['rush'], { timeout: 4000 });
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Signed-in state from `rush whoami`, read with THIS context's keychain access.
|
|
50
|
-
* 'unknown' (a timeout or output we can't classify) is treated as reachable by
|
|
51
|
-
* the caller — a slow or unexpected probe must not cry wolf and paint a false
|
|
52
|
-
* critical. Only a definitive "not logged in" is reported as signed out. */
|
|
53
|
-
async function rushSignedIn() {
|
|
54
|
-
const classify = (s) => {
|
|
55
|
-
const out = s.toLowerCase();
|
|
56
|
-
if (/not (logged in|signed in)/.test(out))
|
|
57
|
-
return 'no';
|
|
58
|
-
if (/logged in as|session:\s*valid/.test(out))
|
|
59
|
-
return 'yes';
|
|
60
|
-
return 'unknown';
|
|
61
|
-
};
|
|
62
|
-
try {
|
|
63
|
-
const { stdout, stderr } = await execFileAsync('rush', ['whoami'], { timeout: 5000 });
|
|
64
|
-
return classify(`${stdout}\n${stderr}`);
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
const e = err;
|
|
68
|
-
if (e.killed)
|
|
69
|
-
return 'unknown'; // timed out — do not conclude signed-out
|
|
70
|
-
// A signed-out rush commonly exits non-zero; trust an explicit message only.
|
|
71
|
-
return classify(`${e.stdout ?? ''}\n${e.stderr ?? ''}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Probe whether THIS box can deliver an owner notification right now. Returns
|
|
76
|
-
* `configured:false` (and the caller emits no finding) when the fleet has no
|
|
77
|
-
* owner channel configured — an un-opted-in box is not broken. When configured,
|
|
78
|
-
* reports whether the resolved transport can actually deliver from here.
|
|
79
|
-
*
|
|
80
|
-
* Only the concrete rush-backed failure the lane hits is reported as unreachable;
|
|
81
|
-
* non-rush transports (desktop / mailbox / …) deliver locally and are treated as
|
|
82
|
-
* reachable rather than probed, so this never invents a critical it cannot back.
|
|
83
|
-
*/
|
|
23
|
+
import { RUSH_CHANNELS, resolveSlackToken } from './providers/rush.js';
|
|
84
24
|
export async function probeOwnerSink(meta) {
|
|
85
25
|
const dest = readOwnerDest(meta);
|
|
86
26
|
if (!dest)
|
|
87
27
|
return { configured: false, reachable: false };
|
|
88
28
|
const channel = dest.channel;
|
|
89
29
|
const transport = meta.notify?.transports?.[channel] ?? channel;
|
|
90
|
-
if (RUSH_CHANNELS.includes(transport)) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if ((
|
|
95
|
-
return { configured: true, reachable: false, channel, transport, reason: '
|
|
30
|
+
if (!RUSH_CHANNELS.includes(transport)) {
|
|
31
|
+
return { configured: true, reachable: true, channel, transport };
|
|
32
|
+
}
|
|
33
|
+
if (transport === 'imessage') {
|
|
34
|
+
if (platform() !== 'darwin') {
|
|
35
|
+
return { configured: true, reachable: false, channel, transport, reason: 'imessage-not-macos' };
|
|
96
36
|
}
|
|
97
37
|
return { configured: true, reachable: true, channel, transport };
|
|
98
38
|
}
|
|
99
|
-
|
|
39
|
+
if (transport === 'slack') {
|
|
40
|
+
if (resolveSlackToken()) {
|
|
41
|
+
return { configured: true, reachable: true, channel, transport };
|
|
42
|
+
}
|
|
43
|
+
return { configured: true, reachable: false, channel, transport, reason: 'slack-no-token' };
|
|
44
|
+
}
|
|
45
|
+
// telegram / discord — daemon removed, no direct transport.
|
|
46
|
+
return { configured: true, reachable: false, channel, transport, reason: 'channel-unsupported' };
|
|
100
47
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ChannelProvider
|
|
2
|
-
type RushChannel = 'telegram' | 'imessage' | 'slack' | 'discord';
|
|
1
|
+
import type { ChannelProvider } from '../registry.js';
|
|
2
|
+
export type RushChannel = 'telegram' | 'imessage' | 'slack' | 'discord';
|
|
3
3
|
export declare const RUSH_CHANNELS: RushChannel[];
|
|
4
|
-
/** Build the
|
|
5
|
-
export declare function
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/** Build the osascript argv for sending an iMessage. */
|
|
5
|
+
export declare function buildImessageOsascriptArgs(text: string, phone: string): string[];
|
|
6
|
+
export declare function resolveSlackToken(): string | undefined;
|
|
7
|
+
/** Build the Slack chat.postMessage payload (exported for tests). */
|
|
8
|
+
export declare function buildSlackPayload(channel: string, text: string, thread?: string): Record<string, string>;
|
|
8
9
|
export declare const rushProviders: ChannelProvider[];
|
|
9
|
-
export {};
|
|
@@ -1,70 +1,140 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Channel providers formerly backed by the Rush daemon's live gateways.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The desktop-era `rush message send` (iMessage) and `rush send --channel`
|
|
5
|
+
* (Slack/Telegram/Discord) commands were removed with the daemon in the
|
|
6
|
+
* thin-client trim (PHNX-3839/3933). This module now delivers directly:
|
|
7
|
+
*
|
|
8
|
+
* - **imessage:** `osascript` → Messages.app (macOS only; Linux boxes
|
|
9
|
+
* rely on the peer-forward in owner-forward.ts to reach a macOS peer).
|
|
10
|
+
* - **slack:** Slack Web API `chat.postMessage` via `fetch()`, reading
|
|
11
|
+
* `SLACK_BOT_TOKEN` from env or the `webhooks` secrets bundle.
|
|
12
|
+
* - **telegram / discord:** error — no direct transport; openclaw-telegram
|
|
13
|
+
* is available as a separate provider for Telegram.
|
|
7
14
|
*/
|
|
8
15
|
import { execFile } from 'child_process';
|
|
16
|
+
import { platform } from 'os';
|
|
9
17
|
import { promisify } from 'util';
|
|
10
18
|
const execFileAsync = promisify(execFile);
|
|
11
19
|
export const RUSH_CHANNELS = ['telegram', 'imessage', 'slack', 'discord'];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// ── iMessage via osascript ──────────────────────────────────────────────
|
|
21
|
+
function escapeAppleScript(s) {
|
|
22
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
23
|
+
}
|
|
24
|
+
/** Build the osascript argv for sending an iMessage. */
|
|
25
|
+
export function buildImessageOsascriptArgs(text, phone) {
|
|
26
|
+
const escapedText = escapeAppleScript(text);
|
|
27
|
+
const escapedPhone = escapeAppleScript(phone);
|
|
28
|
+
const script = [
|
|
29
|
+
'tell application "Messages"',
|
|
30
|
+
` set targetBuddy to buddy "${escapedPhone}" of (first account whose service type is iMessage)`,
|
|
31
|
+
` send "${escapedText}" to targetBuddy`,
|
|
32
|
+
'end tell',
|
|
33
|
+
].join('\n');
|
|
34
|
+
return ['-e', script];
|
|
35
|
+
}
|
|
36
|
+
async function sendImessage(text, opts) {
|
|
37
|
+
const channel = 'imessage';
|
|
38
|
+
if (opts.dryRun)
|
|
39
|
+
return { ok: true, channel, id: opts.target };
|
|
40
|
+
if (platform() !== 'darwin') {
|
|
41
|
+
return { ok: false, channel, id: opts.target, error: 'iMessage requires macOS (peer-forward handles Linux delivery)' };
|
|
42
|
+
}
|
|
43
|
+
if ((opts.attachments?.length ?? 0) > 0) {
|
|
44
|
+
return { ok: false, channel, id: opts.target, error: 'iMessage attachments not supported via osascript' };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await execFileAsync('osascript', buildImessageOsascriptArgs(text, opts.target), { timeout: 10_000 });
|
|
48
|
+
return { ok: true, channel, id: opts.target };
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return { ok: false, channel, id: opts.target, error: err.message };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ── Slack via Web API ───────────────────────────────────────────────────
|
|
55
|
+
export function resolveSlackToken() {
|
|
56
|
+
// 1. Environment variable (set by `secrets exec` or the daemon).
|
|
57
|
+
if (process.env.SLACK_BOT_TOKEN) {
|
|
58
|
+
return process.env.SLACK_BOT_TOKEN;
|
|
59
|
+
}
|
|
60
|
+
// 2. Secrets bundle — the webhook receiver bundle carries SLACK_BOT_TOKEN
|
|
61
|
+
// when a Slack app is configured for this fleet.
|
|
62
|
+
try {
|
|
63
|
+
// Dynamic import to avoid a hard dependency on the secrets subsystem in
|
|
64
|
+
// contexts where it is unavailable (CI, containers without a secrets store).
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
66
|
+
const { readAndResolveBundleEnvSync } = require('../../secrets-client.js');
|
|
67
|
+
const { env } = readAndResolveBundleEnvSync('webhooks', { caller: 'slack-provider', agentOnly: true });
|
|
68
|
+
if (env.SLACK_BOT_TOKEN) {
|
|
69
|
+
return env.SLACK_BOT_TOKEN;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Bundle missing, store locked, or secrets CLI not available — not fatal.
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/** Build the Slack chat.postMessage payload (exported for tests). */
|
|
78
|
+
export function buildSlackPayload(channel, text, thread) {
|
|
79
|
+
const payload = { channel, text };
|
|
80
|
+
if (thread)
|
|
81
|
+
payload.thread_ts = thread;
|
|
82
|
+
return payload;
|
|
20
83
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
84
|
+
async function sendSlack(text, opts) {
|
|
85
|
+
const channel = 'slack';
|
|
86
|
+
if (opts.dryRun)
|
|
87
|
+
return { ok: true, channel, id: opts.target };
|
|
88
|
+
const token = resolveSlackToken();
|
|
89
|
+
if (!token) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
channel,
|
|
93
|
+
id: opts.target,
|
|
94
|
+
error: 'No SLACK_BOT_TOKEN — set it in env or the webhooks secrets bundle',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const payload = buildSlackPayload(opts.target, text, opts.thread);
|
|
99
|
+
const res = await fetch('https://slack.com/api/chat.postMessage', {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Authorization': `Bearer ${token}`,
|
|
103
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(payload),
|
|
106
|
+
});
|
|
107
|
+
const body = await res.json();
|
|
108
|
+
if (!body.ok) {
|
|
109
|
+
return { ok: false, channel, id: opts.target, error: `Slack API: ${body.error ?? 'unknown error'}` };
|
|
110
|
+
}
|
|
111
|
+
return { ok: true, channel, id: opts.target };
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return { ok: false, channel, id: opts.target, error: err.message };
|
|
115
|
+
}
|
|
24
116
|
}
|
|
25
|
-
|
|
117
|
+
// ── Unsupported (daemon-era only) ───────────────────────────────────────
|
|
118
|
+
function unsupportedProvider(name) {
|
|
26
119
|
return {
|
|
27
|
-
name
|
|
28
|
-
async send(
|
|
29
|
-
if (opts.dryRun)
|
|
30
|
-
return { ok: true, channel, id: opts.target
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return { ok: false, channel, id: opts.target, error: 'rush CLI not found on PATH' };
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
if (channel === 'imessage' && opts.ownerScoped) {
|
|
41
|
-
if ((opts.attachments?.length ?? 0) > 0) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
channel,
|
|
45
|
-
id: opts.target,
|
|
46
|
-
error: 'owner-scoped iMessage does not support attachments',
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
await execFileAsync('rush', buildRushOwnerMessageArgs(text));
|
|
50
|
-
return { ok: true, channel, id: opts.target };
|
|
51
|
-
}
|
|
52
|
-
const { stdout } = await execFileAsync('rush', buildRushSendArgs(channel, text, opts));
|
|
53
|
-
const parsed = JSON.parse(stdout);
|
|
54
|
-
return {
|
|
55
|
-
ok: parsed.ok === true,
|
|
56
|
-
channel,
|
|
57
|
-
id: opts.target,
|
|
58
|
-
attachments: parsed.attachments,
|
|
59
|
-
error: parsed.ok === true ? undefined : 'rush send returned ok=false',
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
// Daemon down surfaces here as the exec error (ErrDaemonDown), as does a
|
|
64
|
-
// non-JSON stdout (parse throw). Fail loud — never silently reroute.
|
|
65
|
-
return { ok: false, channel, id: opts.target, error: err.message };
|
|
66
|
-
}
|
|
120
|
+
name,
|
|
121
|
+
async send(_text, opts) {
|
|
122
|
+
if (opts.dryRun)
|
|
123
|
+
return { ok: true, channel: name, id: opts.target };
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
channel: name,
|
|
127
|
+
id: opts.target,
|
|
128
|
+
error: `${name} delivery requires the Rush daemon (removed); use openclaw-telegram for Telegram`,
|
|
129
|
+
};
|
|
67
130
|
},
|
|
68
131
|
};
|
|
69
132
|
}
|
|
70
|
-
|
|
133
|
+
// ── Provider registry ───────────────────────────────────────────────────
|
|
134
|
+
const PROVIDERS = {
|
|
135
|
+
imessage: { name: 'imessage', send: sendImessage },
|
|
136
|
+
slack: { name: 'slack', send: sendSlack },
|
|
137
|
+
telegram: unsupportedProvider('telegram'),
|
|
138
|
+
discord: unsupportedProvider('discord'),
|
|
139
|
+
};
|
|
140
|
+
export const rushProviders = RUSH_CHANNELS.map((ch) => PROVIDERS[ch]);
|
|
@@ -30,7 +30,7 @@ interface ClaudeStatusLinePayload {
|
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
export declare function ingestClaudeStatusLineUsage(payload: ClaudeStatusLinePayload, identity: ClaudeHomeIdentity | null): boolean;
|
|
33
|
+
export declare function ingestClaudeStatusLineUsage(payload: ClaudeStatusLinePayload, identity: ClaudeHomeIdentity | null, versionHome?: string | null): boolean;
|
|
34
34
|
/**
|
|
35
35
|
* Where the running Claude keeps the config this render attributes to: the version
|
|
36
36
|
* home when the launch shim set CLAUDE_CONFIG_DIR, otherwise the real HOME — the
|