very-happy-cli 0.2.133 → 0.2.135

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-DgG8o4jd.cjs → AcpBackend-C0Bwx6bc.cjs} +3 -3
  2. package/dist/{AcpBackend-BQm7dNza.mjs → AcpBackend-CkDpu1n_.mjs} +3 -3
  3. package/dist/{AcpSessionManager-C7unPEZO.mjs → AcpSessionManager-DB_mfFaH.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DDFEmhQp.cjs → AcpSessionManager-DqvUp-UU.cjs} +1 -1
  5. package/dist/{agentGuidance-BaplwDcV.mjs → agentGuidance-7MByWxKj.mjs} +1 -1
  6. package/dist/{agentGuidance-BItqIZTf.cjs → agentGuidance-DlZM_YPV.cjs} +1 -1
  7. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  8. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  9. package/dist/{config-uLywzv5t.cjs → config-BO0ZHyoB.cjs} +4 -4
  10. package/dist/{config-BZ8xEnOv.mjs → config-DzvbcL_x.mjs} +4 -4
  11. package/dist/{index-Daga0ztv.cjs → index-BEpZjXs4.cjs} +15 -9
  12. package/dist/{index-ClCeQ1FW.mjs → index-C33JB4xB.mjs} +16 -10
  13. package/dist/{index-D8i2EViA.mjs → index-CpEdOGpe.mjs} +4 -4
  14. package/dist/{index-CdH3ecC5.cjs → index-DHI6Y1xW.cjs} +4 -4
  15. package/dist/{index-6Xm0CgN_.cjs → index-DNbKbe23.cjs} +1536 -1202
  16. package/dist/{index-EsR8ayIg.mjs → index-gefLe1Bk.mjs} +1534 -1200
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-NH4kiB2E.mjs → installTerminalHooks-BWoHGCew.mjs} +4 -4
  20. package/dist/{installTerminalHooks-CKG0MYVK.cjs → installTerminalHooks-gmWKACNb.cjs} +4 -4
  21. package/dist/lib.cjs +2 -2
  22. package/dist/lib.d.cts +20 -60
  23. package/dist/lib.d.mts +20 -60
  24. package/dist/lib.mjs +2 -2
  25. package/dist/{limits-Du7S6jqA.mjs → limits-CPTgiQ9A.mjs} +8 -8
  26. package/dist/{limits-XRNzIlVm.cjs → limits-K17AgMHD.cjs} +8 -8
  27. package/dist/{mcp-CAMCuudA.cjs → mcp-CK1NCZm0.cjs} +4 -4
  28. package/dist/{mcp-D8zhjyZa.mjs → mcp-DhuxpwTB.mjs} +4 -4
  29. package/dist/{runGemini-Db4rX54F.mjs → runGemini-BVJE7LZl.mjs} +6 -6
  30. package/dist/{runGemini-BIVHp0ST.cjs → runGemini-n0wD2Z9R.cjs} +6 -6
  31. package/dist/{runOpenClaw-D2MceXPO.mjs → runOpenClaw-CJaIYpB7.mjs} +5 -5
  32. package/dist/{runOpenClaw-a0f2pZZp.cjs → runOpenClaw-fYx1XweW.cjs} +5 -5
  33. package/dist/{send-DKbumEcg.cjs → send-WlJ4r3U2.cjs} +4 -4
  34. package/dist/{send-1Wjb0lIh.mjs → send-fWHOFJV9.mjs} +4 -4
  35. package/dist/{sessions-DXCCRDb5.mjs → sessions-5fJxQ2xj.mjs} +4 -4
  36. package/dist/{sessions-SIFOOlGp.cjs → sessions-Cx_3cvN6.cjs} +4 -4
  37. package/dist/{spawn-Bj3W6HR4.mjs → spawn-CF1ZV-YH.mjs} +4 -4
  38. package/dist/{spawn-C-XpSPLX.cjs → spawn-DVJtNwWN.cjs} +4 -4
  39. package/dist/{teams-ChFzXAqr.cjs → teams-BbYi0fWG.cjs} +4 -4
  40. package/dist/{teams-CqhFQLVC.mjs → teams-gzvKrMfn.mjs} +4 -4
  41. package/dist/{todo-B0gbQHH8.mjs → todo-BAK2DVLV.mjs} +3 -3
  42. package/dist/{todo-o0clp-_I.cjs → todo-DHY20tpD.cjs} +3 -3
  43. package/dist/{types-6DrEgHVx.mjs → types-BEXGahmj.mjs} +128 -35
  44. package/dist/{types-CcjVmS2i.cjs → types-DjAHWb4J.cjs} +129 -35
  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-CcjVmS2i.cjs');
7
- var persistence = require('./limits-XRNzIlVm.cjs');
6
+ var api = require('./types-DjAHWb4J.cjs');
7
+ var persistence = require('./limits-K17AgMHD.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-BItqIZTf.cjs');
12
+ var agentGuidance = require('./agentGuidance-DlZM_YPV.cjs');
13
13
  var promises = require('node:fs/promises');
14
14
  var fs = require('fs/promises');
15
15
  var ink = require('ink');
@@ -1353,6 +1353,152 @@ async function claudeLocalLauncher(session) {
1353
1353
  return exitReason || { type: "exit", code: 0 };
1354
1354
  }
1355
1355
 
1356
+ const text = (value) => typeof value === "string" && value.length > 0 && value.length <= 1024 && !value.includes("\0") ? value : null;
1357
+ const publicMcpStatus = (servers) => servers.map(({ name, status, serverInfo, scope, tools }) => ({ name, status, serverInfo, scope, tools: tools?.map(({ name: name2 }) => ({ name: name2 })) }));
1358
+ const fingerprint = (result) => JSON.stringify([result.canRewind, [...result.filesChanged ?? []].sort(), result.insertions, result.deletions]);
1359
+ function isRuntimeCheckpointReplay(message) {
1360
+ const m = message;
1361
+ 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"));
1362
+ }
1363
+ function createRuntimeControls(isThinking) {
1364
+ let query = null, generation = node_crypto.randomInt(0, 2 ** 40), activityRevision = 0;
1365
+ const operations = /* @__PURE__ */ new Map();
1366
+ const confirmations = /* @__PURE__ */ new Map();
1367
+ const tasks = /* @__PURE__ */ new Map();
1368
+ const checkpoints = /* @__PURE__ */ new Map();
1369
+ let pending = false;
1370
+ const setQuery = (next) => {
1371
+ for (const operation of operations.values()) if (operation.status === "running") {
1372
+ operation.status = "failed";
1373
+ operation.error = "Claude query ended before this operation completed";
1374
+ }
1375
+ query = next;
1376
+ generation++;
1377
+ pending = false;
1378
+ confirmations.clear();
1379
+ tasks.clear();
1380
+ checkpoints.clear();
1381
+ };
1382
+ function observe(message) {
1383
+ const m = message;
1384
+ if (m.type === "user" || m.type === "assistant") {
1385
+ activityRevision++;
1386
+ confirmations.clear();
1387
+ }
1388
+ 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(" ") : "";
1389
+ if (isRuntimeCheckpointReplay(message) && text(m.uuid) && content) {
1390
+ checkpoints.set(m.uuid, { id: m.uuid, title: content.slice(0, 120) });
1391
+ if (checkpoints.size > 100) checkpoints.delete(checkpoints.keys().next().value);
1392
+ }
1393
+ if (m.type === "system" && text(m.task_id) && ["task_started", "task_updated", "task_progress", "task_notification"].includes(m.subtype)) {
1394
+ const previous = tasks.get(m.task_id);
1395
+ if (m.subtype !== "task_notification" && ["completed", "failed", "stopped", "cancelled"].includes(String(previous?.status))) return;
1396
+ tasks.set(m.task_id, {
1397
+ ...previous,
1398
+ id: m.task_id,
1399
+ ...text(m.tool_use_id) ? { toolUseId: m.tool_use_id } : {},
1400
+ ...text(m.description) ? { description: m.description } : {},
1401
+ ...text(m.task_type) ? { type: m.task_type } : {},
1402
+ status: m.subtype === "task_notification" ? m.status ?? "completed" : "running"
1403
+ });
1404
+ if (tasks.size > 100) tasks.delete(tasks.keys().next().value);
1405
+ }
1406
+ }
1407
+ function request(input) {
1408
+ const r = input && typeof input === "object" ? input : {};
1409
+ 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) };
1410
+ if (r.action === "operation") {
1411
+ const op2 = operations.get(String(r.operationId));
1412
+ return op2 ? { ...op2 } : { error: "Operation not found" };
1413
+ }
1414
+ const q = query;
1415
+ if (!q) return { error: "No active Claude SDK query" };
1416
+ if (pending || !q.canControl()) return { error: "Claude is waiting for an interaction or another control operation; retry after it finishes" };
1417
+ const action = String(r.action), server = text(r.serverName), task = text(r.taskId), target = text(r.userMessageId);
1418
+ const runGeneration = generation, runRevision = activityRevision;
1419
+ let execute;
1420
+ switch (action) {
1421
+ case "reload-skills":
1422
+ execute = () => q.reloadSkills();
1423
+ break;
1424
+ case "reload-plugins":
1425
+ execute = async () => {
1426
+ const result = await q.reloadPlugins();
1427
+ return { ...result, mcpServers: publicMcpStatus(result.mcpServers ?? []) };
1428
+ };
1429
+ break;
1430
+ case "mcp-status":
1431
+ execute = async () => publicMcpStatus(await q.mcpServerStatus());
1432
+ break;
1433
+ case "mcp-reconnect":
1434
+ if (!server) return { error: "Invalid server name" };
1435
+ execute = () => q.reconnectMcpServer(server);
1436
+ break;
1437
+ case "mcp-toggle":
1438
+ if (!server || typeof r.enabled !== "boolean") return { error: "Invalid server or enabled flag" };
1439
+ execute = () => q.toggleMcpServer(server, r.enabled);
1440
+ break;
1441
+ case "stop-task":
1442
+ if (!task || !tasks.has(task)) return { error: "Unknown task in this query" };
1443
+ execute = () => q.stopTask(task);
1444
+ break;
1445
+ case "background-tasks":
1446
+ if (r.toolUseId !== void 0 && !text(r.toolUseId)) return { error: "Invalid tool use ID" };
1447
+ execute = async () => ({ backgrounded: await q.backgroundTasks(r.toolUseId) });
1448
+ break;
1449
+ case "rewind-preview":
1450
+ if (isThinking() || !target || !checkpoints.has(target)) return { error: "Rewind requires an idle query and a known user checkpoint" };
1451
+ execute = async () => {
1452
+ const result = await q.rewindFiles(target, { dryRun: true });
1453
+ if (!result.canRewind) return result;
1454
+ if (query !== q || generation !== runGeneration || activityRevision !== runRevision || isThinking() || !q.canControl()) throw Error("Query changed; preview again");
1455
+ const token = node_crypto.randomUUID();
1456
+ confirmations.set(token, { generation, revision: activityRevision, userMessageId: target, fingerprint: fingerprint(result), expires: Date.now() + 3e5 });
1457
+ return { ...result, confirmationToken: token };
1458
+ };
1459
+ break;
1460
+ case "rewind-apply": {
1461
+ const confirmation = confirmations.get(String(r.confirmationToken));
1462
+ if (isThinking() || !confirmation || confirmation.generation !== generation || confirmation.expires < Date.now()) return { error: "Preview expired or query is running; preview again" };
1463
+ confirmations.delete(String(r.confirmationToken));
1464
+ execute = async () => {
1465
+ const fresh = await q.rewindFiles(confirmation.userMessageId, { dryRun: true });
1466
+ if (query !== q || generation !== runGeneration || activityRevision !== confirmation.revision || isThinking() || !q.canControl() || fingerprint(fresh) !== confirmation.fingerprint) throw Error("Files or query changed; preview again");
1467
+ return q.rewindFiles(confirmation.userMessageId, { dryRun: false });
1468
+ };
1469
+ break;
1470
+ }
1471
+ default:
1472
+ return { error: "Unknown Claude control action" };
1473
+ }
1474
+ pending = true;
1475
+ const op = { id: node_crypto.randomUUID(), queryGeneration: runGeneration, action, status: "running" };
1476
+ operations.set(op.id, op);
1477
+ if (operations.size > 50) for (const [id, old] of operations) {
1478
+ if (old.status !== "running") {
1479
+ operations.delete(id);
1480
+ break;
1481
+ }
1482
+ }
1483
+ void Promise.resolve().then(() => {
1484
+ if (query !== q || generation !== runGeneration || !q.canControl()) throw Error("Claude query changed or is waiting for an interaction");
1485
+ return execute();
1486
+ }).then((result) => {
1487
+ if (generation !== runGeneration || op.status !== "running") return;
1488
+ op.status = "completed";
1489
+ op.result = result ?? null;
1490
+ }, (error) => {
1491
+ if (generation !== runGeneration || op.status !== "running") return;
1492
+ op.status = "failed";
1493
+ op.error = error instanceof Error ? error.message : "Claude control failed";
1494
+ }).finally(() => {
1495
+ if (generation === runGeneration) pending = false;
1496
+ });
1497
+ return { operationId: op.id };
1498
+ }
1499
+ return { setQuery, observe, request };
1500
+ }
1501
+
1356
1502
  class MessageBuffer {
1357
1503
  messages = [];
1358
1504
  listeners = [];
@@ -1622,6 +1768,9 @@ function query(params) {
1622
1768
  tools: opts?.tools,
1623
1769
  persistSession: opts?.persistSession,
1624
1770
  includePartialMessages: opts?.includePartialMessages,
1771
+ forwardSubagentText: true,
1772
+ enableFileCheckpointing: true,
1773
+ extraArgs: { "replay-user-messages": null },
1625
1774
  onElicitation: opts?.onElicitation,
1626
1775
  onUserDialog: opts?.onUserDialog,
1627
1776
  supportedDialogKinds: opts?.onUserDialog ? opts.supportedDialogKinds : void 0
@@ -2064,6 +2213,20 @@ async function claudeRemote(opts) {
2064
2213
  content: initial.message
2065
2214
  }
2066
2215
  });
