@parall/codex-agent 1.30.0 → 1.32.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/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -35
- package/dist/dispatch.d.ts +6 -5
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +102 -82
- package/dist/event-mapping.d.ts +1 -1
- package/dist/event-mapping.d.ts.map +1 -1
- package/dist/event-mapping.js +102 -82
- package/dist/index.js +169 -114
- package/dist/jsonrpc-client.d.ts +4 -4
- package/dist/jsonrpc-client.d.ts.map +1 -1
- package/dist/jsonrpc-client.js +15 -15
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +7 -5
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +40 -39
- package/package.json +4 -4
- package/src/config.ts +41 -36
- package/src/dispatch.ts +140 -110
- package/src/event-mapping.ts +135 -109
- package/src/index.ts +199 -117
- package/src/jsonrpc-client.ts +22 -20
- package/src/session-manager.ts +10 -6
- package/src/workspace.ts +50 -43
package/src/event-mapping.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RuntimeEvent } from
|
|
1
|
+
import type { RuntimeEvent } from '@parall/agent-core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Translation layer between `codex app-server` JSON-RPC notifications and
|
|
@@ -31,39 +31,39 @@ export class EventMapper {
|
|
|
31
31
|
|
|
32
32
|
switch (method) {
|
|
33
33
|
// Streaming deltas are intentionally dropped — see the class comment.
|
|
34
|
-
case
|
|
35
|
-
case
|
|
36
|
-
case
|
|
34
|
+
case 'item/agentMessage/delta':
|
|
35
|
+
case 'item/reasoning/textDelta':
|
|
36
|
+
case 'item/reasoning/summaryTextDelta':
|
|
37
37
|
break;
|
|
38
38
|
|
|
39
|
-
case
|
|
39
|
+
case 'item/started': {
|
|
40
40
|
const item = p.item as Record<string, unknown> | undefined;
|
|
41
|
-
if (!item || typeof item.type !==
|
|
42
|
-
events.push(...this.mapItem(item,
|
|
41
|
+
if (!item || typeof item.type !== 'string') break;
|
|
42
|
+
events.push(...this.mapItem(item, 'started'));
|
|
43
43
|
break;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
case
|
|
46
|
+
case 'item/completed': {
|
|
47
47
|
const item = p.item as Record<string, unknown> | undefined;
|
|
48
|
-
if (!item || typeof item.type !==
|
|
49
|
-
events.push(...this.mapItem(item,
|
|
48
|
+
if (!item || typeof item.type !== 'string') break;
|
|
49
|
+
events.push(...this.mapItem(item, 'completed'));
|
|
50
50
|
break;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
case
|
|
53
|
+
case 'turn/completed': {
|
|
54
54
|
const turn = p.turn as Record<string, unknown> | undefined;
|
|
55
55
|
const status = turn ? asString(turn.status) : undefined;
|
|
56
56
|
const error = turn?.error as Record<string, unknown> | undefined;
|
|
57
|
-
if (status ===
|
|
58
|
-
const message = asString(error?.message) ??
|
|
59
|
-
events.push({ type:
|
|
57
|
+
if (status === 'failed') {
|
|
58
|
+
const message = asString(error?.message) ?? 'Codex turn failed';
|
|
59
|
+
events.push({ type: 'error', message });
|
|
60
60
|
}
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
case
|
|
65
|
-
const message = asString(p.message) ??
|
|
66
|
-
events.push({ type:
|
|
64
|
+
case 'error': {
|
|
65
|
+
const message = asString(p.message) ?? 'Codex error';
|
|
66
|
+
events.push({ type: 'error', message });
|
|
67
67
|
break;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -71,75 +71,82 @@ export class EventMapper {
|
|
|
71
71
|
return events;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
private mapItem(item: Record<string, unknown>, phase:
|
|
74
|
+
private mapItem(item: Record<string, unknown>, phase: 'started' | 'completed'): RuntimeEvent[] {
|
|
75
75
|
const type = asString(item.type);
|
|
76
76
|
const id = asString(item.id);
|
|
77
77
|
const now = Date.now();
|
|
78
78
|
|
|
79
|
-
if (type ===
|
|
80
|
-
if (phase !==
|
|
79
|
+
if (type === 'agentMessage') {
|
|
80
|
+
if (phase !== 'completed') return [];
|
|
81
81
|
const text = asString(item.text);
|
|
82
82
|
if (!text) return [];
|
|
83
83
|
// Layer 0 symmetric output contract: plain text is never auto-projected;
|
|
84
84
|
// agents must explicitly call `@parall/cli messages send` / `dm` to reach
|
|
85
85
|
// the chat. The text is still recorded as a session step for audit.
|
|
86
|
-
return [{ type:
|
|
86
|
+
return [{ type: 'text', text, project: false }];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
if (type ===
|
|
90
|
-
if (phase !==
|
|
89
|
+
if (type === 'reasoning') {
|
|
90
|
+
if (phase !== 'completed') return [];
|
|
91
91
|
const text = joinReasoningText(item);
|
|
92
92
|
if (!text) return [];
|
|
93
|
-
return [{ type:
|
|
93
|
+
return [{ type: 'thinking', text }];
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
if (type ===
|
|
96
|
+
if (type === 'commandExecution') {
|
|
97
97
|
const command = formatCommand(item.command);
|
|
98
98
|
const callId = id ?? `shell-${now}`;
|
|
99
|
-
if (phase ===
|
|
99
|
+
if (phase === 'started') {
|
|
100
100
|
this.toolCallStart.set(callId, now);
|
|
101
|
-
return [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
type: 'tool_call',
|
|
104
|
+
callId,
|
|
105
|
+
toolName: 'shell',
|
|
106
|
+
input: { command, cwd: asString(item.cwd) },
|
|
107
|
+
startedAt: new Date(now).toISOString(),
|
|
108
|
+
},
|
|
109
|
+
];
|
|
108
110
|
}
|
|
109
111
|
// Codex app-server uses camelCase: aggregatedOutput / exitCode / durationMs
|
|
110
112
|
// (see openai/codex codex-rs/app-server-protocol). Earlier snake_case
|
|
111
113
|
// probing dropped exit codes and split outputs that arrive as one string.
|
|
112
114
|
const durationMs = this.resolveDuration(callId, now, item.durationMs);
|
|
113
115
|
const output = asString(item.aggregatedOutput) ?? joinStreams(item.stdout, item.stderr);
|
|
114
|
-
const exitCode = typeof item.exitCode ===
|
|
116
|
+
const exitCode = typeof item.exitCode === 'number' ? item.exitCode : undefined;
|
|
115
117
|
const status = asString(item.status);
|
|
116
|
-
const failed =
|
|
117
|
-
|| status ===
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
const failed =
|
|
119
|
+
(exitCode !== undefined && exitCode !== 0) || status === 'failed' || status === 'declined';
|
|
120
|
+
return [
|
|
121
|
+
{
|
|
122
|
+
type: 'tool_result',
|
|
123
|
+
callId,
|
|
124
|
+
toolName: 'shell',
|
|
125
|
+
output,
|
|
126
|
+
...(failed
|
|
127
|
+
? { error: output || `shell ${status ?? 'exited'} (code=${exitCode ?? '?'})` }
|
|
128
|
+
: {}),
|
|
129
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
130
|
+
},
|
|
131
|
+
];
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
if (type ===
|
|
134
|
+
if (type === 'mcpToolCall') {
|
|
130
135
|
const server = asString(item.server);
|
|
131
136
|
const tool = asString(item.tool);
|
|
132
|
-
const toolName = server && tool ? `${server}:${tool}` : tool ?? server ??
|
|
137
|
+
const toolName = server && tool ? `${server}:${tool}` : (tool ?? server ?? 'mcp');
|
|
133
138
|
const callId = id ?? `mcp-${now}`;
|
|
134
|
-
if (phase ===
|
|
139
|
+
if (phase === 'started') {
|
|
135
140
|
this.toolCallStart.set(callId, now);
|
|
136
|
-
return [
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
type: 'tool_call',
|
|
144
|
+
callId,
|
|
145
|
+
toolName,
|
|
146
|
+
input: item.arguments ?? {},
|
|
147
|
+
startedAt: new Date(now).toISOString(),
|
|
148
|
+
},
|
|
149
|
+
];
|
|
143
150
|
}
|
|
144
151
|
const durationMs = this.resolveDuration(callId, now, item.durationMs);
|
|
145
152
|
const result = item.result;
|
|
@@ -148,64 +155,73 @@ export class EventMapper {
|
|
|
148
155
|
// success path renders an empty-quoted output and the failure path's
|
|
149
156
|
// `output || "MCP tool call failed"` fallback gets eaten because the
|
|
150
157
|
// literal `'""'` is truthy.
|
|
151
|
-
const output =
|
|
152
|
-
?
|
|
153
|
-
: typeof result === "string" ? result : JSON.stringify(result);
|
|
158
|
+
const output =
|
|
159
|
+
result == null ? '' : typeof result === 'string' ? result : JSON.stringify(result);
|
|
154
160
|
const success = item.isSuccess !== false;
|
|
155
|
-
return [
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
return [
|
|
162
|
+
{
|
|
163
|
+
type: 'tool_result',
|
|
164
|
+
callId,
|
|
165
|
+
toolName,
|
|
166
|
+
output,
|
|
167
|
+
...(success ? {} : { error: output || 'MCP tool call failed' }),
|
|
168
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
169
|
+
},
|
|
170
|
+
];
|
|
163
171
|
}
|
|
164
172
|
|
|
165
|
-
if (type ===
|
|
173
|
+
if (type === 'fileChange') {
|
|
166
174
|
const callId = id ?? `patch-${now}`;
|
|
167
|
-
if (phase ===
|
|
175
|
+
if (phase === 'started') {
|
|
168
176
|
this.toolCallStart.set(callId, now);
|
|
169
|
-
return [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
type: 'tool_call',
|
|
180
|
+
callId,
|
|
181
|
+
toolName: 'patch',
|
|
182
|
+
input: { changes: item.changes ?? [] },
|
|
183
|
+
startedAt: new Date(now).toISOString(),
|
|
184
|
+
},
|
|
185
|
+
];
|
|
176
186
|
}
|
|
177
187
|
const durationMs = this.resolveDuration(callId, now, undefined);
|
|
178
|
-
return [
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
type: 'tool_result',
|
|
191
|
+
callId,
|
|
192
|
+
toolName: 'patch',
|
|
193
|
+
output: '',
|
|
194
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
195
|
+
},
|
|
196
|
+
];
|
|
185
197
|
}
|
|
186
198
|
|
|
187
|
-
if (type ===
|
|
199
|
+
if (type === 'webSearch') {
|
|
188
200
|
const callId = id ?? `search-${now}`;
|
|
189
|
-
if (phase ===
|
|
201
|
+
if (phase === 'started') {
|
|
190
202
|
this.toolCallStart.set(callId, now);
|
|
191
|
-
return [
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
return [
|
|
204
|
+
{
|
|
205
|
+
type: 'tool_call',
|
|
206
|
+
callId,
|
|
207
|
+
toolName: 'web_search',
|
|
208
|
+
input: { query: asString(item.query) ?? '' },
|
|
209
|
+
startedAt: new Date(now).toISOString(),
|
|
210
|
+
},
|
|
211
|
+
];
|
|
198
212
|
}
|
|
199
213
|
const durationMs = this.resolveDuration(callId, now, item.durationMs);
|
|
200
214
|
const results = Array.isArray(item.results) ? item.results : [];
|
|
201
|
-
const output = results.length > 0 ? JSON.stringify(results) :
|
|
202
|
-
return [
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
const output = results.length > 0 ? JSON.stringify(results) : '';
|
|
216
|
+
return [
|
|
217
|
+
{
|
|
218
|
+
type: 'tool_result',
|
|
219
|
+
callId,
|
|
220
|
+
toolName: 'web_search',
|
|
221
|
+
output,
|
|
222
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
223
|
+
},
|
|
224
|
+
];
|
|
209
225
|
}
|
|
210
226
|
|
|
211
227
|
return [];
|
|
@@ -217,10 +233,14 @@ export class EventMapper {
|
|
|
217
233
|
* tracked start timestamp so long-lived bridges don't accumulate stale
|
|
218
234
|
* entries when the server routinely supplies durationMs.
|
|
219
235
|
*/
|
|
220
|
-
private resolveDuration(
|
|
236
|
+
private resolveDuration(
|
|
237
|
+
callId: string,
|
|
238
|
+
now: number,
|
|
239
|
+
serverDurationMs: unknown,
|
|
240
|
+
): number | undefined {
|
|
221
241
|
const start = this.toolCallStart.get(callId);
|
|
222
242
|
this.toolCallStart.delete(callId);
|
|
223
|
-
if (typeof serverDurationMs ===
|
|
243
|
+
if (typeof serverDurationMs === 'number' && Number.isFinite(serverDurationMs)) {
|
|
224
244
|
return Math.max(0, serverDurationMs);
|
|
225
245
|
}
|
|
226
246
|
if (start === undefined) return undefined;
|
|
@@ -229,30 +249,36 @@ export class EventMapper {
|
|
|
229
249
|
}
|
|
230
250
|
|
|
231
251
|
function asString(value: unknown): string | undefined {
|
|
232
|
-
if (typeof value !==
|
|
252
|
+
if (typeof value !== 'string') return undefined;
|
|
233
253
|
const trimmed = value.trim();
|
|
234
254
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
235
255
|
}
|
|
236
256
|
|
|
237
257
|
function joinReasoningText(item: Record<string, unknown>): string {
|
|
238
|
-
const summary = Array.isArray(item.summary)
|
|
239
|
-
|
|
258
|
+
const summary = Array.isArray(item.summary)
|
|
259
|
+
? item.summary.filter((x) => typeof x === 'string')
|
|
260
|
+
: [];
|
|
261
|
+
const content = Array.isArray(item.content)
|
|
262
|
+
? item.content.filter((x) => typeof x === 'string')
|
|
263
|
+
: [];
|
|
240
264
|
const parts = [...summary, ...content, asString(item.text)].filter(Boolean) as string[];
|
|
241
|
-
return parts.join(
|
|
265
|
+
return parts.join('\n').trim();
|
|
242
266
|
}
|
|
243
267
|
|
|
244
268
|
function joinStreams(stdout: unknown, stderr: unknown): string {
|
|
245
269
|
const out = asString(stdout);
|
|
246
270
|
const err = asString(stderr);
|
|
247
271
|
if (out && err) return `${out}\n${err}`;
|
|
248
|
-
return out ?? err ??
|
|
272
|
+
return out ?? err ?? '';
|
|
249
273
|
}
|
|
250
274
|
|
|
251
275
|
/** Codex sends `command` either as a plain string or as an argv array (e.g. ["sh", "-c", "ls"]). */
|
|
252
276
|
function formatCommand(command: unknown): string {
|
|
253
|
-
if (typeof command ===
|
|
277
|
+
if (typeof command === 'string') return command;
|
|
254
278
|
if (Array.isArray(command)) {
|
|
255
|
-
return command
|
|
279
|
+
return command
|
|
280
|
+
.map((part) => (typeof part === 'string' ? part : JSON.stringify(part)))
|
|
281
|
+
.join(' ');
|
|
256
282
|
}
|
|
257
|
-
return
|
|
283
|
+
return '';
|
|
258
284
|
}
|