@someonegg/codex-hub 0.0.0-stage → 0.1.0

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -2
  3. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/dist/client/assets/index-Dx1LBBmn.js +304 -0
  63. package/dist/client/assets/index-lmwuA3r2.css +1 -0
  64. package/dist/client/index.html +16 -0
  65. package/dist/server/server/adapters/codex/agent-identity.js +9 -0
  66. package/dist/server/server/adapters/codex/catalog-source.js +2 -0
  67. package/dist/server/server/adapters/codex/codex-session-source.js +300 -0
  68. package/dist/server/server/adapters/codex/identity-resolver.js +107 -0
  69. package/dist/server/server/adapters/codex/interaction-parser.js +90 -0
  70. package/dist/server/server/adapters/codex/internal-event-parser.js +56 -0
  71. package/dist/server/server/adapters/codex/io-errors.js +5 -0
  72. package/dist/server/server/adapters/codex/jsonl-catalog-source.js +58 -0
  73. package/dist/server/server/adapters/codex/limits.js +10 -0
  74. package/dist/server/server/adapters/codex/message-normalizer.js +121 -0
  75. package/dist/server/server/adapters/codex/path-policy.js +88 -0
  76. package/dist/server/server/adapters/codex/project-directory-resolver.js +79 -0
  77. package/dist/server/server/adapters/codex/response-item-parser.js +100 -0
  78. package/dist/server/server/adapters/codex/rollout-decoder.js +286 -0
  79. package/dist/server/server/adapters/codex/session-index-reader.js +101 -0
  80. package/dist/server/server/adapters/codex/session-manager.js +62 -0
  81. package/dist/server/server/adapters/codex/session-normalizer.js +257 -0
  82. package/dist/server/server/adapters/codex/tool-normalizer.js +58 -0
  83. package/dist/server/server/adapters/codex/user-input-normalizer.js +123 -0
  84. package/dist/server/server/api/session-api-mapper.js +194 -0
  85. package/dist/server/server/application/session-read-service.js +109 -0
  86. package/dist/server/server/application/session-reader.js +2 -0
  87. package/dist/server/server/catalog/config.js +4 -0
  88. package/dist/server/server/catalog/session-catalog-service.js +103 -0
  89. package/dist/server/server/config.js +145 -0
  90. package/dist/server/server/create-hub.js +64 -0
  91. package/dist/server/server/domain/project-directory.js +5 -0
  92. package/dist/server/server/domain/session-domain.js +2 -0
  93. package/dist/server/server/domain/session-text.js +45 -0
  94. package/dist/server/server/http/api-route-handler.js +2 -0
  95. package/dist/server/server/http/api-router.js +63 -0
  96. package/dist/server/server/http/create-server.js +148 -0
  97. package/dist/server/server/http/query-params.js +19 -0
  98. package/dist/server/server/http/request-body.js +48 -0
  99. package/dist/server/server/http/router.js +16 -0
  100. package/dist/server/server/http/security.js +19 -0
  101. package/dist/server/server/http/session-interaction-routes.js +101 -0
  102. package/dist/server/server/http/session-management-routes.js +90 -0
  103. package/dist/server/server/http/session-path.js +13 -0
  104. package/dist/server/server/http/session-read-routes.js +198 -0
  105. package/dist/server/server/http/session-updates-routes.js +84 -0
  106. package/dist/server/server/http/terminal-preview-websocket.js +179 -0
  107. package/dist/server/server/http/upload-routes.js +74 -0
  108. package/dist/server/server/interaction/interaction-service.js +247 -0
  109. package/dist/server/server/interaction/message.js +19 -0
  110. package/dist/server/server/interaction/pane-binding-manager.js +60 -0
  111. package/dist/server/server/interaction/reconcile-session-state.js +21 -0
  112. package/dist/server/server/interaction/session-runtime.js +86 -0
  113. package/dist/server/server/interaction/terminal-preview-diff.js +71 -0
  114. package/dist/server/server/interaction/terminal-preview-service.js +194 -0
  115. package/dist/server/server/interaction/tmux-launcher.js +75 -0
  116. package/dist/server/server/interaction/tmux-preview-listener.js +101 -0
  117. package/dist/server/server/interaction/tmux-service.js +269 -0
  118. package/dist/server/server/interaction/transport.js +9 -0
  119. package/dist/server/server/main.js +41 -0
  120. package/dist/server/server/management/operation-service.js +540 -0
  121. package/dist/server/server/management/session-exit-service.js +50 -0
  122. package/dist/server/server/management/session-manager.js +59 -0
  123. package/dist/server/server/repository/catalog-snapshot-store.js +304 -0
  124. package/dist/server/server/repository/diagnostic-logger.js +70 -0
  125. package/dist/server/server/repository/opaque-cursor.js +79 -0
  126. package/dist/server/server/repository/refresh-coordinator.js +14 -0
  127. package/dist/server/server/repository/session-queries.js +151 -0
  128. package/dist/server/server/repository/session-query-criteria.js +2 -0
  129. package/dist/server/server/repository/timeline-prefix-index.js +342 -0
  130. package/dist/server/server/repository/timeline-prefix-registry.js +55 -0
  131. package/dist/server/server/security/opaque-id.js +13 -0
  132. package/dist/server/server/source/session-source.js +2 -0
  133. package/dist/server/server/updates/session-updates-service.js +147 -0
  134. package/dist/server/server/updates/update-revision.js +25 -0
  135. package/dist/server/shared/api-contract.js +2 -0
  136. package/dist/server/shared/domain.js +2 -0
  137. package/dist/server/shared/interaction.js +12 -0
  138. package/dist/server/shared/uploads.js +2 -0
  139. package/package.json +79 -4
