@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.
Files changed (168) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/src/claude-terminal-agent-adapter.js +1 -1
  3. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  4. package/dist/src/codex-app-server-interaction-client.js +68 -26
  5. package/dist/src/codex-app-server-interaction-client.js.map +1 -1
  6. package/dist/src/codex-app-server-read-client.js +52 -20
  7. package/dist/src/codex-app-server-read-client.js.map +1 -1
  8. package/dist/src/codex-astra-composer-proof.js +1 -1
  9. package/dist/src/codex-astra-composer-proof.js.map +1 -1
  10. package/dist/src/codex-open-rollout-inventory.d.ts +51 -0
  11. package/dist/src/codex-open-rollout-inventory.js +365 -0
  12. package/dist/src/codex-open-rollout-inventory.js.map +1 -0
  13. package/dist/src/codex-paginated-thread-binding.js +1 -1
  14. package/dist/src/codex-paginated-thread-binding.js.map +1 -1
  15. package/dist/src/codex-rollout-bound-completion.d.ts +44 -0
  16. package/dist/src/codex-rollout-bound-completion.js +357 -0
  17. package/dist/src/codex-rollout-bound-completion.js.map +1 -0
  18. package/dist/src/codex-rollout-file.d.ts +15 -0
  19. package/dist/src/codex-rollout-file.js +98 -0
  20. package/dist/src/codex-rollout-file.js.map +1 -0
  21. package/dist/src/codex-rollout-human-task.d.ts +61 -0
  22. package/dist/src/codex-rollout-human-task.js +555 -0
  23. package/dist/src/codex-rollout-human-task.js.map +1 -0
  24. package/dist/src/codex-rollout-interaction-evidence.d.ts +48 -0
  25. package/dist/src/codex-rollout-interaction-evidence.js +521 -0
  26. package/dist/src/codex-rollout-interaction-evidence.js.map +1 -0
  27. package/dist/src/codex-rollout-inventory.d.ts +5 -0
  28. package/dist/src/codex-rollout-inventory.js +81 -0
  29. package/dist/src/codex-rollout-inventory.js.map +1 -0
  30. package/dist/src/codex-rollout-records.d.ts +49 -0
  31. package/dist/src/codex-rollout-records.js +298 -0
  32. package/dist/src/codex-rollout-records.js.map +1 -0
  33. package/dist/src/codex-sqlite-thread-query.d.ts +85 -0
  34. package/dist/src/codex-sqlite-thread-query.js +440 -0
  35. package/dist/src/codex-sqlite-thread-query.js.map +1 -0
  36. package/dist/src/codex-status-history-navigation.d.ts +1 -1
  37. package/dist/src/codex-status-history-navigation.js +1 -1
  38. package/dist/src/codex-status-history-navigation.js.map +1 -1
  39. package/dist/src/codex-store-adapter.d.ts +3 -50
  40. package/dist/src/codex-store-adapter.js +5 -796
  41. package/dist/src/codex-store-adapter.js.map +1 -1
  42. package/dist/src/codex-terminal-agent-adapter.js +1 -1
  43. package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
  44. package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +4 -134
  45. package/dist/src/native-thread-lifecycle-cli-adapter.js +19 -633
  46. package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
  47. package/dist/src/native-thread-lifecycle-cli-contract.d.ts +138 -0
  48. package/dist/src/native-thread-lifecycle-cli-contract.js +2 -0
  49. package/dist/src/native-thread-lifecycle-cli-contract.js.map +1 -0
  50. package/dist/src/semantic-permission-control.js +33 -6
  51. package/dist/src/semantic-permission-control.js.map +1 -1
  52. package/dist/src/semantic-tool-descriptions.d.ts +26 -0
  53. package/dist/src/semantic-tool-descriptions.js +259 -0
  54. package/dist/src/semantic-tool-descriptions.js.map +1 -0
  55. package/dist/src/semantic-tool-runtime.js +45 -37
  56. package/dist/src/semantic-tool-runtime.js.map +1 -1
  57. package/dist/src/semantic-tool-schemas.js +134 -33
  58. package/dist/src/semantic-tool-schemas.js.map +1 -1
  59. package/dist/src/terminal-action-contracts.d.ts +4 -0
  60. package/dist/src/terminal-action-contracts.js +695 -0
  61. package/dist/src/terminal-action-contracts.js.map +1 -0
  62. package/dist/src/terminal-composer-classifier.d.ts +1 -1
  63. package/dist/src/terminal-composer-classifier.js +2 -2
  64. package/dist/src/terminal-composer-classifier.js.map +1 -1
  65. package/dist/src/terminal-interaction-response-bridge.js +1 -1
  66. package/dist/src/terminal-interaction-response-bridge.js.map +1 -1
  67. package/dist/src/terminal-list-renderer.d.ts +1 -2
  68. package/dist/src/terminal-list-renderer.js +8 -424
  69. package/dist/src/terminal-list-renderer.js.map +1 -1
  70. package/dist/src/terminal-model-control-action-policy.d.ts +1 -1
  71. package/dist/src/terminal-model-control-availability.d.ts +1 -1
  72. package/dist/src/terminal-model-control-availability.js +1 -1
  73. package/dist/src/terminal-model-control-availability.js.map +1 -1
  74. package/dist/src/terminal-model-control-cli-adapter.d.ts +34 -0
  75. package/dist/src/terminal-model-control-cli-adapter.js +669 -0
  76. package/dist/src/terminal-model-control-cli-adapter.js.map +1 -0
  77. package/dist/src/terminal-model-control-contract.d.ts +198 -0
  78. package/dist/src/terminal-model-control-contract.js +15 -0
  79. package/dist/src/terminal-model-control-contract.js.map +1 -0
  80. package/dist/src/terminal-model-control-input.d.ts +29 -0
  81. package/dist/src/terminal-model-control-input.js +86 -0
  82. package/dist/src/terminal-model-control-input.js.map +1 -0
  83. package/dist/src/terminal-model-control-profile.d.ts +2 -0
  84. package/dist/src/terminal-model-control-profile.js +6 -0
  85. package/dist/src/terminal-model-control-profile.js.map +1 -1
  86. package/dist/src/terminal-model-control-subject.d.ts +52 -0
  87. package/dist/src/terminal-model-control-subject.js +73 -0
  88. package/dist/src/terminal-model-control-subject.js.map +1 -1
  89. package/dist/src/terminal-model-control-surface.d.ts +82 -0
  90. package/dist/src/terminal-model-control-surface.js +688 -0
  91. package/dist/src/terminal-model-control-surface.js.map +1 -0
  92. package/dist/src/terminal-model-control.d.ts +6 -270
  93. package/dist/src/terminal-model-control.js +11 -988
  94. package/dist/src/terminal-model-control.js.map +1 -1
  95. package/dist/src/terminal-model-selection.d.ts +33 -0
  96. package/dist/src/terminal-model-selection.js +143 -0
  97. package/dist/src/terminal-model-selection.js.map +1 -0
  98. package/dist/src/terminal-monitor-application-service.js +38 -59
  99. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  100. package/dist/src/terminal-monitor-attention-store.d.ts +65 -0
  101. package/dist/src/terminal-monitor-attention-store.js +518 -0
  102. package/dist/src/terminal-monitor-attention-store.js.map +1 -0
  103. package/dist/src/terminal-monitor-collateral-recovery.d.ts +12 -0
  104. package/dist/src/terminal-monitor-collateral-recovery.js +415 -0
  105. package/dist/src/terminal-monitor-collateral-recovery.js.map +1 -0
  106. package/dist/src/terminal-monitor-interaction-projection.d.ts +19 -0
  107. package/dist/src/terminal-monitor-interaction-projection.js +48 -0
  108. package/dist/src/terminal-monitor-interaction-projection.js.map +1 -0
  109. package/dist/src/terminal-monitor-startup-recovery.d.ts +15 -0
  110. package/dist/src/terminal-monitor-startup-recovery.js +767 -0
  111. package/dist/src/terminal-monitor-startup-recovery.js.map +1 -0
  112. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +2 -104
  113. package/dist/src/terminal-monitor-state-cli-adapter.js +28 -1640
  114. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  115. package/dist/src/terminal-monitor-state-contract.d.ts +108 -0
  116. package/dist/src/terminal-monitor-state-contract.js +8 -0
  117. package/dist/src/terminal-monitor-state-contract.js.map +1 -0
  118. package/dist/src/terminal-native-inspection-bridge.d.ts +4 -153
  119. package/dist/src/terminal-native-inspection-bridge.js +7 -646
  120. package/dist/src/terminal-native-inspection-bridge.js.map +1 -1
  121. package/dist/src/terminal-native-inspection-contract.d.ts +128 -0
  122. package/dist/src/terminal-native-inspection-contract.js +38 -0
  123. package/dist/src/terminal-native-inspection-contract.js.map +1 -0
  124. package/dist/src/terminal-native-inspection-profile.d.ts +15 -0
  125. package/dist/src/terminal-native-inspection-profile.js +185 -0
  126. package/dist/src/terminal-native-inspection-profile.js.map +1 -0
  127. package/dist/src/terminal-native-inspection-proof.d.ts +62 -0
  128. package/dist/src/terminal-native-inspection-proof.js +362 -0
  129. package/dist/src/terminal-native-inspection-proof.js.map +1 -0
  130. package/dist/src/terminal-permission-control-bridge.js +1 -1
  131. package/dist/src/terminal-permission-control-bridge.js.map +1 -1
  132. package/dist/src/terminal-runtime-cli-adapter.d.ts +2 -1
  133. package/dist/src/terminal-runtime-cli-adapter.js +1 -1
  134. package/dist/src/terminal-runtime-cli-adapter.js.map +1 -1
  135. package/dist/src/terminal-submission-acceptance.d.ts +5 -148
  136. package/dist/src/terminal-submission-acceptance.js +21 -1845
  137. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  138. package/dist/src/terminal-watch-anchor-capture.d.ts +9 -0
  139. package/dist/src/terminal-watch-anchor-capture.js +139 -0
  140. package/dist/src/terminal-watch-anchor-capture.js.map +1 -0
  141. package/dist/src/terminal-watch-cli-adapter.d.ts +3 -117
  142. package/dist/src/terminal-watch-cli-adapter.js +10 -1992
  143. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  144. package/dist/src/terminal-watch-cli-contract.d.ts +117 -0
  145. package/dist/src/terminal-watch-cli-contract.js +2 -0
  146. package/dist/src/terminal-watch-cli-contract.js.map +1 -0
  147. package/dist/src/terminal-watch-fallback-observation.d.ts +15 -0
  148. package/dist/src/terminal-watch-fallback-observation.js +249 -0
  149. package/dist/src/terminal-watch-fallback-observation.js.map +1 -0
  150. package/dist/src/terminal-watch-interaction-observation.d.ts +26 -0
  151. package/dist/src/terminal-watch-interaction-observation.js +262 -0
  152. package/dist/src/terminal-watch-interaction-observation.js.map +1 -0
  153. package/dist/src/terminal-watch-observation-adapter.d.ts +4 -0
  154. package/dist/src/terminal-watch-observation-adapter.js +307 -0
  155. package/dist/src/terminal-watch-observation-adapter.js.map +1 -0
  156. package/dist/src/terminal-watch-observation-evidence.d.ts +25 -0
  157. package/dist/src/terminal-watch-observation-evidence.js +184 -0
  158. package/dist/src/terminal-watch-observation-evidence.js.map +1 -0
  159. package/dist/src/terminal-watch-response-authority.d.ts +6 -0
  160. package/dist/src/terminal-watch-response-authority.js +333 -0
  161. package/dist/src/terminal-watch-response-authority.js.map +1 -0
  162. package/dist/src/terminal-watch-response-cli-adapter.d.ts +3 -0
  163. package/dist/src/terminal-watch-response-cli-adapter.js +230 -0
  164. package/dist/src/terminal-watch-response-cli-adapter.js.map +1 -0
  165. package/dist/src/terminal-watch-terminal-identity.d.ts +39 -0
  166. package/dist/src/terminal-watch-terminal-identity.js +380 -0
  167. package/dist/src/terminal-watch-terminal-identity.js.map +1 -0
  168. package/package.json +1 -1
@@ -1,21 +1,17 @@
1
- import { spawn } from "node:child_process";
2
- import { createHash, randomUUID } from "node:crypto";
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");