pi-harness-runtime 1.1.136 → 1.1.138

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 (131) hide show
  1. package/harness/langchain/daemon.ts +128 -25
  2. package/harness/langchain/run.ts +1 -1
  3. package/index.ts +41 -2
  4. package/package.json +1 -1
  5. package/packages/a2a-adapter/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/codex-watcher/dist/src/index.d.ts +42 -0
  20. package/packages/codex-watcher/dist/src/index.d.ts.map +1 -0
  21. package/packages/codex-watcher/dist/src/index.js +198 -0
  22. package/packages/codex-watcher/dist/src/index.js.map +1 -0
  23. package/packages/codex-watcher/dist/src/parser.d.ts +36 -0
  24. package/packages/codex-watcher/dist/src/parser.d.ts.map +1 -0
  25. package/packages/codex-watcher/dist/src/parser.js +151 -0
  26. package/packages/codex-watcher/dist/src/parser.js.map +1 -0
  27. package/packages/codex-watcher/dist/src/session.d.ts +9 -0
  28. package/packages/codex-watcher/dist/src/session.d.ts.map +1 -0
  29. package/packages/codex-watcher/dist/src/session.js +131 -0
  30. package/packages/codex-watcher/dist/src/session.js.map +1 -0
  31. package/packages/codex-watcher/dist/src/types.d.ts +63 -0
  32. package/packages/codex-watcher/dist/src/types.d.ts.map +1 -0
  33. package/packages/codex-watcher/dist/src/types.js +17 -0
  34. package/packages/codex-watcher/dist/src/types.js.map +1 -0
  35. package/packages/codex-watcher/package.json +26 -0
  36. package/packages/codex-watcher/src/index.ts +244 -0
  37. package/packages/codex-watcher/src/parser.ts +210 -0
  38. package/packages/codex-watcher/src/session.ts +151 -0
  39. package/packages/codex-watcher/src/types.ts +67 -0
  40. package/packages/codex-watcher/test/watch.test.ts +150 -0
  41. package/packages/config-capture/package.json +1 -1
  42. package/packages/config-capture/src/index.ts +5 -16
  43. package/packages/context-compiler/package.json +1 -1
  44. package/packages/context-discovery/package.json +1 -1
  45. package/packages/context-manager/package.json +1 -1
  46. package/packages/cookie-sanitizer/package.json +1 -1
  47. package/packages/cost-optimizer/package.json +1 -1
  48. package/packages/dependency-analyzer/package.json +1 -1
  49. package/packages/django-plugin/package.json +1 -1
  50. package/packages/doc-generator/package.json +1 -1
  51. package/packages/evaluation-engine/package.json +1 -1
  52. package/packages/evaluation-runner/package.json +1 -1
  53. package/packages/event-bus/package.json +1 -1
  54. package/packages/event-store/package.json +1 -1
  55. package/packages/experience-replay/package.json +1 -1
  56. package/packages/feedback-collector/package.json +1 -1
  57. package/packages/file-copy-helper/package.json +1 -1
  58. package/packages/framework-detector/package.json +1 -1
  59. package/packages/framework-plugin-sdk/package.json +1 -1
  60. package/packages/frappe-plugin/package.json +1 -1
  61. package/packages/generic-web-plugin/package.json +1 -1
  62. package/packages/health-monitor/package.json +1 -1
  63. package/packages/intent-analyzer/package.json +1 -1
  64. package/packages/jev-judge/package.json +1 -1
  65. package/packages/knowledge-graph/package.json +1 -1
  66. package/packages/knowledge-retrieval/package.json +1 -1
  67. package/packages/laravel-plugin/package.json +1 -1
  68. package/packages/learning-engine/package.json +1 -1
  69. package/packages/mcp-adapter/package.json +1 -1
  70. package/packages/memory-engine/package.json +1 -1
  71. package/packages/milestone-manager/package.json +1 -1
  72. package/packages/model-registry/package.json +1 -1
  73. package/packages/nextjs-plugin/package.json +1 -1
  74. package/packages/notification/package.json +1 -1
  75. package/packages/observability/package.json +1 -1
  76. package/packages/okf-indexer/package.json +1 -1
  77. package/packages/performance-optimizer/package.json +1 -1
  78. package/packages/privilege-broker/package.json +1 -1
  79. package/packages/project-analyzer/package.json +1 -1
  80. package/packages/project-bootstrap/package.json +1 -1
  81. package/packages/projection-engine/package.json +1 -1
  82. package/packages/prompt-compiler/package.json +1 -1
  83. package/packages/prompt-versioning/package.json +1 -1
  84. package/packages/provider-adapter-sdk/package.json +1 -1
  85. package/packages/provider-router/package.json +1 -1
  86. package/packages/provider-selector/package.json +1 -1
  87. package/packages/providers/package.json +1 -1
  88. package/packages/qdrant-skills/package.json +1 -1
  89. package/packages/quota-manager/package.json +1 -1
  90. package/packages/rate-limiter/package.json +1 -1
  91. package/packages/react-vite-plugin/package.json +1 -1
  92. package/packages/release-manager/package.json +1 -1
  93. package/packages/requirement-compiler/package.json +1 -1
  94. package/packages/runtime/package.json +1 -1
  95. package/packages/scheduler/package.json +1 -1
  96. package/packages/scheduler-adapter/package.json +1 -1
  97. package/packages/session/package.json +1 -1
  98. package/packages/session-api/package.json +1 -1
  99. package/packages/session-export/package.json +1 -1
  100. package/packages/shared-context/package.json +1 -1
  101. package/packages/skill-mcp-client/package.json +1 -1
  102. package/packages/skill-registry/package.json +1 -1
  103. package/packages/skills/package.json +1 -1
  104. package/packages/sprint-planner/package.json +1 -1
  105. package/packages/subscription-engine/package.json +1 -1
  106. package/packages/task-compiler/package.json +1 -1
  107. package/packages/tencentdb-memory/package.json +1 -1
  108. package/packages/tencentdb-sync/package.json +1 -1
  109. package/packages/test-data-generator/package.json +1 -1
  110. package/packages/test-generator/package.json +1 -1
  111. package/packages/todo-bd-sync/package.json +1 -1
  112. package/packages/token-estimation/package.json +1 -1
  113. package/packages/token-optimizer/package.json +1 -1
  114. package/packages/trajectory/package.json +1 -1
  115. package/packages/tui/dist/harness-header.d.ts +18 -14
  116. package/packages/tui/dist/harness-header.d.ts.map +1 -1
  117. package/packages/tui/dist/harness-header.js +110 -64
  118. package/packages/tui/dist/harness-header.js.map +1 -1
  119. package/packages/tui/dist/index.d.ts +1 -2
  120. package/packages/tui/dist/index.d.ts.map +1 -1
  121. package/packages/tui/dist/index.js +1 -2
  122. package/packages/tui/dist/index.js.map +1 -1
  123. package/packages/tui/package.json +1 -1
  124. package/packages/tui/src/harness-header.ts +156 -77
  125. package/packages/tui/src/index.ts +6 -2
  126. package/packages/tui/test/harness-header.test.ts +380 -100
  127. package/packages/types/package.json +1 -1
  128. package/packages/workflow-events/package.json +1 -1
  129. package/packages/workspace-scanner/package.json +1 -1
  130. package/packages/worktree/package.json +1 -1
  131. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Codex Session Watcher — Main API
