very-happy-cli 0.2.134 → 0.2.136

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 (45) hide show
  1. package/dist/{AcpBackend-B8KEjDwF.mjs → AcpBackend-C142vP3p.mjs} +2 -2
  2. package/dist/{AcpBackend-CeNp7tgD.cjs → AcpBackend-CRzu0_1Z.cjs} +2 -2
  3. package/dist/{AcpSessionManager-Bu1d05bx.cjs → AcpSessionManager-Ba6WMLXc.cjs} +1 -1
  4. package/dist/{AcpSessionManager-KlDZbEmZ.mjs → AcpSessionManager-CosTbAlR.mjs} +1 -1
  5. package/dist/{agentGuidance-Cg8Lha39.cjs → agentGuidance-6yh_wPwD.cjs} +1 -1
  6. package/dist/{agentGuidance-CPG5Cr5T.mjs → agentGuidance-ASxKHxBP.mjs} +1 -1
  7. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  8. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  9. package/dist/{config-C3e90AIH.cjs → config--cxxmkOd.cjs} +4 -4
  10. package/dist/{config-C4gWfnJX.mjs → config-VqHQ67d8.mjs} +4 -4
  11. package/dist/{index-B1plzL9M.cjs → index-8bwU9d-4.cjs} +1596 -1208
  12. package/dist/{index-jqG8CV0c.cjs → index-BPVQFcKW.cjs} +6 -6
  13. package/dist/{index-CX-zp3FH.cjs → index-BqbtKX0T.cjs} +4 -4
  14. package/dist/{index-BqUOj1zK.mjs → index-CHI7iTa0.mjs} +1594 -1206
  15. package/dist/{index-B6UvB88K.mjs → index-D3vlFjXy.mjs} +7 -7
  16. package/dist/{index-CEDPokKf.mjs → index-vN4Xf0zB.mjs} +4 -4
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-DPmEaHg2.cjs → installTerminalHooks-2POBqVlu.cjs} +4 -4
  20. package/dist/{installTerminalHooks-BT4Dq28Y.mjs → installTerminalHooks-D0YRu_p-.mjs} +4 -4
  21. package/dist/lib.cjs +2 -2
  22. package/dist/lib.d.cts +14 -0
  23. package/dist/lib.d.mts +14 -0
  24. package/dist/lib.mjs +2 -2
  25. package/dist/{limits-D6kiAfXJ.cjs → limits-DRrdJ7gr.cjs} +8 -8
  26. package/dist/{limits-BGeIBBE3.mjs → limits-kjHZUlSp.mjs} +8 -8
  27. package/dist/{mcp-DGZmUpuZ.cjs → mcp-C1JQsM1s.cjs} +4 -4
  28. package/dist/{mcp-BGu_dUaJ.mjs → mcp-CgjEHlky.mjs} +4 -4
  29. package/dist/{runGemini-BPPnulIk.cjs → runGemini-B9Rc-H0G.cjs} +6 -6
  30. package/dist/{runGemini-DGTNwiLo.mjs → runGemini-DufGnnl2.mjs} +6 -6
  31. package/dist/{runOpenClaw-BB3Ivt1S.cjs → runOpenClaw-BsRRKDvF.cjs} +5 -5
  32. package/dist/{runOpenClaw-CxgTFOSM.mjs → runOpenClaw-DAl2nnjB.mjs} +5 -5
  33. package/dist/{send-DHShoBeq.mjs → send-BHquPaeG.mjs} +4 -4
  34. package/dist/{send-Bo6iKuXr.cjs → send-DP6n2J9x.cjs} +4 -4
  35. package/dist/{sessions-CJuwvEr0.mjs → sessions-BtrLzv5U.mjs} +4 -4
  36. package/dist/{sessions-DyC5Attg.cjs → sessions-Q4BR02dG.cjs} +4 -4
  37. package/dist/{spawn-gha6nGx-.cjs → spawn-CMjhlwlD.cjs} +4 -4
  38. package/dist/{spawn-QtKlr8Ig.mjs → spawn-DuQXydqI.mjs} +4 -4
  39. package/dist/{teams-BbzWNV9e.cjs → teams-CkdUH10-.cjs} +4 -4
  40. package/dist/{teams-BF-GxUZc.mjs → teams-D5HxnyaW.mjs} +4 -4
  41. package/dist/{todo-CkJ5UjWs.mjs → todo-BiLIZH4t.mjs} +3 -3
  42. package/dist/{todo-BaNaNZ8c.cjs → todo-CaYedmtq.cjs} +3 -3
  43. package/dist/{types-Cq-tvv9t.mjs → types-BKrZubg8.mjs} +45 -9
  44. package/dist/{types-Dabm17Ln.cjs → types-DbBPNIwf.cjs} +46 -9
  45. package/package.json +8 -8
@@ -3,13 +3,13 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var api = require('./types-Dabm17Ln.cjs');
7
- var persistence = require('./limits-D6kiAfXJ.cjs');
6
+ var api = require('./types-DbBPNIwf.cjs');
7
+ var persistence = require('./limits-DRrdJ7gr.cjs');
8
8
  var spawn = require('cross-spawn');
9
9
  var path = require('node:path');
10
10
  var node_readline = require('node:readline');
11
11
  var node_fs = require('node:fs');
12
- var agentGuidance = require('./agentGuidance-Cg8Lha39.cjs');
12
+ var agentGuidance = require('./agentGuidance-6yh_wPwD.cjs');
13
13
  var promises = require('node:fs/promises');
14
14
  var fs = require('fs/promises');
15
15
  var ink = require('ink');
@@ -342,6 +342,20 @@ class Session {
342
342
  sessionId;
343
343
  mode = "local";
344
344
  thinking = false;
345
+ /**
346
+ * Window after a user-initiated abort during which stray promise rejections
347
+ * from tearing down the SDK query (MCP transports, in-flight tool calls,
348
+ * the aborted streaming generator) are benign teardown noise — NOT a real
349
+ * crash. The process-level unhandledRejection/uncaughtException handlers
350
+ * must not treat them as fatal and force-archive the session (the reported
351
+ * "点终止就 archive" bug). Self-expiring so a genuine later crash still
352
+ * archives normally.
353
+ */
354
+ abortingUntil = 0;
355
+ markAborting = (windowMs = 8e3) => {
356
+ this.abortingUntil = Date.now() + windowMs;
357
+ };
358
+ isAborting = () => Date.now() < this.abortingUntil;
345
359
  /**
346
360
  * Account-encrypted notification producer. Set lazily once we have a
347
361
  * server session id (notifications are addressed per session). May be
@@ -1353,6 +1367,152 @@ async function claudeLocalLauncher(session) {
1353
1367
  return exitReason || { type: "exit", code: 0 };
1354
1368
  }
1355
1369
 
1370
+ const text = (value) => typeof value === "string" && value.length > 0 && value.length <= 1024 && !value.includes("\0") ? value : null;
1371
+ const publicMcpStatus = (servers) => servers.map(({ name, status, serverInfo, scope, tools }) => ({ name, status, serverInfo, scope, tools: tools?.map(({ name: name2 }) => ({ name: name2 })) }));
1372
+ const fingerprint = (result) => JSON.stringify([result.canRewind, [...result.filesChanged ?? []].sort(), result.insertions, result.deletions]);
1373
+ function isRuntimeCheckpointReplay(message) {
1374
+ const m = message;
1375
+ return m.type === "user" && m.isReplay === true && !m.parent_tool_use_id && !(Array.isArray(m.message?.content) && m.message.content.some((block) => block.type === "tool_result"));
1376
+ }
1377
+ function createRuntimeControls(isThinking) {
1378
+ let query = null, generation = node_crypto.randomInt(0, 2 ** 40), activityRevision = 0;
1379
+ const operations = /* @__PURE__ */ new Map();
1380
+ const confirmations = /* @__PURE__ */ new Map();
1381
+ const tasks = /* @__PURE__ */ new Map();
1382
+ const checkpoints = /* @__PURE__ */ new Map();
1383
+ let pending = false;
1384
+ const setQuery = (next) => {
1385
+ for (const operation of operations.values()) if (operation.status === "running") {
1386
+ operation.status = "failed";
1387
+ operation.error = "Claude query ended before this operation completed";
1388
+ }
1389
+ query = next;
1390
+ generation++;
1391
+ pending = false;
1392
+ confirmations.clear();
1393
+ tasks.clear();
1394
+ checkpoints.clear();
1395
+ };
1396
+ function observe(message) {
1397
+ const m = message;
1398
+ if (m.type === "user" || m.type === "assistant") {
1399
+ activityRevision++;
1400
+ confirmations.clear();
1401
+ }
1402
+ const content = typeof m.message?.content === "string" ? m.message.content : Array.isArray(m.message?.content) ? m.message.content.filter((block) => block.type === "text").map((block) => block.text).join(" ") : "";
1403
+ if (isRuntimeCheckpointReplay(message) && text(m.uuid) && content) {
1404
+ checkpoints.set(m.uuid, { id: m.uuid, title: content.slice(0, 120) });
1405
+ if (checkpoints.size > 100) checkpoints.delete(checkpoints.keys().next().value);
1406
+ }
1407
+ if (m.type === "system" && text(m.task_id) && ["task_started", "task_updated", "task_progress", "task_notification"].includes(m.subtype)) {
1408
+ const previous = tasks.get(m.task_id);
1409
+ if (m.subtype !== "task_notification" && ["completed", "failed", "stopped", "cancelled"].includes(String(previous?.status))) return;
1410
+ tasks.set(m.task_id, {
1411
+ ...previous,
1412
+ id: m.task_id,
1413
+ ...text(m.tool_use_id) ? { toolUseId: m.tool_use_id } : {},
1414
+ ...text(m.description) ? { description: m.description } : {},
1415
+ ...text(m.task_type) ? { type: m.task_type } : {},
1416
+ status: m.subtype === "task_notification" ? m.status ?? "completed" : "running"
1417
+ });
1418
+ if (tasks.size > 100) tasks.delete(tasks.keys().next().value);
1419
+ }
1420
+ }
1421
+ function request(input) {
1422
+ const r = input && typeof input === "object" ? input : {};
1423
+ if (r.action === "status") return { queryGeneration: generation, active: !!query, isRunning: isThinking(), canRewind: !!query && !isThinking() && !pending && query.canControl(), busy: pending || !!query && !query.canControl(), tasks: [...tasks.values()], checkpoints: [...checkpoints.values()], operations: [...operations.values()].filter((op2) => op2.queryGeneration === generation) };
1424
+ if (r.action === "operation") {
1425
+ const op2 = operations.get(String(r.operationId));
1426
+ return op2 ? { ...op2 } : { error: "Operation not found" };
1427
+ }
1428
+ const q = query;
1429
+ if (!q) return { error: "No active Claude SDK query" };
1430
+ if (pending || !q.canControl()) return { error: "Claude is waiting for an interaction or another control operation; retry after it finishes" };
1431
+ const action = String(r.action), server = text(r.serverName), task = text(r.taskId), target = text(r.userMessageId);
1432
+ const runGeneration = generation, runRevision = activityRevision;
1433
+ let execute;
1434
+ switch (action) {
1435
+ case "reload-skills":
1436
+ execute = () => q.reloadSkills();
1437
+ break;
1438
+ case "reload-plugins":
1439
+ execute = async () => {
1440
+ const result = await q.reloadPlugins();
1441
+ return { ...result, mcpServers: publicMcpStatus(result.mcpServers ?? []) };
1442
+ };
1443
+ break;
1444
+ case "mcp-status":
1445
+ execute = async () => publicMcpStatus(await q.mcpServerStatus());
1446
+ break;
1447
+ case "mcp-reconnect":
1448
+ if (!server) return { error: "Invalid server name" };
1449
+ execute = () => q.reconnectMcpServer(server);
1450
+ break;
1451
+ case "mcp-toggle":
1452
+ if (!server || typeof r.enabled !== "boolean") return { error: "Invalid server or enabled flag" };
1453
+ execute = () => q.toggleMcpServer(server, r.enabled);
1454
+ break;
1455
+ case "stop-task":
1456
+ if (!task || !tasks.has(task)) return { error: "Unknown task in this query" };
1457
+ execute = () => q.stopTask(task);
1458
+ break;
1459
+ case "background-tasks":
1460
+ if (r.toolUseId !== void 0 && !text(r.toolUseId)) return { error: "Invalid tool use ID" };
1461
+ execute = async () => ({ backgrounded: await q.backgroundTasks(r.toolUseId) });
1462
+ break;
1463
+ case "rewind-preview":
1464
+ if (isThinking() || !target || !checkpoints.has(target)) return { error: "Rewind requires an idle query and a known user checkpoint" };
1465
+ execute = async () => {
1466
+ const result = await q.rewindFiles(target, { dryRun: true });
1467
+ if (!result.canRewind) return result;
1468
+ if (query !== q || generation !== runGeneration || activityRevision !== runRevision || isThinking() || !q.canControl()) throw Error("Query changed; preview again");
1469
+ const token = node_crypto.randomUUID();
1470
+ confirmations.set(token, { generation, revision: activityRevision, userMessageId: target, fingerprint: fingerprint(result), expires: Date.now() + 3e5 });
1471
+ return { ...result, confirmationToken: token };
1472
+ };
1473
+ break;
1474
+ case "rewind-apply": {
1475
+ const confirmation = confirmations.get(String(r.confirmationToken));
1476
+ if (isThinking() || !confirmation || confirmation.generation !== generation || confirmation.expires < Date.now()) return { error: "Preview expired or query is running; preview again" };
1477
+ confirmations.delete(String(r.confirmationToken));
1478
+ execute = async () => {
1479
+ const fresh = await q.rewindFiles(confirmation.userMessageId, { dryRun: true });
1480
+ if (query !== q || generation !== runGeneration || activityRevision !== confirmation.revision || isThinking() || !q.canControl() || fingerprint(fresh) !== confirmation.fingerprint) throw Error("Files or query changed; preview again");
1481
+ return q.rewindFiles(confirmation.userMessageId, { dryRun: false });
1482
+ };
1483
+ break;
1484
+ }
1485
+ default:
1486
+ return { error: "Unknown Claude control action" };
1487
+ }
1488
+ pending = true;
1489
+ const op = { id: node_crypto.randomUUID(), queryGeneration: runGeneration, action, status: "running" };
1490
+ operations.set(op.id, op);
1491
+ if (operations.size > 50) for (const [id, old] of operations) {
1492
+ if (old.status !== "running") {
1493
+ operations.delete(id);
1494
+ break;
1495
+ }
1496
+ }
1497
+ void Promise.resolve().then(() => {
1498
+ if (query !== q || generation !== runGeneration || !q.canControl()) throw Error("Claude query changed or is waiting for an interaction");
1499
+ return execute();
1500
+ }).then((result) => {
1501
+ if (generation !== runGeneration || op.status !== "running") return;
1502
+ op.status = "completed";
1503
+ op.result = result ?? null;
1504
+ }, (error) => {
1505
+ if (generation !== runGeneration || op.status !== "running") return;
1506
+ op.status = "failed";
1507
+ op.error = error instanceof Error ? error.message : "Claude control failed";
1508
+ }).finally(() => {
1509
+ if (generation === runGeneration) pending = false;
1510
+ });
1511
+ return { operationId: op.id };
1512
+ }
1513
+ return { setQuery, observe, request };
1514
+ }
1515
+
1356
1516
  class MessageBuffer {
1357
1517
  messages = [];
1358
1518
  listeners = [];
@@ -1622,6 +1782,9 @@ function query(params) {
1622
1782
  tools: opts?.tools,
1623
1783
  persistSession: opts?.persistSession,
1624
1784
  includePartialMessages: opts?.includePartialMessages,
1785
+ forwardSubagentText: true,
1786
+ enableFileCheckpointing: true,
1787
+ extraArgs: { "replay-user-messages": null },
1625
1788
  onElicitation: opts?.onElicitation,
1626
1789
  onUserDialog: opts?.onUserDialog,
1627
1790
  supportedDialogKinds: opts?.onUserDialog ? opts.supportedDialogKinds : void 0
@@ -2064,6 +2227,20 @@ async function claudeRemote(opts) {
2064
2227
  content: initial.message
2065
2228
  }
2066
2229
  });
