@the-open-engine/zeroshot 6.38.1 → 6.39.1
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 +6 -0
- package/cli/event-copy.js +8 -7
- package/cli/event-copy.ts +12 -0
- package/cli/index.js +67 -48
- package/cli/json-export.js +61 -64
- package/cli/json-export.ts +112 -0
- package/cli/message-formatter-utils.js +68 -56
- package/cli/message-formatter-utils.ts +121 -0
- package/cli/message-formatters-normal.js +124 -259
- package/cli/message-formatters-normal.ts +250 -0
- package/cli/message-formatters-watch.js +81 -84
- package/cli/message-formatters-watch.ts +189 -0
- package/lib/cluster/generated/protocol-schema.cjs +1 -1
- package/lib/cluster/generated/protocol-schema.mjs +1 -1
- package/lib/cluster/generated/protocol.d.cts +8 -0
- package/lib/cluster/generated/protocol.d.mts +8 -0
- package/lib/cluster/generated/protocol.d.ts +8 -0
- package/lib/clusters-registry.d.ts +23 -0
- package/lib/clusters-registry.js +17 -29
- package/lib/completion.d.ts +12 -0
- package/lib/completion.js +84 -94
- package/lib/compose-utils.d.ts +32 -0
- package/lib/compose-utils.js +57 -72
- package/lib/detached-startup.d.ts +84 -0
- package/lib/detached-startup.js +232 -302
- package/lib/docker-config.d.ts +43 -0
- package/lib/docker-config.js +150 -325
- package/lib/docker-env-auth.d.ts +17 -0
- package/lib/docker-env-auth.js +75 -0
- package/lib/git-remote-utils.d.ts +48 -0
- package/lib/git-remote-utils.js +147 -213
- package/lib/id-detector.d.ts +15 -0
- package/lib/id-detector.js +37 -41
- package/lib/path-check.d.ts +30 -0
- package/lib/path-check.js +42 -45
- package/lib/process-liveness.d.ts +16 -0
- package/lib/process-liveness.js +19 -12
- package/lib/provider-credential-path.d.ts +16 -0
- package/lib/provider-credential-path.js +31 -29
- package/lib/provider-defaults.d.ts +30 -0
- package/lib/provider-defaults.js +47 -42
- package/lib/provider-detection.d.ts +11 -0
- package/lib/provider-detection.js +91 -54
- package/lib/provider-names.d.ts +38 -0
- package/lib/provider-names.js +56 -64
- package/lib/repo-settings.d.ts +28 -0
- package/lib/repo-settings.js +83 -59
- package/lib/run-mode.d.ts +21 -0
- package/lib/run-mode.js +21 -20
- package/lib/run-plan.d.ts +28 -0
- package/lib/run-plan.js +17 -16
- package/lib/settings/claude-auth.d.ts +35 -0
- package/lib/settings/claude-auth.js +30 -47
- package/lib/settings-defaults.d.ts +20 -0
- package/lib/settings-defaults.js +149 -0
- package/lib/settings-error.d.ts +7 -0
- package/lib/settings-error.js +8 -0
- package/lib/settings-issue-providers.d.ts +10 -0
- package/lib/settings-issue-providers.js +12 -0
- package/lib/settings-models.d.ts +22 -0
- package/lib/settings-models.js +93 -0
- package/lib/settings-storage.d.ts +23 -0
- package/lib/settings-storage.js +205 -0
- package/lib/settings-validation.d.ts +7 -0
- package/lib/settings-validation.js +182 -0
- package/lib/settings.d.ts +29 -0
- package/lib/settings.js +41 -736
- package/lib/setup-apply-types.d.ts +66 -0
- package/lib/setup-apply-types.js +2 -0
- package/lib/setup-apply.d.ts +39 -0
- package/lib/setup-apply.js +267 -264
- package/lib/setup-journal.d.ts +41 -0
- package/lib/setup-journal.js +119 -76
- package/lib/setup-plan-types.d.ts +102 -0
- package/lib/setup-plan-types.js +2 -0
- package/lib/setup-plan.d.ts +30 -0
- package/lib/setup-plan.js +300 -324
- package/lib/setup-undo.d.ts +44 -0
- package/lib/setup-undo.js +61 -74
- package/lib/start-cluster-config.d.ts +26 -0
- package/lib/start-cluster-config.js +56 -0
- package/lib/start-cluster-environment.d.ts +41 -0
- package/lib/start-cluster-environment.js +134 -0
- package/lib/start-cluster-run-options.d.ts +54 -0
- package/lib/start-cluster-run-options.js +148 -0
- package/lib/start-cluster.d.ts +64 -0
- package/lib/start-cluster.js +98 -454
- package/lib/stream-json-parser.d.ts +17 -0
- package/lib/stream-json-parser.js +51 -52
- package/npm-shrinkwrap.json +6 -8
- package/package.json +14 -8
- package/scripts/postinstall.js +27 -0
- package/scripts/rust-distribution.js +36 -45
- package/src/agent/agent-command-proofs-context.js +35 -45
- package/src/agent/agent-command-proofs-context.ts +87 -0
- package/src/agent/agent-config-settings.js +19 -0
- package/src/agent/agent-config-settings.ts +41 -0
- package/src/agent/agent-config.js +122 -231
- package/src/agent/agent-config.ts +285 -0
- package/src/agent/agent-context-builder.js +38 -273
- package/src/agent/agent-context-builder.ts +51 -0
- package/src/agent/agent-context-environment-sections.js +105 -0
- package/src/agent/agent-context-environment-sections.ts +134 -0
- package/src/agent/agent-context-packs.js +165 -0
- package/src/agent/agent-context-packs.ts +296 -0
- package/src/agent/agent-context-prompt-sections.js +100 -0
- package/src/agent/agent-context-prompt-sections.ts +148 -0
- package/src/agent/agent-context-sections.js +25 -334
- package/src/agent/agent-context-sections.ts +36 -0
- package/src/agent/agent-context-sources.js +96 -149
- package/src/agent/agent-context-sources.ts +253 -0
- package/src/agent/agent-context-types.js +2 -0
- package/src/agent/agent-context-types.ts +132 -0
- package/src/agent/agent-context-validation-sections.js +73 -0
- package/src/agent/agent-context-validation-sections.ts +129 -0
- package/src/agent/agent-input-injector.js +99 -118
- package/src/agent/agent-input-injector.ts +195 -0
- package/src/agent/agent-liveness-poll.js +74 -78
- package/src/agent/agent-liveness-poll.ts +163 -0
- package/src/agent/agent-quality-gate-schema.js +77 -86
- package/src/agent/agent-quality-gate-schema.ts +135 -0
- package/src/agent/agent-quality-gates-context.js +44 -50
- package/src/agent/agent-quality-gates-context.ts +91 -0
- package/src/agent/agent-stuck-detector.js +171 -239
- package/src/agent/agent-stuck-detector.ts +288 -0
- package/src/agent/agent-trigger-evaluator.js +29 -46
- package/src/agent/agent-trigger-evaluator.ts +98 -0
- package/src/agent/context-metrics.js +110 -141
- package/src/agent/context-metrics.ts +261 -0
- package/src/agent/context-pack-builder.js +72 -358
- package/src/agent/context-pack-builder.ts +105 -0
- package/src/agent/context-pack-core.js +70 -0
- package/src/agent/context-pack-core.ts +96 -0
- package/src/agent/context-pack-selection.js +119 -0
- package/src/agent/context-pack-selection.ts +187 -0
- package/src/agent/context-pack-truncation.js +139 -0
- package/src/agent/context-pack-truncation.ts +208 -0
- package/src/agent/context-pack-types.js +2 -0
- package/src/agent/context-pack-types.ts +98 -0
- package/src/agent/context-replay-policy.js +30 -39
- package/src/agent/context-replay-policy.ts +71 -0
- package/src/agent/critical-agent-policy.js +6 -10
- package/src/agent/critical-agent-policy.ts +20 -0
- package/src/agent/guidance-queue.js +74 -87
- package/src/agent/guidance-queue.ts +166 -0
- package/src/agent/output-extraction-error-detail.js +45 -0
- package/src/agent/output-extraction-error-detail.ts +49 -0
- package/src/agent/output-extraction-failures.js +125 -0
- package/src/agent/output-extraction-failures.ts +149 -0
- package/src/agent/output-extraction-json.js +234 -0
- package/src/agent/output-extraction-json.ts +235 -0
- package/src/agent/output-extraction-pi.js +124 -0
- package/src/agent/output-extraction-pi.ts +137 -0
- package/src/agent/output-extraction-types.js +2 -0
- package/src/agent/output-extraction-types.ts +49 -0
- package/src/agent/output-extraction.js +17 -617
- package/src/agent/output-extraction.ts +30 -0
- package/src/agent/output-reformatter-errors.js +83 -0
- package/src/agent/output-reformatter-errors.ts +106 -0
- package/src/agent/output-reformatter-types.js +2 -0
- package/src/agent/output-reformatter-types.ts +86 -0
- package/src/agent/output-reformatter.js +140 -185
- package/src/agent/output-reformatter.ts +238 -0
- package/src/agent/pi-terminal-lifecycle-types.js +2 -0
- package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
- package/src/agent/pi-terminal-lifecycle.js +186 -206
- package/src/agent/pi-terminal-lifecycle.ts +297 -0
- package/src/agent/provider-control-plane.js +22 -21
- package/src/agent/provider-control-plane.ts +52 -0
- package/src/agent/provider-terminal-failure-types.js +2 -0
- package/src/agent/provider-terminal-failure-types.ts +60 -0
- package/src/agent/provider-terminal-failure.js +194 -191
- package/src/agent/provider-terminal-failure.ts +262 -0
- package/src/agent/rate-limit-backoff.js +31 -74
- package/src/agent/rate-limit-backoff.ts +71 -0
- package/src/agent/schema-utils.js +85 -125
- package/src/agent/schema-utils.ts +148 -0
- package/src/agent/structured-output-error.js +34 -33
- package/src/agent/structured-output-error.ts +105 -0
- package/src/agent/validation-platform.js +27 -27
- package/src/agent/validation-platform.ts +49 -0
- package/src/agents/git-pusher-template.js +17 -34
- package/src/attach/attach-client.js +232 -405
- package/src/attach/attach-client.ts +300 -0
- package/src/attach/attach-server-cleanup.js +43 -0
- package/src/attach/attach-server-cleanup.ts +39 -0
- package/src/attach/attach-server-clients.js +117 -0
- package/src/attach/attach-server-clients.ts +122 -0
- package/src/attach/attach-server-events.js +38 -0
- package/src/attach/attach-server-events.ts +36 -0
- package/src/attach/attach-server-pty.js +45 -0
- package/src/attach/attach-server-pty.ts +64 -0
- package/src/attach/attach-server-runtime.js +73 -0
- package/src/attach/attach-server-runtime.ts +62 -0
- package/src/attach/attach-server-socket.js +28 -0
- package/src/attach/attach-server-socket.ts +23 -0
- package/src/attach/attach-server-types.js +2 -0
- package/src/attach/attach-server-types.ts +82 -0
- package/src/attach/attach-server.js +106 -523
- package/src/attach/attach-server.ts +158 -0
- package/src/attach/index.js +18 -35
- package/src/attach/index.ts +18 -0
- package/src/attach/protocol.js +95 -184
- package/src/attach/protocol.ts +181 -0
- package/src/attach/ring-buffer.js +76 -102
- package/src/attach/ring-buffer.ts +109 -0
- package/src/attach/send-input.js +64 -75
- package/src/attach/send-input.ts +94 -0
- package/src/attach/socket-discovery.js +164 -218
- package/src/attach/socket-discovery.ts +230 -0
- package/src/attach/socket-paths.js +42 -61
- package/src/attach/socket-paths.ts +81 -0
- package/src/claude-credentials.js +51 -54
- package/src/claude-credentials.ts +89 -0
- package/src/cluster/generated/protocol-schema.ts +1 -1
- package/src/cluster/generated/protocol.ts +2 -1
- package/src/command-proofs.js +110 -141
- package/src/command-proofs.ts +216 -0
- package/src/config-router.js +81 -73
- package/src/config-router.ts +131 -0
- package/src/copy-containment.js +191 -0
- package/src/copy-containment.ts +259 -0
- package/src/copy-worker.js +53 -30
- package/src/copy-worker.ts +88 -0
- package/src/darwin-keychain-boundary.js +80 -111
- package/src/darwin-keychain-boundary.ts +148 -0
- package/src/guidance-topics.js +4 -5
- package/src/guidance-topics.ts +10 -0
- package/src/input-helpers.js +33 -54
- package/src/input-helpers.ts +58 -0
- package/src/isolation-manager.js +59 -20
- package/src/ledger-sequence.js +45 -48
- package/src/ledger-sequence.ts +63 -0
- package/src/legacy-lib/clusters-registry.ts +40 -0
- package/src/legacy-lib/completion.ts +148 -0
- package/src/legacy-lib/compose-utils.ts +102 -0
- package/src/legacy-lib/detached-startup.ts +451 -0
- package/src/legacy-lib/docker-config.ts +282 -0
- package/src/legacy-lib/docker-env-auth.ts +116 -0
- package/src/legacy-lib/git-remote-utils.ts +244 -0
- package/src/legacy-lib/id-detector.ts +57 -0
- package/src/legacy-lib/js-yaml.d.ts +3 -0
- package/src/legacy-lib/omelette.d.ts +15 -0
- package/src/legacy-lib/path-check.ts +89 -0
- package/src/legacy-lib/process-liveness.ts +36 -0
- package/src/legacy-lib/provider-credential-path.ts +57 -0
- package/src/legacy-lib/provider-defaults.ts +114 -0
- package/src/legacy-lib/provider-detection.ts +67 -0
- package/src/legacy-lib/provider-names.ts +126 -0
- package/src/legacy-lib/repo-settings.ts +88 -0
- package/src/legacy-lib/run-mode.ts +58 -0
- package/src/legacy-lib/run-plan.ts +49 -0
- package/src/legacy-lib/settings/claude-auth.ts +80 -0
- package/src/legacy-lib/settings-defaults.ts +212 -0
- package/src/legacy-lib/settings-error.ts +8 -0
- package/src/legacy-lib/settings-issue-providers.ts +18 -0
- package/src/legacy-lib/settings-models.ts +124 -0
- package/src/legacy-lib/settings-storage.ts +277 -0
- package/src/legacy-lib/settings-validation.ts +220 -0
- package/src/legacy-lib/settings.ts +87 -0
- package/src/legacy-lib/setup-apply-types.ts +70 -0
- package/src/legacy-lib/setup-apply.ts +456 -0
- package/src/legacy-lib/setup-journal.ts +185 -0
- package/src/legacy-lib/setup-plan-types.ts +96 -0
- package/src/legacy-lib/setup-plan.ts +500 -0
- package/src/legacy-lib/setup-undo.ts +142 -0
- package/src/legacy-lib/start-cluster-config.ts +124 -0
- package/src/legacy-lib/start-cluster-environment.ts +166 -0
- package/src/legacy-lib/start-cluster-run-options.ts +281 -0
- package/src/legacy-lib/start-cluster.ts +237 -0
- package/src/legacy-lib/stream-json-parser.ts +88 -0
- package/src/message-buffer.js +55 -71
- package/src/message-buffer.ts +107 -0
- package/src/message-bus-bridge.js +92 -133
- package/src/message-bus-bridge.ts +149 -0
- package/src/name-generator-shared.js +220 -0
- package/src/name-generator-shared.ts +231 -0
- package/src/name-generator.js +8 -217
- package/src/name-generator.ts +27 -0
- package/src/omp-blob-root.js +83 -64
- package/src/omp-blob-root.ts +116 -0
- package/src/omp-config-overlay.js +65 -38
- package/src/omp-config-overlay.ts +110 -0
- package/src/omp-execution-fingerprint.js +38 -43
- package/src/omp-execution-fingerprint.ts +92 -0
- package/src/omp-session-limits.js +10 -15
- package/src/omp-session-limits.ts +53 -0
- package/src/omp-session-partition.js +2 -2
- package/src/omp-session-verifier.js +1 -1
- package/src/orchestrator.js +32 -7
- package/src/pr-body-template.js +71 -0
- package/src/providers/anthropic/index.js +3 -1
- package/src/providers/anthropic/index.ts +13 -0
- package/src/providers/capabilities.js +19 -27
- package/src/providers/capabilities.ts +44 -0
- package/src/providers/google/index.js +3 -1
- package/src/providers/google/index.ts +13 -0
- package/src/providers/openai/index.js +3 -1
- package/src/providers/openai/index.ts +13 -0
- package/src/providers/opencode/index.js +3 -1
- package/src/providers/opencode/index.ts +13 -0
- package/src/quality-gates.js +67 -127
- package/src/quality-gates.ts +149 -0
- package/src/repo-settings-access.js +40 -0
- package/src/repo-settings-access.ts +64 -0
- package/src/schemas/sub-cluster.js +127 -183
- package/src/schemas/sub-cluster.ts +238 -0
- package/src/state-snapshot-normalization.js +224 -0
- package/src/state-snapshot-normalization.ts +222 -0
- package/src/state-snapshot.js +192 -359
- package/src/state-snapshot.ts +291 -0
- package/src/state-snapshotter.js +118 -130
- package/src/state-snapshotter.ts +167 -0
- package/src/sub-cluster-wrapper.js +4 -13
- package/src/task-run-model-args.js +87 -124
- package/src/task-run-model-args.ts +204 -0
- package/src/task-runner.js +5 -25
- package/src/task-runner.ts +28 -0
- package/src/task-startup-error.js +73 -58
- package/src/task-startup-error.ts +126 -0
- package/src/template-validation/consensus-gate-contracts.js +2 -0
- package/src/template-validation/consensus-gate-contracts.ts +70 -0
- package/src/template-validation/consensus-gate-scenarios.js +76 -0
- package/src/template-validation/consensus-gate-scenarios.ts +130 -0
- package/src/template-validation/consensus-gate-stage.js +34 -0
- package/src/template-validation/consensus-gate-stage.ts +56 -0
- package/src/template-validation/random-topology-contracts.js +2 -0
- package/src/template-validation/random-topology-contracts.ts +54 -0
- package/src/template-validation/random-topology-dispatch.js +25 -0
- package/src/template-validation/random-topology-dispatch.ts +33 -0
- package/src/template-validation/random-topology-message-loop.js +53 -0
- package/src/template-validation/random-topology-message-loop.ts +73 -0
- package/src/template-validation/random-topology-operations.js +79 -0
- package/src/template-validation/random-topology-operations.ts +101 -0
- package/src/template-validation/random-topology-runtime.js +25 -0
- package/src/template-validation/random-topology-runtime.ts +43 -0
- package/src/template-validation/random-topology-scenario.js +52 -0
- package/src/template-validation/random-topology-scenario.ts +57 -0
- package/src/template-validation/random-topology-schema.js +159 -0
- package/src/template-validation/random-topology-schema.ts +190 -0
- package/src/template-validation/random-topology-state.js +28 -0
- package/src/template-validation/random-topology-state.ts +30 -0
- package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
- package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
- package/src/template-validation/random-topology-trigger.js +75 -0
- package/src/template-validation/random-topology-trigger.ts +88 -0
- package/src/template-validation/report-formatter.js +40 -46
- package/src/template-validation/report-formatter.ts +101 -0
- package/src/template-validation/simulate-consensus-gates.js +111 -302
- package/src/template-validation/simulate-consensus-gates.ts +196 -0
- package/src/template-validation/simulate-random-topology.js +26 -519
- package/src/template-validation/simulate-random-topology.ts +57 -0
- package/src/template-validation/simulate-two-stage-validation.js +41 -265
- package/src/template-validation/simulate-two-stage-validation.ts +55 -0
- package/src/template-validation/simulation-agent-runtime.js +25 -0
- package/src/template-validation/simulation-agent-runtime.ts +40 -0
- package/src/template-validation/simulation-agent.js +29 -0
- package/src/template-validation/simulation-agent.ts +73 -0
- package/src/template-validation/simulation-runtime.js +34 -0
- package/src/template-validation/simulation-runtime.ts +72 -0
- package/src/template-validation/two-stage-contracts.js +2 -0
- package/src/template-validation/two-stage-contracts.ts +41 -0
- package/src/template-validation/two-stage-inputs.js +21 -0
- package/src/template-validation/two-stage-inputs.ts +29 -0
- package/src/template-validation/two-stage-results.js +30 -0
- package/src/template-validation/two-stage-results.ts +40 -0
- package/src/template-validation/two-stage-scenario.js +86 -0
- package/src/template-validation/two-stage-scenario.ts +120 -0
- package/src/worktree-claude-config.js +182 -220
- package/src/worktree-claude-config.ts +272 -0
- package/src/worktree-tooling-env.js +103 -125
- package/src/worktree-tooling-env.ts +142 -0
- package/task-lib/completion.js +2 -3
- package/task-lib/completion.ts +9 -0
- package/task-lib/config.js +1 -4
- package/task-lib/config.ts +11 -0
- package/task-lib/name-generator.js +4 -226
- package/task-lib/name-generator.ts +11 -0
- package/task-lib/omp-session-cleanup.js +1 -1
- package/task-lib/omp-storage-root.js +8 -12
- package/task-lib/omp-storage-root.ts +62 -0
- package/task-lib/process-termination.js +143 -166
- package/task-lib/process-termination.ts +268 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Router - Maps 2D classification to parameterized templates
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for: Complexity × TaskType → { base, params }
|
|
5
|
+
* Used by both logic-engine.js (trigger evaluation) and agent-wrapper.js (transform scripts)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface AgentConfigModule {
|
|
9
|
+
DEFAULT_MAX_ITERATIONS: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isAgentConfigModule(value: unknown): value is AgentConfigModule {
|
|
13
|
+
return (
|
|
14
|
+
typeof value === 'object' &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
'DEFAULT_MAX_ITERATIONS' in value &&
|
|
17
|
+
typeof value.DEFAULT_MAX_ITERATIONS === 'number'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const agentConfig: unknown = require('./agent/agent-config');
|
|
22
|
+
if (!isAgentConfigModule(agentConfig)) {
|
|
23
|
+
throw new TypeError('agent-config must export a numeric DEFAULT_MAX_ITERATIONS');
|
|
24
|
+
}
|
|
25
|
+
const { DEFAULT_MAX_ITERATIONS } = agentConfig;
|
|
26
|
+
|
|
27
|
+
type BaseConfigName = 'debug-workflow' | 'full-workflow' | 'single-worker' | 'worker-validator';
|
|
28
|
+
type ModelLevel = 'level1' | 'level2' | 'level3';
|
|
29
|
+
|
|
30
|
+
interface ConfigOptions {
|
|
31
|
+
autoPr?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ConfigParams {
|
|
35
|
+
task_type: string;
|
|
36
|
+
complexity: string;
|
|
37
|
+
max_tokens: number;
|
|
38
|
+
max_iterations: number;
|
|
39
|
+
worker_level?: ModelLevel;
|
|
40
|
+
validator_level?: ModelLevel;
|
|
41
|
+
investigator_level?: ModelLevel;
|
|
42
|
+
fixer_level?: ModelLevel;
|
|
43
|
+
tester_level?: ModelLevel;
|
|
44
|
+
planner_level?: ModelLevel;
|
|
45
|
+
validator_count?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface RoutedConfig {
|
|
49
|
+
base: BaseConfigName;
|
|
50
|
+
params: ConfigParams;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get cluster config based on complexity and task type
|
|
55
|
+
*/
|
|
56
|
+
function getConfig(
|
|
57
|
+
complexity: string,
|
|
58
|
+
taskType: string,
|
|
59
|
+
options: ConfigOptions = {}
|
|
60
|
+
): RoutedConfig {
|
|
61
|
+
const isPrMode = options.autoPr === true || process.env.ZEROSHOT_PR === '1';
|
|
62
|
+
|
|
63
|
+
const getBase = (): BaseConfigName => {
|
|
64
|
+
if (taskType === 'DEBUG' && complexity !== 'TRIVIAL') {
|
|
65
|
+
return 'debug-workflow';
|
|
66
|
+
}
|
|
67
|
+
if (complexity === 'TRIVIAL' && isPrMode && taskType !== 'INQUIRY') {
|
|
68
|
+
return 'worker-validator';
|
|
69
|
+
}
|
|
70
|
+
if (complexity === 'TRIVIAL') {
|
|
71
|
+
return 'single-worker';
|
|
72
|
+
}
|
|
73
|
+
if (complexity === 'SIMPLE') {
|
|
74
|
+
return 'worker-validator';
|
|
75
|
+
}
|
|
76
|
+
return 'full-workflow';
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getLevel = (role: string): ModelLevel => {
|
|
80
|
+
if (complexity === 'CRITICAL' && role === 'planner') return 'level3';
|
|
81
|
+
if (complexity === 'TRIVIAL') return 'level1';
|
|
82
|
+
return 'level2';
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const getValidatorCount = (): number => {
|
|
86
|
+
if (complexity === 'TRIVIAL') return 0;
|
|
87
|
+
if (complexity === 'SIMPLE') return 1;
|
|
88
|
+
if (complexity === 'STANDARD') return 2;
|
|
89
|
+
// CRITICAL uses two-stage validation pipeline (validators loaded dynamically via meta-coordinator)
|
|
90
|
+
// Setting validator_count=0 signals that inline validators should be skipped
|
|
91
|
+
if (complexity === 'CRITICAL') return 0;
|
|
92
|
+
return 1;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getMaxTokens = (): number => {
|
|
96
|
+
if (complexity === 'TRIVIAL') return 50000;
|
|
97
|
+
if (complexity === 'SIMPLE') return 100000;
|
|
98
|
+
if (complexity === 'STANDARD') return 100000;
|
|
99
|
+
if (complexity === 'CRITICAL') return 150000;
|
|
100
|
+
return 100000;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const base = getBase();
|
|
104
|
+
|
|
105
|
+
const params: ConfigParams = {
|
|
106
|
+
task_type: taskType,
|
|
107
|
+
complexity,
|
|
108
|
+
max_tokens: getMaxTokens(),
|
|
109
|
+
max_iterations: DEFAULT_MAX_ITERATIONS,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (base === 'single-worker') {
|
|
113
|
+
params.worker_level = getLevel('worker');
|
|
114
|
+
} else if (base === 'worker-validator') {
|
|
115
|
+
params.worker_level = getLevel('worker');
|
|
116
|
+
params.validator_level = getLevel('validator');
|
|
117
|
+
} else if (base === 'debug-workflow') {
|
|
118
|
+
params.investigator_level = getLevel('planner');
|
|
119
|
+
params.fixer_level = getLevel('worker');
|
|
120
|
+
params.tester_level = getLevel('validator');
|
|
121
|
+
} else if (base === 'full-workflow') {
|
|
122
|
+
params.planner_level = getLevel('planner');
|
|
123
|
+
params.worker_level = getLevel('worker');
|
|
124
|
+
params.validator_level = getLevel('validator');
|
|
125
|
+
params.validator_count = getValidatorCount();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { base, params };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export = { getConfig };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CopyContainmentError = exports.CONTAINMENT_ERROR_CODE = void 0;
|
|
4
|
+
exports.isCopyRecord = isCopyRecord;
|
|
5
|
+
exports.copyErrorCode = copyErrorCode;
|
|
6
|
+
exports.validateRelativePath = validateRelativePath;
|
|
7
|
+
exports.resolveSourcePath = resolveSourcePath;
|
|
8
|
+
exports.createCopyBoundary = createCopyBoundary;
|
|
9
|
+
exports.resolveCopyPath = resolveCopyPath;
|
|
10
|
+
exports.isCopyContainmentError = isCopyContainmentError;
|
|
11
|
+
exports.copyErrorFromPayload = copyErrorFromPayload;
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
exports.CONTAINMENT_ERROR_CODE = 'ERR_COPY_CONTAINMENT';
|
|
15
|
+
class CopyContainmentError extends Error {
|
|
16
|
+
code = exports.CONTAINMENT_ERROR_CODE;
|
|
17
|
+
relativePath;
|
|
18
|
+
constructor(relativePath, reason) {
|
|
19
|
+
super(`Copy containment violation for ${JSON.stringify(relativePath)}: ${reason}`);
|
|
20
|
+
this.name = 'CopyContainmentError';
|
|
21
|
+
this.relativePath = relativePath;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.CopyContainmentError = CopyContainmentError;
|
|
25
|
+
function isContained(rootPath, targetPath) {
|
|
26
|
+
const relative = path.relative(rootPath, targetPath);
|
|
27
|
+
return (relative === '' ||
|
|
28
|
+
(!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`)));
|
|
29
|
+
}
|
|
30
|
+
function containmentError(relativePath, reason, cause) {
|
|
31
|
+
const error = new CopyContainmentError(relativePath, reason);
|
|
32
|
+
if (cause !== undefined) {
|
|
33
|
+
error.cause = cause;
|
|
34
|
+
}
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
function isCopyRecord(value) {
|
|
38
|
+
return typeof value === 'object' && value !== null;
|
|
39
|
+
}
|
|
40
|
+
function copyErrorCode(error) {
|
|
41
|
+
if (isCopyRecord(error) && 'code' in error) {
|
|
42
|
+
return typeof error.code === 'string' ? error.code : null;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function statIdentity(targetPath) {
|
|
47
|
+
const stats = fs.statSync(targetPath, { bigint: true });
|
|
48
|
+
return {
|
|
49
|
+
device: stats.dev.toString(),
|
|
50
|
+
inode: stats.ino.toString(),
|
|
51
|
+
directory: stats.isDirectory(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function pinRoot(rootPath, label, expectedRoot) {
|
|
55
|
+
const requestedPath = path.resolve(rootPath);
|
|
56
|
+
const canonicalPath = fs.realpathSync.native(requestedPath);
|
|
57
|
+
const identity = statIdentity(canonicalPath);
|
|
58
|
+
if (!identity.directory) {
|
|
59
|
+
throw containmentError('', `${label} root is not a directory`);
|
|
60
|
+
}
|
|
61
|
+
const pinnedRoot = {
|
|
62
|
+
requestedPath,
|
|
63
|
+
canonicalPath,
|
|
64
|
+
device: identity.device,
|
|
65
|
+
inode: identity.inode,
|
|
66
|
+
};
|
|
67
|
+
if (expectedRoot &&
|
|
68
|
+
(expectedRoot.canonicalPath !== pinnedRoot.canonicalPath ||
|
|
69
|
+
expectedRoot.device !== pinnedRoot.device ||
|
|
70
|
+
expectedRoot.inode !== pinnedRoot.inode)) {
|
|
71
|
+
throw containmentError('', `${label} root changed after it was pinned`);
|
|
72
|
+
}
|
|
73
|
+
return pinnedRoot;
|
|
74
|
+
}
|
|
75
|
+
function assertPinnedRoot(root, label, relativePath) {
|
|
76
|
+
let identity;
|
|
77
|
+
try {
|
|
78
|
+
identity = statIdentity(root.canonicalPath);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw containmentError(relativePath, `${label} root can no longer be resolved`, error);
|
|
82
|
+
}
|
|
83
|
+
if (identity.device !== root.device || identity.inode !== root.inode || !identity.directory) {
|
|
84
|
+
throw containmentError(relativePath, `${label} root changed after it was pinned`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function validateRelativePath(relativePath, pathApi = path) {
|
|
88
|
+
if (typeof relativePath !== 'string' || relativePath.length === 0) {
|
|
89
|
+
throw containmentError(relativePath, 'path must be a non-empty relative string');
|
|
90
|
+
}
|
|
91
|
+
if (relativePath.includes('\0')) {
|
|
92
|
+
throw containmentError(relativePath, 'path contains a null byte');
|
|
93
|
+
}
|
|
94
|
+
if (pathApi.isAbsolute(relativePath) || pathApi.parse(relativePath).root) {
|
|
95
|
+
throw containmentError(relativePath, 'absolute paths are not allowed');
|
|
96
|
+
}
|
|
97
|
+
const components = pathApi.sep === '\\' ? relativePath.split(/[\\/]/) : relativePath.split(pathApi.sep);
|
|
98
|
+
if (components.some((component) => component === '' || component === '.' || component === '..')) {
|
|
99
|
+
throw containmentError(relativePath, 'empty, current-directory, and traversal components are not allowed');
|
|
100
|
+
}
|
|
101
|
+
return pathApi.normalize(relativePath);
|
|
102
|
+
}
|
|
103
|
+
function resolveSourcePath(boundary, relativePath) {
|
|
104
|
+
const normalizedPath = validateRelativePath(relativePath);
|
|
105
|
+
const root = boundary.sourceRoot;
|
|
106
|
+
assertPinnedRoot(root, 'source', relativePath);
|
|
107
|
+
const candidatePath = path.resolve(root.canonicalPath, normalizedPath);
|
|
108
|
+
if (!isContained(root.canonicalPath, candidatePath)) {
|
|
109
|
+
throw containmentError(relativePath, 'source path escapes its pinned root');
|
|
110
|
+
}
|
|
111
|
+
let canonicalPath;
|
|
112
|
+
try {
|
|
113
|
+
canonicalPath = fs.realpathSync.native(candidatePath);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (copyErrorCode(error) === 'ELOOP') {
|
|
117
|
+
throw containmentError(relativePath, 'source path contains a symlink cycle', error);
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
if (!isContained(root.canonicalPath, canonicalPath)) {
|
|
122
|
+
throw containmentError(relativePath, 'resolved source path escapes its pinned root');
|
|
123
|
+
}
|
|
124
|
+
return canonicalPath;
|
|
125
|
+
}
|
|
126
|
+
function resolveDestinationPath(boundary, relativePath) {
|
|
127
|
+
const normalizedPath = validateRelativePath(relativePath);
|
|
128
|
+
const root = boundary.destinationRoot;
|
|
129
|
+
assertPinnedRoot(root, 'destination', relativePath);
|
|
130
|
+
const candidatePath = path.resolve(root.canonicalPath, normalizedPath);
|
|
131
|
+
if (!isContained(root.canonicalPath, candidatePath)) {
|
|
132
|
+
throw containmentError(relativePath, 'destination path escapes its pinned root');
|
|
133
|
+
}
|
|
134
|
+
let existingPath;
|
|
135
|
+
try {
|
|
136
|
+
fs.lstatSync(candidatePath);
|
|
137
|
+
existingPath = candidatePath;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (copyErrorCode(error) !== 'ENOENT') {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
// The copy pipeline creates directories parent-first in phase two, so the
|
|
144
|
+
// immediate parent must exist before any mkdir/copy effect is attempted.
|
|
145
|
+
existingPath = path.dirname(candidatePath);
|
|
146
|
+
}
|
|
147
|
+
let canonicalExistingPath;
|
|
148
|
+
try {
|
|
149
|
+
canonicalExistingPath = fs.realpathSync.native(existingPath);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
throw containmentError(relativePath, 'destination contains an unresolved symlink', error);
|
|
153
|
+
}
|
|
154
|
+
if (!isContained(root.canonicalPath, canonicalExistingPath)) {
|
|
155
|
+
throw containmentError(relativePath, 'resolved destination path escapes its pinned root');
|
|
156
|
+
}
|
|
157
|
+
const unresolvedSuffix = path.relative(existingPath, candidatePath);
|
|
158
|
+
const resolvedPath = unresolvedSuffix
|
|
159
|
+
? path.join(canonicalExistingPath, unresolvedSuffix)
|
|
160
|
+
: canonicalExistingPath;
|
|
161
|
+
if (!isContained(root.canonicalPath, resolvedPath)) {
|
|
162
|
+
throw containmentError(relativePath, 'resolved destination path escapes its pinned root');
|
|
163
|
+
}
|
|
164
|
+
return resolvedPath;
|
|
165
|
+
}
|
|
166
|
+
function createCopyBoundary(sourceBase, destinationBase, expectedBoundary) {
|
|
167
|
+
return {
|
|
168
|
+
sourceRoot: pinRoot(sourceBase, 'source', expectedBoundary?.sourceRoot),
|
|
169
|
+
destinationRoot: pinRoot(destinationBase, 'destination', expectedBoundary?.destinationRoot),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function resolveCopyPath(boundary, relativePath) {
|
|
173
|
+
return {
|
|
174
|
+
sourcePath: resolveSourcePath(boundary, relativePath),
|
|
175
|
+
destinationPath: resolveDestinationPath(boundary, relativePath),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function isCopyContainmentError(error) {
|
|
179
|
+
return isCopyRecord(error) && error.code === exports.CONTAINMENT_ERROR_CODE;
|
|
180
|
+
}
|
|
181
|
+
function copyErrorFromPayload(payload) {
|
|
182
|
+
const error = payload.code === exports.CONTAINMENT_ERROR_CODE
|
|
183
|
+
? new CopyContainmentError(payload.relativePath, 'worker rejected an unsafe path')
|
|
184
|
+
: new Error(payload.message);
|
|
185
|
+
error.name = payload.name || error.name;
|
|
186
|
+
error.message = payload.message;
|
|
187
|
+
if (payload.code && !(error instanceof CopyContainmentError)) {
|
|
188
|
+
Object.assign(error, { code: payload.code });
|
|
189
|
+
}
|
|
190
|
+
return error;
|
|
191
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import fs = require('fs');
|
|
2
|
+
import path = require('path');
|
|
3
|
+
|
|
4
|
+
export const CONTAINMENT_ERROR_CODE = 'ERR_COPY_CONTAINMENT';
|
|
5
|
+
|
|
6
|
+
export interface PinnedCopyRoot {
|
|
7
|
+
requestedPath: string;
|
|
8
|
+
canonicalPath: string;
|
|
9
|
+
device: string;
|
|
10
|
+
inode: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CopyBoundary {
|
|
14
|
+
sourceRoot: PinnedCopyRoot;
|
|
15
|
+
destinationRoot: PinnedCopyRoot;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PathIdentity {
|
|
19
|
+
device: string;
|
|
20
|
+
inode: string;
|
|
21
|
+
directory: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CopyPathApi {
|
|
25
|
+
isAbsolute(targetPath: string): boolean;
|
|
26
|
+
normalize(targetPath: string): string;
|
|
27
|
+
parse(targetPath: string): { root: string };
|
|
28
|
+
readonly sep: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class CopyContainmentError extends Error {
|
|
32
|
+
readonly code = CONTAINMENT_ERROR_CODE;
|
|
33
|
+
readonly relativePath: unknown;
|
|
34
|
+
|
|
35
|
+
constructor(relativePath: unknown, reason: string) {
|
|
36
|
+
super(`Copy containment violation for ${JSON.stringify(relativePath)}: ${reason}`);
|
|
37
|
+
this.name = 'CopyContainmentError';
|
|
38
|
+
this.relativePath = relativePath;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isContained(rootPath: string, targetPath: string): boolean {
|
|
43
|
+
const relative = path.relative(rootPath, targetPath);
|
|
44
|
+
return (
|
|
45
|
+
relative === '' ||
|
|
46
|
+
(!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function containmentError(
|
|
51
|
+
relativePath: unknown,
|
|
52
|
+
reason: string,
|
|
53
|
+
cause?: unknown
|
|
54
|
+
): CopyContainmentError {
|
|
55
|
+
const error = new CopyContainmentError(relativePath, reason);
|
|
56
|
+
if (cause !== undefined) {
|
|
57
|
+
error.cause = cause;
|
|
58
|
+
}
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isCopyRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
63
|
+
return typeof value === 'object' && value !== null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function copyErrorCode(error: unknown): string | null {
|
|
67
|
+
if (isCopyRecord(error) && 'code' in error) {
|
|
68
|
+
return typeof error.code === 'string' ? error.code : null;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function statIdentity(targetPath: string): PathIdentity {
|
|
74
|
+
const stats = fs.statSync(targetPath, { bigint: true });
|
|
75
|
+
return {
|
|
76
|
+
device: stats.dev.toString(),
|
|
77
|
+
inode: stats.ino.toString(),
|
|
78
|
+
directory: stats.isDirectory(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pinRoot(rootPath: string, label: string, expectedRoot?: PinnedCopyRoot): PinnedCopyRoot {
|
|
83
|
+
const requestedPath = path.resolve(rootPath);
|
|
84
|
+
const canonicalPath = fs.realpathSync.native(requestedPath);
|
|
85
|
+
const identity = statIdentity(canonicalPath);
|
|
86
|
+
|
|
87
|
+
if (!identity.directory) {
|
|
88
|
+
throw containmentError('', `${label} root is not a directory`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const pinnedRoot = {
|
|
92
|
+
requestedPath,
|
|
93
|
+
canonicalPath,
|
|
94
|
+
device: identity.device,
|
|
95
|
+
inode: identity.inode,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
expectedRoot &&
|
|
100
|
+
(expectedRoot.canonicalPath !== pinnedRoot.canonicalPath ||
|
|
101
|
+
expectedRoot.device !== pinnedRoot.device ||
|
|
102
|
+
expectedRoot.inode !== pinnedRoot.inode)
|
|
103
|
+
) {
|
|
104
|
+
throw containmentError('', `${label} root changed after it was pinned`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return pinnedRoot;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function assertPinnedRoot(root: PinnedCopyRoot, label: string, relativePath: string): void {
|
|
111
|
+
let identity: PathIdentity;
|
|
112
|
+
try {
|
|
113
|
+
identity = statIdentity(root.canonicalPath);
|
|
114
|
+
} catch (error: unknown) {
|
|
115
|
+
throw containmentError(relativePath, `${label} root can no longer be resolved`, error);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (identity.device !== root.device || identity.inode !== root.inode || !identity.directory) {
|
|
119
|
+
throw containmentError(relativePath, `${label} root changed after it was pinned`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function validateRelativePath(relativePath: unknown, pathApi: CopyPathApi = path): string {
|
|
124
|
+
if (typeof relativePath !== 'string' || relativePath.length === 0) {
|
|
125
|
+
throw containmentError(relativePath, 'path must be a non-empty relative string');
|
|
126
|
+
}
|
|
127
|
+
if (relativePath.includes('\0')) {
|
|
128
|
+
throw containmentError(relativePath, 'path contains a null byte');
|
|
129
|
+
}
|
|
130
|
+
if (pathApi.isAbsolute(relativePath) || pathApi.parse(relativePath).root) {
|
|
131
|
+
throw containmentError(relativePath, 'absolute paths are not allowed');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const components =
|
|
135
|
+
pathApi.sep === '\\' ? relativePath.split(/[\\/]/) : relativePath.split(pathApi.sep);
|
|
136
|
+
if (components.some((component) => component === '' || component === '.' || component === '..')) {
|
|
137
|
+
throw containmentError(
|
|
138
|
+
relativePath,
|
|
139
|
+
'empty, current-directory, and traversal components are not allowed'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return pathApi.normalize(relativePath);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function resolveSourcePath(boundary: CopyBoundary, relativePath: string): string {
|
|
147
|
+
const normalizedPath = validateRelativePath(relativePath);
|
|
148
|
+
const root = boundary.sourceRoot;
|
|
149
|
+
assertPinnedRoot(root, 'source', relativePath);
|
|
150
|
+
|
|
151
|
+
const candidatePath = path.resolve(root.canonicalPath, normalizedPath);
|
|
152
|
+
if (!isContained(root.canonicalPath, candidatePath)) {
|
|
153
|
+
throw containmentError(relativePath, 'source path escapes its pinned root');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let canonicalPath: string;
|
|
157
|
+
try {
|
|
158
|
+
canonicalPath = fs.realpathSync.native(candidatePath);
|
|
159
|
+
} catch (error: unknown) {
|
|
160
|
+
if (copyErrorCode(error) === 'ELOOP') {
|
|
161
|
+
throw containmentError(relativePath, 'source path contains a symlink cycle', error);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!isContained(root.canonicalPath, canonicalPath)) {
|
|
167
|
+
throw containmentError(relativePath, 'resolved source path escapes its pinned root');
|
|
168
|
+
}
|
|
169
|
+
return canonicalPath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveDestinationPath(boundary: CopyBoundary, relativePath: string): string {
|
|
173
|
+
const normalizedPath = validateRelativePath(relativePath);
|
|
174
|
+
const root = boundary.destinationRoot;
|
|
175
|
+
assertPinnedRoot(root, 'destination', relativePath);
|
|
176
|
+
|
|
177
|
+
const candidatePath = path.resolve(root.canonicalPath, normalizedPath);
|
|
178
|
+
if (!isContained(root.canonicalPath, candidatePath)) {
|
|
179
|
+
throw containmentError(relativePath, 'destination path escapes its pinned root');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let existingPath: string;
|
|
183
|
+
try {
|
|
184
|
+
fs.lstatSync(candidatePath);
|
|
185
|
+
existingPath = candidatePath;
|
|
186
|
+
} catch (error: unknown) {
|
|
187
|
+
if (copyErrorCode(error) !== 'ENOENT') {
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
// The copy pipeline creates directories parent-first in phase two, so the
|
|
191
|
+
// immediate parent must exist before any mkdir/copy effect is attempted.
|
|
192
|
+
existingPath = path.dirname(candidatePath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let canonicalExistingPath: string;
|
|
196
|
+
try {
|
|
197
|
+
canonicalExistingPath = fs.realpathSync.native(existingPath);
|
|
198
|
+
} catch (error: unknown) {
|
|
199
|
+
throw containmentError(relativePath, 'destination contains an unresolved symlink', error);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!isContained(root.canonicalPath, canonicalExistingPath)) {
|
|
203
|
+
throw containmentError(relativePath, 'resolved destination path escapes its pinned root');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const unresolvedSuffix = path.relative(existingPath, candidatePath);
|
|
207
|
+
const resolvedPath = unresolvedSuffix
|
|
208
|
+
? path.join(canonicalExistingPath, unresolvedSuffix)
|
|
209
|
+
: canonicalExistingPath;
|
|
210
|
+
if (!isContained(root.canonicalPath, resolvedPath)) {
|
|
211
|
+
throw containmentError(relativePath, 'resolved destination path escapes its pinned root');
|
|
212
|
+
}
|
|
213
|
+
return resolvedPath;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function createCopyBoundary(
|
|
217
|
+
sourceBase: string,
|
|
218
|
+
destinationBase: string,
|
|
219
|
+
expectedBoundary?: CopyBoundary
|
|
220
|
+
): CopyBoundary {
|
|
221
|
+
return {
|
|
222
|
+
sourceRoot: pinRoot(sourceBase, 'source', expectedBoundary?.sourceRoot),
|
|
223
|
+
destinationRoot: pinRoot(destinationBase, 'destination', expectedBoundary?.destinationRoot),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function resolveCopyPath(
|
|
228
|
+
boundary: CopyBoundary,
|
|
229
|
+
relativePath: string
|
|
230
|
+
): { sourcePath: string; destinationPath: string } {
|
|
231
|
+
return {
|
|
232
|
+
sourcePath: resolveSourcePath(boundary, relativePath),
|
|
233
|
+
destinationPath: resolveDestinationPath(boundary, relativePath),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function isCopyContainmentError(error: unknown): boolean {
|
|
238
|
+
return isCopyRecord(error) && error.code === CONTAINMENT_ERROR_CODE;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
interface CopyErrorPayload {
|
|
242
|
+
code?: string | null;
|
|
243
|
+
relativePath?: unknown;
|
|
244
|
+
name?: string;
|
|
245
|
+
message: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function copyErrorFromPayload(payload: CopyErrorPayload): Error {
|
|
249
|
+
const error =
|
|
250
|
+
payload.code === CONTAINMENT_ERROR_CODE
|
|
251
|
+
? new CopyContainmentError(payload.relativePath, 'worker rejected an unsafe path')
|
|
252
|
+
: new Error(payload.message);
|
|
253
|
+
error.name = payload.name || error.name;
|
|
254
|
+
error.message = payload.message;
|
|
255
|
+
if (payload.code && !(error instanceof CopyContainmentError)) {
|
|
256
|
+
Object.assign(error, { code: payload.code });
|
|
257
|
+
}
|
|
258
|
+
return error;
|
|
259
|
+
}
|
package/src/copy-worker.js
CHANGED
|
@@ -1,43 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
3
|
/**
|
|
2
4
|
* Copy Worker - Worker thread for parallel file copying
|
|
3
5
|
*
|
|
4
6
|
* Handles copying a batch of files from source to destination.
|
|
5
7
|
* Used by IsolationManager._copyDirExcluding() for parallel copying.
|
|
6
8
|
*/
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const worker_threads_1 = require("worker_threads");
|
|
11
|
+
const copy_containment_1 = require("./copy-containment");
|
|
12
|
+
function isCopyWorkerData(value) {
|
|
13
|
+
if (!(0, copy_containment_1.isCopyRecord)(value)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return (Array.isArray(value.files) &&
|
|
17
|
+
value.files.every((entry) => typeof entry === 'string') &&
|
|
18
|
+
typeof value.sourceBase === 'string' &&
|
|
19
|
+
typeof value.destBase === 'string' &&
|
|
20
|
+
(0, copy_containment_1.isCopyRecord)(value.expectedBoundary));
|
|
21
|
+
}
|
|
22
|
+
function errorMessage(error) {
|
|
23
|
+
return error instanceof Error ? error.message : String(error);
|
|
24
|
+
}
|
|
25
|
+
const rawWorkerData = worker_threads_1.workerData;
|
|
26
|
+
if (!isCopyWorkerData(rawWorkerData)) {
|
|
27
|
+
throw new TypeError('copy worker requires files, sourceBase, and destBase');
|
|
28
|
+
}
|
|
29
|
+
const { files, sourceBase, destBase, expectedBoundary } = rawWorkerData;
|
|
30
|
+
const copyBoundary = (0, copy_containment_1.createCopyBoundary)(sourceBase, destBase, expectedBoundary);
|
|
14
31
|
let copied = 0;
|
|
15
32
|
let skipped = 0;
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
let error = null;
|
|
18
34
|
for (const relativePath of files) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!fs.existsSync(destDir)) {
|
|
26
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
35
|
+
try {
|
|
36
|
+
// Phase two creates every parent directory. Re-resolve the source and
|
|
37
|
+
// destination immediately before the only worker filesystem effect.
|
|
38
|
+
const { sourcePath, destinationPath } = (0, copy_containment_1.resolveCopyPath)(copyBoundary, relativePath);
|
|
39
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
40
|
+
copied++;
|
|
27
41
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
catch (caughtError) {
|
|
43
|
+
// Skip files we can't copy (permission denied, broken symlinks, etc.)
|
|
44
|
+
const code = (0, copy_containment_1.copyErrorCode)(caughtError);
|
|
45
|
+
if (!(0, copy_containment_1.isCopyContainmentError)(caughtError) &&
|
|
46
|
+
(code === 'EACCES' || code === 'EPERM' || code === 'ENOENT')) {
|
|
47
|
+
skipped++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
error = {
|
|
51
|
+
file: relativePath,
|
|
52
|
+
name: caughtError instanceof Error ? caughtError.name : 'Error',
|
|
53
|
+
code,
|
|
54
|
+
message: errorMessage(caughtError),
|
|
55
|
+
relativePath: (0, copy_containment_1.isCopyRecord)(caughtError) && 'relativePath' in caughtError
|
|
56
|
+
? caughtError.relativePath
|
|
57
|
+
: relativePath,
|
|
58
|
+
};
|
|
59
|
+
break;
|
|
37
60
|
}
|
|
38
|
-
errors.push({ file: relativePath, error: err.message });
|
|
39
|
-
}
|
|
40
61
|
}
|
|
41
|
-
|
|
42
62
|
// Report results back to main thread
|
|
43
|
-
parentPort
|
|
63
|
+
if (!worker_threads_1.parentPort) {
|
|
64
|
+
throw new Error('copy worker requires a parent port');
|
|
65
|
+
}
|
|
66
|
+
worker_threads_1.parentPort.postMessage({ copied, skipped, error });
|