@supercorks/krnl 0.1.4 → 0.1.7
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/README.md +33 -0
- package/dist/kernel-companion.mjs +944 -400
- package/package.json +1 -1
|
@@ -15612,7 +15612,7 @@ function date4(params) {
|
|
|
15612
15612
|
config(en_default());
|
|
15613
15613
|
|
|
15614
15614
|
// src/hooks.ts
|
|
15615
|
-
import { readdir
|
|
15615
|
+
import { readdir } from "node:fs/promises";
|
|
15616
15616
|
import { join as join2 } from "node:path";
|
|
15617
15617
|
import { homedir as homedir2 } from "node:os";
|
|
15618
15618
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -15626,7 +15626,8 @@ import { dirname, join } from "node:path";
|
|
|
15626
15626
|
import { homedir } from "node:os";
|
|
15627
15627
|
import { createHash, randomUUID } from "node:crypto";
|
|
15628
15628
|
var dataRoot = join(homedir(), "Library/Application Support/Kernel/Codex");
|
|
15629
|
-
var
|
|
15629
|
+
var executeFile = promisify(execFile);
|
|
15630
|
+
var execute = (file2, args, options = {}) => executeFile(file2, args, { timeout: 1e4, maxBuffer: 4e6, ...options, encoding: "utf8" });
|
|
15630
15631
|
function pairingDirectory(id) {
|
|
15631
15632
|
return join(dataRoot, "machines", external_exports.uuid().parse(id));
|
|
15632
15633
|
}
|
|
@@ -15793,6 +15794,16 @@ var hookEvents = [
|
|
|
15793
15794
|
"PostToolUse",
|
|
15794
15795
|
"SessionEnd"
|
|
15795
15796
|
];
|
|
15797
|
+
var observationSchema = external_exports.object({
|
|
15798
|
+
threadId: external_exports.uuid(),
|
|
15799
|
+
turnId: external_exports.string().max(200).nullable(),
|
|
15800
|
+
event: external_exports.enum(hookEvents),
|
|
15801
|
+
tool: external_exports.string().max(200).nullable(),
|
|
15802
|
+
at: external_exports.number().finite().nonnegative(),
|
|
15803
|
+
pid: external_exports.number().int().positive(),
|
|
15804
|
+
identity: external_exports.string().max(500).optional(),
|
|
15805
|
+
draft: external_exports.object({ launchId: external_exports.uuid(), cwd: external_exports.string().min(1).max(4096) }).optional()
|
|
15806
|
+
});
|
|
15796
15807
|
function observation(raw, pid, at2 = performance.timeOrigin + performance.now()) {
|
|
15797
15808
|
const input = external_exports.object({
|
|
15798
15809
|
session_id: external_exports.uuid(),
|
|
@@ -15916,23 +15927,19 @@ async function captureHook(raw) {
|
|
|
15916
15927
|
const machines = await readdir(join2(dataRoot, "machines")).catch(() => []);
|
|
15917
15928
|
for (const machineId of machines) {
|
|
15918
15929
|
if (!external_exports.uuid().safeParse(machineId).success) continue;
|
|
15919
|
-
const directory = join2(dataRoot, "machines", machineId);
|
|
15920
|
-
const index = await readJson(join2(directory, "threads.json"));
|
|
15921
|
-
let captured = event;
|
|
15922
|
-
if (!index?.[event.threadId]) {
|
|
15923
|
-
const drafts = await readJson(join2(directory, "drafts.json"));
|
|
15924
|
-
const match = drafts && await matchDraftSubmission(raw, drafts);
|
|
15925
|
-
if (!match) continue;
|
|
15926
|
-
captured = { ...event, draft: { launchId: match.launchId, cwd: match.cwd } };
|
|
15927
|
-
}
|
|
15928
|
-
const events = join2(directory, "events");
|
|
15929
|
-
await mkdir2(events, { recursive: true, mode: 448 });
|
|
15930
|
-
const file2 = await open2(join2(events, `${event.at}-${randomUUID2()}.json`), "wx", 384);
|
|
15931
15930
|
try {
|
|
15932
|
-
|
|
15933
|
-
await
|
|
15934
|
-
|
|
15935
|
-
|
|
15931
|
+
const directory = join2(dataRoot, "machines", machineId);
|
|
15932
|
+
const index = await readJson(join2(directory, "threads.json"));
|
|
15933
|
+
let captured = event;
|
|
15934
|
+
if (!index?.[event.threadId]) {
|
|
15935
|
+
const drafts = await readJson(join2(directory, "drafts.json"));
|
|
15936
|
+
const match = drafts && await matchDraftSubmission(raw, drafts);
|
|
15937
|
+
if (!match) continue;
|
|
15938
|
+
captured = { ...event, draft: { launchId: match.launchId, cwd: match.cwd } };
|
|
15939
|
+
}
|
|
15940
|
+
const events = join2(directory, "events");
|
|
15941
|
+
await writeJson(join2(events, `${event.at}-${randomUUID2()}.json`), captured);
|
|
15942
|
+
} catch {
|
|
15936
15943
|
}
|
|
15937
15944
|
}
|
|
15938
15945
|
}
|
|
@@ -15947,21 +15954,48 @@ function kernelOrigin(raw) {
|
|
|
15947
15954
|
return url2.origin;
|
|
15948
15955
|
}
|
|
15949
15956
|
var KernelConnectionError = class extends Error {
|
|
15950
|
-
constructor(status2) {
|
|
15957
|
+
constructor(status2, code, retryAfterMs) {
|
|
15951
15958
|
super(
|
|
15952
|
-
status2 === 401
|
|
15959
|
+
status2 === 401 || status2 === 403 ? "The connection is no longer authorized. Run krnl login, then krnl connect codex." : code === "codex_stale_connection" ? "This connection was replaced. Run krnl connect codex to repair setup." : `Kernel rejected a companion delivery (${code ?? status2}). Run krnl status for recovery details.`
|
|
15953
15960
|
);
|
|
15954
15961
|
this.status = status2;
|
|
15962
|
+
this.code = code;
|
|
15963
|
+
this.retryAfterMs = retryAfterMs;
|
|
15955
15964
|
}
|
|
15956
15965
|
status;
|
|
15966
|
+
code;
|
|
15967
|
+
retryAfterMs;
|
|
15968
|
+
get fatal() {
|
|
15969
|
+
return [401, 403].includes(this.status) || this.code === "codex_stale_connection";
|
|
15970
|
+
}
|
|
15971
|
+
get retryable() {
|
|
15972
|
+
return this.code === "codex_disabled" || this.status === 408 || this.status === 429 || this.status >= 500;
|
|
15973
|
+
}
|
|
15957
15974
|
};
|
|
15958
15975
|
async function postKernel(origin, operation, payload, credential) {
|
|
15976
|
+
const controller = new AbortController();
|
|
15977
|
+
let timer;
|
|
15978
|
+
try {
|
|
15979
|
+
return await Promise.race([
|
|
15980
|
+
requestKernel(origin, operation, payload, credential, controller.signal),
|
|
15981
|
+
new Promise((_2, reject) => {
|
|
15982
|
+
timer = setTimeout(() => {
|
|
15983
|
+
controller.abort();
|
|
15984
|
+
reject(new Error("Kernel did not acknowledge the request within ten seconds"));
|
|
15985
|
+
}, 1e4);
|
|
15986
|
+
})
|
|
15987
|
+
]);
|
|
15988
|
+
} finally {
|
|
15989
|
+
clearTimeout(timer);
|
|
15990
|
+
}
|
|
15991
|
+
}
|
|
15992
|
+
async function requestKernel(origin, operation, payload, credential, signal) {
|
|
15959
15993
|
const response = await fetch(
|
|
15960
15994
|
`${kernelOrigin(origin)}/api/integrations/codex/companion/${operation}`,
|
|
15961
15995
|
{
|
|
15962
15996
|
method: "POST",
|
|
15963
15997
|
redirect: "error",
|
|
15964
|
-
signal
|
|
15998
|
+
signal,
|
|
15965
15999
|
headers: {
|
|
15966
16000
|
"content-type": "application/json",
|
|
15967
16001
|
...credential ? {
|
|
@@ -15973,10 +16007,20 @@ async function postKernel(origin, operation, payload, credential) {
|
|
|
15973
16007
|
}
|
|
15974
16008
|
);
|
|
15975
16009
|
if (!response.ok) {
|
|
15976
|
-
|
|
15977
|
-
|
|
16010
|
+
const retry = response.headers.get("retry-after");
|
|
16011
|
+
const retryAfterMs = retry ? /^\d+$/.test(retry) ? Number(retry) * 1e3 : Math.max(0, Date.parse(retry) - Date.now()) : void 0;
|
|
16012
|
+
const body = await response.json().catch(() => void 0);
|
|
16013
|
+
const rawCode = body?.error?.code;
|
|
16014
|
+
const code = typeof rawCode === "string" && /^[a-zA-Z0-9_:-]{1,100}$/.test(rawCode) ? rawCode : void 0;
|
|
16015
|
+
throw new KernelConnectionError(
|
|
16016
|
+
response.status,
|
|
16017
|
+
code,
|
|
16018
|
+
Number.isFinite(retryAfterMs) ? retryAfterMs : void 0
|
|
16019
|
+
);
|
|
15978
16020
|
}
|
|
15979
16021
|
const result = await response.json();
|
|
16022
|
+
if (!result || result.data === void 0)
|
|
16023
|
+
throw new Error("Kernel returned an invalid acknowledgment");
|
|
15980
16024
|
return result.data;
|
|
15981
16025
|
}
|
|
15982
16026
|
function connection(pairing, token, connectionId) {
|
|
@@ -16088,11 +16132,11 @@ var AppServer = class extends EventEmitter {
|
|
|
16088
16132
|
await new Promise((resolve3, reject) => {
|
|
16089
16133
|
const finish = () => {
|
|
16090
16134
|
clearTimeout(escalate);
|
|
16091
|
-
clearTimeout(
|
|
16135
|
+
clearTimeout(deadline2);
|
|
16092
16136
|
resolve3();
|
|
16093
16137
|
};
|
|
16094
16138
|
const escalate = setTimeout(() => this.child.kill("SIGKILL"), 3e3);
|
|
16095
|
-
const
|
|
16139
|
+
const deadline2 = setTimeout(() => {
|
|
16096
16140
|
clearTimeout(escalate);
|
|
16097
16141
|
this.child.off("exit", finish);
|
|
16098
16142
|
reject(new Error("The owned Codex process did not release its task"));
|
|
@@ -16104,75 +16148,184 @@ var AppServer = class extends EventEmitter {
|
|
|
16104
16148
|
};
|
|
16105
16149
|
|
|
16106
16150
|
// src/projects.ts
|
|
16107
|
-
import { mkdir as
|
|
16151
|
+
import { mkdir as mkdir2, realpath as realpath2, stat } from "node:fs/promises";
|
|
16108
16152
|
import { join as join3, isAbsolute } from "node:path";
|
|
16153
|
+
|
|
16154
|
+
// src/recovery.ts
|
|
16155
|
+
var ReceiptQueue = class {
|
|
16156
|
+
tails = /* @__PURE__ */ new Map();
|
|
16157
|
+
run(id, action) {
|
|
16158
|
+
const previous = this.tails.get(id) ?? Promise.resolve();
|
|
16159
|
+
const next = previous.catch(() => void 0).then(action);
|
|
16160
|
+
this.tails.set(id, next);
|
|
16161
|
+
void next.finally(() => {
|
|
16162
|
+
if (this.tails.get(id) === next) this.tails.delete(id);
|
|
16163
|
+
}).catch(() => void 0);
|
|
16164
|
+
return next;
|
|
16165
|
+
}
|
|
16166
|
+
};
|
|
16167
|
+
function retryFailure(error51, prior, now = Date.now(), random = Math.random) {
|
|
16168
|
+
const attempts = (prior?.attempts ?? 0) + 1;
|
|
16169
|
+
const state = {
|
|
16170
|
+
attempts,
|
|
16171
|
+
pendingSince: Math.min(prior?.pendingSince ?? now, now),
|
|
16172
|
+
nextAttemptAt: now
|
|
16173
|
+
};
|
|
16174
|
+
if (error51 instanceof KernelConnectionError && !error51.retryable) {
|
|
16175
|
+
state.blocked = { code: error51.code ?? `http_${error51.status}`, message: error51.message };
|
|
16176
|
+
} else {
|
|
16177
|
+
const backoff = Math.min(3e4, 1e3 * 2 ** Math.min(attempts, 5));
|
|
16178
|
+
state.nextAttemptAt = now + Math.max(
|
|
16179
|
+
1e3 + random() * (backoff - 1e3),
|
|
16180
|
+
error51 instanceof KernelConnectionError ? error51.retryAfterMs ?? 0 : 0
|
|
16181
|
+
);
|
|
16182
|
+
}
|
|
16183
|
+
return state;
|
|
16184
|
+
}
|
|
16185
|
+
function retryReady(state, now = Date.now()) {
|
|
16186
|
+
return !state?.blocked && (!state || state.nextAttemptAt <= now || now < state.pendingSince);
|
|
16187
|
+
}
|
|
16188
|
+
async function mapConcurrent(items, count, action) {
|
|
16189
|
+
let cursor = 0;
|
|
16190
|
+
const results = new Array(items.length);
|
|
16191
|
+
const settled = await Promise.allSettled(
|
|
16192
|
+
Array.from({ length: Math.min(count, items.length) }, async () => {
|
|
16193
|
+
for (; ; ) {
|
|
16194
|
+
const index = cursor++;
|
|
16195
|
+
if (index >= items.length) return;
|
|
16196
|
+
results[index] = await action(items[index]);
|
|
16197
|
+
}
|
|
16198
|
+
})
|
|
16199
|
+
);
|
|
16200
|
+
const failure = settled.find((result) => result.status === "rejected");
|
|
16201
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
16202
|
+
return results;
|
|
16203
|
+
}
|
|
16204
|
+
async function deadline(work, milliseconds) {
|
|
16205
|
+
let timer;
|
|
16206
|
+
try {
|
|
16207
|
+
return await Promise.race([
|
|
16208
|
+
work,
|
|
16209
|
+
new Promise((_2, reject) => {
|
|
16210
|
+
timer = setTimeout(() => reject(new Error("Local operation timed out")), milliseconds);
|
|
16211
|
+
})
|
|
16212
|
+
]);
|
|
16213
|
+
} finally {
|
|
16214
|
+
clearTimeout(timer);
|
|
16215
|
+
}
|
|
16216
|
+
}
|
|
16217
|
+
var RuntimeLanes = class {
|
|
16218
|
+
constructor(onError) {
|
|
16219
|
+
this.onError = onError;
|
|
16220
|
+
}
|
|
16221
|
+
onError;
|
|
16222
|
+
lanes = /* @__PURE__ */ new Map();
|
|
16223
|
+
start(name, interval, action) {
|
|
16224
|
+
const lane = this.lanes.get(name) ?? { at: -Infinity };
|
|
16225
|
+
if (lane.work || performance.now() - lane.at < interval) return;
|
|
16226
|
+
lane.at = performance.now();
|
|
16227
|
+
lane.work = action().catch((error51) => this.onError(name, error51)).finally(() => {
|
|
16228
|
+
delete lane.work;
|
|
16229
|
+
});
|
|
16230
|
+
this.lanes.set(name, lane);
|
|
16231
|
+
}
|
|
16232
|
+
async settle(milliseconds = 250) {
|
|
16233
|
+
await deadline(this.drain(), milliseconds).catch(() => void 0);
|
|
16234
|
+
}
|
|
16235
|
+
async drain() {
|
|
16236
|
+
for (; ; ) {
|
|
16237
|
+
const work = [...this.lanes.values()].flatMap((lane) => lane.work ? [lane.work] : []);
|
|
16238
|
+
if (!work.length) return;
|
|
16239
|
+
await Promise.all(work);
|
|
16240
|
+
}
|
|
16241
|
+
}
|
|
16242
|
+
};
|
|
16243
|
+
|
|
16244
|
+
// src/projects.ts
|
|
16109
16245
|
async function discoverProjects(server) {
|
|
16246
|
+
const signal = AbortSignal.timeout(25e3);
|
|
16110
16247
|
const projects = [];
|
|
16248
|
+
const visited = /* @__PURE__ */ new Set();
|
|
16111
16249
|
let cursor;
|
|
16112
16250
|
do {
|
|
16251
|
+
signal.throwIfAborted();
|
|
16113
16252
|
const page = await server.call(
|
|
16114
16253
|
"project/list",
|
|
16115
|
-
{ limit: 100, ...cursor ? { cursor } : {} }
|
|
16254
|
+
{ limit: 100, ...cursor ? { cursor } : {} },
|
|
16255
|
+
5e3
|
|
16116
16256
|
);
|
|
16117
16257
|
projects.push(...page.data);
|
|
16118
16258
|
cursor = page.nextCursor ?? void 0;
|
|
16259
|
+
if (cursor && visited.has(cursor)) throw new Error("Codex returned a repeated project page");
|
|
16260
|
+
if (cursor) visited.add(cursor);
|
|
16119
16261
|
if (projects.length > 500) throw new Error("The saved project catalog exceeds 500 projects");
|
|
16120
16262
|
} while (cursor);
|
|
16121
|
-
const discovered = await
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
return unavailable("The saved project folder is unavailable.");
|
|
16138
|
-
} catch {
|
|
16263
|
+
const discovered = await mapConcurrent(projects, 8, async (project) => {
|
|
16264
|
+
signal.throwIfAborted();
|
|
16265
|
+
const unavailable = (issue2) => ({
|
|
16266
|
+
id: project.id,
|
|
16267
|
+
name: project.name,
|
|
16268
|
+
root: project.roots[0]?.path || "/",
|
|
16269
|
+
isGit: false,
|
|
16270
|
+
defaultBranch: null,
|
|
16271
|
+
issue: issue2
|
|
16272
|
+
});
|
|
16273
|
+
if (project.roots.length !== 1)
|
|
16274
|
+
return unavailable("Only saved projects with one local folder are supported.");
|
|
16275
|
+
let root;
|
|
16276
|
+
try {
|
|
16277
|
+
root = await deadline(realpath2(project.roots[0].path), 2e3);
|
|
16278
|
+
if (!(await deadline(stat(root), 2e3)).isDirectory())
|
|
16139
16279
|
return unavailable("The saved project folder is unavailable.");
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
16146
|
-
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
|
|
16157
|
-
})
|
|
16158
|
-
|
|
16280
|
+
} catch {
|
|
16281
|
+
return unavailable("The saved project folder is unavailable.");
|
|
16282
|
+
}
|
|
16283
|
+
let isGit = false;
|
|
16284
|
+
let defaultBranch = null;
|
|
16285
|
+
try {
|
|
16286
|
+
const top = (await execute("git", ["-C", root, "rev-parse", "--show-toplevel"], {
|
|
16287
|
+
signal,
|
|
16288
|
+
timeout: 5e3
|
|
16289
|
+
})).stdout.trim();
|
|
16290
|
+
isGit = await deadline(realpath2(top), 2e3) === root;
|
|
16291
|
+
if (isGit)
|
|
16292
|
+
defaultBranch = (await execute(
|
|
16293
|
+
"git",
|
|
16294
|
+
["-C", root, "symbolic-ref", "--short", "refs/remotes/origin/HEAD"],
|
|
16295
|
+
{ signal, timeout: 5e3 }
|
|
16296
|
+
)).stdout.trim() || null;
|
|
16297
|
+
} catch (error51) {
|
|
16298
|
+
signal.throwIfAborted();
|
|
16299
|
+
if (error51.killed)
|
|
16300
|
+
return unavailable(
|
|
16301
|
+
"The saved project\u2019s Git metadata could not be verified. Discovery will retry."
|
|
16302
|
+
);
|
|
16303
|
+
}
|
|
16304
|
+
return { id: project.id, name: project.name, root, isGit, defaultBranch };
|
|
16305
|
+
});
|
|
16306
|
+
signal.throwIfAborted();
|
|
16159
16307
|
return discovered;
|
|
16160
16308
|
}
|
|
16309
|
+
var DestinationAuthorizationError = class extends Error {
|
|
16310
|
+
};
|
|
16161
16311
|
function authorizedDestination(pairing, launch, catalog2) {
|
|
16162
16312
|
const authorized = pairing.projects.find((project) => project.id === launch.destination.id);
|
|
16163
16313
|
const current = catalog2.find((project) => project.id === launch.destination.id);
|
|
16164
16314
|
if (!current || Boolean(current.issue) || pairing.projectAccess !== "all-saved" && (!authorized || authorized.root !== current.root) || launch.destination.root !== current.root)
|
|
16165
|
-
throw new
|
|
16315
|
+
throw new DestinationAuthorizationError(
|
|
16316
|
+
"This saved project is not locally authorized at the submitted folder"
|
|
16317
|
+
);
|
|
16166
16318
|
if (launch.checkout === "worktree" && (!current.isGit || !current.defaultBranch || current.defaultBranch !== launch.destination.defaultBranch))
|
|
16167
|
-
throw new
|
|
16168
|
-
if (!isAbsolute(current.root))
|
|
16319
|
+
throw new DestinationAuthorizationError("The submitted default branch is no longer available");
|
|
16320
|
+
if (!isAbsolute(current.root))
|
|
16321
|
+
throw new DestinationAuthorizationError("The saved project folder must be absolute");
|
|
16169
16322
|
return current;
|
|
16170
16323
|
}
|
|
16171
16324
|
async function prepareCheckout(project, launch, directory) {
|
|
16172
16325
|
if (launch.checkout === "folder") return project.root;
|
|
16173
16326
|
if (!project.isGit || !project.defaultBranch) throw new Error("A Git default branch is required");
|
|
16174
16327
|
const cwd = join3(directory, "worktrees", launch.id);
|
|
16175
|
-
await
|
|
16328
|
+
await mkdir2(join3(directory, "worktrees"), { recursive: true, mode: 448 });
|
|
16176
16329
|
await execute("git", [
|
|
16177
16330
|
"-C",
|
|
16178
16331
|
project.root,
|
|
@@ -16194,7 +16347,7 @@ async function prepareCheckout(project, launch, directory) {
|
|
|
16194
16347
|
|
|
16195
16348
|
// src/runtime.ts
|
|
16196
16349
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16197
|
-
import { readdir as readdir2, unlink } from "node:fs/promises";
|
|
16350
|
+
import { readdir as readdir2, unlink, rename as rename2, mkdir as mkdir3 } from "node:fs/promises";
|
|
16198
16351
|
import { join as join4 } from "node:path";
|
|
16199
16352
|
import { homedir as homedir3 } from "node:os";
|
|
16200
16353
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -21990,6 +22143,20 @@ var acceptAsanaSuggestionSchema = external_exports.object({
|
|
|
21990
22143
|
existingTaskId: external_exports.uuid().optional(),
|
|
21991
22144
|
syncProjectGid: asanaGidSchema.nullable().optional()
|
|
21992
22145
|
}).strict();
|
|
22146
|
+
var linkKernelTaskToAsanaSchema = external_exports.object({
|
|
22147
|
+
sourceId: external_exports.uuid(),
|
|
22148
|
+
asanaTaskGid: external_exports.string().regex(/^\d+$/).optional(),
|
|
22149
|
+
syncProjectGid: external_exports.string().regex(/^\d+$/).nullable().optional(),
|
|
22150
|
+
create: external_exports.object({
|
|
22151
|
+
title: external_exports.string().trim().min(1).max(500),
|
|
22152
|
+
description: external_exports.string().max(5e4).optional(),
|
|
22153
|
+
projectGid: external_exports.string().regex(/^\d+$/),
|
|
22154
|
+
assigneeGid: external_exports.union([external_exports.literal("me"), external_exports.string().regex(/^\d+$/)]).nullable().optional()
|
|
22155
|
+
}).strict().optional()
|
|
22156
|
+
}).strict().refine(
|
|
22157
|
+
(input) => Boolean(input.asanaTaskGid) !== Boolean(input.create),
|
|
22158
|
+
"Choose an existing Asana task or create a new one"
|
|
22159
|
+
);
|
|
21993
22160
|
|
|
21994
22161
|
// ../domain/src/invoicing.ts
|
|
21995
22162
|
var invoicePresentationSchema = external_exports.enum([
|
|
@@ -22201,6 +22368,7 @@ var codexHealthSchema = external_exports.strictObject({
|
|
|
22201
22368
|
supportsThreadLinking: external_exports.boolean().optional(),
|
|
22202
22369
|
supportsTaskNamePrefix: external_exports.boolean().optional(),
|
|
22203
22370
|
supportsDrafts: external_exports.boolean().optional(),
|
|
22371
|
+
supportsStatusFreshness: external_exports.boolean().optional(),
|
|
22204
22372
|
models: external_exports.array(codexModelSchema).max(100).optional(),
|
|
22205
22373
|
appServerVersion: external_exports.string().max(100),
|
|
22206
22374
|
desktopVersion: external_exports.string().max(100),
|
|
@@ -22240,6 +22408,7 @@ var deleteCodexMappingSchema = external_exports.strictObject({
|
|
|
22240
22408
|
var launchCodexTaskSchema = external_exports.strictObject({
|
|
22241
22409
|
protocolVersion,
|
|
22242
22410
|
requestId: external_exports.uuid(),
|
|
22411
|
+
submissionDeadline: external_exports.iso.datetime().optional(),
|
|
22243
22412
|
taskId: external_exports.uuid(),
|
|
22244
22413
|
taskVersion: external_exports.number().int().positive(),
|
|
22245
22414
|
mode: codexModeSchema,
|
|
@@ -22300,7 +22469,14 @@ var codexDraftOpenedSchema = external_exports.strictObject({
|
|
|
22300
22469
|
generation: sequence,
|
|
22301
22470
|
launchId: external_exports.uuid()
|
|
22302
22471
|
});
|
|
22472
|
+
var cancelCodexOperationSchema = external_exports.strictObject({
|
|
22473
|
+
requestId: external_exports.uuid(),
|
|
22474
|
+
taskId: external_exports.uuid(),
|
|
22475
|
+
kind: external_exports.enum(["launch", "lookup"])
|
|
22476
|
+
});
|
|
22303
22477
|
var codexStatusReportSchema = external_exports.strictObject({
|
|
22478
|
+
title: external_exports.string().trim().min(1).max(300).optional(),
|
|
22479
|
+
observationAgeMs: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
22304
22480
|
launchId: external_exports.uuid(),
|
|
22305
22481
|
sequence,
|
|
22306
22482
|
state: external_exports.enum(["running", "done", "unreachable"]),
|
|
@@ -22605,6 +22781,13 @@ var sourceKinds = [
|
|
|
22605
22781
|
"subAgentOther",
|
|
22606
22782
|
"unknown"
|
|
22607
22783
|
];
|
|
22784
|
+
async function readThreadTitle(server, threadId) {
|
|
22785
|
+
const { thread } = external_exports.object({
|
|
22786
|
+
thread: external_exports.object({ id: external_exports.uuid(), name: external_exports.string().nullable().optional() })
|
|
22787
|
+
}).parse(await server.call("thread/read", { threadId, includeTurns: false }, 2e3));
|
|
22788
|
+
if (thread.id !== threadId) throw new Error("Codex returned a different task");
|
|
22789
|
+
return thread.name === void 0 ? void 0 : thread.name?.trim().slice(0, 300) || "Untitled task";
|
|
22790
|
+
}
|
|
22608
22791
|
var inside = (root, folder) => {
|
|
22609
22792
|
const path = relative(root, folder);
|
|
22610
22793
|
return !isAbsolute2(path) && path !== ".." && !path.startsWith("../");
|
|
@@ -22615,12 +22798,13 @@ async function commonGitDirectory(folder) {
|
|
|
22615
22798
|
timeout: 1500,
|
|
22616
22799
|
maxBuffer: 8192
|
|
22617
22800
|
});
|
|
22618
|
-
return await realpath3(resolve(folder, stdout.trim()));
|
|
22801
|
+
return await deadline(realpath3(resolve(folder, stdout.trim())), 2e3);
|
|
22619
22802
|
} catch {
|
|
22620
22803
|
return null;
|
|
22621
22804
|
}
|
|
22622
22805
|
}
|
|
22623
|
-
async function resolveThreadProject(thread, pairing, catalog2) {
|
|
22806
|
+
async function resolveThreadProject(thread, pairing, catalog2, signal = AbortSignal.timeout(2e4)) {
|
|
22807
|
+
signal.throwIfAborted();
|
|
22624
22808
|
const authorized = catalog2.filter(
|
|
22625
22809
|
(project) => !project.issue && (pairing.projectAccess === "all-saved" || pairing.projects.some(
|
|
22626
22810
|
(allowed) => allowed.id === project.id && allowed.root === project.root
|
|
@@ -22628,19 +22812,21 @@ async function resolveThreadProject(thread, pairing, catalog2) {
|
|
|
22628
22812
|
);
|
|
22629
22813
|
let cwd;
|
|
22630
22814
|
try {
|
|
22631
|
-
cwd = await realpath3(thread.cwd);
|
|
22632
|
-
if (!(await stat2(cwd)).isDirectory()) return null;
|
|
22815
|
+
cwd = await deadline(realpath3(thread.cwd), 2e3);
|
|
22816
|
+
if (!(await deadline(stat2(cwd), 2e3)).isDirectory()) return null;
|
|
22633
22817
|
} catch {
|
|
22634
22818
|
return null;
|
|
22635
22819
|
}
|
|
22636
22820
|
const candidates = thread.projectId ? authorized.filter((project) => project.id === thread.projectId) : [...authorized].sort((a2, b2) => b2.root.length - a2.root.length);
|
|
22637
22821
|
for (const project of candidates) {
|
|
22638
|
-
|
|
22822
|
+
signal.throwIfAborted();
|
|
22823
|
+
if (await deadline(realpath3(project.root), 2e3).catch(() => null) !== project.root) continue;
|
|
22639
22824
|
if (inside(project.root, cwd)) return { project, cwd };
|
|
22640
22825
|
}
|
|
22641
22826
|
const common = candidates.some((project) => project.isGit) ? await commonGitDirectory(cwd) : null;
|
|
22642
22827
|
if (common)
|
|
22643
22828
|
for (const project of candidates) {
|
|
22829
|
+
signal.throwIfAborted();
|
|
22644
22830
|
if (project.isGit && await commonGitDirectory(project.root) === common)
|
|
22645
22831
|
return { project, cwd };
|
|
22646
22832
|
}
|
|
@@ -22701,6 +22887,7 @@ var CodexThreadLookupService = class {
|
|
|
22701
22887
|
return this.loading;
|
|
22702
22888
|
}
|
|
22703
22889
|
async lookup(server, raw, pairing, catalog2) {
|
|
22890
|
+
const signal = AbortSignal.timeout(25e3);
|
|
22704
22891
|
const work = codexThreadLookupWorkSchema.parse(raw);
|
|
22705
22892
|
let candidates;
|
|
22706
22893
|
if (work.threadId) {
|
|
@@ -22720,7 +22907,9 @@ var CodexThreadLookupService = class {
|
|
|
22720
22907
|
for (const thread of candidates) {
|
|
22721
22908
|
if (this.now() - started > 2e4)
|
|
22722
22909
|
throw new Error("Refine the title or paste a Codex task link.");
|
|
22723
|
-
|
|
22910
|
+
signal.throwIfAborted();
|
|
22911
|
+
const destination = await resolveThreadProject(thread, pairing, catalog2, signal);
|
|
22912
|
+
signal.throwIfAborted();
|
|
22724
22913
|
if (!destination) continue;
|
|
22725
22914
|
if (results.length === CODEX_THREAD_RESULT_LIMIT) return { results, hasMore: true };
|
|
22726
22915
|
results.push({
|
|
@@ -22785,33 +22974,53 @@ function initialReceipt(launch) {
|
|
|
22785
22974
|
function canBegin(receipt, now) {
|
|
22786
22975
|
return receipt.phase === "prepared" && "expiresAt" in receipt.launch && now < Date.parse(receipt.launch.expiresAt);
|
|
22787
22976
|
}
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
22794
|
-
|
|
22795
|
-
|
|
22796
|
-
|
|
22797
|
-
|
|
22798
|
-
|
|
22799
|
-
|
|
22800
|
-
|
|
22801
|
-
|
|
22802
|
-
|
|
22803
|
-
|
|
22804
|
-
|
|
22805
|
-
|
|
22806
|
-
|
|
22807
|
-
|
|
22808
|
-
|
|
22977
|
+
var CreationConflictError = class extends Error {
|
|
22978
|
+
};
|
|
22979
|
+
async function findCreation(server, receipt, search = {}, budget = Infinity) {
|
|
22980
|
+
while (!search.done && budget-- > 0) {
|
|
22981
|
+
if (!search.candidates?.length) {
|
|
22982
|
+
if (search.cursor === null) {
|
|
22983
|
+
search.done = true;
|
|
22984
|
+
break;
|
|
22985
|
+
}
|
|
22986
|
+
const page = await server.call(
|
|
22987
|
+
"thread/list",
|
|
22988
|
+
{
|
|
22989
|
+
projectId: receipt.launch.destination.id,
|
|
22990
|
+
...receipt.cwd ? { cwd: receipt.cwd } : {},
|
|
22991
|
+
limit: 100,
|
|
22992
|
+
modelProviders: [],
|
|
22993
|
+
...search.cursor ? { cursor: search.cursor } : {}
|
|
22994
|
+
},
|
|
22995
|
+
2e3
|
|
22996
|
+
);
|
|
22997
|
+
search.candidates = page.data.filter(
|
|
22998
|
+
(candidate2) => candidate2.createdAt * 1e3 >= receipt.preparedAt - 5e3
|
|
22999
|
+
);
|
|
23000
|
+
search.cursor = page.nextCursor ?? null;
|
|
23001
|
+
continue;
|
|
23002
|
+
}
|
|
23003
|
+
const candidate = search.candidates[0];
|
|
23004
|
+
const { thread } = await server.call("thread/read", { threadId: candidate.id, includeTurns: false }, 2e3);
|
|
23005
|
+
if (thread.threadSource === sourceMarker(receipt.launch.id)) {
|
|
23006
|
+
if (search.found)
|
|
23007
|
+
throw new CreationConflictError("Multiple creation receipts require local reconciliation");
|
|
23008
|
+
search.found = thread;
|
|
23009
|
+
}
|
|
23010
|
+
search.candidates.shift();
|
|
23011
|
+
}
|
|
23012
|
+
if (!search.candidates?.length && search.cursor === null) search.done = true;
|
|
23013
|
+
return search.done ? search.found : void 0;
|
|
22809
23014
|
}
|
|
22810
23015
|
async function runCompanion(machineId, entrypoint2, signal) {
|
|
22811
23016
|
const directory = pairingDirectory(machineId);
|
|
22812
23017
|
const unlock = await processLock(directory);
|
|
22813
23018
|
const owners = /* @__PURE__ */ new Map();
|
|
22814
23019
|
let observer;
|
|
23020
|
+
let catalogServer;
|
|
23021
|
+
let lanes;
|
|
23022
|
+
let stopping = false;
|
|
23023
|
+
let persistDiagnostics;
|
|
22815
23024
|
let lookupServer;
|
|
22816
23025
|
let lookupWork;
|
|
22817
23026
|
try {
|
|
@@ -22824,17 +23033,95 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22824
23033
|
await writeJson(join4(directory, "generation.json"), pairing.generation);
|
|
22825
23034
|
const post = connection(pairing, await readCredential(machineId), randomUUID3());
|
|
22826
23035
|
const receipts = /* @__PURE__ */ new Map();
|
|
22827
|
-
const
|
|
22828
|
-
|
|
23036
|
+
const entries = async (path) => {
|
|
23037
|
+
try {
|
|
23038
|
+
return await readdir2(path);
|
|
23039
|
+
} catch (error51) {
|
|
23040
|
+
if (error51.code === "ENOENT") return [];
|
|
23041
|
+
throw error51;
|
|
23042
|
+
}
|
|
23043
|
+
};
|
|
23044
|
+
const writers = new ReceiptQueue();
|
|
23045
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
23046
|
+
const searches = /* @__PURE__ */ new Map();
|
|
23047
|
+
const quarantine = async (path, id) => {
|
|
23048
|
+
await mkdir3(join4(directory, "quarantine"), { recursive: true, mode: 448 });
|
|
23049
|
+
if (id) {
|
|
23050
|
+
blocked.add(id);
|
|
23051
|
+
await writeJson(join4(directory, "blocked", `${id}.json`), {
|
|
23052
|
+
code: "invalid_receipt",
|
|
23053
|
+
message: "A damaged creation receipt needs local recovery. This launch will not be repeated."
|
|
23054
|
+
});
|
|
23055
|
+
}
|
|
23056
|
+
await rename2(path, join4(directory, "quarantine", `${randomUUID3()}.json`));
|
|
23057
|
+
};
|
|
23058
|
+
for (const file2 of await entries(join4(directory, "blocked")))
|
|
23059
|
+
if (file2.endsWith(".json") && external_exports.uuid().safeParse(file2.slice(0, -5)).success)
|
|
23060
|
+
blocked.add(file2.slice(0, -5));
|
|
22829
23061
|
const inspected = /* @__PURE__ */ new Map();
|
|
23062
|
+
const observations = /* @__PURE__ */ new Map();
|
|
23063
|
+
const observe = (receipt, ageMs = 0) => {
|
|
23064
|
+
receipt.observedAt = Date.now() - ageMs;
|
|
23065
|
+
observations.set(receipt.launch.id, {
|
|
23066
|
+
wall: receipt.observedAt,
|
|
23067
|
+
monotonic: performance.now() - ageMs
|
|
23068
|
+
});
|
|
23069
|
+
};
|
|
22830
23070
|
const receiptPath = (id) => join4(directory, "receipts", `${id}.json`);
|
|
22831
23071
|
const save = (receipt) => writeJson(receiptPath(receipt.launch.id), receipt);
|
|
22832
|
-
for (const file2 of await
|
|
23072
|
+
for (const file2 of await entries(join4(directory, "receipts"))) {
|
|
22833
23073
|
if (!file2.endsWith(".json")) continue;
|
|
22834
|
-
const
|
|
22835
|
-
|
|
23074
|
+
const id = file2.slice(0, -5);
|
|
23075
|
+
let receipt;
|
|
23076
|
+
try {
|
|
23077
|
+
const stored = await readJson(join4(directory, "receipts", file2));
|
|
23078
|
+
if (!stored) throw new Error("Missing receipt");
|
|
23079
|
+
receipt = stored;
|
|
23080
|
+
if (!receipt || !external_exports.uuid().safeParse(id).success || receipt.launch?.id !== id || !Number.isFinite(receipt.preparedAt) || ![
|
|
23081
|
+
"prepared",
|
|
23082
|
+
"creating",
|
|
23083
|
+
"created",
|
|
23084
|
+
"starting",
|
|
23085
|
+
"opening-draft",
|
|
23086
|
+
"drafted",
|
|
23087
|
+
"released",
|
|
23088
|
+
"failed"
|
|
23089
|
+
].includes(receipt.phase))
|
|
23090
|
+
throw new Error("Invalid receipt");
|
|
23091
|
+
codexStatusReportSchema.parse(receipt.status);
|
|
23092
|
+
if ("task" in receipt.launch) codexLaunchSchema.parse(receipt.launch);
|
|
23093
|
+
else codexWatchSchema.parse(receipt.launch);
|
|
23094
|
+
} catch {
|
|
23095
|
+
await quarantine(
|
|
23096
|
+
join4(directory, "receipts", file2),
|
|
23097
|
+
external_exports.uuid().safeParse(id).success ? id : void 0
|
|
23098
|
+
);
|
|
23099
|
+
continue;
|
|
23100
|
+
}
|
|
23101
|
+
delete receipt.observedAt;
|
|
23102
|
+
if (receipt.phase === "opening-draft") {
|
|
23103
|
+
receipt.phase = "drafted";
|
|
23104
|
+
receipt.draftOpenUncertain = true;
|
|
23105
|
+
}
|
|
23106
|
+
if (["prepared", "created", "starting"].includes(receipt.phase)) {
|
|
23107
|
+
if (receipt.threadId) receipt.phase = "released";
|
|
23108
|
+
else {
|
|
23109
|
+
receipt.phase = "failed";
|
|
23110
|
+
receipt.failure = "The companion stopped before creating the task. Start again when connected.";
|
|
23111
|
+
}
|
|
23112
|
+
}
|
|
23113
|
+
if (receipt.phase === "released" && receipt.status.state === "running" && !receipt.status.waiting) {
|
|
23114
|
+
receipt.status = {
|
|
23115
|
+
...receipt.status,
|
|
23116
|
+
sequence: receipt.status.sequence + 1,
|
|
23117
|
+
state: "unreachable",
|
|
23118
|
+
message: "Checking the saved task after reconnection."
|
|
23119
|
+
};
|
|
23120
|
+
}
|
|
23121
|
+
receipts.set(id, receipt);
|
|
23122
|
+
await save(receipt);
|
|
22836
23123
|
}
|
|
22837
|
-
const indexThreads = async () => {
|
|
23124
|
+
const indexThreads = () => writers.run("index", async () => {
|
|
22838
23125
|
await writeJson(
|
|
22839
23126
|
join4(directory, "threads.json"),
|
|
22840
23127
|
Object.fromEntries(
|
|
@@ -22849,10 +23136,13 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22849
23136
|
)
|
|
22850
23137
|
)
|
|
22851
23138
|
);
|
|
22852
|
-
};
|
|
23139
|
+
});
|
|
23140
|
+
await indexThreads();
|
|
22853
23141
|
const queue = [];
|
|
22854
23142
|
const lookupService = new CodexThreadLookupService();
|
|
22855
23143
|
let lookupBusy = false;
|
|
23144
|
+
const lookupRetries = /* @__PURE__ */ new Map();
|
|
23145
|
+
const lookupExpiries = /* @__PURE__ */ new Map();
|
|
22856
23146
|
const lookupResults = [];
|
|
22857
23147
|
const watched = /* @__PURE__ */ new Map();
|
|
22858
23148
|
const watchAttempts = /* @__PURE__ */ new Map();
|
|
@@ -22872,10 +23162,18 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22872
23162
|
return lookupServer;
|
|
22873
23163
|
};
|
|
22874
23164
|
const connectObserver = async () => {
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
23165
|
+
const next = new AppServer(directory);
|
|
23166
|
+
try {
|
|
23167
|
+
await next.initialize();
|
|
23168
|
+
observer = next;
|
|
23169
|
+
next.once("closed", () => {
|
|
23170
|
+
if (observer === next) observer = void 0;
|
|
23171
|
+
});
|
|
23172
|
+
return next;
|
|
23173
|
+
} catch (error51) {
|
|
23174
|
+
next.close();
|
|
23175
|
+
throw error51;
|
|
23176
|
+
}
|
|
22879
23177
|
};
|
|
22880
23178
|
let catalog2 = [];
|
|
22881
23179
|
let health = {
|
|
@@ -22891,8 +23189,15 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22891
23189
|
issue: "Checking Codex compatibility."
|
|
22892
23190
|
};
|
|
22893
23191
|
let refreshed = 0;
|
|
22894
|
-
let
|
|
22895
|
-
let
|
|
23192
|
+
let supportsFreshness = false;
|
|
23193
|
+
let supportsTaskTitles = false;
|
|
23194
|
+
let heartbeatHealth = "";
|
|
23195
|
+
const laneRetries = /* @__PURE__ */ new Map();
|
|
23196
|
+
let fatalError = null;
|
|
23197
|
+
const laneErrors = /* @__PURE__ */ new Map();
|
|
23198
|
+
let journalHealthy = true;
|
|
23199
|
+
let clockAnchor;
|
|
23200
|
+
const kernelNow = () => clockAnchor ? clockAnchor.server + Math.max(0, performance.now() - clockAnchor.monotonic) : Date.now();
|
|
22896
23201
|
const release = async (receipt, waiting, message) => {
|
|
22897
23202
|
const owner = owners.get(receipt.launch.id);
|
|
22898
23203
|
receipt.status = {
|
|
@@ -22904,6 +23209,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22904
23209
|
message
|
|
22905
23210
|
};
|
|
22906
23211
|
receipt.handoffWaiting = waiting;
|
|
23212
|
+
observe(receipt);
|
|
22907
23213
|
await save(receipt);
|
|
22908
23214
|
if (waiting && owner && receipt.turnId)
|
|
22909
23215
|
await owner.call("turn/interrupt", { threadId: receipt.threadId, turnId: receipt.turnId }).catch(() => void 0);
|
|
@@ -22914,12 +23220,12 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22914
23220
|
await save(receipt);
|
|
22915
23221
|
};
|
|
22916
23222
|
const begin = async (launch) => {
|
|
22917
|
-
if (receipts.has(launch.id)) return;
|
|
23223
|
+
if (receipts.has(launch.id) || blocked.has(launch.id) || stopping || signal.aborted) return;
|
|
22918
23224
|
const receipt = initialReceipt(launch);
|
|
22919
23225
|
receipts.set(launch.id, receipt);
|
|
22920
23226
|
await save(receipt);
|
|
22921
23227
|
try {
|
|
22922
|
-
if (launch.machineId !== machineId || launch.protocolVersion !== 1 || !canBegin(receipt,
|
|
23228
|
+
if (launch.machineId !== machineId || launch.protocolVersion !== 1 || !canBegin(receipt, kernelNow()))
|
|
22923
23229
|
throw new Error("The launch expired before local creation");
|
|
22924
23230
|
const authorized = await readJson(join4(directory, "pairing.json"));
|
|
22925
23231
|
if (!authorized) throw new Error("The local pairing is unavailable");
|
|
@@ -22942,6 +23248,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22942
23248
|
receipt.phase = "opening-draft";
|
|
22943
23249
|
await save(receipt);
|
|
22944
23250
|
await indexThreads();
|
|
23251
|
+
if (stopping || signal.aborted || kernelNow() >= Date.parse(launch.expiresAt))
|
|
23252
|
+
throw new Error("The launch expired before desktop dispatch");
|
|
22945
23253
|
await execute("/usr/bin/open", [url2]);
|
|
22946
23254
|
receipt.phase = "drafted";
|
|
22947
23255
|
await save(receipt);
|
|
@@ -22949,7 +23257,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22949
23257
|
}
|
|
22950
23258
|
receipt.cwd = await prepareCheckout(project, launch, directory);
|
|
22951
23259
|
await save(receipt);
|
|
22952
|
-
if (!canBegin(receipt,
|
|
23260
|
+
if (!canBegin(receipt, kernelNow()))
|
|
22953
23261
|
throw new Error("The launch expired during checkout preparation");
|
|
22954
23262
|
const latestPairing = await readJson(join4(directory, "pairing.json"));
|
|
22955
23263
|
if (!latestPairing) throw new Error("The local connection is unavailable");
|
|
@@ -22969,12 +23277,14 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22969
23277
|
);
|
|
22970
23278
|
await owner.initialize();
|
|
22971
23279
|
const selection = resolveModelSelection(launch, await discoverModels(owner));
|
|
22972
|
-
if (!canBegin(receipt,
|
|
23280
|
+
if (!canBegin(receipt, kernelNow()))
|
|
22973
23281
|
throw new Error("The launch expired while checking model availability");
|
|
22974
23282
|
receipt.model = selection.model;
|
|
22975
23283
|
receipt.effort = selection.reasoningEffort;
|
|
22976
23284
|
receipt.phase = "creating";
|
|
22977
23285
|
await save(receipt);
|
|
23286
|
+
if (stopping || signal.aborted || kernelNow() >= Date.parse(launch.expiresAt))
|
|
23287
|
+
throw new Error("The connection stopped or the launch expired before creation");
|
|
22978
23288
|
const created = await owner.call("thread/start", {
|
|
22979
23289
|
cwd: receipt.cwd,
|
|
22980
23290
|
projectId: project.id,
|
|
@@ -22993,6 +23303,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
22993
23303
|
});
|
|
22994
23304
|
receipt.phase = "starting";
|
|
22995
23305
|
await save(receipt);
|
|
23306
|
+
if (stopping || signal.aborted || kernelNow() >= Date.parse(launch.expiresAt))
|
|
23307
|
+
throw new Error("The connection stopped or the launch expired before dispatch");
|
|
22996
23308
|
const response = await owner.call("turn/start", {
|
|
22997
23309
|
threadId: receipt.threadId,
|
|
22998
23310
|
input: [{ type: "text", text: codexLaunchPrompt(launch) }],
|
|
@@ -23002,6 +23314,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23002
23314
|
selection.reasoningEffort
|
|
23003
23315
|
)
|
|
23004
23316
|
});
|
|
23317
|
+
observe(receipt);
|
|
23005
23318
|
receipt.turnId = response.turn.id;
|
|
23006
23319
|
receipt.status.turnId = response.turn.id;
|
|
23007
23320
|
await save(receipt);
|
|
@@ -23048,153 +23361,182 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23048
23361
|
await save(receipt);
|
|
23049
23362
|
}
|
|
23050
23363
|
};
|
|
23051
|
-
|
|
23052
|
-
|
|
23053
|
-
|
|
23364
|
+
lanes = new RuntimeLanes((name, error51) => {
|
|
23365
|
+
if (name === "events") journalHealthy = false;
|
|
23366
|
+
const message = error51 instanceof KernelConnectionError ? error51.message : `${name} is temporarily unavailable; recovery will retry.`;
|
|
23367
|
+
laneErrors.set(name, message);
|
|
23368
|
+
laneRetries.set(name, retryFailure(error51, laneRetries.get(name)));
|
|
23369
|
+
if (error51 instanceof KernelConnectionError && error51.fatal) {
|
|
23370
|
+
stopping = true;
|
|
23371
|
+
fatalError = message;
|
|
23372
|
+
process.stdout.write(`${error51.message} The companion has stopped.
|
|
23373
|
+
`);
|
|
23374
|
+
}
|
|
23375
|
+
});
|
|
23376
|
+
const schedule = (name, interval, work) => {
|
|
23377
|
+
if (!stopping && retryReady(laneRetries.get(name)))
|
|
23378
|
+
lanes.start(name, interval, async () => {
|
|
23379
|
+
await work();
|
|
23380
|
+
if (name === "events") journalHealthy = true;
|
|
23381
|
+
laneRetries.delete(name);
|
|
23382
|
+
laneErrors.delete(name);
|
|
23383
|
+
});
|
|
23384
|
+
};
|
|
23385
|
+
const refreshCatalog = async () => {
|
|
23054
23386
|
try {
|
|
23055
|
-
if (
|
|
23387
|
+
if (!catalogServer) {
|
|
23388
|
+
catalogServer = new AppServer(directory);
|
|
23389
|
+
await catalogServer.initialize();
|
|
23390
|
+
}
|
|
23391
|
+
if (!observer) await connectObserver();
|
|
23392
|
+
const local = await readJson(join4(directory, "pairing.json"));
|
|
23393
|
+
if (!local) throw new Error("The local connection is unavailable");
|
|
23394
|
+
const discovered = await discoverProjects(catalogServer);
|
|
23395
|
+
const authorized = discovered.filter(
|
|
23396
|
+
(project) => local.projectAccess === "all-saved" || local.projects.some((p2) => p2.id === project.id && p2.root === project.root)
|
|
23397
|
+
);
|
|
23398
|
+
const ready = await hooksReady(catalogServer, [homedir3()], hookCommand(entrypoint2));
|
|
23399
|
+
const projects = await mapConcurrent(authorized, 4, async (project) => {
|
|
23400
|
+
if (project.issue) return project;
|
|
23056
23401
|
try {
|
|
23057
|
-
|
|
23058
|
-
|
|
23059
|
-
|
|
23060
|
-
const discovered = await discoverProjects(observer);
|
|
23061
|
-
catalog2 = discovered.filter(
|
|
23062
|
-
(project) => local.projectAccess === "all-saved" || local.projects.some(
|
|
23063
|
-
(authorized) => authorized.id === project.id && authorized.root === project.root
|
|
23064
|
-
)
|
|
23065
|
-
);
|
|
23066
|
-
const ready = await hooksReady(observer, [homedir3()], hookCommand(entrypoint2));
|
|
23067
|
-
catalog2 = await Promise.all(
|
|
23068
|
-
catalog2.map(async (project) => {
|
|
23069
|
-
if (project.issue) return project;
|
|
23070
|
-
try {
|
|
23071
|
-
return await hooksReady(observer, [project.root], hookCommand(entrypoint2)) ? project : {
|
|
23072
|
-
...project,
|
|
23073
|
-
issue: "Kernel hooks are disabled or need review for this project. Open /hooks in Codex CLI from its folder."
|
|
23074
|
-
};
|
|
23075
|
-
} catch {
|
|
23076
|
-
return {
|
|
23077
|
-
...project,
|
|
23078
|
-
issue: "This project\u2019s Codex configuration could not be read."
|
|
23079
|
-
};
|
|
23080
|
-
}
|
|
23081
|
-
})
|
|
23082
|
-
);
|
|
23083
|
-
const { stdout } = await execute(codexExecutable(), ["--version"]);
|
|
23084
|
-
const desktop = await execute("/usr/libexec/PlistBuddy", [
|
|
23085
|
-
"-c",
|
|
23086
|
-
"Print:CFBundleShortVersionString",
|
|
23087
|
-
"/Applications/ChatGPT.app/Contents/Info.plist"
|
|
23088
|
-
]).then((result) => result.stdout.trim()).catch(() => "");
|
|
23089
|
-
const models = await discoverModels(observer);
|
|
23090
|
-
health = {
|
|
23091
|
-
reachable: true,
|
|
23092
|
-
hooksReady: ready,
|
|
23093
|
-
supportsModelSelection: true,
|
|
23094
|
-
supportsThreadLinking: true,
|
|
23095
|
-
supportsTaskNamePrefix: true,
|
|
23096
|
-
supportsDrafts: true,
|
|
23097
|
-
models,
|
|
23098
|
-
appServerVersion: stdout.trim(),
|
|
23099
|
-
desktopVersion: desktop,
|
|
23100
|
-
issue: ready ? null : "Review the eight Kernel hooks using /hooks in Codex CLI. The companion detects completed review automatically."
|
|
23101
|
-
};
|
|
23102
|
-
} catch (error51) {
|
|
23103
|
-
observer?.close();
|
|
23104
|
-
observer = void 0;
|
|
23105
|
-
health = {
|
|
23106
|
-
...health,
|
|
23107
|
-
reachable: false,
|
|
23108
|
-
hooksReady: false,
|
|
23109
|
-
models: [],
|
|
23110
|
-
issue: error51 instanceof CodexModelSelectionError ? error51.message : "Codex could not be reached through its supported app-server interface."
|
|
23402
|
+
return await hooksReady(catalogServer, [project.root], hookCommand(entrypoint2)) ? project : {
|
|
23403
|
+
...project,
|
|
23404
|
+
issue: "Kernel hooks are disabled or need review for this project. Open /hooks in Codex CLI from its folder."
|
|
23111
23405
|
};
|
|
23406
|
+
} catch {
|
|
23407
|
+
return { ...project, issue: "This project\u2019s Codex configuration could not be read." };
|
|
23112
23408
|
}
|
|
23409
|
+
});
|
|
23410
|
+
const { stdout } = await execute(codexExecutable(), ["--version"]);
|
|
23411
|
+
const desktop = await execute("/usr/libexec/PlistBuddy", [
|
|
23412
|
+
"-c",
|
|
23413
|
+
"Print:CFBundleShortVersionString",
|
|
23414
|
+
"/Applications/ChatGPT.app/Contents/Info.plist"
|
|
23415
|
+
]).then((result) => result.stdout.trim()).catch(() => "");
|
|
23416
|
+
const models = await discoverModels(catalogServer);
|
|
23417
|
+
catalog2 = projects;
|
|
23418
|
+
health = {
|
|
23419
|
+
...health,
|
|
23420
|
+
reachable: true,
|
|
23421
|
+
hooksReady: ready,
|
|
23422
|
+
models,
|
|
23423
|
+
appServerVersion: stdout.trim(),
|
|
23424
|
+
desktopVersion: desktop,
|
|
23425
|
+
issue: ready ? null : "Review the eight Kernel hooks using /hooks in Codex CLI. The companion detects completed review automatically."
|
|
23426
|
+
};
|
|
23427
|
+
} catch (error51) {
|
|
23428
|
+
catalogServer?.close();
|
|
23429
|
+
catalogServer = void 0;
|
|
23430
|
+
health = {
|
|
23431
|
+
...health,
|
|
23432
|
+
reachable: false,
|
|
23433
|
+
hooksReady: false,
|
|
23434
|
+
issue: error51 instanceof CodexModelSelectionError ? error51.message : "Codex could not be reached through its supported app-server interface."
|
|
23435
|
+
};
|
|
23436
|
+
throw error51;
|
|
23437
|
+
}
|
|
23438
|
+
};
|
|
23439
|
+
const heartbeat = async () => {
|
|
23440
|
+
const sentAt = performance.now();
|
|
23441
|
+
const advertised = {
|
|
23442
|
+
...health,
|
|
23443
|
+
...supportsFreshness ? { supportsStatusFreshness: true } : {}
|
|
23444
|
+
};
|
|
23445
|
+
const response = await post("heartbeat", { health: advertised, projects: catalog2 });
|
|
23446
|
+
const serverTime = Date.parse(response.serverTime);
|
|
23447
|
+
if (!Number.isFinite(serverTime)) throw new Error("Kernel returned an invalid clock");
|
|
23448
|
+
clockAnchor = {
|
|
23449
|
+
server: serverTime + Math.max(0, performance.now() - sentAt),
|
|
23450
|
+
monotonic: performance.now()
|
|
23451
|
+
};
|
|
23452
|
+
supportsFreshness = response.capabilities?.statusFreshness === true;
|
|
23453
|
+
supportsTaskTitles = response.capabilities?.taskTitles === true;
|
|
23454
|
+
heartbeatHealth = JSON.stringify(health);
|
|
23455
|
+
refreshed = performance.now();
|
|
23456
|
+
schedule("launches", 2e3, launches);
|
|
23457
|
+
schedule("lookups", 2e3, lookups);
|
|
23458
|
+
await writeJson(join4(directory, "health.json"), {
|
|
23459
|
+
at: Date.now(),
|
|
23460
|
+
health: advertised,
|
|
23461
|
+
projects: catalog2.length
|
|
23462
|
+
});
|
|
23463
|
+
};
|
|
23464
|
+
let reconciliationCursor = 0;
|
|
23465
|
+
const reconcile = async () => {
|
|
23466
|
+
if (!observer) return;
|
|
23467
|
+
const candidates = [...receipts.values()].filter(
|
|
23468
|
+
(receipt) => receipt.phase === "creating" && !receipt.threadId && !owners.has(receipt.launch.id) && retryReady(receipt.reconciliation)
|
|
23469
|
+
);
|
|
23470
|
+
const batch = Array.from(
|
|
23471
|
+
{ length: Math.min(candidates.length, 4) },
|
|
23472
|
+
() => candidates[reconciliationCursor++ % candidates.length]
|
|
23473
|
+
);
|
|
23474
|
+
await mapConcurrent(
|
|
23475
|
+
batch,
|
|
23476
|
+
2,
|
|
23477
|
+
(receipt) => writers.run(receipt.launch.id, async () => {
|
|
23113
23478
|
try {
|
|
23114
|
-
const
|
|
23115
|
-
|
|
23116
|
-
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
await writeJson(join4(directory, "health.json"), {
|
|
23123
|
-
at: refreshed,
|
|
23124
|
-
health,
|
|
23125
|
-
projects: catalog2.length
|
|
23126
|
-
});
|
|
23127
|
-
} catch (error51) {
|
|
23128
|
-
networkError = error51;
|
|
23129
|
-
}
|
|
23130
|
-
}
|
|
23131
|
-
if (observer) {
|
|
23132
|
-
let recovered = false;
|
|
23133
|
-
for (const receipt of receipts.values()) {
|
|
23134
|
-
if (!reconciled && receipt.phase === "opening-draft") {
|
|
23135
|
-
receipt.phase = "drafted";
|
|
23136
|
-
receipt.draftOpenUncertain = true;
|
|
23137
|
-
}
|
|
23138
|
-
if (reconciled && (receipt.phase !== "creating" || owners.has(receipt.launch.id)))
|
|
23139
|
-
continue;
|
|
23140
|
-
if (receipt.phase === "creating" && !receipt.threadId && !owners.has(receipt.launch.id)) {
|
|
23141
|
-
const found = await findCreation(observer, receipt);
|
|
23142
|
-
if (found) {
|
|
23143
|
-
receipt.threadId = found.id;
|
|
23144
|
-
receipt.cwd = found.cwd;
|
|
23145
|
-
receipt.phase = "released";
|
|
23146
|
-
receipt.status = {
|
|
23147
|
-
...receipt.status,
|
|
23148
|
-
sequence: receipt.status.sequence + 1,
|
|
23149
|
-
state: "unreachable",
|
|
23150
|
-
message: "Recovered the created task. Open it in Codex to check its first turn."
|
|
23151
|
-
};
|
|
23152
|
-
acknowledged.delete(receipt.launch.id);
|
|
23153
|
-
recovered = true;
|
|
23154
|
-
} else {
|
|
23155
|
-
receipt.failure = "Codex creation could not be confirmed. This launch will not be repeated.";
|
|
23156
|
-
receipt.ambiguous = true;
|
|
23157
|
-
}
|
|
23158
|
-
}
|
|
23159
|
-
if (!reconciled && ["prepared", "created", "starting"].includes(receipt.phase)) {
|
|
23160
|
-
if (receipt.threadId) receipt.phase = "released";
|
|
23161
|
-
else {
|
|
23162
|
-
receipt.phase = "failed";
|
|
23163
|
-
receipt.failure = "The companion stopped before creating the task. Start again when connected.";
|
|
23164
|
-
}
|
|
23165
|
-
}
|
|
23166
|
-
if (!reconciled && receipt.phase === "released" && receipt.status.state === "running" && !receipt.status.waiting)
|
|
23479
|
+
const search = searches.get(receipt.launch.id) ?? {};
|
|
23480
|
+
searches.set(receipt.launch.id, search);
|
|
23481
|
+
const found = await findCreation(observer, receipt, search, 5);
|
|
23482
|
+
if (found) {
|
|
23483
|
+
receipt.threadId = found.id;
|
|
23484
|
+
receipt.cwd = found.cwd;
|
|
23485
|
+
receipt.phase = "released";
|
|
23486
|
+
delete receipt.delivery;
|
|
23167
23487
|
receipt.status = {
|
|
23168
23488
|
...receipt.status,
|
|
23169
23489
|
sequence: receipt.status.sequence + 1,
|
|
23170
23490
|
state: "unreachable",
|
|
23171
|
-
message: "
|
|
23491
|
+
message: "Recovered the created task. Open it in Codex to check its first turn."
|
|
23492
|
+
};
|
|
23493
|
+
await indexThreads();
|
|
23494
|
+
} else if (search.done) {
|
|
23495
|
+
receipt.failure = "Codex creation could not be confirmed. This launch will not be repeated.";
|
|
23496
|
+
receipt.ambiguous = true;
|
|
23497
|
+
searches.delete(receipt.launch.id);
|
|
23498
|
+
receipt.reconciliation = retryFailure(
|
|
23499
|
+
new Error("Creation is still uncertain"),
|
|
23500
|
+
receipt.reconciliation
|
|
23501
|
+
);
|
|
23502
|
+
}
|
|
23503
|
+
if (found) delete receipt.reconciliation;
|
|
23504
|
+
} catch (error51) {
|
|
23505
|
+
receipt.reconciliation = retryFailure(error51, receipt.reconciliation);
|
|
23506
|
+
if (error51 instanceof CreationConflictError)
|
|
23507
|
+
receipt.reconciliation.blocked = {
|
|
23508
|
+
code: "ambiguous_creation",
|
|
23509
|
+
message: "Multiple creation records need local review. This launch will not be repeated."
|
|
23172
23510
|
};
|
|
23173
|
-
await save(receipt);
|
|
23174
23511
|
}
|
|
23175
|
-
|
|
23176
|
-
|
|
23177
|
-
|
|
23178
|
-
|
|
23179
|
-
|
|
23512
|
+
await save(receipt);
|
|
23513
|
+
})
|
|
23514
|
+
);
|
|
23515
|
+
};
|
|
23516
|
+
let eventCursor = 0;
|
|
23517
|
+
const invalidEvents = /* @__PURE__ */ new Map();
|
|
23518
|
+
const localEvents = async () => {
|
|
23519
|
+
for (const receipt of watchResults.splice(0)) {
|
|
23520
|
+
if (receipts.has(receipt.launch.id) || blocked.has(receipt.launch.id)) continue;
|
|
23521
|
+
await writers.run(receipt.launch.id, async () => {
|
|
23180
23522
|
receipts.set(receipt.launch.id, receipt);
|
|
23181
23523
|
await save(receipt);
|
|
23182
|
-
|
|
23183
|
-
|
|
23184
|
-
|
|
23185
|
-
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
}
|
|
23190
|
-
for (const { launchId, event } of queue.splice(0)) {
|
|
23524
|
+
});
|
|
23525
|
+
await indexThreads();
|
|
23526
|
+
}
|
|
23527
|
+
const events = queue.splice(0);
|
|
23528
|
+
await mapConcurrent(
|
|
23529
|
+
events,
|
|
23530
|
+
4,
|
|
23531
|
+
({ launchId, event }) => writers.run(launchId, async () => {
|
|
23191
23532
|
const receipt = receipts.get(launchId);
|
|
23192
|
-
if (!receipt || receipt.phase === "released" || receipt.phase === "failed")
|
|
23533
|
+
if (!receipt || receipt.phase === "released" || receipt.phase === "failed") return;
|
|
23193
23534
|
if (event.method === "turn/started") {
|
|
23194
23535
|
const turn = event.params?.turn;
|
|
23195
23536
|
if (turn) {
|
|
23196
23537
|
receipt.turnId = turn.id;
|
|
23197
23538
|
receipt.status.turnId = turn.id;
|
|
23539
|
+
observe(receipt);
|
|
23198
23540
|
await save(receipt);
|
|
23199
23541
|
}
|
|
23200
23542
|
} else if (event.id !== void 0) {
|
|
@@ -23210,7 +23552,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23210
23552
|
false,
|
|
23211
23553
|
turn?.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : null
|
|
23212
23554
|
);
|
|
23213
|
-
} else if (event.method === "kernel/closed") {
|
|
23555
|
+
} else if (event.method === "kernel/closed" && receipt.threadId) {
|
|
23214
23556
|
receipt.phase = "released";
|
|
23215
23557
|
receipt.status = {
|
|
23216
23558
|
...receipt.status,
|
|
@@ -23220,176 +23562,351 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23220
23562
|
};
|
|
23221
23563
|
await save(receipt);
|
|
23222
23564
|
}
|
|
23223
|
-
}
|
|
23224
|
-
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
await indexThreads();
|
|
23242
|
-
} catch {
|
|
23243
|
-
pending.phase = "failed";
|
|
23244
|
-
pending.failure = "The draft's saved project changed. Link the task from Codex using its task link.";
|
|
23245
|
-
await save(pending);
|
|
23246
|
-
await indexThreads();
|
|
23247
|
-
}
|
|
23248
|
-
}
|
|
23565
|
+
})
|
|
23566
|
+
);
|
|
23567
|
+
const files = (await entries(join4(directory, "events"))).filter((file2) => file2.endsWith(".json")).sort();
|
|
23568
|
+
const eventBatch = Array.from(
|
|
23569
|
+
{ length: Math.min(files.length, 100) },
|
|
23570
|
+
() => files[eventCursor++ % files.length]
|
|
23571
|
+
);
|
|
23572
|
+
await mapConcurrent(eventBatch, 4, async (file2) => {
|
|
23573
|
+
const path = join4(directory, "events", file2);
|
|
23574
|
+
let event;
|
|
23575
|
+
try {
|
|
23576
|
+
event = observationSchema.parse(await readJson(path));
|
|
23577
|
+
} catch {
|
|
23578
|
+
const first = invalidEvents.get(file2);
|
|
23579
|
+
if (first === void 0) invalidEvents.set(file2, performance.now());
|
|
23580
|
+
else if (performance.now() - first >= 2e3) {
|
|
23581
|
+
await quarantine(path);
|
|
23582
|
+
invalidEvents.delete(file2);
|
|
23249
23583
|
}
|
|
23250
|
-
|
|
23251
|
-
await unlink(path);
|
|
23584
|
+
return;
|
|
23252
23585
|
}
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23586
|
+
invalidEvents.delete(file2);
|
|
23587
|
+
const target = [...receipts.values()].find((r2) => r2.threadId === event.threadId) ?? (event.draft && receipts.get(event.draft.launchId));
|
|
23588
|
+
if (!target) return;
|
|
23589
|
+
await writers.run(target.launch.id, async () => {
|
|
23590
|
+
if (!target.threadId && event.draft && "mode" in target.launch && target.launch.mode === "draft" && ["opening-draft", "drafted"].includes(target.phase) && event.draft.cwd === target.cwd) {
|
|
23591
|
+
if (!observer) return;
|
|
23259
23592
|
try {
|
|
23260
|
-
const
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
23264
|
-
|
|
23265
|
-
|
|
23266
|
-
|
|
23267
|
-
|
|
23268
|
-
);
|
|
23269
|
-
|
|
23270
|
-
|
|
23271
|
-
if (
|
|
23272
|
-
|
|
23273
|
-
|
|
23274
|
-
|
|
23275
|
-
|
|
23276
|
-
|
|
23277
|
-
|
|
23278
|
-
message: turn.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : null
|
|
23279
|
-
};
|
|
23280
|
-
await save(receipt);
|
|
23281
|
-
} else if (receipt.status.state === "running" && !receipt.handoffWaiting && !await observedProcessAlive(receipt.pid, receipt.nativeIdentity) && receipt.hookAt) {
|
|
23282
|
-
receipt.status = {
|
|
23283
|
-
...receipt.status,
|
|
23284
|
-
sequence: receipt.status.sequence + 1,
|
|
23285
|
-
state: "unreachable",
|
|
23286
|
-
message: "The desktop execution is no longer reachable."
|
|
23287
|
-
};
|
|
23288
|
-
await save(receipt);
|
|
23289
|
-
}
|
|
23290
|
-
} catch {
|
|
23593
|
+
const local = await readJson(join4(directory, "pairing.json"));
|
|
23594
|
+
if (!local) return;
|
|
23595
|
+
const discovered = await discoverProjects(observer);
|
|
23596
|
+
if (discovered.find((p2) => p2.id === target.launch.destination.id)?.issue) return;
|
|
23597
|
+
authorizedDestination(local, target.launch, discovered);
|
|
23598
|
+
target.threadId = event.threadId;
|
|
23599
|
+
target.phase = "released";
|
|
23600
|
+
if (target.delivery) delete target.delivery.acknowledged;
|
|
23601
|
+
await save(target);
|
|
23602
|
+
await indexThreads();
|
|
23603
|
+
} catch (error51) {
|
|
23604
|
+
if (!(error51 instanceof DestinationAuthorizationError)) return;
|
|
23605
|
+
target.phase = "failed";
|
|
23606
|
+
target.failure = "The draft's saved project changed. Link the task from Codex using its task link.";
|
|
23607
|
+
await save(target);
|
|
23608
|
+
await indexThreads();
|
|
23609
|
+
await unlink(path);
|
|
23610
|
+
return;
|
|
23291
23611
|
}
|
|
23292
23612
|
}
|
|
23293
|
-
if (
|
|
23613
|
+
if (target.threadId !== event.threadId) return;
|
|
23614
|
+
if (applyObservation(target, event)) {
|
|
23615
|
+
observe(target, Math.max(0, performance.timeOrigin + performance.now() - event.at));
|
|
23616
|
+
}
|
|
23617
|
+
await save(target);
|
|
23618
|
+
await unlink(path);
|
|
23619
|
+
});
|
|
23620
|
+
});
|
|
23621
|
+
};
|
|
23622
|
+
let inspectionCursor = 0;
|
|
23623
|
+
const inspect = async () => {
|
|
23624
|
+
if (!observer || !journalHealthy) return;
|
|
23625
|
+
const all = [...receipts.values()].filter(
|
|
23626
|
+
(receipt) => receipt.threadId && (receipt.phase === "released" || owners.has(receipt.launch.id))
|
|
23627
|
+
);
|
|
23628
|
+
const batch = [];
|
|
23629
|
+
for (let n2 = 0; n2 < all.length && batch.length < 4; n2++) {
|
|
23630
|
+
const receipt = all[inspectionCursor++ % all.length];
|
|
23631
|
+
if (performance.now() - (inspected.get(receipt.launch.id) ?? -Infinity) >= (receipt.status.state === "done" ? 3e4 : 5e3))
|
|
23632
|
+
batch.push(receipt);
|
|
23633
|
+
}
|
|
23634
|
+
await mapConcurrent(
|
|
23635
|
+
batch,
|
|
23636
|
+
4,
|
|
23637
|
+
(receipt) => writers.run(receipt.launch.id, async () => {
|
|
23638
|
+
inspected.set(receipt.launch.id, performance.now());
|
|
23639
|
+
const title = await readThreadTitle(observer, receipt.threadId).catch(() => void 0);
|
|
23640
|
+
const titleChanged = title !== void 0 && title !== receipt.threadTitle;
|
|
23641
|
+
if (titleChanged) receipt.threadTitle = title;
|
|
23294
23642
|
try {
|
|
23295
|
-
if (
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
});
|
|
23301
|
-
acknowledged.add(receipt.launch.id);
|
|
23302
|
-
} else if (receipt.threadId && receipt.cwd) {
|
|
23303
|
-
if (!acknowledged.has(receipt.launch.id)) {
|
|
23304
|
-
deliveryBudget -= 1;
|
|
23305
|
-
await post("receipt", {
|
|
23306
|
-
launchId: receipt.launch.id,
|
|
23307
|
-
threadId: receipt.threadId,
|
|
23308
|
-
cwd: receipt.cwd
|
|
23309
|
-
});
|
|
23310
|
-
acknowledged.add(receipt.launch.id);
|
|
23311
|
-
}
|
|
23312
|
-
if (deliveryBudget > 0 && reported.get(receipt.launch.id) !== receipt.status.sequence) {
|
|
23313
|
-
deliveryBudget -= 1;
|
|
23314
|
-
await post("status", { reports: [receipt.status] });
|
|
23315
|
-
reported.set(receipt.launch.id, receipt.status.sequence);
|
|
23316
|
-
}
|
|
23317
|
-
} else if (receipt.failure && !acknowledged.has(receipt.launch.id)) {
|
|
23318
|
-
deliveryBudget -= 1;
|
|
23319
|
-
await post("failure", {
|
|
23320
|
-
launchId: receipt.launch.id,
|
|
23321
|
-
ambiguous: receipt.ambiguous ?? false,
|
|
23322
|
-
message: receipt.failure
|
|
23323
|
-
});
|
|
23324
|
-
acknowledged.add(receipt.launch.id);
|
|
23643
|
+
if (owners.has(receipt.launch.id) && receipt.phase !== "released") {
|
|
23644
|
+
observe(receipt);
|
|
23645
|
+
receipt.status.sequence++;
|
|
23646
|
+
await save(receipt);
|
|
23647
|
+
return;
|
|
23325
23648
|
}
|
|
23326
|
-
|
|
23327
|
-
|
|
23328
|
-
|
|
23329
|
-
|
|
23330
|
-
if (networkError) throw networkError;
|
|
23331
|
-
if (health.reachable && health.hooksReady && reconciled && Date.now() - tickStarted <= 5e3) {
|
|
23332
|
-
const lookups = await post("thread-lookups", { cursor: watchCursor });
|
|
23333
|
-
watchCursor = lookups.nextCursor;
|
|
23334
|
-
for (const raw of lookups.watches) {
|
|
23335
|
-
const watch = codexWatchSchema.parse(raw);
|
|
23336
|
-
if (watch.machineId === machineId && !receipts.has(watch.id))
|
|
23337
|
-
watched.set(watch.id, watch);
|
|
23338
|
-
}
|
|
23339
|
-
if (!lookupBusy) {
|
|
23340
|
-
const request = lookups.requests.find(
|
|
23341
|
-
(item) => Date.parse(item.expiresAt) > Date.now() + clockOffset
|
|
23649
|
+
const { data } = await observer.call(
|
|
23650
|
+
"thread/turns/list",
|
|
23651
|
+
{ threadId: receipt.threadId, limit: 1, itemsView: "notLoaded" },
|
|
23652
|
+
2e3
|
|
23342
23653
|
);
|
|
23343
|
-
const
|
|
23344
|
-
|
|
23345
|
-
|
|
23346
|
-
|
|
23347
|
-
|
|
23348
|
-
|
|
23349
|
-
|
|
23350
|
-
|
|
23351
|
-
|
|
23352
|
-
|
|
23353
|
-
|
|
23354
|
-
|
|
23355
|
-
|
|
23356
|
-
|
|
23357
|
-
|
|
23358
|
-
|
|
23359
|
-
|
|
23360
|
-
|
|
23361
|
-
|
|
23362
|
-
|
|
23363
|
-
|
|
23364
|
-
|
|
23365
|
-
|
|
23366
|
-
|
|
23367
|
-
|
|
23368
|
-
|
|
23369
|
-
|
|
23370
|
-
|
|
23371
|
-
|
|
23654
|
+
const turn = data[0];
|
|
23655
|
+
const interrupted = turn?.status === "interrupted" && Number.isFinite(turn.completedAt);
|
|
23656
|
+
const matchesActive = receipt.status.state !== "running" || turn?.id === receipt.status.turnId;
|
|
23657
|
+
const alive = await observedProcessAlive(receipt.pid, receipt.nativeIdentity);
|
|
23658
|
+
if (turn && (["completed", "failed"].includes(turn.status) || interrupted) && matchesActive && !receipt.status.waiting) {
|
|
23659
|
+
receipt.status = {
|
|
23660
|
+
...receipt.status,
|
|
23661
|
+
state: "done",
|
|
23662
|
+
waiting: false,
|
|
23663
|
+
turnId: turn.id,
|
|
23664
|
+
message: turn.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : interrupted ? "Stopped in Codex." : null
|
|
23665
|
+
};
|
|
23666
|
+
observe(receipt);
|
|
23667
|
+
} else if ((receipt.handoffWaiting || receipt.hookAt && alive) && receipt.status.state === "running") {
|
|
23668
|
+
observe(receipt);
|
|
23669
|
+
} else {
|
|
23670
|
+
receipt.status = {
|
|
23671
|
+
...receipt.status,
|
|
23672
|
+
state: "unreachable",
|
|
23673
|
+
message: "The task\u2019s current execution state could not be verified. Open it in Codex."
|
|
23674
|
+
};
|
|
23675
|
+
observe(receipt);
|
|
23676
|
+
}
|
|
23677
|
+
receipt.status.sequence++;
|
|
23678
|
+
await save(receipt);
|
|
23679
|
+
} catch {
|
|
23680
|
+
if (titleChanged) {
|
|
23681
|
+
receipt.status.sequence++;
|
|
23682
|
+
await save(receipt);
|
|
23683
|
+
}
|
|
23684
|
+
observer?.close();
|
|
23685
|
+
observer = void 0;
|
|
23686
|
+
}
|
|
23687
|
+
})
|
|
23688
|
+
);
|
|
23689
|
+
};
|
|
23690
|
+
let deliveryCursor = 0;
|
|
23691
|
+
const deliver = async () => {
|
|
23692
|
+
if (!clockAnchor) return;
|
|
23693
|
+
const all = [...receipts.values()];
|
|
23694
|
+
const batch = [];
|
|
23695
|
+
for (let n2 = 0; n2 < all.length && batch.length < 4; n2++) {
|
|
23696
|
+
const receipt = all[deliveryCursor++ % all.length];
|
|
23697
|
+
const kind = receipt.threadId && receipt.cwd ? "receipt" : receipt.phase === "drafted" ? "draft" : receipt.failure ? "failure" : void 0;
|
|
23698
|
+
if (kind && retryReady(receipt.delivery?.retry) && (receipt.delivery?.acknowledged !== kind || kind === "receipt" && receipt.delivery?.sequence !== receipt.status.sequence))
|
|
23699
|
+
batch.push(receipt);
|
|
23700
|
+
}
|
|
23701
|
+
await mapConcurrent(batch, 4, async (receipt) => {
|
|
23702
|
+
const snapshot = await writers.run(receipt.launch.id, async () => structuredClone(receipt));
|
|
23703
|
+
const snapshotAt = performance.now();
|
|
23704
|
+
const observation2 = observations.get(receipt.launch.id);
|
|
23705
|
+
const observationAge = observation2 && observation2.wall === snapshot.observedAt ? Math.max(0, snapshotAt - observation2.monotonic) : 9e4;
|
|
23706
|
+
const delivery = snapshot.delivery ?? {};
|
|
23707
|
+
try {
|
|
23708
|
+
if (snapshot.threadId && snapshot.cwd) {
|
|
23709
|
+
if (delivery.acknowledged !== "receipt") {
|
|
23710
|
+
await post("receipt", {
|
|
23711
|
+
launchId: snapshot.launch.id,
|
|
23712
|
+
threadId: snapshot.threadId,
|
|
23713
|
+
cwd: snapshot.cwd
|
|
23714
|
+
});
|
|
23715
|
+
delivery.acknowledged = "receipt";
|
|
23716
|
+
}
|
|
23717
|
+
if (delivery.sequence !== snapshot.status.sequence) {
|
|
23718
|
+
await post("status", {
|
|
23719
|
+
reports: [
|
|
23720
|
+
{
|
|
23721
|
+
...snapshot.status,
|
|
23722
|
+
...supportsTaskTitles && snapshot.threadTitle ? { title: snapshot.threadTitle } : {},
|
|
23723
|
+
...supportsFreshness ? {
|
|
23724
|
+
observationAgeMs: Math.ceil(
|
|
23725
|
+
observationAge + Math.max(0, performance.now() - snapshotAt)
|
|
23726
|
+
)
|
|
23727
|
+
} : {}
|
|
23728
|
+
}
|
|
23729
|
+
]
|
|
23372
23730
|
});
|
|
23731
|
+
delivery.sequence = snapshot.status.sequence;
|
|
23373
23732
|
}
|
|
23733
|
+
} else if (snapshot.phase === "drafted") {
|
|
23734
|
+
await post("draft-opened", {
|
|
23735
|
+
launchId: snapshot.launch.id,
|
|
23736
|
+
...snapshot.draftOpenUncertain ? { uncertain: true } : {}
|
|
23737
|
+
});
|
|
23738
|
+
delivery.acknowledged = "draft";
|
|
23739
|
+
} else if (snapshot.failure) {
|
|
23740
|
+
await post("failure", {
|
|
23741
|
+
launchId: snapshot.launch.id,
|
|
23742
|
+
ambiguous: snapshot.ambiguous ?? false,
|
|
23743
|
+
message: snapshot.failure
|
|
23744
|
+
});
|
|
23745
|
+
delivery.acknowledged = "failure";
|
|
23746
|
+
}
|
|
23747
|
+
delete delivery.retry;
|
|
23748
|
+
delivery.lastSuccessAt = Date.now();
|
|
23749
|
+
} catch (error51) {
|
|
23750
|
+
delivery.retry = retryFailure(error51, delivery.retry);
|
|
23751
|
+
if (error51 instanceof KernelConnectionError && error51.fatal) {
|
|
23752
|
+
stopping = true;
|
|
23753
|
+
fatalError = error51.message;
|
|
23374
23754
|
}
|
|
23375
|
-
const response = await post("launches", {});
|
|
23376
|
-
for (const launch of response.launches) await begin(codexLaunchSchema.parse(launch));
|
|
23377
23755
|
}
|
|
23378
|
-
|
|
23379
|
-
|
|
23380
|
-
|
|
23381
|
-
|
|
23382
|
-
|
|
23383
|
-
|
|
23756
|
+
await writers.run(receipt.launch.id, async () => {
|
|
23757
|
+
if (delivery.acknowledged === "draft" && receipt.threadId) delete delivery.acknowledged;
|
|
23758
|
+
receipt.delivery = delivery;
|
|
23759
|
+
await save(receipt);
|
|
23760
|
+
});
|
|
23761
|
+
});
|
|
23762
|
+
for (let i2 = lookupResults.length - 1; i2 >= 0; i2--) {
|
|
23763
|
+
const id = lookupResults[i2].id;
|
|
23764
|
+
if ((lookupExpiries.get(id) ?? 0) <= kernelNow()) {
|
|
23765
|
+
lookupResults.splice(i2, 1);
|
|
23766
|
+
lookupRetries.delete(id);
|
|
23767
|
+
lookupExpiries.delete(id);
|
|
23768
|
+
}
|
|
23769
|
+
}
|
|
23770
|
+
const pending = lookupResults.filter((result) => retryReady(lookupRetries.get(result.id))).slice(0, 4);
|
|
23771
|
+
for (const result of pending) lookupResults.splice(lookupResults.indexOf(result), 1);
|
|
23772
|
+
for (const result of pending) {
|
|
23773
|
+
try {
|
|
23774
|
+
await post("thread-lookup-result", result);
|
|
23775
|
+
} catch (error51) {
|
|
23776
|
+
const retry = retryFailure(error51, lookupRetries.get(result.id));
|
|
23777
|
+
lookupRetries.set(result.id, retry);
|
|
23778
|
+
if (!retry.blocked) lookupResults.push(result);
|
|
23779
|
+
else
|
|
23780
|
+
await writeJson(join4(directory, "blocked-lookups", `${result.id}.json`), {
|
|
23781
|
+
id: result.id,
|
|
23782
|
+
...retry.blocked
|
|
23783
|
+
});
|
|
23784
|
+
if (error51 instanceof KernelConnectionError && error51.fatal) throw error51;
|
|
23384
23785
|
}
|
|
23385
23786
|
}
|
|
23386
|
-
|
|
23787
|
+
};
|
|
23788
|
+
const lookups = async () => {
|
|
23789
|
+
if (!health.reachable || !health.hooksReady || !refreshed || lookupBusy) return;
|
|
23790
|
+
const response = await post("thread-lookups", { cursor: watchCursor });
|
|
23791
|
+
watchCursor = response.nextCursor;
|
|
23792
|
+
for (const request2 of response.requests)
|
|
23793
|
+
lookupExpiries.set(request2.id, Date.parse(request2.expiresAt));
|
|
23794
|
+
for (const raw of response.watches) {
|
|
23795
|
+
const watch2 = codexWatchSchema.parse(raw);
|
|
23796
|
+
if (watch2.machineId === machineId && !receipts.has(watch2.id) && !blocked.has(watch2.id))
|
|
23797
|
+
watched.set(watch2.id, watch2);
|
|
23798
|
+
}
|
|
23799
|
+
const request = response.requests.find((item) => Date.parse(item.expiresAt) > kernelNow());
|
|
23800
|
+
const watch = [...watched.values()].find(
|
|
23801
|
+
(item) => performance.now() - (watchAttempts.get(item.id) ?? -Infinity) > 3e4
|
|
23802
|
+
);
|
|
23803
|
+
if (!request && !watch) return;
|
|
23804
|
+
lookupBusy = true;
|
|
23805
|
+
if (!request && watch) watchAttempts.set(watch.id, performance.now());
|
|
23806
|
+
lookupWork = (async () => {
|
|
23807
|
+
const local = await readJson(join4(directory, "pairing.json"));
|
|
23808
|
+
if (!local) throw new Error("The local connection is unavailable");
|
|
23809
|
+
const server = await connectLookup();
|
|
23810
|
+
if (request) {
|
|
23811
|
+
const result = await deadline(
|
|
23812
|
+
lookupService.lookup(server, request, local, catalog2),
|
|
23813
|
+
25e3
|
|
23814
|
+
);
|
|
23815
|
+
lookupResults.push({ id: request.id, ...result, error: null });
|
|
23816
|
+
} else if (watch) {
|
|
23817
|
+
watchResults.push(await linkedReceipt(server, watch, local, catalog2));
|
|
23818
|
+
watched.delete(watch.id);
|
|
23819
|
+
}
|
|
23820
|
+
})().catch(() => {
|
|
23821
|
+
lookupServer?.close();
|
|
23822
|
+
lookupServer = void 0;
|
|
23823
|
+
if (request)
|
|
23824
|
+
lookupResults.push({
|
|
23825
|
+
id: request.id,
|
|
23826
|
+
results: [],
|
|
23827
|
+
hasMore: false,
|
|
23828
|
+
error: "The local task could not be read. Check Codex and the saved project, then try again."
|
|
23829
|
+
});
|
|
23830
|
+
}).finally(() => {
|
|
23831
|
+
lookupBusy = false;
|
|
23832
|
+
});
|
|
23833
|
+
};
|
|
23834
|
+
const launches = async () => {
|
|
23835
|
+
if (!journalHealthy || !health.reachable || !health.hooksReady || !refreshed || performance.now() - refreshed >= 3e4 || !observer)
|
|
23836
|
+
return;
|
|
23837
|
+
const response = await post("launches", {});
|
|
23838
|
+
await mapConcurrent(response.launches, 4, (raw) => {
|
|
23839
|
+
const launch = codexLaunchSchema.parse(raw);
|
|
23840
|
+
return writers.run(launch.id, () => begin(launch)).then(
|
|
23841
|
+
() => schedule("events", 0, async () => {
|
|
23842
|
+
await localEvents();
|
|
23843
|
+
schedule("delivery", 0, deliver);
|
|
23844
|
+
})
|
|
23845
|
+
);
|
|
23846
|
+
});
|
|
23847
|
+
};
|
|
23848
|
+
const diagnostics = async () => {
|
|
23849
|
+
const pending = [...receipts.values()].filter((r2) => {
|
|
23850
|
+
const kind = r2.threadId && r2.cwd ? "receipt" : r2.phase === "drafted" ? "draft" : r2.failure ? "failure" : void 0;
|
|
23851
|
+
return kind && (r2.delivery?.acknowledged !== kind || kind === "receipt" && r2.delivery?.sequence !== r2.status.sequence);
|
|
23852
|
+
});
|
|
23853
|
+
const successes = [...receipts.values()].flatMap(
|
|
23854
|
+
(r2) => r2.delivery?.lastSuccessAt ? [r2.delivery.lastSuccessAt] : []
|
|
23855
|
+
);
|
|
23856
|
+
await writeJson(join4(directory, "recovery.json"), {
|
|
23857
|
+
lastSuccessfulDelivery: successes.length ? Math.max(...successes) : null,
|
|
23858
|
+
pending: pending.filter((r2) => !r2.delivery?.retry?.blocked).length,
|
|
23859
|
+
blocked: blocked.size + [...receipts.values()].filter(
|
|
23860
|
+
(r2) => r2.delivery?.retry?.blocked || r2.reconciliation?.blocked
|
|
23861
|
+
).length + (await entries(join4(directory, "blocked-lookups"))).length,
|
|
23862
|
+
oldestPendingAt: pending.length ? Math.min(...pending.map((r2) => r2.delivery?.retry?.pendingSince ?? r2.preparedAt)) : null,
|
|
23863
|
+
issue: fatalError ?? [...laneErrors.values()][0] ?? null,
|
|
23864
|
+
failures: [...blocked].map((launchId) => ({
|
|
23865
|
+
launchId,
|
|
23866
|
+
code: "invalid_receipt",
|
|
23867
|
+
message: "A damaged receipt was isolated for local recovery. This launch will not be repeated."
|
|
23868
|
+
})).concat(
|
|
23869
|
+
[...receipts.values()].filter((r2) => r2.delivery?.retry?.blocked || r2.reconciliation?.blocked).map((r2) => ({
|
|
23870
|
+
launchId: r2.launch.id,
|
|
23871
|
+
...r2.delivery?.retry?.blocked ?? r2.reconciliation?.blocked
|
|
23872
|
+
}))
|
|
23873
|
+
)
|
|
23874
|
+
});
|
|
23875
|
+
};
|
|
23876
|
+
persistDiagnostics = diagnostics;
|
|
23877
|
+
while (!signal.aborted && !stopping) {
|
|
23878
|
+
schedule("catalog", 1e4, async () => {
|
|
23879
|
+
await refreshCatalog();
|
|
23880
|
+
schedule("heartbeat", 0, heartbeat);
|
|
23881
|
+
});
|
|
23882
|
+
schedule("heartbeat", heartbeatHealth === JSON.stringify(health) ? 1e4 : 0, heartbeat);
|
|
23883
|
+
schedule("events", 0, localEvents);
|
|
23884
|
+
schedule("reconciliation", 2e3, reconcile);
|
|
23885
|
+
schedule("observation", 2e3, inspect);
|
|
23886
|
+
schedule("delivery", 0, deliver);
|
|
23887
|
+
schedule("lookups", 2e3, lookups);
|
|
23888
|
+
schedule("launches", 2e3, launches);
|
|
23889
|
+
schedule("diagnostics", 2e3, diagnostics);
|
|
23890
|
+
await lanes.settle();
|
|
23891
|
+
await delay(250, void 0, { signal }).catch(() => void 0);
|
|
23387
23892
|
}
|
|
23388
23893
|
} finally {
|
|
23894
|
+
stopping = true;
|
|
23389
23895
|
await Promise.allSettled([...owners.values()].map((server) => server.stop()));
|
|
23390
23896
|
await observer?.stop().catch(() => void 0);
|
|
23897
|
+
await catalogServer?.stop().catch(() => void 0);
|
|
23391
23898
|
await lookupServer?.stop().catch(() => void 0);
|
|
23899
|
+
await lanes?.drain();
|
|
23392
23900
|
await lookupWork;
|
|
23901
|
+
await Promise.allSettled(
|
|
23902
|
+
[
|
|
23903
|
+
...owners.values(),
|
|
23904
|
+
...observer ? [observer] : [],
|
|
23905
|
+
...catalogServer ? [catalogServer] : [],
|
|
23906
|
+
...lookupServer ? [lookupServer] : []
|
|
23907
|
+
].map((server) => server.stop())
|
|
23908
|
+
);
|
|
23909
|
+
await persistDiagnostics?.().catch(() => void 0);
|
|
23393
23910
|
await unlock();
|
|
23394
23911
|
}
|
|
23395
23912
|
}
|
|
@@ -23580,7 +24097,7 @@ import { setTimeout as delay2 } from "node:timers/promises";
|
|
|
23580
24097
|
import { createInterface } from "node:readline/promises";
|
|
23581
24098
|
|
|
23582
24099
|
// src/setup.ts
|
|
23583
|
-
import { chmod as chmod2, copyFile, mkdir as mkdir4, rename as
|
|
24100
|
+
import { chmod as chmod2, copyFile, mkdir as mkdir4, rename as rename3, rm } from "node:fs/promises";
|
|
23584
24101
|
import { homedir as homedir4 } from "node:os";
|
|
23585
24102
|
import { dirname as dirname2, join as join6, resolve as resolve2 } from "node:path";
|
|
23586
24103
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -23592,7 +24109,7 @@ async function installBundle(entrypoint2) {
|
|
|
23592
24109
|
try {
|
|
23593
24110
|
await copyFile(entrypoint2, temporary);
|
|
23594
24111
|
await chmod2(temporary, 448);
|
|
23595
|
-
await
|
|
24112
|
+
await rename3(temporary, installedEntrypoint);
|
|
23596
24113
|
} finally {
|
|
23597
24114
|
await rm(temporary, { force: true });
|
|
23598
24115
|
}
|
|
@@ -23639,12 +24156,12 @@ async function installService(id) {
|
|
|
23639
24156
|
|
|
23640
24157
|
// src/onboarding.ts
|
|
23641
24158
|
async function waitForStoppedCompanion(directory) {
|
|
23642
|
-
const
|
|
24159
|
+
const deadline2 = Date.now() + 2e4;
|
|
23643
24160
|
for (; ; ) {
|
|
23644
24161
|
try {
|
|
23645
24162
|
return await processLock(directory);
|
|
23646
24163
|
} catch (error51) {
|
|
23647
|
-
if (Date.now() >=
|
|
24164
|
+
if (Date.now() >= deadline2) throw error51;
|
|
23648
24165
|
await delay2(200);
|
|
23649
24166
|
}
|
|
23650
24167
|
}
|
|
@@ -23771,10 +24288,11 @@ async function connectCodex(entrypoint2) {
|
|
|
23771
24288
|
else process.stdout.write("Choose your project mapping in Kernel \u2192 Integrations \u2192 Codex.\n");
|
|
23772
24289
|
}
|
|
23773
24290
|
async function status() {
|
|
23774
|
-
const profile = await
|
|
23775
|
-
|
|
24291
|
+
const profile = await readProfile();
|
|
24292
|
+
if (!profile?.session) throw new Error("Run krnl login first");
|
|
24293
|
+
process.stdout.write(`Account: ${profile.session.account} (saved locally)
|
|
23776
24294
|
`);
|
|
23777
|
-
const id = profile.session.machineId;
|
|
24295
|
+
const id = profile.session.machineId ?? profile.pendingMachineId;
|
|
23778
24296
|
if (!id) {
|
|
23779
24297
|
process.stdout.write("Codex is not connected. Run krnl connect codex.\n");
|
|
23780
24298
|
return;
|
|
@@ -23793,6 +24311,32 @@ Saved projects: ${health.projects}
|
|
|
23793
24311
|
if (health.health.issue) process.stdout.write(`${health.health.issue}
|
|
23794
24312
|
`);
|
|
23795
24313
|
}
|
|
24314
|
+
const recovery = await readJson(join7(pairingDirectory(id), "recovery.json"));
|
|
24315
|
+
if (recovery) {
|
|
24316
|
+
process.stdout.write(
|
|
24317
|
+
`Pending deliveries: ${recovery.pending} \xB7 Blocked: ${recovery.blocked}
|
|
24318
|
+
`
|
|
24319
|
+
);
|
|
24320
|
+
if (recovery.lastSuccessfulDelivery)
|
|
24321
|
+
process.stdout.write(
|
|
24322
|
+
`Last successful delivery: ${new Date(recovery.lastSuccessfulDelivery).toISOString()}
|
|
24323
|
+
`
|
|
24324
|
+
);
|
|
24325
|
+
if (recovery.oldestPendingAt)
|
|
24326
|
+
process.stdout.write(
|
|
24327
|
+
`Oldest pending delivery: ${Math.max(0, Math.floor((Date.now() - recovery.oldestPendingAt) / 1e3))} seconds
|
|
24328
|
+
`
|
|
24329
|
+
);
|
|
24330
|
+
if (recovery.issue) process.stdout.write(`${recovery.issue}
|
|
24331
|
+
`);
|
|
24332
|
+
for (const failure of recovery.failures)
|
|
24333
|
+
process.stdout.write(`${failure.launchId}: ${failure.message}
|
|
24334
|
+
`);
|
|
24335
|
+
if (recovery.blocked)
|
|
24336
|
+
process.stdout.write(
|
|
24337
|
+
"Blocked receipts remain on this Mac. Do not remove them or repeat their launches; use the existing Codex task while repairing the connection.\n"
|
|
24338
|
+
);
|
|
24339
|
+
}
|
|
23796
24340
|
}
|
|
23797
24341
|
async function disconnect(logout = false) {
|
|
23798
24342
|
const profile = await readProfile();
|
|
@@ -23907,7 +24451,7 @@ async function main() {
|
|
|
23907
24451
|
}
|
|
23908
24452
|
if (command === "--version") {
|
|
23909
24453
|
process.stdout.write(
|
|
23910
|
-
`${false ? "development" : "0.1.
|
|
24454
|
+
`${false ? "development" : "0.1.7"}
|
|
23911
24455
|
`
|
|
23912
24456
|
);
|
|
23913
24457
|
return;
|