@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.
@@ -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";
@@ -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
- if (await realpath3(project.root).catch(() => null) !== project.root) continue;
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
- const destination = await resolveThreadProject(thread, pairing, catalog2);
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
- 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;
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 acknowledged = /* @__PURE__ */ new Set();
22828
- const reported = /* @__PURE__ */ new Map();
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 readdir2(join4(directory, "receipts")).catch(() => [])) {
23072
+ for (const file2 of await entries(join4(directory, "receipts"))) {
22833
23073
  if (!file2.endsWith(".json")) continue;
22834
- const receipt = await readJson(join4(directory, "receipts", file2));
22835
- if (receipt) receipts.set(receipt.launch.id, receipt);
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
- observer?.close();
22876
- observer = new AppServer(directory);
22877
- await observer.initialize();
22878
- return observer;
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 clockOffset = 0;
22895
- let reconciled = false;
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, Date.now() + clockOffset))
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, Date.now() + clockOffset))
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, Date.now() + clockOffset))
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
- while (!signal.aborted) {
23052
- const tickStarted = Date.now();
23053
- let networkError;
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 (Date.now() - refreshed >= 1e4) {
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
- 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."
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 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)
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: "Checking the saved task after reconnection."
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
- 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;
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
- 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)) {
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") continue;
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
- 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
- }
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
- if (receipt && event && applyObservation(receipt, event)) await save(receipt);
23251
- await unlink(path);
23584
+ return;
23252
23585
  }
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());
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 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 {
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 (networkError || deliveryBudget <= 0 || Date.now() - tickStarted > 5e3) continue;
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 (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);
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
- } 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
23649
+ const { data } = await observer.call(
23650
+ "thread/turns/list",
23651
+ { threadId: receipt.threadId, limit: 1, itemsView: "notLoaded" },
23652
+ 2e3
23342
23653
  );
23343
- const watch = [...watched.values()].find(
23344
- (item) => Date.now() - (watchAttempts.get(item.id) ?? 0) > 3e4
23345
- );
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;
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
- } 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;
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
- await delay(2e3, void 0, { signal }).catch(() => void 0);
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 rename2, rm } from "node:fs/promises";
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 rename2(temporary, installedEntrypoint);
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 deadline = Date.now() + 2e4;
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() >= deadline) throw error51;
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 signedInProfile();
23775
- process.stdout.write(`Signed in: ${profile.session.account}
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.4"}
24454
+ `${false ? "development" : "0.1.7"}
23911
24455
  `
23912
24456
  );
23913
24457
  return;