@phnx-labs/agents-cli 1.21.3 → 1.22.0

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 (73) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +32 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/computer-actions.js +1 -0
  5. package/dist/commands/exec.d.ts +27 -0
  6. package/dist/commands/exec.js +123 -6
  7. package/dist/commands/models.js +36 -1
  8. package/dist/commands/projects.js +22 -2
  9. package/dist/commands/sessions-backfill.d.ts +32 -0
  10. package/dist/commands/sessions-backfill.js +186 -0
  11. package/dist/commands/sessions.d.ts +17 -1
  12. package/dist/commands/sessions.js +317 -18
  13. package/dist/commands/teams.js +1 -1
  14. package/dist/commands/worktree.d.ts +3 -3
  15. package/dist/commands/worktree.js +35 -4
  16. package/dist/lib/daemon.d.ts +5 -1
  17. package/dist/lib/daemon.js +63 -14
  18. package/dist/lib/devices/resolve-target.d.ts +6 -0
  19. package/dist/lib/devices/resolve-target.js +9 -3
  20. package/dist/lib/exec.js +39 -8
  21. package/dist/lib/hosts/dispatch.d.ts +12 -0
  22. package/dist/lib/hosts/dispatch.js +23 -6
  23. package/dist/lib/hosts/reconnect.d.ts +38 -0
  24. package/dist/lib/hosts/reconnect.js +85 -4
  25. package/dist/lib/hosts/run-target.js +14 -2
  26. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  27. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  28. package/dist/lib/model-tiers.d.ts +54 -0
  29. package/dist/lib/model-tiers.js +229 -0
  30. package/dist/lib/models.d.ts +3 -0
  31. package/dist/lib/models.js +44 -7
  32. package/dist/lib/pricing/prices.json +16 -1
  33. package/dist/lib/project-focus.d.ts +42 -0
  34. package/dist/lib/project-focus.js +80 -0
  35. package/dist/lib/project-schedule.d.ts +75 -0
  36. package/dist/lib/project-schedule.js +110 -0
  37. package/dist/lib/redact.d.ts +2 -0
  38. package/dist/lib/redact.js +22 -0
  39. package/dist/lib/remote-agents-json.d.ts +2 -0
  40. package/dist/lib/remote-agents-json.js +3 -3
  41. package/dist/lib/rotate.d.ts +84 -1
  42. package/dist/lib/rotate.js +155 -5
  43. package/dist/lib/runner.d.ts +4 -2
  44. package/dist/lib/runner.js +13 -4
  45. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  46. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  47. package/dist/lib/session/bash-command.js +60 -9
  48. package/dist/lib/session/db.d.ts +7 -1
  49. package/dist/lib/session/db.js +301 -32
  50. package/dist/lib/session/discover.d.ts +40 -7
  51. package/dist/lib/session/discover.js +144 -83
  52. package/dist/lib/session/parse.d.ts +8 -1
  53. package/dist/lib/session/parse.js +83 -32
  54. package/dist/lib/session/remote-list.d.ts +71 -0
  55. package/dist/lib/session/remote-list.js +410 -2
  56. package/dist/lib/session/shell-programs.d.ts +15 -0
  57. package/dist/lib/session/shell-programs.js +359 -0
  58. package/dist/lib/session/tool-calls.d.ts +88 -0
  59. package/dist/lib/session/tool-calls.js +612 -0
  60. package/dist/lib/session/tool-index.d.ts +100 -0
  61. package/dist/lib/session/tool-index.js +773 -0
  62. package/dist/lib/session/tool-store.d.ts +15 -0
  63. package/dist/lib/session/tool-store.js +198 -0
  64. package/dist/lib/session/types.d.ts +7 -0
  65. package/dist/lib/state.d.ts +10 -1
  66. package/dist/lib/state.js +11 -2
  67. package/dist/lib/teams/remoteWorktree.d.ts +3 -4
  68. package/dist/lib/teams/remoteWorktree.js +3 -4
  69. package/dist/lib/teams/worktree.d.ts +11 -1
  70. package/dist/lib/teams/worktree.js +42 -4
  71. package/dist/lib/types.d.ts +17 -0
  72. package/dist/lib/types.js +17 -0
  73. package/package.json +3 -1
