@phnx-labs/agents-cli 1.21.2 → 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 +101 -0
- package/README.md +32 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.d.ts +4 -0
- package/dist/commands/computer-actions.js +35 -0
- package/dist/commands/computer.js +4 -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/perf.d.ts +16 -0
- package/dist/commands/perf.js +11 -1
- package/dist/commands/projects.d.ts +11 -1
- package/dist/commands/projects.js +38 -4
- package/dist/commands/sessions-backfill.d.ts +32 -0
- package/dist/commands/sessions-backfill.js +186 -0
- package/dist/commands/sessions-picker.js +17 -2
- package/dist/commands/sessions.d.ts +22 -1
- package/dist/commands/sessions.js +331 -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/index.js +8 -0
- package/dist/lib/browser/service.js +13 -0
- package/dist/lib/computer/dispatch.d.ts +3 -1
- package/dist/lib/computer/dispatch.js +10 -2
- 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/event-stream.d.ts +2 -0
- package/dist/lib/event-stream.js +3 -0
- package/dist/lib/events.d.ts +3 -1
- package/dist/lib/events.js +4 -2
- package/dist/lib/exec.js +39 -8
- package/dist/lib/git.d.ts +14 -0
- package/dist/lib/git.js +36 -0
- package/dist/lib/hooks/profile.js +1 -14
- 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/percentile.d.ts +12 -0
- package/dist/lib/percentile.js +24 -0
- package/dist/lib/perf/db.d.ts +1 -2
- package/dist/lib/perf/db.js +2 -14
- package/dist/lib/plugins.js +12 -1
- 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/resources.d.ts +16 -0
- package/dist/lib/resources.js +25 -14
- package/dist/lib/rotate.d.ts +84 -1
- package/dist/lib/rotate.js +155 -5
- package/dist/lib/routines.js +1 -14
- package/dist/lib/runner.d.ts +4 -2
- package/dist/lib/runner.js +21 -5
- 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 +22 -1
- package/dist/lib/session/db.js +516 -24
- package/dist/lib/session/discover.d.ts +68 -7
- package/dist/lib/session/discover.js +186 -84
- package/dist/lib/session/highlights.d.ts +24 -4
- package/dist/lib/session/highlights.js +52 -7
- package/dist/lib/session/parse.d.ts +8 -1
- package/dist/lib/session/parse.js +102 -35
- package/dist/lib/session/prompt.d.ts +19 -0
- package/dist/lib/session/prompt.js +43 -0
- 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 +49 -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 +31 -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;
|
|
@@ -52,6 +59,15 @@ export interface SessionEvent {
|
|
|
52
59
|
hookName?: string;
|
|
53
60
|
/** Lifecycle event the hook fired on (SessionStart, PreToolUse, …). */
|
|
54
61
|
hookEvent?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Slash-command invocation name (e.g. `/recap`, `/code:commit`), captured
|
|
64
|
+
* two ways in a Claude transcript: a `role=user` message whose content is
|
|
65
|
+
* the `<command-name>` wrapper Claude injects for a typed slash command
|
|
66
|
+
* (`parseClaudeContent`, see `prompt.ts`'s `extractSlashCommandName`), or a
|
|
67
|
+
* `tool_use` event for the `SlashCommand` tool (a command the MODEL invoked
|
|
68
|
+
* programmatically, not the user). Undefined for every other event.
|
|
69
|
+
*/
|
|
70
|
+
slashCommand?: string;
|
|
55
71
|
}
|
|
56
72
|
/** A displayable file attachment discovered in a session transcript. */
|
|
57
73
|
export interface SessionAttachment {
|
|
@@ -186,6 +202,39 @@ export interface SessionMeta {
|
|
|
186
202
|
todos?: TodoProgress;
|
|
187
203
|
/** Most-recent unique directories changed or used as a shell working directory. */
|
|
188
204
|
recentDirectoriesTouched?: string[];
|
|
205
|
+
/**
|
|
206
|
+
* Skills invoked during the session (structurally identical to
|
|
207
|
+
* session/highlights.ts's SkillUse — declared inline here rather than
|
|
208
|
+
* imported, to avoid a circular import: highlights.ts imports SessionEvent
|
|
209
|
+
* from this file). Populated by discover.ts's incremental Claude
|
|
210
|
+
* accumulator (ClaudeParseState.skillEvents, run through extractSkills at
|
|
211
|
+
* finalize) so session/db.ts's upsertSessionsBatch can write
|
|
212
|
+
* session_resource_usage rows WITHOUT re-parsing the whole transcript —
|
|
213
|
+
* the same reason meta.todos/recentDirectoriesTouched are pre-computed by
|
|
214
|
+
* the caller for claude/codex instead of left to db.ts's re-parse path.
|
|
215
|
+
*/
|
|
216
|
+
skillsUsed?: Array<{
|
|
217
|
+
name: string;
|
|
218
|
+
count: number;
|
|
219
|
+
}>;
|
|
220
|
+
/** Sibling of {@link skillsUsed} for slash-command invocations (SessionEvent.slashCommand). */
|
|
221
|
+
slashCommandsUsed?: Array<{
|
|
222
|
+
name: string;
|
|
223
|
+
count: number;
|
|
224
|
+
}>;
|
|
225
|
+
/**
|
|
226
|
+
* Whether this session emitted at least one `browser.navigate` /
|
|
227
|
+
* `browser.screenshot` event, computed at scan time from a sessionId-scoped
|
|
228
|
+
* read of the events log (events.ts `query({ sessionId })`) rather than a
|
|
229
|
+
* transcript re-scan — see `detectToolUsage` in session/db.ts. `undefined`
|
|
230
|
+
* means a legacy row this scanner hasn't computed the field for yet (never
|
|
231
|
+
* collapsed to `false`, so a consumer — e.g. sessions-picker.ts's
|
|
232
|
+
* `classifySessionTool` — knows to fall back to a transcript-derived guess
|
|
233
|
+
* instead of trusting a false negative).
|
|
234
|
+
*/
|
|
235
|
+
usedBrowser?: boolean;
|
|
236
|
+
/** Sibling of {@link usedBrowser} for `computer.action` events. */
|
|
237
|
+
usedComputer?: boolean;
|
|
189
238
|
/** Linear project containing ticketId, resolved lazily and cached in SQLite. */
|
|
190
239
|
linearProject?: string;
|
|
191
240
|
/** Browser URL for linearProject. */
|
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;
|
|
@@ -605,6 +622,20 @@ export interface DiscoveredPlugin {
|
|
|
605
622
|
* (e.g. workflow-scoped) — those default to the user marketplace on sync.
|
|
606
623
|
*/
|
|
607
624
|
marketplace?: string;
|
|
625
|
+
/**
|
|
626
|
+
* Absolute path to the DotAgents repo root containing this plugin — the
|
|
627
|
+
* grandparent of `root` (`<repo>/plugins/<name>` → `<repo>`), true for every
|
|
628
|
+
* marketplace kind (user/system/extra/project). DotAgents repos are
|
|
629
|
+
* git-tracked (plugins.ts), so this pairs with {@link snapshotSha}.
|
|
630
|
+
*/
|
|
631
|
+
repoRoot: string;
|
|
632
|
+
/**
|
|
633
|
+
* Short HEAD sha of `repoRoot`'s git checkout, lazily resolved (a getter,
|
|
634
|
+
* not computed at discovery time) and memoized per repoRoot
|
|
635
|
+
* (`git.ts` `resolveSnapshotSha`) — see `ResolvedResource.snapshotSha` for
|
|
636
|
+
* the identical rationale. `undefined` when `repoRoot` isn't a git repo.
|
|
637
|
+
*/
|
|
638
|
+
readonly snapshotSha: string | undefined;
|
|
608
639
|
}
|
|
609
640
|
/**
|
|
610
641
|
* Identifies one DotAgents repo that contributes a plugin marketplace. Each
|
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
|
},
|