@kontextmind/kxm 0.6.0 → 0.7.10
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/.claude-plugin/marketplace.json +1 -1
- package/.kxm/agents/coordinator.yaml +9 -0
- package/.kxm/agents/critic-arch.yaml +13 -0
- package/.kxm/agents/critic-cli.yaml +13 -0
- package/.kxm/agents/implementer.yaml +13 -0
- package/.kxm/gates.yaml +8 -0
- package/.kxm/producers.yaml +22 -0
- package/.kxm/project.yaml +15 -0
- package/.kxm/roles/writer.yaml +7 -0
- package/.kxm/workflows/default.yaml +47 -0
- package/CHANGELOG.md +39 -7
- package/README.md +1 -0
- package/docs/README.md +5 -0
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
- package/docs/agent-skills.md +135 -0
- package/docs/architecture.md +1 -1
- package/docs/assignment-runner.md +21 -8
- package/docs/browser-automation.md +116 -0
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +21 -0
- package/docs/kb/how-credentials-retrieved-safely.md +31 -0
- package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
- package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
- package/docs/kb/how-to-resume-after-mfa.md +28 -0
- package/docs/kb/how-to-take-over-session.md +32 -0
- package/docs/kb/why-authentication-disappeared.md +32 -0
- package/docs/kb/why-automation-opened-different-browser.md +32 -0
- package/docs/kb/why-session-viewer-cannot-control.md +31 -0
- package/docs/kxm-handbook.md +3 -3
- package/docs/operations.md +24 -0
- package/docs/operator-pi-packages.md +63 -0
- package/docs/prompts/browser-annotate-feedback.md +41 -0
- package/docs/prompts/browser-diagnose-recover.md +38 -0
- package/docs/prompts/browser-explore.md +42 -0
- package/docs/prompts/browser-repro-fix.md +48 -0
- package/docs/prompts/browser-start.md +41 -0
- package/docs/prompts/browser-takeover.md +50 -0
- package/docs/skills/repo-work-delivery.md +107 -0
- package/docs/skills.md +2 -0
- package/docs/test-matrix.md +4 -3
- package/docs/troubleshooting.md +41 -1
- package/docs/vnext/validation.md +9 -0
- package/docs/webhook-workflows.md +2 -2
- package/examples/README.md +1 -1
- package/package.json +16 -17
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +1 -1
- package/plugins/kxm/dist/cli.js +41620 -35578
- package/plugins/kxm/dist/core.js +271 -34
- package/plugins/kxm/dist/extension.js +7759 -86
- package/plugins/kxm/dist/mcp-server.js +75 -21
- package/plugins/kxm/dist/runtime.js +8218 -2328
- package/plugins/kxm/dist/server.js +3125 -2260
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +5961 -661
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/SUITE.md +5 -0
- package/plugins/kxm/skills/hints.json +103 -0
- package/plugins/kxm/skills/kxm/SKILL.md +30 -83
- package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
- package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +42 -0
- package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
- package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
- package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
- package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
- package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
- package/plugins/kxm/src/autocomplete.ts +9 -3
- package/plugins/kxm/src/browser.ts +603 -0
- package/plugins/kxm/src/cli/context-skills.ts +373 -0
- package/plugins/kxm/src/cli/hub.ts +614 -0
- package/plugins/kxm/src/cli/roles.ts +615 -0
- package/plugins/kxm/src/cli/system.ts +906 -0
- package/plugins/kxm/src/cli/tasks.ts +364 -0
- package/plugins/kxm/src/cli/types.ts +270 -0
- package/plugins/kxm/src/cli/vnext.ts +698 -0
- package/plugins/kxm/src/cli/workflows.ts +699 -0
- package/plugins/kxm/src/cli.ts +362 -2849
- package/plugins/kxm/src/commands.ts +150 -8
- package/plugins/kxm/src/completion-install.ts +223 -0
- package/plugins/kxm/src/config.ts +7 -4
- package/plugins/kxm/src/context-packet.ts +172 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/extension.ts +36 -1
- package/plugins/kxm/src/external-effects.ts +357 -8
- package/plugins/kxm/src/hub-env.ts +193 -0
- package/plugins/kxm/src/hub.ts +2 -4
- package/plugins/kxm/src/improve.ts +72 -0
- package/plugins/kxm/src/init-guide-setup.ts +547 -0
- package/plugins/kxm/src/local-snapshot.ts +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/model-inventory.ts +127 -0
- package/plugins/kxm/src/modes.ts +348 -0
- package/plugins/kxm/src/policy-draft.d.mts +55 -0
- package/plugins/kxm/src/policy-draft.mjs +565 -0
- package/plugins/kxm/src/price-calc.ts +17 -18
- package/plugins/kxm/src/prices.ts +32 -16
- package/plugins/kxm/src/producers.ts +71 -0
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
- package/plugins/kxm/src/restricted-yaml.mjs +145 -0
- package/plugins/kxm/src/role.ts +710 -0
- package/plugins/kxm/src/routing.ts +99 -1
- package/plugins/kxm/src/runtime.ts +4 -0
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/session-work.ts +9 -2
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/ssh-remote.ts +560 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/studio-layout.ts +660 -17
- package/plugins/kxm/src/subagent-control.ts +312 -0
- package/plugins/kxm/src/suggest.ts +7 -13
- package/plugins/kxm/src/telemetry.ts +82 -0
- package/plugins/kxm/src/tui.ts +140 -0
- package/plugins/kxm/src/vnext-bindings.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +53 -111
- package/plugins/kxm/src/vnext-engine-command.ts +2 -0
- package/plugins/kxm/src/vnext-engine.ts +214 -62
- package/plugins/kxm/src/vnext-harness.ts +336 -84
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +117 -0
- package/plugins/kxm/src/vnext-oneshot-process.ts +187 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +182 -224
- package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
- package/plugins/kxm/src/vnext-runtime-store.ts +36 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +122 -5
- package/plugins/kxm/src/vnext-runtime.ts +14 -0
- package/plugins/kxm/src/workflow-manager.ts +392 -0
- package/plugins/kxm/src/workflow-tui.ts +255 -0
- package/plugins/kxm/src/workflow.ts +144 -0
- package/schemas/policy-draft/README.md +17 -0
- package/schemas/policy-draft/model.v2.schema.json +140 -0
- package/schemas/policy-draft/role.v2.schema.json +91 -0
- package/schemas/vnext/modes.schema.json +56 -0
- package/schemas/vnext/role.schema.json +76 -0
- package/schemas/vnext/run-event.schema.json +1 -0
- package/scripts/assignment-run.d.mts +1 -1
- package/scripts/assignment-run.mjs +44 -35
- package/scripts/check-generated.mjs +33 -9
- package/scripts/emit-codex-artifacts.mjs +255 -11
- package/scripts/harness-run.d.mts +12 -4
- package/scripts/harness-run.mjs +65 -17
- package/scripts/kxm-bump-version.mjs +146 -0
- package/scripts/kxm-hub.mjs +150 -2
- package/scripts/kxm-publish-npm.mjs +3 -1
- package/scripts/kxm-release-github.mjs +3 -1
- package/scripts/kxm.mjs +0 -0
- package/scripts/native-critic.d.mts +5 -0
- package/scripts/native-critic.mjs +60 -0
- package/.kxm/config/README.md +0 -5
- package/.kxm/config/agents.json +0 -43
- package/.kxm/config/env.example +0 -56
- package/.kxm/config/update.example.yaml +0 -9
- package/.kxm/config/workflows/fix.json +0 -160
- package/.kxm/config/workflows/jira-development.json +0 -116
- package/.kxm/config/workflows/provenance-quorum.json +0 -150
- package/.kxm/config/workflows/v04-dogfood.json +0 -72
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { beginOneShotEvidence } from "./vnext-oneshot-evidence.ts";
|
|
3
|
+
import { vnextUserStateRoot } from "./vnext-bindings.ts";
|
|
4
|
+
import { defaultSpawn, type VnextOneShotProcessResult, type VnextOneShotSpawn } from "./vnext-oneshot-process.ts";
|
|
5
|
+
export { defaultSpawn, type VnextOneShotProcessResult, type VnextOneShotSpawn } from "./vnext-oneshot-process.ts";
|
|
2
6
|
import {
|
|
3
7
|
BUILTIN_HARNESSES,
|
|
4
8
|
NATIVE_HARNESS_PROVIDERS,
|
|
5
|
-
|
|
9
|
+
probeHarnessAssignmentAsync,
|
|
10
|
+
oneShotReadOnlyArgs,
|
|
11
|
+
type HarnessAssignmentProbeOptions,
|
|
12
|
+
type HarnessStatus,
|
|
6
13
|
type HarnessCatalogEntry,
|
|
7
14
|
type HarnessInventory,
|
|
15
|
+
type OneShotParsedOutput,
|
|
8
16
|
} from "./vnext-harness.ts";
|
|
9
17
|
import {
|
|
10
18
|
calculateModelCost,
|
|
19
|
+
findModelPrice,
|
|
11
20
|
loadPriceCatalog,
|
|
21
|
+
parsePriceCatalog,
|
|
12
22
|
type PriceCatalog,
|
|
13
23
|
} from "./prices.ts";
|
|
14
24
|
import {
|
|
@@ -18,27 +28,9 @@ import {
|
|
|
18
28
|
type VnextProducerResult,
|
|
19
29
|
} from "./vnext-engine.ts";
|
|
20
30
|
|
|
21
|
-
export interface VnextOneShotProcessResult {
|
|
22
|
-
stdout: string;
|
|
23
|
-
stderr: string;
|
|
24
|
-
code: number | null;
|
|
25
|
-
error?: Error | undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type VnextOneShotSpawn = (
|
|
29
|
-
command: string,
|
|
30
|
-
args: readonly string[],
|
|
31
|
-
options: {
|
|
32
|
-
cwd?: string | undefined;
|
|
33
|
-
env?: NodeJS.ProcessEnv | undefined;
|
|
34
|
-
input?: string | undefined;
|
|
35
|
-
timeoutMs?: number | undefined;
|
|
36
|
-
signal?: AbortSignal | undefined;
|
|
37
|
-
},
|
|
38
|
-
) => Promise<VnextOneShotProcessResult>;
|
|
39
|
-
|
|
40
31
|
export interface VnextOneShotProducerOptions {
|
|
41
32
|
projectRoot?: string | undefined;
|
|
33
|
+
evidenceRoot?: string | undefined;
|
|
42
34
|
defaultHarness?: string | undefined;
|
|
43
35
|
defaultModel?: string | undefined;
|
|
44
36
|
defaultProvider?: string | undefined;
|
|
@@ -48,7 +40,7 @@ export interface VnextOneShotProducerOptions {
|
|
|
48
40
|
env?: NodeJS.ProcessEnv | undefined;
|
|
49
41
|
timeoutMs?: number | undefined;
|
|
50
42
|
spawnProcess?: VnextOneShotSpawn | undefined;
|
|
51
|
-
probeHarness?:
|
|
43
|
+
probeHarness?: ((options: Omit<HarnessAssignmentProbeOptions, "runCommand"> & { signal?: AbortSignal | undefined }) => HarnessStatus | Promise<HarnessStatus>) | undefined;
|
|
52
44
|
resolveHarness?: ((agentId: string, runId: string) => string | undefined) | undefined;
|
|
53
45
|
resolveModel?: ((agentId: string, runId: string) => {
|
|
54
46
|
harness?: string | undefined;
|
|
@@ -65,96 +57,20 @@ export interface VnextOneShotProducer extends VnextProducer {
|
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
function determineOutcome(text: string, allowedOutcomes: readonly string[]): string {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (const outcome of allowedOutcomes) {
|
|
74
|
-
const regex = new RegExp(`\\b${outcome}\\b`, "i");
|
|
75
|
-
if (regex.test(normalized)) {
|
|
76
|
-
return outcome;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (allowedOutcomes.includes("passed")) return "passed";
|
|
80
|
-
return allowedOutcomes[0] ?? "completed";
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function defaultSpawn(
|
|
84
|
-
command: string,
|
|
85
|
-
args: readonly string[],
|
|
86
|
-
options: {
|
|
87
|
-
cwd?: string | undefined;
|
|
88
|
-
env?: NodeJS.ProcessEnv | undefined;
|
|
89
|
-
input?: string | undefined;
|
|
90
|
-
timeoutMs?: number | undefined;
|
|
91
|
-
signal?: AbortSignal | undefined;
|
|
92
|
-
},
|
|
93
|
-
): Promise<VnextOneShotProcessResult> {
|
|
94
|
-
return new Promise((resolve) => {
|
|
95
|
-
let stdout = "";
|
|
96
|
-
let stderr = "";
|
|
97
|
-
let killed = false;
|
|
98
|
-
|
|
99
|
-
const child = spawn(command, [...args], {
|
|
100
|
-
cwd: options.cwd,
|
|
101
|
-
env: options.env ?? process.env,
|
|
102
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
103
|
-
windowsHide: true,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (options.signal) {
|
|
107
|
-
if (options.signal.aborted) {
|
|
108
|
-
child.kill();
|
|
109
|
-
return resolve({ stdout: "", stderr: "aborted", code: null, error: new Error("process_aborted") });
|
|
110
|
-
}
|
|
111
|
-
const onAbort = () => {
|
|
112
|
-
killed = true;
|
|
113
|
-
child.kill();
|
|
114
|
-
};
|
|
115
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
116
|
-
child.on("close", () => options.signal?.removeEventListener("abort", onAbort));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
120
|
-
const timer = setTimeout(() => {
|
|
121
|
-
killed = true;
|
|
122
|
-
child.kill();
|
|
123
|
-
}, options.timeoutMs);
|
|
124
|
-
child.on("close", () => clearTimeout(timer));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
child.stdout.on("data", (chunk: Buffer | string) => {
|
|
128
|
-
stdout += chunk.toString();
|
|
129
|
-
});
|
|
130
|
-
child.stderr.on("data", (chunk: Buffer | string) => {
|
|
131
|
-
stderr += chunk.toString();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
child.on("error", (err) => {
|
|
135
|
-
resolve({ stdout, stderr, code: null, error: err });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
child.on("close", (code) => {
|
|
139
|
-
resolve({
|
|
140
|
-
stdout,
|
|
141
|
-
stderr,
|
|
142
|
-
code,
|
|
143
|
-
...(killed ? { error: new Error("process_aborted") } : {}),
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (options.input !== undefined) {
|
|
148
|
-
child.stdin.write(options.input);
|
|
149
|
-
child.stdin.end();
|
|
150
|
-
} else {
|
|
151
|
-
child.stdin.end();
|
|
60
|
+
try {
|
|
61
|
+
const result: unknown = JSON.parse(text.trim());
|
|
62
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
63
|
+
const outcome = (result as Record<string, unknown>).outcome;
|
|
64
|
+
if (typeof outcome === "string" && allowedOutcomes.includes(outcome)) return outcome;
|
|
152
65
|
}
|
|
153
|
-
}
|
|
66
|
+
} catch { /* Prose, substring matches, and missing results cannot imply success. */ }
|
|
67
|
+
return "failed";
|
|
154
68
|
}
|
|
155
69
|
|
|
156
70
|
export function createVnextOneShotProducer(options: VnextOneShotProducerOptions = {}): VnextOneShotProducer {
|
|
157
71
|
const defaultHarness = options.defaultHarness ?? "claude";
|
|
72
|
+
const running = new Map<AbortController, Promise<VnextProducerResult>>();
|
|
73
|
+
let closed = false;
|
|
158
74
|
|
|
159
75
|
function resolveHarnessForRequest(request: VnextProducerRequest): string {
|
|
160
76
|
if (request.harness) return request.harness;
|
|
@@ -202,12 +118,14 @@ export function createVnextOneShotProducer(options: VnextOneShotProducerOptions
|
|
|
202
118
|
};
|
|
203
119
|
}
|
|
204
120
|
}
|
|
205
|
-
const defaultModel = options.defaultModel ?? (
|
|
121
|
+
const defaultModel = options.defaultModel ?? (
|
|
122
|
+
harness === "codex" ? "gpt-5.6-sol" : harness === "kimi" ? "kimi-for-coding" : harness === "agy" ? "gemini-3.8-flash-high" : "claude-3-7-sonnet"
|
|
123
|
+
);
|
|
206
124
|
const parsed = parseModelString(defaultModel, harness);
|
|
207
125
|
return { provider: parsed.provider, model: parsed.model, thinking: request.thinking };
|
|
208
126
|
}
|
|
209
127
|
|
|
210
|
-
function checkAuth(harness: string, provider: string, model: string):
|
|
128
|
+
async function checkAuth(harness: string, provider: string, model: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<HarnessStatus> {
|
|
211
129
|
if (options.inventory) {
|
|
212
130
|
const entry = options.inventory.harnesses.find((h) => h.id === harness);
|
|
213
131
|
if (!entry || !entry.detected || entry.authenticated !== true) {
|
|
@@ -215,13 +133,8 @@ export function createVnextOneShotProducer(options: VnextOneShotProducerOptions
|
|
|
215
133
|
}
|
|
216
134
|
}
|
|
217
135
|
|
|
218
|
-
const probeFn = options.probeHarness ??
|
|
219
|
-
const probe = probeFn({
|
|
220
|
-
harness,
|
|
221
|
-
provider,
|
|
222
|
-
model,
|
|
223
|
-
env: options.env,
|
|
224
|
-
});
|
|
136
|
+
const probeFn = options.probeHarness ?? probeHarnessAssignmentAsync;
|
|
137
|
+
const probe = await probeFn({ harness, provider, model, env, signal, timeoutMs: 10_000 });
|
|
225
138
|
|
|
226
139
|
if (!probe.detected) {
|
|
227
140
|
throw new Error(`${harness}_not_authenticated: ${harness} harness not detected (${probe.issues.join(", ")})`);
|
|
@@ -229,26 +142,32 @@ export function createVnextOneShotProducer(options: VnextOneShotProducerOptions
|
|
|
229
142
|
if (probe.authenticated !== true) {
|
|
230
143
|
throw new Error(`${harness}_not_authenticated: ${harness} not authenticated (${probe.issues.join(", ")})`);
|
|
231
144
|
}
|
|
145
|
+
return probe;
|
|
232
146
|
}
|
|
233
147
|
|
|
234
|
-
|
|
235
|
-
id: "oneshot",
|
|
236
|
-
|
|
237
|
-
async produce(request: VnextProducerRequest): Promise<VnextProducerResult> {
|
|
148
|
+
async function executeRequest(request: VnextProducerRequest): Promise<VnextProducerResult> {
|
|
238
149
|
const startTime = Date.now();
|
|
239
150
|
const harness = resolveHarnessForRequest(request);
|
|
240
151
|
const resolved = resolveModelForRequest(request, harness);
|
|
241
152
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
153
|
+
if (closed) throw new Error("oneshot_producer_closed");
|
|
154
|
+
const cancelled = (): VnextProducerResult => ({ outcome: "cancelled", costBasis: "unknown", costUsd: null,
|
|
155
|
+
latencyMs: Math.max(0, Date.now() - startTime), harness, provider: resolved.provider,
|
|
156
|
+
requestedModel: resolved.model, effectiveModel: "unknown", agentRole: request.agentRole ?? request.agentId });
|
|
157
|
+
if (request.signal.aborted) return cancelled();
|
|
245
158
|
const catalogEntry = (options.catalog ?? BUILTIN_HARNESSES).find((h) => h.id === harness);
|
|
246
|
-
if (!catalogEntry
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
159
|
+
if (!catalogEntry?.oneShot) throw new Error(`oneshot_harness_unsupported: ${harness}`);
|
|
160
|
+
const permissionArgs = oneShotReadOnlyArgs(harness);
|
|
161
|
+
if (!permissionArgs) throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
|
|
162
|
+
// Pin the environment for auth and execution; don't observe subscription
|
|
163
|
+
// auth under one environment and then spawn under changed API-key settings.
|
|
164
|
+
const env = { ...(options.env ?? process.env) };
|
|
165
|
+
let auth: HarnessStatus;
|
|
166
|
+
try { auth = await checkAuth(harness, resolved.provider, resolved.model, env, request.signal); }
|
|
167
|
+
catch (error) { if (request.signal.aborted) return cancelled(); throw error; }
|
|
168
|
+
if (request.signal.aborted) return cancelled();
|
|
169
|
+
|
|
170
|
+
const command = auth.command ?? catalogEntry.commands[0] ?? harness;
|
|
252
171
|
const oneShot = catalogEntry.oneShot;
|
|
253
172
|
|
|
254
173
|
let args: string[];
|
|
@@ -258,6 +177,7 @@ export function createVnextOneShotProducer(options: VnextOneShotProducerOptions
|
|
|
258
177
|
"--model",
|
|
259
178
|
resolved.model,
|
|
260
179
|
...(resolved.thinking ? ["--effort", resolved.thinking] : []),
|
|
180
|
+
...permissionArgs,
|
|
261
181
|
"--output-format",
|
|
262
182
|
"json",
|
|
263
183
|
];
|
|
@@ -267,134 +187,150 @@ export function createVnextOneShotProducer(options: VnextOneShotProducerOptions
|
|
|
267
187
|
"-m",
|
|
268
188
|
resolved.model,
|
|
269
189
|
...(resolved.thinking ? ["-c", `model_reasoning_effort="${resolved.thinking}"`] : []),
|
|
190
|
+
...permissionArgs,
|
|
270
191
|
"--json",
|
|
271
192
|
"-",
|
|
272
193
|
];
|
|
273
|
-
} else if (harness === "
|
|
194
|
+
} else if (harness === "grok") {
|
|
195
|
+
args = ["--model", resolved.model,
|
|
196
|
+
...(resolved.thinking ? ["--reasoning-effort", resolved.thinking] : []),
|
|
197
|
+
...permissionArgs, "--output-format", "json"];
|
|
198
|
+
} else if (harness === "agy") {
|
|
274
199
|
args = [
|
|
275
|
-
"--output-format",
|
|
276
|
-
"stream-json",
|
|
277
200
|
"-m",
|
|
278
201
|
resolved.model,
|
|
279
|
-
|
|
280
|
-
];
|
|
281
|
-
} else if (harness === "agy") {
|
|
282
|
-
args = [
|
|
202
|
+
...permissionArgs,
|
|
283
203
|
"--output-format",
|
|
284
204
|
"json",
|
|
285
|
-
...(resolved.thinking ? ["--effort", resolved.thinking] : []),
|
|
286
|
-
"--model",
|
|
287
|
-
resolved.model,
|
|
288
205
|
"-p",
|
|
289
206
|
];
|
|
290
|
-
} else {
|
|
207
|
+
} else if (harness === "kimi") {
|
|
291
208
|
args = [
|
|
292
|
-
|
|
293
|
-
"--model",
|
|
209
|
+
"-m",
|
|
294
210
|
resolved.model,
|
|
295
|
-
...
|
|
211
|
+
...permissionArgs,
|
|
212
|
+
"--output-format",
|
|
213
|
+
"stream-json",
|
|
214
|
+
"-p",
|
|
296
215
|
];
|
|
216
|
+
} else {
|
|
217
|
+
throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
|
|
297
218
|
}
|
|
298
219
|
|
|
299
|
-
const
|
|
300
|
-
?? `Execute step ${request.stepId} (attempt ${request.stepAttempt}) for agent ${request.agentId}
|
|
220
|
+
const taskPrompt = request.prompt
|
|
221
|
+
?? `Execute step ${request.stepId} (attempt ${request.stepAttempt}) for agent ${request.agentId}.`;
|
|
222
|
+
const promptMessage = `${taskPrompt}\n\nReturn a final JSON object with an "outcome" field chosen from ${JSON.stringify(request.allowedOutcomes)} and a concise "summary". Do not wrap it in Markdown. If no allowed outcome is truthful, return {"outcome":"failed"}; never infer success from an incomplete task.`;
|
|
301
223
|
|
|
302
224
|
let input: string | undefined;
|
|
303
225
|
if (oneShot.promptVia === "stdin") {
|
|
304
226
|
input = promptMessage;
|
|
305
227
|
} else {
|
|
228
|
+
if (harness === "grok") args.push("--single");
|
|
306
229
|
args.push(promptMessage);
|
|
307
230
|
}
|
|
308
231
|
|
|
309
|
-
if (request.signal?.aborted) {
|
|
310
|
-
const outcome = request.allowedOutcomes.includes("cancelled")
|
|
311
|
-
? "cancelled"
|
|
312
|
-
: (request.allowedOutcomes.includes("failed") ? "failed" : request.allowedOutcomes[0]!);
|
|
313
|
-
return {
|
|
314
|
-
outcome,
|
|
315
|
-
costBasis: "unknown",
|
|
316
|
-
costUsd: null,
|
|
317
|
-
latencyMs: 0,
|
|
318
|
-
harness,
|
|
319
|
-
provider: resolved.provider,
|
|
320
|
-
requestedModel: resolved.model,
|
|
321
|
-
effectiveModel: resolved.model,
|
|
322
|
-
agentRole: request.agentRole ?? request.agentId,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
232
|
const spawnFn = options.spawnProcess ?? defaultSpawn;
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
input,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
outcome,
|
|
341
|
-
costBasis: "unknown",
|
|
342
|
-
costUsd: null,
|
|
343
|
-
latencyMs: Math.max(0, Date.now() - startTime),
|
|
344
|
-
harness,
|
|
345
|
-
provider: resolved.provider,
|
|
346
|
-
requestedModel: resolved.model,
|
|
347
|
-
effectiveModel: resolved.model,
|
|
348
|
-
agentRole: request.agentRole ?? request.agentId,
|
|
349
|
-
};
|
|
233
|
+
const cwd = options.projectRoot ?? process.cwd();
|
|
234
|
+
const evidence = await beginOneShotEvidence(options.evidenceRoot ?? join(vnextUserStateRoot(), "runtime", "oneshot-evidence"), {
|
|
235
|
+
runId: request.runId, stepId: request.stepId, attemptId: request.attemptId, assignmentId: request.assignmentId,
|
|
236
|
+
harness, provider: resolved.provider, model: resolved.model, cwd, command, args, input,
|
|
237
|
+
}, [request.capability, ...Object.entries(env).filter(([key]) => /TOKEN|KEY|SECRET|PASSWORD|COOKIE|AUTH/i.test(key)).map(([, value]) => value ?? "")]);
|
|
238
|
+
let procResult: VnextOneShotProcessResult;
|
|
239
|
+
try {
|
|
240
|
+
procResult = request.signal.aborted
|
|
241
|
+
? { stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") }
|
|
242
|
+
: await spawnFn(command, args, { cwd, env, input, timeoutMs: options.timeoutMs ?? 120_000, signal: request.signal });
|
|
243
|
+
} catch (error) {
|
|
244
|
+
// A rejected adapter promise is not proof that no process/effect started.
|
|
245
|
+
procResult = { stdout: "", stderr: "", code: null, observedChildExit: false, error: error instanceof Error ? error : new Error("process_adapter_error") };
|
|
350
246
|
}
|
|
351
247
|
|
|
352
|
-
|
|
353
|
-
let
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
248
|
+
// Parse available usage even when execution failed or was interrupted.
|
|
249
|
+
let parsed: OneShotParsedOutput;
|
|
250
|
+
try { parsed = oneShot.usageParser(procResult.stdout, procResult.stderr, resolved.model); }
|
|
251
|
+
catch { parsed = { text: "", isError: true }; }
|
|
252
|
+
const aborted = request.signal.aborted || procResult.error?.message === "process_aborted";
|
|
253
|
+
// Process groups are cleanup, not containment. A detached descendant or
|
|
254
|
+
// provider leader may outlive the client; interrupted effects need recovery.
|
|
255
|
+
const unverifiedDescendants = procResult.unverifiedDescendants === true
|
|
256
|
+
|| procResult.terminationRequested === true
|
|
257
|
+
|| procResult.error?.message === "process_exit_unobserved"
|
|
258
|
+
|| (procResult.observedChildExit === false && procResult.started !== false);
|
|
259
|
+
const effectUncertain = unverifiedDescendants || Boolean(procResult.signal);
|
|
260
|
+
const transportFailed = procResult.code !== 0 || Boolean(procResult.error) || effectUncertain;
|
|
261
|
+
const outcome = aborted ? "cancelled" : transportFailed || parsed.isError ? "failed"
|
|
262
|
+
: determineOutcome(parsed.text, request.allowedOutcomes);
|
|
263
|
+
const providerMetadata: Record<string, string | number | boolean> = {
|
|
264
|
+
processStatus: aborted ? "aborted" : transportFailed ? "failed" : "completed",
|
|
265
|
+
executionEvidenceId: evidence.id,
|
|
266
|
+
};
|
|
267
|
+
if (procResult.code !== null && Number.isFinite(procResult.code)) providerMetadata.processExitCode = procResult.code;
|
|
268
|
+
if (procResult.signal) providerMetadata.processSignal = procResult.signal;
|
|
269
|
+
if (procResult.observedChildExit !== undefined) providerMetadata.observedChildExit = procResult.observedChildExit;
|
|
270
|
+
if (procResult.started !== undefined) providerMetadata.processStarted = procResult.started;
|
|
271
|
+
if (procResult.terminationRequested !== undefined) providerMetadata.terminationRequested = procResult.terminationRequested;
|
|
272
|
+
if (unverifiedDescendants) providerMetadata.descendantEffects = "unverified";
|
|
273
|
+
if (procResult.error) {
|
|
274
|
+
const reason = procResult.error.message;
|
|
275
|
+
providerMetadata.processError = /^process_(aborted|timeout|output_limit|stdin_error|stdio_error|stdio_unclosed|exit_unobserved)$/.test(reason) ? reason : "process_error";
|
|
358
276
|
}
|
|
359
|
-
|
|
360
277
|
const latencyMs = Math.max(0, Date.now() - startTime);
|
|
361
278
|
const usage = parsed.usage;
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
279
|
+
const validCount = (value: unknown): value is number => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
280
|
+
const boundedCount = (field: string, value: unknown): number | null => {
|
|
281
|
+
if (validCount(value) && value <= 1_000_000) return value;
|
|
282
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
283
|
+
providerMetadata[`raw${field[0]!.toUpperCase()}${field.slice(1)}`] = value;
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
};
|
|
287
|
+
const tokensIn = boundedCount("tokensIn", usage?.tokensIn);
|
|
288
|
+
const tokensOut = boundedCount("tokensOut", usage?.tokensOut);
|
|
289
|
+
const cacheReadTokens = boundedCount("cacheReadTokens", usage?.cacheReadTokens);
|
|
290
|
+
const cacheWriteTokens = boundedCount("cacheWriteTokens", usage?.cacheWriteTokens);
|
|
291
|
+
const contextTokens = boundedCount("contextTokens", usage?.contextTokens);
|
|
292
|
+
if (typeof usage?.costUsd === "number" && Number.isFinite(usage.costUsd) && usage.costUsd >= 0) providerMetadata.providerReportedCostUsd = usage.costUsd;
|
|
293
|
+
|
|
294
|
+
// List estimates and provider-reported amounts are not proof of a bill.
|
|
295
|
+
let catalog: PriceCatalog | undefined;
|
|
296
|
+
try {
|
|
297
|
+
catalog = options.priceCatalog ? parsePriceCatalog(JSON.stringify(options.priceCatalog))
|
|
298
|
+
: loadPriceCatalog(options.projectRoot ?? process.cwd());
|
|
299
|
+
} catch { providerMetadata.priceCatalogUnavailable = true; }
|
|
300
|
+
// Without a freshness-aware vendor feed, don't silently quote an older
|
|
301
|
+
// local snapshot. Even today's hash-verified snapshot is only an estimate.
|
|
302
|
+
if (catalog && catalog.date !== new Date().toISOString().slice(0, 10)) {
|
|
303
|
+
providerMetadata.priceCatalogStale = true;
|
|
304
|
+
catalog = undefined;
|
|
305
|
+
}
|
|
306
|
+
const subscription = ["claude.ai", "ChatGPT", "antigravity-oauth"].includes(auth.authMethod ?? "");
|
|
307
|
+
const costBasis = subscription ? "unmetered" as const : "unknown" as const;
|
|
308
|
+
const costUsd = null;
|
|
309
|
+
const priceRef = subscription ? `subscription:${harness}` : undefined;
|
|
310
|
+
if (auth.authMethod) providerMetadata.authMethod = auth.authMethod;
|
|
311
|
+
const priceRow = catalog ? findModelPrice(catalog, resolved.model, resolved.provider) : undefined;
|
|
312
|
+
const flatTier = priceRow?.tiers.length === 1 && priceRow.tiers[0]?.upToContextTokens == null ? priceRow.tiers[0] : undefined;
|
|
313
|
+
// Unknown context occupancy cannot select a context tier. Claude reports
|
|
314
|
+
// uncached input separately; other protocols need their own price semantics.
|
|
315
|
+
if (catalog && harness === "claude" && flatTier
|
|
316
|
+
&& validCount(usage?.tokensIn) && validCount(usage?.tokensOut)
|
|
317
|
+
&& validCount(usage?.cacheReadTokens) && validCount(usage?.cacheWriteTokens)
|
|
318
|
+
&& (usage.cacheReadTokens === 0 || flatTier.cacheReadPerMillion != null)
|
|
319
|
+
&& (usage.cacheWriteTokens === 0 || flatTier.cacheWritePerMillion != null)) {
|
|
375
320
|
const calculated = calculateModelCost(catalog, {
|
|
376
|
-
model: resolved.model,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
tokensOut,
|
|
380
|
-
cacheReadTokens,
|
|
381
|
-
cacheWriteTokens,
|
|
321
|
+
model: resolved.model, provider: resolved.provider,
|
|
322
|
+
tokensIn: usage.tokensIn, tokensOut: usage.tokensOut,
|
|
323
|
+
cacheReadTokens: usage.cacheReadTokens, cacheWriteTokens: usage.cacheWriteTokens,
|
|
382
324
|
contextTokens,
|
|
383
325
|
});
|
|
384
|
-
if (calculated) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
326
|
+
if (calculated && Number.isFinite(calculated.costUsd)) {
|
|
327
|
+
providerMetadata.listCostUsd = calculated.costUsd;
|
|
328
|
+
providerMetadata.listPriceRef = calculated.priceRef;
|
|
329
|
+
providerMetadata.listPriceSha256 = catalog.sha256;
|
|
388
330
|
}
|
|
389
331
|
}
|
|
390
332
|
|
|
391
|
-
|
|
392
|
-
costBasis = "unmetered";
|
|
393
|
-
costUsd = null;
|
|
394
|
-
priceRef = `subscription:${harness}`;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return {
|
|
333
|
+
const result: VnextProducerResult = {
|
|
398
334
|
outcome,
|
|
399
335
|
costBasis,
|
|
400
336
|
costUsd,
|
|
@@ -407,15 +343,37 @@ export function createVnextOneShotProducer(options: VnextOneShotProducerOptions
|
|
|
407
343
|
harness,
|
|
408
344
|
provider: resolved.provider,
|
|
409
345
|
requestedModel: resolved.model,
|
|
410
|
-
effectiveModel:
|
|
346
|
+
effectiveModel: parsed.effectiveModel ?? "unknown",
|
|
347
|
+
providerMetadata,
|
|
348
|
+
...(effectUncertain ? { effectUncertain: true as const } : {}),
|
|
411
349
|
...(resolved.thinking !== undefined ? { thinking: resolved.thinking } : {}),
|
|
412
350
|
agentRole: request.agentRole ?? request.agentId,
|
|
413
351
|
...(priceRef !== undefined ? { priceRef } : {}),
|
|
414
352
|
};
|
|
353
|
+
try { providerMetadata.executionEvidenceSha256 = await evidence.finish(procResult, result); }
|
|
354
|
+
catch {
|
|
355
|
+
providerMetadata.evidenceWriteFailed = true;
|
|
356
|
+
return { ...result, outcome: "failed", effectUncertain: true };
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const producer: VnextOneShotProducer = {
|
|
362
|
+
id: "oneshot",
|
|
363
|
+
produce(request): Promise<VnextProducerResult> {
|
|
364
|
+
const controller = new AbortController();
|
|
365
|
+
const signal = AbortSignal.any([request.signal, controller.signal]);
|
|
366
|
+
const pending = Promise.resolve().then(() => executeRequest({ ...request, signal }))
|
|
367
|
+
.finally(() => { running.delete(controller); });
|
|
368
|
+
running.set(controller, pending);
|
|
369
|
+
return pending;
|
|
415
370
|
},
|
|
416
371
|
|
|
417
372
|
async close(): Promise<void> {
|
|
418
|
-
|
|
373
|
+
closed = true;
|
|
374
|
+
const pending = [...running.values()];
|
|
375
|
+
for (const controller of running.keys()) controller.abort();
|
|
376
|
+
await Promise.allSettled(pending);
|
|
419
377
|
},
|
|
420
378
|
};
|
|
421
379
|
|
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
type VnextProducerResult,
|
|
9
9
|
} from "./vnext-engine.ts";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
probeHarnessAssignmentAsync,
|
|
12
|
+
type HarnessAssignmentProbeOptions,
|
|
12
13
|
type HarnessInventory,
|
|
14
|
+
type HarnessStatus,
|
|
13
15
|
} from "./vnext-harness.ts";
|
|
14
16
|
import {
|
|
15
17
|
calculateModelCost,
|
|
@@ -386,7 +388,7 @@ export interface VnextPiProducerOptions {
|
|
|
386
388
|
priceCatalog?: PriceCatalog | undefined;
|
|
387
389
|
env?: NodeJS.ProcessEnv | undefined;
|
|
388
390
|
spawnProcess?: ((command: string, args: readonly string[], options: Record<string, unknown>) => PiRpcProcess) | undefined;
|
|
389
|
-
probeHarness?:
|
|
391
|
+
probeHarness?: ((options: Omit<HarnessAssignmentProbeOptions, "runCommand"> & { signal?: AbortSignal | undefined }) => HarnessStatus | Promise<HarnessStatus>) | undefined;
|
|
390
392
|
resolveModel?: ((agentId: string, runId: string) => { provider?: string | undefined; model?: string | undefined; thinking?: string | undefined } | undefined) | undefined;
|
|
391
393
|
defaultModel?: string | undefined;
|
|
392
394
|
defaultProvider?: string | undefined;
|
|
@@ -438,7 +440,7 @@ export function createVnextPiProducer(options: VnextPiProducerOptions = {}): Vne
|
|
|
438
440
|
return { provider: parsed.provider, model: parsed.model };
|
|
439
441
|
}
|
|
440
442
|
|
|
441
|
-
function checkPiAuth(provider: string, model: string): void {
|
|
443
|
+
async function checkPiAuth(provider: string, model: string, signal: AbortSignal): Promise<void> {
|
|
442
444
|
if (options.inventory) {
|
|
443
445
|
const piEntry = options.inventory.harnesses.find((h) => h.id === "pi");
|
|
444
446
|
if (!piEntry || !piEntry.detected || piEntry.authenticated === false) {
|
|
@@ -446,12 +448,14 @@ export function createVnextPiProducer(options: VnextPiProducerOptions = {}): Vne
|
|
|
446
448
|
}
|
|
447
449
|
}
|
|
448
450
|
|
|
449
|
-
const probeFn = options.probeHarness ??
|
|
450
|
-
const probe = probeFn({
|
|
451
|
+
const probeFn = options.probeHarness ?? probeHarnessAssignmentAsync;
|
|
452
|
+
const probe = await probeFn({
|
|
451
453
|
harness: "pi",
|
|
452
454
|
provider,
|
|
453
455
|
model,
|
|
454
456
|
env: options.env,
|
|
457
|
+
signal,
|
|
458
|
+
timeoutMs: 10_000,
|
|
455
459
|
});
|
|
456
460
|
|
|
457
461
|
if (!probe.detected) {
|
|
@@ -538,8 +542,8 @@ export function createVnextPiProducer(options: VnextPiProducerOptions = {}): Vne
|
|
|
538
542
|
const startTime = Date.now();
|
|
539
543
|
const resolved = resolveModelForRequest(request);
|
|
540
544
|
|
|
541
|
-
// Preflight auth check - fails closed
|
|
542
|
-
checkPiAuth(resolved.provider, resolved.model);
|
|
545
|
+
// Preflight auth check - fails closed via the async bounded probe
|
|
546
|
+
await checkPiAuth(resolved.provider, resolved.model, request.signal);
|
|
543
547
|
|
|
544
548
|
// Get or create session
|
|
545
549
|
const session = await getOrCreateSession(request, resolved.model);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { DatabaseSync } from "
|
|
4
|
+
import { DatabaseSync } from "./sqlite.ts";
|
|
5
5
|
import { VnextConfigError, validateRunEvent, vnextCanonicalJson, type JsonValue, type VnextConfigIssue, type VnextConfigOptions } from "./vnext-config.ts";
|
|
6
6
|
import { vnextUserStateRoot } from "./vnext-bindings.ts";
|
|
7
7
|
|
|
@@ -658,6 +658,40 @@ export class VnextRunEventStore {
|
|
|
658
658
|
this.database.close();
|
|
659
659
|
}
|
|
660
660
|
|
|
661
|
+
/** Persist the accepted prompt outside the event log (hashed in events only). */
|
|
662
|
+
putRunPrompt(runId: string, prompt: string): void {
|
|
663
|
+
const all = this.readRunPrompts();
|
|
664
|
+
all[runId] = prompt;
|
|
665
|
+
this.writeRunPrompts(all);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
getRunPrompt(runId: string): string | undefined {
|
|
669
|
+
const value = this.readRunPrompts()[runId];
|
|
670
|
+
return typeof value === "string" ? value : undefined;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private runPromptSidecarPath(): string {
|
|
674
|
+
return `${this.path}.run-prompts.json`;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private readRunPrompts(): Record<string, string> {
|
|
678
|
+
const sidecar = this.runPromptSidecarPath();
|
|
679
|
+
if (!existsSync(sidecar)) return {};
|
|
680
|
+
try {
|
|
681
|
+
const parsed = JSON.parse(readFileSync(sidecar, "utf8")) as unknown;
|
|
682
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
683
|
+
return Object.fromEntries(
|
|
684
|
+
Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === "string"),
|
|
685
|
+
);
|
|
686
|
+
} catch {
|
|
687
|
+
return {};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private writeRunPrompts(all: Record<string, string>): void {
|
|
692
|
+
writeFileSync(this.runPromptSidecarPath(), `${JSON.stringify(all)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
693
|
+
}
|
|
694
|
+
|
|
661
695
|
/** Run one immutable transaction, rolling back on any failure. */
|
|
662
696
|
transaction<T>(work: () => T): T {
|
|
663
697
|
return withDatabaseTransaction(this.database, work);
|