@geminixiang/mikan 1.0.0-beta.7 → 1.0.0-beta.71
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 +1052 -0
- package/README.md +98 -38
- package/dist/adapters/commands/admin.d.ts.map +1 -0
- package/dist/{commands → adapters/commands}/admin.js +11 -8
- package/dist/adapters/commands/admin.js.map +1 -0
- package/dist/{commands → adapters/commands}/auto-reply.d.ts +0 -3
- package/dist/adapters/commands/auto-reply.d.ts.map +1 -0
- package/dist/adapters/commands/auto-reply.js +47 -0
- package/dist/adapters/commands/auto-reply.js.map +1 -0
- package/dist/adapters/commands/login.d.ts +6 -0
- package/dist/adapters/commands/login.d.ts.map +1 -0
- package/dist/adapters/commands/login.js +166 -0
- package/dist/adapters/commands/login.js.map +1 -0
- package/dist/adapters/commands/manifest.d.ts +37 -0
- package/dist/adapters/commands/manifest.d.ts.map +1 -0
- package/dist/adapters/commands/manifest.js +155 -0
- package/dist/adapters/commands/manifest.js.map +1 -0
- package/dist/adapters/commands/model.d.ts +15 -0
- package/dist/adapters/commands/model.d.ts.map +1 -0
- package/dist/adapters/commands/model.js +153 -0
- package/dist/adapters/commands/model.js.map +1 -0
- package/dist/adapters/commands/new.d.ts.map +1 -0
- package/dist/adapters/commands/new.js +41 -0
- package/dist/adapters/commands/new.js.map +1 -0
- package/dist/adapters/commands/registry.d.ts +5 -0
- package/dist/adapters/commands/registry.d.ts.map +1 -0
- package/dist/adapters/commands/registry.js +43 -0
- package/dist/adapters/commands/registry.js.map +1 -0
- package/dist/adapters/commands/sandbox.d.ts.map +1 -0
- package/dist/adapters/commands/sandbox.js +125 -0
- package/dist/adapters/commands/sandbox.js.map +1 -0
- package/dist/adapters/commands/session-view.d.ts.map +1 -0
- package/dist/{commands → adapters/commands}/session-view.js +23 -16
- package/dist/adapters/commands/session-view.js.map +1 -0
- package/dist/adapters/commands/types.d.ts +145 -0
- package/dist/adapters/commands/types.d.ts.map +1 -0
- package/dist/adapters/commands/types.js.map +1 -0
- package/dist/adapters/commands/utils.d.ts +14 -0
- package/dist/adapters/commands/utils.d.ts.map +1 -0
- package/dist/{commands → adapters/commands}/utils.js +18 -1
- package/dist/adapters/commands/utils.js.map +1 -0
- package/dist/adapters/discord/bot.d.ts +11 -8
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +223 -256
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/components.d.ts +28 -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 +2 -6
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +64 -218
- 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 +84 -0
- package/dist/adapters/discord/format.js.map +1 -0
- package/dist/adapters/discord/types.d.ts +8 -1
- package/dist/adapters/discord/types.d.ts.map +1 -1
- package/dist/adapters/discord/types.js.map +1 -1
- package/dist/adapters/github/bot.d.ts +102 -0
- package/dist/adapters/github/bot.d.ts.map +1 -0
- package/dist/adapters/github/bot.js +720 -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 +301 -0
- package/dist/adapters/github/client.js.map +1 -0
- package/dist/adapters/github/context.d.ts +5 -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/github-ops.d.ts +78 -0
- package/dist/adapters/github/github-ops.d.ts.map +1 -0
- package/dist/adapters/github/github-ops.js +337 -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 +55 -0
- package/dist/adapters/github/tool-pack.js.map +1 -0
- package/dist/adapters/github/tools/checks.d.ts +18 -0
- package/dist/adapters/github/tools/checks.d.ts.map +1 -0
- package/dist/adapters/github/tools/checks.js +86 -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 +345 -0
- package/dist/adapters/github/types.d.ts.map +1 -0
- package/dist/adapters/github/types.js.map +1 -0
- package/dist/adapters/github/webhook.d.ts +20 -0
- package/dist/adapters/github/webhook.d.ts.map +1 -0
- package/dist/adapters/github/webhook.js +78 -0
- package/dist/adapters/github/webhook.js.map +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +19 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/intake.d.ts +17 -6
- package/dist/adapters/intake.d.ts.map +1 -1
- package/dist/adapters/intake.js +96 -27
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/markdown-tables.d.ts +19 -0
- package/dist/adapters/markdown-tables.d.ts.map +1 -0
- package/dist/adapters/markdown-tables.js +152 -0
- package/dist/adapters/markdown-tables.js.map +1 -0
- package/dist/{platform-messages.d.ts → adapters/messages.d.ts} +3 -2
- package/dist/adapters/messages.d.ts.map +1 -0
- package/dist/{platform-messages.js → adapters/messages.js} +4 -1
- package/dist/adapters/messages.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 +443 -0
- package/dist/adapters/progressive-renderer.js.map +1 -0
- package/dist/adapters/shared.d.ts +37 -11
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +130 -24
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/assistant.d.ts +73 -0
- package/dist/adapters/slack/assistant.d.ts.map +1 -0
- package/dist/adapters/slack/assistant.js +205 -0
- package/dist/adapters/slack/assistant.js.map +1 -0
- package/dist/adapters/slack/auto-reply-context.d.ts +21 -0
- package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -0
- package/dist/adapters/slack/auto-reply-context.js +28 -0
- package/dist/adapters/slack/auto-reply-context.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 +104 -148
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +154 -21
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +1037 -484
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +2 -6
- 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/jev-context.d.ts +30 -0
- package/dist/adapters/slack/jev-context.d.ts.map +1 -0
- package/dist/adapters/slack/jev-context.js +67 -0
- package/dist/adapters/slack/jev-context.js.map +1 -0
- package/dist/adapters/slack/markdown.d.ts +3 -0
- package/dist/adapters/slack/markdown.d.ts.map +1 -0
- package/dist/adapters/slack/markdown.js +48 -0
- package/dist/adapters/slack/markdown.js.map +1 -0
- package/dist/adapters/slack/persona.d.ts +15 -0
- package/dist/adapters/slack/persona.d.ts.map +1 -0
- package/dist/adapters/slack/persona.js +22 -0
- package/dist/adapters/slack/persona.js.map +1 -0
- package/dist/adapters/slack/response-lifecycle.d.ts +1 -1
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +261 -345
- 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/task-intent.d.ts +32 -0
- package/dist/adapters/slack/task-intent.d.ts.map +1 -0
- package/dist/adapters/slack/task-intent.js +72 -0
- package/dist/adapters/slack/task-intent.js.map +1 -0
- package/dist/adapters/slack/task-status.d.ts +7 -0
- package/dist/adapters/slack/task-status.d.ts.map +1 -0
- package/dist/adapters/slack/task-status.js +107 -0
- package/dist/adapters/slack/task-status.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/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 +73 -2
- package/dist/adapters/slack/types.d.ts.map +1 -1
- package/dist/adapters/slack/types.js.map +1 -1
- package/dist/adapters/slack/update-diagnostics.d.ts +14 -0
- package/dist/adapters/slack/update-diagnostics.d.ts.map +1 -0
- package/dist/adapters/slack/update-diagnostics.js +134 -0
- package/dist/adapters/slack/update-diagnostics.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts +10 -9
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +133 -202
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +2 -6
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +53 -190
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/types.d.ts +1 -1
- package/dist/adapters/telegram/types.d.ts.map +1 -1
- package/dist/adapters/telegram/types.js.map +1 -1
- package/dist/adapters/types.d.ts +144 -13
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/adapters/web/admin/portal.d.ts +44 -0
- package/dist/adapters/web/admin/portal.d.ts.map +1 -0
- package/dist/adapters/web/admin/portal.js +3207 -0
- package/dist/adapters/web/admin/portal.js.map +1 -0
- package/dist/{web → adapters/web}/admin/provider-models.d.ts +1 -1
- package/dist/adapters/web/admin/provider-models.d.ts.map +1 -0
- package/dist/{web → adapters/web}/admin/provider-models.js +1 -1
- package/dist/adapters/web/admin/provider-models.js.map +1 -0
- package/dist/adapters/web/admin/types.d.ts +53 -0
- package/dist/adapters/web/admin/types.d.ts.map +1 -0
- package/dist/adapters/web/admin/types.js.map +1 -0
- package/dist/adapters/web/login/oauth.d.ts +5 -0
- package/dist/adapters/web/login/oauth.d.ts.map +1 -0
- package/dist/{web → adapters/web}/login/oauth.js +50 -85
- package/dist/adapters/web/login/oauth.js.map +1 -0
- package/dist/{web → adapters/web}/login/portal.d.ts +9 -3
- package/dist/adapters/web/login/portal.d.ts.map +1 -0
- package/dist/{web → adapters/web}/login/portal.js +161 -119
- package/dist/adapters/web/login/portal.js.map +1 -0
- package/dist/{web → adapters/web}/login/types.d.ts +1 -12
- package/dist/adapters/web/login/types.d.ts.map +1 -0
- package/dist/adapters/web/login/types.js.map +1 -0
- package/dist/adapters/web/portal-shell.d.ts +22 -0
- package/dist/adapters/web/portal-shell.d.ts.map +1 -0
- package/dist/{portal-shell.js → adapters/web/portal-shell.js} +78 -2
- package/dist/adapters/web/portal-shell.js.map +1 -0
- package/dist/{web → adapters/web}/server.d.ts +14 -8
- package/dist/adapters/web/server.d.ts.map +1 -0
- package/dist/{web → adapters/web}/server.js +14 -7
- package/dist/adapters/web/server.js.map +1 -0
- package/dist/adapters/web/session-view/portal.d.ts +20 -0
- package/dist/adapters/web/session-view/portal.d.ts.map +1 -0
- package/dist/{web → adapters/web}/session-view/portal.js +633 -162
- package/dist/adapters/web/session-view/portal.js.map +1 -0
- package/dist/{web → adapters/web}/session-view/types.d.ts +11 -6
- package/dist/adapters/web/session-view/types.d.ts.map +1 -0
- package/dist/adapters/web/session-view/types.js.map +1 -0
- package/dist/adapters/web/token-store.d.ts.map +1 -0
- package/dist/{web → adapters/web}/token-store.js +1 -1
- package/dist/adapters/web/token-store.js.map +1 -0
- package/dist/adapters/web/types.d.ts.map +1 -0
- package/dist/adapters/web/types.js.map +1 -0
- package/dist/cli/arg-grammar.d.ts +11 -0
- package/dist/cli/arg-grammar.d.ts.map +1 -0
- package/dist/cli/arg-grammar.js +50 -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 +90 -0
- package/dist/cli/boot.js.map +1 -0
- package/dist/cli/download.d.ts.map +1 -0
- package/dist/cli/download.js +88 -0
- package/dist/cli/download.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 +181 -0
- package/dist/cli/office.js.map +1 -0
- package/dist/cli/onboard.d.ts +10 -0
- package/dist/cli/onboard.d.ts.map +1 -0
- package/dist/cli/onboard.js +225 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/process-lifecycle.d.ts +19 -0
- package/dist/cli/process-lifecycle.d.ts.map +1 -0
- package/dist/cli/process-lifecycle.js +67 -0
- package/dist/cli/process-lifecycle.js.map +1 -0
- package/dist/cli/sessions.d.ts +2 -0
- package/dist/cli/sessions.d.ts.map +1 -0
- package/dist/cli/sessions.js +88 -0
- package/dist/cli/sessions.js.map +1 -0
- package/dist/cli/types.d.ts +25 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/dream/index.d.ts +20 -0
- package/dist/dream/index.d.ts.map +1 -0
- package/dist/dream/index.js +264 -0
- package/dist/dream/index.js.map +1 -0
- package/dist/dream/types.d.ts +30 -0
- package/dist/dream/types.d.ts.map +1 -0
- package/dist/dream/types.js +2 -0
- package/dist/dream/types.js.map +1 -0
- package/dist/env-manifest.d.ts +38 -0
- package/dist/env-manifest.d.ts.map +1 -0
- package/dist/env-manifest.js +260 -0
- package/dist/env-manifest.js.map +1 -0
- package/dist/events/index.d.ts +172 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +340 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/scheduler.d.ts +34 -0
- package/dist/events/scheduler.d.ts.map +1 -0
- package/dist/events/scheduler.js +231 -0
- package/dist/events/scheduler.js.map +1 -0
- package/dist/file-guards.d.ts +26 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +133 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/harness/execution-env.d.ts +19 -0
- package/dist/harness/execution-env.d.ts.map +1 -0
- package/dist/harness/execution-env.js +262 -0
- package/dist/harness/execution-env.js.map +1 -0
- package/dist/harness/execution-resolver.d.ts +25 -0
- package/dist/harness/execution-resolver.d.ts.map +1 -0
- package/dist/harness/execution-resolver.js +156 -0
- package/dist/harness/execution-resolver.js.map +1 -0
- package/dist/harness/index.d.ts +18 -13
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +11 -11
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/jev.d.ts +86 -0
- package/dist/harness/jev.d.ts.map +1 -0
- package/dist/harness/jev.js +167 -0
- package/dist/harness/jev.js.map +1 -0
- package/dist/harness/mcp.d.ts +38 -0
- package/dist/harness/mcp.d.ts.map +1 -0
- package/dist/harness/mcp.js +304 -0
- package/dist/harness/mcp.js.map +1 -0
- package/dist/harness/models.d.ts +7 -4
- package/dist/harness/models.d.ts.map +1 -1
- package/dist/harness/models.js +54 -55
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/open-connector.d.ts +25 -0
- package/dist/harness/open-connector.d.ts.map +1 -0
- package/dist/harness/open-connector.js +178 -0
- package/dist/harness/open-connector.js.map +1 -0
- package/dist/harness/presenter.d.ts +31 -0
- package/dist/harness/presenter.d.ts.map +1 -0
- package/dist/harness/presenter.js +859 -0
- package/dist/harness/presenter.js.map +1 -0
- package/dist/harness/prompt.d.ts +30 -0
- package/dist/harness/prompt.d.ts.map +1 -0
- package/dist/harness/prompt.js +420 -0
- package/dist/harness/prompt.js.map +1 -0
- package/dist/harness/runner.d.ts +2 -123
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +551 -416
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session.d.ts +122 -0
- package/dist/harness/session.d.ts.map +1 -0
- package/dist/harness/session.js +581 -0
- package/dist/harness/session.js.map +1 -0
- package/dist/harness/skills.d.ts +17 -28
- package/dist/harness/skills.d.ts.map +1 -1
- package/dist/harness/skills.js +104 -39
- 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 +293 -0
- package/dist/harness/subagent-profiles.js.map +1 -0
- package/dist/harness/subagent.d.ts +64 -0
- package/dist/harness/subagent.d.ts.map +1 -0
- package/dist/harness/subagent.js +687 -0
- package/dist/harness/subagent.js.map +1 -0
- package/dist/harness/tools/attach.d.ts.map +1 -0
- package/dist/{adapters/slack → harness}/tools/attach.js +1 -1
- package/dist/harness/tools/attach.js.map +1 -0
- package/dist/{tools → harness/tools}/event.d.ts +4 -33
- package/dist/harness/tools/event.d.ts.map +1 -0
- package/dist/harness/tools/event.js +175 -0
- package/dist/harness/tools/event.js.map +1 -0
- package/dist/harness/tools/generate-image.d.ts +23 -0
- package/dist/harness/tools/generate-image.d.ts.map +1 -0
- package/dist/harness/tools/generate-image.js +65 -0
- package/dist/harness/tools/generate-image.js.map +1 -0
- package/dist/harness/tools/host-fn-tool.d.ts +21 -0
- package/dist/harness/tools/host-fn-tool.d.ts.map +1 -0
- package/dist/harness/tools/host-fn-tool.js +32 -0
- package/dist/harness/tools/host-fn-tool.js.map +1 -0
- package/dist/harness/tools/index.d.ts +41 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +51 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/jev.d.ts +21 -0
- package/dist/harness/tools/jev.d.ts.map +1 -0
- package/dist/harness/tools/jev.js +109 -0
- package/dist/harness/tools/jev.js.map +1 -0
- package/dist/harness/tools/pi-tools.d.ts +18 -0
- package/dist/harness/tools/pi-tools.d.ts.map +1 -0
- package/dist/harness/tools/pi-tools.js +84 -0
- package/dist/harness/tools/pi-tools.js.map +1 -0
- package/dist/harness/tools/react.d.ts.map +1 -0
- package/dist/{tools → harness/tools}/react.js +6 -17
- package/dist/harness/tools/react.js.map +1 -0
- package/dist/{tools → harness/tools}/sandbox.d.ts +4 -4
- package/dist/harness/tools/sandbox.d.ts.map +1 -0
- package/dist/{tools → harness/tools}/sandbox.js +12 -8
- package/dist/harness/tools/sandbox.js.map +1 -0
- package/dist/harness/tools/subagent.d.ts +85 -0
- package/dist/harness/tools/subagent.d.ts.map +1 -0
- package/dist/harness/tools/subagent.js +677 -0
- package/dist/harness/tools/subagent.js.map +1 -0
- package/dist/harness/tools/task.d.ts +20 -0
- package/dist/harness/tools/task.d.ts.map +1 -0
- package/dist/harness/tools/task.js +79 -0
- package/dist/harness/tools/task.js.map +1 -0
- package/dist/harness/tools/types.d.ts +29 -0
- package/dist/harness/tools/types.d.ts.map +1 -0
- package/dist/harness/tools/types.js +2 -0
- package/dist/harness/tools/types.js.map +1 -0
- package/dist/harness/types.d.ts +384 -25
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +33 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +4 -1
- package/dist/log.js.map +1 -1
- package/dist/main.js +357 -198
- package/dist/main.js.map +1 -1
- package/dist/observability/index.d.ts +33 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +257 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/instrument.js +14 -6
- package/dist/observability/instrument.js.map +1 -1
- package/dist/observability/otel.d.ts +10 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +183 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/observability/sentry.d.ts +40 -7
- package/dist/observability/sentry.d.ts.map +1 -1
- package/dist/observability/sentry.js +176 -32
- package/dist/observability/sentry.js.map +1 -1
- package/dist/observability/types.d.ts +24 -3
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/observability/types.js.map +1 -1
- package/dist/office/index.d.ts +139 -0
- package/dist/office/index.d.ts.map +1 -0
- package/dist/office/index.js +1084 -0
- package/dist/office/index.js.map +1 -0
- package/dist/office/projection.d.ts +42 -0
- package/dist/office/projection.d.ts.map +1 -0
- package/dist/office/projection.js +182 -0
- package/dist/office/projection.js.map +1 -0
- package/dist/office/types.d.ts +116 -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/runtime/conversation-runtime.d.ts +1 -1
- package/dist/runtime/conversation-runtime.d.ts.map +1 -1
- package/dist/runtime/conversation-runtime.js +423 -152
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +76 -0
- package/dist/runtime/session-lifecycle.d.ts.map +1 -0
- package/dist/runtime/session-lifecycle.js +404 -0
- package/dist/runtime/session-lifecycle.js.map +1 -0
- package/dist/runtime/types.d.ts +63 -15
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts +4 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +54 -52
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts +3 -0
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +19 -7
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/host.d.ts +3 -0
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +44 -35
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +42 -0
- package/dist/sandbox/identity.d.ts.map +1 -0
- package/dist/sandbox/identity.js +77 -0
- package/dist/sandbox/identity.js.map +1 -0
- package/dist/sandbox/index.d.ts +24 -6
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +77 -17
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/provisioner.d.ts +139 -0
- package/dist/sandbox/provisioner.d.ts.map +1 -0
- package/dist/{provisioner.js → sandbox/provisioner.js} +378 -24
- package/dist/sandbox/provisioner.js.map +1 -0
- package/dist/sandbox/types.d.ts +43 -9
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +24 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +92 -1
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts +23 -0
- package/dist/sessions/chat-history-sync.d.ts.map +1 -0
- package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +239 -175
- package/dist/sessions/chat-history-sync.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/migrate-common.d.ts +43 -0
- package/dist/sessions/migrate-common.d.ts.map +1 -0
- package/dist/sessions/migrate-common.js +99 -0
- package/dist/sessions/migrate-common.js.map +1 -0
- package/dist/sessions/migrate-pi-084.d.ts +8 -0
- package/dist/sessions/migrate-pi-084.d.ts.map +1 -0
- package/dist/sessions/migrate-pi-084.js +302 -0
- package/dist/sessions/migrate-pi-084.js.map +1 -0
- package/dist/sessions/migrate-v3.d.ts +15 -0
- package/dist/sessions/migrate-v3.d.ts.map +1 -0
- package/dist/sessions/migrate-v3.js +419 -0
- package/dist/sessions/migrate-v3.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/session-store.d.ts +61 -0
- package/dist/sessions/session-store.d.ts.map +1 -0
- package/dist/sessions/session-store.js +684 -0
- package/dist/sessions/session-store.js.map +1 -0
- package/dist/sessions/store.d.ts +17 -10
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +182 -47
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +74 -8
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/settings/apply.d.ts +16 -0
- package/dist/settings/apply.d.ts.map +1 -0
- package/dist/settings/apply.js +60 -0
- package/dist/settings/apply.js.map +1 -0
- package/dist/settings/index.d.ts +126 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +422 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/migrate.d.ts +21 -0
- package/dist/settings/migrate.d.ts.map +1 -0
- package/dist/settings/migrate.js +86 -0
- package/dist/settings/migrate.js.map +1 -0
- package/dist/types.d.ts +330 -141
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/vault/index.d.ts +52 -2
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +218 -86
- 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/package.json +79 -40
- package/dist/adapter.d.ts +0 -2
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -2
- package/dist/adapter.js.map +0 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +0 -1
- package/dist/adapters/slack/tools/attach.js.map +0 -1
- 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/agent-events.d.ts +0 -5
- package/dist/agent-events.d.ts.map +0 -1
- package/dist/agent-events.js +0 -26
- package/dist/agent-events.js.map +0 -1
- package/dist/agent.d.ts +0 -39
- package/dist/agent.d.ts.map +0 -1
- package/dist/agent.js +0 -1291
- package/dist/agent.js.map +0 -1
- package/dist/cli/ext-git.d.ts +0 -22
- package/dist/cli/ext-git.d.ts.map +0 -1
- package/dist/cli/ext-git.js +0 -76
- package/dist/cli/ext-git.js.map +0 -1
- package/dist/cli/ext.d.ts +0 -2
- package/dist/cli/ext.d.ts.map +0 -1
- package/dist/cli/ext.js +0 -217
- package/dist/cli/ext.js.map +0 -1
- package/dist/commands/admin.d.ts.map +0 -1
- package/dist/commands/admin.js.map +0 -1
- package/dist/commands/auto-reply.d.ts.map +0 -1
- package/dist/commands/auto-reply.js +0 -76
- package/dist/commands/auto-reply.js.map +0 -1
- package/dist/commands/extensions.d.ts +0 -14
- package/dist/commands/extensions.d.ts.map +0 -1
- package/dist/commands/extensions.js +0 -55
- package/dist/commands/extensions.js.map +0 -1
- package/dist/commands/login.d.ts +0 -5
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -115
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/model.d.ts +0 -10
- package/dist/commands/model.d.ts.map +0 -1
- package/dist/commands/model.js +0 -102
- package/dist/commands/model.js.map +0 -1
- package/dist/commands/new.d.ts.map +0 -1
- package/dist/commands/new.js +0 -25
- package/dist/commands/new.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/commands/registry.d.ts +0 -5
- package/dist/commands/registry.d.ts.map +0 -1
- package/dist/commands/registry.js +0 -31
- package/dist/commands/registry.js.map +0 -1
- package/dist/commands/sandbox.d.ts.map +0 -1
- package/dist/commands/sandbox.js +0 -78
- package/dist/commands/sandbox.js.map +0 -1
- package/dist/commands/session-view.d.ts.map +0 -1
- package/dist/commands/session-view.js.map +0 -1
- package/dist/commands/types.d.ts +0 -66
- package/dist/commands/types.d.ts.map +0 -1
- package/dist/commands/types.js.map +0 -1
- package/dist/commands/utils.d.ts +0 -11
- package/dist/commands/utils.d.ts.map +0 -1
- package/dist/commands/utils.js.map +0 -1
- package/dist/config.d.ts +0 -78
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -450
- package/dist/config.js.map +0 -1
- package/dist/download.d.ts.map +0 -1
- package/dist/download.js +0 -89
- package/dist/download.js.map +0 -1
- package/dist/events.d.ts +0 -43
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -456
- package/dist/events.js.map +0 -1
- package/dist/execution-resolver.d.ts +0 -21
- package/dist/execution-resolver.d.ts.map +0 -1
- package/dist/execution-resolver.js +0 -170
- package/dist/execution-resolver.js.map +0 -1
- package/dist/harness/auth.d.ts +0 -16
- package/dist/harness/auth.d.ts.map +0 -1
- package/dist/harness/auth.js +0 -86
- package/dist/harness/auth.js.map +0 -1
- package/dist/harness/extensions/loader.d.ts +0 -86
- package/dist/harness/extensions/loader.d.ts.map +0 -1
- package/dist/harness/extensions/loader.js +0 -525
- package/dist/harness/extensions/loader.js.map +0 -1
- package/dist/harness/extensions/registry.d.ts +0 -23
- package/dist/harness/extensions/registry.d.ts.map +0 -1
- package/dist/harness/extensions/registry.js +0 -45
- package/dist/harness/extensions/registry.js.map +0 -1
- package/dist/harness/extensions/types.d.ts +0 -248
- package/dist/harness/extensions/types.d.ts.map +0 -1
- package/dist/harness/extensions/types.js.map +0 -1
- package/dist/harness/session-store.d.ts +0 -77
- package/dist/harness/session-store.d.ts.map +0 -1
- package/dist/harness/session-store.js +0 -248
- package/dist/harness/session-store.js.map +0 -1
- package/dist/harness/settings.d.ts +0 -47
- package/dist/harness/settings.d.ts.map +0 -1
- package/dist/harness/settings.js +0 -29
- package/dist/harness/settings.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/platform-messages.d.ts.map +0 -1
- package/dist/platform-messages.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/provisioner.d.ts +0 -61
- package/dist/provisioner.d.ts.map +0 -1
- package/dist/provisioner.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/errors.d.ts +0 -6
- package/dist/sandbox/errors.d.ts.map +0 -1
- package/dist/sandbox/errors.js +0 -11
- package/dist/sandbox/errors.js.map +0 -1
- package/dist/sandbox/firecracker.d.ts +0 -15
- package/dist/sandbox/firecracker.d.ts.map +0 -1
- package/dist/sandbox/firecracker.js +0 -212
- package/dist/sandbox/firecracker.js.map +0 -1
- package/dist/sandbox/image.d.ts +0 -3
- package/dist/sandbox/image.d.ts.map +0 -1
- package/dist/sandbox/image.js +0 -27
- package/dist/sandbox/image.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/bash.d.ts +0 -10
- package/dist/tools/bash.d.ts.map +0 -1
- package/dist/tools/bash.js +0 -80
- package/dist/tools/bash.js.map +0 -1
- package/dist/tools/edit.d.ts +0 -11
- package/dist/tools/edit.d.ts.map +0 -1
- package/dist/tools/edit.js +0 -131
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/event.d.ts.map +0 -1
- package/dist/tools/event.js +0 -252
- package/dist/tools/event.js.map +0 -1
- package/dist/tools/index.d.ts +0 -24
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -31
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/react.d.ts.map +0 -1
- package/dist/tools/react.js.map +0 -1
- package/dist/tools/read.d.ts +0 -11
- package/dist/tools/read.d.ts.map +0 -1
- package/dist/tools/read.js +0 -134
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/sandbox.d.ts.map +0 -1
- package/dist/tools/sandbox.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/tools/types.d.ts +0 -54
- package/dist/tools/types.d.ts.map +0 -1
- package/dist/tools/types.js.map +0 -1
- package/dist/tools/write.d.ts +0 -10
- package/dist/tools/write.d.ts.map +0 -1
- package/dist/tools/write.js +0 -29
- package/dist/tools/write.js.map +0 -1
- package/dist/trigger.d.ts +0 -22
- package/dist/trigger.d.ts.map +0 -1
- package/dist/trigger.js +0 -100
- package/dist/trigger.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/file-guards.d.ts +0 -9
- package/dist/utils/file-guards.d.ts.map +0 -1
- package/dist/utils/file-guards.js +0 -59
- package/dist/utils/file-guards.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/admin/portal.d.ts +0 -5
- package/dist/web/admin/portal.d.ts.map +0 -1
- package/dist/web/admin/portal.js +0 -2572
- package/dist/web/admin/portal.js.map +0 -1
- package/dist/web/admin/provider-models.d.ts.map +0 -1
- package/dist/web/admin/provider-models.js.map +0 -1
- package/dist/web/admin/store.d.ts +0 -13
- package/dist/web/admin/store.d.ts.map +0 -1
- package/dist/web/admin/store.js +0 -14
- package/dist/web/admin/store.js.map +0 -1
- package/dist/web/admin/types.d.ts +0 -30
- package/dist/web/admin/types.d.ts.map +0 -1
- package/dist/web/admin/types.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/login/oauth.d.ts +0 -6
- package/dist/web/login/oauth.d.ts.map +0 -1
- package/dist/web/login/oauth.js.map +0 -1
- package/dist/web/login/portal.d.ts.map +0 -1
- package/dist/web/login/portal.js.map +0 -1
- package/dist/web/login/store.d.ts +0 -8
- package/dist/web/login/store.d.ts.map +0 -1
- package/dist/web/login/store.js +0 -15
- package/dist/web/login/store.js.map +0 -1
- package/dist/web/login/types.d.ts.map +0 -1
- package/dist/web/login/types.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/server.d.ts.map +0 -1
- package/dist/web/server.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/web/session-view/portal.d.ts +0 -13
- package/dist/web/session-view/portal.d.ts.map +0 -1
- package/dist/web/session-view/portal.js.map +0 -1
- package/dist/web/session-view/service.d.ts +0 -6
- package/dist/web/session-view/service.d.ts.map +0 -1
- package/dist/web/session-view/service.js +0 -498
- package/dist/web/session-view/service.js.map +0 -1
- package/dist/web/session-view/store.d.ts +0 -8
- package/dist/web/session-view/store.d.ts.map +0 -1
- package/dist/web/session-view/store.js +0 -15
- package/dist/web/session-view/store.js.map +0 -1
- package/dist/web/session-view/types.d.ts.map +0 -1
- package/dist/web/session-view/types.js.map +0 -1
- package/dist/web/token-store.d.ts.map +0 -1
- package/dist/web/token-store.js.map +0 -1
- package/dist/web/types.d.ts.map +0 -1
- package/dist/web/types.js.map +0 -1
- /package/dist/{commands → adapters/commands}/admin.d.ts +0 -0
- /package/dist/{commands → adapters/commands}/new.d.ts +0 -0
- /package/dist/{commands → adapters/commands}/sandbox.d.ts +0 -0
- /package/dist/{commands → adapters/commands}/session-view.d.ts +0 -0
- /package/dist/{commands → adapters/commands}/types.js +0 -0
- /package/dist/{harness/extensions → adapters/github}/types.js +0 -0
- /package/dist/{tools → adapters/web/admin}/types.js +0 -0
- /package/dist/{web/admin → adapters/web/login}/types.js +0 -0
- /package/dist/{web/login → adapters/web/session-view}/types.js +0 -0
- /package/dist/{web → adapters/web}/token-store.d.ts +0 -0
- /package/dist/{web → adapters/web}/types.d.ts +0 -0
- /package/dist/{web/session-view → adapters/web}/types.js +0 -0
- /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
- /package/dist/{web → cli}/types.js +0 -0
- /package/dist/{adapters/slack → harness}/tools/attach.d.ts +0 -0
- /package/dist/{tools → harness/tools}/react.d.ts +0 -0
|
@@ -1,22 +1,64 @@
|
|
|
1
1
|
import { SocketModeClient } from "@slack/socket-mode";
|
|
2
|
-
import { WebClient } from "@slack/web-api";
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
4
|
-
import { readFile } from "fs/promises";
|
|
5
|
-
import { basename, join } from "path";
|
|
6
|
-
import {
|
|
2
|
+
import { WebAPIRateLimitedError, WebClient } from "@slack/web-api";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
6
|
+
import { createConversationEvent, createConversationMessage, } from "../index.js";
|
|
7
|
+
import { createOfficeAddress, listRegisteredOffices } from "../../office/index.js";
|
|
8
|
+
import { COMMAND_MANIFEST } from "../commands/manifest.js";
|
|
9
|
+
import { slackConversationAutoReplyMode, resolveConversationSettings, } from "../../settings/index.js";
|
|
10
|
+
import { evaluateWithJev, JevNotConfiguredError } from "../../harness/index.js";
|
|
7
11
|
import * as log from "../../log.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
12
|
+
import { isRecord, readTextFileIfExists } from "../../file-guards.js";
|
|
13
|
+
import { PRODUCT_NAME, formatForceStopped } from "../messages.js";
|
|
14
|
+
import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, MessagingIntakeTracker, saveIncomingAttachments, withRetry, } from "../shared.js";
|
|
15
|
+
import { matchMagicWord, processMessageIntake } from "../intake.js";
|
|
16
|
+
import { readPlatformChannelKind, recordPlatformChannelKind, } from "../../office/projection.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
|
-
import { reportUserFacingError } from "../../observability/
|
|
15
|
-
import {
|
|
22
|
+
import { reportUserFacingError } from "../../observability/index.js";
|
|
23
|
+
import { recordSlackUpdate } from "./update-diagnostics.js";
|
|
24
|
+
import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
|
|
25
|
+
import { querySlackTasks, formatTaskStatus, readTaskRoots, isTaskStatusQuestion, } from "./task-status.js";
|
|
26
|
+
import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
|
|
27
|
+
import { buildTaskIntentState, classifyTaskIntent } from "./task-intent.js";
|
|
28
|
+
import { StreamStartLimiter } from "./stream-limits.js";
|
|
16
29
|
const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
function hasMessageContent(message, includeBlocks = true) {
|
|
31
|
+
const content = [message.text, message.files];
|
|
32
|
+
if (includeBlocks)
|
|
33
|
+
content.push(message.blocks, message.attachments);
|
|
34
|
+
return content.some((part) => !!part?.length);
|
|
35
|
+
}
|
|
36
|
+
function hasBotIdentity(message) {
|
|
37
|
+
return !!message.bot_id || message.subtype === "bot_message";
|
|
38
|
+
}
|
|
39
|
+
const USER_MESSAGE_SUBTYPES = new Set([undefined, "file_share"]);
|
|
40
|
+
const BOT_MESSAGE_SUBTYPES = new Set([...USER_MESSAGE_SUBTYPES, "bot_message"]);
|
|
41
|
+
/**
|
|
42
|
+
* Slack's per-call limit on `markdown_text`: "Limit this field to 12,000
|
|
43
|
+
* characters." mikan allows far more in a message, so streamed text is split
|
|
44
|
+
* to fit rather than truncated.
|
|
45
|
+
*/
|
|
46
|
+
const MAX_STREAM_TEXT_CHARS = 12_000;
|
|
47
|
+
/** Slices `text` into pieces that each fit one streaming call. */
|
|
48
|
+
export function chunkStreamText(text, limit = MAX_STREAM_TEXT_CHARS) {
|
|
49
|
+
if (text.length <= limit)
|
|
50
|
+
return [text];
|
|
51
|
+
const chunks = [];
|
|
52
|
+
for (let index = 0; index < text.length; index += limit) {
|
|
53
|
+
chunks.push(text.slice(index, index + limit));
|
|
54
|
+
}
|
|
55
|
+
return chunks;
|
|
56
|
+
}
|
|
57
|
+
// web-api v8 throws WebAPIRateLimitedError; the duck-typed shapes cover
|
|
58
|
+
// platform `data.error === "rate_limited"` / 429 responses and older callers.
|
|
19
59
|
function slackIsRateLimited(err) {
|
|
60
|
+
if (err instanceof WebAPIRateLimitedError)
|
|
61
|
+
return true;
|
|
20
62
|
if (err.code === "rate_limited")
|
|
21
63
|
return true;
|
|
22
64
|
const data = err.data;
|
|
@@ -72,14 +114,110 @@ export function buildMrkdwnContextBlock(text) {
|
|
|
72
114
|
// ============================================================================
|
|
73
115
|
// SlackMessagingBot
|
|
74
116
|
// ============================================================================
|
|
117
|
+
class AttachmentDownloadHttpError extends Error {
|
|
118
|
+
constructor(message, status) {
|
|
119
|
+
super(message);
|
|
120
|
+
this.status = status;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function isRetryableAttachmentDownloadError(error) {
|
|
124
|
+
if (!(error instanceof AttachmentDownloadHttpError))
|
|
125
|
+
return true;
|
|
126
|
+
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
127
|
+
}
|
|
75
128
|
export class SlackMessagingBot {
|
|
129
|
+
/** Host office dir for a Slack conversation. */
|
|
130
|
+
conversationDir(channelId) {
|
|
131
|
+
return this.workspace.office(createOfficeAddress("slack", channelId)).dir;
|
|
132
|
+
}
|
|
133
|
+
resolveReplyMode(address) {
|
|
134
|
+
return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
|
|
135
|
+
}
|
|
76
136
|
createContext(event) {
|
|
77
|
-
|
|
78
|
-
replyMode:
|
|
79
|
-
"top-level",
|
|
137
|
+
const context = createSlackAdapters(event, this, {
|
|
138
|
+
replyMode: this.resolveReplyMode(event.address),
|
|
80
139
|
});
|
|
140
|
+
if (event.conversationKind === "direct") {
|
|
141
|
+
context.responder.getTaskStatus = (key) => querySlackTasks(this.conversationDir(event.channel), event.channel, this.handler
|
|
142
|
+
.getRunningSessions()
|
|
143
|
+
.filter((s) => s.address.platform === "slack" && s.address.conversationId === event.channel), key);
|
|
144
|
+
}
|
|
145
|
+
if (event.conversationKind === "direct" &&
|
|
146
|
+
event.thread_ts &&
|
|
147
|
+
this.isTaskThread(event.channel, event.thread_ts)) {
|
|
148
|
+
let notified = false;
|
|
149
|
+
context.responder.notifyCompletion = async () => {
|
|
150
|
+
if (notified)
|
|
151
|
+
return;
|
|
152
|
+
const text = `<@${event.user}> 這一輪處理已結束,請查看上方結果。`;
|
|
153
|
+
const ts = await this.postInThread(event.channel, event.thread_ts, text);
|
|
154
|
+
notified = true;
|
|
155
|
+
this.logBotResponse(event.channel, text, ts, event.thread_ts);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (event.conversationKind === "direct" && !event.thread_ts) {
|
|
159
|
+
context.responder.startTask = async (message, task) => {
|
|
160
|
+
if (this.stopped)
|
|
161
|
+
throw new Error("Slack is shutting down; task was not started.");
|
|
162
|
+
await context.responder.deleteResponse();
|
|
163
|
+
const root = await this.postMessage(event.channel, message);
|
|
164
|
+
this.logToFile(event.channel, {
|
|
165
|
+
date: new Date().toISOString(),
|
|
166
|
+
ts: root,
|
|
167
|
+
user: "bot",
|
|
168
|
+
text: message,
|
|
169
|
+
isMessagingBot: true,
|
|
170
|
+
taskRoot: true,
|
|
171
|
+
});
|
|
172
|
+
const sessionKey = resolveSlackSessionKey(event.channel, root);
|
|
173
|
+
try {
|
|
174
|
+
registerThreadSession({
|
|
175
|
+
conversationDir: this.conversationDir(event.channel),
|
|
176
|
+
sessionKey,
|
|
177
|
+
});
|
|
178
|
+
const child = {
|
|
179
|
+
...event,
|
|
180
|
+
ts: `task:${root}`,
|
|
181
|
+
thread_ts: root,
|
|
182
|
+
sessionKey,
|
|
183
|
+
text: task,
|
|
184
|
+
};
|
|
185
|
+
if (!this.getQueue(sessionKey).enqueue(async () => {
|
|
186
|
+
try {
|
|
187
|
+
await this.handler.handleEvent({
|
|
188
|
+
...child,
|
|
189
|
+
attachments: child.attachments?.map((a) => ({
|
|
190
|
+
name: a.original,
|
|
191
|
+
localPath: a.localPath,
|
|
192
|
+
})),
|
|
193
|
+
}, this, this.createContext(child));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
reportUserFacingError(error, {
|
|
197
|
+
domain: "mikan",
|
|
198
|
+
surface: "task_handoff",
|
|
199
|
+
operation: "start_task_run",
|
|
200
|
+
severity: "error",
|
|
201
|
+
platform: "slack",
|
|
202
|
+
context: { conversationId: event.channel, sessionKey, threadTs: root },
|
|
203
|
+
});
|
|
204
|
+
await this.postInThread(event.channel, root, "Task could not start. Please reply here to try again.");
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}))
|
|
208
|
+
throw new Error("Task queue is closed.");
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
await this.updateMessage(event.channel, root, `${message}\n\nTask could not start.`);
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
return sessionKey;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return context;
|
|
81
218
|
}
|
|
82
219
|
constructor(handler, config) {
|
|
220
|
+
this.statusUpdates = new Map();
|
|
83
221
|
this.botUserId = null;
|
|
84
222
|
this.botId = null;
|
|
85
223
|
this.teamId = null;
|
|
@@ -87,11 +225,22 @@ export class SlackMessagingBot {
|
|
|
87
225
|
this.startupTs = null; // Messages older than this are just logged, not processed
|
|
88
226
|
this.users = new Map();
|
|
89
227
|
this.channels = new Map();
|
|
228
|
+
/** Which threads belong to the assistant pane, and their last known context. */
|
|
229
|
+
this.assistantThreads = new AssistantThreadRegistry();
|
|
230
|
+
/**
|
|
231
|
+
* Native response streams opened recently. Per bot rather than per module:
|
|
232
|
+
* the rate tier applies per workspace, and this bot is the connection to
|
|
233
|
+
* one — which also means a test constructing a bot gets a fresh budget
|
|
234
|
+
* instead of inheriting whatever ran before it.
|
|
235
|
+
*/
|
|
236
|
+
this.streamStarts = new StreamStartLimiter();
|
|
237
|
+
this.stopped = false;
|
|
90
238
|
this.queues = new Map();
|
|
91
|
-
this.
|
|
239
|
+
this.intake = new MessagingIntakeTracker("Slack");
|
|
240
|
+
this.eventScheduler = null;
|
|
92
241
|
this.handler = handler;
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
242
|
+
this.workspace = config.workspace;
|
|
243
|
+
this.botToken = config.botToken;
|
|
95
244
|
this.socketClient = new SocketModeClient({
|
|
96
245
|
appToken: config.appToken,
|
|
97
246
|
// Default 5s is too tight: brief event-loop stalls (e.g. backfill, sync fs)
|
|
@@ -99,20 +248,29 @@ export class SlackMessagingBot {
|
|
|
99
248
|
clientPingTimeout: 12_000,
|
|
100
249
|
});
|
|
101
250
|
this.webClient = new WebClient(config.botToken);
|
|
251
|
+
this.statusClient = new WebClient(config.botToken, {
|
|
252
|
+
timeout: 3000,
|
|
253
|
+
retryConfig: { retries: 0 },
|
|
254
|
+
rejectRateLimitedCalls: true,
|
|
255
|
+
});
|
|
102
256
|
}
|
|
103
|
-
|
|
104
|
-
this.
|
|
257
|
+
setEventScheduler(scheduler) {
|
|
258
|
+
this.eventScheduler = scheduler;
|
|
105
259
|
}
|
|
106
260
|
// ==========================================================================
|
|
107
261
|
// Public API
|
|
108
262
|
// ==========================================================================
|
|
109
263
|
async start() {
|
|
264
|
+
this.stopped = false;
|
|
110
265
|
const auth = await this.webClient.auth.test();
|
|
111
266
|
this.botUserId = auth.user_id;
|
|
112
267
|
this.botId = typeof auth.bot_id === "string" ? auth.bot_id : null;
|
|
113
268
|
this.teamId = typeof auth.team_id === "string" ? auth.team_id : null;
|
|
114
269
|
await Promise.all([this.fetchUsers(), this.fetchChannels()]);
|
|
270
|
+
if (this.stopped)
|
|
271
|
+
return;
|
|
115
272
|
log.logInfo(`Loaded ${this.channels.size} channels, ${this.users.size} users`);
|
|
273
|
+
this.backfillChannelKinds();
|
|
116
274
|
// Record startup time before opening the socket. Slack may replay older events;
|
|
117
275
|
// those should be logged but not processed. Backfill runs in the background up
|
|
118
276
|
// to this timestamp so startup is not blocked by one history call per channel.
|
|
@@ -124,6 +282,16 @@ export class SlackMessagingBot {
|
|
|
124
282
|
log.logWarning("Slack backfill failed", String(error));
|
|
125
283
|
});
|
|
126
284
|
}
|
|
285
|
+
async stop() {
|
|
286
|
+
this.stopped = true;
|
|
287
|
+
try {
|
|
288
|
+
await this.socketClient.disconnect();
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
await this.intake.close();
|
|
292
|
+
await Promise.all([...this.queues.values()].map((queue) => queue.close()));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
127
295
|
getUser(userId) {
|
|
128
296
|
return this.users.get(userId);
|
|
129
297
|
}
|
|
@@ -145,9 +313,22 @@ export class SlackMessagingBot {
|
|
|
145
313
|
}
|
|
146
314
|
return source.replace(this.ownMentionRegex, "").trim();
|
|
147
315
|
}
|
|
148
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Response-source `<@userName>` mentions become native `<@U…>` here — the
|
|
318
|
+
* one conversion every outgoing response path funnels through. Slack only
|
|
319
|
+
* links and notifies on raw user ids.
|
|
320
|
+
*/
|
|
321
|
+
resolveMentions(text) {
|
|
322
|
+
return resolveSlackMentions(text, this.users.values());
|
|
323
|
+
}
|
|
324
|
+
async postMessage(channel, text, threadTs, identity) {
|
|
149
325
|
return slackRetry(async () => {
|
|
150
|
-
const payload = {
|
|
326
|
+
const payload = {
|
|
327
|
+
channel,
|
|
328
|
+
...(threadTs !== undefined ? { thread_ts: threadTs } : {}),
|
|
329
|
+
...(identity ? { username: identity.username, icon_emoji: identity.iconEmoji } : {}),
|
|
330
|
+
...renderSlackBlocks(this.resolveMentions(text)),
|
|
331
|
+
};
|
|
151
332
|
const result = await this.webClient.chat.postMessage(payload);
|
|
152
333
|
return result.ts;
|
|
153
334
|
});
|
|
@@ -167,32 +348,35 @@ export class SlackMessagingBot {
|
|
|
167
348
|
}
|
|
168
349
|
});
|
|
169
350
|
}
|
|
170
|
-
async postEphemeral(channel, user, text, threadTs) {
|
|
351
|
+
async postEphemeral(channel, user, text, threadTs, blocks) {
|
|
171
352
|
return slackRetry(async () => {
|
|
172
353
|
await this.webClient.chat.postEphemeral({
|
|
173
354
|
channel,
|
|
174
355
|
user,
|
|
175
356
|
text,
|
|
357
|
+
...(blocks !== undefined ? { blocks: blocks } : {}),
|
|
176
358
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
177
359
|
});
|
|
178
360
|
});
|
|
179
361
|
}
|
|
180
362
|
async postEphemeralBlocks(channel, user, text, blocks, threadTs) {
|
|
363
|
+
return this.postEphemeral(channel, user, text, threadTs, blocks);
|
|
364
|
+
}
|
|
365
|
+
async postMessageBlocks(channel, text, blocks, threadTs) {
|
|
181
366
|
return slackRetry(async () => {
|
|
182
|
-
|
|
367
|
+
const payload = {
|
|
183
368
|
channel,
|
|
184
|
-
user,
|
|
185
369
|
text,
|
|
186
370
|
blocks: blocks,
|
|
187
|
-
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
188
|
-
}
|
|
371
|
+
...(threadTs !== undefined ? { thread_ts: threadTs } : {}),
|
|
372
|
+
};
|
|
373
|
+
const result = await this.webClient.chat.postMessage(payload);
|
|
374
|
+
return result.ts;
|
|
189
375
|
});
|
|
190
376
|
}
|
|
191
|
-
async
|
|
377
|
+
async updateMessageBlocks(channel, ts, text, blocks) {
|
|
192
378
|
return slackRetry(async () => {
|
|
193
|
-
|
|
194
|
-
const result = await this.webClient.chat.postMessage(payload);
|
|
195
|
-
return result.ts;
|
|
379
|
+
await this.webClient.chat.update({ channel, ts, text, blocks: blocks });
|
|
196
380
|
});
|
|
197
381
|
}
|
|
198
382
|
async postPrivate(conversationId, userId, text) {
|
|
@@ -205,7 +389,7 @@ export class SlackMessagingBot {
|
|
|
205
389
|
}
|
|
206
390
|
await this.postEphemeralBlocks(conversationId, userId, text, [buildMrkdwnContextBlock(text)]);
|
|
207
391
|
}
|
|
208
|
-
async
|
|
392
|
+
async openDirectConversation(userId) {
|
|
209
393
|
return slackRetry(async () => {
|
|
210
394
|
const result = await this.webClient.conversations.open({ users: userId });
|
|
211
395
|
const channelId = result.channel?.id;
|
|
@@ -215,33 +399,134 @@ export class SlackMessagingBot {
|
|
|
215
399
|
return channelId;
|
|
216
400
|
});
|
|
217
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Fetch recent messages from a channel, oldest first. Without `threadTs`
|
|
404
|
+
* this is one `conversations.history` page of top-level messages (thread
|
|
405
|
+
* replies are not expanded); with it, one `conversations.replies` page of
|
|
406
|
+
* that thread's replies, excluding the parent. Callers page forward by
|
|
407
|
+
* passing the last returned `ts` as `oldest`.
|
|
408
|
+
*/
|
|
409
|
+
async fetchHistory(channel, options) {
|
|
410
|
+
const limit = Math.min(Math.max(options?.limit ?? 200, 1), 999);
|
|
411
|
+
const threadTs = options?.threadTs;
|
|
412
|
+
return slackRetry(async () => {
|
|
413
|
+
const result = threadTs
|
|
414
|
+
? await this.webClient.conversations.replies({
|
|
415
|
+
channel,
|
|
416
|
+
ts: threadTs,
|
|
417
|
+
...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
|
|
418
|
+
limit,
|
|
419
|
+
})
|
|
420
|
+
: await this.webClient.conversations.history({
|
|
421
|
+
channel,
|
|
422
|
+
...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
|
|
423
|
+
limit,
|
|
424
|
+
});
|
|
425
|
+
const messages = (result.messages ?? []);
|
|
426
|
+
const mapped = messages
|
|
427
|
+
.filter((msg) => !!msg.ts)
|
|
428
|
+
// conversations.replies leads with the thread parent; the caller asked
|
|
429
|
+
// for the replies to it, and including it would re-ingest the bot's
|
|
430
|
+
// own message on every poll.
|
|
431
|
+
.filter((msg) => msg.ts !== threadTs)
|
|
432
|
+
.map((msg) => {
|
|
433
|
+
const user = msg.user ? this.users.get(msg.user) : undefined;
|
|
434
|
+
const message = {
|
|
435
|
+
ts: msg.ts,
|
|
436
|
+
text: msg.text ?? "",
|
|
437
|
+
isBot: !!msg.bot_id || msg.subtype === "bot_message" || user?.isBot === true,
|
|
438
|
+
};
|
|
439
|
+
if (msg.thread_ts && msg.thread_ts !== msg.ts)
|
|
440
|
+
message.threadTs = msg.thread_ts;
|
|
441
|
+
if (msg.user)
|
|
442
|
+
message.userId = msg.user;
|
|
443
|
+
if (user)
|
|
444
|
+
message.userName = user.userName;
|
|
445
|
+
return message;
|
|
446
|
+
});
|
|
447
|
+
// history returns newest-first, replies oldest-first; the contract is
|
|
448
|
+
// oldest-first either way.
|
|
449
|
+
return threadTs ? mapped : mapped.toReversed();
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/** Refresh and list the workspace's active users (`users.list`). */
|
|
453
|
+
async listUsers() {
|
|
454
|
+
await slackRetry(() => this.fetchUsers());
|
|
455
|
+
return this.getAllUsers().map((user) => ({
|
|
456
|
+
id: user.id,
|
|
457
|
+
userName: user.userName,
|
|
458
|
+
displayName: user.displayName,
|
|
459
|
+
isBot: user.isBot === true,
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
218
462
|
async updateMessage(channel, ts, text) {
|
|
219
463
|
return slackRetry(async () => {
|
|
220
|
-
const payload = { channel, ts, ...renderSlackBlocks(text) };
|
|
221
|
-
|
|
464
|
+
const payload = { channel, ts, ...renderSlackBlocks(this.resolveMentions(text)) };
|
|
465
|
+
try {
|
|
466
|
+
await this.webClient.chat.update(payload);
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
recordSlackUpdate(this, { channel, ts }, text, payload, { error });
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
recordSlackUpdate(this, { channel, ts }, text, payload, { success: true });
|
|
222
473
|
});
|
|
223
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Claim one native stream, or decline. Declining is not an error: the caller
|
|
477
|
+
* falls back to edit-based updates, which is a working answer rather than a
|
|
478
|
+
* degraded one, and costs no request against the tighter start tier.
|
|
479
|
+
*/
|
|
480
|
+
tryReserveStreamStart() {
|
|
481
|
+
return this.streamStarts.tryReserve();
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Open a stream, carrying text of any length.
|
|
485
|
+
*
|
|
486
|
+
* Slack caps `markdown_text` at 12,000 characters per call, and a long first
|
|
487
|
+
* render exceeds that easily — mikan allows 35,000 in a message. Rather than
|
|
488
|
+
* truncating (losing the answer) or failing (falling back to edit mode for
|
|
489
|
+
* the whole response), the opening call takes the first slice and the rest
|
|
490
|
+
* follows as appends before this resolves. The caller sees one operation
|
|
491
|
+
* that sent everything, so the renderer's idea of what has been streamed
|
|
492
|
+
* stays true.
|
|
493
|
+
*
|
|
494
|
+
* Splitting mid-word or mid-fence is harmless here: a stream concatenates,
|
|
495
|
+
* so Slack renders the reassembled text, not the pieces.
|
|
496
|
+
*/
|
|
224
497
|
async startMessageStream(channel, text, threadTs, recipientUserId) {
|
|
225
|
-
|
|
498
|
+
const [head, ...tail] = chunkStreamText(text);
|
|
499
|
+
const ts = await slackRetry(async () => {
|
|
226
500
|
const result = await this.webClient.apiCall("chat.startStream", {
|
|
227
501
|
channel,
|
|
228
|
-
markdown_text:
|
|
502
|
+
markdown_text: this.resolveMentions(head ?? ""),
|
|
229
503
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
230
504
|
...(this.teamId ? { recipient_team_id: this.teamId } : {}),
|
|
231
505
|
...(recipientUserId ? { recipient_user_id: recipientUserId } : {}),
|
|
232
506
|
});
|
|
233
|
-
const
|
|
234
|
-
if (!
|
|
507
|
+
const streamTs = result.ts;
|
|
508
|
+
if (!streamTs)
|
|
235
509
|
throw new Error("Slack chat.startStream did not return ts");
|
|
236
|
-
return
|
|
510
|
+
return streamTs;
|
|
237
511
|
});
|
|
512
|
+
for (const part of tail)
|
|
513
|
+
await this.appendStreamChunk(channel, ts, part);
|
|
514
|
+
return ts;
|
|
238
515
|
}
|
|
516
|
+
/** Append a delta of any length, split to the per-call cap. */
|
|
239
517
|
async appendMessageStream(channel, ts, text) {
|
|
518
|
+
for (const part of chunkStreamText(text)) {
|
|
519
|
+
await this.appendStreamChunk(channel, ts, part);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async appendStreamChunk(channel, ts, text) {
|
|
240
523
|
return slackRetry(async () => {
|
|
241
524
|
await this.webClient.apiCall("chat.appendStream", {
|
|
242
525
|
channel,
|
|
243
526
|
ts,
|
|
244
|
-
|
|
527
|
+
// A mention split across stream deltas stays unresolved here; the
|
|
528
|
+
// canonical final render (updateMessage) resolves the full text.
|
|
529
|
+
markdown_text: this.resolveMentions(text),
|
|
245
530
|
});
|
|
246
531
|
});
|
|
247
532
|
}
|
|
@@ -260,33 +545,97 @@ export class SlackMessagingBot {
|
|
|
260
545
|
// ==========================================================================
|
|
261
546
|
/** Set the status for an assistant thread (shows "thinking" state) */
|
|
262
547
|
async setAssistantStatus(channel, threadTs, status) {
|
|
263
|
-
|
|
264
|
-
|
|
548
|
+
const key = `${channel}:${threadTs}`;
|
|
549
|
+
const previous = this.statusUpdates.get(key) ?? Promise.resolve();
|
|
550
|
+
const update = previous
|
|
551
|
+
.catch(() => undefined)
|
|
552
|
+
.then(async () => {
|
|
553
|
+
await this.statusClient.assistant.threads.setStatus({
|
|
265
554
|
channel_id: channel,
|
|
266
555
|
thread_ts: threadTs,
|
|
267
556
|
status,
|
|
268
557
|
});
|
|
269
558
|
});
|
|
559
|
+
this.statusUpdates.set(key, update);
|
|
560
|
+
try {
|
|
561
|
+
await update;
|
|
562
|
+
}
|
|
563
|
+
finally {
|
|
564
|
+
if (this.statusUpdates.get(key) === update)
|
|
565
|
+
this.statusUpdates.delete(key);
|
|
566
|
+
}
|
|
270
567
|
}
|
|
271
|
-
|
|
568
|
+
/** Offer starting points in a new assistant thread. */
|
|
569
|
+
async setAssistantSuggestedPrompts(channel, threadTs, prompts) {
|
|
272
570
|
return slackRetry(async () => {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
571
|
+
// Omitting thread_ts pins the prompts to the DM itself, which is what
|
|
572
|
+
// the agent surface wants: there is no thread yet when it opens.
|
|
573
|
+
await this.webClient.assistant.threads.setSuggestedPrompts({
|
|
574
|
+
channel_id: channel,
|
|
575
|
+
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
576
|
+
prompts,
|
|
577
|
+
});
|
|
276
578
|
});
|
|
277
579
|
}
|
|
278
|
-
|
|
580
|
+
/** Name an assistant thread, which is what the sidebar conversation list shows. */
|
|
581
|
+
async setAssistantTitle(channel, threadTs, title) {
|
|
279
582
|
return slackRetry(async () => {
|
|
280
|
-
|
|
281
|
-
channel,
|
|
583
|
+
await this.webClient.assistant.threads.setTitle({
|
|
584
|
+
channel_id: channel,
|
|
282
585
|
thread_ts: threadTs,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
};
|
|
286
|
-
const result = await this.webClient.chat.postMessage(payload);
|
|
287
|
-
return result.ts;
|
|
586
|
+
title,
|
|
587
|
+
});
|
|
288
588
|
});
|
|
289
589
|
}
|
|
590
|
+
/** The assistant surface's view of this bot, handed to the lifecycle handlers. */
|
|
591
|
+
assistantOps() {
|
|
592
|
+
return {
|
|
593
|
+
postInThread: (channel, threadTs, text) => this.postInThread(channel, threadTs, text),
|
|
594
|
+
setSuggestedPrompts: (channel, threadTs, prompts) => this.setAssistantSuggestedPrompts(channel, threadTs, prompts),
|
|
595
|
+
setTitle: (channel, threadTs, title) => this.setAssistantTitle(channel, threadTs, title),
|
|
596
|
+
channelName: (channelId) => this.channels.get(channelId)?.name,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* A new conversation opened in the assistant pane. Slack waits for the app
|
|
601
|
+
* to speak first here; staying silent is what makes the pane look broken.
|
|
602
|
+
*/
|
|
603
|
+
handleAssistantThreadStarted({ event, ack }) {
|
|
604
|
+
ack();
|
|
605
|
+
const payload = event;
|
|
606
|
+
if (!payload.assistant_thread)
|
|
607
|
+
return;
|
|
608
|
+
void handleAssistantThreadStarted(this.assistantOps(), this.assistantThreads, payload.assistant_thread);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* The person navigated to another channel while the pane is open. Slack
|
|
612
|
+
* sends this as `assistant_thread_context_changed` under `assistant_view`
|
|
613
|
+
* and `app_context_changed` under `agent_view`; the payload differs enough
|
|
614
|
+
* to normalize here rather than in the handler.
|
|
615
|
+
*/
|
|
616
|
+
handleAgentContextChangedEvent({ event, ack, }) {
|
|
617
|
+
ack();
|
|
618
|
+
const payload = event;
|
|
619
|
+
if (payload.assistant_thread) {
|
|
620
|
+
handleAgentContextChanged(this.assistantThreads, payload.assistant_thread);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const channelId = payload.channel_id ?? payload.channel;
|
|
624
|
+
if (channelId) {
|
|
625
|
+
handleAgentContextChanged(this.assistantThreads, {
|
|
626
|
+
channel_id: channelId,
|
|
627
|
+
context: payload.context,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async postInThread(channel, threadTs, text, identity) {
|
|
632
|
+
return identity
|
|
633
|
+
? this.postMessage(channel, text, threadTs, identity)
|
|
634
|
+
: this.postMessage(channel, text, threadTs);
|
|
635
|
+
}
|
|
636
|
+
async postInThreadBlocks(channel, threadTs, text, blocks) {
|
|
637
|
+
return this.postMessageBlocks(channel, text, blocks, threadTs);
|
|
638
|
+
}
|
|
290
639
|
async uploadFile(channel, filePath, title, threadTs) {
|
|
291
640
|
return slackRetry(async () => {
|
|
292
641
|
const fileName = title || basename(filePath);
|
|
@@ -301,18 +650,40 @@ export class SlackMessagingBot {
|
|
|
301
650
|
});
|
|
302
651
|
}
|
|
303
652
|
logToFile(channel, entry) {
|
|
304
|
-
appendChannelLog(this.
|
|
653
|
+
appendChannelLog(this.workspace.office(createOfficeAddress("slack", channel)), entry);
|
|
305
654
|
}
|
|
306
655
|
logBotResponse(channel, text, ts, threadTs, slackBlocks) {
|
|
307
|
-
appendBotResponseLog(this.
|
|
656
|
+
appendBotResponseLog(this.workspace.office(createOfficeAddress("slack", channel)), text, ts, threadTs, {
|
|
308
657
|
platform: "slack",
|
|
309
658
|
...(slackBlocks ? { slackBlocks } : {}),
|
|
310
659
|
});
|
|
311
660
|
}
|
|
661
|
+
ownsBlockKitMessage(channel, ts, threadTs) {
|
|
662
|
+
const content = readTextFileIfExists(join(this.conversationDir(channel), "log.jsonl"));
|
|
663
|
+
if (content === undefined)
|
|
664
|
+
return false;
|
|
665
|
+
for (const line of content.trim().split("\n").toReversed()) {
|
|
666
|
+
try {
|
|
667
|
+
const entry = JSON.parse(line);
|
|
668
|
+
if (!isRecord(entry) || entry.ts !== ts)
|
|
669
|
+
continue;
|
|
670
|
+
return (entry.isMessagingBot === true &&
|
|
671
|
+
entry.platform === "slack" &&
|
|
672
|
+
Array.isArray(entry.slackBlocks) &&
|
|
673
|
+
entry.threadTs === threadTs);
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
312
681
|
getMessagingInfo() {
|
|
313
682
|
return {
|
|
314
683
|
name: "slack",
|
|
315
|
-
|
|
684
|
+
...(this.teamId ? { workspaceId: this.teamId } : {}),
|
|
685
|
+
trustModel: "membership",
|
|
686
|
+
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.",
|
|
316
687
|
channels: this.getAllChannels().map((c) => ({ id: c.id, name: c.name })),
|
|
317
688
|
users: this.getAllUsers().map((u) => ({
|
|
318
689
|
id: u.id,
|
|
@@ -332,6 +703,8 @@ export class SlackMessagingBot {
|
|
|
332
703
|
* Returns true if enqueued, false if queue is full (max 5).
|
|
333
704
|
*/
|
|
334
705
|
enqueueEvent(event) {
|
|
706
|
+
if (this.stopped)
|
|
707
|
+
return false;
|
|
335
708
|
const conversationId = event.conversationId;
|
|
336
709
|
const queue = this.getQueue(conversationId);
|
|
337
710
|
if (queue.size() >= 5) {
|
|
@@ -339,7 +712,7 @@ export class SlackMessagingBot {
|
|
|
339
712
|
return false;
|
|
340
713
|
}
|
|
341
714
|
log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);
|
|
342
|
-
queue.enqueue(async () => {
|
|
715
|
+
return queue.enqueue(async () => {
|
|
343
716
|
let anchorTs;
|
|
344
717
|
if (!event.thread_ts) {
|
|
345
718
|
try {
|
|
@@ -367,15 +740,17 @@ export class SlackMessagingBot {
|
|
|
367
740
|
const eventForRun = eventPlan.event;
|
|
368
741
|
if (eventPlan.initialMessageTs && eventForRun.sessionKey) {
|
|
369
742
|
registerThreadSession({
|
|
370
|
-
conversationDir:
|
|
743
|
+
conversationDir: this.conversationDir(conversationId),
|
|
371
744
|
sessionKey: eventForRun.sessionKey,
|
|
372
745
|
});
|
|
373
746
|
}
|
|
374
747
|
const runQueueKey = planSlackAdapterSession(eventForRun, {
|
|
375
748
|
initialMessageTs: eventPlan.initialMessageTs,
|
|
376
749
|
}).sessionKey;
|
|
377
|
-
|
|
378
|
-
const slackEvent = {
|
|
750
|
+
const run = async () => {
|
|
751
|
+
const slackEvent = createConversationEvent({
|
|
752
|
+
platform: "slack",
|
|
753
|
+
address: eventForRun.address,
|
|
379
754
|
type: eventForRun.type,
|
|
380
755
|
conversationId,
|
|
381
756
|
conversationKind: eventForRun.conversationKind,
|
|
@@ -384,21 +759,21 @@ export class SlackMessagingBot {
|
|
|
384
759
|
thread_ts: eventForRun.thread_ts,
|
|
385
760
|
user: eventForRun.user,
|
|
386
761
|
text: eventForRun.text,
|
|
387
|
-
attachments: eventForRun.attachments
|
|
388
|
-
original: attachment.name,
|
|
389
|
-
localPath: attachment.localPath,
|
|
390
|
-
})),
|
|
762
|
+
attachments: eventForRun.attachments,
|
|
391
763
|
sessionKey: eventForRun.sessionKey,
|
|
392
|
-
};
|
|
764
|
+
});
|
|
393
765
|
const context = createSlackAdapters(slackEvent, this, {
|
|
394
766
|
initialMessageTs: eventPlan.initialMessageTs,
|
|
395
|
-
replyMode:
|
|
396
|
-
?.replyMode ?? "top-level",
|
|
767
|
+
replyMode: this.resolveReplyMode(eventForRun.address),
|
|
397
768
|
});
|
|
398
769
|
return this.handler.handleEvent(eventForRun, this, context);
|
|
399
|
-
}
|
|
770
|
+
};
|
|
771
|
+
const runQueue = this.getQueue(runQueueKey);
|
|
772
|
+
if (!runQueue.enqueue(run)) {
|
|
773
|
+
await runQueue.close();
|
|
774
|
+
await run();
|
|
775
|
+
}
|
|
400
776
|
});
|
|
401
|
-
return true;
|
|
402
777
|
}
|
|
403
778
|
// ==========================================================================
|
|
404
779
|
// Private - Event Handlers
|
|
@@ -407,6 +782,8 @@ export class SlackMessagingBot {
|
|
|
407
782
|
let queue = this.queues.get(channelId);
|
|
408
783
|
if (!queue) {
|
|
409
784
|
queue = new MessagingEventQueue("Slack");
|
|
785
|
+
if (this.stopped)
|
|
786
|
+
void queue.close();
|
|
410
787
|
this.queues.set(channelId, queue);
|
|
411
788
|
}
|
|
412
789
|
return queue;
|
|
@@ -414,21 +791,108 @@ export class SlackMessagingBot {
|
|
|
414
791
|
resolveQueueKey(conversationId, sessionKey) {
|
|
415
792
|
if (!isSlackThreadSessionKey(sessionKey))
|
|
416
793
|
return sessionKey;
|
|
417
|
-
if (this.handler.isRunning(sessionKey))
|
|
794
|
+
if (this.handler.isRunning(createOfficeAddress("slack", conversationId), sessionKey)) {
|
|
418
795
|
return sessionKey;
|
|
796
|
+
}
|
|
419
797
|
return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
|
|
420
798
|
}
|
|
799
|
+
isTaskThread(channel, root) {
|
|
800
|
+
return readTaskRoots(this.conversationDir(channel)).has(root);
|
|
801
|
+
}
|
|
802
|
+
hasRunningTaskThread(channel) {
|
|
803
|
+
const roots = readTaskRoots(this.conversationDir(channel));
|
|
804
|
+
if (!roots.size)
|
|
805
|
+
return false;
|
|
806
|
+
return this.handler
|
|
807
|
+
.getRunningSessions()
|
|
808
|
+
.some((s) => s.address.platform === "slack" &&
|
|
809
|
+
s.address.conversationId === channel &&
|
|
810
|
+
[...roots.keys()].some((root) => resolveSlackSessionKey(channel, root) === s.sessionKey));
|
|
811
|
+
}
|
|
421
812
|
hasKnownThreadSession(conversationId, sessionKey) {
|
|
422
813
|
return hasMaterializedChatSession({
|
|
423
|
-
conversationDir:
|
|
814
|
+
conversationDir: this.conversationDir(conversationId),
|
|
424
815
|
sessionKey,
|
|
425
816
|
});
|
|
426
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Slack's own conversation vocabulary for this channel, from the metadata
|
|
820
|
+
* fetchChannels() already loads. Externally shared channels report
|
|
821
|
+
* "external" regardless of privacy — platform-public within one workspace
|
|
822
|
+
* is the sharing condition, and a channel visible to another organization
|
|
823
|
+
* does not satisfy it. Unknown channels return undefined (never recorded,
|
|
824
|
+
* so the projection keeps its isolated default).
|
|
825
|
+
*/
|
|
826
|
+
channelKindFor(channelId) {
|
|
827
|
+
if (channelId.startsWith("D"))
|
|
828
|
+
return "im";
|
|
829
|
+
const channel = this.channels.get(channelId);
|
|
830
|
+
if (!channel)
|
|
831
|
+
return undefined;
|
|
832
|
+
if (channel.name.startsWith("DM:"))
|
|
833
|
+
return "im";
|
|
834
|
+
if (channel.isExternallyShared)
|
|
835
|
+
return "external";
|
|
836
|
+
if (channel.isPrivate === true)
|
|
837
|
+
return "private_channel";
|
|
838
|
+
if (channel.isPrivate === false)
|
|
839
|
+
return "public_channel";
|
|
840
|
+
return undefined;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Office visibility follows the recorded channel kind, and an unrecorded
|
|
844
|
+
* kind fails closed to private (ADR 0008). Offices created before kinds
|
|
845
|
+
* were recorded would therefore lose public status until their next
|
|
846
|
+
* message; the channel list loaded at startup already knows every kind, so
|
|
847
|
+
* record it for all registered Slack offices now. Metadata only.
|
|
848
|
+
*/
|
|
849
|
+
backfillChannelKinds() {
|
|
850
|
+
let recorded = 0;
|
|
851
|
+
for (const record of listRegisteredOffices(this.workspace.stateDir)) {
|
|
852
|
+
if (record.platform !== "slack")
|
|
853
|
+
continue;
|
|
854
|
+
const kind = this.channelKindFor(record.conversationId);
|
|
855
|
+
if (!kind)
|
|
856
|
+
continue;
|
|
857
|
+
try {
|
|
858
|
+
const office = this.workspace.office(record);
|
|
859
|
+
if (readPlatformChannelKind(office) === kind)
|
|
860
|
+
continue;
|
|
861
|
+
recordPlatformChannelKind(office, kind);
|
|
862
|
+
recorded++;
|
|
863
|
+
}
|
|
864
|
+
catch (err) {
|
|
865
|
+
log.logWarning("Failed to backfill Slack channel kind", String(err));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (recorded > 0)
|
|
869
|
+
log.logInfo(`Recorded channel kind for ${recorded} Slack offices`);
|
|
870
|
+
}
|
|
427
871
|
processSlackMessageIntake(options) {
|
|
428
|
-
|
|
872
|
+
const kind = this.channelKindFor(options.event.conversationId);
|
|
873
|
+
if (kind) {
|
|
874
|
+
try {
|
|
875
|
+
recordPlatformChannelKind(this.workspace.office(options.event.address), kind);
|
|
876
|
+
}
|
|
877
|
+
catch (err) {
|
|
878
|
+
// A failed snapshot must not block the message; the projection just
|
|
879
|
+
// keeps its previous (or isolated default) posture.
|
|
880
|
+
log.logWarning("Failed to record Slack channel kind", String(err));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const absorbAttachmentFailure = () => {
|
|
884
|
+
void options.attachmentsPromise.catch((err) => {
|
|
885
|
+
log.logWarning("Failed to log Slack message", String(err));
|
|
886
|
+
});
|
|
887
|
+
};
|
|
888
|
+
return processMessageIntake({
|
|
429
889
|
eventBase: options.event,
|
|
430
|
-
|
|
431
|
-
|
|
890
|
+
addressed: options.addressed,
|
|
891
|
+
magicWord: {
|
|
892
|
+
addressed: options.magicWordAddressed ?? options.addressed,
|
|
893
|
+
scopeFallback: "top-level",
|
|
894
|
+
},
|
|
895
|
+
busyPolicy: "queue",
|
|
432
896
|
logEntryBase: {},
|
|
433
897
|
processAttachments: () => options.attachmentsPromise,
|
|
434
898
|
queueKey: options.queueKey,
|
|
@@ -436,26 +900,18 @@ export class SlackMessagingBot {
|
|
|
436
900
|
handler: this.handler,
|
|
437
901
|
bot: this,
|
|
438
902
|
createContext: (event) => this.createContext(event),
|
|
439
|
-
onNotTriggered: options.onNotTriggered,
|
|
440
903
|
deferAttachmentsUntilRun: true,
|
|
904
|
+
}).then((outcome) => {
|
|
905
|
+
// Slack logs eagerly via logUserMessage; when intake does not enqueue,
|
|
906
|
+
// nothing awaits the attachment download, so absorb its failure here.
|
|
907
|
+
if (outcome !== "enqueued")
|
|
908
|
+
absorbAttachmentFailure();
|
|
909
|
+
}, (err) => {
|
|
910
|
+
log.logWarning("Slack message intake failed", String(err));
|
|
911
|
+
absorbAttachmentFailure();
|
|
441
912
|
});
|
|
442
913
|
}
|
|
443
|
-
|
|
444
|
-
const blocks = [
|
|
445
|
-
{
|
|
446
|
-
type: "section",
|
|
447
|
-
text: {
|
|
448
|
-
type: "mrkdwn",
|
|
449
|
-
text: `*${PRODUCT_NAME}*\nStart a new task or check on running work.`,
|
|
450
|
-
},
|
|
451
|
-
accessory: {
|
|
452
|
-
type: "image",
|
|
453
|
-
image_url: "https://media1.tenor.com/m/lfDATg4Bhc0AAAAC/happy-cat.gif",
|
|
454
|
-
alt_text: PRODUCT_NAME,
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
];
|
|
458
|
-
// --- Running tasks ---
|
|
914
|
+
appendRunningTasks(blocks) {
|
|
459
915
|
const runningSessions = this.handler.getRunningSessions();
|
|
460
916
|
blocks.push({ type: "divider" }, {
|
|
461
917
|
type: "header",
|
|
@@ -470,61 +926,56 @@ export class SlackMessagingBot {
|
|
|
470
926
|
type: "context",
|
|
471
927
|
elements: [{ type: "mrkdwn", text: "_No tasks running right now._" }],
|
|
472
928
|
});
|
|
929
|
+
return;
|
|
473
930
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
931
|
+
const stuckThresholdMs = 10 * 60 * 1000;
|
|
932
|
+
for (const session of runningSessions) {
|
|
933
|
+
const channelId = conversationIdOf(session.sessionKey);
|
|
934
|
+
const channelName = this.channels.get(channelId)?.name;
|
|
935
|
+
const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
|
|
936
|
+
const elapsedStr = elapsed < 1 ? "<1 min" : `${elapsed} min`;
|
|
937
|
+
const lastActivity = session.lastActivityAt ? Date.now() - session.lastActivityAt : 0;
|
|
938
|
+
const isStuck = lastActivity > stuckThresholdMs;
|
|
939
|
+
let statusLine = `${isStuck ? "_stuck_" : "_running_"} · ${elapsedStr}`;
|
|
940
|
+
if (session.currentTool)
|
|
941
|
+
statusLine += ` · ${session.currentTool}`;
|
|
942
|
+
if (isStuck)
|
|
943
|
+
statusLine += ` · idle ${Math.floor(lastActivity / 60000)}m`;
|
|
944
|
+
blocks.push({
|
|
945
|
+
type: "context",
|
|
946
|
+
elements: [
|
|
947
|
+
{
|
|
948
|
+
type: "mrkdwn",
|
|
949
|
+
text: `*${channelName ? `#${channelName}` : channelId}* · ${statusLine}`,
|
|
950
|
+
},
|
|
951
|
+
],
|
|
952
|
+
});
|
|
953
|
+
if (isStuck) {
|
|
497
954
|
blocks.push({
|
|
498
955
|
type: "context",
|
|
499
956
|
elements: [
|
|
957
|
+
{ type: "mrkdwn", text: " " },
|
|
500
958
|
{
|
|
501
|
-
type: "
|
|
502
|
-
text:
|
|
959
|
+
type: "button",
|
|
960
|
+
text: { type: "plain_text", text: "Force Stop", emoji: true },
|
|
961
|
+
// `value` preserves the raw key; action_id is only a unique route.
|
|
962
|
+
action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
|
|
963
|
+
value: session.sessionKey,
|
|
964
|
+
style: "danger",
|
|
503
965
|
},
|
|
504
966
|
],
|
|
505
967
|
});
|
|
506
|
-
// Add Force Stop button as separate element if stuck
|
|
507
|
-
if (isStuck) {
|
|
508
|
-
blocks.push({
|
|
509
|
-
type: "context",
|
|
510
|
-
elements: [
|
|
511
|
-
{
|
|
512
|
-
type: "mrkdwn",
|
|
513
|
-
text: " ",
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
type: "button",
|
|
517
|
-
text: { type: "plain_text", text: "Force Stop", emoji: true },
|
|
518
|
-
action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
|
|
519
|
-
style: "danger",
|
|
520
|
-
},
|
|
521
|
-
],
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
968
|
}
|
|
525
969
|
}
|
|
526
|
-
|
|
527
|
-
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Scheduled jobs of the opener's own DM office only: the App Home is a
|
|
973
|
+
* personal surface, so other offices' schedules are not listed here.
|
|
974
|
+
*/
|
|
975
|
+
appendScheduledJobs(blocks, dmChannelId) {
|
|
976
|
+
const periodicEvents = dmChannelId && this.eventScheduler
|
|
977
|
+
? this.eventScheduler.periodicEvents(createOfficeAddress("slack", dmChannelId))
|
|
978
|
+
: [];
|
|
528
979
|
blocks.push({ type: "divider" }, {
|
|
529
980
|
type: "header",
|
|
530
981
|
text: {
|
|
@@ -538,34 +989,46 @@ export class SlackMessagingBot {
|
|
|
538
989
|
type: "context",
|
|
539
990
|
elements: [{ type: "mrkdwn", text: "_No scheduled jobs._" }],
|
|
540
991
|
});
|
|
992
|
+
return;
|
|
541
993
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
:
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
:
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
text: {
|
|
562
|
-
type: "mrkdwn",
|
|
563
|
-
text: `*${ev.text}*\n└ \`${ev.schedule}\` · ${channelLabel} · Next: ${nextStr}`,
|
|
564
|
-
},
|
|
565
|
-
});
|
|
566
|
-
}
|
|
994
|
+
for (const event of periodicEvents) {
|
|
995
|
+
const channel = event.platform === "slack" ? this.channels.get(event.conversationId) : undefined;
|
|
996
|
+
const channelId = channel ? `#${channel.name}` : event.conversationId;
|
|
997
|
+
const channelLabel = `${event.platform}:${channelId}`;
|
|
998
|
+
const nextRun = event.nextRun
|
|
999
|
+
? new Date(event.nextRun).toLocaleString("en-US", {
|
|
1000
|
+
month: "short",
|
|
1001
|
+
day: "numeric",
|
|
1002
|
+
hour: "2-digit",
|
|
1003
|
+
minute: "2-digit",
|
|
1004
|
+
})
|
|
1005
|
+
: "—";
|
|
1006
|
+
blocks.push({
|
|
1007
|
+
type: "section",
|
|
1008
|
+
text: {
|
|
1009
|
+
type: "mrkdwn",
|
|
1010
|
+
text: `*${event.text}*\n└ \`${event.schedule}\` · ${channelLabel} · Next: ${nextRun}`,
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
567
1013
|
}
|
|
568
|
-
|
|
1014
|
+
}
|
|
1015
|
+
buildHomeView(dmChannelId) {
|
|
1016
|
+
const blocks = [
|
|
1017
|
+
{
|
|
1018
|
+
type: "section",
|
|
1019
|
+
text: {
|
|
1020
|
+
type: "mrkdwn",
|
|
1021
|
+
text: `*${PRODUCT_NAME}*\nStart a new task or check on running work.`,
|
|
1022
|
+
},
|
|
1023
|
+
accessory: {
|
|
1024
|
+
type: "image",
|
|
1025
|
+
image_url: "https://media1.tenor.com/m/lfDATg4Bhc0AAAAC/happy-cat.gif",
|
|
1026
|
+
alt_text: PRODUCT_NAME,
|
|
1027
|
+
},
|
|
1028
|
+
},
|
|
1029
|
+
];
|
|
1030
|
+
this.appendRunningTasks(blocks);
|
|
1031
|
+
this.appendScheduledJobs(blocks, dmChannelId);
|
|
569
1032
|
blocks.push({ type: "divider" }, {
|
|
570
1033
|
type: "context",
|
|
571
1034
|
elements: [
|
|
@@ -574,55 +1037,38 @@ export class SlackMessagingBot {
|
|
|
574
1037
|
});
|
|
575
1038
|
return { type: "home", blocks: blocks };
|
|
576
1039
|
}
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
});
|
|
583
|
-
if (directTarget)
|
|
584
|
-
return directTarget;
|
|
585
|
-
if (threadTs)
|
|
586
|
-
return null;
|
|
587
|
-
return resolveOnlyScopedStopTarget(this.handler, channelId);
|
|
588
|
-
}
|
|
589
|
-
isStopText(text) {
|
|
590
|
-
const normalized = text.trim().toLowerCase();
|
|
591
|
-
return normalized === "stop" || normalized === "/stop";
|
|
592
|
-
}
|
|
593
|
-
createCommandAdapters(conversationId, userId, userName, text, ts, options = {}) {
|
|
594
|
-
const message = {
|
|
1040
|
+
createCommandAdapters(input) {
|
|
1041
|
+
const { conversationId, userId, userName, text, ts, ...options } = input;
|
|
1042
|
+
const message = createConversationMessage({
|
|
1043
|
+
platform: "slack",
|
|
1044
|
+
conversationId,
|
|
595
1045
|
id: ts,
|
|
596
|
-
|
|
1046
|
+
// One session identity per dispatch: the caller's thread-resolved key.
|
|
1047
|
+
// Defaulting to the top-level conversation here while the event carried
|
|
1048
|
+
// a thread key made the same run answer to two different sessions.
|
|
1049
|
+
sessionKey: options.sessionKey ?? conversationId,
|
|
597
1050
|
conversationKind: options.ephemeralChannelId ? "shared" : "direct",
|
|
598
1051
|
userId,
|
|
599
1052
|
userName,
|
|
600
1053
|
text,
|
|
601
1054
|
attachments: [],
|
|
602
|
-
};
|
|
603
|
-
const respond = async (responseText) => {
|
|
604
|
-
if (options.ephemeralChannelId) {
|
|
605
|
-
await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs);
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
const messageTs = await this.postMessage(conversationId, responseText);
|
|
609
|
-
this.logBotResponse(conversationId, responseText, messageTs);
|
|
610
|
-
};
|
|
611
|
-
const respondMuted = async (responseText) => {
|
|
612
|
-
const blocks = [buildMrkdwnContextBlock(responseText)];
|
|
1055
|
+
});
|
|
1056
|
+
const respond = async (responseText, blocks) => {
|
|
613
1057
|
if (options.ephemeralChannelId) {
|
|
614
|
-
await this.
|
|
1058
|
+
await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs, blocks);
|
|
615
1059
|
return;
|
|
616
1060
|
}
|
|
617
|
-
const messageTs =
|
|
1061
|
+
const messageTs = blocks
|
|
1062
|
+
? await this.postMessageBlocks(conversationId, responseText, blocks)
|
|
1063
|
+
: await this.postMessage(conversationId, responseText);
|
|
618
1064
|
this.logBotResponse(conversationId, responseText, messageTs);
|
|
619
1065
|
};
|
|
620
1066
|
const responder = {
|
|
621
1067
|
respond,
|
|
622
|
-
replaceResponse: respond,
|
|
1068
|
+
replaceResponse: (responseText) => respond(responseText),
|
|
623
1069
|
respondDiagnostic: async (responseText, responseOptions) => {
|
|
624
1070
|
if (responseOptions?.style === "muted") {
|
|
625
|
-
await
|
|
1071
|
+
await respond(responseText, [buildMrkdwnContextBlock(responseText)]);
|
|
626
1072
|
return;
|
|
627
1073
|
}
|
|
628
1074
|
await respond(responseOptions?.style === "error" ? `_${responseText}_` : responseText);
|
|
@@ -639,6 +1085,7 @@ export class SlackMessagingBot {
|
|
|
639
1085
|
deleteResponse: async () => { },
|
|
640
1086
|
};
|
|
641
1087
|
return {
|
|
1088
|
+
address: message.address,
|
|
642
1089
|
message,
|
|
643
1090
|
responder,
|
|
644
1091
|
platform: this.getMessagingInfo(),
|
|
@@ -666,9 +1113,10 @@ export class SlackMessagingBot {
|
|
|
666
1113
|
isMessagingBot: false,
|
|
667
1114
|
...(threadTs ? { threadTs } : {}),
|
|
668
1115
|
});
|
|
669
|
-
const event = {
|
|
670
|
-
|
|
1116
|
+
const event = createConversationEvent({
|
|
1117
|
+
platform: "slack",
|
|
671
1118
|
conversationId,
|
|
1119
|
+
type: options.type ?? (isDirectMessage ? "dm" : "mention"),
|
|
672
1120
|
conversationKind: isDirectMessage ? "direct" : "shared",
|
|
673
1121
|
ts: eventTs,
|
|
674
1122
|
user: payload.user_id,
|
|
@@ -676,54 +1124,37 @@ export class SlackMessagingBot {
|
|
|
676
1124
|
attachments: [],
|
|
677
1125
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
678
1126
|
sessionKey,
|
|
679
|
-
};
|
|
680
|
-
const context = this.createCommandAdapters(conversationId, payload.user_id, userName, commandText, eventTs, isDirectMessage ? { threadTs } : { ephemeralChannelId: conversationId, threadTs });
|
|
681
|
-
return { event, context };
|
|
682
|
-
}
|
|
683
|
-
async routeSlashLoginCommand(payload) {
|
|
684
|
-
const { event, context } = this.buildSlashCommandEvent(payload, {
|
|
685
|
-
type: payload.channel_id.startsWith("D") ? "dm" : "private_command",
|
|
686
|
-
includeText: true,
|
|
687
1127
|
});
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const conversationId = payload.channel_id;
|
|
692
|
-
if (!conversationId.startsWith("D")) {
|
|
693
|
-
await this.postEphemeral(conversationId, payload.user_id, `為了避免誤清除共享上下文,${payload.command} 目前只能在與 ${PRODUCT_NAME} 的私訊中使用。`);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
const createdAt = new Date();
|
|
697
|
-
const eventTs = (createdAt.getTime() / 1000).toFixed(6);
|
|
698
|
-
const userName = payload.user_name ?? this.getUser(payload.user_id)?.userName;
|
|
699
|
-
this.logToFile(conversationId, {
|
|
700
|
-
date: createdAt.toISOString(),
|
|
701
|
-
ts: eventTs,
|
|
702
|
-
user: payload.user_id,
|
|
1128
|
+
const context = this.createCommandAdapters({
|
|
1129
|
+
conversationId,
|
|
1130
|
+
userId: payload.user_id,
|
|
703
1131
|
userName,
|
|
704
|
-
text:
|
|
705
|
-
|
|
706
|
-
|
|
1132
|
+
text: commandText,
|
|
1133
|
+
ts: eventTs,
|
|
1134
|
+
...(isDirectMessage
|
|
1135
|
+
? { threadTs, sessionKey }
|
|
1136
|
+
: { ephemeralChannelId: conversationId, threadTs, sessionKey }),
|
|
707
1137
|
});
|
|
708
|
-
|
|
709
|
-
start: async () => { },
|
|
710
|
-
postMessage: async (_channel, text) => this.postMessage(conversationId, text),
|
|
711
|
-
updateMessage: async (channel, ts, text) => this.updateMessage(channel, ts, text),
|
|
712
|
-
enqueueEvent: (event) => this.enqueueEvent(event),
|
|
713
|
-
getMessagingInfo: () => this.getMessagingInfo(),
|
|
714
|
-
};
|
|
715
|
-
await this.handler.handleNewCommand(conversationId, conversationId, commandMessagingBot);
|
|
716
|
-
}
|
|
717
|
-
async routeSlashModelCommand(payload) {
|
|
718
|
-
const { event, context } = this.buildSlashCommandEvent(payload, { includeText: true });
|
|
719
|
-
await this.handler.handleEvent(event, this, context);
|
|
720
|
-
}
|
|
721
|
-
async routeSlashSessionCommand(payload) {
|
|
722
|
-
const { event, context } = this.buildSlashCommandEvent(payload, { thread: true });
|
|
723
|
-
await this.handler.handleEvent(event, this, context);
|
|
1138
|
+
return { event, context };
|
|
724
1139
|
}
|
|
725
|
-
|
|
726
|
-
|
|
1140
|
+
/**
|
|
1141
|
+
* Generic slash-command route: synthesize the command text per the
|
|
1142
|
+
* manifest's SlackSlashRoute data and dispatch to the handler. Every
|
|
1143
|
+
* routed command — including `/pi-new` — reaches its CommandHandler
|
|
1144
|
+
* through runtime dispatch; the adapter holds no per-command behavior.
|
|
1145
|
+
*/
|
|
1146
|
+
async routeSlashCommand(route, payload) {
|
|
1147
|
+
const { event, context } = this.buildSlashCommandEvent(payload, {
|
|
1148
|
+
includeText: route.includeText,
|
|
1149
|
+
thread: route.thread,
|
|
1150
|
+
...(route.privateCommand
|
|
1151
|
+
? {
|
|
1152
|
+
type: payload.channel_id.startsWith("D")
|
|
1153
|
+
? "dm"
|
|
1154
|
+
: "private_command",
|
|
1155
|
+
}
|
|
1156
|
+
: {}),
|
|
1157
|
+
});
|
|
727
1158
|
await this.handler.handleEvent(event, this, context);
|
|
728
1159
|
}
|
|
729
1160
|
setupEventHandlers() {
|
|
@@ -736,14 +1167,39 @@ export class SlackMessagingBot {
|
|
|
736
1167
|
this.socketClient.on("unable_to_socket_mode_start", (err) => {
|
|
737
1168
|
log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
|
|
738
1169
|
});
|
|
739
|
-
this.socketClient.on("app_mention", (payload) =>
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
this.socketClient.on("
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1170
|
+
this.socketClient.on("app_mention", (payload) => {
|
|
1171
|
+
void this.intake.run(() => this.handleAppMention(payload));
|
|
1172
|
+
});
|
|
1173
|
+
this.socketClient.on("message", (payload) => {
|
|
1174
|
+
void this.intake.run(() => this.handleMessageEvent(payload));
|
|
1175
|
+
});
|
|
1176
|
+
this.socketClient.on("slash_commands", (payload) => {
|
|
1177
|
+
void this.intake.run(() => this.handleSlashCommand(payload));
|
|
1178
|
+
});
|
|
1179
|
+
this.socketClient.on("app_home_opened", (payload) => {
|
|
1180
|
+
void this.intake.run(() => this.handleAppHomeOpened(payload));
|
|
1181
|
+
});
|
|
1182
|
+
// The assistant surface's lifecycle. Both events were already subscribed
|
|
1183
|
+
// in the app manifest and then dropped on the floor, which is why the pane
|
|
1184
|
+
// opened empty and the sidebar filled with untitled conversations.
|
|
1185
|
+
this.socketClient.on("assistant_thread_started", (payload) => {
|
|
1186
|
+
void this.intake.run(() => this.handleAssistantThreadStarted(payload));
|
|
1187
|
+
});
|
|
1188
|
+
this.socketClient.on("assistant_thread_context_changed", (payload) => {
|
|
1189
|
+
void this.intake.run(() => this.handleAgentContextChangedEvent(payload));
|
|
1190
|
+
});
|
|
1191
|
+
// agent_view's spelling of the same signal.
|
|
1192
|
+
this.socketClient.on("app_context_changed", (payload) => {
|
|
1193
|
+
void this.intake.run(() => this.handleAgentContextChangedEvent(payload));
|
|
1194
|
+
});
|
|
1195
|
+
this.socketClient.on("block_actions", (payload) => {
|
|
1196
|
+
void this.intake.run(() => this.handleBlockAction(payload));
|
|
1197
|
+
});
|
|
1198
|
+
this.socketClient.on("interactive", (payload) => {
|
|
1199
|
+
void this.intake.run(() => this.handleBlockAction(payload));
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
async handleAppMention({ event, ack, }) {
|
|
747
1203
|
const e = event;
|
|
748
1204
|
// Skip DMs (handled by message event)
|
|
749
1205
|
if (e.channel.startsWith("D")) {
|
|
@@ -754,18 +1210,19 @@ export class SlackMessagingBot {
|
|
|
754
1210
|
// Thread replies get their own isolated session (channelId:thread_ts).
|
|
755
1211
|
const sessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
756
1212
|
const mentionText = this.stripOwnMention(e.text);
|
|
757
|
-
const slackEvent = {
|
|
758
|
-
|
|
1213
|
+
const slackEvent = createConversationEvent({
|
|
1214
|
+
platform: "slack",
|
|
759
1215
|
conversationId: e.channel,
|
|
1216
|
+
type: "mention",
|
|
760
1217
|
conversationKind: "shared",
|
|
761
|
-
channel: e.channel,
|
|
762
1218
|
ts: e.ts,
|
|
763
1219
|
thread_ts: e.thread_ts,
|
|
764
1220
|
user: e.user,
|
|
765
1221
|
text: mentionText || "Please respond to the recent conversation context.",
|
|
766
|
-
files: e.files,
|
|
767
1222
|
sessionKey,
|
|
768
|
-
|
|
1223
|
+
channel: e.channel,
|
|
1224
|
+
files: e.files,
|
|
1225
|
+
});
|
|
769
1226
|
const attachmentsPromise = this.logUserMessage(slackEvent);
|
|
770
1227
|
// Only trigger processing for messages AFTER startup (not replayed old messages)
|
|
771
1228
|
if (this.startupTs && e.ts < this.startupTs) {
|
|
@@ -776,67 +1233,162 @@ export class SlackMessagingBot {
|
|
|
776
1233
|
ack();
|
|
777
1234
|
return;
|
|
778
1235
|
}
|
|
779
|
-
|
|
780
|
-
if (this.isStopText(slackEvent.text)) {
|
|
781
|
-
const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
|
|
782
|
-
if (stopTarget) {
|
|
783
|
-
this.handler.handleStop(stopTarget, e.channel, this);
|
|
784
|
-
}
|
|
785
|
-
else {
|
|
786
|
-
this.postMessage(e.channel, formatNothingRunning("slack"));
|
|
787
|
-
}
|
|
788
|
-
void attachmentsPromise.catch((err) => {
|
|
789
|
-
log.logWarning("Failed to log Slack message", String(err));
|
|
790
|
-
});
|
|
791
|
-
ack();
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
this.processSlackMessageIntake({
|
|
1236
|
+
const intake = this.processSlackMessageIntake({
|
|
795
1237
|
event: slackEvent,
|
|
796
1238
|
attachmentsPromise,
|
|
797
1239
|
queueKey: this.resolveQueueKey(e.channel, sessionKey),
|
|
798
|
-
|
|
1240
|
+
addressed: true,
|
|
799
1241
|
});
|
|
800
1242
|
ack();
|
|
1243
|
+
await intake;
|
|
801
1244
|
}
|
|
802
|
-
|
|
803
|
-
const
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
const isOwnMessagingBotMessage = (!!e.user && e.user === this.botUserId) || (!!this.botId && e.bot_id === this.botId);
|
|
807
|
-
if (isOwnMessagingBotMessage) {
|
|
1245
|
+
admitHumanMessage(event, ack) {
|
|
1246
|
+
const hasSlackContent = hasMessageContent(event);
|
|
1247
|
+
const isOwnMessage = event.user === this.botUserId || (!!this.botId && event.bot_id === this.botId);
|
|
1248
|
+
if (isOwnMessage) {
|
|
808
1249
|
ack();
|
|
809
|
-
return;
|
|
1250
|
+
return false;
|
|
810
1251
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
if (
|
|
818
|
-
|
|
819
|
-
|
|
1252
|
+
// User-token posts carry bot_id/app_id beside the human user. Only unknown
|
|
1253
|
+
// authors and known bot users take the external-bot loop-protection path.
|
|
1254
|
+
const authorIsKnownHuman = !!event.user && this.users.get(event.user)?.isBot === false;
|
|
1255
|
+
const isExternalMessage = event.subtype === "bot_message" || (!!event.bot_id && !authorIsKnownHuman);
|
|
1256
|
+
if (isExternalMessage) {
|
|
1257
|
+
const supportedSubtype = BOT_MESSAGE_SUBTYPES.has(event.subtype);
|
|
1258
|
+
if (supportedSubtype && hasSlackContent) {
|
|
1259
|
+
void this.logExternalMessagingBotMessage(event).catch((err) => {
|
|
1260
|
+
log.logWarning("Failed to log Slack bot message", String(err));
|
|
1261
|
+
});
|
|
820
1262
|
}
|
|
821
|
-
void this.logExternalMessagingBotMessage(e).catch((err) => {
|
|
822
|
-
log.logWarning("Failed to log Slack bot message", String(err));
|
|
823
|
-
});
|
|
824
1263
|
ack();
|
|
825
|
-
return;
|
|
1264
|
+
return false;
|
|
826
1265
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return;
|
|
1266
|
+
const isSupportedUserMessage = !!event.user && USER_MESSAGE_SUBTYPES.has(event.subtype) && hasSlackContent;
|
|
1267
|
+
if (isSupportedUserMessage)
|
|
1268
|
+
return true;
|
|
1269
|
+
ack();
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Handle a DM message that may be about a background task without spending
|
|
1274
|
+
* a model turn: a pure status question is answered from observation, and a
|
|
1275
|
+
* supplement is steered into the running task. Jev decides which (falling
|
|
1276
|
+
* back to the regex status shortcut); anything else returns false so the
|
|
1277
|
+
* message becomes an ordinary conversation turn.
|
|
1278
|
+
*/
|
|
1279
|
+
async deliverTaskUpdate(event, attachmentsPromise) {
|
|
1280
|
+
if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
|
|
1281
|
+
return false;
|
|
1282
|
+
event.attachments = await attachmentsPromise;
|
|
1283
|
+
const context = this.createContext(event);
|
|
1284
|
+
const post = (text) => event.thread_ts
|
|
1285
|
+
? this.postInThread(event.channel, event.thread_ts, text)
|
|
1286
|
+
: this.postMessage(event.channel, text);
|
|
1287
|
+
try {
|
|
1288
|
+
const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
|
|
1289
|
+
if (!tasks.length)
|
|
1290
|
+
return false;
|
|
1291
|
+
const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
|
|
1292
|
+
const user = this.users.get(event.user);
|
|
1293
|
+
// A message carrying files is never a pure status question; in a task
|
|
1294
|
+
// thread it is a supplement, at top level an ordinary turn.
|
|
1295
|
+
const intent = event.attachments?.length
|
|
1296
|
+
? event.thread_ts
|
|
1297
|
+
? "steer"
|
|
1298
|
+
: "request"
|
|
1299
|
+
: await classifyTaskIntent(buildTaskIntentState(this.conversationDir(event.channel), event, {
|
|
1300
|
+
speaker: user?.displayName ?? user?.userName,
|
|
1301
|
+
tasks,
|
|
1302
|
+
resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
|
|
1303
|
+
botUserId: this.botUserId,
|
|
1304
|
+
}), event.text, { conversationId: event.channel, inTaskThread: !!event.thread_ts });
|
|
1305
|
+
if (intent === "status") {
|
|
1306
|
+
const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
|
|
1307
|
+
const text = selection.length > 1
|
|
1308
|
+
? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
|
|
1309
|
+
: formatTaskStatus(selection);
|
|
1310
|
+
const ts = await post(text);
|
|
1311
|
+
this.logBotResponse(event.channel, text, ts, event.thread_ts);
|
|
1312
|
+
return true;
|
|
1313
|
+
}
|
|
1314
|
+
if (intent === "steer") {
|
|
1315
|
+
// In a thread the message already targets that task; at top level it
|
|
1316
|
+
// can only be steered when exactly one task is running.
|
|
1317
|
+
const target = event.thread_ts
|
|
1318
|
+
? context.message
|
|
1319
|
+
: active.length === 1
|
|
1320
|
+
? {
|
|
1321
|
+
...context.message,
|
|
1322
|
+
sessionKey: active[0].sessionKey,
|
|
1323
|
+
threadTs: active[0].threadTs,
|
|
1324
|
+
}
|
|
1325
|
+
: undefined;
|
|
1326
|
+
if (target && (await this.handler.steer?.(target))) {
|
|
1327
|
+
const text = "收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。";
|
|
1328
|
+
const ts = await post(text);
|
|
1329
|
+
this.logBotResponse(event.channel, text, ts, event.thread_ts);
|
|
1330
|
+
return true;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
830
1333
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return;
|
|
1334
|
+
catch (error) {
|
|
1335
|
+
await post(error instanceof Error ? error.message : "Could not deliver task update.");
|
|
1336
|
+
return true;
|
|
834
1337
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Ask Jev a single typed yes/no question about whether this unaddressed
|
|
1342
|
+
* shared-channel message (top-level or thread reply) addresses mikan. The
|
|
1343
|
+
* scored state carries the surrounding scope from log.jsonl, since a bare
|
|
1344
|
+
* thread reply cannot be judged from its own text. Only called in `jev`
|
|
1345
|
+
* auto-reply mode, and only awaited by the caller in that mode, so
|
|
1346
|
+
* `on`/`off` stay synchronous (magic-word `stop` handling depends on that).
|
|
1347
|
+
* Any failure (including a missing `OPENROUTER_API_KEY`) fails closed to
|
|
1348
|
+
* "not addressed" so a misconfiguration cannot make the bot noisy in a
|
|
1349
|
+
* shared channel.
|
|
1350
|
+
*/
|
|
1351
|
+
async evaluateJevAddressed(event) {
|
|
1352
|
+
if (!event.text.trim() || matchMagicWord(event.text) === "stop")
|
|
1353
|
+
return false;
|
|
1354
|
+
try {
|
|
1355
|
+
const user = this.users.get(event.user);
|
|
1356
|
+
const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
|
|
1357
|
+
speaker: user?.displayName ?? user?.userName,
|
|
1358
|
+
resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
|
|
1359
|
+
botUserId: this.botUserId,
|
|
1360
|
+
});
|
|
1361
|
+
const result = await evaluateWithJev(state, {
|
|
1362
|
+
addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS },
|
|
1363
|
+
});
|
|
1364
|
+
const probability = result.answers.addressed.probability;
|
|
1365
|
+
log.logInfo(`[${event.channel}] jev auto-reply: probability=${probability.toFixed(2)} addressed=${probability > 0.5} text="${event.text.slice(0, 80)}"`);
|
|
1366
|
+
return probability > 0.5;
|
|
1367
|
+
}
|
|
1368
|
+
catch (err) {
|
|
1369
|
+
if (err instanceof JevNotConfiguredError) {
|
|
1370
|
+
log.logWarning("Slack auto-reply jev mode requires OPENROUTER_API_KEY; treating message as unaddressed", String(err));
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
reportUserFacingError(err, {
|
|
1374
|
+
domain: "chat_platform",
|
|
1375
|
+
surface: "slack_auto_reply_jev",
|
|
1376
|
+
operation: "evaluate",
|
|
1377
|
+
severity: "warning",
|
|
1378
|
+
context: { conversationId: event.conversationId },
|
|
1379
|
+
});
|
|
1380
|
+
return false;
|
|
838
1381
|
}
|
|
839
|
-
|
|
1382
|
+
}
|
|
1383
|
+
async handleMessageEvent({ event, ack, }) {
|
|
1384
|
+
const e = event;
|
|
1385
|
+
if (!this.admitHumanMessage(e, ack))
|
|
1386
|
+
return;
|
|
1387
|
+
// message.im normally carries channel_type "im", but fall back to the
|
|
1388
|
+
// D-prefix convention (used by handleAppMention and session keys) so a
|
|
1389
|
+
// missing channel_type cannot silently classify a DM as an unaddressed
|
|
1390
|
+
// channel message that never triggers.
|
|
1391
|
+
const isDM = e.channel_type === "im" || e.channel.startsWith("D");
|
|
840
1392
|
const conversationKind = isDM ? "direct" : "shared";
|
|
841
1393
|
const isMessagingBotMention = e.text?.includes(`<@${this.botUserId}>`);
|
|
842
1394
|
// Skip channel @mentions - already handled by app_mention event
|
|
@@ -844,20 +1396,26 @@ export class SlackMessagingBot {
|
|
|
844
1396
|
ack();
|
|
845
1397
|
return;
|
|
846
1398
|
}
|
|
847
|
-
const isThreadReply = !!e.thread_ts;
|
|
848
1399
|
const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
|
|
849
|
-
|
|
850
|
-
|
|
1400
|
+
// Name the conversation from the first thing the person said. Guarded on
|
|
1401
|
+
// the assistant registry, so classic DM threads are untouched, and it
|
|
1402
|
+
// claims the title once per thread.
|
|
1403
|
+
if (isDM && e.thread_ts && e.text) {
|
|
1404
|
+
void titleAssistantThread(this.assistantOps(), this.assistantThreads, e.channel, e.thread_ts, this.stripOwnMention(e.text));
|
|
1405
|
+
}
|
|
1406
|
+
const slackEvent = createConversationEvent({
|
|
1407
|
+
platform: "slack",
|
|
851
1408
|
conversationId: e.channel,
|
|
1409
|
+
type: (isDM ? "dm" : "mention"),
|
|
852
1410
|
conversationKind,
|
|
853
|
-
channel: e.channel,
|
|
854
1411
|
ts: e.ts,
|
|
855
1412
|
thread_ts: e.thread_ts,
|
|
856
1413
|
user: e.user,
|
|
857
1414
|
text: this.stripOwnMention(e.text),
|
|
858
|
-
files: e.files,
|
|
859
1415
|
sessionKey,
|
|
860
|
-
|
|
1416
|
+
channel: e.channel,
|
|
1417
|
+
files: e.files,
|
|
1418
|
+
});
|
|
861
1419
|
const attachmentsPromise = this.logUserMessage(slackEvent);
|
|
862
1420
|
// Only trigger processing for messages AFTER startup (not replayed old messages)
|
|
863
1421
|
if (this.startupTs && e.ts < this.startupTs) {
|
|
@@ -868,61 +1426,42 @@ export class SlackMessagingBot {
|
|
|
868
1426
|
ack();
|
|
869
1427
|
return;
|
|
870
1428
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
});
|
|
883
|
-
ack();
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
const enqueueTriggered = () => {
|
|
887
|
-
const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
888
|
-
// Auto-reply top-level channel messages start with no sessionKey because
|
|
889
|
-
// they are only candidates until the policy allows them. Once triggered,
|
|
890
|
-
// persist the resolved key on the event; otherwise the runtime fallback
|
|
891
|
-
// treats the message ts as a thread session (`channel:ts`) instead of the
|
|
892
|
-
// persistent top-level channel session.
|
|
893
|
-
slackEvent.sessionKey = activeSessionKey;
|
|
894
|
-
this.processSlackMessageIntake({
|
|
895
|
-
event: slackEvent,
|
|
896
|
-
attachmentsPromise,
|
|
897
|
-
queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
|
|
898
|
-
isAutoReplyCandidate: false,
|
|
899
|
-
});
|
|
900
|
-
};
|
|
901
|
-
const logOnly = () => {
|
|
902
|
-
void attachmentsPromise.catch((err) => {
|
|
903
|
-
log.logWarning("Failed to log Slack message", String(err));
|
|
904
|
-
});
|
|
905
|
-
};
|
|
906
|
-
if (isDM) {
|
|
907
|
-
enqueueTriggered();
|
|
908
|
-
ack();
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
if (isThreadReply) {
|
|
912
|
-
logOnly();
|
|
1429
|
+
const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
1430
|
+
slackEvent.sessionKey = activeSessionKey;
|
|
1431
|
+
// Task threads always go through the task gate. Top-level DMs do only
|
|
1432
|
+
// while a task is running (so supplements can be steered) or when the
|
|
1433
|
+
// regex already recognises a status question about a finished one, so
|
|
1434
|
+
// idle DMs never pay a Jev round-trip.
|
|
1435
|
+
const taskControl = isDM &&
|
|
1436
|
+
(e.thread_ts
|
|
1437
|
+
? this.isTaskThread(e.channel, e.thread_ts)
|
|
1438
|
+
: this.hasRunningTaskThread(e.channel) || isTaskStatusQuestion(slackEvent.text));
|
|
1439
|
+
if (taskControl) {
|
|
913
1440
|
ack();
|
|
914
|
-
|
|
1441
|
+
if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
|
|
1442
|
+
return;
|
|
915
1443
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1444
|
+
// Shared-channel messages without a mention, top-level or in a thread,
|
|
1445
|
+
// all go through the same auto-reply gate; jev mode sees the surrounding
|
|
1446
|
+
// scope so thread follow-ups are judged in context rather than dropped.
|
|
1447
|
+
const autoReplyMode = isDM
|
|
1448
|
+
? "off"
|
|
1449
|
+
: slackConversationAutoReplyMode(this.workspace.office(slackEvent.address));
|
|
1450
|
+
const autoReply = autoReplyMode === "on"
|
|
1451
|
+
? true
|
|
1452
|
+
: autoReplyMode === "jev"
|
|
1453
|
+
? await this.evaluateJevAddressed(slackEvent)
|
|
1454
|
+
: false;
|
|
1455
|
+
const intake = this.processSlackMessageIntake({
|
|
919
1456
|
event: slackEvent,
|
|
920
1457
|
attachmentsPromise,
|
|
921
1458
|
queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
|
|
922
|
-
|
|
923
|
-
|
|
1459
|
+
addressed: isDM || autoReply,
|
|
1460
|
+
magicWordAddressed: isDM,
|
|
924
1461
|
});
|
|
925
|
-
|
|
1462
|
+
if (!taskControl)
|
|
1463
|
+
ack();
|
|
1464
|
+
await intake;
|
|
926
1465
|
}
|
|
927
1466
|
async handleSlashCommand({ body, ack, }) {
|
|
928
1467
|
const payload = body;
|
|
@@ -931,73 +1470,44 @@ export class SlackMessagingBot {
|
|
|
931
1470
|
return;
|
|
932
1471
|
}
|
|
933
1472
|
const { command, text, channel_id, user_id, user_name, thread_ts } = payload;
|
|
934
|
-
|
|
935
|
-
if (
|
|
936
|
-
|
|
1473
|
+
const entry = COMMAND_MANIFEST.find((candidate) => candidate.slackCommand === command);
|
|
1474
|
+
if (!entry)
|
|
1475
|
+
return;
|
|
1476
|
+
if (!entry.slackRoute)
|
|
1477
|
+
return;
|
|
1478
|
+
try {
|
|
1479
|
+
await this.routeSlashCommand(entry.slackRoute, {
|
|
937
1480
|
command,
|
|
938
1481
|
text,
|
|
939
1482
|
channel_id,
|
|
940
1483
|
user_id,
|
|
941
1484
|
user_name,
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
else if (command === "/pi-new") {
|
|
945
|
-
handlerPromise = this.routeSlashNewCommand({ command, channel_id, user_id, user_name });
|
|
946
|
-
}
|
|
947
|
-
else if (command === "/pi-session") {
|
|
948
|
-
handlerPromise = this.routeSlashSessionCommand({
|
|
949
|
-
command,
|
|
950
|
-
channel_id,
|
|
951
|
-
user_id,
|
|
952
|
-
user_name,
|
|
953
1485
|
thread_ts,
|
|
954
1486
|
});
|
|
955
1487
|
}
|
|
956
|
-
|
|
957
|
-
handlerPromise = this.routeSlashModelCommand({
|
|
958
|
-
command,
|
|
959
|
-
text,
|
|
960
|
-
channel_id,
|
|
961
|
-
user_id,
|
|
962
|
-
user_name,
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
else if (command === "/pi-sandbox" ||
|
|
966
|
-
command === "/pi-auto-reply" ||
|
|
967
|
-
command === "/pi-extensions") {
|
|
968
|
-
handlerPromise = this.routeSlashModelCommand({
|
|
969
|
-
command,
|
|
970
|
-
text,
|
|
971
|
-
channel_id,
|
|
972
|
-
user_id,
|
|
973
|
-
user_name,
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
else if (command === "/pi-admin") {
|
|
977
|
-
handlerPromise = this.routeSlashAdminCommand({
|
|
978
|
-
command,
|
|
979
|
-
channel_id,
|
|
980
|
-
user_id,
|
|
981
|
-
user_name,
|
|
982
|
-
thread_ts,
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
if (!handlerPromise) {
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
handlerPromise.catch((err) => {
|
|
1488
|
+
catch (err) {
|
|
989
1489
|
log.logWarning("Slack slash command error", err instanceof Error ? err.message : String(err));
|
|
990
|
-
}
|
|
1490
|
+
}
|
|
991
1491
|
}
|
|
992
1492
|
handleAppHomeOpened({ event, ack }) {
|
|
993
1493
|
const e = event;
|
|
994
1494
|
ack();
|
|
1495
|
+
// Under agent_view this is how Slack says "the person opened the app's
|
|
1496
|
+
// DM" — the signal assistant_thread_started used to carry. It fires on
|
|
1497
|
+
// every open, not once per conversation, so this path refreshes the
|
|
1498
|
+
// pinned prompts and deliberately does not greet.
|
|
1499
|
+
if (e.tab === "messages") {
|
|
1500
|
+
if (e.channel) {
|
|
1501
|
+
void handleAgentDmOpened(this.assistantOps(), this.assistantThreads, e.channel, e.context);
|
|
1502
|
+
}
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
995
1505
|
if (e.tab !== "home")
|
|
996
1506
|
return;
|
|
997
1507
|
this.webClient.views
|
|
998
1508
|
.publish({
|
|
999
1509
|
user_id: e.user,
|
|
1000
|
-
view: this.buildHomeView(),
|
|
1510
|
+
view: this.buildHomeView(e.channel),
|
|
1001
1511
|
})
|
|
1002
1512
|
.catch((err) => {
|
|
1003
1513
|
log.logWarning(`Failed to publish App Home view`, String(err));
|
|
@@ -1015,21 +1525,24 @@ export class SlackMessagingBot {
|
|
|
1015
1525
|
return;
|
|
1016
1526
|
}
|
|
1017
1527
|
ack();
|
|
1018
|
-
|
|
1528
|
+
// Prefer the verbatim key from `value`; the action_id fallback only
|
|
1529
|
+
// serves buttons rendered before `value` existed and misdecodes session
|
|
1530
|
+
// keys whose conversation id contains "_".
|
|
1531
|
+
const sessionKey = action.value ?? action.action_id.replace("force_stop_", "").replace(/_/g, ":");
|
|
1019
1532
|
const userId = body.user?.id;
|
|
1020
|
-
const channelId = body.container?.channel_id || sessionKey
|
|
1533
|
+
const channelId = body.container?.channel_id || conversationIdOf(sessionKey);
|
|
1021
1534
|
log.logInfo(`[Force Stop] User ${userId} requested force stop for ${sessionKey}`);
|
|
1022
1535
|
// Use handler's forceStop method
|
|
1023
|
-
this.handler.forceStop(sessionKey);
|
|
1536
|
+
this.handler.forceStop(createOfficeAddress("slack", channelId), sessionKey);
|
|
1024
1537
|
// Notify in channel
|
|
1025
1538
|
await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
|
|
1026
1539
|
// Refresh home tab
|
|
1027
1540
|
if (userId) {
|
|
1028
|
-
this.
|
|
1029
|
-
.publish({
|
|
1541
|
+
this.openDirectConversation(userId)
|
|
1542
|
+
.then((dmChannelId) => this.webClient.views.publish({
|
|
1030
1543
|
user_id: userId,
|
|
1031
|
-
view: this.buildHomeView(),
|
|
1032
|
-
})
|
|
1544
|
+
view: this.buildHomeView(dmChannelId),
|
|
1545
|
+
}))
|
|
1033
1546
|
.catch((err) => {
|
|
1034
1547
|
log.logWarning(`Failed to refresh App Home view`, String(err));
|
|
1035
1548
|
});
|
|
@@ -1045,6 +1558,8 @@ export class SlackMessagingBot {
|
|
|
1045
1558
|
const selectedOptions = Array.isArray(action.selected_options)
|
|
1046
1559
|
? action.selected_options
|
|
1047
1560
|
: undefined;
|
|
1561
|
+
const threadTs = container.thread_ts;
|
|
1562
|
+
const sessionKey = resolveSlackSessionKey(channelId, threadTs);
|
|
1048
1563
|
const selectedText = selectedOption?.text?.text ?? selectedOption?.value;
|
|
1049
1564
|
const selectedTexts = selectedOptions?.map((option) => option.text?.text ?? option.value);
|
|
1050
1565
|
const valueText = selectedTexts?.length
|
|
@@ -1052,8 +1567,6 @@ export class SlackMessagingBot {
|
|
|
1052
1567
|
: (selectedText ?? action.value ?? action.action_id);
|
|
1053
1568
|
const text = `[Slack action] ${action.action_id}: ${valueText}`;
|
|
1054
1569
|
const ts = `action:${Date.now()}`;
|
|
1055
|
-
const threadTs = container.thread_ts;
|
|
1056
|
-
const sessionKey = resolveSlackSessionKey(channelId, threadTs);
|
|
1057
1570
|
this.logToFile(channelId, {
|
|
1058
1571
|
date: new Date().toISOString(),
|
|
1059
1572
|
ts,
|
|
@@ -1080,9 +1593,10 @@ export class SlackMessagingBot {
|
|
|
1080
1593
|
messageTs: container.message_ts,
|
|
1081
1594
|
},
|
|
1082
1595
|
});
|
|
1083
|
-
const event = {
|
|
1084
|
-
|
|
1596
|
+
const event = createConversationEvent({
|
|
1597
|
+
platform: "slack",
|
|
1085
1598
|
conversationId: channelId,
|
|
1599
|
+
type: "slack_action",
|
|
1086
1600
|
conversationKind: channelId.startsWith("D") ? "direct" : "shared",
|
|
1087
1601
|
ts,
|
|
1088
1602
|
user: userId,
|
|
@@ -1090,23 +1604,71 @@ export class SlackMessagingBot {
|
|
|
1090
1604
|
attachments: [],
|
|
1091
1605
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
1092
1606
|
sessionKey,
|
|
1093
|
-
};
|
|
1607
|
+
});
|
|
1094
1608
|
this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
|
|
1095
1609
|
const slackEvent = {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1610
|
+
...createConversationEvent({
|
|
1611
|
+
platform: "slack",
|
|
1612
|
+
conversationId: channelId,
|
|
1613
|
+
type: (event.conversationKind === "direct" ? "dm" : "mention"),
|
|
1614
|
+
conversationKind: event.conversationKind,
|
|
1615
|
+
ts,
|
|
1616
|
+
thread_ts: threadTs,
|
|
1617
|
+
user: userId,
|
|
1618
|
+
text,
|
|
1619
|
+
attachments: [],
|
|
1620
|
+
sessionKey,
|
|
1621
|
+
}),
|
|
1099
1622
|
channel: channelId,
|
|
1100
|
-
ts,
|
|
1101
|
-
thread_ts: threadTs,
|
|
1102
|
-
user: userId,
|
|
1103
|
-
text,
|
|
1104
1623
|
attachments: [],
|
|
1105
|
-
sessionKey,
|
|
1106
1624
|
};
|
|
1107
1625
|
return this.handler.handleEvent(event, this, this.createContext(slackEvent));
|
|
1108
1626
|
});
|
|
1109
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Download Slack message files into the office's attachments directory.
|
|
1630
|
+
* The Slack contract is all-or-error: any failed download throws after the
|
|
1631
|
+
* successful ones are on disk, so callers still log the message text.
|
|
1632
|
+
*/
|
|
1633
|
+
async processAttachments(channelId, files, timestamp) {
|
|
1634
|
+
const items = [];
|
|
1635
|
+
for (const file of files) {
|
|
1636
|
+
const url = file.url_private_download || file.url_private;
|
|
1637
|
+
if (!url)
|
|
1638
|
+
continue;
|
|
1639
|
+
if (!file.name) {
|
|
1640
|
+
throw new Error(`Attachment missing name for URL: ${url}`);
|
|
1641
|
+
}
|
|
1642
|
+
items.push({
|
|
1643
|
+
name: file.name,
|
|
1644
|
+
// Slack timestamps are float seconds; the stored filename uses ms.
|
|
1645
|
+
timestampMs: Math.floor(parseFloat(timestamp) * 1000),
|
|
1646
|
+
download: (destPath) => this.downloadSlackFile(url, destPath),
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
const office = this.workspace.office(createOfficeAddress("slack", channelId));
|
|
1650
|
+
const { saved, failed } = await saveIncomingAttachments(office, items);
|
|
1651
|
+
const firstFailure = failed[0];
|
|
1652
|
+
if (firstFailure) {
|
|
1653
|
+
const { name, error } = firstFailure;
|
|
1654
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1655
|
+
throw new Error(`Failed to download attachment ${name}: ${errorMsg}`, { cause: error });
|
|
1656
|
+
}
|
|
1657
|
+
return saved;
|
|
1658
|
+
}
|
|
1659
|
+
/** Authorized download with retry; Slack file URLs require the bot token. */
|
|
1660
|
+
async downloadSlackFile(url, destPath) {
|
|
1661
|
+
await withRetry(async () => {
|
|
1662
|
+
const response = await fetch(url, {
|
|
1663
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
1664
|
+
});
|
|
1665
|
+
if (!response.ok) {
|
|
1666
|
+
throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
|
|
1667
|
+
}
|
|
1668
|
+
const buffer = await response.arrayBuffer();
|
|
1669
|
+
await writeFile(destPath, Buffer.from(buffer));
|
|
1670
|
+
}, { maxAttempts: 3, baseDelayMs: 250, isRateLimited: isRetryableAttachmentDownloadError });
|
|
1671
|
+
}
|
|
1110
1672
|
/**
|
|
1111
1673
|
* Log a user message to log.jsonl after attachments are ready.
|
|
1112
1674
|
*/
|
|
@@ -1116,7 +1678,7 @@ export class SlackMessagingBot {
|
|
|
1116
1678
|
let attachmentError;
|
|
1117
1679
|
if (event.files) {
|
|
1118
1680
|
try {
|
|
1119
|
-
attachments = await this.
|
|
1681
|
+
attachments = await this.processAttachments(event.channel, event.files, event.ts);
|
|
1120
1682
|
}
|
|
1121
1683
|
catch (err) {
|
|
1122
1684
|
attachmentError = err;
|
|
@@ -1141,7 +1703,7 @@ export class SlackMessagingBot {
|
|
|
1141
1703
|
}
|
|
1142
1704
|
async logExternalMessagingBotMessage(event) {
|
|
1143
1705
|
const attachments = event.files
|
|
1144
|
-
? await this.
|
|
1706
|
+
? await this.processAttachments(event.channel, event.files, event.ts)
|
|
1145
1707
|
: [];
|
|
1146
1708
|
const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
|
|
1147
1709
|
this.logToFile(event.channel, {
|
|
@@ -1164,7 +1726,7 @@ export class SlackMessagingBot {
|
|
|
1164
1726
|
// Private - Backfill
|
|
1165
1727
|
// ==========================================================================
|
|
1166
1728
|
async getExistingTimestamps(channelId) {
|
|
1167
|
-
const logPath = join(this.
|
|
1729
|
+
const logPath = join(this.conversationDir(channelId), "log.jsonl");
|
|
1168
1730
|
const timestamps = new Set();
|
|
1169
1731
|
if (!existsSync(logPath))
|
|
1170
1732
|
return timestamps;
|
|
@@ -1172,7 +1734,7 @@ export class SlackMessagingBot {
|
|
|
1172
1734
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1173
1735
|
for (let i = 0; i < lines.length; i++) {
|
|
1174
1736
|
try {
|
|
1175
|
-
const entry = JSON.parse(lines[i]);
|
|
1737
|
+
const entry = JSON.parse(lines[i] ?? "");
|
|
1176
1738
|
if (entry.ts)
|
|
1177
1739
|
timestamps.add(entry.ts);
|
|
1178
1740
|
}
|
|
@@ -1182,6 +1744,19 @@ export class SlackMessagingBot {
|
|
|
1182
1744
|
}
|
|
1183
1745
|
return timestamps;
|
|
1184
1746
|
}
|
|
1747
|
+
isBackfillableMessage(message) {
|
|
1748
|
+
if (message.user === this.botUserId)
|
|
1749
|
+
return true;
|
|
1750
|
+
if (hasBotIdentity(message)) {
|
|
1751
|
+
if (this.botId && message.bot_id === this.botId)
|
|
1752
|
+
return false;
|
|
1753
|
+
return BOT_MESSAGE_SUBTYPES.has(message.subtype) && hasMessageContent(message);
|
|
1754
|
+
}
|
|
1755
|
+
// Unlike live intake, historical human posts need text or files, not just blocks.
|
|
1756
|
+
return (!!message.user &&
|
|
1757
|
+
USER_MESSAGE_SUBTYPES.has(message.subtype) &&
|
|
1758
|
+
hasMessageContent(message, false));
|
|
1759
|
+
}
|
|
1185
1760
|
async backfillChannel(channelId, upperBoundTs) {
|
|
1186
1761
|
const existingTs = await this.getExistingTimestamps(channelId);
|
|
1187
1762
|
// Find the biggest ts in log.jsonl
|
|
@@ -1213,36 +1788,14 @@ export class SlackMessagingBot {
|
|
|
1213
1788
|
const relevantMessages = allMessages.filter((msg) => {
|
|
1214
1789
|
if (!msg.ts || existingTs.has(msg.ts))
|
|
1215
1790
|
return false; // Skip duplicates
|
|
1216
|
-
|
|
1217
|
-
return true;
|
|
1218
|
-
const isExternalMessagingBotMessage = !!msg.bot_id || msg.subtype === "bot_message";
|
|
1219
|
-
if (isExternalMessagingBotMessage) {
|
|
1220
|
-
if (this.botId && msg.bot_id === this.botId)
|
|
1221
|
-
return false;
|
|
1222
|
-
if (msg.subtype !== undefined &&
|
|
1223
|
-
msg.subtype !== "bot_message" &&
|
|
1224
|
-
msg.subtype !== "file_share") {
|
|
1225
|
-
return false;
|
|
1226
|
-
}
|
|
1227
|
-
return (!!msg.text ||
|
|
1228
|
-
!!(msg.files && msg.files.length > 0) ||
|
|
1229
|
-
!!msg.blocks?.length ||
|
|
1230
|
-
!!msg.attachments?.length);
|
|
1231
|
-
}
|
|
1232
|
-
if (msg.subtype !== undefined && msg.subtype !== "file_share")
|
|
1233
|
-
return false;
|
|
1234
|
-
if (!msg.user)
|
|
1235
|
-
return false;
|
|
1236
|
-
if (!msg.text && (!msg.files || msg.files.length === 0))
|
|
1237
|
-
return false;
|
|
1238
|
-
return true;
|
|
1791
|
+
return this.isBackfillableMessage(msg);
|
|
1239
1792
|
});
|
|
1240
1793
|
// Reverse to chronological order
|
|
1241
1794
|
relevantMessages.reverse();
|
|
1242
1795
|
// Log each message to log.jsonl
|
|
1243
1796
|
for (const msg of relevantMessages) {
|
|
1244
1797
|
const isMikanMessage = msg.user === this.botUserId;
|
|
1245
|
-
const isExternalMessagingBotMessage = !isMikanMessage && (
|
|
1798
|
+
const isExternalMessagingBotMessage = !isMikanMessage && hasBotIdentity(msg);
|
|
1246
1799
|
if (isExternalMessagingBotMessage) {
|
|
1247
1800
|
await this.logExternalMessagingBotMessage({ ...msg, channel: channelId, ts: msg.ts });
|
|
1248
1801
|
continue;
|
|
@@ -1250,7 +1803,7 @@ export class SlackMessagingBot {
|
|
|
1250
1803
|
const user = this.users.get(msg.user);
|
|
1251
1804
|
const text = this.stripOwnMention(msg.text);
|
|
1252
1805
|
const attachments = msg.files
|
|
1253
|
-
? await this.
|
|
1806
|
+
? await this.processAttachments(channelId, msg.files, msg.ts)
|
|
1254
1807
|
: [];
|
|
1255
1808
|
this.logToFile(channelId, {
|
|
1256
1809
|
date: new Date(parseFloat(msg.ts) * 1000).toISOString(),
|
|
@@ -1271,7 +1824,7 @@ export class SlackMessagingBot {
|
|
|
1271
1824
|
// Only backfill channels that already have a log.jsonl (mikan has interacted with them before)
|
|
1272
1825
|
const channelsToBackfill = [];
|
|
1273
1826
|
for (const [channelId, channel] of this.channels) {
|
|
1274
|
-
const logPath = join(this.
|
|
1827
|
+
const logPath = join(this.conversationDir(channelId), "log.jsonl");
|
|
1275
1828
|
if (existsSync(logPath)) {
|
|
1276
1829
|
channelsToBackfill.push([channelId, channel]);
|
|
1277
1830
|
}
|
|
@@ -1289,7 +1842,7 @@ export class SlackMessagingBot {
|
|
|
1289
1842
|
log.logWarning(`Failed to backfill #${channel.name}`, String(error));
|
|
1290
1843
|
}
|
|
1291
1844
|
// Add delay between channels to avoid hitting Slack rate limits
|
|
1292
|
-
if (channelId !== channelsToBackfill[channelsToBackfill.length - 1][0]) {
|
|
1845
|
+
if (channelId !== channelsToBackfill[channelsToBackfill.length - 1]?.[0]) {
|
|
1293
1846
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1294
1847
|
}
|
|
1295
1848
|
}
|
|
@@ -1304,16 +1857,15 @@ export class SlackMessagingBot {
|
|
|
1304
1857
|
do {
|
|
1305
1858
|
const result = await this.webClient.users.list({ limit: 200, cursor });
|
|
1306
1859
|
const members = result.members;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1860
|
+
for (const u of members ?? []) {
|
|
1861
|
+
if (!u.id || !u.name || u.deleted)
|
|
1862
|
+
continue;
|
|
1863
|
+
this.users.set(u.id, {
|
|
1864
|
+
id: u.id,
|
|
1865
|
+
userName: u.name,
|
|
1866
|
+
displayName: u.real_name || u.name,
|
|
1867
|
+
isBot: !!u.is_bot,
|
|
1868
|
+
});
|
|
1317
1869
|
}
|
|
1318
1870
|
cursor = result.response_metadata?.next_cursor;
|
|
1319
1871
|
} while (cursor);
|
|
@@ -1329,12 +1881,15 @@ export class SlackMessagingBot {
|
|
|
1329
1881
|
cursor,
|
|
1330
1882
|
});
|
|
1331
1883
|
const channels = result.channels;
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1884
|
+
for (const c of channels ?? []) {
|
|
1885
|
+
if (!c.id || !c.name || !c.is_member)
|
|
1886
|
+
continue;
|
|
1887
|
+
this.channels.set(c.id, {
|
|
1888
|
+
id: c.id,
|
|
1889
|
+
name: c.name,
|
|
1890
|
+
...(typeof c.is_private === "boolean" ? { isPrivate: c.is_private } : {}),
|
|
1891
|
+
...(c.is_shared || c.is_ext_shared ? { isExternallyShared: true } : {}),
|
|
1892
|
+
});
|
|
1338
1893
|
}
|
|
1339
1894
|
cursor = result.response_metadata?.next_cursor;
|
|
1340
1895
|
} while (cursor);
|
|
@@ -1347,15 +1902,13 @@ export class SlackMessagingBot {
|
|
|
1347
1902
|
cursor,
|
|
1348
1903
|
});
|
|
1349
1904
|
const ims = result.channels;
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1905
|
+
for (const im of ims ?? []) {
|
|
1906
|
+
if (!im.id)
|
|
1907
|
+
continue;
|
|
1908
|
+
// Use user's name as channel name for DMs
|
|
1909
|
+
const user = im.user ? this.users.get(im.user) : undefined;
|
|
1910
|
+
const name = user ? `DM:${user.userName}` : `DM:${im.id}`;
|
|
1911
|
+
this.channels.set(im.id, { id: im.id, name });
|
|
1359
1912
|
}
|
|
1360
1913
|
cursor = result.response_metadata?.next_cursor;
|
|
1361
1914
|
} while (cursor);
|