flockbay 0.10.15 → 0.10.16

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.
Files changed (31) hide show
  1. package/dist/codex/flockbayMcpStdioBridge.cjs +339 -0
  2. package/dist/codex/flockbayMcpStdioBridge.mjs +339 -0
  3. package/dist/{index--o4BPz5o.cjs → index-Cau-_Qvn.cjs} +2683 -609
  4. package/dist/{index-CUp3juDS.mjs → index-DtmFQzXY.mjs} +2684 -611
  5. package/dist/index.cjs +3 -5
  6. package/dist/index.mjs +3 -5
  7. package/dist/lib.cjs +7 -9
  8. package/dist/lib.d.cts +219 -531
  9. package/dist/lib.d.mts +219 -531
  10. package/dist/lib.mjs +7 -9
  11. package/dist/{runCodex-o6PCbHQ7.mjs → runCodex-Di9eHddq.mjs} +263 -42
  12. package/dist/{runCodex-D3eT-TvB.cjs → runCodex-DzP3VUa-.cjs} +264 -43
  13. package/dist/{runGemini-Bt0oEj_g.mjs → runGemini-BS6sBU_V.mjs} +63 -28
  14. package/dist/{runGemini-CBxZp6I7.cjs → runGemini-CpmehDQ2.cjs} +64 -29
  15. package/dist/{types-DGd6ea2Z.mjs → types-CwzNqYEx.mjs} +465 -1142
  16. package/dist/{types-C-jnUdn_.cjs → types-SUAKq-K0.cjs} +466 -1146
  17. package/package.json +1 -1
  18. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +195 -6
  19. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +376 -5
  20. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommandSchema.cpp +731 -0
  21. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp +476 -8
  22. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +1518 -94
  23. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp +7 -4
  24. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp +150 -112
  25. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h +2 -1
  26. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +4 -1
  27. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommandSchema.h +42 -0
  28. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h +21 -0
  29. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +4 -1
  30. package/dist/flockbayScreenshotGate-DJX3Is5d.mjs +0 -136
  31. package/dist/flockbayScreenshotGate-DkxU24cR.cjs +0 -138
@@ -2,7 +2,7 @@
2
2
 
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
- var types = require('./types-C-jnUdn_.cjs');
5
+ var types = require('./types-SUAKq-K0.cjs');
6
6
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
7
7
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
8
8
  var z = require('zod');
@@ -12,15 +12,13 @@ var fs = require('node:fs');
12
12
  var os = require('node:os');
13
13
  var path = require('node:path');
14
14
  var node_child_process = require('node:child_process');
15
- var index = require('./index--o4BPz5o.cjs');
16
- var flockbayScreenshotGate = require('./flockbayScreenshotGate-DkxU24cR.cjs');
15
+ var index = require('./index-Cau-_Qvn.cjs');
17
16
  require('axios');
17
+ require('node:events');
18
+ require('socket.io-client');
18
19
  require('chalk');
19
20
  require('fs');
20
21
  require('node:fs/promises');
21
- require('tweetnacl');
22
- require('node:events');
23
- require('socket.io-client');
24
22
  require('child_process');
25
23
  require('fs/promises');
26
24
  require('crypto');
@@ -29,19 +27,18 @@ require('url');
29
27
  require('node:process');
30
28
  require('os');
31
29
  require('node:net');
32
- require('expo-server-sdk');
33
30
  require('node:readline');
34
31
  require('node:url');
35
32
  require('ps-list');
36
33
  require('cross-spawn');
37
34
  require('tmp');
38
- require('qrcode-terminal');
39
35
  require('open');
40
36
  require('fastify');
41
37
  require('fastify-type-provider-zod');
42
38
  require('@modelcontextprotocol/sdk/server/mcp.js');
43
39
  require('node:http');
44
40
  require('@modelcontextprotocol/sdk/server/streamableHttp.js');
41
+ require('tweetnacl');
45
42
  require('http');
46
43
  require('util');
47
44
 
@@ -1288,6 +1285,27 @@ class CodexMcpClient {
1288
1285
  types.logger.debug("[CodexMCP] Storing conversation for potential resume:", this.conversationId);
1289
1286
  return this.conversationId;
1290
1287
  }
