@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 +22 -1
- package/dist/kernel-companion.mjs +366 -21
- package/package.json +2 -2
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
|
|
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(
|
|
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, ...
|
|
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((
|
|
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 },
|
|
15667
|
+
server.listen({ host: "127.0.0.1", port, exclusive: true }, resolve3);
|
|
15668
15668
|
});
|
|
15669
15669
|
return async () => {
|
|
15670
|
-
await new Promise((
|
|
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((
|
|
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 ?
|
|
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((
|
|
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) =>
|
|
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((
|
|
16044
|
+
await new Promise((resolve3, reject) => {
|
|
16045
16045
|
const finish = () => {
|
|
16046
16046
|
clearTimeout(escalate);
|
|
16047
16047
|
clearTimeout(deadline);
|
|
16048
|
-
|
|
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
|
|
23237
|
+
let resolve3;
|
|
22893
23238
|
let reject;
|
|
22894
23239
|
const result = new Promise((yes, no2) => {
|
|
22895
|
-
|
|
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
|
|
23277
|
+
else resolve3(code);
|
|
22933
23278
|
});
|
|
22934
|
-
await new Promise((
|
|
23279
|
+
await new Promise((resolve4, reject2) => {
|
|
22935
23280
|
server.once("error", reject2);
|
|
22936
|
-
server.listen(0, "127.0.0.1",
|
|
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((
|
|
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 (
|
|
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.
|
|
23697
|
+
`${false ? "development" : "0.1.2"}
|
|
23353
23698
|
`
|
|
23354
23699
|
);
|
|
23355
23700
|
return;
|
package/package.json
CHANGED