@geminixiang/mikan 1.0.0-beta.4 → 1.0.0-beta.41
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 +714 -0
- package/README.md +99 -36
- package/dist/adapter.d.ts +11 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +18 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +6 -7
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +88 -122
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/components.d.ts +33 -0
- package/dist/adapters/discord/components.d.ts.map +1 -0
- package/dist/adapters/discord/components.js +32 -0
- package/dist/adapters/discord/components.js.map +1 -0
- package/dist/adapters/discord/context.d.ts +3 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +66 -217
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/format.d.ts +3 -0
- package/dist/adapters/discord/format.d.ts.map +1 -0
- package/dist/adapters/discord/format.js +86 -0
- package/dist/adapters/discord/format.js.map +1 -0
- package/dist/adapters/github/bot.d.ts +89 -0
- package/dist/adapters/github/bot.d.ts.map +1 -0
- package/dist/adapters/github/bot.js +653 -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 +12 -0
- package/dist/adapters/github/context.d.ts.map +1 -0
- package/dist/adapters/github/context.js +101 -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 +91 -0
- package/dist/adapters/github/github-ops.d.ts.map +1 -0
- package/dist/adapters/github/github-ops.js +406 -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 +57 -0
- package/dist/adapters/github/ids.js.map +1 -0
- package/dist/adapters/github/repo.d.ts +33 -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 +373 -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 +86 -23
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/markdown-tables.d.ts +37 -0
- package/dist/adapters/markdown-tables.d.ts.map +1 -0
- package/dist/adapters/markdown-tables.js +164 -0
- package/dist/adapters/markdown-tables.js.map +1 -0
- 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 +440 -0
- package/dist/adapters/progressive-renderer.js.map +1 -0
- package/dist/adapters/shared.d.ts +23 -10
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +60 -22
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/assistant.d.ts +101 -0
- package/dist/adapters/slack/assistant.d.ts.map +1 -0
- package/dist/adapters/slack/assistant.js +204 -0
- package/dist/adapters/slack/assistant.js.map +1 -0
- package/dist/adapters/slack/blocks.d.ts +14 -0
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +105 -147
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +95 -13
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +545 -211
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +3 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +11 -17
- 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 +197 -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 +7 -11
- package/dist/adapters/slack/session.js.map +1 -1
- package/dist/adapters/slack/stream-limits.d.ts +35 -0
- package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
- package/dist/adapters/slack/stream-limits.js +56 -0
- package/dist/adapters/slack/stream-limits.js.map +1 -0
- 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/attach.js +1 -1
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- 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 +40 -2
- 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 +8 -9
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +115 -163
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +2 -0
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +53 -188
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/types.d.ts +132 -12
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent-events.d.ts +3 -2
- package/dist/agent-events.d.ts.map +1 -1
- package/dist/agent-events.js +19 -1
- package/dist/agent-events.js.map +1 -1
- package/dist/agent.d.ts +14 -19
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +841 -298
- 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 +61 -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 +117 -0
- package/dist/cli/boot.js.map +1 -0
- package/dist/cli/download.d.ts.map +1 -0
- package/dist/{download.js → cli/download.js} +2 -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 +188 -0
- package/dist/cli/ext-dev.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 +324 -0
- package/dist/cli/ext.js.map +1 -0
- package/dist/cli/office.d.ts +2 -0
- package/dist/cli/office.d.ts.map +1 -0
- package/dist/cli/office.js +91 -0
- package/dist/cli/office.js.map +1 -0
- package/dist/cli/types.d.ts +16 -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 +3 -2
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +5 -9
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/extensions.d.ts +18 -6
- package/dist/commands/extensions.d.ts.map +1 -1
- package/dist/commands/extensions.js +64 -14
- 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 +40 -5
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/manifest.d.ts +37 -0
- package/dist/commands/manifest.d.ts.map +1 -0
- package/dist/commands/manifest.js +151 -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 +63 -24
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +15 -3
- 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 +83 -33
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/session-view.d.ts.map +1 -1
- package/dist/commands/session-view.js +6 -3
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +79 -7
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +23 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +222 -135
- package/dist/config.js.map +1 -1
- package/dist/env-manifest.d.ts +30 -0
- package/dist/env-manifest.d.ts.map +1 -0
- package/dist/env-manifest.js +233 -0
- package/dist/env-manifest.js.map +1 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +20 -81
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +7 -8
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +106 -110
- package/dist/execution-resolver.js.map +1 -1
- package/dist/extension-schedules.d.ts +31 -0
- package/dist/extension-schedules.d.ts.map +1 -0
- package/dist/extension-schedules.js +229 -0
- package/dist/extension-schedules.js.map +1 -0
- package/dist/harness/auth.d.ts +2 -1
- package/dist/harness/auth.d.ts.map +1 -1
- package/dist/harness/auth.js +11 -5
- package/dist/harness/auth.js.map +1 -1
- package/dist/harness/event-format.d.ts +27 -0
- package/dist/harness/event-format.d.ts.map +1 -0
- package/dist/harness/event-format.js +160 -0
- package/dist/harness/event-format.js.map +1 -0
- package/dist/harness/extensions/loader.d.ts +27 -49
- package/dist/harness/extensions/loader.d.ts.map +1 -1
- package/dist/harness/extensions/loader.js +486 -130
- package/dist/harness/extensions/loader.js.map +1 -1
- package/dist/harness/extensions/registry.d.ts +107 -2
- package/dist/harness/extensions/registry.d.ts.map +1 -1
- package/dist/harness/extensions/registry.js +316 -0
- package/dist/harness/extensions/registry.js.map +1 -1
- package/dist/harness/extensions/types.d.ts +482 -42
- 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 +46 -24
- 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 +232 -46
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session-store.d.ts +11 -5
- package/dist/harness/session-store.d.ts.map +1 -1
- package/dist/harness/session-store.js +141 -36
- 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 +12 -1
- package/dist/harness/settings.js.map +1 -1
- package/dist/harness/skills.d.ts +10 -29
- package/dist/harness/skills.d.ts.map +1 -1
- package/dist/harness/skills.js +48 -6
- 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 +299 -0
- package/dist/harness/subagent-profiles.js.map +1 -0
- package/dist/harness/subagent-runner.d.ts +45 -0
- package/dist/harness/subagent-runner.d.ts.map +1 -0
- package/dist/harness/subagent-runner.js +680 -0
- package/dist/harness/subagent-runner.js.map +1 -0
- package/dist/harness/types.d.ts +267 -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 +36 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -6
- package/dist/index.js.map +1 -1
- package/dist/main.js +391 -127
- package/dist/main.js.map +1 -1
- package/dist/observability/instrument.js +7 -5
- package/dist/observability/instrument.js.map +1 -1
- package/dist/observability/sentry.d.ts +2 -0
- package/dist/observability/sentry.d.ts.map +1 -1
- package/dist/observability/sentry.js +10 -2
- package/dist/observability/sentry.js.map +1 -1
- package/dist/office/address.d.ts +31 -0
- package/dist/office/address.d.ts.map +1 -0
- package/dist/office/address.js +113 -0
- package/dist/office/address.js.map +1 -0
- package/dist/office/index.d.ts +11 -0
- package/dist/office/index.d.ts.map +1 -0
- package/dist/office/index.js +11 -0
- package/dist/office/index.js.map +1 -0
- package/dist/office/layout.d.ts +27 -0
- package/dist/office/layout.d.ts.map +1 -0
- package/dist/office/layout.js +97 -0
- package/dist/office/layout.js.map +1 -0
- package/dist/office/migration.d.ts +44 -0
- package/dist/office/migration.d.ts.map +1 -0
- package/dist/office/migration.js +255 -0
- package/dist/office/migration.js.map +1 -0
- package/dist/office/registry.d.ts +75 -0
- package/dist/office/registry.d.ts.map +1 -0
- package/dist/office/registry.js +646 -0
- package/dist/office/registry.js.map +1 -0
- package/dist/office/types.d.ts +71 -0
- package/dist/office/types.d.ts.map +1 -0
- package/dist/office/types.js +2 -0
- package/dist/office/types.js.map +1 -0
- package/dist/packages/admin.d.ts +17 -0
- package/dist/packages/admin.d.ts.map +1 -0
- package/dist/packages/admin.js +98 -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 +98 -0
- package/dist/packages/inspect.js.map +1 -0
- package/dist/packages/materialize.d.ts +40 -0
- package/dist/packages/materialize.d.ts.map +1 -0
- package/dist/packages/materialize.js +297 -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 +208 -0
- package/dist/packages/resolve.js.map +1 -0
- package/dist/packages/source.d.ts +39 -0
- package/dist/packages/source.d.ts.map +1 -0
- package/dist/packages/source.js +189 -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 +66 -0
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +335 -13
- 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 +562 -118
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +38 -0
- package/dist/runtime/session-lifecycle.d.ts.map +1 -0
- package/dist/runtime/session-lifecycle.js +193 -0
- package/dist/runtime/session-lifecycle.js.map +1 -0
- package/dist/runtime/types.d.ts +66 -7
- 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 +13 -1
- 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 +13 -3
- 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 +13 -2
- 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 +3 -0
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +18 -3
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +44 -0
- package/dist/sandbox/identity.d.ts.map +1 -0
- package/dist/sandbox/identity.js +68 -0
- package/dist/sandbox/identity.js.map +1 -0
- package/dist/sandbox/image.d.ts.map +1 -1
- package/dist/sandbox/image.js +2 -0
- package/dist/sandbox/image.js.map +1 -1
- package/dist/sandbox/index.d.ts +17 -4
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +37 -3
- package/dist/sandbox/index.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 +14 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +68 -1
- 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} +135 -131
- 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 +50 -0
- package/dist/sessions/conversation-log.js.map +1 -0
- package/dist/sessions/history-line.d.ts +29 -0
- package/dist/sessions/history-line.d.ts.map +1 -0
- package/dist/sessions/history-line.js +45 -0
- package/dist/sessions/history-line.js.map +1 -0
- 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 +33 -0
- package/dist/sessions/session-key.d.ts.map +1 -0
- package/dist/sessions/session-key.js +115 -0
- package/dist/sessions/session-key.js.map +1 -0
- package/dist/sessions/store.d.ts +15 -7
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +127 -36
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +19 -5
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/settings-mutation.d.ts +40 -0
- package/dist/settings-mutation.d.ts.map +1 -0
- package/dist/settings-mutation.js +61 -0
- package/dist/settings-mutation.js.map +1 -0
- package/dist/subagent-progress.d.ts +39 -0
- package/dist/subagent-progress.d.ts.map +1 -0
- package/dist/subagent-progress.js +225 -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 +26 -18
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +14 -13
- 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 +58 -45
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/generate-image.d.ts +23 -0
- package/dist/tools/generate-image.d.ts.map +1 -0
- package/dist/tools/generate-image.js +65 -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 +19 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +25 -2
- 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 +39 -33
- 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 +61 -0
- package/dist/tools/subagent.d.ts.map +1 -0
- package/dist/tools/subagent.js +434 -0
- package/dist/tools/subagent.js.map +1 -0
- package/dist/tools/types.d.ts +40 -32
- 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 +2 -1
- package/dist/trigger.d.ts.map +1 -1
- package/dist/trigger.js +6 -9
- package/dist/trigger.js.map +1 -1
- package/dist/types.d.ts +411 -59
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/utils/file-guards.d.ts +9 -0
- package/dist/utils/file-guards.d.ts.map +1 -1
- package/dist/utils/file-guards.js +44 -1
- package/dist/utils/file-guards.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 +25 -0
- package/dist/vault/disabled.js.map +1 -0
- package/dist/vault/index.d.ts +45 -2
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +127 -36
- package/dist/vault/index.js.map +1 -1
- package/dist/vault/types.d.ts +6 -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 +31 -3
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +850 -345
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/provider-models.js +1 -1
- package/dist/web/admin/provider-models.js.map +1 -1
- package/dist/web/admin/types.d.ts +23 -3
- package/dist/web/admin/types.d.ts.map +1 -1
- package/dist/web/admin/types.js.map +1 -1
- package/dist/web/login/oauth.d.ts +2 -3
- package/dist/web/login/oauth.d.ts.map +1 -1
- package/dist/web/login/oauth.js +1 -32
- package/dist/web/login/oauth.js.map +1 -1
- package/dist/web/login/portal.d.ts +1 -1
- package/dist/web/login/portal.d.ts.map +1 -1
- package/dist/web/login/portal.js +13 -36
- 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 +21 -0
- package/dist/web/portal-shell.d.ts.map +1 -0
- package/dist/{portal-shell.js → web/portal-shell.js} +67 -2
- package/dist/web/portal-shell.js.map +1 -0
- package/dist/web/server.d.ts +3 -2
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +11 -4
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-view/portal.d.ts +1 -1
- package/dist/web/session-view/portal.d.ts.map +1 -1
- package/dist/web/session-view/portal.js +37 -28
- package/dist/web/session-view/portal.js.map +1 -1
- package/dist/web/session-view/service.d.ts +1 -1
- package/dist/web/session-view/service.d.ts.map +1 -1
- package/dist/web/session-view/service.js +61 -18
- package/dist/web/session-view/service.js.map +1 -1
- package/dist/web/session-view/types.d.ts +0 -3
- package/dist/web/session-view/types.d.ts.map +1 -1
- package/dist/web/session-view/types.js.map +1 -1
- package/dist/web/token-store.js +1 -1
- package/dist/web/token-store.js.map +1 -1
- package/dist/workspace-projection/index.d.ts +10 -0
- package/dist/workspace-projection/index.d.ts.map +1 -0
- package/dist/workspace-projection/index.js +120 -0
- package/dist/workspace-projection/index.js.map +1 -0
- package/dist/workspace-projection/types.d.ts +16 -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 +68 -37
- 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/adapters/telegram/html.d.ts +0 -3
- package/dist/adapters/telegram/html.d.ts.map +0 -1
- package/dist/adapters/telegram/html.js +0 -98
- package/dist/adapters/telegram/html.js.map +0 -1
- package/dist/commands/parse.d.ts +0 -7
- package/dist/commands/parse.d.ts.map +0 -1
- package/dist/commands/parse.js +0 -14
- package/dist/commands/parse.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/sandbox/path-context.d.ts +0 -3
- package/dist/sandbox/path-context.d.ts.map +0 -1
- package/dist/sandbox/path-context.js +0 -20
- package/dist/sandbox/path-context.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/sessions/metadata.d.ts +0 -3
- package/dist/sessions/metadata.d.ts.map +0 -1
- package/dist/sessions/metadata.js +0 -11
- package/dist/sessions/metadata.js.map +0 -1
- package/dist/sessions/policy.d.ts +0 -6
- package/dist/sessions/policy.d.ts.map +0 -1
- package/dist/sessions/policy.js +0 -23
- package/dist/sessions/policy.js.map +0 -1
- package/dist/store.d.ts +0 -46
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -177
- package/dist/store.js.map +0 -1
- package/dist/tools/truncate.d.ts +0 -33
- package/dist/tools/truncate.d.ts.map +0 -1
- package/dist/tools/truncate.js +0 -184
- package/dist/tools/truncate.js.map +0 -1
- package/dist/utils/date.d.ts +0 -10
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/date.js +0 -23
- package/dist/utils/date.js.map +0 -1
- package/dist/utils/env.d.ts +0 -3
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/env.js +0 -12
- package/dist/utils/env.js.map +0 -1
- package/dist/utils/fs-atomic.d.ts +0 -10
- package/dist/utils/fs-atomic.d.ts.map +0 -1
- package/dist/utils/fs-atomic.js +0 -45
- package/dist/utils/fs-atomic.js.map +0 -1
- package/dist/utils/html.d.ts +0 -2
- package/dist/utils/html.d.ts.map +0 -1
- package/dist/utils/html.js +0 -4
- package/dist/utils/html.js.map +0 -1
- package/dist/utils/http-body.d.ts +0 -10
- package/dist/utils/http-body.d.ts.map +0 -1
- package/dist/utils/http-body.js +0 -34
- package/dist/utils/http-body.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/web/agent-events/portal.d.ts +0 -3
- package/dist/web/agent-events/portal.d.ts.map +0 -1
- package/dist/web/agent-events/portal.js +0 -18
- package/dist/web/agent-events/portal.js.map +0 -1
- package/dist/web/request.d.ts +0 -3
- package/dist/web/request.d.ts.map +0 -1
- package/dist/web/request.js +0 -13
- package/dist/web/request.js.map +0 -1
- package/dist/web/session-view/command.d.ts +0 -4
- package/dist/web/session-view/command.d.ts.map +0 -1
- package/dist/web/session-view/command.js +0 -7
- package/dist/web/session-view/command.js.map +0 -1
- /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
package/dist/web/admin/portal.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync,
|
|
2
|
-
import { basename, join, resolve as pathResolve, sep as pathSep } from "path";
|
|
3
|
-
import { MikanModels, SessionStore } from "../../harness/index.js";
|
|
4
|
-
import { loadConversationAutoReplyConfig, loadGlobalSettings, resolveConversationSettings, saveConversationAutoReplyConfig,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { renderPortalShell } from "
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, join, resolve as pathResolve, sep as pathSep } from "node:path";
|
|
3
|
+
import { defaultExtensionDirs, listInstalledExtensions, MikanModels, parseFrontmatter, SessionStore, } from "../../harness/index.js";
|
|
4
|
+
import { loadConversationAutoReplyConfig, loadConversationWorkspaceOverride, loadGlobalSettings, resolveConversationSettings, saveConversationAutoReplyConfig, } from "../../config.js";
|
|
5
|
+
import { applyConversationSettings, applyConversationWorkspacePolicy, applyGlobalSettings, applyGlobalWorkspacePolicy, } from "../../settings-mutation.js";
|
|
6
|
+
import { addPackage, inspectConversationPackages, refreshPackage, removePackage, } from "../../packages/index.js";
|
|
7
|
+
import { escapeHtml, readJsonBody, renderPortalShell } from "../portal-shell.js";
|
|
8
8
|
import { resolveExistingSessionFile } from "../session-view/service.js";
|
|
9
9
|
import { PRODUCT_NAME } from "../../platform-messages.js";
|
|
10
|
-
import {
|
|
10
|
+
import { credentialAuthorizationKey } from "../../sandbox/identity.js";
|
|
11
|
+
import { resolveWorkspaceProjection } from "../../workspace-projection/index.js";
|
|
11
12
|
import { sharedVaultKey } from "../../vault/index.js";
|
|
12
13
|
import { modelKey, resolveAdminModelAccessStatuses } from "./provider-models.js";
|
|
14
|
+
import { assertPlatformName, createOfficeAddress, listRegisteredOffices, sameOffice, } from "../../office/index.js";
|
|
13
15
|
// ── Handler ────────────────────────────────────────────────────────────────────
|
|
14
16
|
export async function handleAdminRequest(req, res, url, services) {
|
|
15
17
|
if (!url.pathname.startsWith("/admin"))
|
|
@@ -87,8 +89,16 @@ async function routeApiRequest(req, res, url, services) {
|
|
|
87
89
|
serveSkillFile(res, url, services, token);
|
|
88
90
|
return;
|
|
89
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
|
+
}
|
|
90
100
|
if (url.pathname === "/admin/api/events") {
|
|
91
|
-
serveEventsList(res, services);
|
|
101
|
+
await serveEventsList(res, services);
|
|
92
102
|
return;
|
|
93
103
|
}
|
|
94
104
|
if (url.pathname === "/admin/api/events/file") {
|
|
@@ -96,7 +106,7 @@ async function routeApiRequest(req, res, url, services) {
|
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
98
108
|
if (url.pathname === "/admin/api/conversations/events") {
|
|
99
|
-
serveConversationEventsList(res, url, services, token);
|
|
109
|
+
await serveConversationEventsList(res, url, services, token);
|
|
100
110
|
return;
|
|
101
111
|
}
|
|
102
112
|
jsonRes(res, 404, { error: "Not found" });
|
|
@@ -106,7 +116,7 @@ async function routeApiRequest(req, res, url, services) {
|
|
|
106
116
|
jsonRes(res, 405, { error: "Method not allowed" });
|
|
107
117
|
return;
|
|
108
118
|
}
|
|
109
|
-
const body = await readJsonBody(req, res);
|
|
119
|
+
const body = await readJsonBody(req, res, 32 * 1024);
|
|
110
120
|
if (!body)
|
|
111
121
|
return;
|
|
112
122
|
const rawToken = typeof body.token === "string" ? body.token : "";
|
|
@@ -140,44 +150,65 @@ async function routeApiRequest(req, res, url, services) {
|
|
|
140
150
|
return;
|
|
141
151
|
}
|
|
142
152
|
if (url.pathname === "/admin/api/conversations/events/delete") {
|
|
143
|
-
serveConversationEventDelete(res, body, services, token);
|
|
153
|
+
await serveConversationEventDelete(res, body, services, token);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (url.pathname === "/admin/api/packages/mutate") {
|
|
157
|
+
servePackageMutation(res, body, services, token);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (url.pathname === "/admin/api/extension-secrets/mutate") {
|
|
161
|
+
serveExtensionSecretMutation(res, body, services);
|
|
144
162
|
return;
|
|
145
163
|
}
|
|
146
164
|
if (url.pathname === "/admin/api/settings/model") {
|
|
147
|
-
serveGlobalModelUpdate(res, body);
|
|
165
|
+
serveGlobalModelUpdate(res, body, services);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (url.pathname === "/admin/api/settings/workspace") {
|
|
169
|
+
serveGlobalWorkspaceUpdate(res, body, services);
|
|
148
170
|
return;
|
|
149
171
|
}
|
|
150
172
|
if (url.pathname === "/admin/api/settings/sandbox") {
|
|
151
|
-
serveGlobalSandboxUpdate(res, body);
|
|
173
|
+
serveGlobalSandboxUpdate(res, body, services);
|
|
152
174
|
return;
|
|
153
175
|
}
|
|
154
176
|
if (url.pathname === "/admin/api/settings/slack") {
|
|
155
|
-
serveGlobalSlackUpdate(res, body);
|
|
177
|
+
serveGlobalSlackUpdate(res, body, services);
|
|
156
178
|
return;
|
|
157
179
|
}
|
|
158
180
|
jsonRes(res, 404, { error: "Not found" });
|
|
159
181
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Admin scope is a full office address. The platform defaults to the token's
|
|
184
|
+
* (an admin invoked from Slack browses Slack offices); cross-platform targets
|
|
185
|
+
* name theirs explicitly. Identity validation is the address factory's.
|
|
186
|
+
*/
|
|
187
|
+
export function resolveConversationScope(requestedId, requestedPlatform, token) {
|
|
188
|
+
const fallback = createOfficeAddress(token.platform, token.conversationId);
|
|
189
|
+
try {
|
|
190
|
+
const address = createOfficeAddress(requestedPlatform ? assertPlatformName(requestedPlatform) : token.platform, requestedId || token.conversationId);
|
|
191
|
+
return { address, conversationId: address.conversationId };
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return {
|
|
195
|
+
address: fallback,
|
|
196
|
+
conversationId: fallback.conversationId,
|
|
197
|
+
error: "Invalid conversation scope.",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
170
200
|
}
|
|
171
201
|
function resolveTargetConversation(body, token) {
|
|
172
202
|
const requested = typeof body.conversationId === "string" ? body.conversationId.trim() : "";
|
|
173
|
-
|
|
203
|
+
const platform = typeof body.platform === "string" ? body.platform.trim() : "";
|
|
204
|
+
return resolveConversationScope(requested, platform, token);
|
|
174
205
|
}
|
|
175
|
-
function
|
|
176
|
-
if (!services.
|
|
206
|
+
function requireAdminWorkspace(res, services) {
|
|
207
|
+
if (!services.workspace) {
|
|
177
208
|
jsonRes(res, 503, { error: "Working directory not available" });
|
|
178
209
|
return null;
|
|
179
210
|
}
|
|
180
|
-
return services.
|
|
211
|
+
return services.workspace;
|
|
181
212
|
}
|
|
182
213
|
// ── API handlers ───────────────────────────────────────────────────────────────
|
|
183
214
|
function serveMe(res, token) {
|
|
@@ -189,30 +220,20 @@ function serveMe(res, token) {
|
|
|
189
220
|
expiresAt: token.expiresAt,
|
|
190
221
|
});
|
|
191
222
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.filter((
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const items = readdirSync(dir);
|
|
204
|
-
// Conversation settings migrated to the state dir, so a sessions/
|
|
205
|
-
// subdir is the primary marker for existing conversation dirs.
|
|
206
|
-
return items.some((item) => item === "sessions" || SETTINGS_FILES.has(item) || item.endsWith(".jsonl"));
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
})
|
|
212
|
-
.toSorted((a, b) => a.localeCompare(b));
|
|
223
|
+
/**
|
|
224
|
+
* Office directories are office-key named and not reversible to raw ids, so
|
|
225
|
+
* enumeration reads the office registry — the durable raw-id ↔ office
|
|
226
|
+
* mapping — instead of scanning the workspace. Offices whose directory
|
|
227
|
+
* disappeared are filtered out.
|
|
228
|
+
*/
|
|
229
|
+
function listAdminOffices(workspace) {
|
|
230
|
+
return listRegisteredOffices(workspace.stateDir)
|
|
231
|
+
.filter((office) => existsSync(workspace.office(office).dir))
|
|
232
|
+
.map((office) => createOfficeAddress(office.platform, office.conversationId))
|
|
233
|
+
.toSorted((a, b) => a.platform.localeCompare(b.platform) || a.conversationId.localeCompare(b.conversationId));
|
|
213
234
|
}
|
|
214
|
-
function conversationLastActivity(
|
|
215
|
-
const dir =
|
|
235
|
+
function conversationLastActivity(workspace, office) {
|
|
236
|
+
const dir = workspace.office(office).dir;
|
|
216
237
|
if (!existsSync(dir))
|
|
217
238
|
return null;
|
|
218
239
|
let latest = 0;
|
|
@@ -245,48 +266,43 @@ function conversationLastActivity(workingDir, conversationId) {
|
|
|
245
266
|
visit(dir, 0);
|
|
246
267
|
return latest > 0 ? latest : null;
|
|
247
268
|
}
|
|
248
|
-
function conversationDisplayLabel(services,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
return conversationId;
|
|
269
|
+
function conversationDisplayLabel(services, office) {
|
|
270
|
+
const bot = services.botsByPlatform?.[office.platform];
|
|
271
|
+
const channel = bot?.getMessagingInfo().channels.find((c) => c.id === office.conversationId);
|
|
272
|
+
if (channel)
|
|
273
|
+
return `${office.platform}:#${channel.name}:${office.conversationId}`;
|
|
274
|
+
return `${office.platform}:${office.conversationId}`;
|
|
255
275
|
}
|
|
256
276
|
function serveConversationsList(res, services) {
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
259
|
-
return;
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
running,
|
|
269
|
-
lastActivityAt: lastActivity,
|
|
270
|
-
};
|
|
271
|
-
});
|
|
277
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
278
|
+
if (!workspace)
|
|
279
|
+
return;
|
|
280
|
+
const running = services.runtime?.getRunningSessions() ?? [];
|
|
281
|
+
const conversations = listAdminOffices(workspace).map((office) => ({
|
|
282
|
+
platform: office.platform,
|
|
283
|
+
conversationId: office.conversationId,
|
|
284
|
+
label: conversationDisplayLabel(services, office),
|
|
285
|
+
running: running.some((session) => sameOffice(session.address, office)),
|
|
286
|
+
lastActivityAt: conversationLastActivity(workspace, office),
|
|
287
|
+
}));
|
|
272
288
|
jsonRes(res, 200, { conversations });
|
|
273
289
|
}
|
|
274
290
|
function serveSessionUsage(res, services) {
|
|
275
|
-
const
|
|
276
|
-
if (!
|
|
291
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
292
|
+
if (!workspace)
|
|
277
293
|
return;
|
|
278
|
-
const rows =
|
|
279
|
-
.flatMap((
|
|
294
|
+
const rows = listAdminOffices(workspace)
|
|
295
|
+
.flatMap((office) => listConversationSessionUsage(workspace, office, conversationDisplayLabel(services, office)))
|
|
280
296
|
.toSorted((a, b) => b.total - a.total)
|
|
281
297
|
.slice(0, 20);
|
|
282
298
|
jsonRes(res, 200, { sessions: rows });
|
|
283
299
|
}
|
|
284
|
-
function listConversationSessionUsage(
|
|
285
|
-
const sessionDir =
|
|
300
|
+
function listConversationSessionUsage(workspace, office, label) {
|
|
301
|
+
const sessionDir = workspace.office(office).sessionsDir;
|
|
286
302
|
try {
|
|
287
303
|
return readdirSync(sessionDir, { withFileTypes: true })
|
|
288
304
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
289
|
-
.flatMap((entry) => readSessionUsage(join(sessionDir, entry.name), conversationId, label));
|
|
305
|
+
.flatMap((entry) => readSessionUsage(join(sessionDir, entry.name), office.conversationId, label));
|
|
290
306
|
}
|
|
291
307
|
catch {
|
|
292
308
|
return [];
|
|
@@ -346,11 +362,14 @@ function emptyBucket(date) {
|
|
|
346
362
|
}
|
|
347
363
|
/** Per-conversation daily token usage over the last N days (N clamped to 1..7). */
|
|
348
364
|
function serveConversationUsage(res, url, services) {
|
|
349
|
-
const
|
|
350
|
-
if (!
|
|
351
|
-
return;
|
|
352
|
-
const conversationId = url.searchParams.get("conversationId") ?? "";
|
|
353
|
-
|
|
365
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
366
|
+
if (!workspace)
|
|
367
|
+
return;
|
|
368
|
+
const conversationId = (url.searchParams.get("conversationId") ?? "").trim();
|
|
369
|
+
const platformParam = (url.searchParams.get("platform") ?? "").trim();
|
|
370
|
+
const office = listAdminOffices(workspace).find((candidate) => candidate.conversationId === conversationId &&
|
|
371
|
+
(!platformParam || candidate.platform === platformParam));
|
|
372
|
+
if (!office) {
|
|
354
373
|
jsonRes(res, 400, { error: "Unknown conversationId" });
|
|
355
374
|
return;
|
|
356
375
|
}
|
|
@@ -369,7 +388,7 @@ function serveConversationUsage(res, url, services) {
|
|
|
369
388
|
const cutoff = new Date(today);
|
|
370
389
|
cutoff.setDate(today.getDate() - (days - 1));
|
|
371
390
|
const flags = { hasOlder: false };
|
|
372
|
-
const sessionDir =
|
|
391
|
+
const sessionDir = workspace.office(office).sessionsDir;
|
|
373
392
|
try {
|
|
374
393
|
for (const entry of readdirSync(sessionDir, { withFileTypes: true })) {
|
|
375
394
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
|
|
@@ -391,8 +410,9 @@ function serveConversationUsage(res, url, services) {
|
|
|
391
410
|
return sum;
|
|
392
411
|
}, emptyBucket(""));
|
|
393
412
|
jsonRes(res, 200, {
|
|
413
|
+
platform: office.platform,
|
|
394
414
|
conversationId,
|
|
395
|
-
label: conversationDisplayLabel(services,
|
|
415
|
+
label: conversationDisplayLabel(services, office),
|
|
396
416
|
days,
|
|
397
417
|
hasOlder: flags.hasOlder,
|
|
398
418
|
buckets: series,
|
|
@@ -444,19 +464,21 @@ function accumulateSessionUsageByDay(sessionFile, cutoff, buckets, flags) {
|
|
|
444
464
|
}
|
|
445
465
|
}
|
|
446
466
|
function serveConversationState(res, url, services, token) {
|
|
447
|
-
const
|
|
448
|
-
if (!
|
|
467
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
468
|
+
if (!workspace)
|
|
449
469
|
return;
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
jsonRes(res, 400, { error: "Invalid conversationId" });
|
|
470
|
+
const scope = resolveConversationFromQuery(url, token);
|
|
471
|
+
if (scope.error) {
|
|
472
|
+
jsonRes(res, 400, { error: scope.error });
|
|
454
473
|
return;
|
|
455
474
|
}
|
|
456
|
-
const
|
|
475
|
+
const conversationId = scope.conversationId;
|
|
476
|
+
const office = workspace.office(scope.address);
|
|
457
477
|
const globalConfig = loadGlobalSettings();
|
|
458
|
-
const conversationConfig = resolveConversationSettings(
|
|
459
|
-
const
|
|
478
|
+
const conversationConfig = resolveConversationSettings(office);
|
|
479
|
+
const conversationWorkspace = resolveWorkspaceProjection(office);
|
|
480
|
+
const globalWorkspaceSettings = globalConfig.sandbox?.workspace;
|
|
481
|
+
const autoReply = loadConversationAutoReplyConfig(office.dir);
|
|
460
482
|
jsonRes(res, 200, {
|
|
461
483
|
conversationId,
|
|
462
484
|
provider: conversationConfig.provider,
|
|
@@ -465,8 +487,11 @@ function serveConversationState(res, url, services, token) {
|
|
|
465
487
|
globalProvider: globalConfig.provider,
|
|
466
488
|
globalModel: globalConfig.model,
|
|
467
489
|
globalThinkingLevel: globalConfig.thinkingLevel,
|
|
468
|
-
|
|
469
|
-
|
|
490
|
+
workspaceDoorPolicy: conversationWorkspace.doorPolicy,
|
|
491
|
+
workspaceLayout: conversationWorkspace.layout,
|
|
492
|
+
workspaceOverride: doorPolicyChoiceKey(loadConversationWorkspaceOverride(office)),
|
|
493
|
+
globalWorkspaceDoorPolicy: globalWorkspaceSettings?.doorPolicy ?? "isolated",
|
|
494
|
+
globalWorkspaceLayout: globalWorkspaceSettings?.layout ?? "conversation",
|
|
470
495
|
autoReplyEnabled: autoReply.enabled,
|
|
471
496
|
autoReplyRules: autoReply.rules,
|
|
472
497
|
slack: {
|
|
@@ -482,12 +507,13 @@ function serveGlobalSettings(res) {
|
|
|
482
507
|
provider: config.provider,
|
|
483
508
|
model: config.model,
|
|
484
509
|
thinkingLevel: config.thinkingLevel,
|
|
485
|
-
sandboxCpus: config.
|
|
486
|
-
sandboxMemory: config.
|
|
487
|
-
sandboxBoostCpus: config.
|
|
488
|
-
sandboxBoostMemory: config.
|
|
489
|
-
|
|
490
|
-
|
|
510
|
+
sandboxCpus: config.sandbox?.cpus ?? null,
|
|
511
|
+
sandboxMemory: config.sandbox?.memory ?? null,
|
|
512
|
+
sandboxBoostCpus: config.sandbox?.boost?.cpus ?? null,
|
|
513
|
+
sandboxBoostMemory: config.sandbox?.boost?.memory ?? null,
|
|
514
|
+
workspaceDoorPolicy: config.sandbox?.workspace?.doorPolicy ?? "isolated",
|
|
515
|
+
workspaceLayout: config.sandbox?.workspace?.layout ?? "conversation",
|
|
516
|
+
defaultSharedVault: config.sandbox?.defaultSharedVault ?? null,
|
|
491
517
|
slack: {
|
|
492
518
|
replyMode: config.slack?.replyMode ?? "top-level",
|
|
493
519
|
},
|
|
@@ -518,7 +544,7 @@ async function serveModelsList(res) {
|
|
|
518
544
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
519
545
|
}
|
|
520
546
|
}
|
|
521
|
-
const VALID_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
|
|
547
|
+
const VALID_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
522
548
|
function serveConversationModelUpdate(res, body, services, token) {
|
|
523
549
|
const provider = typeof body.provider === "string" ? body.provider.trim() : "";
|
|
524
550
|
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
@@ -534,30 +560,55 @@ function serveConversationModelUpdate(res, body, services, token) {
|
|
|
534
560
|
jsonRes(res, 403, { error: scope.error });
|
|
535
561
|
return;
|
|
536
562
|
}
|
|
537
|
-
const
|
|
538
|
-
if (!
|
|
563
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
564
|
+
if (!workspace)
|
|
539
565
|
return;
|
|
540
|
-
const dir = join(workingDir, scope.conversationId);
|
|
541
566
|
try {
|
|
542
|
-
|
|
567
|
+
const result = applyConversationSettings(services.runtime, workspace.office(scope.address), {
|
|
543
568
|
provider,
|
|
544
569
|
model,
|
|
545
570
|
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
546
571
|
});
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
572
|
+
if (!result.ok) {
|
|
573
|
+
jsonRes(res, 409, {
|
|
574
|
+
error: "Conversation has a running job; retry after it finishes (or /stop it).",
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
550
577
|
}
|
|
551
|
-
jsonRes(res, 200, { ok: true, runtimeSwitched });
|
|
578
|
+
jsonRes(res, 200, { ok: true, runtimeSwitched: result.runtimeSwitched });
|
|
552
579
|
}
|
|
553
580
|
catch (err) {
|
|
554
581
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
555
582
|
}
|
|
556
583
|
}
|
|
584
|
+
/** Wire values for a door-policy selection; "default" clears the office's override. */
|
|
585
|
+
function parseDoorPolicyChoice(value) {
|
|
586
|
+
switch (value) {
|
|
587
|
+
case "default":
|
|
588
|
+
return { choice: null };
|
|
589
|
+
case "isolated":
|
|
590
|
+
return { choice: { doorPolicy: "isolated" } };
|
|
591
|
+
case "trusted-shared-support":
|
|
592
|
+
return { choice: { doorPolicy: "trusted", layout: "shared-support" } };
|
|
593
|
+
case "trusted-full":
|
|
594
|
+
return { choice: { doorPolicy: "trusted", layout: "full" } };
|
|
595
|
+
default:
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function doorPolicyChoiceKey(choice) {
|
|
600
|
+
if (!choice)
|
|
601
|
+
return "default";
|
|
602
|
+
if (choice.doorPolicy === "isolated")
|
|
603
|
+
return "isolated";
|
|
604
|
+
return choice.layout === "full" ? "trusted-full" : "trusted-shared-support";
|
|
605
|
+
}
|
|
557
606
|
function serveConversationSandboxUpdate(res, body, services, token) {
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
560
|
-
jsonRes(res, 400, {
|
|
607
|
+
const parsed = parseDoorPolicyChoice(body.doorPolicy);
|
|
608
|
+
if (!parsed) {
|
|
609
|
+
jsonRes(res, 400, {
|
|
610
|
+
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
|
|
611
|
+
});
|
|
561
612
|
return;
|
|
562
613
|
}
|
|
563
614
|
const scope = resolveTargetConversation(body, token);
|
|
@@ -565,18 +616,37 @@ function serveConversationSandboxUpdate(res, body, services, token) {
|
|
|
565
616
|
jsonRes(res, 403, { error: scope.error });
|
|
566
617
|
return;
|
|
567
618
|
}
|
|
568
|
-
const
|
|
569
|
-
if (!
|
|
619
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
620
|
+
if (!workspace)
|
|
570
621
|
return;
|
|
571
|
-
const dir = join(workingDir, scope.conversationId);
|
|
572
622
|
try {
|
|
573
|
-
|
|
623
|
+
const result = applyConversationWorkspacePolicy(services.runtime, workspace.office(scope.address), parsed.choice);
|
|
624
|
+
if (!result.ok) {
|
|
625
|
+
jsonRes(res, 409, { error: "Conversation is busy; retry when the current run finishes" });
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
574
628
|
jsonRes(res, 200, { ok: true });
|
|
575
629
|
}
|
|
576
630
|
catch (err) {
|
|
577
631
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
578
632
|
}
|
|
579
633
|
}
|
|
634
|
+
function serveGlobalWorkspaceUpdate(res, body, services) {
|
|
635
|
+
const parsed = parseDoorPolicyChoice(body.doorPolicy);
|
|
636
|
+
if (!parsed) {
|
|
637
|
+
jsonRes(res, 400, {
|
|
638
|
+
error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
|
|
639
|
+
});
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const result = applyGlobalWorkspacePolicy(services.runtime, parsed.choice);
|
|
644
|
+
jsonRes(res, 200, { ok: true, staleConversations: result.staleConversations.length });
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
580
650
|
function serveConversationSlackUpdate(res, body, services, token) {
|
|
581
651
|
const replyMode = body.replyMode;
|
|
582
652
|
if (replyMode !== "top-level" && replyMode !== "thread") {
|
|
@@ -588,12 +658,13 @@ function serveConversationSlackUpdate(res, body, services, token) {
|
|
|
588
658
|
jsonRes(res, 403, { error: scope.error });
|
|
589
659
|
return;
|
|
590
660
|
}
|
|
591
|
-
const
|
|
592
|
-
if (!
|
|
661
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
662
|
+
if (!workspace)
|
|
593
663
|
return;
|
|
594
|
-
const dir = join(workingDir, scope.conversationId);
|
|
595
664
|
try {
|
|
596
|
-
|
|
665
|
+
applyConversationSettings(services.runtime, workspace.office(scope.address), {
|
|
666
|
+
slack: { replyMode },
|
|
667
|
+
});
|
|
597
668
|
jsonRes(res, 200, { ok: true });
|
|
598
669
|
}
|
|
599
670
|
catch (err) {
|
|
@@ -613,10 +684,10 @@ function serveConversationAutoReplyUpdate(res, body, services, token) {
|
|
|
613
684
|
jsonRes(res, 403, { error: scope.error });
|
|
614
685
|
return;
|
|
615
686
|
}
|
|
616
|
-
const
|
|
617
|
-
if (!
|
|
687
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
688
|
+
if (!workspace)
|
|
618
689
|
return;
|
|
619
|
-
const dir =
|
|
690
|
+
const dir = workspace.office(scope.address).dir;
|
|
620
691
|
try {
|
|
621
692
|
const existing = loadConversationAutoReplyConfig(dir);
|
|
622
693
|
saveConversationAutoReplyConfig(dir, {
|
|
@@ -635,8 +706,8 @@ function serveConversationSessionLink(res, body, services, token) {
|
|
|
635
706
|
jsonRes(res, 403, { error: scope.error });
|
|
636
707
|
return;
|
|
637
708
|
}
|
|
638
|
-
const
|
|
639
|
-
if (!
|
|
709
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
710
|
+
if (!workspace)
|
|
640
711
|
return;
|
|
641
712
|
if (!services.sessionViewTokenStore) {
|
|
642
713
|
jsonRes(res, 503, { error: "Session view token store not available" });
|
|
@@ -648,7 +719,7 @@ function serveConversationSessionLink(res, body, services, token) {
|
|
|
648
719
|
});
|
|
649
720
|
return;
|
|
650
721
|
}
|
|
651
|
-
const sessionFile = resolveExistingSessionFile(
|
|
722
|
+
const sessionFile = resolveExistingSessionFile(workspace.office(scope.address).dir, scope.conversationId);
|
|
652
723
|
if (!sessionFile) {
|
|
653
724
|
jsonRes(res, 404, { error: "No session file found for this conversation" });
|
|
654
725
|
return;
|
|
@@ -688,7 +759,10 @@ function serveConversationLoginLink(res, body, services, token) {
|
|
|
688
759
|
}
|
|
689
760
|
else {
|
|
690
761
|
try {
|
|
691
|
-
vaultId =
|
|
762
|
+
vaultId = credentialAuthorizationKey(services.sandbox, {
|
|
763
|
+
userId: token.platformUserId,
|
|
764
|
+
address: scope.address,
|
|
765
|
+
});
|
|
692
766
|
}
|
|
693
767
|
catch (err) {
|
|
694
768
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -704,7 +778,7 @@ function serveConversationLoginLink(res, body, services, token) {
|
|
|
704
778
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
705
779
|
}
|
|
706
780
|
}
|
|
707
|
-
function serveGlobalModelUpdate(res, body) {
|
|
781
|
+
function serveGlobalModelUpdate(res, body, services) {
|
|
708
782
|
const provider = typeof body.provider === "string" ? body.provider.trim() : "";
|
|
709
783
|
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
710
784
|
const thinkingLevel = typeof body.thinkingLevel === "string" && VALID_THINKING_LEVELS.has(body.thinkingLevel)
|
|
@@ -715,55 +789,57 @@ function serveGlobalModelUpdate(res, body) {
|
|
|
715
789
|
return;
|
|
716
790
|
}
|
|
717
791
|
try {
|
|
718
|
-
|
|
792
|
+
const result = applyGlobalSettings(services.runtime, {
|
|
719
793
|
provider,
|
|
720
794
|
model,
|
|
721
795
|
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
722
796
|
});
|
|
723
|
-
jsonRes(res, 200, {
|
|
797
|
+
jsonRes(res, 200, {
|
|
798
|
+
ok: true,
|
|
799
|
+
staleConversations: result.staleConversations.map((office) => `${office.platform}:${office.conversationId}`),
|
|
800
|
+
});
|
|
724
801
|
}
|
|
725
802
|
catch (err) {
|
|
726
803
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
727
804
|
}
|
|
728
805
|
}
|
|
729
|
-
function serveGlobalSlackUpdate(res, body) {
|
|
806
|
+
function serveGlobalSlackUpdate(res, body, services) {
|
|
730
807
|
const replyMode = body.replyMode;
|
|
731
808
|
if (replyMode !== "top-level" && replyMode !== "thread") {
|
|
732
809
|
jsonRes(res, 400, { error: "replyMode must be 'top-level' or 'thread'" });
|
|
733
810
|
return;
|
|
734
811
|
}
|
|
735
812
|
try {
|
|
736
|
-
|
|
813
|
+
applyGlobalSettings(services.runtime, { slack: { replyMode } });
|
|
737
814
|
jsonRes(res, 200, { ok: true });
|
|
738
815
|
}
|
|
739
816
|
catch (err) {
|
|
740
817
|
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
741
818
|
}
|
|
742
819
|
}
|
|
743
|
-
function serveGlobalSandboxUpdate(res, body) {
|
|
820
|
+
function serveGlobalSandboxUpdate(res, body, services) {
|
|
744
821
|
const cpus = typeof body.cpus === "string" ? body.cpus.trim() : "";
|
|
745
822
|
const memory = typeof body.memory === "string" ? body.memory.trim() : "";
|
|
746
823
|
const boostCpus = typeof body.boostCpus === "string" ? body.boostCpus.trim() : "";
|
|
747
824
|
const boostMemory = typeof body.boostMemory === "string" ? body.boostMemory.trim() : "";
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
update.sandboxImageWorkspaceMount = workspaceMount;
|
|
825
|
+
const update = {
|
|
826
|
+
...(cpus ? { cpus } : {}),
|
|
827
|
+
...(memory ? { memory } : {}),
|
|
828
|
+
...(boostCpus || boostMemory
|
|
829
|
+
? {
|
|
830
|
+
boost: {
|
|
831
|
+
...(boostCpus ? { cpus: boostCpus } : {}),
|
|
832
|
+
...(boostMemory ? { memory: boostMemory } : {}),
|
|
833
|
+
},
|
|
834
|
+
}
|
|
835
|
+
: {}),
|
|
836
|
+
};
|
|
761
837
|
if (Object.keys(update).length === 0) {
|
|
762
838
|
jsonRes(res, 400, { error: "No valid sandbox fields provided" });
|
|
763
839
|
return;
|
|
764
840
|
}
|
|
765
841
|
try {
|
|
766
|
-
|
|
842
|
+
applyGlobalSettings(services.runtime, { sandbox: update });
|
|
767
843
|
jsonRes(res, 200, { ok: true });
|
|
768
844
|
}
|
|
769
845
|
catch (err) {
|
|
@@ -784,9 +860,9 @@ function isWorkspacePathAllowed(rel) {
|
|
|
784
860
|
if (rel === "")
|
|
785
861
|
return true;
|
|
786
862
|
const segments = rel.split("/").filter(Boolean);
|
|
787
|
-
if (segments.length === 0)
|
|
788
|
-
return true;
|
|
789
863
|
const first = segments[0];
|
|
864
|
+
if (first === undefined)
|
|
865
|
+
return true;
|
|
790
866
|
if (segments.length === 1) {
|
|
791
867
|
return WORKSPACE_TOP_DIRS.has(first) || WORKSPACE_TOP_FILES.has(first);
|
|
792
868
|
}
|
|
@@ -794,7 +870,8 @@ function isWorkspacePathAllowed(rel) {
|
|
|
794
870
|
}
|
|
795
871
|
function resolveConversationFromQuery(url, token) {
|
|
796
872
|
const requested = (url.searchParams.get("conversationId") ?? "").trim();
|
|
797
|
-
|
|
873
|
+
const platform = (url.searchParams.get("platform") ?? "").trim();
|
|
874
|
+
return resolveConversationScope(requested, platform, token);
|
|
798
875
|
}
|
|
799
876
|
function safeJoinUnderRoot(rootDir, relative) {
|
|
800
877
|
if (relative.startsWith("/") || relative.includes("\0")) {
|
|
@@ -880,10 +957,10 @@ function serveWorkspaceTree(res, url, services, token) {
|
|
|
880
957
|
jsonRes(res, 403, { error: scope.error });
|
|
881
958
|
return;
|
|
882
959
|
}
|
|
883
|
-
const
|
|
884
|
-
if (!
|
|
960
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
961
|
+
if (!workspace)
|
|
885
962
|
return;
|
|
886
|
-
const convDir =
|
|
963
|
+
const convDir = workspace.office(scope.address).dir;
|
|
887
964
|
if (!existsSync(convDir)) {
|
|
888
965
|
jsonRes(res, 200, { conversationId: scope.conversationId, tree: null });
|
|
889
966
|
return;
|
|
@@ -910,7 +987,7 @@ function looksTextual(buf) {
|
|
|
910
987
|
const limit = Math.min(buf.length, BINARY_PROBE_BYTES);
|
|
911
988
|
for (let i = 0; i < limit; i++) {
|
|
912
989
|
const byte = buf[i];
|
|
913
|
-
if (byte === 0)
|
|
990
|
+
if (byte === undefined || byte === 0)
|
|
914
991
|
return false;
|
|
915
992
|
if (byte < 9)
|
|
916
993
|
return false;
|
|
@@ -974,8 +1051,8 @@ function serveWorkspaceFile(res, url, services, token) {
|
|
|
974
1051
|
jsonRes(res, 403, { error: scope.error });
|
|
975
1052
|
return;
|
|
976
1053
|
}
|
|
977
|
-
const
|
|
978
|
-
if (!
|
|
1054
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1055
|
+
if (!workspace)
|
|
979
1056
|
return;
|
|
980
1057
|
const requestedPath = (url.searchParams.get("path") ?? "").trim();
|
|
981
1058
|
if (!requestedPath) {
|
|
@@ -986,7 +1063,7 @@ function serveWorkspaceFile(res, url, services, token) {
|
|
|
986
1063
|
jsonRes(res, 403, { error: "Workspace path is not exposed" });
|
|
987
1064
|
return;
|
|
988
1065
|
}
|
|
989
|
-
const convDir =
|
|
1066
|
+
const convDir = workspace.office(scope.address).dir;
|
|
990
1067
|
const safe = safeJoinUnderRoot(convDir, requestedPath);
|
|
991
1068
|
if (safe.error) {
|
|
992
1069
|
jsonRes(res, 400, { error: safe.error });
|
|
@@ -994,7 +1071,12 @@ function serveWorkspaceFile(res, url, services, token) {
|
|
|
994
1071
|
}
|
|
995
1072
|
servePreviewFile(res, safe.absolute, { path: requestedPath }, "File not found");
|
|
996
1073
|
}
|
|
997
|
-
|
|
1074
|
+
/**
|
|
1075
|
+
* Read skill metadata via the harness frontmatter parser (the owning module).
|
|
1076
|
+
* The portal historically accepted frontmatter keys case-insensitively, so
|
|
1077
|
+
* the lookup — not the parser — preserves that leniency.
|
|
1078
|
+
*/
|
|
1079
|
+
function readSkillMeta(filePath) {
|
|
998
1080
|
let text;
|
|
999
1081
|
try {
|
|
1000
1082
|
text = readFileSync(filePath, "utf-8");
|
|
@@ -1002,31 +1084,18 @@ function parseSkillFrontmatter(filePath) {
|
|
|
1002
1084
|
catch {
|
|
1003
1085
|
return {};
|
|
1004
1086
|
}
|
|
1005
|
-
|
|
1006
|
-
return {};
|
|
1007
|
-
const end = text.indexOf("\n---", 3);
|
|
1008
|
-
if (end < 0)
|
|
1009
|
-
return {};
|
|
1010
|
-
const block = text.slice(3, end);
|
|
1087
|
+
const { values } = parseFrontmatter(text);
|
|
1011
1088
|
const out = {};
|
|
1012
|
-
for (const
|
|
1013
|
-
const
|
|
1014
|
-
if (
|
|
1015
|
-
continue;
|
|
1016
|
-
const key = line.slice(0, colon).trim().toLowerCase();
|
|
1017
|
-
let value = line.slice(colon + 1).trim();
|
|
1018
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
1019
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1020
|
-
value = value.slice(1, -1);
|
|
1021
|
-
}
|
|
1022
|
-
if (key === "name")
|
|
1089
|
+
for (const [key, value] of Object.entries(values)) {
|
|
1090
|
+
const normalized = key.toLowerCase();
|
|
1091
|
+
if (normalized === "name")
|
|
1023
1092
|
out.name = value;
|
|
1024
|
-
if (
|
|
1093
|
+
if (normalized === "description")
|
|
1025
1094
|
out.description = value;
|
|
1026
1095
|
}
|
|
1027
1096
|
return out;
|
|
1028
1097
|
}
|
|
1029
|
-
function readSkillsFromDir(skillsDir, source) {
|
|
1098
|
+
export function readSkillsFromDir(skillsDir, source) {
|
|
1030
1099
|
if (!existsSync(skillsDir))
|
|
1031
1100
|
return [];
|
|
1032
1101
|
const out = [];
|
|
@@ -1043,7 +1112,7 @@ function readSkillsFromDir(skillsDir, source) {
|
|
|
1043
1112
|
const skillMd = join(skillsDir, entry.name, "SKILL.md");
|
|
1044
1113
|
if (!existsSync(skillMd))
|
|
1045
1114
|
continue;
|
|
1046
|
-
const meta =
|
|
1115
|
+
const meta = readSkillMeta(skillMd);
|
|
1047
1116
|
out.push({
|
|
1048
1117
|
name: meta.name ?? entry.name,
|
|
1049
1118
|
description: meta.description ?? "",
|
|
@@ -1054,17 +1123,157 @@ function readSkillsFromDir(skillsDir, source) {
|
|
|
1054
1123
|
}
|
|
1055
1124
|
return out.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
1056
1125
|
}
|
|
1126
|
+
/** Inventory of both scopes' declared packages for the selected conversation. */
|
|
1127
|
+
async function servePackagesList(res, url, services, token) {
|
|
1128
|
+
const scope = resolveConversationFromQuery(url, token);
|
|
1129
|
+
if (scope.error) {
|
|
1130
|
+
jsonRes(res, 403, { error: scope.error });
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1134
|
+
if (!workspace)
|
|
1135
|
+
return;
|
|
1136
|
+
try {
|
|
1137
|
+
const inventory = await inspectConversationPackages({
|
|
1138
|
+
office: workspace.office(scope.address),
|
|
1139
|
+
});
|
|
1140
|
+
jsonRes(res, 200, { conversationId: scope.conversationId, ...inventory });
|
|
1141
|
+
}
|
|
1142
|
+
catch (err) {
|
|
1143
|
+
jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const EXTENSION_SLUG_PATTERN = /^[a-z0-9_-]{1,64}$/;
|
|
1147
|
+
const EXTENSION_SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1148
|
+
/**
|
|
1149
|
+
* Declared + provisioned secrets for every extension visible to the selected
|
|
1150
|
+
* conversation (global scope plus the conversation's own installs, deduped by
|
|
1151
|
+
* slug — the vault is keyed by slug, so two scopes share one secret set).
|
|
1152
|
+
* Values never leave the server; only key names and provisioning status do.
|
|
1153
|
+
*/
|
|
1154
|
+
function serveExtensionSecretsList(res, url, services, token) {
|
|
1155
|
+
const scope = resolveConversationFromQuery(url, token);
|
|
1156
|
+
if (scope.error) {
|
|
1157
|
+
jsonRes(res, 403, { error: scope.error });
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1161
|
+
if (!workspace)
|
|
1162
|
+
return;
|
|
1163
|
+
const infosBySlug = new Map();
|
|
1164
|
+
for (const info of listInstalledExtensions(defaultExtensionDirs(scope.address, workspace.stateDir))) {
|
|
1165
|
+
infosBySlug.set(info.slug, info);
|
|
1166
|
+
}
|
|
1167
|
+
const extensions = [...infosBySlug.values()]
|
|
1168
|
+
.toSorted((left, right) => left.slug.localeCompare(right.slug))
|
|
1169
|
+
.map((info) => {
|
|
1170
|
+
const provisioned = Object.keys(services.vaultManager.resolve(`extensions/${info.slug}`)?.env ?? {}).toSorted();
|
|
1171
|
+
const declaredKeys = new Set(info.secrets.map((secret) => secret.key));
|
|
1172
|
+
return {
|
|
1173
|
+
slug: info.slug,
|
|
1174
|
+
name: info.name,
|
|
1175
|
+
declared: info.secrets.map((secret) => ({
|
|
1176
|
+
...secret,
|
|
1177
|
+
provisioned: provisioned.includes(secret.key),
|
|
1178
|
+
})),
|
|
1179
|
+
extraProvisioned: provisioned.filter((key) => !declaredKeys.has(key)),
|
|
1180
|
+
};
|
|
1181
|
+
});
|
|
1182
|
+
jsonRes(res, 200, { extensions });
|
|
1183
|
+
}
|
|
1184
|
+
/** Set or delete one extension secret. Values are write-only through here. */
|
|
1185
|
+
function serveExtensionSecretMutation(res, body, services) {
|
|
1186
|
+
const action = body.action;
|
|
1187
|
+
if (action !== "set" && action !== "delete") {
|
|
1188
|
+
jsonRes(res, 400, { error: "action must be 'set' or 'delete'" });
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
const slug = typeof body.slug === "string" ? body.slug : "";
|
|
1192
|
+
if (!EXTENSION_SLUG_PATTERN.test(slug)) {
|
|
1193
|
+
jsonRes(res, 400, { error: "invalid extension slug" });
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const key = typeof body.key === "string" ? body.key : "";
|
|
1197
|
+
if (!EXTENSION_SECRET_KEY_PATTERN.test(key)) {
|
|
1198
|
+
jsonRes(res, 400, { error: "invalid secret key (expected an env-style name)" });
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const vaultKey = `extensions/${slug}`;
|
|
1202
|
+
if (action === "set") {
|
|
1203
|
+
const value = typeof body.value === "string" ? body.value : "";
|
|
1204
|
+
if (!value) {
|
|
1205
|
+
jsonRes(res, 400, { error: "value is required" });
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
services.vaultManager.upsertEnv(vaultKey, { [key]: value });
|
|
1209
|
+
}
|
|
1210
|
+
else if (!services.vaultManager.deleteEnvKey(vaultKey, key)) {
|
|
1211
|
+
jsonRes(res, 404, { error: "Not provisioned." });
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
const provisioned = Object.keys(services.vaultManager.resolve(vaultKey)?.env ?? {}).toSorted();
|
|
1215
|
+
jsonRes(res, 200, { ok: true, provisioned });
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Add / remove / refresh, in one route because they share validation and all
|
|
1219
|
+
* three answer with the freshly re-read inventory: the panel never has to
|
|
1220
|
+
* guess what the write did.
|
|
1221
|
+
*/
|
|
1222
|
+
function servePackageMutation(res, body, services, token) {
|
|
1223
|
+
const action = body.action;
|
|
1224
|
+
if (action !== "add" && action !== "remove" && action !== "refresh") {
|
|
1225
|
+
jsonRes(res, 400, { error: "action must be 'add', 'remove', or 'refresh'" });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const packageScope = body.scope === "global" ? "global" : "conversation";
|
|
1229
|
+
const source = typeof body.source === "string" ? body.source.trim() : "";
|
|
1230
|
+
if (!source) {
|
|
1231
|
+
jsonRes(res, 400, { error: "source is required" });
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
const scope = resolveTargetConversation(body, token);
|
|
1235
|
+
if (scope.error) {
|
|
1236
|
+
jsonRes(res, 403, { error: scope.error });
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1240
|
+
if (!workspace)
|
|
1241
|
+
return;
|
|
1242
|
+
const context = {
|
|
1243
|
+
office: workspace.office(scope.address),
|
|
1244
|
+
runtime: services.runtime,
|
|
1245
|
+
};
|
|
1246
|
+
try {
|
|
1247
|
+
if (action === "add") {
|
|
1248
|
+
const result = addPackage(packageScope, source, context);
|
|
1249
|
+
jsonRes(res, 200, { ok: true, source: result.source, dir: result.dir });
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (action === "refresh") {
|
|
1253
|
+
const result = refreshPackage(packageScope, source, context);
|
|
1254
|
+
jsonRes(res, 200, { ok: true, source: result.source, dir: result.dir });
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const removed = removePackage(packageScope, source, context);
|
|
1258
|
+
jsonRes(res, removed ? 200 : 404, removed ? { ok: true } : { error: "Not declared here." });
|
|
1259
|
+
}
|
|
1260
|
+
catch (err) {
|
|
1261
|
+
// Fetch/validation failures are the admin's problem to fix, not a server
|
|
1262
|
+
// fault: report them as a bad request with git's own message.
|
|
1263
|
+
jsonRes(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1057
1266
|
function serveSkillsList(res, url, services, token) {
|
|
1058
1267
|
const scope = resolveConversationFromQuery(url, token);
|
|
1059
1268
|
if (scope.error) {
|
|
1060
1269
|
jsonRes(res, 403, { error: scope.error });
|
|
1061
1270
|
return;
|
|
1062
1271
|
}
|
|
1063
|
-
const
|
|
1064
|
-
if (!
|
|
1272
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1273
|
+
if (!workspace)
|
|
1065
1274
|
return;
|
|
1066
|
-
const global = readSkillsFromDir(
|
|
1067
|
-
const conversation = readSkillsFromDir(
|
|
1275
|
+
const global = readSkillsFromDir(workspace.skillsDir, "global");
|
|
1276
|
+
const conversation = readSkillsFromDir(workspace.office(scope.address).skillsDir, "conversation");
|
|
1068
1277
|
jsonRes(res, 200, {
|
|
1069
1278
|
conversationId: scope.conversationId,
|
|
1070
1279
|
skills: [...global, ...conversation],
|
|
@@ -1076,8 +1285,8 @@ function serveSkillFile(res, url, services, token) {
|
|
|
1076
1285
|
jsonRes(res, 403, { error: scope.error });
|
|
1077
1286
|
return;
|
|
1078
1287
|
}
|
|
1079
|
-
const
|
|
1080
|
-
if (!
|
|
1288
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1289
|
+
if (!workspace)
|
|
1081
1290
|
return;
|
|
1082
1291
|
const source = (url.searchParams.get("source") ?? "").trim();
|
|
1083
1292
|
const directory = (url.searchParams.get("directory") ?? "").trim();
|
|
@@ -1092,9 +1301,7 @@ function serveSkillFile(res, url, services, token) {
|
|
|
1092
1301
|
jsonRes(res, 400, { error: "Invalid skill directory" });
|
|
1093
1302
|
return;
|
|
1094
1303
|
}
|
|
1095
|
-
const skillsRoot = source === "global"
|
|
1096
|
-
? join(workingDir, "skills")
|
|
1097
|
-
: join(workingDir, scope.conversationId, "skills");
|
|
1304
|
+
const skillsRoot = source === "global" ? workspace.skillsDir : workspace.office(scope.address).skillsDir;
|
|
1098
1305
|
const safe = safeJoinUnderRoot(skillsRoot, join(directory, "SKILL.md"));
|
|
1099
1306
|
if (safe.error) {
|
|
1100
1307
|
jsonRes(res, 400, { error: safe.error });
|
|
@@ -1104,87 +1311,65 @@ function serveSkillFile(res, url, services, token) {
|
|
|
1104
1311
|
}
|
|
1105
1312
|
// ── Events ─────────────────────────────────────────────────────────────────────
|
|
1106
1313
|
const EVENTS_FILE_MAX_BYTES = 64 * 1024;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
return [];
|
|
1117
|
-
}
|
|
1118
|
-
return entries
|
|
1119
|
-
.filter((e) => e.isFile() && e.name.endsWith(".json"))
|
|
1120
|
-
.map((e) => {
|
|
1121
|
-
const filePath = join(dir, e.name);
|
|
1122
|
-
let stats;
|
|
1123
|
-
try {
|
|
1124
|
-
stats = statSync(filePath);
|
|
1125
|
-
}
|
|
1126
|
-
catch {
|
|
1127
|
-
return null;
|
|
1128
|
-
}
|
|
1129
|
-
let parsed = null;
|
|
1130
|
-
try {
|
|
1131
|
-
parsed = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
1132
|
-
}
|
|
1133
|
-
catch {
|
|
1134
|
-
// Keep entry; just omit parsed fields.
|
|
1135
|
-
}
|
|
1136
|
-
const meta = parsed && typeof parsed === "object" ? parsed : {};
|
|
1137
|
-
// events.ts accepts `channelId` as a legacy alias for `conversationId`.
|
|
1138
|
-
const conversationId = typeof meta.conversationId === "string"
|
|
1139
|
-
? meta.conversationId
|
|
1140
|
-
: typeof meta.channelId === "string"
|
|
1141
|
-
? meta.channelId
|
|
1142
|
-
: null;
|
|
1314
|
+
/**
|
|
1315
|
+
* List events through the owning store, whose payloads are validated by the
|
|
1316
|
+
* event-format module (files that fail validation stay visible with a null
|
|
1317
|
+
* payload so operators can delete them).
|
|
1318
|
+
*/
|
|
1319
|
+
export async function listAllEvents(store) {
|
|
1320
|
+
const entries = await store.list();
|
|
1321
|
+
return entries.map((entry) => {
|
|
1322
|
+
const payload = entry.payload;
|
|
1143
1323
|
return {
|
|
1144
|
-
name:
|
|
1145
|
-
size:
|
|
1146
|
-
mtimeMs:
|
|
1147
|
-
type:
|
|
1148
|
-
platform:
|
|
1149
|
-
conversationId,
|
|
1150
|
-
text:
|
|
1151
|
-
at:
|
|
1152
|
-
schedule:
|
|
1153
|
-
timezone:
|
|
1324
|
+
name: entry.filename,
|
|
1325
|
+
size: entry.size,
|
|
1326
|
+
mtimeMs: entry.mtimeMs,
|
|
1327
|
+
type: payload?.type ?? null,
|
|
1328
|
+
platform: payload?.platform ?? null,
|
|
1329
|
+
conversationId: payload?.conversationId ?? null,
|
|
1330
|
+
text: payload?.text ?? null,
|
|
1331
|
+
at: payload?.type === "one-shot" ? payload.at : null,
|
|
1332
|
+
schedule: payload?.type === "periodic" ? payload.schedule : null,
|
|
1333
|
+
timezone: payload?.type === "periodic" ? payload.timezone : null,
|
|
1154
1334
|
};
|
|
1155
|
-
})
|
|
1156
|
-
.filter((e) => e !== null)
|
|
1157
|
-
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
1335
|
+
});
|
|
1158
1336
|
}
|
|
1159
|
-
function
|
|
1160
|
-
|
|
1161
|
-
|
|
1337
|
+
function requireAdminEventStore(res, services) {
|
|
1338
|
+
if (!services.eventStore) {
|
|
1339
|
+
jsonRes(res, 503, { error: "Working directory not available" });
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
return services.eventStore;
|
|
1343
|
+
}
|
|
1344
|
+
async function serveEventsList(res, services) {
|
|
1345
|
+
const store = requireAdminEventStore(res, services);
|
|
1346
|
+
if (!store)
|
|
1162
1347
|
return;
|
|
1163
|
-
jsonRes(res, 200, { events: listAllEvents(
|
|
1348
|
+
jsonRes(res, 200, { events: await listAllEvents(store) });
|
|
1164
1349
|
}
|
|
1165
1350
|
/** Per-conversation listing — filter all events by conversationId match. */
|
|
1166
|
-
function serveConversationEventsList(res, url, services, token) {
|
|
1351
|
+
async function serveConversationEventsList(res, url, services, token) {
|
|
1167
1352
|
const scope = resolveConversationFromQuery(url, token);
|
|
1168
1353
|
if (scope.error) {
|
|
1169
1354
|
jsonRes(res, 403, { error: scope.error });
|
|
1170
1355
|
return;
|
|
1171
1356
|
}
|
|
1172
|
-
const
|
|
1173
|
-
if (!
|
|
1357
|
+
const store = requireAdminEventStore(res, services);
|
|
1358
|
+
if (!store)
|
|
1174
1359
|
return;
|
|
1175
|
-
const events = listAllEvents(
|
|
1360
|
+
const events = (await listAllEvents(store)).filter((e) => e.conversationId === scope.conversationId);
|
|
1176
1361
|
jsonRes(res, 200, { conversationId: scope.conversationId, events });
|
|
1177
1362
|
}
|
|
1178
1363
|
function serveEventsFile(res, url, services) {
|
|
1179
|
-
const
|
|
1180
|
-
if (!
|
|
1364
|
+
const workspace = requireAdminWorkspace(res, services);
|
|
1365
|
+
if (!workspace)
|
|
1181
1366
|
return;
|
|
1182
1367
|
const name = (url.searchParams.get("name") ?? "").trim();
|
|
1183
1368
|
if (!name || name.includes("/") || name.includes("\\") || name.includes("..")) {
|
|
1184
1369
|
jsonRes(res, 400, { error: "Invalid name" });
|
|
1185
1370
|
return;
|
|
1186
1371
|
}
|
|
1187
|
-
const filePath = join(
|
|
1372
|
+
const filePath = join(workspace.eventsDir, name);
|
|
1188
1373
|
let stats;
|
|
1189
1374
|
try {
|
|
1190
1375
|
stats = statSync(filePath);
|
|
@@ -1212,7 +1397,7 @@ function serveEventsFile(res, url, services) {
|
|
|
1212
1397
|
jsonRes(res, 200, { name, content: raw });
|
|
1213
1398
|
}
|
|
1214
1399
|
/** Delete a single event file scoped to the caller's conversation. */
|
|
1215
|
-
function serveConversationEventDelete(res, body, services, token) {
|
|
1400
|
+
async function serveConversationEventDelete(res, body, services, token) {
|
|
1216
1401
|
const scope = resolveTargetConversation(body, token);
|
|
1217
1402
|
if (scope.error) {
|
|
1218
1403
|
jsonRes(res, 403, { error: scope.error });
|
|
@@ -1223,38 +1408,24 @@ function serveConversationEventDelete(res, body, services, token) {
|
|
|
1223
1408
|
jsonRes(res, 400, { error: "Invalid name" });
|
|
1224
1409
|
return;
|
|
1225
1410
|
}
|
|
1226
|
-
const
|
|
1227
|
-
if (!
|
|
1411
|
+
const store = requireAdminEventStore(res, services);
|
|
1412
|
+
if (!store)
|
|
1228
1413
|
return;
|
|
1229
|
-
|
|
1230
|
-
let raw;
|
|
1414
|
+
let payload;
|
|
1231
1415
|
try {
|
|
1232
|
-
|
|
1416
|
+
payload = (await store.read(name)).payload;
|
|
1233
1417
|
}
|
|
1234
1418
|
catch {
|
|
1235
1419
|
jsonRes(res, 404, { error: "Event not found" });
|
|
1236
1420
|
return;
|
|
1237
1421
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
const j = JSON.parse(raw);
|
|
1241
|
-
if (j && typeof j === "object")
|
|
1242
|
-
parsed = j;
|
|
1243
|
-
}
|
|
1244
|
-
catch {
|
|
1245
|
-
// Malformed events cannot be associated with a conversation below.
|
|
1246
|
-
}
|
|
1247
|
-
const eventConvId = typeof parsed.conversationId === "string"
|
|
1248
|
-
? parsed.conversationId
|
|
1249
|
-
: typeof parsed.channelId === "string"
|
|
1250
|
-
? parsed.channelId
|
|
1251
|
-
: null;
|
|
1252
|
-
if (eventConvId !== scope.conversationId) {
|
|
1422
|
+
// The store normalizes the legacy `channelId` alias into `conversationId`.
|
|
1423
|
+
if (payload?.conversationId !== scope.conversationId) {
|
|
1253
1424
|
jsonRes(res, 403, { error: "Event does not belong to this conversation." });
|
|
1254
1425
|
return;
|
|
1255
1426
|
}
|
|
1256
1427
|
try {
|
|
1257
|
-
|
|
1428
|
+
await store.delete(name);
|
|
1258
1429
|
jsonRes(res, 200, { ok: true });
|
|
1259
1430
|
}
|
|
1260
1431
|
catch (err) {
|
|
@@ -1269,18 +1440,6 @@ function jsonRes(res, status, body) {
|
|
|
1269
1440
|
});
|
|
1270
1441
|
res.end(JSON.stringify(body));
|
|
1271
1442
|
}
|
|
1272
|
-
async function readJsonBody(req, res) {
|
|
1273
|
-
const data = await readRawBody(req, res, 32 * 1024);
|
|
1274
|
-
if (data === null)
|
|
1275
|
-
return null;
|
|
1276
|
-
try {
|
|
1277
|
-
return JSON.parse(data);
|
|
1278
|
-
}
|
|
1279
|
-
catch {
|
|
1280
|
-
jsonRes(res, 400, { error: "Invalid JSON" });
|
|
1281
|
-
return null;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
1443
|
const esc = escapeHtml;
|
|
1285
1444
|
// ── HTML ───────────────────────────────────────────────────────────────────────
|
|
1286
1445
|
function renderAdminPage(token) {
|
|
@@ -1330,6 +1489,25 @@ function renderAdminPage(token) {
|
|
|
1330
1489
|
</div>
|
|
1331
1490
|
</section>
|
|
1332
1491
|
|
|
1492
|
+
<section class="card sect" id="sect-packages" data-section="packages">
|
|
1493
|
+
<header class="sect-head">
|
|
1494
|
+
<div>
|
|
1495
|
+
<p class="eyebrow">Extensions</p>
|
|
1496
|
+
<h2 class="card-title">此對話的 extension 套件</h2>
|
|
1497
|
+
</div>
|
|
1498
|
+
<button class="refresh-btn" onclick="loadPackages()">↻</button>
|
|
1499
|
+
</header>
|
|
1500
|
+
<div class="pkg-add">
|
|
1501
|
+
<input id="pkg-conv-url" class="pkg-input" type="text" spellcheck="false"
|
|
1502
|
+
placeholder="github:owner/repo 或 https://github.com/owner/repo.git" />
|
|
1503
|
+
<input id="pkg-conv-ref" class="pkg-input pkg-input-ref" type="text" spellcheck="false"
|
|
1504
|
+
placeholder="tag / branch / commit(可留空)" />
|
|
1505
|
+
<button class="primary-action-btn" onclick="addPackage('conversation')">Add</button>
|
|
1506
|
+
</div>
|
|
1507
|
+
<div id="pkg-conv-msg" class="pkg-msg" style="display:none"></div>
|
|
1508
|
+
<div id="pkg-conv-content"><div class="loading-msg">Loading…</div></div>
|
|
1509
|
+
</section>
|
|
1510
|
+
|
|
1333
1511
|
<section class="card sect" id="sect-vault" data-section="vault">
|
|
1334
1512
|
<header class="sect-head">
|
|
1335
1513
|
<div>
|
|
@@ -1407,6 +1585,37 @@ function renderAdminPage(token) {
|
|
|
1407
1585
|
<div id="global-settings-content"><div class="loading-msg">Loading…</div></div>
|
|
1408
1586
|
</section>
|
|
1409
1587
|
|
|
1588
|
+
<section class="card sect">
|
|
1589
|
+
<header class="sect-head">
|
|
1590
|
+
<div>
|
|
1591
|
+
<p class="eyebrow">Global Extensions</p>
|
|
1592
|
+
<h2 class="card-title">所有對話都會載入的 extension 套件</h2>
|
|
1593
|
+
</div>
|
|
1594
|
+
<button class="refresh-btn" onclick="loadPackages()">↻</button>
|
|
1595
|
+
</header>
|
|
1596
|
+
<div class="pkg-add">
|
|
1597
|
+
<input id="pkg-global-url" class="pkg-input" type="text" spellcheck="false"
|
|
1598
|
+
placeholder="github:owner/repo 或 https://github.com/owner/repo.git" />
|
|
1599
|
+
<input id="pkg-global-ref" class="pkg-input pkg-input-ref" type="text" spellcheck="false"
|
|
1600
|
+
placeholder="tag / branch / commit(可留空)" />
|
|
1601
|
+
<button class="primary-action-btn" onclick="addPackage('global')">Add</button>
|
|
1602
|
+
</div>
|
|
1603
|
+
<div id="pkg-global-msg" class="pkg-msg" style="display:none"></div>
|
|
1604
|
+
<div id="pkg-global-content"><div class="loading-msg">Loading…</div></div>
|
|
1605
|
+
</section>
|
|
1606
|
+
|
|
1607
|
+
<section class="card sect">
|
|
1608
|
+
<header class="sect-head">
|
|
1609
|
+
<div>
|
|
1610
|
+
<p class="eyebrow">Extension Secrets</p>
|
|
1611
|
+
<h2 class="card-title">Extension 密鑰(依 slug 全域共用)</h2>
|
|
1612
|
+
</div>
|
|
1613
|
+
<button class="refresh-btn" onclick="loadExtensionSecrets()">↻</button>
|
|
1614
|
+
</header>
|
|
1615
|
+
<div id="ext-secrets-msg" class="pkg-msg" style="display:none"></div>
|
|
1616
|
+
<div id="ext-secrets-content"><div class="loading-msg">Loading…</div></div>
|
|
1617
|
+
</section>
|
|
1618
|
+
|
|
1410
1619
|
<section class="card sect">
|
|
1411
1620
|
<header class="sect-head">
|
|
1412
1621
|
<div>
|
|
@@ -1431,8 +1640,23 @@ function renderAdminPage(token) {
|
|
|
1431
1640
|
</div>`;
|
|
1432
1641
|
const script = `
|
|
1433
1642
|
const adminToken = ${JSON.stringify(token.token)};
|
|
1434
|
-
|
|
1435
|
-
|
|
1643
|
+
// Conversation ids never contain ":" (session-key grammar), so
|
|
1644
|
+
// "platform:id" is a safe composite scope key for the UI.
|
|
1645
|
+
const defaultConversationKey = ${JSON.stringify(`${token.platform}:${token.conversationId}`)};
|
|
1646
|
+
let activeConversationKey = defaultConversationKey;
|
|
1647
|
+
function scopeOf(key) {
|
|
1648
|
+
const sep = key.indexOf(':');
|
|
1649
|
+
return { platform: key.slice(0, sep), conversationId: key.slice(sep + 1) };
|
|
1650
|
+
}
|
|
1651
|
+
function scopeQuery() {
|
|
1652
|
+
const scope = scopeOf(activeConversationKey);
|
|
1653
|
+
return 'conversationId=' + encodeURIComponent(scope.conversationId) +
|
|
1654
|
+
'&platform=' + encodeURIComponent(scope.platform);
|
|
1655
|
+
}
|
|
1656
|
+
function scopeBody() {
|
|
1657
|
+
const scope = scopeOf(activeConversationKey);
|
|
1658
|
+
return { conversationId: scope.conversationId, platform: scope.platform };
|
|
1659
|
+
}
|
|
1436
1660
|
let availableModels = [];
|
|
1437
1661
|
let modelsLoaded = false;
|
|
1438
1662
|
|
|
@@ -1540,9 +1764,10 @@ function renderAdminPage(token) {
|
|
|
1540
1764
|
try {
|
|
1541
1765
|
const data = await apiGet('/admin/api/conversations');
|
|
1542
1766
|
sel.innerHTML = data.conversations.map((c) => {
|
|
1767
|
+
const key = c.platform + ':' + c.conversationId;
|
|
1543
1768
|
const label = (c.label || c.conversationId) + (c.running ? ' (running)' : '');
|
|
1544
|
-
const selected =
|
|
1545
|
-
return '<option value="' + escAttr(
|
|
1769
|
+
const selected = key === defaultConversationKey ? ' selected' : '';
|
|
1770
|
+
return '<option value="' + escAttr(key) + '"' + selected + '>' + escHtml(label) + '</option>';
|
|
1546
1771
|
}).join('');
|
|
1547
1772
|
sel.addEventListener('change', () => setActiveConversation(sel.value));
|
|
1548
1773
|
} catch (err) {
|
|
@@ -1550,14 +1775,15 @@ function renderAdminPage(token) {
|
|
|
1550
1775
|
}
|
|
1551
1776
|
}
|
|
1552
1777
|
|
|
1553
|
-
function setActiveConversation(
|
|
1554
|
-
|
|
1778
|
+
function setActiveConversation(key) {
|
|
1779
|
+
activeConversationKey = key;
|
|
1555
1780
|
const sel = document.getElementById('conv-switcher');
|
|
1556
|
-
if (sel && sel.value !==
|
|
1781
|
+
if (sel && sel.value !== key) sel.value = key;
|
|
1557
1782
|
// Reset all conversation sections.
|
|
1558
1783
|
loadSettings();
|
|
1559
1784
|
loadWorkspace();
|
|
1560
1785
|
loadSkills();
|
|
1786
|
+
loadPackages();
|
|
1561
1787
|
loadConversationEvents();
|
|
1562
1788
|
openLogin(true);
|
|
1563
1789
|
openSessionView(true);
|
|
@@ -1570,7 +1796,7 @@ function renderAdminPage(token) {
|
|
|
1570
1796
|
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
1571
1797
|
if (!modelsLoaded) await loadModels();
|
|
1572
1798
|
try {
|
|
1573
|
-
const data = await apiGet('/admin/api/conversation-state?
|
|
1799
|
+
const data = await apiGet('/admin/api/conversation-state?' + scopeQuery());
|
|
1574
1800
|
container.innerHTML = renderSettings(data);
|
|
1575
1801
|
} catch (err) {
|
|
1576
1802
|
container.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
@@ -1578,14 +1804,10 @@ function renderAdminPage(token) {
|
|
|
1578
1804
|
}
|
|
1579
1805
|
|
|
1580
1806
|
function renderSettings(data) {
|
|
1581
|
-
const thinking = ['off','minimal','low','medium','high','xhigh'];
|
|
1807
|
+
const thinking = ['off','minimal','low','medium','high','xhigh','max'];
|
|
1582
1808
|
const thinkingOpts = thinking.map((t) =>
|
|
1583
1809
|
'<option value="' + t + '"' + (data.thinkingLevel === t ? ' selected' : '') + '>' + t + '</option>'
|
|
1584
1810
|
).join('');
|
|
1585
|
-
const mounts = ['private','full'];
|
|
1586
|
-
const mountOpts = mounts.map((m) =>
|
|
1587
|
-
'<option value="' + m + '"' + (data.sandboxImageWorkspaceMount === m ? ' selected' : '') + '>' + m + '</option>'
|
|
1588
|
-
).join('');
|
|
1589
1811
|
const rulesText = (data.autoReplyRules || []).join('\\n');
|
|
1590
1812
|
const replyModes = ['top-level','thread'];
|
|
1591
1813
|
const replyModeOpts = replyModes.map((m) =>
|
|
@@ -1594,7 +1816,15 @@ function renderAdminPage(token) {
|
|
|
1594
1816
|
const globalReplyMode = (data.slack && data.slack.globalReplyMode) || 'top-level';
|
|
1595
1817
|
const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');
|
|
1596
1818
|
const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');
|
|
1597
|
-
const
|
|
1819
|
+
const doorPolicyChoices = [
|
|
1820
|
+
['default', 'Global default (' + data.globalWorkspaceDoorPolicy + ' / ' + data.globalWorkspaceLayout + ')'],
|
|
1821
|
+
['isolated', 'isolated — own office only'],
|
|
1822
|
+
['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
|
|
1823
|
+
['trusted-full', 'trusted / full — entire workspace'],
|
|
1824
|
+
];
|
|
1825
|
+
const doorPolicyOpts = doorPolicyChoices.map(([value, label]) =>
|
|
1826
|
+
'<option value="' + value + '"' + ((data.workspaceOverride || 'default') === value ? ' selected' : '') + '>' + escHtml(label) + '</option>'
|
|
1827
|
+
).join('');
|
|
1598
1828
|
return [
|
|
1599
1829
|
'<div class="config-grid">',
|
|
1600
1830
|
'<div class="config-block">',
|
|
@@ -1613,10 +1843,11 @@ function renderAdminPage(token) {
|
|
|
1613
1843
|
'<div id="auto-save-result" class="inline-result" style="display:none"></div>',
|
|
1614
1844
|
'</div>',
|
|
1615
1845
|
'<div class="config-block">',
|
|
1616
|
-
'<h3 class="card-subtitle">
|
|
1617
|
-
'<div class="config-row"><label>
|
|
1618
|
-
'<p class="muted-note">
|
|
1619
|
-
'<
|
|
1846
|
+
'<h3 class="card-subtitle">Office data policy</h3>',
|
|
1847
|
+
'<div class="config-row"><label>Door policy</label><select id="m-door-policy">' + doorPolicyOpts + '</select></div>',
|
|
1848
|
+
'<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout) + '</p>',
|
|
1849
|
+
'<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>',
|
|
1850
|
+
'<button class="primary-action-btn" onclick="saveDoorPolicy(this)">Save door policy</button>',
|
|
1620
1851
|
'<div id="mount-save-result" class="inline-result" style="display:none"></div>',
|
|
1621
1852
|
'</div>',
|
|
1622
1853
|
'<div class="config-block">',
|
|
@@ -1644,12 +1875,10 @@ function renderAdminPage(token) {
|
|
|
1644
1875
|
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
1645
1876
|
try {
|
|
1646
1877
|
const data = await apiPost('/admin/api/conversations/model', {
|
|
1647
|
-
|
|
1878
|
+
...scopeBody(), provider, model, thinkingLevel,
|
|
1648
1879
|
});
|
|
1649
1880
|
result.style.display = 'block'; result.className = 'inline-result ok';
|
|
1650
|
-
result.textContent =
|
|
1651
|
-
? 'Saved — running session pinned; new model applies on next start.'
|
|
1652
|
-
: 'Saved ✓';
|
|
1881
|
+
result.textContent = 'Saved ✓';
|
|
1653
1882
|
} catch (err) {
|
|
1654
1883
|
result.style.display = 'block'; result.className = 'inline-result err';
|
|
1655
1884
|
result.textContent = err.message;
|
|
@@ -1665,7 +1894,7 @@ function renderAdminPage(token) {
|
|
|
1665
1894
|
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
1666
1895
|
try {
|
|
1667
1896
|
await apiPost('/admin/api/conversations/auto-reply', {
|
|
1668
|
-
|
|
1897
|
+
...scopeBody(), enabled, rules,
|
|
1669
1898
|
});
|
|
1670
1899
|
result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';
|
|
1671
1900
|
} catch (err) {
|
|
@@ -1675,35 +1904,36 @@ function renderAdminPage(token) {
|
|
|
1675
1904
|
}
|
|
1676
1905
|
}
|
|
1677
1906
|
|
|
1678
|
-
async function
|
|
1679
|
-
const
|
|
1680
|
-
const result = document.getElementById('
|
|
1907
|
+
async function saveSlack(btn) {
|
|
1908
|
+
const replyMode = document.getElementById('m-slack-reply-mode').value;
|
|
1909
|
+
const result = document.getElementById('slack-save-result');
|
|
1681
1910
|
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
1682
1911
|
try {
|
|
1683
|
-
await apiPost('/admin/api/conversations/
|
|
1684
|
-
|
|
1912
|
+
await apiPost('/admin/api/conversations/slack', {
|
|
1913
|
+
...scopeBody(), replyMode,
|
|
1685
1914
|
});
|
|
1686
1915
|
result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';
|
|
1687
1916
|
} catch (err) {
|
|
1688
1917
|
result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
|
|
1689
1918
|
} finally {
|
|
1690
|
-
btn.disabled = false; btn.textContent = 'Save
|
|
1919
|
+
btn.disabled = false; btn.textContent = 'Save Slack';
|
|
1691
1920
|
}
|
|
1692
1921
|
}
|
|
1693
1922
|
|
|
1694
|
-
async function
|
|
1695
|
-
const
|
|
1696
|
-
const result = document.getElementById('
|
|
1923
|
+
async function saveDoorPolicy(btn) {
|
|
1924
|
+
const doorPolicy = document.getElementById('m-door-policy').value;
|
|
1925
|
+
const result = document.getElementById('mount-save-result');
|
|
1697
1926
|
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
1698
1927
|
try {
|
|
1699
|
-
await apiPost('/admin/api/conversations/
|
|
1700
|
-
|
|
1928
|
+
await apiPost('/admin/api/conversations/sandbox', {
|
|
1929
|
+
...scopeBody(), doorPolicy,
|
|
1701
1930
|
});
|
|
1702
1931
|
result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';
|
|
1932
|
+
loadSettings();
|
|
1703
1933
|
} catch (err) {
|
|
1704
1934
|
result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
|
|
1705
1935
|
} finally {
|
|
1706
|
-
btn.disabled = false; btn.textContent = 'Save
|
|
1936
|
+
btn.disabled = false; btn.textContent = 'Save door policy';
|
|
1707
1937
|
}
|
|
1708
1938
|
}
|
|
1709
1939
|
|
|
@@ -1715,7 +1945,7 @@ function renderAdminPage(token) {
|
|
|
1715
1945
|
treeEl.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
1716
1946
|
previewEl.innerHTML = '<div class="placeholder-msg">Click a file to preview</div>';
|
|
1717
1947
|
try {
|
|
1718
|
-
const data = await apiGet('/admin/api/workspace/tree?
|
|
1948
|
+
const data = await apiGet('/admin/api/workspace/tree?' + scopeQuery());
|
|
1719
1949
|
if (!data.tree) {
|
|
1720
1950
|
treeEl.innerHTML = '<div class="empty-state">No files</div>';
|
|
1721
1951
|
return;
|
|
@@ -1755,7 +1985,7 @@ function renderAdminPage(token) {
|
|
|
1755
1985
|
const previewEl = document.getElementById('workspace-preview');
|
|
1756
1986
|
previewEl.innerHTML = '<div class="loading-msg">Loading ' + escHtml(path) + '…</div>';
|
|
1757
1987
|
try {
|
|
1758
|
-
const data = await apiGet('/admin/api/workspace/file?
|
|
1988
|
+
const data = await apiGet('/admin/api/workspace/file?' + scopeQuery() + '&path=' + encodeURIComponent(path));
|
|
1759
1989
|
renderPreviewFileResult(previewEl, path, data);
|
|
1760
1990
|
} catch (err) {
|
|
1761
1991
|
previewEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
@@ -1764,13 +1994,200 @@ function renderAdminPage(token) {
|
|
|
1764
1994
|
|
|
1765
1995
|
// ── Skills ───────────────────────────────────────────────────────────────────
|
|
1766
1996
|
|
|
1997
|
+
function packageMessage(scope, text, kind) {
|
|
1998
|
+
const el = document.getElementById(scope === 'global' ? 'pkg-global-msg' : 'pkg-conv-msg');
|
|
1999
|
+
if (!text) { el.style.display = 'none'; return; }
|
|
2000
|
+
el.className = 'pkg-msg pkg-msg-' + kind;
|
|
2001
|
+
el.textContent = text;
|
|
2002
|
+
el.style.display = 'block';
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function renderPackageList(container, rows, scope) {
|
|
2006
|
+
if (rows.length === 0) {
|
|
2007
|
+
container.innerHTML = '<div class="empty-state">尚未加入任何套件</div>';
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
container.innerHTML = '<div class="pkg-list">' + rows.map((p) => {
|
|
2011
|
+
const provides = []
|
|
2012
|
+
.concat(p.extensions.map((s) => 'ext: ' + s))
|
|
2013
|
+
.concat(p.skills.map((s) => 'skill: ' + s));
|
|
2014
|
+
const status = p.error
|
|
2015
|
+
? '<span class="pkg-badge pkg-badge-err">' + escHtml(p.error) + '</span>'
|
|
2016
|
+
: p.shadowed
|
|
2017
|
+
? '<span class="pkg-badge pkg-badge-warn">被此對話的同名套件覆蓋,不會載入</span>'
|
|
2018
|
+
// "downloaded", not "ready": this status means the checkout is on
|
|
2019
|
+
// disk and was inspected, which is not the same as activated. The
|
|
2020
|
+
// extension loads on the conversation's next harness instance.
|
|
2021
|
+
: '<span class="pkg-badge pkg-badge-ok">已下載</span>'
|
|
2022
|
+
+ '<span class="pkg-badge pkg-badge-warn">需 /pi-new 才會載入</span>';
|
|
2023
|
+
return '<div class="pkg-row">' +
|
|
2024
|
+
'<div class="pkg-row-main">' +
|
|
2025
|
+
'<code class="pkg-source">' + escHtml(p.source) + '</code>' + status +
|
|
2026
|
+
'</div>' +
|
|
2027
|
+
(provides.length > 0
|
|
2028
|
+
? '<div class="pkg-provides">' + provides.map((t) => '<span class="pkg-chip">' + escHtml(t) + '</span>').join('') + '</div>'
|
|
2029
|
+
: '<div class="pkg-provides pkg-provides-empty">此套件沒有提供 extension 或 skill</div>') +
|
|
2030
|
+
'<div class="pkg-actions">' +
|
|
2031
|
+
'<button class="pkg-btn" data-pkg-action="refresh" data-pkg-scope="' + scope + '" data-pkg-source="' + escAttr(p.source) + '">Update</button>' +
|
|
2032
|
+
'<button class="pkg-btn pkg-btn-danger" data-pkg-action="remove" data-pkg-scope="' + scope + '" data-pkg-source="' + escAttr(p.source) + '">Remove</button>' +
|
|
2033
|
+
'</div>' +
|
|
2034
|
+
'</div>';
|
|
2035
|
+
}).join('') + '</div>';
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
async function loadPackages() {
|
|
2039
|
+
const convEl = document.getElementById('pkg-conv-content');
|
|
2040
|
+
const globalEl = document.getElementById('pkg-global-content');
|
|
2041
|
+
if (convEl) convEl.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2042
|
+
if (globalEl) globalEl.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2043
|
+
try {
|
|
2044
|
+
const data = await apiGet('/admin/api/packages?' + scopeQuery());
|
|
2045
|
+
if (convEl) renderPackageList(convEl, data.conversation, 'conversation');
|
|
2046
|
+
if (globalEl) renderPackageList(globalEl, data.global, 'global');
|
|
2047
|
+
} catch (err) {
|
|
2048
|
+
if (convEl) convEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
2049
|
+
if (globalEl) globalEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
async function mutatePackage(scope, action, source) {
|
|
2054
|
+
packageMessage(scope, action === 'remove' ? '移除中…' : '取得中…', 'busy');
|
|
2055
|
+
try {
|
|
2056
|
+
const result = await apiPost('/admin/api/packages/mutate', {
|
|
2057
|
+
action: action,
|
|
2058
|
+
scope: scope,
|
|
2059
|
+
source: source,
|
|
2060
|
+
...scopeBody(),
|
|
2061
|
+
});
|
|
2062
|
+
packageMessage(
|
|
2063
|
+
scope,
|
|
2064
|
+
action === 'remove'
|
|
2065
|
+
? '已移除。對話輸入 /pi-new 生效。'
|
|
2066
|
+
: '完成:' + result.source + '(對話輸入 /pi-new 生效)',
|
|
2067
|
+
'ok',
|
|
2068
|
+
);
|
|
2069
|
+
await loadPackages();
|
|
2070
|
+
} catch (err) {
|
|
2071
|
+
// Fetch and validation failures land here, next to the input that
|
|
2072
|
+
// caused them, rather than turning into a silently missing feature.
|
|
2073
|
+
packageMessage(scope, err.message, 'err');
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
async function addPackage(scope) {
|
|
2078
|
+
const urlEl = document.getElementById(scope === 'global' ? 'pkg-global-url' : 'pkg-conv-url');
|
|
2079
|
+
const refEl = document.getElementById(scope === 'global' ? 'pkg-global-ref' : 'pkg-conv-ref');
|
|
2080
|
+
const url = urlEl.value.trim();
|
|
2081
|
+
const ref = refEl.value.trim();
|
|
2082
|
+
if (!url) { packageMessage(scope, '請填入 git URL', 'err'); return; }
|
|
2083
|
+
// Assembled here so nobody has to hand-write the @ref form, whose
|
|
2084
|
+
// ambiguity with git@host:owner/repo is the one sharp edge in the syntax.
|
|
2085
|
+
await mutatePackage(scope, 'add', ref ? url + '@' + ref : url);
|
|
2086
|
+
urlEl.value = '';
|
|
2087
|
+
refEl.value = '';
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
document.addEventListener('click', (event) => {
|
|
2091
|
+
const btn = event.target.closest('[data-pkg-action]');
|
|
2092
|
+
if (!btn) return;
|
|
2093
|
+
void mutatePackage(btn.dataset.pkgScope, btn.dataset.pkgAction, btn.dataset.pkgSource);
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
// ── Extension secrets ───────────────────────────────────────────────────────
|
|
2097
|
+
|
|
2098
|
+
function extSecretsMessage(text, kind) {
|
|
2099
|
+
const el = document.getElementById('ext-secrets-msg');
|
|
2100
|
+
if (!el) return;
|
|
2101
|
+
el.style.display = 'block';
|
|
2102
|
+
el.className = 'pkg-msg pkg-msg-' + kind;
|
|
2103
|
+
el.textContent = text;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
function renderSecretRow(slug, key, meta) {
|
|
2107
|
+
return '<div class="pkg-row">' +
|
|
2108
|
+
'<div class="pkg-row-main"><span class="pkg-source">' + escHtml(key) + '</span>' +
|
|
2109
|
+
(meta.required ? '<span class="pkg-badge pkg-badge-warn">required</span>' : '') +
|
|
2110
|
+
(meta.provisioned
|
|
2111
|
+
? '<span class="pkg-badge pkg-badge-ok">provisioned</span>'
|
|
2112
|
+
: '<span class="pkg-badge pkg-badge-err">missing</span>') +
|
|
2113
|
+
'</div>' +
|
|
2114
|
+
(meta.description ? '<div class="skill-desc">' + escHtml(meta.description) + '</div>' : '') +
|
|
2115
|
+
'<div class="pkg-actions">' +
|
|
2116
|
+
'<button class="pkg-btn" data-secret-action="set" data-secret-slug="' + escAttr(slug) + '" data-secret-key="' + escAttr(key) + '">Set</button>' +
|
|
2117
|
+
(meta.provisioned
|
|
2118
|
+
? '<button class="pkg-btn pkg-btn-danger" data-secret-action="delete" data-secret-slug="' + escAttr(slug) + '" data-secret-key="' + escAttr(key) + '">Delete</button>'
|
|
2119
|
+
: '') +
|
|
2120
|
+
'</div>' +
|
|
2121
|
+
'</div>';
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
async function loadExtensionSecrets() {
|
|
2125
|
+
const container = document.getElementById('ext-secrets-content');
|
|
2126
|
+
if (!container) return;
|
|
2127
|
+
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2128
|
+
try {
|
|
2129
|
+
const data = await apiGet('/admin/api/extension-secrets?' + scopeQuery());
|
|
2130
|
+
if (data.extensions.length === 0) {
|
|
2131
|
+
container.innerHTML = '<div class="empty-state">No extensions installed</div>';
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
container.innerHTML = data.extensions.map((ext) => {
|
|
2135
|
+
const declared = ext.declared.map((s) => renderSecretRow(ext.slug, s.key, s)).join('');
|
|
2136
|
+
const extras = ext.extraProvisioned
|
|
2137
|
+
.map((key) => renderSecretRow(ext.slug, key, { provisioned: true }))
|
|
2138
|
+
.join('');
|
|
2139
|
+
return '<div class="pkg-group">' +
|
|
2140
|
+
'<div class="pkg-group-head"><strong>' + escHtml(ext.name) + '</strong>' +
|
|
2141
|
+
'<span class="pkg-source">' + escHtml(ext.slug) + '</span>' +
|
|
2142
|
+
'<button class="pkg-btn" data-secret-action="add" data-secret-slug="' + escAttr(ext.slug) + '">Add key</button>' +
|
|
2143
|
+
'</div>' +
|
|
2144
|
+
'<div class="pkg-list">' +
|
|
2145
|
+
(declared + extras || '<div class="pkg-provides-empty">No secrets declared</div>') +
|
|
2146
|
+
'</div>' +
|
|
2147
|
+
'</div>';
|
|
2148
|
+
}).join('');
|
|
2149
|
+
} catch (err) {
|
|
2150
|
+
container.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
document.addEventListener('click', async (event) => {
|
|
2155
|
+
const btn = event.target.closest('[data-secret-action]');
|
|
2156
|
+
if (!btn) return;
|
|
2157
|
+
const slug = btn.dataset.secretSlug;
|
|
2158
|
+
const action = btn.dataset.secretAction;
|
|
2159
|
+
let key = btn.dataset.secretKey;
|
|
2160
|
+
if (action === 'add') {
|
|
2161
|
+
key = (prompt('Secret key for ' + slug + '(e.g. SLACK_BOT_TOKEN)') || '').trim();
|
|
2162
|
+
if (!key) return;
|
|
2163
|
+
}
|
|
2164
|
+
if (action === 'delete' && !confirm('Delete ' + key + ' from ' + slug + '?')) return;
|
|
2165
|
+
let value;
|
|
2166
|
+
if (action !== 'delete') {
|
|
2167
|
+
value = prompt('Value for ' + key + '(' + slug + ')');
|
|
2168
|
+
if (!value) return;
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
await apiPost('/admin/api/extension-secrets/mutate', {
|
|
2172
|
+
action: action === 'delete' ? 'delete' : 'set',
|
|
2173
|
+
slug: slug,
|
|
2174
|
+
key: key,
|
|
2175
|
+
value: value,
|
|
2176
|
+
});
|
|
2177
|
+
extSecretsMessage('已更新。對話輸入 /pi-new 生效。', 'ok');
|
|
2178
|
+
await loadExtensionSecrets();
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
extSecretsMessage(err.message, 'err');
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
|
|
1767
2184
|
async function loadSkills() {
|
|
1768
2185
|
const container = document.getElementById('skills-content');
|
|
1769
2186
|
const previewEl = document.getElementById('skills-preview');
|
|
1770
2187
|
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
1771
2188
|
if (previewEl) previewEl.innerHTML = '<div class="placeholder-msg">Click a skill to preview SKILL.md</div>';
|
|
1772
2189
|
try {
|
|
1773
|
-
const data = await apiGet('/admin/api/skills?
|
|
2190
|
+
const data = await apiGet('/admin/api/skills?' + scopeQuery());
|
|
1774
2191
|
if (data.skills.length === 0) {
|
|
1775
2192
|
container.innerHTML = '<div class="empty-state">No skills available</div>';
|
|
1776
2193
|
return;
|
|
@@ -1796,7 +2213,7 @@ function renderAdminPage(token) {
|
|
|
1796
2213
|
}
|
|
1797
2214
|
previewEl.innerHTML = '<div class="loading-msg">Loading ' + escHtml(name || directory) + '…</div>';
|
|
1798
2215
|
try {
|
|
1799
|
-
const data = await apiGet('/admin/api/skills/file?
|
|
2216
|
+
const data = await apiGet('/admin/api/skills/file?' + scopeQuery() + '&source=' + encodeURIComponent(source) + '&directory=' + encodeURIComponent(directory));
|
|
1800
2217
|
renderPreviewFileResult(previewEl, source + '/' + directory + '/SKILL.md', data);
|
|
1801
2218
|
} catch (err) {
|
|
1802
2219
|
previewEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
|
|
@@ -1817,7 +2234,7 @@ function renderAdminPage(token) {
|
|
|
1817
2234
|
if (silent) { frame.removeAttribute('src'); frame.style.display = 'none'; result.style.display = 'none'; return; }
|
|
1818
2235
|
result.style.display = 'block'; result.className = 'link-result loading'; result.textContent = 'Generating link…';
|
|
1819
2236
|
try {
|
|
1820
|
-
const data = await apiPost('/admin/api/conversations/login-link',
|
|
2237
|
+
const data = await apiPost('/admin/api/conversations/login-link', scopeBody());
|
|
1821
2238
|
result.className = 'link-result ok';
|
|
1822
2239
|
result.innerHTML =
|
|
1823
2240
|
'<span class="link-vault">vault: <code>' + escHtml(data.vaultId) + '</code></span>' +
|
|
@@ -1838,7 +2255,7 @@ function renderAdminPage(token) {
|
|
|
1838
2255
|
if (silent) { frame.removeAttribute('src'); frame.style.display = 'none'; result.style.display = 'none'; return; }
|
|
1839
2256
|
result.style.display = 'block'; result.className = 'link-result loading'; result.textContent = 'Generating link…';
|
|
1840
2257
|
try {
|
|
1841
|
-
const data = await apiPost('/admin/api/conversations/session-link',
|
|
2258
|
+
const data = await apiPost('/admin/api/conversations/session-link', scopeBody());
|
|
1842
2259
|
result.className = 'link-result ok';
|
|
1843
2260
|
result.innerHTML =
|
|
1844
2261
|
'<a href="' + escAttr(data.url) + '" target="_blank" rel="noopener">' + escHtml(data.url) + '</a>' +
|
|
@@ -1857,7 +2274,7 @@ function renderAdminPage(token) {
|
|
|
1857
2274
|
if (!container) return;
|
|
1858
2275
|
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
1859
2276
|
try {
|
|
1860
|
-
const data = await apiGet('/admin/api/conversations/events?
|
|
2277
|
+
const data = await apiGet('/admin/api/conversations/events?' + scopeQuery());
|
|
1861
2278
|
if (data.events.length === 0) {
|
|
1862
2279
|
container.innerHTML = '<div class="empty-state">沒有關聯此對話的 event</div>';
|
|
1863
2280
|
return;
|
|
@@ -1908,7 +2325,7 @@ function renderAdminPage(token) {
|
|
|
1908
2325
|
btn.disabled = true; btn.textContent = 'Deleting…';
|
|
1909
2326
|
try {
|
|
1910
2327
|
await apiPost('/admin/api/conversations/events/delete', {
|
|
1911
|
-
|
|
2328
|
+
...scopeBody(), name,
|
|
1912
2329
|
});
|
|
1913
2330
|
await loadConversationEvents();
|
|
1914
2331
|
} catch (err) {
|
|
@@ -1928,6 +2345,7 @@ function renderAdminPage(token) {
|
|
|
1928
2345
|
loadGlobalSettings();
|
|
1929
2346
|
loadGlobalSkills();
|
|
1930
2347
|
loadEvents();
|
|
2348
|
+
loadExtensionSecrets();
|
|
1931
2349
|
}
|
|
1932
2350
|
|
|
1933
2351
|
async function loadAllConversations() {
|
|
@@ -1996,12 +2414,13 @@ function renderAdminPage(token) {
|
|
|
1996
2414
|
timelineConvLoaded = true;
|
|
1997
2415
|
return;
|
|
1998
2416
|
}
|
|
1999
|
-
const want = prev ||
|
|
2000
|
-
sel.innerHTML = data.conversations.map((c) =>
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2417
|
+
const want = prev || defaultConversationKey;
|
|
2418
|
+
sel.innerHTML = data.conversations.map((c) => {
|
|
2419
|
+
const key = c.platform + ':' + c.conversationId;
|
|
2420
|
+
return '<option value="' + escAttr(key) + '"' +
|
|
2421
|
+
(key === want ? ' selected' : '') + '>' +
|
|
2422
|
+
escHtml(c.label || c.conversationId) + '</option>';
|
|
2423
|
+
}).join('');
|
|
2005
2424
|
timelineConvLoaded = true;
|
|
2006
2425
|
}
|
|
2007
2426
|
|
|
@@ -2024,8 +2443,10 @@ function renderAdminPage(token) {
|
|
|
2024
2443
|
return;
|
|
2025
2444
|
}
|
|
2026
2445
|
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2446
|
+
const convScope = scopeOf(conv);
|
|
2027
2447
|
const data = await apiGet('/admin/api/conversation-usage?conversationId=' +
|
|
2028
|
-
encodeURIComponent(
|
|
2448
|
+
encodeURIComponent(convScope.conversationId) +
|
|
2449
|
+
'&platform=' + encodeURIComponent(convScope.platform));
|
|
2029
2450
|
timelineData = data;
|
|
2030
2451
|
container.innerHTML = renderUsageTimeline(data);
|
|
2031
2452
|
} catch (err) {
|
|
@@ -2130,18 +2551,25 @@ function renderAdminPage(token) {
|
|
|
2130
2551
|
}
|
|
2131
2552
|
|
|
2132
2553
|
function renderGlobalSettings(data) {
|
|
2133
|
-
const thinking = ['off','minimal','low','medium','high','xhigh'];
|
|
2554
|
+
const thinking = ['off','minimal','low','medium','high','xhigh','max'];
|
|
2134
2555
|
const thinkingOpts = thinking.map((t) =>
|
|
2135
2556
|
'<option value="' + t + '"' + (data.thinkingLevel === t ? ' selected' : '') + '>' + t + '</option>'
|
|
2136
2557
|
).join('');
|
|
2137
|
-
const mounts = ['private','full'];
|
|
2138
|
-
const mountOpts = mounts.map((m) =>
|
|
2139
|
-
'<option value="' + m + '"' + (data.sandboxImageWorkspaceMount === m ? ' selected' : '') + '>' + m + '</option>'
|
|
2140
|
-
).join('');
|
|
2141
2558
|
const replyModes = ['top-level','thread'];
|
|
2142
2559
|
const replyModeOpts = replyModes.map((m) =>
|
|
2143
2560
|
'<option value="' + m + '"' + (((data.slack && data.slack.replyMode) || 'top-level') === m ? ' selected' : '') + '>' + m + '</option>'
|
|
2144
2561
|
).join('');
|
|
2562
|
+
const gDoorKey = data.workspaceDoorPolicy === 'isolated'
|
|
2563
|
+
? 'isolated'
|
|
2564
|
+
: (data.workspaceLayout === 'full' ? 'trusted-full' : 'trusted-shared-support');
|
|
2565
|
+
const gDoorPolicyChoices = [
|
|
2566
|
+
['isolated', 'isolated — own office only (built-in default)'],
|
|
2567
|
+
['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
|
|
2568
|
+
['trusted-full', 'trusted / full — entire workspace'],
|
|
2569
|
+
];
|
|
2570
|
+
const gDoorPolicyOpts = gDoorPolicyChoices.map(([value, label]) =>
|
|
2571
|
+
'<option value="' + value + '"' + (gDoorKey === value ? ' selected' : '') + '>' + escHtml(label) + '</option>'
|
|
2572
|
+
).join('');
|
|
2145
2573
|
return [
|
|
2146
2574
|
'<div class="config-grid">',
|
|
2147
2575
|
'<div class="config-block">',
|
|
@@ -2157,10 +2585,16 @@ function renderAdminPage(token) {
|
|
|
2157
2585
|
'<div class="config-row"><label>Memory</label><input id="g-mem" placeholder="1g" value="' + escAttr(data.sandboxMemory || '') + '"></div>',
|
|
2158
2586
|
'<div class="config-row"><label>Boost CPUs</label><input id="g-bcpus" placeholder="2" value="' + escAttr(data.sandboxBoostCpus || '') + '"></div>',
|
|
2159
2587
|
'<div class="config-row"><label>Boost Mem</label><input id="g-bmem" placeholder="4g" value="' + escAttr(data.sandboxBoostMemory || '') + '"></div>',
|
|
2160
|
-
'<div class="config-row"><label>Mount</label><select id="g-mount">' + mountOpts + '</select></div>',
|
|
2161
2588
|
'<button class="primary-action-btn" onclick="saveGlobalSandbox(this)">Save sandbox</button>',
|
|
2162
2589
|
'<div id="g-sandbox-result" class="inline-result" style="display:none"></div>',
|
|
2163
2590
|
'</div>',
|
|
2591
|
+
'<div class="config-block">',
|
|
2592
|
+
'<h3 class="card-subtitle">Office door policy</h3>',
|
|
2593
|
+
'<div class="config-row"><label>Default</label><select id="g-door-policy">' + gDoorPolicyOpts + '</select></div>',
|
|
2594
|
+
'<p class="muted-note">Applies to offices without their own policy. Affected offices rebuild their sandbox container with the new mounts on the next message; container contents are preserved.</p>',
|
|
2595
|
+
'<button class="primary-action-btn" onclick="saveGlobalWorkspace(this)">Save door policy</button>',
|
|
2596
|
+
'<div id="g-workspace-result" class="inline-result" style="display:none"></div>',
|
|
2597
|
+
'</div>',
|
|
2164
2598
|
'<div class="config-block">',
|
|
2165
2599
|
'<h3 class="card-subtitle">Slack</h3>',
|
|
2166
2600
|
'<div class="config-row"><label>Reply mode</label><select id="g-slack-reply-mode">' + replyModeOpts + '</select></div>',
|
|
@@ -2197,11 +2631,10 @@ function renderAdminPage(token) {
|
|
|
2197
2631
|
const memory = document.getElementById('g-mem').value.trim();
|
|
2198
2632
|
const boostCpus = document.getElementById('g-bcpus').value.trim();
|
|
2199
2633
|
const boostMemory = document.getElementById('g-bmem').value.trim();
|
|
2200
|
-
const workspaceMount = document.getElementById('g-mount').value;
|
|
2201
2634
|
const result = document.getElementById('g-sandbox-result');
|
|
2202
2635
|
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
2203
2636
|
try {
|
|
2204
|
-
await apiPost('/admin/api/settings/sandbox', { cpus, memory, boostCpus, boostMemory
|
|
2637
|
+
await apiPost('/admin/api/settings/sandbox', { cpus, memory, boostCpus, boostMemory });
|
|
2205
2638
|
result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';
|
|
2206
2639
|
} catch (err) {
|
|
2207
2640
|
result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
|
|
@@ -2210,6 +2643,23 @@ function renderAdminPage(token) {
|
|
|
2210
2643
|
}
|
|
2211
2644
|
}
|
|
2212
2645
|
|
|
2646
|
+
async function saveGlobalWorkspace(btn) {
|
|
2647
|
+
const doorPolicy = document.getElementById('g-door-policy').value;
|
|
2648
|
+
const result = document.getElementById('g-workspace-result');
|
|
2649
|
+
btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
|
|
2650
|
+
try {
|
|
2651
|
+
const data = await apiPost('/admin/api/settings/workspace', { doorPolicy });
|
|
2652
|
+
result.style.display = 'block'; result.className = 'inline-result ok';
|
|
2653
|
+
result.textContent = data.staleConversations > 0
|
|
2654
|
+
? 'Saved ✓ (' + data.staleConversations + ' busy conversation(s) refresh after their current run)'
|
|
2655
|
+
: 'Saved ✓';
|
|
2656
|
+
} catch (err) {
|
|
2657
|
+
result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
|
|
2658
|
+
} finally {
|
|
2659
|
+
btn.disabled = false; btn.textContent = 'Save door policy';
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2213
2663
|
async function saveGlobalSlack(btn) {
|
|
2214
2664
|
const replyMode = document.getElementById('g-slack-reply-mode').value;
|
|
2215
2665
|
const result = document.getElementById('g-slack-result');
|
|
@@ -2229,7 +2679,7 @@ function renderAdminPage(token) {
|
|
|
2229
2679
|
container.innerHTML = '<div class="loading-msg">Loading…</div>';
|
|
2230
2680
|
try {
|
|
2231
2681
|
// Reuse skills endpoint scoped to a conversation that doesn't have any of its own; the global half is what we want.
|
|
2232
|
-
const data = await apiGet('/admin/api/skills?
|
|
2682
|
+
const data = await apiGet('/admin/api/skills?' + scopeQuery());
|
|
2233
2683
|
const globals = data.skills.filter((s) => s.source === 'global');
|
|
2234
2684
|
if (globals.length === 0) {
|
|
2235
2685
|
container.innerHTML = '<div class="empty-state">No global skills</div>';
|
|
@@ -2251,6 +2701,7 @@ function renderAdminPage(token) {
|
|
|
2251
2701
|
loadSettings();
|
|
2252
2702
|
loadWorkspace();
|
|
2253
2703
|
loadSkills();
|
|
2704
|
+
loadPackages();
|
|
2254
2705
|
loadConversationEvents();
|
|
2255
2706
|
});
|
|
2256
2707
|
`;
|
|
@@ -2433,6 +2884,60 @@ const adminViewStyles = `
|
|
|
2433
2884
|
.skill-source-conversation { background: rgba(217,119,6,0.1); color: var(--accent); }
|
|
2434
2885
|
.skill-desc { color: var(--muted); font-size: 0.82rem; margin-top: 4px; line-height: 1.5; }
|
|
2435
2886
|
|
|
2887
|
+
/* ── Packages ───────────────────────────────────────────────────────── */
|
|
2888
|
+
|
|
2889
|
+
.pkg-add { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
2890
|
+
.pkg-input {
|
|
2891
|
+
flex: 1 1 240px; min-width: 0; padding: 8px 10px;
|
|
2892
|
+
border: 1px solid var(--border); border-radius: 8px; background: var(--card);
|
|
2893
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.8rem; color: var(--text);
|
|
2894
|
+
}
|
|
2895
|
+
.pkg-input-ref { flex: 0 1 200px; }
|
|
2896
|
+
.pkg-msg {
|
|
2897
|
+
padding: 8px 12px; border-radius: 8px; margin-bottom: 12px;
|
|
2898
|
+
font-size: 0.82rem; line-height: 1.5; word-break: break-word;
|
|
2899
|
+
}
|
|
2900
|
+
.pkg-msg-ok { background: rgba(22,163,74,0.1); color: #15803d; }
|
|
2901
|
+
.pkg-msg-err { background: rgba(220,38,38,0.1); color: #b91c1c; }
|
|
2902
|
+
.pkg-msg-busy { background: rgba(0,0,0,0.05); color: var(--muted); }
|
|
2903
|
+
.pkg-list { display: flex; flex-direction: column; gap: 8px; }
|
|
2904
|
+
.pkg-row {
|
|
2905
|
+
padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
|
|
2906
|
+
background: rgba(0,0,0,0.02);
|
|
2907
|
+
}
|
|
2908
|
+
.pkg-row-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
2909
|
+
.pkg-source {
|
|
2910
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
2911
|
+
font-size: 0.78rem; color: var(--text); word-break: break-all;
|
|
2912
|
+
}
|
|
2913
|
+
.pkg-badge {
|
|
2914
|
+
padding: 1px 8px; border-radius: 999px; font-size: 0.7rem;
|
|
2915
|
+
font-weight: 600; letter-spacing: 0.03em;
|
|
2916
|
+
}
|
|
2917
|
+
.pkg-badge-ok { background: rgba(22,163,74,0.1); color: #15803d; }
|
|
2918
|
+
.pkg-badge-warn { background: rgba(217,119,6,0.12); color: var(--accent); }
|
|
2919
|
+
.pkg-badge-err { background: rgba(220,38,38,0.1); color: #b91c1c; }
|
|
2920
|
+
.pkg-provides { margin-top: 6px; display: flex; gap: 6px; flex-wrap: wrap; }
|
|
2921
|
+
.pkg-provides-empty { color: var(--subtle); font-size: 0.78rem; }
|
|
2922
|
+
.pkg-chip {
|
|
2923
|
+
padding: 1px 8px; border-radius: 6px; background: rgba(59,130,246,0.1);
|
|
2924
|
+
color: #1d4ed8; font-size: 0.72rem;
|
|
2925
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
2926
|
+
}
|
|
2927
|
+
.pkg-actions { margin-top: 8px; display: flex; gap: 8px; }
|
|
2928
|
+
.pkg-btn {
|
|
2929
|
+
padding: 4px 10px; border: 1px solid var(--border); border-radius: 7px;
|
|
2930
|
+
background: var(--card); color: var(--text); font-size: 0.76rem; cursor: pointer;
|
|
2931
|
+
}
|
|
2932
|
+
.pkg-btn:hover { background: rgba(0,0,0,0.05); }
|
|
2933
|
+
.pkg-btn-danger { color: #b91c1c; }
|
|
2934
|
+
.pkg-group { margin-bottom: 16px; }
|
|
2935
|
+
.pkg-group-head {
|
|
2936
|
+
display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
|
|
2937
|
+
font-size: 0.85rem;
|
|
2938
|
+
}
|
|
2939
|
+
.pkg-group-head .pkg-btn { margin-left: auto; }
|
|
2940
|
+
|
|
2436
2941
|
/* ── Events ─────────────────────────────────────────────────────────── */
|
|
2437
2942
|
|
|
2438
2943
|
.events-list { display: flex; flex-direction: column; gap: 8px; }
|