@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.
- package/CHANGELOG.md +67 -0
- package/README.md +32 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.js +1 -0
- 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 +22 -2
- 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/daemon.d.ts +5 -1
- package/dist/lib/daemon.js +63 -14
- package/dist/lib/devices/resolve-target.d.ts +6 -0
- package/dist/lib/devices/resolve-target.js +9 -3
- 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/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/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/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/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/package.json +3 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type Database from '../sqlite.js';
|
|
2
|
+
import { type IndexedToolCall } from './tool-calls.js';
|
|
3
|
+
import type { SessionMeta } from './types.js';
|
|
4
|
+
export declare function canonicalToolLedgerPath(filePath: string): string;
|
|
5
|
+
/** Resolve the transcript that actually carries tool events for split-file harnesses. */
|
|
6
|
+
export declare function toolEvidenceSourcePath(filePath: string, agent: string): string;
|
|
7
|
+
/** Remove cached tool evidence when its source transcript no longer exists. */
|
|
8
|
+
export declare function purgeToolCalls(db: Database.Database, sessionId: string): void;
|
|
9
|
+
/** Purge deleted direct children when a transcript directory's stamp changes. */
|
|
10
|
+
export declare function purgeMissingToolCallsInDirectory(db: Database.Database, dirPath: string, currentFilePaths: string[]): number;
|
|
11
|
+
/** Persist one parser batch and its file stamp atomically. */
|
|
12
|
+
export declare function persistToolCalls(db: Database.Database, session: SessionMeta, calls: IndexedToolCall[], sourceStamp: {
|
|
13
|
+
fileMtimeMs: number;
|
|
14
|
+
fileSize: number;
|
|
15
|
+
}, mode?: 'replace' | 'append', maxSessionBytes?: number): void;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { TOOL_INDEX_VERSION, TOOL_INDEX_LIMIT_ORDINAL, TOOL_SESSION_EVIDENCE_MAX_BYTES, toolCallEvidenceBytes, toolCallKey, } from './tool-calls.js';
|
|
4
|
+
export function canonicalToolLedgerPath(filePath) {
|
|
5
|
+
let probe = path.resolve(filePath);
|
|
6
|
+
const suffix = [];
|
|
7
|
+
while (true) {
|
|
8
|
+
try {
|
|
9
|
+
return path.join(fs.realpathSync(probe), ...suffix.reverse());
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
const parent = path.dirname(probe);
|
|
13
|
+
if (parent === probe)
|
|
14
|
+
return path.resolve(filePath);
|
|
15
|
+
suffix.push(path.basename(probe));
|
|
16
|
+
probe = parent;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Resolve the transcript that actually carries tool events for split-file harnesses. */
|
|
21
|
+
export function toolEvidenceSourcePath(filePath, agent) {
|
|
22
|
+
if (agent === 'kimi' && path.basename(filePath) === 'state.json') {
|
|
23
|
+
return path.join(path.dirname(filePath), 'agents', 'main', 'wire.jsonl');
|
|
24
|
+
}
|
|
25
|
+
if (agent === 'grok' && path.basename(filePath) === 'summary.json') {
|
|
26
|
+
return path.join(path.dirname(filePath), 'chat_history.jsonl');
|
|
27
|
+
}
|
|
28
|
+
return filePath;
|
|
29
|
+
}
|
|
30
|
+
function deleteSessionCalls(db, sessionId) {
|
|
31
|
+
const keys = db.prepare(`SELECT call_key FROM tool_calls WHERE session_id = ?`).all(sessionId);
|
|
32
|
+
for (const { call_key } of keys) {
|
|
33
|
+
db.prepare(`DELETE FROM tool_call_programs WHERE call_key = ?`).run(call_key);
|
|
34
|
+
db.prepare(`DELETE FROM tool_program_occurrences WHERE call_key = ?`).run(call_key);
|
|
35
|
+
db.prepare(`DELETE FROM tool_call_text WHERE call_key = ?`).run(call_key);
|
|
36
|
+
}
|
|
37
|
+
db.prepare(`DELETE FROM tool_calls WHERE session_id = ?`).run(sessionId);
|
|
38
|
+
}
|
|
39
|
+
/** Remove cached tool evidence when its source transcript no longer exists. */
|
|
40
|
+
export function purgeToolCalls(db, sessionId) {
|
|
41
|
+
deleteSessionCalls(db, sessionId);
|
|
42
|
+
db.prepare(`DELETE FROM tool_scan_ledger WHERE session_id = ?`).run(sessionId);
|
|
43
|
+
}
|
|
44
|
+
/** Purge deleted direct children when a transcript directory's stamp changes. */
|
|
45
|
+
export function purgeMissingToolCallsInDirectory(db, dirPath, currentFilePaths) {
|
|
46
|
+
const normalizedDir = path.normalize(dirPath);
|
|
47
|
+
const prefix = normalizedDir.endsWith(path.sep) ? normalizedDir : normalizedDir + path.sep;
|
|
48
|
+
const present = new Set(currentFilePaths.map((filePath) => path.normalize(filePath)));
|
|
49
|
+
const rows = db.prepare(`
|
|
50
|
+
SELECT s.id, s.file_path, s.agent
|
|
51
|
+
FROM sessions s
|
|
52
|
+
WHERE substr(s.file_path, 1, ?) = ?
|
|
53
|
+
`).all(prefix.length, prefix);
|
|
54
|
+
let purged = 0;
|
|
55
|
+
for (const row of rows) {
|
|
56
|
+
const filePath = path.normalize(row.file_path);
|
|
57
|
+
if (path.dirname(filePath) !== normalizedDir || present.has(filePath))
|
|
58
|
+
continue;
|
|
59
|
+
purgeToolCalls(db, row.id);
|
|
60
|
+
purged++;
|
|
61
|
+
}
|
|
62
|
+
return purged;
|
|
63
|
+
}
|
|
64
|
+
/** Persist one parser batch and its file stamp atomically. */
|
|
65
|
+
export function persistToolCalls(db, session, calls, sourceStamp, mode = 'replace', maxSessionBytes = TOOL_SESSION_EVIDENCE_MAX_BYTES) {
|
|
66
|
+
const sourcePath = toolEvidenceSourcePath(session.filePath, session.agent);
|
|
67
|
+
const insertCall = db.prepare(`
|
|
68
|
+
INSERT INTO tool_calls (
|
|
69
|
+
call_key, session_id, ordinal, source_call_id, timestamp, tool, input,
|
|
70
|
+
outcome, exit_code, status_code, error_code, output, error, parse_error
|
|
71
|
+
, evidence_bytes
|
|
72
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
73
|
+
ON CONFLICT(session_id, ordinal) DO UPDATE SET
|
|
74
|
+
source_call_id = excluded.source_call_id,
|
|
75
|
+
timestamp = excluded.timestamp,
|
|
76
|
+
tool = excluded.tool,
|
|
77
|
+
input = excluded.input,
|
|
78
|
+
outcome = excluded.outcome,
|
|
79
|
+
exit_code = excluded.exit_code,
|
|
80
|
+
status_code = excluded.status_code,
|
|
81
|
+
error_code = excluded.error_code,
|
|
82
|
+
output = excluded.output,
|
|
83
|
+
error = excluded.error,
|
|
84
|
+
parse_error = excluded.parse_error,
|
|
85
|
+
evidence_bytes = excluded.evidence_bytes
|
|
86
|
+
`);
|
|
87
|
+
const insertProgram = db.prepare(`INSERT OR IGNORE INTO tool_call_programs (call_key, program) VALUES (?, ?)`);
|
|
88
|
+
const insertOccurrence = db.prepare(`
|
|
89
|
+
INSERT INTO tool_program_occurrences (call_key, occurrence_ordinal, program, role)
|
|
90
|
+
VALUES (?, ?, ?, ?)
|
|
91
|
+
`);
|
|
92
|
+
const insertText = db.prepare(`INSERT INTO tool_call_text (call_key, tool, input, output, error) VALUES (?, ?, ?, ?, ?)`);
|
|
93
|
+
const deletePrograms = db.prepare(`DELETE FROM tool_call_programs WHERE call_key = ?`);
|
|
94
|
+
const deleteOccurrences = db.prepare(`DELETE FROM tool_program_occurrences WHERE call_key = ?`);
|
|
95
|
+
const deleteText = db.prepare(`DELETE FROM tool_call_text WHERE call_key = ?`);
|
|
96
|
+
const deleteCall = db.prepare(`DELETE FROM tool_calls WHERE call_key = ?`);
|
|
97
|
+
const writeLedger = db.prepare(`
|
|
98
|
+
INSERT INTO tool_scan_ledger (
|
|
99
|
+
session_id, file_path, file_mtime_ms, file_size, extractor_version, indexed_at, call_count,
|
|
100
|
+
evidence_bytes
|
|
101
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
102
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
103
|
+
file_path = excluded.file_path,
|
|
104
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
105
|
+
file_size = excluded.file_size,
|
|
106
|
+
extractor_version = excluded.extractor_version,
|
|
107
|
+
indexed_at = excluded.indexed_at,
|
|
108
|
+
call_count = excluded.call_count,
|
|
109
|
+
evidence_bytes = excluded.evidence_bytes
|
|
110
|
+
`);
|
|
111
|
+
const txn = db.transaction(() => {
|
|
112
|
+
if (mode === 'replace')
|
|
113
|
+
deleteSessionCalls(db, session.id);
|
|
114
|
+
const ledgerPath = canonicalToolLedgerPath(sourcePath);
|
|
115
|
+
const priorLedger = mode === 'append'
|
|
116
|
+
? db.prepare(`SELECT call_count, evidence_bytes FROM tool_scan_ledger WHERE session_id = ?`).get(session.id)
|
|
117
|
+
: undefined;
|
|
118
|
+
if (mode === 'append' && !priorLedger) {
|
|
119
|
+
throw new Error('Append tool evidence requires an existing tool ledger row.');
|
|
120
|
+
}
|
|
121
|
+
if (mode === 'append' && calls.length === 0) {
|
|
122
|
+
writeLedger.run(session.id, ledgerPath, sourceStamp.fileMtimeMs, sourceStamp.fileSize, TOOL_INDEX_VERSION, Date.now(), priorLedger.call_count, priorLedger.evidence_bytes);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const limitKey = toolCallKey(session.id, TOOL_INDEX_LIMIT_ORDINAL);
|
|
126
|
+
const existingSize = db.prepare(`
|
|
127
|
+
SELECT evidence_bytes FROM tool_calls WHERE session_id = ? AND ordinal = ?
|
|
128
|
+
`);
|
|
129
|
+
const priorLimit = mode === 'append'
|
|
130
|
+
? existingSize.get(session.id, TOOL_INDEX_LIMIT_ORDINAL)
|
|
131
|
+
: undefined;
|
|
132
|
+
deletePrograms.run(limitKey);
|
|
133
|
+
deleteOccurrences.run(limitKey);
|
|
134
|
+
deleteText.run(limitKey);
|
|
135
|
+
deleteCall.run(limitKey);
|
|
136
|
+
const existingRows = mode === 'append'
|
|
137
|
+
? calls.map((call) => ({
|
|
138
|
+
ordinal: call.ordinal,
|
|
139
|
+
bytes: existingSize.get(session.id, call.ordinal)?.evidence_bytes ?? 0,
|
|
140
|
+
}))
|
|
141
|
+
: [];
|
|
142
|
+
const existingByOrdinal = new Map(existingRows.map((row) => [row.ordinal, row.bytes]));
|
|
143
|
+
let storedBytes = priorLedger?.evidence_bytes ?? 0;
|
|
144
|
+
let count = priorLedger?.call_count ?? 0;
|
|
145
|
+
if (priorLimit) {
|
|
146
|
+
storedBytes -= priorLimit.evidence_bytes;
|
|
147
|
+
count--;
|
|
148
|
+
}
|
|
149
|
+
const limitCall = {
|
|
150
|
+
ordinal: TOOL_INDEX_LIMIT_ORDINAL,
|
|
151
|
+
timestamp: session.timestamp,
|
|
152
|
+
tool: 'index_limit',
|
|
153
|
+
programs: [],
|
|
154
|
+
programOccurrences: [],
|
|
155
|
+
input: `Tool evidence stopped at the ${Math.floor(maxSessionBytes / 1024 / 1024)} MiB per-session payload limit.`,
|
|
156
|
+
outcome: 'unknown',
|
|
157
|
+
parseError: 'Additional tool calls were not indexed for this session.',
|
|
158
|
+
};
|
|
159
|
+
const limitBytes = toolCallEvidenceBytes(limitCall);
|
|
160
|
+
const ordinaryLimit = Math.max(0, maxSessionBytes - limitBytes);
|
|
161
|
+
const accepted = [];
|
|
162
|
+
let truncated = false;
|
|
163
|
+
for (const call of [...calls].sort((a, b) => a.ordinal - b.ordinal)) {
|
|
164
|
+
const replacingBytes = existingByOrdinal.get(call.ordinal) ?? 0;
|
|
165
|
+
const nextBytes = storedBytes - replacingBytes + toolCallEvidenceBytes(call);
|
|
166
|
+
if (nextBytes > ordinaryLimit) {
|
|
167
|
+
truncated = true;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
storedBytes = nextBytes;
|
|
171
|
+
accepted.push(call);
|
|
172
|
+
if (!existingByOrdinal.has(call.ordinal) || replacingBytes === 0)
|
|
173
|
+
count++;
|
|
174
|
+
}
|
|
175
|
+
if (truncated) {
|
|
176
|
+
if (storedBytes + limitBytes <= maxSessionBytes) {
|
|
177
|
+
accepted.push(limitCall);
|
|
178
|
+
storedBytes += limitBytes;
|
|
179
|
+
count++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const call of accepted) {
|
|
183
|
+
const key = toolCallKey(session.id, call.ordinal);
|
|
184
|
+
insertCall.run(key, session.id, call.ordinal, call.sourceCallId ?? null, call.timestamp, call.tool, call.input, call.outcome, call.exitCode ?? null, call.statusCode ?? null, call.errorCode ?? null, call.output ?? null, call.error ?? null, call.parseError ?? null, toolCallEvidenceBytes(call));
|
|
185
|
+
deletePrograms.run(key);
|
|
186
|
+
deleteOccurrences.run(key);
|
|
187
|
+
deleteText.run(key);
|
|
188
|
+
for (const program of call.programs)
|
|
189
|
+
insertProgram.run(key, program);
|
|
190
|
+
call.programOccurrences.forEach((occurrence, occurrenceOrdinal) => {
|
|
191
|
+
insertOccurrence.run(key, occurrenceOrdinal, occurrence.program, occurrence.role);
|
|
192
|
+
});
|
|
193
|
+
insertText.run(key, call.tool, call.input, call.output ?? '', call.error ?? '');
|
|
194
|
+
}
|
|
195
|
+
writeLedger.run(session.id, ledgerPath, sourceStamp.fileMtimeMs, sourceStamp.fileSize, TOOL_INDEX_VERSION, Date.now(), count, storedBytes);
|
|
196
|
+
});
|
|
197
|
+
txn();
|
|
198
|
+
}
|
|
@@ -24,10 +24,17 @@ export interface SessionEvent {
|
|
|
24
24
|
role?: 'user' | 'assistant';
|
|
25
25
|
content?: string;
|
|
26
26
|
tool?: string;
|
|
27
|
+
/** Harness-native call identity, used to correlate concurrent results. */
|
|
28
|
+
callId?: string;
|
|
27
29
|
args?: Record<string, any>;
|
|
28
30
|
path?: string;
|
|
29
31
|
command?: string;
|
|
30
32
|
success?: boolean;
|
|
33
|
+
/** Structured harness outcome; never inferred from free-text output. */
|
|
34
|
+
outcome?: 'ok' | 'error' | 'unknown';
|
|
35
|
+
exitCode?: number;
|
|
36
|
+
statusCode?: number;
|
|
37
|
+
errorCode?: string;
|
|
31
38
|
output?: string;
|
|
32
39
|
/** Internal: marks tool_use events from local commands */
|
|
33
40
|
_local?: boolean;
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -272,7 +272,16 @@ export declare function getCompanionDir(): string;
|
|
|
272
272
|
export declare function getBrowserRuntimeDir(): string;
|
|
273
273
|
/** Path to helper subprocess scratch (~/.agents/.cache/helpers/). */
|
|
274
274
|
export declare function getHelpersDir(): string;
|
|
275
|
-
/**
|
|
275
|
+
/**
|
|
276
|
+
* Path to scheduler daemon scratch (~/.agents/.cache/helpers/daemon/) — holds
|
|
277
|
+
* the daemon pid file, heartbeat, start lock, and log. AGENTS_DAEMON_DIR
|
|
278
|
+
* redirects it to a fork-private temp so daemon tests (which write pid/heartbeat
|
|
279
|
+
* files and acquire the real start lock) can never clobber a live daemon's state
|
|
280
|
+
* on a dev machine — the surgical mirror of AGENTS_DEVICES_DIR /
|
|
281
|
+
* AGENTS_HOOK_SHIMS_DIR, leaving HOME untouched. Read at CALL time (daemon.ts
|
|
282
|
+
* resolves every path helper through this), so tests/setup.ts can set it before
|
|
283
|
+
* the daemon module is exercised. Never set in production code.
|
|
284
|
+
*/
|
|
276
285
|
export declare function getDaemonDir(): string;
|
|
277
286
|
/** Path to PTY server scratch (~/.agents/.cache/helpers/pty/). */
|
|
278
287
|
export declare function getPtyDir(): string;
|
package/dist/lib/state.js
CHANGED
|
@@ -579,8 +579,17 @@ export function getCompanionDir() { return COMPANION_CACHE_DIR; }
|
|
|
579
579
|
export function getBrowserRuntimeDir() { return BROWSER_RUNTIME_DIR; }
|
|
580
580
|
/** Path to helper subprocess scratch (~/.agents/.cache/helpers/). */
|
|
581
581
|
export function getHelpersDir() { return HELPERS_DIR; }
|
|
582
|
-
/**
|
|
583
|
-
|
|
582
|
+
/**
|
|
583
|
+
* Path to scheduler daemon scratch (~/.agents/.cache/helpers/daemon/) — holds
|
|
584
|
+
* the daemon pid file, heartbeat, start lock, and log. AGENTS_DAEMON_DIR
|
|
585
|
+
* redirects it to a fork-private temp so daemon tests (which write pid/heartbeat
|
|
586
|
+
* files and acquire the real start lock) can never clobber a live daemon's state
|
|
587
|
+
* on a dev machine — the surgical mirror of AGENTS_DEVICES_DIR /
|
|
588
|
+
* AGENTS_HOOK_SHIMS_DIR, leaving HOME untouched. Read at CALL time (daemon.ts
|
|
589
|
+
* resolves every path helper through this), so tests/setup.ts can set it before
|
|
590
|
+
* the daemon module is exercised. Never set in production code.
|
|
591
|
+
*/
|
|
592
|
+
export function getDaemonDir() { return process.env.AGENTS_DAEMON_DIR ?? DAEMON_DIR; }
|
|
584
593
|
/** Path to PTY server scratch (~/.agents/.cache/helpers/pty/). */
|
|
585
594
|
export function getPtyDir() { return PTY_DIR; }
|
|
586
595
|
/** Path to tmux scratch (~/.agents/.cache/helpers/tmux/) — shared server socket + per-session meta JSONs. */
|
|
@@ -26,10 +26,9 @@ export declare function remoteDefaultBranch(target: string, repoPath: string): s
|
|
|
26
26
|
* Create a worktree on the host for a teammate, branched off the freshly-fetched
|
|
27
27
|
* default branch. Returns the absolute worktree path on the host.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
* origin
|
|
31
|
-
*
|
|
32
|
-
* checkout on the host can't fork the teammate off old code.
|
|
29
|
+
* Same base policy as local `createWorktree`: fetch origin, then
|
|
30
|
+
* `worktree add -b … origin/<default>` so a stale checkout can't fork the
|
|
31
|
+
* teammate off old code.
|
|
33
32
|
*/
|
|
34
33
|
export declare function createRemoteWorktree(target: string, repoPath: string, worktreeName: string): string;
|
|
35
34
|
/**
|
|
@@ -87,10 +87,9 @@ export function remoteDefaultBranch(target, repoPath) {
|
|
|
87
87
|
* Create a worktree on the host for a teammate, branched off the freshly-fetched
|
|
88
88
|
* default branch. Returns the absolute worktree path on the host.
|
|
89
89
|
*
|
|
90
|
-
*
|
|
91
|
-
* origin
|
|
92
|
-
*
|
|
93
|
-
* checkout on the host can't fork the teammate off old code.
|
|
90
|
+
* Same base policy as local `createWorktree`: fetch origin, then
|
|
91
|
+
* `worktree add -b … origin/<default>` so a stale checkout can't fork the
|
|
92
|
+
* teammate off old code.
|
|
94
93
|
*/
|
|
95
94
|
export function createRemoteWorktree(target, repoPath, worktreeName) {
|
|
96
95
|
assertValidSshTarget(target);
|
|
@@ -22,7 +22,17 @@ export declare const DEFAULT_DIFF_MAX_BYTES = 200000;
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function gitDiff(worktreePath: string, maxBytes?: number): Promise<string>;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Resolve the repo's default branch name (origin/HEAD → `main`/`master`/…).
|
|
26
|
+
* Refreshes origin/HEAD first so a repo cloned before the default was set resolves.
|
|
27
|
+
* Falls back to `main` when origin/HEAD isn't set.
|
|
28
|
+
*/
|
|
29
|
+
export declare function localDefaultBranch(gitRoot: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Create a new git worktree for a teammate, branched off the freshly-fetched
|
|
32
|
+
* default branch. Fetches `origin` first and bases the branch on
|
|
33
|
+
* `origin/<default>` so a stale local checkout cannot fork teammates off old
|
|
34
|
+
* code. Matches `createRemoteWorktree` — local and remote isolation share one
|
|
35
|
+
* base policy.
|
|
26
36
|
*
|
|
27
37
|
* @param repoDir - Directory inside the git repository
|
|
28
38
|
* @param worktreeName - Name for the worktree (used in path and branch)
|
|
@@ -77,7 +77,34 @@ export async function gitDiff(worktreePath, maxBytes = DEFAULT_DIFF_MAX_BYTES) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
80
|
+
* Resolve the repo's default branch name (origin/HEAD → `main`/`master`/…).
|
|
81
|
+
* Refreshes origin/HEAD first so a repo cloned before the default was set resolves.
|
|
82
|
+
* Falls back to `main` when origin/HEAD isn't set.
|
|
83
|
+
*/
|
|
84
|
+
export async function localDefaultBranch(gitRoot) {
|
|
85
|
+
try {
|
|
86
|
+
await execFileAsync('git', ['remote', 'set-head', 'origin', '--auto'], { cwd: gitRoot });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Offline / no origin — fall through to symbolic-ref or main.
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const { stdout } = await execFileAsync('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { cwd: gitRoot });
|
|
93
|
+
const base = stdout.trim().replace(/^origin\//, '');
|
|
94
|
+
if (base)
|
|
95
|
+
return base;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// no origin/HEAD
|
|
99
|
+
}
|
|
100
|
+
return 'main';
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a new git worktree for a teammate, branched off the freshly-fetched
|
|
104
|
+
* default branch. Fetches `origin` first and bases the branch on
|
|
105
|
+
* `origin/<default>` so a stale local checkout cannot fork teammates off old
|
|
106
|
+
* code. Matches `createRemoteWorktree` — local and remote isolation share one
|
|
107
|
+
* base policy.
|
|
81
108
|
*
|
|
82
109
|
* @param repoDir - Directory inside the git repository
|
|
83
110
|
* @param worktreeName - Name for the worktree (used in path and branch)
|
|
@@ -90,10 +117,21 @@ export async function createWorktree(repoDir, worktreeName) {
|
|
|
90
117
|
const gitRoot = await getGitRoot(repoDir);
|
|
91
118
|
const worktreePath = safeJoin(path.join(gitRoot, '.agents', 'worktrees'), worktreeName);
|
|
92
119
|
const branchName = `agents/${worktreeName}`;
|
|
120
|
+
const base = await localDefaultBranch(gitRoot);
|
|
93
121
|
await fs.mkdir(path.dirname(worktreePath), { recursive: true });
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
// Fetch first so origin/<default> is current. Fail loud on network errors —
|
|
123
|
+
// silent fallback to HEAD is how swarms write on a days-stale base and only
|
|
124
|
+
// discover it at merge time.
|
|
125
|
+
try {
|
|
126
|
+
await execFileAsync('git', ['fetch', 'origin'], { cwd: gitRoot });
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const detail = (err?.stderr || err?.message || String(err)).toString().trim();
|
|
130
|
+
throw new Error(`createWorktree: git fetch origin failed in ${gitRoot}` +
|
|
131
|
+
(detail ? `: ${detail}` : '') +
|
|
132
|
+
`. Cannot base a teammate worktree on a stale remote-tracking ref.`);
|
|
133
|
+
}
|
|
134
|
+
await execFileAsync('git', ['worktree', 'add', '-b', branchName, worktreePath, `origin/${base}`], { cwd: gitRoot });
|
|
97
135
|
return worktreePath;
|
|
98
136
|
}
|
|
99
137
|
/**
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -11,6 +11,23 @@ import type { FeedBroadcastConfig } from './feed-broadcast.js';
|
|
|
11
11
|
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes';
|
|
12
12
|
/** How `agents run <agent>` chooses an installed version when none is pinned. */
|
|
13
13
|
export type RunStrategy = 'pinned' | 'available' | 'balanced';
|
|
14
|
+
/**
|
|
15
|
+
* Reserved `<agent>` keyword for `agents run auto` — full-auto dispatch:
|
|
16
|
+
* host (14d launch affinity) → harness (best-account headroom, weighted) →
|
|
17
|
+
* account (balanced). Lives in lib (not commands/) because both the run
|
|
18
|
+
* command (exec.ts) and the host dispatch layer (hosts/dispatch.ts, which
|
|
19
|
+
* arms the chain-hop guard for remote `run auto`) must agree on it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const RUN_AUTO_KEYWORD = "auto";
|
|
22
|
+
/**
|
|
23
|
+
* Env var a host dispatcher exports into the remote SHELL when it dispatches
|
|
24
|
+
* `agents run auto`: tells the remote CLI its host layer is already resolved,
|
|
25
|
+
* so it must not re-run affinity and chain-hop to a third host. It rides the
|
|
26
|
+
* shell-export prelude (hosts/dispatch.ts `remoteRunShellPrelude`) because
|
|
27
|
+
* `--env` flags only reach the spawned AGENT's env — the remote CLI's own
|
|
28
|
+
* process.env (which exec.ts `runAutoDefaultsToAffinity` reads) never sees them.
|
|
29
|
+
*/
|
|
30
|
+
export declare const RUN_AUTO_HOST_RESOLVED_ENV = "AGENTS_RUN_AUTO_HOST_RESOLVED";
|
|
14
31
|
/** Per-agent run strategy config. */
|
|
15
32
|
export interface AgentRunConfig {
|
|
16
33
|
strategy?: RunStrategy;
|
package/dist/lib/types.js
CHANGED
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
* configuration schemas, resource tracking, registry types, and permission
|
|
6
6
|
* formats for each supported agent.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Reserved `<agent>` keyword for `agents run auto` — full-auto dispatch:
|
|
10
|
+
* host (14d launch affinity) → harness (best-account headroom, weighted) →
|
|
11
|
+
* account (balanced). Lives in lib (not commands/) because both the run
|
|
12
|
+
* command (exec.ts) and the host dispatch layer (hosts/dispatch.ts, which
|
|
13
|
+
* arms the chain-hop guard for remote `run auto`) must agree on it.
|
|
14
|
+
*/
|
|
15
|
+
export const RUN_AUTO_KEYWORD = 'auto';
|
|
16
|
+
/**
|
|
17
|
+
* Env var a host dispatcher exports into the remote SHELL when it dispatches
|
|
18
|
+
* `agents run auto`: tells the remote CLI its host layer is already resolved,
|
|
19
|
+
* so it must not re-run affinity and chain-hop to a third host. It rides the
|
|
20
|
+
* shell-export prelude (hosts/dispatch.ts `remoteRunShellPrelude`) because
|
|
21
|
+
* `--env` flags only reach the spawned AGENT's env — the remote CLI's own
|
|
22
|
+
* process.env (which exec.ts `runAutoDefaultsToAffinity` reads) never sees them.
|
|
23
|
+
*/
|
|
24
|
+
export const RUN_AUTO_HOST_RESOLVED_ENV = 'AGENTS_RUN_AUTO_HOST_RESOLVED';
|
|
8
25
|
/** Every canonical mode in declaration order. Useful for iteration / validation. */
|
|
9
26
|
export const ALL_MODES = ['plan', 'edit', 'auto', 'skip'];
|
|
10
27
|
/** Canonical system repo cloned into ~/.agents/.system/. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
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",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"@types/proper-lockfile": "4.1.4",
|
|
90
90
|
"@xterm/headless": "6.0.0",
|
|
91
91
|
"@zed-industries/agent-client-protocol": "0.4.5",
|
|
92
|
+
"acorn": "8.18.0",
|
|
92
93
|
"age-encryption": "0.3.0",
|
|
93
94
|
"chalk": "5.6.2",
|
|
94
95
|
"commander": "15.0.0",
|
|
@@ -101,6 +102,7 @@
|
|
|
101
102
|
"shlex": "3.0.0",
|
|
102
103
|
"simple-git": "3.36.0",
|
|
103
104
|
"smol-toml": "1.7.0",
|
|
105
|
+
"unbash": "4.0.5",
|
|
104
106
|
"ws": "^8.21.0",
|
|
105
107
|
"yaml": "2.9.0"
|
|
106
108
|
},
|