@elench/testkit 0.1.82 → 0.1.84

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.
Files changed (100) hide show
  1. package/README.md +37 -7
  2. package/lib/cli/agents/index.mjs +64 -0
  3. package/lib/cli/agents/investigate.mjs +75 -0
  4. package/lib/cli/agents/investigation-context.mjs +102 -0
  5. package/lib/cli/agents/prompt-builder.mjs +25 -0
  6. package/lib/cli/agents/providers/claude.mjs +74 -0
  7. package/lib/cli/agents/providers/codex.mjs +83 -0
  8. package/lib/cli/agents/providers/shared.mjs +134 -0
  9. package/lib/cli/command-helpers.mjs +53 -25
  10. package/lib/cli/commands/investigate.mjs +87 -0
  11. package/lib/cli/entrypoint.mjs +3 -0
  12. package/lib/cli/presentation/colors.mjs +12 -0
  13. package/lib/cli/presentation/events-reporter.mjs +135 -0
  14. package/lib/cli/presentation/summary-box.mjs +11 -11
  15. package/lib/cli/presentation/tree-reporter.mjs +159 -0
  16. package/lib/cli/tui/run-app.mjs +1 -0
  17. package/lib/cli/tui/run-session-app.mjs +370 -0
  18. package/lib/cli/tui/run-session-state.mjs +481 -0
  19. package/lib/cli/tui/run-tree-state.mjs +1 -0
  20. package/lib/config-api/auth-fixtures.mjs +15 -10
  21. package/lib/discovery/index.mjs +1 -1
  22. package/lib/index.d.ts +5 -1
  23. package/lib/runner/orchestrator.mjs +1 -0
  24. package/lib/runtime/index.d.ts +138 -5
  25. package/lib/runtime/index.mjs +68 -2
  26. package/lib/runtime-src/k6/http-assertions.js +31 -1
  27. package/lib/runtime-src/k6/http-checks.js +120 -0
  28. package/lib/runtime-src/k6/http-suite-runtime.js +5 -1
  29. package/lib/runtime-src/k6/http.js +213 -23
  30. package/lib/runtime-src/shared/error-body.mjs +42 -0
  31. package/lib/runtime-src/shared/http-parsing.mjs +68 -0
  32. package/node_modules/@elench/next-analysis/package.json +1 -1
  33. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  34. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  35. package/node_modules/@elench/ts-analysis/package.json +1 -1
  36. package/package.json +7 -6
  37. package/lib/app/configs.test.mjs +0 -34
  38. package/lib/app/typecheck.test.mjs +0 -24
  39. package/lib/bundler/index.test.mjs +0 -164
  40. package/lib/cli/args.test.mjs +0 -110
  41. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  42. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  43. package/lib/cli/presentation/summary-box.test.mjs +0 -43
  44. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  45. package/lib/config/database.test.mjs +0 -29
  46. package/lib/config/discovery.test.mjs +0 -276
  47. package/lib/config/env.test.mjs +0 -40
  48. package/lib/config/index.test.mjs +0 -44
  49. package/lib/config/paths.test.mjs +0 -27
  50. package/lib/config/runtime.test.mjs +0 -82
  51. package/lib/config/skip-config.test.mjs +0 -63
  52. package/lib/config-api/index.test.mjs +0 -344
  53. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  54. package/lib/coverage/backend-discovery.test.mjs +0 -61
  55. package/lib/coverage/evidence.test.mjs +0 -87
  56. package/lib/coverage/index.test.mjs +0 -715
  57. package/lib/coverage/routing.test.mjs +0 -36
  58. package/lib/coverage/shared.test.mjs +0 -72
  59. package/lib/database/fingerprint.test.mjs +0 -99
  60. package/lib/database/index.test.mjs +0 -95
  61. package/lib/database/naming.test.mjs +0 -39
  62. package/lib/database/state.test.mjs +0 -66
  63. package/lib/database/template-steps.test.mjs +0 -43
  64. package/lib/discovery/file-metadata.test.mjs +0 -51
  65. package/lib/discovery/index.test.mjs +0 -182
  66. package/lib/discovery/path-policy.test.mjs +0 -65
  67. package/lib/drizzle/index.test.mjs +0 -33
  68. package/lib/env/index.test.mjs +0 -82
  69. package/lib/history/index.test.mjs +0 -115
  70. package/lib/package.test.mjs +0 -59
  71. package/lib/playwright/index.test.mjs +0 -43
  72. package/lib/regressions/github.test.mjs +0 -324
  73. package/lib/regressions/index.test.mjs +0 -187
  74. package/lib/reporters/playwright.test.mjs +0 -167
  75. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  76. package/lib/runner/execution-config.test.mjs +0 -67
  77. package/lib/runner/failure-details.test.mjs +0 -114
  78. package/lib/runner/formatting.test.mjs +0 -205
  79. package/lib/runner/metadata.test.mjs +0 -52
  80. package/lib/runner/planning.test.mjs +0 -371
  81. package/lib/runner/playwright-config.test.mjs +0 -78
  82. package/lib/runner/processes.test.mjs +0 -21
  83. package/lib/runner/regressions.test.mjs +0 -168
  84. package/lib/runner/reporting.test.mjs +0 -310
  85. package/lib/runner/results.test.mjs +0 -376
  86. package/lib/runner/runtime-manager.test.mjs +0 -252
  87. package/lib/runner/runtime-preparation.test.mjs +0 -141
  88. package/lib/runner/selection.test.mjs +0 -24
  89. package/lib/runner/setup-operations.test.mjs +0 -94
  90. package/lib/runner/state.test.mjs +0 -62
  91. package/lib/runner/suite-selection.test.mjs +0 -49
  92. package/lib/runner/template.test.mjs +0 -272
  93. package/lib/shared/build-config.test.mjs +0 -132
  94. package/lib/shared/configured-steps.test.mjs +0 -102
  95. package/lib/shared/execution-schema.test.mjs +0 -26
  96. package/lib/shared/file-timeout.test.mjs +0 -64
  97. package/lib/shared/test-context.test.mjs +0 -43
  98. package/lib/timing/index.test.mjs +0 -64
  99. package/lib/toolchains/index.test.mjs +0 -168
  100. package/lib/vitest/index.test.mjs +0 -20
