@phnx-labs/agents-cli 1.22.83 → 1.22.85
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 +96 -0
- package/README.md +80 -53
- package/dist/bootstrap.d.ts +7 -6
- package/dist/bootstrap.js +20 -27
- package/dist/cli/command-registry.js +5 -1
- package/dist/commands/accounts.d.ts +6 -13
- package/dist/commands/accounts.js +330 -143
- package/dist/commands/apply.js +1 -1
- package/dist/commands/auth-mint.d.ts +7 -3
- package/dist/commands/auth-mint.js +21 -9
- package/dist/commands/auth.js +2 -3
- package/dist/commands/browser.js +10 -10
- package/dist/commands/daemon.d.ts +7 -3
- package/dist/commands/daemon.js +40 -25
- package/dist/commands/doctor.js +28 -2
- package/dist/commands/exec.js +87 -39
- package/dist/commands/fleet-capture.js +21 -2
- package/dist/commands/harness-wizard.js +2 -2
- package/dist/commands/lease.js +7 -12
- package/dist/commands/profiles.d.ts +2 -2
- package/dist/commands/profiles.js +20 -13
- package/dist/commands/repo.js +3 -3
- package/dist/commands/run-account-picker.d.ts +3 -3
- package/dist/commands/run-account-picker.js +29 -30
- package/dist/commands/secrets-passthrough.d.ts +20 -0
- package/dist/commands/secrets-passthrough.js +43 -0
- package/dist/commands/sessions-trace.d.ts +11 -0
- package/dist/commands/sessions-trace.js +71 -0
- package/dist/commands/setup-accounts.d.ts +3 -2
- package/dist/commands/setup-accounts.js +4 -4
- package/dist/commands/setup-secrets.d.ts +16 -21
- package/dist/commands/setup-secrets.js +43 -224
- package/dist/commands/ssh.js +2 -2
- package/dist/commands/sync.js +1 -1
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.js +43 -40
- package/dist/commands/versions.d.ts +11 -11
- package/dist/commands/versions.js +131 -83
- package/dist/commands/view.d.ts +1 -9
- package/dist/commands/view.js +41 -55
- package/dist/commands/webhook.js +5 -5
- package/dist/commands/workflows.js +2 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +13 -42
- package/dist/lib/account-capabilities.d.ts +1 -1
- package/dist/lib/account-capabilities.js +1 -1
- package/dist/lib/account-catalog.d.ts +121 -6
- package/dist/lib/account-catalog.js +421 -7
- package/dist/lib/account-registry.d.ts +8 -30
- package/dist/lib/account-registry.js +36 -79
- package/dist/lib/account-schema.d.ts +1 -1
- package/dist/lib/account-schema.js +1 -1
- package/dist/lib/accounting/account-pool-collect.js +16 -4
- package/dist/lib/accounting/rotate.d.ts +20 -7
- package/dist/lib/accounting/rotate.js +89 -13
- package/dist/lib/accounting/usage.js +11 -16
- package/dist/lib/accounts/add.d.ts +138 -0
- package/dist/lib/accounts/add.js +651 -0
- package/dist/lib/accounts/migrate.d.ts +117 -0
- package/dist/lib/accounts/migrate.js +536 -0
- package/dist/lib/accounts/slots.d.ts +13 -0
- package/dist/lib/accounts/slots.js +100 -0
- package/dist/lib/agent-spec/agents.d.ts +16 -0
- package/dist/lib/agent-spec/agents.js +32 -4
- package/dist/lib/app-bundle-install.js +5 -4
- package/dist/lib/auth-health.d.ts +3 -0
- package/dist/lib/auth-health.js +11 -0
- package/dist/lib/auth-mint.d.ts +52 -16
- package/dist/lib/auth-mint.js +117 -39
- package/dist/lib/browser/chrome.d.ts +1 -1
- package/dist/lib/browser/chrome.js +5 -5
- package/dist/lib/byok-usage.js +3 -3
- package/dist/lib/claude-account-token.d.ts +47 -3
- package/dist/lib/claude-account-token.js +172 -50
- package/dist/lib/cloud/antigravity.js +4 -4
- package/dist/lib/cloud/cursor.js +4 -4
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +6 -6
- package/dist/lib/crabbox/runtimes.d.ts +3 -3
- package/dist/lib/crabbox/runtimes.js +5 -5
- package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
- package/dist/lib/daemon/account-state-daemon-service.js +199 -3
- package/dist/lib/daemon/auth-sync-service.js +28 -1
- package/dist/lib/daemon/daemon.d.ts +6 -16
- package/dist/lib/daemon/daemon.js +52 -97
- package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
- package/dist/lib/daemon/daemon.test-fixture.js +22 -40
- package/dist/lib/daemon/harness-update-service.d.ts +1 -1
- package/dist/lib/daemon/harness-update-service.js +1 -1
- package/dist/lib/daemon/runner.d.ts +15 -3
- package/dist/lib/daemon/runner.js +49 -24
- package/dist/lib/daemon-health.d.ts +1 -2
- package/dist/lib/daemon-health.js +7 -6
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +1 -11
- package/dist/lib/daemon-ticks.js +23 -2
- package/dist/lib/daemon-webhooks.d.ts +8 -7
- package/dist/lib/daemon-webhooks.js +10 -9
- package/dist/lib/device-config.js +1 -1
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/harness-inventory.d.ts +39 -0
- package/dist/lib/devices/harness-inventory.js +126 -4
- package/dist/lib/doctor-diff.js +2 -1
- package/dist/lib/exec-account-home.d.ts +38 -0
- package/dist/lib/exec-account-home.js +164 -0
- package/dist/lib/exec.d.ts +30 -0
- package/dist/lib/exec.js +101 -31
- package/dist/lib/feed/activity-stream.d.ts +60 -0
- package/dist/lib/feed/activity-stream.js +271 -0
- package/dist/lib/feed/activity.d.ts +7 -0
- package/dist/lib/feed/activity.js +10 -4
- package/dist/lib/feed/watch.d.ts +9 -3
- package/dist/lib/feed/watch.js +72 -36
- package/dist/lib/fleet/apply.d.ts +2 -2
- package/dist/lib/fleet/apply.js +4 -4
- package/dist/lib/fleet/auth-sync.js +4 -3
- package/dist/lib/fleet-shared-repo-sync.js +6 -9
- package/dist/lib/fleet-shared-state.d.ts +6 -0
- package/dist/lib/harness/adapter.d.ts +7 -7
- package/dist/lib/harness/adapter.js +10 -6
- package/dist/lib/harness/adapters/grok.js +8 -3
- package/dist/lib/harness/adapters/muse.js +1 -1
- package/dist/lib/harness/adapters/opencode.js +12 -3
- package/dist/lib/harness-auth-capabilities.d.ts +61 -0
- package/dist/lib/harness-auth-capabilities.js +52 -0
- package/dist/lib/helper-versions.d.ts +6 -4
- package/dist/lib/helper-versions.js +5 -4
- package/dist/lib/hosts/credential-transport.d.ts +10 -0
- package/dist/lib/hosts/credential-transport.js +37 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +13 -6
- package/dist/lib/identity/client.d.ts +3 -3
- package/dist/lib/identity/client.js +3 -3
- package/dist/lib/installations/index.d.ts +1 -1
- package/dist/lib/installations/index.js +1 -1
- package/dist/lib/installations/migrate.js +9 -0
- package/dist/lib/installations/resolve.js +1 -1
- package/dist/lib/installations/shims.d.ts +15 -0
- package/dist/lib/installations/shims.js +69 -0
- package/dist/lib/installations/store.d.ts +43 -0
- package/dist/lib/installations/store.js +75 -7
- package/dist/lib/installations/versions.js +1 -1
- package/dist/lib/menubar/install-menubar.d.ts +3 -3
- package/dist/lib/menubar/install-menubar.js +3 -3
- package/dist/lib/native-accounts.d.ts +35 -0
- package/dist/lib/native-accounts.js +41 -0
- package/dist/lib/net-close.d.ts +11 -0
- package/dist/lib/net-close.js +25 -0
- package/dist/lib/openclaw-keychain.js +27 -1
- package/dist/lib/profiles.js +22 -9
- package/dist/lib/project-resources.js +1 -1
- package/dist/lib/reserved-stores.d.ts +53 -0
- package/dist/lib/reserved-stores.js +120 -0
- package/dist/lib/secrets-client.d.ts +191 -0
- package/dist/lib/secrets-client.js +710 -0
- package/dist/lib/secrets-policy.d.ts +222 -0
- package/dist/lib/secrets-policy.js +483 -0
- package/dist/lib/secrets-types.d.ts +188 -0
- package/dist/lib/secrets-types.js +24 -0
- package/dist/lib/service-manifest.js +3 -4
- package/dist/lib/session/active.d.ts +38 -4
- package/dist/lib/session/active.js +36 -4
- package/dist/lib/session/bash-command.js +10 -0
- package/dist/lib/session/db.d.ts +65 -2
- package/dist/lib/session/db.js +178 -18
- package/dist/lib/session/mirror.d.ts +5 -0
- package/dist/lib/session/mirror.js +166 -0
- package/dist/lib/session/parse.d.ts +49 -0
- package/dist/lib/session/parse.js +324 -30
- package/dist/lib/session/prompt.d.ts +93 -2
- package/dist/lib/session/prompt.js +266 -15
- package/dist/lib/session/remote/peer-stream.d.ts +47 -0
- package/dist/lib/session/remote/peer-stream.js +142 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +41 -42
- package/dist/lib/session/session-cache.d.ts +9 -0
- package/dist/lib/session/session-cache.js +40 -2
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/session/timeline-pass.d.ts +129 -0
- package/dist/lib/session/timeline-pass.js +323 -0
- package/dist/lib/session/timeline.d.ts +182 -0
- package/dist/lib/session/timeline.js +636 -0
- package/dist/lib/session/types.d.ts +155 -1
- package/dist/lib/sha256-asset.d.ts +11 -16
- package/dist/lib/sha256-asset.js +11 -16
- package/dist/lib/share/config.js +14 -5
- package/dist/lib/signin-badge.d.ts +26 -0
- package/dist/lib/signin-badge.js +42 -0
- package/dist/lib/staleness/detectors/workflows.js +3 -105
- package/dist/lib/staleness/writers/workflows.js +2 -1
- package/dist/lib/state.d.ts +7 -4
- package/dist/lib/state.js +18 -7
- package/dist/lib/summarizer/pass.d.ts +2 -0
- package/dist/lib/summarizer/pass.js +14 -1
- package/dist/lib/summarizer/summarize.d.ts +7 -0
- package/dist/lib/summarizer/summarize.js +3 -0
- package/dist/lib/sync-umbrella.js +2 -2
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/types.d.ts +74 -33
- package/dist/lib/view-types.d.ts +6 -3
- package/dist/lib/workflows-registry.d.ts +71 -0
- package/dist/lib/workflows-registry.js +280 -0
- package/dist/lib/workflows.d.ts +21 -21
- package/dist/lib/workflows.js +12 -327
- package/package.json +2 -3
- package/scripts/postinstall.js +4 -29
- package/dist/commands/secrets-import.d.ts +0 -18
- package/dist/commands/secrets-import.js +0 -74
- package/dist/commands/secrets-migrate.d.ts +0 -25
- package/dist/commands/secrets-migrate.js +0 -334
- package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
- package/dist/commands/secrets-rotate-passphrase.js +0 -96
- package/dist/commands/secrets-sync.d.ts +0 -11
- package/dist/commands/secrets-sync.js +0 -153
- package/dist/commands/secrets-vault.d.ts +0 -10
- package/dist/commands/secrets-vault.js +0 -130
- package/dist/commands/secrets.d.ts +0 -212
- package/dist/commands/secrets.js +0 -3030
- package/dist/lib/accounts/connect.d.ts +0 -170
- package/dist/lib/accounts/connect.js +0 -383
- package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
- package/dist/lib/daemon/keychain-reap-service.js +0 -32
- package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
- package/dist/lib/daemon/secrets-broker-service.js +0 -51
- package/dist/lib/secrets/agent.d.ts +0 -358
- package/dist/lib/secrets/agent.js +0 -1291
- package/dist/lib/secrets/audit.d.ts +0 -46
- package/dist/lib/secrets/audit.js +0 -101
- package/dist/lib/secrets/bundles.d.ts +0 -290
- package/dist/lib/secrets/bundles.js +0 -1547
- package/dist/lib/secrets/download-keychain.d.ts +0 -47
- package/dist/lib/secrets/download-keychain.js +0 -70
- package/dist/lib/secrets/drivers/rush.d.ts +0 -14
- package/dist/lib/secrets/drivers/rush.js +0 -90
- package/dist/lib/secrets/fallback.d.ts +0 -48
- package/dist/lib/secrets/fallback.js +0 -48
- package/dist/lib/secrets/filestore.d.ts +0 -222
- package/dist/lib/secrets/filestore.js +0 -1099
- package/dist/lib/secrets/headless.d.ts +0 -43
- package/dist/lib/secrets/headless.js +0 -56
- package/dist/lib/secrets/icloud-import.d.ts +0 -79
- package/dist/lib/secrets/icloud-import.js +0 -206
- package/dist/lib/secrets/index.d.ts +0 -451
- package/dist/lib/secrets/index.js +0 -1568
- package/dist/lib/secrets/install-helper.d.ts +0 -72
- package/dist/lib/secrets/install-helper.js +0 -245
- package/dist/lib/secrets/lease.d.ts +0 -25
- package/dist/lib/secrets/lease.js +0 -44
- package/dist/lib/secrets/linux.d.ts +0 -77
- package/dist/lib/secrets/linux.js +0 -393
- package/dist/lib/secrets/list-filter.d.ts +0 -109
- package/dist/lib/secrets/list-filter.js +0 -261
- package/dist/lib/secrets/mcp.d.ts +0 -93
- package/dist/lib/secrets/mcp.js +0 -211
- package/dist/lib/secrets/profiles.d.ts +0 -10
- package/dist/lib/secrets/profiles.js +0 -13
- package/dist/lib/secrets/push.d.ts +0 -133
- package/dist/lib/secrets/push.js +0 -273
- package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
- package/dist/lib/secrets/rc-hygiene.js +0 -143
- package/dist/lib/secrets/read-backoff.d.ts +0 -27
- package/dist/lib/secrets/read-backoff.js +0 -64
- package/dist/lib/secrets/reaper.d.ts +0 -90
- package/dist/lib/secrets/reaper.js +0 -243
- package/dist/lib/secrets/remote.d.ts +0 -152
- package/dist/lib/secrets/remote.js +0 -344
- package/dist/lib/secrets/reserved-sync.d.ts +0 -66
- package/dist/lib/secrets/reserved-sync.js +0 -147
- package/dist/lib/secrets/scope.d.ts +0 -26
- package/dist/lib/secrets/scope.js +0 -29
- package/dist/lib/secrets/session-store.d.ts +0 -107
- package/dist/lib/secrets/session-store.js +0 -342
- package/dist/lib/secrets/sync-backend.d.ts +0 -48
- package/dist/lib/secrets/sync-backend.js +0 -13
- package/dist/lib/secrets/sync-commands.d.ts +0 -21
- package/dist/lib/secrets/sync-commands.js +0 -21
- package/dist/lib/secrets/sync.d.ts +0 -48
- package/dist/lib/secrets/sync.js +0 -237
- package/dist/lib/secrets/unlock-hints.d.ts +0 -27
- package/dist/lib/secrets/unlock-hints.js +0 -36
- package/dist/lib/secrets/usage-db.d.ts +0 -46
- package/dist/lib/secrets/usage-db.js +0 -96
- package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
- package/dist/lib/secrets/vault-age-helper.js +0 -34
- package/dist/lib/secrets/vault.d.ts +0 -49
- package/dist/lib/secrets/vault.js +0 -399
- package/dist/lib/secrets/windows.d.ts +0 -81
- package/dist/lib/secrets/windows.js +0 -556
- package/scripts/install-helper.js +0 -97
- /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
- /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type KeychainBackend } from '../secrets/index.js';
|
|
2
1
|
/**
|
|
3
2
|
* Shared fixture for the daemon.*.test.ts suite slices (RUSH-2819).
|
|
4
3
|
*
|
|
@@ -9,15 +8,14 @@ import { type KeychainBackend } from '../secrets/index.js';
|
|
|
9
8
|
* process-spawning / real-daemon integration tests across workers; the
|
|
10
9
|
* helpers shared by more than one slice live here.
|
|
11
10
|
*/
|
|
12
|
-
/** An in-memory KeychainBackend stand-in, so a test never touches the real OS keychain. */
|
|
13
|
-
export declare function makeMemoryBackend(): {
|
|
14
|
-
backend: KeychainBackend;
|
|
15
|
-
store: Map<string, string>;
|
|
16
|
-
};
|
|
17
11
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
12
|
+
* Isolates every slice against a fresh, empty standalone `secrets` store
|
|
13
|
+
* (PHNX-3989) — an in-process fake keychain can no longer serve reads, since
|
|
14
|
+
* the client always spawns the real standalone, and several slices here also
|
|
15
|
+
* spawn a REAL `__daemon-run` subprocess that must see the SAME isolated
|
|
16
|
+
* store (a fresh `SECRETS_HOME` covers both: `useFreshSecretsHome` sets it as
|
|
17
|
+
* a `process.env` var, which every spawned child inherits by default). Call
|
|
18
|
+
* once at the top level of each slice file.
|
|
21
19
|
*/
|
|
22
20
|
export declare function installKeychainHermeticity(): void;
|
|
23
21
|
export declare const REPO_ROOT: string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
2
4
|
import * as path from 'path';
|
|
3
5
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import {
|
|
5
|
-
import { deleteBundle } from '../secrets/bundles.js';
|
|
6
|
+
import { deleteBundleSync, _resetSecretsClientForTest } from '../secrets-client.js';
|
|
6
7
|
/**
|
|
7
8
|
* Shared fixture for the daemon.*.test.ts suite slices (RUSH-2819).
|
|
8
9
|
*
|
|
@@ -13,54 +14,35 @@ import { deleteBundle } from '../secrets/bundles.js';
|
|
|
13
14
|
* process-spawning / real-daemon integration tests across workers; the
|
|
14
15
|
* helpers shared by more than one slice live here.
|
|
15
16
|
*/
|
|
16
|
-
/** An in-memory KeychainBackend stand-in, so a test never touches the real OS keychain. */
|
|
17
|
-
export function makeMemoryBackend() {
|
|
18
|
-
const store = new Map();
|
|
19
|
-
const backend = {
|
|
20
|
-
has: (item) => store.has(item),
|
|
21
|
-
get: (item) => {
|
|
22
|
-
const v = store.get(item);
|
|
23
|
-
if (v === undefined)
|
|
24
|
-
throw new Error(`Keychain item '${item}' not found.`);
|
|
25
|
-
return v;
|
|
26
|
-
},
|
|
27
|
-
set: (item, value) => { store.set(item, value); },
|
|
28
|
-
delete: (item) => store.delete(item),
|
|
29
|
-
list: (prefix) => Array.from(store.keys()).filter((k) => k.startsWith(prefix)),
|
|
30
|
-
};
|
|
31
|
-
return { backend, store };
|
|
32
|
-
}
|
|
33
17
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
18
|
+
* Isolates every slice against a fresh, empty standalone `secrets` store
|
|
19
|
+
* (PHNX-3989) — an in-process fake keychain can no longer serve reads, since
|
|
20
|
+
* the client always spawns the real standalone, and several slices here also
|
|
21
|
+
* spawn a REAL `__daemon-run` subprocess that must see the SAME isolated
|
|
22
|
+
* store (a fresh `SECRETS_HOME` covers both: `useFreshSecretsHome` sets it as
|
|
23
|
+
* a `process.env` var, which every spawned child inherits by default). Call
|
|
24
|
+
* once at the top level of each slice file.
|
|
37
25
|
*/
|
|
38
26
|
export function installKeychainHermeticity() {
|
|
39
|
-
let
|
|
40
|
-
let
|
|
27
|
+
let home = '';
|
|
28
|
+
let saved;
|
|
41
29
|
beforeEach(() => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// On a dev machine where the agent is live and the real `claude` bundle is
|
|
47
|
-
// unlocked, that returns the machine's real CLAUDE_CODE_OAUTH_TOKEN and this
|
|
48
|
-
// test reads a live credential instead of the seeded value (CI has no agent,
|
|
49
|
-
// so it only bites locally). Disable the agent so the read falls through to
|
|
50
|
-
// the in-memory backend above — hermetic regardless of host state.
|
|
51
|
-
prevNoAgent = process.env.AGENTS_SECRETS_NO_AGENT;
|
|
52
|
-
process.env.AGENTS_SECRETS_NO_AGENT = '1';
|
|
30
|
+
saved = process.env.SECRETS_HOME;
|
|
31
|
+
home = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-daemon-secrets-home-'));
|
|
32
|
+
process.env.SECRETS_HOME = home;
|
|
33
|
+
_resetSecretsClientForTest();
|
|
53
34
|
});
|
|
54
35
|
afterEach(() => {
|
|
55
36
|
try {
|
|
56
|
-
|
|
37
|
+
deleteBundleSync('claude');
|
|
57
38
|
}
|
|
58
39
|
catch { /* not created */ }
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
delete process.env.AGENTS_SECRETS_NO_AGENT;
|
|
40
|
+
if (saved === undefined)
|
|
41
|
+
delete process.env.SECRETS_HOME;
|
|
62
42
|
else
|
|
63
|
-
process.env.
|
|
43
|
+
process.env.SECRETS_HOME = saved;
|
|
44
|
+
_resetSecretsClientForTest();
|
|
45
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
64
46
|
});
|
|
65
47
|
}
|
|
66
48
|
// The real compiled CLI entry, shared by every slice that drives an actual
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* staging an npm install into a sibling directory, `fs.renameSync`/`fs.cpSync`
|
|
11
11
|
* swaps, `fs.rmSync` cleanup — potentially across several installations in one
|
|
12
12
|
* tick. Running that inline on the daemon's own event loop would stall every
|
|
13
|
-
* other service (
|
|
13
|
+
* other service (browser IPC, the scheduler, monitors) for the
|
|
14
14
|
* duration, the same class of problem `self-update-service.ts` solves for the
|
|
15
15
|
* CLI's OWN upgrade by installing into a fresh process it then exits into.
|
|
16
16
|
* Here there is no "exit and let the supervisor restart" option (the daemon
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* staging an npm install into a sibling directory, `fs.renameSync`/`fs.cpSync`
|
|
11
11
|
* swaps, `fs.rmSync` cleanup — potentially across several installations in one
|
|
12
12
|
* tick. Running that inline on the daemon's own event loop would stall every
|
|
13
|
-
* other service (
|
|
13
|
+
* other service (browser IPC, the scheduler, monitors) for the
|
|
14
14
|
* duration, the same class of problem `self-update-service.ts` solves for the
|
|
15
15
|
* CLI's OWN upgrade by installing into a fresh process it then exits into.
|
|
16
16
|
* Here there is no "exit and let the supervisor restart" option (the daemon
|
|
@@ -18,7 +18,7 @@ import { readMeta } from '../state.js';
|
|
|
18
18
|
import type { AgentId } from '../types.js';
|
|
19
19
|
import { type FallbackEntry } from '../exec.js';
|
|
20
20
|
import type { LoopDeps } from '../loop.js';
|
|
21
|
-
import { resolveRunVersion, resolveAccountVersion, type RotateResult } from '../accounting/rotate.js';
|
|
21
|
+
import { resolveRunVersion, resolveAccountCandidate, resolveAccountVersion, type RotateResult } from '../accounting/rotate.js';
|
|
22
22
|
/** Result of a completed job execution, including metadata and optional report. */
|
|
23
23
|
export interface RunResult {
|
|
24
24
|
meta: RunMeta;
|
|
@@ -110,7 +110,11 @@ export interface RoutineLaunchPlan {
|
|
|
110
110
|
rotation: RotateResult | null;
|
|
111
111
|
/** True when `config.version` pinned the target (no rotation). */
|
|
112
112
|
pinned: boolean;
|
|
113
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* When true, `buildJobCommand` appends `--account`. Native slot accounts
|
|
115
|
+
* share one managed-install version, so the pin is forwarded rather than
|
|
116
|
+
* treated as implied by the version. False only when the caller opts out.
|
|
117
|
+
*/
|
|
114
118
|
forwardAccount?: boolean;
|
|
115
119
|
}
|
|
116
120
|
/** Claude's own local auth check; unlike account metadata it cannot mistake identity for a usable login. */
|
|
@@ -127,6 +131,7 @@ export declare function claudeVersionIsAuthenticated(version: string): boolean;
|
|
|
127
131
|
export declare function resolveRoutineLaunch(config: JobConfig, cwd?: string, deps?: {
|
|
128
132
|
resolveRunVersion?: typeof resolveRunVersion;
|
|
129
133
|
resolveAccountVersion?: typeof resolveAccountVersion;
|
|
134
|
+
resolveAccountCandidate?: typeof resolveAccountCandidate;
|
|
130
135
|
findCredentialAccount?: (name: string) => boolean;
|
|
131
136
|
readMeta?: typeof readMeta;
|
|
132
137
|
resolveCredentialAccount?: (name: string, host: AgentId) => {
|
|
@@ -181,7 +186,14 @@ export declare function buildHostDispatchOptions(config: JobConfig, ctx: {
|
|
|
181
186
|
runDir: string;
|
|
182
187
|
detached: boolean;
|
|
183
188
|
}): import('../hosts/run-target.js').HostPromptRun;
|
|
184
|
-
export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow' | 'resume' | 'agent'>): boolean;
|
|
189
|
+
export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow' | 'resume' | 'agent' | 'account'>): boolean;
|
|
190
|
+
/**
|
|
191
|
+
* Inject a provider account's env into a routine spawn. Native accounts do not
|
|
192
|
+
* belong here — they re-enter `agents run <agent>#<name>` so T5 slot resolution
|
|
193
|
+
* picks the HOME. A provider-pinned routine stays on this path so the durable
|
|
194
|
+
* credential is still in the child env (PHNX-3940 T5 seam / T7).
|
|
195
|
+
*/
|
|
196
|
+
export declare function mergeRoutineProviderEnv(env: Record<string, string>, config: Pick<JobConfig, 'account' | 'agent' | 'workflow' | 'resume'>, agent: AgentId): Record<string, string>;
|
|
185
197
|
/**
|
|
186
198
|
* Merge sandbox/base env with the canonical per-version exec env
|
|
187
199
|
* (CLAUDE_CONFIG_DIR / CODEX_HOME / …) so routines share account isolation
|
|
@@ -39,10 +39,11 @@ import { logAndContinueOnLockCompromised } from '../lock-compromise.js';
|
|
|
39
39
|
import { walkForFiles } from '../fs-walk.js';
|
|
40
40
|
import { getBinaryPath, isVersionInstalled, resolveVersion, getVersionHomePath } from '../installations/versions.js';
|
|
41
41
|
import { resolveClaudeSetupToken } from '../claude-account-token.js';
|
|
42
|
-
import { getConfiguredRunStrategy, resolveRunVersion,
|
|
42
|
+
import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountCandidate, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, formatNoVerifiedUsageError, } from '../accounting/rotate.js';
|
|
43
43
|
import { isHeadedDeviceRole, selfConfiguredDeviceRole } from '../device-config.js';
|
|
44
44
|
import { isSelfUpdatingAgent, ROUTINE_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../agents.js';
|
|
45
45
|
import { isCustomHarnessName, readProfile } from '../profiles.js';
|
|
46
|
+
import { findAccount, findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } from '../account-registry.js';
|
|
46
47
|
export class RoutineAlreadyRunningError extends Error {
|
|
47
48
|
constructor(jobName, runId) {
|
|
48
49
|
super(`Routine '${jobName}' already has a running execution (${runId})`);
|
|
@@ -571,6 +572,19 @@ export function buildJobCommand(config, resolvedPrompt, forwardAccount = true) {
|
|
|
571
572
|
cmd.push('--account', config.account);
|
|
572
573
|
return cmd;
|
|
573
574
|
}
|
|
575
|
+
// Native slot accounts share one managed binary version, so the baked
|
|
576
|
+
// harness argv cannot identify which login to spawn. Dispatch through
|
|
577
|
+
// `agents run <agent>#<name>` (the same seam as resume/custom) so T5
|
|
578
|
+
// spawn-time resolution selects the slot. `--account` is an agents-cli
|
|
579
|
+
// flag; the harness binary would ignore or reject it.
|
|
580
|
+
// Provider/durable-pinned accounts stay on the harness argv + env-injection
|
|
581
|
+
// path: `#name` is a native selector and would not inject their bundle.
|
|
582
|
+
if (config.account && forwardAccount && !findAccount(config.account)) {
|
|
583
|
+
const spec = config.version
|
|
584
|
+
? `${agent}@${config.version}#${config.account}`
|
|
585
|
+
: `${agent}#${config.account}`;
|
|
586
|
+
return ['agents', 'run', spec, resolvedPrompt, '--mode', config.mode];
|
|
587
|
+
}
|
|
574
588
|
const template = bakeRoutineArgv(agent);
|
|
575
589
|
if (!template) {
|
|
576
590
|
// A name outside both tables was either never valid or was a custom
|
|
@@ -934,7 +948,11 @@ export async function resolveRoutineLaunch(config, cwd = process.cwd(), deps = {
|
|
|
934
948
|
throw new Error(`Routine '${config.name}' account '${config.account}' is a ${unified.agent} login and cannot authenticate ${agent}.`);
|
|
935
949
|
}
|
|
936
950
|
const identity = unified?.kind === 'native' ? unified.identityKey : config.account;
|
|
937
|
-
|
|
951
|
+
// Prefer the full candidate so a slot-sourced native account (shared
|
|
952
|
+
// managed-install `version`) still carries its name for `--account`.
|
|
953
|
+
const accountVersion = deps.resolveAccountVersion
|
|
954
|
+
? await deps.resolveAccountVersion(agent, identity)
|
|
955
|
+
: (await (deps.resolveAccountCandidate ?? resolveAccountCandidate)(agent, identity))?.version ?? null;
|
|
938
956
|
if (accountVersion) {
|
|
939
957
|
if (config.version && config.version !== accountVersion) {
|
|
940
958
|
throw new Error(`Routine '${config.name}' account '${config.account}' is signed in at ${agent}@${accountVersion}, not pinned ${agent}@${config.version}.`);
|
|
@@ -943,7 +961,10 @@ export async function resolveRoutineLaunch(config, cwd = process.cwd(), deps = {
|
|
|
943
961
|
chain: [{ agent, version: config.version ?? accountVersion }],
|
|
944
962
|
rotation: null,
|
|
945
963
|
pinned: true,
|
|
946
|
-
|
|
964
|
+
// Post-T5 every native slot on a harness shares the managed binary
|
|
965
|
+
// version, so the version pin is no longer enough — forward `--account`
|
|
966
|
+
// (name or `#name`) so spawn resolves the selected slot.
|
|
967
|
+
forwardAccount: true,
|
|
947
968
|
};
|
|
948
969
|
}
|
|
949
970
|
throw new Error(`Routine '${config.name}' account '${config.account}' is not signed in for ${agent}; refusing to rotate to another account.`);
|
|
@@ -1144,7 +1165,29 @@ export function buildHostDispatchOptions(config, ctx) {
|
|
|
1144
1165
|
};
|
|
1145
1166
|
}
|
|
1146
1167
|
export function dispatchesViaAgentsRun(config) {
|
|
1147
|
-
return Boolean(config.workflow
|
|
1168
|
+
return Boolean(config.workflow
|
|
1169
|
+
|| config.resume
|
|
1170
|
+
|| (config.agent && isCustomHarnessName(config.agent))
|
|
1171
|
+
|| (config.account && config.agent && !findAccount(config.account)));
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Inject a provider account's env into a routine spawn. Native accounts do not
|
|
1175
|
+
* belong here — they re-enter `agents run <agent>#<name>` so T5 slot resolution
|
|
1176
|
+
* picks the HOME. A provider-pinned routine stays on this path so the durable
|
|
1177
|
+
* credential is still in the child env (PHNX-3940 T5 seam / T7).
|
|
1178
|
+
*/
|
|
1179
|
+
export function mergeRoutineProviderEnv(env, config, agent) {
|
|
1180
|
+
if (dispatchesViaAgentsRun({ ...config, agent }))
|
|
1181
|
+
return env;
|
|
1182
|
+
const meta = readMeta();
|
|
1183
|
+
const selected = resolveAccountSelection(config.account, agent, meta);
|
|
1184
|
+
if (!selected)
|
|
1185
|
+
return env;
|
|
1186
|
+
const unified = findUnifiedAccount(selected.id, meta, undefined, agent);
|
|
1187
|
+
if (unified?.kind !== 'provider')
|
|
1188
|
+
return env;
|
|
1189
|
+
Object.assign(env, resolveCredentialAccount(unified.name, agent).env);
|
|
1190
|
+
return env;
|
|
1148
1191
|
}
|
|
1149
1192
|
/**
|
|
1150
1193
|
* Merge sandbox/base env with the canonical per-version exec env
|
|
@@ -1390,18 +1433,7 @@ async function executeJobPlaced(config, deps, attempt) {
|
|
|
1390
1433
|
: harnessName
|
|
1391
1434
|
? readProfile(harnessName).host.agent
|
|
1392
1435
|
: config.agent;
|
|
1393
|
-
|
|
1394
|
-
const { findUnifiedAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
|
|
1395
|
-
const meta = readMeta();
|
|
1396
|
-
const selectedAccount = resolveAccountSelection(config.account, effectiveAgent, meta)?.id;
|
|
1397
|
-
if (selectedAccount) {
|
|
1398
|
-
// Only a provider account injects env; a native account (explicit or via a
|
|
1399
|
-
// device-scoped binding) is read from the harness home and forwards nothing.
|
|
1400
|
-
const unified = findUnifiedAccount(selectedAccount, meta);
|
|
1401
|
-
if (unified?.kind === 'provider')
|
|
1402
|
-
Object.assign(baseEnv, resolveCredentialAccount(selectedAccount, effectiveAgent).env);
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1436
|
+
mergeRoutineProviderEnv(baseEnv, config, effectiveAgent);
|
|
1405
1437
|
// RUSH-2860: if this host holds gh auth, the sandbox child MUST see it.
|
|
1406
1438
|
if (useSandbox)
|
|
1407
1439
|
assertSandboxForwardsHostGhAuth(baseEnv);
|
|
@@ -1912,14 +1944,7 @@ async function executeJobDetachedClaimed(config, attempt, hooks) {
|
|
|
1912
1944
|
const baseEnv = injectRoutineActor(useSandbox
|
|
1913
1945
|
? buildSpawnEnv(overlayHome, config.env)
|
|
1914
1946
|
: { ...process.env }, config);
|
|
1915
|
-
|
|
1916
|
-
const { findAccount, resolveAccountSelection, resolveCredentialAccount } = await import('../account-registry.js');
|
|
1917
|
-
const selectedAccount = config.account && !findAccount(config.account)
|
|
1918
|
-
? undefined
|
|
1919
|
-
: resolveAccountSelection(config.account, config.agent, readMeta())?.id;
|
|
1920
|
-
if (selectedAccount)
|
|
1921
|
-
Object.assign(baseEnv, resolveCredentialAccount(selectedAccount, config.agent).env);
|
|
1922
|
-
}
|
|
1947
|
+
mergeRoutineProviderEnv(baseEnv, config, config.agent);
|
|
1923
1948
|
const spawnEnv = dispatchesViaAgentsRun(config)
|
|
1924
1949
|
? (() => {
|
|
1925
1950
|
const e = { ...baseEnv };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
|
|
2
|
-
export declare const SUBSYSTEM_SECRETS_BROKER = "secrets-broker";
|
|
3
2
|
export declare const SUBSYSTEM_BROWSER_IPC = "browser-ipc";
|
|
4
3
|
/**
|
|
5
4
|
* Daemon startup itself (RUSH-2418). Unlike the two above, this record is
|
|
@@ -12,7 +11,7 @@ export declare const SUBSYSTEM_BROWSER_IPC = "browser-ipc";
|
|
|
12
11
|
export declare const SUBSYSTEM_DAEMON_START = "daemon-start";
|
|
13
12
|
/** One subsystem's health as of the last time it reported in. */
|
|
14
13
|
export interface SubsystemHealth {
|
|
15
|
-
/** Stable identifier, e.g. '
|
|
14
|
+
/** Stable identifier, e.g. 'browser-ipc', 'monitors'. */
|
|
16
15
|
subsystem: string;
|
|
17
16
|
/** Most recent error message, or null if it has never failed. */
|
|
18
17
|
lastError: string | null;
|
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
* Today a subsystem failure inside `runDaemon()` (daemon.ts) is a single
|
|
5
5
|
* `log('ERROR', ...)` line that scrolls out of the log file and is never
|
|
6
6
|
* surfaced anywhere else — `agents daemon status` has no way to answer "is the
|
|
7
|
-
*
|
|
8
|
-
* alive". This module gives every subsystem a small persisted record —
|
|
7
|
+
* browser IPC server actually healthy right now?" beyond "the daemon process
|
|
8
|
+
* is alive". This module gives every subsystem a small persisted record —
|
|
9
9
|
* {@link SubsystemHealth} — so `agents daemon status` / `agents daemon
|
|
10
10
|
* services` can report health, not just liveness (RUSH-2354).
|
|
11
11
|
*
|
|
12
12
|
* Scheduled routines get this for free once migrated onto `agents routines`
|
|
13
13
|
* (their run history already carries success/failure — `agents routines
|
|
14
14
|
* stats`). This module exists for the subsystems that predate routines and have
|
|
15
|
-
* no run history of their own: the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* no run history of their own: the browser IPC server, plus the daemon's own
|
|
16
|
+
* startup (`SUBSYSTEM_DAEMON_START`, RUSH-2418) — which is the one record that
|
|
17
|
+
* also GATES behaviour rather than only reporting it. The secrets broker moved
|
|
18
|
+
* with the standalone `secrets` engine (PHNX-3989 OWN-1) — this daemon no
|
|
19
|
+
* longer hosts or supervises it, so it carries no health record here.
|
|
18
20
|
*
|
|
19
21
|
* File-backed (one JSON object keyed by subsystem name) rather than in-memory
|
|
20
22
|
* because `agents daemon status` runs as a SEPARATE process from the daemon —
|
|
@@ -26,7 +28,6 @@ import { getDaemonDir } from './state.js';
|
|
|
26
28
|
import { atomicWriteFileSync, ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
27
29
|
const HEALTH_FILE = 'health.json';
|
|
28
30
|
/** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
|
|
29
|
-
export const SUBSYSTEM_SECRETS_BROKER = 'secrets-broker';
|
|
30
31
|
export const SUBSYSTEM_BROWSER_IPC = 'browser-ipc';
|
|
31
32
|
/**
|
|
32
33
|
* Daemon startup itself (RUSH-2418). Unlike the two above, this record is
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* enabled without pulling in the whole daemon lifecycle.
|
|
8
8
|
*/
|
|
9
9
|
/** Every service the daemon can host. IDs are kebab-case and stable. */
|
|
10
|
-
export type DaemonServiceId = '
|
|
10
|
+
export type DaemonServiceId = 'scheduler' | 'catchup' | 'monitors' | 'browser-ipc' | 'webhook-receiver' | 'self-heal' | 'self-update' | 'account-state' | 'account-auth' | 'watchdog' | 'device-probe' | 'state-dir-check' | 'session-index' | 'auth-sync' | 'usage-sync' | 'daemon-heartbeat' | 'tmux-reap' | 'browser-task-reap' | 'session-state' | 'session-summarizer' | 'harness-update';
|
|
11
11
|
/** Human-readable metadata for each service. */
|
|
12
12
|
export interface DaemonServiceDef {
|
|
13
13
|
id: DaemonServiceId;
|
|
@@ -12,11 +12,6 @@ import * as yaml from 'yaml';
|
|
|
12
12
|
import { getDaemonConfigDir } from './state.js';
|
|
13
13
|
import { atomicWriteFileSync } from './fs-atomic.js';
|
|
14
14
|
export const DAEMON_SERVICES = [
|
|
15
|
-
{
|
|
16
|
-
id: 'secrets-broker',
|
|
17
|
-
title: 'Secrets broker',
|
|
18
|
-
description: 'Hosts the keychain-backed secrets broker socket so unlocked bundles stay warm across agent runs.',
|
|
19
|
-
},
|
|
20
15
|
{
|
|
21
16
|
id: 'scheduler',
|
|
22
17
|
title: 'Routine scheduler',
|
|
@@ -40,7 +35,7 @@ export const DAEMON_SERVICES = [
|
|
|
40
35
|
{
|
|
41
36
|
id: 'webhook-receiver',
|
|
42
37
|
title: 'Webhook receiver',
|
|
43
|
-
description: 'Hosts signed GitHub/Linear webhook ingress declared in daemon/webhooks.yaml, drawing signing secrets from the
|
|
38
|
+
description: 'Hosts signed GitHub/Linear webhook ingress declared in daemon/webhooks.yaml, drawing signing secrets from the standalone secrets CLI; binds nothing when no receivers are declared.',
|
|
44
39
|
},
|
|
45
40
|
{
|
|
46
41
|
id: 'self-heal',
|
|
@@ -52,11 +47,6 @@ export const DAEMON_SERVICES = [
|
|
|
52
47
|
title: 'Self-update',
|
|
53
48
|
description: 'Checks npm for a newer agents-cli, installs + verifies it, then exits so the OS supervisor relaunches onto the new code (PHNX-3695).',
|
|
54
49
|
},
|
|
55
|
-
{
|
|
56
|
-
id: 'keychain-reap',
|
|
57
|
-
title: 'Keychain reap',
|
|
58
|
-
description: 'Reaps orphaned keychain helpers and stuck agents processes.',
|
|
59
|
-
},
|
|
60
50
|
{
|
|
61
51
|
id: 'account-state',
|
|
62
52
|
title: 'Account usage refresh',
|
package/dist/lib/daemon-ticks.js
CHANGED
|
@@ -162,8 +162,29 @@ export async function runActiveSessionsWarmTick(opts = {}) {
|
|
|
162
162
|
console.log('active-sessions warm: idle (no recent reader), skipping gather');
|
|
163
163
|
return { sessions: 0 };
|
|
164
164
|
}
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
// ONE gather per tick, then fold, then publish. The order is load-bearing:
|
|
166
|
+
// the publish's per-row merge reads the timeline cache, so folding first is
|
|
167
|
+
// what puts a CURRENT timeline on the row this tick writes to the journal
|
|
168
|
+
// rather than the previous tick's (PHNX-3939).
|
|
169
|
+
const gather = opts.gather ?? (async () => {
|
|
170
|
+
const { getActiveSessions } = await import('./session/active.js');
|
|
171
|
+
return getActiveSessions({ localOnly: true });
|
|
172
|
+
});
|
|
173
|
+
const gathered = await gather();
|
|
174
|
+
// Bounded so it can never own the tick: at most 8 sessions, and for the
|
|
175
|
+
// resumable harnesses only the bytes each transcript grew by. A failure here
|
|
176
|
+
// must not cost the publish.
|
|
177
|
+
const { runTimelinePassSync } = await import('./session/timeline-pass.js');
|
|
178
|
+
let timeline = { computed: 0, reused: 0, skipped: 0 };
|
|
179
|
+
try {
|
|
180
|
+
timeline = runTimelinePassSync({ sessions: gathered, nowMs });
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
console.log(`active-sessions warm: timeline pass failed: ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
const r = await publishLocalActiveSessions({ gather: async () => gathered, nowMs });
|
|
186
|
+
console.log(`active-sessions warm: ${r.sessions.length} session(s) published; `
|
|
187
|
+
+ `timeline ${timeline.computed} folded, ${timeline.reused} current, ${timeline.skipped} skipped`);
|
|
167
188
|
return { sessions: r.sessions.length };
|
|
168
189
|
}
|
|
169
190
|
/**
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* `~/.agents/daemon/webhooks.yaml` declares which signed webhook receivers THIS
|
|
5
5
|
* box hosts — bundle, local port, rate limit, and an optional public Tailscale
|
|
6
6
|
* Funnel port. The daemon's `webhook-receiver` service reads it and binds one
|
|
7
|
-
* receiver per entry, drawing each receiver's signing secret
|
|
8
|
-
*
|
|
9
|
-
* absent or empty file hosts
|
|
7
|
+
* receiver per entry, drawing each receiver's signing secret through the
|
|
8
|
+
* standalone `secrets` CLI (an agentOnly read via secrets-client.ts — no
|
|
9
|
+
* `AGENTS_SECRETS_PASSPHRASE`, no `nohup`). An absent or empty file hosts
|
|
10
|
+
* nothing, so an unconfigured box binds nothing.
|
|
10
11
|
*
|
|
11
12
|
* This is per-box operational state (a public receiver runs on exactly one box),
|
|
12
13
|
* so it is deliberately NOT part of the fleet-synced device config — it mirrors
|
|
@@ -87,15 +88,15 @@ type Logger = (level: string, message: string) => void;
|
|
|
87
88
|
* `server.listen()` surfaces EADDRINUSE as an `'error'` event, so a `try/catch`
|
|
88
89
|
* around the start call never sees it and the event reaches the process-level
|
|
89
90
|
* `uncaughtException` handler (`index.ts`), which exits 1 for the supervisor to
|
|
90
|
-
* restart — a crash loop that would take the
|
|
91
|
-
*
|
|
92
|
-
*
|
|
91
|
+
* restart — a crash loop that would take the scheduler, monitors, browser IPC,
|
|
92
|
+
* and self-heal down with it. `waitForListening` is what turns that into one
|
|
93
|
+
* skipped receiver.
|
|
93
94
|
*/
|
|
94
95
|
export declare function startHostedWebhookReceivers(opts: {
|
|
95
96
|
log: Logger;
|
|
96
97
|
/**
|
|
97
98
|
* How a receiver's signing secrets are resolved. Defaults to
|
|
98
|
-
* `resolveReceiverSecrets` (the real
|
|
99
|
+
* `resolveReceiverSecrets` (the real secrets-client read). Injectable for the same
|
|
99
100
|
* reason `FireWebhookOptions.dispatch` is — so a test can exercise the bind
|
|
100
101
|
* path against real sockets without a machine-local secrets bundle.
|
|
101
102
|
*/
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* `~/.agents/daemon/webhooks.yaml` declares which signed webhook receivers THIS
|
|
5
5
|
* box hosts — bundle, local port, rate limit, and an optional public Tailscale
|
|
6
6
|
* Funnel port. The daemon's `webhook-receiver` service reads it and binds one
|
|
7
|
-
* receiver per entry, drawing each receiver's signing secret
|
|
8
|
-
*
|
|
9
|
-
* absent or empty file hosts
|
|
7
|
+
* receiver per entry, drawing each receiver's signing secret through the
|
|
8
|
+
* standalone `secrets` CLI (an agentOnly read via secrets-client.ts — no
|
|
9
|
+
* `AGENTS_SECRETS_PASSPHRASE`, no `nohup`). An absent or empty file hosts
|
|
10
|
+
* nothing, so an unconfigured box binds nothing.
|
|
10
11
|
*
|
|
11
12
|
* This is per-box operational state (a public receiver runs on exactly one box),
|
|
12
13
|
* so it is deliberately NOT part of the fleet-synced device config — it mirrors
|
|
@@ -18,8 +19,8 @@ import * as yaml from 'yaml';
|
|
|
18
19
|
import { exec } from 'child_process';
|
|
19
20
|
import { getDaemonConfigDir, getRuntimeStateDir } from './state.js';
|
|
20
21
|
import { atomicWriteFileSync } from './fs-atomic.js';
|
|
21
|
-
import { closeServerBounded } from './
|
|
22
|
-
import {
|
|
22
|
+
import { closeServerBounded } from './net-close.js';
|
|
23
|
+
import { readAndResolveBundleEnvSync } from './secrets-client.js';
|
|
23
24
|
import { startWebhookServer, createFileDeliveryStore, waitForListening } from './triggers/webhook.js';
|
|
24
25
|
import { buildFunnelUpCommand, FUNNEL_PORTS } from './funnel.js';
|
|
25
26
|
export const DEFAULT_WEBHOOK_PORT = 8787;
|
|
@@ -107,7 +108,7 @@ export function removeHostedReceiver(port) {
|
|
|
107
108
|
* verifiable signature. Throws when neither webhook secret is present.
|
|
108
109
|
*/
|
|
109
110
|
export function resolveReceiverSecrets(bundle) {
|
|
110
|
-
const { env } =
|
|
111
|
+
const { env } = readAndResolveBundleEnvSync(bundle, { caller: 'daemon webhook-receiver', agentOnly: true });
|
|
111
112
|
const secrets = {};
|
|
112
113
|
if (env.GITHUB_WEBHOOK_SECRET)
|
|
113
114
|
secrets.github = env.GITHUB_WEBHOOK_SECRET;
|
|
@@ -150,9 +151,9 @@ function reconcileFunnel(publicPort, localPort, log) {
|
|
|
150
151
|
* `server.listen()` surfaces EADDRINUSE as an `'error'` event, so a `try/catch`
|
|
151
152
|
* around the start call never sees it and the event reaches the process-level
|
|
152
153
|
* `uncaughtException` handler (`index.ts`), which exits 1 for the supervisor to
|
|
153
|
-
* restart — a crash loop that would take the
|
|
154
|
-
*
|
|
155
|
-
*
|
|
154
|
+
* restart — a crash loop that would take the scheduler, monitors, browser IPC,
|
|
155
|
+
* and self-heal down with it. `waitForListening` is what turns that into one
|
|
156
|
+
* skipped receiver.
|
|
156
157
|
*/
|
|
157
158
|
export async function startHostedWebhookReceivers(opts) {
|
|
158
159
|
const { log } = opts;
|
|
@@ -182,7 +182,7 @@ export const CONFIG_KEYS = [
|
|
|
182
182
|
visibility: 'machine',
|
|
183
183
|
type: 'bool',
|
|
184
184
|
defaultValue: true,
|
|
185
|
-
description: 'Whether the daemon may run on this device at all (
|
|
185
|
+
description: 'Whether the daemon may run on this device at all (browser IPC, watchdog, and the ' +
|
|
186
186
|
'routines scheduler). Disabling is the top-level kill switch: nothing auto-starts the daemon while it ' +
|
|
187
187
|
'is set, including `routines add`/`routines start`/`routines catchup`/`monitors add`/webhook triggers. ' +
|
|
188
188
|
'`agents daemon start` still starts it explicitly.',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentId } from '../types.js';
|
|
2
2
|
import type { DuplicateVersionHook } from '../hooks/install.js';
|
|
3
3
|
import type { AgentsBinaryShadow } from '../binary-shadow.js';
|
|
4
|
-
import type { RcSecretFinding } from '../secrets
|
|
4
|
+
import type { RcSecretFinding } from '../secrets-types.js';
|
|
5
5
|
import type { OwnerSinkStatus } from '../channels/owner-sink.js';
|
|
6
6
|
import { type WindowsSshEnrollmentAudit } from './windows-ssh-enrollment.js';
|
|
7
7
|
import type { SyncStatusRow, OrphanRow } from '../drift.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AccountInfo } from '../agents.js';
|
|
2
2
|
import type { AgentId } from '../types.js';
|
|
3
3
|
import { type UsageSnapshot } from '../accounting/usage.js';
|
|
4
|
+
import { type AccountVerdict } from '../signin-badge.js';
|
|
4
5
|
/** An account's usage headroom rolled into one glanceable summary. */
|
|
5
6
|
export interface QuotaSummary {
|
|
6
7
|
/**
|
|
@@ -33,6 +34,8 @@ export interface HarnessRow {
|
|
|
33
34
|
ready: boolean;
|
|
34
35
|
/** When `!ready`, a short reason ("signed out" / "rate-limited"). */
|
|
35
36
|
reason?: string;
|
|
37
|
+
verdict: AccountVerdict;
|
|
38
|
+
fix: string | null;
|
|
36
39
|
}
|
|
37
40
|
/** One host's rows, or the reason it produced none. */
|
|
38
41
|
export interface HostHarnessResult {
|
|
@@ -65,6 +68,25 @@ export interface AccountGroup {
|
|
|
65
68
|
* account) and takes the highest utilization across those windows. Pure.
|
|
66
69
|
*/
|
|
67
70
|
export declare function summarizeQuota(snapshot: UsageSnapshot | null | undefined, unavailableReason?: string | null, accountStatus?: AccountInfo['usageStatus']): QuotaSummary;
|
|
71
|
+
/**
|
|
72
|
+
* Scope/permission failures on a worker mean the usage endpoint cannot be read.
|
|
73
|
+
* They are not evidence of exhausted credits, even when stale account metadata
|
|
74
|
+
* previously carried that coarse status.
|
|
75
|
+
*/
|
|
76
|
+
export declare function summarizeObservedQuota(snapshot: UsageSnapshot | null | undefined, error: string | null | undefined, accountStatus?: AccountInfo['usageStatus']): {
|
|
77
|
+
quota: QuotaSummary;
|
|
78
|
+
unverified: boolean;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* A scope/permission failure on the usage endpoint is not a credit claim.
|
|
82
|
+
* Strip utilization and, only when auth still looks healthy, surface
|
|
83
|
+
* `unverified` instead of `live`/`rate_limited`. A real auth failure
|
|
84
|
+
* (expired/revoked/missing) stays the auth failure.
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyUsageHonesty(verdict: AccountVerdict, usage: QuotaSummary | null): {
|
|
87
|
+
verdict: AccountVerdict;
|
|
88
|
+
usage: QuotaSummary | null;
|
|
89
|
+
};
|
|
68
90
|
/**
|
|
69
91
|
* "Ready" = signed in AND not rate-limited. A missing quota snapshot does NOT
|
|
70
92
|
* block readiness: the account is signed in and usable, we just have no live
|
|
@@ -104,3 +126,20 @@ export declare function renderHarnessMatrix(results: HostHarnessResult[]): strin
|
|
|
104
126
|
* account (installs sharing it collapsed), showing which harnesses use it. Pure.
|
|
105
127
|
*/
|
|
106
128
|
export declare function renderAccountsMatrix(results: HostHarnessResult[]): string[];
|
|
129
|
+
export interface AccountFleetMatrixRow {
|
|
130
|
+
harness: AgentId;
|
|
131
|
+
name: string | null;
|
|
132
|
+
identityLabel: string;
|
|
133
|
+
devices: Array<{
|
|
134
|
+
device: string;
|
|
135
|
+
verdict: AccountVerdict;
|
|
136
|
+
}>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Account × device matrix used by `agents accounts list --fleet`.
|
|
140
|
+
* Rows are logical accounts, not installations. Columns come from the
|
|
141
|
+
* daemon-state observations already joined onto each account row.
|
|
142
|
+
*/
|
|
143
|
+
export declare function renderAccountFleetMatrix(rows: AccountFleetMatrixRow[], opts?: {
|
|
144
|
+
uncoveredDevices?: string[];
|
|
145
|
+
}): string[];
|