@assemblyline-agents/runtime 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/a2a-client.d.ts +22 -0
- package/dist/a2a-client.d.ts.map +1 -0
- package/dist/a2a-client.js +211 -0
- package/dist/a2a-client.js.map +1 -0
- package/dist/agent-hook-context.d.ts +6 -1
- package/dist/agent-hook-context.d.ts.map +1 -1
- package/dist/agent-hook-context.js +24 -11
- package/dist/agent-hook-context.js.map +1 -1
- package/dist/agent-hook-evaluation.d.ts +6 -1
- package/dist/agent-hook-evaluation.d.ts.map +1 -1
- package/dist/agent-hook-evaluation.js +16 -5
- package/dist/agent-hook-evaluation.js.map +1 -1
- package/dist/agent-hook-run-lifecycle.d.ts +5 -1
- package/dist/agent-hook-run-lifecycle.d.ts.map +1 -1
- package/dist/agent-hook-run-lifecycle.js +11 -13
- package/dist/agent-hook-run-lifecycle.js.map +1 -1
- package/dist/agent-surface.d.ts +32 -0
- package/dist/agent-surface.d.ts.map +1 -0
- package/dist/agent-surface.js +85 -0
- package/dist/agent-surface.js.map +1 -0
- package/dist/attachments.d.ts +5 -1
- package/dist/attachments.d.ts.map +1 -1
- package/dist/attachments.js +58 -32
- package/dist/attachments.js.map +1 -1
- package/dist/audience.d.ts +30 -0
- package/dist/audience.d.ts.map +1 -0
- package/dist/audience.js +68 -0
- package/dist/audience.js.map +1 -0
- package/dist/automation-event-match.d.ts +8 -0
- package/dist/automation-event-match.d.ts.map +1 -0
- package/dist/automation-event-match.js +25 -0
- package/dist/automation-event-match.js.map +1 -0
- package/dist/background-review-trigger.d.ts.map +1 -1
- package/dist/background-review-trigger.js +14 -16
- package/dist/background-review-trigger.js.map +1 -1
- package/dist/background-review-types.d.ts +9 -2
- package/dist/background-review-types.d.ts.map +1 -1
- package/dist/background-review-worker.js +2 -2
- package/dist/background-review-worker.js.map +1 -1
- package/dist/background-review.d.ts.map +1 -1
- package/dist/background-review.js +27 -10
- package/dist/background-review.js.map +1 -1
- package/dist/builtin-tool-executors.d.ts +47 -21
- package/dist/builtin-tool-executors.d.ts.map +1 -1
- package/dist/builtin-tool-executors.js +293 -121
- package/dist/builtin-tool-executors.js.map +1 -1
- package/dist/builtin-tools.d.ts +16 -8
- package/dist/builtin-tools.d.ts.map +1 -1
- package/dist/builtin-tools.js +309 -70
- package/dist/builtin-tools.js.map +1 -1
- package/dist/capability-catalog.d.ts +3 -2
- package/dist/capability-catalog.d.ts.map +1 -1
- package/dist/capability-catalog.js +10 -6
- package/dist/capability-catalog.js.map +1 -1
- package/dist/capability-inspection.d.ts +31 -0
- package/dist/capability-inspection.d.ts.map +1 -0
- package/dist/capability-inspection.js +58 -0
- package/dist/capability-inspection.js.map +1 -0
- package/dist/channel-http-util.d.ts.map +1 -1
- package/dist/channel-http-util.js +6 -2
- package/dist/channel-http-util.js.map +1 -1
- package/dist/channel-ingress.d.ts +16 -1
- package/dist/channel-ingress.d.ts.map +1 -1
- package/dist/channel-ingress.js +117 -7
- package/dist/channel-ingress.js.map +1 -1
- package/dist/channel.d.ts +58 -2
- package/dist/channel.d.ts.map +1 -1
- package/dist/checkpoints.d.ts +1 -1
- package/dist/checkpoints.d.ts.map +1 -1
- package/dist/checkpoints.js +6 -6
- package/dist/checkpoints.js.map +1 -1
- package/dist/child-process-env.d.ts +7 -0
- package/dist/child-process-env.d.ts.map +1 -0
- package/dist/child-process-env.js +41 -0
- package/dist/child-process-env.js.map +1 -0
- package/dist/computer-relay-protocol.d.ts.map +1 -1
- package/dist/computer-relay-protocol.js +4 -4
- package/dist/computer-relay-protocol.js.map +1 -1
- package/dist/concurrency.d.ts +1 -1
- package/dist/concurrency.js +1 -1
- package/dist/connection-auth-util.d.ts +1 -0
- package/dist/connection-auth-util.d.ts.map +1 -1
- package/dist/connection-auth-util.js +1 -1
- package/dist/connection-auth-util.js.map +1 -1
- package/dist/connection-clients.d.ts +11 -3
- package/dist/connection-clients.d.ts.map +1 -1
- package/dist/connection-clients.js +71 -8
- package/dist/connection-clients.js.map +1 -1
- package/dist/connection-event-store.d.ts +98 -0
- package/dist/connection-event-store.d.ts.map +1 -0
- package/dist/connection-event-store.js +162 -0
- package/dist/connection-event-store.js.map +1 -0
- package/dist/connection-events.d.ts +51 -0
- package/dist/connection-events.d.ts.map +1 -0
- package/dist/connection-events.js +343 -0
- package/dist/connection-events.js.map +1 -0
- package/dist/connection-stores.d.ts +3 -0
- package/dist/connection-stores.d.ts.map +1 -1
- package/dist/connection-stores.js +4 -1
- package/dist/connection-stores.js.map +1 -1
- package/dist/connection-util.d.ts +3 -2
- package/dist/connection-util.d.ts.map +1 -1
- package/dist/connection-util.js +18 -1
- package/dist/connection-util.js.map +1 -1
- package/dist/connections.d.ts +54 -5
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +342 -53
- package/dist/connections.js.map +1 -1
- package/dist/context-policy-util.d.ts.map +1 -1
- package/dist/context-policy-util.js +1 -3
- package/dist/context-policy-util.js.map +1 -1
- package/dist/conversation-context.d.ts +6 -2
- package/dist/conversation-context.d.ts.map +1 -1
- package/dist/conversation-context.js +45 -12
- package/dist/conversation-context.js.map +1 -1
- package/dist/conversation-transcript.d.ts +8 -5
- package/dist/conversation-transcript.d.ts.map +1 -1
- package/dist/conversation-transcript.js +24 -5
- package/dist/conversation-transcript.js.map +1 -1
- package/dist/conversation-turn-coalescing.d.ts +22 -0
- package/dist/conversation-turn-coalescing.d.ts.map +1 -0
- package/dist/conversation-turn-coalescing.js +121 -0
- package/dist/conversation-turn-coalescing.js.map +1 -0
- package/dist/conversation-turn-dispatch.d.ts +5 -2
- package/dist/conversation-turn-dispatch.d.ts.map +1 -1
- package/dist/conversation-turn-dispatch.js +59 -22
- package/dist/conversation-turn-dispatch.js.map +1 -1
- package/dist/default-outbound-route.d.ts +29 -0
- package/dist/default-outbound-route.d.ts.map +1 -0
- package/dist/default-outbound-route.js +105 -0
- package/dist/default-outbound-route.js.map +1 -0
- package/dist/delivery-artifacts.d.ts +2 -0
- package/dist/delivery-artifacts.d.ts.map +1 -0
- package/dist/delivery-artifacts.js +34 -0
- package/dist/delivery-artifacts.js.map +1 -0
- package/dist/delivery-files.d.ts +5 -0
- package/dist/delivery-files.d.ts.map +1 -0
- package/dist/delivery-files.js +33 -0
- package/dist/delivery-files.js.map +1 -0
- package/dist/delivery-queue.d.ts +21 -1
- package/dist/delivery-queue.d.ts.map +1 -1
- package/dist/delivery-queue.js +144 -1
- package/dist/delivery-queue.js.map +1 -1
- package/dist/delivery-send.d.ts +2 -2
- package/dist/delivery-send.d.ts.map +1 -1
- package/dist/delivery-send.js +256 -100
- package/dist/delivery-send.js.map +1 -1
- package/dist/durable-files.d.ts +37 -0
- package/dist/durable-files.d.ts.map +1 -0
- package/dist/durable-files.js +63 -0
- package/dist/durable-files.js.map +1 -0
- package/dist/harness-loop.d.ts +6 -1
- package/dist/harness-loop.d.ts.map +1 -1
- package/dist/harness-loop.js +110 -25
- package/dist/harness-loop.js.map +1 -1
- package/dist/heic-transcode-worker.d.ts +2 -0
- package/dist/heic-transcode-worker.d.ts.map +1 -0
- package/dist/heic-transcode-worker.js +59 -0
- package/dist/heic-transcode-worker.js.map +1 -0
- package/dist/heic-transcode.d.ts +18 -0
- package/dist/heic-transcode.d.ts.map +1 -0
- package/dist/heic-transcode.js +99 -0
- package/dist/heic-transcode.js.map +1 -0
- package/dist/human-input-routing.d.ts +22 -0
- package/dist/human-input-routing.d.ts.map +1 -0
- package/dist/human-input-routing.js +26 -0
- package/dist/human-input-routing.js.map +1 -0
- package/dist/in-memory-conversation-turn-store.d.ts +3 -1
- package/dist/in-memory-conversation-turn-store.d.ts.map +1 -1
- package/dist/in-memory-conversation-turn-store.js +41 -2
- package/dist/in-memory-conversation-turn-store.js.map +1 -1
- package/dist/in-memory-run-store.d.ts +8 -0
- package/dist/in-memory-run-store.d.ts.map +1 -1
- package/dist/in-memory-run-store.js +28 -5
- package/dist/in-memory-run-store.js.map +1 -1
- package/dist/in-memory-sandbox-session-store.d.ts +2 -1
- package/dist/in-memory-sandbox-session-store.d.ts.map +1 -1
- package/dist/in-memory-sandbox-session-store.js +65 -1
- package/dist/in-memory-sandbox-session-store.js.map +1 -1
- package/dist/in-memory-small-stores.d.ts +18 -1
- package/dist/in-memory-small-stores.d.ts.map +1 -1
- package/dist/in-memory-small-stores.js +107 -1
- package/dist/in-memory-small-stores.js.map +1 -1
- package/dist/in-memory-workspace-store.d.ts +40 -0
- package/dist/in-memory-workspace-store.d.ts.map +1 -0
- package/dist/in-memory-workspace-store.js +370 -0
- package/dist/in-memory-workspace-store.js.map +1 -0
- package/dist/index.d.ts +73 -36
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +390 -258
- package/dist/index.js.map +1 -1
- package/dist/json-util.d.ts +8 -0
- package/dist/json-util.d.ts.map +1 -1
- package/dist/json-util.js +8 -0
- package/dist/json-util.js.map +1 -1
- package/dist/local-sandbox.d.ts +3 -2
- package/dist/local-sandbox.d.ts.map +1 -1
- package/dist/local-sandbox.js +69 -25
- package/dist/local-sandbox.js.map +1 -1
- package/dist/mcp-client.d.ts.map +1 -1
- package/dist/mcp-client.js +3 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/media-processor.d.ts +42 -0
- package/dist/media-processor.d.ts.map +1 -0
- package/dist/media-processor.js +2 -0
- package/dist/media-processor.js.map +1 -0
- package/dist/model-media.d.ts +13 -1
- package/dist/model-media.d.ts.map +1 -1
- package/dist/model-media.js +81 -18
- package/dist/model-media.js.map +1 -1
- package/dist/model-visible-interrupts.d.ts +10 -0
- package/dist/model-visible-interrupts.d.ts.map +1 -0
- package/dist/model-visible-interrupts.js +41 -0
- package/dist/model-visible-interrupts.js.map +1 -0
- package/dist/operator-controls.d.ts.map +1 -1
- package/dist/operator-controls.js +16 -3
- package/dist/operator-controls.js.map +1 -1
- package/dist/outbound-http-policy.d.ts +7 -0
- package/dist/outbound-http-policy.d.ts.map +1 -0
- package/dist/outbound-http-policy.js +155 -0
- package/dist/outbound-http-policy.js.map +1 -0
- package/dist/public-api.d.ts +21 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +19 -0
- package/dist/public-api.js.map +1 -0
- package/dist/replay-resume.d.ts +8 -0
- package/dist/replay-resume.d.ts.map +1 -1
- package/dist/replay-resume.js +141 -11
- package/dist/replay-resume.js.map +1 -1
- package/dist/resource-projection-util.d.ts +1 -1
- package/dist/resource-projection-util.d.ts.map +1 -1
- package/dist/resource-projection-util.js +21 -10
- package/dist/resource-projection-util.js.map +1 -1
- package/dist/resource-projection.d.ts +8 -1
- package/dist/resource-projection.d.ts.map +1 -1
- package/dist/resource-projection.js +229 -29
- package/dist/resource-projection.js.map +1 -1
- package/dist/run-heartbeat.d.ts +12 -2
- package/dist/run-heartbeat.d.ts.map +1 -1
- package/dist/run-heartbeat.js +31 -4
- package/dist/run-heartbeat.js.map +1 -1
- package/dist/run-outcome.d.ts +9 -5
- package/dist/run-outcome.d.ts.map +1 -1
- package/dist/run-outcome.js +14 -5
- package/dist/run-outcome.js.map +1 -1
- package/dist/run-recovery.d.ts +19 -3
- package/dist/run-recovery.d.ts.map +1 -1
- package/dist/run-recovery.js +205 -34
- package/dist/run-recovery.js.map +1 -1
- package/dist/run-replay.d.ts +35 -0
- package/dist/run-replay.d.ts.map +1 -0
- package/dist/run-replay.js +193 -0
- package/dist/run-replay.js.map +1 -0
- package/dist/run-result.d.ts +4 -0
- package/dist/run-result.d.ts.map +1 -0
- package/dist/run-result.js +18 -0
- package/dist/run-result.js.map +1 -0
- package/dist/run-timeline-query.d.ts +38 -0
- package/dist/run-timeline-query.d.ts.map +1 -0
- package/dist/run-timeline-query.js +99 -0
- package/dist/run-timeline-query.js.map +1 -0
- package/dist/runs-query.d.ts.map +1 -1
- package/dist/runs-query.js +11 -3
- package/dist/runs-query.js.map +1 -1
- package/dist/runtime-bundle.d.ts.map +1 -1
- package/dist/runtime-bundle.js +2 -0
- package/dist/runtime-bundle.js.map +1 -1
- package/dist/runtime-learning.d.ts +15 -13
- package/dist/runtime-learning.d.ts.map +1 -1
- package/dist/runtime-learning.js +79 -58
- package/dist/runtime-learning.js.map +1 -1
- package/dist/runtime-types.d.ts +68 -9
- package/dist/runtime-types.d.ts.map +1 -1
- package/dist/sandbox-fs.d.ts +8 -5
- package/dist/sandbox-fs.d.ts.map +1 -1
- package/dist/sandbox-fs.js +30 -31
- package/dist/sandbox-fs.js.map +1 -1
- package/dist/sandbox-handle.d.ts +25 -0
- package/dist/sandbox-handle.d.ts.map +1 -0
- package/dist/sandbox-handle.js +120 -0
- package/dist/sandbox-handle.js.map +1 -0
- package/dist/sandbox-session-identity.d.ts +14 -0
- package/dist/sandbox-session-identity.d.ts.map +1 -0
- package/dist/sandbox-session-identity.js +33 -0
- package/dist/sandbox-session-identity.js.map +1 -0
- package/dist/sandbox-session-key.d.ts.map +1 -1
- package/dist/sandbox-session-key.js +12 -9
- package/dist/sandbox-session-key.js.map +1 -1
- package/dist/sandbox-session-lifecycle.d.ts.map +1 -1
- package/dist/sandbox-session-lifecycle.js +187 -44
- package/dist/sandbox-session-lifecycle.js.map +1 -1
- package/dist/sandbox-snapshot-policy.js +3 -3
- package/dist/sandbox-snapshot-policy.js.map +1 -1
- package/dist/sandbox-state-sync.d.ts +4 -2
- package/dist/sandbox-state-sync.d.ts.map +1 -1
- package/dist/sandbox-state-sync.js +47 -15
- package/dist/sandbox-state-sync.js.map +1 -1
- package/dist/sandbox-sync-worker.d.ts +11 -2
- package/dist/sandbox-sync-worker.d.ts.map +1 -1
- package/dist/sandbox-sync-worker.js +143 -31
- package/dist/sandbox-sync-worker.js.map +1 -1
- package/dist/schedule-execution.d.ts.map +1 -1
- package/dist/schedule-execution.js +127 -23
- package/dist/schedule-execution.js.map +1 -1
- package/dist/sdk-api-client.d.ts +17 -0
- package/dist/sdk-api-client.d.ts.map +1 -0
- package/dist/sdk-api-client.js +55 -0
- package/dist/sdk-api-client.js.map +1 -0
- package/dist/security.js +2 -2
- package/dist/security.js.map +1 -1
- package/dist/self-improvement.d.ts +5 -1
- package/dist/self-improvement.d.ts.map +1 -1
- package/dist/self-improvement.js +28 -14
- package/dist/self-improvement.js.map +1 -1
- package/dist/skill-bundles.d.ts +55 -0
- package/dist/skill-bundles.d.ts.map +1 -0
- package/dist/skill-bundles.js +134 -0
- package/dist/skill-bundles.js.map +1 -0
- package/dist/source-loader.js +7 -7
- package/dist/source-loader.js.map +1 -1
- package/dist/state-store-helpers.d.ts +2 -0
- package/dist/state-store-helpers.d.ts.map +1 -1
- package/dist/state-store-helpers.js +11 -1
- package/dist/state-store-helpers.js.map +1 -1
- package/dist/state-stores.d.ts +1 -0
- package/dist/state-stores.d.ts.map +1 -1
- package/dist/state-stores.js +26 -7
- package/dist/state-stores.js.map +1 -1
- package/dist/state.d.ts +305 -5
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +41 -2
- package/dist/state.js.map +1 -1
- package/dist/storage.d.ts +47 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +158 -6
- package/dist/storage.js.map +1 -1
- package/dist/subagent-execution.d.ts +1 -1
- package/dist/subagent-execution.d.ts.map +1 -1
- package/dist/subagent-execution.js +10 -12
- package/dist/subagent-execution.js.map +1 -1
- package/dist/target-instructions.js +1 -1
- package/dist/target-instructions.js.map +1 -1
- package/dist/tool-apis.d.ts.map +1 -1
- package/dist/tool-apis.js +33 -9
- package/dist/tool-apis.js.map +1 -1
- package/dist/tool-execution.d.ts +22 -12
- package/dist/tool-execution.d.ts.map +1 -1
- package/dist/tool-execution.js +368 -238
- package/dist/tool-execution.js.map +1 -1
- package/dist/turn-media.d.ts +29 -0
- package/dist/turn-media.d.ts.map +1 -0
- package/dist/turn-media.js +233 -0
- package/dist/turn-media.js.map +1 -0
- package/dist/usage-reconciliation.d.ts +1 -1
- package/dist/usage-reconciliation.js +1 -1
- package/dist/workspace-id.d.ts +10 -0
- package/dist/workspace-id.d.ts.map +1 -0
- package/dist/workspace-id.js +55 -0
- package/dist/workspace-id.js.map +1 -0
- package/dist/workspace-manifest.d.ts +24 -0
- package/dist/workspace-manifest.d.ts.map +1 -0
- package/dist/workspace-manifest.js +111 -0
- package/dist/workspace-manifest.js.map +1 -0
- package/dist/workspace-operation-limiter.d.ts +16 -0
- package/dist/workspace-operation-limiter.d.ts.map +1 -0
- package/dist/workspace-operation-limiter.js +31 -0
- package/dist/workspace-operation-limiter.js.map +1 -0
- package/dist/workspace-operations.d.ts +145 -0
- package/dist/workspace-operations.d.ts.map +1 -0
- package/dist/workspace-operations.js +420 -0
- package/dist/workspace-operations.js.map +1 -0
- package/dist/workspace-runtime.d.ts +110 -0
- package/dist/workspace-runtime.d.ts.map +1 -0
- package/dist/workspace-runtime.js +240 -0
- package/dist/workspace-runtime.js.map +1 -0
- package/dist/workspace-search.d.ts +55 -0
- package/dist/workspace-search.d.ts.map +1 -0
- package/dist/workspace-search.js +262 -0
- package/dist/workspace-search.js.map +1 -0
- package/dist/workspace-service.d.ts +173 -0
- package/dist/workspace-service.d.ts.map +1 -0
- package/dist/workspace-service.js +619 -0
- package/dist/workspace-service.js.map +1 -0
- package/dist/workspace.d.ts +7 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +7 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { shellQuote } from "@assemblyline-agents/core";
|
|
3
|
+
import { resolveWorkspaceForRun, workspaceScopeKey } from "./workspace-id.js";
|
|
4
|
+
import { createWorkspaceManifest, DEFAULT_WORKSPACE_LIMITS, encodeWorkspaceManifest, normalizeWorkspaceRelativePath, parseWorkspaceManifest, scanWorkspaceEntries, workspaceManifestSha256 } from "./workspace-manifest.js";
|
|
5
|
+
import { buildWorkspaceSearchChunks, DEFAULT_WORKSPACE_SEARCH_LIMITS, directWorkspaceSearch, embedWorkspaceSearchQuery, mergeWorkspaceSearchResults } from "./workspace-search.js";
|
|
6
|
+
import { WorkspaceOperations } from "./workspace-operations.js";
|
|
7
|
+
import { SlidingWindowWorkspaceOperationLimiter } from "./workspace-operation-limiter.js";
|
|
8
|
+
import { RESERVED_WORKSPACE_DIRS } from "./sandbox-fs.js";
|
|
9
|
+
export class WorkspaceService {
|
|
10
|
+
host;
|
|
11
|
+
limits;
|
|
12
|
+
searchLimits;
|
|
13
|
+
operationLimiter;
|
|
14
|
+
maxCheckpoints;
|
|
15
|
+
constructor(host) {
|
|
16
|
+
this.host = host;
|
|
17
|
+
this.limits = host.limits ?? DEFAULT_WORKSPACE_LIMITS;
|
|
18
|
+
this.searchLimits = host.searchLimits ?? DEFAULT_WORKSPACE_SEARCH_LIMITS;
|
|
19
|
+
this.operationLimiter = host.operationLimiter ?? new SlidingWindowWorkspaceOperationLimiter();
|
|
20
|
+
this.maxCheckpoints = Math.max(1, Math.floor(host.maxCheckpoints ?? 1_000));
|
|
21
|
+
}
|
|
22
|
+
async resolveForRun(run) {
|
|
23
|
+
return resolveWorkspaceForRun(this.host.state, this.host.agentScope, run);
|
|
24
|
+
}
|
|
25
|
+
async loadManifest(workspace, versionId = workspace.headVersionId) {
|
|
26
|
+
if (versionId === null) {
|
|
27
|
+
return { manifest: createWorkspaceManifest(workspace.id, [], this.host.now().toISOString()) };
|
|
28
|
+
}
|
|
29
|
+
if (!versionId)
|
|
30
|
+
versionId = workspace.headVersionId;
|
|
31
|
+
if (!versionId)
|
|
32
|
+
return { manifest: createWorkspaceManifest(workspace.id, [], this.host.now().toISOString()) };
|
|
33
|
+
const version = await this.host.state.getWorkspaceVersion(versionId);
|
|
34
|
+
if (!version || version.workspaceId !== workspace.id)
|
|
35
|
+
throw new Error(`Workspace version not found: ${versionId}`);
|
|
36
|
+
const bytes = await this.host.blob.get(version.manifestBlobKey);
|
|
37
|
+
if (!bytes)
|
|
38
|
+
throw new Error(`Workspace manifest blob is missing: ${version.manifestBlobKey}`);
|
|
39
|
+
const sha256 = workspaceManifestSha256(bytes);
|
|
40
|
+
if (sha256 !== version.manifestSha256)
|
|
41
|
+
throw new Error(`Workspace manifest hash mismatch: ${version.id}`);
|
|
42
|
+
const manifest = parseWorkspaceManifest(bytes);
|
|
43
|
+
if (manifest.workspaceId !== workspace.id && !await this.isInheritedForkManifest(workspace, version)) {
|
|
44
|
+
throw new Error(`Workspace manifest belongs to another workspace: ${version.id}`);
|
|
45
|
+
}
|
|
46
|
+
if (manifest.fileCount !== version.fileCount || manifest.totalBytes !== version.totalBytes) {
|
|
47
|
+
throw new Error(`Workspace version totals do not match its manifest: ${version.id}`);
|
|
48
|
+
}
|
|
49
|
+
return { version, manifest };
|
|
50
|
+
}
|
|
51
|
+
async hydrate(run, sandbox, sessionKey) {
|
|
52
|
+
const workspace = await this.resolveForRun(run);
|
|
53
|
+
this.operationLimiter.assertAllowed("hydrate", workspace.id, this.host.now());
|
|
54
|
+
const session = await this.host.state.getSandboxSession(sessionKey);
|
|
55
|
+
if (session?.dirty) {
|
|
56
|
+
const sessionWorkspaceId = stringValue(session.manifest.workspaceId);
|
|
57
|
+
if (sessionWorkspaceId !== workspace.id) {
|
|
58
|
+
throw new Error(`Dirty sandbox belongs to workspace ${sessionWorkspaceId ?? "unknown"}, not ${workspace.id}.`);
|
|
59
|
+
}
|
|
60
|
+
const current = await this.loadManifest(workspace, stringValue(session.manifest.baseVersionId) ?? workspace.headVersionId);
|
|
61
|
+
return { workspace, ...current, skippedDirty: true };
|
|
62
|
+
}
|
|
63
|
+
const current = await this.loadManifest(workspace);
|
|
64
|
+
this.assertVersionSize(current.manifest, "hydrate");
|
|
65
|
+
await clearWorkspaceTree(sandbox);
|
|
66
|
+
for (const entry of current.manifest.entries) {
|
|
67
|
+
const contents = await this.host.blob.get(entry.blobKey);
|
|
68
|
+
if (!contents)
|
|
69
|
+
throw new Error(`Workspace content blob is missing: ${entry.blobKey}`);
|
|
70
|
+
if (contents.byteLength !== entry.sizeBytes || workspaceManifestSha256(contents) !== entry.sha256) {
|
|
71
|
+
throw new Error(`Workspace content failed integrity verification: ${entry.path}`);
|
|
72
|
+
}
|
|
73
|
+
await sandbox.writeFile(`/workspace/${entry.path}`, contents);
|
|
74
|
+
if ((entry.mode & 0o111) !== 0) {
|
|
75
|
+
await sandbox.shell(`chmod 755 -- ${shellQuote(entry.path)}`, { cwd: "/workspace" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const manifest = {
|
|
79
|
+
...(session?.manifest ?? {}),
|
|
80
|
+
workspaceId: workspace.id,
|
|
81
|
+
baseVersionId: workspace.headVersionId ?? null,
|
|
82
|
+
workspaceManifestSha256: current.version?.manifestSha256 ?? null,
|
|
83
|
+
sandboxRoot: sandbox.root,
|
|
84
|
+
sessionKey
|
|
85
|
+
};
|
|
86
|
+
await this.host.state.updateSandboxSession(sessionKey, {
|
|
87
|
+
runId: run.id,
|
|
88
|
+
sandboxId: sandbox.id,
|
|
89
|
+
state: "clean",
|
|
90
|
+
dirty: false,
|
|
91
|
+
lastSeenAt: this.host.now().toISOString(),
|
|
92
|
+
manifest
|
|
93
|
+
});
|
|
94
|
+
await this.host.state.appendEvent(run.id, "workspace.hydrated", {
|
|
95
|
+
workspaceId: workspace.id,
|
|
96
|
+
versionId: workspace.headVersionId ?? null,
|
|
97
|
+
fileCount: current.manifest.fileCount,
|
|
98
|
+
totalBytes: current.manifest.totalBytes
|
|
99
|
+
});
|
|
100
|
+
return { workspace, ...current, skippedDirty: false };
|
|
101
|
+
}
|
|
102
|
+
async sync(input) {
|
|
103
|
+
const workspace = await this.host.state.getWorkspace(input.workspaceId);
|
|
104
|
+
if (!workspace)
|
|
105
|
+
throw new Error(`Workspace not found: ${input.workspaceId}`);
|
|
106
|
+
const selected = await this.resolveForRun(input.run);
|
|
107
|
+
if (selected.id !== workspace.id)
|
|
108
|
+
throw new Error(`Sandbox workspace does not match run workspace: ${workspace.id}`);
|
|
109
|
+
this.operationLimiter.assertAllowed("sync", workspace.id, this.host.now());
|
|
110
|
+
const base = await this.loadManifest(workspace, input.baseVersionId);
|
|
111
|
+
const rawEntries = await listWorkspaceFiles(input.sandbox);
|
|
112
|
+
const scanned = scanWorkspaceEntries(rawEntries, this.limits);
|
|
113
|
+
const baseByPath = new Map(base.manifest.entries.map((entry) => [entry.path, entry]));
|
|
114
|
+
const scannedPaths = new Set(scanned.map((entry) => entry.path));
|
|
115
|
+
const deletedPaths = base.manifest.entries.filter((entry) => !scannedPaths.has(entry.path)).map((entry) => entry.path);
|
|
116
|
+
const changedPaths = [];
|
|
117
|
+
const entries = [];
|
|
118
|
+
for (const file of scanned) {
|
|
119
|
+
const previous = baseByPath.get(file.path);
|
|
120
|
+
if (previous && previous.sha256 === file.sha256 && previous.mode === file.mode) {
|
|
121
|
+
entries.push(previous);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const blobKey = workspaceContentBlobKey(file.sha256);
|
|
125
|
+
const blob = await this.host.blob.put(blobKey, file.contents, { contentType: file.contentType, visibility: "private" });
|
|
126
|
+
if (blob.sha256 !== file.sha256 || blob.size !== file.sizeBytes)
|
|
127
|
+
throw new Error(`Workspace blob write failed integrity verification: ${file.path}`);
|
|
128
|
+
changedPaths.push(file.path);
|
|
129
|
+
entries.push({
|
|
130
|
+
path: file.path,
|
|
131
|
+
blobKey: blob.key,
|
|
132
|
+
sha256: file.sha256,
|
|
133
|
+
sizeBytes: file.sizeBytes,
|
|
134
|
+
contentType: file.contentType,
|
|
135
|
+
mode: file.mode
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const manifest = createWorkspaceManifest(workspace.id, entries, this.host.now().toISOString());
|
|
139
|
+
const manifestBytes = encodeWorkspaceManifest(manifest);
|
|
140
|
+
const manifestSha256 = workspaceManifestSha256(manifestBytes);
|
|
141
|
+
const manifestBlob = await this.host.blob.put(workspaceManifestBlobKey(manifestSha256), manifestBytes, {
|
|
142
|
+
contentType: "application/json",
|
|
143
|
+
visibility: "private"
|
|
144
|
+
});
|
|
145
|
+
if (manifestBlob.sha256 !== manifestSha256)
|
|
146
|
+
throw new Error(`Workspace manifest write failed integrity verification: ${workspace.id}`);
|
|
147
|
+
const committed = await this.host.state.commitWorkspaceVersion({
|
|
148
|
+
workspaceId: workspace.id,
|
|
149
|
+
expectedHeadVersionId: input.baseVersionId,
|
|
150
|
+
manifestBlobKey: manifestBlob.key,
|
|
151
|
+
manifestSha256,
|
|
152
|
+
fileCount: manifest.fileCount,
|
|
153
|
+
totalBytes: manifest.totalBytes,
|
|
154
|
+
reason: "sync",
|
|
155
|
+
runId: input.run.id,
|
|
156
|
+
idempotencyKey: `workspace-sync:${input.syncJobId}`,
|
|
157
|
+
metadata: { changedPaths, deletedPaths }
|
|
158
|
+
});
|
|
159
|
+
const committedManifest = committed.created
|
|
160
|
+
? manifest
|
|
161
|
+
: (await this.loadManifest(committed.workspace, committed.version.id)).manifest;
|
|
162
|
+
const committedChangedPaths = committed.created
|
|
163
|
+
? changedPaths
|
|
164
|
+
: stringArray(committed.version.metadata.changedPaths);
|
|
165
|
+
const committedDeletedPaths = committed.created
|
|
166
|
+
? deletedPaths
|
|
167
|
+
: stringArray(committed.version.metadata.deletedPaths);
|
|
168
|
+
await this.host.state.appendEvent(input.run.id, "workspace.version_created", {
|
|
169
|
+
workspaceId: workspace.id,
|
|
170
|
+
versionId: committed.version.id,
|
|
171
|
+
parentVersionId: committed.version.parentVersionId ?? null,
|
|
172
|
+
fileCount: committedManifest.fileCount,
|
|
173
|
+
totalBytes: committedManifest.totalBytes,
|
|
174
|
+
changedPaths: committedChangedPaths,
|
|
175
|
+
deletedPaths: committedDeletedPaths,
|
|
176
|
+
created: committed.created
|
|
177
|
+
});
|
|
178
|
+
await this.tryIndexVersion(workspace.id, committed.version.id, input.run.id);
|
|
179
|
+
return {
|
|
180
|
+
...committed,
|
|
181
|
+
manifest: committedManifest,
|
|
182
|
+
changedPaths: committedChangedPaths,
|
|
183
|
+
deletedPaths: committedDeletedPaths
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async status(workspaceId) {
|
|
187
|
+
const workspace = await this.requireWorkspace(workspaceId);
|
|
188
|
+
const versions = await this.host.state.listWorkspaceVersions(workspace.id, { limit: 10_000 });
|
|
189
|
+
const checkpoints = await this.host.state.listWorkspaceCheckpoints(workspace.id);
|
|
190
|
+
const status = { workspace, checkpoints, versionCount: versions.length };
|
|
191
|
+
if (workspace.headVersionId) {
|
|
192
|
+
const head = versions.find((version) => version.id === workspace.headVersionId)
|
|
193
|
+
?? await this.host.state.getWorkspaceVersion(workspace.headVersionId);
|
|
194
|
+
if (!head)
|
|
195
|
+
throw new Error(`Workspace head version is missing: ${workspace.headVersionId}`);
|
|
196
|
+
status.head = head;
|
|
197
|
+
}
|
|
198
|
+
return status;
|
|
199
|
+
}
|
|
200
|
+
async statusForRun(run) {
|
|
201
|
+
return this.status((await this.resolveForRun(run)).id);
|
|
202
|
+
}
|
|
203
|
+
retention(workspaceId, input = {}) {
|
|
204
|
+
return this.operations().retention(workspaceId, input);
|
|
205
|
+
}
|
|
206
|
+
reachability() {
|
|
207
|
+
return this.operations().reachability();
|
|
208
|
+
}
|
|
209
|
+
garbageCollect(input = {}) {
|
|
210
|
+
return this.operations().garbageCollect(input);
|
|
211
|
+
}
|
|
212
|
+
usage() {
|
|
213
|
+
return this.operations().usage();
|
|
214
|
+
}
|
|
215
|
+
diagnostics() {
|
|
216
|
+
return this.operations().diagnostics();
|
|
217
|
+
}
|
|
218
|
+
verify(workspaceId) {
|
|
219
|
+
return this.operations().verify(workspaceId);
|
|
220
|
+
}
|
|
221
|
+
repairBlob(input) {
|
|
222
|
+
return this.operations().repairBlob(input);
|
|
223
|
+
}
|
|
224
|
+
repairHead(input) {
|
|
225
|
+
return this.operations().repairHead(input);
|
|
226
|
+
}
|
|
227
|
+
async readFile(workspaceId, path, versionId) {
|
|
228
|
+
const workspace = await this.requireWorkspace(workspaceId);
|
|
229
|
+
const current = await this.loadManifest(workspace, versionId);
|
|
230
|
+
if (!current.version)
|
|
231
|
+
throw new Error(`Workspace has no committed version: ${workspace.id}`);
|
|
232
|
+
const normalized = normalizeWorkspaceRelativePath(path);
|
|
233
|
+
const entry = current.manifest.entries.find((candidate) => candidate.path === normalized);
|
|
234
|
+
if (!entry)
|
|
235
|
+
throw new Error(`Workspace file not found: ${normalized}`);
|
|
236
|
+
const contents = await this.host.blob.get(entry.blobKey);
|
|
237
|
+
if (!contents)
|
|
238
|
+
throw new Error(`Workspace content blob is missing: ${entry.blobKey}`);
|
|
239
|
+
if (contents.byteLength !== entry.sizeBytes || workspaceManifestSha256(contents) !== entry.sha256) {
|
|
240
|
+
throw new Error(`Workspace content failed integrity verification: ${entry.path}`);
|
|
241
|
+
}
|
|
242
|
+
return { workspaceId: workspace.id, versionId: current.version.id, entry, contents };
|
|
243
|
+
}
|
|
244
|
+
async listFiles(workspaceId, input = {}) {
|
|
245
|
+
const workspace = await this.requireWorkspace(workspaceId);
|
|
246
|
+
const current = await this.loadManifest(workspace, input.versionId);
|
|
247
|
+
if (!current.version)
|
|
248
|
+
throw new Error(`Workspace has no committed version: ${workspace.id}`);
|
|
249
|
+
const prefix = input.prefix ? normalizeWorkspaceRelativePath(input.prefix).replace(/\/$/u, "") : undefined;
|
|
250
|
+
const entries = prefix
|
|
251
|
+
? current.manifest.entries.filter((entry) => entry.path === prefix || entry.path.startsWith(`${prefix}/`))
|
|
252
|
+
: current.manifest.entries;
|
|
253
|
+
return { workspaceId: workspace.id, versionId: current.version.id, entries };
|
|
254
|
+
}
|
|
255
|
+
async grep(workspaceId, pattern, input = {}) {
|
|
256
|
+
if (!pattern || pattern.length > 1_000)
|
|
257
|
+
throw new Error("Workspace grep pattern must contain 1 to 1000 characters.");
|
|
258
|
+
const workspace = await this.requireWorkspace(workspaceId);
|
|
259
|
+
const current = await this.loadManifest(workspace, input.versionId);
|
|
260
|
+
if (!current.version)
|
|
261
|
+
throw new Error(`Workspace has no committed version: ${workspace.id}`);
|
|
262
|
+
const flags = input.caseSensitive === true ? "g" : "gi";
|
|
263
|
+
const source = input.literal === false ? pattern : escapeRegExp(pattern);
|
|
264
|
+
let matcher;
|
|
265
|
+
try {
|
|
266
|
+
matcher = new RegExp(source, flags);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
throw new Error(`Invalid workspace grep pattern: ${error instanceof Error ? error.message : String(error)}`);
|
|
270
|
+
}
|
|
271
|
+
const limit = boundedSearchLimit(input.limit, 100);
|
|
272
|
+
const results = [];
|
|
273
|
+
for (const entry of current.manifest.entries) {
|
|
274
|
+
if (!entry.contentType.startsWith("text/") && entry.contentType !== "application/json")
|
|
275
|
+
continue;
|
|
276
|
+
const file = await this.readFile(workspace.id, entry.path, current.version.id);
|
|
277
|
+
const lines = Buffer.from(file.contents).toString("utf8").split("\n");
|
|
278
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
279
|
+
const line = lines[index] ?? "";
|
|
280
|
+
matcher.lastIndex = 0;
|
|
281
|
+
const match = matcher.exec(line);
|
|
282
|
+
if (!match)
|
|
283
|
+
continue;
|
|
284
|
+
results.push({ path: entry.path, versionId: current.version.id, line: index + 1, column: match.index + 1, text: line });
|
|
285
|
+
if (results.length >= limit)
|
|
286
|
+
return { workspaceId: workspace.id, versionId: current.version.id, results };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { workspaceId: workspace.id, versionId: current.version.id, results };
|
|
290
|
+
}
|
|
291
|
+
async indexVersion(workspaceId, versionId) {
|
|
292
|
+
const workspace = await this.requireWorkspace(workspaceId);
|
|
293
|
+
const current = await this.loadManifest(workspace, versionId);
|
|
294
|
+
if (!current.version)
|
|
295
|
+
throw new Error(`Workspace has no committed version: ${workspace.id}`);
|
|
296
|
+
const built = await buildWorkspaceSearchChunks({
|
|
297
|
+
workspaceId: workspace.id,
|
|
298
|
+
versionId: current.version.id,
|
|
299
|
+
manifest: current.manifest,
|
|
300
|
+
blob: this.host.blob,
|
|
301
|
+
now: this.host.now(),
|
|
302
|
+
limits: this.searchLimits,
|
|
303
|
+
...(this.host.embeddingProvider ? { embeddingProvider: this.host.embeddingProvider } : {})
|
|
304
|
+
});
|
|
305
|
+
const index = await this.host.state.replaceWorkspaceSearchChunks({
|
|
306
|
+
workspaceId: workspace.id,
|
|
307
|
+
versionId: current.version.id,
|
|
308
|
+
chunks: built.chunks,
|
|
309
|
+
...(built.embeddingModel ? { embeddingModel: built.embeddingModel } : {})
|
|
310
|
+
});
|
|
311
|
+
return { ...index, excluded: built.excluded };
|
|
312
|
+
}
|
|
313
|
+
async search(workspaceId, query, input = {}) {
|
|
314
|
+
if (!query.trim() || query.length > 2_000)
|
|
315
|
+
throw new Error("Workspace search query must contain 1 to 2000 characters.");
|
|
316
|
+
const workspace = await this.requireWorkspace(workspaceId);
|
|
317
|
+
const current = await this.loadManifest(workspace, input.versionId);
|
|
318
|
+
if (!current.version)
|
|
319
|
+
throw new Error(`Workspace has no committed version: ${workspace.id}`);
|
|
320
|
+
const index = await this.host.state.getWorkspaceSearchIndex(workspace.id);
|
|
321
|
+
const indexStale = index?.versionId !== current.version.id;
|
|
322
|
+
if (indexStale) {
|
|
323
|
+
return {
|
|
324
|
+
workspaceId: workspace.id,
|
|
325
|
+
versionId: current.version.id,
|
|
326
|
+
...(index ? { indexedVersionId: index.versionId } : {}),
|
|
327
|
+
indexStale: true,
|
|
328
|
+
results: await directWorkspaceSearch({
|
|
329
|
+
workspaceId: workspace.id,
|
|
330
|
+
versionId: current.version.id,
|
|
331
|
+
manifest: current.manifest,
|
|
332
|
+
blob: this.host.blob,
|
|
333
|
+
query,
|
|
334
|
+
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
335
|
+
limits: this.searchLimits
|
|
336
|
+
})
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const fullText = await this.host.state.searchWorkspaceChunks({
|
|
340
|
+
workspaceId: workspace.id,
|
|
341
|
+
versionId: current.version.id,
|
|
342
|
+
query,
|
|
343
|
+
...(input.limit !== undefined ? { limit: input.limit } : {})
|
|
344
|
+
});
|
|
345
|
+
let results = fullText;
|
|
346
|
+
if (this.host.embeddingProvider && index.embeddingModel) {
|
|
347
|
+
try {
|
|
348
|
+
const embedded = await embedWorkspaceSearchQuery(this.host.embeddingProvider, query);
|
|
349
|
+
const semantic = await this.host.state.searchWorkspaceEmbeddings({
|
|
350
|
+
workspaceId: workspace.id,
|
|
351
|
+
versionId: current.version.id,
|
|
352
|
+
embedding: embedded.embedding,
|
|
353
|
+
model: embedded.model,
|
|
354
|
+
...(input.limit !== undefined ? { limit: input.limit } : {})
|
|
355
|
+
});
|
|
356
|
+
results = mergeWorkspaceSearchResults(fullText, semantic, input.limit);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
results = fullText;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
workspaceId: workspace.id,
|
|
364
|
+
versionId: current.version.id,
|
|
365
|
+
indexedVersionId: index.versionId,
|
|
366
|
+
indexStale: false,
|
|
367
|
+
results
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async createCheckpoint(input) {
|
|
371
|
+
const workspace = await this.requireWorkspace(input.workspaceId);
|
|
372
|
+
this.operationLimiter.assertAllowed("checkpoint", workspace.id, this.host.now());
|
|
373
|
+
if (!workspace.headVersionId)
|
|
374
|
+
throw new Error(`Workspace has no version to checkpoint: ${workspace.id}`);
|
|
375
|
+
const name = normalizeCheckpointName(input.name);
|
|
376
|
+
const head = await this.host.state.getWorkspaceVersion(workspace.headVersionId);
|
|
377
|
+
if (!head)
|
|
378
|
+
throw new Error(`Workspace head version is missing: ${workspace.headVersionId}`);
|
|
379
|
+
const current = await this.loadManifest(workspace, head.id);
|
|
380
|
+
this.assertVersionSize(current.manifest, "checkpoint");
|
|
381
|
+
const existingCheckpoint = await this.host.state.getWorkspaceCheckpoint(workspace.id, name);
|
|
382
|
+
if (!existingCheckpoint && (await this.host.state.listWorkspaceCheckpoints(workspace.id)).length >= this.maxCheckpoints) {
|
|
383
|
+
throw new Error(`Workspace checkpoint limit exceeded. Maximum is ${this.maxCheckpoints}.`);
|
|
384
|
+
}
|
|
385
|
+
const checkpoint = await this.host.state.createWorkspaceCheckpoint({
|
|
386
|
+
workspaceId: workspace.id,
|
|
387
|
+
name,
|
|
388
|
+
versionId: workspace.headVersionId,
|
|
389
|
+
...(input.replace !== undefined ? { replace: input.replace } : {})
|
|
390
|
+
});
|
|
391
|
+
if (input.runId) {
|
|
392
|
+
await this.host.state.appendEvent(input.runId, "workspace.checkpoint_created", {
|
|
393
|
+
workspaceId: workspace.id,
|
|
394
|
+
checkpointId: checkpoint.id,
|
|
395
|
+
name: checkpoint.name,
|
|
396
|
+
versionId: checkpoint.versionId,
|
|
397
|
+
replaced: input.replace === true
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return checkpoint;
|
|
401
|
+
}
|
|
402
|
+
async listCheckpoints(workspaceId) {
|
|
403
|
+
await this.requireWorkspace(workspaceId);
|
|
404
|
+
return this.host.state.listWorkspaceCheckpoints(workspaceId);
|
|
405
|
+
}
|
|
406
|
+
async getCheckpoint(workspaceId, name) {
|
|
407
|
+
await this.requireWorkspace(workspaceId);
|
|
408
|
+
return this.host.state.getWorkspaceCheckpoint(workspaceId, normalizeCheckpointName(name));
|
|
409
|
+
}
|
|
410
|
+
async deleteCheckpoint(input) {
|
|
411
|
+
await this.requireWorkspace(input.workspaceId);
|
|
412
|
+
const name = normalizeCheckpointName(input.name);
|
|
413
|
+
const deleted = await this.host.state.deleteWorkspaceCheckpoint(input.workspaceId, name);
|
|
414
|
+
if (deleted && input.runId) {
|
|
415
|
+
await this.host.state.appendEvent(input.runId, "workspace.checkpoint_deleted", {
|
|
416
|
+
workspaceId: input.workspaceId,
|
|
417
|
+
name
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return deleted;
|
|
421
|
+
}
|
|
422
|
+
async restoreCheckpoint(input) {
|
|
423
|
+
const workspace = await this.requireWorkspace(input.workspaceId);
|
|
424
|
+
this.operationLimiter.assertAllowed("restore", workspace.id, this.host.now());
|
|
425
|
+
const name = normalizeCheckpointName(input.name);
|
|
426
|
+
const checkpoint = await this.host.state.getWorkspaceCheckpoint(workspace.id, name);
|
|
427
|
+
if (!checkpoint)
|
|
428
|
+
throw new Error(`Workspace checkpoint not found: ${name}`);
|
|
429
|
+
const source = await this.host.state.getWorkspaceVersion(checkpoint.versionId);
|
|
430
|
+
if (!source || source.workspaceId !== workspace.id)
|
|
431
|
+
throw new Error(`Workspace checkpoint version is missing: ${checkpoint.versionId}`);
|
|
432
|
+
const current = await this.loadManifest(workspace, source.id);
|
|
433
|
+
this.assertVersionSize(current.manifest, "restore");
|
|
434
|
+
const restored = await this.host.state.commitWorkspaceVersion({
|
|
435
|
+
workspaceId: workspace.id,
|
|
436
|
+
expectedHeadVersionId: workspace.headVersionId ?? null,
|
|
437
|
+
manifestBlobKey: source.manifestBlobKey,
|
|
438
|
+
manifestSha256: source.manifestSha256,
|
|
439
|
+
fileCount: source.fileCount,
|
|
440
|
+
totalBytes: source.totalBytes,
|
|
441
|
+
reason: "restore",
|
|
442
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
443
|
+
sourceVersionId: source.id,
|
|
444
|
+
sourceCheckpointId: checkpoint.id,
|
|
445
|
+
idempotencyKey: input.idempotencyKey ?? `workspace-restore:${workspace.id}:${checkpoint.id}:${workspace.headVersionId ?? "empty"}:${randomUUID()}`,
|
|
446
|
+
metadata: { checkpointName: checkpoint.name }
|
|
447
|
+
});
|
|
448
|
+
if (input.runId) {
|
|
449
|
+
await this.host.state.appendEvent(input.runId, "workspace.restored", {
|
|
450
|
+
workspaceId: workspace.id,
|
|
451
|
+
checkpointId: checkpoint.id,
|
|
452
|
+
checkpointName: checkpoint.name,
|
|
453
|
+
sourceVersionId: source.id,
|
|
454
|
+
previousHeadVersionId: workspace.headVersionId ?? null,
|
|
455
|
+
versionId: restored.version.id,
|
|
456
|
+
created: restored.created
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
await this.tryIndexVersion(workspace.id, restored.version.id, input.runId);
|
|
460
|
+
return restored;
|
|
461
|
+
}
|
|
462
|
+
async fork(input) {
|
|
463
|
+
const sourceWorkspace = await this.requireWorkspace(input.sourceWorkspaceId);
|
|
464
|
+
this.operationLimiter.assertAllowed("fork", sourceWorkspace.id, this.host.now());
|
|
465
|
+
if (sourceWorkspace.scope.tenantId !== input.tenantId) {
|
|
466
|
+
throw new Error(`Workspace is outside the tenant scope: ${sourceWorkspace.id}`);
|
|
467
|
+
}
|
|
468
|
+
let sourceVersionId = input.sourceVersionId ?? sourceWorkspace.headVersionId;
|
|
469
|
+
if (input.checkpointName) {
|
|
470
|
+
const checkpoint = await this.host.state.getWorkspaceCheckpoint(sourceWorkspace.id, normalizeCheckpointName(input.checkpointName));
|
|
471
|
+
if (!checkpoint)
|
|
472
|
+
throw new Error(`Workspace checkpoint not found: ${input.checkpointName}`);
|
|
473
|
+
sourceVersionId = checkpoint.versionId;
|
|
474
|
+
}
|
|
475
|
+
if (!sourceVersionId)
|
|
476
|
+
throw new Error(`Workspace has no version to fork: ${sourceWorkspace.id}`);
|
|
477
|
+
const sourceVersion = await this.host.state.getWorkspaceVersion(sourceVersionId);
|
|
478
|
+
if (!sourceVersion || sourceVersion.workspaceId !== sourceWorkspace.id) {
|
|
479
|
+
throw new Error(`Workspace version does not belong to source workspace: ${sourceVersionId}`);
|
|
480
|
+
}
|
|
481
|
+
const current = await this.loadManifest(sourceWorkspace, sourceVersion.id);
|
|
482
|
+
this.assertVersionSize(current.manifest, "fork");
|
|
483
|
+
const workspaceId = input.workspaceId ?? randomUUID();
|
|
484
|
+
const scope = {
|
|
485
|
+
agentScope: this.host.agentScope,
|
|
486
|
+
kind: "explicit",
|
|
487
|
+
scopeId: workspaceId,
|
|
488
|
+
...(input.tenantId ? { tenantId: input.tenantId } : {})
|
|
489
|
+
};
|
|
490
|
+
const forked = await this.host.state.forkWorkspace({
|
|
491
|
+
id: workspaceId,
|
|
492
|
+
sourceWorkspaceId: sourceWorkspace.id,
|
|
493
|
+
sourceVersionId,
|
|
494
|
+
scope,
|
|
495
|
+
scopeKey: workspaceScopeKey(scope),
|
|
496
|
+
...(input.name ? { name: input.name } : {})
|
|
497
|
+
});
|
|
498
|
+
if (forked.headVersionId)
|
|
499
|
+
await this.tryIndexVersion(forked.id, forked.headVersionId);
|
|
500
|
+
return forked;
|
|
501
|
+
}
|
|
502
|
+
async tryIndexVersion(workspaceId, versionId, runId) {
|
|
503
|
+
try {
|
|
504
|
+
const indexed = await this.indexVersion(workspaceId, versionId);
|
|
505
|
+
if (runId) {
|
|
506
|
+
await this.host.state.appendEvent(runId, "workspace.indexed", {
|
|
507
|
+
workspaceId,
|
|
508
|
+
versionId,
|
|
509
|
+
chunkCount: indexed.chunkCount,
|
|
510
|
+
excludedCount: indexed.excluded.length,
|
|
511
|
+
embeddingModel: indexed.embeddingModel ?? null
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
if (runId) {
|
|
517
|
+
await this.host.state.appendEvent(runId, "workspace.index_failed", {
|
|
518
|
+
workspaceId,
|
|
519
|
+
versionId,
|
|
520
|
+
error: error instanceof Error ? error.message : String(error)
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async requireWorkspace(workspaceId) {
|
|
526
|
+
const workspace = await this.host.state.getWorkspace(workspaceId);
|
|
527
|
+
if (!workspace)
|
|
528
|
+
throw new Error(`Workspace not found: ${workspaceId}`);
|
|
529
|
+
if (workspace.scope.agentScope !== this.host.agentScope) {
|
|
530
|
+
throw new Error(`Workspace is outside the agent scope: ${workspace.id}`);
|
|
531
|
+
}
|
|
532
|
+
return workspace;
|
|
533
|
+
}
|
|
534
|
+
async isInheritedForkManifest(workspace, version) {
|
|
535
|
+
if (version.reason !== "fork" || !version.sourceVersionId || !workspace.sourceWorkspaceId)
|
|
536
|
+
return false;
|
|
537
|
+
const source = await this.host.state.getWorkspaceVersion(version.sourceVersionId);
|
|
538
|
+
return Boolean(source &&
|
|
539
|
+
source.workspaceId === workspace.sourceWorkspaceId &&
|
|
540
|
+
source.manifestBlobKey === version.manifestBlobKey &&
|
|
541
|
+
source.manifestSha256 === version.manifestSha256);
|
|
542
|
+
}
|
|
543
|
+
operations() {
|
|
544
|
+
return new WorkspaceOperations({
|
|
545
|
+
state: this.host.state,
|
|
546
|
+
blob: this.host.blob,
|
|
547
|
+
agentScope: this.host.agentScope,
|
|
548
|
+
now: this.host.now,
|
|
549
|
+
requireWorkspace: (workspaceId) => this.requireWorkspace(workspaceId),
|
|
550
|
+
loadManifest: (workspace, versionId) => this.loadManifest(workspace, versionId)
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
assertVersionSize(manifest, operation) {
|
|
554
|
+
if (manifest.fileCount > this.limits.maxFiles || manifest.totalBytes > this.limits.maxTotalBytes ||
|
|
555
|
+
manifest.entries.some((entry) => entry.sizeBytes > this.limits.maxFileBytes)) {
|
|
556
|
+
throw new Error(`Workspace ${operation} exceeds the configured file or byte limit.`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
export function workspaceContentBlobKey(sha256) {
|
|
561
|
+
return `workspaces/content/${sha256.slice(0, 2)}/${sha256}`;
|
|
562
|
+
}
|
|
563
|
+
export function workspaceManifestBlobKey(sha256) {
|
|
564
|
+
return `workspaces/manifests/${sha256.slice(0, 2)}/${sha256}.json`;
|
|
565
|
+
}
|
|
566
|
+
async function clearWorkspaceTree(sandbox) {
|
|
567
|
+
const entries = await listWorkspaceFiles(sandbox);
|
|
568
|
+
if (entries.length === 0)
|
|
569
|
+
return;
|
|
570
|
+
if (!sandbox.deletePath)
|
|
571
|
+
throw new Error("Sandbox adapter must support deletePath for durable workspace hydration.");
|
|
572
|
+
for (const entry of entries)
|
|
573
|
+
await sandbox.deletePath(`/workspace/${entry.path}`, { recursive: false });
|
|
574
|
+
}
|
|
575
|
+
async function listWorkspaceFiles(sandbox) {
|
|
576
|
+
if (!sandbox.listFiles)
|
|
577
|
+
throw new Error("Sandbox adapter must support listFiles for durable workspaces.");
|
|
578
|
+
const listed = await sandbox.listFiles("/workspace", {
|
|
579
|
+
excludeTopLevel: RESERVED_WORKSPACE_DIRS,
|
|
580
|
+
includeContents: true
|
|
581
|
+
});
|
|
582
|
+
const entries = [];
|
|
583
|
+
for (const entry of listed) {
|
|
584
|
+
const relative = relativeWorkspacePath(entry.path);
|
|
585
|
+
if (!relative || relative === ".keep")
|
|
586
|
+
continue;
|
|
587
|
+
entries.push({ ...entry, path: relative });
|
|
588
|
+
}
|
|
589
|
+
return entries;
|
|
590
|
+
}
|
|
591
|
+
function relativeWorkspacePath(path) {
|
|
592
|
+
const normalized = path.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
593
|
+
const relative = normalized === "/workspace"
|
|
594
|
+
? ""
|
|
595
|
+
: normalized.startsWith("/workspace/")
|
|
596
|
+
? normalized.slice("/workspace/".length)
|
|
597
|
+
: normalized.replace(/^\/+/, "");
|
|
598
|
+
return relative ? normalizeWorkspaceRelativePath(relative) : "";
|
|
599
|
+
}
|
|
600
|
+
function stringValue(value) {
|
|
601
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
602
|
+
}
|
|
603
|
+
function stringArray(value) {
|
|
604
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
605
|
+
}
|
|
606
|
+
function normalizeCheckpointName(value) {
|
|
607
|
+
const name = value.trim();
|
|
608
|
+
if (!name || name.length > 128 || /[\u0000-\u001f\u007f]/u.test(name)) {
|
|
609
|
+
throw new Error("Workspace checkpoint names must contain 1 to 128 visible characters.");
|
|
610
|
+
}
|
|
611
|
+
return name;
|
|
612
|
+
}
|
|
613
|
+
function escapeRegExp(value) {
|
|
614
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
615
|
+
}
|
|
616
|
+
function boundedSearchLimit(value, fallback) {
|
|
617
|
+
return Math.max(1, Math.min(1_000, Math.floor(value ?? fallback)));
|
|
618
|
+
}
|
|
619
|
+
//# sourceMappingURL=workspace-service.js.map
|