@cydm/happy-elves 0.1.0-beta.7 → 0.1.0-beta.71
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 +9 -1
- package/apps/cli/dist/commands/account.d.ts +4 -0
- package/apps/cli/dist/commands/account.js +349 -35
- package/apps/cli/dist/commands/app.js +32 -21
- package/apps/cli/dist/commands/collect.js +8 -3
- package/apps/cli/dist/commands/command.js +0 -1
- package/apps/cli/dist/commands/config.js +12 -5
- package/apps/cli/dist/commands/daemon.js +386 -87
- package/apps/cli/dist/commands/gateway.d.ts +2 -0
- package/apps/cli/dist/commands/gateway.js +1252 -0
- package/apps/cli/dist/commands/interaction.d.ts +2 -0
- package/apps/cli/dist/commands/interaction.js +416 -0
- package/apps/cli/dist/commands/lib/args.js +86 -2
- package/apps/cli/dist/commands/lib/audit.js +2 -2
- package/apps/cli/dist/commands/lib/bootstrap-config.d.ts +26 -13
- package/apps/cli/dist/commands/lib/bootstrap-config.js +94 -80
- package/apps/cli/dist/commands/lib/bootstrap-daemon.d.ts +61 -10
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +408 -30
- package/apps/cli/dist/commands/lib/bootstrap-join.d.ts +1 -0
- package/apps/cli/dist/commands/lib/bootstrap-join.js +199 -0
- package/apps/cli/dist/commands/lib/bootstrap-output.d.ts +2 -5
- package/apps/cli/dist/commands/lib/bootstrap-output.js +7 -11
- package/apps/cli/dist/commands/lib/bootstrap.js +241 -43
- package/apps/cli/dist/commands/lib/cli-exit.d.ts +6 -0
- package/apps/cli/dist/commands/lib/cli-exit.js +15 -0
- package/apps/cli/dist/commands/lib/config.d.ts +34 -3
- package/apps/cli/dist/commands/lib/config.js +781 -27
- package/apps/cli/dist/commands/lib/daemon-enrollment-intent.d.ts +47 -0
- package/apps/cli/dist/commands/lib/daemon-enrollment-intent.js +257 -0
- package/apps/cli/dist/commands/lib/doctor.d.ts +14 -0
- package/apps/cli/dist/commands/lib/doctor.js +297 -34
- package/apps/cli/dist/commands/lib/exit.d.ts +1 -0
- package/apps/cli/dist/commands/lib/exit.js +143 -14
- package/apps/cli/dist/commands/lib/index.d.ts +7 -3
- package/apps/cli/dist/commands/lib/index.js +7 -4
- package/apps/cli/dist/commands/lib/json.d.ts +2 -2
- package/apps/cli/dist/commands/lib/json.js +8 -3
- package/apps/cli/dist/commands/lib/local-daemon.d.ts +53 -8
- package/apps/cli/dist/commands/lib/local-daemon.js +168 -72
- package/apps/cli/dist/commands/lib/loop-store.d.ts +4 -0
- package/apps/cli/dist/commands/lib/loop-store.js +29 -19
- package/apps/cli/dist/commands/lib/orchestrator.d.ts +41 -0
- package/apps/cli/dist/commands/lib/orchestrator.js +205 -0
- package/apps/cli/dist/commands/lib/paths.d.ts +2 -3
- package/apps/cli/dist/commands/lib/paths.js +8 -5
- package/apps/cli/dist/commands/lib/relay-http.d.ts +5 -6
- package/apps/cli/dist/commands/lib/relay-http.js +22 -31
- package/apps/cli/dist/commands/lib/scope.d.ts +17 -5
- package/apps/cli/dist/commands/lib/scope.js +68 -12
- package/apps/cli/dist/commands/lib/session-output.d.ts +42 -1
- package/apps/cli/dist/commands/lib/session-output.js +243 -15
- package/apps/cli/dist/commands/lib/session-view.d.ts +38 -7
- package/apps/cli/dist/commands/lib/session-view.js +116 -22
- package/apps/cli/dist/commands/lib/status.js +56 -17
- package/apps/cli/dist/commands/lib/types.d.ts +48 -6
- package/apps/cli/dist/commands/lib/types.js +0 -1
- package/apps/cli/dist/commands/lib/usage.d.ts +1 -0
- package/apps/cli/dist/commands/lib/usage.js +337 -145
- package/apps/cli/dist/commands/lib/version.d.ts +8 -0
- package/apps/cli/dist/commands/lib/version.js +69 -0
- package/apps/cli/dist/commands/lib/workspace.d.ts +5 -9
- package/apps/cli/dist/commands/lib/workspace.js +52 -35
- package/apps/cli/dist/commands/loop.js +274 -117
- package/apps/cli/dist/commands/machine.js +240 -44
- package/apps/cli/dist/commands/memory.d.ts +3 -0
- package/apps/cli/dist/commands/memory.js +250 -0
- package/apps/cli/dist/commands/orchestrator.d.ts +22 -0
- package/apps/cli/dist/commands/orchestrator.js +1281 -0
- package/apps/cli/dist/commands/relay.js +37 -11
- package/apps/cli/dist/commands/remote.js +143 -27
- package/apps/cli/dist/commands/session.d.ts +26 -0
- package/apps/cli/dist/commands/session.js +922 -108
- package/apps/cli/dist/commands/skill.d.ts +10 -0
- package/apps/cli/dist/commands/skill.js +351 -0
- package/apps/cli/dist/commands/token.js +25 -3
- package/apps/cli/dist/commands/turn.d.ts +2 -0
- package/apps/cli/dist/commands/turn.js +268 -0
- package/apps/cli/dist/errors.js +0 -1
- package/apps/cli/dist/index.js +13 -3
- package/apps/cli/dist/skills/registry.d.ts +21 -0
- package/apps/cli/dist/skills/registry.js +312 -0
- package/apps/daemon/dist/account-config-lock.d.ts +14 -0
- package/apps/daemon/dist/account-config-lock.js +360 -0
- package/apps/daemon/dist/agent-interaction-ipc.d.ts +45 -0
- package/apps/daemon/dist/agent-interaction-ipc.js +578 -0
- package/apps/daemon/dist/agent-interaction-mcp.d.ts +6 -0
- package/apps/daemon/dist/agent-interaction-mcp.js +259 -0
- package/apps/daemon/dist/app.js +0 -1
- package/apps/daemon/dist/args.js +1 -2
- package/apps/daemon/dist/audit.d.ts +1 -1
- package/apps/daemon/dist/audit.js +4 -3
- package/apps/daemon/dist/cli.js +0 -1
- package/apps/daemon/dist/config.d.ts +3 -2
- package/apps/daemon/dist/config.js +91 -11
- package/apps/daemon/dist/errors.js +0 -1
- package/apps/daemon/dist/gateway/adapters.d.ts +45 -0
- package/apps/daemon/dist/gateway/adapters.js +1 -0
- package/apps/daemon/dist/gateway/commands.d.ts +3 -0
- package/apps/daemon/dist/gateway/commands.js +542 -0
- package/apps/daemon/dist/gateway/fake.d.ts +2 -0
- package/apps/daemon/dist/gateway/fake.js +24 -0
- package/apps/daemon/dist/gateway/feishu.d.ts +81 -0
- package/apps/daemon/dist/gateway/feishu.js +546 -0
- package/apps/daemon/dist/gateway/format.d.ts +9 -0
- package/apps/daemon/dist/gateway/format.js +33 -0
- package/apps/daemon/dist/gateway/runtime.d.ts +33 -0
- package/apps/daemon/dist/gateway/runtime.js +2207 -0
- package/apps/daemon/dist/gateway/store.d.ts +29 -0
- package/apps/daemon/dist/gateway/store.js +64 -0
- package/apps/daemon/dist/gateway/telegram.d.ts +9 -0
- package/apps/daemon/dist/gateway/telegram.js +387 -0
- package/apps/daemon/dist/interaction-claim-history.d.ts +4 -0
- package/apps/daemon/dist/interaction-claim-history.js +51 -0
- package/apps/daemon/dist/interaction-claim-journal.d.ts +20 -0
- package/apps/daemon/dist/interaction-claim-journal.js +139 -0
- package/apps/daemon/dist/interaction-local-state.d.ts +3 -0
- package/apps/daemon/dist/interaction-local-state.js +12 -0
- package/apps/daemon/dist/interaction-privacy.d.ts +10 -0
- package/apps/daemon/dist/interaction-privacy.js +23 -0
- package/apps/daemon/dist/interaction-prompt-redaction-cache.d.ts +7 -0
- package/apps/daemon/dist/interaction-prompt-redaction-cache.js +254 -0
- package/apps/daemon/dist/interaction-runtime.d.ts +58 -0
- package/apps/daemon/dist/interaction-runtime.js +674 -0
- package/apps/daemon/dist/lifecycle/artifacts.d.ts +44 -0
- package/apps/daemon/dist/lifecycle/artifacts.js +588 -0
- package/apps/daemon/dist/lifecycle/capability.d.ts +25 -0
- package/apps/daemon/dist/lifecycle/capability.js +290 -0
- package/apps/daemon/dist/lifecycle/detached-helper-bundle.mjs +18505 -0
- package/apps/daemon/dist/lifecycle/detached-helper-cli.d.ts +1 -0
- package/apps/daemon/dist/lifecycle/detached-helper-cli.js +11 -0
- package/apps/daemon/dist/lifecycle/errors.d.ts +3 -0
- package/apps/daemon/dist/lifecycle/errors.js +10 -0
- package/apps/daemon/dist/lifecycle/helper.d.ts +39 -0
- package/apps/daemon/dist/lifecycle/helper.js +403 -0
- package/apps/daemon/dist/lifecycle/index.d.ts +7 -0
- package/apps/daemon/dist/lifecycle/index.js +6 -0
- package/apps/daemon/dist/lifecycle/journal.d.ts +21 -0
- package/apps/daemon/dist/lifecycle/journal.js +389 -0
- package/apps/daemon/dist/lifecycle/npm.d.ts +37 -0
- package/apps/daemon/dist/lifecycle/npm.js +164 -0
- package/apps/daemon/dist/lifecycle/operation.d.ts +19 -0
- package/apps/daemon/dist/lifecycle/operation.js +175 -0
- package/apps/daemon/dist/lifecycle/relay.d.ts +5 -0
- package/apps/daemon/dist/lifecycle/relay.js +72 -0
- package/apps/daemon/dist/lifecycle/reporter.d.ts +29 -0
- package/apps/daemon/dist/lifecycle/reporter.js +402 -0
- package/apps/daemon/dist/lifecycle/restart.d.ts +127 -0
- package/apps/daemon/dist/lifecycle/restart.js +650 -0
- package/apps/daemon/dist/lifecycle/runtime.d.ts +36 -0
- package/apps/daemon/dist/lifecycle/runtime.js +210 -0
- package/apps/daemon/dist/lifecycle/types.d.ts +110 -0
- package/apps/daemon/dist/lifecycle/types.js +3 -0
- package/apps/daemon/dist/loop/handlers.d.ts +5 -0
- package/apps/daemon/dist/loop/handlers.js +406 -0
- package/apps/daemon/dist/loop/runner.js +670 -62
- package/apps/daemon/dist/loop/schedule.d.ts +34 -0
- package/apps/daemon/dist/loop/schedule.js +161 -0
- package/apps/daemon/dist/loop/store.d.ts +11 -2
- package/apps/daemon/dist/loop/store.js +119 -35
- package/apps/daemon/dist/memory/access-lifecycle.d.ts +15 -0
- package/apps/daemon/dist/memory/access-lifecycle.js +85 -0
- package/apps/daemon/dist/memory/commands.d.ts +3 -0
- package/apps/daemon/dist/memory/commands.js +184 -0
- package/apps/daemon/dist/memory/config.d.ts +7 -0
- package/apps/daemon/dist/memory/config.js +24 -0
- package/apps/daemon/dist/memory/flush.d.ts +10 -0
- package/apps/daemon/dist/memory/flush.js +158 -0
- package/apps/daemon/dist/memory/inject.d.ts +23 -0
- package/apps/daemon/dist/memory/inject.js +57 -0
- package/apps/daemon/dist/memory/instructions.d.ts +9 -0
- package/apps/daemon/dist/memory/instructions.js +47 -0
- package/apps/daemon/dist/memory/managers.d.ts +8 -0
- package/apps/daemon/dist/memory/managers.js +68 -0
- package/apps/daemon/dist/pair.js +149 -23
- package/apps/daemon/dist/paths.d.ts +10 -3
- package/apps/daemon/dist/paths.js +10 -4
- package/apps/daemon/dist/process.d.ts +44 -8
- package/apps/daemon/dist/process.js +526 -47
- package/apps/daemon/dist/relay/connection-diagnostics.d.ts +30 -0
- package/apps/daemon/dist/relay/connection-diagnostics.js +60 -0
- package/apps/daemon/dist/relay/connection.d.ts +1 -1
- package/apps/daemon/dist/relay/connection.js +218 -18
- package/apps/daemon/dist/relay/devtools.d.ts +4 -0
- package/apps/daemon/dist/relay/devtools.js +359 -0
- package/apps/daemon/dist/relay/register.d.ts +3 -0
- package/apps/daemon/dist/relay/register.js +84 -3
- package/apps/daemon/dist/relay/send.d.ts +26 -5
- package/apps/daemon/dist/relay/send.js +178 -21
- package/apps/daemon/dist/relay-http.d.ts +1 -1
- package/apps/daemon/dist/relay-http.js +4 -13
- package/apps/daemon/dist/runtime/cancel-settlement.d.ts +19 -0
- package/apps/daemon/dist/runtime/cancel-settlement.js +191 -0
- package/apps/daemon/dist/runtime/errors.d.ts +3 -0
- package/apps/daemon/dist/runtime/errors.js +37 -0
- package/apps/daemon/dist/runtime/event-pump.d.ts +14 -0
- package/apps/daemon/dist/runtime/event-pump.js +38 -0
- package/apps/daemon/dist/runtime/external-provider.d.ts +20 -0
- package/apps/daemon/dist/runtime/external-provider.js +941 -0
- package/apps/daemon/dist/runtime/selection.d.ts +10 -0
- package/apps/daemon/dist/runtime/selection.js +33 -0
- package/apps/daemon/dist/runtime/session-recovery.d.ts +10 -0
- package/apps/daemon/dist/runtime/session-recovery.js +130 -0
- package/apps/daemon/dist/runtime.d.ts +3 -1
- package/apps/daemon/dist/runtime.js +24 -6
- package/apps/daemon/dist/session/create-session.d.ts +1 -1
- package/apps/daemon/dist/session/create-session.js +9 -3
- package/apps/daemon/dist/session/directory.d.ts +7 -2
- package/apps/daemon/dist/session/directory.js +117 -16
- package/apps/daemon/dist/session/events.d.ts +33 -3
- package/apps/daemon/dist/session/events.js +194 -16
- package/apps/daemon/dist/session/file-preview.d.ts +4 -0
- package/apps/daemon/dist/session/file-preview.js +169 -0
- package/apps/daemon/dist/session/lifecycle.d.ts +26 -3
- package/apps/daemon/dist/session/lifecycle.js +773 -90
- package/apps/daemon/dist/session/metadata.d.ts +6 -2
- package/apps/daemon/dist/session/metadata.js +149 -11
- package/apps/daemon/dist/session/primitives.d.ts +1 -1
- package/apps/daemon/dist/session/primitives.js +132 -18
- package/apps/daemon/dist/session/prompt.d.ts +1 -1
- package/apps/daemon/dist/session/prompt.js +559 -124
- package/apps/daemon/dist/session/runtime-handle.d.ts +1 -1
- package/apps/daemon/dist/session/runtime-handle.js +6 -2
- package/apps/daemon/dist/session/workspace-paths.d.ts +3 -0
- package/apps/daemon/dist/session/workspace-paths.js +30 -0
- package/apps/daemon/dist/start.js +268 -17
- package/apps/daemon/dist/state.d.ts +10 -2
- package/apps/daemon/dist/state.js +0 -1
- package/apps/daemon/dist/turn-coordinator.d.ts +10 -1
- package/apps/daemon/dist/turn-coordinator.js +31 -2
- package/apps/daemon/dist/types.d.ts +137 -9
- package/apps/daemon/dist/types.js +0 -1
- package/apps/daemon/dist/utils.js +0 -1
- package/apps/daemon/package.json +3 -23
- package/apps/relay/dist/account-access-revocation.d.ts +39 -0
- package/apps/relay/dist/account-access-revocation.js +67 -0
- package/apps/relay/dist/account-http-routes.d.ts +3 -0
- package/apps/relay/dist/account-http-routes.js +650 -0
- package/apps/relay/dist/account-projections.d.ts +40 -0
- package/apps/relay/dist/account-projections.js +96 -0
- package/apps/relay/dist/auth.js +13 -3
- package/apps/relay/dist/connections.d.ts +56 -5
- package/apps/relay/dist/connections.js +1 -1
- package/apps/relay/dist/controller-handlers.d.ts +89 -10
- package/apps/relay/dist/controller-handlers.js +1614 -72
- package/apps/relay/dist/db.d.ts +6 -0
- package/apps/relay/dist/db.js +357 -6
- package/apps/relay/dist/enrollment-repository.d.ts +61 -0
- package/apps/relay/dist/enrollment-repository.js +113 -0
- package/apps/relay/dist/errors.js +0 -1
- package/apps/relay/dist/event-visibility.d.ts +7 -0
- package/apps/relay/dist/event-visibility.js +46 -0
- package/apps/relay/dist/http-routes.d.ts +1 -1
- package/apps/relay/dist/http-routes.js +781 -78
- package/apps/relay/dist/http-schemas.d.ts +98 -11
- package/apps/relay/dist/http-schemas.js +40 -13
- package/apps/relay/dist/index.js +60 -11
- package/apps/relay/dist/machine-command-result-handlers.d.ts +24 -0
- package/apps/relay/dist/machine-command-result-handlers.js +1010 -0
- package/apps/relay/dist/machine-handler-context.d.ts +85 -0
- package/apps/relay/dist/machine-handler-context.js +1 -0
- package/apps/relay/dist/machine-handlers.d.ts +3 -33
- package/apps/relay/dist/machine-handlers.js +9 -235
- package/apps/relay/dist/machine-reliability.d.ts +7 -0
- package/apps/relay/dist/machine-reliability.js +12 -0
- package/apps/relay/dist/metadata-source.d.ts +3 -0
- package/apps/relay/dist/metadata-source.js +16 -0
- package/apps/relay/dist/projection-repository.d.ts +80 -0
- package/apps/relay/dist/projection-repository.js +214 -0
- package/apps/relay/dist/projections.d.ts +2 -2
- package/apps/relay/dist/projections.js +91 -5
- package/apps/relay/dist/relay-context.d.ts +104 -14
- package/apps/relay/dist/relay-context.js +729 -83
- package/apps/relay/dist/repositories.d.ts +5 -3
- package/apps/relay/dist/repositories.js +38 -14
- package/apps/relay/dist/retention.d.ts +6 -0
- package/apps/relay/dist/retention.js +26 -3
- package/apps/relay/dist/scope.d.ts +15 -2
- package/apps/relay/dist/scope.js +177 -15
- package/apps/relay/dist/security.js +0 -1
- package/apps/relay/dist/session-projection-reducer.d.ts +4 -0
- package/apps/relay/dist/session-projection-reducer.js +541 -0
- package/apps/relay/dist/session-record-handlers.d.ts +15 -0
- package/apps/relay/dist/session-record-handlers.js +148 -0
- package/apps/relay/dist/session-record-repository.d.ts +38 -0
- package/apps/relay/dist/session-record-repository.js +312 -0
- package/apps/relay/dist/types.d.ts +53 -12
- package/apps/relay/dist/types.js +0 -1
- package/apps/relay/dist/websocket.js +122 -25
- package/build-identity.json +6 -0
- package/npm-shrinkwrap.json +2226 -0
- package/package.json +32 -14
- package/packages/agent-sdk/dist/index.d.ts +130 -0
- package/packages/agent-sdk/dist/index.js +198 -0
- package/packages/client/dist/account-management.d.ts +64 -0
- package/packages/client/dist/account-management.js +486 -0
- package/packages/client/dist/account.d.ts +11 -5
- package/packages/client/dist/account.js +57 -31
- package/packages/client/dist/client.d.ts +100 -6
- package/packages/client/dist/client.js +1051 -26
- package/packages/client/dist/errors.d.ts +26 -3
- package/packages/client/dist/errors.js +25 -3
- package/packages/client/dist/http.d.ts +2 -2
- package/packages/client/dist/http.js +29 -21
- package/packages/client/dist/index.d.ts +10 -2
- package/packages/client/dist/index.js +5 -1
- package/packages/client/dist/live-session-state.d.ts +15 -0
- package/packages/client/dist/live-session-state.js +60 -0
- package/packages/client/dist/live-state.d.ts +8 -0
- package/packages/client/dist/live-state.js +37 -0
- package/packages/client/dist/live-types.d.ts +155 -0
- package/packages/client/dist/live-types.js +1 -0
- package/packages/client/dist/live.d.ts +118 -0
- package/packages/client/dist/live.js +1654 -0
- package/packages/client/dist/parsers.d.ts +3 -5
- package/packages/client/dist/parsers.js +34 -37
- package/packages/client/dist/schedule-presets.d.ts +17 -0
- package/packages/client/dist/schedule-presets.js +33 -0
- package/packages/client/dist/session-event-order.d.ts +16 -0
- package/packages/client/dist/session-event-order.js +83 -0
- package/packages/client/dist/session-event-window.d.ts +5 -0
- package/packages/client/dist/session-event-window.js +54 -0
- package/packages/client/dist/transport.d.ts +35 -7
- package/packages/client/dist/transport.js +426 -81
- package/packages/client/dist/types.d.ts +305 -5
- package/packages/client/dist/types.js +0 -1
- package/packages/client/dist/validation.d.ts +2 -2
- package/packages/client/dist/validation.js +11 -1
- package/packages/memory/dist/access.d.ts +44 -0
- package/packages/memory/dist/access.js +357 -0
- package/packages/memory/dist/chunk.d.ts +16 -0
- package/packages/memory/dist/chunk.js +51 -0
- package/packages/memory/dist/embeddings.d.ts +10 -0
- package/packages/memory/dist/embeddings.js +77 -0
- package/packages/memory/dist/index.d.ts +6 -0
- package/packages/memory/dist/index.js +6 -0
- package/packages/memory/dist/manager.d.ts +53 -0
- package/packages/memory/dist/manager.js +689 -0
- package/packages/memory/dist/paths.d.ts +11 -0
- package/packages/memory/dist/paths.js +42 -0
- package/packages/memory/dist/store.d.ts +19 -0
- package/packages/memory/dist/store.js +210 -0
- package/packages/memory/dist/types.d.ts +70 -0
- package/packages/memory/dist/types.js +8 -0
- package/packages/pie-provider/dist/index.d.ts +2 -0
- package/packages/pie-provider/dist/index.js +1228 -0
- package/packages/provider-protocol/dist/index.d.ts +920 -0
- package/packages/provider-protocol/dist/index.js +355 -0
- package/packages/runtime/dist/index.d.ts +48 -98
- package/packages/runtime/dist/index.js +0 -1
- package/packages/runtime-acpx/dist/index.d.ts +2 -3
- package/packages/runtime-acpx/dist/index.js +70 -2
- package/packages/runtime-cli/dist/async-queue.d.ts +0 -1
- package/packages/runtime-cli/dist/async-queue.js +0 -1
- package/packages/runtime-cli/dist/backend-session-lease.d.ts +40 -0
- package/packages/runtime-cli/dist/backend-session-lease.js +561 -0
- package/packages/runtime-cli/dist/claude-history.d.ts +2 -3
- package/packages/runtime-cli/dist/claude-history.js +137 -18
- package/packages/runtime-cli/dist/claude-json.d.ts +7 -1
- package/packages/runtime-cli/dist/claude-json.js +7 -1
- package/packages/runtime-cli/dist/claude-rename.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-rename.js +0 -1
- package/packages/runtime-cli/dist/claude-session-store.d.ts +3 -3
- package/packages/runtime-cli/dist/claude-session-store.js +28 -12
- package/packages/runtime-cli/dist/claude-turn.d.ts +13 -3
- package/packages/runtime-cli/dist/claude-turn.js +281 -17
- package/packages/runtime-cli/dist/codex-app-server.d.ts +56 -7
- package/packages/runtime-cli/dist/codex-app-server.js +829 -46
- package/packages/runtime-cli/dist/codex-approvals.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-approvals.js +0 -1
- package/packages/runtime-cli/dist/codex-history.d.ts +13 -4
- package/packages/runtime-cli/dist/codex-history.js +592 -15
- package/packages/runtime-cli/dist/codex-json-rpc.d.ts +30 -2
- package/packages/runtime-cli/dist/codex-json-rpc.js +282 -16
- package/packages/runtime-cli/dist/codex-lifecycle.d.ts +0 -1
- package/packages/runtime-cli/dist/codex-lifecycle.js +16 -4
- package/packages/runtime-cli/dist/codex-protocol.d.ts +4 -4
- package/packages/runtime-cli/dist/codex-protocol.js +43 -8
- package/packages/runtime-cli/dist/command-resolver.d.ts +23 -0
- package/packages/runtime-cli/dist/command-resolver.js +100 -0
- package/packages/runtime-cli/dist/history-merge.d.ts +17 -0
- package/packages/runtime-cli/dist/history-merge.js +286 -0
- package/packages/runtime-cli/dist/index.d.ts +7 -3
- package/packages/runtime-cli/dist/index.js +385 -59
- package/packages/runtime-cli/dist/permissions.d.ts +1 -2
- package/packages/runtime-cli/dist/permissions.js +3 -4
- package/packages/runtime-cli/dist/process-identity.d.ts +15 -0
- package/packages/runtime-cli/dist/process-identity.js +102 -0
- package/packages/runtime-cli/dist/runtime-status.d.ts +20 -0
- package/packages/runtime-cli/dist/runtime-status.js +133 -0
- package/packages/runtime-cli/dist/session-environment.d.ts +7 -0
- package/packages/runtime-cli/dist/session-environment.js +24 -0
- package/packages/runtime-cli/dist/session-store.d.ts +41 -2
- package/packages/runtime-cli/dist/session-store.js +232 -22
- package/packages/shared/dist/account.d.ts +184 -0
- package/packages/shared/dist/account.js +205 -0
- package/packages/shared/dist/args.d.ts +0 -1
- package/packages/shared/dist/args.js +4 -1
- package/packages/shared/dist/crypto.d.ts +234 -1
- package/packages/shared/dist/crypto.js +547 -1
- package/packages/shared/dist/daemon-lifecycle.d.ts +85 -0
- package/packages/shared/dist/daemon-lifecycle.js +66 -0
- package/packages/shared/dist/diagnostics.d.ts +21 -0
- package/packages/shared/dist/diagnostics.js +156 -0
- package/packages/shared/dist/ids.d.ts +0 -1
- package/packages/shared/dist/ids.js +0 -1
- package/packages/shared/dist/index.d.ts +5 -1
- package/packages/shared/dist/index.js +5 -1
- package/packages/shared/dist/interaction.d.ts +95 -0
- package/packages/shared/dist/interaction.js +169 -0
- package/packages/shared/dist/node/daemon-process.d.ts +91 -0
- package/packages/shared/dist/node/daemon-process.js +485 -0
- package/packages/shared/dist/node/gateway-store.d.ts +265 -0
- package/packages/shared/dist/node/gateway-store.js +2413 -0
- package/packages/shared/dist/process-command.d.ts +6 -0
- package/packages/shared/dist/process-command.js +113 -0
- package/packages/shared/dist/protocol-schemas.d.ts +467 -15
- package/packages/shared/dist/protocol-schemas.js +242 -1
- package/packages/shared/dist/protocol-types.d.ts +413 -1
- package/packages/shared/dist/protocol-types.js +0 -1
- package/packages/shared/dist/protocol.d.ts +416 -7
- package/packages/shared/dist/protocol.js +467 -5
- package/packages/shared/dist/relay-url.d.ts +8 -0
- package/packages/shared/dist/relay-url.js +37 -0
- package/packages/shared/dist/session-name.d.ts +0 -1
- package/packages/shared/dist/session-name.js +0 -1
- package/packages/shared/dist/session-record.d.ts +2 -0
- package/packages/shared/dist/session-record.js +4 -0
- package/packages/shared/dist/session-state.d.ts +0 -1
- package/packages/shared/dist/session-state.js +3 -2
- package/apps/cli/dist/args.d.ts +0 -5
- package/apps/cli/dist/args.js +0 -73
- package/apps/cli/dist/args.js.map +0 -1
- package/apps/cli/dist/commands/account.js.map +0 -1
- package/apps/cli/dist/commands/app.js.map +0 -1
- package/apps/cli/dist/commands/cancel.d.ts +0 -2
- package/apps/cli/dist/commands/cancel.js +0 -15
- package/apps/cli/dist/commands/cancel.js.map +0 -1
- package/apps/cli/dist/commands/collect.js.map +0 -1
- package/apps/cli/dist/commands/command.js.map +0 -1
- package/apps/cli/dist/commands/config.js.map +0 -1
- package/apps/cli/dist/commands/daemon.js.map +0 -1
- package/apps/cli/dist/commands/lib/args.js.map +0 -1
- package/apps/cli/dist/commands/lib/audit.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-config.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-output.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap.js.map +0 -1
- package/apps/cli/dist/commands/lib/config.js.map +0 -1
- package/apps/cli/dist/commands/lib/doctor.js.map +0 -1
- package/apps/cli/dist/commands/lib/exit.js.map +0 -1
- package/apps/cli/dist/commands/lib/index.js.map +0 -1
- package/apps/cli/dist/commands/lib/json.js.map +0 -1
- package/apps/cli/dist/commands/lib/local-daemon.js.map +0 -1
- package/apps/cli/dist/commands/lib/loop-store.js.map +0 -1
- package/apps/cli/dist/commands/lib/paths.js.map +0 -1
- package/apps/cli/dist/commands/lib/relay-http.js.map +0 -1
- package/apps/cli/dist/commands/lib/scope.js.map +0 -1
- package/apps/cli/dist/commands/lib/session-output.js.map +0 -1
- package/apps/cli/dist/commands/lib/session-view.js.map +0 -1
- package/apps/cli/dist/commands/lib/status.js.map +0 -1
- package/apps/cli/dist/commands/lib/types.js.map +0 -1
- package/apps/cli/dist/commands/lib/usage.js.map +0 -1
- package/apps/cli/dist/commands/lib/workspace.js.map +0 -1
- package/apps/cli/dist/commands/loop.js.map +0 -1
- package/apps/cli/dist/commands/machine.js.map +0 -1
- package/apps/cli/dist/commands/relay.js.map +0 -1
- package/apps/cli/dist/commands/remote.js.map +0 -1
- package/apps/cli/dist/commands/result.d.ts +0 -2
- package/apps/cli/dist/commands/result.js +0 -19
- package/apps/cli/dist/commands/result.js.map +0 -1
- package/apps/cli/dist/commands/run.d.ts +0 -2
- package/apps/cli/dist/commands/run.js +0 -47
- package/apps/cli/dist/commands/run.js.map +0 -1
- package/apps/cli/dist/commands/session.js.map +0 -1
- package/apps/cli/dist/commands/token.js.map +0 -1
- package/apps/cli/dist/commands/wait.d.ts +0 -2
- package/apps/cli/dist/commands/wait.js +0 -20
- package/apps/cli/dist/commands/wait.js.map +0 -1
- package/apps/cli/dist/commands/workspace.d.ts +0 -2
- package/apps/cli/dist/commands/workspace.js +0 -30
- package/apps/cli/dist/commands/workspace.js.map +0 -1
- package/apps/cli/dist/errors.js.map +0 -1
- package/apps/cli/dist/index.js.map +0 -1
- package/apps/daemon/dist/app.js.map +0 -1
- package/apps/daemon/dist/args.js.map +0 -1
- package/apps/daemon/dist/audit.js.map +0 -1
- package/apps/daemon/dist/cli.js.map +0 -1
- package/apps/daemon/dist/config.js.map +0 -1
- package/apps/daemon/dist/errors.js.map +0 -1
- package/apps/daemon/dist/loop/runner.js.map +0 -1
- package/apps/daemon/dist/loop/store.js.map +0 -1
- package/apps/daemon/dist/pair.js.map +0 -1
- package/apps/daemon/dist/paths.js.map +0 -1
- package/apps/daemon/dist/process.js.map +0 -1
- package/apps/daemon/dist/relay/connection.js.map +0 -1
- package/apps/daemon/dist/relay/register.js.map +0 -1
- package/apps/daemon/dist/relay/send.js.map +0 -1
- package/apps/daemon/dist/relay-http.js.map +0 -1
- package/apps/daemon/dist/runtime.js.map +0 -1
- package/apps/daemon/dist/session/create-session.js.map +0 -1
- package/apps/daemon/dist/session/directory.js.map +0 -1
- package/apps/daemon/dist/session/events.js.map +0 -1
- package/apps/daemon/dist/session/lifecycle.js.map +0 -1
- package/apps/daemon/dist/session/metadata.js.map +0 -1
- package/apps/daemon/dist/session/primitives.js.map +0 -1
- package/apps/daemon/dist/session/prompt.js.map +0 -1
- package/apps/daemon/dist/session/runtime-handle.js.map +0 -1
- package/apps/daemon/dist/start.js.map +0 -1
- package/apps/daemon/dist/state.js.map +0 -1
- package/apps/daemon/dist/turn-coordinator.js.map +0 -1
- package/apps/daemon/dist/types.js.map +0 -1
- package/apps/daemon/dist/utils.js.map +0 -1
- package/apps/relay/dist/auth.js.map +0 -1
- package/apps/relay/dist/connections.js.map +0 -1
- package/apps/relay/dist/controller-handlers.js.map +0 -1
- package/apps/relay/dist/db.js.map +0 -1
- package/apps/relay/dist/errors.js.map +0 -1
- package/apps/relay/dist/http-routes.js.map +0 -1
- package/apps/relay/dist/http-schemas.js.map +0 -1
- package/apps/relay/dist/index.js.map +0 -1
- package/apps/relay/dist/machine-handlers.js.map +0 -1
- package/apps/relay/dist/projections.js.map +0 -1
- package/apps/relay/dist/relay-context.js.map +0 -1
- package/apps/relay/dist/repositories.js.map +0 -1
- package/apps/relay/dist/retention.js.map +0 -1
- package/apps/relay/dist/scope.js.map +0 -1
- package/apps/relay/dist/security.js.map +0 -1
- package/apps/relay/dist/types.js.map +0 -1
- package/apps/relay/dist/websocket.js.map +0 -1
- package/apps/relay/package.json +0 -29
- package/packages/client/dist/account.d.ts.map +0 -1
- package/packages/client/dist/account.js.map +0 -1
- package/packages/client/dist/client.d.ts.map +0 -1
- package/packages/client/dist/client.js.map +0 -1
- package/packages/client/dist/errors.d.ts.map +0 -1
- package/packages/client/dist/errors.js.map +0 -1
- package/packages/client/dist/http.d.ts.map +0 -1
- package/packages/client/dist/http.js.map +0 -1
- package/packages/client/dist/index.d.ts.map +0 -1
- package/packages/client/dist/index.js.map +0 -1
- package/packages/client/dist/parsers.d.ts.map +0 -1
- package/packages/client/dist/parsers.js.map +0 -1
- package/packages/client/dist/transport.d.ts.map +0 -1
- package/packages/client/dist/transport.js.map +0 -1
- package/packages/client/dist/types.d.ts.map +0 -1
- package/packages/client/dist/types.js.map +0 -1
- package/packages/client/dist/validation.d.ts.map +0 -1
- package/packages/client/dist/validation.js.map +0 -1
- package/packages/client/package.json +0 -22
- package/packages/runtime/dist/index.d.ts.map +0 -1
- package/packages/runtime/dist/index.js.map +0 -1
- package/packages/runtime/package.json +0 -19
- package/packages/runtime-acpx/dist/index.d.ts.map +0 -1
- package/packages/runtime-acpx/dist/index.js.map +0 -1
- package/packages/runtime-acpx/package.json +0 -23
- package/packages/runtime-cli/dist/async-queue.d.ts.map +0 -1
- package/packages/runtime-cli/dist/async-queue.js.map +0 -1
- package/packages/runtime-cli/dist/claude-history.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-history.js.map +0 -1
- package/packages/runtime-cli/dist/claude-json.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-json.js.map +0 -1
- package/packages/runtime-cli/dist/claude-rename.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-rename.js.map +0 -1
- package/packages/runtime-cli/dist/claude-session-store.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-session-store.js.map +0 -1
- package/packages/runtime-cli/dist/claude-turn.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-turn.js.map +0 -1
- package/packages/runtime-cli/dist/codex-app-server.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-app-server.js.map +0 -1
- package/packages/runtime-cli/dist/codex-approvals.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-approvals.js.map +0 -1
- package/packages/runtime-cli/dist/codex-history.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-history.js.map +0 -1
- package/packages/runtime-cli/dist/codex-json-rpc.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-json-rpc.js.map +0 -1
- package/packages/runtime-cli/dist/codex-lifecycle.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-lifecycle.js.map +0 -1
- package/packages/runtime-cli/dist/codex-protocol.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-protocol.js.map +0 -1
- package/packages/runtime-cli/dist/index.d.ts.map +0 -1
- package/packages/runtime-cli/dist/index.js.map +0 -1
- package/packages/runtime-cli/dist/permissions.d.ts.map +0 -1
- package/packages/runtime-cli/dist/permissions.js.map +0 -1
- package/packages/runtime-cli/dist/session-store.d.ts.map +0 -1
- package/packages/runtime-cli/dist/session-store.js.map +0 -1
- package/packages/runtime-cli/package.json +0 -22
- package/packages/shared/dist/args.d.ts.map +0 -1
- package/packages/shared/dist/args.js.map +0 -1
- package/packages/shared/dist/crypto.d.ts.map +0 -1
- package/packages/shared/dist/crypto.js.map +0 -1
- package/packages/shared/dist/ids.d.ts.map +0 -1
- package/packages/shared/dist/ids.js.map +0 -1
- package/packages/shared/dist/index.d.ts.map +0 -1
- package/packages/shared/dist/index.js.map +0 -1
- package/packages/shared/dist/protocol-schemas.d.ts.map +0 -1
- package/packages/shared/dist/protocol-schemas.js.map +0 -1
- package/packages/shared/dist/protocol-types.d.ts.map +0 -1
- package/packages/shared/dist/protocol-types.js.map +0 -1
- package/packages/shared/dist/protocol.d.ts.map +0 -1
- package/packages/shared/dist/protocol.js.map +0 -1
- package/packages/shared/dist/relay-responses.d.ts +0 -7
- package/packages/shared/dist/relay-responses.d.ts.map +0 -1
- package/packages/shared/dist/relay-responses.js +0 -24
- package/packages/shared/dist/relay-responses.js.map +0 -1
- package/packages/shared/dist/session-name.d.ts.map +0 -1
- package/packages/shared/dist/session-name.js.map +0 -1
- package/packages/shared/dist/session-state.d.ts.map +0 -1
- package/packages/shared/dist/session-state.js.map +0 -1
- package/packages/shared/package.json +0 -24
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { daemonProcessStatusForIdentity, isDaemonProcessAlive, parseDaemonPidIdentity, readDaemonProcessStartedAtMs, } from "../../../../packages/shared/dist/node/daemon-process.js";
|
|
7
|
+
import { configDir, daemonLifecycleJournalPath, daemonScriptPath, pidPath } from "../paths.js";
|
|
8
|
+
import { lifecycleError } from "./errors.js";
|
|
9
|
+
const lifecycleDaemonPathEnv = "HAPPY_ELVES_LIFECYCLE_DAEMON_PATH";
|
|
10
|
+
const maxBundledHelperBytes = 16 * 1024 * 1024;
|
|
11
|
+
const helperCleanupMarkerVersion = 1;
|
|
12
|
+
export function detachedLifecycleHelperSpawnSpec(requestId, helperNonce, options = {}) {
|
|
13
|
+
assertLifecycleIdentifier(requestId, "request id");
|
|
14
|
+
if (!/^[a-f0-9]{48}$/u.test(helperNonce)) {
|
|
15
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_ID_INVALID", "Daemon lifecycle helper nonce is invalid");
|
|
16
|
+
}
|
|
17
|
+
const helperPath = options.helperPath ?? detachedHelperPath();
|
|
18
|
+
const nodeExecPath = options.nodeExecPath ?? process.execPath;
|
|
19
|
+
if (!path.isAbsolute(helperPath) || !path.isAbsolute(nodeExecPath)) {
|
|
20
|
+
throw lifecycleError("DAEMON_LIFECYCLE_EXEC_INVALID", "Detached lifecycle helper paths must be absolute");
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
command: nodeExecPath,
|
|
24
|
+
args: [helperPath, "--request-id", requestId, "--helper-nonce", helperNonce],
|
|
25
|
+
options: fixedDetachedOptions(options.cwd ?? configDir, {
|
|
26
|
+
...(options.env ?? process.env),
|
|
27
|
+
[lifecycleDaemonPathEnv]: daemonScriptPath,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function spawnDetachedLifecycleHelper(requestId, helperNonce, options = {}) {
|
|
32
|
+
const spec = detachedLifecycleHelperSpawnSpec(requestId, helperNonce, options);
|
|
33
|
+
return await (options.spawnDetached ?? spawnDetachedProcess)(spec);
|
|
34
|
+
}
|
|
35
|
+
export function detachedDaemonRestartSpawnSpec(cwd, instanceNonce, options = {}) {
|
|
36
|
+
if (!path.isAbsolute(cwd))
|
|
37
|
+
throw lifecycleError("DAEMON_RESTART_CWD_INVALID", "Daemon restart cwd must be absolute");
|
|
38
|
+
if (!/^[a-f0-9]{32,128}$/u.test(instanceNonce)) {
|
|
39
|
+
throw lifecycleError("DAEMON_INSTANCE_NONCE_INVALID", "Daemon restart instance nonce is invalid");
|
|
40
|
+
}
|
|
41
|
+
const daemonPath = options.daemonPath ?? lifecycleDaemonPathFromEnvironment() ?? daemonScriptPath;
|
|
42
|
+
const nodeExecPath = options.nodeExecPath ?? process.execPath;
|
|
43
|
+
if (!path.isAbsolute(daemonPath) || !path.isAbsolute(nodeExecPath)) {
|
|
44
|
+
throw lifecycleError("DAEMON_LIFECYCLE_EXEC_INVALID", "Daemon restart paths must be absolute");
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
command: nodeExecPath,
|
|
48
|
+
args: [daemonPath, "start", "--instance-nonce", instanceNonce],
|
|
49
|
+
options: fixedDetachedOptions(cwd, options.env ?? process.env),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Publish a self-contained helper under Happy Elves home before the parent
|
|
54
|
+
* exits. An npm upgrade may replace the installation directory while this
|
|
55
|
+
* process is running, so the detached helper must never execute from there.
|
|
56
|
+
*/
|
|
57
|
+
export async function stageDetachedLifecycleHelper(helperNonce, options = {}) {
|
|
58
|
+
if (!/^[a-f0-9]{48}$/u.test(helperNonce)) {
|
|
59
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_ID_INVALID", "Daemon lifecycle helper nonce is invalid");
|
|
60
|
+
}
|
|
61
|
+
const journalPath = options.journalPath ?? daemonLifecycleJournalPath;
|
|
62
|
+
const directory = path.join(path.dirname(path.resolve(journalPath)), "daemon-lifecycle-helpers-v1");
|
|
63
|
+
await ensurePrivateHelperDirectory(directory);
|
|
64
|
+
const sourcePath = options.sourcePath ?? detachedHelperBundlePath();
|
|
65
|
+
const source = await readRegularHelper(sourcePath);
|
|
66
|
+
if (!source) {
|
|
67
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper bundle is missing");
|
|
68
|
+
}
|
|
69
|
+
const targetPath = path.join(directory, `${helperNonce}.mjs`);
|
|
70
|
+
const existing = await readRegularHelper(targetPath, true);
|
|
71
|
+
if (existing) {
|
|
72
|
+
if (helperDigest(existing) !== helperDigest(source)) {
|
|
73
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_CONFLICT", "Staged lifecycle helper does not match the installed helper bundle");
|
|
74
|
+
}
|
|
75
|
+
await fs.chmod(targetPath, 0o600);
|
|
76
|
+
// A previous attempt may have published the target and then failed its
|
|
77
|
+
// directory sync. Re-establish rename durability before treating the
|
|
78
|
+
// existing helper as safely staged.
|
|
79
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
80
|
+
return targetPath;
|
|
81
|
+
}
|
|
82
|
+
const temporaryPath = path.join(directory, `.${helperNonce}-${randomBytes(18).toString("hex")}.tmp`);
|
|
83
|
+
let handle;
|
|
84
|
+
try {
|
|
85
|
+
handle = await fs.open(temporaryPath, "wx", 0o600);
|
|
86
|
+
await handle.writeFile(source);
|
|
87
|
+
await handle.sync();
|
|
88
|
+
await handle.close();
|
|
89
|
+
handle = undefined;
|
|
90
|
+
await fs.rename(temporaryPath, targetPath);
|
|
91
|
+
await fs.chmod(targetPath, 0o600);
|
|
92
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
await handle?.close().catch(() => undefined);
|
|
96
|
+
await fs.rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return targetPath;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Retire only the immutable helper selected by an exact operation nonce.
|
|
103
|
+
* Cleanup is bounded because Windows antivirus/process handles can briefly
|
|
104
|
+
* delay unlink after the bundled module has finished loading. Unknown file
|
|
105
|
+
* types are preserved and fail closed instead of being removed recursively.
|
|
106
|
+
*/
|
|
107
|
+
export async function retireStagedLifecycleHelper(helperNonce, options = {}) {
|
|
108
|
+
await queueStagedLifecycleHelperCleanup(helperNonce, options);
|
|
109
|
+
const outcome = await retireStagedLifecycleHelperFile(helperNonce, options);
|
|
110
|
+
if (outcome !== "pending") {
|
|
111
|
+
await removeStagedLifecycleHelperCleanupMarker(helperNonce, options);
|
|
112
|
+
}
|
|
113
|
+
return outcome;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Publish the exact helper nonce before a terminal journal can be replaced.
|
|
117
|
+
* A marker is immutable and one-per-nonce, so concurrent operations cannot
|
|
118
|
+
* lose an older pending cleanup through read-modify-write races.
|
|
119
|
+
*/
|
|
120
|
+
export async function queueStagedLifecycleHelperCleanup(helperNonce, options = {}) {
|
|
121
|
+
assertHelperNonce(helperNonce);
|
|
122
|
+
const directory = helperCleanupDirectory(options.journalPath);
|
|
123
|
+
await ensurePrivateHelperDirectory(directory);
|
|
124
|
+
const markerPath = helperCleanupMarkerPath(helperNonce, options.journalPath);
|
|
125
|
+
const expected = helperCleanupMarker(helperNonce);
|
|
126
|
+
const raw = `${JSON.stringify(expected)}\n`;
|
|
127
|
+
const stagingDirectory = path.dirname(directory);
|
|
128
|
+
const temporaryPath = path.join(stagingDirectory, `.daemon-lifecycle-helper-cleanup-${helperNonce}-${randomBytes(18).toString("hex")}.tmp`);
|
|
129
|
+
let handle;
|
|
130
|
+
try {
|
|
131
|
+
// The canonical marker must never be visible partially written. A crash
|
|
132
|
+
// before the hard-link can leave only a sibling temp file, which is
|
|
133
|
+
// outside the manifest directory and cannot poison future drains.
|
|
134
|
+
handle = await fs.open(temporaryPath, "wx", 0o600);
|
|
135
|
+
await handle.writeFile(raw, "utf8");
|
|
136
|
+
await handle.chmod(0o600);
|
|
137
|
+
await handle.sync();
|
|
138
|
+
await handle.close();
|
|
139
|
+
handle = undefined;
|
|
140
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
141
|
+
let linked = false;
|
|
142
|
+
try {
|
|
143
|
+
await fs.link(temporaryPath, markerPath);
|
|
144
|
+
linked = true;
|
|
145
|
+
await options.afterMarkerLinked?.();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error.code !== "EEXIST")
|
|
149
|
+
throw error;
|
|
150
|
+
await options.afterMarkerLinkConflict?.();
|
|
151
|
+
}
|
|
152
|
+
// On EEXIST, validate before fsync so corrupt or foreign content never
|
|
153
|
+
// becomes authority merely because it occupies the canonical path.
|
|
154
|
+
if (!linked) {
|
|
155
|
+
const beforeSync = await cleanupPublicationState(helperNonce, markerPath, options.journalPath);
|
|
156
|
+
if (beforeSync === "helper-absent")
|
|
157
|
+
return;
|
|
158
|
+
if (beforeSync === "retry")
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
162
|
+
// A legal drain can unlink the marker while this publisher is between
|
|
163
|
+
// link/read and fsync. It removes the exact helper first, so absence is
|
|
164
|
+
// safe only when that helper is also proven absent; otherwise retry the
|
|
165
|
+
// create-only publication with the same fully-synced temp inode.
|
|
166
|
+
const afterSync = await cleanupPublicationState(helperNonce, markerPath, options.journalPath);
|
|
167
|
+
if (afterSync === "published" || afterSync === "helper-absent")
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker could not be published stably");
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
await handle?.close().catch(() => undefined);
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
await fs.rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Drain cleanup records independently of the current lifecycle journal. This
|
|
182
|
+
* lets a later Daemon retire a Windows-blocked helper even after a new
|
|
183
|
+
* operation has replaced the terminal journal that originally owned it.
|
|
184
|
+
*/
|
|
185
|
+
export async function drainStagedLifecycleHelperCleanup(options = {}) {
|
|
186
|
+
const directory = helperCleanupDirectory(options.journalPath);
|
|
187
|
+
const stat = await fs.lstat(directory).catch((error) => {
|
|
188
|
+
if (error.code === "ENOENT")
|
|
189
|
+
return undefined;
|
|
190
|
+
throw error;
|
|
191
|
+
});
|
|
192
|
+
if (!stat)
|
|
193
|
+
return { removed: 0, pending: 0 };
|
|
194
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
195
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup directory is unsafe");
|
|
196
|
+
}
|
|
197
|
+
const names = await fs.readdir(directory);
|
|
198
|
+
names.sort();
|
|
199
|
+
let removed = 0;
|
|
200
|
+
let pending = 0;
|
|
201
|
+
for (const name of names) {
|
|
202
|
+
const match = /^([a-f0-9]{48})\.json$/u.exec(name);
|
|
203
|
+
if (!match) {
|
|
204
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup directory contains an unknown entry");
|
|
205
|
+
}
|
|
206
|
+
const helperNonce = match[1];
|
|
207
|
+
if (!helperNonce) {
|
|
208
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker name is invalid");
|
|
209
|
+
}
|
|
210
|
+
const marker = await readStagedLifecycleHelperCleanupMarker(path.join(directory, name)).catch((error) => {
|
|
211
|
+
if (error.code === "ENOENT")
|
|
212
|
+
return undefined;
|
|
213
|
+
throw error;
|
|
214
|
+
});
|
|
215
|
+
if (!marker)
|
|
216
|
+
continue;
|
|
217
|
+
if (marker.helperNonce !== helperNonce) {
|
|
218
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker name does not match its content");
|
|
219
|
+
}
|
|
220
|
+
const outcome = await retireStagedLifecycleHelperFile(helperNonce, options);
|
|
221
|
+
if (outcome === "pending") {
|
|
222
|
+
pending += 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
await removeStagedLifecycleHelperCleanupMarker(helperNonce, options);
|
|
226
|
+
removed += 1;
|
|
227
|
+
}
|
|
228
|
+
return { removed, pending };
|
|
229
|
+
}
|
|
230
|
+
async function retireStagedLifecycleHelperFile(helperNonce, options) {
|
|
231
|
+
assertHelperNonce(helperNonce);
|
|
232
|
+
const journalPath = options.journalPath ?? daemonLifecycleJournalPath;
|
|
233
|
+
const directory = path.join(path.dirname(path.resolve(journalPath)), "daemon-lifecycle-helpers-v1");
|
|
234
|
+
const targetPath = path.join(directory, `${helperNonce}.mjs`);
|
|
235
|
+
const retryDelaysMs = options.retryDelaysMs ?? [0, 50, 200, 500];
|
|
236
|
+
if (retryDelaysMs.length === 0) {
|
|
237
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup requires at least one attempt");
|
|
238
|
+
}
|
|
239
|
+
const sleep = options.sleep ?? ((durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs)));
|
|
240
|
+
const unlink = options.unlink ?? ((value) => fs.unlink(value));
|
|
241
|
+
for (let attempt = 0; attempt < retryDelaysMs.length; attempt += 1) {
|
|
242
|
+
const delayMs = retryDelaysMs[attempt] ?? 0;
|
|
243
|
+
if (delayMs > 0)
|
|
244
|
+
await sleep(delayMs);
|
|
245
|
+
const directoryStat = await fs.lstat(directory).catch((error) => {
|
|
246
|
+
if (error.code === "ENOENT")
|
|
247
|
+
return undefined;
|
|
248
|
+
throw error;
|
|
249
|
+
});
|
|
250
|
+
if (!directoryStat)
|
|
251
|
+
return "absent";
|
|
252
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
|
|
253
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper directory is unsafe");
|
|
254
|
+
}
|
|
255
|
+
const helperStat = await fs.lstat(targetPath).catch((error) => {
|
|
256
|
+
if (error.code === "ENOENT")
|
|
257
|
+
return undefined;
|
|
258
|
+
throw error;
|
|
259
|
+
});
|
|
260
|
+
if (!helperStat) {
|
|
261
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
262
|
+
return "absent";
|
|
263
|
+
}
|
|
264
|
+
if (!helperStat.isFile() || helperStat.isSymbolicLink() || helperStat.size <= 0 || helperStat.size > maxBundledHelperBytes) {
|
|
265
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Staged lifecycle helper is not a safe regular file");
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
await unlink(targetPath);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
const code = error.code;
|
|
272
|
+
if (code === "ENOENT") {
|
|
273
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
274
|
+
return "absent";
|
|
275
|
+
}
|
|
276
|
+
if (!["EBUSY", "EPERM", "EACCES"].includes(code ?? ""))
|
|
277
|
+
throw error;
|
|
278
|
+
if (attempt === retryDelaysMs.length - 1)
|
|
279
|
+
return "pending";
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
// Once unlink succeeds, durability errors are not equivalent to a locked
|
|
283
|
+
// file. Propagate them so the per-nonce marker remains available for
|
|
284
|
+
// crash recovery instead of observing ENOENT on a misleading retry.
|
|
285
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
286
|
+
return "removed";
|
|
287
|
+
}
|
|
288
|
+
return "pending";
|
|
289
|
+
}
|
|
290
|
+
export async function spawnDetachedDaemonRestart(cwd, options = {}) {
|
|
291
|
+
const instanceNonce = options.instanceNonce ?? randomBytes(24).toString("hex");
|
|
292
|
+
const spec = detachedDaemonRestartSpawnSpec(cwd, instanceNonce, options);
|
|
293
|
+
const pid = await (options.spawnDetached ?? spawnDetachedProcess)(spec);
|
|
294
|
+
return { pid, instanceNonce };
|
|
295
|
+
}
|
|
296
|
+
export async function waitForDetachedDaemonRestart(expected, options = {}) {
|
|
297
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
298
|
+
const pollMs = options.pollMs ?? 100;
|
|
299
|
+
const sleep = options.sleep ?? ((durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs)));
|
|
300
|
+
const readPid = options.readPid ?? readPidFile;
|
|
301
|
+
const deadline = Date.now() + timeoutMs;
|
|
302
|
+
while (Date.now() < deadline) {
|
|
303
|
+
const raw = await readPid();
|
|
304
|
+
if (raw !== undefined) {
|
|
305
|
+
const identity = parseDaemonPidIdentity(raw);
|
|
306
|
+
if (identity.record?.pid === expected.pid &&
|
|
307
|
+
identity.record.instanceNonce === expected.instanceNonce) {
|
|
308
|
+
const status = daemonProcessStatusForIdentity(identity, [options.expectedScriptPath ?? daemonScriptPath]);
|
|
309
|
+
if (status.running)
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
await sleep(pollMs);
|
|
314
|
+
}
|
|
315
|
+
throw lifecycleError("DAEMON_RESTART_SETTLEMENT_FAILED", "Detached daemon restart did not publish a verified owner before the deadline");
|
|
316
|
+
}
|
|
317
|
+
export async function findVerifiedDaemonRestart(instanceGeneration, options = {}) {
|
|
318
|
+
if (!/^[a-f0-9]{32,128}$/u.test(instanceGeneration)) {
|
|
319
|
+
throw lifecycleError("DAEMON_INSTANCE_NONCE_INVALID", "Daemon restart instance generation is invalid");
|
|
320
|
+
}
|
|
321
|
+
const raw = await (options.readPid ?? readPidFile)();
|
|
322
|
+
if (raw === undefined)
|
|
323
|
+
return undefined;
|
|
324
|
+
const identity = parseDaemonPidIdentity(raw);
|
|
325
|
+
const status = daemonProcessStatusForIdentity(identity, [options.expectedScriptPath ?? daemonScriptPath]);
|
|
326
|
+
if (status.alive && !status.running) {
|
|
327
|
+
throw lifecycleError("DAEMON_PID_UNVERIFIED", "A live process owns the daemon pid file but cannot be verified");
|
|
328
|
+
}
|
|
329
|
+
if (!status.running || !identity.record || identity.record.instanceNonce !== instanceGeneration)
|
|
330
|
+
return undefined;
|
|
331
|
+
return {
|
|
332
|
+
pid: identity.record.pid,
|
|
333
|
+
instanceNonce: identity.record.instanceNonce,
|
|
334
|
+
startedAt: identity.record.startedAt,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Stop only the replacement process whose pid and instance generation are
|
|
339
|
+
* still proven by the daemon pid identity. This is intentionally stricter
|
|
340
|
+
* than a generic process kill: PID reuse or a changed owner aborts the
|
|
341
|
+
* operation without signalling the observed process.
|
|
342
|
+
*/
|
|
343
|
+
export async function terminateVerifiedDaemonRestart(expected, options = {}) {
|
|
344
|
+
const findRestart = options.findRestart ?? findVerifiedDaemonRestart;
|
|
345
|
+
const signal = options.signal ?? ((pid, value) => process.kill(pid, value));
|
|
346
|
+
const sleep = options.sleep ?? ((durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs)));
|
|
347
|
+
const pollMs = options.pollMs ?? 100;
|
|
348
|
+
const current = await findRestart(expected.instanceNonce);
|
|
349
|
+
if (!current) {
|
|
350
|
+
if (!isDaemonProcessAlive(expected.pid))
|
|
351
|
+
return;
|
|
352
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The upgraded daemon is alive but its exact owner proof is missing");
|
|
353
|
+
}
|
|
354
|
+
if (current.pid !== expected.pid || current.instanceNonce !== expected.instanceNonce) {
|
|
355
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The upgraded daemon owner changed before rollback");
|
|
356
|
+
}
|
|
357
|
+
const expectedStartedAt = expected.startedAt ?? current.startedAt;
|
|
358
|
+
if (expectedStartedAt === undefined || current.startedAt === undefined || Math.abs(current.startedAt - expectedStartedAt) > 10_000) {
|
|
359
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The upgraded daemon process start identity could not be verified for rollback");
|
|
360
|
+
}
|
|
361
|
+
const exactExpected = { ...expected, startedAt: expectedStartedAt };
|
|
362
|
+
try {
|
|
363
|
+
signal(expected.pid, "SIGTERM");
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
if (error.code !== "ESRCH")
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
if (await waitUntilRestartIsGone(exactExpected, options.timeoutMs ?? 5_000, pollMs, sleep))
|
|
370
|
+
return;
|
|
371
|
+
const beforeKill = await findRestart(expected.instanceNonce);
|
|
372
|
+
if (!beforeKill) {
|
|
373
|
+
if (!isDaemonProcessAlive(expected.pid))
|
|
374
|
+
return;
|
|
375
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The upgraded daemon is alive but its owner proof disappeared before forced termination");
|
|
376
|
+
}
|
|
377
|
+
if (beforeKill.pid !== expected.pid ||
|
|
378
|
+
beforeKill.instanceNonce !== expected.instanceNonce ||
|
|
379
|
+
beforeKill.startedAt === undefined ||
|
|
380
|
+
Math.abs(beforeKill.startedAt - expectedStartedAt) > 10_000) {
|
|
381
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The upgraded daemon owner changed before forced rollback termination");
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
signal(expected.pid, "SIGKILL");
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
if (error.code !== "ESRCH")
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
if (await waitUntilRestartIsGone(exactExpected, options.killTimeoutMs ?? 2_000, pollMs, sleep))
|
|
391
|
+
return;
|
|
392
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The exact upgraded daemon process could not be terminated for rollback");
|
|
393
|
+
}
|
|
394
|
+
async function waitUntilRestartIsGone(expected, timeoutMs, pollMs, sleep) {
|
|
395
|
+
const deadline = Date.now() + timeoutMs;
|
|
396
|
+
while (Date.now() < deadline) {
|
|
397
|
+
if (!isDaemonProcessAlive(expected.pid))
|
|
398
|
+
return true;
|
|
399
|
+
const observedStartedAt = readDaemonProcessStartedAtMs(expected.pid);
|
|
400
|
+
if (observedStartedAt === undefined || Math.abs(observedStartedAt - expected.startedAt) > 10_000) {
|
|
401
|
+
throw lifecycleError("DAEMON_UPGRADE_ROLLBACK_FAILED", "The upgraded daemon process identity became unverified while rollback termination was settling");
|
|
402
|
+
}
|
|
403
|
+
await sleep(pollMs);
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
async function spawnDetachedProcess(spec) {
|
|
408
|
+
return await new Promise((resolve, reject) => {
|
|
409
|
+
let child;
|
|
410
|
+
try {
|
|
411
|
+
child = spawn(spec.command, spec.args, spec.options);
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
reject(lifecycleError("DAEMON_LIFECYCLE_HELPER_SPAWN_FAILED", allowedSpawnMessage(error)));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
let settled = false;
|
|
418
|
+
const timer = setTimeout(() => {
|
|
419
|
+
if (settled)
|
|
420
|
+
return;
|
|
421
|
+
settled = true;
|
|
422
|
+
child.kill();
|
|
423
|
+
reject(lifecycleError("DAEMON_LIFECYCLE_HELPER_SPAWN_FAILED", "Detached lifecycle process did not spawn before the deadline"));
|
|
424
|
+
}, 5_000);
|
|
425
|
+
timer.unref?.();
|
|
426
|
+
child.once("error", (error) => {
|
|
427
|
+
if (settled)
|
|
428
|
+
return;
|
|
429
|
+
settled = true;
|
|
430
|
+
clearTimeout(timer);
|
|
431
|
+
reject(lifecycleError("DAEMON_LIFECYCLE_HELPER_SPAWN_FAILED", allowedSpawnMessage(error)));
|
|
432
|
+
});
|
|
433
|
+
child.once("spawn", () => {
|
|
434
|
+
if (settled)
|
|
435
|
+
return;
|
|
436
|
+
settled = true;
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
const pid = child.pid;
|
|
439
|
+
if (!pid) {
|
|
440
|
+
reject(lifecycleError("DAEMON_LIFECYCLE_HELPER_SPAWN_FAILED", "Detached lifecycle process did not return a pid"));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
child.unref();
|
|
444
|
+
resolve(pid);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function detachedHelperPath() {
|
|
449
|
+
return fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "./detached-helper-cli.ts" : "./detached-helper-cli.js", import.meta.url));
|
|
450
|
+
}
|
|
451
|
+
function detachedHelperBundlePath() {
|
|
452
|
+
return fileURLToPath(new URL("./detached-helper-bundle.mjs", import.meta.url));
|
|
453
|
+
}
|
|
454
|
+
function lifecycleDaemonPathFromEnvironment() {
|
|
455
|
+
const value = process.env[lifecycleDaemonPathEnv];
|
|
456
|
+
return value && path.isAbsolute(value) ? value : undefined;
|
|
457
|
+
}
|
|
458
|
+
async function ensurePrivateHelperDirectory(directory) {
|
|
459
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
460
|
+
const stat = await fs.lstat(directory);
|
|
461
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
462
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper directory is not a private directory");
|
|
463
|
+
}
|
|
464
|
+
await fs.chmod(directory, 0o700);
|
|
465
|
+
}
|
|
466
|
+
async function readRegularHelper(filePath, allowMissing = false) {
|
|
467
|
+
const stat = await fs.lstat(filePath).catch((error) => {
|
|
468
|
+
if (allowMissing && error.code === "ENOENT")
|
|
469
|
+
return undefined;
|
|
470
|
+
throw error;
|
|
471
|
+
});
|
|
472
|
+
if (!stat)
|
|
473
|
+
return undefined;
|
|
474
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size <= 0 || stat.size > maxBundledHelperBytes) {
|
|
475
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper bundle is not a safe regular file");
|
|
476
|
+
}
|
|
477
|
+
return await fs.readFile(filePath);
|
|
478
|
+
}
|
|
479
|
+
function helperDigest(value) {
|
|
480
|
+
return createHash("sha256").update(value).digest("hex");
|
|
481
|
+
}
|
|
482
|
+
function helperCleanupDirectory(journalPath = daemonLifecycleJournalPath) {
|
|
483
|
+
return path.join(path.dirname(path.resolve(journalPath)), "daemon-lifecycle-helper-cleanup-v1");
|
|
484
|
+
}
|
|
485
|
+
function helperCleanupMarkerPath(helperNonce, journalPath) {
|
|
486
|
+
return path.join(helperCleanupDirectory(journalPath), `${helperNonce}.json`);
|
|
487
|
+
}
|
|
488
|
+
function helperCleanupMarker(helperNonce) {
|
|
489
|
+
return {
|
|
490
|
+
version: helperCleanupMarkerVersion,
|
|
491
|
+
helperNonce,
|
|
492
|
+
checksum: createHash("sha256")
|
|
493
|
+
.update("daemon-lifecycle-helper-cleanup-v1\n")
|
|
494
|
+
.update(helperNonce)
|
|
495
|
+
.digest("hex"),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
async function readStagedLifecycleHelperCleanupMarker(markerPath) {
|
|
499
|
+
const stat = await fs.lstat(markerPath);
|
|
500
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size <= 0 || stat.size > 4 * 1024) {
|
|
501
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker is not a safe regular file");
|
|
502
|
+
}
|
|
503
|
+
const raw = await fs.readFile(markerPath, "utf8");
|
|
504
|
+
let parsed;
|
|
505
|
+
try {
|
|
506
|
+
parsed = JSON.parse(raw);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker is not valid JSON");
|
|
510
|
+
}
|
|
511
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
512
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker has an invalid root");
|
|
513
|
+
}
|
|
514
|
+
const value = parsed;
|
|
515
|
+
const keys = Object.keys(value).sort();
|
|
516
|
+
if (keys.length !== 3 ||
|
|
517
|
+
keys[0] !== "checksum" ||
|
|
518
|
+
keys[1] !== "helperNonce" ||
|
|
519
|
+
keys[2] !== "version" ||
|
|
520
|
+
value.version !== helperCleanupMarkerVersion ||
|
|
521
|
+
typeof value.helperNonce !== "string" ||
|
|
522
|
+
!/^[a-f0-9]{48}$/u.test(value.helperNonce) ||
|
|
523
|
+
typeof value.checksum !== "string") {
|
|
524
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker has an invalid shape");
|
|
525
|
+
}
|
|
526
|
+
const expected = helperCleanupMarker(value.helperNonce);
|
|
527
|
+
if (value.checksum !== expected.checksum) {
|
|
528
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker checksum does not match");
|
|
529
|
+
}
|
|
530
|
+
return expected;
|
|
531
|
+
}
|
|
532
|
+
async function cleanupPublicationState(helperNonce, markerPath, journalPath) {
|
|
533
|
+
const marker = await readStagedLifecycleHelperCleanupMarker(markerPath).catch((error) => {
|
|
534
|
+
if (error.code === "ENOENT")
|
|
535
|
+
return undefined;
|
|
536
|
+
throw error;
|
|
537
|
+
});
|
|
538
|
+
if (marker) {
|
|
539
|
+
if (marker.helperNonce !== helperNonce) {
|
|
540
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker ownership changed");
|
|
541
|
+
}
|
|
542
|
+
return "published";
|
|
543
|
+
}
|
|
544
|
+
return await stagedLifecycleHelperExists(helperNonce, journalPath) ? "retry" : "helper-absent";
|
|
545
|
+
}
|
|
546
|
+
async function stagedLifecycleHelperExists(helperNonce, journalPath = daemonLifecycleJournalPath) {
|
|
547
|
+
const directory = path.join(path.dirname(path.resolve(journalPath)), "daemon-lifecycle-helpers-v1");
|
|
548
|
+
const directoryStat = await fs.lstat(directory).catch((error) => {
|
|
549
|
+
if (error.code === "ENOENT")
|
|
550
|
+
return undefined;
|
|
551
|
+
throw error;
|
|
552
|
+
});
|
|
553
|
+
if (!directoryStat)
|
|
554
|
+
return false;
|
|
555
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
|
|
556
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper directory is unsafe");
|
|
557
|
+
}
|
|
558
|
+
const targetPath = path.join(directory, `${helperNonce}.mjs`);
|
|
559
|
+
const helperStat = await fs.lstat(targetPath).catch((error) => {
|
|
560
|
+
if (error.code === "ENOENT")
|
|
561
|
+
return undefined;
|
|
562
|
+
throw error;
|
|
563
|
+
});
|
|
564
|
+
if (!helperStat)
|
|
565
|
+
return false;
|
|
566
|
+
if (!helperStat.isFile() || helperStat.isSymbolicLink() || helperStat.size <= 0 || helperStat.size > maxBundledHelperBytes) {
|
|
567
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Staged lifecycle helper is not a safe regular file");
|
|
568
|
+
}
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
async function removeStagedLifecycleHelperCleanupMarker(helperNonce, options) {
|
|
572
|
+
const directory = helperCleanupDirectory(options.journalPath);
|
|
573
|
+
const markerPath = helperCleanupMarkerPath(helperNonce, options.journalPath);
|
|
574
|
+
const marker = await readStagedLifecycleHelperCleanupMarker(markerPath).catch((error) => {
|
|
575
|
+
if (error.code === "ENOENT")
|
|
576
|
+
return undefined;
|
|
577
|
+
throw error;
|
|
578
|
+
});
|
|
579
|
+
if (!marker)
|
|
580
|
+
return;
|
|
581
|
+
if (marker.helperNonce !== helperNonce) {
|
|
582
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_PATH_INVALID", "Lifecycle helper cleanup marker ownership changed");
|
|
583
|
+
}
|
|
584
|
+
await fs.unlink(markerPath).catch((error) => {
|
|
585
|
+
if (error.code !== "ENOENT")
|
|
586
|
+
throw error;
|
|
587
|
+
});
|
|
588
|
+
await syncLifecycleDirectory(directory, options.syncDirectory, options.platform);
|
|
589
|
+
}
|
|
590
|
+
async function syncLifecycleDirectory(directory, sync, platform = process.platform) {
|
|
591
|
+
let directoryHandle;
|
|
592
|
+
try {
|
|
593
|
+
if (sync) {
|
|
594
|
+
await sync(directory);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
directoryHandle = await fs.open(directory, "r");
|
|
598
|
+
await directoryHandle.sync();
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
const code = error.code ?? "";
|
|
602
|
+
if (platform !== "win32" || !["EINVAL", "ENOTSUP", "EISDIR", "EPERM"].includes(code))
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
await directoryHandle?.close();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function fixedDetachedOptions(cwd, sourceEnv) {
|
|
610
|
+
const env = { ...sourceEnv };
|
|
611
|
+
for (const key of Object.keys(env)) {
|
|
612
|
+
if (key.toLowerCase() === "node_options")
|
|
613
|
+
delete env[key];
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
cwd,
|
|
617
|
+
env,
|
|
618
|
+
detached: true,
|
|
619
|
+
shell: false,
|
|
620
|
+
stdio: "ignore",
|
|
621
|
+
windowsHide: true,
|
|
622
|
+
windowsVerbatimArguments: false,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function assertLifecycleIdentifier(value, label) {
|
|
626
|
+
if (!/^[A-Za-z0-9._:-]{1,200}$/u.test(value)) {
|
|
627
|
+
throw lifecycleError("DAEMON_LIFECYCLE_REQUEST_INVALID", `Daemon lifecycle ${label} is invalid`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function assertHelperNonce(helperNonce) {
|
|
631
|
+
if (!/^[a-f0-9]{48}$/u.test(helperNonce)) {
|
|
632
|
+
throw lifecycleError("DAEMON_LIFECYCLE_HELPER_ID_INVALID", "Daemon lifecycle helper nonce is invalid");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async function readPidFile() {
|
|
636
|
+
try {
|
|
637
|
+
return await fs.readFile(pidPath, "utf8");
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
if (error.code === "ENOENT")
|
|
641
|
+
return undefined;
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function allowedSpawnMessage(error) {
|
|
646
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
647
|
+
return code && new Set(["ENOENT", "EACCES", "EPERM"]).has(code)
|
|
648
|
+
? `Could not spawn the fixed lifecycle executable (${code})`
|
|
649
|
+
: "Could not spawn the fixed lifecycle executable";
|
|
650
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { runDetachedLifecycleOperation } from "./helper.js";
|
|
2
|
+
import { transitionDaemonLifecycleJournal, type DaemonLifecycleJournalOptions } from "./journal.js";
|
|
3
|
+
export type ActiveDaemonLifecycleRuntime = {
|
|
4
|
+
daemonId: string;
|
|
5
|
+
instanceGeneration: string;
|
|
6
|
+
requestShutdown(): void;
|
|
7
|
+
};
|
|
8
|
+
type LifecycleStartOptions = DaemonLifecycleJournalOptions & {
|
|
9
|
+
processId?: number;
|
|
10
|
+
processStartedAt?: number;
|
|
11
|
+
helperNonce?: string;
|
|
12
|
+
recover?: typeof runDetachedLifecycleOperation;
|
|
13
|
+
afterHelperCleanupQueued?: () => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export declare function configureDaemonLifecycleRuntime(runtime: ActiveDaemonLifecycleRuntime): void;
|
|
16
|
+
export declare function clearDaemonLifecycleRuntime(instanceGeneration: string): void;
|
|
17
|
+
export declare function daemonLifecycleRuntime(): ActiveDaemonLifecycleRuntime;
|
|
18
|
+
/**
|
|
19
|
+
* During upgrade/restart, the lifetime lock is intentionally absent after the
|
|
20
|
+
* old daemon exits. The journal is the only authority that can let a new
|
|
21
|
+
* daemon cross that window. If the helper died, this start process atomically
|
|
22
|
+
* becomes the recovery helper, resumes the recorded phase, and exits instead
|
|
23
|
+
* of racing the replacement daemon it launches.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertDaemonLifecycleStartAllowed(instanceGeneration: string, options?: LifecycleStartOptions): Promise<"start" | "recovered">;
|
|
26
|
+
export declare function settleAuthorizedLifecycleRestartAfterRelayAuthentication(input: {
|
|
27
|
+
daemonId: string;
|
|
28
|
+
instanceGeneration: string;
|
|
29
|
+
pid?: number;
|
|
30
|
+
}, options?: DaemonLifecycleJournalOptions & {
|
|
31
|
+
now?: () => Date;
|
|
32
|
+
processStartedAt?: number;
|
|
33
|
+
afterReplacementPublication?: (journal: Awaited<ReturnType<typeof transitionDaemonLifecycleJournal>>) => Promise<void>;
|
|
34
|
+
verifyRunningVersion?: (expectedVersion: string, packageRoot: string) => Promise<void>;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
export {};
|