@minhspark/codex-mcp-bridge 1.10.0 → 1.11.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/src/turn.mjs CHANGED
@@ -1,140 +1,140 @@
1
- const TERMINAL_STATUSES = new Set(["completed", "interrupted", "failed"]);
2
-
3
- function summarizeItem(item) {
4
- switch (item?.type) {
5
- case "agentMessage":
6
- return { kind: "agentMessage", text: item.text ?? "" };
7
- case "commandExecution":
8
- return {
9
- kind: "command",
10
- command: item.command ?? item.parsedCmd ?? null,
11
- exitCode: item.exitCode ?? null,
12
- status: item.status ?? null,
13
- };
14
- case "fileChange":
15
- return {
16
- kind: "fileChange",
17
- files: (item.changes ?? []).map((c) => c.path ?? c.file ?? null).filter(Boolean),
18
- status: item.status ?? null,
19
- };
20
- case "mcpToolCall":
21
- return { kind: "mcpToolCall", server: item.server ?? null, tool: item.tool ?? null };
22
- case "webSearch":
23
- return { kind: "webSearch", query: item.query ?? null };
24
- default:
25
- return null;
26
- }
27
- }
28
-
29
- /**
30
- * Send one user turn into an existing Codex thread and wait for it to finish.
31
- * Returns the agent text plus a compact activity trail.
32
- */
33
- export async function runTurn(client, { threadId, input, timeoutMs = 240000, turnOverrides = {} }) {
34
- const messages = [];
35
- const activity = [];
36
- const errors = [];
37
- const buffered = [];
38
- let turnId = null;
39
- let settled = false;
40
- let resolveDone;
41
-
42
- /**
43
- * Only ever resolved, never rejected. `done` has exactly one consumer, and it
44
- * sits after `turn/start` has already returned - so rejecting it from the
45
- * catch below would reject a promise nobody is awaiting, which Node turns
46
- * into an unhandledRejection and, by default, into process exit. A failing
47
- * `turn/start` (a thread locked by the desktop app is the everyday case) used
48
- * to take the whole MCP server down that way, while the tool handler was
49
- * still busy formatting a tidy error message for a client that no longer had
50
- * a server to talk to.
51
- */
52
- const done = new Promise((resolve) => {
53
- resolveDone = resolve;
54
- });
55
-
56
- const process = (msg) => {
57
- const params = msg.params ?? {};
58
- if (turnId && params.turnId && params.turnId !== turnId) return;
59
-
60
- switch (msg.method) {
61
- case "item/completed": {
62
- const summary = summarizeItem(params.item);
63
- if (!summary) return;
64
- if (summary.kind === "agentMessage") {
65
- if (summary.text.trim()) messages.push(summary.text);
66
- } else {
67
- activity.push(summary);
68
- }
69
- return;
70
- }
71
- case "error": {
72
- errors.push(params.error ?? { message: "unknown error" });
73
- if (!params.willRetry && !settled) {
74
- settled = true;
75
- resolveDone({ status: "failed", error: params.error ?? null });
76
- }
77
- return;
78
- }
79
- case "turn/completed": {
80
- const turn = params.turn ?? {};
81
- if (turnId && turn.id && turn.id !== turnId) return;
82
- if (!TERMINAL_STATUSES.has(turn.status)) return;
83
- if (settled) return;
84
- settled = true;
85
- resolveDone({ status: turn.status, error: turn.error ?? null, durationMs: turn.durationMs ?? null });
86
- return;
87
- }
88
- default:
89
- }
90
- };
91
-
92
- const unsubscribe = client.subscribe(threadId, (msg) => {
93
- if (!turnId) {
94
- buffered.push(msg);
95
- return;
96
- }
97
- process(msg);
98
- });
99
-
100
- const timer = globalThis.setTimeout(() => {
101
- if (settled) return;
102
- settled = true;
103
- resolveDone({ status: "timeout", error: null });
104
- }, timeoutMs);
105
-
106
- const unsubscribeDisconnect = client.subscribeDisconnect(() => {
107
- if (settled) return;
108
- settled = true;
109
- resolveDone({
110
- status: "disconnected",
111
- error: { message: "the app-server connection dropped while the turn was running" },
112
- });
113
- });
114
-
115
- try {
116
- const started = await client.request(
117
- "turn/start",
118
- { threadId, input, ...turnOverrides },
119
- { timeoutMs: Math.min(timeoutMs, 60000) },
120
- );
121
- turnId = started?.turn?.id ?? null;
122
- for (const msg of buffered.splice(0)) process(msg);
123
-
124
- const outcome = await done;
125
- return {
126
- threadId,
127
- turnId,
128
- status: outcome.status,
129
- error: outcome.error,
130
- durationMs: outcome.durationMs ?? null,
131
- text: messages.join("\n\n").trim(),
132
- activity,
133
- errors,
134
- };
135
- } finally {
136
- globalThis.clearTimeout(timer);
137
- unsubscribe();
138
- unsubscribeDisconnect();
139
- }
140
- }
1
+ const TERMINAL_STATUSES = new Set(["completed", "interrupted", "failed"]);
2
+
3
+ function summarizeItem(item) {
4
+ switch (item?.type) {
5
+ case "agentMessage":
6
+ return { kind: "agentMessage", text: item.text ?? "" };
7
+ case "commandExecution":
8
+ return {
9
+ kind: "command",
10
+ command: item.command ?? item.parsedCmd ?? null,
11
+ exitCode: item.exitCode ?? null,
12
+ status: item.status ?? null,
13
+ };
14
+ case "fileChange":
15
+ return {
16
+ kind: "fileChange",
17
+ files: (item.changes ?? []).map((c) => c.path ?? c.file ?? null).filter(Boolean),
18
+ status: item.status ?? null,
19
+ };
20
+ case "mcpToolCall":
21
+ return { kind: "mcpToolCall", server: item.server ?? null, tool: item.tool ?? null };
22
+ case "webSearch":
23
+ return { kind: "webSearch", query: item.query ?? null };
24
+ default:
25
+ return null;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Send one user turn into an existing Codex thread and wait for it to finish.
31
+ * Returns the agent text plus a compact activity trail.
32
+ */
33
+ export async function runTurn(client, { threadId, input, timeoutMs = 240000, turnOverrides = {} }) {
34
+ const messages = [];
35
+ const activity = [];
36
+ const errors = [];
37
+ const buffered = [];
38
+ let turnId = null;
39
+ let settled = false;
40
+ let resolveDone;
41
+
42
+ /**
43
+ * Only ever resolved, never rejected. `done` has exactly one consumer, and it
44
+ * sits after `turn/start` has already returned - so rejecting it from the
45
+ * catch below would reject a promise nobody is awaiting, which Node turns
46
+ * into an unhandledRejection and, by default, into process exit. A failing
47
+ * `turn/start` (a thread locked by the desktop app is the everyday case) used
48
+ * to take the whole MCP server down that way, while the tool handler was
49
+ * still busy formatting a tidy error message for a client that no longer had
50
+ * a server to talk to.
51
+ */
52
+ const done = new Promise((resolve) => {
53
+ resolveDone = resolve;
54
+ });
55
+
56
+ const process = (msg) => {
57
+ const params = msg.params ?? {};
58
+ if (turnId && params.turnId && params.turnId !== turnId) return;
59
+
60
+ switch (msg.method) {
61
+ case "item/completed": {
62
+ const summary = summarizeItem(params.item);
63
+ if (!summary) return;
64
+ if (summary.kind === "agentMessage") {
65
+ if (summary.text.trim()) messages.push(summary.text);
66
+ } else {
67
+ activity.push(summary);
68
+ }
69
+ return;
70
+ }
71
+ case "error": {
72
+ errors.push(params.error ?? { message: "unknown error" });
73
+ if (!params.willRetry && !settled) {
74
+ settled = true;
75
+ resolveDone({ status: "failed", error: params.error ?? null });
76
+ }
77
+ return;
78
+ }
79
+ case "turn/completed": {
80
+ const turn = params.turn ?? {};
81
+ if (turnId && turn.id && turn.id !== turnId) return;
82
+ if (!TERMINAL_STATUSES.has(turn.status)) return;
83
+ if (settled) return;
84
+ settled = true;
85
+ resolveDone({ status: turn.status, error: turn.error ?? null, durationMs: turn.durationMs ?? null });
86
+ return;
87
+ }
88
+ default:
89
+ }
90
+ };
91
+
92
+ const unsubscribe = client.subscribe(threadId, (msg) => {
93
+ if (!turnId) {
94
+ buffered.push(msg);
95
+ return;
96
+ }
97
+ process(msg);
98
+ });
99
+
100
+ const timer = globalThis.setTimeout(() => {
101
+ if (settled) return;
102
+ settled = true;
103
+ resolveDone({ status: "timeout", error: null });
104
+ }, timeoutMs);
105
+
106
+ const unsubscribeDisconnect = client.subscribeDisconnect(() => {
107
+ if (settled) return;
108
+ settled = true;
109
+ resolveDone({
110
+ status: "disconnected",
111
+ error: { message: "the app-server connection dropped while the turn was running" },
112
+ });
113
+ });
114
+
115
+ try {
116
+ const started = await client.request(
117
+ "turn/start",
118
+ { threadId, input, ...turnOverrides },
119
+ { timeoutMs: Math.min(timeoutMs, 60000) },
120
+ );
121
+ turnId = started?.turn?.id ?? null;
122
+ for (const msg of buffered.splice(0)) process(msg);
123
+
124
+ const outcome = await done;
125
+ return {
126
+ threadId,
127
+ turnId,
128
+ status: outcome.status,
129
+ error: outcome.error,
130
+ durationMs: outcome.durationMs ?? null,
131
+ text: messages.join("\n\n").trim(),
132
+ activity,
133
+ errors,
134
+ };
135
+ } finally {
136
+ globalThis.clearTimeout(timer);
137
+ unsubscribe();
138
+ unsubscribeDisconnect();
139
+ }
140
+ }