@hexidecibel/companion 0.0.1
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/__tests__/task-parser.test.d.ts +2 -0
- package/dist/__tests__/task-parser.test.d.ts.map +1 -0
- package/dist/__tests__/task-parser.test.js +79 -0
- package/dist/__tests__/task-parser.test.js.map +1 -0
- package/dist/anthropic-usage.d.ts +5 -0
- package/dist/anthropic-usage.d.ts.map +1 -0
- package/dist/anthropic-usage.js +112 -0
- package/dist/anthropic-usage.js.map +1 -0
- package/dist/cert-generator.d.ts +15 -0
- package/dist/cert-generator.d.ts.map +1 -0
- package/dist/cert-generator.js +298 -0
- package/dist/cert-generator.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +122 -0
- package/dist/config.js.map +1 -0
- package/dist/encryption.d.ts +28 -0
- package/dist/encryption.d.ts.map +1 -0
- package/dist/encryption.js +95 -0
- package/dist/encryption.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +211 -0
- package/dist/index.js.map +1 -0
- package/dist/input-injector.d.ts +21 -0
- package/dist/input-injector.d.ts.map +1 -0
- package/dist/input-injector.js +126 -0
- package/dist/input-injector.js.map +1 -0
- package/dist/mdns.d.ts +11 -0
- package/dist/mdns.d.ts.map +1 -0
- package/dist/mdns.js +93 -0
- package/dist/mdns.js.map +1 -0
- package/dist/parser.d.ts +43 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +800 -0
- package/dist/parser.js.map +1 -0
- package/dist/push.d.ts +38 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +359 -0
- package/dist/push.js.map +1 -0
- package/dist/qr-server.d.ts +13 -0
- package/dist/qr-server.d.ts.map +1 -0
- package/dist/qr-server.js +421 -0
- package/dist/qr-server.js.map +1 -0
- package/dist/scaffold/generator.d.ts +11 -0
- package/dist/scaffold/generator.d.ts.map +1 -0
- package/dist/scaffold/generator.js +206 -0
- package/dist/scaffold/generator.js.map +1 -0
- package/dist/scaffold/templates/index.d.ts +5 -0
- package/dist/scaffold/templates/index.d.ts.map +1 -0
- package/dist/scaffold/templates/index.js +22 -0
- package/dist/scaffold/templates/index.js.map +1 -0
- package/dist/scaffold/templates/node-express.d.ts +3 -0
- package/dist/scaffold/templates/node-express.d.ts.map +1 -0
- package/dist/scaffold/templates/node-express.js +218 -0
- package/dist/scaffold/templates/node-express.js.map +1 -0
- package/dist/scaffold/templates/python-fastapi.d.ts +3 -0
- package/dist/scaffold/templates/python-fastapi.d.ts.map +1 -0
- package/dist/scaffold/templates/python-fastapi.js +302 -0
- package/dist/scaffold/templates/python-fastapi.js.map +1 -0
- package/dist/scaffold/templates/react-mui-website.d.ts +3 -0
- package/dist/scaffold/templates/react-mui-website.d.ts.map +1 -0
- package/dist/scaffold/templates/react-mui-website.js +405 -0
- package/dist/scaffold/templates/react-mui-website.js.map +1 -0
- package/dist/scaffold/templates/react-typescript.d.ts +3 -0
- package/dist/scaffold/templates/react-typescript.d.ts.map +1 -0
- package/dist/scaffold/templates/react-typescript.js +275 -0
- package/dist/scaffold/templates/react-typescript.js.map +1 -0
- package/dist/scaffold/types.d.ts +55 -0
- package/dist/scaffold/types.d.ts.map +1 -0
- package/dist/scaffold/types.js +3 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/subagent-watcher.d.ts +24 -0
- package/dist/subagent-watcher.d.ts.map +1 -0
- package/dist/subagent-watcher.js +307 -0
- package/dist/subagent-watcher.js.map +1 -0
- package/dist/tls.d.ts +10 -0
- package/dist/tls.d.ts.map +1 -0
- package/dist/tls.js +77 -0
- package/dist/tls.js.map +1 -0
- package/dist/tmux-manager.d.ts +71 -0
- package/dist/tmux-manager.d.ts.map +1 -0
- package/dist/tmux-manager.js +243 -0
- package/dist/tmux-manager.js.map +1 -0
- package/dist/tool-config.d.ts +33 -0
- package/dist/tool-config.d.ts.map +1 -0
- package/dist/tool-config.js +211 -0
- package/dist/tool-config.js.map +1 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/watcher.d.ts +63 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +596 -0
- package/dist/watcher.js.map +1 -0
- package/dist/watcher.test.d.ts +2 -0
- package/dist/watcher.test.d.ts.map +1 -0
- package/dist/watcher.test.js +110 -0
- package/dist/watcher.test.js.map +1 -0
- package/dist/websocket.d.ts +62 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +1695 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +71 -0
- package/scripts/build.sh +23 -0
- package/scripts/install-remote.sh +18 -0
- package/scripts/install.sh +558 -0
- package/scripts/uninstall.sh +113 -0
package/dist/parser.js
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectCurrentActivityFast = detectCurrentActivityFast;
|
|
37
|
+
exports.parseConversationFile = parseConversationFile;
|
|
38
|
+
exports.extractHighlights = extractHighlights;
|
|
39
|
+
exports.detectWaitingForInput = detectWaitingForInput;
|
|
40
|
+
exports.detectIdle = detectIdle;
|
|
41
|
+
exports.detectCurrentActivity = detectCurrentActivity;
|
|
42
|
+
exports.getRecentActivity = getRecentActivity;
|
|
43
|
+
exports.getSessionStatus = getSessionStatus;
|
|
44
|
+
exports.getPendingApprovalTools = getPendingApprovalTools;
|
|
45
|
+
exports.detectCompaction = detectCompaction;
|
|
46
|
+
exports.extractUsageFromFile = extractUsageFromFile;
|
|
47
|
+
exports.extractTasks = extractTasks;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const tool_config_1 = require("./tool-config");
|
|
50
|
+
const MAX_MESSAGES = 100; // Limit to most recent messages
|
|
51
|
+
// KNOWN_TOOLS alias for backward compatibility in this file
|
|
52
|
+
const KNOWN_TOOLS = tool_config_1.KNOWN_TOOL_NAMES;
|
|
53
|
+
// Rate-limit parser warnings: max one per key per 60s
|
|
54
|
+
const _warnedRecently = new Map();
|
|
55
|
+
function logParserWarning(type, details) {
|
|
56
|
+
const key = `${type}:${details.substring(0, 100)}`;
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const last = _warnedRecently.get(key);
|
|
59
|
+
if (last && now - last < 60000)
|
|
60
|
+
return;
|
|
61
|
+
_warnedRecently.set(key, now);
|
|
62
|
+
console.log(`[PARSER_WARN] ${type}: ${details}`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Fast function to detect current activity by reading only the last few KB of a file.
|
|
66
|
+
* Much faster than parsing the entire conversation file.
|
|
67
|
+
* Tracks tool_result entries to avoid showing stale "pending" status for completed tools.
|
|
68
|
+
*/
|
|
69
|
+
function detectCurrentActivityFast(filePath) {
|
|
70
|
+
if (!fs.existsSync(filePath)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const stats = fs.statSync(filePath);
|
|
75
|
+
const fileSize = stats.size;
|
|
76
|
+
// Read last 32KB - enough to get recent messages
|
|
77
|
+
const readSize = Math.min(32 * 1024, fileSize);
|
|
78
|
+
const buffer = Buffer.alloc(readSize);
|
|
79
|
+
const fd = fs.openSync(filePath, 'r');
|
|
80
|
+
fs.readSync(fd, buffer, 0, readSize, Math.max(0, fileSize - readSize));
|
|
81
|
+
fs.closeSync(fd);
|
|
82
|
+
const tail = buffer.toString('utf-8');
|
|
83
|
+
const lines = tail.split('\n').filter(line => line.trim());
|
|
84
|
+
// Collect tool_result IDs from recent lines so we know which tools completed
|
|
85
|
+
const completedToolIds = new Set();
|
|
86
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
87
|
+
try {
|
|
88
|
+
const entry = JSON.parse(lines[i]);
|
|
89
|
+
if (entry.message?.content && Array.isArray(entry.message.content)) {
|
|
90
|
+
for (const block of entry.message.content) {
|
|
91
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
92
|
+
completedToolIds.add(block.tool_use_id);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Walk backward to find the most recent meaningful entry
|
|
102
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
103
|
+
try {
|
|
104
|
+
const entry = JSON.parse(lines[i]);
|
|
105
|
+
if (entry.message?.role === 'user') {
|
|
106
|
+
return 'Processing...';
|
|
107
|
+
}
|
|
108
|
+
if (entry.message?.role === 'assistant' && entry.message.content) {
|
|
109
|
+
const entryContent = entry.message.content;
|
|
110
|
+
if (Array.isArray(entryContent)) {
|
|
111
|
+
// Find the last tool_use that hasn't been completed
|
|
112
|
+
for (let j = entryContent.length - 1; j >= 0; j--) {
|
|
113
|
+
const block = entryContent[j];
|
|
114
|
+
if (block.type === 'tool_use' && block.name && block.id) {
|
|
115
|
+
// Skip tools that already have results
|
|
116
|
+
if (completedToolIds.has(block.id)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Warn about unknown tools
|
|
120
|
+
if (!(0, tool_config_1.isKnownTool)(block.name)) {
|
|
121
|
+
logParserWarning('unknown_tool', `Unrecognized tool: ${block.name}`);
|
|
122
|
+
}
|
|
123
|
+
// Check if this needs approval
|
|
124
|
+
if (tool_config_1.APPROVAL_TOOLS.includes(block.name)) {
|
|
125
|
+
const input = block.input;
|
|
126
|
+
if (block.name === 'Bash' && input?.command) {
|
|
127
|
+
const cmd = input.command.substring(0, 40);
|
|
128
|
+
return `Approve? ${cmd}${input.command.length > 40 ? '...' : ''}`;
|
|
129
|
+
}
|
|
130
|
+
if ((block.name === 'Edit' || block.name === 'Write') && input?.file_path) {
|
|
131
|
+
const fileName = input.file_path.split('/').pop() || input.file_path;
|
|
132
|
+
return `Approve ${block.name.toLowerCase()}: ${fileName}?`;
|
|
133
|
+
}
|
|
134
|
+
return `Approve ${block.name}?`;
|
|
135
|
+
}
|
|
136
|
+
return (0, tool_config_1.getToolDescription)(block.name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return undefined; // Assistant message, all tools completed
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function parseConversationFile(filePath, limit = MAX_MESSAGES, preReadContent) {
|
|
154
|
+
const content = preReadContent ?? (fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '');
|
|
155
|
+
if (!content) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
159
|
+
// First pass: collect all tool results, start times, and completion times
|
|
160
|
+
const toolResults = new Map();
|
|
161
|
+
const toolStartTimes = new Map();
|
|
162
|
+
const toolCompleteTimes = new Map();
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
try {
|
|
165
|
+
const entry = JSON.parse(line);
|
|
166
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
|
|
167
|
+
if (entry.message?.content && Array.isArray(entry.message.content)) {
|
|
168
|
+
for (const block of entry.message.content) {
|
|
169
|
+
// Track tool_use start times
|
|
170
|
+
if (block.type === 'tool_use' && block.id) {
|
|
171
|
+
toolStartTimes.set(block.id, timestamp);
|
|
172
|
+
}
|
|
173
|
+
// Track tool_result completion times and outputs
|
|
174
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
175
|
+
toolCompleteTimes.set(block.tool_use_id, timestamp);
|
|
176
|
+
// Extract output content - can be string or array of content blocks
|
|
177
|
+
let output = '';
|
|
178
|
+
if (typeof block.content === 'string') {
|
|
179
|
+
output = block.content;
|
|
180
|
+
}
|
|
181
|
+
else if (Array.isArray(block.content)) {
|
|
182
|
+
output = block.content
|
|
183
|
+
.filter(c => c.type === 'text' && c.text)
|
|
184
|
+
.map(c => c.text || '')
|
|
185
|
+
.join('\n');
|
|
186
|
+
}
|
|
187
|
+
toolResults.set(block.tool_use_id, output);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Skip malformed lines
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const completedToolIds = new Set(toolResults.keys());
|
|
197
|
+
const messages = [];
|
|
198
|
+
// Process from the end to get most recent messages first
|
|
199
|
+
for (let i = lines.length - 1; i >= 0 && messages.length < limit * 2; i--) {
|
|
200
|
+
try {
|
|
201
|
+
const entry = JSON.parse(lines[i]);
|
|
202
|
+
if (entry.type === 'user' || entry.type === 'assistant') {
|
|
203
|
+
const message = parseEntry(entry, toolResults, toolStartTimes, toolCompleteTimes);
|
|
204
|
+
if (message) {
|
|
205
|
+
messages.unshift(message); // Add to beginning to maintain order
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (entry.type && entry.type !== 'summary') {
|
|
209
|
+
logParserWarning('unknown_entry_type', `Unexpected JSONL entry type: ${entry.type}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Skip malformed lines
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Return only the limit number of messages
|
|
217
|
+
return messages.slice(-limit);
|
|
218
|
+
}
|
|
219
|
+
function parseEntry(entry, toolResults, toolStartTimes, toolCompleteTimes) {
|
|
220
|
+
const message = entry.message;
|
|
221
|
+
if (!message)
|
|
222
|
+
return null;
|
|
223
|
+
let content = '';
|
|
224
|
+
const toolCalls = [];
|
|
225
|
+
let options;
|
|
226
|
+
let questions;
|
|
227
|
+
let isWaitingForChoice = false;
|
|
228
|
+
let multiSelect = false;
|
|
229
|
+
if (typeof message.content === 'string') {
|
|
230
|
+
content = message.content;
|
|
231
|
+
}
|
|
232
|
+
else if (Array.isArray(message.content)) {
|
|
233
|
+
for (const block of message.content) {
|
|
234
|
+
if (block.type === 'text' && block.text) {
|
|
235
|
+
content += block.text;
|
|
236
|
+
}
|
|
237
|
+
else if (block.type === 'tool_use') {
|
|
238
|
+
if (!block.name) {
|
|
239
|
+
logParserWarning('missing_tool_name', `tool_use block without name, id: ${block.id}`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (!KNOWN_TOOLS.has(block.name)) {
|
|
243
|
+
logParserWarning('unknown_tool', `Unrecognized tool in parseEntry: ${block.name}`);
|
|
244
|
+
}
|
|
245
|
+
const toolId = block.id || entry.uuid || '';
|
|
246
|
+
const output = toolResults.get(toolId);
|
|
247
|
+
const isPending = !output && output !== '';
|
|
248
|
+
const startedAt = toolStartTimes.get(toolId);
|
|
249
|
+
const completedAt = toolCompleteTimes.get(toolId);
|
|
250
|
+
toolCalls.push({
|
|
251
|
+
id: toolId,
|
|
252
|
+
name: block.name,
|
|
253
|
+
input: block.input || {},
|
|
254
|
+
output: output,
|
|
255
|
+
status: isPending ? 'pending' : 'completed',
|
|
256
|
+
startedAt,
|
|
257
|
+
completedAt,
|
|
258
|
+
});
|
|
259
|
+
// Extract options from AskUserQuestion tool (only if still pending)
|
|
260
|
+
if (block.name === 'AskUserQuestion' && isPending) {
|
|
261
|
+
const input = block.input;
|
|
262
|
+
console.log(`Parser: Found AskUserQuestion tool, questions count: ${input.questions?.length || 0}`);
|
|
263
|
+
if (input.questions && input.questions.length > 0) {
|
|
264
|
+
// Extract all questions
|
|
265
|
+
questions = input.questions.map(q => ({
|
|
266
|
+
question: q.question,
|
|
267
|
+
header: q.header,
|
|
268
|
+
options: q.options.map(opt => ({
|
|
269
|
+
label: opt.label,
|
|
270
|
+
description: opt.description,
|
|
271
|
+
})),
|
|
272
|
+
multiSelect: q.multiSelect || false,
|
|
273
|
+
}));
|
|
274
|
+
// Set content to first question for backward compat / message bubble text
|
|
275
|
+
const firstQuestion = input.questions[0];
|
|
276
|
+
content = firstQuestion.question;
|
|
277
|
+
// Set options from first question for backward compat (single-question case)
|
|
278
|
+
options = firstQuestion.options.map(opt => ({
|
|
279
|
+
label: opt.label,
|
|
280
|
+
description: opt.description,
|
|
281
|
+
}));
|
|
282
|
+
isWaitingForChoice = true;
|
|
283
|
+
multiSelect = firstQuestion.multiSelect || false;
|
|
284
|
+
console.log(`Parser: Extracted ${questions.length} questions, first has ${options.length} options: "${content.substring(0, 50)}..." (multiSelect: ${multiSelect})`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (block.name === 'AskUserQuestion' && !isPending) {
|
|
288
|
+
// Show the question content but no options (already answered)
|
|
289
|
+
const input = block.input;
|
|
290
|
+
if (input.questions && input.questions.length > 0) {
|
|
291
|
+
content = input.questions[0].question;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Add Yes/No options for pending approval tools
|
|
295
|
+
// But NOT for Task tools - they run in background and stay "pending" for a long time
|
|
296
|
+
else if (isPending && tool_config_1.APPROVAL_TOOLS.includes(block.name) && block.name !== 'Task') {
|
|
297
|
+
const input = block.input;
|
|
298
|
+
let description = '';
|
|
299
|
+
// Build a helpful description based on tool type
|
|
300
|
+
if (block.name === 'Bash' && input.command) {
|
|
301
|
+
description = `Run: ${input.command.substring(0, 100)}`;
|
|
302
|
+
}
|
|
303
|
+
else if ((block.name === 'Edit' || block.name === 'Write') && input.file_path) {
|
|
304
|
+
description = `${block.name}: ${input.file_path}`;
|
|
305
|
+
}
|
|
306
|
+
else if (block.name === 'Task' && input.description) {
|
|
307
|
+
description = `Task: ${input.description}`;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
description = `Allow ${block.name}?`;
|
|
311
|
+
}
|
|
312
|
+
options = [
|
|
313
|
+
{ label: 'yes', description: `Approve: ${description}` },
|
|
314
|
+
{ label: 'no', description: 'Reject this action' },
|
|
315
|
+
];
|
|
316
|
+
isWaitingForChoice = true;
|
|
317
|
+
console.log(`Parser: Pending ${block.name} tool needs approval: "${description.substring(0, 50)}..."`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else if (block.type === 'tool_result') {
|
|
321
|
+
// Skip tool results entirely - they're internal assistant responses
|
|
322
|
+
// We only want to show actual user-typed messages
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
|
|
327
|
+
return {
|
|
328
|
+
id: entry.uuid || String(timestamp),
|
|
329
|
+
type: entry.type,
|
|
330
|
+
content,
|
|
331
|
+
timestamp,
|
|
332
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
333
|
+
options,
|
|
334
|
+
questions,
|
|
335
|
+
isWaitingForChoice,
|
|
336
|
+
multiSelect: multiSelect || undefined,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function extractHighlights(messages) {
|
|
340
|
+
// Find the index of the last user message - anything before this has been "responded to"
|
|
341
|
+
let lastUserMessageIndex = -1;
|
|
342
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
343
|
+
if (messages[i].type === 'user') {
|
|
344
|
+
lastUserMessageIndex = i;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const rawHighlights = messages
|
|
349
|
+
.filter(msg => {
|
|
350
|
+
// Include user messages with content
|
|
351
|
+
if (msg.type === 'user' && msg.content && msg.content.trim())
|
|
352
|
+
return true;
|
|
353
|
+
// Include assistant messages with content OR toolCalls
|
|
354
|
+
if (msg.type === 'assistant') {
|
|
355
|
+
const hasContent = msg.content && msg.content.trim();
|
|
356
|
+
const hasToolCalls = msg.toolCalls && msg.toolCalls.length > 0;
|
|
357
|
+
return hasContent || hasToolCalls;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
})
|
|
361
|
+
.map((msg, index, arr) => {
|
|
362
|
+
const isLastMessage = index === arr.length - 1;
|
|
363
|
+
const originalIndex = messages.indexOf(msg);
|
|
364
|
+
// Check if this message has pending approval tools
|
|
365
|
+
const hasPendingApprovalTools = msg.toolCalls?.some(tc => tc.status === 'pending' && tool_config_1.APPROVAL_TOOLS.includes(tc.name) && tc.name !== 'Task') ?? false;
|
|
366
|
+
// Check if all tools in this message are already completed/errored
|
|
367
|
+
const allToolsCompleted = (msg.toolCalls?.length ?? 0) > 0 && msg.toolCalls?.every(tc => tc.status === 'completed' || tc.status === 'error' || tc.output !== undefined);
|
|
368
|
+
// Check if user already responded after this message (tool is running, not waiting)
|
|
369
|
+
const userRespondedAfter = originalIndex < messages.length - 1 &&
|
|
370
|
+
messages.slice(originalIndex + 1).some(m => m.type === 'user');
|
|
371
|
+
// Show options if:
|
|
372
|
+
// 1. This message has options AND
|
|
373
|
+
// 2. Either it's the last message OR it has pending approval tools AND
|
|
374
|
+
// 3. Tools haven't all completed AND
|
|
375
|
+
// 4. User hasn't already responded (tool would be running, not waiting)
|
|
376
|
+
const showOptions = msg.options && msg.options.length > 0 &&
|
|
377
|
+
(isLastMessage || hasPendingApprovalTools) && !allToolsCompleted && !userRespondedAfter;
|
|
378
|
+
// If user responded after this message, pending tools are now running (not waiting for approval)
|
|
379
|
+
const toolCalls = userRespondedAfter && msg.toolCalls
|
|
380
|
+
? msg.toolCalls.map(tc => tc.status === 'pending' ? { ...tc, status: 'running' } : tc)
|
|
381
|
+
: msg.toolCalls;
|
|
382
|
+
return {
|
|
383
|
+
id: msg.id,
|
|
384
|
+
type: msg.type,
|
|
385
|
+
content: msg.content,
|
|
386
|
+
timestamp: msg.timestamp,
|
|
387
|
+
options: showOptions ? msg.options : undefined,
|
|
388
|
+
questions: showOptions ? msg.questions : undefined,
|
|
389
|
+
isWaitingForChoice: showOptions ? msg.isWaitingForChoice : false,
|
|
390
|
+
multiSelect: showOptions ? msg.multiSelect : undefined,
|
|
391
|
+
toolCalls,
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
// Merge consecutive assistant messages that are tool-only (no text content)
|
|
395
|
+
// into a single message so the UI can collapse them together
|
|
396
|
+
const highlights = [];
|
|
397
|
+
for (const h of rawHighlights) {
|
|
398
|
+
const prev = highlights[highlights.length - 1];
|
|
399
|
+
const isToolOnly = h.type === 'assistant' && (!h.content || !h.content.trim()) && h.toolCalls && h.toolCalls.length > 0;
|
|
400
|
+
const prevIsToolOnly = prev && prev.type === 'assistant' && (!prev.content || !prev.content.trim()) && prev.toolCalls && prev.toolCalls.length > 0;
|
|
401
|
+
if (isToolOnly && prevIsToolOnly && !h.options && !prev.options) {
|
|
402
|
+
// Merge: append tool calls to previous message
|
|
403
|
+
prev.toolCalls = [...(prev.toolCalls || []), ...(h.toolCalls || [])];
|
|
404
|
+
prev.timestamp = h.timestamp; // Use latest timestamp
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
highlights.push(h);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Log if the last highlight has options
|
|
411
|
+
const lastHighlight = highlights[highlights.length - 1];
|
|
412
|
+
if (lastHighlight?.options && lastHighlight.options.length > 0) {
|
|
413
|
+
console.log(`Parser: Last message has ${lastHighlight.options.length} options`);
|
|
414
|
+
}
|
|
415
|
+
return highlights;
|
|
416
|
+
}
|
|
417
|
+
function detectWaitingForInput(messages) {
|
|
418
|
+
if (messages.length === 0)
|
|
419
|
+
return false;
|
|
420
|
+
const lastMessage = messages[messages.length - 1];
|
|
421
|
+
// If the last message is from the assistant
|
|
422
|
+
if (lastMessage.type === 'assistant') {
|
|
423
|
+
// Check for pending tool calls that need approval
|
|
424
|
+
if (lastMessage.toolCalls) {
|
|
425
|
+
const hasPendingApproval = lastMessage.toolCalls.some(tc => tc.status === 'pending' && tool_config_1.APPROVAL_TOOLS.includes(tc.name));
|
|
426
|
+
if (hasPendingApproval) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Check for text content that looks like a question or prompt
|
|
431
|
+
if (lastMessage.content.trim()) {
|
|
432
|
+
const questionPatterns = [
|
|
433
|
+
/\?$/,
|
|
434
|
+
/would you like/i,
|
|
435
|
+
/do you want/i,
|
|
436
|
+
/should I/i,
|
|
437
|
+
/let me know/i,
|
|
438
|
+
/please confirm/i,
|
|
439
|
+
/please provide/i,
|
|
440
|
+
];
|
|
441
|
+
for (const pattern of questionPatterns) {
|
|
442
|
+
if (pattern.test(lastMessage.content)) {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
// Detect if the assistant has finished working and is idle (not actively expecting a response)
|
|
451
|
+
function detectIdle(messages) {
|
|
452
|
+
if (messages.length === 0)
|
|
453
|
+
return false;
|
|
454
|
+
const lastMessage = messages[messages.length - 1];
|
|
455
|
+
// If the last message is from the assistant with all tools completed and no question
|
|
456
|
+
if (lastMessage.type === 'assistant') {
|
|
457
|
+
// Still has running/pending tools = not idle
|
|
458
|
+
if (lastMessage.toolCalls?.some(tc => tc.status === 'pending' || tc.status === 'running')) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
// All tools completed, no question pattern = idle (finished task)
|
|
462
|
+
if (!lastMessage.toolCalls || lastMessage.toolCalls.every(tc => tc.status === 'completed')) {
|
|
463
|
+
return !detectWaitingForInput(messages);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
function detectCurrentActivity(messages) {
|
|
469
|
+
if (messages.length === 0)
|
|
470
|
+
return undefined;
|
|
471
|
+
const lastMessage = messages[messages.length - 1];
|
|
472
|
+
// If last message is from user, the assistant is processing
|
|
473
|
+
if (lastMessage.type === 'user') {
|
|
474
|
+
return 'Processing...';
|
|
475
|
+
}
|
|
476
|
+
// Check for tool calls in the last assistant message
|
|
477
|
+
if (lastMessage.type === 'assistant' && lastMessage.toolCalls && lastMessage.toolCalls.length > 0) {
|
|
478
|
+
const lastTool = lastMessage.toolCalls[lastMessage.toolCalls.length - 1];
|
|
479
|
+
// Check if this is a pending approval
|
|
480
|
+
if (lastTool.status === 'pending' && tool_config_1.APPROVAL_TOOLS.includes(lastTool.name)) {
|
|
481
|
+
const input = lastTool.input;
|
|
482
|
+
if (lastTool.name === 'Bash' && input.command) {
|
|
483
|
+
const cmd = input.command.substring(0, 40);
|
|
484
|
+
return `Approve? ${cmd}${input.command.length > 40 ? '...' : ''}`;
|
|
485
|
+
}
|
|
486
|
+
if ((lastTool.name === 'Edit' || lastTool.name === 'Write') && input.file_path) {
|
|
487
|
+
const filePath = input.file_path;
|
|
488
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
489
|
+
return `Approve ${lastTool.name.toLowerCase()}: ${fileName}?`;
|
|
490
|
+
}
|
|
491
|
+
return `Approve ${lastTool.name}?`;
|
|
492
|
+
}
|
|
493
|
+
const description = (0, tool_config_1.getToolDescription)(lastTool.name);
|
|
494
|
+
// Add file path info if available
|
|
495
|
+
if (lastTool.input) {
|
|
496
|
+
const input = lastTool.input;
|
|
497
|
+
if (input.file_path) {
|
|
498
|
+
const filePath = input.file_path;
|
|
499
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
500
|
+
return `${description}: ${fileName}`;
|
|
501
|
+
}
|
|
502
|
+
if (input.command) {
|
|
503
|
+
const cmd = input.command.substring(0, 30);
|
|
504
|
+
return `${description}: ${cmd}${input.command.length > 30 ? '...' : ''}`;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return description;
|
|
508
|
+
}
|
|
509
|
+
// Don't show "waiting for input" - there's already a separate indicator for that
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
function getRecentActivity(messages, limit = 5) {
|
|
513
|
+
const activities = [];
|
|
514
|
+
// Go through messages in reverse to get recent activity
|
|
515
|
+
for (let i = messages.length - 1; i >= 0 && activities.length < limit; i--) {
|
|
516
|
+
const msg = messages[i];
|
|
517
|
+
if (msg.type === 'assistant' && msg.toolCalls) {
|
|
518
|
+
for (const tool of msg.toolCalls) {
|
|
519
|
+
if (activities.length >= limit)
|
|
520
|
+
break;
|
|
521
|
+
const input = tool.input;
|
|
522
|
+
let inputStr = '';
|
|
523
|
+
let outputStr = tool.output || '';
|
|
524
|
+
// Format input based on tool type
|
|
525
|
+
if (input.file_path) {
|
|
526
|
+
inputStr = input.file_path;
|
|
527
|
+
}
|
|
528
|
+
else if (input.command) {
|
|
529
|
+
inputStr = input.command;
|
|
530
|
+
}
|
|
531
|
+
else if (input.pattern) {
|
|
532
|
+
inputStr = `Pattern: ${input.pattern}`;
|
|
533
|
+
}
|
|
534
|
+
else if (input.query) {
|
|
535
|
+
inputStr = input.query;
|
|
536
|
+
}
|
|
537
|
+
activities.push({
|
|
538
|
+
summary: `${tool.name}${inputStr ? `: ${inputStr.substring(0, 100)}` : ''}`,
|
|
539
|
+
toolName: tool.name,
|
|
540
|
+
input: inputStr,
|
|
541
|
+
output: outputStr.substring(0, 2000), // Limit output size
|
|
542
|
+
timestamp: msg.timestamp,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return activities.reverse(); // Return in chronological order
|
|
548
|
+
}
|
|
549
|
+
function getSessionStatus(conversationPath, isProcessRunning) {
|
|
550
|
+
const messages = parseConversationFile(conversationPath);
|
|
551
|
+
const lastMessage = messages[messages.length - 1];
|
|
552
|
+
return {
|
|
553
|
+
isRunning: isProcessRunning,
|
|
554
|
+
isWaitingForInput: isProcessRunning && detectWaitingForInput(messages),
|
|
555
|
+
lastActivity: lastMessage?.timestamp || 0,
|
|
556
|
+
conversationId: conversationPath,
|
|
557
|
+
currentActivity: isProcessRunning ? detectCurrentActivity(messages) : undefined,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Get list of pending tools that need approval from the last message
|
|
562
|
+
*/
|
|
563
|
+
function getPendingApprovalTools(messages) {
|
|
564
|
+
if (messages.length === 0)
|
|
565
|
+
return [];
|
|
566
|
+
const lastMessage = messages[messages.length - 1];
|
|
567
|
+
if (lastMessage.type !== 'assistant' || !lastMessage.toolCalls)
|
|
568
|
+
return [];
|
|
569
|
+
return lastMessage.toolCalls
|
|
570
|
+
.filter(tc => tc.status === 'pending')
|
|
571
|
+
.map(tc => tc.name);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Detect compaction events in a conversation file
|
|
575
|
+
* Returns the most recent compaction summary if found
|
|
576
|
+
*/
|
|
577
|
+
function detectCompaction(filePath, sessionId, sessionName, projectPath, lastCheckedLine = 0, preReadContent) {
|
|
578
|
+
const content = preReadContent ?? (fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '');
|
|
579
|
+
if (!content) {
|
|
580
|
+
return { event: null, lastLine: 0 };
|
|
581
|
+
}
|
|
582
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
583
|
+
let compactionEvent = null;
|
|
584
|
+
// Only check lines after lastCheckedLine to avoid re-detecting old compactions
|
|
585
|
+
for (let i = lastCheckedLine; i < lines.length; i++) {
|
|
586
|
+
try {
|
|
587
|
+
const entry = JSON.parse(lines[i]);
|
|
588
|
+
// Look for summary type entries (context compaction)
|
|
589
|
+
if (entry.type === 'summary' && entry.summary) {
|
|
590
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
|
|
591
|
+
compactionEvent = {
|
|
592
|
+
sessionId,
|
|
593
|
+
sessionName,
|
|
594
|
+
projectPath,
|
|
595
|
+
summary: entry.summary,
|
|
596
|
+
timestamp,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
// Skip malformed lines
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return { event: compactionEvent, lastLine: lines.length };
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Extract usage data from a conversation JSONL file
|
|
608
|
+
*/
|
|
609
|
+
function extractUsageFromFile(filePath, sessionName) {
|
|
610
|
+
const result = {
|
|
611
|
+
sessionId: filePath,
|
|
612
|
+
sessionName,
|
|
613
|
+
totalInputTokens: 0,
|
|
614
|
+
totalOutputTokens: 0,
|
|
615
|
+
totalCacheCreationTokens: 0,
|
|
616
|
+
totalCacheReadTokens: 0,
|
|
617
|
+
messageCount: 0,
|
|
618
|
+
currentContextTokens: 0,
|
|
619
|
+
};
|
|
620
|
+
if (!fs.existsSync(filePath)) {
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
624
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
625
|
+
const seenMessageIds = new Set();
|
|
626
|
+
for (const line of lines) {
|
|
627
|
+
try {
|
|
628
|
+
const entry = JSON.parse(line);
|
|
629
|
+
// Only count assistant messages with usage data
|
|
630
|
+
if (entry.type === 'assistant' && entry.message?.usage) {
|
|
631
|
+
const msgId = entry.message?.id;
|
|
632
|
+
// Skip duplicate message IDs (same message can appear multiple times as it streams)
|
|
633
|
+
if (msgId && seenMessageIds.has(msgId)) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (msgId) {
|
|
637
|
+
seenMessageIds.add(msgId);
|
|
638
|
+
}
|
|
639
|
+
const usage = entry.message.usage;
|
|
640
|
+
// Only add non-zero usage (final message has the totals)
|
|
641
|
+
if (usage.input_tokens && usage.input_tokens > 0) {
|
|
642
|
+
result.totalInputTokens += usage.input_tokens;
|
|
643
|
+
result.messageCount++;
|
|
644
|
+
}
|
|
645
|
+
if (usage.output_tokens && usage.output_tokens > 0) {
|
|
646
|
+
result.totalOutputTokens += usage.output_tokens;
|
|
647
|
+
}
|
|
648
|
+
if (usage.cache_creation_input_tokens && usage.cache_creation_input_tokens > 0) {
|
|
649
|
+
result.totalCacheCreationTokens += usage.cache_creation_input_tokens;
|
|
650
|
+
}
|
|
651
|
+
if (usage.cache_read_input_tokens && usage.cache_read_input_tokens > 0) {
|
|
652
|
+
result.totalCacheReadTokens += usage.cache_read_input_tokens;
|
|
653
|
+
}
|
|
654
|
+
// Track current context size from the most recent message
|
|
655
|
+
if (usage.input_tokens) {
|
|
656
|
+
result.currentContextTokens = usage.input_tokens;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
// Skip malformed lines
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Extract tasks from JSONL content (from TaskCreate/TaskUpdate tool calls)
|
|
668
|
+
*/
|
|
669
|
+
function extractTasks(content) {
|
|
670
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
671
|
+
// Track tasks by temporary ID (toolu_xxx) until we get real ID from result
|
|
672
|
+
const pendingTasks = new Map();
|
|
673
|
+
// Map toolu_xxx to real task ID
|
|
674
|
+
const toolIdToTaskId = new Map();
|
|
675
|
+
// Final tasks by real ID
|
|
676
|
+
const tasks = new Map();
|
|
677
|
+
for (const line of lines) {
|
|
678
|
+
try {
|
|
679
|
+
const entry = JSON.parse(line);
|
|
680
|
+
if (entry.message?.content && Array.isArray(entry.message.content)) {
|
|
681
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
|
|
682
|
+
for (const block of entry.message.content) {
|
|
683
|
+
// Handle TaskCreate
|
|
684
|
+
if (block.type === 'tool_use' && block.name === 'TaskCreate') {
|
|
685
|
+
const input = block.input;
|
|
686
|
+
const toolId = block.id;
|
|
687
|
+
pendingTasks.set(toolId, {
|
|
688
|
+
task: {
|
|
689
|
+
subject: input.subject,
|
|
690
|
+
description: input.description,
|
|
691
|
+
activeForm: input.activeForm,
|
|
692
|
+
status: 'pending',
|
|
693
|
+
blockedBy: [],
|
|
694
|
+
blocks: [],
|
|
695
|
+
createdAt: timestamp,
|
|
696
|
+
updatedAt: timestamp,
|
|
697
|
+
},
|
|
698
|
+
timestamp,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
// Handle TaskUpdate
|
|
702
|
+
if (block.type === 'tool_use' && block.name === 'TaskUpdate') {
|
|
703
|
+
const input = block.input;
|
|
704
|
+
const taskId = input.taskId;
|
|
705
|
+
// Find existing task
|
|
706
|
+
const existingTask = tasks.get(taskId);
|
|
707
|
+
if (existingTask) {
|
|
708
|
+
// Handle deletion
|
|
709
|
+
if (input.status === 'deleted') {
|
|
710
|
+
tasks.delete(taskId);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
// Apply updates
|
|
714
|
+
if (input.status) {
|
|
715
|
+
existingTask.status = input.status;
|
|
716
|
+
}
|
|
717
|
+
if (input.subject) {
|
|
718
|
+
existingTask.subject = input.subject;
|
|
719
|
+
}
|
|
720
|
+
if (input.description) {
|
|
721
|
+
existingTask.description = input.description;
|
|
722
|
+
}
|
|
723
|
+
if (input.activeForm) {
|
|
724
|
+
existingTask.activeForm = input.activeForm;
|
|
725
|
+
}
|
|
726
|
+
else if (input.status === 'completed') {
|
|
727
|
+
// Clear activeForm when completed
|
|
728
|
+
existingTask.activeForm = undefined;
|
|
729
|
+
}
|
|
730
|
+
if (input.owner) {
|
|
731
|
+
existingTask.owner = input.owner;
|
|
732
|
+
}
|
|
733
|
+
if (input.addBlockedBy) {
|
|
734
|
+
existingTask.blockedBy = [
|
|
735
|
+
...(existingTask.blockedBy || []),
|
|
736
|
+
...input.addBlockedBy,
|
|
737
|
+
];
|
|
738
|
+
}
|
|
739
|
+
if (input.addBlocks) {
|
|
740
|
+
existingTask.blocks = [
|
|
741
|
+
...(existingTask.blocks || []),
|
|
742
|
+
...input.addBlocks,
|
|
743
|
+
];
|
|
744
|
+
}
|
|
745
|
+
existingTask.updatedAt = timestamp;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Handle tool_result to get real task IDs
|
|
749
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
750
|
+
const toolId = block.tool_use_id;
|
|
751
|
+
const pending = pendingTasks.get(toolId);
|
|
752
|
+
if (pending) {
|
|
753
|
+
// Extract task ID from result content
|
|
754
|
+
let resultContent = '';
|
|
755
|
+
if (typeof block.content === 'string') {
|
|
756
|
+
resultContent = block.content;
|
|
757
|
+
}
|
|
758
|
+
else if (Array.isArray(block.content)) {
|
|
759
|
+
resultContent = block.content
|
|
760
|
+
.filter((c) => c.type === 'text' && c.text)
|
|
761
|
+
.map((c) => c.text || '')
|
|
762
|
+
.join('\n');
|
|
763
|
+
}
|
|
764
|
+
// Try to extract task ID from "Task created with ID: X"
|
|
765
|
+
const idMatch = resultContent.match(/(?:Task created with ID:|id[:\s]+)(\d+)/i);
|
|
766
|
+
if (idMatch) {
|
|
767
|
+
const realId = idMatch[1];
|
|
768
|
+
toolIdToTaskId.set(toolId, realId);
|
|
769
|
+
// Create the task with real ID
|
|
770
|
+
tasks.set(realId, {
|
|
771
|
+
id: realId,
|
|
772
|
+
subject: pending.task.subject || '',
|
|
773
|
+
description: pending.task.description || '',
|
|
774
|
+
status: pending.task.status || 'pending',
|
|
775
|
+
activeForm: pending.task.activeForm,
|
|
776
|
+
owner: pending.task.owner,
|
|
777
|
+
blockedBy: pending.task.blockedBy,
|
|
778
|
+
blocks: pending.task.blocks,
|
|
779
|
+
createdAt: pending.task.createdAt || timestamp,
|
|
780
|
+
updatedAt: pending.task.updatedAt || timestamp,
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
pendingTasks.delete(toolId);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
// Skip malformed lines
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
// Return tasks sorted by ID (numeric order)
|
|
794
|
+
return Array.from(tasks.values()).sort((a, b) => {
|
|
795
|
+
const aNum = parseInt(a.id, 10);
|
|
796
|
+
const bNum = parseInt(b.id, 10);
|
|
797
|
+
return aNum - bNum;
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
//# sourceMappingURL=parser.js.map
|