@vitest-agent/sdk 3.1.2 → 4.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/README.md +23 -19
- package/dispatch.d.ts +25 -6
- package/formatters/markdown.js +1 -0
- package/formatters/terminal.js +4 -3
- package/index.d.ts +448 -2341
- package/index.js +10 -68
- package/internal-inject-env.js +5 -7
- package/package.json +3 -19
- package/sidecar-dispatch.js +7 -5
- package/utils/format-console.js +9 -10
- package/utils/format-terminal.js +7 -7
- package/utils/posix-path.js +52 -0
- package/utils/test-location.js +11 -5
- package/version.js +17 -0
- package/layers/ConfigLive.js +0 -46
- package/layers/DataReaderLive.js +0 -1923
- package/layers/DataStoreLive.js +0 -1287
- package/layers/DetailResolverLive.js +0 -9
- package/layers/DiscoveryRegistryLive.js +0 -70
- package/layers/EnvironmentDetectorLive.js +0 -21
- package/layers/EnvironmentDetectorTest.js +0 -13
- package/layers/ExecutorResolverLive.js +0 -9
- package/layers/FormatSelectorLive.js +0 -20
- package/layers/HistoryTrackerLive.js +0 -49
- package/layers/HistoryTrackerTest.js +0 -25
- package/layers/LoggerLive.js +0 -64
- package/layers/OutputPipelineLive.js +0 -13
- package/layers/OutputRendererLive.js +0 -28
- package/layers/PathResolutionLive.js +0 -40
- package/layers/PerClientSessionMapLive.js +0 -145
- package/layers/ProjectDiscoveryLive.js +0 -68
- package/layers/ProjectDiscoveryTest.js +0 -12
- package/layers/ProjectIdentityLive.js +0 -96
- package/layers/RunContextLive.js +0 -81
- package/lib/format-triage.js +0 -103
- package/lib/format-wrapup.js +0 -67
- package/migrations/0001_initial.js +0 -824
- package/migrations/0002_test_artifacts.js +0 -30
- package/migrations/index.js +0 -26
- package/migrations/registry_0001_initial.js +0 -39
- package/migrations/session_map_0001_initial.js +0 -38
- package/services/Config.js +0 -15
- package/services/DataReader.js +0 -8
- package/services/DataStore.js +0 -15
- package/services/DetailResolver.js +0 -8
- package/services/DiscoveryRegistry.js +0 -8
- package/services/EnvironmentDetector.js +0 -8
- package/services/ExecutorResolver.js +0 -8
- package/services/FormatSelector.js +0 -8
- package/services/HistoryTracker.js +0 -20
- package/services/OutputRenderer.js +0 -8
- package/services/PerClientSessionMap.js +0 -22
- package/services/ProjectDiscovery.js +0 -8
- package/services/ProjectIdentity.js +0 -63
- package/services/RunContext.js +0 -53
- package/services/idempotency.js +0 -58
- package/sql/assemblers.js +0 -69
- package/testing/layers.js +0 -21
- package/testing.d.ts +0 -1862
- package/testing.js +0 -254
- package/utils/ensure-migrated.js +0 -52
- package/utils/failure-signature.js +0 -34
- package/utils/resolve-data-path.js +0 -65
- package/utils/resolve-project-key-from-cwd.js +0 -53
- package/utils/resolve-workspace-key.js +0 -41
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { ProjectIdentityNotResolvableError } from "../errors/ProjectIdentityError.js";
|
|
2
|
-
import { VitestAgentConfig } from "../schemas/Config.js";
|
|
3
|
-
import { VitestAgentConfigFile } from "../services/Config.js";
|
|
4
|
-
import { ProjectIdentity, resolveProjectIdentityFromCandidates } from "../services/ProjectIdentity.js";
|
|
5
|
-
import { Effect, FileSystem, Layer, Option } from "effect";
|
|
6
|
-
import { WorkspaceDiscovery } from "@effected/workspaces";
|
|
7
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
8
|
-
|
|
9
|
-
//#region src/layers/ProjectIdentityLive.ts
|
|
10
|
-
const readGitRemote = (cwd) => Effect.gen(function* () {
|
|
11
|
-
const trimmed = (yield* (yield* ChildProcessSpawner.ChildProcessSpawner).string(ChildProcess.make("git", [
|
|
12
|
-
"config",
|
|
13
|
-
"--get",
|
|
14
|
-
"remote.origin.url"
|
|
15
|
-
]).pipe(ChildProcess.setCwd(cwd)))).trim();
|
|
16
|
-
return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
|
|
17
|
-
}).pipe(Effect.catch(() => Effect.succeed(Option.none())));
|
|
18
|
-
const readPackageRepoUrl = (packageJsonPath) => Effect.gen(function* () {
|
|
19
|
-
const raw = yield* (yield* FileSystem.FileSystem).readFileString(packageJsonPath).pipe(Effect.catch(() => Effect.succeed("")));
|
|
20
|
-
if (raw.length === 0) return Option.none();
|
|
21
|
-
try {
|
|
22
|
-
const repo = JSON.parse(raw).repository;
|
|
23
|
-
if (typeof repo === "string" && repo.trim().length > 0) return Option.some(repo.trim());
|
|
24
|
-
if (repo !== null && typeof repo === "object" && "url" in repo && typeof repo.url === "string") {
|
|
25
|
-
const url = repo.url.trim();
|
|
26
|
-
return url.length > 0 ? Option.some(url) : Option.none();
|
|
27
|
-
}
|
|
28
|
-
} catch {}
|
|
29
|
-
return Option.none();
|
|
30
|
-
});
|
|
31
|
-
const collectCandidates = (workspaceRoot, options) => Effect.gen(function* () {
|
|
32
|
-
const tomlConfig = yield* (yield* VitestAgentConfigFile).loadOrDefault(new VitestAgentConfig({})).pipe(Effect.catch(() => Effect.succeed(new VitestAgentConfig({}))));
|
|
33
|
-
const gitRemote = yield* readGitRemote(workspaceRoot);
|
|
34
|
-
const root = (yield* (yield* WorkspaceDiscovery).listPackagesIn(workspaceRoot).pipe(Effect.catch(() => Effect.succeed([])))).find((pkg) => pkg.isRootWorkspace);
|
|
35
|
-
const packageJsonRepoUrl = root ? yield* readPackageRepoUrl(root.packageJsonPath) : Option.none();
|
|
36
|
-
const candidates = {};
|
|
37
|
-
if (options.projectId !== void 0) candidates.explicit = options.projectId;
|
|
38
|
-
if (tomlConfig.projectKey !== void 0) candidates.toml = tomlConfig.projectKey;
|
|
39
|
-
const remoteValue = Option.getOrUndefined(gitRemote);
|
|
40
|
-
if (remoteValue !== void 0) candidates.gitRemote = remoteValue;
|
|
41
|
-
const repoUrlValue = Option.getOrUndefined(packageJsonRepoUrl);
|
|
42
|
-
if (repoUrlValue !== void 0) candidates.packageJsonRepoUrl = repoUrlValue;
|
|
43
|
-
if (root?.name !== void 0) candidates.packageJsonName = root.name;
|
|
44
|
-
return candidates;
|
|
45
|
-
});
|
|
46
|
-
const candidatesToTriedList = (candidates) => [
|
|
47
|
-
{
|
|
48
|
-
source: "explicit",
|
|
49
|
-
reason: candidates.explicit ? "value rejected by normalizer" : "not provided in plugin options"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
source: "toml",
|
|
53
|
-
reason: candidates.toml ? "value rejected by normalizer" : "not configured in vitest-agent.config.toml"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
source: "git-remote",
|
|
57
|
-
reason: candidates.gitRemote ? "URL not canonicalizable" : "git command produced no remote.origin.url"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
source: "package-repository",
|
|
61
|
-
reason: candidates.packageJsonRepoUrl ? "URL not canonicalizable" : "no repository.url in root package.json"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
source: "package-name",
|
|
65
|
-
reason: candidates.packageJsonName ? "value rejected by normalizer" : "no name in root package.json"
|
|
66
|
-
}
|
|
67
|
-
];
|
|
68
|
-
/**
|
|
69
|
-
* Live layer that wires the resolver into real I/O. Requires
|
|
70
|
-
* `FileSystem`, `CommandExecutor`, `WorkspaceDiscovery`, and
|
|
71
|
-
* `VitestAgentConfigFile`. The platform context comes from
|
|
72
|
-
* `NodeServices.layer` at the entry point.
|
|
73
|
-
* @public
|
|
74
|
-
*/
|
|
75
|
-
const ProjectIdentityLive = Layer.effect(ProjectIdentity, Effect.gen(function* () {
|
|
76
|
-
const fs = yield* FileSystem.FileSystem;
|
|
77
|
-
const discovery = yield* WorkspaceDiscovery;
|
|
78
|
-
const configFile = yield* VitestAgentConfigFile;
|
|
79
|
-
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
80
|
-
return { resolve: (workspaceRoot, options = {}) => collectCandidates(workspaceRoot, options).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(WorkspaceDiscovery, discovery), Effect.provideService(VitestAgentConfigFile, configFile), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.flatMap((candidates) => {
|
|
81
|
-
const result = resolveProjectIdentityFromCandidates(candidates);
|
|
82
|
-
if (result === null) return Effect.fail(new ProjectIdentityNotResolvableError({ tried: candidatesToTriedList(candidates) }));
|
|
83
|
-
return Effect.succeed(result);
|
|
84
|
-
})) };
|
|
85
|
-
}));
|
|
86
|
-
/**
|
|
87
|
-
* Build a Test layer that returns a fixed `ResolvedIdentity` (or
|
|
88
|
-
* fails with `ProjectIdentityNotResolvableError`). Use in unit tests
|
|
89
|
-
* that depend on `ProjectIdentity` but don't want to wire upstream
|
|
90
|
-
* services.
|
|
91
|
-
* @public
|
|
92
|
-
*/
|
|
93
|
-
const ProjectIdentityTest = (result) => Layer.succeed(ProjectIdentity, { resolve: () => result instanceof ProjectIdentityNotResolvableError ? Effect.fail(result) : Effect.succeed(result) });
|
|
94
|
-
|
|
95
|
-
//#endregion
|
|
96
|
-
export { ProjectIdentityLive, ProjectIdentityTest };
|
package/layers/RunContextLive.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { AgentContext, RunContext, RunContextService } from "../services/RunContext.js";
|
|
2
|
-
import { probeHostMetadataFromEnv } from "../utils/probe-host-metadata.js";
|
|
3
|
-
import { Effect, Layer, Option } from "effect";
|
|
4
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
5
|
-
|
|
6
|
-
//#region src/layers/RunContextLive.ts
|
|
7
|
-
const runGit = (spawner, cwd, args) => spawner.string(ChildProcess.make("git", args).pipe(ChildProcess.setCwd(cwd))).pipe(Effect.map((output) => {
|
|
8
|
-
const trimmed = output.trim();
|
|
9
|
-
return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
|
|
10
|
-
}), Effect.catch(() => Effect.succeed(Option.none())));
|
|
11
|
-
const captureRunContextEffect = (cwd, spawner, env) => Effect.gen(function* () {
|
|
12
|
-
const branch = yield* runGit(spawner, cwd, [
|
|
13
|
-
"rev-parse",
|
|
14
|
-
"--abbrev-ref",
|
|
15
|
-
"HEAD"
|
|
16
|
-
]);
|
|
17
|
-
const sha = yield* runGit(spawner, cwd, ["rev-parse", "HEAD"]);
|
|
18
|
-
const dirtyOut = yield* runGit(spawner, cwd, ["status", "--porcelain"]);
|
|
19
|
-
const upstream = yield* runGit(spawner, cwd, [
|
|
20
|
-
"rev-parse",
|
|
21
|
-
"--abbrev-ref",
|
|
22
|
-
"@{upstream}"
|
|
23
|
-
]);
|
|
24
|
-
const worktree = yield* runGit(spawner, cwd, ["rev-parse", "--show-toplevel"]);
|
|
25
|
-
const dirtyValue = Option.match(dirtyOut, {
|
|
26
|
-
onNone: () => null,
|
|
27
|
-
onSome: (out) => out.length > 0
|
|
28
|
-
});
|
|
29
|
-
const host = probeHostMetadataFromEnv(env);
|
|
30
|
-
return new RunContext({
|
|
31
|
-
gitBranch: Option.getOrNull(branch),
|
|
32
|
-
gitCommitSha: Option.getOrNull(sha),
|
|
33
|
-
gitDirty: dirtyValue,
|
|
34
|
-
gitUpstream: Option.getOrNull(upstream),
|
|
35
|
-
gitWorktreeDir: Option.getOrNull(worktree),
|
|
36
|
-
hostSource: host.source,
|
|
37
|
-
hostValue: host.value,
|
|
38
|
-
hostMetadata: host.metadata
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
const captureAgentContextEffect = (cwd, spawner) => Effect.gen(function* () {
|
|
42
|
-
const branch = yield* runGit(spawner, cwd, [
|
|
43
|
-
"rev-parse",
|
|
44
|
-
"--abbrev-ref",
|
|
45
|
-
"HEAD"
|
|
46
|
-
]);
|
|
47
|
-
const sha = yield* runGit(spawner, cwd, ["rev-parse", "HEAD"]);
|
|
48
|
-
const worktree = yield* runGit(spawner, cwd, ["rev-parse", "--show-toplevel"]);
|
|
49
|
-
return new AgentContext({
|
|
50
|
-
startGitBranch: Option.getOrNull(branch),
|
|
51
|
-
startGitCommitSha: Option.getOrNull(sha),
|
|
52
|
-
startWorktreeDir: Option.getOrNull(worktree)
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
/**
|
|
56
|
-
* Live layer. Requires `ChildProcessSpawner` (provided by
|
|
57
|
-
* `NodeServices.layer` at the entry point). Reads `process.env`
|
|
58
|
-
* directly for host-metadata probes — the env walk is pure but the
|
|
59
|
-
* env source is process-global, captured once when the layer
|
|
60
|
-
* constructs.
|
|
61
|
-
* @public
|
|
62
|
-
*/
|
|
63
|
-
const RunContextLive = Layer.effect(RunContextService, Effect.gen(function* () {
|
|
64
|
-
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
65
|
-
return {
|
|
66
|
-
captureRunContext: (cwd) => captureRunContextEffect(cwd, spawner, process.env),
|
|
67
|
-
captureAgentContext: (cwd) => captureAgentContextEffect(cwd, spawner)
|
|
68
|
-
};
|
|
69
|
-
}));
|
|
70
|
-
/**
|
|
71
|
-
* Build a Test layer that returns fixed `RunContext` and
|
|
72
|
-
* `AgentContext` values regardless of `cwd`.
|
|
73
|
-
* @public
|
|
74
|
-
*/
|
|
75
|
-
const RunContextTest = (fixture) => Layer.succeed(RunContextService, {
|
|
76
|
-
captureRunContext: () => Effect.succeed(fixture.runContext),
|
|
77
|
-
captureAgentContext: () => Effect.succeed(fixture.agentContext)
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
//#endregion
|
|
81
|
-
export { RunContextLive, RunContextTest };
|
package/lib/format-triage.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { DataReader } from "../services/DataReader.js";
|
|
2
|
-
import { Effect, Option } from "effect";
|
|
3
|
-
|
|
4
|
-
//#region src/lib/format-triage.ts
|
|
5
|
-
/**
|
|
6
|
-
* Generates an orientation triage markdown string for LLM agents.
|
|
7
|
-
* Summarises recent test runs, active sessions, acceptance metrics,
|
|
8
|
-
* and (forward-compat) the most recent TDD session.
|
|
9
|
-
*
|
|
10
|
-
* Error channel is `never` — all DataReader errors are swallowed and
|
|
11
|
-
* replaced with empty defaults so the caller is guaranteed a string.
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
const formatTriageEffect = (options = {}) => Effect.gen(function* () {
|
|
15
|
-
const reader = yield* DataReader;
|
|
16
|
-
const allProjects = yield* reader.getRunsByProject().pipe(Effect.orElseSucceed(() => []));
|
|
17
|
-
const projects = options.project ? allProjects.filter((p) => p.project === options.project) : allProjects;
|
|
18
|
-
const sessions = yield* reader.listSessions({}).pipe(Effect.orElseSucceed(() => []));
|
|
19
|
-
const fallbackMetrics = {
|
|
20
|
-
phaseEvidenceIntegrity: {
|
|
21
|
-
total: 0,
|
|
22
|
-
compliant: 0,
|
|
23
|
-
ratio: 0
|
|
24
|
-
},
|
|
25
|
-
complianceHookResponsiveness: {
|
|
26
|
-
total: 0,
|
|
27
|
-
withFollowup: 0,
|
|
28
|
-
ratio: 0
|
|
29
|
-
},
|
|
30
|
-
orientationUsefulness: {
|
|
31
|
-
total: 0,
|
|
32
|
-
referencedCount: 0,
|
|
33
|
-
ratio: 0
|
|
34
|
-
},
|
|
35
|
-
antiPatternDetectionRate: {
|
|
36
|
-
total: 0,
|
|
37
|
-
cleanSessions: 0,
|
|
38
|
-
ratio: 0
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
const metrics = yield* reader.computeAcceptanceMetrics().pipe(Effect.orElseSucceed(() => fallbackMetrics));
|
|
42
|
-
const openTddRaw = yield* reader.getTddTaskById(1).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
43
|
-
const lines = [];
|
|
44
|
-
lines.push("## Vitest Agent Reporter — Orientation Triage");
|
|
45
|
-
lines.push("");
|
|
46
|
-
lines.push("### Available vitest-agent MCP tools (most useful)");
|
|
47
|
-
lines.push("");
|
|
48
|
-
lines.push("- `run_tests` — run Vitest programmatically; returns AgentReport + classifications");
|
|
49
|
-
lines.push("- `test_errors` — failure detail (after a failing run)");
|
|
50
|
-
lines.push("- `test_history` — failure classification series (after recurring failures)");
|
|
51
|
-
lines.push("- `file_coverage` — per-file coverage gaps (after coverage drops)");
|
|
52
|
-
lines.push("- `triage_brief` — orient on the current test landscape");
|
|
53
|
-
lines.push("");
|
|
54
|
-
if (projects.length > 0) {
|
|
55
|
-
lines.push("### Recent Test Runs");
|
|
56
|
-
lines.push("");
|
|
57
|
-
for (const p of projects) {
|
|
58
|
-
const status = p.lastResult ?? "unknown";
|
|
59
|
-
const counts = `${p.passed} passed, ${p.failed} failed`;
|
|
60
|
-
lines.push(`- **${p.project}** — ${status} (${counts})`);
|
|
61
|
-
}
|
|
62
|
-
lines.push("");
|
|
63
|
-
} else {
|
|
64
|
-
lines.push("### Recent Test Runs");
|
|
65
|
-
lines.push("");
|
|
66
|
-
lines.push("_No test runs recorded yet._");
|
|
67
|
-
lines.push("");
|
|
68
|
-
}
|
|
69
|
-
lines.push("### Session Log");
|
|
70
|
-
lines.push("");
|
|
71
|
-
if (sessions.length > 0) for (const s of sessions) {
|
|
72
|
-
const end = s.endedAt ? ` → ended ${s.endedAt}` : " → active";
|
|
73
|
-
lines.push(`- session \`${s.chatId}\` (${s.agentKind}) started ${s.startedAt}${end}`);
|
|
74
|
-
}
|
|
75
|
-
else lines.push("_No session data recorded yet._");
|
|
76
|
-
lines.push("");
|
|
77
|
-
lines.push("### Acceptance Metrics");
|
|
78
|
-
lines.push("");
|
|
79
|
-
const pct = (r) => `${(r * 100).toFixed(0)}%`;
|
|
80
|
-
lines.push(`- Phase evidence integrity: ${pct(metrics.phaseEvidenceIntegrity.ratio)} (${metrics.phaseEvidenceIntegrity.compliant}/${metrics.phaseEvidenceIntegrity.total})`);
|
|
81
|
-
lines.push(`- Compliance hook responsiveness: ${pct(metrics.complianceHookResponsiveness.ratio)} (${metrics.complianceHookResponsiveness.withFollowup}/${metrics.complianceHookResponsiveness.total})`);
|
|
82
|
-
lines.push(`- Orientation usefulness: ${pct(metrics.orientationUsefulness.ratio)} (${metrics.orientationUsefulness.referencedCount}/${metrics.orientationUsefulness.total})`);
|
|
83
|
-
lines.push(`- Anti-pattern detection: ${pct(metrics.antiPatternDetectionRate.ratio)} (${metrics.antiPatternDetectionRate.cleanSessions}/${metrics.antiPatternDetectionRate.total})`);
|
|
84
|
-
lines.push("");
|
|
85
|
-
if (Option.isSome(openTddRaw)) {
|
|
86
|
-
const tdd = openTddRaw.value;
|
|
87
|
-
lines.push("### Open TDD Session");
|
|
88
|
-
lines.push("");
|
|
89
|
-
lines.push(`- Goal: ${tdd.goal}`);
|
|
90
|
-
lines.push(`- Started: ${tdd.startedAt}`);
|
|
91
|
-
lines.push(`- Phases recorded: ${tdd.phases.length}`);
|
|
92
|
-
lines.push("");
|
|
93
|
-
}
|
|
94
|
-
const out = lines.join("\n");
|
|
95
|
-
if (options.maxLines !== void 0) {
|
|
96
|
-
const arr = out.split("\n");
|
|
97
|
-
if (arr.length > options.maxLines) return arr.slice(0, options.maxLines).join("\n");
|
|
98
|
-
}
|
|
99
|
-
return out;
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
//#endregion
|
|
103
|
-
export { formatTriageEffect };
|
package/lib/format-wrapup.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { DataReader } from "../services/DataReader.js";
|
|
2
|
-
import { Effect, Option } from "effect";
|
|
3
|
-
|
|
4
|
-
//#region src/lib/format-wrapup.ts
|
|
5
|
-
const FAILURE_PROMPT_PATTERN = /\b(?:test fail|fix\b[^.]*\btest|why\b[^.]*\bfail(?:ing)?|broken test)\b/i;
|
|
6
|
-
/** @public */
|
|
7
|
-
const formatWrapupEffect = (options) => Effect.gen(function* () {
|
|
8
|
-
const reader = yield* DataReader;
|
|
9
|
-
if (options.kind === "user_prompt_nudge") {
|
|
10
|
-
const hint = options.userPromptHint ?? "";
|
|
11
|
-
if (!FAILURE_PROMPT_PATTERN.test(hint)) return "";
|
|
12
|
-
return [
|
|
13
|
-
"<vitest-agent-nudge>",
|
|
14
|
-
"Before fixing, consider checking with test_history (is this flaky?) and failure_signature_get (have we seen this failure before?).",
|
|
15
|
-
"</vitest-agent-nudge>"
|
|
16
|
-
].join("\n");
|
|
17
|
-
}
|
|
18
|
-
let sessionId = null;
|
|
19
|
-
let agentKind = "main";
|
|
20
|
-
if (options.sessionId !== void 0) {
|
|
21
|
-
sessionId = options.sessionId;
|
|
22
|
-
const opt = yield* reader.getSessionById(options.sessionId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
23
|
-
if (Option.isSome(opt)) agentKind = opt.value.agentKind;
|
|
24
|
-
} else if (options.chatId !== void 0) {
|
|
25
|
-
const opt = yield* reader.getSessionByChatId(options.chatId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
26
|
-
if (Option.isSome(opt)) {
|
|
27
|
-
sessionId = opt.value.id;
|
|
28
|
-
agentKind = opt.value.agentKind;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (options.kind === "tdd_handoff") {
|
|
32
|
-
if (sessionId === null) return "";
|
|
33
|
-
const tddOpt = yield* reader.getTddTaskById(1).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
34
|
-
if (Option.isNone(tddOpt) || tddOpt.value.sessionId !== sessionId) return "";
|
|
35
|
-
const tdd = tddOpt.value;
|
|
36
|
-
return [
|
|
37
|
-
`Subagent tdd-orchestrator (tddTaskId=${tdd.id}) finished.`,
|
|
38
|
-
`Outcome: ${tdd.outcome ?? "in progress"}. Goal: "${tdd.goal}".`,
|
|
39
|
-
`Phases: ${tdd.phases.length}. Artifacts: ${tdd.artifacts.length}.`,
|
|
40
|
-
`[Read full summary: tdd_task({ action: "get", tddTaskId: ${tdd.id} })]`,
|
|
41
|
-
`[Resume: /tdd resume:${tdd.id}]`
|
|
42
|
-
].join("\n");
|
|
43
|
-
}
|
|
44
|
-
if (sessionId === null) return "";
|
|
45
|
-
const fileEditCount = (yield* reader.searchTurns({
|
|
46
|
-
sessionId,
|
|
47
|
-
limit: 50
|
|
48
|
-
}).pipe(Effect.orElseSucceed(() => []))).filter((t) => t.type === "file_edit").length;
|
|
49
|
-
const openHypotheses = (yield* reader.listHypotheses({
|
|
50
|
-
sessionId,
|
|
51
|
-
limit: 50
|
|
52
|
-
}).pipe(Effect.orElseSucceed(() => []))).filter((h) => h.validationOutcome === null);
|
|
53
|
-
if (fileEditCount === 0 && openHypotheses.length === 0) return "";
|
|
54
|
-
const isSubagent = agentKind === "subagent";
|
|
55
|
-
const lines = [];
|
|
56
|
-
if (isSubagent) {
|
|
57
|
-
if (fileEditCount > 0 && openHypotheses.length === 0) lines.push(`- ${fileEditCount} recent file edit${fileEditCount === 1 ? "" : "s"} but no hypotheses recorded. Did your changes have a hypothesis? Use \`hypothesis({ action: "record" })\` to externalize the why before continuing.`);
|
|
58
|
-
if (openHypotheses.length > 0) lines.push(`- ${openHypotheses.length} open hypothesis(es). Use \`hypothesis({ action: "validate", id, outcome: "confirmed"|"refuted"|"abandoned" })\` to mark them.`);
|
|
59
|
-
}
|
|
60
|
-
if (options.kind === "session_end") lines.push("- Use `note({ action: \"create\" })` to record any insights worth carrying forward.");
|
|
61
|
-
if (options.kind === "pre_compact") lines.push("- What from this session matters next? Record it now via `note({ action: \"create\" })` before context compaction.");
|
|
62
|
-
if (lines.length === 0) return "";
|
|
63
|
-
return [options.kind === "stop" ? "## Before you finish" : "## Session wrap-up", ...lines].join("\n");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
//#endregion
|
|
67
|
-
export { formatWrapupEffect };
|