@elench/testkit 0.1.97 → 0.1.98
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/app/browser-bridge.mjs +1 -1
- package/lib/cli/assistant/app.mjs +25 -1
- package/lib/cli/assistant/composer.mjs +1 -1
- package/lib/cli/assistant/context-pack.mjs +4 -4
- package/lib/cli/assistant/interactive.mjs +1 -1
- package/lib/cli/assistant/prompt-builder.mjs +2 -2
- package/lib/cli/{agents → assistant/providers}/index.mjs +3 -3
- package/lib/cli/assistant/session.mjs +5 -5
- package/lib/cli/assistant/slash-commands.mjs +22 -1
- package/lib/cli/assistant/state.mjs +148 -75
- package/lib/cli/assistant/tool-registry.mjs +305 -39
- package/lib/cli/assistant/view-model.mjs +1 -1
- package/lib/cli/commands/assistant.mjs +4 -3
- package/lib/cli/commands/browser/serve.mjs +5 -23
- package/lib/cli/commands/cleanup.mjs +8 -2
- package/lib/cli/commands/db/snapshot/capture.mjs +8 -4
- package/lib/cli/commands/destroy.mjs +8 -2
- package/lib/cli/commands/discover.mjs +5 -27
- package/lib/cli/commands/doctor.mjs +5 -5
- package/lib/cli/commands/flags.mjs +61 -0
- package/lib/cli/commands/run.mjs +10 -2
- package/lib/cli/commands/status.mjs +10 -2
- package/lib/cli/commands/typecheck.mjs +5 -5
- package/lib/cli/{tui/inspect-app.mjs → components/blocks/run-tree.mjs} +29 -54
- package/lib/cli/{tui → components/primitives}/filter-bar.mjs +1 -1
- package/lib/cli/{presentation → components/primitives}/summary-box.mjs +1 -1
- package/lib/cli/config.mjs +63 -0
- package/lib/cli/operations/browser/serve/operation.mjs +23 -0
- package/lib/cli/operations/cleanup/operation.mjs +8 -0
- package/lib/cli/{db.mjs → operations/db/snapshot/capture/operation.mjs} +15 -9
- package/lib/cli/operations/destroy/operation.mjs +12 -0
- package/lib/cli/operations/discover/operation.mjs +32 -0
- package/lib/cli/operations/doctor/operation.mjs +5 -0
- package/lib/cli/operations/run/operation.mjs +129 -0
- package/lib/cli/operations/status/operation.mjs +7 -0
- package/lib/cli/operations/typecheck/operation.mjs +5 -0
- package/lib/cli/renderers/browser-serve/text.mjs +6 -0
- package/lib/cli/renderers/cleanup/text.mjs +3 -0
- package/lib/cli/renderers/db-snapshot-capture/text.mjs +3 -0
- package/lib/cli/renderers/destroy/text.mjs +3 -0
- package/lib/cli/{presentation/discovery-reporter.mjs → renderers/discover/report.mjs} +3 -3
- package/lib/cli/renderers/discover/text.mjs +7 -0
- package/lib/cli/renderers/doctor/text.mjs +7 -0
- package/lib/cli/{presentation/failure-presentation.mjs → renderers/run/failure.mjs} +6 -6
- package/lib/cli/renderers/run/interactive.mjs +119 -0
- package/lib/cli/{presentation/run-reporter.mjs → renderers/run/text-reporter.mjs} +5 -5
- package/lib/cli/renderers/status/text.mjs +7 -0
- package/lib/cli/renderers/typecheck/text.mjs +7 -0
- package/lib/cli/{tui/inspect-model.mjs → state/run/model.mjs} +11 -26
- package/lib/cli/{tui/inspect-state.mjs → state/run/state.mjs} +11 -18
- package/lib/cli/{tui → state/tree}/fuzzy-match.mjs +1 -1
- package/lib/cli/terminal/capabilities.mjs +33 -0
- package/lib/database/index.mjs +9 -21
- package/lib/{cli/viewer.mjs → results/artifacts.mjs} +1 -1
- package/lib/{cli/context-resources.mjs → results/context.mjs} +1 -1
- package/lib/runner/maintenance.mjs +25 -14
- package/lib/runner/readiness.mjs +5 -4
- package/lib/runner/state-io.mjs +10 -4
- 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 +6 -7
- package/lib/cli/command-helpers.mjs +0 -191
- package/lib/cli/presentation/tree-reporter.mjs +0 -96
- package/lib/cli/tui/inspect-artifact-adapter.mjs +0 -3
- package/lib/cli/tui/inspect-live-adapter.mjs +0 -15
- /package/lib/cli/{agents → assistant}/providers/claude.mjs +0 -0
- /package/lib/cli/{agents → assistant}/providers/codex.mjs +0 -0
- /package/lib/cli/{agents → assistant}/providers/shared.mjs +0 -0
- /package/lib/cli/{presentation/events-reporter.mjs → renderers/run/events.mjs} +0 -0
- /package/lib/cli/{presentation → terminal}/colors.mjs +0 -0
- /package/lib/cli/{presentation/terminal-layout.mjs → terminal/layout.mjs} +0 -0
- /package/lib/{cli/presentation → results}/code-frames.mjs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadConfigContext, resolveProductDir } from "../config/index.mjs";
|
|
2
2
|
import { discoverTests } from "../discovery/index.mjs";
|
|
3
|
-
import { loadCurrentRunArtifact } from "../
|
|
3
|
+
import { loadCurrentRunArtifact } from "../results/artifacts.mjs";
|
|
4
4
|
|
|
5
5
|
export async function loadBrowserBridgeContext(options = {}) {
|
|
6
6
|
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { createElement, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useApp, useBoxMetrics, useCursor, useInput, useStdout } from "ink";
|
|
3
|
-
import { bold, cyan, dim, green, red, yellow } from "../
|
|
3
|
+
import { bold, cyan, dim, green, red, yellow } from "../terminal/colors.mjs";
|
|
4
|
+
import { RunTreeView } from "../components/blocks/run-tree.mjs";
|
|
4
5
|
import { getComposerDisplayModel } from "./composer.mjs";
|
|
5
6
|
import { buildAssistantViewModel } from "./view-model.mjs";
|
|
6
7
|
|
|
@@ -49,6 +50,7 @@ export function AssistantApp({
|
|
|
49
50
|
}),
|
|
50
51
|
[snapshot, stdout?.columns]
|
|
51
52
|
);
|
|
53
|
+
const runSession = assistantState.getLiveRunSession?.() || assistantState.getLastRunSession?.() || null;
|
|
52
54
|
|
|
53
55
|
return createElement(
|
|
54
56
|
Box,
|
|
@@ -63,6 +65,28 @@ export function AssistantApp({
|
|
|
63
65
|
view.blocks.length === 0
|
|
64
66
|
? createElement(WelcomePanel, { view })
|
|
65
67
|
: createElement(Transcript, { view }),
|
|
68
|
+
runSession
|
|
69
|
+
? createElement(
|
|
70
|
+
Box,
|
|
71
|
+
{ flexDirection: "column", marginTop: 1 },
|
|
72
|
+
createElement(Text, null, bold("Run Session")),
|
|
73
|
+
createElement(
|
|
74
|
+
Box,
|
|
75
|
+
{
|
|
76
|
+
borderStyle: "round",
|
|
77
|
+
flexDirection: "column",
|
|
78
|
+
paddingLeft: 1,
|
|
79
|
+
paddingRight: 1,
|
|
80
|
+
},
|
|
81
|
+
createElement(RunTreeView, {
|
|
82
|
+
runState: runSession.runState,
|
|
83
|
+
stdout,
|
|
84
|
+
productDir: runSession.productDir,
|
|
85
|
+
interactive: false,
|
|
86
|
+
})
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
: null,
|
|
66
90
|
createElement(Text, null, ""),
|
|
67
91
|
createElement(ComposerBar, { view, busy: snapshot.busy }),
|
|
68
92
|
createElement(Text, null, dim(view.statusLine)),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { readContextContent, buildContextSelection } from "
|
|
4
|
+
import { readContextContent, buildContextSelection } from "../../results/context.mjs";
|
|
5
5
|
|
|
6
6
|
export function prepareAssistantContextPack({
|
|
7
7
|
productDir,
|
|
8
|
-
|
|
8
|
+
runState,
|
|
9
9
|
} = {}) {
|
|
10
10
|
const contextDir = path.join(productDir, ".testkit", "assistant");
|
|
11
11
|
const binDir = path.join(contextDir, "bin");
|
|
@@ -23,7 +23,7 @@ export function prepareAssistantContextPack({
|
|
|
23
23
|
const wrapperPath = path.join(binDir, "testkit");
|
|
24
24
|
|
|
25
25
|
function refresh() {
|
|
26
|
-
const snapshot =
|
|
26
|
+
const snapshot = runState?.getSnapshot?.() || {};
|
|
27
27
|
const detailContent = readContextContent({ productDir, snapshot, mode: "detail", logTail: 12 });
|
|
28
28
|
const logsContent = readContextContent({ productDir, snapshot, mode: "logs", logTail: 12 });
|
|
29
29
|
const artifactsContent = readContextContent({ productDir, snapshot, mode: "artifacts", logTail: 12 });
|
|
@@ -160,7 +160,7 @@ function buildContextMarkdown(productDir, snapshot, paths) {
|
|
|
160
160
|
lines.push(
|
|
161
161
|
"",
|
|
162
162
|
"## Guidance",
|
|
163
|
-
"- Use
|
|
163
|
+
"- Use dedicated testkit tools for run/discover/status/doctor/typecheck actions before falling back to generic shell commands.",
|
|
164
164
|
"- Do not reinterpret CLI syntax after an execution failure unless `testkit run --help` confirms a syntax problem.",
|
|
165
165
|
"- Use the command log and focused context files before rereading artifacts manually.",
|
|
166
166
|
"- Prefer repo-local commands over guessing project-specific wrappers.",
|
|
@@ -2,7 +2,7 @@ import React, { createElement } from "react";
|
|
|
2
2
|
import { render } from "ink";
|
|
3
3
|
import { createAssistantState } from "./state.mjs";
|
|
4
4
|
import { AssistantApp } from "./app.mjs";
|
|
5
|
-
import { loadLatestRunArtifact, resolveFileSubject } from "
|
|
5
|
+
import { loadLatestRunArtifact, resolveFileSubject } from "../../results/artifacts.mjs";
|
|
6
6
|
|
|
7
7
|
export async function runInteractiveAssistant({
|
|
8
8
|
productDir,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readContextContent } from "
|
|
1
|
+
import { readContextContent } from "../../results/context.mjs";
|
|
2
2
|
import { buildAssistantResponseContract } from "./protocol.mjs";
|
|
3
3
|
|
|
4
4
|
export function buildAssistantPrompt({
|
|
@@ -16,7 +16,7 @@ export function buildAssistantPrompt({
|
|
|
16
16
|
"You are Testkit Assistant.",
|
|
17
17
|
"You help users run tests, inspect failures, read logs and artifacts, and navigate the current local test state.",
|
|
18
18
|
"All user natural-language requests must be handled through your own reasoning plus the available tools.",
|
|
19
|
-
"
|
|
19
|
+
"Use the dedicated testkit tools for run/discover/status/doctor/typecheck actions; use shell_exec only for arbitrary repository commands outside those actions.",
|
|
20
20
|
"Use read_context before repeating artifact/log inspection work, and use read_file/search_repo when you need codebase context.",
|
|
21
21
|
buildAssistantResponseContract({ tools }),
|
|
22
22
|
"",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { startClaudeHostedSession } from "./
|
|
4
|
-
import { startCodexHostedSession } from "./
|
|
3
|
+
import { startClaudeHostedSession } from "./claude.mjs";
|
|
4
|
+
import { startCodexHostedSession } from "./codex.mjs";
|
|
5
5
|
|
|
6
6
|
const PROVIDERS = ["codex", "claude"];
|
|
7
7
|
|
|
@@ -55,7 +55,7 @@ export function isProviderInstalled(provider, env = process.env) {
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function
|
|
58
|
+
export function startProviderSession({
|
|
59
59
|
provider = "auto",
|
|
60
60
|
model = null,
|
|
61
61
|
effort = null,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { startProviderSession, resolvePreferredProvider } from "./providers/index.mjs";
|
|
2
2
|
import { buildAssistantPrompt } from "./prompt-builder.mjs";
|
|
3
3
|
import { listAssistantTools, executeAssistantTool } from "./tool-registry.mjs";
|
|
4
4
|
import { parseAssistantEnvelope } from "./protocol.mjs";
|
|
5
5
|
|
|
6
6
|
export async function runAssistantConversationTurn({
|
|
7
7
|
productDir,
|
|
8
|
-
|
|
8
|
+
runState,
|
|
9
9
|
transcript,
|
|
10
10
|
userMessage,
|
|
11
11
|
provider = "auto",
|
|
@@ -21,7 +21,7 @@ export async function runAssistantConversationTurn({
|
|
|
21
21
|
const tools = listAssistantTools();
|
|
22
22
|
const toolContext = {
|
|
23
23
|
productDir,
|
|
24
|
-
|
|
24
|
+
runState,
|
|
25
25
|
configs,
|
|
26
26
|
env,
|
|
27
27
|
commandLog,
|
|
@@ -32,7 +32,7 @@ export async function runAssistantConversationTurn({
|
|
|
32
32
|
const emitted = [];
|
|
33
33
|
|
|
34
34
|
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
35
|
-
const snapshot =
|
|
35
|
+
const snapshot = runState.getSnapshot();
|
|
36
36
|
const prompt = buildAssistantPrompt({
|
|
37
37
|
productDir,
|
|
38
38
|
snapshot,
|
|
@@ -52,7 +52,7 @@ export async function runAssistantConversationTurn({
|
|
|
52
52
|
});
|
|
53
53
|
onStatus?.(`Thinking with ${resolvedProvider}...`);
|
|
54
54
|
const events = [];
|
|
55
|
-
const session =
|
|
55
|
+
const session = startProviderSession({
|
|
56
56
|
provider: runtimeSettings.provider || provider,
|
|
57
57
|
model: runtimeSettings.model || null,
|
|
58
58
|
effort: runtimeSettings.effort || null,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const RUN_TYPES = new Set(["int", "e2e", "scenario", "dal", "load", "pw", "all"]);
|
|
2
1
|
import { ASSISTANT_EFFORTS, ASSISTANT_PROVIDERS } from "./settings.mjs";
|
|
3
2
|
|
|
3
|
+
const RUN_TYPES = new Set(["int", "e2e", "scenario", "dal", "load", "pw", "all"]);
|
|
4
4
|
const PROVIDERS = new Set(ASSISTANT_PROVIDERS);
|
|
5
5
|
const EFFORTS = new Set(ASSISTANT_EFFORTS);
|
|
6
6
|
|
|
@@ -62,6 +62,17 @@ export function parseSlashCommand(input) {
|
|
|
62
62
|
throw new Error('/settings expects "show" or "reset"');
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
if (command === "config") {
|
|
66
|
+
const action = tokens[0] || "show";
|
|
67
|
+
if (action === "show") return { type: "config-show" };
|
|
68
|
+
if (action === "reset") return { type: "config-reset" };
|
|
69
|
+
if (action === "auto-collapse-passed" || action === "autoCollapsePassedTreeBranches") {
|
|
70
|
+
const value = parseBooleanToken(tokens[1], "/config auto-collapse-passed");
|
|
71
|
+
return { type: "config-set-auto-collapse", value };
|
|
72
|
+
}
|
|
73
|
+
throw new Error('/config expects "show", "reset", or "auto-collapse-passed <on|off>"');
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
if (command === "file" || command === "focus") {
|
|
66
77
|
if (!tokens[0]) throw new Error(`/${command} expects a file path`);
|
|
67
78
|
return { type: "file", file: tokens.join(" ") };
|
|
@@ -134,11 +145,21 @@ export function formatSlashHelpLines() {
|
|
|
134
145
|
"/provider-arg clear",
|
|
135
146
|
"/settings",
|
|
136
147
|
"/settings reset",
|
|
148
|
+
"/config",
|
|
149
|
+
"/config auto-collapse-passed <on|off>",
|
|
150
|
+
"/config reset",
|
|
137
151
|
"/clear",
|
|
138
152
|
"/quit",
|
|
139
153
|
];
|
|
140
154
|
}
|
|
141
155
|
|
|
156
|
+
function parseBooleanToken(value, commandName) {
|
|
157
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
158
|
+
if (["on", "true", "yes", "1"].includes(normalized)) return true;
|
|
159
|
+
if (["off", "false", "no", "0"].includes(normalized)) return false;
|
|
160
|
+
throw new Error(`${commandName} expects on or off`);
|
|
161
|
+
}
|
|
162
|
+
|
|
142
163
|
function parseRunCommandTokens(tokens) {
|
|
143
164
|
const options = {
|
|
144
165
|
type: [],
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { loadCurrentRunArtifact, loadLatestRunArtifact } from "
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { loadCurrentRunArtifact, loadLatestRunArtifact } from "../../results/artifacts.mjs";
|
|
2
|
+
import {
|
|
3
|
+
formatCliConfig,
|
|
4
|
+
loadCliConfig,
|
|
5
|
+
mergeCliConfig,
|
|
6
|
+
resetCliConfig,
|
|
7
|
+
saveCliConfig,
|
|
8
|
+
} from "../config.mjs";
|
|
9
|
+
import { createRunState } from "../state/run/state.mjs";
|
|
10
|
+
import { buildContextSelection } from "../../results/context.mjs";
|
|
11
|
+
import { isProviderInstalled } from "./providers/index.mjs";
|
|
5
12
|
import { parseSlashCommand, formatSlashHelpLines } from "./slash-commands.mjs";
|
|
6
13
|
import { executeAssistantTool } from "./tool-registry.mjs";
|
|
7
14
|
import { runAssistantConversationTurn } from "./session.mjs";
|
|
@@ -41,10 +48,12 @@ export function createAssistantState({
|
|
|
41
48
|
configs = [],
|
|
42
49
|
env = process.env,
|
|
43
50
|
} = {}) {
|
|
44
|
-
const
|
|
51
|
+
const runState = createRunState({ dataSource });
|
|
52
|
+
let cliConfig = loadCliConfig(productDir);
|
|
53
|
+
runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
|
|
45
54
|
const commandLog = prepareAssistantContextPack({
|
|
46
55
|
productDir,
|
|
47
|
-
|
|
56
|
+
runState,
|
|
48
57
|
});
|
|
49
58
|
|
|
50
59
|
const listeners = new Set();
|
|
@@ -76,8 +85,11 @@ export function createAssistantState({
|
|
|
76
85
|
model: settings.model,
|
|
77
86
|
prompt: "",
|
|
78
87
|
});
|
|
88
|
+
let liveRunSession = null;
|
|
89
|
+
let lastRunSession = null;
|
|
90
|
+
let liveRunSessionUnsubscribe = null;
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
runState.subscribe(() => {
|
|
81
93
|
commandLog.refresh();
|
|
82
94
|
notify();
|
|
83
95
|
});
|
|
@@ -104,13 +116,46 @@ export function createAssistantState({
|
|
|
104
116
|
commandLog.refresh();
|
|
105
117
|
}
|
|
106
118
|
|
|
119
|
+
function attachRunSession(session, { active = true } = {}) {
|
|
120
|
+
if (liveRunSessionUnsubscribe) {
|
|
121
|
+
liveRunSessionUnsubscribe();
|
|
122
|
+
liveRunSessionUnsubscribe = null;
|
|
123
|
+
}
|
|
124
|
+
if (!session) {
|
|
125
|
+
if (!active) notify();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
lastRunSession = session;
|
|
129
|
+
if (active) {
|
|
130
|
+
liveRunSession = session;
|
|
131
|
+
liveRunSessionUnsubscribe = session.runState.subscribe(() => {
|
|
132
|
+
notify();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
notify();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function completeRunSession(session) {
|
|
139
|
+
if (liveRunSession === session) {
|
|
140
|
+
if (liveRunSessionUnsubscribe) {
|
|
141
|
+
liveRunSessionUnsubscribe();
|
|
142
|
+
liveRunSessionUnsubscribe = null;
|
|
143
|
+
}
|
|
144
|
+
liveRunSession = null;
|
|
145
|
+
}
|
|
146
|
+
if (session) lastRunSession = session;
|
|
147
|
+
notify();
|
|
148
|
+
}
|
|
149
|
+
|
|
107
150
|
const state = {
|
|
108
|
-
|
|
151
|
+
runState,
|
|
109
152
|
commandLog,
|
|
153
|
+
attachRunSession,
|
|
154
|
+
completeRunSession,
|
|
110
155
|
|
|
111
156
|
async loadLatestArtifact() {
|
|
112
157
|
try {
|
|
113
|
-
|
|
158
|
+
runState.hydrateFromArtifact(loadLatestRunArtifact(productDir));
|
|
114
159
|
} catch {
|
|
115
160
|
// No artifact yet.
|
|
116
161
|
}
|
|
@@ -119,7 +164,7 @@ export function createAssistantState({
|
|
|
119
164
|
|
|
120
165
|
async loadCurrentArtifact() {
|
|
121
166
|
try {
|
|
122
|
-
|
|
167
|
+
runState.hydrateFromArtifact(loadCurrentRunArtifact(productDir));
|
|
123
168
|
} catch {
|
|
124
169
|
// No artifact yet.
|
|
125
170
|
}
|
|
@@ -127,13 +172,13 @@ export function createAssistantState({
|
|
|
127
172
|
},
|
|
128
173
|
|
|
129
174
|
revealFile(serviceName, filePath) {
|
|
130
|
-
const revealed =
|
|
175
|
+
const revealed = runState.revealFile(serviceName, filePath);
|
|
131
176
|
refreshContextPack();
|
|
132
177
|
return revealed;
|
|
133
178
|
},
|
|
134
179
|
|
|
135
180
|
revealService(serviceName) {
|
|
136
|
-
const revealed =
|
|
181
|
+
const revealed = runState.revealService(serviceName);
|
|
137
182
|
refreshContextPack();
|
|
138
183
|
return revealed;
|
|
139
184
|
},
|
|
@@ -228,6 +273,19 @@ export function createAssistantState({
|
|
|
228
273
|
notify();
|
|
229
274
|
},
|
|
230
275
|
|
|
276
|
+
setCliConfig(nextConfig) {
|
|
277
|
+
cliConfig = mergeCliConfig(cliConfig, nextConfig);
|
|
278
|
+
saveCliConfig(productDir, cliConfig);
|
|
279
|
+
runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
|
|
280
|
+
notify();
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
resetCliConfig() {
|
|
284
|
+
cliConfig = resetCliConfig(productDir);
|
|
285
|
+
runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
|
|
286
|
+
notify();
|
|
287
|
+
},
|
|
288
|
+
|
|
231
289
|
resetSettings() {
|
|
232
290
|
settings = resetAssistantSettings(productDir);
|
|
233
291
|
resolvedProviderName = null;
|
|
@@ -239,6 +297,14 @@ export function createAssistantState({
|
|
|
239
297
|
notify();
|
|
240
298
|
},
|
|
241
299
|
|
|
300
|
+
getLiveRunSession() {
|
|
301
|
+
return liveRunSession;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
getLastRunSession() {
|
|
305
|
+
return lastRunSession;
|
|
306
|
+
},
|
|
307
|
+
|
|
242
308
|
async submitCurrentComposer() {
|
|
243
309
|
const value = composerState.text.trim();
|
|
244
310
|
composerState = createComposerState();
|
|
@@ -263,6 +329,7 @@ export function createAssistantState({
|
|
|
263
329
|
}
|
|
264
330
|
if (slash) {
|
|
265
331
|
try {
|
|
332
|
+
setBusy(true, `Running ${slash.type}...`);
|
|
266
333
|
await executeSlashCommand({
|
|
267
334
|
slash,
|
|
268
335
|
state,
|
|
@@ -277,6 +344,8 @@ export function createAssistantState({
|
|
|
277
344
|
role: "system",
|
|
278
345
|
text: error instanceof Error ? error.message : String(error),
|
|
279
346
|
});
|
|
347
|
+
} finally {
|
|
348
|
+
setBusy(false, null);
|
|
280
349
|
}
|
|
281
350
|
refreshContextPack();
|
|
282
351
|
notify();
|
|
@@ -286,6 +355,7 @@ export function createAssistantState({
|
|
|
286
355
|
const routedSlash = routeLocalIntent(trimmed);
|
|
287
356
|
if (routedSlash) {
|
|
288
357
|
try {
|
|
358
|
+
setBusy(true, `Running ${routedSlash.type}...`);
|
|
289
359
|
await executeSlashCommand({
|
|
290
360
|
slash: routedSlash,
|
|
291
361
|
state,
|
|
@@ -300,6 +370,8 @@ export function createAssistantState({
|
|
|
300
370
|
role: "system",
|
|
301
371
|
text: error instanceof Error ? error.message : String(error),
|
|
302
372
|
});
|
|
373
|
+
} finally {
|
|
374
|
+
setBusy(false, null);
|
|
303
375
|
}
|
|
304
376
|
refreshContextPack();
|
|
305
377
|
notify();
|
|
@@ -310,7 +382,7 @@ export function createAssistantState({
|
|
|
310
382
|
setBusy(true, `Thinking with ${settings.provider === "auto" ? "provider" : settings.provider}...`);
|
|
311
383
|
const emitted = await runAssistantConversationTurn({
|
|
312
384
|
productDir,
|
|
313
|
-
|
|
385
|
+
runState,
|
|
314
386
|
transcript: messages.map((entry) => ({ role: entry.role, text: entry.text })),
|
|
315
387
|
userMessage: trimmed,
|
|
316
388
|
settings,
|
|
@@ -334,7 +406,7 @@ export function createAssistantState({
|
|
|
334
406
|
notify();
|
|
335
407
|
},
|
|
336
408
|
onToolEvent(event) {
|
|
337
|
-
handleAssistantToolEvent(event, appendMessage);
|
|
409
|
+
handleAssistantToolEvent(state, event, appendMessage);
|
|
338
410
|
},
|
|
339
411
|
});
|
|
340
412
|
for (const message of emitted) appendMessage(message);
|
|
@@ -356,8 +428,8 @@ export function createAssistantState({
|
|
|
356
428
|
|
|
357
429
|
getSnapshot() {
|
|
358
430
|
return {
|
|
359
|
-
context: buildContextSelection(
|
|
360
|
-
|
|
431
|
+
context: buildContextSelection(runState.getSnapshot()),
|
|
432
|
+
run: runState.getSnapshot(),
|
|
361
433
|
productDir,
|
|
362
434
|
messages: [...messages],
|
|
363
435
|
composer: composerState.text,
|
|
@@ -369,8 +441,11 @@ export function createAssistantState({
|
|
|
369
441
|
model: settings.model,
|
|
370
442
|
effort: settings.effort,
|
|
371
443
|
providerArgs: [...settings.providerArgs],
|
|
444
|
+
cliConfig,
|
|
372
445
|
activeStatus,
|
|
373
446
|
contextUsage,
|
|
447
|
+
liveRunSession: serializeRunSession(liveRunSession),
|
|
448
|
+
lastRunSession: serializeRunSession(lastRunSession),
|
|
374
449
|
contextPaths: {
|
|
375
450
|
contextPath: commandLog.contextPath,
|
|
376
451
|
summaryPath: commandLog.summaryPath,
|
|
@@ -460,28 +535,32 @@ async function executeSlashCommand({
|
|
|
460
535
|
appendMessage({ role: "assistant", text: "Assistant settings reset." });
|
|
461
536
|
return;
|
|
462
537
|
}
|
|
538
|
+
if (slash.type === "config-show") {
|
|
539
|
+
appendMessage({ role: "assistant", text: formatCliConfig(state.getSnapshot().cliConfig) });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (slash.type === "config-reset") {
|
|
543
|
+
state.resetCliConfig();
|
|
544
|
+
appendMessage({ role: "assistant", text: "CLI config reset." });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (slash.type === "config-set-auto-collapse") {
|
|
548
|
+
state.setCliConfig({ autoCollapsePassedTreeBranches: slash.value });
|
|
549
|
+
appendMessage({
|
|
550
|
+
role: "assistant",
|
|
551
|
+
text: `autoCollapsePassedTreeBranches set to ${slash.value}.`,
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
463
555
|
|
|
464
556
|
const result = await executeSlashTool(slash, {
|
|
465
557
|
productDir,
|
|
466
|
-
|
|
558
|
+
runState: state.runState,
|
|
467
559
|
configs,
|
|
468
560
|
env,
|
|
469
561
|
commandLog: state.commandLog,
|
|
470
562
|
onEvent(event) {
|
|
471
|
-
|
|
472
|
-
appendMessage({
|
|
473
|
-
role: "tool",
|
|
474
|
-
status: "running",
|
|
475
|
-
title: event.title || event.tool || "Tool",
|
|
476
|
-
text: event.message,
|
|
477
|
-
data: {
|
|
478
|
-
command: event.command || null,
|
|
479
|
-
testkitRelated: Boolean(event.testkitRelated),
|
|
480
|
-
},
|
|
481
|
-
});
|
|
482
|
-
} else if (event.type === "tool-status") {
|
|
483
|
-
state.setNotice(event.message);
|
|
484
|
-
}
|
|
563
|
+
handleAssistantToolEvent(state, event, appendMessage);
|
|
485
564
|
},
|
|
486
565
|
provider: settings.provider,
|
|
487
566
|
});
|
|
@@ -494,18 +573,32 @@ async function executeSlashCommand({
|
|
|
494
573
|
});
|
|
495
574
|
}
|
|
496
575
|
|
|
497
|
-
function handleAssistantToolEvent(event, appendMessage) {
|
|
498
|
-
if (!event
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
576
|
+
function handleAssistantToolEvent(state, event, appendMessage) {
|
|
577
|
+
if (!event) return;
|
|
578
|
+
if (event.type === "tool-start") {
|
|
579
|
+
appendMessage({
|
|
580
|
+
role: "tool",
|
|
581
|
+
status: "running",
|
|
582
|
+
title: event.title || event.tool || "Tool",
|
|
583
|
+
text: event.message || "Running tool",
|
|
584
|
+
data: {
|
|
585
|
+
command: event.command || null,
|
|
586
|
+
testkitRelated: Boolean(event.testkitRelated),
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (event.type === "tool-status") {
|
|
592
|
+
state.setNotice(event.message);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (event.type === "run-session-start") {
|
|
596
|
+
state.attachRunSession(event.session, { active: true });
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (event.type === "run-session-end") {
|
|
600
|
+
state.completeRunSession(event.session);
|
|
601
|
+
}
|
|
509
602
|
}
|
|
510
603
|
|
|
511
604
|
function formatSettings(snapshot) {
|
|
@@ -538,46 +631,18 @@ async function executeSlashTool(slash, context) {
|
|
|
538
631
|
case "service":
|
|
539
632
|
return executeAssistantTool("read_context", { service: slash.service, mode: "detail" }, context);
|
|
540
633
|
case "status":
|
|
541
|
-
return executeAssistantTool("
|
|
634
|
+
return executeAssistantTool("show_status", {}, context);
|
|
542
635
|
case "discover":
|
|
543
|
-
return executeAssistantTool("
|
|
636
|
+
return executeAssistantTool("discover_tests", {}, context);
|
|
544
637
|
case "doctor":
|
|
545
|
-
return executeAssistantTool("
|
|
638
|
+
return executeAssistantTool("run_doctor", {}, context);
|
|
546
639
|
case "run":
|
|
547
|
-
return executeAssistantTool("
|
|
640
|
+
return executeAssistantTool("run_tests", slash.options, context);
|
|
548
641
|
default:
|
|
549
642
|
throw new Error(`Unsupported slash command "${slash.type}"`);
|
|
550
643
|
}
|
|
551
644
|
}
|
|
552
645
|
|
|
553
|
-
function buildRunSlashCommand(options = {}) {
|
|
554
|
-
const types = options.type || [];
|
|
555
|
-
const parts = ["testkit", "run"];
|
|
556
|
-
if (types.length === 1) {
|
|
557
|
-
parts.push(types[0]);
|
|
558
|
-
}
|
|
559
|
-
parts.push("--dir", ".");
|
|
560
|
-
if (types.length !== 1) {
|
|
561
|
-
for (const type of types) {
|
|
562
|
-
parts.push("--type", type);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
for (const suite of options.suite || []) {
|
|
566
|
-
parts.push("--suite", suite);
|
|
567
|
-
}
|
|
568
|
-
for (const file of options.file || []) {
|
|
569
|
-
parts.push("--file", file);
|
|
570
|
-
}
|
|
571
|
-
if (options.service) parts.push("--service", options.service);
|
|
572
|
-
return parts.map(shellEscapeArg).join(" ");
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function shellEscapeArg(value) {
|
|
576
|
-
const stringValue = String(value);
|
|
577
|
-
if (/^[a-zA-Z0-9._:/-]+$/.test(stringValue)) return stringValue;
|
|
578
|
-
return `'${stringValue.replace(/'/g, `'\\''`)}'`;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
646
|
function parseSlashCommandSafe(input) {
|
|
582
647
|
try {
|
|
583
648
|
return parseSlashCommand(input);
|
|
@@ -619,3 +684,11 @@ function routeLocalIntent(input) {
|
|
|
619
684
|
if (/^list\s+test\s+files$/.test(normalized)) return { type: "discover" };
|
|
620
685
|
return null;
|
|
621
686
|
}
|
|
687
|
+
|
|
688
|
+
function serializeRunSession(session) {
|
|
689
|
+
if (!session) return null;
|
|
690
|
+
return {
|
|
691
|
+
productDir: session.productDir,
|
|
692
|
+
snapshot: session.getSnapshot(),
|
|
693
|
+
};
|
|
694
|
+
}
|