mcp-codex-worker 0.1.25 → 0.1.26
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/src/execution/codex-adapter.js +5 -0
- package/dist/src/execution/codex-adapter.js.map +1 -1
- package/dist/src/execution/codex-event-capture.d.ts +8 -0
- package/dist/src/execution/codex-event-capture.js +137 -0
- package/dist/src/execution/codex-event-capture.js.map +1 -0
- package/package.json +1 -1
- package/src/execution/codex-adapter.ts +5 -0
- package/src/execution/codex-event-capture.ts +156 -0
|
@@ -6,6 +6,7 @@ import { CODEX_CAPABILITIES } from './provider-capabilities.js';
|
|
|
6
6
|
import { BaseProviderAdapter, } from './base-adapter.js';
|
|
7
7
|
import { CodexRuntime } from '../services/codex-runtime.js';
|
|
8
8
|
import { attachPauseFlow } from './codex-pause-flow.js';
|
|
9
|
+
import { attachEventCapture } from './codex-event-capture.js';
|
|
9
10
|
import { REQUEST_TIMEOUT_MS } from '../config/defaults.js';
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Adapter
|
|
@@ -34,11 +35,14 @@ export class CodexAdapter extends BaseProviderAdapter {
|
|
|
34
35
|
async executeSession(handle, prompt, _signal, options) {
|
|
35
36
|
const runtime = this.getRuntime();
|
|
36
37
|
let detachPauseFlow;
|
|
38
|
+
let detachEventCapture;
|
|
37
39
|
let removeExitListener;
|
|
38
40
|
// Clean up listeners only once, regardless of which path triggers it.
|
|
39
41
|
const cleanup = () => {
|
|
40
42
|
detachPauseFlow?.();
|
|
41
43
|
detachPauseFlow = undefined;
|
|
44
|
+
detachEventCapture?.();
|
|
45
|
+
detachEventCapture = undefined;
|
|
42
46
|
removeExitListener?.();
|
|
43
47
|
removeExitListener = undefined;
|
|
44
48
|
};
|
|
@@ -62,6 +66,7 @@ export class CodexAdapter extends BaseProviderAdapter {
|
|
|
62
66
|
const client = runtime
|
|
63
67
|
.getCurrentClient();
|
|
64
68
|
detachPauseFlow = attachPauseFlow(client, handle, threadId);
|
|
69
|
+
detachEventCapture = attachEventCapture(client, handle, threadId);
|
|
65
70
|
// 3b. Listen for app-server crashes. If the process exits while the
|
|
66
71
|
// task is paused (WAITING_ANSWER), mark it failed so the
|
|
67
72
|
// orchestrator doesn't wait on a ghost.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../../../src/execution/codex-adapter.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4EAA4E;AAC5E,uBAAuB;AACvB,8EAA8E;AAK9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EACL,mBAAmB,GAGpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAY3D,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,MAAM,OAAO,YAAa,SAAQ,mBAAmB;IAC1C,EAAE,GAAa,OAAO,CAAC;IACvB,WAAW,GAAG,OAAO,CAAC;IAEd,OAAO,CAAsB;IACtC,OAAO,CAAgB;IAE/B,YAAY,OAA4B;QACtC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,iBAAiB;QACf,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,eAAe;QACb,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,4EAA4E;IAC5E,yBAAyB;IACzB,4EAA4E;IAElE,KAAK,CAAC,cAAc,CAC5B,MAAkB,EAClB,MAAc,EACd,OAAoB,EACpB,OAA6B;QAE7B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,IAAI,eAAyC,CAAC;QAC9C,IAAI,kBAA4C,CAAC;QAEjD,sEAAsE;QACtE,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,eAAe,EAAE,EAAE,CAAC;YACpB,eAAe,GAAG,SAAS,CAAC;YAC5B,kBAAkB,EAAE,EAAE,CAAC;YACvB,kBAAkB,GAAG,SAAS,CAAC;QACjC,CAAC,CAAC;QAEF,IAAI,CAAC;YACH,yBAAyB;YACzB,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,OAAO,CAAC,sBAAsB,CAAC;gBACpE,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,GAAG,EAAE,OAAO,CAAC,GAAG;aACjB,CAAC,CAAC;YACH,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,YAAY,CAEtE,CAAC;YACF,MAAM,QAAQ,GAAG,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAC7D,CAAC;YAED,2DAA2D;YAC3D,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YAE7B,qEAAqE;YACrE,qEAAqE;YACrE,0EAA0E;YAC1E,MAAM,MAAM,GAAI,OAA8D;iBAC3E,gBAAgB,EAAE,CAAC;YACtB,eAAe,GAAG,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../../../src/execution/codex-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppServerClient } from '../services/app-server-client.js';
|
|
2
|
+
import type { TaskHandle } from '../task/task-handle.js';
|
|
3
|
+
/**
|
|
4
|
+
* Listen for `notification` events on the AppServerClient, capture agent
|
|
5
|
+
* messages, command output, file diffs, and errors — writing them to the
|
|
6
|
+
* TaskHandle's output methods. Returns an unsubscribe function.
|
|
7
|
+
*/
|
|
8
|
+
export declare function attachEventCapture(client: AppServerClient, handle: TaskHandle, threadId: string): () => void;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
function asObject(value) {
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
6
|
+
return undefined;
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
function asString(value) {
|
|
10
|
+
return typeof value === 'string' ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
function ts() {
|
|
13
|
+
const d = new Date();
|
|
14
|
+
return [d.getHours(), d.getMinutes(), d.getSeconds()]
|
|
15
|
+
.map(n => String(n).padStart(2, '0'))
|
|
16
|
+
.join(':');
|
|
17
|
+
}
|
|
18
|
+
function truncate(text, max = 200) {
|
|
19
|
+
const oneLine = text.replace(/\n/g, ' ').trim();
|
|
20
|
+
return oneLine.length <= max ? oneLine : oneLine.slice(0, max) + '...';
|
|
21
|
+
}
|
|
22
|
+
function countDiffLines(diff) {
|
|
23
|
+
let added = 0, removed = 0;
|
|
24
|
+
for (const line of diff.split('\n')) {
|
|
25
|
+
if (line.startsWith('+') && !line.startsWith('+++'))
|
|
26
|
+
added++;
|
|
27
|
+
if (line.startsWith('-') && !line.startsWith('---'))
|
|
28
|
+
removed++;
|
|
29
|
+
}
|
|
30
|
+
return { added, removed };
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Attachment: wire event capture onto an AppServerClient + TaskHandle
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Listen for `notification` events on the AppServerClient, capture agent
|
|
37
|
+
* messages, command output, file diffs, and errors — writing them to the
|
|
38
|
+
* TaskHandle's output methods. Returns an unsubscribe function.
|
|
39
|
+
*/
|
|
40
|
+
export function attachEventCapture(client, handle, threadId) {
|
|
41
|
+
const agentMessageBuffers = new Map();
|
|
42
|
+
const commandOutputBuffers = new Map();
|
|
43
|
+
const onNotification = (notification) => {
|
|
44
|
+
const params = asObject(notification.params);
|
|
45
|
+
if (asString(params?.threadId) !== threadId)
|
|
46
|
+
return;
|
|
47
|
+
switch (notification.method) {
|
|
48
|
+
// -- agent message deltas -----------------------------------------------
|
|
49
|
+
case 'item/agentMessage/delta': {
|
|
50
|
+
const itemId = asString(params?.itemId);
|
|
51
|
+
const delta = asString(params?.delta);
|
|
52
|
+
if (!itemId || !delta)
|
|
53
|
+
break;
|
|
54
|
+
const existing = agentMessageBuffers.get(itemId) ?? '';
|
|
55
|
+
agentMessageBuffers.set(itemId, existing + delta);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
// -- command output deltas ----------------------------------------------
|
|
59
|
+
case 'item/commandExecution/outputDelta': {
|
|
60
|
+
const itemId = asString(params?.itemId);
|
|
61
|
+
const delta = asString(params?.delta);
|
|
62
|
+
if (!itemId || !delta)
|
|
63
|
+
break;
|
|
64
|
+
const existing = commandOutputBuffers.get(itemId) ?? '';
|
|
65
|
+
commandOutputBuffers.set(itemId, existing + delta);
|
|
66
|
+
handle.writeOutputFileOnly(delta);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
// -- item completed -----------------------------------------------------
|
|
70
|
+
case 'item/completed': {
|
|
71
|
+
const item = asObject(params?.item);
|
|
72
|
+
const itemType = asString(item?.type);
|
|
73
|
+
const itemId = asString(item?.id);
|
|
74
|
+
if (itemType === 'agentMessage') {
|
|
75
|
+
const text = (itemId ? agentMessageBuffers.get(itemId) : undefined)
|
|
76
|
+
?? asString(item?.text) ?? '';
|
|
77
|
+
handle.writeOutput(`[${ts()}] agent: ${truncate(text)}`);
|
|
78
|
+
handle.writeOutputFileOnly(`[${ts()}] === agent message ===\n${text}`);
|
|
79
|
+
if (itemId)
|
|
80
|
+
agentMessageBuffers.delete(itemId);
|
|
81
|
+
}
|
|
82
|
+
else if (itemType === 'commandExecution') {
|
|
83
|
+
const command = asString(item?.command) ?? 'unknown';
|
|
84
|
+
const exitCode = typeof item?.exitCode === 'number' ? item.exitCode : undefined;
|
|
85
|
+
const durationMs = typeof item?.durationMs === 'number' ? item.durationMs : undefined;
|
|
86
|
+
const duration = durationMs !== undefined ? (durationMs / 1000).toFixed(1) + 's' : '';
|
|
87
|
+
handle.writeOutput(`[${ts()}] cmd: ${command} (exit ${exitCode ?? '?'}${duration ? ', ' + duration : ''})`);
|
|
88
|
+
if (itemId) {
|
|
89
|
+
const accumulated = commandOutputBuffers.get(itemId);
|
|
90
|
+
if (accumulated) {
|
|
91
|
+
handle.writeOutputFileOnly(`[${ts()}] === command output: ${command} ===\n${accumulated}`);
|
|
92
|
+
}
|
|
93
|
+
commandOutputBuffers.delete(itemId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (itemType === 'fileChange') {
|
|
97
|
+
const changes = Array.isArray(item?.changes) ? item.changes : [];
|
|
98
|
+
for (const raw of changes) {
|
|
99
|
+
const change = asObject(raw);
|
|
100
|
+
if (!change)
|
|
101
|
+
continue;
|
|
102
|
+
const path = asString(change.path) ?? 'unknown';
|
|
103
|
+
const diff = asString(change.diff) ?? '';
|
|
104
|
+
const { added, removed } = countDiffLines(diff);
|
|
105
|
+
handle.writeOutput(`[${ts()}] edit: ${path} (+${added} -${removed})`);
|
|
106
|
+
handle.writeOutputFileOnly(`[${ts()}] === file diff: ${path} ===\n${diff}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
// -- turn diff ----------------------------------------------------------
|
|
112
|
+
case 'turn/diff/updated': {
|
|
113
|
+
const diff = asString(params?.diff);
|
|
114
|
+
if (diff) {
|
|
115
|
+
handle.writeOutputFileOnly(`[${ts()}] === turn diff ===\n${diff}`);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
// -- error --------------------------------------------------------------
|
|
120
|
+
case 'error': {
|
|
121
|
+
const errorObj = asObject(params?.error);
|
|
122
|
+
const message = asString(errorObj?.message) ?? asString(params?.message) ?? 'unknown';
|
|
123
|
+
const codexErrorInfo = asString(errorObj?.codexErrorInfo) ?? asString(params?.codexErrorInfo) ?? 'unknown';
|
|
124
|
+
handle.writeOutput(`[${ts()}] ERROR: ${codexErrorInfo} — ${truncate(message)}`);
|
|
125
|
+
handle.writeOutputFileOnly(`[${ts()}] === error ===\n${message}\ncodexErrorInfo: ${codexErrorInfo}`);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
client.on('notification', onNotification);
|
|
133
|
+
return () => {
|
|
134
|
+
client.off('notification', onNotification);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=codex-event-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-event-capture.js","sourceRoot":"","sources":["../../../src/execution/codex-event-capture.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { CodexRuntime } from '../services/codex-runtime.js';
|
|
16
16
|
import type { AppServerClient } from '../services/app-server-client.js';
|
|
17
17
|
import { attachPauseFlow } from './codex-pause-flow.js';
|
|
18
|
+
import { attachEventCapture } from './codex-event-capture.js';
|
|
18
19
|
import { REQUEST_TIMEOUT_MS } from '../config/defaults.js';
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -67,12 +68,15 @@ export class CodexAdapter extends BaseProviderAdapter {
|
|
|
67
68
|
): Promise<void> {
|
|
68
69
|
const runtime = this.getRuntime();
|
|
69
70
|
let detachPauseFlow: (() => void) | undefined;
|
|
71
|
+
let detachEventCapture: (() => void) | undefined;
|
|
70
72
|
let removeExitListener: (() => void) | undefined;
|
|
71
73
|
|
|
72
74
|
// Clean up listeners only once, regardless of which path triggers it.
|
|
73
75
|
const cleanup = () => {
|
|
74
76
|
detachPauseFlow?.();
|
|
75
77
|
detachPauseFlow = undefined;
|
|
78
|
+
detachEventCapture?.();
|
|
79
|
+
detachEventCapture = undefined;
|
|
76
80
|
removeExitListener?.();
|
|
77
81
|
removeExitListener = undefined;
|
|
78
82
|
};
|
|
@@ -101,6 +105,7 @@ export class CodexAdapter extends BaseProviderAdapter {
|
|
|
101
105
|
const client = (runtime as unknown as { getCurrentClient(): AppServerClient })
|
|
102
106
|
.getCurrentClient();
|
|
103
107
|
detachPauseFlow = attachPauseFlow(client, handle, threadId);
|
|
108
|
+
detachEventCapture = attachEventCapture(client, handle, threadId);
|
|
104
109
|
|
|
105
110
|
// 3b. Listen for app-server crashes. If the process exits while the
|
|
106
111
|
// task is paused (WAITING_ANSWER), mark it failed so the
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { AppServerClient } from '../services/app-server-client.js';
|
|
2
|
+
import type { TaskHandle } from '../task/task-handle.js';
|
|
3
|
+
import type { JsonLineNotification } from '../types/codex.js';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
10
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
11
|
+
return value as Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function asString(value: unknown): string | undefined {
|
|
15
|
+
return typeof value === 'string' ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ts(): string {
|
|
19
|
+
const d = new Date();
|
|
20
|
+
return [d.getHours(), d.getMinutes(), d.getSeconds()]
|
|
21
|
+
.map(n => String(n).padStart(2, '0'))
|
|
22
|
+
.join(':');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function truncate(text: string, max = 200): string {
|
|
26
|
+
const oneLine = text.replace(/\n/g, ' ').trim();
|
|
27
|
+
return oneLine.length <= max ? oneLine : oneLine.slice(0, max) + '...';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function countDiffLines(diff: string): { added: number; removed: number } {
|
|
31
|
+
let added = 0, removed = 0;
|
|
32
|
+
for (const line of diff.split('\n')) {
|
|
33
|
+
if (line.startsWith('+') && !line.startsWith('+++')) added++;
|
|
34
|
+
if (line.startsWith('-') && !line.startsWith('---')) removed++;
|
|
35
|
+
}
|
|
36
|
+
return { added, removed };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Attachment: wire event capture onto an AppServerClient + TaskHandle
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Listen for `notification` events on the AppServerClient, capture agent
|
|
45
|
+
* messages, command output, file diffs, and errors — writing them to the
|
|
46
|
+
* TaskHandle's output methods. Returns an unsubscribe function.
|
|
47
|
+
*/
|
|
48
|
+
export function attachEventCapture(
|
|
49
|
+
client: AppServerClient,
|
|
50
|
+
handle: TaskHandle,
|
|
51
|
+
threadId: string,
|
|
52
|
+
): () => void {
|
|
53
|
+
const agentMessageBuffers = new Map<string, string>();
|
|
54
|
+
const commandOutputBuffers = new Map<string, string>();
|
|
55
|
+
|
|
56
|
+
const onNotification = (notification: JsonLineNotification) => {
|
|
57
|
+
const params = asObject(notification.params);
|
|
58
|
+
if (asString(params?.threadId) !== threadId) return;
|
|
59
|
+
|
|
60
|
+
switch (notification.method) {
|
|
61
|
+
// -- agent message deltas -----------------------------------------------
|
|
62
|
+
case 'item/agentMessage/delta': {
|
|
63
|
+
const itemId = asString(params?.itemId);
|
|
64
|
+
const delta = asString(params?.delta);
|
|
65
|
+
if (!itemId || !delta) break;
|
|
66
|
+
const existing = agentMessageBuffers.get(itemId) ?? '';
|
|
67
|
+
agentMessageBuffers.set(itemId, existing + delta);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// -- command output deltas ----------------------------------------------
|
|
72
|
+
case 'item/commandExecution/outputDelta': {
|
|
73
|
+
const itemId = asString(params?.itemId);
|
|
74
|
+
const delta = asString(params?.delta);
|
|
75
|
+
if (!itemId || !delta) break;
|
|
76
|
+
const existing = commandOutputBuffers.get(itemId) ?? '';
|
|
77
|
+
commandOutputBuffers.set(itemId, existing + delta);
|
|
78
|
+
handle.writeOutputFileOnly(delta);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// -- item completed -----------------------------------------------------
|
|
83
|
+
case 'item/completed': {
|
|
84
|
+
const item = asObject(params?.item);
|
|
85
|
+
const itemType = asString(item?.type);
|
|
86
|
+
const itemId = asString(item?.id);
|
|
87
|
+
|
|
88
|
+
if (itemType === 'agentMessage') {
|
|
89
|
+
const text = (itemId ? agentMessageBuffers.get(itemId) : undefined)
|
|
90
|
+
?? asString(item?.text) ?? '';
|
|
91
|
+
handle.writeOutput(`[${ts()}] agent: ${truncate(text)}`);
|
|
92
|
+
handle.writeOutputFileOnly(`[${ts()}] === agent message ===\n${text}`);
|
|
93
|
+
if (itemId) agentMessageBuffers.delete(itemId);
|
|
94
|
+
} else if (itemType === 'commandExecution') {
|
|
95
|
+
const command = asString(item?.command) ?? 'unknown';
|
|
96
|
+
const exitCode = typeof item?.exitCode === 'number' ? item.exitCode : undefined;
|
|
97
|
+
const durationMs = typeof item?.durationMs === 'number' ? item.durationMs : undefined;
|
|
98
|
+
const duration = durationMs !== undefined ? (durationMs / 1000).toFixed(1) + 's' : '';
|
|
99
|
+
handle.writeOutput(
|
|
100
|
+
`[${ts()}] cmd: ${command} (exit ${exitCode ?? '?'}${duration ? ', ' + duration : ''})`,
|
|
101
|
+
);
|
|
102
|
+
if (itemId) {
|
|
103
|
+
const accumulated = commandOutputBuffers.get(itemId);
|
|
104
|
+
if (accumulated) {
|
|
105
|
+
handle.writeOutputFileOnly(
|
|
106
|
+
`[${ts()}] === command output: ${command} ===\n${accumulated}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
commandOutputBuffers.delete(itemId);
|
|
110
|
+
}
|
|
111
|
+
} else if (itemType === 'fileChange') {
|
|
112
|
+
const changes = Array.isArray(item?.changes) ? (item.changes as unknown[]) : [];
|
|
113
|
+
for (const raw of changes) {
|
|
114
|
+
const change = asObject(raw);
|
|
115
|
+
if (!change) continue;
|
|
116
|
+
const path = asString(change.path) ?? 'unknown';
|
|
117
|
+
const diff = asString(change.diff) ?? '';
|
|
118
|
+
const { added, removed } = countDiffLines(diff);
|
|
119
|
+
handle.writeOutput(`[${ts()}] edit: ${path} (+${added} -${removed})`);
|
|
120
|
+
handle.writeOutputFileOnly(`[${ts()}] === file diff: ${path} ===\n${diff}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// -- turn diff ----------------------------------------------------------
|
|
127
|
+
case 'turn/diff/updated': {
|
|
128
|
+
const diff = asString(params?.diff);
|
|
129
|
+
if (diff) {
|
|
130
|
+
handle.writeOutputFileOnly(`[${ts()}] === turn diff ===\n${diff}`);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// -- error --------------------------------------------------------------
|
|
136
|
+
case 'error': {
|
|
137
|
+
const errorObj = asObject(params?.error);
|
|
138
|
+
const message = asString(errorObj?.message) ?? asString(params?.message) ?? 'unknown';
|
|
139
|
+
const codexErrorInfo = asString(errorObj?.codexErrorInfo) ?? asString(params?.codexErrorInfo) ?? 'unknown';
|
|
140
|
+
handle.writeOutput(`[${ts()}] ERROR: ${codexErrorInfo} — ${truncate(message)}`);
|
|
141
|
+
handle.writeOutputFileOnly(
|
|
142
|
+
`[${ts()}] === error ===\n${message}\ncodexErrorInfo: ${codexErrorInfo}`,
|
|
143
|
+
);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
default:
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
client.on('notification', onNotification);
|
|
153
|
+
return () => {
|
|
154
|
+
client.off('notification', onNotification);
|
|
155
|
+
};
|
|
156
|
+
}
|