@elench/testkit 0.1.101 → 0.1.102
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/app.mjs +6 -0
- package/lib/cli/assistant/providers/claude.mjs +42 -7
- package/lib/cli/assistant/providers/codex.mjs +87 -9
- package/lib/cli/assistant/providers/events.mjs +71 -0
- package/lib/cli/assistant/providers/index.mjs +5 -4
- package/lib/cli/assistant/providers/shared.mjs +40 -21
- package/lib/cli/assistant/session.mjs +46 -8
- package/lib/cli/assistant/settings.mjs +2 -1
- package/lib/cli/assistant/state.mjs +174 -5
- package/lib/cli/assistant/transcript-text.mjs +35 -0
- package/lib/cli/assistant/view-model.mjs +11 -0
- 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 +14 -8
|
@@ -281,6 +281,9 @@ function colorWelcomeValue(label, value) {
|
|
|
281
281
|
function colorMarker(block) {
|
|
282
282
|
if (block.kind === "user") return cyan(block.marker);
|
|
283
283
|
if (block.kind === "system") return red(block.marker);
|
|
284
|
+
if (block.kind === "provider-error") return red(block.marker);
|
|
285
|
+
if (block.kind === "provider-activity") return dim(block.marker);
|
|
286
|
+
if (block.kind === "provider-tool") return yellow(block.marker);
|
|
284
287
|
if (block.kind === "tool-running") return yellow(block.marker);
|
|
285
288
|
if (block.kind === "testkit-run") return green(block.marker);
|
|
286
289
|
return block.marker;
|
|
@@ -289,6 +292,9 @@ function colorMarker(block) {
|
|
|
289
292
|
function colorBlockText(block, text) {
|
|
290
293
|
if (block.kind === "user") return text;
|
|
291
294
|
if (block.kind === "system") return red(text);
|
|
295
|
+
if (block.kind === "provider-error") return red(text);
|
|
296
|
+
if (block.kind === "provider-activity") return dim(text);
|
|
297
|
+
if (block.kind === "provider-tool") return yellow(text);
|
|
292
298
|
if (block.kind === "tool-running") return yellow(text);
|
|
293
299
|
return text;
|
|
294
300
|
}
|
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
buildStatusEvent,
|
|
5
5
|
buildToolEvent,
|
|
6
6
|
createHostedSessionRunner,
|
|
7
|
-
extractTextFragments,
|
|
8
7
|
} from "./shared.mjs";
|
|
8
|
+
import { providerAssistantDelta, providerAssistantFinal, providerToolEnd, providerToolStart } from "./events.mjs";
|
|
9
9
|
|
|
10
10
|
export function startClaudeHostedSession({
|
|
11
11
|
command = "claude",
|
|
12
12
|
cwd,
|
|
13
13
|
prompt,
|
|
14
14
|
onEvent,
|
|
15
|
+
onRawLine,
|
|
15
16
|
purpose = "assistant",
|
|
16
17
|
model = null,
|
|
17
18
|
effort = null,
|
|
@@ -49,6 +50,7 @@ export function startClaudeHostedSession({
|
|
|
49
50
|
provider: "claude",
|
|
50
51
|
child,
|
|
51
52
|
onEvent,
|
|
53
|
+
onRawLine,
|
|
52
54
|
parsePayload: parseClaudePayload,
|
|
53
55
|
readFinalText(result) {
|
|
54
56
|
return readClaudeFinalText(result?.stdout || "") || null;
|
|
@@ -77,7 +79,20 @@ export function parseClaudePayload(payload) {
|
|
|
77
79
|
const streamEvent = payload.event || {};
|
|
78
80
|
if (streamEvent.type === "content_block_delta" && streamEvent.delta?.type === "text_delta") {
|
|
79
81
|
const text = String(streamEvent.delta.text || "");
|
|
80
|
-
|
|
82
|
+
const event = providerAssistantDelta(text);
|
|
83
|
+
if (event) events.push(event);
|
|
84
|
+
return events;
|
|
85
|
+
}
|
|
86
|
+
if (streamEvent.type === "content_block_start" && streamEvent.content_block?.type === "tool_use") {
|
|
87
|
+
const tool = streamEvent.content_block;
|
|
88
|
+
const event = providerToolStart(
|
|
89
|
+
tool.name || tool.tool_name || "tool_use",
|
|
90
|
+
{
|
|
91
|
+
id: tool.id || streamEvent.index,
|
|
92
|
+
input: tool.input || null,
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
if (event) events.push(event);
|
|
81
96
|
return events;
|
|
82
97
|
}
|
|
83
98
|
if (streamEvent.type === "tool_use" || streamEvent.content_block?.type === "tool_use") {
|
|
@@ -102,10 +117,9 @@ export function parseClaudePayload(payload) {
|
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
if (type === "assistant") {
|
|
105
|
-
const fragments =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
120
|
+
const fragments = extractClaudeTextFragments(payload.message?.content || payload.content || []);
|
|
121
|
+
const event = providerAssistantFinal(fragments.join(""));
|
|
122
|
+
if (event) events.push(event);
|
|
109
123
|
return events;
|
|
110
124
|
}
|
|
111
125
|
|
|
@@ -113,6 +127,10 @@ export function parseClaudePayload(payload) {
|
|
|
113
127
|
if (payload.is_error || payload.subtype === "error") {
|
|
114
128
|
const event = buildErrorEvent(payload.result || payload.error || "Claude command failed");
|
|
115
129
|
if (event) events.push(event);
|
|
130
|
+
} else if (typeof payload.result === "string") {
|
|
131
|
+
const event = providerAssistantFinal(payload.result);
|
|
132
|
+
if (event) events.push(event);
|
|
133
|
+
events.push(providerToolEnd("claude", { status: "ok", durationMs: payload.duration_ms || null }));
|
|
116
134
|
}
|
|
117
135
|
return events;
|
|
118
136
|
}
|
|
@@ -147,10 +165,27 @@ export function readClaudeFinalText(stdout) {
|
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
if (payload.type === "assistant") {
|
|
150
|
-
const fragments =
|
|
168
|
+
const fragments = extractClaudeTextFragments(payload.message?.content || payload.content || []);
|
|
151
169
|
if (fragments.length > 0) fallback = fragments.join("");
|
|
152
170
|
}
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
return fallback;
|
|
156
174
|
}
|
|
175
|
+
|
|
176
|
+
function extractClaudeTextFragments(content) {
|
|
177
|
+
const entries = Array.isArray(content) ? content : [content];
|
|
178
|
+
const fragments = [];
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (typeof entry === "string") {
|
|
181
|
+
const text = entry.trim();
|
|
182
|
+
if (text) fragments.push(text);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (!entry || typeof entry !== "object") continue;
|
|
186
|
+
if (entry.type !== "text") continue;
|
|
187
|
+
const text = String(entry.text || "").trim();
|
|
188
|
+
if (text) fragments.push(text);
|
|
189
|
+
}
|
|
190
|
+
return [...new Set(fragments)];
|
|
191
|
+
}
|
|
@@ -7,15 +7,16 @@ import {
|
|
|
7
7
|
buildStatusEvent,
|
|
8
8
|
buildToolEvent,
|
|
9
9
|
createHostedSessionRunner,
|
|
10
|
-
extractTextFragments,
|
|
11
10
|
readTextFileIfPresent,
|
|
12
11
|
} from "./shared.mjs";
|
|
12
|
+
import { providerAssistantDelta, providerAssistantFinal, providerToolEnd, providerToolStart, providerToolUpdate } from "./events.mjs";
|
|
13
13
|
|
|
14
14
|
export function startCodexHostedSession({
|
|
15
15
|
command = "codex",
|
|
16
16
|
cwd,
|
|
17
17
|
prompt,
|
|
18
18
|
onEvent,
|
|
19
|
+
onRawLine,
|
|
19
20
|
purpose = "assistant",
|
|
20
21
|
model = null,
|
|
21
22
|
providerArgs = [],
|
|
@@ -44,6 +45,7 @@ export function startCodexHostedSession({
|
|
|
44
45
|
provider: "codex",
|
|
45
46
|
child,
|
|
46
47
|
onEvent,
|
|
48
|
+
onRawLine,
|
|
47
49
|
parsePayload: parseCodexPayload,
|
|
48
50
|
shouldIgnoreStatus(message) {
|
|
49
51
|
return String(message || "").trim() === "Reading additional input from stdin...";
|
|
@@ -96,6 +98,45 @@ export function parseCodexPayload(payload) {
|
|
|
96
98
|
return events;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
if (type === "thread.started") {
|
|
102
|
+
const event = buildStatusEvent(payload.thread_id ? `Codex thread ${payload.thread_id} started` : "Codex thread started");
|
|
103
|
+
if (event) events.push(event);
|
|
104
|
+
return events;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (type === "turn.started") {
|
|
108
|
+
const event = buildStatusEvent("Codex turn started");
|
|
109
|
+
if (event) events.push(event);
|
|
110
|
+
return events;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (type === "turn.completed") {
|
|
114
|
+
const event = providerToolEnd("codex", { status: "ok", usage: payload.usage || null });
|
|
115
|
+
if (event) events.push(event);
|
|
116
|
+
return events;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (type === "item.started") {
|
|
120
|
+
const item = payload.item || {};
|
|
121
|
+
const event = codexItemStartedEvent(item);
|
|
122
|
+
if (event) events.push(event);
|
|
123
|
+
return events;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (type === "item.updated") {
|
|
127
|
+
const item = payload.item || {};
|
|
128
|
+
const event = codexItemUpdatedEvent(item);
|
|
129
|
+
if (event) events.push(event);
|
|
130
|
+
return events;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (type === "item.completed") {
|
|
134
|
+
const item = payload.item || {};
|
|
135
|
+
const event = codexItemCompletedEvent(item);
|
|
136
|
+
if (event) events.push(event);
|
|
137
|
+
return events;
|
|
138
|
+
}
|
|
139
|
+
|
|
99
140
|
if (type && /(tool|command|patch|exec)/i.test(type)) {
|
|
100
141
|
const event = buildToolEvent(
|
|
101
142
|
payload.name || payload.tool_name || payload.command || type,
|
|
@@ -105,15 +146,52 @@ export function parseCodexPayload(payload) {
|
|
|
105
146
|
return events;
|
|
106
147
|
}
|
|
107
148
|
|
|
108
|
-
const fragments = [...new Set(extractTextFragments(payload, []))];
|
|
109
|
-
if (fragments.length > 0) {
|
|
110
|
-
for (const fragment of fragments) {
|
|
111
|
-
events.push({ type: "delta", text: fragment });
|
|
112
|
-
}
|
|
113
|
-
return events;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
149
|
const statusEvent = buildStatusEvent(type ? `Codex event: ${type}` : JSON.stringify(payload));
|
|
117
150
|
if (statusEvent) events.push(statusEvent);
|
|
118
151
|
return events;
|
|
119
152
|
}
|
|
153
|
+
|
|
154
|
+
function codexItemStartedEvent(item) {
|
|
155
|
+
if (!item || typeof item !== "object") return null;
|
|
156
|
+
if (item.type === "command_execution" || item.type === "tool_call") {
|
|
157
|
+
return providerToolStart(item.command || item.name || item.type, {
|
|
158
|
+
id: item.id || null,
|
|
159
|
+
input: item.arguments || item.input || null,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (item.type === "agent_message") return buildStatusEvent("Codex started response");
|
|
163
|
+
if (item.type) return buildStatusEvent(`Codex started ${item.type}`);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function codexItemUpdatedEvent(item) {
|
|
168
|
+
if (!item || typeof item !== "object") return null;
|
|
169
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
170
|
+
return providerAssistantDelta(item.text);
|
|
171
|
+
}
|
|
172
|
+
if (item.type === "command_execution" || item.type === "tool_call") {
|
|
173
|
+
return providerToolUpdate(item.command || item.name || item.type, {
|
|
174
|
+
id: item.id || null,
|
|
175
|
+
text: item.output || item.status || null,
|
|
176
|
+
data: item,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function codexItemCompletedEvent(item) {
|
|
183
|
+
if (!item || typeof item !== "object") return null;
|
|
184
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
185
|
+
return providerAssistantFinal(item.text);
|
|
186
|
+
}
|
|
187
|
+
if (item.type === "command_execution" || item.type === "tool_call") {
|
|
188
|
+
return providerToolEnd(item.command || item.name || item.type, {
|
|
189
|
+
id: item.id || null,
|
|
190
|
+
status: item.status === "failed" ? "error" : "ok",
|
|
191
|
+
output: item.output || null,
|
|
192
|
+
data: item,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (item.type) return buildStatusEvent(`Codex completed ${item.type}`);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const PROVIDER_EVENT_TYPES = new Set([
|
|
2
|
+
"session-start",
|
|
3
|
+
"status",
|
|
4
|
+
"assistant-delta",
|
|
5
|
+
"assistant-final",
|
|
6
|
+
"tool-start",
|
|
7
|
+
"tool-update",
|
|
8
|
+
"tool-end",
|
|
9
|
+
"error",
|
|
10
|
+
"session-end",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function providerEvent(type, fields = {}) {
|
|
14
|
+
if (!PROVIDER_EVENT_TYPES.has(type)) {
|
|
15
|
+
throw new Error(`Unknown provider event type: ${type}`);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
type,
|
|
19
|
+
...fields,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function providerStatus(text, fields = {}) {
|
|
24
|
+
const normalized = normalizeText(text);
|
|
25
|
+
if (!normalized) return null;
|
|
26
|
+
return providerEvent("status", { text: normalized, ...fields });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function providerAssistantDelta(text, fields = {}) {
|
|
30
|
+
if (!text) return null;
|
|
31
|
+
return providerEvent("assistant-delta", { text: String(text), ...fields });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function providerAssistantFinal(text, fields = {}) {
|
|
35
|
+
const normalized = normalizeText(text);
|
|
36
|
+
if (!normalized) return null;
|
|
37
|
+
return providerEvent("assistant-final", { text: normalized, ...fields });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function providerToolStart(name, fields = {}) {
|
|
41
|
+
const normalized = normalizeText(name);
|
|
42
|
+
if (!normalized) return null;
|
|
43
|
+
return providerEvent("tool-start", { name: normalized, ...fields });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function providerToolUpdate(name, fields = {}) {
|
|
47
|
+
const normalized = normalizeText(name);
|
|
48
|
+
if (!normalized && !fields.text && !fields.data) return null;
|
|
49
|
+
return providerEvent("tool-update", { ...(normalized ? { name: normalized } : {}), ...fields });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function providerToolEnd(name, fields = {}) {
|
|
53
|
+
const normalized = normalizeText(name);
|
|
54
|
+
if (!normalized) return null;
|
|
55
|
+
return providerEvent("tool-end", { name: normalized, status: fields.status || "ok", ...fields });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function providerError(text, fields = {}) {
|
|
59
|
+
const normalized = normalizeText(text);
|
|
60
|
+
if (!normalized) return null;
|
|
61
|
+
return providerEvent("error", { text: normalized, ...fields });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function providerSessionEnd(fields = {}) {
|
|
65
|
+
return providerEvent("session-end", fields);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function normalizeText(value) {
|
|
69
|
+
const text = String(value || "").trim();
|
|
70
|
+
return text || null;
|
|
71
|
+
}
|
|
@@ -3,7 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { startClaudeHostedSession } from "./claude.mjs";
|
|
4
4
|
import { startCodexHostedSession } from "./codex.mjs";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
export const HOSTED_ASSISTANT_PROVIDERS = Object.freeze(["codex", "claude"]);
|
|
7
7
|
|
|
8
8
|
export function resolvePreferredProvider(preferred = null, env = process.env) {
|
|
9
9
|
if (preferred && preferred !== "auto") {
|
|
@@ -13,7 +13,7 @@ export function resolvePreferredProvider(preferred = null, env = process.env) {
|
|
|
13
13
|
return preferred;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
for (const provider of
|
|
16
|
+
for (const provider of HOSTED_ASSISTANT_PROVIDERS) {
|
|
17
17
|
if (isProviderInstalled(provider, env)) return provider;
|
|
18
18
|
}
|
|
19
19
|
throw new Error("Neither codex nor claude was found on PATH");
|
|
@@ -63,13 +63,14 @@ export function startProviderSession({
|
|
|
63
63
|
cwd,
|
|
64
64
|
prompt,
|
|
65
65
|
onEvent,
|
|
66
|
+
onRawLine,
|
|
66
67
|
purpose = "assistant",
|
|
67
68
|
env = process.env,
|
|
68
69
|
} = {}) {
|
|
69
70
|
const resolvedProvider = resolvePreferredProvider(provider, env);
|
|
70
71
|
const command = resolveProviderBinary(resolvedProvider, env);
|
|
71
72
|
if (resolvedProvider === "claude") {
|
|
72
|
-
return startClaudeHostedSession({ command, cwd, prompt, onEvent, purpose, model, effort, providerArgs, env });
|
|
73
|
+
return startClaudeHostedSession({ command, cwd, prompt, onEvent, onRawLine, purpose, model, effort, providerArgs, env });
|
|
73
74
|
}
|
|
74
|
-
return startCodexHostedSession({ command, cwd, prompt, onEvent, purpose, model, providerArgs, env });
|
|
75
|
+
return startCodexHostedSession({ command, cwd, prompt, onEvent, onRawLine, purpose, model, providerArgs, env });
|
|
75
76
|
}
|
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import readline from "readline";
|
|
3
|
+
import {
|
|
4
|
+
providerError,
|
|
5
|
+
providerEvent,
|
|
6
|
+
providerSessionEnd,
|
|
7
|
+
providerStatus,
|
|
8
|
+
providerToolStart,
|
|
9
|
+
} from "./events.mjs";
|
|
3
10
|
|
|
4
|
-
export function createHostedSessionRunner({
|
|
11
|
+
export function createHostedSessionRunner({
|
|
12
|
+
provider,
|
|
13
|
+
child,
|
|
14
|
+
onEvent,
|
|
15
|
+
onRawLine,
|
|
16
|
+
parsePayload,
|
|
17
|
+
readFinalText,
|
|
18
|
+
shouldIgnoreStatus,
|
|
19
|
+
} = {}) {
|
|
5
20
|
let cancelled = false;
|
|
6
21
|
let settled = false;
|
|
7
22
|
let assistantText = "";
|
|
23
|
+
let finalText = null;
|
|
8
24
|
let lastErrorMessage = null;
|
|
9
25
|
|
|
10
26
|
const emit = (event) => {
|
|
11
|
-
if (event
|
|
27
|
+
if (!event) return;
|
|
28
|
+
if (event.type === "assistant-delta") {
|
|
12
29
|
assistantText += event.text || "";
|
|
13
30
|
}
|
|
14
|
-
if (event
|
|
15
|
-
|
|
31
|
+
if (event.type === "assistant-final") {
|
|
32
|
+
finalText = event.text || finalText;
|
|
33
|
+
}
|
|
34
|
+
if (event.type === "error") {
|
|
35
|
+
lastErrorMessage = event.text || lastErrorMessage;
|
|
16
36
|
}
|
|
17
37
|
if (typeof onEvent === "function" && event) onEvent({ provider, ...event });
|
|
18
38
|
};
|
|
19
39
|
|
|
20
|
-
emit(
|
|
40
|
+
emit(providerEvent("session-start"));
|
|
21
41
|
|
|
22
42
|
const stdoutReader = readline.createInterface({ input: child.stdout });
|
|
23
43
|
stdoutReader.on("line", (line) => {
|
|
44
|
+
onRawLine?.({ provider, stream: "stdout", line });
|
|
24
45
|
const parsed = tryParseJson(line);
|
|
25
46
|
if (parsed == null) {
|
|
26
|
-
emit(
|
|
47
|
+
emit(providerStatus(line));
|
|
27
48
|
return;
|
|
28
49
|
}
|
|
29
50
|
const events = parsePayload ? parsePayload(parsed) : [];
|
|
@@ -33,27 +54,31 @@ export function createHostedSessionRunner({ provider, child, onEvent, parsePaylo
|
|
|
33
54
|
|
|
34
55
|
const stderrReader = readline.createInterface({ input: child.stderr });
|
|
35
56
|
stderrReader.on("line", (line) => {
|
|
57
|
+
onRawLine?.({ provider, stream: "stderr", line });
|
|
36
58
|
if (shouldIgnoreStatus?.(line)) return;
|
|
37
|
-
emit({
|
|
59
|
+
emit(providerStatus(line, { stream: "stderr" }));
|
|
38
60
|
});
|
|
39
61
|
|
|
40
62
|
const completion = (async () => {
|
|
41
63
|
const result = await child;
|
|
42
|
-
const
|
|
64
|
+
const fileFinalText = readFinalText ? readFinalText(result) : null;
|
|
65
|
+
const resolvedFinalText = fileFinalText || finalText || assistantText.trim() || null;
|
|
43
66
|
if ((result.exitCode ?? 0) !== 0) {
|
|
44
67
|
const message = lastErrorMessage || result.stderr || `${provider} exited with code ${result.exitCode ?? 1}`;
|
|
45
|
-
emit(
|
|
68
|
+
emit(providerError(message));
|
|
46
69
|
throw new Error(message);
|
|
47
70
|
}
|
|
48
|
-
if (
|
|
49
|
-
|
|
71
|
+
if (resolvedFinalText && resolvedFinalText !== finalText) {
|
|
72
|
+
emit(providerEvent("assistant-final", { text: resolvedFinalText }));
|
|
73
|
+
}
|
|
74
|
+
emit(providerSessionEnd({ exitCode: result.exitCode ?? 0 }));
|
|
50
75
|
settled = true;
|
|
51
76
|
return {
|
|
52
77
|
provider,
|
|
53
78
|
exitCode: result.exitCode ?? 0,
|
|
54
79
|
stdout: result.stdout || "",
|
|
55
80
|
stderr: result.stderr || "",
|
|
56
|
-
finalText:
|
|
81
|
+
finalText: resolvedFinalText || result.stdout || "",
|
|
57
82
|
cancelled,
|
|
58
83
|
};
|
|
59
84
|
})();
|
|
@@ -84,21 +109,15 @@ export function tryParseJson(line) {
|
|
|
84
109
|
|
|
85
110
|
export function buildToolEvent(name, detail = null) {
|
|
86
111
|
if (!name) return null;
|
|
87
|
-
return {
|
|
88
|
-
type: "tool",
|
|
89
|
-
name: String(name),
|
|
90
|
-
...(detail ? { detail: String(detail) } : {}),
|
|
91
|
-
};
|
|
112
|
+
return providerToolStart(name, detail ? { detail: String(detail) } : {});
|
|
92
113
|
}
|
|
93
114
|
|
|
94
115
|
export function buildStatusEvent(message) {
|
|
95
|
-
|
|
96
|
-
return { type: "status", message: String(message) };
|
|
116
|
+
return providerStatus(message);
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
export function buildErrorEvent(message) {
|
|
100
|
-
|
|
101
|
-
return { type: "error", message: String(message) };
|
|
120
|
+
return providerError(message);
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
export function extractTextFragments(payload, fragments = [], depth = 0) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
1
3
|
import { startProviderSession, resolvePreferredProvider } from "./providers/index.mjs";
|
|
2
4
|
import { buildAssistantPrompt } from "./prompt-builder.mjs";
|
|
3
5
|
import { createAssistantCommandObserver } from "./command-observer.mjs";
|
|
@@ -14,6 +16,7 @@ export async function runAssistantConversationTurn({
|
|
|
14
16
|
commandLog,
|
|
15
17
|
onStatus,
|
|
16
18
|
onToolEvent,
|
|
19
|
+
onProviderEvent,
|
|
17
20
|
onResolvedProvider,
|
|
18
21
|
onPrompt,
|
|
19
22
|
} = {}) {
|
|
@@ -36,6 +39,12 @@ export async function runAssistantConversationTurn({
|
|
|
36
39
|
const runtimeSettings = settings || { provider };
|
|
37
40
|
const resolvedProvider = resolvePreferredProvider(runtimeSettings.provider || provider, env);
|
|
38
41
|
const providerEnv = commandLog?.providerEnv?.(env) || env;
|
|
42
|
+
const tracePath = shouldTraceProviderEvents(env, providerEnv)
|
|
43
|
+
? path.join(commandLog?.contextDir || path.join(productDir, ".testkit", "assistant"), "provider-events.jsonl")
|
|
44
|
+
: null;
|
|
45
|
+
const rawTracePath = shouldTraceProviderEvents(env, providerEnv)
|
|
46
|
+
? path.join(commandLog?.contextDir || path.join(productDir, ".testkit", "assistant"), "provider-raw.jsonl")
|
|
47
|
+
: null;
|
|
39
48
|
onResolvedProvider?.(resolvedProvider);
|
|
40
49
|
onPrompt?.({
|
|
41
50
|
prompt,
|
|
@@ -44,6 +53,7 @@ export async function runAssistantConversationTurn({
|
|
|
44
53
|
effort: runtimeSettings.effort || null,
|
|
45
54
|
});
|
|
46
55
|
onStatus?.(`Thinking with ${resolvedProvider}...`);
|
|
56
|
+
onProviderEvent?.({ type: "status", provider: resolvedProvider, text: `Thinking with ${resolvedProvider}...` });
|
|
47
57
|
|
|
48
58
|
observer.start();
|
|
49
59
|
try {
|
|
@@ -56,25 +66,53 @@ export async function runAssistantConversationTurn({
|
|
|
56
66
|
prompt,
|
|
57
67
|
purpose: "assistant",
|
|
58
68
|
env: providerEnv,
|
|
69
|
+
onRawLine(line) {
|
|
70
|
+
appendProviderTrace(rawTracePath, line);
|
|
71
|
+
},
|
|
59
72
|
onEvent(event) {
|
|
60
|
-
|
|
73
|
+
appendProviderTrace(tracePath, event);
|
|
74
|
+
onProviderEvent?.(event);
|
|
75
|
+
if (event.type === "status" || event.type === "tool-start" || event.type === "tool-update" || event.type === "tool-end") {
|
|
76
|
+
onStatus?.(formatProviderEvent(event));
|
|
77
|
+
}
|
|
61
78
|
},
|
|
62
79
|
});
|
|
63
|
-
|
|
80
|
+
await session.completion;
|
|
64
81
|
observer.scan();
|
|
65
82
|
commandLog?.refresh?.();
|
|
66
|
-
return
|
|
67
|
-
role: "assistant",
|
|
68
|
-
text: result.finalText || "",
|
|
69
|
-
}];
|
|
83
|
+
return { provider: resolvedProvider };
|
|
70
84
|
} finally {
|
|
71
85
|
observer.stop();
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
function formatProviderEvent(event) {
|
|
76
|
-
if (event.type === "tool") {
|
|
90
|
+
if (event.type === "tool-start") {
|
|
77
91
|
return `${event.provider}: ${event.name}${event.detail ? ` (${event.detail})` : ""}`;
|
|
78
92
|
}
|
|
79
|
-
|
|
93
|
+
if (event.type === "tool-update") {
|
|
94
|
+
return `${event.provider}: ${event.name || "tool"}${event.text ? ` (${event.text})` : ""}`;
|
|
95
|
+
}
|
|
96
|
+
if (event.type === "tool-end") {
|
|
97
|
+
return `${event.provider}: ${event.name || "tool"} ${event.status || "done"}`;
|
|
98
|
+
}
|
|
99
|
+
return `${event.provider}: ${event.text || "working"}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function shouldTraceProviderEvents(...envs) {
|
|
103
|
+
return envs.some((entry) => String(entry?.TESTKIT_PROVIDER_TRACE || "").trim() === "1");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function appendProviderTrace(tracePath, event) {
|
|
107
|
+
if (!tracePath || !event) return;
|
|
108
|
+
try {
|
|
109
|
+
fs.mkdirSync(path.dirname(tracePath), { recursive: true });
|
|
110
|
+
fs.appendFileSync(
|
|
111
|
+
tracePath,
|
|
112
|
+
`${JSON.stringify({ timestamp: new Date().toISOString(), ...event })}\n`,
|
|
113
|
+
"utf8"
|
|
114
|
+
);
|
|
115
|
+
} catch {
|
|
116
|
+
// Provider tracing must never interfere with the assistant turn.
|
|
117
|
+
}
|
|
80
118
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { HOSTED_ASSISTANT_PROVIDERS } from "./providers/index.mjs";
|
|
3
4
|
|
|
4
|
-
export const ASSISTANT_PROVIDERS = ["auto",
|
|
5
|
+
export const ASSISTANT_PROVIDERS = ["auto", ...HOSTED_ASSISTANT_PROVIDERS];
|
|
5
6
|
export const ASSISTANT_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_ASSISTANT_SETTINGS = Object.freeze({
|
|
@@ -99,11 +99,25 @@ export function createAssistantState({
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function appendMessage(message) {
|
|
102
|
-
|
|
102
|
+
const entry = {
|
|
103
103
|
id: `msg-${messages.length + 1}`,
|
|
104
104
|
...message,
|
|
105
|
-
}
|
|
105
|
+
};
|
|
106
|
+
messages.push(entry);
|
|
106
107
|
notify();
|
|
108
|
+
return entry.id;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updateMessage(id, updater) {
|
|
112
|
+
const index = messages.findIndex((message) => message.id === id);
|
|
113
|
+
if (index < 0) return false;
|
|
114
|
+
const next = updater(messages[index]);
|
|
115
|
+
messages[index] = {
|
|
116
|
+
...messages[index],
|
|
117
|
+
...next,
|
|
118
|
+
};
|
|
119
|
+
notify();
|
|
120
|
+
return true;
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
function setBusy(nextBusy, status = null) {
|
|
@@ -354,10 +368,11 @@ export function createAssistantState({
|
|
|
354
368
|
|
|
355
369
|
try {
|
|
356
370
|
setBusy(true, `Thinking with ${settings.provider === "auto" ? "provider" : settings.provider}...`);
|
|
357
|
-
const
|
|
371
|
+
const providerTurn = createProviderTurnState();
|
|
372
|
+
await runAssistantConversationTurn({
|
|
358
373
|
productDir,
|
|
359
374
|
runState,
|
|
360
|
-
transcript: messages
|
|
375
|
+
transcript: buildConversationTranscript(messages),
|
|
361
376
|
userMessage: trimmed,
|
|
362
377
|
settings,
|
|
363
378
|
env,
|
|
@@ -379,11 +394,20 @@ export function createAssistantState({
|
|
|
379
394
|
});
|
|
380
395
|
notify();
|
|
381
396
|
},
|
|
397
|
+
onProviderEvent(event) {
|
|
398
|
+
handleProviderEvent(providerTurn, event, {
|
|
399
|
+
appendMessage,
|
|
400
|
+
updateMessage,
|
|
401
|
+
setStatus(status) {
|
|
402
|
+
activeStatus = status;
|
|
403
|
+
notify();
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
},
|
|
382
407
|
onToolEvent(event) {
|
|
383
408
|
handleAssistantToolEvent(state, event, appendMessage);
|
|
384
409
|
},
|
|
385
410
|
});
|
|
386
|
-
for (const message of emitted) appendMessage(message);
|
|
387
411
|
} catch (error) {
|
|
388
412
|
appendMessage({
|
|
389
413
|
role: "system",
|
|
@@ -608,6 +632,145 @@ function handleAssistantToolEvent(state, event, appendMessage) {
|
|
|
608
632
|
}
|
|
609
633
|
}
|
|
610
634
|
|
|
635
|
+
function createProviderTurnState() {
|
|
636
|
+
return {
|
|
637
|
+
assistantMessageId: null,
|
|
638
|
+
lastActivityText: null,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function handleProviderEvent(turn, event, { appendMessage, updateMessage, setStatus } = {}) {
|
|
643
|
+
if (!event) return;
|
|
644
|
+
if (event.type === "assistant-delta") {
|
|
645
|
+
appendAssistantDelta(turn, event, { appendMessage, updateMessage });
|
|
646
|
+
setStatus?.(`${event.provider || "provider"} responding`);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (event.type === "assistant-final") {
|
|
650
|
+
finalizeAssistantMessage(turn, event, { appendMessage, updateMessage });
|
|
651
|
+
setStatus?.(`${event.provider || "provider"} complete`);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (event.type === "session-start") {
|
|
655
|
+
appendProviderActivity(turn, {
|
|
656
|
+
role: "provider-activity",
|
|
657
|
+
title: formatProviderName(event.provider),
|
|
658
|
+
text: "Session started",
|
|
659
|
+
data: event,
|
|
660
|
+
}, { appendMessage, setStatus });
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (event.type === "session-end") {
|
|
664
|
+
appendProviderActivity(turn, {
|
|
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 });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (event.type === "status") {
|
|
673
|
+
appendProviderActivity(turn, {
|
|
674
|
+
role: "provider-activity",
|
|
675
|
+
title: formatProviderName(event.provider),
|
|
676
|
+
text: event.text || "Working",
|
|
677
|
+
data: event,
|
|
678
|
+
}, { appendMessage, setStatus });
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (event.type === "tool-start" || event.type === "tool-update" || event.type === "tool-end") {
|
|
682
|
+
appendProviderActivity(turn, {
|
|
683
|
+
role: "provider-tool",
|
|
684
|
+
title: formatProviderToolTitle(event),
|
|
685
|
+
text: formatProviderToolText(event),
|
|
686
|
+
data: event,
|
|
687
|
+
}, { appendMessage, setStatus });
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (event.type === "error") {
|
|
691
|
+
appendMessage({
|
|
692
|
+
role: "provider-error",
|
|
693
|
+
title: formatProviderName(event.provider),
|
|
694
|
+
text: event.text || "Provider error",
|
|
695
|
+
data: event,
|
|
696
|
+
});
|
|
697
|
+
setStatus?.(`${event.provider || "provider"} error`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function appendAssistantDelta(turn, event, { appendMessage, updateMessage }) {
|
|
702
|
+
if (!turn.assistantMessageId) {
|
|
703
|
+
turn.assistantMessageId = appendMessage({
|
|
704
|
+
role: "assistant",
|
|
705
|
+
status: "streaming",
|
|
706
|
+
provider: event.provider || null,
|
|
707
|
+
text: "",
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
updateMessage(turn.assistantMessageId, (message) => ({
|
|
711
|
+
text: `${message.text || ""}${event.text || ""}`,
|
|
712
|
+
status: "streaming",
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function finalizeAssistantMessage(turn, event, { appendMessage, updateMessage }) {
|
|
717
|
+
const finalText = event.text || "";
|
|
718
|
+
if (!turn.assistantMessageId) {
|
|
719
|
+
turn.assistantMessageId = appendMessage({
|
|
720
|
+
role: "assistant",
|
|
721
|
+
provider: event.provider || null,
|
|
722
|
+
text: finalText,
|
|
723
|
+
});
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
updateMessage(turn.assistantMessageId, (message) => ({
|
|
727
|
+
text: finalText || message.text || "",
|
|
728
|
+
status: null,
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function appendProviderActivity(turn, message, { appendMessage, setStatus }) {
|
|
733
|
+
const text = String(message.text || "").trim();
|
|
734
|
+
if (!text) return;
|
|
735
|
+
const signature = `${message.role}:${message.title || ""}:${text}`;
|
|
736
|
+
if (turn.lastActivityText === signature) return;
|
|
737
|
+
turn.lastActivityText = signature;
|
|
738
|
+
appendMessage(message);
|
|
739
|
+
setStatus?.(message.title ? `${message.title}: ${text}` : text);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function formatProviderName(provider) {
|
|
743
|
+
return provider ? String(provider) : "provider";
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function formatProviderToolTitle(event) {
|
|
747
|
+
const provider = formatProviderName(event.provider);
|
|
748
|
+
const name = event.name || "tool";
|
|
749
|
+
if (event.type === "tool-end") return `${provider} finished ${name}`;
|
|
750
|
+
if (event.type === "tool-update") return `${provider} updated ${name}`;
|
|
751
|
+
return `${provider} started ${name}`;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function formatProviderToolText(event) {
|
|
755
|
+
const lines = [];
|
|
756
|
+
if (event.detail) lines.push(String(event.detail));
|
|
757
|
+
if (event.text) lines.push(String(event.text));
|
|
758
|
+
if (event.input) lines.push(formatProviderData("input", event.input));
|
|
759
|
+
if (event.output) lines.push(formatProviderData("output", event.output));
|
|
760
|
+
if (event.status) lines.push(`status: ${event.status}`);
|
|
761
|
+
return lines.filter(Boolean).join("\n") || event.name || "Provider tool activity";
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function formatProviderData(label, value) {
|
|
765
|
+
if (value == null) return null;
|
|
766
|
+
if (typeof value === "string") return `${label}: ${value}`;
|
|
767
|
+
try {
|
|
768
|
+
return `${label}: ${JSON.stringify(value)}`;
|
|
769
|
+
} catch {
|
|
770
|
+
return `${label}: ${String(value)}`;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
611
774
|
function formatSettings(snapshot) {
|
|
612
775
|
const rows = [
|
|
613
776
|
["Provider", snapshot.provider || "auto"],
|
|
@@ -654,6 +817,12 @@ function serializeRunSession(session) {
|
|
|
654
817
|
};
|
|
655
818
|
}
|
|
656
819
|
|
|
820
|
+
function buildConversationTranscript(messages) {
|
|
821
|
+
return (messages || [])
|
|
822
|
+
.filter((entry) => !["provider-activity", "provider-tool", "provider-error"].includes(entry.role))
|
|
823
|
+
.map((entry) => ({ role: entry.role, text: entry.text }));
|
|
824
|
+
}
|
|
825
|
+
|
|
657
826
|
function formatObservedCommandTitle(command) {
|
|
658
827
|
const kind = command?.kind || "command";
|
|
659
828
|
return `testkit ${kind}`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import stripAnsi from "strip-ansi";
|
|
2
|
+
import { buildAssistantViewModel } from "./view-model.mjs";
|
|
3
|
+
import { renderMarkdownToAnsi } from "./markdown-block.mjs";
|
|
4
|
+
|
|
5
|
+
export function renderAssistantSnapshotText(snapshot, { cwd = process.cwd(), ansi = false } = {}) {
|
|
6
|
+
const view = buildAssistantViewModel(snapshot || {}, { cwd });
|
|
7
|
+
const lines = [view.title, view.welcome.rows.find(([label]) => label === "Provider")?.[1] || ""]
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
for (const block of view.blocks || []) {
|
|
10
|
+
lines.push("");
|
|
11
|
+
lines.push(...renderBlockLines(block, { ansi }));
|
|
12
|
+
}
|
|
13
|
+
lines.push("");
|
|
14
|
+
lines.push(view.statusLine);
|
|
15
|
+
const text = `${lines.join("\n").trimEnd()}\n`;
|
|
16
|
+
return ansi ? text : stripAnsi(text);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderBlockLines(block, { ansi = false } = {}) {
|
|
20
|
+
const marker = block.marker || "";
|
|
21
|
+
const title = block.title ? ` ${block.title}` : "";
|
|
22
|
+
const text = String(block.text || "").trimEnd();
|
|
23
|
+
if (block.format === "markdown") {
|
|
24
|
+
const rendered = text ? renderMarkdownToAnsi(text) : "";
|
|
25
|
+
const normalized = ansi ? rendered : stripAnsi(rendered);
|
|
26
|
+
return prefixLines(`${marker}${title}`, normalized);
|
|
27
|
+
}
|
|
28
|
+
return prefixLines(`${marker}${title}`, text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function prefixLines(prefix, text) {
|
|
32
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
33
|
+
if (lines.length === 0 || (lines.length === 1 && lines[0] === "")) return [prefix];
|
|
34
|
+
return lines.map((line, index) => (index === 0 ? `${prefix}${prefix && line ? " " : ""}${line}` : ` ${line}`));
|
|
35
|
+
}
|
|
@@ -65,6 +65,17 @@ export function buildTranscriptBlocks(messages) {
|
|
|
65
65
|
exitCode: message.data?.exitCode ?? null,
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
+
if (role === "provider-activity" || role === "provider-tool" || role === "provider-error") {
|
|
69
|
+
return {
|
|
70
|
+
id: message.id,
|
|
71
|
+
kind: role,
|
|
72
|
+
format: "plain",
|
|
73
|
+
marker: role === "provider-error" ? "!" : "◌",
|
|
74
|
+
title: message.title || null,
|
|
75
|
+
text: message.text || "",
|
|
76
|
+
status: message.status || null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
68
79
|
if (role === "user") {
|
|
69
80
|
return {
|
|
70
81
|
id: message.id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.102",
|
|
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.102"
|
|
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.102",
|
|
4
4
|
"description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -58,11 +58,12 @@
|
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build:packages": "npm --workspace packages/testkit-protocol run build && npm --workspace packages/ts-analysis run build && npm --workspace packages/next-analysis run build && npm --workspace packages/testkit-bridge run build",
|
|
60
60
|
"typecheck:packages": "npm --workspace packages/testkit-protocol run typecheck && npm --workspace packages/ts-analysis run typecheck && npm --workspace packages/next-analysis run typecheck && npm --workspace packages/testkit-bridge run typecheck && npm --workspace packages/testkit-extension run compile",
|
|
61
|
-
"test": "npm run build:packages && vitest run &&
|
|
61
|
+
"test": "npm run build:packages && vitest run && npm run test:live",
|
|
62
62
|
"test:audit": "node scripts/test-boundary-audit.mjs",
|
|
63
|
+
"test:live": "node scripts/live-sandbox/harness.mjs",
|
|
63
64
|
"test:unit": "npm run build:packages && npm run test:audit && vitest run --config vitest.unit.config.mjs",
|
|
64
65
|
"test:integration": "npm run build:packages && vitest run test/integration",
|
|
65
|
-
"test:system": "npm run build:packages && vitest run test/system
|
|
66
|
+
"test:system": "npm run build:packages && vitest run test/system"
|
|
66
67
|
},
|
|
67
68
|
"files": [
|
|
68
69
|
"bin/",
|
|
@@ -89,10 +90,10 @@
|
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
92
|
"@babel/code-frame": "^7.29.0",
|
|
92
|
-
"@elench/next-analysis": "0.1.
|
|
93
|
-
"@elench/testkit-bridge": "0.1.
|
|
94
|
-
"@elench/testkit-protocol": "0.1.
|
|
95
|
-
"@elench/ts-analysis": "0.1.
|
|
93
|
+
"@elench/next-analysis": "0.1.102",
|
|
94
|
+
"@elench/testkit-bridge": "0.1.102",
|
|
95
|
+
"@elench/testkit-protocol": "0.1.102",
|
|
96
|
+
"@elench/ts-analysis": "0.1.102",
|
|
96
97
|
"@oclif/core": "^4.10.6",
|
|
97
98
|
"esbuild": "^0.25.11",
|
|
98
99
|
"execa": "^9.5.0",
|
|
@@ -109,6 +110,11 @@
|
|
|
109
110
|
"wrap-ansi": "^10.0.0"
|
|
110
111
|
},
|
|
111
112
|
"engines": {
|
|
112
|
-
"node": ">=
|
|
113
|
+
"node": ">=24.14.1",
|
|
114
|
+
"npm": ">=11.11.0"
|
|
115
|
+
},
|
|
116
|
+
"volta": {
|
|
117
|
+
"node": "24.14.1",
|
|
118
|
+
"npm": "11.11.0"
|
|
113
119
|
}
|
|
114
120
|
}
|