@supercorks/krnl 0.1.4 → 0.1.5

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.
@@ -15612,7 +15612,7 @@ function date4(params) {
15612
15612
  config(en_default());
15613
15613
 
15614
15614
  // src/hooks.ts
15615
- import { readdir, mkdir as mkdir2, open as open2 } from "node:fs/promises";
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 execute = promisify(execFile);
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
- await file2.writeFile(JSON.stringify(captured));
15933
- await file2.sync();
15934
- } finally {
15935
- await file2.close();
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 ? "The machine pairing was revoked or is no longer authorized" : "Kernel could not acknowledge the companion request"
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: AbortSignal.timeout(1e4),
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
- await response.body?.cancel();
15977
- throw new KernelConnectionError(response.status);
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(deadline);
16135
+ clearTimeout(deadline2);
16092
16136
  resolve3();
16093
16137
  };
16094
16138
  const escalate = setTimeout(() => this.child.kill("SIGKILL"), 3e3);
16095
- const deadline = setTimeout(() => {
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 mkdir3, realpath as realpath2, stat } from "node:fs/promises";
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 Promise.all(
16122
- projects.map(async (project) => {
16123
- const unavailable = (issue2) => ({
16124
- id: project.id,
16125
- name: project.name,
16126
- root: project.roots[0]?.path || "/",
16127
- isGit: false,
16128
- defaultBranch: null,
16129
- issue: issue2
16130
- });
16131
- if (project.roots.length !== 1)
16132
- return unavailable("Only saved projects with one local folder are supported.");
16133
- let root;
16134
- try {
16135
- root = await realpath2(project.roots[0].path);
16136
- if (!(await stat(root)).isDirectory())
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
- let isGit = false;
16142
- let defaultBranch = null;
16143
- try {
16144
- const top = (await execute("git", ["-C", root, "rev-parse", "--show-toplevel"])).stdout.trim();
16145
- isGit = await realpath2(top) === root;
16146
- if (isGit)
16147
- defaultBranch = (await execute("git", [
16148
- "-C",
16149
- root,
16150
- "symbolic-ref",
16151
- "--short",
16152
- "refs/remotes/origin/HEAD"
16153
- ])).stdout.trim() || null;
16154
- } catch {
16155
- }
16156
- return { id: project.id, name: project.name, root, isGit, defaultBranch };
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 Error("This saved project is not locally authorized at the submitted folder");
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 Error("The submitted default branch is no longer available");
16168
- if (!isAbsolute(current.root)) throw new Error("The saved project folder must be absolute");
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 mkdir3(join3(directory, "worktrees"), { recursive: true, mode: 448 });
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";
@@ -22201,6 +22354,7 @@ var codexHealthSchema = external_exports.strictObject({
22201
22354
  supportsThreadLinking: external_exports.boolean().optional(),
22202
22355
  supportsTaskNamePrefix: external_exports.boolean().optional(),
22203
22356
  supportsDrafts: external_exports.boolean().optional(),
22357
+ supportsStatusFreshness: external_exports.boolean().optional(),
22204
22358
  models: external_exports.array(codexModelSchema).max(100).optional(),
22205
22359
  appServerVersion: external_exports.string().max(100),
22206
22360
  desktopVersion: external_exports.string().max(100),
@@ -22240,6 +22394,7 @@ var deleteCodexMappingSchema = external_exports.strictObject({
22240
22394
  var launchCodexTaskSchema = external_exports.strictObject({
22241
22395
  protocolVersion,
22242
22396
  requestId: external_exports.uuid(),
22397
+ submissionDeadline: external_exports.iso.datetime().optional(),
22243
22398
  taskId: external_exports.uuid(),
22244
22399
  taskVersion: external_exports.number().int().positive(),
22245
22400
  mode: codexModeSchema,
@@ -22300,7 +22455,13 @@ var codexDraftOpenedSchema = external_exports.strictObject({
22300
22455
  generation: sequence,
22301
22456
  launchId: external_exports.uuid()
22302
22457
  });
22458
+ var cancelCodexOperationSchema = external_exports.strictObject({
22459
+ requestId: external_exports.uuid(),
22460
+ taskId: external_exports.uuid(),
22461
+ kind: external_exports.enum(["launch", "lookup"])
22462
+ });
22303
22463
  var codexStatusReportSchema = external_exports.strictObject({
22464
+ observationAgeMs: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
22304
22465
  launchId: external_exports.uuid(),
22305
22466
  sequence,
22306
22467
  state: external_exports.enum(["running", "done", "unreachable"]),
@@ -22615,12 +22776,13 @@ async function commonGitDirectory(folder) {
22615
22776
  timeout: 1500,
22616
22777
  maxBuffer: 8192
22617
22778
  });
22618
- return await realpath3(resolve(folder, stdout.trim()));
22779
+ return await deadline(realpath3(resolve(folder, stdout.trim())), 2e3);
22619
22780
  } catch {
22620
22781
  return null;
22621
22782
  }
22622
22783
  }
22623
- async function resolveThreadProject(thread, pairing, catalog2) {
22784
+ async function resolveThreadProject(thread, pairing, catalog2, signal = AbortSignal.timeout(2e4)) {
22785
+ signal.throwIfAborted();
22624
22786
  const authorized = catalog2.filter(
22625
22787
  (project) => !project.issue && (pairing.projectAccess === "all-saved" || pairing.projects.some(
22626
22788
  (allowed) => allowed.id === project.id && allowed.root === project.root
@@ -22628,19 +22790,21 @@ async function resolveThreadProject(thread, pairing, catalog2) {
22628
22790
  );
22629
22791
  let cwd;
22630
22792
  try {
22631
- cwd = await realpath3(thread.cwd);
22632
- if (!(await stat2(cwd)).isDirectory()) return null;
22793
+ cwd = await deadline(realpath3(thread.cwd), 2e3);
22794
+ if (!(await deadline(stat2(cwd), 2e3)).isDirectory()) return null;
22633
22795
  } catch {
22634
22796
  return null;
22635
22797
  }
22636
22798
  const candidates = thread.projectId ? authorized.filter((project) => project.id === thread.projectId) : [...authorized].sort((a2, b2) => b2.root.length - a2.root.length);
22637
22799
  for (const project of candidates) {
22638
- if (await realpath3(project.root).catch(() => null) !== project.root) continue;
22800
+ signal.throwIfAborted();
22801
+ if (await deadline(realpath3(project.root), 2e3).catch(() => null) !== project.root) continue;
22639
22802
  if (inside(project.root, cwd)) return { project, cwd };
22640
22803
  }
22641
22804
  const common = candidates.some((project) => project.isGit) ? await commonGitDirectory(cwd) : null;
22642
22805
  if (common)
22643
22806
  for (const project of candidates) {
22807
+ signal.throwIfAborted();
22644
22808
  if (project.isGit && await commonGitDirectory(project.root) === common)
22645
22809
  return { project, cwd };
22646
22810
  }
@@ -22701,6 +22865,7 @@ var CodexThreadLookupService = class {
22701
22865
  return this.loading;
22702
22866
  }
22703
22867
  async lookup(server, raw, pairing, catalog2) {
22868
+ const signal = AbortSignal.timeout(25e3);
22704
22869
  const work = codexThreadLookupWorkSchema.parse(raw);
22705
22870
  let candidates;
22706
22871
  if (work.threadId) {
@@ -22720,7 +22885,9 @@ var CodexThreadLookupService = class {
22720
22885
  for (const thread of candidates) {
22721
22886
  if (this.now() - started > 2e4)
22722
22887
  throw new Error("Refine the title or paste a Codex task link.");
22723
- const destination = await resolveThreadProject(thread, pairing, catalog2);
22888
+ signal.throwIfAborted();
22889
+ const destination = await resolveThreadProject(thread, pairing, catalog2, signal);
22890
+ signal.throwIfAborted();
22724
22891
  if (!destination) continue;
22725
22892
  if (results.length === CODEX_THREAD_RESULT_LIMIT) return { results, hasMore: true };
22726
22893
  results.push({
@@ -22785,33 +22952,53 @@ function initialReceipt(launch) {
22785
22952
  function canBegin(receipt, now) {
22786
22953
  return receipt.phase === "prepared" && "expiresAt" in receipt.launch && now < Date.parse(receipt.launch.expiresAt);
22787
22954
  }
22788
- async function findCreation(server, receipt) {
22789
- let cursor = null;
22790
- let found;
22791
- do {
22792
- const page = await server.call("thread/list", {
22793
- projectId: receipt.launch.destination.id,
22794
- ...receipt.cwd ? { cwd: receipt.cwd } : {},
22795
- limit: 100,
22796
- modelProviders: [],
22797
- ...cursor ? { cursor } : {}
22798
- });
22799
- for (const candidate of page.data) {
22800
- if (candidate.createdAt * 1e3 < receipt.preparedAt - 5e3) continue;
22801
- const { thread } = await server.call("thread/read", { threadId: candidate.id, includeTurns: false });
22802
- if (thread.threadSource !== sourceMarker(receipt.launch.id)) continue;
22803
- if (found) throw new Error("Multiple creation receipts require local reconciliation");
22804
- found = thread;
22805
- }
22806
- cursor = page.nextCursor ?? null;
22807
- } while (cursor);
22808
- return found;
22955
+ var CreationConflictError = class extends Error {
22956
+ };
22957
+ async function findCreation(server, receipt, search = {}, budget = Infinity) {
22958
+ while (!search.done && budget-- > 0) {
22959
+ if (!search.candidates?.length) {
22960
+ if (search.cursor === null) {
22961
+ search.done = true;
22962
+ break;
22963
+ }
22964
+ const page = await server.call(
22965
+ "thread/list",
22966
+ {
22967
+ projectId: receipt.launch.destination.id,
22968
+ ...receipt.cwd ? { cwd: receipt.cwd } : {},
22969
+ limit: 100,
22970
+ modelProviders: [],
22971
+ ...search.cursor ? { cursor: search.cursor } : {}
22972
+ },
22973
+ 2e3
22974
+ );
22975
+ search.candidates = page.data.filter(
22976
+ (candidate2) => candidate2.createdAt * 1e3 >= receipt.preparedAt - 5e3
22977
+ );
22978
+ search.cursor = page.nextCursor ?? null;
22979
+ continue;
22980
+ }
22981
+ const candidate = search.candidates[0];
22982
+ const { thread } = await server.call("thread/read", { threadId: candidate.id, includeTurns: false }, 2e3);
22983
+ if (thread.threadSource === sourceMarker(receipt.launch.id)) {
22984
+ if (search.found)
22985
+ throw new CreationConflictError("Multiple creation receipts require local reconciliation");
22986
+ search.found = thread;
22987
+ }
22988
+ search.candidates.shift();
22989
+ }
22990
+ if (!search.candidates?.length && search.cursor === null) search.done = true;
22991
+ return search.done ? search.found : void 0;
22809
22992
  }
22810
22993
  async function runCompanion(machineId, entrypoint2, signal) {
22811
22994
  const directory = pairingDirectory(machineId);
22812
22995
  const unlock = await processLock(directory);
22813
22996
  const owners = /* @__PURE__ */ new Map();
22814
22997
  let observer;
22998
+ let catalogServer;
22999
+ let lanes;
23000
+ let stopping = false;
23001
+ let persistDiagnostics;
22815
23002
  let lookupServer;
22816
23003
  let lookupWork;
22817
23004
  try {
@@ -22824,17 +23011,95 @@ async function runCompanion(machineId, entrypoint2, signal) {
22824
23011
  await writeJson(join4(directory, "generation.json"), pairing.generation);
22825
23012
  const post = connection(pairing, await readCredential(machineId), randomUUID3());
22826
23013
  const receipts = /* @__PURE__ */ new Map();
22827
- const acknowledged = /* @__PURE__ */ new Set();
22828
- const reported = /* @__PURE__ */ new Map();
23014
+ const entries = async (path) => {
23015
+ try {
23016
+ return await readdir2(path);
23017
+ } catch (error51) {
23018
+ if (error51.code === "ENOENT") return [];
23019
+ throw error51;
23020
+ }
23021
+ };
23022
+ const writers = new ReceiptQueue();
23023
+ const blocked = /* @__PURE__ */ new Set();
23024
+ const searches = /* @__PURE__ */ new Map();
23025
+ const quarantine = async (path, id) => {
23026
+ await mkdir3(join4(directory, "quarantine"), { recursive: true, mode: 448 });
23027
+ if (id) {
23028
+ blocked.add(id);
23029
+ await writeJson(join4(directory, "blocked", `${id}.json`), {
23030
+ code: "invalid_receipt",
23031
+ message: "A damaged creation receipt needs local recovery. This launch will not be repeated."
23032
+ });
23033
+ }
23034
+ await rename2(path, join4(directory, "quarantine", `${randomUUID3()}.json`));
23035
+ };
23036
+ for (const file2 of await entries(join4(directory, "blocked")))
23037
+ if (file2.endsWith(".json") && external_exports.uuid().safeParse(file2.slice(0, -5)).success)
23038
+ blocked.add(file2.slice(0, -5));
22829
23039
  const inspected = /* @__PURE__ */ new Map();
23040
+ const observations = /* @__PURE__ */ new Map();
23041
+ const observe = (receipt, ageMs = 0) => {
23042
+ receipt.observedAt = Date.now() - ageMs;
23043
+ observations.set(receipt.launch.id, {
23044
+ wall: receipt.observedAt,
23045
+ monotonic: performance.now() - ageMs
23046
+ });
23047
+ };
22830
23048
  const receiptPath = (id) => join4(directory, "receipts", `${id}.json`);
22831
23049
  const save = (receipt) => writeJson(receiptPath(receipt.launch.id), receipt);
22832
- for (const file2 of await readdir2(join4(directory, "receipts")).catch(() => [])) {
23050
+ for (const file2 of await entries(join4(directory, "receipts"))) {
22833
23051
  if (!file2.endsWith(".json")) continue;
22834
- const receipt = await readJson(join4(directory, "receipts", file2));
22835
- if (receipt) receipts.set(receipt.launch.id, receipt);
23052
+ const id = file2.slice(0, -5);
23053
+ let receipt;
23054
+ try {
23055
+ const stored = await readJson(join4(directory, "receipts", file2));
23056
+ if (!stored) throw new Error("Missing receipt");
23057
+ receipt = stored;
23058
+ if (!receipt || !external_exports.uuid().safeParse(id).success || receipt.launch?.id !== id || !Number.isFinite(receipt.preparedAt) || ![
23059
+ "prepared",
23060
+ "creating",
23061
+ "created",
23062
+ "starting",
23063
+ "opening-draft",
23064
+ "drafted",
23065
+ "released",
23066
+ "failed"
23067
+ ].includes(receipt.phase))
23068
+ throw new Error("Invalid receipt");
23069
+ codexStatusReportSchema.parse(receipt.status);
23070
+ if ("task" in receipt.launch) codexLaunchSchema.parse(receipt.launch);
23071
+ else codexWatchSchema.parse(receipt.launch);
23072
+ } catch {
23073
+ await quarantine(
23074
+ join4(directory, "receipts", file2),
23075
+ external_exports.uuid().safeParse(id).success ? id : void 0
23076
+ );
23077
+ continue;
23078
+ }
23079
+ delete receipt.observedAt;
23080
+ if (receipt.phase === "opening-draft") {
23081
+ receipt.phase = "drafted";
23082
+ receipt.draftOpenUncertain = true;
23083
+ }
23084
+ if (["prepared", "created", "starting"].includes(receipt.phase)) {
23085
+ if (receipt.threadId) receipt.phase = "released";
23086
+ else {
23087
+ receipt.phase = "failed";
23088
+ receipt.failure = "The companion stopped before creating the task. Start again when connected.";
23089
+ }
23090
+ }
23091
+ if (receipt.phase === "released" && receipt.status.state === "running" && !receipt.status.waiting) {
23092
+ receipt.status = {
23093
+ ...receipt.status,
23094
+ sequence: receipt.status.sequence + 1,
23095
+ state: "unreachable",
23096
+ message: "Checking the saved task after reconnection."
23097
+ };
23098
+ }
23099
+ receipts.set(id, receipt);
23100
+ await save(receipt);
22836
23101
  }
22837
- const indexThreads = async () => {
23102
+ const indexThreads = () => writers.run("index", async () => {
22838
23103
  await writeJson(
22839
23104
  join4(directory, "threads.json"),
22840
23105
  Object.fromEntries(
@@ -22849,10 +23114,13 @@ async function runCompanion(machineId, entrypoint2, signal) {
22849
23114
  )
22850
23115
  )
22851
23116
  );
22852
- };
23117
+ });
23118
+ await indexThreads();
22853
23119
  const queue = [];
22854
23120
  const lookupService = new CodexThreadLookupService();
22855
23121
  let lookupBusy = false;
23122
+ const lookupRetries = /* @__PURE__ */ new Map();
23123
+ const lookupExpiries = /* @__PURE__ */ new Map();
22856
23124
  const lookupResults = [];
22857
23125
  const watched = /* @__PURE__ */ new Map();
22858
23126
  const watchAttempts = /* @__PURE__ */ new Map();
@@ -22872,10 +23140,18 @@ async function runCompanion(machineId, entrypoint2, signal) {
22872
23140
  return lookupServer;
22873
23141
  };
22874
23142
  const connectObserver = async () => {
22875
- observer?.close();
22876
- observer = new AppServer(directory);
22877
- await observer.initialize();
22878
- return observer;
23143
+ const next = new AppServer(directory);
23144
+ try {
23145
+ await next.initialize();
23146
+ observer = next;
23147
+ next.once("closed", () => {
23148
+ if (observer === next) observer = void 0;
23149
+ });
23150
+ return next;
23151
+ } catch (error51) {
23152
+ next.close();
23153
+ throw error51;
23154
+ }
22879
23155
  };
22880
23156
  let catalog2 = [];
22881
23157
  let health = {
@@ -22891,8 +23167,14 @@ async function runCompanion(machineId, entrypoint2, signal) {
22891
23167
  issue: "Checking Codex compatibility."
22892
23168
  };
22893
23169
  let refreshed = 0;
22894
- let clockOffset = 0;
22895
- let reconciled = false;
23170
+ let supportsFreshness = false;
23171
+ let heartbeatHealth = "";
23172
+ const laneRetries = /* @__PURE__ */ new Map();
23173
+ let fatalError = null;
23174
+ const laneErrors = /* @__PURE__ */ new Map();
23175
+ let journalHealthy = true;
23176
+ let clockAnchor;
23177
+ const kernelNow = () => clockAnchor ? clockAnchor.server + Math.max(0, performance.now() - clockAnchor.monotonic) : Date.now();
22896
23178
  const release = async (receipt, waiting, message) => {
22897
23179
  const owner = owners.get(receipt.launch.id);
22898
23180
  receipt.status = {
@@ -22904,6 +23186,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
22904
23186
  message
22905
23187
  };
22906
23188
  receipt.handoffWaiting = waiting;
23189
+ observe(receipt);
22907
23190
  await save(receipt);
22908
23191
  if (waiting && owner && receipt.turnId)
22909
23192
  await owner.call("turn/interrupt", { threadId: receipt.threadId, turnId: receipt.turnId }).catch(() => void 0);
@@ -22914,12 +23197,12 @@ async function runCompanion(machineId, entrypoint2, signal) {
22914
23197
  await save(receipt);
22915
23198
  };
22916
23199
  const begin = async (launch) => {
22917
- if (receipts.has(launch.id)) return;
23200
+ if (receipts.has(launch.id) || blocked.has(launch.id) || stopping || signal.aborted) return;
22918
23201
  const receipt = initialReceipt(launch);
22919
23202
  receipts.set(launch.id, receipt);
22920
23203
  await save(receipt);
22921
23204
  try {
22922
- if (launch.machineId !== machineId || launch.protocolVersion !== 1 || !canBegin(receipt, Date.now() + clockOffset))
23205
+ if (launch.machineId !== machineId || launch.protocolVersion !== 1 || !canBegin(receipt, kernelNow()))
22923
23206
  throw new Error("The launch expired before local creation");
22924
23207
  const authorized = await readJson(join4(directory, "pairing.json"));
22925
23208
  if (!authorized) throw new Error("The local pairing is unavailable");
@@ -22942,6 +23225,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
22942
23225
  receipt.phase = "opening-draft";
22943
23226
  await save(receipt);
22944
23227
  await indexThreads();
23228
+ if (stopping || signal.aborted || kernelNow() >= Date.parse(launch.expiresAt))
23229
+ throw new Error("The launch expired before desktop dispatch");
22945
23230
  await execute("/usr/bin/open", [url2]);
22946
23231
  receipt.phase = "drafted";
22947
23232
  await save(receipt);
@@ -22949,7 +23234,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
22949
23234
  }
22950
23235
  receipt.cwd = await prepareCheckout(project, launch, directory);
22951
23236
  await save(receipt);
22952
- if (!canBegin(receipt, Date.now() + clockOffset))
23237
+ if (!canBegin(receipt, kernelNow()))
22953
23238
  throw new Error("The launch expired during checkout preparation");
22954
23239
  const latestPairing = await readJson(join4(directory, "pairing.json"));
22955
23240
  if (!latestPairing) throw new Error("The local connection is unavailable");
@@ -22969,12 +23254,14 @@ async function runCompanion(machineId, entrypoint2, signal) {
22969
23254
  );
22970
23255
  await owner.initialize();
22971
23256
  const selection = resolveModelSelection(launch, await discoverModels(owner));
22972
- if (!canBegin(receipt, Date.now() + clockOffset))
23257
+ if (!canBegin(receipt, kernelNow()))
22973
23258
  throw new Error("The launch expired while checking model availability");
22974
23259
  receipt.model = selection.model;
22975
23260
  receipt.effort = selection.reasoningEffort;
22976
23261
  receipt.phase = "creating";
22977
23262
  await save(receipt);
23263
+ if (stopping || signal.aborted || kernelNow() >= Date.parse(launch.expiresAt))
23264
+ throw new Error("The connection stopped or the launch expired before creation");
22978
23265
  const created = await owner.call("thread/start", {
22979
23266
  cwd: receipt.cwd,
22980
23267
  projectId: project.id,
@@ -22993,6 +23280,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
22993
23280
  });
22994
23281
  receipt.phase = "starting";
22995
23282
  await save(receipt);
23283
+ if (stopping || signal.aborted || kernelNow() >= Date.parse(launch.expiresAt))
23284
+ throw new Error("The connection stopped or the launch expired before dispatch");
22996
23285
  const response = await owner.call("turn/start", {
22997
23286
  threadId: receipt.threadId,
22998
23287
  input: [{ type: "text", text: codexLaunchPrompt(launch) }],
@@ -23002,6 +23291,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
23002
23291
  selection.reasoningEffort
23003
23292
  )
23004
23293
  });
23294
+ observe(receipt);
23005
23295
  receipt.turnId = response.turn.id;
23006
23296
  receipt.status.turnId = response.turn.id;
23007
23297
  await save(receipt);
@@ -23048,153 +23338,181 @@ async function runCompanion(machineId, entrypoint2, signal) {
23048
23338
  await save(receipt);
23049
23339
  }
23050
23340
  };
23051
- while (!signal.aborted) {
23052
- const tickStarted = Date.now();
23053
- let networkError;
23341
+ lanes = new RuntimeLanes((name, error51) => {
23342
+ if (name === "events") journalHealthy = false;
23343
+ const message = error51 instanceof KernelConnectionError ? error51.message : `${name} is temporarily unavailable; recovery will retry.`;
23344
+ laneErrors.set(name, message);
23345
+ laneRetries.set(name, retryFailure(error51, laneRetries.get(name)));
23346
+ if (error51 instanceof KernelConnectionError && error51.fatal) {
23347
+ stopping = true;
23348
+ fatalError = message;
23349
+ process.stdout.write(`${error51.message} The companion has stopped.
23350
+ `);
23351
+ }
23352
+ });
23353
+ const schedule = (name, interval, work) => {
23354
+ if (!stopping && retryReady(laneRetries.get(name)))
23355
+ lanes.start(name, interval, async () => {
23356
+ await work();
23357
+ if (name === "events") journalHealthy = true;
23358
+ laneRetries.delete(name);
23359
+ laneErrors.delete(name);
23360
+ });
23361
+ };
23362
+ const refreshCatalog = async () => {
23054
23363
  try {
23055
- if (Date.now() - refreshed >= 1e4) {
23364
+ if (!catalogServer) {
23365
+ catalogServer = new AppServer(directory);
23366
+ await catalogServer.initialize();
23367
+ }
23368
+ if (!observer) await connectObserver();
23369
+ const local = await readJson(join4(directory, "pairing.json"));
23370
+ if (!local) throw new Error("The local connection is unavailable");
23371
+ const discovered = await discoverProjects(catalogServer);
23372
+ const authorized = discovered.filter(
23373
+ (project) => local.projectAccess === "all-saved" || local.projects.some((p2) => p2.id === project.id && p2.root === project.root)
23374
+ );
23375
+ const ready = await hooksReady(catalogServer, [homedir3()], hookCommand(entrypoint2));
23376
+ const projects = await mapConcurrent(authorized, 4, async (project) => {
23377
+ if (project.issue) return project;
23056
23378
  try {
23057
- if (!observer) await connectObserver();
23058
- const local = await readJson(join4(directory, "pairing.json"));
23059
- if (!local) throw new Error("The local connection is unavailable");
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."
23379
+ return await hooksReady(catalogServer, [project.root], hookCommand(entrypoint2)) ? project : {
23380
+ ...project,
23381
+ issue: "Kernel hooks are disabled or need review for this project. Open /hooks in Codex CLI from its folder."
23111
23382
  };
23383
+ } catch {
23384
+ return { ...project, issue: "This project\u2019s Codex configuration could not be read." };
23112
23385
  }
23386
+ });
23387
+ const { stdout } = await execute(codexExecutable(), ["--version"]);
23388
+ const desktop = await execute("/usr/libexec/PlistBuddy", [
23389
+ "-c",
23390
+ "Print:CFBundleShortVersionString",
23391
+ "/Applications/ChatGPT.app/Contents/Info.plist"
23392
+ ]).then((result) => result.stdout.trim()).catch(() => "");
23393
+ const models = await discoverModels(catalogServer);
23394
+ catalog2 = projects;
23395
+ health = {
23396
+ ...health,
23397
+ reachable: true,
23398
+ hooksReady: ready,
23399
+ models,
23400
+ appServerVersion: stdout.trim(),
23401
+ desktopVersion: desktop,
23402
+ issue: ready ? null : "Review the eight Kernel hooks using /hooks in Codex CLI. The companion detects completed review automatically."
23403
+ };
23404
+ } catch (error51) {
23405
+ catalogServer?.close();
23406
+ catalogServer = void 0;
23407
+ health = {
23408
+ ...health,
23409
+ reachable: false,
23410
+ hooksReady: false,
23411
+ issue: error51 instanceof CodexModelSelectionError ? error51.message : "Codex could not be reached through its supported app-server interface."
23412
+ };
23413
+ throw error51;
23414
+ }
23415
+ };
23416
+ const heartbeat = async () => {
23417
+ const sentAt = performance.now();
23418
+ const advertised = {
23419
+ ...health,
23420
+ ...supportsFreshness ? { supportsStatusFreshness: true } : {}
23421
+ };
23422
+ const response = await post("heartbeat", { health: advertised, projects: catalog2 });
23423
+ const serverTime = Date.parse(response.serverTime);
23424
+ if (!Number.isFinite(serverTime)) throw new Error("Kernel returned an invalid clock");
23425
+ clockAnchor = {
23426
+ server: serverTime + Math.max(0, performance.now() - sentAt),
23427
+ monotonic: performance.now()
23428
+ };
23429
+ supportsFreshness = response.capabilities?.statusFreshness === true;
23430
+ heartbeatHealth = JSON.stringify(health);
23431
+ refreshed = performance.now();
23432
+ schedule("launches", 2e3, launches);
23433
+ schedule("lookups", 2e3, lookups);
23434
+ await writeJson(join4(directory, "health.json"), {
23435
+ at: Date.now(),
23436
+ health: advertised,
23437
+ projects: catalog2.length
23438
+ });
23439
+ };
23440
+ let reconciliationCursor = 0;
23441
+ const reconcile = async () => {
23442
+ if (!observer) return;
23443
+ const candidates = [...receipts.values()].filter(
23444
+ (receipt) => receipt.phase === "creating" && !receipt.threadId && !owners.has(receipt.launch.id) && retryReady(receipt.reconciliation)
23445
+ );
23446
+ const batch = Array.from(
23447
+ { length: Math.min(candidates.length, 4) },
23448
+ () => candidates[reconciliationCursor++ % candidates.length]
23449
+ );
23450
+ await mapConcurrent(
23451
+ batch,
23452
+ 2,
23453
+ (receipt) => writers.run(receipt.launch.id, async () => {
23113
23454
  try {
23114
- const sentAt = Date.now();
23115
- const heartbeat = await post("heartbeat", {
23116
- health,
23117
- projects: catalog2
23118
- });
23119
- clockOffset = Date.parse(heartbeat.serverTime) - sentAt;
23120
- if (!Number.isFinite(clockOffset)) throw new Error("Kernel returned an invalid clock");
23121
- refreshed = Date.now();
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)
23455
+ const search = searches.get(receipt.launch.id) ?? {};
23456
+ searches.set(receipt.launch.id, search);
23457
+ const found = await findCreation(observer, receipt, search, 5);
23458
+ if (found) {
23459
+ receipt.threadId = found.id;
23460
+ receipt.cwd = found.cwd;
23461
+ receipt.phase = "released";
23462
+ delete receipt.delivery;
23167
23463
  receipt.status = {
23168
23464
  ...receipt.status,
23169
23465
  sequence: receipt.status.sequence + 1,
23170
23466
  state: "unreachable",
23171
- message: "Checking the saved task after reconnection."
23467
+ message: "Recovered the created task. Open it in Codex to check its first turn."
23468
+ };
23469
+ await indexThreads();
23470
+ } else if (search.done) {
23471
+ receipt.failure = "Codex creation could not be confirmed. This launch will not be repeated.";
23472
+ receipt.ambiguous = true;
23473
+ searches.delete(receipt.launch.id);
23474
+ receipt.reconciliation = retryFailure(
23475
+ new Error("Creation is still uncertain"),
23476
+ receipt.reconciliation
23477
+ );
23478
+ }
23479
+ if (found) delete receipt.reconciliation;
23480
+ } catch (error51) {
23481
+ receipt.reconciliation = retryFailure(error51, receipt.reconciliation);
23482
+ if (error51 instanceof CreationConflictError)
23483
+ receipt.reconciliation.blocked = {
23484
+ code: "ambiguous_creation",
23485
+ message: "Multiple creation records need local review. This launch will not be repeated."
23172
23486
  };
23173
- await save(receipt);
23174
23487
  }
23175
- if (!reconciled || recovered) await indexThreads();
23176
- reconciled = true;
23177
- }
23178
- for (const receipt of watchResults.splice(0)) {
23179
- if (receipts.has(receipt.launch.id)) continue;
23488
+ await save(receipt);
23489
+ })
23490
+ );
23491
+ };
23492
+ let eventCursor = 0;
23493
+ const invalidEvents = /* @__PURE__ */ new Map();
23494
+ const localEvents = async () => {
23495
+ for (const receipt of watchResults.splice(0)) {
23496
+ if (receipts.has(receipt.launch.id) || blocked.has(receipt.launch.id)) continue;
23497
+ await writers.run(receipt.launch.id, async () => {
23180
23498
  receipts.set(receipt.launch.id, receipt);
23181
23499
  await save(receipt);
23182
- await indexThreads();
23183
- }
23184
- if (!networkError) {
23185
- while (lookupResults.length) {
23186
- await post("thread-lookup-result", lookupResults[0]);
23187
- lookupResults.shift();
23188
- }
23189
- }
23190
- for (const { launchId, event } of queue.splice(0)) {
23500
+ });
23501
+ await indexThreads();
23502
+ }
23503
+ const events = queue.splice(0);
23504
+ await mapConcurrent(
23505
+ events,
23506
+ 4,
23507
+ ({ launchId, event }) => writers.run(launchId, async () => {
23191
23508
  const receipt = receipts.get(launchId);
23192
- if (!receipt || receipt.phase === "released" || receipt.phase === "failed") continue;
23509
+ if (!receipt || receipt.phase === "released" || receipt.phase === "failed") return;
23193
23510
  if (event.method === "turn/started") {
23194
23511
  const turn = event.params?.turn;
23195
23512
  if (turn) {
23196
23513
  receipt.turnId = turn.id;
23197
23514
  receipt.status.turnId = turn.id;
23515
+ observe(receipt);
23198
23516
  await save(receipt);
23199
23517
  }
23200
23518
  } else if (event.id !== void 0) {
@@ -23210,7 +23528,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
23210
23528
  false,
23211
23529
  turn?.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : null
23212
23530
  );
23213
- } else if (event.method === "kernel/closed") {
23531
+ } else if (event.method === "kernel/closed" && receipt.threadId) {
23214
23532
  receipt.phase = "released";
23215
23533
  receipt.status = {
23216
23534
  ...receipt.status,
@@ -23220,176 +23538,342 @@ async function runCompanion(machineId, entrypoint2, signal) {
23220
23538
  };
23221
23539
  await save(receipt);
23222
23540
  }
23223
- }
23224
- for (const file2 of (await readdir2(join4(directory, "events")).catch(() => [])).sort()) {
23225
- if (!file2.endsWith(".json")) continue;
23226
- const path = join4(directory, "events", file2);
23227
- const event = await readJson(path);
23228
- let receipt = event && [...receipts.values()].find((r2) => r2.threadId === event.threadId);
23229
- if (!receipt && event?.draft && observer) {
23230
- const pending = receipts.get(event.draft.launchId);
23231
- if (pending && "mode" in pending.launch && pending.launch.mode === "draft" && !pending.threadId && ["opening-draft", "drafted"].includes(pending.phase) && event.draft.cwd === pending.cwd) {
23232
- try {
23233
- const authorized = await readJson(join4(directory, "pairing.json"));
23234
- if (!authorized) throw new Error("Connection unavailable");
23235
- authorizedDestination(authorized, pending.launch, await discoverProjects(observer));
23236
- pending.threadId = event.threadId;
23237
- pending.phase = "released";
23238
- acknowledged.delete(pending.launch.id);
23239
- receipt = pending;
23240
- await save(pending);
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
- }
23541
+ })
23542
+ );
23543
+ const files = (await entries(join4(directory, "events"))).filter((file2) => file2.endsWith(".json")).sort();
23544
+ const eventBatch = Array.from(
23545
+ { length: Math.min(files.length, 100) },
23546
+ () => files[eventCursor++ % files.length]
23547
+ );
23548
+ await mapConcurrent(eventBatch, 4, async (file2) => {
23549
+ const path = join4(directory, "events", file2);
23550
+ let event;
23551
+ try {
23552
+ event = observationSchema.parse(await readJson(path));
23553
+ } catch {
23554
+ const first = invalidEvents.get(file2);
23555
+ if (first === void 0) invalidEvents.set(file2, performance.now());
23556
+ else if (performance.now() - first >= 2e3) {
23557
+ await quarantine(path);
23558
+ invalidEvents.delete(file2);
23249
23559
  }
23250
- if (receipt && event && applyObservation(receipt, event)) await save(receipt);
23251
- await unlink(path);
23560
+ return;
23252
23561
  }
23253
- let inspectionBudget = 4;
23254
- let deliveryBudget = 4;
23255
- for (const receipt of receipts.values()) {
23256
- if (receipt.phase === "released" && receipt.status.state !== "done" && !receipt.handoffWaiting && inspectionBudget > 0 && receipt.threadId && observer && Date.now() - (inspected.get(receipt.launch.id) ?? 0) > 5e3) {
23257
- inspectionBudget -= 1;
23258
- inspected.set(receipt.launch.id, Date.now());
23562
+ invalidEvents.delete(file2);
23563
+ const target = [...receipts.values()].find((r2) => r2.threadId === event.threadId) ?? (event.draft && receipts.get(event.draft.launchId));
23564
+ if (!target) return;
23565
+ await writers.run(target.launch.id, async () => {
23566
+ if (!target.threadId && event.draft && "mode" in target.launch && target.launch.mode === "draft" && ["opening-draft", "drafted"].includes(target.phase) && event.draft.cwd === target.cwd) {
23567
+ if (!observer) return;
23259
23568
  try {
23260
- const latest = await observer.call(
23261
- "thread/turns/list",
23262
- {
23263
- threadId: receipt.threadId,
23264
- limit: 1,
23265
- itemsView: "notLoaded"
23266
- },
23267
- 2e3
23268
- );
23269
- const turn = latest.data[0];
23270
- const matchesActive = receipt.status.state !== "running" || turn?.id === receipt.status.turnId;
23271
- if (turn && ["completed", "failed"].includes(turn.status) && matchesActive && !receipt.status.waiting) {
23272
- receipt.status = {
23273
- ...receipt.status,
23274
- sequence: receipt.status.sequence + 1,
23275
- state: "done",
23276
- waiting: false,
23277
- turnId: turn.id,
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 {
23569
+ const local = await readJson(join4(directory, "pairing.json"));
23570
+ if (!local) return;
23571
+ const discovered = await discoverProjects(observer);
23572
+ if (discovered.find((p2) => p2.id === target.launch.destination.id)?.issue) return;
23573
+ authorizedDestination(local, target.launch, discovered);
23574
+ target.threadId = event.threadId;
23575
+ target.phase = "released";
23576
+ if (target.delivery) delete target.delivery.acknowledged;
23577
+ await save(target);
23578
+ await indexThreads();
23579
+ } catch (error51) {
23580
+ if (!(error51 instanceof DestinationAuthorizationError)) return;
23581
+ target.phase = "failed";
23582
+ target.failure = "The draft's saved project changed. Link the task from Codex using its task link.";
23583
+ await save(target);
23584
+ await indexThreads();
23585
+ await unlink(path);
23586
+ return;
23291
23587
  }
23292
23588
  }
23293
- if (networkError || deliveryBudget <= 0 || Date.now() - tickStarted > 5e3) continue;
23589
+ if (target.threadId !== event.threadId) return;
23590
+ if (applyObservation(target, event)) {
23591
+ observe(target, Math.max(0, performance.timeOrigin + performance.now() - event.at));
23592
+ }
23593
+ await save(target);
23594
+ await unlink(path);
23595
+ });
23596
+ });
23597
+ };
23598
+ let inspectionCursor = 0;
23599
+ const inspect = async () => {
23600
+ if (!observer || !journalHealthy) return;
23601
+ const all = [...receipts.values()].filter(
23602
+ (receipt) => receipt.threadId && (receipt.phase === "released" || owners.has(receipt.launch.id))
23603
+ );
23604
+ const batch = [];
23605
+ for (let n2 = 0; n2 < all.length && batch.length < 4; n2++) {
23606
+ const receipt = all[inspectionCursor++ % all.length];
23607
+ if (performance.now() - (inspected.get(receipt.launch.id) ?? -Infinity) >= (receipt.status.state === "done" ? 3e4 : 5e3))
23608
+ batch.push(receipt);
23609
+ }
23610
+ await mapConcurrent(
23611
+ batch,
23612
+ 4,
23613
+ (receipt) => writers.run(receipt.launch.id, async () => {
23614
+ inspected.set(receipt.launch.id, performance.now());
23294
23615
  try {
23295
- if (receipt.phase === "drafted" && !acknowledged.has(receipt.launch.id)) {
23296
- deliveryBudget -= 1;
23297
- await post("draft-opened", {
23298
- launchId: receipt.launch.id,
23299
- ...receipt.draftOpenUncertain ? { uncertain: true } : {}
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);
23616
+ if (owners.has(receipt.launch.id) && receipt.phase !== "released") {
23617
+ observe(receipt);
23618
+ receipt.status.sequence++;
23619
+ await save(receipt);
23620
+ return;
23325
23621
  }
23326
- } catch (error51) {
23327
- networkError = error51;
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
23342
- );
23343
- const watch = [...watched.values()].find(
23344
- (item) => Date.now() - (watchAttempts.get(item.id) ?? 0) > 3e4
23622
+ const { data } = await observer.call(
23623
+ "thread/turns/list",
23624
+ { threadId: receipt.threadId, limit: 1, itemsView: "notLoaded" },
23625
+ 2e3
23345
23626
  );
23346
- if (request || watch) {
23347
- lookupBusy = true;
23348
- if (!request && watch) watchAttempts.set(watch.id, Date.now());
23349
- lookupWork = (async () => {
23350
- const local = await readJson(join4(directory, "pairing.json"));
23351
- if (!local) throw new Error("The local connection is unavailable");
23352
- const server = await connectLookup();
23353
- if (request) {
23354
- const result = await lookupService.lookup(server, request, local, catalog2);
23355
- lookupResults.push({ id: request.id, ...result, error: null });
23356
- } else if (watch) {
23357
- watchResults.push(await linkedReceipt(server, watch, local, catalog2));
23358
- watched.delete(watch.id);
23359
- }
23360
- })().catch(() => {
23361
- lookupServer?.close();
23362
- lookupServer = void 0;
23363
- if (request)
23364
- lookupResults.push({
23365
- id: request.id,
23366
- results: [],
23367
- hasMore: false,
23368
- error: "The local task could not be read. Check Codex and the saved project, then try again."
23369
- });
23370
- }).finally(() => {
23371
- lookupBusy = false;
23627
+ const turn = data[0];
23628
+ const matchesActive = receipt.status.state !== "running" || turn?.id === receipt.status.turnId;
23629
+ const alive = await observedProcessAlive(receipt.pid, receipt.nativeIdentity);
23630
+ if (turn && ["completed", "failed"].includes(turn.status) && matchesActive && !receipt.status.waiting) {
23631
+ receipt.status = {
23632
+ ...receipt.status,
23633
+ state: "done",
23634
+ waiting: false,
23635
+ turnId: turn.id,
23636
+ message: turn.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : null
23637
+ };
23638
+ observe(receipt);
23639
+ } else if ((receipt.handoffWaiting || receipt.hookAt && alive) && receipt.status.state === "running") {
23640
+ observe(receipt);
23641
+ } else {
23642
+ receipt.status = {
23643
+ ...receipt.status,
23644
+ state: "unreachable",
23645
+ message: "The task\u2019s current execution state could not be verified. Open it in Codex."
23646
+ };
23647
+ observe(receipt);
23648
+ }
23649
+ receipt.status.sequence++;
23650
+ await save(receipt);
23651
+ } catch {
23652
+ observer?.close();
23653
+ observer = void 0;
23654
+ }
23655
+ })
23656
+ );
23657
+ };
23658
+ let deliveryCursor = 0;
23659
+ const deliver = async () => {
23660
+ if (!clockAnchor) return;
23661
+ const all = [...receipts.values()];
23662
+ const batch = [];
23663
+ for (let n2 = 0; n2 < all.length && batch.length < 4; n2++) {
23664
+ const receipt = all[deliveryCursor++ % all.length];
23665
+ const kind = receipt.threadId && receipt.cwd ? "receipt" : receipt.phase === "drafted" ? "draft" : receipt.failure ? "failure" : void 0;
23666
+ if (kind && retryReady(receipt.delivery?.retry) && (receipt.delivery?.acknowledged !== kind || kind === "receipt" && receipt.delivery?.sequence !== receipt.status.sequence))
23667
+ batch.push(receipt);
23668
+ }
23669
+ await mapConcurrent(batch, 4, async (receipt) => {
23670
+ const snapshot = await writers.run(receipt.launch.id, async () => structuredClone(receipt));
23671
+ const snapshotAt = performance.now();
23672
+ const observation2 = observations.get(receipt.launch.id);
23673
+ const observationAge = observation2 && observation2.wall === snapshot.observedAt ? Math.max(0, snapshotAt - observation2.monotonic) : 9e4;
23674
+ const delivery = snapshot.delivery ?? {};
23675
+ try {
23676
+ if (snapshot.threadId && snapshot.cwd) {
23677
+ if (delivery.acknowledged !== "receipt") {
23678
+ await post("receipt", {
23679
+ launchId: snapshot.launch.id,
23680
+ threadId: snapshot.threadId,
23681
+ cwd: snapshot.cwd
23372
23682
  });
23683
+ delivery.acknowledged = "receipt";
23373
23684
  }
23685
+ if (delivery.sequence !== snapshot.status.sequence) {
23686
+ await post("status", {
23687
+ reports: [
23688
+ {
23689
+ ...snapshot.status,
23690
+ ...supportsFreshness ? {
23691
+ observationAgeMs: Math.ceil(
23692
+ observationAge + Math.max(0, performance.now() - snapshotAt)
23693
+ )
23694
+ } : {}
23695
+ }
23696
+ ]
23697
+ });
23698
+ delivery.sequence = snapshot.status.sequence;
23699
+ }
23700
+ } else if (snapshot.phase === "drafted") {
23701
+ await post("draft-opened", {
23702
+ launchId: snapshot.launch.id,
23703
+ ...snapshot.draftOpenUncertain ? { uncertain: true } : {}
23704
+ });
23705
+ delivery.acknowledged = "draft";
23706
+ } else if (snapshot.failure) {
23707
+ await post("failure", {
23708
+ launchId: snapshot.launch.id,
23709
+ ambiguous: snapshot.ambiguous ?? false,
23710
+ message: snapshot.failure
23711
+ });
23712
+ delivery.acknowledged = "failure";
23713
+ }
23714
+ delete delivery.retry;
23715
+ delivery.lastSuccessAt = Date.now();
23716
+ } catch (error51) {
23717
+ delivery.retry = retryFailure(error51, delivery.retry);
23718
+ if (error51 instanceof KernelConnectionError && error51.fatal) {
23719
+ stopping = true;
23720
+ fatalError = error51.message;
23374
23721
  }
23375
- const response = await post("launches", {});
23376
- for (const launch of response.launches) await begin(codexLaunchSchema.parse(launch));
23377
23722
  }
23378
- } catch (error51) {
23379
- if (error51 instanceof KernelConnectionError && [401, 403].includes(error51.status)) {
23380
- process.stdout.write(
23381
- "The machine pairing is no longer authorized. The companion has stopped.\n"
23382
- );
23383
- return;
23723
+ await writers.run(receipt.launch.id, async () => {
23724
+ if (delivery.acknowledged === "draft" && receipt.threadId) delete delivery.acknowledged;
23725
+ receipt.delivery = delivery;
23726
+ await save(receipt);
23727
+ });
23728
+ });
23729
+ for (let i2 = lookupResults.length - 1; i2 >= 0; i2--) {
23730
+ const id = lookupResults[i2].id;
23731
+ if ((lookupExpiries.get(id) ?? 0) <= kernelNow()) {
23732
+ lookupResults.splice(i2, 1);
23733
+ lookupRetries.delete(id);
23734
+ lookupExpiries.delete(id);
23735
+ }
23736
+ }
23737
+ const pending = lookupResults.filter((result) => retryReady(lookupRetries.get(result.id))).slice(0, 4);
23738
+ for (const result of pending) lookupResults.splice(lookupResults.indexOf(result), 1);
23739
+ for (const result of pending) {
23740
+ try {
23741
+ await post("thread-lookup-result", result);
23742
+ } catch (error51) {
23743
+ const retry = retryFailure(error51, lookupRetries.get(result.id));
23744
+ lookupRetries.set(result.id, retry);
23745
+ if (!retry.blocked) lookupResults.push(result);
23746
+ else
23747
+ await writeJson(join4(directory, "blocked-lookups", `${result.id}.json`), {
23748
+ id: result.id,
23749
+ ...retry.blocked
23750
+ });
23751
+ if (error51 instanceof KernelConnectionError && error51.fatal) throw error51;
23384
23752
  }
23385
23753
  }
23386
- await delay(2e3, void 0, { signal }).catch(() => void 0);
23754
+ };
23755
+ const lookups = async () => {
23756
+ if (!health.reachable || !health.hooksReady || !refreshed || lookupBusy) return;
23757
+ const response = await post("thread-lookups", { cursor: watchCursor });
23758
+ watchCursor = response.nextCursor;
23759
+ for (const request2 of response.requests)
23760
+ lookupExpiries.set(request2.id, Date.parse(request2.expiresAt));
23761
+ for (const raw of response.watches) {
23762
+ const watch2 = codexWatchSchema.parse(raw);
23763
+ if (watch2.machineId === machineId && !receipts.has(watch2.id) && !blocked.has(watch2.id))
23764
+ watched.set(watch2.id, watch2);
23765
+ }
23766
+ const request = response.requests.find((item) => Date.parse(item.expiresAt) > kernelNow());
23767
+ const watch = [...watched.values()].find(
23768
+ (item) => performance.now() - (watchAttempts.get(item.id) ?? -Infinity) > 3e4
23769
+ );
23770
+ if (!request && !watch) return;
23771
+ lookupBusy = true;
23772
+ if (!request && watch) watchAttempts.set(watch.id, performance.now());
23773
+ lookupWork = (async () => {
23774
+ const local = await readJson(join4(directory, "pairing.json"));
23775
+ if (!local) throw new Error("The local connection is unavailable");
23776
+ const server = await connectLookup();
23777
+ if (request) {
23778
+ const result = await deadline(
23779
+ lookupService.lookup(server, request, local, catalog2),
23780
+ 25e3
23781
+ );
23782
+ lookupResults.push({ id: request.id, ...result, error: null });
23783
+ } else if (watch) {
23784
+ watchResults.push(await linkedReceipt(server, watch, local, catalog2));
23785
+ watched.delete(watch.id);
23786
+ }
23787
+ })().catch(() => {
23788
+ lookupServer?.close();
23789
+ lookupServer = void 0;
23790
+ if (request)
23791
+ lookupResults.push({
23792
+ id: request.id,
23793
+ results: [],
23794
+ hasMore: false,
23795
+ error: "The local task could not be read. Check Codex and the saved project, then try again."
23796
+ });
23797
+ }).finally(() => {
23798
+ lookupBusy = false;
23799
+ });
23800
+ };
23801
+ const launches = async () => {
23802
+ if (!journalHealthy || !health.reachable || !health.hooksReady || !refreshed || performance.now() - refreshed >= 3e4 || !observer)
23803
+ return;
23804
+ const response = await post("launches", {});
23805
+ await mapConcurrent(response.launches, 4, (raw) => {
23806
+ const launch = codexLaunchSchema.parse(raw);
23807
+ return writers.run(launch.id, () => begin(launch)).then(
23808
+ () => schedule("events", 0, async () => {
23809
+ await localEvents();
23810
+ schedule("delivery", 0, deliver);
23811
+ })
23812
+ );
23813
+ });
23814
+ };
23815
+ const diagnostics = async () => {
23816
+ const pending = [...receipts.values()].filter((r2) => {
23817
+ const kind = r2.threadId && r2.cwd ? "receipt" : r2.phase === "drafted" ? "draft" : r2.failure ? "failure" : void 0;
23818
+ return kind && (r2.delivery?.acknowledged !== kind || kind === "receipt" && r2.delivery?.sequence !== r2.status.sequence);
23819
+ });
23820
+ const successes = [...receipts.values()].flatMap(
23821
+ (r2) => r2.delivery?.lastSuccessAt ? [r2.delivery.lastSuccessAt] : []
23822
+ );
23823
+ await writeJson(join4(directory, "recovery.json"), {
23824
+ lastSuccessfulDelivery: successes.length ? Math.max(...successes) : null,
23825
+ pending: pending.filter((r2) => !r2.delivery?.retry?.blocked).length,
23826
+ blocked: blocked.size + [...receipts.values()].filter(
23827
+ (r2) => r2.delivery?.retry?.blocked || r2.reconciliation?.blocked
23828
+ ).length + (await entries(join4(directory, "blocked-lookups"))).length,
23829
+ oldestPendingAt: pending.length ? Math.min(...pending.map((r2) => r2.delivery?.retry?.pendingSince ?? r2.preparedAt)) : null,
23830
+ issue: fatalError ?? [...laneErrors.values()][0] ?? null,
23831
+ failures: [...blocked].map((launchId) => ({
23832
+ launchId,
23833
+ code: "invalid_receipt",
23834
+ message: "A damaged receipt was isolated for local recovery. This launch will not be repeated."
23835
+ })).concat(
23836
+ [...receipts.values()].filter((r2) => r2.delivery?.retry?.blocked || r2.reconciliation?.blocked).map((r2) => ({
23837
+ launchId: r2.launch.id,
23838
+ ...r2.delivery?.retry?.blocked ?? r2.reconciliation?.blocked
23839
+ }))
23840
+ )
23841
+ });
23842
+ };
23843
+ persistDiagnostics = diagnostics;
23844
+ while (!signal.aborted && !stopping) {
23845
+ schedule("catalog", 1e4, async () => {
23846
+ await refreshCatalog();
23847
+ schedule("heartbeat", 0, heartbeat);
23848
+ });
23849
+ schedule("heartbeat", heartbeatHealth === JSON.stringify(health) ? 1e4 : 0, heartbeat);
23850
+ schedule("events", 0, localEvents);
23851
+ schedule("reconciliation", 2e3, reconcile);
23852
+ schedule("observation", 2e3, inspect);
23853
+ schedule("delivery", 0, deliver);
23854
+ schedule("lookups", 2e3, lookups);
23855
+ schedule("launches", 2e3, launches);
23856
+ schedule("diagnostics", 2e3, diagnostics);
23857
+ await lanes.settle();
23858
+ await delay(250, void 0, { signal }).catch(() => void 0);
23387
23859
  }
23388
23860
  } finally {
23861
+ stopping = true;
23389
23862
  await Promise.allSettled([...owners.values()].map((server) => server.stop()));
23390
23863
  await observer?.stop().catch(() => void 0);
23864
+ await catalogServer?.stop().catch(() => void 0);
23391
23865
  await lookupServer?.stop().catch(() => void 0);
23866
+ await lanes?.drain();
23392
23867
  await lookupWork;
23868
+ await Promise.allSettled(
23869
+ [
23870
+ ...owners.values(),
23871
+ ...observer ? [observer] : [],
23872
+ ...catalogServer ? [catalogServer] : [],
23873
+ ...lookupServer ? [lookupServer] : []
23874
+ ].map((server) => server.stop())
23875
+ );
23876
+ await persistDiagnostics?.().catch(() => void 0);
23393
23877
  await unlock();
23394
23878
  }
23395
23879
  }
@@ -23580,7 +24064,7 @@ import { setTimeout as delay2 } from "node:timers/promises";
23580
24064
  import { createInterface } from "node:readline/promises";
23581
24065
 
23582
24066
  // src/setup.ts
23583
- import { chmod as chmod2, copyFile, mkdir as mkdir4, rename as rename2, rm } from "node:fs/promises";
24067
+ import { chmod as chmod2, copyFile, mkdir as mkdir4, rename as rename3, rm } from "node:fs/promises";
23584
24068
  import { homedir as homedir4 } from "node:os";
23585
24069
  import { dirname as dirname2, join as join6, resolve as resolve2 } from "node:path";
23586
24070
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -23592,7 +24076,7 @@ async function installBundle(entrypoint2) {
23592
24076
  try {
23593
24077
  await copyFile(entrypoint2, temporary);
23594
24078
  await chmod2(temporary, 448);
23595
- await rename2(temporary, installedEntrypoint);
24079
+ await rename3(temporary, installedEntrypoint);
23596
24080
  } finally {
23597
24081
  await rm(temporary, { force: true });
23598
24082
  }
@@ -23639,12 +24123,12 @@ async function installService(id) {
23639
24123
 
23640
24124
  // src/onboarding.ts
23641
24125
  async function waitForStoppedCompanion(directory) {
23642
- const deadline = Date.now() + 2e4;
24126
+ const deadline2 = Date.now() + 2e4;
23643
24127
  for (; ; ) {
23644
24128
  try {
23645
24129
  return await processLock(directory);
23646
24130
  } catch (error51) {
23647
- if (Date.now() >= deadline) throw error51;
24131
+ if (Date.now() >= deadline2) throw error51;
23648
24132
  await delay2(200);
23649
24133
  }
23650
24134
  }
@@ -23771,10 +24255,11 @@ async function connectCodex(entrypoint2) {
23771
24255
  else process.stdout.write("Choose your project mapping in Kernel \u2192 Integrations \u2192 Codex.\n");
23772
24256
  }
23773
24257
  async function status() {
23774
- const profile = await signedInProfile();
23775
- process.stdout.write(`Signed in: ${profile.session.account}
24258
+ const profile = await readProfile();
24259
+ if (!profile?.session) throw new Error("Run krnl login first");
24260
+ process.stdout.write(`Account: ${profile.session.account} (saved locally)
23776
24261
  `);
23777
- const id = profile.session.machineId;
24262
+ const id = profile.session.machineId ?? profile.pendingMachineId;
23778
24263
  if (!id) {
23779
24264
  process.stdout.write("Codex is not connected. Run krnl connect codex.\n");
23780
24265
  return;
@@ -23793,6 +24278,32 @@ Saved projects: ${health.projects}
23793
24278
  if (health.health.issue) process.stdout.write(`${health.health.issue}
23794
24279
  `);
23795
24280
  }
24281
+ const recovery = await readJson(join7(pairingDirectory(id), "recovery.json"));
24282
+ if (recovery) {
24283
+ process.stdout.write(
24284
+ `Pending deliveries: ${recovery.pending} \xB7 Blocked: ${recovery.blocked}
24285
+ `
24286
+ );
24287
+ if (recovery.lastSuccessfulDelivery)
24288
+ process.stdout.write(
24289
+ `Last successful delivery: ${new Date(recovery.lastSuccessfulDelivery).toISOString()}
24290
+ `
24291
+ );
24292
+ if (recovery.oldestPendingAt)
24293
+ process.stdout.write(
24294
+ `Oldest pending delivery: ${Math.max(0, Math.floor((Date.now() - recovery.oldestPendingAt) / 1e3))} seconds
24295
+ `
24296
+ );
24297
+ if (recovery.issue) process.stdout.write(`${recovery.issue}
24298
+ `);
24299
+ for (const failure of recovery.failures)
24300
+ process.stdout.write(`${failure.launchId}: ${failure.message}
24301
+ `);
24302
+ if (recovery.blocked)
24303
+ process.stdout.write(
24304
+ "Blocked receipts remain on this Mac. Do not remove them or repeat their launches; use the existing Codex task while repairing the connection.\n"
24305
+ );
24306
+ }
23796
24307
  }
23797
24308
  async function disconnect(logout = false) {
23798
24309
  const profile = await readProfile();
@@ -23907,7 +24418,7 @@ async function main() {
23907
24418
  }
23908
24419
  if (command === "--version") {
23909
24420
  process.stdout.write(
23910
- `${false ? "development" : "0.1.4"}
24421
+ `${false ? "development" : "0.1.5"}
23911
24422
  `
23912
24423
  );
23913
24424
  return;