@@ -11,6 +11,8 @@ import {
11
11
  } from "./args.mjs";
12
12
  import * as runner from "../runner/index.mjs";
13
13
  import { createRunReporter } from "./presentation/run-reporter.mjs";
14
+ import { createTreeReporter } from "./presentation/tree-reporter.mjs";
15
+ import { createRunEventsReporter } from "./presentation/events-reporter.mjs";
14
16
 
15
17
  export const sharedFlags = {
16
18
  dir: Flags.string({
@@ -64,7 +66,7 @@ export const runFlags = {
64
66
  }),
65
67
  "output-mode": Flags.string({
66
68
  description: "Reporter mode",
67
- options: ["compact", "debug"],
69
+ options: ["compact", "debug", "events"],
68
70
  }),
69
71
  debug: Flags.boolean({
70
72
  description: "Alias for --output-mode debug",
@@ -94,31 +96,57 @@ export async function executeRunCommand(command, flags, positionalType = null) {
94
96
  : flags.debug
95
97
  ? "debug"
96
98
  : flags["output-mode"] || "compact";
97
- const reporter = createRunReporter({ outputMode });
98
- const result = await runner.runAll(
99
- configs,
100
- typeValues,
101
- suiteSelectors,
102
- {
103
- ...flags,
99
+
100
+ let reporter;
101
+ let finalize = Promise.resolve();
102
+ let close = () => {};
103
+
104
+ if (outputMode === "compact" && process.stdout.isTTY) {
105
+ const tree = createTreeReporter({
106
+ stdout: process.stdout,
107
+ stderr: process.stderr,
108
+ productDir,
109
+ });
110
+ reporter = tree.reporter;
111
+ finalize = tree.finalize;
112
+ close = tree.close;
113
+ } else if (outputMode === "events") {
114
+ reporter = createRunEventsReporter({ stdout: process.stdout, stderr: process.stderr });
115
+ } else {
116
+ reporter = createRunReporter({ outputMode });
117
+ }
118
+
119
+ try {
120
+ const result = await runner.runAll(
121
+ configs,
104
122
  typeValues,
105
- fileNames,
106
- workers,
107
- fileTimeoutSeconds,
108
- shard,
109
- scenarioSeed: flags.seed || null,
110
- serviceFilter: flags.service || null,
111
- reporter,
112
- writeStatus: flags["write-status"],
113
- allowPartialStatus: flags["allow-partial-status"],
114
- ignoreSkipRules: flags["ignore-skip-rules"],
115
- },
116
- allConfigs
117
- );
118
- return {
119
- outputMode,
120
- ...result,
121
- };
123
+ suiteSelectors,
124
+ {
125
+ ...flags,
126
+ typeValues,
127
+ fileNames,
128
+ workers,
129
+ fileTimeoutSeconds,
130
+ shard,
131
+ scenarioSeed: flags.seed || null,
132
+ serviceFilter: flags.service || null,
133
+ reporter,
134
+ writeStatus: flags["write-status"],
135
+ allowPartialStatus: flags["allow-partial-status"],
136
+ ignoreSkipRules: flags["ignore-skip-rules"],
137
+ },
138
+ allConfigs
139
+ );
140
+ await finalize;
141
+ return {
142
+ outputMode,
143
+ ...result,
144
+ };
145
+ } catch (error) {
146
+ close();
147
+ await finalize.catch(() => {});
148
+ throw error;
149
+ }
122
150
  }
123
151
 
124
152
  export async function runStatusLike(commandName, flags) {
@@ -0,0 +1,87 @@
1
+ import { Args, Command, Flags } from "@oclif/core";
2
+ import { sharedFlags } from "../command-helpers.mjs";
3
+ import { loadCurrentRunArtifact, resolveFileSubject } from "../viewer.mjs";
4
+ import { runInteractiveInvestigation, startHostedInvestigation } from "../agents/investigate.mjs";
5
+
6
+ export default class InvestigateCommand extends Command {
7
+ static summary = "Investigate a failed file from the latest run with Codex or Claude";
8
+
9
+ static enableJsonFlag = true;
10
+
11
+ static args = {
12
+ file: Args.string({
13
+ description: "Optional file path; defaults to the first failed file",
14
+ required: false,
15
+ }),
16
+ };
17
+
18
+ static flags = {
19
+ ...sharedFlags,
20
+ provider: Flags.string({
21
+ description: "Agent provider to use",
22
+ options: ["auto", "claude", "codex"],
23
+ default: "auto",
24
+ }),
25
+ message: Flags.string({
26
+ description: "Additional user instruction for the investigation prompt",
27
+ }),
28
+ handoff: Flags.boolean({
29
+ description: "Launch the provider's native interactive TUI instead of hosted output",
30
+ default: false,
31
+ }),
32
+ };
33
+
34
+ async run() {
35
+ const { args, flags } = await this.parse(InvestigateCommand);
36
+ const productDir = flags.dir || process.cwd();
37
+ const runArtifact = loadCurrentRunArtifact(productDir);
38
+ const subject = resolveFileSubject(runArtifact, args.file || null, flags.service || null);
39
+
40
+ if (flags.handoff) {
41
+ const result = await runInteractiveInvestigation({
42
+ productDir,
43
+ serviceName: subject.service.name,
44
+ filePath: subject.file.path,
45
+ provider: flags.provider,
46
+ userMessage: flags.message || null,
47
+ });
48
+ if (!this.jsonEnabled()) {
49
+ this.log(`${result.provider} exited with code ${result.exitCode}`);
50
+ }
51
+ return result;
52
+ }
53
+
54
+ let finalText = "";
55
+ const session = startHostedInvestigation({
56
+ productDir,
57
+ serviceName: subject.service.name,
58
+ filePath: subject.file.path,
59
+ provider: flags.provider,
60
+ userMessage: flags.message || null,
61
+ onEvent: this.jsonEnabled()
62
+ ? null
63
+ : (event) => {
64
+ if (event.type === "status" || event.type === "tool") {
65
+ this.log(event.type === "tool" ? `[tool] ${event.name}${event.detail ? `: ${event.detail}` : ""}` : `[status] ${event.message}`);
66
+ } else if (event.type === "error") {
67
+ this.error(event.message);
68
+ }
69
+ },
70
+ });
71
+ const result = await session.completion;
72
+ finalText = result.finalText || "";
73
+
74
+ if (!this.jsonEnabled() && finalText.trim()) {
75
+ this.log("");
76
+ this.log(finalText.trim());
77
+ }
78
+
79
+ return {
80
+ provider: result.provider,
81
+ exitCode: result.exitCode,
82
+ file: subject.file.path,
83
+ service: subject.service.name,
84
+ finalText,
85
+ };
86
+ }
87
+ }
@@ -11,6 +11,7 @@ export function normalizeCliArgs(argv) {
11
11
  "discover",
12
12
  "typecheck",
13
13
  "doctor",
14
+ "investigate",
14
15
  "browser",
15
16
  "db",
16
17
  "help",
@@ -37,6 +38,8 @@ export function normalizeCliArgs(argv) {
37
38
  "--output-mode",
38
39
  "--tail",
39
40
  "--log-tail",
41
+ "--provider",
42
+ "--message",
40
43
  ]);
41
44
  const positionals = findPositionals(argv, valueFlags);
42
45
  const firstPositional = positionals[0] || null;
@@ -35,6 +35,18 @@ export function red(text) {
35
35
  return pc.red(text);
36
36
  }
37
37
 
38
+ export function green(text) {
39
+ return pc.green(text);
40
+ }
41
+
42
+ export function yellow(text) {
43
+ return pc.yellow(text);
44
+ }
45
+
46
+ export function cyan(text) {
47
+ return pc.cyan(text);
48
+ }
49
+
38
50
  export function muted(text) {
39
51
  return pc.dim(text);
40
52
  }
@@ -0,0 +1,135 @@
1
+ export function createRunEventsReporter({ stdout = process.stdout, stderr = process.stderr } = {}) {
2
+ function writeEvent(type, payload = {}) {
3
+ stdout.write(`${JSON.stringify({ type, ...payload })}\n`);
4
+ }
5
+
6
+ return {
7
+ outputMode: "events",
8
+
9
+ setRegressionCatalog(document) {
10
+ writeEvent("run.regression_catalog", {
11
+ configured: Boolean(document?.configured),
12
+ });
13
+ },
14
+
15
+ setServicePlans(plans) {
16
+ writeEvent("run.service_plans", {
17
+ services: plans.map((plan) => ({
18
+ service: plan.config.name,
19
+ skipped: Boolean(plan.skipped),
20
+ suites: (plan.suites || []).map((suite) => ({
21
+ name: suite.name,
22
+ type: suite.type,
23
+ displayType: suite.displayType,
24
+ framework: suite.framework,
25
+ files: suite.files,
26
+ })),
27
+ })),
28
+ });
29
+ },
30
+
31
+ setTotalFileCount(count) {
32
+ writeEvent("run.total_files", { total: count });
33
+ },
34
+
35
+ phaseStarted(label) {
36
+ writeEvent("run.phase_started", { label });
37
+ },
38
+
39
+ toolchainResolved(config, resolvedToolchain) {
40
+ writeEvent("run.toolchain_resolved", {
41
+ service: config.name,
42
+ runtimeLabel: config.runtimeLabel || config.name,
43
+ summary: resolvedToolchain.summary,
44
+ });
45
+ },
46
+
47
+ setupOperationFinished(operation) {
48
+ if (!operation) return;
49
+ writeEvent("run.setup_finished", {
50
+ service: operation.serviceName,
51
+ stage: operation.stage,
52
+ status: operation.status,
53
+ summary: operation.summary || null,
54
+ durationMs: operation.durationMs ?? null,
55
+ error: operation.error || null,
56
+ });
57
+ },
58
+
59
+ localServiceStarting(config, command) {
60
+ writeEvent("run.local_service_starting", {
61
+ service: config.name,
62
+ runtimeLabel: config.runtimeLabel || config.name,
63
+ command,
64
+ });
65
+ },
66
+
67
+ serviceSkipped(config, reason) {
68
+ writeEvent("run.service_skipped", {
69
+ service: config.name,
70
+ reason,
71
+ });
72
+ },
73
+
74
+ plannedSkip(entry) {
75
+ writeEvent("run.task_planned_skip", {
76
+ service: entry.serviceName,
77
+ file: entry.file,
78
+ reason: entry.reason || null,
79
+ });
80
+ },
81
+
82
+ taskStarted(task) {
83
+ writeEvent("run.task_started", {
84
+ service: task.serviceName,
85
+ file: task.file,
86
+ suite: task.suiteName,
87
+ type: task.type,
88
+ framework: task.framework,
89
+ });
90
+ },
91
+
92
+ taskFinished(task, outcome) {
93
+ writeEvent("run.task_finished", {
94
+ service: task.serviceName,
95
+ file: task.file,
96
+ suite: task.suiteName,
97
+ type: task.type,
98
+ framework: task.framework,
99
+ outcome,
100
+ });
101
+ },
102
+
103
+ runtimeError(task, message) {
104
+ writeEvent("run.runtime_error", {
105
+ service: task.serviceName,
106
+ file: task.file,
107
+ message,
108
+ });
109
+ },
110
+
111
+ telemetry(message) {
112
+ writeEvent("run.telemetry", { message });
113
+ },
114
+
115
+ writeLine(line = "") {
116
+ writeEvent("run.output", { line });
117
+ },
118
+
119
+ writeDebugLine(line = "") {
120
+ writeEvent("run.debug", { line });
121
+ },
122
+
123
+ runSummary(results, durationMs, regressionReport) {
124
+ writeEvent("run.summary", {
125
+ results,
126
+ durationMs,
127
+ regressionReport,
128
+ });
129
+ },
130
+
131
+ error(message) {
132
+ stderr.write(`${message}\n`);
133
+ },
134
+ };
135
+ }
@@ -8,8 +8,8 @@ export function renderSummaryBox(
8
8
  widthRatio = 0.55,
9
9
  minWidth = 30,
10
10
  maxWidth = 56,
11
+ minKeyWidth = 6,
11
12
  minValueWidth = 8,
12
- maxKeyWidth = 12,
13
13
  } = {}
14
14
  ) {
15
15
  if (!Array.isArray(rows) || rows.length === 0) return [];
@@ -17,23 +17,23 @@ export function renderSummaryBox(
17
17
  const terminalWidth = getTerminalWidth(stdout, 100);
18
18
  const preferredWidth = clamp(Math.floor(terminalWidth * widthRatio), minWidth, maxWidth);
19
19
  const maxRenderableWidth = Math.max(minWidth, Math.min(preferredWidth, terminalWidth - 1));
20
- const keyWidth = Math.min(
21
- maxKeyWidth,
22
- Math.max(...rows.map(([label]) => measureWidth(label)), 6)
23
- );
24
- const minBoxWidth = keyWidth + minValueWidth + 7;
25
- const boxWidth = Math.max(minBoxWidth, maxRenderableWidth);
26
- const valueWidth = Math.max(minValueWidth, boxWidth - keyWidth - 7);
20
+ const widestLabel = Math.max(...rows.map(([label]) => measureWidth(label)), minKeyWidth);
21
+ const boxWidth = Math.max(minWidth, maxRenderableWidth);
22
+ const contentWidth = Math.max(2, boxWidth - 7);
23
+ const keyWidth = Math.min(widestLabel, Math.max(minKeyWidth, contentWidth - minValueWidth));
24
+ const valueWidth = Math.max(1, contentWidth - keyWidth);
27
25
 
28
26
  const top = `${figures.lineDownRight}${figures.line.repeat(boxWidth - 2)}${figures.lineDownLeft}`;
29
27
  const bottom = `${figures.lineUpRight}${figures.line.repeat(boxWidth - 2)}${figures.lineUpLeft}`;
30
28
  const rendered = [top];
31
29
 
32
30
  for (const [label, value] of rows) {
31
+ const wrappedKeyLines = wrapText(label, keyWidth);
33
32
  const wrappedValueLines = wrapText(value, valueWidth);
34
- for (let index = 0; index < wrappedValueLines.length; index += 1) {
35
- const keyCell = index === 0 ? padEndVisible(label, keyWidth) : " ".repeat(keyWidth);
36
- const valueCell = padEndVisible(wrappedValueLines[index], valueWidth);
33
+ const lineCount = Math.max(wrappedKeyLines.length, wrappedValueLines.length);
34
+ for (let index = 0; index < lineCount; index += 1) {
35
+ const keyCell = padEndVisible(wrappedKeyLines[index] ?? "", keyWidth);
36
+ const valueCell = padEndVisible(wrappedValueLines[index] ?? "", valueWidth);
37
37
  rendered.push(
38
38
  `${figures.lineVertical} ${keyCell} ${figures.lineVertical} ${valueCell} ${figures.lineVertical}`
39
39
  );
@@ -0,0 +1,159 @@
1
+ import React, { createElement } from "react";
2
+ import { render } from "ink";
3
+ import { createRunSessionState } from "../tui/run-session-state.mjs";
4
+ import { RunSessionApp } from "../tui/run-session-app.mjs";
5
+ import { suiteSelectionType } from "../../runner/suite-selection.mjs";
6
+ import { startHostedInvestigation } from "../agents/investigate.mjs";
7
+
8
+ export function createTreeReporter({ stdout = process.stdout, stderr = process.stderr, productDir } = {}) {
9
+ const sessionState = createRunSessionState();
10
+ let activeAgentSession = null;
11
+ let investigationToken = 0;
12
+
13
+ const app = render(
14
+ createElement(RunSessionApp, {
15
+ sessionState,
16
+ stdout,
17
+ productDir,
18
+ onInvestigate: startInvestigation,
19
+ onRequestClose: close,
20
+ onCancelInvestigation: cancelInvestigation,
21
+ }),
22
+ { stdout, exitOnCtrlC: false }
23
+ );
24
+
25
+ const finalize = app.waitUntilExit();
26
+
27
+ const reporter = {
28
+ outputMode: "compact",
29
+
30
+ setServicePlans(plans) {
31
+ sessionState.initFromPlans(plans);
32
+ },
33
+
34
+ setTotalFileCount(count) {
35
+ sessionState.setTotalFileCount(count);
36
+ },
37
+
38
+ setRegressionCatalog(document) {
39
+ sessionState.setRegressionCatalog(document);
40
+ },
41
+
42
+ serviceSkipped(config, reason) {
43
+ sessionState.markServiceSkipped(config.name, reason);
44
+ },
45
+
46
+ plannedSkip(entry) {
47
+ sessionState.markPlannedSkip(entry);
48
+ },
49
+
50
+ taskStarted(task, _config) {
51
+ const suiteKey = `${task.displayType || suiteSelectionType(task.type, task.framework)}:${task.suiteName}`;
52
+ sessionState.markFileRunning(task.serviceName, suiteKey, task.file);
53
+ },
54
+
55
+ taskFinished(task, outcome) {
56
+ sessionState.markFileFinished(task, outcome);
57
+ },
58
+
59
+ runtimeError(task, message) {
60
+ sessionState.markRuntimeError(task, message);
61
+ },
62
+
63
+ setupOperationFinished(_operation) {
64
+ // tree handles this implicitly through phase
65
+ },
66
+
67
+ phaseStarted(label) {
68
+ sessionState.setPhase(label);
69
+ },
70
+
71
+ toolchainResolved() {},
72
+ localServiceStarting() {},
73
+ writeLine() {},
74
+ writeDebugLine() {},
75
+ telemetry() {},
76
+
77
+ runSummary(results, durationMs, regressionReport) {
78
+ sessionState.finish(results, durationMs, regressionReport);
79
+ },
80
+
81
+ error(message) {
82
+ stderr.write(`${message}\n`);
83
+ },
84
+ };
85
+
86
+ return {
87
+ reporter,
88
+ finalize,
89
+ close,
90
+ };
91
+
92
+ async function startInvestigation({ provider = "auto", userMessage } = {}) {
93
+ const snapshot = sessionState.getSnapshot();
94
+ if (!snapshot.selectedFailure) {
95
+ sessionState.setNotice("No failed file is selected for investigation.");
96
+ return;
97
+ }
98
+ if (activeAgentSession) {
99
+ sessionState.setNotice("An investigation is already running.");
100
+ return;
101
+ }
102
+
103
+ const token = ++investigationToken;
104
+ sessionState.beginInvestigation({ provider, userMessage });
105
+
106
+ try {
107
+ activeAgentSession = startHostedInvestigation({
108
+ productDir,
109
+ serviceName: snapshot.selectedFailure.serviceName,
110
+ filePath: snapshot.selectedFailure.filePath,
111
+ provider,
112
+ userMessage,
113
+ onEvent(event) {
114
+ if (token !== investigationToken) return;
115
+ sessionState.appendAgentEvent(event);
116
+ },
117
+ });
118
+ const result = await activeAgentSession.completion;
119
+ if (token !== investigationToken) return;
120
+ activeAgentSession = null;
121
+ if (result.cancelled) {
122
+ sessionState.cancelAgentSession("Cancelled investigation.");
123
+ return;
124
+ }
125
+ if (result.exitCode !== 0 && !result.finalText) {
126
+ sessionState.failAgentSession(result.stderr || `Agent exited with code ${result.exitCode}`);
127
+ return;
128
+ }
129
+ sessionState.completeAgentSession({
130
+ finalText: result.finalText,
131
+ exitCode: result.exitCode,
132
+ });
133
+ } catch (error) {
134
+ if (token !== investigationToken) return;
135
+ activeAgentSession = null;
136
+ sessionState.failAgentSession(error);
137
+ }
138
+ }
139
+
140
+ function cancelInvestigation() {
141
+ if (!activeAgentSession) {
142
+ sessionState.returnToSummary();
143
+ return;
144
+ }
145
+ investigationToken += 1;
146
+ activeAgentSession.cancel();
147
+ activeAgentSession = null;
148
+ sessionState.cancelAgentSession("Cancelled investigation.");
149
+ }
150
+
151
+ function close() {
152
+ if (activeAgentSession) {
153
+ investigationToken += 1;
154
+ activeAgentSession.cancel();
155
+ activeAgentSession = null;
156
+ }
157
+ app.unmount();
158
+ }
159
+ }
@@ -0,0 +1 @@
1
+ export { RunSessionApp as RunApp } from "./run-session-app.mjs";