ai-cli-mcp 2.17.0 → 2.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/CHANGELOG.md +7 -0
- package/README.ja.md +13 -9
- package/README.md +13 -9
- package/dist/__tests__/app-cli.test.js +3 -3
- package/dist/__tests__/cli-process-service.test.js +3 -2
- package/dist/__tests__/mcp-contract.test.js +1 -0
- package/dist/__tests__/parsers.test.js +144 -1
- package/dist/__tests__/peek.test.js +8 -7
- package/dist/__tests__/process-management.test.js +78 -4
- package/dist/app/cli.js +6 -5
- package/dist/app/mcp.js +11 -2
- package/dist/cli-process-service.js +10 -10
- package/dist/parsers.js +282 -22
- package/dist/peek.js +8 -5
- package/dist/process-service.js +10 -10
- package/package.json +1 -1
- package/src/__tests__/app-cli.test.ts +3 -3
- package/src/__tests__/cli-process-service.test.ts +3 -2
- package/src/__tests__/mcp-contract.test.ts +1 -0
- package/src/__tests__/parsers.test.ts +155 -1
- package/src/__tests__/peek.test.ts +8 -7
- package/src/__tests__/process-management.test.ts +86 -4
- package/src/app/cli.ts +7 -6
- package/src/app/mcp.ts +11 -2
- package/src/cli-process-service.ts +12 -12
- package/src/parsers.ts +371 -27
- package/src/peek.ts +14 -7
- package/src/process-service.ts +12 -12
|
@@ -4,9 +4,9 @@ import { join, basename, dirname } from 'node:path';
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { buildCliCommand } from './cli-builder.js';
|
|
6
6
|
import { findClaudeCli, findCodexCli, findForgeCli, findGeminiCli, findOpencodeCli } from './cli-utils.js';
|
|
7
|
-
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput, parseOpenCodeOutput,
|
|
7
|
+
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput, parseOpenCodeOutput, PeekEventExtractor } from './parsers.js';
|
|
8
8
|
import { buildProcessResult } from './process-result.js';
|
|
9
|
-
import {
|
|
9
|
+
import { appendPeekEvents, buildNotFoundPeekProcess, observedDurationSec, validatePeekPids, validatePeekTimeSec, } from './peek.js';
|
|
10
10
|
function resolveDefaultStateDir() {
|
|
11
11
|
return process.env.AI_CLI_STATE_DIR || join(homedir(), '.local', 'state', 'ai-cli');
|
|
12
12
|
}
|
|
@@ -175,7 +175,7 @@ export class CliProcessService {
|
|
|
175
175
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
-
async peekProcesses(pids, peekTimeSec = 10) {
|
|
178
|
+
async peekProcesses(pids, peekTimeSec = 10, includeToolCalls = false) {
|
|
179
179
|
const targetPids = validatePeekPids(pids);
|
|
180
180
|
const targetPeekTimeSec = validatePeekTimeSec(peekTimeSec);
|
|
181
181
|
const processes = [];
|
|
@@ -193,7 +193,7 @@ export class CliProcessService {
|
|
|
193
193
|
pid,
|
|
194
194
|
agent: process.toolType,
|
|
195
195
|
status: process.status,
|
|
196
|
-
|
|
196
|
+
events: [],
|
|
197
197
|
truncated: false,
|
|
198
198
|
error: null,
|
|
199
199
|
};
|
|
@@ -201,8 +201,8 @@ export class CliProcessService {
|
|
|
201
201
|
observers.push({
|
|
202
202
|
process,
|
|
203
203
|
result,
|
|
204
|
-
stdoutExtractor: new
|
|
205
|
-
stderrExtractor: new
|
|
204
|
+
stdoutExtractor: new PeekEventExtractor(process.toolType, { includeToolCalls }),
|
|
205
|
+
stderrExtractor: new PeekEventExtractor(process.toolType, { includeToolCalls }),
|
|
206
206
|
stdoutOffset: this.fileSizeSafe(process.stdoutPath),
|
|
207
207
|
stderrOffset: this.fileSizeSafe(process.stderrPath),
|
|
208
208
|
});
|
|
@@ -216,10 +216,10 @@ export class CliProcessService {
|
|
|
216
216
|
for (const observer of observers) {
|
|
217
217
|
const stdoutRead = this.readTextFromOffset(observer.process.stdoutPath, observer.stdoutOffset);
|
|
218
218
|
observer.stdoutOffset = stdoutRead.offset;
|
|
219
|
-
|
|
219
|
+
appendPeekEvents(observer.result, observer.stdoutExtractor.push(stdoutRead.text, observedAt));
|
|
220
220
|
const stderrRead = this.readTextFromOffset(observer.process.stderrPath, observer.stderrOffset);
|
|
221
221
|
observer.stderrOffset = stderrRead.offset;
|
|
222
|
-
|
|
222
|
+
appendPeekEvents(observer.result, observer.stderrExtractor.push(stderrRead.text, observedAt));
|
|
223
223
|
observer.process = this.refreshStatus(this.readProcess(observer.process.pid));
|
|
224
224
|
observer.result.status = observer.process.status;
|
|
225
225
|
if (observer.process.status === 'running') {
|
|
@@ -239,8 +239,8 @@ export class CliProcessService {
|
|
|
239
239
|
for (const observer of observers) {
|
|
240
240
|
observer.process = this.refreshStatus(this.readProcess(observer.process.pid));
|
|
241
241
|
observer.result.status = observer.process.status;
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
appendPeekEvents(observer.result, observer.stdoutExtractor.flush(flushTs));
|
|
243
|
+
appendPeekEvents(observer.result, observer.stderrExtractor.flush(flushTs));
|
|
244
244
|
}
|
|
245
245
|
return {
|
|
246
246
|
peek_started_at: startedAt.toISOString(),
|
package/dist/parsers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { debugLog } from './cli-utils.js';
|
|
2
|
+
const PEEK_TOOL_SUMMARY_MAX_LENGTH = 200;
|
|
2
3
|
function isGeminiAssistantMessageEvent(parsed) {
|
|
3
4
|
return parsed.type === 'message' && parsed.role === 'assistant' && typeof parsed.content === 'string';
|
|
4
5
|
}
|
|
@@ -14,32 +15,248 @@ const GEMINI_STREAM_EVENT_TYPES = new Set([
|
|
|
14
15
|
function isGeminiStreamJsonEvent(parsed) {
|
|
15
16
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) && GEMINI_STREAM_EVENT_TYPES.has(parsed.type);
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
+
function oneLine(value) {
|
|
19
|
+
return String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
20
|
+
}
|
|
21
|
+
function boundedSummary(value) {
|
|
22
|
+
const summary = oneLine(value);
|
|
23
|
+
if (summary.length <= PEEK_TOOL_SUMMARY_MAX_LENGTH) {
|
|
24
|
+
return { summary };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
summary: `${summary.slice(0, PEEK_TOOL_SUMMARY_MAX_LENGTH - 3)}...`,
|
|
28
|
+
summary_truncated: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function normalizeMcpToolName(tool, explicitServer) {
|
|
32
|
+
if (explicitServer) {
|
|
33
|
+
return {
|
|
34
|
+
server: explicitServer,
|
|
35
|
+
...boundedSummary(`${explicitServer}.${tool}`),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const mcpDouble = tool.match(/^mcp__([^_]+)__(.+)$/);
|
|
39
|
+
if (mcpDouble) {
|
|
40
|
+
return {
|
|
41
|
+
server: mcpDouble[1],
|
|
42
|
+
...boundedSummary(`${mcpDouble[1]}.${mcpDouble[2]}`),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const mcpSingle = tool.match(/^mcp_([^_]+)_(.+)$/);
|
|
46
|
+
if (mcpSingle) {
|
|
47
|
+
return {
|
|
48
|
+
server: mcpSingle[1],
|
|
49
|
+
...boundedSummary(`${mcpSingle[1]}.${mcpSingle[2]}`),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const acmShort = tool.match(/^acm_(.+)$/);
|
|
53
|
+
if (acmShort) {
|
|
54
|
+
return {
|
|
55
|
+
server: 'acm',
|
|
56
|
+
...boundedSummary(`acm.${acmShort[1]}`),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function buildToolSummary(tool, options = {}) {
|
|
62
|
+
if (typeof options.command === 'string' && options.command.trim()) {
|
|
63
|
+
return boundedSummary(options.command);
|
|
64
|
+
}
|
|
65
|
+
const mcpSummary = normalizeMcpToolName(tool, options.server);
|
|
66
|
+
if (mcpSummary) {
|
|
67
|
+
return mcpSummary;
|
|
68
|
+
}
|
|
69
|
+
return boundedSummary(tool || 'tool_call');
|
|
70
|
+
}
|
|
71
|
+
function normalizeToolStatus(rawStatus, exitCode, defaultStatus = 'unknown') {
|
|
72
|
+
if (typeof exitCode === 'number') {
|
|
73
|
+
return exitCode === 0 ? 'success' : 'failed';
|
|
74
|
+
}
|
|
75
|
+
const status = typeof rawStatus === 'string' ? rawStatus.toLowerCase() : '';
|
|
76
|
+
if (['success', 'succeeded', 'ok', 'completed'].includes(status)) {
|
|
77
|
+
return 'success';
|
|
78
|
+
}
|
|
79
|
+
if (['failed', 'failure', 'error', 'errored'].includes(status)) {
|
|
80
|
+
return 'failed';
|
|
81
|
+
}
|
|
82
|
+
if (['cancelled', 'canceled'].includes(status)) {
|
|
83
|
+
return 'cancelled';
|
|
84
|
+
}
|
|
85
|
+
return defaultStatus;
|
|
86
|
+
}
|
|
87
|
+
function createToolCallEvent(params) {
|
|
88
|
+
const tool = params.tool || 'tool_call';
|
|
89
|
+
const summary = buildToolSummary(tool, { server: params.server, command: params.command });
|
|
90
|
+
const event = {
|
|
91
|
+
kind: 'tool_call',
|
|
92
|
+
ts: params.ts,
|
|
93
|
+
phase: params.phase,
|
|
94
|
+
tool,
|
|
95
|
+
summary: summary.summary,
|
|
96
|
+
};
|
|
97
|
+
if (params.id) {
|
|
98
|
+
event.id = params.id;
|
|
99
|
+
}
|
|
100
|
+
if (summary.server) {
|
|
101
|
+
event.server = summary.server;
|
|
102
|
+
}
|
|
103
|
+
else if (params.server) {
|
|
104
|
+
event.server = params.server;
|
|
105
|
+
}
|
|
106
|
+
if (summary.summary_truncated) {
|
|
107
|
+
event.summary_truncated = true;
|
|
108
|
+
}
|
|
109
|
+
if (params.phase === 'completed') {
|
|
110
|
+
event.status = normalizeToolStatus(params.status, params.exit_code, params.defaultStatus);
|
|
111
|
+
if (typeof params.exit_code === 'number') {
|
|
112
|
+
event.exit_code = params.exit_code;
|
|
113
|
+
}
|
|
114
|
+
if (typeof params.duration_ms === 'number' && Number.isFinite(params.duration_ms)) {
|
|
115
|
+
event.duration_ms = params.duration_ms;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return event;
|
|
119
|
+
}
|
|
120
|
+
function rememberToolCall(event, memory) {
|
|
121
|
+
if (event.kind !== 'tool_call' || !event.id) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
memory.set(event.id, {
|
|
125
|
+
tool: event.tool,
|
|
126
|
+
server: event.server,
|
|
127
|
+
summary: event.summary,
|
|
128
|
+
summary_truncated: event.summary_truncated,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function createRememberedCompletion(params) {
|
|
132
|
+
const remembered = params.id ? params.memory.get(params.id) : undefined;
|
|
133
|
+
const event = createToolCallEvent({
|
|
134
|
+
ts: params.ts,
|
|
135
|
+
phase: 'completed',
|
|
136
|
+
id: params.id,
|
|
137
|
+
tool: remembered?.tool || params.fallbackTool,
|
|
138
|
+
server: remembered?.server,
|
|
139
|
+
status: params.status,
|
|
140
|
+
defaultStatus: params.defaultStatus,
|
|
141
|
+
});
|
|
142
|
+
if (remembered) {
|
|
143
|
+
event.summary = remembered.summary;
|
|
144
|
+
if (remembered.summary_truncated) {
|
|
145
|
+
event.summary_truncated = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return event;
|
|
149
|
+
}
|
|
150
|
+
function extractPeekEventsFromParsedEvent(agent, parsed, observedAt, includeToolCalls, memory) {
|
|
18
151
|
if (agent === 'codex') {
|
|
19
152
|
if (parsed.item?.type === 'agent_message' && typeof parsed.item.text === 'string' && parsed.item.text.trim()) {
|
|
20
|
-
return [{ ts: observedAt, text: parsed.item.text }];
|
|
153
|
+
return [{ kind: 'message', ts: observedAt, text: parsed.item.text }];
|
|
21
154
|
}
|
|
22
155
|
if (parsed.msg?.type === 'agent_message' && typeof parsed.msg.message === 'string' && parsed.msg.message.trim()) {
|
|
23
|
-
return [{ ts: observedAt, text: parsed.msg.message }];
|
|
156
|
+
return [{ kind: 'message', ts: observedAt, text: parsed.msg.message }];
|
|
157
|
+
}
|
|
158
|
+
if (includeToolCalls && (parsed.type === 'item.started' || parsed.type === 'item.completed')) {
|
|
159
|
+
const item = parsed.item;
|
|
160
|
+
if (item?.type === 'command_execution') {
|
|
161
|
+
const event = createToolCallEvent({
|
|
162
|
+
ts: observedAt,
|
|
163
|
+
phase: parsed.type === 'item.started' ? 'started' : 'completed',
|
|
164
|
+
id: item.id,
|
|
165
|
+
tool: 'command_execution',
|
|
166
|
+
command: item.command,
|
|
167
|
+
status: item.status || item.error,
|
|
168
|
+
exit_code: typeof item.exit_code === 'number' ? item.exit_code : undefined,
|
|
169
|
+
defaultStatus: parsed.type === 'item.completed' ? 'success' : 'unknown',
|
|
170
|
+
});
|
|
171
|
+
rememberToolCall(event, memory);
|
|
172
|
+
return [event];
|
|
173
|
+
}
|
|
174
|
+
if (item?.type === 'mcp_tool_call') {
|
|
175
|
+
const event = createToolCallEvent({
|
|
176
|
+
ts: observedAt,
|
|
177
|
+
phase: parsed.type === 'item.started' ? 'started' : 'completed',
|
|
178
|
+
id: item.id,
|
|
179
|
+
tool: item.tool || 'mcp_tool_call',
|
|
180
|
+
server: item.server,
|
|
181
|
+
status: item.status || item.error,
|
|
182
|
+
defaultStatus: parsed.type === 'item.completed' ? 'success' : 'unknown',
|
|
183
|
+
});
|
|
184
|
+
rememberToolCall(event, memory);
|
|
185
|
+
return [event];
|
|
186
|
+
}
|
|
24
187
|
}
|
|
25
188
|
return [];
|
|
26
189
|
}
|
|
27
|
-
if (agent === 'claude'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
190
|
+
if (agent === 'claude') {
|
|
191
|
+
if (parsed.type === 'assistant' && Array.isArray(parsed.message?.content)) {
|
|
192
|
+
const events = [];
|
|
193
|
+
for (const content of parsed.message.content) {
|
|
194
|
+
if (content?.type === 'text' && typeof content.text === 'string' && content.text.trim()) {
|
|
195
|
+
events.push({ kind: 'message', ts: observedAt, text: content.text });
|
|
196
|
+
}
|
|
197
|
+
else if (includeToolCalls && content?.type === 'tool_use') {
|
|
198
|
+
const event = createToolCallEvent({
|
|
199
|
+
ts: observedAt,
|
|
200
|
+
phase: 'started',
|
|
201
|
+
id: content.id,
|
|
202
|
+
tool: content.name || 'tool_use',
|
|
203
|
+
command: content.input?.command,
|
|
204
|
+
});
|
|
205
|
+
rememberToolCall(event, memory);
|
|
206
|
+
events.push(event);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return events;
|
|
210
|
+
}
|
|
211
|
+
if (includeToolCalls && parsed.type === 'user' && Array.isArray(parsed.message?.content)) {
|
|
212
|
+
const events = [];
|
|
213
|
+
for (const content of parsed.message.content) {
|
|
214
|
+
if (content?.type === 'tool_result') {
|
|
215
|
+
events.push(createRememberedCompletion({
|
|
216
|
+
ts: observedAt,
|
|
217
|
+
id: content.tool_use_id,
|
|
218
|
+
memory,
|
|
219
|
+
fallbackTool: 'tool_result',
|
|
220
|
+
status: content.is_error === true ? 'failed' : undefined,
|
|
221
|
+
defaultStatus: content.is_error === true ? 'failed' : 'success',
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return events;
|
|
226
|
+
}
|
|
227
|
+
return [];
|
|
31
228
|
}
|
|
32
229
|
if (agent === 'opencode' && parsed.type === 'text' && parsed.part?.type === 'text' && typeof parsed.part.text === 'string' && parsed.part.text.trim()) {
|
|
33
|
-
return [{ ts: observedAt, text: parsed.part.text }];
|
|
230
|
+
return [{ kind: 'message', ts: observedAt, text: parsed.part.text }];
|
|
231
|
+
}
|
|
232
|
+
if (agent === 'opencode' && includeToolCalls && parsed.type === 'tool_use' && parsed.part?.type === 'tool') {
|
|
233
|
+
const state = parsed.part.state || {};
|
|
234
|
+
const start = state.time?.start;
|
|
235
|
+
const end = state.time?.end;
|
|
236
|
+
const event = createToolCallEvent({
|
|
237
|
+
ts: observedAt,
|
|
238
|
+
phase: state.status === 'running' || state.status === 'pending' ? 'started' : 'completed',
|
|
239
|
+
id: parsed.part.callID,
|
|
240
|
+
tool: parsed.part.tool || 'tool_use',
|
|
241
|
+
command: state.input?.command,
|
|
242
|
+
status: state.status,
|
|
243
|
+
defaultStatus: state.status === 'completed' ? 'success' : 'unknown',
|
|
244
|
+
duration_ms: typeof start === 'number' && typeof end === 'number' ? end - start : undefined,
|
|
245
|
+
});
|
|
246
|
+
rememberToolCall(event, memory);
|
|
247
|
+
return [event];
|
|
34
248
|
}
|
|
35
249
|
return [];
|
|
36
250
|
}
|
|
37
|
-
export class
|
|
251
|
+
export class PeekEventExtractor {
|
|
38
252
|
agent;
|
|
39
253
|
pending = '';
|
|
40
254
|
geminiAssistantBuffer = '';
|
|
41
|
-
|
|
255
|
+
includeToolCalls;
|
|
256
|
+
toolMemory = new Map();
|
|
257
|
+
constructor(agent, options = {}) {
|
|
42
258
|
this.agent = agent;
|
|
259
|
+
this.includeToolCalls = options.includeToolCalls === true;
|
|
43
260
|
}
|
|
44
261
|
push(chunk, observedAt = new Date().toISOString()) {
|
|
45
262
|
if (!chunk) {
|
|
@@ -50,40 +267,66 @@ export class PeekMessageExtractor {
|
|
|
50
267
|
return this.extractLines(lines, observedAt);
|
|
51
268
|
}
|
|
52
269
|
flush(observedAt = new Date().toISOString()) {
|
|
53
|
-
const
|
|
270
|
+
const events = [];
|
|
54
271
|
if (this.pending) {
|
|
55
272
|
const line = this.pending;
|
|
56
273
|
this.pending = '';
|
|
57
|
-
|
|
274
|
+
events.push(...this.extractLines([line], observedAt));
|
|
58
275
|
}
|
|
59
|
-
|
|
60
|
-
return
|
|
276
|
+
events.push(...this.flushGeminiAssistantBuffer(observedAt));
|
|
277
|
+
return events;
|
|
61
278
|
}
|
|
62
279
|
extractLines(lines, observedAt) {
|
|
63
|
-
const
|
|
280
|
+
const events = [];
|
|
64
281
|
for (const line of lines) {
|
|
65
282
|
if (!line.trim()) {
|
|
66
283
|
continue;
|
|
67
284
|
}
|
|
68
285
|
try {
|
|
69
|
-
|
|
286
|
+
events.push(...this.extractParsedEvent(JSON.parse(line), observedAt));
|
|
70
287
|
}
|
|
71
288
|
catch {
|
|
72
289
|
debugLog(`[Debug] Skipping invalid peek JSON line: ${line}`);
|
|
73
|
-
|
|
290
|
+
events.push(...this.flushGeminiAssistantBuffer(observedAt));
|
|
74
291
|
}
|
|
75
292
|
}
|
|
76
|
-
return
|
|
293
|
+
return events;
|
|
77
294
|
}
|
|
78
295
|
extractParsedEvent(parsed, observedAt) {
|
|
79
|
-
if (this.agent
|
|
80
|
-
|
|
296
|
+
if (this.agent === 'gemini') {
|
|
297
|
+
const events = this.extractGeminiParsedEvent(parsed, observedAt);
|
|
298
|
+
return events;
|
|
81
299
|
}
|
|
300
|
+
return extractPeekEventsFromParsedEvent(this.agent, parsed, observedAt, this.includeToolCalls, this.toolMemory);
|
|
301
|
+
}
|
|
302
|
+
extractGeminiParsedEvent(parsed, observedAt) {
|
|
82
303
|
if (isGeminiAssistantMessageEvent(parsed)) {
|
|
83
304
|
this.geminiAssistantBuffer += parsed.content;
|
|
84
305
|
return [];
|
|
85
306
|
}
|
|
86
|
-
|
|
307
|
+
const events = this.flushGeminiAssistantBuffer(observedAt);
|
|
308
|
+
if (this.includeToolCalls && parsed.type === 'tool_use') {
|
|
309
|
+
const event = createToolCallEvent({
|
|
310
|
+
ts: observedAt,
|
|
311
|
+
phase: 'started',
|
|
312
|
+
id: parsed.tool_id,
|
|
313
|
+
tool: parsed.tool_name || parsed.name || 'tool_use',
|
|
314
|
+
command: parsed.parameters?.command,
|
|
315
|
+
});
|
|
316
|
+
rememberToolCall(event, this.toolMemory);
|
|
317
|
+
events.push(event);
|
|
318
|
+
}
|
|
319
|
+
else if (this.includeToolCalls && parsed.type === 'tool_result') {
|
|
320
|
+
events.push(createRememberedCompletion({
|
|
321
|
+
ts: observedAt,
|
|
322
|
+
id: parsed.tool_id,
|
|
323
|
+
memory: this.toolMemory,
|
|
324
|
+
fallbackTool: parsed.tool_name || parsed.name || 'tool_result',
|
|
325
|
+
status: parsed.status,
|
|
326
|
+
defaultStatus: 'unknown',
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
return events;
|
|
87
330
|
}
|
|
88
331
|
flushGeminiAssistantBuffer(observedAt) {
|
|
89
332
|
if (this.agent !== 'gemini' || !this.geminiAssistantBuffer) {
|
|
@@ -94,7 +337,24 @@ export class PeekMessageExtractor {
|
|
|
94
337
|
if (!text.trim()) {
|
|
95
338
|
return [];
|
|
96
339
|
}
|
|
97
|
-
return [{ ts: observedAt, text }];
|
|
340
|
+
return [{ kind: 'message', ts: observedAt, text }];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
export class PeekMessageExtractor {
|
|
344
|
+
extractor;
|
|
345
|
+
constructor(agent) {
|
|
346
|
+
this.extractor = new PeekEventExtractor(agent, { includeToolCalls: false });
|
|
347
|
+
}
|
|
348
|
+
push(chunk, observedAt = new Date().toISOString()) {
|
|
349
|
+
return this.toMessages(this.extractor.push(chunk, observedAt));
|
|
350
|
+
}
|
|
351
|
+
flush(observedAt = new Date().toISOString()) {
|
|
352
|
+
return this.toMessages(this.extractor.flush(observedAt));
|
|
353
|
+
}
|
|
354
|
+
toMessages(events) {
|
|
355
|
+
return events
|
|
356
|
+
.filter((event) => event.kind === 'message')
|
|
357
|
+
.map((event) => ({ ts: event.ts, text: event.text }));
|
|
98
358
|
}
|
|
99
359
|
}
|
|
100
360
|
export function parseCodexOutput(stdout) {
|
package/dist/peek.js
CHANGED
|
@@ -36,21 +36,24 @@ export function buildNotFoundPeekProcess(pid) {
|
|
|
36
36
|
pid,
|
|
37
37
|
agent: null,
|
|
38
38
|
status: 'not_found',
|
|
39
|
-
|
|
39
|
+
events: [],
|
|
40
40
|
truncated: false,
|
|
41
41
|
error: 'process not found',
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
export function
|
|
45
|
-
for (const
|
|
46
|
-
if (target.
|
|
47
|
-
target.
|
|
44
|
+
export function appendPeekEvents(target, events) {
|
|
45
|
+
for (const event of events) {
|
|
46
|
+
if (target.events.length < PEEK_MESSAGE_CAP) {
|
|
47
|
+
target.events.push(event);
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
50
|
target.truncated = true;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
export function appendPeekMessages(target, messages) {
|
|
55
|
+
appendPeekEvents(target, messages.map((message) => ({ kind: 'message', ...message })));
|
|
56
|
+
}
|
|
54
57
|
export function observedDurationSec(startedAtMs, endedAtMs = Date.now()) {
|
|
55
58
|
return Number(((endedAtMs - startedAtMs) / 1000).toFixed(2));
|
|
56
59
|
}
|
package/dist/process-service.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { buildCliCommand } from './cli-builder.js';
|
|
3
|
-
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput, parseOpenCodeOutput,
|
|
4
|
-
import {
|
|
3
|
+
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput, parseOpenCodeOutput, PeekEventExtractor } from './parsers.js';
|
|
4
|
+
import { appendPeekEvents, buildNotFoundPeekProcess, observedDurationSec, validatePeekPids, validatePeekTimeSec, } from './peek.js';
|
|
5
5
|
import { buildProcessResult } from './process-result.js';
|
|
6
6
|
function parseAgentOutput(agent, stdout, stderr) {
|
|
7
7
|
if (agent === 'codex') {
|
|
@@ -156,7 +156,7 @@ export class ProcessService {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
async peekProcesses(pids, peekTimeSec = 10) {
|
|
159
|
+
async peekProcesses(pids, peekTimeSec = 10, includeToolCalls = false) {
|
|
160
160
|
const targetPids = validatePeekPids(pids);
|
|
161
161
|
const targetPeekTimeSec = validatePeekTimeSec(peekTimeSec);
|
|
162
162
|
const processes = [];
|
|
@@ -171,18 +171,18 @@ export class ProcessService {
|
|
|
171
171
|
pid,
|
|
172
172
|
agent: entry.toolType,
|
|
173
173
|
status: entry.status,
|
|
174
|
-
|
|
174
|
+
events: [],
|
|
175
175
|
truncated: false,
|
|
176
176
|
error: null,
|
|
177
177
|
};
|
|
178
178
|
processes.push(result);
|
|
179
|
-
const stdoutExtractor = new
|
|
180
|
-
const stderrExtractor = new
|
|
179
|
+
const stdoutExtractor = new PeekEventExtractor(entry.toolType, { includeToolCalls });
|
|
180
|
+
const stderrExtractor = new PeekEventExtractor(entry.toolType, { includeToolCalls });
|
|
181
181
|
const onStdout = (data) => {
|
|
182
|
-
|
|
182
|
+
appendPeekEvents(result, stdoutExtractor.push(data.toString(), new Date().toISOString()));
|
|
183
183
|
};
|
|
184
184
|
const onStderr = (data) => {
|
|
185
|
-
|
|
185
|
+
appendPeekEvents(result, stderrExtractor.push(data.toString(), new Date().toISOString()));
|
|
186
186
|
};
|
|
187
187
|
if (entry.status === 'running') {
|
|
188
188
|
entry.process.stdout?.on('data', onStdout);
|
|
@@ -210,8 +210,8 @@ export class ProcessService {
|
|
|
210
210
|
for (const observer of observers) {
|
|
211
211
|
observer.entry.process.stdout?.off('data', observer.onStdout);
|
|
212
212
|
observer.entry.process.stderr?.off('data', observer.onStderr);
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
appendPeekEvents(observer.result, observer.stdoutExtractor.flush(flushTs));
|
|
214
|
+
appendPeekEvents(observer.result, observer.stderrExtractor.flush(flushTs));
|
|
215
215
|
observer.result.status = observer.entry.status;
|
|
216
216
|
}
|
|
217
217
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-cli-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"mcpName": "io.github.mkXultra/ai-cli-mcp",
|
|
5
5
|
"description": "MCP server for AI CLI tools (Claude, Codex, Gemini, Forge, and OpenCode) with background process management",
|
|
6
6
|
"author": "mkXultra",
|
|
@@ -210,7 +210,7 @@ describe('ai-cli app', () => {
|
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
const exitCode = await runCli(
|
|
213
|
-
['peek', '123', '456', '123', '--time', '5'],
|
|
213
|
+
['peek', '123', '456', '123', '--time', '5', '--include-tool-calls'],
|
|
214
214
|
{
|
|
215
215
|
stdout,
|
|
216
216
|
stderr,
|
|
@@ -219,7 +219,7 @@ describe('ai-cli app', () => {
|
|
|
219
219
|
);
|
|
220
220
|
|
|
221
221
|
expect(exitCode).toBe(0);
|
|
222
|
-
expect(peekProcesses).toHaveBeenCalledWith([123, 456], 5);
|
|
222
|
+
expect(peekProcesses).toHaveBeenCalledWith([123, 456], 5, true);
|
|
223
223
|
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('"peek_started_at"'));
|
|
224
224
|
expect(stderr).not.toHaveBeenCalled();
|
|
225
225
|
});
|
|
@@ -235,7 +235,7 @@ describe('ai-cli app', () => {
|
|
|
235
235
|
|
|
236
236
|
const defaultExitCode = await runCli(['peek', '123'], { stdout, stderr, peekProcesses });
|
|
237
237
|
expect(defaultExitCode).toBe(0);
|
|
238
|
-
expect(peekProcesses).toHaveBeenCalledWith([123], 10);
|
|
238
|
+
expect(peekProcesses).toHaveBeenCalledWith([123], 10, false);
|
|
239
239
|
|
|
240
240
|
const followExitCode = await runCli(['peek', '123', '--follow'], { stdout, stderr, peekProcesses });
|
|
241
241
|
expect(followExitCode).toBe(1);
|
|
@@ -171,8 +171,9 @@ printf '%s\n' '{"type":"user","message":{"content":[{"type":"tool_result","tool_
|
|
|
171
171
|
pid: runResult.pid,
|
|
172
172
|
agent: 'claude',
|
|
173
173
|
status: 'completed',
|
|
174
|
-
|
|
174
|
+
events: [
|
|
175
175
|
{
|
|
176
|
+
kind: 'message',
|
|
176
177
|
ts: expect.any(String),
|
|
177
178
|
text: 'new cli message',
|
|
178
179
|
},
|
|
@@ -184,7 +185,7 @@ printf '%s\n' '{"type":"user","message":{"content":[{"type":"tool_result","tool_
|
|
|
184
185
|
pid: 999999,
|
|
185
186
|
agent: null,
|
|
186
187
|
status: 'not_found',
|
|
187
|
-
|
|
188
|
+
events: [],
|
|
188
189
|
truncated: false,
|
|
189
190
|
error: 'process not found',
|
|
190
191
|
});
|
|
@@ -137,6 +137,7 @@ describe('MCP Contract Tests', () => {
|
|
|
137
137
|
const peekTool = tools.find((tool: any) => tool.name === 'peek');
|
|
138
138
|
expect(peekTool.inputSchema.required).toEqual(['pids']);
|
|
139
139
|
expect(Object.keys(peekTool.inputSchema.properties).sort()).toEqual([
|
|
140
|
+
'include_tool_calls',
|
|
140
141
|
'peek_time_sec',
|
|
141
142
|
'pids',
|
|
142
143
|
]);
|