1288
+ /**
1289
+ * Best-effort interrupt for an in-flight Codex request.
1290
+ * Codex MCP does not currently support protocol-level cancellation, so we interrupt the
1291
+ * underlying `codex` process. This should stop streaming and allow the UI abort button
1292
+ * to actually halt the current turn.
1293
+ */
1294
+ interrupt() {
1295
+ const pid = this.transport?.pid ?? null;
1296
+ if (!pid) return false;
1297
+ try {
1298
+ types.logger.debug("[CodexMCP] Interrupt requested; sending SIGINT to child", { pid });
1299
+ process.kill(pid, "SIGINT");
1300
+ return true;
1301
+ } catch (error) {
1302
+ const e = error;
1303
+ if (e?.code !== "ESRCH") {
1304
+ types.logger.debug("[CodexMCP] Failed to SIGINT child process", { pid, error });
1305
+ }
1306
+ return false;
1307
+ }
1308
+ }
1291
1309
  async disconnect() {
1292
1310
  if (!this.connected) return;
1293
1311
  const pid = this.transport?.pid ?? null;
@@ -1333,6 +1351,8 @@ class CodexMcpClient {
1333
1351
  this.sessionId = null;
1334
1352
  this.conversationId = null;
1335
1353
  this.authSnapshot = null;
1354
+ this.reconnectPromise = null;
1355
+ this.client = this.createClient();
1336
1356
  types.logger.debug("[CodexMCP] Disconnected");
1337
1357
  }
1338
1358
  }
