@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/workflows/pause.js
CHANGED
|
@@ -14,6 +14,7 @@ import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
16
16
|
import { deliverNotice } from "../lib/hooks.js";
|
|
17
|
+
import { trackWorkPaused } from "../lib/metrics.js";
|
|
17
18
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
18
19
|
import { isTurnCredentialMissing } from "../loop/turn-credentials.js";
|
|
19
20
|
import { modelFailureOf } from "../providers/errors.js";
|
|
@@ -123,30 +124,36 @@ export function workPause(scope) {
|
|
|
123
124
|
await suspendCredential("pause.suspend");
|
|
124
125
|
await scope.emit({ type: "data-pause", id: workflowId, ...work, state, cause, attempt, retryAt });
|
|
125
126
|
log.warn("work paused", { workflowId, ...work, state, cause: cause.kind, attempt });
|
|
126
|
-
const waited = await waitForMessage(topic, delayMs, signal);
|
|
127
127
|
let end;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
const untrack = trackWorkPaused({ workKind: scope.workKind });
|
|
129
|
+
try {
|
|
130
|
+
const waited = await waitForMessage(topic, delayMs, signal);
|
|
131
|
+
if (waited === "hold") {
|
|
132
|
+
// A person held the work: the timer stops, and the work waits for a resume.
|
|
133
|
+
await DBOS.runStep(async () => unwrapOrThrow(await recordPause(scope.pool, {
|
|
134
|
+
workflowId,
|
|
135
|
+
...work,
|
|
136
|
+
analysisId: scope.analysisId,
|
|
137
|
+
threadId: scope.threadId,
|
|
138
|
+
state: "paused",
|
|
139
|
+
cause,
|
|
140
|
+
attempt,
|
|
141
|
+
topic,
|
|
142
|
+
pausedAt: new Date(pausedAt).toISOString(),
|
|
143
|
+
runSession: scope.run?.session,
|
|
144
|
+
})), { name: "pause.hold" });
|
|
145
|
+
await suspendCredential("pause.hold.suspend");
|
|
146
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "paused", cause, attempt });
|
|
147
|
+
log.info("work held", { workflowId, ...work, attempt });
|
|
148
|
+
const held = await waitForMessage(topic, undefined, signal);
|
|
149
|
+
end = held === "hold" ? "resume" : held;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
end = waited;
|
|
153
|
+
}
|
|
147
154
|
}
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
finally {
|
|
156
|
+
untrack();
|
|
150
157
|
}
|
|
151
158
|
await DBOS.runStep(async () => unwrapOrThrow(await clearPause(scope.pool, workflowId)), { name: "pause.clear" });
|
|
152
159
|
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "running" });
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* replays from the cache instead of re-issuing work):
|
|
7
7
|
*
|
|
8
8
|
* 1. `mark-running` — update `cortex_step_executions` to running.
|
|
9
|
-
* 2.
|
|
9
|
+
* 2. the lease — bind the lease of the step. The agent acquires
|
|
10
|
+
* its sandbox with `acquire_sandbox`.
|
|
10
11
|
* 3. `runLoop` — the harness agent loop. Every model call and
|
|
11
12
|
* tool call inside is its own durable step.
|
|
12
13
|
* 4. `generateFileMetadata`— the file-metadata follow-up request of the step agent.
|
|
@@ -16,15 +17,16 @@
|
|
|
16
17
|
* 7. `syncArtifacts` — register provenance + upload missing file_ids
|
|
17
18
|
* via the managed root (scoped to this run+step).
|
|
18
19
|
* 8. `vector-index` — index file descriptions + summary into pgvector.
|
|
19
|
-
* 9. `
|
|
20
|
+
* 9. `sandbox.release` — release the lease, which removes its machine.
|
|
20
21
|
* 10. `mark-terminal` — write the final status, emit `data-step-activity`
|
|
21
22
|
* onto the parent's DBOS stream.
|
|
22
23
|
*
|
|
23
|
-
* The parent owns sibling lifecycle. This child
|
|
24
|
-
* own pause (work-pause spec): a failed model request of the agent loop
|
|
25
|
-
* cause of a pause, or a refusal with the suspend flag of the
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* The parent owns sibling lifecycle. This child releases its own lease, and it
|
|
25
|
+
* owns its own pause (work-pause spec): a failed model request of the agent loop
|
|
26
|
+
* with a cause of a pause, or a refusal with the suspend flag of the register
|
|
27
|
+
* gate. The child waits in place, and the run row stays `running`. The other
|
|
28
|
+
* steps go on, and each one pauses at its own failure. A refused spawn pauses
|
|
29
|
+
* the lease workflow under the run, not this child.
|
|
28
30
|
*/
|
|
29
31
|
import type { TokenUsageRollup } from "@inflexa-ai/harness/contracts/usage.js";
|
|
30
32
|
import type { Querier } from "../state/db.js";
|
|
@@ -39,13 +41,11 @@ import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
|
39
41
|
import type { FileMetadataEntry } from "../execution/artifact-metadata.js";
|
|
40
42
|
import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
|
|
41
43
|
import type { StepSummary } from "../schemas/step-summary.js";
|
|
42
|
-
import type {
|
|
44
|
+
import type { SandboxLeases, BoundLease } from "../sandbox/leases.js";
|
|
43
45
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
44
46
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
45
47
|
import type { ArtifactRegistry } from "../execution/artifact-registry.js";
|
|
46
48
|
import { type StepFileMetadata } from "../execution/post-step-pipeline.js";
|
|
47
|
-
import type { ResourceSpec } from "../config/resource-limits.js";
|
|
48
|
-
import type { SandboxRef } from "../sandbox/types.js";
|
|
49
49
|
import { ProvenanceCollector } from "../provenance/collector.js";
|
|
50
50
|
import { type FileMetadataCell } from "../tools/sandbox/submit-file-metadata.js";
|
|
51
51
|
import type { TemplateBinding } from "../tools/knowledge/template.js";
|
|
@@ -89,15 +89,6 @@ export type SandboxStepInput = {
|
|
|
89
89
|
readonly image?: string;
|
|
90
90
|
/** Per-step env overrides forwarded to sandbox-server. */
|
|
91
91
|
readonly extraEnv?: Record<string, string>;
|
|
92
|
-
/** Planner-estimated CPU/memory/GPU request for this step's sandbox. */
|
|
93
|
-
readonly resources: ResourceSpec;
|
|
94
|
-
/**
|
|
95
|
-
* Per-step execution budget in seconds. The body translates this into the
|
|
96
|
-
* absolute unix-ms deadline that each exec of the step honours. Comes from the plan's `step.timeout` (defaulted on the parent
|
|
97
|
-
* side); falls back to `DEFAULT_STEP_TIMEOUT_SECONDS` when unset so a
|
|
98
|
-
* client that omits the field still gets a non-pathological ceiling.
|
|
99
|
-
*/
|
|
100
|
-
readonly timeoutSeconds?: number;
|
|
101
92
|
/**
|
|
102
93
|
* Durable `RunSession` derived by the parent via `forStep`. Carries the
|
|
103
94
|
* run-authorization credential, identity, scope, and `runFrame = { runId, stepId }`.
|
|
@@ -106,11 +97,6 @@ export type SandboxStepInput = {
|
|
|
106
97
|
*/
|
|
107
98
|
readonly runSession: SpawnSession;
|
|
108
99
|
};
|
|
109
|
-
/**
|
|
110
|
-
* Fallback when the plan step omits `timeout`. Plans that need 3-hour runs
|
|
111
|
-
* declare it explicitly.
|
|
112
|
-
*/
|
|
113
|
-
export declare const DEFAULT_STEP_TIMEOUT_SECONDS = 3600;
|
|
114
100
|
/**
|
|
115
101
|
* Deterministic `blocked_reason` for a loop that spent every iteration and
|
|
116
102
|
* persisted no output files (see the harness-sandbox-agents spec). Fixed prose,
|
|
@@ -170,15 +156,15 @@ export type PostStepArtifacts = {
|
|
|
170
156
|
};
|
|
171
157
|
/**
|
|
172
158
|
* Per-step coordinates the workflow body computes once and threads to the
|
|
173
|
-
* deps factory. The parent supplies the durable inputs; the
|
|
174
|
-
*
|
|
159
|
+
* deps factory. The parent supplies the durable inputs; the body supplies the
|
|
160
|
+
* lease of the step.
|
|
175
161
|
*/
|
|
176
162
|
export type SandboxAgentBuildContext = {
|
|
177
163
|
readonly input: SandboxStepInput;
|
|
178
164
|
readonly session: RunSession;
|
|
179
|
-
|
|
165
|
+
/** The lease of the step. The agent tools acquire, release, and run commands through it. */
|
|
166
|
+
readonly lease: BoundLease;
|
|
180
167
|
readonly stepWritePrefix: string;
|
|
181
|
-
readonly deadlineMs: () => number;
|
|
182
168
|
/**
|
|
183
169
|
* Step-scoped lineage collector. The body owns one per step and threads
|
|
184
170
|
* it both here (so `execute_command` feeds each exec's provenance frame)
|
|
@@ -227,7 +213,8 @@ export type SandboxStepDeps = {
|
|
|
227
213
|
readonly provider: ChatProvider;
|
|
228
214
|
/** Write-side embedder for the post-step vector index. */
|
|
229
215
|
readonly embedding: EmbeddingProvider;
|
|
230
|
-
|
|
216
|
+
/** The sandbox module. The step binds its lease, and releases it at the end. */
|
|
217
|
+
readonly leases: SandboxLeases;
|
|
231
218
|
/**
|
|
232
219
|
* External artifact registration + sync seam. The harness's post-step pipeline
|
|
233
220
|
* registers each step's outputs through it (filesystem index in the
|
|
@@ -277,8 +264,6 @@ export type PostStepContext = {
|
|
|
277
264
|
readonly fileMetadata: FileMetadataCell;
|
|
278
265
|
/** Absolute path to the step's writable artifact directory. */
|
|
279
266
|
readonly writePrefix: string;
|
|
280
|
-
/** Sandbox handle id — informational only, used in log lines. */
|
|
281
|
-
readonly sandboxId: string;
|
|
282
267
|
/** Same step-scoped collector the agent loop fed each exec frame into. */
|
|
283
268
|
readonly lineageCollector: ProvenanceCollector;
|
|
284
269
|
};
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* replays from the cache instead of re-issuing work):
|
|
7
7
|
*
|
|
8
8
|
* 1. `mark-running` — update `cortex_step_executions` to running.
|
|
9
|
-
* 2.
|
|
9
|
+
* 2. the lease — bind the lease of the step. The agent acquires
|
|
10
|
+
* its sandbox with `acquire_sandbox`.
|
|
10
11
|
* 3. `runLoop` — the harness agent loop. Every model call and
|
|
11
12
|
* tool call inside is its own durable step.
|
|
12
13
|
* 4. `generateFileMetadata`— the file-metadata follow-up request of the step agent.
|
|
@@ -16,15 +17,16 @@
|
|
|
16
17
|
* 7. `syncArtifacts` — register provenance + upload missing file_ids
|
|
17
18
|
* via the managed root (scoped to this run+step).
|
|
18
19
|
* 8. `vector-index` — index file descriptions + summary into pgvector.
|
|
19
|
-
* 9. `
|
|
20
|
+
* 9. `sandbox.release` — release the lease, which removes its machine.
|
|
20
21
|
* 10. `mark-terminal` — write the final status, emit `data-step-activity`
|
|
21
22
|
* onto the parent's DBOS stream.
|
|
22
23
|
*
|
|
23
|
-
* The parent owns sibling lifecycle. This child
|
|
24
|
-
* own pause (work-pause spec): a failed model request of the agent loop
|
|
25
|
-
* cause of a pause, or a refusal with the suspend flag of the
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* The parent owns sibling lifecycle. This child releases its own lease, and it
|
|
25
|
+
* owns its own pause (work-pause spec): a failed model request of the agent loop
|
|
26
|
+
* with a cause of a pause, or a refusal with the suspend flag of the register
|
|
27
|
+
* gate. The child waits in place, and the run row stays `running`. The other
|
|
28
|
+
* steps go on, and each one pauses at its own failure. A refused spawn pauses
|
|
29
|
+
* the lease workflow under the run, not this child.
|
|
28
30
|
*/
|
|
29
31
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
30
32
|
import { isWorkflowCancellation } from "../runtime/dbos.js";
|
|
@@ -34,7 +36,7 @@ import { createNoopLogger } from "../lib/console-logger.js";
|
|
|
34
36
|
import { deliverNotice } from "../lib/hooks.js";
|
|
35
37
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
36
38
|
import { maskExcept } from "../loop/call-policy.js";
|
|
37
|
-
import { runLoop } from "../loop/run-loop.js";
|
|
39
|
+
import { runLoop, withRunScript } from "../loop/run-loop.js";
|
|
38
40
|
import { READ_TOOL_OUTPUT_TOOL_ID } from "../loop/tool-output.js";
|
|
39
41
|
import { lastExecOutcome } from "../sandbox/exec-outcome.js";
|
|
40
42
|
import { activityEmission, activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta, stepPartId, toolActivityKey, } from "../sandbox/sandbox-step-translate.js";
|
|
@@ -42,17 +44,10 @@ import { createDetailResolver } from "../tools/detail-resolver.js";
|
|
|
42
44
|
import { forSubAgent } from "../auth/types.js";
|
|
43
45
|
import { walkStepArtifacts } from "../execution/post-step.js";
|
|
44
46
|
import { collectStepOutputs, describeStepRegistrationFailure, generateStepFileMetadata, generateStepSummaryAndWrite, reconcileAndRegisterStepArtifacts, vectorIndexStepOutputs, } from "../execution/post-step-pipeline.js";
|
|
45
|
-
import { mintSandboxIdentity } from "../sandbox/identity.js";
|
|
46
|
-
import { keepSuspendingRefusal } from "../sandbox/sandbox-error.js";
|
|
47
47
|
import { ProvenanceCollector } from "../provenance/collector.js";
|
|
48
48
|
import { blockerOf } from "../tools/sandbox/report-blocker.js";
|
|
49
49
|
import { createFileMetadataCell, SUBMIT_FILE_METADATA_TOOL_ID } from "../tools/sandbox/submit-file-metadata.js";
|
|
50
50
|
import { pauseAtGate, workPause } from "./pause.js";
|
|
51
|
-
/**
|
|
52
|
-
* Fallback when the plan step omits `timeout`. Plans that need 3-hour runs
|
|
53
|
-
* declare it explicitly.
|
|
54
|
-
*/
|
|
55
|
-
export const DEFAULT_STEP_TIMEOUT_SECONDS = 3600;
|
|
56
51
|
/**
|
|
57
52
|
* Deterministic `blocked_reason` for a loop that spent every iteration and
|
|
58
53
|
* persisted no output files (see the harness-sandbox-agents spec). Fixed prose,
|
|
@@ -157,7 +152,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
157
152
|
logger.warn("emitToParentStream failed (non-fatal)", logger.errorFields(err));
|
|
158
153
|
}
|
|
159
154
|
};
|
|
160
|
-
// The step waits in place, and
|
|
155
|
+
// The step waits in place. The lease stays, and its machine goes away when the pause outlasts the idle period.
|
|
161
156
|
const pause = workPause({
|
|
162
157
|
pool: deps.pool,
|
|
163
158
|
workId: input.runId,
|
|
@@ -175,57 +170,23 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
175
170
|
const session = forSubAgent(input.runSession, input.agentId);
|
|
176
171
|
const writePrefix = await deps.resolveWritePrefix(input);
|
|
177
172
|
const lineageCollector = createLineageCollector(input);
|
|
178
|
-
// (
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const sandboxSpec = {
|
|
188
|
-
childWorkflowId,
|
|
189
|
-
image: input.image,
|
|
190
|
-
extraEnv: input.extraEnv,
|
|
191
|
-
resources: input.resources,
|
|
192
|
-
};
|
|
193
|
-
const spawn = () => DBOS.runStep(async () => keepSuspendingRefusal(await deps.sandboxClient.createSandbox(session, sandboxSpec, identity)), {
|
|
194
|
-
name: "sandbox.create",
|
|
173
|
+
// (2) The lease of the step. The agent acquires its sandbox with `acquire_sandbox` when it needs one, and the
|
|
174
|
+
// sandbox module owns the machine: the admission, the spawn, a new machine after a pause or a death, and the
|
|
175
|
+
// pause of a refused spawn. The lease key is the id of this workflow.
|
|
176
|
+
const lease = deps.leases.bind({
|
|
177
|
+
leaseKey: childWorkflowId,
|
|
178
|
+
session: input.runSession,
|
|
179
|
+
work: { kind: "run", id: input.runId },
|
|
180
|
+
spawn: { image: input.image, extraEnv: input.extraEnv },
|
|
181
|
+
emit: safeEmit,
|
|
195
182
|
});
|
|
196
|
-
let spawned = await spawn();
|
|
197
|
-
// A refusal of the label gate with the suspend flag waits in a pause, and the spawn runs again after the resume.
|
|
198
|
-
while (spawned.isErr()) {
|
|
199
|
-
await pauseAtGate(pause, spawned.error.reason);
|
|
200
|
-
spawned = await spawn();
|
|
201
|
-
}
|
|
202
|
-
const sandbox = spawned.value;
|
|
203
|
-
// Recovery path: re-check `isAlive` on the persisted ref before continuing.
|
|
204
|
-
// A classified-dead sandbox triggers a fresh `createSandbox` (task 4.6).
|
|
205
|
-
// For first execution this is a cheap no-op; on replay it's the seam
|
|
206
|
-
// that catches a dead pod between checkpoints.
|
|
207
|
-
await DBOS.runStep(async () => {
|
|
208
|
-
const liveness = await deps.sandboxClient.isAlive(sandbox);
|
|
209
|
-
if (!liveness.alive) {
|
|
210
|
-
const cause = liveness.oomKilled ? "was killed for exceeding its memory limit (sandbox-oom-killed)" : "no longer alive on replay";
|
|
211
|
-
throw new Error(`sandbox ${sandbox.sandboxId} ${cause} — caller must restart the step`);
|
|
212
|
-
}
|
|
213
|
-
}, { name: "sandbox.recheck-alive" });
|
|
214
183
|
// (3) runLoop — the harness loop, each call a durable step.
|
|
215
|
-
// The step deadline is captured ONCE at step start from the checkpointed
|
|
216
|
-
// clock so it reproduces on replay — a raw `Date.now()` here would grow the
|
|
217
|
-
// deadline on each recovery (see the harness-durable-runtime spec). `deadlineMs` is a getter so
|
|
218
|
-
// deps read the absolute ms without holding a captured value; returning a
|
|
219
|
-
// fresh `now + timeout` per call would make a step with N tool calls runnable
|
|
220
|
-
// for up to N × `timeoutSeconds`.
|
|
221
|
-
const stepDeadlineMs = (await DBOS.now()) + (input.timeoutSeconds ?? DEFAULT_STEP_TIMEOUT_SECONDS) * 1000;
|
|
222
184
|
const fileMetadata = createFileMetadataCell();
|
|
223
185
|
const agent = await deps.buildAgent({
|
|
224
186
|
input,
|
|
225
187
|
session,
|
|
226
|
-
|
|
188
|
+
lease,
|
|
227
189
|
stepWritePrefix: writePrefix,
|
|
228
|
-
deadlineMs: () => stepDeadlineMs,
|
|
229
190
|
lineageCollector,
|
|
230
191
|
reportsBlocker: true,
|
|
231
192
|
fileMetadata,
|
|
@@ -237,7 +198,9 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
237
198
|
}
|
|
238
199
|
// Built over THIS agent's tools, not a shared table: a sandbox agent's roster
|
|
239
200
|
// differs per agent type, and each tool describes its own calls.
|
|
240
|
-
|
|
201
|
+
// The declared tools and `run_script` describe the calls of the agent, and its script tools describe the nested calls.
|
|
202
|
+
const declared = withRunScript(agent).tools;
|
|
203
|
+
const resolveDetail = createDetailResolver([...declared, ...(agent.scriptTools ?? [])], logger);
|
|
241
204
|
// Forward every loop event onto the child's own DBOS `"events"` stream
|
|
242
205
|
// (the route fans-in parent + every active child stream — Pattern A).
|
|
243
206
|
// Pre-shaped chat data parts (e.g. `data-sandbox-event` from `run-exec.ts`)
|
|
@@ -275,7 +238,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
275
238
|
const failStep = async (errorClass, err) => {
|
|
276
239
|
if (isWorkflowCancellation(err))
|
|
277
240
|
throw err;
|
|
278
|
-
await
|
|
241
|
+
await tryRelease(logger, deps, childWorkflowId);
|
|
279
242
|
const durationMs = (await DBOS.now()) - startedAt;
|
|
280
243
|
// The scrub below is deliberate and load-bearing, which makes this record
|
|
281
244
|
// the ONLY account of why the step died — the thrown error carries just the
|
|
@@ -283,7 +246,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
283
246
|
// `durationMs`, the iteration cap, and the last exec of the sandbox are
|
|
284
247
|
// what separate a step that died in the model from one that died in the
|
|
285
248
|
// machine. `lastExec` holds no stream text (see `exec-outcome.ts`).
|
|
286
|
-
const lastExec = lastExecOutcome(
|
|
249
|
+
const lastExec = lastExecOutcome(childWorkflowId);
|
|
287
250
|
logger.error("step failure", {
|
|
288
251
|
errorClass,
|
|
289
252
|
durationMs,
|
|
@@ -369,7 +332,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
369
332
|
// The agent declares the output tool of the file-metadata follow-up
|
|
370
333
|
// request from its first request, and the task must not use it.
|
|
371
334
|
policy: {
|
|
372
|
-
mask: maskExcept(
|
|
335
|
+
mask: maskExcept(declared.map((tool) => tool.id), [SUBMIT_FILE_METADATA_TOOL_ID]),
|
|
373
336
|
},
|
|
374
337
|
// A blocker ends the step: the agent declared that it cannot do the work.
|
|
375
338
|
stopWhen: (done) => blockerOf(done) !== null,
|
|
@@ -435,7 +398,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
435
398
|
const cappedOutEmpty = hitMaxSteps && manifest.length === 0;
|
|
436
399
|
if (blockerOutcome || cappedOutEmpty) {
|
|
437
400
|
const blockedReason = blockerOutcome?.reason ?? CAPPED_OUT_EMPTY_REASON;
|
|
438
|
-
await
|
|
401
|
+
await tryRelease(logger, deps, childWorkflowId);
|
|
439
402
|
const durationMs = (await DBOS.now()) - startedAt;
|
|
440
403
|
await DBOS.runStep(async () => {
|
|
441
404
|
unwrapOrThrow(await updateStepExecution(deps.pool, input.runId, input.stepId, {
|
|
@@ -479,7 +442,6 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
479
442
|
agent,
|
|
480
443
|
fileMetadata,
|
|
481
444
|
writePrefix,
|
|
482
|
-
sandboxId: sandbox.sandboxId,
|
|
483
445
|
lineageCollector,
|
|
484
446
|
};
|
|
485
447
|
await emitActivity("generating-metadata", "Describing output files");
|
|
@@ -552,8 +514,8 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
552
514
|
const stepOutputs = collectStepOutputs(postCtx, postArtifacts);
|
|
553
515
|
await emitActivity("indexing", "Indexing outputs for search");
|
|
554
516
|
await safeRun(logger, () => vectorIndexStepOutputs(deps, postCtx, postArtifacts), "post-step.vector-index");
|
|
555
|
-
// (9)
|
|
556
|
-
await
|
|
517
|
+
// (9) release — the lease workflow removes the machine of the step.
|
|
518
|
+
await deps.leases.release(childWorkflowId);
|
|
557
519
|
// (10) mark-terminal + emit. The outcome metric is recorded inside the same
|
|
558
520
|
// step as the ledger row: a replayed body reads the cached step and records
|
|
559
521
|
// nothing again.
|
|
@@ -662,11 +624,14 @@ async function safeRunValue(logger, fn, label, fallback) {
|
|
|
662
624
|
return fallback;
|
|
663
625
|
}
|
|
664
626
|
}
|
|
665
|
-
|
|
627
|
+
/** Release the lease of a step that failed or blocked. The owner test of the lease workflow covers a failed release. */
|
|
628
|
+
async function tryRelease(logger, deps, leaseKey) {
|
|
666
629
|
try {
|
|
667
|
-
await
|
|
630
|
+
await deps.leases.release(leaseKey);
|
|
668
631
|
}
|
|
669
632
|
catch (err) {
|
|
670
|
-
|
|
633
|
+
if (isWorkflowCancellation(err))
|
|
634
|
+
throw err;
|
|
635
|
+
logger.warn("release failed (non-fatal)", logger.errorFields(err));
|
|
671
636
|
}
|
|
672
637
|
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
export declare const ANALYSIS_STATE_WRITE: RegExp;
|
|
8
8
|
export declare const PAUSE_RECORD_WRITE: RegExp;
|
|
9
9
|
export declare const PAUSE_CLEAR_WRITE: RegExp;
|
|
10
|
-
export declare const PAUSES_OF_WORK_CLEAR_WRITE: RegExp;
|
|
11
10
|
export declare const RUNS_STATUS_WRITE: RegExp;
|
|
12
11
|
export declare const RUNS_STATUS_WRITE_ANY_CASE: RegExp;
|
|
13
12
|
export declare const SYNTHESIS_STATUS_WRITE: RegExp;
|
|
@@ -10,8 +10,6 @@ export const ANALYSIS_STATE_WRITE = /UPDATE\s+cortex_analysis_state/i;
|
|
|
10
10
|
export const PAUSE_RECORD_WRITE = /INSERT INTO cortex_paused_work/i;
|
|
11
11
|
// oxlint-disable-next-line eslint-js/no-restricted-syntax -- the delete of the row of one waiting workflow
|
|
12
12
|
export const PAUSE_CLEAR_WRITE = /DELETE FROM cortex_paused_work WHERE workflow_id = \$1/i;
|
|
13
|
-
// oxlint-disable-next-line eslint-js/no-restricted-syntax -- the delete of each row of a pause of one work
|
|
14
|
-
export const PAUSES_OF_WORK_CLEAR_WRITE = /DELETE FROM cortex_paused_work WHERE work_id = \$1/i;
|
|
15
13
|
// oxlint-disable-next-line eslint-js/no-restricted-syntax -- the run status write, across wrapped SQL lines
|
|
16
14
|
export const RUNS_STATUS_WRITE = /UPDATE cortex_runs\s+SET status/;
|
|
17
15
|
// oxlint-disable-next-line eslint-js/no-restricted-syntax -- the run status write in any letter case, across wrapped SQL lines
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Reads resolve from the analysis's workspace root when the file is present and
|
|
7
7
|
* fall back to an embedder-supplied presigned URL otherwise. The seam writes no
|
|
8
|
-
* provenance — reads
|
|
9
|
-
* sandbox.
|
|
8
|
+
* provenance — a read tool records its reads in the lineage of a step itself —
|
|
9
|
+
* and does not depend on any sandbox.
|
|
10
10
|
*
|
|
11
11
|
* Scoping is enforced once, here, by `resolveWorkspacePath`: any path that
|
|
12
12
|
* escapes the analysis's resolved workspace root is rejected before any I/O.
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Reads resolve from the analysis's workspace root when the file is present and
|
|
7
7
|
* fall back to an embedder-supplied presigned URL otherwise. The seam writes no
|
|
8
|
-
* provenance — reads
|
|
9
|
-
* sandbox.
|
|
8
|
+
* provenance — a read tool records its reads in the lineage of a step itself —
|
|
9
|
+
* and does not depend on any sandbox.
|
|
10
10
|
*
|
|
11
11
|
* Scoping is enforced once, here, by `resolveWorkspacePath`: any path that
|
|
12
12
|
* escapes the analysis's resolved workspace root is rejected before any I/O.
|
|
@@ -37,7 +37,7 @@ import { scopeResource } from "../auth/types.js";
|
|
|
37
37
|
import { tryFetch, tryFs, errnoOf } from "../lib/fs-result.js";
|
|
38
38
|
import { splitLines } from "../lib/text.js";
|
|
39
39
|
import { classifyWithinRoot } from "../lib/fs-helpers.js";
|
|
40
|
-
import { resolveWorkspacePath } from "./paths.js";
|
|
40
|
+
import { HARNESS_PRIVATE_DIR, resolveWorkspacePath } from "./paths.js";
|
|
41
41
|
// ── Constructor ───────────────────────────────────────────────────────────
|
|
42
42
|
export function createWorkspaceFilesystem(deps) {
|
|
43
43
|
const { resolveWorkspaceRoot, presignedFallback } = deps;
|
|
@@ -77,7 +77,7 @@ export function createWorkspaceFilesystem(deps) {
|
|
|
77
77
|
op: "workspace.confineRealpath",
|
|
78
78
|
path: resolved.absolute,
|
|
79
79
|
cause,
|
|
80
|
-
})).map((verdict) => (verdict === "escaped" ? { kind: "out_of_scope" } : resolved));
|
|
80
|
+
})).map((verdict) => (verdict === "escaped" || isHarnessPrivate(resolved.relative) ? { kind: "out_of_scope" } : resolved));
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
return {
|
|
@@ -159,7 +159,8 @@ export function createWorkspaceFilesystem(deps) {
|
|
|
159
159
|
return tryFs("workspace.list", () => fsReaddir(absolute, { withFileTypes: true }), { path: absolute, onAbsent: () => ({ notFound: true }) }).andThen((dirents) => {
|
|
160
160
|
if ("notFound" in dirents)
|
|
161
161
|
return okAsync({ kind: "not_found" });
|
|
162
|
-
|
|
162
|
+
const visible = resolved.relative === "" ? dirents.filter((d) => d.name !== HARNESS_PRIVATE_DIR) : dirents;
|
|
163
|
+
return collectEntries(absolute, visible).map((entries) => ({ kind: "ok", entries }));
|
|
163
164
|
});
|
|
164
165
|
});
|
|
165
166
|
});
|
|
@@ -190,6 +191,10 @@ export function createWorkspaceFilesystem(deps) {
|
|
|
190
191
|
};
|
|
191
192
|
}
|
|
192
193
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
194
|
+
/** True for a root-relative path inside the private directory of the harness, which no workspace tool reaches. */
|
|
195
|
+
function isHarnessPrivate(relative) {
|
|
196
|
+
return relative === HARNESS_PRIVATE_DIR || relative.startsWith(`${HARNESS_PRIVATE_DIR}/`);
|
|
197
|
+
}
|
|
193
198
|
/** A bare `ok(value)` as a `ResultAsync`, with no `fs` call to wrap. */
|
|
194
199
|
function okAsync(value) {
|
|
195
200
|
return new ResultAsync(Promise.resolve(ok(value)));
|
|
@@ -181,6 +181,12 @@ export declare function toSandboxPath(workspaceRoot: string, resourceId: string,
|
|
|
181
181
|
* the resolver as `out_of_scope`. A prefix would only make it look canonical.
|
|
182
182
|
*/
|
|
183
183
|
export declare function toAnalysisRootPath(resourceId: string, path: string): string;
|
|
184
|
+
/**
|
|
185
|
+
* Where the embedder stages the inputs of an analysis, workspace-root-relative
|
|
186
|
+
* (see the data-profile-init spec). The inputs are immutable: no file tool of the
|
|
187
|
+
* harness writes under this directory.
|
|
188
|
+
*/
|
|
189
|
+
export declare const STAGED_INPUT_ROOT = "data/inputs";
|
|
184
190
|
/**
|
|
185
191
|
* Parent of every report session, workspace-root-relative. Callers that
|
|
186
192
|
* enumerate the sessions join this. Callers that address one join
|
|
@@ -199,10 +205,17 @@ export declare function reportSessionDir(threadId: string): string;
|
|
|
199
205
|
* Directory of the derived tables of one report session, workspace-root-relative.
|
|
200
206
|
*
|
|
201
207
|
* The derivation writes its output here, and the record prunes an output that the recorded document does
|
|
202
|
-
* not name. Both spell the segment through this helper, thus the write
|
|
208
|
+
* not name. Both spell the segment through this helper, thus the write and the prune scope cannot
|
|
203
209
|
* disagree.
|
|
204
210
|
*/
|
|
205
211
|
export declare function reportSessionDerivedDir(threadId: string): string;
|
|
212
|
+
/** Parent of the scratch directory of each conversation, workspace-root-relative. */
|
|
213
|
+
export declare const CONVERSATION_SESSIONS_ROOT = "conversation-sessions";
|
|
214
|
+
/**
|
|
215
|
+
* The scratch directory of one conversation, workspace-root-relative: the scripts that its turns ran, and the
|
|
216
|
+
* files that those scripts derived.
|
|
217
|
+
*/
|
|
218
|
+
export declare function conversationSessionDir(threadId: string): string;
|
|
206
219
|
/**
|
|
207
220
|
* True when `value` is a safe single path segment: the safe-id charset (word
|
|
208
221
|
* chars, `.`, `-` — no slash, NUL, or shell-hostile char) AND not a pure-dot
|
|
@@ -221,6 +234,11 @@ export declare function isSafeId(value: string): boolean;
|
|
|
221
234
|
* (the container-path source of truth) validate the same way.
|
|
222
235
|
*/
|
|
223
236
|
export declare function assertSafeId(value: string, label: string): void;
|
|
237
|
+
/**
|
|
238
|
+
* The directory of the harness under each workspace root: the exec records of the sandbox leases. No workspace tool
|
|
239
|
+
* lists or reads it, and no artifact walk reaches it.
|
|
240
|
+
*/
|
|
241
|
+
export declare const HARNESS_PRIVATE_DIR = ".sandbox";
|
|
224
242
|
/** Root directory for a specific workflow run, workspace-root-relative. */
|
|
225
243
|
export declare function runDir(runId: string): string;
|
|
226
244
|
/** Step directory within a run, workspace-root-relative. */
|
package/dist/workspace/paths.js
CHANGED
|
@@ -197,6 +197,12 @@ function normalizeTail(tail) {
|
|
|
197
197
|
return "";
|
|
198
198
|
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Where the embedder stages the inputs of an analysis, workspace-root-relative
|
|
202
|
+
* (see the data-profile-init spec). The inputs are immutable: no file tool of the
|
|
203
|
+
* harness writes under this directory.
|
|
204
|
+
*/
|
|
205
|
+
export const STAGED_INPUT_ROOT = "data/inputs";
|
|
200
206
|
/**
|
|
201
207
|
* Parent of every report session, workspace-root-relative. Callers that
|
|
202
208
|
* enumerate the sessions join this. Callers that address one join
|
|
@@ -218,12 +224,22 @@ export function reportSessionDir(threadId) {
|
|
|
218
224
|
* Directory of the derived tables of one report session, workspace-root-relative.
|
|
219
225
|
*
|
|
220
226
|
* The derivation writes its output here, and the record prunes an output that the recorded document does
|
|
221
|
-
* not name. Both spell the segment through this helper, thus the write
|
|
227
|
+
* not name. Both spell the segment through this helper, thus the write and the prune scope cannot
|
|
222
228
|
* disagree.
|
|
223
229
|
*/
|
|
224
230
|
export function reportSessionDerivedDir(threadId) {
|
|
225
231
|
return `${reportSessionDir(threadId)}/derived`;
|
|
226
232
|
}
|
|
233
|
+
/** Parent of the scratch directory of each conversation, workspace-root-relative. */
|
|
234
|
+
export const CONVERSATION_SESSIONS_ROOT = "conversation-sessions";
|
|
235
|
+
/**
|
|
236
|
+
* The scratch directory of one conversation, workspace-root-relative: the scripts that its turns ran, and the
|
|
237
|
+
* files that those scripts derived.
|
|
238
|
+
*/
|
|
239
|
+
export function conversationSessionDir(threadId) {
|
|
240
|
+
assertSafeId(threadId, "threadId");
|
|
241
|
+
return `${CONVERSATION_SESSIONS_ROOT}/${threadId}`;
|
|
242
|
+
}
|
|
227
243
|
/**
|
|
228
244
|
* For an analysis-rooted input (`/{analysisId}/...`), strip the leading
|
|
229
245
|
* `/{analysisId}/` segment so it can be resolved relative to the analysis
|
|
@@ -275,6 +291,11 @@ export function assertSafeId(value, label) {
|
|
|
275
291
|
throw new Error(`Invalid ${label}: ${value}`);
|
|
276
292
|
}
|
|
277
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* The directory of the harness under each workspace root: the exec records of the sandbox leases. No workspace tool
|
|
296
|
+
* lists or reads it, and no artifact walk reaches it.
|
|
297
|
+
*/
|
|
298
|
+
export const HARNESS_PRIVATE_DIR = ".sandbox";
|
|
278
299
|
/** Root directory for a specific workflow run, workspace-root-relative. */
|
|
279
300
|
export function runDir(runId) {
|
|
280
301
|
assertSafeId(runId, "runId");
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files that a session writes outside a run, and the host hook that keeps a copy of each.
|
|
3
|
+
*
|
|
4
|
+
* A run registers its step files through `ArtifactRegistry`. A chat thread and a report session write files too:
|
|
5
|
+
* the scripts and the derived files of `run_script`, and the files of `write_file` and `edit_file`. A host that
|
|
6
|
+
* keeps the files of an analysis elsewhere binds `syncSessionFile`, and each write reaches it with its bytes. The
|
|
7
|
+
* call runs inside the tool step of the write, thus a step that runs again after a crash calls the hook again with
|
|
8
|
+
* the same file.
|
|
9
|
+
*/
|
|
10
|
+
import type { ResultAsync } from "neverthrow";
|
|
11
|
+
import type { AgentSession } from "../auth/types.js";
|
|
12
|
+
import { type NoticeFailure } from "../lib/hooks.js";
|
|
13
|
+
import type { Logger } from "../lib/logger.js";
|
|
14
|
+
/** Who owns a session file. A file in the scratch directory of a thread belongs to that thread. */
|
|
15
|
+
export type SessionFileOwner = {
|
|
16
|
+
readonly kind: "thread";
|
|
17
|
+
readonly threadId: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "analysis";
|
|
20
|
+
};
|
|
21
|
+
export type SessionFileWrite = {
|
|
22
|
+
/** The session of the tool call that wrote the file. */
|
|
23
|
+
readonly session: AgentSession;
|
|
24
|
+
readonly owner: SessionFileOwner;
|
|
25
|
+
/** The analysis-relative path, and the hash and the size of the bytes. */
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly hash: string;
|
|
28
|
+
readonly size: number;
|
|
29
|
+
/** The exact bytes that landed. */
|
|
30
|
+
readonly content: Uint8Array;
|
|
31
|
+
};
|
|
32
|
+
/** A notice: a failure is logged, and the write stays done. */
|
|
33
|
+
export type SyncSessionFile = (write: SessionFileWrite) => ResultAsync<void, NoticeFailure>;
|
|
34
|
+
/** Give one landed write to the hook of the host. An unbound hook does nothing. */
|
|
35
|
+
export declare function syncWrittenFile(sync: SyncSessionFile | undefined, logger: Logger, write: SessionFileWrite): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files that a session writes outside a run, and the host hook that keeps a copy of each.
|
|
3
|
+
*
|
|
4
|
+
* A run registers its step files through `ArtifactRegistry`. A chat thread and a report session write files too:
|
|
5
|
+
* the scripts and the derived files of `run_script`, and the files of `write_file` and `edit_file`. A host that
|
|
6
|
+
* keeps the files of an analysis elsewhere binds `syncSessionFile`, and each write reaches it with its bytes. The
|
|
7
|
+
* call runs inside the tool step of the write, thus a step that runs again after a crash calls the hook again with
|
|
8
|
+
* the same file.
|
|
9
|
+
*/
|
|
10
|
+
import { deliverNotice } from "../lib/hooks.js";
|
|
11
|
+
/** Give one landed write to the hook of the host. An unbound hook does nothing. */
|
|
12
|
+
export async function syncWrittenFile(sync, logger, write) {
|
|
13
|
+
if (sync === undefined)
|
|
14
|
+
return;
|
|
15
|
+
await deliverNotice(logger.with({ analysisId: write.session.scope.analysisId, path: write.path }), "syncSessionFile", sync(write));
|
|
16
|
+
}
|