@phnx-labs/agents-cli 1.20.86 → 1.20.88
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 +172 -0
- package/README.md +3 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/doctor.d.ts +0 -19
- package/dist/commands/doctor.js +219 -305
- package/dist/commands/exec.js +7 -19
- package/dist/commands/inspect.js +3 -5
- package/dist/commands/routines.js +2 -2
- package/dist/commands/sessions-browser.d.ts +18 -0
- package/dist/commands/sessions-browser.js +126 -24
- package/dist/commands/sessions-picker.d.ts +21 -8
- package/dist/commands/sessions-picker.js +83 -7
- package/dist/commands/sessions.d.ts +19 -0
- package/dist/commands/sessions.js +148 -18
- package/dist/commands/ssh.js +62 -4
- package/dist/commands/teams-picker.d.ts +2 -0
- package/dist/commands/teams-picker.js +2 -1
- package/dist/commands/teams.d.ts +4 -1
- package/dist/commands/teams.js +106 -70
- package/dist/commands/usage.d.ts +3 -2
- package/dist/commands/usage.js +2 -9
- package/dist/commands/view.js +14 -3
- package/dist/index.js +31 -1
- package/dist/lib/agents.d.ts +31 -1
- package/dist/lib/agents.js +55 -0
- package/dist/lib/claude-account-token.d.ts +12 -0
- package/dist/lib/claude-account-token.js +63 -0
- package/dist/lib/command-skills.d.ts +10 -0
- package/dist/lib/command-skills.js +14 -0
- package/dist/lib/commands.js +19 -1
- package/dist/lib/daemon.js +17 -2
- package/dist/lib/devices/doctor-findings.d.ts +167 -0
- package/dist/lib/devices/doctor-findings.js +893 -0
- package/dist/lib/devices/fleet-divergence.d.ts +22 -0
- package/dist/lib/devices/fleet-divergence.js +34 -10
- package/dist/lib/devices/fleet-inventory.d.ts +17 -6
- package/dist/lib/devices/fleet-inventory.js +56 -8
- package/dist/lib/devices/registry.d.ts +25 -0
- package/dist/lib/devices/registry.js +82 -1
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +13 -0
- package/dist/lib/exec.d.ts +14 -3
- package/dist/lib/exec.js +51 -9
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.js +11 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/project-resources.js +34 -20
- package/dist/lib/runner.d.ts +14 -1
- package/dist/lib/runner.js +37 -8
- package/dist/lib/sandbox.d.ts +2 -0
- package/dist/lib/sandbox.js +38 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/rc-hygiene.d.ts +0 -6
- package/dist/lib/secrets/rc-hygiene.js +0 -24
- package/dist/lib/session/active.d.ts +6 -6
- package/dist/lib/session/active.js +6 -6
- package/dist/lib/session/db.d.ts +21 -0
- package/dist/lib/session/db.js +45 -4
- package/dist/lib/session/discover.d.ts +5 -0
- package/dist/lib/session/discover.js +137 -1
- package/dist/lib/session/parse.d.ts +2 -0
- package/dist/lib/session/parse.js +76 -37
- package/dist/lib/session/remote-list.d.ts +7 -0
- package/dist/lib/session/remote-list.js +8 -4
- package/dist/lib/session/state.js +69 -2
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/team-filter.d.ts +22 -3
- package/dist/lib/session/team-filter.js +106 -17
- package/dist/lib/session/types.d.ts +9 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/signin-badge.d.ts +17 -0
- package/dist/lib/signin-badge.js +19 -0
- package/dist/lib/staleness/detectors/commands.js +14 -5
- package/dist/lib/staleness/types.d.ts +2 -0
- package/dist/lib/staleness/writers/commands.js +13 -7
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +2 -0
- package/dist/lib/usage.d.ts +72 -1
- package/dist/lib/usage.js +22 -87
- package/dist/lib/versions.js +30 -13
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { AGENTS, agentConfigDirName, isAgentHardDeprecated } from './agents.js';
|
|
4
4
|
import { supports } from './capabilities.js';
|
|
5
|
-
import { buildCommandSkillContent, commandSkillName, readSkillSourceCommandMarker, shouldInstallCommandAsSkill } from './command-skills.js';
|
|
5
|
+
import { buildCommandSkillContent, commandSkillName, readSkillSourceCommandMarker, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill } from './command-skills.js';
|
|
6
6
|
import { commandAppliesTo, parseCommandMetadata } from './commands.js';
|
|
7
7
|
import { markdownToToml } from './convert.js';
|
|
8
8
|
import { safeJoin } from './paths.js';
|
|
@@ -134,6 +134,7 @@ function skip(dest, projectRoot, result) {
|
|
|
134
134
|
function syncProjectCommands(agent, version, projectAgentsDir, agentRoot, result, manifestPaths) {
|
|
135
135
|
const cfg = AGENTS[agent];
|
|
136
136
|
const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
|
|
137
|
+
const commandsAlsoAsSkills = shouldAlsoInstallCommandAsSkill(agent, version);
|
|
137
138
|
const supportsCommands = supports(agent, 'commands', version).ok;
|
|
138
139
|
if (!commandsAsSkills && !supportsCommands)
|
|
139
140
|
return;
|
|
@@ -146,37 +147,50 @@ function syncProjectCommands(agent, version, projectAgentsDir, agentRoot, result
|
|
|
146
147
|
const metadata = parseCommandMetadata(srcFile);
|
|
147
148
|
if (!commandAppliesTo(agent, version, metadata).ok)
|
|
148
149
|
continue;
|
|
149
|
-
|
|
150
|
+
const written = [];
|
|
151
|
+
if (commandsAsSkills || commandsAlsoAsSkills) {
|
|
150
152
|
const sourceMarker = readSkillSourceCommandMarker(name, [path.join(projectAgentsDir, 'skills')]);
|
|
151
|
-
if (pathExists(path.join(projectAgentsDir, 'skills', name)) && sourceMarker !== name)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
if (pathExists(path.join(projectAgentsDir, 'skills', name)) && sourceMarker !== name) {
|
|
154
|
+
if (commandsAsSkills)
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const skillName = commandSkillName(name);
|
|
159
|
+
const rel = path.join('skills', skillName);
|
|
160
|
+
const destDir = path.join(agentRoot, rel);
|
|
161
|
+
if (pathExists(destDir)) {
|
|
162
|
+
skip(destDir, projectRoot, result);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
166
|
+
fs.writeFileSync(path.join(destDir, 'SKILL.md'), buildCommandSkillContent(name, srcFile), 'utf-8');
|
|
167
|
+
written.push(rel);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (commandsAsSkills) {
|
|
171
|
+
if (written.length > 0)
|
|
172
|
+
record('commands', name, written, result, manifestPaths);
|
|
158
173
|
continue;
|
|
159
174
|
}
|
|
160
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
161
|
-
fs.writeFileSync(path.join(destDir, 'SKILL.md'), buildCommandSkillContent(name, srcFile), 'utf-8');
|
|
162
|
-
record('commands', name, [rel], result, manifestPaths);
|
|
163
|
-
continue;
|
|
164
175
|
}
|
|
165
176
|
const ext = cfg.format === 'toml' ? '.toml' : '.md';
|
|
166
177
|
const rel = path.join(cfg.commandsSubdir, `${name}${ext}`);
|
|
167
178
|
const destFile = path.join(agentRoot, rel);
|
|
168
179
|
if (pathExists(destFile)) {
|
|
169
180
|
skip(destFile, projectRoot, result);
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
173
|
-
if (cfg.format === 'toml') {
|
|
174
|
-
fs.writeFileSync(destFile, markdownToToml(name, fs.readFileSync(srcFile, 'utf-8')), 'utf-8');
|
|
175
181
|
}
|
|
176
182
|
else {
|
|
177
|
-
fs.
|
|
183
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
184
|
+
if (cfg.format === 'toml') {
|
|
185
|
+
fs.writeFileSync(destFile, markdownToToml(name, fs.readFileSync(srcFile, 'utf-8')), 'utf-8');
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
fs.copyFileSync(srcFile, destFile);
|
|
189
|
+
}
|
|
190
|
+
written.push(rel);
|
|
178
191
|
}
|
|
179
|
-
|
|
192
|
+
if (written.length > 0)
|
|
193
|
+
record('commands', name, written, result, manifestPaths);
|
|
180
194
|
}
|
|
181
195
|
}
|
|
182
196
|
function syncProjectSkills(agent, version, projectAgentsDir, agentRoot, result, manifestPaths) {
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface RunResult {
|
|
|
23
23
|
meta: RunMeta;
|
|
24
24
|
reportPath: string | null;
|
|
25
25
|
}
|
|
26
|
+
/** Agents the daemon can actually run, derived from the command table above
|
|
27
|
+
* so the `--agent` help and any validation can never drift from it. */
|
|
28
|
+
export declare const ROUTINE_AGENT_IDS: readonly string[];
|
|
26
29
|
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
|
|
27
30
|
export declare function buildJobCommand(config: JobConfig, resolvedPrompt: string): string[];
|
|
28
31
|
export declare function archiveRoutineTranscripts(meta: Pick<RunMeta, 'jobName' | 'runId' | 'agent'>, runDir: string, overlayHome?: string): void;
|
|
@@ -63,7 +66,7 @@ export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow
|
|
|
63
66
|
* (CLAUDE_CONFIG_DIR / CODEX_HOME / …) so routines share account isolation
|
|
64
67
|
* with `agents run`.
|
|
65
68
|
*/
|
|
66
|
-
export declare function buildRoutineSpawnEnv(baseEnv: Record<string, string>, agent: AgentId, version: string | undefined, timezone?: string): Record<string, string>;
|
|
69
|
+
export declare function buildRoutineSpawnEnv(baseEnv: Record<string, string>, agent: AgentId, version: string | undefined, timezone?: string, overlayHome?: string): Record<string, string>;
|
|
67
70
|
export declare function executeJob(config: JobConfig, deps?: LoopDeps): Promise<RunResult>;
|
|
68
71
|
/**
|
|
69
72
|
* Optional lifecycle callbacks for a detached routine run. The daemon passes an
|
|
@@ -80,5 +83,15 @@ export interface RoutineHooks {
|
|
|
80
83
|
export declare function executeJobDetached(config: JobConfig, hooks?: RoutineHooks): Promise<RunMeta>;
|
|
81
84
|
/** Extract the final assistant message from a stream-JSON log file as a markdown report. */
|
|
82
85
|
export declare function extractReport(stdoutPath: string, agentType: AgentId): string | null;
|
|
86
|
+
/** Derive the final status of a detached run by reading the agent's stream-json
|
|
87
|
+
* tail. Detached children fire-and-forget, so we never see their exit code
|
|
88
|
+
* directly — but Claude's stream-json terminates with a `type: result` line
|
|
89
|
+
* that carries `is_error`. If we find it, the run completed cleanly (modulo
|
|
90
|
+
* agent-reported error). If not, the process likely died mid-stream and the
|
|
91
|
+
* caller should treat the run as failed. */
|
|
92
|
+
export declare function inferFinalStatusFromLog(stdoutPath: string, agent: AgentId): {
|
|
93
|
+
status: 'completed' | 'failed';
|
|
94
|
+
exitCode: number;
|
|
95
|
+
} | null;
|
|
83
96
|
/** Scan all runs marked "running" and finalize any whose process has exited. */
|
|
84
97
|
export declare function monitorRunningJobs(): void;
|
package/dist/lib/runner.js
CHANGED
|
@@ -32,17 +32,23 @@ import { getBinaryPath, isVersionInstalled, resolveVersion } from './versions.js
|
|
|
32
32
|
import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountVersion, rotationFailoverChain, readinessFromCandidate, } from './rotate.js';
|
|
33
33
|
import { readAuthHealth, isDeadVerdict } from './auth-health.js';
|
|
34
34
|
import { machineId } from './machine-id.js';
|
|
35
|
+
import { isSelfUpdatingAgent } from './agents.js';
|
|
35
36
|
/** CLI command templates per agent, with {prompt} as a placeholder. */
|
|
36
37
|
const AGENT_COMMANDS = {
|
|
37
38
|
claude: ['claude', '-p', '--verbose', '{prompt}', '--output-format', 'stream-json', '--permission-mode', 'plan'],
|
|
38
39
|
codex: ['codex', 'exec', '--sandbox', 'workspace-write', '{prompt}', '--json'],
|
|
39
40
|
gemini: ['gemini', '{prompt}', '--output-format', 'stream-json'],
|
|
41
|
+
cursor: ['cursor-agent', '-p', '{prompt}', '--output-format', 'stream-json'],
|
|
40
42
|
kimi: ['kimi', '--prompt', '{prompt}', '--output-format', 'stream-json'],
|
|
41
43
|
droid: ['droid', 'exec', '{prompt}', '-o', 'stream-json'],
|
|
42
44
|
};
|
|
45
|
+
/** Agents the daemon can actually run, derived from the command table above
|
|
46
|
+
* so the `--agent` help and any validation can never drift from it. */
|
|
47
|
+
export const ROUTINE_AGENT_IDS = Object.freeze(Object.keys(AGENT_COMMANDS));
|
|
43
48
|
const ROUTINE_TRANSCRIPT_SPECS = {
|
|
44
49
|
claude: [{ root: ['.claude', 'projects'], ext: '.jsonl' }],
|
|
45
50
|
codex: [{ root: ['.codex', 'sessions'], ext: '.jsonl' }],
|
|
51
|
+
cursor: [{ root: ['.cursor', 'projects'], ext: '.jsonl' }],
|
|
46
52
|
};
|
|
47
53
|
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
|
|
48
54
|
export function buildJobCommand(config, resolvedPrompt) {
|
|
@@ -133,6 +139,20 @@ export function buildJobCommand(config, resolvedPrompt) {
|
|
|
133
139
|
}
|
|
134
140
|
appendModelAndReasoning(cmd, config);
|
|
135
141
|
}
|
|
142
|
+
if (config.agent === 'cursor') {
|
|
143
|
+
// cursor-agent supports --plan, but the capability registry has not been
|
|
144
|
+
// upgraded yet. RUSH-2101 tracks adding that read-only mode after PR #1721.
|
|
145
|
+
const cursorMode = resolveHeadlessMode('cursor', mode, false, `routine ${config.name}`);
|
|
146
|
+
if (cursorMode === 'skip') {
|
|
147
|
+
cmd.push('-f');
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// The configured cwd is the user's workspace trust decision. --trust is
|
|
151
|
+
// narrower than --yolo/-f because it does not bypass tool permissions.
|
|
152
|
+
cmd.push('--trust');
|
|
153
|
+
}
|
|
154
|
+
appendModelAndReasoning(cmd, config);
|
|
155
|
+
}
|
|
136
156
|
if (config.agent === 'kimi') {
|
|
137
157
|
// kimi daemon jobs always run headless via `--prompt`, which cannot be
|
|
138
158
|
// combined with any startup-mode flag (--plan/--auto/--yolo all abort with
|
|
@@ -385,7 +405,7 @@ export function dispatchesViaAgentsRun(config) {
|
|
|
385
405
|
* (CLAUDE_CONFIG_DIR / CODEX_HOME / …) so routines share account isolation
|
|
386
406
|
* with `agents run`.
|
|
387
407
|
*/
|
|
388
|
-
export function buildRoutineSpawnEnv(baseEnv, agent, version, timezone) {
|
|
408
|
+
export function buildRoutineSpawnEnv(baseEnv, agent, version, timezone, overlayHome) {
|
|
389
409
|
const execEnv = buildExecEnv({
|
|
390
410
|
agent,
|
|
391
411
|
version,
|
|
@@ -415,6 +435,11 @@ export function buildRoutineSpawnEnv(baseEnv, agent, version, timezone) {
|
|
|
415
435
|
// provisioned box does. Drop it here so a routine always uses the login of the
|
|
416
436
|
// machine it runs on.
|
|
417
437
|
delete out.CLAUDE_CODE_OAUTH_TOKEN;
|
|
438
|
+
if (agent === 'cursor' && overlayHome) {
|
|
439
|
+
// prepareJobHome links this host's Cursor auth file here. Pin XDG_CONFIG_HOME
|
|
440
|
+
// to the overlay so an ambient value cannot bypass the routine sandbox.
|
|
441
|
+
out.XDG_CONFIG_HOME = path.join(overlayHome, '.config');
|
|
442
|
+
}
|
|
418
443
|
if (timezone)
|
|
419
444
|
out.TZ = timezone;
|
|
420
445
|
return out;
|
|
@@ -621,16 +646,20 @@ export async function executeJob(config, deps) {
|
|
|
621
646
|
const timeoutMs = parseTimeout(config.timeout) || 10 * 60 * 1000;
|
|
622
647
|
// Loop path: delegate to runLoop (same driver as `agents run --loop` / workflow loop:).
|
|
623
648
|
if (config.loop) {
|
|
624
|
-
const spawnEnv = buildRoutineSpawnEnv(baseEnv, effectiveAgent, primaryVersion, config.timezone);
|
|
649
|
+
const spawnEnv = buildRoutineSpawnEnv(baseEnv, effectiveAgent, primaryVersion, config.timezone, overlayHome);
|
|
625
650
|
const execOptions = {
|
|
626
651
|
agent: effectiveAgent,
|
|
627
|
-
|
|
652
|
+
// Routine-supported self-updating CLIs (Cursor/Droid) use one global
|
|
653
|
+
// binary; a versioned shim would point at a nonexistent isolated install.
|
|
654
|
+
version: isSelfUpdatingAgent(effectiveAgent) ? undefined : primaryVersion,
|
|
628
655
|
prompt: resolvedPrompt,
|
|
629
656
|
mode: normalizeMode(config.mode),
|
|
630
657
|
effort: config.effort,
|
|
631
658
|
env: spawnEnv,
|
|
632
659
|
json: true,
|
|
633
660
|
headless: true,
|
|
661
|
+
modeWarningContext: `routine ${config.name}`,
|
|
662
|
+
modeWarningState: {},
|
|
634
663
|
...(config.config?.model ? { model: config.config.model } : {}),
|
|
635
664
|
...(config.allow?.dirs ? {
|
|
636
665
|
addDirs: config.allow.dirs
|
|
@@ -681,7 +710,7 @@ export async function executeJob(config, deps) {
|
|
|
681
710
|
e.TZ = config.timezone;
|
|
682
711
|
return e;
|
|
683
712
|
})()
|
|
684
|
-
: buildRoutineSpawnEnv(baseEnv, attemptAgent, attemptVersion, config.timezone);
|
|
713
|
+
: buildRoutineSpawnEnv(baseEnv, attemptAgent, attemptVersion, config.timezone, overlayHome);
|
|
685
714
|
// Remaining timeout budget shared across failover attempts.
|
|
686
715
|
const elapsed = Date.now() - Date.parse(meta.startedAt);
|
|
687
716
|
const remaining = Math.max(1_000, timeoutMs - (Number.isFinite(elapsed) ? elapsed : 0));
|
|
@@ -1067,7 +1096,7 @@ export async function executeJobDetached(config, hooks) {
|
|
|
1067
1096
|
return e;
|
|
1068
1097
|
})()
|
|
1069
1098
|
// Non-command path only: config.agent is always set here (command/workflow branch earlier).
|
|
1070
|
-
: buildRoutineSpawnEnv(baseEnv, config.agent, version, config.timezone);
|
|
1099
|
+
: buildRoutineSpawnEnv(baseEnv, config.agent, version, config.timezone, overlayHome);
|
|
1071
1100
|
const effectiveAgent = config.workflow ? 'claude' : config.agent;
|
|
1072
1101
|
const meta = {
|
|
1073
1102
|
jobName: config.name,
|
|
@@ -1262,7 +1291,7 @@ export function extractReport(stdoutPath, agentType) {
|
|
|
1262
1291
|
for (const line of lines) {
|
|
1263
1292
|
try {
|
|
1264
1293
|
const parsed = JSON.parse(line);
|
|
1265
|
-
if (agentType === 'claude') {
|
|
1294
|
+
if (agentType === 'claude' || agentType === 'cursor') {
|
|
1266
1295
|
if (parsed.type === 'assistant' && parsed.message?.content) {
|
|
1267
1296
|
for (const block of parsed.message.content) {
|
|
1268
1297
|
if (block.type === 'text' && block.text) {
|
|
@@ -1298,7 +1327,7 @@ export function extractReport(stdoutPath, agentType) {
|
|
|
1298
1327
|
* that carries `is_error`. If we find it, the run completed cleanly (modulo
|
|
1299
1328
|
* agent-reported error). If not, the process likely died mid-stream and the
|
|
1300
1329
|
* caller should treat the run as failed. */
|
|
1301
|
-
function inferFinalStatusFromLog(stdoutPath, agent) {
|
|
1330
|
+
export function inferFinalStatusFromLog(stdoutPath, agent) {
|
|
1302
1331
|
if (!fs.existsSync(stdoutPath))
|
|
1303
1332
|
return null;
|
|
1304
1333
|
try {
|
|
@@ -1309,7 +1338,7 @@ function inferFinalStatusFromLog(stdoutPath, agent) {
|
|
|
1309
1338
|
for (let i = lines.length - 1, scanned = 0; i >= 0 && scanned < 20; i--, scanned++) {
|
|
1310
1339
|
try {
|
|
1311
1340
|
const parsed = JSON.parse(lines[i]);
|
|
1312
|
-
if (agent === 'claude' && parsed.type === 'result') {
|
|
1341
|
+
if ((agent === 'claude' || agent === 'cursor') && parsed.type === 'result') {
|
|
1313
1342
|
return parsed.is_error
|
|
1314
1343
|
? { status: 'failed', exitCode: 1 }
|
|
1315
1344
|
: { status: 'completed', exitCode: 0 };
|
package/dist/lib/sandbox.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare function buildSpawnEnv(overlayHome: string, extraEnv?: Record<str
|
|
|
21
21
|
export declare function getJobHomePath(name: string): string;
|
|
22
22
|
/** Create a fresh overlay HOME for a job, including agent config and allowed-dir symlinks. */
|
|
23
23
|
export declare function prepareJobHome(config: JobConfig): string;
|
|
24
|
+
/** Link this host's Cursor login and CLI config into the disposable overlay. */
|
|
25
|
+
export declare function generateCursorConfig(overlayHome: string): void;
|
|
24
26
|
/** Remove a job's overlay HOME directory entirely. */
|
|
25
27
|
export declare function cleanJobHome(name: string): void;
|
|
26
28
|
/** Symlink allowed directories into the overlay HOME, skipping paths outside the real HOME. */
|
package/dist/lib/sandbox.js
CHANGED
|
@@ -100,11 +100,49 @@ export function prepareJobHome(config) {
|
|
|
100
100
|
else if (config.agent === 'gemini') {
|
|
101
101
|
generateGeminiConfig(overlayHome, config);
|
|
102
102
|
}
|
|
103
|
+
else if (config.agent === 'cursor') {
|
|
104
|
+
generateCursorConfig(overlayHome);
|
|
105
|
+
}
|
|
103
106
|
if (config.allow?.dirs) {
|
|
104
107
|
symlinkAllowedDirs(overlayHome, config.allow.dirs);
|
|
105
108
|
}
|
|
106
109
|
return overlayHome;
|
|
107
110
|
}
|
|
111
|
+
/** Link this host's Cursor login and CLI config into the disposable overlay. */
|
|
112
|
+
export function generateCursorConfig(overlayHome) {
|
|
113
|
+
const realConfigHome = process.env.XDG_CONFIG_HOME || path.join(resolveRealHome(), '.config');
|
|
114
|
+
const realAuth = path.join(realConfigHome, 'cursor', 'auth.json');
|
|
115
|
+
if (!fs.existsSync(realAuth))
|
|
116
|
+
return;
|
|
117
|
+
const overlayCursorDir = path.join(overlayHome, '.config', 'cursor');
|
|
118
|
+
fs.mkdirSync(overlayCursorDir, { recursive: true });
|
|
119
|
+
const overlayAuth = path.join(overlayCursorDir, 'auth.json');
|
|
120
|
+
if (process.platform === 'win32') {
|
|
121
|
+
// File symlinks need Developer Mode on Windows. A same-volume hard link
|
|
122
|
+
// shares the credential inode without copying its contents to another host.
|
|
123
|
+
try {
|
|
124
|
+
fs.linkSync(realAuth, overlayAuth);
|
|
125
|
+
}
|
|
126
|
+
catch { /* cross-volume or link creation refused: Cursor will fail auth loudly */ }
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
fs.symlinkSync(realAuth, overlayAuth);
|
|
130
|
+
}
|
|
131
|
+
// Setting XDG_CONFIG_HOME makes Cursor look for cli-config.json beside auth.json.
|
|
132
|
+
// This file carries account identity (authId, displayName, email, userId) as
|
|
133
|
+
// well as preferences, so it must remain linked rather than copied.
|
|
134
|
+
const realCliConfig = path.join(resolveRealHome(), '.cursor', 'cli-config.json');
|
|
135
|
+
if (fs.existsSync(realCliConfig)) {
|
|
136
|
+
const overlayCliConfig = path.join(overlayCursorDir, 'cli-config.json');
|
|
137
|
+
try {
|
|
138
|
+
if (process.platform === 'win32')
|
|
139
|
+
fs.linkSync(realCliConfig, overlayCliConfig);
|
|
140
|
+
else
|
|
141
|
+
fs.symlinkSync(realCliConfig, overlayCliConfig);
|
|
142
|
+
}
|
|
143
|
+
catch { /* cross-volume or link creation refused: Cursor will fail auth loudly */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
108
146
|
/** Remove a job's overlay HOME directory entirely. */
|
|
109
147
|
export function cleanJobHome(name) {
|
|
110
148
|
const overlayHome = getJobHomePath(name);
|
|
Binary file
|
|
Binary file
|
|
@@ -47,9 +47,3 @@ export declare function scanRcExports(basename: string, content: string): RcSecr
|
|
|
47
47
|
* unreadable files are skipped silently — an advisory, not a hard requirement.
|
|
48
48
|
*/
|
|
49
49
|
export declare function scanUserRcFiles(homeDir?: string): RcSecretFinding[];
|
|
50
|
-
/**
|
|
51
|
-
* Build the advisory lines for `agents doctor` from a set of findings. Returns
|
|
52
|
-
* `[]` when there is nothing to report. The first line is the headline; the
|
|
53
|
-
* rest are indented detail. Names only — never values.
|
|
54
|
-
*/
|
|
55
|
-
export declare function rcSecretWarningLines(findings: RcSecretFinding[]): string[];
|
|
@@ -128,27 +128,3 @@ export function scanUserRcFiles(homeDir = os.homedir()) {
|
|
|
128
128
|
}
|
|
129
129
|
return out;
|
|
130
130
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Build the advisory lines for `agents doctor` from a set of findings. Returns
|
|
133
|
-
* `[]` when there is nothing to report. The first line is the headline; the
|
|
134
|
-
* rest are indented detail. Names only — never values.
|
|
135
|
-
*/
|
|
136
|
-
export function rcSecretWarningLines(findings) {
|
|
137
|
-
if (findings.length === 0)
|
|
138
|
-
return [];
|
|
139
|
-
const lines = [];
|
|
140
|
-
const master = findings.filter((f) => f.isMasterPassphrase);
|
|
141
|
-
const others = findings.filter((f) => !f.isMasterPassphrase);
|
|
142
|
-
const total = findings.length;
|
|
143
|
-
lines.push(`${total} credential-shaped export${total === 1 ? '' : 's'} found in shell rc files — ` +
|
|
144
|
-
`readable from /proc/<pid>/environ by any same-user process.`);
|
|
145
|
-
for (const f of master) {
|
|
146
|
-
lines.push(`${f.file}:${f.line} ${f.name} — the file-store master key. Move it off-env to ` +
|
|
147
|
-
`~/.agents/.secrets-key/passphrase (chmod 600) and delete the export.`);
|
|
148
|
-
}
|
|
149
|
-
for (const f of others) {
|
|
150
|
-
lines.push(`${f.file}:${f.line} ${f.name} — move to \`agents secrets\` and inject via \`agents secrets exec\`.`);
|
|
151
|
-
}
|
|
152
|
-
lines.push('Rule: no credentials in env vars or shell config. Use the keychain-backed store.');
|
|
153
|
-
return lines;
|
|
154
|
-
}
|
|
@@ -298,7 +298,7 @@ export declare function sessionFileTimes(sessionFile: string | undefined): {
|
|
|
298
298
|
export declare function lifecycleStatus(pidAlive: boolean, mtimeMs: number | undefined, nowMs?: number): ActiveStatus | undefined;
|
|
299
299
|
/**
|
|
300
300
|
* The ONE place a fallback status is decided when no rich transcript state is
|
|
301
|
-
* available — an opaque kind we cannot parse (
|
|
301
|
+
* available — an opaque kind we cannot parse (openclaw), or a transcript
|
|
302
302
|
* whose parse/tail was empty or unreadable. Honest by construction: computed from
|
|
303
303
|
* PID + mtime, never a fabricated `idle`.
|
|
304
304
|
*
|
|
@@ -316,13 +316,13 @@ export declare function resolveFallbackStatus(sessionFile: string | undefined, p
|
|
|
316
316
|
* Locate the live transcript for an agent process. Claude files are keyed by cwd
|
|
317
317
|
* (+ optional session uuid), so they resolve straight off disk. Every OTHER
|
|
318
318
|
* tracked harness — Codex (date-partitioned), plus grok / droid / rush / gemini /
|
|
319
|
-
* kimi / hermes / opencode / antigravity (per-session dirs, SQLite, single-JSON)
|
|
319
|
+
* kimi / hermes / opencode / antigravity / cursor (per-session dirs, SQLite, single-JSON)
|
|
320
320
|
* — is resolved through the session index by cwd: the newest indexed transcript
|
|
321
321
|
* for that cwd, bounded by ACTIVE_SESSION_STALE_MS so a live pid never borrows a
|
|
322
322
|
* weeks-old transcript. This is what lets a live NON-claude/codex agent get a real
|
|
323
323
|
* status instead of falling through to `unknown` (the file feeds
|
|
324
|
-
* {@link computeLiveSignals}). An opaque kind we don't track
|
|
325
|
-
*
|
|
324
|
+
* {@link computeLiveSignals}). An opaque kind we don't track still yields undefined
|
|
325
|
+
* here and degrades honestly to a live `running`.
|
|
326
326
|
*/
|
|
327
327
|
export declare function findSessionFileForKind(kind: string, cwd?: string, sessionId?: string): string | undefined;
|
|
328
328
|
/** Live per-session signals derived from one transcript-tail read. */
|
|
@@ -339,10 +339,10 @@ interface LiveSignals {
|
|
|
339
339
|
* Claude/Codex take the fast bounded byte-tail ({@link readSessionTailWithRaw}) —
|
|
340
340
|
* the hot path, and the only two that also yield throughput (their raw lines
|
|
341
341
|
* carry usage the event model drops). EVERY OTHER tracked harness (grok, droid,
|
|
342
|
-
* rush, gemini, kimi, hermes, opencode, antigravity) is parsed with its own
|
|
342
|
+
* rush, gemini, kimi, hermes, opencode, antigravity, cursor) is parsed with its own
|
|
343
343
|
* parser and run through the SAME {@link inferSessionState}, so a live
|
|
344
344
|
* non-claude/codex agent gets a real working/waiting/idle instead of the blanket
|
|
345
|
-
* `unknown` it used to fall through to. An opaque/untracked kind
|
|
345
|
+
* `unknown` it used to fall through to. An opaque/untracked kind or an
|
|
346
346
|
* unreadable/empty transcript yields an empty signal set, and the caller's
|
|
347
347
|
* {@link resolveFallbackStatus} reports the honest live floor (`running`).
|
|
348
348
|
*/
|
|
@@ -358,7 +358,7 @@ export function lifecycleStatus(pidAlive, mtimeMs, nowMs = Date.now()) {
|
|
|
358
358
|
}
|
|
359
359
|
/**
|
|
360
360
|
* The ONE place a fallback status is decided when no rich transcript state is
|
|
361
|
-
* available — an opaque kind we cannot parse (
|
|
361
|
+
* available — an opaque kind we cannot parse (openclaw), or a transcript
|
|
362
362
|
* whose parse/tail was empty or unreadable. Honest by construction: computed from
|
|
363
363
|
* PID + mtime, never a fabricated `idle`.
|
|
364
364
|
*
|
|
@@ -379,13 +379,13 @@ export function resolveFallbackStatus(sessionFile, pidAlive, nowMs = Date.now())
|
|
|
379
379
|
* Locate the live transcript for an agent process. Claude files are keyed by cwd
|
|
380
380
|
* (+ optional session uuid), so they resolve straight off disk. Every OTHER
|
|
381
381
|
* tracked harness — Codex (date-partitioned), plus grok / droid / rush / gemini /
|
|
382
|
-
* kimi / hermes / opencode / antigravity (per-session dirs, SQLite, single-JSON)
|
|
382
|
+
* kimi / hermes / opencode / antigravity / cursor (per-session dirs, SQLite, single-JSON)
|
|
383
383
|
* — is resolved through the session index by cwd: the newest indexed transcript
|
|
384
384
|
* for that cwd, bounded by ACTIVE_SESSION_STALE_MS so a live pid never borrows a
|
|
385
385
|
* weeks-old transcript. This is what lets a live NON-claude/codex agent get a real
|
|
386
386
|
* status instead of falling through to `unknown` (the file feeds
|
|
387
|
-
* {@link computeLiveSignals}). An opaque kind we don't track
|
|
388
|
-
*
|
|
387
|
+
* {@link computeLiveSignals}). An opaque kind we don't track still yields undefined
|
|
388
|
+
* here and degrades honestly to a live `running`.
|
|
389
389
|
*/
|
|
390
390
|
export function findSessionFileForKind(kind, cwd, sessionId) {
|
|
391
391
|
if (!cwd)
|
|
@@ -432,10 +432,10 @@ function parseTailEventsForKind(agent, sessionFile) {
|
|
|
432
432
|
* Claude/Codex take the fast bounded byte-tail ({@link readSessionTailWithRaw}) —
|
|
433
433
|
* the hot path, and the only two that also yield throughput (their raw lines
|
|
434
434
|
* carry usage the event model drops). EVERY OTHER tracked harness (grok, droid,
|
|
435
|
-
* rush, gemini, kimi, hermes, opencode, antigravity) is parsed with its own
|
|
435
|
+
* rush, gemini, kimi, hermes, opencode, antigravity, cursor) is parsed with its own
|
|
436
436
|
* parser and run through the SAME {@link inferSessionState}, so a live
|
|
437
437
|
* non-claude/codex agent gets a real working/waiting/idle instead of the blanket
|
|
438
|
-
* `unknown` it used to fall through to. An opaque/untracked kind
|
|
438
|
+
* `unknown` it used to fall through to. An opaque/untracked kind or an
|
|
439
439
|
* unreadable/empty transcript yields an empty signal set, and the caller's
|
|
440
440
|
* {@link resolveFallbackStatus} reports the honest live floor (`running`).
|
|
441
441
|
*/
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import Database from '../sqlite.js';
|
|
10
10
|
import type { SessionAgentId, SessionMeta } from './types.js';
|
|
11
|
+
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
12
|
+
* assert against the constant instead of hardcoding a number that every bump
|
|
13
|
+
* then has to chase (docs/05-sessions.md calls the constant the source of truth). */
|
|
14
|
+
export declare const SCHEMA_VERSION = 21;
|
|
11
15
|
/** Raw row shape returned from the sessions table. */
|
|
12
16
|
export interface SessionRow {
|
|
13
17
|
id: string;
|
|
@@ -40,6 +44,7 @@ export interface SessionRow {
|
|
|
40
44
|
pr_number: number | null;
|
|
41
45
|
worktree_slug: string | null;
|
|
42
46
|
ticket_id: string | null;
|
|
47
|
+
spawned_team: string | null;
|
|
43
48
|
plan: string | null;
|
|
44
49
|
machine: string | null;
|
|
45
50
|
todos: string | null;
|
|
@@ -297,6 +302,22 @@ export declare function queryAffinityRollup(options: {
|
|
|
297
302
|
export declare function queryUsageRollup(options: QueryOptions & {
|
|
298
303
|
groupBy: UsageRollupGroup;
|
|
299
304
|
}): UsageRollupRow[];
|
|
305
|
+
/** Who spawned a team: the orchestrator session, from its transcript. */
|
|
306
|
+
export interface TeamSpawner {
|
|
307
|
+
sessionId: string;
|
|
308
|
+
shortId: string;
|
|
309
|
+
/** The human the orchestrator ran as, when the row carries actor provenance. */
|
|
310
|
+
actor?: string;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Map every team name to the session that ran `agents teams create/add` for it.
|
|
314
|
+
*
|
|
315
|
+
* One scan over the rows that carry a `spawned_team`, rather than a query per
|
|
316
|
+
* team — `agents teams list` needs the whole map at once, and the column has no
|
|
317
|
+
* index. When two sessions spawned the same team name (a team re-created after a
|
|
318
|
+
* disband), the most recent wins, which is the one whose work the name refers to.
|
|
319
|
+
*/
|
|
320
|
+
export declare function teamSpawners(): Map<string, TeamSpawner>;
|
|
300
321
|
/** A session with its cost, for the top-N-by-cost listing. */
|
|
301
322
|
export interface TopCostSession {
|
|
302
323
|
meta: SessionMeta;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -16,8 +16,10 @@ import { machineForSessionFile } from './origin-machine.js';
|
|
|
16
16
|
import { loadSessionActorIndex, readSessionActorRecord } from './actor-sidecar.js';
|
|
17
17
|
const SESSIONS_DIR = getSessionsDir();
|
|
18
18
|
const DB_PATH = getSessionsDbPath();
|
|
19
|
-
/** Current schema version; bumped when migrations are added.
|
|
20
|
-
|
|
19
|
+
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
20
|
+
* assert against the constant instead of hardcoding a number that every bump
|
|
21
|
+
* then has to chase (docs/05-sessions.md calls the constant the source of truth). */
|
|
22
|
+
export const SCHEMA_VERSION = 21;
|
|
21
23
|
/**
|
|
22
24
|
* Canonicalize a file path for use as a scan_ledger key. The same physical
|
|
23
25
|
* session file is reachable via multiple aliases — `~/.claude/projects/x.jsonl`
|
|
@@ -74,6 +76,7 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
74
76
|
pr_number INTEGER,
|
|
75
77
|
worktree_slug TEXT,
|
|
76
78
|
ticket_id TEXT,
|
|
79
|
+
spawned_team TEXT,
|
|
77
80
|
plan TEXT,
|
|
78
81
|
machine TEXT,
|
|
79
82
|
todos TEXT,
|
|
@@ -383,6 +386,19 @@ function migrateSchema(db, fromVersion) {
|
|
|
383
386
|
db.exec(`ALTER TABLE sessions ADD COLUMN model TEXT`);
|
|
384
387
|
db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
|
|
385
388
|
}
|
|
389
|
+
if (fromVersion < 21) {
|
|
390
|
+
// v20 → v21: persist the team a session SPAWNED (`agents teams create/add`).
|
|
391
|
+
// The value was already derived at scan time (discover.ts detectSpawnedTeam)
|
|
392
|
+
// and set on SessionMeta, but had no column — so it was dropped at the write
|
|
393
|
+
// and no consumer ever saw it. Wipe BOTH ledgers, not just scan_ledger: with
|
|
394
|
+
// dir_ledger intact, collectChangedFilesInLeafDirs treats every non-live-root
|
|
395
|
+
// dir as unchanged and derives its hot set from the scan stamps just deleted,
|
|
396
|
+
// so archived dirs would never be re-parsed and would stay NULL forever.
|
|
397
|
+
const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
398
|
+
if (!cols.some(c => c.name === 'spawned_team'))
|
|
399
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN spawned_team TEXT`);
|
|
400
|
+
db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
|
|
401
|
+
}
|
|
386
402
|
}
|
|
387
403
|
/** Open (or return the cached) sessions database, applying migrations as needed. */
|
|
388
404
|
export function getDB() {
|
|
@@ -744,7 +760,7 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
744
760
|
project, cwd, git_branch, topic, label, message_count, token_count,
|
|
745
761
|
output_tokens, cost_usd, duration_ms, model,
|
|
746
762
|
file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
|
|
747
|
-
pr_url, pr_number, worktree_slug, ticket_id, plan, todos,
|
|
763
|
+
pr_url, pr_number, worktree_slug, ticket_id, spawned_team, plan, todos,
|
|
748
764
|
recent_directories_touched, linear_project, linear_project_url, machine,
|
|
749
765
|
actor, initiated_by
|
|
750
766
|
) VALUES (
|
|
@@ -753,7 +769,7 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
753
769
|
@project, @cwd, @git_branch, @topic, @label, @message_count, @token_count,
|
|
754
770
|
@output_tokens, @cost_usd, @duration_ms, @model,
|
|
755
771
|
@file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
|
|
756
|
-
@pr_url, @pr_number, @worktree_slug, @ticket_id, @plan, @todos,
|
|
772
|
+
@pr_url, @pr_number, @worktree_slug, @ticket_id, @spawned_team, @plan, @todos,
|
|
757
773
|
@recent_directories_touched, @linear_project, @linear_project_url, @machine,
|
|
758
774
|
@actor, @initiated_by
|
|
759
775
|
)
|
|
@@ -795,6 +811,7 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
795
811
|
pr_number = excluded.pr_number,
|
|
796
812
|
worktree_slug = excluded.worktree_slug,
|
|
797
813
|
ticket_id = excluded.ticket_id,
|
|
814
|
+
spawned_team = excluded.spawned_team,
|
|
798
815
|
plan = excluded.plan,
|
|
799
816
|
todos = excluded.todos,
|
|
800
817
|
recent_directories_touched = excluded.recent_directories_touched,
|
|
@@ -911,6 +928,7 @@ export function upsertSession(meta, content, scan) {
|
|
|
911
928
|
pr_number: meta.prNumber ?? null,
|
|
912
929
|
worktree_slug: meta.worktreeSlug ?? null,
|
|
913
930
|
ticket_id: meta.ticketId ?? null,
|
|
931
|
+
spawned_team: meta.spawnedTeam ?? null,
|
|
914
932
|
plan: meta.plan ?? null,
|
|
915
933
|
todos: meta.todos ? JSON.stringify(meta.todos) : null,
|
|
916
934
|
recent_directories_touched: meta.recentDirectoriesTouched ? JSON.stringify(meta.recentDirectoriesTouched) : null,
|
|
@@ -1031,6 +1049,7 @@ export function upsertSessionsBatch(entries) {
|
|
|
1031
1049
|
pr_number: meta.prNumber ?? null,
|
|
1032
1050
|
worktree_slug: meta.worktreeSlug ?? null,
|
|
1033
1051
|
ticket_id: meta.ticketId ?? null,
|
|
1052
|
+
spawned_team: meta.spawnedTeam ?? null,
|
|
1034
1053
|
plan: meta.plan ?? null,
|
|
1035
1054
|
todos: meta.todos ? JSON.stringify(meta.todos) : null,
|
|
1036
1055
|
recent_directories_touched: meta.recentDirectoriesTouched ? JSON.stringify(meta.recentDirectoriesTouched) : null,
|
|
@@ -1219,6 +1238,7 @@ function rowToMeta(row) {
|
|
|
1219
1238
|
prNumber: row.pr_number ?? undefined,
|
|
1220
1239
|
worktreeSlug: row.worktree_slug ?? undefined,
|
|
1221
1240
|
ticketId: row.ticket_id ?? undefined,
|
|
1241
|
+
spawnedTeam: row.spawned_team ?? undefined,
|
|
1222
1242
|
plan: row.plan ?? undefined,
|
|
1223
1243
|
todos: parseJsonColumn(row.todos),
|
|
1224
1244
|
recentDirectoriesTouched: parseJsonColumn(row.recent_directories_touched),
|
|
@@ -1483,6 +1503,27 @@ export function queryUsageRollup(options) {
|
|
|
1483
1503
|
`;
|
|
1484
1504
|
return db.prepare(sql).all(...params);
|
|
1485
1505
|
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Map every team name to the session that ran `agents teams create/add` for it.
|
|
1508
|
+
*
|
|
1509
|
+
* One scan over the rows that carry a `spawned_team`, rather than a query per
|
|
1510
|
+
* team — `agents teams list` needs the whole map at once, and the column has no
|
|
1511
|
+
* index. When two sessions spawned the same team name (a team re-created after a
|
|
1512
|
+
* disband), the most recent wins, which is the one whose work the name refers to.
|
|
1513
|
+
*/
|
|
1514
|
+
export function teamSpawners() {
|
|
1515
|
+
const db = getDB();
|
|
1516
|
+
const rows = db
|
|
1517
|
+
.prepare(`SELECT spawned_team, id, short_id, actor FROM sessions
|
|
1518
|
+
WHERE spawned_team IS NOT NULL AND spawned_team != ''
|
|
1519
|
+
ORDER BY timestamp ASC`)
|
|
1520
|
+
.all();
|
|
1521
|
+
const out = new Map();
|
|
1522
|
+
for (const r of rows) {
|
|
1523
|
+
out.set(r.spawned_team, { sessionId: r.id, shortId: r.short_id, actor: r.actor ?? undefined });
|
|
1524
|
+
}
|
|
1525
|
+
return out;
|
|
1526
|
+
}
|
|
1486
1527
|
/**
|
|
1487
1528
|
* Return the N most expensive sessions (cost_usd DESC, NULLs excluded),
|
|
1488
1529
|
* honoring the same filter shape as querySessions. Drops rows whose JSONL
|
|
@@ -622,6 +622,11 @@ export declare function __codexScanBranchCountsForTest(): {
|
|
|
622
622
|
};
|
|
623
623
|
/** Test seam: reset the Codex parse-branch counters to observe a scan from a clean slate. */
|
|
624
624
|
export declare function __resetCodexScanBranchCountsForTest(): void;
|
|
625
|
+
/** Parse one Cursor transcript and enrich it with the matching chat meta.json. */
|
|
626
|
+
export declare function readCursorMeta(filePath: string, currentVersion?: string): {
|
|
627
|
+
meta: SessionMeta;
|
|
628
|
+
content: string;
|
|
629
|
+
} | null;
|
|
625
630
|
/** Parse a single Kimi session state.json file to extract session metadata. */
|
|
626
631
|
export declare function readKimiMeta(filePath: string, priorRow?: {
|
|
627
632
|
parserState: string | null;
|