@elench/testkit 0.1.97 → 0.1.99
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/README.md +8 -8
- package/lib/app/browser-bridge.mjs +1 -1
- package/lib/cli/assistant/actions.mjs +333 -0
- package/lib/cli/assistant/app.mjs +25 -1
- package/lib/cli/assistant/command-observer.mjs +110 -0
- package/lib/cli/assistant/command-results.mjs +167 -0
- package/lib/cli/assistant/composer.mjs +1 -1
- package/lib/cli/assistant/context-pack.mjs +73 -6
- package/lib/cli/assistant/interactive.mjs +1 -1
- package/lib/cli/assistant/prompt-builder.mjs +15 -8
- package/lib/cli/{agents → assistant}/providers/claude.mjs +2 -3
- package/lib/cli/{agents → assistant}/providers/codex.mjs +2 -6
- package/lib/cli/{agents → assistant/providers}/index.mjs +5 -5
- package/lib/cli/assistant/session.mjs +36 -94
- package/lib/cli/assistant/slash-commands.mjs +22 -1
- package/lib/cli/assistant/state.mjs +187 -100
- package/lib/cli/assistant/view-model.mjs +1 -1
- package/lib/cli/command-flags.mjs +61 -0
- 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 +13 -32
- package/lib/cli/commands/doctor.mjs +17 -14
- package/lib/cli/commands/run.mjs +14 -3
- package/lib/cli/commands/status.mjs +14 -3
- package/lib/cli/commands/typecheck.mjs +12 -9
- 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/entrypoint.mjs +14 -5
- 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 +7 -8
- package/lib/cli/assistant/protocol.mjs +0 -67
- package/lib/cli/assistant/tool-registry.mjs +0 -318
- 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/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,15 +1,26 @@
|
|
|
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
|
+
import {
|
|
6
|
+
ASSISTANT_COMMAND_ID_ENV,
|
|
7
|
+
ASSISTANT_COMMAND_LOG_ENV,
|
|
8
|
+
ASSISTANT_RESULT_DIR_ENV,
|
|
9
|
+
ASSISTANT_SESSION_ENV,
|
|
10
|
+
ASSISTANT_WRAPPER_LOGGED_ENV,
|
|
11
|
+
} from "./command-results.mjs";
|
|
5
12
|
|
|
6
13
|
export function prepareAssistantContextPack({
|
|
7
14
|
productDir,
|
|
8
|
-
|
|
15
|
+
runState,
|
|
9
16
|
} = {}) {
|
|
10
17
|
const contextDir = path.join(productDir, ".testkit", "assistant");
|
|
11
18
|
const binDir = path.join(contextDir, "bin");
|
|
19
|
+
const resultDir = path.join(contextDir, "command-results");
|
|
20
|
+
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
12
21
|
fs.mkdirSync(binDir, { recursive: true });
|
|
22
|
+
fs.rmSync(resultDir, { recursive: true, force: true });
|
|
23
|
+
fs.mkdirSync(resultDir, { recursive: true });
|
|
13
24
|
|
|
14
25
|
const commandLogPath = path.join(contextDir, "commands.jsonl");
|
|
15
26
|
const contextPath = path.join(contextDir, "context.md");
|
|
@@ -23,7 +34,7 @@ export function prepareAssistantContextPack({
|
|
|
23
34
|
const wrapperPath = path.join(binDir, "testkit");
|
|
24
35
|
|
|
25
36
|
function refresh() {
|
|
26
|
-
const snapshot =
|
|
37
|
+
const snapshot = runState?.getSnapshot?.() || {};
|
|
27
38
|
const detailContent = readContextContent({ productDir, snapshot, mode: "detail", logTail: 12 });
|
|
28
39
|
const logsContent = readContextContent({ productDir, snapshot, mode: "logs", logTail: 12 });
|
|
29
40
|
const artifactsContent = readContextContent({ productDir, snapshot, mode: "artifacts", logTail: 12 });
|
|
@@ -55,7 +66,7 @@ export function prepareAssistantContextPack({
|
|
|
55
66
|
}),
|
|
56
67
|
"utf8"
|
|
57
68
|
);
|
|
58
|
-
fs.writeFileSync(wrapperPath, buildWrapperScript({ cliPath: resolveCliPath() }), {
|
|
69
|
+
fs.writeFileSync(wrapperPath, buildWrapperScript({ cliPath: resolveCliPath(), sessionId, resultDir, commandLogPath }), {
|
|
59
70
|
encoding: "utf8",
|
|
60
71
|
mode: 0o755,
|
|
61
72
|
});
|
|
@@ -71,12 +82,23 @@ export function prepareAssistantContextPack({
|
|
|
71
82
|
selectionPath,
|
|
72
83
|
commandsPath,
|
|
73
84
|
commandLogPath,
|
|
85
|
+
resultDir,
|
|
86
|
+
sessionId,
|
|
74
87
|
focusedDetailPath,
|
|
75
88
|
focusedLogsPath,
|
|
76
89
|
focusedArtifactsPath,
|
|
77
90
|
focusedSetupPath,
|
|
78
91
|
binDir,
|
|
79
92
|
wrapperPath,
|
|
93
|
+
providerEnv(baseEnv = process.env) {
|
|
94
|
+
return {
|
|
95
|
+
...baseEnv,
|
|
96
|
+
PATH: [binDir, baseEnv?.PATH, process.env.PATH].filter(Boolean).join(path.delimiter),
|
|
97
|
+
[ASSISTANT_SESSION_ENV]: sessionId,
|
|
98
|
+
[ASSISTANT_RESULT_DIR_ENV]: resultDir,
|
|
99
|
+
[ASSISTANT_COMMAND_LOG_ENV]: commandLogPath,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
80
102
|
refresh,
|
|
81
103
|
appendCommandLog(event) {
|
|
82
104
|
if (!event || typeof event !== "object") return;
|
|
@@ -97,15 +119,36 @@ function resolveCliPath() {
|
|
|
97
119
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "bin", "testkit.mjs");
|
|
98
120
|
}
|
|
99
121
|
|
|
100
|
-
function buildWrapperScript({ cliPath } = {}) {
|
|
122
|
+
function buildWrapperScript({ cliPath, sessionId, resultDir, commandLogPath } = {}) {
|
|
101
123
|
return `#!/usr/bin/env node
|
|
102
124
|
import { spawnSync } from "child_process";
|
|
125
|
+
import fs from "fs";
|
|
126
|
+
import path from "path";
|
|
127
|
+
|
|
128
|
+
const commandId = process.env.${ASSISTANT_COMMAND_ID_ENV} || \`cmd-\${Date.now()}-\${Math.random().toString(36).slice(2, 10)}\`;
|
|
129
|
+
const commandLogPath = process.env.${ASSISTANT_COMMAND_LOG_ENV} || ${JSON.stringify(commandLogPath)};
|
|
130
|
+
const sessionId = process.env.${ASSISTANT_SESSION_ENV} || ${JSON.stringify(sessionId)};
|
|
131
|
+
const argv = process.argv.slice(2);
|
|
132
|
+
|
|
133
|
+
appendCommandLog({
|
|
134
|
+
type: "command_start",
|
|
135
|
+
commandId,
|
|
136
|
+
command: "testkit",
|
|
137
|
+
kind: inferKind(argv),
|
|
138
|
+
argv,
|
|
139
|
+
cwd: process.cwd(),
|
|
140
|
+
});
|
|
103
141
|
|
|
104
142
|
const result = spawnSync(process.execPath, [${JSON.stringify(cliPath)}, ...process.argv.slice(2)], {
|
|
105
143
|
cwd: process.cwd(),
|
|
106
144
|
env: {
|
|
107
145
|
...process.env,
|
|
108
146
|
TESTKIT_NO_ASSISTANT_DEFAULT: "1",
|
|
147
|
+
${ASSISTANT_SESSION_ENV}: sessionId,
|
|
148
|
+
${ASSISTANT_RESULT_DIR_ENV}: process.env.${ASSISTANT_RESULT_DIR_ENV} || ${JSON.stringify(resultDir)},
|
|
149
|
+
${ASSISTANT_COMMAND_LOG_ENV}: commandLogPath,
|
|
150
|
+
${ASSISTANT_COMMAND_ID_ENV}: commandId,
|
|
151
|
+
${ASSISTANT_WRAPPER_LOGGED_ENV}: "1",
|
|
109
152
|
},
|
|
110
153
|
stdio: "inherit",
|
|
111
154
|
});
|
|
@@ -113,7 +156,30 @@ const result = spawnSync(process.execPath, [${JSON.stringify(cliPath)}, ...proce
|
|
|
113
156
|
if (result.error) {
|
|
114
157
|
throw result.error;
|
|
115
158
|
}
|
|
159
|
+
appendCommandLog({
|
|
160
|
+
type: "command_exit",
|
|
161
|
+
commandId,
|
|
162
|
+
command: "testkit",
|
|
163
|
+
kind: inferKind(argv),
|
|
164
|
+
argv,
|
|
165
|
+
cwd: process.cwd(),
|
|
166
|
+
code: result.status ?? 0,
|
|
167
|
+
signal: result.signal ?? null,
|
|
168
|
+
});
|
|
116
169
|
process.exit(result.status ?? 0);
|
|
170
|
+
|
|
171
|
+
function appendCommandLog(event) {
|
|
172
|
+
try {
|
|
173
|
+
fs.mkdirSync(path.dirname(commandLogPath), { recursive: true });
|
|
174
|
+
fs.appendFileSync(commandLogPath, \`\${JSON.stringify({ timestamp: new Date().toISOString(), sessionId, ...event })}\\n\`, "utf8");
|
|
175
|
+
} catch {
|
|
176
|
+
// Command observation must not affect command execution.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function inferKind(args) {
|
|
181
|
+
return args.find((arg) => !String(arg).startsWith("-")) || "run";
|
|
182
|
+
}
|
|
117
183
|
`;
|
|
118
184
|
}
|
|
119
185
|
|
|
@@ -160,7 +226,8 @@ function buildContextMarkdown(productDir, snapshot, paths) {
|
|
|
160
226
|
lines.push(
|
|
161
227
|
"",
|
|
162
228
|
"## Guidance",
|
|
163
|
-
"-
|
|
229
|
+
"- Work normally in the repository and run real commands. Testkit observes recognized Testkit commands and renders them in the assistant UI.",
|
|
230
|
+
"- Prefer `testkit ...` commands when using Testkit directly; `npx testkit ...` and project scripts are also valid.",
|
|
164
231
|
"- Do not reinterpret CLI syntax after an execution failure unless `testkit run --help` confirms a syntax problem.",
|
|
165
232
|
"- Use the command log and focused context files before rereading artifacts manually.",
|
|
166
233
|
"- 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,12 +1,11 @@
|
|
|
1
|
-
import { readContextContent } from "
|
|
2
|
-
import { buildAssistantResponseContract } from "./protocol.mjs";
|
|
1
|
+
import { readContextContent } from "../../results/context.mjs";
|
|
3
2
|
|
|
4
3
|
export function buildAssistantPrompt({
|
|
5
4
|
productDir,
|
|
6
5
|
snapshot,
|
|
7
6
|
transcript = [],
|
|
8
|
-
tools = [],
|
|
9
7
|
userMessage,
|
|
8
|
+
commandLog = null,
|
|
10
9
|
} = {}) {
|
|
11
10
|
const selectionSummary = buildSelectionSummary(snapshot);
|
|
12
11
|
const focusPreview = buildFocusPreview(productDir, snapshot);
|
|
@@ -14,11 +13,19 @@ export function buildAssistantPrompt({
|
|
|
14
13
|
|
|
15
14
|
return [
|
|
16
15
|
"You are Testkit Assistant.",
|
|
17
|
-
"You
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
16
|
+
"You are running as a coding agent inside the user's repository.",
|
|
17
|
+
"Work normally: inspect files, edit files, run real shell commands, and iterate until the user's request is handled.",
|
|
18
|
+
"When using Testkit, run real commands such as `testkit discover`, `testkit run int`, `npx testkit run int`, or the repository's package scripts.",
|
|
19
|
+
"Testkit observes recognized Testkit commands and renders rich assistant UI from the real command output, sidecars, and artifacts.",
|
|
20
|
+
"Do not respond with a JSON tool envelope. Give the user a normal final answer when you are done.",
|
|
21
|
+
"",
|
|
22
|
+
"Assistant context files:",
|
|
23
|
+
...(commandLog ? [
|
|
24
|
+
`- Context: ${commandLog.contextPath}`,
|
|
25
|
+
`- Command reference: ${commandLog.commandsPath}`,
|
|
26
|
+
`- Command log: ${commandLog.commandLogPath}`,
|
|
27
|
+
`- Current selection: ${commandLog.selectionPath}`,
|
|
28
|
+
] : ["- No assistant context pack is available."]),
|
|
22
29
|
"",
|
|
23
30
|
"Current run summary:",
|
|
24
31
|
...(summaryRows.length > 0 ? summaryRows.map(([label, value]) => `- ${label}: ${value}`) : ["- No run artifact is currently loaded."]),
|
|
@@ -16,6 +16,7 @@ export function startClaudeHostedSession({
|
|
|
16
16
|
model = null,
|
|
17
17
|
effort = null,
|
|
18
18
|
providerArgs = [],
|
|
19
|
+
env = process.env,
|
|
19
20
|
} = {}) {
|
|
20
21
|
const args = [
|
|
21
22
|
"-p",
|
|
@@ -25,9 +26,6 @@ export function startClaudeHostedSession({
|
|
|
25
26
|
"--include-partial-messages",
|
|
26
27
|
];
|
|
27
28
|
|
|
28
|
-
if (purpose === "assistant") {
|
|
29
|
-
args.push("--permission-mode", "plan");
|
|
30
|
-
}
|
|
31
29
|
if (model) {
|
|
32
30
|
args.push("--model", String(model));
|
|
33
31
|
}
|
|
@@ -44,6 +42,7 @@ export function startClaudeHostedSession({
|
|
|
44
42
|
stdout: "pipe",
|
|
45
43
|
stderr: "pipe",
|
|
46
44
|
reject: false,
|
|
45
|
+
env,
|
|
47
46
|
});
|
|
48
47
|
|
|
49
48
|
return createHostedSessionRunner({
|
|
@@ -19,6 +19,7 @@ export function startCodexHostedSession({
|
|
|
19
19
|
purpose = "assistant",
|
|
20
20
|
model = null,
|
|
21
21
|
providerArgs = [],
|
|
22
|
+
env = process.env,
|
|
22
23
|
} = {}) {
|
|
23
24
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-codex-"));
|
|
24
25
|
const outputFile = path.join(tempDir, "final-message.txt");
|
|
@@ -28,7 +29,6 @@ export function startCodexHostedSession({
|
|
|
28
29
|
model,
|
|
29
30
|
providerArgs,
|
|
30
31
|
prompt,
|
|
31
|
-
sandbox: process.env.TESTKIT_CODEX_SANDBOX,
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
const child = execa(command, args, {
|
|
@@ -37,6 +37,7 @@ export function startCodexHostedSession({
|
|
|
37
37
|
stdout: "pipe",
|
|
38
38
|
stderr: "pipe",
|
|
39
39
|
reject: false,
|
|
40
|
+
env,
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
const session = createHostedSessionRunner({
|
|
@@ -64,18 +65,13 @@ export function startCodexHostedSession({
|
|
|
64
65
|
|
|
65
66
|
export function buildCodexArgs({
|
|
66
67
|
outputFile,
|
|
67
|
-
purpose = "assistant",
|
|
68
68
|
model = null,
|
|
69
69
|
providerArgs = [],
|
|
70
70
|
prompt = "",
|
|
71
|
-
sandbox = null,
|
|
72
71
|
} = {}) {
|
|
73
72
|
const args = ["exec", "--json"];
|
|
74
73
|
if (outputFile) args.push("-o", outputFile);
|
|
75
74
|
|
|
76
|
-
if (purpose === "assistant") {
|
|
77
|
-
args.push("-s", String(sandbox || "workspace-write"));
|
|
78
|
-
}
|
|
79
75
|
if (model) {
|
|
80
76
|
args.push("--model", String(model));
|
|
81
77
|
}
|
|
@@ -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,
|
|
@@ -69,7 +69,7 @@ export function startAgentSession({
|
|
|
69
69
|
const resolvedProvider = resolvePreferredProvider(provider, env);
|
|
70
70
|
const command = resolveProviderBinary(resolvedProvider, env);
|
|
71
71
|
if (resolvedProvider === "claude") {
|
|
72
|
-
return startClaudeHostedSession({ command, cwd, prompt, onEvent, purpose, model, effort, providerArgs });
|
|
72
|
+
return startClaudeHostedSession({ command, cwd, prompt, onEvent, purpose, model, effort, providerArgs, env });
|
|
73
73
|
}
|
|
74
|
-
return startCodexHostedSession({ command, cwd, prompt, onEvent, purpose, model, providerArgs });
|
|
74
|
+
return startCodexHostedSession({ command, cwd, prompt, onEvent, purpose, model, providerArgs, env });
|
|
75
75
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { startProviderSession, resolvePreferredProvider } from "./providers/index.mjs";
|
|
2
2
|
import { buildAssistantPrompt } from "./prompt-builder.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { parseAssistantEnvelope } from "./protocol.mjs";
|
|
3
|
+
import { createAssistantCommandObserver } from "./command-observer.mjs";
|
|
5
4
|
|
|
6
5
|
export async function runAssistantConversationTurn({
|
|
7
6
|
productDir,
|
|
8
|
-
|
|
7
|
+
runState,
|
|
9
8
|
transcript,
|
|
10
9
|
userMessage,
|
|
11
10
|
provider = "auto",
|
|
@@ -18,41 +17,37 @@ export async function runAssistantConversationTurn({
|
|
|
18
17
|
onResolvedProvider,
|
|
19
18
|
onPrompt,
|
|
20
19
|
} = {}) {
|
|
21
|
-
const
|
|
22
|
-
const toolContext = {
|
|
20
|
+
const observer = createAssistantCommandObserver({
|
|
23
21
|
productDir,
|
|
24
|
-
|
|
25
|
-
configs,
|
|
26
|
-
env,
|
|
22
|
+
runState,
|
|
27
23
|
commandLog,
|
|
28
24
|
onEvent: onToolEvent,
|
|
29
|
-
};
|
|
25
|
+
});
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
const
|
|
27
|
+
const snapshot = runState.getSnapshot();
|
|
28
|
+
const prompt = buildAssistantPrompt({
|
|
29
|
+
productDir,
|
|
30
|
+
snapshot,
|
|
31
|
+
transcript: [...(transcript || []), { role: "user", text: userMessage }],
|
|
32
|
+
userMessage,
|
|
33
|
+
commandLog,
|
|
34
|
+
});
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
const runtimeSettings = settings || { provider };
|
|
37
|
+
const resolvedProvider = resolvePreferredProvider(runtimeSettings.provider || provider, env);
|
|
38
|
+
const providerEnv = commandLog?.providerEnv?.(env) || env;
|
|
39
|
+
onResolvedProvider?.(resolvedProvider);
|
|
40
|
+
onPrompt?.({
|
|
41
|
+
prompt,
|
|
42
|
+
provider: resolvedProvider,
|
|
43
|
+
model: runtimeSettings.model || null,
|
|
44
|
+
effort: runtimeSettings.effort || null,
|
|
45
|
+
});
|
|
46
|
+
onStatus?.(`Thinking with ${resolvedProvider}...`);
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
onPrompt?.({
|
|
48
|
-
prompt,
|
|
49
|
-
provider: resolvedProvider,
|
|
50
|
-
model: runtimeSettings.model || null,
|
|
51
|
-
effort: runtimeSettings.effort || null,
|
|
52
|
-
});
|
|
53
|
-
onStatus?.(`Thinking with ${resolvedProvider}...`);
|
|
54
|
-
const events = [];
|
|
55
|
-
const session = startAgentSession({
|
|
48
|
+
observer.start();
|
|
49
|
+
try {
|
|
50
|
+
const session = startProviderSession({
|
|
56
51
|
provider: runtimeSettings.provider || provider,
|
|
57
52
|
model: runtimeSettings.model || null,
|
|
58
53
|
effort: runtimeSettings.effort || null,
|
|
@@ -60,74 +55,21 @@ export async function runAssistantConversationTurn({
|
|
|
60
55
|
cwd: productDir,
|
|
61
56
|
prompt,
|
|
62
57
|
purpose: "assistant",
|
|
63
|
-
env,
|
|
58
|
+
env: providerEnv,
|
|
64
59
|
onEvent(event) {
|
|
65
|
-
events.push(event);
|
|
66
60
|
if (event.type === "status" || event.type === "tool") onStatus?.(formatProviderEvent(event));
|
|
67
61
|
},
|
|
68
62
|
});
|
|
69
63
|
const result = await session.completion;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (envelope.commentary) {
|
|
74
|
-
emitted.push({ role: "assistant", text: envelope.commentary });
|
|
75
|
-
currentTranscript.push({ role: "assistant", text: envelope.commentary });
|
|
76
|
-
}
|
|
77
|
-
let toolResult;
|
|
78
|
-
try {
|
|
79
|
-
toolResult = await executeAssistantTool(envelope.tool, envelope.arguments, toolContext);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
const toolText = formatToolError(envelope.tool, error);
|
|
82
|
-
emitted.push({
|
|
83
|
-
role: "tool",
|
|
84
|
-
text: toolText,
|
|
85
|
-
toolName: envelope.tool,
|
|
86
|
-
title: `${envelope.tool} error`,
|
|
87
|
-
data: { ok: false, error: toolText },
|
|
88
|
-
});
|
|
89
|
-
currentTranscript.push({
|
|
90
|
-
role: "tool",
|
|
91
|
-
text: `${envelope.tool}: ${toolText}`,
|
|
92
|
-
});
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const toolText = toolResult.text || `${envelope.tool} completed`;
|
|
96
|
-
emitted.push({
|
|
97
|
-
role: "tool",
|
|
98
|
-
text: toolText,
|
|
99
|
-
toolName: envelope.tool,
|
|
100
|
-
title: toolResult.title || envelope.tool,
|
|
101
|
-
data: toolResult.data || null,
|
|
102
|
-
});
|
|
103
|
-
currentTranscript.push({
|
|
104
|
-
role: "tool",
|
|
105
|
-
text: `${envelope.tool}: ${toolText}`,
|
|
106
|
-
});
|
|
107
|
-
onToolEvent?.({ type: "tool-result", tool: envelope.tool, text: toolText });
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
emitted.push({
|
|
64
|
+
observer.scan();
|
|
65
|
+
commandLog?.refresh?.();
|
|
66
|
+
return [{
|
|
112
67
|
role: "assistant",
|
|
113
|
-
text:
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
emitted.push({
|
|
119
|
-
role: "assistant",
|
|
120
|
-
text: "I hit the assistant tool-call limit before reaching a final answer.",
|
|
121
|
-
});
|
|
122
|
-
return emitted;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function formatToolError(tool, error) {
|
|
126
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
-
if (tool === "shell_exec" && /command string/.test(message)) {
|
|
128
|
-
return "The assistant requested shell_exec without a command. Retry with arguments.command set to the exact shell command.";
|
|
68
|
+
text: result.finalText || "",
|
|
69
|
+
}];
|
|
70
|
+
} finally {
|
|
71
|
+
observer.stop();
|
|
129
72
|
}
|
|
130
|
-
return `Tool failed: ${message}`;
|
|
131
73
|
}
|
|
132
74
|
|
|
133
75
|
function formatProviderEvent(event) {
|
|
@@ -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: [],
|