@geminixiang/mikan 1.0.0-beta.3 → 1.0.0-beta.30
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 +563 -0
- package/README.md +60 -24
- 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 +47 -65
- 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 +46 -211
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/github/bot.d.ts +86 -0
- package/dist/adapters/github/bot.d.ts.map +1 -0
- package/dist/adapters/github/bot.js +640 -0
- package/dist/adapters/github/bot.js.map +1 -0
- package/dist/adapters/github/client.d.ts +122 -0
- package/dist/adapters/github/client.d.ts.map +1 -0
- package/dist/adapters/github/client.js +314 -0
- package/dist/adapters/github/client.js.map +1 -0
- package/dist/adapters/github/cloudbuild.d.ts +19 -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 +98 -0
- package/dist/adapters/github/context.js.map +1 -0
- package/dist/adapters/github/gcp-auth.d.ts +28 -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/github-ops.d.ts +90 -0
- package/dist/adapters/github/github-ops.d.ts.map +1 -0
- package/dist/adapters/github/github-ops.js +405 -0
- package/dist/adapters/github/github-ops.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 +32 -0
- package/dist/adapters/github/repo.d.ts.map +1 -0
- package/dist/adapters/github/repo.js +141 -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 +19 -0
- package/dist/adapters/github/tools/checks.d.ts.map +1 -0
- package/dist/adapters/github/tools/checks.js +105 -0
- package/dist/adapters/github/tools/checks.js.map +1 -0
- package/dist/adapters/github/tools/issue.d.ts +21 -0
- package/dist/adapters/github/tools/issue.d.ts.map +1 -0
- package/dist/adapters/github/tools/issue.js +48 -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 +48 -0
- package/dist/adapters/github/tools/pr.js.map +1 -0
- package/dist/adapters/github/tools/read.d.ts +21 -0
- package/dist/adapters/github/tools/read.d.ts.map +1 -0
- package/dist/adapters/github/tools/read.js +141 -0
- package/dist/adapters/github/tools/read.js.map +1 -0
- package/dist/adapters/github/tools/review-reply.d.ts +17 -0
- package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
- package/dist/adapters/github/tools/review-reply.js +38 -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 +34 -0
- package/dist/adapters/github/tools/sync.js.map +1 -0
- package/dist/adapters/github/types.d.ts +372 -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 +84 -22
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/progressive-renderer.d.ts +7 -0
- package/dist/adapters/progressive-renderer.d.ts.map +1 -0
- package/dist/adapters/progressive-renderer.js +383 -0
- package/dist/adapters/progressive-renderer.js.map +1 -0
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +111 -111
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +10 -6
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +173 -145
- 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 +126 -323
- 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/slack/tool-pack.d.ts +11 -0
- package/dist/adapters/slack/tool-pack.d.ts.map +1 -0
- package/dist/adapters/slack/tool-pack.js +45 -0
- package/dist/adapters/slack/tool-pack.js.map +1 -0
- package/dist/adapters/slack/tools/blockkit.d.ts +14 -0
- package/dist/adapters/slack/tools/blockkit.d.ts.map +1 -0
- package/dist/adapters/slack/tools/blockkit.js +106 -0
- package/dist/adapters/slack/tools/blockkit.js.map +1 -0
- package/dist/adapters/slack/types.d.ts +37 -0
- package/dist/adapters/slack/types.d.ts.map +1 -1
- package/dist/adapters/slack/types.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 +51 -63
- 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 +55 -182
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/types.d.ts +86 -10
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent.d.ts +14 -19
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +745 -243
- package/dist/agent.js.map +1 -1
- package/dist/cli/arg-grammar.d.ts +25 -0
- package/dist/cli/arg-grammar.d.ts.map +1 -0
- package/dist/cli/arg-grammar.js +59 -0
- package/dist/cli/arg-grammar.js.map +1 -0
- package/dist/cli/boot.d.ts +5 -0
- package/dist/cli/boot.d.ts.map +1 -0
- package/dist/cli/boot.js +112 -0
- package/dist/cli/boot.js.map +1 -0
- package/dist/cli/download.d.ts.map +1 -0
- package/dist/cli/download.js.map +1 -0
- package/dist/cli/ext-dev.d.ts +2 -0
- package/dist/cli/ext-dev.d.ts.map +1 -0
- package/dist/cli/ext-dev.js +177 -0
- package/dist/cli/ext-dev.js.map +1 -0
- package/dist/cli/ext-git.d.ts +17 -0
- package/dist/cli/ext-git.d.ts.map +1 -0
- package/dist/cli/ext-git.js +107 -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 +222 -0
- package/dist/cli/ext.js.map +1 -0
- package/dist/cli/types.d.ts +20 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.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 +18 -8
- 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 +35 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/manifest.d.ts +30 -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 +2 -3
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +57 -20
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +10 -2
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/registry.d.ts +2 -2
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +1 -2
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +23 -17
- 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 +70 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +1 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +102 -95
- package/dist/config.js.map +1 -1
- package/dist/env-manifest.d.ts +28 -0
- package/dist/env-manifest.d.ts.map +1 -0
- package/dist/env-manifest.js +214 -0
- package/dist/env-manifest.js.map +1 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +9 -73
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +7 -6
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +105 -110
- package/dist/execution-resolver.js.map +1 -1
- package/dist/harness/auth.d.ts +2 -1
- package/dist/harness/auth.d.ts.map +1 -1
- package/dist/harness/auth.js +6 -0
- package/dist/harness/auth.js.map +1 -1
- package/dist/harness/event-format.d.ts +25 -0
- package/dist/harness/event-format.d.ts.map +1 -0
- package/dist/harness/event-format.js +159 -0
- package/dist/harness/event-format.js.map +1 -0
- package/dist/harness/extensions/blockkit.d.ts +19 -0
- package/dist/harness/extensions/blockkit.d.ts.map +1 -0
- package/dist/harness/extensions/blockkit.js +42 -0
- package/dist/harness/extensions/blockkit.js.map +1 -0
- package/dist/harness/extensions/loader.d.ts +25 -48
- package/dist/harness/extensions/loader.d.ts.map +1 -1
- package/dist/harness/extensions/loader.js +373 -113
- package/dist/harness/extensions/loader.js.map +1 -1
- package/dist/harness/extensions/registry.d.ts +75 -2
- package/dist/harness/extensions/registry.d.ts.map +1 -1
- package/dist/harness/extensions/registry.js +239 -0
- package/dist/harness/extensions/registry.js.map +1 -1
- package/dist/harness/extensions/types.d.ts +318 -25
- 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 +18 -8
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +7 -4
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/models.d.ts +7 -4
- 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 +50 -54
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +230 -44
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session-store.d.ts +9 -5
- package/dist/harness/session-store.d.ts.map +1 -1
- package/dist/harness/session-store.js +131 -30
- package/dist/harness/session-store.js.map +1 -1
- package/dist/harness/settings.d.ts +2 -20
- package/dist/harness/settings.d.ts.map +1 -1
- package/dist/harness/settings.js +11 -0
- package/dist/harness/settings.js.map +1 -1
- package/dist/harness/skills.d.ts +2 -29
- package/dist/harness/skills.d.ts.map +1 -1
- package/dist/harness/skills.js.map +1 -1
- package/dist/harness/subagent-profiles.d.ts +8 -0
- package/dist/harness/subagent-profiles.d.ts.map +1 -0
- package/dist/harness/subagent-profiles.js +290 -0
- package/dist/harness/subagent-profiles.js.map +1 -0
- package/dist/harness/subagent-runner.d.ts +39 -0
- package/dist/harness/subagent-runner.d.ts.map +1 -0
- package/dist/harness/subagent-runner.js +615 -0
- package/dist/harness/subagent-runner.js.map +1 -0
- package/dist/harness/types.d.ts +263 -1
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +6 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +32 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/index.d.ts +33 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/main.js +253 -109
- package/dist/main.js.map +1 -1
- package/dist/observability/instrument.js +6 -4
- package/dist/observability/instrument.js.map +1 -1
- package/dist/observability/sentry.d.ts.map +1 -1
- package/dist/observability/sentry.js +3 -2
- package/dist/observability/sentry.js.map +1 -1
- package/dist/packages/admin.d.ts +17 -0
- package/dist/packages/admin.d.ts.map +1 -0
- package/dist/packages/admin.js +96 -0
- package/dist/packages/admin.js.map +1 -0
- package/dist/packages/index.d.ts +6 -0
- package/dist/packages/index.d.ts.map +1 -0
- package/dist/packages/index.js +6 -0
- package/dist/packages/index.js.map +1 -0
- package/dist/packages/inspect.d.ts +11 -0
- package/dist/packages/inspect.d.ts.map +1 -0
- package/dist/packages/inspect.js +100 -0
- package/dist/packages/inspect.js.map +1 -0
- package/dist/packages/materialize.d.ts +39 -0
- package/dist/packages/materialize.d.ts.map +1 -0
- package/dist/packages/materialize.js +300 -0
- package/dist/packages/materialize.js.map +1 -0
- package/dist/packages/resolve.d.ts +25 -0
- package/dist/packages/resolve.d.ts.map +1 -0
- package/dist/packages/resolve.js +212 -0
- package/dist/packages/resolve.js.map +1 -0
- package/dist/packages/source.d.ts +33 -0
- package/dist/packages/source.d.ts.map +1 -0
- package/dist/packages/source.js +177 -0
- package/dist/packages/source.js.map +1 -0
- package/dist/packages/types.d.ts +104 -0
- package/dist/packages/types.d.ts.map +1 -0
- package/dist/packages/types.js +2 -0
- package/dist/packages/types.js.map +1 -0
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +6 -6
- 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 +481 -98
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +28 -0
- package/dist/runtime/session-lifecycle.d.ts.map +1 -0
- package/dist/runtime/session-lifecycle.js +161 -0
- package/dist/runtime/session-lifecycle.js.map +1 -0
- package/dist/runtime/types.d.ts +45 -5
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts +3 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +11 -0
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts +4 -0
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +12 -2
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/firecracker.d.ts +3 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -1
- package/dist/sandbox/firecracker.js +11 -1
- package/dist/sandbox/firecracker.js.map +1 -1
- package/dist/sandbox/gondolin.d.ts +57 -0
- package/dist/sandbox/gondolin.d.ts.map +1 -0
- package/dist/sandbox/gondolin.js +764 -0
- package/dist/sandbox/gondolin.js.map +1 -0
- package/dist/sandbox/host.d.ts +4 -0
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +52 -0
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +16 -0
- package/dist/sandbox/identity.d.ts.map +1 -0
- package/dist/sandbox/identity.js +42 -0
- package/dist/sandbox/identity.js.map +1 -0
- package/dist/sandbox/image.d.ts.map +1 -1
- package/dist/sandbox/image.js +1 -0
- package/dist/sandbox/image.js.map +1 -1
- package/dist/sandbox/index.d.ts +15 -4
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +25 -3
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/path-context.js +18 -8
- package/dist/sandbox/path-context.js.map +1 -1
- package/dist/sandbox/types.d.ts +65 -1
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +12 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +38 -0
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts +25 -0
- package/dist/sessions/chat-history-sync.d.ts.map +1 -0
- package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +98 -124
- package/dist/sessions/chat-history-sync.js.map +1 -0
- package/dist/sessions/conversation-log.d.ts +9 -0
- package/dist/sessions/conversation-log.d.ts.map +1 -0
- package/dist/sessions/conversation-log.js +47 -0
- package/dist/sessions/conversation-log.js.map +1 -0
- 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/rotation.d.ts +2 -0
- package/dist/sessions/rotation.d.ts.map +1 -0
- package/dist/sessions/rotation.js +39 -0
- package/dist/sessions/rotation.js.map +1 -0
- package/dist/sessions/session-key.d.ts +40 -0
- package/dist/sessions/session-key.d.ts.map +1 -0
- package/dist/sessions/session-key.js +92 -0
- package/dist/sessions/session-key.js.map +1 -0
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +40 -18
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +17 -4
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/settings-mutation.d.ts +8 -0
- package/dist/settings-mutation.d.ts.map +1 -0
- package/dist/settings-mutation.js +43 -0
- package/dist/settings-mutation.js.map +1 -0
- package/dist/subagent-progress.d.ts +43 -0
- package/dist/subagent-progress.d.ts.map +1 -0
- package/dist/subagent-progress.js +220 -0
- package/dist/subagent-progress.js.map +1 -0
- package/dist/tools/bash.d.ts +3 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +25 -17
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +10 -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 +53 -43
- 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/host-fn-tool.d.ts +21 -0
- package/dist/tools/host-fn-tool.d.ts.map +1 -0
- package/dist/tools/host-fn-tool.js +32 -0
- package/dist/tools/host-fn-tool.js.map +1 -0
- package/dist/tools/index.d.ts +14 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +28 -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 +29 -0
- package/dist/tools/react.js.map +1 -0
- package/dist/tools/read.d.ts +1 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +37 -31
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/sandbox.d.ts +2 -2
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +14 -9
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/subagent-slots.d.ts +28 -0
- package/dist/tools/subagent-slots.d.ts.map +1 -0
- package/dist/tools/subagent-slots.js +72 -0
- package/dist/tools/subagent-slots.js.map +1 -0
- package/dist/tools/subagent.d.ts +54 -0
- package/dist/tools/subagent.d.ts.map +1 -0
- package/dist/tools/subagent.js +401 -0
- package/dist/tools/subagent.js.map +1 -0
- package/dist/tools/types.d.ts +40 -6
- 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 +2 -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 +232 -32
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/date.d.ts +1 -1
- package/dist/utils/date.js +1 -1
- package/dist/utils/date.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 +22 -0
- package/dist/vault/disabled.js.map +1 -0
- package/dist/vault/index.d.ts +0 -2
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +30 -33
- package/dist/vault/index.js.map +1 -1
- package/dist/vault/injection.d.ts +10 -0
- package/dist/vault/injection.d.ts.map +1 -0
- package/dist/vault/injection.js +30 -0
- package/dist/vault/injection.js.map +1 -0
- 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/types.d.ts +4 -2
- package/dist/vault/types.d.ts.map +1 -1
- package/dist/vault/types.js.map +1 -1
- package/dist/web/admin/portal.d.ts +17 -2
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +385 -155
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/types.d.ts +18 -0
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/agent-events/portal.d.ts.map +1 -1
- package/dist/web/agent-events/portal.js +2 -1
- package/dist/web/agent-events/portal.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/dist/workspace-projection/index.d.ts +5 -0
- package/dist/workspace-projection/index.d.ts.map +1 -0
- package/dist/workspace-projection/index.js +71 -0
- package/dist/workspace-projection/index.js.map +1 -0
- package/dist/workspace-projection/types.d.ts +7 -0
- package/dist/workspace-projection/types.d.ts.map +1 -0
- package/dist/workspace-projection/types.js +2 -0
- package/dist/workspace-projection/types.js.map +1 -0
- package/package.json +28 -15
- package/dist/adapters/streaming.d.ts +0 -18
- package/dist/adapters/streaming.d.ts.map +0 -1
- package/dist/adapters/streaming.js +0 -44
- package/dist/adapters/streaming.js.map +0 -1
- 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/sessions/agent-memory-file-manager.d.ts +0 -19
- package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
- package/dist/sessions/agent-memory-file-manager.js.map +0 -1
- package/dist/vault/routing.d.ts +0 -3
- package/dist/vault/routing.d.ts.map +0 -1
- package/dist/vault/routing.js +0 -13
- package/dist/vault/routing.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,764 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import * as log from "../log.js";
|
|
5
|
+
import { withRuntimeBootstrap } from "./container.js";
|
|
6
|
+
import { SandboxError } from "./errors.js";
|
|
7
|
+
import { createMountedRuntimePathContext } from "./path-context.js";
|
|
8
|
+
import { execReadFile, execReadFileBase64, execWriteFile, shellEscape } from "./utils.js";
|
|
9
|
+
const MINIMUM_NODE_VERSION = [23, 6, 0];
|
|
10
|
+
const MIKAN_IMAGE = "mikan-sandbox:latest";
|
|
11
|
+
/** Write-back cadence for projected `/workspace` files (e.g. MEMORY.md). */
|
|
12
|
+
const PROJECTION_SYNC_INTERVAL_MS = 2000;
|
|
13
|
+
const PROJECTION_LOCK_RETRY_MS = 25;
|
|
14
|
+
const PROJECTION_LOCK_STALE_MS = 60_000;
|
|
15
|
+
/**
|
|
16
|
+
* A command may quietly work for a long time, but a session that produces no
|
|
17
|
+
* frame at all for this long is indistinguishable from one wedged on dead
|
|
18
|
+
* storage (for example, blocked guest workspace I/O) — kill it and say so
|
|
19
|
+
* instead of hanging the run.
|
|
20
|
+
*/
|
|
21
|
+
const EXEC_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
22
|
+
const sessions = new Map();
|
|
23
|
+
const transitions = new Map();
|
|
24
|
+
let activeShutdowns = 0;
|
|
25
|
+
let shutdownGeneration = 0;
|
|
26
|
+
/*
|
|
27
|
+
* Crash-recovery vocabulary.
|
|
28
|
+
*
|
|
29
|
+
* - **Gone**: the command never reached the runtime because its session socket
|
|
30
|
+
* refused the connection. Recreating the runtime and retrying is safe.
|
|
31
|
+
* - **Interrupted**: the runtime died or went silent with a command in flight.
|
|
32
|
+
* Side effects may have landed, so the failure must surface.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* The runtime is gone and the command never reached it (the session socket
|
|
36
|
+
* refused the connection) — safe to recreate the runtime and retry.
|
|
37
|
+
*/
|
|
38
|
+
class GondolinRuntimeGoneError extends Error {
|
|
39
|
+
}
|
|
40
|
+
/** The runtime died with the command in flight — not safe to retry blindly. */
|
|
41
|
+
class GondolinRuntimeInterruptedError extends Error {
|
|
42
|
+
}
|
|
43
|
+
class GondolinResourceManager {
|
|
44
|
+
constructor() {
|
|
45
|
+
this.boostedKeys = new Set();
|
|
46
|
+
this.overrideLimits = new Map();
|
|
47
|
+
}
|
|
48
|
+
configure(defaultLimits, boostLimits) {
|
|
49
|
+
this.defaultLimits = defaultLimits;
|
|
50
|
+
this.boostLimits = boostLimits;
|
|
51
|
+
this.boostedKeys.clear();
|
|
52
|
+
this.overrideLimits.clear();
|
|
53
|
+
}
|
|
54
|
+
async boost(key) {
|
|
55
|
+
if (this.boostLimits?.cpus || this.boostLimits?.memory) {
|
|
56
|
+
this.overrideLimits.delete(key);
|
|
57
|
+
this.boostedKeys.add(key);
|
|
58
|
+
}
|
|
59
|
+
return this.getLimitStatus(key);
|
|
60
|
+
}
|
|
61
|
+
async setLimits(key, limits) {
|
|
62
|
+
this.boostedKeys.delete(key);
|
|
63
|
+
this.overrideLimits.set(key, { ...this.defaultLimits, ...limits });
|
|
64
|
+
return this.getLimitStatus(key);
|
|
65
|
+
}
|
|
66
|
+
getLimitStatus(key) {
|
|
67
|
+
return { limits: this.effectiveLimits(key), boosted: this.boostedKeys.has(key) };
|
|
68
|
+
}
|
|
69
|
+
getDefaultLimits() {
|
|
70
|
+
return this.defaultLimits;
|
|
71
|
+
}
|
|
72
|
+
getBoostLimits() {
|
|
73
|
+
return this.boostLimits;
|
|
74
|
+
}
|
|
75
|
+
clear(key) {
|
|
76
|
+
this.boostedKeys.delete(key);
|
|
77
|
+
this.overrideLimits.delete(key);
|
|
78
|
+
}
|
|
79
|
+
effectiveLimits(key) {
|
|
80
|
+
const override = this.overrideLimits.get(key);
|
|
81
|
+
if (override)
|
|
82
|
+
return override;
|
|
83
|
+
if (!this.boostedKeys.has(key))
|
|
84
|
+
return this.defaultLimits;
|
|
85
|
+
return { ...this.defaultLimits, ...this.boostLimits };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export const gondolinResources = new GondolinResourceManager();
|
|
89
|
+
/** Configure the Gondolin resource controller. */
|
|
90
|
+
export function configureGondolinRuntime(options) {
|
|
91
|
+
gondolinResources.configure(options.limits, options.boostLimits);
|
|
92
|
+
}
|
|
93
|
+
function parseGondolinSandboxArg(value) {
|
|
94
|
+
if (!value.startsWith("gondolin:"))
|
|
95
|
+
return undefined;
|
|
96
|
+
const profile = value.slice("gondolin:".length);
|
|
97
|
+
if (!profile) {
|
|
98
|
+
throw new SandboxError("Error: gondolin sandbox requires a profile (e.g., gondolin:default)");
|
|
99
|
+
}
|
|
100
|
+
if (profile !== "default") {
|
|
101
|
+
throw new SandboxError(`Error: unsupported gondolin profile '${profile}'. Use 'gondolin:default'`);
|
|
102
|
+
}
|
|
103
|
+
return { type: "gondolin", profile };
|
|
104
|
+
}
|
|
105
|
+
function assertSupportedNodeVersion() {
|
|
106
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
107
|
+
if (major < MINIMUM_NODE_VERSION[0] || (major === MINIMUM_NODE_VERSION[0] && minor < 6)) {
|
|
108
|
+
throw new SandboxError(`Error: gondolin:default requires Node.js >=23.6.0 (current: ${process.versions.node}). Other sandbox modes remain available on Node.js >=22.19.0.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function validateGondolinSandbox() {
|
|
112
|
+
assertSupportedNodeVersion();
|
|
113
|
+
console.log(" Gondolin microVM enabled. Profile: default");
|
|
114
|
+
}
|
|
115
|
+
async function resolveDesiredRuntime(config) {
|
|
116
|
+
const selector = config.image ?? MIKAN_IMAGE;
|
|
117
|
+
const { ensureImageSelector } = (await import("@earendil-works/gondolin"));
|
|
118
|
+
const resolvedImage = await ensureImageSelector(selector);
|
|
119
|
+
const image = resolvedImage.assetDir;
|
|
120
|
+
const imageIdentity = resolvedImage.buildId ?? resolvedImage.assetDir;
|
|
121
|
+
const mounts = (config.mounts ?? [{ source: config.workspacePath ?? process.cwd(), target: "/workspace" }]).toSorted((left, right) => left.target.localeCompare(right.target));
|
|
122
|
+
return {
|
|
123
|
+
image,
|
|
124
|
+
imageIdentity,
|
|
125
|
+
mounts,
|
|
126
|
+
credentialIdentity: credentialIdentity(mounts),
|
|
127
|
+
limits: config.resourceKey
|
|
128
|
+
? gondolinResources.getLimitStatus(config.resourceKey).limits
|
|
129
|
+
: undefined,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Content hashes of credential file mounts (files projected outside
|
|
134
|
+
* /workspace). Rotating a credential on the host changes the fingerprint, so
|
|
135
|
+
* the next command recreates the runtime with a fresh projection instead of
|
|
136
|
+
* serving the stale copy for the rest of the VM's life. Workspace files are
|
|
137
|
+
* excluded: the guest writes them back, which would drift the runtime it runs
|
|
138
|
+
* in.
|
|
139
|
+
*/
|
|
140
|
+
function credentialIdentity(mounts) {
|
|
141
|
+
const identity = {};
|
|
142
|
+
for (const mount of mounts) {
|
|
143
|
+
if (mount.target.startsWith("/workspace/") || mount.target === "/workspace")
|
|
144
|
+
continue;
|
|
145
|
+
try {
|
|
146
|
+
if (!statSync(mount.source).isFile())
|
|
147
|
+
continue;
|
|
148
|
+
identity[mount.target] = createHash("sha256")
|
|
149
|
+
.update(readFileSync(mount.source))
|
|
150
|
+
.digest("hex");
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// missing source: creation skips it too, so it has no runtime identity
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return identity;
|
|
157
|
+
}
|
|
158
|
+
function runtimeFingerprint(desired) {
|
|
159
|
+
return createHash("sha256")
|
|
160
|
+
.update(JSON.stringify({
|
|
161
|
+
image: desired.imageIdentity,
|
|
162
|
+
mounts: desired.mounts,
|
|
163
|
+
credentials: desired.credentialIdentity,
|
|
164
|
+
limits: desired.limits,
|
|
165
|
+
}))
|
|
166
|
+
.digest("hex");
|
|
167
|
+
}
|
|
168
|
+
function gondolinCpuCount(cpus) {
|
|
169
|
+
if (cpus === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
const parsed = Number(cpus);
|
|
172
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
173
|
+
throw new SandboxError(`Error: invalid Gondolin CPU limit '${cpus}'`);
|
|
174
|
+
}
|
|
175
|
+
// ponytail: Gondolin exposes whole vCPUs; add host cgroup quotas if fractional enforcement matters.
|
|
176
|
+
return Math.ceil(parsed);
|
|
177
|
+
}
|
|
178
|
+
function sha256(data) {
|
|
179
|
+
return createHash("sha256").update(data).digest("hex");
|
|
180
|
+
}
|
|
181
|
+
function partitionMounts(mounts) {
|
|
182
|
+
const directories = [];
|
|
183
|
+
const files = [];
|
|
184
|
+
for (const mount of mounts) {
|
|
185
|
+
let isDirectory;
|
|
186
|
+
try {
|
|
187
|
+
isDirectory = statSync(mount.source).isDirectory();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
log.logWarning(`Skipping missing Gondolin mount source ${mount.source}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (isDirectory) {
|
|
194
|
+
directories.push(mount);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
files.push({
|
|
198
|
+
...mount,
|
|
199
|
+
syncBack: mount.target.startsWith("/workspace/"),
|
|
200
|
+
lastHash: "",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { directories, files };
|
|
205
|
+
}
|
|
206
|
+
/** Copy file mounts into the guest; credentials get user-only permissions. */
|
|
207
|
+
async function projectFiles(vm, files) {
|
|
208
|
+
for (const projection of files) {
|
|
209
|
+
const content = readFileSync(projection.source);
|
|
210
|
+
await vm.fs.mkdir(dirname(projection.target), { recursive: true });
|
|
211
|
+
await vm.fs.writeFile(projection.target, content);
|
|
212
|
+
if (!projection.syncBack) {
|
|
213
|
+
// credential projection: no write-back, keep it owner-only
|
|
214
|
+
await vm.exec(`chmod 600 ${shellEscape(projection.target)}`);
|
|
215
|
+
}
|
|
216
|
+
projection.lastHash = sha256(content);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function restrictSocketAccess(socketPath) {
|
|
220
|
+
// The session socket grants arbitrary exec inside the VM (and through its
|
|
221
|
+
// mounts, the workspace) — keep it to this user even under a lax umask.
|
|
222
|
+
try {
|
|
223
|
+
chmodSync(dirname(socketPath), 0o700);
|
|
224
|
+
chmodSync(socketPath, 0o600);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// best-effort hardening
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function projectionLockIsStale(lockPath) {
|
|
231
|
+
let ownerKnown = false;
|
|
232
|
+
let ownerAlive = false;
|
|
233
|
+
try {
|
|
234
|
+
const pid = Number(readFileSync(join(lockPath, "owner"), "utf8").trim());
|
|
235
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
236
|
+
ownerKnown = true;
|
|
237
|
+
try {
|
|
238
|
+
process.kill(pid, 0);
|
|
239
|
+
ownerAlive = true;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
const code = error.code;
|
|
243
|
+
if (code === "EPERM")
|
|
244
|
+
ownerAlive = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// An owner file may not have been written before its process died.
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const oldEnough = Date.now() - statSync(lockPath).mtimeMs >= PROJECTION_LOCK_STALE_MS;
|
|
253
|
+
return (ownerKnown && !ownerAlive) || oldEnough;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function withProjectionLock(source, operation) {
|
|
260
|
+
const lockPath = `${source}.mikan-sync.lock`;
|
|
261
|
+
for (;;) {
|
|
262
|
+
try {
|
|
263
|
+
mkdirSync(lockPath, { mode: 0o700 });
|
|
264
|
+
writeFileSync(join(lockPath, "owner"), `${process.pid}\n`, { mode: 0o600 });
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (error.code !== "EEXIST")
|
|
269
|
+
throw error;
|
|
270
|
+
if (projectionLockIsStale(lockPath)) {
|
|
271
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, PROJECTION_LOCK_RETRY_MS));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
return await operation();
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** One live microVM owned by this process, addressed by its session socket. */
|
|
285
|
+
class GondolinRuntime {
|
|
286
|
+
constructor(vm, socketPath, projections) {
|
|
287
|
+
this.vm = vm;
|
|
288
|
+
this.socketPath = socketPath;
|
|
289
|
+
this.projections = projections;
|
|
290
|
+
this.syncing = false;
|
|
291
|
+
}
|
|
292
|
+
/** Boot a VM for the desired configuration and make it command-ready. */
|
|
293
|
+
static async create(instanceId, desired) {
|
|
294
|
+
const { VM, RealFSProvider, ReadonlyProvider, findSession } = (await import("@earendil-works/gondolin"));
|
|
295
|
+
const { directories, files } = partitionMounts(desired.mounts);
|
|
296
|
+
const vm = await VM.create({
|
|
297
|
+
sandbox: { imagePath: desired.image },
|
|
298
|
+
env: { TZ: "Asia/Taipei" },
|
|
299
|
+
sessionLabel: `mikan:${instanceId}`,
|
|
300
|
+
cpus: gondolinCpuCount(desired.limits?.cpus),
|
|
301
|
+
memory: desired.limits?.memory,
|
|
302
|
+
vfs: {
|
|
303
|
+
mounts: Object.fromEntries(directories.map(({ source, target, readOnly }) => [
|
|
304
|
+
target,
|
|
305
|
+
// ReadonlyProvider blocks every write operation on the backing
|
|
306
|
+
// provider — gondolin's own answer to read-only host directories.
|
|
307
|
+
readOnly
|
|
308
|
+
? new ReadonlyProvider(new RealFSProvider(source))
|
|
309
|
+
: new RealFSProvider(source),
|
|
310
|
+
])),
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
try {
|
|
314
|
+
await vm.start();
|
|
315
|
+
await projectFiles(vm, files);
|
|
316
|
+
const session = await findSession(vm.id);
|
|
317
|
+
if (!session)
|
|
318
|
+
throw new Error(`Gondolin session '${vm.id}' did not register`);
|
|
319
|
+
restrictSocketAccess(session.socketPath);
|
|
320
|
+
const runtime = new GondolinRuntime(vm, session.socketPath, files);
|
|
321
|
+
runtime.startProjectionSync();
|
|
322
|
+
return runtime;
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
// never leave a half-booted runner (and its overlay disk) behind
|
|
326
|
+
await vm.close().catch(() => { });
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Run one command over a dedicated session IPC connection. Aborting (or the
|
|
332
|
+
* caller's timeout signal firing) destroys the connection, which kills the
|
|
333
|
+
* in-flight guest process.
|
|
334
|
+
*/
|
|
335
|
+
async exec(command, options = {}) {
|
|
336
|
+
return execOverSessionConnect(async (callbacks) => {
|
|
337
|
+
const { connectToSession } = await import("@earendil-works/gondolin");
|
|
338
|
+
return connectToSession(this.socketPath, callbacks);
|
|
339
|
+
}, command, options);
|
|
340
|
+
}
|
|
341
|
+
/** Whether the VM runner process is still up. */
|
|
342
|
+
isAlive() {
|
|
343
|
+
try {
|
|
344
|
+
return this.vm.getHostPid() !== null;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Sync projected files back one last time, then shut the VM down. */
|
|
351
|
+
async close() {
|
|
352
|
+
this.closing ??= (async () => {
|
|
353
|
+
clearInterval(this.syncTimer);
|
|
354
|
+
try {
|
|
355
|
+
await this.syncProjections();
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// best effort; the periodic sync already captured earlier edits
|
|
359
|
+
}
|
|
360
|
+
await this.vm.close();
|
|
361
|
+
})();
|
|
362
|
+
return this.closing;
|
|
363
|
+
}
|
|
364
|
+
startProjectionSync() {
|
|
365
|
+
if (!this.projections.some((projection) => projection.syncBack))
|
|
366
|
+
return;
|
|
367
|
+
this.syncTimer = setInterval(() => void this.syncProjections(), PROJECTION_SYNC_INTERVAL_MS);
|
|
368
|
+
this.syncTimer.unref?.();
|
|
369
|
+
}
|
|
370
|
+
async syncProjections() {
|
|
371
|
+
if (this.syncing)
|
|
372
|
+
return;
|
|
373
|
+
this.syncing = true;
|
|
374
|
+
try {
|
|
375
|
+
for (const projection of this.projections) {
|
|
376
|
+
if (!projection.syncBack)
|
|
377
|
+
continue;
|
|
378
|
+
try {
|
|
379
|
+
const data = await this.vm.fs.readFile(projection.target);
|
|
380
|
+
const content = Buffer.isBuffer(data) ? data : Buffer.from(String(data));
|
|
381
|
+
const hash = sha256(content);
|
|
382
|
+
if (hash === projection.lastHash)
|
|
383
|
+
continue;
|
|
384
|
+
await withProjectionLock(projection.source, async () => {
|
|
385
|
+
let hostHash;
|
|
386
|
+
try {
|
|
387
|
+
hostHash = sha256(readFileSync(projection.source));
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error.code !== "ENOENT")
|
|
391
|
+
throw error;
|
|
392
|
+
hostHash = "";
|
|
393
|
+
}
|
|
394
|
+
if (hostHash !== projection.lastHash) {
|
|
395
|
+
const conflict = `${projection.source}.mikan-conflict.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
396
|
+
writeFileSync(conflict, content);
|
|
397
|
+
projection.lastHash = hash;
|
|
398
|
+
log.logWarning(`Skipped Gondolin projection write-back for '${projection.source}': host content changed; guest content preserved at '${conflict}'`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
// atomic host write: a crash mid-sync must not truncate the file
|
|
402
|
+
const staged = `${projection.source}.mikan-sync.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
403
|
+
try {
|
|
404
|
+
writeFileSync(staged, content);
|
|
405
|
+
renameSync(staged, projection.source);
|
|
406
|
+
projection.lastHash = hash;
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
try {
|
|
410
|
+
unlinkSync(staged);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// rename succeeded or the staging write failed
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// guest file missing or read raced a writer; next tick retries
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
this.syncing = false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function abortError() {
|
|
429
|
+
return new Error("Error: command aborted");
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Run one command through a fresh session connection. Closing the connection
|
|
433
|
+
* (abort, timeout, or the far side dying) kills the in-flight guest process.
|
|
434
|
+
*/
|
|
435
|
+
export async function execOverSessionConnect(connect, command, options = {}) {
|
|
436
|
+
if (options.signal?.aborted)
|
|
437
|
+
throw abortError();
|
|
438
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? EXEC_IDLE_TIMEOUT_MS;
|
|
439
|
+
return await new Promise((resolve, reject) => {
|
|
440
|
+
let stdout = "";
|
|
441
|
+
let stderr = "";
|
|
442
|
+
let received = false;
|
|
443
|
+
let settled = false;
|
|
444
|
+
let client;
|
|
445
|
+
let idleTimer;
|
|
446
|
+
const settle = (fn) => {
|
|
447
|
+
if (settled)
|
|
448
|
+
return;
|
|
449
|
+
settled = true;
|
|
450
|
+
clearTimeout(idleTimer);
|
|
451
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
452
|
+
fn();
|
|
453
|
+
};
|
|
454
|
+
const onAbort = () => settle(() => {
|
|
455
|
+
client?.close();
|
|
456
|
+
reject(abortError());
|
|
457
|
+
});
|
|
458
|
+
const onIdle = () => settle(() => {
|
|
459
|
+
client?.close();
|
|
460
|
+
reject(new GondolinRuntimeInterruptedError(`no session activity for ${Math.round(idleTimeoutMs / 1000)}s — killed the command ` +
|
|
461
|
+
`(guest I/O hangs when the workspace sits on a dead mount; check the workspace health)`));
|
|
462
|
+
});
|
|
463
|
+
const armIdleTimer = () => {
|
|
464
|
+
if (idleTimeoutMs <= 0 || settled)
|
|
465
|
+
return;
|
|
466
|
+
clearTimeout(idleTimer);
|
|
467
|
+
idleTimer = setTimeout(onIdle, idleTimeoutMs);
|
|
468
|
+
idleTimer.unref?.();
|
|
469
|
+
};
|
|
470
|
+
armIdleTimer();
|
|
471
|
+
const callbacks = {
|
|
472
|
+
onJson: (message) => {
|
|
473
|
+
received = true;
|
|
474
|
+
armIdleTimer();
|
|
475
|
+
if (message.type === "exec_response" && message.id === 1) {
|
|
476
|
+
settle(() => {
|
|
477
|
+
client?.close();
|
|
478
|
+
resolve({
|
|
479
|
+
stdout,
|
|
480
|
+
stderr,
|
|
481
|
+
code: typeof message.exit_code === "number" ? message.exit_code : 1,
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else if (message.type === "error") {
|
|
486
|
+
settle(() => {
|
|
487
|
+
client?.close();
|
|
488
|
+
reject(new Error(`Error: Gondolin exec failed (${message.code}): ${message.message}`));
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
onBinary: (frame) => {
|
|
493
|
+
received = true;
|
|
494
|
+
armIdleTimer();
|
|
495
|
+
const tag = frame.readUInt8(0);
|
|
496
|
+
const data = frame.subarray(5).toString("utf8");
|
|
497
|
+
if (tag === 1)
|
|
498
|
+
stdout += data;
|
|
499
|
+
else
|
|
500
|
+
stderr += data;
|
|
501
|
+
},
|
|
502
|
+
onClose: (error) => {
|
|
503
|
+
settle(() => {
|
|
504
|
+
const detail = error?.message ?? "connection closed before the command finished";
|
|
505
|
+
const code = error?.code;
|
|
506
|
+
if (!received && (code === "ENOENT" || code === "ECONNREFUSED")) {
|
|
507
|
+
reject(new GondolinRuntimeGoneError(detail));
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
reject(new GondolinRuntimeInterruptedError(detail));
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
516
|
+
Promise.resolve(connect(callbacks))
|
|
517
|
+
.then((created) => {
|
|
518
|
+
client = created;
|
|
519
|
+
if (settled) {
|
|
520
|
+
created.close();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
created.send({
|
|
524
|
+
type: "exec",
|
|
525
|
+
id: 1,
|
|
526
|
+
cmd: "/bin/sh",
|
|
527
|
+
argv: ["-c", command],
|
|
528
|
+
env: Object.entries(options.env ?? {}).map(([key, value]) => `${key}=${value}`),
|
|
529
|
+
cwd: "/workspace",
|
|
530
|
+
});
|
|
531
|
+
})
|
|
532
|
+
.catch((err) => settle(() => reject(new GondolinRuntimeGoneError(err instanceof Error ? err.message : String(err)))));
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function createSession(key, config, desired, fingerprint) {
|
|
536
|
+
let session;
|
|
537
|
+
const runtime = GondolinRuntime.create(key, desired).catch((error) => {
|
|
538
|
+
if (sessions.get(key) === session)
|
|
539
|
+
sessions.delete(key);
|
|
540
|
+
throw error;
|
|
541
|
+
});
|
|
542
|
+
session = {
|
|
543
|
+
runtime,
|
|
544
|
+
fingerprint,
|
|
545
|
+
resourceKey: config.resourceKey,
|
|
546
|
+
activeOperations: 0,
|
|
547
|
+
lastUsed: Date.now(),
|
|
548
|
+
idleWaiters: [],
|
|
549
|
+
};
|
|
550
|
+
return session;
|
|
551
|
+
}
|
|
552
|
+
async function acquireSession(key, config, generation = shutdownGeneration) {
|
|
553
|
+
if (activeShutdowns > 0 || generation !== shutdownGeneration) {
|
|
554
|
+
throw new SandboxError("Error: Gondolin runtime is shutting down");
|
|
555
|
+
}
|
|
556
|
+
const pending = transitions.get(key);
|
|
557
|
+
if (pending) {
|
|
558
|
+
await pending;
|
|
559
|
+
return acquireSession(key, config, generation);
|
|
560
|
+
}
|
|
561
|
+
const desired = await resolveDesiredRuntime(config);
|
|
562
|
+
if (activeShutdowns > 0 || generation !== shutdownGeneration) {
|
|
563
|
+
throw new SandboxError("Error: Gondolin runtime is shutting down");
|
|
564
|
+
}
|
|
565
|
+
const concurrentTransition = transitions.get(key);
|
|
566
|
+
if (concurrentTransition) {
|
|
567
|
+
await concurrentTransition;
|
|
568
|
+
return acquireSession(key, config, generation);
|
|
569
|
+
}
|
|
570
|
+
const fingerprint = runtimeFingerprint(desired);
|
|
571
|
+
const existing = sessions.get(key);
|
|
572
|
+
if (existing?.fingerprint === fingerprint) {
|
|
573
|
+
existing.activeOperations += 1;
|
|
574
|
+
return existing;
|
|
575
|
+
}
|
|
576
|
+
const transition = replaceSession(key, config, desired, fingerprint, existing);
|
|
577
|
+
transitions.set(key, transition);
|
|
578
|
+
try {
|
|
579
|
+
await transition;
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
if (transitions.get(key) === transition)
|
|
583
|
+
transitions.delete(key);
|
|
584
|
+
}
|
|
585
|
+
return acquireSession(key, config, generation);
|
|
586
|
+
}
|
|
587
|
+
async function replaceSession(key, config, desired, fingerprint, existing) {
|
|
588
|
+
if (existing) {
|
|
589
|
+
await closeSession(key, existing, {
|
|
590
|
+
waitForActiveOperations: true,
|
|
591
|
+
resetResources: false,
|
|
592
|
+
throwOnError: true,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const replacement = createSession(key, config, desired, fingerprint);
|
|
596
|
+
sessions.set(key, replacement);
|
|
597
|
+
await replacement.runtime;
|
|
598
|
+
}
|
|
599
|
+
function discardDeadSession(key, session) {
|
|
600
|
+
if (sessions.get(key) !== session)
|
|
601
|
+
return;
|
|
602
|
+
sessions.delete(key);
|
|
603
|
+
log.logWarning(`Gondolin runtime for '${key}' died; the session will be recreated`);
|
|
604
|
+
}
|
|
605
|
+
async function withRuntime(key, config, operation) {
|
|
606
|
+
for (let attempt = 0;; attempt += 1) {
|
|
607
|
+
const session = await acquireSession(key, config);
|
|
608
|
+
let runtime;
|
|
609
|
+
try {
|
|
610
|
+
runtime = await session.runtime;
|
|
611
|
+
return await operation(runtime);
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
const gone = error instanceof GondolinRuntimeGoneError;
|
|
615
|
+
const interrupted = error instanceof GondolinRuntimeInterruptedError;
|
|
616
|
+
if (gone || (interrupted && runtime && !runtime.isAlive())) {
|
|
617
|
+
discardDeadSession(key, session);
|
|
618
|
+
}
|
|
619
|
+
// Nothing reached a gone runtime, so recreating and retrying is safe;
|
|
620
|
+
// an interrupted command may have side effects and must surface.
|
|
621
|
+
if (gone && attempt === 0)
|
|
622
|
+
continue;
|
|
623
|
+
if (gone || interrupted) {
|
|
624
|
+
throw new SandboxError(`Error: Gondolin runtime for '${key}' died (${error.message}); it is recreated on the next command`);
|
|
625
|
+
}
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
finally {
|
|
629
|
+
session.activeOperations -= 1;
|
|
630
|
+
session.lastUsed = Date.now();
|
|
631
|
+
if (session.activeOperations === 0) {
|
|
632
|
+
for (const resolve of session.idleWaiters.splice(0))
|
|
633
|
+
resolve();
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function waitForIdle(session) {
|
|
639
|
+
if (session.activeOperations === 0)
|
|
640
|
+
return Promise.resolve();
|
|
641
|
+
return new Promise((resolve) => session.idleWaiters.push(resolve));
|
|
642
|
+
}
|
|
643
|
+
async function closeSession(key, session, options = {}) {
|
|
644
|
+
if (sessions.get(key) !== session)
|
|
645
|
+
return;
|
|
646
|
+
sessions.delete(key);
|
|
647
|
+
try {
|
|
648
|
+
if (options.waitForActiveOperations)
|
|
649
|
+
await waitForIdle(session);
|
|
650
|
+
const runtime = await session.runtime;
|
|
651
|
+
await runtime.close();
|
|
652
|
+
if (options.resetResources !== false &&
|
|
653
|
+
session.resourceKey &&
|
|
654
|
+
!Array.from(sessions.values()).some(({ resourceKey }) => resourceKey === session.resourceKey)) {
|
|
655
|
+
gondolinResources.clear(session.resourceKey);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
session.fingerprint = "";
|
|
660
|
+
// Re-track for a retried close, unless a replacement already claimed the
|
|
661
|
+
// key — then the failed runtime stays for the reconcile paths and
|
|
662
|
+
// clobbering the replacement would leak it too.
|
|
663
|
+
if (!sessions.has(key))
|
|
664
|
+
sessions.set(key, session);
|
|
665
|
+
if (options.throwOnError)
|
|
666
|
+
throw error;
|
|
667
|
+
log.logWarning(`Failed to close Gondolin session '${key}'`, error instanceof Error ? error.message : String(error));
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
export async function stopIdleGondolinVms(maxIdleMs, now = Date.now()) {
|
|
671
|
+
const idle = Array.from(sessions.entries()).filter(([key, session]) => !transitions.has(key) &&
|
|
672
|
+
session.activeOperations === 0 &&
|
|
673
|
+
now - session.lastUsed >= maxIdleMs);
|
|
674
|
+
await Promise.all(idle.map(([key, session]) => {
|
|
675
|
+
// Register the close as a transition so a concurrent acquire waits for
|
|
676
|
+
// it instead of racing the shared session slot.
|
|
677
|
+
const transition = closeSession(key, session).finally(() => {
|
|
678
|
+
if (transitions.get(key) === transition)
|
|
679
|
+
transitions.delete(key);
|
|
680
|
+
});
|
|
681
|
+
transitions.set(key, transition);
|
|
682
|
+
return transition;
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Drop Gondolin's registry entries for sessions that no longer answer — socket
|
|
687
|
+
* files and metadata left behind by a mikan that died without cleanup.
|
|
688
|
+
*/
|
|
689
|
+
export async function reconcileGondolinRuntimes() {
|
|
690
|
+
try {
|
|
691
|
+
const { gcSessions } = (await import("@earendil-works/gondolin"));
|
|
692
|
+
const collected = await gcSessions();
|
|
693
|
+
log.logInfo(`Reconciled Gondolin sessions (collected=${collected})`);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
log.logWarning("Failed to collect Gondolin session registry", error instanceof Error ? error.message : String(error));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Stop every runtime this process owns. Runtimes do not outlive mikan, so
|
|
701
|
+
* shutdown must close them: that syncs projected files back to the host and
|
|
702
|
+
* releases each VM's overlay disk before the process exits.
|
|
703
|
+
*/
|
|
704
|
+
export async function stopAllGondolinRuntimes() {
|
|
705
|
+
shutdownGeneration += 1;
|
|
706
|
+
activeShutdowns += 1;
|
|
707
|
+
try {
|
|
708
|
+
await Promise.allSettled(transitions.values());
|
|
709
|
+
const current = Array.from(sessions.entries());
|
|
710
|
+
await Promise.all(current.map(([key, session]) => closeSession(key, session, { waitForActiveOperations: true })));
|
|
711
|
+
}
|
|
712
|
+
finally {
|
|
713
|
+
activeShutdowns -= 1;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function executionSignal(options) {
|
|
717
|
+
const signals = [options?.signal];
|
|
718
|
+
if (options?.timeout && options.timeout > 0) {
|
|
719
|
+
signals.push(AbortSignal.timeout(options.timeout * 1000));
|
|
720
|
+
}
|
|
721
|
+
const active = signals.filter((signal) => signal !== undefined);
|
|
722
|
+
return active.length > 0 ? AbortSignal.any(active) : undefined;
|
|
723
|
+
}
|
|
724
|
+
export class GondolinExecutor {
|
|
725
|
+
constructor(config, env) {
|
|
726
|
+
this.config = config;
|
|
727
|
+
this.env = env;
|
|
728
|
+
assertSupportedNodeVersion();
|
|
729
|
+
this.workspacePath = config.workspacePath ?? process.cwd();
|
|
730
|
+
this.instanceId = config.instanceId ?? this.workspacePath;
|
|
731
|
+
}
|
|
732
|
+
async exec(command, options) {
|
|
733
|
+
return withRuntime(this.instanceId, this.config, (runtime) => runtime.exec(withRuntimeBootstrap(command, this.env), {
|
|
734
|
+
env: this.env,
|
|
735
|
+
signal: executionSignal(options),
|
|
736
|
+
}));
|
|
737
|
+
}
|
|
738
|
+
async readFile(path) {
|
|
739
|
+
return execReadFile(this, path);
|
|
740
|
+
}
|
|
741
|
+
async readFileBase64(path) {
|
|
742
|
+
return execReadFileBase64(this, path);
|
|
743
|
+
}
|
|
744
|
+
async writeFile(path, content) {
|
|
745
|
+
return execWriteFile(this, path, content);
|
|
746
|
+
}
|
|
747
|
+
getWorkspacePath() {
|
|
748
|
+
return "/workspace";
|
|
749
|
+
}
|
|
750
|
+
getPathContext(hostWorkspaceRoot) {
|
|
751
|
+
return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
|
|
752
|
+
}
|
|
753
|
+
getSandboxConfig() {
|
|
754
|
+
return this.config;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
export const gondolinSandboxAdapter = {
|
|
758
|
+
type: "gondolin",
|
|
759
|
+
credentials: { env: true, fileMounts: true },
|
|
760
|
+
parse: parseGondolinSandboxArg,
|
|
761
|
+
validate: validateGondolinSandbox,
|
|
762
|
+
createExecutor: (config, env) => new GondolinExecutor(config, env),
|
|
763
|
+
};
|
|
764
|
+
//# sourceMappingURL=gondolin.js.map
|