@mongodb-js/harness-tester 3.0.0-prerelease.4 → 3.0.4
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/LICENSE +202 -0
- package/dist/harness/claude/claudeConfig.d.ts +30 -0
- package/dist/harness/claude/claudeConfig.d.ts.map +1 -0
- package/dist/harness/claude/claudeConfig.js +87 -0
- package/dist/harness/claude/claudeConfig.js.map +1 -0
- package/dist/harness/claude/claudeHarness.d.ts +30 -0
- package/dist/harness/claude/claudeHarness.d.ts.map +1 -0
- package/dist/harness/claude/claudeHarness.js +84 -0
- package/dist/harness/claude/claudeHarness.js.map +1 -0
- package/dist/harness/claude/claudeSession.d.ts +24 -0
- package/dist/harness/claude/claudeSession.d.ts.map +1 -0
- package/dist/harness/claude/claudeSession.js +49 -0
- package/dist/harness/claude/claudeSession.js.map +1 -0
- package/dist/harness/claude/parseClaudeToolCalls.d.ts +19 -0
- package/dist/harness/claude/parseClaudeToolCalls.d.ts.map +1 -0
- package/dist/harness/claude/parseClaudeToolCalls.js +84 -0
- package/dist/harness/claude/parseClaudeToolCalls.js.map +1 -0
- package/dist/harness/codex/codexConfig.d.ts +38 -0
- package/dist/harness/codex/codexConfig.d.ts.map +1 -0
- package/dist/harness/codex/codexConfig.js +162 -0
- package/dist/harness/codex/codexConfig.js.map +1 -0
- package/dist/harness/codex/codexHarness.d.ts +30 -0
- package/dist/harness/codex/codexHarness.d.ts.map +1 -0
- package/dist/harness/codex/codexHarness.js +68 -0
- package/dist/harness/codex/codexHarness.js.map +1 -0
- package/dist/harness/codex/codexParseTuiTranscript.d.ts +8 -0
- package/dist/harness/codex/codexParseTuiTranscript.d.ts.map +1 -0
- package/dist/harness/codex/codexParseTuiTranscript.js +37 -0
- package/dist/harness/codex/codexParseTuiTranscript.js.map +1 -0
- package/dist/harness/codex/codexSession.d.ts +21 -0
- package/dist/harness/codex/codexSession.d.ts.map +1 -0
- package/dist/harness/codex/codexSession.js +31 -0
- package/dist/harness/codex/codexSession.js.map +1 -0
- package/dist/harness/logger.d.ts +10 -0
- package/dist/harness/logger.d.ts.map +1 -0
- package/dist/harness/logger.js +18 -0
- package/dist/harness/logger.js.map +1 -0
- package/dist/harness/shared.d.ts +18 -0
- package/dist/harness/shared.d.ts.map +1 -0
- package/dist/harness/shared.js +46 -0
- package/dist/harness/shared.js.map +1 -0
- package/dist/harness/tuiSession.d.ts +73 -0
- package/dist/harness/tuiSession.d.ts.map +1 -0
- package/dist/harness/tuiSession.js +202 -0
- package/dist/harness/tuiSession.js.map +1 -0
- package/dist/harness/types.d.ts +91 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harnesses.d.ts +3 -0
- package/dist/harnesses.d.ts.map +1 -0
- package/dist/harnesses.js +4 -0
- package/dist/harnesses.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/useAgent.d.ts +16 -0
- package/dist/useAgent.d.ts.map +1 -0
- package/dist/useAgent.js +47 -0
- package/dist/useAgent.js.map +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Canonicalize a path so config keys match the agent's view of the filesystem
|
|
5
|
+
* (macOS resolves `/var` -> `/private/var`; a non-canonical key silently misses).
|
|
6
|
+
*/
|
|
7
|
+
export function canonicalPath(p) {
|
|
8
|
+
try {
|
|
9
|
+
return fs.realpathSync(p);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return path.resolve(p);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** New content = everything after the previous snapshot (scrollback grows monotonically). */
|
|
16
|
+
export function diffTranscript(full, start) {
|
|
17
|
+
const idx = full.indexOf(start);
|
|
18
|
+
return idx >= 0 ? full.slice(idx + start.length) : full;
|
|
19
|
+
}
|
|
20
|
+
export function sleep(ms) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
/** Debug switch: single `AGENT_E2E_DEBUG` env var for the whole harness. */
|
|
24
|
+
export function isHarnessDebug() {
|
|
25
|
+
return !!process.env.AGENT_E2E_DEBUG;
|
|
26
|
+
}
|
|
27
|
+
/** Default timeout for a single `prompt()` turn. */
|
|
28
|
+
export const DEFAULT_PROMPT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
29
|
+
const SUPPORTED_TUI_BACKENDS = ["alacritty", "ghostty", "rio"];
|
|
30
|
+
/** Validate an env-var backend override (`AGENT_E2E_TUI_BACKEND`) against the supported backends. */
|
|
31
|
+
export function resolveBackend(explicit) {
|
|
32
|
+
if (explicit) {
|
|
33
|
+
return explicit;
|
|
34
|
+
}
|
|
35
|
+
const fromEnv = process.env.AGENT_E2E_TUI_BACKEND;
|
|
36
|
+
if (fromEnv && SUPPORTED_TUI_BACKENDS.includes(fromEnv)) {
|
|
37
|
+
return fromEnv;
|
|
38
|
+
}
|
|
39
|
+
return "alacritty";
|
|
40
|
+
}
|
|
41
|
+
/** Strip the `<server>.` / `mcp__<server>__` prefix agents render before tool names. */
|
|
42
|
+
export function normalizeToolName(name) {
|
|
43
|
+
const lastSegment = name.split(/__|\./).pop();
|
|
44
|
+
return lastSegment ?? name;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/harness/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACnC,IAAI,CAAC;QACD,OAAO,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;AACL,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAa;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;IAC5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc;IAC1B,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AACzC,CAAC;AAED,oDAAoD;AACpD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEvD,MAAM,sBAAsB,GAAuB,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAEnF,qGAAqG;AACrG,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC7C,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAClD,IAAI,OAAO,IAAK,sBAA4C,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7E,OAAO,OAAkB,CAAC;IAC9B,CAAC;IACD,OAAO,WAAW,CAAC;AACvB,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC;IAC9C,OAAO,WAAW,IAAI,IAAI,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { TuiTest } from "@microsoft/tui-test";
|
|
2
|
+
import type { AgentHarnessOptions, AgentSession, AgentTurn, AgentTurnState, ToolCallRecord } from "./types.js";
|
|
3
|
+
/** Per-poll state shared by both agent TUI sessions. */
|
|
4
|
+
export interface TuiState {
|
|
5
|
+
/** True while the session's status/footer shows an in-progress turn. */
|
|
6
|
+
working: boolean;
|
|
7
|
+
/** True once the composer shows its idle line (turn finished). */
|
|
8
|
+
composerIdle: boolean;
|
|
9
|
+
/** Full live viewport text (or the transcript when the viewport is gone). */
|
|
10
|
+
viewport: string;
|
|
11
|
+
/** ms since the poll loop started (first poll after Enter). */
|
|
12
|
+
elapsedMs: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Shared polling loop + terminal helpers for agent TUI sessions. Subclasses
|
|
16
|
+
* supply only the agent-specific bits: the label, the in-progress and idle
|
|
17
|
+
* markers, and how to extract tool calls for a completed turn.
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class TuiSessionBase implements AgentSession {
|
|
20
|
+
protected readonly terminal: TuiTest;
|
|
21
|
+
protected readonly options: AgentHarnessOptions;
|
|
22
|
+
private readonly onState;
|
|
23
|
+
private _log;
|
|
24
|
+
/** Length of the turn delta already streamed to stdout (debug streaming). */
|
|
25
|
+
private lastShownDeltaLength;
|
|
26
|
+
/** Transcript snapshot the current turn started from (so chooseOption can continue it). */
|
|
27
|
+
private currentTurnStartText;
|
|
28
|
+
private currentState;
|
|
29
|
+
get state(): AgentTurnState;
|
|
30
|
+
protected constructor({ terminal, options, onState, }: {
|
|
31
|
+
terminal: TuiTest;
|
|
32
|
+
options: AgentHarnessOptions;
|
|
33
|
+
onState?: (state: TuiState) => void;
|
|
34
|
+
});
|
|
35
|
+
/** Agent label used in debug/error output (e.g. "codex"). */
|
|
36
|
+
protected abstract get label(): string;
|
|
37
|
+
private get log();
|
|
38
|
+
/** Whether the viewport shows an in-progress turn. */
|
|
39
|
+
protected abstract isWorking(text: string): boolean;
|
|
40
|
+
/** Whether the composer has returned to idle (turn finished). */
|
|
41
|
+
protected abstract isComposerIdle(text: string): boolean;
|
|
42
|
+
/** Extract tool calls for a completed turn's transcript delta. */
|
|
43
|
+
protected abstract extractToolCalls(delta: string): ToolCallRecord[];
|
|
44
|
+
/** Wait until the composer is ready for a prompt. */
|
|
45
|
+
initialise(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Answer a pending elicitation (`confirm`/`decline`) and run the turn to
|
|
48
|
+
* completion, returning the completed {@link AgentTurn}.
|
|
49
|
+
*/
|
|
50
|
+
chooseOption(choice: "confirm" | "decline"): Promise<AgentTurn>;
|
|
51
|
+
/**
|
|
52
|
+
* Submit `choice` in the confirmation prompt currently on screen. Each agent
|
|
53
|
+
* renders it with different labels/controls, so subclasses implement this.
|
|
54
|
+
*/
|
|
55
|
+
protected abstract sendChoice(choice: "confirm" | "decline"): Promise<void>;
|
|
56
|
+
/** Codex interposes its own tool-approval prompt before the server's elicitation. */
|
|
57
|
+
protected isToolApproval(text: string): boolean;
|
|
58
|
+
protected approveTool(): Promise<void>;
|
|
59
|
+
prompt(prompt: string): Promise<AgentTurn>;
|
|
60
|
+
/**
|
|
61
|
+
* Poll until the turn completes or, when `stopOnElicitation`, the agent is
|
|
62
|
+
* awaiting a confirmation (auto-approving codex's tool-permission prompt).
|
|
63
|
+
*/
|
|
64
|
+
private pollTurn;
|
|
65
|
+
/** Whether the composer has returned to its idle prompt (turn finished). */
|
|
66
|
+
private waitIdleComposer;
|
|
67
|
+
private viewportText;
|
|
68
|
+
private transcriptText;
|
|
69
|
+
private buildTurn;
|
|
70
|
+
private printState;
|
|
71
|
+
dispose(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=tuiSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tuiSession.d.ts","sourceRoot":"","sources":["../../src/harness/tuiSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAY/G,wDAAwD;AACxD,MAAM,WAAW,QAAQ;IACrB,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,YAAY,EAAE,OAAO,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,8BAAsB,cAAe,YAAW,YAAY;IACxD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IACrC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,IAAI,CAA4B;IACxC,6EAA6E;IAC7E,OAAO,CAAC,oBAAoB,CAAqB;IACjD,2FAA2F;IAC3F,OAAO,CAAC,oBAAoB,CAAM;IAClC,OAAO,CAAC,YAAY,CAA+B;IAEnD,IAAI,KAAK,IAAI,cAAc,CAE1B;IAED,SAAS,aAAa,EAClB,QAAQ,EACR,OAAO,EACP,OAAO,GACV,EAAE;QACC,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,EAAE,mBAAmB,CAAC;QAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;KACvC;IAMD,6DAA6D;IAC7D,SAAS,CAAC,QAAQ,KAAK,KAAK,IAAI,MAAM,CAAC;IACvC,OAAO,KAAK,GAAG,GAEd;IACD,sDAAsD;IACtD,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IACnD,iEAAiE;IACjE,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IACxD,kEAAkE;IAClE,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE;IAEpE,qDAAqD;IAC/C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAKrE;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3E,qFAAqF;IACrF,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;cAI/B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAShD;;;OAGG;YACW,QAAQ;IA0GtB,4EAA4E;YAC9D,gBAAgB;YAahB,YAAY;YAIZ,cAAc;YAId,SAAS;IAgBvB,OAAO,CAAC,UAAU;IAWZ,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAGjC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { DEFAULT_PROMPT_TIMEOUT_MS, diffTranscript, sleep } from "./shared.js";
|
|
2
|
+
import { HarnessLogger } from "./logger.js";
|
|
3
|
+
/** Delay between typing a prompt and pressing Enter (agents drop a same-tick Enter). */
|
|
4
|
+
const TYPE_TO_ENTER_DELAY_MS = 800;
|
|
5
|
+
/** How long an idle+not-working composer may persist before we accept a turn as complete
|
|
6
|
+
* even without observing the turn start (guard against pathological no-activity hangs). */
|
|
7
|
+
const IDLE_BAIL_GRACE_MS = 30000;
|
|
8
|
+
/** How long the not-working+not-idle state must persist before we treat it as an awaited confirmation. */
|
|
9
|
+
const CONFIRMATION_PENDING_GRACE_MS = 1000;
|
|
10
|
+
/**
|
|
11
|
+
* Shared polling loop + terminal helpers for agent TUI sessions. Subclasses
|
|
12
|
+
* supply only the agent-specific bits: the label, the in-progress and idle
|
|
13
|
+
* markers, and how to extract tool calls for a completed turn.
|
|
14
|
+
*/
|
|
15
|
+
export class TuiSessionBase {
|
|
16
|
+
get state() {
|
|
17
|
+
return this.currentState;
|
|
18
|
+
}
|
|
19
|
+
constructor({ terminal, options, onState, }) {
|
|
20
|
+
/** Transcript snapshot the current turn started from (so chooseOption can continue it). */
|
|
21
|
+
this.currentTurnStartText = "";
|
|
22
|
+
this.currentState = "completed";
|
|
23
|
+
this.terminal = terminal;
|
|
24
|
+
this.options = options;
|
|
25
|
+
this.onState = onState ?? ((state) => this.printState(state));
|
|
26
|
+
}
|
|
27
|
+
get log() {
|
|
28
|
+
return (this._log ?? (this._log = new HarnessLogger(`${this.label}-tui`)));
|
|
29
|
+
}
|
|
30
|
+
/** Wait until the composer is ready for a prompt. */
|
|
31
|
+
async initialise() {
|
|
32
|
+
await this.waitIdleComposer(30000);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Answer a pending elicitation (`confirm`/`decline`) and run the turn to
|
|
36
|
+
* completion, returning the completed {@link AgentTurn}.
|
|
37
|
+
*/
|
|
38
|
+
async chooseOption(choice) {
|
|
39
|
+
await this.sendChoice(choice);
|
|
40
|
+
return this.pollTurn(false);
|
|
41
|
+
}
|
|
42
|
+
/** Codex interposes its own tool-approval prompt before the server's elicitation. */
|
|
43
|
+
isToolApproval(text) {
|
|
44
|
+
return /Allow the .* MCP server to run tool/.test(text);
|
|
45
|
+
}
|
|
46
|
+
async approveTool() {
|
|
47
|
+
await this.terminal.keyboard.press("Enter");
|
|
48
|
+
}
|
|
49
|
+
async prompt(prompt) {
|
|
50
|
+
// Enter after a short settle: agents drop a same-tick Enter, leaving the prompt in the composer.
|
|
51
|
+
this.currentTurnStartText = await this.transcriptText();
|
|
52
|
+
await this.terminal.type(prompt);
|
|
53
|
+
await sleep(TYPE_TO_ENTER_DELAY_MS);
|
|
54
|
+
await this.terminal.keyboard.press("Enter");
|
|
55
|
+
return this.pollTurn(true);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Poll until the turn completes or, when `stopOnElicitation`, the agent is
|
|
59
|
+
* awaiting a confirmation (auto-approving codex's tool-permission prompt).
|
|
60
|
+
*/
|
|
61
|
+
async pollTurn(stopOnElicitation) {
|
|
62
|
+
const startText = this.currentTurnStartText;
|
|
63
|
+
const timeoutMs = this.options.promptTimeoutMs ?? DEFAULT_PROMPT_TIMEOUT_MS;
|
|
64
|
+
const deadline = Date.now() + timeoutMs;
|
|
65
|
+
const startedAt = Date.now();
|
|
66
|
+
let lastError = "";
|
|
67
|
+
// A turn is only "real" once we have observed it begin: the working marker,
|
|
68
|
+
// the composer leaving its idle placeholder, or the transcript growing past
|
|
69
|
+
// the prompt echo. Without this the first poll after Enter (before the agent
|
|
70
|
+
// has rendered anything) satisfies `!working && composerIdle` and returns a
|
|
71
|
+
// bogus empty turn.
|
|
72
|
+
let sawTurnActivity = false;
|
|
73
|
+
let confirmationPendingSinceMs;
|
|
74
|
+
let prevDeltaLength;
|
|
75
|
+
let idleSinceMs;
|
|
76
|
+
while (Date.now() < deadline) {
|
|
77
|
+
const full = await this.transcriptText();
|
|
78
|
+
const delta = diffTranscript(full, startText);
|
|
79
|
+
const viewport = await this.viewportText();
|
|
80
|
+
const state = {
|
|
81
|
+
working: this.isWorking(viewport),
|
|
82
|
+
composerIdle: this.isComposerIdle(viewport),
|
|
83
|
+
viewport,
|
|
84
|
+
elapsedMs: Date.now() - startedAt,
|
|
85
|
+
};
|
|
86
|
+
this.onState(state);
|
|
87
|
+
if (state.working ||
|
|
88
|
+
!state.composerIdle ||
|
|
89
|
+
(prevDeltaLength !== undefined && delta.length > prevDeltaLength)) {
|
|
90
|
+
sawTurnActivity = true;
|
|
91
|
+
}
|
|
92
|
+
prevDeltaLength = delta.length;
|
|
93
|
+
// Agent is awaiting input when not working and not idle; require that
|
|
94
|
+
// state to persist a couple of polls so we do not fire mid-transition.
|
|
95
|
+
if (sawTurnActivity && !state.working && !state.composerIdle) {
|
|
96
|
+
confirmationPendingSinceMs ?? (confirmationPendingSinceMs = state.elapsedMs);
|
|
97
|
+
if (state.elapsedMs - confirmationPendingSinceMs >= CONFIRMATION_PENDING_GRACE_MS) {
|
|
98
|
+
if (this.isToolApproval(viewport)) {
|
|
99
|
+
await this.approveTool();
|
|
100
|
+
confirmationPendingSinceMs = undefined;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (stopOnElicitation) {
|
|
104
|
+
this.currentState = "elicitation";
|
|
105
|
+
this.lastShownDeltaLength = delta.length;
|
|
106
|
+
return { text: delta, toolCalls: [], state: "elicitation", confirmation: viewport };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
confirmationPendingSinceMs = undefined;
|
|
112
|
+
}
|
|
113
|
+
// Turn done when the in-progress marker is gone and the composer is idle.
|
|
114
|
+
if (!state.working && state.composerIdle) {
|
|
115
|
+
if (sawTurnActivity ||
|
|
116
|
+
(idleSinceMs !== undefined && state.elapsedMs - idleSinceMs >= IDLE_BAIL_GRACE_MS)) {
|
|
117
|
+
this.log.debug(`<<turn complete after ${state.elapsedMs}ms>>`);
|
|
118
|
+
this.onState({
|
|
119
|
+
...state,
|
|
120
|
+
viewport: `turn complete after ${state.elapsedMs}ms\n` + delta.slice(-1200),
|
|
121
|
+
});
|
|
122
|
+
return await this.buildTurn(startText);
|
|
123
|
+
}
|
|
124
|
+
idleSinceMs ?? (idleSinceMs = state.elapsedMs);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
idleSinceMs = undefined;
|
|
128
|
+
}
|
|
129
|
+
// Debug: stream the transcript as it grows.
|
|
130
|
+
if (delta.length > (this.lastShownDeltaLength ?? 0)) {
|
|
131
|
+
const chunk = delta.slice(this.lastShownDeltaLength ?? 0);
|
|
132
|
+
this.log.debug(chunk);
|
|
133
|
+
this.lastShownDeltaLength = delta.length;
|
|
134
|
+
}
|
|
135
|
+
// Session exited (e.g. the TUI crashed).
|
|
136
|
+
const exitCode = await this.terminal.getExitCode().catch(() => null);
|
|
137
|
+
if (exitCode !== null) {
|
|
138
|
+
lastError = `${this.label} TUI exited with code ${exitCode}`;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
lastError = "";
|
|
142
|
+
await sleep(500);
|
|
143
|
+
}
|
|
144
|
+
const text = await this.transcriptText();
|
|
145
|
+
const delta = diffTranscript(text, startText);
|
|
146
|
+
const message = lastError || `${this.label} TUI turn timed out after ${timeoutMs}ms`;
|
|
147
|
+
this.log.debug(`<<aborting: ${message}>>\n${delta}`);
|
|
148
|
+
this.currentState = "completed";
|
|
149
|
+
// Fail loudly with the raw transcript attached for diagnosis.
|
|
150
|
+
return {
|
|
151
|
+
text: `ERROR: ${message}${text ? "\n\n--- terminal content ---\n" + text : ""}`,
|
|
152
|
+
toolCalls: [],
|
|
153
|
+
state: "completed",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** Whether the composer has returned to its idle prompt (turn finished). */
|
|
157
|
+
async waitIdleComposer(timeoutMs) {
|
|
158
|
+
const deadline = Date.now() + timeoutMs;
|
|
159
|
+
let text = "";
|
|
160
|
+
while (Date.now() < deadline) {
|
|
161
|
+
text = await this.viewportText();
|
|
162
|
+
if (!this.isWorking(text) && this.isComposerIdle(text)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await sleep(1000);
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`${this.label} TUI composer did not become idle within ${timeoutMs}ms:\n${text.slice(-2000)}`);
|
|
168
|
+
}
|
|
169
|
+
async viewportText() {
|
|
170
|
+
return this.terminal.text({ full: false });
|
|
171
|
+
}
|
|
172
|
+
async transcriptText() {
|
|
173
|
+
return this.terminal.text({ full: true });
|
|
174
|
+
}
|
|
175
|
+
async buildTurn(startText) {
|
|
176
|
+
const full = await this.transcriptText();
|
|
177
|
+
let delta = diffTranscript(full, startText);
|
|
178
|
+
// Append the viewport: the final reply may still be live, not in the scrollback.
|
|
179
|
+
const viewport = await this.viewportText();
|
|
180
|
+
if (viewport && !delta.includes(viewport.replace(/\s+$/, ""))) {
|
|
181
|
+
delta = delta + "\n" + viewport;
|
|
182
|
+
}
|
|
183
|
+
this.currentState = "completed";
|
|
184
|
+
return {
|
|
185
|
+
text: delta,
|
|
186
|
+
toolCalls: this.extractToolCalls(delta),
|
|
187
|
+
state: "completed",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
printState(state) {
|
|
191
|
+
const tail = state.viewport
|
|
192
|
+
.split("\n")
|
|
193
|
+
.filter((l) => l.trim())
|
|
194
|
+
.slice(-7);
|
|
195
|
+
this.log.debug(`t=${Math.round(state.elapsedMs / 1000)}s working=${state.working} idle=${state.composerIdle}\n` +
|
|
196
|
+
tail.join("\n"));
|
|
197
|
+
}
|
|
198
|
+
async dispose() {
|
|
199
|
+
await this.terminal.closeQuiet();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=tuiSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tuiSession.js","sourceRoot":"","sources":["../../src/harness/tuiSession.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export interface ToolCallRecord {
|
|
2
|
+
/** Normalized tool name, e.g. "mcp__server__list-databases" -> "list-databases". */
|
|
3
|
+
name: string;
|
|
4
|
+
/** The raw tool name as reported by the harness, when it differs. */
|
|
5
|
+
rawName?: string;
|
|
6
|
+
/** Tool arguments as reported by the harness (best-effort). */
|
|
7
|
+
args?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentTurn {
|
|
10
|
+
/** Raw terminal content captured at the end of the turn (scrollback delta + live viewport). */
|
|
11
|
+
text: string;
|
|
12
|
+
/** Tool calls observed during the turn, deduplicated. */
|
|
13
|
+
toolCalls: ToolCallRecord[];
|
|
14
|
+
/** "elicitation" when the turn paused for a confirmation; otherwise "completed". */
|
|
15
|
+
state: AgentTurnState;
|
|
16
|
+
/** Raw confirmation screen when `state` is "elicitation". */
|
|
17
|
+
confirmation?: string;
|
|
18
|
+
}
|
|
19
|
+
/** How a turn ended: completed normally, or paused for an elicitation confirmation. */
|
|
20
|
+
export type AgentTurnState = "completed" | "elicitation";
|
|
21
|
+
export interface AgentHarnessOptions {
|
|
22
|
+
/**
|
|
23
|
+
* URL of the MongoDB MCP server (streamable HTTP), e.g.
|
|
24
|
+
* `http://127.0.0.1:PORT/mcp`. Mutually exclusive with `stdioServer`.
|
|
25
|
+
*/
|
|
26
|
+
serverUrl?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Stdio server spec; when set, the harness spawns the MCP server itself.
|
|
29
|
+
* Mutually exclusive with `serverUrl`.
|
|
30
|
+
*/
|
|
31
|
+
stdioServer?: {
|
|
32
|
+
command: string;
|
|
33
|
+
args: string[];
|
|
34
|
+
env: Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
/** Name under which the MCP server is registered with the harness. */
|
|
37
|
+
mcpServerName?: string;
|
|
38
|
+
/** Working directory for the harness session (created by the test). */
|
|
39
|
+
workDir: string;
|
|
40
|
+
/** Optional model override (harness/provider default used when omitted). */
|
|
41
|
+
model?: string;
|
|
42
|
+
/** Timeout for a single `prompt()` call (ms). */
|
|
43
|
+
promptTimeoutMs?: number;
|
|
44
|
+
/** Dump the generated config (redacted) to the test logs. */
|
|
45
|
+
debug?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface AgentHarness {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
/**
|
|
50
|
+
* True when the binary + auth are available (tests skip otherwise).
|
|
51
|
+
* Must be synchronous: `describe.skipIf` treats a Promise as truthy.
|
|
52
|
+
*/
|
|
53
|
+
isAvailable(): boolean;
|
|
54
|
+
start(options: AgentHarnessOptions): Promise<AgentSession>;
|
|
55
|
+
}
|
|
56
|
+
export interface AgentSession {
|
|
57
|
+
/**
|
|
58
|
+
* Run one turn: submit the prompt and wait. Resolves with
|
|
59
|
+
* `state: "elicitation"` when the agent is awaiting a confirmation
|
|
60
|
+
* (call {@link AgentSession.chooseOption}), else `state: "completed"`.
|
|
61
|
+
*/
|
|
62
|
+
prompt(prompt: string): Promise<AgentTurn>;
|
|
63
|
+
/**
|
|
64
|
+
* Answer a pending elicitation (`confirm`/`decline`) and run the turn to
|
|
65
|
+
* completion, returning the completed `AgentTurn`.
|
|
66
|
+
*/
|
|
67
|
+
chooseOption(choice: "confirm" | "decline"): Promise<AgentTurn>;
|
|
68
|
+
/** Current state of the last turn: "completed" or "elicitation". */
|
|
69
|
+
readonly state: AgentTurnState;
|
|
70
|
+
/** Release any resources. */
|
|
71
|
+
dispose(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
/** Config generator for an agent CLI harness: builds agent-specific config content for a hermetic session. */
|
|
74
|
+
export interface AgentHarnessConfig {
|
|
75
|
+
/**
|
|
76
|
+
* Env var pointing the agent at its config home (e.g. `"CODEX_HOME"`);
|
|
77
|
+
* set to a per-session hermetic directory.
|
|
78
|
+
*/
|
|
79
|
+
readonly homeDirEnvVar: string;
|
|
80
|
+
/** Config file within the home dir (e.g. `"config.toml"`); empty when the config is the whole home dir. */
|
|
81
|
+
readonly configFileName: string;
|
|
82
|
+
/** The developer's real config home (e.g. `~/.codex`), read without mutating it. */
|
|
83
|
+
getHostHomeDir(): string;
|
|
84
|
+
/**
|
|
85
|
+
* Build the agent-specific config file content for a hermetic session.
|
|
86
|
+
* @param sessionHomeDir per-session hermetic home the file is written into
|
|
87
|
+
* (configs can reference files placed next to it).
|
|
88
|
+
*/
|
|
89
|
+
buildConfig(options: AgentHarnessOptions, sessionHomeDir: string): string;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/harness/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC3B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACtB,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,oFAAoF;IACpF,KAAK,EAAE,cAAc,CAAC;IACtB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,uFAAuF;AACvF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,aAAa,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IAC/E,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC;IACvB,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,YAAY;IACzB;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3C;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAChE,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,6BAA6B;IAC7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,8GAA8G;AAC9G,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,2GAA2G;IAC3G,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,oFAAoF;IACpF,cAAc,IAAI,MAAM,CAAC;IAEzB;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/harness/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harnesses.d.ts","sourceRoot":"","sources":["../src/harnesses.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,SAAS,CAAC,UAAU,YAAY,CAAC,EAAwC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harnesses.js","sourceRoot":"","sources":["../src/harnesses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAGrE,MAAM,CAAC,MAAM,eAAe,GAAwC,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ClaudeTuiHarness } from "./harness/claude/claudeHarness.js";
|
|
2
|
+
import { CodexTuiHarness } from "./harness/codex/codexHarness.js";
|
|
3
|
+
export { useAgent, type AgentContext } from "./useAgent.js";
|
|
4
|
+
export { AGENT_HARNESSES } from "./harnesses.js";
|
|
5
|
+
export type { AgentHarness, AgentHarnessConfig, AgentHarnessOptions, AgentSession, AgentTurn, AgentTurnState, ToolCallRecord, } from "./harness/types.js";
|
|
6
|
+
export { ClaudeTuiHarness, CodexTuiHarness };
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EACR,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,SAAS,EACT,cAAc,EACd,cAAc,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ClaudeTuiHarness } from "./harness/claude/claudeHarness.js";
|
|
2
|
+
import { CodexTuiHarness } from "./harness/codex/codexHarness.js";
|
|
3
|
+
export { useAgent } from "./useAgent.js";
|
|
4
|
+
export { AGENT_HARNESSES } from "./harnesses.js";
|
|
5
|
+
export { ClaudeTuiHarness, CodexTuiHarness };
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAUjD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentHarness, AgentHarnessOptions } from "./harness/types.js";
|
|
2
|
+
export interface AgentContext {
|
|
3
|
+
harness: AgentHarness;
|
|
4
|
+
workDir: () => string;
|
|
5
|
+
/** Cached availability probe; the skip gate doesn't re-probe per test. */
|
|
6
|
+
isHarnessAvailable: () => boolean;
|
|
7
|
+
buildOptions: (overrides?: Partial<AgentHarnessOptions>) => AgentHarnessOptions;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Agent e2e suite setup: temp workdir, availability skip gate, and base
|
|
11
|
+
* session options. Call inside a `describe` block with the harness to drive.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useAgent({ harness }: {
|
|
14
|
+
harness: AgentHarness;
|
|
15
|
+
}): AgentContext;
|
|
16
|
+
//# sourceMappingURL=useAgent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAgent.d.ts","sourceRoot":"","sources":["../src/useAgent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,0EAA0E;IAC1E,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAClC,YAAY,EAAE,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,KAAK,mBAAmB,CAAC;CACnF;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,GAAG,YAAY,CA0C7E"}
|
package/dist/useAgent.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterAll, beforeAll, beforeEach } from "vitest";
|
|
5
|
+
import { DEFAULT_PROMPT_TIMEOUT_MS } from "./harness/shared.js";
|
|
6
|
+
/**
|
|
7
|
+
* Agent e2e suite setup: temp workdir, availability skip gate, and base
|
|
8
|
+
* session options. Call inside a `describe` block with the harness to drive.
|
|
9
|
+
*/
|
|
10
|
+
export function useAgent({ harness }) {
|
|
11
|
+
let workDir = "";
|
|
12
|
+
let harnessAvailable;
|
|
13
|
+
const isHarnessAvailable = () => (harnessAvailable ?? (harnessAvailable = harness.isAvailable()));
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
workDir = await fs.mkdtemp(path.join(os.tmpdir(), "e2e-tests-"));
|
|
16
|
+
});
|
|
17
|
+
beforeEach((ctx) => {
|
|
18
|
+
if (!isHarnessAvailable()) {
|
|
19
|
+
ctx.skip(`harness '${harness.name}' not available; agent e2e suite skipped`);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
if (workDir) {
|
|
24
|
+
await fs.rm(workDir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
harness,
|
|
29
|
+
workDir: () => workDir,
|
|
30
|
+
isHarnessAvailable,
|
|
31
|
+
buildOptions: (overrides = {}) => ({
|
|
32
|
+
workDir,
|
|
33
|
+
// Model: per-harness env var > shared AGENT_E2E_MODEL > harness default.
|
|
34
|
+
...(() => {
|
|
35
|
+
const model = (harness.name === "codex-tui" && process.env.AGENT_E2E_CODEX_MODEL) ||
|
|
36
|
+
(harness.name === "claude-tui" && process.env.AGENT_E2E_CLAUDE_MODEL) ||
|
|
37
|
+
process.env.AGENT_E2E_MODEL;
|
|
38
|
+
return model ? { model } : {};
|
|
39
|
+
})(),
|
|
40
|
+
promptTimeoutMs: DEFAULT_PROMPT_TIMEOUT_MS,
|
|
41
|
+
// AGENT_E2E_DEBUG: per-session debug dumps across the suite.
|
|
42
|
+
debug: !!process.env.AGENT_E2E_DEBUG,
|
|
43
|
+
...overrides,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=useAgent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAgent.js","sourceRoot":"","sources":["../src/useAgent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAWhE;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,OAAO,EAA6B;IAC3D,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,IAAI,gBAAqC,CAAC;IAC1C,MAAM,kBAAkB,GAAG,GAAY,EAAE,CAAC,CAAC,gBAAgB,KAAhB,gBAAgB,GAAK,OAAO,CAAC,WAAW,EAAE,EAAC,CAAC;IAEvF,SAAS,CAAC,KAAK,IAAI,EAAE;QACjB,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE;QACf,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;YACxB,GAAG,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,IAAI,0CAA0C,CAAC,CAAC;QACjF,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QAChB,IAAI,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACH,OAAO;QACP,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO;QACtB,kBAAkB;QAClB,YAAY,EAAE,CAAC,SAAS,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/B,OAAO;YACP,yEAAyE;YACzE,GAAG,CAAC,GAAuB,EAAE;gBACzB,MAAM,KAAK,GACP,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;oBACnE,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;oBACrE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;gBAChC,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClC,CAAC,CAAC,EAAE;YACJ,eAAe,EAAE,yBAAyB;YAC1C,6DAA6D;YAC7D,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe;YACpC,GAAG,SAAS;SACf,CAAC;KACkB,CAAC;AAC7B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongodb-js/harness-tester",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Tooling to drive real LLM coding-agent CLIs (codex, claude) interactively through their TUIs for end-to-end tests",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,10 +20,6 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist"
|
|
22
22
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "tsc --build tsconfig.build.json",
|
|
25
|
-
"check": "tsc --noEmit --project tsconfig.json"
|
|
26
|
-
},
|
|
27
23
|
"dependencies": {
|
|
28
24
|
"@microsoft/tui-test": "0.1.0-beta.2",
|
|
29
25
|
"vitest": "^4.1.5"
|
|
@@ -34,5 +30,9 @@
|
|
|
34
30
|
},
|
|
35
31
|
"publishConfig": {
|
|
36
32
|
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc --build tsconfig.build.json",
|
|
36
|
+
"check": "tsc --noEmit --project tsconfig.json"
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|