@supercorks/krnl 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,7 @@ krnl connect codex
57
57
  ```
58
58
 
59
59
  Version **0.1.1 or later** is required to launch with mapping model and reasoning settings.
60
+ Version **0.1.2 or later** also supports linking an existing Codex task.
60
61
  Kernel explains when an older companion needs this update; existing task monitoring continues.
61
62
 
62
63
  Repeated connection reuses the machine identity. Review changed hook commands in `/hooks` if Node
@@ -83,6 +84,26 @@ Existing companion installations keep their allowlist until you explicitly upgra
83
84
  `krnl login` and `krnl connect codex`. The upgrade retains their machine identity, mappings, and
84
85
  handoff history when the existing connection is still authorized.
85
86
 
87
+ ## Link an existing task
88
+
89
+ On a Kernel task, choose **Link existing task**. Search by the current Codex task title
90
+ (at least three characters) or paste a `codex://threads/<task-id>` link, select the match,
91
+ and choose **Link task**. Choose a Mac if more than one is connected. Only tasks in
92
+ compatible, locally authorized saved projects appear. Archived tasks are labeled.
93
+
94
+ Linking adds monitoring and a direct Codex link; it never starts a turn, changes its model,
95
+ or changes its folder. A Codex task can be associated with one Kernel task per Mac;
96
+ linking it again to that same task returns the existing association.
97
+
98
+ Search uses a short-lived local metadata cache and never searches messages or previews.
99
+ Kernel receives at most 25 matching titles and destination metadata per lookup. Searches
100
+ expire after 30 seconds, with resolved matches available for two minutes. Refine a broad
101
+ query or paste a deep link if the catalog cannot be searched in time.
102
+
103
+ Completed tasks become Done after their saved turn metadata is checked. A task already
104
+ executing or awaiting input when linked can remain Disconnected until its next status hook;
105
+ continue in Codex normally. Subsequent hooks provide Running, waiting, and Done transitions.
106
+
86
107
  ## Local data and troubleshooting
87
108
 
88
109
  The executable is copied to a stable location under
@@ -100,7 +121,7 @@ revoked the Mac remotely, sign in again with `krnl login` before reconnecting. I
100
121
  reach Kernel, the disconnect command stops the local service and asks you to retry online.
101
122
 
102
123
  Disconnect and logout preserve Codex tasks, local creation receipts, and worktrees. Hooks retain
103
- only lifecycle metadata for tasks created by this connection. Neither transcripts nor attachment
124
+ only lifecycle metadata for tasks launched or explicitly linked through this connection. Neither transcripts nor attachment
104
125
  contents are uploaded. Keep receipts intact when investigating an uncertain launch; the companion
105
126
  will reconcile creation before retrying an acknowledgement and will not repeat an uncertain turn.
106
127
 
@@ -12425,12 +12425,12 @@ function describe(description) {
12425
12425
  return ch;
12426
12426
  }
12427
12427
  // @__NO_SIDE_EFFECTS__
