@phnx-labs/agents-cli 1.20.76 → 1.20.77

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 (85) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/dist/bin/agents +0 -0
  3. package/dist/commands/exec.js +58 -16
  4. package/dist/commands/routines.js +271 -13
  5. package/dist/commands/secrets.js +59 -17
  6. package/dist/commands/sessions-browser.js +11 -1
  7. package/dist/commands/sessions-picker.d.ts +14 -1
  8. package/dist/commands/sessions-picker.js +168 -15
  9. package/dist/commands/sessions.d.ts +23 -4
  10. package/dist/commands/sessions.js +128 -38
  11. package/dist/commands/ssh.d.ts +13 -0
  12. package/dist/commands/ssh.js +39 -2
  13. package/dist/index.js +2 -1
  14. package/dist/lib/activity.d.ts +5 -4
  15. package/dist/lib/activity.js +450 -36
  16. package/dist/lib/cloud/session-index.js +2 -2
  17. package/dist/lib/daemon.d.ts +10 -1
  18. package/dist/lib/daemon.js +99 -12
  19. package/dist/lib/devices/fleet.d.ts +16 -1
  20. package/dist/lib/devices/fleet.js +10 -2
  21. package/dist/lib/events.d.ts +1 -1
  22. package/dist/lib/exec.d.ts +16 -0
  23. package/dist/lib/exec.js +27 -4
  24. package/dist/lib/feed.d.ts +7 -1
  25. package/dist/lib/feed.js +96 -6
  26. package/dist/lib/heal.d.ts +7 -4
  27. package/dist/lib/heal.js +10 -22
  28. package/dist/lib/hosts/dispatch.d.ts +9 -0
  29. package/dist/lib/hosts/dispatch.js +24 -4
  30. package/dist/lib/hosts/passthrough.js +7 -2
  31. package/dist/lib/hosts/remote-cmd.d.ts +11 -0
  32. package/dist/lib/hosts/remote-cmd.js +31 -8
  33. package/dist/lib/hosts/remote-session-id.d.ts +45 -0
  34. package/dist/lib/hosts/remote-session-id.js +84 -0
  35. package/dist/lib/hosts/run-target.d.ts +4 -0
  36. package/dist/lib/hosts/run-target.js +5 -1
  37. package/dist/lib/hosts/session-index.js +3 -3
  38. package/dist/lib/menubar/install-menubar.d.ts +9 -0
  39. package/dist/lib/menubar/install-menubar.js +14 -0
  40. package/dist/lib/menubar/notify-desktop.d.ts +44 -0
  41. package/dist/lib/menubar/notify-desktop.js +78 -0
  42. package/dist/lib/overdue.d.ts +4 -5
  43. package/dist/lib/overdue.js +8 -41
  44. package/dist/lib/routine-notify.d.ts +76 -0
  45. package/dist/lib/routine-notify.js +190 -0
  46. package/dist/lib/routines-placement.d.ts +38 -0
  47. package/dist/lib/routines-placement.js +79 -0
  48. package/dist/lib/routines-project.d.ts +97 -0
  49. package/dist/lib/routines-project.js +349 -0
  50. package/dist/lib/routines.d.ts +68 -0
  51. package/dist/lib/routines.js +74 -7
  52. package/dist/lib/runner.d.ts +12 -2
  53. package/dist/lib/runner.js +150 -25
  54. package/dist/lib/sandbox.js +10 -0
  55. package/dist/lib/secrets/account-token.d.ts +20 -0
  56. package/dist/lib/secrets/account-token.js +64 -0
  57. package/dist/lib/secrets/agent.d.ts +9 -4
  58. package/dist/lib/secrets/agent.js +30 -20
  59. package/dist/lib/secrets/bundles.d.ts +20 -6
  60. package/dist/lib/secrets/bundles.js +70 -34
  61. package/dist/lib/secrets/index.d.ts +11 -2
  62. package/dist/lib/secrets/index.js +21 -9
  63. package/dist/lib/secrets/session-store.d.ts +6 -2
  64. package/dist/lib/secrets/session-store.js +65 -15
  65. package/dist/lib/secrets/unlock-hints.d.ts +27 -0
  66. package/dist/lib/secrets/unlock-hints.js +36 -0
  67. package/dist/lib/session/active.d.ts +10 -0
  68. package/dist/lib/session/active.js +67 -4
  69. package/dist/lib/session/db.d.ts +6 -0
  70. package/dist/lib/session/db.js +83 -6
  71. package/dist/lib/session/discover.d.ts +13 -1
  72. package/dist/lib/session/discover.js +91 -3
  73. package/dist/lib/session/parse.js +11 -2
  74. package/dist/lib/session/prompt.d.ts +7 -0
  75. package/dist/lib/session/prompt.js +37 -0
  76. package/dist/lib/session/provenance.d.ts +7 -0
  77. package/dist/lib/session/render.js +18 -16
  78. package/dist/lib/session/state.d.ts +4 -0
  79. package/dist/lib/session/state.js +93 -11
  80. package/dist/lib/session/types.d.ts +25 -1
  81. package/dist/lib/session/types.js +8 -0
  82. package/dist/lib/state.d.ts +7 -3
  83. package/dist/lib/state.js +20 -5
  84. package/dist/lib/types.d.ts +10 -0
  85. package/package.json +1 -1
