@agtd/agent 0.1.2 → 0.1.3

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 (110) hide show
  1. package/dist/agent.js +6 -49
  2. package/dist/{chunk-24ORBJSI.js → chunk-RJLNNX7L.js} +489 -32
  3. package/dist/cli.js +186 -120
  4. package/package.json +2 -1
  5. package/dist/__tests__/codexAdapterFindFile.test.d.ts +0 -2
  6. package/dist/__tests__/codexAdapterFindFile.test.d.ts.map +0 -1
  7. package/dist/__tests__/codexAdapterFindFile.test.js +0 -298
  8. package/dist/__tests__/codexAdapterFindFile.test.js.map +0 -1
  9. package/dist/__tests__/enrichers.test.d.ts +0 -2
  10. package/dist/__tests__/enrichers.test.d.ts.map +0 -1
  11. package/dist/__tests__/enrichers.test.js +0 -47
  12. package/dist/__tests__/enrichers.test.js.map +0 -1
  13. package/dist/__tests__/tmux.integration.test.d.ts +0 -2
  14. package/dist/__tests__/tmux.integration.test.d.ts.map +0 -1
  15. package/dist/__tests__/tmux.integration.test.js +0 -112
  16. package/dist/__tests__/tmux.integration.test.js.map +0 -1
  17. package/dist/__tests__/tmux.test.d.ts +0 -2
  18. package/dist/__tests__/tmux.test.d.ts.map +0 -1
  19. package/dist/__tests__/tmux.test.js +0 -26
  20. package/dist/__tests__/tmux.test.js.map +0 -1
  21. package/dist/__tests__/transcriptAdapters.test.d.ts +0 -2
  22. package/dist/__tests__/transcriptAdapters.test.d.ts.map +0 -1
  23. package/dist/__tests__/transcriptAdapters.test.js +0 -133
  24. package/dist/__tests__/transcriptAdapters.test.js.map +0 -1
  25. package/dist/adapters/claude-code.d.ts +0 -3
  26. package/dist/adapters/claude-code.d.ts.map +0 -1
  27. package/dist/adapters/claude-code.js +0 -48
  28. package/dist/adapters/claude-code.js.map +0 -1
  29. package/dist/adapters/codex.d.ts +0 -3
  30. package/dist/adapters/codex.d.ts.map +0 -1
  31. package/dist/adapters/codex.js +0 -33
  32. package/dist/adapters/codex.js.map +0 -1
  33. package/dist/adapters/generic.d.ts +0 -3
  34. package/dist/adapters/generic.d.ts.map +0 -1
  35. package/dist/adapters/generic.js +0 -38
  36. package/dist/adapters/generic.js.map +0 -1
  37. package/dist/adapters/index.d.ts +0 -7
  38. package/dist/adapters/index.d.ts.map +0 -1
  39. package/dist/adapters/index.js +0 -12
  40. package/dist/adapters/index.js.map +0 -1
  41. package/dist/agent.d.ts +0 -3
  42. package/dist/agent.d.ts.map +0 -1
  43. package/dist/agent.js.map +0 -1
  44. package/dist/cli.d.ts +0 -11
  45. package/dist/cli.d.ts.map +0 -1
  46. package/dist/cli.js.map +0 -1
  47. package/dist/config.d.ts +0 -26
  48. package/dist/config.d.ts.map +0 -1
  49. package/dist/config.js +0 -48
  50. package/dist/config.js.map +0 -1
  51. package/dist/enrichers/git.d.ts +0 -6
  52. package/dist/enrichers/git.d.ts.map +0 -1
  53. package/dist/enrichers/git.js +0 -20
  54. package/dist/enrichers/git.js.map +0 -1
  55. package/dist/enrichers/pr.d.ts +0 -2
  56. package/dist/enrichers/pr.d.ts.map +0 -1
  57. package/dist/enrichers/pr.js +0 -10
  58. package/dist/enrichers/pr.js.map +0 -1
  59. package/dist/enrichers/transcript.d.ts +0 -8
  60. package/dist/enrichers/transcript.d.ts.map +0 -1
  61. package/dist/enrichers/transcript.js +0 -33
  62. package/dist/enrichers/transcript.js.map +0 -1
  63. package/dist/heartbeat.d.ts +0 -4
  64. package/dist/heartbeat.d.ts.map +0 -1
  65. package/dist/heartbeat.js +0 -15
  66. package/dist/heartbeat.js.map +0 -1
  67. package/dist/init.d.ts +0 -10
  68. package/dist/init.d.ts.map +0 -1
  69. package/dist/init.js +0 -70
  70. package/dist/init.js.map +0 -1
  71. package/dist/register.d.ts +0 -3
  72. package/dist/register.d.ts.map +0 -1
  73. package/dist/register.js +0 -22
  74. package/dist/register.js.map +0 -1
  75. package/dist/sessionScanner.d.ts +0 -20
  76. package/dist/sessionScanner.d.ts.map +0 -1
  77. package/dist/sessionScanner.js +0 -479
  78. package/dist/sessionScanner.js.map +0 -1
  79. package/dist/spawn.d.ts +0 -8
  80. package/dist/spawn.d.ts.map +0 -1
  81. package/dist/spawn.js +0 -73
  82. package/dist/spawn.js.map +0 -1
  83. package/dist/syncProjects.d.ts +0 -3
  84. package/dist/syncProjects.d.ts.map +0 -1
  85. package/dist/syncProjects.js +0 -91
  86. package/dist/syncProjects.js.map +0 -1
  87. package/dist/terminalBridge.d.ts +0 -4
  88. package/dist/terminalBridge.d.ts.map +0 -1
  89. package/dist/terminalBridge.js +0 -42
  90. package/dist/terminalBridge.js.map +0 -1
  91. package/dist/tmux.d.ts +0 -21
  92. package/dist/tmux.d.ts.map +0 -1
  93. package/dist/tmux.js +0 -89
  94. package/dist/tmux.js.map +0 -1
  95. package/dist/transcriptAdapters/claude-code.d.ts +0 -3
  96. package/dist/transcriptAdapters/claude-code.d.ts.map +0 -1
  97. package/dist/transcriptAdapters/claude-code.js +0 -147
  98. package/dist/transcriptAdapters/claude-code.js.map +0 -1
  99. package/dist/transcriptAdapters/codex.d.ts +0 -3
  100. package/dist/transcriptAdapters/codex.d.ts.map +0 -1
  101. package/dist/transcriptAdapters/codex.js +0 -251
  102. package/dist/transcriptAdapters/codex.js.map +0 -1
  103. package/dist/transcriptAdapters/index.d.ts +0 -17
  104. package/dist/transcriptAdapters/index.d.ts.map +0 -1
  105. package/dist/transcriptAdapters/index.js +0 -9
  106. package/dist/transcriptAdapters/index.js.map +0 -1
  107. package/dist/wsClient.d.ts +0 -6
  108. package/dist/wsClient.d.ts.map +0 -1
  109. package/dist/wsClient.js +0 -156
  110. package/dist/wsClient.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,45 +1,117 @@
