@vitest-agent/engine 3.0.1 → 3.1.1
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 +24 -19
- package/index.js +18 -18
- package/layers/DataStoreLive.js +42 -43
- package/layers/EnvironmentDetectorLive.js +1 -1
- package/layers/LoggerLive.js +2 -2
- package/layers/PerClientSessionMapLive.js +3 -3
- package/layers/ProjectDiscoveryLive.js +11 -10
- package/layers/ProjectIdentityLive.js +5 -6
- package/package.json +21 -20
- package/platform.js +2 -2
- package/programs/hook-paths.js +2 -2
- package/programs/platform-sidecar.js +1 -1
- package/programs/record-turn.js +11 -17
- package/programs/register-agent.js +1 -1
- package/programs/resolve-session-for-recording.js +4 -3
- package/project-dir.js +2 -2
- package/testing.js +2 -2
- package/tsdoc-metadata.json +1 -1
- package/utils/ensure-migrated.js +1 -1
- package/utils/format-fatal-error.js +2 -2
- package/utils/resolve-project-key-from-cwd.js +19 -31
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -1269,7 +1269,7 @@ export declare class EnvironmentDetector extends EnvironmentDetector_base {}
|
|
|
1269
1269
|
*
|
|
1270
1270
|
* @remarks
|
|
1271
1271
|
* The agent/CI/human split is `@effected/env`'s `Audience.detect`, so the
|
|
1272
|
-
* engine and every `@effected/cli` front end agree on who is running; this
|
|
1272
|
+
* engine and every `@effected/cli-kit` front end agree on who is running; this
|
|
1273
1273
|
* only refines CI into GitHub Actions versus any other CI.
|
|
1274
1274
|
*
|
|
1275
1275
|
* @param runtime - the `@effected/env` runtime snapshot to classify
|
|
@@ -1361,7 +1361,7 @@ export declare const HistoryTrackerTest: {
|
|
|
1361
1361
|
};
|
|
1362
1362
|
/**
|
|
1363
1363
|
* Create a structured JSON (NDJSON) logger layer for stderr, over
|
|
1364
|
-
* `@effected/cli`'s `CliLog` in its diagnostics-only mode
|
|
1364
|
+
* `@effected/cli-kit`'s `CliLog` in its diagnostics-only mode
|
|
1365
1365
|
* (`format: "json"`, `plainLogger: false`): the already-resolved level is
|
|
1366
1366
|
* passed as `level`, and no plain `CliLogger` is installed beside the sink, so
|
|
1367
1367
|
* each record prints once and nothing reads the audience or the terminal.
|
|
@@ -1565,7 +1565,7 @@ declare const ProjectDiscovery_base: Context.ServiceClass<ProjectDiscovery, "vit
|
|
|
1565
1565
|
/** @public */
|
|
1566
1566
|
export declare class ProjectDiscovery extends ProjectDiscovery_base {}
|
|
1567
1567
|
/** @public */
|
|
1568
|
-
export declare const ProjectDiscoveryLive: Layer.Layer<ProjectDiscovery, never, FileSystem.FileSystem>;
|
|
1568
|
+
export declare const ProjectDiscoveryLive: Layer.Layer<ProjectDiscovery, never, FileSystem.FileSystem | Path.Path>;
|
|
1569
1569
|
/** @public */
|
|
1570
1570
|
export declare const ProjectDiscoveryTest: {
|
|
1571
1571
|
readonly layer: (entries: ReadonlyArray<TestFileEntry>) => Layer.Layer<ProjectDiscovery>;
|
|
@@ -1865,8 +1865,8 @@ export interface SqliteStack {
|
|
|
1865
1865
|
}
|
|
1866
1866
|
/**
|
|
1867
1867
|
* The Node platform services every SQLite stack and every Live layer share.
|
|
1868
|
-
* `NodeServices.layer` aggregates
|
|
1869
|
-
*
|
|
1868
|
+
* `NodeServices.layer` aggregates ChildProcessSpawner | FileSystem | Path |
|
|
1869
|
+
* Stdio | Terminal; since Effect 4.1 it carries no crypto service.
|
|
1870
1870
|
* @public
|
|
1871
1871
|
*/
|
|
1872
1872
|
export declare const NodePlatformLayer: Layer.Layer<NodeServices.NodeServices, never, never>;
|
|
@@ -1905,7 +1905,7 @@ export interface PlatformOptions {
|
|
|
1905
1905
|
/**
|
|
1906
1906
|
* Whether `PlatformLive` installs its own logger set (`LoggerLive`, from
|
|
1907
1907
|
* `logLevel` / `logFile`). Defaults to `true`. Pass `false` when the caller
|
|
1908
|
-
* already owns the logger set (for example `@effected/cli`'s `CliLog`
|
|
1908
|
+
* already owns the logger set (for example `@effected/cli-kit`'s `CliLog`
|
|
1909
1909
|
* installed by `CliRuntime.main`'s `env.log`): `LoggerLive` replaces the
|
|
1910
1910
|
* installed loggers, so leaving it in would silence the caller's set.
|
|
1911
1911
|
* `logLevel` and `logFile` are ignored when this is `false`.
|
|
@@ -2026,7 +2026,7 @@ export type HookPathsError = XdgEnvError | AppDirsError$1 | PlatformError | Proj
|
|
|
2026
2026
|
*
|
|
2027
2027
|
* Precedence: the `CLAUDE_PLUGIN_DATA` env var, then
|
|
2028
2028
|
* `VITEST_AGENT_SESSION_MAP_DIR`, then `~/.vitest-agent/` (`HOME`, then
|
|
2029
|
-
* `USERPROFILE`). The two variables are read through `@effected/
|
|
2029
|
+
* `USERPROFILE`). The two variables are read through `@effected/kit`'s
|
|
2030
2030
|
* `LaunchContext.resolve`: a value is trimmed, and a blank or unsubstituted
|
|
2031
2031
|
* `${...}` placeholder value counts as unset. The chosen directory is created. Fails with
|
|
2032
2032
|
* `ProjectIdentityNotResolvableError` when no home directory is resolvable.
|
|
@@ -2207,7 +2207,7 @@ export interface RecordTddArtifactResult {
|
|
|
2207
2207
|
* @param input - the artifact to record, keyed by host chat id
|
|
2208
2208
|
* @public
|
|
2209
2209
|
*/
|
|
2210
|
-
export declare const recordTddArtifactEffect: (input: RecordTddArtifactInput) => Effect.Effect<RecordTddArtifactResult, DataStoreError | Error, DataReader | DataStore | FileSystem.FileSystem>;
|
|
2210
|
+
export declare const recordTddArtifactEffect: (input: RecordTddArtifactInput) => Effect.Effect<RecordTddArtifactResult, DataStoreError | Error, DataReader | DataStore | FileSystem.FileSystem | Path.Path>;
|
|
2211
2211
|
/**
|
|
2212
2212
|
* Input for {@link recordTddArtifactByTaskIdEffect}: an artifact keyed by an
|
|
2213
2213
|
* explicit TDD task id.
|
|
@@ -2290,7 +2290,7 @@ export interface DispatchRecordTddArtifactInput {
|
|
|
2290
2290
|
* @param input - the artifact plus either a chat id or a TDD task id
|
|
2291
2291
|
* @public
|
|
2292
2292
|
*/
|
|
2293
|
-
export declare const dispatchRecordTddArtifactEffect: (input: DispatchRecordTddArtifactInput) => Effect.Effect<RecordTddArtifactResult, DataStoreError | Error, DataReader | DataStore | FileSystem.FileSystem>;
|
|
2293
|
+
export declare const dispatchRecordTddArtifactEffect: (input: DispatchRecordTddArtifactInput) => Effect.Effect<RecordTddArtifactResult, DataStoreError | Error, DataReader | DataStore | FileSystem.FileSystem | Path.Path>;
|
|
2294
2294
|
/**
|
|
2295
2295
|
* Outcome of {@link parseAndValidateTurnPayload}: the decoded `TurnPayload`
|
|
2296
2296
|
* on success, or a human-readable error string.
|
|
@@ -2308,6 +2308,9 @@ export type ParseResult = {
|
|
|
2308
2308
|
* Parse a raw JSON string and validate it against the `TurnPayload` schema
|
|
2309
2309
|
* without throwing.
|
|
2310
2310
|
*
|
|
2311
|
+
* Text that is not JSON at all reads `Invalid JSON: …`; JSON whose body is
|
|
2312
|
+
* not a `TurnPayload` reads `Invalid TurnPayload: …`.
|
|
2313
|
+
*
|
|
2311
2314
|
* @param raw - the JSON text delivered by the hook
|
|
2312
2315
|
* @returns the decoded payload, or an error describing why it was rejected
|
|
2313
2316
|
* @public
|
|
@@ -2350,7 +2353,7 @@ export interface RecordTurnInput {
|
|
|
2350
2353
|
*/
|
|
2351
2354
|
export declare const recordTurnEffect: (input: RecordTurnInput) => Effect.Effect<{
|
|
2352
2355
|
turnId: number;
|
|
2353
|
-
}, Error, DataReader | DataStore | FileSystem.FileSystem>;
|
|
2356
|
+
}, Error, DataReader | DataStore | FileSystem.FileSystem | Path.Path>;
|
|
2354
2357
|
/**
|
|
2355
2358
|
* Input for {@link recordRunWorkspaceChangesEffect}: one commit plus the
|
|
2356
2359
|
* files it touched.
|
|
@@ -2485,7 +2488,7 @@ export interface ResolveSessionForRecordingInput {
|
|
|
2485
2488
|
* @returns the resolved (or newly created) session row
|
|
2486
2489
|
* @public
|
|
2487
2490
|
*/
|
|
2488
|
-
export declare const resolveSessionForRecording: (input: ResolveSessionForRecordingInput) => Effect.Effect<SessionDetail, DataStoreError, DataReader | DataStore | FileSystem.FileSystem>;
|
|
2491
|
+
export declare const resolveSessionForRecording: (input: ResolveSessionForRecordingInput) => Effect.Effect<SessionDetail, DataStoreError, DataReader | DataStore | FileSystem.FileSystem | Path.Path>;
|
|
2489
2492
|
/**
|
|
2490
2493
|
* Recovered session attribution context — the canonical UUIDs the
|
|
2491
2494
|
* SessionStart hook wrote to `${CLAUDE_ENV_FILE}` (auto-sourced into the
|
|
@@ -2552,7 +2555,7 @@ export declare const recoverSessionContextFromSessionEnv: (opts: {
|
|
|
2552
2555
|
* Precedence: `VITEST_AGENT_PROJECT_DIR` (hook-driven override), then
|
|
2553
2556
|
* `VITEST_AGENT_REPORTER_PROJECT_DIR` (the plugin-spawned MCP server's
|
|
2554
2557
|
* hand-off), then `CLAUDE_PROJECT_DIR` (Claude Code's own convention),
|
|
2555
|
-
* then `cwd`. Delegates to `@effected/
|
|
2558
|
+
* then `cwd`. Delegates to `@effected/kit`'s `LaunchContext.projectDir`:
|
|
2556
2559
|
* a value is trimmed, and an empty or whitespace-only value, or one a plugin
|
|
2557
2560
|
* host left as a literal unsubstituted `${...}` placeholder, counts as unset.
|
|
2558
2561
|
* Pure: both the env map and the working directory are injected by the caller.
|
|
@@ -2698,7 +2701,7 @@ export declare const computeFailureSignature: (input: FailureSignatureInput) =>
|
|
|
2698
2701
|
* is (one from `Effect.runPromiseExit` keeps every reason and the span
|
|
2699
2702
|
* trail), and anything else is rendered as a defect, `Cause.die(err)`: an
|
|
2700
2703
|
* unexpected throw is a bug to report, and a defect is the shape whose
|
|
2701
|
-
* document carries the stack. `@effected/cli`'s `CliFailure.toDoc` keeps
|
|
2704
|
+
* document carries the stack. `@effected/cli-kit`'s `CliFailure.toDoc` keeps
|
|
2702
2705
|
* the program's own frames, hides `node_modules` and runtime frames,
|
|
2703
2706
|
* follows an `Error.cause` chain, and renders a non-`Error` value as its
|
|
2704
2707
|
* text.
|
|
@@ -2854,7 +2857,7 @@ export interface ResolveDataPathOptions {
|
|
|
2854
2857
|
* @param options - Optional programmatic overrides.
|
|
2855
2858
|
* @public
|
|
2856
2859
|
*/
|
|
2857
|
-
export declare const resolveDataPath: (projectDir: string, options?: ResolveDataPathOptions) => Effect.Effect<string, import("@effected/xdg").AppDirsError | import("effect/PlatformError").PlatformError | ProjectIdentitySourceError | import("@effected/config-file").ConfigReadError<import("@effected/config-file").ConfigCodecError>, AppDirs$1 | FileSystem.FileSystem | VitestAgentConfigFile>;
|
|
2860
|
+
export declare const resolveDataPath: (projectDir: string, options?: ResolveDataPathOptions) => Effect.Effect<string, import("@effected/xdg").AppDirsError | import("effect/PlatformError").PlatformError | ProjectIdentitySourceError | import("@effected/config-file").ConfigReadError<import("@effected/config-file").ConfigCodecError>, AppDirs$1 | FileSystem.FileSystem | import("effect/Path").Path | VitestAgentConfigFile>;
|
|
2858
2861
|
/**
|
|
2859
2862
|
* Compute the project key for a workspace by reading `package.json`
|
|
2860
2863
|
* fields directly. Returns the canonical `host__path` form when a git
|
|
@@ -2864,15 +2867,17 @@ export declare const resolveDataPath: (projectDir: string, options?: ResolveData
|
|
|
2864
2867
|
* (or `"anonymous-project"`) so callers don't have to handle the
|
|
2865
2868
|
* empty case.
|
|
2866
2869
|
*
|
|
2867
|
-
* Synchronous and reads the real disk through `node:fs
|
|
2868
|
-
*
|
|
2869
|
-
*
|
|
2870
|
+
* Synchronous and reads the real disk through `node:fs`: it runs
|
|
2871
|
+
* {@link resolveProjectKeyFromCwdEffect} under `Effect.runSync` over a
|
|
2872
|
+
* read-only synchronous `FileSystem` and the host's Node `Path`. Effect
|
|
2873
|
+
* callers that already hold a `FileSystem` and `Path` use the Effect form
|
|
2874
|
+
* directly.
|
|
2870
2875
|
* @public
|
|
2871
2876
|
*/
|
|
2872
2877
|
export declare const resolveProjectKeyFromCwd: (cwd: string) => string;
|
|
2873
2878
|
/**
|
|
2874
2879
|
* {@link resolveProjectKeyFromCwd} read through the ambient
|
|
2875
|
-
* `FileSystem`
|
|
2880
|
+
* `FileSystem` and `Path` services instead of `node:fs` — same upward walk to the
|
|
2876
2881
|
* nearest `package.json` (`@effected/walker`'s `Walker.ascend` +
|
|
2877
2882
|
* `Walker.findUpward`), same precedence, same basename fallback.
|
|
2878
2883
|
*
|
|
@@ -2883,7 +2888,7 @@ export declare const resolveProjectKeyFromCwd: (cwd: string) => string;
|
|
|
2883
2888
|
* @param cwd - Absolute directory to start the upward walk from.
|
|
2884
2889
|
* @public
|
|
2885
2890
|
*/
|
|
2886
|
-
export declare const resolveProjectKeyFromCwdEffect: (cwd: string) => Effect.Effect<string, never, FileSystem.FileSystem>;
|
|
2891
|
+
export declare const resolveProjectKeyFromCwdEffect: (cwd: string) => Effect.Effect<string, never, FileSystem.FileSystem | Path.Path>;
|
|
2887
2892
|
/**
|
|
2888
2893
|
* Resolve the normalized workspace key for the workspace containing
|
|
2889
2894
|
* `projectDir`.
|
package/index.js
CHANGED
|
@@ -1,34 +1,20 @@
|
|
|
1
|
-
import { VitestAgentConfigFile } from "./services/Config.js";
|
|
2
1
|
import { ConfigLive } from "./layers/ConfigLive.js";
|
|
3
|
-
import { DataReader } from "./services/DataReader.js";
|
|
4
|
-
import { HistoryTracker, historyKey } from "./services/HistoryTracker.js";
|
|
5
2
|
import { DataReaderLive } from "./layers/DataReaderLive.js";
|
|
6
|
-
import { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES } from "./services/DataStore.js";
|
|
7
|
-
import { withPersistSavepoint, withPersistTransaction } from "./utils/persist-transaction.js";
|
|
8
3
|
import { DataStoreLive } from "./layers/DataStoreLive.js";
|
|
9
|
-
import { DetailResolver } from "./services/DetailResolver.js";
|
|
10
4
|
import { DetailResolverLive } from "./layers/DetailResolverLive.js";
|
|
11
|
-
import { DiscoveryRegistry } from "./services/DiscoveryRegistry.js";
|
|
12
5
|
import { DiscoveryRegistryLive } from "./layers/DiscoveryRegistryLive.js";
|
|
13
|
-
import { EnvironmentDetector } from "./services/EnvironmentDetector.js";
|
|
14
6
|
import { EnvironmentDetectorLive, classifyEnvironment } from "./layers/EnvironmentDetectorLive.js";
|
|
15
7
|
import { EnvironmentDetectorTest } from "./layers/EnvironmentDetectorTest.js";
|
|
16
|
-
import { ExecutorResolver } from "./services/ExecutorResolver.js";
|
|
17
8
|
import { ExecutorResolverLive } from "./layers/ExecutorResolverLive.js";
|
|
18
9
|
import { HistoryTrackerLive } from "./layers/HistoryTrackerLive.js";
|
|
19
10
|
import { HistoryTrackerTest } from "./layers/HistoryTrackerTest.js";
|
|
20
11
|
import { LoggerLive, resolveLogFile, resolveLogLevel } from "./layers/LoggerLive.js";
|
|
21
12
|
import { OutputPipelineLive } from "./layers/OutputPipelineLive.js";
|
|
22
13
|
import { APP_NAMESPACE, DATA_FALLBACK_DIR, PathResolutionLive } from "./layers/PathResolutionLive.js";
|
|
23
|
-
import { PerClientSessionMapReader, PerClientSessionMapWriter } from "./services/PerClientSessionMap.js";
|
|
24
14
|
import { PerClientSessionMapReaderLive, PerClientSessionMapWriterLive } from "./layers/PerClientSessionMapLive.js";
|
|
25
|
-
import { ProjectDiscovery } from "./services/ProjectDiscovery.js";
|
|
26
15
|
import { ProjectDiscoveryLive } from "./layers/ProjectDiscoveryLive.js";
|
|
27
16
|
import { ProjectDiscoveryTest } from "./layers/ProjectDiscoveryTest.js";
|
|
28
|
-
import { ProjectIdentity, resolveProjectIdentityFromCandidates } from "./services/ProjectIdentity.js";
|
|
29
17
|
import { ProjectIdentityLive, ProjectIdentityTest } from "./layers/ProjectIdentityLive.js";
|
|
30
|
-
import { AgentContext, RunContext, RunContext$, RunContextService } from "./services/RunContext.js";
|
|
31
|
-
import { probeHostMetadataFromEnv } from "./utils/host-metadata.js";
|
|
32
18
|
import { RunContextLive, RunContextTest } from "./layers/RunContextLive.js";
|
|
33
19
|
import { formatStrayOutputLine, formatTriageEffect } from "./lib/format-triage.js";
|
|
34
20
|
import { formatWrapupEffect } from "./lib/format-wrapup.js";
|
|
@@ -37,26 +23,40 @@ import migration$1 from "./migrations/0002_test_artifacts.js";
|
|
|
37
23
|
import { PROJECT_MIGRATIONS } from "./migrations/index.js";
|
|
38
24
|
import migration$2 from "./migrations/registry_0001_initial.js";
|
|
39
25
|
import migration$3 from "./migrations/session_map_0001_initial.js";
|
|
40
|
-
import { RegistryStore, SessionMapStore } from "./stores.js";
|
|
41
26
|
import { NodePlatformLayer, PlatformLive, makeSqliteStack } from "./platform.js";
|
|
42
27
|
import { endAgentEffect } from "./programs/end-agent.js";
|
|
43
28
|
import { DATA_DB_FILENAME, REGISTRY_DB_FILENAME, REGISTRY_STORE_OPTIONS, SESSIONS_DB_FILENAME, resolveHookPaths, resolveSessionMapPath } from "./programs/hook-paths.js";
|
|
44
29
|
import { RegistryStoreLive, SessionMapStoreLive, SidecarPlatformLive } from "./programs/platform-sidecar.js";
|
|
45
30
|
import { recordSessionEnd, recordSessionStart } from "./programs/record-session.js";
|
|
46
|
-
import { resolveSessionForRecording } from "./programs/resolve-session-for-recording.js";
|
|
47
31
|
import { dispatchRecordTddArtifactEffect, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect } from "./programs/record-tdd-artifact.js";
|
|
48
32
|
import { parseAndValidateTurnPayload, recordTurnEffect } from "./programs/record-turn.js";
|
|
49
33
|
import { recordRunWorkspaceChangesEffect } from "./programs/record-workspace-changes.js";
|
|
50
|
-
import { IDEMPOTENCY_ROOT_SENTINEL, deriveIdempotencyKey } from "./services/idempotency.js";
|
|
51
34
|
import { registerAgentEffect } from "./programs/register-agent.js";
|
|
35
|
+
import { resolveSessionForRecording } from "./programs/resolve-session-for-recording.js";
|
|
52
36
|
import { recoverSessionContextFromSessionEnv } from "./programs/session-env.js";
|
|
53
37
|
import { resolveProjectDir } from "./project-dir.js";
|
|
38
|
+
import { VitestAgentConfigFile } from "./services/Config.js";
|
|
39
|
+
import { DataReader } from "./services/DataReader.js";
|
|
40
|
+
import { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES } from "./services/DataStore.js";
|
|
41
|
+
import { DetailResolver } from "./services/DetailResolver.js";
|
|
42
|
+
import { DiscoveryRegistry } from "./services/DiscoveryRegistry.js";
|
|
43
|
+
import { EnvironmentDetector } from "./services/EnvironmentDetector.js";
|
|
44
|
+
import { ExecutorResolver } from "./services/ExecutorResolver.js";
|
|
45
|
+
import { HistoryTracker, historyKey } from "./services/HistoryTracker.js";
|
|
46
|
+
import { IDEMPOTENCY_ROOT_SENTINEL, deriveIdempotencyKey } from "./services/idempotency.js";
|
|
47
|
+
import { PerClientSessionMapReader, PerClientSessionMapWriter } from "./services/PerClientSessionMap.js";
|
|
48
|
+
import { ProjectDiscovery } from "./services/ProjectDiscovery.js";
|
|
49
|
+
import { ProjectIdentity, resolveProjectIdentityFromCandidates } from "./services/ProjectIdentity.js";
|
|
50
|
+
import { AgentContext, RunContext, RunContext$, RunContextService } from "./services/RunContext.js";
|
|
54
51
|
import { assembleHistoryRecord, assembleManifest } from "./sql/assemblers.js";
|
|
52
|
+
import { RegistryStore, SessionMapStore } from "./stores.js";
|
|
55
53
|
import { ensureMigrated } from "./utils/ensure-migrated.js";
|
|
56
54
|
import { computeFailureSignature, normalizeAssertionShape } from "./utils/failure-signature.js";
|
|
57
55
|
import { formatFatalError } from "./utils/format-fatal-error.js";
|
|
58
|
-
import {
|
|
56
|
+
import { probeHostMetadataFromEnv } from "./utils/host-metadata.js";
|
|
57
|
+
import { withPersistSavepoint, withPersistTransaction } from "./utils/persist-transaction.js";
|
|
59
58
|
import { DATABASE_FILENAME, resolveDataPath } from "./utils/resolve-data-path.js";
|
|
59
|
+
import { resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect } from "./utils/resolve-project-key-from-cwd.js";
|
|
60
60
|
import { resolveWorkspaceKey } from "./utils/resolve-workspace-key.js";
|
|
61
61
|
import { CURRENT_ENGINE_VERSION } from "./version.js";
|
|
62
62
|
|
package/layers/DataStoreLive.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES } from "../services/DataStore.js";
|
|
2
2
|
import { PersistBatch, withPersistSavepoint } from "../utils/persist-transaction.js";
|
|
3
|
-
import { Agent, AgentNotFoundError, BehaviorNotFoundError, DataStoreError, GoalNotFoundError, IdempotencyHit, IllegalStatusTransitionError, RegistrationConflictError, TddTaskAlreadyEndedError, TddTaskNotFoundError, coerceErrorText, extractSqlReason } from "@vitest-agent/sdk";
|
|
4
|
-
import { Clock, Effect, Layer, Option } from "effect";
|
|
3
|
+
import { Agent, AgentNotFoundError, BehaviorNotFoundError, DataStoreError, FileEditPayload, GoalNotFoundError, IdempotencyHit, IllegalStatusTransitionError, RegistrationConflictError, TddTaskAlreadyEndedError, TddTaskNotFoundError, ToolResultPayload, coerceErrorText, extractSqlReason } from "@vitest-agent/sdk";
|
|
4
|
+
import { Clock, Effect, Layer, Option, Schema, Struct } from "effect";
|
|
5
5
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
6
6
|
import { Buffer } from "node:buffer";
|
|
7
|
-
import {
|
|
7
|
+
import { SecureRandom } from "effect/crypto";
|
|
8
8
|
|
|
9
9
|
// src/layers/DataStoreLive.ts
|
|
10
10
|
/**
|
|
@@ -50,6 +50,8 @@ const behaviorRowFromDb = (row) => ({
|
|
|
50
50
|
createdAt: row.created_at
|
|
51
51
|
});
|
|
52
52
|
const boolToInt = (v) => v === void 0 ? null : v ? 1 : 0;
|
|
53
|
+
const decodeFileEditPayload = Schema.decodeUnknownEffect(Schema.fromJsonString(FileEditPayload.mapFields(Struct.omit(["type"]))));
|
|
54
|
+
const decodeToolResultPayload = Schema.decodeUnknownEffect(Schema.fromJsonString(ToolResultPayload.mapFields(Struct.omit(["type"]))));
|
|
53
55
|
/** @public */
|
|
54
56
|
const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
55
57
|
const sql = yield* SqlClient;
|
|
@@ -456,45 +458,42 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
456
458
|
if (input.turnNo !== void 0) yield* sql`INSERT INTO turns (session_id, turn_no, type, payload, occurred_at) VALUES (${input.sessionId}, ${input.turnNo}, ${input.type}, ${input.payload}, ${input.occurredAt})`;
|
|
457
459
|
else yield* sql`INSERT INTO turns (session_id, turn_no, type, payload, occurred_at) SELECT ${input.sessionId}, COALESCE(MAX(turn_no), 0) + 1, ${input.type}, ${input.payload}, ${input.occurredAt} FROM turns WHERE session_id = ${input.sessionId}`;
|
|
458
460
|
const turnId = (yield* sql`SELECT last_insert_rowid() as id`)[0].id;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
)
|
|
496
|
-
`;
|
|
497
|
-
}
|
|
461
|
+
const invalidPayload = (table) => (e) => new DataStoreError({
|
|
462
|
+
operation: "write",
|
|
463
|
+
table,
|
|
464
|
+
reason: `invalid turn payload: ${e.message}`
|
|
465
|
+
});
|
|
466
|
+
if (input.type === "file_edit") {
|
|
467
|
+
const payload = yield* decodeFileEditPayload(input.payload).pipe(Effect.mapError(invalidPayload("file_edits")));
|
|
468
|
+
const filePath = payload.file_path;
|
|
469
|
+
yield* sql`INSERT OR IGNORE INTO files (path) VALUES (${filePath})`;
|
|
470
|
+
const fileId = (yield* sql`SELECT id FROM files WHERE path = ${filePath}`)[0].id;
|
|
471
|
+
yield* sql`
|
|
472
|
+
INSERT INTO file_edits (turn_id, file_id, edit_kind, lines_added, lines_removed, diff)
|
|
473
|
+
VALUES (
|
|
474
|
+
${turnId},
|
|
475
|
+
${fileId},
|
|
476
|
+
${payload.edit_kind},
|
|
477
|
+
${payload.lines_added ?? null},
|
|
478
|
+
${payload.lines_removed ?? null},
|
|
479
|
+
${payload.diff ?? null}
|
|
480
|
+
)
|
|
481
|
+
`;
|
|
482
|
+
} else if (input.type === "tool_result") {
|
|
483
|
+
const payload = yield* decodeToolResultPayload(input.payload).pipe(Effect.mapError(invalidPayload("tool_invocations")));
|
|
484
|
+
const rawToolName = payload.tool_name;
|
|
485
|
+
const toolName = rawToolName.startsWith("mcp__") ? rawToolName.split("__").at(-1) ?? rawToolName : rawToolName;
|
|
486
|
+
yield* sql`
|
|
487
|
+
INSERT INTO tool_invocations (turn_id, tool_name, params_hash, result_summary, duration_ms, success)
|
|
488
|
+
VALUES (
|
|
489
|
+
${turnId},
|
|
490
|
+
${toolName},
|
|
491
|
+
${null},
|
|
492
|
+
${payload.result_summary ?? null},
|
|
493
|
+
${payload.duration_ms ?? null},
|
|
494
|
+
${boolToInt(payload.success) ?? 0}
|
|
495
|
+
)
|
|
496
|
+
`;
|
|
498
497
|
}
|
|
499
498
|
return turnId;
|
|
500
499
|
})).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => e instanceof DataStoreError ? e : new DataStoreError({
|
|
@@ -1295,7 +1294,7 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
1295
1294
|
const parentSession = parentRows[0].session_id;
|
|
1296
1295
|
if (parentSession !== input.sessionId && parentSession !== registeringParentSessionId) return yield* Effect.fail(new RegistrationConflictError({ reason: `parent agent ${input.parentAgentId} belongs to session ${parentSession}, which is neither the registering session ${input.sessionId} nor its parent session ${registeringParentSessionId ?? "(none)"}` }));
|
|
1297
1296
|
}
|
|
1298
|
-
const agentId = input.agentId ??
|
|
1297
|
+
const agentId = input.agentId ?? SecureRandom.uuidV4Unsafe();
|
|
1299
1298
|
yield* sql`
|
|
1300
1299
|
INSERT INTO agents (
|
|
1301
1300
|
agent_id, session_id, parent_agent_id, conversation_id, agent_type,
|
|
@@ -11,7 +11,7 @@ const ciEnvironment = (ci) => ci === "github-actions" ? "ci-github" : "ci-generi
|
|
|
11
11
|
*
|
|
12
12
|
* @remarks
|
|
13
13
|
* The agent/CI/human split is `@effected/env`'s `Audience.detect`, so the
|
|
14
|
-
* engine and every `@effected/cli` front end agree on who is running; this
|
|
14
|
+
* engine and every `@effected/cli-kit` front end agree on who is running; this
|
|
15
15
|
* only refines CI into GitHub Actions versus any other CI.
|
|
16
16
|
*
|
|
17
17
|
* @param runtime - the `@effected/env` runtime snapshot to classify
|
package/layers/LoggerLive.js
CHANGED
|
@@ -2,12 +2,12 @@ import { Layer, Logger, Option } from "effect";
|
|
|
2
2
|
import { CurrentRuntimeEnv } from "@effected/env";
|
|
3
3
|
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
4
4
|
import * as NodePath from "@effect/platform-node/NodePath";
|
|
5
|
-
import { CliLog } from "@effected/cli";
|
|
5
|
+
import { CliLog } from "@effected/cli-kit";
|
|
6
6
|
|
|
7
7
|
// src/layers/LoggerLive.ts
|
|
8
8
|
/**
|
|
9
9
|
* Create a structured JSON (NDJSON) logger layer for stderr, over
|
|
10
|
-
* `@effected/cli`'s `CliLog` in its diagnostics-only mode
|
|
10
|
+
* `@effected/cli-kit`'s `CliLog` in its diagnostics-only mode
|
|
11
11
|
* (`format: "json"`, `plainLogger: false`): the already-resolved level is
|
|
12
12
|
* passed as `level`, and no plain `CliLogger` is installed beside the sink, so
|
|
13
13
|
* each record prints once and nothing reads the audience or the terminal.
|
|
@@ -2,7 +2,7 @@ import { PerClientSessionMapReader, PerClientSessionMapWriter } from "../service
|
|
|
2
2
|
import { DataStoreError, extractSqlReason } from "@vitest-agent/sdk";
|
|
3
3
|
import { Effect, Layer, Option } from "effect";
|
|
4
4
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
5
|
-
import {
|
|
5
|
+
import { SecureRandom } from "effect/crypto";
|
|
6
6
|
import { basename } from "node:path";
|
|
7
7
|
|
|
8
8
|
// src/layers/PerClientSessionMapLive.ts
|
|
@@ -83,7 +83,7 @@ const PerClientSessionMapWriterLive = Layer.effect(PerClientSessionMapWriter, Ef
|
|
|
83
83
|
yield* sql`UPDATE conversation_map SET last_seen_at = ${now} WHERE transcript_uuid = ${uuid}`;
|
|
84
84
|
return existing[0].conversation_id;
|
|
85
85
|
}
|
|
86
|
-
const conversationId =
|
|
86
|
+
const conversationId = SecureRandom.uuidV4Unsafe();
|
|
87
87
|
yield* sql`
|
|
88
88
|
INSERT INTO conversation_map (transcript_uuid, transcript_path, conversation_id, first_seen_at, last_seen_at)
|
|
89
89
|
VALUES (${uuid}, ${transcriptPath}, ${conversationId}, ${now}, ${now})
|
|
@@ -102,7 +102,7 @@ const PerClientSessionMapWriterLive = Layer.effect(PerClientSessionMapWriter, Ef
|
|
|
102
102
|
mainAgentId: existing[0].main_agent_id,
|
|
103
103
|
conversationId: existing[0].conversation_id
|
|
104
104
|
};
|
|
105
|
-
const mainAgentId =
|
|
105
|
+
const mainAgentId = SecureRandom.uuidV4Unsafe();
|
|
106
106
|
const now = Date.now();
|
|
107
107
|
yield* sql`
|
|
108
108
|
INSERT INTO session_map (
|
|
@@ -2,6 +2,7 @@ import { ProjectDiscovery } from "../services/ProjectDiscovery.js";
|
|
|
2
2
|
import { DiscoveryError, NON_DISCOVERABLE_DIRS } from "@vitest-agent/sdk";
|
|
3
3
|
import { Effect, FileSystem, Layer, Path } from "effect";
|
|
4
4
|
import { GlobPattern, GlobPatternOptions } from "@effected/glob";
|
|
5
|
+
import { ErrorText } from "@effected/kit";
|
|
5
6
|
import { descend } from "@effected/walker";
|
|
6
7
|
|
|
7
8
|
// src/layers/ProjectDiscoveryLive.ts
|
|
@@ -17,39 +18,39 @@ function testFileToSource(testFile) {
|
|
|
17
18
|
return testFile.replace(/\.(test|spec)\.(ts|tsx)$/, ".$2");
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
* Every test file under `rootDir`,
|
|
21
|
-
* relative path. `@effected/walker`'s `descend` follows directory symlinks
|
|
21
|
+
* Every test file under `rootDir`, each joined to `rootDir` with the ambient
|
|
22
|
+
* `Path` (backslashes on win32), sorted by relative path. `@effected/walker`'s `descend` follows directory symlinks
|
|
22
23
|
* (as the former `fs.stat`-based walk did) under its ancestor-chain cycle
|
|
23
24
|
* guard, so a link back to an ancestor is skipped instead of recursing until
|
|
24
25
|
* the OS answers `ELOOP` and failing the whole discovery.
|
|
25
26
|
*/
|
|
26
27
|
const walkTestFiles = (rootDir) => Effect.gen(function* () {
|
|
27
28
|
const pattern = yield* GlobPattern.compile(TEST_FILE_GLOB, TEST_FILE_GLOB_OPTIONS);
|
|
28
|
-
const
|
|
29
|
+
const path = yield* Path.Path;
|
|
30
|
+
return (yield* descend(pattern, {
|
|
29
31
|
cwd: rootDir,
|
|
30
32
|
prune: SKIP_DIRS,
|
|
31
33
|
followSymlinks: true
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return relative.map((rel) => `${base}/${rel}`);
|
|
35
|
-
}).pipe(Effect.provide(Path.layer));
|
|
34
|
+
})).map((rel) => path.join(rootDir, rel));
|
|
35
|
+
});
|
|
36
36
|
/** @public */
|
|
37
37
|
const ProjectDiscoveryLive = Layer.effect(ProjectDiscovery, Effect.gen(function* () {
|
|
38
38
|
const fs = yield* FileSystem.FileSystem;
|
|
39
|
+
const path = yield* Path.Path;
|
|
39
40
|
const mapTestToSource = (testFile) => Effect.gen(function* () {
|
|
40
41
|
const sourceFile = testFileToSource(testFile);
|
|
41
42
|
return (yield* fs.exists(sourceFile)) ? [sourceFile] : [];
|
|
42
43
|
}).pipe(Effect.mapError((error) => new DiscoveryError({
|
|
43
44
|
operation: "stat",
|
|
44
45
|
path: testFile,
|
|
45
|
-
reason:
|
|
46
|
+
reason: ErrorText.describe(error)
|
|
46
47
|
})));
|
|
47
48
|
return {
|
|
48
49
|
discoverTestFiles: (rootDir) => Effect.gen(function* () {
|
|
49
|
-
const testFiles = yield* walkTestFiles(rootDir).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.mapError((error) => new DiscoveryError({
|
|
50
|
+
const testFiles = yield* walkTestFiles(rootDir).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path), Effect.mapError((error) => new DiscoveryError({
|
|
50
51
|
operation: "glob",
|
|
51
52
|
path: rootDir,
|
|
52
|
-
reason:
|
|
53
|
+
reason: ErrorText.describe(error)
|
|
53
54
|
})));
|
|
54
55
|
const entries = [];
|
|
55
56
|
for (const testFile of testFiles) {
|
|
@@ -6,11 +6,10 @@ import { ProjectIdentityNotResolvableError, ProjectIdentitySourceError, VitestAg
|
|
|
6
6
|
import { Effect, FileSystem, Layer, Option, Result } from "effect";
|
|
7
7
|
import { WorkspaceDiscovery } from "@effected/workspaces";
|
|
8
8
|
import { AppDirs } from "@effected/xdg";
|
|
9
|
+
import { ErrorText } from "@effected/kit";
|
|
9
10
|
import { LenientManifest } from "@effected/package-json";
|
|
10
11
|
|
|
11
12
|
// src/layers/ProjectIdentityLive.ts
|
|
12
|
-
/** The one-line text of a kit failure, for `ProjectIdentitySourceError.detail`. */
|
|
13
|
-
const detailOf = (cause) => cause instanceof Error && cause.message.length > 0 ? cause.message : String(cause);
|
|
14
13
|
/**
|
|
15
14
|
* The git remote is an OPTIONAL source: a repository with no
|
|
16
15
|
* `remote.origin.url`, a directory that is not a git checkout, or a machine
|
|
@@ -38,14 +37,14 @@ const readPackageRepoUrl = (packageJsonPath) => Effect.gen(function* () {
|
|
|
38
37
|
const raw = yield* (yield* FileSystem.FileSystem).readFileString(packageJsonPath).pipe(Effect.map(Option.some), Effect.catchTag("PlatformError", (error) => error.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(new ProjectIdentitySourceError({
|
|
39
38
|
source: "package-json",
|
|
40
39
|
path: packageJsonPath,
|
|
41
|
-
detail:
|
|
40
|
+
detail: ErrorText.describe(error),
|
|
42
41
|
cause: error
|
|
43
42
|
}))));
|
|
44
43
|
if (Option.isNone(raw)) return Option.none();
|
|
45
44
|
const manifest = yield* LenientManifest.parse(raw.value).pipe(Effect.map(Option.some), Effect.catchTag("PackageJsonSyntaxError", (cause) => cause.reason === "not-an-object" ? Effect.succeed(Option.none()) : Effect.fail(new ProjectIdentitySourceError({
|
|
46
45
|
source: "package-json",
|
|
47
46
|
path: packageJsonPath,
|
|
48
|
-
detail:
|
|
47
|
+
detail: ErrorText.describe(cause),
|
|
49
48
|
cause
|
|
50
49
|
}))));
|
|
51
50
|
return Option.flatMap(manifest, (m) => Option.fromNullishOr(repositoryUrlOf(m.repository)));
|
|
@@ -54,14 +53,14 @@ const collectCandidates = (workspaceRoot, options) => Effect.gen(function* () {
|
|
|
54
53
|
const tomlConfig = yield* (yield* VitestAgentConfigFile).loadOrDefault(new VitestAgentConfig({})).pipe(Effect.mapError((cause) => new ProjectIdentitySourceError({
|
|
55
54
|
source: "toml",
|
|
56
55
|
path: cause._tag === "ConfigValidationError" ? Option.getOrElse(cause.path, () => workspaceRoot) : cause.path ?? workspaceRoot,
|
|
57
|
-
detail:
|
|
56
|
+
detail: ErrorText.describe(cause),
|
|
58
57
|
cause
|
|
59
58
|
})));
|
|
60
59
|
const gitRemote = yield* readGitRemote(workspaceRoot);
|
|
61
60
|
const root = (yield* (yield* WorkspaceDiscovery).listPackagesIn(workspaceRoot).pipe(Effect.catchTag("WorkspaceRootNotFoundError", () => Effect.succeed([])), Effect.mapError((cause) => new ProjectIdentitySourceError({
|
|
62
61
|
source: "workspace",
|
|
63
62
|
path: cause._tag === "WorkspacePatternError" ? cause.root : cause.path,
|
|
64
|
-
detail:
|
|
63
|
+
detail: ErrorText.describe(cause),
|
|
65
64
|
cause
|
|
66
65
|
})))).find((pkg) => pkg.isRootWorkspace);
|
|
67
66
|
const packageJsonRepoUrl = root ? yield* readPackageRepoUrl(root.packageJsonPath) : Option.none();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/engine",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared services, layers, SQLite data layer, migrations and platform resolution for the vitest-agent CLI and MCP server.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,25 +40,26 @@
|
|
|
40
40
|
"./package.json": "./package.json"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@effect/platform-node": "
|
|
44
|
-
"@effect/sql-sqlite-node": "
|
|
45
|
-
"@effected/app": "^0.
|
|
46
|
-
"@effected/cli": "^0.
|
|
47
|
-
"@effected/config-file": "^0.
|
|
48
|
-
"@effected/
|
|
49
|
-
"@effected/
|
|
50
|
-
"@effected/
|
|
51
|
-
"@effected/
|
|
52
|
-
"@effected/
|
|
53
|
-
"@effected/
|
|
54
|
-
"@effected/
|
|
55
|
-
"@effected/
|
|
56
|
-
"@effected/
|
|
57
|
-
"@effected/
|
|
58
|
-
"@effected/
|
|
59
|
-
"@effected/
|
|
60
|
-
"@
|
|
61
|
-
"
|
|
43
|
+
"@effect/platform-node": "~4.1.2",
|
|
44
|
+
"@effect/sql-sqlite-node": "~4.1.2",
|
|
45
|
+
"@effected/app": "^0.31.0",
|
|
46
|
+
"@effected/cli-kit": "^0.26.0",
|
|
47
|
+
"@effected/config-file": "^0.24.0",
|
|
48
|
+
"@effected/env": "^0.10.0",
|
|
49
|
+
"@effected/git": "^0.30.0",
|
|
50
|
+
"@effected/glob": "^0.18.0",
|
|
51
|
+
"@effected/jsonc": "^0.23.0",
|
|
52
|
+
"@effected/kit": "^0.12.0",
|
|
53
|
+
"@effected/memfs": "^0.23.0",
|
|
54
|
+
"@effected/package-json": "^0.29.0",
|
|
55
|
+
"@effected/store": "^0.23.0",
|
|
56
|
+
"@effected/toml": "^0.19.0",
|
|
57
|
+
"@effected/walker": "^0.25.0",
|
|
58
|
+
"@effected/workspaces": "^0.42.0",
|
|
59
|
+
"@effected/xdg": "^0.19.0",
|
|
60
|
+
"@effected/yaml": "^0.28.0",
|
|
61
|
+
"@vitest-agent/sdk": "9.0.3",
|
|
62
|
+
"effect": "~4.1.2"
|
|
62
63
|
},
|
|
63
64
|
"engines": {
|
|
64
65
|
"node": ">=24.11.0"
|
package/platform.js
CHANGED
|
@@ -15,8 +15,8 @@ import { Store } from "@effected/store";
|
|
|
15
15
|
// src/platform.ts
|
|
16
16
|
/**
|
|
17
17
|
* The Node platform services every SQLite stack and every Live layer share.
|
|
18
|
-
* `NodeServices.layer` aggregates
|
|
19
|
-
*
|
|
18
|
+
* `NodeServices.layer` aggregates ChildProcessSpawner | FileSystem | Path |
|
|
19
|
+
* Stdio | Terminal; since Effect 4.1 it carries no crypto service.
|
|
20
20
|
* @public
|
|
21
21
|
*/
|
|
22
22
|
const NodePlatformLayer = NodeServices.layer;
|
package/programs/hook-paths.js
CHANGED
|
@@ -4,9 +4,9 @@ import { LEDGER_OPTIONS } from "../stores.js";
|
|
|
4
4
|
import { ProjectIdentityNotResolvableError } from "@vitest-agent/sdk";
|
|
5
5
|
import { Effect, FileSystem, Layer, Option, Path } from "effect";
|
|
6
6
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
7
|
+
import { LaunchContext } from "@effected/kit";
|
|
7
8
|
import { Store } from "@effected/store";
|
|
8
9
|
import { AppStore } from "@effected/app";
|
|
9
|
-
import { LaunchContext } from "@effected/engine";
|
|
10
10
|
|
|
11
11
|
// src/programs/hook-paths.ts
|
|
12
12
|
/**
|
|
@@ -72,7 +72,7 @@ const hookAppDirs = (env) => {
|
|
|
72
72
|
*
|
|
73
73
|
* Precedence: the `CLAUDE_PLUGIN_DATA` env var, then
|
|
74
74
|
* `VITEST_AGENT_SESSION_MAP_DIR`, then `~/.vitest-agent/` (`HOME`, then
|
|
75
|
-
* `USERPROFILE`). The two variables are read through `@effected/
|
|
75
|
+
* `USERPROFILE`). The two variables are read through `@effected/kit`'s
|
|
76
76
|
* `LaunchContext.resolve`: a value is trimmed, and a blank or unsubstituted
|
|
77
77
|
* `${...}` placeholder value counts as unset. The chosen directory is created. Fails with
|
|
78
78
|
* `ProjectIdentityNotResolvableError` when no home directory is resolvable.
|
|
@@ -5,8 +5,8 @@ import { LoggerLive } from "../layers/LoggerLive.js";
|
|
|
5
5
|
import { PerClientSessionMapWriterLive } from "../layers/PerClientSessionMapLive.js";
|
|
6
6
|
import { RunContextLive } from "../layers/RunContextLive.js";
|
|
7
7
|
import migration from "../migrations/session_map_0001_initial.js";
|
|
8
|
-
import { CONNECTION_OPTIONS, LEDGER_OPTIONS, RegistryStore, SessionMapStore } from "../stores.js";
|
|
9
8
|
import { NodePlatformLayer, makeSqliteStack } from "../platform.js";
|
|
9
|
+
import { CONNECTION_OPTIONS, LEDGER_OPTIONS, RegistryStore, SessionMapStore } from "../stores.js";
|
|
10
10
|
import { REGISTRY_STORE_OPTIONS } from "./hook-paths.js";
|
|
11
11
|
import { Layer } from "effect";
|
|
12
12
|
import { Store } from "@effected/store";
|
package/programs/record-turn.js
CHANGED
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
import { DataStore } from "../services/DataStore.js";
|
|
2
2
|
import { resolveSessionForRecording } from "./resolve-session-for-recording.js";
|
|
3
3
|
import { TurnPayload } from "@vitest-agent/sdk";
|
|
4
|
-
import { Effect, Schema } from "effect";
|
|
5
|
-
import { ErrorText } from "@effected/engine";
|
|
4
|
+
import { Effect, Result, Schema } from "effect";
|
|
6
5
|
|
|
7
6
|
// src/programs/record-turn.ts
|
|
7
|
+
const decodeTurnPayload = Schema.decodeUnknownResult(Schema.fromJsonString(TurnPayload));
|
|
8
8
|
/**
|
|
9
9
|
* Parse a raw JSON string and validate it against the `TurnPayload` schema
|
|
10
10
|
* without throwing.
|
|
11
11
|
*
|
|
12
|
+
* Text that is not JSON at all reads `Invalid JSON: …`; JSON whose body is
|
|
13
|
+
* not a `TurnPayload` reads `Invalid TurnPayload: …`.
|
|
14
|
+
*
|
|
12
15
|
* @param raw - the JSON text delivered by the hook
|
|
13
16
|
* @returns the decoded payload, or an error describing why it was rejected
|
|
14
17
|
* @public
|
|
15
18
|
*/
|
|
16
19
|
const parseAndValidateTurnPayload = (raw) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
parsed = JSON.parse(raw);
|
|
20
|
-
} catch (e) {
|
|
21
|
-
return {
|
|
22
|
-
ok: false,
|
|
23
|
-
error: `Invalid JSON: ${ErrorText.describe(e)}`
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
const decoded = Schema.decodeUnknownResult(TurnPayload)(parsed);
|
|
27
|
-
if (decoded._tag === "Failure") return {
|
|
28
|
-
ok: false,
|
|
29
|
-
error: `Invalid TurnPayload: ${decoded.failure.message}`
|
|
30
|
-
};
|
|
31
|
-
return {
|
|
20
|
+
const decoded = decodeTurnPayload(raw);
|
|
21
|
+
if (Result.isSuccess(decoded)) return {
|
|
32
22
|
ok: true,
|
|
33
23
|
payload: decoded.success
|
|
34
24
|
};
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: `${decoded.failure.issue._tag === "Encoding" ? "Invalid JSON" : "Invalid TurnPayload"}: ${decoded.failure.message}`
|
|
28
|
+
};
|
|
35
29
|
};
|
|
36
30
|
/**
|
|
37
31
|
* Validate a turn payload and write it as a `turns` row under the session
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DataReader } from "../services/DataReader.js";
|
|
2
2
|
import { DataStore } from "../services/DataStore.js";
|
|
3
|
+
import { deriveIdempotencyKey } from "../services/idempotency.js";
|
|
3
4
|
import { PerClientSessionMapWriter } from "../services/PerClientSessionMap.js";
|
|
4
5
|
import { RunContextService } from "../services/RunContext.js";
|
|
5
|
-
import { deriveIdempotencyKey } from "../services/idempotency.js";
|
|
6
6
|
import { Effect, Option } from "effect";
|
|
7
7
|
|
|
8
8
|
// src/programs/register-agent.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DataReader } from "../services/DataReader.js";
|
|
2
2
|
import { DataStore } from "../services/DataStore.js";
|
|
3
|
-
import { Effect, FileSystem, Option } from "effect";
|
|
3
|
+
import { Effect, FileSystem, Option, Path } from "effect";
|
|
4
4
|
import { LenientManifest } from "@effected/package-json";
|
|
5
5
|
|
|
6
6
|
// src/programs/resolve-session-for-recording.ts
|
|
@@ -17,12 +17,13 @@ const resolveSessionForRecording = (input) => Effect.gen(function* () {
|
|
|
17
17
|
const reader = yield* DataReader;
|
|
18
18
|
const store = yield* DataStore;
|
|
19
19
|
const fs = yield* FileSystem.FileSystem;
|
|
20
|
+
const path = yield* Path.Path;
|
|
20
21
|
const exact = yield* reader.getSessionByChatId(input.chatId);
|
|
21
22
|
if (Option.isSome(exact)) return exact.value;
|
|
22
23
|
const synthetic = yield* reader.findSessionsByChatPrefix(`${input.chatId}-subagent-`);
|
|
23
24
|
if (synthetic.length > 0) return synthetic[0];
|
|
24
25
|
const cwd = input.cwd;
|
|
25
|
-
const project = input.project ?? (yield* readPackageName(fs, cwd)) ?? "unknown";
|
|
26
|
+
const project = input.project ?? (yield* readPackageName(fs, path.join(cwd, "package.json"))) ?? "unknown";
|
|
26
27
|
yield* store.upsertSession({
|
|
27
28
|
chatId: input.chatId,
|
|
28
29
|
project,
|
|
@@ -40,6 +41,6 @@ const resolveSessionForRecording = (input) => Effect.gen(function* () {
|
|
|
40
41
|
* Any read or parse failure resolves to `undefined` — the caller falls
|
|
41
42
|
* back to `"unknown"`, exactly as the former sync `readFileSync` probe did.
|
|
42
43
|
*/
|
|
43
|
-
const readPackageName = (fs,
|
|
44
|
+
const readPackageName = (fs, packageJsonPath) => fs.readFileString(packageJsonPath).pipe(Effect.flatMap(LenientManifest.parse), Effect.map((manifest) => manifest.name), Effect.orElseSucceed(() => void 0));
|
|
44
45
|
|
|
45
46
|
export { resolveSessionForRecording };
|
package/project-dir.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LaunchContext } from "@effected/
|
|
1
|
+
import { LaunchContext } from "@effected/kit";
|
|
2
2
|
|
|
3
3
|
// src/project-dir.ts
|
|
4
4
|
/**
|
|
@@ -16,7 +16,7 @@ const PROJECT_DIR_ENV_KEYS = [
|
|
|
16
16
|
* Precedence: `VITEST_AGENT_PROJECT_DIR` (hook-driven override), then
|
|
17
17
|
* `VITEST_AGENT_REPORTER_PROJECT_DIR` (the plugin-spawned MCP server's
|
|
18
18
|
* hand-off), then `CLAUDE_PROJECT_DIR` (Claude Code's own convention),
|
|
19
|
-
* then `cwd`. Delegates to `@effected/
|
|
19
|
+
* then `cwd`. Delegates to `@effected/kit`'s `LaunchContext.projectDir`:
|
|
20
20
|
* a value is trimmed, and an empty or whitespace-only value, or one a plugin
|
|
21
21
|
* host left as a literal unsubstituted `${...}` placeholder, counts as unset.
|
|
22
22
|
* Pure: both the env map and the working directory are injected by the caller.
|
package/testing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DataReader } from "./services/DataReader.js";
|
|
2
1
|
import { DataStore } from "./services/DataStore.js";
|
|
3
|
-
import { PROJECT_MIGRATIONS } from "./migrations/index.js";
|
|
4
2
|
import { DataStoreTestLayer, makeTestLayer } from "./testing/layers.js";
|
|
3
|
+
import { PROJECT_MIGRATIONS } from "./migrations/index.js";
|
|
4
|
+
import { DataReader } from "./services/DataReader.js";
|
|
5
5
|
import { Agent, AgentId, AgentNotFoundError, AgentReport, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, ChatId, CoverageBaselines, CoverageReport, DataStoreError, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, RegistrationConflictError, RunRetentionPolicy, TddTaskAlreadyEndedError, TddTaskNotFoundError, TrendEntry, TrendRecord } from "@vitest-agent/sdk";
|
|
6
6
|
import { Effect, Layer } from "effect";
|
|
7
7
|
|
package/tsdoc-metadata.json
CHANGED
package/utils/ensure-migrated.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LoggerLive } from "../layers/LoggerLive.js";
|
|
2
2
|
import { PROJECT_MIGRATIONS } from "../migrations/index.js";
|
|
3
|
-
import { BUSY_TIMEOUT, LEDGER_OPTIONS, applyConnectionPragmas } from "../stores.js";
|
|
4
3
|
import { NodePlatformLayer } from "../platform.js";
|
|
4
|
+
import { BUSY_TIMEOUT, LEDGER_OPTIONS, applyConnectionPragmas } from "../stores.js";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
import { Store } from "@effected/store";
|
|
7
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Cause } from "effect";
|
|
2
|
-
import { CliFailure, Render } from "@effected/cli";
|
|
2
|
+
import { CliFailure, Render } from "@effected/cli-kit";
|
|
3
3
|
|
|
4
4
|
// src/utils/format-fatal-error.ts
|
|
5
5
|
const ISSUE_URL = "https://github.com/spencerbeggs/vitest-agent/issues";
|
|
@@ -18,7 +18,7 @@ const CONTEXT = Render.contextOf({ audience: "human" });
|
|
|
18
18
|
* is (one from `Effect.runPromiseExit` keeps every reason and the span
|
|
19
19
|
* trail), and anything else is rendered as a defect, `Cause.die(err)`: an
|
|
20
20
|
* unexpected throw is a bug to report, and a defect is the shape whose
|
|
21
|
-
* document carries the stack. `@effected/cli`'s `CliFailure.toDoc` keeps
|
|
21
|
+
* document carries the stack. `@effected/cli-kit`'s `CliFailure.toDoc` keeps
|
|
22
22
|
* the program's own frames, hides `node_modules` and runtime frames,
|
|
23
23
|
* follows an `Error.cause` chain, and renders a non-`Error` value as its
|
|
24
24
|
* text.
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { repositoryUrlOf } from "./package-manifest.js";
|
|
2
2
|
import { gitUrlToProjectKey } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, FileSystem, Option, Path, Result } from "effect";
|
|
3
|
+
import { Effect, FileSystem, Layer, Option, Path, Result } from "effect";
|
|
4
|
+
import * as NodePath from "@effect/platform-node/NodePath";
|
|
4
5
|
import { AppDirs } from "@effected/xdg";
|
|
5
|
-
import { join } from "node:path";
|
|
6
6
|
import { Walker } from "@effected/walker";
|
|
7
7
|
import { LenientManifest } from "@effected/package-json";
|
|
8
|
-
import {
|
|
9
|
-
import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
|
|
8
|
+
import { NodeSyncFileSystem } from "@effected/memfs/node-sync";
|
|
10
9
|
|
|
11
10
|
// src/utils/resolve-project-key-from-cwd.ts
|
|
12
11
|
const PACKAGE_JSON = "package.json";
|
|
13
|
-
const findNearestPackageJson = (startDir) => {
|
|
14
|
-
const dirs = Walker.ascendSync(startDir, { path: nodeWalkerSyncOps.path });
|
|
15
|
-
return Option.getOrNull(Walker.findUpwardSync(dirs, (dir) => [join(dir, PACKAGE_JSON)], nodeWalkerSyncOps));
|
|
16
|
-
};
|
|
17
12
|
/**
|
|
18
13
|
* The key a `package.json` body names: the canonical `host__path` form of
|
|
19
14
|
* `repository.url` when present and canonicalizable, else the normalized
|
|
@@ -32,10 +27,11 @@ const keyFromPackageJson = (content) => {
|
|
|
32
27
|
if (manifest.name !== void 0) return Result.getOrNull(AppDirs.segment(manifest.name));
|
|
33
28
|
return null;
|
|
34
29
|
};
|
|
35
|
-
const basenameKey = (cwd) => {
|
|
36
|
-
const basename =
|
|
37
|
-
return Result.getOrElse(AppDirs.segment(basename), () => "anonymous-project");
|
|
30
|
+
const basenameKey = (path, cwd) => {
|
|
31
|
+
const basename = path.basename(cwd);
|
|
32
|
+
return basename.length === 0 ? "anonymous-project" : Result.getOrElse(AppDirs.segment(basename), () => "anonymous-project");
|
|
38
33
|
};
|
|
34
|
+
const NodeSyncPlatform = Layer.merge(NodeSyncFileSystem.layer, NodePath.layer);
|
|
39
35
|
/**
|
|
40
36
|
* Compute the project key for a workspace by reading `package.json`
|
|
41
37
|
* fields directly. Returns the canonical `host__path` form when a git
|
|
@@ -45,26 +41,17 @@ const basenameKey = (cwd) => {
|
|
|
45
41
|
* (or `"anonymous-project"`) so callers don't have to handle the
|
|
46
42
|
* empty case.
|
|
47
43
|
*
|
|
48
|
-
* Synchronous and reads the real disk through `node:fs
|
|
49
|
-
*
|
|
50
|
-
*
|
|
44
|
+
* Synchronous and reads the real disk through `node:fs`: it runs
|
|
45
|
+
* {@link resolveProjectKeyFromCwdEffect} under `Effect.runSync` over a
|
|
46
|
+
* read-only synchronous `FileSystem` and the host's Node `Path`. Effect
|
|
47
|
+
* callers that already hold a `FileSystem` and `Path` use the Effect form
|
|
48
|
+
* directly.
|
|
51
49
|
* @public
|
|
52
50
|
*/
|
|
53
|
-
const resolveProjectKeyFromCwd = (cwd) =>
|
|
54
|
-
const pkgPath = findNearestPackageJson(cwd);
|
|
55
|
-
if (pkgPath !== null) {
|
|
56
|
-
let content = null;
|
|
57
|
-
try {
|
|
58
|
-
content = readFileSync(pkgPath, "utf-8");
|
|
59
|
-
} catch {}
|
|
60
|
-
const key = content === null ? null : keyFromPackageJson(content);
|
|
61
|
-
if (key !== null) return key;
|
|
62
|
-
}
|
|
63
|
-
return basenameKey(cwd);
|
|
64
|
-
};
|
|
51
|
+
const resolveProjectKeyFromCwd = (cwd) => Effect.runSync(resolveProjectKeyFromCwdEffect(cwd).pipe(Effect.provide(NodeSyncPlatform)));
|
|
65
52
|
/**
|
|
66
53
|
* {@link resolveProjectKeyFromCwd} read through the ambient
|
|
67
|
-
* `FileSystem`
|
|
54
|
+
* `FileSystem` and `Path` services instead of `node:fs` — same upward walk to the
|
|
68
55
|
* nearest `package.json` (`@effected/walker`'s `Walker.ascend` +
|
|
69
56
|
* `Walker.findUpward`), same precedence, same basename fallback.
|
|
70
57
|
*
|
|
@@ -77,11 +64,12 @@ const resolveProjectKeyFromCwd = (cwd) => {
|
|
|
77
64
|
*/
|
|
78
65
|
const resolveProjectKeyFromCwdEffect = (cwd) => Effect.gen(function* () {
|
|
79
66
|
const fs = yield* FileSystem.FileSystem;
|
|
67
|
+
const path = yield* Path.Path;
|
|
80
68
|
const dirs = yield* Walker.ascend(cwd);
|
|
81
|
-
const found = yield* Walker.findUpward(dirs, (dir) => [join(dir, PACKAGE_JSON)]);
|
|
82
|
-
if (Option.isNone(found)) return basenameKey(cwd);
|
|
69
|
+
const found = yield* Walker.findUpward(dirs, (dir) => [path.join(dir, PACKAGE_JSON)]);
|
|
70
|
+
if (Option.isNone(found)) return basenameKey(path, cwd);
|
|
83
71
|
const content = yield* fs.readFileString(found.value).pipe(Effect.orElseSucceed(() => null));
|
|
84
|
-
return (content === null ? null : keyFromPackageJson(content)) ?? basenameKey(cwd);
|
|
85
|
-
})
|
|
72
|
+
return (content === null ? null : keyFromPackageJson(content)) ?? basenameKey(path, cwd);
|
|
73
|
+
});
|
|
86
74
|
|
|
87
75
|
export { resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect };
|
package/version.js
CHANGED