@@ -14,6 +14,7 @@
14
14
  * markers. Codex has no such tools, so it falls back to last-role + question
15
15
  * shape + mtime — same function, driven off the normalized events.
16
16
  */
17
+ import * as path from 'path';
17
18
  import { summarizeToolUse } from './parse.js';
18
19
  /**
19
20
  * Detect per-session rate-limit / usage-limit signals in assistant or error
@@ -47,9 +48,9 @@ const PROSE_QUESTION_FRESH_MS = 30 * 60_000;
47
48
  /** Claude tool names that structurally mean "the agent handed control back to you". */
48
49
  const PLAN_TOOL = 'ExitPlanMode';
49
50
  const ASK_TOOL = 'AskUserQuestion';
50
- /** The live plan/checklist tools: Claude's `TodoWrite` and Codex's `update_plan`. */
51
- const TODO_TOOL = 'TodoWrite';
52
- const CODEX_PLAN_TOOL = 'update_plan';
51
+ const SNAPSHOT_TODO_TOOLS = new Set(['TodoWrite', 'todo_write', 'update_plan']);
52
+ const TASK_CREATE_TOOL = 'TaskCreate';
53
+ const TASK_UPDATE_TOOL = 'TaskUpdate';
53
54
  /**
54
55
  * Derive live plan progress from a checklist tool call's args. Accepts both
55
56
  * Claude's `TodoWrite` (`todos: [{content,status,activeForm}]`) and Codex's
@@ -58,10 +59,11 @@ const CODEX_PLAN_TOOL = 'update_plan';
58
59
  * list, so a session with no plan carries no `todos` field.
59
60
  */
60
61
  export function extractTodoProgress(args) {
61
- const raw = Array.isArray(args?.todos)
62
- ? args.todos
63
- : Array.isArray(args?.plan)
64
- ? args.plan
62
+ const input = args?.input && typeof args.input === 'object' ? args.input : args;
63
+ const raw = Array.isArray(input?.todos)
64
+ ? input.todos
65
+ : Array.isArray(input?.plan)
66
+ ? input.plan
65
67
  : undefined;
66
68
  if (!Array.isArray(raw) || raw.length === 0)
67
69
  return undefined;
@@ -78,7 +80,8 @@ export function extractTodoProgress(args) {
78
80
  if (!content)
79
81
  continue;
80
82
  const status = t?.status === 'completed' || t?.status === 'in_progress' ? t.status : 'pending';
81
- items.push(activeForm ? { content, status, activeForm } : { content, status });
83
+ const description = typeof t?.description === 'string' && t.description ? t.description : undefined;
84
+ items.push({ content, status, ...(description ? { description } : {}), ...(activeForm ? { activeForm } : {}) });
82
85
  }
83
86
  if (items.length === 0)
84
87
  return undefined;
@@ -91,6 +94,86 @@ export function extractTodoProgress(args) {
91
94
  activeForm: inProgress ? inProgress.activeForm ?? inProgress.content : undefined,
92
95
  };
93
96
  }
