@phnx-labs/agents-cli 1.22.37 → 1.22.38

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +8 -8
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +1 -0
  5. package/dist/commands/artifacts-setup.d.ts +53 -0
  6. package/dist/commands/{setup-share.js → artifacts-setup.js} +59 -13
  7. package/dist/commands/artifacts.d.ts +18 -0
  8. package/dist/commands/artifacts.js +58 -0
  9. package/dist/commands/browser.js +2 -0
  10. package/dist/commands/exec.js +1 -1
  11. package/dist/commands/models.js +67 -0
  12. package/dist/commands/setup.js +5 -5
  13. package/dist/commands/share.d.ts +20 -7
  14. package/dist/commands/share.js +74 -75
  15. package/dist/lib/browser/hygiene.d.ts +90 -0
  16. package/dist/lib/browser/hygiene.js +146 -0
  17. package/dist/lib/browser/ipc.js +12 -0
  18. package/dist/lib/browser/service.d.ts +75 -1
  19. package/dist/lib/browser/service.js +201 -11
  20. package/dist/lib/browser/types.d.ts +44 -1
  21. package/dist/lib/git.d.ts +1 -1
  22. package/dist/lib/git.js +1 -1
  23. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  24. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  25. package/dist/lib/routines.d.ts +3 -1
  26. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  27. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  28. package/dist/lib/share/analytics.js +1 -1
  29. package/dist/lib/share/capture.d.ts +1 -1
  30. package/dist/lib/share/capture.js +3 -3
  31. package/dist/lib/share/config.d.ts +2 -2
  32. package/dist/lib/share/config.js +5 -5
  33. package/dist/lib/share/delete.js +3 -3
  34. package/dist/lib/share/provision.js +3 -3
  35. package/dist/lib/share/publish.d.ts +1 -1
  36. package/dist/lib/share/publish.js +3 -3
  37. package/dist/lib/share/worker-template.d.ts +12 -1
  38. package/dist/lib/share/worker-template.js +13 -2
  39. package/dist/lib/startup/command-registry.d.ts +10 -2
  40. package/dist/lib/startup/command-registry.js +16 -7
  41. package/dist/lib/tmux/orphan-reap.d.ts +15 -19
  42. package/dist/lib/tmux/orphan-reap.js +15 -21
  43. package/dist/lib/tmux/session.js +4 -3
  44. package/dist/lib/triggers/handlers.js +10 -0
  45. package/dist/lib/triggers/webhook.js +10 -0
  46. package/dist/lib/types.d.ts +2 -2
  47. package/package.json +1 -1
  48. package/dist/commands/set.d.ts +0 -15
  49. package/dist/commands/set.js +0 -79
  50. package/dist/commands/setup-share.d.ts +0 -17
@@ -1,4 +1,4 @@
1
- // Cloudflare provisioning for `agents share setup` — plain `fetch` against the CF
1
+ // Cloudflare provisioning for `agents artifacts setup` — plain `fetch` against the CF
2
2
  // REST API (the repo has no CF wrapper). Creates the R2 bucket, configures its
3
3
  // lifecycle, uploads the Worker (with an R2 binding), sets the WRITE_TOKEN secret,
4
4
  // enables the free `*.workers.dev` subdomain, and — when the token owns the zone —
@@ -134,14 +134,14 @@ export async function updateWorker(apiToken, accountId, workerName, bucketName,
134
134
  await deployWorker(apiToken, accountId, workerName, script, bucketName, opts);
135
135
  // Script upload clears bindings/secrets (see JSDoc above). If re-applying
136
136
  // WRITE_TOKEN fails here, the live Worker has no write token — every
137
- // `agents share` publish/delete 401s until a re-run of `agents share update`
137
+ // `agents artifacts share` publish/delete 401s until a re-run of `agents artifacts share update`
138
138
  // completes both steps. Surface that explicitly instead of the raw CF error.
139
139
  try {
140
140
  await setWorkerSecret(apiToken, accountId, workerName, writeToken, opts);
141
141
  }
142
142
  catch (e) {
143
143
  const detail = e instanceof Error ? e.message : String(e);
144
- throw new Error(`Worker deployed but the write token failed to re-apply — re-run \`agents share update\` to fix this before publishing/deleting anything. (${detail})`);
144
+ throw new Error(`Worker deployed but the write token failed to re-apply — re-run \`agents artifacts share update\` to fix this before publishing/deleting anything. (${detail})`);
145
145
  }
146
146
  return { templateHash, skipped: false };
147
147
  }