@@ -0,0 +1,359 @@
1
+ import path from 'path';
2
+ import { parse, } from 'unbash';
3
+ const SHELL_WRAPPERS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh']);
4
+ const PROGRAM_WRAPPERS = new Set(['command', 'builtin', 'env', 'nohup', 'sudo']);
5
+ const MAX_NESTED_DEPTH = 3;
6
+ const SUDO_OPTIONS_WITH_VALUE = new Set([
7
+ '-C', '--close-from', '-D', '--chdir', '-g', '--group', '-h', '--host',
8
+ '-p', '--prompt', '-R', '--chroot', '-r', '--role', '-t', '--type',
9
+ '-T', '--command-timeout', '-u', '--user',
10
+ ]);
11
+ function normalizeProgram(value) {
12
+ const trimmed = value.trim();
13
+ if (!trimmed || trimmed.includes('\0'))
14
+ return undefined;
15
+ const normalized = trimmed.replace(/\\/g, '/');
16
+ const base = path.posix.basename(normalized);
17
+ return base || undefined;
18
+ }
19
+ function staticParts(parts) {
20
+ if (!parts)
21
+ return true;
22
+ return parts.every((part) => {
23
+ switch (part.type) {
24
+ case 'Literal':
25
+ case 'SingleQuoted':
26
+ case 'AnsiCQuoted':
27
+ return true;
28
+ case 'DoubleQuoted':
29
+ case 'LocaleString':
30
+ return staticParts(part.parts);
31
+ default:
32
+ return false;
33
+ }
34
+ });
35
+ }
36
+ /** Return a word only when Bash expansion cannot change its value. */
37
+ export function staticShellWord(word) {
38
+ if (!word || !staticParts(word.parts))
39
+ return undefined;
40
+ return word.value || undefined;
41
+ }
42
+ function walkWord(word, visitScript) {
43
+ if (!word)
44
+ return;
45
+ const walkPart = (part) => {
46
+ switch (part.type) {
47
+ case 'DoubleQuoted':
48
+ case 'LocaleString':
49
+ part.parts.forEach(walkPart);
50
+ break;
51
+ case 'CommandExpansion':
52
+ case 'ProcessSubstitution':
53
+ if (part.script)
54
+ visitScript(part.script);
55
+ break;
56
+ case 'ArithmeticExpansion':
57
+ if (part.expression)
58
+ walkArithmetic(part.expression, visitScript);
59
+ break;
60
+ case 'ParameterExpansion':
61
+ part.indexParts?.forEach(walkPart);
62
+ walkWord(part.operand, visitScript);
63
+ if (part.slice) {
64
+ walkWord(part.slice.offset, visitScript);
65
+ walkWord(part.slice.length, visitScript);
66
+ }
67
+ if (part.replace) {
68
+ walkWord(part.replace.pattern, visitScript);
69
+ walkWord(part.replace.replacement, visitScript);
70
+ }
71
+ break;
72
+ case 'ExtendedGlob':
73
+ case 'BraceExpansion':
74
+ part.parts?.forEach(walkPart);
75
+ break;
76
+ default:
77
+ break;
78
+ }
79
+ };
80
+ // `parts` is a lazy getter in unbash. Access it explicitly; Object.keys(word)
81
+ // does not enumerate expansions.
82
+ word.parts?.forEach(walkPart);
83
+ }
84
+ function walkArithmetic(expr, visitScript) {
85
+ switch (expr.type) {
86
+ case 'ArithmeticBinary':
87
+ walkArithmetic(expr.left, visitScript);
88
+ walkArithmetic(expr.right, visitScript);
89
+ break;
90
+ case 'ArithmeticUnary':
91
+ walkArithmetic(expr.operand, visitScript);
92
+ break;
93
+ case 'ArithmeticTernary':
94
+ walkArithmetic(expr.test, visitScript);
95
+ walkArithmetic(expr.consequent, visitScript);
96
+ walkArithmetic(expr.alternate, visitScript);
97
+ break;
98
+ case 'ArithmeticGroup':
99
+ walkArithmetic(expr.expression, visitScript);
100
+ break;
101
+ case 'ArithmeticWord':
102
+ expr.parts?.forEach((part) => walkWordPart(part, visitScript));
103
+ break;
104
+ case 'ArithmeticCommandExpansion':
105
+ if (expr.script)
106
+ visitScript(expr.script);
107
+ break;
108
+ }
109
+ }
110
+ function walkWordPart(part, visitScript) {
111
+ const synthetic = { text: part.text, value: '', pos: 0, end: 0, parts: [part] };
112
+ walkWord(synthetic, visitScript);
113
+ }
114
+ function walkTest(expr, visitScript) {
115
+ switch (expr.type) {
116
+ case 'TestUnary':
117
+ walkWord(expr.operand, visitScript);
118
+ break;
119
+ case 'TestBinary':
120
+ walkWord(expr.left, visitScript);
121
+ walkWord(expr.right, visitScript);
122
+ break;
123
+ case 'TestLogical':
124
+ walkTest(expr.left, visitScript);
125
+ walkTest(expr.right, visitScript);
126
+ break;
127
+ case 'TestNot':
128
+ walkTest(expr.operand, visitScript);
129
+ break;
130
+ case 'TestGroup':
131
+ walkTest(expr.expression, visitScript);
132
+ break;
133
+ }
134
+ }
135
+ function nestedPayload(command) {
136
+ const program = normalizeProgram(staticShellWord(command.name) ?? '');
137
+ const suffix = command.suffix.map(staticShellWord);
138
+ if (!program || suffix.some((part) => part === undefined))
139
+ return undefined;
140
+ const words = suffix;
141
+ if (SHELL_WRAPPERS.has(program)) {
142
+ const flagIndex = words.findIndex((word) => /^-[a-zA-Z]*c[a-zA-Z]*$/.test(word));
143
+ return flagIndex >= 0 ? words[flagIndex + 1] : undefined;
144
+ }
145
+ if (program === 'ssh') {
146
+ let i = 0;
147
+ while (i < words.length && words[i].startsWith('-')) {
148
+ const option = words[i++];
149
+ if (/^-(?:b|c|D|E|F|I|i|J|L|l|m|O|o|p|Q|R|S|W|w)$/.test(option))
150
+ i++;
151
+ }
152
+ i++; // host
153
+ return i < words.length ? words.slice(i).join(' ') : undefined;
154
+ }
155
+ if ((program === 'agents' || program === 'ag') && words[0] === 'ssh') {
156
+ return words.length > 2 ? words.slice(2).join(' ') : undefined;
157
+ }
158
+ return undefined;
159
+ }
160
+ function wrapperDelegate(words, wrapper) {
161
+ let index = 0;
162
+ if (wrapper === 'env') {
163
+ while (index < words.length) {
164
+ const word = words[index];
165
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(word)) {
166
+ index++;
167
+ continue;
168
+ }
169
+ if (word === '-u' || word === '--unset' || word === '-C' || word === '--chdir') {
170
+ index += 2;
171
+ continue;
172
+ }
173
+ if (word === '--') {
174
+ index++;
175
+ break;
176
+ }
177
+ if (word.startsWith('-')) {
178
+ index++;
179
+ continue;
180
+ }
181
+ break;
182
+ }
183
+ }
184
+ else if (wrapper === 'sudo') {
185
+ while (index < words.length) {
186
+ const word = words[index];
187
+ if (word === '--') {
188
+ index++;
189
+ break;
190
+ }
191
+ const option = word.includes('=') ? word.slice(0, word.indexOf('=')) : word;
192
+ if (!word.startsWith('-'))
193
+ break;
194
+ index++;
195
+ if (!word.includes('=') && SUDO_OPTIONS_WITH_VALUE.has(option))
196
+ index++;
197
+ }
198
+ while (index < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[index]))
199
+ index++;
200
+ }
201
+ else {
202
+ while (index < words.length && words[index].startsWith('-')) {
203
+ if (words[index++] === '--')
204
+ break;
205
+ }
206
+ }
207
+ return index < words.length ? words.slice(index) : undefined;
208
+ }
209
+ /** Parse Bash without executing it and return every statically identifiable program. */
210
+ export function extractShellPrograms(source, depth = 0) {
211
+ const programs = [];
212
+ const occurrences = [];
213
+ const diagnostics = [];
214
+ const seen = new Set();
215
+ const add = (value, role) => {
216
+ const program = value ? normalizeProgram(value) : undefined;
217
+ if (!program)
218
+ return undefined;
219
+ occurrences.push({ program, role });
220
+ if (!seen.has(program)) {
221
+ seen.add(program);
222
+ programs.push(program);
223
+ }
224
+ return program;
225
+ };
226
+ const addWrapperDelegates = (wrapper, words) => {
227
+ let remaining = wrapperDelegate(words, wrapper);
228
+ while (remaining) {
229
+ const program = normalizeProgram(remaining[0]);
230
+ if (!program)
231
+ return;
232
+ const isWrapper = PROGRAM_WRAPPERS.has(program);
233
+ add(program, isWrapper ? 'wrapper' : 'effective');
234
+ if (!isWrapper)
235
+ return;
236
+ remaining = wrapperDelegate(remaining.slice(1), program);
237
+ }
238
+ };
239
+ const visitScript = (script) => {
240
+ for (const error of script.errors ?? [])
241
+ diagnostics.push(`${error.pos}: ${error.message}`);
242
+ script.commands.forEach((statement) => visitNode(statement));
243
+ };
244
+ const visitNode = (node) => {
245
+ switch (node.type) {
246
+ case 'Statement':
247
+ visitNode(node.command);
248
+ node.redirects.forEach((redirect) => {
249
+ walkWord(redirect.target, visitScript);
250
+ if (!redirect.heredocQuoted)
251
+ walkWord(redirect.body, visitScript);
252
+ });
253
+ break;
254
+ case 'Command': {
255
+ const program = staticShellWord(node.name);
256
+ const normalized = normalizeProgram(program ?? '');
257
+ const payload = nestedPayload(node);
258
+ add(program, normalized && (PROGRAM_WRAPPERS.has(normalized) || payload) ? 'wrapper' : 'effective');
259
+ node.prefix.forEach((prefix) => {
260
+ walkWord(prefix.value, visitScript);
261
+ prefix.indexParts?.forEach((part) => walkWordPart(part, visitScript));
262
+ prefix.array?.forEach((word) => walkWord(word, visitScript));
263
+ });
264
+ walkWord(node.name, visitScript);
265
+ node.suffix.forEach((word) => walkWord(word, visitScript));
266
+ node.redirects.forEach((redirect) => {
267
+ walkWord(redirect.target, visitScript);
268
+ if (!redirect.heredocQuoted)
269
+ walkWord(redirect.body, visitScript);
270
+ });
271
+ if (normalized && PROGRAM_WRAPPERS.has(normalized)) {
272
+ const words = node.suffix.map(staticShellWord);
273
+ if (words.every((word) => word !== undefined)) {
274
+ addWrapperDelegates(normalized, words);
275
+ }
276
+ }
277
+ if (payload && depth < MAX_NESTED_DEPTH) {
278
+ const nested = extractShellPrograms(payload, depth + 1);
279
+ for (const occurrence of nested.occurrences)
280
+ add(occurrence.program, occurrence.role);
281
+ diagnostics.push(...nested.diagnostics);
282
+ }
283
+ break;
284
+ }
285
+ case 'Pipeline':
286
+ case 'AndOr':
287
+ node.commands.forEach(visitNode);
288
+ break;
289
+ case 'If':
290
+ visitNode(node.clause);
291
+ visitNode(node.then);
292
+ if (node.else)
293
+ visitNode(node.else);
294
+ break;
295
+ case 'For':
296
+ case 'Select':
297
+ node.wordlist.forEach((word) => walkWord(word, visitScript));
298
+ visitNode(node.body);
299
+ break;
300
+ case 'ArithmeticFor':
301
+ if (node.initialize)
302
+ walkArithmetic(node.initialize, visitScript);
303
+ if (node.test)
304
+ walkArithmetic(node.test, visitScript);
305
+ if (node.update)
306
+ walkArithmetic(node.update, visitScript);
307
+ visitNode(node.body);
308
+ break;
309
+ case 'While':
310
+ visitNode(node.clause);
311
+ visitNode(node.body);
312
+ break;
313
+ case 'Function':
314
+ walkWord(node.name, visitScript);
315
+ visitNode(node.body);
316
+ break;
317
+ case 'Subshell':
318
+ case 'BraceGroup':
319
+ visitNode(node.body);
320
+ break;
321
+ case 'CompoundList':
322
+ node.commands.forEach(visitNode);
323
+ break;
324
+ case 'Case':
325
+ walkWord(node.word, visitScript);
326
+ node.items.forEach((item) => {
327
+ item.pattern.forEach((word) => walkWord(word, visitScript));
328
+ visitNode(item.body);
329
+ });
330
+ break;
331
+ case 'Coproc':
332
+ walkWord(node.name, visitScript);
333
+ visitNode(node.body);
334
+ break;
335
+ case 'TestCommand':
336
+ walkTest(node.expression, visitScript);
337
+ break;
338
+ case 'ArithmeticCommand':
339
+ if (node.expression)
340
+ walkArithmetic(node.expression, visitScript);
341
+ break;
342
+ }
343
+ };
344
+ try {
345
+ const parsed = parse(source);
346
+ if (parsed.errors?.length) {
347
+ return {
348
+ programs: [],
349
+ occurrences: [],
350
+ diagnostics: parsed.errors.map((error) => `${error.pos}: ${error.message}`),
351
+ };
352
+ }
353
+ visitScript(parsed);
354
+ }
355
+ catch (error) {
356
+ diagnostics.push(error instanceof Error ? error.message : String(error));
357
+ }
358
+ return { programs, occurrences, diagnostics };
359
+ }
@@ -0,0 +1,88 @@
1
+ import type { SessionEvent } from './types.js';
2
+ import { type ShellProgramOccurrence } from './shell-programs.js';
3
+ export declare const TOOL_INPUT_MAX_BYTES: number;
4
+ export declare const TOOL_SUCCESS_OUTPUT_MAX_BYTES = 1024;
5
+ export declare const TOOL_ERROR_OUTPUT_MAX_BYTES: number;
6
+ export declare const TOOL_SESSION_EVIDENCE_MAX_BYTES: number;
7
+ export declare const TOOL_PENDING_MAX_BYTES: number;
8
+ export declare const TOOL_PENDING_MAX_CALLS = 256;
9
+ export declare const TOOL_CHANGED_MAX_CALLS = 10000;
10
+ export declare const TOOL_INDEX_LIMIT_ORDINAL: number;
11
+ export declare const TOOL_TEXT_PROCESSING_MAX_BYTES: number;
12
+ export declare const TOOL_SHELL_PARSE_MAX_BYTES: number;
13
+ export declare const TOOL_INDEX_VERSION = 7;
14
+ export type ToolCallOutcome = 'ok' | 'error' | 'unknown';
15
+ export interface IndexedToolCall {
16
+ ordinal: number;
17
+ sourceCallId?: string;
18
+ timestamp: string;
19
+ tool: string;
20
+ programs: string[];
21
+ programOccurrences: ShellProgramOccurrence[];
22
+ input: string;
23
+ outcome: ToolCallOutcome;
24
+ exitCode?: number;
25
+ statusCode?: number;
26
+ errorCode?: string;
27
+ output?: string;
28
+ error?: string;
29
+ parseError?: string;
30
+ }
31
+ export interface ToolCallCollectorSnapshot {
32
+ v: 1;
33
+ nextOrdinal: number;
34
+ pending: IndexedToolCall[];
35
+ }
36
+ export declare function sanitizeToolEvidenceText(value: string, maxBytes: number, knownValues?: readonly string[]): string;
37
+ export declare function structuredToolResult(value: unknown): {
38
+ text: string;
39
+ outcome: ToolCallOutcome;
40
+ exitCode?: number;
41
+ statusCode?: number;
42
+ errorCode?: string;
43
+ };
44
+ export declare function toolCallEvidenceBytes(call: IndexedToolCall): number;
45
+ export declare class ToolCallCollector {
46
+ private nextOrdinal;
47
+ private readonly pendingById;
48
+ private readonly pendingOrder;
49
+ private readonly changed;
50
+ private readonly changedSizes;
51
+ private pendingBytes;
52
+ private changedBytes;
53
+ private changedTruncated;
54
+ constructor(snapshot?: ToolCallCollectorSnapshot);
55
+ start(args: {
56
+ timestamp: string;
57
+ tool: string;
58
+ input?: Record<string, unknown>;
59
+ command?: string;
60
+ sourceCallId?: string;
61
+ }): IndexedToolCall;
62
+ finish(args: {
63
+ timestamp: string;
64
+ sourceCallId?: string;
65
+ tool?: string;
66
+ success?: boolean;
67
+ outcome?: ToolCallOutcome;
68
+ exitCode?: number;
69
+ statusCode?: number;
70
+ errorCode?: string;
71
+ output?: string;
72
+ error?: string;
73
+ }): IndexedToolCall | undefined;
74
+ drainChanged(): IndexedToolCall[];
75
+ snapshot(): ToolCallCollectorSnapshot;
76
+ recordIndexLimit(): void;
77
+ private rememberPending;
78
+ private takePending;
79
+ private removePending;
80
+ private markChanged;
81
+ }
82
+ /** Build indexed calls from the normalized parser contract used by full-file harnesses. */
83
+ export declare function toolCallsFromEvents(events: SessionEvent[]): IndexedToolCall[];
84
+ export declare function toolCallKey(sessionId: string, ordinal: number): string;
85
+ /** Fold one raw Claude record into the resumable call collector. */
86
+ export declare function collectClaudeToolCalls(collector: ToolCallCollector, parsed: any): void;
87
+ /** Fold one raw Codex rollout record into the resumable call collector. */
88
+ export declare function collectCodexToolCalls(collector: ToolCallCollector, parsed: any): void;