97
+ /** Fold snapshot checklist tools and Claude TaskCreate/TaskUpdate event logs. */
98
+ export function extractTodoProgressFromEvents(events) {
99
+ let items = [];
100
+ let nextTaskId = 1;
101
+ let sawChecklist = false;
102
+ for (const event of events) {
103
+ if (event.type !== 'tool_use')
104
+ continue;
105
+ const args = event.args ?? {};
106
+ if (SNAPSHOT_TODO_TOOLS.has(event.tool ?? '')) {
107
+ const input = args.input && typeof args.input === 'object' ? args.input : args;
108
+ const raw = Array.isArray(input.todos) ? input.todos : Array.isArray(input.plan) ? input.plan : undefined;
109
+ if (raw) {
110
+ items = raw.map((item) => ({ ...item }));
111
+ sawChecklist = true;
112
+ }
113
+ continue;
114
+ }
115
+ if (event.tool === TASK_CREATE_TOOL) {
116
+ const content = args.subject || args.description;
117
+ if (typeof content !== 'string' || !content.trim())
118
+ continue;
119
+ items.push({
120
+ taskId: String(nextTaskId++),
121
+ content: content.trim(),
122
+ description: typeof args.description === 'string' ? args.description : undefined,
123
+ activeForm: typeof args.activeForm === 'string' ? args.activeForm : undefined,
124
+ status: 'pending',
125
+ });
126
+ sawChecklist = true;
127
+ continue;
128
+ }
129
+ if (event.tool === TASK_UPDATE_TOOL) {
130
+ const taskId = String(args.taskId ?? args.task_id ?? '');
131
+ const index = items.findIndex(item => String(item.taskId ?? '') === taskId);
132
+ if (index < 0)
133
+ continue;
134
+ if (args.status === 'deleted') {
135
+ items.splice(index, 1);
136
+ continue;
137
+ }
138
+ const prior = items[index];
139
+ items[index] = {
140
+ ...prior,
141
+ ...(typeof args.subject === 'string' ? { content: args.subject } : {}),
142
+ ...(typeof args.description === 'string' ? { description: args.description } : {}),
143
+ ...(typeof args.activeForm === 'string' ? { activeForm: args.activeForm } : {}),
144
+ ...(typeof args.status === 'string' ? { status: args.status } : {}),
145
+ };
146
+ }
147
+ }
148
+ return sawChecklist ? extractTodoProgress({ todos: items }) : undefined;
149
+ }
150
+ /** Derive a recency-ordered, de-duplicated list of directories touched by tools. */
151
+ export function extractRecentDirectoriesTouched(events, cwd) {
152
+ const dirs = [];
153
+ const add = (value, file = false) => {
154
+ if (typeof value !== 'string' || !value.trim())
155
+ return;
156
+ const resolved = path.isAbsolute(value) ? value : path.resolve(cwd || process.cwd(), value);
157
+ const dir = file ? path.dirname(resolved) : resolved;
158
+ const old = dirs.indexOf(dir);
159
+ if (old >= 0)
160
+ dirs.splice(old, 1);
161
+ dirs.push(dir);
162
+ };
163
+ for (const event of events) {
164
+ if (event.type !== 'tool_use')
165
+ continue;
166
+ const tool = event.tool ?? '';
167
+ const args = event.args ?? {};
168
+ if (['Edit', 'Write', 'edit_file', 'write_file', 'create_file', 'edit', 'write'].includes(tool)) {
169
+ add(args.file_path ?? args.filePath ?? args.path ?? event.path, true);
170
+ }
171
+ else if (['Bash', 'exec_command', 'run_shell_command', 'shell', 'Execute'].includes(tool)) {
172
+ add(args.cwd ?? args.Cwd ?? args.workdir ?? args.working_directory ?? cwd);
173
+ }
174
+ }
175
+ return dirs.length ? dirs.slice(-10) : undefined;
176
+ }
94
177
  /** Trailing '?' or a leading interrogative — a question aimed at the user. */