3
+ *
4
+ * Watches the active Codex CLI session and emits visible messages (user + assistant)
5
+ * so the harness PING-PONG middleware can feed them to pi.dev.
6
+ *
7
+ * Usage:
8
+ * import { startWatcher, getLatestSession, getVisibleMessages } from "@pi-harness/codex-watcher";
9
+ *
10
+ * // One-shot: get latest messages from the current session
11
+ * const session = getLatestSession();
12
+ * const messages = await getVisibleMessages(session.rolloutPath, { fromOrdinal: 0 });
13
+ *
14
+ * // Continuous: poll the active session and emit messages
15
+ * const handle = startWatcher({
16
+ * onMessages: (msgs) => console.log(msgs),
17
+ * });
18
+ * // later: handle.stop();
19
+ */
20
+ import { existsSync, openSync, readFileSync, statSync } from "node:fs";
21
+ import { homedir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { classifyMessage, parseNewMessages } from "./parser.js";
24
+ import { getAllSessions, getLatestSession } from "./session.js";
25
+ export { classifyMessage, getAllSessions, getLatestSession };
26
+ // ---------------------------------------------------------------------------
27
+ // One-shot API
28
+ // ---------------------------------------------------------------------------
29
+ /** Read all visible messages from a rollout file, starting from a given ordinal. */
30
+ export async function getVisibleMessages(rolloutPath, options = {}) {
31
+ const { fromOrdinal = 0, limit } = options;
32
+ if (!existsSync(rolloutPath))
33
+ return [];
34
+ const content = readFileSync(rolloutPath, "utf8");
35
+ const lines = content.split("\n").filter((l) => l.trim());
36
+ let messages = parseNewMessages(lines, fromOrdinal);
37
+ if (limit)
38
+ messages = messages.slice(-limit);
39
+ if (messages.length > 0) {
40
+ messages[messages.length - 1] = {
41
+ ...messages[messages.length - 1],
42
+ isLatest: true,
43
+ };
44
+ }
45
+ return messages;
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Streaming watcher API
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * Start watching the active Codex session and call `onMessages` whenever
52
+ * new visible messages appear in the rollout file.
53
+ *
54
+ * @returns WatcherHandle — call handle.stop() to stop watching
55
+ *
56
+ * The watcher:
57
+ * 1. Polls session_index.jsonl to detect active session changes
58
+ * 2. Tracks file position so it only reads new lines (no re-read of full file)
59
+ * 3. Calls onMessages with incremental messages each poll cycle
60
+ */
61
+ export function startWatcher(options = {}) {
62
+ const { pollIntervalMs = 2000, codexDir, onMessages, onSessionChange, } = options;
63
+ const home = codexDir ?? join(homedir(), ".codex");
64
+ let stopped = false;
65
+ let currentFd = null;
66
+ let currentPath = null;
67
+ let currentOrdinal = 0;
68
+ let pollCount = 0;
69
+ const maxIdlePolls = options.maxIdlePolls ?? Infinity;
70
+ // Track last known session to detect changes
71
+ let lastSessionId = null;
72
+ function stop() {
73
+ stopped = true;
74
+ if (currentFd !== null) {
75
+ try {
76
+ const { closeSync } = require("node:fs");
77
+ closeSync(currentFd);
78
+ }
79
+ catch {
80
+ // ignore
81
+ }
82
+ }
83
+ }
84
+ function isRunning() {
85
+ return !stopped;
86
+ }
87
+ function latestSession() {
88
+ return getLatestSession(home);
89
+ }
90
+ function latestOrdinal() {
91
+ return currentOrdinal;
92
+ }
93
+ function openRollout(path) {
94
+ if (currentFd !== null && currentPath !== path) {
95
+ try {
96
+ const { closeSync } = require("node:fs");
97
+ closeSync(currentFd);
98
+ }
99
+ catch {
100
+ // ignore
101
+ }
102
+ }
103
+ // stat to get current size
104
+ const stat = statSync(path);
105
+ currentFd = openSync(path, "r");
106
+ currentPath = path;
107
+ currentOrdinal = 0;
108
+ return { fd: currentFd, size: stat.size };
109
+ }
110
+ function readNewLines(fd, knownSize) {
111
+ const { readSync, fstatSync } = require("node:fs");
112
+ const stat = fstatSync(fd);
113
+ const newSize = stat.size;
114
+ if (newSize <= knownSize) {
115
+ return { lines: [], newSize: knownSize };
116
+ }
117
+ const buf = Buffer.alloc(newSize - knownSize);
118
+ const bytesRead = readSync(fd, buf, 0, buf.length, knownSize);
119
+ const text = buf.subarray(0, bytesRead).toString("utf8");
120
+ const lines = text.split("\n").filter((l) => l.trim());
121
+ return { lines, newSize };
122
+ }
123
+ async function poll() {
124
+ if (stopped)
125
+ return;
126
+ try {
127
+ // Check for session change
128
+ const session = getLatestSession(home);
129
+ const sessionId = session?.id ?? null;
130
+ if (sessionId !== lastSessionId) {
131
+ // New session or session ended
132
+ if (session) {
133
+ // Open new rollout
134
+ openRollout(session.rolloutPath);
135
+ currentOrdinal = 0;
136
+ // Read all existing lines as "historical"
137
+ const existing = await getVisibleMessages(session.rolloutPath, {
138
+ fromOrdinal: 0,
139
+ });
140
+ if (existing.length > 0) {
141
+ currentOrdinal = existing[existing.length - 1].ordinal;
142
+ }
143
+ onSessionChange?.(session);
144
+ }
145
+ else {
146
+ onSessionChange?.(null);
147
+ }
148
+ lastSessionId = sessionId;
149
+ pollCount = 0;
150
+ }
151
+ if (!session || !currentFd || currentPath !== session.rolloutPath) {
152
+ if (!stopped) {
153
+ await sleep(pollIntervalMs);
154
+ poll();
155
+ }
156
+ return;
157
+ }
158
+ const stat = statSync(session.rolloutPath);
159
+ const { lines } = readNewLines(currentFd, stat.size);
160
+ if (lines.length > 0) {
161
+ pollCount = 0;
162
+ const messages = parseNewMessages(lines, currentOrdinal);
163
+ if (messages.length > 0) {
164
+ currentOrdinal = messages[messages.length - 1].ordinal;
165
+ messages[messages.length - 1] = {
166
+ ...messages[messages.length - 1],
167
+ isLatest: true,
168
+ };
169
+ onMessages?.(messages);
170
+ }
171
+ }
172
+ else {
173
+ pollCount++;
174
+ if (pollCount > maxIdlePolls) {
175
+ stop();
176
+ return;
177
+ }
178
+ }
179
+ }
180
+ catch (_err) {
181
+ // Session file may not exist yet; just wait
182
+ }
183
+ if (!stopped) {
184
+ await sleep(pollIntervalMs);
185
+ poll();
186
+ }
187
+ }
188
+ // Start polling
189
+ poll();
190
+ return { stop, isRunning, latestSession, latestOrdinal };
191
+ }
192
+ // ---------------------------------------------------------------------------
193
+ // Helpers
194
+ // ---------------------------------------------------------------------------
195
+ function sleep(ms) {
196
+ return new Promise((resolve) => setTimeout(resolve, ms));
197
+ }
198
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Parser — extracts visible messages from Codex JSONL rollout lines.
3
+ *
4
+ * Safe subset (no developer/reasoning):
5
+ * type == "response_item" && payload.role == "user" → input_text content
6
+ * type == "event_msg" && payload.type == "item_completed"
7
+ * && payload.item.type == "AgentMessage"
8
+ * && payload.item.content[].type == "Text" → visible assistant text
9
+ */
10
+ import type { CodexMessage } from "./types.js";
11
+ /**
12
+ * Parse a single JSONL line from a Codex rollout file.
13
+ * Returns null if the line is not a visible message or is malformed.
14
+ */
15
+ export declare function parseRolloutLine(line: string): CodexMessage | null;
16
+ /**
17
+ * Parse all visible messages from a JSONL file.
18
+ * Reads from a given byte offset so callers can do incremental reads.
19
+ *
20
+ * @param lines Lines to parse (typically newly read lines)
21
+ * @param latestOrdinal The last ordinal already known (messages with ordinal ≤ this are skipped)
22
+ * @returns Messages with ordinal > latestOrdinal
23
+ */
24
+ export declare function parseNewMessages(lines: string[], latestOrdinal?: number): CodexMessage[];
25
+ /**
26
+ * Extract code blocks from assistant message text.
27
+ * Useful for detecting "plan" vs "code" in the Codex output.
28
+ */
29
+ export declare function extractCodeBlocks(text: string): string[];
30
+ /**
31
+ * Detect whether the message is a plan (markdown outline) vs code output.
32
+ * Returns "plan" if it looks like a planning document, "code" if mostly code,
33
+ * "mixed" if both, "unknown" otherwise.
34
+ */
35
+ export declare function classifyMessage(text: string): "plan" | "code" | "mixed" | "unknown";
36
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAiD/C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAuBlE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,MAAM,EAAE,EACf,aAAa,SAAI,GACf,YAAY,EAAE,CAWhB;AAqED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMxD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,GACV,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAWvC"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Parser — extracts visible messages from Codex JSONL rollout lines.
3
+ *
4
+ * Safe subset (no developer/reasoning):
5
+ * type == "response_item" && payload.role == "user" → input_text content
6
+ * type == "event_msg" && payload.type == "item_completed"
7
+ * && payload.item.type == "AgentMessage"
8
+ * && payload.item.content[].type == "Text" → visible assistant text
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // Parse a single JSONL line into a CodexMessage
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Parse a single JSONL line from a Codex rollout file.
15
+ * Returns null if the line is not a visible message or is malformed.
16
+ */
17
+ export function parseRolloutLine(line) {
18
+ if (!line.trim())
19
+ return null;
20
+ let raw;
21
+ try {
22
+ raw = JSON.parse(line);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ // Assistant: visible text from AgentMessage item_completed events
28
+ if (isEventMsg(raw)) {
29
+ const msg = parseAssistantMessage(raw);
30
+ if (msg)
31
+ return msg;
32
+ }
33
+ // User: input_text from response_item events
34
+ if (isResponseItem(raw)) {
35
+ const msg = parseUserMessage(raw);
36
+ if (msg)
37
+ return msg;
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Parse all visible messages from a JSONL file.
43
+ * Reads from a given byte offset so callers can do incremental reads.
44
+ *
45
+ * @param lines Lines to parse (typically newly read lines)
46
+ * @param latestOrdinal The last ordinal already known (messages with ordinal ≤ this are skipped)
47
+ * @returns Messages with ordinal > latestOrdinal
48
+ */
49
+ export function parseNewMessages(lines, latestOrdinal = 0) {
50
+ const messages = [];
51
+ for (const line of lines) {
52
+ const msg = parseRolloutLine(line);
53
+ if (msg && msg.ordinal > latestOrdinal) {
54
+ messages.push(msg);
55
+ }
56
+ }
57
+ // Sort by ordinal (ascending) to preserve order
58
+ messages.sort((a, b) => a.ordinal - b.ordinal);
59
+ return messages;
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Type guards
63
+ // ---------------------------------------------------------------------------
64
+ function isEventMsg(raw) {
65
+ return raw.type === "event_msg";
66
+ }
67
+ function isResponseItem(raw) {
68
+ return raw.type === "response_item";
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Parse specific message types
72
+ // ---------------------------------------------------------------------------
73
+ function parseAssistantMessage(raw) {
74
+ const p = raw.payload;
75
+ if (p.type !== "item_completed")
76
+ return null;
77
+ if (p.item?.type !== "AgentMessage")
78
+ return null;
79
+ // Collect all Text content blocks
80
+ const texts = [];
81
+ for (const content of p.item.content ?? []) {
82
+ if (content.type === "Text" && content.text) {
83
+ texts.push(content.text);
84
+ }
85
+ }
86
+ if (texts.length === 0)
87
+ return null;
88
+ return {
89
+ ordinal: raw.ordinal,
90
+ timestamp: raw.timestamp,
91
+ role: "assistant",
92
+ text: texts.join("\n"),
93
+ isLatest: false,
94
+ };
95
+ }
96
+ function parseUserMessage(raw) {
97
+ const p = raw.payload;
98
+ if (p.type !== "message")
99
+ return null;
100
+ if (p.role !== "user")
101
+ return null;
102
+ const texts = [];
103
+ for (const content of p.content ?? []) {
104
+ if (content.type === "input_text" && content.text) {
105
+ texts.push(content.text);
106
+ }
107
+ }
108
+ if (texts.length === 0)
109
+ return null;
110
+ return {
111
+ ordinal: raw.ordinal,
112
+ timestamp: raw.timestamp,
113
+ role: "user",
114
+ text: texts.join("\n"),
115
+ isLatest: false,
116
+ };
117
+ }
118
+ // ---------------------------------------------------------------------------
119
+ // Text extraction utilities
120
+ // ---------------------------------------------------------------------------
121
+ /**
122
+ * Extract code blocks from assistant message text.
123
+ * Useful for detecting "plan" vs "code" in the Codex output.
124
+ */
125
+ export function extractCodeBlocks(text) {
126
+ const blocks = [];
127
+ for (const match of text.matchAll(/```[\w]*\n?([\s\S]*?)```/g)) {
128
+ blocks.push(match[1]?.trim() ?? "");
129
+ }
130
+ return blocks;
131
+ }
132
+ /**
133
+ * Detect whether the message is a plan (markdown outline) vs code output.
134
+ * Returns "plan" if it looks like a planning document, "code" if mostly code,
135
+ * "mixed" if both, "unknown" otherwise.
136
+ */
137
+ export function classifyMessage(text) {
138
+ const codeBlocks = extractCodeBlocks(text);
139
+ const codeCharCount = codeBlocks.join("").replace(/\s/g, "").length;
140
+ const nonCodeCharCount = text
141
+ .replace(/```[\s\S]*?```/g, "")
142
+ .replace(/\s/g, "").length;
143
+ if (codeCharCount === 0 && nonCodeCharCount > 0)
144
+ return "plan";
145
+ if (nonCodeCharCount === 0 && codeCharCount > 0)
146
+ return "code";
147
+ if (codeCharCount > 0 && nonCodeCharCount > 0)
148
+ return "mixed";
149
+ return "unknown";
150
+ }
151
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+CH,8EAA8E;AAC9E,gDAAgD;AAChD,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAE9B,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;IAED,kEAAkE;IAClE,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACrB,CAAC;IAED,6CAA6C;IAC7C,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACrB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC/B,KAAe,EACf,aAAa,GAAG,CAAC;IAEjB,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,GAAG,aAAa,EAAE,CAAC;YACxC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;IACF,CAAC;IACD,gDAAgD;IAChD,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,SAAS,UAAU,CAAC,GAAa;IAChC,OAAO,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC;AACjC,CAAC;AAED,SAAS,cAAc,CAAC,GAAa;IACpC,OAAO,GAAG,CAAC,IAAI,KAAK,eAAe,CAAC;AACrC,CAAC;AAED,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AAE9E,SAAS,qBAAqB,CAAC,GAAgB;IAC9C,MAAM,CAAC,GAAG,GAAG,CAAC,OAA0B,CAAC;IACzC,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IAEjD,kCAAkC;IAClC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QAC5C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,OAAO;QACN,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,QAAQ,EAAE,KAAK;KACf,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAoB;IAC7C,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;IACtB,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,OAAO;QACN,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,QAAQ,EAAE,KAAK;KACf,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,4BAA4B;AAC5B,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC9B,IAAY;IAEZ,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;IACpE,MAAM,gBAAgB,GAAG,IAAI;SAC3B,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;SAC9B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;IAE5B,IAAI,aAAa,KAAK,CAAC,IAAI,gBAAgB,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/D,IAAI,gBAAgB,KAAK,CAAC,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/D,IAAI,aAAa,GAAG,CAAC,IAAI,gBAAgB,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAC9D,OAAO,SAAS,CAAC;AAClB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Session discovery — reads ~/.codex/session_index.jsonl to find active sessions.
3
+ */
4
+ import type { CodexSession } from "./types.js";
5
+ /** Find the most recently active Codex session. */
6
+ export declare function getLatestSession(codexDir?: string): CodexSession | null;
7
+ /** Get all sessions sorted by most recent first. */
8
+ export declare function getAllSessions(codexDir?: string): CodexSession[];
9
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAsBvE;AAED,oDAAoD;AACpD,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CAkBhE"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Session discovery — reads ~/.codex/session_index.jsonl to find active sessions.
3
+ */
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+ /** Find the most recently active Codex session. */
8
+ export function getLatestSession(codexDir) {
9
+ const indexPath = getSessionIndexPath(codexDir);
10
+ if (!existsSync(indexPath))
11
+ return null;
12
+ let latest = null;
13
+ for (const line of readLines(indexPath)) {
14
+ const entry = parseSessionIndexLine(line);
15
+ if (!entry)
16
+ continue;
17
+ if (!latest || entry.updated_at > latest.updated_at) {
18
+ latest = entry;
19
+ }
20
+ }
21
+ if (!latest)
22
+ return null;
23
+ return {
24
+ id: latest.id,
25
+ threadName: latest.thread_name,
26
+ updatedAt: latest.updated_at,
27
+ rolloutPath: findRolloutPath(latest.id, codexDir),
28
+ };
29
+ }
30
+ /** Get all sessions sorted by most recent first. */
31
+ export function getAllSessions(codexDir) {
32
+ const indexPath = getSessionIndexPath(codexDir);
33
+ if (!existsSync(indexPath))
34
+ return [];
35
+ const sessions = [];
36
+ for (const line of readLines(indexPath)) {
37
+ const entry = parseSessionIndexLine(line);
38
+ if (entry)
39
+ sessions.push(entry);
40
+ }
41
+ return sessions
42
+ .sort((a, b) => b.updated_at.localeCompare(a.updated_at))
43
+ .map((e) => ({
44
+ id: e.id,
45
+ threadName: e.thread_name,
46
+ updatedAt: e.updated_at,
47
+ rolloutPath: findRolloutPath(e.id, codexDir),
48
+ }));
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Internal helpers
52
+ // ---------------------------------------------------------------------------
53
+ function getSessionIndexPath(codexDir) {
54
+ const home = codexDir ?? join(homedir(), ".codex");
55
+ return join(home, "session_index.jsonl");
56
+ }
57
+ function findRolloutPath(sessionId, codexDir) {
58
+ const home = codexDir ?? join(homedir(), ".codex");
59
+ const sessionsDir = join(home, "sessions");
60
+ // The rollout path follows: sessions/YYYY/MM/DD/rollout-YYYY-MM-DDTHH-MM-SS-<sessionId>.jsonl
61
+ // We need to find the file whose name ends with <sessionId>.jsonl
62
+ // Since there can be multiple sessions per day, iterate all files.
63
+ try {
64
+ const { readdirSync } = require("node:fs");
65
+ for (const dirPath of walkDirs(sessionsDir)) {
66
+ for (const file of readdirSync(dirPath)) {
67
+ if (file.endsWith(`${sessionId}.jsonl`)) {
68
+ return join(dirPath, file);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ catch {
74
+ // walkSync may not be available on all Node versions; fall back to find
75
+ }
76
+ return findRolloutPathFallback(sessionId, sessionsDir);
77
+ }
78
+ function findRolloutPathFallback(sessionId, sessionsDir) {
79
+ const found = walkForSession(sessionsDir, sessionId);
80
+ return found ?? join(sessionsDir, "unknown", `${sessionId}.jsonl`);
81
+ }
82
+ /** Recursively walk all subdirectories, yielding each directory path. */
83
+ function* walkDirs(dir) {
84
+ const { readdirSync, statSync } = require("node:fs");
85
+ for (const entry of readdirSync(dir)) {
86
+ const full = join(dir, entry);
87
+ const stat = statSync(full);
88
+ if (stat.isDirectory()) {
89
+ yield full;
90
+ yield* walkDirs(full);
91
+ }
92
+ }
93
+ }
94
+ /** Find the rollout file for a given session ID by walking directories. */
95
+ function walkForSession(sessionsDir, sessionId) {
96
+ const { readdirSync } = require("node:fs");
97
+ for (const dirPath of walkDirs(sessionsDir)) {
98
+ for (const file of readdirSync(dirPath)) {
99
+ if (file.startsWith("rollout-") && file.endsWith(`${sessionId}.jsonl`)) {
100
+ return join(dirPath, file);
101
+ }
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ function parseSessionIndexLine(line) {
107
+ try {
108
+ const obj = JSON.parse(line);
109
+ if (typeof obj.id !== "string" || typeof obj.thread_name !== "string") {
110
+ return null;
111
+ }
112
+ return {
113
+ id: obj.id,
114
+ thread_name: obj.thread_name,
115
+ updated_at: String(obj.updated_at ?? ""),
116
+ };
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ function readLines(filePath) {
123
+ try {
124
+ const content = readFileSync(filePath, "utf8");
125
+ return content.split("\n").filter((l) => l.trim().length > 0);
126
+ }
127
+ catch {
128
+ return [];
129
+ }
130
+ }
131
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,mDAAmD;AACnD,MAAM,UAAU,gBAAgB,CAAC,QAAiB;IACjD,MAAM,SAAS,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,IAAI,MAAM,GAA6B,IAAI,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACrD,MAAM,GAAG,KAAK,CAAC;QAChB,CAAC;IACF,CAAC;IAED,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO;QACN,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,UAAU,EAAE,MAAM,CAAC,WAAW;QAC9B,SAAS,EAAE,MAAM,CAAC,UAAU;QAC5B,WAAW,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC;KACjD,CAAC;AACH,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,QAAiB;IAC/C,MAAM,SAAS,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,QAAQ;SACb,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;SACxD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACZ,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,SAAS,EAAE,CAAC,CAAC,UAAU;QACvB,WAAW,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;KAC5C,CAAC,CAAC,CAAC;AACN,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,SAAS,mBAAmB,CAAC,QAAiB;IAC7C,MAAM,IAAI,GAAG,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB,EAAE,QAAiB;IAC5D,MAAM,IAAI,GAAG,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAE3C,8FAA8F;IAC9F,kEAAkE;IAClE,mEAAmE;IACnE,IAAI,CAAC;QACJ,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,CAA6B,CAAC;QACvE,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,SAAS,QAAQ,CAAC,EAAE,CAAC;oBACzC,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC5B,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,wEAAwE;IACzE,CAAC;IACD,OAAO,uBAAuB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,uBAAuB,CAC/B,SAAiB,EACjB,WAAmB;IAEnB,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,yEAAyE;AACzE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAW;IAC7B,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,GAC9B,OAAO,CAAC,SAAS,CAA6B,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC;YACX,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;AACF,CAAC;AAED,2EAA2E;AAC3E,SAAS,cAAc,CAAC,WAAmB,EAAE,SAAiB;IAC7D,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,CAA6B,CAAC;IACvE,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;YACzC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,SAAS,QAAQ,CAAC,EAAE,CAAC;gBACxE,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC5B,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAQD,SAAS,qBAAqB,CAAC,IAAY;IAC1C,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QACxD,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YACvE,OAAO,IAAI,CAAC;QACb,CAAC;QACD,OAAO;YACN,EAAE,EAAE,GAAG,CAAC,EAAY;YACpB,WAAW,EAAE,GAAG,CAAC,WAAqB;YACtC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;SACxC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB;IAClC,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Codex Session Watcher — types
3
+ *
4
+ * Watches ~/.codex/ for active Codex CLI sessions and parses visible
5
+ * communication between the user and Codex (Planner role in PING-PONG).
6
+ *
7
+ * Key file locations:
8
+ * ~/.codex/session_index.jsonl — session registry
9
+ * ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl — session transcripts
10
+ *
11
+ * Visible message schema (safe subset — no developer/reasoning):
12
+ * type == "event_msg" && payload.type == "item_completed"
13
+ * && payload.item.type == "AgentMessage"
14
+ * && payload.item.content[].type == "Text"
15
+ */
16
+ export interface CodexSession {
17
+ /** Codex session ID (UUID) */
18
+ id: string;
19
+ /** Human-readable session name */
20
+ threadName: string;
21
+ /** ISO-8601 when session was last active */
22
+ updatedAt: string;
23
+ /** Absolute path to the rollout transcript file */
24
+ rolloutPath: string;
25
+ }
26
+ export interface CodexMessage {
27
+ /** Unique ordinal within the session (monotonically increasing) */
28
+ ordinal: number;
29
+ /** ISO-8601 timestamp */
30
+ timestamp: string;
31
+ /** Role: "user" (human) or "assistant" (Codex) */
32
+ role: "user" | "assistant";
33
+ /** Raw text content (markdown, code blocks, etc.) */
34
+ text: string;
35
+ /** Whether this message is the latest known message */
36
+ isLatest: boolean;
37
+ }
38
+ export interface CodexEvent {
39
+ /** Monotonically increasing ordinal within the session */
40
+ ordinal: number;
41
+ timestamp: string;
42
+ /** Raw JSON payload from the JSONL line */
43
+ raw: unknown;
44
+ }
45
+ export interface WatcherOptions {
46
+ /** How often to poll session_index.jsonl and active rollout file (ms). Default: 2000 */
47
+ pollIntervalMs?: number;
48
+ /** Absolute path to Codex home. Default: ~/.codex */
49
+ codexDir?: string;
50
+ /** Called with new visible messages each poll cycle */
51
+ onMessages?: (messages: CodexMessage[]) => void;
52
+ /** Called when a new session becomes active */
53
+ onSessionChange?: (session: CodexSession | null) => void;
54
+ /** Stop watching after N polls with no new lines. Default: Infinity */
55
+ maxIdlePolls?: number;
56
+ }
57
+ export interface WatcherHandle {
58
+ stop: () => void;
59
+ isRunning: () => boolean;
60
+ latestSession: () => CodexSession | null;
61
+ latestOrdinal: () => number;
62
+ }
63
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,YAAY;IAC5B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC5B,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IAC1B,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,GAAG,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC9B,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAChD,+CAA+C;IAC/C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IACzD,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,YAAY,GAAG,IAAI,CAAC;IACzC,aAAa,EAAE,MAAM,MAAM,CAAC;CAC5B"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Codex Session Watcher — types
3
+ *
4
+ * Watches ~/.codex/ for active Codex CLI sessions and parses visible
5
+ * communication between the user and Codex (Planner role in PING-PONG).
6
+ *
7
+ * Key file locations:
8
+ * ~/.codex/session_index.jsonl — session registry
9
+ * ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl — session transcripts
10
+ *
11
+ * Visible message schema (safe subset — no developer/reasoning):
12
+ * type == "event_msg" && payload.type == "item_completed"
13
+ * && payload.item.type == "AgentMessage"
14
+ * && payload.item.content[].type == "Text"
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}