cogpit 3.5.1 → 3.5.3

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 (90) hide show
  1. package/dist/cli-runtime.js +39279 -35071
  2. package/dist/instance-worker.js +1206 -154
  3. package/dist/web/assets/AutomationsPanel-D7XmgTgZ.js +1 -0
  4. package/dist/web/assets/{CogpitBlock-ZgOubpsS.js → CogpitBlock-CiS2M1Q_.js} +16 -16
  5. package/dist/web/assets/CommandPaletteHost-Dj06VjlP.js +1 -0
  6. package/dist/web/assets/ConfigBrowser-DqQo1F4o.js +1 -0
  7. package/dist/web/assets/CrewPanel-DzLPmS6o.js +1 -0
  8. package/dist/web/assets/HighlightedEditor-DPrSCx1c.js +2 -0
  9. package/dist/web/assets/KeyboardShortcutsDialog-BdXPWBJw.js +1 -0
  10. package/dist/web/assets/MissionHome-CTcx5vR7.js +2 -0
  11. package/dist/web/assets/MobileFileChanges-Ngv9AlhR.js +1 -0
  12. package/dist/web/assets/PluginsDialog-CWRSNNll.js +1 -0
  13. package/dist/web/assets/PreviewPanel-CQcJIdef.js +1 -0
  14. package/dist/web/assets/ProjectFilesPanel-D5EKIBD7.js +4 -0
  15. package/dist/web/assets/ProjectSwitcherModal-BoSCOiv9.js +1 -0
  16. package/dist/web/assets/{TerminalOutput-DOX29EmA.js → TerminalOutput-DYZJD2WR.js} +3 -3
  17. package/dist/web/assets/ThemeSelectorModal-8sO11Ob-.js +1 -0
  18. package/dist/web/assets/WorkflowsPanel-qv6TkN7g.js +1 -0
  19. package/dist/web/assets/WorktreePanel-Dr82TWOP.js +1 -0
  20. package/dist/web/assets/{bash-Yzrsuije.js → bash-DgJyDXW6.js} +1 -1
  21. package/dist/web/assets/{c-BIGW1oBm.js → c-BG-tZi7o.js} +1 -1
  22. package/dist/web/assets/cpp-BzPMphJ4.js +1 -0
  23. package/dist/web/assets/{css-DPfMkruS.js → css-PZBBrkI0.js} +1 -1
  24. package/dist/web/assets/edition-ui-DQHX5fm4.js +2 -0
  25. package/dist/web/assets/github-dark-BQV5ugrc.js +1 -0
  26. package/dist/web/assets/github-light-Rcb2q3L5.js +1 -0
  27. package/dist/web/assets/{go-CxLEBnE3.js → go-BBr2UQCn.js} +1 -1
  28. package/dist/web/assets/{html-GMplVEZG.js → html-DOf0XnR2.js} +1 -1
  29. package/dist/web/assets/index-BGSPI7dK.js +1 -0
  30. package/dist/web/assets/index-BVmNox_3.js +504 -0
  31. package/dist/web/assets/index-CHtH7TlZ.js +2 -0
  32. package/dist/web/assets/index-DKDki7Rz.css +1 -0
  33. package/dist/web/assets/index-DU4gWgPu.js +3 -0
  34. package/dist/web/assets/{index-By5-_P50.js → index-zhwBlO_8.js} +4 -4
  35. package/dist/web/assets/{java-CylS5w8V.js → java-BTDmaRNO.js} +1 -1
  36. package/dist/web/assets/{javascript-wDzz0qaB.js → javascript-WyGyM1wV.js} +1 -1
  37. package/dist/web/assets/{json-Cp-IABpG.js → json-B8JryHcM.js} +1 -1
  38. package/dist/web/assets/{jsx-g9-lgVsj.js → jsx-8cLv2Six.js} +1 -1
  39. package/dist/web/assets/{kotlin-BdnUsdx6.js → kotlin-BQy537ds.js} +1 -1
  40. package/dist/web/assets/{markdown-Cvjx9yec.js → markdown-DX-TNHnc.js} +1 -1
  41. package/dist/web/assets/{php-aq0itWAE.js → php-47JHu45N.js} +1 -1
  42. package/dist/web/assets/{python-B6aJPvgy.js → python-B6Ihcp-E.js} +1 -1
  43. package/dist/web/assets/render.worker-DW0Lo4fc.js +154 -0
  44. package/dist/web/assets/ruby-BrGyNOch.js +1 -0
  45. package/dist/web/assets/{rust-B1yitclQ.js → rust-CxuBE0J8.js} +1 -1
  46. package/dist/web/assets/{scss-OYdSNvt2.js → scss-DxBkwadj.js} +1 -1
  47. package/dist/web/assets/session-parser.worker-DCsmYUeX.js +41 -0
  48. package/dist/web/assets/{sql-BLtJtn59.js → sql-BbONfI8K.js} +1 -1
  49. package/dist/web/assets/svelte-H3swLp47.js +1 -0
  50. package/dist/web/assets/{swift-D82vCrfD.js → swift-tEesUtni.js} +1 -1
  51. package/dist/web/assets/switch-gTnbAJaZ.js +1 -0
  52. package/dist/web/assets/{toml-vGWfd6FD.js → toml-Ms459DBw.js} +1 -1
  53. package/dist/web/assets/{tsx-COt5Ahok.js → tsx-D6116Ycy.js} +1 -1
  54. package/dist/web/assets/{typescript-BPQ3VLAy.js → typescript-BqvgUolh.js} +1 -1
  55. package/dist/web/assets/vendor-markdown-BEJ8yVzA.js +29 -0
  56. package/dist/web/assets/{vendor-ui-DQeMoSiV.js → vendor-ui-CKjyvB3_.js} +226 -191
  57. package/dist/web/assets/vue-CWSgrUPf.js +1 -0
  58. package/dist/web/assets/{xml-D2mA2LmW.js → xml-CdYS3kUe.js} +1 -1
  59. package/dist/web/assets/{yaml-Buea-lGh.js → yaml-Bopit2L3.js} +1 -1
  60. package/dist/web/index.html +4 -5
  61. package/package.json +1 -1
  62. package/dist/web/assets/CommandPaletteHost-Djn2jqYR.js +0 -1
  63. package/dist/web/assets/ConfigBrowser-CRwh8BXq.js +0 -1
  64. package/dist/web/assets/CrewPanel-BxPl5D7h.js +0 -1
  65. package/dist/web/assets/HighlightedEditor-D9A-dW2Z.js +0 -2
  66. package/dist/web/assets/KeyboardShortcutsDialog-L7rRhApI.js +0 -1
  67. package/dist/web/assets/MissionHome-zp7VebSO.js +0 -2
  68. package/dist/web/assets/MobileFileChanges-O-SvMq9F.js +0 -1
  69. package/dist/web/assets/PluginsDialog-CBOyfoUX.js +0 -1
  70. package/dist/web/assets/PreviewPanel-_pKuUaps.js +0 -1
  71. package/dist/web/assets/ProjectFilesPanel-BsbpTgWK.js +0 -4
  72. package/dist/web/assets/ProjectSwitcherModal-D4MzdPVH.js +0 -1
  73. package/dist/web/assets/ThemeSelectorModal-7CWXZB-z.js +0 -1
  74. package/dist/web/assets/WorkflowsPanel-aOz1o3kA.js +0 -1
  75. package/dist/web/assets/WorktreePanel-BFve9bpC.js +0 -1
  76. package/dist/web/assets/cpp-BRuaLJcg.js +0 -1
  77. package/dist/web/assets/edition-ui-CFxEM3WF.js +0 -2
  78. package/dist/web/assets/github-dark-DHJKELXO.js +0 -1
  79. package/dist/web/assets/github-light-DAi9KRSo.js +0 -1
  80. package/dist/web/assets/index-CPTFl4Qh.js +0 -498
  81. package/dist/web/assets/index-DjpYbGVZ.js +0 -2
  82. package/dist/web/assets/index-DmMg-zf3.js +0 -1
  83. package/dist/web/assets/index-hGMtuUNI.css +0 -1
  84. package/dist/web/assets/ruby-B0Tyh1qT.js +0 -1
  85. package/dist/web/assets/session-parser.worker-3tFlB6dy.js +0 -31
  86. package/dist/web/assets/svelte-DZMnMd3x.js +0 -1
  87. package/dist/web/assets/switch-DS7UyNS2.js +0 -1
  88. package/dist/web/assets/vendor-markdown-BE1gYnO9.js +0 -37
  89. package/dist/web/assets/vendor-shiki-DOn6sF-i.js +0 -144
  90. package/dist/web/assets/vue-DVfSmUF7.js +0 -1
