@phnx-labs/agents-cli 1.22.66 → 1.22.69
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 +56 -0
- package/README.md +21 -8
- package/dist/bootstrap.js +6 -3
- package/dist/commands/browser.js +46 -21
- package/dist/commands/daemon-test-harness.d.ts +1 -1
- package/dist/commands/daemon-test-harness.js +2 -2
- package/dist/commands/daemon.js +21 -22
- package/dist/commands/exec.js +50 -0
- package/dist/commands/feed.js +20 -7
- package/dist/commands/monitors.js +5 -2
- package/dist/commands/projects.d.ts +26 -6
- package/dist/commands/projects.js +55 -22
- package/dist/commands/repo.js +57 -19
- package/dist/commands/resume.d.ts +16 -0
- package/dist/commands/resume.js +41 -8
- package/dist/commands/routines.js +42 -21
- package/dist/commands/send.js +29 -2
- package/dist/commands/sessions-inject.d.ts +58 -0
- package/dist/commands/sessions-inject.js +143 -7
- package/dist/commands/sessions-optimize.js +1 -1
- package/dist/commands/sessions-picker.js +1 -0
- package/dist/commands/sessions.js +4 -11
- package/dist/commands/share.d.ts +5 -3
- package/dist/commands/share.js +73 -20
- package/dist/commands/ssh.js +205 -2
- package/dist/lib/accounting/account-pool-collect.d.ts +6 -4
- package/dist/lib/accounting/account-pool-collect.js +6 -4
- package/dist/lib/accounting/usage-ingest.js +4 -2
- package/dist/lib/accounting/usage-sync.d.ts +38 -93
- package/dist/lib/accounting/usage-sync.js +66 -210
- package/dist/lib/accounting/usage.d.ts +18 -5
- package/dist/lib/accounting/usage.js +165 -20
- package/dist/lib/auth-health.d.ts +8 -0
- package/dist/lib/auth-health.js +4 -4
- package/dist/lib/boot-profile.d.ts +14 -0
- package/dist/lib/boot-profile.js +66 -0
- package/dist/lib/browser/caller-identity.d.ts +12 -0
- package/dist/lib/browser/caller-identity.js +19 -0
- package/dist/lib/browser/ipc.d.ts +37 -32
- package/dist/lib/browser/ipc.js +146 -94
- package/dist/lib/browser/task-index.d.ts +10 -2
- package/dist/lib/browser/task-index.js +22 -3
- package/dist/lib/channels/providers/desktop.d.ts +5 -4
- package/dist/lib/channels/providers/desktop.js +5 -4
- package/dist/lib/claude-account-token.js +108 -4
- package/dist/lib/daemon/account-state-daemon-service.d.ts +49 -9
- package/dist/lib/daemon/account-state-daemon-service.js +81 -18
- package/dist/lib/daemon/auth-sync-service.d.ts +4 -4
- package/dist/lib/daemon/auth-sync-service.js +17 -6
- package/dist/lib/daemon/catchup-service.d.ts +51 -0
- package/dist/lib/daemon/catchup-service.js +51 -0
- package/dist/lib/daemon/daemon.d.ts +12 -22
- package/dist/lib/daemon/daemon.js +463 -176
- package/dist/lib/daemon/runner.js +2 -0
- package/dist/lib/daemon/service.d.ts +22 -4
- package/dist/lib/daemon/service.js +2 -2
- package/dist/lib/daemon/supervisor.d.ts +55 -15
- package/dist/lib/daemon/supervisor.js +119 -29
- package/dist/lib/daemon/usage-sync-service.d.ts +4 -6
- package/dist/lib/daemon/usage-sync-service.js +22 -18
- package/dist/lib/daemon-health.js +36 -31
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +12 -2
- package/dist/lib/daemon-ticks.d.ts +9 -6
- package/dist/lib/daemon-ticks.js +14 -8
- package/dist/lib/devices/health.d.ts +38 -2
- package/dist/lib/devices/health.js +43 -5
- package/dist/lib/devices/registry.js +2 -0
- package/dist/lib/devices/worker-pick.d.ts +1 -1
- package/dist/lib/devices/worker-pick.js +4 -1
- package/dist/lib/exec.js +15 -0
- package/dist/lib/feed/watch.d.ts +3 -0
- package/dist/lib/feed/watch.js +13 -3
- package/dist/lib/feed-broadcast.d.ts +64 -5
- package/dist/lib/feed-broadcast.js +124 -22
- package/dist/lib/fleet-shared-repo-sync.d.ts +36 -0
- package/dist/lib/fleet-shared-repo-sync.js +333 -0
- package/dist/lib/fleet-shared-state.d.ts +38 -0
- package/dist/lib/fleet-shared-state.js +105 -0
- package/dist/lib/hosts/remote-cmd.d.ts +2 -0
- package/dist/lib/hosts/remote-cmd.js +12 -3
- package/dist/lib/lock-compromise.d.ts +8 -0
- package/dist/lib/lock-compromise.js +12 -0
- package/dist/lib/monitors/engine.d.ts +2 -1
- package/dist/lib/monitors/engine.js +27 -2
- package/dist/lib/monitors/sources/command.js +13 -3
- package/dist/lib/monitors/sources/failure.d.ts +32 -0
- package/dist/lib/monitors/sources/failure.js +52 -0
- package/dist/lib/monitors/sources/types.d.ts +9 -0
- package/dist/lib/owner-message.d.ts +12 -0
- package/dist/lib/owner-message.js +44 -0
- package/dist/lib/refresh-coordinator.js +2 -0
- package/dist/lib/run-trace-sync.d.ts +28 -0
- package/dist/lib/run-trace-sync.js +99 -0
- package/dist/lib/secrets/filestore.d.ts +4 -0
- package/dist/lib/secrets/filestore.js +164 -3
- package/dist/lib/secrets/push.d.ts +10 -0
- package/dist/lib/secrets/push.js +86 -7
- package/dist/lib/secrets/remote.d.ts +18 -6
- package/dist/lib/secrets/remote.js +29 -4
- package/dist/lib/secrets/reserved-sync.d.ts +28 -27
- package/dist/lib/secrets/reserved-sync.js +119 -101
- package/dist/lib/session/active.d.ts +13 -1
- package/dist/lib/session/active.js +5 -0
- package/dist/lib/session/actor-sidecar.d.ts +12 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +16 -2
- package/dist/lib/session/db.js +73 -22
- package/dist/lib/session/discover.d.ts +12 -3
- package/dist/lib/session/discover.js +187 -26
- package/dist/lib/session/linear.d.ts +13 -0
- package/dist/lib/session/linear.js +44 -0
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/parse.js +2 -3
- package/dist/lib/session/prompt.d.ts +17 -0
- package/dist/lib/session/prompt.js +35 -0
- package/dist/lib/session/recovery.d.ts +43 -6
- package/dist/lib/session/recovery.js +80 -10
- package/dist/lib/session/remote/remote-list.d.ts +17 -1
- package/dist/lib/session/remote/remote-list.js +29 -4
- package/dist/lib/session/remote/watch.d.ts +25 -2
- package/dist/lib/session/remote/watch.js +188 -11
- package/dist/lib/session/session-cache.d.ts +2 -1
- package/dist/lib/session/session-cache.js +1 -0
- package/dist/lib/session/state.js +11 -13
- package/dist/lib/session/types.d.ts +2 -0
- package/dist/lib/share/backend.d.ts +2 -2
- package/dist/lib/share/backend.js +20 -9
- package/dist/lib/share/delete.d.ts +5 -1
- package/dist/lib/share/delete.js +7 -2
- package/dist/lib/share/http-error.d.ts +52 -0
- package/dist/lib/share/http-error.js +65 -0
- package/dist/lib/share/publish.d.ts +67 -11
- package/dist/lib/share/publish.js +98 -16
- package/dist/lib/share/worker-template.js +105 -9
- package/dist/lib/smart-launch.js +27 -4
- package/dist/lib/ssh-exec.d.ts +2 -0
- package/dist/lib/ssh-exec.js +20 -4
- package/dist/lib/storage/index.d.ts +14 -0
- package/dist/lib/storage/index.js +14 -0
- package/dist/lib/storage/selection.d.ts +48 -0
- package/dist/lib/storage/selection.js +39 -0
- package/dist/lib/storage/visibility.d.ts +82 -0
- package/dist/lib/storage/visibility.js +99 -0
- package/dist/lib/teams/agents.js +3 -1
- package/dist/lib/teams/placement-probe.js +1 -0
- package/dist/lib/teams/registry.js +2 -0
- package/dist/lib/teams/scheduler.d.ts +8 -1
- package/dist/lib/teams/scheduler.js +4 -1
- package/dist/lib/testdata/daemon-health-writer.d.ts +1 -0
- package/dist/lib/testdata/daemon-health-writer.js +8 -0
- package/dist/lib/traces/backend.js +13 -2
- package/dist/lib/traces/sync.d.ts +7 -0
- package/dist/lib/traces/sync.js +9 -0
- package/dist/lib/usage-refresh.d.ts +8 -2
- package/dist/lib/usage-refresh.js +3 -3
- package/dist/lib/worktree/held.d.ts +166 -0
- package/dist/lib/worktree/held.js +368 -0
- package/package.json +2 -2
- package/dist/lib/account-state-service.d.ts +0 -21
- package/dist/lib/account-state-service.js +0 -60
package/dist/lib/ssh-exec.js
CHANGED
|
@@ -173,6 +173,8 @@ export function controlOpts() {
|
|
|
173
173
|
export function sshConnectOpts(mux, hostKeyOpts) {
|
|
174
174
|
return [...(hostKeyOpts ?? []), ...SSH_OPTS, ...mux];
|
|
175
175
|
}
|
|
176
|
+
/** Grace after a timed-out child receives SIGTERM before SIGKILL enforces the bound. */
|
|
177
|
+
export const SSH_TIMEOUT_KILL_GRACE_MS = 250;
|
|
176
178
|
/**
|
|
177
179
|
* Run `remoteCmd` on `target` over ssh and capture stdout/stderr/exit.
|
|
178
180
|
*
|
|
@@ -228,12 +230,28 @@ export function sshExecAsync(target, remoteCmd, opts = {}) {
|
|
|
228
230
|
let stderr = '';
|
|
229
231
|
let settled = false;
|
|
230
232
|
let timedOut = false;
|
|
233
|
+
let killTimer = null;
|
|
231
234
|
const timer = opts.timeoutMs
|
|
232
235
|
? setTimeout(() => {
|
|
233
236
|
timedOut = true;
|
|
234
237
|
child.kill('SIGTERM');
|
|
238
|
+
// SIGTERM is advisory. A wedged ssh process can ignore it and keep the
|
|
239
|
+
// Promise (and daemon tick) open forever, so enforce a short hard-kill
|
|
240
|
+
// bound. Timeout calls always disable ControlMaster above; killing this
|
|
241
|
+
// direct client therefore tears down the remote connection too.
|
|
242
|
+
killTimer = setTimeout(() => {
|
|
243
|
+
if (!settled)
|
|
244
|
+
child.kill('SIGKILL');
|
|
245
|
+
}, SSH_TIMEOUT_KILL_GRACE_MS);
|
|
246
|
+
killTimer.unref?.();
|
|
235
247
|
}, opts.timeoutMs)
|
|
236
248
|
: null;
|
|
249
|
+
const clearTimers = () => {
|
|
250
|
+
if (timer)
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
if (killTimer)
|
|
253
|
+
clearTimeout(killTimer);
|
|
254
|
+
};
|
|
237
255
|
child.stdout.setEncoding('utf-8');
|
|
238
256
|
child.stderr.setEncoding('utf-8');
|
|
239
257
|
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
@@ -251,16 +269,14 @@ export function sshExecAsync(target, remoteCmd, opts = {}) {
|
|
|
251
269
|
if (settled)
|
|
252
270
|
return;
|
|
253
271
|
settled = true;
|
|
254
|
-
|
|
255
|
-
clearTimeout(timer);
|
|
272
|
+
clearTimers();
|
|
256
273
|
resolve({ code: null, stdout, stderr: stderr + err.message, timedOut });
|
|
257
274
|
});
|
|
258
275
|
child.on('close', (code) => {
|
|
259
276
|
if (settled)
|
|
260
277
|
return;
|
|
261
278
|
settled = true;
|
|
262
|
-
|
|
263
|
-
clearTimeout(timer);
|
|
279
|
+
clearTimers();
|
|
264
280
|
resolve({ code, stdout, stderr, timedOut });
|
|
265
281
|
});
|
|
266
282
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lib/storage` — the surface-agnostic core the managed-storage surfaces share:
|
|
3
|
+
*
|
|
4
|
+
* - {@link selection} the ONE managed-vs-BYO selection policy.
|
|
5
|
+
* - {@link visibility} the ONE visibility model + product default (`me`).
|
|
6
|
+
*
|
|
7
|
+
* Consume the SELECTION + VISIBILITY POLICY from here; keep each surface's typed,
|
|
8
|
+
* `kind`-tagged backend adapter (endpoint, namespace, covers) in that surface's
|
|
9
|
+
* own module. `agents artifacts share` (`lib/share/`) and `agents traces`
|
|
10
|
+
* (`lib/traces/`) are the two current adapters; the `sessions` sync adapter is
|
|
11
|
+
* the next consumer.
|
|
12
|
+
*/
|
|
13
|
+
export { type StorageBackendKind, type StorageSelectionOpts, selectStorageBackendKind, isManagedSelection, } from './selection.js';
|
|
14
|
+
export { type ShareVisibility, type VisibilityFlags, PUBLISH_VISIBILITY_LEVELS, EDITABLE_VISIBILITY_LEVELS, MANAGED_DEFAULT_VISIBILITY, BYO_DEFAULT_VISIBILITY, defaultVisibilityForBackend, explicitVisibility, resolveVisibility, publishVisibility, } from './visibility.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lib/storage` — the surface-agnostic core the managed-storage surfaces share:
|
|
3
|
+
*
|
|
4
|
+
* - {@link selection} the ONE managed-vs-BYO selection policy.
|
|
5
|
+
* - {@link visibility} the ONE visibility model + product default (`me`).
|
|
6
|
+
*
|
|
7
|
+
* Consume the SELECTION + VISIBILITY POLICY from here; keep each surface's typed,
|
|
8
|
+
* `kind`-tagged backend adapter (endpoint, namespace, covers) in that surface's
|
|
9
|
+
* own module. `agents artifacts share` (`lib/share/`) and `agents traces`
|
|
10
|
+
* (`lib/traces/`) are the two current adapters; the `sessions` sync adapter is
|
|
11
|
+
* the next consumer.
|
|
12
|
+
*/
|
|
13
|
+
export { selectStorageBackendKind, isManagedSelection, } from './selection.js';
|
|
14
|
+
export { PUBLISH_VISIBILITY_LEVELS, EDITABLE_VISIBILITY_LEVELS, MANAGED_DEFAULT_VISIBILITY, BYO_DEFAULT_VISIBILITY, defaultVisibilityForBackend, explicitVisibility, resolveVisibility, publishVisibility, } from './visibility.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE managed-vs-BYO storage-backend selection policy.
|
|
3
|
+
*
|
|
4
|
+
* Every surface that persists to Phoenix-managed storage (`agents artifacts
|
|
5
|
+
* share` today, `agents sessions` sync next) makes the SAME choice: run on OUR
|
|
6
|
+
* managed infrastructure when the caller is signed in to Phoenix, and only fall
|
|
7
|
+
* back to a bring-your-own bucket when the caller explicitly asked for it. This
|
|
8
|
+
* module owns that decision so it is not re-derived — and re-drifted — per
|
|
9
|
+
* surface.
|
|
10
|
+
*
|
|
11
|
+
* What lives here is ONLY the identity + selection policy. It deliberately does
|
|
12
|
+
* NOT know a surface's endpoint, namespace shape, public-read semantics, or OG
|
|
13
|
+
* covers — those differ (share uses an email-handle namespace with public reads
|
|
14
|
+
* and covers; a traces/sessions adapter uses the userId and differs again). Each
|
|
15
|
+
* surface keeps its own DISCRIMINATED, typed adapter that reads this decision and
|
|
16
|
+
* returns its own `kind`-tagged backend. See `lib/share/backend.ts` and
|
|
17
|
+
* `lib/traces/backend.ts` for the two adapters.
|
|
18
|
+
*/
|
|
19
|
+
import { type PhoenixSession } from '../identity/client.js';
|
|
20
|
+
/** The two legitimate principals any managed-capable surface can resolve to. */
|
|
21
|
+
export type StorageBackendKind = 'managed' | 'byo';
|
|
22
|
+
export interface StorageSelectionOpts {
|
|
23
|
+
/**
|
|
24
|
+
* True when the SURFACE detected an explicit bring-your-own override — a
|
|
25
|
+
* `--byo` flag, a caller-supplied static write token, a `…_BACKEND=byo` env,
|
|
26
|
+
* or a full BYO endpoint config. Detecting WHICH signals count is the
|
|
27
|
+
* surface's job (they differ per surface); this policy only honors the boolean.
|
|
28
|
+
*/
|
|
29
|
+
byoOverride?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* DI seam for the Phoenix session. `undefined` reads the real persisted
|
|
32
|
+
* session (`readSession()`); `null` means "explicitly signed out".
|
|
33
|
+
*/
|
|
34
|
+
session?: PhoenixSession | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pick the storage principal. Managed when signed in (`readSession() != null`)
|
|
38
|
+
* AND the surface reported no explicit BYO override; otherwise BYO.
|
|
39
|
+
*
|
|
40
|
+
* This is not a fallback chain — it is a single decision. A surface that cannot
|
|
41
|
+
* authenticate EITHER principal (signed out AND no BYO config) still reads `byo`
|
|
42
|
+
* here and fails loud in its own adapter, where the actionable "run auth login
|
|
43
|
+
* or set up your bucket" message belongs.
|
|
44
|
+
*/
|
|
45
|
+
export declare function selectStorageBackendKind(opts?: StorageSelectionOpts): StorageBackendKind;
|
|
46
|
+
/** True when the shared policy resolves to the managed principal. Thin sugar
|
|
47
|
+
* over {@link selectStorageBackendKind} for the common boolean check. */
|
|
48
|
+
export declare function isManagedSelection(opts?: StorageSelectionOpts): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE managed-vs-BYO storage-backend selection policy.
|
|
3
|
+
*
|
|
4
|
+
* Every surface that persists to Phoenix-managed storage (`agents artifacts
|
|
5
|
+
* share` today, `agents sessions` sync next) makes the SAME choice: run on OUR
|
|
6
|
+
* managed infrastructure when the caller is signed in to Phoenix, and only fall
|
|
7
|
+
* back to a bring-your-own bucket when the caller explicitly asked for it. This
|
|
8
|
+
* module owns that decision so it is not re-derived — and re-drifted — per
|
|
9
|
+
* surface.
|
|
10
|
+
*
|
|
11
|
+
* What lives here is ONLY the identity + selection policy. It deliberately does
|
|
12
|
+
* NOT know a surface's endpoint, namespace shape, public-read semantics, or OG
|
|
13
|
+
* covers — those differ (share uses an email-handle namespace with public reads
|
|
14
|
+
* and covers; a traces/sessions adapter uses the userId and differs again). Each
|
|
15
|
+
* surface keeps its own DISCRIMINATED, typed adapter that reads this decision and
|
|
16
|
+
* returns its own `kind`-tagged backend. See `lib/share/backend.ts` and
|
|
17
|
+
* `lib/traces/backend.ts` for the two adapters.
|
|
18
|
+
*/
|
|
19
|
+
import { readSession } from '../identity/client.js';
|
|
20
|
+
/**
|
|
21
|
+
* Pick the storage principal. Managed when signed in (`readSession() != null`)
|
|
22
|
+
* AND the surface reported no explicit BYO override; otherwise BYO.
|
|
23
|
+
*
|
|
24
|
+
* This is not a fallback chain — it is a single decision. A surface that cannot
|
|
25
|
+
* authenticate EITHER principal (signed out AND no BYO config) still reads `byo`
|
|
26
|
+
* here and fails loud in its own adapter, where the actionable "run auth login
|
|
27
|
+
* or set up your bucket" message belongs.
|
|
28
|
+
*/
|
|
29
|
+
export function selectStorageBackendKind(opts = {}) {
|
|
30
|
+
if (opts.byoOverride === true)
|
|
31
|
+
return 'byo';
|
|
32
|
+
const session = opts.session === undefined ? readSession() : opts.session;
|
|
33
|
+
return session != null ? 'managed' : 'byo';
|
|
34
|
+
}
|
|
35
|
+
/** True when the shared policy resolves to the managed principal. Thin sugar
|
|
36
|
+
* over {@link selectStorageBackendKind} for the common boolean check. */
|
|
37
|
+
export function isManagedSelection(opts = {}) {
|
|
38
|
+
return selectStorageBackendKind(opts) === 'managed';
|
|
39
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE visibility model, identical on every surface that publishes to managed
|
|
3
|
+
* storage. Three levels the operator ever chooses between:
|
|
4
|
+
*
|
|
5
|
+
* - PRIVATE = `me` — owner-only, Phoenix-gated. THE DEFAULT for a new
|
|
6
|
+
* managed share (see {@link MANAGED_DEFAULT_VISIBILITY}).
|
|
7
|
+
* - TEAM = `org` — everyone at the sharer's signed-in email DOMAIN,
|
|
8
|
+
* Phoenix-gated. Automatic from the domain — there is
|
|
9
|
+
* no organization to create and no member to add.
|
|
10
|
+
* - PUBLIC = `public` — explicit opt-in; listed in the gallery, gets an OG
|
|
11
|
+
* card.
|
|
12
|
+
*
|
|
13
|
+
* Two capability-URL levels remain for the power path (unguessable link, not a
|
|
14
|
+
* login gate): `unlisted` (obscurity — noindex, gallery-hidden, still
|
|
15
|
+
* world-readable) and `private`-token-gated (`--protected`: a `?k=` key the
|
|
16
|
+
* Worker checks, 404 without it). `me`/`org` require a Phoenix session; the
|
|
17
|
+
* Worker refuses them for a bare WRITE_TOKEN (BYO) publish, which is why the BYO
|
|
18
|
+
* default stays `public`.
|
|
19
|
+
*
|
|
20
|
+
* The names match the Worker's own metadata vocabulary (`lib/share/worker-template.ts`),
|
|
21
|
+
* so this module is the single client-side source of truth for the level set and
|
|
22
|
+
* the DEFAULT, reusable by any surface.
|
|
23
|
+
*/
|
|
24
|
+
/** Every visibility level the Worker understands. `me`/`org` are the
|
|
25
|
+
* Phoenix-gated (login-required) levels; `public`/`unlisted`/`private` are the
|
|
26
|
+
* link-reachable levels. */
|
|
27
|
+
export type ShareVisibility = 'public' | 'unlisted' | 'private' | 'me' | 'org';
|
|
28
|
+
/** The levels a publish (`share <file> --visibility`) may select. */
|
|
29
|
+
export declare const PUBLISH_VISIBILITY_LEVELS: readonly ShareVisibility[];
|
|
30
|
+
/** The levels an ALREADY-published page may be re-scoped to in place
|
|
31
|
+
* (`share visibility <target> <level>`). Excludes `private`: re-scoping to
|
|
32
|
+
* token-gated needs a fresh viewer token, which only the publish path mints. */
|
|
33
|
+
export declare const EDITABLE_VISIBILITY_LEVELS: readonly ShareVisibility[];
|
|
34
|
+
/**
|
|
35
|
+
* The product default for a MANAGED (signed-in) share: PRIVATE = owner-only,
|
|
36
|
+
* Phoenix-gated. A publish with no visibility flag lands here.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MANAGED_DEFAULT_VISIBILITY: ShareVisibility;
|
|
39
|
+
/**
|
|
40
|
+
* The default for a BYO (bring-your-own bucket) publish. `me`/`org` are refused
|
|
41
|
+
* server-side for a WRITE_TOKEN publish — there is no Phoenix owner to gate on —
|
|
42
|
+
* so a BYO share with no flag stays `public`, matching the pre-managed behavior.
|
|
43
|
+
*/
|
|
44
|
+
export declare const BYO_DEFAULT_VISIBILITY: ShareVisibility;
|
|
45
|
+
import type { StorageBackendKind } from './selection.js';
|
|
46
|
+
/** The no-flags default for a given storage backend: `me` on managed,
|
|
47
|
+
* `public` on BYO. */
|
|
48
|
+
export declare function defaultVisibilityForBackend(kind: StorageBackendKind): ShareVisibility;
|
|
49
|
+
export interface VisibilityFlags {
|
|
50
|
+
/** An explicit `--visibility <level>`. */
|
|
51
|
+
visibility?: ShareVisibility;
|
|
52
|
+
/** `--unlisted` / `--private` (obscurity, not read-auth). */
|
|
53
|
+
unlisted?: boolean;
|
|
54
|
+
/** `--protected` — token-gated read auth (maps to `private`). */
|
|
55
|
+
protected?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The visibility the caller EXPLICITLY asked for, or `undefined` when they
|
|
59
|
+
* passed no visibility signal at all. `--protected` wins over `--unlisted`
|
|
60
|
+
* (stronger control), which wins over an explicit `--visibility`.
|
|
61
|
+
*
|
|
62
|
+
* The distinction between "explicit" and "no preference" is what lets a caller
|
|
63
|
+
* apply the product default only when nothing was asked — a caller that passes
|
|
64
|
+
* `--visibility public` gets `public`, never the `me` default.
|
|
65
|
+
*/
|
|
66
|
+
export declare function explicitVisibility(opts?: VisibilityFlags): ShareVisibility | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Resolve visibility from the caller's flags, falling back to `fallback` when no
|
|
69
|
+
* explicit signal was given. The library fallback stays `public` so a caller
|
|
70
|
+
* that hasn't opted into the managed private default (e.g. an existing lib
|
|
71
|
+
* consumer) is never silently flipped; the surface applies the product default
|
|
72
|
+
* itself via {@link defaultVisibilityForBackend}.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveVisibility(opts?: VisibilityFlags, fallback?: ShareVisibility): ShareVisibility;
|
|
75
|
+
/**
|
|
76
|
+
* The visibility a PUBLISH should stamp: the caller's explicit flag if any, else
|
|
77
|
+
* the product default for the resolved backend (`me` on managed, `public` on
|
|
78
|
+
* BYO). This is the one call a publishing surface makes — `agents artifacts
|
|
79
|
+
* share` today, `agents sessions` sync next — so "private by default when signed
|
|
80
|
+
* in" is decided in exactly one place.
|
|
81
|
+
*/
|
|
82
|
+
export declare function publishVisibility(opts: VisibilityFlags, kind: StorageBackendKind): ShareVisibility;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE visibility model, identical on every surface that publishes to managed
|
|
3
|
+
* storage. Three levels the operator ever chooses between:
|
|
4
|
+
*
|
|
5
|
+
* - PRIVATE = `me` — owner-only, Phoenix-gated. THE DEFAULT for a new
|
|
6
|
+
* managed share (see {@link MANAGED_DEFAULT_VISIBILITY}).
|
|
7
|
+
* - TEAM = `org` — everyone at the sharer's signed-in email DOMAIN,
|
|
8
|
+
* Phoenix-gated. Automatic from the domain — there is
|
|
9
|
+
* no organization to create and no member to add.
|
|
10
|
+
* - PUBLIC = `public` — explicit opt-in; listed in the gallery, gets an OG
|
|
11
|
+
* card.
|
|
12
|
+
*
|
|
13
|
+
* Two capability-URL levels remain for the power path (unguessable link, not a
|
|
14
|
+
* login gate): `unlisted` (obscurity — noindex, gallery-hidden, still
|
|
15
|
+
* world-readable) and `private`-token-gated (`--protected`: a `?k=` key the
|
|
16
|
+
* Worker checks, 404 without it). `me`/`org` require a Phoenix session; the
|
|
17
|
+
* Worker refuses them for a bare WRITE_TOKEN (BYO) publish, which is why the BYO
|
|
18
|
+
* default stays `public`.
|
|
19
|
+
*
|
|
20
|
+
* The names match the Worker's own metadata vocabulary (`lib/share/worker-template.ts`),
|
|
21
|
+
* so this module is the single client-side source of truth for the level set and
|
|
22
|
+
* the DEFAULT, reusable by any surface.
|
|
23
|
+
*/
|
|
24
|
+
/** The levels a publish (`share <file> --visibility`) may select. */
|
|
25
|
+
export const PUBLISH_VISIBILITY_LEVELS = [
|
|
26
|
+
'public',
|
|
27
|
+
'unlisted',
|
|
28
|
+
'private',
|
|
29
|
+
'me',
|
|
30
|
+
'org',
|
|
31
|
+
];
|
|
32
|
+
/** The levels an ALREADY-published page may be re-scoped to in place
|
|
33
|
+
* (`share visibility <target> <level>`). Excludes `private`: re-scoping to
|
|
34
|
+
* token-gated needs a fresh viewer token, which only the publish path mints. */
|
|
35
|
+
export const EDITABLE_VISIBILITY_LEVELS = [
|
|
36
|
+
'public',
|
|
37
|
+
'unlisted',
|
|
38
|
+
'me',
|
|
39
|
+
'org',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* The product default for a MANAGED (signed-in) share: PRIVATE = owner-only,
|
|
43
|
+
* Phoenix-gated. A publish with no visibility flag lands here.
|
|
44
|
+
*/
|
|
45
|
+
export const MANAGED_DEFAULT_VISIBILITY = 'me';
|
|
46
|
+
/**
|
|
47
|
+
* The default for a BYO (bring-your-own bucket) publish. `me`/`org` are refused
|
|
48
|
+
* server-side for a WRITE_TOKEN publish — there is no Phoenix owner to gate on —
|
|
49
|
+
* so a BYO share with no flag stays `public`, matching the pre-managed behavior.
|
|
50
|
+
*/
|
|
51
|
+
export const BYO_DEFAULT_VISIBILITY = 'public';
|
|
52
|
+
/** The no-flags default for a given storage backend: `me` on managed,
|
|
53
|
+
* `public` on BYO. */
|
|
54
|
+
export function defaultVisibilityForBackend(kind) {
|
|
55
|
+
return kind === 'managed' ? MANAGED_DEFAULT_VISIBILITY : BYO_DEFAULT_VISIBILITY;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The visibility the caller EXPLICITLY asked for, or `undefined` when they
|
|
59
|
+
* passed no visibility signal at all. `--protected` wins over `--unlisted`
|
|
60
|
+
* (stronger control), which wins over an explicit `--visibility`.
|
|
61
|
+
*
|
|
62
|
+
* The distinction between "explicit" and "no preference" is what lets a caller
|
|
63
|
+
* apply the product default only when nothing was asked — a caller that passes
|
|
64
|
+
* `--visibility public` gets `public`, never the `me` default.
|
|
65
|
+
*/
|
|
66
|
+
export function explicitVisibility(opts = {}) {
|
|
67
|
+
if (opts.protected === true)
|
|
68
|
+
return 'private';
|
|
69
|
+
if (opts.unlisted === true)
|
|
70
|
+
return 'unlisted';
|
|
71
|
+
if (opts.visibility === 'public' ||
|
|
72
|
+
opts.visibility === 'unlisted' ||
|
|
73
|
+
opts.visibility === 'private' ||
|
|
74
|
+
opts.visibility === 'me' ||
|
|
75
|
+
opts.visibility === 'org') {
|
|
76
|
+
return opts.visibility;
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolve visibility from the caller's flags, falling back to `fallback` when no
|
|
82
|
+
* explicit signal was given. The library fallback stays `public` so a caller
|
|
83
|
+
* that hasn't opted into the managed private default (e.g. an existing lib
|
|
84
|
+
* consumer) is never silently flipped; the surface applies the product default
|
|
85
|
+
* itself via {@link defaultVisibilityForBackend}.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveVisibility(opts = {}, fallback = 'public') {
|
|
88
|
+
return explicitVisibility(opts) ?? fallback;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The visibility a PUBLISH should stamp: the caller's explicit flag if any, else
|
|
92
|
+
* the product default for the resolved backend (`me` on managed, `public` on
|
|
93
|
+
* BYO). This is the one call a publishing surface makes — `agents artifacts
|
|
94
|
+
* share` today, `agents sessions` sync next — so "private by default when signed
|
|
95
|
+
* in" is decided in exactly one place.
|
|
96
|
+
*/
|
|
97
|
+
export function publishVisibility(opts, kind) {
|
|
98
|
+
return explicitVisibility(opts) ?? defaultVisibilityForBackend(kind);
|
|
99
|
+
}
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -2361,7 +2361,9 @@ export class AgentManager {
|
|
|
2361
2361
|
? `at its agents.max-concurrent cap (${e.detail} running)`
|
|
2362
2362
|
: e.reason === 'not-installed'
|
|
2363
2363
|
? `does not have ${this.placementAgentLabel(agent)} installed`
|
|
2364
|
-
: e.reason
|
|
2364
|
+
: e.reason === 'probe-timed-out'
|
|
2365
|
+
? 'did not answer the probe in time (likely up but on a slow/relayed link)'
|
|
2366
|
+
: e.reason;
|
|
2365
2367
|
console.error(chalk.dim(`[placement] '${e.device}' excluded from auto-pick — ${why}`));
|
|
2366
2368
|
}
|
|
2367
2369
|
}
|
|
@@ -137,6 +137,7 @@ export async function probePoolSignals(pool, agent, opts = {}) {
|
|
|
137
137
|
continue; // fully unknown device — leave it out of the map
|
|
138
138
|
signals.set(name, {
|
|
139
139
|
reachable: s?.reachable,
|
|
140
|
+
timedOut: s?.timedOut,
|
|
140
141
|
headroom: s ? headroom(s) : undefined,
|
|
141
142
|
loadPercent: s?.loadPercent,
|
|
142
143
|
memPercent: s?.memPercent,
|
|
@@ -14,6 +14,7 @@ import lockfile from 'proper-lockfile';
|
|
|
14
14
|
import { getTeamsRegistryPath } from '../state.js';
|
|
15
15
|
import { emit } from '../feed/events.js';
|
|
16
16
|
import { atomicWriteJsonSync } from '../fs-atomic.js';
|
|
17
|
+
import { logAndContinueOnLockCompromised } from '../lock-compromise.js';
|
|
17
18
|
async function registryPath() {
|
|
18
19
|
return getTeamsRegistryPath();
|
|
19
20
|
}
|
|
@@ -38,6 +39,7 @@ async function withRegistryLock(p, fn) {
|
|
|
38
39
|
const release = await lockfile.lock(p, {
|
|
39
40
|
retries: { retries: 60, minTimeout: 25, maxTimeout: 250, factor: 1.5 },
|
|
40
41
|
stale: 10_000,
|
|
42
|
+
onCompromised: logAndContinueOnLockCompromised('teams registry'),
|
|
41
43
|
});
|
|
42
44
|
try {
|
|
43
45
|
return await fn();
|
|
@@ -10,6 +10,13 @@ import type { Headroom } from '../devices/health.js';
|
|
|
10
10
|
export interface DevicePlacementSignal {
|
|
11
11
|
/** SSH probe answered. `false` → excluded (unreachable). undefined → unknown. */
|
|
12
12
|
reachable?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The probe was killed for exceeding its budget rather than failing to
|
|
15
|
+
* connect. Ranking treats it exactly like `reachable:false` — an
|
|
16
|
+
* unresponsive box is still not placeable — but the operator-facing
|
|
17
|
+
* exclusion reason says "probe timed out", not "unreachable" (PHNX-3682).
|
|
18
|
+
*/
|
|
19
|
+
timedOut?: boolean;
|
|
13
20
|
/** Headroom bucket from load+memory. `'loaded'` → excluded (overloaded). */
|
|
14
21
|
headroom?: Headroom;
|
|
15
22
|
/** Normalized CPU load percent (finer rank tiebreak within a headroom tier). */
|
|
@@ -60,7 +67,7 @@ export interface PlacementOptions {
|
|
|
60
67
|
preferred?: ReadonlySet<string>;
|
|
61
68
|
}
|
|
62
69
|
/** Why a device was excluded from the viable set, for the fail-loud message. */
|
|
63
|
-
export type ExclusionReason = 'unreachable' | 'overloaded' | 'capped' | 'not-installed';
|
|
70
|
+
export type ExclusionReason = 'unreachable' | 'probe-timed-out' | 'overloaded' | 'capped' | 'not-installed';
|
|
64
71
|
/** A pool device dropped from the auto-pick, with the reason + live detail. */
|
|
65
72
|
export interface ExcludedDevice {
|
|
66
73
|
device: string;
|
|
@@ -199,7 +199,10 @@ export function classifyExclusions(devices, roster, opts) {
|
|
|
199
199
|
for (const d of devices) {
|
|
200
200
|
const s = signals?.get(d);
|
|
201
201
|
if (s?.reachable === false) {
|
|
202
|
-
|
|
202
|
+
// A probe killed for exceeding its budget is a slow link, not a box that
|
|
203
|
+
// is down. Same exclusion either way — an unresponsive device is still not
|
|
204
|
+
// placeable — but the operator sees which one it was (PHNX-3682).
|
|
205
|
+
excluded.push({ device: d, reason: s.timedOut ? 'probe-timed-out' : 'unreachable' });
|
|
203
206
|
continue;
|
|
204
207
|
}
|
|
205
208
|
if (s?.installed === false) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { recordSubsystemError } from '../daemon-health.js';
|
|
2
|
+
const [subsystem, countRaw] = process.argv.slice(2);
|
|
3
|
+
const count = Number(countRaw);
|
|
4
|
+
if (!subsystem || !Number.isInteger(count) || count < 1)
|
|
5
|
+
process.exit(2);
|
|
6
|
+
for (let i = 0; i < count; i++) {
|
|
7
|
+
recordSubsystemError(subsystem, `${process.pid}:${i}`);
|
|
8
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `agents artifacts setup`.
|
|
11
11
|
*/
|
|
12
12
|
import { readSession } from '../identity/client.js';
|
|
13
|
+
import { selectStorageBackendKind } from '../storage/selection.js';
|
|
13
14
|
export const DEFAULT_TRACES_DOMAIN = 'traces.agents-cli.sh';
|
|
14
15
|
/**
|
|
15
16
|
* Resolve the backend for the current machine. Throws when not signed in.
|
|
@@ -21,11 +22,21 @@ export const DEFAULT_TRACES_DOMAIN = 'traces.agents-cli.sh';
|
|
|
21
22
|
export function resolveTracesBackend() {
|
|
22
23
|
const envBase = (process.env['AGENTS_TRACES_BASE_URL'] ?? '').replace(/\/+$/, '').trim();
|
|
23
24
|
const envToken = (process.env['AGENTS_TRACES_WRITE_TOKEN'] ?? '').trim();
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// The traces surface's only BYO signal is the full BASE_URL + WRITE_TOKEN env
|
|
26
|
+
// pair; the managed-vs-BYO decision itself is the shared selection policy.
|
|
27
|
+
const byoOverride = Boolean(envBase && envToken);
|
|
28
|
+
if (selectStorageBackendKind({ byoOverride }) === 'byo') {
|
|
29
|
+
if (byoOverride) {
|
|
30
|
+
return { baseUrl: envBase, token: envToken, userId: 'byo' };
|
|
31
|
+
}
|
|
32
|
+
// Not signed in and no BYO env pair — the managed principal is the only one
|
|
33
|
+
// this surface exposes, so fail loud with the login hint.
|
|
34
|
+
throw new Error("Not signed in. Run 'agents auth login' to sync traces to your Phoenix account.");
|
|
26
35
|
}
|
|
27
36
|
const session = readSession();
|
|
28
37
|
if (!session) {
|
|
38
|
+
// selectStorageBackendKind read the same session and returned 'managed', so a
|
|
39
|
+
// null here means it was cleared between the two reads — treat as signed out.
|
|
29
40
|
throw new Error("Not signed in. Run 'agents auth login' to sync traces to your Phoenix account.");
|
|
30
41
|
}
|
|
31
42
|
if (!session.access_token) {
|
|
@@ -390,6 +390,13 @@ interface SyncLedger {
|
|
|
390
390
|
}
|
|
391
391
|
export declare function readSyncLedger(): SyncLedger;
|
|
392
392
|
export declare function writeSyncLedger(ledger: SyncLedger): void;
|
|
393
|
+
/**
|
|
394
|
+
* True once the user has run `agents traces sync` at least once (the ledger
|
|
395
|
+
* file exists). This is the opt-in signal the run-exit auto-sync gates on
|
|
396
|
+
* (PHNX-3628): a user who has never synced has not opted into the traces store,
|
|
397
|
+
* so an `agents run` never uploads on their behalf.
|
|
398
|
+
*/
|
|
399
|
+
export declare function hasSyncedBefore(): boolean;
|
|
393
400
|
export interface SyncRow {
|
|
394
401
|
id: string;
|
|
395
402
|
short_id: string;
|
package/dist/lib/traces/sync.js
CHANGED
|
@@ -816,6 +816,15 @@ export function writeSyncLedger(ledger) {
|
|
|
816
816
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
817
817
|
fs.writeFileSync(p, JSON.stringify(ledger, null, 2) + '\n', 'utf8');
|
|
818
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* True once the user has run `agents traces sync` at least once (the ledger
|
|
821
|
+
* file exists). This is the opt-in signal the run-exit auto-sync gates on
|
|
822
|
+
* (PHNX-3628): a user who has never synced has not opted into the traces store,
|
|
823
|
+
* so an `agents run` never uploads on their behalf.
|
|
824
|
+
*/
|
|
825
|
+
export function hasSyncedBefore() {
|
|
826
|
+
return fs.existsSync(ledgerPath());
|
|
827
|
+
}
|
|
819
828
|
// ---------------------------------------------------------------------------
|
|
820
829
|
// Local device name
|
|
821
830
|
// ---------------------------------------------------------------------------
|
|
@@ -150,8 +150,12 @@ export declare function providerSpacingTokens(lastCallMs: number, now: number):
|
|
|
150
150
|
export interface LocalUsageAccount {
|
|
151
151
|
usageKey: string;
|
|
152
152
|
agentId: AgentId;
|
|
153
|
-
/**
|
|
154
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Live-fetch this account's usage; the daemon passes the real network fetch.
|
|
155
|
+
* `signal` (the daemon tick's deadline AbortSignal) bounds the provider fetch
|
|
156
|
+
* so a hung refresh is aborted at deadlineMs, not just its own 5s timeout.
|
|
157
|
+
*/
|
|
158
|
+
fetch: (signal?: AbortSignal) => Promise<UsageInfo>;
|
|
155
159
|
}
|
|
156
160
|
/**
|
|
157
161
|
* Order a pass STALEST-FIRST so a scarce per-provider budget
|
|
@@ -206,6 +210,8 @@ export interface UsageRefreshDeps {
|
|
|
206
210
|
* spending scarce provider budget re-fetching an already-current account.
|
|
207
211
|
*/
|
|
208
212
|
readCachedSnapshot?: (usageKey: string) => UsageSnapshot | null;
|
|
213
|
+
/** Daemon tick deadline signal, forwarded to each account's provider fetch (PHNX-3608). */
|
|
214
|
+
signal?: AbortSignal;
|
|
209
215
|
}
|
|
210
216
|
export interface UsageRefreshResult {
|
|
211
217
|
refreshed: number;
|
|
@@ -392,14 +392,14 @@ export async function buildLocalUsageAccounts() {
|
|
|
392
392
|
// only, never the interactive login (see loadClaudeOauth); no setup-token
|
|
393
393
|
// reads as "usage unavailable (no usage credential)" — "usage pending"
|
|
394
394
|
// is now the cold-cache state only (#2987).
|
|
395
|
-
fetch: async () => {
|
|
395
|
+
fetch: async (signal) => {
|
|
396
396
|
const { getUsageInfoForIdentity } = await import('./accounting/usage.js');
|
|
397
397
|
return getUsageInfoForIdentity({
|
|
398
398
|
agentId,
|
|
399
399
|
home: fetchInput.home,
|
|
400
400
|
cliVersion: fetchInput.cliVersion,
|
|
401
401
|
info: canonical,
|
|
402
|
-
}, { forceRefresh: true, fileOnly: true });
|
|
402
|
+
}, { forceRefresh: true, fileOnly: true, signal });
|
|
403
403
|
},
|
|
404
404
|
});
|
|
405
405
|
}
|
|
@@ -487,7 +487,7 @@ export async function runUsageRefresh(deps) {
|
|
|
487
487
|
spacingUsed.set(account.agentId, (spacingUsed.get(account.agentId) ?? 0) + 1);
|
|
488
488
|
}
|
|
489
489
|
try {
|
|
490
|
-
const usage = await account.fetch();
|
|
490
|
+
const usage = await account.fetch(deps.signal);
|
|
491
491
|
if (usage.snapshot) {
|
|
492
492
|
// `source` is provenance, not freshness. A forced collection that just
|
|
493
493
|
// reread a local harness event returns `last_seen`; that is still a
|