@codedeck/codedeck 2026.3.1-4.63

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 (239) hide show
  1. package/README.md +38 -0
  2. package/config/default.yaml +51 -0
  3. package/dist/agent/brain-dispatcher.d.ts +67 -0
  4. package/dist/agent/brain-dispatcher.d.ts.map +1 -0
  5. package/dist/agent/brain-dispatcher.js +136 -0
  6. package/dist/agent/brain-dispatcher.js.map +1 -0
  7. package/dist/agent/detect.d.ts +20 -0
  8. package/dist/agent/detect.d.ts.map +1 -0
  9. package/dist/agent/detect.js +187 -0
  10. package/dist/agent/detect.js.map +1 -0
  11. package/dist/agent/drivers/base.d.ts +57 -0
  12. package/dist/agent/drivers/base.d.ts.map +1 -0
  13. package/dist/agent/drivers/base.js +3 -0
  14. package/dist/agent/drivers/base.js.map +1 -0
  15. package/dist/agent/drivers/claude-code.d.ts +14 -0
  16. package/dist/agent/drivers/claude-code.d.ts.map +1 -0
  17. package/dist/agent/drivers/claude-code.js +112 -0
  18. package/dist/agent/drivers/claude-code.js.map +1 -0
  19. package/dist/agent/drivers/codex.d.ts +14 -0
  20. package/dist/agent/drivers/codex.d.ts.map +1 -0
  21. package/dist/agent/drivers/codex.js +77 -0
  22. package/dist/agent/drivers/codex.js.map +1 -0
  23. package/dist/agent/drivers/gemini.d.ts +24 -0
  24. package/dist/agent/drivers/gemini.d.ts.map +1 -0
  25. package/dist/agent/drivers/gemini.js +142 -0
  26. package/dist/agent/drivers/gemini.js.map +1 -0
  27. package/dist/agent/drivers/opencode.d.ts +18 -0
  28. package/dist/agent/drivers/opencode.d.ts.map +1 -0
  29. package/dist/agent/drivers/opencode.js +50 -0
  30. package/dist/agent/drivers/opencode.js.map +1 -0
  31. package/dist/agent/drivers/shell.d.ts +13 -0
  32. package/dist/agent/drivers/shell.d.ts.map +1 -0
  33. package/dist/agent/drivers/shell.js +30 -0
  34. package/dist/agent/drivers/shell.js.map +1 -0
  35. package/dist/agent/env-isolation.d.ts +26 -0
  36. package/dist/agent/env-isolation.d.ts.map +1 -0
  37. package/dist/agent/env-isolation.js +103 -0
  38. package/dist/agent/env-isolation.js.map +1 -0
  39. package/dist/agent/notify-setup.d.ts +18 -0
  40. package/dist/agent/notify-setup.d.ts.map +1 -0
  41. package/dist/agent/notify-setup.js +68 -0
  42. package/dist/agent/notify-setup.js.map +1 -0
  43. package/dist/agent/session-manager.d.ts +75 -0
  44. package/dist/agent/session-manager.d.ts.map +1 -0
  45. package/dist/agent/session-manager.js +407 -0
  46. package/dist/agent/session-manager.js.map +1 -0
  47. package/dist/agent/signal.d.ts +32 -0
  48. package/dist/agent/signal.d.ts.map +1 -0
  49. package/dist/agent/signal.js +199 -0
  50. package/dist/agent/signal.js.map +1 -0
  51. package/dist/agent/status-poller.d.ts +27 -0
  52. package/dist/agent/status-poller.d.ts.map +1 -0
  53. package/dist/agent/status-poller.js +76 -0
  54. package/dist/agent/status-poller.js.map +1 -0
  55. package/dist/agent/templates/brain-prompt.d.ts +14 -0
  56. package/dist/agent/templates/brain-prompt.d.ts.map +1 -0
  57. package/dist/agent/templates/brain-prompt.js +57 -0
  58. package/dist/agent/templates/brain-prompt.js.map +1 -0
  59. package/dist/agent/templates/identity.d.ts +19 -0
  60. package/dist/agent/templates/identity.d.ts.map +1 -0
  61. package/dist/agent/templates/identity.js +97 -0
  62. package/dist/agent/templates/identity.js.map +1 -0
  63. package/dist/agent/tmux.d.ts +90 -0
  64. package/dist/agent/tmux.d.ts.map +1 -0
  65. package/dist/agent/tmux.js +386 -0
  66. package/dist/agent/tmux.js.map +1 -0
  67. package/dist/autofix/audit-engine.d.ts +35 -0
  68. package/dist/autofix/audit-engine.d.ts.map +1 -0
  69. package/dist/autofix/audit-engine.js +144 -0
  70. package/dist/autofix/audit-engine.js.map +1 -0
  71. package/dist/autofix/branch-manager.d.ts +44 -0
  72. package/dist/autofix/branch-manager.d.ts.map +1 -0
  73. package/dist/autofix/branch-manager.js +97 -0
  74. package/dist/autofix/branch-manager.js.map +1 -0
  75. package/dist/autofix/decision-engine.d.ts +38 -0
  76. package/dist/autofix/decision-engine.d.ts.map +1 -0
  77. package/dist/autofix/decision-engine.js +115 -0
  78. package/dist/autofix/decision-engine.js.map +1 -0
  79. package/dist/autofix/index.d.ts +23 -0
  80. package/dist/autofix/index.d.ts.map +1 -0
  81. package/dist/autofix/index.js +192 -0
  82. package/dist/autofix/index.js.map +1 -0
  83. package/dist/autofix/prompt-builder.d.ts +25 -0
  84. package/dist/autofix/prompt-builder.d.ts.map +1 -0
  85. package/dist/autofix/prompt-builder.js +137 -0
  86. package/dist/autofix/prompt-builder.js.map +1 -0
  87. package/dist/autofix/report-parser.d.ts +18 -0
  88. package/dist/autofix/report-parser.d.ts.map +1 -0
  89. package/dist/autofix/report-parser.js +74 -0
  90. package/dist/autofix/report-parser.js.map +1 -0
  91. package/dist/autofix/state-machine.d.ts +40 -0
  92. package/dist/autofix/state-machine.d.ts.map +1 -0
  93. package/dist/autofix/state-machine.js +76 -0
  94. package/dist/autofix/state-machine.js.map +1 -0
  95. package/dist/bind/bind-flow.d.ts +15 -0
  96. package/dist/bind/bind-flow.d.ts.map +1 -0
  97. package/dist/bind/bind-flow.js +198 -0
  98. package/dist/bind/bind-flow.js.map +1 -0
  99. package/dist/config.d.ts +53 -0
  100. package/dist/config.d.ts.map +1 -0
  101. package/dist/config.js +89 -0
  102. package/dist/config.js.map +1 -0
  103. package/dist/daemon/codex-watcher.d.ts +46 -0
  104. package/dist/daemon/codex-watcher.d.ts.map +1 -0
  105. package/dist/daemon/codex-watcher.js +533 -0
  106. package/dist/daemon/codex-watcher.js.map +1 -0
  107. package/dist/daemon/command-handler.d.ts +6 -0
  108. package/dist/daemon/command-handler.d.ts.map +1 -0
  109. package/dist/daemon/command-handler.js +770 -0
  110. package/dist/daemon/command-handler.js.map +1 -0
  111. package/dist/daemon/discussion-orchestrator.d.ts +63 -0
  112. package/dist/daemon/discussion-orchestrator.d.ts.map +1 -0
  113. package/dist/daemon/discussion-orchestrator.js +482 -0
  114. package/dist/daemon/discussion-orchestrator.js.map +1 -0
  115. package/dist/daemon/gemini-watcher.d.ts +42 -0
  116. package/dist/daemon/gemini-watcher.d.ts.map +1 -0
  117. package/dist/daemon/gemini-watcher.js +463 -0
  118. package/dist/daemon/gemini-watcher.js.map +1 -0
  119. package/dist/daemon/hook-server.d.ts +42 -0
  120. package/dist/daemon/hook-server.d.ts.map +1 -0
  121. package/dist/daemon/hook-server.js +160 -0
  122. package/dist/daemon/hook-server.js.map +1 -0
  123. package/dist/daemon/jsonl-watcher.d.ts +35 -0
  124. package/dist/daemon/jsonl-watcher.d.ts.map +1 -0
  125. package/dist/daemon/jsonl-watcher.js +635 -0
  126. package/dist/daemon/jsonl-watcher.js.map +1 -0
  127. package/dist/daemon/lifecycle.d.ts +20 -0
  128. package/dist/daemon/lifecycle.d.ts.map +1 -0
  129. package/dist/daemon/lifecycle.js +331 -0
  130. package/dist/daemon/lifecycle.js.map +1 -0
  131. package/dist/daemon/server-link.d.ts +44 -0
  132. package/dist/daemon/server-link.d.ts.map +1 -0
  133. package/dist/daemon/server-link.js +232 -0
  134. package/dist/daemon/server-link.js.map +1 -0
  135. package/dist/daemon/subsession-manager.d.ts +37 -0
  136. package/dist/daemon/subsession-manager.d.ts.map +1 -0
  137. package/dist/daemon/subsession-manager.js +240 -0
  138. package/dist/daemon/subsession-manager.js.map +1 -0
  139. package/dist/daemon/terminal-parser.d.ts +42 -0
  140. package/dist/daemon/terminal-parser.d.ts.map +1 -0
  141. package/dist/daemon/terminal-parser.js +278 -0
  142. package/dist/daemon/terminal-parser.js.map +1 -0
  143. package/dist/daemon/terminal-streamer.d.ts +93 -0
  144. package/dist/daemon/terminal-streamer.d.ts.map +1 -0
  145. package/dist/daemon/terminal-streamer.js +451 -0
  146. package/dist/daemon/terminal-streamer.js.map +1 -0
  147. package/dist/daemon/timeline-emitter.d.ts +32 -0
  148. package/dist/daemon/timeline-emitter.d.ts.map +1 -0
  149. package/dist/daemon/timeline-emitter.js +97 -0
  150. package/dist/daemon/timeline-emitter.js.map +1 -0
  151. package/dist/daemon/timeline-event.d.ts +23 -0
  152. package/dist/daemon/timeline-event.d.ts.map +1 -0
  153. package/dist/daemon/timeline-event.js +7 -0
  154. package/dist/daemon/timeline-event.js.map +1 -0
  155. package/dist/daemon/timeline-store.d.ts +40 -0
  156. package/dist/daemon/timeline-store.d.ts.map +1 -0
  157. package/dist/daemon/timeline-store.js +153 -0
  158. package/dist/daemon/timeline-store.js.map +1 -0
  159. package/dist/index.d.ts +3 -0
  160. package/dist/index.d.ts.map +1 -0
  161. package/dist/index.js +149 -0
  162. package/dist/index.js.map +1 -0
  163. package/dist/memory/claude-mem.d.ts +9 -0
  164. package/dist/memory/claude-mem.d.ts.map +1 -0
  165. package/dist/memory/claude-mem.js +58 -0
  166. package/dist/memory/claude-mem.js.map +1 -0
  167. package/dist/memory/context-builder.d.ts +4 -0
  168. package/dist/memory/context-builder.d.ts.map +1 -0
  169. package/dist/memory/context-builder.js +35 -0
  170. package/dist/memory/context-builder.js.map +1 -0
  171. package/dist/memory/detector.d.ts +7 -0
  172. package/dist/memory/detector.d.ts.map +1 -0
  173. package/dist/memory/detector.js +17 -0
  174. package/dist/memory/detector.js.map +1 -0
  175. package/dist/memory/extractor.d.ts +21 -0
  176. package/dist/memory/extractor.d.ts.map +1 -0
  177. package/dist/memory/extractor.js +83 -0
  178. package/dist/memory/extractor.js.map +1 -0
  179. package/dist/memory/injector.d.ts +7 -0
  180. package/dist/memory/injector.d.ts.map +1 -0
  181. package/dist/memory/injector.js +18 -0
  182. package/dist/memory/injector.js.map +1 -0
  183. package/dist/memory/interface.d.ts +25 -0
  184. package/dist/memory/interface.d.ts.map +1 -0
  185. package/dist/memory/interface.js +3 -0
  186. package/dist/memory/interface.js.map +1 -0
  187. package/dist/memory/mem0.d.ts +12 -0
  188. package/dist/memory/mem0.d.ts.map +1 -0
  189. package/dist/memory/mem0.js +93 -0
  190. package/dist/memory/mem0.js.map +1 -0
  191. package/dist/router/command-parser.d.ts +33 -0
  192. package/dist/router/command-parser.d.ts.map +1 -0
  193. package/dist/router/command-parser.js +66 -0
  194. package/dist/router/command-parser.js.map +1 -0
  195. package/dist/router/message-router.d.ts +42 -0
  196. package/dist/router/message-router.d.ts.map +1 -0
  197. package/dist/router/message-router.js +222 -0
  198. package/dist/router/message-router.js.map +1 -0
  199. package/dist/router/response-collector.d.ts +28 -0
  200. package/dist/router/response-collector.d.ts.map +1 -0
  201. package/dist/router/response-collector.js +164 -0
  202. package/dist/router/response-collector.js.map +1 -0
  203. package/dist/store/project-store.d.ts +37 -0
  204. package/dist/store/project-store.d.ts.map +1 -0
  205. package/dist/store/project-store.js +70 -0
  206. package/dist/store/project-store.js.map +1 -0
  207. package/dist/store/session-store.d.ts +32 -0
  208. package/dist/store/session-store.d.ts.map +1 -0
  209. package/dist/store/session-store.js +67 -0
  210. package/dist/store/session-store.js.map +1 -0
  211. package/dist/tracker/branch.d.ts +24 -0
  212. package/dist/tracker/branch.d.ts.map +1 -0
  213. package/dist/tracker/branch.js +55 -0
  214. package/dist/tracker/branch.js.map +1 -0
  215. package/dist/tracker/github.d.ts +31 -0
  216. package/dist/tracker/github.d.ts.map +1 -0
  217. package/dist/tracker/github.js +117 -0
  218. package/dist/tracker/github.js.map +1 -0
  219. package/dist/tracker/gitlab.d.ts +31 -0
  220. package/dist/tracker/gitlab.d.ts.map +1 -0
  221. package/dist/tracker/gitlab.js +116 -0
  222. package/dist/tracker/gitlab.js.map +1 -0
  223. package/dist/tracker/index.d.ts +9 -0
  224. package/dist/tracker/index.d.ts.map +1 -0
  225. package/dist/tracker/index.js +28 -0
  226. package/dist/tracker/index.js.map +1 -0
  227. package/dist/tracker/interface.d.ts +39 -0
  228. package/dist/tracker/interface.d.ts.map +1 -0
  229. package/dist/tracker/interface.js +7 -0
  230. package/dist/tracker/interface.js.map +1 -0
  231. package/dist/tracker/priority.d.ts +19 -0
  232. package/dist/tracker/priority.d.ts.map +1 -0
  233. package/dist/tracker/priority.js +40 -0
  234. package/dist/tracker/priority.js.map +1 -0
  235. package/dist/util/logger.d.ts +4 -0
  236. package/dist/util/logger.d.ts.map +1 -0
  237. package/dist/util/logger.js +14 -0
  238. package/dist/util/logger.js.map +1 -0
  239. package/package.json +65 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;QACrB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,EAAE;QACN,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,OAAO,EAAE;QACP,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,EAAE,CAAC,EAAE;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,GAAG,EAAE;QACH,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;CACH;AAgDD,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CA8BlD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
