@vitest-agent/sdk 6.1.0 → 7.0.0

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/index.d.ts CHANGED
@@ -495,6 +495,21 @@ export declare const RenderState: Schema.Struct<{
495
495
  readonly expected: Schema.optional<Schema.String>;
496
496
  readonly received: Schema.optional<Schema.String>;
497
497
  }>>;
498
+ /**
499
+ * What the run wrote straight to the terminal, past Vitest's console
500
+ * capture, folded from `RunFinished.strayOutput`. Absent when the run
501
+ * wrote nothing stray.
502
+ */
503
+ readonly strayOutput: Schema.optional<Schema.Struct<{
504
+ readonly total: Schema.Number;
505
+ readonly stdout: Schema.Number;
506
+ readonly stderr: Schema.Number;
507
+ readonly bytes: Schema.Number;
508
+ readonly samples: Schema.$Array<Schema.Struct<{
509
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
510
+ readonly text: Schema.String;
511
+ }>>;
512
+ }>>;
498
513
  }>;
499
514
  /** @public */
500
515
  export type RenderState = typeof RenderState.Type;
@@ -930,6 +945,16 @@ export declare const AgentReport: Schema.Struct<{
930
945
  readonly files: Schema.Number;
931
946
  }>>;
932
947
  }>>;
948
+ readonly strayOutput: Schema.optional<Schema.Struct<{
949
+ readonly total: Schema.Number;
950
+ readonly stdout: Schema.Number;
951
+ readonly stderr: Schema.Number;
952
+ readonly bytes: Schema.Number;
953
+ readonly samples: Schema.$Array<Schema.Struct<{
954
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
955
+ readonly text: Schema.String;
956
+ }>>;
957
+ }>>;
933
958
  }>;
934
959
  /** @public */
935
960
  export type AgentReport = typeof AgentReport.Type;
@@ -1021,13 +1046,6 @@ export type Environment = typeof Environment.Type;
1021
1046
  export declare const Executor: Schema.Literals<readonly ["human", "agent", "ci"]>;
1022
1047
  /** @public */
1023
1048
  export type Executor = typeof Executor.Type;
1024
- /**
1025
- * Output format for the reporter pipeline.
1026
- * @public
1027
- */
1028
- export declare const OutputFormat: Schema.Literals<readonly ["terminal", "markdown", "json", "vitest-bypass", "silent", "ci-annotations"]>;
1029
- /** @public */
1030
- export type OutputFormat = typeof OutputFormat.Type;
1031
1049
  /**
1032
1050
  * Level of detail in reporter output.
1033
1051
  * @public
@@ -1322,6 +1340,21 @@ export declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunSt
1322
1340
  readonly expected: Schema.optional<Schema.String>;
1323
1341
  readonly received: Schema.optional<Schema.String>;
1324
1342
  }>>>;
1343
+ /**
1344
+ * What the run wrote straight to the terminal, past Vitest's console
1345
+ * capture (`AgentReport.strayOutput`). Optional: absent when the run
1346
+ * wrote nothing stray, and in older replay data.
1347
+ */
1348
+ readonly strayOutput: Schema.optional<Schema.Struct<{
1349
+ readonly total: Schema.Number;
1350
+ readonly stdout: Schema.Number;
1351
+ readonly stderr: Schema.Number;
1352
+ readonly bytes: Schema.Number;
1353
+ readonly samples: Schema.$Array<Schema.Struct<{
1354
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
1355
+ readonly text: Schema.String;
1356
+ }>>;
1357
+ }>>;
1325
1358
  }>]>;
1326
1359
  /** @public */
1327
1360
  export type RunEvent = typeof RunEvent.Type;
@@ -1461,10 +1494,10 @@ export type Transport = typeof Transport.Type;
1461
1494
  * plugin always populates it in practice — the option exists for clarity
1462
1495
  * and to leave room for future "no-persistence" experiments.
1463
1496
  *
1464
- * `format` and `detail` are pre-resolved by the plugin (via
1465
- * `FormatSelector` / `DetailResolver`) but reporters that want to override
1466
- * can ignore them. `noColor` is the resolved value of the `NO_COLOR` env
1467
- * var; reporters use it to gate ANSI escapes and OSC-8 hyperlinks.
1497
+ * `detail` is pre-resolved by the plugin (via `DetailResolver`) but
1498
+ * reporters that want to override can ignore it. `noColor` is the resolved
1499
+ * value of the `NO_COLOR` env var; reporters use it to gate ANSI escapes and
1500
+ * OSC-8 hyperlinks.
1468
1501
  * @public