1
- import { execSync } from "node:child_process";
2
- import { createInterface } from "node:readline/promises";
3
- import { platform } from "node:os";
4
- import { resolveConfigPath, parseConfigFile, mergeCliOverrides, } from "./config.js";
5
- import { runInitWizard } from "./init.js";
6
- import { startAgent } from "./agent.js";
7
- import { buildConfigFromAnswers } from "./init.js";
8
- export function parseArgs(argv) {
9
- const args = { init: false, help: false };
10
- for (let i = 2; i < argv.length; i++) {
11
- const arg = argv[i];
12
- const next = argv[i + 1];
13
- switch (arg) {
14
- case "--init":
15
- args.init = true;
16
- break;
17
- case "--backend":
18
- args.backend = next;
19
- i++;
20
- break;
21
- case "--api-key":
22
- args.apiKey = next;
23
- i++;
24
- break;
25
- case "--device-name":
26
- args.deviceName = next;
27
- i++;
28
- break;
29
- case "--config":
30
- args.configPath = next;
31
- i++;
32
- break;
33
- case "--help":
34
- case "-h":
35
- args.help = true;
36
- break;
37
- }
1
+ import {
2
+ mergeCliOverrides,
3
+ parseConfigFile,
4
+ resolveConfigPath,
5
+ startAgent
6
+ } from "./chunk-RJLNNX7L.js";
7
+
8
+ // src/cli.ts
9
+ import { execSync } from "child_process";
10
+ import { createInterface as createInterface2 } from "readline/promises";
11
+ import { platform as platform2 } from "os";
12
+
13
+ // src/init.ts
14
+ import { createInterface } from "readline/promises";
15
+ import { mkdirSync, writeFileSync } from "fs";
16
+ import { resolve } from "path";
17
+ import { hostname, platform, homedir } from "os";
18
+ function buildConfigFromAnswers(answers) {
19
+ return {
20
+ deviceId: hostname(),
21
+ deviceName: answers.deviceName || `${hostname()}-${platform()}`,
22
+ backendUrl: answers.backendUrl,
23
+ apiKey: answers.apiKey,
24
+ projects: [],
25
+ projectDirs: answers.projectDirs ? answers.projectDirs.split(",").map((d) => d.trim()).filter(Boolean) : []
26
+ };
27
+ }
28
+ async function validateBackend(url) {
29
+ try {
30
+ const res = await fetch(`${url}/health`);
31
+ return res.ok;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ async function runInitWizard() {
37
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
38
+ console.log("\nNo config found. Let's set up your agent.\n");
39
+ try {
40
+ const backendUrl = await rl.question("Backend URL: ");
41
+ if (!backendUrl) {
42
+ throw new Error("Backend URL is required");
43
+ }
44
+ process.stdout.write("Checking backend... ");
45
+ const reachable = await validateBackend(backendUrl);
46
+ if (!reachable) {
47
+ console.log("unreachable (continuing anyway)");
48
+ } else {
49
+ console.log("ok");
50
+ }
51
+ const apiKey = await rl.question("API Key: ");
52
+ if (!apiKey) {
53
+ throw new Error("API Key is required");
54
+ }
55
+ const defaultName = `${hostname()}-${platform()}`;
56
+ const deviceName = await rl.question(`Device name (${defaultName}): `);
57
+ const projectDirs = await rl.question(
58
+ "Project directories (comma-separated, optional): "
59
+ );
60
+ const config = buildConfigFromAnswers({
61
+ backendUrl,
62
+ apiKey,
63
+ deviceName,
64
+ projectDirs
65
+ });
66
+ const configDir = resolve(homedir(), ".agtd");
67
+ const configPath = resolve(configDir, "agent.config.json");
68
+ mkdirSync(configDir, { recursive: true });
69
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
70
+ console.log(`
71
+ Config saved to ${configPath}
72
+ `);
73
+ return config;
74
+ } finally {
75
+ rl.close();
76
+ }
77
+ }
78
+
79
+ // src/cli.ts
80
+ function parseArgs(argv) {
81
+ const args = { init: false, help: false };
82
+ for (let i = 2; i < argv.length; i++) {
83
+ const arg = argv[i];
84
+ const next = argv[i + 1];
85
+ switch (arg) {
86
+ case "--init":
87
+ args.init = true;
88
+ break;
89
+ case "--backend":
90
+ args.backend = next;
91
+ i++;
92
+ break;
93
+ case "--api-key":
94
+ args.apiKey = next;
95
+ i++;
96
+ break;
97
+ case "--device-name":
98
+ args.deviceName = next;
99
+ i++;
100
+ break;
101
+ case "--config":
102
+ args.configPath = next;
103
+ i++;
104
+ break;
105
+ case "--help":
106
+ case "-h":
107
+ args.help = true;
108
+ break;
38
109
  }
39
- return args;
110
+ }
111
+ return args;
40
112
  }
41
113
  function printHelp() {
42
- console.log(`
114
+ console.log(`
43
115
  Usage: agtd-agent [options]
44
116
 
45
117
  Options:
@@ -59,92 +131,86 @@ Config is loaded from (in order):
59
131
  `);
60
132
  }
61
133
  function hasTmux() {
62
- try {
63
- execSync("which tmux", { stdio: "ignore" });
64
- return true;
65
- }
66
- catch {
67
- return false;
68
- }
134
+ try {
135
+ execSync("which tmux", { stdio: "ignore" });
136
+ return true;
137
+ } catch {
138
+ return false;
139
+ }
69
140
  }
70
141
  async function ensureTmux() {
71
- if (hasTmux())
72
- return;
73
- const os = platform();
74
- let installCmd;
75
- if (os === "darwin") {
76
- installCmd = "brew install tmux";
77
- }
78
- else if (os === "linux") {
79
- installCmd = "sudo apt install -y tmux";
80
- }
81
- else {
82
- console.error("tmux is required but not installed. Please install it manually.");
83
- process.exit(1);
84
- }
85
- const rl = createInterface({ input: process.stdin, output: process.stdout });
86
- try {
87
- const answer = await rl.question(`tmux is required but not installed. Install with "${installCmd}"? (Y/n) `);
88
- if (answer.toLowerCase() === "n") {
89
- console.error("tmux is required to run the agent.");
90
- process.exit(1);
91
- }
92
- }
93
- finally {
94
- rl.close();
95
- }
96
- console.log(`Running: ${installCmd}`);
97
- try {
98
- execSync(installCmd, { stdio: "inherit" });
99
- console.log("tmux installed successfully.\n");
100
- }
101
- catch {
102
- console.error("Failed to install tmux. Please install it manually.");
103
- process.exit(1);
142
+ if (hasTmux()) return;
143
+ const os = platform2();
144
+ let installCmd;
145
+ if (os === "darwin") {
146
+ installCmd = "brew install tmux";
147
+ } else if (os === "linux") {
148
+ installCmd = "sudo apt install -y tmux";
149
+ } else {
150
+ console.error(
151
+ "tmux is required but not installed. Please install it manually."
152
+ );
153
+ process.exit(1);
154
+ }
155
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
156
+ try {
157
+ const answer = await rl.question(
158
+ `tmux is required but not installed. Install with "${installCmd}"? (Y/n) `
159
+ );
160
+ if (answer.toLowerCase() === "n") {
161
+ console.error("tmux is required to run the agent.");
162
+ process.exit(1);
104
163
  }
164
+ } finally {
165
+ rl.close();
166
+ }
167
+ console.log(`Running: ${installCmd}`);
168
+ try {
169
+ execSync(installCmd, { stdio: "inherit" });
170
+ console.log("tmux installed successfully.\n");
171
+ } catch {
172
+ console.error("Failed to install tmux. Please install it manually.");
173
+ process.exit(1);
174
+ }
105
175
  }
106
176
  async function main() {
107
- const args = parseArgs(process.argv);
108
- if (args.help) {
109
- printHelp();
110
- return;
111
- }
112
- await ensureTmux();
113
- // Force init wizard
114
- if (args.init) {
115
- const config = await runInitWizard();
116
- await startAgent(config);
117
- return;
118
- }
119
- // Try to find existing config
120
- const configPath = resolveConfigPath(args.configPath);
121
- if (configPath) {
122
- const overrides = {
123
- backend: args.backend,
124
- apiKey: args.apiKey,
125
- deviceName: args.deviceName,
126
- };
127
- const config = mergeCliOverrides(parseConfigFile(configPath), overrides);
128
- await startAgent(config);
129
- }
130
- else if (args.backend && args.apiKey) {
131
- // No config file but CLI flags provided
132
- const config = buildConfigFromAnswers({
133
- backendUrl: args.backend,
134
- apiKey: args.apiKey,
135
- deviceName: args.deviceName ?? "",
136
- projectDirs: "",
137
- });
138
- await startAgent(config);
139
- }
140
- else {
141
- // No config, no flags — run wizard
142
- const config = await runInitWizard();
143
- await startAgent(config);
144
- }
177
+ const args = parseArgs(process.argv);
178
+ if (args.help) {
179
+ printHelp();
180
+ return;
181
+ }
182
+ await ensureTmux();
183
+ if (args.init) {
184
+ const config = await runInitWizard();
185
+ await startAgent(config);
186
+ return;
187
+ }
188
+ const configPath = resolveConfigPath(args.configPath);
189
+ if (configPath) {
190
+ const overrides = {
191
+ backend: args.backend,
192
+ apiKey: args.apiKey,
193
+ deviceName: args.deviceName
194
+ };
195
+ const config = mergeCliOverrides(parseConfigFile(configPath), overrides);
196
+ await startAgent(config);
197
+ } else if (args.backend && args.apiKey) {
198
+ const config = buildConfigFromAnswers({
199
+ backendUrl: args.backend,
200
+ apiKey: args.apiKey,
201
+ deviceName: args.deviceName ?? "",
202
+ projectDirs: ""
203
+ });
204
+ await startAgent(config);
205
+ } else {
206
+ const config = await runInitWizard();
207
+ await startAgent(config);
208
+ }
145
209
  }
146
210
  main().catch((e) => {
147
- console.error("Agent failed:", e);
148
- process.exit(1);
211
+ console.error("Agent failed:", e);
212
+ process.exit(1);
149
213
  });
150
- //# sourceMappingURL=cli.js.map
214
+ export {
215
+ parseArgs
216
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agtd/agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "agtd-agent": "./bin/agtd-agent.js"
@@ -15,6 +15,7 @@
15
15
  "dev": "tsx watch src/agent.ts",
16
16
  "build": "tsc",
17
17
  "build:publish": "tsup",
18
+ "prepublishOnly": "tsup",
18
19
  "start": "node dist/agent.js",
19
20
  "test": "vitest run",
20
21
  "test:watch": "vitest"
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=codexAdapterFindFile.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"codexAdapterFindFile.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/codexAdapterFindFile.test.ts"],"names":[],"mappings":""}
@@ -1,298 +0,0 @@
1
- /**
2
- * Integration tests for codexAdapter.findFile() — the three-tier lookup:
3
- *
4
- * Tier 1: stored codexThreadId → exact match, unique per session
5
- * Tier 2: tmux session creation time → used when codexThreadId absent
6
- * Tier 3: latest thread by cwd → last resort, may collide if cwd shared
7
- *
8
- * node:child_process is mocked so these tests run without sqlite3 CLI or tmux.
9
- */
10
- import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from "vitest";
11
- import { writeFileSync, mkdirSync, rmSync } from "node:fs";
12
- import { join } from "node:path";
13
- import { tmpdir } from "node:os";
14
- vi.mock("node:child_process", () => ({
15
- execSync: vi.fn(),
16
- }));
17
- // Import AFTER vi.mock so the adapter picks up the mocked execSync
18
- import { codexAdapter } from "../transcriptAdapters/codex.js";
19
- import { execSync } from "node:child_process";
20
- const mockExecSync = vi.mocked(execSync);
21
- // ---------------------------------------------------------------------------
22
- // Fixture setup
23
- // ---------------------------------------------------------------------------
24
- const THREAD_ID_1 = "01234567-0000-7000-0000-000000000001";
25
- const THREAD_ID_2 = "01234567-0000-7000-0000-000000000002";
26
- const SHARED_CWD = "/projects/shared-project";
27
- const TMUX_SESSION_1 = "aidash-sess_aaa11111";
28
- const TMUX_SESSION_2 = "aidash-sess_bbb22222";
29
- const TMUX_CREATED_1 = "1700000000"; // earlier session
30
- const TMUX_CREATED_2 = "1700000300"; // later session (5 min later)
31
- let tmpDir;
32
- let jsonlPath1; // rollout file for thread 1
33
- let jsonlPath2; // rollout file for thread 2
34
- beforeAll(() => {
35
- tmpDir = join(tmpdir(), "agtd-codex-findfile-" + Date.now());
36
- mkdirSync(join(tmpDir, ".codex"), { recursive: true });
37
- // A zero-byte file is enough — existsSync(dbPath) just needs to pass
38
- writeFileSync(join(tmpDir, ".codex", "state_5.sqlite"), "");
39
- // Create real JSONL files so existsSync(rolloutPath) passes
40
- jsonlPath1 = join(tmpDir, "rollout-thread1.jsonl");
41
- jsonlPath2 = join(tmpDir, "rollout-thread2.jsonl");
42
- writeFileSync(jsonlPath1, JSON.stringify({
43
- type: "response_item",
44
- timestamp: "2026-01-01T00:00:00Z",
45
- payload: {
46
- type: "message",
47
- role: "user",
48
- content: [{ type: "text", text: "session 1 message" }],
49
- },
50
- }) + "\n");
51
- writeFileSync(jsonlPath2, JSON.stringify({
52
- type: "response_item",
53
- timestamp: "2026-01-02T00:00:00Z",
54
- payload: {
55
- type: "message",
56
- role: "user",
57
- content: [{ type: "text", text: "session 2 message" }],
58
- },
59
- }) + "\n");
60
- });
61
- afterAll(() => {
62
- rmSync(tmpDir, { recursive: true, force: true });
63
- });
64
- beforeEach(() => {
65
- mockExecSync.mockReset();
66
- });
67
- // ---------------------------------------------------------------------------
68
- // Tier 1 tests — codexThreadId direct lookup
69
- // ---------------------------------------------------------------------------
70
- describe("Tier 1: codexThreadId lookup", () => {
71
- it("returns the rollout file matching the stored codexThreadId", () => {
72
- mockExecSync.mockReturnValueOnce((jsonlPath1 + "\n"));
73
- const session = {
74
- id: "sess_1",
75
- cwd: SHARED_CWD,
76
- agentType: "codex",
77
- codexThreadId: THREAD_ID_1,
78
- tmuxSession: TMUX_SESSION_1,
79
- };
80
- const result = codexAdapter.findFile(session, tmpDir);
81
- expect(result).toBe(jsonlPath1);
82
- // Must query by thread ID, not by cwd
83
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(THREAD_ID_1), expect.any(Object));
84
- });
85
- it("two sessions with the SAME cwd get DIFFERENT transcripts via codexThreadId", () => {
86
- // Session 1 lookup
87
- mockExecSync.mockReturnValueOnce((jsonlPath1 + "\n"));
88
- const session1 = {
89
- id: "sess_1",
90
- cwd: SHARED_CWD,
91
- codexThreadId: THREAD_ID_1,
92
- };
93
- const file1 = codexAdapter.findFile(session1, tmpDir);
94
- mockExecSync.mockReset();
95
- // Session 2 lookup
96
- mockExecSync.mockReturnValueOnce((jsonlPath2 + "\n"));
97
- const session2 = {
98
- id: "sess_2",
99
- cwd: SHARED_CWD,
100
- codexThreadId: THREAD_ID_2,
101
- };
102
- const file2 = codexAdapter.findFile(session2, tmpDir);
103
- expect(file1).toBe(jsonlPath1);
104
- expect(file2).toBe(jsonlPath2);
105
- // KEY regression assertion: same cwd must not produce same transcript
106
- expect(file1).not.toBe(file2);
107
- });
108
- it("falls through to Tier 2 when Tier 1 returns a non-existent path", () => {
109
- // Tier 1: returns a path that does not exist on disk
110
- mockExecSync
111
- .mockReturnValueOnce(("/nonexistent/rollout.jsonl\n")) // Tier 1 sqlite3
112
- .mockReturnValueOnce((TMUX_CREATED_1 + "\n")) // Tier 2: tmux display-message
113
- .mockReturnValueOnce((jsonlPath1 + "\n")); // Tier 2: sqlite3 by timestamp
114
- const session = {
115
- id: "sess_1",
116
- cwd: SHARED_CWD,
117
- codexThreadId: THREAD_ID_1,
118
- tmuxSession: TMUX_SESSION_1,
119
- };
120
- const result = codexAdapter.findFile(session, tmpDir);
121
- expect(result).toBe(jsonlPath1);
122
- });
123
- it("falls through to Tier 3 when codexThreadId returns empty string", () => {
124
- // Tier 1: sqlite3 returns empty (thread not found)
125
- // Tier 2: no tmuxSession → skipped
126
- // Tier 3: returns latest by cwd
127
- mockExecSync
128
- .mockReturnValueOnce(("")) // Tier 1: empty
129
- .mockReturnValueOnce((jsonlPath2 + "\n")); // Tier 3: latest by cwd
130
- const session = {
131
- id: "sess_x",
132
- cwd: SHARED_CWD,
133
- codexThreadId: THREAD_ID_1,
134
- tmuxSession: null, // no tmux → skip Tier 2
135
- };
136
- const result = codexAdapter.findFile(session, tmpDir);
137
- expect(result).toBe(jsonlPath2);
138
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("ORDER BY updated_at DESC"), expect.any(Object));
139
- });
140
- });
141
- // ---------------------------------------------------------------------------
142
- // Tier 2 tests — tmux session creation time
143
- // ---------------------------------------------------------------------------
144
- describe("Tier 2: tmux session creation time lookup", () => {
145
- it("uses tmux creation time when codexThreadId is null", () => {
146
- mockExecSync
147
- .mockReturnValueOnce((TMUX_CREATED_1 + "\n")) // tmux display-message
148
- .mockReturnValueOnce((jsonlPath1 + "\n")); // sqlite3 WHERE created_at >=
149
- const session = {
150
- id: "sess_3",
151
- cwd: SHARED_CWD,
152
- codexThreadId: null,
153
- tmuxSession: TMUX_SESSION_1,
154
- };
155
- const result = codexAdapter.findFile(session, tmpDir);
156
- expect(result).toBe(jsonlPath1);
157
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(`tmux display-message -t '${TMUX_SESSION_1}'`), expect.any(Object));
158
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining(`created_at >= ${TMUX_CREATED_1}`), expect.any(Object));
159
- });
160
- it("two sessions sharing cwd get correct files via different tmux creation times", () => {
161
- // Session 1: earlier tmux time → finds thread 1
162
- mockExecSync
163
- .mockReturnValueOnce((TMUX_CREATED_1 + "\n"))
164
- .mockReturnValueOnce((jsonlPath1 + "\n"));
165
- const session1 = {
166
- id: "sess_3",
167
- cwd: SHARED_CWD,
168
- codexThreadId: null,
169
- tmuxSession: TMUX_SESSION_1,
170
- };
171
- const file1 = codexAdapter.findFile(session1, tmpDir);
172
- expect(file1).toBe(jsonlPath1);
173
- mockExecSync.mockReset();
174
- // Session 2: later tmux time → finds thread 2
175
- mockExecSync
176
- .mockReturnValueOnce((TMUX_CREATED_2 + "\n"))
177
- .mockReturnValueOnce((jsonlPath2 + "\n"));
178
- const session2 = {
179
- id: "sess_4",
180
- cwd: SHARED_CWD,
181
- codexThreadId: null,
182
- tmuxSession: TMUX_SESSION_2,
183
- };
184
- const file2 = codexAdapter.findFile(session2, tmpDir);
185
- expect(file2).toBe(jsonlPath2);
186
- expect(file1).not.toBe(file2);
187
- });
188
- it("falls through to Tier 3 when tmux command fails", () => {
189
- mockExecSync
190
- .mockImplementationOnce(() => {
191
- throw new Error("no server running on /tmp/tmux/default");
192
- })
193
- .mockReturnValueOnce((jsonlPath2 + "\n")); // Tier 3 fallback
194
- const session = {
195
- id: "sess_5",
196
- cwd: SHARED_CWD,
197
- codexThreadId: null,
198
- tmuxSession: TMUX_SESSION_1,
199
- };
200
- const result = codexAdapter.findFile(session, tmpDir);
201
- expect(result).toBe(jsonlPath2);
202
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("ORDER BY updated_at DESC"), expect.any(Object));
203
- });
204
- it("falls through to Tier 3 when tmux returns non-numeric output", () => {
205
- mockExecSync
206
- .mockReturnValueOnce(("not-a-number\n")) // tmux returns garbage
207
- .mockReturnValueOnce((jsonlPath2 + "\n")); // Tier 3
208
- const session = {
209
- id: "sess_6",
210
- cwd: SHARED_CWD,
211
- codexThreadId: null,
212
- tmuxSession: TMUX_SESSION_1,
213
- };
214
- const result = codexAdapter.findFile(session, tmpDir);
215
- expect(result).toBe(jsonlPath2);
216
- });
217
- });
218
- // ---------------------------------------------------------------------------
219
- // Tier 3 tests — latest thread by cwd (last resort)
220
- // ---------------------------------------------------------------------------
221
- describe("Tier 3: latest thread by cwd (last resort)", () => {
222
- it("returns latest updated thread when no codexThreadId and no tmuxSession", () => {
223
- mockExecSync.mockReturnValueOnce((jsonlPath2 + "\n"));
224
- const session = {
225
- id: "sess_7",
226
- cwd: SHARED_CWD,
227
- codexThreadId: null,
228
- tmuxSession: null,
229
- };
230
- const result = codexAdapter.findFile(session, tmpDir);
231
- expect(result).toBe(jsonlPath2);
232
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("ORDER BY updated_at DESC LIMIT 1"), expect.any(Object));
233
- });
234
- it("WARNING: returns same file for two sessions with same cwd (Tier 3 limitation)", () => {
235
- // This test documents the known limitation of Tier 3 — it cannot disambiguate
236
- // sessions sharing the same cwd without codexThreadId or tmuxSession.
237
- // Tier 1 and Tier 2 exist to avoid hitting this case.
238
- const session1 = { id: "sess_8", cwd: SHARED_CWD, codexThreadId: null, tmuxSession: null };
239
- const session2 = { id: "sess_9", cwd: SHARED_CWD, codexThreadId: null, tmuxSession: null };
240
- mockExecSync.mockReturnValueOnce((jsonlPath2 + "\n"));
241
- const file1 = codexAdapter.findFile(session1, tmpDir);
242
- mockExecSync.mockReset();
243
- mockExecSync.mockReturnValueOnce((jsonlPath2 + "\n"));
244
- const file2 = codexAdapter.findFile(session2, tmpDir);
245
- // Both get the same (latest) file — this is the documented limitation
246
- expect(file1).toBe(jsonlPath2);
247
- expect(file2).toBe(jsonlPath2);
248
- expect(file1).toBe(file2); // expected collision — document this behavior
249
- });
250
- it("returns null when cwd is empty", () => {
251
- // No SQLite DB fallback and no sessions dir
252
- const session = {
253
- id: "sess_10",
254
- cwd: "",
255
- codexThreadId: null,
256
- tmuxSession: null,
257
- };
258
- const result = codexAdapter.findFile(session, join(tmpDir, "no-sessions-dir"));
259
- expect(result).toBeNull();
260
- });
261
- });
262
- // ---------------------------------------------------------------------------
263
- // SQL injection / quoting edge cases
264
- // ---------------------------------------------------------------------------
265
- describe("shell quoting — paths with special characters", () => {
266
- it("handles cwd containing single quotes in SQLite query", () => {
267
- // This is the exact bug that broke Tier 2 before the fix:
268
- // using single-quote outer wrapping with a cwd like /Users/foo/it's-project
269
- // broke because the shell interpreter saw the inner ' as ending the argument.
270
- mockExecSync.mockReturnValueOnce((jsonlPath1 + "\n"));
271
- const session = {
272
- id: "sess_sq",
273
- cwd: "/projects/it's-project",
274
- codexThreadId: THREAD_ID_1,
275
- tmuxSession: null,
276
- };
277
- // Should not throw; the quoting logic must handle single quotes in paths
278
- expect(() => codexAdapter.findFile(session, tmpDir)).not.toThrow();
279
- });
280
- it("Tier 3 CWD query uses double-quote outer wrapping (regression: near '/' syntax error)", () => {
281
- // The original bug: sqlite3 'db' 'WHERE cwd='/Users/...' was broken because
282
- // the shell split on / in the path. Fixed by using "double-quote wrapping".
283
- // Verify the query string passed to execSync uses the correct quoting.
284
- mockExecSync.mockReturnValueOnce((jsonlPath1 + "\n"));
285
- const session = {
286
- id: "sess_dq",
287
- cwd: "/Users/robyparapat/workspace/repo/payment-connectors-service",
288
- codexThreadId: null,
289
- tmuxSession: null,
290
- };
291
- codexAdapter.findFile(session, tmpDir);
292
- // The execSync call must use double quotes as outer wrapper for the SQL argument
293
- // so the cwd single quotes are treated as literal characters, not shell delimiters
294
- const call = mockExecSync.mock.calls[0][0];
295
- expect(call).toMatch(/sqlite3 '.*' "SELECT rollout_path/);
296
- });
297
- });
298
- //# sourceMappingURL=codexAdapterFindFile.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"codexAdapterFindFile.test.js","sourceRoot":"","sources":["../../src/__tests__/codexAdapterFindFile.test.ts"],"names":[],"mappings":"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"}