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.
- package/dist/cli-runtime.js +39279 -35071
- package/dist/instance-worker.js +1206 -154
- package/dist/web/assets/AutomationsPanel-D7XmgTgZ.js +1 -0
- package/dist/web/assets/{CogpitBlock-ZgOubpsS.js → CogpitBlock-CiS2M1Q_.js} +16 -16
- package/dist/web/assets/CommandPaletteHost-Dj06VjlP.js +1 -0
- package/dist/web/assets/ConfigBrowser-DqQo1F4o.js +1 -0
- package/dist/web/assets/CrewPanel-DzLPmS6o.js +1 -0
- package/dist/web/assets/HighlightedEditor-DPrSCx1c.js +2 -0
- package/dist/web/assets/KeyboardShortcutsDialog-BdXPWBJw.js +1 -0
- package/dist/web/assets/MissionHome-CTcx5vR7.js +2 -0
- package/dist/web/assets/MobileFileChanges-Ngv9AlhR.js +1 -0
- package/dist/web/assets/PluginsDialog-CWRSNNll.js +1 -0
- package/dist/web/assets/PreviewPanel-CQcJIdef.js +1 -0
- package/dist/web/assets/ProjectFilesPanel-D5EKIBD7.js +4 -0
- package/dist/web/assets/ProjectSwitcherModal-BoSCOiv9.js +1 -0
- package/dist/web/assets/{TerminalOutput-DOX29EmA.js → TerminalOutput-DYZJD2WR.js} +3 -3
- package/dist/web/assets/ThemeSelectorModal-8sO11Ob-.js +1 -0
- package/dist/web/assets/WorkflowsPanel-qv6TkN7g.js +1 -0
- package/dist/web/assets/WorktreePanel-Dr82TWOP.js +1 -0
- package/dist/web/assets/{bash-Yzrsuije.js → bash-DgJyDXW6.js} +1 -1
- package/dist/web/assets/{c-BIGW1oBm.js → c-BG-tZi7o.js} +1 -1
- package/dist/web/assets/cpp-BzPMphJ4.js +1 -0
- package/dist/web/assets/{css-DPfMkruS.js → css-PZBBrkI0.js} +1 -1
- package/dist/web/assets/edition-ui-DQHX5fm4.js +2 -0
- package/dist/web/assets/github-dark-BQV5ugrc.js +1 -0
- package/dist/web/assets/github-light-Rcb2q3L5.js +1 -0
- package/dist/web/assets/{go-CxLEBnE3.js → go-BBr2UQCn.js} +1 -1
- package/dist/web/assets/{html-GMplVEZG.js → html-DOf0XnR2.js} +1 -1
- package/dist/web/assets/index-BGSPI7dK.js +1 -0
- package/dist/web/assets/index-BVmNox_3.js +504 -0
- package/dist/web/assets/index-CHtH7TlZ.js +2 -0
- package/dist/web/assets/index-DKDki7Rz.css +1 -0
- package/dist/web/assets/index-DU4gWgPu.js +3 -0
- package/dist/web/assets/{index-By5-_P50.js → index-zhwBlO_8.js} +4 -4
- package/dist/web/assets/{java-CylS5w8V.js → java-BTDmaRNO.js} +1 -1
- package/dist/web/assets/{javascript-wDzz0qaB.js → javascript-WyGyM1wV.js} +1 -1
- package/dist/web/assets/{json-Cp-IABpG.js → json-B8JryHcM.js} +1 -1
- package/dist/web/assets/{jsx-g9-lgVsj.js → jsx-8cLv2Six.js} +1 -1
- package/dist/web/assets/{kotlin-BdnUsdx6.js → kotlin-BQy537ds.js} +1 -1
- package/dist/web/assets/{markdown-Cvjx9yec.js → markdown-DX-TNHnc.js} +1 -1
- package/dist/web/assets/{php-aq0itWAE.js → php-47JHu45N.js} +1 -1
- package/dist/web/assets/{python-B6aJPvgy.js → python-B6Ihcp-E.js} +1 -1
- package/dist/web/assets/render.worker-DW0Lo4fc.js +154 -0
- package/dist/web/assets/ruby-BrGyNOch.js +1 -0
- package/dist/web/assets/{rust-B1yitclQ.js → rust-CxuBE0J8.js} +1 -1
- package/dist/web/assets/{scss-OYdSNvt2.js → scss-DxBkwadj.js} +1 -1
- package/dist/web/assets/session-parser.worker-DCsmYUeX.js +41 -0
- package/dist/web/assets/{sql-BLtJtn59.js → sql-BbONfI8K.js} +1 -1
- package/dist/web/assets/svelte-H3swLp47.js +1 -0
- package/dist/web/assets/{swift-D82vCrfD.js → swift-tEesUtni.js} +1 -1
- package/dist/web/assets/switch-gTnbAJaZ.js +1 -0
- package/dist/web/assets/{toml-vGWfd6FD.js → toml-Ms459DBw.js} +1 -1
- package/dist/web/assets/{tsx-COt5Ahok.js → tsx-D6116Ycy.js} +1 -1
- package/dist/web/assets/{typescript-BPQ3VLAy.js → typescript-BqvgUolh.js} +1 -1
- package/dist/web/assets/vendor-markdown-BEJ8yVzA.js +29 -0
- package/dist/web/assets/{vendor-ui-DQeMoSiV.js → vendor-ui-CKjyvB3_.js} +226 -191
- package/dist/web/assets/vue-CWSgrUPf.js +1 -0
- package/dist/web/assets/{xml-D2mA2LmW.js → xml-CdYS3kUe.js} +1 -1
- package/dist/web/assets/{yaml-Buea-lGh.js → yaml-Bopit2L3.js} +1 -1
- package/dist/web/index.html +4 -5
- package/package.json +1 -1
- package/dist/web/assets/CommandPaletteHost-Djn2jqYR.js +0 -1
- package/dist/web/assets/ConfigBrowser-CRwh8BXq.js +0 -1
- package/dist/web/assets/CrewPanel-BxPl5D7h.js +0 -1
- package/dist/web/assets/HighlightedEditor-D9A-dW2Z.js +0 -2
- package/dist/web/assets/KeyboardShortcutsDialog-L7rRhApI.js +0 -1
- package/dist/web/assets/MissionHome-zp7VebSO.js +0 -2
- package/dist/web/assets/MobileFileChanges-O-SvMq9F.js +0 -1
- package/dist/web/assets/PluginsDialog-CBOyfoUX.js +0 -1
- package/dist/web/assets/PreviewPanel-_pKuUaps.js +0 -1
- package/dist/web/assets/ProjectFilesPanel-BsbpTgWK.js +0 -4
- package/dist/web/assets/ProjectSwitcherModal-D4MzdPVH.js +0 -1
- package/dist/web/assets/ThemeSelectorModal-7CWXZB-z.js +0 -1
- package/dist/web/assets/WorkflowsPanel-aOz1o3kA.js +0 -1
- package/dist/web/assets/WorktreePanel-BFve9bpC.js +0 -1
- package/dist/web/assets/cpp-BRuaLJcg.js +0 -1
- package/dist/web/assets/edition-ui-CFxEM3WF.js +0 -2
- package/dist/web/assets/github-dark-DHJKELXO.js +0 -1
- package/dist/web/assets/github-light-DAi9KRSo.js +0 -1
- package/dist/web/assets/index-CPTFl4Qh.js +0 -498
- package/dist/web/assets/index-DjpYbGVZ.js +0 -2
- package/dist/web/assets/index-DmMg-zf3.js +0 -1
- package/dist/web/assets/index-hGMtuUNI.css +0 -1
- package/dist/web/assets/ruby-B0Tyh1qT.js +0 -1
- package/dist/web/assets/session-parser.worker-3tFlB6dy.js +0 -31
- package/dist/web/assets/svelte-DZMnMd3x.js +0 -1
- package/dist/web/assets/switch-DS7UyNS2.js +0 -1
- package/dist/web/assets/vendor-markdown-BE1gYnO9.js +0 -37
- package/dist/web/assets/vendor-shiki-DOn6sF-i.js +0 -144
- package/dist/web/assets/vue-DVfSmUF7.js +0 -1
package/dist/instance-worker.js
CHANGED
|
@@ -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
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
3680
|
+
}
|
|
3681
|
+
function unescapeAttribute(value) {
|
|
3682
|
+
return value.replace(/ /g, "\n").replace(/ /g, "\r").replace(/</g, "<").replace(/"/g, '"').replace(/&/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
|
|
3650
|
-
const movedTo =
|
|
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 =
|
|
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 >
|
|
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=
|
|
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((
|
|
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
|
-
|
|
8439
|
+
resolve10({ code: null, stdout, stderr, timedOut });
|
|
7637
8440
|
});
|
|
7638
8441
|
child2.on("close", (code) => {
|
|
7639
8442
|
clearTimeout(timer);
|
|
7640
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
8778
|
-
observer = { turn, resolve:
|
|
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((
|
|
9616
|
+
return new Promise((resolve10) => {
|
|
8814
9617
|
const pending = {
|
|
8815
9618
|
input,
|
|
8816
9619
|
askedAt: Date.now(),
|
|
8817
|
-
resolve:
|
|
9620
|
+
resolve: resolve10
|
|
8818
9621
|
};
|
|
8819
9622
|
state.pendingUserQuestions.set(toolUseId, pending);
|
|
8820
9623
|
const onAbort = () => {
|
|
8821
9624
|
if (state.pendingUserQuestions.delete(toolUseId)) {
|
|
8822
|
-
|
|
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((
|
|
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:
|
|
9654
|
+
resolve: resolve10
|
|
8852
9655
|
};
|
|
8853
9656
|
state.pendingPermissions.set(requestId, pending);
|
|
8854
9657
|
const onAbort = () => {
|
|
8855
9658
|
if (state.pendingPermissions.delete(requestId)) {
|
|
8856
|
-
|
|
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((
|
|
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:
|
|
9682
|
+
resolve: resolve10
|
|
8880
9683
|
});
|
|
8881
9684
|
const onAbort = () => {
|
|
8882
|
-
if (state.pendingElicitations.delete(requestId))
|
|
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((
|
|
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:
|
|
9718
|
+
resolve: resolve10
|
|
8916
9719
|
});
|
|
8917
9720
|
const onAbort = () => {
|
|
8918
|
-
if (state.pendingUserDialogs.delete(requestId))
|
|
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
|
|
9119
|
-
if (
|
|
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
|
|
9128
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
10685
|
+
const completion = new Promise((resolve10) => {
|
|
9826
10686
|
state.onResult = (result) => {
|
|
9827
10687
|
state.onResult = null;
|
|
9828
|
-
|
|
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.
|
|
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((
|
|
17818
|
+
return new Promise((resolve10, reject) => {
|
|
16853
17819
|
const waiting = this.completionWaiters.get(key) ?? [];
|
|
16854
|
-
waiting.push({ resolve:
|
|
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((
|
|
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) =>
|
|
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((
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
17278
|
-
resolveClose =
|
|
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
|
|
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 =
|
|
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
|
|
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 || !
|
|
17618
|
-
const root =
|
|
17619
|
-
const filePath =
|
|
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}`) ||
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
18863
|
+
const completion = new Promise((resolve10) => {
|
|
17891
18864
|
session.onResult = (result) => {
|
|
17892
18865
|
session.onResult = null;
|
|
17893
|
-
|
|
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:
|
|
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
|
|
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((
|
|
18457
|
-
observer = { resolve:
|
|
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 ??
|
|
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((
|
|
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
|
-
|
|
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 :
|
|
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((
|
|
20141
|
+
return new Promise((resolve10, reject) => {
|
|
19163
20142
|
this.pendingUserInputs.set(
|
|
19164
20143
|
this.pendingInteractionKey(sessionId, requestId),
|
|
19165
|
-
{ value, resolve:
|
|
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 =
|
|
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((
|
|
20164
|
+
return new Promise((resolve10, reject) => {
|
|
19186
20165
|
this.pendingExitPlans.set(
|
|
19187
20166
|
this.pendingInteractionKey(sessionId, requestId),
|
|
19188
|
-
{ value, resolve:
|
|
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
|
|
19474
|
-
return typeof
|
|
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((
|
|
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
|
|
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 =
|
|
20123
|
-
for (const directory of [userRoot,
|
|
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:
|
|
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((
|
|
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
|
-
|
|
21244
|
+
if (method === "start") abort();
|
|
21245
|
+
call2?.reject(new Error("Provider instance operation timed out; inspect session history before retrying"));
|
|
20244
21246
|
}, timeoutMs);
|
|
20245
|
-
|
|
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
|
-
|
|
20264
|
-
|
|
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((
|
|
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
|
|
21299
|
+
else resolve10(early.value);
|
|
20276
21300
|
return;
|
|
20277
21301
|
}
|
|
20278
|
-
completions.set(id, { resolve:
|
|
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((
|
|
21415
|
+
var unregisterSkills = registerPluginSkills((cwd) => new Promise((resolve10) => {
|
|
20390
21416
|
if (skillRequests.size >= 64) {
|
|
20391
|
-
|
|
21417
|
+
resolve10("");
|
|
20392
21418
|
return;
|
|
20393
21419
|
}
|
|
20394
21420
|
const requestId = ++skillRequestId;
|
|
20395
21421
|
const timer = setTimeout(() => {
|
|
20396
21422
|
skillRequests.delete(requestId);
|
|
20397
|
-
|
|
21423
|
+
resolve10("");
|
|
20398
21424
|
}, 5e3);
|
|
20399
21425
|
timer.unref();
|
|
20400
|
-
skillRequests.set(requestId, { resolve:
|
|
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]))
|
|
20449
|
-
|
|
20450
|
-
|
|
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
|
-
})().
|
|
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);
|