1469
1502
  */
1470
1503
  export interface ResolvedReporterConfig {
@@ -1503,7 +1536,6 @@ export interface ResolvedReporterConfig {
1503
1536
  * @internal
1504
1537
  */
1505
1538
  readonly coverageMode: "full" | "ui-only";
1506
- readonly format: OutputFormat;
1507
1539
  readonly detail: DetailLevel;
1508
1540
  readonly noColor: boolean;
1509
1541
  readonly runCommand?: string;
@@ -1625,6 +1657,24 @@ export interface VitestAgentReporter {
1625
1657
  * awaits it and treats a rejection as a logged warning.
1626
1658
  */
1627
1659
  readonly close?: () => Promise<void>;
1660
+ /**
1661
+ * Print one whole line of stray output: bytes a test process wrote
1662
+ * straight to the terminal, past Vitest's console capture (a child
1663
+ * process spawned with inherited stdio is the usual source). A reporter
1664
+ * that draws in place (a live view) implements this to print the line
1665
+ * above its drawing instead of under it, where the next redraw would
1666
+ * strand it. Return `true` when the line was taken; `false` sends it to
1667
+ * the terminal unchanged.
1668
+ *
1669
+ * The plugin calls it synchronously, from the factory's return until just
1670
+ * before {@link VitestAgentReporter.close | close}, only for lines bound
1671
+ * for the process's own stdout or stderr (never when Vitest was given
1672
+ * other streams, as MCP's `run_tests` does), and only while it owns the
1673
+ * console (any mode but `passthrough`). The line carries no trailing
1674
+ * newline. Optional; without it every line passes through unchanged. The
1675
+ * run's stray output is reported on `AgentReport.strayOutput` either way.
1676
+ */
1677
+ readonly printStrayLine?: (stream: "stdout" | "stderr", line: string) => boolean;
1628
1678
  }
1629
1679
  /**
1630
1680
  * Factory that the plugin's `reporter` option accepts. The plugin calls
@@ -2932,11 +2982,62 @@ export declare const RunReportFile: Schema.Struct<{
2932
2982
  readonly files: Schema.Number;
2933
2983
  }>>;
2934
2984
  }>>;
2985
+ readonly strayOutput: Schema.optional<Schema.Struct<{
2986
+ readonly total: Schema.Number;
2987
+ readonly stdout: Schema.Number;
2988
+ readonly stderr: Schema.Number;
2989
+ readonly bytes: Schema.Number;
2990
+ readonly samples: Schema.$Array<Schema.Struct<{
2991
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
2992
+ readonly text: Schema.String;
2993
+ }>>;
2994
+ }>>;
2935
2995
  }>>;
2936
2996
  }>;
2937
2997
  /** @public */
2938
2998
  export type RunReportFile = typeof RunReportFile.Type;
2939
2999
  //#endregion
3000
+ //#region src/schemas/StrayOutput.d.ts
3001
+ /**
3002
+ * One line of stray output, kept as a sample: the stream it was written to,
3003
+ * and its text with escapes stripped, cut to a sane length.
3004
+ * @public
3005
+ */
3006
+ export declare const StrayOutputSample: Schema.Struct<{
3007
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
3008
+ readonly text: Schema.String;
3009
+ }>;
3010
+ /** @public */
3011
+ export type StrayOutputSample = typeof StrayOutputSample.Type;
3012
+ /**
3013
+ * Output a test run wrote straight to the terminal, past Vitest's console
3014
+ * capture: bytes a worker process (or a child process it spawned with
3015
+ * inherited stdio, such as `execFileSync("git", ...)`) wrote to its own
3016
+ * stdout or stderr, which Vitest pipes through to the main process
3017
+ * untouched. Distinct from `consoleLeaks`, which counts `console.*` calls
3018
+ * Vitest did capture.
3019
+ *
3020
+ * A run-level signal: the bytes arrive on one stream shared by every project,
3021
+ * so nothing attributes them to a project, file or test. Every project report
3022
+ * of a run carries the same value. Counts are of non-blank lines; `bytes`
3023
+ * counts everything recorded. `samples` holds the first few lines. Omitted
3024
+ * when the run wrote nothing stray (and whenever the plugin does not own the
3025
+ * console: `passthrough` mode records nothing).
3026
+ * @public
3027
+ */
3028
+ export declare const StrayOutput: Schema.Struct<{
3029
+ readonly total: Schema.Number;
3030
+ readonly stdout: Schema.Number;
3031
+ readonly stderr: Schema.Number;
3032
+ readonly bytes: Schema.Number;
3033
+ readonly samples: Schema.$Array<Schema.Struct<{
3034
+ readonly stream: Schema.Literals<readonly ["stdout", "stderr"]>;
3035
+ readonly text: Schema.String;
3036
+ }>>;
3037
+ }>;
3038
+ /** @public */
3039
+ export type StrayOutput = typeof StrayOutput.Type;
3040
+ //#endregion
2940
3041
  //#region src/schemas/Tdd.d.ts
2941
3042
  /** @public */
2942
3043
  export declare const GoalStatus: Schema.Literals<readonly ["pending", "in_progress", "done", "abandoned"]>;
@@ -4144,6 +4245,79 @@ export declare const probeHostMetadataFromEnv: (env: Record<string, string | und
4144
4245
  */
4145
4246
  export declare function safeFilename(name: string): string;
4146
4247
  //#endregion
4248
+ //#region src/utils/stray-output.d.ts
4249
+ /**
4250
+ * A stream stray output was written to.
4251
+ * @public
4252
+ */
4253
+ export type StrayOutputStream = "stdout" | "stderr";
4254
+ /**
4255
+ * Records stray output for one run at a time: counts its non-blank lines per
4256
+ * stream and its bytes, and keeps the first few lines as samples. Memory is
4257
+ * bounded whatever is written: only the samples and one partial line per
4258
+ * stream (itself cut to the sample length) are held.
4259
+ * @public
4260
+ */
4261
+ export interface StrayOutputRecorder {
4262
+ /** Record text written to `stream`. A write made only of escape sequences is ignored. */
4263
+ readonly write: (stream: StrayOutputStream, text: string) => void;
4264
+ /** What the run has written so far, a trailing partial line included; `undefined` when nothing. */
4265
+ readonly snapshot: () => StrayOutput | undefined;
4266
+ /** Forget everything: a new run starts. */
4267
+ readonly reset: () => void;
4268
+ }
4269
+ /**
4270
+ * Options for {@link makeStrayOutputRecorder}.
4271
+ * @public
4272
+ */
4273
+ export interface StrayOutputRecorderOptions {
4274
+ /** How many lines to keep as samples. @defaultValue 5 */
4275
+ readonly maxSamples?: number;
4276
+ /** How many characters of a sample to keep before cutting it with `…`. @defaultValue 160 */
4277
+ readonly maxSampleChars?: number;
4278
+ }
4279
+ /**
4280
+ * True when `text` is nothing but terminal escape sequences: the cursor
4281
+ * hide/show, clear-screen and erase-scrollback writes Vitest's own `Logger`
4282
+ * makes straight to its output stream. Empty text is not escape-only.
4283
+ * @public
4284
+ */
4285
+ export declare const isEscapeOnly: (text: string) => boolean;
4286
+ /**
4287
+ * Make a {@link StrayOutputRecorder}.
4288
+ * @public
4289
+ */
4290
+ export declare const makeStrayOutputRecorder: (options?: StrayOutputRecorderOptions) => StrayOutputRecorder;
4291
+ /**
4292
+ * The key a stray-output capture publishes its {@link StrayOutputSource}
4293
+ * under, on the Vitest `Logger` whose streams it wraps. `Symbol.for`, so a
4294
+ * plugin and an MCP server that load different copies of this package still
4295
+ * meet on it.
4296
+ * @public
4297
+ */
4298
+ export declare const STRAY_OUTPUT_SOURCE: unique symbol;
4299
+ /**
4300
+ * What a stray-output capture publishes for a reader that did not install it.
4301
+ * @public
4302
+ */
4303
+ export interface StrayOutputSource {
4304
+ /** The current run's stray output, or `undefined` when it wrote none. */
4305
+ readonly snapshot: () => StrayOutput | undefined;
4306
+ }
4307
+ /**
4308
+ * Read the current run's stray output from a Vitest `Logger` a capture was
4309
+ * installed on. `undefined` when none was installed, or the run wrote nothing.
4310
+ * @public
4311
+ */
4312
+ export declare const readStrayOutput: (logger: unknown) => StrayOutput | undefined;
4313
+ /**
4314
+ * The note a renderer prints for a run's stray output: one line naming the
4315
+ * count, the streams, the usual cause and the fix, then up to three sample
4316
+ * lines and how many it left out.
4317
+ * @public
4318
+ */
4319
+ export declare const formatStrayOutputNote: (stray: StrayOutput) => ReadonlyArray<string>;
4320
+ //#endregion
4147
4321
  //#region src/utils/test-location.d.ts
4148
4322
  /** Directory holding source files whose tests may be co-located. @public */
4149
4323
  export declare const SRC_DIR = "src";
package/index.js CHANGED
@@ -7,10 +7,11 @@ import { GitCommandError, ProbeMissError } from "./errors/RunContextError.js";
7
7
  import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "./errors/TddErrors.js";
8
8
  import { ActorType, AgentId, ChatId, ConversationId, HostKind, ProjectKey, TddTaskId } from "./schemas/Identity.js";
9
9
  import { Agent, IdempotencyHit } from "./schemas/Agent.js";
10
- import { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, OutputFormat, PackageManager, ReportError, TestClassification, TestRunReason, TestState } from "./schemas/Common.js";
10
+ import { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, PackageManager, ReportError, TestClassification, TestRunReason, TestState } from "./schemas/Common.js";
11
11
  import { ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests } from "./schemas/ConsoleLeaks.js";
12
12
  import { MetricThresholds, PatternMetricThresholds, PatternThresholds, PerFileThresholds, ResolvedThresholds } from "./schemas/Thresholds.js";
13
13
  import { CoverageReport, CoverageTotals, FileCoverageReport, GlobShortfall } from "./schemas/Coverage.js";
14
+ import { StrayOutput, StrayOutputSample } from "./schemas/StrayOutput.js";
14
15
  import { AgentReport, ModuleReport, ReportSummary, TagCountEntry, TestReport } from "./schemas/AgentReport.js";
15
16
  import { CoverageBaselines } from "./schemas/Baselines.js";
16
17
  import { CacheManifest, CacheManifestEntry } from "./schemas/CacheManifest.js";
@@ -57,9 +58,10 @@ import { normalizeWorkspaceKey } from "./utils/normalize-workspace-key.js";
57
58
  import { basenamePosix, joinPosix, relativePosix, toPosix } from "./utils/posix-path.js";
58
59
  import { probeHostMetadataFromEnv } from "./utils/probe-host-metadata.js";
59
60
  import { safeFilename } from "./utils/safe-filename.js";
61
+ import { STRAY_OUTPUT_SOURCE, formatStrayOutputNote, isEscapeOnly, makeStrayOutputRecorder, readStrayOutput } from "./utils/stray-output.js";
60
62
  import { BATS_FILE_GLOB_SUFFIX, NON_DISCOVERABLE_DIRS, SRC_DIR, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, classifyTestPath, findOwningWorkspace, isTestFileName } from "./utils/test-location.js";
61
63
  import { validateCoverageTargetsShape } from "./utils/validate-coverage-targets-shape.js";
62
64
  import { requiredArtifactForTransition, transitionEnforcesBehaviorMatch, validatePhaseTransition } from "./utils/validate-phase-transition.js";
63
65
  import { CURRENT_SDK_VERSION } from "./version.js";
64
66
 
65
- export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BATS_FILE_GLOB_SUFFIX, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FailureRecord, FileCoverageReport, FileEditPayload, GitCommandError, GlobShortfall, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IdempotencyHit, IllegalStatusTransitionError, MetricThresholds, ModuleRecord, ModuleReport, NON_DISCOVERABLE_DIRS, NotePayload, OutputFormat, PackageManager, PathResolutionError, PatternMetricThresholds, PatternThresholds, PerFileThresholds, PhaseTransitionEvent, ProbeMissError, ProjectIdentityNotResolvableError, ProjectKey, RUN_REPORT_FILE_SCHEMA_URL, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportOption, ReportSettings, ReportSummary, ResolvedThresholds, RunEvent, RunReportFile, SRC_DIR, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SuggestedActionRecord, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, ansi, basenamePosix, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, classifyTest, classifyTestPath, coerceErrorField, coerceErrorText, collectConsoleLeakEntries, compressLines, computeTrend, countSuiteFailures, detectNonDefaultDiscoverStrategy, detectPackageManager, detectVitestScripts, determineTier, extractSqlReason, findFunctionBoundary, findOwningWorkspace, formatConsoleMarkdown, formatFatalError, formatGfm, formatScopedCoverageNote, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, initialRenderState, isTestFileName, isTimeoutError, isVitestInvocation, joinPosix, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, relativePath, relativePosix, requiredArtifactForTransition, resolveCoverageInput, rewriteBashCommand, safeFilename, stripAnsi, toPosix, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
67
+ export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BATS_FILE_GLOB_SUFFIX, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConsoleLeakFile, ConsoleLeaks, ConsoleLeaksFromFailingTests, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FailureRecord, FileCoverageReport, FileEditPayload, GitCommandError, GlobShortfall, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IdempotencyHit, IllegalStatusTransitionError, MetricThresholds, ModuleRecord, ModuleReport, NON_DISCOVERABLE_DIRS, NotePayload, PackageManager, PathResolutionError, PatternMetricThresholds, PatternThresholds, PerFileThresholds, PhaseTransitionEvent, ProbeMissError, ProjectIdentityNotResolvableError, ProjectKey, RUN_REPORT_FILE_SCHEMA_URL, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportOption, ReportSettings, ReportSummary, ResolvedThresholds, RunEvent, RunReportFile, SRC_DIR, STRAY_OUTPUT_SOURCE, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, StrayOutput, StrayOutputSample, SuggestedActionRecord, TEST_DIR, TEST_FILE_GLOB_SUFFIX, TEST_HELPER_DIRS, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TestAnnotation, TestArtifact, TestArtifactLocation, TestAttachment, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, ansi, basenamePosix, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, classifyTest, classifyTestPath, coerceErrorField, coerceErrorText, collectConsoleLeakEntries, compressLines, computeTrend, countSuiteFailures, detectNonDefaultDiscoverStrategy, detectPackageManager, detectVitestScripts, determineTier, extractSqlReason, findFunctionBoundary, findOwningWorkspace, formatConsoleMarkdown, formatFatalError, formatGfm, formatScopedCoverageNote, formatStrayOutputNote, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, initialRenderState, isEscapeOnly, isTestFileName, isTimeoutError, isVitestInvocation, joinPosix, makeStrayOutputRecorder, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, readStrayOutput, relativePath, relativePosix, requiredArtifactForTransition, resolveCoverageInput, rewriteBashCommand, safeFilename, stripAnsi, toPosix, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/sdk",
3
- "version": "6.1.0",
3
+ "version": "7.0.0",
4
4
  "private": false,
5
5
  "description": "Platform-free core for the vitest-agent package family: Effect Schema definitions, reporter and dispatcher contracts, errors, and pure utilities.",
6
6
  "keywords": [
@@ -41,9 +41,9 @@
41
41
  "./package.json": "./package.json"
42
42
  },
43
43
  "dependencies": {
44
- "acorn": "^8.18.0",
44
+ "acorn": "^8.19.0",
45
45
  "acorn-typescript": "^1.4.13",
46
- "effect": "^4.0.0"
46
+ "effect": "^4.0.2"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=24.11.0"
@@ -887,6 +887,77 @@
887
887
  "required": ["total", "byFile"],
888
888
  "additionalProperties": true
889
889
  },
890
+ "StrayOutputSample": {
891
+ "type": "object",
892
+ "properties": {
893
+ "stream": {
894
+ "type": "string",
895
+ "enum": ["stdout", "stderr"]
896
+ },
897
+ "text": {
898
+ "type": "string"
899
+ }
900
+ },
901
+ "required": ["stream", "text"],
902
+ "additionalProperties": true
903
+ },
904
+ "StrayOutput": {
905
+ "type": "object",
906
+ "properties": {
907
+ "total": {
908
+ "anyOf": [
909
+ {
910
+ "type": "number"
911
+ },
912
+ {
913
+ "type": "string",
914
+ "enum": ["Infinity", "-Infinity", "NaN"]
915
+ }
916
+ ]
917
+ },
918
+ "stdout": {
919
+ "anyOf": [
920
+ {
921
+ "type": "number"
922
+ },
923
+ {
924
+ "type": "string",
925
+ "enum": ["Infinity", "-Infinity", "NaN"]
926
+ }
927
+ ]
928
+ },
929
+ "stderr": {
930
+ "anyOf": [
931
+ {
932
+ "type": "number"
933
+ },
934
+ {
935
+ "type": "string",
936
+ "enum": ["Infinity", "-Infinity", "NaN"]
937
+ }
938
+ ]
939
+ },
940
+ "bytes": {
941
+ "anyOf": [
942
+ {
943
+ "type": "number"
944
+ },
945
+ {
946
+ "type": "string",
947
+ "enum": ["Infinity", "-Infinity", "NaN"]
948
+ }
949
+ ]
950
+ },
951
+ "samples": {
952
+ "type": "array",
953
+ "items": {
954
+ "$ref": "#/$defs/StrayOutputSample"
955
+ }
956
+ }
957
+ },
958
+ "required": ["total", "stdout", "stderr", "bytes", "samples"],
959
+ "additionalProperties": true
960
+ },
890
961
  "AgentReport": {
891
962
  "type": "object",
892
963
  "properties": {
@@ -959,6 +1030,16 @@
959
1030
  "type": "null"
960
1031
  }
961
1032
  ]
1033
+ },
1034
+ "strayOutput": {
1035
+ "anyOf": [
1036
+ {
1037
+ "$ref": "#/$defs/StrayOutput"
1038
+ },
1039
+ {
1040
+ "type": "null"
1041
+ }
1042
+ ]
962
1043
  }
963
1044
  },
964
1045
  "required": ["timestamp", "reason", "summary", "failed", "unhandledErrors", "failedFiles"],
@@ -1,6 +1,7 @@
1
1
  import { ReportError, TestClassification, TestRunReason, TestState } from "./Common.js";
2
2
  import { ConsoleLeaks } from "./ConsoleLeaks.js";
3
3
  import { CoverageReport } from "./Coverage.js";
4
+ import { StrayOutput } from "./StrayOutput.js";
4
5
  import { Schema } from "effect";
5
6
 
6
7
  //#region src/schemas/AgentReport.ts
@@ -71,7 +72,8 @@ const AgentReport = Schema.Struct({
71
72
  failedFiles: Schema.Array(Schema.String),
72
73
  coverage: Schema.optional(CoverageReport),
73
74
  tagCounts: Schema.optional(Schema.Record(Schema.String, TagCountEntry)),
74
- consoleLeaks: Schema.optional(ConsoleLeaks)
75
+ consoleLeaks: Schema.optional(ConsoleLeaks),
76
+ strayOutput: Schema.optional(StrayOutput)
75
77
  }).annotate({ identifier: "AgentReport" });
76
78
 
77
79
  //#endregion
package/schemas/Common.js CHANGED
@@ -116,18 +116,6 @@ const Executor = Schema.Literals([
116
116
  "ci"
117
117
  ]).annotate({ identifier: "Executor" });
118
118
  /**
119
- * Output format for the reporter pipeline.
120
- * @public
121
- */
122
- const OutputFormat = Schema.Literals([
123
- "terminal",
124
- "markdown",
125
- "json",
126
- "vitest-bypass",
127
- "silent",
128
- "ci-annotations"
129
- ]).annotate({ identifier: "OutputFormat" });
130
- /**
131
119
  * Level of detail in reporter output.
132
120
  * @public
133
121
  */
@@ -158,4 +146,4 @@ const ReportError = Schema.Struct({
158
146
  }).annotate({ identifier: "ReportError" });
159
147
 
160
148
  //#endregion
161
- export { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, OutputFormat, PackageManager, ReportError, TestClassification, TestRunReason, TestState };
149
+ export { AgentConsoleMode, CiConsoleMode, ConsoleMode, ConsoleOutputMode, DetailLevel, Environment, Executor, HumanConsoleMode, PackageManager, ReportError, TestClassification, TestRunReason, TestState };
@@ -1,6 +1,7 @@
1
1
  import { ReportError, TestClassification, TestState } from "./Common.js";
2
2
  import { MetricThresholds } from "./Thresholds.js";
3
3
  import { CoverageTotals, GlobShortfall } from "./Coverage.js";
4
+ import { StrayOutput } from "./StrayOutput.js";
4
5
  import { ActionSeverity, CoverageGap, CoverageMetric } from "./RunEvent.js";
5
6
  import { Schema } from "effect";
6
7
 
@@ -156,7 +157,13 @@ const RenderState = Schema.Struct({
156
157
  * (older replay data, hand-built fixtures) defaults to `[]`. See
157
158
  * issue #240.
158
159
  */
159
- unhandledErrors: Schema.Array(ReportError)
160
+ unhandledErrors: Schema.Array(ReportError),
161
+ /**
162
+ * What the run wrote straight to the terminal, past Vitest's console
163
+ * capture, folded from `RunFinished.strayOutput`. Absent when the run
164
+ * wrote nothing stray.
165
+ */
166
+ strayOutput: Schema.optional(StrayOutput)
160
167
  }).annotate({ identifier: "RenderState" });
161
168
  /**
162
169
  * Initial state — what the reducer returns when no events have been
@@ -1,6 +1,7 @@
1
1
  import { ReportError, TestClassification, TestState } from "./Common.js";
2
2
  import { MetricThresholds } from "./Thresholds.js";
3
3
  import { CoverageTotals, FileCoverageReport, GlobShortfall } from "./Coverage.js";
4
+ import { StrayOutput } from "./StrayOutput.js";
4
5
  import { TestArtifactLocation, TestAttachment } from "./TestArtifacts.js";
5
6
  import { Schema } from "effect";
6
7
 
@@ -219,7 +220,13 @@ const RunEvent = Schema.Union([
219
220
  * argument to `onTestRunEnd`. Optional so hand-built fixtures
220
221
  * and older replay data keep decoding. See issue #240.
221
222
  */
222
- unhandledErrors: Schema.optional(Schema.Array(ReportError))
223
+ unhandledErrors: Schema.optional(Schema.Array(ReportError)),
224
+ /**
225
+ * What the run wrote straight to the terminal, past Vitest's console
226
+ * capture (`AgentReport.strayOutput`). Optional: absent when the run
227
+ * wrote nothing stray, and in older replay data.
228
+ */
229
+ strayOutput: Schema.optional(StrayOutput)
223
230
  })