@@ -19,7 +19,7 @@ export interface PublishOptions {
19
19
  expire?: string;
20
20
  contentType?: string;
21
21
  /**
22
- * Hide this page from the public `/<user>` gallery and `agents share list`
22
+ * Hide this page from the public `/<user>` gallery and `agents artifacts share list`
23
23
  * (metadata `visibility=unlisted`). The direct URL is still world-readable —
24
24
  * unlisted, not secret (RUSH-2443). Alias of `--private` on the CLI.
25
25
  */
@@ -1,4 +1,4 @@
1
- // The publish path for `agents share <file>` — an authed PUT to the Worker.
1
+ // The publish path for `agents artifacts share <file>` — an authed PUT to the Worker.
2
2
  // Pure logic (slug, expiry) is exported for tests; the network call is behind a DI seam.
3
3
  //
4
4
  // For HTML publishes it also captures a 1200×630 cover (the page's own hero) and
@@ -255,7 +255,7 @@ export function buildShareKey(username, slugPart) {
255
255
  export async function publishFile(filePath, opts = {}) {
256
256
  const cfg = opts.config ?? readShareConfig();
257
257
  if (!cfg) {
258
- throw new Error("Not set up yet. Run 'agents share setup' (provision your own endpoint) or 'agents share join' (use an existing one).");
258
+ throw new Error("Not set up yet. Run 'agents artifacts setup' (provision your own endpoint) or 'agents artifacts share join' (use an existing one).");
259
259
  }
260
260
  const token = opts.writeToken ?? readWriteToken();
261
261
  const username = await resolveShareUsername(opts);
@@ -318,7 +318,7 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
318
318
  }
319
319
  const r = await put(pageUrl, body, authHeaders(opts.contentType ?? guessContentType(filePath)));
320
320
  if (!r.ok) {
321
- throw new Error(`Publish failed (${r.status}) for ${pageUrl}. Check the write token, or that 'agents share setup' completed.`);
321
+ throw new Error(`Publish failed (${r.status}) for ${pageUrl}. Check the write token, or that 'agents artifacts setup' completed.`);
322
322
  }
323
323
  return {
324
324
  url: r.url ?? pageUrl,
@@ -1,2 +1,13 @@
1
- /** Render the Worker source. Pure — the R2 binding + token are wired at deploy time. */
1
+ /**
2
+ * Render the Worker source. Pure — the R2 binding + token are wired at deploy time.
3
+ *
4
+ * The literal below still spells the CLI `agents share` in its provenance comment,
5
+ * its root response, and its gallery title, even though the command is now
6
+ * `agents artifacts share` (RUSH-2580). That is deliberate: `hashWorkerScript` of
7
+ * this exact text is what `shareTemplateStatus` compares a provisioned endpoint's
8
+ * recorded `templateHash` against, so editing ANY byte here marks every already-
9
+ * deployed endpoint `outdated` — which makes `agents artifacts share list` refuse
10
+ * until its owner re-runs `agents artifacts share update`. Cosmetic renames are not
11
+ * worth that; change this text only alongside a real Worker behavior change.
12
+ */
2
13
  export declare function renderWorkerScript(): string;
@@ -9,7 +9,7 @@
9
9
  // sweeper; this is the immediate gate.
10
10
  // - GET /<username> — public gallery of that user's shares (HTML).
11
11
  // - GET /<username>?format=json — public machine-readable listing of that user's
12
- // ACTIVE shares (`agents share list`). Same single-segment path as the HTML
12
+ // ACTIVE shares (`agents artifacts share list`). Same single-segment path as the HTML
13
13
  // gallery and gated on the SAME "does <username>/ hold any object" check, so it
14
14
  // only intercepts a genuine namespace — a legacy flat slug with ?format=json
15
15
  // still serves its real content, never a fake empty listing.
@@ -19,7 +19,18 @@
19
19
  // Emitted as a string (mirrors src/lib/serve/page.ts `renderPage()`), so it compiles
20
20
  // into `dist/**` and ships with no package.json#files change. `provision.ts` uploads
21
21
  // this verbatim as an ES-module Worker with a BUCKET (R2) binding + a WRITE_TOKEN secret.
22
- /** Render the Worker source. Pure — the R2 binding + token are wired at deploy time. */
22
+ /**
23
+ * Render the Worker source. Pure — the R2 binding + token are wired at deploy time.
24
+ *
25
+ * The literal below still spells the CLI `agents share` in its provenance comment,
26
+ * its root response, and its gallery title, even though the command is now
27
+ * `agents artifacts share` (RUSH-2580). That is deliberate: `hashWorkerScript` of
28
+ * this exact text is what `shareTemplateStatus` compares a provisioned endpoint's
29
+ * recorded `templateHash` against, so editing ANY byte here marks every already-
30
+ * deployed endpoint `outdated` — which makes `agents artifacts share list` refuse
31
+ * until its owner re-runs `agents artifacts share update`. Cosmetic renames are not
32
+ * worth that; change this text only alongside a real Worker behavior change.
33
+ */
23
34
  export function renderWorkerScript() {
24
35
  return `// GENERATED by agents-cli agents share setup — do not edit here; edit
25
36
  // src/lib/share/worker-template.ts and re-run setup.
@@ -50,7 +50,6 @@ export declare const loadOpen: ModuleLoader;
50
50
  export declare const loadReconnect: ModuleLoader;
51
51
  export declare const loadFork: ModuleLoader;
52
52
  export declare const loadConfig: ModuleLoader;
53
- export declare const loadSet: ModuleLoader;
54
53
  export declare const loadModels: ModuleLoader;
55
54
  export declare const loadModes: ModuleLoader;
56
55
  export declare const loadPrune: ModuleLoader;
@@ -100,7 +99,7 @@ export declare const loadSend: ModuleLoader;
100
99
  export declare const loadFeed: ModuleLoader;
101
100
  export declare const loadMailboxes: ModuleLoader;
102
101
  export declare const loadServe: ModuleLoader;
103
- export declare const loadShare: ModuleLoader;
102
+ export declare const loadArtifacts: ModuleLoader;
104
103
  export declare const loadAudit: ModuleLoader;
105
104
  export declare const loadWebhooks: ModuleLoader;
106
105
  export declare const loadHumans: ModuleLoader;
@@ -143,6 +142,15 @@ export declare const COMMAND_LOADERS: Record<string, ModuleLoader[]>;
143
142
  * registered command tree so a new command can never drift out of it.
144
143
  */
145
144
  export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
145
+ /**
146
+ * Former top-level names that must NOT auto-correct (edit-distance 1) into a
147
+ * live command. Without this a pruned surface silently misroutes: the typed
148
+ * name is gone, the spellchecker finds a neighbour, and the CLI runs something
149
+ * the user never asked for instead of saying the command is gone.
150
+ *
151
+ * `set` moved under `agents models`/`agents config` (RUSH-2579); `share` moved
152
+ * under `agents artifacts share` (RUSH-2580).
153
+ */
146
154
  export declare const RETIRED_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
147
155
  /** Whether `name` is a top-level command this CLI registers. See {@link KNOWN_TOP_LEVEL_COMMANDS}. */
148
156
  export declare function isKnownTopLevelCommand(name: string): boolean;
@@ -52,7 +52,6 @@ export const loadOpen = async () => (await import('../../commands/open.js')).reg
52
52
  export const loadReconnect = async () => (await import('../../commands/reconnect.js')).registerReconnectCommand;
53
53
  export const loadFork = async () => (await import('../../commands/fork.js')).registerForkCommand;
54
54
  export const loadConfig = async () => (await import('../../commands/config.js')).registerConfigCommand;
55
- export const loadSet = async () => (await import('../../commands/set.js')).registerSetCommand;
56
55
  export const loadModels = async () => (await import('../../commands/models.js')).registerModelsCommand;
57
56
  export const loadModes = async () => (await import('../../commands/modes.js')).registerModesCommand;
58
57
  export const loadPrune = async () => (await import('../../commands/prune.js')).registerPruneCommand;
@@ -103,7 +102,9 @@ export const loadSend = async () => (await import('../../commands/send.js')).reg
103
102
  export const loadFeed = async () => (await import('../../commands/feed.js')).registerFeedCommand;
104
103
  export const loadMailboxes = async () => (await import('../../commands/mailboxes.js')).registerMailboxesCommand;
105
104
  export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
106
- export const loadShare = async () => (await import('../../commands/share.js')).registerShareCommands;
105
+ // Registers the `artifacts` group (with `share` + `setup` under it) AND the
106
+ // top-level `unshare` alias — see commands/artifacts.ts.
107
+ export const loadArtifacts = async () => (await import('../../commands/artifacts.js')).registerArtifactsCommands;
107
108
  export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
108
109
  export const loadWebhooks = async () => (await import('../../commands/webhook.js')).registerWebhooksCommand;
109
110
  export const loadHumans = async () => (await import('../../commands/humans.js')).registerHumansCommands;
@@ -180,7 +181,6 @@ export const COMMAND_LOADERS = {
180
181
  reconnect: [loadReconnect],
181
182
  fork: [loadFork],
182
183
  config: [loadConfig],
183
- set: [loadSet],
184
184
  models: [loadModels],
185
185
  modes: [loadModes],
186
186
  trash: [loadTrash],
@@ -246,10 +246,10 @@ export const COMMAND_LOADERS = {
246
246
  mailboxes: [loadMailboxes],
247
247
  mailbox: [loadMailboxes],
248
248
  serve: [loadServe],
249
- share: [loadShare],
250
- // `unshare` is a top-level convenience alias of `share delete` (see
249
+ artifacts: [loadArtifacts],
250
+ // `unshare` is a top-level convenience alias of `artifacts share delete` (see
251
251
  // commands/share.ts) — same module, registered as its own program.command().
252
- unshare: [loadShare],
252
+ unshare: [loadArtifacts],
253
253
  audit: [loadAudit],
254
254
  webhooks: [loadWebhooks],
255
255
  humans: [loadHumans],
@@ -288,7 +288,16 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
288
288
  ...Object.keys(COMMAND_LOADERS),
289
289
  ...INLINE_COMMAND_NAMES,
290
290
  ]);
291
- export const RETIRED_TOP_LEVEL_COMMANDS = new Set(['webhook']);
291
+ /**
292
+ * Former top-level names that must NOT auto-correct (edit-distance 1) into a
293
+ * live command. Without this a pruned surface silently misroutes: the typed
294
+ * name is gone, the spellchecker finds a neighbour, and the CLI runs something
295
+ * the user never asked for instead of saying the command is gone.
296
+ *
297
+ * `set` moved under `agents models`/`agents config` (RUSH-2579); `share` moved
298
+ * under `agents artifacts share` (RUSH-2580).
299
+ */
300
+ export const RETIRED_TOP_LEVEL_COMMANDS = new Set(['webhook', 'set', 'share']);
292
301
  /** Whether `name` is a top-level command this CLI registers. See {@link KNOWN_TOP_LEVEL_COMMANDS}. */
293
302
  export function isKnownTopLevelCommand(name) {
294
303
  return KNOWN_TOP_LEVEL_COMMANDS.has(name);
@@ -58,12 +58,10 @@
58
58
  * - the reaping process itself, its ancestors, pid 1, and every long-lived
59
59
  * agents-cli service ({@link isProtectedAgentsService}) are never candidates.
60
60
  * - an UNRELIABLE read of tmux's session state (the query threw, timed out, or
61
- * tmux itself never answered) disables tier 1 for that sweep entirely — an
62
- * absent map entry proves a session is gone only when the map is known-good.
63
- * Distinguishing "tmux answered: no such session" (a completed, if nonzero,
64
- * exit genuinely nothing there) from "tmux did not answer" (a thrown
65
- * error/timeout — unknown) is exactly this: a completed process is a real
66
- * answer, a rejected promise is not one at all.
61
+ * tmux itself never answered) disables tier 1 for that sweep entirely;
62
+ * - an absent session entry is unknown, even after a reliable read. A tmux
63
+ * server restart can produce the same empty snapshot while marked agents
64
+ * remain alive, so tier 1 requires a present owner with positive death proof.
67
65
  * - tier 2 is anchored on the actual claude EXECUTABLE (argv[0]'s basename),
68
66
  * never a substring match anywhere in the command line, and excludes any
69
67
  * process still structurally part of a LIVE pane leaf's process tree right
@@ -121,7 +119,7 @@ export interface PaneOwner {
121
119
  attached: boolean;
122
120
  }
123
121
  /** Why a process was selected, for human output and tests. */
124
- export type OrphanReason = 'tmux-session-gone' | 'tmux-agent-exited' | 'detached-helper';
122
+ export type OrphanReason = 'tmux-agent-exited' | 'detached-helper';
125
123
  export interface OrphanCandidate {
126
124
  pid: number;
127
125
  args: string;
@@ -170,16 +168,14 @@ export declare function isProtectedAgentsService(args: string): boolean;
170
168
  /**
171
169
  * Pure. Select the processes whose owning agent is provably gone.
172
170
  *
173
- * `owners` maps a tmux session name to its liveness; a name ABSENT from the map
174
- * is a session tmux no longer has, which is the strongest orphan signal there is
175
- * but ONLY when `owners` is itself known-complete. `opts.ownersReliable`
176
- * (default `true`, so existing direct callers/tests keep their prior meaning)
177
- * says whether the caller actually got a real answer from tmux for every
178
- * socket it queried. When it is `false` a query threw, timed out, or tmux
179
- * itself never answered tier 1 is skipped entirely for this call: an absent
180
- * map entry proves nothing when the map itself might be missing entries for
181
- * sessions tmux never got asked about (RUSH-2521 review — a flaky tick must
182
- * never fall back to "treat every marked process as orphaned").
171
+ * `owners` maps a tmux session name to its liveness. An ABSENT name is never
172
+ * proof that the marked process is orphaned: the tmux server can restart or its
173
+ * socket can disappear while the pane's process tree is still alive. That exact
174
+ * state made the daemon classify every live agent as `tmux-session-gone` and
175
+ * SIGTERM it on startup (RUSH-2603). Tier 1 therefore requires a PRESENT owner
176
+ * whose pane process is confirmed dead and has no attached client. A reliable
177
+ * empty map means "nothing proven dead", not "every marked process is dead".
178
+ * `opts.ownersReliable` still disables tier 1 when any query failed to answer.
183
179
  */
184
180
  export declare function selectOrphanProcesses(procs: AgentProcess[], owners: Map<string, PaneOwner>, opts: {
185
181
  protectedPids: Set<number>;
@@ -281,8 +277,8 @@ export declare function readPaneOwners(socket: string): Promise<PaneOwnersRead>;
281
277
  * Reap every helper process whose owning agent has exited.
282
278
  *
283
279
  * Called from the daemon's periodic tick and from `agents sessions reap`. Panes
284
- * are read BEFORE processes so a session that disappears mid-scan is treated as
285
- * gone (reapable) rather than as a live owner.
280
+ * are read before processes to take one ownership snapshot. A session absent
281
+ * from that snapshot is unknown and is never sufficient proof for tier 1.
286
282
  *
287
283
  * `opts.pids` is the test-only process-table scope described on
288
284
  * {@link readAgentProcesses} — never set by production callers.
@@ -58,12 +58,10 @@
58
58
  * - the reaping process itself, its ancestors, pid 1, and every long-lived
59
59
  * agents-cli service ({@link isProtectedAgentsService}) are never candidates.
60
60
  * - an UNRELIABLE read of tmux's session state (the query threw, timed out, or
61
- * tmux itself never answered) disables tier 1 for that sweep entirely — an
62
- * absent map entry proves a session is gone only when the map is known-good.
63
- * Distinguishing "tmux answered: no such session" (a completed, if nonzero,
64
- * exit genuinely nothing there) from "tmux did not answer" (a thrown
65
- * error/timeout — unknown) is exactly this: a completed process is a real
66
- * answer, a rejected promise is not one at all.
61
+ * tmux itself never answered) disables tier 1 for that sweep entirely;
62
+ * - an absent session entry is unknown, even after a reliable read. A tmux
63
+ * server restart can produce the same empty snapshot while marked agents
64
+ * remain alive, so tier 1 requires a present owner with positive death proof.
67
65
  * - tier 2 is anchored on the actual claude EXECUTABLE (argv[0]'s basename),
68
66
  * never a substring match anywhere in the command line, and excludes any
69
67
  * process still structurally part of a LIVE pane leaf's process tree right
@@ -193,16 +191,14 @@ function livePid(pid) {
193
191
  /**
194
192
  * Pure. Select the processes whose owning agent is provably gone.
195
193
  *
196
- * `owners` maps a tmux session name to its liveness; a name ABSENT from the map
197
- * is a session tmux no longer has, which is the strongest orphan signal there is
198
- * but ONLY when `owners` is itself known-complete. `opts.ownersReliable`
199
- * (default `true`, so existing direct callers/tests keep their prior meaning)
200
- * says whether the caller actually got a real answer from tmux for every
201
- * socket it queried. When it is `false` a query threw, timed out, or tmux
202
- * itself never answered tier 1 is skipped entirely for this call: an absent
203
- * map entry proves nothing when the map itself might be missing entries for
204
- * sessions tmux never got asked about (RUSH-2521 review — a flaky tick must
205
- * never fall back to "treat every marked process as orphaned").
194
+ * `owners` maps a tmux session name to its liveness. An ABSENT name is never
195
+ * proof that the marked process is orphaned: the tmux server can restart or its
196
+ * socket can disappear while the pane's process tree is still alive. That exact
197
+ * state made the daemon classify every live agent as `tmux-session-gone` and
198
+ * SIGTERM it on startup (RUSH-2603). Tier 1 therefore requires a PRESENT owner
199
+ * whose pane process is confirmed dead and has no attached client. A reliable
200
+ * empty map means "nothing proven dead", not "every marked process is dead".
201
+ * `opts.ownersReliable` still disables tier 1 when any query failed to answer.
206
202
  */
207
203
  export function selectOrphanProcesses(procs, owners, opts) {
208
204
  const isAlive = opts.isAlive ?? livePid;
@@ -256,10 +252,8 @@ export function selectOrphanProcesses(procs, owners, opts) {
256
252
  if (!p.tmuxSession || !eligible(p))
257
253
  continue;
258
254
  const owner = owners.get(p.tmuxSession);
259
- if (!owner) {
260
- push(p, 'tmux-session-gone');
255
+ if (!owner)
261
256
  continue;
262
- }
263
257
  // An attached client or a live agent pane process is a hard exclusion.
264
258
  if (owner.attached || owner.agentAlive)
265
259
  continue;
@@ -553,8 +547,8 @@ function describe(c) {
553
547
  * Reap every helper process whose owning agent has exited.
554
548
  *
555
549
  * Called from the daemon's periodic tick and from `agents sessions reap`. Panes
556
- * are read BEFORE processes so a session that disappears mid-scan is treated as
557
- * gone (reapable) rather than as a live owner.
550
+ * are read before processes to take one ownership snapshot. A session absent
551
+ * from that snapshot is unknown and is never sufficient proof for tier 1.
558
552
  *
559
553
  * `opts.pids` is the test-only process-table scope described on
560
554
  * {@link readAgentProcesses} — never set by production callers.
@@ -211,9 +211,10 @@ export async function killAll(socket) {
211
211
  export async function reapDeadTmuxPanes(socket, opts = {}) {
212
212
  const sock = socket ?? getDefaultSocketPath();
213
213
  const result = { reaped: 0, sessions: [], details: [], processes: 0, processDetails: [], warnings: [] };
214
- // The process sweep runs even with no server on this socket: a torn-down
215
- // server (`killAll` unlinks the socket) is the strongest orphan signal there
216
- // is, and skipping it here would strand exactly those leftovers forever.
214
+ // The process sweep may run with no server, but an absent session is not
215
+ // evidence that a still-live marked process is orphaned. Tier 1 only acts on
216
+ // a present pane owner confirmed dead; tier 2 independently verifies its
217
+ // declared spawner pid (RUSH-2603).
217
218
  // `opts.pids` is a test-only process-table scope (see `readAgentProcesses`)
218
219
  // — production callers never set it.
219
220
  const { reapOrphanAgentProcesses } = await import('./orphan-reap.js');
@@ -226,6 +226,16 @@ export function handlerMatchesWebhook(handler, webhook) {
226
226
  const current = data?.state?.name;
227
227
  if (current !== handler.stateTo)
228
228
  return false;
229
+ // RUSH-2539: `stateTo` is a TRANSITION predicate, not a current-state one.
230
+ // Linear carries the prior value of each changed field in `updatedFrom`, so a
231
+ // real state change has `updatedFrom.state` (this codebase's shape) or
232
+ // `updatedFrom.stateId` (Linear's scalar). With neither, this Issue/update
233
+ // touched something else (label, assignee, description) while the issue merely
234
+ // still sits in `stateTo` — matching there re-fires on every later edit
235
+ // (RUSH-1459 accumulated 11 duplicate plan comments).
236
+ const updatedTo = webhook.payload.updatedFrom;
237
+ if (!updatedTo || (updatedTo.state === undefined && updatedTo.stateId === undefined))
238
+ return false;
229
239
  }
230
240
  if (handler.stateFrom) {
231
241
  const updatedFrom = webhook.payload.updatedFrom;
@@ -155,6 +155,16 @@ function linearTriggerMatches(trigger, webhook) {
155
155
  const current = data?.state?.name;
156
156
  if (current !== trigger.stateTo)
157
157
  return false;
158
+ // RUSH-2539: `stateTo` is a TRANSITION predicate, not a current-state one.
159
+ // Linear carries the prior value of each changed field in `updatedFrom`, so a
160
+ // real state change has `updatedFrom.state` (this codebase's shape) or
161
+ // `updatedFrom.stateId` (Linear's scalar). With neither, this Issue/update
162
+ // touched something else while the issue merely still sits in `stateTo` —
163
+ // matching there re-fires on every later edit (RUSH-1459 got 11 duplicate
164
+ // plan comments).
165
+ const updatedTo = webhook.payload.updatedFrom;
166
+ if (!updatedTo || (updatedTo.state === undefined && updatedTo.stateId === undefined))
167
+ return false;
158
168
  }
159
169
  if (trigger.stateFrom) {
160
170
  const updatedFrom = webhook.payload.updatedFrom;
@@ -995,7 +995,7 @@ export interface Meta {
995
995
  * Full shape in `lib/fleet/types.ts` (FleetManifest).
996
996
  */
997
997
  fleet?: import('./fleet/types.js').FleetManifest;
998
- /** `agents share` endpoint (Cloudflare R2 + Worker). Set by `agents share
998
+ /** Artifact share endpoint (Cloudflare R2 + Worker). Set by `agents artifacts
999
999
  * setup`/`join`; syncs fleet-wide via `agents repo push/pull`. The write token
1000
1000
  * lives in the `share` secrets bundle, not here. */
1001
1001
  share?: {
@@ -1007,7 +1007,7 @@ export interface Meta {
1007
1007
  /** Cloudflare Web Analytics token injected into published HTML pages. */
1008
1008
  analyticsToken?: string;
1009
1009
  /** sha256 of the Worker script deployed at the last provision/update, so
1010
- * `agents share status` can tell current vs outdated vs unknown (a config
1010
+ * `agents artifacts share status` can tell current vs outdated vs unknown (a config
1011
1011
  * from before this field existed has no hash — always "unknown"). */
1012
1012
  templateHash?: string;
1013
1013
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.37",
3
+ "version": "1.22.38",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,15 +0,0 @@
1
- /**
2
- * `agents set` — ergonomic top-level setter for per-agent/version run defaults.
3
- *
4
- * `agents set claude@2.1.220 --model opus-5` pins the default model (and/or
5
- * mode) that `agents run` uses for that agent version. It reads and writes the
6
- * same store as `agents config set run.<agent@version>.*` (agents.yaml -> run.defaults), so the
7
- * two stay consistent — `set` is just the short front door.
8
- *
9
- * agents set # list every configured default
10
- * agents set claude@2.1.220 # show the default for one version
11
- * agents set claude@2.1.220 --model opus-5
12
- * agents set 'claude:*' --mode auto --model opus
13
- */
14
- import type { Command } from 'commander';
15
- export declare function registerSetCommand(program: Command): void;
@@ -1,79 +0,0 @@
1
- /**
2
- * `agents set` — ergonomic top-level setter for per-agent/version run defaults.
3
- *
4
- * `agents set claude@2.1.220 --model opus-5` pins the default model (and/or
5
- * mode) that `agents run` uses for that agent version. It reads and writes the
6
- * same store as `agents config set run.<agent@version>.*` (agents.yaml -> run.defaults), so the
7
- * two stay consistent — `set` is just the short front door.
8
- *
9
- * agents set # list every configured default
10
- * agents set claude@2.1.220 # show the default for one version
11
- * agents set claude@2.1.220 --model opus-5
12
- * agents set 'claude:*' --mode auto --model opus
13
- */
14
- import chalk from 'chalk';
15
- import { setHelpSections } from '../lib/help.js';
16
- import { formatRunDefaultEntry, listRunDefaults, parseRunDefaultSelector, setRunDefault, } from '../lib/run-defaults.js';
17
- export function registerSetCommand(program) {
18
- const set = program
19
- .command('set [selector]')
20
- .description('Set the default model/mode an agent version uses for `agents run`')
21
- .option('--model <model>', 'Default model or model alias, forwarded via --model')
22
- .option('--mode <mode>', "Default mode: plan, edit, auto, skip. 'full' accepted as alias for skip.")
23
- .action((selector, options) => {
24
- try {
25
- const hasFlags = options.model !== undefined || options.mode !== undefined;
26
- if (!selector) {
27
- if (hasFlags) {
28
- throw new Error('Selector is required when passing --model/--mode. Example: agents set claude@2.1.220 --model opus-5');
29
- }
30
- const entries = listRunDefaults();
31
- if (entries.length === 0) {
32
- console.log(chalk.gray('No agent defaults configured.'));
33
- console.log(chalk.gray('Set one with: agents set claude@2.1.220 --model opus-5'));
34
- return;
35
- }
36
- console.log(chalk.bold('Agent Defaults\n'));
37
- for (const entry of entries) {
38
- console.log(` ${formatRunDefaultEntry(entry)}`);
39
- }
40
- return;
41
- }
42
- if (!hasFlags) {
43
- const parsed = parseRunDefaultSelector(selector);
44
- const entry = listRunDefaults().find((e) => e.selector === parsed.selector);
45
- if (!entry || (!entry.defaults.mode && !entry.defaults.model)) {
46
- console.log(chalk.gray(`No default set for ${parsed.selector}.`));
47
- console.log(chalk.gray(`Set one with: agents set ${selector} --model <model>`));
48
- return;
49
- }
50
- console.log(` ${formatRunDefaultEntry(entry)}`);
51
- return;
52
- }
53
- const entry = setRunDefault(selector, {
54
- ...(options.mode !== undefined ? { mode: options.mode } : {}),
55
- ...(options.model !== undefined ? { model: options.model } : {}),
56
- });
57
- console.log(chalk.green('Set default:'));
58
- console.log(` ${formatRunDefaultEntry(entry)}`);
59
- }
60
- catch (err) {
61
- console.error(chalk.red(err.message));
62
- process.exit(1);
63
- }
64
- });
65
- setHelpSections(set, {
66
- examples: `
67
- agents set claude@2.1.220 --model opus-5
68
- agents set 'claude:*' --mode auto --model opus
69
- agents set claude@2.1.220
70
- agents set
71
- `,
72
- notes: `
73
- Selectors use <agent>@<version> or <agent>:<version>; * matches all versions.
74
- Exact selectors override wildcard selectors field by field.
75
- Writes the same store as 'agents config set run.<agent@version>.*'. Explicit flags on
76
- 'agents run' always win over configured defaults.
77
- `,
78
- });
79
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * `agents setup share` — interactive wizard to configure the `agents share`
3
- * endpoint (Cloudflare R2 + Worker). A friendly front door over the existing
4
- * `agents share setup` (provision) and `agents share join` flows, reusing their
5
- * exact logic so there is a single source of truth for provisioning.
6
- *
7
- * Idempotent: re-running shows the current endpoint and offers to reconfigure.
8
- */
9
- import type { Command } from 'commander';
10
- /**
11
- * Interactive share setup. Returns true if the user configured (or already had)
12
- * an endpoint, false if they skipped. Never throws on user cancel — callers
13
- * (the `agents setup` hub) rely on that to keep the fresh-machine flow going.
14
- */
15
- export declare function runShareWizard(): Promise<boolean>;
16
- /** Register `agents setup share` under the parent `setup` command. */
17
- export declare function registerSetupShareCommand(setupCmd: Command): void;