@supercorks/krnl 0.1.12 → 0.1.13
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 +20 -1
- package/dist/kernel-companion.mjs +221 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,10 +81,22 @@ projects with one accessible local folder. Git mappings default to a new worktre
|
|
|
81
81
|
needed. Non-Git projects use their saved folder. Missing folders, multi-root projects, and disabled
|
|
82
82
|
project hooks are explained beside that project in Kernel.
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
New Work checkouts live in Codex's configured worktree directory (normally
|
|
85
|
+
`~/.codex/worktrees/<full-task-id>/<repository-name>`), so Desktop can group them under
|
|
86
|
+
the selected saved project. Each task still has its own branch and checkout. Existing
|
|
87
|
+
checkouts and their saved paths are preserved when the companion updates.
|
|
88
|
+
|
|
89
|
+
Tested Codex capabilities: desktop **26.908.40834** with bundled CLI **0.154.0-alpha.6.2**. The companion prefers
|
|
85
90
|
`/Applications/ChatGPT.app/Contents/Resources/codex`, falling back to `codex` on PATH. The app-server
|
|
86
91
|
capabilities are experimental, so other Codex versions require verification.
|
|
87
92
|
|
|
93
|
+
In Kernel, **Stop** beside **Open in Codex** interrupts the companion-owned first Work turn
|
|
94
|
+
and releases the same saved task to desktop. Kernel shows **Stopping…** until release is
|
|
95
|
+
confirmed, then **Stopped. Continue in Codex.** It stays open and does not send a new message,
|
|
96
|
+
mark the Kernel task complete, or discard completed work. If confirmation takes longer than
|
|
97
|
+
fifteen seconds, **Retry Stop** retains the same durable request. Desktop continuations, Drafts,
|
|
98
|
+
and linked tasks have no Stop control in Kernel.
|
|
99
|
+
|
|
88
100
|
The companion releases its first-turn app-server after completion or a request for input. Continue
|
|
89
101
|
in desktop after that release; an interrupted structured question may be asked again. Kernel
|
|
90
102
|
observes Running/Done across later desktop turns through hooks. It does not answer questions or
|
|
@@ -206,6 +218,13 @@ Install the current CLI and run `krnl connect codex` with the current Kernel ser
|
|
|
206
218
|
Hooks update every association and retain an event until all receipt writes succeed, including
|
|
207
219
|
across restarts.
|
|
208
220
|
|
|
221
|
+
CLI 0.1.13 adds first-turn ownership reports and durable stop delivery. Deploy the matching
|
|
222
|
+
Kernel migration and web contract before updating this package. Startup enriches historical
|
|
223
|
+
receipts with unknown ownership; it never assumes that a Desktop turn belongs to the companion.
|
|
224
|
+
An accepted stop survives network failures. After restart, release is confirmed only when the
|
|
225
|
+
saved owner's exit or process-identity replacement can be proved; inaccessible process metadata
|
|
226
|
+
remains unknown. Wait for active first turns to finish before restarting the service to upgrade.
|
|
227
|
+
|
|
209
228
|
## Bundled third-party software
|
|
210
229
|
|
|
211
230
|
This executable includes Zod under the following license:
|
|
@@ -2737,13 +2737,13 @@ function time(args) {
|
|
|
2737
2737
|
return new RegExp(`^${timeSource(args)}$`);
|
|
2738
2738
|
}
|
|
2739
2739
|
function datetime(args) {
|
|
2740
|
-
const
|
|
2740
|
+
const time4 = timeSource({ precision: args.precision });
|
|
2741
2741
|
const opts = ["Z"];
|
|
2742
2742
|
if (args.local)
|
|
2743
2743
|
opts.push("");
|
|
2744
2744
|
if (args.offset)
|
|
2745
2745
|
opts.push(`([+-](?:[01]\\d|2[0-3]):[0-5]\\d)`);
|
|
2746
|
-
const timeRegex = `${
|
|
2746
|
+
const timeRegex = `${time4}(?:${opts.join("|")})`;
|
|
2747
2747
|
return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
|
|
2748
2748
|
}
|
|
2749
2749
|
var string = (params) => {
|
|
@@ -15702,6 +15702,16 @@ async function processIdentity(pid) {
|
|
|
15702
15702
|
async function observedProcessAlive(pid, identity2) {
|
|
15703
15703
|
return Boolean(identity2 && await processIdentity(pid) === identity2);
|
|
15704
15704
|
}
|
|
15705
|
+
async function ownedProcessReleased(pid, identity2) {
|
|
15706
|
+
if (!pid || !identity2) return false;
|
|
15707
|
+
try {
|
|
15708
|
+
process.kill(pid, 0);
|
|
15709
|
+
} catch (error51) {
|
|
15710
|
+
return error51.code === "ESRCH";
|
|
15711
|
+
}
|
|
15712
|
+
const current = await processIdentity(pid);
|
|
15713
|
+
return Boolean(current && current !== identity2);
|
|
15714
|
+
}
|
|
15705
15715
|
var service = "Kernel Codex companion";
|
|
15706
15716
|
async function readCredential(id, keychainService = service) {
|
|
15707
15717
|
const { stdout } = await execute("/usr/bin/security", [
|
|
@@ -15954,7 +15964,7 @@ async function captureHook(raw) {
|
|
|
15954
15964
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
15955
15965
|
import { readdir as readdir2, unlink, rename as rename2, mkdir as mkdir3 } from "node:fs/promises";
|
|
15956
15966
|
import { join as join4 } from "node:path";
|
|
15957
|
-
import { homedir as
|
|
15967
|
+
import { homedir as homedir4 } from "node:os";
|
|
15958
15968
|
import { setTimeout as delay } from "node:timers/promises";
|
|
15959
15969
|
|
|
15960
15970
|
// ../domain/src/api-keys.ts
|
|
@@ -20193,6 +20203,12 @@ var updateTaskSchema = taskFieldsSchema.partial().extend({
|
|
|
20193
20203
|
var createTaskCommentSchema = external_exports.object({
|
|
20194
20204
|
body: external_exports.string().trim().min(1).max(1e4)
|
|
20195
20205
|
}).strict();
|
|
20206
|
+
var updateTaskCommentSchema = createTaskCommentSchema.extend({
|
|
20207
|
+
version: external_exports.number().int().positive()
|
|
20208
|
+
});
|
|
20209
|
+
var deleteTaskCommentSchema = external_exports.object({
|
|
20210
|
+
version: external_exports.number().int().positive()
|
|
20211
|
+
}).strict();
|
|
20196
20212
|
var bulkUpdateTasksSchema = external_exports.object({
|
|
20197
20213
|
tasks: external_exports.array(
|
|
20198
20214
|
external_exports.object({
|
|
@@ -20682,33 +20698,39 @@ var pageFields = {
|
|
|
20682
20698
|
cursor: external_exports.string().max(2048).optional(),
|
|
20683
20699
|
limit: external_exports.coerce.number().int().min(1).max(100).default(25)
|
|
20684
20700
|
};
|
|
20701
|
+
function dashboardSelections(option) {
|
|
20702
|
+
return external_exports.union([
|
|
20703
|
+
external_exports.array(option),
|
|
20704
|
+
external_exports.string().transform((value) => value.trim() ? value.split(",").map((v2) => v2.trim()) : []).pipe(external_exports.array(option))
|
|
20705
|
+
]).optional().transform((values) => [...new Set(values ?? [])].sort());
|
|
20706
|
+
}
|
|
20685
20707
|
var dashboardTaskQuerySchema = external_exports.object({
|
|
20686
20708
|
...pageFields,
|
|
20687
20709
|
search: external_exports.string().trim().max(160).default(""),
|
|
20688
|
-
project: external_exports.string().uuid().or(external_exports.literal("none"))
|
|
20689
|
-
status: external_exports.enum(["blocked", "backlog", "ready", "active", "done"])
|
|
20710
|
+
project: dashboardSelections(external_exports.string().uuid().or(external_exports.literal("none"))),
|
|
20711
|
+
status: dashboardSelections(external_exports.enum(["blocked", "backlog", "ready", "active", "done"])),
|
|
20690
20712
|
direction: external_exports.enum(["asc", "desc"]).default("asc")
|
|
20691
20713
|
}).strict();
|
|
20692
20714
|
var dashboardInvoiceQuerySchema = external_exports.object({
|
|
20693
20715
|
...pageFields,
|
|
20694
20716
|
search: external_exports.string().trim().max(160).default(""),
|
|
20695
|
-
payment: external_exports.enum(["unpaid", "partially_paid", "paid"])
|
|
20696
|
-
currency: billingCurrencySchema
|
|
20717
|
+
payment: dashboardSelections(external_exports.enum(["unpaid", "partially_paid", "paid"])),
|
|
20718
|
+
currency: dashboardSelections(billingCurrencySchema),
|
|
20697
20719
|
sort: external_exports.enum(["number", "issueDate", "dueDate", "totalMinor"]).default("issueDate"),
|
|
20698
20720
|
direction: external_exports.enum(["asc", "desc"]).default("desc")
|
|
20699
|
-
}).strict().refine((q2) => q2.sort !== "totalMinor" ||
|
|
20700
|
-
message: "Choose
|
|
20721
|
+
}).strict().refine((q2) => q2.sort !== "totalMinor" || q2.currency.length === 1, {
|
|
20722
|
+
message: "Choose exactly one currency before sorting totals",
|
|
20701
20723
|
path: ["currency"]
|
|
20702
20724
|
});
|
|
20703
20725
|
var dashboardReportQuerySchema = external_exports.object({
|
|
20704
20726
|
...pageFields,
|
|
20705
20727
|
from: billingDateSchema.optional(),
|
|
20706
20728
|
to: billingDateSchema.optional(),
|
|
20707
|
-
project: external_exports.string().uuid().or(external_exports.literal("none"))
|
|
20708
|
-
task: external_exports.string().uuid().or(external_exports.literal("none"))
|
|
20709
|
-
billable: external_exports.enum(["billable", "non_billable"])
|
|
20710
|
-
state: external_exports.enum(["unbilled", "billed"])
|
|
20711
|
-
currency: billingCurrencySchema
|
|
20729
|
+
project: dashboardSelections(external_exports.string().uuid().or(external_exports.literal("none"))),
|
|
20730
|
+
task: dashboardSelections(external_exports.string().uuid().or(external_exports.literal("none"))),
|
|
20731
|
+
billable: dashboardSelections(external_exports.enum(["billable", "non_billable"])),
|
|
20732
|
+
state: dashboardSelections(external_exports.enum(["unbilled", "billed"])),
|
|
20733
|
+
currency: dashboardSelections(billingCurrencySchema),
|
|
20712
20734
|
groupBy: external_exports.enum(["project", "task", "billability", "state"]).default("project"),
|
|
20713
20735
|
sort: external_exports.enum([
|
|
20714
20736
|
"workDate",
|
|
@@ -21443,6 +21465,46 @@ var quickSchedulingAvailabilityQuerySchema = external_exports.object({
|
|
|
21443
21465
|
}
|
|
21444
21466
|
});
|
|
21445
21467
|
|
|
21468
|
+
// ../domain/src/scheduling-link-customization.ts
|
|
21469
|
+
var date5 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use YYYY-MM-DD").refine((value) => {
|
|
21470
|
+
try {
|
|
21471
|
+
return qi.PlainDate.from(value).toString() === value;
|
|
21472
|
+
} catch {
|
|
21473
|
+
return false;
|
|
21474
|
+
}
|
|
21475
|
+
}, "Use a real calendar date");
|
|
21476
|
+
var time3 = external_exports.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/, "Use a time from 00:00 to 23:59");
|
|
21477
|
+
var schedulingLinkCustomizationSchema = external_exports.object({
|
|
21478
|
+
durationMinutes: external_exports.coerce.number().int().min(5).max(480).optional(),
|
|
21479
|
+
fromDate: date5.optional(),
|
|
21480
|
+
toDate: date5.optional(),
|
|
21481
|
+
after: time3.optional(),
|
|
21482
|
+
before: time3.optional(),
|
|
21483
|
+
timezone: external_exports.string().refine(isValidTimezone, "Choose a valid timezone").optional()
|
|
21484
|
+
}).superRefine((value, context) => {
|
|
21485
|
+
if ((value.fromDate || value.toDate || value.after || value.before) && !value.timezone) {
|
|
21486
|
+
context.addIssue({
|
|
21487
|
+
code: "custom",
|
|
21488
|
+
path: ["timezone"],
|
|
21489
|
+
message: "Choose a timezone for the date and time limits"
|
|
21490
|
+
});
|
|
21491
|
+
}
|
|
21492
|
+
if (value.fromDate && value.toDate && value.toDate < value.fromDate) {
|
|
21493
|
+
context.addIssue({
|
|
21494
|
+
code: "custom",
|
|
21495
|
+
path: ["toDate"],
|
|
21496
|
+
message: "Through date must be on or after From date"
|
|
21497
|
+
});
|
|
21498
|
+
}
|
|
21499
|
+
if (value.after && value.before && value.before <= value.after) {
|
|
21500
|
+
context.addIssue({
|
|
21501
|
+
code: "custom",
|
|
21502
|
+
path: ["before"],
|
|
21503
|
+
message: "Finish by must be later than Start at or after; overnight windows are not supported"
|
|
21504
|
+
});
|
|
21505
|
+
}
|
|
21506
|
+
});
|
|
21507
|
+
|
|
21446
21508
|
// ../domain/src/scheduling-links.ts
|
|
21447
21509
|
var normalizedUniqueDurations = external_exports.array(external_exports.number().int().min(5).max(480)).min(1).max(8).transform((durations) => [...new Set(durations)].sort((left, right) => left - right));
|
|
21448
21510
|
var schedulingLinkSlugSchema = external_exports.string().trim().toLowerCase().min(3).max(80).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use lowercase letters, numbers, and single hyphens");
|
|
@@ -21486,7 +21548,7 @@ var schedulingAvailabilityQuerySchema = external_exports.object({
|
|
|
21486
21548
|
durationMinutes: external_exports.coerce.number().int().min(5).max(480),
|
|
21487
21549
|
from: external_exports.coerce.date(),
|
|
21488
21550
|
to: external_exports.coerce.date()
|
|
21489
|
-
}).refine((value) => value.to > value.from, {
|
|
21551
|
+
}).and(schedulingLinkCustomizationSchema).refine((value) => value.to > value.from, {
|
|
21490
21552
|
message: "Availability must end after it starts",
|
|
21491
21553
|
path: ["to"]
|
|
21492
21554
|
}).refine((value) => value.to.getTime() - value.from.getTime() <= 32 * 864e5, {
|
|
@@ -22004,6 +22066,7 @@ var launchCodexTaskSchema = external_exports.strictObject({
|
|
|
22004
22066
|
taskId: external_exports.uuid(),
|
|
22005
22067
|
taskVersion: external_exports.number().int().positive(),
|
|
22006
22068
|
mode: codexModeSchema,
|
|
22069
|
+
additionalInstructions: external_exports.string().max(CODEX_LAUNCH_MAX_LENGTH).optional(),
|
|
22007
22070
|
model: codexModelIdSchema.optional(),
|
|
22008
22071
|
reasoningEffort: codexReasoningEffortSchema.optional(),
|
|
22009
22072
|
checkout: codexCheckoutSchema.optional(),
|
|
@@ -22075,7 +22138,27 @@ var cancelCodexOperationSchema = external_exports.strictObject({
|
|
|
22075
22138
|
taskId: external_exports.uuid(),
|
|
22076
22139
|
kind: external_exports.enum(["launch", "lookup"])
|
|
22077
22140
|
});
|
|
22141
|
+
var stopCodexTurnSchema = external_exports.strictObject({ taskId: external_exports.uuid(), launchId: external_exports.uuid() });
|
|
22142
|
+
var codexFirstTurnReportSchema = external_exports.strictObject({
|
|
22143
|
+
owner: external_exports.enum(["unknown", "companion", "released"]),
|
|
22144
|
+
turnId: external_exports.string().min(1).max(200).nullable(),
|
|
22145
|
+
stopped: external_exports.boolean()
|
|
22146
|
+
}).refine(
|
|
22147
|
+
(value) => (value.owner !== "companion" || value.turnId !== null) && (!value.stopped || value.owner === "released" && value.turnId !== null),
|
|
22148
|
+
"Invalid first-turn ownership"
|
|
22149
|
+
);
|
|
22150
|
+
var codexStopRequestsSchema = external_exports.strictObject({
|
|
22151
|
+
requests: external_exports.array(
|
|
22152
|
+
external_exports.strictObject({
|
|
22153
|
+
launchId: external_exports.uuid(),
|
|
22154
|
+
threadId: external_exports.uuid(),
|
|
22155
|
+
turnId: external_exports.string().min(1).max(200),
|
|
22156
|
+
requestedAt: external_exports.iso.datetime()
|
|
22157
|
+
})
|
|
22158
|
+
).max(200)
|
|
22159
|
+
});
|
|
22078
22160
|
var codexStatusReportSchema = external_exports.strictObject({
|
|
22161
|
+
firstTurn: codexFirstTurnReportSchema.optional(),
|
|
22079
22162
|
title: external_exports.string().trim().min(1).max(300).optional(),
|
|
22080
22163
|
observationAgeMs: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
22081
22164
|
launchId: external_exports.uuid(),
|
|
@@ -22267,6 +22350,29 @@ var settingsHealthSnapshotSchema = external_exports.object({
|
|
|
22267
22350
|
})
|
|
22268
22351
|
});
|
|
22269
22352
|
|
|
22353
|
+
// ../domain/src/work-notes.ts
|
|
22354
|
+
var createTaskWorkNoteSchema = external_exports.object({
|
|
22355
|
+
body: external_exports.string().trim().min(1).max(5e4),
|
|
22356
|
+
codexHandoffId: external_exports.string().uuid().nullable().optional()
|
|
22357
|
+
}).strict();
|
|
22358
|
+
var updateTaskWorkNoteSchema = external_exports.object({
|
|
22359
|
+
body: external_exports.string().trim().min(1).max(5e4),
|
|
22360
|
+
version: external_exports.number().int().positive()
|
|
22361
|
+
}).strict();
|
|
22362
|
+
var deleteTaskWorkNoteSchema = external_exports.object({ version: external_exports.number().int().positive() }).strict();
|
|
22363
|
+
var workNoteSourceSchema = external_exports.enum(["task", "time-entry", "draft-entry"]);
|
|
22364
|
+
var workNoteReferenceSchema = external_exports.string().regex(
|
|
22365
|
+
/^(task|time-entry|draft-entry):[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
22366
|
+
);
|
|
22367
|
+
var taskWorkNotesQuerySchema = external_exports.object({
|
|
22368
|
+
limit: external_exports.coerce.number().int().min(1).max(100).default(25),
|
|
22369
|
+
cursor: external_exports.string().max(2e3).optional(),
|
|
22370
|
+
codexHandoffId: external_exports.string().uuid().optional()
|
|
22371
|
+
});
|
|
22372
|
+
var workNoteHistoryQuerySchema = taskWorkNotesQuerySchema.extend({
|
|
22373
|
+
note: workNoteReferenceSchema.optional()
|
|
22374
|
+
});
|
|
22375
|
+
|
|
22270
22376
|
// src/protocol.ts
|
|
22271
22377
|
import { spawn as spawn2 } from "node:child_process";
|
|
22272
22378
|
import { existsSync } from "node:fs";
|
|
@@ -22824,7 +22930,8 @@ async function linkedReceipt(server, watch, pairing, catalog) {
|
|
|
22824
22930
|
|
|
22825
22931
|
// src/projects.ts
|
|
22826
22932
|
import { mkdir as mkdir2, realpath as realpath3, stat as stat2 } from "node:fs/promises";
|
|
22827
|
-
import {
|
|
22933
|
+
import { homedir as homedir3 } from "node:os";
|
|
22934
|
+
import { basename, join as join3, isAbsolute as isAbsolute2 } from "node:path";
|
|
22828
22935
|
async function discoverProjects(server) {
|
|
22829
22936
|
const signal = AbortSignal.timeout(25e3);
|
|
22830
22937
|
const projects = [];
|
|
@@ -22903,11 +23010,20 @@ function authorizedDestination(pairing, launch, catalog) {
|
|
|
22903
23010
|
throw new DestinationAuthorizationError("The saved project folder must be absolute");
|
|
22904
23011
|
return current;
|
|
22905
23012
|
}
|
|
22906
|
-
async function prepareCheckout(project, launch,
|
|
23013
|
+
async function prepareCheckout(project, launch, server) {
|
|
22907
23014
|
if (launch.checkout === "folder") return project.root;
|
|
22908
23015
|
if (!project.isGit || !project.defaultBranch) throw new Error("A Git default branch is required");
|
|
22909
|
-
const
|
|
22910
|
-
|
|
23016
|
+
const config2 = await server.call("config/read", { includeLayers: false }, 5e3);
|
|
23017
|
+
const configuredRoot = config2.config.desktop?.["git-worktree-root"];
|
|
23018
|
+
if (configuredRoot != null && typeof configuredRoot !== "string")
|
|
23019
|
+
throw new Error("The Codex worktree directory is invalid");
|
|
23020
|
+
const worktreesRoot = configuredRoot?.trim() || join3(process.env.CODEX_HOME || join3(homedir3(), ".codex"), "worktrees");
|
|
23021
|
+
if (!isAbsolute2(worktreesRoot)) throw new Error("The Codex worktree directory must be absolute");
|
|
23022
|
+
const container = join3(worktreesRoot, launch.id.replaceAll("-", ""));
|
|
23023
|
+
const name = basename(project.root);
|
|
23024
|
+
const checkoutName = /^\d{6}-\d{4}-[0-9a-f]{8}$/i.test(name) ? `project-${name}` : name;
|
|
23025
|
+
const cwd = join3(container, checkoutName);
|
|
23026
|
+
await mkdir2(container, { recursive: true, mode: 448 });
|
|
22911
23027
|
await execute("git", [
|
|
22912
23028
|
"-C",
|
|
22913
23029
|
project.root,
|
|
@@ -22940,7 +23056,8 @@ function initialReceipt(launch) {
|
|
|
22940
23056
|
state: "running",
|
|
22941
23057
|
waiting: false,
|
|
22942
23058
|
turnId: null,
|
|
22943
|
-
message: null
|
|
23059
|
+
message: null,
|
|
23060
|
+
firstTurn: { owner: "unknown", turnId: null, stopped: false }
|
|
22944
23061
|
}
|
|
22945
23062
|
};
|
|
22946
23063
|
}
|
|
@@ -23071,6 +23188,23 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23071
23188
|
continue;
|
|
23072
23189
|
}
|
|
23073
23190
|
delete receipt.observedAt;
|
|
23191
|
+
if (!receipt.status.firstTurn) {
|
|
23192
|
+
receipt.status.firstTurn = {
|
|
23193
|
+
owner: "unknown",
|
|
23194
|
+
turnId: receipt.turnId ?? null,
|
|
23195
|
+
stopped: false
|
|
23196
|
+
};
|
|
23197
|
+
receipt.status.sequence++;
|
|
23198
|
+
}
|
|
23199
|
+
if (receipt.status.firstTurn?.owner === "companion") {
|
|
23200
|
+
const released = await ownedProcessReleased(receipt.ownerPid, receipt.ownerIdentity);
|
|
23201
|
+
receipt.status.firstTurn = {
|
|
23202
|
+
...receipt.status.firstTurn,
|
|
23203
|
+
owner: released ? "released" : "unknown",
|
|
23204
|
+
stopped: released && Boolean(receipt.stopRequestedAt)
|
|
23205
|
+
};
|
|
23206
|
+
receipt.status.sequence++;
|
|
23207
|
+
}
|
|
23074
23208
|
if (receipt.phase === "opening-draft") {
|
|
23075
23209
|
receipt.phase = "drafted";
|
|
23076
23210
|
receipt.draftOpenUncertain = true;
|
|
@@ -23164,24 +23298,32 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23164
23298
|
let journalHealthy = true;
|
|
23165
23299
|
let clockAnchor;
|
|
23166
23300
|
const kernelNow = () => clockAnchor ? clockAnchor.server + Math.max(0, performance.now() - clockAnchor.monotonic) : Date.now();
|
|
23167
|
-
const release = async (receipt, waiting, message) => {
|
|
23301
|
+
const release = async (receipt, waiting, message, stopRequestedAt) => {
|
|
23168
23302
|
const owner = owners.get(receipt.launch.id);
|
|
23303
|
+
if (stopRequestedAt) receipt.stopRequestedAt = stopRequestedAt;
|
|
23169
23304
|
receipt.status = {
|
|
23170
23305
|
...receipt.status,
|
|
23171
23306
|
sequence: receipt.status.sequence + 1,
|
|
23172
23307
|
state: "running",
|
|
23173
23308
|
waiting,
|
|
23174
23309
|
turnId: receipt.turnId ?? null,
|
|
23175
|
-
message
|
|
23310
|
+
message: stopRequestedAt ? "Stopping the first turn." : message
|
|
23176
23311
|
};
|
|
23177
23312
|
receipt.handoffWaiting = waiting;
|
|
23178
23313
|
observe(receipt);
|
|
23179
23314
|
await save(receipt);
|
|
23180
|
-
if (waiting && owner && receipt.turnId)
|
|
23181
|
-
await owner.call("turn/interrupt", { threadId: receipt.threadId, turnId: receipt.turnId }).catch(() => void 0);
|
|
23315
|
+
if ((waiting || stopRequestedAt) && owner && receipt.turnId)
|
|
23316
|
+
await owner.call("turn/interrupt", { threadId: receipt.threadId, turnId: receipt.turnId }, 5e3).catch(() => void 0);
|
|
23182
23317
|
await owner?.stop();
|
|
23183
23318
|
owners.delete(receipt.launch.id);
|
|
23184
23319
|
receipt.phase = "released";
|
|
23320
|
+
receipt.status.sequence++;
|
|
23321
|
+
receipt.status.firstTurn = {
|
|
23322
|
+
owner: "released",
|
|
23323
|
+
turnId: receipt.turnId ?? null,
|
|
23324
|
+
stopped: Boolean(receipt.stopRequestedAt)
|
|
23325
|
+
};
|
|
23326
|
+
receipt.status.message = message;
|
|
23185
23327
|
if (!waiting) receipt.status.state = "done";
|
|
23186
23328
|
await save(receipt);
|
|
23187
23329
|
};
|
|
@@ -23220,7 +23362,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23220
23362
|
await save(receipt);
|
|
23221
23363
|
return;
|
|
23222
23364
|
}
|
|
23223
|
-
receipt.cwd = await prepareCheckout(project, launch,
|
|
23365
|
+
receipt.cwd = await prepareCheckout(project, launch, observer);
|
|
23224
23366
|
await save(receipt);
|
|
23225
23367
|
if (!canBegin(receipt, kernelNow()))
|
|
23226
23368
|
throw new Error("The launch expired during checkout preparation");
|
|
@@ -23281,6 +23423,8 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23281
23423
|
observe(receipt);
|
|
23282
23424
|
receipt.turnId = response.turn.id;
|
|
23283
23425
|
receipt.status.turnId = response.turn.id;
|
|
23426
|
+
receipt.status.firstTurn = { owner: "companion", turnId: response.turn.id, stopped: false };
|
|
23427
|
+
receipt.status.sequence++;
|
|
23284
23428
|
await save(receipt);
|
|
23285
23429
|
} catch (error51) {
|
|
23286
23430
|
if (launch.mode === "draft") {
|
|
@@ -23309,6 +23453,11 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23309
23453
|
}
|
|
23310
23454
|
owners.delete(launch.id);
|
|
23311
23455
|
if (receipt.threadId) {
|
|
23456
|
+
receipt.status.firstTurn = {
|
|
23457
|
+
owner: "released",
|
|
23458
|
+
turnId: receipt.turnId ?? null,
|
|
23459
|
+
stopped: false
|
|
23460
|
+
};
|
|
23312
23461
|
receipt.phase = "released";
|
|
23313
23462
|
receipt.status = {
|
|
23314
23463
|
...receipt.status,
|
|
@@ -23355,7 +23504,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23355
23504
|
if (!observer) await connectObserver();
|
|
23356
23505
|
pairingSchema.parse(await readJson(join4(directory, "pairing.json")));
|
|
23357
23506
|
const discovered = await discoverProjects(catalogServer);
|
|
23358
|
-
const ready = await hooksReady(catalogServer, [
|
|
23507
|
+
const ready = await hooksReady(catalogServer, [homedir4()], hookCommand(entrypoint2));
|
|
23359
23508
|
const projects = await mapConcurrent(discovered, 4, async (project) => {
|
|
23360
23509
|
if (project.issue) return project;
|
|
23361
23510
|
try {
|
|
@@ -23492,6 +23641,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23492
23641
|
if (turn) {
|
|
23493
23642
|
receipt.turnId = turn.id;
|
|
23494
23643
|
receipt.status.turnId = turn.id;
|
|
23644
|
+
receipt.status.firstTurn = { owner: "companion", turnId: turn.id, stopped: false };
|
|
23495
23645
|
observe(receipt);
|
|
23496
23646
|
await save(receipt);
|
|
23497
23647
|
}
|
|
@@ -23509,7 +23659,13 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23509
23659
|
turn?.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : null
|
|
23510
23660
|
);
|
|
23511
23661
|
} else if (event.method === "kernel/closed" && receipt.threadId) {
|
|
23662
|
+
owners.delete(receipt.launch.id);
|
|
23512
23663
|
receipt.phase = "released";
|
|
23664
|
+
receipt.status.firstTurn = {
|
|
23665
|
+
owner: "released",
|
|
23666
|
+
turnId: receipt.turnId ?? null,
|
|
23667
|
+
stopped: Boolean(receipt.stopRequestedAt)
|
|
23668
|
+
};
|
|
23513
23669
|
receipt.status = {
|
|
23514
23670
|
...receipt.status,
|
|
23515
23671
|
sequence: receipt.status.sequence + 1,
|
|
@@ -23602,6 +23758,13 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23602
23758
|
await save(receipt);
|
|
23603
23759
|
return;
|
|
23604
23760
|
}
|
|
23761
|
+
if (receipt.status.firstTurn?.owner === "unknown" && receipt.ownerIdentity && await ownedProcessReleased(receipt.ownerPid, receipt.ownerIdentity)) {
|
|
23762
|
+
receipt.status.firstTurn = {
|
|
23763
|
+
...receipt.status.firstTurn,
|
|
23764
|
+
owner: "released",
|
|
23765
|
+
stopped: Boolean(receipt.stopRequestedAt)
|
|
23766
|
+
};
|
|
23767
|
+
}
|
|
23605
23768
|
const { data } = await observer.call(
|
|
23606
23769
|
"thread/turns/list",
|
|
23607
23770
|
{ threadId: receipt.threadId, limit: 1, itemsView: "notLoaded" },
|
|
@@ -23617,7 +23780,15 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23617
23780
|
state: "done",
|
|
23618
23781
|
waiting: false,
|
|
23619
23782
|
turnId: turn.id,
|
|
23620
|
-
message: turn.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : interrupted ? "Stopped in Codex." : null
|
|
23783
|
+
message: turn.status === "failed" ? "Codex ended this turn with an error. Open the task for details." : interrupted ? receipt.status.firstTurn?.stopped && turn.id === receipt.turnId ? "Stopped. Continue in Codex." : "Stopped in Codex." : null
|
|
23784
|
+
};
|
|
23785
|
+
observe(receipt);
|
|
23786
|
+
} else if (receipt.status.firstTurn?.stopped && (!turn || turn.id === receipt.turnId) && (!receipt.status.turnId || receipt.status.turnId === receipt.turnId)) {
|
|
23787
|
+
receipt.status = {
|
|
23788
|
+
...receipt.status,
|
|
23789
|
+
state: "done",
|
|
23790
|
+
waiting: false,
|
|
23791
|
+
message: "Stopped. Continue in Codex."
|
|
23621
23792
|
};
|
|
23622
23793
|
observe(receipt);
|
|
23623
23794
|
} else if ((receipt.handoffWaiting || receipt.hookAt && alive) && receipt.status.state === "running") {
|
|
@@ -23643,6 +23814,20 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23643
23814
|
})
|
|
23644
23815
|
);
|
|
23645
23816
|
};
|
|
23817
|
+
const stops = async () => {
|
|
23818
|
+
if (!clockAnchor) return;
|
|
23819
|
+
const { requests } = codexStopRequestsSchema.parse(await post("stops", {}));
|
|
23820
|
+
await mapConcurrent(
|
|
23821
|
+
requests,
|
|
23822
|
+
4,
|
|
23823
|
+
(request) => writers.run(request.launchId, async () => {
|
|
23824
|
+
const receipt = receipts.get(request.launchId);
|
|
23825
|
+
if (!receipt || receipt.phase === "released" || !owners.has(request.launchId) || receipt.threadId !== request.threadId || receipt.turnId !== request.turnId || receipt.status.firstTurn?.owner !== "companion" || !("mode" in receipt.launch) || receipt.launch.mode !== "work")
|
|
23826
|
+
return;
|
|
23827
|
+
await release(receipt, false, "Stopped. Continue in Codex.", request.requestedAt);
|
|
23828
|
+
})
|
|
23829
|
+
);
|
|
23830
|
+
};
|
|
23646
23831
|
let deliveryCursor = 0;
|
|
23647
23832
|
const deliver = async () => {
|
|
23648
23833
|
if (!clockAnchor) return;
|
|
@@ -23837,6 +24022,7 @@ async function runCompanion(machineId, entrypoint2, signal) {
|
|
|
23837
24022
|
schedule("reconciliation", 2e3, reconcile);
|
|
23838
24023
|
schedule("observation", 2e3, inspect);
|
|
23839
24024
|
schedule("delivery", 0, deliver);
|
|
24025
|
+
schedule("stops", 2e3, stops);
|
|
23840
24026
|
schedule("lookups", 2e3, lookups);
|
|
23841
24027
|
schedule("launches", 2e3, launches);
|
|
23842
24028
|
schedule("diagnostics", 2e3, diagnostics);
|
|
@@ -24048,14 +24234,14 @@ ${url2.href}
|
|
|
24048
24234
|
// src/onboarding.ts
|
|
24049
24235
|
import { randomBytes as randomBytes2, randomUUID as randomUUID6 } from "node:crypto";
|
|
24050
24236
|
import { rm as rm2 } from "node:fs/promises";
|
|
24051
|
-
import { homedir as
|
|
24237
|
+
import { homedir as homedir6, hostname as hostname4 } from "node:os";
|
|
24052
24238
|
import { join as join7 } from "node:path";
|
|
24053
24239
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
24054
24240
|
import { createInterface } from "node:readline/promises";
|
|
24055
24241
|
|
|
24056
24242
|
// src/setup.ts
|
|
24057
24243
|
import { chmod as chmod2, copyFile, mkdir as mkdir4, rename as rename3, rm } from "node:fs/promises";
|
|
24058
|
-
import { homedir as
|
|
24244
|
+
import { homedir as homedir5 } from "node:os";
|
|
24059
24245
|
import { dirname as dirname2, join as join6, resolve as resolve2 } from "node:path";
|
|
24060
24246
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
24061
24247
|
var installedEntrypoint = join6(dataRoot, "bin/kernel-companion.mjs");
|
|
@@ -24072,7 +24258,7 @@ async function installBundle(entrypoint2) {
|
|
|
24072
24258
|
}
|
|
24073
24259
|
}
|
|
24074
24260
|
const hooksPath = await installHooks(installedEntrypoint);
|
|
24075
|
-
const server = new AppServer(
|
|
24261
|
+
const server = new AppServer(homedir5());
|
|
24076
24262
|
try {
|
|
24077
24263
|
await server.initialize();
|
|
24078
24264
|
await server.call("config/value/write", {
|
|
@@ -24093,7 +24279,7 @@ function serviceDefinition(id, executable = process.execPath, path = process.env
|
|
|
24093
24279
|
const args = [executable, installedEntrypoint, "run", id].map((value) => `<string>${xml(value)}</string>`).join("");
|
|
24094
24280
|
return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>work.krnl.companion.${id}</string><key>ProgramArguments</key><array>${args}</array><key>WorkingDirectory</key><string>${xml(pairingDirectory(id))}</string><key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(path)}</string>${codexHome ? `<key>CODEX_HOME</key><string>${xml(codexHome)}</string>` : ""}</dict><key>StandardOutPath</key><string>${xml(join6(pairingDirectory(id), "service.log"))}</string><key>StandardErrorPath</key><string>${xml(join6(pairingDirectory(id), "service.log"))}</string><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>ThrottleInterval</key><integer>30</integer></dict></plist>`;
|
|
24095
24281
|
}
|
|
24096
|
-
var plistPath = (id) => join6(
|
|
24282
|
+
var plistPath = (id) => join6(homedir5(), "Library/LaunchAgents", `work.krnl.companion.${external_exports.uuid().parse(id)}.plist`);
|
|
24097
24283
|
async function stopService(id, remove = false) {
|
|
24098
24284
|
external_exports.uuid().parse(id);
|
|
24099
24285
|
await execute("launchctl", [
|
|
@@ -24137,7 +24323,7 @@ async function consent(account) {
|
|
|
24137
24323
|
}
|
|
24138
24324
|
async function connectCodex(entrypoint2) {
|
|
24139
24325
|
const profile = await signedInProfile();
|
|
24140
|
-
const server = new AppServer(
|
|
24326
|
+
const server = new AppServer(homedir6());
|
|
24141
24327
|
try {
|
|
24142
24328
|
await server.initialize();
|
|
24143
24329
|
await discoverProjects(server);
|
|
@@ -24211,11 +24397,11 @@ async function connectCodex(entrypoint2) {
|
|
|
24211
24397
|
await unlock();
|
|
24212
24398
|
}
|
|
24213
24399
|
await installService(id);
|
|
24214
|
-
const check2 = new AppServer(
|
|
24400
|
+
const check2 = new AppServer(homedir6());
|
|
24215
24401
|
let ready = false;
|
|
24216
24402
|
try {
|
|
24217
24403
|
await check2.initialize();
|
|
24218
|
-
ready = await hooksReady(check2, [
|
|
24404
|
+
ready = await hooksReady(check2, [homedir6()], hookCommand(installedEntrypoint));
|
|
24219
24405
|
} finally {
|
|
24220
24406
|
await check2.stop();
|
|
24221
24407
|
}
|
|
@@ -24352,7 +24538,7 @@ async function main() {
|
|
|
24352
24538
|
}
|
|
24353
24539
|
if (command === "--version") {
|
|
24354
24540
|
process.stdout.write(
|
|
24355
|
-
`${false ? "development" : "0.1.
|
|
24541
|
+
`${false ? "development" : "0.1.13"}
|
|
24356
24542
|
`
|
|
24357
24543
|
);
|
|
24358
24544
|
return;
|