openmausbot 0.1.72 → 0.1.73
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/assets/{index-BFPYw6P-.js → index-BLJ7byG3.js} +138 -138
- package/dist/assets/{index-Bpai86ua.js → index-BYbK7ber.js} +1 -1
- package/dist/assets/index-De2TRLxu.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/drivers/agents-proxy.js +2 -2
- package/dist-server/index.js +129 -31
- package/dist-server/openmausbot.js +50 -16
- package/dist-server/pair-cli.js +50 -16
- package/dist-server/server/drivers/agents-proxy.js +2 -2
- package/dist-server/server/drivers/codex.js +64 -16
- package/dist-server/server/index.js +25 -8
- package/dist-server/server/vps-computer.js +81 -12
- package/package.json +1 -1
- package/dist/assets/index-D1odUdTi.css +0 -1
package/dist-server/index.js
CHANGED
|
@@ -34774,6 +34774,10 @@ var CodexDriver = {
|
|
|
34774
34774
|
stdio: ["pipe", "pipe", "pipe"]
|
|
34775
34775
|
});
|
|
34776
34776
|
let abandoned = false;
|
|
34777
|
+
let codexThreadId = null;
|
|
34778
|
+
let codexTurnId = null;
|
|
34779
|
+
let startingNativeTurn = false;
|
|
34780
|
+
const earlyNotifications = [];
|
|
34777
34781
|
const state = {
|
|
34778
34782
|
settled: false,
|
|
34779
34783
|
lastText: "",
|
|
@@ -34807,6 +34811,18 @@ var CodexDriver = {
|
|
|
34807
34811
|
rpcPending.set(id, {
|
|
34808
34812
|
resolve: (v2) => {
|
|
34809
34813
|
clearTimeout(timer);
|
|
34814
|
+
if (method === "turn/start") {
|
|
34815
|
+
if (typeof v2?.turn?.id !== "string" || !v2.turn.id) {
|
|
34816
|
+
reject(new Error("Codex did not return a native turn id"));
|
|
34817
|
+
return;
|
|
34818
|
+
}
|
|
34819
|
+
codexTurnId = v2.turn.id;
|
|
34820
|
+
startingNativeTurn = false;
|
|
34821
|
+
for (const notification of earlyNotifications.splice(0)) {
|
|
34822
|
+
if (state.settled) break;
|
|
34823
|
+
handleNotification(notification);
|
|
34824
|
+
}
|
|
34825
|
+
}
|
|
34810
34826
|
resolve16(v2);
|
|
34811
34827
|
},
|
|
34812
34828
|
reject: (e) => {
|
|
@@ -34916,6 +34932,22 @@ var CodexDriver = {
|
|
|
34916
34932
|
};
|
|
34917
34933
|
const handleNotification = (msg) => {
|
|
34918
34934
|
const p2 = msg.params ?? {};
|
|
34935
|
+
const connectionError = msg.method === "error" && !("threadId" in p2) && !("turnId" in p2);
|
|
34936
|
+
if (!connectionError) {
|
|
34937
|
+
if (!codexThreadId || p2.threadId !== codexThreadId) return;
|
|
34938
|
+
if (!codexTurnId) {
|
|
34939
|
+
if (startingNativeTurn) {
|
|
34940
|
+
if (earlyNotifications.length >= 1024) {
|
|
34941
|
+
void settle(false, "too_many_events_before_turn_start");
|
|
34942
|
+
} else {
|
|
34943
|
+
earlyNotifications.push(msg);
|
|
34944
|
+
}
|
|
34945
|
+
}
|
|
34946
|
+
return;
|
|
34947
|
+
}
|
|
34948
|
+
const eventTurnId = msg.method === "turn/started" || msg.method === "turn/completed" ? p2.turn?.id : p2.turnId;
|
|
34949
|
+
if (eventTurnId !== codexTurnId) return;
|
|
34950
|
+
}
|
|
34919
34951
|
switch (msg.method) {
|
|
34920
34952
|
// token-level chat text; the item/completed frame follows with the
|
|
34921
34953
|
// whole message, so its delta is only a fallback when none streamed
|
|
@@ -35100,7 +35132,6 @@ var CodexDriver = {
|
|
|
35100
35132
|
}
|
|
35101
35133
|
autoAcceptPermissions = approvalMode === "full";
|
|
35102
35134
|
const cursor = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
35103
|
-
let codexThreadId = null;
|
|
35104
35135
|
let startedModel = null;
|
|
35105
35136
|
if (cursor) {
|
|
35106
35137
|
try {
|
|
@@ -35153,21 +35184,24 @@ var CodexDriver = {
|
|
|
35153
35184
|
...promptText ? [{ type: "text", text: promptText }] : [],
|
|
35154
35185
|
...(turn.images ?? []).map((image) => ({ type: "localImage", path: image.path }))
|
|
35155
35186
|
];
|
|
35156
|
-
const startTurn2 = () =>
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
|
|
35165
|
-
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35187
|
+
const startTurn2 = () => {
|
|
35188
|
+
startingNativeTurn = true;
|
|
35189
|
+
return request2("turn/start", {
|
|
35190
|
+
threadId: codexThreadId,
|
|
35191
|
+
input: turnInput,
|
|
35192
|
+
...approvalParams.turn,
|
|
35193
|
+
// Spread, not `effort: turn.effort ?? null`. Probed against
|
|
35194
|
+
// codex-cli 0.146.0: null is indistinguishable from an absent key
|
|
35195
|
+
// — both leave the thread's current effort alone, emitting no
|
|
35196
|
+
// thread/settings/updated, and thread/resume reads the old value
|
|
35197
|
+
// back. The app-server offers no way to clear a level either:
|
|
35198
|
+
// "" is rejected outright and thread/start takes no effort at
|
|
35199
|
+
// all. So a thread keeps the last level it was sent until it is
|
|
35200
|
+
// sent another, and choosing Default lands on the bot's next new
|
|
35201
|
+
// thread rather than the current one.
|
|
35202
|
+
...turn.effort ? { effort: turn.effort } : {}
|
|
35203
|
+
});
|
|
35204
|
+
};
|
|
35171
35205
|
try {
|
|
35172
35206
|
await startTurn2();
|
|
35173
35207
|
} catch (error51) {
|
|
@@ -51101,6 +51135,13 @@ var MANAGED_VPS_CONTAINER_NAME = /^openmausbot-vps-[a-z0-9]{1,12}-[a-f0-9]{12}$/
|
|
|
51101
51135
|
var IMAGE_ID = /^sha256:[a-f0-9]{64}$/i;
|
|
51102
51136
|
var PIDS_LIMIT2 = 512;
|
|
51103
51137
|
var SCREENSHOT_PATH = "/tmp/openmausbot-vps-preview.png";
|
|
51138
|
+
var SCREENSHOT_TRANSFER = `/opt/venv/bin/python -I -c 'import base64, io, sys
|
|
51139
|
+
from PIL import Image
|
|
51140
|
+
with Image.open(sys.argv[1]) as image:
|
|
51141
|
+
image.thumbnail((1280, 1280))
|
|
51142
|
+
output = io.BytesIO()
|
|
51143
|
+
image.convert("RGB").save(output, format="JPEG", quality=70)
|
|
51144
|
+
sys.stdout.write(base64.b64encode(output.getvalue()).decode("ascii"))' "$1" 2>/dev/null || { base64 < "$1" | tr -d "\\n"; }`;
|
|
51104
51145
|
var INTERNAL_VIEWER_PORT2 = 6901;
|
|
51105
51146
|
var VIEWER_VERSION = "1";
|
|
51106
51147
|
var lifecycleLocks = /* @__PURE__ */ new Map();
|
|
@@ -51114,6 +51155,9 @@ function vpsLifecycleBusy() {
|
|
|
51114
51155
|
var LOCK_ACQUIRE_TIMEOUT_MS = 5e3;
|
|
51115
51156
|
var STATUS_CACHE_TTL_MS = 1e4;
|
|
51116
51157
|
var statusCache = /* @__PURE__ */ new Map();
|
|
51158
|
+
var SCREENSHOT_BUDGET_MS = 45e3;
|
|
51159
|
+
var SCREENSHOT_CLEANUP_BUDGET_MS = 1e4;
|
|
51160
|
+
var pendingScreenshots = /* @__PURE__ */ new Map();
|
|
51117
51161
|
var viewerConnections = /* @__PURE__ */ new Map();
|
|
51118
51162
|
var desktopTunnels = /* @__PURE__ */ new Map();
|
|
51119
51163
|
function containerNamePart(botId) {
|
|
@@ -51448,6 +51492,8 @@ async function vpsComputerStatus(cfg2, botId, runner = defaultRunner) {
|
|
|
51448
51492
|
const key3 = vpsLockKey(cfg2, botId);
|
|
51449
51493
|
const cacheable = runner === defaultRunner && key3 !== null;
|
|
51450
51494
|
if (cacheable) {
|
|
51495
|
+
await pendingScreenshots.get(key3)?.catch(() => {
|
|
51496
|
+
});
|
|
51451
51497
|
const cached3 = statusCache.get(key3);
|
|
51452
51498
|
if (cached3 && cached3.expiresAt > Date.now()) return cached3.status;
|
|
51453
51499
|
}
|
|
@@ -51892,21 +51938,49 @@ async function vpsComputerScreenshot(cfg2, botId, runner = defaultRunner) {
|
|
|
51892
51938
|
const alias = vpsSshAlias(cfg2);
|
|
51893
51939
|
if (!alias) throw Object.assign(new Error("VPS is not configured"), { status: 409 });
|
|
51894
51940
|
const key3 = `${alias}:${vpsContainerName(botId)}`;
|
|
51941
|
+
const pending = pendingScreenshots.get(key3);
|
|
51942
|
+
if (pending) return pending;
|
|
51895
51943
|
const cacheable = runner === defaultRunner;
|
|
51896
|
-
|
|
51944
|
+
const deadline = Date.now() + SCREENSHOT_BUDGET_MS;
|
|
51945
|
+
const workDeadline = deadline - SCREENSHOT_CLEANUP_BUDGET_MS;
|
|
51946
|
+
let budgetExpired = false;
|
|
51947
|
+
const timeoutError = () => Object.assign(new Error("The VPS screen preview timed out. Retry the preview when the connection recovers."), { status: 504 });
|
|
51948
|
+
const boundedRunner = async (args, options = {}) => {
|
|
51949
|
+
const remaining = workDeadline - Date.now() - COMMAND_TIMEOUT_KILL_GRACE_MS;
|
|
51950
|
+
if (remaining <= 0) {
|
|
51951
|
+
budgetExpired = true;
|
|
51952
|
+
throw timeoutError();
|
|
51953
|
+
}
|
|
51954
|
+
try {
|
|
51955
|
+
const result = await runner(args, { ...options, timeoutMs: Math.min(options.timeoutMs ?? 12e4, remaining) });
|
|
51956
|
+
if (Date.now() >= workDeadline) {
|
|
51957
|
+
budgetExpired = true;
|
|
51958
|
+
throw timeoutError();
|
|
51959
|
+
}
|
|
51960
|
+
return result;
|
|
51961
|
+
} catch (error51) {
|
|
51962
|
+
if (Date.now() >= workDeadline - COMMAND_TIMEOUT_KILL_GRACE_MS) budgetExpired = true;
|
|
51963
|
+
throw budgetExpired ? timeoutError() : error51;
|
|
51964
|
+
}
|
|
51965
|
+
};
|
|
51966
|
+
const capture = withVpsLifecycleLock(key3, async () => {
|
|
51897
51967
|
const cached3 = cacheable ? statusCache.get(key3) : void 0;
|
|
51898
|
-
const status = cached3 && cached3.expiresAt > Date.now() ? cached3.status : await computeVpsComputerStatus(cfg2, botId,
|
|
51968
|
+
const status = cached3 && cached3.expiresAt > Date.now() ? cached3.status : await computeVpsComputerStatus(cfg2, botId, boundedRunner);
|
|
51969
|
+
if (budgetExpired) {
|
|
51970
|
+
if (cacheable) statusCache.delete(key3);
|
|
51971
|
+
throw timeoutError();
|
|
51972
|
+
}
|
|
51899
51973
|
if (!status.ready) {
|
|
51900
51974
|
if (cacheable) statusCache.delete(key3);
|
|
51901
51975
|
throw Object.assign(new Error(status.problem ?? "The VPS computer is not ready"), { status: 409 });
|
|
51902
51976
|
}
|
|
51903
|
-
if (cacheable) statusCache.set(key3, { status, expiresAt: Date.now() + STATUS_CACHE_TTL_MS });
|
|
51904
51977
|
const containerRef = status.container_id ?? status.container_name;
|
|
51905
51978
|
if (!CONTAINER_ID.test(containerRef) && !CONTAINER_NAME.test(containerRef)) {
|
|
51906
51979
|
throw Object.assign(new Error("the VPS container reference is malformed"), { status: 409 });
|
|
51907
51980
|
}
|
|
51981
|
+
let frame;
|
|
51908
51982
|
try {
|
|
51909
|
-
await
|
|
51983
|
+
await boundedRunner(
|
|
51910
51984
|
vpsDockerArgs(
|
|
51911
51985
|
alias,
|
|
51912
51986
|
cuaExecArgs(
|
|
@@ -51916,30 +51990,41 @@ async function vpsComputerScreenshot(cfg2, botId, runner = defaultRunner) {
|
|
|
51916
51990
|
),
|
|
51917
51991
|
{ timeoutMs: 3e4 }
|
|
51918
51992
|
);
|
|
51919
|
-
const encoded = (await
|
|
51993
|
+
const encoded = (await boundedRunner(vpsDockerArgs(alias, [
|
|
51920
51994
|
"exec",
|
|
51921
51995
|
"-u",
|
|
51922
51996
|
"cua",
|
|
51923
51997
|
"-e",
|
|
51924
51998
|
"HOME=/home/cua",
|
|
51925
51999
|
containerRef,
|
|
51926
|
-
"
|
|
51927
|
-
"-
|
|
52000
|
+
"sh",
|
|
52001
|
+
"-c",
|
|
52002
|
+
SCREENSHOT_TRANSFER,
|
|
52003
|
+
"openmausbot-preview",
|
|
51928
52004
|
SCREENSHOT_PATH
|
|
51929
52005
|
]), { timeoutMs: 3e4 })).stdout.trim();
|
|
51930
52006
|
const checked = wholeScreenshot(Buffer.from(encoded, "base64"));
|
|
51931
52007
|
if (!checked.ok) throw Object.assign(new Error("Cua Driver returned an incomplete VPS screenshot"), { status: 502 });
|
|
51932
|
-
|
|
52008
|
+
frame = { png: encoded, format: checked.mime === "image/jpeg" ? "jpeg" : "png" };
|
|
51933
52009
|
} catch (error51) {
|
|
51934
52010
|
if (cacheable) statusCache.delete(key3);
|
|
51935
52011
|
throw error51;
|
|
51936
52012
|
} finally {
|
|
51937
|
-
|
|
51938
|
-
|
|
52013
|
+
const cleanupMs = Math.min(5e3, deadline - Date.now() - COMMAND_TIMEOUT_KILL_GRACE_MS);
|
|
52014
|
+
if (cleanupMs > 0) await runner(vpsDockerArgs(alias, ["exec", "-u", "cua", containerRef, "rm", "-f", SCREENSHOT_PATH]), {
|
|
52015
|
+
timeoutMs: cleanupMs
|
|
51939
52016
|
}).catch(() => {
|
|
51940
52017
|
});
|
|
51941
52018
|
}
|
|
52019
|
+
if (cacheable) statusCache.set(key3, { status, expiresAt: Date.now() + STATUS_CACHE_TTL_MS });
|
|
52020
|
+
return frame;
|
|
51942
52021
|
});
|
|
52022
|
+
pendingScreenshots.set(key3, capture);
|
|
52023
|
+
try {
|
|
52024
|
+
return await capture;
|
|
52025
|
+
} finally {
|
|
52026
|
+
if (pendingScreenshots.get(key3) === capture) pendingScreenshots.delete(key3);
|
|
52027
|
+
}
|
|
51943
52028
|
}
|
|
51944
52029
|
|
|
51945
52030
|
// server/routines.ts
|
|
@@ -69491,6 +69576,7 @@ var localVmProvisionBusy = false;
|
|
|
69491
69576
|
var localVmModeChangeBusy = false;
|
|
69492
69577
|
var activeVpsThreads = /* @__PURE__ */ new Map();
|
|
69493
69578
|
var boxLifecycleBusyBots = /* @__PURE__ */ new Set();
|
|
69579
|
+
var vpsPreviewRequests = /* @__PURE__ */ new Map();
|
|
69494
69580
|
var orphanBoxLifecycleBusyIds = /* @__PURE__ */ new Set();
|
|
69495
69581
|
var boxInventoryRequestsBusyIds = /* @__PURE__ */ new Set();
|
|
69496
69582
|
var computerProviderConfigTransitions = /* @__PURE__ */ new Set();
|
|
@@ -69522,7 +69608,7 @@ function providerOperationConflict(provider) {
|
|
|
69522
69608
|
if (managedBoxOwners().some((owner2) => owner2.inUse)) {
|
|
69523
69609
|
return `stop active bot work and computer control before changing ${provider === "box" ? "the Box account" : "the VPS connection"}`;
|
|
69524
69610
|
}
|
|
69525
|
-
if (boxLifecycleBusyBots.size > 0) {
|
|
69611
|
+
if (boxLifecycleBusyBots.size > 0 || vpsPreviewRequests.size > 0) {
|
|
69526
69612
|
return "wait for cloud computer actions to finish before changing provider settings";
|
|
69527
69613
|
}
|
|
69528
69614
|
if (provider === "box") {
|
|
@@ -69568,10 +69654,13 @@ function claimManagedBoxMutation(instance) {
|
|
|
69568
69654
|
}
|
|
69569
69655
|
return claimBotComputerLifecycle(ownerBotId);
|
|
69570
69656
|
}
|
|
69571
|
-
function claimBotComputerLifecycle(botId) {
|
|
69657
|
+
function claimBotComputerLifecycle(botId, allowPreview = false) {
|
|
69572
69658
|
if (boxLifecycleBusyBots.has(botId)) {
|
|
69573
69659
|
throw Object.assign(new Error("this bot's cloud computer is being changed \u2014 wait for it to finish"), { status: 409 });
|
|
69574
69660
|
}
|
|
69661
|
+
if (!allowPreview && vpsPreviewRequests.has(botId)) {
|
|
69662
|
+
throw Object.assign(new Error("a screen preview is still refreshing \u2014 wait before changing this computer"), { status: 409 });
|
|
69663
|
+
}
|
|
69575
69664
|
boxLifecycleBusyBots.add(botId);
|
|
69576
69665
|
return () => boxLifecycleBusyBots.delete(botId);
|
|
69577
69666
|
}
|
|
@@ -73491,7 +73580,7 @@ var workspaceBackupRoutes = createWorkspaceBackupRoutes({
|
|
|
73491
73580
|
return Boolean(current2?.scopes.includes("admin") && current2.kind === original.kind && (current2.kind !== "session" || original.kind === "session" && current2.session.id === original.session.id));
|
|
73492
73581
|
},
|
|
73493
73582
|
exclusive: (work, keepLocked) => workspaceMaintenance.run(work, {
|
|
73494
|
-
idle: () => !providerFleetReloading && !providerAuthSessions.active && !browserEngineInstall && !routines?.isTicking && !calendarCalls?.isTicking && !localVmImageBusy && !localVmProvisionBusy && !localVmModeChangeBusy && !localVmLifecycleBusy.size && !boxLifecycleBusyBots.size && !orphanBoxLifecycleBusyIds.size && !computerProviderConfigTransitions.size && !checkpointRestoreLeases.size && store.bots.every((bot) => !botHasActiveTurn(bot.id) && !routines?.activeRunForBot(bot.id) && !computerControl.snapshot(bot.id).held) && store.groups.every((group) => !groupIsWorking(group)),
|
|
73583
|
+
idle: () => !providerFleetReloading && !providerAuthSessions.active && !browserEngineInstall && !routines?.isTicking && !calendarCalls?.isTicking && !localVmImageBusy && !localVmProvisionBusy && !localVmModeChangeBusy && !localVmLifecycleBusy.size && !boxLifecycleBusyBots.size && !vpsPreviewRequests.size && !orphanBoxLifecycleBusyIds.size && !computerProviderConfigTransitions.size && !checkpointRestoreLeases.size && store.bots.every((bot) => !botHasActiveTurn(bot.id) && !routines?.activeRunForBot(bot.id) && !computerControl.snapshot(bot.id).held) && store.groups.every((group) => !groupIsWorking(group)),
|
|
73495
73584
|
pause: () => {
|
|
73496
73585
|
routines?.stop();
|
|
73497
73586
|
calendarCalls?.stop();
|
|
@@ -78596,7 +78685,17 @@ data: ${JSON.stringify({ kind: "ping" })}
|
|
|
78596
78685
|
return json3(res, 409, { error: "this bot's cloud computer is being changed \u2014 wait for it to finish" });
|
|
78597
78686
|
}
|
|
78598
78687
|
if (bot.cloudBackend === "vps") {
|
|
78599
|
-
|
|
78688
|
+
if (m2[2] === "screenshot") {
|
|
78689
|
+
let preview = vpsPreviewRequests.get(botId);
|
|
78690
|
+
if (!preview) {
|
|
78691
|
+
preview = vpsComputerScreenshot(cfg, botId).finally(() => {
|
|
78692
|
+
vpsPreviewRequests.delete(botId);
|
|
78693
|
+
});
|
|
78694
|
+
vpsPreviewRequests.set(botId, preview);
|
|
78695
|
+
}
|
|
78696
|
+
return json3(res, 200, await preview);
|
|
78697
|
+
}
|
|
78698
|
+
const releaseComputerLifecycle2 = claimBotComputerLifecycle(botId, m2[2] === "join");
|
|
78600
78699
|
try {
|
|
78601
78700
|
if (m2[2] === "exec") {
|
|
78602
78701
|
return json3(res, 409, { error: "the VPS console is available to the bot through its scoped computer tools" });
|
|
@@ -78610,7 +78709,6 @@ data: ${JSON.stringify({ kind: "ping" })}
|
|
|
78610
78709
|
if (m2[2] === "join") {
|
|
78611
78710
|
return json3(res, 200, await vpsComputerJoin(cfg, botId));
|
|
78612
78711
|
}
|
|
78613
|
-
if (m2[2] === "screenshot") return json3(res, 200, await vpsComputerScreenshot(cfg, botId));
|
|
78614
78712
|
const action = m2[2] === "provision" ? "provision" : m2[2] === "remove" ? "remove" : "stop";
|
|
78615
78713
|
return json3(res, 200, await vpsComputerAction(action, cfg, botId));
|
|
78616
78714
|
} finally {
|
|
@@ -24762,6 +24762,10 @@ var init_codex = __esm({
|
|
|
24762
24762
|
stdio: ["pipe", "pipe", "pipe"]
|
|
24763
24763
|
});
|
|
24764
24764
|
let abandoned = false;
|
|
24765
|
+
let codexThreadId = null;
|
|
24766
|
+
let codexTurnId = null;
|
|
24767
|
+
let startingNativeTurn = false;
|
|
24768
|
+
const earlyNotifications = [];
|
|
24765
24769
|
const state = {
|
|
24766
24770
|
settled: false,
|
|
24767
24771
|
lastText: "",
|
|
@@ -24795,6 +24799,18 @@ var init_codex = __esm({
|
|
|
24795
24799
|
rpcPending.set(id, {
|
|
24796
24800
|
resolve: (v) => {
|
|
24797
24801
|
clearTimeout(timer);
|
|
24802
|
+
if (method === "turn/start") {
|
|
24803
|
+
if (typeof v?.turn?.id !== "string" || !v.turn.id) {
|
|
24804
|
+
reject(new Error("Codex did not return a native turn id"));
|
|
24805
|
+
return;
|
|
24806
|
+
}
|
|
24807
|
+
codexTurnId = v.turn.id;
|
|
24808
|
+
startingNativeTurn = false;
|
|
24809
|
+
for (const notification of earlyNotifications.splice(0)) {
|
|
24810
|
+
if (state.settled) break;
|
|
24811
|
+
handleNotification(notification);
|
|
24812
|
+
}
|
|
24813
|
+
}
|
|
24798
24814
|
resolve13(v);
|
|
24799
24815
|
},
|
|
24800
24816
|
reject: (e) => {
|
|
@@ -24904,6 +24920,22 @@ var init_codex = __esm({
|
|
|
24904
24920
|
};
|
|
24905
24921
|
const handleNotification = (msg) => {
|
|
24906
24922
|
const p = msg.params ?? {};
|
|
24923
|
+
const connectionError = msg.method === "error" && !("threadId" in p) && !("turnId" in p);
|
|
24924
|
+
if (!connectionError) {
|
|
24925
|
+
if (!codexThreadId || p.threadId !== codexThreadId) return;
|
|
24926
|
+
if (!codexTurnId) {
|
|
24927
|
+
if (startingNativeTurn) {
|
|
24928
|
+
if (earlyNotifications.length >= 1024) {
|
|
24929
|
+
void settle(false, "too_many_events_before_turn_start");
|
|
24930
|
+
} else {
|
|
24931
|
+
earlyNotifications.push(msg);
|
|
24932
|
+
}
|
|
24933
|
+
}
|
|
24934
|
+
return;
|
|
24935
|
+
}
|
|
24936
|
+
const eventTurnId = msg.method === "turn/started" || msg.method === "turn/completed" ? p.turn?.id : p.turnId;
|
|
24937
|
+
if (eventTurnId !== codexTurnId) return;
|
|
24938
|
+
}
|
|
24907
24939
|
switch (msg.method) {
|
|
24908
24940
|
// token-level chat text; the item/completed frame follows with the
|
|
24909
24941
|
// whole message, so its delta is only a fallback when none streamed
|
|
@@ -25088,7 +25120,6 @@ var init_codex = __esm({
|
|
|
25088
25120
|
}
|
|
25089
25121
|
autoAcceptPermissions = approvalMode === "full";
|
|
25090
25122
|
const cursor3 = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
25091
|
-
let codexThreadId = null;
|
|
25092
25123
|
let startedModel = null;
|
|
25093
25124
|
if (cursor3) {
|
|
25094
25125
|
try {
|
|
@@ -25141,21 +25172,24 @@ var init_codex = __esm({
|
|
|
25141
25172
|
...promptText ? [{ type: "text", text: promptText }] : [],
|
|
25142
25173
|
...(turn.images ?? []).map((image) => ({ type: "localImage", path: image.path }))
|
|
25143
25174
|
];
|
|
25144
|
-
const startTurn = () =>
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25175
|
+
const startTurn = () => {
|
|
25176
|
+
startingNativeTurn = true;
|
|
25177
|
+
return request("turn/start", {
|
|
25178
|
+
threadId: codexThreadId,
|
|
25179
|
+
input: turnInput,
|
|
25180
|
+
...approvalParams.turn,
|
|
25181
|
+
// Spread, not `effort: turn.effort ?? null`. Probed against
|
|
25182
|
+
// codex-cli 0.146.0: null is indistinguishable from an absent key
|
|
25183
|
+
// — both leave the thread's current effort alone, emitting no
|
|
25184
|
+
// thread/settings/updated, and thread/resume reads the old value
|
|
25185
|
+
// back. The app-server offers no way to clear a level either:
|
|
25186
|
+
// "" is rejected outright and thread/start takes no effort at
|
|
25187
|
+
// all. So a thread keeps the last level it was sent until it is
|
|
25188
|
+
// sent another, and choosing Default lands on the bot's next new
|
|
25189
|
+
// thread rather than the current one.
|
|
25190
|
+
...turn.effort ? { effort: turn.effort } : {}
|
|
25191
|
+
});
|
|
25192
|
+
};
|
|
25159
25193
|
try {
|
|
25160
25194
|
await startTurn();
|
|
25161
25195
|
} catch (error51) {
|
package/dist-server/pair-cli.js
CHANGED
|
@@ -24762,6 +24762,10 @@ var init_codex = __esm({
|
|
|
24762
24762
|
stdio: ["pipe", "pipe", "pipe"]
|
|
24763
24763
|
});
|
|
24764
24764
|
let abandoned = false;
|
|
24765
|
+
let codexThreadId = null;
|
|
24766
|
+
let codexTurnId = null;
|
|
24767
|
+
let startingNativeTurn = false;
|
|
24768
|
+
const earlyNotifications = [];
|
|
24765
24769
|
const state = {
|
|
24766
24770
|
settled: false,
|
|
24767
24771
|
lastText: "",
|
|
@@ -24795,6 +24799,18 @@ var init_codex = __esm({
|
|
|
24795
24799
|
rpcPending.set(id, {
|
|
24796
24800
|
resolve: (v) => {
|
|
24797
24801
|
clearTimeout(timer);
|
|
24802
|
+
if (method === "turn/start") {
|
|
24803
|
+
if (typeof v?.turn?.id !== "string" || !v.turn.id) {
|
|
24804
|
+
reject(new Error("Codex did not return a native turn id"));
|
|
24805
|
+
return;
|
|
24806
|
+
}
|
|
24807
|
+
codexTurnId = v.turn.id;
|
|
24808
|
+
startingNativeTurn = false;
|
|
24809
|
+
for (const notification of earlyNotifications.splice(0)) {
|
|
24810
|
+
if (state.settled) break;
|
|
24811
|
+
handleNotification(notification);
|
|
24812
|
+
}
|
|
24813
|
+
}
|
|
24798
24814
|
resolve13(v);
|
|
24799
24815
|
},
|
|
24800
24816
|
reject: (e) => {
|
|
@@ -24904,6 +24920,22 @@ var init_codex = __esm({
|
|
|
24904
24920
|
};
|
|
24905
24921
|
const handleNotification = (msg) => {
|
|
24906
24922
|
const p = msg.params ?? {};
|
|
24923
|
+
const connectionError = msg.method === "error" && !("threadId" in p) && !("turnId" in p);
|
|
24924
|
+
if (!connectionError) {
|
|
24925
|
+
if (!codexThreadId || p.threadId !== codexThreadId) return;
|
|
24926
|
+
if (!codexTurnId) {
|
|
24927
|
+
if (startingNativeTurn) {
|
|
24928
|
+
if (earlyNotifications.length >= 1024) {
|
|
24929
|
+
void settle(false, "too_many_events_before_turn_start");
|
|
24930
|
+
} else {
|
|
24931
|
+
earlyNotifications.push(msg);
|
|
24932
|
+
}
|
|
24933
|
+
}
|
|
24934
|
+
return;
|
|
24935
|
+
}
|
|
24936
|
+
const eventTurnId = msg.method === "turn/started" || msg.method === "turn/completed" ? p.turn?.id : p.turnId;
|
|
24937
|
+
if (eventTurnId !== codexTurnId) return;
|
|
24938
|
+
}
|
|
24907
24939
|
switch (msg.method) {
|
|
24908
24940
|
// token-level chat text; the item/completed frame follows with the
|
|
24909
24941
|
// whole message, so its delta is only a fallback when none streamed
|
|
@@ -25088,7 +25120,6 @@ var init_codex = __esm({
|
|
|
25088
25120
|
}
|
|
25089
25121
|
autoAcceptPermissions = approvalMode === "full";
|
|
25090
25122
|
const cursor3 = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
25091
|
-
let codexThreadId = null;
|
|
25092
25123
|
let startedModel = null;
|
|
25093
25124
|
if (cursor3) {
|
|
25094
25125
|
try {
|
|
@@ -25141,21 +25172,24 @@ var init_codex = __esm({
|
|
|
25141
25172
|
...promptText ? [{ type: "text", text: promptText }] : [],
|
|
25142
25173
|
...(turn.images ?? []).map((image) => ({ type: "localImage", path: image.path }))
|
|
25143
25174
|
];
|
|
25144
|
-
const startTurn = () =>
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25175
|
+
const startTurn = () => {
|
|
25176
|
+
startingNativeTurn = true;
|
|
25177
|
+
return request("turn/start", {
|
|
25178
|
+
threadId: codexThreadId,
|
|
25179
|
+
input: turnInput,
|
|
25180
|
+
...approvalParams.turn,
|
|
25181
|
+
// Spread, not `effort: turn.effort ?? null`. Probed against
|
|
25182
|
+
// codex-cli 0.146.0: null is indistinguishable from an absent key
|
|
25183
|
+
// — both leave the thread's current effort alone, emitting no
|
|
25184
|
+
// thread/settings/updated, and thread/resume reads the old value
|
|
25185
|
+
// back. The app-server offers no way to clear a level either:
|
|
25186
|
+
// "" is rejected outright and thread/start takes no effort at
|
|
25187
|
+
// all. So a thread keeps the last level it was sent until it is
|
|
25188
|
+
// sent another, and choosing Default lands on the bot's next new
|
|
25189
|
+
// thread rather than the current one.
|
|
25190
|
+
...turn.effort ? { effort: turn.effort } : {}
|
|
25191
|
+
});
|
|
25192
|
+
};
|
|
25159
25193
|
try {
|
|
25160
25194
|
await startTurn();
|
|
25161
25195
|
} catch (error51) {
|
|
@@ -531,7 +531,7 @@ const TOOLS = [
|
|
|
531
531
|
additionalProperties: false,
|
|
532
532
|
properties: {
|
|
533
533
|
action: { type: "string", enum: ["append", "replace", "remove", "supersede"] },
|
|
534
|
-
text: { type: "string", minLength: 1,
|
|
534
|
+
text: { type: "string", minLength: 1, description: "Non-blank new text for append, replace, or supersede: the fact itself, without a date or bullet. Omit for remove; use remove to delete a passage." },
|
|
535
535
|
old_text: { type: "string", minLength: 1, description: "Exact unique existing passage for replace, supersede, or remove. Omit for append." },
|
|
536
536
|
},
|
|
537
537
|
required: ["action"],
|
|
@@ -544,7 +544,7 @@ const TOOLS = [
|
|
|
544
544
|
type: "object",
|
|
545
545
|
additionalProperties: false,
|
|
546
546
|
properties: {
|
|
547
|
-
text: { type: "string", minLength: 1,
|
|
547
|
+
text: { type: "string", minLength: 1, description: "One line about what happened, in plain words." },
|
|
548
548
|
},
|
|
549
549
|
required: ["text"],
|
|
550
550
|
},
|
|
@@ -515,6 +515,10 @@ export const CodexDriver = {
|
|
|
515
515
|
stdio: ["pipe", "pipe", "pipe"],
|
|
516
516
|
});
|
|
517
517
|
let abandoned = false;
|
|
518
|
+
let codexThreadId = null;
|
|
519
|
+
let codexTurnId = null;
|
|
520
|
+
let startingNativeTurn = false;
|
|
521
|
+
const earlyNotifications = [];
|
|
518
522
|
const state = {
|
|
519
523
|
settled: false,
|
|
520
524
|
lastText: "",
|
|
@@ -553,6 +557,21 @@ export const CodexDriver = {
|
|
|
553
557
|
rpcPending.set(id, {
|
|
554
558
|
resolve: (v) => {
|
|
555
559
|
clearTimeout(timer);
|
|
560
|
+
if (method === "turn/start") {
|
|
561
|
+
if (typeof v?.turn?.id !== "string" || !v.turn.id) {
|
|
562
|
+
reject(new Error("Codex did not return a native turn id"));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
// Bind synchronously: a single stdout chunk can contain the
|
|
566
|
+
// response, streamed events, completion and a late request.
|
|
567
|
+
codexTurnId = v.turn.id;
|
|
568
|
+
startingNativeTurn = false;
|
|
569
|
+
for (const notification of earlyNotifications.splice(0)) {
|
|
570
|
+
if (state.settled)
|
|
571
|
+
break;
|
|
572
|
+
handleNotification(notification);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
556
575
|
resolve(v);
|
|
557
576
|
},
|
|
558
577
|
reject: (e) => {
|
|
@@ -710,6 +729,33 @@ export const CodexDriver = {
|
|
|
710
729
|
};
|
|
711
730
|
const handleNotification = (msg) => {
|
|
712
731
|
const p = msg.params ?? {};
|
|
732
|
+
// An app-server also emits notifications for native helper threads.
|
|
733
|
+
// Only this request's parent may write its transcript/usage or settle
|
|
734
|
+
// its run. Requests still use the approval broker above, including
|
|
735
|
+
// helper requests; ignoring child *notifications* must not grant tools.
|
|
736
|
+
const connectionError = msg.method === "error" &&
|
|
737
|
+
!("threadId" in p) && !("turnId" in p);
|
|
738
|
+
if (!connectionError) {
|
|
739
|
+
if (!codexThreadId || p.threadId !== codexThreadId)
|
|
740
|
+
return;
|
|
741
|
+
if (!codexTurnId) {
|
|
742
|
+
// Some servers stream before acknowledging turn/start. Retain a
|
|
743
|
+
// bounded prefix, then filter against the authoritative response.
|
|
744
|
+
if (startingNativeTurn) {
|
|
745
|
+
if (earlyNotifications.length >= 1024) {
|
|
746
|
+
void settle(false, "too_many_events_before_turn_start");
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
earlyNotifications.push(msg);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const eventTurnId = msg.method === "turn/started" || msg.method === "turn/completed"
|
|
755
|
+
? p.turn?.id : p.turnId;
|
|
756
|
+
if (eventTurnId !== codexTurnId)
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
713
759
|
switch (msg.method) {
|
|
714
760
|
// token-level chat text; the item/completed frame follows with the
|
|
715
761
|
// whole message, so its delta is only a fallback when none streamed
|
|
@@ -959,7 +1005,6 @@ export const CodexDriver = {
|
|
|
959
1005
|
// on start AND resume so Codex owns their lifetime through compaction.
|
|
960
1006
|
// Removed bot rules are cleared without dropping native configured rules.
|
|
961
1007
|
const cursor = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
962
|
-
let codexThreadId = null;
|
|
963
1008
|
let startedModel = null;
|
|
964
1009
|
if (cursor) {
|
|
965
1010
|
try {
|
|
@@ -1020,21 +1065,24 @@ export const CodexDriver = {
|
|
|
1020
1065
|
...(promptText ? [{ type: "text", text: promptText }] : []),
|
|
1021
1066
|
...(turn.images ?? []).map((image) => ({ type: "localImage", path: image.path })),
|
|
1022
1067
|
];
|
|
1023
|
-
const startTurn = () =>
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1068
|
+
const startTurn = () => {
|
|
1069
|
+
startingNativeTurn = true;
|
|
1070
|
+
return request("turn/start", {
|
|
1071
|
+
threadId: codexThreadId,
|
|
1072
|
+
input: turnInput,
|
|
1073
|
+
...approvalParams.turn,
|
|
1074
|
+
// Spread, not `effort: turn.effort ?? null`. Probed against
|
|
1075
|
+
// codex-cli 0.146.0: null is indistinguishable from an absent key
|
|
1076
|
+
// — both leave the thread's current effort alone, emitting no
|
|
1077
|
+
// thread/settings/updated, and thread/resume reads the old value
|
|
1078
|
+
// back. The app-server offers no way to clear a level either:
|
|
1079
|
+
// "" is rejected outright and thread/start takes no effort at
|
|
1080
|
+
// all. So a thread keeps the last level it was sent until it is
|
|
1081
|
+
// sent another, and choosing Default lands on the bot's next new
|
|
1082
|
+
// thread rather than the current one.
|
|
1083
|
+
...(turn.effort ? { effort: turn.effort } : {}),
|
|
1084
|
+
});
|
|
1085
|
+
};
|
|
1038
1086
|
try {
|
|
1039
1087
|
await startTurn();
|
|
1040
1088
|
}
|