12428
- function meta(metadata) {
12428
+ function meta(metadata2) {
12429
12429
  const ch = new $ZodCheck({ check: "meta" });
12430
12430
  ch._zod.onattach = [
12431
12431
  (inst) => {
12432
12432
  const existing = globalRegistry.get(inst) ?? {};
12433
- globalRegistry.add(inst, { ...existing, ...metadata });
12433
+ globalRegistry.add(inst, { ...existing, ...metadata2 });
12434
12434
  }
12435
12435
  ];
12436
12436
  ch._zod.check = () => {
@@ -15659,15 +15659,15 @@ async function writeJson(path, value) {
15659
15659
  async function processLock(directory) {
15660
15660
  const port = 4e4 + createHash("sha256").update(directory).digest().readUInt16BE(0) % 2e4;
15661
15661
  const server = createServer((socket) => socket.destroy());
15662
- await new Promise((resolve2, reject) => {
15662
+ await new Promise((resolve3, reject) => {
15663
15663
  server.once(
15664
15664
  "error",
15665
15665
  () => reject(new Error("A companion already owns this pairing, or its local lock port is in use"))
15666
15666
  );
15667
- server.listen({ host: "127.0.0.1", port, exclusive: true }, resolve2);
15667
+ server.listen({ host: "127.0.0.1", port, exclusive: true }, resolve3);
15668
15668
  });
15669
15669
  return async () => {
15670
- await new Promise((resolve2) => server.close(() => resolve2()));
15670
+ await new Promise((resolve3) => server.close(() => resolve3()));
15671
15671
  };
15672
15672
  }
15673
15673
  function processAlive(pid) {
@@ -15710,12 +15710,12 @@ async function readCredential(id, keychainService = service) {
15710
15710
  async function saveCredential(id, token, keychainService = service) {
15711
15711
  external_exports.uuid().parse(id);
15712
15712
  external_exports.string().regex(/^[A-Za-z0-9_-]{43,128}$/).parse(token);
15713
- await new Promise((resolve2, reject) => {
15713
+ await new Promise((resolve3, reject) => {
15714
15714
  const child = spawn("/usr/bin/security", ["-i"], { stdio: ["pipe", "ignore", "ignore"] });
15715
15715
  child.on("error", reject);
15716
15716
  child.on(
15717
15717
  "exit",
15718
- (code) => code === 0 ? resolve2() : reject(new Error("Keychain could not save the machine credential"))
15718
+ (code) => code === 0 ? resolve3() : reject(new Error("Keychain could not save the machine credential"))
15719
15719
  );
15720
15720
  child.stdin.end(`add-generic-password -U -s "${keychainService}" -a "${id}" -w "${token}"
15721
15721
  `);
@@ -16016,12 +16016,12 @@ var AppServer = class extends EventEmitter {
16016
16016
  call(method, params, timeoutMs = 3e4) {
16017
16017
  if (this.closed) return Promise.reject(new Error("Codex connection closed"));
16018
16018
  const id = ++this.nextId;
16019
- return new Promise((resolve2, reject) => {
16019
+ return new Promise((resolve3, reject) => {
16020
16020
  const timer = setTimeout(() => {
16021
16021
  this.pending.delete(id);
16022
16022
  reject(new Error(`Codex ${method} acknowledgement is uncertain`));
16023
16023
  }, timeoutMs);
16024
- this.pending.set(id, { resolve: (value) => resolve2(value), reject, timer });
16024
+ this.pending.set(id, { resolve: (value) => resolve3(value), reject, timer });
16025
16025
  this.child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
16026
16026
  });
16027
16027
  }
@@ -16041,11 +16041,11 @@ var AppServer = class extends EventEmitter {
16041
16041
  this.close();
16042
16042
  return;
16043
16043
  }
16044
- await new Promise((resolve2, reject) => {
16044
+ await new Promise((resolve3, reject) => {
16045
16045
  const finish = () => {
16046
16046
  clearTimeout(escalate);
16047
16047
  clearTimeout(deadline);
16048
- resolve2();
16048
+ resolve3();
16049
16049
  };
16050
16050
  const escalate = setTimeout(() => this.child.kill("SIGKILL"), 3e3);
16051
16051
  const deadline = setTimeout(() => {
@@ -22099,6 +22099,7 @@ var codexHealthSchema = external_exports.strictObject({
22099
22099
  hooksReady: external_exports.boolean(),
22100
22100
  // Older companions cannot parse model settings in strict launch payloads.
22101
22101
  supportsModelSelection: external_exports.boolean().optional(),
22102
+ supportsThreadLinking: external_exports.boolean().optional(),
22102
22103
  models: external_exports.array(codexModelSchema).max(100).optional(),
22103
22104
  appServerVersion: external_exports.string().max(100),
22104
22105
  desktopVersion: external_exports.string().max(100),
@@ -22225,6 +22226,83 @@ function codexLaunchPrompt(launch) {
22225
22226
  ].join("\n");
22226
22227
  }
22227
22228
 
22229
+ // ../domain/src/codex-threads.ts
22230
+ var CODEX_THREAD_SEARCH_MIN = 3;
22231
+ var CODEX_THREAD_RESULT_LIMIT = 25;
22232
+ var codexTitleQuerySchema = external_exports.string().trim().max(200).refine(
22233
+ (value) => [...value].length >= CODEX_THREAD_SEARCH_MIN,
22234
+ "Enter at least 3 characters to search task titles"
22235
+ );
22236
+ function codexThreadIdFromUrl(value) {
22237
+ try {
22238
+ if (!/^codex:\/\/threads\/[0-9a-f-]+\/?$/i.test(value.trim())) return null;
22239
+ const url2 = new URL(value.trim());
22240
+ if (url2.protocol !== "codex:" || url2.hostname !== "threads" || url2.port || url2.username || url2.password || url2.search || url2.hash)
22241
+ return null;
22242
+ const match = /^\/([^/]+)\/?$/.exec(url2.pathname);
22243
+ const id = external_exports.uuid().safeParse(match?.[1]);
22244
+ return id.success ? id.data.toLowerCase() : null;
22245
+ } catch {
22246
+ return null;
22247
+ }
22248
+ }
22249
+ var base = {
22250
+ protocolVersion: external_exports.literal(1),
22251
+ requestId: external_exports.uuid(),
22252
+ taskId: external_exports.uuid(),
22253
+ machineId: external_exports.uuid()
22254
+ };
22255
+ var startCodexThreadLookupSchema = external_exports.discriminatedUnion("kind", [
22256
+ external_exports.strictObject({ ...base, kind: external_exports.literal("search"), query: codexTitleQuerySchema }),
22257
+ external_exports.strictObject({
22258
+ ...base,
22259
+ kind: external_exports.literal("link"),
22260
+ url: external_exports.string().trim().max(200).refine(
22261
+ (value) => codexThreadIdFromUrl(value) !== null,
22262
+ "Paste a codex://threads/<task-id> link"
22263
+ )
22264
+ })
22265
+ ]);
22266
+ var codexThreadResultSchema = external_exports.strictObject({
22267
+ id: external_exports.uuid(),
22268
+ title: external_exports.string().trim().min(1).max(300),
22269
+ project: codexCatalogProjectSchema,
22270
+ cwd: external_exports.string().min(1).max(4096),
22271
+ updatedAt: external_exports.iso.datetime(),
22272
+ archived: external_exports.boolean()
22273
+ });
22274
+ var codexThreadLookupWorkSchema = external_exports.strictObject({
22275
+ id: external_exports.uuid(),
22276
+ query: codexTitleQuerySchema.nullable(),
22277
+ threadId: external_exports.uuid().nullable(),
22278
+ expiresAt: external_exports.iso.datetime()
22279
+ }).refine(
22280
+ (value) => value.query === null !== (value.threadId === null),
22281
+ "Choose title search or a task link"
22282
+ );
22283
+ var codexThreadLookupResultSchema = external_exports.strictObject({
22284
+ protocolVersion: external_exports.literal(1),
22285
+ connectionId: external_exports.uuid(),
22286
+ generation: external_exports.number().int().nonnegative(),
22287
+ id: external_exports.uuid(),
22288
+ results: external_exports.array(codexThreadResultSchema).max(CODEX_THREAD_RESULT_LIMIT),
22289
+ hasMore: external_exports.boolean(),
22290
+ error: external_exports.string().max(500).nullable()
22291
+ });
22292
+ var linkCodexThreadSchema = external_exports.strictObject({
22293
+ ...base,
22294
+ lookupId: external_exports.uuid(),
22295
+ threadId: external_exports.uuid()
22296
+ });
22297
+ var codexWatchSchema = external_exports.strictObject({
22298
+ id: external_exports.uuid(),
22299
+ machineId: external_exports.uuid(),
22300
+ sequence: external_exports.number().int().nonnegative(),
22301
+ linkedThreadId: external_exports.uuid(),
22302
+ title: external_exports.string().min(1).max(300),
22303
+ destination: codexCatalogProjectSchema
22304
+ });
22305
+
22228
22306
  // ../domain/src/cli.ts
22229
22307
  var CLI_PROTOCOL_VERSION = 1;
22230
22308
  var CLI_LOGIN_TTL_MS = 5 * 6e4;
@@ -22363,6 +22441,192 @@ function resolveModelSelection(launch, models) {
22363
22441
  return { model, reasoningEffort };
22364
22442
  }
22365
22443
 
22444
+ // src/threads.ts
22445
+ import { realpath as realpath2, stat as stat2 } from "node:fs/promises";
22446
+ import { isAbsolute as isAbsolute2, relative, resolve } from "node:path";
22447
+ var metadata = external_exports.object({
22448
+ id: external_exports.uuid(),
22449
+ name: external_exports.string().nullable().optional(),
22450
+ projectId: external_exports.string().nullable().optional(),
22451
+ cwd: external_exports.string(),
22452
+ updatedAt: external_exports.number().finite().nonnegative()
22453
+ });
22454
+ var pageSchema = external_exports.object({
22455
+ data: external_exports.array(metadata).max(200),
22456
+ nextCursor: external_exports.string().nullable().optional()
22457
+ });
22458
+ var sourceKinds = [
22459
+ "cli",
22460
+ "vscode",
22461
+ "exec",
22462
+ "appServer",
22463
+ "subAgent",
22464
+ "subAgentReview",
22465
+ "subAgentCompact",
22466
+ "subAgentThreadSpawn",
22467
+ "subAgentOther",
22468
+ "unknown"
22469
+ ];
22470
+ var inside = (root, folder) => {
22471
+ const path = relative(root, folder);
22472
+ return !isAbsolute2(path) && path !== ".." && !path.startsWith("../");
22473
+ };
22474
+ async function commonGitDirectory(folder) {
22475
+ try {
22476
+ const { stdout } = await execute("git", ["-C", folder, "rev-parse", "--git-common-dir"], {
22477
+ timeout: 1500,
22478
+ maxBuffer: 8192
22479
+ });
22480
+ return await realpath2(resolve(folder, stdout.trim()));
22481
+ } catch {
22482
+ return null;
22483
+ }
22484
+ }
22485
+ async function resolveThreadProject(thread, pairing, catalog2) {
22486
+ const authorized = catalog2.filter(
22487
+ (project) => !project.issue && (pairing.projectAccess === "all-saved" || pairing.projects.some(
22488
+ (allowed) => allowed.id === project.id && allowed.root === project.root
22489
+ ))
22490
+ );
22491
+ let cwd;
22492
+ try {
22493
+ cwd = await realpath2(thread.cwd);
22494
+ if (!(await stat2(cwd)).isDirectory()) return null;
22495
+ } catch {
22496
+ return null;
22497
+ }
22498
+ const candidates = thread.projectId ? authorized.filter((project) => project.id === thread.projectId) : [...authorized].sort((a2, b2) => b2.root.length - a2.root.length);
22499
+ for (const project of candidates) {
22500
+ if (await realpath2(project.root).catch(() => null) !== project.root) continue;
22501
+ if (inside(project.root, cwd)) return { project, cwd };
22502
+ }
22503
+ const common = candidates.some((project) => project.isGit) ? await commonGitDirectory(cwd) : null;
22504
+ if (common)
22505
+ for (const project of candidates) {
22506
+ if (project.isGit && await commonGitDirectory(project.root) === common)
22507
+ return { project, cwd };
22508
+ }
22509
+ return null;
22510
+ }
22511
+ var CodexThreadLookupService = class {
22512
+ constructor(now = () => performance.now()) {
22513
+ this.now = now;
22514
+ }
22515
+ now;
22516
+ cached = null;
22517
+ refreshedAt = 0;
22518
+ loading;
22519
+ index(server) {
22520
+ if (this.cached && this.now() - this.refreshedAt < 3e4) return Promise.resolve(this.cached);
22521
+ if (this.loading) return this.loading;
22522
+ this.loading = (async () => {
22523
+ const threads = [];
22524
+ const started = this.now();
22525
+ for (const archived of [false, true]) {
22526
+ let cursor = null;
22527
+ const visited = /* @__PURE__ */ new Set();
22528
+ do {
22529
+ if (this.now() - started > 2e4 || threads.length > 5e4)
22530
+ throw new Error(
22531
+ "The task catalog is too large to search right now. Paste a Codex task link instead."
22532
+ );
22533
+ const page = pageSchema.parse(
22534
+ await server.call(
22535
+ "thread/list",
22536
+ {
22537
+ limit: 200,
22538
+ archived,
22539
+ modelProviders: [],
22540
+ sourceKinds,
22541
+ useStateDbOnly: true,
22542
+ sortKey: "updated_at",
22543
+ ...cursor ? { cursor } : {}
22544
+ },
22545
+ 5e3
22546
+ )
22547
+ );
22548
+ threads.push(...page.data.map((thread) => ({ ...thread, archived })));
22549
+ cursor = page.nextCursor ?? null;
22550
+ if (cursor && visited.has(cursor))
22551
+ throw new Error("Codex returned an invalid task catalog. Try again.");
22552
+ if (cursor) visited.add(cursor);
22553
+ } while (cursor);
22554
+ }
22555
+ this.cached = [...new Map(threads.map((thread) => [thread.id, thread])).values()].sort(
22556
+ (a2, b2) => b2.updatedAt - a2.updatedAt || a2.id.localeCompare(b2.id)
22557
+ );
22558
+ this.refreshedAt = this.now();
22559
+ return this.cached;
22560
+ })().finally(() => {
22561
+ this.loading = void 0;
22562
+ });
22563
+ return this.loading;
22564
+ }
22565
+ async lookup(server, raw, pairing, catalog2) {
22566
+ const work = codexThreadLookupWorkSchema.parse(raw);
22567
+ let candidates;
22568
+ if (work.threadId) {
22569
+ const result = external_exports.object({ thread: metadata }).parse(
22570
+ await server.call("thread/read", { threadId: work.threadId, includeTurns: false }, 5e3)
22571
+ );
22572
+ if (result.thread.id !== work.threadId) throw new Error("Codex returned a different task.");
22573
+ candidates = [{ ...result.thread, archived: false }];
22574
+ } else {
22575
+ const query = work.query.toLowerCase();
22576
+ candidates = (await this.index(server)).filter(
22577
+ (thread) => thread.name?.trim().slice(0, 300).toLowerCase().includes(query)
22578
+ );
22579
+ }
22580
+ const results = [];
22581
+ const started = this.now();
22582
+ for (const thread of candidates) {
22583
+ if (this.now() - started > 2e4)
22584
+ throw new Error("Refine the title or paste a Codex task link.");
22585
+ const destination = await resolveThreadProject(thread, pairing, catalog2);
22586
+ if (!destination) continue;
22587
+ if (results.length === CODEX_THREAD_RESULT_LIMIT) return { results, hasMore: true };
22588
+ results.push({
22589
+ id: thread.id,
22590
+ title: thread.name?.trim().slice(0, 300) || "Untitled task",
22591
+ ...destination,
22592
+ updatedAt: new Date(thread.updatedAt * 1e3).toISOString(),
22593
+ archived: thread.archived
22594
+ });
22595
+ }
22596
+ return { results, hasMore: false };
22597
+ }
22598
+ };
22599
+ async function linkedReceipt(server, watch, pairing, catalog2) {
22600
+ if (watch.machineId !== pairing.machineId)
22601
+ throw new Error("This task belongs to another machine");
22602
+ const { thread } = external_exports.object({ thread: metadata }).parse(
22603
+ await server.call(
22604
+ "thread/read",
22605
+ { threadId: watch.linkedThreadId, includeTurns: false },
22606
+ 5e3
22607
+ )
22608
+ );
22609
+ if (thread.id !== watch.linkedThreadId) throw new Error("Codex returned a different task");
22610
+ const destination = await resolveThreadProject(thread, pairing, catalog2);
22611
+ if (!destination || destination.project.id !== watch.destination.id || destination.project.root !== watch.destination.root)
22612
+ throw new Error("The saved project is no longer locally authorized");
22613
+ return {
22614
+ launch: watch,
22615
+ preparedAt: Date.now(),
22616
+ phase: "released",
22617
+ threadId: thread.id,
22618
+ cwd: destination.cwd,
22619
+ status: {
22620
+ launchId: watch.id,
22621
+ sequence: watch.sequence + 1,
22622
+ state: "unreachable",
22623
+ waiting: false,
22624
+ turnId: null,
22625
+ message: "Checking the linked task in Codex. Activity is observed from the next status hook."
22626
+ }
22627
+ };
22628
+ }
22629
+
22366
22630
  // src/runtime.ts
22367
22631
  var sourceMarker = (id) => `kernel-handoff:${id}`;
22368
22632
  function initialReceipt(launch) {
@@ -22381,7 +22645,7 @@ function initialReceipt(launch) {
22381
22645
  };
22382
22646
  }
22383
22647
  function canBegin(receipt, now) {
22384
- return receipt.phase === "prepared" && now < Date.parse(receipt.launch.expiresAt);
22648
+ return receipt.phase === "prepared" && "expiresAt" in receipt.launch && now < Date.parse(receipt.launch.expiresAt);
22385
22649
  }
22386
22650
  async function findCreation(server, receipt) {
22387
22651
  let cursor = null;
@@ -22410,6 +22674,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
22410
22674
  const unlock = await processLock(directory);
22411
22675
  const owners = /* @__PURE__ */ new Map();
22412
22676
  let observer;
22677
+ let lookupServer;
22678
+ let lookupWork;
22413
22679
  try {
22414
22680
  const pairing = await readJson(join4(directory, "pairing.json"));
22415
22681
  if (!pairing?.workspaceId) throw new Error("Complete this machine pairing first");
@@ -22437,6 +22703,26 @@ async function runCompanion(machineId, entrypoint2, signal) {
22437
22703
  )
22438
22704
  );
22439
22705
  const queue = [];
22706
+ const lookupService = new CodexThreadLookupService();
22707
+ let lookupBusy = false;
22708
+ const lookupResults = [];
22709
+ const watched = /* @__PURE__ */ new Map();
22710
+ const watchAttempts = /* @__PURE__ */ new Map();
22711
+ const watchResults = [];
22712
+ let watchCursor = null;
22713
+ const connectLookup = async () => {
22714
+ if (!lookupServer) {
22715
+ lookupServer = new AppServer(directory);
22716
+ try {
22717
+ await lookupServer.initialize();
22718
+ } catch (error51) {
22719
+ lookupServer.close();
22720
+ lookupServer = void 0;
22721
+ throw error51;
22722
+ }
22723
+ }
22724
+ return lookupServer;
22725
+ };
22440
22726
  const connectObserver = async () => {
22441
22727
  observer?.close();
22442
22728
  observer = new AppServer(directory);
@@ -22448,6 +22734,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
22448
22734
  reachable: false,
22449
22735
  hooksReady: false,
22450
22736
  supportsModelSelection: true,
22737
+ supportsThreadLinking: true,
22451
22738
  models: [],
22452
22739
  appServerVersion: "",
22453
22740
  desktopVersion: "",
@@ -22625,6 +22912,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
22625
22912
  reachable: true,
22626
22913
  hooksReady: ready,
22627
22914
  supportsModelSelection: true,
22915
+ supportsThreadLinking: true,
22628
22916
  models,
22629
22917
  appServerVersion: stdout.trim(),
22630
22918
  desktopVersion: desktop,
@@ -22702,6 +22990,18 @@ async function runCompanion(machineId, entrypoint2, signal) {
22702
22990
  if (!reconciled || recovered) await indexThreads();
22703
22991
  reconciled = true;
22704
22992
  }
22993
+ for (const receipt of watchResults.splice(0)) {
22994
+ if (receipts.has(receipt.launch.id)) continue;
22995
+ receipts.set(receipt.launch.id, receipt);
22996
+ await save(receipt);
22997
+ await indexThreads();
22998
+ }
22999
+ if (!networkError) {
23000
+ while (lookupResults.length) {
23001
+ await post("thread-lookup-result", lookupResults[0]);
23002
+ lookupResults.shift();
23003
+ }
23004
+ }
22705
23005
  for (const { launchId, event } of queue.splice(0)) {
22706
23006
  const receipt = receipts.get(launchId);
22707
23007
  if (!receipt || receipt.phase === "released" || receipt.phase === "failed") continue;
@@ -22816,6 +23116,49 @@ async function runCompanion(machineId, entrypoint2, signal) {
22816
23116
  }
22817
23117
  if (networkError) throw networkError;
22818
23118
  if (health.reachable && health.hooksReady && reconciled && Date.now() - tickStarted <= 5e3) {
23119
+ const lookups = await post("thread-lookups", { cursor: watchCursor });
23120
+ watchCursor = lookups.nextCursor;
23121
+ for (const raw of lookups.watches) {
23122
+ const watch = codexWatchSchema.parse(raw);
23123
+ if (watch.machineId === machineId && !receipts.has(watch.id))
23124
+ watched.set(watch.id, watch);
23125
+ }
23126
+ if (!lookupBusy) {
23127
+ const request = lookups.requests.find(
23128
+ (item) => Date.parse(item.expiresAt) > Date.now() + clockOffset
23129
+ );
23130
+ const watch = [...watched.values()].find(
23131
+ (item) => Date.now() - (watchAttempts.get(item.id) ?? 0) > 3e4
23132
+ );
23133
+ if (request || watch) {
23134
+ lookupBusy = true;
23135
+ if (!request && watch) watchAttempts.set(watch.id, Date.now());
23136
+ lookupWork = (async () => {
23137
+ const local = await readJson(join4(directory, "pairing.json"));
23138
+ if (!local) throw new Error("The local connection is unavailable");
23139
+ const server = await connectLookup();
23140
+ if (request) {
23141
+ const result = await lookupService.lookup(server, request, local, catalog2);
23142
+ lookupResults.push({ id: request.id, ...result, error: null });
23143
+ } else if (watch) {
23144
+ watchResults.push(await linkedReceipt(server, watch, local, catalog2));
23145
+ watched.delete(watch.id);
23146
+ }
23147
+ })().catch(() => {
23148
+ lookupServer?.close();
23149
+ lookupServer = void 0;
23150
+ if (request)
23151
+ lookupResults.push({
23152
+ id: request.id,
23153
+ results: [],
23154
+ hasMore: false,
23155
+ error: "The local task could not be read. Check Codex and the saved project, then try again."
23156
+ });
23157
+ }).finally(() => {
23158
+ lookupBusy = false;
23159
+ });
23160
+ }
23161
+ }
22819
23162
  const response = await post("launches", {});
22820
23163
  for (const launch of response.launches) await begin(codexLaunchSchema.parse(launch));
22821
23164
  }
@@ -22832,6 +23175,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
22832
23175
  } finally {
22833
23176
  await Promise.allSettled([...owners.values()].map((server) => server.stop()));
22834
23177
  await observer?.stop().catch(() => void 0);
23178
+ await lookupServer?.stop().catch(() => void 0);
23179
+ await lookupWork;
22835
23180
  await unlock();
22836
23181
  }
22837
23182
  }
@@ -22889,10 +23234,10 @@ async function signedInProfile() {
22889
23234
  }
22890
23235
  async function loginCallback(state, timeoutMs = 3e5) {
22891
23236
  cliSecretSchema.parse(state);
22892
- let resolve2;
23237
+ let resolve3;
22893
23238
  let reject;
22894
23239
  const result = new Promise((yes, no2) => {
22895
- resolve2 = yes;
23240
+ resolve3 = yes;
22896
23241
  reject = no2;
22897
23242
  });
22898
23243
  void result.catch(() => void 0);
@@ -22929,11 +23274,11 @@ async function loginCallback(state, timeoutMs = 3e5) {
22929
23274
  `<html><title>Kernel CLI</title><body style="font:18px system-ui;padding:48px"><h1>${cancelled ? "Login cancelled" : "Return to your terminal"}</h1><p>${cancelled ? "No CLI login was granted." : "Kernel is finishing your CLI login. You can close this tab."}</p></body></html>`
22930
23275
  );
22931
23276
  if (cancelled) reject(new Error("Login cancelled. No connection was created."));
22932
- else resolve2(code);
23277
+ else resolve3(code);
22933
23278
  });
22934
- await new Promise((resolve3, reject2) => {
23279
+ await new Promise((resolve4, reject2) => {
22935
23280
  server.once("error", reject2);
22936
- server.listen(0, "127.0.0.1", resolve3);
23281
+ server.listen(0, "127.0.0.1", resolve4);
22937
23282
  });
22938
23283
  port = server.address().port;
22939
23284
  const timer = setTimeout(
@@ -22943,7 +23288,7 @@ async function loginCallback(state, timeoutMs = 3e5) {
22943
23288
  const close = async () => {
22944
23289
  clearTimeout(timer);
22945
23290
  server.closeAllConnections();
22946
- await new Promise((resolve3) => server.close(() => resolve3()));
23291
+ await new Promise((resolve4) => server.close(() => resolve4()));
22947
23292
  };
22948
23293
  return { redirectUri: `http://127.0.0.1:${port}/callback`, result, close };
22949
23294
  }
@@ -23024,12 +23369,12 @@ import { createInterface } from "node:readline/promises";
23024
23369
  // src/setup.ts
23025
23370
  import { chmod as chmod2, copyFile, mkdir as mkdir4, rename as rename2, rm } from "node:fs/promises";
23026
23371
  import { homedir as homedir4 } from "node:os";
23027
- import { dirname as dirname2, join as join6, resolve } from "node:path";
23372
+ import { dirname as dirname2, join as join6, resolve as resolve2 } from "node:path";
23028
23373
  import { randomUUID as randomUUID5 } from "node:crypto";
23029
23374
  var installedEntrypoint = join6(dataRoot, "bin/kernel-companion.mjs");
23030
23375
  async function installBundle(entrypoint2) {
23031
23376
  await mkdir4(dirname2(installedEntrypoint), { recursive: true, mode: 448 });
23032
- if (resolve(entrypoint2) !== resolve(installedEntrypoint)) {
23377
+ if (resolve2(entrypoint2) !== resolve2(installedEntrypoint)) {
23033
23378
  const temporary = `${installedEntrypoint}.${randomUUID5()}.tmp`;
23034
23379
  try {
23035
23380
  await copyFile(entrypoint2, temporary);
@@ -23349,7 +23694,7 @@ async function main() {
23349
23694
  }
23350
23695
  if (command === "--version") {
23351
23696
  process.stdout.write(
23352
- `${false ? "development" : "0.1.1"}
23697
+ `${false ? "development" : "0.1.2"}
23353
23698
  `
23354
23699
  );
23355
23700
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercorks/krnl",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Connect Kernel to Codex on your Mac",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,4 +17,4 @@
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  }
20
- }
20
+ }