@phnx-labs/agents-cli 1.21.3 → 1.22.1
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 +136 -0
- package/README.md +32 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.js +1 -0
- package/dist/commands/doctor.js +34 -2
- package/dist/commands/exec.d.ts +27 -0
- package/dist/commands/exec.js +123 -6
- package/dist/commands/models.js +36 -1
- package/dist/commands/projects.js +120 -62
- package/dist/commands/sessions-backfill.d.ts +32 -0
- package/dist/commands/sessions-backfill.js +186 -0
- package/dist/commands/sessions.d.ts +17 -1
- package/dist/commands/sessions.js +317 -18
- package/dist/commands/teams.js +1 -1
- package/dist/commands/worktree.d.ts +3 -3
- package/dist/commands/worktree.js +35 -4
- package/dist/lib/app-bundle-install.d.ts +17 -0
- package/dist/lib/app-bundle-install.js +94 -0
- package/dist/lib/daemon.d.ts +5 -1
- package/dist/lib/daemon.js +63 -14
- package/dist/lib/devices/doctor-findings.js +12 -4
- package/dist/lib/devices/doctor-overview-cache.d.ts +45 -0
- package/dist/lib/devices/doctor-overview-cache.js +168 -0
- package/dist/lib/devices/fleet.js +7 -2
- package/dist/lib/devices/resolve-target.d.ts +6 -0
- package/dist/lib/devices/resolve-target.js +9 -3
- package/dist/lib/devices/self-host.d.ts +9 -0
- package/dist/lib/devices/self-host.js +61 -0
- package/dist/lib/exec.js +39 -8
- package/dist/lib/hosts/dispatch.d.ts +12 -0
- package/dist/lib/hosts/dispatch.js +23 -6
- package/dist/lib/hosts/passthrough.js +8 -6
- package/dist/lib/hosts/reconnect.d.ts +38 -0
- package/dist/lib/hosts/reconnect.js +85 -4
- package/dist/lib/hosts/run-target.js +14 -2
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.js +27 -23
- package/dist/lib/model-tiers.d.ts +54 -0
- package/dist/lib/model-tiers.js +229 -0
- package/dist/lib/models.d.ts +3 -0
- package/dist/lib/models.js +44 -7
- package/dist/lib/pricing/prices.json +16 -1
- package/dist/lib/project-focus.d.ts +42 -0
- package/dist/lib/project-focus.js +80 -0
- package/dist/lib/project-schedule.d.ts +75 -0
- package/dist/lib/project-schedule.js +110 -0
- package/dist/lib/project-status.d.ts +7 -0
- package/dist/lib/project-status.js +9 -0
- package/dist/lib/projects.d.ts +11 -2
- package/dist/lib/projects.js +57 -9
- package/dist/lib/redact.d.ts +2 -0
- package/dist/lib/redact.js +22 -0
- package/dist/lib/remote-agents-json.d.ts +2 -0
- package/dist/lib/remote-agents-json.js +3 -3
- package/dist/lib/rotate.d.ts +84 -1
- package/dist/lib/rotate.js +155 -5
- package/dist/lib/runner.d.ts +4 -2
- package/dist/lib/runner.js +13 -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/install-helper.js +28 -31
- package/dist/lib/session/bash-command.js +60 -9
- package/dist/lib/session/db.d.ts +7 -1
- package/dist/lib/session/db.js +301 -32
- package/dist/lib/session/discover.d.ts +40 -7
- package/dist/lib/session/discover.js +144 -83
- package/dist/lib/session/parse.d.ts +8 -1
- package/dist/lib/session/parse.js +83 -32
- package/dist/lib/session/remote-list.d.ts +71 -0
- package/dist/lib/session/remote-list.js +410 -2
- package/dist/lib/session/shell-programs.d.ts +15 -0
- package/dist/lib/session/shell-programs.js +359 -0
- package/dist/lib/session/tool-calls.d.ts +88 -0
- package/dist/lib/session/tool-calls.js +612 -0
- package/dist/lib/session/tool-index.d.ts +100 -0
- package/dist/lib/session/tool-index.js +773 -0
- package/dist/lib/session/tool-store.d.ts +15 -0
- package/dist/lib/session/tool-store.js +198 -0
- package/dist/lib/session/types.d.ts +7 -0
- package/dist/lib/state.d.ts +10 -1
- package/dist/lib/state.js +11 -2
- package/dist/lib/teams/remoteWorktree.d.ts +3 -4
- package/dist/lib/teams/remoteWorktree.js +3 -4
- package/dist/lib/teams/worktree.d.ts +11 -1
- package/dist/lib/teams/worktree.js +42 -4
- package/dist/lib/types.d.ts +17 -0
- package/dist/lib/types.js +17 -0
- package/dist/lib/versions.js +69 -22
- package/package.json +3 -1
|
@@ -18,6 +18,7 @@ import { createHash } from 'crypto';
|
|
|
18
18
|
import * as fs from 'fs';
|
|
19
19
|
import * as os from 'os';
|
|
20
20
|
import * as path from 'path';
|
|
21
|
+
import { copyAppBundle, withInstallLock } from '../app-bundle-install.js';
|
|
21
22
|
const APP_BUNDLE_NAME = 'Agents CLI.app';
|
|
22
23
|
const INSTALL_DIR_NAME = 'agents-cli';
|
|
23
24
|
let installRootOverride = null;
|
|
@@ -107,19 +108,6 @@ function spctlAssess(appPath) {
|
|
|
107
108
|
});
|
|
108
109
|
return { ok: r.status === 0, output: (r.stderr || r.stdout || '').toString().trim() };
|
|
109
110
|
}
|
|
110
|
-
function copyAppBundle(src, dest) {
|
|
111
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
112
|
-
if (fs.existsSync(dest))
|
|
113
|
-
fs.rmSync(dest, { recursive: true, force: true });
|
|
114
|
-
// `cp -R` preserves the bundle's signature, symlinks, and resource forks.
|
|
115
|
-
// `fs.cpSync({recursive: true})` works on simple trees but has historically
|
|
116
|
-
// mishandled extended attributes on `.app` bundles, breaking codesign.
|
|
117
|
-
const r = spawnSync('cp', ['-R', src, dest], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
|
|
118
|
-
if (r.status !== 0) {
|
|
119
|
-
const msg = (r.stderr || r.stdout || '').toString().trim();
|
|
120
|
-
throw new Error(`Failed to copy ${src} -> ${dest}: ${msg || 'unknown error'}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
111
|
/**
|
|
124
112
|
* Idempotent install. Copies the bundled `.app` to the stable user path. Skips
|
|
125
113
|
* if the destination already exists, `codesign --verify` passes, AND the
|
|
@@ -135,25 +123,34 @@ function copyAppBundle(src, dest) {
|
|
|
135
123
|
export function ensureKeychainHelperInstalled(opts = {}) {
|
|
136
124
|
assertDarwin();
|
|
137
125
|
const dest = installedAppPath();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
126
|
+
const upToDate = () => fs.existsSync(dest) && codesignVerify(dest).ok && !installedHelperIsStale();
|
|
127
|
+
// Fast path: already installed, valid, and current — no lock, no copy.
|
|
128
|
+
if (!opts.forceReinstall && upToDate())
|
|
129
|
+
return;
|
|
130
|
+
// Serialize the install so concurrent `agents` invocations don't race the
|
|
131
|
+
// atomic swap (and don't each run a redundant `cp`). The re-check inside the
|
|
132
|
+
// lock means a burst of callers copies once, not N times — the stampede that
|
|
133
|
+
// transiently corrupted the bundle and tripped the "damaged" dialog.
|
|
134
|
+
withInstallLock(dest, (heartbeat) => {
|
|
135
|
+
if (!opts.forceReinstall && upToDate())
|
|
141
136
|
return;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
137
|
+
const src = sourceAppPath();
|
|
138
|
+
copyAppBundle(src, dest);
|
|
139
|
+
heartbeat(); // cp -R done; keep the lock fresh across the codesign/spctl spawns
|
|
140
|
+
const verify = codesignVerify(dest);
|
|
141
|
+
if (!verify.ok) {
|
|
142
|
+
throw new Error(`Installed helper failed codesign verification at ${dest}.\n${verify.output}\n` +
|
|
143
|
+
'The bundle may be corrupted. Try `agents helper install` to reinstall, or reinstall agents-cli.');
|
|
144
|
+
}
|
|
145
|
+
heartbeat(); // codesign done; spctl does a network Gatekeeper lookup — refresh again
|
|
146
|
+
const assess = spctlAssess(dest);
|
|
147
|
+
if (!assess.ok) {
|
|
148
|
+
// Warn, do not fail. Gatekeeper ticket lookup needs network; offline
|
|
149
|
+
// installs and CI runners commonly fail this check. The ACL semantics
|
|
150
|
+
// we care about depend on codesign, not spctl.
|
|
151
|
+
process.stderr.write(`agents-cli: notarization check (spctl) did not pass for ${dest}: ${assess.output}\n`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
157
154
|
}
|
|
158
155
|
/**
|
|
159
156
|
* Return the absolute path to the helper executable. If the installed bundle
|
|
@@ -64,6 +64,7 @@ const TOOL_REGISTRY = {
|
|
|
64
64
|
mv: { category: 'shell', signal: 'low', action: 'moving files' },
|
|
65
65
|
cp: { category: 'shell', signal: 'low', action: 'copying files' },
|
|
66
66
|
mkdir: { category: 'shell', signal: 'low', action: 'making directories' },
|
|
67
|
+
rmdir: { category: 'shell', signal: 'low', action: 'removing directories' },
|
|
67
68
|
touch: { category: 'shell', signal: 'low', action: 'touching files' },
|
|
68
69
|
echo: { category: 'shell', signal: 'low', action: 'echoing' },
|
|
69
70
|
printf: { category: 'shell', signal: 'low', action: 'printing' },
|
|
@@ -115,6 +116,13 @@ const VALUE_FLAGS = {
|
|
|
115
116
|
kubectl: new Set(['-n', '--namespace', '--kubeconfig', '--context', '--cluster', '--user', '-s', '--server', '--as', '--token', '--cache-dir', '--request-timeout']),
|
|
116
117
|
rush: new Set(),
|
|
117
118
|
openclaw: new Set(),
|
|
119
|
+
// The repo's own toolchain — heavy in real transcripts (`agents` alone was the
|
|
120
|
+
// top unrecognized token). Two-level so they bucket by subcommand
|
|
121
|
+
// (`agents sessions`, `linear list`) instead of one flat `other` pile (#1830).
|
|
122
|
+
// `ag` is deliberately NOT here — it is the silver searcher in TOOL_REGISTRY,
|
|
123
|
+
// not the agents alias, in this classifier's world.
|
|
124
|
+
agents: new Set(['-H', '--host', '--device']),
|
|
125
|
+
linear: new Set(),
|
|
118
126
|
};
|
|
119
127
|
/** Tools whose bucket key includes the second token (subcommand). */
|
|
120
128
|
const TWO_LEVEL_TOOLS = new Set(Object.keys(VALUE_FLAGS));
|
|
@@ -141,8 +149,8 @@ export function unwrapCommand(cmd) {
|
|
|
141
149
|
const prefix = s.match(/^(?:sudo|time)(?:\s+(?:-[uUgGhpCrtDR]\s+\S+|-\S+))*\s+(.+)$/);
|
|
142
150
|
if (prefix)
|
|
143
151
|
return unwrapCommand(prefix[1]);
|
|
144
|
-
// cd foo && command
|
|
145
|
-
const cd = s.match(/^cd\s+\S+\s
|
|
152
|
+
// cd foo && command (also `cd foo; command` and newline-separated `cd foo\ncommand`)
|
|
153
|
+
const cd = s.match(/^cd\s+\S+\s*(?:&&|;|\n)\s*([\s\S]+)$/);
|
|
146
154
|
if (cd)
|
|
147
155
|
return unwrapCommand(cd[1]);
|
|
148
156
|
// npx / bunx
|
|
@@ -191,7 +199,11 @@ function splitOnOperators(cmd) {
|
|
|
191
199
|
i += 2;
|
|
192
200
|
continue;
|
|
193
201
|
}
|
|
194
|
-
|
|
202
|
+
// A newline separates commands the same way `;` does (an unquoted, un-escaped
|
|
203
|
+
// line break), so `cd X\ncmd` splits into two segments instead of reading as
|
|
204
|
+
// one `cd` command — the top source of `other` classifications (#1830). A
|
|
205
|
+
// `\`-continued line never reaches here (handled by the escape branch above).
|
|
206
|
+
if (ch === '|' || ch === ';' || ch === '\n') {
|
|
195
207
|
if (current.trim())
|
|
196
208
|
parts.push(current.trim());
|
|
197
209
|
current = '';
|
|
@@ -247,22 +259,61 @@ function scanSubcommand(tokens, tool) {
|
|
|
247
259
|
}
|
|
248
260
|
return '';
|
|
249
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* The classifier reads only the executable and the first non-flag token, both at
|
|
264
|
+
* the very head of the first simple command. Tokenizing the *entire* command —
|
|
265
|
+
* every pipeline segment, multi-KB heredoc bodies included — to reach the first
|
|
266
|
+
* word cost up to ~1ms per call (a 7.8KB `cat <<HEREDOC …` classified on the word
|
|
267
|
+
* `cat`). Tokenize only this much of the head instead: enough for the executable
|
|
268
|
+
* plus a subcommand and its flags, never a heredoc tail (#1830, ~3.3x faster).
|
|
269
|
+
*/
|
|
270
|
+
const CLASSIFY_HEAD_LIMIT = 200;
|
|
271
|
+
/**
|
|
272
|
+
* Tokens of the first simple command only, tokenizing just the head of the
|
|
273
|
+
* unwrapped string. Mirrors {@link tokenizeBash}'s first-segment result for
|
|
274
|
+
* short commands but skips the cost of tokenizing the whole command; the full
|
|
275
|
+
* multi-segment tokenizer stays available for callers that need every segment.
|
|
276
|
+
*/
|
|
277
|
+
function firstSimpleCommandTokens(command) {
|
|
278
|
+
const unwrapped = unwrapCommand(command);
|
|
279
|
+
const head = unwrapped.length > CLASSIFY_HEAD_LIMIT ? unwrapped.slice(0, CLASSIFY_HEAD_LIMIT) : unwrapped;
|
|
280
|
+
const firstSegment = splitOnOperators(head)[0] ?? '';
|
|
281
|
+
if (!firstSegment)
|
|
282
|
+
return [];
|
|
283
|
+
try {
|
|
284
|
+
return shlexSplit(firstSegment);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// The head cut a quote mid-string (e.g. a long quoted flag value like
|
|
288
|
+
// `git -c http.extraheader="Authorization: Bearer …" fetch`), so shlex threw
|
|
289
|
+
// on the unbalanced quote. A whitespace split of the truncated head would
|
|
290
|
+
// mis-read the value as the subcommand, so re-tokenize the FULL first
|
|
291
|
+
// segment instead — only the rare throw path pays that cost.
|
|
292
|
+
const fullFirst = splitOnOperators(unwrapped)[0] ?? firstSegment;
|
|
293
|
+
try {
|
|
294
|
+
return shlexSplit(fullFirst);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// Genuinely unbalanced quoting even in the full segment — a whitespace
|
|
298
|
+
// split of the full segment still yields the leading executable.
|
|
299
|
+
return fullFirst.split(/\s+/).filter(Boolean);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
250
303
|
/**
|
|
251
304
|
* Classify the first simple command in a Bash string. Returns coarse metadata
|
|
252
305
|
* (tool name, category, subcommand, human action) used for summaries and
|
|
253
306
|
* activity logging. Unknown executables fall back to `other`.
|
|
254
307
|
*/
|
|
255
308
|
export function classifyBashCommand(command) {
|
|
256
|
-
const
|
|
257
|
-
if (!simpleCommands.length) {
|
|
258
|
-
return { tool: 'other', category: 'other', subcommand: '', action: 'running command', summary: '', signal: 'low' };
|
|
259
|
-
}
|
|
260
|
-
const tokens = simpleCommands[0];
|
|
309
|
+
const tokens = firstSimpleCommandTokens(command);
|
|
261
310
|
if (!tokens.length) {
|
|
262
311
|
return { tool: 'other', category: 'other', subcommand: '', action: 'running command', summary: '', signal: 'low' };
|
|
263
312
|
}
|
|
264
313
|
const first = tokens[0];
|
|
265
|
-
|
|
314
|
+
// Reduce a path executable to its basename so `~/.agents/skills/linear/scripts/linear`,
|
|
315
|
+
// `/usr/bin/git`, and `./tool` all resolve by tool name, not the full path (#1830).
|
|
316
|
+
const baseRaw = first.replace(/^.*\//, '').toLowerCase();
|
|
266
317
|
const base = baseRaw.endsWith('.exe') ? baseRaw.slice(0, -4) : baseRaw;
|
|
267
318
|
const canonical = ALIAS_MAP.get(base);
|
|
268
319
|
const info = canonical ? TOOL_REGISTRY[canonical] : undefined;
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import Database from '../sqlite.js';
|
|
10
10
|
import type { SessionAgentId, SessionMeta } from './types.js';
|
|
11
|
+
import { type IndexedToolCall } from './tool-calls.js';
|
|
11
12
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
12
13
|
* assert against the constant instead of hardcoding a number that every bump
|
|
13
14
|
* then has to chase (docs/05-sessions.md calls the constant the source of truth). */
|
|
14
|
-
export declare const SCHEMA_VERSION =
|
|
15
|
+
export declare const SCHEMA_VERSION = 30;
|
|
15
16
|
/** Raw row shape returned from the sessions table. */
|
|
16
17
|
export interface SessionRow {
|
|
17
18
|
id: string;
|
|
@@ -91,6 +92,8 @@ export interface QueryOptions {
|
|
|
91
92
|
* with NULLs sorted last so unpriced rows never crowd out real data.
|
|
92
93
|
*/
|
|
93
94
|
sortBy?: 'timestamp' | 'cost' | 'duration';
|
|
95
|
+
/** Internal warm-cache path; callers must validate the small final result set. */
|
|
96
|
+
skipExistenceCheck?: boolean;
|
|
94
97
|
/**
|
|
95
98
|
* Only sessions that invoked this skill (#12), joined against
|
|
96
99
|
* session_resource_usage.kind='skill'. Matches either the full stored name
|
|
@@ -204,6 +207,9 @@ export declare function upsertSessionsBatch(entries: Array<{
|
|
|
204
207
|
scan?: ScanStamp;
|
|
205
208
|
parserState?: string;
|
|
206
209
|
contentText?: string;
|
|
210
|
+
toolCalls?: IndexedToolCall[];
|
|
211
|
+
toolScan?: ScanStamp;
|
|
212
|
+
toolIndexMode?: 'replace' | 'append';
|
|
207
213
|
}>): void;
|
|
208
214
|
/**
|
|
209
215
|
* Sync labels for a set of sessions. For each id in the map, if the stored
|