@interf/compiler 0.9.5 → 0.13.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 +96 -92
- package/TRADEMARKS.md +2 -13
- package/agent-skills/interf-actions/SKILL.md +95 -36
- package/agent-skills/interf-actions/references/cli.md +118 -51
- package/builtin-methods/interf-default/README.md +3 -4
- package/builtin-methods/interf-default/compile/stages/shape/SKILL.md +2 -2
- package/builtin-methods/interf-default/compile/stages/summarize/SKILL.md +2 -1
- package/builtin-methods/interf-default/improve/SKILL.md +1 -1
- package/builtin-methods/interf-default/method.json +10 -4
- package/builtin-methods/interf-default/method.schema.json +0 -9
- package/builtin-methods/interf-default/use/query/SKILL.md +5 -5
- package/dist/cli/commands/compile.d.ts +8 -25
- package/dist/cli/commands/compile.js +75 -360
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/login.d.ts +7 -0
- package/dist/cli/commands/login.js +39 -0
- package/dist/cli/commands/logout.d.ts +2 -0
- package/dist/cli/commands/logout.js +16 -0
- package/dist/cli/commands/method.d.ts +2 -0
- package/dist/cli/commands/method.js +113 -0
- package/dist/cli/commands/prep.d.ts +2 -0
- package/dist/cli/commands/prep.js +134 -0
- package/dist/cli/commands/reset.d.ts +8 -1
- package/dist/cli/commands/reset.js +47 -26
- package/dist/cli/commands/runs.d.ts +2 -0
- package/dist/cli/commands/runs.js +120 -0
- package/dist/cli/commands/status.d.ts +6 -1
- package/dist/cli/commands/status.js +68 -111
- package/dist/cli/commands/test.d.ts +6 -14
- package/dist/cli/commands/test.js +65 -181
- package/dist/cli/commands/web.d.ts +0 -9
- package/dist/cli/commands/web.js +147 -120
- package/dist/cli/commands/wizard.d.ts +9 -0
- package/dist/cli/commands/wizard.js +442 -0
- package/dist/cli/index.d.ts +7 -6
- package/dist/cli/index.js +13 -10
- package/dist/compiler-ui/404.html +1 -1
- package/dist/compiler-ui/__next.__PAGE__.txt +2 -2
- package/dist/compiler-ui/__next._full.txt +3 -3
- package/dist/compiler-ui/__next._head.txt +1 -1
- package/dist/compiler-ui/__next._index.txt +2 -2
- package/dist/compiler-ui/__next._tree.txt +2 -2
- package/dist/compiler-ui/_next/static/chunks/{18a8f2jkv3z.c.css → 045gole2ojo3g.css} +1 -1
- package/dist/compiler-ui/_next/static/chunks/{177mvn4rse235.js → 17t-lulmyawg5.js} +9 -9
- package/dist/compiler-ui/_not-found/__next._full.txt +2 -2
- package/dist/compiler-ui/_not-found/__next._head.txt +1 -1
- package/dist/compiler-ui/_not-found/__next._index.txt +2 -2
- package/dist/compiler-ui/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/compiler-ui/_not-found/__next._not-found.txt +1 -1
- package/dist/compiler-ui/_not-found/__next._tree.txt +2 -2
- package/dist/compiler-ui/_not-found.html +1 -1
- package/dist/compiler-ui/_not-found.txt +2 -2
- package/dist/compiler-ui/index.html +1 -1
- package/dist/compiler-ui/index.txt +3 -3
- package/dist/packages/agents/lib/shells.d.ts +1 -1
- package/dist/packages/agents/lib/shells.js +111 -52
- package/dist/packages/agents/lib/user-config.d.ts +4 -2
- package/dist/packages/agents/lib/user-config.js +15 -7
- package/dist/packages/compiler/compiled-paths.d.ts +9 -2
- package/dist/packages/compiler/compiled-paths.js +30 -15
- package/dist/packages/compiler/compiled-pipeline.js +23 -3
- package/dist/packages/compiler/compiled-stage-plan.js +4 -0
- package/dist/packages/compiler/compiled-target.d.ts +1 -1
- package/dist/packages/compiler/compiled-target.js +1 -1
- package/dist/packages/compiler/index.d.ts +1 -0
- package/dist/packages/compiler/index.js +1 -0
- package/dist/packages/compiler/lib/schema.d.ts +26 -31
- package/dist/packages/compiler/lib/schema.js +1 -12
- package/dist/packages/compiler/method-runs.d.ts +2 -3
- package/dist/packages/compiler/method-runs.js +2 -3
- package/dist/packages/compiler/reset.js +3 -1
- package/dist/packages/compiler/runtime-contracts.js +0 -3
- package/dist/packages/compiler/runtime-prompt.js +1 -1
- package/dist/packages/compiler/source-files.d.ts +46 -0
- package/dist/packages/compiler/source-files.js +149 -0
- package/dist/packages/compiler/state-artifacts.d.ts +3 -2
- package/dist/packages/compiler/state-artifacts.js +4 -3
- package/dist/packages/compiler/state-io.d.ts +3 -2
- package/dist/packages/compiler/state-io.js +11 -5
- package/dist/packages/compiler/state-paths.d.ts +2 -1
- package/dist/packages/compiler/state-paths.js +6 -3
- package/dist/packages/compiler/state-view.d.ts +3 -2
- package/dist/packages/compiler/state-view.js +18 -28
- package/dist/packages/compiler/state.d.ts +4 -4
- package/dist/packages/compiler/state.js +3 -3
- package/dist/packages/contracts/index.d.ts +1 -1
- package/dist/packages/contracts/lib/preparation-paths.d.ts +117 -0
- package/dist/packages/contracts/lib/preparation-paths.js +177 -0
- package/dist/packages/contracts/lib/schema.d.ts +85 -5
- package/dist/packages/contracts/lib/schema.js +46 -1
- package/dist/packages/execution/lib/schema.d.ts +50 -50
- package/dist/packages/execution/lib/schema.js +1 -1
- package/dist/packages/local-service/action-definitions.d.ts +14 -14
- package/dist/packages/local-service/action-definitions.js +27 -28
- package/dist/packages/local-service/action-planner.js +2 -1
- package/dist/packages/local-service/client.d.ts +51 -52
- package/dist/packages/local-service/client.js +132 -140
- package/dist/packages/local-service/connection-config.d.ts +38 -0
- package/dist/packages/local-service/connection-config.js +75 -0
- package/dist/packages/local-service/index.d.ts +11 -7
- package/dist/packages/local-service/index.js +6 -4
- package/dist/packages/local-service/instance-paths.d.ts +100 -0
- package/dist/packages/local-service/instance-paths.js +165 -0
- package/dist/packages/local-service/lib/schema.d.ts +405 -2297
- package/dist/packages/local-service/lib/schema.js +146 -62
- package/dist/packages/local-service/native-run-handlers.js +3 -3
- package/dist/packages/local-service/preparation-store.d.ts +92 -0
- package/dist/packages/local-service/preparation-store.js +171 -0
- package/dist/packages/local-service/routes.d.ts +33 -16
- package/dist/packages/local-service/routes.js +44 -20
- package/dist/packages/local-service/run-observability.js +11 -11
- package/dist/packages/local-service/runtime-caches.d.ts +76 -0
- package/dist/packages/local-service/runtime-caches.js +191 -0
- package/dist/packages/local-service/runtime-event-applier.d.ts +12 -0
- package/dist/packages/local-service/runtime-event-applier.js +177 -0
- package/dist/packages/local-service/runtime-persistence.d.ts +47 -0
- package/dist/packages/local-service/runtime-persistence.js +137 -0
- package/dist/packages/local-service/runtime-proposal-helpers.d.ts +35 -0
- package/dist/packages/local-service/runtime-proposal-helpers.js +251 -0
- package/dist/packages/local-service/runtime-resource-builders.d.ts +52 -0
- package/dist/packages/local-service/runtime-resource-builders.js +149 -0
- package/dist/packages/local-service/runtime.d.ts +197 -43
- package/dist/packages/local-service/runtime.js +800 -974
- package/dist/packages/local-service/server.d.ts +15 -0
- package/dist/packages/local-service/server.js +641 -273
- package/dist/packages/local-service/service-registry.d.ts +47 -0
- package/dist/packages/local-service/service-registry.js +137 -0
- package/dist/packages/method-authoring/method-authoring.d.ts +1 -1
- package/dist/packages/method-authoring/method-authoring.js +2 -2
- package/dist/packages/method-authoring/method-improvement.js +1 -1
- package/dist/packages/method-package/builtin-compiled-method.d.ts +4 -5
- package/dist/packages/method-package/builtin-compiled-method.js +8 -14
- package/dist/packages/method-package/context-interface.d.ts +4 -40
- package/dist/packages/method-package/context-interface.js +1 -23
- package/dist/packages/method-package/interf-method-package.d.ts +4 -4
- package/dist/packages/method-package/interf-method-package.js +21 -33
- package/dist/packages/method-package/local-methods.d.ts +10 -6
- package/dist/packages/method-package/local-methods.js +57 -39
- package/dist/packages/method-package/method-definitions.d.ts +8 -34
- package/dist/packages/method-package/method-definitions.js +49 -37
- package/dist/packages/method-package/method-helpers.d.ts +1 -13
- package/dist/packages/method-package/method-helpers.js +8 -42
- package/dist/packages/method-package/method-stage-runner.js +2 -2
- package/dist/packages/method-package/user-methods.d.ts +17 -0
- package/dist/packages/method-package/user-methods.js +77 -0
- package/dist/packages/project-model/index.d.ts +0 -1
- package/dist/packages/project-model/index.js +0 -1
- package/dist/packages/project-model/interf-detect.d.ts +8 -3
- package/dist/packages/project-model/interf-detect.js +34 -34
- package/dist/packages/project-model/interf-scaffold.d.ts +3 -3
- package/dist/packages/project-model/interf-scaffold.js +23 -32
- package/dist/packages/project-model/lib/schema.js +38 -1
- package/dist/packages/project-model/preparation-entries.d.ts +5 -5
- package/dist/packages/project-model/preparation-entries.js +14 -14
- package/dist/packages/project-model/source-config.d.ts +11 -11
- package/dist/packages/project-model/source-config.js +74 -46
- package/dist/packages/project-model/source-folders.d.ts +5 -5
- package/dist/packages/project-model/source-folders.js +14 -14
- package/dist/packages/shared/filesystem.d.ts +7 -0
- package/dist/packages/shared/filesystem.js +97 -10
- package/dist/packages/testing/lib/schema.d.ts +10 -10
- package/dist/packages/testing/lib/schema.js +2 -2
- package/dist/packages/testing/readiness-check-run.d.ts +4 -4
- package/dist/packages/testing/readiness-check-run.js +36 -36
- package/dist/packages/testing/test-execution.js +6 -6
- package/dist/packages/testing/test-paths.js +4 -3
- package/dist/packages/testing/test-sandbox.d.ts +0 -1
- package/dist/packages/testing/test-sandbox.js +14 -30
- package/dist/packages/testing/test-targets.d.ts +1 -1
- package/dist/packages/testing/test-targets.js +6 -6
- package/dist/packages/testing/test.d.ts +1 -1
- package/dist/packages/testing/test.js +1 -1
- package/package.json +3 -4
- package/CHANGELOG.md +0 -93
- package/LICENSE +0 -183
- package/dist/cli/commands/action-input-cli.d.ts +0 -25
- package/dist/cli/commands/action-input-cli.js +0 -73
- package/dist/cli/commands/control-path.d.ts +0 -11
- package/dist/cli/commands/control-path.js +0 -72
- package/dist/cli/commands/create-method-wizard.d.ts +0 -64
- package/dist/cli/commands/create-method-wizard.js +0 -434
- package/dist/cli/commands/create.d.ts +0 -6
- package/dist/cli/commands/create.js +0 -183
- package/dist/cli/commands/default.d.ts +0 -2
- package/dist/cli/commands/default.js +0 -39
- package/dist/cli/commands/executor-flow.d.ts +0 -29
- package/dist/cli/commands/executor-flow.js +0 -163
- package/dist/cli/commands/init.d.ts +0 -26
- package/dist/cli/commands/init.js +0 -771
- package/dist/cli/commands/list.d.ts +0 -2
- package/dist/cli/commands/list.js +0 -30
- package/dist/cli/commands/preparation-action.d.ts +0 -8
- package/dist/cli/commands/preparation-action.js +0 -29
- package/dist/cli/commands/preparation-picker.d.ts +0 -5
- package/dist/cli/commands/preparation-picker.js +0 -36
- package/dist/cli/commands/preparation-selection.d.ts +0 -6
- package/dist/cli/commands/preparation-selection.js +0 -11
- package/dist/cli/commands/service-action-flow.d.ts +0 -9
- package/dist/cli/commands/service-action-flow.js +0 -19
- package/dist/cli/commands/source-config-wizard.d.ts +0 -51
- package/dist/cli/commands/source-config-wizard.js +0 -670
- package/dist/cli/commands/verify.d.ts +0 -2
- package/dist/cli/commands/verify.js +0 -94
- package/dist/packages/compiler/raw-snapshot.d.ts +0 -49
- package/dist/packages/compiler/raw-snapshot.js +0 -101
- package/dist/packages/method-package/index.d.ts +0 -11
- package/dist/packages/method-package/index.js +0 -11
- package/dist/packages/method-package/method-stage-policy.d.ts +0 -5
- package/dist/packages/method-package/method-stage-policy.js +0 -31
- package/dist/packages/project-model/project-paths.d.ts +0 -12
- package/dist/packages/project-model/project-paths.js +0 -33
- /package/dist/compiler-ui/_next/static/{84FaeF3EzBF9kKTMjSEVN → C6vVfy3aeYuIO3d2AoNvC}/_buildManifest.js +0 -0
- /package/dist/compiler-ui/_next/static/{84FaeF3EzBF9kKTMjSEVN → C6vVfy3aeYuIO3d2AoNvC}/_clientMiddlewareManifest.js +0 -0
- /package/dist/compiler-ui/_next/static/{84FaeF3EzBF9kKTMjSEVN → C6vVfy3aeYuIO3d2AoNvC}/_ssgManifest.js +0 -0
|
@@ -1,714 +1,233 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, statSync, } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
import { CompileRunSchema, } from "../execution/lib/schema.js";
|
|
4
4
|
import { createRunEventId, createRunEventTimestamp, } from "../execution/events.js";
|
|
5
|
-
import { compiledRuntimeRunHistoryPath, compiledRuntimeRoot, testRootForCompiled, } from "../compiler/compiled-paths.js";
|
|
6
5
|
import { loadState, } from "../compiler/state.js";
|
|
7
|
-
import {
|
|
6
|
+
import { actionProposalPath, actionProposalsRoot, byCreatedAtDesc, compileRunPath, compileRunsRoot, listJsonFiles, localJobPath, localJobsRoot, newestFirst, readActionProposalAt, readCompileRunAt, readLocalJobRunAt, readRuntimeRunHistory, readTestRunAt, testRunPath, testRunsRoot, timestampKey, writeJsonFile, } from "./runtime-persistence.js";
|
|
7
|
+
import { MethodListingCache, MtimeListingCache, ReadinessCache, RunListingCache, } from "./runtime-caches.js";
|
|
8
|
+
import { applyEventToCompileRun, applyEventToLocalJob, } from "./runtime-event-applier.js";
|
|
9
|
+
import { buildMethodResource, buildPreparationResource, createRunId, logsForRuntimeRun, logsForStageRun, proofForStage, readinessStateToPreparationReadiness, readinessSummaryForStatus, readinessTargetResult, stageArtifactRefs, } from "./runtime-resource-builders.js";
|
|
10
|
+
import { ACTION_PLANNER_CLARIFICATION_MESSAGE, actionAssistantMessage, actionCommandPreview, actionTypeFromValues, actionValueMethodTaskPrompt, configuredAgentName, createActionProposalId, detachMethodFromPreparation, detectedExecutorOptions, directServiceEndpointForAction, hasCompiledTestTarget, methodAuthoringHintFromPrompt, methodAuthoringPromptFallback, methodIdForProposal, methodLabelFromId, numberValue, requireSelectedMethod, sanitizeActionProposalPlan, stringValue, testModeFromValues, testModeValue, } from "./runtime-proposal-helpers.js";
|
|
8
11
|
import { ReadinessStateSchema, } from "../contracts/lib/schema.js";
|
|
9
12
|
import { discoverSourceFiles, } from "../compiler/discovery.js";
|
|
10
13
|
import { resetCompiledGeneratedState, } from "../compiler/reset.js";
|
|
11
14
|
import { ensurePortableContextScaffold, readInterfConfig, } from "../project-model/interf.js";
|
|
12
15
|
import { findSourcePreparationConfig, fingerprintReadinessChecks, listSourcePreparationConfigs, loadSourceFolderConfig, DEFAULT_METHOD_ID, methodIdForSourcePreparationConfig, resolveConfiguredSourceFolderPath, resolveSourcePreparationPath, removeSourcePreparationConfig, saveSourceFolderConfig, syncCompiledInterfConfigFromSourcePreparationConfig, upsertSourcePreparationConfig, } from "../project-model/source-config.js";
|
|
13
16
|
import { listSourceFolderChoices, } from "../project-model/source-folders.js";
|
|
14
|
-
import {
|
|
17
|
+
import { asPreparationDataDir, preparationPortableContextPath, userMethodsRoot, preparationConfigPath, preparationMethodPackagePath, preparationMethodsRoot, } from "../contracts/lib/preparation-paths.js";
|
|
15
18
|
import { getCompiledMethod, listCompiledMethodChoices, } from "../method-package/method-definitions.js";
|
|
16
19
|
import { contextInterfaceArtifactPath, } from "../method-package/context-interface.js";
|
|
17
|
-
import { methodDefinitionPath, resolveMethodPackageSourcePath, } from "../method-package/local-methods.js";
|
|
20
|
+
import { methodDefinitionPath, resolveMethodPackageSourcePath, seedLocalDefaultMethod, } from "../method-package/local-methods.js";
|
|
18
21
|
import { seedLocalMethodPackageFromBase, } from "../method-package/interf-method-package.js";
|
|
22
|
+
import { PACKAGE_ROOT } from "../method-package/lib/package-root.js";
|
|
19
23
|
import { resolveAgent, detectAgents, supportsAutomatedRuns, } from "../agents/lib/detection.js";
|
|
20
|
-
import { AGENTS, } from "../agents/lib/constants.js";
|
|
21
24
|
import { loadUserConfig, saveUserConfig, } from "../agents/lib/user-config.js";
|
|
22
25
|
import { readSavedReadinessCheckRun, } from "../testing/readiness-check-run.js";
|
|
23
26
|
import { createCompiledTestTarget, } from "../testing/test-targets.js";
|
|
24
|
-
import { ActionProposalApprovalRequestSchema, ActionProposalCreateRequestSchema, ActionProposalPlanSchema, ActionProposalResourceSchema,
|
|
27
|
+
import { ActionProposalApprovalRequestSchema, ActionProposalCreateRequestSchema, ActionProposalPlanSchema, ActionProposalResourceSchema, ActionProposalTypeSchema, CompileRunCreateRequestSchema, CompileRunResourceSchema, LocalExecutorStatusSchema, LocalExecutorSelectRequestSchema, LocalServiceHealthSchema, LocalRunHandlerResultSchema, LocalJobEventAppendRequestSchema, LocalJobRunCreateRequestSchema, LocalJobRunResourceSchema, SourceFileResourceSchema, WorkspaceFileResourceSchema, PortableContextResourceSchema, PreparationSetupCreateRequestSchema, PreparationSetupResultSchema, WorkspaceBootstrapCreateRequestSchema, WorkspaceBootstrapResultSchema, MethodChangeCreateRequestSchema, MethodChangeResultSchema, PreparationChangeCreateRequestSchema, PreparationChangeResultSchema, ReadinessCheckDraftCreateRequestSchema, ReadinessCheckDraftResultSchema, ResetRequestSchema, ResetResultSchema, ServiceRegistryWorkspaceSchema, TestRunCreateRequestSchema, TestRunResourceSchema, MethodAuthoringCreateRequestSchema, MethodAuthoringResultSchema, } from "./lib/schema.js";
|
|
25
28
|
import { buildLocalServiceUrl, } from "./routes.js";
|
|
26
|
-
import {
|
|
29
|
+
import { MethodAuthoringActionValuesSchema, PreparationSetupActionValuesSchema, } from "./action-values.js";
|
|
27
30
|
import { compileRunToObservability, jobRunToObservability, testRunToObservability, uniqueArtifacts, } from "./run-observability.js";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return `action_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
33
|
-
}
|
|
34
|
-
const ACTION_PLANNER_CLARIFICATION_MESSAGE = "I can help with this Interf Workspace. Ask a question about Interf, or ask me to create a Preparation, prepare, check readiness, improve, or draft a Method and I will prepare an approval proposal.";
|
|
35
|
-
function readJsonFile(filePath) {
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function writeJsonFile(filePath, value) {
|
|
44
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
45
|
-
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
46
|
-
}
|
|
47
|
-
function sanitizeActionProposalPlan(value) {
|
|
48
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
49
|
-
return value;
|
|
50
|
-
const plan = { ...value };
|
|
51
|
-
for (const key of ["preparation", "method", "title", "summary", "assistant_message", "command_preview"]) {
|
|
52
|
-
const current = plan[key];
|
|
53
|
-
if (current === null || current === undefined) {
|
|
54
|
-
delete plan[key];
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (typeof current === "string" && current.trim().length === 0) {
|
|
58
|
-
delete plan[key];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return plan;
|
|
62
|
-
}
|
|
63
|
-
function compileRunsRoot(compiledPath) {
|
|
64
|
-
return join(compiledRuntimeRoot(compiledPath), "compile-runs");
|
|
65
|
-
}
|
|
66
|
-
function compileRunPath(compiledPath, runId) {
|
|
67
|
-
return join(compileRunsRoot(compiledPath), `${runId}.json`);
|
|
68
|
-
}
|
|
69
|
-
function testRunsRoot(compiledPath) {
|
|
70
|
-
return join(testRootForCompiled(compiledPath), "service-runs");
|
|
71
|
-
}
|
|
72
|
-
function testRunPath(compiledPath, runId) {
|
|
73
|
-
return join(testRunsRoot(compiledPath), `${runId}.json`);
|
|
74
|
-
}
|
|
75
|
-
function localJobsRoot(rootPath) {
|
|
76
|
-
return join(rootPath, "interf", ".service", "jobs");
|
|
77
|
-
}
|
|
78
|
-
function localJobPath(rootPath, runId) {
|
|
79
|
-
return join(localJobsRoot(rootPath), `${runId}.json`);
|
|
80
|
-
}
|
|
81
|
-
function actionProposalsRoot(rootPath) {
|
|
82
|
-
return join(rootPath, "interf", ".service", "action-proposals");
|
|
83
|
-
}
|
|
84
|
-
function actionProposalPath(rootPath, proposalId) {
|
|
85
|
-
return join(actionProposalsRoot(rootPath), `${proposalId}.json`);
|
|
86
|
-
}
|
|
87
|
-
function listJsonFiles(dirPath) {
|
|
88
|
-
if (!existsSync(dirPath))
|
|
89
|
-
return [];
|
|
90
|
-
try {
|
|
91
|
-
if (!statSync(dirPath).isDirectory())
|
|
92
|
-
return [];
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
return readdirSync(dirPath)
|
|
98
|
-
.filter((entry) => entry.endsWith(".json"))
|
|
99
|
-
.map((entry) => join(dirPath, entry));
|
|
100
|
-
}
|
|
101
|
-
function readCompileRunAt(filePath) {
|
|
102
|
-
const parsed = CompileRunSchema.safeParse(readJsonFile(filePath));
|
|
103
|
-
return parsed.success ? parsed.data : null;
|
|
104
|
-
}
|
|
105
|
-
function readRuntimeRunHistory(compiledPath) {
|
|
106
|
-
const historyPath = compiledRuntimeRunHistoryPath(compiledPath);
|
|
107
|
-
if (!existsSync(historyPath))
|
|
108
|
-
return [];
|
|
109
|
-
try {
|
|
110
|
-
return readFileSync(historyPath, "utf8")
|
|
111
|
-
.split(/\r?\n/)
|
|
112
|
-
.map((line) => line.trim())
|
|
113
|
-
.filter((line) => line.length > 0)
|
|
114
|
-
.map((line) => {
|
|
115
|
-
try {
|
|
116
|
-
return RuntimeRunSchema.safeParse(JSON.parse(line));
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return { success: false };
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
.filter((parsed) => parsed.success)
|
|
123
|
-
.map((parsed) => parsed.data);
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function readTestRunAt(filePath) {
|
|
130
|
-
const parsed = TestRunResourceSchema.safeParse(readJsonFile(filePath));
|
|
131
|
-
return parsed.success ? parsed.data : null;
|
|
132
|
-
}
|
|
133
|
-
function readLocalJobRunAt(filePath) {
|
|
134
|
-
const parsed = LocalJobRunResourceSchema.safeParse(readJsonFile(filePath));
|
|
135
|
-
return parsed.success ? parsed.data : null;
|
|
136
|
-
}
|
|
137
|
-
function readActionProposalAt(filePath) {
|
|
138
|
-
const parsed = ActionProposalResourceSchema.safeParse(readJsonFile(filePath));
|
|
139
|
-
return parsed.success ? parsed.data : null;
|
|
140
|
-
}
|
|
141
|
-
function newestFirst(items) {
|
|
142
|
-
return [...items].sort((left, right) => {
|
|
143
|
-
const leftTime = Date.parse(left.started_at ?? left.finished_at ?? "");
|
|
144
|
-
const rightTime = Date.parse(right.started_at ?? right.finished_at ?? "");
|
|
145
|
-
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
function newestJobFirst(items) {
|
|
149
|
-
return [...items].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
|
|
150
|
-
}
|
|
151
|
-
function newestActionProposalFirst(items) {
|
|
152
|
-
return [...items].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
|
|
153
|
-
}
|
|
154
|
-
function newestCompileFirst(items) {
|
|
155
|
-
return [...items].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
|
|
156
|
-
}
|
|
157
|
-
function configuredAgentName() {
|
|
158
|
-
const config = loadUserConfig();
|
|
159
|
-
if (!config)
|
|
160
|
-
return null;
|
|
161
|
-
const configured = AGENTS.find((agent) => agent.command === config.agentCommand) ??
|
|
162
|
-
AGENTS.find((agent) => agent.name === config.agent) ??
|
|
163
|
-
AGENTS.find((agent) => agent.displayName === config.agent);
|
|
164
|
-
return configured?.name ?? null;
|
|
165
|
-
}
|
|
166
|
-
function detectedExecutorOptions(currentAgentName) {
|
|
167
|
-
return detectAgents()
|
|
168
|
-
.filter(supportsAutomatedRuns)
|
|
169
|
-
.map((agent) => ({
|
|
170
|
-
name: agent.name,
|
|
171
|
-
display_name: agent.displayName,
|
|
172
|
-
command: agent.command,
|
|
173
|
-
current: agent.name === currentAgentName,
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
176
|
-
function stageArtifactRefs(stageId, artifacts) {
|
|
177
|
-
return (artifacts ?? []).map((path) => ({
|
|
178
|
-
path,
|
|
179
|
-
role: "output",
|
|
180
|
-
stage_id: stageId,
|
|
181
|
-
label: path,
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
184
|
-
function proofForStage(options) {
|
|
185
|
-
return {
|
|
186
|
-
id: `${options.runId}-${options.stageId}-proof`,
|
|
187
|
-
run_id: options.runId,
|
|
188
|
-
stage_id: options.stageId,
|
|
189
|
-
generated_at: options.stageState.finished_at ?? new Date().toISOString(),
|
|
190
|
-
summary: options.summary ?? `${options.stageId} produced stage evidence.`,
|
|
191
|
-
files_processed: options.stageState.counts?.source_total,
|
|
192
|
-
artifacts: options.artifacts,
|
|
193
|
-
checks: [
|
|
194
|
-
{
|
|
195
|
-
id: `${options.stageId}-status`,
|
|
196
|
-
label: "stage completed",
|
|
197
|
-
ok: options.stageState.status === "succeeded",
|
|
198
|
-
...(options.stageState.status === "succeeded"
|
|
199
|
-
? {}
|
|
200
|
-
: { detail: options.stageState.summary ?? "Stage did not complete successfully." }),
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
id: `${options.stageId}-artifacts`,
|
|
204
|
-
label: "artifacts recorded",
|
|
205
|
-
ok: options.artifacts.length > 0,
|
|
206
|
-
...(options.artifacts.length > 0
|
|
207
|
-
? {}
|
|
208
|
-
: { detail: "No stage artifacts were recorded." }),
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function logsForStageRun(stageState) {
|
|
214
|
-
const runId = stageState?.run_id;
|
|
215
|
-
if (!runId)
|
|
216
|
-
return undefined;
|
|
217
|
-
return {
|
|
218
|
-
prompt_path: `.interf/runtime/logs/${runId}.prompt.txt`,
|
|
219
|
-
event_stream_path: `.interf/runtime/logs/${runId}.events.ndjson`,
|
|
220
|
-
status_path: `.interf/runtime/logs/${runId}.status.log`,
|
|
221
|
-
contract_path: `.interf/runtime/logs/${runId}.stage-contract.json`,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
function logsForRuntimeRun(run) {
|
|
225
|
-
if (!run)
|
|
226
|
-
return undefined;
|
|
227
|
-
return {
|
|
228
|
-
...(run.logs?.prompt_path ? { prompt_path: run.logs.prompt_path } : {}),
|
|
229
|
-
...(run.logs?.event_stream_path ? { event_stream_path: run.logs.event_stream_path } : {}),
|
|
230
|
-
...(run.logs?.status_path ? { status_path: run.logs.status_path } : {}),
|
|
231
|
-
contract_path: run.contract_path,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
function timestampKey(value) {
|
|
235
|
-
const parsed = Date.parse(value ?? "");
|
|
236
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
237
|
-
}
|
|
238
|
-
function applyEventToCompileRun(run, event) {
|
|
239
|
-
const now = event.timestamp;
|
|
240
|
-
const stageFor = (stageId) => {
|
|
241
|
-
const existing = run.stages.find((stage) => stage.stage_id === stageId);
|
|
242
|
-
if (existing)
|
|
243
|
-
return existing;
|
|
244
|
-
const created = {
|
|
245
|
-
run_id: run.run_id,
|
|
246
|
-
stage_id: stageId,
|
|
247
|
-
status: "queued",
|
|
248
|
-
artifacts: [],
|
|
249
|
-
};
|
|
250
|
-
run.stages.push(created);
|
|
251
|
-
return created;
|
|
252
|
-
};
|
|
253
|
-
const updateStage = (stageId, patch) => {
|
|
254
|
-
const current = stageFor(stageId);
|
|
255
|
-
Object.assign(current, patch);
|
|
256
|
-
};
|
|
257
|
-
switch (event.type) {
|
|
258
|
-
case "run.started":
|
|
259
|
-
return {
|
|
260
|
-
...run,
|
|
261
|
-
status: "running",
|
|
262
|
-
started_at: run.started_at ?? now,
|
|
263
|
-
events: [...run.events, event],
|
|
264
|
-
};
|
|
265
|
-
case "stage.started":
|
|
266
|
-
updateStage(event.stage_id, {
|
|
267
|
-
status: "running",
|
|
268
|
-
started_at: now,
|
|
269
|
-
stage_index: event.stage_index,
|
|
270
|
-
stage_total: event.stage_total,
|
|
271
|
-
});
|
|
272
|
-
break;
|
|
273
|
-
case "artifact.written": {
|
|
274
|
-
const stage = stageFor(event.stage_id);
|
|
275
|
-
updateStage(event.stage_id, {
|
|
276
|
-
artifacts: uniqueArtifacts([...(stage.artifacts ?? []), event.artifact]),
|
|
277
|
-
});
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
case "proof.updated":
|
|
281
|
-
if (event.stage_id) {
|
|
282
|
-
updateStage(event.stage_id, {
|
|
283
|
-
latest_proof: event.proof,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
...run,
|
|
288
|
-
latest_proof: event.proof,
|
|
289
|
-
events: [...run.events, event],
|
|
290
|
-
};
|
|
291
|
-
case "log.appended":
|
|
292
|
-
break;
|
|
293
|
-
case "stage.passed":
|
|
294
|
-
updateStage(event.stage_id, {
|
|
295
|
-
status: "succeeded",
|
|
296
|
-
finished_at: now,
|
|
297
|
-
summary: event.summary ?? null,
|
|
298
|
-
failure: null,
|
|
299
|
-
});
|
|
300
|
-
break;
|
|
301
|
-
case "stage.failed":
|
|
302
|
-
updateStage(event.stage_id, {
|
|
303
|
-
status: "failed",
|
|
304
|
-
finished_at: now,
|
|
305
|
-
summary: event.error,
|
|
306
|
-
failure: event.error,
|
|
307
|
-
});
|
|
308
|
-
break;
|
|
309
|
-
case "run.completed":
|
|
310
|
-
return {
|
|
311
|
-
...run,
|
|
312
|
-
status: "succeeded",
|
|
313
|
-
finished_at: run.finished_at ?? now,
|
|
314
|
-
events: [...run.events, event],
|
|
315
|
-
};
|
|
316
|
-
case "run.failed":
|
|
317
|
-
return {
|
|
318
|
-
...run,
|
|
319
|
-
status: "failed",
|
|
320
|
-
finished_at: run.finished_at ?? now,
|
|
321
|
-
events: [...run.events, event],
|
|
322
|
-
};
|
|
323
|
-
case "readiness.updated":
|
|
324
|
-
return {
|
|
325
|
-
...run,
|
|
326
|
-
readiness: event.readiness,
|
|
327
|
-
events: [...run.events, event],
|
|
328
|
-
};
|
|
329
|
-
default:
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
...run,
|
|
334
|
-
events: [...run.events, event],
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function applyEventToLocalJob(run, event) {
|
|
338
|
-
const stepFor = (stepId) => {
|
|
339
|
-
const existing = run.steps.find((step) => step.id === stepId);
|
|
340
|
-
if (existing)
|
|
341
|
-
return existing;
|
|
342
|
-
const created = {
|
|
343
|
-
id: stepId,
|
|
344
|
-
label: stepId,
|
|
345
|
-
status: "queued",
|
|
346
|
-
};
|
|
347
|
-
run.steps.push(created);
|
|
348
|
-
return created;
|
|
349
|
-
};
|
|
350
|
-
const updateStep = (stepId, patch) => {
|
|
351
|
-
if (!stepId)
|
|
352
|
-
return;
|
|
353
|
-
Object.assign(stepFor(stepId), patch);
|
|
354
|
-
};
|
|
355
|
-
switch (event.type) {
|
|
356
|
-
case "job.started":
|
|
357
|
-
return {
|
|
358
|
-
...run,
|
|
359
|
-
status: "running",
|
|
360
|
-
started_at: run.started_at ?? event.timestamp,
|
|
361
|
-
events: [...run.events, event],
|
|
362
|
-
};
|
|
363
|
-
case "step.started":
|
|
364
|
-
updateStep(event.step_id, {
|
|
365
|
-
status: "running",
|
|
366
|
-
started_at: event.timestamp,
|
|
367
|
-
...(event.input ? { input: event.input } : {}),
|
|
368
|
-
});
|
|
369
|
-
break;
|
|
370
|
-
case "step.completed":
|
|
371
|
-
updateStep(event.step_id, {
|
|
372
|
-
status: "succeeded",
|
|
373
|
-
finished_at: event.timestamp,
|
|
374
|
-
summary: event.message ?? null,
|
|
375
|
-
...(event.output ? { output: event.output } : {}),
|
|
376
|
-
});
|
|
377
|
-
break;
|
|
378
|
-
case "step.failed":
|
|
379
|
-
updateStep(event.step_id, {
|
|
380
|
-
status: "failed",
|
|
381
|
-
finished_at: event.timestamp,
|
|
382
|
-
summary: event.message ?? null,
|
|
383
|
-
...(event.output ? { output: event.output } : {}),
|
|
384
|
-
});
|
|
385
|
-
break;
|
|
386
|
-
case "job.completed":
|
|
387
|
-
return {
|
|
388
|
-
...run,
|
|
389
|
-
status: "succeeded",
|
|
390
|
-
finished_at: run.finished_at ?? event.timestamp,
|
|
391
|
-
events: [...run.events, event],
|
|
392
|
-
};
|
|
393
|
-
case "job.failed":
|
|
394
|
-
return {
|
|
395
|
-
...run,
|
|
396
|
-
status: "failed",
|
|
397
|
-
finished_at: run.finished_at ?? event.timestamp,
|
|
398
|
-
error: event.message ?? "Job failed.",
|
|
399
|
-
events: [...run.events, event],
|
|
400
|
-
};
|
|
401
|
-
case "artifact.written":
|
|
402
|
-
case "log.appended":
|
|
403
|
-
break;
|
|
404
|
-
default:
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
return {
|
|
408
|
-
...run,
|
|
409
|
-
events: [...run.events, event],
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
function slugFromText(value) {
|
|
413
|
-
const slug = value
|
|
414
|
-
.toLowerCase()
|
|
415
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
416
|
-
.replace(/^-+|-+$/g, "")
|
|
417
|
-
.slice(0, 42)
|
|
418
|
-
.replace(/-+$/g, "");
|
|
419
|
-
return slug || "custom";
|
|
420
|
-
}
|
|
421
|
-
function escapeRegExp(value) {
|
|
422
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
423
|
-
}
|
|
424
|
-
function stringValue(values, key) {
|
|
425
|
-
const value = values?.[key];
|
|
426
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
427
|
-
}
|
|
428
|
-
function actionTypeFromValues(values) {
|
|
429
|
-
const explicit = stringValue(values, "action_type") ?? stringValue(values, "service_action");
|
|
430
|
-
if (explicit) {
|
|
431
|
-
const parsed = ActionProposalPlanActionTypeSchema.safeParse(explicit);
|
|
432
|
-
if (parsed.success && parsed.data !== "clarification")
|
|
433
|
-
return parsed.data;
|
|
434
|
-
}
|
|
435
|
-
const action = stringValue(values, "action");
|
|
436
|
-
if (action === "compile" || action === "prepare-run" || action === "run-preparation")
|
|
437
|
-
return "compile";
|
|
438
|
-
if (action === "test" || action === "check-readiness")
|
|
439
|
-
return "test";
|
|
440
|
-
if (action === "draft-readiness-checks" || action === "readiness-check-draft")
|
|
441
|
-
return "readiness-check-draft";
|
|
442
|
-
if (action === "create-method" || action === "method-authoring")
|
|
443
|
-
return "method-authoring";
|
|
444
|
-
if (action === "method-duplicate" || action === "method-remove" || action === "method-change")
|
|
445
|
-
return "method-change";
|
|
446
|
-
if (action === "preparation-remove" || action === "preparation-change")
|
|
447
|
-
return "preparation-change";
|
|
448
|
-
if (action === "improve-preparation" || action === "method-improvement")
|
|
449
|
-
return "method-improvement";
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
452
|
-
function directServiceEndpointForAction(actionType) {
|
|
453
|
-
if (actionType === "preparation-setup")
|
|
454
|
-
return "/v1/preparation-setups";
|
|
455
|
-
if (actionType === "method-change")
|
|
456
|
-
return "/v1/method-changes";
|
|
457
|
-
if (actionType === "preparation-change")
|
|
458
|
-
return "/v1/preparation-changes";
|
|
459
|
-
return null;
|
|
460
|
-
}
|
|
461
|
-
function numberValue(values, key) {
|
|
462
|
-
const value = values?.[key];
|
|
463
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
464
|
-
}
|
|
465
|
-
function testModeFromValues(values) {
|
|
466
|
-
const value = stringValue(values, "mode") ?? stringValue(values, "target");
|
|
467
|
-
if (value === "source-files")
|
|
468
|
-
return "raw";
|
|
469
|
-
if (value === "portable-context")
|
|
470
|
-
return "compiled";
|
|
471
|
-
return value === "raw" || value === "compiled" || value === "both" ? value : null;
|
|
472
|
-
}
|
|
473
|
-
function testModeValue(values, defaultMode = "both") {
|
|
474
|
-
return testModeFromValues(values) ?? defaultMode;
|
|
475
|
-
}
|
|
476
|
-
function testModeCliTarget(mode) {
|
|
477
|
-
if (mode === "raw")
|
|
478
|
-
return "source-files";
|
|
479
|
-
if (mode === "compiled")
|
|
480
|
-
return "portable-context";
|
|
481
|
-
return "both";
|
|
482
|
-
}
|
|
483
|
-
function methodIdForProposal(message, values) {
|
|
484
|
-
const explicit = stringValue(values, "method_id") ??
|
|
485
|
-
stringValue(values, "method");
|
|
486
|
-
const fromMessage = message.match(/\b(?:create|reate|eate|draft|author|build|make)\s+(?:a\s+new\s+)?(?:interf\s+)?method\s+([a-z0-9][a-z0-9-]{0,79})\b/i)?.[1];
|
|
487
|
-
return explicit ?? fromMessage ?? `custom-${slugFromText(message)}`;
|
|
488
|
-
}
|
|
489
|
-
function actionValueMethodTaskPrompt(values) {
|
|
490
|
-
const parsed = MethodAuthoringActionValuesSchema.safeParse(values);
|
|
491
|
-
return parsed.success ? methodAuthoringTaskPrompt(parsed.data) : null;
|
|
492
|
-
}
|
|
493
|
-
const METHOD_AUTHORING_INTERNAL_INSTRUCTION = /Use the attached values as the Method authoring request before proposing the action\.?/gi;
|
|
494
|
-
const METHOD_AUTHORING_LABELS = [
|
|
495
|
-
"Agent work",
|
|
496
|
-
"Portable-context output",
|
|
497
|
-
"Readiness checks",
|
|
498
|
-
"CLI preview",
|
|
499
|
-
];
|
|
500
|
-
function normalizeMethodAuthoringText(value) {
|
|
501
|
-
return value
|
|
502
|
-
.replace(/[│]+/g, " ")
|
|
503
|
-
.replace(METHOD_AUTHORING_INTERNAL_INSTRUCTION, " ")
|
|
504
|
-
.replace(/\s+/g, " ")
|
|
505
|
-
.trim();
|
|
506
|
-
}
|
|
507
|
-
function extractMethodAuthoringSection(value, label) {
|
|
508
|
-
const otherLabels = METHOD_AUTHORING_LABELS
|
|
509
|
-
.filter((candidate) => candidate !== label)
|
|
510
|
-
.map(escapeRegExp)
|
|
511
|
-
.join("|");
|
|
512
|
-
const match = value.match(new RegExp(`${escapeRegExp(label)}\\s*:\\s*([\\s\\S]*?)(?=\\s*(?:${otherLabels})\\s*:|$)`, "i"));
|
|
513
|
-
return match?.[1] ? normalizeMethodAuthoringText(match[1]) : null;
|
|
514
|
-
}
|
|
515
|
-
function stripMethodCommandPrefix(value, methodId) {
|
|
516
|
-
const specific = new RegExp(`^(?:create|reate|eate|draft|author|build|make)\\s+(?:a\\s+new\\s+)?(?:interf\\s+)?method\\s+${escapeRegExp(methodId)}\\.?\\s*`, "i");
|
|
517
|
-
const generic = /^(?:create|reate|eate|draft|author|build|make)\s+(?:a\s+new\s+)?(?:interf\s+)?method\b[^.]{0,180}\.\s*/i;
|
|
518
|
-
const stripped = normalizeMethodAuthoringText(value)
|
|
519
|
-
.replace(specific, "")
|
|
520
|
-
.replace(generic, "")
|
|
521
|
-
.trim();
|
|
522
|
-
return stripped || normalizeMethodAuthoringText(value);
|
|
523
|
-
}
|
|
524
|
-
function methodAuthoringPromptFallback(message, methodId) {
|
|
525
|
-
const cleaned = message
|
|
526
|
-
.replace(/[│]+/g, " ")
|
|
527
|
-
.replace(METHOD_AUTHORING_INTERNAL_INSTRUCTION, " ")
|
|
528
|
-
.trim();
|
|
529
|
-
const agentWork = extractMethodAuthoringSection(cleaned, "Agent work");
|
|
530
|
-
const portableOutput = extractMethodAuthoringSection(cleaned, "Portable-context output");
|
|
531
|
-
const readinessNotes = extractMethodAuthoringSection(cleaned, "Readiness checks");
|
|
532
|
-
const lines = [
|
|
533
|
-
agentWork ? `Agent work: ${stripMethodCommandPrefix(agentWork, methodId)}` : null,
|
|
534
|
-
portableOutput ? `Portable-context output: ${portableOutput}` : null,
|
|
535
|
-
readinessNotes ? `Readiness checks: ${readinessNotes}` : null,
|
|
536
|
-
].filter((line) => Boolean(line));
|
|
537
|
-
if (lines.length > 0)
|
|
538
|
-
return lines.join("\n");
|
|
539
|
-
return stripMethodCommandPrefix(cleaned, methodId);
|
|
540
|
-
}
|
|
541
|
-
function methodAuthoringHintFromPrompt(prompt) {
|
|
542
|
-
const plain = normalizeMethodAuthoringText(prompt.replace(/\b(?:Agent work|Portable-context output|Readiness checks)\s*:/gi, " "));
|
|
543
|
-
if (plain.length <= 180)
|
|
544
|
-
return plain || "Custom Method";
|
|
545
|
-
const clipped = plain.slice(0, 177).replace(/\s+\S*$/, "").trim();
|
|
546
|
-
return `${clipped || plain.slice(0, 177)}...`;
|
|
547
|
-
}
|
|
548
|
-
function methodLabelFromId(methodId) {
|
|
549
|
-
return methodId
|
|
550
|
-
.split("-")
|
|
551
|
-
.filter(Boolean)
|
|
552
|
-
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
|
|
553
|
-
.join(" ") || methodId;
|
|
554
|
-
}
|
|
555
|
-
function detachMethodFromPreparation(preparation, methodId) {
|
|
556
|
-
if (methodIdForSourcePreparationConfig(preparation) !== methodId)
|
|
557
|
-
return preparation;
|
|
558
|
-
const { method: _removedMethod, ...detachedPreparation } = preparation;
|
|
559
|
-
return detachedPreparation;
|
|
560
|
-
}
|
|
561
|
-
function requireSelectedMethod(preparation) {
|
|
562
|
-
const methodId = methodIdForSourcePreparationConfig(preparation);
|
|
563
|
-
if (methodId)
|
|
564
|
-
return methodId;
|
|
565
|
-
throw new Error(`Select a Method for Preparation "${preparation.name}" before preparing it.`);
|
|
566
|
-
}
|
|
567
|
-
function actionCommandPreview(actionType, preparationName, methodId, values) {
|
|
568
|
-
if (actionType === "compile") {
|
|
569
|
-
const methodSuffix = methodId ? ` # Method: ${methodId}` : "";
|
|
570
|
-
return preparationName
|
|
571
|
-
? `interf compile --preparation ${preparationName}${methodSuffix}`
|
|
572
|
-
: `interf compile${methodSuffix}`;
|
|
573
|
-
}
|
|
574
|
-
if (actionType === "test") {
|
|
575
|
-
const mode = testModeCliTarget(testModeValue(values));
|
|
576
|
-
return preparationName
|
|
577
|
-
? `interf test --preparation ${preparationName} --target ${mode}`
|
|
578
|
-
: `interf test --target ${mode}`;
|
|
579
|
-
}
|
|
580
|
-
if (actionType === "readiness-check-draft") {
|
|
581
|
-
return "interf # choose Auto-create readiness checks";
|
|
582
|
-
}
|
|
583
|
-
if (actionType === "method-authoring" || actionType === "method-improvement") {
|
|
584
|
-
return "interf create method";
|
|
585
|
-
}
|
|
586
|
-
return "Try: create a Preparation, prepare, check readiness, draft readiness checks, or draft a Method.";
|
|
587
|
-
}
|
|
588
|
-
function hasCompiledTestTarget(sourcePath, preparationConfig) {
|
|
589
|
-
const compiledPath = portableContextPath(sourcePath, preparationConfig.name);
|
|
590
|
-
if (!existsSync(compiledPath))
|
|
591
|
-
return false;
|
|
592
|
-
return createCompiledTestTarget(compiledPath, preparationConfig.name, methodIdForSourcePreparationConfig(preparationConfig) ?? DEFAULT_METHOD_ID).eligible;
|
|
593
|
-
}
|
|
594
|
-
function actionAssistantMessage(actionType, preparationName, commandPreview) {
|
|
595
|
-
const preparationSuffix = preparationName ? ` for Preparation "${preparationName}"` : "";
|
|
596
|
-
if (actionType === "compile") {
|
|
597
|
-
return `Interf prepared a prepare-run proposal${preparationSuffix}. Approve to submit it through the local Interf service and watch the run in Interf. CLI equivalent: ${commandPreview}`;
|
|
598
|
-
}
|
|
599
|
-
if (actionType === "test") {
|
|
600
|
-
return `Interf prepared a readiness-check proposal${preparationSuffix}. Approve to run the requested target against saved readiness checks. CLI equivalent: ${commandPreview}`;
|
|
601
|
-
}
|
|
602
|
-
if (actionType === "readiness-check-draft") {
|
|
603
|
-
return `Interf prepared a proposal to draft readiness checks${preparationSuffix}. Approve to ask the configured local executor to draft saved checks as a visible run. CLI equivalent: ${commandPreview}`;
|
|
604
|
-
}
|
|
605
|
-
if (actionType === "method-authoring" || actionType === "method-improvement") {
|
|
606
|
-
return `Interf prepared a Method draft proposal${preparationSuffix}. Approve to draft a reusable local Method as a visible run. CLI equivalent: ${commandPreview}`;
|
|
607
|
-
}
|
|
608
|
-
return "I could not map that to a safe Interf action. Ask for one action in plain English, such as create a Preparation, prepare the data, check readiness, draft readiness checks, or draft a Method.";
|
|
609
|
-
}
|
|
610
|
-
function passRate(passed, total) {
|
|
611
|
-
if (total <= 0)
|
|
612
|
-
return null;
|
|
613
|
-
return Math.round((passed / total) * 100);
|
|
614
|
-
}
|
|
615
|
-
function readinessTargetResult(summary, currentFingerprint, readinessRunFingerprint) {
|
|
616
|
-
if (!summary)
|
|
617
|
-
return null;
|
|
618
|
-
const resultFingerprint = readinessRunFingerprint ?? null;
|
|
619
|
-
return {
|
|
620
|
-
passed: summary.passed_cases,
|
|
621
|
-
total: summary.total_cases,
|
|
622
|
-
pass_rate: passRate(summary.passed_cases, summary.total_cases),
|
|
623
|
-
checks_fingerprint: resultFingerprint,
|
|
624
|
-
stale: Boolean(currentFingerprint && resultFingerprint && currentFingerprint !== resultFingerprint),
|
|
625
|
-
run_id: null,
|
|
626
|
-
run_path: summary.run_path,
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
function readinessSummaryForStatus(status) {
|
|
630
|
-
if (status === "ready")
|
|
631
|
-
return "Ready for agent work.";
|
|
632
|
-
if (status === "not-ready")
|
|
633
|
-
return "Readiness checks did not pass.";
|
|
634
|
-
if (status === "stale")
|
|
635
|
-
return "Readiness checks are stale for the current saved checks.";
|
|
636
|
-
if (status === "checking")
|
|
637
|
-
return "Readiness checks are running.";
|
|
638
|
-
if (status === "building")
|
|
639
|
-
return "Portable context is building.";
|
|
640
|
-
if (status === "built")
|
|
641
|
-
return "Portable context is built; readiness has not been proven yet.";
|
|
642
|
-
if (status === "not-built")
|
|
643
|
-
return "Portable context has not been built yet.";
|
|
644
|
-
if (status === "not-configured")
|
|
645
|
-
return "No readiness checks are configured.";
|
|
646
|
-
return "Latest preparation failed.";
|
|
647
|
-
}
|
|
648
|
-
function readinessStateToPreparationReadiness(readiness) {
|
|
649
|
-
return PreparationReadinessStateSchema.parse({
|
|
650
|
-
...readiness,
|
|
651
|
-
checks: readiness.checks.map((check) => ({ ...check })),
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
function buildPreparationResource(rootPath, preparation, readiness, latestCompileRunId, latestTestRunId) {
|
|
655
|
-
const methodId = methodIdForSourcePreparationConfig(preparation);
|
|
656
|
-
return PreparationResourceSchema.parse({
|
|
657
|
-
id: preparation.name,
|
|
658
|
-
name: preparation.name,
|
|
659
|
-
preparation,
|
|
660
|
-
source_path: resolveSourcePreparationPath(rootPath, preparation),
|
|
661
|
-
method_id: methodId,
|
|
662
|
-
checks: preparation.checks,
|
|
663
|
-
portable_context: {
|
|
664
|
-
preparation: preparation.name,
|
|
665
|
-
path: readiness.portable_context_path,
|
|
666
|
-
exists: readiness.portable_context_path !== null,
|
|
667
|
-
method_id: methodId,
|
|
668
|
-
latest_compile_run_id: latestCompileRunId,
|
|
669
|
-
latest_test_run_id: latestTestRunId,
|
|
670
|
-
},
|
|
671
|
-
portable_context_path: readiness.portable_context_path,
|
|
672
|
-
readiness: readinessStateToPreparationReadiness(readiness),
|
|
673
|
-
runs: {
|
|
674
|
-
latest_compile_run_id: latestCompileRunId,
|
|
675
|
-
latest_test_run_id: latestTestRunId,
|
|
676
|
-
},
|
|
677
|
-
latest_compile_run_id: latestCompileRunId,
|
|
678
|
-
latest_test_run_id: latestTestRunId,
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
function buildMethodResource(resource) {
|
|
682
|
-
return MethodResourceSchema.parse({
|
|
683
|
-
id: resource.id,
|
|
684
|
-
method_id: resource.id,
|
|
685
|
-
path: resource.path,
|
|
686
|
-
...(resource.label ? { label: resource.label } : {}),
|
|
687
|
-
...(resource.hint ? { hint: resource.hint } : {}),
|
|
688
|
-
source_kind: resource.source_kind,
|
|
689
|
-
built_in: resource.built_in,
|
|
690
|
-
active_for_preparations: resource.active_for_preparations,
|
|
691
|
-
output_paths: resource.output_paths,
|
|
692
|
-
stages: resource.stages,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
31
|
+
/** TTL for `POST /v1/compile-runs` idempotency-key dedupe entries. */
|
|
32
|
+
const IDEMPOTENCY_TTL_MS = 60 * 60 * 1000;
|
|
33
|
+
/** Idempotency cache size at which to schedule an opportunistic prune. */
|
|
34
|
+
const IDEMPOTENCY_PRUNE_THRESHOLD = 64;
|
|
695
35
|
export class LocalServiceRuntime {
|
|
696
|
-
rootPath;
|
|
697
36
|
host;
|
|
698
37
|
port;
|
|
699
38
|
startedAt;
|
|
700
39
|
packageVersion;
|
|
701
40
|
handlers;
|
|
41
|
+
/**
|
|
42
|
+
* The seed root path the runtime was constructed with. Used as a
|
|
43
|
+
* non-preparation fallback when a preparation-independent route
|
|
44
|
+
* (methods, action proposals, runs listings) needs an anchor to load
|
|
45
|
+
* shared state (user-library methods, bundled methods, etc).
|
|
46
|
+
*/
|
|
47
|
+
rootPath;
|
|
48
|
+
/**
|
|
49
|
+
* Per-instance bearer token. Mutating routes require this on the
|
|
50
|
+
* Authorization header. `null` means token-less mode (test harness).
|
|
51
|
+
*/
|
|
52
|
+
authToken;
|
|
53
|
+
/** Map of prepDataDir -> PreparationContext. */
|
|
54
|
+
workspaces = new Map();
|
|
55
|
+
/** Hook called whenever a workspace is registered or deregistered. */
|
|
56
|
+
onRegistryChanged = null;
|
|
57
|
+
/** In-flight runs across all workspaces. Used for `idle_for_seconds`. */
|
|
58
|
+
activeRunCount = 0;
|
|
59
|
+
/**
|
|
60
|
+
* Active compile-run cancellation handles, keyed by run id. Populated
|
|
61
|
+
* when a compile run is launched and cleared once the run reaches a
|
|
62
|
+
* terminal state. Each entry remembers where the persisted record lives
|
|
63
|
+
* so cancel can mark it without re-resolving the Preparation.
|
|
64
|
+
*/
|
|
65
|
+
activeCompileRuns = new Map();
|
|
66
|
+
/**
|
|
67
|
+
* Idempotency-key cache for `POST /v1/compile-runs`. Outer key is the
|
|
68
|
+
* resolved workspace root; inner key is the client-supplied idempotency
|
|
69
|
+
* value. Namespacing per workspace prevents key collisions across
|
|
70
|
+
* tenants on the same engine (CSO finding: a malicious workspace could
|
|
71
|
+
* otherwise hijack another workspace's run id by reusing its key).
|
|
72
|
+
* Entries expire after `IDEMPOTENCY_TTL_MS`.
|
|
73
|
+
*/
|
|
74
|
+
idempotencyKeyCache = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* Read-side caches. Polling clients (Compiler UI, CLI status loops)
|
|
77
|
+
* hit list/get endpoints multiple times per second; without these,
|
|
78
|
+
* every request re-walks the filesystem and re-parses every JSON
|
|
79
|
+
* record through Zod. The runtime invalidates each cache on the
|
|
80
|
+
* matching write path. See {@link runtime-caches} for design notes.
|
|
81
|
+
*/
|
|
82
|
+
compileRunCache = new RunListingCache();
|
|
83
|
+
testRunCache = new RunListingCache();
|
|
84
|
+
readinessCache = new ReadinessCache();
|
|
85
|
+
sourceFilesCache = new MtimeListingCache();
|
|
86
|
+
methodListingCache = new MethodListingCache();
|
|
702
87
|
constructor(options) {
|
|
703
|
-
this.rootPath = resolve(options.rootPath);
|
|
704
88
|
this.host = options.host;
|
|
705
89
|
this.port = options.port;
|
|
706
90
|
this.startedAt = options.startedAt ?? new Date().toISOString();
|
|
707
91
|
this.packageVersion = options.packageVersion;
|
|
708
92
|
this.handlers = options.handlers ?? {};
|
|
93
|
+
this.authToken = options.authToken ?? null;
|
|
94
|
+
this.rootPath = resolve(options.rootPath);
|
|
95
|
+
// Auto-register the initial workspace so single-workspace callers
|
|
96
|
+
// (existing tests, the current `interf web` command) work without
|
|
97
|
+
// additional bootstrapping. The constructor seed is the only role
|
|
98
|
+
// `options.rootPath` plays; runtime methods take `prepDataDir`
|
|
99
|
+
// explicitly afterwards.
|
|
100
|
+
this.registerPreparation(this.rootPath);
|
|
101
|
+
}
|
|
102
|
+
setBoundPort(port) {
|
|
103
|
+
this.port = port;
|
|
104
|
+
}
|
|
105
|
+
/** Set a hook that fires whenever the registered workspaces change. */
|
|
106
|
+
setOnRegistryChanged(handler) {
|
|
107
|
+
this.onRegistryChanged = handler;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Register a workspace with this runtime. Returns the PreparationContext.
|
|
111
|
+
* Idempotent: re-registering an existing workspace updates `lastActivity`.
|
|
112
|
+
*/
|
|
113
|
+
registerPreparation(prepDataDir) {
|
|
114
|
+
const resolved = resolve(prepDataDir);
|
|
115
|
+
const now = new Date().toISOString();
|
|
116
|
+
const existing = this.workspaces.get(resolved);
|
|
117
|
+
if (existing) {
|
|
118
|
+
existing.lastActivity = now;
|
|
119
|
+
this.onRegistryChanged?.();
|
|
120
|
+
return existing;
|
|
121
|
+
}
|
|
122
|
+
const context = {
|
|
123
|
+
rootPath: resolved,
|
|
124
|
+
startedAt: now,
|
|
125
|
+
lastActivity: now,
|
|
126
|
+
};
|
|
127
|
+
this.workspaces.set(resolved, context);
|
|
128
|
+
this.onRegistryChanged?.();
|
|
129
|
+
return context;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Remove a workspace from the runtime. Returns true if a workspace was
|
|
133
|
+
* removed.
|
|
134
|
+
*/
|
|
135
|
+
deregisterPreparation(prepDataDir) {
|
|
136
|
+
const resolved = resolve(prepDataDir);
|
|
137
|
+
const removed = this.workspaces.delete(resolved);
|
|
138
|
+
if (removed) {
|
|
139
|
+
this.onRegistryChanged?.();
|
|
140
|
+
}
|
|
141
|
+
return removed;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Most recently active workspace, or the first registered if none has
|
|
145
|
+
* activity yet. Server code uses this as the fallback when a request
|
|
146
|
+
* omits the workspace header. Throws if no workspace is registered.
|
|
147
|
+
*/
|
|
148
|
+
defaultPreparationDataDir() {
|
|
149
|
+
if (this.workspaces.size === 0) {
|
|
150
|
+
throw new Error("Local service has no registered workspaces.");
|
|
151
|
+
}
|
|
152
|
+
let best = null;
|
|
153
|
+
let bestKey = -Infinity;
|
|
154
|
+
for (const context of this.workspaces.values()) {
|
|
155
|
+
const key = Date.parse(context.lastActivity);
|
|
156
|
+
if (Number.isFinite(key) && key > bestKey) {
|
|
157
|
+
best = context;
|
|
158
|
+
bestKey = key;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return (best ?? this.workspaces.values().next().value).rootPath;
|
|
162
|
+
}
|
|
163
|
+
/** Look up a workspace context by rootPath. */
|
|
164
|
+
getPreparationContext(prepDataDir) {
|
|
165
|
+
return this.workspaces.get(resolve(prepDataDir)) ?? null;
|
|
166
|
+
}
|
|
167
|
+
/** All registered workspaces, ordered by registration time. */
|
|
168
|
+
listRegisteredPreparations() {
|
|
169
|
+
return Array.from(this.workspaces.values()).sort((left, right) => Date.parse(left.startedAt) - Date.parse(right.startedAt));
|
|
170
|
+
}
|
|
171
|
+
/** True when no workspaces are registered. */
|
|
172
|
+
hasNoWorkspaces() {
|
|
173
|
+
return this.workspaces.size === 0;
|
|
174
|
+
}
|
|
175
|
+
/** Number of registered workspaces. */
|
|
176
|
+
registeredPreparationCount() {
|
|
177
|
+
return this.workspaces.size;
|
|
178
|
+
}
|
|
179
|
+
/** Increment in-flight run counter. Call when a long-running run starts. */
|
|
180
|
+
beginActiveRun() {
|
|
181
|
+
this.activeRunCount += 1;
|
|
182
|
+
}
|
|
183
|
+
/** Decrement in-flight run counter. Pair with `beginActiveRun`. */
|
|
184
|
+
endActiveRun() {
|
|
185
|
+
if (this.activeRunCount > 0)
|
|
186
|
+
this.activeRunCount -= 1;
|
|
187
|
+
}
|
|
188
|
+
/** Sum of in-flight runs across all workspaces. */
|
|
189
|
+
activeRuns() {
|
|
190
|
+
return this.activeRunCount;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Mark the workspace as recently active. Routes call this on entry so
|
|
194
|
+
* `idleForSeconds` and the registry snapshots stay in sync with the
|
|
195
|
+
* actual request cadence.
|
|
196
|
+
*/
|
|
197
|
+
touchPreparation(prepDataDir) {
|
|
198
|
+
const context = this.workspaces.get(resolve(prepDataDir));
|
|
199
|
+
if (context) {
|
|
200
|
+
context.lastActivity = new Date().toISOString();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Snapshot of registered workspaces for the registry / status output. */
|
|
204
|
+
registeredPreparationSnapshots() {
|
|
205
|
+
return this.listRegisteredPreparations().map((context) => ServiceRegistryWorkspaceSchema.parse({
|
|
206
|
+
control_path: context.rootPath,
|
|
207
|
+
registered_at: context.startedAt,
|
|
208
|
+
last_activity: context.lastActivity,
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
/** Seconds since the most recent workspace activity (0 if active). */
|
|
212
|
+
idleForSeconds() {
|
|
213
|
+
const all = this.listRegisteredPreparations();
|
|
214
|
+
if (all.length === 0)
|
|
215
|
+
return 0;
|
|
216
|
+
if (this.activeRunCount > 0)
|
|
217
|
+
return 0;
|
|
218
|
+
let mostRecent = 0;
|
|
219
|
+
for (const context of all) {
|
|
220
|
+
const ts = Date.parse(context.lastActivity);
|
|
221
|
+
if (Number.isFinite(ts) && ts > mostRecent)
|
|
222
|
+
mostRecent = ts;
|
|
223
|
+
}
|
|
224
|
+
if (mostRecent <= 0)
|
|
225
|
+
return 0;
|
|
226
|
+
const elapsed = Math.max(0, Date.now() - mostRecent);
|
|
227
|
+
return Math.floor(elapsed / 1000);
|
|
709
228
|
}
|
|
710
|
-
health() {
|
|
711
|
-
const sourceFolderPath = resolveConfiguredSourceFolderPath(
|
|
229
|
+
health(prepDataDir) {
|
|
230
|
+
const sourceFolderPath = prepDataDir ? resolveConfiguredSourceFolderPath(prepDataDir) : null;
|
|
712
231
|
return LocalServiceHealthSchema.parse({
|
|
713
232
|
kind: "interf-local-service-health",
|
|
714
233
|
version: 1,
|
|
@@ -716,52 +235,59 @@ export class LocalServiceRuntime {
|
|
|
716
235
|
host: this.host,
|
|
717
236
|
port: this.port,
|
|
718
237
|
service_url: buildLocalServiceUrl({ host: this.host, port: this.port }),
|
|
719
|
-
control_path:
|
|
238
|
+
...(prepDataDir ? { control_path: prepDataDir } : {}),
|
|
720
239
|
source_folder_path: sourceFolderPath,
|
|
721
240
|
started_at: this.startedAt,
|
|
722
241
|
...(this.packageVersion ? { package_version: this.packageVersion } : {}),
|
|
242
|
+
instance_started_at: this.startedAt,
|
|
243
|
+
registered_workspaces: this.registeredPreparationSnapshots(),
|
|
244
|
+
active_runs: this.activeRunCount,
|
|
245
|
+
idle_for_seconds: this.idleForSeconds(),
|
|
723
246
|
});
|
|
724
247
|
}
|
|
725
|
-
listPreparations() {
|
|
726
|
-
const config = loadSourceFolderConfig(
|
|
248
|
+
listPreparations(prepDataDir) {
|
|
249
|
+
const config = loadSourceFolderConfig(prepDataDir);
|
|
727
250
|
return listSourcePreparationConfigs(config).map((preparation) => {
|
|
728
|
-
const compileRuns = this.listCompileRunsForPreparation(preparation.name);
|
|
729
|
-
const testRuns = this.listTestRunsForPreparation(preparation.name);
|
|
730
|
-
const readiness = this.computePreparationReadiness(preparation);
|
|
731
|
-
return buildPreparationResource(
|
|
251
|
+
const compileRuns = this.listCompileRunsForPreparation(prepDataDir, preparation.name);
|
|
252
|
+
const testRuns = this.listTestRunsForPreparation(prepDataDir, preparation.name);
|
|
253
|
+
const readiness = this.computePreparationReadiness(prepDataDir, preparation);
|
|
254
|
+
return buildPreparationResource(prepDataDir, preparation, readiness, compileRuns[0]?.run_id ?? null, testRuns[0]?.run_id ?? null);
|
|
732
255
|
});
|
|
733
256
|
}
|
|
734
|
-
getPreparation(preparationName) {
|
|
735
|
-
return this.listPreparations().find((preparation) => preparation.name === preparationName) ?? null;
|
|
257
|
+
getPreparation(prepDataDir, preparationName) {
|
|
258
|
+
return this.listPreparations(prepDataDir).find((preparation) => preparation.name === preparationName) ?? null;
|
|
736
259
|
}
|
|
737
|
-
listPreparationReadiness() {
|
|
738
|
-
return this.listReadiness().map(readinessStateToPreparationReadiness);
|
|
260
|
+
listPreparationReadiness(prepDataDir) {
|
|
261
|
+
return this.listReadiness(prepDataDir).map(readinessStateToPreparationReadiness);
|
|
739
262
|
}
|
|
740
|
-
getPreparationReadiness(preparationName) {
|
|
741
|
-
const readiness = this.getReadiness(preparationName);
|
|
263
|
+
getPreparationReadiness(prepDataDir, preparationName) {
|
|
264
|
+
const readiness = this.getReadiness(prepDataDir, preparationName);
|
|
742
265
|
return readiness ? readinessStateToPreparationReadiness(readiness) : null;
|
|
743
266
|
}
|
|
744
|
-
listReadiness() {
|
|
745
|
-
const config = loadSourceFolderConfig(
|
|
746
|
-
return listSourcePreparationConfigs(config).map((preparation) => this.computePreparationReadiness(preparation));
|
|
267
|
+
listReadiness(prepDataDir) {
|
|
268
|
+
const config = loadSourceFolderConfig(prepDataDir);
|
|
269
|
+
return listSourcePreparationConfigs(config).map((preparation) => this.computePreparationReadiness(prepDataDir, preparation));
|
|
270
|
+
}
|
|
271
|
+
getReadiness(prepDataDir, preparationName) {
|
|
272
|
+
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), preparationName);
|
|
273
|
+
return preparation ? this.computePreparationReadiness(prepDataDir, preparation) : null;
|
|
747
274
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
return preparation ? this.computePreparationReadiness(preparation) : null;
|
|
275
|
+
computePreparationReadiness(prepDataDir, preparation) {
|
|
276
|
+
return this.readinessCache.get(prepDataDir, preparation.name, () => this.computePreparationReadinessUncached(prepDataDir, preparation));
|
|
751
277
|
}
|
|
752
|
-
|
|
278
|
+
computePreparationReadinessUncached(prepDataDir, preparation) {
|
|
753
279
|
const generatedAt = new Date().toISOString();
|
|
754
|
-
const compiledPath =
|
|
280
|
+
const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparation.name);
|
|
755
281
|
const contextExists = existsSync(compiledPath);
|
|
756
282
|
const compiledTarget = createCompiledTestTarget(compiledPath, preparation.name, methodIdForSourcePreparationConfig(preparation) ?? DEFAULT_METHOD_ID);
|
|
757
283
|
const contextReady = compiledTarget.eligible;
|
|
758
|
-
const compileRun = this.listCompileRunsForPreparation(preparation.name)[0] ?? null;
|
|
759
|
-
const testRun = this.listTestRunsForPreparation(preparation.name)[0] ?? null;
|
|
760
|
-
const readinessRun = this.readLatestReadinessRun(preparation.name);
|
|
284
|
+
const compileRun = this.listCompileRunsForPreparation(prepDataDir, preparation.name)[0] ?? null;
|
|
285
|
+
const testRun = this.listTestRunsForPreparation(prepDataDir, preparation.name)[0] ?? null;
|
|
286
|
+
const readinessRun = this.readLatestReadinessRun(prepDataDir, preparation.name);
|
|
761
287
|
const configuredChecks = preparation.checks.length;
|
|
762
288
|
const currentFingerprint = configuredChecks > 0 ? fingerprintReadinessChecks(preparation.checks) : null;
|
|
763
289
|
const readinessRunFingerprint = readinessRun?.checks_fingerprint ?? null;
|
|
764
|
-
const sourceResult = readinessTargetResult(readinessRun?.
|
|
290
|
+
const sourceResult = readinessTargetResult(readinessRun?.source_files, currentFingerprint, readinessRunFingerprint);
|
|
765
291
|
const contextResult = readinessTargetResult(readinessRun?.compiled, currentFingerprint, readinessRunFingerprint);
|
|
766
292
|
const checksStale = Boolean(currentFingerprint && readinessRunFingerprint && currentFingerprint !== readinessRunFingerprint);
|
|
767
293
|
const compileCheck = (() => {
|
|
@@ -879,39 +405,46 @@ export class LocalServiceRuntime {
|
|
|
879
405
|
checks,
|
|
880
406
|
});
|
|
881
407
|
}
|
|
882
|
-
listSourceFiles(preparationName) {
|
|
883
|
-
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(
|
|
408
|
+
listSourceFiles(prepDataDir, preparationName) {
|
|
409
|
+
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
|
|
884
410
|
.filter((preparation) => !preparationName || preparation.name === preparationName);
|
|
885
411
|
return preparations.flatMap((preparation) => {
|
|
886
|
-
const sourceFolderPath = resolveSourcePreparationPath(
|
|
887
|
-
const compiledPath =
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
412
|
+
const sourceFolderPath = resolveSourcePreparationPath(prepDataDir, preparation);
|
|
413
|
+
const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparation.name);
|
|
414
|
+
// Cache by source-folder root mtime so identical UI polls do not
|
|
415
|
+
// re-walk and re-stat the entire tree. The cache imposes a short
|
|
416
|
+
// TTL (see runtime-caches.ts) so deeper changes are still picked
|
|
417
|
+
// up promptly.
|
|
418
|
+
const cacheKey = `${preparation.name}\0${sourceFolderPath}\0${compiledPath}`;
|
|
419
|
+
return this.sourceFilesCache.get(cacheKey, sourceFolderPath, () => {
|
|
420
|
+
return discoverSourceFiles(sourceFolderPath, compiledPath).sourceFiles.map((relativePath) => {
|
|
421
|
+
const absolutePath = join(sourceFolderPath, relativePath);
|
|
422
|
+
let sizeBytes = 0;
|
|
423
|
+
let modifiedAt = null;
|
|
424
|
+
try {
|
|
425
|
+
const stat = statSync(absolutePath);
|
|
426
|
+
sizeBytes = stat.size;
|
|
427
|
+
modifiedAt = stat.mtime.toISOString();
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
sizeBytes = 0;
|
|
431
|
+
modifiedAt = null;
|
|
432
|
+
}
|
|
433
|
+
return SourceFileResourceSchema.parse({
|
|
434
|
+
preparation: preparation.name,
|
|
435
|
+
path: relativePath,
|
|
436
|
+
absolute_path: absolutePath,
|
|
437
|
+
size_bytes: sizeBytes,
|
|
438
|
+
modified_at: modifiedAt,
|
|
439
|
+
source_folder_path: sourceFolderPath,
|
|
440
|
+
});
|
|
908
441
|
});
|
|
909
442
|
});
|
|
910
443
|
});
|
|
911
444
|
}
|
|
912
|
-
listWorkspaceFiles() {
|
|
913
|
-
const sourceFolderPath = resolveConfiguredSourceFolderPath(
|
|
914
|
-
return discoverSourceFiles(sourceFolderPath,
|
|
445
|
+
listWorkspaceFiles(prepDataDir) {
|
|
446
|
+
const sourceFolderPath = resolveConfiguredSourceFolderPath(prepDataDir) ?? prepDataDir;
|
|
447
|
+
return discoverSourceFiles(sourceFolderPath, prepDataDir).sourceFiles.map((relativePath) => {
|
|
915
448
|
const absolutePath = join(sourceFolderPath, relativePath);
|
|
916
449
|
let sizeBytes = 0;
|
|
917
450
|
let modifiedAt = null;
|
|
@@ -932,51 +465,61 @@ export class LocalServiceRuntime {
|
|
|
932
465
|
});
|
|
933
466
|
});
|
|
934
467
|
}
|
|
935
|
-
listMethods() {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
.
|
|
953
|
-
.
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
468
|
+
listMethods(prepDataDir) {
|
|
469
|
+
// The Method choices list is dominated by repeated reads of
|
|
470
|
+
// method.json + context-interface across builtin / user / workspace
|
|
471
|
+
// method roots. Key the cache off mtimes for the three roots; if
|
|
472
|
+
// any of them changes (a new local Method, an edit to the user
|
|
473
|
+
// library, etc.) the cache misses and we re-resolve.
|
|
474
|
+
const builtinRoot = join(PACKAGE_ROOT, "builtin-methods");
|
|
475
|
+
const localRoot = preparationMethodsRoot(asPreparationDataDir(prepDataDir));
|
|
476
|
+
const userRoot = userMethodsRoot();
|
|
477
|
+
return this.methodListingCache.get(prepDataDir, [builtinRoot, localRoot, userRoot], () => {
|
|
478
|
+
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir));
|
|
479
|
+
const choices = listCompiledMethodChoices(prepDataDir);
|
|
480
|
+
return choices.map((method) => {
|
|
481
|
+
const activeForPreparations = preparations
|
|
482
|
+
.filter((preparation) => methodIdForSourcePreparationConfig(preparation) === method.id)
|
|
483
|
+
.map((preparation) => preparation.name);
|
|
484
|
+
return buildMethodResource({
|
|
485
|
+
id: method.id,
|
|
486
|
+
path: resolveMethodPackageSourcePath(prepDataDir, method.id) ?? method.id,
|
|
487
|
+
label: method.label,
|
|
488
|
+
hint: method.hint,
|
|
489
|
+
source_kind: method.scope === "builtin" ? "builtin" : "local",
|
|
490
|
+
built_in: method.scope === "builtin",
|
|
491
|
+
active_for_preparations: activeForPreparations,
|
|
492
|
+
output_paths: (method.contextInterface?.zones ?? [])
|
|
493
|
+
.filter((zone) => zone.role === "output")
|
|
494
|
+
.map((zone) => contextInterfaceArtifactPath(zone))
|
|
495
|
+
.sort(),
|
|
496
|
+
stages: method.stages.map((stage) => ({
|
|
497
|
+
id: stage.id,
|
|
498
|
+
label: stage.label,
|
|
499
|
+
description: stage.description,
|
|
500
|
+
contract_type: stage.contractType,
|
|
501
|
+
skill_dir: stage.skillDir,
|
|
502
|
+
reads: stage.reads,
|
|
503
|
+
writes: stage.writes,
|
|
504
|
+
...(stage.acceptance ? { acceptance: stage.acceptance } : {}),
|
|
505
|
+
})),
|
|
506
|
+
});
|
|
964
507
|
});
|
|
965
508
|
});
|
|
966
509
|
}
|
|
967
|
-
getMethod(methodId) {
|
|
968
|
-
return this.listMethods().find((method) => method.id === methodId) ?? null;
|
|
510
|
+
getMethod(prepDataDir, methodId) {
|
|
511
|
+
return this.listMethods(prepDataDir).find((method) => method.id === methodId) ?? null;
|
|
969
512
|
}
|
|
970
|
-
listJobs() {
|
|
971
|
-
return
|
|
513
|
+
listJobs(prepDataDir) {
|
|
514
|
+
return byCreatedAtDesc(listJsonFiles(localJobsRoot(prepDataDir))
|
|
972
515
|
.map(readLocalJobRunAt)
|
|
973
516
|
.filter((run) => run !== null));
|
|
974
517
|
}
|
|
975
|
-
getJob(runId) {
|
|
976
|
-
return this.listJobs().find((run) => run.run_id === runId) ?? null;
|
|
518
|
+
getJob(prepDataDir, runId) {
|
|
519
|
+
return this.listJobs(prepDataDir).find((run) => run.run_id === runId) ?? null;
|
|
977
520
|
}
|
|
978
|
-
getJobEvents(runId) {
|
|
979
|
-
return this.getJob(runId)?.events ?? null;
|
|
521
|
+
getJobEvents(prepDataDir, runId) {
|
|
522
|
+
return this.getJob(prepDataDir, runId)?.events ?? null;
|
|
980
523
|
}
|
|
981
524
|
getExecutorStatus() {
|
|
982
525
|
const checkedAt = new Date().toISOString();
|
|
@@ -1046,26 +589,26 @@ export class LocalServiceRuntime {
|
|
|
1046
589
|
});
|
|
1047
590
|
return this.getExecutorStatus();
|
|
1048
591
|
}
|
|
1049
|
-
listActionProposals() {
|
|
1050
|
-
return
|
|
592
|
+
listActionProposals(prepDataDir) {
|
|
593
|
+
return byCreatedAtDesc(listJsonFiles(actionProposalsRoot(prepDataDir))
|
|
1051
594
|
.map(readActionProposalAt)
|
|
1052
595
|
.filter((proposal) => proposal !== null));
|
|
1053
596
|
}
|
|
1054
|
-
getActionProposal(proposalId) {
|
|
1055
|
-
return this.listActionProposals().find((proposal) => proposal.proposal_id === proposalId) ?? null;
|
|
597
|
+
getActionProposal(prepDataDir, proposalId) {
|
|
598
|
+
return this.listActionProposals(prepDataDir).find((proposal) => proposal.proposal_id === proposalId) ?? null;
|
|
1056
599
|
}
|
|
1057
|
-
async createActionProposal(requestValue) {
|
|
600
|
+
async createActionProposal(prepDataDir, requestValue) {
|
|
1058
601
|
const request = ActionProposalCreateRequestSchema.parse(requestValue);
|
|
1059
602
|
const proposal = ActionProposalResourceSchema.parse({
|
|
1060
|
-
...(await this.buildActionProposal(request)),
|
|
603
|
+
...(await this.buildActionProposal(prepDataDir, request)),
|
|
1061
604
|
client_origin: request.client_origin,
|
|
1062
605
|
});
|
|
1063
|
-
this.writeActionProposal(proposal);
|
|
606
|
+
this.writeActionProposal(prepDataDir, proposal);
|
|
1064
607
|
return proposal;
|
|
1065
608
|
}
|
|
1066
|
-
async decideActionProposal(proposalId, requestValue) {
|
|
609
|
+
async decideActionProposal(prepDataDir, proposalId, requestValue) {
|
|
1067
610
|
const decision = ActionProposalApprovalRequestSchema.parse(requestValue);
|
|
1068
|
-
const current = this.getActionProposal(proposalId);
|
|
611
|
+
const current = this.getActionProposal(prepDataDir, proposalId);
|
|
1069
612
|
if (!current)
|
|
1070
613
|
return null;
|
|
1071
614
|
if (current.status !== "awaiting_approval") {
|
|
@@ -1082,11 +625,11 @@ export class LocalServiceRuntime {
|
|
|
1082
625
|
...(decision.note ? { note: decision.note } : {}),
|
|
1083
626
|
},
|
|
1084
627
|
});
|
|
1085
|
-
this.writeActionProposal(decided);
|
|
628
|
+
this.writeActionProposal(prepDataDir, decided);
|
|
1086
629
|
if (!decision.approved)
|
|
1087
630
|
return decided;
|
|
1088
631
|
try {
|
|
1089
|
-
const submission = await this.submitActionProposal(decided);
|
|
632
|
+
const submission = await this.submitActionProposal(prepDataDir, decided);
|
|
1090
633
|
const submitted = ActionProposalResourceSchema.parse({
|
|
1091
634
|
...decided,
|
|
1092
635
|
status: "submitted",
|
|
@@ -1094,7 +637,7 @@ export class LocalServiceRuntime {
|
|
|
1094
637
|
submitted_run_id: submission.runId,
|
|
1095
638
|
submitted_run_type: submission.runType,
|
|
1096
639
|
});
|
|
1097
|
-
this.writeActionProposal(submitted);
|
|
640
|
+
this.writeActionProposal(prepDataDir, submitted);
|
|
1098
641
|
return submitted;
|
|
1099
642
|
}
|
|
1100
643
|
catch (error) {
|
|
@@ -1104,25 +647,25 @@ export class LocalServiceRuntime {
|
|
|
1104
647
|
updated_at: new Date().toISOString(),
|
|
1105
648
|
error: error instanceof Error ? error.message : String(error),
|
|
1106
649
|
});
|
|
1107
|
-
this.writeActionProposal(failed);
|
|
650
|
+
this.writeActionProposal(prepDataDir, failed);
|
|
1108
651
|
return failed;
|
|
1109
652
|
}
|
|
1110
653
|
}
|
|
1111
|
-
listRunObservability() {
|
|
654
|
+
listRunObservability(prepDataDir) {
|
|
1112
655
|
return [
|
|
1113
|
-
...this.listCompileRuns().map((resource) => compileRunToObservability(resource.run)),
|
|
1114
|
-
...this.listTestRuns().map(testRunToObservability),
|
|
1115
|
-
...this.listJobs().map(jobRunToObservability),
|
|
656
|
+
...this.listCompileRuns(prepDataDir).map((resource) => compileRunToObservability(resource.run)),
|
|
657
|
+
...this.listTestRuns(prepDataDir).map(testRunToObservability),
|
|
658
|
+
...this.listJobs(prepDataDir).map(jobRunToObservability),
|
|
1116
659
|
].sort((left, right) => {
|
|
1117
660
|
const leftTime = timestampKey(left.started_at ?? left.created_at ?? left.finished_at);
|
|
1118
661
|
const rightTime = timestampKey(right.started_at ?? right.created_at ?? right.finished_at);
|
|
1119
662
|
return rightTime - leftTime;
|
|
1120
663
|
});
|
|
1121
664
|
}
|
|
1122
|
-
getRunObservability(runId) {
|
|
1123
|
-
return this.listRunObservability().find((run) => run.run_id === runId) ?? null;
|
|
665
|
+
getRunObservability(prepDataDir, runId) {
|
|
666
|
+
return this.listRunObservability(prepDataDir).find((run) => run.run_id === runId) ?? null;
|
|
1124
667
|
}
|
|
1125
|
-
createJobRun(requestValue) {
|
|
668
|
+
createJobRun(prepDataDir, requestValue) {
|
|
1126
669
|
const request = LocalJobRunCreateRequestSchema.parse(requestValue);
|
|
1127
670
|
const runId = createRunId("job");
|
|
1128
671
|
const now = new Date().toISOString();
|
|
@@ -1154,12 +697,12 @@ export class LocalServiceRuntime {
|
|
|
1154
697
|
},
|
|
1155
698
|
],
|
|
1156
699
|
});
|
|
1157
|
-
this.writeJobRun(run);
|
|
700
|
+
this.writeJobRun(prepDataDir, run);
|
|
1158
701
|
return run;
|
|
1159
702
|
}
|
|
1160
|
-
appendJobRunEvent(runId, requestValue) {
|
|
703
|
+
appendJobRunEvent(prepDataDir, runId, requestValue) {
|
|
1161
704
|
const request = LocalJobEventAppendRequestSchema.parse(requestValue);
|
|
1162
|
-
const current = this.getJob(runId);
|
|
705
|
+
const current = this.getJob(prepDataDir, runId);
|
|
1163
706
|
if (!current)
|
|
1164
707
|
return null;
|
|
1165
708
|
const event = {
|
|
@@ -1174,12 +717,12 @@ export class LocalServiceRuntime {
|
|
|
1174
717
|
...(request.output ? { output: request.output } : {}),
|
|
1175
718
|
};
|
|
1176
719
|
const next = LocalJobRunResourceSchema.parse(applyEventToLocalJob(current, event));
|
|
1177
|
-
this.writeJobRun(next);
|
|
720
|
+
this.writeJobRun(prepDataDir, next);
|
|
1178
721
|
return next;
|
|
1179
722
|
}
|
|
1180
|
-
async createReadinessCheckDraftRun(requestValue) {
|
|
723
|
+
async createReadinessCheckDraftRun(prepDataDir, requestValue) {
|
|
1181
724
|
const request = ReadinessCheckDraftCreateRequestSchema.parse(requestValue);
|
|
1182
|
-
const job = this.createJobRun({
|
|
725
|
+
const job = this.createJobRun(prepDataDir, {
|
|
1183
726
|
job_type: "readiness-check-draft",
|
|
1184
727
|
title: `Draft readiness checks for ${request.preparation}`,
|
|
1185
728
|
preparation: request.preparation,
|
|
@@ -1207,7 +750,7 @@ export class LocalServiceRuntime {
|
|
|
1207
750
|
},
|
|
1208
751
|
],
|
|
1209
752
|
});
|
|
1210
|
-
this.appendJobRunEvent(job.run_id, {
|
|
753
|
+
this.appendJobRunEvent(prepDataDir, job.run_id, {
|
|
1211
754
|
type: "step.started",
|
|
1212
755
|
step_id: "read-source",
|
|
1213
756
|
message: "Reading source files for readiness-check evidence.",
|
|
@@ -1216,7 +759,7 @@ export class LocalServiceRuntime {
|
|
|
1216
759
|
source_folder_path: request.source_folder_path,
|
|
1217
760
|
},
|
|
1218
761
|
});
|
|
1219
|
-
this.appendJobRunEvent(job.run_id, {
|
|
762
|
+
this.appendJobRunEvent(prepDataDir, job.run_id, {
|
|
1220
763
|
type: "step.completed",
|
|
1221
764
|
step_id: "read-source",
|
|
1222
765
|
message: "Source folder is ready for drafting readiness checks.",
|
|
@@ -1225,7 +768,7 @@ export class LocalServiceRuntime {
|
|
|
1225
768
|
source_folder_path: request.source_folder_path,
|
|
1226
769
|
},
|
|
1227
770
|
});
|
|
1228
|
-
this.appendJobRunEvent(job.run_id, {
|
|
771
|
+
this.appendJobRunEvent(prepDataDir, job.run_id, {
|
|
1229
772
|
type: "step.started",
|
|
1230
773
|
step_id: "agent-draft",
|
|
1231
774
|
message: "Drafting saved readiness checks from the source files.",
|
|
@@ -1234,30 +777,31 @@ export class LocalServiceRuntime {
|
|
|
1234
777
|
target_count: request.target_count,
|
|
1235
778
|
},
|
|
1236
779
|
});
|
|
1237
|
-
void this.runReadinessCheckDraftInBackground(request, job.run_id);
|
|
1238
|
-
return this.getJob(job.run_id) ?? job;
|
|
780
|
+
void this.runReadinessCheckDraftInBackground(prepDataDir, request, job.run_id);
|
|
781
|
+
return this.getJob(prepDataDir, job.run_id) ?? job;
|
|
1239
782
|
}
|
|
1240
|
-
applyMethodChange(requestValue) {
|
|
783
|
+
applyMethodChange(prepDataDir, requestValue) {
|
|
1241
784
|
const request = MethodChangeCreateRequestSchema.parse(requestValue);
|
|
1242
785
|
const outputPath = request.operation === "duplicate"
|
|
1243
|
-
? methodDefinitionPath(
|
|
1244
|
-
: methodDefinitionPath(
|
|
786
|
+
? methodDefinitionPath(prepDataDir, request.new_method_id)
|
|
787
|
+
: methodDefinitionPath(prepDataDir, request.method);
|
|
1245
788
|
if (request.operation === "duplicate") {
|
|
1246
|
-
if (resolveMethodPackageSourcePath(
|
|
789
|
+
if (resolveMethodPackageSourcePath(prepDataDir, request.new_method_id)) {
|
|
1247
790
|
throw new Error(`Method "${request.new_method_id}" already exists.`);
|
|
1248
791
|
}
|
|
1249
|
-
if (!resolveMethodPackageSourcePath(
|
|
792
|
+
if (!resolveMethodPackageSourcePath(prepDataDir, request.method)) {
|
|
1250
793
|
throw new Error(`Method "${request.method}" does not exist.`);
|
|
1251
794
|
}
|
|
1252
795
|
const label = request.label ?? methodLabelFromId(request.new_method_id);
|
|
1253
796
|
const hint = request.hint ?? `Duplicate of ${request.method}`;
|
|
1254
797
|
const methodPath = seedLocalMethodPackageFromBase({
|
|
1255
|
-
|
|
798
|
+
prepDataDir,
|
|
1256
799
|
baseMethodId: request.method,
|
|
1257
800
|
methodId: request.new_method_id,
|
|
1258
801
|
label,
|
|
1259
802
|
hint,
|
|
1260
803
|
});
|
|
804
|
+
this.methodListingCache.invalidate(prepDataDir);
|
|
1261
805
|
return MethodChangeResultSchema.parse({
|
|
1262
806
|
kind: "interf-method-change-result",
|
|
1263
807
|
version: 1,
|
|
@@ -1273,19 +817,24 @@ export class LocalServiceRuntime {
|
|
|
1273
817
|
if (request.confirmation !== request.method) {
|
|
1274
818
|
throw new Error(`Type ${request.method} to confirm Method removal.`);
|
|
1275
819
|
}
|
|
1276
|
-
const localMethodPath = methodDefinitionPath(
|
|
820
|
+
const localMethodPath = methodDefinitionPath(prepDataDir, request.method);
|
|
1277
821
|
if (request.method === DEFAULT_METHOD_ID || !existsSync(localMethodPath)) {
|
|
1278
822
|
throw new Error(`Method "${request.method}" is not a removable local Method.`);
|
|
1279
823
|
}
|
|
1280
|
-
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(
|
|
824
|
+
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir));
|
|
1281
825
|
const updatedPreparations = preparations
|
|
1282
826
|
.filter((preparation) => methodIdForSourcePreparationConfig(preparation) === request.method);
|
|
1283
827
|
if (updatedPreparations.length > 0) {
|
|
1284
|
-
saveSourceFolderConfig(
|
|
828
|
+
saveSourceFolderConfig(prepDataDir, {
|
|
1285
829
|
preparations: preparations.map((preparation) => detachMethodFromPreparation(preparation, request.method)),
|
|
1286
830
|
});
|
|
831
|
+
// Detaching a Method changes readiness shape for those Preparations.
|
|
832
|
+
for (const preparation of updatedPreparations) {
|
|
833
|
+
this.readinessCache.invalidatePreparation(prepDataDir, preparation.name);
|
|
834
|
+
}
|
|
1287
835
|
}
|
|
1288
836
|
rmSync(outputPath, { recursive: true, force: true });
|
|
837
|
+
this.methodListingCache.invalidate(prepDataDir);
|
|
1289
838
|
return MethodChangeResultSchema.parse({
|
|
1290
839
|
kind: "interf-method-change-result",
|
|
1291
840
|
version: 1,
|
|
@@ -1299,7 +848,75 @@ export class LocalServiceRuntime {
|
|
|
1299
848
|
: `Removed Method ${request.method}.`,
|
|
1300
849
|
});
|
|
1301
850
|
}
|
|
1302
|
-
|
|
851
|
+
/**
|
|
852
|
+
* Bootstrap the workspace's source-folder binding and seed the default
|
|
853
|
+
* Method. Idempotent: re-running with the same source folder preserves
|
|
854
|
+
* existing preparations and reports `changed: false`.
|
|
855
|
+
*
|
|
856
|
+
* The CLI calls this in place of writing `interf.json` directly, so the
|
|
857
|
+
* operation is recorded by the service (and visible to other clients).
|
|
858
|
+
*/
|
|
859
|
+
bootstrapWorkspace(prepDataDir, requestValue) {
|
|
860
|
+
const request = WorkspaceBootstrapCreateRequestSchema.parse(requestValue);
|
|
861
|
+
const requestedPath = request.source_folder?.path?.trim() ?? null;
|
|
862
|
+
const existing = loadSourceFolderConfig(prepDataDir);
|
|
863
|
+
const existingSourcePath = existing?.source_folder?.path ?? null;
|
|
864
|
+
const preparations = existing?.preparations ?? [];
|
|
865
|
+
let sourceFolderPath = existingSourcePath;
|
|
866
|
+
let sourceChanged = false;
|
|
867
|
+
if (requestedPath && requestedPath.length > 0) {
|
|
868
|
+
// Validate that the source folder exists relative to the workspace.
|
|
869
|
+
const resolvedSourcePath = resolve(prepDataDir, requestedPath);
|
|
870
|
+
if (!existsSync(resolvedSourcePath) || !statSync(resolvedSourcePath).isDirectory()) {
|
|
871
|
+
throw new Error(`Source folder "${requestedPath}" is not available.`);
|
|
872
|
+
}
|
|
873
|
+
if (existingSourcePath !== requestedPath) {
|
|
874
|
+
sourceFolderPath = requestedPath;
|
|
875
|
+
sourceChanged = true;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const writeNeeded = !existing || sourceChanged;
|
|
879
|
+
if (writeNeeded) {
|
|
880
|
+
saveSourceFolderConfig(prepDataDir, {
|
|
881
|
+
...(sourceFolderPath ? { source_folder: { path: sourceFolderPath } } : {}),
|
|
882
|
+
preparations,
|
|
883
|
+
});
|
|
884
|
+
// Source folder rebound: drop every cached read for this workspace.
|
|
885
|
+
this.readinessCache.invalidateWorkspace(prepDataDir);
|
|
886
|
+
this.compileRunCache.invalidateWorkspace(prepDataDir);
|
|
887
|
+
this.testRunCache.invalidateWorkspace(prepDataDir);
|
|
888
|
+
this.sourceFilesCache.invalidateAll();
|
|
889
|
+
this.methodListingCache.invalidate(prepDataDir);
|
|
890
|
+
}
|
|
891
|
+
let seededMethodId = null;
|
|
892
|
+
let seededDefaultMethod = false;
|
|
893
|
+
if (request.seed_default_method) {
|
|
894
|
+
const seeded = seedLocalDefaultMethod({ prepDataDir });
|
|
895
|
+
seededMethodId = seeded.methodId;
|
|
896
|
+
seededDefaultMethod = !seeded.alreadyExisted;
|
|
897
|
+
if (seededDefaultMethod) {
|
|
898
|
+
this.methodListingCache.invalidate(prepDataDir);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const changed = writeNeeded || seededDefaultMethod;
|
|
902
|
+
return WorkspaceBootstrapResultSchema.parse({
|
|
903
|
+
kind: "interf-workspace-bootstrap-result",
|
|
904
|
+
version: 1,
|
|
905
|
+
control_path: prepDataDir,
|
|
906
|
+
config_path: preparationConfigPath(asPreparationDataDir(prepDataDir)),
|
|
907
|
+
source_folder_path: sourceFolderPath,
|
|
908
|
+
preparations: preparations.length,
|
|
909
|
+
seeded_default_method: seededDefaultMethod,
|
|
910
|
+
default_method_id: seededMethodId,
|
|
911
|
+
changed,
|
|
912
|
+
message: changed
|
|
913
|
+
? sourceFolderPath
|
|
914
|
+
? `Workspace ready. Source Folder: ${sourceFolderPath}.`
|
|
915
|
+
: `Workspace ready.`
|
|
916
|
+
: `Workspace already initialized.`,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
applyPreparationSetup(prepDataDir, requestValue) {
|
|
1303
920
|
const request = PreparationSetupCreateRequestSchema.parse(requestValue);
|
|
1304
921
|
const preparationConfig = request.preparation;
|
|
1305
922
|
const methodId = methodIdForSourcePreparationConfig(preparationConfig) ?? DEFAULT_METHOD_ID;
|
|
@@ -1307,11 +924,18 @@ export class LocalServiceRuntime {
|
|
|
1307
924
|
...preparationConfig,
|
|
1308
925
|
method: methodId,
|
|
1309
926
|
};
|
|
1310
|
-
const sourceFolderPath = resolveSourcePreparationPath(
|
|
927
|
+
const sourceFolderPath = resolveSourcePreparationPath(prepDataDir, normalizedPreparationConfig);
|
|
1311
928
|
if (!existsSync(sourceFolderPath) || !statSync(sourceFolderPath).isDirectory()) {
|
|
1312
929
|
throw new Error(`Source folder "${preparationConfig.path}" is not available.`);
|
|
1313
930
|
}
|
|
1314
|
-
upsertSourcePreparationConfig(
|
|
931
|
+
upsertSourcePreparationConfig(prepDataDir, normalizedPreparationConfig);
|
|
932
|
+
// The Preparation's bound source folder + Method may have changed:
|
|
933
|
+
// bust the per-preparation readiness, runs, and method-listing
|
|
934
|
+
// caches so the next read reflects the new shape.
|
|
935
|
+
this.readinessCache.invalidatePreparation(prepDataDir, normalizedPreparationConfig.name);
|
|
936
|
+
this.compileRunCache.invalidatePreparation(prepDataDir, normalizedPreparationConfig.name);
|
|
937
|
+
this.testRunCache.invalidatePreparation(prepDataDir, normalizedPreparationConfig.name);
|
|
938
|
+
this.methodListingCache.invalidate(prepDataDir);
|
|
1315
939
|
const operation = request.setup_mode === "select-method" ? "select-method" : "create";
|
|
1316
940
|
return PreparationSetupResultSchema.parse({
|
|
1317
941
|
kind: "interf-preparation-setup-result",
|
|
@@ -1320,47 +944,55 @@ export class LocalServiceRuntime {
|
|
|
1320
944
|
preparation: normalizedPreparationConfig.name,
|
|
1321
945
|
method: methodId,
|
|
1322
946
|
source_folder_path: sourceFolderPath,
|
|
1323
|
-
config_path:
|
|
1324
|
-
portable_context_path:
|
|
947
|
+
config_path: preparationConfigPath(asPreparationDataDir(prepDataDir)),
|
|
948
|
+
portable_context_path: preparationPortableContextPath(asPreparationDataDir(prepDataDir), normalizedPreparationConfig.name),
|
|
1325
949
|
changed: true,
|
|
1326
950
|
message: operation === "select-method"
|
|
1327
951
|
? `Preparation ${normalizedPreparationConfig.name} now uses Method ${methodId}.`
|
|
1328
952
|
: `Preparation ${normalizedPreparationConfig.name} is saved.`,
|
|
1329
953
|
});
|
|
1330
954
|
}
|
|
1331
|
-
applyPreparationChange(requestValue) {
|
|
955
|
+
applyPreparationChange(prepDataDir, requestValue) {
|
|
1332
956
|
const request = PreparationChangeCreateRequestSchema.parse(requestValue);
|
|
1333
957
|
if (request.confirmation !== request.preparation) {
|
|
1334
958
|
throw new Error(`Type ${request.preparation} to confirm Preparation removal.`);
|
|
1335
959
|
}
|
|
1336
|
-
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(
|
|
960
|
+
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), request.preparation);
|
|
1337
961
|
if (!preparation) {
|
|
1338
962
|
throw new Error(`Preparation "${request.preparation}" is not saved.`);
|
|
1339
963
|
}
|
|
1340
|
-
removeSourcePreparationConfig(
|
|
964
|
+
removeSourcePreparationConfig(prepDataDir, request.preparation);
|
|
965
|
+
this.readinessCache.invalidatePreparation(prepDataDir, request.preparation);
|
|
966
|
+
this.compileRunCache.invalidatePreparation(prepDataDir, request.preparation);
|
|
967
|
+
this.testRunCache.invalidatePreparation(prepDataDir, request.preparation);
|
|
968
|
+
this.methodListingCache.invalidate(prepDataDir);
|
|
1341
969
|
return PreparationChangeResultSchema.parse({
|
|
1342
970
|
kind: "interf-preparation-change-result",
|
|
1343
971
|
version: 1,
|
|
1344
972
|
operation: "remove",
|
|
1345
973
|
preparation: request.preparation,
|
|
1346
|
-
config_path:
|
|
1347
|
-
portable_context_path:
|
|
974
|
+
config_path: preparationConfigPath(asPreparationDataDir(prepDataDir)),
|
|
975
|
+
portable_context_path: preparationPortableContextPath(asPreparationDataDir(prepDataDir), request.preparation),
|
|
1348
976
|
portable_context_retained: true,
|
|
1349
977
|
changed: true,
|
|
1350
978
|
message: `Removed Preparation ${request.preparation}. Portable Context files were retained.`,
|
|
1351
979
|
});
|
|
1352
980
|
}
|
|
1353
|
-
applyReset(requestValue) {
|
|
981
|
+
applyReset(prepDataDir, requestValue) {
|
|
1354
982
|
const request = ResetRequestSchema.parse(requestValue);
|
|
1355
|
-
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(
|
|
983
|
+
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), request.preparation);
|
|
1356
984
|
if (!preparation) {
|
|
1357
985
|
throw new Error(`Preparation "${request.preparation}" is not saved.`);
|
|
1358
986
|
}
|
|
1359
|
-
const compiledPath =
|
|
987
|
+
const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), request.preparation);
|
|
1360
988
|
if (!existsSync(compiledPath)) {
|
|
1361
989
|
throw new Error(`Portable Context for Preparation "${request.preparation}" does not exist.`);
|
|
1362
990
|
}
|
|
1363
991
|
resetCompiledGeneratedState(compiledPath, request.scope);
|
|
992
|
+
// Reset wipes generated state, including saved compile/test/readiness records.
|
|
993
|
+
this.compileRunCache.invalidatePreparation(prepDataDir, request.preparation);
|
|
994
|
+
this.testRunCache.invalidatePreparation(prepDataDir, request.preparation);
|
|
995
|
+
this.readinessCache.invalidatePreparation(prepDataDir, request.preparation);
|
|
1364
996
|
return ResetResultSchema.parse({
|
|
1365
997
|
kind: "interf-reset-result",
|
|
1366
998
|
version: 1,
|
|
@@ -1371,16 +1003,16 @@ export class LocalServiceRuntime {
|
|
|
1371
1003
|
message: `Reset ${request.scope} state for Preparation ${request.preparation}.`,
|
|
1372
1004
|
});
|
|
1373
1005
|
}
|
|
1374
|
-
async createMethodAuthoringRun(requestValue, jobType = "method-authoring") {
|
|
1006
|
+
async createMethodAuthoringRun(prepDataDir, requestValue, jobType = "method-authoring") {
|
|
1375
1007
|
const request = MethodAuthoringCreateRequestSchema.parse(requestValue);
|
|
1376
1008
|
const isImprovement = jobType === "method-improvement";
|
|
1377
|
-
const job = this.createJobRun({
|
|
1009
|
+
const job = this.createJobRun(prepDataDir, {
|
|
1378
1010
|
job_type: jobType,
|
|
1379
1011
|
title: isImprovement ? `Improve Method ${request.method_id}` : `Draft Method ${request.method_id}`,
|
|
1380
1012
|
preparation: request.preparation ?? null,
|
|
1381
1013
|
method: request.method_id,
|
|
1382
1014
|
source_path: request.source_folder_path,
|
|
1383
|
-
output_path:
|
|
1015
|
+
output_path: preparationMethodPackagePath(asPreparationDataDir(prepDataDir), request.method_id),
|
|
1384
1016
|
steps: [
|
|
1385
1017
|
{
|
|
1386
1018
|
id: "inspect-source",
|
|
@@ -1409,7 +1041,7 @@ export class LocalServiceRuntime {
|
|
|
1409
1041
|
},
|
|
1410
1042
|
],
|
|
1411
1043
|
});
|
|
1412
|
-
this.appendJobRunEvent(job.run_id, {
|
|
1044
|
+
this.appendJobRunEvent(prepDataDir, job.run_id, {
|
|
1413
1045
|
type: "step.started",
|
|
1414
1046
|
step_id: "inspect-source",
|
|
1415
1047
|
message: isImprovement ? "Inspecting source files for Method improvement." : "Inspecting source files for Method drafting.",
|
|
@@ -1419,7 +1051,7 @@ export class LocalServiceRuntime {
|
|
|
1419
1051
|
checks: request.checks.length,
|
|
1420
1052
|
},
|
|
1421
1053
|
});
|
|
1422
|
-
this.appendJobRunEvent(job.run_id, {
|
|
1054
|
+
this.appendJobRunEvent(prepDataDir, job.run_id, {
|
|
1423
1055
|
type: "step.completed",
|
|
1424
1056
|
step_id: "inspect-source",
|
|
1425
1057
|
message: "Source context is ready.",
|
|
@@ -1428,7 +1060,7 @@ export class LocalServiceRuntime {
|
|
|
1428
1060
|
checks: request.checks.length,
|
|
1429
1061
|
},
|
|
1430
1062
|
});
|
|
1431
|
-
this.appendJobRunEvent(job.run_id, {
|
|
1063
|
+
this.appendJobRunEvent(prepDataDir, job.run_id, {
|
|
1432
1064
|
type: "step.started",
|
|
1433
1065
|
step_id: "draft-package",
|
|
1434
1066
|
message: isImprovement ? "Improving Method package." : "Drafting Method package.",
|
|
@@ -1438,23 +1070,23 @@ export class LocalServiceRuntime {
|
|
|
1438
1070
|
task_prompt: request.task_prompt,
|
|
1439
1071
|
},
|
|
1440
1072
|
});
|
|
1441
|
-
void this.runMethodAuthoringInBackground(request, job.run_id);
|
|
1442
|
-
return this.getJob(job.run_id) ?? job;
|
|
1073
|
+
void this.runMethodAuthoringInBackground(prepDataDir, request, job.run_id);
|
|
1074
|
+
return this.getJob(prepDataDir, job.run_id) ?? job;
|
|
1443
1075
|
}
|
|
1444
|
-
listPortableContexts() {
|
|
1445
|
-
return listSourcePreparationConfigs(loadSourceFolderConfig(
|
|
1446
|
-
.map((preparation) => this.getPortableContext(preparation.name))
|
|
1076
|
+
listPortableContexts(prepDataDir) {
|
|
1077
|
+
return listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
|
|
1078
|
+
.map((preparation) => this.getPortableContext(prepDataDir, preparation.name))
|
|
1447
1079
|
.filter((context) => context !== null);
|
|
1448
1080
|
}
|
|
1449
|
-
getPortableContext(preparationName) {
|
|
1450
|
-
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(
|
|
1081
|
+
getPortableContext(prepDataDir, preparationName) {
|
|
1082
|
+
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), preparationName);
|
|
1451
1083
|
if (!preparation)
|
|
1452
1084
|
return null;
|
|
1453
|
-
const path =
|
|
1085
|
+
const path = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparation.name);
|
|
1454
1086
|
const config = readInterfConfig(path);
|
|
1455
|
-
const compileRuns = this.listCompileRunsForPreparation(preparation.name);
|
|
1456
|
-
const testRuns = this.listTestRunsForPreparation(preparation.name);
|
|
1457
|
-
const readiness = this.computePreparationReadiness(preparation);
|
|
1087
|
+
const compileRuns = this.listCompileRunsForPreparation(prepDataDir, preparation.name);
|
|
1088
|
+
const testRuns = this.listTestRunsForPreparation(prepDataDir, preparation.name);
|
|
1089
|
+
const readiness = this.computePreparationReadiness(prepDataDir, preparation);
|
|
1458
1090
|
const method = config?.method ?? methodIdForSourcePreparationConfig(preparation);
|
|
1459
1091
|
return PortableContextResourceSchema.parse({
|
|
1460
1092
|
preparation: preparation.name,
|
|
@@ -1467,52 +1099,65 @@ export class LocalServiceRuntime {
|
|
|
1467
1099
|
artifacts: uniqueArtifacts(compileRuns[0]?.stages.flatMap((stage) => stage.artifacts) ?? []),
|
|
1468
1100
|
});
|
|
1469
1101
|
}
|
|
1470
|
-
listCompileRuns() {
|
|
1471
|
-
return
|
|
1472
|
-
.flatMap((preparation) => this.listCompileRunsForPreparation(preparation.name))).map((run) => CompileRunResourceSchema.parse({ run }));
|
|
1473
|
-
}
|
|
1474
|
-
listCompileRunsForPreparation(preparationName) {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1102
|
+
listCompileRuns(prepDataDir) {
|
|
1103
|
+
return byCreatedAtDesc(listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
|
|
1104
|
+
.flatMap((preparation) => this.listCompileRunsForPreparation(prepDataDir, preparation.name))).map((run) => CompileRunResourceSchema.parse({ run }));
|
|
1105
|
+
}
|
|
1106
|
+
listCompileRunsForPreparation(prepDataDir, preparationName) {
|
|
1107
|
+
return this.compileRunCache.get(prepDataDir, preparationName, () => {
|
|
1108
|
+
const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparationName);
|
|
1109
|
+
return byCreatedAtDesc(listJsonFiles(compileRunsRoot(compiledPath))
|
|
1110
|
+
.map(readCompileRunAt)
|
|
1111
|
+
.filter((run) => run !== null));
|
|
1112
|
+
}, (run) => run.run_id);
|
|
1113
|
+
}
|
|
1114
|
+
getCompileRun(prepDataDir, runId) {
|
|
1115
|
+
// Fast path: if the runId was seen during a recent listing, look up
|
|
1116
|
+
// its owning preparation directly and return that preparation's
|
|
1117
|
+
// cached entry instead of scanning every preparation on disk.
|
|
1118
|
+
const known = this.compileRunCache.preparationFor(prepDataDir, runId);
|
|
1119
|
+
if (known) {
|
|
1120
|
+
const found = this.listCompileRunsForPreparation(prepDataDir, known).find((entry) => entry.run_id === runId);
|
|
1121
|
+
if (found)
|
|
1122
|
+
return CompileRunResourceSchema.parse({ run: found });
|
|
1123
|
+
}
|
|
1124
|
+
// Slow path: scan all preparations. Falls through after a cache
|
|
1125
|
+
// miss for an in-flight run created before this process restarted.
|
|
1126
|
+
for (const resource of this.listCompileRuns(prepDataDir)) {
|
|
1482
1127
|
if (resource.run.run_id === runId)
|
|
1483
1128
|
return resource;
|
|
1484
1129
|
}
|
|
1485
1130
|
return null;
|
|
1486
1131
|
}
|
|
1487
|
-
getCompileRunEvents(runId) {
|
|
1488
|
-
return this.getCompileRun(runId)?.run.events ?? null;
|
|
1132
|
+
getCompileRunEvents(prepDataDir, runId) {
|
|
1133
|
+
return this.getCompileRun(prepDataDir, runId)?.run.events ?? null;
|
|
1489
1134
|
}
|
|
1490
|
-
getCompileRunProof(runId) {
|
|
1491
|
-
const run = this.getCompileRun(runId)?.run;
|
|
1135
|
+
getCompileRunProof(prepDataDir, runId) {
|
|
1136
|
+
const run = this.getCompileRun(prepDataDir, runId)?.run;
|
|
1492
1137
|
if (!run)
|
|
1493
1138
|
return null;
|
|
1494
1139
|
return run.stages
|
|
1495
1140
|
.map((stage) => stage.latest_proof ?? null)
|
|
1496
1141
|
.filter((proof) => proof !== null);
|
|
1497
1142
|
}
|
|
1498
|
-
getCompileRunArtifacts(runId) {
|
|
1499
|
-
const run = this.getCompileRun(runId)?.run;
|
|
1143
|
+
getCompileRunArtifacts(prepDataDir, runId) {
|
|
1144
|
+
const run = this.getCompileRun(prepDataDir, runId)?.run;
|
|
1500
1145
|
if (!run)
|
|
1501
1146
|
return null;
|
|
1502
1147
|
return uniqueArtifacts(run.stages.flatMap((stage) => stage.artifacts));
|
|
1503
1148
|
}
|
|
1504
|
-
async createCompileRun(requestValue) {
|
|
1149
|
+
async createCompileRun(prepDataDir, requestValue) {
|
|
1505
1150
|
const request = CompileRunCreateRequestSchema.parse(requestValue);
|
|
1506
|
-
const preparationConfig = this.resolvePreparationConfig(request.preparation, {
|
|
1151
|
+
const preparationConfig = this.resolvePreparationConfig(prepDataDir, request.preparation, {
|
|
1507
1152
|
method: request.method,
|
|
1508
1153
|
max_attempts: request.max_attempts,
|
|
1509
1154
|
max_loops: request.max_loops,
|
|
1510
1155
|
});
|
|
1511
|
-
const compiledPath = this.ensureCompiledForRun(preparationConfig);
|
|
1156
|
+
const compiledPath = this.ensureCompiledForRun(prepDataDir, preparationConfig);
|
|
1512
1157
|
const runId = createRunId("compile");
|
|
1513
1158
|
const now = new Date().toISOString();
|
|
1514
1159
|
const method = getCompiledMethod(requireSelectedMethod(preparationConfig), {
|
|
1515
|
-
|
|
1160
|
+
prepDataDir,
|
|
1516
1161
|
});
|
|
1517
1162
|
const stageTotal = method.stages.length;
|
|
1518
1163
|
const run = CompileRunSchema.parse({
|
|
@@ -1523,7 +1168,7 @@ export class LocalServiceRuntime {
|
|
|
1523
1168
|
preparation: preparationConfig.name,
|
|
1524
1169
|
method: method.id,
|
|
1525
1170
|
backend: "native",
|
|
1526
|
-
source_path: resolveSourcePreparationPath(
|
|
1171
|
+
source_path: resolveSourcePreparationPath(prepDataDir, preparationConfig),
|
|
1527
1172
|
portable_context_path: compiledPath,
|
|
1528
1173
|
created_at: now,
|
|
1529
1174
|
started_at: now,
|
|
@@ -1549,8 +1194,14 @@ export class LocalServiceRuntime {
|
|
|
1549
1194
|
}),
|
|
1550
1195
|
events: [],
|
|
1551
1196
|
});
|
|
1552
|
-
this.writeCompileRun(compiledPath, run);
|
|
1553
|
-
|
|
1197
|
+
this.writeCompileRun(prepDataDir, compiledPath, run);
|
|
1198
|
+
this.activeCompileRuns.set(runId, {
|
|
1199
|
+
prepDataDir,
|
|
1200
|
+
compiledPath,
|
|
1201
|
+
preparation: preparationConfig.name,
|
|
1202
|
+
cancelled: false,
|
|
1203
|
+
});
|
|
1204
|
+
await this.recordCompileRunEvent(prepDataDir, compiledPath, runId, {
|
|
1554
1205
|
type: "run.started",
|
|
1555
1206
|
event_id: createRunEventId("event"),
|
|
1556
1207
|
run_id: runId,
|
|
@@ -1561,11 +1212,11 @@ export class LocalServiceRuntime {
|
|
|
1561
1212
|
backend: "native",
|
|
1562
1213
|
});
|
|
1563
1214
|
const sink = {
|
|
1564
|
-
emit: (event) => this.recordCompileRunEvent(compiledPath, runId, event),
|
|
1215
|
+
emit: (event) => this.recordCompileRunEvent(prepDataDir, compiledPath, runId, event),
|
|
1565
1216
|
};
|
|
1566
|
-
void this.runCompileInBackground(request, {
|
|
1217
|
+
void this.runCompileInBackground(prepDataDir, request, {
|
|
1567
1218
|
runId,
|
|
1568
|
-
sourcePath:
|
|
1219
|
+
sourcePath: prepDataDir,
|
|
1569
1220
|
compiledPath,
|
|
1570
1221
|
preparationConfig,
|
|
1571
1222
|
events: sink,
|
|
@@ -1573,23 +1224,155 @@ export class LocalServiceRuntime {
|
|
|
1573
1224
|
const saved = this.readCompileRun(compiledPath, runId) ?? run;
|
|
1574
1225
|
return CompileRunResourceSchema.parse({ run: saved });
|
|
1575
1226
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1227
|
+
/**
|
|
1228
|
+
* Cancel an in-flight compile run. Marks the persisted record as
|
|
1229
|
+
* `cancelled`, emits a `run.failed` event to capture the cancellation in
|
|
1230
|
+
* the run timeline, and clears the active handle so retries may start a
|
|
1231
|
+
* fresh run. If the run already finished, returns
|
|
1232
|
+
* `{ cancelled: false, reason: "already finished" }` and persists nothing.
|
|
1233
|
+
*/
|
|
1234
|
+
cancelCompileRun(runId) {
|
|
1235
|
+
const handle = this.activeCompileRuns.get(runId);
|
|
1236
|
+
if (!handle) {
|
|
1237
|
+
// Either unknown or already terminal. The server route already 404s
|
|
1238
|
+
// unknown ids before calling this, so anything reaching here is a run
|
|
1239
|
+
// we already finalized.
|
|
1240
|
+
return { cancelled: false, reason: "already finished" };
|
|
1241
|
+
}
|
|
1242
|
+
if (handle.cancelled) {
|
|
1243
|
+
return { cancelled: false, reason: "already cancelled" };
|
|
1244
|
+
}
|
|
1245
|
+
const cancelledAt = new Date().toISOString();
|
|
1246
|
+
handle.cancelled = true;
|
|
1247
|
+
handle.cancelledAt = cancelledAt;
|
|
1248
|
+
const current = this.readCompileRun(handle.compiledPath, runId);
|
|
1249
|
+
if (current && current.status !== "succeeded" && current.status !== "failed" && current.status !== "cancelled") {
|
|
1250
|
+
const cancelledRun = {
|
|
1251
|
+
...current,
|
|
1252
|
+
status: "cancelled",
|
|
1253
|
+
finished_at: current.finished_at ?? cancelledAt,
|
|
1254
|
+
events: [
|
|
1255
|
+
...current.events,
|
|
1256
|
+
{
|
|
1257
|
+
type: "run.failed",
|
|
1258
|
+
event_id: createRunEventId("event"),
|
|
1259
|
+
run_id: runId,
|
|
1260
|
+
timestamp: cancelledAt,
|
|
1261
|
+
error: "Compile run cancelled by request.",
|
|
1262
|
+
},
|
|
1263
|
+
],
|
|
1264
|
+
};
|
|
1265
|
+
this.writeCompileRun(handle.prepDataDir, handle.compiledPath, cancelledRun);
|
|
1266
|
+
}
|
|
1267
|
+
return { cancelled: true };
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Look up the run id previously associated with this idempotency key in
|
|
1271
|
+
* `prepDataDir`. Returns null when the key is unknown or its TTL has
|
|
1272
|
+
* elapsed. The workspace argument is required so that the same key in
|
|
1273
|
+
* two different workspaces always returns two different runs.
|
|
1274
|
+
*/
|
|
1275
|
+
findIdempotentCompileRun(prepDataDir, key) {
|
|
1276
|
+
const resolvedRoot = resolve(prepDataDir);
|
|
1277
|
+
const bucket = this.idempotencyKeyCache.get(resolvedRoot);
|
|
1278
|
+
if (!bucket)
|
|
1279
|
+
return null;
|
|
1280
|
+
const entry = bucket.get(key);
|
|
1281
|
+
if (!entry)
|
|
1282
|
+
return null;
|
|
1283
|
+
if (entry.expiresAt <= Date.now()) {
|
|
1284
|
+
// Opportunistic single-key prune. The bulk prune runs on writes
|
|
1285
|
+
// when the cache crosses the size threshold (see
|
|
1286
|
+
// {@link recordIdempotentCompileRun}).
|
|
1287
|
+
bucket.delete(key);
|
|
1288
|
+
if (bucket.size === 0)
|
|
1289
|
+
this.idempotencyKeyCache.delete(resolvedRoot);
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
return entry.runId;
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Cache the run id created (or returned) for this idempotency key in
|
|
1296
|
+
* `prepDataDir`. Entries expire after `IDEMPOTENCY_TTL_MS`. Pruning
|
|
1297
|
+
* is opportunistic: the previous implementation walked every entry on
|
|
1298
|
+
* every read AND write, which was O(N) per request. Now we only sweep
|
|
1299
|
+
* when the cache grows past {@link IDEMPOTENCY_PRUNE_THRESHOLD}.
|
|
1300
|
+
*/
|
|
1301
|
+
recordIdempotentCompileRun(prepDataDir, key, runId) {
|
|
1302
|
+
const resolvedRoot = resolve(prepDataDir);
|
|
1303
|
+
let bucket = this.idempotencyKeyCache.get(resolvedRoot);
|
|
1304
|
+
if (!bucket) {
|
|
1305
|
+
bucket = new Map();
|
|
1306
|
+
this.idempotencyKeyCache.set(resolvedRoot, bucket);
|
|
1307
|
+
}
|
|
1308
|
+
bucket.set(key, {
|
|
1309
|
+
runId,
|
|
1310
|
+
expiresAt: Date.now() + IDEMPOTENCY_TTL_MS,
|
|
1311
|
+
});
|
|
1312
|
+
if (this.totalIdempotencyEntries() > IDEMPOTENCY_PRUNE_THRESHOLD) {
|
|
1313
|
+
this.pruneIdempotencyKeyCache();
|
|
1314
|
+
}
|
|
1579
1315
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1316
|
+
/** Total cached idempotency entries across all workspaces. */
|
|
1317
|
+
totalIdempotencyEntries() {
|
|
1318
|
+
let total = 0;
|
|
1319
|
+
for (const bucket of this.idempotencyKeyCache.values())
|
|
1320
|
+
total += bucket.size;
|
|
1321
|
+
return total;
|
|
1322
|
+
}
|
|
1323
|
+
pruneIdempotencyKeyCache() {
|
|
1324
|
+
const now = Date.now();
|
|
1325
|
+
for (const [prepDataDir, bucket] of this.idempotencyKeyCache) {
|
|
1326
|
+
for (const [key, entry] of bucket) {
|
|
1327
|
+
if (entry.expiresAt <= now)
|
|
1328
|
+
bucket.delete(key);
|
|
1329
|
+
}
|
|
1330
|
+
if (bucket.size === 0)
|
|
1331
|
+
this.idempotencyKeyCache.delete(prepDataDir);
|
|
1332
|
+
}
|
|
1585
1333
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1334
|
+
/**
|
|
1335
|
+
* Test seam: force the cached entry for `key` in `prepDataDir` to be
|
|
1336
|
+
* expired so the next lookup returns null. Returns true when an entry was
|
|
1337
|
+
* found and expired. Tests use this in place of fake timers because the
|
|
1338
|
+
* idempotency TTL is one hour and faking `Date.now()` would destabilize
|
|
1339
|
+
* unrelated runtime state.
|
|
1340
|
+
*/
|
|
1341
|
+
expireIdempotencyKeyForTesting(prepDataDir, key) {
|
|
1342
|
+
const bucket = this.idempotencyKeyCache.get(resolve(prepDataDir));
|
|
1343
|
+
if (!bucket)
|
|
1344
|
+
return false;
|
|
1345
|
+
const entry = bucket.get(key);
|
|
1346
|
+
if (!entry)
|
|
1347
|
+
return false;
|
|
1348
|
+
entry.expiresAt = Date.now() - 1;
|
|
1349
|
+
return true;
|
|
1350
|
+
}
|
|
1351
|
+
listTestRuns(prepDataDir) {
|
|
1352
|
+
return newestFirst(listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))
|
|
1353
|
+
.flatMap((preparation) => this.listTestRunsForPreparation(prepDataDir, preparation.name)));
|
|
1354
|
+
}
|
|
1355
|
+
listTestRunsForPreparation(prepDataDir, preparationName) {
|
|
1356
|
+
return this.testRunCache.get(prepDataDir, preparationName, () => {
|
|
1357
|
+
const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparationName);
|
|
1358
|
+
return newestFirst(listJsonFiles(testRunsRoot(compiledPath))
|
|
1359
|
+
.map(readTestRunAt)
|
|
1360
|
+
.filter((run) => run !== null));
|
|
1361
|
+
}, (run) => run.run_id);
|
|
1362
|
+
}
|
|
1363
|
+
getTestRun(prepDataDir, runId) {
|
|
1364
|
+
const known = this.testRunCache.preparationFor(prepDataDir, runId);
|
|
1365
|
+
if (known) {
|
|
1366
|
+
const found = this.listTestRunsForPreparation(prepDataDir, known).find((run) => run.run_id === runId);
|
|
1367
|
+
if (found)
|
|
1368
|
+
return found;
|
|
1369
|
+
}
|
|
1370
|
+
return this.listTestRuns(prepDataDir).find((run) => run.run_id === runId) ?? null;
|
|
1588
1371
|
}
|
|
1589
|
-
async createTestRun(requestValue) {
|
|
1372
|
+
async createTestRun(prepDataDir, requestValue) {
|
|
1590
1373
|
const request = TestRunCreateRequestSchema.parse(requestValue);
|
|
1591
|
-
const preparationConfig = this.resolvePreparationConfig(request.preparation);
|
|
1592
|
-
const compiledPath =
|
|
1374
|
+
const preparationConfig = this.resolvePreparationConfig(prepDataDir, request.preparation);
|
|
1375
|
+
const compiledPath = preparationPortableContextPath(asPreparationDataDir(prepDataDir), preparationConfig.name);
|
|
1593
1376
|
const compiledTarget = createCompiledTestTarget(compiledPath, preparationConfig.name, methodIdForSourcePreparationConfig(preparationConfig) ?? DEFAULT_METHOD_ID);
|
|
1594
1377
|
const runId = createRunId("test");
|
|
1595
1378
|
const now = new Date().toISOString();
|
|
@@ -1598,31 +1381,42 @@ export class LocalServiceRuntime {
|
|
|
1598
1381
|
status: "running",
|
|
1599
1382
|
preparation: preparationConfig.name,
|
|
1600
1383
|
mode: request.mode,
|
|
1601
|
-
source_path:
|
|
1384
|
+
source_path: prepDataDir,
|
|
1602
1385
|
portable_context_path: compiledTarget.eligible ? compiledPath : null,
|
|
1603
1386
|
started_at: now,
|
|
1604
1387
|
readiness_run: null,
|
|
1605
1388
|
events: [],
|
|
1606
1389
|
});
|
|
1607
|
-
this.writeTestRun(compiledPath, initial);
|
|
1608
|
-
void this.runTestInBackground(request, {
|
|
1390
|
+
this.writeTestRun(prepDataDir, compiledPath, initial);
|
|
1391
|
+
void this.runTestInBackground(prepDataDir, request, {
|
|
1609
1392
|
runId,
|
|
1610
|
-
sourcePath:
|
|
1393
|
+
sourcePath: prepDataDir,
|
|
1611
1394
|
compiledPath,
|
|
1612
1395
|
preparationConfig,
|
|
1613
1396
|
}, initial);
|
|
1614
1397
|
return initial;
|
|
1615
1398
|
}
|
|
1616
|
-
async runCompileInBackground(request, context) {
|
|
1399
|
+
async runCompileInBackground(prepDataDir, request, context) {
|
|
1400
|
+
this.beginActiveRun();
|
|
1617
1401
|
try {
|
|
1618
1402
|
if (!this.handlers.createCompileRun) {
|
|
1619
1403
|
throw new Error("No compile-run handler is configured for this local service.");
|
|
1620
1404
|
}
|
|
1621
1405
|
const result = LocalRunHandlerResultSchema.parse(await this.handlers.createCompileRun(request, context));
|
|
1622
|
-
this.
|
|
1623
|
-
|
|
1406
|
+
const wasCancelled = this.activeCompileRuns.get(context.runId)?.cancelled === true;
|
|
1407
|
+
if (wasCancelled) {
|
|
1408
|
+
// The run was cancelled while the handler was still running. The
|
|
1409
|
+
// cancellation path already wrote a `cancelled` record; just refresh
|
|
1410
|
+
// observability and skip emitting a second terminal event.
|
|
1411
|
+
this.refreshCompileRunFromRuntime(prepDataDir, context.compiledPath, context.runId);
|
|
1412
|
+
await this.emitRuntimeDerivedEvents(prepDataDir, context.compiledPath, context.runId);
|
|
1413
|
+
await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(prepDataDir, context.preparationConfig)));
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
this.refreshCompileRunFromRuntime(prepDataDir, context.compiledPath, context.runId);
|
|
1417
|
+
await this.emitRuntimeDerivedEvents(prepDataDir, context.compiledPath, context.runId);
|
|
1624
1418
|
if (!result.ok) {
|
|
1625
|
-
await this.recordCompileRunEvent(context.compiledPath, context.runId, {
|
|
1419
|
+
await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, {
|
|
1626
1420
|
type: "run.failed",
|
|
1627
1421
|
event_id: createRunEventId("event"),
|
|
1628
1422
|
run_id: context.runId,
|
|
@@ -1631,7 +1425,7 @@ export class LocalServiceRuntime {
|
|
|
1631
1425
|
});
|
|
1632
1426
|
}
|
|
1633
1427
|
else {
|
|
1634
|
-
await this.recordCompileRunEvent(context.compiledPath, context.runId, {
|
|
1428
|
+
await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, {
|
|
1635
1429
|
type: "run.completed",
|
|
1636
1430
|
event_id: createRunEventId("event"),
|
|
1637
1431
|
run_id: context.runId,
|
|
@@ -1639,26 +1433,31 @@ export class LocalServiceRuntime {
|
|
|
1639
1433
|
summary: "Portable context ready.",
|
|
1640
1434
|
});
|
|
1641
1435
|
}
|
|
1642
|
-
await this.recordCompileRunEvent(context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(context.preparationConfig)));
|
|
1436
|
+
await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(prepDataDir, context.preparationConfig)));
|
|
1643
1437
|
}
|
|
1644
1438
|
catch (error) {
|
|
1645
|
-
await this.recordCompileRunEvent(context.compiledPath, context.runId, {
|
|
1439
|
+
await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, {
|
|
1646
1440
|
type: "run.failed",
|
|
1647
1441
|
event_id: createRunEventId("event"),
|
|
1648
1442
|
run_id: context.runId,
|
|
1649
1443
|
timestamp: createRunEventTimestamp(),
|
|
1650
1444
|
error: error instanceof Error ? error.message : String(error),
|
|
1651
1445
|
});
|
|
1652
|
-
await this.recordCompileRunEvent(context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(context.preparationConfig)));
|
|
1446
|
+
await this.recordCompileRunEvent(prepDataDir, context.compiledPath, context.runId, this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, this.computePreparationReadiness(prepDataDir, context.preparationConfig)));
|
|
1447
|
+
}
|
|
1448
|
+
finally {
|
|
1449
|
+
this.activeCompileRuns.delete(context.runId);
|
|
1450
|
+
this.endActiveRun();
|
|
1653
1451
|
}
|
|
1654
1452
|
}
|
|
1655
|
-
async runTestInBackground(request, context, initial) {
|
|
1453
|
+
async runTestInBackground(prepDataDir, request, context, initial) {
|
|
1454
|
+
this.beginActiveRun();
|
|
1656
1455
|
try {
|
|
1657
1456
|
if (!this.handlers.createTestRun) {
|
|
1658
1457
|
throw new Error("No test-run handler is configured for this local service.");
|
|
1659
1458
|
}
|
|
1660
1459
|
const result = LocalRunHandlerResultSchema.parse(await this.handlers.createTestRun(request, context));
|
|
1661
|
-
const readinessRun = result.readiness_run ?? this.readLatestReadinessRun(context.preparationConfig.name);
|
|
1460
|
+
const readinessRun = result.readiness_run ?? this.readLatestReadinessRun(prepDataDir, context.preparationConfig.name);
|
|
1662
1461
|
const resultEvent = readinessRun
|
|
1663
1462
|
? this.checksEvaluatedEvent(context.runId, readinessRun)
|
|
1664
1463
|
: null;
|
|
@@ -1670,8 +1469,8 @@ export class LocalServiceRuntime {
|
|
|
1670
1469
|
events: resultEvent ? [resultEvent] : [],
|
|
1671
1470
|
...(!result.ok ? { error: result.error ?? "Readiness check failed." } : {}),
|
|
1672
1471
|
});
|
|
1673
|
-
this.writeTestRun(context.compiledPath, nextWithoutReadiness);
|
|
1674
|
-
const readiness = this.computePreparationReadiness(context.preparationConfig);
|
|
1472
|
+
this.writeTestRun(prepDataDir, context.compiledPath, nextWithoutReadiness);
|
|
1473
|
+
const readiness = this.computePreparationReadiness(prepDataDir, context.preparationConfig);
|
|
1675
1474
|
const next = TestRunResourceSchema.parse({
|
|
1676
1475
|
...nextWithoutReadiness,
|
|
1677
1476
|
readiness,
|
|
@@ -1680,7 +1479,7 @@ export class LocalServiceRuntime {
|
|
|
1680
1479
|
this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, readiness),
|
|
1681
1480
|
],
|
|
1682
1481
|
});
|
|
1683
|
-
this.writeTestRun(context.compiledPath, next);
|
|
1482
|
+
this.writeTestRun(prepDataDir, context.compiledPath, next);
|
|
1684
1483
|
}
|
|
1685
1484
|
catch (error) {
|
|
1686
1485
|
const failedWithoutReadiness = TestRunResourceSchema.parse({
|
|
@@ -1689,23 +1488,32 @@ export class LocalServiceRuntime {
|
|
|
1689
1488
|
finished_at: new Date().toISOString(),
|
|
1690
1489
|
error: error instanceof Error ? error.message : String(error),
|
|
1691
1490
|
});
|
|
1692
|
-
this.writeTestRun(context.compiledPath, failedWithoutReadiness);
|
|
1693
|
-
const readiness = this.computePreparationReadiness(context.preparationConfig);
|
|
1491
|
+
this.writeTestRun(prepDataDir, context.compiledPath, failedWithoutReadiness);
|
|
1492
|
+
const readiness = this.computePreparationReadiness(prepDataDir, context.preparationConfig);
|
|
1694
1493
|
const next = TestRunResourceSchema.parse({
|
|
1695
1494
|
...failedWithoutReadiness,
|
|
1696
1495
|
readiness,
|
|
1697
1496
|
events: [this.readinessUpdatedEvent(context.runId, context.preparationConfig.name, readiness)],
|
|
1698
1497
|
});
|
|
1699
|
-
this.writeTestRun(context.compiledPath, next);
|
|
1498
|
+
this.writeTestRun(prepDataDir, context.compiledPath, next);
|
|
1499
|
+
}
|
|
1500
|
+
finally {
|
|
1501
|
+
this.endActiveRun();
|
|
1700
1502
|
}
|
|
1701
1503
|
}
|
|
1702
|
-
async runReadinessCheckDraftInBackground(request, runId) {
|
|
1504
|
+
async runReadinessCheckDraftInBackground(prepDataDir, request, runId) {
|
|
1505
|
+
this.beginActiveRun();
|
|
1506
|
+
return this.runReadinessCheckDraftInBackgroundInner(prepDataDir, request, runId).finally(() => {
|
|
1507
|
+
this.endActiveRun();
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
async runReadinessCheckDraftInBackgroundInner(prepDataDir, request, runId) {
|
|
1703
1511
|
try {
|
|
1704
1512
|
if (!this.handlers.createReadinessCheckDraft) {
|
|
1705
1513
|
throw new Error("No readiness-check-draft handler is configured for this local service.");
|
|
1706
1514
|
}
|
|
1707
|
-
const result = ReadinessCheckDraftResultSchema.parse(await this.handlers.createReadinessCheckDraft(request, this.jobRunContext(runId)));
|
|
1708
|
-
this.appendJobRunEvent(runId, {
|
|
1515
|
+
const result = ReadinessCheckDraftResultSchema.parse(await this.handlers.createReadinessCheckDraft(request, this.jobRunContext(prepDataDir, runId)));
|
|
1516
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1709
1517
|
type: "step.completed",
|
|
1710
1518
|
step_id: "agent-draft",
|
|
1711
1519
|
message: `Drafted ${result.checks.length} readiness checks.`,
|
|
@@ -1713,7 +1521,7 @@ export class LocalServiceRuntime {
|
|
|
1713
1521
|
checks: result.checks,
|
|
1714
1522
|
},
|
|
1715
1523
|
});
|
|
1716
|
-
this.appendJobRunEvent(runId, {
|
|
1524
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1717
1525
|
type: "step.started",
|
|
1718
1526
|
step_id: "normalize-checks",
|
|
1719
1527
|
message: "Normalizing drafted readiness checks into saved check records.",
|
|
@@ -1721,7 +1529,7 @@ export class LocalServiceRuntime {
|
|
|
1721
1529
|
checks: result.checks.length,
|
|
1722
1530
|
},
|
|
1723
1531
|
});
|
|
1724
|
-
this.appendJobRunEvent(runId, {
|
|
1532
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1725
1533
|
type: "step.completed",
|
|
1726
1534
|
step_id: "normalize-checks",
|
|
1727
1535
|
message: `${result.checks.length} readiness checks ready for review.`,
|
|
@@ -1729,15 +1537,15 @@ export class LocalServiceRuntime {
|
|
|
1729
1537
|
checks: result.checks.length,
|
|
1730
1538
|
},
|
|
1731
1539
|
});
|
|
1732
|
-
this.setJobRunResult(runId, result);
|
|
1733
|
-
this.appendJobRunEvent(runId, {
|
|
1540
|
+
this.setJobRunResult(prepDataDir, runId, result);
|
|
1541
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1734
1542
|
type: "job.completed",
|
|
1735
1543
|
message: `Drafted ${result.checks.length} readiness checks.`,
|
|
1736
1544
|
});
|
|
1737
1545
|
}
|
|
1738
1546
|
catch (error) {
|
|
1739
1547
|
const message = error instanceof Error ? error.message : String(error);
|
|
1740
|
-
this.appendJobRunEvent(runId, {
|
|
1548
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1741
1549
|
type: "step.failed",
|
|
1742
1550
|
step_id: "agent-draft",
|
|
1743
1551
|
message,
|
|
@@ -1745,20 +1553,26 @@ export class LocalServiceRuntime {
|
|
|
1745
1553
|
error: message,
|
|
1746
1554
|
},
|
|
1747
1555
|
});
|
|
1748
|
-
this.appendJobRunEvent(runId, {
|
|
1556
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1749
1557
|
type: "job.failed",
|
|
1750
1558
|
message,
|
|
1751
1559
|
});
|
|
1752
1560
|
}
|
|
1753
1561
|
}
|
|
1754
|
-
async runMethodAuthoringInBackground(request, runId) {
|
|
1562
|
+
async runMethodAuthoringInBackground(prepDataDir, request, runId) {
|
|
1563
|
+
this.beginActiveRun();
|
|
1564
|
+
return this.runMethodAuthoringInBackgroundInner(prepDataDir, request, runId).finally(() => {
|
|
1565
|
+
this.endActiveRun();
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
async runMethodAuthoringInBackgroundInner(prepDataDir, request, runId) {
|
|
1755
1569
|
try {
|
|
1756
1570
|
if (!this.handlers.createMethodAuthoringRun) {
|
|
1757
1571
|
throw new Error("No Method-authoring handler is configured for this local service.");
|
|
1758
1572
|
}
|
|
1759
|
-
const result = MethodAuthoringResultSchema.parse(await this.handlers.createMethodAuthoringRun(request, this.jobRunContext(runId)));
|
|
1760
|
-
this.setJobRunResult(runId, result);
|
|
1761
|
-
this.appendJobRunEvent(runId, {
|
|
1573
|
+
const result = MethodAuthoringResultSchema.parse(await this.handlers.createMethodAuthoringRun(request, this.jobRunContext(prepDataDir, runId)));
|
|
1574
|
+
this.setJobRunResult(prepDataDir, runId, result);
|
|
1575
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1762
1576
|
type: result.status === "executor-failed" ? "step.failed" : "step.completed",
|
|
1763
1577
|
step_id: "draft-package",
|
|
1764
1578
|
message: result.summary,
|
|
@@ -1769,7 +1583,7 @@ export class LocalServiceRuntime {
|
|
|
1769
1583
|
shell_path: result.shell_path,
|
|
1770
1584
|
},
|
|
1771
1585
|
});
|
|
1772
|
-
this.appendJobRunEvent(runId, {
|
|
1586
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1773
1587
|
type: "step.started",
|
|
1774
1588
|
step_id: "validate-package",
|
|
1775
1589
|
message: "Validating Method package structure and stage contract.",
|
|
@@ -1778,7 +1592,7 @@ export class LocalServiceRuntime {
|
|
|
1778
1592
|
},
|
|
1779
1593
|
});
|
|
1780
1594
|
if (result.status === "updated" || result.status === "no-change") {
|
|
1781
|
-
this.appendJobRunEvent(runId, {
|
|
1595
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1782
1596
|
type: "step.completed",
|
|
1783
1597
|
step_id: "validate-package",
|
|
1784
1598
|
message: result.summary,
|
|
@@ -1787,13 +1601,13 @@ export class LocalServiceRuntime {
|
|
|
1787
1601
|
validation: result.validation ?? null,
|
|
1788
1602
|
},
|
|
1789
1603
|
});
|
|
1790
|
-
this.appendJobRunEvent(runId, {
|
|
1604
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1791
1605
|
type: "job.completed",
|
|
1792
1606
|
message: result.summary,
|
|
1793
1607
|
});
|
|
1794
1608
|
}
|
|
1795
1609
|
else {
|
|
1796
|
-
this.appendJobRunEvent(runId, {
|
|
1610
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1797
1611
|
type: "step.failed",
|
|
1798
1612
|
step_id: "validate-package",
|
|
1799
1613
|
message: result.summary,
|
|
@@ -1802,7 +1616,7 @@ export class LocalServiceRuntime {
|
|
|
1802
1616
|
validation: result.validation ?? null,
|
|
1803
1617
|
},
|
|
1804
1618
|
});
|
|
1805
|
-
this.appendJobRunEvent(runId, {
|
|
1619
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1806
1620
|
type: "job.failed",
|
|
1807
1621
|
message: result.summary,
|
|
1808
1622
|
});
|
|
@@ -1810,7 +1624,7 @@ export class LocalServiceRuntime {
|
|
|
1810
1624
|
}
|
|
1811
1625
|
catch (error) {
|
|
1812
1626
|
const message = error instanceof Error ? error.message : String(error);
|
|
1813
|
-
this.appendJobRunEvent(runId, {
|
|
1627
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1814
1628
|
type: "step.failed",
|
|
1815
1629
|
step_id: "draft-package",
|
|
1816
1630
|
message,
|
|
@@ -1818,29 +1632,29 @@ export class LocalServiceRuntime {
|
|
|
1818
1632
|
error: message,
|
|
1819
1633
|
},
|
|
1820
1634
|
});
|
|
1821
|
-
this.appendJobRunEvent(runId, {
|
|
1635
|
+
this.appendJobRunEvent(prepDataDir, runId, {
|
|
1822
1636
|
type: "job.failed",
|
|
1823
1637
|
message,
|
|
1824
1638
|
});
|
|
1825
1639
|
}
|
|
1826
1640
|
}
|
|
1827
|
-
jobRunContext(runId) {
|
|
1641
|
+
jobRunContext(prepDataDir, runId) {
|
|
1828
1642
|
return {
|
|
1829
1643
|
runId,
|
|
1830
|
-
sourcePath:
|
|
1644
|
+
sourcePath: prepDataDir,
|
|
1831
1645
|
emit: (event) => {
|
|
1832
|
-
this.appendJobRunEvent(runId, event);
|
|
1646
|
+
this.appendJobRunEvent(prepDataDir, runId, event);
|
|
1833
1647
|
},
|
|
1834
1648
|
};
|
|
1835
1649
|
}
|
|
1836
|
-
defaultPreparationName() {
|
|
1837
|
-
const preparation = listSourcePreparationConfigs(loadSourceFolderConfig(
|
|
1650
|
+
defaultPreparationName(prepDataDir) {
|
|
1651
|
+
const preparation = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))[0];
|
|
1838
1652
|
if (!preparation) {
|
|
1839
1653
|
throw new Error("No Preparation is saved in this control plane folder.");
|
|
1840
1654
|
}
|
|
1841
1655
|
return preparation.name;
|
|
1842
1656
|
}
|
|
1843
|
-
async planActionProposal(request) {
|
|
1657
|
+
async planActionProposal(prepDataDir, request) {
|
|
1844
1658
|
if (!this.handlers.planActionProposal) {
|
|
1845
1659
|
return ActionProposalPlanSchema.parse({
|
|
1846
1660
|
action_type: "clarification",
|
|
@@ -1848,15 +1662,15 @@ export class LocalServiceRuntime {
|
|
|
1848
1662
|
assistant_message: "No local action planner is configured for this Interf Workspace.",
|
|
1849
1663
|
});
|
|
1850
1664
|
}
|
|
1851
|
-
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(
|
|
1665
|
+
const preparations = listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir));
|
|
1852
1666
|
let rawPlan;
|
|
1853
1667
|
try {
|
|
1854
1668
|
rawPlan = await this.handlers.planActionProposal(request, {
|
|
1855
|
-
sourcePath:
|
|
1669
|
+
sourcePath: prepDataDir,
|
|
1856
1670
|
preparations,
|
|
1857
1671
|
preparationHealth: preparations.map((preparation) => {
|
|
1858
1672
|
const readinessChecks = preparation.checks?.length ?? 0;
|
|
1859
|
-
const portableContextReady = hasCompiledTestTarget(
|
|
1673
|
+
const portableContextReady = hasCompiledTestTarget(prepDataDir, preparation);
|
|
1860
1674
|
return {
|
|
1861
1675
|
name: preparation.name,
|
|
1862
1676
|
readiness_checks: readinessChecks,
|
|
@@ -1869,8 +1683,8 @@ export class LocalServiceRuntime {
|
|
|
1869
1683
|
: ["prepare"],
|
|
1870
1684
|
};
|
|
1871
1685
|
}),
|
|
1872
|
-
sourceFolders: listSourceFolderChoices(
|
|
1873
|
-
recentProposals: this.listActionProposals().slice(0, 5),
|
|
1686
|
+
sourceFolders: listSourceFolderChoices(prepDataDir),
|
|
1687
|
+
recentProposals: this.listActionProposals(prepDataDir).slice(0, 5),
|
|
1874
1688
|
});
|
|
1875
1689
|
}
|
|
1876
1690
|
catch {
|
|
@@ -1927,7 +1741,7 @@ export class LocalServiceRuntime {
|
|
|
1927
1741
|
error: null,
|
|
1928
1742
|
});
|
|
1929
1743
|
}
|
|
1930
|
-
async buildActionProposal(request) {
|
|
1744
|
+
async buildActionProposal(prepDataDir, request) {
|
|
1931
1745
|
const structuredPreparationSetup = PreparationSetupActionValuesSchema.safeParse(request.values);
|
|
1932
1746
|
const structuredMethodAuthoring = MethodAuthoringActionValuesSchema.safeParse(request.values);
|
|
1933
1747
|
const structuredActionType = actionTypeFromValues(request.values);
|
|
@@ -1952,7 +1766,7 @@ export class LocalServiceRuntime {
|
|
|
1952
1766
|
action_type: structuredPlanActionType,
|
|
1953
1767
|
...(request.preparation ? { preparation: request.preparation } : {}),
|
|
1954
1768
|
})
|
|
1955
|
-
: await this.planActionProposal(request);
|
|
1769
|
+
: await this.planActionProposal(prepDataDir, request);
|
|
1956
1770
|
const actionType = structuredPlanActionType ?? plan.action_type;
|
|
1957
1771
|
if (directServiceEndpointForAction(actionType)) {
|
|
1958
1772
|
return this.directServiceActionClarification({
|
|
@@ -2003,13 +1817,13 @@ export class LocalServiceRuntime {
|
|
|
2003
1817
|
const requestedPreparationName = plan.preparation ?? request.preparation ?? null;
|
|
2004
1818
|
const fallbackPreparation = requestedPreparationName
|
|
2005
1819
|
? null
|
|
2006
|
-
: listSourcePreparationConfigs(loadSourceFolderConfig(
|
|
1820
|
+
: listSourcePreparationConfigs(loadSourceFolderConfig(prepDataDir))[0] ?? null;
|
|
2007
1821
|
const preparationConfig = requestedPreparationName
|
|
2008
|
-
? this.resolvePreparationConfig(requestedPreparationName)
|
|
1822
|
+
? this.resolvePreparationConfig(prepDataDir, requestedPreparationName)
|
|
2009
1823
|
: fallbackPreparation;
|
|
2010
1824
|
const preparationPath = preparationConfig
|
|
2011
|
-
? resolveSourcePreparationPath(
|
|
2012
|
-
: resolveConfiguredSourceFolderPath(
|
|
1825
|
+
? resolveSourcePreparationPath(prepDataDir, preparationConfig)
|
|
1826
|
+
: resolveConfiguredSourceFolderPath(prepDataDir) ?? prepDataDir;
|
|
2013
1827
|
const requestedMethodId = stringValue(request.values, "method_id") ??
|
|
2014
1828
|
stringValue(request.values, "method");
|
|
2015
1829
|
const plannedMethodId = plan.method ??
|
|
@@ -2057,9 +1871,9 @@ export class LocalServiceRuntime {
|
|
|
2057
1871
|
error: null,
|
|
2058
1872
|
});
|
|
2059
1873
|
}
|
|
2060
|
-
const preparationConfig = this.resolvePreparationConfig(plan.preparation ?? request.preparation ?? this.defaultPreparationName());
|
|
1874
|
+
const preparationConfig = this.resolvePreparationConfig(prepDataDir, plan.preparation ?? request.preparation ?? this.defaultPreparationName(prepDataDir));
|
|
2061
1875
|
const proposalActionType = ActionProposalTypeSchema.parse(actionType);
|
|
2062
|
-
const preparationPath = resolveSourcePreparationPath(
|
|
1876
|
+
const preparationPath = resolveSourcePreparationPath(prepDataDir, preparationConfig);
|
|
2063
1877
|
const requestedMethodId = stringValue(request.values, "method_id") ??
|
|
2064
1878
|
stringValue(request.values, "method");
|
|
2065
1879
|
const plannedMethodId = plan.method ??
|
|
@@ -2093,7 +1907,7 @@ export class LocalServiceRuntime {
|
|
|
2093
1907
|
if (actionType === "test") {
|
|
2094
1908
|
const requestedMode = testModeFromValues(proposalValues);
|
|
2095
1909
|
const hasReadinessChecks = (preparationConfig.checks ?? []).length > 0;
|
|
2096
|
-
const portableContextReady = hasCompiledTestTarget(
|
|
1910
|
+
const portableContextReady = hasCompiledTestTarget(prepDataDir, preparationConfig);
|
|
2097
1911
|
if (!hasReadinessChecks) {
|
|
2098
1912
|
return clarifyResolvedAction({
|
|
2099
1913
|
title: `Add readiness checks for ${preparationConfig.name}`,
|
|
@@ -2101,7 +1915,7 @@ export class LocalServiceRuntime {
|
|
|
2101
1915
|
assistantMessage: `Preparation "${preparationConfig.name}" does not have saved readiness checks yet. Ask me to draft readiness checks after the Source Folder is prepared, or add readiness guidance first.`,
|
|
2102
1916
|
});
|
|
2103
1917
|
}
|
|
2104
|
-
if (!portableContextReady && requestedMode !== "
|
|
1918
|
+
if (!portableContextReady && requestedMode !== "source-files") {
|
|
2105
1919
|
return clarifyResolvedAction({
|
|
2106
1920
|
title: `Prepare ${preparationConfig.name} first`,
|
|
2107
1921
|
summary: "Readiness checks need portable context unless you explicitly ask for a source-files-only baseline.",
|
|
@@ -2117,7 +1931,7 @@ export class LocalServiceRuntime {
|
|
|
2117
1931
|
};
|
|
2118
1932
|
}
|
|
2119
1933
|
if (actionType === "test") {
|
|
2120
|
-
const defaultMode = hasCompiledTestTarget(
|
|
1934
|
+
const defaultMode = hasCompiledTestTarget(prepDataDir, preparationConfig) ? "both" : "source-files";
|
|
2121
1935
|
return {
|
|
2122
1936
|
preparation: preparationConfig.name,
|
|
2123
1937
|
mode: testModeValue(proposalValues, defaultMode),
|
|
@@ -2197,26 +2011,26 @@ export class LocalServiceRuntime {
|
|
|
2197
2011
|
error: null,
|
|
2198
2012
|
});
|
|
2199
2013
|
}
|
|
2200
|
-
async submitActionProposal(proposal) {
|
|
2014
|
+
async submitActionProposal(prepDataDir, proposal) {
|
|
2201
2015
|
if (proposal.action_type === "clarification") {
|
|
2202
2016
|
throw new Error("Clarification proposals cannot be submitted.");
|
|
2203
2017
|
}
|
|
2204
2018
|
if (proposal.action_type === "compile") {
|
|
2205
|
-
const resource = await this.createCompileRun(proposal.request);
|
|
2019
|
+
const resource = await this.createCompileRun(prepDataDir, proposal.request);
|
|
2206
2020
|
return {
|
|
2207
2021
|
runId: resource.run.run_id,
|
|
2208
2022
|
runType: "compile-run",
|
|
2209
2023
|
};
|
|
2210
2024
|
}
|
|
2211
2025
|
if (proposal.action_type === "test") {
|
|
2212
|
-
const resource = await this.createTestRun(proposal.request);
|
|
2026
|
+
const resource = await this.createTestRun(prepDataDir, proposal.request);
|
|
2213
2027
|
return {
|
|
2214
2028
|
runId: resource.run_id,
|
|
2215
2029
|
runType: "test-run",
|
|
2216
2030
|
};
|
|
2217
2031
|
}
|
|
2218
2032
|
if (proposal.action_type === "readiness-check-draft") {
|
|
2219
|
-
const job = await this.createReadinessCheckDraftRun(proposal.request);
|
|
2033
|
+
const job = await this.createReadinessCheckDraftRun(prepDataDir, proposal.request);
|
|
2220
2034
|
return {
|
|
2221
2035
|
runId: job.run_id,
|
|
2222
2036
|
runType: "job-run",
|
|
@@ -2226,14 +2040,14 @@ export class LocalServiceRuntime {
|
|
|
2226
2040
|
if (directEndpoint) {
|
|
2227
2041
|
throw new Error(`Action "${proposal.action_type}" must be submitted directly to ${directEndpoint}.`);
|
|
2228
2042
|
}
|
|
2229
|
-
const job = await this.createMethodAuthoringRun(proposal.request, proposal.action_type === "method-improvement" ? "method-improvement" : "method-authoring");
|
|
2043
|
+
const job = await this.createMethodAuthoringRun(prepDataDir, proposal.request, proposal.action_type === "method-improvement" ? "method-improvement" : "method-authoring");
|
|
2230
2044
|
return {
|
|
2231
2045
|
runId: job.run_id,
|
|
2232
2046
|
runType: "job-run",
|
|
2233
2047
|
};
|
|
2234
2048
|
}
|
|
2235
|
-
resolvePreparationConfig(preparationName, overrides = {}) {
|
|
2236
|
-
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(
|
|
2049
|
+
resolvePreparationConfig(prepDataDir, preparationName, overrides = {}) {
|
|
2050
|
+
const preparation = findSourcePreparationConfig(loadSourceFolderConfig(prepDataDir), preparationName);
|
|
2237
2051
|
if (!preparation) {
|
|
2238
2052
|
throw new Error(`Preparation "${preparationName}" is not saved in this control plane folder.`);
|
|
2239
2053
|
}
|
|
@@ -2245,49 +2059,59 @@ export class LocalServiceRuntime {
|
|
|
2245
2059
|
...(typeof overrides.max_loops === "number" ? { max_loops: overrides.max_loops } : {}),
|
|
2246
2060
|
};
|
|
2247
2061
|
}
|
|
2248
|
-
ensureCompiledForRun(preparationConfig) {
|
|
2062
|
+
ensureCompiledForRun(prepDataDir, preparationConfig) {
|
|
2249
2063
|
const methodId = requireSelectedMethod(preparationConfig);
|
|
2250
|
-
const compiledPath = ensurePortableContextScaffold(
|
|
2064
|
+
const compiledPath = ensurePortableContextScaffold(prepDataDir, preparationConfig.name, methodId);
|
|
2251
2065
|
syncCompiledInterfConfigFromSourcePreparationConfig(compiledPath, preparationConfig);
|
|
2252
2066
|
return compiledPath;
|
|
2253
2067
|
}
|
|
2254
2068
|
readCompileRun(compiledPath, runId) {
|
|
2255
2069
|
return readCompileRunAt(compileRunPath(compiledPath, runId));
|
|
2256
2070
|
}
|
|
2257
|
-
writeCompileRun(compiledPath, run) {
|
|
2071
|
+
writeCompileRun(prepDataDir, compiledPath, run) {
|
|
2258
2072
|
mkdirSync(compileRunsRoot(compiledPath), { recursive: true });
|
|
2259
2073
|
writeJsonFile(compileRunPath(compiledPath, run.run_id), CompileRunSchema.parse(run));
|
|
2074
|
+
// Bust per-preparation list + readiness caches so the next read
|
|
2075
|
+
// reflects the write. We invalidate broadly (per-preparation, not
|
|
2076
|
+
// per-record) because list recompute cost is bounded by run count
|
|
2077
|
+
// and the simpler model avoids fan-out bugs.
|
|
2078
|
+
this.compileRunCache.invalidatePreparation(prepDataDir, run.preparation);
|
|
2079
|
+
this.readinessCache.invalidatePreparation(prepDataDir, run.preparation);
|
|
2080
|
+
}
|
|
2081
|
+
writeJobRun(prepDataDir, run) {
|
|
2082
|
+
mkdirSync(localJobsRoot(prepDataDir), { recursive: true });
|
|
2083
|
+
writeJsonFile(localJobPath(prepDataDir, run.run_id), LocalJobRunResourceSchema.parse(run));
|
|
2084
|
+
if (run.preparation) {
|
|
2085
|
+
// Some job runs (readiness-check drafts) flip readiness state.
|
|
2086
|
+
this.readinessCache.invalidatePreparation(prepDataDir, run.preparation);
|
|
2087
|
+
}
|
|
2260
2088
|
}
|
|
2261
|
-
|
|
2262
|
-
mkdirSync(
|
|
2263
|
-
writeJsonFile(
|
|
2264
|
-
}
|
|
2265
|
-
writeActionProposal(proposal) {
|
|
2266
|
-
mkdirSync(actionProposalsRoot(this.rootPath), { recursive: true });
|
|
2267
|
-
writeJsonFile(actionProposalPath(this.rootPath, proposal.proposal_id), ActionProposalResourceSchema.parse(proposal));
|
|
2089
|
+
writeActionProposal(prepDataDir, proposal) {
|
|
2090
|
+
mkdirSync(actionProposalsRoot(prepDataDir), { recursive: true });
|
|
2091
|
+
writeJsonFile(actionProposalPath(prepDataDir, proposal.proposal_id), ActionProposalResourceSchema.parse(proposal));
|
|
2268
2092
|
}
|
|
2269
|
-
setJobRunResult(runId, result) {
|
|
2270
|
-
const current = this.getJob(runId);
|
|
2093
|
+
setJobRunResult(prepDataDir, runId, result) {
|
|
2094
|
+
const current = this.getJob(prepDataDir, runId);
|
|
2271
2095
|
if (!current)
|
|
2272
2096
|
return;
|
|
2273
2097
|
const normalizedResult = result && typeof result === "object" && !Array.isArray(result)
|
|
2274
2098
|
? result
|
|
2275
2099
|
: { value: result };
|
|
2276
|
-
this.writeJobRun(LocalJobRunResourceSchema.parse({
|
|
2100
|
+
this.writeJobRun(prepDataDir, LocalJobRunResourceSchema.parse({
|
|
2277
2101
|
...current,
|
|
2278
2102
|
result: normalizedResult,
|
|
2279
2103
|
}));
|
|
2280
2104
|
}
|
|
2281
|
-
async recordCompileRunEvent(compiledPath, runId, event) {
|
|
2105
|
+
async recordCompileRunEvent(prepDataDir, compiledPath, runId, event) {
|
|
2282
2106
|
const current = this.readCompileRun(compiledPath, runId);
|
|
2283
2107
|
if (!current)
|
|
2284
2108
|
return;
|
|
2285
|
-
this.writeCompileRun(compiledPath, applyEventToCompileRun(current, event));
|
|
2109
|
+
this.writeCompileRun(prepDataDir, compiledPath, applyEventToCompileRun(current, event));
|
|
2286
2110
|
if (event.type === "stage.passed" || event.type === "stage.failed") {
|
|
2287
|
-
this.refreshCompileRunFromRuntime(compiledPath, runId);
|
|
2111
|
+
this.refreshCompileRunFromRuntime(prepDataDir, compiledPath, runId);
|
|
2288
2112
|
}
|
|
2289
2113
|
}
|
|
2290
|
-
refreshCompileRunFromRuntime(compiledPath, runId) {
|
|
2114
|
+
refreshCompileRunFromRuntime(prepDataDir, compiledPath, runId) {
|
|
2291
2115
|
const current = this.readCompileRun(compiledPath, runId);
|
|
2292
2116
|
if (!current)
|
|
2293
2117
|
return;
|
|
@@ -2334,9 +2158,9 @@ export class LocalServiceRuntime {
|
|
|
2334
2158
|
}),
|
|
2335
2159
|
};
|
|
2336
2160
|
next.latest_proof = [...next.stages].reverse().find((stage) => Boolean(stage.latest_proof))?.latest_proof ?? next.latest_proof;
|
|
2337
|
-
this.writeCompileRun(compiledPath, next);
|
|
2161
|
+
this.writeCompileRun(prepDataDir, compiledPath, next);
|
|
2338
2162
|
}
|
|
2339
|
-
async emitRuntimeDerivedEvents(compiledPath, runId) {
|
|
2163
|
+
async emitRuntimeDerivedEvents(prepDataDir, compiledPath, runId) {
|
|
2340
2164
|
const state = loadState(compiledPath);
|
|
2341
2165
|
const run = this.readCompileRun(compiledPath, runId);
|
|
2342
2166
|
if (!state?.stages || !run)
|
|
@@ -2347,7 +2171,7 @@ export class LocalServiceRuntime {
|
|
|
2347
2171
|
continue;
|
|
2348
2172
|
const artifacts = stageArtifactRefs(stage.stage_id, stageState.artifacts);
|
|
2349
2173
|
for (const artifact of artifacts) {
|
|
2350
|
-
await this.recordCompileRunEvent(compiledPath, runId, {
|
|
2174
|
+
await this.recordCompileRunEvent(prepDataDir, compiledPath, runId, {
|
|
2351
2175
|
type: "artifact.written",
|
|
2352
2176
|
event_id: createRunEventId("event"),
|
|
2353
2177
|
run_id: runId,
|
|
@@ -2356,7 +2180,7 @@ export class LocalServiceRuntime {
|
|
|
2356
2180
|
artifact,
|
|
2357
2181
|
});
|
|
2358
2182
|
}
|
|
2359
|
-
await this.recordCompileRunEvent(compiledPath, runId, {
|
|
2183
|
+
await this.recordCompileRunEvent(prepDataDir, compiledPath, runId, {
|
|
2360
2184
|
type: "proof.updated",
|
|
2361
2185
|
event_id: createRunEventId("event"),
|
|
2362
2186
|
run_id: runId,
|
|
@@ -2372,11 +2196,11 @@ export class LocalServiceRuntime {
|
|
|
2372
2196
|
});
|
|
2373
2197
|
}
|
|
2374
2198
|
}
|
|
2375
|
-
readLatestReadinessRun(preparationName) {
|
|
2376
|
-
return readSavedReadinessCheckRun(
|
|
2199
|
+
readLatestReadinessRun(prepDataDir, preparationName) {
|
|
2200
|
+
return readSavedReadinessCheckRun(prepDataDir, preparationName);
|
|
2377
2201
|
}
|
|
2378
2202
|
checksEvaluatedEvent(runId, readinessRun) {
|
|
2379
|
-
const target = readinessRun.compiled ?? readinessRun.
|
|
2203
|
+
const target = readinessRun.compiled ?? readinessRun.source_files;
|
|
2380
2204
|
return {
|
|
2381
2205
|
type: "checks.evaluated",
|
|
2382
2206
|
event_id: createRunEventId("event"),
|
|
@@ -2397,9 +2221,11 @@ export class LocalServiceRuntime {
|
|
|
2397
2221
|
readiness,
|
|
2398
2222
|
};
|
|
2399
2223
|
}
|
|
2400
|
-
writeTestRun(compiledPath, run) {
|
|
2224
|
+
writeTestRun(prepDataDir, compiledPath, run) {
|
|
2401
2225
|
mkdirSync(testRunsRoot(compiledPath), { recursive: true });
|
|
2402
2226
|
writeJsonFile(testRunPath(compiledPath, run.run_id), TestRunResourceSchema.parse(run));
|
|
2227
|
+
this.testRunCache.invalidatePreparation(prepDataDir, run.preparation);
|
|
2228
|
+
this.readinessCache.invalidatePreparation(prepDataDir, run.preparation);
|
|
2403
2229
|
}
|
|
2404
2230
|
}
|
|
2405
2231
|
export function createLocalServiceRuntime(options) {
|