@geminixiang/mikan 1.0.0-beta.44 → 1.0.0-beta.46
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 +32 -0
- package/README.md +12 -19
- 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 +3 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +143 -141
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +2 -8
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +1 -1
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/format.d.ts.map +1 -1
- package/dist/adapters/discord/format.js +2 -4
- package/dist/adapters/discord/format.js.map +1 -1
- package/dist/adapters/github/bot.d.ts.map +1 -1
- package/dist/adapters/github/bot.js +1 -4
- package/dist/adapters/github/bot.js.map +1 -1
- package/dist/adapters/github/context.d.ts +2 -9
- package/dist/adapters/github/context.d.ts.map +1 -1
- package/dist/adapters/github/context.js.map +1 -1
- package/dist/adapters/github/github-ops.d.ts +4 -17
- package/dist/adapters/github/github-ops.d.ts.map +1 -1
- package/dist/adapters/github/github-ops.js +17 -86
- package/dist/adapters/github/github-ops.js.map +1 -1
- package/dist/adapters/github/tool-pack.d.ts.map +1 -1
- package/dist/adapters/github/tool-pack.js +0 -1
- package/dist/adapters/github/tool-pack.js.map +1 -1
- package/dist/adapters/github/tools/checks.d.ts +0 -1
- package/dist/adapters/github/tools/checks.d.ts.map +1 -1
- package/dist/adapters/github/tools/checks.js +3 -22
- package/dist/adapters/github/tools/checks.js.map +1 -1
- package/dist/adapters/github/types.d.ts +0 -33
- package/dist/adapters/github/types.d.ts.map +1 -1
- package/dist/adapters/github/types.js.map +1 -1
- package/dist/adapters/progressive-renderer.d.ts.map +1 -1
- package/dist/adapters/progressive-renderer.js +311 -318
- package/dist/adapters/progressive-renderer.js.map +1 -1
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +1 -4
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +12 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +158 -178
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +2 -8
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +1 -1
- 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 +135 -123
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/stream-limits.d.ts +1 -1
- package/dist/adapters/slack/stream-limits.js +1 -1
- package/dist/adapters/slack/stream-limits.js.map +1 -1
- package/dist/adapters/slack/types.d.ts +4 -0
- package/dist/adapters/slack/types.d.ts.map +1 -1
- package/dist/adapters/slack/types.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +2 -8
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +1 -2
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/types.d.ts +11 -5
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent/catalog.d.ts +6 -18
- package/dist/agent/catalog.d.ts.map +1 -1
- package/dist/agent/catalog.js +19 -155
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/execution.d.ts +0 -7
- package/dist/agent/execution.d.ts.map +1 -1
- package/dist/agent/execution.js +23 -21
- package/dist/agent/execution.js.map +1 -1
- package/dist/agent/presenter.d.ts +7 -9
- package/dist/agent/presenter.d.ts.map +1 -1
- package/dist/agent/presenter.js +355 -357
- package/dist/agent/presenter.js.map +1 -1
- package/dist/agent/prompt.d.ts +5 -12
- package/dist/agent/prompt.d.ts.map +1 -1
- package/dist/agent/prompt.js +56 -81
- package/dist/agent/prompt.js.map +1 -1
- package/dist/agent/runner.d.ts +0 -7
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +255 -308
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/types.d.ts +21 -17
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js +0 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/boot.d.ts.map +1 -1
- package/dist/cli/boot.js +7 -10
- package/dist/cli/boot.js.map +1 -1
- package/dist/cli/onboard.d.ts +10 -0
- package/dist/cli/onboard.d.ts.map +1 -0
- package/dist/cli/onboard.js +237 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/types.d.ts +8 -3
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +5 -12
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/manifest.d.ts.map +1 -1
- package/dist/commands/manifest.js +9 -16
- package/dist/commands/manifest.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +15 -10
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +0 -2
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +89 -70
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/session-view.js +9 -2
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +4 -3
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +13 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +39 -37
- package/dist/config.js.map +1 -1
- package/dist/dream/index.d.ts +20 -0
- package/dist/dream/index.d.ts.map +1 -0
- package/dist/dream/index.js +253 -0
- package/dist/dream/index.js.map +1 -0
- package/dist/dream/types.d.ts +30 -0
- package/dist/dream/types.d.ts.map +1 -0
- package/dist/dream/types.js.map +1 -0
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +2 -34
- package/dist/env-manifest.js.map +1 -1
- package/dist/execution-resolver.d.ts +9 -2
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +26 -27
- package/dist/execution-resolver.js.map +1 -1
- package/dist/harness/event-format.d.ts +1 -2
- package/dist/harness/event-format.d.ts.map +1 -1
- package/dist/harness/event-format.js +9 -15
- package/dist/harness/event-format.js.map +1 -1
- package/dist/harness/index.d.ts +1 -7
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +0 -4
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/models.d.ts.map +1 -1
- package/dist/harness/models.js +15 -35
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/runner.d.ts +7 -16
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +13 -131
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session-store.d.ts +1 -1
- package/dist/harness/session-store.d.ts.map +1 -1
- package/dist/harness/session-store.js +1 -1
- package/dist/harness/session-store.js.map +1 -1
- package/dist/harness/subagent-runner.d.ts +2 -2
- package/dist/harness/subagent-runner.d.ts.map +1 -1
- package/dist/harness/subagent-runner.js +157 -162
- package/dist/harness/subagent-runner.js.map +1 -1
- package/dist/harness/types.d.ts +1 -13
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +3 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/main.js +31 -212
- package/dist/main.js.map +1 -1
- package/dist/mcp/catalog.d.ts +5 -0
- package/dist/mcp/catalog.d.ts.map +1 -0
- package/dist/mcp/catalog.js +135 -0
- package/dist/mcp/catalog.js.map +1 -0
- package/dist/mcp/loader.d.ts.map +1 -1
- package/dist/mcp/loader.js +3 -6
- package/dist/mcp/loader.js.map +1 -1
- package/dist/mcp/types.d.ts +22 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/office/index.d.ts +136 -8
- package/dist/office/index.d.ts.map +1 -1
- package/dist/office/index.js +1050 -9
- package/dist/office/index.js.map +1 -1
- package/dist/office/types.d.ts +19 -1
- package/dist/office/types.d.ts.map +1 -1
- package/dist/office/types.js.map +1 -1
- package/dist/packages/index.d.ts +168 -5
- package/dist/packages/index.d.ts.map +1 -1
- package/dist/packages/index.js +749 -5
- package/dist/packages/index.js.map +1 -1
- package/dist/packages/types.d.ts +1 -5
- package/dist/packages/types.d.ts.map +1 -1
- package/dist/packages/types.js.map +1 -1
- package/dist/provisioner.d.ts +2 -9
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +3 -12
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-runtime.d.ts.map +1 -1
- package/dist/runtime/conversation-runtime.js +117 -380
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +32 -18
- package/dist/runtime/session-lifecycle.d.ts.map +1 -1
- package/dist/runtime/session-lifecycle.js +159 -46
- package/dist/runtime/session-lifecycle.js.map +1 -1
- package/dist/runtime/types.d.ts +4 -26
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +2 -3
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts +0 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +2 -3
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +4 -2
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +10 -19
- package/dist/sandbox/identity.d.ts.map +1 -1
- package/dist/sandbox/identity.js +8 -8
- package/dist/sandbox/identity.js.map +1 -1
- package/dist/sandbox/index.d.ts +7 -10
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +45 -16
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.ts +13 -45
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +5 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +10 -0
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts +1 -3
- package/dist/sessions/chat-history-sync.d.ts.map +1 -1
- package/dist/sessions/chat-history-sync.js +58 -11
- package/dist/sessions/chat-history-sync.js.map +1 -1
- package/dist/sessions/migrate-v3.d.ts +2 -5
- package/dist/sessions/migrate-v3.d.ts.map +1 -1
- package/dist/sessions/migrate-v3.js +34 -4
- package/dist/sessions/migrate-v3.js.map +1 -1
- package/dist/sessions/store.d.ts +5 -6
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +62 -2
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +9 -0
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +2 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/read.js +2 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +2 -3
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/subagent.d.ts +1 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/types.d.ts +38 -127
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/index.d.ts +9 -2
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +29 -5
- package/dist/vault/index.js.map +1 -1
- package/dist/web/admin/portal.d.ts +12 -1
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +437 -422
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/types.d.ts +2 -2
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/login/portal.d.ts +7 -1
- package/dist/web/login/portal.d.ts.map +1 -1
- package/dist/web/login/portal.js +143 -78
- package/dist/web/login/portal.js.map +1 -1
- package/dist/web/portal-shell.d.ts +1 -0
- package/dist/web/portal-shell.d.ts.map +1 -1
- package/dist/web/portal-shell.js +12 -1
- package/dist/web/portal-shell.js.map +1 -1
- package/dist/web/server.d.ts +3 -3
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-view/portal.d.ts +8 -1
- package/dist/web/session-view/portal.d.ts.map +1 -1
- package/dist/web/session-view/portal.js +609 -132
- package/dist/web/session-view/portal.js.map +1 -1
- package/dist/web/session-view/types.d.ts +8 -0
- 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 +14 -2
- package/dist/workspace-projection/index.d.ts.map +1 -1
- package/dist/workspace-projection/index.js +102 -14
- package/dist/workspace-projection/index.js.map +1 -1
- package/dist/workspace-projection/types.d.ts +14 -1
- package/dist/workspace-projection/types.d.ts.map +1 -1
- package/dist/workspace-projection/types.js.map +1 -1
- package/package.json +3 -5
- package/dist/adapters/github/cloudbuild.d.ts +0 -19
- package/dist/adapters/github/cloudbuild.d.ts.map +0 -1
- package/dist/adapters/github/cloudbuild.js +0 -71
- package/dist/adapters/github/cloudbuild.js.map +0 -1
- package/dist/adapters/github/gcp-auth.d.ts +0 -28
- package/dist/adapters/github/gcp-auth.d.ts.map +0 -1
- package/dist/adapters/github/gcp-auth.js +0 -196
- package/dist/adapters/github/gcp-auth.js.map +0 -1
- package/dist/agent/index.d.ts +0 -9
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/index.js +0 -8
- package/dist/agent/index.js.map +0 -1
- package/dist/cli/ext-dev.d.ts +0 -2
- package/dist/cli/ext-dev.d.ts.map +0 -1
- package/dist/cli/ext-dev.js +0 -188
- package/dist/cli/ext-dev.js.map +0 -1
- package/dist/cli/ext.d.ts +0 -2
- package/dist/cli/ext.d.ts.map +0 -1
- package/dist/cli/ext.js +0 -324
- package/dist/cli/ext.js.map +0 -1
- package/dist/commands/extensions.d.ts +0 -26
- package/dist/commands/extensions.d.ts.map +0 -1
- package/dist/commands/extensions.js +0 -103
- package/dist/commands/extensions.js.map +0 -1
- package/dist/extension-schedules.d.ts +0 -31
- package/dist/extension-schedules.d.ts.map +0 -1
- package/dist/extension-schedules.js +0 -229
- package/dist/extension-schedules.js.map +0 -1
- package/dist/harness/auth.d.ts +0 -17
- package/dist/harness/auth.d.ts.map +0 -1
- package/dist/harness/auth.js +0 -92
- package/dist/harness/auth.js.map +0 -1
- package/dist/harness/extensions/loader.d.ts +0 -40
- package/dist/harness/extensions/loader.d.ts.map +0 -1
- package/dist/harness/extensions/loader.js +0 -723
- package/dist/harness/extensions/loader.js.map +0 -1
- package/dist/harness/extensions/registry.d.ts +0 -155
- package/dist/harness/extensions/registry.d.ts.map +0 -1
- package/dist/harness/extensions/registry.js +0 -420
- package/dist/harness/extensions/registry.js.map +0 -1
- package/dist/harness/extensions/types.d.ts +0 -665
- package/dist/harness/extensions/types.d.ts.map +0 -1
- package/dist/harness/extensions/types.js.map +0 -1
- package/dist/office/address.d.ts +0 -31
- package/dist/office/address.d.ts.map +0 -1
- package/dist/office/address.js +0 -113
- package/dist/office/address.js.map +0 -1
- package/dist/office/layout.d.ts +0 -27
- package/dist/office/layout.d.ts.map +0 -1
- package/dist/office/layout.js +0 -97
- package/dist/office/layout.js.map +0 -1
- package/dist/office/migration.d.ts +0 -44
- package/dist/office/migration.d.ts.map +0 -1
- package/dist/office/migration.js +0 -255
- package/dist/office/migration.js.map +0 -1
- package/dist/office/registry.d.ts +0 -75
- package/dist/office/registry.d.ts.map +0 -1
- package/dist/office/registry.js +0 -646
- package/dist/office/registry.js.map +0 -1
- package/dist/packages/admin.d.ts +0 -17
- package/dist/packages/admin.d.ts.map +0 -1
- package/dist/packages/admin.js +0 -98
- package/dist/packages/admin.js.map +0 -1
- package/dist/packages/inspect.d.ts +0 -11
- package/dist/packages/inspect.d.ts.map +0 -1
- package/dist/packages/inspect.js +0 -98
- package/dist/packages/inspect.js.map +0 -1
- package/dist/packages/materialize.d.ts +0 -40
- package/dist/packages/materialize.d.ts.map +0 -1
- package/dist/packages/materialize.js +0 -297
- package/dist/packages/materialize.js.map +0 -1
- package/dist/packages/resolve.d.ts +0 -25
- package/dist/packages/resolve.d.ts.map +0 -1
- package/dist/packages/resolve.js +0 -208
- package/dist/packages/resolve.js.map +0 -1
- package/dist/packages/source.d.ts +0 -39
- package/dist/packages/source.d.ts.map +0 -1
- package/dist/packages/source.js +0 -189
- package/dist/packages/source.js.map +0 -1
- package/dist/sandbox/errors.d.ts +0 -6
- package/dist/sandbox/errors.d.ts.map +0 -1
- package/dist/sandbox/errors.js +0 -11
- package/dist/sandbox/errors.js.map +0 -1
- package/dist/sandbox/firecracker.d.ts +0 -18
- package/dist/sandbox/firecracker.d.ts.map +0 -1
- package/dist/sandbox/firecracker.js +0 -223
- package/dist/sandbox/firecracker.js.map +0 -1
- package/dist/sandbox/gondolin.d.ts +0 -57
- package/dist/sandbox/gondolin.d.ts.map +0 -1
- package/dist/sandbox/gondolin.js +0 -764
- package/dist/sandbox/gondolin.js.map +0 -1
- package/dist/sandbox/image.d.ts +0 -3
- package/dist/sandbox/image.d.ts.map +0 -1
- package/dist/sandbox/image.js +0 -29
- package/dist/sandbox/image.js.map +0 -1
- package/dist/sessions/conversation-log.d.ts +0 -9
- package/dist/sessions/conversation-log.d.ts.map +0 -1
- package/dist/sessions/conversation-log.js +0 -50
- package/dist/sessions/conversation-log.js.map +0 -1
- package/dist/sessions/rotation.d.ts +0 -2
- package/dist/sessions/rotation.d.ts.map +0 -1
- package/dist/sessions/rotation.js +0 -41
- package/dist/sessions/rotation.js.map +0 -1
- package/dist/vault/disabled.d.ts +0 -9
- package/dist/vault/disabled.d.ts.map +0 -1
- package/dist/vault/disabled.js +0 -25
- package/dist/vault/disabled.js.map +0 -1
- package/dist/web/admin/store.d.ts +0 -13
- package/dist/web/admin/store.d.ts.map +0 -1
- package/dist/web/admin/store.js +0 -14
- package/dist/web/admin/store.js.map +0 -1
- package/dist/web/login/store.d.ts +0 -8
- package/dist/web/login/store.d.ts.map +0 -1
- package/dist/web/login/store.js +0 -15
- package/dist/web/login/store.js.map +0 -1
- package/dist/web/session-view/service.d.ts +0 -6
- package/dist/web/session-view/service.d.ts.map +0 -1
- package/dist/web/session-view/service.js +0 -517
- package/dist/web/session-view/service.js.map +0 -1
- package/dist/web/session-view/store.d.ts +0 -8
- package/dist/web/session-view/store.d.ts.map +0 -1
- package/dist/web/session-view/store.js +0 -15
- package/dist/web/session-view/store.js.map +0 -1
- /package/dist/{harness/extensions → dream}/types.js +0 -0
package/dist/web/admin/portal.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve as pathResolve, sep as pathSep } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { MikanModels, parseFrontmatter, SessionStore, validateEventFilename, } from "../../harness/index.js";
|
|
4
|
+
import { InMemoryTokenStore } from "../token-store.js";
|
|
5
|
+
const ADMIN_TOKEN_TTL_MS = 30 * 60 * 1000;
|
|
6
|
+
export class InMemoryAdminTokenStore extends InMemoryTokenStore {
|
|
7
|
+
create(args) {
|
|
8
|
+
this.deleteWhere((token) => token.platform === args.platform && token.platformUserId === args.platformUserId);
|
|
9
|
+
return this.createRecord(ADMIN_TOKEN_TTL_MS, {
|
|
10
|
+
platform: args.platform,
|
|
11
|
+
platformUserId: args.platformUserId,
|
|
12
|
+
...(args.platformUserName ? { platformUserName: args.platformUserName } : {}),
|
|
13
|
+
conversationId: args.conversationId,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
import { loadConversationAutoReplyConfig, loadConversationWorkspaceOverride, loadGlobalSettings, loadScopeMcpServers, resolveConversationSettings, saveConversationAutoReplyConfig, } from "../../config.js";
|
|
18
|
+
import { findMcpPreset, listMcpPresets, materializeMcpPreset } from "../../mcp/catalog.js";
|
|
5
19
|
import { applyConversationSettings, applyConversationWorkspacePolicy, applyGlobalSettings, applyGlobalWorkspacePolicy, } from "../../settings-mutation.js";
|
|
6
20
|
import { addPackage, inspectConversationPackages, refreshPackage, removePackage, } from "../../packages/index.js";
|
|
7
|
-
import { escapeHtml, readJsonBody, renderPortalShell } from "../portal-shell.js";
|
|
8
|
-
import { resolveExistingSessionFile } from "../session-view/
|
|
21
|
+
import { escapeHtml, jsonResponse as jsonRes, readJsonBody, renderPortalShell, } from "../portal-shell.js";
|
|
22
|
+
import { resolveExistingSessionFile } from "../session-view/portal.js";
|
|
9
23
|
import { PRODUCT_NAME } from "../../platform-messages.js";
|
|
10
24
|
import { credentialAuthorizationKey } from "../../sandbox/identity.js";
|
|
11
25
|
import { resolveWorkspaceProjection } from "../../workspace-projection/index.js";
|
|
@@ -40,152 +54,93 @@ export async function handleAdminRequest(req, res, url, services) {
|
|
|
40
54
|
// ── API routing ────────────────────────────────────────────────────────────────
|
|
41
55
|
async function routeApiRequest(req, res, url, services) {
|
|
42
56
|
if (req.method === "GET") {
|
|
43
|
-
|
|
44
|
-
if (!token) {
|
|
45
|
-
jsonRes(res, 403, { error: "Unauthorized" });
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (url.pathname === "/admin/api/me") {
|
|
49
|
-
serveMe(res, token);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (url.pathname === "/admin/api/conversations") {
|
|
53
|
-
serveConversationsList(res, services);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (url.pathname === "/admin/api/session-usage") {
|
|
57
|
-
await serveSessionUsage(res, services);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (url.pathname === "/admin/api/conversation-usage") {
|
|
61
|
-
await serveConversationUsage(res, url, services);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (url.pathname === "/admin/api/conversation-state") {
|
|
65
|
-
serveConversationState(res, url, services, token);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (url.pathname === "/admin/api/settings/global") {
|
|
69
|
-
serveGlobalSettings(res);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (url.pathname === "/admin/api/models") {
|
|
73
|
-
void serveModelsList(res);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (url.pathname === "/admin/api/workspace/tree") {
|
|
77
|
-
serveWorkspaceTree(res, url, services, token);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (url.pathname === "/admin/api/workspace/file") {
|
|
81
|
-
serveWorkspaceFile(res, url, services, token);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (url.pathname === "/admin/api/skills") {
|
|
85
|
-
serveSkillsList(res, url, services, token);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (url.pathname === "/admin/api/skills/file") {
|
|
89
|
-
serveSkillFile(res, url, services, token);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (url.pathname === "/admin/api/packages") {
|
|
93
|
-
await servePackagesList(res, url, services, token);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (url.pathname === "/admin/api/extension-secrets") {
|
|
97
|
-
serveExtensionSecretsList(res, url, services, token);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (url.pathname === "/admin/api/mcp-servers") {
|
|
101
|
-
serveMcpServersList(res, url, services, token);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (url.pathname === "/admin/api/events") {
|
|
105
|
-
await serveEventsList(res, services);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (url.pathname === "/admin/api/events/file") {
|
|
109
|
-
serveEventsFile(res, url, services);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
if (url.pathname === "/admin/api/conversations/events") {
|
|
113
|
-
await serveConversationEventsList(res, url, services, token);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
jsonRes(res, 404, { error: "Not found" });
|
|
57
|
+
await routeGetApiRequest(res, url, services);
|
|
117
58
|
return;
|
|
118
59
|
}
|
|
119
60
|
if (req.method !== "POST") {
|
|
120
61
|
jsonRes(res, 405, { error: "Method not allowed" });
|
|
121
62
|
return;
|
|
122
63
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const token = services.adminTokenStore.peek(rawToken);
|
|
64
|
+
await routePostApiRequest(req, res, url, services);
|
|
65
|
+
}
|
|
66
|
+
async function routeGetApiRequest(res, url, services) {
|
|
67
|
+
const token = services.adminTokenStore.peek(url.searchParams.get("token") ?? "");
|
|
128
68
|
if (!token) {
|
|
129
69
|
jsonRes(res, 403, { error: "Unauthorized" });
|
|
130
70
|
return;
|
|
131
71
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
if (url.pathname === "/admin/api/packages/mutate") {
|
|
165
|
-
servePackageMutation(res, body, services, token);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (url.pathname === "/admin/api/extension-secrets/mutate") {
|
|
169
|
-
serveExtensionSecretMutation(res, body, services);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (url.pathname === "/admin/api/settings/model") {
|
|
173
|
-
serveGlobalModelUpdate(res, body, services);
|
|
174
|
-
return;
|
|
72
|
+
switch (url.pathname) {
|
|
73
|
+
case "/admin/api/conversations":
|
|
74
|
+
return serveConversationsList(res, services);
|
|
75
|
+
case "/admin/api/session-usage":
|
|
76
|
+
return serveSessionUsage(res, services);
|
|
77
|
+
case "/admin/api/conversation-usage":
|
|
78
|
+
return serveConversationUsage(res, url, services);
|
|
79
|
+
case "/admin/api/conversation-state":
|
|
80
|
+
return serveConversationState(res, url, services, token);
|
|
81
|
+
case "/admin/api/settings/global":
|
|
82
|
+
return serveGlobalSettings(res);
|
|
83
|
+
case "/admin/api/models":
|
|
84
|
+
return serveModelsList(res);
|
|
85
|
+
case "/admin/api/workspace/tree":
|
|
86
|
+
return serveWorkspaceTree(res, url, services, token);
|
|
87
|
+
case "/admin/api/workspace/file":
|
|
88
|
+
return serveWorkspaceFile(res, url, services, token);
|
|
89
|
+
case "/admin/api/skills":
|
|
90
|
+
return serveSkillsList(res, url, services, token);
|
|
91
|
+
case "/admin/api/skills/file":
|
|
92
|
+
return serveSkillFile(res, url, services, token);
|
|
93
|
+
case "/admin/api/packages":
|
|
94
|
+
return servePackagesList(res, url, services, token);
|
|
95
|
+
case "/admin/api/mcp-servers":
|
|
96
|
+
return serveMcpServersList(res, url, services, token);
|
|
97
|
+
case "/admin/api/events":
|
|
98
|
+
return serveEventsList(res, services);
|
|
99
|
+
case "/admin/api/conversations/events":
|
|
100
|
+
return serveConversationEventsList(res, url, services, token);
|
|
101
|
+
default:
|
|
102
|
+
jsonRes(res, 404, { error: "Not found" });
|
|
175
103
|
}
|
|
176
|
-
|
|
177
|
-
|
|
104
|
+
}
|
|
105
|
+
async function routePostApiRequest(req, res, url, services) {
|
|
106
|
+
const body = await readJsonBody(req, res, 32 * 1024);
|
|
107
|
+
if (!body)
|
|
178
108
|
return;
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
109
|
+
const token = services.adminTokenStore.peek(typeof body.token === "string" ? body.token : "");
|
|
110
|
+
if (!token) {
|
|
111
|
+
jsonRes(res, 403, { error: "Unauthorized" });
|
|
182
112
|
return;
|
|
183
113
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
114
|
+
switch (url.pathname) {
|
|
115
|
+
case "/admin/api/conversations/model":
|
|
116
|
+
return serveConversationModelUpdate(res, body, services, token);
|
|
117
|
+
case "/admin/api/conversations/sandbox":
|
|
118
|
+
return serveConversationSandboxUpdate(res, body, services, token);
|
|
119
|
+
case "/admin/api/conversations/auto-reply":
|
|
120
|
+
return serveConversationAutoReplyUpdate(res, body, services, token);
|
|
121
|
+
case "/admin/api/conversations/slack":
|
|
122
|
+
return serveConversationSlackUpdate(res, body, services, token);
|
|
123
|
+
case "/admin/api/conversations/session-link":
|
|
124
|
+
return serveConversationSessionLink(res, body, services, token);
|
|
125
|
+
case "/admin/api/conversations/login-link":
|
|
126
|
+
return serveConversationLoginLink(res, body, services, token);
|
|
127
|
+
case "/admin/api/conversations/events/delete":
|
|
128
|
+
return serveConversationEventDelete(res, body, services, token);
|
|
129
|
+
case "/admin/api/mcp-servers/mutate":
|
|
130
|
+
return serveMcpServerMutation(res, body, services, token);
|
|
131
|
+
case "/admin/api/packages/mutate":
|
|
132
|
+
return servePackageMutation(res, body, services, token);
|
|
133
|
+
case "/admin/api/settings/model":
|
|
134
|
+
return serveGlobalModelUpdate(res, body, services);
|
|
135
|
+
case "/admin/api/settings/workspace":
|
|
136
|
+
return serveGlobalWorkspaceUpdate(res, body, services);
|
|
137
|
+
case "/admin/api/settings/sandbox":
|
|
138
|
+
return serveGlobalSandboxUpdate(res, body, services);
|
|
139
|
+
case "/admin/api/settings/slack":
|
|
140
|
+
return serveGlobalSlackUpdate(res, body, services);
|
|
141
|
+
default:
|
|
142
|
+
jsonRes(res, 404, { error: "Not found" });
|
|
187
143
|
}
|
|
188
|
-
jsonRes(res, 404, { error: "Not found" });
|
|
189
144
|
}
|
|
190
145
|
/**
|
|
191
146
|
* Admin scope is a full office address. The platform defaults to the token's
|
|
@@ -219,15 +174,6 @@ function requireAdminWorkspace(res, services) {
|
|
|
219
174
|
return services.workspace;
|
|
220
175
|
}
|
|
221
176
|
// ── API handlers ───────────────────────────────────────────────────────────────
|
|
222
|
-
function serveMe(res, token) {
|
|
223
|
-
jsonRes(res, 200, {
|
|
224
|
-
platform: token.platform,
|
|
225
|
-
platformUserId: token.platformUserId,
|
|
226
|
-
platformUserName: token.platformUserName ?? null,
|
|
227
|
-
conversationId: token.conversationId,
|
|
228
|
-
expiresAt: token.expiresAt,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
177
|
/**
|
|
232
178
|
* Office directories are office-key named and not reversible to raw ids, so
|
|
233
179
|
* enumeration reads the office registry — the durable raw-id ↔ office
|
|
@@ -503,9 +449,11 @@ function serveConversationState(res, url, services, token) {
|
|
|
503
449
|
globalThinkingLevel: globalConfig.thinkingLevel,
|
|
504
450
|
workspaceDoorPolicy: conversationWorkspace.doorPolicy,
|
|
505
451
|
workspaceLayout: conversationWorkspace.layout,
|
|
452
|
+
workspaceVisibility: conversationWorkspace.visibility,
|
|
506
453
|
workspaceOverride: doorPolicyChoiceKey(loadConversationWorkspaceOverride(office)),
|
|
507
454
|
globalWorkspaceDoorPolicy: globalWorkspaceSettings?.doorPolicy ?? "isolated",
|
|
508
455
|
globalWorkspaceLayout: globalWorkspaceSettings?.layout ?? "conversation",
|
|
456
|
+
globalWorkspaceVisibility: globalWorkspaceSettings?.visibility ?? "public",
|
|
509
457
|
autoReplyEnabled: autoReply.enabled,
|
|
510
458
|
autoReplyRules: autoReply.rules,
|
|
511
459
|
slack: {
|
|
@@ -525,8 +473,9 @@ function serveGlobalSettings(res) {
|
|
|
525
473
|
sandboxMemory: config.sandbox?.memory ?? null,
|
|
526
474
|
sandboxBoostCpus: config.sandbox?.boost?.cpus ?? null,
|
|
527
475
|
sandboxBoostMemory: config.sandbox?.boost?.memory ?? null,
|
|
528
|
-
workspaceDoorPolicy: config.sandbox?.workspace?.doorPolicy ??
|
|
529
|
-
workspaceLayout: config.sandbox?.workspace?.layout ??
|
|
476
|
+
workspaceDoorPolicy: config.sandbox?.workspace?.doorPolicy ?? null,
|
|
477
|
+
workspaceLayout: config.sandbox?.workspace?.layout ?? null,
|
|
478
|
+
workspaceVisibility: config.sandbox?.workspace?.visibility ?? null,
|
|
530
479
|
defaultSharedVault: config.sandbox?.defaultSharedVault ?? null,
|
|
531
480
|
slack: {
|
|
532
481
|
replyMode: config.slack?.replyMode ?? "top-level",
|
|
@@ -595,7 +544,13 @@ function serveConversationModelUpdate(res, body, services, token) {
|
|
|
595
544
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
596
545
|
}
|
|
597
546
|
}
|
|
598
|
-
/**
|
|
547
|
+
/**
|
|
548
|
+
* Wire values for a door-policy selection; "default" clears the office's
|
|
549
|
+
* override. `trusted-shared-support` keeps the historical read-write shared
|
|
550
|
+
* MEMORY.md; `trusted-shared-support-private` is the same layout with the
|
|
551
|
+
* shared MEMORY.md mounted read-only (modeled on Claude Tag's private-
|
|
552
|
+
* channel memory: read the shared pool, never write into it).
|
|
553
|
+
*/
|
|
599
554
|
function parseDoorPolicyChoice(value) {
|
|
600
555
|
switch (value) {
|
|
601
556
|
case "default":
|
|
@@ -603,7 +558,11 @@ function parseDoorPolicyChoice(value) {
|
|
|
603
558
|
case "isolated":
|
|
604
559
|
return { choice: { doorPolicy: "isolated" } };
|
|
605
560
|
case "trusted-shared-support":
|
|
606
|
-
return { choice: { doorPolicy: "trusted", layout: "shared-support" } };
|
|
561
|
+
return { choice: { doorPolicy: "trusted", layout: "shared-support", visibility: "public" } };
|
|
562
|
+
case "trusted-shared-support-private":
|
|
563
|
+
return {
|
|
564
|
+
choice: { doorPolicy: "trusted", layout: "shared-support", visibility: "private" },
|
|
565
|
+
};
|
|
607
566
|
case "trusted-full":
|
|
608
567
|
return { choice: { doorPolicy: "trusted", layout: "full" } };
|
|
609
568
|
default:
|
|
@@ -615,13 +574,17 @@ function doorPolicyChoiceKey(choice) {
|
|
|
615
574
|
return "default";
|
|
616
575
|
if (choice.doorPolicy === "isolated")
|
|
617
576
|
return "isolated";
|
|
618
|
-
|
|
577
|
+
if (choice.layout === "full")
|
|
578
|
+
return "trusted-full";
|
|
579
|
+
return choice.visibility === "private"
|
|
580
|
+
? "trusted-shared-support-private"
|
|
581
|
+
: "trusted-shared-support";
|
|
619
582
|
}
|
|
620
583
|
function serveConversationSandboxUpdate(res, body, services, token) {
|
|
621
584
|
const parsed = parseDoorPolicyChoice(body.doorPolicy);
|
|
622
585
|
if (!parsed) {
|
|
623
586
|
jsonRes(res, 400, {
|
|
624
|
-
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
|
|
587
|
+
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', 'trusted-shared-support-private', or 'trusted-full'",
|
|
625
588
|
});
|
|
626
589
|
return;
|
|
627
590
|
}
|
|
@@ -649,7 +612,7 @@ function serveGlobalWorkspaceUpdate(res, body, services) {
|
|
|
649
612
|
const parsed = parseDoorPolicyChoice(body.doorPolicy);
|
|
650
613
|
if (!parsed) {
|
|
651
614
|
jsonRes(res, 400, {
|
|
652
|
-
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
|
|
615
|
+
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', 'trusted-shared-support-private', or 'trusted-full'",
|
|
653
616
|
});
|
|
654
617
|
return;
|
|
655
618
|
}
|
|
@@ -739,7 +702,14 @@ function serveConversationSessionLink(res, body, services, token) {
|
|
|
739
702
|
return;
|
|
740
703
|
}
|
|
741
704
|
try {
|
|
742
|
-
const { token: viewToken } = services.sessionViewTokenStore.create(
|
|
705
|
+
const { token: viewToken } = services.sessionViewTokenStore.create({
|
|
706
|
+
platform: token.platform,
|
|
707
|
+
platformUserId: token.platformUserId,
|
|
708
|
+
conversationId: scope.conversationId,
|
|
709
|
+
sessionKey: scope.conversationId,
|
|
710
|
+
sessionFile,
|
|
711
|
+
platformUserName: token.platformUserName,
|
|
712
|
+
});
|
|
743
713
|
const url = `${services.portalBaseUrl}/session?token=${encodeURIComponent(viewToken)}`;
|
|
744
714
|
jsonRes(res, 200, { ok: true, url });
|
|
745
715
|
}
|
|
@@ -1157,77 +1127,6 @@ async function servePackagesList(res, url, services, token) {
|
|
|
1157
1127
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
1158
1128
|
}
|
|
1159
1129
|
}
|
|
1160
|
-
const EXTENSION_SLUG_PATTERN = /^[a-z0-9_-]{1,64}$/;
|
|
1161
|
-
const EXTENSION_SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1162
|
-
/**
|
|
1163
|
-
* Declared + provisioned secrets for every extension visible to the selected
|
|
1164
|
-
* conversation (global scope plus the conversation's own installs, deduped by
|
|
1165
|
-
* slug — the vault is keyed by slug, so two scopes share one secret set).
|
|
1166
|
-
* Values never leave the server; only key names and provisioning status do.
|
|
1167
|
-
*/
|
|
1168
|
-
function serveExtensionSecretsList(res, url, services, token) {
|
|
1169
|
-
const scope = resolveConversationFromQuery(url, token);
|
|
1170
|
-
if (scope.error) {
|
|
1171
|
-
jsonRes(res, 403, { error: scope.error });
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1175
|
-
if (!workspace)
|
|
1176
|
-
return;
|
|
1177
|
-
const infosBySlug = new Map();
|
|
1178
|
-
for (const info of listInstalledExtensions(defaultExtensionDirs(scope.address, workspace.stateDir))) {
|
|
1179
|
-
infosBySlug.set(info.slug, info);
|
|
1180
|
-
}
|
|
1181
|
-
const extensions = [...infosBySlug.values()]
|
|
1182
|
-
.toSorted((left, right) => left.slug.localeCompare(right.slug))
|
|
1183
|
-
.map((info) => {
|
|
1184
|
-
const provisioned = Object.keys(services.vaultManager.resolve(`extensions/${info.slug}`)?.env ?? {}).toSorted();
|
|
1185
|
-
const declaredKeys = new Set(info.secrets.map((secret) => secret.key));
|
|
1186
|
-
return {
|
|
1187
|
-
slug: info.slug,
|
|
1188
|
-
name: info.name,
|
|
1189
|
-
declared: info.secrets.map((secret) => ({
|
|
1190
|
-
...secret,
|
|
1191
|
-
provisioned: provisioned.includes(secret.key),
|
|
1192
|
-
})),
|
|
1193
|
-
extraProvisioned: provisioned.filter((key) => !declaredKeys.has(key)),
|
|
1194
|
-
};
|
|
1195
|
-
});
|
|
1196
|
-
jsonRes(res, 200, { extensions });
|
|
1197
|
-
}
|
|
1198
|
-
/** Set or delete one extension secret. Values are write-only through here. */
|
|
1199
|
-
function serveExtensionSecretMutation(res, body, services) {
|
|
1200
|
-
const action = body.action;
|
|
1201
|
-
if (action !== "set" && action !== "delete") {
|
|
1202
|
-
jsonRes(res, 400, { error: "action must be 'set' or 'delete'" });
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
const slug = typeof body.slug === "string" ? body.slug : "";
|
|
1206
|
-
if (!EXTENSION_SLUG_PATTERN.test(slug)) {
|
|
1207
|
-
jsonRes(res, 400, { error: "invalid extension slug" });
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
const key = typeof body.key === "string" ? body.key : "";
|
|
1211
|
-
if (!EXTENSION_SECRET_KEY_PATTERN.test(key)) {
|
|
1212
|
-
jsonRes(res, 400, { error: "invalid secret key (expected an env-style name)" });
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
const vaultKey = `extensions/${slug}`;
|
|
1216
|
-
if (action === "set") {
|
|
1217
|
-
const value = typeof body.value === "string" ? body.value : "";
|
|
1218
|
-
if (!value) {
|
|
1219
|
-
jsonRes(res, 400, { error: "value is required" });
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
services.vaultManager.upsertEnv(vaultKey, { [key]: value });
|
|
1223
|
-
}
|
|
1224
|
-
else if (!services.vaultManager.deleteEnvKey(vaultKey, key)) {
|
|
1225
|
-
jsonRes(res, 404, { error: "Not provisioned." });
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
const provisioned = Object.keys(services.vaultManager.resolve(vaultKey)?.env ?? {}).toSorted();
|
|
1229
|
-
jsonRes(res, 200, { ok: true, provisioned });
|
|
1230
|
-
}
|
|
1231
1130
|
/**
|
|
1232
1131
|
* Add / remove / refresh, in one route because they share validation and all
|
|
1233
1132
|
* three answer with the freshly re-read inventory: the panel never has to
|
|
@@ -1294,6 +1193,7 @@ function serveMcpServersList(res, url, services, token) {
|
|
|
1294
1193
|
const servers = loadScopeMcpServers(workspace.office(scope.address));
|
|
1295
1194
|
jsonRes(res, 200, {
|
|
1296
1195
|
conversationId: scope.conversationId,
|
|
1196
|
+
presets: listMcpPresets(),
|
|
1297
1197
|
global: redactMcpServers(servers.global),
|
|
1298
1198
|
conversation: redactMcpServers(servers.conversation),
|
|
1299
1199
|
});
|
|
@@ -1358,12 +1258,19 @@ function parseMcpServerEntry(raw) {
|
|
|
1358
1258
|
*/
|
|
1359
1259
|
function serveMcpServerMutation(res, body, services, token) {
|
|
1360
1260
|
const action = body.action;
|
|
1361
|
-
if (action !== "set" && action !== "remove" && action !== "toggle") {
|
|
1362
|
-
jsonRes(res, 400, { error: "action must be 'set', 'remove', or 'toggle'" });
|
|
1261
|
+
if (action !== "set" && action !== "install" && action !== "remove" && action !== "toggle") {
|
|
1262
|
+
jsonRes(res, 400, { error: "action must be 'set', 'install', 'remove', or 'toggle'" });
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const preset = action === "install" && typeof body.presetId === "string"
|
|
1266
|
+
? findMcpPreset(body.presetId)
|
|
1267
|
+
: undefined;
|
|
1268
|
+
if (action === "install" && !preset) {
|
|
1269
|
+
jsonRes(res, 400, { error: "unknown MCP preset" });
|
|
1363
1270
|
return;
|
|
1364
1271
|
}
|
|
1365
1272
|
const mutationScope = body.scope === "global" ? "global" : "conversation";
|
|
1366
|
-
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
1273
|
+
const name = preset?.serverName ?? (typeof body.name === "string" ? body.name.trim() : "");
|
|
1367
1274
|
if (!MCP_SERVER_NAME_PATTERN.test(name)) {
|
|
1368
1275
|
jsonRes(res, 400, {
|
|
1369
1276
|
error: "invalid server name (letters, digits, '_' or '-', starting with a letter)",
|
|
@@ -1399,6 +1306,15 @@ function serveMcpServerMutation(res, body, services, token) {
|
|
|
1399
1306
|
: {}),
|
|
1400
1307
|
};
|
|
1401
1308
|
}
|
|
1309
|
+
else if (action === "install") {
|
|
1310
|
+
try {
|
|
1311
|
+
next[name] = materializeMcpPreset(preset, mcpStringMap(body.credentials) ?? {});
|
|
1312
|
+
}
|
|
1313
|
+
catch (err) {
|
|
1314
|
+
jsonRes(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1402
1318
|
else if (action === "remove") {
|
|
1403
1319
|
if (!(name in next)) {
|
|
1404
1320
|
jsonRes(res, 404, { error: "Not declared here." });
|
|
@@ -1470,7 +1386,6 @@ function serveSkillFile(res, url, services, token) {
|
|
|
1470
1386
|
servePreviewFile(res, safe.absolute, { source, directory }, "Skill file not found");
|
|
1471
1387
|
}
|
|
1472
1388
|
// ── Events ─────────────────────────────────────────────────────────────────────
|
|
1473
|
-
const EVENTS_FILE_MAX_BYTES = 64 * 1024;
|
|
1474
1389
|
/**
|
|
1475
1390
|
* List events through the owning store, whose payloads are validated by the
|
|
1476
1391
|
* event-format module (files that fail validation stay visible with a null
|
|
@@ -1520,45 +1435,6 @@ async function serveConversationEventsList(res, url, services, token) {
|
|
|
1520
1435
|
const events = (await listAllEvents(store)).filter((e) => e.conversationId === scope.conversationId);
|
|
1521
1436
|
jsonRes(res, 200, { conversationId: scope.conversationId, events });
|
|
1522
1437
|
}
|
|
1523
|
-
function serveEventsFile(res, url, services) {
|
|
1524
|
-
const workspace = requireAdminWorkspace(res, services);
|
|
1525
|
-
if (!workspace)
|
|
1526
|
-
return;
|
|
1527
|
-
let name;
|
|
1528
|
-
try {
|
|
1529
|
-
name = validateEventFilename(url.searchParams.get("name") ?? "");
|
|
1530
|
-
}
|
|
1531
|
-
catch {
|
|
1532
|
-
jsonRes(res, 400, { error: "Invalid name" });
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
|
-
const filePath = join(workspace.eventsDir, name);
|
|
1536
|
-
let stats;
|
|
1537
|
-
try {
|
|
1538
|
-
stats = statSync(filePath);
|
|
1539
|
-
}
|
|
1540
|
-
catch {
|
|
1541
|
-
jsonRes(res, 404, { error: "Not found" });
|
|
1542
|
-
return;
|
|
1543
|
-
}
|
|
1544
|
-
if (!stats.isFile()) {
|
|
1545
|
-
jsonRes(res, 400, { error: "Not a file" });
|
|
1546
|
-
return;
|
|
1547
|
-
}
|
|
1548
|
-
if (stats.size > EVENTS_FILE_MAX_BYTES) {
|
|
1549
|
-
jsonRes(res, 413, { error: "File too large" });
|
|
1550
|
-
return;
|
|
1551
|
-
}
|
|
1552
|
-
let raw;
|
|
1553
|
-
try {
|
|
1554
|
-
raw = readFileSync(filePath, "utf-8");
|
|
1555
|
-
}
|
|
1556
|
-
catch (err) {
|
|
1557
|
-
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
1558
|
-
return;
|
|
1559
|
-
}
|
|
1560
|
-
jsonRes(res, 200, { name, content: raw });
|
|
1561
|
-
}
|
|
1562
1438
|
/** Delete a single event file scoped to the caller's conversation. */
|
|
1563
1439
|
async function serveConversationEventDelete(res, body, services, token) {
|
|
1564
1440
|
const scope = resolveTargetConversation(body, token);
|
|
@@ -1599,18 +1475,9 @@ async function serveConversationEventDelete(res, body, services, token) {
|
|
|
1599
1475
|
}
|
|
1600
1476
|
}
|
|
1601
1477
|
// ── Utilities ──────────────────────────────────────────────────────────────────
|
|
1602
|
-
function jsonRes(res, status, body) {
|
|
1603
|
-
res.writeHead(status, {
|
|
1604
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
1605
|
-
"Cache-Control": "no-store",
|
|
1606
|
-
});
|
|
1607
|
-
res.end(JSON.stringify(body));
|
|
1608
|
-
}
|
|
1609
1478
|
const esc = escapeHtml;
|
|
1610
1479
|
// ── HTML ───────────────────────────────────────────────────────────────────────
|
|
1611
|
-
|
|
1612
|
-
const userLabel = token.platformUserName ?? token.platformUserId;
|
|
1613
|
-
const body = `<nav class="tab-nav" role="tablist" aria-label="Admin sections">
|
|
1480
|
+
const adminViewBody = `<nav class="tab-nav" role="tablist" aria-label="Admin sections">
|
|
1614
1481
|
<button class="tab-btn active" role="tab" aria-selected="true" aria-controls="panel-conversation" data-tab="conversation">Conversation</button>
|
|
1615
1482
|
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-global" data-tab="global">Global</button>
|
|
1616
1483
|
</nav>
|
|
@@ -1658,8 +1525,8 @@ function renderAdminPage(token) {
|
|
|
1658
1525
|
<section class="card sect" id="sect-packages" data-section="packages">
|
|
1659
1526
|
<header class="sect-head">
|
|
1660
1527
|
<div>
|
|
1661
|
-
<p class="eyebrow">
|
|
1662
|
-
<h2 class="card-title">此對話的
|
|
1528
|
+
<p class="eyebrow">Skill Packages</p>
|
|
1529
|
+
<h2 class="card-title">此對話的 skill 套件</h2>
|
|
1663
1530
|
</div>
|
|
1664
1531
|
<button class="refresh-btn" onclick="loadPackages()">↻</button>
|
|
1665
1532
|
</header>
|
|
@@ -1766,8 +1633,8 @@ function renderAdminPage(token) {
|
|
|
1766
1633
|
<section class="card sect">
|
|
1767
1634
|
<header class="sect-head">
|
|
1768
1635
|
<div>
|
|
1769
|
-
<p class="eyebrow">Global
|
|
1770
|
-
<h2 class="card-title">所有對話都會載入的
|
|
1636
|
+
<p class="eyebrow">Global Skill Packages</p>
|
|
1637
|
+
<h2 class="card-title">所有對話都會載入的 skill 套件</h2>
|
|
1771
1638
|
</div>
|
|
1772
1639
|
<button class="refresh-btn" onclick="loadPackages()">↻</button>
|
|
1773
1640
|
</header>
|
|
@@ -1794,18 +1661,6 @@ function renderAdminPage(token) {
|
|
|
1794
1661
|
<div id="mcp-global-content"><div class="loading-msg">Loading…</div></div>
|
|
1795
1662
|
</section>
|
|
1796
1663
|
|
|
1797
|
-
<section class="card sect">
|
|
1798
|
-
<header class="sect-head">
|
|
1799
|
-
<div>
|
|
1800
|
-
<p class="eyebrow">Extension Secrets</p>
|
|
1801
|
-
<h2 class="card-title">Extension 密鑰(依 slug 全域共用)</h2>
|
|
1802
|
-
</div>
|
|
1803
|
-
<button class="refresh-btn" onclick="loadExtensionSecrets()">↻</button>
|
|
1804
|
-
</header>
|
|
1805
|
-
<div id="ext-secrets-msg" class="pkg-msg" style="display:none"></div>
|
|
1806
|
-
<div id="ext-secrets-content"><div class="loading-msg">Loading…</div></div>
|
|
1807
|
-
</section>
|
|
1808
|
-
|
|
1809
1664
|
<section class="card sect">
|
|
1810
1665
|
<header class="sect-head">
|
|
1811
1666
|
<div>
|
|
@@ -1827,13 +1682,24 @@ function renderAdminPage(token) {
|
|
|
1827
1682
|
</header>
|
|
1828
1683
|
<div id="global-events-content"><div class="loading-msg">Loading…</div></div>
|
|
1829
1684
|
</section>
|
|
1830
|
-
</div
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1685
|
+
</div>
|
|
1686
|
+
|
|
1687
|
+
<dialog id="mcp-install-dialog" class="mcp-dialog" aria-labelledby="mcp-dialog-title">
|
|
1688
|
+
<div class="mcp-dialog-head">
|
|
1689
|
+
<div>
|
|
1690
|
+
<p class="eyebrow">Install MCP preset</p>
|
|
1691
|
+
<h2 id="mcp-dialog-title" class="card-title"></h2>
|
|
1692
|
+
</div>
|
|
1693
|
+
<button class="mcp-dialog-close" type="button" aria-label="Close" onclick="closeMcpInstall()">×</button>
|
|
1694
|
+
</div>
|
|
1695
|
+
<div id="mcp-dialog-content"></div>
|
|
1696
|
+
<div id="mcp-dialog-error" class="inline-result err" style="display:none"></div>
|
|
1697
|
+
<div class="mcp-dialog-actions">
|
|
1698
|
+
<button class="pkg-btn" type="button" onclick="closeMcpInstall()">Cancel</button>
|
|
1699
|
+
<button id="mcp-dialog-install" class="primary-action-btn" type="button" onclick="installMcpPreset(this)">Install preset</button>
|
|
1700
|
+
</div>
|
|
1701
|
+
</dialog>`;
|
|
1702
|
+
const adminViewScript = ` let activeConversationKey = defaultConversationKey;
|
|
1837
1703
|
function scopeOf(key) {
|
|
1838
1704
|
const sep = key.indexOf(':');
|
|
1839
1705
|
return { platform: key.slice(0, sep), conversationId: key.slice(sep + 1) };
|
|
@@ -1849,6 +1715,9 @@ function renderAdminPage(token) {
|
|
|
1849
1715
|
}
|
|
1850
1716
|
let availableModels = [];
|
|
1851
1717
|
let modelsLoaded = false;
|
|
1718
|
+
let mcpPresets = [];
|
|
1719
|
+
let mcpServersByScope = { conversation: {}, global: {} };
|
|
1720
|
+
let pendingMcpInstall = null;
|
|
1852
1721
|
|
|
1853
1722
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
1854
1723
|
|
|
@@ -1865,6 +1734,30 @@ function renderAdminPage(token) {
|
|
|
1865
1734
|
async function copyToClipboard(text) {
|
|
1866
1735
|
try { await navigator.clipboard.writeText(text); } catch { prompt('Copy this link:', text); }
|
|
1867
1736
|
}
|
|
1737
|
+
document.addEventListener('click', (event) => {
|
|
1738
|
+
const target = event.target instanceof Element
|
|
1739
|
+
? event.target.closest('[data-admin-action]')
|
|
1740
|
+
: null;
|
|
1741
|
+
if (!target) return;
|
|
1742
|
+
switch (target.dataset.adminAction) {
|
|
1743
|
+
case 'preview-file':
|
|
1744
|
+
void previewFile(target.dataset.filePath || '');
|
|
1745
|
+
break;
|
|
1746
|
+
case 'copy-link':
|
|
1747
|
+
void copyToClipboard(target.dataset.copyText || '');
|
|
1748
|
+
break;
|
|
1749
|
+
case 'delete-event':
|
|
1750
|
+
void deleteEvent(target.dataset.eventName || '', target);
|
|
1751
|
+
break;
|
|
1752
|
+
case 'select-conversation':
|
|
1753
|
+
setActiveConversation(target.dataset.conversationId || '');
|
|
1754
|
+
switchTab('conversation');
|
|
1755
|
+
break;
|
|
1756
|
+
case 'toggle-timeline-filter':
|
|
1757
|
+
toggleTimelineFilter(target.dataset.filterKey || '');
|
|
1758
|
+
break;
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1868
1761
|
async function apiGet(path) {
|
|
1869
1762
|
const url = path + (path.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(adminToken);
|
|
1870
1763
|
const r = await fetch(url);
|
|
@@ -2008,9 +1901,10 @@ function renderAdminPage(token) {
|
|
|
2008
1901
|
const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');
|
|
2009
1902
|
const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');
|
|
2010
1903
|
const doorPolicyChoices = [
|
|
2011
|
-
['default', '
|
|
1904
|
+
['default', 'Automatic — follows the platform channel (public shares, private reads only, DMs isolated)'],
|
|
2012
1905
|
['isolated', 'isolated — own office only'],
|
|
2013
|
-
['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
|
|
1906
|
+
['trusted-shared-support', 'trusted / shared-support (public) — office + shared memory, skills, events — read-write'],
|
|
1907
|
+
['trusted-shared-support-private', 'trusted / shared-support (private) — same, but shared MEMORY.md is read-only'],
|
|
2014
1908
|
['trusted-full', 'trusted / full — entire workspace'],
|
|
2015
1909
|
];
|
|
2016
1910
|
const doorPolicyOpts = doorPolicyChoices.map(([value, label]) =>
|
|
@@ -2036,7 +1930,7 @@ function renderAdminPage(token) {
|
|
|
2036
1930
|
'<div class="config-block">',
|
|
2037
1931
|
'<h3 class="card-subtitle">Office data policy</h3>',
|
|
2038
1932
|
'<div class="config-row"><label>Door policy</label><select id="m-door-policy">' + doorPolicyOpts + '</select></div>',
|
|
2039
|
-
'<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout) + '</p>',
|
|
1933
|
+
'<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout + ' / ' + (data.workspaceVisibility || 'public')) + '</p>',
|
|
2040
1934
|
'<p class="muted-note">Changing the policy rebuilds the office sandbox container with the new mounts on its next message; the container contents are preserved.</p>',
|
|
2041
1935
|
'<button class="primary-action-btn" onclick="saveDoorPolicy(this)">Save door policy</button>',
|
|
2042
1936
|
'<div id="mount-save-result" class="inline-result" style="display:none"></div>',
|
|
@@ -2149,14 +2043,14 @@ function renderAdminPage(token) {
|
|
|
2149
2043
|
|
|
2150
2044
|
function renderTreeChildren(node) {
|
|
2151
2045
|
if (node.type === 'file') {
|
|
2152
|
-
return '<li><button class="tree-file"
|
|
2046
|
+
return '<li><button class="tree-file" data-admin-action="preview-file" data-file-path="' + escAttr(node.path) + '">' + escHtml(node.name) + '</button></li>';
|
|
2153
2047
|
}
|
|
2154
2048
|
if (!node.children || node.children.length === 0) {
|
|
2155
2049
|
return '<li><span class="tree-dir empty">' + escHtml(node.name || '.') + '/</span></li>';
|
|
2156
2050
|
}
|
|
2157
2051
|
const inner = node.children.map((c) =>
|
|
2158
2052
|
c.type === 'file'
|
|
2159
|
-
? '<li><button class="tree-file"
|
|
2053
|
+
? '<li><button class="tree-file" data-admin-action="preview-file" data-file-path="' + escAttr(c.path) + '">' + escHtml(c.name) + '</button></li>'
|
|
2160
2054
|
: '<li><details open><summary class="tree-dir">' + escHtml(c.name) + '/</summary><ul>' + renderTreeChildren(c) + '</ul></details></li>'
|
|
2161
2055
|
).join('');
|
|
2162
2056
|
return inner;
|
|
@@ -2199,16 +2093,11 @@ function renderAdminPage(token) {
|
|
|
2199
2093
|
return;
|
|
2200
2094
|
}
|
|
2201
2095
|
container.innerHTML = '<div class="pkg-list">' + rows.map((p) => {
|
|
2202
|
-
const provides =
|
|
2203
|
-
.concat(p.extensions.map((s) => 'ext: ' + s))
|
|
2204
|
-
.concat(p.skills.map((s) => 'skill: ' + s));
|
|
2096
|
+
const provides = p.skills.map((s) => 'skill: ' + s);
|
|
2205
2097
|
const status = p.error
|
|
2206
2098
|
? '<span class="pkg-badge pkg-badge-err">' + escHtml(p.error) + '</span>'
|
|
2207
2099
|
: p.shadowed
|
|
2208
2100
|
? '<span class="pkg-badge pkg-badge-warn">被此對話的同名套件覆蓋,不會載入</span>'
|
|
2209
|
-
// "downloaded", not "ready": this status means the checkout is on
|
|
2210
|
-
// disk and was inspected, which is not the same as activated. The
|
|
2211
|
-
// extension loads on the conversation's next harness instance.
|
|
2212
2101
|
: '<span class="pkg-badge pkg-badge-ok">已下載</span>'
|
|
2213
2102
|
+ '<span class="pkg-badge pkg-badge-warn">需 /pi-new 才會載入</span>';
|
|
2214
2103
|
return '<div class="pkg-row">' +
|
|
@@ -2217,7 +2106,7 @@ function renderAdminPage(token) {
|
|
|
2217
2106
|
'</div>' +
|
|
2218
2107
|
(provides.length > 0
|
|
2219
2108
|
? '<div class="pkg-provides">' + provides.map((t) => '<span class="pkg-chip">' + escHtml(t) + '</span>').join('') + '</div>'
|
|
2220
|
-
: '<div class="pkg-provides pkg-provides-empty">此套件沒有提供
|
|
2109
|
+
: '<div class="pkg-provides pkg-provides-empty">此套件沒有提供 skill</div>') +
|
|
2221
2110
|
'<div class="pkg-actions">' +
|
|
2222
2111
|
'<button class="pkg-btn" data-pkg-action="refresh" data-pkg-scope="' + scope + '" data-pkg-source="' + escAttr(p.source) + '">Update</button>' +
|
|
2223
2112
|
'<button class="pkg-btn pkg-btn-danger" data-pkg-action="remove" data-pkg-scope="' + scope + '" data-pkg-source="' + escAttr(p.source) + '">Remove</button>' +
|
|
@@ -2294,10 +2183,14 @@ function renderAdminPage(token) {
|
|
|
2294
2183
|
el.textContent = text;
|
|
2295
2184
|
}
|
|
2296
2185
|
|
|
2186
|
+
function mcpTransport(server) {
|
|
2187
|
+
return server.command
|
|
2188
|
+
? server.command + (server.args && server.args.length ? ' ' + server.args.join(' ') : '')
|
|
2189
|
+
: (server.url || '');
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2297
2192
|
function renderMcpServer(scope, name, server) {
|
|
2298
|
-
const transport = server.command
|
|
2299
|
-
? 'stdio: ' + server.command + (server.args && server.args.length ? ' ' + server.args.join(' ') : '')
|
|
2300
|
-
: 'http: ' + (server.url || '');
|
|
2193
|
+
const transport = (server.command ? 'stdio: ' : 'http: ') + mcpTransport(server);
|
|
2301
2194
|
const keys = [];
|
|
2302
2195
|
if (server.envKeys && server.envKeys.length) keys.push('env: ' + server.envKeys.join(', '));
|
|
2303
2196
|
if (server.headerKeys && server.headerKeys.length) keys.push('headers: ' + server.headerKeys.join(', '));
|
|
@@ -2313,16 +2206,41 @@ function renderAdminPage(token) {
|
|
|
2313
2206
|
'</div>';
|
|
2314
2207
|
}
|
|
2315
2208
|
|
|
2209
|
+
function renderMcpPreset(scope, preset, servers) {
|
|
2210
|
+
const installed = Boolean(servers[preset.serverName]);
|
|
2211
|
+
const transport = preset.server.command ? 'Runs on host' : 'Remote service';
|
|
2212
|
+
return '<article class="mcp-preset">' +
|
|
2213
|
+
'<div class="mcp-preset-top">' +
|
|
2214
|
+
'<span class="mcp-preset-category">' + escHtml(preset.category) + '</span>' +
|
|
2215
|
+
(installed ? '<span class="pkg-badge pkg-badge-ok">Installed here</span>' : '') +
|
|
2216
|
+
'</div>' +
|
|
2217
|
+
'<h3>' + escHtml(preset.name) + '</h3>' +
|
|
2218
|
+
'<p>' + escHtml(preset.description) + '</p>' +
|
|
2219
|
+
'<div class="mcp-preset-meta">' + escHtml(transport) + ' · <code>' + escHtml(preset.serverName) + '</code></div>' +
|
|
2220
|
+
'<div class="mcp-preset-actions">' +
|
|
2221
|
+
'<a href="' + escAttr(preset.sourceUrl) + '" target="_blank" rel="noopener">Source ↗</a>' +
|
|
2222
|
+
'<button class="primary-action-btn" data-mcp-action="preset" data-mcp-scope="' + scope + '" data-mcp-preset="' + escAttr(preset.id) + '">' + (installed ? 'Review' : 'Install') + '</button>' +
|
|
2223
|
+
'</div>' +
|
|
2224
|
+
'</article>';
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2316
2227
|
function renderMcpScope(el, scope, servers) {
|
|
2228
|
+
const presets = mcpPresets.map((preset) => renderMcpPreset(scope, preset, servers)).join('');
|
|
2317
2229
|
const rows = Object.entries(servers).map(([name, server]) => renderMcpServer(scope, name, server)).join('');
|
|
2318
2230
|
el.innerHTML =
|
|
2319
|
-
'<div class="
|
|
2320
|
-
'<div class="
|
|
2321
|
-
|
|
2322
|
-
'<
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2231
|
+
'<div class="mcp-market-head"><div><h3>Explore presets</h3><p>Reviewed recipes that install into this scope.</p></div><span>' + mcpPresets.length + ' available</span></div>' +
|
|
2232
|
+
'<div class="mcp-preset-grid">' + presets + '</div>' +
|
|
2233
|
+
'<details class="mcp-installed" open><summary>Installed in this scope</summary>' +
|
|
2234
|
+
'<div class="pkg-list">' + (rows || '<div class="pkg-provides-empty">No MCP servers installed here</div>') + '</div>' +
|
|
2235
|
+
'</details>' +
|
|
2236
|
+
'<details class="mcp-manual"><summary>Advanced: add a custom server</summary>' +
|
|
2237
|
+
'<div class="pkg-add">' +
|
|
2238
|
+
'<input id="mcp-' + scope + '-name" class="pkg-input pkg-input-ref" type="text" spellcheck="false" placeholder="name" />' +
|
|
2239
|
+
'<input id="mcp-' + scope + '-target" class="pkg-input" type="text" spellcheck="false" placeholder="npx -y package@version 或 https://host/mcp" />' +
|
|
2240
|
+
'<input id="mcp-' + scope + '-env" class="pkg-input" type="text" spellcheck="false" placeholder="KEY=value(HTTP 則為 header)" />' +
|
|
2241
|
+
'<button class="primary-action-btn" data-mcp-action="add" data-mcp-scope="' + scope + '">Add custom</button>' +
|
|
2242
|
+
'</div>' +
|
|
2243
|
+
'</details>';
|
|
2326
2244
|
}
|
|
2327
2245
|
|
|
2328
2246
|
async function loadMcpServers() {
|
|
@@ -2332,8 +2250,10 @@ function renderAdminPage(token) {
|
|
|
2332
2250
|
if (globalEl) globalEl.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2333
2251
|
try {
|
|
2334
2252
|
const data = await apiGet('/admin/api/mcp-servers?' + scopeQuery());
|
|
2335
|
-
|
|
2336
|
-
|
|
2253
|
+
mcpPresets = Array.isArray(data.presets) ? data.presets : [];
|
|
2254
|
+
mcpServersByScope = { conversation: data.conversation || {}, global: data.global || {} };
|
|
2255
|
+
if (convEl) renderMcpScope(convEl, 'conversation', mcpServersByScope.conversation);
|
|
2256
|
+
if (globalEl) renderMcpScope(globalEl, 'global', mcpServersByScope.global);
|
|
2337
2257
|
} catch (err) {
|
|
2338
2258
|
if (convEl) convEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
2339
2259
|
if (globalEl) globalEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
@@ -2357,6 +2277,68 @@ function renderAdminPage(token) {
|
|
|
2357
2277
|
}
|
|
2358
2278
|
}
|
|
2359
2279
|
|
|
2280
|
+
function openMcpPreset(scope, presetId) {
|
|
2281
|
+
const preset = mcpPresets.find((item) => item.id === presetId);
|
|
2282
|
+
if (!preset) return;
|
|
2283
|
+
pendingMcpInstall = { scope: scope, preset: preset };
|
|
2284
|
+
const local = Boolean(preset.server.command);
|
|
2285
|
+
const installed = Boolean(mcpServersByScope[scope][preset.serverName]);
|
|
2286
|
+
const credentials = preset.credentials.map((credential, index) =>
|
|
2287
|
+
'<label class="mcp-credential"><span>' + escHtml(credential.label) + (credential.required ? ' *' : '') + '</span>' +
|
|
2288
|
+
'<input data-mcp-credential="' + index + '" type="' + (credential.secret ? 'password' : 'text') + '" autocomplete="off" />' +
|
|
2289
|
+
'<small>' + escHtml(credential.description) + '</small></label>'
|
|
2290
|
+
).join('');
|
|
2291
|
+
document.getElementById('mcp-dialog-title').textContent = preset.name;
|
|
2292
|
+
const dialogError = document.getElementById('mcp-dialog-error');
|
|
2293
|
+
dialogError.style.display = 'none';
|
|
2294
|
+
dialogError.textContent = '';
|
|
2295
|
+
document.getElementById('mcp-dialog-content').innerHTML =
|
|
2296
|
+
'<p class="mcp-dialog-desc">' + escHtml(preset.description) + '</p>' +
|
|
2297
|
+
'<div class="mcp-install-preview"><span>' + (local ? 'Host command' : 'Remote endpoint') + '</span><code>' + escHtml(mcpTransport(preset.server)) + '</code></div>' +
|
|
2298
|
+
'<div class="mcp-security-note ' + (local ? 'local' : 'remote') + '">' +
|
|
2299
|
+
(local
|
|
2300
|
+
? '<strong>Host code execution.</strong> This command runs outside the conversation sandbox with the mikan process user permissions.'
|
|
2301
|
+
: '<strong>External service.</strong> Tool calls and selected data will be sent to this remote origin.') +
|
|
2302
|
+
'</div>' +
|
|
2303
|
+
(installed ? '<div class="mcp-replace-note">Installing again replaces the existing <code>' + escHtml(preset.serverName) + '</code> entry in this scope.</div>' : '') +
|
|
2304
|
+
(credentials || '<p class="mcp-no-credentials">No credentials required.</p>') +
|
|
2305
|
+
'<p class="mcp-secret-note">These values are stored in host-private settings and are not shown to the model or sandbox.</p>' +
|
|
2306
|
+
'<a class="mcp-setup-link" href="' + escAttr(preset.setupUrl) + '" target="_blank" rel="noopener">Setup documentation ↗</a>';
|
|
2307
|
+
document.getElementById('mcp-install-dialog').showModal();
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function closeMcpInstall() {
|
|
2311
|
+
pendingMcpInstall = null;
|
|
2312
|
+
document.getElementById('mcp-install-dialog').close();
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
async function installMcpPreset(btn) {
|
|
2316
|
+
if (!pendingMcpInstall) return;
|
|
2317
|
+
const { scope, preset } = pendingMcpInstall;
|
|
2318
|
+
const credentials = {};
|
|
2319
|
+
document.querySelectorAll('[data-mcp-credential]').forEach((input) => {
|
|
2320
|
+
const descriptor = preset.credentials[Number(input.dataset.mcpCredential)];
|
|
2321
|
+
if (descriptor) credentials[descriptor.key] = input.value;
|
|
2322
|
+
});
|
|
2323
|
+
btn.disabled = true;
|
|
2324
|
+
btn.textContent = 'Installing…';
|
|
2325
|
+
try {
|
|
2326
|
+
await apiPost('/admin/api/mcp-servers/mutate', {
|
|
2327
|
+
action: 'install', scope: scope, presetId: preset.id, credentials: credentials, ...scopeBody(),
|
|
2328
|
+
});
|
|
2329
|
+
closeMcpInstall();
|
|
2330
|
+
mcpMessage(scope, preset.name + ' installed. It will be available on the next response.', 'ok');
|
|
2331
|
+
await loadMcpServers();
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
const dialogError = document.getElementById('mcp-dialog-error');
|
|
2334
|
+
dialogError.textContent = err.message;
|
|
2335
|
+
dialogError.style.display = 'block';
|
|
2336
|
+
} finally {
|
|
2337
|
+
btn.disabled = false;
|
|
2338
|
+
btn.textContent = 'Install preset';
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2360
2342
|
function addMcpServer(scope) {
|
|
2361
2343
|
const name = document.getElementById('mcp-' + scope + '-name').value.trim();
|
|
2362
2344
|
const target = document.getElementById('mcp-' + scope + '-target').value.trim();
|
|
@@ -2384,97 +2366,10 @@ function renderAdminPage(token) {
|
|
|
2384
2366
|
const btn = event.target.closest('[data-mcp-action]');
|
|
2385
2367
|
if (!btn) return;
|
|
2386
2368
|
if (btn.dataset.mcpAction === 'add') { addMcpServer(btn.dataset.mcpScope); return; }
|
|
2369
|
+
if (btn.dataset.mcpAction === 'preset') { openMcpPreset(btn.dataset.mcpScope, btn.dataset.mcpPreset); return; }
|
|
2387
2370
|
void mutateMcpServer(btn.dataset.mcpScope, btn.dataset.mcpAction, btn.dataset.mcpName);
|
|
2388
2371
|
});
|
|
2389
2372
|
|
|
2390
|
-
// ── Extension secrets ───────────────────────────────────────────────────────
|
|
2391
|
-
|
|
2392
|
-
function extSecretsMessage(text, kind) {
|
|
2393
|
-
const el = document.getElementById('ext-secrets-msg');
|
|
2394
|
-
if (!el) return;
|
|
2395
|
-
el.style.display = 'block';
|
|
2396
|
-
el.className = 'pkg-msg pkg-msg-' + kind;
|
|
2397
|
-
el.textContent = text;
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
function renderSecretRow(slug, key, meta) {
|
|
2401
|
-
return '<div class="pkg-row">' +
|
|
2402
|
-
'<div class="pkg-row-main"><span class="pkg-source">' + escHtml(key) + '</span>' +
|
|
2403
|
-
(meta.required ? '<span class="pkg-badge pkg-badge-warn">required</span>' : '') +
|
|
2404
|
-
(meta.provisioned
|
|
2405
|
-
? '<span class="pkg-badge pkg-badge-ok">provisioned</span>'
|
|
2406
|
-
: '<span class="pkg-badge pkg-badge-err">missing</span>') +
|
|
2407
|
-
'</div>' +
|
|
2408
|
-
(meta.description ? '<div class="skill-desc">' + escHtml(meta.description) + '</div>' : '') +
|
|
2409
|
-
'<div class="pkg-actions">' +
|
|
2410
|
-
'<button class="pkg-btn" data-secret-action="set" data-secret-slug="' + escAttr(slug) + '" data-secret-key="' + escAttr(key) + '">Set</button>' +
|
|
2411
|
-
(meta.provisioned
|
|
2412
|
-
? '<button class="pkg-btn pkg-btn-danger" data-secret-action="delete" data-secret-slug="' + escAttr(slug) + '" data-secret-key="' + escAttr(key) + '">Delete</button>'
|
|
2413
|
-
: '') +
|
|
2414
|
-
'</div>' +
|
|
2415
|
-
'</div>';
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
async function loadExtensionSecrets() {
|
|
2419
|
-
const container = document.getElementById('ext-secrets-content');
|
|
2420
|
-
if (!container) return;
|
|
2421
|
-
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2422
|
-
try {
|
|
2423
|
-
const data = await apiGet('/admin/api/extension-secrets?' + scopeQuery());
|
|
2424
|
-
if (data.extensions.length === 0) {
|
|
2425
|
-
container.innerHTML = '<div class="empty-state">No extensions installed</div>';
|
|
2426
|
-
return;
|
|
2427
|
-
}
|
|
2428
|
-
container.innerHTML = data.extensions.map((ext) => {
|
|
2429
|
-
const declared = ext.declared.map((s) => renderSecretRow(ext.slug, s.key, s)).join('');
|
|
2430
|
-
const extras = ext.extraProvisioned
|
|
2431
|
-
.map((key) => renderSecretRow(ext.slug, key, { provisioned: true }))
|
|
2432
|
-
.join('');
|
|
2433
|
-
return '<div class="pkg-group">' +
|
|
2434
|
-
'<div class="pkg-group-head"><strong>' + escHtml(ext.name) + '</strong>' +
|
|
2435
|
-
'<span class="pkg-source">' + escHtml(ext.slug) + '</span>' +
|
|
2436
|
-
'<button class="pkg-btn" data-secret-action="add" data-secret-slug="' + escAttr(ext.slug) + '">Add key</button>' +
|
|
2437
|
-
'</div>' +
|
|
2438
|
-
'<div class="pkg-list">' +
|
|
2439
|
-
(declared + extras || '<div class="pkg-provides-empty">No secrets declared</div>') +
|
|
2440
|
-
'</div>' +
|
|
2441
|
-
'</div>';
|
|
2442
|
-
}).join('');
|
|
2443
|
-
} catch (err) {
|
|
2444
|
-
container.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
document.addEventListener('click', async (event) => {
|
|
2449
|
-
const btn = event.target.closest('[data-secret-action]');
|
|
2450
|
-
if (!btn) return;
|
|
2451
|
-
const slug = btn.dataset.secretSlug;
|
|
2452
|
-
const action = btn.dataset.secretAction;
|
|
2453
|
-
let key = btn.dataset.secretKey;
|
|
2454
|
-
if (action === 'add') {
|
|
2455
|
-
key = (prompt('Secret key for ' + slug + '(e.g. SLACK_BOT_TOKEN)') || '').trim();
|
|
2456
|
-
if (!key) return;
|
|
2457
|
-
}
|
|
2458
|
-
if (action === 'delete' && !confirm('Delete ' + key + ' from ' + slug + '?')) return;
|
|
2459
|
-
let value;
|
|
2460
|
-
if (action !== 'delete') {
|
|
2461
|
-
value = prompt('Value for ' + key + '(' + slug + ')');
|
|
2462
|
-
if (!value) return;
|
|
2463
|
-
}
|
|
2464
|
-
try {
|
|
2465
|
-
await apiPost('/admin/api/extension-secrets/mutate', {
|
|
2466
|
-
action: action === 'delete' ? 'delete' : 'set',
|
|
2467
|
-
slug: slug,
|
|
2468
|
-
key: key,
|
|
2469
|
-
value: value,
|
|
2470
|
-
});
|
|
2471
|
-
extSecretsMessage('已更新。對話輸入 /pi-new 生效。', 'ok');
|
|
2472
|
-
await loadExtensionSecrets();
|
|
2473
|
-
} catch (err) {
|
|
2474
|
-
extSecretsMessage(err.message, 'err');
|
|
2475
|
-
}
|
|
2476
|
-
});
|
|
2477
|
-
|
|
2478
2373
|
async function loadSkills() {
|
|
2479
2374
|
const container = document.getElementById('skills-content');
|
|
2480
2375
|
const previewEl = document.getElementById('skills-preview');
|
|
@@ -2533,7 +2428,7 @@ function renderAdminPage(token) {
|
|
|
2533
2428
|
result.innerHTML =
|
|
2534
2429
|
'<span class="link-vault">vault: <code>' + escHtml(data.vaultId) + '</code></span>' +
|
|
2535
2430
|
'<a href="' + escAttr(data.url) + '" target="_blank" rel="noopener">' + escHtml(data.url) + '</a>' +
|
|
2536
|
-
'<button class="copy-link-btn"
|
|
2431
|
+
'<button class="copy-link-btn" data-admin-action="copy-link" data-copy-text="' + escAttr(data.url) + '">Copy</button>';
|
|
2537
2432
|
frame.src = data.url; frame.style.display = 'block';
|
|
2538
2433
|
} catch (err) {
|
|
2539
2434
|
result.className = 'link-result err'; result.textContent = err.message;
|
|
@@ -2553,7 +2448,7 @@ function renderAdminPage(token) {
|
|
|
2553
2448
|
result.className = 'link-result ok';
|
|
2554
2449
|
result.innerHTML =
|
|
2555
2450
|
'<a href="' + escAttr(data.url) + '" target="_blank" rel="noopener">' + escHtml(data.url) + '</a>' +
|
|
2556
|
-
'<button class="copy-link-btn"
|
|
2451
|
+
'<button class="copy-link-btn" data-admin-action="copy-link" data-copy-text="' + escAttr(data.url) + '">Copy</button>';
|
|
2557
2452
|
frame.src = data.url; frame.style.display = 'block';
|
|
2558
2453
|
} catch (err) {
|
|
2559
2454
|
result.className = 'link-result err'; result.textContent = err.message;
|
|
@@ -2602,7 +2497,7 @@ function renderAdminPage(token) {
|
|
|
2602
2497
|
.filter(Boolean).map(escHtml).join(' · ');
|
|
2603
2498
|
const preview = e.text ? '<div class="event-text">' + escHtml(e.text.length > 240 ? e.text.slice(0, 237) + '…' : e.text) + '</div>' : '';
|
|
2604
2499
|
const deleteBtn = allowDelete
|
|
2605
|
-
? '<button class="event-delete-btn"
|
|
2500
|
+
? '<button class="event-delete-btn" data-admin-action="delete-event" data-event-name="' + escAttr(e.name) + '">Delete</button>'
|
|
2606
2501
|
: '';
|
|
2607
2502
|
return '<div class="event-row">' +
|
|
2608
2503
|
'<div class="event-row-top">' +
|
|
@@ -2639,7 +2534,6 @@ function renderAdminPage(token) {
|
|
|
2639
2534
|
loadGlobalSettings();
|
|
2640
2535
|
loadGlobalSkills();
|
|
2641
2536
|
loadEvents();
|
|
2642
|
-
loadExtensionSecrets();
|
|
2643
2537
|
loadMcpServers();
|
|
2644
2538
|
}
|
|
2645
2539
|
|
|
@@ -2654,7 +2548,7 @@ function renderAdminPage(token) {
|
|
|
2654
2548
|
}
|
|
2655
2549
|
container.innerHTML = '<div class="conv-list">' + data.conversations.map((c) => {
|
|
2656
2550
|
const last = c.lastActivityAt ? new Date(c.lastActivityAt).toLocaleString() : '—';
|
|
2657
|
-
return '<button class="conv-row-btn"
|
|
2551
|
+
return '<button class="conv-row-btn" data-admin-action="select-conversation" data-conversation-id="' + escAttr(c.conversationId) + '">' +
|
|
2658
2552
|
'<span class="conv-id">' + escHtml(c.label || c.conversationId) + '</span>' +
|
|
2659
2553
|
(c.running ? '<span class="status-pill running">running</span>' : '') +
|
|
2660
2554
|
'<span class="conv-last">' + escHtml(last) + '</span>' +
|
|
@@ -2794,7 +2688,7 @@ function renderAdminPage(token) {
|
|
|
2794
2688
|
const legend = '<div class="tl-legend">' + TL_SERIES.map((s) => {
|
|
2795
2689
|
const cls = 'tl-legend-item' +
|
|
2796
2690
|
(active && active.key === s.key ? ' active' : (active ? ' dim' : ''));
|
|
2797
|
-
return '<span class="' + cls + '"
|
|
2691
|
+
return '<span class="' + cls + '" data-admin-action="toggle-timeline-filter" data-filter-key="' + escAttr(s.key) + '">' +
|
|
2798
2692
|
'<i class="sw ' + s.sw + '"></i>' + s.label + '</span>';
|
|
2799
2693
|
}).join('') + '</div>';
|
|
2800
2694
|
|
|
@@ -2854,12 +2748,18 @@ function renderAdminPage(token) {
|
|
|
2854
2748
|
const replyModeOpts = replyModes.map((m) =>
|
|
2855
2749
|
'<option value="' + m + '"' + (((data.slack && data.slack.replyMode) || 'top-level') === m ? ' selected' : '') + '>' + m + '</option>'
|
|
2856
2750
|
).join('');
|
|
2857
|
-
const gDoorKey = data.workspaceDoorPolicy
|
|
2858
|
-
? '
|
|
2859
|
-
: (data.
|
|
2751
|
+
const gDoorKey = !data.workspaceDoorPolicy
|
|
2752
|
+
? 'default'
|
|
2753
|
+
: (data.workspaceDoorPolicy === 'isolated'
|
|
2754
|
+
? 'isolated'
|
|
2755
|
+
: (data.workspaceLayout === 'full'
|
|
2756
|
+
? 'trusted-full'
|
|
2757
|
+
: ((data.workspaceVisibility || 'public') === 'private' ? 'trusted-shared-support-private' : 'trusted-shared-support')));
|
|
2860
2758
|
const gDoorPolicyChoices = [
|
|
2861
|
-
['
|
|
2862
|
-
['
|
|
2759
|
+
['default', 'Automatic — follows each platform channel (public shares, private reads only, DMs isolated)'],
|
|
2760
|
+
['isolated', 'isolated — own office only'],
|
|
2761
|
+
['trusted-shared-support', 'trusted / shared-support (public) — office + shared memory, skills, events — read-write'],
|
|
2762
|
+
['trusted-shared-support-private', 'trusted / shared-support (private) — same, but shared MEMORY.md is read-only'],
|
|
2863
2763
|
['trusted-full', 'trusted / full — entire workspace'],
|
|
2864
2764
|
];
|
|
2865
2765
|
const gDoorPolicyOpts = gDoorPolicyChoices.map(([value, label]) =>
|
|
@@ -3001,12 +2901,20 @@ function renderAdminPage(token) {
|
|
|
3001
2901
|
loadConversationEvents();
|
|
3002
2902
|
});
|
|
3003
2903
|
`;
|
|
2904
|
+
function renderAdminPage(token) {
|
|
2905
|
+
const userLabel = token.platformUserName ?? token.platformUserId;
|
|
2906
|
+
const script = `
|
|
2907
|
+
const adminToken = ${JSON.stringify(token.token)};
|
|
2908
|
+
// Conversation ids never contain ":" (session-key grammar), so
|
|
2909
|
+
// "platform:id" is a safe composite scope key for the UI.
|
|
2910
|
+
const defaultConversationKey = ${JSON.stringify(`${token.platform}:${token.conversationId}`)};
|
|
2911
|
+
${adminViewScript}`;
|
|
3004
2912
|
return renderPortalShell({
|
|
3005
2913
|
activeView: "admin",
|
|
3006
2914
|
pageTitle: "Admin",
|
|
3007
2915
|
identity: { primary: token.platform, secondary: userLabel },
|
|
3008
2916
|
conversationSwitcher: { currentId: token.conversationId },
|
|
3009
|
-
body,
|
|
2917
|
+
body: adminViewBody,
|
|
3010
2918
|
extraStyles: adminViewStyles,
|
|
3011
2919
|
inlineScript: script,
|
|
3012
2920
|
});
|
|
@@ -3234,6 +3142,111 @@ const adminViewStyles = `
|
|
|
3234
3142
|
}
|
|
3235
3143
|
.pkg-group-head .pkg-btn { margin-left: auto; }
|
|
3236
3144
|
|
|
3145
|
+
/* ── MCP marketplace ───────────────────────────────────────────────── */
|
|
3146
|
+
|
|
3147
|
+
.mcp-market-head {
|
|
3148
|
+
display: flex; align-items: end; justify-content: space-between; gap: 16px;
|
|
3149
|
+
margin-bottom: 12px;
|
|
3150
|
+
}
|
|
3151
|
+
.mcp-market-head h3 { margin: 0; font-size: 1rem; }
|
|
3152
|
+
.mcp-market-head p { margin: 3px 0 0; color: var(--muted); font-size: 0.8rem; }
|
|
3153
|
+
.mcp-market-head > span {
|
|
3154
|
+
color: var(--subtle); font: 500 0.72rem/1.2 'JetBrains Mono', ui-monospace, monospace;
|
|
3155
|
+
}
|
|
3156
|
+
.mcp-preset-grid {
|
|
3157
|
+
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;
|
|
3158
|
+
}
|
|
3159
|
+
.mcp-preset {
|
|
3160
|
+
min-width: 0; padding: 14px; border: 1px solid var(--border); border-radius: 12px;
|
|
3161
|
+
background:
|
|
3162
|
+
linear-gradient(145deg, rgba(255,255,255,0.85), rgba(247,245,238,0.72)),
|
|
3163
|
+
var(--card);
|
|
3164
|
+
display: flex; flex-direction: column; min-height: 190px;
|
|
3165
|
+
}
|
|
3166
|
+
.mcp-preset-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
3167
|
+
.mcp-preset-category {
|
|
3168
|
+
color: var(--accent); font-size: 0.66rem; font-weight: 700;
|
|
3169
|
+
letter-spacing: 0.09em; text-transform: uppercase;
|
|
3170
|
+
}
|
|
3171
|
+
.mcp-preset h3 { margin: 14px 0 5px; font-size: 1.06rem; letter-spacing: -0.02em; }
|
|
3172
|
+
.mcp-preset p { margin: 0; color: var(--muted); font-size: 0.8rem; line-height: 1.5; }
|
|
3173
|
+
.mcp-preset-meta {
|
|
3174
|
+
margin-top: 12px; color: var(--subtle); font-size: 0.72rem;
|
|
3175
|
+
}
|
|
3176
|
+
.mcp-preset-meta code { color: var(--text); background: transparent; }
|
|
3177
|
+
.mcp-preset-actions {
|
|
3178
|
+
margin-top: auto; padding-top: 16px; display: flex; align-items: center;
|
|
3179
|
+
justify-content: space-between; gap: 10px;
|
|
3180
|
+
}
|
|
3181
|
+
.mcp-preset-actions a, .mcp-setup-link {
|
|
3182
|
+
color: var(--muted); font-size: 0.76rem; text-decoration: none;
|
|
3183
|
+
}
|
|
3184
|
+
.mcp-preset-actions a:hover, .mcp-setup-link:hover { color: var(--text); text-decoration: underline; }
|
|
3185
|
+
.mcp-preset-actions .primary-action-btn { padding: 6px 12px; }
|
|
3186
|
+
.mcp-installed, .mcp-manual {
|
|
3187
|
+
margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px;
|
|
3188
|
+
}
|
|
3189
|
+
.mcp-installed > summary, .mcp-manual > summary {
|
|
3190
|
+
cursor: pointer; color: var(--muted); font-size: 0.8rem; font-weight: 600;
|
|
3191
|
+
margin-bottom: 10px;
|
|
3192
|
+
}
|
|
3193
|
+
.mcp-manual .pkg-add { margin: 12px 0 0; }
|
|
3194
|
+
|
|
3195
|
+
.mcp-dialog {
|
|
3196
|
+
width: min(620px, calc(100vw - 28px)); max-height: calc(100vh - 40px);
|
|
3197
|
+
border: 1px solid var(--border); border-radius: 18px; padding: 22px;
|
|
3198
|
+
color: var(--text); background: #fbfaf6; box-shadow: 0 28px 90px rgba(18,18,16,0.24);
|
|
3199
|
+
}
|
|
3200
|
+
.mcp-dialog::backdrop { background: rgba(20,20,18,0.5); backdrop-filter: blur(3px); }
|
|
3201
|
+
.mcp-dialog-head {
|
|
3202
|
+
display: flex; align-items: flex-start; justify-content: space-between; gap: 18px;
|
|
3203
|
+
padding-bottom: 14px; border-bottom: 1px solid var(--border);
|
|
3204
|
+
}
|
|
3205
|
+
.mcp-dialog-head .card-title { margin: 4px 0 0; }
|
|
3206
|
+
.mcp-dialog-close {
|
|
3207
|
+
border: 0; background: transparent; color: var(--muted); cursor: pointer;
|
|
3208
|
+
font-size: 1.6rem; line-height: 1; padding: 2px 5px;
|
|
3209
|
+
}
|
|
3210
|
+
.mcp-dialog-desc { color: var(--muted); line-height: 1.55; font-size: 0.88rem; }
|
|
3211
|
+
.mcp-install-preview {
|
|
3212
|
+
display: grid; gap: 6px; padding: 12px; border-radius: 10px;
|
|
3213
|
+
background: #1d211f; color: #f5f2e9;
|
|
3214
|
+
}
|
|
3215
|
+
.mcp-install-preview span {
|
|
3216
|
+
color: #aeb8b1; font-size: 0.66rem; font-weight: 700;
|
|
3217
|
+
letter-spacing: 0.08em; text-transform: uppercase;
|
|
3218
|
+
}
|
|
3219
|
+
.mcp-install-preview code {
|
|
3220
|
+
color: #f5f2e9; background: transparent; word-break: break-all; font-size: 0.78rem;
|
|
3221
|
+
}
|
|
3222
|
+
.mcp-security-note, .mcp-replace-note {
|
|
3223
|
+
margin-top: 12px; padding: 10px 12px; border-radius: 9px;
|
|
3224
|
+
font-size: 0.78rem; line-height: 1.5;
|
|
3225
|
+
}
|
|
3226
|
+
.mcp-security-note.local {
|
|
3227
|
+
background: rgba(220,38,38,0.08); border: 1px solid rgba(185,28,28,0.18); color: #991b1b;
|
|
3228
|
+
}
|
|
3229
|
+
.mcp-security-note.remote {
|
|
3230
|
+
background: rgba(59,130,246,0.08); border: 1px solid rgba(29,78,216,0.16); color: #1e40af;
|
|
3231
|
+
}
|
|
3232
|
+
.mcp-replace-note { background: rgba(217,119,6,0.1); color: #92400e; }
|
|
3233
|
+
.mcp-credential { display: grid; gap: 5px; margin-top: 14px; }
|
|
3234
|
+
.mcp-credential span { font-size: 0.78rem; font-weight: 650; }
|
|
3235
|
+
.mcp-credential input {
|
|
3236
|
+
width: 100%; padding: 9px 10px; border: 1px solid var(--border); border-radius: 8px;
|
|
3237
|
+
background: #fff; color: var(--text); font: 0.8rem/1.3 'JetBrains Mono', ui-monospace, monospace;
|
|
3238
|
+
}
|
|
3239
|
+
.mcp-credential small, .mcp-secret-note, .mcp-no-credentials {
|
|
3240
|
+
color: var(--subtle); font-size: 0.72rem; line-height: 1.45;
|
|
3241
|
+
}
|
|
3242
|
+
.mcp-secret-note { margin: 14px 0 8px; }
|
|
3243
|
+
.mcp-no-credentials { margin: 14px 0 0; }
|
|
3244
|
+
#mcp-dialog-error { margin-top: 14px; }
|
|
3245
|
+
.mcp-dialog-actions {
|
|
3246
|
+
display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px;
|
|
3247
|
+
padding-top: 14px; border-top: 1px solid var(--border);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3237
3250
|
/* ── Events ─────────────────────────────────────────────────────────── */
|
|
3238
3251
|
|
|
3239
3252
|
.events-list { display: flex; flex-direction: column; gap: 8px; }
|
|
@@ -3368,6 +3381,8 @@ const adminViewStyles = `
|
|
|
3368
3381
|
.portal-frame { min-height: 520px; }
|
|
3369
3382
|
.workspace-split { grid-template-columns: 1fr; }
|
|
3370
3383
|
.workspace-tree, .workspace-preview { max-height: 260px; }
|
|
3384
|
+
.mcp-preset-grid { grid-template-columns: 1fr; }
|
|
3385
|
+
.mcp-dialog { padding: 18px; }
|
|
3371
3386
|
}
|
|
3372
3387
|
`;
|
|
3373
3388
|
//# sourceMappingURL=portal.js.map
|