@neta-art/cohub-cli 8.2.0 → 8.3.1
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/dist/commands/runtime.d.ts +0 -12
- package/dist/commands/runtime.js +129 -144
- package/dist/commands/search.js +3 -9
- package/dist/commands/spaces.js +5 -2
- package/dist/output.d.ts +6 -0
- package/dist/output.js +11 -0
- package/dist/runtime/codex-usage.d.ts +0 -2
- package/dist/runtime/codex-usage.js +0 -12
- package/dist/runtime/connection.d.ts +5 -5
- package/dist/runtime/connection.js +27 -16
- package/dist/runtime/harness.d.ts +8 -16
- package/dist/runtime/harness.js +1 -366
- package/dist/runtime/instance.d.ts +8 -1
- package/dist/runtime/instance.js +15 -8
- package/dist/runtime/launch.js +11 -5
- package/dist/runtime/native/adapters.d.ts +32 -0
- package/dist/runtime/native/adapters.js +79 -0
- package/dist/runtime/{native-archive.d.ts → native/archive.d.ts} +0 -3
- package/dist/runtime/{native-archive.js → native/archive.js} +2 -17
- package/dist/runtime/native/attach.d.ts +38 -0
- package/dist/runtime/native/attach.js +113 -0
- package/dist/runtime/native/codex-link.d.ts +40 -0
- package/dist/runtime/native/codex-link.js +249 -0
- package/dist/runtime/native/config.d.ts +16 -0
- package/dist/runtime/native/config.js +24 -0
- package/dist/runtime/native/daemon.d.ts +88 -0
- package/dist/runtime/native/daemon.js +367 -0
- package/dist/runtime/native/execution.d.ts +45 -0
- package/dist/runtime/native/execution.js +337 -0
- package/dist/runtime/native/identity.d.ts +9 -0
- package/dist/runtime/native/identity.js +15 -0
- package/dist/runtime/native/ingest.d.ts +241 -0
- package/dist/runtime/native/ingest.js +823 -0
- package/dist/runtime/native/install.d.ts +9 -0
- package/dist/runtime/native/install.js +113 -0
- package/dist/runtime/native/pi-extension.d.ts +35 -0
- package/dist/runtime/native/pi-extension.js +160 -0
- package/dist/runtime/native/pi-link.d.ts +67 -0
- package/dist/runtime/native/pi-link.js +238 -0
- package/dist/runtime/native/results.d.ts +41 -0
- package/dist/runtime/native/results.js +128 -0
- package/dist/runtime/native/sessions.d.ts +45 -0
- package/dist/runtime/native/sessions.js +92 -0
- package/dist/runtime/{native-transcript.d.ts → native/transcript.d.ts} +5 -5
- package/dist/runtime/{native-transcript.js → native/transcript.js} +37 -11
- package/dist/runtime/native/translate.d.ts +44 -0
- package/dist/runtime/native/translate.js +263 -0
- package/dist/runtime/native/transport.d.ts +7 -0
- package/dist/runtime/native/transport.js +16 -0
- package/dist/runtime/native/version.d.ts +5 -0
- package/dist/runtime/native/version.js +18 -0
- package/dist/runtime/presentation.d.ts +7 -10
- package/dist/runtime/presentation.js +35 -10
- package/dist/runtime/projection-store.d.ts +6 -13
- package/dist/runtime/projection-store.js +11 -74
- package/dist/runtime/supervisor.d.ts +2 -0
- package/dist/runtime/supervisor.js +36 -110
- package/dist/runtime/turn-projection.d.ts +1 -3
- package/dist/runtime/turn-projection.js +3 -10
- package/package.json +4 -5
- package/dist/runtime/native-attach.d.ts +0 -28
- package/dist/runtime/native-attach.js +0 -47
- package/dist/runtime/native-codex-hook.d.ts +0 -1
- package/dist/runtime/native-codex-hook.js +0 -28
- package/dist/runtime/native-install.d.ts +0 -21
- package/dist/runtime/native-install.js +0 -130
- package/dist/runtime/native-ipc.d.ts +0 -37
- package/dist/runtime/native-ipc.js +0 -118
- package/dist/runtime/native-pi-extension.d.ts +0 -20
- package/dist/runtime/native-pi-extension.js +0 -47
- package/dist/runtime/native-sync-store.d.ts +0 -116
- package/dist/runtime/native-sync-store.js +0 -495
- package/dist/runtime/native-sync.d.ts +0 -65
- package/dist/runtime/native-sync.js +0 -326
- package/dist/runtime/session-store.d.ts +0 -84
- package/dist/runtime/session-store.js +0 -601
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
-
import { RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, runtimeCommandSchema, runtimeReadySchema, } from "@neta-art/cohub";
|
|
4
|
-
import {
|
|
3
|
+
import { RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, runtimeCommandSchema, runtimeReadySchema, runtimeNativeStopSchema, } from "@neta-art/cohub";
|
|
4
|
+
import { executeTurn } from "./native/execution.js";
|
|
5
|
+
import { ContextRequiredError } from "./native/results.js";
|
|
5
6
|
import { ProcessCleanupUncertainError } from "./process-group.js";
|
|
6
|
-
import { ContextRequiredError } from "./session-store.js";
|
|
7
7
|
import { serializeDiagnosticError, } from "./diagnostics.js";
|
|
8
8
|
export async function serveRuntime(options) {
|
|
9
9
|
const runtimeId = options.runtimeId ?? options.diagnostics?.runtimeId ?? randomUUID();
|
|
@@ -13,7 +13,7 @@ export async function serveRuntime(options) {
|
|
|
13
13
|
const uploads = new AbortController();
|
|
14
14
|
const uploadSignal = AbortSignal.any([options.signal, uploads.signal]);
|
|
15
15
|
const log = (level, event, data, context) => options.diagnostics?.log(level, event, data, context);
|
|
16
|
-
const flush = () => options.
|
|
16
|
+
const flush = () => options.executor.archives.flush(uploadSignal).catch((error) => {
|
|
17
17
|
if (!uploadSignal.aborted) {
|
|
18
18
|
log("warn", "archive.flush_failed", { error: serializeDiagnosticError(error) });
|
|
19
19
|
// The diagnostic sink handles terminal presentation and throttling.
|
|
@@ -151,7 +151,8 @@ async function connect(options) {
|
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
153
|
const requestId = randomUUID();
|
|
154
|
-
|
|
154
|
+
// An ingest batch writes each of its Turns durably; everything else answers from memory or one row.
|
|
155
|
+
const timer = setTimeout(() => { nativePending.delete(requestId); reject(new Error("Native Runtime event timed out")); }, event.type === "ingest" ? 120_000 : 30_000);
|
|
155
156
|
nativePending.set(requestId, { resolve, reject, timer });
|
|
156
157
|
try {
|
|
157
158
|
send({ type: "runtime.native", requestId, event });
|
|
@@ -290,7 +291,7 @@ async function connect(options) {
|
|
|
290
291
|
options.onNativeChannel?.(sendNative);
|
|
291
292
|
options.onReady();
|
|
292
293
|
let recoveryBatch = 0;
|
|
293
|
-
for await (const executions of options.
|
|
294
|
+
for await (const executions of options.executor.results.pendingBatches()) {
|
|
294
295
|
recoveryBatch += 1;
|
|
295
296
|
log("info", "runtime.recovery_batch_sent", {
|
|
296
297
|
batch: recoveryBatch,
|
|
@@ -305,6 +306,16 @@ async function connect(options) {
|
|
|
305
306
|
if (raw.type === "runtime.heartbeat") {
|
|
306
307
|
return;
|
|
307
308
|
}
|
|
309
|
+
if (raw.type === "runtime.native.stop") {
|
|
310
|
+
const stop = runtimeNativeStopSchema.safeParse(raw);
|
|
311
|
+
if (!stop.success) {
|
|
312
|
+
log("warn", "runtime.protocol.invalid_native_stop", { error: stop.error.message }, { connectionId });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (stop.data.spaceId === options.spaceId)
|
|
316
|
+
options.onNativeStop?.(stop.data);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
308
319
|
if (raw.type === "runtime.native.result") {
|
|
309
320
|
const result = raw;
|
|
310
321
|
const pending = result.requestId ? nativePending.get(result.requestId) : null;
|
|
@@ -341,7 +352,7 @@ async function connect(options) {
|
|
|
341
352
|
await execution.promise;
|
|
342
353
|
if (!execution.result)
|
|
343
354
|
throw new Error("Runtime result is not available");
|
|
344
|
-
await options.
|
|
355
|
+
await options.executor.results.acknowledge(execution.sessionId, frame.turnId);
|
|
345
356
|
}
|
|
346
357
|
catch (error) {
|
|
347
358
|
log("error", "runtime.turn_ack_failed", {
|
|
@@ -373,7 +384,7 @@ async function connect(options) {
|
|
|
373
384
|
if (previous.sessionId !== identity.sessionId || previous.turnId !== identity.turnId || previous.harness !== identity.harness)
|
|
374
385
|
throw new Error("Runtime recovery identity changed");
|
|
375
386
|
await previous.promise;
|
|
376
|
-
const saved = await options.
|
|
387
|
+
const saved = await options.executor.results.recover(identity);
|
|
377
388
|
if (saved)
|
|
378
389
|
for (const event of saved.events)
|
|
379
390
|
send({ type: "runtime.event", requestId: frame.requestId, event });
|
|
@@ -395,12 +406,12 @@ async function connect(options) {
|
|
|
395
406
|
await running[1].promise;
|
|
396
407
|
active.delete(running[0]);
|
|
397
408
|
}
|
|
398
|
-
const saved = await options.
|
|
409
|
+
const saved = await options.executor.results.recover(identity);
|
|
399
410
|
recovery.controller.signal.throwIfAborted();
|
|
400
411
|
const last = saved?.events.at(-1);
|
|
401
412
|
if (!saved || last?.type !== "turn.end")
|
|
402
413
|
throw new Error("No confirmed result");
|
|
403
|
-
recovery.result = {
|
|
414
|
+
recovery.result = { session: saved.session, event: last };
|
|
404
415
|
for (const event of saved.events)
|
|
405
416
|
send({ type: "runtime.event", requestId: frame.requestId, event });
|
|
406
417
|
}
|
|
@@ -432,7 +443,7 @@ async function connect(options) {
|
|
|
432
443
|
if (previous.sessionId !== frame.input.sessionId || previous.turnId !== frame.input.turnId || previous.harness !== frame.input.harness)
|
|
433
444
|
throw new Error("Runtime execution identity changed");
|
|
434
445
|
await previous.promise;
|
|
435
|
-
const saved = await options.
|
|
446
|
+
const saved = await options.executor.results.recover(frame.input, frame.requestId);
|
|
436
447
|
if (saved)
|
|
437
448
|
for (const event of saved.events)
|
|
438
449
|
send({ type: "runtime.event", requestId: frame.requestId, event });
|
|
@@ -441,7 +452,7 @@ async function connect(options) {
|
|
|
441
452
|
const requestContext = async (executionSignal) => {
|
|
442
453
|
const signal = AbortSignal.any([executionSignal, disconnected.signal]);
|
|
443
454
|
signal.throwIfAborted();
|
|
444
|
-
const pendingTurnIds = await options.
|
|
455
|
+
const pendingTurnIds = await options.executor.results.pendingTurnIds(frame.input.sessionId);
|
|
445
456
|
log("debug", "runtime.context_requested", { pendingTurnCount: pendingTurnIds.length }, context);
|
|
446
457
|
return new Promise((resolve, reject) => {
|
|
447
458
|
const abort = () => {
|
|
@@ -521,12 +532,12 @@ async function connect(options) {
|
|
|
521
532
|
};
|
|
522
533
|
execution.promise = (async () => {
|
|
523
534
|
try {
|
|
524
|
-
const saved = await options.
|
|
535
|
+
const saved = await options.executor.results.recover(frame.input, frame.requestId);
|
|
525
536
|
if (saved) {
|
|
526
537
|
const last = saved.events.at(-1);
|
|
527
538
|
if (last?.type !== "turn.end")
|
|
528
539
|
throw new Error("Incomplete saved Runtime result");
|
|
529
|
-
execution.result = {
|
|
540
|
+
execution.result = { session: saved.session, event: last };
|
|
530
541
|
for (const event of saved.events)
|
|
531
542
|
emit(event);
|
|
532
543
|
return;
|
|
@@ -536,7 +547,7 @@ async function connect(options) {
|
|
|
536
547
|
active.delete(frame.requestId);
|
|
537
548
|
return;
|
|
538
549
|
}
|
|
539
|
-
const run = () => (
|
|
550
|
+
const run = () => executeTurn(options.executor, frame.input, emit, controller.signal, frame.requestId, context);
|
|
540
551
|
// Preparation can request context, then fall back once from native archive to DB.
|
|
541
552
|
// These retries precede started(), so they never replay model or tool work.
|
|
542
553
|
for (let retry = 0;; retry += 1) {
|
|
@@ -550,7 +561,7 @@ async function connect(options) {
|
|
|
550
561
|
frame.input.context = await requestContext(controller.signal);
|
|
551
562
|
}
|
|
552
563
|
}
|
|
553
|
-
await options.
|
|
564
|
+
await options.executor.results.record(execution.result.session, frame.input, frame.requestId, [...durableEvents, execution.result.event], execution.result.uncertainCleanup);
|
|
554
565
|
if (execution.result.uncertainCleanup) {
|
|
555
566
|
log("warn", "runtime.turn_cleanup_pending", { cleanup: execution.result.uncertainCleanup.error }, context);
|
|
556
567
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { ContentBlock, RuntimeCapabilities
|
|
1
|
+
import type { ContentBlock, RuntimeCapabilities } from "@neta-art/cohub";
|
|
2
2
|
import { type JsonRecord } from "./json-rpc.js";
|
|
3
|
-
import type { RuntimeSessionStore, NativeSession } from "./session-store.js";
|
|
4
|
-
import { type RuntimeDiagnostics, type RuntimeDiagnosticContext } from "./diagnostics.js";
|
|
5
3
|
export type HarnessOptions = {
|
|
6
4
|
pi?: string;
|
|
7
5
|
codex?: string;
|
|
@@ -9,19 +7,13 @@ export type HarnessOptions = {
|
|
|
9
7
|
export declare function harnessExecutableCandidates(binary: string, cwd: string, path: string, platform?: NodeJS.Platform, pathExt?: string | undefined): string[];
|
|
10
8
|
/** Discover installed executables only; authentication/capability errors stay explicit. */
|
|
11
9
|
export declare function installedHarnesses(cwd: string, options: HarnessOptions, path?: string): Promise<("pi" | "codex")[]>;
|
|
12
|
-
export type HarnessResult = {
|
|
13
|
-
state: NativeSession;
|
|
14
|
-
event: Extract<RuntimeExecutionEvent, {
|
|
15
|
-
type: "turn.end";
|
|
16
|
-
}>;
|
|
17
|
-
uncertainCleanup?: {
|
|
18
|
-
processGroupId: number;
|
|
19
|
-
error: string;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
10
|
export declare function piContent(value: unknown): ContentBlock[];
|
|
23
|
-
export declare function
|
|
11
|
+
export declare function imageForPi(block: Extract<ContentBlock, {
|
|
12
|
+
type: "image";
|
|
13
|
+
}>): Promise<{
|
|
14
|
+
type: string;
|
|
15
|
+
data: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
}>;
|
|
24
18
|
export declare function discoverHarnesses(harnesses: ("pi" | "codex")[], options: HarnessOptions, cwd: string): Promise<RuntimeCapabilities>;
|
|
25
|
-
export declare function executePi(input: RuntimeTurnInput, options: HarnessOptions, cwd: string, store: RuntimeSessionStore, emit: (event: RuntimeExecutionEvent) => void, signal: AbortSignal, diagnostics?: RuntimeDiagnostics, diagnosticContext?: RuntimeDiagnosticContext): Promise<HarnessResult>;
|
|
26
19
|
export declare function codexItemContent(item: JsonRecord): ContentBlock[];
|
|
27
|
-
export declare function executeCodex(input: RuntimeTurnInput, options: HarnessOptions, cwd: string, store: RuntimeSessionStore, emit: (event: RuntimeExecutionEvent) => void, signal: AbortSignal, diagnostics?: RuntimeDiagnostics, diagnosticContext?: RuntimeDiagnosticContext): Promise<HarnessResult>;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -2,11 +2,8 @@ import { access, stat } from "node:fs/promises";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { delimiter, join, resolve, win32 } from "node:path";
|
|
4
4
|
import { JsonRpcProcess, record } from "./json-rpc.js";
|
|
5
|
-
import { ProcessCleanupUncertainError } from "./process-group.js";
|
|
6
5
|
import { codexModelCatalog } from "./model-catalog.js";
|
|
7
|
-
import { codexTokenTotals, codexUsage, subtractCodexTokens } from "./codex-usage.js";
|
|
8
6
|
import { downloadPublicImage } from "../safe-remote-image.js";
|
|
9
|
-
import { serializeDiagnosticError } from "./diagnostics.js";
|
|
10
7
|
export function harnessExecutableCandidates(binary, cwd, path, platform = process.platform, pathExt = process.env.PATHEXT) {
|
|
11
8
|
const windows = platform === "win32";
|
|
12
9
|
const paths = windows ? win32 : { delimiter, join, resolve };
|
|
@@ -36,7 +33,6 @@ export async function installedHarnesses(cwd, options, path = process.env.PATH ?
|
|
|
36
33
|
}));
|
|
37
34
|
return found.filter((name) => name !== null);
|
|
38
35
|
}
|
|
39
|
-
const runtimeEnvironment = (input) => ({ COHUB_SPACE_ID: input.spaceId, COHUB_SESSION_ID: input.sessionId, COHUB_TURN_ID: input.turnId });
|
|
40
36
|
const array = (value) => Array.isArray(value) ? value : [];
|
|
41
37
|
const text = (value) => typeof value === "string" ? value : "";
|
|
42
38
|
export function piContent(value) {
|
|
@@ -53,49 +49,16 @@ export function piContent(value) {
|
|
|
53
49
|
return [{ type: "text", text: JSON.stringify(block) }];
|
|
54
50
|
});
|
|
55
51
|
}
|
|
56
|
-
async function imageForPi(block) {
|
|
52
|
+
export async function imageForPi(block) {
|
|
57
53
|
if (block.source.type === "base64")
|
|
58
54
|
return { type: "image", data: block.source.data, mimeType: block.source.media_type };
|
|
59
55
|
const image = await downloadPublicImage(block.source.url);
|
|
60
56
|
return { type: "image", data: Buffer.from(image.bytes).toString("base64"), mimeType: image.mimeType };
|
|
61
57
|
}
|
|
62
|
-
export function promptText(content) {
|
|
63
|
-
return content.filter((block) => block.type !== "image").map((block) => block.type === "text" ? block.text : JSON.stringify(block)).join("\n");
|
|
64
|
-
}
|
|
65
58
|
async function initializeCodex(rpc) {
|
|
66
59
|
await rpc.request("initialize", { clientInfo: { name: "cohub", title: "Cohub", version: "1" }, capabilities: { experimentalApi: true } });
|
|
67
60
|
rpc.write({ method: "initialized", params: {} });
|
|
68
61
|
}
|
|
69
|
-
/** Ask the native process to stop, then hard-close it if the request does not settle quickly. */
|
|
70
|
-
function createAbortEscalation(rpc, signal, interrupt) {
|
|
71
|
-
let timer = null;
|
|
72
|
-
const abort = () => {
|
|
73
|
-
interrupt();
|
|
74
|
-
timer ??= setTimeout(() => { void rpc.close().catch(() => undefined); }, 5000);
|
|
75
|
-
};
|
|
76
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
77
|
-
return {
|
|
78
|
-
abort,
|
|
79
|
-
clear: () => { signal.removeEventListener("abort", abort); if (timer)
|
|
80
|
-
clearTimeout(timer); },
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/** Native files stay authoritative; archival failure only degrades cross-host resume. */
|
|
84
|
-
async function finishHarnessTurn(store, state, message, resume, turnId, diagnosticContext) {
|
|
85
|
-
// The store records a redacted diagnostic; native files remain authoritative.
|
|
86
|
-
const archive = await store.archive(state, turnId, diagnosticContext).catch(() => null);
|
|
87
|
-
return { state, event: { type: "turn.end", message, resume, archive } };
|
|
88
|
-
}
|
|
89
|
-
/** Close the RPC process. An unconfirmable cleanup degrades the result to uncertain, never masks it. */
|
|
90
|
-
async function closeHarness(rpc, executionError) {
|
|
91
|
-
return rpc.close().then(() => undefined, (cleanupError) => {
|
|
92
|
-
const processGroupId = rpc.processGroupId;
|
|
93
|
-
if (!(cleanupError instanceof ProcessCleanupUncertainError) || processGroupId == null)
|
|
94
|
-
throw cleanupError;
|
|
95
|
-
const detail = executionError ? `; execution error: ${executionError}` : "";
|
|
96
|
-
return { processGroupId, error: `${cleanupError.message}${detail}` };
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
62
|
export async function discoverHarnesses(harnesses, options, cwd) {
|
|
100
63
|
const models = [];
|
|
101
64
|
await Promise.all(harnesses.map(async (harness) => {
|
|
@@ -133,132 +96,6 @@ export async function discoverHarnesses(harnesses, options, cwd) {
|
|
|
133
96
|
}));
|
|
134
97
|
return { harnesses, models };
|
|
135
98
|
}
|
|
136
|
-
export async function executePi(input, options, cwd, store, emit, signal, diagnostics, diagnosticContext) {
|
|
137
|
-
if (input.accessMode === "read_only")
|
|
138
|
-
throw new Error("Pi cannot enforce read-only access; select Cohub or Codex");
|
|
139
|
-
signal.throwIfAborted();
|
|
140
|
-
const { state, resume } = await store.prepare(input, cwd, signal);
|
|
141
|
-
signal.throwIfAborted();
|
|
142
|
-
const rpc = new JsonRpcProcess(options.pi || "pi", ["--mode", "rpc", "--session", state.path], cwd, "pi", runtimeEnvironment(input));
|
|
143
|
-
let ordinal = -1;
|
|
144
|
-
let currentContent = [];
|
|
145
|
-
let last = { ordinal: 0, content: [] };
|
|
146
|
-
const abortEscalation = createAbortEscalation(rpc, signal, () => { void rpc.request("abort").catch(() => undefined); });
|
|
147
|
-
let uncertainCleanup;
|
|
148
|
-
const stopFailureLogging = diagnostics
|
|
149
|
-
? rpc.onFailure((error) => diagnostics.log("error", "harness.rpc_process_failed", { error: serializeDiagnosticError(error) }, { ...diagnosticContext, component: "harness" }))
|
|
150
|
-
: () => undefined;
|
|
151
|
-
const stopTimeoutLogging = diagnostics
|
|
152
|
-
? rpc.onTimeout((method, timeoutMs) => diagnostics.log("error", "harness.rpc_timeout", { method, timeoutMs }, { ...diagnosticContext, component: "harness" }))
|
|
153
|
-
: () => undefined;
|
|
154
|
-
try {
|
|
155
|
-
if (input.model) {
|
|
156
|
-
let provider = input.provider;
|
|
157
|
-
if (!provider) {
|
|
158
|
-
const catalog = await rpc.request("get_available_models");
|
|
159
|
-
const matches = array(catalog.models).map(record).filter((model) => model.id === input.model);
|
|
160
|
-
if (matches.length !== 1)
|
|
161
|
-
throw new Error("Select a provider for this local model");
|
|
162
|
-
provider = text(matches[0]?.provider);
|
|
163
|
-
}
|
|
164
|
-
await rpc.request("set_model", { provider, modelId: input.model });
|
|
165
|
-
}
|
|
166
|
-
if (input.thinkingLevel)
|
|
167
|
-
await rpc.request("set_thinking_level", { level: input.thinkingLevel });
|
|
168
|
-
const stateResult = await rpc.request("get_state");
|
|
169
|
-
state.nativeSessionId = text(stateResult.sessionId) || state.nativeSessionId;
|
|
170
|
-
const model = record(stateResult.model);
|
|
171
|
-
const content = input.messages.flatMap((message) => message.content);
|
|
172
|
-
const images = await Promise.all(content.flatMap((block) => block.type === "image" ? [imageForPi(block)] : []));
|
|
173
|
-
signal.throwIfAborted();
|
|
174
|
-
await store.started(state, input.turnId);
|
|
175
|
-
await new Promise((resolve, reject) => {
|
|
176
|
-
const offFailure = rpc.onFailure(reject);
|
|
177
|
-
const off = rpc.onEvent((event) => {
|
|
178
|
-
try {
|
|
179
|
-
if (event.type === "extension_ui_request") {
|
|
180
|
-
if (["confirm", "select", "input", "editor"].includes(text(event.method)))
|
|
181
|
-
rpc.write({ type: "extension_ui_response", id: event.id, cancelled: true });
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (event.type === "message_start" && record(event.message).role === "assistant") {
|
|
185
|
-
ordinal++;
|
|
186
|
-
currentContent = [];
|
|
187
|
-
emit({ type: "message.start", ordinal });
|
|
188
|
-
}
|
|
189
|
-
if (event.type === "message_end" && record(event.message).role === "assistant") {
|
|
190
|
-
currentContent = piContent(record(event.message).content);
|
|
191
|
-
emit({ type: "content.replace", ordinal, content: currentContent });
|
|
192
|
-
}
|
|
193
|
-
if (event.type === "tool_execution_start") {
|
|
194
|
-
const id = text(event.toolCallId);
|
|
195
|
-
if (!currentContent.some((block) => block.type === "tool_use" && block.id === id))
|
|
196
|
-
currentContent.push({ type: "tool_use", id, name: text(event.toolName), input: record(event.args), _meta: { toolStatus: "running" } });
|
|
197
|
-
emit({ type: "content.replace", ordinal, content: [...currentContent] });
|
|
198
|
-
}
|
|
199
|
-
if (event.type === "tool_execution_update" || event.type === "tool_execution_end") {
|
|
200
|
-
const id = text(event.toolCallId);
|
|
201
|
-
const rawResult = event.type === "tool_execution_end" ? event.result : event.partialResult;
|
|
202
|
-
const result = record(rawResult);
|
|
203
|
-
const resultContent = typeof rawResult === "string" ? rawResult : piContent(result.content);
|
|
204
|
-
currentContent = currentContent.filter((block) => block.type !== "tool_result" || block.tool_use_id !== id);
|
|
205
|
-
currentContent.push({ type: "tool_result", tool_use_id: id, content: resultContent, is_error: Boolean(event.isError), _meta: { toolStatus: event.type === "tool_execution_end" ? "done" : "running" } });
|
|
206
|
-
emit({ type: "content.replace", ordinal, content: [...currentContent] });
|
|
207
|
-
}
|
|
208
|
-
if (event.type === "message_update") {
|
|
209
|
-
const delta = record(event.assistantMessageEvent);
|
|
210
|
-
if (delta.type === "text_delta" || delta.type === "thinking_delta")
|
|
211
|
-
emit({ type: "text.delta", ordinal, index: Number(delta.contentIndex ?? 0), kind: delta.type === "text_delta" ? "text" : "thinking", delta: text(delta.delta) });
|
|
212
|
-
}
|
|
213
|
-
if (event.type === "turn_end") {
|
|
214
|
-
const message = record(event.message);
|
|
215
|
-
const content = piContent(message.content);
|
|
216
|
-
for (const resultValue of array(event.toolResults)) {
|
|
217
|
-
const result = record(resultValue);
|
|
218
|
-
content.push({ type: "tool_result", tool_use_id: text(result.toolCallId), content: typeof result.content === "string" ? result.content : piContent(result.content), is_error: Boolean(result.isError) });
|
|
219
|
-
}
|
|
220
|
-
last = { ordinal: Math.max(0, ordinal), content, provider: text(message.provider) || text(model.provider), model: text(message.model) || text(model.id), usage: message.usage, stopReason: text(message.stopReason) === "toolUse" ? "tool_use" : text(message.stopReason) || "stop", errorMessage: text(message.errorMessage) || null };
|
|
221
|
-
emit({ type: "content.replace", ordinal: last.ordinal, content });
|
|
222
|
-
if (last.stopReason === "tool_use")
|
|
223
|
-
emit({ type: "message.commit", message: last });
|
|
224
|
-
}
|
|
225
|
-
if (event.type === "agent_settled" || event.type === "agent_end" && event.willRetry === undefined) {
|
|
226
|
-
off();
|
|
227
|
-
offFailure();
|
|
228
|
-
resolve();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
off();
|
|
233
|
-
offFailure();
|
|
234
|
-
reject(error);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
void rpc.request("prompt", { message: promptText(content), images }).catch((error) => { off(); offFailure(); reject(error); });
|
|
238
|
-
});
|
|
239
|
-
await rpc.request("get_state");
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
if (!state.pendingTurnId)
|
|
243
|
-
throw error;
|
|
244
|
-
last = { ...last, stopReason: signal.aborted ? "aborted" : "error", errorMessage: signal.aborted ? null : error instanceof Error ? error.message : String(error) };
|
|
245
|
-
}
|
|
246
|
-
finally {
|
|
247
|
-
abortEscalation.clear();
|
|
248
|
-
stopFailureLogging();
|
|
249
|
-
stopTimeoutLogging();
|
|
250
|
-
uncertainCleanup = await closeHarness(rpc, last.errorMessage);
|
|
251
|
-
}
|
|
252
|
-
if (signal.aborted)
|
|
253
|
-
last = { ...last, stopReason: "aborted" };
|
|
254
|
-
return { ...await finishHarnessTurn(store, state, last, resume, input.turnId, {
|
|
255
|
-
component: "harness",
|
|
256
|
-
sessionId: input.sessionId,
|
|
257
|
-
turnId: input.turnId,
|
|
258
|
-
harness: "pi",
|
|
259
|
-
traceContext: input.traceContext,
|
|
260
|
-
}), ...(uncertainCleanup ? { uncertainCleanup } : {}) };
|
|
261
|
-
}
|
|
262
99
|
export function codexItemContent(item) {
|
|
263
100
|
if (item.type === "agentMessage" || item.type === "plan")
|
|
264
101
|
return [{ type: "text", text: text(item.text) }];
|
|
@@ -276,205 +113,3 @@ export function codexItemContent(item) {
|
|
|
276
113
|
{ type: "tool_result", tool_use_id: id, content: typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : JSON.stringify(item.result ?? item.error ?? item.changes ?? item), is_error: item.status === "failed" || typeof item.exitCode === "number" && item.exitCode !== 0 },
|
|
277
114
|
];
|
|
278
115
|
}
|
|
279
|
-
export async function executeCodex(input, options, cwd, store, emit, signal, diagnostics, diagnosticContext) {
|
|
280
|
-
signal.throwIfAborted();
|
|
281
|
-
const { state, resume } = await store.prepare(input, cwd, signal);
|
|
282
|
-
signal.throwIfAborted();
|
|
283
|
-
const rpc = new JsonRpcProcess(options.codex || "codex", ["app-server", "--listen", "stdio://"], cwd, "codex", runtimeEnvironment(input));
|
|
284
|
-
const stopFailureLogging = diagnostics
|
|
285
|
-
? rpc.onFailure((error) => diagnostics.log("error", "harness.rpc_process_failed", { error: serializeDiagnosticError(error) }, { ...diagnosticContext, component: "harness" }))
|
|
286
|
-
: () => undefined;
|
|
287
|
-
const stopTimeoutLogging = diagnostics
|
|
288
|
-
? rpc.onTimeout((method, timeoutMs) => diagnostics.log("error", "harness.rpc_timeout", { method, timeoutMs }, { ...diagnosticContext, component: "harness" }))
|
|
289
|
-
: () => undefined;
|
|
290
|
-
let nativeTurnId = null;
|
|
291
|
-
let ordinal = -1;
|
|
292
|
-
const ordinals = new Map();
|
|
293
|
-
const items = new Map();
|
|
294
|
-
const outputs = new Map();
|
|
295
|
-
const completedItems = new Set();
|
|
296
|
-
let pending = null;
|
|
297
|
-
let final = { ordinal: 0, content: [] };
|
|
298
|
-
let usage;
|
|
299
|
-
let usageBaseline = state.codexTokenTotals;
|
|
300
|
-
const latestMessage = () => {
|
|
301
|
-
if (pending && pending.ordinal >= ordinal)
|
|
302
|
-
return pending;
|
|
303
|
-
const item = [...items.entries()].find(([id]) => ordinals.get(id) === ordinal)?.[1];
|
|
304
|
-
return { ordinal: Math.max(0, ordinal), content: item ? codexItemContent(item) : [] };
|
|
305
|
-
};
|
|
306
|
-
const abortEscalation = createAbortEscalation(rpc, signal, () => {
|
|
307
|
-
if (nativeTurnId)
|
|
308
|
-
void rpc.request("turn/interrupt", { threadId: state.nativeSessionId, turnId: nativeTurnId }).catch(() => undefined);
|
|
309
|
-
});
|
|
310
|
-
let uncertainCleanup;
|
|
311
|
-
try {
|
|
312
|
-
await initializeCodex(rpc);
|
|
313
|
-
const threadOptions = {
|
|
314
|
-
cwd,
|
|
315
|
-
...(input.model ? { model: input.model } : {}),
|
|
316
|
-
...(input.provider && input.provider !== "codex" ? { modelProvider: input.provider } : {}),
|
|
317
|
-
...(input.accessMode === "read_only" ? { sandbox: "read-only" } : {}),
|
|
318
|
-
};
|
|
319
|
-
const opened = resume === "native"
|
|
320
|
-
? await rpc.request("thread/resume", { ...threadOptions, threadId: state.nativeSessionId, path: state.path, excludeTurns: true })
|
|
321
|
-
: resume === "restored"
|
|
322
|
-
? await rpc.request("thread/fork", { ...threadOptions, threadId: state.nativeSessionId, path: state.path, excludeTurns: true })
|
|
323
|
-
: resume === "handoff"
|
|
324
|
-
? await rpc.request("thread/resume", { ...threadOptions, threadId: state.nativeSessionId, path: state.path, excludeTurns: true })
|
|
325
|
-
: await rpc.request("thread/start", threadOptions);
|
|
326
|
-
const thread = record(opened.thread);
|
|
327
|
-
if (typeof thread.id !== "string" || typeof thread.path !== "string")
|
|
328
|
-
throw new Error("Codex did not provide a durable native thread");
|
|
329
|
-
state.nativeSessionId = thread.id;
|
|
330
|
-
state.path = thread.path;
|
|
331
|
-
const provider = text(opened.modelProvider) || "codex";
|
|
332
|
-
const model = text(opened.model) || input.model;
|
|
333
|
-
const content = await Promise.all(input.messages.flatMap((message) => message.content).map(async (block) => {
|
|
334
|
-
if (block.type === "image") {
|
|
335
|
-
const image = await imageForPi(block);
|
|
336
|
-
return { type: "image", url: `data:${image.mimeType};base64,${image.data}` };
|
|
337
|
-
}
|
|
338
|
-
return { type: "text", text: block.type === "text" ? block.text : JSON.stringify(block), text_elements: [] };
|
|
339
|
-
}));
|
|
340
|
-
signal.throwIfAborted();
|
|
341
|
-
await store.started(state, input.turnId);
|
|
342
|
-
await new Promise((resolve, reject) => {
|
|
343
|
-
const offFailure = rpc.onFailure(reject);
|
|
344
|
-
const off = rpc.onEvent((event) => {
|
|
345
|
-
try {
|
|
346
|
-
const method = text(event.method);
|
|
347
|
-
const params = record(event.params);
|
|
348
|
-
if (event.id != null) {
|
|
349
|
-
// Keep native approval policy. An unattended client never grants an escalation.
|
|
350
|
-
rpc.write({ id: event.id, error: { code: -32000, message: "Approval requires an interactive local client" } });
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (params.threadId !== state.nativeSessionId)
|
|
354
|
-
return;
|
|
355
|
-
if (method === "turn/started") {
|
|
356
|
-
nativeTurnId = text(record(params.turn).id);
|
|
357
|
-
if (signal.aborted)
|
|
358
|
-
abortEscalation.abort();
|
|
359
|
-
}
|
|
360
|
-
if (method === "thread/tokenUsage/updated" && nativeTurnId && params.turnId === nativeTurnId) {
|
|
361
|
-
const nativeUsage = record(params.tokenUsage);
|
|
362
|
-
const total = codexTokenTotals(nativeUsage.total);
|
|
363
|
-
usageBaseline ??= subtractCodexTokens(total, codexTokenTotals(nativeUsage.last));
|
|
364
|
-
state.codexTokenTotals = total;
|
|
365
|
-
usage = codexUsage(subtractCodexTokens(total, usageBaseline));
|
|
366
|
-
}
|
|
367
|
-
if (method === "item/started") {
|
|
368
|
-
const item = record(params.item);
|
|
369
|
-
if (item.type === "userMessage" || completedItems.has(text(item.id)))
|
|
370
|
-
return;
|
|
371
|
-
const existingOrdinal = ordinals.get(text(item.id));
|
|
372
|
-
if (pending && (existingOrdinal == null || existingOrdinal > pending.ordinal)) {
|
|
373
|
-
emit({ type: "message.commit", message: pending });
|
|
374
|
-
pending = null;
|
|
375
|
-
}
|
|
376
|
-
if (existingOrdinal != null) {
|
|
377
|
-
items.set(text(item.id), item);
|
|
378
|
-
if (item.type === "agentMessage" && text(item.text))
|
|
379
|
-
emit({ type: "content.replace", ordinal: existingOrdinal, content: codexItemContent(item) });
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
ordinal++;
|
|
383
|
-
ordinals.set(text(item.id), ordinal);
|
|
384
|
-
items.set(text(item.id), item);
|
|
385
|
-
emit({ type: "message.start", ordinal });
|
|
386
|
-
if (!["agentMessage", "reasoning", "plan"].includes(text(item.type)))
|
|
387
|
-
emit({ type: "content.replace", ordinal, content: codexItemContent(item).filter((block) => block.type !== "tool_result") });
|
|
388
|
-
}
|
|
389
|
-
if (method === "item/agentMessage/delta" || method === "item/reasoning/textDelta" || method === "item/reasoning/summaryTextDelta") {
|
|
390
|
-
const id = text(params.itemId);
|
|
391
|
-
if (completedItems.has(id))
|
|
392
|
-
return;
|
|
393
|
-
const itemOrdinal = ordinals.get(id);
|
|
394
|
-
const item = items.get(id);
|
|
395
|
-
if (item) {
|
|
396
|
-
if (method === "item/agentMessage/delta")
|
|
397
|
-
item.text = text(item.text) + text(params.delta);
|
|
398
|
-
else {
|
|
399
|
-
const key = method.includes("summary") ? "summary" : "content";
|
|
400
|
-
item[key] = [array(item[key]).map(text).join("\n") + text(params.delta)];
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (itemOrdinal != null)
|
|
404
|
-
emit({ type: "text.delta", ordinal: itemOrdinal, index: 0, kind: method === "item/agentMessage/delta" ? "text" : "thinking", delta: text(params.delta) });
|
|
405
|
-
}
|
|
406
|
-
if (method === "item/commandExecution/outputDelta") {
|
|
407
|
-
const id = text(params.itemId);
|
|
408
|
-
const item = items.get(id);
|
|
409
|
-
const itemOrdinal = ordinals.get(id);
|
|
410
|
-
if (item && itemOrdinal != null) {
|
|
411
|
-
const output = (outputs.get(id) ?? "") + text(params.delta);
|
|
412
|
-
outputs.set(id, output);
|
|
413
|
-
item.aggregatedOutput = output;
|
|
414
|
-
emit({ type: "content.replace", ordinal: itemOrdinal, content: codexItemContent(item) });
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (method === "item/completed") {
|
|
418
|
-
const item = record(params.item);
|
|
419
|
-
if (item.type === "userMessage" || completedItems.has(text(item.id)))
|
|
420
|
-
return;
|
|
421
|
-
completedItems.add(text(item.id));
|
|
422
|
-
const itemOrdinal = ordinals.get(text(item.id)) ?? ++ordinal;
|
|
423
|
-
if (!ordinals.has(text(item.id))) {
|
|
424
|
-
ordinals.set(text(item.id), itemOrdinal);
|
|
425
|
-
emit({ type: "message.start", ordinal: itemOrdinal });
|
|
426
|
-
}
|
|
427
|
-
items.set(text(item.id), item);
|
|
428
|
-
const message = { ordinal: itemOrdinal, content: codexItemContent(item), provider, model, stopReason: "stop" };
|
|
429
|
-
emit({ type: "content.replace", ordinal: itemOrdinal, content: message.content });
|
|
430
|
-
if (pending && pending.ordinal > itemOrdinal)
|
|
431
|
-
emit({ type: "message.commit", message });
|
|
432
|
-
else {
|
|
433
|
-
if (pending && pending.ordinal !== itemOrdinal)
|
|
434
|
-
emit({ type: "message.commit", message: pending });
|
|
435
|
-
pending = message;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (method === "turn/completed") {
|
|
439
|
-
const turn = record(params.turn);
|
|
440
|
-
final = { ...latestMessage(), provider, model, stopReason: turn.status === "interrupted" ? "aborted" : turn.status === "failed" ? "error" : "stop", errorMessage: text(record(turn.error).message) || null };
|
|
441
|
-
off();
|
|
442
|
-
offFailure();
|
|
443
|
-
resolve();
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
catch (error) {
|
|
447
|
-
off();
|
|
448
|
-
offFailure();
|
|
449
|
-
reject(error);
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
void rpc.request("turn/start", { threadId: state.nativeSessionId, clientUserMessageId: input.userMessageId, input: content, ...(input.thinkingLevel ? { effort: input.thinkingLevel } : {}) })
|
|
453
|
-
.then((result) => { nativeTurnId = text(record(result.turn).id); if (signal.aborted)
|
|
454
|
-
abortEscalation.abort(); })
|
|
455
|
-
.catch((error) => { off(); offFailure(); reject(error); });
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
if (!state.pendingTurnId)
|
|
460
|
-
throw error;
|
|
461
|
-
final = { ...latestMessage(), stopReason: signal.aborted ? "aborted" : "error", errorMessage: signal.aborted ? null : error instanceof Error ? error.message : String(error) };
|
|
462
|
-
}
|
|
463
|
-
finally {
|
|
464
|
-
abortEscalation.clear();
|
|
465
|
-
stopFailureLogging();
|
|
466
|
-
stopTimeoutLogging();
|
|
467
|
-
uncertainCleanup = await closeHarness(rpc, final.errorMessage);
|
|
468
|
-
}
|
|
469
|
-
if (signal.aborted)
|
|
470
|
-
final = { ...final, stopReason: "aborted" };
|
|
471
|
-
if (usage)
|
|
472
|
-
final = { ...final, usage };
|
|
473
|
-
return { ...await finishHarnessTurn(store, state, final, resume, input.turnId, {
|
|
474
|
-
component: "harness",
|
|
475
|
-
sessionId: input.sessionId,
|
|
476
|
-
turnId: input.turnId,
|
|
477
|
-
harness: "codex",
|
|
478
|
-
traceContext: input.traceContext,
|
|
479
|
-
}), ...(uncertainCleanup ? { uncertainCleanup } : {}) };
|
|
480
|
-
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { RuntimeSummary } from "./presentation.js";
|
|
2
2
|
export declare function runtimeInstanceDirectory(identity: string, spaceId: string): string;
|
|
3
|
+
type Response = {
|
|
4
|
+
status: RuntimeSummary;
|
|
5
|
+
result?: unknown;
|
|
6
|
+
};
|
|
3
7
|
export declare function requestRuntimeInstance(directory: string, action?: "status" | "stop", force?: boolean): Promise<RuntimeSummary | null>;
|
|
8
|
+
/** Send a control action to the running Runtime; `null` when none runs. */
|
|
9
|
+
export declare function controlRuntimeInstance(directory: string, action: string, payload?: Record<string, unknown>, timeoutMs?: number): Promise<Response | null>;
|
|
4
10
|
/** Private local IPC is both the single-instance guard and the control surface. */
|
|
5
|
-
export declare function ownRuntimeInstance(directory: string, status: () => RuntimeSummary, stop: (force: boolean) => Promise<void>): Promise<() => Promise<void>>;
|
|
11
|
+
export declare function ownRuntimeInstance(directory: string, status: () => RuntimeSummary, stop: (force: boolean) => Promise<void>, control?: (action: string, message: Record<string, unknown>) => Promise<unknown>): Promise<() => Promise<void>>;
|
|
12
|
+
export {};
|