@phnx-labs/agents-cli 1.20.54 → 1.20.56
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 +17 -0
- package/README.md +9 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/message.js +7 -5
- package/dist/commands/packages.js +113 -2
- package/dist/commands/profiles.js +4 -3
- package/dist/commands/routines.js +185 -15
- package/dist/commands/ssh.js +2 -9
- package/dist/commands/tmux.js +11 -3
- package/dist/index.js +1 -1
- package/dist/lib/daemon.d.ts +33 -1
- package/dist/lib/daemon.js +131 -14
- package/dist/lib/devices/sync.d.ts +7 -0
- package/dist/lib/devices/sync.js +13 -1
- package/dist/lib/exec.js +10 -8
- package/dist/lib/hosts/passthrough.js +8 -4
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +9 -0
- package/dist/lib/migrate.js +48 -0
- package/dist/lib/overdue.js +6 -1
- package/dist/lib/profiles-presets.js +52 -0
- package/dist/lib/registry.d.ts +55 -0
- package/dist/lib/registry.js +82 -1
- package/dist/lib/routines.d.ts +42 -1
- package/dist/lib/routines.js +72 -5
- package/dist/lib/runner.js +62 -9
- package/dist/lib/scheduler.js +4 -3
- package/dist/lib/teams/agents.js +6 -6
- package/dist/lib/tmux/binary.d.ts +4 -0
- package/dist/lib/tmux/binary.js +26 -3
- package/dist/lib/tmux/index.d.ts +1 -1
- package/dist/lib/tmux/index.js +1 -1
- package/dist/lib/tmux/session.d.ts +23 -7
- package/dist/lib/tmux/session.js +32 -9
- package/dist/lib/triggers/webhook.js +2 -2
- package/dist/lib/types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -123,9 +123,10 @@ export declare function paneExitStatus(pane: string, socket?: string): Promise<P
|
|
|
123
123
|
* Bind a per-session hook. Used by the spawn-wrap path to install a `pane-died`
|
|
124
124
|
* hook that detaches the attach client the instant the wrapped agent exits (the
|
|
125
125
|
* global `remain-on-exit on` otherwise leaves the client staring at a dead pane).
|
|
126
|
-
* Best-effort —
|
|
126
|
+
* Best-effort — returns false when tmux rejects the hook so callers do not
|
|
127
|
+
* stamp a schema marker and daemon reconciliation can retry later.
|
|
127
128
|
*/
|
|
128
|
-
export declare function setSessionHook(name: string, hook: string, command: string, socket?: string): Promise<
|
|
129
|
+
export declare function setSessionHook(name: string, hook: string, command: string, socket?: string): Promise<boolean>;
|
|
129
130
|
/**
|
|
130
131
|
* Schema version of the `pane-died` hook installed on managed `agents run`
|
|
131
132
|
* sessions. Bump whenever the hook's SHAPE changes so the daemon reconcile
|
|
@@ -135,15 +136,30 @@ export declare function setSessionHook(name: string, hook: string, command: stri
|
|
|
135
136
|
* user exiting a split they opened) tore down the whole client.
|
|
136
137
|
* v2 — `#{hook_pane}`-guarded: only the AGENT pane dying detaches; a user
|
|
137
138
|
* split's death runs `kill-pane`, closing just that split.
|
|
139
|
+
* v3 — the else-branch pins its target via
|
|
140
|
+
* `run-shell "tmux -S <socket> kill-pane -t #{hook_pane}"`. Untargeted
|
|
141
|
+
* kill-pane resolves "current pane" inside the hook context, which goes
|
|
142
|
+
* nondeterministic on a loaded detached server (CI flake #965: the dead
|
|
143
|
+
* split survived as a husk); run-shell format-expands its command at
|
|
144
|
+
* fire time, so the event pane is always the target.
|
|
145
|
+
* v4 — `run-shell -C "kill-pane -t #{hook_pane}"` executes the targeted command
|
|
146
|
+
* in the tmux server instead of launching a second tmux client against
|
|
147
|
+
* the same socket from inside the hook. That self-client could race the
|
|
148
|
+
* server under load and leave the dead split behind.
|
|
138
149
|
*/
|
|
139
|
-
export declare const AGENT_HOOK_SCHEMA =
|
|
150
|
+
export declare const AGENT_HOOK_SCHEMA = 4;
|
|
140
151
|
/**
|
|
141
152
|
* The guarded `pane-died` hook. Detach the client ONLY when the agent pane dies
|
|
142
153
|
* (so the blocking attach in runInTmux returns and the exit status can be read);
|
|
143
|
-
* a user split's death
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
154
|
+
* a user split's death runs the else-branch, closing just that split. The
|
|
155
|
+
* else-branch goes through `run-shell -C` with an explicit `-t #{hook_pane}`
|
|
156
|
+
* target: tmux format-expands the command at fire time and executes it inside
|
|
157
|
+
* the server command queue, so the event pane is always the one killed without
|
|
158
|
+
* launching a second tmux client against the same socket. A bare `kill-pane`
|
|
159
|
+
* relied on the hook context supplying a "current pane", while an external
|
|
160
|
+
* self-client could race the server under load. Single source of truth: both
|
|
161
|
+
* the spawn-wrap (exec.ts) and the daemon reconcile build the hook here, so the
|
|
162
|
+
* two can never drift.
|
|
147
163
|
*/
|
|
148
164
|
export declare function agentPaneDiedHook(sessionName: string, agentPane: string): string;
|
|
149
165
|
/** Stamp a session's hook-schema marker to the current version. */
|
package/dist/lib/tmux/session.js
CHANGED
|
@@ -262,12 +262,18 @@ export async function paneExitStatus(pane, socket) {
|
|
|
262
262
|
* Bind a per-session hook. Used by the spawn-wrap path to install a `pane-died`
|
|
263
263
|
* hook that detaches the attach client the instant the wrapped agent exits (the
|
|
264
264
|
* global `remain-on-exit on` otherwise leaves the client staring at a dead pane).
|
|
265
|
-
* Best-effort —
|
|
265
|
+
* Best-effort — returns false when tmux rejects the hook so callers do not
|
|
266
|
+
* stamp a schema marker and daemon reconciliation can retry later.
|
|
266
267
|
*/
|
|
267
268
|
export async function setSessionHook(name, hook, command, socket) {
|
|
268
269
|
assertValidSessionName(name);
|
|
269
270
|
const sock = socket ?? getDefaultSocketPath();
|
|
270
|
-
|
|
271
|
+
const result = await runTmux({
|
|
272
|
+
socket: sock,
|
|
273
|
+
args: ['set-hook', '-t', name, hook, command],
|
|
274
|
+
throwOnError: false,
|
|
275
|
+
}).catch(() => null);
|
|
276
|
+
return result?.code === 0;
|
|
271
277
|
}
|
|
272
278
|
/**
|
|
273
279
|
* Schema version of the `pane-died` hook installed on managed `agents run`
|
|
@@ -278,20 +284,35 @@ export async function setSessionHook(name, hook, command, socket) {
|
|
|
278
284
|
* user exiting a split they opened) tore down the whole client.
|
|
279
285
|
* v2 — `#{hook_pane}`-guarded: only the AGENT pane dying detaches; a user
|
|
280
286
|
* split's death runs `kill-pane`, closing just that split.
|
|
287
|
+
* v3 — the else-branch pins its target via
|
|
288
|
+
* `run-shell "tmux -S <socket> kill-pane -t #{hook_pane}"`. Untargeted
|
|
289
|
+
* kill-pane resolves "current pane" inside the hook context, which goes
|
|
290
|
+
* nondeterministic on a loaded detached server (CI flake #965: the dead
|
|
291
|
+
* split survived as a husk); run-shell format-expands its command at
|
|
292
|
+
* fire time, so the event pane is always the target.
|
|
293
|
+
* v4 — `run-shell -C "kill-pane -t #{hook_pane}"` executes the targeted command
|
|
294
|
+
* in the tmux server instead of launching a second tmux client against
|
|
295
|
+
* the same socket from inside the hook. That self-client could race the
|
|
296
|
+
* server under load and leave the dead split behind.
|
|
281
297
|
*/
|
|
282
|
-
export const AGENT_HOOK_SCHEMA =
|
|
298
|
+
export const AGENT_HOOK_SCHEMA = 4;
|
|
283
299
|
/** Per-session tmux user-option that records which AGENT_HOOK_SCHEMA a session's hook is at. */
|
|
284
300
|
const HOOK_SCHEMA_OPTION = '@ag_hook_schema';
|
|
285
301
|
/**
|
|
286
302
|
* The guarded `pane-died` hook. Detach the client ONLY when the agent pane dies
|
|
287
303
|
* (so the blocking attach in runInTmux returns and the exit status can be read);
|
|
288
|
-
* a user split's death
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
304
|
+
* a user split's death runs the else-branch, closing just that split. The
|
|
305
|
+
* else-branch goes through `run-shell -C` with an explicit `-t #{hook_pane}`
|
|
306
|
+
* target: tmux format-expands the command at fire time and executes it inside
|
|
307
|
+
* the server command queue, so the event pane is always the one killed without
|
|
308
|
+
* launching a second tmux client against the same socket. A bare `kill-pane`
|
|
309
|
+
* relied on the hook context supplying a "current pane", while an external
|
|
310
|
+
* self-client could race the server under load. Single source of truth: both
|
|
311
|
+
* the spawn-wrap (exec.ts) and the daemon reconcile build the hook here, so the
|
|
312
|
+
* two can never drift.
|
|
292
313
|
*/
|
|
293
314
|
export function agentPaneDiedHook(sessionName, agentPane) {
|
|
294
|
-
return `if -F '#{==:#{hook_pane},${agentPane}}' 'detach-client -s =${sessionName}' 'kill-pane'`;
|
|
315
|
+
return `if -F '#{==:#{hook_pane},${agentPane}}' 'detach-client -s =${sessionName}' 'run-shell -C "kill-pane -t #{hook_pane}"'`;
|
|
295
316
|
}
|
|
296
317
|
/** Stamp a session's hook-schema marker to the current version. */
|
|
297
318
|
export async function markSessionHookSchema(name, socket) {
|
|
@@ -355,7 +376,9 @@ export async function reconcileSessionHooks(socket) {
|
|
|
355
376
|
const agentPane = s.meta?.pane ?? await lowestPaneId(s.name, sock);
|
|
356
377
|
if (!agentPane)
|
|
357
378
|
continue;
|
|
358
|
-
await setSessionHook(s.name, 'pane-died', agentPaneDiedHook(s.name, agentPane), sock);
|
|
379
|
+
const installed = await setSessionHook(s.name, 'pane-died', agentPaneDiedHook(s.name, agentPane), sock);
|
|
380
|
+
if (!installed)
|
|
381
|
+
continue;
|
|
359
382
|
await markSessionHookSchema(s.name, sock);
|
|
360
383
|
reconciled++;
|
|
361
384
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* The optional http listener (`startWebhookServer`) is a thin adapter over it.
|
|
13
13
|
*/
|
|
14
14
|
import * as http from 'http';
|
|
15
|
-
import { listJobs } from '../routines.js';
|
|
15
|
+
import { listJobs, jobRunsOnThisDevice } from '../routines.js';
|
|
16
16
|
import { executeJobDetached } from '../runner.js';
|
|
17
17
|
/** Read `repository.full_name` (`owner/name`) from a webhook payload, if present. */
|
|
18
18
|
export function webhookRepo(payload) {
|
|
@@ -85,7 +85,7 @@ export function jobMatchesWebhook(job, webhook) {
|
|
|
85
85
|
* time-based jobs are unaffected by webhook delivery.
|
|
86
86
|
*/
|
|
87
87
|
export function matchJobsToWebhook(jobs, webhook) {
|
|
88
|
-
return jobs.filter((job) => job.enabled !== false && jobMatchesWebhook(job, webhook));
|
|
88
|
+
return jobs.filter((job) => job.enabled !== false && jobRunsOnThisDevice(job) && jobMatchesWebhook(job, webhook));
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
91
|
* Match an incoming webhook against the persisted routines and fire each match
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -439,6 +439,12 @@ export interface SkillEntry {
|
|
|
439
439
|
tags?: string[];
|
|
440
440
|
/** Registry-specific trust signal (e.g. 'builtin', 'trusted', 'community'). */
|
|
441
441
|
trustLevel?: string;
|
|
442
|
+
/**
|
|
443
|
+
* Lowercase hex sha256 of the skill's SKILL.md, as recorded by
|
|
444
|
+
* `agents publish`. When present, install verifies the cloned SKILL.md
|
|
445
|
+
* against it and aborts on mismatch.
|
|
446
|
+
*/
|
|
447
|
+
sha256?: string;
|
|
442
448
|
}
|
|
443
449
|
/** Paginated response from a skill registry search endpoint. */
|
|
444
450
|
export interface SkillRegistryResponse {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.56",
|
|
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",
|