@phnx-labs/agents-cli 1.18.6 → 1.19.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 +13 -2
- package/README.md +22 -20
- package/dist/commands/browser.js +25 -2
- package/dist/commands/cloud.js +3 -3
- package/dist/commands/computer.d.ts +6 -0
- package/dist/commands/computer.js +477 -0
- package/dist/commands/doctor.js +19 -17
- package/dist/commands/exec.js +37 -59
- package/dist/commands/factory.js +12 -5
- package/dist/commands/import.js +6 -1
- package/dist/commands/mcp.js +9 -4
- package/dist/commands/packages.d.ts +3 -0
- package/dist/commands/packages.js +20 -12
- package/dist/commands/permissions.d.ts +2 -0
- package/dist/commands/permissions.js +20 -1
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +23 -4
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pty.js +126 -112
- package/dist/commands/pull.js +29 -25
- package/dist/commands/repo.js +24 -26
- package/dist/commands/routines.js +29 -26
- package/dist/commands/secrets.js +66 -73
- package/dist/commands/sessions-tail.js +21 -22
- package/dist/commands/sessions.js +36 -68
- package/dist/commands/setup.js +20 -24
- package/dist/commands/teams.js +30 -39
- package/dist/commands/versions.js +60 -68
- package/dist/commands/worktree.d.ts +20 -0
- package/dist/commands/worktree.js +242 -0
- package/dist/computer.d.ts +2 -0
- package/dist/computer.js +7 -0
- package/dist/index.js +70 -26
- package/dist/lib/agents.d.ts +4 -1
- package/dist/lib/agents.js +23 -5
- package/dist/lib/browser/cdp.d.ts +15 -1
- package/dist/lib/browser/cdp.js +77 -8
- package/dist/lib/browser/chrome.js +17 -24
- package/dist/lib/browser/drivers/ssh.d.ts +1 -0
- package/dist/lib/browser/drivers/ssh.js +20 -8
- package/dist/lib/browser/ipc.js +38 -5
- package/dist/lib/browser/profiles.js +34 -2
- package/dist/lib/browser/runtime-state.d.ts +1 -2
- package/dist/lib/browser/runtime-state.js +11 -3
- package/dist/lib/browser/service.d.ts +5 -0
- package/dist/lib/browser/service.js +32 -4
- package/dist/lib/browser/types.d.ts +1 -1
- package/dist/lib/browser/upload.d.ts +2 -0
- package/dist/lib/browser/upload.js +34 -0
- package/dist/lib/cloud/rush.d.ts +2 -1
- package/dist/lib/cloud/rush.js +28 -9
- package/dist/lib/computer-rpc.d.ts +24 -0
- package/dist/lib/computer-rpc.js +263 -0
- package/dist/lib/daemon.js +7 -7
- package/dist/lib/exec.d.ts +2 -1
- package/dist/lib/exec.js +3 -2
- package/dist/lib/fs-atomic.d.ts +18 -0
- package/dist/lib/fs-atomic.js +76 -0
- package/dist/lib/git.js +2 -4
- package/dist/lib/help.d.ts +15 -0
- package/dist/lib/help.js +41 -0
- package/dist/lib/hooks/match.d.ts +1 -0
- package/dist/lib/hooks/match.js +57 -12
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +27 -10
- package/dist/lib/import.d.ts +1 -0
- package/dist/lib/import.js +7 -0
- package/dist/lib/manifest.js +27 -1
- package/dist/lib/mcp.d.ts +14 -0
- package/dist/lib/mcp.js +79 -14
- package/dist/lib/migrate.js +3 -3
- package/dist/lib/models.js +3 -1
- package/dist/lib/permissions.d.ts +5 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/plugin-marketplace.d.ts +3 -1
- package/dist/lib/plugin-marketplace.js +36 -1
- package/dist/lib/plugins.d.ts +19 -1
- package/dist/lib/plugins.js +99 -8
- package/dist/lib/redact.d.ts +4 -0
- package/dist/lib/redact.js +18 -0
- package/dist/lib/registry.d.ts +2 -0
- package/dist/lib/registry.js +15 -0
- package/dist/lib/sandbox.js +15 -5
- package/dist/lib/secrets/bundles.d.ts +7 -12
- package/dist/lib/secrets/bundles.js +45 -29
- package/dist/lib/secrets/index.js +4 -4
- package/dist/lib/session/cloud.d.ts +2 -0
- package/dist/lib/session/cloud.js +34 -6
- package/dist/lib/session/parse.js +7 -2
- package/dist/lib/session/render.d.ts +4 -1
- package/dist/lib/session/render.js +81 -35
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +29 -7
- package/dist/lib/state.d.ts +5 -5
- package/dist/lib/state.js +43 -13
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/types.d.ts +4 -3
- package/dist/lib/types.js +0 -2
- package/dist/lib/versions.js +65 -40
- package/dist/lib/workflows.d.ts +7 -0
- package/dist/lib/workflows.js +42 -1
- package/npm-shrinkwrap.json +3162 -0
- package/package.json +32 -26
- package/scripts/postinstall.js +8 -2
|
@@ -17,6 +17,7 @@ import { getCacheDir } from '../state.js';
|
|
|
17
17
|
const PROXY_BASE = process.env.RUSH_PROXY_BASE ?? 'https://api.prix.dev';
|
|
18
18
|
const USER_YAML = path.join(os.homedir(), '.rush', 'user.yaml');
|
|
19
19
|
const CLOUD_CACHE_DIR = path.join(getCacheDir(), 'cloud-runs');
|
|
20
|
+
const CLOUD_EXECUTION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
20
21
|
function readToken() {
|
|
21
22
|
if (!fs.existsSync(USER_YAML)) {
|
|
22
23
|
throw new Error('Not logged in to Rush. Run `rush login` first.');
|
|
@@ -45,6 +46,24 @@ function agentToFormat(agent) {
|
|
|
45
46
|
return 'rush';
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
49
|
+
export function assertContained(candidate, rootDir) {
|
|
50
|
+
const root = path.resolve(rootDir);
|
|
51
|
+
const resolved = path.resolve(root, candidate);
|
|
52
|
+
if (!resolved.startsWith(root + path.sep)) {
|
|
53
|
+
throw new Error(`Path escapes cloud session cache: ${candidate}`);
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
export function validateCloudExecutionId(executionId) {
|
|
58
|
+
if (!CLOUD_EXECUTION_ID_RE.test(executionId)) {
|
|
59
|
+
throw new Error(`Invalid cloud execution_id: ${JSON.stringify(executionId)}`);
|
|
60
|
+
}
|
|
61
|
+
return executionId;
|
|
62
|
+
}
|
|
63
|
+
function cachePathForExecution(executionId, agent) {
|
|
64
|
+
const id = validateCloudExecutionId(executionId);
|
|
65
|
+
return assertContained(path.join(id, `session.${agent}.jsonl`), CLOUD_CACHE_DIR);
|
|
66
|
+
}
|
|
48
67
|
/**
|
|
49
68
|
* List cloud executions the user has captured sessions for. Includes
|
|
50
69
|
* completed + needs_review + failed; an empty session_path means capture
|
|
@@ -64,13 +83,13 @@ export async function discoverCloudSessions(options) {
|
|
|
64
83
|
const agent = agentToFormat(row.agent);
|
|
65
84
|
if (!agent)
|
|
66
85
|
continue;
|
|
67
|
-
const id = row.execution_id;
|
|
86
|
+
const id = validateCloudExecutionId(row.execution_id);
|
|
68
87
|
const timestamp = row.updated_at || row.created_at || new Date().toISOString();
|
|
69
88
|
const project = row.repo_owner && row.repo_name ? `${row.repo_owner}/${row.repo_name}` : undefined;
|
|
70
89
|
// filePath doubles as the sink path for the cached jsonl. parseSession
|
|
71
90
|
// dispatches on detectAgent which recognizes the `session.<format>.jsonl`
|
|
72
91
|
// suffix — so the local cache file name must preserve it.
|
|
73
|
-
const filePath =
|
|
92
|
+
const filePath = cachePathForExecution(id, agent);
|
|
74
93
|
out.push({
|
|
75
94
|
id,
|
|
76
95
|
shortId: id.slice(0, 8),
|
|
@@ -91,8 +110,10 @@ export async function discoverCloudSessions(options) {
|
|
|
91
110
|
* are immutable once complete). Callers may pass an already-known filePath.
|
|
92
111
|
*/
|
|
93
112
|
export async function ensureCloudSessionCached(executionId, destPath) {
|
|
113
|
+
const id = validateCloudExecutionId(executionId);
|
|
114
|
+
const callerPath = destPath ? assertContained(destPath, CLOUD_CACHE_DIR) : undefined;
|
|
94
115
|
const token = readToken();
|
|
95
|
-
const res = await api('GET', `/api/v1/cloud-runs/${encodeURIComponent(
|
|
116
|
+
const res = await api('GET', `/api/v1/cloud-runs/${encodeURIComponent(id)}/session.jsonl`, token);
|
|
96
117
|
if (!res.ok) {
|
|
97
118
|
const body = await res.text().catch(() => '');
|
|
98
119
|
throw new Error(`session.jsonl fetch ${res.status}: ${body.slice(0, 200)}`);
|
|
@@ -101,7 +122,7 @@ export async function ensureCloudSessionCached(executionId, destPath) {
|
|
|
101
122
|
if (!['claude', 'codex', 'rush'].includes(format)) {
|
|
102
123
|
throw new Error(`Unknown X-Session-Format on cloud response: "${format}"`);
|
|
103
124
|
}
|
|
104
|
-
const finalPath =
|
|
125
|
+
const finalPath = callerPath ?? cachePathForExecution(id, format);
|
|
105
126
|
fs.mkdirSync(path.dirname(finalPath), { recursive: true });
|
|
106
127
|
const body = Buffer.from(await res.arrayBuffer());
|
|
107
128
|
fs.writeFileSync(finalPath, body);
|
|
@@ -109,7 +130,9 @@ export async function ensureCloudSessionCached(executionId, destPath) {
|
|
|
109
130
|
}
|
|
110
131
|
/** True if filePath points into the cloud session cache dir. */
|
|
111
132
|
export function isCloudSessionPath(filePath) {
|
|
112
|
-
|
|
133
|
+
const root = path.resolve(CLOUD_CACHE_DIR);
|
|
134
|
+
const resolved = path.resolve(filePath);
|
|
135
|
+
return resolved.startsWith(root + path.sep);
|
|
113
136
|
}
|
|
114
137
|
/** Extract execution_id from a cloud cache path. */
|
|
115
138
|
export function executionIdFromCloudPath(filePath) {
|
|
@@ -117,5 +140,10 @@ export function executionIdFromCloudPath(filePath) {
|
|
|
117
140
|
return null;
|
|
118
141
|
const rel = path.relative(CLOUD_CACHE_DIR, filePath);
|
|
119
142
|
const parts = rel.split(path.sep);
|
|
120
|
-
|
|
143
|
+
try {
|
|
144
|
+
return parts[0] ? validateCloudExecutionId(parts[0]) : null;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
121
149
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* objects suitable for rendering, filtering, and summarization.
|
|
7
7
|
*/
|
|
8
8
|
import * as fs from 'fs';
|
|
9
|
-
import {
|
|
9
|
+
import { execFileSync } from 'child_process';
|
|
10
10
|
/**
|
|
11
11
|
* Largest session file we will load into memory. Above this we throw a clean
|
|
12
12
|
* error instead of OOMing or hitting V8's ERR_STRING_TOO_LONG. Aligns with
|
|
@@ -668,7 +668,12 @@ export function parseOpenCode(filePath) {
|
|
|
668
668
|
WHERE m.session_id = '${sessionId.replace(/'/g, "''")}'
|
|
669
669
|
ORDER BY m.time_created ASC, p.time_created ASC;
|
|
670
670
|
`.replace(/\n/g, ' ');
|
|
671
|
-
const out =
|
|
671
|
+
const out = execFileSync('sqlite3', ['-separator', '|||', dbPath, query], {
|
|
672
|
+
encoding: 'utf-8',
|
|
673
|
+
timeout: 10000,
|
|
674
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
675
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
676
|
+
});
|
|
672
677
|
for (const line of out.split('\n')) {
|
|
673
678
|
if (!line.trim())
|
|
674
679
|
continue;
|
|
@@ -94,7 +94,10 @@ export declare function filterEvents(events: SessionEvent[], opts: FilterOptions
|
|
|
94
94
|
* order so reasoning sits where it actually occurred relative to the assistant
|
|
95
95
|
* reply.
|
|
96
96
|
*/
|
|
97
|
-
export
|
|
97
|
+
export interface RenderConversationMarkdownOptions {
|
|
98
|
+
redact?: boolean;
|
|
99
|
+
}
|
|
100
|
+
export declare function renderConversationMarkdown(events: SessionEvent[], opts?: RenderConversationMarkdownOptions): string;
|
|
98
101
|
/**
|
|
99
102
|
* Render session as JSON (normalized events).
|
|
100
103
|
*/
|
|
@@ -10,6 +10,7 @@ import chalk from 'chalk';
|
|
|
10
10
|
import { summarizeToolUse } from './parse.js';
|
|
11
11
|
import { cleanSessionPrompt, extractSessionTopic } from './prompt.js';
|
|
12
12
|
import { renderMarkdown } from '../markdown.js';
|
|
13
|
+
import { redactSecrets } from '../redact.js';
|
|
13
14
|
// ── Path helpers ──────────────────────────────────────────────────────────────
|
|
14
15
|
/**
|
|
15
16
|
* Return absPath relative to cwd; fall back to ~/… then absolute.
|
|
@@ -405,6 +406,26 @@ function renderFileGroup(lines, groups, absPathMap) {
|
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
}
|
|
409
|
+
// ── Recent activity renderer ──────────────────────────────────────────────────
|
|
410
|
+
/** Render a single Recent Activity line as `<kind-tag> <label>`, colored by kind. */
|
|
411
|
+
function renderActivityLine(item) {
|
|
412
|
+
const MAX = 90;
|
|
413
|
+
const trim = (s) => (s.length <= MAX ? s : s.slice(0, MAX - 1) + '…');
|
|
414
|
+
switch (item.kind) {
|
|
415
|
+
case 'edit': {
|
|
416
|
+
const linked = item.absPath ? linkPath(item.absPath, item.label) : item.label;
|
|
417
|
+
return chalk.cyan('Edit ') + ' ' + linked;
|
|
418
|
+
}
|
|
419
|
+
case 'cmd':
|
|
420
|
+
return chalk.yellow('Bash ') + ' ' + chalk.gray(trim(item.label));
|
|
421
|
+
case 'agent':
|
|
422
|
+
return chalk.magenta('Agent') + ' ' + trim(item.label);
|
|
423
|
+
case 'error':
|
|
424
|
+
return chalk.red('Error') + ' ' + chalk.gray(trim(item.label));
|
|
425
|
+
case 'msg':
|
|
426
|
+
return chalk.green('Msg ') + ' ' + chalk.gray('"' + trim(item.label) + '"');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
408
429
|
// ── Main summary renderer ─────────────────────────────────────────────────────
|
|
409
430
|
/**
|
|
410
431
|
* Render session as an activity summary.
|
|
@@ -429,6 +450,7 @@ export function renderSummary(events, cwd) {
|
|
|
429
450
|
const subagents = [];
|
|
430
451
|
// Errors
|
|
431
452
|
const errors = [];
|
|
453
|
+
const recentActivity = [];
|
|
432
454
|
// Assistant message count (used to decide whether the session produced any narration)
|
|
433
455
|
let assistantCount = 0;
|
|
434
456
|
const isInsideCwd = (p) => !!(cwd && p.startsWith(cwd + '/'));
|
|
@@ -451,13 +473,16 @@ export function renderSummary(events, cwd) {
|
|
|
451
473
|
}
|
|
452
474
|
else {
|
|
453
475
|
(isInsideCwd(p) || !cwd ? filesModifiedAbs : filesModifiedExternal).add(p);
|
|
476
|
+
recentActivity.push({ kind: 'edit', label: relativeToCwd(p, cwd), ts, absPath: p });
|
|
454
477
|
}
|
|
455
478
|
}
|
|
456
479
|
}
|
|
457
480
|
if (event.command) {
|
|
458
481
|
const cmd = event.command.replace(/\n/g, ' ').trim();
|
|
459
|
-
if (cmd)
|
|
482
|
+
if (cmd) {
|
|
460
483
|
cmdList.push({ cmd, ts });
|
|
484
|
+
recentActivity.push({ kind: 'cmd', label: cmd, ts });
|
|
485
|
+
}
|
|
461
486
|
}
|
|
462
487
|
// Plan items: TodoWrite items + TaskCreate descriptions (project's task tracker)
|
|
463
488
|
if (tool === 'TodoWrite' && Array.isArray(args.todos)) {
|
|
@@ -477,18 +502,26 @@ export function renderSummary(events, cwd) {
|
|
|
477
502
|
}
|
|
478
503
|
// Subagent spawns
|
|
479
504
|
if ((tool === 'Agent' || tool === 'Task') && (args.description || args.prompt)) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
});
|
|
505
|
+
const description = String(args.description || args.prompt || '').slice(0, 120);
|
|
506
|
+
const subagentType = String(args.subagent_type || '');
|
|
507
|
+
subagents.push({ description, subagentType });
|
|
508
|
+
const typeSuffix = subagentType ? ` (${subagentType})` : '';
|
|
509
|
+
recentActivity.push({ kind: 'agent', label: description + typeSuffix, ts });
|
|
484
510
|
}
|
|
485
511
|
}
|
|
486
512
|
else if (event.type === 'error') {
|
|
487
|
-
|
|
513
|
+
const err = {
|
|
488
514
|
tool: event.tool || 'unknown',
|
|
489
515
|
cmd: event.args?.command ? String(event.args.command).slice(0, 80) : undefined,
|
|
490
516
|
content: event.content?.slice(0, 120),
|
|
491
|
-
}
|
|
517
|
+
};
|
|
518
|
+
errors.push(err);
|
|
519
|
+
const errLabel = err.cmd
|
|
520
|
+
? `${err.tool} "${err.cmd.slice(0, 60)}"`
|
|
521
|
+
: err.content
|
|
522
|
+
? `${err.tool}: ${err.content.slice(0, 60)}`
|
|
523
|
+
: err.tool;
|
|
524
|
+
recentActivity.push({ kind: 'error', label: errLabel, ts });
|
|
492
525
|
}
|
|
493
526
|
else if (event.type === 'message') {
|
|
494
527
|
if (event.role === 'user') {
|
|
@@ -504,6 +537,9 @@ export function renderSummary(events, cwd) {
|
|
|
504
537
|
else if (event.role === 'assistant' && event.content) {
|
|
505
538
|
lastAssistantMessage = event.content;
|
|
506
539
|
assistantCount++;
|
|
540
|
+
const preview = event.content.replace(/\s+/g, ' ').trim().slice(0, 100);
|
|
541
|
+
if (preview)
|
|
542
|
+
recentActivity.push({ kind: 'msg', label: preview, ts });
|
|
507
543
|
}
|
|
508
544
|
}
|
|
509
545
|
else if (event.type === 'attachment') {
|
|
@@ -549,7 +585,18 @@ export function renderSummary(events, cwd) {
|
|
|
549
585
|
}
|
|
550
586
|
if (firstUserMessage || attachments.length > 0)
|
|
551
587
|
lines.push('');
|
|
552
|
-
// 2.
|
|
588
|
+
// 2. Recent Activity (first content section — chronological tail of the
|
|
589
|
+
// session so the top of the recap reflects what happened most recently).
|
|
590
|
+
if (recentActivity.length > 0) {
|
|
591
|
+
const RECENT_LIMIT = 7;
|
|
592
|
+
const tail = recentActivity.slice(-RECENT_LIMIT);
|
|
593
|
+
lines.push(chalk.bold('Recent Activity') + chalk.gray(` (last ${tail.length} of ${recentActivity.length})`));
|
|
594
|
+
for (const item of tail) {
|
|
595
|
+
lines.push(' ' + renderActivityLine(item));
|
|
596
|
+
}
|
|
597
|
+
lines.push('');
|
|
598
|
+
}
|
|
599
|
+
// 3. Plan
|
|
553
600
|
if (todoItems.length > 0 || exitPlanContent || planFilePath) {
|
|
554
601
|
lines.push(chalk.bold('Plan'));
|
|
555
602
|
if (planFilePath) {
|
|
@@ -569,7 +616,8 @@ export function renderSummary(events, cwd) {
|
|
|
569
616
|
}
|
|
570
617
|
lines.push('');
|
|
571
618
|
}
|
|
572
|
-
//
|
|
619
|
+
// 4. Subagents (describe attempts — grouped with Plan and Errors above the
|
|
620
|
+
// file/command deltas because they speak to *what was tried*, not the final state).
|
|
573
621
|
if (subagents.length > 0) {
|
|
574
622
|
lines.push(chalk.bold('Subagents') + chalk.gray(` (${subagents.length})`));
|
|
575
623
|
for (const s of subagents) {
|
|
@@ -578,14 +626,28 @@ export function renderSummary(events, cwd) {
|
|
|
578
626
|
}
|
|
579
627
|
lines.push('');
|
|
580
628
|
}
|
|
581
|
-
//
|
|
629
|
+
// 5. Errors (moved up from the bottom: it describes failed attempts, not the
|
|
630
|
+
// session's final state. Sitting at the bottom previously made early errors
|
|
631
|
+
// look recent, which confused readers.)
|
|
632
|
+
if (errors.length > 0) {
|
|
633
|
+
const first = errors[0];
|
|
634
|
+
const firstDesc = first.cmd
|
|
635
|
+
? `${first.tool} "${first.cmd.slice(0, 60)}"`
|
|
636
|
+
: first.content
|
|
637
|
+
? `${first.tool}: ${first.content.slice(0, 60)}`
|
|
638
|
+
: first.tool;
|
|
639
|
+
lines.push(chalk.red(chalk.bold('Errors')) +
|
|
640
|
+
chalk.gray(`: ${errors.length} failure${errors.length !== 1 ? 's' : ''} — first: ${firstDesc}`));
|
|
641
|
+
lines.push('');
|
|
642
|
+
}
|
|
643
|
+
// 6. Modified files
|
|
582
644
|
if (filesModifiedAbs.size > 0) {
|
|
583
645
|
lines.push(chalk.bold('Modified') + chalk.gray(` (${filesModifiedAbs.size})`));
|
|
584
646
|
const groups = groupByParentDir(filesModifiedAbs, cwd);
|
|
585
647
|
renderFileGroup(lines, groups, modifiedAbsMap);
|
|
586
648
|
lines.push('');
|
|
587
649
|
}
|
|
588
|
-
//
|
|
650
|
+
// 6b. External edits (files edited outside the project root — typically /tmp)
|
|
589
651
|
// Filter out plan files (already shown in Plan section)
|
|
590
652
|
const externalNonPlan = [...filesModifiedExternal].filter(p => !(p.includes('.claude/plans/') && p.endsWith('.md')));
|
|
591
653
|
if (externalNonPlan.length > 0) {
|
|
@@ -596,7 +658,7 @@ export function renderSummary(events, cwd) {
|
|
|
596
658
|
lines.push(chalk.gray(`External edits (${externalList.length}): ${display.join(', ')}${more}`));
|
|
597
659
|
lines.push('');
|
|
598
660
|
}
|
|
599
|
-
//
|
|
661
|
+
// 7. Read files
|
|
600
662
|
if (filesReadAbs.size > 0) {
|
|
601
663
|
if (filesReadAbs.size <= 5) {
|
|
602
664
|
lines.push(chalk.bold('Read') + chalk.gray(` (${filesReadAbs.size})`));
|
|
@@ -608,20 +670,8 @@ export function renderSummary(events, cwd) {
|
|
|
608
670
|
}
|
|
609
671
|
lines.push('');
|
|
610
672
|
}
|
|
611
|
-
//
|
|
673
|
+
// 8. Commands
|
|
612
674
|
renderCommandsSection(cmdList, lines);
|
|
613
|
-
// 7. Errors
|
|
614
|
-
if (errors.length > 0) {
|
|
615
|
-
const first = errors[0];
|
|
616
|
-
const firstDesc = first.cmd
|
|
617
|
-
? `${first.tool} "${first.cmd.slice(0, 60)}"`
|
|
618
|
-
: first.content
|
|
619
|
-
? `${first.tool}: ${first.content.slice(0, 60)}`
|
|
620
|
-
: first.tool;
|
|
621
|
-
lines.push(chalk.red(chalk.bold('Errors')) +
|
|
622
|
-
chalk.gray(`: ${errors.length} failure${errors.length !== 1 ? 's' : ''} — first: ${firstDesc}`));
|
|
623
|
-
lines.push('');
|
|
624
|
-
}
|
|
625
675
|
// 9. Final message
|
|
626
676
|
if (lastAssistantMessage) {
|
|
627
677
|
const hasActivity = filesModifiedAbs.size > 0 || filesReadAbs.size > 0 || cmdList.length > 0;
|
|
@@ -749,15 +799,10 @@ export function filterEvents(events, opts) {
|
|
|
749
799
|
const sliced = applyTurnSlice(events, opts);
|
|
750
800
|
return applyRoleFilter(sliced, opts);
|
|
751
801
|
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* Build the conversation as a single markdown string: user / assistant
|
|
755
|
-
* messages, inline thinking blocks, tool calls, and errors. Emitted in event
|
|
756
|
-
* order so reasoning sits where it actually occurred relative to the assistant
|
|
757
|
-
* reply.
|
|
758
|
-
*/
|
|
759
|
-
export function renderConversationMarkdown(events) {
|
|
802
|
+
export function renderConversationMarkdown(events, opts = {}) {
|
|
760
803
|
const parts = [];
|
|
804
|
+
const shouldRedact = opts.redact !== false;
|
|
805
|
+
const sanitize = (text) => shouldRedact ? redactSecrets(text) : text;
|
|
761
806
|
for (const event of events) {
|
|
762
807
|
if (event.type === 'message') {
|
|
763
808
|
if (event.role === 'user') {
|
|
@@ -774,7 +819,7 @@ export function renderConversationMarkdown(events) {
|
|
|
774
819
|
else if (event.type === 'tool_use') {
|
|
775
820
|
const tool = event.tool || 'unknown';
|
|
776
821
|
if (event.command) {
|
|
777
|
-
parts.push(`### Tool: ${tool}\n\n\`\`\`bash\n${event.command}\n\`\`\``);
|
|
822
|
+
parts.push(`### Tool: ${tool}\n\n\`\`\`bash\n${sanitize(event.command)}\n\`\`\``);
|
|
778
823
|
}
|
|
779
824
|
else if (event.path) {
|
|
780
825
|
parts.push(`### Tool: ${tool}\n\n\`${shortenPathTrace(event.path)}\``);
|
|
@@ -786,7 +831,8 @@ export function renderConversationMarkdown(events) {
|
|
|
786
831
|
}
|
|
787
832
|
else if (event.type === 'tool_result') {
|
|
788
833
|
if (event.content) {
|
|
789
|
-
const
|
|
834
|
+
const truncated = event.content.length > 2000 ? event.content.slice(0, 2000) + '\n…' : event.content;
|
|
835
|
+
const body = sanitize(truncated);
|
|
790
836
|
parts.push(`### Tool Result\n\n\`\`\`\n${body}\n\`\`\``);
|
|
791
837
|
}
|
|
792
838
|
}
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -52,8 +52,11 @@ export interface ConflictInfo {
|
|
|
52
52
|
* .oauth_token file on Linux (keychain-less sandbox fallback).
|
|
53
53
|
* v11 — when no default is set or the configured version is not installed,
|
|
54
54
|
* interactively propose the latest already-installed version.
|
|
55
|
+
* v12 — helper calls inside generated shims use the absolute agents-cli
|
|
56
|
+
* entrypoint instead of PATH-resolved `agents`.
|
|
57
|
+
* v13 — validate agents.yaml version strings before constructing binary paths.
|
|
55
58
|
*/
|
|
56
|
-
export declare const SHIM_SCHEMA_VERSION =
|
|
59
|
+
export declare const SHIM_SCHEMA_VERSION = 13;
|
|
57
60
|
/**
|
|
58
61
|
* Generate the full bash shim script for the given agent. The returned string
|
|
59
62
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
|
@@ -206,7 +209,7 @@ export declare function getPathShadowingExecutable(agent: AgentId): string | nul
|
|
|
206
209
|
* Delete the legacy ~/.agents/shims/<cli> file if it exists, returning whether
|
|
207
210
|
* anything was removed. Pre-split installs put shims under ~/.agents/shims/;
|
|
208
211
|
* the new layout uses ~/.agents-system/shims/. The leftover file causes the
|
|
209
|
-
* repair-prompt loop reported in
|
|
212
|
+
* repair-prompt loop reported in EXAMPLE-664 — `getPathShadowingExecutable` flags
|
|
210
213
|
* it as a shadow but `addShimsToPath` only edits rc files, never the file
|
|
211
214
|
* itself. Removing it ends the loop.
|
|
212
215
|
*/
|
package/dist/lib/shims.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import * as os from 'os';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
14
15
|
import { confirm, select } from '@inquirer/prompts';
|
|
15
16
|
import { getShimsDir, getVersionsDir, getBackupsDir, ensureAgentsDir } from './state.js';
|
|
16
17
|
export { getShimsDir };
|
|
@@ -175,10 +176,19 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
175
176
|
* .oauth_token file on Linux (keychain-less sandbox fallback).
|
|
176
177
|
* v11 — when no default is set or the configured version is not installed,
|
|
177
178
|
* interactively propose the latest already-installed version.
|
|
179
|
+
* v12 — helper calls inside generated shims use the absolute agents-cli
|
|
180
|
+
* entrypoint instead of PATH-resolved `agents`.
|
|
181
|
+
* v13 — validate agents.yaml version strings before constructing binary paths.
|
|
178
182
|
*/
|
|
179
|
-
export const SHIM_SCHEMA_VERSION =
|
|
183
|
+
export const SHIM_SCHEMA_VERSION = 13;
|
|
180
184
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
181
185
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
186
|
+
function shellQuote(value) {
|
|
187
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
188
|
+
}
|
|
189
|
+
function getAgentsBinForGeneratedShim() {
|
|
190
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'index.js');
|
|
191
|
+
}
|
|
182
192
|
/**
|
|
183
193
|
* Generate the full bash shim script for the given agent. The returned string
|
|
184
194
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
|
@@ -187,6 +197,7 @@ export function generateShimScript(agent) {
|
|
|
187
197
|
const agentConfig = AGENTS[agent];
|
|
188
198
|
const cliCommand = agentConfig.cliCommand;
|
|
189
199
|
const configDirName = `.${agent}`;
|
|
200
|
+
const agentsBin = shellQuote(getAgentsBinForGeneratedShim());
|
|
190
201
|
const managedEnv = agent === 'claude'
|
|
191
202
|
? `
|
|
192
203
|
# Claude stores OAuth credentials in the macOS keychain. Scope them to the
|
|
@@ -217,7 +228,7 @@ export CODEX_HOME="$VERSION_DIR/home/${configDirName}"
|
|
|
217
228
|
# Recompile rules if any rule/preset source has changed since last sync.
|
|
218
229
|
# Fast-path check (~10-20ms) when nothing changed; full recompile only on
|
|
219
230
|
# actual diff. Non-blocking failure — if the refresh errors, we still launch.
|
|
220
|
-
|
|
231
|
+
"$AGENTS_BIN" refresh-rules --agent "$AGENT" --agent-version "$VERSION" --quiet 2>/dev/null || true
|
|
221
232
|
`
|
|
222
233
|
: '';
|
|
223
234
|
const launchArgs = agent === 'codex' ? ' -c check_for_update_on_startup=false' : '';
|
|
@@ -228,9 +239,15 @@ agents refresh-rules --agent "$AGENT" --agent-version "$VERSION" --quiet 2>/dev/
|
|
|
228
239
|
|
|
229
240
|
AGENTS_SYSTEM_DIR="$HOME/.agents-system"
|
|
230
241
|
AGENTS_USER_DIR="$HOME/.agents"
|
|
242
|
+
AGENTS_BIN=${agentsBin}
|
|
231
243
|
AGENT="${agent}"
|
|
232
244
|
CLI_COMMAND="${cliCommand}"
|
|
233
245
|
|
|
246
|
+
if [ -z "$AGENTS_BIN" ] || [ ! -x "$AGENTS_BIN" ]; then
|
|
247
|
+
echo "agents: agents-cli entrypoint missing or not executable: $AGENTS_BIN" >&2
|
|
248
|
+
exit 127
|
|
249
|
+
fi
|
|
250
|
+
|
|
234
251
|
# Find project agents.yaml walking up from cwd (skip $HOME/.agents-system/agents.yaml)
|
|
235
252
|
find_project_version() {
|
|
236
253
|
local dir="$PWD"
|
|
@@ -325,7 +342,7 @@ if [ -z "$VERSION" ]; then
|
|
|
325
342
|
read -r _ans </dev/tty
|
|
326
343
|
case "$_ans" in
|
|
327
344
|
""|y|Y)
|
|
328
|
-
|
|
345
|
+
"$AGENTS_BIN" use "$AGENT" "$LATEST" >/dev/null 2>&1
|
|
329
346
|
VERSION="$LATEST"
|
|
330
347
|
VERSION_SOURCE="default"
|
|
331
348
|
;;
|
|
@@ -345,6 +362,11 @@ if [ -z "$VERSION" ]; then
|
|
|
345
362
|
fi
|
|
346
363
|
fi
|
|
347
364
|
|
|
365
|
+
if [[ ! "$VERSION" =~ ^(latest|[A-Za-z0-9._+-]{1,64})$ || "$VERSION" == *..* ]]; then
|
|
366
|
+
echo "agents: invalid version in agents.yaml for $AGENT: $VERSION. Allowed: latest or [A-Za-z0-9._+-]{1,64}" >&2
|
|
367
|
+
exit 1
|
|
368
|
+
fi
|
|
369
|
+
|
|
348
370
|
VERSION_DIR="$AGENTS_USER_DIR/.history/versions/$AGENT/$VERSION"
|
|
349
371
|
BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
|
|
350
372
|
|
|
@@ -366,7 +388,7 @@ if [ ! -x "$BINARY" ]; then
|
|
|
366
388
|
}
|
|
367
389
|
|
|
368
390
|
# Run install in background with spinner
|
|
369
|
-
|
|
391
|
+
"$AGENTS_BIN" add "$AGENT@$VERSION" --yes >/dev/null 2>&1 &
|
|
370
392
|
install_pid=$!
|
|
371
393
|
spin $install_pid
|
|
372
394
|
wait $install_pid
|
|
@@ -387,7 +409,7 @@ if [ ! -x "$BINARY" ]; then
|
|
|
387
409
|
read -r _ans </dev/tty
|
|
388
410
|
case "$_ans" in
|
|
389
411
|
""|y|Y)
|
|
390
|
-
|
|
412
|
+
"$AGENTS_BIN" use "$AGENT" "$LATEST" >/dev/null 2>&1
|
|
391
413
|
VERSION="$LATEST"
|
|
392
414
|
VERSION_DIR="$AGENTS_USER_DIR/.history/versions/$AGENT/$VERSION"
|
|
393
415
|
BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
|
|
@@ -412,7 +434,7 @@ fi
|
|
|
412
434
|
# Sync project-scoped resources into version home if a project .agents/ is present
|
|
413
435
|
PROJECT_AGENTS_DIR=$(find_project_agents_dir)
|
|
414
436
|
if [ -n "$PROJECT_AGENTS_DIR" ]; then
|
|
415
|
-
|
|
437
|
+
"$AGENTS_BIN" sync --agent "$AGENT" --agent-version "$VERSION" --project-dir "$PROJECT_AGENTS_DIR" --quiet >/dev/null 2>&1
|
|
416
438
|
fi
|
|
417
439
|
${refreshRulesCall}${managedEnv}
|
|
418
440
|
|
|
@@ -1144,7 +1166,7 @@ export function getPathShadowingExecutable(agent) {
|
|
|
1144
1166
|
* Delete the legacy ~/.agents/shims/<cli> file if it exists, returning whether
|
|
1145
1167
|
* anything was removed. Pre-split installs put shims under ~/.agents/shims/;
|
|
1146
1168
|
* the new layout uses ~/.agents-system/shims/. The leftover file causes the
|
|
1147
|
-
* repair-prompt loop reported in
|
|
1169
|
+
* repair-prompt loop reported in EXAMPLE-664 — `getPathShadowingExecutable` flags
|
|
1148
1170
|
* it as a shadow but `addShimsToPath` only edits rc files, never the file
|
|
1149
1171
|
* itself. Removing it ends the loop.
|
|
1150
1172
|
*/
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `agents repo push`.
|
|
14
14
|
* - ~/.agents/.cache/ — regenerable runtime data (shims, packages, helpers
|
|
15
15
|
* for daemon/pty, terminals, cloud, drive, browser
|
|
16
|
-
* chrome-data, logs,
|
|
16
|
+
* chrome-data, logs, companion). Gitignored.
|
|
17
17
|
*
|
|
18
18
|
* Resolution precedence for resources: project > user > system.
|
|
19
19
|
* Every module that needs a path or reads/writes agents.yaml goes through here.
|
|
@@ -135,8 +135,8 @@ export declare function getTerminalsDir(): string;
|
|
|
135
135
|
export declare function getLogsDir(): string;
|
|
136
136
|
/** Path to per-process runtime state (~/.agents/.cache/state/). */
|
|
137
137
|
export declare function getRuntimeStateDir(): string;
|
|
138
|
-
/** Path to
|
|
139
|
-
export declare function
|
|
138
|
+
/** Path to companion-extension scratch (~/.agents/.cache/companion/). */
|
|
139
|
+
export declare function getCompanionDir(): string;
|
|
140
140
|
/** Path to browser runtime data — chrome-data, pids (~/.agents/.cache/browser/). */
|
|
141
141
|
export declare function getBrowserRuntimeDir(): string;
|
|
142
142
|
/** Path to helper subprocess scratch (~/.agents/.cache/helpers/). */
|
|
@@ -195,8 +195,8 @@ export declare function createDefaultMeta(): Meta;
|
|
|
195
195
|
export declare function readMeta(): Meta;
|
|
196
196
|
/** Serialize and write agents.yaml to the user repo, invalidating the in-memory cache. */
|
|
197
197
|
export declare function writeMeta(meta: Meta): void;
|
|
198
|
-
/**
|
|
199
|
-
export declare function updateMeta(updates: Partial<Meta>): Meta;
|
|
198
|
+
/** Update agents.yaml under lock and return the new state. */
|
|
199
|
+
export declare function updateMeta(updates: Partial<Meta> | ((meta: Meta) => Meta)): Meta;
|
|
200
200
|
/** Derive a filesystem-safe local clone path for a package source URL. */
|
|
201
201
|
export declare function getPackageLocalPath(source: string): string;
|
|
202
202
|
import type { AgentId, ResourceType, VersionResources, ResourcePattern } from './types.js';
|
package/dist/lib/state.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `agents repo push`.
|
|
14
14
|
* - ~/.agents/.cache/ — regenerable runtime data (shims, packages, helpers
|
|
15
15
|
* for daemon/pty, terminals, cloud, drive, browser
|
|
16
|
-
* chrome-data, logs,
|
|
16
|
+
* chrome-data, logs, companion). Gitignored.
|
|
17
17
|
*
|
|
18
18
|
* Resolution precedence for resources: project > user > system.
|
|
19
19
|
* Every module that needs a path or reads/writes agents.yaml goes through here.
|
|
@@ -22,6 +22,7 @@ import * as fs from 'fs';
|
|
|
22
22
|
import * as path from 'path';
|
|
23
23
|
import * as os from 'os';
|
|
24
24
|
import * as yaml from 'yaml';
|
|
25
|
+
import { ensureLockTarget, atomicWriteFileSync, withFileLock } from './fs-atomic.js';
|
|
25
26
|
import { SEEDED_REGISTRIES } from './types.js';
|
|
26
27
|
const HOME = process.env.HOME ?? os.homedir();
|
|
27
28
|
// ─── Root directories ─────────────────────────────────────────────────────────
|
|
@@ -73,7 +74,7 @@ const DRIVE_DIR = path.join(CACHE_DIR, 'drive');
|
|
|
73
74
|
const TERMINALS_DIR = path.join(CACHE_DIR, 'terminals');
|
|
74
75
|
const LOGS_DIR = path.join(CACHE_DIR, 'logs');
|
|
75
76
|
const RUNTIME_STATE_DIR = path.join(CACHE_DIR, 'state');
|
|
76
|
-
const SWARMIFY_DIR = path.join(CACHE_DIR, '
|
|
77
|
+
const SWARMIFY_DIR = path.join(CACHE_DIR, 'companion');
|
|
77
78
|
const BROWSER_RUNTIME_DIR = path.join(CACHE_DIR, 'browser');
|
|
78
79
|
const HELPERS_DIR = path.join(CACHE_DIR, 'helpers');
|
|
79
80
|
const DAEMON_DIR = path.join(HELPERS_DIR, 'daemon');
|
|
@@ -292,8 +293,8 @@ export function getTerminalsDir() { return TERMINALS_DIR; }
|
|
|
292
293
|
export function getLogsDir() { return LOGS_DIR; }
|
|
293
294
|
/** Path to per-process runtime state (~/.agents/.cache/state/). */
|
|
294
295
|
export function getRuntimeStateDir() { return RUNTIME_STATE_DIR; }
|
|
295
|
-
/** Path to
|
|
296
|
-
export function
|
|
296
|
+
/** Path to companion-extension scratch (~/.agents/.cache/companion/). */
|
|
297
|
+
export function getCompanionDir() { return SWARMIFY_DIR; }
|
|
297
298
|
/** Path to browser runtime data — chrome-data, pids (~/.agents/.cache/browser/). */
|
|
298
299
|
export function getBrowserRuntimeDir() { return BROWSER_RUNTIME_DIR; }
|
|
299
300
|
/** Path to helper subprocess scratch (~/.agents/.cache/helpers/). */
|
|
@@ -411,6 +412,34 @@ export function createDefaultMeta() {
|
|
|
411
412
|
return {};
|
|
412
413
|
}
|
|
413
414
|
let metaCache = null;
|
|
415
|
+
let metaLockDepth = 0;
|
|
416
|
+
function withMetaLock(fn) {
|
|
417
|
+
ensureAgentsDir();
|
|
418
|
+
if (metaLockDepth > 0) {
|
|
419
|
+
metaLockDepth++;
|
|
420
|
+
try {
|
|
421
|
+
return fn();
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
metaLockDepth--;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
ensureLockTarget(META_FILE, META_HEADER + yaml.stringify(createDefaultMeta()), 0o700);
|
|
428
|
+
return withFileLock(META_FILE, () => {
|
|
429
|
+
metaLockDepth = 1;
|
|
430
|
+
try {
|
|
431
|
+
return fn();
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
metaLockDepth = 0;
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function writeMetaUnlocked(meta) {
|
|
439
|
+
const content = META_HEADER + yaml.stringify(meta);
|
|
440
|
+
atomicWriteFileSync(META_FILE, content);
|
|
441
|
+
metaCache = null;
|
|
442
|
+
}
|
|
414
443
|
function applyRegistrySeeds(meta) {
|
|
415
444
|
const seeded = new Set(meta.seededPresets || []);
|
|
416
445
|
let changed = false;
|
|
@@ -540,17 +569,18 @@ export function readMeta() {
|
|
|
540
569
|
}
|
|
541
570
|
/** Serialize and write agents.yaml to the user repo, invalidating the in-memory cache. */
|
|
542
571
|
export function writeMeta(meta) {
|
|
543
|
-
|
|
544
|
-
const content = META_HEADER + yaml.stringify(meta);
|
|
545
|
-
fs.writeFileSync(META_FILE, content, 'utf-8');
|
|
546
|
-
metaCache = null;
|
|
572
|
+
withMetaLock(() => writeMetaUnlocked(meta));
|
|
547
573
|
}
|
|
548
|
-
/**
|
|
574
|
+
/** Update agents.yaml under lock and return the new state. */
|
|
549
575
|
export function updateMeta(updates) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
576
|
+
return withMetaLock(() => {
|
|
577
|
+
const meta = readMeta();
|
|
578
|
+
const newMeta = typeof updates === 'function'
|
|
579
|
+
? updates(meta)
|
|
580
|
+
: { ...meta, ...updates };
|
|
581
|
+
writeMetaUnlocked(newMeta);
|
|
582
|
+
return newMeta;
|
|
583
|
+
});
|
|
554
584
|
}
|
|
555
585
|
/** Derive a filesystem-safe local clone path for a package source URL. */
|
|
556
586
|
export function getPackageLocalPath(source) {
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -295,7 +295,7 @@ export function checkCliAvailable(agentType) {
|
|
|
295
295
|
return [false, `Unknown agent type: ${agentType}`];
|
|
296
296
|
}
|
|
297
297
|
try {
|
|
298
|
-
const whichPath =
|
|
298
|
+
const whichPath = execFileSync('which', [executable], { encoding: 'utf-8' }).trim();
|
|
299
299
|
return [true, whichPath];
|
|
300
300
|
}
|
|
301
301
|
catch {
|