@elench/testkit 0.1.102 → 0.1.104
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/lib/cli/assistant/providers/claude.mjs +1 -2
- package/lib/cli/assistant/providers/codex.mjs +51 -17
- package/lib/cli/assistant/providers/shared.mjs +9 -0
- package/lib/cli/assistant/session.mjs +6 -1
- package/lib/cli/assistant/state.mjs +26 -18
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +5 -5
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
buildToolEvent,
|
|
6
6
|
createHostedSessionRunner,
|
|
7
7
|
} from "./shared.mjs";
|
|
8
|
-
import { providerAssistantDelta, providerAssistantFinal,
|
|
8
|
+
import { providerAssistantDelta, providerAssistantFinal, providerToolStart } from "./events.mjs";
|
|
9
9
|
|
|
10
10
|
export function startClaudeHostedSession({
|
|
11
11
|
command = "claude",
|
|
@@ -130,7 +130,6 @@ export function parseClaudePayload(payload) {
|
|
|
130
130
|
} else if (typeof payload.result === "string") {
|
|
131
131
|
const event = providerAssistantFinal(payload.result);
|
|
132
132
|
if (event) events.push(event);
|
|
133
|
-
events.push(providerToolEnd("claude", { status: "ok", durationMs: payload.duration_ms || null }));
|
|
134
133
|
}
|
|
135
134
|
return events;
|
|
136
135
|
}
|
|
@@ -4,12 +4,18 @@ import path from "path";
|
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import {
|
|
6
6
|
buildErrorEvent,
|
|
7
|
-
buildStatusEvent,
|
|
8
7
|
buildToolEvent,
|
|
9
8
|
createHostedSessionRunner,
|
|
10
9
|
readTextFileIfPresent,
|
|
11
10
|
} from "./shared.mjs";
|
|
12
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
providerAssistantDelta,
|
|
13
|
+
providerAssistantFinal,
|
|
14
|
+
providerStatus,
|
|
15
|
+
providerToolEnd,
|
|
16
|
+
providerToolStart,
|
|
17
|
+
providerToolUpdate,
|
|
18
|
+
} from "./events.mjs";
|
|
13
19
|
|
|
14
20
|
export function startCodexHostedSession({
|
|
15
21
|
command = "codex",
|
|
@@ -99,19 +105,22 @@ export function parseCodexPayload(payload) {
|
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
if (type === "thread.started") {
|
|
102
|
-
const event =
|
|
108
|
+
const event = providerStatus(
|
|
109
|
+
payload.thread_id ? `Codex thread ${payload.thread_id} started` : "Codex thread started",
|
|
110
|
+
{ transient: true }
|
|
111
|
+
);
|
|
103
112
|
if (event) events.push(event);
|
|
104
113
|
return events;
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
if (type === "turn.started") {
|
|
108
|
-
const event =
|
|
117
|
+
const event = providerStatus("Codex turn started", { transient: true });
|
|
109
118
|
if (event) events.push(event);
|
|
110
119
|
return events;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
if (type === "turn.completed") {
|
|
114
|
-
const event =
|
|
123
|
+
const event = providerStatus("Codex turn completed", { transient: true, usage: payload.usage || null });
|
|
115
124
|
if (event) events.push(event);
|
|
116
125
|
return events;
|
|
117
126
|
}
|
|
@@ -146,31 +155,46 @@ export function parseCodexPayload(payload) {
|
|
|
146
155
|
return events;
|
|
147
156
|
}
|
|
148
157
|
|
|
149
|
-
const statusEvent =
|
|
158
|
+
const statusEvent = providerStatus(type ? `Codex event: ${type}` : JSON.stringify(payload));
|
|
150
159
|
if (statusEvent) events.push(statusEvent);
|
|
151
160
|
return events;
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
function codexItemStartedEvent(item) {
|
|
155
164
|
if (!item || typeof item !== "object") return null;
|
|
156
|
-
if (item.type === "command_execution"
|
|
157
|
-
return providerToolStart(
|
|
165
|
+
if (item.type === "command_execution") {
|
|
166
|
+
return providerToolStart("command", {
|
|
167
|
+
id: item.id || null,
|
|
168
|
+
input: item.command || item.input || null,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (item.type === "tool_call") {
|
|
172
|
+
return providerToolStart(item.name || item.type, {
|
|
158
173
|
id: item.id || null,
|
|
159
174
|
input: item.arguments || item.input || null,
|
|
160
175
|
});
|
|
161
176
|
}
|
|
162
|
-
if (item.type === "agent_message") return
|
|
163
|
-
if (item.type) return
|
|
177
|
+
if (item.type === "agent_message") return providerStatus("Codex started response", { transient: true });
|
|
178
|
+
if (item.type) return providerStatus(`Codex started ${item.type}`);
|
|
164
179
|
return null;
|
|
165
180
|
}
|
|
166
181
|
|
|
167
182
|
function codexItemUpdatedEvent(item) {
|
|
168
183
|
if (!item || typeof item !== "object") return null;
|
|
169
184
|
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
170
|
-
return providerAssistantDelta(item.text
|
|
185
|
+
return providerAssistantDelta(item.text, {
|
|
186
|
+
id: item.id || null,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (item.type === "command_execution") {
|
|
190
|
+
return providerToolUpdate("command", {
|
|
191
|
+
id: item.id || null,
|
|
192
|
+
text: item.output || item.status || null,
|
|
193
|
+
data: item,
|
|
194
|
+
});
|
|
171
195
|
}
|
|
172
|
-
if (item.type === "
|
|
173
|
-
return providerToolUpdate(item.
|
|
196
|
+
if (item.type === "tool_call") {
|
|
197
|
+
return providerToolUpdate(item.name || item.type, {
|
|
174
198
|
id: item.id || null,
|
|
175
199
|
text: item.output || item.status || null,
|
|
176
200
|
data: item,
|
|
@@ -182,16 +206,26 @@ function codexItemUpdatedEvent(item) {
|
|
|
182
206
|
function codexItemCompletedEvent(item) {
|
|
183
207
|
if (!item || typeof item !== "object") return null;
|
|
184
208
|
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
185
|
-
return providerAssistantFinal(item.text
|
|
209
|
+
return providerAssistantFinal(item.text, {
|
|
210
|
+
id: item.id || null,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (item.type === "command_execution") {
|
|
214
|
+
return providerToolEnd("command", {
|
|
215
|
+
id: item.id || null,
|
|
216
|
+
status: item.status === "failed" ? "error" : "ok",
|
|
217
|
+
output: item.output || null,
|
|
218
|
+
data: item,
|
|
219
|
+
});
|
|
186
220
|
}
|
|
187
|
-
if (item.type === "
|
|
188
|
-
return providerToolEnd(item.
|
|
221
|
+
if (item.type === "tool_call") {
|
|
222
|
+
return providerToolEnd(item.name || item.type, {
|
|
189
223
|
id: item.id || null,
|
|
190
224
|
status: item.status === "failed" ? "error" : "ok",
|
|
191
225
|
output: item.output || null,
|
|
192
226
|
data: item,
|
|
193
227
|
});
|
|
194
228
|
}
|
|
195
|
-
if (item.type) return
|
|
229
|
+
if (item.type) return providerStatus(`Codex completed ${item.type}`);
|
|
196
230
|
return null;
|
|
197
231
|
}
|
|
@@ -40,6 +40,7 @@ export function createHostedSessionRunner({
|
|
|
40
40
|
emit(providerEvent("session-start"));
|
|
41
41
|
|
|
42
42
|
const stdoutReader = readline.createInterface({ input: child.stdout });
|
|
43
|
+
const stdoutClosed = waitForReaderClose(stdoutReader);
|
|
43
44
|
stdoutReader.on("line", (line) => {
|
|
44
45
|
onRawLine?.({ provider, stream: "stdout", line });
|
|
45
46
|
const parsed = tryParseJson(line);
|
|
@@ -53,6 +54,7 @@ export function createHostedSessionRunner({
|
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
const stderrReader = readline.createInterface({ input: child.stderr });
|
|
57
|
+
const stderrClosed = waitForReaderClose(stderrReader);
|
|
56
58
|
stderrReader.on("line", (line) => {
|
|
57
59
|
onRawLine?.({ provider, stream: "stderr", line });
|
|
58
60
|
if (shouldIgnoreStatus?.(line)) return;
|
|
@@ -61,6 +63,7 @@ export function createHostedSessionRunner({
|
|
|
61
63
|
|
|
62
64
|
const completion = (async () => {
|
|
63
65
|
const result = await child;
|
|
66
|
+
await Promise.all([stdoutClosed, stderrClosed]);
|
|
64
67
|
const fileFinalText = readFinalText ? readFinalText(result) : null;
|
|
65
68
|
const resolvedFinalText = fileFinalText || finalText || assistantText.trim() || null;
|
|
66
69
|
if ((result.exitCode ?? 0) !== 0) {
|
|
@@ -97,6 +100,12 @@ export function createHostedSessionRunner({
|
|
|
97
100
|
};
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
function waitForReaderClose(reader) {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
reader.once("close", resolve);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
100
109
|
export function tryParseJson(line) {
|
|
101
110
|
const normalized = String(line || "").trim();
|
|
102
111
|
if (!normalized) return null;
|
|
@@ -53,7 +53,12 @@ export async function runAssistantConversationTurn({
|
|
|
53
53
|
effort: runtimeSettings.effort || null,
|
|
54
54
|
});
|
|
55
55
|
onStatus?.(`Thinking with ${resolvedProvider}...`);
|
|
56
|
-
onProviderEvent?.({
|
|
56
|
+
onProviderEvent?.({
|
|
57
|
+
type: "status",
|
|
58
|
+
provider: resolvedProvider,
|
|
59
|
+
text: `Thinking with ${resolvedProvider}...`,
|
|
60
|
+
transient: true,
|
|
61
|
+
});
|
|
57
62
|
|
|
58
63
|
observer.start();
|
|
59
64
|
try {
|
|
@@ -635,12 +635,17 @@ function handleAssistantToolEvent(state, event, appendMessage) {
|
|
|
635
635
|
function createProviderTurnState() {
|
|
636
636
|
return {
|
|
637
637
|
assistantMessageId: null,
|
|
638
|
+
assistantMessageIdsByProviderItem: new Map(),
|
|
638
639
|
lastActivityText: null,
|
|
639
640
|
};
|
|
640
641
|
}
|
|
641
642
|
|
|
642
643
|
function handleProviderEvent(turn, event, { appendMessage, updateMessage, setStatus } = {}) {
|
|
643
644
|
if (!event) return;
|
|
645
|
+
if (event.transient || event.display === false) {
|
|
646
|
+
if (event.type === "status" && event.text) setStatus?.(event.text);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
644
649
|
if (event.type === "assistant-delta") {
|
|
645
650
|
appendAssistantDelta(turn, event, { appendMessage, updateMessage });
|
|
646
651
|
setStatus?.(`${event.provider || "provider"} responding`);
|
|
@@ -652,21 +657,11 @@ function handleProviderEvent(turn, event, { appendMessage, updateMessage, setSta
|
|
|
652
657
|
return;
|
|
653
658
|
}
|
|
654
659
|
if (event.type === "session-start") {
|
|
655
|
-
|
|
656
|
-
role: "provider-activity",
|
|
657
|
-
title: formatProviderName(event.provider),
|
|
658
|
-
text: "Session started",
|
|
659
|
-
data: event,
|
|
660
|
-
}, { appendMessage, setStatus });
|
|
660
|
+
setStatus?.(`${event.provider || "provider"} session started`);
|
|
661
661
|
return;
|
|
662
662
|
}
|
|
663
663
|
if (event.type === "session-end") {
|
|
664
|
-
|
|
665
|
-
role: "provider-activity",
|
|
666
|
-
title: formatProviderName(event.provider),
|
|
667
|
-
text: Number.isInteger(event.exitCode) ? `Session ended with exit code ${event.exitCode}` : "Session ended",
|
|
668
|
-
data: event,
|
|
669
|
-
}, { appendMessage, setStatus });
|
|
664
|
+
setStatus?.(`${event.provider || "provider"} session ended`);
|
|
670
665
|
return;
|
|
671
666
|
}
|
|
672
667
|
if (event.type === "status") {
|
|
@@ -699,15 +694,19 @@ function handleProviderEvent(turn, event, { appendMessage, updateMessage, setSta
|
|
|
699
694
|
}
|
|
700
695
|
|
|
701
696
|
function appendAssistantDelta(turn, event, { appendMessage, updateMessage }) {
|
|
702
|
-
|
|
703
|
-
|
|
697
|
+
const messageKey = providerAssistantMessageKey(event);
|
|
698
|
+
let messageId = messageKey ? turn.assistantMessageIdsByProviderItem.get(messageKey) : turn.assistantMessageId;
|
|
699
|
+
if (!messageId) {
|
|
700
|
+
messageId = appendMessage({
|
|
704
701
|
role: "assistant",
|
|
705
702
|
status: "streaming",
|
|
706
703
|
provider: event.provider || null,
|
|
707
704
|
text: "",
|
|
708
705
|
});
|
|
706
|
+
if (messageKey) turn.assistantMessageIdsByProviderItem.set(messageKey, messageId);
|
|
707
|
+
else turn.assistantMessageId = messageId;
|
|
709
708
|
}
|
|
710
|
-
updateMessage(
|
|
709
|
+
updateMessage(messageId, (message) => ({
|
|
711
710
|
text: `${message.text || ""}${event.text || ""}`,
|
|
712
711
|
status: "streaming",
|
|
713
712
|
}));
|
|
@@ -715,20 +714,29 @@ function appendAssistantDelta(turn, event, { appendMessage, updateMessage }) {
|
|
|
715
714
|
|
|
716
715
|
function finalizeAssistantMessage(turn, event, { appendMessage, updateMessage }) {
|
|
717
716
|
const finalText = event.text || "";
|
|
718
|
-
|
|
719
|
-
|
|
717
|
+
const messageKey = providerAssistantMessageKey(event);
|
|
718
|
+
let messageId = messageKey ? turn.assistantMessageIdsByProviderItem.get(messageKey) : turn.assistantMessageId;
|
|
719
|
+
if (!messageId) {
|
|
720
|
+
messageId = appendMessage({
|
|
720
721
|
role: "assistant",
|
|
721
722
|
provider: event.provider || null,
|
|
722
723
|
text: finalText,
|
|
723
724
|
});
|
|
725
|
+
if (messageKey) turn.assistantMessageIdsByProviderItem.set(messageKey, messageId);
|
|
726
|
+
else turn.assistantMessageId = messageId;
|
|
724
727
|
return;
|
|
725
728
|
}
|
|
726
|
-
updateMessage(
|
|
729
|
+
updateMessage(messageId, (message) => ({
|
|
727
730
|
text: finalText || message.text || "",
|
|
728
731
|
status: null,
|
|
729
732
|
}));
|
|
730
733
|
}
|
|
731
734
|
|
|
735
|
+
function providerAssistantMessageKey(event) {
|
|
736
|
+
if (!event?.id) return null;
|
|
737
|
+
return `${event.provider || "provider"}:${event.id}`;
|
|
738
|
+
}
|
|
739
|
+
|
|
732
740
|
function appendProviderActivity(turn, message, { appendMessage, setStatus }) {
|
|
733
741
|
const text = String(message.text || "").trim();
|
|
734
742
|
if (!text) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.104",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.104"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.104",
|
|
4
4
|
"description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
92
92
|
"@babel/code-frame": "^7.29.0",
|
|
93
|
-
"@elench/next-analysis": "0.1.
|
|
94
|
-
"@elench/testkit-bridge": "0.1.
|
|
95
|
-
"@elench/testkit-protocol": "0.1.
|
|
96
|
-
"@elench/ts-analysis": "0.1.
|
|
93
|
+
"@elench/next-analysis": "0.1.104",
|
|
94
|
+
"@elench/testkit-bridge": "0.1.104",
|
|
95
|
+
"@elench/testkit-protocol": "0.1.104",
|
|
96
|
+
"@elench/ts-analysis": "0.1.104",
|
|
97
97
|
"@oclif/core": "^4.10.6",
|
|
98
98
|
"esbuild": "^0.25.11",
|
|
99
99
|
"execa": "^9.5.0",
|