2216
+ let callbackDepth = 0;
2217
+ const guardCallback = (callback) => (async (...args) => {
2218
+ callbackDepth++;
2219
+ try {
2220
+ return await callback(...args);
2221
+ } finally {
2222
+ setImmediate(() => {
2223
+ callbackDepth--;
2224
+ });
2225
+ }
2226
+ });
2227
+ if (sdkOptions.canCallTool) sdkOptions.canCallTool = guardCallback(sdkOptions.canCallTool);
2228
+ if (sdkOptions.onElicitation) sdkOptions.onElicitation = guardCallback(sdkOptions.onElicitation);
2229
+ if (sdkOptions.onUserDialog) sdkOptions.onUserDialog = guardCallback(sdkOptions.onUserDialog);
2067
2230
  const response = query({
2068
2231
  prompt: messages,
2069
2232
  options: sdkOptions
@@ -2084,6 +2247,15 @@ async function claudeRemote(opts) {
2084
2247
  }
2085
2248
  if (opts.onQueryReady) {
2086
2249
  opts.onQueryReady({
2250
+ canControl: () => callbackDepth === 0,
2251
+ reloadSkills: () => response.reloadSkills(),
2252
+ reloadPlugins: () => response.reloadPlugins(),
2253
+ mcpServerStatus: () => response.mcpServerStatus(),
2254
+ reconnectMcpServer: (name) => response.reconnectMcpServer(name),
2255
+ toggleMcpServer: (name, enabled) => response.toggleMcpServer(name, enabled),
2256
+ stopTask: (id) => response.stopTask(id),
2257
+ backgroundTasks: (id) => response.backgroundTasks(id),
2258
+ rewindFiles: (id, options) => response.rewindFiles(id, options),
2087
2259
  setPermissionMode: (mode2) => response.setPermissionMode(mode2),
2088
2260
  setModel: (model) => response.setModel(modelTarget(model)),
2089
2261
  interrupt: async () => {
@@ -2727,7 +2899,7 @@ class PermissionHandler {
2727
2899
  } else if (pending.kind === "elicitation") {
2728
2900
  pending.resolve(message.approved ? { action: "accept", content: elicitationContent(message.updatedInput) } : { action: message.decision === "abort" ? "cancel" : "decline" });
2729
2901
  } else {
2730
- pending.resolve(message.approved ? { behavior: "completed", result: message.updatedInput?.result ?? "retry_fallback" } : { behavior: "cancelled" });
2902
+ 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
2903
  }
2732
2904
  this.recordCompletedRequest(id, effectiveResponse);
2733
2905
  this.scheduleDeferredPermissionModeUpdate();
@@ -3189,6 +3361,7 @@ class SDKToLogConverter {
3189
3361
  }
3190
3362
  }
3191
3363
 
3364
+ const transcriptSystemSubtypes = /* @__PURE__ */ new Set(["task_started", "task_updated", "task_progress", "task_notification"]);
3192
3365
  class OutgoingMessageQueue {
3193
3366
  constructor(sendFunction) {
3194
3367
  this.sendFunction = sendFunction;
@@ -3270,7 +3443,7 @@ class OutgoingMessageQueue {
3270
3443
  break;
3271
3444
  }
3272
3445
  if (!item.sent) {
3273
- if (item.logMessage.type !== "system") {
3446
+ if (item.logMessage.type !== "system" || transcriptSystemSubtypes.has(item.logMessage.subtype)) {
3274
3447
  this.sendFunction(item.logMessage);
3275
3448
  }
3276
3449
  item.sent = true;
@@ -3898,6 +4071,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3898
4071
  let abortController = null;
3899
4072
  let abortFuture = null;
3900
4073
  const turnSteering = createTurnSteeringController();
4074
+ const runtimeControls = createRuntimeControls(() => session.thinking);
4075
+ session.client.rpcHandlerManager.registerHandler("claude-runtime-control", async (input) => runtimeControls.request(input));
3901
4076
  async function abort() {
3902
4077
  if (abortController && !abortController.signal.aborted) {
3903
4078
  abortController.abort();
@@ -3958,6 +4133,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3958
4133
  let ongoingToolCalls = /* @__PURE__ */ new Map();
3959
4134
  let notifiedQuestionToolCalls = /* @__PURE__ */ new Set();
3960
4135
  function onMessage(message) {
4136
+ runtimeControls.observe(message);
4137
+ if (isRuntimeCheckpointReplay(message)) return;
3961
4138
  if (message.type === "user" && isClaudeInterruptSentinelContent(message.message.content)) {
3962
4139
  sdkToLogConverter.convert(message);
3963
4140
  return;
@@ -4180,6 +4357,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4180
4357
  if (metadata.permissionMode) onEffectivePermissionMode?.(metadata.permissionMode);
4181
4358
  },
4182
4359
  onQueryReady: (q) => {
4360
+ runtimeControls.setQuery(q);
4183
4361
  turnSteering.setInterrupt(q.interrupt);
4184
4362
  session.setSteerHandler(async (input) => {
4185
4363
  if (!modeGate.matches(input.mode)) return false;
@@ -4269,6 +4447,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4269
4447
  continue;
4270
4448
  }
4271
4449
  } finally {
4450
+ runtimeControls.setQuery(null);
4272
4451
  turnSteering.reset();
4273
4452
  session.setSteerHandler(null);
4274
4453
  livePermissionModeHandler = null;
@@ -5319,6 +5498,80 @@ ${typeLabels[type] || type}:`));
5319
5498
  console.log(chalk.green("\n\u2705 Doctor diagnosis complete!\n"));
5320
5499
  }
5321
5500
 
5501
+ const AGENT_CHECK_INTERVAL = 6 * 60 * 60 * 1e3;
5502
+ function stableVersion(value) {
5503
+ const match = value.trim().match(/(?:^|\s|v)(\d+\.\d+\.\d+)([-+][\w.-]+)?(?=\s|$)/);
5504
+ return match && !match[2] ? match[1] : null;
5505
+ }
5506
+ function newerVersion(current, latest) {
5507
+ const a = current.split(".").map(Number), b = latest.split(".").map(Number);
5508
+ for (let i = 0; i < 3; i++) {
5509
+ if (b[i] !== a[i]) return b[i] > a[i];
5510
+ }
5511
+ return false;
5512
+ }
5513
+ const runVersionCommand = (command, args, shell) => new Promise((resolve, reject) => {
5514
+ node_child_process.execFile(
5515
+ command,
5516
+ args,
5517
+ { timeout: 5e3, maxBuffer: 32 * 1024, windowsHide: true, shell },
5518
+ (error, stdout) => error ? reject(error) : resolve(stdout)
5519
+ );
5520
+ });
5521
+ async function probeAgentVersion(command, platform = process.platform, run = runVersionCommand) {
5522
+ if (platform === "win32") {
5523
+ try {
5524
+ await run("where.exe", [command], false);
5525
+ } catch (error) {
5526
+ return { version: null, missing: error.code === 1 };
5527
+ }
5528
+ }
5529
+ try {
5530
+ return { version: stableVersion(await run(command, ["--version"], platform === "win32")), missing: false };
5531
+ } catch (error) {
5532
+ return { version: null, missing: error.code === "ENOENT" };
5533
+ }
5534
+ }
5535
+ async function registryVersion(name) {
5536
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, { signal: AbortSignal.timeout(5e3) });
5537
+ if (!response.ok) throw new Error("Registry unavailable");
5538
+ const data = await response.json();
5539
+ return typeof data.version === "string" ? stableVersion(data.version) : null;
5540
+ }
5541
+ async function detectAgentVersions(deps = { probe: probeAgentVersion, registryVersion }, now = Date.now()) {
5542
+ const agents = await Promise.all(api.AGENT_VERSION_SOURCES.map(async (source) => {
5543
+ 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 };
5544
+ if (local.missing) return { id: source.id, installed: null, latest: null, status: "not-installed" };
5545
+ const latest = await deps.registryVersion(source.package).catch(() => null);
5546
+ const installed = local.version;
5547
+ const status = !installed || !latest ? "unknown" : newerVersion(installed, latest) ? "update-available" : "current";
5548
+ return { id: source.id, installed, latest, status };
5549
+ }));
5550
+ return { checkedAt: now, daemonPid: process.pid, agents };
5551
+ }
5552
+ function startAgentVersionChecks(publish, onError, detect = detectAgentVersions) {
5553
+ let stopped = false, pending = false;
5554
+ async function refresh() {
5555
+ if (stopped || pending) return;
5556
+ pending = true;
5557
+ try {
5558
+ const snapshot = await detect();
5559
+ if (!stopped) await publish(snapshot);
5560
+ } catch (error) {
5561
+ onError(error);
5562
+ } finally {
5563
+ pending = false;
5564
+ }
5565
+ }
5566
+ void refresh();
5567
+ const timer = setInterval(() => void refresh(), AGENT_CHECK_INTERVAL);
5568
+ timer.unref();
5569
+ return () => {
5570
+ stopped = true;
5571
+ clearInterval(timer);
5572
+ };
5573
+ }
5574
+
5322
5575
  function sanitizeSpawnModel(value) {
5323
5576
  if (typeof value !== "string") return null;
5324
5577
  return /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}(\[1m\])?$/i.test(value) ? value : null;
@@ -7750,7 +8003,7 @@ function parseJsonObject(text) {
7750
8003
  return null;
7751
8004
  }
7752
8005
  }
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-6Xm0CgN_.cjs', document.baseURI).href))).resolve(s)) {
8006
+ 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-DNbKbe23.cjs', document.baseURI).href))).resolve(s)) {
7754
8007
  const platformArch = `${platform}-${arch}`;
7755
8008
  const candidates = [
7756
8009
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -10991,6 +11244,10 @@ async function startDaemon() {
10991
11244
  persistence.logger.warn(`[DAEMON RUN] Ignoring invalid HAPPY_CLI_UPDATE_CHECK_INTERVAL; using ${api.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS}ms`);
10992
11245
  }
10993
11246
  const cliUpdateInterval = setInterval(() => void refreshCliUpdate(), cliUpdateIntervalMs);
11247
+ const stopAgentVersionChecks = startAgentVersionChecks(
11248
+ (snapshot) => apiMachine.updateDaemonState((state) => ({ ...state, status: state?.status ?? "running", agentVersions: snapshot })),
11249
+ (error) => persistence.logger.debug("[DAEMON RUN] Agent version check failed", error)
11250
+ );
10994
11251
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
10995
11252
  const restartOnStaleVersionAndHeartbeat = setInterval(serialTask(async () => {
10996
11253
  try {
@@ -11031,6 +11288,7 @@ async function startDaemon() {
11031
11288
  teamWorker?.stop();
11032
11289
  clearInterval(restartOnStaleVersionAndHeartbeat);
11033
11290
  clearInterval(cliUpdateInterval);
11291
+ stopAgentVersionChecks();
11034
11292
  claudeAuthService.stop();
11035
11293
  apiMachine.shutdown();
11036
11294
  await stopControlServer();
@@ -11092,6 +11350,7 @@ async function startDaemon() {
11092
11350
  persistence.logger.debug("[DAEMON RUN] Health check interval cleared");
11093
11351
  }
11094
11352
  clearInterval(cliUpdateInterval);
11353
+ stopAgentVersionChecks();
11095
11354
  await apiMachine.updateDaemonState((state) => ({
11096
11355
  ...state,
11097
11356
  status: "shutting-down",
@@ -12040,7 +12299,7 @@ function cleanupHookSettingsFile(filepath) {
12040
12299
  }
12041
12300
  }
12042
12301
 
12043
- function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEvents) {
12302
+ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEvents, options) {
12044
12303
  let terminationStarted = false;
12045
12304
  const terminateOnce = () => {
12046
12305
  if (terminationStarted) return;
@@ -12048,6 +12307,10 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEve
12048
12307
  void killThisHappy();
12049
12308
  };
12050
12309
  archiveEvents.on("archived", terminateOnce);
12310
+ if (options?.processSignals) {
12311
+ process.on("SIGTERM", terminateOnce);
12312
+ process.on("SIGINT", terminateOnce);
12313
+ }
12051
12314
  rpcHandlerManager.registerHandler("killSession", async () => {
12052
12315
  persistence.logger.debug("Kill session request received");
12053
12316
  terminateOnce();
@@ -12056,6 +12319,13 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEve
12056
12319
  message: "Killing happy-cli process"
12057
12320
  };
12058
12321
  });
12322
+ return () => {
12323
+ archiveEvents.off?.("archived", terminateOnce);
12324
+ if (options?.processSignals) {
12325
+ process.off("SIGTERM", terminateOnce);
12326
+ process.off("SIGINT", terminateOnce);
12327
+ }
12328
+ };
12059
12329
  }
12060
12330
 
12061
12331
  const SIDE_QUESTION_SYSTEM_PROMPT = [
@@ -12170,7 +12440,7 @@ function registerSideQuestionHandler(rpc, deps) {
12170
12440
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
12171
12441
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
12172
12442
  const run = deps.run ?? (async (input) => {
12173
- const { query } = await Promise.resolve().then(function () { return require('./index-CdH3ecC5.cjs'); });
12443
+ const { query } = await Promise.resolve().then(function () { return require('./index-DHI6Y1xW.cjs'); });
12174
12444
  return runSideQuestion(query, input);
12175
12445
  });
12176
12446
  const slots = /* @__PURE__ */ new Map();
@@ -12272,7 +12542,7 @@ const MAX_TITLE_CHARS = 60;
12272
12542
  const GENERATION_TIMEOUT_MS = 3e4;
12273
12543
  function resolveClaudeBinary() {
12274
12544
  try {
12275
- 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-6Xm0CgN_.cjs', document.baseURI).href)));
12545
+ 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-DNbKbe23.cjs', document.baseURI).href)));
12276
12546
  const utilsPath = path.resolve(path.join(api.projectPath(), "scripts", "claude_version_utils.cjs"));
12277
12547
  const { getClaudeCliPath } = require$1(utilsPath);
12278
12548
  const path$1 = getClaudeCliPath();
@@ -12733,7 +13003,7 @@ async function runClaude(credentials, options = {}) {
12733
13003
  // a brand-new session cannot pick a PDF until after sending once.
12734
13004
  attachmentKinds: [...CLAUDE_ATTACHMENT_KINDS],
12735
13005
  queueCancellation: true,
12736
- capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2", "claude-btw-v1"],
13006
+ capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2", "claude-btw-v1", "claude-runtime-controls-v1"],
12737
13007
  // Effective mode this process enforces. Kept current by
12738
13008
  // publishPermissionMode below; the web renders it instead of guessing.
12739
13009
  permissionMode: mapToClaudeMode(initialPermissionMode ?? "default"),
@@ -14490,9 +14760,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
14490
14760
  };
14491
14761
  }
14492
14762
 
14493
- 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-6Xm0CgN_.cjs', document.baseURI).href)));
14763
+ 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-DNbKbe23.cjs', document.baseURI).href)));
14494
14764
  const __dirname$1 = path.dirname(__filename$1);
14495
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-6Xm0CgN_.cjs', document.baseURI).href)));
14765
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DNbKbe23.cjs', document.baseURI).href)));
14496
14766
  const PRISMA_QUERY_ENGINE_FILES = {
14497
14767
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
14498
14768
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -14914,1269 +15184,1325 @@ async function ensureDaemonRunning() {
14914
15184
  persistence.logger.debug(`Happy background service did not become ready within ${DAEMON_READY_TIMEOUT_MS}ms; continuing anyway`);
14915
15185
  }
14916
15186
 
14917
- function registerAgentAttachmentDownloads(session) {
14918
- session.onFileEvent((fileEvent) => {
14919
- const attachment = fileEvent.content.data.ev;
14920
- const download = (async () => {
14921
- try {
14922
- const data = await session.downloadAndDecryptAttachment(attachment.ref);
14923
- if (!data) throw new Error("Attachment decryption failed");
14924
- return { data, mimeType: attachment.mimeType ?? "image/jpeg", name: attachment.name };
14925
- } catch {
14926
- session.sendSessionEvent({ type: "message", message: `Could not download attachment: ${attachment.name}. Please attach it again.` });
14927
- return null;
14928
- }
14929
- })();
14930
- session.trackAttachmentDownload(download);
14931
- });
15187
+ function getStartedSubagents(state) {
15188
+ return state.startedSubagents ?? /* @__PURE__ */ new Set();
14932
15189
  }
14933
-
14934
- class BasePermissionHandler {
14935
- pendingRequests = /* @__PURE__ */ new Map();
14936
- session;
14937
- isResetting = false;
14938
- constructor(session) {
14939
- this.session = session;
14940
- this.setupRpcHandler();
15190
+ function getActiveSubagents(state) {
15191
+ return state.activeSubagents ?? /* @__PURE__ */ new Set();
15192
+ }
15193
+ function getProviderSubagentToSessionSubagent(state) {
15194
+ return state.providerSubagentToSessionSubagent ?? /* @__PURE__ */ new Map();
15195
+ }
15196
+ function maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes) {
15197
+ if (!subagent || startedSubagents.has(subagent)) {
15198
+ return;
14941
15199
  }
14942
- /**
14943
- * Update the session reference (used after offline reconnection swaps sessions).
14944
- * This is critical for avoiding stale session references after onSessionSwap.
14945
- */
14946
- updateSession(newSession) {
14947
- persistence.logger.debug(`${this.getLogPrefix()} Session reference updated`);
14948
- this.session = newSession;
14949
- this.setupRpcHandler();
15200
+ envelopes.push(api.createEnvelope("agent", { t: "start" }, { ...opts, subagent }));
15201
+ startedSubagents.add(subagent);
15202
+ activeSubagents.add(subagent);
15203
+ }
15204
+ function emitSubagentStops(opts, startedSubagents, activeSubagents) {
15205
+ const envelopes = [];
15206
+ for (const subagent of activeSubagents) {
15207
+ envelopes.push(api.createEnvelope("agent", { t: "stop" }, { ...opts, subagent }));
14950
15208
  }
14951
- /**
14952
- * Setup RPC handler for permission responses.
14953
- */
14954
- setupRpcHandler() {
14955
- this.session.rpcHandlerManager.registerHandler(
14956
- "permission",
14957
- async (response) => {
14958
- const pending = this.pendingRequests.get(response.id);
14959
- if (!pending) {
14960
- persistence.logger.debug(`${this.getLogPrefix()} Permission request not found or already resolved`);
14961
- return;
14962
- }
14963
- this.pendingRequests.delete(response.id);
14964
- const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
14965
- pending.resolve(result);
14966
- this.session.updateAgentState((currentState) => {
14967
- const request = currentState.requests?.[response.id];
14968
- if (!request) return currentState;
14969
- const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
14970
- let res = {
14971
- ...currentState,
14972
- requests: remainingRequests,
14973
- completedRequests: {
14974
- ...currentState.completedRequests,
14975
- [response.id]: {
14976
- ...request,
14977
- completedAt: Date.now(),
14978
- status: response.approved ? "approved" : "denied",
14979
- decision: result.decision
14980
- }
14981
- }
14982
- };
14983
- return res;
14984
- });
14985
- persistence.logger.debug(`${this.getLogPrefix()} Permission ${response.approved ? "approved" : "denied"} for ${pending.toolName}`);
14986
- }
14987
- );
15209
+ activeSubagents.clear();
15210
+ startedSubagents.clear();
15211
+ return envelopes;
15212
+ }
15213
+ function buildEnvelopeOptions(currentTurnId, subagent) {
15214
+ return {
15215
+ ...currentTurnId ? { turn: currentTurnId } : {},
15216
+ ...subagent ? { subagent } : {}
15217
+ };
15218
+ }
15219
+ function pickProviderSubagent(message) {
15220
+ const candidates = [message.subagent, message.parent_call_id, message.parentCallId];
15221
+ for (const candidate of candidates) {
15222
+ if (typeof candidate === "string" && candidate.length > 0) {
15223
+ return candidate;
15224
+ }
14988
15225
  }
14989
- /**
14990
- * Add a pending request to the agent state.
14991
- */
14992
- addPendingRequestToState(toolCallId, toolName, input) {
14993
- this.session.updateAgentState((currentState) => ({
14994
- ...currentState,
14995
- requests: {
14996
- ...currentState.requests,
14997
- [toolCallId]: {
14998
- tool: toolName,
14999
- arguments: input,
15000
- createdAt: Date.now()
15001
- }
15002
- }
15003
- }));
15226
+ return void 0;
15227
+ }
15228
+ function resolveSessionSubagent(message, providerSubagentToSessionSubagent) {
15229
+ const providerSubagent = pickProviderSubagent(message);
15230
+ if (!providerSubagent) {
15231
+ return void 0;
15004
15232
  }
15005
- /**
15006
- * Abort all pending permission requests.
15007
- * Unlike reset(), this resolves (not rejects) pending promises with { decision: 'abort' },
15008
- * causing the approval response to send 'cancel' to the provider. This is used when the
15009
- * user presses the abort/stop button — it unblocks any pending tool approval so the provider
15010
- * can process the turn cancellation.
15011
- */
15012
- abortAll() {
15013
- const pendingSnapshot = Array.from(this.pendingRequests.entries());
15014
- if (pendingSnapshot.length === 0) return;
15015
- this.pendingRequests.clear();
15016
- for (const [id, pending] of pendingSnapshot) {
15017
- try {
15018
- pending.resolve({ decision: "abort" });
15019
- } catch (err) {
15020
- persistence.logger.debug(`${this.getLogPrefix()} Error resolving aborted request ${id}:`, err);
15021
- }
15022
- }
15023
- this.session.updateAgentState((currentState) => {
15024
- const pendingRequests = currentState.requests || {};
15025
- const completedRequests = { ...currentState.completedRequests };
15026
- for (const [id, request] of Object.entries(pendingRequests)) {
15027
- completedRequests[id] = {
15028
- ...request,
15029
- completedAt: Date.now(),
15030
- status: "canceled",
15031
- reason: "Aborted by user"
15032
- };
15033
- }
15034
- return {
15035
- ...currentState,
15036
- requests: {},
15037
- completedRequests
15038
- };
15039
- });
15040
- persistence.logger.debug(`${this.getLogPrefix()} Aborted ${pendingSnapshot.length} pending permission(s)`);
15041
- }
15042
- /**
15043
- * Reset state for new sessions.
15044
- * This method is idempotent - safe to call multiple times.
15045
- */
15046
- reset(reason = "Session reset") {
15047
- if (this.isResetting) {
15048
- persistence.logger.debug(`${this.getLogPrefix()} Reset already in progress, skipping`);
15049
- return;
15050
- }
15051
- this.isResetting = true;
15052
- try {
15053
- const pendingSnapshot = Array.from(this.pendingRequests.entries());
15054
- this.pendingRequests.clear();
15055
- for (const [id, pending] of pendingSnapshot) {
15056
- try {
15057
- pending.reject(new Error("Session reset"));
15058
- } catch (err) {
15059
- persistence.logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
15060
- }
15061
- }
15062
- this.session.updateAgentState((currentState) => {
15063
- const pendingRequests = currentState.requests || {};
15064
- const completedRequests = { ...currentState.completedRequests };
15065
- for (const [id, request] of Object.entries(pendingRequests)) {
15066
- completedRequests[id] = {
15067
- ...request,
15068
- completedAt: Date.now(),
15069
- status: "canceled",
15070
- reason
15071
- };
15072
- }
15073
- return {
15074
- ...currentState,
15075
- requests: {},
15076
- completedRequests
15077
- };
15078
- });
15079
- persistence.logger.debug(`${this.getLogPrefix()} Permission handler reset`);
15080
- } finally {
15081
- this.isResetting = false;
15082
- }
15233
+ const existing = providerSubagentToSessionSubagent.get(providerSubagent);
15234
+ if (existing) {
15235
+ return existing;
15083
15236
  }
15237
+ const created = cuid2.createId();
15238
+ providerSubagentToSessionSubagent.set(providerSubagent, created);
15239
+ return created;
15084
15240
  }
15085
-
15086
- class CodexPermissionHandler extends BasePermissionHandler {
15087
- // Exact tool names that should always be auto-approved. Include the bare
15088
- // form (used by Codex elicitation messages like `tool "change_title"`)
15089
- // and the MCP-qualified form for defense in depth.
15090
- static ALWAYS_AUTO_APPROVE_NAMES = /* @__PURE__ */ new Set([
15091
- "change_title",
15092
- "mcp__happy__change_title"
15093
- ]);
15094
- // Tool-call IDs that should auto-approve when they exactly match one of
15095
- // these values or start with `<name>-` (e.g. `change_title-1765385846663`).
15096
- // Substring matching was a bypass vector — any tool whose ID happened to
15097
- // contain `change_title` as a substring would be silently approved.
15098
- static ALWAYS_AUTO_APPROVE_ID_PREFIXES = [
15099
- "change_title"
15100
- ];
15101
- constructor(session) {
15102
- super(session);
15241
+ function pickCallId(message) {
15242
+ const callId = message.call_id ?? message.callId;
15243
+ if (typeof callId === "string" && callId.length > 0) {
15244
+ return callId;
15103
15245
  }
15104
- getLogPrefix() {
15105
- return "[Codex]";
15246
+ return node_crypto.randomUUID();
15247
+ }
15248
+ function summarizeCommand(command) {
15249
+ if (typeof command === "string" && command.trim().length > 0) {
15250
+ return command;
15106
15251
  }
15107
- shouldAutoApprove(toolName, toolCallId) {
15108
- if (CodexPermissionHandler.ALWAYS_AUTO_APPROVE_NAMES.has(toolName)) {
15109
- return true;
15110
- }
15111
- for (const prefix of CodexPermissionHandler.ALWAYS_AUTO_APPROVE_ID_PREFIXES) {
15112
- if (toolCallId === prefix || toolCallId.startsWith(`${prefix}-`)) {
15113
- return true;
15114
- }
15115
- }
15116
- return false;
15252
+ if (Array.isArray(command)) {
15253
+ const cmd = command.map((v) => String(v)).join(" ").trim();
15254
+ return cmd.length > 0 ? cmd : null;
15117
15255
  }
15118
- /**
15119
- * Handle a tool permission request
15120
- * @param toolCallId - The unique ID of the tool call
15121
- * @param toolName - The name of the tool being called
15122
- * @param input - The input parameters for the tool
15123
- * @returns Promise resolving to permission result
15124
- */
15125
- async handleToolCall(toolCallId, toolName, input) {
15126
- if (this.shouldAutoApprove(toolName, toolCallId)) {
15127
- persistence.logger.debug(`${this.getLogPrefix()} Auto-approving tool ${toolName} (${toolCallId})`);
15128
- this.session.updateAgentState((currentState) => ({
15129
- ...currentState,
15130
- completedRequests: {
15131
- ...currentState.completedRequests,
15132
- [toolCallId]: {
15133
- tool: toolName,
15134
- arguments: input,
15135
- createdAt: Date.now(),
15136
- completedAt: Date.now(),
15137
- status: "approved",
15138
- decision: "approved"
15139
- }
15140
- }
15141
- }));
15142
- return { decision: "approved" };
15143
- }
15144
- return new Promise((resolve, reject) => {
15145
- this.pendingRequests.set(toolCallId, {
15146
- resolve,
15147
- reject,
15148
- toolName,
15149
- input
15150
- });
15151
- this.addPendingRequestToState(toolCallId, toolName, input);
15152
- persistence.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
15153
- });
15256
+ return null;
15257
+ }
15258
+ function commandToTitle(command) {
15259
+ if (!command) {
15260
+ return "Run command";
15154
15261
  }
15262
+ const short = command.length > 80 ? `${command.slice(0, 77)}...` : command;
15263
+ return `Run \`${short}\``;
15155
15264
  }
15156
-
15157
- class BaseReasoningProcessor {
15158
- accumulator = "";
15159
- inTitleCapture = false;
15160
- titleBuffer = "";
15161
- contentBuffer = "";
15162
- hasTitle = false;
15163
- currentCallId = null;
15164
- toolCallStarted = false;
15165
- currentTitle = null;
15166
- onMessage = null;
15167
- constructor(onMessage) {
15168
- this.onMessage = onMessage || null;
15169
- this.reset();
15265
+ function turnTimestampMs(turn) {
15266
+ const seconds = turn.startedAt ?? turn.completedAt;
15267
+ return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15268
+ }
15269
+ function completedTimestampMs(turn) {
15270
+ const seconds = turn.completedAt ?? turn.startedAt;
15271
+ return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15272
+ }
15273
+ function textFromInputItems(items) {
15274
+ if (!Array.isArray(items)) {
15275
+ return null;
15170
15276
  }
15171
- /**
15172
- * Set the message callback for sending messages directly.
15173
- */
15174
- setMessageCallback(callback) {
15175
- this.onMessage = callback;
15277
+ const text = items.filter((item) => Boolean(item) && typeof item === "object" && item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n").trim();
15278
+ return text.length > 0 ? text : null;
15279
+ }
15280
+ function reasoningText(item) {
15281
+ const summary = item.summary;
15282
+ const content = item.content;
15283
+ const parts = [
15284
+ ...Array.isArray(summary) ? summary : [],
15285
+ ...Array.isArray(content) ? content : []
15286
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
15287
+ const text = parts.join("\n").trim();
15288
+ return text.length > 0 ? text : null;
15289
+ }
15290
+ function turnStatus(turn) {
15291
+ const status = typeof turn.status === "string" ? turn.status : null;
15292
+ if (status === "failed") {
15293
+ return "failed";
15176
15294
  }
15177
- /**
15178
- * Process a reasoning section break - indicates a new reasoning section is starting.
15179
- */
15180
- handleSectionBreak() {
15181
- this.finishCurrentToolCall("canceled");
15182
- this.resetState();
15183
- persistence.logger.debug(`${this.getLogPrefix()} Section break - reset state`);
15295
+ if (status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted") {
15296
+ return "cancelled";
15184
15297
  }
15185
- /**
15186
- * Process a reasoning delta/chunk and accumulate content.
15187
- */
15188
- processInput(input) {
15189
- this.accumulator += input;
15190
- if (!this.inTitleCapture && !this.hasTitle && !this.contentBuffer) {
15191
- if (this.accumulator.startsWith("**")) {
15192
- this.inTitleCapture = true;
15193
- this.titleBuffer = this.accumulator.substring(2);
15194
- persistence.logger.debug(`${this.getLogPrefix()} Started title capture`);
15195
- } else if (this.accumulator.length > 0) {
15196
- this.contentBuffer = this.accumulator;
15197
- }
15198
- } else if (this.inTitleCapture) {
15199
- this.titleBuffer = this.accumulator.substring(2);
15200
- const titleEndIndex = this.titleBuffer.indexOf("**");
15201
- if (titleEndIndex !== -1) {
15202
- const title = this.titleBuffer.substring(0, titleEndIndex);
15203
- const afterTitle = this.titleBuffer.substring(titleEndIndex + 2);
15204
- this.hasTitle = true;
15205
- this.inTitleCapture = false;
15206
- this.currentTitle = title;
15207
- this.contentBuffer = afterTitle;
15208
- this.currentCallId = node_crypto.randomUUID();
15209
- persistence.logger.debug(`${this.getLogPrefix()} Title captured: "${title}"`);
15210
- this.sendToolCallStart(title);
15211
- }
15212
- } else if (this.hasTitle) {
15213
- const titleStartIndex = this.accumulator.indexOf("**");
15214
- if (titleStartIndex !== -1) {
15215
- this.contentBuffer = this.accumulator.substring(
15216
- titleStartIndex + 2 + this.currentTitle.length + 2
15217
- );
15218
- }
15219
- } else {
15220
- this.contentBuffer = this.accumulator;
15221
- }
15298
+ return "completed";
15299
+ }
15300
+ function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output) {
15301
+ const time = turnTimestampMs(turn);
15302
+ const opts = { turn: turn.id, time, codexItemId: item.id };
15303
+ envelopes.push(api.createEnvelope("agent", {
15304
+ t: "tool-call-start",
15305
+ call: item.id,
15306
+ name,
15307
+ title,
15308
+ description: title,
15309
+ args
15310
+ }, {
15311
+ ...opts,
15312
+ id: `${item.id}:start`
15313
+ }));
15314
+ if (output && output.trim().length > 0) {
15315
+ envelopes.push(api.createEnvelope("agent", {
15316
+ t: "text",
15317
+ text: output,
15318
+ thinking: true
15319
+ }, {
15320
+ ...opts,
15321
+ id: `${item.id}:output`
15322
+ }));
15222
15323
  }
15223
- /**
15224
- * Send the tool call start message.
15225
- */
15226
- sendToolCallStart(title) {
15227
- if (!this.currentCallId || this.toolCallStarted) {
15228
- return;
15229
- }
15230
- const toolCall = {
15231
- type: "tool-call",
15232
- name: this.getToolName(),
15233
- callId: this.currentCallId,
15234
- input: {
15235
- title
15236
- },
15237
- id: node_crypto.randomUUID()
15238
- };
15239
- persistence.logger.debug(`${this.getLogPrefix()} Sending tool call start for: "${title}"`);
15240
- this.onMessage?.(toolCall);
15241
- this.toolCallStarted = true;
15324
+ if ("status" in item && (item.status === "inProgress" || item.status === "running")) return;
15325
+ envelopes.push(api.createEnvelope("agent", {
15326
+ t: "tool-call-end",
15327
+ call: item.id
15328
+ }, {
15329
+ ...opts,
15330
+ id: `${item.id}:end`,
15331
+ time: completedTimestampMs(turn)
15332
+ }));
15333
+ }
15334
+ function mapCodexCollaborationItem(item, opts = {}, source = "history") {
15335
+ if (item.type === "subAgentActivity" && typeof item.agentThreadId === "string") {
15336
+ item = { ...item, type: "collabAgentToolCall", tool: item.kind, status: "completed", receiverThreadIds: [item.agentThreadId] };
15337
+ }
15338
+ if (item.type !== "collabAgentToolCall" || typeof item.id !== "string" || typeof item.tool !== "string") return [];
15339
+ const args = {
15340
+ operation: item.tool,
15341
+ ...typeof item.prompt === "string" ? { prompt: item.prompt } : {},
15342
+ ...typeof item.model === "string" ? { model: item.model } : {},
15343
+ receiverThreadIds: Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds : [],
15344
+ agentsStates: item.agentsStates ?? {}
15345
+ };
15346
+ const startId = source === "live" ? `${item.id}:${item.status ?? "unknown"}:start` : `${item.id}:start`;
15347
+ const envelopes = [api.createEnvelope("agent", {
15348
+ t: "tool-call-start",
15349
+ call: item.id,
15350
+ name: "CodexCollaboration",
15351
+ title: `Codex \xB7 ${item.tool}`,
15352
+ description: `Codex \xB7 ${item.tool}`,
15353
+ args
15354
+ }, { ...opts, id: startId })];
15355
+ if (item.status === "completed" || item.status === "failed" || item.status === "interrupted") {
15356
+ envelopes.push(api.createEnvelope("agent", {
15357
+ t: "tool-call-end",
15358
+ call: item.id,
15359
+ result: { text: JSON.stringify(args, null, 2), isError: item.status !== "completed" }
15360
+ }, { ...opts, id: `${item.id}:end` }));
15242
15361
  }
15243
- /**
15244
- * Complete the reasoning section.
15245
- * Returns true if reasoning was completed, false if there was nothing to complete.
15246
- */
15247
- completeReasoning(fullText) {
15248
- const text = fullText ?? this.accumulator;
15249
- if (!text.trim() && !this.toolCallStarted) {
15250
- persistence.logger.debug(`${this.getLogPrefix()} Complete called but no content accumulated, skipping`);
15251
- return false;
15252
- }
15253
- let title;
15254
- let content = text;
15255
- if (text.startsWith("**")) {
15256
- const titleEndIndex = text.indexOf("**", 2);
15257
- if (titleEndIndex !== -1) {
15258
- title = text.substring(2, titleEndIndex);
15259
- content = text.substring(titleEndIndex + 2).trim();
15362
+ return envelopes;
15363
+ }
15364
+ function mapCodexThreadToSessionEnvelopes(thread) {
15365
+ const envelopes = [];
15366
+ for (const turn of thread.turns ?? []) {
15367
+ const startedAt = turnTimestampMs(turn);
15368
+ const completedAt = completedTimestampMs(turn);
15369
+ envelopes.push(api.createEnvelope("agent", { t: "turn-start" }, {
15370
+ id: `${turn.id}:start`,
15371
+ turn: turn.id,
15372
+ time: startedAt
15373
+ }));
15374
+ for (const item of turn.items ?? []) {
15375
+ switch (item.type) {
15376
+ case "subAgentActivity":
15377
+ case "collabAgentToolCall": {
15378
+ envelopes.push(...mapCodexCollaborationItem(item, { turn: turn.id, time: startedAt, codexItemId: item.id }));
15379
+ break;
15380
+ }
15381
+ case "userMessage": {
15382
+ const text = textFromInputItems(item.content);
15383
+ if (text) {
15384
+ envelopes.push(api.createEnvelope("user", { t: "text", text }, {
15385
+ id: item.id,
15386
+ time: startedAt,
15387
+ codexItemId: item.id
15388
+ }));
15389
+ }
15390
+ break;
15391
+ }
15392
+ case "agentMessage": {
15393
+ const text = typeof item.text === "string" ? item.text.trim() : "";
15394
+ if (text.length > 0) {
15395
+ envelopes.push(api.createEnvelope("agent", { t: "text", text }, {
15396
+ id: item.id,
15397
+ turn: turn.id,
15398
+ time: completedAt,
15399
+ codexItemId: item.id
15400
+ }));
15401
+ }
15402
+ break;
15403
+ }
15404
+ case "reasoning": {
15405
+ const text = reasoningText(item);
15406
+ if (text) {
15407
+ envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, {
15408
+ id: item.id,
15409
+ turn: turn.id,
15410
+ time: startedAt,
15411
+ codexItemId: item.id
15412
+ }));
15413
+ }
15414
+ break;
15415
+ }
15416
+ case "commandExecution": {
15417
+ const command = typeof item.command === "string" ? item.command : "";
15418
+ emitHistoricalToolCall(
15419
+ envelopes,
15420
+ turn,
15421
+ item,
15422
+ "CodexBash",
15423
+ commandToTitle(command),
15424
+ { command, cwd: item.cwd },
15425
+ typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : null
15426
+ );
15427
+ break;
15428
+ }
15429
+ case "fileChange": {
15430
+ const title = "Apply patch";
15431
+ emitHistoricalToolCall(
15432
+ envelopes,
15433
+ turn,
15434
+ item,
15435
+ "CodexPatch",
15436
+ title,
15437
+ { changes: item.changes, status: item.status },
15438
+ null
15439
+ );
15440
+ break;
15441
+ }
15442
+ case "mcpToolCall": {
15443
+ const title = `${item.server}.${item.tool}`;
15444
+ const output = item.error !== void 0 && item.error !== null ? String(item.error) : item.result !== void 0 && item.result !== null ? String(item.result) : null;
15445
+ emitHistoricalToolCall(
15446
+ envelopes,
15447
+ turn,
15448
+ item,
15449
+ "McpTool",
15450
+ title,
15451
+ {
15452
+ server: item.server,
15453
+ tool: item.tool,
15454
+ arguments: item.arguments
15455
+ },
15456
+ output
15457
+ );
15458
+ break;
15459
+ }
15260
15460
  }
15261
15461
  }
15262
- persistence.logger.debug(`${this.getLogPrefix()} Complete reasoning - Title: "${title}", Has content: ${content.length > 0}`);
15263
- if (title && !this.toolCallStarted) {
15264
- this.currentCallId = this.currentCallId || node_crypto.randomUUID();
15265
- this.sendToolCallStart(title);
15266
- }
15267
- if (this.toolCallStarted && this.currentCallId) {
15268
- const toolResult = {
15269
- type: "tool-call-result",
15270
- callId: this.currentCallId,
15271
- output: {
15272
- content,
15273
- status: "completed"
15274
- },
15275
- id: node_crypto.randomUUID()
15276
- };
15277
- persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result`);
15278
- this.onMessage?.(toolResult);
15279
- } else if (content.trim()) {
15280
- const reasoningMessage = {
15281
- type: "reasoning",
15282
- message: content,
15283
- id: node_crypto.randomUUID()
15284
- };
15285
- persistence.logger.debug(`${this.getLogPrefix()} Sending reasoning message`);
15286
- this.onMessage?.(reasoningMessage);
15287
- }
15288
- this.resetState();
15289
- return true;
15290
- }
15291
- /**
15292
- * Abort the current reasoning section.
15293
- */
15294
- abort() {
15295
- persistence.logger.debug(`${this.getLogPrefix()} Abort called`);
15296
- this.finishCurrentToolCall("canceled");
15297
- this.resetState();
15298
- }
15299
- /**
15300
- * Reset the processor state.
15301
- */
15302
- reset() {
15303
- this.finishCurrentToolCall("canceled");
15304
- this.resetState();
15305
- }
15306
- /**
15307
- * Finish current tool call if one is in progress.
15308
- */
15309
- finishCurrentToolCall(status) {
15310
- if (this.toolCallStarted && this.currentCallId) {
15311
- const toolResult = {
15312
- type: "tool-call-result",
15313
- callId: this.currentCallId,
15314
- output: {
15315
- content: this.contentBuffer || "",
15316
- status
15317
- },
15318
- id: node_crypto.randomUUID()
15319
- };
15320
- persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result with status: ${status}`);
15321
- this.onMessage?.(toolResult);
15322
- }
15323
- }
15324
- /**
15325
- * Reset internal state.
15326
- */
15327
- resetState() {
15328
- this.accumulator = "";
15329
- this.inTitleCapture = false;
15330
- this.titleBuffer = "";
15331
- this.contentBuffer = "";
15332
- this.hasTitle = false;
15333
- this.currentCallId = null;
15334
- this.toolCallStarted = false;
15335
- this.currentTitle = null;
15462
+ envelopes.push(api.createEnvelope("agent", { t: "turn-end", status: turnStatus(turn) }, {
15463
+ id: `${turn.id}:end`,
15464
+ turn: turn.id,
15465
+ time: completedAt
15466
+ }));
15336
15467
  }
15337
- /**
15338
- * Get the current call ID for tool result matching.
15339
- */
15340
- getCurrentCallId() {
15341
- return this.currentCallId;
15468
+ return envelopes;
15469
+ }
15470
+ function patchDescription(changes) {
15471
+ if (!changes || typeof changes !== "object") {
15472
+ return "Applying patch";
15342
15473
  }
15343
- /**
15344
- * Check if a tool call has been started.
15345
- */
15346
- hasStartedToolCall() {
15347
- return this.toolCallStarted;
15474
+ const fileCount = Object.keys(changes).length;
15475
+ if (fileCount === 1) {
15476
+ return "Applying patch to 1 file";
15348
15477
  }
15478
+ return `Applying patch to ${fileCount} files`;
15349
15479
  }
15350
-
15351
- class ReasoningProcessor extends BaseReasoningProcessor {
15352
- getToolName() {
15353
- return "CodexReasoning";
15480
+ function pickTurnEndStatus(message, type) {
15481
+ const rawStatus = message.status;
15482
+ if (rawStatus === "completed" || rawStatus === "failed" || rawStatus === "cancelled") {
15483
+ return rawStatus;
15354
15484
  }
15355
- getLogPrefix() {
15356
- return "[ReasoningProcessor]";
15485
+ if (rawStatus === "canceled") {
15486
+ return "cancelled";
15357
15487
  }
15358
- /**
15359
- * Process a reasoning delta and accumulate content.
15360
- */
15361
- processDelta(delta) {
15362
- this.processInput(delta);
15488
+ if (type === "turn_aborted") {
15489
+ const reason = message.reason;
15490
+ const error = message.error;
15491
+ if (typeof reason === "string" && /(fail|error)/i.test(reason) || typeof error === "string" && error.length > 0 || error !== void 0 && error !== null && typeof error === "object") {
15492
+ return "failed";
15493
+ }
15494
+ return "cancelled";
15363
15495
  }
15364
- /**
15365
- * Complete the reasoning section with final text.
15366
- */
15367
- complete(fullText) {
15368
- this.completeReasoning(fullText);
15496
+ if (message.error !== void 0 && message.error !== null) {
15497
+ return "failed";
15369
15498
  }
15499
+ return "completed";
15370
15500
  }
15371
-
15372
- class DiffProcessor {
15373
- previousDiff = null;
15374
- onMessage = null;
15375
- constructor(onMessage) {
15376
- this.onMessage = onMessage || null;
15501
+ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15502
+ const type = message.type;
15503
+ const startedSubagents = getStartedSubagents(state);
15504
+ const activeSubagents = getActiveSubagents(state);
15505
+ const providerSubagentToSessionSubagent = getProviderSubagentToSessionSubagent(state);
15506
+ if (type === "collab_agent_item") {
15507
+ return {
15508
+ currentTurnId: state.currentTurnId,
15509
+ startedSubagents,
15510
+ activeSubagents,
15511
+ providerSubagentToSessionSubagent,
15512
+ envelopes: mapCodexCollaborationItem(message.item, buildEnvelopeOptions(state.currentTurnId), "live")
15513
+ };
15377
15514
  }
15378
- /**
15379
- * Process a turn_diff message and check if the unified_diff has changed
15380
- */
15381
- processDiff(unifiedDiff) {
15382
- if (this.previousDiff !== unifiedDiff) {
15383
- persistence.logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
15384
- const callId = node_crypto.randomUUID();
15385
- const toolCall = {
15386
- type: "tool-call",
15387
- name: "CodexDiff",
15388
- callId,
15389
- input: {
15390
- unified_diff: unifiedDiff
15391
- },
15392
- id: node_crypto.randomUUID()
15515
+ if (type === "task_started") {
15516
+ const turnId = cuid2.createId();
15517
+ const turnStart = api.createEnvelope("agent", { t: "turn-start" }, { turn: turnId });
15518
+ startedSubagents.clear();
15519
+ activeSubagents.clear();
15520
+ providerSubagentToSessionSubagent.clear();
15521
+ return {
15522
+ currentTurnId: turnId,
15523
+ startedSubagents,
15524
+ activeSubagents,
15525
+ providerSubagentToSessionSubagent,
15526
+ envelopes: [turnStart]
15527
+ };
15528
+ }
15529
+ if (type === "task_complete" || type === "turn_aborted") {
15530
+ if (!state.currentTurnId) {
15531
+ return {
15532
+ currentTurnId: null,
15533
+ startedSubagents,
15534
+ activeSubagents,
15535
+ providerSubagentToSessionSubagent,
15536
+ envelopes: []
15393
15537
  };
15394
- this.onMessage?.(toolCall);
15395
- const toolResult = {
15396
- type: "tool-call-result",
15397
- callId,
15398
- output: {
15399
- status: "completed"
15400
- },
15401
- id: node_crypto.randomUUID()
15538
+ }
15539
+ const lifecycleOpts = { turn: state.currentTurnId };
15540
+ providerSubagentToSessionSubagent.clear();
15541
+ return {
15542
+ currentTurnId: null,
15543
+ startedSubagents,
15544
+ activeSubagents,
15545
+ providerSubagentToSessionSubagent,
15546
+ envelopes: [
15547
+ ...emitSubagentStops(lifecycleOpts, startedSubagents, activeSubagents),
15548
+ api.createEnvelope("agent", {
15549
+ t: "turn-end",
15550
+ status: pickTurnEndStatus(message, type)
15551
+ }, lifecycleOpts)
15552
+ ]
15553
+ };
15554
+ }
15555
+ if (type === "token_count") {
15556
+ return {
15557
+ currentTurnId: state.currentTurnId,
15558
+ startedSubagents,
15559
+ activeSubagents,
15560
+ providerSubagentToSessionSubagent,
15561
+ envelopes: []
15562
+ };
15563
+ }
15564
+ const subagent = resolveSessionSubagent(message, providerSubagentToSessionSubagent);
15565
+ const opts = buildEnvelopeOptions(state.currentTurnId, subagent);
15566
+ if (type === "agent_message") {
15567
+ if (typeof message.message !== "string") {
15568
+ return {
15569
+ currentTurnId: state.currentTurnId,
15570
+ startedSubagents,
15571
+ activeSubagents,
15572
+ providerSubagentToSessionSubagent,
15573
+ envelopes: []
15402
15574
  };
15403
- this.onMessage?.(toolResult);
15404
15575
  }
15405
- this.previousDiff = unifiedDiff;
15406
- persistence.logger.debug("[DiffProcessor] Updated stored diff");
15576
+ const envelopes = [];
15577
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15578
+ envelopes.push(api.createEnvelope("agent", { t: "text", text: message.message }, opts));
15579
+ return {
15580
+ currentTurnId: state.currentTurnId,
15581
+ startedSubagents,
15582
+ activeSubagents,
15583
+ providerSubagentToSessionSubagent,
15584
+ envelopes
15585
+ };
15407
15586
  }
15408
- /**
15409
- * Reset the processor state (called on task_complete or turn_aborted)
15410
- */
15411
- reset() {
15412
- persistence.logger.debug("[DiffProcessor] Resetting diff state");
15413
- this.previousDiff = null;
15587
+ if (type === "agent_reasoning" || type === "agent_reasoning_delta") {
15588
+ const text = typeof message.text === "string" ? message.text : typeof message.delta === "string" ? message.delta : null;
15589
+ if (!text) {
15590
+ return {
15591
+ currentTurnId: state.currentTurnId,
15592
+ startedSubagents,
15593
+ activeSubagents,
15594
+ providerSubagentToSessionSubagent,
15595
+ envelopes: []
15596
+ };
15597
+ }
15598
+ const envelopes = [];
15599
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15600
+ envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, opts));
15601
+ return {
15602
+ currentTurnId: state.currentTurnId,
15603
+ startedSubagents,
15604
+ activeSubagents,
15605
+ providerSubagentToSessionSubagent,
15606
+ envelopes
15607
+ };
15414
15608
  }
15415
- /**
15416
- * Set the message callback for sending messages directly
15417
- */
15418
- setMessageCallback(callback) {
15419
- this.onMessage = callback;
15609
+ if (type === "exec_command_begin") {
15610
+ const call = pickCallId(message);
15611
+ const { call_id: _callIdSnake, callId: _callIdCamel, type: _type, ...args } = message;
15612
+ const command = summarizeCommand(args.command);
15613
+ const description = typeof args.description === "string" ? args.description : command ?? "Execute command";
15614
+ const envelopes = [];
15615
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15616
+ envelopes.push(
15617
+ api.createEnvelope("agent", {
15618
+ t: "tool-call-start",
15619
+ call,
15620
+ name: "CodexBash",
15621
+ title: commandToTitle(command),
15622
+ description,
15623
+ args
15624
+ }, opts)
15625
+ );
15626
+ return {
15627
+ currentTurnId: state.currentTurnId,
15628
+ startedSubagents,
15629
+ activeSubagents,
15630
+ providerSubagentToSessionSubagent,
15631
+ envelopes
15632
+ };
15420
15633
  }
15421
- /**
15422
- * Get the current diff value
15423
- */
15424
- getCurrentDiff() {
15425
- return this.previousDiff;
15634
+ if (type === "exec_command_end") {
15635
+ const call = pickCallId(message);
15636
+ const envelopes = [];
15637
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15638
+ envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
15639
+ return {
15640
+ currentTurnId: state.currentTurnId,
15641
+ startedSubagents,
15642
+ activeSubagents,
15643
+ providerSubagentToSessionSubagent,
15644
+ envelopes
15645
+ };
15646
+ }
15647
+ if (type === "patch_apply_begin") {
15648
+ const call = pickCallId(message);
15649
+ const autoApproved = message.auto_approved;
15650
+ const changes = message.changes;
15651
+ const envelopes = [];
15652
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15653
+ envelopes.push(
15654
+ api.createEnvelope("agent", {
15655
+ t: "tool-call-start",
15656
+ call,
15657
+ name: "CodexPatch",
15658
+ title: "Apply patch",
15659
+ description: patchDescription(changes),
15660
+ args: {
15661
+ auto_approved: autoApproved,
15662
+ changes
15663
+ }
15664
+ }, opts)
15665
+ );
15666
+ return {
15667
+ currentTurnId: state.currentTurnId,
15668
+ startedSubagents,
15669
+ activeSubagents,
15670
+ providerSubagentToSessionSubagent,
15671
+ envelopes
15672
+ };
15673
+ }
15674
+ if (type === "patch_apply_end") {
15675
+ const call = pickCallId(message);
15676
+ const envelopes = [];
15677
+ maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15678
+ envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
15679
+ return {
15680
+ currentTurnId: state.currentTurnId,
15681
+ startedSubagents,
15682
+ activeSubagents,
15683
+ providerSubagentToSessionSubagent,
15684
+ envelopes
15685
+ };
15686
+ }
15687
+ return {
15688
+ currentTurnId: state.currentTurnId,
15689
+ startedSubagents,
15690
+ activeSubagents,
15691
+ providerSubagentToSessionSubagent,
15692
+ envelopes: []
15693
+ };
15694
+ }
15695
+ function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
15696
+ const toolLikeMessage = message;
15697
+ const opts = buildEnvelopeOptions(state.currentTurnId);
15698
+ if (message.type === "reasoning") {
15699
+ return [api.createEnvelope("agent", {
15700
+ t: "text",
15701
+ text: message.message,
15702
+ thinking: true
15703
+ }, opts)];
15704
+ }
15705
+ if (message.type === "tool-call") {
15706
+ const title = typeof toolLikeMessage.input?.title === "string" ? toolLikeMessage.input.title : `${toolLikeMessage.name || "Tool"} call`;
15707
+ return [api.createEnvelope("agent", {
15708
+ t: "tool-call-start",
15709
+ call: toolLikeMessage.callId,
15710
+ name: toolLikeMessage.name || "unknown",
15711
+ title,
15712
+ description: title,
15713
+ args: toolLikeMessage.input && typeof toolLikeMessage.input === "object" ? toolLikeMessage.input : {}
15714
+ }, opts)];
15715
+ }
15716
+ if (message.type === "tool-call-result") {
15717
+ const envelopes = [];
15718
+ const content = toolLikeMessage.output?.content;
15719
+ if (typeof content === "string" && content.trim().length > 0) {
15720
+ envelopes.push(api.createEnvelope("agent", {
15721
+ t: "text",
15722
+ text: content,
15723
+ thinking: true
15724
+ }, opts));
15725
+ }
15726
+ envelopes.push(api.createEnvelope("agent", {
15727
+ t: "tool-call-end",
15728
+ call: toolLikeMessage.callId
15729
+ }, opts));
15730
+ return envelopes;
15426
15731
  }
15732
+ return [];
15427
15733
  }
15428
15734
 
15429
- const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
15430
- const [messages, setMessages] = React.useState([]);
15431
- const [confirmationMode, setConfirmationMode] = React.useState(false);
15432
- const [actionInProgress, setActionInProgress] = React.useState(false);
15433
- const confirmationTimeoutRef = React.useRef(null);
15434
- const { stdout } = ink.useStdout();
15435
- const terminalWidth = stdout.columns || 80;
15436
- const terminalHeight = stdout.rows || 24;
15437
- React.useEffect(() => {
15438
- setMessages(messageBuffer.getMessages());
15439
- const unsubscribe = messageBuffer.onUpdate((newMessages) => {
15440
- setMessages(newMessages);
15441
- });
15442
- return () => {
15443
- unsubscribe();
15444
- if (confirmationTimeoutRef.current) {
15445
- clearTimeout(confirmationTimeoutRef.current);
15735
+ async function readChildThread(client, childId) {
15736
+ if (!client.threadId || typeof childId !== "string" || !childId) throw new Error("Invalid child thread");
15737
+ const parentId = client.threadId;
15738
+ const { thread: parent } = await client.readThread({ threadId: parentId, includeTurns: true });
15739
+ 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));
15740
+ if (!belongs) throw new Error("Thread is not a child of this session");
15741
+ const { thread } = await client.readThread({ threadId: childId, includeTurns: true });
15742
+ if (thread.id !== childId) throw new Error("Child thread mismatch");
15743
+ return { envelopes: mapCodexThreadToSessionEnvelopes(thread) };
15744
+ }
15745
+
15746
+ function registerAgentAttachmentDownloads(session) {
15747
+ session.onFileEvent((fileEvent) => {
15748
+ const attachment = fileEvent.content.data.ev;
15749
+ const download = (async () => {
15750
+ try {
15751
+ const data = await session.downloadAndDecryptAttachment(attachment.ref);
15752
+ if (!data) throw new Error("Attachment decryption failed");
15753
+ return { data, mimeType: attachment.mimeType ?? "image/jpeg", name: attachment.name };
15754
+ } catch {
15755
+ session.sendSessionEvent({ type: "message", message: `Could not download attachment: ${attachment.name}. Please attach it again.` });
15756
+ return null;
15446
15757
  }
15447
- };
15448
- }, [messageBuffer]);
15449
- const resetConfirmation = React.useCallback(() => {
15450
- setConfirmationMode(false);
15451
- if (confirmationTimeoutRef.current) {
15452
- clearTimeout(confirmationTimeoutRef.current);
15453
- confirmationTimeoutRef.current = null;
15454
- }
15455
- }, []);
15456
- const setConfirmationWithTimeout = React.useCallback(() => {
15457
- setConfirmationMode(true);
15458
- if (confirmationTimeoutRef.current) {
15459
- clearTimeout(confirmationTimeoutRef.current);
15460
- }
15461
- confirmationTimeoutRef.current = setTimeout(() => {
15462
- resetConfirmation();
15463
- }, 15e3);
15464
- }, [resetConfirmation]);
15465
- ink.useInput(React.useCallback(async (input, key) => {
15466
- if (actionInProgress) return;
15467
- if (key.ctrl && input === "c") {
15468
- if (confirmationMode) {
15469
- resetConfirmation();
15470
- setActionInProgress(true);
15471
- await new Promise((resolve) => setTimeout(resolve, 100));
15472
- onExit?.();
15473
- } else {
15474
- setConfirmationWithTimeout();
15758
+ })();
15759
+ session.trackAttachmentDownload(download);
15760
+ });
15761
+ }
15762
+
15763
+ class BasePermissionHandler {
15764
+ pendingRequests = /* @__PURE__ */ new Map();
15765
+ session;
15766
+ isResetting = false;
15767
+ constructor(session) {
15768
+ this.session = session;
15769
+ this.setupRpcHandler();
15770
+ }
15771
+ /**
15772
+ * Update the session reference (used after offline reconnection swaps sessions).
15773
+ * This is critical for avoiding stale session references after onSessionSwap.
15774
+ */
15775
+ updateSession(newSession) {
15776
+ persistence.logger.debug(`${this.getLogPrefix()} Session reference updated`);
15777
+ this.session = newSession;
15778
+ this.setupRpcHandler();
15779
+ }
15780
+ /**
15781
+ * Setup RPC handler for permission responses.
15782
+ */
15783
+ setupRpcHandler() {
15784
+ this.session.rpcHandlerManager.registerHandler(
15785
+ "permission",
15786
+ async (response) => {
15787
+ const pending = this.pendingRequests.get(response.id);
15788
+ if (!pending) {
15789
+ persistence.logger.debug(`${this.getLogPrefix()} Permission request not found or already resolved`);
15790
+ return;
15791
+ }
15792
+ this.pendingRequests.delete(response.id);
15793
+ const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
15794
+ pending.resolve(result);
15795
+ this.session.updateAgentState((currentState) => {
15796
+ const request = currentState.requests?.[response.id];
15797
+ if (!request) return currentState;
15798
+ const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
15799
+ let res = {
15800
+ ...currentState,
15801
+ requests: remainingRequests,
15802
+ completedRequests: {
15803
+ ...currentState.completedRequests,
15804
+ [response.id]: {
15805
+ ...request,
15806
+ completedAt: Date.now(),
15807
+ status: response.approved ? "approved" : "denied",
15808
+ decision: result.decision
15809
+ }
15810
+ }
15811
+ };
15812
+ return res;
15813
+ });
15814
+ persistence.logger.debug(`${this.getLogPrefix()} Permission ${response.approved ? "approved" : "denied"} for ${pending.toolName}`);
15475
15815
  }
15476
- return;
15477
- }
15478
- if (confirmationMode) {
15479
- resetConfirmation();
15480
- }
15481
- }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
15482
- const getMessageColor = (type) => {
15483
- switch (type) {
15484
- case "user":
15485
- return "magenta";
15486
- case "assistant":
15487
- return "cyan";
15488
- case "system":
15489
- return "blue";
15490
- case "tool":
15491
- return "yellow";
15492
- case "result":
15493
- return "green";
15494
- case "status":
15495
- return "gray";
15496
- default:
15497
- return "white";
15498
- }
15499
- };
15500
- const formatMessage = (msg) => {
15501
- const lines = msg.content.split("\n");
15502
- const maxLineLength = terminalWidth - 10;
15503
- return lines.map((line) => {
15504
- if (line.length <= maxLineLength) return line;
15505
- const chunks = [];
15506
- for (let i = 0; i < line.length; i += maxLineLength) {
15507
- chunks.push(line.slice(i, i + maxLineLength));
15816
+ );
15817
+ }
15818
+ /**
15819
+ * Add a pending request to the agent state.
15820
+ */
15821
+ addPendingRequestToState(toolCallId, toolName, input) {
15822
+ this.session.updateAgentState((currentState) => ({
15823
+ ...currentState,
15824
+ requests: {
15825
+ ...currentState.requests,
15826
+ [toolCallId]: {
15827
+ tool: toolName,
15828
+ arguments: input,
15829
+ createdAt: Date.now()
15830
+ }
15508
15831
  }
15509
- return chunks.join("\n");
15510
- }).join("\n");
15511
- };
15512
- return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
15513
- ink.Box,
15514
- {
15515
- flexDirection: "column",
15516
- width: terminalWidth,
15517
- height: terminalHeight - 4,
15518
- borderStyle: "round",
15519
- borderColor: "gray",
15520
- paddingX: 1,
15521
- overflow: "hidden"
15522
- },
15523
- /* @__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)))),
15524
- /* @__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...") : (
15525
- // Show only the last messages that fit in the available space
15526
- 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))))
15527
- ))
15528
- ), /* @__PURE__ */ React.createElement(
15529
- ink.Box,
15530
- {
15531
- width: terminalWidth,
15532
- borderStyle: "round",
15533
- borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "green",
15534
- paddingX: 2,
15535
- justifyContent: "center",
15536
- alignItems: "center",
15537
- flexDirection: "column"
15538
- },
15539
- /* @__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))
15540
- ));
15541
- };
15542
-
15543
- function createOfflineSessionStub(sessionTag) {
15544
- return {
15545
- sessionId: `offline-${sessionTag}`,
15546
- sendCodexMessage: () => {
15547
- },
15548
- sendAgentMessage: () => {
15549
- },
15550
- sendAgentUsageSnapshot: () => false,
15551
- sendClaudeSessionMessage: () => {
15552
- },
15553
- keepAlive: () => {
15554
- },
15555
- sendSessionEvent: () => {
15556
- },
15557
- sendSessionDeath: () => {
15558
- },
15559
- updateLifecycleState: () => {
15560
- },
15561
- requestControlTransfer: async () => {
15562
- },
15563
- flush: async () => {
15564
- },
15565
- close: async () => {
15566
- },
15567
- updateMetadata: () => {
15568
- },
15569
- updateAgentState: () => {
15570
- },
15571
- onUserMessage: () => {
15572
- },
15573
- onFileEvent: () => {
15574
- },
15575
- trackAttachmentDownload: () => {
15576
- },
15577
- drainAttachmentsForUserMessage: async () => [],
15578
- rpcHandlerManager: {
15579
- registerHandler: () => {
15832
+ }));
15833
+ }
15834
+ /**
15835
+ * Abort all pending permission requests.
15836
+ * Unlike reset(), this resolves (not rejects) pending promises with { decision: 'abort' },
15837
+ * causing the approval response to send 'cancel' to the provider. This is used when the
15838
+ * user presses the abort/stop button — it unblocks any pending tool approval so the provider
15839
+ * can process the turn cancellation.
15840
+ */
15841
+ abortAll() {
15842
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
15843
+ if (pendingSnapshot.length === 0) return;
15844
+ this.pendingRequests.clear();
15845
+ for (const [id, pending] of pendingSnapshot) {
15846
+ try {
15847
+ pending.resolve({ decision: "abort" });
15848
+ } catch (err) {
15849
+ persistence.logger.debug(`${this.getLogPrefix()} Error resolving aborted request ${id}:`, err);
15580
15850
  }
15581
15851
  }
15582
- };
15583
- }
15584
-
15585
- function setupOfflineReconnection(opts) {
15586
- const { api: api$1, sessionTag, metadata, state, response, onSessionSwap } = opts;
15587
- let session;
15588
- let reconnectionHandle = null;
15589
- if (!response) {
15590
- session = createOfflineSessionStub(sessionTag);
15591
- reconnectionHandle = api.startOfflineReconnection({
15592
- serverUrl: persistence.configuration.serverUrl,
15593
- onReconnected: async () => {
15594
- const resp = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
15595
- if (!resp) throw new Error("Server unavailable");
15596
- const realSession = api$1.sessionSyncClient(resp);
15597
- onSessionSwap(realSession);
15598
- return realSession;
15599
- },
15600
- onNotify: (msg) => {
15601
- console.log(msg);
15852
+ this.session.updateAgentState((currentState) => {
15853
+ const pendingRequests = currentState.requests || {};
15854
+ const completedRequests = { ...currentState.completedRequests };
15855
+ for (const [id, request] of Object.entries(pendingRequests)) {
15856
+ completedRequests[id] = {
15857
+ ...request,
15858
+ completedAt: Date.now(),
15859
+ status: "canceled",
15860
+ reason: "Aborted by user"
15861
+ };
15602
15862
  }
15863
+ return {
15864
+ ...currentState,
15865
+ requests: {},
15866
+ completedRequests
15867
+ };
15603
15868
  });
15604
- return { session, reconnectionHandle, isOffline: true };
15605
- } else {
15606
- session = api$1.sessionSyncClient(response, opts.sessionClientOptions);
15607
- return { session, reconnectionHandle: null, isOffline: false };
15608
- }
15609
- }
15610
-
15611
- function resolveCodexExecutionPolicy(permissionMode, sandboxManagedByHappy) {
15612
- if (sandboxManagedByHappy) {
15613
- return {
15614
- approvalPolicy: "never",
15615
- sandbox: "danger-full-access"
15616
- };
15869
+ persistence.logger.debug(`${this.getLogPrefix()} Aborted ${pendingSnapshot.length} pending permission(s)`);
15617
15870
  }
15618
- const approvalPolicy = (() => {
15619
- switch (permissionMode) {
15620
- // Codex native modes
15621
- case "default":
15622
- return "untrusted";
15623
- // Ask for non-trusted commands
15624
- case "read-only":
15625
- return "never";
15626
- // Never ask, read-only enforced by sandbox
15627
- case "safe-yolo":
15628
- return "on-request";
15629
- // Workspace auto-run; request approval for escalation (0.153 removed on-failure)
15630
- case "yolo":
15631
- return "never";
15632
- // Full YOLO: never interrupt for approvals
15633
- // Defensive fallback for Claude-specific modes (backward compatibility)
15634
- case "bypassPermissions":
15635
- return "never";
15636
- // Full access: map to yolo behavior
15637
- case "acceptEdits":
15638
- return "on-request";
15639
- // Let model decide (closest to auto-approve edits)
15640
- case "plan":
15641
- return "untrusted";
15642
- // Conservative: ask for non-trusted
15643
- default:
15644
- return "untrusted";
15871
+ /**
15872
+ * Reset state for new sessions.
15873
+ * This method is idempotent - safe to call multiple times.
15874
+ */
15875
+ reset(reason = "Session reset") {
15876
+ if (this.isResetting) {
15877
+ persistence.logger.debug(`${this.getLogPrefix()} Reset already in progress, skipping`);
15878
+ return;
15645
15879
  }
15646
- })();
15647
- const sandbox = (() => {
15648
- switch (permissionMode) {
15649
- // Codex native modes
15650
- case "default":
15651
- return "workspace-write";
15652
- // Can write in workspace
15653
- case "read-only":
15654
- return "read-only";
15655
- // Read-only filesystem
15656
- case "safe-yolo":
15657
- return "workspace-write";
15658
- // Can write in workspace
15659
- case "yolo":
15660
- return "danger-full-access";
15661
- // Full system access
15662
- // Defensive fallback for Claude-specific modes
15663
- case "bypassPermissions":
15664
- return "danger-full-access";
15665
- // Full access: map to yolo
15666
- case "acceptEdits":
15667
- return "workspace-write";
15668
- // Can edit files in workspace
15669
- case "plan":
15670
- return "workspace-write";
15671
- // Can write for planning
15672
- default:
15673
- return "workspace-write";
15880
+ this.isResetting = true;
15881
+ try {
15882
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
15883
+ this.pendingRequests.clear();
15884
+ for (const [id, pending] of pendingSnapshot) {
15885
+ try {
15886
+ pending.reject(new Error("Session reset"));
15887
+ } catch (err) {
15888
+ persistence.logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
15889
+ }
15890
+ }
15891
+ this.session.updateAgentState((currentState) => {
15892
+ const pendingRequests = currentState.requests || {};
15893
+ const completedRequests = { ...currentState.completedRequests };
15894
+ for (const [id, request] of Object.entries(pendingRequests)) {
15895
+ completedRequests[id] = {
15896
+ ...request,
15897
+ completedAt: Date.now(),
15898
+ status: "canceled",
15899
+ reason
15900
+ };
15901
+ }
15902
+ return {
15903
+ ...currentState,
15904
+ requests: {},
15905
+ completedRequests
15906
+ };
15907
+ });
15908
+ persistence.logger.debug(`${this.getLogPrefix()} Permission handler reset`);
15909
+ } finally {
15910
+ this.isResetting = false;
15674
15911
  }
15675
- })();
15676
- return { approvalPolicy, sandbox };
15912
+ }
15677
15913
  }
15678
15914
 
15679
- function getStartedSubagents(state) {
15680
- return state.startedSubagents ?? /* @__PURE__ */ new Set();
15681
- }
15682
- function getActiveSubagents(state) {
15683
- return state.activeSubagents ?? /* @__PURE__ */ new Set();
15684
- }
15685
- function getProviderSubagentToSessionSubagent(state) {
15686
- return state.providerSubagentToSessionSubagent ?? /* @__PURE__ */ new Map();
15687
- }
15688
- function maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes) {
15689
- if (!subagent || startedSubagents.has(subagent)) {
15690
- return;
15915
+ class CodexPermissionHandler extends BasePermissionHandler {
15916
+ // Exact tool names that should always be auto-approved. Include the bare
15917
+ // form (used by Codex elicitation messages like `tool "change_title"`)
15918
+ // and the MCP-qualified form for defense in depth.
15919
+ static ALWAYS_AUTO_APPROVE_NAMES = /* @__PURE__ */ new Set([
15920
+ "change_title",
15921
+ "mcp__happy__change_title"
15922
+ ]);
15923
+ // Tool-call IDs that should auto-approve when they exactly match one of
15924
+ // these values or start with `<name>-` (e.g. `change_title-1765385846663`).
15925
+ // Substring matching was a bypass vector — any tool whose ID happened to
15926
+ // contain `change_title` as a substring would be silently approved.
15927
+ static ALWAYS_AUTO_APPROVE_ID_PREFIXES = [
15928
+ "change_title"
15929
+ ];
15930
+ constructor(session) {
15931
+ super(session);
15691
15932
  }
15692
- envelopes.push(api.createEnvelope("agent", { t: "start" }, { ...opts, subagent }));
15693
- startedSubagents.add(subagent);
15694
- activeSubagents.add(subagent);
15695
- }
15696
- function emitSubagentStops(opts, startedSubagents, activeSubagents) {
15697
- const envelopes = [];
15698
- for (const subagent of activeSubagents) {
15699
- envelopes.push(api.createEnvelope("agent", { t: "stop" }, { ...opts, subagent }));
15933
+ getLogPrefix() {
15934
+ return "[Codex]";
15700
15935
  }
15701
- activeSubagents.clear();
15702
- startedSubagents.clear();
15703
- return envelopes;
15704
- }
15705
- function buildEnvelopeOptions(currentTurnId, subagent) {
15706
- return {
15707
- ...currentTurnId ? { turn: currentTurnId } : {},
15708
- ...subagent ? { subagent } : {}
15709
- };
15710
- }
15711
- function pickProviderSubagent(message) {
15712
- const candidates = [message.subagent, message.parent_call_id, message.parentCallId];
15713
- for (const candidate of candidates) {
15714
- if (typeof candidate === "string" && candidate.length > 0) {
15715
- return candidate;
15936
+ shouldAutoApprove(toolName, toolCallId) {
15937
+ if (CodexPermissionHandler.ALWAYS_AUTO_APPROVE_NAMES.has(toolName)) {
15938
+ return true;
15716
15939
  }
15940
+ for (const prefix of CodexPermissionHandler.ALWAYS_AUTO_APPROVE_ID_PREFIXES) {
15941
+ if (toolCallId === prefix || toolCallId.startsWith(`${prefix}-`)) {
15942
+ return true;
15943
+ }
15944
+ }
15945
+ return false;
15717
15946
  }
15718
- return void 0;
15719
- }
15720
- function resolveSessionSubagent(message, providerSubagentToSessionSubagent) {
15721
- const providerSubagent = pickProviderSubagent(message);
15722
- if (!providerSubagent) {
15723
- return void 0;
15724
- }
15725
- const existing = providerSubagentToSessionSubagent.get(providerSubagent);
15726
- if (existing) {
15727
- return existing;
15947
+ /**
15948
+ * Handle a tool permission request
15949
+ * @param toolCallId - The unique ID of the tool call
15950
+ * @param toolName - The name of the tool being called
15951
+ * @param input - The input parameters for the tool
15952
+ * @returns Promise resolving to permission result
15953
+ */
15954
+ async handleToolCall(toolCallId, toolName, input) {
15955
+ if (this.shouldAutoApprove(toolName, toolCallId)) {
15956
+ persistence.logger.debug(`${this.getLogPrefix()} Auto-approving tool ${toolName} (${toolCallId})`);
15957
+ this.session.updateAgentState((currentState) => ({
15958
+ ...currentState,
15959
+ completedRequests: {
15960
+ ...currentState.completedRequests,
15961
+ [toolCallId]: {
15962
+ tool: toolName,
15963
+ arguments: input,
15964
+ createdAt: Date.now(),
15965
+ completedAt: Date.now(),
15966
+ status: "approved",
15967
+ decision: "approved"
15968
+ }
15969
+ }
15970
+ }));
15971
+ return { decision: "approved" };
15972
+ }
15973
+ return new Promise((resolve, reject) => {
15974
+ this.pendingRequests.set(toolCallId, {
15975
+ resolve,
15976
+ reject,
15977
+ toolName,
15978
+ input
15979
+ });
15980
+ this.addPendingRequestToState(toolCallId, toolName, input);
15981
+ persistence.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
15982
+ });
15728
15983
  }
15729
- const created = cuid2.createId();
15730
- providerSubagentToSessionSubagent.set(providerSubagent, created);
15731
- return created;
15732
15984
  }
15733
- function pickCallId(message) {
15734
- const callId = message.call_id ?? message.callId;
15735
- if (typeof callId === "string" && callId.length > 0) {
15736
- return callId;
15985
+
15986
+ class BaseReasoningProcessor {
15987
+ accumulator = "";
15988
+ inTitleCapture = false;
15989
+ titleBuffer = "";
15990
+ contentBuffer = "";
15991
+ hasTitle = false;
15992
+ currentCallId = null;
15993
+ toolCallStarted = false;
15994
+ currentTitle = null;
15995
+ onMessage = null;
15996
+ constructor(onMessage) {
15997
+ this.onMessage = onMessage || null;
15998
+ this.reset();
15737
15999
  }
15738
- return node_crypto.randomUUID();
15739
- }
15740
- function summarizeCommand(command) {
15741
- if (typeof command === "string" && command.trim().length > 0) {
15742
- return command;
16000
+ /**
16001
+ * Set the message callback for sending messages directly.
16002
+ */
16003
+ setMessageCallback(callback) {
16004
+ this.onMessage = callback;
15743
16005
  }
15744
- if (Array.isArray(command)) {
15745
- const cmd = command.map((v) => String(v)).join(" ").trim();
15746
- return cmd.length > 0 ? cmd : null;
16006
+ /**
16007
+ * Process a reasoning section break - indicates a new reasoning section is starting.
16008
+ */
16009
+ handleSectionBreak() {
16010
+ this.finishCurrentToolCall("canceled");
16011
+ this.resetState();
16012
+ persistence.logger.debug(`${this.getLogPrefix()} Section break - reset state`);
15747
16013
  }
15748
- return null;
15749
- }
15750
- function commandToTitle(command) {
15751
- if (!command) {
15752
- return "Run command";
16014
+ /**
16015
+ * Process a reasoning delta/chunk and accumulate content.
16016
+ */
16017
+ processInput(input) {
16018
+ this.accumulator += input;
16019
+ if (!this.inTitleCapture && !this.hasTitle && !this.contentBuffer) {
16020
+ if (this.accumulator.startsWith("**")) {
16021
+ this.inTitleCapture = true;
16022
+ this.titleBuffer = this.accumulator.substring(2);
16023
+ persistence.logger.debug(`${this.getLogPrefix()} Started title capture`);
16024
+ } else if (this.accumulator.length > 0) {
16025
+ this.contentBuffer = this.accumulator;
16026
+ }
16027
+ } else if (this.inTitleCapture) {
16028
+ this.titleBuffer = this.accumulator.substring(2);
16029
+ const titleEndIndex = this.titleBuffer.indexOf("**");
16030
+ if (titleEndIndex !== -1) {
16031
+ const title = this.titleBuffer.substring(0, titleEndIndex);
16032
+ const afterTitle = this.titleBuffer.substring(titleEndIndex + 2);
16033
+ this.hasTitle = true;
16034
+ this.inTitleCapture = false;
16035
+ this.currentTitle = title;
16036
+ this.contentBuffer = afterTitle;
16037
+ this.currentCallId = node_crypto.randomUUID();
16038
+ persistence.logger.debug(`${this.getLogPrefix()} Title captured: "${title}"`);
16039
+ this.sendToolCallStart(title);
16040
+ }
16041
+ } else if (this.hasTitle) {
16042
+ const titleStartIndex = this.accumulator.indexOf("**");
16043
+ if (titleStartIndex !== -1) {
16044
+ this.contentBuffer = this.accumulator.substring(
16045
+ titleStartIndex + 2 + this.currentTitle.length + 2
16046
+ );
16047
+ }
16048
+ } else {
16049
+ this.contentBuffer = this.accumulator;
16050
+ }
15753
16051
  }
15754
- const short = command.length > 80 ? `${command.slice(0, 77)}...` : command;
15755
- return `Run \`${short}\``;
15756
- }
15757
- function turnTimestampMs(turn) {
15758
- const seconds = turn.startedAt ?? turn.completedAt;
15759
- return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15760
- }
15761
- function completedTimestampMs(turn) {
15762
- const seconds = turn.completedAt ?? turn.startedAt;
15763
- return typeof seconds === "number" && Number.isFinite(seconds) ? seconds * 1e3 : Date.now();
15764
- }
15765
- function textFromInputItems(items) {
15766
- if (!Array.isArray(items)) {
15767
- return null;
16052
+ /**
16053
+ * Send the tool call start message.
16054
+ */
16055
+ sendToolCallStart(title) {
16056
+ if (!this.currentCallId || this.toolCallStarted) {
16057
+ return;
16058
+ }
16059
+ const toolCall = {
16060
+ type: "tool-call",
16061
+ name: this.getToolName(),
16062
+ callId: this.currentCallId,
16063
+ input: {
16064
+ title
16065
+ },
16066
+ id: node_crypto.randomUUID()
16067
+ };
16068
+ persistence.logger.debug(`${this.getLogPrefix()} Sending tool call start for: "${title}"`);
16069
+ this.onMessage?.(toolCall);
16070
+ this.toolCallStarted = true;
15768
16071
  }
15769
- const text = items.filter((item) => Boolean(item) && typeof item === "object" && item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n").trim();
15770
- return text.length > 0 ? text : null;
15771
- }
15772
- function reasoningText(item) {
15773
- const summary = item.summary;
15774
- const content = item.content;
15775
- const parts = [
15776
- ...Array.isArray(summary) ? summary : [],
15777
- ...Array.isArray(content) ? content : []
15778
- ].filter((value) => typeof value === "string" && value.trim().length > 0);
15779
- const text = parts.join("\n").trim();
15780
- return text.length > 0 ? text : null;
15781
- }
15782
- function turnStatus(turn) {
15783
- const status = typeof turn.status === "string" ? turn.status : null;
15784
- if (status === "failed") {
15785
- return "failed";
16072
+ /**
16073
+ * Complete the reasoning section.
16074
+ * Returns true if reasoning was completed, false if there was nothing to complete.
16075
+ */
16076
+ completeReasoning(fullText) {
16077
+ const text = fullText ?? this.accumulator;
16078
+ if (!text.trim() && !this.toolCallStarted) {
16079
+ persistence.logger.debug(`${this.getLogPrefix()} Complete called but no content accumulated, skipping`);
16080
+ return false;
16081
+ }
16082
+ let title;
16083
+ let content = text;
16084
+ if (text.startsWith("**")) {
16085
+ const titleEndIndex = text.indexOf("**", 2);
16086
+ if (titleEndIndex !== -1) {
16087
+ title = text.substring(2, titleEndIndex);
16088
+ content = text.substring(titleEndIndex + 2).trim();
16089
+ }
16090
+ }
16091
+ persistence.logger.debug(`${this.getLogPrefix()} Complete reasoning - Title: "${title}", Has content: ${content.length > 0}`);
16092
+ if (title && !this.toolCallStarted) {
16093
+ this.currentCallId = this.currentCallId || node_crypto.randomUUID();
16094
+ this.sendToolCallStart(title);
16095
+ }
16096
+ if (this.toolCallStarted && this.currentCallId) {
16097
+ const toolResult = {
16098
+ type: "tool-call-result",
16099
+ callId: this.currentCallId,
16100
+ output: {
16101
+ content,
16102
+ status: "completed"
16103
+ },
16104
+ id: node_crypto.randomUUID()
16105
+ };
16106
+ persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result`);
16107
+ this.onMessage?.(toolResult);
16108
+ } else if (content.trim()) {
16109
+ const reasoningMessage = {
16110
+ type: "reasoning",
16111
+ message: content,
16112
+ id: node_crypto.randomUUID()
16113
+ };
16114
+ persistence.logger.debug(`${this.getLogPrefix()} Sending reasoning message`);
16115
+ this.onMessage?.(reasoningMessage);
16116
+ }
16117
+ this.resetState();
16118
+ return true;
15786
16119
  }
15787
- if (status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted") {
15788
- return "cancelled";
16120
+ /**
16121
+ * Abort the current reasoning section.
16122
+ */
16123
+ abort() {
16124
+ persistence.logger.debug(`${this.getLogPrefix()} Abort called`);
16125
+ this.finishCurrentToolCall("canceled");
16126
+ this.resetState();
15789
16127
  }
15790
- return "completed";
15791
- }
15792
- function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output) {
15793
- const time = turnTimestampMs(turn);
15794
- const opts = { turn: turn.id, time, codexItemId: item.id };
15795
- envelopes.push(api.createEnvelope("agent", {
15796
- t: "tool-call-start",
15797
- call: item.id,
15798
- name,
15799
- title,
15800
- description: title,
15801
- args
15802
- }, {
15803
- ...opts,
15804
- id: `${item.id}:start`
15805
- }));
15806
- if (output && output.trim().length > 0) {
15807
- envelopes.push(api.createEnvelope("agent", {
15808
- t: "text",
15809
- text: output,
15810
- thinking: true
15811
- }, {
15812
- ...opts,
15813
- id: `${item.id}:output`
15814
- }));
16128
+ /**
16129
+ * Reset the processor state.
16130
+ */
16131
+ reset() {
16132
+ this.finishCurrentToolCall("canceled");
16133
+ this.resetState();
15815
16134
  }
15816
- envelopes.push(api.createEnvelope("agent", {
15817
- t: "tool-call-end",
15818
- call: item.id
15819
- }, {
15820
- ...opts,
15821
- id: `${item.id}:end`,
15822
- time: completedTimestampMs(turn)
15823
- }));
15824
- }
15825
- function mapCodexThreadToSessionEnvelopes(thread) {
15826
- const envelopes = [];
15827
- for (const turn of thread.turns ?? []) {
15828
- const startedAt = turnTimestampMs(turn);
15829
- const completedAt = completedTimestampMs(turn);
15830
- envelopes.push(api.createEnvelope("agent", { t: "turn-start" }, {
15831
- id: `${turn.id}:start`,
15832
- turn: turn.id,
15833
- time: startedAt
15834
- }));
15835
- for (const item of turn.items ?? []) {
15836
- switch (item.type) {
15837
- case "userMessage": {
15838
- const text = textFromInputItems(item.content);
15839
- if (text) {
15840
- envelopes.push(api.createEnvelope("user", { t: "text", text }, {
15841
- id: item.id,
15842
- time: startedAt,
15843
- codexItemId: item.id
15844
- }));
15845
- }
15846
- break;
15847
- }
15848
- case "agentMessage": {
15849
- const text = typeof item.text === "string" ? item.text.trim() : "";
15850
- if (text.length > 0) {
15851
- envelopes.push(api.createEnvelope("agent", { t: "text", text }, {
15852
- id: item.id,
15853
- turn: turn.id,
15854
- time: completedAt,
15855
- codexItemId: item.id
15856
- }));
15857
- }
15858
- break;
15859
- }
15860
- case "reasoning": {
15861
- const text = reasoningText(item);
15862
- if (text) {
15863
- envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, {
15864
- id: item.id,
15865
- turn: turn.id,
15866
- time: startedAt,
15867
- codexItemId: item.id
15868
- }));
15869
- }
15870
- break;
15871
- }
15872
- case "commandExecution": {
15873
- const command = typeof item.command === "string" ? item.command : "";
15874
- emitHistoricalToolCall(
15875
- envelopes,
15876
- turn,
15877
- item,
15878
- "CodexBash",
15879
- commandToTitle(command),
15880
- { command, cwd: item.cwd },
15881
- typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : null
15882
- );
15883
- break;
15884
- }
15885
- case "fileChange": {
15886
- const title = "Apply patch";
15887
- emitHistoricalToolCall(
15888
- envelopes,
15889
- turn,
15890
- item,
15891
- "CodexPatch",
15892
- title,
15893
- { changes: item.changes, status: item.status },
15894
- null
15895
- );
15896
- break;
15897
- }
15898
- case "mcpToolCall": {
15899
- const title = `${item.server}.${item.tool}`;
15900
- const output = item.error !== void 0 && item.error !== null ? String(item.error) : item.result !== void 0 && item.result !== null ? String(item.result) : null;
15901
- emitHistoricalToolCall(
15902
- envelopes,
15903
- turn,
15904
- item,
15905
- "McpTool",
15906
- title,
15907
- {
15908
- server: item.server,
15909
- tool: item.tool,
15910
- arguments: item.arguments
15911
- },
15912
- output
15913
- );
15914
- break;
15915
- }
15916
- }
16135
+ /**
16136
+ * Finish current tool call if one is in progress.
16137
+ */
16138
+ finishCurrentToolCall(status) {
16139
+ if (this.toolCallStarted && this.currentCallId) {
16140
+ const toolResult = {
16141
+ type: "tool-call-result",
16142
+ callId: this.currentCallId,
16143
+ output: {
16144
+ content: this.contentBuffer || "",
16145
+ status
16146
+ },
16147
+ id: node_crypto.randomUUID()
16148
+ };
16149
+ persistence.logger.debug(`${this.getLogPrefix()} Sending tool call result with status: ${status}`);
16150
+ this.onMessage?.(toolResult);
15917
16151
  }
15918
- envelopes.push(api.createEnvelope("agent", { t: "turn-end", status: turnStatus(turn) }, {
15919
- id: `${turn.id}:end`,
15920
- turn: turn.id,
15921
- time: completedAt
15922
- }));
15923
16152
  }
15924
- return envelopes;
15925
- }
15926
- function patchDescription(changes) {
15927
- if (!changes || typeof changes !== "object") {
15928
- return "Applying patch";
16153
+ /**
16154
+ * Reset internal state.
16155
+ */
16156
+ resetState() {
16157
+ this.accumulator = "";
16158
+ this.inTitleCapture = false;
16159
+ this.titleBuffer = "";
16160
+ this.contentBuffer = "";
16161
+ this.hasTitle = false;
16162
+ this.currentCallId = null;
16163
+ this.toolCallStarted = false;
16164
+ this.currentTitle = null;
15929
16165
  }
15930
- const fileCount = Object.keys(changes).length;
15931
- if (fileCount === 1) {
15932
- return "Applying patch to 1 file";
16166
+ /**
16167
+ * Get the current call ID for tool result matching.
16168
+ */
16169
+ getCurrentCallId() {
16170
+ return this.currentCallId;
16171
+ }
16172
+ /**
16173
+ * Check if a tool call has been started.
16174
+ */
16175
+ hasStartedToolCall() {
16176
+ return this.toolCallStarted;
15933
16177
  }
15934
- return `Applying patch to ${fileCount} files`;
15935
16178
  }
15936
- function pickTurnEndStatus(message, type) {
15937
- const rawStatus = message.status;
15938
- if (rawStatus === "completed" || rawStatus === "failed" || rawStatus === "cancelled") {
15939
- return rawStatus;
16179
+
16180
+ class ReasoningProcessor extends BaseReasoningProcessor {
16181
+ getToolName() {
16182
+ return "CodexReasoning";
15940
16183
  }
15941
- if (rawStatus === "canceled") {
15942
- return "cancelled";
16184
+ getLogPrefix() {
16185
+ return "[ReasoningProcessor]";
15943
16186
  }
15944
- if (type === "turn_aborted") {
15945
- const reason = message.reason;
15946
- const error = message.error;
15947
- if (typeof reason === "string" && /(fail|error)/i.test(reason) || typeof error === "string" && error.length > 0 || error !== void 0 && error !== null && typeof error === "object") {
15948
- return "failed";
15949
- }
15950
- return "cancelled";
16187
+ /**
16188
+ * Process a reasoning delta and accumulate content.
16189
+ */
16190
+ processDelta(delta) {
16191
+ this.processInput(delta);
15951
16192
  }
15952
- if (message.error !== void 0 && message.error !== null) {
15953
- return "failed";
16193
+ /**
16194
+ * Complete the reasoning section with final text.
16195
+ */
16196
+ complete(fullText) {
16197
+ this.completeReasoning(fullText);
15954
16198
  }
15955
- return "completed";
15956
16199
  }
15957
- function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15958
- const type = message.type;
15959
- const startedSubagents = getStartedSubagents(state);
15960
- const activeSubagents = getActiveSubagents(state);
15961
- const providerSubagentToSessionSubagent = getProviderSubagentToSessionSubagent(state);
15962
- if (type === "task_started") {
15963
- const turnId = cuid2.createId();
15964
- const turnStart = api.createEnvelope("agent", { t: "turn-start" }, { turn: turnId });
15965
- startedSubagents.clear();
15966
- activeSubagents.clear();
15967
- providerSubagentToSessionSubagent.clear();
15968
- return {
15969
- currentTurnId: turnId,
15970
- startedSubagents,
15971
- activeSubagents,
15972
- providerSubagentToSessionSubagent,
15973
- envelopes: [turnStart]
15974
- };
15975
- }
15976
- if (type === "task_complete" || type === "turn_aborted") {
15977
- if (!state.currentTurnId) {
15978
- return {
15979
- currentTurnId: null,
15980
- startedSubagents,
15981
- activeSubagents,
15982
- providerSubagentToSessionSubagent,
15983
- envelopes: []
15984
- };
15985
- }
15986
- const lifecycleOpts = { turn: state.currentTurnId };
15987
- providerSubagentToSessionSubagent.clear();
15988
- return {
15989
- currentTurnId: null,
15990
- startedSubagents,
15991
- activeSubagents,
15992
- providerSubagentToSessionSubagent,
15993
- envelopes: [
15994
- ...emitSubagentStops(lifecycleOpts, startedSubagents, activeSubagents),
15995
- api.createEnvelope("agent", {
15996
- t: "turn-end",
15997
- status: pickTurnEndStatus(message, type)
15998
- }, lifecycleOpts)
15999
- ]
16000
- };
16001
- }
16002
- if (type === "token_count") {
16003
- return {
16004
- currentTurnId: state.currentTurnId,
16005
- startedSubagents,
16006
- activeSubagents,
16007
- providerSubagentToSessionSubagent,
16008
- envelopes: []
16009
- };
16200
+
16201
+ class DiffProcessor {
16202
+ previousDiff = null;
16203
+ onMessage = null;
16204
+ constructor(onMessage) {
16205
+ this.onMessage = onMessage || null;
16010
16206
  }
16011
- const subagent = resolveSessionSubagent(message, providerSubagentToSessionSubagent);
16012
- const opts = buildEnvelopeOptions(state.currentTurnId, subagent);
16013
- if (type === "agent_message") {
16014
- if (typeof message.message !== "string") {
16015
- return {
16016
- currentTurnId: state.currentTurnId,
16017
- startedSubagents,
16018
- activeSubagents,
16019
- providerSubagentToSessionSubagent,
16020
- envelopes: []
16207
+ /**
16208
+ * Process a turn_diff message and check if the unified_diff has changed
16209
+ */
16210
+ processDiff(unifiedDiff) {
16211
+ if (this.previousDiff !== unifiedDiff) {
16212
+ persistence.logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
16213
+ const callId = node_crypto.randomUUID();
16214
+ const toolCall = {
16215
+ type: "tool-call",
16216
+ name: "CodexDiff",
16217
+ callId,
16218
+ input: {
16219
+ unified_diff: unifiedDiff
16220
+ },
16221
+ id: node_crypto.randomUUID()
16021
16222
  };
16022
- }
16023
- const envelopes = [];
16024
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16025
- envelopes.push(api.createEnvelope("agent", { t: "text", text: message.message }, opts));
16026
- return {
16027
- currentTurnId: state.currentTurnId,
16028
- startedSubagents,
16029
- activeSubagents,
16030
- providerSubagentToSessionSubagent,
16031
- envelopes
16032
- };
16033
- }
16034
- if (type === "agent_reasoning" || type === "agent_reasoning_delta") {
16035
- const text = typeof message.text === "string" ? message.text : typeof message.delta === "string" ? message.delta : null;
16036
- if (!text) {
16037
- return {
16038
- currentTurnId: state.currentTurnId,
16039
- startedSubagents,
16040
- activeSubagents,
16041
- providerSubagentToSessionSubagent,
16042
- envelopes: []
16223
+ this.onMessage?.(toolCall);
16224
+ const toolResult = {
16225
+ type: "tool-call-result",
16226
+ callId,
16227
+ output: {
16228
+ status: "completed"
16229
+ },
16230
+ id: node_crypto.randomUUID()
16043
16231
  };
16232
+ this.onMessage?.(toolResult);
16044
16233
  }
16045
- const envelopes = [];
16046
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16047
- envelopes.push(api.createEnvelope("agent", { t: "text", text, thinking: true }, opts));
16048
- return {
16049
- currentTurnId: state.currentTurnId,
16050
- startedSubagents,
16051
- activeSubagents,
16052
- providerSubagentToSessionSubagent,
16053
- envelopes
16054
- };
16234
+ this.previousDiff = unifiedDiff;
16235
+ persistence.logger.debug("[DiffProcessor] Updated stored diff");
16055
16236
  }
16056
- if (type === "exec_command_begin") {
16057
- const call = pickCallId(message);
16058
- const { call_id: _callIdSnake, callId: _callIdCamel, type: _type, ...args } = message;
16059
- const command = summarizeCommand(args.command);
16060
- const description = typeof args.description === "string" ? args.description : command ?? "Execute command";
16061
- const envelopes = [];
16062
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16063
- envelopes.push(
16064
- api.createEnvelope("agent", {
16065
- t: "tool-call-start",
16066
- call,
16067
- name: "CodexBash",
16068
- title: commandToTitle(command),
16069
- description,
16070
- args
16071
- }, opts)
16072
- );
16073
- return {
16074
- currentTurnId: state.currentTurnId,
16075
- startedSubagents,
16076
- activeSubagents,
16077
- providerSubagentToSessionSubagent,
16078
- envelopes
16079
- };
16237
+ /**
16238
+ * Reset the processor state (called on task_complete or turn_aborted)
16239
+ */
16240
+ reset() {
16241
+ persistence.logger.debug("[DiffProcessor] Resetting diff state");
16242
+ this.previousDiff = null;
16080
16243
  }
16081
- if (type === "exec_command_end") {
16082
- const call = pickCallId(message);
16083
- const envelopes = [];
16084
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16085
- envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16086
- return {
16087
- currentTurnId: state.currentTurnId,
16088
- startedSubagents,
16089
- activeSubagents,
16090
- providerSubagentToSessionSubagent,
16091
- envelopes
16092
- };
16244
+ /**
16245
+ * Set the message callback for sending messages directly
16246
+ */
16247
+ setMessageCallback(callback) {
16248
+ this.onMessage = callback;
16093
16249
  }
16094
- if (type === "patch_apply_begin") {
16095
- const call = pickCallId(message);
16096
- const autoApproved = message.auto_approved;
16097
- const changes = message.changes;
16098
- const envelopes = [];
16099
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16100
- envelopes.push(
16101
- api.createEnvelope("agent", {
16102
- t: "tool-call-start",
16103
- call,
16104
- name: "CodexPatch",
16105
- title: "Apply patch",
16106
- description: patchDescription(changes),
16107
- args: {
16108
- auto_approved: autoApproved,
16109
- changes
16110
- }
16111
- }, opts)
16112
- );
16113
- return {
16114
- currentTurnId: state.currentTurnId,
16115
- startedSubagents,
16116
- activeSubagents,
16117
- providerSubagentToSessionSubagent,
16118
- envelopes
16119
- };
16250
+ /**
16251
+ * Get the current diff value
16252
+ */
16253
+ getCurrentDiff() {
16254
+ return this.previousDiff;
16120
16255
  }
16121
- if (type === "patch_apply_end") {
16122
- const call = pickCallId(message);
16123
- const envelopes = [];
16124
- maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16125
- envelopes.push(api.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16126
- return {
16127
- currentTurnId: state.currentTurnId,
16128
- startedSubagents,
16129
- activeSubagents,
16130
- providerSubagentToSessionSubagent,
16131
- envelopes
16256
+ }
16257
+
16258
+ const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
16259
+ const [messages, setMessages] = React.useState([]);
16260
+ const [confirmationMode, setConfirmationMode] = React.useState(false);
16261
+ const [actionInProgress, setActionInProgress] = React.useState(false);
16262
+ const confirmationTimeoutRef = React.useRef(null);
16263
+ const { stdout } = ink.useStdout();
16264
+ const terminalWidth = stdout.columns || 80;
16265
+ const terminalHeight = stdout.rows || 24;
16266
+ React.useEffect(() => {
16267
+ setMessages(messageBuffer.getMessages());
16268
+ const unsubscribe = messageBuffer.onUpdate((newMessages) => {
16269
+ setMessages(newMessages);
16270
+ });
16271
+ return () => {
16272
+ unsubscribe();
16273
+ if (confirmationTimeoutRef.current) {
16274
+ clearTimeout(confirmationTimeoutRef.current);
16275
+ }
16132
16276
  };
16133
- }
16277
+ }, [messageBuffer]);
16278
+ const resetConfirmation = React.useCallback(() => {
16279
+ setConfirmationMode(false);
16280
+ if (confirmationTimeoutRef.current) {
16281
+ clearTimeout(confirmationTimeoutRef.current);
16282
+ confirmationTimeoutRef.current = null;
16283
+ }
16284
+ }, []);
16285
+ const setConfirmationWithTimeout = React.useCallback(() => {
16286
+ setConfirmationMode(true);
16287
+ if (confirmationTimeoutRef.current) {
16288
+ clearTimeout(confirmationTimeoutRef.current);
16289
+ }
16290
+ confirmationTimeoutRef.current = setTimeout(() => {
16291
+ resetConfirmation();
16292
+ }, 15e3);
16293
+ }, [resetConfirmation]);
16294
+ ink.useInput(React.useCallback(async (input, key) => {
16295
+ if (actionInProgress) return;
16296
+ if (key.ctrl && input === "c") {
16297
+ if (confirmationMode) {
16298
+ resetConfirmation();
16299
+ setActionInProgress(true);
16300
+ await new Promise((resolve) => setTimeout(resolve, 100));
16301
+ onExit?.();
16302
+ } else {
16303
+ setConfirmationWithTimeout();
16304
+ }
16305
+ return;
16306
+ }
16307
+ if (confirmationMode) {
16308
+ resetConfirmation();
16309
+ }
16310
+ }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
16311
+ const getMessageColor = (type) => {
16312
+ switch (type) {
16313
+ case "user":
16314
+ return "magenta";
16315
+ case "assistant":
16316
+ return "cyan";
16317
+ case "system":
16318
+ return "blue";
16319
+ case "tool":
16320
+ return "yellow";
16321
+ case "result":
16322
+ return "green";
16323
+ case "status":
16324
+ return "gray";
16325
+ default:
16326
+ return "white";
16327
+ }
16328
+ };
16329
+ const formatMessage = (msg) => {
16330
+ const lines = msg.content.split("\n");
16331
+ const maxLineLength = terminalWidth - 10;
16332
+ return lines.map((line) => {
16333
+ if (line.length <= maxLineLength) return line;
16334
+ const chunks = [];
16335
+ for (let i = 0; i < line.length; i += maxLineLength) {
16336
+ chunks.push(line.slice(i, i + maxLineLength));
16337
+ }
16338
+ return chunks.join("\n");
16339
+ }).join("\n");
16340
+ };
16341
+ return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
16342
+ ink.Box,
16343
+ {
16344
+ flexDirection: "column",
16345
+ width: terminalWidth,
16346
+ height: terminalHeight - 4,
16347
+ borderStyle: "round",
16348
+ borderColor: "gray",
16349
+ paddingX: 1,
16350
+ overflow: "hidden"
16351
+ },
16352
+ /* @__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)))),
16353
+ /* @__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...") : (
16354
+ // Show only the last messages that fit in the available space
16355
+ 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))))
16356
+ ))
16357
+ ), /* @__PURE__ */ React.createElement(
16358
+ ink.Box,
16359
+ {
16360
+ width: terminalWidth,
16361
+ borderStyle: "round",
16362
+ borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "green",
16363
+ paddingX: 2,
16364
+ justifyContent: "center",
16365
+ alignItems: "center",
16366
+ flexDirection: "column"
16367
+ },
16368
+ /* @__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))
16369
+ ));
16370
+ };
16371
+
16372
+ function createOfflineSessionStub(sessionTag) {
16134
16373
  return {
16135
- currentTurnId: state.currentTurnId,
16136
- startedSubagents,
16137
- activeSubagents,
16138
- providerSubagentToSessionSubagent,
16139
- envelopes: []
16374
+ sessionId: `offline-${sessionTag}`,
16375
+ sendCodexMessage: () => {
16376
+ },
16377
+ sendAgentMessage: () => {
16378
+ },
16379
+ sendAgentUsageSnapshot: () => false,
16380
+ sendClaudeSessionMessage: () => {
16381
+ },
16382
+ keepAlive: () => {
16383
+ },
16384
+ sendSessionEvent: () => {
16385
+ },
16386
+ sendSessionDeath: () => {
16387
+ },
16388
+ updateLifecycleState: () => {
16389
+ },
16390
+ requestControlTransfer: async () => {
16391
+ },
16392
+ flush: async () => {
16393
+ },
16394
+ close: async () => {
16395
+ },
16396
+ updateMetadata: () => {
16397
+ },
16398
+ updateAgentState: () => {
16399
+ },
16400
+ onUserMessage: () => {
16401
+ },
16402
+ onFileEvent: () => {
16403
+ },
16404
+ trackAttachmentDownload: () => {
16405
+ },
16406
+ drainAttachmentsForUserMessage: async () => [],
16407
+ rpcHandlerManager: {
16408
+ registerHandler: () => {
16409
+ }
16410
+ }
16140
16411
  };
16141
16412
  }
16142
- function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
16143
- const toolLikeMessage = message;
16144
- const opts = buildEnvelopeOptions(state.currentTurnId);
16145
- if (message.type === "reasoning") {
16146
- return [api.createEnvelope("agent", {
16147
- t: "text",
16148
- text: message.message,
16149
- thinking: true
16150
- }, opts)];
16413
+
16414
+ function setupOfflineReconnection(opts) {
16415
+ const { api: api$1, sessionTag, metadata, state, response, onSessionSwap } = opts;
16416
+ let session;
16417
+ let reconnectionHandle = null;
16418
+ if (!response) {
16419
+ session = createOfflineSessionStub(sessionTag);
16420
+ reconnectionHandle = api.startOfflineReconnection({
16421
+ serverUrl: persistence.configuration.serverUrl,
16422
+ onReconnected: async () => {
16423
+ const resp = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
16424
+ if (!resp) throw new Error("Server unavailable");
16425
+ const realSession = api$1.sessionSyncClient(resp);
16426
+ onSessionSwap(realSession);
16427
+ return realSession;
16428
+ },
16429
+ onNotify: (msg) => {
16430
+ console.log(msg);
16431
+ }
16432
+ });
16433
+ return { session, reconnectionHandle, isOffline: true };
16434
+ } else {
16435
+ session = api$1.sessionSyncClient(response, opts.sessionClientOptions);
16436
+ return { session, reconnectionHandle: null, isOffline: false };
16151
16437
  }
16152
- if (message.type === "tool-call") {
16153
- const title = typeof toolLikeMessage.input?.title === "string" ? toolLikeMessage.input.title : `${toolLikeMessage.name || "Tool"} call`;
16154
- return [api.createEnvelope("agent", {
16155
- t: "tool-call-start",
16156
- call: toolLikeMessage.callId,
16157
- name: toolLikeMessage.name || "unknown",
16158
- title,
16159
- description: title,
16160
- args: toolLikeMessage.input && typeof toolLikeMessage.input === "object" ? toolLikeMessage.input : {}
16161
- }, opts)];
16438
+ }
16439
+
16440
+ function resolveCodexExecutionPolicy(permissionMode, sandboxManagedByHappy) {
16441
+ if (sandboxManagedByHappy) {
16442
+ return {
16443
+ approvalPolicy: "never",
16444
+ sandbox: "danger-full-access"
16445
+ };
16162
16446
  }
16163
- if (message.type === "tool-call-result") {
16164
- const envelopes = [];
16165
- const content = toolLikeMessage.output?.content;
16166
- if (typeof content === "string" && content.trim().length > 0) {
16167
- envelopes.push(api.createEnvelope("agent", {
16168
- t: "text",
16169
- text: content,
16170
- thinking: true
16171
- }, opts));
16447
+ const approvalPolicy = (() => {
16448
+ switch (permissionMode) {
16449
+ // Codex native modes
16450
+ case "default":
16451
+ return "untrusted";
16452
+ // Ask for non-trusted commands
16453
+ case "read-only":
16454
+ return "never";
16455
+ // Never ask, read-only enforced by sandbox
16456
+ case "safe-yolo":
16457
+ return "on-request";
16458
+ // Workspace auto-run; request approval for escalation (0.153 removed on-failure)
16459
+ case "yolo":
16460
+ return "never";
16461
+ // Full YOLO: never interrupt for approvals
16462
+ // Defensive fallback for Claude-specific modes (backward compatibility)
16463
+ case "bypassPermissions":
16464
+ return "never";
16465
+ // Full access: map to yolo behavior
16466
+ case "acceptEdits":
16467
+ return "on-request";
16468
+ // Let model decide (closest to auto-approve edits)
16469
+ case "plan":
16470
+ return "untrusted";
16471
+ // Conservative: ask for non-trusted
16472
+ default:
16473
+ return "untrusted";
16172
16474
  }
16173
- envelopes.push(api.createEnvelope("agent", {
16174
- t: "tool-call-end",
16175
- call: toolLikeMessage.callId
16176
- }, opts));
16177
- return envelopes;
16178
- }
16179
- return [];
16475
+ })();
16476
+ const sandbox = (() => {
16477
+ switch (permissionMode) {
16478
+ // Codex native modes
16479
+ case "default":
16480
+ return "workspace-write";
16481
+ // Can write in workspace
16482
+ case "read-only":
16483
+ return "read-only";
16484
+ // Read-only filesystem
16485
+ case "safe-yolo":
16486
+ return "workspace-write";
16487
+ // Can write in workspace
16488
+ case "yolo":
16489
+ return "danger-full-access";
16490
+ // Full system access
16491
+ // Defensive fallback for Claude-specific modes
16492
+ case "bypassPermissions":
16493
+ return "danger-full-access";
16494
+ // Full access: map to yolo
16495
+ case "acceptEdits":
16496
+ return "workspace-write";
16497
+ // Can edit files in workspace
16498
+ case "plan":
16499
+ return "workspace-write";
16500
+ // Can write for planning
16501
+ default:
16502
+ return "workspace-write";
16503
+ }
16504
+ })();
16505
+ return { approvalPolicy, sandbox };
16180
16506
  }
16181
16507
 
16182
16508
  async function resumeExistingThread(opts) {
@@ -16648,8 +16974,15 @@ async function runCodex(opts) {
16648
16974
  process.exit(1);
16649
16975
  }
16650
16976
  };
16977
+ session.rpcHandlerManager.registerHandler("codex-child-read", async (args) => {
16978
+ try {
16979
+ return await readChildThread(client, args?.threadId);
16980
+ } catch (error) {
16981
+ return { error: error instanceof Error ? error.message : "Child read failed" };
16982
+ }
16983
+ });
16651
16984
  session.rpcHandlerManager.registerHandler("abort", handleAbort);
16652
- registerKillSessionHandler(session.rpcHandlerManager, handleKillSession, session);
16985
+ const disposeTermination = registerKillSessionHandler(session.rpcHandlerManager, handleKillSession, session, { processSignals: true });
16653
16986
  const messageBuffer = new MessageBuffer();
16654
16987
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
16655
16988
  let inkInstance = null;
@@ -16985,6 +17318,7 @@ async function runCodex(opts) {
16985
17318
  }
16986
17319
  }
16987
17320
  } finally {
17321
+ disposeTermination?.();
16988
17322
  persistence.logger.debug("[codex]: Final cleanup start");
16989
17323
  logActiveHandles("cleanup-start");
16990
17324
  if (reconnectionHandle) {
@@ -17187,7 +17521,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17187
17521
  process.exit(0);
17188
17522
  } else if (subcommand === "install-terminal-hooks") {
17189
17523
  try {
17190
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-CKG0MYVK.cjs'); });
17524
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-gmWKACNb.cjs'); });
17191
17525
  const command = parseTerminalHooksArgs(args.slice(1));
17192
17526
  if (command.action === "help") {
17193
17527
  console.log(TERMINAL_HOOKS_HELP);
@@ -17204,7 +17538,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17204
17538
  }
17205
17539
  } else if (subcommand === "spawn") {
17206
17540
  try {
17207
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-C-XpSPLX.cjs'); });
17541
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-DVJtNwWN.cjs'); });
17208
17542
  await handleSpawnCommand(args.slice(1));
17209
17543
  } catch (error) {
17210
17544
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17216,7 +17550,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17216
17550
  return;
17217
17551
  } else if (subcommand === "sessions") {
17218
17552
  try {
17219
- const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-SIFOOlGp.cjs'); });
17553
+ const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-Cx_3cvN6.cjs'); });
17220
17554
  await handleSessionsCommand(args.slice(1));
17221
17555
  } catch (error) {
17222
17556
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17228,7 +17562,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17228
17562
  return;
17229
17563
  } else if (subcommand === "send") {
17230
17564
  try {
17231
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-DKbumEcg.cjs'); });
17565
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-WlJ4r3U2.cjs'); });
17232
17566
  await handleSendCommand(args.slice(1));
17233
17567
  } catch (error) {
17234
17568
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17334,9 +17668,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17334
17668
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
17335
17669
  const projectId = args[3];
17336
17670
  try {
17337
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-uLywzv5t.cjs'); });
17338
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-XRNzIlVm.cjs'); }).then(function (n) { return n.persistence; });
17339
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-CcjVmS2i.cjs'); }).then(function (n) { return n.api; });
17671
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-BO0ZHyoB.cjs'); });
17672
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-K17AgMHD.cjs'); }).then(function (n) { return n.persistence; });
17673
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-DjAHWb4J.cjs'); }).then(function (n) { return n.api; });
17340
17674
  let userEmail = void 0;
17341
17675
  try {
17342
17676
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -17367,7 +17701,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17367
17701
  }
17368
17702
  if (geminiSubcommand === "project" && args[2] === "get") {
17369
17703
  try {
17370
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-uLywzv5t.cjs'); });
17704
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BO0ZHyoB.cjs'); });
17371
17705
  const config = readGeminiLocalConfig();
17372
17706
  if (config.googleCloudProject) {
17373
17707
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -17407,7 +17741,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17407
17741
  process.exit(0);
17408
17742
  }
17409
17743
  try {
17410
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-BIVHp0ST.cjs'); });
17744
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-n0wD2Z9R.cjs'); });
17411
17745
  let startedBy = void 0;
17412
17746
  for (let i = 1; i < args.length; i++) {
17413
17747
  if (args[i] === "--started-by") {
@@ -17437,7 +17771,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17437
17771
  }
17438
17772
  };
17439
17773
  try {
17440
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-Daga0ztv.cjs'); });
17774
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BEpZjXs4.cjs'); });
17441
17775
  const parsed = parsePiRunnerArgs(args.slice(1));
17442
17776
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
17443
17777
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -17465,7 +17799,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17465
17799
  return;
17466
17800
  } else if (subcommand === "acp") {
17467
17801
  try {
17468
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-Daga0ztv.cjs'); });
17802
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BEpZjXs4.cjs'); });
17469
17803
  let startedBy = void 0;
17470
17804
  let verbose = false;
17471
17805
  const acpArgs = [];
@@ -17505,7 +17839,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17505
17839
  return;
17506
17840
  } else if (subcommand === "openclaw") {
17507
17841
  try {
17508
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-a0f2pZZp.cjs'); });
17842
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-fYx1XweW.cjs'); });
17509
17843
  let startedBy = void 0;
17510
17844
  let verbose = false;
17511
17845
  let gatewayUrl;
@@ -17556,7 +17890,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17556
17890
  return;
17557
17891
  } else if (subcommand === "todo") {
17558
17892
  try {
17559
- const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-o0clp-_I.cjs'); });
17893
+ const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-DHY20tpD.cjs'); });
17560
17894
  await handleTodoCommand(args.slice(1));
17561
17895
  } catch (error) {
17562
17896
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -17565,7 +17899,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17565
17899
  return;
17566
17900
  } else if (subcommand === "teams") {
17567
17901
  try {
17568
- const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-ChFzXAqr.cjs'); });
17902
+ const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-BbYi0fWG.cjs'); });
17569
17903
  await handleTeamsCommand(args.slice(1));
17570
17904
  } catch (error) {
17571
17905
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -17574,7 +17908,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17574
17908
  return;
17575
17909
  } else if (subcommand === "mcp" && args.length === 1) {
17576
17910
  try {
17577
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-CAMCuudA.cjs'); });
17911
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-CK1NCZm0.cjs'); });
17578
17912
  await handleMcpCommand();
17579
17913
  } catch (error) {
17580
17914
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}