@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
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
import { SocketModeClient } from "@slack/socket-mode";
|
|
2
|
-
import { WebClient } from "@slack/web-api";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { WebAPIRateLimitedError, WebClient } from "@slack/web-api";
|
|
3
|
+
import { readEnv } from "../../env-manifest.js";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { basename, join } from "node:path";
|
|
7
|
+
import { createConversationEvent, createConversationMessage, } from "../../adapter.js";
|
|
8
|
+
import { createOfficeAddress } from "../../office/index.js";
|
|
9
|
+
import { COMMAND_MANIFEST } from "../../commands/manifest.js";
|
|
6
10
|
import { resolveConversationSettings } from "../../config.js";
|
|
11
|
+
import { parseExtActionId } from "../../harness/extensions/registry.js";
|
|
7
12
|
import * as log from "../../log.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
13
|
+
import { isRecord, readTextFileIfExists } from "../../utils/file-guards.js";
|
|
14
|
+
import { PRODUCT_NAME, formatForceStopped } from "../../platform-messages.js";
|
|
15
|
+
import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, saveIncomingAttachments, withRetry, } from "../shared.js";
|
|
10
16
|
import { processMessageIntake } from "../intake.js";
|
|
17
|
+
import { AssistantThreadRegistry, handleAgentContextChanged, handleAgentDmOpened, handleAssistantThreadStarted, titleAssistantThread, } from "./assistant.js";
|
|
11
18
|
import { createSlackAdapters } from "./context.js";
|
|
12
|
-
import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/
|
|
19
|
+
import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/chat-history-sync.js";
|
|
20
|
+
import { conversationIdOf } from "../../sessions/session-key.js";
|
|
13
21
|
import { isSlackThreadSessionKey, planSlackAdapterSession, planSlackEventAnchorRun, resolveSlackSessionKey, } from "./session.js";
|
|
14
22
|
import { reportUserFacingError } from "../../observability/sentry.js";
|
|
15
|
-
import { renderSlackBlocks } from "./blocks.js";
|
|
23
|
+
import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
|
|
24
|
+
import { StreamStartLimiter } from "./stream-limits.js";
|
|
16
25
|
const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Slack's per-call limit on `markdown_text`: "Limit this field to 12,000
|
|
28
|
+
* characters." mikan allows far more in a message, so streamed text is split
|
|
29
|
+
* to fit rather than truncated.
|
|
30
|
+
*/
|
|
31
|
+
const MAX_STREAM_TEXT_CHARS = 12_000;
|
|
32
|
+
/** Slices `text` into pieces that each fit one streaming call. */
|
|
33
|
+
export function chunkStreamText(text, limit = MAX_STREAM_TEXT_CHARS) {
|
|
34
|
+
if (text.length <= limit)
|
|
35
|
+
return [text];
|
|
36
|
+
const chunks = [];
|
|
37
|
+
for (let index = 0; index < text.length; index += limit) {
|
|
38
|
+
chunks.push(text.slice(index, index + limit));
|
|
39
|
+
}
|
|
40
|
+
return chunks;
|
|
41
|
+
}
|
|
42
|
+
// web-api v8 throws WebAPIRateLimitedError; the duck-typed shapes cover
|
|
43
|
+
// platform `data.error === "rate_limited"` / 429 responses and older callers.
|
|
19
44
|
function slackIsRateLimited(err) {
|
|
45
|
+
if (err instanceof WebAPIRateLimitedError)
|
|
46
|
+
return true;
|
|
20
47
|
if (err.code === "rate_limited")
|
|
21
48
|
return true;
|
|
22
49
|
const data = err.data;
|
|
@@ -72,11 +99,28 @@ export function buildMrkdwnContextBlock(text) {
|
|
|
72
99
|
// ============================================================================
|
|
73
100
|
// SlackMessagingBot
|
|
74
101
|
// ============================================================================
|
|
102
|
+
class AttachmentDownloadHttpError extends Error {
|
|
103
|
+
constructor(message, status) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.status = status;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isRetryableAttachmentDownloadError(error) {
|
|
109
|
+
if (!(error instanceof AttachmentDownloadHttpError))
|
|
110
|
+
return true;
|
|
111
|
+
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
112
|
+
}
|
|
75
113
|
export class SlackMessagingBot {
|
|
114
|
+
/** Host office dir for a Slack conversation. */
|
|
115
|
+
conversationDir(channelId) {
|
|
116
|
+
return this.workspace.office(createOfficeAddress("slack", channelId)).dir;
|
|
117
|
+
}
|
|
118
|
+
resolveReplyMode(address) {
|
|
119
|
+
return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
|
|
120
|
+
}
|
|
76
121
|
createContext(event) {
|
|
77
122
|
return createSlackAdapters(event, this, {
|
|
78
|
-
replyMode:
|
|
79
|
-
"top-level",
|
|
123
|
+
replyMode: this.resolveReplyMode(event.address),
|
|
80
124
|
});
|
|
81
125
|
}
|
|
82
126
|
constructor(handler, config) {
|
|
@@ -87,11 +131,20 @@ export class SlackMessagingBot {
|
|
|
87
131
|
this.startupTs = null; // Messages older than this are just logged, not processed
|
|
88
132
|
this.users = new Map();
|
|
89
133
|
this.channels = new Map();
|
|
134
|
+
/** Which threads belong to the assistant pane, and their last known context. */
|
|
135
|
+
this.assistantThreads = new AssistantThreadRegistry();
|
|
136
|
+
/**
|
|
137
|
+
* Native response streams opened recently. Per bot rather than per module:
|
|
138
|
+
* the rate tier applies per workspace, and this bot is the connection to
|
|
139
|
+
* one — which also means a test constructing a bot gets a fresh budget
|
|
140
|
+
* instead of inheriting whatever ran before it.
|
|
141
|
+
*/
|
|
142
|
+
this.streamStarts = new StreamStartLimiter();
|
|
90
143
|
this.queues = new Map();
|
|
91
144
|
this.eventsWatcher = null;
|
|
92
145
|
this.handler = handler;
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
146
|
+
this.workspace = config.workspace;
|
|
147
|
+
this.botToken = config.botToken;
|
|
95
148
|
this.socketClient = new SocketModeClient({
|
|
96
149
|
appToken: config.appToken,
|
|
97
150
|
// Default 5s is too tight: brief event-loop stalls (e.g. backfill, sync fs)
|
|
@@ -145,13 +198,36 @@ export class SlackMessagingBot {
|
|
|
145
198
|
}
|
|
146
199
|
return source.replace(this.ownMentionRegex, "").trim();
|
|
147
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Response-source `<@userName>` mentions become native `<@U…>` here — the
|
|
203
|
+
* one conversion every outgoing response path funnels through. Slack only
|
|
204
|
+
* links and notifies on raw user ids.
|
|
205
|
+
*/
|
|
206
|
+
resolveMentions(text) {
|
|
207
|
+
return resolveSlackMentions(text, this.users.values());
|
|
208
|
+
}
|
|
148
209
|
async postMessage(channel, text) {
|
|
149
210
|
return slackRetry(async () => {
|
|
150
|
-
const payload = { channel, ...renderSlackBlocks(text) };
|
|
211
|
+
const payload = { channel, ...renderSlackBlocks(this.resolveMentions(text)) };
|
|
151
212
|
const result = await this.webClient.chat.postMessage(payload);
|
|
152
213
|
return result.ts;
|
|
153
214
|
});
|
|
154
215
|
}
|
|
216
|
+
async addReaction(channel, messageTs, emoji) {
|
|
217
|
+
// Slack reaction names are colon-free short names; strip any wrapping colons.
|
|
218
|
+
const name = emoji.replace(/^:|:$/g, "");
|
|
219
|
+
await slackRetry(async () => {
|
|
220
|
+
try {
|
|
221
|
+
await this.webClient.reactions.add({ channel, timestamp: messageTs, name });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
// Re-reacting with the same emoji is not an error worth surfacing.
|
|
225
|
+
if (err?.data?.error === "already_reacted")
|
|
226
|
+
return;
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
155
231
|
async postEphemeral(channel, user, text, threadTs) {
|
|
156
232
|
return slackRetry(async () => {
|
|
157
233
|
await this.webClient.chat.postEphemeral({
|
|
@@ -180,6 +256,11 @@ export class SlackMessagingBot {
|
|
|
180
256
|
return result.ts;
|
|
181
257
|
});
|
|
182
258
|
}
|
|
259
|
+
async updateMessageBlocks(channel, ts, text, blocks) {
|
|
260
|
+
return slackRetry(async () => {
|
|
261
|
+
await this.webClient.chat.update({ channel, ts, text, blocks: blocks });
|
|
262
|
+
});
|
|
263
|
+
}
|
|
183
264
|
async postPrivate(conversationId, userId, text) {
|
|
184
265
|
await this.postEphemeral(conversationId, userId, text);
|
|
185
266
|
}
|
|
@@ -190,7 +271,7 @@ export class SlackMessagingBot {
|
|
|
190
271
|
}
|
|
191
272
|
await this.postEphemeralBlocks(conversationId, userId, text, [buildMrkdwnContextBlock(text)]);
|
|
192
273
|
}
|
|
193
|
-
async
|
|
274
|
+
async openDirectConversation(userId) {
|
|
194
275
|
return slackRetry(async () => {
|
|
195
276
|
const result = await this.webClient.conversations.open({ users: userId });
|
|
196
277
|
const channelId = result.channel?.id;
|
|
@@ -200,33 +281,127 @@ export class SlackMessagingBot {
|
|
|
200
281
|
return channelId;
|
|
201
282
|
});
|
|
202
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Fetch recent messages from a channel, oldest first. Without `threadTs`
|
|
286
|
+
* this is one `conversations.history` page of top-level messages (thread
|
|
287
|
+
* replies are not expanded); with it, one `conversations.replies` page of
|
|
288
|
+
* that thread's replies, excluding the parent. Callers page forward by
|
|
289
|
+
* passing the last returned `ts` as `oldest`.
|
|
290
|
+
*/
|
|
291
|
+
async fetchHistory(channel, options) {
|
|
292
|
+
const limit = Math.min(Math.max(options?.limit ?? 200, 1), 999);
|
|
293
|
+
const threadTs = options?.threadTs;
|
|
294
|
+
return slackRetry(async () => {
|
|
295
|
+
const result = threadTs
|
|
296
|
+
? await this.webClient.conversations.replies({
|
|
297
|
+
channel,
|
|
298
|
+
ts: threadTs,
|
|
299
|
+
...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
|
|
300
|
+
limit,
|
|
301
|
+
})
|
|
302
|
+
: await this.webClient.conversations.history({
|
|
303
|
+
channel,
|
|
304
|
+
...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
|
|
305
|
+
limit,
|
|
306
|
+
});
|
|
307
|
+
const messages = (result.messages ?? []);
|
|
308
|
+
const mapped = messages
|
|
309
|
+
.filter((msg) => !!msg.ts)
|
|
310
|
+
// conversations.replies leads with the thread parent; the caller asked
|
|
311
|
+
// for the replies to it, and including it would re-ingest the bot's
|
|
312
|
+
// own message on every poll.
|
|
313
|
+
.filter((msg) => msg.ts !== threadTs)
|
|
314
|
+
.map((msg) => {
|
|
315
|
+
const user = msg.user ? this.users.get(msg.user) : undefined;
|
|
316
|
+
const message = {
|
|
317
|
+
ts: msg.ts,
|
|
318
|
+
text: msg.text ?? "",
|
|
319
|
+
isBot: !!msg.bot_id || msg.subtype === "bot_message" || user?.isBot === true,
|
|
320
|
+
};
|
|
321
|
+
if (msg.thread_ts && msg.thread_ts !== msg.ts)
|
|
322
|
+
message.threadTs = msg.thread_ts;
|
|
323
|
+
if (msg.user)
|
|
324
|
+
message.userId = msg.user;
|
|
325
|
+
if (user)
|
|
326
|
+
message.userName = user.userName;
|
|
327
|
+
return message;
|
|
328
|
+
});
|
|
329
|
+
// history returns newest-first, replies oldest-first; the contract is
|
|
330
|
+
// oldest-first either way.
|
|
331
|
+
return threadTs ? mapped : mapped.toReversed();
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
/** Refresh and list the workspace's active users (`users.list`). */
|
|
335
|
+
async listUsers() {
|
|
336
|
+
await slackRetry(() => this.fetchUsers());
|
|
337
|
+
return this.getAllUsers().map((user) => ({
|
|
338
|
+
id: user.id,
|
|
339
|
+
userName: user.userName,
|
|
340
|
+
displayName: user.displayName,
|
|
341
|
+
isBot: user.isBot === true,
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
203
344
|
async updateMessage(channel, ts, text) {
|
|
204
345
|
return slackRetry(async () => {
|
|
205
|
-
const payload = { channel, ts, ...renderSlackBlocks(text) };
|
|
346
|
+
const payload = { channel, ts, ...renderSlackBlocks(this.resolveMentions(text)) };
|
|
206
347
|
await this.webClient.chat.update(payload);
|
|
207
348
|
});
|
|
208
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Claim one native stream, or decline. Declining is not an error: the caller
|
|
352
|
+
* falls back to edit-based updates, which is a working answer rather than a
|
|
353
|
+
* degraded one, and costs no request against the tighter start tier.
|
|
354
|
+
*/
|
|
355
|
+
tryReserveStreamStart() {
|
|
356
|
+
return this.streamStarts.tryReserve();
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Open a stream, carrying text of any length.
|
|
360
|
+
*
|
|
361
|
+
* Slack caps `markdown_text` at 12,000 characters per call, and a long first
|
|
362
|
+
* render exceeds that easily — mikan allows 35,000 in a message. Rather than
|
|
363
|
+
* truncating (losing the answer) or failing (falling back to edit mode for
|
|
364
|
+
* the whole response), the opening call takes the first slice and the rest
|
|
365
|
+
* follows as appends before this resolves. The caller sees one operation
|
|
366
|
+
* that sent everything, so the renderer's idea of what has been streamed
|
|
367
|
+
* stays true.
|
|
368
|
+
*
|
|
369
|
+
* Splitting mid-word or mid-fence is harmless here: a stream concatenates,
|
|
370
|
+
* so Slack renders the reassembled text, not the pieces.
|
|
371
|
+
*/
|
|
209
372
|
async startMessageStream(channel, text, threadTs, recipientUserId) {
|
|
210
|
-
|
|
373
|
+
const [head, ...tail] = chunkStreamText(text);
|
|
374
|
+
const ts = await slackRetry(async () => {
|
|
211
375
|
const result = await this.webClient.apiCall("chat.startStream", {
|
|
212
376
|
channel,
|
|
213
|
-
markdown_text:
|
|
377
|
+
markdown_text: this.resolveMentions(head ?? ""),
|
|
214
378
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
215
379
|
...(this.teamId ? { recipient_team_id: this.teamId } : {}),
|
|
216
380
|
...(recipientUserId ? { recipient_user_id: recipientUserId } : {}),
|
|
217
381
|
});
|
|
218
|
-
const
|
|
219
|
-
if (!
|
|
382
|
+
const streamTs = result.ts;
|
|
383
|
+
if (!streamTs)
|
|
220
384
|
throw new Error("Slack chat.startStream did not return ts");
|
|
221
|
-
return
|
|
385
|
+
return streamTs;
|
|
222
386
|
});
|
|
387
|
+
for (const part of tail)
|
|
388
|
+
await this.appendStreamChunk(channel, ts, part);
|
|
389
|
+
return ts;
|
|
223
390
|
}
|
|
391
|
+
/** Append a delta of any length, split to the per-call cap. */
|
|
224
392
|
async appendMessageStream(channel, ts, text) {
|
|
393
|
+
for (const part of chunkStreamText(text)) {
|
|
394
|
+
await this.appendStreamChunk(channel, ts, part);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async appendStreamChunk(channel, ts, text) {
|
|
225
398
|
return slackRetry(async () => {
|
|
226
399
|
await this.webClient.apiCall("chat.appendStream", {
|
|
227
400
|
channel,
|
|
228
401
|
ts,
|
|
229
|
-
|
|
402
|
+
// A mention split across stream deltas stays unresolved here; the
|
|
403
|
+
// canonical final render (updateMessage) resolves the full text.
|
|
404
|
+
markdown_text: this.resolveMentions(text),
|
|
230
405
|
});
|
|
231
406
|
});
|
|
232
407
|
}
|
|
@@ -253,9 +428,76 @@ export class SlackMessagingBot {
|
|
|
253
428
|
});
|
|
254
429
|
});
|
|
255
430
|
}
|
|
431
|
+
/** Offer starting points in a new assistant thread. */
|
|
432
|
+
async setAssistantSuggestedPrompts(channel, threadTs, prompts) {
|
|
433
|
+
return slackRetry(async () => {
|
|
434
|
+
// Omitting thread_ts pins the prompts to the DM itself, which is what
|
|
435
|
+
// the agent surface wants: there is no thread yet when it opens.
|
|
436
|
+
await this.webClient.assistant.threads.setSuggestedPrompts({
|
|
437
|
+
channel_id: channel,
|
|
438
|
+
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
439
|
+
prompts,
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/** Name an assistant thread, which is what the sidebar conversation list shows. */
|
|
444
|
+
async setAssistantTitle(channel, threadTs, title) {
|
|
445
|
+
return slackRetry(async () => {
|
|
446
|
+
await this.webClient.assistant.threads.setTitle({
|
|
447
|
+
channel_id: channel,
|
|
448
|
+
thread_ts: threadTs,
|
|
449
|
+
title,
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/** The assistant surface's view of this bot, handed to the lifecycle handlers. */
|
|
454
|
+
assistantOps() {
|
|
455
|
+
return {
|
|
456
|
+
postInThread: (channel, threadTs, text) => this.postInThread(channel, threadTs, text),
|
|
457
|
+
setSuggestedPrompts: (channel, threadTs, prompts) => this.setAssistantSuggestedPrompts(channel, threadTs, prompts),
|
|
458
|
+
setTitle: (channel, threadTs, title) => this.setAssistantTitle(channel, threadTs, title),
|
|
459
|
+
channelName: (channelId) => this.channels.get(channelId)?.name,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* A new conversation opened in the assistant pane. Slack waits for the app
|
|
464
|
+
* to speak first here; staying silent is what makes the pane look broken.
|
|
465
|
+
*/
|
|
466
|
+
handleAssistantThreadStarted({ event, ack }) {
|
|
467
|
+
ack();
|
|
468
|
+
const payload = event;
|
|
469
|
+
if (!payload.assistant_thread)
|
|
470
|
+
return;
|
|
471
|
+
void handleAssistantThreadStarted(this.assistantOps(), this.assistantThreads, payload.assistant_thread);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* The person navigated to another channel while the pane is open. Slack
|
|
475
|
+
* sends this as `assistant_thread_context_changed` under `assistant_view`
|
|
476
|
+
* and `app_context_changed` under `agent_view`; the payload differs enough
|
|
477
|
+
* to normalize here rather than in the handler.
|
|
478
|
+
*/
|
|
479
|
+
handleAgentContextChangedEvent({ event, ack, }) {
|
|
480
|
+
ack();
|
|
481
|
+
const payload = event;
|
|
482
|
+
if (payload.assistant_thread) {
|
|
483
|
+
handleAgentContextChanged(this.assistantThreads, payload.assistant_thread);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const channelId = payload.channel_id ?? payload.channel;
|
|
487
|
+
if (channelId) {
|
|
488
|
+
handleAgentContextChanged(this.assistantThreads, {
|
|
489
|
+
channel_id: channelId,
|
|
490
|
+
context: payload.context,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
256
494
|
async postInThread(channel, threadTs, text) {
|
|
257
495
|
return slackRetry(async () => {
|
|
258
|
-
const payload = {
|
|
496
|
+
const payload = {
|
|
497
|
+
channel,
|
|
498
|
+
thread_ts: threadTs,
|
|
499
|
+
...renderSlackBlocks(this.resolveMentions(text)),
|
|
500
|
+
};
|
|
259
501
|
const result = await this.webClient.chat.postMessage(payload);
|
|
260
502
|
return result.ts;
|
|
261
503
|
});
|
|
@@ -286,18 +528,39 @@ export class SlackMessagingBot {
|
|
|
286
528
|
});
|
|
287
529
|
}
|
|
288
530
|
logToFile(channel, entry) {
|
|
289
|
-
appendChannelLog(this.
|
|
531
|
+
appendChannelLog(this.workspace.office(createOfficeAddress("slack", channel)), entry);
|
|
290
532
|
}
|
|
291
533
|
logBotResponse(channel, text, ts, threadTs, slackBlocks) {
|
|
292
|
-
appendBotResponseLog(this.
|
|
534
|
+
appendBotResponseLog(this.workspace.office(createOfficeAddress("slack", channel)), text, ts, threadTs, {
|
|
293
535
|
platform: "slack",
|
|
294
536
|
...(slackBlocks ? { slackBlocks } : {}),
|
|
295
537
|
});
|
|
296
538
|
}
|
|
539
|
+
ownsBlockKitMessage(channel, ts, threadTs) {
|
|
540
|
+
const content = readTextFileIfExists(join(this.conversationDir(channel), "log.jsonl"));
|
|
541
|
+
if (content === undefined)
|
|
542
|
+
return false;
|
|
543
|
+
for (const line of content.trim().split("\n").toReversed()) {
|
|
544
|
+
try {
|
|
545
|
+
const entry = JSON.parse(line);
|
|
546
|
+
if (!isRecord(entry) || entry.ts !== ts)
|
|
547
|
+
continue;
|
|
548
|
+
return (entry.isMessagingBot === true &&
|
|
549
|
+
entry.platform === "slack" &&
|
|
550
|
+
Array.isArray(entry.slackBlocks) &&
|
|
551
|
+
entry.threadTs === threadTs);
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
297
559
|
getMessagingInfo() {
|
|
298
560
|
return {
|
|
299
561
|
name: "slack",
|
|
300
|
-
|
|
562
|
+
trustModel: "membership",
|
|
563
|
+
formattingGuide: "## Slack Formatting\nWrite standard Markdown/GFM: **bold**, _italic_, ~~strike~~, `code`, fenced code blocks, [links](url), lists, and pipe tables (rendered as native Slack tables).\nDo NOT use Slack mrkdwn syntax like *single-asterisk bold* or <url|label> links.",
|
|
301
564
|
channels: this.getAllChannels().map((c) => ({ id: c.id, name: c.name })),
|
|
302
565
|
users: this.getAllUsers().map((u) => ({
|
|
303
566
|
id: u.id,
|
|
@@ -352,7 +615,7 @@ export class SlackMessagingBot {
|
|
|
352
615
|
const eventForRun = eventPlan.event;
|
|
353
616
|
if (eventPlan.initialMessageTs && eventForRun.sessionKey) {
|
|
354
617
|
registerThreadSession({
|
|
355
|
-
conversationDir:
|
|
618
|
+
conversationDir: this.conversationDir(conversationId),
|
|
356
619
|
sessionKey: eventForRun.sessionKey,
|
|
357
620
|
});
|
|
358
621
|
}
|
|
@@ -360,7 +623,9 @@ export class SlackMessagingBot {
|
|
|
360
623
|
initialMessageTs: eventPlan.initialMessageTs,
|
|
361
624
|
}).sessionKey;
|
|
362
625
|
this.getQueue(runQueueKey).enqueue(async () => {
|
|
363
|
-
const slackEvent = {
|
|
626
|
+
const slackEvent = createConversationEvent({
|
|
627
|
+
platform: "slack",
|
|
628
|
+
address: eventForRun.address,
|
|
364
629
|
type: eventForRun.type,
|
|
365
630
|
conversationId,
|
|
366
631
|
conversationKind: eventForRun.conversationKind,
|
|
@@ -369,16 +634,12 @@ export class SlackMessagingBot {
|
|
|
369
634
|
thread_ts: eventForRun.thread_ts,
|
|
370
635
|
user: eventForRun.user,
|
|
371
636
|
text: eventForRun.text,
|
|
372
|
-
attachments: eventForRun.attachments
|
|
373
|
-
original: attachment.name,
|
|
374
|
-
localPath: attachment.localPath,
|
|
375
|
-
})),
|
|
637
|
+
attachments: eventForRun.attachments,
|
|
376
638
|
sessionKey: eventForRun.sessionKey,
|
|
377
|
-
};
|
|
639
|
+
});
|
|
378
640
|
const context = createSlackAdapters(slackEvent, this, {
|
|
379
641
|
initialMessageTs: eventPlan.initialMessageTs,
|
|
380
|
-
replyMode:
|
|
381
|
-
?.replyMode ?? "top-level",
|
|
642
|
+
replyMode: this.resolveReplyMode(eventForRun.address),
|
|
382
643
|
});
|
|
383
644
|
return this.handler.handleEvent(eventForRun, this, context);
|
|
384
645
|
});
|
|
@@ -399,21 +660,29 @@ export class SlackMessagingBot {
|
|
|
399
660
|
resolveQueueKey(conversationId, sessionKey) {
|
|
400
661
|
if (!isSlackThreadSessionKey(sessionKey))
|
|
401
662
|
return sessionKey;
|
|
402
|
-
if (this.handler.isRunning(sessionKey))
|
|
663
|
+
if (this.handler.isRunning(createOfficeAddress("slack", conversationId), sessionKey)) {
|
|
403
664
|
return sessionKey;
|
|
665
|
+
}
|
|
404
666
|
return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
|
|
405
667
|
}
|
|
406
668
|
hasKnownThreadSession(conversationId, sessionKey) {
|
|
407
669
|
return hasMaterializedChatSession({
|
|
408
|
-
conversationDir:
|
|
670
|
+
conversationDir: this.conversationDir(conversationId),
|
|
409
671
|
sessionKey,
|
|
410
672
|
});
|
|
411
673
|
}
|
|
412
674
|
processSlackMessageIntake(options) {
|
|
413
|
-
|
|
675
|
+
const absorbAttachmentFailure = () => {
|
|
676
|
+
void options.attachmentsPromise.catch((err) => {
|
|
677
|
+
log.logWarning("Failed to log Slack message", String(err));
|
|
678
|
+
});
|
|
679
|
+
};
|
|
680
|
+
processMessageIntake({
|
|
414
681
|
eventBase: options.event,
|
|
415
|
-
|
|
682
|
+
office: this.workspace.office(options.event.address),
|
|
416
683
|
isAutoReplyCandidate: options.isAutoReplyCandidate,
|
|
684
|
+
magicWord: { addressed: options.addressed, scopeFallback: "top-level" },
|
|
685
|
+
busyPolicy: "queue",
|
|
417
686
|
logEntryBase: {},
|
|
418
687
|
processAttachments: () => options.attachmentsPromise,
|
|
419
688
|
queueKey: options.queueKey,
|
|
@@ -421,8 +690,15 @@ export class SlackMessagingBot {
|
|
|
421
690
|
handler: this.handler,
|
|
422
691
|
bot: this,
|
|
423
692
|
createContext: (event) => this.createContext(event),
|
|
424
|
-
onNotTriggered: options.onNotTriggered,
|
|
425
693
|
deferAttachmentsUntilRun: true,
|
|
694
|
+
}).then((outcome) => {
|
|
695
|
+
// Slack logs eagerly via logUserMessage; when intake does not enqueue,
|
|
696
|
+
// nothing awaits the attachment download, so absorb its failure here.
|
|
697
|
+
if (outcome !== "enqueued")
|
|
698
|
+
absorbAttachmentFailure();
|
|
699
|
+
}, (err) => {
|
|
700
|
+
log.logWarning("Slack message intake failed", String(err));
|
|
701
|
+
absorbAttachmentFailure();
|
|
426
702
|
});
|
|
427
703
|
}
|
|
428
704
|
buildHomeView() {
|
|
@@ -460,7 +736,7 @@ export class SlackMessagingBot {
|
|
|
460
736
|
// Threshold for "stuck" detection (10 minutes)
|
|
461
737
|
const STUCK_THRESHOLD_MS = 10 * 60 * 1000;
|
|
462
738
|
for (const session of runningSessions) {
|
|
463
|
-
const channelId = session.sessionKey
|
|
739
|
+
const channelId = conversationIdOf(session.sessionKey);
|
|
464
740
|
const channel = this.channels.get(channelId);
|
|
465
741
|
const channelName = channel ? `#${channel.name}` : channelId;
|
|
466
742
|
const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
|
|
@@ -500,7 +776,14 @@ export class SlackMessagingBot {
|
|
|
500
776
|
{
|
|
501
777
|
type: "button",
|
|
502
778
|
text: { type: "plain_text", text: "Force Stop", emoji: true },
|
|
779
|
+
// The raw session key travels in `value` (Slack returns it
|
|
780
|
+
// verbatim); the action_id only routes and must be unique per
|
|
781
|
+
// view, so it carries a sanitized copy of the key. Never
|
|
782
|
+
// decode the key from the action_id — the `:`↔`_` rewrite is
|
|
783
|
+
// irreversible for conversation ids that contain `_` (GitHub's
|
|
784
|
+
// GH_owner_repo_number do, by design).
|
|
503
785
|
action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
|
|
786
|
+
value: session.sessionKey,
|
|
504
787
|
style: "danger",
|
|
505
788
|
},
|
|
506
789
|
],
|
|
@@ -559,24 +842,10 @@ export class SlackMessagingBot {
|
|
|
559
842
|
});
|
|
560
843
|
return { type: "home", blocks: blocks };
|
|
561
844
|
}
|
|
562
|
-
resolveStopTarget(channelId, threadTs) {
|
|
563
|
-
const directTarget = resolveStopTarget({
|
|
564
|
-
handler: this.handler,
|
|
565
|
-
conversationId: channelId,
|
|
566
|
-
sessionKey: resolveSlackSessionKey(channelId, threadTs),
|
|
567
|
-
});
|
|
568
|
-
if (directTarget)
|
|
569
|
-
return directTarget;
|
|
570
|
-
if (threadTs)
|
|
571
|
-
return null;
|
|
572
|
-
return resolveOnlyScopedStopTarget(this.handler, channelId);
|
|
573
|
-
}
|
|
574
|
-
isStopText(text) {
|
|
575
|
-
const normalized = text.trim().toLowerCase();
|
|
576
|
-
return normalized === "stop" || normalized === "/stop";
|
|
577
|
-
}
|
|
578
845
|
createCommandAdapters(conversationId, userId, userName, text, ts, options = {}) {
|
|
579
|
-
const message = {
|
|
846
|
+
const message = createConversationMessage({
|
|
847
|
+
platform: "slack",
|
|
848
|
+
conversationId,
|
|
580
849
|
id: ts,
|
|
581
850
|
sessionKey: conversationId,
|
|
582
851
|
conversationKind: options.ephemeralChannelId ? "shared" : "direct",
|
|
@@ -584,7 +853,7 @@ export class SlackMessagingBot {
|
|
|
584
853
|
userName,
|
|
585
854
|
text,
|
|
586
855
|
attachments: [],
|
|
587
|
-
};
|
|
856
|
+
});
|
|
588
857
|
const respond = async (responseText) => {
|
|
589
858
|
if (options.ephemeralChannelId) {
|
|
590
859
|
await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs);
|
|
@@ -624,6 +893,7 @@ export class SlackMessagingBot {
|
|
|
624
893
|
deleteResponse: async () => { },
|
|
625
894
|
};
|
|
626
895
|
return {
|
|
896
|
+
address: message.address,
|
|
627
897
|
message,
|
|
628
898
|
responder,
|
|
629
899
|
platform: this.getMessagingInfo(),
|
|
@@ -651,9 +921,10 @@ export class SlackMessagingBot {
|
|
|
651
921
|
isMessagingBot: false,
|
|
652
922
|
...(threadTs ? { threadTs } : {}),
|
|
653
923
|
});
|
|
654
|
-
const event = {
|
|
655
|
-
|
|
924
|
+
const event = createConversationEvent({
|
|
925
|
+
platform: "slack",
|
|
656
926
|
conversationId,
|
|
927
|
+
type: options.type ?? (isDirectMessage ? "dm" : "mention"),
|
|
657
928
|
conversationKind: isDirectMessage ? "direct" : "shared",
|
|
658
929
|
ts: eventTs,
|
|
659
930
|
user: payload.user_id,
|
|
@@ -661,14 +932,27 @@ export class SlackMessagingBot {
|
|
|
661
932
|
attachments: [],
|
|
662
933
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
663
934
|
sessionKey,
|
|
664
|
-
};
|
|
935
|
+
});
|
|
665
936
|
const context = this.createCommandAdapters(conversationId, payload.user_id, userName, commandText, eventTs, isDirectMessage ? { threadTs } : { ephemeralChannelId: conversationId, threadTs });
|
|
666
937
|
return { event, context };
|
|
667
938
|
}
|
|
668
|
-
|
|
939
|
+
/**
|
|
940
|
+
* Generic slash-command route: synthesize the command text per the
|
|
941
|
+
* manifest's SlackSlashRoute data and dispatch to the handler. `/pi-new`
|
|
942
|
+
* stays bespoke (routeSlashNewCommand) because it replies in 中文 and calls
|
|
943
|
+
* handleNewCommand directly.
|
|
944
|
+
*/
|
|
945
|
+
async routeSlashCommand(route, payload) {
|
|
669
946
|
const { event, context } = this.buildSlashCommandEvent(payload, {
|
|
670
|
-
|
|
671
|
-
|
|
947
|
+
includeText: route.includeText,
|
|
948
|
+
thread: route.thread,
|
|
949
|
+
...(route.privateCommand
|
|
950
|
+
? {
|
|
951
|
+
type: payload.channel_id.startsWith("D")
|
|
952
|
+
? "dm"
|
|
953
|
+
: "private_command",
|
|
954
|
+
}
|
|
955
|
+
: {}),
|
|
672
956
|
});
|
|
673
957
|
await this.handler.handleEvent(event, this, context);
|
|
674
958
|
}
|
|
@@ -697,19 +981,22 @@ export class SlackMessagingBot {
|
|
|
697
981
|
enqueueEvent: (event) => this.enqueueEvent(event),
|
|
698
982
|
getMessagingInfo: () => this.getMessagingInfo(),
|
|
699
983
|
};
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
984
|
+
const event = {
|
|
985
|
+
...createConversationEvent({
|
|
986
|
+
platform: "slack",
|
|
987
|
+
conversationId,
|
|
988
|
+
type: "dm",
|
|
989
|
+
conversationKind: "direct",
|
|
990
|
+
ts: eventTs,
|
|
991
|
+
user: payload.user_id,
|
|
992
|
+
text: payload.command,
|
|
993
|
+
attachments: [],
|
|
994
|
+
sessionKey: conversationId,
|
|
995
|
+
}),
|
|
996
|
+
channel: conversationId,
|
|
997
|
+
};
|
|
998
|
+
const context = createSlackAdapters(event, this);
|
|
999
|
+
await this.handler.handleNewCommand(conversationId, conversationId, commandMessagingBot, context.message, context.responder, context.platform);
|
|
713
1000
|
}
|
|
714
1001
|
setupEventHandlers() {
|
|
715
1002
|
this.socketClient.on("disconnect", (err) => {
|
|
@@ -721,10 +1008,27 @@ export class SlackMessagingBot {
|
|
|
721
1008
|
this.socketClient.on("unable_to_socket_mode_start", (err) => {
|
|
722
1009
|
log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
|
|
723
1010
|
});
|
|
1011
|
+
// Which events Slack actually delivers is otherwise unknowable from here:
|
|
1012
|
+
// a listener that never fires looks the same whether the subscription is
|
|
1013
|
+
// missing, the app needs reinstalling, or the surface never emits it at
|
|
1014
|
+
// all. Off by default — this is one line per event on a busy workspace.
|
|
1015
|
+
if (readEnv("SLACK_TRACE_EVENTS")) {
|
|
1016
|
+
this.socketClient.on("slack_event", ({ type, body }) => {
|
|
1017
|
+
const inner = body?.event;
|
|
1018
|
+
log.logInfo(`Slack socket event: ${type ?? "?"}${inner?.type ? ` → ${inner.type}` : ""}${inner?.tab ? ` (tab: ${inner.tab})` : ""}`);
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
724
1021
|
this.socketClient.on("app_mention", (payload) => this.handleAppMention(payload));
|
|
725
1022
|
this.socketClient.on("message", (payload) => this.handleMessageEvent(payload));
|
|
726
1023
|
this.socketClient.on("slash_commands", (payload) => void this.handleSlashCommand(payload));
|
|
727
1024
|
this.socketClient.on("app_home_opened", (payload) => this.handleAppHomeOpened(payload));
|
|
1025
|
+
// The assistant surface's lifecycle. Both events were already subscribed
|
|
1026
|
+
// in the app manifest and then dropped on the floor, which is why the pane
|
|
1027
|
+
// opened empty and the sidebar filled with untitled conversations.
|
|
1028
|
+
this.socketClient.on("assistant_thread_started", (payload) => this.handleAssistantThreadStarted(payload));
|
|
1029
|
+
this.socketClient.on("assistant_thread_context_changed", (payload) => this.handleAgentContextChangedEvent(payload));
|
|
1030
|
+
// agent_view's spelling of the same signal.
|
|
1031
|
+
this.socketClient.on("app_context_changed", (payload) => this.handleAgentContextChangedEvent(payload));
|
|
728
1032
|
this.socketClient.on("block_actions", (payload) => void this.handleBlockAction(payload));
|
|
729
1033
|
this.socketClient.on("interactive", (payload) => void this.handleBlockAction(payload));
|
|
730
1034
|
}
|
|
@@ -739,18 +1043,19 @@ export class SlackMessagingBot {
|
|
|
739
1043
|
// Thread replies get their own isolated session (channelId:thread_ts).
|
|
740
1044
|
const sessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
741
1045
|
const mentionText = this.stripOwnMention(e.text);
|
|
742
|
-
const slackEvent = {
|
|
743
|
-
|
|
1046
|
+
const slackEvent = createConversationEvent({
|
|
1047
|
+
platform: "slack",
|
|
744
1048
|
conversationId: e.channel,
|
|
1049
|
+
type: "mention",
|
|
745
1050
|
conversationKind: "shared",
|
|
746
|
-
channel: e.channel,
|
|
747
1051
|
ts: e.ts,
|
|
748
1052
|
thread_ts: e.thread_ts,
|
|
749
1053
|
user: e.user,
|
|
750
1054
|
text: mentionText || "Please respond to the recent conversation context.",
|
|
751
|
-
files: e.files,
|
|
752
1055
|
sessionKey,
|
|
753
|
-
|
|
1056
|
+
channel: e.channel,
|
|
1057
|
+
files: e.files,
|
|
1058
|
+
});
|
|
754
1059
|
const attachmentsPromise = this.logUserMessage(slackEvent);
|
|
755
1060
|
// Only trigger processing for messages AFTER startup (not replayed old messages)
|
|
756
1061
|
if (this.startupTs && e.ts < this.startupTs) {
|
|
@@ -761,26 +1066,12 @@ export class SlackMessagingBot {
|
|
|
761
1066
|
ack();
|
|
762
1067
|
return;
|
|
763
1068
|
}
|
|
764
|
-
// Check for stop command - execute immediately, don't queue!
|
|
765
|
-
if (this.isStopText(slackEvent.text)) {
|
|
766
|
-
const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
|
|
767
|
-
if (stopTarget) {
|
|
768
|
-
this.handler.handleStop(stopTarget, e.channel, this);
|
|
769
|
-
}
|
|
770
|
-
else {
|
|
771
|
-
this.postMessage(e.channel, formatNothingRunning("slack"));
|
|
772
|
-
}
|
|
773
|
-
void attachmentsPromise.catch((err) => {
|
|
774
|
-
log.logWarning("Failed to log Slack message", String(err));
|
|
775
|
-
});
|
|
776
|
-
ack();
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
1069
|
this.processSlackMessageIntake({
|
|
780
1070
|
event: slackEvent,
|
|
781
1071
|
attachmentsPromise,
|
|
782
1072
|
queueKey: this.resolveQueueKey(e.channel, sessionKey),
|
|
783
1073
|
isAutoReplyCandidate: false,
|
|
1074
|
+
addressed: true,
|
|
784
1075
|
});
|
|
785
1076
|
ack();
|
|
786
1077
|
}
|
|
@@ -793,7 +1084,13 @@ export class SlackMessagingBot {
|
|
|
793
1084
|
ack();
|
|
794
1085
|
return;
|
|
795
1086
|
}
|
|
796
|
-
|
|
1087
|
+
// API posts made with a user token carry the posting app's bot_id/app_id
|
|
1088
|
+
// alongside the human `user` — bot_id alone does not make the author a
|
|
1089
|
+
// bot. Only treat the message as bot-authored when the author is not a
|
|
1090
|
+
// known human user (bot users have is_bot=true; unknown authors stay on
|
|
1091
|
+
// the conservative bot path so loop protection holds).
|
|
1092
|
+
const authorIsKnownHuman = !!e.user && this.users.get(e.user)?.isBot === false;
|
|
1093
|
+
const isExternalMessagingBotMessage = e.subtype === "bot_message" || (!!e.bot_id && !authorIsKnownHuman);
|
|
797
1094
|
if (isExternalMessagingBotMessage) {
|
|
798
1095
|
if (e.subtype !== undefined && e.subtype !== "bot_message" && e.subtype !== "file_share") {
|
|
799
1096
|
ack();
|
|
@@ -821,7 +1118,11 @@ export class SlackMessagingBot {
|
|
|
821
1118
|
ack();
|
|
822
1119
|
return;
|
|
823
1120
|
}
|
|
824
|
-
|
|
1121
|
+
// message.im normally carries channel_type "im", but fall back to the
|
|
1122
|
+
// D-prefix convention (used by handleAppMention and session keys) so a
|
|
1123
|
+
// missing channel_type cannot silently demote a DM to an auto-reply
|
|
1124
|
+
// candidate that never triggers.
|
|
1125
|
+
const isDM = e.channel_type === "im" || e.channel.startsWith("D");
|
|
825
1126
|
const conversationKind = isDM ? "direct" : "shared";
|
|
826
1127
|
const isMessagingBotMention = e.text?.includes(`<@${this.botUserId}>`);
|
|
827
1128
|
// Skip channel @mentions - already handled by app_mention event
|
|
@@ -831,18 +1132,25 @@ export class SlackMessagingBot {
|
|
|
831
1132
|
}
|
|
832
1133
|
const isThreadReply = !!e.thread_ts;
|
|
833
1134
|
const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
|
|
834
|
-
|
|
835
|
-
|
|
1135
|
+
// Name the conversation from the first thing the person said. Guarded on
|
|
1136
|
+
// the assistant registry, so classic DM threads are untouched, and it
|
|
1137
|
+
// claims the title once per thread.
|
|
1138
|
+
if (isDM && e.thread_ts && e.text) {
|
|
1139
|
+
void titleAssistantThread(this.assistantOps(), this.assistantThreads, e.channel, e.thread_ts, this.stripOwnMention(e.text));
|
|
1140
|
+
}
|
|
1141
|
+
const slackEvent = createConversationEvent({
|
|
1142
|
+
platform: "slack",
|
|
836
1143
|
conversationId: e.channel,
|
|
1144
|
+
type: (isDM ? "dm" : "mention"),
|
|
837
1145
|
conversationKind,
|
|
838
|
-
channel: e.channel,
|
|
839
1146
|
ts: e.ts,
|
|
840
1147
|
thread_ts: e.thread_ts,
|
|
841
1148
|
user: e.user,
|
|
842
1149
|
text: this.stripOwnMention(e.text),
|
|
843
|
-
files: e.files,
|
|
844
1150
|
sessionKey,
|
|
845
|
-
|
|
1151
|
+
channel: e.channel,
|
|
1152
|
+
files: e.files,
|
|
1153
|
+
});
|
|
846
1154
|
const attachmentsPromise = this.logUserMessage(slackEvent);
|
|
847
1155
|
// Only trigger processing for messages AFTER startup (not replayed old messages)
|
|
848
1156
|
if (this.startupTs && e.ts < this.startupTs) {
|
|
@@ -853,59 +1161,26 @@ export class SlackMessagingBot {
|
|
|
853
1161
|
ack();
|
|
854
1162
|
return;
|
|
855
1163
|
}
|
|
856
|
-
|
|
857
|
-
if (isDM && this.isStopText(slackEvent.text)) {
|
|
858
|
-
const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
|
|
859
|
-
if (stopTarget) {
|
|
860
|
-
this.handler.handleStop(stopTarget, e.channel, this);
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
this.postMessage(e.channel, formatNothingRunning("slack"));
|
|
864
|
-
}
|
|
1164
|
+
if (!isDM && isThreadReply) {
|
|
865
1165
|
void attachmentsPromise.catch((err) => {
|
|
866
1166
|
log.logWarning("Failed to log Slack message", String(err));
|
|
867
1167
|
});
|
|
868
1168
|
ack();
|
|
869
1169
|
return;
|
|
870
1170
|
}
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
// persistent top-level channel session.
|
|
878
|
-
slackEvent.sessionKey = activeSessionKey;
|
|
879
|
-
this.processSlackMessageIntake({
|
|
880
|
-
event: slackEvent,
|
|
881
|
-
attachmentsPromise,
|
|
882
|
-
queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
|
|
883
|
-
isAutoReplyCandidate: false,
|
|
884
|
-
});
|
|
885
|
-
};
|
|
886
|
-
const logOnly = () => {
|
|
887
|
-
void attachmentsPromise.catch((err) => {
|
|
888
|
-
log.logWarning("Failed to log Slack message", String(err));
|
|
889
|
-
});
|
|
890
|
-
};
|
|
891
|
-
if (isDM) {
|
|
892
|
-
enqueueTriggered();
|
|
893
|
-
ack();
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
if (isThreadReply) {
|
|
897
|
-
logOnly();
|
|
898
|
-
ack();
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
const activeSessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
1171
|
+
const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
1172
|
+
// Auto-reply top-level channel messages start with no sessionKey because
|
|
1173
|
+
// they are only candidates until the policy allows them. Persist the
|
|
1174
|
+
// resolved key on the event; otherwise the runtime fallback treats the
|
|
1175
|
+
// message ts as a thread session (`channel:ts`) instead of the persistent
|
|
1176
|
+
// top-level channel session.
|
|
902
1177
|
slackEvent.sessionKey = activeSessionKey;
|
|
903
1178
|
this.processSlackMessageIntake({
|
|
904
1179
|
event: slackEvent,
|
|
905
1180
|
attachmentsPromise,
|
|
906
1181
|
queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
|
|
907
|
-
isAutoReplyCandidate:
|
|
908
|
-
|
|
1182
|
+
isAutoReplyCandidate: !isDM,
|
|
1183
|
+
addressed: isDM,
|
|
909
1184
|
});
|
|
910
1185
|
ack();
|
|
911
1186
|
}
|
|
@@ -916,54 +1191,20 @@ export class SlackMessagingBot {
|
|
|
916
1191
|
return;
|
|
917
1192
|
}
|
|
918
1193
|
const { command, text, channel_id, user_id, user_name, thread_ts } = payload;
|
|
1194
|
+
const entry = COMMAND_MANIFEST.find((candidate) => candidate.slackCommand === command);
|
|
1195
|
+
if (!entry)
|
|
1196
|
+
return;
|
|
919
1197
|
let handlerPromise = null;
|
|
920
|
-
if (
|
|
921
|
-
handlerPromise = this.routeSlashLoginCommand({
|
|
922
|
-
command,
|
|
923
|
-
text,
|
|
924
|
-
channel_id,
|
|
925
|
-
user_id,
|
|
926
|
-
user_name,
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
else if (command === "/pi-new") {
|
|
1198
|
+
if (entry.name === "new") {
|
|
930
1199
|
handlerPromise = this.routeSlashNewCommand({ command, channel_id, user_id, user_name });
|
|
931
1200
|
}
|
|
932
|
-
else if (
|
|
933
|
-
handlerPromise = this.
|
|
934
|
-
command,
|
|
935
|
-
channel_id,
|
|
936
|
-
user_id,
|
|
937
|
-
user_name,
|
|
938
|
-
thread_ts,
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
else if (command === "/pi-model") {
|
|
942
|
-
handlerPromise = this.routeSlashModelCommand({
|
|
943
|
-
command,
|
|
944
|
-
text,
|
|
945
|
-
channel_id,
|
|
946
|
-
user_id,
|
|
947
|
-
user_name,
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
else if (command === "/pi-sandbox" ||
|
|
951
|
-
command === "/pi-auto-reply" ||
|
|
952
|
-
command === "/pi-extensions") {
|
|
953
|
-
handlerPromise = this.routeSlashModelCommand({
|
|
1201
|
+
else if (entry.slackRoute) {
|
|
1202
|
+
handlerPromise = this.routeSlashCommand(entry.slackRoute, {
|
|
954
1203
|
command,
|
|
955
1204
|
text,
|
|
956
1205
|
channel_id,
|
|
957
1206
|
user_id,
|
|
958
1207
|
user_name,
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
else if (command === "/pi-admin") {
|
|
962
|
-
handlerPromise = this.routeSlashAdminCommand({
|
|
963
|
-
command,
|
|
964
|
-
channel_id,
|
|
965
|
-
user_id,
|
|
966
|
-
user_name,
|
|
967
1208
|
thread_ts,
|
|
968
1209
|
});
|
|
969
1210
|
}
|
|
@@ -977,6 +1218,16 @@ export class SlackMessagingBot {
|
|
|
977
1218
|
handleAppHomeOpened({ event, ack }) {
|
|
978
1219
|
const e = event;
|
|
979
1220
|
ack();
|
|
1221
|
+
// Under agent_view this is how Slack says "the person opened the app's
|
|
1222
|
+
// DM" — the signal assistant_thread_started used to carry. It fires on
|
|
1223
|
+
// every open, not once per conversation, so this path refreshes the
|
|
1224
|
+
// pinned prompts and deliberately does not greet.
|
|
1225
|
+
if (e.tab === "messages") {
|
|
1226
|
+
if (e.channel) {
|
|
1227
|
+
void handleAgentDmOpened(this.assistantOps(), this.assistantThreads, e.channel, e.context);
|
|
1228
|
+
}
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
980
1231
|
if (e.tab !== "home")
|
|
981
1232
|
return;
|
|
982
1233
|
this.webClient.views
|
|
@@ -1000,12 +1251,15 @@ export class SlackMessagingBot {
|
|
|
1000
1251
|
return;
|
|
1001
1252
|
}
|
|
1002
1253
|
ack();
|
|
1003
|
-
|
|
1254
|
+
// Prefer the verbatim key from `value`; the action_id fallback only
|
|
1255
|
+
// serves buttons rendered before `value` existed and misdecodes session
|
|
1256
|
+
// keys whose conversation id contains "_".
|
|
1257
|
+
const sessionKey = action.value ?? action.action_id.replace("force_stop_", "").replace(/_/g, ":");
|
|
1004
1258
|
const userId = body.user?.id;
|
|
1005
|
-
const channelId = body.container?.channel_id || sessionKey
|
|
1259
|
+
const channelId = body.container?.channel_id || conversationIdOf(sessionKey);
|
|
1006
1260
|
log.logInfo(`[Force Stop] User ${userId} requested force stop for ${sessionKey}`);
|
|
1007
1261
|
// Use handler's forceStop method
|
|
1008
|
-
this.handler.forceStop(sessionKey);
|
|
1262
|
+
this.handler.forceStop(createOfficeAddress("slack", channelId), sessionKey);
|
|
1009
1263
|
// Notify in channel
|
|
1010
1264
|
await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
|
|
1011
1265
|
// Refresh home tab
|
|
@@ -1030,6 +1284,38 @@ export class SlackMessagingBot {
|
|
|
1030
1284
|
const selectedOptions = Array.isArray(action.selected_options)
|
|
1031
1285
|
? action.selected_options
|
|
1032
1286
|
: undefined;
|
|
1287
|
+
const threadTs = container.thread_ts;
|
|
1288
|
+
const sessionKey = resolveSlackSessionKey(channelId, threadTs);
|
|
1289
|
+
// Extension-owned actions (ext:<slug>: namespaced by api.blockkit.post)
|
|
1290
|
+
// dispatch exclusively to the extension's onAction handler — no agent
|
|
1291
|
+
// run, no chat-history entry. They never fall through to the model: an
|
|
1292
|
+
// unconsumed namespaced action means the extension is gone, and its
|
|
1293
|
+
// routing id would only confuse an agent run.
|
|
1294
|
+
const extAction = parseExtActionId(action.action_id ?? "");
|
|
1295
|
+
if (extAction) {
|
|
1296
|
+
this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
|
|
1297
|
+
const consumed = await this.handler.handleExtensionAction({
|
|
1298
|
+
address: createOfficeAddress("slack", channelId),
|
|
1299
|
+
sessionKey,
|
|
1300
|
+
conversationKind: channelId.startsWith("D") ? "direct" : "shared",
|
|
1301
|
+
slug: extAction.slug,
|
|
1302
|
+
action: {
|
|
1303
|
+
actionId: extAction.actionId,
|
|
1304
|
+
value: action.value ?? selectedOption?.value ?? selectedOption?.text?.text,
|
|
1305
|
+
selectedValues: selectedOptions?.map((option) => option.value ?? option.text?.text ?? ""),
|
|
1306
|
+
userId,
|
|
1307
|
+
userName: body.user?.username ?? body.user?.name,
|
|
1308
|
+
conversationId: channelId,
|
|
1309
|
+
messageTs: container.message_ts,
|
|
1310
|
+
threadTs,
|
|
1311
|
+
},
|
|
1312
|
+
});
|
|
1313
|
+
if (!consumed) {
|
|
1314
|
+
log.logWarning(`[${channelId}] Dropped extension action ${action.action_id}`, `no handler registered by extension "${extAction.slug}"`);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1033
1319
|
const selectedText = selectedOption?.text?.text ?? selectedOption?.value;
|
|
1034
1320
|
const selectedTexts = selectedOptions?.map((option) => option.text?.text ?? option.value);
|
|
1035
1321
|
const valueText = selectedTexts?.length
|
|
@@ -1037,8 +1323,6 @@ export class SlackMessagingBot {
|
|
|
1037
1323
|
: (selectedText ?? action.value ?? action.action_id);
|
|
1038
1324
|
const text = `[Slack action] ${action.action_id}: ${valueText}`;
|
|
1039
1325
|
const ts = `action:${Date.now()}`;
|
|
1040
|
-
const threadTs = container.thread_ts;
|
|
1041
|
-
const sessionKey = resolveSlackSessionKey(channelId, threadTs);
|
|
1042
1326
|
this.logToFile(channelId, {
|
|
1043
1327
|
date: new Date().toISOString(),
|
|
1044
1328
|
ts,
|
|
@@ -1065,9 +1349,10 @@ export class SlackMessagingBot {
|
|
|
1065
1349
|
messageTs: container.message_ts,
|
|
1066
1350
|
},
|
|
1067
1351
|
});
|
|
1068
|
-
const event = {
|
|
1069
|
-
|
|
1352
|
+
const event = createConversationEvent({
|
|
1353
|
+
platform: "slack",
|
|
1070
1354
|
conversationId: channelId,
|
|
1355
|
+
type: "slack_action",
|
|
1071
1356
|
conversationKind: channelId.startsWith("D") ? "direct" : "shared",
|
|
1072
1357
|
ts,
|
|
1073
1358
|
user: userId,
|
|
@@ -1075,23 +1360,71 @@ export class SlackMessagingBot {
|
|
|
1075
1360
|
attachments: [],
|
|
1076
1361
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
1077
1362
|
sessionKey,
|
|
1078
|
-
};
|
|
1363
|
+
});
|
|
1079
1364
|
this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
|
|
1080
1365
|
const slackEvent = {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1366
|
+
...createConversationEvent({
|
|
1367
|
+
platform: "slack",
|
|
1368
|
+
conversationId: channelId,
|
|
1369
|
+
type: (event.conversationKind === "direct" ? "dm" : "mention"),
|
|
1370
|
+
conversationKind: event.conversationKind,
|
|
1371
|
+
ts,
|
|
1372
|
+
thread_ts: threadTs,
|
|
1373
|
+
user: userId,
|
|
1374
|
+
text,
|
|
1375
|
+
attachments: [],
|
|
1376
|
+
sessionKey,
|
|
1377
|
+
}),
|
|
1084
1378
|
channel: channelId,
|
|
1085
|
-
ts,
|
|
1086
|
-
thread_ts: threadTs,
|
|
1087
|
-
user: userId,
|
|
1088
|
-
text,
|
|
1089
1379
|
attachments: [],
|
|
1090
|
-
sessionKey,
|
|
1091
1380
|
};
|
|
1092
1381
|
return this.handler.handleEvent(event, this, this.createContext(slackEvent));
|
|
1093
1382
|
});
|
|
1094
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Download Slack message files into the office's attachments directory.
|
|
1386
|
+
* The Slack contract is all-or-error: any failed download throws after the
|
|
1387
|
+
* successful ones are on disk, so callers still log the message text.
|
|
1388
|
+
*/
|
|
1389
|
+
async processAttachments(channelId, files, timestamp) {
|
|
1390
|
+
const items = [];
|
|
1391
|
+
for (const file of files) {
|
|
1392
|
+
const url = file.url_private_download || file.url_private;
|
|
1393
|
+
if (!url)
|
|
1394
|
+
continue;
|
|
1395
|
+
if (!file.name) {
|
|
1396
|
+
throw new Error(`Attachment missing name for URL: ${url}`);
|
|
1397
|
+
}
|
|
1398
|
+
items.push({
|
|
1399
|
+
name: file.name,
|
|
1400
|
+
// Slack timestamps are float seconds; the stored filename uses ms.
|
|
1401
|
+
timestampMs: Math.floor(parseFloat(timestamp) * 1000),
|
|
1402
|
+
download: (destPath) => this.downloadSlackFile(url, destPath),
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
const office = this.workspace.office(createOfficeAddress("slack", channelId));
|
|
1406
|
+
const { saved, failed } = await saveIncomingAttachments(office, items);
|
|
1407
|
+
const firstFailure = failed[0];
|
|
1408
|
+
if (firstFailure) {
|
|
1409
|
+
const { name, error } = firstFailure;
|
|
1410
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1411
|
+
throw new Error(`Failed to download attachment ${name}: ${errorMsg}`, { cause: error });
|
|
1412
|
+
}
|
|
1413
|
+
return saved;
|
|
1414
|
+
}
|
|
1415
|
+
/** Authorized download with retry; Slack file URLs require the bot token. */
|
|
1416
|
+
async downloadSlackFile(url, destPath) {
|
|
1417
|
+
await withRetry(async () => {
|
|
1418
|
+
const response = await fetch(url, {
|
|
1419
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
1420
|
+
});
|
|
1421
|
+
if (!response.ok) {
|
|
1422
|
+
throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
|
|
1423
|
+
}
|
|
1424
|
+
const buffer = await response.arrayBuffer();
|
|
1425
|
+
await writeFile(destPath, Buffer.from(buffer));
|
|
1426
|
+
}, { maxAttempts: 3, baseDelayMs: 250, isRateLimited: isRetryableAttachmentDownloadError });
|
|
1427
|
+
}
|
|
1095
1428
|
/**
|
|
1096
1429
|
* Log a user message to log.jsonl after attachments are ready.
|
|
1097
1430
|
*/
|
|
@@ -1101,7 +1434,7 @@ export class SlackMessagingBot {
|
|
|
1101
1434
|
let attachmentError;
|
|
1102
1435
|
if (event.files) {
|
|
1103
1436
|
try {
|
|
1104
|
-
attachments = await this.
|
|
1437
|
+
attachments = await this.processAttachments(event.channel, event.files, event.ts);
|
|
1105
1438
|
}
|
|
1106
1439
|
catch (err) {
|
|
1107
1440
|
attachmentError = err;
|
|
@@ -1126,7 +1459,7 @@ export class SlackMessagingBot {
|
|
|
1126
1459
|
}
|
|
1127
1460
|
async logExternalMessagingBotMessage(event) {
|
|
1128
1461
|
const attachments = event.files
|
|
1129
|
-
? await this.
|
|
1462
|
+
? await this.processAttachments(event.channel, event.files, event.ts)
|
|
1130
1463
|
: [];
|
|
1131
1464
|
const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
|
|
1132
1465
|
this.logToFile(event.channel, {
|
|
@@ -1149,7 +1482,7 @@ export class SlackMessagingBot {
|
|
|
1149
1482
|
// Private - Backfill
|
|
1150
1483
|
// ==========================================================================
|
|
1151
1484
|
async getExistingTimestamps(channelId) {
|
|
1152
|
-
const logPath = join(this.
|
|
1485
|
+
const logPath = join(this.conversationDir(channelId), "log.jsonl");
|
|
1153
1486
|
const timestamps = new Set();
|
|
1154
1487
|
if (!existsSync(logPath))
|
|
1155
1488
|
return timestamps;
|
|
@@ -1157,7 +1490,7 @@ export class SlackMessagingBot {
|
|
|
1157
1490
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1158
1491
|
for (let i = 0; i < lines.length; i++) {
|
|
1159
1492
|
try {
|
|
1160
|
-
const entry = JSON.parse(lines[i]);
|
|
1493
|
+
const entry = JSON.parse(lines[i] ?? "");
|
|
1161
1494
|
if (entry.ts)
|
|
1162
1495
|
timestamps.add(entry.ts);
|
|
1163
1496
|
}
|
|
@@ -1235,7 +1568,7 @@ export class SlackMessagingBot {
|
|
|
1235
1568
|
const user = this.users.get(msg.user);
|
|
1236
1569
|
const text = this.stripOwnMention(msg.text);
|
|
1237
1570
|
const attachments = msg.files
|
|
1238
|
-
? await this.
|
|
1571
|
+
? await this.processAttachments(channelId, msg.files, msg.ts)
|
|
1239
1572
|
: [];
|
|
1240
1573
|
this.logToFile(channelId, {
|
|
1241
1574
|
date: new Date(parseFloat(msg.ts) * 1000).toISOString(),
|
|
@@ -1256,7 +1589,7 @@ export class SlackMessagingBot {
|
|
|
1256
1589
|
// Only backfill channels that already have a log.jsonl (mikan has interacted with them before)
|
|
1257
1590
|
const channelsToBackfill = [];
|
|
1258
1591
|
for (const [channelId, channel] of this.channels) {
|
|
1259
|
-
const logPath = join(this.
|
|
1592
|
+
const logPath = join(this.conversationDir(channelId), "log.jsonl");
|
|
1260
1593
|
if (existsSync(logPath)) {
|
|
1261
1594
|
channelsToBackfill.push([channelId, channel]);
|
|
1262
1595
|
}
|
|
@@ -1274,7 +1607,7 @@ export class SlackMessagingBot {
|
|
|
1274
1607
|
log.logWarning(`Failed to backfill #${channel.name}`, String(error));
|
|
1275
1608
|
}
|
|
1276
1609
|
// Add delay between channels to avoid hitting Slack rate limits
|
|
1277
|
-
if (channelId !== channelsToBackfill[channelsToBackfill.length - 1][0]) {
|
|
1610
|
+
if (channelId !== channelsToBackfill[channelsToBackfill.length - 1]?.[0]) {
|
|
1278
1611
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1279
1612
|
}
|
|
1280
1613
|
}
|
|
@@ -1296,6 +1629,7 @@ export class SlackMessagingBot {
|
|
|
1296
1629
|
id: u.id,
|
|
1297
1630
|
userName: u.name,
|
|
1298
1631
|
displayName: u.real_name || u.name,
|
|
1632
|
+
isBot: !!u.is_bot,
|
|
1299
1633
|
});
|
|
1300
1634
|
}
|
|
1301
1635
|
}
|