95
178
  const QUESTION_TRAILING = /\?["'”)\]]?\s*$/;
96
179
  const QUESTION_PHRASE = /\b(shall i|should i|do you want|would you like|which (?:one|option|approach|of)|can you (?:confirm|clarify)|please (?:confirm|clarify|advise)|let me know|are you (?:ok|okay|sure)|proceed\?)\b/i;
@@ -325,11 +408,10 @@ export function inferActivity(events, ctx = {}) {
325
408
  .slice(-3)
326
409
  .map(e => oneLine(e.content ?? ''))
327
410
  .filter(Boolean);
328
- // Live plan progress: the most recent TodoWrite's checklist (RUSH-1380). Attached
411
+ // Live plan progress: snapshot checklist tools or the folded Task* event log. Attached
329
412
  // to `base` so every return path below carries it — a working, waiting, or idle
330
413
  // session all keep showing how far the plan got.
331
- const lastTodo = lastOf(meaningful, e => e.type === 'tool_use' && (e.tool === TODO_TOOL || e.tool === CODEX_PLAN_TOOL));
332
- const todos = lastTodo ? extractTodoProgress(lastTodo.args) : undefined;
414
+ const todos = extractTodoProgressFromEvents(meaningful);
333
415
  const base = {
334
416
  activity: 'idle',
335
417
  lastRole: lastMsg?.role,
@@ -10,6 +10,12 @@
10
10
  export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'antigravity' | 'opencode' | 'openclaw' | 'rush' | 'hermes' | 'grok' | 'kimi' | 'droid';
11
11
  /** All agents with session discovery support, in display order. */
12
12
  export declare const SESSION_AGENTS: SessionAgentId[];
13
+ /**
14
+ * True when `agent` stores session data `agents sessions` can discover (a member
15
+ * of {@link SESSION_AGENTS}). The single predicate every session-index writer
16
+ * gates on, so "is this a trackable agent?" is decided in exactly one place.
17
+ */
18
+ export declare function isSessionTrackedAgent(agent: string): agent is SessionAgentId;
13
19
  /** A single normalized event within a session (message, tool call, thinking, etc.). */
14
20
  export interface SessionEvent {
15
21
  type: 'message' | 'tool_use' | 'tool_result' | 'thinking' | 'error' | 'init' | 'result' | 'usage' | 'attachment';
@@ -25,6 +31,16 @@ export interface SessionEvent {
25
31
  output?: string;
26
32
  /** Internal: marks tool_use events from local commands */
27
33
  _local?: boolean;
34
+ /**
35
+ * Internal: marks a `role=user` message that is harness-injected scaffolding
36
+ * (Claude `<bash-input>`/`<bash-stdout>` from `!`-prefix runs, `<system-reminder>`,
37
+ * `<task-notification>`, `<command-*>` wrappers, `[Request interrupted]`, skill
38
+ * bodies, hook feedback) rather than a genuine user turn. Set centrally in
39
+ * `parseSession` via `isSyntheticUserMessage`. Such events are excluded from
40
+ * `--include user` and are not counted as turn starts by `--first`/`--last`,
41
+ * but stay in the default full stream so `--markdown` keeps full fidelity.
42
+ */
43
+ _synthetic?: boolean;
28
44
  model?: string;
29
45
  inputTokens?: number;
30
46
  outputTokens?: number;
@@ -41,10 +57,12 @@ export interface SessionAttachment {
41
57
  mediaType: string;
42
58
  sizeBytes?: number;
43
59
  }
44
- /** One checklist item, as Claude's `TodoWrite` (`content`) or Codex's `update_plan` (`step`) emits it. */
60
+ /** One normalized checklist/task item emitted by any transcript harness. */
45
61
  export interface TodoItem {
46
62
  content: string;
47
63
  status: 'pending' | 'in_progress' | 'completed';
64
+ /** Optional longer explanation supplied by task-based harnesses. */
65
+ description?: string;
48
66
  /** Present-continuous label shown while this item is the active step. */
49
67
  activeForm?: string;
50
68
  }
@@ -152,6 +170,12 @@ export interface SessionMeta {
152
170
  * instead of re-parsing the transcript. Absent when the session wrote no list.
153
171
  */
154
172
  todos?: TodoProgress;
173
+ /** Most-recent unique directories changed or used as a shell working directory. */
174
+ recentDirectoriesTouched?: string[];
175
+ /** Linear project containing ticketId, resolved lazily and cached in SQLite. */
176
+ linearProject?: string;
177
+ /** Browser URL for linearProject. */
178
+ linearProjectUrl?: string;
155
179
  /**
156
180
  * True when the session was spawned programmatically (SDK entrypoint) rather
157
181
  * than by a human at the Claude CLI. Captured at scan time from the JSONL
@@ -8,3 +8,11 @@
8
8
  */
9
9
  /** All agents with session discovery support, in display order. */
10
10
  export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'antigravity', 'opencode', 'openclaw', 'rush', 'hermes', 'grok', 'kimi', 'droid'];
11
+ /**
12
+ * True when `agent` stores session data `agents sessions` can discover (a member
13
+ * of {@link SESSION_AGENTS}). The single predicate every session-index writer
14
+ * gates on, so "is this a trackable agent?" is decided in exactly one place.
15
+ */
16
+ export function isSessionTrackedAgent(agent) {
17
+ return SESSION_AGENTS.includes(agent);
18
+ }
@@ -121,9 +121,13 @@ export declare function getSystemRoutinesDir(): string;
121
121
  * Path to a project-scoped routines directory (`<project>/.agents/routines/`),
122
122
  * or null when no project `.agents/` is found by walking up from cwd.
123
123
  *
124
- * Project routines participate in `list`/`view`/`run` for inspection but are
125
- * NOT fired by the daemon (which runs from $HOME and loads only user + system
126
- * routines). Opt-in firing for project routines is tracked as a follow-up.
124
+ * Project routines participate in `list`/`view` for inspection always. Daemon
125
+ * firing requires an explicit opt-in via `agents routines enable-project`
126
+ * (writes `meta.routines.projects`); after opt-in, `agents routines sync`
127
+ * materialises them into the user layer with `source:` provenance so the
128
+ * daemon (which loads user + system only) can fire them safely. A project's
129
+ * own `agents.yaml` `routines.enable: true` is a documentation signal only —
130
+ * it does not auto-enable firing. See `lib/routines-project.ts`.
127
131
  */
128
132
  export declare function getProjectRoutinesDir(cwd?: string): string | null;
129
133
  /** Path to routine execution logs (~/.agents/.history/runs/). */
package/dist/lib/state.js CHANGED
@@ -319,9 +319,13 @@ export function getSystemRoutinesDir() { return SYSTEM_ROUTINES_DIR; }
319
319
  * Path to a project-scoped routines directory (`<project>/.agents/routines/`),
320
320
  * or null when no project `.agents/` is found by walking up from cwd.
321
321
  *
322
- * Project routines participate in `list`/`view`/`run` for inspection but are
323
- * NOT fired by the daemon (which runs from $HOME and loads only user + system
324
- * routines). Opt-in firing for project routines is tracked as a follow-up.
322
+ * Project routines participate in `list`/`view` for inspection always. Daemon
323
+ * firing requires an explicit opt-in via `agents routines enable-project`
324
+ * (writes `meta.routines.projects`); after opt-in, `agents routines sync`
325
+ * materialises them into the user layer with `source:` provenance so the
326
+ * daemon (which loads user + system only) can fire them safely. A project's
327
+ * own `agents.yaml` `routines.enable: true` is a documentation signal only —
328
+ * it does not auto-enable firing. See `lib/routines-project.ts`.
325
329
  */
326
330
  export function getProjectRoutinesDir(cwd = process.cwd()) {
327
331
  const projectAgentsDir = getProjectAgentsDir(cwd);
@@ -383,10 +387,21 @@ export function getTeamsDir() { return TEAMS_DIR; }
383
387
  export function getTeamsAgentsDir() { return TEAMS_AGENTS_DIR; }
384
388
  /** Path to the team registry — list of named teams with timestamps. Durable runtime, per-machine. */
385
389
  export function getTeamsRegistryPath() { return path.join(HISTORY_DIR, 'teams', 'registry.json'); }
390
+ /**
391
+ * The devices dir (registry + ignore-list live here). Read at CALL time so a
392
+ * test can redirect it to a temp dir via AGENTS_DEVICES_DIR without racing the
393
+ * module-load capture of HISTORY_DIR — mirrors the AGENTS_EVENTS_PATH /
394
+ * AGENTS_SECRETS_AGENT_DIR test-isolation escape hatches. Never set in
395
+ * production code; it exists so the vitest fork's device-registry writes can
396
+ * never reach the user's real ~/.agents/.history/devices (RUSH-2042).
397
+ */
398
+ function getDevicesDir() {
399
+ return process.env.AGENTS_DEVICES_DIR ?? path.join(HISTORY_DIR, 'devices');
400
+ }
386
401
  /** Path to the device registry — SSH device profiles with platform/auth metadata. Durable runtime, per-machine (host list + addresses are NOT pulled by `agents repo push`). */
387
- export function getDevicesRegistryPath() { return path.join(HISTORY_DIR, 'devices', 'registry.json'); }
402
+ export function getDevicesRegistryPath() { return path.join(getDevicesDir(), 'registry.json'); }
388
403
  /** Path to the device ignore-list — tailscale node names the user dismissed, so auto-discovery never re-suggests them. Per-machine, same dir as the registry. */
389
- export function getDevicesIgnoredPath() { return path.join(HISTORY_DIR, 'devices', 'ignored.json'); }
404
+ export function getDevicesIgnoredPath() { return path.join(getDevicesDir(), 'ignored.json'); }
390
405
  /** Dir of "pending device" sentinels (~/.agents/.cache/state/devices-pending/) — one empty-ish file per newly-discovered, not-yet-approved tailnet node. Written by the daemon probe, read by the menu-bar helper (mirrors the attention sentinel dir). */
391
406
  export function getDevicesPendingDir() { return path.join(RUNTIME_STATE_DIR, 'devices-pending'); }
392
407
  /** Path to cloud dispatch cache (~/.agents/.cache/cloud/). */
@@ -866,6 +866,16 @@ export interface Meta {
866
866
  };
867
867
  transports?: Record<string, string>;
868
868
  };
869
+ /**
870
+ * Routines daemon settings. `projects` is the allowlist of project roots
871
+ * whose `.agents/routines/*.yml` may be synced into `~/.agents/routines/`
872
+ * and fired by the daemon — never auto-discovered from a cloned public
873
+ * repo without an explicit opt-in (`agents routines enable-project`).
874
+ * Paths are stored absolute (or home-relative `~/…`).
875
+ */
876
+ routines?: {
877
+ projects?: string[];
878
+ };
869
879
  }
870
880
  /** Persisted agent-host entry in agents.yaml (overlay or inline). */
871
881
  export interface HostEntry {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.76",
3
+ "version": "1.20.77",
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",