@inflexa-ai/harness 0.46.2 → 0.48.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 +2 -2
- package/dist/agents/conversation-agent.d.ts +7 -6
- package/dist/agents/conversation-agent.js +26 -4
- package/dist/agents/report-session-agent.d.ts +14 -14
- package/dist/agents/report-session-agent.js +40 -26
- package/dist/agents/sandbox/shared.d.ts +18 -13
- package/dist/agents/sandbox/shared.js +42 -27
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/sandbox-catalog.d.ts +2 -0
- package/dist/agents/sandbox-catalog.js +30 -1
- package/dist/app/synthesize-run.d.ts +10 -6
- package/dist/app/synthesize-run.js +4 -2
- package/dist/contracts/analogy-report.d.ts +46 -2
- package/dist/contracts/analogy-report.js +31 -25
- package/dist/contracts/chat-parts.d.ts +3 -14
- package/dist/contracts/report-reference.d.ts +2 -1
- package/dist/contracts/report-reference.js +2 -1
- package/dist/contracts/schemas/chat-parts.d.ts +0 -27
- package/dist/contracts/schemas/chat-parts.js +1 -3
- package/dist/execution/run-canceler.d.ts +2 -0
- package/dist/execution/run-canceler.js +9 -0
- package/dist/execution/run-synthesis.d.ts +36 -8
- package/dist/execution/run-synthesis.js +114 -32
- package/dist/index.d.ts +5 -6
- package/dist/index.js +5 -7
- package/dist/input-scan/enrich.d.ts +5 -10
- package/dist/input-scan/enrich.js +12 -8
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/markdown-pdf.d.ts +27 -0
- package/dist/lib/markdown-pdf.js +236 -0
- package/dist/lib/metrics.d.ts +31 -0
- package/dist/lib/metrics.js +64 -0
- package/dist/lib/pdf-text.d.ts +23 -0
- package/dist/lib/pdf-text.js +34 -0
- package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
- package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
- package/dist/loop/chat-stream.js +2 -1
- package/dist/loop/run-loop.d.ts +5 -0
- package/dist/loop/run-loop.js +30 -2
- package/dist/loop/tool-name-repair.d.ts +11 -0
- package/dist/loop/tool-name-repair.js +23 -0
- package/dist/loop/types.d.ts +19 -0
- package/dist/memory/card-builders.d.ts +1 -1
- package/dist/prompts/SOUL.d.ts +6 -6
- package/dist/prompts/SOUL.js +22 -121
- package/dist/prompts/analogical-reasoner.d.ts +1 -1
- package/dist/prompts/analogical-reasoner.js +35 -101
- package/dist/prompts/briefing.d.ts +20 -11
- package/dist/prompts/briefing.js +36 -19
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +103 -383
- package/dist/prompts/planner.d.ts +1 -5
- package/dist/prompts/planner.js +45 -128
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +29 -58
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
- package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
- package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
- package/dist/prompts/sandbox/chromatin-agent.js +4 -5
- package/dist/prompts/sandbox/data-profiler.js +11 -34
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
- package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
- package/dist/prompts/sandbox/enrichment-agent.js +3 -1
- package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
- package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +3 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +3 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
- package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/proteomics-agent.js +3 -1
- package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
- package/dist/prompts/sandbox/scientific-executor.js +0 -12
- package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
- package/dist/prompts/sandbox/single-cell-agent.js +3 -1
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
- package/dist/prompts/sandbox-standards.d.ts +15 -18
- package/dist/prompts/sandbox-standards.js +96 -305
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +24 -90
- package/dist/provenance/collector.d.ts +2 -5
- package/dist/provenance/collector.js +2 -5
- package/dist/provenance/seam.d.ts +36 -1
- package/dist/provenance/workspace-reads.d.ts +36 -0
- package/dist/provenance/workspace-reads.js +33 -0
- package/dist/report-model/production-resolver.d.ts +22 -52
- package/dist/report-model/production-resolver.js +43 -112
- package/dist/report-render/chart-export.d.ts +9 -0
- package/dist/report-render/chart-export.js +1 -1
- package/dist/report-render/views/chart-view.d.ts +1 -0
- package/dist/report-render/views/chart-view.js +1 -0
- package/dist/report-render/views/lineage.d.ts +1 -0
- package/dist/report-render/views/page-view.d.ts +1 -0
- package/dist/report-render/views/page-view.js +1 -0
- package/dist/report-render/views/prose.d.ts +1 -0
- package/dist/report-render/views/prose.js +1 -0
- package/dist/report-render/views/references-view.d.ts +1 -0
- package/dist/report-render/views/values.d.ts +1 -0
- package/dist/report-render/views/values.js +1 -0
- package/dist/runtime/assemble.d.ts +32 -12
- package/dist/runtime/assemble.js +49 -38
- package/dist/sandbox/client.d.ts +5 -3
- package/dist/sandbox/create-sandbox.d.ts +7 -12
- package/dist/sandbox/create-sandbox.js +15 -37
- package/dist/sandbox/docker-client.d.ts +1 -3
- package/dist/sandbox/docker-client.js +3 -5
- package/dist/sandbox/exec-outcome.d.ts +11 -11
- package/dist/sandbox/exec-outcome.js +22 -22
- package/dist/sandbox/exec.d.ts +5 -0
- package/dist/sandbox/exec.js +9 -1
- package/dist/sandbox/identity.d.ts +4 -16
- package/dist/sandbox/identity.js +5 -21
- package/dist/sandbox/k8s-client.d.ts +0 -1
- package/dist/sandbox/k8s-client.js +8 -13
- package/dist/sandbox/lease-names.d.ts +6 -0
- package/dist/sandbox/lease-names.js +12 -0
- package/dist/sandbox/leases.d.ts +105 -0
- package/dist/sandbox/leases.js +519 -0
- package/dist/sandbox/liveness.d.ts +5 -2
- package/dist/sandbox/mount-plan.d.ts +10 -1
- package/dist/sandbox/mount-plan.js +7 -1
- package/dist/sandbox/reconciler.d.ts +58 -0
- package/dist/sandbox/reconciler.js +153 -0
- package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
- package/dist/sandbox/types.d.ts +25 -18
- package/dist/sandbox/types.js +10 -4
- package/dist/schemas/plan-schemas.d.ts +0 -21
- package/dist/schemas/plan-schemas.js +2 -3
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/run-synthesis.js +3 -3
- package/dist/schemas/validate-plan.d.ts +1 -14
- package/dist/schemas/validate-plan.js +5 -24
- package/dist/schemas/workflow-state.d.ts +3 -32
- package/dist/schemas/workflow-state.js +4 -13
- package/dist/state/index.d.ts +2 -3
- package/dist/state/index.js +1 -2
- package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
- package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
- package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
- package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
- package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
- package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
- package/dist/state/migrations/index.js +6 -0
- package/dist/state/purge-analysis.d.ts +4 -2
- package/dist/state/purge-analysis.js +22 -11
- package/dist/state/sandbox-leases.d.ts +81 -0
- package/dist/state/sandbox-leases.js +247 -0
- package/dist/state/schema.d.ts +1 -13
- package/dist/state/schema.js +1 -6
- package/dist/state/step-executions.d.ts +13 -2
- package/dist/state/step-executions.js +23 -4
- package/dist/tasks/data-profile.d.ts +40 -2
- package/dist/tasks/data-profile.js +89 -87
- package/dist/tools/ad-hoc-router.d.ts +1 -6
- package/dist/tools/ad-hoc-router.js +5 -48
- package/dist/tools/bio/comptox.js +1 -1
- package/dist/tools/bio/drug-gene-interactions.js +1 -1
- package/dist/tools/bio/gene-disease-evidence.js +1 -1
- package/dist/tools/bio/keys.d.ts +1 -1
- package/dist/tools/bio/pubmed.js +31 -0
- package/dist/tools/bio/search-gene.js +17 -0
- package/dist/tools/bio/search-geo-datasets.js +13 -0
- package/dist/tools/bio/search-regulatory-approvals.js +1 -1
- package/dist/tools/define-tool.d.ts +25 -2
- package/dist/tools/define-tool.js +11 -1
- package/dist/tools/display/show-user.js +1 -1
- package/dist/tools/execute-analysis.d.ts +1 -3
- package/dist/tools/execute-analysis.js +5 -33
- package/dist/tools/report-authoring/authoring-tools.d.ts +2 -2
- package/dist/tools/report-authoring/authoring-tools.js +31 -6
- package/dist/tools/report-session/index.d.ts +0 -1
- package/dist/tools/report-session/index.js +0 -1
- package/dist/tools/report-session/script-tools.d.ts +33 -0
- package/dist/tools/report-session/script-tools.js +186 -0
- package/dist/tools/research/generate-analogy-report.d.ts +16 -9
- package/dist/tools/research/generate-analogy-report.js +111 -49
- package/dist/tools/research/generate-plan.d.ts +102 -7
- package/dist/tools/research/generate-plan.js +74 -43
- package/dist/tools/research/index.d.ts +4 -3
- package/dist/tools/research/index.js +4 -3
- package/dist/tools/research/inspect-run.d.ts +0 -1
- package/dist/tools/research/inspect-run.js +5 -10
- package/dist/tools/research/search-arxiv.js +15 -0
- package/dist/tools/research/search-github-repos.js +13 -0
- package/dist/tools/research/search-semantic-scholar.js +16 -0
- package/dist/tools/research/synthesis-lookups.d.ts +21 -0
- package/dist/tools/research/synthesis-lookups.js +29 -0
- package/dist/tools/research/web-fetch.d.ts +1 -1
- package/dist/tools/research/web-fetch.js +14 -12
- package/dist/tools/sandbox/lease-tools.d.ts +32 -0
- package/dist/tools/sandbox/lease-tools.js +60 -0
- package/dist/tools/script/archive.d.ts +34 -0
- package/dist/tools/script/archive.js +182 -0
- package/dist/tools/script/codemode-worker.d.ts +5 -0
- package/dist/tools/script/codemode-worker.js +5 -0
- package/dist/tools/script/file-tools.d.ts +106 -0
- package/dist/tools/script/file-tools.js +491 -0
- package/dist/tools/script/metrics.d.ts +30 -0
- package/dist/tools/script/metrics.js +53 -0
- package/dist/tools/script/quickjs.wasm +0 -0
- package/dist/tools/script/run-script.d.ts +76 -0
- package/dist/tools/script/run-script.js +413 -0
- package/dist/tools/script/scratch.d.ts +84 -0
- package/dist/tools/script/scratch.js +145 -0
- package/dist/tools/start-report-session.js +1 -1
- package/dist/tools/workspace/edit-file.js +2 -1
- package/dist/tools/workspace/execute-command.d.ts +9 -13
- package/dist/tools/workspace/execute-command.js +23 -19
- package/dist/tools/workspace/file-stat.js +1 -1
- package/dist/tools/workspace/grep.d.ts +5 -2
- package/dist/tools/workspace/grep.js +19 -10
- package/dist/tools/workspace/mutator.d.ts +51 -4
- package/dist/tools/workspace/mutator.js +54 -13
- package/dist/tools/workspace/read-file.d.ts +5 -2
- package/dist/tools/workspace/read-file.js +15 -11
- package/dist/tools/workspace/read-surface.d.ts +17 -0
- package/dist/tools/workspace/read-surface.js +17 -0
- package/dist/tools/workspace/result-bounds.d.ts +1 -0
- package/dist/tools/workspace/run-exec.d.ts +12 -13
- package/dist/tools/workspace/run-exec.js +13 -13
- package/dist/tools/workspace/scan-inputs.d.ts +2 -5
- package/dist/tools/workspace/scan-inputs.js +2 -3
- package/dist/tools/workspace/show-file.js +1 -1
- package/dist/tools/workspace/show-plan.js +4 -3
- package/dist/tools/workspace/write-file.js +2 -1
- package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
- package/dist/workflows/execute-analysis-scheduler.js +12 -32
- package/dist/workflows/execute-analysis.d.ts +17 -35
- package/dist/workflows/execute-analysis.js +13 -107
- package/dist/workflows/pause.js +29 -22
- package/dist/workflows/sandbox-step.d.ts +16 -31
- package/dist/workflows/sandbox-step.js +36 -71
- package/dist/workflows/test/sql-writes.d.ts +0 -1
- package/dist/workflows/test/sql-writes.js +0 -2
- package/dist/workspace/filesystem.d.ts +2 -2
- package/dist/workspace/filesystem.js +10 -5
- package/dist/workspace/paths.d.ts +19 -1
- package/dist/workspace/paths.js +22 -1
- package/dist/workspace/session-files.d.ts +35 -0
- package/dist/workspace/session-files.js +16 -0
- package/package.json +7 -3
- package/dist/prompts/literature-reviewer.d.ts +0 -1
- package/dist/prompts/literature-reviewer.js +0 -114
- package/dist/sandbox/reaper.d.ts +0 -81
- package/dist/sandbox/reaper.js +0 -161
- package/dist/sandbox/step-sandbox.d.ts +0 -47
- package/dist/sandbox/step-sandbox.js +0 -53
- package/dist/state/active-sandboxes.d.ts +0 -33
- package/dist/state/active-sandboxes.js +0 -65
- package/dist/tasks/extract-values-script.d.ts +0 -64
- package/dist/tasks/extract-values-script.js +0 -180
- package/dist/tasks/extract-values.d.ts +0 -77
- package/dist/tasks/extract-values.js +0 -146
- package/dist/tools/report-session/derive-table.d.ts +0 -164
- package/dist/tools/report-session/derive-table.js +0 -419
- package/dist/tools/research/literature-reviewer.d.ts +0 -42
- package/dist/tools/research/literature-reviewer.js +0 -107
- package/dist/workflows/metrics.d.ts +0 -23
- package/dist/workflows/metrics.js +0 -38
package/dist/runtime/assemble.js
CHANGED
|
@@ -28,9 +28,11 @@ import { workPause } from "../workflows/pause.js";
|
|
|
28
28
|
import { hasBrowserUrl } from "../lib/chrome.js";
|
|
29
29
|
import { createStaticEyes } from "../lib/eyes.js";
|
|
30
30
|
import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
|
|
31
|
+
import { createSandboxLeases } from "../sandbox/leases.js";
|
|
32
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
33
|
+
import { dbosClientFor } from "../execution/dbos-workflow-purger.js";
|
|
31
34
|
import { registerSandboxStep } from "../workflows/sandbox-step.js";
|
|
32
35
|
import { registerDataProfileWorkflow, stopDataProfile } from "../tasks/data-profile.js";
|
|
33
|
-
import { createExtractionArm, runExtraction } from "../tasks/extract-values.js";
|
|
34
36
|
import { createCitationResolver } from "../citations/resolve.js";
|
|
35
37
|
import { createThreadStore } from "../memory/thread-store.js";
|
|
36
38
|
import { createWorkingMemory } from "../memory/working-memory.js";
|
|
@@ -38,7 +40,7 @@ import { createArtifactReadStore, createProductionResolver } from "../report-mod
|
|
|
38
40
|
import { createReportVersionStore } from "../state/report-versions.js";
|
|
39
41
|
import { createToolOutputStore } from "../state/tool-outputs.js";
|
|
40
42
|
import { createAskGateway } from "../tools/approval/gateway.js";
|
|
41
|
-
import {
|
|
43
|
+
import { createSynthesisLookupTools } from "../tools/research/synthesis-lookups.js";
|
|
42
44
|
const _assertDomainError = true;
|
|
43
45
|
/**
|
|
44
46
|
* Wrap a type→agent registry as the resolution surface. Held apart from
|
|
@@ -83,7 +85,7 @@ export function resolveCompositionEyes(seam, chrome) {
|
|
|
83
85
|
return undefined;
|
|
84
86
|
}
|
|
85
87
|
export function assembleCoreRuntime(deps) {
|
|
86
|
-
const { conversation, workflows: wf
|
|
88
|
+
const { conversation, workflows: wf } = deps;
|
|
87
89
|
const usageRecorder = deps.usageRecorder ?? createNoopUsageRecorder();
|
|
88
90
|
const citationResolver = createCitationResolver({
|
|
89
91
|
...deps.citationResolverConfig,
|
|
@@ -102,8 +104,21 @@ export function assembleCoreRuntime(deps) {
|
|
|
102
104
|
logger: conversation.logger,
|
|
103
105
|
}).afterFailure;
|
|
104
106
|
const registerPausingSubAgent = (name, body) => registerSubAgent(name, body, subAgentPauses);
|
|
107
|
+
// One sandbox module for each runtime: one budget, one lease workflow, and one reconciler.
|
|
108
|
+
const leases = createSandboxLeases({
|
|
109
|
+
pool: conversation.pool,
|
|
110
|
+
client: deps.sandbox.client,
|
|
111
|
+
resourcePolicy: deps.resourcePolicy,
|
|
112
|
+
resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
|
|
113
|
+
runAuthorizer: conversation.runAuthorizer,
|
|
114
|
+
idlePeriodMs: deps.sandbox.idlePeriodMs,
|
|
115
|
+
pauseRetryDelaysMs,
|
|
116
|
+
logger: conversation.logger,
|
|
117
|
+
dbosClient: () => dbosClientFor(conversation.pool, conversation.logger ?? createNoopLogger()),
|
|
118
|
+
});
|
|
105
119
|
const sandboxStep = registerSandboxStep({
|
|
106
120
|
...wf.sandboxStep,
|
|
121
|
+
leases,
|
|
107
122
|
citationResolver,
|
|
108
123
|
usageRecorder,
|
|
109
124
|
toolOutputStore,
|
|
@@ -111,24 +126,18 @@ export function assembleCoreRuntime(deps) {
|
|
|
111
126
|
runAuthorizer: conversation.runAuthorizer,
|
|
112
127
|
});
|
|
113
128
|
const { bioKeys: synthesisBioKeys, ...executeAnalysisDeps } = wf.buildExecuteAnalysis(sandboxStep);
|
|
114
|
-
const
|
|
115
|
-
provider: executeAnalysisDeps.provider,
|
|
116
|
-
model: executeAnalysisDeps.synthesisModel,
|
|
117
|
-
bioKeys: synthesisBioKeys,
|
|
118
|
-
citationResolver,
|
|
119
|
-
usageRecorder,
|
|
120
|
-
toolOutputStore,
|
|
121
|
-
logger: executeAnalysisDeps.logger,
|
|
122
|
-
}, registerPausingSubAgent);
|
|
129
|
+
const synthesisLookups = createSynthesisLookupTools({ bioKeys: synthesisBioKeys, citationResolver, logger: executeAnalysisDeps.logger });
|
|
123
130
|
const executeAnalysis = registerExecuteAnalysis({
|
|
124
131
|
...executeAnalysisDeps,
|
|
125
|
-
|
|
132
|
+
synthesisLookups,
|
|
133
|
+
scriptRuntime: conversation.scriptRuntime,
|
|
134
|
+
workspaceFs: conversation.workspaceFs,
|
|
126
135
|
usageRecorder,
|
|
127
136
|
toolOutputStore,
|
|
128
137
|
workingMemory: createWorkingMemory(conversation.pool),
|
|
129
138
|
pauseRetryDelaysMs,
|
|
130
139
|
});
|
|
131
|
-
const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder, toolOutputStore, pauseRetryDelaysMs });
|
|
140
|
+
const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, leases, usageRecorder, toolOutputStore, pauseRetryDelaysMs });
|
|
132
141
|
// The session runtime binds the per-session state to the thread behind the
|
|
133
142
|
// tool boundary, thus one instance serves every report thread. It comes before
|
|
134
143
|
// the conversation agent, because `start_report_session` takes its anchor
|
|
@@ -147,7 +156,6 @@ export function assembleCoreRuntime(deps) {
|
|
|
147
156
|
...conversation,
|
|
148
157
|
executeAnalysisWorkflow: executeAnalysis,
|
|
149
158
|
anchorReportSession: reportSession.ensureSessionState.bind(reportSession),
|
|
150
|
-
resourcePolicy,
|
|
151
159
|
usageRecorder,
|
|
152
160
|
toolOutputStore,
|
|
153
161
|
citationResolver,
|
|
@@ -155,6 +163,7 @@ export function assembleCoreRuntime(deps) {
|
|
|
155
163
|
// The start tool passes it to the spawn, which emits the creation of a
|
|
156
164
|
// report session. The same seam reaches the report agent below.
|
|
157
165
|
provenance: deps.provenance,
|
|
166
|
+
syncSessionFile: deps.syncSessionFile,
|
|
158
167
|
registerSubAgent: registerPausingSubAgent,
|
|
159
168
|
});
|
|
160
169
|
// The report agent is a singleton over the conversation deps, the same way the
|
|
@@ -164,21 +173,16 @@ export function assembleCoreRuntime(deps) {
|
|
|
164
173
|
const reportThreads = createThreadStore(conversation.pool);
|
|
165
174
|
// The read cache of the production report path, one for each recent analysis. A preview
|
|
166
175
|
// and the record that follows it are two tool calls over one draft, thus they meet the
|
|
167
|
-
// same artifacts. The store carries the streamed hash and the
|
|
168
|
-
//
|
|
169
|
-
// call never outlives the call, and one unchanged file costs one hash and one container.
|
|
176
|
+
// same artifacts. The store carries the streamed hash and the parsed rows between them,
|
|
177
|
+
// thus one unchanged file costs one hash and one parse.
|
|
170
178
|
const reportArtifactReads = createArtifactReadStore();
|
|
171
179
|
// The reference resolver of the report path, and its production wiring. A resolver reads
|
|
172
180
|
// the pinned artifacts of one analysis, thus the factory binds one analysis and one caller
|
|
173
|
-
// for each tool call. The
|
|
174
|
-
// rail of the profile for an over-cap file or a host parse fault. The pass runs inside the
|
|
175
|
-
// step of the tool call. The cap gates the host read, and the resolver defaults it to 16 MiB.
|
|
181
|
+
// for each tool call. The cap gates the host read, and the resolver defaults it to 16 MiB.
|
|
176
182
|
const makeProductionReportResolver = async (scope) => {
|
|
177
|
-
const arm = createExtractionArm((requests) => runExtraction({ sandboxClient: wf.dataProfile.sandboxClient, logger: wf.dataProfile.logger }, scope, requests));
|
|
178
183
|
return createProductionResolver({
|
|
179
184
|
workspaceRoot: await conversation.resolveWorkspaceRoot(scope.analysisId),
|
|
180
185
|
analysisId: scope.analysisId,
|
|
181
|
-
extractionArm: arm,
|
|
182
186
|
readCache: reportArtifactReads.forAnalysis(scope.analysisId),
|
|
183
187
|
cap: deps.reportHostReadCapBytes,
|
|
184
188
|
});
|
|
@@ -192,21 +196,19 @@ export function assembleCoreRuntime(deps) {
|
|
|
192
196
|
gateway: reportSession.gateway,
|
|
193
197
|
resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
|
|
194
198
|
store: reportVersionStore,
|
|
199
|
+
derivations: reportSession.derivations,
|
|
195
200
|
threads: reportThreads,
|
|
196
201
|
chrome: conversation.chrome,
|
|
197
|
-
// The derivation tool draws the rail of the value tier: the same sandbox client. Thus a
|
|
198
|
-
// session derivation and an extraction run on one substrate, and neither one mints a run
|
|
199
|
-
// of the analysis.
|
|
200
|
-
derivations: reportSession.derivations,
|
|
201
|
-
sandboxClient: wf.dataProfile.sandboxClient,
|
|
202
202
|
makeResolver: makeReportResolver,
|
|
203
203
|
eyes,
|
|
204
204
|
resolvePageAsset: deps.resolveReportPageAsset,
|
|
205
205
|
makeSessionPages: deps.makeSessionPagePublisher,
|
|
206
206
|
resolvePageUrl: deps.resolveReportPageUrl,
|
|
207
207
|
provenance: deps.provenance,
|
|
208
|
+
syncSessionFile: deps.syncSessionFile,
|
|
208
209
|
logger: conversation.logger,
|
|
209
210
|
toolOutputStore,
|
|
211
|
+
scriptRuntime: conversation.scriptRuntime,
|
|
210
212
|
});
|
|
211
213
|
// The single registration point for every typed agent. A future thread type's
|
|
212
214
|
// agent plugs in here at assembly with no embedder change. `conversation` is
|
|
@@ -232,8 +234,18 @@ export function assembleCoreRuntime(deps) {
|
|
|
232
234
|
credentialFor: deps.credentialFor,
|
|
233
235
|
pauseRetryDelaysMs,
|
|
234
236
|
});
|
|
237
|
+
const runCanceler = createRunCanceler({
|
|
238
|
+
pool: conversation.pool,
|
|
239
|
+
runCharge: executeAnalysisDeps.runCharge,
|
|
240
|
+
runAuthorizer: conversation.runAuthorizer,
|
|
241
|
+
// The end of a canceled run goes where the run engine emits the rest of the run.
|
|
242
|
+
provenance: executeAnalysisDeps.provenance,
|
|
243
|
+
logger: conversation.logger,
|
|
244
|
+
releaseLeasesOf: (workflowIds) => leases.releaseOwnedBy(workflowIds),
|
|
245
|
+
});
|
|
235
246
|
return {
|
|
236
247
|
agents: agentResolver,
|
|
248
|
+
runCanceler,
|
|
237
249
|
askGateway,
|
|
238
250
|
chatTurns,
|
|
239
251
|
work: createWork({
|
|
@@ -241,15 +253,15 @@ export function assembleCoreRuntime(deps) {
|
|
|
241
253
|
runAuthorizer: conversation.runAuthorizer,
|
|
242
254
|
logger: conversation.logger,
|
|
243
255
|
stopTurn: (turnId) => chatTurns.stop(turnId),
|
|
244
|
-
runCanceler
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
256
|
+
runCanceler,
|
|
257
|
+
stopProfile: async (analysisId, workflowId) => {
|
|
258
|
+
await stopDataProfile({ pool: conversation.pool, logger: conversation.logger, notifyDataProfileSettled: wf.dataProfile.notifyDataProfileSettled }, analysisId, workflowId);
|
|
259
|
+
// A canceled body runs no teardown, thus the lease of the profile ends here and not at its owner check.
|
|
260
|
+
await leases.releaseOwnedBy([workflowId]).catch((failure) => {
|
|
261
|
+
const logger = conversation.logger ?? createNoopLogger();
|
|
262
|
+
logger.error("the sandbox lease of a stopped profile did not end", { analysisId, ...logger.errorFields(failure) });
|
|
263
|
+
});
|
|
264
|
+
},
|
|
253
265
|
}),
|
|
254
266
|
reportSession: { ensureSessionState: reportSession.ensureSessionState.bind(reportSession) },
|
|
255
267
|
workflows: {
|
|
@@ -263,7 +275,6 @@ export function assembleCoreRuntime(deps) {
|
|
|
263
275
|
executeAnalysisWorkflow: executeAnalysis,
|
|
264
276
|
runAuthorizer: conversation.runAuthorizer,
|
|
265
277
|
runLauncher: conversation.runLauncher,
|
|
266
|
-
resourcePolicy,
|
|
267
278
|
extendAnalysisFarm: conversation.extendAnalysisFarm,
|
|
268
279
|
logger: conversation.logger,
|
|
269
280
|
provenance: deps.provenance,
|
package/dist/sandbox/client.d.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { ResultAsync } from "neverthrow";
|
|
21
21
|
import type { SpawnSession } from "../auth/types.js";
|
|
22
|
+
import type { ResourceLimits } from "../config/resource-limits.js";
|
|
22
23
|
import type { SandboxError } from "./sandbox-error.js";
|
|
23
24
|
import type { ExecEmit, ExecRequest, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxSpec, ToolchainSource } from "./types.js";
|
|
24
25
|
export type SandboxClient = {
|
|
@@ -31,6 +32,8 @@ export type SandboxClient = {
|
|
|
31
32
|
* optional field would carry two meanings, undeclared and unknown.
|
|
32
33
|
*/
|
|
33
34
|
readonly toolchainSource: ToolchainSource;
|
|
35
|
+
/** The per-machine ceilings that the client clamps each spawn to. The sandbox module clamps a lease request to the same values. */
|
|
36
|
+
readonly resourceLimits: ResourceLimits;
|
|
34
37
|
/**
|
|
35
38
|
* DBOS step (`sandbox.create`) — the spawn half of the two-step create
|
|
36
39
|
* (see the harness-sandbox-exec spec). Launches the sandbox-base container/Job under the
|
|
@@ -59,9 +62,8 @@ export type SandboxClient = {
|
|
|
59
62
|
* same step gets the same id, and the sandbox gives it the result of the
|
|
60
63
|
* first. Outside a workflow it throws.
|
|
61
64
|
*
|
|
62
|
-
* `execId` replaces that id.
|
|
63
|
-
*
|
|
64
|
-
* (`sandbox/step-sandbox.ts`).
|
|
65
|
+
* `execId` replaces that id. The sandbox leases give an id under the salt
|
|
66
|
+
* of the lease (`sandbox/leases.ts`).
|
|
65
67
|
*
|
|
66
68
|
* A recovered step submits again under the same id. The sandbox dedups the
|
|
67
69
|
* submit on the id, thus the poll attaches to the exec that already ran or
|
|
@@ -2,17 +2,11 @@
|
|
|
2
2
|
* Backend-selected `SandboxClient` factory.
|
|
3
3
|
*
|
|
4
4
|
* Docker for local dev, K8s for production — the single place the
|
|
5
|
-
* `SANDBOX_BACKEND` env decision lives.
|
|
6
|
-
*
|
|
7
|
-
* backend module directly.
|
|
8
|
-
*
|
|
9
|
-
* Registry write/clear callbacks are wired here so the per-backend
|
|
10
|
-
* `createSandbox` / `teardown` implementations stay free of state-layer
|
|
11
|
-
* coupling (they just call the closures we hand them). `exec` is
|
|
5
|
+
* `SANDBOX_BACKEND` env decision lives. The embedder makes the client and gives
|
|
6
|
+
* it to `assembleCoreRuntime`, and only the sandbox module calls it. `exec` is
|
|
12
7
|
* backend-agnostic — it only needs the SandboxRef.
|
|
13
8
|
*/
|
|
14
9
|
import type { V1Toleration } from "@kubernetes/client-node";
|
|
15
|
-
import type { Querier } from "../state/db.js";
|
|
16
10
|
import type { Logger } from "../lib/logger.js";
|
|
17
11
|
import { type ResourceLimits } from "../config/resource-limits.js";
|
|
18
12
|
import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
@@ -30,8 +24,6 @@ export type SandboxBackendConfig = {
|
|
|
30
24
|
readonly namespace: string;
|
|
31
25
|
};
|
|
32
26
|
export type CreateSandboxClientConfig = {
|
|
33
|
-
/** App pool — used for registry writes inside the create/teardown steps. */
|
|
34
|
-
pool: Querier;
|
|
35
27
|
/** Backend default + namespace fallback source. */
|
|
36
28
|
env: SandboxBackendConfig;
|
|
37
29
|
/** Default sandbox-base image when the workflow doesn't override per-step. */
|
|
@@ -174,6 +166,9 @@ export declare function precreateStepTree(deps: {
|
|
|
174
166
|
resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
175
167
|
stepTreeAccess?: "world-writable";
|
|
176
168
|
}, coords: MountPlanCoords): Promise<void>;
|
|
169
|
+
/** Pre-create the directory of the exec records of the lease, which each machine of the lease mounts read-write. */
|
|
170
|
+
export declare function precreateRecordDir(deps: {
|
|
171
|
+
resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
172
|
+
stepTreeAccess?: "world-writable";
|
|
173
|
+
}, coords: MountPlanCoords): Promise<void>;
|
|
177
174
|
export declare function createSandboxClient(config: CreateSandboxClientConfig): SandboxClient;
|
|
178
|
-
/** Re-exports so consumers import once from this module. */
|
|
179
|
-
export { clearSandboxRef } from "../state/index.js";
|
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
* Backend-selected `SandboxClient` factory.
|
|
3
3
|
*
|
|
4
4
|
* Docker for local dev, K8s for production — the single place the
|
|
5
|
-
* `SANDBOX_BACKEND` env decision lives.
|
|
6
|
-
*
|
|
7
|
-
* backend module directly.
|
|
8
|
-
*
|
|
9
|
-
* Registry write/clear callbacks are wired here so the per-backend
|
|
10
|
-
* `createSandbox` / `teardown` implementations stay free of state-layer
|
|
11
|
-
* coupling (they just call the closures we hand them). `exec` is
|
|
5
|
+
* `SANDBOX_BACKEND` env decision lives. The embedder makes the client and gives
|
|
6
|
+
* it to `assembleCoreRuntime`, and only the sandbox module calls it. `exec` is
|
|
12
7
|
* backend-agnostic — it only needs the SandboxRef.
|
|
13
8
|
*/
|
|
14
9
|
import { chmod, mkdir } from "node:fs/promises";
|
|
@@ -20,13 +15,11 @@ import { recordSandboxExec } from "../lib/metrics.js";
|
|
|
20
15
|
import { ATTR_INFLEXA_EXEC_ID, stableSpan } from "../lib/otel-spans.js";
|
|
21
16
|
import { clampResources } from "../config/resource-limits.js";
|
|
22
17
|
import { tailWritePrefix } from "../workspace/paths.js";
|
|
23
|
-
import { tryMutation } from "../lib/db-result.js";
|
|
24
18
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
25
|
-
import { setSandboxRef } from "../state/index.js";
|
|
26
19
|
import { capExecStreams, EXEC_STREAM_BYTE_CAP } from "../tools/workspace/result-bounds.js";
|
|
27
20
|
import { createDockerSandboxOps } from "./docker-client.js";
|
|
28
21
|
import { runExec, stepExecId } from "./exec.js";
|
|
29
|
-
import {
|
|
22
|
+
import { sandboxExecOutcomeOf } from "./exec-outcome.js";
|
|
30
23
|
import { createK8sSandboxOps } from "./k8s-client.js";
|
|
31
24
|
import { mountCoordsOf, sandboxWriteTail } from "./mount-plan.js";
|
|
32
25
|
import { SandboxFailure } from "./sandbox-error.js";
|
|
@@ -65,6 +58,13 @@ export async function precreateStepTree(deps, coords) {
|
|
|
65
58
|
await Promise.all(write.subdirs.map((sub) => chmod(join(writeDir, sub), 0o777)));
|
|
66
59
|
}
|
|
67
60
|
}
|
|
61
|
+
/** Pre-create the directory of the exec records of the lease, which each machine of the lease mounts read-write. */
|
|
62
|
+
export async function precreateRecordDir(deps, coords) {
|
|
63
|
+
const recordDir = tailWritePrefix({ workspaceRoot: await deps.resolveWorkspaceRoot(coords.analysisId), tail: coords.recordTail });
|
|
64
|
+
await mkdir(recordDir, { recursive: true });
|
|
65
|
+
if (deps.stepTreeAccess === "world-writable")
|
|
66
|
+
await chmod(recordDir, 0o777);
|
|
67
|
+
}
|
|
68
68
|
export function createSandboxClient(config) {
|
|
69
69
|
const backend = config.backend ?? config.env.backend;
|
|
70
70
|
// Normalized once, here, and handed to the backends and to the client
|
|
@@ -100,9 +100,6 @@ export function createSandboxClient(config) {
|
|
|
100
100
|
* unwraps below stay on the plain bridge: they carry a `DbError`.
|
|
101
101
|
*/
|
|
102
102
|
const failing = (e) => new SandboxFailure(e);
|
|
103
|
-
const registerSandbox = async (session, spec, ref) => {
|
|
104
|
-
unwrapOrThrow(await setSandboxRef(config.pool, session.runFrame.runId, session.runFrame.stepId, ref));
|
|
105
|
-
};
|
|
106
103
|
const ops = backend === "k8s"
|
|
107
104
|
? createK8sSandboxOps({
|
|
108
105
|
image: config.image,
|
|
@@ -120,7 +117,6 @@ export function createSandboxClient(config) {
|
|
|
120
117
|
tolerations: config.tolerations,
|
|
121
118
|
runtimeClassName: config.runtimeClassName,
|
|
122
119
|
logger: config.logger,
|
|
123
|
-
registerSandbox,
|
|
124
120
|
})
|
|
125
121
|
: createDockerSandboxOps({
|
|
126
122
|
image: config.image,
|
|
@@ -133,27 +129,9 @@ export function createSandboxClient(config) {
|
|
|
133
129
|
platform: config.platform,
|
|
134
130
|
engineSocketPath: config.engineSocketPath,
|
|
135
131
|
logger: config.logger,
|
|
136
|
-
registerSandbox,
|
|
137
132
|
});
|
|
138
|
-
// `teardown` needs the registry-clear closure but the SandboxRef alone
|
|
139
|
-
// doesn't carry runId/stepId. Wrap the backend impl so callers pass the
|
|
140
|
-
// ref + step coordinates and the wrapper does the clear after the
|
|
141
|
-
// backend-level removal.
|
|
142
133
|
const teardown = async (ref) => {
|
|
143
134
|
unwrapOrThrow((await ops.teardown(ref)).mapErr(failing));
|
|
144
|
-
// Sandboxes are 1-per-step (see `sandbox-step.ts` body — each child
|
|
145
|
-
// workflow calls `createSandbox` once then `teardown` once), so the
|
|
146
|
-
// broad WHERE clause matches exactly one row. If that invariant ever
|
|
147
|
-
// changes (e.g. a step reuses a sibling's sandbox), this clear must
|
|
148
|
-
// be scoped to (run, step) to avoid wiping prior steps' provenance.
|
|
149
|
-
unwrapOrThrow(await tryMutation("createSandbox.teardownClearSandboxRef", async () => {
|
|
150
|
-
await config.pool.query({
|
|
151
|
-
text: `UPDATE cortex_step_executions
|
|
152
|
-
SET sandbox_ref = NULL
|
|
153
|
-
WHERE sandbox_ref->>'sandboxId' = $1`,
|
|
154
|
-
values: [ref.sandboxId],
|
|
155
|
-
});
|
|
156
|
-
}));
|
|
157
135
|
};
|
|
158
136
|
// One arbiter for both surfaces: the client's `isAlive` method and the
|
|
159
137
|
// poll loop's escalation probe are the same backend inspect.
|
|
@@ -185,10 +163,12 @@ export function createSandboxClient(config) {
|
|
|
185
163
|
// The config states an engine fact (binds preserve host ownership);
|
|
186
164
|
// which remediation that fact demands — today, a world-writable step
|
|
187
165
|
// write tree — is harness-owned and chosen here, not by the embedder.
|
|
188
|
-
const
|
|
166
|
+
const treeDeps = {
|
|
189
167
|
resolveWorkspaceRoot: config.resolveWorkspaceRoot,
|
|
190
168
|
stepTreeAccess: config.engineBindOwnership === "host-preserved" ? "world-writable" : undefined,
|
|
191
|
-
}
|
|
169
|
+
};
|
|
170
|
+
const coords = mountCoordsOf(session, spec);
|
|
171
|
+
const precreated = await ResultAsync.fromPromise(Promise.all([precreateStepTree(treeDeps, coords), precreateRecordDir(treeDeps, coords)]), createFailed("createSandbox.precreateStepTree"));
|
|
192
172
|
if (precreated.isErr())
|
|
193
173
|
return err(precreated.error);
|
|
194
174
|
// Clamp to cluster ceilings so the pod is always quota-admissible.
|
|
@@ -197,6 +177,7 @@ export function createSandboxClient(config) {
|
|
|
197
177
|
};
|
|
198
178
|
return {
|
|
199
179
|
toolchainSource,
|
|
180
|
+
resourceLimits: config.resourceLimits,
|
|
200
181
|
createSandbox: (session, spec, identity) => new ResultAsync(createSandbox(session, spec, identity)),
|
|
201
182
|
// The one place an ExecResult crosses from the wire into the process, so
|
|
202
183
|
// the cap lands here rather than at each consumer. Every downstream use —
|
|
@@ -223,7 +204,6 @@ export function createSandboxClient(config) {
|
|
|
223
204
|
peakMemoryBytes: result.usage?.peakMemoryBytes,
|
|
224
205
|
cpuMillis: result.usage?.cpuMillis,
|
|
225
206
|
});
|
|
226
|
-
noteExecOutcome(ref.sandboxId, summarizeExec(result));
|
|
227
207
|
return result;
|
|
228
208
|
}, { name: "sandbox.exec" }),
|
|
229
209
|
isAlive,
|
|
@@ -233,5 +213,3 @@ export function createSandboxClient(config) {
|
|
|
233
213
|
listManagedSandboxes: async () => unwrapOrThrow((await ops.listManagedSandboxes()).mapErr(failing)),
|
|
234
214
|
};
|
|
235
215
|
}
|
|
236
|
-
/** Re-exports so consumers import once from this module. */
|
|
237
|
-
export { clearSandboxRef } from "../state/index.js";
|
|
@@ -143,11 +143,9 @@ export type DockerClientConfig = {
|
|
|
143
143
|
/**
|
|
144
144
|
* Optional logger so a store degradation (a resolved farm whose `inflexa.lock`
|
|
145
145
|
* is absent or invalid by sandbox-create time) is observable instead of a
|
|
146
|
-
* silent libs-mount drop.
|
|
146
|
+
* silent libs-mount drop.
|
|
147
147
|
*/
|
|
148
148
|
logger?: Logger;
|
|
149
|
-
/** Hook called after the registry row is written. */
|
|
150
|
-
registerSandbox: (session: SpawnSession, spec: SandboxSpec, ref: SandboxRef) => Promise<void>;
|
|
151
149
|
};
|
|
152
150
|
/**
|
|
153
151
|
* dockerode connection options for a configured engine socket, or `undefined`
|
|
@@ -319,6 +319,7 @@ export function createDockerSandboxOps(config) {
|
|
|
319
319
|
...(libsMounted && farm && plan.farmPath ? [`${farm.farmPath}:${plan.farmPath}:ro`] : []),
|
|
320
320
|
...(libsMounted && cacheBindable && farm?.cachePath && plan.cachePath ? [`${farm.cachePath}:${plan.cachePath}:rw`] : []),
|
|
321
321
|
...(refsMounted && config.refStorePath ? [`${config.refStorePath}:${String(plan.refsPath)}:ro`] : []),
|
|
322
|
+
`${tailWritePrefix({ workspaceRoot: hostTreePath, tail: plan.recordTail })}:${plan.recordsPath}:rw`,
|
|
322
323
|
...cpuBinds,
|
|
323
324
|
];
|
|
324
325
|
const createFailed = (status, cause) => ({
|
|
@@ -348,7 +349,7 @@ export function createDockerSandboxOps(config) {
|
|
|
348
349
|
Env: env,
|
|
349
350
|
User: SANDBOX_ROOT_USER,
|
|
350
351
|
WorkingDir: plan.workingDir,
|
|
351
|
-
Labels: mergeLabels(hostLabels, { ...harnessLabels(session, sandboxId), [OWNER_WORKFLOW_LABEL]: spec.
|
|
352
|
+
Labels: mergeLabels(hostLabels, { ...harnessLabels(session, sandboxId), [OWNER_WORKFLOW_LABEL]: spec.ownerWorkflowId }),
|
|
352
353
|
ExposedPorts: { [`${SANDBOX_SERVER_PORT}/tcp`]: {} },
|
|
353
354
|
HostConfig: {
|
|
354
355
|
Binds: binds,
|
|
@@ -378,7 +379,7 @@ export function createDockerSandboxOps(config) {
|
|
|
378
379
|
};
|
|
379
380
|
if (config.platform !== undefined)
|
|
380
381
|
createOpts.platform = config.platform;
|
|
381
|
-
const sandbox = await createOrAdopt(docker, createOpts, sandboxId, spec.
|
|
382
|
+
const sandbox = await createOrAdopt(docker, createOpts, sandboxId, spec.ownerWorkflowId, createFailed, logger);
|
|
382
383
|
if (sandbox.isErr())
|
|
383
384
|
return err(sandbox.error);
|
|
384
385
|
if (!sandbox.value.alreadyRunning) {
|
|
@@ -407,9 +408,6 @@ export function createDockerSandboxOps(config) {
|
|
|
407
408
|
port,
|
|
408
409
|
backend: "docker",
|
|
409
410
|
};
|
|
410
|
-
const registered = await trySandbox(() => config.registerSandbox(session, spec, ref), createFailed);
|
|
411
|
-
if (registered.isErr())
|
|
412
|
-
return err(registered.error);
|
|
413
411
|
return ok(ref);
|
|
414
412
|
})());
|
|
415
413
|
},
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
* keeps that text on the sandbox side of the boundary. A summary gives the
|
|
9
9
|
* SIZE of stderr and never a byte of it.
|
|
10
10
|
*
|
|
11
|
-
* The summary of the last exec of a
|
|
12
|
-
* keyed by
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* re-runs the await loop and writes the cell again, thus
|
|
16
|
-
* reads the summary of the same exec. The map is capped, so a
|
|
17
|
-
* step never reaches the read site cannot hold memory.
|
|
11
|
+
* The summary of the last exec of a lease lives in one process-local cell,
|
|
12
|
+
* keyed by the lease key. A step holds one lease key, thus the cell is
|
|
13
|
+
* step-scoped in effect and the step body reads it without a new dependency
|
|
14
|
+
* through the agent factory. The cell keeps the summary across a machine
|
|
15
|
+
* replacement. A replay re-runs the await loop and writes the cell again, thus
|
|
16
|
+
* a recovered body reads the summary of the same exec. The map is capped, so a
|
|
17
|
+
* lease whose step never reaches the read site cannot hold memory.
|
|
18
18
|
*/
|
|
19
19
|
import type { SandboxExecOutcome } from "../lib/metrics.js";
|
|
20
20
|
import type { ExecResult } from "./types.js";
|
|
@@ -39,7 +39,7 @@ export type ExecOutcomeSummary = {
|
|
|
39
39
|
};
|
|
40
40
|
/** Reduce a result to its summary. */
|
|
41
41
|
export declare function summarizeExec(result: ExecResult): ExecOutcomeSummary;
|
|
42
|
-
/** Keep `summary` as the last known exec of `
|
|
43
|
-
export declare function noteExecOutcome(
|
|
44
|
-
/** The last known exec of `
|
|
45
|
-
export declare function lastExecOutcome(
|
|
42
|
+
/** Keep `summary` as the last known exec of the lease `leaseKey`. */
|
|
43
|
+
export declare function noteExecOutcome(leaseKey: string, summary: ExecOutcomeSummary): void;
|
|
44
|
+
/** The last known exec of the lease `leaseKey`, or `undefined` when it ran none. */
|
|
45
|
+
export declare function lastExecOutcome(leaseKey: string): ExecOutcomeSummary | undefined;
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
* keeps that text on the sandbox side of the boundary. A summary gives the
|
|
9
9
|
* SIZE of stderr and never a byte of it.
|
|
10
10
|
*
|
|
11
|
-
* The summary of the last exec of a
|
|
12
|
-
* keyed by
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* re-runs the await loop and writes the cell again, thus
|
|
16
|
-
* reads the summary of the same exec. The map is capped, so a
|
|
17
|
-
* step never reaches the read site cannot hold memory.
|
|
11
|
+
* The summary of the last exec of a lease lives in one process-local cell,
|
|
12
|
+
* keyed by the lease key. A step holds one lease key, thus the cell is
|
|
13
|
+
* step-scoped in effect and the step body reads it without a new dependency
|
|
14
|
+
* through the agent factory. The cell keeps the summary across a machine
|
|
15
|
+
* replacement. A replay re-runs the await loop and writes the cell again, thus
|
|
16
|
+
* a recovered body reads the summary of the same exec. The map is capped, so a
|
|
17
|
+
* lease whose step never reaches the read site cannot hold memory.
|
|
18
18
|
*/
|
|
19
19
|
/** The reason that names a machine the kernel killed for its memory limit. */
|
|
20
20
|
const OOM_KILLED = "sandbox-oom-killed";
|
|
@@ -41,25 +41,25 @@ export function summarizeExec(result) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* How many
|
|
45
|
-
*
|
|
44
|
+
* How many leases keep a summary. A host holds far fewer leases at once than
|
|
45
|
+
* this, thus the cap only drops the summary of a step that ended long ago.
|
|
46
46
|
*/
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
/** Keep `summary` as the last known exec of `
|
|
50
|
-
export function noteExecOutcome(
|
|
47
|
+
const MAX_TRACKED_LEASES = 256;
|
|
48
|
+
const lastExecByLease = new Map();
|
|
49
|
+
/** Keep `summary` as the last known exec of the lease `leaseKey`. */
|
|
50
|
+
export function noteExecOutcome(leaseKey, summary) {
|
|
51
51
|
// Delete before set so the key moves to the end of the insertion order and
|
|
52
|
-
// the eviction below drops the least recently written
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
while (
|
|
56
|
-
const oldest =
|
|
52
|
+
// the eviction below drops the least recently written lease.
|
|
53
|
+
lastExecByLease.delete(leaseKey);
|
|
54
|
+
lastExecByLease.set(leaseKey, summary);
|
|
55
|
+
while (lastExecByLease.size > MAX_TRACKED_LEASES) {
|
|
56
|
+
const oldest = lastExecByLease.keys().next();
|
|
57
57
|
if (oldest.done)
|
|
58
58
|
break;
|
|
59
|
-
|
|
59
|
+
lastExecByLease.delete(oldest.value);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
/** The last known exec of `
|
|
63
|
-
export function lastExecOutcome(
|
|
64
|
-
return
|
|
62
|
+
/** The last known exec of the lease `leaseKey`, or `undefined` when it ran none. */
|
|
63
|
+
export function lastExecOutcome(leaseKey) {
|
|
64
|
+
return lastExecByLease.get(leaseKey);
|
|
65
65
|
}
|
package/dist/sandbox/exec.d.ts
CHANGED
|
@@ -52,6 +52,11 @@ export type ExecDeps = {
|
|
|
52
52
|
* makes the pair unique, thus no caller mints an exec id.
|
|
53
53
|
*/
|
|
54
54
|
export declare function stepExecId(): string;
|
|
55
|
+
/**
|
|
56
|
+
* The exec id of a step under a salt that no machine receives. The id is the same on each run of the step, but the
|
|
57
|
+
* ids that a command can read in the exec records do not give the id of a later exec.
|
|
58
|
+
*/
|
|
59
|
+
export declare function saltedStepExecId(salt: string): string;
|
|
55
60
|
/**
|
|
56
61
|
* Run one exec to its terminal result.
|
|
57
62
|
*
|
package/dist/sandbox/exec.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { errorMessageOf } from "../lib/thrown.js";
|
|
16
16
|
import { textOf } from "../lib/unknown-text.js";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
17
18
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
18
19
|
import { DBOS, Error as DBOSErrors, StatusString } from "@dbos-inc/dbos-sdk";
|
|
19
20
|
import { untracedFetch } from "../lib/otel-spans.js";
|
|
@@ -62,6 +63,13 @@ export function stepExecId() {
|
|
|
62
63
|
}
|
|
63
64
|
return `${workflowId}:${stepId}`;
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The exec id of a step under a salt that no machine receives. The id is the same on each run of the step, but the
|
|
68
|
+
* ids that a command can read in the exec records do not give the id of a later exec.
|
|
69
|
+
*/
|
|
70
|
+
export function saltedStepExecId(salt) {
|
|
71
|
+
return createHash("sha256").update(`${salt}:${stepExecId()}`).digest("hex");
|
|
72
|
+
}
|
|
65
73
|
/**
|
|
66
74
|
* Run one exec to its terminal result.
|
|
67
75
|
*
|
|
@@ -177,7 +185,7 @@ async function pollExec(ref, execId, emit, deadlineMs, deps) {
|
|
|
177
185
|
if (resp.cursor > cursor)
|
|
178
186
|
cursor = resp.cursor;
|
|
179
187
|
if (resp.result !== undefined)
|
|
180
|
-
return resp.result;
|
|
188
|
+
return resp.result.lost === undefined ? resp.result : syntheticFailureResult(execId, "sandbox-replaced");
|
|
181
189
|
}
|
|
182
190
|
if (deps.isAlive && escalation.onPoll(polled.kind === "ok" ? "ok" : "unavailable")) {
|
|
183
191
|
const verdict = await probeLiveness(deps.isAlive, ref);
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* `sandbox.mint` DBOS step, then handed to the backend's spawn step. Because the
|
|
5
|
-
* identity is durable before any machine exists, a crash between spawn and the
|
|
6
|
-
* spawn-step checkpoint is recoverable — the re-run adopts the existing machine
|
|
7
|
-
* under the same name.
|
|
2
|
+
* The name of a sandbox machine. It comes from a key: the id of the lease workflow and the machine generation. Thus
|
|
3
|
+
* a rerun of the spawn step adopts the machine of the earlier attempt, and no step checkpoints a name before a spawn.
|
|
8
4
|
*/
|
|
9
5
|
import type { SandboxIdentity } from "./types.js";
|
|
10
|
-
/** `sbx-{run8}-{rand8}` — run prefix aids `kubectl` triage; the 8-hex random
|
|
11
|
-
* suffix makes within-run collisions across a run's concurrent steps
|
|
12
|
-
* negligible. Names are NOT the idempotency mechanism (the checkpoint is), and
|
|
13
|
-
* adoption is owner-label-guarded, but a unique name keeps a collision from
|
|
14
|
-
* ever reaching that guard. */
|
|
15
|
-
export declare function mintSandboxIdentity(runId: string): SandboxIdentity;
|
|
16
6
|
/**
|
|
17
|
-
* `sbx-{run8}-{hash12}`
|
|
18
|
-
*
|
|
19
|
-
* of a step adopts the machine of the earlier attempt. The key is hashed,
|
|
20
|
-
* because a workflow id can hold characters that a container name refuses.
|
|
7
|
+
* `sbx-{run8}-{hash12}`. The run prefix aids `kubectl` triage. The same key gives the same name, and the key is
|
|
8
|
+
* hashed, because a workflow id can hold characters that a container name refuses.
|
|
21
9
|
*/
|
|
22
10
|
export declare function keyedSandboxIdentity(runId: string, key: string): SandboxIdentity;
|