@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.
- package/README.md +38 -0
- package/config/default.yaml +51 -0
- package/dist/agent/brain-dispatcher.d.ts +67 -0
- package/dist/agent/brain-dispatcher.d.ts.map +1 -0
- package/dist/agent/brain-dispatcher.js +136 -0
- package/dist/agent/brain-dispatcher.js.map +1 -0
- package/dist/agent/detect.d.ts +20 -0
- package/dist/agent/detect.d.ts.map +1 -0
- package/dist/agent/detect.js +187 -0
- package/dist/agent/detect.js.map +1 -0
- package/dist/agent/drivers/base.d.ts +57 -0
- package/dist/agent/drivers/base.d.ts.map +1 -0
- package/dist/agent/drivers/base.js +3 -0
- package/dist/agent/drivers/base.js.map +1 -0
- package/dist/agent/drivers/claude-code.d.ts +14 -0
- package/dist/agent/drivers/claude-code.d.ts.map +1 -0
- package/dist/agent/drivers/claude-code.js +112 -0
- package/dist/agent/drivers/claude-code.js.map +1 -0
- package/dist/agent/drivers/codex.d.ts +14 -0
- package/dist/agent/drivers/codex.d.ts.map +1 -0
- package/dist/agent/drivers/codex.js +77 -0
- package/dist/agent/drivers/codex.js.map +1 -0
- package/dist/agent/drivers/gemini.d.ts +24 -0
- package/dist/agent/drivers/gemini.d.ts.map +1 -0
- package/dist/agent/drivers/gemini.js +142 -0
- package/dist/agent/drivers/gemini.js.map +1 -0
- package/dist/agent/drivers/opencode.d.ts +18 -0
- package/dist/agent/drivers/opencode.d.ts.map +1 -0
- package/dist/agent/drivers/opencode.js +50 -0
- package/dist/agent/drivers/opencode.js.map +1 -0
- package/dist/agent/drivers/shell.d.ts +13 -0
- package/dist/agent/drivers/shell.d.ts.map +1 -0
- package/dist/agent/drivers/shell.js +30 -0
- package/dist/agent/drivers/shell.js.map +1 -0
- package/dist/agent/env-isolation.d.ts +26 -0
- package/dist/agent/env-isolation.d.ts.map +1 -0
- package/dist/agent/env-isolation.js +103 -0
- package/dist/agent/env-isolation.js.map +1 -0
- package/dist/agent/notify-setup.d.ts +18 -0
- package/dist/agent/notify-setup.d.ts.map +1 -0
- package/dist/agent/notify-setup.js +68 -0
- package/dist/agent/notify-setup.js.map +1 -0
- package/dist/agent/session-manager.d.ts +75 -0
- package/dist/agent/session-manager.d.ts.map +1 -0
- package/dist/agent/session-manager.js +407 -0
- package/dist/agent/session-manager.js.map +1 -0
- package/dist/agent/signal.d.ts +32 -0
- package/dist/agent/signal.d.ts.map +1 -0
- package/dist/agent/signal.js +199 -0
- package/dist/agent/signal.js.map +1 -0
- package/dist/agent/status-poller.d.ts +27 -0
- package/dist/agent/status-poller.d.ts.map +1 -0
- package/dist/agent/status-poller.js +76 -0
- package/dist/agent/status-poller.js.map +1 -0
- package/dist/agent/templates/brain-prompt.d.ts +14 -0
- package/dist/agent/templates/brain-prompt.d.ts.map +1 -0
- package/dist/agent/templates/brain-prompt.js +57 -0
- package/dist/agent/templates/brain-prompt.js.map +1 -0
- package/dist/agent/templates/identity.d.ts +19 -0
- package/dist/agent/templates/identity.d.ts.map +1 -0
- package/dist/agent/templates/identity.js +97 -0
- package/dist/agent/templates/identity.js.map +1 -0
- package/dist/agent/tmux.d.ts +90 -0
- package/dist/agent/tmux.d.ts.map +1 -0
- package/dist/agent/tmux.js +386 -0
- package/dist/agent/tmux.js.map +1 -0
- package/dist/autofix/audit-engine.d.ts +35 -0
- package/dist/autofix/audit-engine.d.ts.map +1 -0
- package/dist/autofix/audit-engine.js +144 -0
- package/dist/autofix/audit-engine.js.map +1 -0
- package/dist/autofix/branch-manager.d.ts +44 -0
- package/dist/autofix/branch-manager.d.ts.map +1 -0
- package/dist/autofix/branch-manager.js +97 -0
- package/dist/autofix/branch-manager.js.map +1 -0
- package/dist/autofix/decision-engine.d.ts +38 -0
- package/dist/autofix/decision-engine.d.ts.map +1 -0
- package/dist/autofix/decision-engine.js +115 -0
- package/dist/autofix/decision-engine.js.map +1 -0
- package/dist/autofix/index.d.ts +23 -0
- package/dist/autofix/index.d.ts.map +1 -0
- package/dist/autofix/index.js +192 -0
- package/dist/autofix/index.js.map +1 -0
- package/dist/autofix/prompt-builder.d.ts +25 -0
- package/dist/autofix/prompt-builder.d.ts.map +1 -0
- package/dist/autofix/prompt-builder.js +137 -0
- package/dist/autofix/prompt-builder.js.map +1 -0
- package/dist/autofix/report-parser.d.ts +18 -0
- package/dist/autofix/report-parser.d.ts.map +1 -0
- package/dist/autofix/report-parser.js +74 -0
- package/dist/autofix/report-parser.js.map +1 -0
- package/dist/autofix/state-machine.d.ts +40 -0
- package/dist/autofix/state-machine.d.ts.map +1 -0
- package/dist/autofix/state-machine.js +76 -0
- package/dist/autofix/state-machine.js.map +1 -0
- package/dist/bind/bind-flow.d.ts +15 -0
- package/dist/bind/bind-flow.d.ts.map +1 -0
- package/dist/bind/bind-flow.js +198 -0
- package/dist/bind/bind-flow.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +89 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/codex-watcher.d.ts +46 -0
- package/dist/daemon/codex-watcher.d.ts.map +1 -0
- package/dist/daemon/codex-watcher.js +533 -0
- package/dist/daemon/codex-watcher.js.map +1 -0
- package/dist/daemon/command-handler.d.ts +6 -0
- package/dist/daemon/command-handler.d.ts.map +1 -0
- package/dist/daemon/command-handler.js +770 -0
- package/dist/daemon/command-handler.js.map +1 -0
- package/dist/daemon/discussion-orchestrator.d.ts +63 -0
- package/dist/daemon/discussion-orchestrator.d.ts.map +1 -0
- package/dist/daemon/discussion-orchestrator.js +482 -0
- package/dist/daemon/discussion-orchestrator.js.map +1 -0
- package/dist/daemon/gemini-watcher.d.ts +42 -0
- package/dist/daemon/gemini-watcher.d.ts.map +1 -0
- package/dist/daemon/gemini-watcher.js +463 -0
- package/dist/daemon/gemini-watcher.js.map +1 -0
- package/dist/daemon/hook-server.d.ts +42 -0
- package/dist/daemon/hook-server.d.ts.map +1 -0
- package/dist/daemon/hook-server.js +160 -0
- package/dist/daemon/hook-server.js.map +1 -0
- package/dist/daemon/jsonl-watcher.d.ts +35 -0
- package/dist/daemon/jsonl-watcher.d.ts.map +1 -0
- package/dist/daemon/jsonl-watcher.js +635 -0
- package/dist/daemon/jsonl-watcher.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +20 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +331 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/server-link.d.ts +44 -0
- package/dist/daemon/server-link.d.ts.map +1 -0
- package/dist/daemon/server-link.js +232 -0
- package/dist/daemon/server-link.js.map +1 -0
- package/dist/daemon/subsession-manager.d.ts +37 -0
- package/dist/daemon/subsession-manager.d.ts.map +1 -0
- package/dist/daemon/subsession-manager.js +240 -0
- package/dist/daemon/subsession-manager.js.map +1 -0
- package/dist/daemon/terminal-parser.d.ts +42 -0
- package/dist/daemon/terminal-parser.d.ts.map +1 -0
- package/dist/daemon/terminal-parser.js +278 -0
- package/dist/daemon/terminal-parser.js.map +1 -0
- package/dist/daemon/terminal-streamer.d.ts +93 -0
- package/dist/daemon/terminal-streamer.d.ts.map +1 -0
- package/dist/daemon/terminal-streamer.js +451 -0
- package/dist/daemon/terminal-streamer.js.map +1 -0
- package/dist/daemon/timeline-emitter.d.ts +32 -0
- package/dist/daemon/timeline-emitter.d.ts.map +1 -0
- package/dist/daemon/timeline-emitter.js +97 -0
- package/dist/daemon/timeline-emitter.js.map +1 -0
- package/dist/daemon/timeline-event.d.ts +23 -0
- package/dist/daemon/timeline-event.d.ts.map +1 -0
- package/dist/daemon/timeline-event.js +7 -0
- package/dist/daemon/timeline-event.js.map +1 -0
- package/dist/daemon/timeline-store.d.ts +40 -0
- package/dist/daemon/timeline-store.d.ts.map +1 -0
- package/dist/daemon/timeline-store.js +153 -0
- package/dist/daemon/timeline-store.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/claude-mem.d.ts +9 -0
- package/dist/memory/claude-mem.d.ts.map +1 -0
- package/dist/memory/claude-mem.js +58 -0
- package/dist/memory/claude-mem.js.map +1 -0
- package/dist/memory/context-builder.d.ts +4 -0
- package/dist/memory/context-builder.d.ts.map +1 -0
- package/dist/memory/context-builder.js +35 -0
- package/dist/memory/context-builder.js.map +1 -0
- package/dist/memory/detector.d.ts +7 -0
- package/dist/memory/detector.d.ts.map +1 -0
- package/dist/memory/detector.js +17 -0
- package/dist/memory/detector.js.map +1 -0
- package/dist/memory/extractor.d.ts +21 -0
- package/dist/memory/extractor.d.ts.map +1 -0
- package/dist/memory/extractor.js +83 -0
- package/dist/memory/extractor.js.map +1 -0
- package/dist/memory/injector.d.ts +7 -0
- package/dist/memory/injector.d.ts.map +1 -0
- package/dist/memory/injector.js +18 -0
- package/dist/memory/injector.js.map +1 -0
- package/dist/memory/interface.d.ts +25 -0
- package/dist/memory/interface.d.ts.map +1 -0
- package/dist/memory/interface.js +3 -0
- package/dist/memory/interface.js.map +1 -0
- package/dist/memory/mem0.d.ts +12 -0
- package/dist/memory/mem0.d.ts.map +1 -0
- package/dist/memory/mem0.js +93 -0
- package/dist/memory/mem0.js.map +1 -0
- package/dist/router/command-parser.d.ts +33 -0
- package/dist/router/command-parser.d.ts.map +1 -0
- package/dist/router/command-parser.js +66 -0
- package/dist/router/command-parser.js.map +1 -0
- package/dist/router/message-router.d.ts +42 -0
- package/dist/router/message-router.d.ts.map +1 -0
- package/dist/router/message-router.js +222 -0
- package/dist/router/message-router.js.map +1 -0
- package/dist/router/response-collector.d.ts +28 -0
- package/dist/router/response-collector.d.ts.map +1 -0
- package/dist/router/response-collector.js +164 -0
- package/dist/router/response-collector.js.map +1 -0
- package/dist/store/project-store.d.ts +37 -0
- package/dist/store/project-store.d.ts.map +1 -0
- package/dist/store/project-store.js +70 -0
- package/dist/store/project-store.js.map +1 -0
- package/dist/store/session-store.d.ts +32 -0
- package/dist/store/session-store.d.ts.map +1 -0
- package/dist/store/session-store.js +67 -0
- package/dist/store/session-store.js.map +1 -0
- package/dist/tracker/branch.d.ts +24 -0
- package/dist/tracker/branch.d.ts.map +1 -0
- package/dist/tracker/branch.js +55 -0
- package/dist/tracker/branch.js.map +1 -0
- package/dist/tracker/github.d.ts +31 -0
- package/dist/tracker/github.d.ts.map +1 -0
- package/dist/tracker/github.js +117 -0
- package/dist/tracker/github.js.map +1 -0
- package/dist/tracker/gitlab.d.ts +31 -0
- package/dist/tracker/gitlab.d.ts.map +1 -0
- package/dist/tracker/gitlab.js +116 -0
- package/dist/tracker/gitlab.js.map +1 -0
- package/dist/tracker/index.d.ts +9 -0
- package/dist/tracker/index.d.ts.map +1 -0
- package/dist/tracker/index.js +28 -0
- package/dist/tracker/index.js.map +1 -0
- package/dist/tracker/interface.d.ts +39 -0
- package/dist/tracker/interface.d.ts.map +1 -0
- package/dist/tracker/interface.js +7 -0
- package/dist/tracker/interface.js.map +1 -0
- package/dist/tracker/priority.d.ts +19 -0
- package/dist/tracker/priority.d.ts.map +1 -0
- package/dist/tracker/priority.js +40 -0
- package/dist/tracker/priority.js.map +1 -0
- package/dist/util/logger.d.ts +4 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +14 -0
- package/dist/util/logger.js.map +1 -0
- 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":";;;;;AA2GA,gCA8BC;AAED,4CAEC;AA7ID,0CAAuC;AACvC,2BAA6B;AAC7B,+BAAqC;AACrC,gDAAwB;AAExB,MAAM,mBAAmB,GAAG,IAAA,WAAI,EAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC5E,SAAS,cAAc;IACrB,OAAO,IAAA,WAAI,EAAC,IAAA,YAAO,GAAE,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;AACrD,CAAC;AAqDD,0EAA0E;AAC1E,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE;QACjD,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,QAAQ,IAAI,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACrD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,GAAY;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,IAAA,WAAI,EAAC,IAAA,YAAO,GAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,SAAS,CAAC,IAA6B,EAAE,QAAiC;IACjF,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YAClG,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAA4B,EAAE,CAA4B,CAAC,CAAC;QAC5F,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,IAAI,YAAY,GAAkB,IAAI,CAAC;AAEhC,KAAK,UAAU,UAAU;IAC9B,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IAEtC,MAAM,UAAU,GAAG,MAAM,IAAA,mBAAQ,EAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAC/D,IAAI,MAAM,GAAG,cAAI,CAAC,KAAK,CAAC,UAAU,CAA4B,CAAC;IAE/D,yDAAyD;IACzD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,MAAM,CAAC,MAAiC,CAAC;QACnD,MAAM,CAAC,EAAE,GAAG;YACV,SAAS,EAAE,CAAC,CAAC,WAAW;YACxB,MAAM,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM;YAC9B,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,IAAA,WAAI,EAAC,IAAA,YAAO,GAAE,EAAE,WAAW,EAAE,aAAa,CAAC;SAClF,CAAC;QACF,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,cAAc,EAAE,EAAE,MAAM,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,cAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;QAClE,IAAI,UAAU;YAAE,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;IAED,MAAM,GAAG,YAAY,CAAC,MAAM,CAA4B,CAAC;IACzD,MAAM,GAAG,WAAW,CAAC,MAAM,CAA4B,CAAC;IAExD,YAAY,GAAG,MAA2B,CAAC;IAC3C,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAgB,gBAAgB;IAC9B,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC"}
|
|
@@ -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
|