@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
package/dist/lib/agents.js
CHANGED
|
@@ -313,7 +313,9 @@ export const AGENTS = {
|
|
|
313
313
|
skillsDir: path.join(HOME, '.opencode', 'skills'),
|
|
314
314
|
// Plugins: TS/JS modules auto-loaded from ~/.config/opencode/plugins/ (global)
|
|
315
315
|
// and .opencode/plugins/ (project). Not Claude marketplace format — see
|
|
316
|
-
// installOpenCodePlugin in plugins.ts.
|
|
316
|
+
// installOpenCodePlugin in plugins.ts. OpenCode v1.18.4 exposes lifecycle
|
|
317
|
+
// hooks through plugin modules (event/tool/etc. functions), not a native
|
|
318
|
+
// opencode.json shell-command hooks block.
|
|
317
319
|
hooksDir: 'hooks',
|
|
318
320
|
instructionsFile: 'AGENTS.md',
|
|
319
321
|
format: 'markdown',
|
|
@@ -341,7 +343,9 @@ export const AGENTS = {
|
|
|
341
343
|
// tools.alsoAllow (allow) / tools.deny (deny). OpenClaw gates at tool
|
|
342
344
|
// granularity only, so sub-command/path/domain patterns are skipped.
|
|
343
345
|
// OpenClaw is self-updating (no pinned since), so `true` is correct.
|
|
344
|
-
|
|
346
|
+
// Workflows sync as Lobster `.lobster` files under `.openclaw/workflows/`;
|
|
347
|
+
// the Lobster tool runs them by receiving the file path as `pipeline`.
|
|
348
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'skip'] },
|
|
345
349
|
},
|
|
346
350
|
copilot: {
|
|
347
351
|
id: 'copilot',
|
|
@@ -366,7 +370,7 @@ export const AGENTS = {
|
|
|
366
370
|
format: 'markdown',
|
|
367
371
|
variableSyntax: '$ARGUMENTS',
|
|
368
372
|
supportsHooks: true,
|
|
369
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist:
|
|
373
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '0.0.353' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'auto', 'skip'] },
|
|
370
374
|
},
|
|
371
375
|
amp: {
|
|
372
376
|
id: 'amp',
|
|
@@ -650,7 +654,11 @@ export const AGENTS = {
|
|
|
650
654
|
mcp: true,
|
|
651
655
|
mcpHttp: true,
|
|
652
656
|
mcpHeaders: false,
|
|
653
|
-
|
|
657
|
+
// Permissions: ~/.hermes/config.yaml carries `command_allowlist` for
|
|
658
|
+
// always-approved command globs and `approvals.deny` for unconditional
|
|
659
|
+
// command blocks. It is command-glob only; session `/tools` toggles are
|
|
660
|
+
// intentionally not persisted by agents-cli.
|
|
661
|
+
allowlist: true,
|
|
654
662
|
skills: true,
|
|
655
663
|
commands: false,
|
|
656
664
|
plugins: true,
|
|
@@ -691,7 +699,10 @@ export const AGENTS = {
|
|
|
691
699
|
mcp: true,
|
|
692
700
|
mcpHttp: true,
|
|
693
701
|
mcpHeaders: false,
|
|
694
|
-
|
|
702
|
+
// Permissions: ~/.forge/permissions.yaml (or $FORGE_CONFIG/permissions.yaml)
|
|
703
|
+
// carries ordered operation-family glob policies. It is active only when
|
|
704
|
+
// `.forge.toml` sets `restricted = true`; MCP tools bypass this file.
|
|
705
|
+
allowlist: true,
|
|
695
706
|
skills: true,
|
|
696
707
|
commands: true,
|
|
697
708
|
plugins: false,
|
|
@@ -849,7 +860,20 @@ export async function getCliState(agentId) {
|
|
|
849
860
|
}
|
|
850
861
|
}
|
|
851
862
|
}
|
|
852
|
-
|
|
863
|
+
return getUnmanagedCliState(agentId);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Resolve the agent's OWN install — the one agents-cli does not manage — by plain
|
|
867
|
+
* PATH lookup, ignoring the version dirs entirely.
|
|
868
|
+
*
|
|
869
|
+
* Callers that specifically mean "the user's own globally-installed CLI" must use
|
|
870
|
+
* this rather than `getCliState`, whose managed fast path reports an installed
|
|
871
|
+
* version (any version dir, in readdir order) and would therefore hand back an
|
|
872
|
+
* isolated copy — a copy that is deliberately unreachable from PATH — labelled as
|
|
873
|
+
* the global install.
|
|
874
|
+
*/
|
|
875
|
+
export async function getUnmanagedCliState(agentId) {
|
|
876
|
+
const agent = AGENTS[agentId];
|
|
853
877
|
// Special case for grok: it manages its own binaries in ~/.grok/downloads/
|
|
854
878
|
if (agentId === 'grok') {
|
|
855
879
|
const grokBin = resolveGrokBinary();
|
|
@@ -1311,6 +1335,48 @@ function isValidOpenCodeCredential(value) {
|
|
|
1311
1335
|
default: return false;
|
|
1312
1336
|
}
|
|
1313
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Whether a Claude version home's credential file is present but carries no
|
|
1340
|
+
* token — the "must have a real credential" floor (see
|
|
1341
|
+
* `isValidOpenCodeCredential`) applied to claude.
|
|
1342
|
+
*
|
|
1343
|
+
* A FAILED OAuth refresh leaves exactly this state behind: Claude Code rewrites
|
|
1344
|
+
* `.claude/.credentials.json` with `accessToken: ""`, `refreshToken: ""` and
|
|
1345
|
+
* `expiresAt: 0`, keeping only the descriptive fields (`subscriptionType`,
|
|
1346
|
+
* `rateLimitTier`, `refreshTokenExpiresAt`). Everything we derive from
|
|
1347
|
+
* `.claude.json` — email, plan — still looks healthy, so the install reported
|
|
1348
|
+
* `signedIn: true`, `agents view` drew usage bars for it, and balanced rotation
|
|
1349
|
+
* (whose `authValid` is just "email present") kept picking it — every pick dying
|
|
1350
|
+
* at spawn on "OAuth session expired and could not be refreshed".
|
|
1351
|
+
*
|
|
1352
|
+
* Only decidable off macOS: there the login Keychain is the canonical store and
|
|
1353
|
+
* this file is not authoritative, and probing the Keychain would raise an
|
|
1354
|
+
* authorization sheet per installed version on every `agents run` — the reason
|
|
1355
|
+
* rotation stopped calling `isClaudeAuthValid` at all. Off macOS the file IS the
|
|
1356
|
+
* only store, so a token-less file is proof of signed-out. `platform` is a
|
|
1357
|
+
* parameter so both branches are testable on any host.
|
|
1358
|
+
*
|
|
1359
|
+
* Sync, no Keychain, no network — safe on the `agents run` hot path.
|
|
1360
|
+
*/
|
|
1361
|
+
export function isClaudeCredentialFileBlank(base, platform = process.platform) {
|
|
1362
|
+
if (platform === 'darwin')
|
|
1363
|
+
return false;
|
|
1364
|
+
try {
|
|
1365
|
+
const raw = fs.readFileSync(path.join(base, '.claude', '.credentials.json'), 'utf-8');
|
|
1366
|
+
const oauth = JSON.parse(raw).claudeAiOauth;
|
|
1367
|
+
if (!oauth)
|
|
1368
|
+
return false;
|
|
1369
|
+
const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
|
|
1370
|
+
return !nonEmpty(oauth.accessToken) && !nonEmpty(oauth.refreshToken);
|
|
1371
|
+
}
|
|
1372
|
+
catch {
|
|
1373
|
+
// No file (a Keychain-backed home, or never logged in here) or an
|
|
1374
|
+
// unreadable/corrupt one: not positive evidence of a blank credential, so
|
|
1375
|
+
// leave the existing signal alone rather than declaring a working install
|
|
1376
|
+
// signed out.
|
|
1377
|
+
return false;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1314
1380
|
export async function getAccountInfo(agentId, home) {
|
|
1315
1381
|
const base = home || os.homedir();
|
|
1316
1382
|
const empty = {
|
|
@@ -1347,6 +1413,13 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1347
1413
|
const accountId = normalizeIdentityPart(oa?.accountUuid);
|
|
1348
1414
|
const organizationId = normalizeIdentityPart(oa?.organizationUuid);
|
|
1349
1415
|
const email = oa?.emailAddress || null;
|
|
1416
|
+
// Credential floor: a blanked credential file means this home cannot
|
|
1417
|
+
// authenticate, whatever `.claude.json` still says. Report it signed out
|
|
1418
|
+
// so `agents view` prompts a re-login and rotation routes around it,
|
|
1419
|
+
// instead of handing runs to an install that dies at spawn.
|
|
1420
|
+
if (email && isClaudeCredentialFileBlank(base)) {
|
|
1421
|
+
return { ...empty, lastActive };
|
|
1422
|
+
}
|
|
1350
1423
|
const accountKey = buildIdentityKey(agentId, [
|
|
1351
1424
|
['account', accountId],
|
|
1352
1425
|
['org', organizationId],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Artifact action types and validation.
|
|
3
|
-
* Ported from
|
|
3
|
+
* Ported from the legacy agent CLI config helpers.
|
|
4
4
|
*
|
|
5
5
|
* Artifact actions map tool invocations to artifact labels, allowing agents to
|
|
6
6
|
* trigger tools automatically when specific artifacts are produced.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Artifact action types and validation.
|
|
3
|
-
* Ported from
|
|
3
|
+
* Ported from the legacy agent CLI config helpers.
|
|
4
4
|
*
|
|
5
5
|
* Artifact actions map tool invocations to artifact labels, allowing agents to
|
|
6
6
|
* trigger tools automatically when specific artifacts are produced.
|
|
@@ -50,7 +50,7 @@ export interface SuppressResult {
|
|
|
50
50
|
* Auto-answer + remove a suppressible block so it never renders as a card.
|
|
51
51
|
* Logs the stall as answered by policy:stall-suppression.
|
|
52
52
|
*/
|
|
53
|
-
export declare function suppressStallBlock(block: OpenBlock, root?: string): SuppressResult;
|
|
53
|
+
export declare function suppressStallBlock(block: OpenBlock, root?: string, mailboxRoot?: string): SuppressResult;
|
|
54
54
|
export interface FeedFilterResult {
|
|
55
55
|
/** Blocks that should render as cards. */
|
|
56
56
|
surfaced: OpenBlock[];
|
|
@@ -66,6 +66,7 @@ export interface FeedFilterResult {
|
|
|
66
66
|
export declare function filterBlocksForFeed(blocks: OpenBlock[], opts?: {
|
|
67
67
|
apply?: boolean;
|
|
68
68
|
root?: string;
|
|
69
|
+
mailboxRoot?: string;
|
|
69
70
|
}): FeedFilterResult;
|
|
70
71
|
/** Human digest: "31 stalls auto-resolved by policy". */
|
|
71
72
|
export declare function suppressionDigest(result: FeedFilterResult): string;
|
|
@@ -108,7 +108,7 @@ export function classifyBlock(block) {
|
|
|
108
108
|
* Auto-answer + remove a suppressible block so it never renders as a card.
|
|
109
109
|
* Logs the stall as answered by policy:stall-suppression.
|
|
110
110
|
*/
|
|
111
|
-
export function suppressStallBlock(block, root) {
|
|
111
|
+
export function suppressStallBlock(block, root, mailboxRoot) {
|
|
112
112
|
const c = classifyBlock(block);
|
|
113
113
|
if (!c.suppress || !c.autoAnswer) {
|
|
114
114
|
return { blockId: block.blockId, class: c.class, rule: c.rule, autoAnswer: '', suppressed: false };
|
|
@@ -124,7 +124,7 @@ export function suppressStallBlock(block, root) {
|
|
|
124
124
|
}
|
|
125
125
|
try {
|
|
126
126
|
// Mailbox lives under the global mailbox root (not the feed dir).
|
|
127
|
-
const msgId = enqueue(mailboxDir(block.mailboxId), {
|
|
127
|
+
const msgId = enqueue(mailboxDir(block.mailboxId, mailboxRoot), {
|
|
128
128
|
to: block.mailboxId,
|
|
129
129
|
text: c.autoAnswer,
|
|
130
130
|
from: 'stall-suppression',
|
|
@@ -164,7 +164,7 @@ export function filterBlocksForFeed(blocks, opts) {
|
|
|
164
164
|
counts[c.class] += 1;
|
|
165
165
|
if (c.suppress) {
|
|
166
166
|
if (apply) {
|
|
167
|
-
const r = suppressStallBlock(block, opts?.root);
|
|
167
|
+
const r = suppressStallBlock(block, opts?.root, opts?.mailboxRoot);
|
|
168
168
|
suppressed.push(r);
|
|
169
169
|
if (!r.suppressed)
|
|
170
170
|
surfaced.push(block);
|
|
@@ -35,18 +35,46 @@ export declare function verdictLabel(verdict: AuthVerdict): string;
|
|
|
35
35
|
/** Roll a set of verdicts (one host×agent's installs) into counts for a matrix cell. */
|
|
36
36
|
export interface VerdictSummary {
|
|
37
37
|
live: number;
|
|
38
|
+
/**
|
|
39
|
+
* unverified — signed in, but this agent has no in-repo live-probe endpoint
|
|
40
|
+
* (codex/grok). A benign, neutral state: the account is present and usable, we
|
|
41
|
+
* just can't complete a 2xx to prove it. It must NOT be lumped with the soft
|
|
42
|
+
* `warn` bucket, or a fully-logged-in codex/grok fleet reads as half-degraded
|
|
43
|
+
* (the exact "cry wolf" the ping matrix used to produce).
|
|
44
|
+
*/
|
|
45
|
+
present: number;
|
|
38
46
|
/** revoked — the server rejected the token (401/403). Genuinely needs re-login. */
|
|
39
47
|
bad: number;
|
|
40
48
|
/**
|
|
41
|
-
* expired / rate_limited /
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
49
|
+
* expired / rate_limited / error — degraded or unknown, but NOT "re-login now".
|
|
50
|
+
* `expired` is soft for kimi/droid (their CLIs refresh the token on next launch;
|
|
51
|
+
* we don't refresh on the read path), so it must not be lumped with revoked or
|
|
52
|
+
* we'd cry wolf on a self-healing token.
|
|
45
53
|
*/
|
|
46
54
|
warn: number;
|
|
47
55
|
total: number;
|
|
48
56
|
}
|
|
49
57
|
export declare function summarizeVerdicts(verdicts: AuthVerdict[]): VerdictSummary;
|
|
58
|
+
/** A resolved display color; the caller maps it to chalk. Pure, so it's unit-tested. */
|
|
59
|
+
export type AuthCellColor = 'green' | 'yellow' | 'red' | 'gray' | 'dim';
|
|
60
|
+
/**
|
|
61
|
+
* Color for a single verdict in the per-account (`--verbose`) breakdown. This is
|
|
62
|
+
* the one source of truth shared with {@link authCellColor} so the matrix and the
|
|
63
|
+
* account list can never drift — they did: the matrix painted `expired` yellow
|
|
64
|
+
* while the verbose list painted it *red* (lumped with revoked), directly against
|
|
65
|
+
* the {@link VerdictSummary} contract. Red is reserved for `revoked` (the only
|
|
66
|
+
* "re-login now"); `unverified` is a neutral signed-in state (gray);
|
|
67
|
+
* `expired`/`rate_limited`/`error` are soft (yellow).
|
|
68
|
+
*/
|
|
69
|
+
export declare function verdictColor(verdict: AuthVerdict): AuthCellColor;
|
|
70
|
+
/**
|
|
71
|
+
* Color for a matrix cell that rolls up several accounts. Red only when a token
|
|
72
|
+
* was genuinely rejected (`revoked`); yellow for soft/expired; green when at least
|
|
73
|
+
* one account is live-verified and none are soft/revoked; gray when accounts are
|
|
74
|
+
* present but unverifiable (codex/grok) — never the alarming yellow the old
|
|
75
|
+
* renderer used, which made a fully-logged-in fleet read as half-broken.
|
|
76
|
+
*/
|
|
77
|
+
export declare function authCellColor(summary: VerdictSummary): AuthCellColor;
|
|
50
78
|
/** Verdicts that mean "this token was rejected by the server — re-login required". */
|
|
51
79
|
export declare function isDeadVerdict(verdict: AuthVerdict): boolean;
|
|
52
80
|
/**
|
package/dist/lib/auth-health.js
CHANGED
|
@@ -82,17 +82,56 @@ export function verdictLabel(verdict) {
|
|
|
82
82
|
}
|
|
83
83
|
export function summarizeVerdicts(verdicts) {
|
|
84
84
|
let live = 0;
|
|
85
|
+
let present = 0;
|
|
85
86
|
let bad = 0;
|
|
86
87
|
let warn = 0;
|
|
87
88
|
for (const v of verdicts) {
|
|
88
89
|
if (v === 'live')
|
|
89
90
|
live++;
|
|
91
|
+
else if (v === 'unverified')
|
|
92
|
+
present++;
|
|
90
93
|
else if (v === 'revoked')
|
|
91
94
|
bad++;
|
|
92
95
|
else
|
|
93
96
|
warn++;
|
|
94
97
|
}
|
|
95
|
-
return { live, bad, warn, total: verdicts.length };
|
|
98
|
+
return { live, present, bad, warn, total: verdicts.length };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Color for a single verdict in the per-account (`--verbose`) breakdown. This is
|
|
102
|
+
* the one source of truth shared with {@link authCellColor} so the matrix and the
|
|
103
|
+
* account list can never drift — they did: the matrix painted `expired` yellow
|
|
104
|
+
* while the verbose list painted it *red* (lumped with revoked), directly against
|
|
105
|
+
* the {@link VerdictSummary} contract. Red is reserved for `revoked` (the only
|
|
106
|
+
* "re-login now"); `unverified` is a neutral signed-in state (gray);
|
|
107
|
+
* `expired`/`rate_limited`/`error` are soft (yellow).
|
|
108
|
+
*/
|
|
109
|
+
export function verdictColor(verdict) {
|
|
110
|
+
switch (verdict) {
|
|
111
|
+
case 'live': return 'green';
|
|
112
|
+
case 'revoked': return 'red';
|
|
113
|
+
case 'unverified': return 'gray';
|
|
114
|
+
case 'unconfigured': return 'dim';
|
|
115
|
+
default: return 'yellow'; // expired / rate_limited / error — soft, self-healing/indeterminate
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Color for a matrix cell that rolls up several accounts. Red only when a token
|
|
120
|
+
* was genuinely rejected (`revoked`); yellow for soft/expired; green when at least
|
|
121
|
+
* one account is live-verified and none are soft/revoked; gray when accounts are
|
|
122
|
+
* present but unverifiable (codex/grok) — never the alarming yellow the old
|
|
123
|
+
* renderer used, which made a fully-logged-in fleet read as half-broken.
|
|
124
|
+
*/
|
|
125
|
+
export function authCellColor(summary) {
|
|
126
|
+
if (summary.total === 0)
|
|
127
|
+
return 'dim';
|
|
128
|
+
if (summary.bad > 0)
|
|
129
|
+
return 'red';
|
|
130
|
+
if (summary.warn > 0)
|
|
131
|
+
return 'yellow';
|
|
132
|
+
if (summary.live > 0)
|
|
133
|
+
return 'green';
|
|
134
|
+
return 'gray'; // all present/unverifiable — signed in, neutral
|
|
96
135
|
}
|
|
97
136
|
/** Verdicts that mean "this token was rejected by the server — re-login required". */
|
|
98
137
|
export function isDeadVerdict(verdict) {
|
package/dist/lib/auto-pull.js
CHANGED
|
@@ -38,10 +38,17 @@ export function spawnDetachedSync() {
|
|
|
38
38
|
if (!fs.existsSync(workerPath))
|
|
39
39
|
return;
|
|
40
40
|
try {
|
|
41
|
+
// Scrub AGENTS_BRAND so the background sync always reconciles the FULL
|
|
42
|
+
// resource set into the shared agent homes. Otherwise a branded foreground
|
|
43
|
+
// invocation (e.g. `jack …`) would leak its curated/reduced profile into the
|
|
44
|
+
// detached sync and silently strip skills/plugins for the plain `agents`
|
|
45
|
+
// user (last-writer-wins on shared homes). Brand curation must stay a
|
|
46
|
+
// foreground, in-process view — never a background mutation of shared state.
|
|
47
|
+
const { AGENTS_BRAND: _brand, ...unbrandedEnv } = process.env;
|
|
41
48
|
const child = spawn(process.execPath, [workerPath], {
|
|
42
49
|
...backgroundSpawnOptions(),
|
|
43
50
|
stdio: 'ignore',
|
|
44
|
-
env:
|
|
51
|
+
env: unbrandedEnv,
|
|
45
52
|
});
|
|
46
53
|
child.unref();
|
|
47
54
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BrandConfig } from './types.js';
|
|
2
|
+
/** The default (unbranded) program name. */
|
|
3
|
+
export declare const DEFAULT_CLI_NAME = "agents";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the name this invocation runs under. The brand shim exports
|
|
6
|
+
* `AGENTS_BRAND`; when unset (a normal `agents`/`ag` call) we are unbranded and
|
|
7
|
+
* everything is byte-identical to before.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveBrandName(): string;
|
|
10
|
+
/** The active brand name, or null when unbranded. */
|
|
11
|
+
export declare function activeBrandName(): string | null;
|
|
12
|
+
/** True when this process is running under a brand (not the plain `agents` CLI). */
|
|
13
|
+
export declare function isBranded(): boolean;
|
|
14
|
+
/** Names that would clobber an agent CLI shim or the `agents`/`ag` binary. */
|
|
15
|
+
export declare function reservedBrandNames(): Set<string>;
|
|
16
|
+
/** Validate a proposed brand name; returns an error string or null when ok. */
|
|
17
|
+
export declare function validateBrandName(name: string): string | null;
|
|
18
|
+
/** All configured brands, keyed by name. */
|
|
19
|
+
export declare function listBrands(): Record<string, BrandConfig>;
|
|
20
|
+
/** One brand's config, or undefined. */
|
|
21
|
+
export declare function getBrandConfig(name: string): BrandConfig | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The active brand's config (from AGENTS_BRAND), or null when unbranded or when
|
|
24
|
+
* the brand is explicitly disabled (`enabled: false`) — a disabled brand's shim
|
|
25
|
+
* still works as a plain pass-through but applies no command/resource curation.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getActiveBrandConfig(): BrandConfig | null;
|
|
28
|
+
/**
|
|
29
|
+
* The resource-profile preset name a brand pins, or null. Read on the hot path
|
|
30
|
+
* by resource-profiles.ts to make the active profile brand-scoped.
|
|
31
|
+
*/
|
|
32
|
+
export declare function brandProfileName(): string | null;
|
|
33
|
+
/** Built-in top-level commands the active brand has turned off. */
|
|
34
|
+
export declare function disabledCommandsForActiveBrand(): Set<string>;
|
|
35
|
+
/** The preset name a brand owns (one resource profile per brand). */
|
|
36
|
+
export declare function brandPresetName(name: string): string;
|
|
37
|
+
/** Create or replace a brand's config in agents.yaml. */
|
|
38
|
+
export declare function upsertBrand(cfg: BrandConfig): void;
|
|
39
|
+
/** Remove a brand's config (leaves its resource preset unless `purgePreset`). */
|
|
40
|
+
export declare function removeBrand(name: string, purgePreset?: boolean): void;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand ("white-label") support — let a user run agents-cli under their own
|
|
3
|
+
* personally-named binary (e.g. `jack` instead of `agents`), skinned with their
|
|
4
|
+
* name and pinned to the exact feature set they enable.
|
|
5
|
+
*
|
|
6
|
+
* A brand is minted by `agents setup mine` / `agents mine init <name>`, which
|
|
7
|
+
* writes a pure pass-through shim (`~/.agents/.cache/shims/<name>`) that sets
|
|
8
|
+
* `AGENTS_BRAND=<name>` then execs the real agents-cli entrypoint. The entrypoint
|
|
9
|
+
* reads `AGENTS_BRAND` to (a) present its own name/help/errors as the brand, and
|
|
10
|
+
* (b) apply the brand's config: a list of disabled built-in commands plus an
|
|
11
|
+
* optional resource-profile preset that curates skills/plugins/mcp/etc.
|
|
12
|
+
*
|
|
13
|
+
* Brand config lives in `meta.brands[<name>]` (agents.yaml), so it rides
|
|
14
|
+
* `agents repo push/pull` across the fleet like every other user config. The
|
|
15
|
+
* curated resource set reuses the existing resource-profile engine — a brand
|
|
16
|
+
* pins a preset name (`meta.profiles.presets[...]`); see resource-profiles.ts,
|
|
17
|
+
* where the active profile is resolved brand-first.
|
|
18
|
+
*/
|
|
19
|
+
import { readMeta, updateMeta } from './state.js';
|
|
20
|
+
import { ALL_AGENT_IDS, AGENTS } from './agents.js';
|
|
21
|
+
/** The default (unbranded) program name. */
|
|
22
|
+
export const DEFAULT_CLI_NAME = 'agents';
|
|
23
|
+
/** Valid brand names: a letter, then letters/digits/_/- (matches alias rules). */
|
|
24
|
+
const BRAND_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the name this invocation runs under. The brand shim exports
|
|
27
|
+
* `AGENTS_BRAND`; when unset (a normal `agents`/`ag` call) we are unbranded and
|
|
28
|
+
* everything is byte-identical to before.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveBrandName() {
|
|
31
|
+
const raw = process.env.AGENTS_BRAND?.trim();
|
|
32
|
+
if (raw && BRAND_NAME_PATTERN.test(raw))
|
|
33
|
+
return raw;
|
|
34
|
+
return DEFAULT_CLI_NAME;
|
|
35
|
+
}
|
|
36
|
+
/** The active brand name, or null when unbranded. */
|
|
37
|
+
export function activeBrandName() {
|
|
38
|
+
const name = resolveBrandName();
|
|
39
|
+
return name === DEFAULT_CLI_NAME ? null : name;
|
|
40
|
+
}
|
|
41
|
+
/** True when this process is running under a brand (not the plain `agents` CLI). */
|
|
42
|
+
export function isBranded() {
|
|
43
|
+
return activeBrandName() !== null;
|
|
44
|
+
}
|
|
45
|
+
/** Names that would clobber an agent CLI shim or the `agents`/`ag` binary. */
|
|
46
|
+
export function reservedBrandNames() {
|
|
47
|
+
const reserved = new Set([DEFAULT_CLI_NAME, 'ag']);
|
|
48
|
+
for (const id of ALL_AGENT_IDS)
|
|
49
|
+
reserved.add(AGENTS[id].cliCommand);
|
|
50
|
+
return reserved;
|
|
51
|
+
}
|
|
52
|
+
/** Validate a proposed brand name; returns an error string or null when ok. */
|
|
53
|
+
export function validateBrandName(name) {
|
|
54
|
+
if (!BRAND_NAME_PATTERN.test(name)) {
|
|
55
|
+
return `Invalid name "${name}". Use a letter, then letters, digits, _ or -.`;
|
|
56
|
+
}
|
|
57
|
+
if (reservedBrandNames().has(name)) {
|
|
58
|
+
return `"${name}" is reserved (collides with an agent CLI or the agents binary).`;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/** All configured brands, keyed by name. */
|
|
63
|
+
export function listBrands() {
|
|
64
|
+
return readMeta().brands ?? {};
|
|
65
|
+
}
|
|
66
|
+
/** One brand's config, or undefined. */
|
|
67
|
+
export function getBrandConfig(name) {
|
|
68
|
+
return listBrands()[name];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The active brand's config (from AGENTS_BRAND), or null when unbranded or when
|
|
72
|
+
* the brand is explicitly disabled (`enabled: false`) — a disabled brand's shim
|
|
73
|
+
* still works as a plain pass-through but applies no command/resource curation.
|
|
74
|
+
*/
|
|
75
|
+
export function getActiveBrandConfig() {
|
|
76
|
+
const name = activeBrandName();
|
|
77
|
+
if (!name)
|
|
78
|
+
return null;
|
|
79
|
+
const cfg = getBrandConfig(name);
|
|
80
|
+
if (!cfg || cfg.enabled === false)
|
|
81
|
+
return null;
|
|
82
|
+
return cfg;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The resource-profile preset name a brand pins, or null. Read on the hot path
|
|
86
|
+
* by resource-profiles.ts to make the active profile brand-scoped.
|
|
87
|
+
*/
|
|
88
|
+
export function brandProfileName() {
|
|
89
|
+
const cfg = getActiveBrandConfig();
|
|
90
|
+
return cfg?.profile ?? null;
|
|
91
|
+
}
|
|
92
|
+
/** Built-in top-level commands the active brand has turned off. */
|
|
93
|
+
export function disabledCommandsForActiveBrand() {
|
|
94
|
+
const cfg = getActiveBrandConfig();
|
|
95
|
+
return new Set(cfg?.disabledCommands ?? []);
|
|
96
|
+
}
|
|
97
|
+
/** The preset name a brand owns (one resource profile per brand). */
|
|
98
|
+
export function brandPresetName(name) {
|
|
99
|
+
return `mine-${name}`;
|
|
100
|
+
}
|
|
101
|
+
/** Create or replace a brand's config in agents.yaml. */
|
|
102
|
+
export function upsertBrand(cfg) {
|
|
103
|
+
updateMeta((meta) => {
|
|
104
|
+
const brands = { ...(meta.brands ?? {}) };
|
|
105
|
+
brands[cfg.name] = cfg;
|
|
106
|
+
return { ...meta, brands };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** Remove a brand's config (leaves its resource preset unless `purgePreset`). */
|
|
110
|
+
export function removeBrand(name, purgePreset = false) {
|
|
111
|
+
updateMeta((meta) => {
|
|
112
|
+
const brands = { ...(meta.brands ?? {}) };
|
|
113
|
+
delete brands[name];
|
|
114
|
+
let profiles = meta.profiles;
|
|
115
|
+
if (purgePreset && profiles?.presets) {
|
|
116
|
+
const presets = { ...profiles.presets };
|
|
117
|
+
delete presets[brandPresetName(name)];
|
|
118
|
+
profiles = { ...profiles, presets };
|
|
119
|
+
}
|
|
120
|
+
return { ...meta, brands, ...(profiles ? { profiles } : {}) };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -5,11 +5,8 @@ import { getPortOccupant } from '../chrome.js';
|
|
|
5
5
|
import { parseEndpointUrl } from '../profiles.js';
|
|
6
6
|
import { writeProfileRuntime, clearProfileRuntime } from '../runtime-state.js';
|
|
7
7
|
// shellQuote lives in the shared ssh-exec helper (single choke point); re-export
|
|
8
|
-
// so existing importers of `shellQuote` from this module keep working.
|
|
9
|
-
|
|
10
|
-
// reuse it so the raw-ssh spawns below fail fast on an unreachable host instead
|
|
11
|
-
// of hanging on the default ~127s TCP timeout, rather than re-listing options.
|
|
12
|
-
import { shellQuote, SSH_OPTS, assertValidSshTarget } from '../../ssh-exec.js';
|
|
8
|
+
// so existing importers of `shellQuote` from this module keep working.
|
|
9
|
+
import { shellQuote, assertValidSshTarget, controlOpts, sshConnectOpts } from '../../ssh-exec.js';
|
|
13
10
|
export { shellQuote };
|
|
14
11
|
// The `ssh -L` tunnel spawn is shared with `agents computer --host`; it lives in
|
|
15
12
|
// the single ssh-tunnel helper. Calling it with no options preserves this
|
|
@@ -309,7 +306,7 @@ export async function ensureRemoteBrowser(user, host, browserType, port, remoteO
|
|
|
309
306
|
// macOS stops at the first non-option, so any `-o` after `user@host` would
|
|
310
307
|
// be swallowed into the remote command instead of applied.
|
|
311
308
|
const child = spawn('ssh', [
|
|
312
|
-
...
|
|
309
|
+
...sshConnectOpts(controlOpts()),
|
|
313
310
|
`${user}@${host}`,
|
|
314
311
|
remoteCmd,
|
|
315
312
|
], { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
@@ -384,7 +381,7 @@ function runSSHCommand(user, host, cmd) {
|
|
|
384
381
|
// host is bounded by ConnectTimeout (the local 3s kill below only bounds a
|
|
385
382
|
// connected-but-slow command, not the connect itself). Options precede the
|
|
386
383
|
// target — see ensureRemoteBrowser.
|
|
387
|
-
const child = spawn('ssh', [...
|
|
384
|
+
const child = spawn('ssh', [...sshConnectOpts(controlOpts()), `${user}@${host}`, cmd], {
|
|
388
385
|
stdio: 'ignore',
|
|
389
386
|
windowsHide: true,
|
|
390
387
|
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
import { execFile } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
4
|
import { BrowserCdpConnectionError, CDPClient, discoverBrowserWsUrl, verifyBrowserIdentity, } from './cdp.js';
|
|
7
5
|
import { getProfile, getProfileRuntimeDir, getProfileDownloadsDir, getProfileSessionsDir, getBrowserRuntimeDir, listProfiles, extractConfiguredPort, resolveEndpoint, } from './profiles.js';
|
|
8
6
|
import { killChrome, getRunningChromeInfo, launchBrowser, allocatePort } from './chrome.js';
|
|
@@ -16,6 +14,7 @@ import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focus
|
|
|
16
14
|
import { typeEditorText } from './editor.js';
|
|
17
15
|
import { detectUploadPattern, stageUploadFile, uploadToDropTarget, uploadToFileInput, uploadViaFileChooser, } from './upload.js';
|
|
18
16
|
import { emit } from '../events.js';
|
|
17
|
+
import { sshExecAsync } from '../ssh-exec.js';
|
|
19
18
|
function isPathInside(candidate, dir) {
|
|
20
19
|
const rel = path.relative(dir, candidate);
|
|
21
20
|
return rel === '' || (!!rel && !rel.startsWith('..') && !path.isAbsolute(rel));
|
|
@@ -149,7 +148,6 @@ export function pickWindowTarget(targets, filter) {
|
|
|
149
148
|
return visible;
|
|
150
149
|
return pages[0];
|
|
151
150
|
}
|
|
152
|
-
const execFileP = promisify(execFile);
|
|
153
151
|
/**
|
|
154
152
|
* Parse a `--since`/`--until` value. Accepts ISO-8601 absolute timestamps
|
|
155
153
|
* or relative offsets like `30s`, `5m`, `2h`, `1d`.
|
|
@@ -166,11 +164,11 @@ export function parseSinceUntil(s) {
|
|
|
166
164
|
return new Date(Date.now() - n * unitMs[m[2]]);
|
|
167
165
|
}
|
|
168
166
|
async function execSSH(host, cmd) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
return stdout;
|
|
167
|
+
const res = await sshExecAsync(host, cmd, { timeoutMs: 10_000 });
|
|
168
|
+
if (res.code !== 0) {
|
|
169
|
+
throw new Error(`ssh ${host} failed${res.stderr.trim() ? `: ${res.stderr.trim()}` : ''}`);
|
|
170
|
+
}
|
|
171
|
+
return res.stdout;
|
|
174
172
|
}
|
|
175
173
|
export function readNewestMatchingRemoteFileCommand(dir, prefix, tailLines) {
|
|
176
174
|
const glob = `${shellQuote(dir)}/${prefix}*.jsonl`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider barrel — importing this registers every built-in channel provider.
|
|
3
|
+
* "App" providers register themselves the same way (registerChannelProvider),
|
|
4
|
+
* so nothing here is privileged over an extension.
|
|
5
|
+
*/
|
|
6
|
+
import { registerChannelProvider } from '../registry.js';
|
|
7
|
+
import { mailboxProvider } from './mailbox.js';
|
|
8
|
+
import { rushProviders } from './rush.js';
|
|
9
|
+
import { openclawTelegramProvider } from './openclaw-telegram.js';
|
|
10
|
+
let registered = false;
|
|
11
|
+
/** Register all built-in providers once (idempotent). */
|
|
12
|
+
export function registerBuiltinProviders() {
|
|
13
|
+
if (registered)
|
|
14
|
+
return;
|
|
15
|
+
registered = true;
|
|
16
|
+
registerChannelProvider(mailboxProvider);
|
|
17
|
+
for (const p of rushProviders)
|
|
18
|
+
registerChannelProvider(p);
|
|
19
|
+
registerChannelProvider(openclawTelegramProvider);
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mailbox channel provider — delivers to an agent's file-based mailbox.
|
|
3
|
+
*
|
|
4
|
+
* The mailbox is one channel among many. This provider is a thin wrapper over
|
|
5
|
+
* the same `enqueue` seam that `agents message` uses (commands/message.ts), so
|
|
6
|
+
* both callers share one spool at ~/.agents/.history/mailbox/<id>/.
|
|
7
|
+
*/
|
|
8
|
+
import { mailboxDir, enqueue, isValidMailboxId } from '../../mailbox.js';
|
|
9
|
+
export const mailboxProvider = {
|
|
10
|
+
name: 'mailbox',
|
|
11
|
+
async send(text, opts) {
|
|
12
|
+
if (!isValidMailboxId(opts.target)) {
|
|
13
|
+
return { ok: false, channel: 'mailbox', id: opts.target, error: `invalid mailbox id '${opts.target}'` };
|
|
14
|
+
}
|
|
15
|
+
if (opts.dryRun) {
|
|
16
|
+
return { ok: true, channel: 'mailbox', id: opts.target };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const msgId = enqueue(mailboxDir(opts.target), { to: opts.target, text, from: opts.from });
|
|
20
|
+
return { ok: true, channel: 'mailbox', id: opts.target, msgId };
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return { ok: false, channel: 'mailbox', id: opts.target, error: err.message };
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|