package/dist/config.js ADDED
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = loadConfig;
7
+ exports.resetConfigCache = resetConfigCache;
8
+ const promises_1 = require("fs/promises");
9
+ const os_1 = require("os");
10
+ const path_1 = require("path");
11
+ const yaml_1 = __importDefault(require("yaml"));
12
+ const DEFAULT_CONFIG_PATH = (0, path_1.join)(__dirname, '..', 'config', 'default.yaml');
13
+ function userConfigPath() {
14
+ return (0, path_1.join)((0, os_1.homedir)(), '.codedeck', 'config.yaml');
15
+ }
16
+ /** Expand ${ENV_VAR} and ${ENV_VAR:-default} patterns in string values */
17
+ function expandEnvVars(value) {
18
+ return value.replace(/\$\{([^}]+)\}/g, (_, expr) => {
19
+ const [name, fallback] = expr.split(':-');
20
+ return process.env[name.trim()] ?? fallback ?? '';
21
+ });
22
+ }
23
+ function expandConfig(obj) {
24
+ if (typeof obj === 'string')
25
+ return expandEnvVars(obj);
26
+ if (Array.isArray(obj))
27
+ return obj.map(expandConfig);
28
+ if (obj && typeof obj === 'object') {
29
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, expandConfig(v)]));
30
+ }
31
+ return obj;
32
+ }
33
+ function expandPaths(obj) {
34
+ if (typeof obj === 'string' && obj.startsWith('~/')) {
35
+ return (0, path_1.join)((0, os_1.homedir)(), obj.slice(2));
36
+ }
37
+ if (Array.isArray(obj))
38
+ return obj.map(expandPaths);
39
+ if (obj && typeof obj === 'object') {
40
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, expandPaths(v)]));
41
+ }
42
+ return obj;
43
+ }
44
+ function deepMerge(base, override) {
45
+ const result = { ...base };
46
+ for (const [k, v] of Object.entries(override)) {
47
+ if (v && typeof v === 'object' && !Array.isArray(v) && result[k] && typeof result[k] === 'object') {
48
+ result[k] = deepMerge(result[k], v);
49
+ }
50
+ else {
51
+ result[k] = v;
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ let cachedConfig = null;
57
+ async function loadConfig() {
58
+ if (cachedConfig)
59
+ return cachedConfig;
60
+ const defaultRaw = await (0, promises_1.readFile)(DEFAULT_CONFIG_PATH, 'utf8');
61
+ let config = yaml_1.default.parse(defaultRaw);
62
+ // Map server section to cf on defaults before user merge
63
+ if (config.server) {
64
+ const s = config.server;
65
+ config.cf = {
66
+ workerUrl: s.cfWorkerUrl,
67
+ apiKey: s.cfApiKey ?? s.apiKey,
68
+ credentialsPath: s.credentialsPath ?? (0, path_1.join)((0, os_1.homedir)(), '.codedeck', 'server.json'),
69
+ };
70
+ delete config.server;
71
+ }
72
+ try {
73
+ const userRaw = await (0, promises_1.readFile)(userConfigPath(), 'utf8');
74
+ const userConfig = yaml_1.default.parse(userRaw);
75
+ if (userConfig)
76
+ config = deepMerge(config, userConfig);
77
+ }
78
+ catch {
79
+ // No user config — use defaults
80
+ }
81
+ config = expandConfig(config);
82
+ config = expandPaths(config);
83
+ cachedConfig = config;
84
+ return cachedConfig;
85
+ }
86
+ function resetConfigCache() {
87
+ cachedConfig = null;
88
+ }
89
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Watches Codex JSONL rollout files for structured events.
3
+ *
4
+ * Codex writes per-session rollout files to:
5
+ * ~/.codex/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl
6
+ *
7
+ * The first line of each file is a "session_meta" record whose payload.cwd
8
+ * identifies the project directory. We match files to codedeck sessions by
9
+ * comparing payload.cwd to the session's workDir.
10
+ *
11
+ * Events emitted:
12
+ * - user.message ← event_msg { type: "user_message", message: "..." }
13
+ * - assistant.text ← event_msg { type: "agent_message", phase: "final_answer", message: "..." }
14
+ *
15
+ * Integration:
16
+ * - startWatching(sessionName, workDir) when a codex session starts
17
+ * - stopWatching(sessionName) when it stops
18
+ */
19
+ /**
20
+ * Read the first line of a rollout file and return payload.cwd if it's a
21
+ * session_meta record, otherwise null.
22
+ * Exported for testing.
23
+ */
24
+ export declare function readCwd(filePath: string): Promise<string | null>;
25
+ /** Exported for testing. */
26
+ export declare function parseLine(sessionName: string, line: string, model?: string): void;
27
+ /**
28
+ * Scan the last 30 days of session dirs for a rollout file whose session_meta.cwd matches
29
+ * workDir and whose mtime is > since. Returns the UUID from the filename, or null if not found.
30
+ * Polls every 1s for up to 60s.
31
+ */
32
+ export declare function extractNewRolloutUuid(workDir: string, since: number): Promise<string | null>;
33
+ /**
34
+ * Find the full path of a rollout file by UUID, scanning the last 30 days.
35
+ * Returns null if not found.
36
+ */
37
+ export declare function findRolloutPathByUuid(uuid: string): Promise<string | null>;
38
+ export declare function startWatching(sessionName: string, workDir: string, model?: string): Promise<void>;
39
+ export declare function isWatching(sessionName: string): boolean;
40
+ /**
41
+ * Watch a specific rollout file directly (used when UUID is already known).
42
+ * The file is expected to already exist.
43
+ */
44
+ export declare function startWatchingSpecificFile(sessionName: string, filePath: string, model?: string): Promise<void>;
45
+ export declare function stopWatching(sessionName: string): void;
46
+ //# sourceMappingURL=codex-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-watcher.d.ts","sourceRoot":"","sources":["../../src/daemon/codex-watcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+BH;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBtE;AAkDD,4BAA4B;AAC5B,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAoDjF;AAyGD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkClG;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBhF;AAID,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDvG;AAED,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCpH;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAWtD"}
@@ -0,0 +1,533 @@
1
+ "use strict";
2
+ /**
3
+ * Watches Codex JSONL rollout files for structured events.
4
+ *
5
+ * Codex writes per-session rollout files to:
6
+ * ~/.codex/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl
7
+ *
8
+ * The first line of each file is a "session_meta" record whose payload.cwd
9
+ * identifies the project directory. We match files to codedeck sessions by
10
+ * comparing payload.cwd to the session's workDir.
11
+ *
12
+ * Events emitted:
13
+ * - user.message ← event_msg { type: "user_message", message: "..." }
14
+ * - assistant.text ← event_msg { type: "agent_message", phase: "final_answer", message: "..." }
15
+ *
16
+ * Integration:
17
+ * - startWatching(sessionName, workDir) when a codex session starts
18
+ * - stopWatching(sessionName) when it stops
19
+ */
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.readCwd = readCwd;
25
+ exports.parseLine = parseLine;
26
+ exports.extractNewRolloutUuid = extractNewRolloutUuid;
27
+ exports.findRolloutPathByUuid = findRolloutPathByUuid;
28
+ exports.startWatching = startWatching;
29
+ exports.isWatching = isWatching;
30
+ exports.startWatchingSpecificFile = startWatchingSpecificFile;
31
+ exports.stopWatching = stopWatching;
32
+ const promises_1 = require("fs/promises");
33
+ const path_1 = require("path");
34
+ const os_1 = require("os");
35
+ const timeline_emitter_js_1 = require("./timeline-emitter.js");
36
+ const logger_js_1 = __importDefault(require("../util/logger.js"));
37
+ // ── Path helpers ───────────────────────────────────────────────────────────────
38
+ /** Return ~/.codex/sessions/YYYY/MM/DD for a given Date. */
39
+ function codexSessionDir(d) {
40
+ const yyyy = d.getUTCFullYear();
41
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
42
+ const dd = String(d.getUTCDate()).padStart(2, '0');
43
+ return (0, path_1.join)((0, os_1.homedir)(), '.codex', 'sessions', String(yyyy), mm, dd);
44
+ }
45
+ /** Return the last 30 days of session dirs (newest first). */
46
+ function recentSessionDirs() {
47
+ const dirs = [];
48
+ for (let i = 0; i < 30; i++) {
49
+ const d = new Date(Date.now() - i * 86_400_000);
50
+ dirs.push(codexSessionDir(d));
51
+ }
52
+ return dirs;
53
+ }
54
+ // ── JSONL matching ─────────────────────────────────────────────────────────────
55
+ /**
56
+ * Read the first line of a rollout file and return payload.cwd if it's a
57
+ * session_meta record, otherwise null.
58
+ * Exported for testing.
59
+ */
60
+ async function readCwd(filePath) {
61
+ let fh = null;
62
+ try {
63
+ fh = await (0, promises_1.open)(filePath, 'r');
64
+ // The session_meta first line can be very large (includes full conversation context).
65
+ // Read only the first 4KB — enough to find the "cwd" field which appears early.
66
+ // We extract cwd via regex instead of full JSON.parse to avoid truncation issues.
67
+ const buf = Buffer.allocUnsafe(4096);
68
+ const { bytesRead } = await fh.read(buf, 0, 4096, 0);
69
+ if (bytesRead === 0)
70
+ return null;
71
+ const snippet = buf.subarray(0, bytesRead).toString('utf8');
72
+ // Verify this is a session_meta line
73
+ if (!snippet.includes('"session_meta"'))
74
+ return null;
75
+ // Extract "cwd":"..." value — cwd paths don't contain quotes or backslashes
76
+ const m = /"cwd"\s*:\s*"([^"]+)"/.exec(snippet);
77
+ return m ? m[1] : null;
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ finally {
83
+ if (fh)
84
+ await fh.close().catch(() => { });
85
+ }
86
+ }
87
+ /**
88
+ * Find the most recent rollout-*.jsonl in dir whose session_meta.cwd matches workDir.
89
+ * Returns the file path, or null if none found.
90
+ */
91
+ async function findLatestRollout(dir, workDir) {
92
+ let entries;
93
+ try {
94
+ entries = await (0, promises_1.readdir)(dir);
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ const rollouts = entries.filter((e) => e.startsWith('rollout-') && e.endsWith('.jsonl'));
100
+ if (rollouts.length === 0)
101
+ return null;
102
+ // Sort newest first by filename (timestamps embedded in name)
103
+ rollouts.sort((a, b) => b.localeCompare(a));
104
+ for (const name of rollouts) {
105
+ const fpath = (0, path_1.join)(dir, name);
106
+ const cwd = await readCwd(fpath);
107
+ if (cwd && normalizePath(cwd) === normalizePath(workDir)) {
108
+ return fpath;
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ function normalizePath(p) {
114
+ return p.replace(/\/+$/, '');
115
+ }
116
+ // ── JSONL parsing ──────────────────────────────────────────────────────────────
117
+ // Debounce buffers for streaming final_answer events.
118
+ // Codex emits a new final_answer snapshot on every token; we only want the last one.
119
+ const finalAnswerBuffers = new Map();
120
+ const FINAL_ANSWER_DEBOUNCE_MS = 600;
121
+ function flushFinalAnswer(sessionName) {
122
+ const buf = finalAnswerBuffers.get(sessionName);
123
+ if (!buf)
124
+ return;
125
+ finalAnswerBuffers.delete(sessionName);
126
+ timeline_emitter_js_1.timelineEmitter.emit(sessionName, 'assistant.text', { text: buf.text, streaming: false }, { source: 'daemon', confidence: 'high' });
127
+ }
128
+ /** Exported for testing. */
129
+ function parseLine(sessionName, line, model) {
130
+ if (!line.trim())
131
+ return;
132
+ let raw;
133
+ try {
134
+ raw = JSON.parse(line);
135
+ }
136
+ catch {
137
+ return;
138
+ }
139
+ if (raw['type'] !== 'event_msg')
140
+ return;
141
+ const payload = raw['payload'];
142
+ if (!payload)
143
+ return;
144
+ const evtType = payload['type'];
145
+ if (evtType === 'token_count') {
146
+ const info = payload['info'];
147
+ const last = info?.['last_token_usage'];
148
+ const ctxWin = info?.['model_context_window'] ?? 1_000_000;
149
+ if (last && typeof last['input_tokens'] === 'number') {
150
+ timeline_emitter_js_1.timelineEmitter.emit(sessionName, 'usage.update', {
151
+ inputTokens: last['input_tokens'],
152
+ cacheTokens: last['cached_input_tokens'] ?? 0,
153
+ contextWindow: ctxWin,
154
+ ...(model ? { model } : {}),
155
+ }, { source: 'daemon', confidence: 'high' });
156
+ }
157
+ return;
158
+ }
159
+ if (evtType === 'user_message') {
160
+ // Flush any pending assistant text before a new user message
161
+ flushFinalAnswer(sessionName);
162
+ const text = payload['message'];
163
+ if (text?.trim()) {
164
+ timeline_emitter_js_1.timelineEmitter.emit(sessionName, 'user.message', { text }, { source: 'daemon', confidence: 'high' });
165
+ }
166
+ return;
167
+ }
168
+ if (evtType === 'agent_message' && payload['phase'] === 'final_answer') {
169
+ const text = payload['message'];
170
+ if (!text?.trim())
171
+ return;
172
+ // Debounce: buffer the latest snapshot and reset the timer
173
+ const existing = finalAnswerBuffers.get(sessionName);
174
+ if (existing)
175
+ clearTimeout(existing.timer);
176
+ const timer = setTimeout(() => flushFinalAnswer(sessionName), FINAL_ANSWER_DEBOUNCE_MS);
177
+ finalAnswerBuffers.set(sessionName, { text, timer });
178
+ }
179
+ }
180
+ // ── History replay ─────────────────────────────────────────────────────────────
181
+ const HISTORY_LINES = 200;
182
+ async function emitRecentHistory(sessionName, filePath, model) {
183
+ let fh = null;
184
+ try {
185
+ fh = await (0, promises_1.open)(filePath, 'r');
186
+ const { size } = await fh.stat();
187
+ if (size === 0)
188
+ return;
189
+ const readSize = Math.min(size, 256 * 1024);
190
+ const buf = Buffer.allocUnsafe(readSize);
191
+ const { bytesRead } = await fh.read(buf, 0, readSize, size - readSize);
192
+ if (bytesRead === 0)
193
+ return;
194
+ const chunk = buf.subarray(0, bytesRead).toString('utf8');
195
+ const lines = chunk.split('\n');
196
+ const startIdx = size > readSize ? 1 : 0; // skip possible partial first line
197
+ const historyEvents = [];
198
+ let lastTokenPayload = null;
199
+ for (let i = startIdx; i < lines.length; i++) {
200
+ const line = lines[i];
201
+ if (!line.trim())
202
+ continue;
203
+ let raw;
204
+ try {
205
+ raw = JSON.parse(line);
206
+ }
207
+ catch {
208
+ continue;
209
+ }
210
+ if (raw['type'] !== 'event_msg')
211
+ continue;
212
+ const payload = raw['payload'];
213
+ if (!payload)
214
+ continue;
215
+ const evtType = payload['type'];
216
+ if (evtType === 'user_message') {
217
+ const text = payload['message'];
218
+ if (text?.trim())
219
+ historyEvents.push({ type: 'user', text });
220
+ }
221
+ else if (evtType === 'agent_message' && payload['phase'] === 'final_answer') {
222
+ const text = payload['message'];
223
+ if (!text?.trim())
224
+ continue;
225
+ const last = historyEvents[historyEvents.length - 1];
226
+ if (last?.type === 'assistant') {
227
+ last.text = text;
228
+ }
229
+ else {
230
+ historyEvents.push({ type: 'assistant', text });
231
+ }
232
+ }
233
+ else if (evtType === 'token_count') {
234
+ const info = payload['info'];
235
+ const last = info?.['last_token_usage'];
236
+ const ctxWin = info?.['model_context_window'] ?? 1_000_000;
237
+ if (last && typeof last['input_tokens'] === 'number') {
238
+ lastTokenPayload = {
239
+ inputTokens: last['input_tokens'],
240
+ cacheTokens: last['cached_input_tokens'] ?? 0,
241
+ contextWindow: ctxWin,
242
+ };
243
+ }
244
+ }
245
+ }
246
+ // Emit deduplicated history (most recent HISTORY_LINES events)
247
+ const slice = historyEvents.slice(-HISTORY_LINES);
248
+ for (const ev of slice) {
249
+ if (ev.type === 'user') {
250
+ timeline_emitter_js_1.timelineEmitter.emit(sessionName, 'user.message', { text: ev.text }, { source: 'daemon', confidence: 'high' });
251
+ }
252
+ else {
253
+ timeline_emitter_js_1.timelineEmitter.emit(sessionName, 'assistant.text', { text: ev.text, streaming: false }, { source: 'daemon', confidence: 'high' });
254
+ }
255
+ }
256
+ // Emit last usage snapshot so the context bar populates on load
257
+ if (lastTokenPayload) {
258
+ timeline_emitter_js_1.timelineEmitter.emit(sessionName, 'usage.update', { ...lastTokenPayload, ...(model ? { model } : {}) }, { source: 'daemon', confidence: 'high' });
259
+ }
260
+ }
261
+ catch {
262
+ // best-effort
263
+ }
264
+ finally {
265
+ if (fh)
266
+ await fh.close().catch(() => { });
267
+ }
268
+ }
269
+ const watchers = new Map();
270
+ // ── UUID extraction helpers ────────────────────────────────────────────────────
271
+ const UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/;
272
+ /**
273
+ * Scan the last 30 days of session dirs for a rollout file whose session_meta.cwd matches
274
+ * workDir and whose mtime is > since. Returns the UUID from the filename, or null if not found.
275
+ * Polls every 1s for up to 60s.
276
+ */
277
+ async function extractNewRolloutUuid(workDir, since) {
278
+ for (let attempt = 0; attempt < 60; attempt++) {
279
+ for (const dir of recentSessionDirs()) {
280
+ let entries;
281
+ try {
282
+ entries = await (0, promises_1.readdir)(dir);
283
+ }
284
+ catch {
285
+ continue;
286
+ }
287
+ const rollouts = entries.filter((e) => e.startsWith('rollout-') && e.endsWith('.jsonl'));
288
+ for (const filename of rollouts) {
289
+ const uuidMatch = UUID_RE.exec(filename);
290
+ if (!uuidMatch)
291
+ continue;
292
+ const fpath = (0, path_1.join)(dir, filename);
293
+ try {
294
+ const s = await (0, promises_1.stat)(fpath);
295
+ if (s.mtimeMs <= since)
296
+ continue;
297
+ }
298
+ catch {
299
+ continue;
300
+ }
301
+ const cwd = await readCwd(fpath);
302
+ if (cwd && normalizePath(cwd) === normalizePath(workDir)) {
303
+ return uuidMatch[1];
304
+ }
305
+ }
306
+ }
307
+ await new Promise((r) => setTimeout(r, 1000));
308
+ }
309
+ return null;
310
+ }
311
+ /**
312
+ * Find the full path of a rollout file by UUID, scanning the last 30 days.
313
+ * Returns null if not found.
314
+ */
315
+ async function findRolloutPathByUuid(uuid) {
316
+ for (const dir of recentSessionDirs()) {
317
+ let entries;
318
+ try {
319
+ entries = await (0, promises_1.readdir)(dir);
320
+ }
321
+ catch {
322
+ continue;
323
+ }
324
+ for (const filename of entries) {
325
+ if (!filename.startsWith('rollout-') || !filename.endsWith('.jsonl'))
326
+ continue;
327
+ const uuidMatch = UUID_RE.exec(filename);
328
+ if (uuidMatch && uuidMatch[1] === uuid) {
329
+ return (0, path_1.join)(dir, filename);
330
+ }
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+ // ── Public API ─────────────────────────────────────────────────────────────────
336
+ async function startWatching(sessionName, workDir, model) {
337
+ if (watchers.has(sessionName)) {
338
+ stopWatching(sessionName);
339
+ }
340
+ const state = {
341
+ workDir,
342
+ activeFile: null,
343
+ fileOffset: 0,
344
+ abort: new AbortController(),
345
+ stopped: false,
346
+ ...(model ? { model } : {}),
347
+ };
348
+ watchers.set(sessionName, state);
349
+ // Search recent dirs for existing rollout matching workDir
350
+ for (const dir of recentSessionDirs()) {
351
+ const found = await findLatestRollout(dir, workDir);
352
+ if (found) {
353
+ try {
354
+ const s = await (0, promises_1.stat)(found);
355
+ state.activeFile = found;
356
+ state.fileOffset = s.size;
357
+ await emitRecentHistory(sessionName, found, state.model);
358
+ }
359
+ catch {
360
+ state.activeFile = found;
361
+ state.fileOffset = 0;
362
+ }
363
+ break;
364
+ }
365
+ }
366
+ // Poll every 2s as fallback (fs.watch on macOS misses file appends)
367
+ state.pollTimer = setInterval(() => {
368
+ void drainNewLines(sessionName, state);
369
+ }, 2000);
370
+ // Watch all recent dirs for new/modified rollout files.
371
+ // Only start a watcher for dirs that exist (or today's dir which Codex may create soon).
372
+ const todayDir = codexSessionDir(new Date());
373
+ for (const dir of recentSessionDirs()) {
374
+ const isToday = dir === todayDir;
375
+ if (!isToday) {
376
+ // Skip non-existent historical dirs to avoid WARN spam
377
+ try {
378
+ await (0, promises_1.stat)(dir);
379
+ }
380
+ catch {
381
+ continue;
382
+ }
383
+ }
384
+ void watchDir(sessionName, state, dir);
385
+ }
386
+ }
387
+ function isWatching(sessionName) {
388
+ return watchers.has(sessionName);
389
+ }
390
+ /**
391
+ * Watch a specific rollout file directly (used when UUID is already known).
392
+ * The file is expected to already exist.
393
+ */
394
+ async function startWatchingSpecificFile(sessionName, filePath, model) {
395
+ if (watchers.has(sessionName)) {
396
+ stopWatching(sessionName);
397
+ }
398
+ let fileSize = 0;
399
+ try {
400
+ const s = await (0, promises_1.stat)(filePath);
401
+ fileSize = s.size;
402
+ }
403
+ catch {
404
+ // file may not exist yet — start from 0
405
+ }
406
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
407
+ const state = {
408
+ workDir: dir,
409
+ activeFile: filePath,
410
+ fileOffset: fileSize,
411
+ abort: new AbortController(),
412
+ stopped: false,
413
+ ...(model ? { model } : {}),
414
+ };
415
+ watchers.set(sessionName, state);
416
+ await emitRecentHistory(sessionName, filePath, state.model);
417
+ // Poll every 2s as fallback
418
+ state.pollTimer = setInterval(() => {
419
+ void drainNewLines(sessionName, state);
420
+ }, 2000);
421
+ // Watch the parent dir for changes to this specific file
422
+ void watchDir(sessionName, state, dir);
423
+ }
424
+ function stopWatching(sessionName) {
425
+ const state = watchers.get(sessionName);
426
+ if (!state)
427
+ return;
428
+ state.stopped = true;
429
+ state.abort.abort();
430
+ if (state.pollTimer)
431
+ clearInterval(state.pollTimer);
432
+ watchers.delete(sessionName);
433
+ // Flush any buffered final_answer on stop
434
+ flushFinalAnswer(sessionName);
435
+ const buf = finalAnswerBuffers.get(sessionName);
436
+ if (buf) {
437
+ clearTimeout(buf.timer);
438
+ finalAnswerBuffers.delete(sessionName);
439
+ }
440
+ }
441
+ // ── Internal watcher logic ─────────────────────────────────────────────────────
442
+ async function watchDir(sessionName, state, dir) {
443
+ // Wait for dir to exist (Codex may not have created it yet)
444
+ for (let i = 0; i < 60; i++) {
445
+ if (state.stopped)
446
+ return;
447
+ try {
448
+ await (0, promises_1.stat)(dir);
449
+ break;
450
+ }
451
+ catch {
452
+ await new Promise((r) => setTimeout(r, 1000));
453
+ }
454
+ }
455
+ if (state.stopped)
456
+ return;
457
+ try {
458
+ const watcher = (0, promises_1.watch)(dir, { persistent: false, signal: state.abort.signal });
459
+ for await (const event of watcher) {
460
+ if (state.stopped)
461
+ break;
462
+ if (typeof event.filename !== 'string')
463
+ continue;
464
+ if (!event.filename.startsWith('rollout-') || !event.filename.endsWith('.jsonl'))
465
+ continue;
466
+ const changedFile = (0, path_1.join)(dir, event.filename);
467
+ if (changedFile !== state.activeFile) {
468
+ // New file — check if it matches our workDir and is newer
469
+ const cwd = await readCwd(changedFile);
470
+ if (!cwd || normalizePath(cwd) !== normalizePath(state.workDir))
471
+ continue;
472
+ const isNewer = await checkNewer(changedFile, state.activeFile);
473
+ if (isNewer || !state.activeFile) {
474
+ logger_js_1.default.debug({ sessionName, file: event.filename }, 'codex-watcher: switching to new rollout file');
475
+ state.activeFile = changedFile;
476
+ state.fileOffset = 0;
477
+ }
478
+ else {
479
+ continue;
480
+ }
481
+ }
482
+ await drainNewLines(sessionName, state);
483
+ }
484
+ }
485
+ catch (err) {
486
+ if (!state.stopped) {
487
+ logger_js_1.default.warn({ sessionName, dir, err }, 'codex-watcher: dir watch error');
488
+ }
489
+ }
490
+ }
491
+ async function checkNewer(candidate, current) {
492
+ if (!current)
493
+ return true;
494
+ try {
495
+ const [cs, curS] = await Promise.all([(0, promises_1.stat)(candidate), (0, promises_1.stat)(current)]);
496
+ return cs.mtimeMs > curS.mtimeMs;
497
+ }
498
+ catch {
499
+ return false;
500
+ }
501
+ }
502
+ async function drainNewLines(sessionName, state) {
503
+ if (!state.activeFile)
504
+ return;
505
+ let fh = null;
506
+ try {
507
+ fh = await (0, promises_1.open)(state.activeFile, 'r');
508
+ const fileStat = await fh.stat();
509
+ if (fileStat.size <= state.fileOffset)
510
+ return;
511
+ const buf = Buffer.allocUnsafe(fileStat.size - state.fileOffset);
512
+ const { bytesRead } = await fh.read(buf, 0, buf.length, state.fileOffset);
513
+ if (bytesRead === 0)
514
+ return;
515
+ state.fileOffset += bytesRead;
516
+ const chunk = buf.subarray(0, bytesRead).toString('utf8');
517
+ for (const line of chunk.split('\n')) {
518
+ if (state.stopped)
519
+ break;
520
+ parseLine(sessionName, line, state.model);
521
+ }
522
+ }
523
+ catch (err) {
524
+ if (!state.stopped) {
525
+ logger_js_1.default.debug({ sessionName, err }, 'codex-watcher: drain error');
526
+ }
527
+ }
528
+ finally {
529
+ if (fh)
530
+ await fh.close().catch(() => { });
531
+ }
532
+ }
533
+ //# sourceMappingURL=codex-watcher.js.map