224
231
  ]).annotate({ identifier: "RunEvent" });
225
232
 
@@ -0,0 +1,38 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/StrayOutput.ts
4
+ /**
5
+ * One line of stray output, kept as a sample: the stream it was written to,
6
+ * and its text with escapes stripped, cut to a sane length.
7
+ * @public
8
+ */
9
+ const StrayOutputSample = Schema.Struct({
10
+ stream: Schema.Literals(["stdout", "stderr"]),
11
+ text: Schema.String
12
+ }).annotate({ identifier: "StrayOutputSample" });
13
+ /**
14
+ * Output a test run wrote straight to the terminal, past Vitest's console
15
+ * capture: bytes a worker process (or a child process it spawned with
16
+ * inherited stdio, such as `execFileSync("git", ...)`) wrote to its own
17
+ * stdout or stderr, which Vitest pipes through to the main process
18
+ * untouched. Distinct from `consoleLeaks`, which counts `console.*` calls
19
+ * Vitest did capture.
20
+ *
21
+ * A run-level signal: the bytes arrive on one stream shared by every project,
22
+ * so nothing attributes them to a project, file or test. Every project report
23
+ * of a run carries the same value. Counts are of non-blank lines; `bytes`
24
+ * counts everything recorded. `samples` holds the first few lines. Omitted
25
+ * when the run wrote nothing stray (and whenever the plugin does not own the
26
+ * console: `passthrough` mode records nothing).
27
+ * @public
28
+ */
29
+ const StrayOutput = Schema.Struct({
30
+ total: Schema.Number,
31
+ stdout: Schema.Number,
32
+ stderr: Schema.Number,
33
+ bytes: Schema.Number,
34
+ samples: Schema.Array(StrayOutputSample)
35
+ }).annotate({ identifier: "StrayOutput" });
36
+
37
+ //#endregion
38
+ export { StrayOutput, StrayOutputSample };
@@ -0,0 +1,143 @@
1
+ //#region src/utils/stray-output.ts
2
+ const ESCAPE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\-_]|c)/g;
3
+ const CONTROL = /[\x00-\x08\x0b-\x1f\x7f]/g;
4
+ /**
5
+ * True when `text` is nothing but terminal escape sequences: the cursor
6
+ * hide/show, clear-screen and erase-scrollback writes Vitest's own `Logger`
7
+ * makes straight to its output stream. Empty text is not escape-only.
8
+ * @public
9
+ */
10
+ const isEscapeOnly = (text) => text.length > 0 && text.replace(ESCAPE, "").length === 0;
11
+ const encoder = new TextEncoder();
12
+ /**
13
+ * Make a {@link StrayOutputRecorder}.
14
+ * @public
15
+ */
16
+ const makeStrayOutputRecorder = (options = {}) => {
17
+ const maxSamples = options.maxSamples ?? 5;
18
+ const maxChars = options.maxSampleChars ?? 160;
19
+ let counts = {
20
+ stdout: 0,
21
+ stderr: 0
22
+ };
23
+ let bytes = 0;
24
+ let samples = [];
25
+ let pending = {
26
+ stdout: {
27
+ text: "",
28
+ cut: false
29
+ },
30
+ stderr: {
31
+ text: "",
32
+ cut: false
33
+ }
34
+ };
35
+ const clean = (line) => line.replace(ESCAPE, "").replace(CONTROL, "").trim();
36
+ const sampleOf = (line) => {
37
+ const text = clean(line.text);
38
+ return line.cut || text.length > maxChars ? `${text.slice(0, maxChars)}…` : text;
39
+ };
40
+ const finish = (stream, line, into, out) => {
41
+ if (clean(line.text) === "") return;
42
+ into[stream]++;
43
+ if (out.length < maxSamples) out.push({
44
+ stream,
45
+ text: sampleOf(line)
46
+ });
47
+ };
48
+ const append = (line, text) => {
49
+ const room = maxChars * 4 - line.text.length;
50
+ if (text.length <= room) line.text += text;
51
+ else {
52
+ line.text += text.slice(0, Math.max(0, room));
53
+ line.cut = true;
54
+ }
55
+ };
56
+ return {
57
+ write: (stream, text) => {
58
+ if (text.length === 0 || isEscapeOnly(text)) return;
59
+ bytes += encoder.encode(text).length;
60
+ const parts = text.split("\n");
61
+ const line = pending[stream];
62
+ append(line, parts[0] ?? "");
63
+ for (let i = 1; i < parts.length; i++) {
64
+ finish(stream, line, counts, samples);
65
+ line.text = "";
66
+ line.cut = false;
67
+ append(line, parts[i] ?? "");
68
+ }
69
+ },
70
+ snapshot: () => {
71
+ const totals = { ...counts };
72
+ const out = [...samples];
73
+ finish("stdout", pending.stdout, totals, out);
74
+ finish("stderr", pending.stderr, totals, out);
75
+ const total = totals.stdout + totals.stderr;
76
+ if (total === 0) return void 0;
77
+ return {
78
+ total,
79
+ stdout: totals.stdout,
80
+ stderr: totals.stderr,
81
+ bytes,
82
+ samples: out
83
+ };
84
+ },
85
+ reset: () => {
86
+ counts = {
87
+ stdout: 0,
88
+ stderr: 0
89
+ };
90
+ bytes = 0;
91
+ samples = [];
92
+ pending = {
93
+ stdout: {
94
+ text: "",
95
+ cut: false
96
+ },
97
+ stderr: {
98
+ text: "",
99
+ cut: false
100
+ }
101
+ };
102
+ }
103
+ };
104
+ };
105
+ /**
106
+ * The key a stray-output capture publishes its {@link StrayOutputSource}
107
+ * under, on the Vitest `Logger` whose streams it wraps. `Symbol.for`, so a
108
+ * plugin and an MCP server that load different copies of this package still
109
+ * meet on it.
110
+ * @public
111
+ */
112
+ const STRAY_OUTPUT_SOURCE = Symbol.for("vitest-agent/stray-output");
113
+ /**
114
+ * Read the current run's stray output from a Vitest `Logger` a capture was
115
+ * installed on. `undefined` when none was installed, or the run wrote nothing.
116
+ * @public
117
+ */
118
+ const readStrayOutput = (logger) => {
119
+ if (logger === null || typeof logger !== "object") return void 0;
120
+ const source = logger[STRAY_OUTPUT_SOURCE];
121
+ if (source === null || typeof source !== "object") return void 0;
122
+ const snapshot = source.snapshot;
123
+ return typeof snapshot === "function" ? snapshot() : void 0;
124
+ };
125
+ const MAX_NOTE_SAMPLES = 3;
126
+ /**
127
+ * The note a renderer prints for a run's stray output: one line naming the
128
+ * count, the streams, the usual cause and the fix, then up to three sample
129
+ * lines and how many it left out.
130
+ * @public
131
+ */
132
+ const formatStrayOutputNote = (stray) => {
133
+ const streams = stray.stdout > 0 && stray.stderr > 0 ? "stdout and stderr" : stray.stdout > 0 ? "stdout" : "stderr";
134
+ const lines = [`Stray output: tests wrote ${stray.total} ${stray.total === 1 ? "line" : "lines"} directly to ${streams}, bypassing Vitest's console capture. A child process with inherited stdio is the usual cause; pass \`stdio: "pipe"\`.`];
135
+ const shown = stray.samples.slice(0, MAX_NOTE_SAMPLES);
136
+ for (const sample of shown) lines.push(` ${sample.stream}: ${sample.text}`);
137
+ const more = stray.total - shown.length;
138
+ if (more > 0 && shown.length > 0) lines.push(` (+${more} more)`);
139
+ return lines;
140
+ };
141
+
142
+ //#endregion
143
+ export { STRAY_OUTPUT_SOURCE, formatStrayOutputNote, isEscapeOnly, makeStrayOutputRecorder, readStrayOutput };
package/version.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * survives into the published artifact.
12
12
  * @public
13
13
  */
14
- const CURRENT_SDK_VERSION = "6.1.0";
14
+ const CURRENT_SDK_VERSION = "7.0.0";
15
15
 
16
16
  //#endregion
17
17
  export { CURRENT_SDK_VERSION };