@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
package/src/isolation-manager.js
CHANGED
|
@@ -34,6 +34,14 @@ const { resolveProviderCredentialPaths } = require('../lib/provider-credential-p
|
|
|
34
34
|
const { getProvider } = require('./providers');
|
|
35
35
|
const { readRepoSettings } = require('../lib/repo-settings');
|
|
36
36
|
const { provisionClaudeCredentials } = require('./claude-credentials');
|
|
37
|
+
const {
|
|
38
|
+
CopyContainmentError,
|
|
39
|
+
copyErrorFromPayload,
|
|
40
|
+
createCopyBoundary,
|
|
41
|
+
resolveCopyPath,
|
|
42
|
+
resolveSourcePath,
|
|
43
|
+
validateRelativePath,
|
|
44
|
+
} = require('./copy-containment');
|
|
37
45
|
|
|
38
46
|
const DEFAULT_WORKTREE_SETUP_TIMEOUT_MS = 15 * 60 * 1000;
|
|
39
47
|
const FRESH_BASE_REF_PREFIX = 'refs/zeroshot/base-fetch';
|
|
@@ -1298,6 +1306,7 @@ class IsolationManager {
|
|
|
1298
1306
|
// Phase 1: Collect all files and directories
|
|
1299
1307
|
const files = [];
|
|
1300
1308
|
const directories = new Set();
|
|
1309
|
+
const copyBoundary = createCopyBoundary(src, dest);
|
|
1301
1310
|
|
|
1302
1311
|
const shouldIgnoreFsError = (err) =>
|
|
1303
1312
|
err.code === 'EACCES' || err.code === 'EPERM' || err.code === 'ENOENT';
|
|
@@ -1328,12 +1337,13 @@ class IsolationManager {
|
|
|
1328
1337
|
}
|
|
1329
1338
|
};
|
|
1330
1339
|
|
|
1331
|
-
function handleEntry(entry,
|
|
1340
|
+
function handleEntry(entry, relPath, relativePath, ancestorDirectories) {
|
|
1332
1341
|
if (entry.isSymbolicLink()) {
|
|
1333
|
-
const
|
|
1342
|
+
const resolvedSourcePath = resolveSourcePath(copyBoundary, relPath);
|
|
1343
|
+
const targetStats = fs.statSync(resolvedSourcePath);
|
|
1334
1344
|
if (targetStats.isDirectory()) {
|
|
1335
1345
|
directories.add(relPath);
|
|
1336
|
-
collectFiles(
|
|
1346
|
+
collectFiles(relPath, ancestorDirectories);
|
|
1337
1347
|
return;
|
|
1338
1348
|
}
|
|
1339
1349
|
|
|
@@ -1344,7 +1354,7 @@ class IsolationManager {
|
|
|
1344
1354
|
|
|
1345
1355
|
if (entry.isDirectory()) {
|
|
1346
1356
|
directories.add(relPath);
|
|
1347
|
-
collectFiles(
|
|
1357
|
+
collectFiles(relPath, ancestorDirectories);
|
|
1348
1358
|
return;
|
|
1349
1359
|
}
|
|
1350
1360
|
|
|
@@ -1352,7 +1362,15 @@ class IsolationManager {
|
|
|
1352
1362
|
ensureParentDirTracked(relativePath);
|
|
1353
1363
|
}
|
|
1354
1364
|
|
|
1355
|
-
function collectFiles(
|
|
1365
|
+
function collectFiles(relativePath = '', ancestorDirectories = new Set()) {
|
|
1366
|
+
const currentSrc = relativePath
|
|
1367
|
+
? resolveSourcePath(copyBoundary, relativePath)
|
|
1368
|
+
: copyBoundary.sourceRoot.canonicalPath;
|
|
1369
|
+
if (ancestorDirectories.has(currentSrc)) {
|
|
1370
|
+
throw new CopyContainmentError(relativePath, 'source directory symlink creates a cycle');
|
|
1371
|
+
}
|
|
1372
|
+
const childAncestors = new Set(ancestorDirectories);
|
|
1373
|
+
childAncestors.add(currentSrc);
|
|
1356
1374
|
const entries = readEntries(currentSrc);
|
|
1357
1375
|
|
|
1358
1376
|
for (const entry of entries) {
|
|
@@ -1360,11 +1378,11 @@ class IsolationManager {
|
|
|
1360
1378
|
continue;
|
|
1361
1379
|
}
|
|
1362
1380
|
|
|
1363
|
-
const
|
|
1364
|
-
const relPath = relativePath ? path.join(relativePath,
|
|
1381
|
+
const entryName = validateRelativePath(entry.name);
|
|
1382
|
+
const relPath = relativePath ? path.join(relativePath, entryName) : entryName;
|
|
1365
1383
|
|
|
1366
1384
|
try {
|
|
1367
|
-
handleEntry(entry,
|
|
1385
|
+
handleEntry(entry, relPath, relativePath, childAncestors);
|
|
1368
1386
|
} catch (err) {
|
|
1369
1387
|
if (shouldIgnoreFsError(err)) {
|
|
1370
1388
|
continue;
|
|
@@ -1374,7 +1392,7 @@ class IsolationManager {
|
|
|
1374
1392
|
}
|
|
1375
1393
|
}
|
|
1376
1394
|
|
|
1377
|
-
collectFiles(
|
|
1395
|
+
collectFiles();
|
|
1378
1396
|
|
|
1379
1397
|
// Phase 2: Create directory structure (sequential - must exist before file copy)
|
|
1380
1398
|
// Sort directories by depth to ensure parents are created before children
|
|
@@ -1385,7 +1403,7 @@ class IsolationManager {
|
|
|
1385
1403
|
});
|
|
1386
1404
|
|
|
1387
1405
|
for (const dir of sortedDirs) {
|
|
1388
|
-
const destDir =
|
|
1406
|
+
const { destinationPath: destDir } = resolveCopyPath(copyBoundary, dir);
|
|
1389
1407
|
try {
|
|
1390
1408
|
fs.mkdirSync(destDir, { recursive: true });
|
|
1391
1409
|
} catch (err) {
|
|
@@ -1399,10 +1417,9 @@ class IsolationManager {
|
|
|
1399
1417
|
// For small file counts (<100), use synchronous copy (worker overhead not worth it)
|
|
1400
1418
|
if (files.length < 100) {
|
|
1401
1419
|
for (const relPath of files) {
|
|
1402
|
-
const srcPath = path.join(src, relPath);
|
|
1403
|
-
const destPath = path.join(dest, relPath);
|
|
1404
1420
|
try {
|
|
1405
|
-
|
|
1421
|
+
const { sourcePath, destinationPath } = resolveCopyPath(copyBoundary, relPath);
|
|
1422
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
1406
1423
|
} catch (err) {
|
|
1407
1424
|
if (err.code !== 'EACCES' && err.code !== 'EPERM' && err.code !== 'ENOENT') {
|
|
1408
1425
|
throw err;
|
|
@@ -1424,18 +1441,22 @@ class IsolationManager {
|
|
|
1424
1441
|
}
|
|
1425
1442
|
|
|
1426
1443
|
// Spawn workers and wait for completion
|
|
1444
|
+
const workers = [];
|
|
1427
1445
|
const workerPromises = chunks.map((chunk) => {
|
|
1428
1446
|
return new Promise((resolve, reject) => {
|
|
1429
1447
|
const worker = new Worker(workerPath, {
|
|
1430
1448
|
workerData: {
|
|
1431
1449
|
files: chunk,
|
|
1432
|
-
sourceBase:
|
|
1433
|
-
destBase:
|
|
1450
|
+
sourceBase: copyBoundary.sourceRoot.canonicalPath,
|
|
1451
|
+
destBase: copyBoundary.destinationRoot.canonicalPath,
|
|
1452
|
+
expectedBoundary: copyBoundary,
|
|
1434
1453
|
},
|
|
1435
1454
|
});
|
|
1455
|
+
workers.push(worker);
|
|
1456
|
+
let result = null;
|
|
1436
1457
|
|
|
1437
|
-
worker.on('message', (
|
|
1438
|
-
|
|
1458
|
+
worker.on('message', (workerResult) => {
|
|
1459
|
+
result = workerResult;
|
|
1439
1460
|
});
|
|
1440
1461
|
|
|
1441
1462
|
worker.on('error', (err) => {
|
|
@@ -1445,6 +1466,12 @@ class IsolationManager {
|
|
|
1445
1466
|
worker.on('exit', (code) => {
|
|
1446
1467
|
if (code !== 0) {
|
|
1447
1468
|
reject(new Error(`Worker exited with code ${code}`));
|
|
1469
|
+
} else if (!result) {
|
|
1470
|
+
reject(new Error('Copy worker exited without reporting a result'));
|
|
1471
|
+
} else if (result.error) {
|
|
1472
|
+
reject(copyErrorFromPayload(result.error));
|
|
1473
|
+
} else {
|
|
1474
|
+
resolve(result);
|
|
1448
1475
|
}
|
|
1449
1476
|
});
|
|
1450
1477
|
});
|
|
@@ -1453,7 +1480,12 @@ class IsolationManager {
|
|
|
1453
1480
|
// Wait for all workers to complete (proper async/await - no busy-wait!)
|
|
1454
1481
|
// FIX: Previous version used busy-wait which blocked the event loop,
|
|
1455
1482
|
// preventing worker thread messages from being processed (timeout bug)
|
|
1456
|
-
|
|
1483
|
+
try {
|
|
1484
|
+
await Promise.all(workerPromises);
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
await Promise.all(workers.map((worker) => worker.terminate().catch(() => undefined)));
|
|
1487
|
+
throw err;
|
|
1488
|
+
}
|
|
1457
1489
|
}
|
|
1458
1490
|
|
|
1459
1491
|
/**
|
|
@@ -2383,8 +2415,15 @@ class IsolationManager {
|
|
|
2383
2415
|
// NEVER pass --volumes (irreversible data loss) and NEVER tear down a pinned/shared
|
|
2384
2416
|
// Compose project — only a project scoped to the worktree directory basename, which is
|
|
2385
2417
|
// the only kind zeroshot could itself have created, is touched.
|
|
2386
|
-
|
|
2387
|
-
|
|
2418
|
+
let teardown = { shouldTeardown: false };
|
|
2419
|
+
try {
|
|
2420
|
+
const { resolveWorktreeComposeTeardown } = require('../lib/compose-utils');
|
|
2421
|
+
teardown = resolveWorktreeComposeTeardown(worktreeInfo.path);
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
console.warn(
|
|
2424
|
+
`[IsolationManager] Skipping Docker Compose teardown in ${worktreeInfo.path}: ${error.message}`
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2388
2427
|
if (teardown.shouldTeardown) {
|
|
2389
2428
|
try {
|
|
2390
2429
|
runSync('docker', teardown.args, {
|
package/src/ledger-sequence.js
CHANGED
|
@@ -1,63 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
const MAX_SQLITE_ROWID = 9223372036854775807n;
|
|
2
3
|
const CANONICAL_SEQUENCE = /^(0|[1-9][0-9]*)$/;
|
|
3
|
-
|
|
4
4
|
function canonicalMessageSequence(value, name = 'message sequence') {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
let sequence;
|
|
6
|
+
if (typeof value === 'string' && CANONICAL_SEQUENCE.test(value)) {
|
|
7
|
+
sequence = value;
|
|
8
|
+
}
|
|
9
|
+
else if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
10
|
+
// Accept legacy JSON state while normalizing every new boundary to a
|
|
11
|
+
// JSON-safe decimal string.
|
|
12
|
+
sequence = String(value);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
throw new TypeError(`${name} must be a canonical non-negative decimal string`);
|
|
16
|
+
}
|
|
17
|
+
if (BigInt(sequence) > MAX_SQLITE_ROWID) {
|
|
18
|
+
throw new RangeError(`${name} exceeds the SQLite rowid range`);
|
|
19
|
+
}
|
|
20
|
+
return sequence;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
22
|
function messageSequenceFromSql(value, name = 'message sequence') {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (typeof value === 'bigint') {
|
|
24
|
+
if (value < 0n || value > MAX_SQLITE_ROWID) {
|
|
25
|
+
throw new RangeError(`${name} is outside the SQLite rowid range`);
|
|
26
|
+
}
|
|
27
|
+
return value.toString();
|
|
26
28
|
}
|
|
27
|
-
return value
|
|
28
|
-
}
|
|
29
|
-
return canonicalMessageSequence(value, name);
|
|
29
|
+
return canonicalMessageSequence(value, name);
|
|
30
30
|
}
|
|
31
|
-
|
|
32
31
|
function messageSequenceToSql(value, name = 'message sequence') {
|
|
33
|
-
|
|
32
|
+
return BigInt(canonicalMessageSequence(value, name));
|
|
34
33
|
}
|
|
35
|
-
|
|
36
34
|
function compareMessageSequences(left, right) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
const leftValue = messageSequenceToSql(left, 'left message sequence');
|
|
36
|
+
const rightValue = messageSequenceToSql(right, 'right message sequence');
|
|
37
|
+
if (leftValue < rightValue) {
|
|
38
|
+
return -1;
|
|
39
|
+
}
|
|
40
|
+
if (leftValue > rightValue) {
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
46
44
|
}
|
|
47
|
-
|
|
48
45
|
function tryCanonicalMessageSequence(value) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
try {
|
|
47
|
+
return canonicalMessageSequence(value);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
54
52
|
}
|
|
55
|
-
|
|
56
53
|
module.exports = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
MAX_SQLITE_ROWID,
|
|
55
|
+
canonicalMessageSequence,
|
|
56
|
+
compareMessageSequences,
|
|
57
|
+
messageSequenceFromSql,
|
|
58
|
+
messageSequenceToSql,
|
|
59
|
+
tryCanonicalMessageSequence,
|
|
63
60
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const MAX_SQLITE_ROWID = 9223372036854775807n;
|
|
2
|
+
const CANONICAL_SEQUENCE = /^(0|[1-9][0-9]*)$/;
|
|
3
|
+
|
|
4
|
+
function canonicalMessageSequence(value: unknown, name = 'message sequence'): string {
|
|
5
|
+
let sequence: string;
|
|
6
|
+
if (typeof value === 'string' && CANONICAL_SEQUENCE.test(value)) {
|
|
7
|
+
sequence = value;
|
|
8
|
+
} else if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
9
|
+
// Accept legacy JSON state while normalizing every new boundary to a
|
|
10
|
+
// JSON-safe decimal string.
|
|
11
|
+
sequence = String(value);
|
|
12
|
+
} else {
|
|
13
|
+
throw new TypeError(`${name} must be a canonical non-negative decimal string`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (BigInt(sequence) > MAX_SQLITE_ROWID) {
|
|
17
|
+
throw new RangeError(`${name} exceeds the SQLite rowid range`);
|
|
18
|
+
}
|
|
19
|
+
return sequence;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function messageSequenceFromSql(value: unknown, name = 'message sequence'): string {
|
|
23
|
+
if (typeof value === 'bigint') {
|
|
24
|
+
if (value < 0n || value > MAX_SQLITE_ROWID) {
|
|
25
|
+
throw new RangeError(`${name} is outside the SQLite rowid range`);
|
|
26
|
+
}
|
|
27
|
+
return value.toString();
|
|
28
|
+
}
|
|
29
|
+
return canonicalMessageSequence(value, name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function messageSequenceToSql(value: unknown, name = 'message sequence'): bigint {
|
|
33
|
+
return BigInt(canonicalMessageSequence(value, name));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function compareMessageSequences(left: unknown, right: unknown): -1 | 0 | 1 {
|
|
37
|
+
const leftValue = messageSequenceToSql(left, 'left message sequence');
|
|
38
|
+
const rightValue = messageSequenceToSql(right, 'right message sequence');
|
|
39
|
+
if (leftValue < rightValue) {
|
|
40
|
+
return -1;
|
|
41
|
+
}
|
|
42
|
+
if (leftValue > rightValue) {
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function tryCanonicalMessageSequence(value: unknown): string | null {
|
|
49
|
+
try {
|
|
50
|
+
return canonicalMessageSequence(value);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export = {
|
|
57
|
+
MAX_SQLITE_ROWID,
|
|
58
|
+
canonicalMessageSequence,
|
|
59
|
+
compareMessageSequences,
|
|
60
|
+
messageSequenceFromSql,
|
|
61
|
+
messageSequenceToSql,
|
|
62
|
+
tryCanonicalMessageSequence,
|
|
63
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single read/write path for clusters.json.
|
|
3
|
+
*
|
|
4
|
+
* All callers that need the raw registry (Orchestrator, id-detector, gc,
|
|
5
|
+
* socket-discovery, CLI) go through readClustersFileSync/writeClustersFileAtomic
|
|
6
|
+
* instead of ad-hoc JSON.parse(fs.readFileSync(...)) / fs.writeFileSync(...).
|
|
7
|
+
*
|
|
8
|
+
* Writes are atomic (temp file + rename) so a reader can never observe a
|
|
9
|
+
* partially-written file, even without taking the write lock. Callers that
|
|
10
|
+
* read-modify-write (Orchestrator._saveClusters) still need proper-lockfile
|
|
11
|
+
* around the whole operation to avoid losing concurrent updates.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs = require('fs');
|
|
15
|
+
import path = require('path');
|
|
16
|
+
|
|
17
|
+
function clustersFilePath(storageDir: string): string {
|
|
18
|
+
return path.join(storageDir, 'clusters.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Read clusters.json. Returns an empty registry if the file is missing. */
|
|
22
|
+
function readClustersFileSync(storageDir: string): unknown {
|
|
23
|
+
const clustersFile = clustersFilePath(storageDir);
|
|
24
|
+
if (!fs.existsSync(clustersFile)) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
const raw = fs.readFileSync(clustersFile, 'utf8');
|
|
28
|
+
const parsed: unknown = JSON.parse(raw);
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Write clusters.json atomically through a process-scoped temporary file. */
|
|
33
|
+
function writeClustersFileAtomic(storageDir: string, data: unknown): void {
|
|
34
|
+
const clustersFile = clustersFilePath(storageDir);
|
|
35
|
+
const tmpPath = `${clustersFile}.tmp-${process.pid}`;
|
|
36
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
37
|
+
fs.renameSync(tmpPath, clustersFile);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export = { clustersFilePath, readClustersFileSync, writeClustersFileAtomic };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import os = require('os');
|
|
2
|
+
import path = require('path');
|
|
3
|
+
import type { Command, Option } from 'commander';
|
|
4
|
+
import omelette = require('omelette');
|
|
5
|
+
import clustersRegistry = require('./clusters-registry');
|
|
6
|
+
|
|
7
|
+
interface ClustersRegistryFacade {
|
|
8
|
+
readClustersFileSync(storageDir: string): unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface CompletionDependencies {
|
|
12
|
+
listClusterIds?: () => unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { readClustersFileSync }: ClustersRegistryFacade = clustersRegistry;
|
|
16
|
+
|
|
17
|
+
const CLUSTER_ID_COMMANDS = new Set([
|
|
18
|
+
'attach',
|
|
19
|
+
'export',
|
|
20
|
+
'finish',
|
|
21
|
+
'kill',
|
|
22
|
+
'logs',
|
|
23
|
+
'resume',
|
|
24
|
+
'status',
|
|
25
|
+
'stop',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function commandNames(command: Command): string[] {
|
|
29
|
+
return [command.name(), ...command.aliases()];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function visibleCommands(command: Command): Command[] {
|
|
33
|
+
return command.createHelp().visibleCommands(command);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function visibleOptions(command: Command): Option[] {
|
|
37
|
+
return command.createHelp().visibleOptions(command);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function optionNames(option: Option): string[] {
|
|
41
|
+
return [option.short, option.long].filter((name): name is string => Boolean(name));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findSubcommand(command: Command, token: string): Command | undefined {
|
|
45
|
+
return visibleCommands(command).find((candidate) => commandNames(candidate).includes(token));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function findOption(command: Command, token: string): Option | undefined {
|
|
49
|
+
const optionToken = token.split('=', 1)[0] ?? '';
|
|
50
|
+
return visibleOptions(command).find((option) => optionNames(option).includes(optionToken));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseCompletionLine(line: string): { completed: string[]; current: string } {
|
|
54
|
+
const trailingSpace = /\s$/.test(line);
|
|
55
|
+
const tokens = line.trim().split(/\s+/).filter(Boolean);
|
|
56
|
+
tokens.shift();
|
|
57
|
+
const current = trailingSpace ? '' : (tokens.pop() ?? '');
|
|
58
|
+
return { completed: tokens, current };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveCommandContext(
|
|
62
|
+
program: Command,
|
|
63
|
+
tokens: string[]
|
|
64
|
+
): { command: Command; commandPath: string } {
|
|
65
|
+
let command = program;
|
|
66
|
+
const pathParts: string[] = [];
|
|
67
|
+
let expectsOptionValue = false;
|
|
68
|
+
let sawPositional = false;
|
|
69
|
+
|
|
70
|
+
for (const token of tokens) {
|
|
71
|
+
if (expectsOptionValue) {
|
|
72
|
+
expectsOptionValue = false;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const option = findOption(command, token);
|
|
77
|
+
if (option) {
|
|
78
|
+
expectsOptionValue = !token.includes('=') && (option.required || option.optional);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (token.startsWith('-')) continue;
|
|
82
|
+
|
|
83
|
+
const subcommand = sawPositional ? undefined : findSubcommand(command, token);
|
|
84
|
+
if (subcommand) {
|
|
85
|
+
command = subcommand;
|
|
86
|
+
pathParts.push(subcommand.name());
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
sawPositional = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { command, commandPath: pathParts.join(' ') };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function defaultListClusterIds(): string[] {
|
|
96
|
+
const homeDir =
|
|
97
|
+
process.env.ZEROSHOT_HOME || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
98
|
+
const registry = readClustersFileSync(path.join(homeDir, '.zeroshot'));
|
|
99
|
+
// Preserve the legacy failure behavior if a corrupt registry is not an object.
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
101
|
+
return Object.keys(registry as object);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function safeDynamicIds(commandPath: string, listClusterIds: () => unknown): string[] {
|
|
105
|
+
if (!CLUSTER_ID_COMMANDS.has(commandPath)) return [];
|
|
106
|
+
try {
|
|
107
|
+
const ids = listClusterIds();
|
|
108
|
+
if (!Array.isArray(ids)) return [];
|
|
109
|
+
const unknownIds: unknown[] = ids;
|
|
110
|
+
return unknownIds.filter((id): id is string => typeof id === 'string' && id.length > 0);
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getCompletionCandidates(
|
|
117
|
+
program: Command,
|
|
118
|
+
line: string,
|
|
119
|
+
deps: CompletionDependencies = {}
|
|
120
|
+
): string[] {
|
|
121
|
+
const { completed, current } = parseCompletionLine(line);
|
|
122
|
+
const { command, commandPath } = resolveCommandContext(program, completed);
|
|
123
|
+
const listClusterIds = deps.listClusterIds || defaultListClusterIds;
|
|
124
|
+
const candidates = [
|
|
125
|
+
...visibleCommands(command).flatMap(commandNames),
|
|
126
|
+
...visibleOptions(command).flatMap(optionNames),
|
|
127
|
+
...safeDynamicIds(commandPath, listClusterIds),
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
return [...new Set(candidates)].filter((candidate) => candidate.startsWith(current));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setupCompletion(
|
|
134
|
+
program: Command,
|
|
135
|
+
deps: CompletionDependencies = {}
|
|
136
|
+
): ReturnType<typeof omelette> {
|
|
137
|
+
const complete = omelette('zeroshot');
|
|
138
|
+
complete.on('complete', (_fragment, data) => {
|
|
139
|
+
data.reply(getCompletionCandidates(program, data.line, deps));
|
|
140
|
+
});
|
|
141
|
+
complete.init();
|
|
142
|
+
return complete;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export = {
|
|
146
|
+
getCompletionCandidates,
|
|
147
|
+
setupCompletion,
|
|
148
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Compose teardown resolution for worktree cleanup.
|
|
3
|
+
*
|
|
4
|
+
* Zeroshot never runs `docker compose up` — any compose file found in a worktree
|
|
5
|
+
* belongs to the target repo, not to zeroshot. Tearing it down is only safe when
|
|
6
|
+
* the resolved Compose project name is scoped to the worktree directory itself
|
|
7
|
+
* (i.e. nothing pins it to the host's real, possibly-already-running project).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs = require('fs');
|
|
11
|
+
import path = require('path');
|
|
12
|
+
import yaml = require('js-yaml');
|
|
13
|
+
|
|
14
|
+
const COMPOSE_FILENAMES = [
|
|
15
|
+
'docker-compose.yml',
|
|
16
|
+
'docker-compose.yaml',
|
|
17
|
+
'compose.yml',
|
|
18
|
+
'compose.yaml',
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
interface ComposeTeardownResolution {
|
|
22
|
+
readonly args?: string[];
|
|
23
|
+
readonly composePath?: string;
|
|
24
|
+
readonly reason?: string;
|
|
25
|
+
readonly shouldTeardown: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reproduce Docker Compose's default project-name normalization
|
|
30
|
+
* (compose-go NormalizeProjectName: lowercase -> keep [a-z0-9_-] -> trim leading '_'/'-').
|
|
31
|
+
*/
|
|
32
|
+
function normalizeComposeProjectName(name: string): string {
|
|
33
|
+
return name
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/[^a-z0-9_-]/g, '')
|
|
36
|
+
.replace(/^[-_]+/, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decide whether it's safe to run `docker compose down` in a worktree, and with what args.
|
|
41
|
+
* Never includes `--volumes` — automatic cleanup must not delete named volumes.
|
|
42
|
+
* Skips teardown entirely when the Compose project name is pinned (top-level `name:`
|
|
43
|
+
* in the compose file, or `COMPOSE_PROJECT_NAME` env var), since that resolves to a
|
|
44
|
+
* shared/host project zeroshot did not create.
|
|
45
|
+
*/
|
|
46
|
+
function resolveWorktreeComposeTeardown(worktreePath: string): ComposeTeardownResolution {
|
|
47
|
+
let composePath: string | null = null;
|
|
48
|
+
for (const filename of COMPOSE_FILENAMES) {
|
|
49
|
+
const candidate = path.join(worktreePath, filename);
|
|
50
|
+
if (fs.existsSync(candidate)) {
|
|
51
|
+
composePath = candidate;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!composePath) {
|
|
57
|
+
return { shouldTeardown: false, reason: 'no compose file' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
typeof process.env.COMPOSE_PROJECT_NAME === 'string' &&
|
|
62
|
+
process.env.COMPOSE_PROJECT_NAME.trim() !== ''
|
|
63
|
+
) {
|
|
64
|
+
return {
|
|
65
|
+
shouldTeardown: false,
|
|
66
|
+
reason: 'pinned compose project name (shared host project)',
|
|
67
|
+
composePath,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const contents = fs.readFileSync(composePath, 'utf8');
|
|
73
|
+
const parsed = yaml.load(contents);
|
|
74
|
+
if (parsed && typeof parsed === 'object' && Reflect.get(parsed, 'name')) {
|
|
75
|
+
return {
|
|
76
|
+
shouldTeardown: false,
|
|
77
|
+
reason: 'pinned compose project name (shared host project)',
|
|
78
|
+
composePath,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Fail safe: if the compose file can't be read/parsed, we cannot confirm project
|
|
83
|
+
// identity is worktree-scoped, so never tear down.
|
|
84
|
+
return { shouldTeardown: false, reason: 'compose file unreadable or unparsable', composePath };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
shouldTeardown: true,
|
|
89
|
+
composePath,
|
|
90
|
+
args: [
|
|
91
|
+
'compose',
|
|
92
|
+
'-p',
|
|
93
|
+
normalizeComposeProjectName(path.basename(worktreePath)),
|
|
94
|
+
'down',
|
|
95
|
+
'--remove-orphans',
|
|
96
|
+
'--timeout',
|
|
97
|
+
'10',
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export = { resolveWorktreeComposeTeardown, normalizeComposeProjectName };
|