@swarmify/agents-cli 1.13.2 → 1.13.3
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/dist/commands/__tests__/sessions-tail.test.d.ts +2 -0
- package/dist/commands/__tests__/sessions-tail.test.d.ts.map +1 -0
- package/dist/commands/__tests__/sessions-tail.test.js +108 -0
- package/dist/commands/__tests__/sessions-tail.test.js.map +1 -0
- package/dist/commands/sessions-tail.d.ts +20 -0
- package/dist/commands/sessions-tail.d.ts.map +1 -0
- package/dist/commands/sessions-tail.js +236 -0
- package/dist/commands/sessions-tail.js.map +1 -0
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +84 -1
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/teams.d.ts.map +1 -1
- package/dist/commands/teams.js +46 -0
- package/dist/commands/teams.js.map +1 -1
- package/dist/lib/__tests__/shims.test.js +20 -2
- package/dist/lib/__tests__/shims.test.js.map +1 -1
- package/dist/lib/exec.d.ts +2 -1
- package/dist/lib/exec.d.ts.map +1 -1
- package/dist/lib/exec.js +16 -6
- package/dist/lib/exec.js.map +1 -1
- package/dist/lib/factory.d.ts.map +1 -1
- package/dist/lib/factory.js +2 -5
- package/dist/lib/factory.js.map +1 -1
- package/dist/lib/session/active.d.ts +44 -0
- package/dist/lib/session/active.d.ts.map +1 -0
- package/dist/lib/session/active.js +379 -0
- package/dist/lib/session/active.js.map +1 -0
- package/dist/lib/shims.d.ts +7 -2
- package/dist/lib/shims.d.ts.map +1 -1
- package/dist/lib/shims.js +23 -4
- package/dist/lib/shims.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-tail.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/sessions-tail.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as fsp from 'fs/promises';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { tailFile } from '../sessions-tail.js';
|
|
7
|
+
function mkTempDir() {
|
|
8
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'agents-tail-test-'));
|
|
9
|
+
}
|
|
10
|
+
/** Wait for a predicate to become true, polling every 20ms. */
|
|
11
|
+
async function waitFor(predicate, timeoutMs = 2000) {
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
while (!predicate()) {
|
|
14
|
+
if (Date.now() - start > timeoutMs) {
|
|
15
|
+
throw new Error(`timeout waiting for predicate after ${timeoutMs}ms`);
|
|
16
|
+
}
|
|
17
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
describe('tailFile', () => {
|
|
21
|
+
it('emits each line exactly once across multiple appends', async () => {
|
|
22
|
+
const dir = mkTempDir();
|
|
23
|
+
const filePath = path.join(dir, 'session.jsonl');
|
|
24
|
+
fs.writeFileSync(filePath, '');
|
|
25
|
+
const lines = [];
|
|
26
|
+
const ac = new AbortController();
|
|
27
|
+
const tailPromise = tailFile(filePath, (l) => lines.push(l), ac);
|
|
28
|
+
// Give watcher time to attach
|
|
29
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
30
|
+
await fsp.appendFile(filePath, 'one\n');
|
|
31
|
+
await waitFor(() => lines.length >= 1);
|
|
32
|
+
await fsp.appendFile(filePath, 'two\nthree\n');
|
|
33
|
+
await waitFor(() => lines.length >= 3);
|
|
34
|
+
// Partial line: should NOT emit until newline arrives
|
|
35
|
+
await fsp.appendFile(filePath, 'partial');
|
|
36
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
37
|
+
expect(lines).toEqual(['one', 'two', 'three']);
|
|
38
|
+
await fsp.appendFile(filePath, '-done\n');
|
|
39
|
+
await waitFor(() => lines.length >= 4);
|
|
40
|
+
ac.abort();
|
|
41
|
+
await tailPromise;
|
|
42
|
+
expect(lines).toEqual(['one', 'two', 'three', 'partial-done']);
|
|
43
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
44
|
+
});
|
|
45
|
+
it('starts at EOF by default (does not emit pre-existing lines)', async () => {
|
|
46
|
+
const dir = mkTempDir();
|
|
47
|
+
const filePath = path.join(dir, 'session.jsonl');
|
|
48
|
+
fs.writeFileSync(filePath, 'old-a\nold-b\n');
|
|
49
|
+
const lines = [];
|
|
50
|
+
const ac = new AbortController();
|
|
51
|
+
const tailPromise = tailFile(filePath, (l) => lines.push(l), ac);
|
|
52
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
53
|
+
await fsp.appendFile(filePath, 'new\n');
|
|
54
|
+
await waitFor(() => lines.length >= 1);
|
|
55
|
+
ac.abort();
|
|
56
|
+
await tailPromise;
|
|
57
|
+
expect(lines).toEqual(['new']);
|
|
58
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
it('replays the full file when fromStart is true', async () => {
|
|
61
|
+
const dir = mkTempDir();
|
|
62
|
+
const filePath = path.join(dir, 'session.jsonl');
|
|
63
|
+
fs.writeFileSync(filePath, 'a\nb\nc\n');
|
|
64
|
+
const lines = [];
|
|
65
|
+
const ac = new AbortController();
|
|
66
|
+
const tailPromise = tailFile(filePath, (l) => lines.push(l), ac, { fromStart: true });
|
|
67
|
+
await waitFor(() => lines.length >= 3);
|
|
68
|
+
ac.abort();
|
|
69
|
+
await tailPromise;
|
|
70
|
+
expect(lines).toEqual(['a', 'b', 'c']);
|
|
71
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
72
|
+
});
|
|
73
|
+
it('waits for a file that does not exist yet', async () => {
|
|
74
|
+
const dir = mkTempDir();
|
|
75
|
+
const filePath = path.join(dir, 'later.jsonl');
|
|
76
|
+
const lines = [];
|
|
77
|
+
const ac = new AbortController();
|
|
78
|
+
const tailPromise = tailFile(filePath, (l) => lines.push(l), ac);
|
|
79
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
80
|
+
fs.writeFileSync(filePath, 'hello\n');
|
|
81
|
+
await waitFor(() => lines.length >= 1);
|
|
82
|
+
expect(lines).toEqual(['hello']);
|
|
83
|
+
ac.abort();
|
|
84
|
+
await tailPromise;
|
|
85
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
86
|
+
});
|
|
87
|
+
it('handles file truncation by resetting offset', async () => {
|
|
88
|
+
const dir = mkTempDir();
|
|
89
|
+
const filePath = path.join(dir, 'trunc.jsonl');
|
|
90
|
+
fs.writeFileSync(filePath, '');
|
|
91
|
+
const lines = [];
|
|
92
|
+
const ac = new AbortController();
|
|
93
|
+
const tailPromise = tailFile(filePath, (l) => lines.push(l), ac);
|
|
94
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
95
|
+
await fsp.appendFile(filePath, 'first\nsecond\n');
|
|
96
|
+
await waitFor(() => lines.length >= 2);
|
|
97
|
+
// Truncate + write new content
|
|
98
|
+
fs.writeFileSync(filePath, 'after-trunc\n');
|
|
99
|
+
await waitFor(() => lines.length >= 3, 3000);
|
|
100
|
+
ac.abort();
|
|
101
|
+
await tailPromise;
|
|
102
|
+
expect(lines).toContain('first');
|
|
103
|
+
expect(lines).toContain('second');
|
|
104
|
+
expect(lines).toContain('after-trunc');
|
|
105
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=sessions-tail.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-tail.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/sessions-tail.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export interface TailFileOptions {
|
|
3
|
+
/** If true, emit every line from byte 0 first, then follow. Default false (EOF). */
|
|
4
|
+
fromStart?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Tail a file: emit each newline-terminated line via onLine as it's written.
|
|
8
|
+
*
|
|
9
|
+
* Returns a promise that resolves when the AbortController fires. Uses
|
|
10
|
+
* fs.watch on the parent directory (more reliable on macOS than watching
|
|
11
|
+
* the file directly) and tracks byte offset to avoid re-reading content.
|
|
12
|
+
*
|
|
13
|
+
* Emits each line exactly once. Handles partial lines across reads, file
|
|
14
|
+
* truncation (offset reset), and files that don't exist yet (watches the
|
|
15
|
+
* parent dir until the file appears).
|
|
16
|
+
*/
|
|
17
|
+
export declare function tailFile(filePath: string, onLine: (line: string) => void, ac: AbortController, opts?: TailFileOptions): Promise<void>;
|
|
18
|
+
/** Attach the `tail` subcommand to an existing `sessions` command. */
|
|
19
|
+
export declare function registerSessionsTailCommand(sessionsCmd: Command): void;
|
|
20
|
+
//# sourceMappingURL=sessions-tail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-tail.d.ts","sourceRoot":"","sources":["../../src/commands/sessions-tail.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMzC,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC9B,EAAE,EAAE,eAAe,EACnB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAkGf;AAgFD,sEAAsE;AACtE,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CA+BtE"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-tail a session file and stream new events as they're written.
|
|
3
|
+
*
|
|
4
|
+
* Implements `agents sessions tail` — position-tracked reader on the session
|
|
5
|
+
* JSONL, driven by an fs.watch on the parent directory. Claude and Codex
|
|
6
|
+
* only for v1 (both use append-only JSONL).
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as fsp from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { discoverSessions, resolveSessionById } from '../lib/session/discover.js';
|
|
13
|
+
const TAIL_SUPPORTED = ['claude', 'codex'];
|
|
14
|
+
/**
|
|
15
|
+
* Tail a file: emit each newline-terminated line via onLine as it's written.
|
|
16
|
+
*
|
|
17
|
+
* Returns a promise that resolves when the AbortController fires. Uses
|
|
18
|
+
* fs.watch on the parent directory (more reliable on macOS than watching
|
|
19
|
+
* the file directly) and tracks byte offset to avoid re-reading content.
|
|
20
|
+
*
|
|
21
|
+
* Emits each line exactly once. Handles partial lines across reads, file
|
|
22
|
+
* truncation (offset reset), and files that don't exist yet (watches the
|
|
23
|
+
* parent dir until the file appears).
|
|
24
|
+
*/
|
|
25
|
+
export async function tailFile(filePath, onLine, ac, opts = {}) {
|
|
26
|
+
const dir = path.dirname(filePath);
|
|
27
|
+
const base = path.basename(filePath);
|
|
28
|
+
let fd = null;
|
|
29
|
+
let offset = 0;
|
|
30
|
+
let partial = '';
|
|
31
|
+
let reading = false;
|
|
32
|
+
let dirty = false;
|
|
33
|
+
const openIfPossible = async (initial) => {
|
|
34
|
+
try {
|
|
35
|
+
fd = await fsp.open(filePath, 'r');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (initial) {
|
|
41
|
+
const st = await fd.stat();
|
|
42
|
+
offset = opts.fromStart ? 0 : st.size;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// File appeared after we started watching — emit from byte 0.
|
|
46
|
+
offset = 0;
|
|
47
|
+
partial = '';
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const closeFd = async () => {
|
|
52
|
+
const h = fd;
|
|
53
|
+
fd = null;
|
|
54
|
+
if (h) {
|
|
55
|
+
try {
|
|
56
|
+
await h.close();
|
|
57
|
+
}
|
|
58
|
+
catch { /* already closed */ }
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const drain = async () => {
|
|
62
|
+
if (reading) {
|
|
63
|
+
dirty = true;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
reading = true;
|
|
67
|
+
try {
|
|
68
|
+
while (!ac.signal.aborted) {
|
|
69
|
+
if (!fd) {
|
|
70
|
+
const ok = await openIfPossible(false);
|
|
71
|
+
if (!ok)
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const st = await fd.stat();
|
|
75
|
+
if (st.size < offset) {
|
|
76
|
+
// Truncation or rotation: reset to start of the new content.
|
|
77
|
+
offset = 0;
|
|
78
|
+
partial = '';
|
|
79
|
+
}
|
|
80
|
+
if (st.size === offset) {
|
|
81
|
+
if (dirty) {
|
|
82
|
+
dirty = false;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const len = st.size - offset;
|
|
88
|
+
const buf = Buffer.alloc(len);
|
|
89
|
+
await fd.read(buf, 0, len, offset);
|
|
90
|
+
offset = st.size;
|
|
91
|
+
const text = partial + buf.toString('utf-8');
|
|
92
|
+
const lines = text.split('\n');
|
|
93
|
+
partial = lines.pop() ?? '';
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (ac.signal.aborted)
|
|
96
|
+
return;
|
|
97
|
+
if (line.length > 0)
|
|
98
|
+
onLine(line);
|
|
99
|
+
}
|
|
100
|
+
if (!dirty)
|
|
101
|
+
return;
|
|
102
|
+
dirty = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
reading = false;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
if (fs.existsSync(filePath)) {
|
|
110
|
+
await openIfPossible(true);
|
|
111
|
+
}
|
|
112
|
+
const watcher = fs.watch(dir, { recursive: false }, (_event, filename) => {
|
|
113
|
+
if (ac.signal.aborted)
|
|
114
|
+
return;
|
|
115
|
+
// On macOS, filename is sometimes null — don't filter in that case.
|
|
116
|
+
if (filename !== null && filename !== base)
|
|
117
|
+
return;
|
|
118
|
+
// Stat detects append, truncation, or reappearance in drain(); trust that
|
|
119
|
+
// over the event type, which macOS coalesces inconsistently.
|
|
120
|
+
void drain();
|
|
121
|
+
});
|
|
122
|
+
// Initial drain in case the file has content beyond EOF-of-our-offset
|
|
123
|
+
// (e.g. --from-start, or a write between stat and watch attach).
|
|
124
|
+
await drain();
|
|
125
|
+
await new Promise((resolve) => {
|
|
126
|
+
const onAbort = () => {
|
|
127
|
+
ac.signal.removeEventListener('abort', onAbort);
|
|
128
|
+
try {
|
|
129
|
+
watcher.close();
|
|
130
|
+
}
|
|
131
|
+
catch { /* noop */ }
|
|
132
|
+
void closeFd().then(() => resolve());
|
|
133
|
+
};
|
|
134
|
+
if (ac.signal.aborted)
|
|
135
|
+
onAbort();
|
|
136
|
+
else
|
|
137
|
+
ac.signal.addEventListener('abort', onAbort);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function findLatestTailable() {
|
|
141
|
+
const sessions = await discoverSessions({ all: true, limit: 100 });
|
|
142
|
+
const eligible = sessions.filter(s => TAIL_SUPPORTED.includes(s.agent));
|
|
143
|
+
if (eligible.length === 0)
|
|
144
|
+
return undefined;
|
|
145
|
+
eligible.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
146
|
+
return eligible[0];
|
|
147
|
+
}
|
|
148
|
+
async function resolveTailable(sessionId) {
|
|
149
|
+
const sessions = await discoverSessions({ all: true, limit: 5000 });
|
|
150
|
+
const matches = resolveSessionById(sessions, sessionId);
|
|
151
|
+
if (matches.length === 0)
|
|
152
|
+
return undefined;
|
|
153
|
+
// Prefer a supported agent among matches; if only unsupported match, signal.
|
|
154
|
+
const supported = matches.find(s => TAIL_SUPPORTED.includes(s.agent));
|
|
155
|
+
if (supported)
|
|
156
|
+
return supported;
|
|
157
|
+
return 'unsupported';
|
|
158
|
+
}
|
|
159
|
+
async function runTail(sessionId, options) {
|
|
160
|
+
let session;
|
|
161
|
+
if (options.latest) {
|
|
162
|
+
session = await findLatestTailable();
|
|
163
|
+
if (!session) {
|
|
164
|
+
console.log(chalk.gray('No tailable sessions found (claude or codex).'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (!sessionId) {
|
|
170
|
+
console.error(chalk.red('Missing session ID. Pass an ID or use --latest.'));
|
|
171
|
+
process.exit(2);
|
|
172
|
+
}
|
|
173
|
+
const resolved = await resolveTailable(sessionId);
|
|
174
|
+
if (resolved === 'unsupported') {
|
|
175
|
+
console.error(chalk.red(`Tailing is supported for append-only JSONL agents only (claude, codex).`));
|
|
176
|
+
process.exit(2);
|
|
177
|
+
}
|
|
178
|
+
if (!resolved) {
|
|
179
|
+
console.error(chalk.red(`No session found matching: ${sessionId}`));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
session = resolved;
|
|
183
|
+
}
|
|
184
|
+
const filePath = session.filePath.split('#')[0];
|
|
185
|
+
if (process.stderr.isTTY) {
|
|
186
|
+
process.stderr.write(chalk.gray(`Tailing ${session.agent} ${session.shortId} — ${filePath}\n`) +
|
|
187
|
+
chalk.gray('Ctrl+C to stop.\n'));
|
|
188
|
+
}
|
|
189
|
+
const ac = new AbortController();
|
|
190
|
+
const onSig = () => { ac.abort(); };
|
|
191
|
+
process.on('SIGINT', onSig);
|
|
192
|
+
process.on('SIGTERM', onSig);
|
|
193
|
+
try {
|
|
194
|
+
await tailFile(filePath, (line) => {
|
|
195
|
+
process.stdout.write(line + '\n');
|
|
196
|
+
}, ac, { fromStart: options.fromStart });
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
process.off('SIGINT', onSig);
|
|
200
|
+
process.off('SIGTERM', onSig);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Attach the `tail` subcommand to an existing `sessions` command. */
|
|
204
|
+
export function registerSessionsTailCommand(sessionsCmd) {
|
|
205
|
+
sessionsCmd
|
|
206
|
+
.command('tail [sessionId]')
|
|
207
|
+
.description('Live-tail a session file, streaming new JSONL events as they are written. Long-running: press Ctrl+C to stop. Claude and Codex only.')
|
|
208
|
+
.option('--latest', 'Tail the most recent tailable session (claude or codex)')
|
|
209
|
+
.option('--from-start', 'Emit the full file first, then follow (default: start at EOF)')
|
|
210
|
+
.option('--json', 'Raw JSONL passthrough (default)')
|
|
211
|
+
.addHelpText('after', `
|
|
212
|
+
This command runs until interrupted (Ctrl+C). Each line printed to stdout
|
|
213
|
+
is a raw JSONL event from the session file — parse with jq or similar.
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
# Follow the most recent active Claude or Codex session
|
|
217
|
+
agents sessions tail --latest
|
|
218
|
+
|
|
219
|
+
# Follow a specific session by short or full ID
|
|
220
|
+
agents sessions tail a1b2c3d4
|
|
221
|
+
|
|
222
|
+
# Replay from the beginning, then follow
|
|
223
|
+
agents sessions tail a1b2c3d4 --from-start
|
|
224
|
+
|
|
225
|
+
# Pipe through jq to extract just user messages
|
|
226
|
+
agents sessions tail --latest | jq 'select(.type == "user")'
|
|
227
|
+
|
|
228
|
+
Only Claude and Codex sessions are supported — they append JSONL one event
|
|
229
|
+
per line, which makes live-tailing safe. Gemini, OpenCode, and OpenClaw
|
|
230
|
+
use formats that rewrite the file or store state elsewhere.
|
|
231
|
+
`)
|
|
232
|
+
.action(async (sessionId, options) => {
|
|
233
|
+
await runTail(sessionId, options);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=sessions-tail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions-tail.js","sourceRoot":"","sources":["../../src/commands/sessions-tail.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,OAAO,KAAK,EAAkB,WAAW,EAAY,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,OAAO,KAAK,EAAkB,WAAW,EAAY,MAAM,yBAAyB,CAAC;AA4rBrF;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAgBhG;AA2BD,yFAAyF;AACzF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EAAE,EACvB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,WAAW,EAAE,CAgDf;AAwMD,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkF/D"}
|
|
@@ -15,6 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import ora from 'ora';
|
|
16
16
|
import { SESSION_AGENTS } from '../lib/session/types.js';
|
|
17
17
|
import { discoverArtifacts, readArtifact, resolveArtifact } from '../lib/session/artifacts.js';
|
|
18
|
+
import { getActiveSessions } from '../lib/session/active.js';
|
|
18
19
|
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex } from '../lib/session/discover.js';
|
|
19
20
|
import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
20
21
|
import { parseSession } from '../lib/session/parse.js';
|
|
@@ -24,6 +25,7 @@ import { colorAgent } from '../lib/agents.js';
|
|
|
24
25
|
import { resolveVersion } from '../lib/versions.js';
|
|
25
26
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
26
27
|
import { sessionPicker } from './sessions-picker.js';
|
|
28
|
+
import { registerSessionsTailCommand } from './sessions-tail.js';
|
|
27
29
|
const SESSION_AGENT_FILTER_HELP = `Filter by agent, e.g. claude, codex, claude@2.0.65`;
|
|
28
30
|
const CLAUDE_RESUME_MATCH_WINDOW_MS = 10 * 60_000;
|
|
29
31
|
const LOAD_VERBS = ['Loading', 'Scanning', 'Gathering', 'Indexing', 'Reading'];
|
|
@@ -131,8 +133,83 @@ async function renderArtifactsForSession(session, listAll, name) {
|
|
|
131
133
|
}
|
|
132
134
|
console.log(chalk.gray(`\n${artifacts.length} artifact${artifacts.length !== 1 ? 's' : ''}.`));
|
|
133
135
|
}
|
|
136
|
+
function statusColor(status) {
|
|
137
|
+
switch (status) {
|
|
138
|
+
case 'running': return chalk.green;
|
|
139
|
+
case 'idle': return chalk.gray;
|
|
140
|
+
case 'queued': return chalk.blue;
|
|
141
|
+
case 'input_required': return chalk.yellow;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function contextColor(context) {
|
|
145
|
+
switch (context) {
|
|
146
|
+
case 'terminal': return chalk.magenta;
|
|
147
|
+
case 'teams': return chalk.cyan;
|
|
148
|
+
case 'cloud': return chalk.blue;
|
|
149
|
+
case 'headless': return chalk.gray;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function shortCwd(cwd) {
|
|
153
|
+
if (!cwd)
|
|
154
|
+
return '-';
|
|
155
|
+
const home = os.homedir();
|
|
156
|
+
return cwd.startsWith(home) ? '~' + cwd.slice(home.length) : cwd;
|
|
157
|
+
}
|
|
158
|
+
function formatStartedAt(startedAtMs) {
|
|
159
|
+
if (!startedAtMs)
|
|
160
|
+
return '-';
|
|
161
|
+
return formatRelativeTime(new Date(startedAtMs).toISOString());
|
|
162
|
+
}
|
|
163
|
+
/** Render the unified active-session view. */
|
|
164
|
+
async function renderActiveSessions(asJson) {
|
|
165
|
+
const sessions = await getActiveSessions();
|
|
166
|
+
if (asJson) {
|
|
167
|
+
process.stdout.write(JSON.stringify(sessions, null, 2) + '\n');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (sessions.length === 0) {
|
|
171
|
+
console.log(chalk.gray('No active agent sessions.'));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
for (const s of sessions) {
|
|
175
|
+
const kindCol = colorAgent(s.kind)(padRight(truncate(s.kind, 8), 9));
|
|
176
|
+
const ctxCol = contextColor(s.context)(padRight(truncate(s.context, 8), 9));
|
|
177
|
+
const hostCol = chalk.gray(padRight(truncate(s.host ?? '-', 8), 9));
|
|
178
|
+
const statusCol = statusColor(s.status)(padRight(truncate(s.status, 8), 9));
|
|
179
|
+
const pidCol = chalk.yellow(padRight(s.pid ? String(s.pid) : '-', 7));
|
|
180
|
+
const idCol = chalk.white(padRight(s.sessionId ? s.sessionId.slice(0, 8) : '-', 10));
|
|
181
|
+
const detail = s.context === 'cloud'
|
|
182
|
+
? `${s.cloudProvider ?? ''}${s.cloudTaskId ? ` · ${s.cloudTaskId.slice(0, 12)}` : ''}`
|
|
183
|
+
: s.context === 'teams'
|
|
184
|
+
? `${s.teamName ?? ''}${s.label ? ` · ${s.label}` : ''}`
|
|
185
|
+
: s.label ?? shortCwd(s.cwd);
|
|
186
|
+
console.log(pidCol +
|
|
187
|
+
kindCol +
|
|
188
|
+
ctxCol +
|
|
189
|
+
hostCol +
|
|
190
|
+
statusCol +
|
|
191
|
+
idCol +
|
|
192
|
+
chalk.cyan(padRight(truncate(detail || '-', 30), 32)) +
|
|
193
|
+
chalk.gray(formatStartedAt(s.startedAtMs)));
|
|
194
|
+
}
|
|
195
|
+
const runningCount = sessions.filter(s => s.status === 'running').length;
|
|
196
|
+
const idleCount = sessions.filter(s => s.status === 'idle').length;
|
|
197
|
+
const queuedCount = sessions.filter(s => s.status === 'queued' || s.status === 'input_required').length;
|
|
198
|
+
const parts = [];
|
|
199
|
+
if (runningCount > 0)
|
|
200
|
+
parts.push(`${runningCount} running`);
|
|
201
|
+
if (idleCount > 0)
|
|
202
|
+
parts.push(`${idleCount} idle`);
|
|
203
|
+
if (queuedCount > 0)
|
|
204
|
+
parts.push(`${queuedCount} queued`);
|
|
205
|
+
console.log(chalk.gray(`\n${sessions.length} active (${parts.join(', ')}).`));
|
|
206
|
+
}
|
|
134
207
|
/** Main action handler for `agents sessions`. Routes to picker, table, or single-session render. */
|
|
135
208
|
async function sessionsAction(query, options) {
|
|
209
|
+
if (options.active) {
|
|
210
|
+
await renderActiveSessions(options.json === true);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
136
213
|
let filterOpts;
|
|
137
214
|
try {
|
|
138
215
|
filterOpts = buildFilterOptions(options);
|
|
@@ -800,7 +877,7 @@ async function renderOneSession(query, mode, scope) {
|
|
|
800
877
|
}
|
|
801
878
|
/** Register the `agents sessions` command with all its options and help text. */
|
|
802
879
|
export function registerSessionsCommands(program) {
|
|
803
|
-
program
|
|
880
|
+
const sessionsCmd = program
|
|
804
881
|
.command('sessions')
|
|
805
882
|
.argument('[query]', 'Session ID, search query, or path (., ../, /path) to filter by project')
|
|
806
883
|
.description('Find, browse, and read agent conversation transcripts across Claude, Codex, Gemini, and OpenCode.')
|
|
@@ -819,6 +896,7 @@ export function registerSessionsCommands(program) {
|
|
|
819
896
|
.option('--last <n>', 'Keep only the last N turns (a turn starts at each user message)')
|
|
820
897
|
.option('--artifacts', 'List all files written or edited during a session')
|
|
821
898
|
.option('--artifact <name>', 'Read a specific artifact by filename or path (outputs to stdout)')
|
|
899
|
+
.option('--active', 'Show only sessions running right now across terminals, teams, cloud, and headless agents')
|
|
822
900
|
.addHelpText('after', `
|
|
823
901
|
Examples:
|
|
824
902
|
# Interactive picker: browse and search recent sessions (TTY only)
|
|
@@ -866,6 +944,10 @@ Examples:
|
|
|
866
944
|
# Include team-spawned sessions in results
|
|
867
945
|
agents sessions --teams
|
|
868
946
|
|
|
947
|
+
# Show only live sessions across terminals, teams, cloud, and headless agents
|
|
948
|
+
agents sessions --active
|
|
949
|
+
agents sessions --active --json
|
|
950
|
+
|
|
869
951
|
Notes:
|
|
870
952
|
- --include and --exclude are mutually exclusive.
|
|
871
953
|
- --first and --last are mutually exclusive.
|
|
@@ -874,6 +956,7 @@ Notes:
|
|
|
874
956
|
.action(async (query, options) => {
|
|
875
957
|
await sessionsAction(query, options);
|
|
876
958
|
});
|
|
959
|
+
registerSessionsTailCommand(sessionsCmd);
|
|
877
960
|
}
|
|
878
961
|
function formatNoSessionsMessage(showAll, project) {
|
|
879
962
|
const projectQuery = project?.trim();
|