@love-moon/tui-driver 0.2.12 → 0.2.13
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/dist/driver/TuiDriver.d.ts +64 -0
- package/dist/driver/TuiDriver.d.ts.map +1 -1
- package/dist/driver/TuiDriver.js +924 -8
- package/dist/driver/TuiDriver.js.map +1 -1
- package/dist/driver/index.d.ts +2 -1
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pty/PtySession.d.ts +1 -0
- package/dist/pty/PtySession.d.ts.map +1 -1
- package/dist/pty/PtySession.js +9 -0
- package/dist/pty/PtySession.js.map +1 -1
- package/docs/how-to-add-a-new-backend.md +212 -0
- package/package.json +1 -1
- package/src/driver/TuiDriver.ts +1112 -10
- package/src/driver/index.ts +9 -1
- package/src/index.ts +2 -0
- package/src/pty/PtySession.ts +10 -0
- package/test/codex-session-discovery.test.ts +101 -0
- package/test/session-file-extraction.test.ts +257 -0
- package/test/timeout-resolution.test.ts +37 -0
package/src/driver/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
TuiDriver,
|
|
3
|
+
TuiDriverOptions,
|
|
4
|
+
AskResult,
|
|
5
|
+
TuiScreenSignals,
|
|
6
|
+
HealthStatus,
|
|
7
|
+
HealthReason,
|
|
8
|
+
} from "./TuiDriver.js";
|
|
9
|
+
export type { TuiSessionInfo, TuiSessionUsageSummary } from "./TuiDriver.js";
|
|
2
10
|
export { TuiProfile, TuiProfileName, TuiAnchors, TuiKeys, TuiExtraction, TuiSignals, createProfile } from "./TuiProfile.js";
|
|
3
11
|
export { StateMachine, TuiState, StateTransition } from "./StateMachine.js";
|
|
4
12
|
export { claudeCodeProfile, codexProfile, copilotProfile } from "./profiles/index.js";
|
package/src/index.ts
CHANGED
package/src/pty/PtySession.ts
CHANGED
|
@@ -137,10 +137,20 @@ export class PtySession extends EventEmitter {
|
|
|
137
137
|
|
|
138
138
|
this.ptyProcess.onExit(({ exitCode, signal }) => {
|
|
139
139
|
this._isRunning = false;
|
|
140
|
+
// Clear spawn state on natural exit so follow-up restarts can spawn safely.
|
|
141
|
+
this.ptyProcess = null;
|
|
140
142
|
this.emit("exit", exitCode, signal);
|
|
141
143
|
});
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
setCommandArgs(command: string, args: string[] = []): void {
|
|
147
|
+
if (this.ptyProcess) {
|
|
148
|
+
throw new Error("Cannot update PTY command while session is running");
|
|
149
|
+
}
|
|
150
|
+
this.command = command;
|
|
151
|
+
this.args = Array.isArray(args) ? [...args] : [];
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
write(data: string): void {
|
|
145
155
|
if (!this.ptyProcess) {
|
|
146
156
|
throw new Error("PTY session not spawned");
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { TuiDriver } from "../src/driver/TuiDriver.js";
|
|
3
|
+
import { codexProfile } from "../src/driver/profiles/codex.profile.js";
|
|
4
|
+
|
|
5
|
+
describe("codex session discovery", () => {
|
|
6
|
+
it("returns session info when current cwd has a matching codex thread", async () => {
|
|
7
|
+
const rolloutPath = "/tmp/rollout-current.jsonl";
|
|
8
|
+
const driver = new TuiDriver({
|
|
9
|
+
profile: codexProfile,
|
|
10
|
+
cwd: "/tmp/current-workspace",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
(driver as any).pathExists = vi.fn(async (filePath: string) => {
|
|
14
|
+
return filePath.endsWith("state_5.sqlite") || filePath === rolloutPath;
|
|
15
|
+
});
|
|
16
|
+
(driver as any).querySqliteRow = vi.fn(async () => `session-current|${rolloutPath}`);
|
|
17
|
+
|
|
18
|
+
const detected = await (driver as any).detectCodexSessionInfo();
|
|
19
|
+
expect(detected).toEqual({
|
|
20
|
+
backend: "codex",
|
|
21
|
+
sessionId: "session-current",
|
|
22
|
+
sessionFilePath: rolloutPath,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("does not fall back to global latest codex thread when cwd has no match", async () => {
|
|
27
|
+
const driver = new TuiDriver({
|
|
28
|
+
profile: codexProfile,
|
|
29
|
+
cwd: "/tmp/task-workspace",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
(driver as any).pathExists = vi.fn(async (filePath: string) => filePath.endsWith("state_5.sqlite"));
|
|
33
|
+
const querySqliteRow = vi.fn(async () => null);
|
|
34
|
+
(driver as any).querySqliteRow = querySqliteRow;
|
|
35
|
+
|
|
36
|
+
const detected = await (driver as any).detectCodexSessionInfo();
|
|
37
|
+
expect(detected).toBeNull();
|
|
38
|
+
expect(querySqliteRow).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(String(querySqliteRow.mock.calls[0]?.[1] || "")).toContain("cwd='/tmp/task-workspace'");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("prefers pinned session id before cwd lookup", async () => {
|
|
43
|
+
const pinnedPath = "/tmp/rollout-pinned.jsonl";
|
|
44
|
+
const driver = new TuiDriver({
|
|
45
|
+
profile: codexProfile,
|
|
46
|
+
cwd: "/tmp/task-workspace",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
(driver as any).lastSessionInfo = {
|
|
50
|
+
backend: "codex",
|
|
51
|
+
sessionId: "session-pinned",
|
|
52
|
+
sessionFilePath: "/tmp/old.jsonl",
|
|
53
|
+
};
|
|
54
|
+
(driver as any).pathExists = vi.fn(async (filePath: string) => {
|
|
55
|
+
return filePath.endsWith("state_5.sqlite") || filePath === pinnedPath;
|
|
56
|
+
});
|
|
57
|
+
const querySqliteRow = vi.fn(async () => `session-pinned|${pinnedPath}`);
|
|
58
|
+
(driver as any).querySqliteRow = querySqliteRow;
|
|
59
|
+
|
|
60
|
+
const detected = await (driver as any).detectCodexSessionInfo();
|
|
61
|
+
expect(detected).toEqual({
|
|
62
|
+
backend: "codex",
|
|
63
|
+
sessionId: "session-pinned",
|
|
64
|
+
sessionFilePath: pinnedPath,
|
|
65
|
+
});
|
|
66
|
+
expect(querySqliteRow).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(String(querySqliteRow.mock.calls[0]?.[1] || "")).toContain("id='session-pinned'");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("falls back to cwd lookup when pinned session id is missing", async () => {
|
|
71
|
+
const cwdPath = "/tmp/rollout-cwd.jsonl";
|
|
72
|
+
const driver = new TuiDriver({
|
|
73
|
+
profile: codexProfile,
|
|
74
|
+
cwd: "/tmp/task-workspace",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
(driver as any).lastSessionInfo = {
|
|
78
|
+
backend: "codex",
|
|
79
|
+
sessionId: "session-missing",
|
|
80
|
+
sessionFilePath: "/tmp/missing.jsonl",
|
|
81
|
+
};
|
|
82
|
+
(driver as any).pathExists = vi.fn(async (filePath: string) => {
|
|
83
|
+
return filePath.endsWith("state_5.sqlite") || filePath === cwdPath;
|
|
84
|
+
});
|
|
85
|
+
const querySqliteRow = vi
|
|
86
|
+
.fn(async () => null)
|
|
87
|
+
.mockImplementationOnce(async () => null)
|
|
88
|
+
.mockImplementationOnce(async () => `session-cwd|${cwdPath}`);
|
|
89
|
+
(driver as any).querySqliteRow = querySqliteRow;
|
|
90
|
+
|
|
91
|
+
const detected = await (driver as any).detectCodexSessionInfo();
|
|
92
|
+
expect(detected).toEqual({
|
|
93
|
+
backend: "codex",
|
|
94
|
+
sessionId: "session-cwd",
|
|
95
|
+
sessionFilePath: cwdPath,
|
|
96
|
+
});
|
|
97
|
+
expect(querySqliteRow).toHaveBeenCalledTimes(2);
|
|
98
|
+
expect(String(querySqliteRow.mock.calls[0]?.[1] || "")).toContain("id='session-missing'");
|
|
99
|
+
expect(String(querySqliteRow.mock.calls[1]?.[1] || "")).toContain("cwd='/tmp/task-workspace'");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { TuiDriver } from "../src/driver/TuiDriver.js";
|
|
3
|
+
import { claudeCodeProfile } from "../src/driver/profiles/claudeCode.profile.js";
|
|
4
|
+
import { codexProfile } from "../src/driver/profiles/codex.profile.js";
|
|
5
|
+
import { copilotProfile } from "../src/driver/profiles/copilot.profile.js";
|
|
6
|
+
|
|
7
|
+
describe("session file reply extraction", () => {
|
|
8
|
+
it("prefers codex task_complete last_agent_message over intermediate assistant text", () => {
|
|
9
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
10
|
+
const lines = [
|
|
11
|
+
JSON.stringify({
|
|
12
|
+
type: "response_item",
|
|
13
|
+
payload: {
|
|
14
|
+
type: "message",
|
|
15
|
+
role: "assistant",
|
|
16
|
+
content: [{ type: "output_text", text: "我先快速检查一下" }],
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
type: "event_msg",
|
|
21
|
+
payload: {
|
|
22
|
+
type: "task_complete",
|
|
23
|
+
last_agent_message: "最终结论:readback 被主线程排队阻塞",
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const extracted = (driver as any).extractCodexTaskCompleteMessageFromJsonLines(lines);
|
|
29
|
+
expect(extracted).toBe("最终结论:readback 被主线程排队阻塞");
|
|
30
|
+
driver.kill();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("detects codex task_complete completion marker from jsonl lines", () => {
|
|
34
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
35
|
+
const lines = [
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
type: "response_item",
|
|
38
|
+
payload: {
|
|
39
|
+
type: "message",
|
|
40
|
+
role: "assistant",
|
|
41
|
+
content: [{ type: "output_text", text: "intermediate" }],
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
JSON.stringify({
|
|
45
|
+
type: "event_msg",
|
|
46
|
+
payload: {
|
|
47
|
+
type: "task_complete",
|
|
48
|
+
last_agent_message: "final",
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const detected = (driver as any).hasCodexTaskCompleteFromJsonLines(lines);
|
|
54
|
+
expect(detected).toBe(true);
|
|
55
|
+
driver.kill();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("detects copilot assistant.turn_end completion marker from jsonl lines", () => {
|
|
59
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
60
|
+
const lines = [
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
type: "assistant.message",
|
|
63
|
+
data: {
|
|
64
|
+
content: "先看下代码",
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
type: "assistant.turn_end",
|
|
69
|
+
data: {
|
|
70
|
+
turnId: "12",
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const detected = (driver as any).hasCopilotTurnEndFromJsonLines(lines);
|
|
76
|
+
expect(detected).toBe(true);
|
|
77
|
+
driver.kill();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("extracts the latest codex assistant message from jsonl lines", () => {
|
|
81
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
82
|
+
const lines = [
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
type: "response_item",
|
|
85
|
+
payload: {
|
|
86
|
+
type: "message",
|
|
87
|
+
role: "assistant",
|
|
88
|
+
content: [{ type: "output_text", text: "first answer" }],
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
JSON.stringify({
|
|
92
|
+
type: "response_item",
|
|
93
|
+
payload: {
|
|
94
|
+
type: "message",
|
|
95
|
+
role: "assistant",
|
|
96
|
+
content: [{ type: "output_text", text: "second answer" }],
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const extracted = (driver as any).extractAssistantReplyFromJsonLines(lines, "codex");
|
|
102
|
+
expect(extracted).toBe("second answer");
|
|
103
|
+
driver.kill();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("extracts the latest claude assistant text block", () => {
|
|
107
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
108
|
+
const lines = [
|
|
109
|
+
JSON.stringify({
|
|
110
|
+
type: "assistant",
|
|
111
|
+
message: {
|
|
112
|
+
role: "assistant",
|
|
113
|
+
content: [{ type: "tool_use", id: "x", name: "Bash", input: {} }],
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
JSON.stringify({
|
|
117
|
+
type: "assistant",
|
|
118
|
+
message: {
|
|
119
|
+
role: "assistant",
|
|
120
|
+
content: [{ type: "text", text: "claude final answer" }],
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const extracted = (driver as any).extractAssistantReplyFromJsonLines(lines, "claude-code");
|
|
126
|
+
expect(extracted).toBe("claude final answer");
|
|
127
|
+
driver.kill();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("ignores empty copilot wrapper messages and keeps text replies", () => {
|
|
131
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
132
|
+
const lines = [
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
type: "assistant.message",
|
|
135
|
+
data: { content: "", toolRequests: [{ name: "report_intent" }] },
|
|
136
|
+
}),
|
|
137
|
+
JSON.stringify({
|
|
138
|
+
type: "assistant.message",
|
|
139
|
+
data: { content: "copilot final answer", toolRequests: [] },
|
|
140
|
+
}),
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const extracted = (driver as any).extractAssistantReplyFromJsonLines(lines, "copilot");
|
|
144
|
+
expect(extracted).toBe("copilot final answer");
|
|
145
|
+
driver.kill();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("builds codex resume args for restart and strips stale resume tokens", () => {
|
|
149
|
+
const driver = new TuiDriver({
|
|
150
|
+
profile: {
|
|
151
|
+
...codexProfile,
|
|
152
|
+
args: ["--sandbox", "workspace-write", "resume", "old-session"],
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const restartArgs = (driver as any).resolveRestartArgs("new-session");
|
|
156
|
+
expect(restartArgs).toEqual(["--sandbox", "workspace-write", "resume", "new-session"]);
|
|
157
|
+
driver.kill();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("builds claude and copilot resume args for restart", () => {
|
|
161
|
+
const claudeDriver = new TuiDriver({
|
|
162
|
+
profile: {
|
|
163
|
+
...claudeCodeProfile,
|
|
164
|
+
args: ["--resume", "old-session", "--verbose"],
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
const claudeArgs = (claudeDriver as any).resolveRestartArgs("new-session");
|
|
168
|
+
expect(claudeArgs).toEqual(["--verbose", "--resume", "new-session"]);
|
|
169
|
+
claudeDriver.kill();
|
|
170
|
+
|
|
171
|
+
const copilotDriver = new TuiDriver({
|
|
172
|
+
profile: {
|
|
173
|
+
...copilotProfile,
|
|
174
|
+
args: ["--resume=old-session", "--all-tools"],
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const copilotArgs = (copilotDriver as any).resolveRestartArgs("new-session");
|
|
178
|
+
expect(copilotArgs).toEqual(["--all-tools", "--resume=new-session"]);
|
|
179
|
+
copilotDriver.kill();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("extracts codex token/context usage percentages from session jsonl", () => {
|
|
183
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
184
|
+
const lines = [
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
type: "event_msg",
|
|
187
|
+
payload: {
|
|
188
|
+
type: "token_count",
|
|
189
|
+
rate_limits: {
|
|
190
|
+
secondary: {
|
|
191
|
+
used_percent: 26,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
info: {
|
|
195
|
+
last_token_usage: {
|
|
196
|
+
input_tokens: 12920,
|
|
197
|
+
},
|
|
198
|
+
model_context_window: 258400,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const extracted = (driver as any).extractCodexUsageFromJsonLines(lines);
|
|
205
|
+
expect(extracted.tokenUsagePercent).toBe(26);
|
|
206
|
+
expect(extracted.contextUsagePercent).toBeCloseTo(5, 4);
|
|
207
|
+
driver.kill();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("extracts copilot context usage percentage from compaction/session telemetry", () => {
|
|
211
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
212
|
+
const lines = [
|
|
213
|
+
JSON.stringify({
|
|
214
|
+
type: "tool.execution_complete",
|
|
215
|
+
data: {
|
|
216
|
+
toolTelemetry: {
|
|
217
|
+
metrics: {
|
|
218
|
+
responseTokenLimit: 68000,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
}),
|
|
223
|
+
JSON.stringify({
|
|
224
|
+
type: "session.compaction_complete",
|
|
225
|
+
data: {
|
|
226
|
+
preCompactionTokens: 34000,
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
const extracted = (driver as any).extractCopilotUsageFromJsonLines(lines);
|
|
232
|
+
expect(extracted.tokenUsagePercent).toBeUndefined();
|
|
233
|
+
expect(extracted.contextUsagePercent).toBe(50);
|
|
234
|
+
driver.kill();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("keeps claude usage percentages undefined when no limits are available", () => {
|
|
238
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
239
|
+
const lines = [
|
|
240
|
+
JSON.stringify({
|
|
241
|
+
type: "assistant",
|
|
242
|
+
message: {
|
|
243
|
+
role: "assistant",
|
|
244
|
+
usage: {
|
|
245
|
+
input_tokens: 20516,
|
|
246
|
+
output_tokens: 0,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const extracted = (driver as any).extractClaudeUsageFromJsonLines(lines);
|
|
253
|
+
expect(extracted.tokenUsagePercent).toBeUndefined();
|
|
254
|
+
expect(extracted.contextUsagePercent).toBeUndefined();
|
|
255
|
+
driver.kill();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { TuiDriver } from "../src/driver/TuiDriver.js";
|
|
3
|
+
import { codexProfile } from "../src/driver/profiles/codex.profile.js";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_STAGE_TIMEOUT_MAX_MS = 15 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
describe("timeout resolution", () => {
|
|
8
|
+
const originalMaxTimeout = process.env.CONDUCTOR_TUI_MAX_TIMEOUT_MS;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (typeof originalMaxTimeout === "undefined") {
|
|
12
|
+
delete process.env.CONDUCTOR_TUI_MAX_TIMEOUT_MS;
|
|
13
|
+
} else {
|
|
14
|
+
process.env.CONDUCTOR_TUI_MAX_TIMEOUT_MS = originalMaxTimeout;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("treats timeout=0 as max stage timeout", () => {
|
|
19
|
+
delete process.env.CONDUCTOR_TUI_MAX_TIMEOUT_MS;
|
|
20
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
21
|
+
const resolved = (driver as any).resolveTimeout(0, 10_000);
|
|
22
|
+
expect(resolved).toBe(DEFAULT_STAGE_TIMEOUT_MAX_MS);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("respects CONDUCTOR_TUI_MAX_TIMEOUT_MS when timeout=0", () => {
|
|
26
|
+
process.env.CONDUCTOR_TUI_MAX_TIMEOUT_MS = "600000";
|
|
27
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
28
|
+
const resolved = (driver as any).resolveTimeout(0, 10_000);
|
|
29
|
+
expect(resolved).toBe(600_000);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("keeps default timeout when configured timeout is undefined", () => {
|
|
33
|
+
const driver = new TuiDriver({ profile: codexProfile });
|
|
34
|
+
const resolved = (driver as any).resolveTimeout(undefined, 12_345);
|
|
35
|
+
expect(resolved).toBe(12_345);
|
|
36
|
+
});
|
|
37
|
+
});
|