@scotthuang/agent-knock-knock 0.13.16 → 0.13.17
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/CHANGELOG.md +8 -0
- package/dist/src/claude-terminal-agent-adapter.js +1 -1
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/codex-app-server-interaction-client.js +68 -26
- package/dist/src/codex-app-server-interaction-client.js.map +1 -1
- package/dist/src/codex-app-server-read-client.js +52 -20
- package/dist/src/codex-app-server-read-client.js.map +1 -1
- package/dist/src/codex-astra-composer-proof.js +1 -1
- package/dist/src/codex-astra-composer-proof.js.map +1 -1
- package/dist/src/codex-open-rollout-inventory.d.ts +51 -0
- package/dist/src/codex-open-rollout-inventory.js +365 -0
- package/dist/src/codex-open-rollout-inventory.js.map +1 -0
- package/dist/src/codex-paginated-thread-binding.js +1 -1
- package/dist/src/codex-paginated-thread-binding.js.map +1 -1
- package/dist/src/codex-rollout-bound-completion.d.ts +44 -0
- package/dist/src/codex-rollout-bound-completion.js +357 -0
- package/dist/src/codex-rollout-bound-completion.js.map +1 -0
- package/dist/src/codex-rollout-file.d.ts +15 -0
- package/dist/src/codex-rollout-file.js +98 -0
- package/dist/src/codex-rollout-file.js.map +1 -0
- package/dist/src/codex-rollout-human-task.d.ts +61 -0
- package/dist/src/codex-rollout-human-task.js +555 -0
- package/dist/src/codex-rollout-human-task.js.map +1 -0
- package/dist/src/codex-rollout-interaction-evidence.d.ts +48 -0
- package/dist/src/codex-rollout-interaction-evidence.js +521 -0
- package/dist/src/codex-rollout-interaction-evidence.js.map +1 -0
- package/dist/src/codex-rollout-inventory.d.ts +5 -0
- package/dist/src/codex-rollout-inventory.js +81 -0
- package/dist/src/codex-rollout-inventory.js.map +1 -0
- package/dist/src/codex-rollout-records.d.ts +49 -0
- package/dist/src/codex-rollout-records.js +298 -0
- package/dist/src/codex-rollout-records.js.map +1 -0
- package/dist/src/codex-sqlite-thread-query.d.ts +85 -0
- package/dist/src/codex-sqlite-thread-query.js +440 -0
- package/dist/src/codex-sqlite-thread-query.js.map +1 -0
- package/dist/src/codex-status-history-navigation.d.ts +1 -1
- package/dist/src/codex-status-history-navigation.js +1 -1
- package/dist/src/codex-status-history-navigation.js.map +1 -1
- package/dist/src/codex-store-adapter.d.ts +3 -50
- package/dist/src/codex-store-adapter.js +5 -796
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.js +1 -1
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +4 -134
- package/dist/src/native-thread-lifecycle-cli-adapter.js +19 -633
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-contract.d.ts +138 -0
- package/dist/src/native-thread-lifecycle-cli-contract.js +2 -0
- package/dist/src/native-thread-lifecycle-cli-contract.js.map +1 -0
- package/dist/src/semantic-permission-control.js +33 -6
- package/dist/src/semantic-permission-control.js.map +1 -1
- package/dist/src/semantic-tool-descriptions.d.ts +26 -0
- package/dist/src/semantic-tool-descriptions.js +259 -0
- package/dist/src/semantic-tool-descriptions.js.map +1 -0
- package/dist/src/semantic-tool-runtime.js +45 -37
- package/dist/src/semantic-tool-runtime.js.map +1 -1
- package/dist/src/semantic-tool-schemas.js +134 -33
- package/dist/src/semantic-tool-schemas.js.map +1 -1
- package/dist/src/terminal-action-contracts.d.ts +4 -0
- package/dist/src/terminal-action-contracts.js +695 -0
- package/dist/src/terminal-action-contracts.js.map +1 -0
- package/dist/src/terminal-composer-classifier.d.ts +1 -1
- package/dist/src/terminal-composer-classifier.js +2 -2
- package/dist/src/terminal-composer-classifier.js.map +1 -1
- package/dist/src/terminal-interaction-response-bridge.js +1 -1
- package/dist/src/terminal-interaction-response-bridge.js.map +1 -1
- package/dist/src/terminal-list-renderer.d.ts +1 -2
- package/dist/src/terminal-list-renderer.js +8 -424
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-model-control-action-policy.d.ts +1 -1
- package/dist/src/terminal-model-control-availability.d.ts +1 -1
- package/dist/src/terminal-model-control-availability.js +1 -1
- package/dist/src/terminal-model-control-availability.js.map +1 -1
- package/dist/src/terminal-model-control-cli-adapter.d.ts +34 -0
- package/dist/src/terminal-model-control-cli-adapter.js +669 -0
- package/dist/src/terminal-model-control-cli-adapter.js.map +1 -0
- package/dist/src/terminal-model-control-contract.d.ts +198 -0
- package/dist/src/terminal-model-control-contract.js +15 -0
- package/dist/src/terminal-model-control-contract.js.map +1 -0
- package/dist/src/terminal-model-control-input.d.ts +29 -0
- package/dist/src/terminal-model-control-input.js +86 -0
- package/dist/src/terminal-model-control-input.js.map +1 -0
- package/dist/src/terminal-model-control-profile.d.ts +2 -0
- package/dist/src/terminal-model-control-profile.js +6 -0
- package/dist/src/terminal-model-control-profile.js.map +1 -1
- package/dist/src/terminal-model-control-subject.d.ts +52 -0
- package/dist/src/terminal-model-control-subject.js +73 -0
- package/dist/src/terminal-model-control-subject.js.map +1 -1
- package/dist/src/terminal-model-control-surface.d.ts +82 -0
- package/dist/src/terminal-model-control-surface.js +688 -0
- package/dist/src/terminal-model-control-surface.js.map +1 -0
- package/dist/src/terminal-model-control.d.ts +6 -270
- package/dist/src/terminal-model-control.js +11 -988
- package/dist/src/terminal-model-control.js.map +1 -1
- package/dist/src/terminal-model-selection.d.ts +33 -0
- package/dist/src/terminal-model-selection.js +143 -0
- package/dist/src/terminal-model-selection.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.js +38 -59
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-monitor-attention-store.d.ts +65 -0
- package/dist/src/terminal-monitor-attention-store.js +518 -0
- package/dist/src/terminal-monitor-attention-store.js.map +1 -0
- package/dist/src/terminal-monitor-collateral-recovery.d.ts +12 -0
- package/dist/src/terminal-monitor-collateral-recovery.js +415 -0
- package/dist/src/terminal-monitor-collateral-recovery.js.map +1 -0
- package/dist/src/terminal-monitor-interaction-projection.d.ts +19 -0
- package/dist/src/terminal-monitor-interaction-projection.js +48 -0
- package/dist/src/terminal-monitor-interaction-projection.js.map +1 -0
- package/dist/src/terminal-monitor-startup-recovery.d.ts +15 -0
- package/dist/src/terminal-monitor-startup-recovery.js +767 -0
- package/dist/src/terminal-monitor-startup-recovery.js.map +1 -0
- package/dist/src/terminal-monitor-state-cli-adapter.d.ts +2 -104
- package/dist/src/terminal-monitor-state-cli-adapter.js +28 -1640
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-contract.d.ts +108 -0
- package/dist/src/terminal-monitor-state-contract.js +8 -0
- package/dist/src/terminal-monitor-state-contract.js.map +1 -0
- package/dist/src/terminal-native-inspection-bridge.d.ts +4 -153
- package/dist/src/terminal-native-inspection-bridge.js +7 -646
- package/dist/src/terminal-native-inspection-bridge.js.map +1 -1
- package/dist/src/terminal-native-inspection-contract.d.ts +128 -0
- package/dist/src/terminal-native-inspection-contract.js +38 -0
- package/dist/src/terminal-native-inspection-contract.js.map +1 -0
- package/dist/src/terminal-native-inspection-profile.d.ts +15 -0
- package/dist/src/terminal-native-inspection-profile.js +185 -0
- package/dist/src/terminal-native-inspection-profile.js.map +1 -0
- package/dist/src/terminal-native-inspection-proof.d.ts +62 -0
- package/dist/src/terminal-native-inspection-proof.js +362 -0
- package/dist/src/terminal-native-inspection-proof.js.map +1 -0
- package/dist/src/terminal-permission-control-bridge.js +1 -1
- package/dist/src/terminal-permission-control-bridge.js.map +1 -1
- package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-runtime-cli-adapter.js +1 -1
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +5 -148
- package/dist/src/terminal-submission-acceptance.js +21 -1845
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-watch-anchor-capture.d.ts +9 -0
- package/dist/src/terminal-watch-anchor-capture.js +139 -0
- package/dist/src/terminal-watch-anchor-capture.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.d.ts +3 -117
- package/dist/src/terminal-watch-cli-adapter.js +10 -1992
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-contract.d.ts +117 -0
- package/dist/src/terminal-watch-cli-contract.js +2 -0
- package/dist/src/terminal-watch-cli-contract.js.map +1 -0
- package/dist/src/terminal-watch-fallback-observation.d.ts +15 -0
- package/dist/src/terminal-watch-fallback-observation.js +249 -0
- package/dist/src/terminal-watch-fallback-observation.js.map +1 -0
- package/dist/src/terminal-watch-interaction-observation.d.ts +26 -0
- package/dist/src/terminal-watch-interaction-observation.js +262 -0
- package/dist/src/terminal-watch-interaction-observation.js.map +1 -0
- package/dist/src/terminal-watch-observation-adapter.d.ts +4 -0
- package/dist/src/terminal-watch-observation-adapter.js +307 -0
- package/dist/src/terminal-watch-observation-adapter.js.map +1 -0
- package/dist/src/terminal-watch-observation-evidence.d.ts +25 -0
- package/dist/src/terminal-watch-observation-evidence.js +184 -0
- package/dist/src/terminal-watch-observation-evidence.js.map +1 -0
- package/dist/src/terminal-watch-response-authority.d.ts +6 -0
- package/dist/src/terminal-watch-response-authority.js +333 -0
- package/dist/src/terminal-watch-response-authority.js.map +1 -0
- package/dist/src/terminal-watch-response-cli-adapter.d.ts +3 -0
- package/dist/src/terminal-watch-response-cli-adapter.js +230 -0
- package/dist/src/terminal-watch-response-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-terminal-identity.d.ts +39 -0
- package/dist/src/terminal-watch-terminal-identity.js +380 -0
- package/dist/src/terminal-watch-terminal-identity.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
export { parseLsofOpenFiles, resolveCodexOpenRolloutIdentity, inspectCodexOpenRootRolloutInventory } from "./codex-open-rollout-inventory.js";
|
|
2
|
+
import { inspectCodexOpenRootRolloutInventory, selectCodexOpenRolloutIdentity } from "./codex-open-rollout-inventory.js";
|
|
3
|
+
export { runCodexSqliteThreadQuery, latestStateDbPath, buildThreadSelect, buildThreadByIdSelect } from "./codex-sqlite-thread-query.js";
|
|
4
|
+
import { NATIVE_THREAD_ID_PATTERN, runCodexSqliteThreadQuery, validateCodexThreadQueryResult, inspectCodexSqliteFiles, assertStableCodexSqliteMain, codexSqliteQueryFailure, isSqliteCantOpen, codexSqliteQueryDiagnosticError, latestStateDbPath } from "./codex-sqlite-thread-query.js";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
3
6
|
import fs from "node:fs";
|
|
4
7
|
import os from "node:os";
|
|
5
8
|
import path from "node:path";
|
|
6
|
-
import { pathToFileURL } from "node:url";
|
|
7
|
-
import { CodexTransientDuplicateOpenRootDescriptorsError } from "./agent-session-provider.js";
|
|
8
9
|
import { isValidCodexAgentVersion } from "./codex-lifecycle-compatibility.js";
|
|
9
10
|
import { codexThreadUsesLegacyRollout, discoverCodexProcesses } from "./codex-session-provider.js";
|
|
10
11
|
import { SystemTerminalProcessSource, runProcessCommand } from "./terminal-process-source.js";
|
|
11
12
|
export { parseLsofCwdMap, parsePsProcessSnapshots } from "./terminal-process-source.js";
|
|
12
|
-
const NATIVE_THREAD_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
13
13
|
const MAX_CODEX_SESSION_META_BYTES = 1024 * 1024;
|
|
14
|
-
const MAX_SQLITE_QUERY_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
15
|
-
const MAX_SQLITE_ERROR_OUTPUT_BYTES = 1024 * 1024;
|
|
16
|
-
const SQLITE_QUERY_TIMEOUT_MS = 10_000;
|
|
17
14
|
const DEFAULT_SQLITE_CANTOPEN_RETRY_DELAYS_MS = [25, 75, 150];
|
|
18
|
-
const MAX_CODEX_OPEN_ROOT_ROLLOUTS = 128;
|
|
19
15
|
const NO_FOLLOW_FLAG = typeof fs.constants.O_NOFOLLOW === "number"
|
|
20
16
|
? fs.constants.O_NOFOLLOW
|
|
21
17
|
: 0;
|
|
@@ -317,796 +313,9 @@ export class CodexStoreAdapter {
|
|
|
317
313
|
return validateCodexThreadQueryResult(materializedResult);
|
|
318
314
|
}
|
|
319
315
|
}
|
|
320
|
-
class CodexSqliteSessionError extends Error {
|
|
321
|
-
status;
|
|
322
|
-
stage;
|
|
323
|
-
constructor({ message, status, stage }) {
|
|
324
|
-
super(message);
|
|
325
|
-
this.name = "CodexSqliteSessionError";
|
|
326
|
-
this.status = status;
|
|
327
|
-
this.stage = stage;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
export async function runCodexSqliteThreadQuery(request) {
|
|
331
|
-
if (request.nativeThreadId && request.filters) {
|
|
332
|
-
throw new Error("Codex thread query cannot combine an exact UUID with lifecycle filters");
|
|
333
|
-
}
|
|
334
|
-
if (request.nativeThreadId &&
|
|
335
|
-
!NATIVE_THREAD_ID_PATTERN.test(request.nativeThreadId)) {
|
|
336
|
-
throw new Error("Codex thread lookup requires an exact UUID");
|
|
337
|
-
}
|
|
338
|
-
if (request.filters?.cwd && !path.isAbsolute(request.filters.cwd)) {
|
|
339
|
-
throw new Error("Codex lifecycle thread query requires an absolute cwd");
|
|
340
|
-
}
|
|
341
|
-
const nonce = randomUUID();
|
|
342
|
-
const controlColumn = "__akk_sqlite_control";
|
|
343
|
-
const schemaControl = `schema:${nonce}`;
|
|
344
|
-
const rowsControl = `rows:${nonce}`;
|
|
345
|
-
const schemaMarker = JSON.stringify([{ [controlColumn]: schemaControl }]);
|
|
346
|
-
const rowsMarker = JSON.stringify([{ [controlColumn]: rowsControl }]);
|
|
347
|
-
const databaseArgument = request.openMode === "query_only"
|
|
348
|
-
? sqliteReadWriteUri(request.dbPath)
|
|
349
|
-
: request.dbPath;
|
|
350
|
-
const args = ["-batch", "-bail", "-json"];
|
|
351
|
-
if (request.openMode === "readonly") {
|
|
352
|
-
args.push("-readonly");
|
|
353
|
-
}
|
|
354
|
-
const parameterCommands = sqliteThreadFilterParameterCommands(request.filters);
|
|
355
|
-
for (const command of parameterCommands) {
|
|
356
|
-
args.push("-cmd", command);
|
|
357
|
-
}
|
|
358
|
-
if (request.openMode === "query_only") {
|
|
359
|
-
// SQLite may materialize WAL/SHM bookkeeping on this mode=rw connection,
|
|
360
|
-
// while query_only forbids business SQL writes for the AKK session.
|
|
361
|
-
// Parameter initialization, when needed, only creates a TEMP table and
|
|
362
|
-
// must precede query_only because SQLite also applies it to TEMP writes.
|
|
363
|
-
args.push("-cmd", "PRAGMA query_only=ON");
|
|
364
|
-
}
|
|
365
|
-
args.push(databaseArgument);
|
|
366
|
-
return new Promise((resolve, reject) => {
|
|
367
|
-
const child = spawn("sqlite3", args, {
|
|
368
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
369
|
-
});
|
|
370
|
-
let phase = "schema";
|
|
371
|
-
let output = "";
|
|
372
|
-
let stderr = "";
|
|
373
|
-
let result;
|
|
374
|
-
let authoritativeColumns = [];
|
|
375
|
-
let terminalError;
|
|
376
|
-
let settled = false;
|
|
377
|
-
const timeout = setTimeout(() => {
|
|
378
|
-
if (!terminalError) {
|
|
379
|
-
terminalError = new CodexSqliteSessionError({
|
|
380
|
-
message: `sqlite3 thread query timed out after ${SQLITE_QUERY_TIMEOUT_MS}ms`,
|
|
381
|
-
status: null,
|
|
382
|
-
stage: phase
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
child.kill("SIGKILL");
|
|
386
|
-
}, SQLITE_QUERY_TIMEOUT_MS);
|
|
387
|
-
const stopWithError = (error) => {
|
|
388
|
-
if (!terminalError) {
|
|
389
|
-
terminalError = error instanceof CodexSqliteSessionError
|
|
390
|
-
? error
|
|
391
|
-
: new CodexSqliteSessionError({
|
|
392
|
-
message: error.message,
|
|
393
|
-
status: null,
|
|
394
|
-
stage: phase
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
child.kill("SIGKILL");
|
|
398
|
-
};
|
|
399
|
-
const appendOutput = (current, chunk, limit) => {
|
|
400
|
-
const next = current + chunk;
|
|
401
|
-
if (Buffer.byteLength(next, "utf8") > limit) {
|
|
402
|
-
throw new Error(`sqlite3 ${phase} output exceeded ${limit} bytes`);
|
|
403
|
-
}
|
|
404
|
-
return next;
|
|
405
|
-
};
|
|
406
|
-
const parseArray = (text, label) => {
|
|
407
|
-
if (!text.trim()) {
|
|
408
|
-
return [];
|
|
409
|
-
}
|
|
410
|
-
const parsed = JSON.parse(text);
|
|
411
|
-
if (!Array.isArray(parsed)) {
|
|
412
|
-
throw new Error(`sqlite3 ${label} output was not a JSON array`);
|
|
413
|
-
}
|
|
414
|
-
return parsed;
|
|
415
|
-
};
|
|
416
|
-
const writeRowsQuery = (columns) => {
|
|
417
|
-
validateCodexThreadColumns(columns);
|
|
418
|
-
authoritativeColumns = columns;
|
|
419
|
-
const sql = request.nativeThreadId
|
|
420
|
-
? buildThreadByIdSelect(columns, request.nativeThreadId)
|
|
421
|
-
: buildThreadSelect(columns, request.maxSessions, request.filters);
|
|
422
|
-
phase = "rows";
|
|
423
|
-
child.stdin.write(`${sql};\nselect '${rowsControl}' as "${controlColumn}";\n`);
|
|
424
|
-
};
|
|
425
|
-
const consumeOutput = () => {
|
|
426
|
-
if (phase === "schema") {
|
|
427
|
-
const markerIndex = output.indexOf(schemaMarker);
|
|
428
|
-
if (markerIndex < 0) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
const schema = parseArray(output.slice(0, markerIndex).trim(), "schema");
|
|
432
|
-
const columns = schema
|
|
433
|
-
.map((column) => typeof column.name === "string" ? column.name : "")
|
|
434
|
-
.filter(Boolean);
|
|
435
|
-
output = output.slice(markerIndex + schemaMarker.length).trimStart();
|
|
436
|
-
phase = "schema_hook";
|
|
437
|
-
void Promise.resolve(request.afterSchema?.(columns))
|
|
438
|
-
.then(() => writeRowsQuery(columns))
|
|
439
|
-
.catch((error) => stopWithError(error instanceof Error ? error : new Error(String(error))));
|
|
440
|
-
}
|
|
441
|
-
if (phase === "rows") {
|
|
442
|
-
const markerIndex = output.indexOf(rowsMarker);
|
|
443
|
-
if (markerIndex < 0) {
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
const rows = parseArray(output.slice(0, markerIndex).trim(), "rows");
|
|
447
|
-
result = {
|
|
448
|
-
columns: authoritativeColumns,
|
|
449
|
-
rows
|
|
450
|
-
};
|
|
451
|
-
phase = "complete";
|
|
452
|
-
child.stdin.end("COMMIT;\n.quit\n");
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
child.stdout.setEncoding("utf8");
|
|
456
|
-
child.stderr.setEncoding("utf8");
|
|
457
|
-
child.stdout.on("data", (chunk) => {
|
|
458
|
-
try {
|
|
459
|
-
output = appendOutput(output, chunk, MAX_SQLITE_QUERY_OUTPUT_BYTES);
|
|
460
|
-
consumeOutput();
|
|
461
|
-
}
|
|
462
|
-
catch (error) {
|
|
463
|
-
stopWithError(error instanceof Error ? error : new Error(String(error)));
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
child.stderr.on("data", (chunk) => {
|
|
467
|
-
try {
|
|
468
|
-
stderr = appendOutput(stderr, chunk, MAX_SQLITE_ERROR_OUTPUT_BYTES);
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
stopWithError(error instanceof Error ? error : new Error(String(error)));
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
child.on("error", (error) => {
|
|
475
|
-
if (!terminalError) {
|
|
476
|
-
terminalError = new CodexSqliteSessionError({
|
|
477
|
-
message: error.message,
|
|
478
|
-
status: null,
|
|
479
|
-
stage: phase
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
child.stdin.on("error", (error) => {
|
|
484
|
-
if (!terminalError && phase !== "complete") {
|
|
485
|
-
terminalError = error;
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
child.on("close", (status) => {
|
|
489
|
-
clearTimeout(timeout);
|
|
490
|
-
if (settled) {
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
settled = true;
|
|
494
|
-
if (terminalError) {
|
|
495
|
-
reject(terminalError);
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (status !== 0) {
|
|
499
|
-
reject(new CodexSqliteSessionError({
|
|
500
|
-
message: stderr.trim() || `sqlite3 exited with status ${status ?? "unknown"}`,
|
|
501
|
-
status,
|
|
502
|
-
stage: phase
|
|
503
|
-
}));
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
if (!result || phase !== "complete") {
|
|
507
|
-
reject(new CodexSqliteSessionError({
|
|
508
|
-
message: "sqlite3 exited before the thread query protocol completed",
|
|
509
|
-
status,
|
|
510
|
-
stage: phase
|
|
511
|
-
}));
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
resolve(result);
|
|
515
|
-
});
|
|
516
|
-
child.stdin.write(`BEGIN;\npragma table_info(threads);\n` +
|
|
517
|
-
`select '${schemaControl}' as "${controlColumn}";\n`);
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
function validateCodexThreadQueryResult(result) {
|
|
521
|
-
validateCodexThreadColumns(result.columns);
|
|
522
|
-
return result.columns.includes("history_mode")
|
|
523
|
-
? result.rows.map((row) => ({ ...row, history_mode: row.history_mode ?? null }))
|
|
524
|
-
: result.rows;
|
|
525
|
-
}
|
|
526
|
-
function validateCodexThreadColumns(columns) {
|
|
527
|
-
if (!columns.includes("id") || !columns.includes("cwd")) {
|
|
528
|
-
throw new Error("Codex threads table is missing required id or cwd columns");
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function sqliteReadWriteUri(dbPath) {
|
|
532
|
-
const uri = pathToFileURL(path.resolve(dbPath));
|
|
533
|
-
uri.searchParams.set("mode", "rw");
|
|
534
|
-
return uri.href;
|
|
535
|
-
}
|
|
536
316
|
function waitForMilliseconds(milliseconds) {
|
|
537
317
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, milliseconds)));
|
|
538
318
|
}
|
|
539
|
-
function inspectCodexSqliteFiles(dbPath) {
|
|
540
|
-
return {
|
|
541
|
-
dbPath: path.resolve(dbPath),
|
|
542
|
-
main: inspectCodexSqliteFile(dbPath),
|
|
543
|
-
wal: inspectCodexSqliteFile(`${dbPath}-wal`),
|
|
544
|
-
shm: inspectCodexSqliteFile(`${dbPath}-shm`)
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
function inspectCodexSqliteFile(filePath) {
|
|
548
|
-
try {
|
|
549
|
-
const stat = fs.statSync(filePath);
|
|
550
|
-
return {
|
|
551
|
-
path: path.resolve(filePath),
|
|
552
|
-
exists: true,
|
|
553
|
-
kind: stat.isFile()
|
|
554
|
-
? "file"
|
|
555
|
-
: stat.isDirectory()
|
|
556
|
-
? "directory"
|
|
557
|
-
: "other",
|
|
558
|
-
device: String(stat.dev),
|
|
559
|
-
inode: String(stat.ino),
|
|
560
|
-
size: stat.size,
|
|
561
|
-
mtimeMs: stat.mtimeMs
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
catch (error) {
|
|
565
|
-
const code = typeof error === "object" && error !== null && "code" in error
|
|
566
|
-
? String(error.code)
|
|
567
|
-
: undefined;
|
|
568
|
-
return {
|
|
569
|
-
path: path.resolve(filePath),
|
|
570
|
-
exists: false,
|
|
571
|
-
...(code ? { errorCode: code } : {})
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
function assertStableCodexSqliteMain({ baseline, current, stage, selectedPath = current.dbPath }) {
|
|
576
|
-
const samePath = Boolean(selectedPath &&
|
|
577
|
-
path.resolve(selectedPath) === baseline.dbPath &&
|
|
578
|
-
current.dbPath === baseline.dbPath);
|
|
579
|
-
const sameFile = Boolean(baseline.main.exists &&
|
|
580
|
-
baseline.main.kind === "file" &&
|
|
581
|
-
current.main.exists &&
|
|
582
|
-
current.main.kind === "file" &&
|
|
583
|
-
baseline.main.device === current.main.device &&
|
|
584
|
-
baseline.main.inode === current.main.inode);
|
|
585
|
-
if (samePath && sameFile) {
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
throw new Error(`Codex SQLite main database changed during ${stage}; refusing stale ` +
|
|
589
|
-
`thread discovery (selected_db=${selectedPath ?? "missing"}, ` +
|
|
590
|
-
`baseline_db=${baseline.dbPath}, current_db=${current.dbPath}; ` +
|
|
591
|
-
`${formatCodexSqliteFiles(current)})`);
|
|
592
|
-
}
|
|
593
|
-
function codexSqliteQueryFailure(error, context) {
|
|
594
|
-
const errorRecord = typeof error === "object" && error !== null
|
|
595
|
-
? error
|
|
596
|
-
: undefined;
|
|
597
|
-
const reportedStatus = typeof errorRecord?.status === "number"
|
|
598
|
-
? errorRecord.status
|
|
599
|
-
: null;
|
|
600
|
-
const reportedStage = typeof errorRecord?.stage === "string"
|
|
601
|
-
? errorRecord.stage
|
|
602
|
-
: undefined;
|
|
603
|
-
return {
|
|
604
|
-
dbPath: path.resolve(context.dbPath),
|
|
605
|
-
stage: reportedStage
|
|
606
|
-
? `${context.stage}:${reportedStage}`
|
|
607
|
-
: context.stage,
|
|
608
|
-
status: reportedStatus !== null && Number.isInteger(reportedStatus)
|
|
609
|
-
? reportedStatus
|
|
610
|
-
: null,
|
|
611
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
612
|
-
files: context.files,
|
|
613
|
-
previousFailure: context.previousFailure
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
function isSqliteCantOpen(failure) {
|
|
617
|
-
return failure.status === 14 ||
|
|
618
|
-
/(?:SQLITE_CANTOPEN|unable to open database file|\(14\))/iu.test(failure.detail);
|
|
619
|
-
}
|
|
620
|
-
function codexSqliteQueryDiagnosticError(failure) {
|
|
621
|
-
const prior = failure.previousFailure
|
|
622
|
-
? `; previous=[stage=${failure.previousFailure.stage},status=` +
|
|
623
|
-
`${failure.previousFailure.status ?? "unknown"},` +
|
|
624
|
-
`${formatCodexSqliteFiles(failure.previousFailure.files)}]`
|
|
625
|
-
: "";
|
|
626
|
-
return new Error(`Codex SQLite thread query failed ` +
|
|
627
|
-
`(stage=${failure.stage}, db=${failure.dbPath}, ` +
|
|
628
|
-
`status=${failure.status ?? "unknown"}${prior}; ` +
|
|
629
|
-
`${formatCodexSqliteFiles(failure.files)}): ` +
|
|
630
|
-
failure.detail.replace(/\s+/gu, " ").trim());
|
|
631
|
-
}
|
|
632
|
-
function formatCodexSqliteFiles(snapshot) {
|
|
633
|
-
return [
|
|
634
|
-
["main", snapshot.main],
|
|
635
|
-
["wal", snapshot.wal],
|
|
636
|
-
["shm", snapshot.shm]
|
|
637
|
-
].map(([label, value]) => {
|
|
638
|
-
const file = value;
|
|
639
|
-
if (!file.exists) {
|
|
640
|
-
return `${label}=missing${file.errorCode ? `(${file.errorCode})` : ""}`;
|
|
641
|
-
}
|
|
642
|
-
return `${label}=${file.kind}(dev=${file.device},ino=${file.inode},` +
|
|
643
|
-
`size=${file.size},mtime_ms=${Math.trunc(file.mtimeMs ?? 0)})`;
|
|
644
|
-
}).join(" ");
|
|
645
|
-
}
|
|
646
|
-
export function latestStateDbPath(codexHome) {
|
|
647
|
-
if (!fs.existsSync(codexHome)) {
|
|
648
|
-
return undefined;
|
|
649
|
-
}
|
|
650
|
-
return fs.readdirSync(codexHome)
|
|
651
|
-
.filter((entry) => /^state_\d+\.sqlite$/u.test(entry))
|
|
652
|
-
.map((entry) => path.join(codexHome, entry))
|
|
653
|
-
.flatMap((filePath) => {
|
|
654
|
-
try {
|
|
655
|
-
const stat = fs.statSync(filePath);
|
|
656
|
-
return stat.isFile() ? [{ filePath, mtimeMs: stat.mtimeMs }] : [];
|
|
657
|
-
}
|
|
658
|
-
catch {
|
|
659
|
-
// Codex may rotate a versioned state database while discovery is
|
|
660
|
-
// enumerating it. The caller will re-resolve and validate identity.
|
|
661
|
-
return [];
|
|
662
|
-
}
|
|
663
|
-
})
|
|
664
|
-
.sort((left, right) => right.mtimeMs - left.mtimeMs)[0]?.filePath;
|
|
665
|
-
}
|
|
666
|
-
export function parseLsofOpenFiles(text) {
|
|
667
|
-
const records = [];
|
|
668
|
-
let current;
|
|
669
|
-
const flush = () => {
|
|
670
|
-
if (current) {
|
|
671
|
-
records.push(current);
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
for (const line of text.split(/\r?\n/u)) {
|
|
675
|
-
if (line.startsWith("f")) {
|
|
676
|
-
flush();
|
|
677
|
-
current = { fd: line.slice(1) };
|
|
678
|
-
}
|
|
679
|
-
else if (current && line.startsWith("t")) {
|
|
680
|
-
current.type = line.slice(1);
|
|
681
|
-
}
|
|
682
|
-
else if (current && line.startsWith("D")) {
|
|
683
|
-
current.device = line.slice(1);
|
|
684
|
-
}
|
|
685
|
-
else if (current && line.startsWith("i")) {
|
|
686
|
-
current.inode = line.slice(1);
|
|
687
|
-
}
|
|
688
|
-
else if (current && line.startsWith("n")) {
|
|
689
|
-
current.path = line.slice(1);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
flush();
|
|
693
|
-
return records;
|
|
694
|
-
}
|
|
695
|
-
export function resolveCodexOpenRolloutIdentity({ codexHome, pid, cwd, preferredSessionId, allowedCompanionIdentity, allowedAdditionalIdentities, processBirth, lsofOutput }) {
|
|
696
|
-
const inventory = inspectCodexOpenRootRolloutInventory({
|
|
697
|
-
codexHome,
|
|
698
|
-
pid,
|
|
699
|
-
cwd,
|
|
700
|
-
processBirth,
|
|
701
|
-
lsofOutput
|
|
702
|
-
});
|
|
703
|
-
return selectCodexOpenRolloutIdentity({
|
|
704
|
-
inventory,
|
|
705
|
-
preferredSessionId,
|
|
706
|
-
allowedCompanionIdentity,
|
|
707
|
-
allowedAdditionalIdentities
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
export function inspectCodexOpenRootRolloutInventory({ codexHome, pid, cwd, processBirth, lsofOutput }) {
|
|
711
|
-
if (!Number.isSafeInteger(pid) || pid <= 1 || !processBirth.trim()) {
|
|
712
|
-
throw new Error("Codex rollout inventory requires an exact process incarnation");
|
|
713
|
-
}
|
|
714
|
-
const rolloutFiles = parseLsofOpenFiles(lsofOutput).filter((openFile) => {
|
|
715
|
-
if (!openFile.path) {
|
|
716
|
-
return false;
|
|
717
|
-
}
|
|
718
|
-
const openPath = openFile.path.replace(/\s+\(deleted\)$/u, "");
|
|
719
|
-
return /^rollout-.*\.jsonl$/u.test(path.basename(openPath));
|
|
720
|
-
});
|
|
721
|
-
if (rolloutFiles.length > MAX_CODEX_OPEN_ROOT_ROLLOUTS) {
|
|
722
|
-
throw new Error(`Codex process ${pid} has too many open rollout files to inspect safely`);
|
|
723
|
-
}
|
|
724
|
-
const processUuid = `codex-pid:${pid}:birth:${processBirth}`;
|
|
725
|
-
const expectedCwd = cwd ? path.resolve(cwd) : undefined;
|
|
726
|
-
// An absent sessions directory is only evidence of a virgin process when
|
|
727
|
-
// lsof also reported no rollout descriptor at all. Once a rollout FD exists,
|
|
728
|
-
// every descriptor and every root/subagent classification must be verified.
|
|
729
|
-
if (rolloutFiles.length === 0) {
|
|
730
|
-
return codexOpenRootRolloutInventoryResult({
|
|
731
|
-
pid,
|
|
732
|
-
processUuid,
|
|
733
|
-
processBirth,
|
|
734
|
-
cwd: expectedCwd,
|
|
735
|
-
roots: []
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
const configuredSessionsRoot = path.join(codexHome, "sessions");
|
|
739
|
-
let sessionsRoot;
|
|
740
|
-
try {
|
|
741
|
-
sessionsRoot = fs.realpathSync(configuredSessionsRoot);
|
|
742
|
-
}
|
|
743
|
-
catch {
|
|
744
|
-
throw new Error(`Codex process ${pid} has open rollout files but CODEX_HOME/sessions is unavailable`);
|
|
745
|
-
}
|
|
746
|
-
const identities = [];
|
|
747
|
-
for (const openFile of rolloutFiles) {
|
|
748
|
-
const openPath = openFile.path;
|
|
749
|
-
const descriptorPath = openPath.replace(/\s+\(deleted\)$/u, "");
|
|
750
|
-
const lexicalRelative = path.relative(path.resolve(configuredSessionsRoot), path.resolve(descriptorPath));
|
|
751
|
-
if (!lexicalRelative ||
|
|
752
|
-
lexicalRelative.startsWith("..") ||
|
|
753
|
-
path.isAbsolute(lexicalRelative)) {
|
|
754
|
-
throw new Error(`Codex process ${pid} has an open rollout outside CODEX_HOME/sessions`);
|
|
755
|
-
}
|
|
756
|
-
if (openFile.type !== "REG" ||
|
|
757
|
-
!openFile.fd ||
|
|
758
|
-
!openFile.device ||
|
|
759
|
-
!openFile.inode ||
|
|
760
|
-
/\s+\(deleted\)$/u.test(openPath)) {
|
|
761
|
-
throw new Error(`Codex process ${pid} has an unverifiable open rollout descriptor`);
|
|
762
|
-
}
|
|
763
|
-
let realPath;
|
|
764
|
-
try {
|
|
765
|
-
realPath = fs.realpathSync(descriptorPath);
|
|
766
|
-
}
|
|
767
|
-
catch {
|
|
768
|
-
throw new Error(`Codex process ${pid} has an unreadable open rollout descriptor`);
|
|
769
|
-
}
|
|
770
|
-
const relative = path.relative(sessionsRoot, realPath);
|
|
771
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
772
|
-
throw new Error(`Codex process ${pid} has an open rollout outside CODEX_HOME/sessions`);
|
|
773
|
-
}
|
|
774
|
-
const expectedDevice = parseLsofInteger(openFile.device);
|
|
775
|
-
const expectedInode = parseLsofInteger(openFile.inode);
|
|
776
|
-
const metadata = readCodexSessionMetadata(descriptorPath, expectedDevice, expectedInode, pid);
|
|
777
|
-
let confirmedRealPath;
|
|
778
|
-
try {
|
|
779
|
-
confirmedRealPath = fs.realpathSync(descriptorPath);
|
|
780
|
-
}
|
|
781
|
-
catch {
|
|
782
|
-
throw new Error(`Codex process ${pid} has an unreadable open rollout descriptor`);
|
|
783
|
-
}
|
|
784
|
-
if (confirmedRealPath !== realPath) {
|
|
785
|
-
throw new Error(`Codex process ${pid} rollout path changed while it was being verified`);
|
|
786
|
-
}
|
|
787
|
-
const filenameSessionId = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/iu
|
|
788
|
-
.exec(path.basename(descriptorPath))?.[1];
|
|
789
|
-
if (!metadata || metadata.id !== filenameSessionId) {
|
|
790
|
-
throw new Error(`Codex process ${pid} has invalid rollout session metadata`);
|
|
791
|
-
}
|
|
792
|
-
if (typeof metadata.source === "object" &&
|
|
793
|
-
metadata.source !== null &&
|
|
794
|
-
"subagent" in metadata.source) {
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
if (metadata.originator !== "codex-tui" ||
|
|
798
|
-
metadata.source !== "cli" ||
|
|
799
|
-
(expectedCwd && path.resolve(metadata.cwd) !== expectedCwd)) {
|
|
800
|
-
throw new Error(`Codex process ${pid} has an indeterminate open root rollout`);
|
|
801
|
-
}
|
|
802
|
-
identities.push({
|
|
803
|
-
sessionId: metadata.id,
|
|
804
|
-
processUuid,
|
|
805
|
-
processBirth,
|
|
806
|
-
rollout: {
|
|
807
|
-
fd: openFile.fd,
|
|
808
|
-
device: openFile.device,
|
|
809
|
-
inode: openFile.inode,
|
|
810
|
-
path: confirmedRealPath
|
|
811
|
-
},
|
|
812
|
-
evidence: "codex_open_root_rollout"
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
if (identities.length === 0) {
|
|
816
|
-
throw new Error(`Codex process ${pid} has open rollout files but no exact TUI root identity`);
|
|
817
|
-
}
|
|
818
|
-
identities.sort((left, right) => left.sessionId.localeCompare(right.sessionId) ||
|
|
819
|
-
left.rollout.path.localeCompare(right.rollout.path) ||
|
|
820
|
-
left.rollout.fd.localeCompare(right.rollout.fd));
|
|
821
|
-
const duplicateDisposition = classifyCodexOpenRootDuplicates(identities);
|
|
822
|
-
if (duplicateDisposition === "transient_exact_descriptor_duplicate") {
|
|
823
|
-
throw new CodexTransientDuplicateOpenRootDescriptorsError(pid);
|
|
824
|
-
}
|
|
825
|
-
if (duplicateDisposition === "conflicting_identity") {
|
|
826
|
-
throw new Error(`Codex process ${pid} has duplicate open root rollout identities`);
|
|
827
|
-
}
|
|
828
|
-
return codexOpenRootRolloutInventoryResult({
|
|
829
|
-
pid,
|
|
830
|
-
processUuid,
|
|
831
|
-
processBirth,
|
|
832
|
-
cwd: expectedCwd,
|
|
833
|
-
roots: identities
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
function classifyCodexOpenRootDuplicates(identities) {
|
|
837
|
-
let exactDescriptorDuplicate = false;
|
|
838
|
-
for (let leftIndex = 0; leftIndex < identities.length; leftIndex += 1) {
|
|
839
|
-
const left = identities[leftIndex];
|
|
840
|
-
for (let rightIndex = leftIndex + 1; rightIndex < identities.length; rightIndex += 1) {
|
|
841
|
-
const right = identities[rightIndex];
|
|
842
|
-
const sameSession = left.sessionId === right.sessionId;
|
|
843
|
-
const sameFile = left.rollout.device === right.rollout.device &&
|
|
844
|
-
left.rollout.inode === right.rollout.inode;
|
|
845
|
-
if (!sameSession && !sameFile) {
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
const exactRolloutWithDistinctDescriptors = sameSession &&
|
|
849
|
-
sameFile &&
|
|
850
|
-
left.processUuid === right.processUuid &&
|
|
851
|
-
left.processBirth === right.processBirth &&
|
|
852
|
-
left.rollout.path === right.rollout.path &&
|
|
853
|
-
left.rollout.fd !== right.rollout.fd;
|
|
854
|
-
if (!exactRolloutWithDistinctDescriptors) {
|
|
855
|
-
return "conflicting_identity";
|
|
856
|
-
}
|
|
857
|
-
exactDescriptorDuplicate = true;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
return exactDescriptorDuplicate
|
|
861
|
-
? "transient_exact_descriptor_duplicate"
|
|
862
|
-
: "none";
|
|
863
|
-
}
|
|
864
|
-
function codexOpenRootRolloutInventoryResult({ pid, processUuid, processBirth, cwd, roots }) {
|
|
865
|
-
const authority = {
|
|
866
|
-
schema: "agent-knock-knock/codex-open-root-rollout-inventory",
|
|
867
|
-
version: 1,
|
|
868
|
-
pid,
|
|
869
|
-
processUuid,
|
|
870
|
-
processBirth,
|
|
871
|
-
...(cwd ? { cwd } : {}),
|
|
872
|
-
roots
|
|
873
|
-
};
|
|
874
|
-
const inventoryFingerprint = createHash("sha256")
|
|
875
|
-
.update(JSON.stringify(authority))
|
|
876
|
-
.digest("hex");
|
|
877
|
-
if (roots.length === 0) {
|
|
878
|
-
return {
|
|
879
|
-
...authority,
|
|
880
|
-
status: "verified_absent",
|
|
881
|
-
roots: [],
|
|
882
|
-
inventoryFingerprint
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
if (roots.length === 1) {
|
|
886
|
-
return {
|
|
887
|
-
...authority,
|
|
888
|
-
status: "resolved",
|
|
889
|
-
roots: [roots[0]],
|
|
890
|
-
inventoryFingerprint
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
...authority,
|
|
895
|
-
status: "unbound",
|
|
896
|
-
reason: "multiple_open_root_rollouts",
|
|
897
|
-
inventoryFingerprint
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
function selectCodexOpenRolloutIdentity({ inventory, preferredSessionId, allowedCompanionIdentity, allowedAdditionalIdentities }) {
|
|
901
|
-
const pid = inventory.pid;
|
|
902
|
-
const identities = inventory.roots;
|
|
903
|
-
if (identities.length === 0) {
|
|
904
|
-
return undefined;
|
|
905
|
-
}
|
|
906
|
-
if (preferredSessionId) {
|
|
907
|
-
const preferred = identities.filter((identity) => identity.sessionId === preferredSessionId);
|
|
908
|
-
if (preferred.length > 1) {
|
|
909
|
-
throw new Error(`Codex process ${pid} has multiple open root rollouts for preferred ` +
|
|
910
|
-
`session ${preferredSessionId}`);
|
|
911
|
-
}
|
|
912
|
-
if (allowedCompanionIdentity) {
|
|
913
|
-
const allowedConstraints = [
|
|
914
|
-
allowedCompanionIdentity,
|
|
915
|
-
...(allowedAdditionalIdentities ?? [])
|
|
916
|
-
];
|
|
917
|
-
const allowedMatches = [];
|
|
918
|
-
for (const constraint of allowedConstraints) {
|
|
919
|
-
const matches = identities.filter((identity) => sameActiveCodexIdentity(identity, constraint));
|
|
920
|
-
if (matches.length > 1) {
|
|
921
|
-
throw new Error(`Codex process ${pid} has multiple open root rollouts for an ` +
|
|
922
|
-
"allowed companion session");
|
|
923
|
-
}
|
|
924
|
-
if (matches[0] && !allowedMatches.includes(matches[0])) {
|
|
925
|
-
allowedMatches.push(matches[0]);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
const unexpected = identities.filter((identity) => identity !== preferred[0] && !allowedMatches.includes(identity));
|
|
929
|
-
if (unexpected.length > 0) {
|
|
930
|
-
throw new Error(`Codex process ${pid} has an unexpected open root rollout outside ` +
|
|
931
|
-
"the preferred and exact companion identities");
|
|
932
|
-
}
|
|
933
|
-
if (preferred.length === 1) {
|
|
934
|
-
return preferred[0];
|
|
935
|
-
}
|
|
936
|
-
const primaryCompanion = identities.find((identity) => sameActiveCodexIdentity(identity, allowedCompanionIdentity));
|
|
937
|
-
if (primaryCompanion) {
|
|
938
|
-
return primaryCompanion;
|
|
939
|
-
}
|
|
940
|
-
if (allowedMatches.length > 0) {
|
|
941
|
-
// The immediately preceding rollout can close while an older,
|
|
942
|
-
// independently verified managed ancestor remains open. Constraint
|
|
943
|
-
// order is authoritative and deterministic; unknown roots were
|
|
944
|
-
// rejected above, so the first surviving exact companion is safe
|
|
945
|
-
// process-incarnation evidence for a fresh status-card proof.
|
|
946
|
-
return allowedMatches[0];
|
|
947
|
-
}
|
|
948
|
-
throw new Error(`Codex process ${pid} has neither the preferred session nor an ` +
|
|
949
|
-
"exact managed companion rollout open");
|
|
950
|
-
}
|
|
951
|
-
else if (preferred.length === 1 && identities.length === 1) {
|
|
952
|
-
return preferred[0];
|
|
953
|
-
}
|
|
954
|
-
else {
|
|
955
|
-
throw new Error(`Codex process ${pid} does not have the preferred session as its ` +
|
|
956
|
-
"sole open root rollout");
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
if (identities.length !== 1) {
|
|
960
|
-
throw new Error(`Codex process ${pid} has ${identities.length} open root rollout files; ` +
|
|
961
|
-
"the foreground native session is ambiguous");
|
|
962
|
-
}
|
|
963
|
-
return identities[0];
|
|
964
|
-
}
|
|
965
|
-
function sameActiveCodexIdentity(left, right) {
|
|
966
|
-
return Boolean(left.sessionId === right.sessionId &&
|
|
967
|
-
left.processUuid === right.processUuid &&
|
|
968
|
-
left.processBirth === right.processBirth &&
|
|
969
|
-
left.rollout?.fd === right.rollout?.fd &&
|
|
970
|
-
left.rollout?.device === right.rollout?.device &&
|
|
971
|
-
left.rollout?.inode === right.rollout?.inode &&
|
|
972
|
-
left.rollout?.path === right.rollout?.path);
|
|
973
|
-
}
|
|
974
|
-
function parseLsofInteger(value) {
|
|
975
|
-
try {
|
|
976
|
-
return BigInt(value);
|
|
977
|
-
}
|
|
978
|
-
catch {
|
|
979
|
-
return -1n;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
function readCodexSessionMetadata(filePath, expectedDevice, expectedInode, pid) {
|
|
983
|
-
let fd;
|
|
984
|
-
try {
|
|
985
|
-
fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
986
|
-
}
|
|
987
|
-
catch {
|
|
988
|
-
throw new Error(`Codex process ${pid} has an unreadable open rollout descriptor`);
|
|
989
|
-
}
|
|
990
|
-
try {
|
|
991
|
-
const stat = fs.fstatSync(fd, { bigint: true });
|
|
992
|
-
if (!stat.isFile() ||
|
|
993
|
-
stat.dev !== expectedDevice ||
|
|
994
|
-
stat.ino !== expectedInode) {
|
|
995
|
-
throw new Error(`Codex process ${pid} rollout descriptor no longer matches its file`);
|
|
996
|
-
}
|
|
997
|
-
const buffer = Buffer.alloc(1024 * 1024);
|
|
998
|
-
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
999
|
-
const text = buffer.subarray(0, bytesRead).toString("utf8");
|
|
1000
|
-
const newline = text.indexOf("\n");
|
|
1001
|
-
if (newline < 0 && bytesRead === buffer.length) {
|
|
1002
|
-
throw new Error(`Codex session metadata line is too large: ${filePath}`);
|
|
1003
|
-
}
|
|
1004
|
-
const parsed = JSON.parse(newline >= 0 ? text.slice(0, newline) : text);
|
|
1005
|
-
const payload = parsed?.type === "session_meta" ? parsed.payload : undefined;
|
|
1006
|
-
if (typeof payload?.id !== "string" ||
|
|
1007
|
-
typeof payload?.cwd !== "string" ||
|
|
1008
|
-
typeof payload?.originator !== "string" ||
|
|
1009
|
-
!codexThreadUsesLegacyRollout(payload)) {
|
|
1010
|
-
return undefined;
|
|
1011
|
-
}
|
|
1012
|
-
return {
|
|
1013
|
-
id: payload.id,
|
|
1014
|
-
cwd: payload.cwd,
|
|
1015
|
-
originator: payload.originator,
|
|
1016
|
-
source: payload.source
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
finally {
|
|
1020
|
-
fs.closeSync(fd);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
export function buildThreadSelect(columns, limit, filters = {}) {
|
|
1024
|
-
const columnSet = new Set(columns);
|
|
1025
|
-
const updatedAtExpression = columnSet.has("updated_at_ms")
|
|
1026
|
-
? "updated_at_ms"
|
|
1027
|
-
: columnSet.has("updated_at")
|
|
1028
|
-
? "updated_at * 1000"
|
|
1029
|
-
: "0";
|
|
1030
|
-
const select = [
|
|
1031
|
-
"id",
|
|
1032
|
-
"cwd",
|
|
1033
|
-
columnSet.has("rollout_path") ? "rollout_path" : "null as rollout_path",
|
|
1034
|
-
columnSet.has("title") ? "title" : "null as title",
|
|
1035
|
-
columnSet.has("preview") ? "preview" : "null as preview",
|
|
1036
|
-
columnSet.has("first_user_message") ? "first_user_message" : "null as first_user_message",
|
|
1037
|
-
columnSet.has("updated_at_ms") ? "updated_at_ms" : columnSet.has("updated_at") ? "updated_at * 1000 as updated_at_ms" : "null as updated_at_ms",
|
|
1038
|
-
columnSet.has("archived") ? "archived" : "0 as archived",
|
|
1039
|
-
columnSet.has("source") ? "source" : "null as source",
|
|
1040
|
-
columnSet.has("model_provider") ? "model_provider" : "null as model_provider",
|
|
1041
|
-
columnSet.has("cli_version") ? "cli_version" : "null as cli_version",
|
|
1042
|
-
columnSet.has("name") ? "name" : "null as name",
|
|
1043
|
-
columnSet.has("history_mode") ? "history_mode" : "'legacy' as history_mode"
|
|
1044
|
-
].join(", ");
|
|
1045
|
-
const predicates = [];
|
|
1046
|
-
if (filters.cwd !== undefined) {
|
|
1047
|
-
predicates.push("cwd collate binary = :akk_cwd");
|
|
1048
|
-
}
|
|
1049
|
-
if (filters.source !== undefined) {
|
|
1050
|
-
predicates.push(columnSet.has("source")
|
|
1051
|
-
? "source collate binary = :akk_source"
|
|
1052
|
-
: "0 = 1");
|
|
1053
|
-
}
|
|
1054
|
-
if (filters.archived !== undefined) {
|
|
1055
|
-
if (columnSet.has("archived")) {
|
|
1056
|
-
predicates.push(`archived = ${filters.archived ? "1" : "0"}`);
|
|
1057
|
-
}
|
|
1058
|
-
else if (filters.archived) {
|
|
1059
|
-
predicates.push("0 = 1");
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
if (filters.modelProvider !== undefined) {
|
|
1063
|
-
predicates.push(columnSet.has("model_provider")
|
|
1064
|
-
? "model_provider collate binary = :akk_model_provider"
|
|
1065
|
-
: "0 = 1");
|
|
1066
|
-
}
|
|
1067
|
-
if (filters.historyMode !== undefined) {
|
|
1068
|
-
if (columnSet.has("history_mode")) {
|
|
1069
|
-
predicates.push("history_mode collate binary = :akk_history_mode");
|
|
1070
|
-
}
|
|
1071
|
-
else if (filters.historyMode !== "legacy") {
|
|
1072
|
-
predicates.push("0 = 1");
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
const where = predicates.length > 0
|
|
1076
|
-
? ` where ${predicates.join(" and ")}`
|
|
1077
|
-
: "";
|
|
1078
|
-
const deterministicTieBreak = predicates.length > 0 ? ", id desc" : "";
|
|
1079
|
-
return `select ${select} from threads${where} order by ${updatedAtExpression} desc${deterministicTieBreak} limit ${Math.max(1, Math.floor(limit))}`;
|
|
1080
|
-
}
|
|
1081
|
-
function sqliteThreadFilterParameterCommands(filters) {
|
|
1082
|
-
if (!filters) {
|
|
1083
|
-
return [];
|
|
1084
|
-
}
|
|
1085
|
-
const values = [
|
|
1086
|
-
["akk_cwd", filters.cwd],
|
|
1087
|
-
["akk_source", filters.source],
|
|
1088
|
-
["akk_model_provider", filters.modelProvider],
|
|
1089
|
-
["akk_history_mode", filters.historyMode]
|
|
1090
|
-
];
|
|
1091
|
-
const present = values.filter((entry) => entry[1] !== undefined);
|
|
1092
|
-
if (present.length === 0) {
|
|
1093
|
-
return [];
|
|
1094
|
-
}
|
|
1095
|
-
return [
|
|
1096
|
-
".parameter init",
|
|
1097
|
-
...present.map(([name, value]) => {
|
|
1098
|
-
const hex = Buffer.from(value, "utf8").toString("hex");
|
|
1099
|
-
return `.parameter set :${name} "CAST(X'${hex}' AS TEXT)"`;
|
|
1100
|
-
})
|
|
1101
|
-
];
|
|
1102
|
-
}
|
|
1103
|
-
export function buildThreadByIdSelect(columns, nativeThreadId) {
|
|
1104
|
-
if (!NATIVE_THREAD_ID_PATTERN.test(nativeThreadId)) {
|
|
1105
|
-
throw new Error("Codex thread lookup requires an exact UUID");
|
|
1106
|
-
}
|
|
1107
|
-
const base = buildThreadSelect(columns, 1);
|
|
1108
|
-
return base.replace(" from threads order by ", ` from threads where id = '${nativeThreadId.toLowerCase()}' order by `);
|
|
1109
|
-
}
|
|
1110
319
|
function assertCodexLifecycleCandidateRequest(request) {
|
|
1111
320
|
if (!isValidCodexAgentVersion(request.agentVersion)) {
|
|
1112
321
|
throw new Error("Codex lifecycle candidate discovery requires a complete x.y.z agent version");
|