@@ -1550,11 +1570,14 @@ class CodexPermissionHandler {
1550
1570
  this.pendingRequests.clear();
1551
1571
  this.aliasToPrimary.clear();
1552
1572
  this.session.updateAgentState((currentState) => {
1553
- const pendingRequests = currentState.requests || {};
1554
- const completedRequests = { ...currentState.completedRequests };
1573
+ const pendingRaw = currentState?.requests;
1574
+ const pendingRequests = pendingRaw && typeof pendingRaw === "object" ? pendingRaw : {};
1575
+ const completedRaw = currentState?.completedRequests;
1576
+ const completedRequests = completedRaw && typeof completedRaw === "object" ? { ...completedRaw } : {};
1555
1577
  for (const [id, request] of Object.entries(pendingRequests)) {
1578
+ const reqObj = request && typeof request === "object" ? request : { value: request };
1556
1579
  completedRequests[id] = {
1557
- ...request,
1580
+ ...reqObj,
1558
1581
  completedAt: Date.now(),
1559
1582
  status: "canceled",
1560
1583
  reason: "Session reset"
@@ -2323,10 +2346,21 @@ async function runCodex(opts) {
2323
2346
  lifecycleStateSince: Date.now(),
2324
2347
  flavor: "codex"
2325
2348
  };
2326
- const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
2349
+ const response = opts.sessionId ? await api.getSessionById(opts.sessionId) : await api.getOrCreateSession({ tag: sessionTag, metadata, state });
2327
2350
  const session = api.sessionSyncClient(response);
2351
+ if (opts.sessionId) {
2352
+ session.updateMetadata((currentMetadata) => ({
2353
+ ...currentMetadata,
2354
+ ...metadata,
2355
+ // Preserve user-facing fields set by other clients.
2356
+ name: currentMetadata?.name,
2357
+ summary: currentMetadata?.summary
2358
+ }));
2359
+ }
2328
2360
  const elicitationHub = new index.ElicitationHub();
2329
2361
  const permissionHandler = new CodexPermissionHandler(session, { elicitationHub });
2362
+ await session.connectAndWait(15e3);
2363
+ session.keepAlive(false, "remote");
2330
2364
  try {
2331
2365
  types.logger.debug(`[START] Reporting session ${response.id} to daemon`);
2332
2366
  const result = await index.notifyDaemonSessionStarted(response.id, metadata);
@@ -2341,8 +2375,10 @@ async function runCodex(opts) {
2341
2375
  const messageQueue = new index.MessageQueue2((mode) => hashCodexSessionModeFromEnhancedMode(mode));
2342
2376
  const autoPrompt = String(process.env.FLOCKBAY_AUTO_PROMPT || "").trim();
2343
2377
  if (autoPrompt) {
2378
+ const autoPromptPermissionModeRaw = String(process.env.FLOCKBAY_AUTO_PROMPT_PERMISSION_MODE || "").trim();
2379
+ const autoPromptPermissionMode = autoPromptPermissionModeRaw === "safe-yolo" || autoPromptPermissionModeRaw === "yolo" || autoPromptPermissionModeRaw === "bypassPermissions" ? autoPromptPermissionModeRaw : "default";
2344
2380
  messageQueue.push(autoPrompt, {
2345
- permissionMode: "default",
2381
+ permissionMode: autoPromptPermissionMode,
2346
2382
  model: void 0,
2347
2383
  appendSystemPrompt: index.PLATFORM_SYSTEM_PROMPT
2348
2384
  });
@@ -2437,13 +2473,46 @@ async function runCodex(opts) {
2437
2473
  types.logger.debug("[codex][handles] Failed to serialize active handles:", error);
2438
2474
  }
2439
2475
  }
2476
+ let wasCreated = false;
2477
+ let currentModeHash = null;
2440
2478
  let abortController = new AbortController();
2441
2479
  let shouldExit = false;
2442
2480
  let storedConversationIdForRecovery = null;
2443
- async function handleAbort() {
2481
+ let abortNote = null;
2482
+ let abortNoteSentToSession = false;
2483
+ let abortRequested = false;
2484
+ function normalizeCancelNote(input) {
2485
+ if (typeof input === "string") return input.trim() ? input.trim() : null;
2486
+ if (!input || typeof input !== "object") return null;
2487
+ const note = typeof input.note === "string" ? String(input.note).trim() : "";
2488
+ if (note) return note;
2489
+ const reason = typeof input.reason === "string" ? String(input.reason).trim() : "";
2490
+ if (reason) return reason;
2491
+ return null;
2492
+ }
2493
+ async function handleAbort(note, options) {
2444
2494
  types.logger.debug("[Codex] Abort requested - stopping current task");
2445
2495
  try {
2496
+ abortRequested = true;
2497
+ const normalizedNote = normalizeCancelNote(note);
2498
+ if (normalizedNote) {
2499
+ abortNote = normalizedNote;
2500
+ abortNoteSentToSession = Boolean(options?.alreadySentToSession);
2501
+ }
2446
2502
  abortController.abort();
2503
+ try {
2504
+ storedConversationIdForRecovery = client.storeConversationForResume() || client.storeSessionForResume();
2505
+ client.interrupt();
2506
+ try {
2507
+ await client.disconnect();
2508
+ } catch (disconnectError) {
2509
+ types.logger.debug("[Codex] Failed to disconnect Codex MCP after interrupt", disconnectError);
2510
+ }
2511
+ wasCreated = false;
2512
+ currentModeHash = null;
2513
+ } catch (error) {
2514
+ types.logger.debug("[Codex] Failed to interrupt Codex process on abort", error);
2515
+ }
2447
2516
  messageQueue.reset();
2448
2517
  permissionHandler.reset();
2449
2518
  reasoningProcessor.abort();
@@ -2481,7 +2550,10 @@ async function runCodex(opts) {
2481
2550
  process.exit(1);
2482
2551
  }
2483
2552
  };
2484
- session.rpcHandlerManager.registerHandler("abort", handleAbort);
2553
+ async function handleCancelGeneration(params) {
2554
+ await handleAbort(params);
2555
+ }
2556
+ session.rpcHandlerManager.registerHandler("cancel-generation", handleCancelGeneration);
2485
2557
  index.registerKillSessionHandler(session.rpcHandlerManager, handleKillSession);
2486
2558
  const messageBuffer = new index.MessageBuffer();
2487
2559
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
@@ -2509,6 +2581,57 @@ async function runCodex(opts) {
2509
2581
  process.stdin.setEncoding("utf8");
2510
2582
  }
2511
2583
  const client = new CodexMcpClient();
2584
+ async function buildReplayPromptFromServerMessages() {
2585
+ try {
2586
+ const rows = await session.listMessages();
2587
+ if (!Array.isArray(rows) || rows.length === 0) return null;
2588
+ const lines = [];
2589
+ const push = (role, text) => {
2590
+ const t = String(text || "").trim();
2591
+ if (!t) return;
2592
+ lines.push(`${role === "user" ? "User" : "Assistant"}: ${t}`);
2593
+ };
2594
+ for (const row of rows.slice(-80)) {
2595
+ const content = row?.content;
2596
+ if (!content || typeof content !== "object") continue;
2597
+ const role = content.role;
2598
+ const payload = content.content;
2599
+ if (role === "user") {
2600
+ if (payload && typeof payload === "object" && payload.type === "text" && typeof payload.text === "string") {
2601
+ push("user", payload.text);
2602
+ }
2603
+ continue;
2604
+ }
2605
+ if (role !== "agent") continue;
2606
+ if (!payload || typeof payload !== "object") continue;
2607
+ if (payload.type === "output") {
2608
+ const data = payload.data;
2609
+ if (data && typeof data === "object" && data.type === "assistant") {
2610
+ const blocks = data.message?.content;
2611
+ if (Array.isArray(blocks)) {
2612
+ const txt = blocks.map((b) => b?.type === "text" && typeof b.text === "string" ? b.text : "").join("").trim();
2613
+ if (txt) push("assistant", txt);
2614
+ }
2615
+ }
2616
+ } else if (payload.type === "codex") {
2617
+ const data = payload.data;
2618
+ if (data && typeof data === "object" && data.type === "message" && typeof data.message === "string") {
2619
+ push("assistant", data.message);
2620
+ }
2621
+ }
2622
+ }
2623
+ const compact = lines.filter((l) => l.length > 0).slice(-24).join("\n");
2624
+ const clipped = compact.length > 1e4 ? compact.slice(compact.length - 1e4) : compact;
2625
+ if (!clipped.trim()) return null;
2626
+ return [
2627
+ "Conversation so far (most recent last). Continue naturally; do not repeat it verbatim unless asked:",
2628
+ clipped
2629
+ ].join("\n");
2630
+ } catch (e) {
2631
+ types.logger.debug("[Codex] Failed to build replay prompt from server messages", e);
2632
+ return null;
2633
+ }
2634
+ }
2512
2635
  function findCodexResumeFile(conversationId) {
2513
2636
  if (!conversationId) return null;
2514
2637
  try {
@@ -2593,7 +2716,7 @@ async function runCodex(opts) {
2593
2716
  if (!output) return;
2594
2717
  const cwdHint = output?.cwd;
2595
2718
  const cwd = typeof cwdHint === "string" && cwdHint.trim().length > 0 ? cwdHint.trim() : process.cwd();
2596
- const detected = flockbayScreenshotGate.detectScreenshotsForGate({ output, cwd });
2719
+ const detected = index.detectScreenshotsForGate({ output, cwd });
2597
2720
  if (detected.paths.length === 0) return;
2598
2721
  if (detected.hasImageBlocks) screenshotGate.hasImageBlocks = true;
2599
2722
  for (const p of detected.paths) {
@@ -2618,9 +2741,13 @@ async function runCodex(opts) {
2618
2741
  Before doing anything else, call \`mcp__flockbay__read_images\` with:
2619
2742
  ${toolArgs}
2620
2743
 
2621
- Then visually inspect EVERY screenshot and report:
2622
- - One short bullet per image ("Image N: ...") describing what you see
2623
- - Whether the screenshots match the user's request
2744
+ Then visually inspect EVERY screenshot and report (this is for YOUR validation, not ceremony):
2745
+ 1) List 2\u20135 concrete acceptance criteria for the user's request.
2746
+ 2) One short bullet per image ("Image N: ...") describing what you see.
2747
+ 3) For each acceptance criterion: mark it as Verified / Not visible / Failed based ONLY on the screenshots.
2748
+ - If "Not visible", say what evidence is missing.
2749
+ - If "Failed", say what is wrong.
2750
+ 4) If anything is not Verified: state the next tool/action you will take to fix or validate it (do not claim completion yet).
2624
2751
 
2625
2752
  Do not run any other tools in this step.
2626
2753
  `);
@@ -2628,6 +2755,28 @@ async function runCodex(opts) {
2628
2755
  client.setPermissionHandler(permissionHandler);
2629
2756
  const execInflight = /* @__PURE__ */ new Set();
2630
2757
  const execAliasToCallId = /* @__PURE__ */ new Map();
2758
+ const execToolCallEmitted = /* @__PURE__ */ new Set();
2759
+ const forceCloseInflightExecCalls = (opts2) => {
2760
+ if (execInflight.size === 0) return;
2761
+ for (const callId of Array.from(execInflight)) {
2762
+ try {
2763
+ session.sendCodexMessage({
2764
+ type: "tool-call-result",
2765
+ callId,
2766
+ output: { ok: !opts2.isError, reason: opts2.reason, missing_exec_command_end: true },
2767
+ is_error: opts2.isError,
2768
+ id: node_crypto.randomUUID()
2769
+ });
2770
+ } catch (err) {
2771
+ types.logger.debug("[codex] Failed to force-close exec tool-call", {
2772
+ callId,
2773
+ error: err instanceof Error ? err.message : String(err)
2774
+ });
2775
+ }
2776
+ }
2777
+ execInflight.clear();
2778
+ execAliasToCallId.clear();
2779
+ };
2631
2780
  client.setHandler((msg) => {
2632
2781
  try {
2633
2782
  const preview = typeof msg?.type === "string" ? msg.type : "unknown";
@@ -2737,8 +2886,7 @@ async function runCodex(opts) {
2737
2886
  const out = [];
2738
2887
  const seen = /* @__PURE__ */ new Set();
2739
2888
  for (const v of raw) {
2740
- if (typeof v !== "string") continue;
2741
- const s = v.trim();
2889
+ const s = typeof v === "string" ? v.trim() : typeof v === "number" && Number.isFinite(v) ? String(v) : typeof v === "bigint" ? String(v) : "";
2742
2890
  if (!s || s === "undefined") continue;
2743
2891
  if (seen.has(s)) continue;
2744
2892
  seen.add(s);
@@ -3062,30 +3210,30 @@ async function runCodex(opts) {
3062
3210
  id: node_crypto.randomUUID()
3063
3211
  });
3064
3212
  }
3065
- if (msg.type === "exec_command_begin" || msg.type === "exec_approval_request") {
3213
+ if (msg.type === "exec_command_begin") {
3066
3214
  const callId = resolveExecCallId(msg, true);
3067
3215
  if (!callId) {
3068
- console.error("[codexbash-diag] Missing call id for exec begin/approval event", {
3069
- type: msg.type,
3216
+ console.error("[codexbash-diag] Missing call id for exec begin event", {
3070
3217
  keys: Object.keys(msg || {})
3071
3218
  });
3072
- throw new Error("Missing call id for exec begin/approval event");
3219
+ throw new Error("Missing call id for exec begin event");
3073
3220
  }
3074
- if (msg.type === "exec_command_begin") {
3075
- try {
3076
- console.error("[codexbash-diag] exec begin callId", { callId, aliases: getCallIdAliases(msg) });
3077
- } catch {
3078
- }
3221
+ try {
3222
+ console.error("[codexbash-diag] exec begin callId", { callId, aliases: getCallIdAliases(msg) });
3223
+ } catch {
3079
3224
  }
3080
3225
  markExecStarted(callId, msg);
3081
- let { call_id, callId: _callId, type, ...inputs } = msg;
3082
- session.sendCodexMessage({
3083
- type: "tool-call",
3084
- name: "CodexBash",
3085
- callId,
3086
- input: inputs,
3087
- id: node_crypto.randomUUID()
3088
- });
3226
+ if (!execToolCallEmitted.has(callId)) {
3227
+ execToolCallEmitted.add(callId);
3228
+ let { call_id, callId: _callId, type, ...inputs } = msg;
3229
+ session.sendCodexMessage({
3230
+ type: "tool-call",
3231
+ name: "CodexBash",
3232
+ callId,
3233
+ input: inputs,
3234
+ id: node_crypto.randomUUID()
3235
+ });
3236
+ }
3089
3237
  }
3090
3238
  if (msg.type === "exec_command_end") {
3091
3239
  const callId = resolveExecCallId(msg, false);
@@ -3100,6 +3248,24 @@ async function runCodex(opts) {
3100
3248
  console.error("[codexbash-diag] exec end callId", { callId, aliases: getCallIdAliases(msg) });
3101
3249
  } catch {
3102
3250
  }
3251
+ if (!execToolCallEmitted.has(callId)) {
3252
+ execToolCallEmitted.add(callId);
3253
+ try {
3254
+ let { call_id: call_id2, callId: _callId2, type: type2, ...inputs } = msg;
3255
+ session.sendCodexMessage({
3256
+ type: "tool-call",
3257
+ name: "CodexBash",
3258
+ callId,
3259
+ input: inputs,
3260
+ id: node_crypto.randomUUID()
3261
+ });
3262
+ } catch (err) {
3263
+ types.logger.debug("[codex] Failed to synthesize exec tool-call begin", {
3264
+ callId,
3265
+ error: err instanceof Error ? err.message : String(err)
3266
+ });
3267
+ }
3268
+ }
3103
3269
  let { call_id, callId: _callId, type, ...output } = msg;
3104
3270
  const sanitized = sanitizeExecCommandEndOutput(output);
3105
3271
  collectScreenshotsForGate({ ...sanitized, cwd: output?.cwd ?? sanitized?.cwd ?? msg?.cwd });
@@ -3174,6 +3340,21 @@ async function runCodex(opts) {
3174
3340
  }
3175
3341
  });
3176
3342
  const flockbayServer = await index.startFlockbayServer(session, { elicitationHub });
3343
+ const unsubscribeUnrealIssue = (() => {
3344
+ const onIssue = flockbayServer?.unreal?.onIssue;
3345
+ if (typeof onIssue !== "function") return null;
3346
+ return onIssue((event) => {
3347
+ try {
3348
+ const kind = typeof event?.kind === "string" ? event.kind : "unreachable";
3349
+ const base = typeof event?.message === "string" && event.message.trim() ? event.message.trim() : "Unreal Editor issue detected.";
3350
+ const msg = kind === "process_exit" ? `Unreal Editor crashed. ${base}` : `Unreal Editor is not reachable. ${base}`;
3351
+ session.sendSessionEvent({ type: "message", message: msg });
3352
+ void handleAbort(msg, { alreadySentToSession: true });
3353
+ } catch (err) {
3354
+ types.logger.debug("[Codex] Failed to handle Unreal issue event", err);
3355
+ }
3356
+ });
3357
+ })();
3177
3358
  const bridgeCommand = path.join(types.projectPath(), "bin", "flockbay-mcp.mjs");
3178
3359
  const mcpServers = {
3179
3360
  flockbay: {
@@ -3207,8 +3388,8 @@ Error: ${message}`,
3207
3388
  return;
3208
3389
  }
3209
3390
  types.logger.debug("[codex]: client.connect done");
3210
- let wasCreated = false;
3211
- let currentModeHash = null;
3391
+ wasCreated = false;
3392
+ currentModeHash = null;
3212
3393
  let pending = null;
3213
3394
  let nextExperimentalResume = null;
3214
3395
  while (!shouldExit) {
@@ -3261,6 +3442,8 @@ Error: ${message}`,
3261
3442
  messageBuffer.addMessage(message.message, "user");
3262
3443
  currentModeHash = message.hash;
3263
3444
  let skipAutoFinalize = false;
3445
+ let turnCleanupExecIsError = false;
3446
+ let turnCleanupExecReason = "turn_cleanup";
3264
3447
  try {
3265
3448
  resetScreenshotGateForTurn();
3266
3449
  const overrides = { approvalPolicy: "untrusted", sandbox: "workspace-write" };
@@ -3300,18 +3483,35 @@ Error: ${message}`,
3300
3483
  ].filter((v) => typeof v === "string" && v.trim().length > 0).join("\n\n")
3301
3484
  );
3302
3485
  let resumeFile = null;
3486
+ let resumeSource = null;
3303
3487
  if (nextExperimentalResume) {
3304
3488
  resumeFile = nextExperimentalResume;
3489
+ resumeSource = "mode-change";
3305
3490
  nextExperimentalResume = null;
3306
3491
  types.logger.debug("[Codex] Using resume file from mode change:", resumeFile);
3307
3492
  } else if (storedConversationIdForRecovery) {
3308
3493
  const recoveryResumeFile = findCodexResumeFile(storedConversationIdForRecovery);
3309
3494
  if (recoveryResumeFile) {
3310
3495
  resumeFile = recoveryResumeFile;
3496
+ resumeSource = "recovery";
3311
3497
  types.logger.debug("[Codex] Using resume file from previous session (error recovery):", resumeFile);
3312
3498
  }
3313
3499
  storedConversationIdForRecovery = null;
3314
3500
  }
3501
+ const replay = await buildReplayPromptFromServerMessages();
3502
+ if (replay) {
3503
+ const escapedUser = String(message.message || "").trim();
3504
+ const replayTrimmed = replay.trimEnd();
3505
+ const needle = escapedUser ? `User: ${escapedUser}` : "";
3506
+ const replayDeduped = needle && replayTrimmed.endsWith(needle) ? replayTrimmed.slice(0, replayTrimmed.length - needle.length).trimEnd() : replayTrimmed;
3507
+ startConfig.prompt = escapedUser ? `${replayDeduped}
3508
+
3509
+ User: ${escapedUser}` : replayDeduped;
3510
+ if (resumeSource === "recovery") {
3511
+ resumeFile = null;
3512
+ resumeSource = null;
3513
+ }
3514
+ }
3315
3515
  if (resumeFile) {
3316
3516
  startConfig.config.experimental_resume = resumeFile;
3317
3517
  }
@@ -3338,10 +3538,19 @@ Error: ${message}`,
3338
3538
  } catch (error) {
3339
3539
  types.logger.warn("Error in codex session:", error);
3340
3540
  const isAbortError = error instanceof Error && error.name === "AbortError";
3341
- if (isAbortError) {
3541
+ const treatAsAbort = abortRequested || isAbortError;
3542
+ if (treatAsAbort) {
3342
3543
  skipAutoFinalize = true;
3343
- messageBuffer.addMessage("Aborted by user", "status");
3344
- session.sendSessionEvent({ type: "message", message: "Aborted by user" });
3544
+ turnCleanupExecIsError = true;
3545
+ turnCleanupExecReason = "turn_aborted";
3546
+ const note = abortNote || "Aborted by user";
3547
+ abortNote = null;
3548
+ const alreadySent = abortNoteSentToSession;
3549
+ abortNoteSentToSession = false;
3550
+ abortRequested = false;
3551
+ messageBuffer.addMessage(note, "status");
3552
+ if (!alreadySent) session.sendSessionEvent({ type: "message", message: note });
3553
+ session.sendSessionEvent({ type: "ready" });
3345
3554
  if (!client.hasActiveSession()) {
3346
3555
  wasCreated = false;
3347
3556
  currentModeHash = null;
@@ -3351,6 +3560,8 @@ Error: ${message}`,
3351
3560
  types.logger.debug("[Codex] Abort completed; keeping active session for next message");
3352
3561
  }
3353
3562
  } else {
3563
+ turnCleanupExecIsError = true;
3564
+ turnCleanupExecReason = "turn_error";
3354
3565
  const errorMessage = error instanceof Error ? error.message : String(error);
3355
3566
  messageBuffer.addMessage(`Error: ${errorMessage}`, "status");
3356
3567
  session.sendSessionEvent({ type: "message", message: `Error: ${errorMessage}` });
@@ -3366,8 +3577,14 @@ Error: ${message}`,
3366
3577
  permissionHandler.reset();
3367
3578
  reasoningProcessor.abort();
3368
3579
  diffProcessor.reset();
3580
+ forceCloseInflightExecCalls({ isError: turnCleanupExecIsError, reason: turnCleanupExecReason });
3581
+ abortRequested = false;
3369
3582
  thinking = false;
3370
3583
  session.keepAlive(thinking, "remote");
3584
+ try {
3585
+ session.flush();
3586
+ } catch {
3587
+ }
3371
3588
  if (!skipAutoFinalize) {
3372
3589
  try {
3373
3590
  const finalizeRes = await index.autoFinalizeCoordinationWorkItem({
@@ -3398,6 +3615,10 @@ Error: ${message}`,
3398
3615
  } finally {
3399
3616
  types.logger.debug("[codex]: Final cleanup start");
3400
3617
  logActiveHandles("cleanup-start");
3618
+ try {
3619
+ if (typeof unsubscribeUnrealIssue === "function") unsubscribeUnrealIssue();
3620
+ } catch {
3621
+ }
3401
3622
  try {
3402
3623
  types.logger.debug("[codex]: sendSessionDeath");
3403
3624
  session.sendSessionDeath();