@elench/testkit 0.1.88 → 0.1.90

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.
@@ -1,5 +1,6 @@
1
1
  import { loadCurrentRunArtifact, loadLatestRunArtifact } from "../viewer.mjs";
2
2
  import { createInspectState } from "../tui/inspect-state.mjs";
3
+ import { buildContextSelection } from "../context-resources.mjs";
3
4
  import { parseSlashCommand, formatSlashHelpLines } from "./slash-commands.mjs";
4
5
  import { executeAssistantTool } from "./tool-registry.mjs";
5
6
  import { runAssistantConversationTurn } from "./session.mjs";
@@ -7,12 +8,11 @@ import { runAssistantConversationTurn } from "./session.mjs";
7
8
  export function createAssistantState({
8
9
  productDir,
9
10
  provider = "auto",
10
- initialPane = "detail",
11
11
  dataSource = "artifact",
12
12
  configs = [],
13
+ env = process.env,
13
14
  } = {}) {
14
15
  const inspectState = createInspectState({ dataSource });
15
- inspectState.setPaneMode(initialPane);
16
16
 
17
17
  const listeners = new Set();
18
18
  const messages = [];
@@ -69,10 +69,6 @@ export function createAssistantState({
69
69
  return inspectState.revealService(serviceName);
70
70
  },
71
71
 
72
- setPaneMode(pane) {
73
- inspectState.setPaneMode(pane);
74
- },
75
-
76
72
  setComposer(value) {
77
73
  composer = String(value || "");
78
74
  notify();
@@ -165,6 +161,7 @@ export function createAssistantState({
165
161
  transcript: messages.map((entry) => ({ role: entry.role, text: entry.text })),
166
162
  userMessage: trimmed,
167
163
  provider: providerName,
164
+ env,
168
165
  configs,
169
166
  onStatus(status) {
170
167
  activeStatus = status;
@@ -189,7 +186,7 @@ export function createAssistantState({
189
186
 
190
187
  getSnapshot() {
191
188
  return {
192
- workbench: inspectState.getSnapshot(),
189
+ context: buildContextSelection(inspectState.getSnapshot()),
193
190
  messages: [...messages],
194
191
  composer,
195
192
  notice,
@@ -237,6 +234,7 @@ async function executeSlashCommand({
237
234
  appendMessage({
238
235
  role: "tool",
239
236
  toolName: slash.type,
237
+ title: result.title || slash.type,
240
238
  text: result.text || result.title || "Done.",
241
239
  data: result.data || null,
242
240
  });
@@ -244,16 +242,20 @@ async function executeSlashCommand({
244
242
 
245
243
  async function executeSlashTool(slash, context) {
246
244
  switch (slash.type) {
247
- case "pane":
248
- return executeAssistantTool("set_pane", { pane: slash.pane }, context);
249
245
  case "file":
250
- return executeAssistantTool("select_file", { file: slash.file }, context);
246
+ return executeAssistantTool("focus_file", { file: slash.file }, context);
251
247
  case "inspect":
252
248
  return slash.file
253
- ? executeAssistantTool("select_file", { file: slash.file }, context)
254
- : executeAssistantTool("inspect_selection", {}, context);
249
+ ? executeAssistantTool("focus_file", { file: slash.file }, context)
250
+ : executeAssistantTool("inspect_focus", {}, context);
251
+ case "logs":
252
+ return executeAssistantTool("read_logs", { service: slash.service || null }, context);
253
+ case "artifacts":
254
+ return executeAssistantTool("read_artifacts", { file: slash.file || null }, context);
255
+ case "setup":
256
+ return executeAssistantTool("read_setup", { service: slash.service || null }, context);
255
257
  case "service":
256
- return executeAssistantTool("select_service", { service: slash.service }, context);
258
+ return executeAssistantTool("focus_service", { service: slash.service }, context);
257
259
  case "status":
258
260
  return executeAssistantTool("show_status", {}, context);
259
261
  case "discover":
@@ -4,7 +4,10 @@ import { resolveProductDir } from "../../config/index.mjs";
4
4
  import { resolveRequestedFiles } from "../args.mjs";
5
5
  import { buildDiscoveryReportLines } from "../presentation/discovery-reporter.mjs";
6
6
  import { loadCurrentRunArtifact, loadLatestRunArtifact, resolveFileSubject } from "../viewer.mjs";
7
- import { buildInspectPaneContent } from "../tui/detail-pane.mjs";
7
+ import {
8
+ formatContextToolText,
9
+ readContextContent,
10
+ } from "../context-resources.mjs";
8
11
  import { createAssistantRunReporter } from "./tool-run-reporter.mjs";
9
12
  import { buildRunRequest } from "../command-helpers.mjs";
10
13
  import * as runner from "../../runner/index.mjs";
@@ -12,10 +15,12 @@ import * as runner from "../../runner/index.mjs";
12
15
  export function listAssistantTools() {
13
16
  return [
14
17
  { name: "run_tests", description: "Run testkit-managed tests with optional type/service/file filters." },
15
- { name: "select_file", description: "Focus a specific test file in the workbench." },
16
- { name: "select_service", description: "Focus a specific service in the workbench." },
17
- { name: "set_pane", description: "Switch the workbench pane: detail, artifacts, logs, or setup." },
18
- { name: "inspect_selection", description: "Render the current selection in the active pane." },
18
+ { name: "focus_file", description: "Focus a specific test file and summarize it." },
19
+ { name: "focus_service", description: "Focus a specific service and summarize it." },
20
+ { name: "inspect_focus", description: "Inspect the current focus inline in the conversation." },
21
+ { name: "read_logs", description: "Read backend logs for the current focus or an explicit service." },
22
+ { name: "read_artifacts", description: "Read persisted artifacts for the current focus or an explicit file." },
23
+ { name: "read_setup", description: "Read setup operations for the current focus or an explicit service." },
19
24
  { name: "discover_tests", description: "Discover managed tests and summarize them." },
20
25
  { name: "show_status", description: "Show the current local testkit state for the product." },
21
26
  { name: "run_doctor", description: "Run built-in testkit doctor checks." },
@@ -27,14 +32,18 @@ export async function executeAssistantTool(name, argumentsObject, context) {
27
32
  switch (name) {
28
33
  case "run_tests":
29
34
  return runTestsTool(args, context);
30
- case "select_file":
31
- return selectFileTool(args, context);
32
- case "select_service":
33
- return selectServiceTool(args, context);
34
- case "set_pane":
35
- return setPaneTool(args, context);
36
- case "inspect_selection":
37
- return inspectSelectionTool(args, context);
35
+ case "focus_file":
36
+ return focusFileTool(args, context);
37
+ case "focus_service":
38
+ return focusServiceTool(args, context);
39
+ case "inspect_focus":
40
+ return inspectFocusTool(args, context);
41
+ case "read_logs":
42
+ return readLogsTool(args, context);
43
+ case "read_artifacts":
44
+ return readArtifactsTool(args, context);
45
+ case "read_setup":
46
+ return readSetupTool(args, context);
38
47
  case "discover_tests":
39
48
  return discoverTestsTool(args, context);
40
49
  case "show_status":
@@ -101,50 +110,120 @@ async function runTestsTool(args, context) {
101
110
  }
102
111
  }
103
112
 
104
- function selectFileTool(args, context) {
113
+ function focusFileTool(args, context) {
105
114
  const file = args.file || args.path || null;
106
- if (!file) throw new Error("select_file requires a file argument");
115
+ if (!file) throw new Error("focus_file requires a file argument");
107
116
  ensureArtifactLoaded(context);
108
117
  const artifact = context.inspectState.getSnapshot().runArtifact;
109
118
  const subject = resolveFileSubject(artifact, file, args.service || null);
110
119
  context.inspectState.revealFile(subject.service.name, subject.file.path);
111
- return inspectSelectionTool({}, context);
120
+ return inspectFocusTool({}, context);
112
121
  }
113
122
 
114
- function selectServiceTool(args, context) {
123
+ function focusServiceTool(args, context) {
115
124
  const service = args.service || args.name || null;
116
- if (!service) throw new Error("select_service requires a service argument");
125
+ if (!service) throw new Error("focus_service requires a service argument");
117
126
  ensureArtifactLoaded(context);
118
127
  if (!context.inspectState.revealService(service)) {
119
128
  throw new Error(`Unknown service "${service}"`);
120
129
  }
121
- return inspectSelectionTool({}, context);
130
+ return inspectFocusTool({}, context);
122
131
  }
123
132
 
124
- function setPaneTool(args, context) {
125
- const pane = args.pane || "detail";
126
- context.inspectState.setPaneMode(pane);
127
- return inspectSelectionTool({}, context);
133
+ function inspectFocusTool(_args, context) {
134
+ const content = readContextContent({
135
+ productDir: context.productDir,
136
+ snapshot: context.inspectState.getSnapshot(),
137
+ mode: "detail",
138
+ logTail: 12,
139
+ });
140
+ return {
141
+ ok: true,
142
+ title: content.title,
143
+ text: formatContextToolText(content.title, content.lines),
144
+ data: {
145
+ title: content.title,
146
+ lines: content.lines,
147
+ selection: content.selection,
148
+ mode: "detail",
149
+ },
150
+ };
128
151
  }
129
152
 
130
- function inspectSelectionTool(_args, context) {
131
- ensureArtifactLoaded(context);
132
- const snapshot = context.inspectState.getSnapshot();
133
- const pane = buildInspectPaneContent({
153
+ function readLogsTool(args, context) {
154
+ if (args.service) {
155
+ ensureArtifactLoaded(context);
156
+ if (!context.inspectState.revealService(args.service)) {
157
+ throw new Error(`Unknown service "${args.service}"`);
158
+ }
159
+ }
160
+ const content = readContextContent({
161
+ productDir: context.productDir,
162
+ snapshot: context.inspectState.getSnapshot(),
163
+ mode: "logs",
164
+ logTail: args.logTail == null ? 12 : Number(args.logTail),
165
+ });
166
+ return {
167
+ ok: true,
168
+ title: content.title,
169
+ text: formatContextToolText(content.title, content.lines),
170
+ data: {
171
+ title: content.title,
172
+ lines: content.lines,
173
+ selection: content.selection,
174
+ mode: "logs",
175
+ },
176
+ };
177
+ }
178
+
179
+ function readArtifactsTool(args, context) {
180
+ if (args.file || args.path) {
181
+ ensureArtifactLoaded(context);
182
+ const artifact = context.inspectState.getSnapshot().runArtifact;
183
+ const subject = resolveFileSubject(artifact, args.file || args.path, args.service || null);
184
+ context.inspectState.revealFile(subject.service.name, subject.file.path);
185
+ }
186
+ const content = readContextContent({
134
187
  productDir: context.productDir,
135
- snapshot,
136
- paneMode: snapshot.paneMode,
188
+ snapshot: context.inspectState.getSnapshot(),
189
+ mode: "artifacts",
137
190
  logTail: 12,
138
191
  });
139
192
  return {
140
193
  ok: true,
141
- title: pane.title,
142
- text: pane.lines.join("\n"),
194
+ title: content.title,
195
+ text: formatContextToolText(content.title, content.lines),
143
196
  data: {
144
- title: pane.title,
145
- lines: pane.lines,
146
- selection: snapshot.selectedEntry,
147
- paneMode: snapshot.paneMode,
197
+ title: content.title,
198
+ lines: content.lines,
199
+ selection: content.selection,
200
+ mode: "artifacts",
201
+ },
202
+ };
203
+ }
204
+
205
+ function readSetupTool(args, context) {
206
+ if (args.service) {
207
+ ensureArtifactLoaded(context);
208
+ if (!context.inspectState.revealService(args.service)) {
209
+ throw new Error(`Unknown service "${args.service}"`);
210
+ }
211
+ }
212
+ const content = readContextContent({
213
+ productDir: context.productDir,
214
+ snapshot: context.inspectState.getSnapshot(),
215
+ mode: "setup",
216
+ logTail: 12,
217
+ });
218
+ return {
219
+ ok: true,
220
+ title: content.title,
221
+ text: formatContextToolText(content.title, content.lines),
222
+ data: {
223
+ title: content.title,
224
+ lines: content.lines,
225
+ selection: content.selection,
226
+ mode: "setup",
148
227
  },
149
228
  };
150
229
  }
@@ -176,7 +255,7 @@ function showStatusTool(_args, context) {
176
255
  return {
177
256
  ok: true,
178
257
  title: "Status",
179
- text: lines.join("\n"),
258
+ text: formatContextToolText("Status", lines),
180
259
  data: { lines },
181
260
  };
182
261
  }
@@ -193,7 +272,7 @@ async function runDoctorTool(args, context) {
193
272
  return {
194
273
  ok: result.ok,
195
274
  title: "Doctor",
196
- text: lines.join("\n"),
275
+ text: formatContextToolText("Doctor", lines),
197
276
  data: result,
198
277
  };
199
278
  }
@@ -1,12 +1,10 @@
1
- import React, { createElement } from "react";
2
1
  import { Command, Flags } from "@oclif/core";
3
- import { render } from "ink";
4
2
  import { sharedFlags, resolveConfigsForCommand } from "../command-helpers.mjs";
5
3
  import { createAssistantState } from "../assistant/state.mjs";
6
- import { loadLatestRunArtifact, resolveFileSubject } from "../viewer.mjs";
4
+ import { runInteractiveAssistant } from "../assistant/interactive.mjs";
7
5
 
8
6
  export default class AssistantCommand extends Command {
9
- static summary = "Launch the interactive testkit assistant";
7
+ static summary = "Launch a native Codex or Claude session with testkit context";
10
8
 
11
9
  static enableJsonFlag = true;
12
10
 
@@ -17,14 +15,12 @@ export default class AssistantCommand extends Command {
17
15
  options: ["auto", "claude", "codex"],
18
16
  default: "auto",
19
17
  }),
20
- pane: Flags.string({
21
- description: "Initial workbench pane",
22
- options: ["detail", "artifacts", "logs", "setup"],
23
- default: "detail",
24
- }),
25
18
  file: Flags.string({
26
19
  description: "Initial file selection",
27
20
  }),
21
+ prompt: Flags.string({
22
+ description: "Initial interactive prompt for the provider session",
23
+ }),
28
24
  message: Flags.string({
29
25
  description: "Run one assistant turn non-interactively",
30
26
  }),
@@ -32,30 +28,41 @@ export default class AssistantCommand extends Command {
32
28
 
33
29
  async run() {
34
30
  const { flags } = await this.parse(AssistantCommand);
31
+ if (flags.message && flags.prompt) {
32
+ this.error("Use either --message or --prompt, not both.");
33
+ }
35
34
  const { allConfigs } = await resolveConfigsForCommand(flags);
36
35
  const productDir = allConfigs[0]?.productDir || process.cwd();
37
- const assistantState = createAssistantState({
38
- productDir,
39
- provider: flags.provider,
40
- initialPane: flags.pane,
41
- configs: allConfigs,
42
- });
43
-
44
- await assistantState.loadLatestArtifact();
45
- if (flags.file) {
46
- try {
47
- const artifact = loadLatestRunArtifact(productDir);
48
- const subject = resolveFileSubject(artifact, flags.file, flags.service || null);
49
- assistantState.revealFile(subject.service.name, subject.file.path);
50
- } catch {
51
- // Ignore missing initial selection.
52
- }
53
- } else if (flags.service) {
54
- assistantState.revealService(flags.service);
36
+ const interactive =
37
+ (process.stdout.isTTY || process.env.TESTKIT_FORCE_INTERACTIVE_ASSISTANT === "1") &&
38
+ !this.jsonEnabled() &&
39
+ !flags.message;
40
+ if (flags.prompt && !interactive) {
41
+ this.error("--prompt requires an interactive assistant session.");
55
42
  }
56
-
57
- const interactive = process.stdout.isTTY && !this.jsonEnabled() && !flags.message;
58
43
  if (!interactive) {
44
+ if (!flags.message) {
45
+ this.error("assistant requires an interactive tty; use --message for one non-interactive turn.");
46
+ }
47
+ const assistantState = createAssistantState({
48
+ productDir,
49
+ provider: flags.provider,
50
+ configs: allConfigs,
51
+ env: process.env,
52
+ });
53
+ await assistantState.loadLatestArtifact();
54
+ if (flags.file) {
55
+ try {
56
+ const { loadLatestRunArtifact, resolveFileSubject } = await import("../viewer.mjs");
57
+ const artifact = loadLatestRunArtifact(productDir);
58
+ const subject = resolveFileSubject(artifact, flags.file, flags.service || null);
59
+ assistantState.revealFile(subject.service.name, subject.file.path);
60
+ } catch {
61
+ // Ignore missing initial selection.
62
+ }
63
+ } else if (flags.service) {
64
+ assistantState.revealService(flags.service);
65
+ }
59
66
  if (flags.message) {
60
67
  await assistantState.submitInput(flags.message);
61
68
  }
@@ -68,17 +75,13 @@ export default class AssistantCommand extends Command {
68
75
  return snapshot;
69
76
  }
70
77
 
71
- const { AssistantApp } = await import("../tui/assistant-app.mjs");
72
- const app = render(
73
- createElement(AssistantApp, {
74
- assistantState,
75
- stdout: process.stdout,
76
- productDir,
77
- }),
78
- { stdout: process.stdout, exitOnCtrlC: false }
79
- );
80
-
81
- await app.waitUntilExit();
82
- return assistantState.getSnapshot();
78
+ return runInteractiveAssistant({
79
+ productDir,
80
+ provider: flags.provider,
81
+ file: flags.file || null,
82
+ service: flags.service || null,
83
+ prompt: flags.prompt || null,
84
+ env: process.env,
85
+ });
83
86
  }
84
87
  }
@@ -7,32 +7,87 @@ import {
7
7
  getSetupOperationsForService,
8
8
  loadCurrentRunArtifact,
9
9
  resolveFileSubject,
10
- } from "../viewer.mjs";
11
- import { formatDuration } from "../../runner/formatting.mjs";
12
- import { readLogTail } from "../../runner/logs.mjs";
10
+ } from "./viewer.mjs";
11
+ import { formatDuration } from "../runner/formatting.mjs";
12
+ import { readLogTail } from "../runner/logs.mjs";
13
13
 
14
- export function buildInspectPaneContent({ productDir, snapshot, paneMode = "detail", logTail = 12 } = {}) {
15
- const selectedEntry = snapshot.selectedEntry || null;
14
+ export function readContextContent({
15
+ productDir,
16
+ snapshot,
17
+ mode = "detail",
18
+ logTail = 12,
19
+ previewLength = 6,
20
+ } = {}) {
21
+ const normalizedMode = normalizeMode(mode);
22
+ const selectedEntry = snapshot?.selectedEntry || null;
23
+ const summaryRows = snapshot?.summaryData?.rows || [];
16
24
  if (!selectedEntry) {
17
- return { title: "Selection", lines: ["No entry selected."], data: null };
25
+ return {
26
+ mode: normalizedMode,
27
+ title: "Selection",
28
+ lines: ["No entry selected."],
29
+ data: null,
30
+ selection: null,
31
+ summaryRows,
32
+ };
18
33
  }
19
34
 
20
35
  const runArtifact = resolveArtifact(productDir, snapshot);
21
36
  const subject = resolveSubject(runArtifact, selectedEntry);
22
37
 
23
- if (paneMode === "artifacts") {
24
- return buildArtifactsPane(productDir, runArtifact, selectedEntry, subject);
25
- }
26
- if (paneMode === "logs") {
27
- return buildLogsPane(productDir, runArtifact, selectedEntry, logTail);
28
- }
29
- if (paneMode === "setup") {
30
- return buildSetupPane(productDir, runArtifact, selectedEntry);
38
+ let content = null;
39
+ if (normalizedMode === "artifacts") {
40
+ content = buildArtifactsContent(productDir, runArtifact, selectedEntry, subject, previewLength);
41
+ } else if (normalizedMode === "logs") {
42
+ content = buildLogsContent(productDir, runArtifact, selectedEntry, logTail);
43
+ } else if (normalizedMode === "setup") {
44
+ content = buildSetupContent(runArtifact, selectedEntry);
45
+ } else {
46
+ content = buildDetailContent(productDir, runArtifact, selectedEntry, subject, logTail);
31
47
  }
32
- return buildDetailPane(productDir, runArtifact, selectedEntry, subject, logTail);
48
+
49
+ return {
50
+ mode: normalizedMode,
51
+ title: content.title,
52
+ lines: content.lines || [],
53
+ data: content.data ?? null,
54
+ selection: selectedEntry,
55
+ summaryRows,
56
+ };
33
57
  }
34
58
 
35
- function buildDetailPane(productDir, runArtifact, entry, subject, logTail) {
59
+ export function buildContextSelection(snapshot) {
60
+ const selectedEntry = snapshot?.selectedEntry || null;
61
+ const summaryRows = snapshot?.summaryData?.rows || [];
62
+ return {
63
+ selection: selectedEntry
64
+ ? {
65
+ kind: selectedEntry.kind,
66
+ label: selectedEntry.label || selectedEntry.filePath || selectedEntry.serviceName || "selection",
67
+ serviceName: selectedEntry.serviceName || null,
68
+ type: selectedEntry.type || null,
69
+ suiteName: selectedEntry.suiteName || null,
70
+ filePath: selectedEntry.filePath || null,
71
+ status: selectedEntry.status || null,
72
+ }
73
+ : null,
74
+ summaryRows,
75
+ phase: snapshot?.phase || null,
76
+ hasArtifact: Boolean(snapshot?.runArtifact),
77
+ };
78
+ }
79
+
80
+ export function formatContextToolText(title, lines) {
81
+ const normalizedTitle = String(title || "").trim();
82
+ const normalizedLines = Array.isArray(lines) ? lines.filter((line) => String(line).length > 0) : [];
83
+ if (normalizedTitle && normalizedLines.length > 0) {
84
+ return [normalizedTitle, ...normalizedLines].join("\n");
85
+ }
86
+ if (normalizedTitle) return normalizedTitle;
87
+ return normalizedLines.join("\n");
88
+ }
89
+
90
+ function buildDetailContent(productDir, runArtifact, entry, subject, logTail) {
36
91
  if (subject && runArtifact) {
37
92
  return {
38
93
  title: "Detail",
@@ -48,7 +103,7 @@ function buildDetailPane(productDir, runArtifact, entry, subject, logTail) {
48
103
  };
49
104
  }
50
105
 
51
- function buildArtifactsPane(productDir, runArtifact, entry, subject) {
106
+ function buildArtifactsContent(productDir, runArtifact, entry, subject, previewLength) {
52
107
  if (!runArtifact || !subject) {
53
108
  return {
54
109
  title: "Artifacts",
@@ -65,7 +120,7 @@ function buildArtifactsPane(productDir, runArtifact, entry, subject) {
65
120
  kind: item.artifactRef.kind,
66
121
  summary: item.artifactRef.summary,
67
122
  path: item.artifactRef.path,
68
- preview: formatArtifactPreview(item.payload, 6),
123
+ preview: formatArtifactPreview(item.payload, previewLength),
69
124
  }));
70
125
 
71
126
  if (entries.length === 0) {
@@ -82,7 +137,7 @@ function buildArtifactsPane(productDir, runArtifact, entry, subject) {
82
137
  return { title: "Artifacts", lines, data: entries };
83
138
  }
84
139
 
85
- function buildLogsPane(productDir, runArtifact, entry, tail) {
140
+ function buildLogsContent(productDir, runArtifact, entry, tail) {
86
141
  if (!runArtifact) {
87
142
  return { title: "Logs", lines: ["Backend logs are available only from persisted run artifacts."], data: [] };
88
143
  }
@@ -103,7 +158,7 @@ function buildLogsPane(productDir, runArtifact, entry, tail) {
103
158
  return { title: "Logs", lines, data: logs };
104
159
  }
105
160
 
106
- function buildSetupPane(productDir, runArtifact, entry) {
161
+ function buildSetupContent(runArtifact, entry) {
107
162
  if (!runArtifact) {
108
163
  return { title: "Setup", lines: ["Setup operations are available only from persisted run artifacts."], data: [] };
109
164
  }
@@ -123,7 +178,7 @@ function buildSetupPane(productDir, runArtifact, entry) {
123
178
  }
124
179
 
125
180
  function resolveArtifact(productDir, snapshot) {
126
- if (snapshot.runArtifact) return snapshot.runArtifact;
181
+ if (snapshot?.runArtifact) return snapshot.runArtifact;
127
182
  try {
128
183
  return loadCurrentRunArtifact(productDir);
129
184
  } catch {
@@ -159,3 +214,8 @@ function formatAggregateDetail(entry) {
159
214
  if (entry.error) lines.push(`Error: ${entry.error}`);
160
215
  return lines;
161
216
  }
217
+
218
+ function normalizeMode(mode) {
219
+ if (mode === "logs" || mode === "artifacts" || mode === "setup") return mode;
220
+ return "detail";
221
+ }
@@ -36,11 +36,13 @@ export function normalizeCliArgs(argv) {
36
36
  "--log-tail",
37
37
  "--provider",
38
38
  "--message",
39
- "--pane",
39
+ "--prompt",
40
40
  ]);
41
41
  const positionals = findPositionals(argv, valueFlags);
42
42
  const firstPositional = positionals[0] || null;
43
- const interactiveTty = process.stdout.isTTY;
43
+ const forcedInteractiveAssistant = process.env.TESTKIT_FORCE_INTERACTIVE_ASSISTANT === "1";
44
+ const interactiveTty = process.stdout.isTTY || forcedInteractiveAssistant;
45
+ const assistantDefaultDisabled = process.env.TESTKIT_NO_ASSISTANT_DEFAULT === "1";
44
46
  const runFlagPresent = argv.some((value) =>
45
47
  [
46
48
  "--type",
@@ -66,7 +68,7 @@ export function normalizeCliArgs(argv) {
66
68
  runFlagPresent ||
67
69
  !topLevelCommands.has(firstPositional?.value);
68
70
 
69
- if (!firstPositional && interactiveTty && !runFlagPresent) {
71
+ if (!firstPositional && interactiveTty && !runFlagPresent && !assistantDefaultDisabled) {
70
72
  return ["assistant", ...argv];
71
73
  }
72
74
 
@@ -74,8 +76,13 @@ export function normalizeCliArgs(argv) {
74
76
  return reorderCommandArgs(argv, positionals);
75
77
  }
76
78
 
77
- if (!topLevelCommands.has(firstPositional?.value) && interactiveTty && !runFlagPresent) {
78
- return ["assistant", "--message", argv.join(" ")];
79
+ if (!topLevelCommands.has(firstPositional?.value) && interactiveTty && !assistantDefaultDisabled) {
80
+ const shouldOpenAssistantPrompt =
81
+ forcedInteractiveAssistant ||
82
+ (!runFlagPresent && !runTypeShortcuts.has(firstPositional?.value));
83
+ if (shouldOpenAssistantPrompt) {
84
+ return ["assistant", "--prompt", argv.join(" ")];
85
+ }
79
86
  }
80
87
 
81
88
  if (shouldPrefixRun) {