@ai-devkit/agent-manager 0.16.0 → 0.18.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/.swcrc +27 -0
- package/dist/AgentManager.js +72 -74
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +564 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js +1419 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/PiAdapter.test.js +590 -0
- package/dist/__tests__/adapters/PiAdapter.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
- package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
- package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
- package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
- package/dist/__tests__/utils/matching.test.js +324 -0
- package/dist/__tests__/utils/matching.test.js.map +1 -0
- package/dist/__tests__/utils/process.test.js +177 -0
- package/dist/__tests__/utils/process.test.js.map +1 -0
- package/dist/__tests__/utils/session.test.js +158 -0
- package/dist/__tests__/utils/session.test.js.map +1 -0
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +8 -7
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +66 -77
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +13 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +225 -141
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +44 -0
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
- package/dist/adapters/CopilotAdapter.js +314 -0
- package/dist/adapters/CopilotAdapter.js.map +1 -0
- package/dist/adapters/GeminiCliAdapter.d.ts +2 -0
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +138 -156
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +60 -69
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/PiAdapter.d.ts +62 -0
- package/dist/adapters/PiAdapter.d.ts.map +1 -0
- package/dist/adapters/PiAdapter.js +450 -0
- package/dist/adapters/PiAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +62 -49
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TmuxManager.js +59 -34
- package/dist/terminal/TmuxManager.js.map +1 -1
- package/dist/terminal/TtyWriter.js +37 -16
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/index.js +1 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/utils/AgentRegistry.js +41 -32
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/dist/utils/ClaudeSessionParser.js +46 -87
- package/dist/utils/ClaudeSessionParser.js.map +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +22 -11
- package/dist/utils/agents.js.map +1 -1
- package/dist/utils/applescript.js +3 -6
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/matching.js +21 -29
- package/dist/utils/matching.js.map +1 -1
- package/dist/utils/process.js +77 -56
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/session.js +20 -34
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/sortAgents.js +6 -8
- package/dist/utils/sortAgents.js.map +1 -1
- package/package.json +11 -7
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +2 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +145 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +74 -1
- package/src/__tests__/adapters/PiAdapter.test.ts +435 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +164 -6
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +42 -3
- package/src/adapters/PiAdapter.ts +597 -0
- package/src/adapters/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/utils/agents.ts +1 -1
|
@@ -3,17 +3,15 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
export class RenameNotFoundError extends Error {
|
|
5
5
|
agentName;
|
|
6
|
-
constructor(agentName)
|
|
7
|
-
super(`Agent "${agentName}" not found in registry.`);
|
|
8
|
-
this.agentName = agentName;
|
|
6
|
+
constructor(agentName){
|
|
7
|
+
super(`Agent "${agentName}" not found in registry.`), this.agentName = agentName;
|
|
9
8
|
this.name = 'RenameNotFoundError';
|
|
10
9
|
}
|
|
11
10
|
}
|
|
12
11
|
export class RenameConflictError extends Error {
|
|
13
12
|
agentName;
|
|
14
|
-
constructor(agentName)
|
|
15
|
-
super(`Agent "${agentName}" is already in use.`);
|
|
16
|
-
this.agentName = agentName;
|
|
13
|
+
constructor(agentName){
|
|
14
|
+
super(`Agent "${agentName}" is already in use.`), this.agentName = agentName;
|
|
17
15
|
this.name = 'RenameConflictError';
|
|
18
16
|
}
|
|
19
17
|
}
|
|
@@ -21,7 +19,7 @@ const DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.ai-devkit', 'agents.json
|
|
|
21
19
|
let defaultInstance = null;
|
|
22
20
|
export class AgentRegistry {
|
|
23
21
|
filePath;
|
|
24
|
-
constructor(filePath = DEFAULT_REGISTRY_PATH)
|
|
22
|
+
constructor(filePath = DEFAULT_REGISTRY_PATH){
|
|
25
23
|
this.filePath = filePath;
|
|
26
24
|
}
|
|
27
25
|
static default() {
|
|
@@ -34,56 +32,61 @@ export class AgentRegistry {
|
|
|
34
32
|
try {
|
|
35
33
|
const raw = fs.readFileSync(this.filePath, 'utf8');
|
|
36
34
|
const parsed = JSON.parse(raw);
|
|
37
|
-
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
return {
|
|
36
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : []
|
|
37
|
+
};
|
|
38
|
+
} catch {
|
|
39
|
+
return {
|
|
40
|
+
entries: []
|
|
41
|
+
};
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
writeFile(data) {
|
|
44
45
|
const dir = path.dirname(this.filePath);
|
|
45
|
-
fs.mkdirSync(dir, {
|
|
46
|
+
fs.mkdirSync(dir, {
|
|
47
|
+
recursive: true
|
|
48
|
+
});
|
|
46
49
|
const tmp = `${this.filePath}.tmp`;
|
|
47
50
|
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
|
|
48
51
|
fs.renameSync(tmp, this.filePath);
|
|
49
52
|
}
|
|
50
53
|
mergeEntry(incoming, existing) {
|
|
51
|
-
if (!existing)
|
|
52
|
-
return incoming;
|
|
54
|
+
if (!existing) return incoming;
|
|
53
55
|
return {
|
|
54
56
|
...incoming,
|
|
55
|
-
tmuxSession: incoming.tmuxSession || existing.tmuxSession
|
|
57
|
+
tmuxSession: incoming.tmuxSession || existing.tmuxSession
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
60
|
isAlive(entry) {
|
|
59
61
|
try {
|
|
60
62
|
process.kill(entry.pid, 0);
|
|
61
63
|
return true;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
+
} catch {
|
|
64
65
|
return false;
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
prune() {
|
|
68
69
|
const data = this.readFile();
|
|
69
|
-
const live = data.entries.filter((e)
|
|
70
|
+
const live = data.entries.filter((e)=>this.isAlive(e));
|
|
70
71
|
if (live.length !== data.entries.length) {
|
|
71
|
-
this.writeFile({
|
|
72
|
+
this.writeFile({
|
|
73
|
+
entries: live
|
|
74
|
+
});
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
register(entry) {
|
|
75
|
-
this.registerBatch([
|
|
78
|
+
this.registerBatch([
|
|
79
|
+
entry
|
|
80
|
+
]);
|
|
76
81
|
}
|
|
77
82
|
registerBatch(entries) {
|
|
78
|
-
if (entries.length === 0)
|
|
79
|
-
return;
|
|
83
|
+
if (entries.length === 0) return;
|
|
80
84
|
const data = this.readFile();
|
|
81
|
-
for (const incoming of entries)
|
|
82
|
-
const idx = data.entries.findIndex((e)
|
|
85
|
+
for (const incoming of entries){
|
|
86
|
+
const idx = data.entries.findIndex((e)=>e.name === incoming.name);
|
|
83
87
|
if (idx >= 0) {
|
|
84
88
|
data.entries[idx] = this.mergeEntry(incoming, data.entries[idx]);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
89
|
+
} else {
|
|
87
90
|
data.entries.push(incoming);
|
|
88
91
|
}
|
|
89
92
|
}
|
|
@@ -91,24 +94,30 @@ export class AgentRegistry {
|
|
|
91
94
|
}
|
|
92
95
|
rename(currentName, newName) {
|
|
93
96
|
const data = this.readFile();
|
|
94
|
-
const idx = data.entries.findIndex((e)
|
|
97
|
+
const idx = data.entries.findIndex((e)=>e.name === currentName);
|
|
95
98
|
if (idx < 0) {
|
|
96
99
|
throw new RenameNotFoundError(currentName);
|
|
97
100
|
}
|
|
98
|
-
const liveEntries = data.entries.filter((e)
|
|
99
|
-
const conflict = liveEntries.find((e)
|
|
101
|
+
const liveEntries = data.entries.filter((e)=>this.isAlive(e));
|
|
102
|
+
const conflict = liveEntries.find((e)=>e.name === newName);
|
|
100
103
|
if (conflict) {
|
|
101
104
|
throw new RenameConflictError(newName);
|
|
102
105
|
}
|
|
103
|
-
const pruned = liveEntries.map((e)
|
|
104
|
-
|
|
106
|
+
const pruned = liveEntries.map((e)=>e.name === currentName ? {
|
|
107
|
+
...e,
|
|
108
|
+
name: newName
|
|
109
|
+
} : e);
|
|
110
|
+
this.writeFile({
|
|
111
|
+
entries: pruned
|
|
112
|
+
});
|
|
105
113
|
}
|
|
106
114
|
lookup(name) {
|
|
107
115
|
const data = this.readFile();
|
|
108
|
-
return data.entries.find((e)
|
|
116
|
+
return data.entries.find((e)=>e.name === name) ?? null;
|
|
109
117
|
}
|
|
110
118
|
list() {
|
|
111
119
|
return this.readFile().entries;
|
|
112
120
|
}
|
|
113
121
|
}
|
|
122
|
+
|
|
114
123
|
//# sourceMappingURL=AgentRegistry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/AgentRegistry.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport type { AgentType } from '../adapters/AgentAdapter.js';\n\nexport class RenameNotFoundError extends Error {\n constructor(public agentName: string) {\n super(`Agent \"${agentName}\" not found in registry.`);\n this.name = 'RenameNotFoundError';\n }\n}\n\nexport class RenameConflictError extends Error {\n constructor(public agentName: string) {\n super(`Agent \"${agentName}\" is already in use.`);\n this.name = 'RenameConflictError';\n }\n}\n\nexport interface RegistryEntry {\n name: string;\n type: AgentType;\n pid: number;\n tmuxSession: string;\n cwd: string;\n startedAt: string; // ISO 8601\n sessionId: string;\n sessionFilePath: string;\n}\n\ninterface RegistryFile {\n entries: RegistryEntry[];\n}\n\nconst DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.ai-devkit', 'agents.json');\n\nlet defaultInstance: AgentRegistry | null = null;\n\nexport class AgentRegistry {\n private filePath: string;\n\n constructor(filePath: string = DEFAULT_REGISTRY_PATH) {\n this.filePath = filePath;\n }\n\n static default(): AgentRegistry {\n if (!defaultInstance) {\n defaultInstance = new AgentRegistry();\n }\n return defaultInstance;\n }\n\n private readFile(): RegistryFile {\n try {\n const raw = fs.readFileSync(this.filePath, 'utf8');\n const parsed = JSON.parse(raw) as RegistryFile;\n return { entries: Array.isArray(parsed.entries) ? parsed.entries : [] };\n } catch {\n return { entries: [] };\n }\n }\n\n private writeFile(data: RegistryFile): void {\n const dir = path.dirname(this.filePath);\n fs.mkdirSync(dir, { recursive: true });\n const tmp = `${this.filePath}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');\n fs.renameSync(tmp, this.filePath);\n }\n\n private mergeEntry(incoming: RegistryEntry, existing: RegistryEntry | undefined): RegistryEntry {\n if (!existing) return incoming;\n return {\n ...incoming,\n tmuxSession: incoming.tmuxSession || existing.tmuxSession,\n };\n }\n\n isAlive(entry: RegistryEntry): boolean {\n try {\n process.kill(entry.pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n prune(): void {\n const data = this.readFile();\n const live = data.entries.filter((e) => this.isAlive(e));\n if (live.length !== data.entries.length) {\n this.writeFile({ entries: live });\n }\n }\n\n register(entry: RegistryEntry): void {\n this.registerBatch([entry]);\n }\n\n registerBatch(entries: RegistryEntry[]): void {\n if (entries.length === 0) return;\n const data = this.readFile();\n for (const incoming of entries) {\n const idx = data.entries.findIndex((e) => e.name === incoming.name);\n if (idx >= 0) {\n data.entries[idx] = this.mergeEntry(incoming, data.entries[idx]);\n } else {\n data.entries.push(incoming);\n }\n }\n this.writeFile(data);\n }\n\n rename(currentName: string, newName: string): void {\n const data = this.readFile();\n const idx = data.entries.findIndex((e) => e.name === currentName);\n if (idx < 0) {\n throw new RenameNotFoundError(currentName);\n }\n const liveEntries = data.entries.filter((e) => this.isAlive(e));\n const conflict = liveEntries.find((e) => e.name === newName);\n if (conflict) {\n throw new RenameConflictError(newName);\n }\n const pruned = liveEntries.map((e) =>\n e.name === currentName ? { ...e, name: newName } : e,\n );\n this.writeFile({ entries: pruned });\n }\n\n lookup(name: string): RegistryEntry | null {\n const data = this.readFile();\n return data.entries.find((e) => e.name === name) ?? null;\n }\n\n list(): RegistryEntry[] {\n return this.readFile().entries;\n }\n}\n"],"names":["fs","os","path","RenameNotFoundError","Error","agentName","name","RenameConflictError","DEFAULT_REGISTRY_PATH","join","homedir","defaultInstance","AgentRegistry","filePath","default","readFile","raw","readFileSync","parsed","JSON","parse","entries","Array","isArray","writeFile","data","dir","dirname","mkdirSync","recursive","tmp","writeFileSync","stringify","renameSync","mergeEntry","incoming","existing","tmuxSession","isAlive","entry","process","kill","pid","prune","live","filter","e","length","register","registerBatch","idx","findIndex","push","rename","currentName","newName","liveEntries","conflict","find","pruned","map","lookup","list"],"mappings":"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"}
|
|
@@ -10,13 +10,12 @@ import { AgentStatus } from '../adapters/AgentAdapter.js';
|
|
|
10
10
|
* `hook_progress`, …) are UI-state events that must not overwrite the
|
|
11
11
|
* last conversation turn — otherwise polling between writes lands on a
|
|
12
12
|
* UI-state entry and `determineStatus` falls through to UNKNOWN.
|
|
13
|
-
*/
|
|
14
|
-
const CONVERSATION_ENTRY_TYPES = new Set([
|
|
13
|
+
*/ const CONVERSATION_ENTRY_TYPES = new Set([
|
|
15
14
|
'user',
|
|
16
15
|
'assistant',
|
|
17
16
|
'system',
|
|
18
17
|
'progress',
|
|
19
|
-
'thinking'
|
|
18
|
+
'thinking'
|
|
20
19
|
]);
|
|
21
20
|
/**
|
|
22
21
|
* Parses Claude Code session JSONL files into structured data.
|
|
@@ -24,8 +23,7 @@ const CONVERSATION_ENTRY_TYPES = new Set([
|
|
|
24
23
|
* Session files live at ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
|
|
25
24
|
* and contain one JSON object per line, each representing a conversation
|
|
26
25
|
* event (user turn, assistant response, tool call, etc.).
|
|
27
|
-
*/
|
|
28
|
-
export class ClaudeSessionParser {
|
|
26
|
+
*/ export class ClaudeSessionParser {
|
|
29
27
|
/**
|
|
30
28
|
* Parse a session JSONL file into a ClaudeSession summary.
|
|
31
29
|
*
|
|
@@ -34,14 +32,12 @@ export class ClaudeSessionParser {
|
|
|
34
32
|
* session was interrupted, and the last meaningful user message.
|
|
35
33
|
*
|
|
36
34
|
* Returns null if the file is unreadable or empty.
|
|
37
|
-
*/
|
|
38
|
-
readSession(filePath, projectPath) {
|
|
35
|
+
*/ readSession(filePath, projectPath) {
|
|
39
36
|
const sessionId = path.basename(filePath, '.jsonl');
|
|
40
37
|
let content;
|
|
41
38
|
try {
|
|
42
39
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
40
|
+
} catch {
|
|
45
41
|
return null;
|
|
46
42
|
}
|
|
47
43
|
const allLines = content.trim().split('\n');
|
|
@@ -55,7 +51,7 @@ export class ClaudeSessionParser {
|
|
|
55
51
|
let isInterrupted = false;
|
|
56
52
|
let lastUserMessage;
|
|
57
53
|
let firstUserMessage;
|
|
58
|
-
for (const line of allLines)
|
|
54
|
+
for (const line of allLines){
|
|
59
55
|
try {
|
|
60
56
|
const entry = JSON.parse(line);
|
|
61
57
|
if (entry.timestamp) {
|
|
@@ -71,12 +67,7 @@ export class ClaudeSessionParser {
|
|
|
71
67
|
lastEntryType = entry.type;
|
|
72
68
|
if (entry.type === 'user') {
|
|
73
69
|
const msgContent = entry.message?.content;
|
|
74
|
-
isInterrupted =
|
|
75
|
-
Array.isArray(msgContent) &&
|
|
76
|
-
msgContent.some((c) => (c.type === 'text' &&
|
|
77
|
-
c.text?.includes('[Request interrupted')) ||
|
|
78
|
-
(c.type === 'tool_result' &&
|
|
79
|
-
c.content?.includes('[Request interrupted')));
|
|
70
|
+
isInterrupted = Array.isArray(msgContent) && msgContent.some((c)=>c.type === 'text' && c.text?.includes('[Request interrupted') || c.type === 'tool_result' && c.content?.includes('[Request interrupted'));
|
|
80
71
|
const text = this.extractUserMessageText(msgContent);
|
|
81
72
|
if (text) {
|
|
82
73
|
lastUserMessage = text;
|
|
@@ -84,13 +75,11 @@ export class ClaudeSessionParser {
|
|
|
84
75
|
firstUserMessage = text;
|
|
85
76
|
}
|
|
86
77
|
}
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
78
|
+
} else {
|
|
89
79
|
isInterrupted = false;
|
|
90
80
|
}
|
|
91
81
|
}
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
82
|
+
} catch {
|
|
94
83
|
continue;
|
|
95
84
|
}
|
|
96
85
|
}
|
|
@@ -103,7 +92,7 @@ export class ClaudeSessionParser {
|
|
|
103
92
|
lastEntryType,
|
|
104
93
|
isInterrupted,
|
|
105
94
|
lastUserMessage,
|
|
106
|
-
firstUserMessage
|
|
95
|
+
firstUserMessage
|
|
107
96
|
};
|
|
108
97
|
}
|
|
109
98
|
/**
|
|
@@ -115,18 +104,14 @@ export class ClaudeSessionParser {
|
|
|
115
104
|
* - "progress" / "thinking" → RUNNING
|
|
116
105
|
* - "assistant" → WAITING (agent responded, awaiting user)
|
|
117
106
|
* - "system" → IDLE
|
|
118
|
-
*/
|
|
119
|
-
determineStatus(session) {
|
|
107
|
+
*/ determineStatus(session) {
|
|
120
108
|
if (!session.lastEntryType) {
|
|
121
109
|
return AgentStatus.UNKNOWN;
|
|
122
110
|
}
|
|
123
111
|
if (session.lastEntryType === 'user') {
|
|
124
|
-
return session.isInterrupted
|
|
125
|
-
? AgentStatus.WAITING
|
|
126
|
-
: AgentStatus.RUNNING;
|
|
112
|
+
return session.isInterrupted ? AgentStatus.WAITING : AgentStatus.RUNNING;
|
|
127
113
|
}
|
|
128
|
-
if (session.lastEntryType === 'progress' ||
|
|
129
|
-
session.lastEntryType === 'thinking') {
|
|
114
|
+
if (session.lastEntryType === 'progress' || session.lastEntryType === 'thinking') {
|
|
130
115
|
return AgentStatus.RUNNING;
|
|
131
116
|
}
|
|
132
117
|
if (session.lastEntryType === 'assistant') {
|
|
@@ -142,46 +127,39 @@ export class ClaudeSessionParser {
|
|
|
142
127
|
*
|
|
143
128
|
* Default mode returns only text content from user/assistant/system messages.
|
|
144
129
|
* Verbose mode also includes tool_use and tool_result blocks.
|
|
145
|
-
*/
|
|
146
|
-
getConversation(sessionFilePath, options) {
|
|
130
|
+
*/ getConversation(sessionFilePath, options) {
|
|
147
131
|
const verbose = options?.verbose ?? false;
|
|
148
132
|
let content;
|
|
149
133
|
try {
|
|
150
134
|
content = fs.readFileSync(sessionFilePath, 'utf-8');
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
135
|
+
} catch {
|
|
153
136
|
return [];
|
|
154
137
|
}
|
|
155
138
|
const lines = content.trim().split('\n');
|
|
156
139
|
const messages = [];
|
|
157
|
-
for (const line of lines)
|
|
140
|
+
for (const line of lines){
|
|
158
141
|
let entry;
|
|
159
142
|
try {
|
|
160
143
|
entry = JSON.parse(line);
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
144
|
+
} catch {
|
|
163
145
|
continue;
|
|
164
146
|
}
|
|
165
147
|
let role;
|
|
166
148
|
if (entry.type === 'user') {
|
|
167
149
|
role = 'user';
|
|
168
|
-
}
|
|
169
|
-
else if (entry.type === 'assistant') {
|
|
150
|
+
} else if (entry.type === 'assistant') {
|
|
170
151
|
role = 'assistant';
|
|
171
|
-
}
|
|
172
|
-
else if (entry.type === 'system') {
|
|
152
|
+
} else if (entry.type === 'system') {
|
|
173
153
|
role = 'system';
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
154
|
+
} else {
|
|
176
155
|
continue;
|
|
177
156
|
}
|
|
178
157
|
const text = this.extractConversationContent(entry.message?.content, role, verbose);
|
|
179
|
-
if (!text)
|
|
180
|
-
continue;
|
|
158
|
+
if (!text) continue;
|
|
181
159
|
messages.push({
|
|
182
160
|
role,
|
|
183
161
|
content: text,
|
|
184
|
-
timestamp: entry.timestamp
|
|
162
|
+
timestamp: entry.timestamp
|
|
185
163
|
});
|
|
186
164
|
}
|
|
187
165
|
return messages;
|
|
@@ -192,8 +170,7 @@ export class ClaudeSessionParser {
|
|
|
192
170
|
* Claude Code may emit a "file-history-snapshot" as the first entry,
|
|
193
171
|
* which stores its timestamp inside "snapshot.timestamp" rather than
|
|
194
172
|
* at the root level.
|
|
195
|
-
*/
|
|
196
|
-
parseSessionStart(firstLine) {
|
|
173
|
+
*/ parseSessionStart(firstLine) {
|
|
197
174
|
try {
|
|
198
175
|
const firstEntry = JSON.parse(firstLine);
|
|
199
176
|
const rawTs = firstEntry.timestamp || firstEntry.snapshot?.timestamp;
|
|
@@ -203,10 +180,8 @@ export class ClaudeSessionParser {
|
|
|
203
180
|
return ts;
|
|
204
181
|
}
|
|
205
182
|
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/* malformed first line */
|
|
209
|
-
}
|
|
183
|
+
} catch {
|
|
184
|
+
/* malformed first line */ }
|
|
210
185
|
return null;
|
|
211
186
|
}
|
|
212
187
|
/**
|
|
@@ -218,17 +193,15 @@ export class ClaudeSessionParser {
|
|
|
218
193
|
* - Skill slash-commands (<command-message> tags)
|
|
219
194
|
* - Expanded skill content (extracts ARGUMENTS line)
|
|
220
195
|
* - Filters noise messages (interruptions, tool loaded, session continued)
|
|
221
|
-
*/
|
|
222
|
-
extractUserMessageText(content) {
|
|
196
|
+
*/ extractUserMessageText(content) {
|
|
223
197
|
if (!content) {
|
|
224
198
|
return undefined;
|
|
225
199
|
}
|
|
226
200
|
let raw;
|
|
227
201
|
if (typeof content === 'string') {
|
|
228
202
|
raw = content.trim();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (const block of content) {
|
|
203
|
+
} else if (Array.isArray(content)) {
|
|
204
|
+
for (const block of content){
|
|
232
205
|
if (block.type === 'text' && block.text?.trim()) {
|
|
233
206
|
raw = block.text.trim();
|
|
234
207
|
break;
|
|
@@ -252,8 +225,7 @@ export class ClaudeSessionParser {
|
|
|
252
225
|
}
|
|
253
226
|
/**
|
|
254
227
|
* Parse a <command-message> string into "/command args" format.
|
|
255
|
-
*/
|
|
256
|
-
parseCommandMessage(raw) {
|
|
228
|
+
*/ parseCommandMessage(raw) {
|
|
257
229
|
const nameMatch = raw.match(/<command-name>([^<]+)<\/command-name>/);
|
|
258
230
|
const argsMatch = raw.match(/<command-args>([^<]+)<\/command-args>/);
|
|
259
231
|
const name = nameMatch?.[1]?.trim();
|
|
@@ -265,33 +237,25 @@ export class ClaudeSessionParser {
|
|
|
265
237
|
}
|
|
266
238
|
/**
|
|
267
239
|
* Extract displayable content from a message content field for conversation output.
|
|
268
|
-
*/
|
|
269
|
-
|
|
270
|
-
if (!content)
|
|
271
|
-
return undefined;
|
|
240
|
+
*/ extractConversationContent(content, role, verbose) {
|
|
241
|
+
if (!content) return undefined;
|
|
272
242
|
if (typeof content === 'string') {
|
|
273
243
|
const cleaned = stripHarnessTags(content);
|
|
274
|
-
if (role === 'user' && isNoiseMessage(cleaned))
|
|
275
|
-
return undefined;
|
|
244
|
+
if (role === 'user' && isNoiseMessage(cleaned)) return undefined;
|
|
276
245
|
return cleaned || undefined;
|
|
277
246
|
}
|
|
278
|
-
if (!Array.isArray(content))
|
|
279
|
-
return undefined;
|
|
247
|
+
if (!Array.isArray(content)) return undefined;
|
|
280
248
|
const parts = [];
|
|
281
|
-
for (const block of content)
|
|
249
|
+
for (const block of content){
|
|
282
250
|
if (block.type === 'text' && block.text?.trim()) {
|
|
283
251
|
const cleaned = stripHarnessTags(block.text);
|
|
284
|
-
if (!cleaned)
|
|
285
|
-
|
|
286
|
-
if (role === 'user' && isNoiseMessage(cleaned))
|
|
287
|
-
continue;
|
|
252
|
+
if (!cleaned) continue;
|
|
253
|
+
if (role === 'user' && isNoiseMessage(cleaned)) continue;
|
|
288
254
|
parts.push(cleaned);
|
|
289
|
-
}
|
|
290
|
-
else if (block.type === 'tool_use' && verbose) {
|
|
255
|
+
} else if (block.type === 'tool_use' && verbose) {
|
|
291
256
|
const inputSummary = block.input?.file_path || block.input?.pattern || block.input?.command || '';
|
|
292
257
|
parts.push(`[Tool: ${block.name}]${inputSummary ? ' ' + inputSummary : ''}`);
|
|
293
|
-
}
|
|
294
|
-
else if (block.type === 'tool_result' && verbose) {
|
|
258
|
+
} else if (block.type === 'tool_result' && verbose) {
|
|
295
259
|
const truncated = truncateToolResult(block.content || '');
|
|
296
260
|
const prefix = block.is_error ? '[Tool Error]' : '[Tool Result]';
|
|
297
261
|
parts.push(`${prefix} ${truncated}`);
|
|
@@ -304,8 +268,7 @@ export class ClaudeSessionParser {
|
|
|
304
268
|
* Tags whose entire block (including content) should be dropped — they are
|
|
305
269
|
* harness-injected prompt context (system reminders, hook output, command
|
|
306
270
|
* stdout), not meaningful conversation content.
|
|
307
|
-
*/
|
|
308
|
-
const HARNESS_DROP_TAGS = [
|
|
271
|
+
*/ const HARNESS_DROP_TAGS = [
|
|
309
272
|
'system-reminder',
|
|
310
273
|
'local-command-stdout',
|
|
311
274
|
'local-command-stderr',
|
|
@@ -315,7 +278,7 @@ const HARNESS_DROP_TAGS = [
|
|
|
315
278
|
'bash-input',
|
|
316
279
|
'bash-stdout',
|
|
317
280
|
'bash-stderr',
|
|
318
|
-
'command-message'
|
|
281
|
+
'command-message'
|
|
319
282
|
];
|
|
320
283
|
const HARNESS_DROP_RE = new RegExp(`<(${HARNESS_DROP_TAGS.join('|')})>[\\s\\S]*?</\\1>`, 'g');
|
|
321
284
|
const COMMAND_INVOCATION_RE = /<command-name>([^<]+)<\/command-name>(?:\s*<command-args>([\s\S]*?)<\/command-args>)?/g;
|
|
@@ -325,26 +288,22 @@ const COMMAND_INVOCATION_RE = /<command-name>([^<]+)<\/command-name>(?:\s*<comma
|
|
|
325
288
|
*
|
|
326
289
|
* Returns the cleaned, trimmed text. Returns an empty string if nothing
|
|
327
290
|
* survives stripping.
|
|
328
|
-
*/
|
|
329
|
-
function stripHarnessTags(text) {
|
|
291
|
+
*/ function stripHarnessTags(text) {
|
|
330
292
|
let out = text.replace(HARNESS_DROP_RE, '');
|
|
331
|
-
out = out.replace(COMMAND_INVOCATION_RE, (_match, rawName, rawArgs)
|
|
293
|
+
out = out.replace(COMMAND_INVOCATION_RE, (_match, rawName, rawArgs)=>{
|
|
332
294
|
const name = rawName.trim();
|
|
333
295
|
const args = rawArgs?.trim();
|
|
334
296
|
return args ? `${name} ${args}` : name;
|
|
335
297
|
});
|
|
336
298
|
return out.replace(/\n{3,}/g, '\n\n').trim();
|
|
337
299
|
}
|
|
338
|
-
/** Check if a message is noise (not a meaningful user intent). */
|
|
339
|
-
|
|
340
|
-
return (text.startsWith('[Request interrupted') ||
|
|
341
|
-
text === 'Tool loaded.' ||
|
|
342
|
-
text.startsWith('This session is being continued'));
|
|
300
|
+
/** Check if a message is noise (not a meaningful user intent). */ function isNoiseMessage(text) {
|
|
301
|
+
return text.startsWith('[Request interrupted') || text === 'Tool loaded.' || text.startsWith('This session is being continued');
|
|
343
302
|
}
|
|
344
303
|
function truncateToolResult(content, maxLength = 200) {
|
|
345
304
|
const firstLine = content.split('\n')[0] || '';
|
|
346
|
-
if (firstLine.length <= maxLength)
|
|
347
|
-
return firstLine;
|
|
305
|
+
if (firstLine.length <= maxLength) return firstLine;
|
|
348
306
|
return firstLine.slice(0, maxLength - 3) + '...';
|
|
349
307
|
}
|
|
308
|
+
|
|
350
309
|
//# sourceMappingURL=ClaudeSessionParser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeSessionParser.js","sourceRoot":"","sources":["../../src/utils/ClaudeSessionParser.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/ClaudeSessionParser.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport type { ConversationMessage } from '../adapters/AgentAdapter.js';\nimport { AgentStatus } from '../adapters/AgentAdapter.js';\n\n/**\n * Content block within a Claude Code JSONL message entry.\n * Handles text, tool_use, and tool_result block types.\n */\nexport interface ContentBlock {\n type?: string;\n text?: string;\n content?: string;\n name?: string;\n input?: Record<string, unknown>;\n tool_use_id?: string;\n is_error?: boolean;\n}\n\n/**\n * A single line entry in a Claude Code session JSONL file.\n *\n * Each line is an independent JSON object with a type discriminator:\n * - \"user\" / \"assistant\" / \"system\" — conversation turns\n * - \"progress\" / \"thinking\" — intermediate agent state\n * - \"last-prompt\" / \"file-history-snapshot\" — metadata (not conversation state)\n */\nexport interface SessionEntry {\n type?: string;\n timestamp?: string;\n cwd?: string;\n message?: {\n content?: string | ContentBlock[];\n };\n}\n\n/**\n * Parsed session state extracted from a JSONL file.\n * Aggregates data from all entries into a single summary.\n */\nexport interface ClaudeSession {\n sessionId: string;\n projectPath: string;\n lastCwd?: string;\n sessionStart: Date;\n lastActive: Date;\n lastEntryType?: string;\n isInterrupted: boolean;\n lastUserMessage?: string;\n /** First meaningful user prompt in the session (post noise filter) */\n firstUserMessage?: string;\n}\n\n/**\n * Top-level JSONL entry types that represent conversation/agent state.\n *\n * Only these types update `lastEntryType` for status determination. All\n * other types Claude Code emits (`attachment`, `permission-mode`,\n * `ai-title`, `queued_command`, `tools_changed`, `model_changed`,\n * `hook_progress`, …) are UI-state events that must not overwrite the\n * last conversation turn — otherwise polling between writes lands on a\n * UI-state entry and `determineStatus` falls through to UNKNOWN.\n */\nconst CONVERSATION_ENTRY_TYPES = new Set([\n 'user',\n 'assistant',\n 'system',\n 'progress',\n 'thinking',\n]);\n\n/**\n * Parses Claude Code session JSONL files into structured data.\n *\n * Session files live at ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl\n * and contain one JSON object per line, each representing a conversation\n * event (user turn, assistant response, tool call, etc.).\n */\nexport class ClaudeSessionParser {\n /**\n * Parse a session JSONL file into a ClaudeSession summary.\n *\n * Iterates all lines to extract: session start time (from first entry),\n * last activity timestamp, last entry type (for status), whether the\n * session was interrupted, and the last meaningful user message.\n *\n * Returns null if the file is unreadable or empty.\n */\n readSession(filePath: string, projectPath: string): ClaudeSession | null {\n const sessionId = path.basename(filePath, '.jsonl');\n\n let content: string;\n try {\n content = fs.readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n const allLines = content.trim().split('\\n');\n if (allLines.length === 0) {\n return null;\n }\n\n const sessionStart = this.parseSessionStart(allLines[0]);\n\n let lastEntryType: string | undefined;\n let lastActive: Date | undefined;\n let lastCwd: string | undefined;\n let isInterrupted = false;\n let lastUserMessage: string | undefined;\n let firstUserMessage: string | undefined;\n\n for (const line of allLines) {\n try {\n const entry: SessionEntry = JSON.parse(line);\n\n if (entry.timestamp) {\n const ts = new Date(entry.timestamp);\n if (!Number.isNaN(ts.getTime())) {\n lastActive = ts;\n }\n }\n\n if (typeof entry.cwd === 'string' && entry.cwd.trim().length > 0) {\n lastCwd = entry.cwd;\n }\n\n if (entry.type && CONVERSATION_ENTRY_TYPES.has(entry.type)) {\n lastEntryType = entry.type;\n\n if (entry.type === 'user') {\n const msgContent = entry.message?.content;\n isInterrupted =\n Array.isArray(msgContent) &&\n msgContent.some(\n (c) =>\n (c.type === 'text' &&\n c.text?.includes('[Request interrupted')) ||\n (c.type === 'tool_result' &&\n c.content?.includes('[Request interrupted')),\n );\n\n const text = this.extractUserMessageText(msgContent);\n if (text) {\n lastUserMessage = text;\n if (!firstUserMessage) {\n firstUserMessage = text;\n }\n }\n } else {\n isInterrupted = false;\n }\n }\n } catch {\n continue;\n }\n }\n\n return {\n sessionId,\n projectPath: projectPath || lastCwd || '',\n lastCwd,\n sessionStart: sessionStart || lastActive || new Date(),\n lastActive: lastActive || new Date(),\n lastEntryType,\n isInterrupted,\n lastUserMessage,\n firstUserMessage,\n };\n }\n\n /**\n * Determine agent status from parsed session state.\n *\n * Status mapping:\n * - \"user\" + interrupted → WAITING (agent finished, awaiting new input)\n * - \"user\" + not interrupted → RUNNING (agent is processing)\n * - \"progress\" / \"thinking\" → RUNNING\n * - \"assistant\" → WAITING (agent responded, awaiting user)\n * - \"system\" → IDLE\n */\n determineStatus(session: ClaudeSession): AgentStatus {\n if (!session.lastEntryType) {\n return AgentStatus.UNKNOWN;\n }\n\n if (session.lastEntryType === 'user') {\n return session.isInterrupted\n ? AgentStatus.WAITING\n : AgentStatus.RUNNING;\n }\n\n if (\n session.lastEntryType === 'progress' ||\n session.lastEntryType === 'thinking'\n ) {\n return AgentStatus.RUNNING;\n }\n\n if (session.lastEntryType === 'assistant') {\n return AgentStatus.WAITING;\n }\n\n if (session.lastEntryType === 'system') {\n return AgentStatus.IDLE;\n }\n\n return AgentStatus.UNKNOWN;\n }\n\n /**\n * Read the full conversation from a session JSONL file.\n *\n * Default mode returns only text content from user/assistant/system messages.\n * Verbose mode also includes tool_use and tool_result blocks.\n */\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {\n const verbose = options?.verbose ?? false;\n\n let content: string;\n try {\n content = fs.readFileSync(sessionFilePath, 'utf-8');\n } catch {\n return [];\n }\n\n const lines = content.trim().split('\\n');\n const messages: ConversationMessage[] = [];\n\n for (const line of lines) {\n let entry: SessionEntry;\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n\n let role: ConversationMessage['role'];\n if (entry.type === 'user') {\n role = 'user';\n } else if (entry.type === 'assistant') {\n role = 'assistant';\n } else if (entry.type === 'system') {\n role = 'system';\n } else {\n continue;\n }\n\n const text = this.extractConversationContent(entry.message?.content, role, verbose);\n if (!text) continue;\n\n messages.push({\n role,\n content: text,\n timestamp: entry.timestamp,\n });\n }\n\n return messages;\n }\n\n /**\n * Parse session start time from the first JSONL line.\n *\n * Claude Code may emit a \"file-history-snapshot\" as the first entry,\n * which stores its timestamp inside \"snapshot.timestamp\" rather than\n * at the root level.\n */\n private parseSessionStart(firstLine: string): Date | null {\n try {\n const firstEntry = JSON.parse(firstLine);\n const rawTs: string | undefined =\n firstEntry.timestamp || firstEntry.snapshot?.timestamp;\n if (rawTs) {\n const ts = new Date(rawTs);\n if (!Number.isNaN(ts.getTime())) {\n return ts;\n }\n }\n } catch {\n /* malformed first line */\n }\n return null;\n }\n\n /**\n * Extract meaningful text from a user message content field.\n *\n * Handles multiple formats:\n * - Plain string content\n * - Array of content blocks (extracts first text block)\n * - Skill slash-commands (<command-message> tags)\n * - Expanded skill content (extracts ARGUMENTS line)\n * - Filters noise messages (interruptions, tool loaded, session continued)\n */\n private extractUserMessageText(\n content: string | Array<{ type?: string; text?: string }> | undefined,\n ): string | undefined {\n if (!content) {\n return undefined;\n }\n\n let raw: string | undefined;\n\n if (typeof content === 'string') {\n raw = content.trim();\n } else if (Array.isArray(content)) {\n for (const block of content) {\n if (block.type === 'text' && block.text?.trim()) {\n raw = block.text.trim();\n break;\n }\n }\n }\n\n if (!raw) {\n return undefined;\n }\n\n if (raw.startsWith('<command-message>')) {\n return this.parseCommandMessage(raw);\n }\n\n if (raw.startsWith('Base directory for this skill:')) {\n const argsMatch = raw.match(/\\nARGUMENTS:\\s*(.+)/);\n return argsMatch?.[1]?.trim() || undefined;\n }\n\n if (isNoiseMessage(raw)) {\n return undefined;\n }\n\n return raw;\n }\n\n /**\n * Parse a <command-message> string into \"/command args\" format.\n */\n private parseCommandMessage(raw: string): string | undefined {\n const nameMatch = raw.match(/<command-name>([^<]+)<\\/command-name>/);\n const argsMatch = raw.match(/<command-args>([^<]+)<\\/command-args>/);\n const name = nameMatch?.[1]?.trim();\n if (!name) {\n return undefined;\n }\n const args = argsMatch?.[1]?.trim();\n return args ? `${name} ${args}` : name;\n }\n\n /**\n * Extract displayable content from a message content field for conversation output.\n */\n private extractConversationContent(\n content: string | ContentBlock[] | undefined,\n role: ConversationMessage['role'],\n verbose: boolean,\n ): string | undefined {\n if (!content) return undefined;\n\n if (typeof content === 'string') {\n const cleaned = stripHarnessTags(content);\n if (role === 'user' && isNoiseMessage(cleaned)) return undefined;\n return cleaned || undefined;\n }\n\n if (!Array.isArray(content)) return undefined;\n\n const parts: string[] = [];\n\n for (const block of content) {\n if (block.type === 'text' && block.text?.trim()) {\n const cleaned = stripHarnessTags(block.text);\n if (!cleaned) continue;\n if (role === 'user' && isNoiseMessage(cleaned)) continue;\n parts.push(cleaned);\n } else if (block.type === 'tool_use' && verbose) {\n const inputSummary = block.input?.file_path || block.input?.pattern || block.input?.command || '';\n parts.push(`[Tool: ${block.name}]${inputSummary ? ' ' + inputSummary : ''}`);\n } else if (block.type === 'tool_result' && verbose) {\n const truncated = truncateToolResult(block.content || '');\n const prefix = block.is_error ? '[Tool Error]' : '[Tool Result]';\n parts.push(`${prefix} ${truncated}`);\n }\n }\n\n return parts.length > 0 ? parts.join('\\n') : undefined;\n }\n}\n\n/**\n * Tags whose entire block (including content) should be dropped — they are\n * harness-injected prompt context (system reminders, hook output, command\n * stdout), not meaningful conversation content.\n */\nconst HARNESS_DROP_TAGS = [\n 'system-reminder',\n 'local-command-stdout',\n 'local-command-stderr',\n 'user-prompt-submit-hook',\n 'command-stdout',\n 'command-stderr',\n 'bash-input',\n 'bash-stdout',\n 'bash-stderr',\n 'command-message',\n] as const;\n\nconst HARNESS_DROP_RE = new RegExp(\n `<(${HARNESS_DROP_TAGS.join('|')})>[\\\\s\\\\S]*?</\\\\1>`,\n 'g',\n);\n\nconst COMMAND_INVOCATION_RE =\n /<command-name>([^<]+)<\\/command-name>(?:\\s*<command-args>([\\s\\S]*?)<\\/command-args>)?/g;\n\n/**\n * Remove harness-injected XML blocks from message text and collapse\n * <command-name>/<command-args> pairs into a \"/name args\" shorthand.\n *\n * Returns the cleaned, trimmed text. Returns an empty string if nothing\n * survives stripping.\n */\nfunction stripHarnessTags(text: string): string {\n let out = text.replace(HARNESS_DROP_RE, '');\n\n out = out.replace(COMMAND_INVOCATION_RE, (_match, rawName: string, rawArgs?: string) => {\n const name = rawName.trim();\n const args = rawArgs?.trim();\n return args ? `${name} ${args}` : name;\n });\n\n return out.replace(/\\n{3,}/g, '\\n\\n').trim();\n}\n\n/** Check if a message is noise (not a meaningful user intent). */\nfunction isNoiseMessage(text: string): boolean {\n return (\n text.startsWith('[Request interrupted') ||\n text === 'Tool loaded.' ||\n text.startsWith('This session is being continued')\n );\n}\n\nfunction truncateToolResult(content: string, maxLength = 200): string {\n const firstLine = content.split('\\n')[0] || '';\n if (firstLine.length <= maxLength) return firstLine;\n return firstLine.slice(0, maxLength - 3) + '...';\n}\n"],"names":["fs","path","AgentStatus","CONVERSATION_ENTRY_TYPES","Set","ClaudeSessionParser","readSession","filePath","projectPath","sessionId","basename","content","readFileSync","allLines","trim","split","length","sessionStart","parseSessionStart","lastEntryType","lastActive","lastCwd","isInterrupted","lastUserMessage","firstUserMessage","line","entry","JSON","parse","timestamp","ts","Date","Number","isNaN","getTime","cwd","type","has","msgContent","message","Array","isArray","some","c","text","includes","extractUserMessageText","determineStatus","session","UNKNOWN","WAITING","RUNNING","IDLE","getConversation","sessionFilePath","options","verbose","lines","messages","role","extractConversationContent","push","firstLine","firstEntry","rawTs","snapshot","undefined","raw","block","startsWith","parseCommandMessage","argsMatch","match","isNoiseMessage","nameMatch","name","args","cleaned","stripHarnessTags","parts","inputSummary","input","file_path","pattern","command","truncated","truncateToolResult","prefix","is_error","join","HARNESS_DROP_TAGS","HARNESS_DROP_RE","RegExp","COMMAND_INVOCATION_RE","out","replace","_match","rawName","rawArgs","maxLength","slice"],"mappings":"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"}
|
package/dist/utils/agents.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentType } from '../adapters/AgentAdapter.js';
|
|
2
|
-
export type StartableAgentType =
|
|
2
|
+
export type StartableAgentType = Extract<AgentType, 'claude' | 'codex' | 'gemini_cli' | 'opencode'>;
|
|
3
3
|
export interface AgentConfig {
|
|
4
4
|
/** Shell command to launch the agent (sent to tmux via `send-keys`). */
|
|
5
5
|
command: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/utils/agents.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/utils/agents.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,SAAS,EAAE,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,CAAC,CAAC;AAEpG,MAAM,WAAW,WAAW;IACxB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAED;;;;GAIG;AACH,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,kBAAkB,EAAE,WAAW,CAK1D,CAAC"}
|