@geminixiang/mikan 1.0.0-beta.2 → 1.0.0-beta.20
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/CHANGELOG.md +354 -0
- package/README.md +42 -16
- package/dist/adapter.d.ts +1 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +1 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +36 -67
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +17 -42
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/github/bot.d.ts +148 -0
- package/dist/adapters/github/bot.d.ts.map +1 -0
- package/dist/adapters/github/bot.js +1031 -0
- package/dist/adapters/github/bot.js.map +1 -0
- package/dist/adapters/github/client.d.ts +118 -0
- package/dist/adapters/github/client.d.ts.map +1 -0
- package/dist/adapters/github/client.js +309 -0
- package/dist/adapters/github/client.js.map +1 -0
- package/dist/adapters/github/cloudbuild.d.ts +24 -0
- package/dist/adapters/github/cloudbuild.d.ts.map +1 -0
- package/dist/adapters/github/cloudbuild.js +71 -0
- package/dist/adapters/github/cloudbuild.js.map +1 -0
- package/dist/adapters/github/context.d.ts +9 -0
- package/dist/adapters/github/context.d.ts.map +1 -0
- package/dist/adapters/github/context.js +175 -0
- package/dist/adapters/github/context.js.map +1 -0
- package/dist/adapters/github/gcp-auth.d.ts +31 -0
- package/dist/adapters/github/gcp-auth.d.ts.map +1 -0
- package/dist/adapters/github/gcp-auth.js +196 -0
- package/dist/adapters/github/gcp-auth.js.map +1 -0
- package/dist/adapters/github/ids.d.ts +34 -0
- package/dist/adapters/github/ids.d.ts.map +1 -0
- package/dist/adapters/github/ids.js +53 -0
- package/dist/adapters/github/ids.js.map +1 -0
- package/dist/adapters/github/repo.d.ts +30 -0
- package/dist/adapters/github/repo.d.ts.map +1 -0
- package/dist/adapters/github/repo.js +136 -0
- package/dist/adapters/github/repo.js.map +1 -0
- package/dist/adapters/github/tool-pack.d.ts +14 -0
- package/dist/adapters/github/tool-pack.d.ts.map +1 -0
- package/dist/adapters/github/tool-pack.js +56 -0
- package/dist/adapters/github/tool-pack.js.map +1 -0
- package/dist/adapters/github/tools/checks.d.ts +24 -0
- package/dist/adapters/github/tools/checks.d.ts.map +1 -0
- package/dist/adapters/github/tools/checks.js +116 -0
- package/dist/adapters/github/tools/checks.js.map +1 -0
- package/dist/adapters/github/tools/issue.d.ts +22 -0
- package/dist/adapters/github/tools/issue.d.ts.map +1 -0
- package/dist/adapters/github/tools/issue.js +59 -0
- package/dist/adapters/github/tools/issue.js.map +1 -0
- package/dist/adapters/github/tools/pr.d.ts +22 -0
- package/dist/adapters/github/tools/pr.d.ts.map +1 -0
- package/dist/adapters/github/tools/pr.js +59 -0
- package/dist/adapters/github/tools/pr.js.map +1 -0
- package/dist/adapters/github/tools/read.d.ts +22 -0
- package/dist/adapters/github/tools/read.d.ts.map +1 -0
- package/dist/adapters/github/tools/read.js +152 -0
- package/dist/adapters/github/tools/read.js.map +1 -0
- package/dist/adapters/github/tools/review-reply.d.ts +19 -0
- package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
- package/dist/adapters/github/tools/review-reply.js +49 -0
- package/dist/adapters/github/tools/review-reply.js.map +1 -0
- package/dist/adapters/github/tools/sync.d.ts +17 -0
- package/dist/adapters/github/tools/sync.d.ts.map +1 -0
- package/dist/adapters/github/tools/sync.js +45 -0
- package/dist/adapters/github/tools/sync.js.map +1 -0
- package/dist/adapters/github/types.d.ts +350 -0
- package/dist/adapters/github/types.d.ts.map +1 -0
- package/dist/adapters/github/types.js +2 -0
- package/dist/adapters/github/types.js.map +1 -0
- package/dist/adapters/intake.d.ts +16 -5
- package/dist/adapters/intake.d.ts.map +1 -1
- package/dist/adapters/intake.js +79 -22
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +8 -6
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +81 -133
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +3 -14
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +14 -17
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/session.d.ts.map +1 -1
- package/dist/adapters/slack/session.js +5 -8
- package/dist/adapters/slack/session.js.map +1 -1
- package/dist/adapters/streaming.d.ts +4 -0
- package/dist/adapters/streaming.d.ts.map +1 -1
- package/dist/adapters/streaming.js +11 -0
- package/dist/adapters/streaming.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +1 -2
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +27 -62
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +8 -27
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/types.d.ts +28 -2
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent.d.ts +38 -14
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +358 -224
- package/dist/agent.js.map +1 -1
- package/dist/cli/download.d.ts.map +1 -0
- package/dist/cli/download.js.map +1 -0
- package/dist/cli/ext-git.d.ts +22 -0
- package/dist/cli/ext-git.d.ts.map +1 -0
- package/dist/cli/ext-git.js +76 -0
- package/dist/cli/ext-git.js.map +1 -0
- package/dist/cli/ext.d.ts +2 -0
- package/dist/cli/ext.d.ts.map +1 -0
- package/dist/cli/ext.js +217 -0
- package/dist/cli/ext.js.map +1 -0
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +2 -1
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +2 -6
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/extensions.d.ts.map +1 -1
- package/dist/commands/extensions.js +16 -6
- package/dist/commands/extensions.js.map +1 -1
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +30 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/manifest.d.ts +72 -0
- package/dist/commands/manifest.d.ts.map +1 -0
- package/dist/commands/manifest.js +121 -0
- package/dist/commands/manifest.js.map +1 -0
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +3 -1
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +2 -1
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +16 -12
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/text.d.ts +2 -0
- package/dist/commands/text.d.ts.map +1 -0
- package/dist/commands/text.js +16 -0
- package/dist/commands/text.js.map +1 -0
- package/dist/commands/types.d.ts +14 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +90 -54
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +10 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +14 -5
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +3 -2
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +28 -12
- package/dist/execution-resolver.js.map +1 -1
- package/dist/harness/extensions/loader.d.ts +39 -9
- package/dist/harness/extensions/loader.d.ts.map +1 -1
- package/dist/harness/extensions/loader.js +258 -54
- package/dist/harness/extensions/loader.js.map +1 -1
- package/dist/harness/extensions/registry.d.ts +48 -1
- package/dist/harness/extensions/registry.d.ts.map +1 -1
- package/dist/harness/extensions/registry.js +146 -0
- package/dist/harness/extensions/registry.js.map +1 -1
- package/dist/harness/extensions/types.d.ts +161 -14
- package/dist/harness/extensions/types.d.ts.map +1 -1
- package/dist/harness/extensions/types.js.map +1 -1
- package/dist/harness/index.d.ts +5 -5
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +2 -2
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/models.d.ts +5 -0
- package/dist/harness/models.d.ts.map +1 -1
- package/dist/harness/models.js +10 -0
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/runner.d.ts +21 -2
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +80 -22
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session-store.d.ts +4 -1
- package/dist/harness/session-store.d.ts.map +1 -1
- package/dist/harness/session-store.js +11 -2
- package/dist/harness/session-store.js.map +1 -1
- package/dist/harness/subagent-runner.d.ts +17 -0
- package/dist/harness/subagent-runner.d.ts.map +1 -0
- package/dist/harness/subagent-runner.js +307 -0
- package/dist/harness/subagent-runner.js.map +1 -0
- package/dist/harness/types.d.ts +53 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.js +232 -29
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +2 -5
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-runtime.d.ts +1 -1
- package/dist/runtime/conversation-runtime.d.ts.map +1 -1
- package/dist/runtime/conversation-runtime.js +264 -41
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +32 -5
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts +2 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +7 -0
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts +3 -0
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +8 -2
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/firecracker.d.ts +2 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -1
- package/dist/sandbox/firecracker.js +7 -1
- package/dist/sandbox/firecracker.js.map +1 -1
- package/dist/sandbox/gondolin-fleet.d.ts +104 -0
- package/dist/sandbox/gondolin-fleet.d.ts.map +1 -0
- package/dist/sandbox/gondolin-fleet.js +330 -0
- package/dist/sandbox/gondolin-fleet.js.map +1 -0
- package/dist/sandbox/gondolin-gateway.d.ts +85 -0
- package/dist/sandbox/gondolin-gateway.d.ts.map +1 -0
- package/dist/sandbox/gondolin-gateway.js +400 -0
- package/dist/sandbox/gondolin-gateway.js.map +1 -0
- package/dist/sandbox/gondolin-inventory.d.ts +89 -0
- package/dist/sandbox/gondolin-inventory.d.ts.map +1 -0
- package/dist/sandbox/gondolin-inventory.js +267 -0
- package/dist/sandbox/gondolin-inventory.js.map +1 -0
- package/dist/sandbox/gondolin-join.d.ts +62 -0
- package/dist/sandbox/gondolin-join.d.ts.map +1 -0
- package/dist/sandbox/gondolin-join.js +261 -0
- package/dist/sandbox/gondolin-join.js.map +1 -0
- package/dist/sandbox/gondolin-nfs-advisory.d.ts +15 -0
- package/dist/sandbox/gondolin-nfs-advisory.d.ts.map +1 -0
- package/dist/sandbox/gondolin-nfs-advisory.js +60 -0
- package/dist/sandbox/gondolin-nfs-advisory.js.map +1 -0
- package/dist/sandbox/gondolin-placement.d.ts +32 -0
- package/dist/sandbox/gondolin-placement.d.ts.map +1 -0
- package/dist/sandbox/gondolin-placement.js +83 -0
- package/dist/sandbox/gondolin-placement.js.map +1 -0
- package/dist/sandbox/gondolin-remote.d.ts +73 -0
- package/dist/sandbox/gondolin-remote.d.ts.map +1 -0
- package/dist/sandbox/gondolin-remote.js +352 -0
- package/dist/sandbox/gondolin-remote.js.map +1 -0
- package/dist/sandbox/gondolin-worker-client.d.ts +139 -0
- package/dist/sandbox/gondolin-worker-client.d.ts.map +1 -0
- package/dist/sandbox/gondolin-worker-client.js +325 -0
- package/dist/sandbox/gondolin-worker-client.js.map +1 -0
- package/dist/sandbox/gondolin-worker-main.d.ts +2 -0
- package/dist/sandbox/gondolin-worker-main.d.ts.map +1 -0
- package/dist/sandbox/gondolin-worker-main.js +22 -0
- package/dist/sandbox/gondolin-worker-main.js.map +1 -0
- package/dist/sandbox/gondolin-worker.d.ts +55 -0
- package/dist/sandbox/gondolin-worker.d.ts.map +1 -0
- package/dist/sandbox/gondolin-worker.js +201 -0
- package/dist/sandbox/gondolin-worker.js.map +1 -0
- package/dist/sandbox/gondolin.d.ts +52 -0
- package/dist/sandbox/gondolin.d.ts.map +1 -0
- package/dist/sandbox/gondolin.js +425 -0
- package/dist/sandbox/gondolin.js.map +1 -0
- package/dist/sandbox/host.d.ts +2 -0
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +11 -0
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +21 -0
- package/dist/sandbox/identity.d.ts.map +1 -0
- package/dist/sandbox/identity.js +36 -0
- package/dist/sandbox/identity.js.map +1 -0
- package/dist/sandbox/index.d.ts +4 -3
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +5 -3
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.ts +26 -1
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +11 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +35 -0
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/sessions/agent-memory-file-manager.d.ts.map +1 -1
- package/dist/sessions/agent-memory-file-manager.js +16 -30
- package/dist/sessions/agent-memory-file-manager.js.map +1 -1
- package/dist/sessions/history-line.d.ts +20 -0
- package/dist/sessions/history-line.d.ts.map +1 -0
- package/dist/sessions/history-line.js +24 -0
- package/dist/sessions/history-line.js.map +1 -0
- package/dist/sessions/policy.d.ts.map +1 -1
- package/dist/sessions/policy.js +5 -3
- package/dist/sessions/policy.js.map +1 -1
- package/dist/sessions/session-key.d.ts +31 -0
- package/dist/sessions/session-key.d.ts.map +1 -0
- package/dist/sessions/session-key.js +44 -0
- package/dist/sessions/session-key.js.map +1 -0
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +2 -2
- package/dist/sessions/store.js.map +1 -1
- package/dist/tools/bash.d.ts +3 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +24 -17
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -12
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +1 -1
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +33 -4
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/generate-image.d.ts +17 -0
- package/dist/tools/generate-image.d.ts.map +1 -0
- package/dist/tools/generate-image.js +63 -0
- package/dist/tools/generate-image.js.map +1 -0
- package/dist/tools/index.d.ts +16 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +27 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/react.d.ts +15 -0
- package/dist/tools/react.d.ts.map +1 -0
- package/dist/tools/react.js +40 -0
- package/dist/tools/react.js.map +1 -0
- package/dist/tools/sandbox.d.ts +2 -2
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +8 -7
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/subagent.d.ts +69 -0
- package/dist/tools/subagent.d.ts.map +1 -0
- package/dist/tools/subagent.js +286 -0
- package/dist/tools/subagent.js.map +1 -0
- package/dist/tools/types.d.ts +34 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +1 -8
- package/dist/tools/write.js.map +1 -1
- package/dist/trigger.d.ts.map +1 -1
- package/dist/trigger.js +1 -2
- package/dist/trigger.js.map +1 -1
- package/dist/types.d.ts +139 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/disabled.d.ts +9 -0
- package/dist/vault/disabled.d.ts.map +1 -0
- package/dist/vault/disabled.js +23 -0
- package/dist/vault/disabled.js.map +1 -0
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +3 -24
- package/dist/vault/index.js.map +1 -1
- package/dist/vault/policy.d.ts +20 -0
- package/dist/vault/policy.d.ts.map +1 -0
- package/dist/vault/policy.js +20 -0
- package/dist/vault/policy.js.map +1 -0
- package/dist/vault/routing.d.ts +1 -0
- package/dist/vault/routing.d.ts.map +1 -1
- package/dist/vault/routing.js +3 -10
- package/dist/vault/routing.js.map +1 -1
- package/dist/web/admin/portal.d.ts +27 -0
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +91 -130
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/types.d.ts +3 -0
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/login/oauth.d.ts +2 -3
- package/dist/web/login/oauth.d.ts.map +1 -1
- package/dist/web/login/oauth.js +0 -31
- package/dist/web/login/oauth.js.map +1 -1
- package/dist/web/login/portal.js +1 -1
- package/dist/web/login/portal.js.map +1 -1
- package/dist/web/login/types.d.ts +0 -11
- package/dist/web/login/types.d.ts.map +1 -1
- package/dist/web/login/types.js.map +1 -1
- package/dist/web/portal-shell.d.ts +4 -0
- package/dist/web/portal-shell.d.ts.map +1 -0
- package/dist/{portal-shell.js → web/portal-shell.js} +2 -2
- package/dist/web/portal-shell.js.map +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +7 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-view/command.d.ts.map +1 -1
- package/dist/web/session-view/command.js +4 -1
- package/dist/web/session-view/command.js.map +1 -1
- package/dist/web/session-view/portal.d.ts.map +1 -1
- package/dist/web/session-view/portal.js +4 -3
- package/dist/web/session-view/portal.js.map +1 -1
- package/dist/web/session-view/service.d.ts.map +1 -1
- package/dist/web/session-view/service.js +2 -1
- package/dist/web/session-view/service.js.map +1 -1
- package/dist/web/session-view/types.d.ts +2 -1
- package/dist/web/session-view/types.d.ts.map +1 -1
- package/dist/web/session-view/types.js.map +1 -1
- package/package.json +7 -4
- package/dist/download.d.ts.map +0 -1
- package/dist/download.js.map +0 -1
- package/dist/model-registry.d.ts +0 -4
- package/dist/model-registry.d.ts.map +0 -1
- package/dist/model-registry.js +0 -7
- package/dist/model-registry.js.map +0 -1
- package/dist/portal-shell.d.ts +0 -4
- package/dist/portal-shell.d.ts.map +0 -1
- package/dist/portal-shell.js.map +0 -1
- package/dist/runtime/agent-run-controller.d.ts +0 -31
- package/dist/runtime/agent-run-controller.d.ts.map +0 -1
- package/dist/runtime/agent-run-controller.js +0 -195
- package/dist/runtime/agent-run-controller.js.map +0 -1
- /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
- /package/dist/{download.js → cli/download.js} +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { SandboxError } from "./errors.js";
|
|
6
|
+
import { gondolinInventory } from "./gondolin-inventory.js";
|
|
7
|
+
/**
|
|
8
|
+
* The runtime is gone and the command never reached it (the session socket
|
|
9
|
+
* refused the connection) — safe to recreate the runtime and retry.
|
|
10
|
+
*/
|
|
11
|
+
export class GondolinRuntimeGoneError extends Error {
|
|
12
|
+
}
|
|
13
|
+
/** The runtime died with the command in flight — not safe to retry blindly. */
|
|
14
|
+
export class GondolinRuntimeInterruptedError extends Error {
|
|
15
|
+
}
|
|
16
|
+
function defaultSpawnProcess(command, args) {
|
|
17
|
+
return spawn(command, args, { detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
18
|
+
}
|
|
19
|
+
async function defaultConnect(socketPath, callbacks) {
|
|
20
|
+
const { connectToSession } = await import("@earendil-works/gondolin");
|
|
21
|
+
return connectToSession(socketPath, callbacks);
|
|
22
|
+
}
|
|
23
|
+
function abortError() {
|
|
24
|
+
return new Error("Error: command aborted");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A command may quietly work for a long time, but a session that produces no
|
|
28
|
+
* frame at all for this long is indistinguishable from one wedged on dead
|
|
29
|
+
* storage (a hung NFS mount under the workspace blocks guest I/O forever) —
|
|
30
|
+
* kill it and say so instead of hanging the run.
|
|
31
|
+
*/
|
|
32
|
+
const EXEC_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Run one command through a fresh session connection — the shared state
|
|
35
|
+
* machine for both transports. Closing the connection (abort, timeout, or the
|
|
36
|
+
* far side dying) kills the in-flight guest process.
|
|
37
|
+
*/
|
|
38
|
+
export async function execOverSessionConnect(connect, command, options = {}) {
|
|
39
|
+
if (options.signal?.aborted)
|
|
40
|
+
throw abortError();
|
|
41
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? EXEC_IDLE_TIMEOUT_MS;
|
|
42
|
+
return await new Promise((resolve, reject) => {
|
|
43
|
+
let stdout = "";
|
|
44
|
+
let stderr = "";
|
|
45
|
+
let received = false;
|
|
46
|
+
let settled = false;
|
|
47
|
+
let client;
|
|
48
|
+
let idleTimer;
|
|
49
|
+
const settle = (fn) => {
|
|
50
|
+
if (settled)
|
|
51
|
+
return;
|
|
52
|
+
settled = true;
|
|
53
|
+
clearTimeout(idleTimer);
|
|
54
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
55
|
+
fn();
|
|
56
|
+
};
|
|
57
|
+
const onAbort = () => settle(() => {
|
|
58
|
+
client?.close();
|
|
59
|
+
reject(abortError());
|
|
60
|
+
});
|
|
61
|
+
const onIdle = () => settle(() => {
|
|
62
|
+
client?.close();
|
|
63
|
+
reject(new GondolinRuntimeInterruptedError(`no session activity for ${Math.round(idleTimeoutMs / 1000)}s — killed the command ` +
|
|
64
|
+
`(guest I/O hangs when the workspace sits on a dead mount; check the worker's workspace health)`));
|
|
65
|
+
});
|
|
66
|
+
const armIdleTimer = () => {
|
|
67
|
+
if (idleTimeoutMs <= 0 || settled)
|
|
68
|
+
return;
|
|
69
|
+
clearTimeout(idleTimer);
|
|
70
|
+
idleTimer = setTimeout(onIdle, idleTimeoutMs);
|
|
71
|
+
idleTimer.unref?.();
|
|
72
|
+
};
|
|
73
|
+
armIdleTimer();
|
|
74
|
+
const callbacks = {
|
|
75
|
+
onJson: (message) => {
|
|
76
|
+
received = true;
|
|
77
|
+
armIdleTimer();
|
|
78
|
+
if (message.type === "exec_response" && message.id === 1) {
|
|
79
|
+
settle(() => {
|
|
80
|
+
client?.close();
|
|
81
|
+
resolve({
|
|
82
|
+
stdout,
|
|
83
|
+
stderr,
|
|
84
|
+
code: typeof message.exit_code === "number" ? message.exit_code : 1,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else if (message.type === "error") {
|
|
89
|
+
settle(() => {
|
|
90
|
+
client?.close();
|
|
91
|
+
reject(new Error(`Error: Gondolin exec failed (${message.code}): ${message.message}`));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
onBinary: (frame) => {
|
|
96
|
+
received = true;
|
|
97
|
+
armIdleTimer();
|
|
98
|
+
const tag = frame.readUInt8(0);
|
|
99
|
+
const data = frame.subarray(5).toString("utf8");
|
|
100
|
+
if (tag === 1)
|
|
101
|
+
stdout += data;
|
|
102
|
+
else
|
|
103
|
+
stderr += data;
|
|
104
|
+
},
|
|
105
|
+
onClose: (error) => {
|
|
106
|
+
settle(() => {
|
|
107
|
+
const detail = error?.message ?? "connection closed before the command finished";
|
|
108
|
+
const code = error?.code;
|
|
109
|
+
if (!received && (code === "ENOENT" || code === "ECONNREFUSED")) {
|
|
110
|
+
reject(new GondolinRuntimeGoneError(detail));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
reject(new GondolinRuntimeInterruptedError(detail));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
119
|
+
Promise.resolve(connect(callbacks))
|
|
120
|
+
.then((created) => {
|
|
121
|
+
client = created;
|
|
122
|
+
if (settled) {
|
|
123
|
+
created.close();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
created.send({
|
|
127
|
+
type: "exec",
|
|
128
|
+
id: 1,
|
|
129
|
+
cmd: "/bin/sh",
|
|
130
|
+
argv: ["-c", command],
|
|
131
|
+
env: Object.entries(options.env ?? {}).map(([key, value]) => `${key}=${value}`),
|
|
132
|
+
cwd: "/workspace",
|
|
133
|
+
});
|
|
134
|
+
})
|
|
135
|
+
.catch((err) => settle(() => reject(new GondolinRuntimeGoneError(err instanceof Error ? err.message : String(err)))));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** Workers self-stop once no mikan has refreshed the heartbeat for this long. */
|
|
139
|
+
const WORKER_HEARTBEAT_STALE_MS = 45 * 60 * 1000;
|
|
140
|
+
class GondolinWorkerClient {
|
|
141
|
+
constructor() {
|
|
142
|
+
this.spawnProcess = defaultSpawnProcess;
|
|
143
|
+
this.connectImpl = defaultConnect;
|
|
144
|
+
this.killImpl = (pid, signal) => process.kill(pid, signal);
|
|
145
|
+
this.handshakeTimeoutMs = 120_000;
|
|
146
|
+
this.stopWaitMs = 15_000;
|
|
147
|
+
this.stopPollIntervalMs = 100;
|
|
148
|
+
}
|
|
149
|
+
configure(overrides) {
|
|
150
|
+
this.spawnProcess = overrides?.spawnProcess ?? defaultSpawnProcess;
|
|
151
|
+
this.connectImpl = overrides?.connect ?? defaultConnect;
|
|
152
|
+
this.killImpl = overrides?.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
153
|
+
this.handshakeTimeoutMs = overrides?.handshakeTimeoutMs ?? 120_000;
|
|
154
|
+
this.stopWaitMs = overrides?.stopWaitMs ?? 15_000;
|
|
155
|
+
this.stopPollIntervalMs = overrides?.stopPollIntervalMs ?? 100;
|
|
156
|
+
}
|
|
157
|
+
/** Adopt a surviving local worker or spawn a fresh one for the spec. */
|
|
158
|
+
async ensure(instanceId, spec) {
|
|
159
|
+
const adopted = await this.adopt(instanceId, spec.fingerprint);
|
|
160
|
+
if (adopted)
|
|
161
|
+
return adopted;
|
|
162
|
+
return this.spawn({
|
|
163
|
+
instanceId,
|
|
164
|
+
image: spec.image,
|
|
165
|
+
mounts: spec.mounts,
|
|
166
|
+
cpus: spec.vmCpus,
|
|
167
|
+
memory: spec.memory,
|
|
168
|
+
fingerprint: spec.fingerprint,
|
|
169
|
+
inventoryDir: gondolinInventory.directory() ?? join(homedir(), ".mikan", "gondolin-runtimes"),
|
|
170
|
+
heartbeatStaleMs: WORKER_HEARTBEAT_STALE_MS,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** Whether the runtime's worker process is still alive. */
|
|
174
|
+
async isRuntimeAlive(handle) {
|
|
175
|
+
return this.isPidAlive(handle.workerPid);
|
|
176
|
+
}
|
|
177
|
+
/** Spawn a detached worker for the config and wait for its ready handshake. */
|
|
178
|
+
async spawn(config) {
|
|
179
|
+
const entry = fileURLToPath(new URL("./gondolin-worker-main.js", import.meta.url));
|
|
180
|
+
const child = this.spawnProcess(process.execPath, [entry, JSON.stringify(config)]);
|
|
181
|
+
const handshake = await this.readHandshake(child);
|
|
182
|
+
if (!handshake.ready || !handshake.sessionId || !handshake.socketPath) {
|
|
183
|
+
throw new SandboxError(`Error: Gondolin worker failed to start: ${handshake.error ?? "no session in handshake"}`);
|
|
184
|
+
}
|
|
185
|
+
child.stdout?.destroy();
|
|
186
|
+
child.stderr?.destroy();
|
|
187
|
+
child.unref();
|
|
188
|
+
return {
|
|
189
|
+
sessionId: handshake.sessionId,
|
|
190
|
+
instanceId: config.instanceId,
|
|
191
|
+
socketPath: handshake.socketPath,
|
|
192
|
+
workerPid: handshake.workerPid ?? child.pid,
|
|
193
|
+
fingerprint: config.fingerprint,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Adopt a runtime a previous mikan left running: newest live record for the
|
|
198
|
+
* key whose fingerprint still matches and whose session answers a probe.
|
|
199
|
+
* Anything stale — drifted fingerprint, unresponsive session — is stopped so
|
|
200
|
+
* the caller can spawn fresh.
|
|
201
|
+
*/
|
|
202
|
+
async adopt(instanceId, fingerprint) {
|
|
203
|
+
const record = await gondolinInventory.findAdoptable(instanceId);
|
|
204
|
+
if (!record?.socketPath)
|
|
205
|
+
return undefined;
|
|
206
|
+
const handle = {
|
|
207
|
+
sessionId: record.sessionId,
|
|
208
|
+
instanceId,
|
|
209
|
+
socketPath: record.socketPath,
|
|
210
|
+
workerPid: record.ownerPid,
|
|
211
|
+
fingerprint: record.fingerprint ?? "",
|
|
212
|
+
};
|
|
213
|
+
if (record.fingerprint !== fingerprint) {
|
|
214
|
+
await this.stop(handle);
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
await this.exec(handle, "true", { signal: AbortSignal.timeout(5000) });
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
await this.stop(handle);
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
return handle;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Stop a worker and wait for it to clean up after itself; escalate to
|
|
228
|
+
* SIGKILL and reap its leftovers if it will not die.
|
|
229
|
+
*/
|
|
230
|
+
async stop(handle) {
|
|
231
|
+
if (this.isPidAlive(handle.workerPid)) {
|
|
232
|
+
this.signal(handle.workerPid, "SIGTERM");
|
|
233
|
+
if (!(await this.waitForExit(handle.workerPid, this.stopWaitMs))) {
|
|
234
|
+
this.signal(handle.workerPid, "SIGKILL");
|
|
235
|
+
await this.waitForExit(handle.workerPid, 2000);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
await gondolinInventory.reapSession(handle.sessionId);
|
|
239
|
+
}
|
|
240
|
+
/** Whether the worker process behind a handle is still alive. */
|
|
241
|
+
isWorkerAlive(handle) {
|
|
242
|
+
return this.isPidAlive(handle.workerPid);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Run one command over a dedicated session IPC connection. Aborting (or the
|
|
246
|
+
* caller's timeout signal firing) destroys the connection, which kills the
|
|
247
|
+
* in-flight guest process.
|
|
248
|
+
*/
|
|
249
|
+
async exec(handle, command, options = {}) {
|
|
250
|
+
return execOverSessionConnect((callbacks) => this.connectImpl(handle.socketPath, callbacks), command, options);
|
|
251
|
+
}
|
|
252
|
+
signal(pid, signal) {
|
|
253
|
+
try {
|
|
254
|
+
this.killImpl(pid, signal);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// already gone
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
isPidAlive(pid) {
|
|
261
|
+
try {
|
|
262
|
+
this.killImpl(pid, 0);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async waitForExit(pid, waitMs) {
|
|
270
|
+
const deadline = Date.now() + waitMs;
|
|
271
|
+
while (this.isPidAlive(pid)) {
|
|
272
|
+
if (Date.now() >= deadline)
|
|
273
|
+
return false;
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, this.stopPollIntervalMs));
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
readHandshake(child) {
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
let buffer = "";
|
|
281
|
+
let stderr = "";
|
|
282
|
+
let settled = false;
|
|
283
|
+
const settle = (fn) => {
|
|
284
|
+
if (settled)
|
|
285
|
+
return;
|
|
286
|
+
settled = true;
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
fn();
|
|
289
|
+
};
|
|
290
|
+
const timer = setTimeout(() => {
|
|
291
|
+
settle(() => {
|
|
292
|
+
child.kill("SIGKILL");
|
|
293
|
+
reject(new SandboxError(`Error: Gondolin worker did not become ready within ${this.handshakeTimeoutMs}ms${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
294
|
+
});
|
|
295
|
+
}, this.handshakeTimeoutMs);
|
|
296
|
+
child.stdout?.on("data", (chunk) => {
|
|
297
|
+
buffer += chunk.toString("utf8");
|
|
298
|
+
const newline = buffer.indexOf("\n");
|
|
299
|
+
if (newline < 0)
|
|
300
|
+
return;
|
|
301
|
+
const line = buffer.slice(0, newline);
|
|
302
|
+
settle(() => {
|
|
303
|
+
try {
|
|
304
|
+
resolve(JSON.parse(line));
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
child.kill("SIGKILL");
|
|
308
|
+
reject(new SandboxError(`Error: invalid Gondolin worker handshake: ${line}`));
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
child.stderr?.on("data", (chunk) => {
|
|
313
|
+
stderr += chunk.toString("utf8");
|
|
314
|
+
});
|
|
315
|
+
child.once("close", (code) => {
|
|
316
|
+
settle(() => reject(new SandboxError(`Error: Gondolin worker exited before becoming ready (code ${code})${stderr ? `: ${stderr.trim()}` : ""}`)));
|
|
317
|
+
});
|
|
318
|
+
child.once("error", (error) => {
|
|
319
|
+
settle(() => reject(new SandboxError(`Error: failed to spawn Gondolin worker: ${error.message}`)));
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export const gondolinWorkers = new GondolinWorkerClient();
|
|
325
|
+
//# sourceMappingURL=gondolin-worker-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-worker-client.js","sourceRoot":"","sources":["../../src/sandbox/gondolin-worker-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Readable } from \"node:stream\";\nimport { fileURLToPath } from \"node:url\";\nimport { SandboxError } from \"./errors.js\";\nimport { gondolinInventory, type GondolinRuntimeRecord } from \"./gondolin-inventory.js\";\nimport type { GondolinWorkerConfig, GondolinWorkerHandshake } from \"./gondolin-worker.js\";\nimport type { ExecResult } from \"./types.js\";\n\n/** A live worker-hosted runtime as seen from mikan. */\nexport interface GondolinRuntimeHandle {\n sessionId: string;\n instanceId: string;\n socketPath: string;\n workerPid: number;\n fingerprint: string;\n /** Fleet identity of the worker hosting the runtime (remote transport). */\n workerName?: string;\n}\n\n/**\n * What a runtime should look like, resolved by the executor layer. `image` is\n * a local asset directory for the local transport and an image selector for\n * the remote one (resolved on the worker host).\n */\nexport interface GondolinRuntimeSpec {\n image: string;\n mounts: Array<{ source: string; target: string }>;\n /** Raw fractional CPU limit (cgroup enforcement on remote Linux workers). */\n cpus?: string;\n /** Whole vCPUs given to the VM. */\n vmCpus?: number;\n memory?: string;\n fingerprint: string;\n /** mikan-host workspace root, for remote mount prefix translation. */\n workspacePath?: string;\n}\n\n/** One way of running Gondolin runtimes: in local workers or on a remote fleet. */\nexport interface GondolinRuntimeTransport {\n ensure(instanceId: string, spec: GondolinRuntimeSpec): Promise<GondolinRuntimeHandle>;\n stop(\n handle: Pick<GondolinRuntimeHandle, \"workerPid\" | \"sessionId\" | \"instanceId\" | \"workerName\">,\n ): Promise<void>;\n exec(\n handle: GondolinRuntimeHandle,\n command: string,\n options?: { env?: Record<string, string>; signal?: AbortSignal },\n ): Promise<ExecResult>;\n isRuntimeAlive(handle: GondolinRuntimeHandle): Promise<boolean>;\n}\n\n/**\n * The runtime is gone and the command never reached it (the session socket\n * refused the connection) — safe to recreate the runtime and retry.\n */\nexport class GondolinRuntimeGoneError extends Error {}\n\n/** The runtime died with the command in flight — not safe to retry blindly. */\nexport class GondolinRuntimeInterruptedError extends Error {}\n\ninterface WorkerProcessLike {\n pid?: number;\n stdout: Readable | null;\n stderr: Readable | null;\n once(event: \"close\", listener: (code: number | null) => void): unknown;\n once(event: \"error\", listener: (error: Error) => void): unknown;\n kill(signal?: NodeJS.Signals): boolean;\n unref(): void;\n}\n\nexport interface SessionClientCallbacks {\n onJson: (message: {\n type: string;\n id?: number;\n exit_code?: number | null;\n code?: string;\n message?: string;\n }) => void;\n onBinary: (frame: Buffer) => void;\n onClose: (error?: Error) => void;\n}\n\nexport interface SessionClient {\n send(message: object): void;\n close(): void;\n}\n\ninterface GondolinWorkerClientOverrides {\n spawnProcess?: (command: string, args: string[]) => WorkerProcessLike;\n connect?: (socketPath: string, callbacks: SessionClientCallbacks) => SessionClient;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n handshakeTimeoutMs?: number;\n stopWaitMs?: number;\n stopPollIntervalMs?: number;\n}\n\nfunction defaultSpawnProcess(command: string, args: string[]): WorkerProcessLike {\n return spawn(command, args, { detached: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n}\n\nasync function defaultConnect(\n socketPath: string,\n callbacks: SessionClientCallbacks,\n): Promise<SessionClient> {\n const { connectToSession } = await import(\"@earendil-works/gondolin\");\n return connectToSession(socketPath, callbacks);\n}\n\nfunction abortError(): Error {\n return new Error(\"Error: command aborted\");\n}\n\n/**\n * A command may quietly work for a long time, but a session that produces no\n * frame at all for this long is indistinguishable from one wedged on dead\n * storage (a hung NFS mount under the workspace blocks guest I/O forever) —\n * kill it and say so instead of hanging the run.\n */\nconst EXEC_IDLE_TIMEOUT_MS = 10 * 60 * 1000;\n\n/**\n * Run one command through a fresh session connection — the shared state\n * machine for both transports. Closing the connection (abort, timeout, or the\n * far side dying) kills the in-flight guest process.\n */\nexport async function execOverSessionConnect(\n connect: (callbacks: SessionClientCallbacks) => SessionClient | Promise<SessionClient>,\n command: string,\n options: { env?: Record<string, string>; signal?: AbortSignal; idleTimeoutMs?: number } = {},\n): Promise<ExecResult> {\n if (options.signal?.aborted) throw abortError();\n const idleTimeoutMs = options.idleTimeoutMs ?? EXEC_IDLE_TIMEOUT_MS;\n return await new Promise<ExecResult>((resolve, reject) => {\n let stdout = \"\";\n let stderr = \"\";\n let received = false;\n let settled = false;\n let client: SessionClient | undefined;\n let idleTimer: NodeJS.Timeout | undefined;\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(idleTimer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n fn();\n };\n const onAbort = (): void =>\n settle(() => {\n client?.close();\n reject(abortError());\n });\n const onIdle = (): void =>\n settle(() => {\n client?.close();\n reject(\n new GondolinRuntimeInterruptedError(\n `no session activity for ${Math.round(idleTimeoutMs / 1000)}s — killed the command ` +\n `(guest I/O hangs when the workspace sits on a dead mount; check the worker's workspace health)`,\n ),\n );\n });\n const armIdleTimer = (): void => {\n if (idleTimeoutMs <= 0 || settled) return;\n clearTimeout(idleTimer);\n idleTimer = setTimeout(onIdle, idleTimeoutMs);\n idleTimer.unref?.();\n };\n armIdleTimer();\n const callbacks: SessionClientCallbacks = {\n onJson: (message) => {\n received = true;\n armIdleTimer();\n if (message.type === \"exec_response\" && message.id === 1) {\n settle(() => {\n client?.close();\n resolve({\n stdout,\n stderr,\n code: typeof message.exit_code === \"number\" ? message.exit_code : 1,\n });\n });\n } else if (message.type === \"error\") {\n settle(() => {\n client?.close();\n reject(new Error(`Error: Gondolin exec failed (${message.code}): ${message.message}`));\n });\n }\n },\n onBinary: (frame) => {\n received = true;\n armIdleTimer();\n const tag = frame.readUInt8(0);\n const data = frame.subarray(5).toString(\"utf8\");\n if (tag === 1) stdout += data;\n else stderr += data;\n },\n onClose: (error) => {\n settle(() => {\n const detail = error?.message ?? \"connection closed before the command finished\";\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n if (!received && (code === \"ENOENT\" || code === \"ECONNREFUSED\")) {\n reject(new GondolinRuntimeGoneError(detail));\n } else {\n reject(new GondolinRuntimeInterruptedError(detail));\n }\n });\n },\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n Promise.resolve(connect(callbacks))\n .then((created) => {\n client = created;\n if (settled) {\n created.close();\n return;\n }\n created.send({\n type: \"exec\",\n id: 1,\n cmd: \"/bin/sh\",\n argv: [\"-c\", command],\n env: Object.entries(options.env ?? {}).map(([key, value]) => `${key}=${value}`),\n cwd: \"/workspace\",\n });\n })\n .catch((err: unknown) =>\n settle(() =>\n reject(new GondolinRuntimeGoneError(err instanceof Error ? err.message : String(err))),\n ),\n );\n });\n}\n\n/** Workers self-stop once no mikan has refreshed the heartbeat for this long. */\nconst WORKER_HEARTBEAT_STALE_MS = 45 * 60 * 1000;\n\nclass GondolinWorkerClient implements GondolinRuntimeTransport {\n private spawnProcess = defaultSpawnProcess;\n private connectImpl: (\n socketPath: string,\n callbacks: SessionClientCallbacks,\n ) => SessionClient | Promise<SessionClient> = defaultConnect;\n private killImpl: (pid: number, signal: NodeJS.Signals | 0) => void = (pid, signal) =>\n process.kill(pid, signal);\n private handshakeTimeoutMs = 120_000;\n private stopWaitMs = 15_000;\n private stopPollIntervalMs = 100;\n\n configure(overrides?: GondolinWorkerClientOverrides): void {\n this.spawnProcess = overrides?.spawnProcess ?? defaultSpawnProcess;\n this.connectImpl = overrides?.connect ?? defaultConnect;\n this.killImpl = overrides?.kill ?? ((pid, signal) => process.kill(pid, signal));\n this.handshakeTimeoutMs = overrides?.handshakeTimeoutMs ?? 120_000;\n this.stopWaitMs = overrides?.stopWaitMs ?? 15_000;\n this.stopPollIntervalMs = overrides?.stopPollIntervalMs ?? 100;\n }\n\n /** Adopt a surviving local worker or spawn a fresh one for the spec. */\n async ensure(instanceId: string, spec: GondolinRuntimeSpec): Promise<GondolinRuntimeHandle> {\n const adopted = await this.adopt(instanceId, spec.fingerprint);\n if (adopted) return adopted;\n return this.spawn({\n instanceId,\n image: spec.image,\n mounts: spec.mounts,\n cpus: spec.vmCpus,\n memory: spec.memory,\n fingerprint: spec.fingerprint,\n inventoryDir: gondolinInventory.directory() ?? join(homedir(), \".mikan\", \"gondolin-runtimes\"),\n heartbeatStaleMs: WORKER_HEARTBEAT_STALE_MS,\n });\n }\n\n /** Whether the runtime's worker process is still alive. */\n async isRuntimeAlive(handle: GondolinRuntimeHandle): Promise<boolean> {\n return this.isPidAlive(handle.workerPid);\n }\n\n /** Spawn a detached worker for the config and wait for its ready handshake. */\n async spawn(config: GondolinWorkerConfig): Promise<GondolinRuntimeHandle> {\n const entry = fileURLToPath(new URL(\"./gondolin-worker-main.js\", import.meta.url));\n const child = this.spawnProcess(process.execPath, [entry, JSON.stringify(config)]);\n const handshake = await this.readHandshake(child);\n if (!handshake.ready || !handshake.sessionId || !handshake.socketPath) {\n throw new SandboxError(\n `Error: Gondolin worker failed to start: ${handshake.error ?? \"no session in handshake\"}`,\n );\n }\n child.stdout?.destroy();\n child.stderr?.destroy();\n child.unref();\n return {\n sessionId: handshake.sessionId,\n instanceId: config.instanceId,\n socketPath: handshake.socketPath,\n workerPid: handshake.workerPid ?? (child.pid as number),\n fingerprint: config.fingerprint,\n };\n }\n\n /**\n * Adopt a runtime a previous mikan left running: newest live record for the\n * key whose fingerprint still matches and whose session answers a probe.\n * Anything stale — drifted fingerprint, unresponsive session — is stopped so\n * the caller can spawn fresh.\n */\n async adopt(instanceId: string, fingerprint: string): Promise<GondolinRuntimeHandle | undefined> {\n const record: GondolinRuntimeRecord | undefined =\n await gondolinInventory.findAdoptable(instanceId);\n if (!record?.socketPath) return undefined;\n const handle: GondolinRuntimeHandle = {\n sessionId: record.sessionId,\n instanceId,\n socketPath: record.socketPath,\n workerPid: record.ownerPid,\n fingerprint: record.fingerprint ?? \"\",\n };\n if (record.fingerprint !== fingerprint) {\n await this.stop(handle);\n return undefined;\n }\n try {\n await this.exec(handle, \"true\", { signal: AbortSignal.timeout(5000) });\n } catch {\n await this.stop(handle);\n return undefined;\n }\n return handle;\n }\n\n /**\n * Stop a worker and wait for it to clean up after itself; escalate to\n * SIGKILL and reap its leftovers if it will not die.\n */\n async stop(handle: Pick<GondolinRuntimeHandle, \"workerPid\" | \"sessionId\">): Promise<void> {\n if (this.isPidAlive(handle.workerPid)) {\n this.signal(handle.workerPid, \"SIGTERM\");\n if (!(await this.waitForExit(handle.workerPid, this.stopWaitMs))) {\n this.signal(handle.workerPid, \"SIGKILL\");\n await this.waitForExit(handle.workerPid, 2000);\n }\n }\n await gondolinInventory.reapSession(handle.sessionId);\n }\n\n /** Whether the worker process behind a handle is still alive. */\n isWorkerAlive(handle: Pick<GondolinRuntimeHandle, \"workerPid\">): boolean {\n return this.isPidAlive(handle.workerPid);\n }\n\n /**\n * Run one command over a dedicated session IPC connection. Aborting (or the\n * caller's timeout signal firing) destroys the connection, which kills the\n * in-flight guest process.\n */\n async exec(\n handle: GondolinRuntimeHandle,\n command: string,\n options: { env?: Record<string, string>; signal?: AbortSignal } = {},\n ): Promise<ExecResult> {\n return execOverSessionConnect(\n (callbacks) => this.connectImpl(handle.socketPath, callbacks),\n command,\n options,\n );\n }\n\n private signal(pid: number, signal: NodeJS.Signals): void {\n try {\n this.killImpl(pid, signal);\n } catch {\n // already gone\n }\n }\n\n private isPidAlive(pid: number): boolean {\n try {\n this.killImpl(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private async waitForExit(pid: number, waitMs: number): Promise<boolean> {\n const deadline = Date.now() + waitMs;\n while (this.isPidAlive(pid)) {\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, this.stopPollIntervalMs));\n }\n return true;\n }\n\n private readHandshake(child: WorkerProcessLike): Promise<GondolinWorkerHandshake> {\n return new Promise((resolve, reject) => {\n let buffer = \"\";\n let stderr = \"\";\n let settled = false;\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n fn();\n };\n const timer = setTimeout(() => {\n settle(() => {\n child.kill(\"SIGKILL\");\n reject(\n new SandboxError(\n `Error: Gondolin worker did not become ready within ${this.handshakeTimeoutMs}ms${\n stderr ? `: ${stderr.trim()}` : \"\"\n }`,\n ),\n );\n });\n }, this.handshakeTimeoutMs);\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString(\"utf8\");\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) return;\n const line = buffer.slice(0, newline);\n settle(() => {\n try {\n resolve(JSON.parse(line) as GondolinWorkerHandshake);\n } catch {\n child.kill(\"SIGKILL\");\n reject(new SandboxError(`Error: invalid Gondolin worker handshake: ${line}`));\n }\n });\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf8\");\n });\n child.once(\"close\", (code) => {\n settle(() =>\n reject(\n new SandboxError(\n `Error: Gondolin worker exited before becoming ready (code ${code})${\n stderr ? `: ${stderr.trim()}` : \"\"\n }`,\n ),\n ),\n );\n });\n child.once(\"error\", (error) => {\n settle(() =>\n reject(new SandboxError(`Error: failed to spawn Gondolin worker: ${error.message}`)),\n );\n });\n });\n }\n}\n\nexport const gondolinWorkers = new GondolinWorkerClient();\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-worker-main.d.ts","sourceRoot":"","sources":["../../src/sandbox/gondolin-worker-main.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Entry point for the detached Gondolin worker process. Spawned by mikan with
|
|
2
|
+
// one JSON argv (the GondolinWorkerConfig); survives the spawning mikan so the
|
|
3
|
+
// VM keeps running across mikan restarts and deploys.
|
|
4
|
+
import { runGondolinWorker } from "./gondolin-worker.js";
|
|
5
|
+
// The spawning mikan may be gone when we write; a broken pipe must not crash
|
|
6
|
+
// a worker that is otherwise healthy.
|
|
7
|
+
process.stdout.on("error", () => { });
|
|
8
|
+
process.stderr.on("error", () => { });
|
|
9
|
+
let config;
|
|
10
|
+
try {
|
|
11
|
+
config = JSON.parse(process.argv[2] ?? "");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
process.stdout.write(JSON.stringify({ ready: false, error: "invalid worker config" }) + "\n");
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
runGondolinWorker(config).catch((err) => {
|
|
18
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
19
|
+
process.stdout.write(JSON.stringify({ ready: false, error }) + "\n");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=gondolin-worker-main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-worker-main.js","sourceRoot":"","sources":["../../src/sandbox/gondolin-worker-main.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,sDAAsD;AACtD,OAAO,EAAE,iBAAiB,EAA6B,MAAM,sBAAsB,CAAC;AAEpF,6EAA6E;AAC7E,sCAAsC;AACtC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACrC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AAErC,IAAI,MAA4B,CAAC;AACjC,IAAI,CAAC;IACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAyB,CAAC;AACrE,CAAC;AAAC,MAAM,CAAC;IACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC/C,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC","sourcesContent":["// Entry point for the detached Gondolin worker process. Spawned by mikan with\n// one JSON argv (the GondolinWorkerConfig); survives the spawning mikan so the\n// VM keeps running across mikan restarts and deploys.\nimport { runGondolinWorker, type GondolinWorkerConfig } from \"./gondolin-worker.js\";\n\n// The spawning mikan may be gone when we write; a broken pipe must not crash\n// a worker that is otherwise healthy.\nprocess.stdout.on(\"error\", () => {});\nprocess.stderr.on(\"error\", () => {});\n\nlet config: GondolinWorkerConfig;\ntry {\n config = JSON.parse(process.argv[2] ?? \"\") as GondolinWorkerConfig;\n} catch {\n process.stdout.write(JSON.stringify({ ready: false, error: \"invalid worker config\" }) + \"\\n\");\n process.exit(2);\n}\n\nrunGondolinWorker(config).catch((err: unknown) => {\n const error = err instanceof Error ? err.message : String(err);\n process.stdout.write(JSON.stringify({ ready: false, error }) + \"\\n\");\n process.exit(1);\n});\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
type GondolinModule = typeof import("@earendil-works/gondolin");
|
|
2
|
+
/**
|
|
3
|
+
* Everything a worker process needs to host one Gondolin VM. Serialized as a
|
|
4
|
+
* single JSON argv entry — it must stay free of secrets (vault env travels
|
|
5
|
+
* per-exec over the session IPC socket, never through the worker command
|
|
6
|
+
* line).
|
|
7
|
+
*/
|
|
8
|
+
export interface GondolinWorkerConfig {
|
|
9
|
+
/** mikan session key the runtime belongs to. */
|
|
10
|
+
instanceId: string;
|
|
11
|
+
/** Resolved guest image asset directory. */
|
|
12
|
+
image?: string;
|
|
13
|
+
/** Image selector resolved inside the worker (remote workers, where the
|
|
14
|
+
* spawning host has no image store). Ignored when `image` is set. */
|
|
15
|
+
imageSelector?: string;
|
|
16
|
+
mounts: Array<{
|
|
17
|
+
source: string;
|
|
18
|
+
target: string;
|
|
19
|
+
}>;
|
|
20
|
+
cpus?: number;
|
|
21
|
+
memory?: string;
|
|
22
|
+
/** Desired-runtime fingerprint recorded for adoption checks. */
|
|
23
|
+
fingerprint: string;
|
|
24
|
+
/** Runtime inventory directory (shared with the spawning mikan). */
|
|
25
|
+
inventoryDir: string;
|
|
26
|
+
/**
|
|
27
|
+
* Self-stop when the mikan heartbeat file is older than this — no mikan is
|
|
28
|
+
* left to adopt the runtime. 0 disables the check.
|
|
29
|
+
*/
|
|
30
|
+
heartbeatStaleMs: number;
|
|
31
|
+
}
|
|
32
|
+
/** First stdout line of a worker, consumed by the spawning mikan. */
|
|
33
|
+
export interface GondolinWorkerHandshake {
|
|
34
|
+
ready: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
sessionId?: string;
|
|
37
|
+
socketPath?: string;
|
|
38
|
+
workerPid?: number;
|
|
39
|
+
runnerPid?: number | null;
|
|
40
|
+
}
|
|
41
|
+
interface GondolinWorkerDeps {
|
|
42
|
+
loadGondolin?: () => Promise<GondolinModule>;
|
|
43
|
+
announce?: (line: string) => void;
|
|
44
|
+
exit?: (code: number) => void;
|
|
45
|
+
pollIntervalMs?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Host one Gondolin VM for its whole life: boot it, persist the inventory
|
|
49
|
+
* record other processes adopt it through, announce readiness on stdout, then
|
|
50
|
+
* watch the VM runner and shut down when it dies, when a SIGTERM arrives, or
|
|
51
|
+
* when every mikan that could adopt the runtime is gone (stale heartbeat).
|
|
52
|
+
*/
|
|
53
|
+
export declare function runGondolinWorker(config: GondolinWorkerConfig, deps?: GondolinWorkerDeps): Promise<void>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=gondolin-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-worker.d.ts","sourceRoot":"","sources":["../../src/sandbox/gondolin-worker.ts"],"names":[],"mappings":"AAMA,KAAK,cAAc,GAAG,cAAc,0BAA0B,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;yEACqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,kBAAkB;IAC1B,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAwED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA8Hf"}
|