@@ -115,14 +115,18 @@ var claude = {
115
115
  // registry checks the prefixed agents first, so this is only ever reached
116
116
  // as the terminal arm.
117
117
  owns: (dirName) => typeof dirName === "string" && !dirName.startsWith(CODEX_DIR_PREFIX) && !dirName.startsWith(COPILOT_DIR_PREFIX),
118
- // An absolute path starts with `/` or `\` (`-`) or a drive (`C:\` → `C--`); a drive root alone is `C-`.
118
+ // An absolute path starts with `/` or `\` (`-`) or a drive (`C:\` → `C--`); a bare drive (`C:`) is `C-`.
119
119
  recognizes: (dirName) => /^(?:(?:-|[A-Za-z]--)[A-Za-z0-9-]*|[A-Za-z]-)$/.test(dirName),
120
120
  /**
121
121
  * Claude Code's own convention: every character outside `[A-Za-z0-9]`
122
122
  * becomes `-`, so `/Users/x/proj` and `C:\Users\x\proj` both collapse into a
123
123
  * flat name under `~/.claude/projects`.
124
124
  */
125
- encode: (cwd) => (cwd.replace(/[\\/]+$/, "") || cwd).replace(/[^a-zA-Z0-9]/g, "-"),
125
+ encode: (cwd) => {
126
+ const trimmed = cwd.replace(/[\\/]+$/, "");
127
+ const isDriveRoot = trimmed !== cwd && /^[A-Za-z]:$/.test(trimmed);
128
+ return (isDriveRoot ? cwd.slice(0, trimmed.length + 1) : trimmed || cwd).replace(/[^a-zA-Z0-9]/g, "-");
129
+ },
126
130
  /**
127
131
  * Best-effort inverse. The encoding is lossy — a literal `-` is
128
132
  * indistinguishable from a separator — so a caller that can read the
@@ -953,6 +957,65 @@ function refreshDirs() {
953
957
  return true;
954
958
  }
955
959
 
960
+ // ../../shared/objects.ts
961
+ function isRecord(value) {
962
+ return typeof value === "object" && value !== null && !Array.isArray(value);
963
+ }
964
+ function asRecord(value) {
965
+ return isRecord(value) ? value : null;
966
+ }
967
+
968
+ // ../../server/agents/runtimeTypes.ts
969
+ function resolvedApproval({ requestId, toolUseId, toolName }) {
970
+ return { requestId, toolUseId, toolName };
971
+ }
972
+ var AgentRuntimeError = class extends Error {
973
+ constructor(status, code, message, details) {
974
+ super(message);
975
+ this.status = status;
976
+ this.code = code;
977
+ this.details = details;
978
+ this.name = "AgentRuntimeError";
979
+ }
980
+ };
981
+ var NO_ACTIVITY = Object.freeze({ live: false, running: false, agentsRunning: false });
982
+ var startingSessions = /* @__PURE__ */ new Set();
983
+ function isSessionStarting(sessionId) {
984
+ return startingSessions.has(sessionId);
985
+ }
986
+ function finishSessionStartup(sessionId) {
987
+ startingSessions.delete(sessionId);
988
+ }
989
+ function reportSessionId(req, sessionId) {
990
+ if (req.beforeFirstTurn) startingSessions.add(sessionId);
991
+ try {
992
+ req.onSessionId?.(sessionId);
993
+ } catch (error2) {
994
+ console.error(`[agents] onSessionId failed for session ${sessionId}:`, error2);
995
+ }
996
+ }
997
+ function selectAvailableDecision(available, requested) {
998
+ if (available.includes(requested)) return requested;
999
+ if (requested === "allow_always" && available.includes("allow")) return "allow";
1000
+ return null;
1001
+ }
1002
+
1003
+ // ../../server/agents/startup.ts
1004
+ var failures = /* @__PURE__ */ new WeakSet();
1005
+ var isStartupFailure = (error2) => typeof error2 === "object" && error2 !== null && failures.has(error2);
1006
+ async function beforeFirstTurn(request, identity) {
1007
+ try {
1008
+ request.signal?.throwIfAborted();
1009
+ await request.beforeFirstTurn?.(identity);
1010
+ request.signal?.throwIfAborted();
1011
+ if (identity.sessionId) finishSessionStartup(identity.sessionId);
1012
+ } catch (error2) {
1013
+ if (identity.sessionId) finishSessionStartup(identity.sessionId);
1014
+ if (typeof error2 === "object" && error2 !== null) failures.add(error2);
1015
+ throw error2;
1016
+ }
1017
+ }
1018
+
956
1019
  // ../../server/agents/acpRuntime.ts
957
1020
  import { spawn } from "node:child_process";
958
1021
  import { appendFileSync, chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -1204,10 +1267,17 @@ function publishCompacting(sessionId, active) {
1204
1267
  emit(state, { type: "compacting", active });
1205
1268
  if (!active) maybeGc(sessionId, state);
1206
1269
  }
1270
+ var rateLimitListeners = /* @__PURE__ */ new Set();
1207
1271
  function publishRateLimit(sessionId, block) {
1208
1272
  const state = block ? getOrCreate(sessionId) : sessions.get(sessionId);
1209
1273
  if (!state || sameBlock(state.rateLimit, block)) return;
1210
1274
  state.rateLimit = block;
1275
+ for (const listener of rateLimitListeners) {
1276
+ try {
1277
+ listener(sessionId, block);
1278
+ } catch {
1279
+ }
1280
+ }
1211
1281
  emit(state, { type: "rate_limit", block });
1212
1282
  if (!block) maybeGc(sessionId, state);
1213
1283
  }
@@ -1294,14 +1364,6 @@ function isWithinDir(parent, child2) {
1294
1364
  // ../../server/agents/claudeMetadata.ts
1295
1365
  import { open as open3 } from "node:fs/promises";
1296
1366
 
1297
- // ../../shared/objects.ts
1298
- function isRecord(value) {
1299
- return typeof value === "object" && value !== null && !Array.isArray(value);
1300
- }
1301
- function asRecord(value) {
1302
- return isRecord(value) ? value : null;
1303
- }
1304
-
1305
1367
  // ../../shared/session/backgroundAgents.ts
1306
1368
  function awaitingAgentsStatus(launches, pendingQueue = 0) {
1307
1369
  if (!launches.some((launch) => launch.endedAt === void 0)) return null;
@@ -2208,6 +2270,29 @@ function buildTurns(messages) {
2208
2270
  return buildTurnsWithStarts(messages).turns;
2209
2271
  }
2210
2272
 
2273
+ // ../../shared/session/resultProjection.ts
2274
+ var ELISION_THRESHOLD_BYTES = 16 * 1024;
2275
+ function isPlainObject(value) {
2276
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2277
+ }
2278
+ function visitField(holder, key, visit, kind2 = "text") {
2279
+ const value = holder[key];
2280
+ if (typeof value !== "string") return false;
2281
+ const next = visit(value, kind2);
2282
+ if (next === value) return false;
2283
+ holder[key] = next;
2284
+ return true;
2285
+ }
2286
+ function visitJoinedText(parts, textOf, separator, withText, visit) {
2287
+ const texts = parts.map(textOf);
2288
+ const first = texts.findIndex((text2) => text2 !== void 0);
2289
+ if (first < 0) return null;
2290
+ const joined = texts.filter((text2) => Boolean(text2)).join(separator);
2291
+ const replacement = visit(joined, "text");
2292
+ if (replacement === joined) return null;
2293
+ return parts.flatMap((part, index) => index === first ? [withText(part, replacement)] : texts[index] === void 0 ? [part] : []);
2294
+ }
2295
+
2211
2296
  // ../../shared/session/claude.ts
2212
2297
  function isClaudeSessionText() {
2213
2298
  return true;
@@ -2332,17 +2417,17 @@ function collectClaudeBackgroundWork(rawMessages) {
2332
2417
  const resolvedAt = /* @__PURE__ */ new Map();
2333
2418
  const queuedIds = /* @__PURE__ */ new Set();
2334
2419
  const deliveredIds = /* @__PURE__ */ new Set();
2335
- function resolve9(id, index) {
2420
+ function resolve10(id, index) {
2336
2421
  if (!resolvedAt.has(id)) resolvedAt.set(id, index);
2337
2422
  }
2338
2423
  function noteNotifications(text2, index, sink) {
2339
2424
  if (!text2.includes(TASK_NOTIFICATION_MARKER)) return;
2340
2425
  for (const match of text2.matchAll(TASK_ID_RE)) {
2341
- resolve9(match[1], index);
2426
+ resolve10(match[1], index);
2342
2427
  sink?.add(match[1]);
2343
2428
  }
2344
2429
  for (const match of text2.matchAll(TOOL_USE_ID_RE)) {
2345
- resolve9(match[1], index);
2430
+ resolve10(match[1], index);
2346
2431
  sink?.add(match[1]);
2347
2432
  }
2348
2433
  }
@@ -2369,7 +2454,7 @@ function collectClaudeBackgroundWork(rawMessages) {
2369
2454
  if (!isObject(block) || block.type !== "tool_use" || block.name !== "TaskStop") continue;
2370
2455
  const input = isObject(block.input) ? block.input : null;
2371
2456
  const taskId = input?.task_id ?? input?.shell_id;
2372
- if (typeof taskId === "string") resolve9(taskId, index);
2457
+ if (typeof taskId === "string") resolve10(taskId, index);
2373
2458
  }
2374
2459
  continue;
2375
2460
  }
@@ -2482,6 +2567,44 @@ function brandClaudeBranch(firstRecord, sessionId, turnIndex) {
2482
2567
  function claudeTurnBoundaries(records) {
2483
2568
  return buildTurnsWithStarts(records).turnStartIndices;
2484
2569
  }
2570
+ var KEPT_TOOL_USE_RESULT_KEYS = /* @__PURE__ */ new Set(["bashEditDiff", "structuredPatch", "prompt", "summary", "description", "status"]);
2571
+ function visitStrings(holder, visit, depth) {
2572
+ let changed = false;
2573
+ for (const key of Object.keys(holder)) {
2574
+ if (KEPT_TOOL_USE_RESULT_KEYS.has(key)) continue;
2575
+ const value = holder[key];
2576
+ if (typeof value === "string") changed = visitField(holder, key, visit) || changed;
2577
+ else if (isPlainObject(value) && depth < 3) changed = visitStrings(value, visit, depth + 1) || changed;
2578
+ }
2579
+ return changed;
2580
+ }
2581
+ function projectClaudeResults(record3, visit) {
2582
+ if (record3.type !== "user") return false;
2583
+ const message = isPlainObject(record3.message) ? record3.message : null;
2584
+ let changed = false;
2585
+ if (message && Array.isArray(message.content)) {
2586
+ for (const block of message.content) {
2587
+ if (!isPlainObject(block) || block.type !== "tool_result") continue;
2588
+ if (typeof block.content === "string") changed = visitField(block, "content", visit) || changed;
2589
+ else if (Array.isArray(block.content)) {
2590
+ const joined = visitJoinedText(block.content, (item) => isPlainObject(item) && item.type === "text" && typeof item.text === "string" ? item.text : void 0, "\n", (item, text2) => ({ ...item, text: text2 }), visit);
2591
+ if (joined) {
2592
+ block.content = joined;
2593
+ changed = true;
2594
+ }
2595
+ for (const item of block.content) {
2596
+ if (isPlainObject(item) && item.type === "image" && isPlainObject(item.source)) changed = visitField(item.source, "data", visit, "image") || changed;
2597
+ }
2598
+ }
2599
+ }
2600
+ }
2601
+ const result = record3.toolUseResult;
2602
+ if (typeof result === "string") changed = visitField(record3, "toolUseResult", visit) || changed;
2603
+ else if (isPlainObject(result) && !result.agentId) {
2604
+ changed = visitStrings(result, visit, 0) || changed;
2605
+ }
2606
+ return changed;
2607
+ }
2485
2608
 
2486
2609
  // ../../server/agents/transcriptHead.ts
2487
2610
  import { readFile as readFile4, stat as stat3 } from "node:fs/promises";
@@ -2731,6 +2854,7 @@ async function readClaudeSessionMeta(filePath, head) {
2731
2854
  let foundMessage = false;
2732
2855
  let foundTimestamp = false;
2733
2856
  let foundAiTitle = false;
2857
+ let foundCustomTitle = false;
2734
2858
  let foundModel = !!model;
2735
2859
  outer: for (let i = 0; i < MAX_CHUNKS && cursor > 0; i++) {
2736
2860
  const readSize = Math.min(CHUNK, cursor);
@@ -2762,6 +2886,17 @@ async function readClaudeSessionMeta(filePath, head) {
2762
2886
  }
2763
2887
  continue;
2764
2888
  }
2889
+ if (!foundCustomTitle && line.includes('"custom-title"')) {
2890
+ try {
2891
+ const obj = JSON.parse(line);
2892
+ if (obj.type === "custom-title" && obj.customTitle) {
2893
+ customTitle = obj.customTitle;
2894
+ foundCustomTitle = true;
2895
+ }
2896
+ } catch {
2897
+ }
2898
+ continue;
2899
+ }
2765
2900
  if (!line.includes('"user"')) continue;
2766
2901
  try {
2767
2902
  const obj = JSON.parse(line);
@@ -2777,7 +2912,7 @@ async function readClaudeSessionMeta(filePath, head) {
2777
2912
  foundMessage = true;
2778
2913
  }
2779
2914
  }
2780
- if (foundMessage && foundTimestamp && foundAiTitle && foundModel) break outer;
2915
+ if (foundMessage && foundTimestamp && foundAiTitle && foundCustomTitle && foundModel) break outer;
2781
2916
  } catch {
2782
2917
  continue;
2783
2918
  }
@@ -3092,33 +3227,6 @@ function pluginSkillContext(workspacePath) {
3092
3227
  return current2 ? Promise.resolve().then(() => current2(workspacePath)).catch(() => "") : null;
3093
3228
  }
3094
3229
 
3095
- // ../../server/agents/runtimeTypes.ts
3096
- function resolvedApproval({ requestId, toolUseId, toolName }) {
3097
- return { requestId, toolUseId, toolName };
3098
- }
3099
- var AgentRuntimeError = class extends Error {
3100
- constructor(status, code, message, details) {
3101
- super(message);
3102
- this.status = status;
3103
- this.code = code;
3104
- this.details = details;
3105
- this.name = "AgentRuntimeError";
3106
- }
3107
- };
3108
- var NO_ACTIVITY = Object.freeze({ live: false, running: false, agentsRunning: false });
3109
- function reportSessionId(req, sessionId) {
3110
- try {
3111
- req.onSessionId?.(sessionId);
3112
- } catch (error2) {
3113
- console.error(`[agents] onSessionId failed for session ${sessionId}:`, error2);
3114
- }
3115
- }
3116
- function selectAvailableDecision(available, requested) {
3117
- if (available.includes(requested)) return requested;
3118
- if (requested === "allow_always" && available.includes("allow")) return "allow";
3119
- return null;
3120
- }
3121
-
3122
3230
  // ../../server/agents/acpRuntime.ts
3123
3231
  var descriptor = descriptorFor("acp");
3124
3232
  var sessions2 = /* @__PURE__ */ new Map();
@@ -3209,20 +3317,20 @@ async function connect() {
3209
3317
  if (!entry || generation !== currentGeneration || permissions.size >= 64) return { outcome: { outcome: "cancelled" } };
3210
3318
  const key = `${currentGeneration}:${JSON.stringify(requestId)}`;
3211
3319
  const decisions = params.options.flatMap((option) => option.kind === "allow_once" ? ["allow"] : option.kind === "allow_always" ? ["allow_always"] : ["deny"]);
3212
- return new Promise((resolve9) => {
3320
+ return new Promise((resolve10) => {
3213
3321
  const cancel = () => {
3214
3322
  const pending = permissions.get(key);
3215
3323
  if (!pending) return;
3216
3324
  clearTimeout(pending.timer);
3217
3325
  permissions.delete(key);
3218
- resolve9({ outcome: { outcome: "cancelled" } });
3326
+ resolve10({ outcome: { outcome: "cancelled" } });
3219
3327
  };
3220
3328
  const timer = setTimeout(cancel, 15 * 60 * 1e3);
3221
3329
  timer.unref();
3222
3330
  signal.addEventListener("abort", cancel, { once: true });
3223
3331
  permissions.set(key, { approval: { timestamp: Date.now(), sessionId: entry[0], requestId: key, toolUseId: params.toolCall.toolCallId, toolName: params.toolCall.title || "Tool", input: { value: params.toolCall.rawInput ?? {} }, availableDecisions: [...new Set(decisions)] }, options: params.options, resolve: (value) => {
3224
3332
  signal.removeEventListener("abort", cancel);
3225
- resolve9(value);
3333
+ resolve10(value);
3226
3334
  }, timer });
3227
3335
  });
3228
3336
  });
@@ -3264,6 +3372,7 @@ async function load(id, request) {
3264
3372
  return session;
3265
3373
  }
3266
3374
  async function send(id, request) {
3375
+ if (isSessionStarting(id)) return { delivery: "busy" };
3267
3376
  if (request.onlyWhileRunning) return { delivery: "busy" };
3268
3377
  if (sending.has(id)) return { delivery: "busy" };
3269
3378
  sending.add(id);
@@ -3342,6 +3451,7 @@ var acpRuntime = {
3342
3451
  kind: "acp",
3343
3452
  descriptor,
3344
3453
  async start(request) {
3454
+ request.signal?.throwIfAborted();
3345
3455
  const connected = await connect();
3346
3456
  const native = await bounded(connected.agent.request("session/new", { cwd: request.cwd, mcpServers: [] }, { cancellationSignal: AbortSignal.timeout(3e4) }), 3e4);
3347
3457
  const id = randomUUID();
@@ -3352,12 +3462,18 @@ var acpRuntime = {
3352
3462
  writeFileSync(path.filePath, JSON.stringify({ type: "acp_session", sessionId: id, nativeSessionId: native.sessionId, cwd: request.cwd, timestamp: (/* @__PURE__ */ new Date()).toISOString() }) + "\n", { mode: 384 });
3353
3463
  sessions2.set(id, { nativeId: native.sessionId, cwd: request.cwd, filePath: path.filePath, running: false, configOptions: native.configOptions ?? void 0 });
3354
3464
  reportSessionId(request, id);
3465
+ try {
3466
+ await beforeFirstTurn(request, { sessionId: id, cwd: request.cwd });
3467
+ } catch (error2) {
3468
+ sessions2.delete(id);
3469
+ throw error2;
3470
+ }
3355
3471
  if (request.message || request.images?.length) {
3356
3472
  const outcome = await send(id, request);
3357
3473
  void outcome.completion?.catch(() => {
3358
3474
  });
3359
3475
  }
3360
- return { sessionId: id, dirName, ...path, initialContent: readFileSync(path.filePath, "utf8") };
3476
+ return { sessionId: id, dirName, cwd: request.cwd, ...path, initialContent: readFileSync(path.filePath, "utf8") };
3361
3477
  },
3362
3478
  send,
3363
3479
  async interrupt(id) {
@@ -3525,6 +3641,164 @@ function linkAsyncAnswers(turns) {
3525
3641
  });
3526
3642
  }
3527
3643
 
3644
+ // ../../shared/session/composerContext.ts
3645
+ var EMPTY_DRAFT = Object.freeze({ v: 1, text: "", context: [], attachments: [] });
3646
+ var CONTEXT_LIMITS = Object.freeze({ items: 12, itemChars: 2e4, metadataChars: 2e3, totalChars: 1e5, noteChars: 2e3 });
3647
+ var ID_RE = /^ctx-[a-z0-9]{8,32}$/;
3648
+ function itemChars(item) {
3649
+ return item.kind === "browser" ? item.note.length : item.kind === "file" ? (item.text?.length ?? 0) + (item.comment?.length ?? 0) : item.text.length;
3650
+ }
3651
+ function itemRefusal(item) {
3652
+ if (!ID_RE.test(item.id)) return "This context has no valid id";
3653
+ if (itemChars(item) > CONTEXT_LIMITS.itemChars) return `This context is longer than ${CONTEXT_LIMITS.itemChars.toLocaleString("en-US")} characters`;
3654
+ if (item.kind === "browser" && item.note.length > CONTEXT_LIMITS.noteChars) return `An annotation note is limited to ${CONTEXT_LIMITS.noteChars.toLocaleString("en-US")} characters`;
3655
+ const metadata = attributes(item).filter(([key]) => key !== "comment");
3656
+ if (metadata.some(([, value]) => value.length > CONTEXT_LIMITS.metadataChars)) return `This context's ${metadata.find(([, value]) => value.length > CONTEXT_LIMITS.metadataChars)[0]} is longer than ${CONTEXT_LIMITS.metadataChars.toLocaleString("en-US")} characters`;
3657
+ return null;
3658
+ }
3659
+ function contextRefusal(context, item) {
3660
+ if (context.some((existing) => existing.id === item.id)) return null;
3661
+ if (context.length >= CONTEXT_LIMITS.items) return `A message holds at most ${CONTEXT_LIMITS.items} context items`;
3662
+ const refusal = itemRefusal(item);
3663
+ if (refusal) return refusal;
3664
+ const total = [...context, item].reduce((sum, existing) => sum + renderContextItem(existing).length, 0);
3665
+ if (total > CONTEXT_LIMITS.totalChars) return `The message's context would exceed ${CONTEXT_LIMITS.totalChars.toLocaleString("en-US")} characters`;
3666
+ return null;
3667
+ }
3668
+ function validateContext(context) {
3669
+ const accepted = [];
3670
+ for (const item of context) {
3671
+ if (accepted.some((existing) => existing.id === item.id)) return "The same context appears twice";
3672
+ const refusal = contextRefusal(accepted, item);
3673
+ if (refusal) return refusal;
3674
+ accepted.push(item);
3675
+ }
3676
+ return null;
3677
+ }
3678
+ function escapeAttribute(value) {
3679
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/\r/g, "&#13;").replace(/\n/g, "&#10;");
3680
+ }
3681
+ function unescapeAttribute(value) {
3682
+ return value.replace(/&#10;/g, "\n").replace(/&#13;/g, "\r").replace(/&lt;/g, "<").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
3683
+ }
3684
+ var CLOSE = "</context>";
3685
+ function escapeBody(text2) {
3686
+ return text2.split("\n").map((line) => /^ *<\/context>/.test(line) ? ` ${line}` : line).join("\n");
3687
+ }
3688
+ function unescapeBody(text2) {
3689
+ return text2.split("\n").map((line) => /^ +<\/context>/.test(line) ? line.slice(1) : line).join("\n");
3690
+ }
3691
+ function attributes(item) {
3692
+ switch (item.kind) {
3693
+ case "file":
3694
+ return [
3695
+ ["path", item.path],
3696
+ ...item.startLine !== void 0 ? [["lines", item.endLine !== void 0 && item.endLine !== item.startLine ? `${item.startLine}-${item.endLine}` : String(item.startLine)]] : [],
3697
+ ...item.comment ? [["comment", item.comment]] : []
3698
+ ];
3699
+ case "terminal":
3700
+ return item.label ? [["label", item.label]] : [];
3701
+ case "quote":
3702
+ return [["session", item.source.sessionId], ["turn", item.source.turnId], ["dir", item.source.dirName], ["file", item.source.fileName]];
3703
+ case "browser":
3704
+ return [
3705
+ ["url", item.url],
3706
+ ["title", item.title],
3707
+ ["viewport", `${item.viewport.width}x${item.viewport.height}`],
3708
+ ["region", `${item.region.x},${item.region.y},${item.region.width},${item.region.height}`],
3709
+ ["captured", item.capturedAt],
3710
+ ["browser", item.browser],
3711
+ ...item.tab ? [["tab", item.tab]] : [],
3712
+ ["image", String(item.image)]
3713
+ ];
3714
+ }
3715
+ }
3716
+ function body(item) {
3717
+ return item.kind === "browser" ? item.note : item.kind === "file" ? item.text ?? "" : item.text;
3718
+ }
3719
+ function renderContextItem(item) {
3720
+ const attrs = [["id", item.id], ["kind", item.kind], ...attributes(item)].map(([key, value]) => `${key}="${escapeAttribute(value)}"`).join(" ");
3721
+ return `<context ${attrs}>
3722
+ ${escapeBody(body(item))}
3723
+ ${CLOSE}`;
3724
+ }
3725
+ var OPEN_RE = /^<context ((?:[a-z]+="[^"]*"\s*)+)>\n/;
3726
+ function parseAttributes(source) {
3727
+ const found = /* @__PURE__ */ new Map();
3728
+ for (const match of source.matchAll(/([a-z]+)="([^"]*)"/g)) found.set(match[1], unescapeAttribute(match[2]));
3729
+ return found;
3730
+ }
3731
+ function positive(value) {
3732
+ if (value === void 0 || !/^\d+$/.test(value)) return void 0;
3733
+ return Number(value);
3734
+ }
3735
+ function itemFrom(attrs, text2) {
3736
+ const id = attrs.get("id");
3737
+ if (!id || !ID_RE.test(id)) return null;
3738
+ switch (attrs.get("kind")) {
3739
+ case "file": {
3740
+ const path = attrs.get("path");
3741
+ if (!path) return null;
3742
+ const [start, end] = (attrs.get("lines") ?? "").split("-");
3743
+ const startLine = positive(start);
3744
+ const endLine = positive(end) ?? startLine;
3745
+ const comment = attrs.get("comment");
3746
+ return { id, kind: "file", path, ...startLine !== void 0 ? { startLine, endLine } : {}, ...text2 ? { text: text2 } : {}, ...comment ? { comment } : {} };
3747
+ }
3748
+ case "terminal": {
3749
+ const label = attrs.get("label");
3750
+ return { id, kind: "terminal", text: text2, ...label ? { label } : {} };
3751
+ }
3752
+ case "quote": {
3753
+ const [sessionId, turnId, dirName, fileName] = ["session", "turn", "dir", "file"].map((key) => attrs.get(key));
3754
+ if (!sessionId || !turnId || !dirName || !fileName) return null;
3755
+ return { id, kind: "quote", text: text2, source: { sessionId, turnId, dirName, fileName } };
3756
+ }
3757
+ case "browser": {
3758
+ const viewport = (attrs.get("viewport") ?? "").split("x").map(Number);
3759
+ const region = (attrs.get("region") ?? "").split(",").map(Number);
3760
+ const image = positive(attrs.get("image"));
3761
+ if (viewport.length !== 2 || region.length !== 4 || ![...viewport, ...region].every(Number.isFinite) || !image) return null;
3762
+ const tab = attrs.get("tab");
3763
+ return {
3764
+ id,
3765
+ kind: "browser",
3766
+ note: text2,
3767
+ url: attrs.get("url") ?? "",
3768
+ title: attrs.get("title") ?? "",
3769
+ viewport: { width: viewport[0], height: viewport[1] },
3770
+ region: { x: region[0], y: region[1], width: region[2], height: region[3] },
3771
+ capturedAt: attrs.get("captured") ?? "",
3772
+ browser: attrs.get("browser") ?? "",
3773
+ ...tab ? { tab } : {},
3774
+ image
3775
+ };
3776
+ }
3777
+ default:
3778
+ return null;
3779
+ }
3780
+ }
3781
+ function parseComposerMessage(message) {
3782
+ const context = [];
3783
+ let rest = message;
3784
+ for (; ; ) {
3785
+ const open6 = OPEN_RE.exec(rest);
3786
+ if (!open6) break;
3787
+ const end = rest.indexOf(`
3788
+ ${CLOSE}`, open6[0].length - 1);
3789
+ if (end < 0) break;
3790
+ const item = itemFrom(parseAttributes(open6[1]), unescapeBody(rest.slice(open6[0].length, end)));
3791
+ if (!item) break;
3792
+ context.push(item);
3793
+ rest = rest.slice(end + CLOSE.length + 1).replace(/^\n{1,2}/, "");
3794
+ }
3795
+ if (context.length === 0 || validateContext(context)) return null;
3796
+ return { context, text: rest.trim() };
3797
+ }
3798
+ function composerPromptText(message) {
3799
+ return parseComposerMessage(message)?.text ?? message;
3800
+ }
3801
+
3528
3802
  // ../../shared/session/codex-exec.ts
3529
3803
  var JS_SIMPLE_ESCAPES = {
3530
3804
  "0": "\0",
@@ -3646,11 +3920,11 @@ function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
3646
3920
  for (let i = 0; i < sections.length; i++) {
3647
3921
  const section = sections[i];
3648
3922
  const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
3649
- const body = patchText.slice(section.startIdx, endIdx);
3650
- const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
3923
+ const body2 = patchText.slice(section.startIdx, endIdx);
3924
+ const movedTo = body2.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
3651
3925
  const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
3652
3926
  const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
3653
- const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
3927
+ const hunkBodies = body2.split(/^@@.*$/m).filter((s) => s.trim());
3654
3928
  const oldLines = [];
3655
3929
  const newLines = [];
3656
3930
  for (const hunk of hunkBodies) {
@@ -4255,7 +4529,7 @@ function extractMetadataFromRecords(records) {
4255
4529
  model ||= typeof record3.payload.model === "string" ? record3.payload.model : "";
4256
4530
  cwd ||= typeof record3.payload.cwd === "string" ? record3.payload.cwd : "";
4257
4531
  }
4258
- const prompt = readPrompt(record3);
4532
+ const prompt = composerPromptText(readPrompt(record3));
4259
4533
  if (!prompt) continue;
4260
4534
  if (!firstUserMessage) firstUserMessage = prompt;
4261
4535
  lastUserMessage = prompt;
@@ -5368,7 +5642,7 @@ function extractMetadata(events) {
5368
5642
  if (event.type !== "user.message") continue;
5369
5643
  metadata.turnCount++;
5370
5644
  metadata.timestamp ||= timestamp;
5371
- const content = readString(data.content).trim();
5645
+ const content = composerPromptText(readString(data.content)).trim();
5372
5646
  if (!content) continue;
5373
5647
  metadata.firstUserMessage ||= content;
5374
5648
  metadata.lastUserMessage = content;
@@ -6131,6 +6405,24 @@ function copilotTurnBoundaries(records) {
6131
6405
  }
6132
6406
  return boundaries;
6133
6407
  }
6408
+ function projectCopilotResults(record3, visit) {
6409
+ if (record3.type !== "tool.execution_complete" || !isObject4(record3.data)) return false;
6410
+ const data = record3.data;
6411
+ if (typeof data.result === "string") return visitField(data, "result", visit);
6412
+ if (!isObject4(data.result)) return false;
6413
+ const result = data.result;
6414
+ let changed = visitField(result, "detailedContent", visit);
6415
+ changed = visitField(result, "content", visit) || changed;
6416
+ if (Array.isArray(result.contents)) {
6417
+ const textOf = (part) => (typeof part === "string" ? part : isObject4(part) ? readString(part.text, part.content, part.output) : "") || void 0;
6418
+ const joined = visitJoinedText(result.contents, textOf, "\n", (part, text2) => typeof part === "string" ? text2 : { ...part, text: text2 }, visit);
6419
+ if (joined) {
6420
+ result.contents = joined;
6421
+ changed = true;
6422
+ }
6423
+ }
6424
+ return changed;
6425
+ }
6134
6426
 
6135
6427
  // ../../server/agents/copilotMetadata.ts
6136
6428
  function parseSimpleYamlValue(raw) {
@@ -6286,7 +6578,22 @@ function openDatabase(path) {
6286
6578
  return new Database(path);
6287
6579
  }
6288
6580
 
6581
+ // ../../shared/contracts/triggers.ts
6582
+ var TERMINAL_OCCURRENCE_STATES = ["completed", "failed", "cancelled", "skipped"];
6583
+ function occurrenceCommandId(occurrenceId) {
6584
+ return `trg-${occurrenceId}`;
6585
+ }
6586
+ function occurrenceCreationId(occurrenceId) {
6587
+ return `trgc-${occurrenceId}`;
6588
+ }
6589
+ function isTerminalOccurrence(state) {
6590
+ return TERMINAL_OCCURRENCE_STATES.includes(state);
6591
+ }
6592
+
6289
6593
  // ../../server/orchestration/store.ts
6594
+ var OCCURRENCE_REQUEST_LIMIT = 256 * 1024;
6595
+ var OCCURRENCE_INPUTS_LIMIT = 1024 * 1024;
6596
+ var ACTIVE_OCCURRENCE = "('admitting','creating')";
6290
6597
  var OrchestrationError = class extends Error {
6291
6598
  constructor(status, message) {
6292
6599
  super(message);
@@ -6323,7 +6630,7 @@ var OrchestrationStore = class {
6323
6630
  try {
6324
6631
  this.db.exec("PRAGMA busy_timeout=5000; PRAGMA foreign_keys=ON; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;");
6325
6632
  const version = Number(this.db.prepare("PRAGMA user_version").get().user_version);
6326
- if (version > 4) throw new Error("Orchestration database was created by a newer Cogpit; upgrade before opening it");
6633
+ if (version > 5) throw new Error("Orchestration database was created by a newer Cogpit; upgrade before opening it");
6327
6634
  this.transaction(() => {
6328
6635
  this.db.exec(`
6329
6636
  CREATE TABLE IF NOT EXISTS conversations(id TEXT PRIMARY KEY, native_key TEXT UNIQUE NOT NULL, data TEXT NOT NULL);
@@ -6345,6 +6652,15 @@ var OrchestrationStore = class {
6345
6652
  CREATE TABLE IF NOT EXISTS crew_chat_listeners(root_id TEXT NOT NULL,session_id TEXT NOT NULL,listening INTEGER NOT NULL DEFAULT 1,cursor INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(root_id,session_id));
6346
6653
  CREATE TABLE IF NOT EXISTS crew_chat_deliveries(sequence INTEGER NOT NULL REFERENCES crew_chat_messages(sequence) ON DELETE CASCADE,session_id TEXT NOT NULL,scope TEXT NOT NULL,kind TEXT NOT NULL,state TEXT NOT NULL DEFAULT 'pending',error TEXT,PRIMARY KEY(sequence,session_id));
6347
6654
  CREATE INDEX IF NOT EXISTS crew_chat_pending ON crew_chat_deliveries(scope,state,sequence);
6655
+ CREATE TABLE IF NOT EXISTS execution_grants(id TEXT PRIMARY KEY,scope TEXT NOT NULL,revision INTEGER NOT NULL,data TEXT NOT NULL,revoked_at INTEGER);
6656
+ CREATE TABLE IF NOT EXISTS triggers(scope TEXT NOT NULL,id TEXT NOT NULL,kind TEXT NOT NULL,revision INTEGER NOT NULL,state TEXT NOT NULL,next_due_at INTEGER,data TEXT NOT NULL,created_at INTEGER NOT NULL,updated_at INTEGER NOT NULL,PRIMARY KEY(scope,id));
6657
+ CREATE INDEX IF NOT EXISTS trigger_due ON triggers(kind,state,next_due_at);
6658
+ CREATE TABLE IF NOT EXISTS trigger_secrets(scope TEXT NOT NULL,trigger_id TEXT NOT NULL,id TEXT NOT NULL,data TEXT NOT NULL,created_at INTEGER NOT NULL,retired_at INTEGER,PRIMARY KEY(scope,trigger_id,id));
6659
+ CREATE TABLE IF NOT EXISTS trigger_occurrences(scope TEXT NOT NULL,id TEXT NOT NULL,trigger_id TEXT NOT NULL,kind TEXT NOT NULL,state TEXT NOT NULL,due_at INTEGER NOT NULL,attempt INTEGER NOT NULL DEFAULT 0,epoch INTEGER NOT NULL DEFAULT 0,fence_conversation TEXT,fence_position INTEGER,data TEXT NOT NULL,created_at INTEGER NOT NULL,updated_at INTEGER NOT NULL,PRIMARY KEY(scope,id));
6660
+ CREATE INDEX IF NOT EXISTS occurrence_due ON trigger_occurrences(state,due_at);
6661
+ CREATE INDEX IF NOT EXISTS occurrence_trigger ON trigger_occurrences(scope,trigger_id,created_at);
6662
+ CREATE INDEX IF NOT EXISTS occurrence_fence ON trigger_occurrences(fence_conversation,state);
6663
+ CREATE TABLE IF NOT EXISTS trigger_keys(scope TEXT NOT NULL,trigger_id TEXT NOT NULL,key TEXT NOT NULL,occurrence_id TEXT NOT NULL,fingerprint TEXT NOT NULL,created_at INTEGER NOT NULL,PRIMARY KEY(scope,trigger_id,key));
6348
6664
  `);
6349
6665
  if (!this.db.prepare("PRAGMA table_info(events)").all().some((row) => row.name === "scope")) this.db.exec("ALTER TABLE events ADD COLUMN scope TEXT");
6350
6666
  if (!this.db.prepare("PRAGMA table_info(operations)").all().some((row) => row.name === "updated_at")) this.db.exec("ALTER TABLE operations ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0");
@@ -6353,7 +6669,7 @@ var OrchestrationStore = class {
6353
6669
  this.db.exec("ALTER TABLE transitions ADD COLUMN scope TEXT");
6354
6670
  this.db.exec("UPDATE transitions SET scope=(SELECT scope FROM operations WHERE id=transitions.operation_id) WHERE (SELECT COUNT(*) FROM operations WHERE id=transitions.operation_id)=1");
6355
6671
  }
6356
- this.db.exec("PRAGMA user_version=4");
6672
+ this.db.exec("PRAGMA user_version=5");
6357
6673
  });
6358
6674
  this.protectFiles();
6359
6675
  } catch (error2) {
@@ -6363,7 +6679,9 @@ var OrchestrationStore = class {
6363
6679
  }
6364
6680
  db;
6365
6681
  listeners = /* @__PURE__ */ new Set();
6682
+ triggerListeners = /* @__PURE__ */ new Set();
6366
6683
  pendingEvents = null;
6684
+ pendingTriggerEvents = [];
6367
6685
  protectFiles() {
6368
6686
  if (this.path === ":memory:" || process.platform === "win32") return;
6369
6687
  for (const file of [this.path, `${this.path}-wal`, `${this.path}-shm`]) if (existsSync(file)) chmodSync2(file, 384);
@@ -6380,6 +6698,7 @@ var OrchestrationStore = class {
6380
6698
  events = this.pendingEvents;
6381
6699
  } catch (error2) {
6382
6700
  this.db.exec("ROLLBACK");
6701
+ this.pendingTriggerEvents = [];
6383
6702
  throw error2;
6384
6703
  } finally {
6385
6704
  this.pendingEvents = null;
@@ -6391,10 +6710,17 @@ var OrchestrationStore = class {
6391
6710
  } catch {
6392
6711
  }
6393
6712
  }
6713
+ for (const change of this.pendingTriggerEvents.splice(0)) for (const listener of this.triggerListeners) {
6714
+ try {
6715
+ listener(change);
6716
+ } catch {
6717
+ }
6718
+ }
6394
6719
  return result;
6395
6720
  }
6396
6721
  close() {
6397
6722
  this.listeners.clear();
6723
+ this.triggerListeners.clear();
6398
6724
  this.db.close();
6399
6725
  }
6400
6726
  chatListener(rootId, sessionId) {
@@ -6560,6 +6886,7 @@ var OrchestrationStore = class {
6560
6886
  const accepted = { id, nativeMessageId, conversationId, sessionId: conversation.binding.sessionId, bindingRevision: conversation.revision, state: "queued", intent, message: typeof payload.message === "string" ? payload.message : void 0, questionId: typeof payload.questionId === "string" ? payload.questionId : void 0, createdAt: this.now(), updatedAt: this.now(), position, replaces };
6561
6887
  this.db.prepare("INSERT INTO commands(scope,id,conversation_id,fingerprint,payload,receipt,state,position,updated_at) VALUES(?,?,?,?,?,?,?,?,?)").run(scope, id, conversationId, hash, JSON.stringify(payload), JSON.stringify(accepted), accepted.state, position, accepted.updatedAt);
6562
6888
  this.event(conversationId, "command", accepted, scope);
6889
+ if (!payload.trigger && payload.notification !== true) this.cancelFenced(conversationId, "Someone messaged the session after this was scheduled");
6563
6890
  return accepted;
6564
6891
  });
6565
6892
  }
@@ -6591,17 +6918,23 @@ var OrchestrationStore = class {
6591
6918
  for (const command of this.pending()) {
6592
6919
  if (["delivered", "unknown"].includes(command.receipt.state) && settlesOnDelivery(command.receipt.delivery, command.payload)) this.update(command, "completed", { error: void 0 });
6593
6920
  else if (command.receipt.state === "dispatching" || command.receipt.state === "delivered") this.update(command, "unknown", { error: "Host stopped during delivery. Inspect native history before resolving or resending." });
6594
- else if (command.receipt.state === "queued") this.update(command, "held", { error: "Host restarted; resume this queue after reviewing it." });
6921
+ else if (command.receipt.state === "queued" && !command.payload.executionGrant) this.update(command, "held", { error: "Host restarted; resume this queue after reviewing it." });
6595
6922
  }
6923
+ this.reconcileOccurrences(epoch);
6596
6924
  return { owner, epoch };
6597
6925
  });
6598
6926
  }
6927
+ /** Gives the lease up for good: nothing the released owner still has in flight can write under it. */
6599
6928
  release(lease) {
6600
- this.db.prepare("UPDATE dispatcher SET expires_at=0 WHERE id=1 AND owner=? AND epoch=?").run(lease.owner, lease.epoch);
6929
+ this.db.prepare("UPDATE dispatcher SET owner='',expires_at=0 WHERE id=1 AND owner=? AND epoch=?").run(lease.owner, lease.epoch);
6601
6930
  }
6602
6931
  owns(lease) {
6603
6932
  return Boolean(this.db.prepare("SELECT 1 FROM dispatcher WHERE id=1 AND owner=? AND epoch=?").get(lease.owner, lease.epoch));
6604
6933
  }
6934
+ /** Whether `lease` is still the one held: released and taken-over leases are not. */
6935
+ ownsLease(lease) {
6936
+ return this.owns(lease);
6937
+ }
6605
6938
  currentAttempt(command, lease) {
6606
6939
  const current2 = this.command(command.scope, command.receipt.id);
6607
6940
  return this.owns(lease) && Boolean(current2 && current2.attempt === command.attempt && current2.epoch === lease.epoch && ["dispatching", "delivered"].includes(current2.receipt.state));
@@ -6676,6 +7009,7 @@ var OrchestrationStore = class {
6676
7009
  return this.transaction(() => {
6677
7010
  const waiting = this.db.prepare("SELECT scope,id FROM commands WHERE conversation_id=? AND state IN ('queued','held')").all(conversationId).map((row) => this.command(String(row.scope), String(row.id)));
6678
7011
  for (const command of waiting) this.update(command, "cancelled", { error: "The session was stopped" });
7012
+ this.cancelFenced(conversationId, "The session was stopped");
6679
7013
  return waiting.length;
6680
7014
  });
6681
7015
  }
@@ -6815,6 +7149,7 @@ var OrchestrationStore = class {
6815
7149
  this.db.prepare("DELETE FROM crew_chat_messages WHERE root_id=?").run(sessionId);
6816
7150
  this.db.prepare("DELETE FROM crew_chat_listeners WHERE root_id=? OR session_id=?").run(sessionId, sessionId);
6817
7151
  this.db.prepare("DELETE FROM crew_chat_deliveries WHERE session_id=?").run(sessionId);
7152
+ for (const row of this.db.prepare("SELECT scope,id FROM trigger_occurrences WHERE json_extract(data,'$.sessionId')=? AND state NOT IN ('completed','failed','cancelled','skipped')").all(sessionId)) this.settleOccurrence(String(row.scope), String(row.id), "cancelled", { reason: "The session was deleted" });
6818
7153
  this.db.prepare("DELETE FROM commands WHERE json_extract(receipt,'$.sessionId')=?").run(sessionId);
6819
7154
  for (const row of this.db.prepare("SELECT native_key,conversation_id,data FROM bindings").all()) {
6820
7155
  if (decode(row.data).sessionId !== sessionId) continue;
@@ -6855,6 +7190,10 @@ var OrchestrationStore = class {
6855
7190
  const task = decode(row.data);
6856
7191
  if (task.acknowledgedAt && task.acknowledgedAt < cutoff) this.db.prepare("UPDATE tasks SET data=? WHERE id=?").run(JSON.stringify({ ...task, result: void 0 }), String(row.id));
6857
7192
  }
7193
+ this.db.prepare("DELETE FROM trigger_occurrences WHERE updated_at<? AND state IN ('completed','failed','cancelled','skipped')").run(cutoff);
7194
+ this.db.prepare("DELETE FROM trigger_keys WHERE created_at<? AND occurrence_id NOT IN (SELECT id FROM trigger_occurrences)").run(cutoff);
7195
+ this.db.prepare("DELETE FROM trigger_secrets WHERE retired_at IS NOT NULL AND retired_at<?").run(cutoff);
7196
+ this.db.prepare("DELETE FROM execution_grants WHERE revoked_at IS NOT NULL AND revoked_at<?").run(cutoff);
6858
7197
  });
6859
7198
  }
6860
7199
  instances() {
@@ -6894,11 +7233,410 @@ var OrchestrationStore = class {
6894
7233
  return { fresh: importedResult === void 0, ...importedResult === void 0 ? {} : { result: importedResult } };
6895
7234
  });
6896
7235
  }
7236
+ /** Forgets a creation claim that provably started nothing, so the same identity can start it later. */
7237
+ abandonOperation(scope, id) {
7238
+ this.transaction(() => {
7239
+ this.db.prepare("DELETE FROM operations WHERE scope=? AND id=? AND result IS NULL").run(scope, id);
7240
+ });
7241
+ }
6897
7242
  finishOperation(scope, id, result) {
6898
7243
  this.transaction(() => {
6899
7244
  this.db.prepare("UPDATE operations SET result=?,updated_at=? WHERE scope=? AND id=? AND result IS NULL").run(JSON.stringify(result), this.now(), scope, id);
6900
7245
  });
6901
7246
  }
7247
+ // ── Unattended execution grants ──────────────────────────────────
7248
+ putGrant(grant) {
7249
+ return this.transaction(() => {
7250
+ this.db.prepare("INSERT INTO execution_grants(id,scope,revision,data,revoked_at) VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET revision=excluded.revision,data=excluded.data,revoked_at=excluded.revoked_at").run(grant.id, grant.scope, grant.revision, JSON.stringify(grant), grant.revokedAt ?? null);
7251
+ return grant;
7252
+ });
7253
+ }
7254
+ grant(id) {
7255
+ const row = this.db.prepare("SELECT data FROM execution_grants WHERE id=?").get(id);
7256
+ return row ? decode(row.data) : null;
7257
+ }
7258
+ grants(scope) {
7259
+ return this.db.prepare("SELECT data FROM execution_grants WHERE scope=? ORDER BY id").all(scope).map((row) => decode(row.data));
7260
+ }
7261
+ /** Revokes a grant for good; work it admitted is held for reauthorization as soon as it is next claimed. */
7262
+ revokeGrant(id, reason) {
7263
+ return this.transaction(() => {
7264
+ const grant = this.grant(id);
7265
+ if (!grant || grant.revokedAt) return grant;
7266
+ return this.putGrant({ ...grant, revision: grant.revision + 1, revokedAt: this.now(), revokedReason: reason, updatedAt: this.now() });
7267
+ });
7268
+ }
7269
+ // ── Trigger definitions ──────────────────────────────────────────
7270
+ /** Whether any definition is active or any occurrence is still open, so the scheduler must run. */
7271
+ hasTriggerWork() {
7272
+ return Boolean(this.db.prepare("SELECT 1 FROM triggers WHERE state='active' UNION ALL SELECT 1 FROM trigger_occurrences WHERE state NOT IN ('completed','failed','cancelled','skipped') LIMIT 1").get());
7273
+ }
7274
+ subscribeTriggers(listener) {
7275
+ this.triggerListeners.add(listener);
7276
+ return () => this.triggerListeners.delete(listener);
7277
+ }
7278
+ triggerEvent(type, scope, triggerId, id) {
7279
+ this.pendingTriggerEvents.push({ type, scope, triggerId, id });
7280
+ }
7281
+ triggerRecord(row) {
7282
+ const data = decode(row.data);
7283
+ return { id: String(row.id), scope: String(row.scope), kind: String(row.kind), revision: Number(row.revision), state: row.state, grant: data.grant, nextDueAt: row.next_due_at === null ? null : Number(row.next_due_at), data: data.data, createdAt: Number(row.created_at), updatedAt: Number(row.updated_at) };
7284
+ }
7285
+ trigger(scope, id) {
7286
+ const row = this.db.prepare("SELECT * FROM triggers WHERE scope=? AND id=?").get(scope, id);
7287
+ return row ? this.triggerRecord(row) : null;
7288
+ }
7289
+ triggers(scope, kind2) {
7290
+ const rows = scope === null ? this.db.prepare("SELECT * FROM triggers WHERE kind=? ORDER BY created_at,id").all(kind2) : this.db.prepare("SELECT * FROM triggers WHERE scope=? AND kind=? ORDER BY created_at,id").all(scope, kind2);
7291
+ return rows.map((row) => this.triggerRecord(row));
7292
+ }
7293
+ /** Active definitions of `kind` whose consumer asked to be woken by now. */
7294
+ dueTriggers(kind2) {
7295
+ return this.db.prepare("SELECT * FROM triggers WHERE kind=? AND state='active' AND next_due_at IS NOT NULL AND next_due_at<=? ORDER BY next_due_at").all(kind2, this.now()).map((row) => this.triggerRecord(row));
7296
+ }
7297
+ /** Creates a definition, or replaces it when `expectedRevision` is the stored one. */
7298
+ putTrigger(scope, input, expectedRevision) {
7299
+ if (!/^[a-zA-Z0-9_-]{8,128}$/.test(input.id)) throw new OrchestrationError(400, "Trigger ids must contain 8\u2013128 letters, digits, underscores or hyphens");
7300
+ if (JSON.stringify(input.data).length > OCCURRENCE_REQUEST_LIMIT) throw new OrchestrationError(413, "Trigger definition is too large");
7301
+ return this.transaction(() => {
7302
+ const old = this.trigger(scope, input.id);
7303
+ if (old && old.kind !== input.kind) throw new OrchestrationError(409, "Trigger id already belongs to another kind");
7304
+ if (old && expectedRevision === void 0) throw new OrchestrationError(409, "Trigger already exists");
7305
+ if (expectedRevision !== void 0 && old?.revision !== expectedRevision) throw new OrchestrationError(409, "Trigger changed; refresh before saving");
7306
+ const revision = (old?.revision ?? 0) + 1;
7307
+ const data = JSON.stringify({ grant: input.grant, data: input.data });
7308
+ this.db.prepare("INSERT INTO triggers(scope,id,kind,revision,state,next_due_at,data,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(scope,id) DO UPDATE SET revision=excluded.revision,state=excluded.state,next_due_at=excluded.next_due_at,data=excluded.data,updated_at=excluded.updated_at").run(scope, input.id, input.kind, revision, input.state, input.nextDueAt, data, old?.createdAt ?? this.now(), this.now());
7309
+ this.triggerEvent("trigger", scope, input.id, input.id);
7310
+ return this.trigger(scope, input.id);
7311
+ });
7312
+ }
7313
+ /** Moves a definition's next wake-up without counting as an edit, unless it changed since `revision`. */
7314
+ setTriggerDue(scope, id, revision, nextDueAt) {
7315
+ return this.transaction(() => {
7316
+ const changed = Number(this.db.prepare("UPDATE triggers SET next_due_at=? WHERE scope=? AND id=? AND revision=?").run(nextDueAt, scope, id, revision).changes) > 0;
7317
+ if (changed) this.triggerEvent("trigger", scope, id, id);
7318
+ return changed;
7319
+ });
7320
+ }
7321
+ /** Deletes a definition with its secrets, cancels what it still has waiting, and revokes a grant nothing else uses. */
7322
+ deleteTrigger(scope, id) {
7323
+ this.transaction(() => {
7324
+ const trigger = this.trigger(scope, id);
7325
+ if (!trigger) return;
7326
+ for (const row of this.db.prepare(`SELECT id FROM trigger_occurrences WHERE scope=? AND trigger_id=? AND state IN ('scheduled','held')`).all(scope, id)) this.cancelOccurrence(scope, String(row.id), "The task was deleted");
7327
+ this.db.prepare("DELETE FROM trigger_secrets WHERE scope=? AND trigger_id=?").run(scope, id);
7328
+ this.db.prepare("DELETE FROM triggers WHERE scope=? AND id=?").run(scope, id);
7329
+ const grantId = trigger.grant?.grantId;
7330
+ if (grantId && !this.db.prepare("SELECT 1 FROM triggers WHERE json_extract(data,'$.grant.grantId')=? LIMIT 1").get(grantId)) this.revokeGrant(grantId, "Its task was deleted");
7331
+ this.triggerEvent("trigger", scope, id, id);
7332
+ });
7333
+ }
7334
+ /** Key material a consumer verifies deliveries with. Never part of a definition, a list or a log. */
7335
+ putTriggerSecret(scope, triggerId, id, data) {
7336
+ this.transaction(() => {
7337
+ this.db.prepare("INSERT INTO trigger_secrets(scope,trigger_id,id,data,created_at) VALUES(?,?,?,?,?)").run(scope, triggerId, id, JSON.stringify(data), this.now());
7338
+ });
7339
+ }
7340
+ /** Retires every secret but `keep`, which stay valid for `graceMs` so a rotation does not drop deliveries already in flight. */
7341
+ retireTriggerSecrets(scope, triggerId, keep, graceMs) {
7342
+ this.transaction(() => {
7343
+ this.db.prepare("UPDATE trigger_secrets SET retired_at=? WHERE scope=? AND trigger_id=? AND id<>? AND retired_at IS NULL").run(this.now() + graceMs, scope, triggerId, keep);
7344
+ });
7345
+ }
7346
+ triggerSecrets(scope, triggerId) {
7347
+ return this.db.prepare("SELECT id,data,created_at FROM trigger_secrets WHERE scope=? AND trigger_id=? AND (retired_at IS NULL OR retired_at>?) ORDER BY created_at DESC").all(scope, triggerId, this.now()).map((row) => ({ id: String(row.id), data: decode(row.data), createdAt: Number(row.created_at) }));
7348
+ }
7349
+ // ── Trigger occurrences ──────────────────────────────────────────
7350
+ occurrenceRow(row) {
7351
+ const data = decode(row.data);
7352
+ const command = this.command(String(row.scope), data.commandId)?.receipt;
7353
+ return { ...data, id: String(row.id), scope: String(row.scope), triggerId: String(row.trigger_id), kind: String(row.kind), state: row.state, dueAt: Number(row.due_at), attempt: Number(row.attempt), createdAt: Number(row.created_at), updatedAt: Number(row.updated_at), ...command ? { command } : {} };
7354
+ }
7355
+ occurrence(scope, id) {
7356
+ const row = this.db.prepare("SELECT * FROM trigger_occurrences WHERE scope=? AND id=?").get(scope, id);
7357
+ return row ? this.occurrenceRow(row) : null;
7358
+ }
7359
+ /** A trigger's occurrences, newest first. */
7360
+ occurrences(scope, triggerId, options = {}) {
7361
+ const limit = Math.min(200, Math.max(1, options.limit ?? 50));
7362
+ return this.db.prepare("SELECT * FROM trigger_occurrences WHERE scope=? AND trigger_id=? AND created_at<? ORDER BY created_at DESC,id DESC LIMIT ?").all(scope, triggerId, options.before ?? Number.MAX_SAFE_INTEGER, limit).map((row) => this.occurrenceRow(row));
7363
+ }
7364
+ /** Every session a trigger's occurrences started, except `exceptId`'s. */
7365
+ triggerSessionIds(scope, triggerId, exceptId) {
7366
+ return this.db.prepare("SELECT DISTINCT json_extract(data,'$.sessionId') AS session FROM trigger_occurrences WHERE scope=? AND trigger_id=? AND id<>? AND json_extract(data,'$.sessionId') IS NOT NULL").all(scope, triggerId, exceptId ?? "").map((row) => String(row.session));
7367
+ }
7368
+ /** Occurrences of a trigger not yet settled: waiting, running, held or awaiting reconciliation. */
7369
+ openOccurrences(scope, triggerId) {
7370
+ return this.db.prepare("SELECT * FROM trigger_occurrences WHERE scope=? AND trigger_id=? AND state NOT IN ('completed','failed','cancelled','skipped') ORDER BY created_at").all(scope, triggerId).map((row) => this.occurrenceRow(row));
7371
+ }
7372
+ writeOccurrence(occurrence, patch, state = occurrence.state, dueAt = occurrence.dueAt) {
7373
+ const old = decode(this.db.prepare("SELECT data FROM trigger_occurrences WHERE scope=? AND id=?").get(occurrence.scope, occurrence.id).data);
7374
+ const terminal = isTerminalOccurrence(state);
7375
+ const data = { ...old, ...patch, ...terminal && !old.finishedAt ? { finishedAt: this.now() } : {} };
7376
+ this.db.prepare("UPDATE trigger_occurrences SET state=?,due_at=?,data=?,updated_at=? WHERE scope=? AND id=?").run(state, dueAt, JSON.stringify(data), this.now(), occurrence.scope, occurrence.id);
7377
+ this.triggerEvent("occurrence", occurrence.scope, occurrence.triggerId, occurrence.id);
7378
+ }
7379
+ /**
7380
+ * Records one due event. A key already recorded returns the occurrence it
7381
+ * made (a replayed delivery or a slot computed twice); different content
7382
+ * under the same key is refused. A burst can fold into the newest
7383
+ * occurrence still waiting instead of starting another run.
7384
+ */
7385
+ recordOccurrence(scope, input) {
7386
+ if (!input.key || input.key.length > 256) throw new OrchestrationError(400, "Occurrence keys must contain 1\u2013256 characters");
7387
+ if (JSON.stringify(input.request).length > OCCURRENCE_REQUEST_LIMIT) throw new OrchestrationError(413, "Occurrence request is too large");
7388
+ const hash = fingerprint({ request: input.request, data: input.input?.data ?? null, action: input.action });
7389
+ return this.transaction(() => {
7390
+ const seen = this.db.prepare("SELECT occurrence_id,fingerprint FROM trigger_keys WHERE scope=? AND trigger_id=? AND key=?").get(scope, input.triggerId, input.key);
7391
+ if (seen) {
7392
+ if (seen.fingerprint !== hash) throw new OrchestrationError(409, "This delivery id was already recorded with different content");
7393
+ const existing = this.occurrence(scope, String(seen.occurrence_id));
7394
+ if (existing) return { occurrence: existing, outcome: "duplicate" };
7395
+ throw new OrchestrationError(409, "This delivery id was already recorded");
7396
+ }
7397
+ const received = input.input ? { key: input.key, receivedAt: this.now(), data: input.input.data } : void 0;
7398
+ if (input.coalesce && received) {
7399
+ const row = this.db.prepare("SELECT * FROM trigger_occurrences WHERE scope=? AND trigger_id=? AND state='scheduled' AND created_at>=? AND json_extract(data,'$.coalesceGroup')=? ORDER BY created_at DESC LIMIT 1").get(scope, input.triggerId, this.now() - input.coalesce.windowMs, input.coalesce.group);
7400
+ const target = row && this.occurrenceRow(row);
7401
+ if (target && target.inputs.length < input.coalesce.maxInputs && JSON.stringify([...target.inputs, received]).length <= OCCURRENCE_INPUTS_LIMIT) {
7402
+ this.writeOccurrence(target, { inputs: [...target.inputs, received] });
7403
+ this.db.prepare("INSERT INTO trigger_keys VALUES(?,?,?,?,?,?)").run(scope, input.triggerId, input.key, target.id, hash, this.now());
7404
+ return { occurrence: this.occurrence(scope, target.id), outcome: "coalesced" };
7405
+ }
7406
+ }
7407
+ if (received && JSON.stringify(received).length > OCCURRENCE_INPUTS_LIMIT) throw new OrchestrationError(413, "Occurrence input is too large");
7408
+ const waiting = Number(this.db.prepare("SELECT COUNT(*) AS n FROM trigger_occurrences WHERE scope=? AND trigger_id=? AND state IN ('scheduled','held')").get(scope, input.triggerId).n);
7409
+ if (waiting >= (input.backlog ?? 100)) throw new OrchestrationError(429, "Too many runs of this task are already waiting");
7410
+ const id = `occ-${fingerprint([scope, input.triggerId, input.key]).slice(0, 32)}`;
7411
+ const fencePosition = input.fence ? Number(this.db.prepare("SELECT COALESCE(MAX(position),0) AS n FROM commands WHERE conversation_id=?").get(input.fence.conversationId).n) : null;
7412
+ const data = { key: input.key, action: input.action, grant: input.grant, request: input.request, inputs: received ? [received] : [], commandId: occurrenceCommandId(id), creationId: occurrenceCreationId(id), ...input.guarded ? { guarded: true } : {}, ...input.coalesce ? { coalesceGroup: input.coalesce.group } : {} };
7413
+ this.db.prepare("INSERT INTO trigger_occurrences(scope,id,trigger_id,kind,state,due_at,fence_conversation,fence_position,data,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)").run(scope, id, input.triggerId, input.kind, "scheduled", input.dueAt, input.fence?.conversationId ?? null, fencePosition, JSON.stringify(data), this.now(), this.now());
7414
+ this.db.prepare("INSERT INTO trigger_keys VALUES(?,?,?,?,?,?)").run(scope, input.triggerId, input.key, id, hash, this.now());
7415
+ this.triggerEvent("occurrence", scope, input.triggerId, id);
7416
+ return { occurrence: this.occurrence(scope, id), outcome: "created" };
7417
+ });
7418
+ }
7419
+ /**
7420
+ * Claims due occurrences of `kinds` for this lease, one at a time per
7421
+ * trigger: a trigger with an occurrence still being admitted or created
7422
+ * waits for it.
7423
+ */
7424
+ claimOccurrences(lease, kinds, limit = 20) {
7425
+ if (!kinds.length) return [];
7426
+ return this.transaction(() => {
7427
+ if (!this.owns(lease)) return [];
7428
+ const rows = this.db.prepare(`SELECT * FROM trigger_occurrences o WHERE state='scheduled' AND due_at<=? AND kind IN (${kinds.map(() => "?").join(",")}) AND NOT EXISTS (SELECT 1 FROM trigger_occurrences a WHERE a.scope=o.scope AND a.trigger_id=o.trigger_id AND a.state IN ${ACTIVE_OCCURRENCE}) ORDER BY due_at,created_at LIMIT ?`).all(this.now(), ...kinds, limit);
7429
+ const claimed = [];
7430
+ const triggers = /* @__PURE__ */ new Set();
7431
+ for (const row of rows) {
7432
+ const key = JSON.stringify([row.scope, row.trigger_id]);
7433
+ if (triggers.has(key)) continue;
7434
+ triggers.add(key);
7435
+ this.db.prepare("UPDATE trigger_occurrences SET state='admitting',attempt=attempt+1,epoch=?,updated_at=? WHERE scope=? AND id=?").run(lease.epoch, this.now(), String(row.scope), String(row.id));
7436
+ this.triggerEvent("occurrence", String(row.scope), String(row.trigger_id), String(row.id));
7437
+ const occurrence = this.occurrence(String(row.scope), String(row.id));
7438
+ claimed.push({ occurrence, attempt: occurrence.attempt, epoch: lease.epoch });
7439
+ }
7440
+ return claimed;
7441
+ });
7442
+ }
7443
+ claimCurrent(claim, lease, states) {
7444
+ if (!this.owns(lease) || lease.epoch !== claim.epoch) return null;
7445
+ const row = this.db.prepare("SELECT * FROM trigger_occurrences WHERE scope=? AND id=? AND attempt=? AND epoch=?").get(claim.occurrence.scope, claim.occurrence.id, claim.attempt, claim.epoch);
7446
+ const occurrence = row && this.occurrenceRow(row);
7447
+ return occurrence && states.includes(occurrence.state) ? occurrence : null;
7448
+ }
7449
+ /** Whether a claim still owns its occurrence; every write a claimed execution makes checks it first. */
7450
+ occurrenceCurrent(claim, lease) {
7451
+ return this.claimCurrent(claim, lease, ["admitting", "creating"]) !== null;
7452
+ }
7453
+ /** Settles a claimed occurrence; false when another attempt or host owns it now. */
7454
+ finishOccurrence(claim, lease, state, details = {}) {
7455
+ return this.transaction(() => {
7456
+ const current2 = this.claimCurrent(claim, lease, ["admitting", "creating"]);
7457
+ if (!current2) return false;
7458
+ const { dueAt, ...patch } = details;
7459
+ this.writeOccurrence(current2, { hold: void 0, reason: void 0, ...patch }, state, dueAt ?? current2.dueAt);
7460
+ return true;
7461
+ });
7462
+ }
7463
+ /**
7464
+ * Marks a claimed occurrence as creating a session. From here a crash
7465
+ * leaves its creation operation to say what happened: no operation means
7466
+ * nothing started, one without a result means the outcome is unknown.
7467
+ */
7468
+ beginOccurrenceCreation(claim, lease) {
7469
+ return this.transaction(() => {
7470
+ const current2 = this.claimCurrent(claim, lease, ["admitting"]);
7471
+ if (!current2) return false;
7472
+ this.writeOccurrence(current2, {}, "creating");
7473
+ return true;
7474
+ });
7475
+ }
7476
+ /**
7477
+ * Admits a claimed occurrence's durable command in the same transaction that
7478
+ * records it, so a crash leaves either both or neither. Refuses, cancelling
7479
+ * the occurrence, when someone else messaged its fenced conversation since
7480
+ * it was recorded.
7481
+ */
7482
+ admitForOccurrence(claim, lease, conversationId, payload, intent, expectedRevision) {
7483
+ return this.transaction(() => {
7484
+ const current2 = this.claimCurrent(claim, lease, ["admitting"]);
7485
+ if (!current2) return null;
7486
+ const row = this.db.prepare("SELECT fence_conversation,fence_position FROM trigger_occurrences WHERE scope=? AND id=?").get(current2.scope, current2.id);
7487
+ if (row.fence_conversation && this.intervened(String(row.fence_conversation), Number(row.fence_position))) {
7488
+ this.writeOccurrence(current2, { reason: "Someone messaged the session after this was scheduled" }, "cancelled");
7489
+ return null;
7490
+ }
7491
+ const conversation = this.conversation(conversationId);
7492
+ if (expectedRevision !== void 0 && conversation?.revision !== expectedRevision) {
7493
+ this.writeOccurrence(current2, { hold: "binding", reason: "The session moved to another provider binding; review this run before resuming it" }, "held");
7494
+ return null;
7495
+ }
7496
+ const receipt2 = this.admit(current2.scope, current2.commandId, conversationId, { ...payload, trigger: { occurrenceId: current2.id } }, intent);
7497
+ this.writeOccurrence(current2, { sessionId: receipt2.sessionId, hold: void 0, reason: void 0 }, "admitted");
7498
+ return receipt2;
7499
+ });
7500
+ }
7501
+ intervened(conversationId, position) {
7502
+ return this.db.prepare("SELECT payload FROM commands WHERE conversation_id=? AND position>?").all(conversationId, position).some((row) => {
7503
+ const payload = decode(row.payload);
7504
+ return !payload.trigger && payload.notification !== true;
7505
+ });
7506
+ }
7507
+ cancelFenced(conversationId, reason) {
7508
+ for (const row of this.db.prepare("SELECT scope,id FROM trigger_occurrences WHERE fence_conversation=? AND state IN ('scheduled','held','admitting')").all(conversationId)) this.settleOccurrence(String(row.scope), String(row.id), "cancelled", { reason });
7509
+ }
7510
+ settleOccurrence(scope, id, state, patch) {
7511
+ const occurrence = this.occurrence(scope, id);
7512
+ if (occurrence) this.writeOccurrence(occurrence, patch, state);
7513
+ }
7514
+ /**
7515
+ * Mirrors admitted occurrences' commands: a settled command settles its
7516
+ * occurrence, a held or uncertain one holds it or marks it unknown with the
7517
+ * command's reason, and one resumed from the queue makes it admitted again.
7518
+ */
7519
+ settleAdmittedOccurrences() {
7520
+ return this.transaction(() => {
7521
+ let changed = 0;
7522
+ for (const row of this.db.prepare("SELECT * FROM trigger_occurrences WHERE state IN ('admitted','held','unknown')").all()) {
7523
+ const occurrence = this.occurrenceRow(row);
7524
+ const command = occurrence.command;
7525
+ if (!command) {
7526
+ if (occurrence.state !== "admitted") continue;
7527
+ this.writeOccurrence(occurrence, { reason: "Its command no longer exists" }, "cancelled");
7528
+ changed++;
7529
+ continue;
7530
+ }
7531
+ const next = command.state === "completed" || command.state === "failed" || command.state === "cancelled" || command.state === "unknown" ? command.state : command.state === "held" ? "held" : "admitted";
7532
+ if (next === occurrence.state) continue;
7533
+ const hold = command.errorCode === "REAUTHORIZE" ? "reauthorize" : "review";
7534
+ this.writeOccurrence(occurrence, next === "admitted" ? { hold: void 0, reason: void 0 } : { ...next === "held" ? { hold } : {}, ...command.error ? { reason: command.error } : {} }, next);
7535
+ changed++;
7536
+ }
7537
+ return changed;
7538
+ });
7539
+ }
7540
+ /**
7541
+ * After another host's lease lapsed, reads what its claims left behind:
7542
+ * an admission either committed its command or never happened, and a
7543
+ * creation's operation says whether a session may exist.
7544
+ */
7545
+ reconcileOccurrences(epoch) {
7546
+ for (const row of this.db.prepare(`SELECT * FROM trigger_occurrences WHERE state IN ${ACTIVE_OCCURRENCE} AND epoch<?`).all(epoch)) {
7547
+ const occurrence = this.occurrenceRow(row);
7548
+ if (occurrence.state === "admitting") {
7549
+ if (occurrence.command) this.writeOccurrence(occurrence, { sessionId: occurrence.command.sessionId }, "admitted");
7550
+ else this.writeOccurrence(occurrence, {}, "scheduled");
7551
+ continue;
7552
+ }
7553
+ const operation = this.operation(occurrence.scope, occurrence.creationId);
7554
+ const sessionId = operation?.result?.sessionId;
7555
+ if (!operation) this.writeOccurrence(occurrence, {}, "scheduled");
7556
+ else if (typeof sessionId === "string") this.writeOccurrence(occurrence, { sessionId }, "completed");
7557
+ else this.writeOccurrence(occurrence, { reason: "The host stopped while this run was starting a session. Check the project's sessions before marking it." }, "unknown");
7558
+ }
7559
+ }
7560
+ /** Cancels an occurrence that has not reached an agent yet, with its waiting command. */
7561
+ cancelOccurrence(scope, id, reason = "Cancelled") {
7562
+ return this.transaction(() => {
7563
+ const occurrence = this.occurrence(scope, id);
7564
+ if (!occurrence) throw new OrchestrationError(404, "Run not found");
7565
+ if (isTerminalOccurrence(occurrence.state)) return occurrence;
7566
+ if (occurrence.state === "admitting" || occurrence.state === "creating") throw new OrchestrationError(409, "This run is being started; wait for it to settle");
7567
+ if (occurrence.state === "unknown") throw new OrchestrationError(409, "Resolve this run's unknown outcome instead");
7568
+ const command = occurrence.command;
7569
+ if (command && ["dispatching", "delivered", "unknown"].includes(command.state)) throw new OrchestrationError(409, "Delivery has begun; stop the session's turn instead");
7570
+ if (command && ["queued", "held"].includes(command.state)) this.cancel(scope, command.id);
7571
+ this.writeOccurrence(occurrence, { hold: void 0, reason }, "cancelled");
7572
+ return this.occurrence(scope, id);
7573
+ });
7574
+ }
7575
+ /**
7576
+ * Returns a held occurrence to the schedule, due now, optionally under a
7577
+ * renewed grant. One whose message was already admitted releases that same
7578
+ * message — its payload and fingerprint untouched — with the grant replacing
7579
+ * the occurrence's own, once the grant is checked to be the owner's, live,
7580
+ * and for exactly what the old one allowed.
7581
+ */
7582
+ requeueOccurrence(scope, id, grant) {
7583
+ return this.transaction(() => {
7584
+ const occurrence = this.occurrence(scope, id);
7585
+ if (!occurrence) throw new OrchestrationError(404, "Run not found");
7586
+ if (occurrence.state !== "held") throw new OrchestrationError(409, "Only a held run can be resumed");
7587
+ if (grant) this.checkReplacementGrant(scope, occurrence, grant);
7588
+ const command = occurrence.command && this.command(scope, occurrence.command.id);
7589
+ if (command) {
7590
+ if (command.receipt.state !== "held") throw new OrchestrationError(409, "This run's message is not held");
7591
+ if (this.conversation(command.receipt.conversationId)?.revision !== command.receipt.bindingRevision) throw new OrchestrationError(409, "The session moved to another provider binding; cancel this run instead");
7592
+ this.update(command, "queued", { error: void 0, errorCode: void 0 });
7593
+ this.writeOccurrence(occurrence, { hold: void 0, reason: void 0, ...grant ? { grant } : {} }, "admitted");
7594
+ return this.occurrence(scope, id);
7595
+ }
7596
+ this.writeOccurrence(occurrence, { hold: void 0, reason: void 0, ...grant ? { grant } : {} }, "scheduled", Math.min(occurrence.dueAt, this.now()));
7597
+ return this.occurrence(scope, id);
7598
+ });
7599
+ }
7600
+ checkReplacementGrant(scope, occurrence, ref) {
7601
+ const grant = this.grant(ref.grantId);
7602
+ if (!grant || grant.revision !== ref.revision) throw new OrchestrationError(404, "Authorization not found");
7603
+ if (grant.scope !== scope) throw new OrchestrationError(403, "That authorization belongs to another account");
7604
+ if (grant.revokedAt || grant.expiresAt !== void 0 && grant.expiresAt <= this.now()) throw new OrchestrationError(409, "That authorization is no longer valid");
7605
+ if (!grant.actions.includes(occurrence.action)) throw new OrchestrationError(409, "That authorization does not allow this run");
7606
+ const previous = occurrence.grant && this.grant(occurrence.grant.grantId);
7607
+ const command = occurrence.command;
7608
+ const sameTarget = previous ? fingerprint(previous.target) === fingerprint(grant.target) : !command || grant.target.sessionId === command.sessionId && (grant.target.bindingRevision === void 0 || grant.target.bindingRevision === command.bindingRevision);
7609
+ if (!sameTarget) throw new OrchestrationError(409, "That authorization is for a different target");
7610
+ }
7611
+ /** Holds a scheduled occurrence for review, such as one a missed-run policy will not start by itself. */
7612
+ holdOccurrence(scope, id, hold, reason) {
7613
+ return this.transaction(() => {
7614
+ const occurrence = this.occurrence(scope, id);
7615
+ if (!occurrence) throw new OrchestrationError(404, "Run not found");
7616
+ if (occurrence.state !== "scheduled") throw new OrchestrationError(409, "Only a waiting run can be held");
7617
+ this.writeOccurrence(occurrence, { hold, reason }, "held");
7618
+ return this.occurrence(scope, id);
7619
+ });
7620
+ }
7621
+ /** Marks a scheduled occurrence skipped, such as a missed slot or an overlapping run. */
7622
+ skipOccurrence(scope, id, reason) {
7623
+ return this.transaction(() => {
7624
+ const occurrence = this.occurrence(scope, id);
7625
+ if (!occurrence) throw new OrchestrationError(404, "Run not found");
7626
+ if (occurrence.state !== "scheduled") throw new OrchestrationError(409, "Only a waiting run can be skipped");
7627
+ this.writeOccurrence(occurrence, { reason }, "skipped");
7628
+ return this.occurrence(scope, id);
7629
+ });
7630
+ }
7631
+ /** The user's answer to an occurrence whose session creation outcome is unknown. */
7632
+ resolveOccurrence(scope, id, disposition, sessionId) {
7633
+ return this.transaction(() => {
7634
+ const occurrence = this.occurrence(scope, id);
7635
+ if (!occurrence || occurrence.state !== "unknown") throw new OrchestrationError(409, "Run is not awaiting reconciliation");
7636
+ this.writeOccurrence(occurrence, { reason: disposition === "failed" ? "Marked as not started" : "Marked as started", ...sessionId ? { sessionId } : {} }, disposition);
7637
+ return this.occurrence(scope, id);
7638
+ });
7639
+ }
6902
7640
  };
6903
7641
 
6904
7642
  // ../../server/orchestration/storage.ts
@@ -7034,6 +7772,68 @@ function allStores() {
7034
7772
  return [...registry.allStores(), ...instanceStores()];
7035
7773
  }
7036
7774
 
7775
+ // ../../shared/session/codex-results.ts
7776
+ var OUTPUT_ITEMS = /* @__PURE__ */ new Set(["function_call_output", "custom_tool_call_output"]);
7777
+ var EXEC_OUTPUT_KEYS = ["aggregated_output", "formatted_output", "stdout", "stderr"];
7778
+ function semantics(output) {
7779
+ const parsed = parseCustomToolOutput(output);
7780
+ return JSON.stringify([parsed.isError, parsed.images?.length ?? 0]);
7781
+ }
7782
+ function visitImage(block, visit) {
7783
+ const source = isPlainObject(block.source) ? { ...block.source } : null;
7784
+ if (source) return visitField(source, "data", visit, "image") ? { ...block, source } : block;
7785
+ if (typeof block.image_url !== "string") return block;
7786
+ const match = /^(data:image\/[^;,]+;base64,)(.+)$/is.exec(block.image_url);
7787
+ if (!match) return block;
7788
+ const next = visit(match[2], "image");
7789
+ return next === match[2] ? block : { ...block, image_url: match[1] + next };
7790
+ }
7791
+ function projectBlocks(blocks, visit) {
7792
+ const joined = visitJoinedText(blocks, (block) => isPlainObject(block) && typeof block.text === "string" ? block.text : void 0, "", (block, text2) => ({ ...block, text: text2 }), visit) ?? blocks;
7793
+ let changed = joined !== blocks;
7794
+ const next = joined.map((block) => {
7795
+ if (!isPlainObject(block) || typeof block.text === "string") return block;
7796
+ const projected = visitImage(block, visit);
7797
+ if (projected !== block) changed = true;
7798
+ return projected;
7799
+ });
7800
+ return changed ? next : blocks;
7801
+ }
7802
+ function projectValue(output, visit) {
7803
+ if (Array.isArray(output)) return projectBlocks(output, visit);
7804
+ if (typeof output !== "string") return output;
7805
+ let envelope = null;
7806
+ try {
7807
+ envelope = JSON.parse(output);
7808
+ } catch {
7809
+ }
7810
+ if (isPlainObject(envelope) && ("output" in envelope || "content" in envelope)) {
7811
+ const key = envelope.output !== void 0 && envelope.output !== null ? "output" : "content";
7812
+ const inner = envelope[key];
7813
+ const projected = typeof inner === "string" ? visit(inner, "text") : Array.isArray(inner) ? projectValue(inner, visit) : inner;
7814
+ return projected === inner ? output : JSON.stringify({ ...envelope, [key]: projected });
7815
+ }
7816
+ return visit(output, "text");
7817
+ }
7818
+ function projectOutput(payload, visit) {
7819
+ const output = payload.output;
7820
+ const next = projectValue(output, visit);
7821
+ if (next === output || semantics(next) !== semantics(output)) return false;
7822
+ payload.output = next;
7823
+ return true;
7824
+ }
7825
+ function projectCodexResults(record3, visit) {
7826
+ const payload = isPlainObject(record3.payload) ? record3.payload : null;
7827
+ if (!payload) return false;
7828
+ if (record3.type === "response_item" && OUTPUT_ITEMS.has(String(payload.type))) return projectOutput(payload, visit);
7829
+ if (record3.type === "event_msg" && payload.type === "exec_command_end") {
7830
+ let changed = false;
7831
+ for (const key of EXEC_OUTPUT_KEYS) changed = visitField(payload, key, visit) || changed;
7832
+ return changed;
7833
+ }
7834
+ return false;
7835
+ }
7836
+
7037
7837
  // ../../shared/session/agents.ts
7038
7838
  var TERMINAL_AGENT_KIND = AGENT_KINDS[AGENT_KINDS.length - 1];
7039
7839
  var hasNoNoiseLines = () => false;
@@ -7049,7 +7849,8 @@ var codexFormat = {
7049
7849
  isNoiseLine: hasNoNoiseLines,
7050
7850
  pageHeaderLine: codexPageHeaderLine,
7051
7851
  brandBranch: brandCodexBranch,
7052
- turnBoundaries: codexTurnBoundaries
7852
+ turnBoundaries: codexTurnBoundaries,
7853
+ projectResults: projectCodexResults
7053
7854
  };
7054
7855
  var copilotFormat = {
7055
7856
  kind: "copilot",
@@ -7062,7 +7863,8 @@ var copilotFormat = {
7062
7863
  status: deriveCopilotSessionStatus,
7063
7864
  isNoiseLine: hasNoNoiseLines,
7064
7865
  brandBranch: brandCopilotBranch,
7065
- turnBoundaries: copilotTurnBoundaries
7866
+ turnBoundaries: copilotTurnBoundaries,
7867
+ projectResults: projectCopilotResults
7066
7868
  };
7067
7869
  var claudeFormat = {
7068
7870
  kind: "claude",
@@ -7076,7 +7878,8 @@ var claudeFormat = {
7076
7878
  status: deriveClaudeSessionStatus,
7077
7879
  isNoiseLine: isClaudeNoiseLine,
7078
7880
  brandBranch: brandClaudeBranch,
7079
- turnBoundaries: claudeTurnBoundaries
7881
+ turnBoundaries: claudeTurnBoundaries,
7882
+ projectResults: projectClaudeResults
7080
7883
  };
7081
7884
  var FORMATS = Object.freeze({
7082
7885
  acp: {
@@ -7611,7 +8414,7 @@ function extractVersion(output) {
7611
8414
  var DEFAULT_PROBE_TIMEOUT_MS = 5e3;
7612
8415
  var CLI_OUTPUT_MAX_CHARS = 1e4;
7613
8416
  function runCli(executable, args, timeoutMs) {
7614
- return new Promise((resolve9) => {
8417
+ return new Promise((resolve10) => {
7615
8418
  const resolved = resolveAgentCommand(executable, [...args]);
7616
8419
  const child2 = spawn3(resolved.command, resolved.args, {
7617
8420
  stdio: ["ignore", "pipe", "pipe"],
@@ -7633,11 +8436,11 @@ function runCli(executable, args, timeoutMs) {
7633
8436
  });
7634
8437
  child2.on("error", () => {
7635
8438
  clearTimeout(timer);
7636
- resolve9({ code: null, stdout, stderr, timedOut });
8439
+ resolve10({ code: null, stdout, stderr, timedOut });
7637
8440
  });
7638
8441
  child2.on("close", (code) => {
7639
8442
  clearTimeout(timer);
7640
- resolve9({ code, stdout, stderr, timedOut });
8443
+ resolve10({ code, stdout, stderr, timedOut });
7641
8444
  });
7642
8445
  });
7643
8446
  }
@@ -7848,12 +8651,12 @@ function effortLabel(effort) {
7848
8651
 
7849
8652
  // ../../server/agents/timeout.ts
7850
8653
  function withTimeout(promise, ms, label) {
7851
- return new Promise((resolve9, reject) => {
8654
+ return new Promise((resolve10, reject) => {
7852
8655
  const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
7853
8656
  promise.then(
7854
8657
  (value) => {
7855
8658
  clearTimeout(timer);
7856
- resolve9(value);
8659
+ resolve10(value);
7857
8660
  },
7858
8661
  (error2) => {
7859
8662
  clearTimeout(timer);
@@ -8774,8 +9577,8 @@ var resultObservers = /* @__PURE__ */ new WeakMap();
8774
9577
  function observeSDKResult(state) {
8775
9578
  let observer;
8776
9579
  const turn = (inputTurns.get(state)?.sent.length ?? Number(state.running)) + 1;
8777
- const completion = new Promise((resolve9) => {
8778
- observer = { turn, resolve: resolve9 };
9580
+ const completion = new Promise((resolve10) => {
9581
+ observer = { turn, resolve: resolve10 };
8779
9582
  });
8780
9583
  const observers = resultObservers.get(state) ?? /* @__PURE__ */ new Set();
8781
9584
  observers.add(observer);
@@ -8810,16 +9613,16 @@ function makeCanUseTool(state) {
8810
9613
  return (toolName, input, options) => {
8811
9614
  if (toolName === "AskUserQuestion") {
8812
9615
  const toolUseId = options.toolUseID;
8813
- return new Promise((resolve9) => {
9616
+ return new Promise((resolve10) => {
8814
9617
  const pending = {
8815
9618
  input,
8816
9619
  askedAt: Date.now(),
8817
- resolve: resolve9
9620
+ resolve: resolve10
8818
9621
  };
8819
9622
  state.pendingUserQuestions.set(toolUseId, pending);
8820
9623
  const onAbort = () => {
8821
9624
  if (state.pendingUserQuestions.delete(toolUseId)) {
8822
- resolve9({ behavior: "deny", message: "Session aborted", interrupt: true });
9625
+ resolve10({ behavior: "deny", message: "Session aborted", interrupt: true });
8823
9626
  }
8824
9627
  };
8825
9628
  options.signal?.addEventListener("abort", onAbort, { once: true });
@@ -8833,7 +9636,7 @@ function makeCanUseTool(state) {
8833
9636
  return Promise.resolve({ behavior: "allow", updatedInput: input });
8834
9637
  }
8835
9638
  const requestId = options.toolUseID;
8836
- return new Promise((resolve9) => {
9639
+ return new Promise((resolve10) => {
8837
9640
  const pending = {
8838
9641
  requestId,
8839
9642
  toolName,
@@ -8848,12 +9651,12 @@ function makeCanUseTool(state) {
8848
9651
  defaultToNo: options.defaultToNo,
8849
9652
  suppressAlwaysAllowRule: options.suppressAlwaysAllowRule,
8850
9653
  timestamp: Date.now(),
8851
- resolve: resolve9
9654
+ resolve: resolve10
8852
9655
  };
8853
9656
  state.pendingPermissions.set(requestId, pending);
8854
9657
  const onAbort = () => {
8855
9658
  if (state.pendingPermissions.delete(requestId)) {
8856
- resolve9({ behavior: "deny", message: "Session aborted", interrupt: true });
9659
+ resolve10({ behavior: "deny", message: "Session aborted", interrupt: true });
8857
9660
  }
8858
9661
  };
8859
9662
  options.signal?.addEventListener("abort", onAbort, { once: true });
@@ -8870,16 +9673,16 @@ function makeOnElicitation(state) {
8870
9673
  );
8871
9674
  return Promise.resolve({ action: "decline" });
8872
9675
  }
8873
- return new Promise((resolve9) => {
9676
+ return new Promise((resolve10) => {
8874
9677
  const requestId = options.requestId;
8875
9678
  state.pendingElicitations.set(requestId, {
8876
9679
  request,
8877
9680
  fields: projection.fields,
8878
9681
  askedAt: Date.now(),
8879
- resolve: resolve9
9682
+ resolve: resolve10
8880
9683
  });
8881
9684
  const onAbort = () => {
8882
- if (state.pendingElicitations.delete(requestId)) resolve9({ action: "decline" });
9685
+ if (state.pendingElicitations.delete(requestId)) resolve10({ action: "decline" });
8883
9686
  };
8884
9687
  options.signal?.addEventListener("abort", onAbort, { once: true });
8885
9688
  });
@@ -8908,14 +9711,14 @@ function makeOnUserDialog(state) {
8908
9711
  if (!(request.dialogKind in DIALOG_CHOICES)) return Promise.resolve(null);
8909
9712
  const projected = projectUserDialog(request);
8910
9713
  if (!projected) return Promise.resolve({ behavior: "cancelled" });
8911
- return new Promise((resolve9) => {
9714
+ return new Promise((resolve10) => {
8912
9715
  const requestId = options.requestId;
8913
9716
  state.pendingUserDialogs.set(requestId, {
8914
9717
  dialog: { ...projected, requestId, askedAt: Date.now() },
8915
- resolve: resolve9
9718
+ resolve: resolve10
8916
9719
  });
8917
9720
  const onAbort = () => {
8918
- if (state.pendingUserDialogs.delete(requestId)) resolve9({ behavior: "cancelled" });
9721
+ if (state.pendingUserDialogs.delete(requestId)) resolve10({ behavior: "cancelled" });
8919
9722
  };
8920
9723
  options.signal?.addEventListener("abort", onAbort, { once: true });
8921
9724
  });
@@ -9115,8 +9918,8 @@ var SDKMessageStream = class {
9115
9918
  closed = false;
9116
9919
  enqueue(message) {
9117
9920
  if (this.closed) return false;
9118
- const resolve9 = this.waiting.shift();
9119
- if (resolve9) resolve9({ value: message, done: false });
9921
+ const resolve10 = this.waiting.shift();
9922
+ if (resolve10) resolve10({ value: message, done: false });
9120
9923
  else this.queued.push(message);
9121
9924
  return true;
9122
9925
  }
@@ -9124,15 +9927,15 @@ var SDKMessageStream = class {
9124
9927
  if (this.closed) return;
9125
9928
  this.closed = true;
9126
9929
  this.queued.length = 0;
9127
- for (const resolve9 of this.waiting.splice(0)) {
9128
- resolve9({ value: void 0, done: true });
9930
+ for (const resolve10 of this.waiting.splice(0)) {
9931
+ resolve10({ value: void 0, done: true });
9129
9932
  }
9130
9933
  }
9131
9934
  next() {
9132
9935
  const message = this.queued.shift();
9133
9936
  if (message) return Promise.resolve({ value: message, done: false });
9134
9937
  if (this.closed) return Promise.resolve({ value: void 0, done: true });
9135
- return new Promise((resolve9) => this.waiting.push(resolve9));
9938
+ return new Promise((resolve10) => this.waiting.push(resolve10));
9136
9939
  }
9137
9940
  return() {
9138
9941
  this.close();
@@ -9181,7 +9984,28 @@ function runQuery(state, prompt, opts, pluginInstructions, preparation) {
9181
9984
  const messageStream = new SDKMessageStream();
9182
9985
  const first = buildUserMessage(prompt, opts.images);
9183
9986
  inputTurns.set(state, { sent: [first.uuid], completed: 0, taken: null });
9184
- messageStream.enqueue(first);
9987
+ if (opts.beforeFirstTurn) {
9988
+ let readiness2;
9989
+ queryOpts.hooks = {
9990
+ ...queryOpts.hooks,
9991
+ SessionStart: [{ timeout: 86400, hooks: [async (input) => {
9992
+ try {
9993
+ await (readiness2 ??= (async () => {
9994
+ state.cwd = input.cwd;
9995
+ await opts.beforeFirstTurn({ sessionId: state.sessionId, cwd: input.cwd });
9996
+ state.abort?.signal.throwIfAborted();
9997
+ messageStream.enqueue(first);
9998
+ })());
9999
+ return {};
10000
+ } catch (error2) {
10001
+ opts.onStartupError?.(error2);
10002
+ messageStream.close();
10003
+ state.abort?.abort();
10004
+ return { continue: false, decision: "block", reason: "Worktree startup did not complete" };
10005
+ }
10006
+ }] }]
10007
+ };
10008
+ } else messageStream.enqueue(first);
9185
10009
  state.messageStream = messageStream;
9186
10010
  const q = query2({ prompt: messageStream, options: queryOpts });
9187
10011
  state.activeQuery = q;
@@ -9310,6 +10134,8 @@ function createSDKSession(opts) {
9310
10134
  runQuery(state, opts.message, {
9311
10135
  name: opts.name,
9312
10136
  worktreeName: opts.worktreeName,
10137
+ beforeFirstTurn: opts.beforeFirstTurn,
10138
+ onStartupError: opts.onStartupError,
9313
10139
  mcpConfig: opts.mcpConfig,
9314
10140
  images: opts.images
9315
10141
  });
@@ -9693,8 +10519,13 @@ function sdkResumeOptions(req) {
9693
10519
  };
9694
10520
  }
9695
10521
  function startInteractive(req) {
10522
+ req.signal?.throwIfAborted();
9696
10523
  const sessionId = randomUUID3();
9697
10524
  reportSessionId(req, sessionId);
10525
+ let readinessComplete = !req.beforeFirstTurn;
10526
+ let initialized = false;
10527
+ let succeeded;
10528
+ let startupError;
9698
10529
  const state = createSDKSession({
9699
10530
  sessionId,
9700
10531
  // An images-only send has no prompt; the SDK substitutes its own
@@ -9705,13 +10536,20 @@ function startInteractive(req) {
9705
10536
  ...sdkResumeOptions(req),
9706
10537
  ultracode: !!req.ultracode,
9707
10538
  name: req.name,
9708
- worktreeName: req.worktreeName
10539
+ worktreeName: req.worktreeName,
10540
+ beforeFirstTurn: req.beforeFirstTurn ? async (identity) => {
10541
+ await beforeFirstTurn(req, { ...identity, nativeWorktree: !!req.worktreeName });
10542
+ readinessComplete = true;
10543
+ if (initialized) succeeded?.();
10544
+ } : void 0,
10545
+ onStartupError: (error2) => startupError?.(error2)
9709
10546
  });
9710
- return new Promise((resolve9, reject) => {
10547
+ return new Promise((resolve10, reject) => {
9711
10548
  let settled = false;
9712
10549
  const succeed = async () => {
9713
- if (settled) return;
10550
+ if (settled || !readinessComplete) return;
9714
10551
  settled = true;
10552
+ req.signal?.removeEventListener("abort", abort);
9715
10553
  state.onInit = null;
9716
10554
  state.onResult = null;
9717
10555
  const dirName = descriptor2.dirName.encode(state.cwd);
@@ -9722,18 +10560,39 @@ function startInteractive(req) {
9722
10560
  initialContent = await readFile6(filePath, "utf-8");
9723
10561
  } catch {
9724
10562
  }
9725
- resolve9({ sessionId, dirName, cwd: state.cwd, fileName, filePath, initialContent });
10563
+ resolve10({ sessionId, dirName, cwd: state.cwd, fileName, filePath, initialContent });
9726
10564
  };
9727
10565
  const fail = (message) => {
9728
10566
  if (settled) return;
9729
10567
  settled = true;
10568
+ req.signal?.removeEventListener("abort", abort);
9730
10569
  state.onInit = null;
9731
10570
  state.onResult = null;
10571
+ stopSDKSession(sessionId);
9732
10572
  reject(new AgentRuntimeError(500, "SPAWN_FAILED", message));
9733
10573
  };
10574
+ const abort = () => {
10575
+ stopSDKSession(sessionId);
10576
+ startupError?.(req.signal?.reason ?? new Error("Session startup cancelled"));
10577
+ };
10578
+ startupError = (error2) => {
10579
+ if (settled) return;
10580
+ settled = true;
10581
+ req.signal?.removeEventListener("abort", abort);
10582
+ state.onInit = null;
10583
+ state.onResult = null;
10584
+ stopSDKSession(sessionId);
10585
+ reject(error2);
10586
+ };
10587
+ succeeded = () => {
10588
+ void succeed();
10589
+ };
9734
10590
  state.onInit = () => {
10591
+ initialized = true;
9735
10592
  void succeed();
9736
10593
  };
10594
+ req.signal?.addEventListener("abort", abort, { once: true });
10595
+ if (req.signal?.aborted) abort();
9737
10596
  state.onResult = (result) => {
9738
10597
  state.onResult = null;
9739
10598
  const outcome = turnResultFrom(result);
@@ -9790,6 +10649,7 @@ var claudeRuntime = {
9790
10649
  return startInteractive(req);
9791
10650
  },
9792
10651
  async send(sessionId, req) {
10652
+ if (isSessionStarting(sessionId)) return { delivery: "busy" };
9793
10653
  const live = sdkSessions.get(sessionId);
9794
10654
  if (live?.pluginSkillLoading) return { delivery: "busy" };
9795
10655
  if (req.onlyWhileRunning && (!isSDKQueryLive(live) || !live.running)) return { delivery: "busy" };
@@ -9822,10 +10682,10 @@ var claudeRuntime = {
9822
10682
  ...sdkResumeOptions(req)
9823
10683
  });
9824
10684
  watchSubagentsFor(state, req.filePath ?? null);
9825
- const completion = new Promise((resolve9) => {
10685
+ const completion = new Promise((resolve10) => {
9826
10686
  state.onResult = (result) => {
9827
10687
  state.onResult = null;
9828
- resolve9(turnResultFrom(result));
10688
+ resolve10(turnResultFrom(result));
9829
10689
  };
9830
10690
  });
9831
10691
  return { delivery: "started", completion };
@@ -9901,6 +10761,112 @@ var claudeRuntime = {
9901
10761
  }
9902
10762
  };
9903
10763
 
10764
+ // ../../server/lib/managedCommand.ts
10765
+ import { spawn as spawn4, execFile } from "node:child_process";
10766
+ import { StringDecoder } from "node:string_decoder";
10767
+ async function runManagedCommand(options) {
10768
+ options.signal.throwIfAborted();
10769
+ const windows = process.platform === "win32";
10770
+ const child2 = spawn4(options.file, options.args, {
10771
+ cwd: options.cwd,
10772
+ stdio: ["ignore", "pipe", "pipe"],
10773
+ detached: !windows,
10774
+ windowsHide: true,
10775
+ env: { ...process.env, CI: "1", GIT_TERMINAL_PROMPT: "0", ...options.env }
10776
+ });
10777
+ const killTree = () => {
10778
+ if (!child2.pid) return;
10779
+ if (windows) {
10780
+ execFile("taskkill", ["/pid", String(child2.pid), "/T", "/F"], { windowsHide: true }, () => {
10781
+ });
10782
+ } else {
10783
+ try {
10784
+ process.kill(-child2.pid, "SIGKILL");
10785
+ } catch {
10786
+ }
10787
+ }
10788
+ };
10789
+ let timedOut = false;
10790
+ const timer = setTimeout(() => {
10791
+ timedOut = true;
10792
+ killTree();
10793
+ }, options.timeoutMs);
10794
+ const abort = () => killTree();
10795
+ options.signal.addEventListener("abort", abort, { once: true });
10796
+ if (options.signal.aborted) killTree();
10797
+ const stdout = new StringDecoder("utf8");
10798
+ const stderr = new StringDecoder("utf8");
10799
+ child2.stdout.on("data", (chunk) => options.output(stdout.write(chunk)));
10800
+ child2.stderr.on("data", (chunk) => options.output(stderr.write(chunk)));
10801
+ try {
10802
+ const code = await new Promise((resolve10, reject) => {
10803
+ child2.once("error", reject);
10804
+ child2.once("close", resolve10);
10805
+ });
10806
+ options.output(stdout.end() + stderr.end());
10807
+ options.signal.throwIfAborted();
10808
+ if (timedOut) throw new Error("Command timed out");
10809
+ return code;
10810
+ } finally {
10811
+ clearTimeout(timer);
10812
+ options.signal.removeEventListener("abort", abort);
10813
+ killTree();
10814
+ }
10815
+ }
10816
+
10817
+ // ../../server/agents/managedWorktree.ts
10818
+ import { randomUUID as randomUUID6 } from "node:crypto";
10819
+ import { mkdir as mkdir3 } from "node:fs/promises";
10820
+ import { join as join16 } from "node:path";
10821
+
10822
+ // ../../server/sessionStartup/project.ts
10823
+ import { execFile as execFile2 } from "node:child_process";
10824
+ import { promisify as promisify2 } from "node:util";
10825
+ import { realpath as realpath4, stat as stat9 } from "node:fs/promises";
10826
+ import { isAbsolute as isAbsolute2, resolve as resolve8 } from "node:path";
10827
+ var execute = promisify2(execFile2);
10828
+ async function gitOutput(cwd, args, signal) {
10829
+ const result = await execute("git", args, { cwd, signal, encoding: "utf8", timeout: 15e3, maxBuffer: 1024 * 1024, windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
10830
+ return result.stdout.trim();
10831
+ }
10832
+ async function physicalDirectory(cwd) {
10833
+ if (!isAbsolute2(cwd) || cwd.includes("\0")) throw new RouteError(400, ErrorCodes.INVALID_REQUEST, "An absolute project directory is required");
10834
+ const path = await realpath4(cwd);
10835
+ if (!(await stat9(path)).isDirectory()) throw new RouteError(400, ErrorCodes.INVALID_REQUEST, "Project directory does not exist");
10836
+ return path;
10837
+ }
10838
+ async function projectIdentity(cwd) {
10839
+ const path = await physicalDirectory(cwd);
10840
+ const common = await gitOutput(path, ["rev-parse", "--git-common-dir"]);
10841
+ return realpath4(resolve8(path, common));
10842
+ }
10843
+ async function validateInitializedDirectory(projectCwd, cwd) {
10844
+ const path = await physicalDirectory(cwd);
10845
+ if (await projectIdentity(projectCwd) !== await projectIdentity(path)) {
10846
+ throw new RouteError(403, ErrorCodes.FORBIDDEN, "Initialized worktree belongs to a different project");
10847
+ }
10848
+ return path;
10849
+ }
10850
+
10851
+ // ../../server/agents/managedWorktree.ts
10852
+ async function prepareManagedWorktree(request) {
10853
+ if (!request.worktreeName || !request.beforeFirstTurn) return request;
10854
+ if (!/^[a-zA-Z0-9._-]{1,80}$/.test(request.worktreeName) || request.worktreeName === "." || request.worktreeName === "..") throw new AgentRuntimeError(400, "INVALID_REQUEST", "Invalid worktree name");
10855
+ request.signal?.throwIfAborted();
10856
+ const suffix = randomUUID6().slice(0, 8);
10857
+ const name = `${request.worktreeName.slice(0, 28)}-${suffix}`;
10858
+ const parent = join16(request.cwd, ".worktrees");
10859
+ await mkdir3(parent, { recursive: true });
10860
+ const cwd = join16(parent, name);
10861
+ let output = "";
10862
+ const code = await runManagedCommand({ cwd: request.cwd, file: "git", args: ["worktree", "add", "-b", `worktree-${name}`, cwd, "HEAD"], signal: request.signal ?? new AbortController().signal, timeoutMs: 12e4, output: (text2) => {
10863
+ output = (output + text2).slice(-8e3);
10864
+ } });
10865
+ if (code !== 0) throw new AgentRuntimeError(500, "SPAWN_FAILED", output.trim() || "Could not initialize the managed worktree");
10866
+ await validateInitializedDirectory(request.cwd, cwd);
10867
+ return { ...request, worktreeName: void 0, cwd, dirName: descriptorFor("codex").dirName.encode(cwd) };
10868
+ }
10869
+
9904
10870
  // ../../shared/session/contextWindowSettings.ts
9905
10871
  function isValidContextWindowTokens(value) {
9906
10872
  return value === void 0 || value === null || typeof value === "number" && Number.isSafeInteger(value) && value > 0;
@@ -15945,7 +16911,7 @@ import { createInterface as createInterface2 } from "node:readline";
15945
16911
  var package_default = {
15946
16912
  name: "cogpit",
15947
16913
  private: true,
15948
- version: "3.5.1",
16914
+ version: "3.5.3",
15949
16915
  description: "Control center for Claude Code, OpenAI Codex, and GitHub Copilot CLI sessions",
15950
16916
  license: "MIT",
15951
16917
  author: {
@@ -16849,9 +17815,9 @@ var CodexAppServer = class {
16849
17815
  const completed = this.completedTurns.get(key);
16850
17816
  if (completed) return Promise.resolve(completed);
16851
17817
  if (!this.child) return Promise.reject(new CodexAppServerError("Codex disconnected before completion"));
16852
- return new Promise((resolve9, reject) => {
17818
+ return new Promise((resolve10, reject) => {
16853
17819
  const waiting = this.completionWaiters.get(key) ?? [];
16854
- waiting.push({ resolve: resolve9, reject });
17820
+ waiting.push({ resolve: resolve10, reject });
16855
17821
  this.completionWaiters.set(key, waiting);
16856
17822
  });
16857
17823
  }
@@ -17168,7 +18134,7 @@ var CodexAppServer = class {
17168
18134
  );
17169
18135
  }
17170
18136
  const id = this.nextRequestId++;
17171
- return new Promise((resolve9, reject) => {
18137
+ return new Promise((resolve10, reject) => {
17172
18138
  const timer = timeoutMs > 0 ? this.setTimer(() => {
17173
18139
  const pending = this.pendingRequests.get(id);
17174
18140
  if (!pending) return;
@@ -17181,7 +18147,7 @@ var CodexAppServer = class {
17181
18147
  }, timeoutMs) : null;
17182
18148
  this.pendingRequests.set(id, {
17183
18149
  method,
17184
- resolve: (result) => resolve9(result),
18150
+ resolve: (result) => resolve10(result),
17185
18151
  reject,
17186
18152
  timer
17187
18153
  });
@@ -17207,12 +18173,12 @@ var CodexAppServer = class {
17207
18173
  new CodexAppServerError("Codex app-server stdin is not writable")
17208
18174
  );
17209
18175
  }
17210
- return new Promise((resolve9, reject) => {
18176
+ return new Promise((resolve10, reject) => {
17211
18177
  try {
17212
18178
  child2.stdin.write(`${JSON.stringify(message)}
17213
18179
  `, (error2) => {
17214
18180
  if (error2) reject(error2);
17215
- else resolve9();
18181
+ else resolve10();
17216
18182
  });
17217
18183
  } catch (error2) {
17218
18184
  reject(error2);
@@ -17259,13 +18225,13 @@ var CodexAppServer = class {
17259
18225
  }
17260
18226
  }
17261
18227
  waitForChildClose(closed, timeoutMs) {
17262
- return new Promise((resolve9) => {
18228
+ return new Promise((resolve10) => {
17263
18229
  let settled = false;
17264
18230
  const finish = (didClose) => {
17265
18231
  if (settled) return;
17266
18232
  settled = true;
17267
18233
  this.clearTimer(timer);
17268
- resolve9(didClose);
18234
+ resolve10(didClose);
17269
18235
  };
17270
18236
  const timer = this.setTimer(() => finish(false), timeoutMs);
17271
18237
  void closed.then(() => finish(true));
@@ -17274,8 +18240,8 @@ var CodexAppServer = class {
17274
18240
  async terminateChild(child2, error2) {
17275
18241
  let didClose = false;
17276
18242
  let resolveClose = () => void 0;
17277
- const closed = new Promise((resolve9) => {
17278
- resolveClose = resolve9;
18243
+ const closed = new Promise((resolve10) => {
18244
+ resolveClose = resolve10;
17279
18245
  });
17280
18246
  child2.on("close", () => {
17281
18247
  didClose = true;
@@ -17395,7 +18361,7 @@ function friendlySpawnError(err, kind2 = "claude") {
17395
18361
  // ../../server/agents/tempImages.ts
17396
18362
  import { writeFile as writeFile3, unlink as unlink3 } from "node:fs/promises";
17397
18363
  import { tmpdir } from "node:os";
17398
- import { join as join16 } from "node:path";
18364
+ import { join as join17 } from "node:path";
17399
18365
  var IMAGE_EXT = {
17400
18366
  "image/jpeg": "jpg",
17401
18367
  "image/gif": "gif",
@@ -17406,7 +18372,7 @@ async function writeTempImageFiles(images) {
17406
18372
  const files = [];
17407
18373
  for (const [index, image] of images.entries()) {
17408
18374
  const ext = IMAGE_EXT[image.mediaType] ?? "png";
17409
- const filePath = join16(tmpdir(), `cogpit-codex-image-${Date.now()}-${index}.${ext}`);
18375
+ const filePath = join17(tmpdir(), `cogpit-codex-image-${Date.now()}-${index}.${ext}`);
17410
18376
  await writeFile3(filePath, Buffer.from(image.data, "base64"));
17411
18377
  files.push(filePath);
17412
18378
  }
@@ -17422,7 +18388,7 @@ async function cleanupTempFiles(paths) {
17422
18388
  }
17423
18389
 
17424
18390
  // ../../server/agents/codexExecution.ts
17425
- import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve8, sep as sep5 } from "node:path";
18391
+ import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve9, sep as sep5 } from "node:path";
17426
18392
 
17427
18393
  // ../../server/agents/codexBrowser.ts
17428
18394
  async function codexBrowserConfig(client2, cwd, sessionId = NO_COGPIT_SESSION) {
@@ -17565,6 +18531,7 @@ async function startCodexExecution(client2, options, onThreadStarted) {
17565
18531
  const input = buildCodexUserInput(options.message, options.images);
17566
18532
  const response = await client2.startThread(await threadSettings(client2, options));
17567
18533
  onThreadStarted?.(response.thread.id);
18534
+ await options.beforeFirstTurn?.({ sessionId: response.thread.id, cwd: typeof response.thread.cwd === "string" ? response.thread.cwd : options.cwd });
17568
18535
  const turn = await client2.startTurn({
17569
18536
  threadId: response.thread.id,
17570
18537
  input,
@@ -17614,11 +18581,11 @@ async function continueCodexExecution(client2, threadId, options) {
17614
18581
  }
17615
18582
  function getCodexThreadIdentity(thread, sessionsDir = storeFor("codex").sessionsRoot() ?? "") {
17616
18583
  const threadPath = typeof thread.path === "string" ? thread.path : "";
17617
- if (!thread.id || !threadPath || !isAbsolute2(threadPath)) return null;
17618
- const root = resolve8(sessionsDir);
17619
- const filePath = resolve8(threadPath);
18584
+ if (!thread.id || !threadPath || !isAbsolute3(threadPath)) return null;
18585
+ const root = resolve9(sessionsDir);
18586
+ const filePath = resolve9(threadPath);
17620
18587
  const fileName = uncompressedTranscriptName(relative4(root, filePath));
17621
- if (!fileName || fileName === ".." || fileName.startsWith(`..${sep5}`) || isAbsolute2(fileName) || !fileName.endsWith(".jsonl")) {
18588
+ if (!fileName || fileName === ".." || fileName.startsWith(`..${sep5}`) || isAbsolute3(fileName) || !fileName.endsWith(".jsonl")) {
17622
18589
  return null;
17623
18590
  }
17624
18591
  return {
@@ -17696,7 +18663,7 @@ async function waitForRollout(announcedId, scan, shouldStop) {
17696
18663
  if (Date.now() - eliminatedAt >= ANNOUNCEMENT_GRACE_MS) return match;
17697
18664
  }
17698
18665
  }
17699
- await new Promise((resolve9) => setTimeout(resolve9, SESSION_DISCOVERY_INTERVAL_MS));
18666
+ await new Promise((resolve10) => setTimeout(resolve10, SESSION_DISCOVERY_INTERVAL_MS));
17700
18667
  }
17701
18668
  return null;
17702
18669
  }
@@ -17747,6 +18714,7 @@ async function resolveStartedIdentity(thread) {
17747
18714
  throw new Error(`Codex created thread ${thread.id} but did not provide a rollout path`);
17748
18715
  }
17749
18716
  async function startLegacy(req, knownPaths) {
18717
+ await beforeFirstTurn(req, { cwd: req.cwd });
17750
18718
  const imagePaths = await writeTempImageFiles(req.images);
17751
18719
  const args = legacyArgs(req, imagePaths);
17752
18720
  const startedAt2 = Date.now();
@@ -17773,7 +18741,7 @@ async function startLegacy(req, knownPaths) {
17773
18741
  child2.stderr?.on("data", (data) => {
17774
18742
  stderr += data.toString();
17775
18743
  });
17776
- return new Promise((resolve9, reject) => {
18744
+ return new Promise((resolve10, reject) => {
17777
18745
  let settled = false;
17778
18746
  let sessionId = null;
17779
18747
  let announcedId = null;
@@ -17803,9 +18771,14 @@ async function startLegacy(req, knownPaths) {
17803
18771
  }
17804
18772
  }
17805
18773
  const worktreeIdentity = req.worktreeName ? await storeFor("codex").readIdentity(identity.filePath).catch(() => null) : null;
17806
- resolve9({
18774
+ if (req.worktreeName && !worktreeIdentity?.cwd) {
18775
+ reject(new AgentRuntimeError(409, "SESSION_UNCONFIRMED", "The native worktree directory is not available yet. Inspect the retained session before retrying."));
18776
+ return;
18777
+ }
18778
+ resolve10({
17807
18779
  sessionId: identity.sessionId,
17808
18780
  dirName: worktreeIdentity?.cwd ? descriptor3.dirName.encode(worktreeIdentity.cwd) : req.dirName,
18781
+ cwd: worktreeIdentity?.cwd ?? req.cwd,
17809
18782
  fileName: identity.fileName,
17810
18783
  filePath: identity.filePath,
17811
18784
  initialContent: content
@@ -17887,10 +18860,10 @@ async function sendLegacy(sessionId, req) {
17887
18860
  child2.stderr?.on("data", (data) => {
17888
18861
  stderr += data.toString();
17889
18862
  });
17890
- const completion = new Promise((resolve9) => {
18863
+ const completion = new Promise((resolve10) => {
17891
18864
  session.onResult = (result) => {
17892
18865
  session.onResult = null;
17893
- resolve9({
18866
+ resolve10({
17894
18867
  isError: result.is_error === true,
17895
18868
  message: result.result
17896
18869
  });
@@ -18124,6 +19097,8 @@ var codexRuntime = {
18124
19097
  kind: "codex",
18125
19098
  descriptor: descriptor3,
18126
19099
  async start(req) {
19100
+ req.signal?.throwIfAborted();
19101
+ req = await prepareManagedWorktree(req);
18127
19102
  validateContextWindow(req.contextWindowTokens);
18128
19103
  const knownPaths = new Set(
18129
19104
  (await storeFor("codex").listSessionFiles()).map((file) => file.filePath)
@@ -18131,7 +19106,7 @@ var codexRuntime = {
18131
19106
  if (req.worktreeName) return startLegacy(req, knownPaths);
18132
19107
  let threadStarted = false;
18133
19108
  try {
18134
- const options = executionOptions(req);
19109
+ const options = { ...executionOptions(req), beforeFirstTurn: (identity2) => beforeFirstTurn(req, identity2) };
18135
19110
  const started = await startCodexExecution(codexAppServer, options, (threadId) => {
18136
19111
  threadStarted = true;
18137
19112
  reportSessionId(req, threadId);
@@ -18143,14 +19118,17 @@ var codexRuntime = {
18143
19118
  initialContent = await readFile6(identity.filePath, "utf-8");
18144
19119
  } catch {
18145
19120
  }
19121
+ const initializedCwd = typeof started.thread.cwd === "string" ? started.thread.cwd : req.cwd;
18146
19122
  return {
18147
19123
  sessionId: identity.sessionId,
18148
- dirName: req.dirName,
19124
+ dirName: descriptor3.dirName.encode(initializedCwd),
19125
+ cwd: initializedCwd,
18149
19126
  fileName: identity.fileName,
18150
19127
  filePath: identity.filePath,
18151
19128
  initialContent
18152
19129
  };
18153
19130
  } catch (error2) {
19131
+ if (isStartupFailure(error2)) throw error2;
18154
19132
  if (threadStarted || !isCodexAppServerUnavailable(error2)) {
18155
19133
  throw new AgentRuntimeError(
18156
19134
  500,
@@ -18162,6 +19140,7 @@ var codexRuntime = {
18162
19140
  return startLegacy(req, knownPaths);
18163
19141
  },
18164
19142
  async send(sessionId, req) {
19143
+ if (isSessionStarting(sessionId)) return { delivery: "busy" };
18165
19144
  validateContextWindow(req.contextWindowTokens);
18166
19145
  const legacy = persistentSessions.get(sessionId);
18167
19146
  if (legacy && !legacy.dead) {
@@ -18321,7 +19300,7 @@ var codexRuntime = {
18321
19300
 
18322
19301
  // ../../server/agents/copilotTransport.ts
18323
19302
  import { spawn as spawnChild2 } from "node:child_process";
18324
- import { randomUUID as randomUUID6 } from "node:crypto";
19303
+ import { randomUUID as randomUUID7 } from "node:crypto";
18325
19304
  import {
18326
19305
  CancellationTokenSource,
18327
19306
  createMessageConnection,
@@ -18453,8 +19432,8 @@ var CopilotRuntime = class {
18453
19432
  }
18454
19433
  observeTurn(sessionId) {
18455
19434
  let observer;
18456
- const completion = new Promise((resolve9, reject) => {
18457
- observer = { resolve: resolve9, reject };
19435
+ const completion = new Promise((resolve10, reject) => {
19436
+ observer = { resolve: resolve10, reject };
18458
19437
  });
18459
19438
  const observers = this.turnObservers.get(sessionId) ?? /* @__PURE__ */ new Set();
18460
19439
  observers.add(observer);
@@ -18584,7 +19563,7 @@ var CopilotRuntime = class {
18584
19563
  }
18585
19564
  async createSession(options = {}) {
18586
19565
  options = await withPluginSkills(options);
18587
- const sessionId = options.sessionId ?? randomUUID6();
19566
+ const sessionId = options.sessionId ?? randomUUID7();
18588
19567
  this.assertSessionAvailable(sessionId);
18589
19568
  this.openingSessions.add(sessionId);
18590
19569
  try {
@@ -19021,7 +20000,7 @@ var CopilotRuntime = class {
19021
20000
  }
19022
20001
  waitForRequest(request, method, timeoutMs = this.transportTimeoutMs, onTimeout) {
19023
20002
  if (timeoutMs <= 0) return request;
19024
- return new Promise((resolve9, reject) => {
20003
+ return new Promise((resolve10, reject) => {
19025
20004
  const timer = setTimeout(() => {
19026
20005
  try {
19027
20006
  onTimeout?.();
@@ -19032,7 +20011,7 @@ var CopilotRuntime = class {
19032
20011
  request.then(
19033
20012
  (value) => {
19034
20013
  clearTimeout(timer);
19035
- resolve9(value);
20014
+ resolve10(value);
19036
20015
  },
19037
20016
  (error2) => {
19038
20017
  clearTimeout(timer);
@@ -19149,7 +20128,7 @@ var CopilotRuntime = class {
19149
20128
  throw new CopilotRuntimeError(`Copilot session ${params.sessionId} is not active`);
19150
20129
  }
19151
20130
  const sessionId = params.sessionId;
19152
- const requestId = typeof params.requestId === "string" ? params.requestId : randomUUID6();
20131
+ const requestId = typeof params.requestId === "string" ? params.requestId : randomUUID7();
19153
20132
  const choices = Array.isArray(params.choices) ? params.choices.filter((choice) => typeof choice === "string") : void 0;
19154
20133
  const value = {
19155
20134
  sessionId,
@@ -19159,10 +20138,10 @@ var CopilotRuntime = class {
19159
20138
  ...typeof params.allowFreeform === "boolean" ? { allowFreeform: params.allowFreeform } : {},
19160
20139
  askedAt: Date.now()
19161
20140
  };
19162
- return new Promise((resolve9, reject) => {
20141
+ return new Promise((resolve10, reject) => {
19163
20142
  this.pendingUserInputs.set(
19164
20143
  this.pendingInteractionKey(sessionId, requestId),
19165
- { value, resolve: resolve9, reject }
20144
+ { value, resolve: resolve10, reject }
19166
20145
  );
19167
20146
  });
19168
20147
  }
@@ -19171,7 +20150,7 @@ var CopilotRuntime = class {
19171
20150
  throw new CopilotRuntimeError("Invalid Copilot exit-plan request");
19172
20151
  }
19173
20152
  const sessionId = params.sessionId;
19174
- const requestId = randomUUID6();
20153
+ const requestId = randomUUID7();
19175
20154
  const actions = Array.isArray(params.actions) ? params.actions.filter((action) => typeof action === "string") : [];
19176
20155
  const value = {
19177
20156
  sessionId,
@@ -19182,10 +20161,10 @@ var CopilotRuntime = class {
19182
20161
  recommendedAction: typeof params.recommendedAction === "string" ? params.recommendedAction : actions[0] ?? "interactive",
19183
20162
  askedAt: Date.now()
19184
20163
  };
19185
- return new Promise((resolve9, reject) => {
20164
+ return new Promise((resolve10, reject) => {
19186
20165
  this.pendingExitPlans.set(
19187
20166
  this.pendingInteractionKey(sessionId, requestId),
19188
- { value, resolve: resolve9, reject }
20167
+ { value, resolve: resolve10, reject }
19189
20168
  );
19190
20169
  });
19191
20170
  }
@@ -19470,8 +20449,8 @@ function rolloutNamedThroughMs(filePath) {
19470
20449
  var FORK_COPY_MAX_GAP_MS = 1e3;
19471
20450
  function isForkedSessionMeta(payload) {
19472
20451
  if (typeof payload.forked_from_id === "string") return true;
19473
- const spawn4 = asRecord(asRecord(asRecord(payload.source)?.subagent)?.thread_spawn);
19474
- return typeof spawn4?.parent_thread_id === "string";
20452
+ const spawn5 = asRecord(asRecord(asRecord(payload.source)?.subagent)?.thread_spawn);
20453
+ return typeof spawn5?.parent_thread_id === "string";
19475
20454
  }
19476
20455
  function parseCodexUsageLine(line, state) {
19477
20456
  const record3 = parseJsonRecord(line);
@@ -19846,6 +20825,7 @@ var copilotRuntime2 = {
19846
20825
  kind: "copilot",
19847
20826
  descriptor: descriptor4,
19848
20827
  async start(req) {
20828
+ req.signal?.throwIfAborted();
19849
20829
  const sessionId = randomUUID3();
19850
20830
  reportSessionId(req, sessionId);
19851
20831
  let opened = false;
@@ -19862,6 +20842,7 @@ var copilotRuntime2 = {
19862
20842
  if (req.permissions?.mode) {
19863
20843
  await copilotRuntime.setPermissionMode(sessionId, allowAll(req.permissions.mode));
19864
20844
  }
20845
+ await beforeFirstTurn(req, { sessionId, cwd: req.cwd });
19865
20846
  const attachments = buildAttachments(req.images);
19866
20847
  await copilotRuntime.send(sessionId, {
19867
20848
  prompt: req.message || IMAGE_ONLY_PROMPT,
@@ -19875,10 +20856,10 @@ var copilotRuntime2 = {
19875
20856
  initialContent = await readFile6(filePath, "utf-8");
19876
20857
  break;
19877
20858
  } catch {
19878
- await new Promise((resolve9) => setTimeout(resolve9, TRANSCRIPT_POLL_INTERVAL_MS));
20859
+ await new Promise((resolve10) => setTimeout(resolve10, TRANSCRIPT_POLL_INTERVAL_MS));
19879
20860
  }
19880
20861
  }
19881
- return { sessionId, dirName: req.dirName, fileName, filePath, initialContent };
20862
+ return { sessionId, dirName: req.dirName, cwd: req.cwd, fileName, filePath, initialContent };
19882
20863
  } catch (error2) {
19883
20864
  if (opened) {
19884
20865
  if (copilotRuntime.isSessionActive(sessionId)) {
@@ -19888,6 +20869,7 @@ var copilotRuntime2 = {
19888
20869
  await copilotRuntime.deleteSession(sessionId).catch(() => {
19889
20870
  });
19890
20871
  }
20872
+ if (isStartupFailure(error2)) throw error2;
19891
20873
  throw new AgentRuntimeError(
19892
20874
  500,
19893
20875
  "SPAWN_FAILED",
@@ -19896,6 +20878,7 @@ var copilotRuntime2 = {
19896
20878
  }
19897
20879
  },
19898
20880
  async send(sessionId, req) {
20881
+ if (isSessionStarting(sessionId)) return { delivery: "busy" };
19899
20882
  if (req.onlyWhileRunning && !copilotRuntime.isTurnActive(sessionId)) return { delivery: "busy" };
19900
20883
  if (!copilotRuntime.isSessionActive(sessionId)) {
19901
20884
  await copilotRuntime.resumeSession(sessionId, {
@@ -20115,14 +21098,14 @@ var copilotRuntime2 = {
20115
21098
  // ../../server/agents/instanceRuntime.ts
20116
21099
  import { fork } from "node:child_process";
20117
21100
  import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
20118
- import { join as join17 } from "node:path";
21101
+ import { join as join18 } from "node:path";
20119
21102
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20120
21103
  function providerInstanceEnvironment(instance, dataRoot2) {
20121
21104
  const descriptor5 = descriptorFor(instance.agent);
20122
- const userRoot = join17(instance.homeDir, "user");
20123
- for (const directory of [userRoot, join17(userRoot, ".config"), join17(userRoot, ".cache"), join17(userRoot, ".local/share"), join17(userRoot, "tmp")]) mkdirSync4(directory, { recursive: true, mode: 448 });
21105
+ const userRoot = join18(instance.homeDir, "user");
21106
+ for (const directory of [userRoot, join18(userRoot, ".config"), join18(userRoot, ".cache"), join18(userRoot, ".local/share"), join18(userRoot, "tmp")]) mkdirSync4(directory, { recursive: true, mode: 448 });
20124
21107
  const env = Object.fromEntries(["PATH", "SystemRoot", "SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT", "LANG", "LC_ALL", "TZ", "COGPIT_PORT"].flatMap((key) => process.env[key] ? [[key, process.env[key]]] : []));
20125
- Object.assign(env, { HOME: userRoot, USERPROFILE: userRoot, XDG_CONFIG_HOME: join17(userRoot, ".config"), XDG_CACHE_HOME: join17(userRoot, ".cache"), XDG_DATA_HOME: join17(userRoot, ".local/share"), TMPDIR: join17(userRoot, "tmp"), TEMP: join17(userRoot, "tmp"), TMP: join17(userRoot, "tmp"), COGPIT_AGENT_WORKER: "1", COGPIT_AGENT_INSTANCE_ID: instance.id, COGPIT_WORKER_AGENT: instance.agent, COGPIT_WORKER_DATA_ROOT: join17(instance.homeDir, "cogpit"), COGPIT_WORKER_CONFIG_PATH: join17(instance.homeDir, "config.json"), COGPIT_ORCHESTRATION_ROOT: dataRoot2, ELECTRON_RUN_AS_NODE: "1" });
21108
+ Object.assign(env, { HOME: userRoot, USERPROFILE: userRoot, XDG_CONFIG_HOME: join18(userRoot, ".config"), XDG_CACHE_HOME: join18(userRoot, ".cache"), XDG_DATA_HOME: join18(userRoot, ".local/share"), TMPDIR: join18(userRoot, "tmp"), TEMP: join18(userRoot, "tmp"), TMP: join18(userRoot, "tmp"), COGPIT_AGENT_WORKER: "1", COGPIT_AGENT_INSTANCE_ID: instance.id, COGPIT_WORKER_AGENT: instance.agent, COGPIT_WORKER_DATA_ROOT: join18(instance.homeDir, "cogpit"), COGPIT_WORKER_CONFIG_PATH: join18(instance.homeDir, "config.json"), COGPIT_ORCHESTRATION_ROOT: dataRoot2, ELECTRON_RUN_AS_NODE: "1" });
20126
21109
  if (descriptor5.cli.homeEnvVar) env[descriptor5.cli.homeEnvVar] = instance.homeDir;
20127
21110
  if (descriptor5.cli.bundledBySdk) env.CLAUDE_CONFIG_DIR = instance.homeDir;
20128
21111
  env.COGPIT_BROWSER_HOME = browserHome();
@@ -20204,6 +21187,22 @@ function createInstanceRuntime(instance, dataRoot2) {
20204
21187
  }
20205
21188
  if (typeof message.id !== "number") return;
20206
21189
  const id = message.id;
21190
+ if (message.type === "startup-ready" && typeof message.cwd === "string") {
21191
+ const call3 = pending.get(id);
21192
+ const ready2 = call3?.beforeFirstTurn;
21193
+ void Promise.resolve().then(async () => {
21194
+ if (!ready2) throw new Error("Startup authorization is no longer available");
21195
+ await ready2({ cwd: message.cwd, nativeWorktree: message.nativeWorktree === true, sessionId: typeof message.sessionId === "string" ? qualified(message.sessionId) : void 0 });
21196
+ }).then(() => {
21197
+ if (child2 === worker && worker.connected) worker.send({ type: "startup-ready-result", id }, () => {
21198
+ });
21199
+ }, (error2) => {
21200
+ if (call3 && error2 instanceof Error) call3.startupError = error2;
21201
+ if (child2 === worker && worker.connected) worker.send({ type: "startup-ready-result", id, error: error2 instanceof Error ? error2.message : "Startup refused" }, () => {
21202
+ });
21203
+ });
21204
+ return;
21205
+ }
20207
21206
  if (message.type === "identity" && typeof message.sessionId === "string") {
20208
21207
  watched2.add(message.sessionId);
20209
21208
  const sessionId = qualified(message.sessionId);
@@ -20228,26 +21227,42 @@ function createInstanceRuntime(instance, dataRoot2) {
20228
21227
  if (!call2) return;
20229
21228
  pending.delete(id);
20230
21229
  clearTimeout(call2.timer);
20231
- if (typeof message.error === "string") call2.reject(new AgentRuntimeError(Number(message.status) || 502, typeof message.code === "string" ? message.code : "INSTANCE_ERROR", message.error));
21230
+ if (typeof message.error === "string") call2.reject(call2.startupError ?? new AgentRuntimeError(Number(message.status) || 502, typeof message.code === "string" ? message.code : "INSTANCE_ERROR", message.error));
20232
21231
  else call2.resolve(message.value);
20233
21232
  });
20234
21233
  return worker;
20235
21234
  }
20236
- async function call(method, args, identity, timeoutMs = 12e4) {
21235
+ async function call(method, args, identity, timeoutMs = 12e4, beforeFirstTurn2, signal) {
21236
+ signal?.throwIfAborted();
20237
21237
  if (pending.size >= 100) throw new AgentRuntimeError(429, "INSTANCE_BUSY", "Provider instance has too many pending operations");
20238
21238
  const worker = processFor();
20239
21239
  const id = ++nextId;
20240
- const value = await new Promise((resolve9, reject) => {
21240
+ const value = await new Promise((resolve10, reject) => {
20241
21241
  const timer = setTimeout(() => {
21242
+ const call2 = pending.get(id);
20242
21243
  pending.delete(id);
20243
- reject(new Error("Provider instance operation timed out; inspect session history before retrying"));
21244
+ if (method === "start") abort();
21245
+ call2?.reject(new Error("Provider instance operation timed out; inspect session history before retrying"));
20244
21246
  }, timeoutMs);
20245
- pending.set(id, { resolve: (value2) => resolve9(value2), reject, timer, identity });
21247
+ const abort = () => {
21248
+ if (worker.connected) worker.send({ type: "startup-abort", id }, () => {
21249
+ });
21250
+ };
21251
+ signal?.addEventListener("abort", abort, { once: true });
21252
+ if (signal?.aborted) abort();
21253
+ pending.set(id, { resolve: (value2) => {
21254
+ signal?.removeEventListener("abort", abort);
21255
+ resolve10(value2);
21256
+ }, reject: (error2) => {
21257
+ signal?.removeEventListener("abort", abort);
21258
+ reject(error2);
21259
+ }, timer, identity, beforeFirstTurn: beforeFirstTurn2 });
20246
21260
  worker.send({ id, method, args }, (error2) => {
20247
21261
  if (error2) {
20248
21262
  clearTimeout(timer);
21263
+ const call2 = pending.get(id);
20249
21264
  pending.delete(id);
20250
- reject(error2);
21265
+ call2?.reject(error2);
20251
21266
  }
20252
21267
  });
20253
21268
  });
@@ -20260,22 +21275,31 @@ function createInstanceRuntime(instance, dataRoot2) {
20260
21275
  descriptor: descriptor5,
20261
21276
  isBusy: () => pending.size > 0 || completions.size > 0 || active.length > 0,
20262
21277
  async start(request) {
20263
- const { value } = await call("start", [{ ...request, onSessionId: void 0, dirName: splitInstanceDirName(request.dirName).nativeDirName }], (id) => reportSessionId(request, id));
20264
- return { ...value, sessionId: qualified(value.sessionId), dirName: instanceDirName(instance.id, value.dirName) };
21278
+ let announced;
21279
+ try {
21280
+ const { value } = await call("start", [{ ...request, signal: void 0, beforeFirstTurn: Boolean(request.beforeFirstTurn), onSessionId: void 0, dirName: splitInstanceDirName(request.dirName).nativeDirName }], (id) => {
21281
+ announced = id;
21282
+ reportSessionId(request, id);
21283
+ }, request.beforeFirstTurn ? 24 * 60 * 6e4 : 12e4, request.beforeFirstTurn ? (identity) => beforeFirstTurn(request, identity) : void 0, request.signal);
21284
+ return { ...value, sessionId: qualified(value.sessionId), dirName: instanceDirName(instance.id, value.dirName) };
21285
+ } finally {
21286
+ if (announced) finishSessionStartup(announced);
21287
+ }
20265
21288
  },
20266
21289
  async send(sessionId, request) {
21290
+ if (isSessionStarting(sessionId)) return { delivery: "busy" };
20267
21291
  watched2.add(native(sessionId));
20268
21292
  const { id, value } = await call("send", [native(sessionId), request]);
20269
21293
  if (!value.hasCompletion) return value;
20270
- const completion = new Promise((resolve9, reject) => {
21294
+ const completion = new Promise((resolve10, reject) => {
20271
21295
  const early = earlyCompletions.get(id);
20272
21296
  if (early) {
20273
21297
  earlyCompletions.delete(id);
20274
21298
  if (early.error) reject(new Error(early.error));
20275
- else resolve9(early.value);
21299
+ else resolve10(early.value);
20276
21300
  return;
20277
21301
  }
20278
- completions.set(id, { resolve: resolve9, reject });
21302
+ completions.set(id, { resolve: resolve10, reject });
20279
21303
  });
20280
21304
  return { delivery: value.delivery, turnId: value.turnId, completion };
20281
21305
  },
@@ -20379,6 +21403,8 @@ await loadConfig();
20379
21403
  refreshDirs();
20380
21404
  installBundledSkills([kind]);
20381
21405
  var runtime = runtimeFor(kind);
21406
+ var startups = /* @__PURE__ */ new Map();
21407
+ var readiness = /* @__PURE__ */ new Map();
20382
21408
  var answers = /* @__PURE__ */ new Map();
20383
21409
  var methods = /* @__PURE__ */ new Set(["start", "send", "interrupt", "stop", "stopAll", "deleteSession", "respondToApproval", "respondToAllApprovals", "respondToPlan", "answerQuestion", "describeRuntime", "listModels", "liveUsageRecords", "fork", "shutdown", "snapshot", "commitAnswer"]);
20384
21410
  function send2(message) {
@@ -20386,18 +21412,18 @@ function send2(message) {
20386
21412
  }
20387
21413
  var skillRequestId = 0;
20388
21414
  var skillRequests = /* @__PURE__ */ new Map();
20389
- var unregisterSkills = registerPluginSkills((cwd) => new Promise((resolve9) => {
21415
+ var unregisterSkills = registerPluginSkills((cwd) => new Promise((resolve10) => {
20390
21416
  if (skillRequests.size >= 64) {
20391
- resolve9("");
21417
+ resolve10("");
20392
21418
  return;
20393
21419
  }
20394
21420
  const requestId = ++skillRequestId;
20395
21421
  const timer = setTimeout(() => {
20396
21422
  skillRequests.delete(requestId);
20397
- resolve9("");
21423
+ resolve10("");
20398
21424
  }, 5e3);
20399
21425
  timer.unref();
20400
- skillRequests.set(requestId, { resolve: resolve9, timer });
21426
+ skillRequests.set(requestId, { resolve: resolve10, timer });
20401
21427
  send2({ type: "plugin-skills", requestId, cwd });
20402
21428
  }));
20403
21429
  function snapshot() {
@@ -20423,6 +21449,19 @@ function publishSnapshot() {
20423
21449
  var poll = setInterval(publishSnapshot, 300);
20424
21450
  poll.unref();
20425
21451
  process.on("message", (message) => {
21452
+ if (isRecord(message) && typeof message.id === "number" && message.type === "startup-abort") {
21453
+ startups.get(message.id)?.abort(new Error("Session startup cancelled"));
21454
+ readiness.get(message.id)?.reject(new Error("Session startup cancelled"));
21455
+ readiness.delete(message.id);
21456
+ return;
21457
+ }
21458
+ if (isRecord(message) && typeof message.id === "number" && message.type === "startup-ready-result") {
21459
+ const pending = readiness.get(message.id);
21460
+ readiness.delete(message.id);
21461
+ if (typeof message.error === "string") pending?.reject(new Error(message.error));
21462
+ else pending?.resolve();
21463
+ return;
21464
+ }
20426
21465
  if (isRecord(message) && message.type === "plugin-skills-result" && typeof message.requestId === "number") {
20427
21466
  const request = skillRequests.get(message.requestId);
20428
21467
  if (request) {
@@ -20445,10 +21484,20 @@ process.on("message", (message) => {
20445
21484
  args[0] = new Map(args[0].map(([id2, models]) => [id2, new Map(models)]));
20446
21485
  args[1] = Array.isArray(args[1]) ? new Set(args[1]) : void 0;
20447
21486
  }
20448
- if (method === "start" && isRecord(args[0])) args[0].onSessionId = (sessionId) => {
20449
- watch2(sessionId);
20450
- send2({ type: "identity", id, sessionId });
20451
- };
21487
+ if (method === "start" && isRecord(args[0])) {
21488
+ args[0].onSessionId = (sessionId) => {
21489
+ watch2(sessionId);
21490
+ send2({ type: "identity", id, sessionId });
21491
+ };
21492
+ const controller = new AbortController();
21493
+ startups.set(id, controller);
21494
+ args[0].signal = controller.signal;
21495
+ if (args[0].beforeFirstTurn === true) args[0].beforeFirstTurn = (identity) => new Promise((resolve10, reject) => {
21496
+ readiness.set(id, { resolve: resolve10, reject });
21497
+ send2({ type: "startup-ready", id, ...identity });
21498
+ });
21499
+ else args[0].beforeFirstTurn = void 0;
21500
+ }
20452
21501
  if (typeof args[0] === "string") watch2(args[0]);
20453
21502
  const operation = runtime[method];
20454
21503
  if (typeof operation !== "function") throw new Error("Unsupported runtime operation");
@@ -20474,7 +21523,10 @@ process.on("message", (message) => {
20474
21523
  return { ...result, completion: void 0, hasCompletion: Boolean(completion) };
20475
21524
  }
20476
21525
  return result;
20477
- })().then((value) => send2({ id, value }), (error2) => send2({ id, error: error2 instanceof Error ? error2.message : "Instance operation failed", status: isRecord(error2) ? error2.status : void 0, code: isRecord(error2) ? error2.code : void 0 }));
21526
+ })().finally(() => {
21527
+ startups.delete(id);
21528
+ readiness.delete(id);
21529
+ }).then((value) => send2({ id, value }), (error2) => send2({ id, error: error2 instanceof Error ? error2.message : "Instance operation failed", status: isRecord(error2) ? error2.status : void 0, code: isRecord(error2) ? error2.code : void 0 }));
20478
21530
  });
20479
21531
  async function close() {
20480
21532
  clearInterval(poll);