2230
+ let callbackDepth = 0;
2231
+ const guardCallback = (callback) => (async (...args) => {
2232
+ callbackDepth++;
2233
+ try {
2234
+ return await callback(...args);
2235
+ } finally {
2236
+ setImmediate(() => {
2237
+ callbackDepth--;
2238
+ });
2239
+ }
2240
+ });
2241
+ if (sdkOptions.canCallTool) sdkOptions.canCallTool = guardCallback(sdkOptions.canCallTool);
2242
+ if (sdkOptions.onElicitation) sdkOptions.onElicitation = guardCallback(sdkOptions.onElicitation);
2243
+ if (sdkOptions.onUserDialog) sdkOptions.onUserDialog = guardCallback(sdkOptions.onUserDialog);
2067
2244
  const response = query({
2068
2245
  prompt: messages,
2069
2246
  options: sdkOptions
@@ -2084,6 +2261,15 @@ async function claudeRemote(opts) {
2084
2261
  }
2085
2262
  if (opts.onQueryReady) {
2086
2263
  opts.onQueryReady({
2264
+ canControl: () => callbackDepth === 0,
2265
+ reloadSkills: () => response.reloadSkills(),
2266
+ reloadPlugins: () => response.reloadPlugins(),
2267
+ mcpServerStatus: () => response.mcpServerStatus(),
2268
+ reconnectMcpServer: (name) => response.reconnectMcpServer(name),
2269
+ toggleMcpServer: (name, enabled) => response.toggleMcpServer(name, enabled),
2270
+ stopTask: (id) => response.stopTask(id),
2271
+ backgroundTasks: (id) => response.backgroundTasks(id),
2272
+ rewindFiles: (id, options) => response.rewindFiles(id, options),
2087
2273
  setPermissionMode: (mode2) => response.setPermissionMode(mode2),
2088
2274
  setModel: (model) => response.setModel(modelTarget(model)),
2089
2275
  interrupt: async () => {
@@ -2727,7 +2913,7 @@ class PermissionHandler {
2727
2913
  } else if (pending.kind === "elicitation") {
2728
2914
  pending.resolve(message.approved ? { action: "accept", content: elicitationContent(message.updatedInput) } : { action: message.decision === "abort" ? "cancel" : "decline" });
2729
2915
  } else {
2730
- pending.resolve(message.approved ? { behavior: "completed", result: message.updatedInput?.result ?? "retry_fallback" } : { behavior: "cancelled" });
2916
+ pending.resolve(message.approved && pending.request.dialogKind === "refusal_fallback_prompt" && (message.updatedInput?.result === void 0 || message.updatedInput.result === "retry_fallback") ? { behavior: "completed", result: "retry_fallback" } : { behavior: "cancelled" });
2731
2917
  }
2732
2918
  this.recordCompletedRequest(id, effectiveResponse);
2733
2919
  this.scheduleDeferredPermissionModeUpdate();
@@ -3189,6 +3375,7 @@ class SDKToLogConverter {
3189
3375
  }
3190
3376
  }
3191
3377
 
3378
+ const transcriptSystemSubtypes = /* @__PURE__ */ new Set(["task_started", "task_updated", "task_progress", "task_notification"]);
3192
3379
  class OutgoingMessageQueue {
3193
3380
  constructor(sendFunction) {
3194
3381
  this.sendFunction = sendFunction;
@@ -3270,7 +3457,7 @@ class OutgoingMessageQueue {
3270
3457
  break;
3271
3458
  }
3272
3459
  if (!item.sent) {
3273
- if (item.logMessage.type !== "system") {
3460
+ if (item.logMessage.type !== "system" || transcriptSystemSubtypes.has(item.logMessage.subtype)) {
3274
3461
  this.sendFunction(item.logMessage);
3275
3462
  }
3276
3463
  item.sent = true;
@@ -3752,6 +3939,18 @@ function createTurnSteeringController() {
3752
3939
  throw error;
3753
3940
  }
3754
3941
  },
3942
+ /**
3943
+ * Interrupt the current turn WITHOUT injecting a follow-up message —
3944
+ * used by the stop button. Like steer it keeps the streaming query
3945
+ * alive (same process, same claudeSessionId); unlike steer it does not
3946
+ * set the steering flag, so the turn simply ends and the query waits
3947
+ * for the next user message. Returns false when no query is attached.
3948
+ */
3949
+ async interruptTurn() {
3950
+ if (!interrupt) return false;
3951
+ await interrupt();
3952
+ return true;
3953
+ },
3755
3954
  consumeReady() {
3756
3955
  if (!steering) return false;
3757
3956
  steering = false;
@@ -3860,6 +4059,7 @@ class LaunchModeGate {
3860
4059
  }
3861
4060
  }
3862
4061
 
4062
+ const ABORT_INTERRUPT_GRACE_MS = 4e3;
3863
4063
  async function claudeRemoteLauncher(session, onPermissionModeChange, onEffectivePermissionMode, onPromptFinalized) {
3864
4064
  persistence.logger.debug("[claudeRemoteLauncher] Starting remote launcher");
3865
4065
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
@@ -3898,6 +4098,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3898
4098
  let abortController = null;
3899
4099
  let abortFuture = null;
3900
4100
  const turnSteering = createTurnSteeringController();
4101
+ const runtimeControls = createRuntimeControls(() => session.thinking);
4102
+ session.client.rpcHandlerManager.registerHandler("claude-runtime-control", async (input) => runtimeControls.request(input));
3901
4103
  async function abort() {
3902
4104
  if (abortController && !abortController.signal.aborted) {
3903
4105
  abortController.abort();
@@ -3906,9 +4108,29 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3906
4108
  }
3907
4109
  async function doAbort() {
3908
4110
  persistence.logger.debug("[remote]: doAbort");
4111
+ session.markAborting();
3909
4112
  session.onAbort();
4113
+ if (session.thinking) {
4114
+ const interrupted = await turnSteering.interruptTurn().catch((e) => {
4115
+ persistence.logger.debug("[remote]: interruptTurn threw, falling back to hard abort", e);
4116
+ return false;
4117
+ });
4118
+ if (interrupted && await waitForThinkingToStop(ABORT_INTERRUPT_GRACE_MS)) {
4119
+ persistence.logger.debug("[remote]: interrupt stopped the turn gracefully; query kept alive");
4120
+ return;
4121
+ }
4122
+ persistence.logger.debug("[remote]: interrupt did not stop the turn in time; escalating to hard abort");
4123
+ }
3910
4124
  await abort();
3911
4125
  }
4126
+ async function waitForThinkingToStop(graceMs) {
4127
+ const deadline = Date.now() + graceMs;
4128
+ while (Date.now() < deadline) {
4129
+ if (!session.thinking) return true;
4130
+ await new Promise((resolve) => setTimeout(resolve, 100));
4131
+ }
4132
+ return !session.thinking;
4133
+ }
3912
4134
  async function doSwitch() {
3913
4135
  persistence.logger.debug("[remote]: doSwitch");
3914
4136
  if (!exitReason) {
@@ -3958,6 +4180,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3958
4180
  let ongoingToolCalls = /* @__PURE__ */ new Map();
3959
4181
  let notifiedQuestionToolCalls = /* @__PURE__ */ new Set();
3960
4182
  function onMessage(message) {
4183
+ runtimeControls.observe(message);
4184
+ if (isRuntimeCheckpointReplay(message)) return;
3961
4185
  if (message.type === "user" && isClaudeInterruptSentinelContent(message.message.content)) {
3962
4186
  sdkToLogConverter.convert(message);
3963
4187
  return;
@@ -4180,6 +4404,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4180
4404
  if (metadata.permissionMode) onEffectivePermissionMode?.(metadata.permissionMode);
4181
4405
  },
4182
4406
  onQueryReady: (q) => {
4407
+ runtimeControls.setQuery(q);
4183
4408
  turnSteering.setInterrupt(q.interrupt);
4184
4409
  session.setSteerHandler(async (input) => {
4185
4410
  if (!modeGate.matches(input.mode)) return false;
@@ -4269,6 +4494,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4269
4494
  continue;
4270
4495
  }
4271
4496
  } finally {
4497
+ runtimeControls.setQuery(null);
4272
4498
  turnSteering.reset();
4273
4499
  session.setSteerHandler(null);
4274
4500
  livePermissionModeHandler = null;
@@ -5319,6 +5545,80 @@ ${typeLabels[type] || type}:`));
5319
5545
  console.log(chalk.green("\n\u2705 Doctor diagnosis complete!\n"));
5320
5546
  }
5321
5547
 
5548
+ const AGENT_CHECK_INTERVAL = 6 * 60 * 60 * 1e3;
5549
+ function stableVersion(value) {
5550
+ const match = value.trim().match(/(?:^|\s|v)(\d+\.\d+\.\d+)([-+][\w.-]+)?(?=\s|$)/);
5551
+ return match && !match[2] ? match[1] : null;
5552
+ }
5553
+ function newerVersion(current, latest) {
5554
+ const a = current.split(".").map(Number), b = latest.split(".").map(Number);
5555
+ for (let i = 0; i < 3; i++) {
5556
+ if (b[i] !== a[i]) return b[i] > a[i];
5557
+ }
5558
+ return false;
5559
+ }
5560
+ const runVersionCommand = (command, args, shell) => new Promise((resolve, reject) => {
5561
+ node_child_process.execFile(
5562
+ command,
5563
+ args,
5564
+ { timeout: 5e3, maxBuffer: 32 * 1024, windowsHide: true, shell },
5565
+ (error, stdout) => error ? reject(error) : resolve(stdout)
5566
+ );
5567
+ });
5568
+ async function probeAgentVersion(command, platform = process.platform, run = runVersionCommand) {
5569
+ if (platform === "win32") {
5570
+ try {
5571
+ await run("where.exe", [command], false);
5572
+ } catch (error) {
5573
+ return { version: null, missing: error.code === 1 };
5574
+ }
5575
+ }
5576
+ try {
5577
+ return { version: stableVersion(await run(command, ["--version"], platform === "win32")), missing: false };
5578
+ } catch (error) {
5579
+ return { version: null, missing: error.code === "ENOENT" };
5580
+ }
5581
+ }
5582
+ async function registryVersion(name) {
5583
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, { signal: AbortSignal.timeout(5e3) });
5584
+ if (!response.ok) throw new Error("Registry unavailable");
5585
+ const data = await response.json();
5586
+ return typeof data.version === "string" ? stableVersion(data.version) : null;
5587
+ }
5588
+ async function detectAgentVersions(deps = { probe: probeAgentVersion, registryVersion }, now = Date.now()) {
5589
+ const agents = await Promise.all(api.AGENT_VERSION_SOURCES.map(async (source) => {
5590
+ const local = source.command ? await deps.probe(source.command).catch(() => ({ version: null, missing: false })) : { version: stableVersion(persistence.packageJson.dependencies["@anthropic-ai/claude-agent-sdk"]), missing: false };
5591
+ if (local.missing) return { id: source.id, installed: null, latest: null, status: "not-installed" };
5592
+ const latest = await deps.registryVersion(source.package).catch(() => null);
5593
+ const installed = local.version;
5594
+ const status = !installed || !latest ? "unknown" : newerVersion(installed, latest) ? "update-available" : "current";
5595
+ return { id: source.id, installed, latest, status };
5596
+ }));
5597
+ return { checkedAt: now, daemonPid: process.pid, agents };
5598
+ }
5599
+ function startAgentVersionChecks(publish, onError, detect = detectAgentVersions) {
5600
+ let stopped = false, pending = false;
5601
+ async function refresh() {
5602
+ if (stopped || pending) return;
5603
+ pending = true;
5604
+ try {
5605
+ const snapshot = await detect();
5606
+ if (!stopped) await publish(snapshot);
5607
+ } catch (error) {
5608
+ onError(error);
5609
+ } finally {
5610
+ pending = false;
5611
+ }
5612
+ }
5613
+ void refresh();
5614
+ const timer = setInterval(() => void refresh(), AGENT_CHECK_INTERVAL);
5615
+ timer.unref();
5616
+ return () => {
5617
+ stopped = true;
5618
+ clearInterval(timer);
5619
+ };
5620
+ }
5621
+
5322
5622
  function sanitizeSpawnModel(value) {
5323
5623
  if (typeof value !== "string") return null;
5324
5624
  return /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}(\[1m\])?$/i.test(value) ? value : null;
@@ -7246,10 +7546,13 @@ function buildResumeLaunch(session, options = {}) {
7246
7546
  const { metadata } = session;
7247
7547
  const flavor = resolveFlavor(metadata);
7248
7548
  if (flavor === "codex") {
7249
- if (!metadata.codexThreadId) {
7250
- throw new Error(`Happy session ${session.id} is missing its Codex thread ID.`);
7549
+ const args = ["codex"];
7550
+ if (options.freshConversation) ; else {
7551
+ if (!metadata.codexThreadId) {
7552
+ throw new Error(`Happy session ${session.id} is missing its Codex thread ID.`);
7553
+ }
7554
+ args.push("--resume", metadata.codexThreadId);
7251
7555
  }
7252
- const args = ["codex", "--resume", metadata.codexThreadId];
7253
7556
  if (options.startedBy) {
7254
7557
  args.push("--started-by", options.startedBy);
7255
7558
  }
@@ -7259,9 +7562,6 @@ function buildResumeLaunch(session, options = {}) {
7259
7562
  };
7260
7563
  }
7261
7564
  if (flavor === "claude") {
7262
- if (!metadata.claudeSessionId) {
7263
- throw new Error(`Happy session ${session.id} is missing its Claude session ID.`);
7264
- }
7265
7565
  const args = ["claude"];
7266
7566
  if (options.claudeStartingMode) {
7267
7567
  args.push("--happy-starting-mode", options.claudeStartingMode);
@@ -7269,7 +7569,12 @@ function buildResumeLaunch(session, options = {}) {
7269
7569
  if (options.startedBy) {
7270
7570
  args.push("--started-by", options.startedBy);
7271
7571
  }
7272
- args.push("--resume", metadata.claudeSessionId);
7572
+ if (options.freshConversation) ; else {
7573
+ if (!metadata.claudeSessionId) {
7574
+ throw new Error(`Happy session ${session.id} is missing its Claude session ID.`);
7575
+ }
7576
+ args.push("--resume", metadata.claudeSessionId);
7577
+ }
7273
7578
  return {
7274
7579
  cwd: metadata.path,
7275
7580
  args
@@ -7750,7 +8055,7 @@ function parseJsonObject(text) {
7750
8055
  return null;
7751
8056
  }
7752
8057
  }
7753
- function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-B1plzL9M.cjs', document.baseURI).href))).resolve(s)) {
8058
+ function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-8bwU9d-4.cjs', document.baseURI).href))).resolve(s)) {
7754
8059
  const platformArch = `${platform}-${arch}`;
7755
8060
  const candidates = [
7756
8061
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -10568,12 +10873,18 @@ async function startDaemon() {
10568
10873
  cwdExists: (p) => fs$1.existsSync(p),
10569
10874
  conversationExists: (cwd, claudeSessionId) => fs$1.existsSync(path$1.join(api.getProjectPath(cwd), `${claudeSessionId}.jsonl`))
10570
10875
  });
10876
+ let freshConversation = false;
10571
10877
  if (!precheck.ok) {
10572
- return { type: "error", errorMessage: `resume-precheck:${precheck.reason}: ${precheck.detail}` };
10878
+ if (precheck.reason === "conversation-missing") {
10879
+ persistence.logger.debug(`[DAEMON RUN] resume: transcript for ${happySessionId} is gone (${precheck.detail}); starting a fresh conversation in ${metadata.path}`);
10880
+ freshConversation = true;
10881
+ } else {
10882
+ return { type: "error", errorMessage: `resume-precheck:${precheck.reason}: ${precheck.detail}` };
10883
+ }
10573
10884
  }
10574
10885
  const launch = buildResumeLaunch(
10575
10886
  { id: happySessionId, active: true, metadata },
10576
- { startedBy: "daemon", claudeStartingMode: "remote" }
10887
+ { startedBy: "daemon", claudeStartingMode: "remote", freshConversation }
10577
10888
  );
10578
10889
  const model = sanitizeResumeModel(options?.model);
10579
10890
  if (model) launch.args.push("--model", model);
@@ -10991,6 +11302,10 @@ async function startDaemon() {
10991
11302
  persistence.logger.warn(`[DAEMON RUN] Ignoring invalid HAPPY_CLI_UPDATE_CHECK_INTERVAL; using ${api.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS}ms`);
10992
11303
  }
10993
11304
  const cliUpdateInterval = setInterval(() => void refreshCliUpdate(), cliUpdateIntervalMs);
11305
+ const stopAgentVersionChecks = startAgentVersionChecks(
11306
+ (snapshot) => apiMachine.updateDaemonState((state) => ({ ...state, status: state?.status ?? "running", agentVersions: snapshot })),
11307
+ (error) => persistence.logger.debug("[DAEMON RUN] Agent version check failed", error)
11308
+ );
10994
11309
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
10995
11310
  const restartOnStaleVersionAndHeartbeat = setInterval(serialTask(async () => {
10996
11311
  try {
@@ -11031,6 +11346,7 @@ async function startDaemon() {
11031
11346
  teamWorker?.stop();
11032
11347
  clearInterval(restartOnStaleVersionAndHeartbeat);
11033
11348
  clearInterval(cliUpdateInterval);
11349
+ stopAgentVersionChecks();
11034
11350
  claudeAuthService.stop();
11035
11351
  apiMachine.shutdown();
11036
11352
  await stopControlServer();
@@ -11092,6 +11408,7 @@ async function startDaemon() {
11092
11408
  persistence.logger.debug("[DAEMON RUN] Health check interval cleared");
11093
11409
  }
11094
11410
  clearInterval(cliUpdateInterval);
11411
+ stopAgentVersionChecks();
11095
11412
  await apiMachine.updateDaemonState((state) => ({
11096
11413
  ...state,
11097
11414
  status: "shutting-down",
@@ -12181,7 +12498,7 @@ function registerSideQuestionHandler(rpc, deps) {
12181
12498
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
12182
12499
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
12183
12500
  const run = deps.run ?? (async (input) => {
12184
- const { query } = await Promise.resolve().then(function () { return require('./index-CX-zp3FH.cjs'); });
12501
+ const { query } = await Promise.resolve().then(function () { return require('./index-BqbtKX0T.cjs'); });
12185
12502
  return runSideQuestion(query, input);
12186
12503
  });
12187
12504
  const slots = /* @__PURE__ */ new Map();
@@ -12283,7 +12600,7 @@ const MAX_TITLE_CHARS = 60;
12283
12600
  const GENERATION_TIMEOUT_MS = 3e4;
12284
12601
  function resolveClaudeBinary() {
12285
12602
  try {
12286
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-B1plzL9M.cjs', document.baseURI).href)));
12603
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-8bwU9d-4.cjs', document.baseURI).href)));
12287
12604
  const utilsPath = path.resolve(path.join(api.projectPath(), "scripts", "claude_version_utils.cjs"));
12288
12605
  const { getClaudeCliPath } = require$1(utilsPath);
12289
12606
  const path$1 = getClaudeCliPath();
@@ -12744,7 +13061,7 @@ async function runClaude(credentials, options = {}) {
12744
13061
  // a brand-new session cannot pick a PDF until after sending once.
12745
13062
  attachmentKinds: [...CLAUDE_ATTACHMENT_KINDS],
12746
13063
  queueCancellation: true,
12747
- capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2", "claude-btw-v1"],
13064
+ capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2", "claude-btw-v1", "claude-runtime-controls-v1"],
12748
13065
  // Effective mode this process enforces. Kept current by
12749
13066
  // publishPermissionMode below; the web renders it instead of guessing.
12750
13067
  permissionMode: mapToClaudeMode(initialPermissionMode ?? "default"),
@@ -13323,10 +13640,18 @@ async function runClaude(credentials, options = {}) {
13323
13640
  });
13324
13641
  process.on("uncaughtException", (error) => {
13325
13642
  persistence.logger.debug("[START] Uncaught exception:", error);
13643
+ if (currentSession?.isAborting()) {
13644
+ persistence.logger.debug("[START] Uncaught exception during abort teardown \u2014 swallowing, not archiving");
13645
+ return;
13646
+ }
13326
13647
  void cleanup({ archive: true });
13327
13648
  });
13328
13649
  process.on("unhandledRejection", (reason) => {
13329
13650
  persistence.logger.debug("[START] Unhandled rejection:", reason);
13651
+ if (currentSession?.isAborting()) {
13652
+ persistence.logger.debug("[START] Unhandled rejection during abort teardown \u2014 swallowing, not archiving");
13653
+ return;
13654
+ }
13330
13655
  void cleanup({ archive: true });
13331
13656
  });
13332
13657
  registerKillSessionHandler(session.rpcHandlerManager, () => cleanup({ archive: true }), session);
@@ -14501,9 +14826,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
14501
14826
  };
14502
14827
  }
14503
14828
 
14504
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-B1plzL9M.cjs', document.baseURI).href)));
14829
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-8bwU9d-4.cjs', document.baseURI).href)));
14505
14830
  const __dirname$1 = path.dirname(__filename$1);
14506
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-B1plzL9M.cjs', document.baseURI).href)));
14831
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-8bwU9d-4.cjs', document.baseURI).href)));
14507
14832
  const PRISMA_QUERY_ENGINE_FILES = {
14508
14833
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
14509
14834
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -14925,1269 +15250,1325 @@ async function ensureDaemonRunning() {
14925
15250
  persistence.logger.debug(`Happy background service did not become ready within ${DAEMON_READY_TIMEOUT_MS}ms; continuing anyway`);
14926
15251
  }
14927
15252
 
14928
- function registerAgentAttachmentDownloads(session) {
14929
- session.onFileEvent((fileEvent) => {
14930
- const attachment = fileEvent.content.data.ev;
14931
- const download = (async () => {
14932
- try {
14933
- const data = await session.downloadAndDecryptAttachment(attachment.ref);
14934
- if (!data) throw new Error("Attachment decryption failed");
14935
- return { data, mimeType: attachment.mimeType ?? "image/jpeg", name: attachment.name };
14936
- } catch {
14937
- session.sendSessionEvent({ type: "message", message: `Could not download attachment: ${attachment.name}. Please attach it again.` });
14938
- return null;
14939
- }
14940
- })();
14941
- session.trackAttachmentDownload(download);
14942
- });
15253
+ function getStartedSubagents(state) {
15254
+ return state.startedSubagents ?? /* @__PURE__ */ new Set();
14943
15255
  }
14944
-
14945
- class BasePermissionHandler {
14946
- pendingRequests = /* @__PURE__ */ new Map();
14947
- session;
14948
- isResetting = false;
14949
- constructor(session) {
14950
- this.session = session;
14951
- this.setupRpcHandler();
15256
+ function getActiveSubagents(state) {
15257
+ return state.activeSubagents ?? /* @__PURE__ */ new Set();
15258
+ }
15259
+ function getProviderSubagentToSessionSubagent(state) {
15260
+ return state.providerSubagentToSessionSubagent ?? /* @__PURE__ */ new Map();
15261
+ }
15262
+ function maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes) {
15263
+ if (!subagent || startedSubagents.has(subagent)) {
15264
+ return;
14952
15265
  }
14953
- /**
14954
- * Update the session reference (used after offline reconnection swaps sessions).
14955
- * This is critical for avoiding stale session references after onSessionSwap.
14956
- */
14957
- updateSession(newSession) {
14958
- persistence.logger.debug(`${this.getLogPrefix()} Session reference updated`);
14959
- this.session = newSession;
14960
- this.setupRpcHandler();
15266
+ envelopes.push(api.createEnvelope("agent", { t: "start" }, { ...opts, subagent }));
15267
+ startedSubagents.add(subagent);
15268
+ activeSubagents.add(subagent);
15269
+ }
15270
+ function emitSubagentStops(opts, startedSubagents, activeSubagents) {
15271
+ const envelopes = [];
15272
+ for (const subagent of activeSubagents) {
15273
+ envelopes.push(api.createEnvelope("agent", { t: "stop" }, { ...opts, subagent }));
14961
15274
  }
14962
- /**
14963
- * Setup RPC handler for permission responses.
14964
- */
14965
- setupRpcHandler() {
14966
- this.session.rpcHandlerManager.registerHandler(
14967
- "permission",
14968
- async (response) => {
14969
- const pending = this.pendingRequests.get(response.id);
14970
- if (!pending) {
14971
- persistence.logger.debug(`${this.getLogPrefix()} Permission request not found or already resolved`);
14972
- return;
14973
- }
14974
- this.pendingRequests.delete(response.id);
14975
- const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
14976
- pending.resolve(result);
14977
- this.session.updateAgentState((currentState) => {
14978
- const request = currentState.requests?.[response.id];
14979
- if (!request) return currentState;
14980
- const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
14981
- let res = {
14982
- ...currentState,
14983
- requests: remainingRequests,
14984
- completedRequests: {
14985
- ...currentState.completedRequests,
14986
- [response.id]: {
14987
- ...request,
14988
- completedAt: Date.now(),
14989
- status: response.approved ? "approved" : "denied",
14990
- decision: result.decision
14991
- }
14992
- }
14993
- };
14994
- return res;
14995
- });
14996
- persistence.logger.debug(`${this.getLogPrefix()} Permission ${response.approved ? "approved" : "denied"} for ${pending.toolName}`);
14997
- }
14998
- );
15275
+ activeSubagents.clear();
15276
+ startedSubagents.clear();
15277
+ return envelopes;
15278
+ }
15279
+ function buildEnvelopeOptions(currentTurnId, subagent) {
15280
+ return {
15281
+ ...currentTurnId ? { turn: currentTurnId } : {},
15282
+ ...subagent ? { subagent } : {}
15283
+ };
15284
+ }
15285
+ function pickProviderSubagent(message) {
15286
+ const candidates = [message.subagent, message.parent_call_id, message.parentCallId];
15287
+ for (const candidate of candidates) {
15288
+ if (typeof candidate === "string" && candidate.length > 0) {
15289
+ return candidate;
15290
+ }
14999
15291
  }
15000
- /**
15001
- * Add a pending request to the agent state.
15002
- */
15003
- addPendingRequestToState(toolCallId, toolName, input) {
15004
- this.session.updateAgentState((currentState) => ({
15005
- ...currentState,
15006
- requests: {
15007
- ...currentState.requests,
15008
- [toolCallId]: {
15009
- tool: toolName,
15010
- arguments: input,
15011
- createdAt: Date.now()
15012
- }
15013
- }
15014
- }));
15292
+ return void 0;
15293
+ }
15294
+ function resolveSessionSubagent(message, providerSubagentToSessionSubagent) {
15295
+ const providerSubagent = pickProviderSubagent(message);
15296
+ if (!providerSubagent) {
15297
+ return void 0;
15015
15298
  }
15016
- /**
15017
- * Abort all pending permission requests.
15018
- * Unlike reset(), this resolves (not rejects) pending promises with { decision: 'abort' },
15019
- * causing the approval response to send 'cancel' to the provider. This is used when the
15020
- * user presses the abort/stop button — it unblocks any pending tool approval so the provider
15021
- * can process the turn cancellation.
15022
- */
15023
- abortAll() {
15024
- const pendingSnapshot = Array.from(this.pendingRequests.entries());
15025
- if (pendingSnapshot.length === 0) return;
15026
- this.pendingRequests.clear();
15027
- for (const [id, pending] of pendingSnapshot) {
15028
- try {
15029
- pending.resolve({ decision: "abort" });
15030
- } catch (err) {
15031
- persistence.logger.debug(`${this.getLogPrefix()} Error resolving aborted request ${id}:`, err);
15032
- }
15033
- }
15034
- this.session.updateAgentState((currentState) => {
15035
- const pendingRequests = currentState.requests || {};
15036
- const completedRequests = { ...currentState.completedRequests };
15037
- for (const [id, request] of Object.entries(pendingRequests)) {
15038
- completedRequests[id] = {
15039
- ...request,
15040
- completedAt: Date.now(),
15041
- status: "canceled",
15042
- reason: "Aborted by user"
15043
- };
15044
- }
15045
- return {
15046
- ...currentState,
15047
- requests: {},
15048
- completedRequests
15049
- };
15050
- });
15051
- persistence.logger.debug(`${this.getLogPrefix()} Aborted ${pendingSnapshot.length} pending permission(s)`);
15299
+ const existing = providerSubagentToSessionSubagent.get(providerSubagent);
15300
+ if (existing) {
15301
+ return existing;
15052
15302
  }
15053
- /**
15054
- * Reset state for new sessions.
15055
- * This method is idempotent - safe to call multiple times.
15056
- */
15057
- reset(reason = "Session reset") {
15058
- if (this.isResetting) {
15059
- persistence.logger.debug(`${this.getLogPrefix()} Reset already in progress, skipping`);
15060
- return;
15061
- }
15062
- this.isResetting = true;
15063
- try {
15064
- const pendingSnapshot = Array.from(this.pendingRequests.entries());
15065
- this.pendingRequests.clear();
15066
- for (const [id, pending] of pendingSnapshot) {
15067
- try {
15068
- pending.reject(new Error("Session reset"));
15069
- } catch (err) {
15070
- persistence.logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
15071
- }
15072
- }
15073
- this.session.updateAgentState((currentState) => {
15074
- const pendingRequests = currentState.requests || {};
15075
- const completedRequests = { ...currentState.completedRequests };
15076
- for (const [id, request] of Object.entries(pendingRequests)) {
15077
- completedRequests[id] = {
15078
- ...request,
15079
- completedAt: Date.now(),
15080
- status: "canceled",
15081
- reason
15082
- };
15083
- }
15084
- return {
15085
- ...currentState,
15086
- requests: {},
15087
- completedRequests
15088
- };
15089
- });
15090
- persistence.logger.debug(`${this.getLogPrefix()} Permission handler reset`);
15091
- } finally {
15092
- this.isResetting = false;
15093
- }
15303
+ const created = cuid2.createId();
15304
+ providerSubagentToSessionSubagent.set(providerSubagent, created);
15305
+ return created;
15306
+ }
15307
+ function pickCallId(message) {
15308
+ const callId = message.call_id ?? message.callId;
15309
+ if (typeof callId === "string" && callId.length > 0) {
15310
+ return callId;
15094
15311
  }
15312
+ return node_crypto.randomUUID();
15095
15313
  }
15096
-
15097
- class CodexPermissionHandler extends BasePermissionHandler {
15098
- // Exact tool names that should always be auto-approved. Include the bare
15099
- // form (used by Codex elicitation messages like `tool "change_title"`)
15100
- // and the MCP-qualified form for defense in depth.
15101
- static ALWAYS_AUTO_APPROVE_NAMES = /* @__PURE__ */ new Set([
15102
- "change_title",
15103
- "mcp__happy__change_title"
15104
- ]);
15105
- // Tool-call IDs that should auto-approve when they exactly match one of
15106
- // these values or start with `<name>-` (e.g. `change_title-1765385846663`).
15107
- // Substring matching was a bypass vector — any tool whose ID happened to
15108
- // contain `change_title` as a substring would be silently approved.
15109
- static ALWAYS_AUTO_APPROVE_ID_PREFIXES = [
15110
- "change_title"
15111
- ];
15112
- constructor(session) {
15113
- super(session);
15314
+ function summarizeCommand(command) {
15315
+ if (typeof command === "string" && command.trim().length > 0) {
15316
+ return command;
15114
15317
  }
15115
- getLogPrefix() {
15116
- return "[Codex]";
15318
+ if (Array.isArray(command)) {
15319
+ const cmd = command.map((v) => String(v)).join(" ").trim();
15320
+ return cmd.length > 0 ? cmd : null;
15117
15321
  }
15118
- shouldAutoApprove(toolName, toolCallId) {
15119
- if (CodexPermissionHandler.ALWAYS_AUTO_APPROVE_NAMES.has(toolName)) {
15120
- return true;
15121
- }
15122
- for (const prefix of CodexPermissionHandler.ALWAYS_AUTO_APPROVE_ID_PREFIXES) {
15123
- if (toolCallId === prefix || toolCallId.startsWith(`${prefix}-`)) {
15124
- return true;
15125
- }
15126
- }
15127
- return false;
15322
+ return null;
15323
+ }
15324
+ function commandToTitle(command) {
15325
+ if (!command) {
15326
+ return "Run command";
15128
15327
  }
15129
- /**
15130
- * Handle a tool permission request
15131
- * @param toolCallId - The unique ID of the tool call
15132
- * @param toolName - The name of the tool being called
15133
- * @param input - The input parameters for the tool
15134
- * @returns Promise resolving to permission result
15135
- */
15136
- async handleToolCall(toolCallId, toolName, input) {
15137
- if (this.shouldAutoApprove(toolName, toolCallId)) {
15138
- persistence.logger.debug(`${this.getLogPrefix()} Auto-approving tool ${toolName} (${toolCallId})`);
15139
- this.session.updateAgentState((currentState) => ({
15140
- ...currentState,
15141
- completedRequests: {
15142
- ...currentState.completedRequests,
15143
- [toolCallId]: {
15144
- tool: toolName,
15145
- arguments: input,
15146
- createdAt: Date.now(),
15147
- completedAt: Date.now(),
15148
- status: "approved",
15149
- decision: "approved"
15150
- }
15151
- }
15152
- }));
15153
- return { decision: "approved" };
15154
- }
15155
- return new Promise((resolve, reject) => {
15156
- this.pendingRequests.set(toolCallId, {
15157
- resolve,
15158
- reject,
15159
- toolName,
15160
- input
15161
- });
15162
- this.addPendingRequestToState(toolCallId, toolName, input);
15163
- persistence.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
15164
- });
15328
+ const short = command.length > 80 ? `${command.slice(0, 77)}...` : command;
15329
+ return `Run \`${short}\``;
15330
+ }
15331
+ function turnTimestampMs(turn) {
15332
+ const seconds = turn.startedAt ?? turn.completedAt;
15333
+ return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15334
+ }
15335
+ function completedTimestampMs(turn) {
15336
+ const seconds = turn.completedAt ?? turn.startedAt;
15337
+ return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15338
+ }
15339
+ function textFromInputItems(items) {
15340
+ if (!Array.isArray(items)) {
15341
+ return null;
15165
15342
  }
15343
+ const text = items.filter((item) => Boolean(item) && typeof item === "object" && item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n").trim();
15344
+ return text.length > 0 ? text : null;
15166
15345
  }
15167
-
15168
- class BaseReasoningProcessor {
15169
- accumulator = "";
15170
- inTitleCapture = false;
15171
- titleBuffer = "";
15172
- contentBuffer = "";
15173
- hasTitle = false;
15174
- currentCallId = null;
15175
- toolCallStarted = false;
15176
- currentTitle = null;
15177
- onMessage = null;
15178
- constructor(onMessage) {
15179
- this.onMessage = onMessage || null;
15180
- this.reset();
15346
+ function reasoningText(item) {
15347
+ const summary = item.summary;
15348
+ const content = item.content;
15349
+ const parts = [
15350
+ ...Array.isArray(summary) ? summary : [],
15351
+ ...Array.isArray(content) ? content : []
15352
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
15353
+ const text = parts.join("\n").trim();
15354
+ return text.length > 0 ? text : null;
15355
+ }
15356
+ function turnStatus(turn) {
15357
+ const status = typeof turn.status === "string" ? turn.status : null;
15358
+ if (status === "failed") {
15359
+ return "failed";
15181
15360
  }
15182
- /**
15183
- * Set the message callback for sending messages directly.
15184
- */
15185
- setMessageCallback(callback) {
15186
- this.onMessage = callback;
15361
+ if (status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted") {
15362
+ return "cancelled";
15187
15363
  }
15188
- /**
15189
- * Process a reasoning section break - indicates a new reasoning section is starting.
15190
- */
15191
- handleSectionBreak() {
15192
- this.finishCurrentToolCall("canceled");
15193
- this.resetState();
15194
- persistence.logger.debug(`${this.getLogPrefix()} Section break - reset state`);
15364
+ return "completed";
15365
+ }
15366
+ function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output) {
15367
+ const time = turnTimestampMs(turn);
15368
+ const opts = { turn: turn.id, time, codexItemId: item.id };
15369
+ envelopes.push(api.createEnvelope("agent", {
15370
+ t: "tool-call-start",
15371
+ call: item.id,
15372
+ name,
15373
+ title,
15374
+ description: title,
15375
+ args
15376
+ }, {
15377
+ ...opts,
15378
+ id: `${item.id}:start`
15379
+ }));
15380
+ if (output && output.trim().length > 0) {
15381
+ envelopes.push(api.createEnvelope("agent", {
15382
+ t: "text",
15383
+ text: output,
15384
+ thinking: true
15385
+ }, {
15386
+ ...opts,
15387
+ id: `${item.id}:output`
15388
+ }));
15195
15389
  }
15196
- /**
15197
- * Process a reasoning delta/chunk and accumulate content.
15198
- */
15199
- processInput(input) {
15200
- this.accumulator += input;
15201
- if (!this.inTitleCapture && !this.hasTitle && !this.contentBuffer) {
15202
- if (this.accumulator.startsWith("**")) {
15203
- this.inTitleCapture = true;
15204
- this.titleBuffer = this.accumulator.substring(2);
15205
- persistence.logger.debug(`${this.getLogPrefix()} Started title capture`);
15206
- } else if (this.accumulator.length > 0) {
15207
- this.contentBuffer = this.accumulator;
15208
- }
15209
- } else if (this.inTitleCapture) {
15210
- this.titleBuffer = this.accumulator.substring(2);
15211
- const titleEndIndex = this.titleBuffer.indexOf("**");
15212
- if (titleEndIndex !== -1) {
15213
- const title = this.titleBuffer.substring(0, titleEndIndex);
15214
- const afterTitle = this.titleBuffer.substring(titleEndIndex + 2);
15215
- this.hasTitle = true;
15216
- this.inTitleCapture = false;
15217
- this.currentTitle = title;
15218
- this.contentBuffer = afterTitle;
15219
- this.currentCallId = node_crypto.randomUUID();
15220
- persistence.logger.debug(`${this.getLogPrefix()} Title captured: "${title}"`);
15221
- this.sendToolCallStart(title);
15222
- }
15223
- } else if (this.hasTitle) {
15224
- const titleStartIndex = this.accumulator.indexOf("**");
15225
- if (titleStartIndex !== -1) {
15226
- this.contentBuffer = this.accumulator.substring(
15227
- titleStartIndex + 2 + this.currentTitle.length + 2
15228
- );
15229
- }
15230
- } else {
15231
- this.contentBuffer = this.accumulator;
15232
- }
15390
+ if ("status" in item && (item.status === "inProgress" || item.status === "running")) return;
15391
+ envelopes.push(api.createEnvelope("agent", {
15392
+ t: "tool-call-end",
15393
+ call: item.id
15394
+ }, {
15395
+ ...opts,
15396
+ id: `${item.id}:end`,
15397
+ time: completedTimestampMs(turn)
15398
+ }));
15399
+ }
15400
+ function mapCodexCollaborationItem(item, opts = {}, source = "history") {
15401
+ if (item.type === "subAgentActivity" && typeof item.agentThreadId === "string") {
15402
+ item = { ...item, type: "collabAgentToolCall", tool: item.kind, status: "completed", receiverThreadIds: [item.agentThreadId] };
15403
+ }
15404
+ if (item.type !== "collabAgentToolCall" || typeof item.id !== "string" || typeof item.tool !== "string") return [];
15405
+ const args = {
15406
+ operation: item.tool,
15407
+ ...typeof item.prompt === "string" ? { prompt: item.prompt } : {},
15408
+ ...typeof item.model === "string" ? { model: item.model } : {},
15409
+ receiverThreadIds: Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds : [],
15410
+ agentsStates: item.agentsStates ?? {}
15411
+ };
15412
+ const startId = source === "live" ? `${item.id}:${item.status ?? "unknown"}:start` : `${item.id}:start`;
15413
+ const envelopes = [api.createEnvelope("agent", {
15414
+ t: "tool-call-start",
15415
+ call: item.id,
15416
+ name: "CodexCollaboration",
15417
+ title: `Codex \xB7 ${item.tool}`,
15418
+ description: `Codex \xB7 ${item.tool}`,
15419
+ args
15420
+ }, { ...opts, id: startId })];
15421
+ if (item.status === "completed" || item.status === "failed" || item.status === "interrupted") {
15422
+ envelopes.push(api.createEnvelope("agent", {
15423
+ t: "tool-call-end",
15424
+ call: item.id,
15425
+ result: { text: JSON.stringify(args, null, 2), isError: item.status !== "completed" }
15426
+ }, { ...opts, id: `${item.id}:end` }));
15233
15427
  }
15234
- /**
15235
- * Send the tool call start message.
15236
- */
15237
- sendToolCallStart(title) {
15238
- if (!this.currentCallId || this.toolCallStarted) {
15239
- return;
15240
- }
15241
- const toolCall = {
15242
- type: "tool-call",
15243
- name: this.getToolName(),
15244
- callId: this.currentCallId,
15245
- input: {
15246
- title
15247
- },
15248
- id: node_crypto.randomUUID()
15249
- };
15250
- persistence.logger.debug(`${this.getLogPrefix()} Sending tool call start for: "${title}"`);
15251
- this.onMessage?.(toolCall);
15252
- this.toolCallStarted = true;
15253
- }
15254
- /**
15255
- * Complete the reasoning section.
15256
- * Returns true if reasoning was completed, false if there was nothing to complete.
15257
- */
15258
- completeReasoning(fullText) {
15259
- const text = fullText ?? this.accumulator;
15260
- if (!text.trim() && !this.toolCallStarted) {
15261
- persistence.logger.debug(`${this.getLogPrefix()} Complete called but no content accumulated, skipping`);
15262
- return false;
15263
- }
15264
- let title;
15265
- let content = text;
15266
- if (text.startsWith("**")) {
15267
- const titleEndIndex = text.indexOf("**", 2);
15268
- if (titleEndIndex !== -1) {
15269
- title = text.substring(2, titleEndIndex);
15270
- content = text.substring(titleEndIndex + 2).trim();
15428
+ return envelopes;
15429
+ }
15430
+ function mapCodexThreadToSessionEnvelopes(thread) {
15431
+ const envelopes = [];
15432
+ for (const turn of thread.turns ?? []) {
15433
+ const startedAt = turnTimestampMs(turn);
15434
+ const completedAt = completedTimestampMs(turn);
15435
+ envelopes.push(api.createEnvelope("agent", { t: "turn-start" }, {
15436
+ id: `${turn.id}:start`,
15437
+ turn: turn.id,
15438
+ time: startedAt
15439
+ }));
15440
+ for (const item of turn.items ?? []) {
15441
+ switch (item.type) {
15442
+ case "subAgentActivity":
15443
+ case "collabAgentToolCall": {
15444
+ envelopes.push(...mapCodexCollaborationItem(item, { turn: turn.id, time: startedAt, codexItemId: item.id }));
15445
+ break;
15446
+ }
15447
+ case "userMessage": {
15448
+ const text = textFromInputItems(item.content);
15449
+ if (text) {
15450
+ envelopes.push(api.createEnvelope("user", { t: "text", text }, {
15451
+ id: item.id,
15452
+ time: startedAt,
15453
+ codexItemId: item.id
15454
+ }));
15455
+ }
15456
+ break;
15457
+ }
15458
+ case "agentMessage": {
15459
+ const text = typeof item.text === "string" ? item.text.trim() : "";
15460
+ if (text.length > 0) {
15461
+ envelopes.push(api.createEnvelope("agent", { t: "text", text }, {
15462
+ id: item.id,
15463
+ turn: turn.id,
15464
+ time: completedAt,
15465
+ codexItemId: item.id
15466
+ }));
15467
+ }
15468
+ break;
15469
+ }
15470
+ case "reasoning": {
15471
+ const text = reasoningText(item);
15472
+ if (text) {
15473
+ envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, {
15474
+ id: item.id,
15475
+ turn: turn.id,
15476
+ time: startedAt,
15477
+ codexItemId: item.id
15478
+ }));
15479
+ }
15480
+ break;
15481
+ }
15482
+ case "commandExecution": {
15483
+ const command = typeof item.command === "string" ? item.command : "";
15484
+ emitHistoricalToolCall(
15485
+ envelopes,
15486
+ turn,
15487
+ item,
15488
+ "CodexBash",
15489
+ commandToTitle(command),
15490
+ { command, cwd: item.cwd },
15491
+ typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : null
15492
+ );
15493
+ break;
15494
+ }
15495
+ case "fileChange": {
15496
+ const title = "Apply patch";
15497
+ emitHistoricalToolCall(
15498
+ envelopes,
15499
+ turn,
15500
+ item,
15501
+ "CodexPatch",
15502
+ title,
15503
+ { changes: item.changes, status: item.status },
15504
+ null
15505
+ );
15506
+ break;
15507
+ }
15508
+ case "mcpToolCall": {
15509
+ const title = `${item.server}.${item.tool}`;
15510
+ const output = item.error !== void 0 && item.error !== null ? String(item.error) : item.result !== void 0 && item.result !== null ? String(item.result) : null;
15511
+ emitHistoricalToolCall(
15512
+ envelopes,
15513
+ turn,
15514
+ item,
15515
+ "McpTool",
15516
+ title,
15517
+ {
15518
+ server: item.server,
15519
+ tool: item.tool,
15520
+ arguments: item.arguments
15521
+ },
15522
+ output
15523
+ );
15524
+ break;
15525
+ }
15271
15526
  }
15272
15527
  }
15273
- persistence.logger.debug(`${this.getLogPrefix()} Complete reasoning - Title: "${title}", Has content: ${content.length > 0}`);
15274
- if (title && !this.toolCallStarted) {
15275
- this.currentCallId = this.currentCallId || node_crypto.randomUUID();
15276
- this.sendToolCallStart(title);
15277
- }
15278
- if (this.toolCallStarted && this.currentCallId) {
15279
- const toolResult = {
15280
- type: "tool-call-result",
15281
- callId: this.currentCallId,
15282
- output: {
15283
- content,
15284
- status: "completed"
15285
- },
15286
- id: node_crypto.randomUUID()
15287
- };
15288
- persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result`);
15289
- this.onMessage?.(toolResult);
15290
- } else if (content.trim()) {
15291
- const reasoningMessage = {
15292
- type: "reasoning",
15293
- message: content,
15294
- id: node_crypto.randomUUID()
15295
- };
15296
- persistence.logger.debug(`${this.getLogPrefix()} Sending reasoning message`);
15297
- this.onMessage?.(reasoningMessage);
15298
- }
15299
- this.resetState();
15300
- return true;
15528
+ envelopes.push(api.createEnvelope("agent", { t: "turn-end", status: turnStatus(turn) }, {
15529
+ id: `${turn.id}:end`,
15530
+ turn: turn.id,
15531
+ time: completedAt
15532
+ }));
15301
15533
  }
15302
- /**
15303
- * Abort the current reasoning section.
15304
- */
15305
- abort() {
15306
- persistence.logger.debug(`${this.getLogPrefix()} Abort called`);
15307
- this.finishCurrentToolCall("canceled");
15308
- this.resetState();
15534
+ return envelopes;
15535
+ }
15536
+ function patchDescription(changes) {
15537
+ if (!changes || typeof changes !== "object") {
15538
+ return "Applying patch";
15309
15539
  }
15310
- /**
15311
- * Reset the processor state.
15312
- */
15313
- reset() {
15314
- this.finishCurrentToolCall("canceled");
15315
- this.resetState();
15540
+ const fileCount = Object.keys(changes).length;
15541
+ if (fileCount === 1) {
15542
+ return "Applying patch to 1 file";
15316
15543
  }
15317
- /**
15318
- * Finish current tool call if one is in progress.
15319
- */
15320
- finishCurrentToolCall(status) {
15321
- if (this.toolCallStarted && this.currentCallId) {
15322
- const toolResult = {
15323
- type: "tool-call-result",
15324
- callId: this.currentCallId,
15325
- output: {
15326
- content: this.contentBuffer || "",
15327
- status
15328
- },
15329
- id: node_crypto.randomUUID()
15330
- };
15331
- persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result with status: ${status}`);
15332
- this.onMessage?.(toolResult);
15333
- }
15544
+ return `Applying patch to ${fileCount} files`;
15545
+ }
15546
+ function pickTurnEndStatus(message, type) {
15547
+ const rawStatus = message.status;
15548
+ if (rawStatus === "completed" || rawStatus === "failed" || rawStatus === "cancelled") {
15549
+ return rawStatus;
15334
15550
  }
15335
- /**
15336
- * Reset internal state.
15337
- */
15338
- resetState() {
15339
- this.accumulator = "";
15340
- this.inTitleCapture = false;
15341
- this.titleBuffer = "";
15342
- this.contentBuffer = "";
15343
- this.hasTitle = false;
15344
- this.currentCallId = null;
15345
- this.toolCallStarted = false;
15346
- this.currentTitle = null;
15551
+ if (rawStatus === "canceled") {
15552
+ return "cancelled";
15347
15553
  }
15348
- /**
15349
- * Get the current call ID for tool result matching.
15350
- */
15351
- getCurrentCallId() {
15352
- return this.currentCallId;
15554
+ if (type === "turn_aborted") {
15555
+ const reason = message.reason;
15556
+ const error = message.error;
15557
+ if (typeof reason === "string" && /(fail|error)/i.test(reason) || typeof error === "string" && error.length > 0 || error !== void 0 && error !== null && typeof error === "object") {
15558
+ return "failed";
15559
+ }
15560
+ return "cancelled";
15353
15561
  }
15354
- /**
15355
- * Check if a tool call has been started.
15356
- */
15357
- hasStartedToolCall() {
15358
- return this.toolCallStarted;
15562
+ if (message.error !== void 0 && message.error !== null) {
15563
+ return "failed";
15359
15564
  }
15565
+ return "completed";
15360
15566
  }
15361
-
15362
- class ReasoningProcessor extends BaseReasoningProcessor {
15363
- getToolName() {
15364
- return "CodexReasoning";
15567
+ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15568
+ const type = message.type;
15569
+ const startedSubagents = getStartedSubagents(state);
15570
+ const activeSubagents = getActiveSubagents(state);
15571
+ const providerSubagentToSessionSubagent = getProviderSubagentToSessionSubagent(state);
15572
+ if (type === "collab_agent_item") {
15573
+ return {
15574
+ currentTurnId: state.currentTurnId,
15575
+ startedSubagents,
15576
+ activeSubagents,
15577
+ providerSubagentToSessionSubagent,
15578
+ envelopes: mapCodexCollaborationItem(message.item, buildEnvelopeOptions(state.currentTurnId), "live")
15579
+ };
15365
15580
  }
15366
- getLogPrefix() {
15367
- return "[ReasoningProcessor]";
15368
- }
15369
- /**
15370
- * Process a reasoning delta and accumulate content.
15371
- */
15372
- processDelta(delta) {
15373
- this.processInput(delta);
15581
+ if (type === "task_started") {
15582
+ const turnId = cuid2.createId();
15583
+ const turnStart = api.createEnvelope("agent", { t: "turn-start" }, { turn: turnId });
15584
+ startedSubagents.clear();
15585
+ activeSubagents.clear();
15586
+ providerSubagentToSessionSubagent.clear();
15587
+ return {
15588
+ currentTurnId: turnId,
15589
+ startedSubagents,
15590
+ activeSubagents,
15591
+ providerSubagentToSessionSubagent,
15592
+ envelopes: [turnStart]
15593
+ };
15374
15594
  }
15375
- /**
15376
- * Complete the reasoning section with final text.
15377
- */
15378
- complete(fullText) {
15379
- this.completeReasoning(fullText);
15595
+ if (type === "task_complete" || type === "turn_aborted") {
15596
+ if (!state.currentTurnId) {
15597
+ return {
15598
+ currentTurnId: null,
15599
+ startedSubagents,
15600
+ activeSubagents,
15601
+ providerSubagentToSessionSubagent,
15602
+ envelopes: []
15603
+ };
15604
+ }
15605
+ const lifecycleOpts = { turn: state.currentTurnId };
15606
+ providerSubagentToSessionSubagent.clear();
15607
+ return {
15608
+ currentTurnId: null,
15609
+ startedSubagents,
15610
+ activeSubagents,
15611
+ providerSubagentToSessionSubagent,
15612
+ envelopes: [
15613
+ ...emitSubagentStops(lifecycleOpts, startedSubagents, activeSubagents),
15614
+ api.createEnvelope("agent", {
15615
+ t: "turn-end",
15616
+ status: pickTurnEndStatus(message, type)
15617
+ }, lifecycleOpts)
15618
+ ]
15619
+ };
15380
15620
  }
15381
- }
15382
-
15383
- class DiffProcessor {
15384
- previousDiff = null;
15385
- onMessage = null;
15386
- constructor(onMessage) {
15387
- this.onMessage = onMessage || null;
15621
+ if (type === "token_count") {
15622
+ return {
15623
+ currentTurnId: state.currentTurnId,
15624
+ startedSubagents,
15625
+ activeSubagents,
15626
+ providerSubagentToSessionSubagent,
15627
+ envelopes: []
15628
+ };
15388
15629
  }
15389
- /**
15390
- * Process a turn_diff message and check if the unified_diff has changed
15391
- */
15392
- processDiff(unifiedDiff) {
15393
- if (this.previousDiff !== unifiedDiff) {
15394
- persistence.logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
15395
- const callId = node_crypto.randomUUID();
15396
- const toolCall = {
15397
- type: "tool-call",
15398
- name: "CodexDiff",
15399
- callId,
15400
- input: {
15401
- unified_diff: unifiedDiff
15402
- },
15403
- id: node_crypto.randomUUID()
15630
+ const subagent = resolveSessionSubagent(message, providerSubagentToSessionSubagent);
15631
+ const opts = buildEnvelopeOptions(state.currentTurnId, subagent);
15632
+ if (type === "agent_message") {
15633
+ if (typeof message.message !== "string") {
15634
+ return {
15635
+ currentTurnId: state.currentTurnId,
15636
+ startedSubagents,
15637
+ activeSubagents,
15638
+ providerSubagentToSessionSubagent,
15639
+ envelopes: []
15404
15640
  };
15405
- this.onMessage?.(toolCall);
15406
- const toolResult = {
15407
- type: "tool-call-result",
15408
- callId,
15409
- output: {
15410
- status: "completed"
15411
- },
15412
- id: node_crypto.randomUUID()
15641
+ }
15642
+ const envelopes = [];
15643
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15644
+ envelopes.push(api.createEnvelope("agent", { t: "text", text: message.message }, opts));
15645
+ return {
15646
+ currentTurnId: state.currentTurnId,
15647
+ startedSubagents,
15648
+ activeSubagents,
15649
+ providerSubagentToSessionSubagent,
15650
+ envelopes
15651
+ };
15652
+ }
15653
+ if (type === "agent_reasoning" || type === "agent_reasoning_delta") {
15654
+ const text = typeof message.text === "string" ? message.text : typeof message.delta === "string" ? message.delta : null;
15655
+ if (!text) {
15656
+ return {
15657
+ currentTurnId: state.currentTurnId,
15658
+ startedSubagents,
15659
+ activeSubagents,
15660
+ providerSubagentToSessionSubagent,
15661
+ envelopes: []
15413
15662
  };
15414
- this.onMessage?.(toolResult);
15415
15663
  }
15416
- this.previousDiff = unifiedDiff;
15417
- persistence.logger.debug("[DiffProcessor] Updated stored diff");
15664
+ const envelopes = [];
15665
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15666
+ envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, opts));
15667
+ return {
15668
+ currentTurnId: state.currentTurnId,
15669
+ startedSubagents,
15670
+ activeSubagents,
15671
+ providerSubagentToSessionSubagent,
15672
+ envelopes
15673
+ };
15418
15674
  }
15419
- /**
15420
- * Reset the processor state (called on task_complete or turn_aborted)
15421
- */
15422
- reset() {
15423
- persistence.logger.debug("[DiffProcessor] Resetting diff state");
15424
- this.previousDiff = null;
15675
+ if (type === "exec_command_begin") {
15676
+ const call = pickCallId(message);
15677
+ const { call_id: _callIdSnake, callId: _callIdCamel, type: _type, ...args } = message;
15678
+ const command = summarizeCommand(args.command);
15679
+ const description = typeof args.description === "string" ? args.description : command ?? "Execute command";
15680
+ const envelopes = [];
15681
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15682
+ envelopes.push(
15683
+ api.createEnvelope("agent", {
15684
+ t: "tool-call-start",
15685
+ call,
15686
+ name: "CodexBash",
15687
+ title: commandToTitle(command),
15688
+ description,
15689
+ args
15690
+ }, opts)
15691
+ );
15692
+ return {
15693
+ currentTurnId: state.currentTurnId,
15694
+ startedSubagents,
15695
+ activeSubagents,
15696
+ providerSubagentToSessionSubagent,
15697
+ envelopes
15698
+ };
15425
15699
  }
15426
- /**
15427
- * Set the message callback for sending messages directly
15428
- */
15429
- setMessageCallback(callback) {
15430
- this.onMessage = callback;
15700
+ if (type === "exec_command_end") {
15701
+ const call = pickCallId(message);
15702
+ const envelopes = [];
15703
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15704
+ envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
15705
+ return {
15706
+ currentTurnId: state.currentTurnId,
15707
+ startedSubagents,
15708
+ activeSubagents,
15709
+ providerSubagentToSessionSubagent,
15710
+ envelopes
15711
+ };
15431
15712
  }
15432
- /**
15433
- * Get the current diff value
15434
- */
15435
- getCurrentDiff() {
15436
- return this.previousDiff;
15713
+ if (type === "patch_apply_begin") {
15714
+ const call = pickCallId(message);
15715
+ const autoApproved = message.auto_approved;
15716
+ const changes = message.changes;
15717
+ const envelopes = [];
15718
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15719
+ envelopes.push(
15720
+ api.createEnvelope("agent", {
15721
+ t: "tool-call-start",
15722
+ call,
15723
+ name: "CodexPatch",
15724
+ title: "Apply patch",
15725
+ description: patchDescription(changes),
15726
+ args: {
15727
+ auto_approved: autoApproved,
15728
+ changes
15729
+ }
15730
+ }, opts)
15731
+ );
15732
+ return {
15733
+ currentTurnId: state.currentTurnId,
15734
+ startedSubagents,
15735
+ activeSubagents,
15736
+ providerSubagentToSessionSubagent,
15737
+ envelopes
15738
+ };
15437
15739
  }
15438
- }
15439
-
15440
- const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
15441
- const [messages, setMessages] = React.useState([]);
15442
- const [confirmationMode, setConfirmationMode] = React.useState(false);
15443
- const [actionInProgress, setActionInProgress] = React.useState(false);
15444
- const confirmationTimeoutRef = React.useRef(null);
15445
- const { stdout } = ink.useStdout();
15446
- const terminalWidth = stdout.columns || 80;
15447
- const terminalHeight = stdout.rows || 24;
15448
- React.useEffect(() => {
15449
- setMessages(messageBuffer.getMessages());
15450
- const unsubscribe = messageBuffer.onUpdate((newMessages) => {
15451
- setMessages(newMessages);
15452
- });
15453
- return () => {
15454
- unsubscribe();
15455
- if (confirmationTimeoutRef.current) {
15456
- clearTimeout(confirmationTimeoutRef.current);
15457
- }
15740
+ if (type === "patch_apply_end") {
15741
+ const call = pickCallId(message);
15742
+ const envelopes = [];
15743
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15744
+ envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
15745
+ return {
15746
+ currentTurnId: state.currentTurnId,
15747
+ startedSubagents,
15748
+ activeSubagents,
15749
+ providerSubagentToSessionSubagent,
15750
+ envelopes
15458
15751
  };
15459
- }, [messageBuffer]);
15460
- const resetConfirmation = React.useCallback(() => {
15461
- setConfirmationMode(false);
15462
- if (confirmationTimeoutRef.current) {
15463
- clearTimeout(confirmationTimeoutRef.current);
15464
- confirmationTimeoutRef.current = null;
15465
- }
15466
- }, []);
15467
- const setConfirmationWithTimeout = React.useCallback(() => {
15468
- setConfirmationMode(true);
15469
- if (confirmationTimeoutRef.current) {
15470
- clearTimeout(confirmationTimeoutRef.current);
15471
- }
15472
- confirmationTimeoutRef.current = setTimeout(() => {
15473
- resetConfirmation();
15474
- }, 15e3);
15475
- }, [resetConfirmation]);
15476
- ink.useInput(React.useCallback(async (input, key) => {
15477
- if (actionInProgress) return;
15478
- if (key.ctrl && input === "c") {
15479
- if (confirmationMode) {
15480
- resetConfirmation();
15481
- setActionInProgress(true);
15482
- await new Promise((resolve) => setTimeout(resolve, 100));
15483
- onExit?.();
15484
- } else {
15485
- setConfirmationWithTimeout();
15486
- }
15487
- return;
15488
- }
15489
- if (confirmationMode) {
15490
- resetConfirmation();
15491
- }
15492
- }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
15493
- const getMessageColor = (type) => {
15494
- switch (type) {
15495
- case "user":
15496
- return "magenta";
15497
- case "assistant":
15498
- return "cyan";
15499
- case "system":
15500
- return "blue";
15501
- case "tool":
15502
- return "yellow";
15503
- case "result":
15504
- return "green";
15505
- case "status":
15506
- return "gray";
15507
- default:
15508
- return "white";
15509
- }
15510
- };
15511
- const formatMessage = (msg) => {
15512
- const lines = msg.content.split("\n");
15513
- const maxLineLength = terminalWidth - 10;
15514
- return lines.map((line) => {
15515
- if (line.length <= maxLineLength) return line;
15516
- const chunks = [];
15517
- for (let i = 0; i < line.length; i += maxLineLength) {
15518
- chunks.push(line.slice(i, i + maxLineLength));
15519
- }
15520
- return chunks.join("\n");
15521
- }).join("\n");
15522
- };
15523
- return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
15524
- ink.Box,
15525
- {
15526
- flexDirection: "column",
15527
- width: terminalWidth,
15528
- height: terminalHeight - 4,
15529
- borderStyle: "round",
15530
- borderColor: "gray",
15531
- paddingX: 1,
15532
- overflow: "hidden"
15533
- },
15534
- /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "\u{1F916} Codex Agent Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
15535
- /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
15536
- // Show only the last messages that fit in the available space
15537
- messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
15538
- ))
15539
- ), /* @__PURE__ */ React.createElement(
15540
- ink.Box,
15541
- {
15542
- width: terminalWidth,
15543
- borderStyle: "round",
15544
- borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "green",
15545
- paddingX: 2,
15546
- justifyContent: "center",
15547
- alignItems: "center",
15548
- flexDirection: "column"
15549
- },
15550
- /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F916} Codex Agent Running \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
15551
- ));
15552
- };
15553
-
15554
- function createOfflineSessionStub(sessionTag) {
15752
+ }
15555
15753
  return {
15556
- sessionId: `offline-${sessionTag}`,
15557
- sendCodexMessage: () => {
15558
- },
15559
- sendAgentMessage: () => {
15560
- },
15561
- sendAgentUsageSnapshot: () => false,
15562
- sendClaudeSessionMessage: () => {
15563
- },
15564
- keepAlive: () => {
15565
- },
15566
- sendSessionEvent: () => {
15567
- },
15568
- sendSessionDeath: () => {
15569
- },
15570
- updateLifecycleState: () => {
15571
- },
15572
- requestControlTransfer: async () => {
15573
- },
15574
- flush: async () => {
15575
- },
15576
- close: async () => {
15577
- },
15578
- updateMetadata: () => {
15579
- },
15580
- updateAgentState: () => {
15581
- },
15582
- onUserMessage: () => {
15583
- },
15584
- onFileEvent: () => {
15585
- },
15586
- trackAttachmentDownload: () => {
15587
- },
15588
- drainAttachmentsForUserMessage: async () => [],
15589
- rpcHandlerManager: {
15590
- registerHandler: () => {
15591
- }
15592
- }
15754
+ currentTurnId: state.currentTurnId,
15755
+ startedSubagents,
15756
+ activeSubagents,
15757
+ providerSubagentToSessionSubagent,
15758
+ envelopes: []
15593
15759
  };
15594
15760
  }
15761
+ function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
15762
+ const toolLikeMessage = message;
15763
+ const opts = buildEnvelopeOptions(state.currentTurnId);
15764
+ if (message.type === "reasoning") {
15765
+ return [api.createEnvelope("agent", {
15766
+ t: "text",
15767
+ text: message.message,
15768
+ thinking: true
15769
+ }, opts)];
15770
+ }
15771
+ if (message.type === "tool-call") {
15772
+ const title = typeof toolLikeMessage.input?.title === "string" ? toolLikeMessage.input.title : `${toolLikeMessage.name || "Tool"} call`;
15773
+ return [api.createEnvelope("agent", {
15774
+ t: "tool-call-start",
15775
+ call: toolLikeMessage.callId,
15776
+ name: toolLikeMessage.name || "unknown",
15777
+ title,
15778
+ description: title,
15779
+ args: toolLikeMessage.input && typeof toolLikeMessage.input === "object" ? toolLikeMessage.input : {}
15780
+ }, opts)];
15781
+ }
15782
+ if (message.type === "tool-call-result") {
15783
+ const envelopes = [];
15784
+ const content = toolLikeMessage.output?.content;
15785
+ if (typeof content === "string" && content.trim().length > 0) {
15786
+ envelopes.push(api.createEnvelope("agent", {
15787
+ t: "text",
15788
+ text: content,
15789
+ thinking: true
15790
+ }, opts));
15791
+ }
15792
+ envelopes.push(api.createEnvelope("agent", {
15793
+ t: "tool-call-end",
15794
+ call: toolLikeMessage.callId
15795
+ }, opts));
15796
+ return envelopes;
15797
+ }
15798
+ return [];
15799
+ }
15595
15800
 
15596
- function setupOfflineReconnection(opts) {
15597
- const { api: api$1, sessionTag, metadata, state, response, onSessionSwap } = opts;
15598
- let session;
15599
- let reconnectionHandle = null;
15600
- if (!response) {
15601
- session = createOfflineSessionStub(sessionTag);
15602
- reconnectionHandle = api.startOfflineReconnection({
15603
- serverUrl: persistence.configuration.serverUrl,
15604
- onReconnected: async () => {
15605
- const resp = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
15606
- if (!resp) throw new Error("Server unavailable");
15607
- const realSession = api$1.sessionSyncClient(resp);
15608
- onSessionSwap(realSession);
15609
- return realSession;
15610
- },
15611
- onNotify: (msg) => {
15612
- console.log(msg);
15801
+ async function readChildThread(client, childId) {
15802
+ if (!client.threadId || typeof childId !== "string" || !childId) throw new Error("Invalid child thread");
15803
+ const parentId = client.threadId;
15804
+ const { thread: parent } = await client.readThread({ threadId: parentId, includeTurns: true });
15805
+ const belongs = parent.id === parentId && parent.turns?.some((turn) => turn.items.some((item) => item.type === "collabAgentToolCall" && Array.isArray(item.receiverThreadIds) && item.receiverThreadIds.includes(childId) || item.type === "subAgentActivity" && item.agentThreadId === childId));
15806
+ if (!belongs) throw new Error("Thread is not a child of this session");
15807
+ const { thread } = await client.readThread({ threadId: childId, includeTurns: true });
15808
+ if (thread.id !== childId) throw new Error("Child thread mismatch");
15809
+ return { envelopes: mapCodexThreadToSessionEnvelopes(thread) };
15810
+ }
15811
+
15812
+ function registerAgentAttachmentDownloads(session) {
15813
+ session.onFileEvent((fileEvent) => {
15814
+ const attachment = fileEvent.content.data.ev;
15815
+ const download = (async () => {
15816
+ try {
15817
+ const data = await session.downloadAndDecryptAttachment(attachment.ref);
15818
+ if (!data) throw new Error("Attachment decryption failed");
15819
+ return { data, mimeType: attachment.mimeType ?? "image/jpeg", name: attachment.name };
15820
+ } catch {
15821
+ session.sendSessionEvent({ type: "message", message: `Could not download attachment: ${attachment.name}. Please attach it again.` });
15822
+ return null;
15613
15823
  }
15614
- });
15615
- return { session, reconnectionHandle, isOffline: true };
15616
- } else {
15617
- session = api$1.sessionSyncClient(response, opts.sessionClientOptions);
15618
- return { session, reconnectionHandle: null, isOffline: false };
15619
- }
15824
+ })();
15825
+ session.trackAttachmentDownload(download);
15826
+ });
15620
15827
  }
15621
15828
 
15622
- function resolveCodexExecutionPolicy(permissionMode, sandboxManagedByHappy) {
15623
- if (sandboxManagedByHappy) {
15624
- return {
15625
- approvalPolicy: "never",
15626
- sandbox: "danger-full-access"
15627
- };
15829
+ class BasePermissionHandler {
15830
+ pendingRequests = /* @__PURE__ */ new Map();
15831
+ session;
15832
+ isResetting = false;
15833
+ constructor(session) {
15834
+ this.session = session;
15835
+ this.setupRpcHandler();
15628
15836
  }
15629
- const approvalPolicy = (() => {
15630
- switch (permissionMode) {
15631
- // Codex native modes
15632
- case "default":
15633
- return "untrusted";
15634
- // Ask for non-trusted commands
15635
- case "read-only":
15636
- return "never";
15637
- // Never ask, read-only enforced by sandbox
15638
- case "safe-yolo":
15639
- return "on-request";
15640
- // Workspace auto-run; request approval for escalation (0.153 removed on-failure)
15641
- case "yolo":
15642
- return "never";
15643
- // Full YOLO: never interrupt for approvals
15644
- // Defensive fallback for Claude-specific modes (backward compatibility)
15645
- case "bypassPermissions":
15646
- return "never";
15647
- // Full access: map to yolo behavior
15648
- case "acceptEdits":
15649
- return "on-request";
15650
- // Let model decide (closest to auto-approve edits)
15651
- case "plan":
15652
- return "untrusted";
15653
- // Conservative: ask for non-trusted
15654
- default:
15655
- return "untrusted";
15837
+ /**
15838
+ * Update the session reference (used after offline reconnection swaps sessions).
15839
+ * This is critical for avoiding stale session references after onSessionSwap.
15840
+ */
15841
+ updateSession(newSession) {
15842
+ persistence.logger.debug(`${this.getLogPrefix()} Session reference updated`);
15843
+ this.session = newSession;
15844
+ this.setupRpcHandler();
15845
+ }
15846
+ /**
15847
+ * Setup RPC handler for permission responses.
15848
+ */
15849
+ setupRpcHandler() {
15850
+ this.session.rpcHandlerManager.registerHandler(
15851
+ "permission",
15852
+ async (response) => {
15853
+ const pending = this.pendingRequests.get(response.id);
15854
+ if (!pending) {
15855
+ persistence.logger.debug(`${this.getLogPrefix()} Permission request not found or already resolved`);
15856
+ return;
15857
+ }
15858
+ this.pendingRequests.delete(response.id);
15859
+ const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
15860
+ pending.resolve(result);
15861
+ this.session.updateAgentState((currentState) => {
15862
+ const request = currentState.requests?.[response.id];
15863
+ if (!request) return currentState;
15864
+ const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
15865
+ let res = {
15866
+ ...currentState,
15867
+ requests: remainingRequests,
15868
+ completedRequests: {
15869
+ ...currentState.completedRequests,
15870
+ [response.id]: {
15871
+ ...request,
15872
+ completedAt: Date.now(),
15873
+ status: response.approved ? "approved" : "denied",
15874
+ decision: result.decision
15875
+ }
15876
+ }
15877
+ };
15878
+ return res;
15879
+ });
15880
+ persistence.logger.debug(`${this.getLogPrefix()} Permission ${response.approved ? "approved" : "denied"} for ${pending.toolName}`);
15881
+ }
15882
+ );
15883
+ }
15884
+ /**
15885
+ * Add a pending request to the agent state.
15886
+ */
15887
+ addPendingRequestToState(toolCallId, toolName, input) {
15888
+ this.session.updateAgentState((currentState) => ({
15889
+ ...currentState,
15890
+ requests: {
15891
+ ...currentState.requests,
15892
+ [toolCallId]: {
15893
+ tool: toolName,
15894
+ arguments: input,
15895
+ createdAt: Date.now()
15896
+ }
15897
+ }
15898
+ }));
15899
+ }
15900
+ /**
15901
+ * Abort all pending permission requests.
15902
+ * Unlike reset(), this resolves (not rejects) pending promises with { decision: 'abort' },
15903
+ * causing the approval response to send 'cancel' to the provider. This is used when the
15904
+ * user presses the abort/stop button — it unblocks any pending tool approval so the provider
15905
+ * can process the turn cancellation.
15906
+ */
15907
+ abortAll() {
15908
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
15909
+ if (pendingSnapshot.length === 0) return;
15910
+ this.pendingRequests.clear();
15911
+ for (const [id, pending] of pendingSnapshot) {
15912
+ try {
15913
+ pending.resolve({ decision: "abort" });
15914
+ } catch (err) {
15915
+ persistence.logger.debug(`${this.getLogPrefix()} Error resolving aborted request ${id}:`, err);
15916
+ }
15656
15917
  }
15657
- })();
15658
- const sandbox = (() => {
15659
- switch (permissionMode) {
15660
- // Codex native modes
15661
- case "default":
15662
- return "workspace-write";
15663
- // Can write in workspace
15664
- case "read-only":
15665
- return "read-only";
15666
- // Read-only filesystem
15667
- case "safe-yolo":
15668
- return "workspace-write";
15669
- // Can write in workspace
15670
- case "yolo":
15671
- return "danger-full-access";
15672
- // Full system access
15673
- // Defensive fallback for Claude-specific modes
15674
- case "bypassPermissions":
15675
- return "danger-full-access";
15676
- // Full access: map to yolo
15677
- case "acceptEdits":
15678
- return "workspace-write";
15679
- // Can edit files in workspace
15680
- case "plan":
15681
- return "workspace-write";
15682
- // Can write for planning
15683
- default:
15684
- return "workspace-write";
15918
+ this.session.updateAgentState((currentState) => {
15919
+ const pendingRequests = currentState.requests || {};
15920
+ const completedRequests = { ...currentState.completedRequests };
15921
+ for (const [id, request] of Object.entries(pendingRequests)) {
15922
+ completedRequests[id] = {
15923
+ ...request,
15924
+ completedAt: Date.now(),
15925
+ status: "canceled",
15926
+ reason: "Aborted by user"
15927
+ };
15928
+ }
15929
+ return {
15930
+ ...currentState,
15931
+ requests: {},
15932
+ completedRequests
15933
+ };
15934
+ });
15935
+ persistence.logger.debug(`${this.getLogPrefix()} Aborted ${pendingSnapshot.length} pending permission(s)`);
15936
+ }
15937
+ /**
15938
+ * Reset state for new sessions.
15939
+ * This method is idempotent - safe to call multiple times.
15940
+ */
15941
+ reset(reason = "Session reset") {
15942
+ if (this.isResetting) {
15943
+ persistence.logger.debug(`${this.getLogPrefix()} Reset already in progress, skipping`);
15944
+ return;
15945
+ }
15946
+ this.isResetting = true;
15947
+ try {
15948
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
15949
+ this.pendingRequests.clear();
15950
+ for (const [id, pending] of pendingSnapshot) {
15951
+ try {
15952
+ pending.reject(new Error("Session reset"));
15953
+ } catch (err) {
15954
+ persistence.logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
15955
+ }
15956
+ }
15957
+ this.session.updateAgentState((currentState) => {
15958
+ const pendingRequests = currentState.requests || {};
15959
+ const completedRequests = { ...currentState.completedRequests };
15960
+ for (const [id, request] of Object.entries(pendingRequests)) {
15961
+ completedRequests[id] = {
15962
+ ...request,
15963
+ completedAt: Date.now(),
15964
+ status: "canceled",
15965
+ reason
15966
+ };
15967
+ }
15968
+ return {
15969
+ ...currentState,
15970
+ requests: {},
15971
+ completedRequests
15972
+ };
15973
+ });
15974
+ persistence.logger.debug(`${this.getLogPrefix()} Permission handler reset`);
15975
+ } finally {
15976
+ this.isResetting = false;
15685
15977
  }
15686
- })();
15687
- return { approvalPolicy, sandbox };
15978
+ }
15688
15979
  }
15689
15980
 
15690
- function getStartedSubagents(state) {
15691
- return state.startedSubagents ?? /* @__PURE__ */ new Set();
15692
- }
15693
- function getActiveSubagents(state) {
15694
- return state.activeSubagents ?? /* @__PURE__ */ new Set();
15695
- }
15696
- function getProviderSubagentToSessionSubagent(state) {
15697
- return state.providerSubagentToSessionSubagent ?? /* @__PURE__ */ new Map();
15698
- }
15699
- function maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes) {
15700
- if (!subagent || startedSubagents.has(subagent)) {
15701
- return;
15981
+ class CodexPermissionHandler extends BasePermissionHandler {
15982
+ // Exact tool names that should always be auto-approved. Include the bare
15983
+ // form (used by Codex elicitation messages like `tool "change_title"`)
15984
+ // and the MCP-qualified form for defense in depth.
15985
+ static ALWAYS_AUTO_APPROVE_NAMES = /* @__PURE__ */ new Set([
15986
+ "change_title",
15987
+ "mcp__happy__change_title"
15988
+ ]);
15989
+ // Tool-call IDs that should auto-approve when they exactly match one of
15990
+ // these values or start with `<name>-` (e.g. `change_title-1765385846663`).
15991
+ // Substring matching was a bypass vector — any tool whose ID happened to
15992
+ // contain `change_title` as a substring would be silently approved.
15993
+ static ALWAYS_AUTO_APPROVE_ID_PREFIXES = [
15994
+ "change_title"
15995
+ ];
15996
+ constructor(session) {
15997
+ super(session);
15702
15998
  }
15703
- envelopes.push(api.createEnvelope("agent", { t: "start" }, { ...opts, subagent }));
15704
- startedSubagents.add(subagent);
15705
- activeSubagents.add(subagent);
15706
- }
15707
- function emitSubagentStops(opts, startedSubagents, activeSubagents) {
15708
- const envelopes = [];
15709
- for (const subagent of activeSubagents) {
15710
- envelopes.push(api.createEnvelope("agent", { t: "stop" }, { ...opts, subagent }));
15999
+ getLogPrefix() {
16000
+ return "[Codex]";
15711
16001
  }
15712
- activeSubagents.clear();
15713
- startedSubagents.clear();
15714
- return envelopes;
15715
- }
15716
- function buildEnvelopeOptions(currentTurnId, subagent) {
15717
- return {
15718
- ...currentTurnId ? { turn: currentTurnId } : {},
15719
- ...subagent ? { subagent } : {}
15720
- };
15721
- }
15722
- function pickProviderSubagent(message) {
15723
- const candidates = [message.subagent, message.parent_call_id, message.parentCallId];
15724
- for (const candidate of candidates) {
15725
- if (typeof candidate === "string" && candidate.length > 0) {
15726
- return candidate;
16002
+ shouldAutoApprove(toolName, toolCallId) {
16003
+ if (CodexPermissionHandler.ALWAYS_AUTO_APPROVE_NAMES.has(toolName)) {
16004
+ return true;
15727
16005
  }
16006
+ for (const prefix of CodexPermissionHandler.ALWAYS_AUTO_APPROVE_ID_PREFIXES) {
16007
+ if (toolCallId === prefix || toolCallId.startsWith(`${prefix}-`)) {
16008
+ return true;
16009
+ }
16010
+ }
16011
+ return false;
15728
16012
  }
15729
- return void 0;
15730
- }
15731
- function resolveSessionSubagent(message, providerSubagentToSessionSubagent) {
15732
- const providerSubagent = pickProviderSubagent(message);
15733
- if (!providerSubagent) {
15734
- return void 0;
15735
- }
15736
- const existing = providerSubagentToSessionSubagent.get(providerSubagent);
15737
- if (existing) {
15738
- return existing;
16013
+ /**
16014
+ * Handle a tool permission request
16015
+ * @param toolCallId - The unique ID of the tool call
16016
+ * @param toolName - The name of the tool being called
16017
+ * @param input - The input parameters for the tool
16018
+ * @returns Promise resolving to permission result
16019
+ */
16020
+ async handleToolCall(toolCallId, toolName, input) {
16021
+ if (this.shouldAutoApprove(toolName, toolCallId)) {
16022
+ persistence.logger.debug(`${this.getLogPrefix()} Auto-approving tool ${toolName} (${toolCallId})`);
16023
+ this.session.updateAgentState((currentState) => ({
16024
+ ...currentState,
16025
+ completedRequests: {
16026
+ ...currentState.completedRequests,
16027
+ [toolCallId]: {
16028
+ tool: toolName,
16029
+ arguments: input,
16030
+ createdAt: Date.now(),
16031
+ completedAt: Date.now(),
16032
+ status: "approved",
16033
+ decision: "approved"
16034
+ }
16035
+ }
16036
+ }));
16037
+ return { decision: "approved" };
16038
+ }
16039
+ return new Promise((resolve, reject) => {
16040
+ this.pendingRequests.set(toolCallId, {
16041
+ resolve,
16042
+ reject,
16043
+ toolName,
16044
+ input
16045
+ });
16046
+ this.addPendingRequestToState(toolCallId, toolName, input);
16047
+ persistence.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
16048
+ });
15739
16049
  }
15740
- const created = cuid2.createId();
15741
- providerSubagentToSessionSubagent.set(providerSubagent, created);
15742
- return created;
15743
16050
  }
15744
- function pickCallId(message) {
15745
- const callId = message.call_id ?? message.callId;
15746
- if (typeof callId === "string" && callId.length > 0) {
15747
- return callId;
16051
+
16052
+ class BaseReasoningProcessor {
16053
+ accumulator = "";
16054
+ inTitleCapture = false;
16055
+ titleBuffer = "";
16056
+ contentBuffer = "";
16057
+ hasTitle = false;
16058
+ currentCallId = null;
16059
+ toolCallStarted = false;
16060
+ currentTitle = null;
16061
+ onMessage = null;
16062
+ constructor(onMessage) {
16063
+ this.onMessage = onMessage || null;
16064
+ this.reset();
15748
16065
  }
15749
- return node_crypto.randomUUID();
15750
- }
15751
- function summarizeCommand(command) {
15752
- if (typeof command === "string" && command.trim().length > 0) {
15753
- return command;
16066
+ /**
16067
+ * Set the message callback for sending messages directly.
16068
+ */
16069
+ setMessageCallback(callback) {
16070
+ this.onMessage = callback;
15754
16071
  }
15755
- if (Array.isArray(command)) {
15756
- const cmd = command.map((v) => String(v)).join(" ").trim();
15757
- return cmd.length > 0 ? cmd : null;
16072
+ /**
16073
+ * Process a reasoning section break - indicates a new reasoning section is starting.
16074
+ */
16075
+ handleSectionBreak() {
16076
+ this.finishCurrentToolCall("canceled");
16077
+ this.resetState();
16078
+ persistence.logger.debug(`${this.getLogPrefix()} Section break - reset state`);
15758
16079
  }
15759
- return null;
15760
- }
15761
- function commandToTitle(command) {
15762
- if (!command) {
15763
- return "Run command";
16080
+ /**
16081
+ * Process a reasoning delta/chunk and accumulate content.
16082
+ */
16083
+ processInput(input) {
16084
+ this.accumulator += input;
16085
+ if (!this.inTitleCapture && !this.hasTitle && !this.contentBuffer) {
16086
+ if (this.accumulator.startsWith("**")) {
16087
+ this.inTitleCapture = true;
16088
+ this.titleBuffer = this.accumulator.substring(2);
16089
+ persistence.logger.debug(`${this.getLogPrefix()} Started title capture`);
16090
+ } else if (this.accumulator.length > 0) {
16091
+ this.contentBuffer = this.accumulator;
16092
+ }
16093
+ } else if (this.inTitleCapture) {
16094
+ this.titleBuffer = this.accumulator.substring(2);
16095
+ const titleEndIndex = this.titleBuffer.indexOf("**");
16096
+ if (titleEndIndex !== -1) {
16097
+ const title = this.titleBuffer.substring(0, titleEndIndex);
16098
+ const afterTitle = this.titleBuffer.substring(titleEndIndex + 2);
16099
+ this.hasTitle = true;
16100
+ this.inTitleCapture = false;
16101
+ this.currentTitle = title;
16102
+ this.contentBuffer = afterTitle;
16103
+ this.currentCallId = node_crypto.randomUUID();
16104
+ persistence.logger.debug(`${this.getLogPrefix()} Title captured: "${title}"`);
16105
+ this.sendToolCallStart(title);
16106
+ }
16107
+ } else if (this.hasTitle) {
16108
+ const titleStartIndex = this.accumulator.indexOf("**");
16109
+ if (titleStartIndex !== -1) {
16110
+ this.contentBuffer = this.accumulator.substring(
16111
+ titleStartIndex + 2 + this.currentTitle.length + 2
16112
+ );
16113
+ }
16114
+ } else {
16115
+ this.contentBuffer = this.accumulator;
16116
+ }
15764
16117
  }
15765
- const short = command.length > 80 ? `${command.slice(0, 77)}...` : command;
15766
- return `Run \`${short}\``;
15767
- }
15768
- function turnTimestampMs(turn) {
15769
- const seconds = turn.startedAt ?? turn.completedAt;
15770
- return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15771
- }
15772
- function completedTimestampMs(turn) {
15773
- const seconds = turn.completedAt ?? turn.startedAt;
15774
- return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15775
- }
15776
- function textFromInputItems(items) {
15777
- if (!Array.isArray(items)) {
15778
- return null;
16118
+ /**
16119
+ * Send the tool call start message.
16120
+ */
16121
+ sendToolCallStart(title) {
16122
+ if (!this.currentCallId || this.toolCallStarted) {
16123
+ return;
16124
+ }
16125
+ const toolCall = {
16126
+ type: "tool-call",
16127
+ name: this.getToolName(),
16128
+ callId: this.currentCallId,
16129
+ input: {
16130
+ title
16131
+ },
16132
+ id: node_crypto.randomUUID()
16133
+ };
16134
+ persistence.logger.debug(`${this.getLogPrefix()} Sending tool call start for: "${title}"`);
16135
+ this.onMessage?.(toolCall);
16136
+ this.toolCallStarted = true;
15779
16137
  }
15780
- const text = items.filter((item) => Boolean(item) && typeof item === "object" && item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n").trim();
15781
- return text.length > 0 ? text : null;
15782
- }
15783
- function reasoningText(item) {
15784
- const summary = item.summary;
15785
- const content = item.content;
15786
- const parts = [
15787
- ...Array.isArray(summary) ? summary : [],
15788
- ...Array.isArray(content) ? content : []
15789
- ].filter((value) => typeof value === "string" && value.trim().length > 0);
15790
- const text = parts.join("\n").trim();
15791
- return text.length > 0 ? text : null;
15792
- }
15793
- function turnStatus(turn) {
15794
- const status = typeof turn.status === "string" ? turn.status : null;
15795
- if (status === "failed") {
15796
- return "failed";
16138
+ /**
16139
+ * Complete the reasoning section.
16140
+ * Returns true if reasoning was completed, false if there was nothing to complete.
16141
+ */
16142
+ completeReasoning(fullText) {
16143
+ const text = fullText ?? this.accumulator;
16144
+ if (!text.trim() && !this.toolCallStarted) {
16145
+ persistence.logger.debug(`${this.getLogPrefix()} Complete called but no content accumulated, skipping`);
16146
+ return false;
16147
+ }
16148
+ let title;
16149
+ let content = text;
16150
+ if (text.startsWith("**")) {
16151
+ const titleEndIndex = text.indexOf("**", 2);
16152
+ if (titleEndIndex !== -1) {
16153
+ title = text.substring(2, titleEndIndex);
16154
+ content = text.substring(titleEndIndex + 2).trim();
16155
+ }
16156
+ }
16157
+ persistence.logger.debug(`${this.getLogPrefix()} Complete reasoning - Title: "${title}", Has content: ${content.length > 0}`);
16158
+ if (title && !this.toolCallStarted) {
16159
+ this.currentCallId = this.currentCallId || node_crypto.randomUUID();
16160
+ this.sendToolCallStart(title);
16161
+ }
16162
+ if (this.toolCallStarted && this.currentCallId) {
16163
+ const toolResult = {
16164
+ type: "tool-call-result",
16165
+ callId: this.currentCallId,
16166
+ output: {
16167
+ content,
16168
+ status: "completed"
16169
+ },
16170
+ id: node_crypto.randomUUID()
16171
+ };
16172
+ persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result`);
16173
+ this.onMessage?.(toolResult);
16174
+ } else if (content.trim()) {
16175
+ const reasoningMessage = {
16176
+ type: "reasoning",
16177
+ message: content,
16178
+ id: node_crypto.randomUUID()
16179
+ };
16180
+ persistence.logger.debug(`${this.getLogPrefix()} Sending reasoning message`);
16181
+ this.onMessage?.(reasoningMessage);
16182
+ }
16183
+ this.resetState();
16184
+ return true;
15797
16185
  }
15798
- if (status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted") {
15799
- return "cancelled";
16186
+ /**
16187
+ * Abort the current reasoning section.
16188
+ */
16189
+ abort() {
16190
+ persistence.logger.debug(`${this.getLogPrefix()} Abort called`);
16191
+ this.finishCurrentToolCall("canceled");
16192
+ this.resetState();
15800
16193
  }
15801
- return "completed";
15802
- }
15803
- function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output) {
15804
- const time = turnTimestampMs(turn);
15805
- const opts = { turn: turn.id, time, codexItemId: item.id };
15806
- envelopes.push(api.createEnvelope("agent", {
15807
- t: "tool-call-start",
15808
- call: item.id,
15809
- name,
15810
- title,
15811
- description: title,
15812
- args
15813
- }, {
15814
- ...opts,
15815
- id: `${item.id}:start`
15816
- }));
15817
- if (output && output.trim().length > 0) {
15818
- envelopes.push(api.createEnvelope("agent", {
15819
- t: "text",
15820
- text: output,
15821
- thinking: true
15822
- }, {
15823
- ...opts,
15824
- id: `${item.id}:output`
15825
- }));
16194
+ /**
16195
+ * Reset the processor state.
16196
+ */
16197
+ reset() {
16198
+ this.finishCurrentToolCall("canceled");
16199
+ this.resetState();
15826
16200
  }
15827
- envelopes.push(api.createEnvelope("agent", {
15828
- t: "tool-call-end",
15829
- call: item.id
15830
- }, {
15831
- ...opts,
15832
- id: `${item.id}:end`,
15833
- time: completedTimestampMs(turn)
15834
- }));
15835
- }
15836
- function mapCodexThreadToSessionEnvelopes(thread) {
15837
- const envelopes = [];
15838
- for (const turn of thread.turns ?? []) {
15839
- const startedAt = turnTimestampMs(turn);
15840
- const completedAt = completedTimestampMs(turn);
15841
- envelopes.push(api.createEnvelope("agent", { t: "turn-start" }, {
15842
- id: `${turn.id}:start`,
15843
- turn: turn.id,
15844
- time: startedAt
15845
- }));
15846
- for (const item of turn.items ?? []) {
15847
- switch (item.type) {
15848
- case "userMessage": {
15849
- const text = textFromInputItems(item.content);
15850
- if (text) {
15851
- envelopes.push(api.createEnvelope("user", { t: "text", text }, {
15852
- id: item.id,
15853
- time: startedAt,
15854
- codexItemId: item.id
15855
- }));
15856
- }
15857
- break;
15858
- }
15859
- case "agentMessage": {
15860
- const text = typeof item.text === "string" ? item.text.trim() : "";
15861
- if (text.length > 0) {
15862
- envelopes.push(api.createEnvelope("agent", { t: "text", text }, {
15863
- id: item.id,
15864
- turn: turn.id,
15865
- time: completedAt,
15866
- codexItemId: item.id
15867
- }));
15868
- }
15869
- break;
15870
- }
15871
- case "reasoning": {
15872
- const text = reasoningText(item);
15873
- if (text) {
15874
- envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, {
15875
- id: item.id,
15876
- turn: turn.id,
15877
- time: startedAt,
15878
- codexItemId: item.id
15879
- }));
15880
- }
15881
- break;
15882
- }
15883
- case "commandExecution": {
15884
- const command = typeof item.command === "string" ? item.command : "";
15885
- emitHistoricalToolCall(
15886
- envelopes,
15887
- turn,
15888
- item,
15889
- "CodexBash",
15890
- commandToTitle(command),
15891
- { command, cwd: item.cwd },
15892
- typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : null
15893
- );
15894
- break;
15895
- }
15896
- case "fileChange": {
15897
- const title = "Apply patch";
15898
- emitHistoricalToolCall(
15899
- envelopes,
15900
- turn,
15901
- item,
15902
- "CodexPatch",
15903
- title,
15904
- { changes: item.changes, status: item.status },
15905
- null
15906
- );
15907
- break;
15908
- }
15909
- case "mcpToolCall": {
15910
- const title = `${item.server}.${item.tool}`;
15911
- const output = item.error !== void 0 && item.error !== null ? String(item.error) : item.result !== void 0 && item.result !== null ? String(item.result) : null;
15912
- emitHistoricalToolCall(
15913
- envelopes,
15914
- turn,
15915
- item,
15916
- "McpTool",
15917
- title,
15918
- {
15919
- server: item.server,
15920
- tool: item.tool,
15921
- arguments: item.arguments
15922
- },
15923
- output
15924
- );
15925
- break;
15926
- }
15927
- }
16201
+ /**
16202
+ * Finish current tool call if one is in progress.
16203
+ */
16204
+ finishCurrentToolCall(status) {
16205
+ if (this.toolCallStarted && this.currentCallId) {
16206
+ const toolResult = {
16207
+ type: "tool-call-result",
16208
+ callId: this.currentCallId,
16209
+ output: {
16210
+ content: this.contentBuffer || "",
16211
+ status
16212
+ },
16213
+ id: node_crypto.randomUUID()
16214
+ };
16215
+ persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result with status: ${status}`);
16216
+ this.onMessage?.(toolResult);
15928
16217
  }
15929
- envelopes.push(api.createEnvelope("agent", { t: "turn-end", status: turnStatus(turn) }, {
15930
- id: `${turn.id}:end`,
15931
- turn: turn.id,
15932
- time: completedAt
15933
- }));
15934
16218
  }
15935
- return envelopes;
15936
- }
15937
- function patchDescription(changes) {
15938
- if (!changes || typeof changes !== "object") {
15939
- return "Applying patch";
16219
+ /**
16220
+ * Reset internal state.
16221
+ */
16222
+ resetState() {
16223
+ this.accumulator = "";
16224
+ this.inTitleCapture = false;
16225
+ this.titleBuffer = "";
16226
+ this.contentBuffer = "";
16227
+ this.hasTitle = false;
16228
+ this.currentCallId = null;
16229
+ this.toolCallStarted = false;
16230
+ this.currentTitle = null;
15940
16231
  }
15941
- const fileCount = Object.keys(changes).length;
15942
- if (fileCount === 1) {
15943
- return "Applying patch to 1 file";
16232
+ /**
16233
+ * Get the current call ID for tool result matching.
16234
+ */
16235
+ getCurrentCallId() {
16236
+ return this.currentCallId;
16237
+ }
16238
+ /**
16239
+ * Check if a tool call has been started.
16240
+ */
16241
+ hasStartedToolCall() {
16242
+ return this.toolCallStarted;
15944
16243
  }
15945
- return `Applying patch to ${fileCount} files`;
15946
16244
  }
15947
- function pickTurnEndStatus(message, type) {
15948
- const rawStatus = message.status;
15949
- if (rawStatus === "completed" || rawStatus === "failed" || rawStatus === "cancelled") {
15950
- return rawStatus;
16245
+
16246
+ class ReasoningProcessor extends BaseReasoningProcessor {
16247
+ getToolName() {
16248
+ return "CodexReasoning";
15951
16249
  }
15952
- if (rawStatus === "canceled") {
15953
- return "cancelled";
16250
+ getLogPrefix() {
16251
+ return "[ReasoningProcessor]";
15954
16252
  }
15955
- if (type === "turn_aborted") {
15956
- const reason = message.reason;
15957
- const error = message.error;
15958
- if (typeof reason === "string" && /(fail|error)/i.test(reason) || typeof error === "string" && error.length > 0 || error !== void 0 && error !== null && typeof error === "object") {
15959
- return "failed";
15960
- }
15961
- return "cancelled";
16253
+ /**
16254
+ * Process a reasoning delta and accumulate content.
16255
+ */
16256
+ processDelta(delta) {
16257
+ this.processInput(delta);
15962
16258
  }
15963
- if (message.error !== void 0 && message.error !== null) {
15964
- return "failed";
16259
+ /**
16260
+ * Complete the reasoning section with final text.
16261
+ */
16262
+ complete(fullText) {
16263
+ this.completeReasoning(fullText);
15965
16264
  }
15966
- return "completed";
15967
16265
  }
15968
- function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15969
- const type = message.type;
15970
- const startedSubagents = getStartedSubagents(state);
15971
- const activeSubagents = getActiveSubagents(state);
15972
- const providerSubagentToSessionSubagent = getProviderSubagentToSessionSubagent(state);
15973
- if (type === "task_started") {
15974
- const turnId = cuid2.createId();
15975
- const turnStart = api.createEnvelope("agent", { t: "turn-start" }, { turn: turnId });
15976
- startedSubagents.clear();
15977
- activeSubagents.clear();
15978
- providerSubagentToSessionSubagent.clear();
15979
- return {
15980
- currentTurnId: turnId,
15981
- startedSubagents,
15982
- activeSubagents,
15983
- providerSubagentToSessionSubagent,
15984
- envelopes: [turnStart]
15985
- };
15986
- }
15987
- if (type === "task_complete" || type === "turn_aborted") {
15988
- if (!state.currentTurnId) {
15989
- return {
15990
- currentTurnId: null,
15991
- startedSubagents,
15992
- activeSubagents,
15993
- providerSubagentToSessionSubagent,
15994
- envelopes: []
15995
- };
15996
- }
15997
- const lifecycleOpts = { turn: state.currentTurnId };
15998
- providerSubagentToSessionSubagent.clear();
15999
- return {
16000
- currentTurnId: null,
16001
- startedSubagents,
16002
- activeSubagents,
16003
- providerSubagentToSessionSubagent,
16004
- envelopes: [
16005
- ...emitSubagentStops(lifecycleOpts, startedSubagents, activeSubagents),
16006
- api.createEnvelope("agent", {
16007
- t: "turn-end",
16008
- status: pickTurnEndStatus(message, type)
16009
- }, lifecycleOpts)
16010
- ]
16011
- };
16012
- }
16013
- if (type === "token_count") {
16014
- return {
16015
- currentTurnId: state.currentTurnId,
16016
- startedSubagents,
16017
- activeSubagents,
16018
- providerSubagentToSessionSubagent,
16019
- envelopes: []
16020
- };
16266
+
16267
+ class DiffProcessor {
16268
+ previousDiff = null;
16269
+ onMessage = null;
16270
+ constructor(onMessage) {
16271
+ this.onMessage = onMessage || null;
16021
16272
  }
16022
- const subagent = resolveSessionSubagent(message, providerSubagentToSessionSubagent);
16023
- const opts = buildEnvelopeOptions(state.currentTurnId, subagent);
16024
- if (type === "agent_message") {
16025
- if (typeof message.message !== "string") {
16026
- return {
16027
- currentTurnId: state.currentTurnId,
16028
- startedSubagents,
16029
- activeSubagents,
16030
- providerSubagentToSessionSubagent,
16031
- envelopes: []
16273
+ /**
16274
+ * Process a turn_diff message and check if the unified_diff has changed
16275
+ */
16276
+ processDiff(unifiedDiff) {
16277
+ if (this.previousDiff !== unifiedDiff) {
16278
+ persistence.logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
16279
+ const callId = node_crypto.randomUUID();
16280
+ const toolCall = {
16281
+ type: "tool-call",
16282
+ name: "CodexDiff",
16283
+ callId,
16284
+ input: {
16285
+ unified_diff: unifiedDiff
16286
+ },
16287
+ id: node_crypto.randomUUID()
16032
16288
  };
16033
- }
16034
- const envelopes = [];
16035
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16036
- envelopes.push(api.createEnvelope("agent", { t: "text", text: message.message }, opts));
16037
- return {
16038
- currentTurnId: state.currentTurnId,
16039
- startedSubagents,
16040
- activeSubagents,
16041
- providerSubagentToSessionSubagent,
16042
- envelopes
16043
- };
16044
- }
16045
- if (type === "agent_reasoning" || type === "agent_reasoning_delta") {
16046
- const text = typeof message.text === "string" ? message.text : typeof message.delta === "string" ? message.delta : null;
16047
- if (!text) {
16048
- return {
16049
- currentTurnId: state.currentTurnId,
16050
- startedSubagents,
16051
- activeSubagents,
16052
- providerSubagentToSessionSubagent,
16053
- envelopes: []
16289
+ this.onMessage?.(toolCall);
16290
+ const toolResult = {
16291
+ type: "tool-call-result",
16292
+ callId,
16293
+ output: {
16294
+ status: "completed"
16295
+ },
16296
+ id: node_crypto.randomUUID()
16054
16297
  };
16298
+ this.onMessage?.(toolResult);
16055
16299
  }
16056
- const envelopes = [];
16057
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16058
- envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, opts));
16059
- return {
16060
- currentTurnId: state.currentTurnId,
16061
- startedSubagents,
16062
- activeSubagents,
16063
- providerSubagentToSessionSubagent,
16064
- envelopes
16065
- };
16300
+ this.previousDiff = unifiedDiff;
16301
+ persistence.logger.debug("[DiffProcessor] Updated stored diff");
16066
16302
  }
16067
- if (type === "exec_command_begin") {
16068
- const call = pickCallId(message);
16069
- const { call_id: _callIdSnake, callId: _callIdCamel, type: _type, ...args } = message;
16070
- const command = summarizeCommand(args.command);
16071
- const description = typeof args.description === "string" ? args.description : command ?? "Execute command";
16072
- const envelopes = [];
16073
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16074
- envelopes.push(
16075
- api.createEnvelope("agent", {
16076
- t: "tool-call-start",
16077
- call,
16078
- name: "CodexBash",
16079
- title: commandToTitle(command),
16080
- description,
16081
- args
16082
- }, opts)
16083
- );
16084
- return {
16085
- currentTurnId: state.currentTurnId,
16086
- startedSubagents,
16087
- activeSubagents,
16088
- providerSubagentToSessionSubagent,
16089
- envelopes
16090
- };
16303
+ /**
16304
+ * Reset the processor state (called on task_complete or turn_aborted)
16305
+ */
16306
+ reset() {
16307
+ persistence.logger.debug("[DiffProcessor] Resetting diff state");
16308
+ this.previousDiff = null;
16091
16309
  }
16092
- if (type === "exec_command_end") {
16093
- const call = pickCallId(message);
16094
- const envelopes = [];
16095
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16096
- envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16097
- return {
16098
- currentTurnId: state.currentTurnId,
16099
- startedSubagents,
16100
- activeSubagents,
16101
- providerSubagentToSessionSubagent,
16102
- envelopes
16103
- };
16310
+ /**
16311
+ * Set the message callback for sending messages directly
16312
+ */
16313
+ setMessageCallback(callback) {
16314
+ this.onMessage = callback;
16104
16315
  }
16105
- if (type === "patch_apply_begin") {
16106
- const call = pickCallId(message);
16107
- const autoApproved = message.auto_approved;
16108
- const changes = message.changes;
16109
- const envelopes = [];
16110
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16111
- envelopes.push(
16112
- api.createEnvelope("agent", {
16113
- t: "tool-call-start",
16114
- call,
16115
- name: "CodexPatch",
16116
- title: "Apply patch",
16117
- description: patchDescription(changes),
16118
- args: {
16119
- auto_approved: autoApproved,
16120
- changes
16121
- }
16122
- }, opts)
16123
- );
16124
- return {
16125
- currentTurnId: state.currentTurnId,
16126
- startedSubagents,
16127
- activeSubagents,
16128
- providerSubagentToSessionSubagent,
16129
- envelopes
16130
- };
16316
+ /**
16317
+ * Get the current diff value
16318
+ */
16319
+ getCurrentDiff() {
16320
+ return this.previousDiff;
16131
16321
  }
16132
- if (type === "patch_apply_end") {
16133
- const call = pickCallId(message);
16134
- const envelopes = [];
16135
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16136
- envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16137
- return {
16138
- currentTurnId: state.currentTurnId,
16139
- startedSubagents,
16140
- activeSubagents,
16141
- providerSubagentToSessionSubagent,
16142
- envelopes
16322
+ }
16323
+
16324
+ const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
16325
+ const [messages, setMessages] = React.useState([]);
16326
+ const [confirmationMode, setConfirmationMode] = React.useState(false);
16327
+ const [actionInProgress, setActionInProgress] = React.useState(false);
16328
+ const confirmationTimeoutRef = React.useRef(null);
16329
+ const { stdout } = ink.useStdout();
16330
+ const terminalWidth = stdout.columns || 80;
16331
+ const terminalHeight = stdout.rows || 24;
16332
+ React.useEffect(() => {
16333
+ setMessages(messageBuffer.getMessages());
16334
+ const unsubscribe = messageBuffer.onUpdate((newMessages) => {
16335
+ setMessages(newMessages);
16336
+ });
16337
+ return () => {
16338
+ unsubscribe();
16339
+ if (confirmationTimeoutRef.current) {
16340
+ clearTimeout(confirmationTimeoutRef.current);
16341
+ }
16143
16342
  };
16144
- }
16343
+ }, [messageBuffer]);
16344
+ const resetConfirmation = React.useCallback(() => {
16345
+ setConfirmationMode(false);
16346
+ if (confirmationTimeoutRef.current) {
16347
+ clearTimeout(confirmationTimeoutRef.current);
16348
+ confirmationTimeoutRef.current = null;
16349
+ }
16350
+ }, []);
16351
+ const setConfirmationWithTimeout = React.useCallback(() => {
16352
+ setConfirmationMode(true);
16353
+ if (confirmationTimeoutRef.current) {
16354
+ clearTimeout(confirmationTimeoutRef.current);
16355
+ }
16356
+ confirmationTimeoutRef.current = setTimeout(() => {
16357
+ resetConfirmation();
16358
+ }, 15e3);
16359
+ }, [resetConfirmation]);
16360
+ ink.useInput(React.useCallback(async (input, key) => {
16361
+ if (actionInProgress) return;
16362
+ if (key.ctrl && input === "c") {
16363
+ if (confirmationMode) {
16364
+ resetConfirmation();
16365
+ setActionInProgress(true);
16366
+ await new Promise((resolve) => setTimeout(resolve, 100));
16367
+ onExit?.();
16368
+ } else {
16369
+ setConfirmationWithTimeout();
16370
+ }
16371
+ return;
16372
+ }
16373
+ if (confirmationMode) {
16374
+ resetConfirmation();
16375
+ }
16376
+ }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
16377
+ const getMessageColor = (type) => {
16378
+ switch (type) {
16379
+ case "user":
16380
+ return "magenta";
16381
+ case "assistant":
16382
+ return "cyan";
16383
+ case "system":
16384
+ return "blue";
16385
+ case "tool":
16386
+ return "yellow";
16387
+ case "result":
16388
+ return "green";
16389
+ case "status":
16390
+ return "gray";
16391
+ default:
16392
+ return "white";
16393
+ }
16394
+ };
16395
+ const formatMessage = (msg) => {
16396
+ const lines = msg.content.split("\n");
16397
+ const maxLineLength = terminalWidth - 10;
16398
+ return lines.map((line) => {
16399
+ if (line.length <= maxLineLength) return line;
16400
+ const chunks = [];
16401
+ for (let i = 0; i < line.length; i += maxLineLength) {
16402
+ chunks.push(line.slice(i, i + maxLineLength));
16403
+ }
16404
+ return chunks.join("\n");
16405
+ }).join("\n");
16406
+ };
16407
+ return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
16408
+ ink.Box,
16409
+ {
16410
+ flexDirection: "column",
16411
+ width: terminalWidth,
16412
+ height: terminalHeight - 4,
16413
+ borderStyle: "round",
16414
+ borderColor: "gray",
16415
+ paddingX: 1,
16416
+ overflow: "hidden"
16417
+ },
16418
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "\u{1F916} Codex Agent Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
16419
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
16420
+ // Show only the last messages that fit in the available space
16421
+ messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
16422
+ ))
16423
+ ), /* @__PURE__ */ React.createElement(
16424
+ ink.Box,
16425
+ {
16426
+ width: terminalWidth,
16427
+ borderStyle: "round",
16428
+ borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "green",
16429
+ paddingX: 2,
16430
+ justifyContent: "center",
16431
+ alignItems: "center",
16432
+ flexDirection: "column"
16433
+ },
16434
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F916} Codex Agent Running \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
16435
+ ));
16436
+ };
16437
+
16438
+ function createOfflineSessionStub(sessionTag) {
16145
16439
  return {
16146
- currentTurnId: state.currentTurnId,
16147
- startedSubagents,
16148
- activeSubagents,
16149
- providerSubagentToSessionSubagent,
16150
- envelopes: []
16440
+ sessionId: `offline-${sessionTag}`,
16441
+ sendCodexMessage: () => {
16442
+ },
16443
+ sendAgentMessage: () => {
16444
+ },
16445
+ sendAgentUsageSnapshot: () => false,
16446
+ sendClaudeSessionMessage: () => {
16447
+ },
16448
+ keepAlive: () => {
16449
+ },
16450
+ sendSessionEvent: () => {
16451
+ },
16452
+ sendSessionDeath: () => {
16453
+ },
16454
+ updateLifecycleState: () => {
16455
+ },
16456
+ requestControlTransfer: async () => {
16457
+ },
16458
+ flush: async () => {
16459
+ },
16460
+ close: async () => {
16461
+ },
16462
+ updateMetadata: () => {
16463
+ },
16464
+ updateAgentState: () => {
16465
+ },
16466
+ onUserMessage: () => {
16467
+ },
16468
+ onFileEvent: () => {
16469
+ },
16470
+ trackAttachmentDownload: () => {
16471
+ },
16472
+ drainAttachmentsForUserMessage: async () => [],
16473
+ rpcHandlerManager: {
16474
+ registerHandler: () => {
16475
+ }
16476
+ }
16151
16477
  };
16152
16478
  }
16153
- function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
16154
- const toolLikeMessage = message;
16155
- const opts = buildEnvelopeOptions(state.currentTurnId);
16156
- if (message.type === "reasoning") {
16157
- return [api.createEnvelope("agent", {
16158
- t: "text",
16159
- text: message.message,
16160
- thinking: true
16161
- }, opts)];
16479
+
16480
+ function setupOfflineReconnection(opts) {
16481
+ const { api: api$1, sessionTag, metadata, state, response, onSessionSwap } = opts;
16482
+ let session;
16483
+ let reconnectionHandle = null;
16484
+ if (!response) {
16485
+ session = createOfflineSessionStub(sessionTag);
16486
+ reconnectionHandle = api.startOfflineReconnection({
16487
+ serverUrl: persistence.configuration.serverUrl,
16488
+ onReconnected: async () => {
16489
+ const resp = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
16490
+ if (!resp) throw new Error("Server unavailable");
16491
+ const realSession = api$1.sessionSyncClient(resp);
16492
+ onSessionSwap(realSession);
16493
+ return realSession;
16494
+ },
16495
+ onNotify: (msg) => {
16496
+ console.log(msg);
16497
+ }
16498
+ });
16499
+ return { session, reconnectionHandle, isOffline: true };
16500
+ } else {
16501
+ session = api$1.sessionSyncClient(response, opts.sessionClientOptions);
16502
+ return { session, reconnectionHandle: null, isOffline: false };
16162
16503
  }
16163
- if (message.type === "tool-call") {
16164
- const title = typeof toolLikeMessage.input?.title === "string" ? toolLikeMessage.input.title : `${toolLikeMessage.name || "Tool"} call`;
16165
- return [api.createEnvelope("agent", {
16166
- t: "tool-call-start",
16167
- call: toolLikeMessage.callId,
16168
- name: toolLikeMessage.name || "unknown",
16169
- title,
16170
- description: title,
16171
- args: toolLikeMessage.input && typeof toolLikeMessage.input === "object" ? toolLikeMessage.input : {}
16172
- }, opts)];
16504
+ }
16505
+
16506
+ function resolveCodexExecutionPolicy(permissionMode, sandboxManagedByHappy) {
16507
+ if (sandboxManagedByHappy) {
16508
+ return {
16509
+ approvalPolicy: "never",
16510
+ sandbox: "danger-full-access"
16511
+ };
16173
16512
  }
16174
- if (message.type === "tool-call-result") {
16175
- const envelopes = [];
16176
- const content = toolLikeMessage.output?.content;
16177
- if (typeof content === "string" && content.trim().length > 0) {
16178
- envelopes.push(api.createEnvelope("agent", {
16179
- t: "text",
16180
- text: content,
16181
- thinking: true
16182
- }, opts));
16513
+ const approvalPolicy = (() => {
16514
+ switch (permissionMode) {
16515
+ // Codex native modes
16516
+ case "default":
16517
+ return "untrusted";
16518
+ // Ask for non-trusted commands
16519
+ case "read-only":
16520
+ return "never";
16521
+ // Never ask, read-only enforced by sandbox
16522
+ case "safe-yolo":
16523
+ return "on-request";
16524
+ // Workspace auto-run; request approval for escalation (0.153 removed on-failure)
16525
+ case "yolo":
16526
+ return "never";
16527
+ // Full YOLO: never interrupt for approvals
16528
+ // Defensive fallback for Claude-specific modes (backward compatibility)
16529
+ case "bypassPermissions":
16530
+ return "never";
16531
+ // Full access: map to yolo behavior
16532
+ case "acceptEdits":
16533
+ return "on-request";
16534
+ // Let model decide (closest to auto-approve edits)
16535
+ case "plan":
16536
+ return "untrusted";
16537
+ // Conservative: ask for non-trusted
16538
+ default:
16539
+ return "untrusted";
16183
16540
  }
16184
- envelopes.push(api.createEnvelope("agent", {
16185
- t: "tool-call-end",
16186
- call: toolLikeMessage.callId
16187
- }, opts));
16188
- return envelopes;
16189
- }
16190
- return [];
16541
+ })();
16542
+ const sandbox = (() => {
16543
+ switch (permissionMode) {
16544
+ // Codex native modes
16545
+ case "default":
16546
+ return "workspace-write";
16547
+ // Can write in workspace
16548
+ case "read-only":
16549
+ return "read-only";
16550
+ // Read-only filesystem
16551
+ case "safe-yolo":
16552
+ return "workspace-write";
16553
+ // Can write in workspace
16554
+ case "yolo":
16555
+ return "danger-full-access";
16556
+ // Full system access
16557
+ // Defensive fallback for Claude-specific modes
16558
+ case "bypassPermissions":
16559
+ return "danger-full-access";
16560
+ // Full access: map to yolo
16561
+ case "acceptEdits":
16562
+ return "workspace-write";
16563
+ // Can edit files in workspace
16564
+ case "plan":
16565
+ return "workspace-write";
16566
+ // Can write for planning
16567
+ default:
16568
+ return "workspace-write";
16569
+ }
16570
+ })();
16571
+ return { approvalPolicy, sandbox };
16191
16572
  }
16192
16573
 
16193
16574
  async function resumeExistingThread(opts) {
@@ -16659,6 +17040,13 @@ async function runCodex(opts) {
16659
17040
  process.exit(1);
16660
17041
  }
16661
17042
  };
17043
+ session.rpcHandlerManager.registerHandler("codex-child-read", async (args) => {
17044
+ try {
17045
+ return await readChildThread(client, args?.threadId);
17046
+ } catch (error) {
17047
+ return { error: error instanceof Error ? error.message : "Child read failed" };
17048
+ }
17049
+ });
16662
17050
  session.rpcHandlerManager.registerHandler("abort", handleAbort);
16663
17051
  const disposeTermination = registerKillSessionHandler(session.rpcHandlerManager, handleKillSession, session, { processSignals: true });
16664
17052
  const messageBuffer = new MessageBuffer();
@@ -17199,7 +17587,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17199
17587
  process.exit(0);
17200
17588
  } else if (subcommand === "install-terminal-hooks") {
17201
17589
  try {
17202
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-DPmEaHg2.cjs'); });
17590
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-2POBqVlu.cjs'); });
17203
17591
  const command = parseTerminalHooksArgs(args.slice(1));
17204
17592
  if (command.action === "help") {
17205
17593
  console.log(TERMINAL_HOOKS_HELP);
@@ -17216,7 +17604,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17216
17604
  }
17217
17605
  } else if (subcommand === "spawn") {
17218
17606
  try {
17219
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-gha6nGx-.cjs'); });
17607
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-CMjhlwlD.cjs'); });
17220
17608
  await handleSpawnCommand(args.slice(1));
17221
17609
  } catch (error) {
17222
17610
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17228,7 +17616,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17228
17616
  return;
17229
17617
  } else if (subcommand === "sessions") {
17230
17618
  try {
17231
- const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-DyC5Attg.cjs'); });
17619
+ const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-Q4BR02dG.cjs'); });
17232
17620
  await handleSessionsCommand(args.slice(1));
17233
17621
  } catch (error) {
17234
17622
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17240,7 +17628,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17240
17628
  return;
17241
17629
  } else if (subcommand === "send") {
17242
17630
  try {
17243
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-Bo6iKuXr.cjs'); });
17631
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-DP6n2J9x.cjs'); });
17244
17632
  await handleSendCommand(args.slice(1));
17245
17633
  } catch (error) {
17246
17634
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17346,9 +17734,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17346
17734
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
17347
17735
  const projectId = args[3];
17348
17736
  try {
17349
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-C3e90AIH.cjs'); });
17350
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-D6kiAfXJ.cjs'); }).then(function (n) { return n.persistence; });
17351
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-Dabm17Ln.cjs'); }).then(function (n) { return n.api; });
17737
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config--cxxmkOd.cjs'); });
17738
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-DRrdJ7gr.cjs'); }).then(function (n) { return n.persistence; });
17739
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-DbBPNIwf.cjs'); }).then(function (n) { return n.api; });
17352
17740
  let userEmail = void 0;
17353
17741
  try {
17354
17742
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -17379,7 +17767,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17379
17767
  }
17380
17768
  if (geminiSubcommand === "project" && args[2] === "get") {
17381
17769
  try {
17382
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-C3e90AIH.cjs'); });
17770
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config--cxxmkOd.cjs'); });
17383
17771
  const config = readGeminiLocalConfig();
17384
17772
  if (config.googleCloudProject) {
17385
17773
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -17419,7 +17807,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17419
17807
  process.exit(0);
17420
17808
  }
17421
17809
  try {
17422
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-BPPnulIk.cjs'); });
17810
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-B9Rc-H0G.cjs'); });
17423
17811
  let startedBy = void 0;
17424
17812
  for (let i = 1; i < args.length; i++) {
17425
17813
  if (args[i] === "--started-by") {
@@ -17449,7 +17837,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17449
17837
  }
17450
17838
  };
17451
17839
  try {
17452
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-jqG8CV0c.cjs'); });
17840
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BPVQFcKW.cjs'); });
17453
17841
  const parsed = parsePiRunnerArgs(args.slice(1));
17454
17842
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
17455
17843
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -17477,7 +17865,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17477
17865
  return;
17478
17866
  } else if (subcommand === "acp") {
17479
17867
  try {
17480
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-jqG8CV0c.cjs'); });
17868
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BPVQFcKW.cjs'); });
17481
17869
  let startedBy = void 0;
17482
17870
  let verbose = false;
17483
17871
  const acpArgs = [];
@@ -17517,7 +17905,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17517
17905
  return;
17518
17906
  } else if (subcommand === "openclaw") {
17519
17907
  try {
17520
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-BB3Ivt1S.cjs'); });
17908
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-BsRRKDvF.cjs'); });
17521
17909
  let startedBy = void 0;
17522
17910
  let verbose = false;
17523
17911
  let gatewayUrl;
@@ -17568,7 +17956,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17568
17956
  return;
17569
17957
  } else if (subcommand === "todo") {
17570
17958
  try {
17571
- const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-BaNaNZ8c.cjs'); });
17959
+ const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-CaYedmtq.cjs'); });
17572
17960
  await handleTodoCommand(args.slice(1));
17573
17961
  } catch (error) {
17574
17962
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -17577,7 +17965,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17577
17965
  return;
17578
17966
  } else if (subcommand === "teams") {
17579
17967
  try {
17580
- const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-BbzWNV9e.cjs'); });
17968
+ const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-CkdUH10-.cjs'); });
17581
17969
  await handleTeamsCommand(args.slice(1));
17582
17970
  } catch (error) {
17583
17971
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -17586,7 +17974,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17586
17974
  return;
17587
17975
  } else if (subcommand === "mcp" && args.length === 1) {
17588
17976
  try {
17589
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-DGZmUpuZ.cjs'); });
17977
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-C1JQsM1s.cjs'); });
17590
17978
  await handleMcpCommand();
17591
17979
  } catch (error) {
17592
17980
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}