@phnx-labs/agents-cli 1.20.78 → 1.20.83
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 +595 -0
- package/README.md +26 -26
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +66 -0
- package/dist/commands/doctor.js +596 -33
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +24 -5
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/menubar.js +12 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +187 -11
- package/dist/commands/routines.d.ts +8 -1
- package/dist/commands/routines.js +241 -77
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +100 -32
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.js +32 -3
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +115 -0
- package/dist/commands/sessions.js +189 -35
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +52 -8
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -2
- package/dist/lib/agents.js +72 -63
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +18 -0
- package/dist/lib/daemon.js +47 -6
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.d.ts +7 -0
- package/dist/lib/exec.js +69 -11
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +14 -14
- package/dist/lib/git.js +41 -44
- package/dist/lib/hooks.d.ts +71 -0
- package/dist/lib/hooks.js +245 -95
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +24 -0
- package/dist/lib/menubar/install-menubar.js +52 -2
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +33 -0
- package/dist/lib/routines.js +122 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +49 -10
- package/dist/lib/scheduler.js +16 -4
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +16 -0
- package/dist/lib/secrets/agent.js +105 -3
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +11 -8
- package/dist/lib/secrets/bundles.js +31 -23
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +128 -26
- package/dist/lib/session/active.js +192 -53
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +4 -1
- package/dist/lib/session/db.js +65 -8
- package/dist/lib/session/discover.d.ts +4 -0
- package/dist/lib/session/discover.js +13 -0
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/state.js +57 -1
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
package/dist/lib/activity.d.ts
CHANGED
|
@@ -7,6 +7,28 @@ export type MilestoneEvent = 'plan.created' | 'pr.opened' | 'pr.merged' | 'workt
|
|
|
7
7
|
export type ActivityKind = 'file.edited';
|
|
8
8
|
export type ActivityEventKind = MilestoneEvent | ActivityKind;
|
|
9
9
|
export type ActivityTier = 'milestone' | 'activity';
|
|
10
|
+
/** Well-known attachment kinds; the field is an open string, not an enum. */
|
|
11
|
+
export type AttachmentKind = 'link' | 'file' | 'image' | 'audio' | 'video';
|
|
12
|
+
/**
|
|
13
|
+
* A generic artifact carried by a progress update — a rendered plan, an audio
|
|
14
|
+
* take, a preview URL. Deliberately domain-agnostic: `kind` is an open string,
|
|
15
|
+
* and `meta` is an open bag (duration, width, …) so any producer can attach
|
|
16
|
+
* anything without a schema change.
|
|
17
|
+
*/
|
|
18
|
+
export interface Attachment {
|
|
19
|
+
/** Coarse kind for glyph/rendering; open string, not a closed enum. */
|
|
20
|
+
kind: AttachmentKind | string;
|
|
21
|
+
/** https:// URL, an absolute path, or a history-relative path. */
|
|
22
|
+
href: string;
|
|
23
|
+
/** Display name (defaults to basename(href) at render time). */
|
|
24
|
+
name?: string;
|
|
25
|
+
/** MIME type when known (e.g. `audio/wav`). */
|
|
26
|
+
mediaType?: string;
|
|
27
|
+
/** Size in bytes for local files, when stat succeeded. */
|
|
28
|
+
bytes?: number;
|
|
29
|
+
/** Open bag for kind-specific facts (duration, width, height, …). */
|
|
30
|
+
meta?: Record<string, string | number>;
|
|
31
|
+
}
|
|
10
32
|
/** The set of milestone events, for tier classification and ordering. */
|
|
11
33
|
export declare const MILESTONE_EVENTS: readonly MilestoneEvent[];
|
|
12
34
|
export declare function tierForEvent(event: string): ActivityTier;
|
|
@@ -29,6 +51,13 @@ export interface ActivityEvent {
|
|
|
29
51
|
runtime: string;
|
|
30
52
|
/** Working directory at event time -- the join key to a project/git repo. */
|
|
31
53
|
cwd?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Project/repo name stamped by the writer (basename of cwd, worktree-aware),
|
|
56
|
+
* so a progress post carries its project even without a live-session join.
|
|
57
|
+
* Read-time enrichment ({@link enrichActivityEvents}) still fills it for
|
|
58
|
+
* hook-written events that lack it.
|
|
59
|
+
*/
|
|
60
|
+
project?: string;
|
|
32
61
|
/** Agent that produced the event (claude, codex, ...). */
|
|
33
62
|
agent?: string;
|
|
34
63
|
/** Tool that triggered the event (Bash, Task, ExitPlanMode, feed.post, ...). */
|
|
@@ -45,7 +74,15 @@ export interface ActivityEvent {
|
|
|
45
74
|
terminalId?: string;
|
|
46
75
|
/** `$TMUX_PANE` at launch when recorded. */
|
|
47
76
|
tmuxPane?: string;
|
|
77
|
+
/** Generic artifacts attached to a deliberate progress post. */
|
|
78
|
+
attachments?: Attachment[];
|
|
48
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Coerce an unknown value into a clean {@link Attachment}[] — a fail-open reader
|
|
82
|
+
* over user/agent-written JSON. Drops entries without a usable `href`, clamps
|
|
83
|
+
* `kind` to a string, and keeps only primitive `meta` values.
|
|
84
|
+
*/
|
|
85
|
+
export declare function sanitizeAttachments(value: unknown): Attachment[] | undefined;
|
|
49
86
|
/**
|
|
50
87
|
* Append one event to a session's activity log. Primarily the Python hook
|
|
51
88
|
* writes these at runtime; this TS writer exists for tests and any in-process
|
|
@@ -111,6 +148,125 @@ export declare function styleForEvent(event: string): {
|
|
|
111
148
|
export declare function formatActivityLine(ev: ActivityEvent, opts?: {
|
|
112
149
|
showHost?: boolean;
|
|
113
150
|
}): string;
|
|
151
|
+
/** Glyph per attachment kind, so an artifact row reads at a glance. */
|
|
152
|
+
export declare const ATTACHMENT_GLYPH: Record<string, string>;
|
|
153
|
+
export declare function attachmentGlyph(kind: string): string;
|
|
154
|
+
/** Display name for an attachment: its `name`, else the basename of its href. */
|
|
155
|
+
export declare function attachmentName(att: Attachment): string;
|
|
156
|
+
/**
|
|
157
|
+
* Short session id for a chip: strip a known prefix (`session_`, `ses_`) then
|
|
158
|
+
* take the first 8 chars — enough to disambiguate, matching `ag sessions`.
|
|
159
|
+
*/
|
|
160
|
+
export declare function shortSessionId(sessionId: string): string;
|
|
161
|
+
/** Extra identity resolved at display time by joining the session index. */
|
|
162
|
+
export interface ProgressJoin {
|
|
163
|
+
ticketId?: string;
|
|
164
|
+
prUrl?: string;
|
|
165
|
+
label?: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Render a deliberate progress update (`status.posted`) as an operator-facing,
|
|
169
|
+
* multi-line row with full identity — not a 60-char one-liner. Shape:
|
|
170
|
+
*
|
|
171
|
+
* ```
|
|
172
|
+
* ▸ update · 3m ago
|
|
173
|
+
* grok · 0108441e · yosemite-s1 · agents
|
|
174
|
+
* "CHANGELOG pushed; watching CI"
|
|
175
|
+
* ♪ draft.wav 🖼 cover.png
|
|
176
|
+
* ↳ ag focus 0108441e · ag sessions 0108441e
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* Chips (agent, short session id, host, project, optional joined ticket/label)
|
|
180
|
+
* are shown only when known. Non-progress milestones keep {@link formatActivityLine}.
|
|
181
|
+
*/
|
|
182
|
+
export declare function formatProgressUpdate(ev: ActivityEvent, opts?: {
|
|
183
|
+
joined?: ProgressJoin;
|
|
184
|
+
}): string;
|
|
185
|
+
/** An activity event with the session-derived facts joined on at read time. */
|
|
186
|
+
export interface EnrichedActivityEvent extends ActivityEvent {
|
|
187
|
+
/** Resolved project/repo name (from cwd or the session join), not the raw path. */
|
|
188
|
+
project?: string;
|
|
189
|
+
/** Tracker ticket the owning session is tied to (e.g. `RUSH-1234`). */
|
|
190
|
+
ticket?: string;
|
|
191
|
+
/** Machine the event actually ran on — session `provenance.host`, else `host`. */
|
|
192
|
+
executionHost?: string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Facts pulled off a live session to enrich its activity events. Keyed by
|
|
196
|
+
* `sessionId`; every field optional so callers pass whatever they resolved.
|
|
197
|
+
*/
|
|
198
|
+
export interface ActivitySessionHint {
|
|
199
|
+
sessionId?: string | null;
|
|
200
|
+
/** Tracker ticket id (from `ActiveSession.ticket`). */
|
|
201
|
+
ticket?: string | null;
|
|
202
|
+
/** Machine the session executes on (`provenance.host` / `machine`). */
|
|
203
|
+
executionHost?: string | null;
|
|
204
|
+
/** Resolved project/repo slug from the session cwd. */
|
|
205
|
+
project?: string | null;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Resolve a stable project/repo name from a working directory. A worktree cwd
|
|
209
|
+
* (`…/<repo>/.agents/worktrees/<slug>[/sub]`) resolves to the repo dir name so
|
|
210
|
+
* a worktree session groups with its own repo; any other path resolves to its
|
|
211
|
+
* basename. Pure — no filesystem access, so it works for remote events too.
|
|
212
|
+
*/
|
|
213
|
+
export declare function projectFromCwd(cwd?: string | null): string | undefined;
|
|
214
|
+
/**
|
|
215
|
+
* Join session facts (ticket / project / execution host) onto each event by
|
|
216
|
+
* `sessionId`. A hint wins; otherwise pre-baked enriched fields (from a remote
|
|
217
|
+
* peer that already enriched its own stream) are preserved, and project/host
|
|
218
|
+
* fall back to what the event itself carries (`cwd`, `host`). Pure.
|
|
219
|
+
*/
|
|
220
|
+
export declare function enrichActivityEvents(events: EnrichedActivityEvent[], hints: ActivitySessionHint[]): EnrichedActivityEvent[];
|
|
221
|
+
/**
|
|
222
|
+
* Validate + host-tag one peer's `activity --json` payload for the fan-out
|
|
223
|
+
* merge. Mirrors {@link parseLine}: skip anything missing `event`/`sessionId`/
|
|
224
|
+
* `ts`, drop corrupt items. The event's own `host` is the execution host and is
|
|
225
|
+
* authoritative; only fall back to the dialed peer's `machine` when it's absent.
|
|
226
|
+
* Enriched fields the peer stamped (project/ticket/executionHost) ride through.
|
|
227
|
+
*/
|
|
228
|
+
export declare function parseActivityPayload(stdout: string, machine: string): EnrichedActivityEvent[];
|
|
229
|
+
/**
|
|
230
|
+
* Merge local + remote event lists, keeping the first copy of a given
|
|
231
|
+
* host/session/ts/event and sorting newest first. Cross-machine activity dirs
|
|
232
|
+
* can sync in a peer's own events, so the identity key dedupes those (mirrors
|
|
233
|
+
* `mergeFeedBlocks`).
|
|
234
|
+
*/
|
|
235
|
+
export declare function mergeActivityEvents(...groups: EnrichedActivityEvent[][]): EnrichedActivityEvent[];
|
|
236
|
+
export type ActivityGroupBy = 'project' | 'device' | 'agent';
|
|
237
|
+
/** One bucket of the grouped view. */
|
|
238
|
+
export interface ActivityGroup {
|
|
239
|
+
/** Grouping value; empty string for the "unknown" bucket. */
|
|
240
|
+
key: string;
|
|
241
|
+
/** Display label. */
|
|
242
|
+
label: string;
|
|
243
|
+
events: EnrichedActivityEvent[];
|
|
244
|
+
}
|
|
245
|
+
/** The (key, label) an event sorts under for a given grouping dimension. */
|
|
246
|
+
export declare function activityGroupKey(ev: EnrichedActivityEvent, by: ActivityGroupBy): {
|
|
247
|
+
key: string;
|
|
248
|
+
label: string;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Bucket events by project, device, or agent. Groups are ordered by event count
|
|
252
|
+
* (desc) then label; the "unknown" bucket always sorts last. Input order is
|
|
253
|
+
* preserved within a group, so newest-first survives when the input is sorted.
|
|
254
|
+
*/
|
|
255
|
+
export declare function groupActivity(events: EnrichedActivityEvent[], by: ActivityGroupBy): ActivityGroup[];
|
|
256
|
+
/**
|
|
257
|
+
* Narrow events to those whose project, device/host, agent, event kind, or
|
|
258
|
+
* ticket contains `filter` (case-insensitive substring). An empty filter is a
|
|
259
|
+
* no-op. Pure.
|
|
260
|
+
*/
|
|
261
|
+
export declare function filterActivityEvents(events: EnrichedActivityEvent[], filter: string): EnrichedActivityEvent[];
|
|
262
|
+
/** One rendered enriched line: the base activity line + `· project · ticket` tags. */
|
|
263
|
+
export declare function formatEnrichedActivityLine(ev: EnrichedActivityEvent, opts?: {
|
|
264
|
+
showHost?: boolean;
|
|
265
|
+
showProject?: boolean;
|
|
266
|
+
indent?: string;
|
|
267
|
+
}): string;
|
|
268
|
+
/** Human header for one grouped bucket: `<label> · N events · M milestones`. */
|
|
269
|
+
export declare function formatActivityGroupHeader(group: ActivityGroup): string;
|
|
114
270
|
/**
|
|
115
271
|
* The activity-log hook (Python), sibling to 10-feed-publish.py. Classifies
|
|
116
272
|
* PreToolUse/PostToolUse payloads into activity events and appends one JSONL
|
package/dist/lib/activity.js
CHANGED
|
@@ -23,6 +23,7 @@ import * as yaml from 'yaml';
|
|
|
23
23
|
import chalk from 'chalk';
|
|
24
24
|
import { relTime, truncate } from './format.js';
|
|
25
25
|
import { getActivityDir, getUserAgentsDir } from './state.js';
|
|
26
|
+
import { normalizeHost } from './machine-id.js';
|
|
26
27
|
/** The set of milestone events, for tier classification and ordering. */
|
|
27
28
|
export const MILESTONE_EVENTS = [
|
|
28
29
|
'plan.created',
|
|
@@ -42,6 +43,45 @@ const MILESTONE_SET = new Set(MILESTONE_EVENTS);
|
|
|
42
43
|
export function tierForEvent(event) {
|
|
43
44
|
return MILESTONE_SET.has(event) ? 'milestone' : 'activity';
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Coerce an unknown value into a clean {@link Attachment}[] — a fail-open reader
|
|
48
|
+
* over user/agent-written JSON. Drops entries without a usable `href`, clamps
|
|
49
|
+
* `kind` to a string, and keeps only primitive `meta` values.
|
|
50
|
+
*/
|
|
51
|
+
export function sanitizeAttachments(value) {
|
|
52
|
+
if (!Array.isArray(value))
|
|
53
|
+
return undefined;
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const raw of value) {
|
|
56
|
+
if (!raw || typeof raw !== 'object')
|
|
57
|
+
continue;
|
|
58
|
+
const a = raw;
|
|
59
|
+
const href = typeof a.href === 'string' ? a.href.trim() : '';
|
|
60
|
+
if (!href)
|
|
61
|
+
continue;
|
|
62
|
+
const att = {
|
|
63
|
+
kind: typeof a.kind === 'string' && a.kind.trim() ? a.kind.trim() : 'link',
|
|
64
|
+
href,
|
|
65
|
+
};
|
|
66
|
+
if (typeof a.name === 'string' && a.name.trim())
|
|
67
|
+
att.name = a.name.trim();
|
|
68
|
+
if (typeof a.mediaType === 'string' && a.mediaType.trim())
|
|
69
|
+
att.mediaType = a.mediaType.trim();
|
|
70
|
+
if (typeof a.bytes === 'number' && Number.isFinite(a.bytes) && a.bytes >= 0)
|
|
71
|
+
att.bytes = a.bytes;
|
|
72
|
+
if (a.meta && typeof a.meta === 'object' && !Array.isArray(a.meta)) {
|
|
73
|
+
const meta = {};
|
|
74
|
+
for (const [k, v] of Object.entries(a.meta)) {
|
|
75
|
+
if (typeof v === 'string' || typeof v === 'number')
|
|
76
|
+
meta[k] = v;
|
|
77
|
+
}
|
|
78
|
+
if (Object.keys(meta).length > 0)
|
|
79
|
+
att.meta = meta;
|
|
80
|
+
}
|
|
81
|
+
out.push(att);
|
|
82
|
+
}
|
|
83
|
+
return out.length > 0 ? out : undefined;
|
|
84
|
+
}
|
|
45
85
|
function activityPath(root, sessionId) {
|
|
46
86
|
const safe = sessionId.replace(/[^A-Za-z0-9._-]/g, '-');
|
|
47
87
|
if (!safe)
|
|
@@ -81,6 +121,7 @@ function parseLine(line) {
|
|
|
81
121
|
host: parsed.host ?? 'unknown',
|
|
82
122
|
runtime: parsed.runtime ?? 'headless',
|
|
83
123
|
cwd: parsed.cwd,
|
|
124
|
+
project: parsed.project,
|
|
84
125
|
agent: parsed.agent,
|
|
85
126
|
tool: parsed.tool,
|
|
86
127
|
detail: parsed.detail,
|
|
@@ -89,6 +130,7 @@ function parseLine(line) {
|
|
|
89
130
|
launchId: parsed.launchId,
|
|
90
131
|
terminalId: parsed.terminalId,
|
|
91
132
|
tmuxPane: parsed.tmuxPane,
|
|
133
|
+
attachments: sanitizeAttachments(parsed.attachments),
|
|
92
134
|
};
|
|
93
135
|
}
|
|
94
136
|
catch {
|
|
@@ -217,9 +259,11 @@ export function activityEventToRecord(ev) {
|
|
|
217
259
|
detail: ev.detail,
|
|
218
260
|
url: ev.url,
|
|
219
261
|
tier: ev.tier,
|
|
262
|
+
...(ev.project ? { project: ev.project } : {}),
|
|
220
263
|
...(ev.launchId ? { launchId: ev.launchId } : {}),
|
|
221
264
|
...(ev.terminalId ? { terminalId: ev.terminalId } : {}),
|
|
222
265
|
...(ev.tmuxPane ? { tmuxPane: ev.tmuxPane } : {}),
|
|
266
|
+
...(ev.attachments?.length ? { attachments: ev.attachments } : {}),
|
|
223
267
|
};
|
|
224
268
|
}
|
|
225
269
|
/** Read recent activity across sessions as unified {@link EventRecord}s. */
|
|
@@ -262,6 +306,244 @@ export function formatActivityLine(ev, opts = {}) {
|
|
|
262
306
|
return ` ${when} ${host}${label}${detail}${agent}${url}`;
|
|
263
307
|
}
|
|
264
308
|
// ---------------------------------------------------------------------------
|
|
309
|
+
// Rich progress render (RUSH-2014): `status.posted` posts are the deliberate,
|
|
310
|
+
// operator-facing announcements — a single truncated line under-serves them.
|
|
311
|
+
// `formatProgressUpdate` renders each as a multi-line row with full identity
|
|
312
|
+
// chips (agent · session · host · project) and any attached artifacts.
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
/** Glyph per attachment kind, so an artifact row reads at a glance. */
|
|
315
|
+
export const ATTACHMENT_GLYPH = {
|
|
316
|
+
image: '🖼',
|
|
317
|
+
audio: '♪',
|
|
318
|
+
video: '▶',
|
|
319
|
+
file: '📎',
|
|
320
|
+
link: '↗',
|
|
321
|
+
};
|
|
322
|
+
export function attachmentGlyph(kind) {
|
|
323
|
+
return ATTACHMENT_GLYPH[kind] ?? '📎';
|
|
324
|
+
}
|
|
325
|
+
/** Display name for an attachment: its `name`, else the basename of its href. */
|
|
326
|
+
export function attachmentName(att) {
|
|
327
|
+
if (att.name && att.name.trim())
|
|
328
|
+
return att.name.trim();
|
|
329
|
+
const href = att.href.replace(/[/\\]+$/, '');
|
|
330
|
+
const slash = Math.max(href.lastIndexOf('/'), href.lastIndexOf('\\'));
|
|
331
|
+
const base = slash >= 0 ? href.slice(slash + 1) : href;
|
|
332
|
+
return base || href;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Short session id for a chip: strip a known prefix (`session_`, `ses_`) then
|
|
336
|
+
* take the first 8 chars — enough to disambiguate, matching `ag sessions`.
|
|
337
|
+
*/
|
|
338
|
+
export function shortSessionId(sessionId) {
|
|
339
|
+
const stripped = sessionId.replace(/^(session_|ses_)/, '');
|
|
340
|
+
return stripped.slice(0, 8) || sessionId.slice(0, 8);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Render a deliberate progress update (`status.posted`) as an operator-facing,
|
|
344
|
+
* multi-line row with full identity — not a 60-char one-liner. Shape:
|
|
345
|
+
*
|
|
346
|
+
* ```
|
|
347
|
+
* ▸ update · 3m ago
|
|
348
|
+
* grok · 0108441e · yosemite-s1 · agents
|
|
349
|
+
* "CHANGELOG pushed; watching CI"
|
|
350
|
+
* ♪ draft.wav 🖼 cover.png
|
|
351
|
+
* ↳ ag focus 0108441e · ag sessions 0108441e
|
|
352
|
+
* ```
|
|
353
|
+
*
|
|
354
|
+
* Chips (agent, short session id, host, project, optional joined ticket/label)
|
|
355
|
+
* are shown only when known. Non-progress milestones keep {@link formatActivityLine}.
|
|
356
|
+
*/
|
|
357
|
+
export function formatProgressUpdate(ev, opts = {}) {
|
|
358
|
+
const shortId = shortSessionId(ev.sessionId);
|
|
359
|
+
const lines = [];
|
|
360
|
+
lines.push(` ${chalk.white('▸ update')} ${chalk.gray(`· ${relTime(ev.ts)}`)}`);
|
|
361
|
+
const chips = [
|
|
362
|
+
ev.agent,
|
|
363
|
+
shortId,
|
|
364
|
+
ev.host && ev.host !== 'unknown' ? ev.host : undefined,
|
|
365
|
+
ev.project,
|
|
366
|
+
opts.joined?.ticketId,
|
|
367
|
+
opts.joined?.label,
|
|
368
|
+
].filter((c) => Boolean(c));
|
|
369
|
+
if (chips.length > 0)
|
|
370
|
+
lines.push(` ${chalk.gray(chips.join(' · '))}`);
|
|
371
|
+
if (ev.detail)
|
|
372
|
+
lines.push(` ${chalk.white(`"${ev.detail}"`)}`);
|
|
373
|
+
if (ev.attachments?.length) {
|
|
374
|
+
const parts = ev.attachments.map((a) => `${attachmentGlyph(a.kind)} ${chalk.cyan(attachmentName(a))}`);
|
|
375
|
+
lines.push(` ${parts.join(' ')}`);
|
|
376
|
+
}
|
|
377
|
+
if (opts.joined?.prUrl)
|
|
378
|
+
lines.push(` ${chalk.gray(opts.joined.prUrl)}`);
|
|
379
|
+
lines.push(` ${chalk.dim(`↳ ag focus ${shortId} · ag sessions ${shortId}`)}`);
|
|
380
|
+
return lines.join('\n');
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Resolve a stable project/repo name from a working directory. A worktree cwd
|
|
384
|
+
* (`…/<repo>/.agents/worktrees/<slug>[/sub]`) resolves to the repo dir name so
|
|
385
|
+
* a worktree session groups with its own repo; any other path resolves to its
|
|
386
|
+
* basename. Pure — no filesystem access, so it works for remote events too.
|
|
387
|
+
*/
|
|
388
|
+
export function projectFromCwd(cwd) {
|
|
389
|
+
if (!cwd)
|
|
390
|
+
return undefined;
|
|
391
|
+
const norm = cwd.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
392
|
+
if (!norm)
|
|
393
|
+
return undefined;
|
|
394
|
+
const wtIdx = norm.indexOf('/.agents/worktrees/');
|
|
395
|
+
if (wtIdx > 0) {
|
|
396
|
+
const repoPath = norm.slice(0, wtIdx);
|
|
397
|
+
const base = repoPath.slice(repoPath.lastIndexOf('/') + 1);
|
|
398
|
+
if (base)
|
|
399
|
+
return base;
|
|
400
|
+
}
|
|
401
|
+
const base = norm.slice(norm.lastIndexOf('/') + 1);
|
|
402
|
+
return base || undefined;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Join session facts (ticket / project / execution host) onto each event by
|
|
406
|
+
* `sessionId`. A hint wins; otherwise pre-baked enriched fields (from a remote
|
|
407
|
+
* peer that already enriched its own stream) are preserved, and project/host
|
|
408
|
+
* fall back to what the event itself carries (`cwd`, `host`). Pure.
|
|
409
|
+
*/
|
|
410
|
+
export function enrichActivityEvents(events, hints) {
|
|
411
|
+
const bySession = new Map();
|
|
412
|
+
for (const h of hints)
|
|
413
|
+
if (h.sessionId)
|
|
414
|
+
bySession.set(h.sessionId, h);
|
|
415
|
+
return events.map((ev) => {
|
|
416
|
+
const hint = ev.sessionId ? bySession.get(ev.sessionId) : undefined;
|
|
417
|
+
const project = hint?.project ?? ev.project ?? projectFromCwd(ev.cwd);
|
|
418
|
+
const ticket = hint?.ticket ?? ev.ticket ?? undefined;
|
|
419
|
+
const executionHost = hint?.executionHost ?? ev.executionHost
|
|
420
|
+
?? (ev.host && ev.host !== 'unknown' ? ev.host : undefined);
|
|
421
|
+
return {
|
|
422
|
+
...ev,
|
|
423
|
+
...(project ? { project } : {}),
|
|
424
|
+
...(ticket ? { ticket } : {}),
|
|
425
|
+
...(executionHost ? { executionHost } : {}),
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Validate + host-tag one peer's `activity --json` payload for the fan-out
|
|
431
|
+
* merge. Mirrors {@link parseLine}: skip anything missing `event`/`sessionId`/
|
|
432
|
+
* `ts`, drop corrupt items. The event's own `host` is the execution host and is
|
|
433
|
+
* authoritative; only fall back to the dialed peer's `machine` when it's absent.
|
|
434
|
+
* Enriched fields the peer stamped (project/ticket/executionHost) ride through.
|
|
435
|
+
*/
|
|
436
|
+
export function parseActivityPayload(stdout, machine) {
|
|
437
|
+
let parsed;
|
|
438
|
+
try {
|
|
439
|
+
parsed = JSON.parse(stdout);
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
if (!Array.isArray(parsed))
|
|
445
|
+
return [];
|
|
446
|
+
const out = [];
|
|
447
|
+
for (const item of parsed) {
|
|
448
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
449
|
+
continue;
|
|
450
|
+
const ev = item;
|
|
451
|
+
if (!ev.event || !ev.sessionId || !ev.ts)
|
|
452
|
+
continue;
|
|
453
|
+
const host = ev.host && ev.host !== 'unknown' ? ev.host : machine;
|
|
454
|
+
out.push({ ...ev, host });
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Merge local + remote event lists, keeping the first copy of a given
|
|
460
|
+
* host/session/ts/event and sorting newest first. Cross-machine activity dirs
|
|
461
|
+
* can sync in a peer's own events, so the identity key dedupes those (mirrors
|
|
462
|
+
* `mergeFeedBlocks`).
|
|
463
|
+
*/
|
|
464
|
+
export function mergeActivityEvents(...groups) {
|
|
465
|
+
const byKey = new Map();
|
|
466
|
+
for (const ev of groups.flat()) {
|
|
467
|
+
const key = `${normalizeHost(ev.host)}\0${ev.sessionId}\0${ev.ts}\0${ev.event}`;
|
|
468
|
+
if (!byKey.has(key))
|
|
469
|
+
byKey.set(key, ev);
|
|
470
|
+
}
|
|
471
|
+
return [...byKey.values()].sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts));
|
|
472
|
+
}
|
|
473
|
+
/** The (key, label) an event sorts under for a given grouping dimension. */
|
|
474
|
+
export function activityGroupKey(ev, by) {
|
|
475
|
+
if (by === 'project') {
|
|
476
|
+
const p = ev.project ?? projectFromCwd(ev.cwd);
|
|
477
|
+
return p ? { key: p, label: p } : { key: '', label: 'unknown project' };
|
|
478
|
+
}
|
|
479
|
+
if (by === 'device') {
|
|
480
|
+
const h = ev.executionHost ?? (ev.host && ev.host !== 'unknown' ? ev.host : undefined);
|
|
481
|
+
return h ? { key: h, label: h } : { key: '', label: 'unknown device' };
|
|
482
|
+
}
|
|
483
|
+
const a = ev.agent;
|
|
484
|
+
return a ? { key: a, label: a } : { key: '', label: 'unknown agent' };
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Bucket events by project, device, or agent. Groups are ordered by event count
|
|
488
|
+
* (desc) then label; the "unknown" bucket always sorts last. Input order is
|
|
489
|
+
* preserved within a group, so newest-first survives when the input is sorted.
|
|
490
|
+
*/
|
|
491
|
+
export function groupActivity(events, by) {
|
|
492
|
+
const byKey = new Map();
|
|
493
|
+
for (const ev of events) {
|
|
494
|
+
const { key, label } = activityGroupKey(ev, by);
|
|
495
|
+
const g = byKey.get(key);
|
|
496
|
+
if (g)
|
|
497
|
+
g.events.push(ev);
|
|
498
|
+
else
|
|
499
|
+
byKey.set(key, { key, label, events: [ev] });
|
|
500
|
+
}
|
|
501
|
+
return [...byKey.values()].sort((a, b) => {
|
|
502
|
+
if (!a.key && b.key)
|
|
503
|
+
return 1;
|
|
504
|
+
if (!b.key && a.key)
|
|
505
|
+
return -1;
|
|
506
|
+
if (b.events.length !== a.events.length)
|
|
507
|
+
return b.events.length - a.events.length;
|
|
508
|
+
return a.label.localeCompare(b.label);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Narrow events to those whose project, device/host, agent, event kind, or
|
|
513
|
+
* ticket contains `filter` (case-insensitive substring). An empty filter is a
|
|
514
|
+
* no-op. Pure.
|
|
515
|
+
*/
|
|
516
|
+
export function filterActivityEvents(events, filter) {
|
|
517
|
+
const needle = filter.trim().toLowerCase();
|
|
518
|
+
if (!needle)
|
|
519
|
+
return events;
|
|
520
|
+
return events.filter((ev) => {
|
|
521
|
+
const fields = [ev.project, ev.executionHost, ev.host, ev.agent, ev.event, ev.ticket];
|
|
522
|
+
return fields.some((f) => typeof f === 'string' && f.toLowerCase().includes(needle));
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
/** One rendered enriched line: the base activity line + `· project · ticket` tags. */
|
|
526
|
+
export function formatEnrichedActivityLine(ev, opts = {}) {
|
|
527
|
+
const base = formatActivityLine(ev, { showHost: opts.showHost });
|
|
528
|
+
const tags = [];
|
|
529
|
+
if (opts.showProject && ev.project)
|
|
530
|
+
tags.push(ev.project);
|
|
531
|
+
if (ev.ticket)
|
|
532
|
+
tags.push(ev.ticket);
|
|
533
|
+
const suffix = tags.length > 0 ? chalk.gray(` · ${tags.join(' · ')}`) : '';
|
|
534
|
+
return `${opts.indent ?? ''}${base}${suffix}`;
|
|
535
|
+
}
|
|
536
|
+
/** Human header for one grouped bucket: `<label> · N events · M milestones`. */
|
|
537
|
+
export function formatActivityGroupHeader(group) {
|
|
538
|
+
const { milestones } = collapseActivity(group.events);
|
|
539
|
+
const n = group.events.length;
|
|
540
|
+
const m = milestones.length;
|
|
541
|
+
const parts = [`${n} event${n === 1 ? '' : 's'}`];
|
|
542
|
+
if (m > 0)
|
|
543
|
+
parts.push(`${m} milestone${m === 1 ? '' : 's'}`);
|
|
544
|
+
return `${group.label} · ${parts.join(' · ')}`;
|
|
545
|
+
}
|
|
546
|
+
// ---------------------------------------------------------------------------
|
|
265
547
|
// Hook installation
|
|
266
548
|
// ---------------------------------------------------------------------------
|
|
267
549
|
/**
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -32,13 +32,17 @@ export declare function findInPath(command: string): string | null;
|
|
|
32
32
|
* single source of truth for agent metadata consumed throughout the codebase.
|
|
33
33
|
*/
|
|
34
34
|
export declare const AGENTS: Record<AgentId, AgentConfig>;
|
|
35
|
-
/** All
|
|
35
|
+
/** All current and legacy agent IDs derived from the AGENTS registry. */
|
|
36
36
|
export declare const ALL_AGENT_IDS: AgentId[];
|
|
37
|
+
/** Agents retained only for legacy reads, not install/import/sync targets. */
|
|
38
|
+
export declare const HARD_DEPRECATED_AGENT_IDS: AgentId[];
|
|
39
|
+
/** Agents that can receive managed installs, imports, and resource sync writes. */
|
|
40
|
+
export declare const MANAGED_AGENT_IDS: AgentId[];
|
|
37
41
|
/**
|
|
38
42
|
* A self-updating agent is a single global binary installed by an official
|
|
39
43
|
* `curl … | sh` / `brew install` script that carries NO version token — the
|
|
40
44
|
* installer can only ever fetch the *current* release, and the binary then keeps
|
|
41
|
-
* itself up to date in place (droid, grok, antigravity, cursor, hermes,
|
|
45
|
+
* itself up to date in place (droid, grok, antigravity, cursor, hermes,
|
|
42
46
|
* kiro, goose). There is no semver to pin, so agents-cli must not model these as
|
|
43
47
|
* having multiple installable version-homes the way it does for npm-packaged
|
|
44
48
|
* agents (claude, codex, kimi, …).
|
|
@@ -55,6 +59,7 @@ export declare const ALL_AGENT_IDS: AgentId[];
|
|
|
55
59
|
* never a scattered `agent === 'droid'`.
|
|
56
60
|
*/
|
|
57
61
|
export declare function isSelfUpdatingAgent(agent: AgentId): boolean;
|
|
62
|
+
export declare function isAgentHardDeprecated(agent: AgentId): boolean;
|
|
58
63
|
/** Get the chalk color function for an agent. Works for any AgentId or SessionAgentId. */
|
|
59
64
|
export declare function colorAgent(agentId: string): (s: string) => string;
|
|
60
65
|
/** Return the agent's display name, colored. */
|
|
@@ -383,6 +388,8 @@ export declare function isAgentName(input: string): boolean;
|
|
|
383
388
|
* capturing stdout. Lines are plain (uncolored) text.
|
|
384
389
|
*/
|
|
385
390
|
export declare function deprecationNotice(agent: AgentId): string[] | null;
|
|
391
|
+
export declare function hardDeprecationNotice(agent: AgentId): string[] | null;
|
|
392
|
+
export declare function hardDeprecationError(agent: AgentId): string;
|
|
386
393
|
/**
|
|
387
394
|
* Print a deprecation warning (yellow) if the agent's registry entry carries a
|
|
388
395
|
* `deprecated` marker; no-op otherwise. Call from any user entry point that
|