@the-open-engine/zeroshot 6.38.0 → 6.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/event-copy.js +8 -7
- package/cli/event-copy.ts +12 -0
- package/cli/index.js +7 -31
- 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/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +3 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- 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 +83 -0
- package/lib/detached-startup.js +231 -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 +40 -0
- package/lib/start-cluster-environment.js +134 -0
- package/lib/start-cluster-run-options.d.ts +53 -0
- package/lib/start-cluster-run-options.js +147 -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 +2 -2
- package/package.json +12 -6
- package/scripts/postinstall.js +27 -0
- package/scripts/rust-distribution.js +23 -14
- 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/agent-cli-provider/adapters/codex.ts +3 -1
- 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-worker.js +53 -29
- package/src/copy-worker.ts +75 -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/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 +449 -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 +165 -0
- package/src/legacy-lib/start-cluster-run-options.ts +279 -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 +19 -4
- 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,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Darwin worker Keychain boundary (issue #704).
|
|
3
|
+
*
|
|
4
|
+
* Non-interactive local/worktree workers are spawned with the host environment,
|
|
5
|
+
* so worker descendants (e.g. `claude doctor` probing Keychain writes through
|
|
6
|
+
* `security -i`) reach the logged-in user's GUI Keychain session and launch
|
|
7
|
+
* SecurityAgent dialogs from a supposedly non-interactive cluster.
|
|
8
|
+
*
|
|
9
|
+
* On darwin, worker spawn envs get a managed shim directory prepended to PATH
|
|
10
|
+
* containing a `security` wrapper that fails closed on interactive invocations
|
|
11
|
+
* (`-i`, `-p`, or no arguments) with a deterministic diagnostic, and execs the
|
|
12
|
+
* real /usr/bin/security for every other subcommand so provider authentication
|
|
13
|
+
* (e.g. `security find-generic-password`) keeps working.
|
|
14
|
+
*
|
|
15
|
+
* Docker isolation never reaches this code path, and non-darwin platforms are
|
|
16
|
+
* left untouched. Set ZEROSHOT_ALLOW_INTERACTIVE_KEYCHAIN=1 to opt out.
|
|
17
|
+
*/
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
import fs = require('fs');
|
|
20
|
+
import os = require('os');
|
|
21
|
+
import path = require('path');
|
|
22
|
+
interface DarwinKeychainBoundaryOptions {
|
|
23
|
+
platform?: string;
|
|
24
|
+
shimBaseDir?: string;
|
|
25
|
+
realSecurityPath?: string;
|
|
26
|
+
}
|
|
27
|
+
const SHIM_DIR_RELATIVE_PATH = path.join('.zeroshot', 'keychain-shim');
|
|
28
|
+
const REAL_SECURITY_PATH = '/usr/bin/security';
|
|
29
|
+
const OPT_OUT_ENV_VAR = 'ZEROSHOT_ALLOW_INTERACTIVE_KEYCHAIN';
|
|
30
|
+
function shellQuote(value: unknown): string {
|
|
31
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
32
|
+
}
|
|
33
|
+
function errorMessage(error: unknown): string {
|
|
34
|
+
return error instanceof Error ? error.message : String(error);
|
|
35
|
+
}
|
|
36
|
+
function buildSecurityShimScript(realSecurityPath: string): string {
|
|
37
|
+
return `#!/bin/sh
|
|
38
|
+
# Managed by Zeroshot (src/darwin-keychain-boundary.js). Do not edit.
|
|
39
|
+
#
|
|
40
|
+
# Non-interactive Zeroshot workers must not open the logged-in user's GUI
|
|
41
|
+
# Keychain session (SecurityAgent). Interactive \`security\` invocations fail
|
|
42
|
+
# closed here; every other subcommand is passed through to the real binary so
|
|
43
|
+
# provider authentication keeps working.
|
|
44
|
+
REAL_SECURITY=${shellQuote(realSecurityPath)}
|
|
45
|
+
if [ "\${${OPT_OUT_ENV_VAR}:-0}" = "1" ]; then
|
|
46
|
+
exec "$REAL_SECURITY" "$@"
|
|
47
|
+
fi
|
|
48
|
+
fail_closed() {
|
|
49
|
+
echo "zeroshot: blocked interactive 'security' invocation from a non-interactive worker (argv: $*)." >&2
|
|
50
|
+
echo "zeroshot: this cluster has no interactive Keychain session, so SecurityAgent prompts are disabled." >&2
|
|
51
|
+
echo "zeroshot: run the cluster with Docker isolation or configure explicit credentials for the tool that "\
|
|
52
|
+
"attempted Keychain access." >&2
|
|
53
|
+
echo "zeroshot: set ${OPT_OUT_ENV_VAR}=1 to restore interactive Keychain access." >&2
|
|
54
|
+
exit 1
|
|
55
|
+
}
|
|
56
|
+
# \`security\` without arguments enters interactive mode.
|
|
57
|
+
[ "$#" -eq 0 ] && fail_closed
|
|
58
|
+
# Global options precede the subcommand. -i (interactive) and -p (prompt,
|
|
59
|
+
# implies -i) must not reach the real binary; option letters may be bundled
|
|
60
|
+
# (e.g. -qi). Scanning stops at the first non-option token (the subcommand).
|
|
61
|
+
for arg in "$@"; do
|
|
62
|
+
case "$arg" in
|
|
63
|
+
-*i*|-*p*) fail_closed "$@" ;;
|
|
64
|
+
-*) ;;
|
|
65
|
+
*) break ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
exec "$REAL_SECURITY" "$@"
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
/** Create or refresh the managed directory containing the `security` shim. */
|
|
72
|
+
function ensureDarwinKeychainShimDir(options: DarwinKeychainBoundaryOptions = {}): string {
|
|
73
|
+
const shimDir = options.shimBaseDir || path.join(os.homedir(), SHIM_DIR_RELATIVE_PATH);
|
|
74
|
+
const script = buildSecurityShimScript(options.realSecurityPath || REAL_SECURITY_PATH);
|
|
75
|
+
const shimPath = path.join(shimDir, 'security');
|
|
76
|
+
fs.mkdirSync(shimDir, { recursive: true });
|
|
77
|
+
let existing: string | null = null;
|
|
78
|
+
try {
|
|
79
|
+
existing = fs.readFileSync(shimPath, 'utf8');
|
|
80
|
+
} catch {
|
|
81
|
+
// Missing or unreadable: (re)write below.
|
|
82
|
+
}
|
|
83
|
+
if (existing !== script) {
|
|
84
|
+
const tempPath = path.join(shimDir, `.security.${process.pid}.${randomUUID()}.tmp`);
|
|
85
|
+
try {
|
|
86
|
+
fs.writeFileSync(tempPath, script, { mode: 0o755, flag: 'wx' });
|
|
87
|
+
// The creation mode is subject to umask. Set the final mode before the
|
|
88
|
+
// rename so the live path is never observable as non-executable.
|
|
89
|
+
fs.chmodSync(tempPath, 0o755);
|
|
90
|
+
fs.renameSync(tempPath, shimPath);
|
|
91
|
+
} catch (error: unknown) {
|
|
92
|
+
try {
|
|
93
|
+
// Remove any unpublished partial file without masking the publication
|
|
94
|
+
// failure that caused this cleanup path.
|
|
95
|
+
fs.rmSync(tempPath, { force: true });
|
|
96
|
+
} catch (cleanupError: unknown) {
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
error.message += ` Cleanup also failed: ${errorMessage(cleanupError)}.`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// An existing matching shim may have drifted permissions.
|
|
105
|
+
fs.chmodSync(shimPath, 0o755);
|
|
106
|
+
}
|
|
107
|
+
return shimDir;
|
|
108
|
+
}
|
|
109
|
+
/** Prepend the Keychain boundary shim to a worker spawn environment's PATH. */
|
|
110
|
+
function applyDarwinKeychainBoundaryToEnv(
|
|
111
|
+
env: NodeJS.ProcessEnv,
|
|
112
|
+
options: DarwinKeychainBoundaryOptions = {}
|
|
113
|
+
): NodeJS.ProcessEnv {
|
|
114
|
+
const platform = options.platform || process.platform;
|
|
115
|
+
if (platform !== 'darwin') {
|
|
116
|
+
return env;
|
|
117
|
+
}
|
|
118
|
+
if (env[OPT_OUT_ENV_VAR] === '1' || process.env[OPT_OUT_ENV_VAR] === '1') {
|
|
119
|
+
return env;
|
|
120
|
+
}
|
|
121
|
+
let shimDir: string;
|
|
122
|
+
try {
|
|
123
|
+
shimDir = ensureDarwinKeychainShimDir(options);
|
|
124
|
+
} catch (error: unknown) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Failed to install the darwin Keychain boundary shim: ${errorMessage(error)}. ` +
|
|
127
|
+
`Non-interactive workers must not reach the interactive Keychain session; ` +
|
|
128
|
+
`use Docker isolation or set ${OPT_OUT_ENV_VAR}=1 to opt out.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
// Darwin environment keys are case-sensitive: descendants consult PATH,
|
|
132
|
+
// never a differently-cased key such as Path. Preserve empty components
|
|
133
|
+
// because POSIX interprets them as the current directory. An absent PATH
|
|
134
|
+
// retains Node/libuv's default Unix search path after the shim, while an
|
|
135
|
+
// explicitly empty PATH retains its empty component (`<shim>:`).
|
|
136
|
+
const existingEntries =
|
|
137
|
+
env.PATH === undefined
|
|
138
|
+
? ['/usr/bin', '/bin']
|
|
139
|
+
: String(env.PATH)
|
|
140
|
+
.split(path.delimiter)
|
|
141
|
+
.filter((entry) => entry !== shimDir);
|
|
142
|
+
env.PATH = [shimDir, ...existingEntries].join(path.delimiter);
|
|
143
|
+
return env;
|
|
144
|
+
}
|
|
145
|
+
export = {
|
|
146
|
+
applyDarwinKeychainBoundaryToEnv,
|
|
147
|
+
ensureDarwinKeychainShimDir,
|
|
148
|
+
};
|
package/src/guidance-topics.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
const USER_GUIDANCE_CLUSTER = 'USER_GUIDANCE_CLUSTER';
|
|
2
3
|
const USER_GUIDANCE_AGENT = 'USER_GUIDANCE_AGENT';
|
|
3
|
-
|
|
4
4
|
const GUIDANCE_TOPICS = [USER_GUIDANCE_CLUSTER, USER_GUIDANCE_AGENT];
|
|
5
|
-
|
|
6
5
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
USER_GUIDANCE_CLUSTER,
|
|
7
|
+
USER_GUIDANCE_AGENT,
|
|
8
|
+
GUIDANCE_TOPICS,
|
|
10
9
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const USER_GUIDANCE_CLUSTER = 'USER_GUIDANCE_CLUSTER';
|
|
2
|
+
const USER_GUIDANCE_AGENT = 'USER_GUIDANCE_AGENT';
|
|
3
|
+
|
|
4
|
+
const GUIDANCE_TOPICS = [USER_GUIDANCE_CLUSTER, USER_GUIDANCE_AGENT] as const;
|
|
5
|
+
|
|
6
|
+
export = {
|
|
7
|
+
USER_GUIDANCE_CLUSTER,
|
|
8
|
+
USER_GUIDANCE_AGENT,
|
|
9
|
+
GUIDANCE_TOPICS,
|
|
10
|
+
};
|
package/src/input-helpers.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
1
4
|
/**
|
|
2
5
|
* Input Helpers - Create input data from text or files
|
|
3
6
|
*
|
|
@@ -5,61 +8,37 @@
|
|
|
5
8
|
* - Plain text input
|
|
6
9
|
* - File input (markdown)
|
|
7
10
|
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
11
|
class InputHelpers {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
12
|
+
static createTextInput(text) {
|
|
13
|
+
return {
|
|
14
|
+
number: null,
|
|
15
|
+
title: 'Manual Input',
|
|
16
|
+
body: text,
|
|
17
|
+
labels: [],
|
|
18
|
+
comments: [],
|
|
19
|
+
url: null,
|
|
20
|
+
context: `# Manual Input\n\n${text}\n`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
static createFileInput(filePath) {
|
|
24
|
+
const resolvedPath = path.resolve(filePath);
|
|
25
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
26
|
+
throw new Error(`File not found: ${filePath}`);
|
|
27
|
+
}
|
|
28
|
+
const fileContent = fs.readFileSync(resolvedPath, 'utf8');
|
|
29
|
+
const headerMatch = /^#\s+(.+)$/m.exec(fileContent);
|
|
30
|
+
const extractedTitle = headerMatch?.[1]?.trim() ?? null;
|
|
31
|
+
const fallbackTitle = path.basename(filePath, path.extname(filePath));
|
|
32
|
+
const title = extractedTitle || fallbackTitle;
|
|
33
|
+
return {
|
|
34
|
+
number: null,
|
|
35
|
+
title,
|
|
36
|
+
body: fileContent,
|
|
37
|
+
labels: [],
|
|
38
|
+
comments: [],
|
|
39
|
+
url: null,
|
|
40
|
+
context: `# ${title}\n\n${fileContent}\n`,
|
|
41
|
+
};
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
// Read file content
|
|
45
|
-
const fileContent = fs.readFileSync(resolvedPath, 'utf8');
|
|
46
|
-
|
|
47
|
-
// Extract title from first header or use filename
|
|
48
|
-
const headerMatch = fileContent.match(/^#\s+(.+)$/m);
|
|
49
|
-
const extractedTitle = headerMatch ? headerMatch[1].trim() : null;
|
|
50
|
-
const fallbackTitle = path.basename(filePath, path.extname(filePath));
|
|
51
|
-
const title = extractedTitle || fallbackTitle;
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
number: null,
|
|
55
|
-
title,
|
|
56
|
-
body: fileContent,
|
|
57
|
-
labels: [],
|
|
58
|
-
comments: [],
|
|
59
|
-
url: null,
|
|
60
|
-
context: `# ${title}\n\n${fileContent}\n`,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
43
|
}
|
|
64
|
-
|
|
65
44
|
module.exports = InputHelpers;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs = require('fs');
|
|
2
|
+
import path = require('path');
|
|
3
|
+
|
|
4
|
+
interface ManualInput {
|
|
5
|
+
number: null;
|
|
6
|
+
title: string;
|
|
7
|
+
body: string;
|
|
8
|
+
labels: unknown[];
|
|
9
|
+
comments: unknown[];
|
|
10
|
+
url: null;
|
|
11
|
+
context: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Input Helpers - Create input data from text or files
|
|
16
|
+
*
|
|
17
|
+
* Provides fallback input methods for non-issue-based input:
|
|
18
|
+
* - Plain text input
|
|
19
|
+
* - File input (markdown)
|
|
20
|
+
*/
|
|
21
|
+
class InputHelpers {
|
|
22
|
+
static createTextInput(text: string): ManualInput {
|
|
23
|
+
return {
|
|
24
|
+
number: null,
|
|
25
|
+
title: 'Manual Input',
|
|
26
|
+
body: text,
|
|
27
|
+
labels: [],
|
|
28
|
+
comments: [],
|
|
29
|
+
url: null,
|
|
30
|
+
context: `# Manual Input\n\n${text}\n`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static createFileInput(filePath: string): ManualInput {
|
|
35
|
+
const resolvedPath = path.resolve(filePath);
|
|
36
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
37
|
+
throw new Error(`File not found: ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const fileContent = fs.readFileSync(resolvedPath, 'utf8');
|
|
41
|
+
const headerMatch = /^#\s+(.+)$/m.exec(fileContent);
|
|
42
|
+
const extractedTitle = headerMatch?.[1]?.trim() ?? null;
|
|
43
|
+
const fallbackTitle = path.basename(filePath, path.extname(filePath));
|
|
44
|
+
const title = extractedTitle || fallbackTitle;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
number: null,
|
|
48
|
+
title,
|
|
49
|
+
body: fileContent,
|
|
50
|
+
labels: [],
|
|
51
|
+
comments: [],
|
|
52
|
+
url: null,
|
|
53
|
+
context: `# ${title}\n\n${fileContent}\n`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export = InputHelpers;
|
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 };
|