@@ -0,0 +1,90 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { validProjectDirectory } from "../../domain/project-directory.js";
3
+ import { isObject } from "./rollout-decoder.js";
4
+ export const CODEX_INTERACTION_KEY_BINDINGS = {
5
+ enter: "Enter",
6
+ up: "Up",
7
+ down: "Down",
8
+ left: "Left",
9
+ right: "Right",
10
+ interrupt: "C-c",
11
+ plan: "BTab",
12
+ };
13
+ export function codexInteraction(projectDirectory) {
14
+ return {
15
+ transport: "tmux",
16
+ bindingCommand: codexBindingCommand(undefined, projectDirectory),
17
+ keyBindings: CODEX_INTERACTION_KEY_BINDINGS,
18
+ };
19
+ }
20
+ export function codexBindingAttemptsFrom(record) {
21
+ if (record.value.type !== "response_item")
22
+ return [];
23
+ const metadata = record.value.metadata;
24
+ if (isObject(metadata) && metadata.inherited_user_message === true)
25
+ return [];
26
+ const payload = record.value.payload;
27
+ if (!isObject(payload) || payload.type !== "message" || payload.role !== "user") {
28
+ return [];
29
+ }
30
+ const text = contentText(payload.content);
31
+ if (text === null || !text.includes("<user_shell_command>"))
32
+ return [];
33
+ const shellBlocks = [...text.matchAll(/<user_shell_command>([\s\S]*?)<\/user_shell_command>/g)];
34
+ const found = [];
35
+ for (const block of shellBlocks) {
36
+ const lines = (block[1] ?? "").split("\n").map((line) => line.replace(/\r$/, ""));
37
+ for (let index = 0; index < lines.length; index += 1) {
38
+ if (!["CODEX_HUB_TMUX_BIND_V3", "CODEX_HUB_TMUX_BIND_V4"].includes(lines[index]))
39
+ continue;
40
+ const project = lines[index] === "CODEX_HUB_TMUX_BIND_V4" ? lines[index + 4] : undefined;
41
+ const tmux = lines[index + 1] ?? "";
42
+ const paneId = lines[index + 2] ?? "";
43
+ const operationId = lines[index + 3] ?? "";
44
+ found.push(isAbsolute(tmux) && /^%\d+$/.test(paneId) && (!operationId || validOperationId(operationId))
45
+ ? {
46
+ ordinal: record.ordinal,
47
+ valid: true,
48
+ socketPath: tmux,
49
+ paneId,
50
+ ...(operationId ? { operationId } : {}),
51
+ ...(validProjectDirectory(project) ? { projectDirectory: project } : {}),
52
+ }
53
+ : { ordinal: record.ordinal, valid: false });
54
+ }
55
+ }
56
+ const lastOpen = text.lastIndexOf("<user_shell_command>");
57
+ const lastClose = text.lastIndexOf("</user_shell_command>");
58
+ if (lastOpen > lastClose) {
59
+ const incomplete = text.slice(lastOpen + "<user_shell_command>".length);
60
+ const incompleteLines = incomplete.replace(/\r\n?/g, "\n").split("\n");
61
+ for (const line of incompleteLines) {
62
+ if (/^CODEX_HUB_TMUX_BIND_V[34]$/.test(line))
63
+ found.push({ ordinal: record.ordinal, valid: false });
64
+ }
65
+ }
66
+ return found;
67
+ }
68
+ function contentText(value) {
69
+ if (!Array.isArray(value))
70
+ return null;
71
+ const parts = [];
72
+ for (const part of value) {
73
+ if (!isObject(part) || typeof part.text !== "string")
74
+ continue;
75
+ if (part.type === "input_text" || part.type === "text")
76
+ parts.push(part.text);
77
+ }
78
+ return parts.length === 0 ? null : parts.join("\n\n");
79
+ }
80
+ export function codexBindingCommand(operationId, projectDirectory) {
81
+ if (operationId !== undefined && !validOperationId(operationId))
82
+ throw new Error("Invalid operation ID");
83
+ const quote = (value) => "'" + value.replaceAll("'", "'\"'\"'") + "'";
84
+ const project = validProjectDirectory(projectDirectory) ? projectDirectory : "";
85
+ return `! printf 'CODEX_HUB_TMUX_BIND_V4\\n%s\\n%s\\n%s\\n%s\\n' "$(tmux display-message -p '#{socket_path}')" "$TMUX_PANE" ${quote(operationId ?? "")} ${quote(project)}`;
86
+ }
87
+ function validOperationId(id) {
88
+ return /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(id);
89
+ }
90
+ //# sourceMappingURL=interaction-parser.js.map
@@ -0,0 +1,56 @@
1
+ import { truncateText } from "../../domain/session-text.js";
2
+ import { MAX_INTERNAL_DETAIL_CHARS } from "./limits.js";
3
+ import { isObject } from "./rollout-decoder.js";
4
+ export function internalItem(ordinal, timestamp, eventType) {
5
+ const safeType = truncateText(eventType.replaceAll(/[^A-Za-z0-9_.:-]/g, "_"), 80).text;
6
+ return {
7
+ kind: "internal",
8
+ id: `internal-${ordinal}`,
9
+ ordinal,
10
+ timestamp,
11
+ eventType: safeType,
12
+ };
13
+ }
14
+ export function internalDetail(value) {
15
+ const json = JSON.stringify(value, null, 2);
16
+ const detail = truncateText(json, MAX_INTERNAL_DETAIL_CHARS);
17
+ return { json: detail.text, truncated: detail.truncated };
18
+ }
19
+ export function internalItemFromPayload(ordinal, timestamp, payload) {
20
+ const eventType = string(payload.type) ?? "event";
21
+ if (eventType !== "token_count") {
22
+ const subtype = eventType === "item_completed" && isObject(payload.item) ? string(payload.item.type) : null;
23
+ return internalItem(ordinal, timestamp, subtype === null ? eventType : `${eventType}.${subtype}`);
24
+ }
25
+ const info = isObject(payload.info) ? payload.info : null;
26
+ return {
27
+ kind: "token",
28
+ id: `token-${ordinal}`,
29
+ ordinal,
30
+ timestamp,
31
+ tokenUsage: {
32
+ total: tokenUsageCounters(info?.total_token_usage),
33
+ last: tokenUsageCounters(info?.last_token_usage),
34
+ },
35
+ };
36
+ }
37
+ function tokenUsageCounters(value) {
38
+ if (!isObject(value))
39
+ return null;
40
+ const counters = {
41
+ totalTokens: tokenCount(value.total_tokens),
42
+ inputTokens: tokenCount(value.input_tokens),
43
+ cachedInputTokens: tokenCount(value.cached_input_tokens),
44
+ cacheWriteInputTokens: tokenCount(value.cache_write_input_tokens),
45
+ outputTokens: tokenCount(value.output_tokens),
46
+ reasoningOutputTokens: tokenCount(value.reasoning_output_tokens),
47
+ };
48
+ return Object.values(counters).some((count) => count !== null) ? counters : null;
49
+ }
50
+ function tokenCount(value) {
51
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
52
+ }
53
+ function string(value) {
54
+ return typeof value === "string" && value.length > 0 ? value : null;
55
+ }
56
+ //# sourceMappingURL=internal-event-parser.js.map
@@ -0,0 +1,5 @@
1
+ const EXPECTED_IO_ERRORS = new Set(["ENOENT", "EACCES", "EPERM", "ESTALE", "EISDIR", "ENOTDIR"]);
2
+ export function isExpectedIoError(error) {
3
+ return error instanceof Error && "code" in error && EXPECTED_IO_ERRORS.has(String(error.code));
4
+ }
5
+ //# sourceMappingURL=io-errors.js.map
@@ -0,0 +1,58 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { isExpectedIoError } from "./io-errors.js";
4
+ export class JsonlCatalogSource {
5
+ policy;
6
+ archivedDescriptors;
7
+ constructor(policy, archivedDescriptors = new Map()) {
8
+ this.policy = policy;
9
+ this.archivedDescriptors = archivedDescriptors;
10
+ }
11
+ async discover() {
12
+ const descriptors = new Map();
13
+ const diagnostics = [];
14
+ for (const root of this.policy.roots()) {
15
+ await scan(root.canonicalPath, async (candidate, regularFile) => {
16
+ // Only reuse successfully loaded archives still enumerated as regular files
17
+ // under the same canonical root. New paths and symlinks retain validation.
18
+ const cached = root.archived && regularFile ? this.archivedDescriptors.get(candidate) : undefined;
19
+ const descriptor = cached ?? (await this.policy.register(candidate));
20
+ if (descriptor !== null)
21
+ descriptors.set(descriptor.canonicalPath, descriptor);
22
+ }, diagnostics);
23
+ }
24
+ const entries = [...descriptors.values()]
25
+ .sort((left, right) => left.canonicalPath.localeCompare(right.canonicalPath))
26
+ .map((descriptor) => ({ descriptor }));
27
+ return {
28
+ entries,
29
+ diagnostics: [...this.policy.diagnostics, ...diagnostics].slice(0, 50),
30
+ };
31
+ }
32
+ }
33
+ async function scan(directory, visit, diagnostics) {
34
+ let entries;
35
+ try {
36
+ entries = await readdir(directory, { withFileTypes: true });
37
+ }
38
+ catch (error) {
39
+ if (!isExpectedIoError(error))
40
+ throw error;
41
+ if (diagnostics.length < 50)
42
+ diagnostics.push({
43
+ code: "session_root_unreadable",
44
+ severity: "warning",
45
+ message: "An allowlisted session directory could not be read.",
46
+ ordinal: null,
47
+ });
48
+ return;
49
+ }
50
+ for (const entry of entries) {
51
+ const path = join(directory, entry.name);
52
+ if (entry.isDirectory())
53
+ await scan(path, visit, diagnostics);
54
+ else if (entry.isFile() || entry.isSymbolicLink())
55
+ await visit(path, entry.isFile());
56
+ }
57
+ }
58
+ //# sourceMappingURL=jsonl-catalog-source.js.map
@@ -0,0 +1,10 @@
1
+ export const DECODER_VERSION = 4;
2
+ export const NORMALIZER_VERSION = 6;
3
+ export const MAX_JSONL_LINE_BYTES = 8 * 1024 * 1024;
4
+ export const MAX_SESSION_DIAGNOSTICS = 50;
5
+ export const MAX_MESSAGE_CHARS = 1_000_000;
6
+ export const MAX_TOOL_DETAIL_CHARS = 256_000;
7
+ export const MAX_DIRECTIVE_CHARS = 256_000;
8
+ export const MAX_INLINE_DIRECTIVE_CHARS = 512;
9
+ export const MAX_INTERNAL_DETAIL_CHARS = 256_000;
10
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1,121 @@
1
+ import { MAX_PREVIEW_CHARS, truncateText } from "../../domain/session-text.js";
2
+ import { MAX_DIRECTIVE_CHARS, MAX_INLINE_DIRECTIVE_CHARS, MAX_MESSAGE_CHARS } from "./limits.js";
3
+ import { isObject } from "./rollout-decoder.js";
4
+ export function responseDirective(ordinal, timestamp, payload) {
5
+ const role = payload.role;
6
+ if (role !== "user" && role !== "assistant" && role !== "developer")
7
+ return null;
8
+ const text = contentText(payload.content);
9
+ if (text === null)
10
+ return null;
11
+ const id = `directive-${ordinal}`;
12
+ if (text.length <= MAX_INLINE_DIRECTIVE_CHARS) {
13
+ return {
14
+ item: {
15
+ kind: "directive",
16
+ id,
17
+ ordinal,
18
+ timestamp,
19
+ text,
20
+ hasDetail: false,
21
+ },
22
+ detail: null,
23
+ };
24
+ }
25
+ const detail = truncateText(text, MAX_DIRECTIVE_CHARS);
26
+ return {
27
+ item: {
28
+ kind: "directive",
29
+ id,
30
+ ordinal,
31
+ timestamp,
32
+ summary: directiveSummary(text),
33
+ charCount: text.length,
34
+ hasDetail: true,
35
+ },
36
+ detail,
37
+ };
38
+ }
39
+ export function eventMessage(ordinal, timestamp, payload) {
40
+ const type = string(payload.type);
41
+ if (type === "user_message" || type === "agent_message") {
42
+ const markdown = string(payload.message);
43
+ if (markdown === null)
44
+ return null;
45
+ const role = type === "user_message" ? "user" : "assistant";
46
+ const phase = type === "agent_message" ? normalizePhase(payload.phase) : null;
47
+ return messageItem(ordinal, timestamp, role, phase, null, markdown);
48
+ }
49
+ if (type !== "item_completed" || !isObject(payload.item))
50
+ return null;
51
+ const item = payload.item;
52
+ const itemType = string(item.type);
53
+ if (itemType === "UserMessage") {
54
+ const markdown = itemContentText(item.content, "text");
55
+ return markdown === null ? null : messageItem(ordinal, timestamp, "user", null, null, markdown);
56
+ }
57
+ if (itemType === "AgentMessage") {
58
+ const markdown = itemContentText(item.content, "Text");
59
+ return markdown === null
60
+ ? null
61
+ : messageItem(ordinal, timestamp, "assistant", normalizePhase(item.phase), null, markdown);
62
+ }
63
+ if (itemType !== "Plan")
64
+ return null;
65
+ const markdown = string(item.text);
66
+ return markdown === null
67
+ ? null
68
+ : messageItem(ordinal, timestamp, "assistant", "final", itemType, markdown);
69
+ }
70
+ function messageItem(ordinal, timestamp, role, phase, itemType, markdown) {
71
+ return {
72
+ kind: "message",
73
+ id: `message-${ordinal}`,
74
+ ordinal,
75
+ timestamp,
76
+ role,
77
+ phase,
78
+ itemType: itemType === null ? null : truncateText(itemType, MAX_MESSAGE_CHARS).text,
79
+ markdown: truncateText(markdown, MAX_MESSAGE_CHARS).text,
80
+ };
81
+ }
82
+ function directiveSummary(value) {
83
+ const firstLine = value
84
+ .split(/\r?\n/)
85
+ .map((line) => line.trim())
86
+ .find(Boolean);
87
+ return truncateText(firstLine ?? "Directive", MAX_PREVIEW_CHARS).text;
88
+ }
89
+ function contentText(value) {
90
+ if (!Array.isArray(value))
91
+ return null;
92
+ const text = value
93
+ .filter(isObject)
94
+ .filter((part) => ["input_text", "output_text", "text"].includes(string(part.type) ?? ""))
95
+ .map((part) => string(part.text))
96
+ .filter((part) => part !== null)
97
+ .join("\n\n");
98
+ return text.length === 0 ? null : text;
99
+ }
100
+ function itemContentText(value, acceptedType) {
101
+ if (!Array.isArray(value))
102
+ return null;
103
+ const text = value
104
+ .filter(isObject)
105
+ .filter((part) => part.type === acceptedType)
106
+ .map((part) => string(part.text))
107
+ .filter((part) => part !== null)
108
+ .join("\n\n");
109
+ return text.length === 0 ? null : text;
110
+ }
111
+ function normalizePhase(value) {
112
+ if (value === "commentary")
113
+ return "commentary";
114
+ if (value === "final" || value === "final_answer")
115
+ return "final";
116
+ return null;
117
+ }
118
+ function string(value) {
119
+ return typeof value === "string" && value.length > 0 ? value : null;
120
+ }
121
+ //# sourceMappingURL=message-normalizer.js.map
@@ -0,0 +1,88 @@
1
+ import { lstat, realpath, stat } from "node:fs/promises";
2
+ import { basename, relative, resolve, sep } from "node:path";
3
+ import { opaqueIdForPath } from "../../security/opaque-id.js";
4
+ import { isExpectedIoError } from "./io-errors.js";
5
+ const ROLLOUT_NAME = /^rollout-[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl$/;
6
+ export class PathPolicy {
7
+ diagnostics = [];
8
+ #roots;
9
+ constructor(roots) {
10
+ this.#roots = roots;
11
+ }
12
+ static async create(codexHome) {
13
+ const configured = [
14
+ { path: resolve(codexHome, "sessions"), archived: false },
15
+ { path: resolve(codexHome, "archived_sessions"), archived: true },
16
+ ];
17
+ const roots = [];
18
+ const policy = new PathPolicy(roots);
19
+ for (const root of configured) {
20
+ try {
21
+ const canonicalPath = await realpath(root.path);
22
+ const info = await stat(canonicalPath);
23
+ if (!info.isDirectory()) {
24
+ throw Object.assign(new Error("Session root is not a directory"), { code: "ENOTDIR" });
25
+ }
26
+ roots.push({ canonicalPath, archived: root.archived });
27
+ }
28
+ catch (error) {
29
+ if (!isExpectedIoError(error))
30
+ throw error;
31
+ if (error.code !== "ENOENT") {
32
+ policy.unreadable("session_root_unreadable");
33
+ }
34
+ // An absent allowlisted root is normal for a new or unarchived Codex home.
35
+ }
36
+ }
37
+ return policy;
38
+ }
39
+ unreadable(code) {
40
+ if (this.diagnostics.length >= 50)
41
+ return;
42
+ this.diagnostics.push({
43
+ code,
44
+ severity: "warning",
45
+ message: "An allowlisted session directory or rollout could not be read.",
46
+ ordinal: null,
47
+ });
48
+ }
49
+ roots() {
50
+ return this.#roots;
51
+ }
52
+ async register(candidatePath) {
53
+ if (!ROLLOUT_NAME.test(basename(candidatePath)))
54
+ return null;
55
+ try {
56
+ const linkInfo = await lstat(candidatePath);
57
+ if (linkInfo.isSymbolicLink())
58
+ return null;
59
+ const canonicalPath = await realpath(candidatePath);
60
+ const root = this.#roots.find((entry) => isWithin(entry.canonicalPath, canonicalPath));
61
+ if (root === undefined)
62
+ return null;
63
+ const info = await stat(canonicalPath);
64
+ if (!info.isFile())
65
+ return null;
66
+ const relativePath = relative(root.canonicalPath, canonicalPath).split(sep).join("/");
67
+ return {
68
+ id: opaqueIdForPath(canonicalPath),
69
+ canonicalPath,
70
+ sourceRelativePath: `${root.archived ? "archived_sessions" : "sessions"}/${relativePath}`,
71
+ archived: root.archived,
72
+ size: info.size,
73
+ mtimeMs: info.mtimeMs,
74
+ };
75
+ }
76
+ catch (error) {
77
+ if (!isExpectedIoError(error))
78
+ throw error;
79
+ this.unreadable("rollout_unavailable");
80
+ return null;
81
+ }
82
+ }
83
+ }
84
+ function isWithin(root, candidate) {
85
+ const child = relative(root, candidate);
86
+ return (child === "" || (!child.startsWith(`..${sep}`) && child !== ".." && !child.startsWith(sep)));
87
+ }
88
+ //# sourceMappingURL=path-policy.js.map
@@ -0,0 +1,79 @@
1
+ import { execFile } from "node:child_process";
2
+ import { isAbsolute } from "node:path";
3
+ import { promisify } from "node:util";
4
+ const execFileAsync = promisify(execFile);
5
+ const CACHE_TTL_MS = 30_000;
6
+ const MAX_CONCURRENT_QUERIES = 4;
7
+ /** Resolves local Git relationships without interpreting rollout message contents. */
8
+ export class ProjectDirectoryResolver {
9
+ query;
10
+ now;
11
+ #cache = new Map();
12
+ #waiting = [];
13
+ #active = 0;
14
+ constructor(query = queryProjectDirectory, now = performance.now.bind(performance)) {
15
+ this.query = query;
16
+ this.now = now;
17
+ }
18
+ async resolveAll(workingDirectories) {
19
+ const unique = new Set(workingDirectories);
20
+ for (const key of this.#cache.keys()) {
21
+ if (!unique.has(key))
22
+ this.#cache.delete(key);
23
+ }
24
+ const pairs = await Promise.all([...unique].map(async (directory) => [directory, await this.resolve(directory)]));
25
+ return new Map(pairs);
26
+ }
27
+ async resolve(workingDirectory) {
28
+ if (workingDirectory === null)
29
+ return null;
30
+ const cached = this.#cache.get(workingDirectory);
31
+ if (cached !== undefined && cached.expiresAt > this.now())
32
+ return cached.result;
33
+ const entry = {
34
+ expiresAt: Number.POSITIVE_INFINITY,
35
+ result: this.#query(workingDirectory),
36
+ };
37
+ this.#cache.set(workingDirectory, entry);
38
+ void entry.result.then(() => {
39
+ entry.expiresAt = this.now() + CACHE_TTL_MS;
40
+ });
41
+ return entry.result;
42
+ }
43
+ async #query(workingDirectory) {
44
+ if (this.#active >= MAX_CONCURRENT_QUERIES) {
45
+ await new Promise((resolve) => this.#waiting.push(resolve));
46
+ }
47
+ else {
48
+ this.#active += 1;
49
+ }
50
+ try {
51
+ return (await this.query(workingDirectory)) ?? workingDirectory;
52
+ }
53
+ catch {
54
+ return workingDirectory;
55
+ }
56
+ finally {
57
+ const next = this.#waiting.shift();
58
+ if (next !== undefined)
59
+ next();
60
+ else
61
+ this.#active -= 1;
62
+ }
63
+ }
64
+ }
65
+ async function queryProjectDirectory(workingDirectory) {
66
+ if (!isAbsolute(workingDirectory))
67
+ return null;
68
+ // A Git environment inherited from the hub must not override the session's repository.
69
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_")));
70
+ const { stdout } = await execFileAsync("git", ["-C", workingDirectory, "worktree", "list", "--porcelain", "-z"], { env, timeout: 2_000, maxBuffer: 1024 * 1024, encoding: "utf8" });
71
+ const primary = stdout.split("\0\0", 1)[0].split("\0");
72
+ const header = primary[0];
73
+ if (header === undefined || !header.startsWith("worktree ") || primary.includes("bare")) {
74
+ return null;
75
+ }
76
+ const directory = header.slice("worktree ".length);
77
+ return isAbsolute(directory) ? directory : null;
78
+ }
79
+ //# sourceMappingURL=project-directory-resolver.js.map
@@ -0,0 +1,100 @@
1
+ import { internalItem } from "./internal-event-parser.js";
2
+ import { responseDirective } from "./message-normalizer.js";
3
+ import { isObject } from "./rollout-decoder.js";
4
+ import { parseUserInputQuestions } from "./user-input-normalizer.js";
5
+ export function parseResponseItem(ordinal, timestamp, payload) {
6
+ const type = string(payload.type);
7
+ if (type === "message") {
8
+ const directive = responseDirective(ordinal, timestamp, payload);
9
+ return directive === null ? { kind: "ignored" } : { kind: "directive", value: directive };
10
+ }
11
+ if (type === "reasoning") {
12
+ return {
13
+ kind: "timeline",
14
+ value: internalItem(ordinal, timestamp, "reasoning"),
15
+ };
16
+ }
17
+ const userInput = userInputRequest(ordinal, timestamp, payload);
18
+ if (userInput !== null)
19
+ return { kind: "user_input_request", value: userInput };
20
+ const call = toolCall(ordinal, timestamp, payload);
21
+ if (call !== null)
22
+ return { kind: "tool_call", value: call };
23
+ const output = toolOutput(ordinal, timestamp, payload);
24
+ if (output !== null)
25
+ return { kind: "tool_output", value: output };
26
+ return {
27
+ kind: "timeline",
28
+ value: internalItem(ordinal, timestamp, type ?? "response_item"),
29
+ };
30
+ }
31
+ function userInputRequest(ordinal, timestamp, payload) {
32
+ if (payload.type !== "function_call" || payload.name !== "request_user_input")
33
+ return null;
34
+ const callId = string(payload.call_id);
35
+ if (callId === null)
36
+ return null;
37
+ const questions = parseUserInputQuestions(payload.arguments);
38
+ return {
39
+ callId,
40
+ ordinal,
41
+ timestamp,
42
+ questions: questions ?? [],
43
+ malformed: questions === null,
44
+ };
45
+ }
46
+ function toolCall(ordinal, timestamp, payload) {
47
+ const type = string(payload.type);
48
+ if (type !== "function_call" && type !== "custom_tool_call")
49
+ return null;
50
+ const callId = string(payload.call_id);
51
+ if (callId === null)
52
+ return null;
53
+ return {
54
+ callId,
55
+ ordinal,
56
+ timestamp,
57
+ toolName: string(payload.name) ?? (type === "custom_tool_call" ? "custom tool" : "function"),
58
+ input: serializeText(payload.arguments ?? payload.input),
59
+ };
60
+ }
61
+ function toolOutput(ordinal, timestamp, payload) {
62
+ const type = string(payload.type);
63
+ if (type !== "function_call_output" && type !== "custom_tool_call_output")
64
+ return null;
65
+ const callId = string(payload.call_id);
66
+ if (callId === null)
67
+ return null;
68
+ return {
69
+ callId,
70
+ ordinal,
71
+ timestamp,
72
+ output: toolOutputText(payload.output),
73
+ failed: payload.success === false || payload.status === "failed",
74
+ };
75
+ }
76
+ function serializeText(value) {
77
+ if (typeof value === "string")
78
+ return value;
79
+ if (value === null || value === undefined)
80
+ return null;
81
+ try {
82
+ return JSON.stringify(value);
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ function toolOutputText(value) {
89
+ if (!Array.isArray(value))
90
+ return serializeText(value);
91
+ const parts = value
92
+ .filter(isObject)
93
+ .map((part) => string(part.text) ?? string(part.output_text) ?? serializeText(part))
94
+ .filter((part) => part !== null);
95
+ return parts.length === 0 ? serializeText(value) : parts.join("\n\n");
96
+ }
97
+ function string(value) {
98
+ return typeof value === "string" && value.length > 0 ? value : null;
99
+ }
100
+ //# sourceMappingURL=response-item-parser.js.map