@geminixiang/mikan 1.0.0-beta.8 → 1.0.0-beta.80
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 +1149 -0
- package/README.md +98 -38
- package/dist/adapters/commands/admin.d.ts.map +1 -0
- package/dist/{commands → adapters/commands}/admin.js +12 -9
- 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 +158 -0
- package/dist/adapters/commands/login.js.map +1 -0
- package/dist/adapters/commands/manifest.d.ts +17 -0
- package/dist/adapters/commands/manifest.d.ts.map +1 -0
- package/dist/adapters/commands/manifest.js +121 -0
- package/dist/adapters/commands/manifest.js.map +1 -0
- package/dist/adapters/commands/model.d.ts +9 -0
- package/dist/adapters/commands/model.d.ts.map +1 -0
- package/dist/adapters/commands/model.js +136 -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 +39 -0
- package/dist/adapters/commands/new.js.map +1 -0
- package/dist/adapters/commands/registry.d.ts +4 -0
- package/dist/adapters/commands/registry.d.ts.map +1 -0
- package/dist/adapters/commands/registry.js +35 -0
- package/dist/adapters/commands/registry.js.map +1 -0
- package/dist/{commands → adapters/commands}/sandbox.d.ts +0 -1
- 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 +21 -16
- package/dist/adapters/commands/session-view.js.map +1 -0
- package/dist/adapters/commands/types.d.ts +105 -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 +11 -0
- package/dist/adapters/commands/utils.d.ts.map +1 -0
- package/dist/{commands → adapters/commands}/utils.js +15 -1
- package/dist/adapters/commands/utils.js.map +1 -0
- package/dist/adapters/discord/bot.d.ts +10 -17
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +232 -297
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/components.d.ts +4 -0
- package/dist/adapters/discord/components.d.ts.map +1 -0
- package/dist/adapters/discord/components.js +9 -0
- package/dist/adapters/discord/components.js.map +1 -0
- package/dist/adapters/discord/context.d.ts +3 -7
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +55 -219
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/format.d.ts +2 -0
- package/dist/adapters/discord/format.d.ts.map +1 -0
- package/dist/adapters/discord/format.js +47 -0
- package/dist/adapters/discord/format.js.map +1 -0
- package/dist/adapters/discord/types.d.ts +90 -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 +25 -9
- package/dist/adapters/github/bot.d.ts.map +1 -1
- package/dist/adapters/github/bot.js +326 -154
- package/dist/adapters/github/bot.js.map +1 -1
- package/dist/adapters/github/client.d.ts +35 -23
- package/dist/adapters/github/client.d.ts.map +1 -1
- package/dist/adapters/github/client.js +121 -28
- package/dist/adapters/github/client.js.map +1 -1
- package/dist/adapters/github/context.d.ts +3 -8
- package/dist/adapters/github/context.d.ts.map +1 -1
- package/dist/adapters/github/context.js +64 -131
- package/dist/adapters/github/context.js.map +1 -1
- package/dist/adapters/github/github-ops.d.ts +22 -0
- package/dist/adapters/github/github-ops.d.ts.map +1 -0
- package/dist/adapters/github/github-ops.js +277 -0
- package/dist/adapters/github/github-ops.js.map +1 -0
- package/dist/adapters/github/ids.d.ts +2 -23
- package/dist/adapters/github/ids.d.ts.map +1 -1
- package/dist/adapters/github/ids.js +6 -36
- package/dist/adapters/github/ids.js.map +1 -1
- package/dist/adapters/github/repo.d.ts +8 -0
- package/dist/adapters/github/repo.d.ts.map +1 -0
- package/dist/adapters/github/repo.js +92 -0
- package/dist/adapters/github/repo.js.map +1 -0
- package/dist/adapters/github/tool-pack.d.ts +4 -0
- package/dist/adapters/github/tool-pack.d.ts.map +1 -0
- package/dist/adapters/github/tool-pack.js +45 -0
- package/dist/adapters/github/tool-pack.js.map +1 -0
- package/dist/adapters/github/tools/checks.d.ts +14 -0
- package/dist/adapters/github/tools/checks.d.ts.map +1 -0
- package/dist/adapters/github/tools/checks.js +80 -0
- package/dist/adapters/github/tools/checks.js.map +1 -0
- package/dist/adapters/github/tools/issue.d.ts +18 -0
- package/dist/adapters/github/tools/issue.d.ts.map +1 -0
- package/dist/adapters/github/tools/issue.js +44 -0
- package/dist/adapters/github/tools/issue.js.map +1 -0
- package/dist/adapters/github/tools/pr.d.ts +17 -0
- package/dist/adapters/github/tools/pr.d.ts.map +1 -0
- package/dist/adapters/github/tools/pr.js +42 -0
- package/dist/adapters/github/tools/pr.js.map +1 -0
- package/dist/adapters/github/tools/read.d.ts +17 -0
- package/dist/adapters/github/tools/read.d.ts.map +1 -0
- package/dist/adapters/github/tools/read.js +134 -0
- package/dist/adapters/github/tools/read.js.map +1 -0
- package/dist/adapters/github/tools/review-reply.d.ts +14 -0
- package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
- package/dist/adapters/github/tools/review-reply.js +34 -0
- package/dist/adapters/github/tools/review-reply.js.map +1 -0
- package/dist/adapters/github/tools/sync.d.ts +13 -0
- package/dist/adapters/github/tools/sync.d.ts.map +1 -0
- package/dist/adapters/github/tools/sync.js +29 -0
- package/dist/adapters/github/tools/sync.js.map +1 -0
- package/dist/adapters/github/types.d.ts +197 -18
- package/dist/adapters/github/types.d.ts.map +1 -1
- package/dist/adapters/github/types.js.map +1 -1
- package/dist/adapters/github/webhook.d.ts +6 -0
- package/dist/adapters/github/webhook.d.ts.map +1 -0
- package/dist/adapters/github/webhook.js +62 -0
- package/dist/adapters/github/webhook.js.map +1 -0
- package/dist/adapters/intake.d.ts +4 -10
- package/dist/adapters/intake.d.ts.map +1 -1
- package/dist/adapters/intake.js +74 -31
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/markdown-tables.d.ts +8 -0
- package/dist/adapters/markdown-tables.d.ts.map +1 -0
- package/dist/adapters/markdown-tables.js +111 -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 +423 -0
- package/dist/adapters/progressive-renderer.js.map +1 -0
- package/dist/adapters/shared.d.ts +24 -53
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +140 -91
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/assistant.d.ts +23 -0
- package/dist/adapters/slack/assistant.d.ts.map +1 -0
- package/dist/adapters/slack/assistant.js +109 -0
- package/dist/adapters/slack/assistant.js.map +1 -0
- package/dist/adapters/slack/auto-reply-context.d.ts +14 -0
- package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -0
- package/dist/adapters/slack/auto-reply-context.js +21 -0
- package/dist/adapters/slack/auto-reply-context.js.map +1 -0
- package/dist/adapters/slack/blocks.d.ts +5 -0
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +84 -148
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +51 -39
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +892 -567
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +3 -9
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +8 -17
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/jev-context.d.ts +17 -0
- package/dist/adapters/slack/jev-context.d.ts.map +1 -0
- package/dist/adapters/slack/jev-context.js +57 -0
- package/dist/adapters/slack/jev-context.js.map +1 -0
- package/dist/adapters/slack/markdown.d.ts +2 -0
- package/dist/adapters/slack/markdown.d.ts.map +1 -0
- package/dist/adapters/slack/markdown.js +43 -0
- package/dist/adapters/slack/markdown.js.map +1 -0
- package/dist/adapters/slack/persona.d.ts +6 -0
- package/dist/adapters/slack/persona.d.ts.map +1 -0
- package/dist/adapters/slack/persona.js +13 -0
- package/dist/adapters/slack/persona.js.map +1 -0
- package/dist/adapters/slack/response-lifecycle.d.ts +3 -4
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +221 -347
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +3 -2
- package/dist/adapters/slack/session.d.ts.map +1 -1
- package/dist/adapters/slack/session.js +9 -13
- package/dist/adapters/slack/session.js.map +1 -1
- package/dist/adapters/slack/stream-limits.d.ts +10 -0
- package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
- package/dist/adapters/slack/stream-limits.js +22 -0
- package/dist/adapters/slack/stream-limits.js.map +1 -0
- package/dist/adapters/slack/task-intent.d.ts +19 -0
- package/dist/adapters/slack/task-intent.d.ts.map +1 -0
- package/dist/adapters/slack/task-intent.js +65 -0
- package/dist/adapters/slack/task-intent.js.map +1 -0
- package/dist/adapters/slack/task-status.d.ts +6 -0
- package/dist/adapters/slack/task-status.d.ts.map +1 -0
- package/dist/adapters/slack/task-status.js +100 -0
- package/dist/adapters/slack/task-status.js.map +1 -0
- package/dist/adapters/slack/tool-pack.d.ts +4 -0
- package/dist/adapters/slack/tool-pack.d.ts.map +1 -0
- package/dist/adapters/slack/tool-pack.js +40 -0
- package/dist/adapters/slack/tool-pack.js.map +1 -0
- package/dist/adapters/slack/tools/blockkit.d.ts +17 -0
- package/dist/adapters/slack/tools/blockkit.d.ts.map +1 -0
- package/dist/adapters/slack/tools/blockkit.js +98 -0
- package/dist/adapters/slack/tools/blockkit.js.map +1 -0
- package/dist/adapters/slack/types.d.ts +91 -5
- 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 +13 -0
- package/dist/adapters/slack/update-diagnostics.d.ts.map +1 -0
- package/dist/adapters/slack/update-diagnostics.js +128 -0
- package/dist/adapters/slack/update-diagnostics.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts +8 -18
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +114 -232
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +3 -7
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +47 -191
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/types.d.ts +24 -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 +89 -16
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/adapters/web/admin/client-assets.d.ts +4 -0
- package/dist/adapters/web/admin/client-assets.d.ts.map +1 -0
- package/dist/adapters/web/admin/client-assets.js +1766 -0
- package/dist/adapters/web/admin/client-assets.js.map +1 -0
- package/dist/adapters/web/admin/portal.d.ts +27 -0
- package/dist/adapters/web/admin/portal.d.ts.map +1 -0
- package/dist/adapters/web/admin/portal.js +1506 -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 +56 -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 +4 -0
- package/dist/adapters/web/login/oauth.d.ts.map +1 -0
- package/dist/{web → adapters/web}/login/oauth.js +61 -100
- package/dist/adapters/web/login/oauth.js.map +1 -0
- package/dist/adapters/web/login/portal.d.ts +10 -0
- package/dist/adapters/web/login/portal.d.ts.map +1 -0
- package/dist/{web → adapters/web}/login/portal.js +169 -159
- package/dist/adapters/web/login/portal.js.map +1 -0
- package/dist/{web → adapters/web}/login/types.d.ts +1 -17
- 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 +9 -0
- package/dist/adapters/web/portal-shell.d.ts.map +1 -0
- package/dist/{portal-shell.js → adapters/web/portal-shell.js} +73 -11
- package/dist/adapters/web/portal-shell.js.map +1 -0
- package/dist/adapters/web/server.d.ts +33 -0
- package/dist/adapters/web/server.d.ts.map +1 -0
- package/dist/{web → adapters/web}/server.js +15 -9
- package/dist/adapters/web/server.js.map +1 -0
- package/dist/adapters/web/session-view/portal.d.ts +19 -0
- package/dist/adapters/web/session-view/portal.d.ts.map +1 -0
- package/dist/{web → adapters/web}/session-view/portal.js +641 -181
- package/dist/adapters/web/session-view/portal.js.map +1 -0
- package/dist/{web → adapters/web}/session-view/types.d.ts +1 -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/{web → adapters/web}/token-store.d.ts +0 -3
- package/dist/adapters/web/token-store.d.ts.map +1 -0
- package/dist/{web → adapters/web}/token-store.js +1 -3
- 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 +8 -0
- package/dist/cli/arg-grammar.d.ts.map +1 -0
- package/dist/cli/arg-grammar.js +47 -0
- package/dist/cli/arg-grammar.js.map +1 -0
- package/dist/cli/boot.d.ts +4 -0
- package/dist/cli/boot.d.ts.map +1 -0
- package/dist/cli/boot.js +81 -0
- package/dist/cli/boot.js.map +1 -0
- package/dist/cli/download.d.ts.map +1 -0
- package/dist/cli/download.js +84 -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 +151 -0
- package/dist/cli/office.js.map +1 -0
- package/dist/cli/onboard.d.ts +8 -0
- package/dist/cli/onboard.d.ts.map +1 -0
- package/dist/cli/onboard.js +195 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/process-lifecycle.d.ts +14 -0
- package/dist/cli/process-lifecycle.d.ts.map +1 -0
- package/dist/cli/process-lifecycle.js +61 -0
- package/dist/cli/process-lifecycle.js.map +1 -0
- package/dist/cli/sandbox.d.ts +3 -0
- package/dist/cli/sandbox.d.ts.map +1 -0
- package/dist/cli/sandbox.js +80 -0
- package/dist/cli/sandbox.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 +78 -0
- package/dist/cli/sessions.js.map +1 -0
- package/dist/cli/types.d.ts +21 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/env-manifest.d.ts +17 -0
- package/dist/env-manifest.d.ts.map +1 -0
- package/dist/env-manifest.js +249 -0
- package/dist/env-manifest.js.map +1 -0
- package/dist/events/index.d.ts +122 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +299 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/scheduler.d.ts +25 -0
- package/dist/events/scheduler.d.ts.map +1 -0
- package/dist/events/scheduler.js +223 -0
- package/dist/events/scheduler.js.map +1 -0
- package/dist/file-guards.d.ts +13 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +104 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/harness/execution-env.d.ts +4 -0
- package/dist/harness/execution-env.d.ts.map +1 -0
- package/dist/harness/execution-env.js +245 -0
- package/dist/harness/execution-env.js.map +1 -0
- package/dist/harness/execution-resolver.d.ts +23 -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/http.d.ts +0 -12
- package/dist/harness/http.d.ts.map +1 -1
- package/dist/harness/http.js +0 -34
- package/dist/harness/http.js.map +1 -1
- package/dist/harness/index.d.ts +19 -17
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +12 -15
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/jev.d.ts +67 -0
- package/dist/harness/jev.d.ts.map +1 -0
- package/dist/harness/jev.js +135 -0
- package/dist/harness/jev.js.map +1 -0
- package/dist/harness/mcp-result.d.ts +20 -0
- package/dist/harness/mcp-result.d.ts.map +1 -0
- package/dist/harness/mcp-result.js +179 -0
- package/dist/harness/mcp-result.js.map +1 -0
- package/dist/harness/mcp.d.ts +17 -0
- package/dist/harness/mcp.d.ts.map +1 -0
- package/dist/harness/mcp.js +243 -0
- package/dist/harness/mcp.js.map +1 -0
- package/dist/harness/models.d.ts +2 -19
- package/dist/harness/models.d.ts.map +1 -1
- package/dist/harness/models.js +42 -94
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/open-connector.d.ts +12 -0
- package/dist/harness/open-connector.d.ts.map +1 -0
- package/dist/harness/open-connector.js +160 -0
- package/dist/harness/open-connector.js.map +1 -0
- package/dist/harness/presenter.d.ts +11 -0
- package/dist/harness/presenter.d.ts.map +1 -0
- package/dist/harness/presenter.js +824 -0
- package/dist/harness/presenter.js.map +1 -0
- package/dist/harness/prompt.d.ts +18 -0
- package/dist/harness/prompt.d.ts.map +1 -0
- package/dist/harness/prompt.js +403 -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 +547 -415
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session.d.ts +98 -0
- package/dist/harness/session.d.ts.map +1 -0
- package/dist/harness/session.js +645 -0
- package/dist/harness/session.js.map +1 -0
- package/dist/harness/skills.d.ts +7 -41
- package/dist/harness/skills.d.ts.map +1 -1
- package/dist/harness/skills.js +101 -55
- package/dist/harness/skills.js.map +1 -1
- package/dist/harness/subagent-profiles.d.ts +3 -0
- package/dist/harness/subagent-profiles.d.ts.map +1 -0
- package/dist/harness/subagent-profiles.js +260 -0
- package/dist/harness/subagent-profiles.js.map +1 -0
- package/dist/harness/subagent.d.ts +39 -0
- package/dist/harness/subagent.d.ts.map +1 -0
- package/dist/harness/subagent.js +618 -0
- package/dist/harness/subagent.js.map +1 -0
- package/dist/harness/tool-args.d.ts +2 -0
- package/dist/harness/tool-args.d.ts.map +1 -0
- package/dist/harness/tool-args.js +20 -0
- package/dist/harness/tool-args.js.map +1 -0
- package/dist/harness/tools/attach.d.ts +13 -0
- package/dist/harness/tools/attach.d.ts.map +1 -0
- package/dist/{adapters/slack → harness}/tools/attach.js +2 -2
- package/dist/harness/tools/attach.js.map +1 -0
- package/dist/harness/tools/event.d.ts +28 -0
- package/dist/harness/tools/event.d.ts.map +1 -0
- package/dist/harness/tools/event.js +166 -0
- package/dist/harness/tools/event.js.map +1 -0
- package/dist/harness/tools/generate-image.d.ts +20 -0
- package/dist/harness/tools/generate-image.d.ts.map +1 -0
- package/dist/harness/tools/generate-image.js +68 -0
- package/dist/harness/tools/generate-image.js.map +1 -0
- package/dist/harness/tools/host-fn-tool.d.ts +14 -0
- package/dist/harness/tools/host-fn-tool.d.ts.map +1 -0
- package/dist/harness/tools/host-fn-tool.js +39 -0
- package/dist/harness/tools/host-fn-tool.js.map +1 -0
- package/dist/harness/tools/index.d.ts +34 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +49 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/jev-browser.d.ts +42 -0
- package/dist/harness/tools/jev-browser.d.ts.map +1 -0
- package/dist/harness/tools/jev-browser.js +547 -0
- package/dist/harness/tools/jev-browser.js.map +1 -0
- package/dist/harness/tools/jev.d.ts +15 -0
- package/dist/harness/tools/jev.d.ts.map +1 -0
- package/dist/harness/tools/jev.js +98 -0
- package/dist/harness/tools/jev.js.map +1 -0
- package/dist/harness/tools/pi-tools.d.ts +8 -0
- package/dist/harness/tools/pi-tools.d.ts.map +1 -0
- package/dist/harness/tools/pi-tools.js +55 -0
- package/dist/harness/tools/pi-tools.js.map +1 -0
- package/dist/harness/tools/react.d.ts +11 -0
- package/dist/harness/tools/react.d.ts.map +1 -0
- package/dist/harness/tools/react.js +24 -0
- package/dist/harness/tools/react.js.map +1 -0
- package/dist/harness/tools/sandbox.d.ts +24 -0
- package/dist/harness/tools/sandbox.d.ts.map +1 -0
- package/dist/{tools → harness/tools}/sandbox.js +15 -9
- package/dist/harness/tools/sandbox.js.map +1 -0
- package/dist/harness/tools/secret-redaction.d.ts +9 -0
- package/dist/harness/tools/secret-redaction.d.ts.map +1 -0
- package/dist/harness/tools/secret-redaction.js +44 -0
- package/dist/harness/tools/secret-redaction.js.map +1 -0
- package/dist/harness/tools/subagent.d.ts +51 -0
- package/dist/harness/tools/subagent.d.ts.map +1 -0
- package/dist/harness/tools/subagent.js +581 -0
- package/dist/harness/tools/subagent.js.map +1 -0
- package/dist/harness/tools/task.d.ts +22 -0
- package/dist/harness/tools/task.d.ts.map +1 -0
- package/dist/harness/tools/task.js +80 -0
- package/dist/harness/tools/task.js.map +1 -0
- package/dist/harness/tools/types.d.ts +14 -0
- package/dist/harness/tools/types.d.ts.map +1 -0
- package/dist/harness/tools/types.js.map +1 -0
- package/dist/harness/types.d.ts +345 -29
- 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 +36 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -7
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +0 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +3 -13
- package/dist/log.js.map +1 -1
- package/dist/main.js +300 -217
- package/dist/main.js.map +1 -1
- package/dist/memory-capture/index.d.ts +15 -0
- package/dist/memory-capture/index.d.ts.map +1 -0
- package/dist/memory-capture/index.js +199 -0
- package/dist/memory-capture/index.js.map +1 -0
- package/dist/memory-capture/types.d.ts +27 -0
- package/dist/memory-capture/types.d.ts.map +1 -0
- package/dist/memory-capture/types.js +2 -0
- package/dist/memory-capture/types.js.map +1 -0
- package/dist/observability/index.d.ts +30 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +273 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/instrument.js +9 -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 +182 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/observability/sentry.d.ts +32 -13
- package/dist/observability/sentry.d.ts.map +1 -1
- package/dist/observability/sentry.js +85 -58
- package/dist/observability/sentry.js.map +1 -1
- package/dist/observability/types.d.ts +30 -3
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/observability/types.js.map +1 -1
- package/dist/office/index.d.ts +74 -0
- package/dist/office/index.d.ts.map +1 -0
- package/dist/office/index.js +971 -0
- package/dist/office/index.js.map +1 -0
- package/dist/office/projection.d.ts +11 -0
- package/dist/office/projection.d.ts.map +1 -0
- package/dist/office/projection.js +146 -0
- package/dist/office/projection.js.map +1 -0
- package/dist/office/types.d.ts +55 -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 +0 -1
- package/dist/runtime/conversation-runtime.d.ts.map +1 -1
- package/dist/runtime/conversation-runtime.js +375 -152
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +66 -0
- package/dist/runtime/session-lifecycle.d.ts.map +1 -0
- package/dist/runtime/session-lifecycle.js +393 -0
- package/dist/runtime/session-lifecycle.js.map +1 -0
- package/dist/runtime/types.d.ts +41 -17
- 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 +59 -55
- 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 +39 -20
- 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 +42 -35
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +13 -0
- package/dist/sandbox/identity.d.ts.map +1 -0
- package/dist/sandbox/identity.js +49 -0
- package/dist/sandbox/identity.js.map +1 -0
- package/dist/sandbox/index.d.ts +5 -15
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +5 -51
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/layout.d.ts +7 -0
- package/dist/sandbox/layout.d.ts.map +1 -0
- package/dist/sandbox/layout.js +13 -0
- package/dist/sandbox/layout.js.map +1 -0
- package/dist/{provisioner.d.ts → sandbox/provisioner.d.ts} +41 -4
- package/dist/sandbox/provisioner.d.ts.map +1 -0
- package/dist/sandbox/provisioner.js +971 -0
- package/dist/sandbox/provisioner.js.map +1 -0
- package/dist/sandbox/registry.d.ts +12 -0
- package/dist/sandbox/registry.d.ts.map +1 -0
- package/dist/sandbox/registry.js +93 -0
- package/dist/sandbox/registry.js.map +1 -0
- package/dist/sandbox/types.d.ts +23 -25
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/utils.d.ts +21 -0
- package/dist/sandbox/utils.d.ts.map +1 -1
- package/dist/sandbox/utils.js +100 -8
- package/dist/sandbox/utils.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts +17 -0
- package/dist/sessions/chat-history-sync.d.ts.map +1 -0
- package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +224 -183
- package/dist/sessions/chat-history-sync.js.map +1 -0
- package/dist/sessions/history-line.d.ts +9 -0
- package/dist/sessions/history-line.d.ts.map +1 -0
- package/dist/sessions/history-line.js +25 -0
- package/dist/sessions/history-line.js.map +1 -0
- package/dist/sessions/migrate-common.d.ts +28 -0
- package/dist/sessions/migrate-common.d.ts.map +1 -0
- package/dist/sessions/migrate-common.js +80 -0
- package/dist/sessions/migrate-common.js.map +1 -0
- package/dist/sessions/migrate-pi-084.d.ts +7 -0
- package/dist/sessions/migrate-pi-084.d.ts.map +1 -0
- package/dist/sessions/migrate-pi-084.js +293 -0
- package/dist/sessions/migrate-pi-084.js.map +1 -0
- package/dist/sessions/migrate-v3.d.ts +7 -0
- package/dist/sessions/migrate-v3.d.ts.map +1 -0
- package/dist/sessions/migrate-v3.js +351 -0
- package/dist/sessions/migrate-v3.js.map +1 -0
- package/dist/sessions/session-key.d.ts +18 -0
- package/dist/sessions/session-key.d.ts.map +1 -0
- package/dist/sessions/session-key.js +88 -0
- package/dist/sessions/session-key.js.map +1 -0
- package/dist/sessions/session-store.d.ts +55 -0
- package/dist/sessions/session-store.d.ts.map +1 -0
- package/dist/sessions/session-store.js +635 -0
- package/dist/sessions/session-store.js.map +1 -0
- package/dist/sessions/store.d.ts +7 -61
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +126 -102
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +59 -8
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +2 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/settings/apply.d.ts +9 -0
- package/dist/settings/apply.d.ts.map +1 -0
- package/dist/settings/apply.js +31 -0
- package/dist/settings/apply.js.map +1 -0
- package/dist/settings/index.d.ts +76 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +351 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/migrate.d.ts +16 -0
- package/dist/settings/migrate.d.ts.map +1 -0
- package/dist/settings/migrate.js +70 -0
- package/dist/settings/migrate.js.map +1 -0
- package/dist/types.d.ts +231 -173
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -3
- package/dist/types.js.map +1 -1
- package/dist/unknown-values.d.ts +3 -0
- package/dist/unknown-values.d.ts.map +1 -0
- package/dist/unknown-values.js +7 -0
- package/dist/unknown-values.js.map +1 -0
- package/dist/vault/index.d.ts +35 -11
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +326 -115
- package/dist/vault/index.js.map +1 -1
- package/dist/vault/types.d.ts +5 -16
- package/dist/vault/types.d.ts.map +1 -1
- package/dist/vault/types.js.map +1 -1
- package/package.json +82 -41
- 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 +0 -12
- 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.map +0 -1
- package/dist/provisioner.js +0 -517
- 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 -24
- 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 +0 -56
- 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 +0 -15
- package/dist/tools/react.d.ts.map +0 -1
- package/dist/tools/react.js +0 -40
- 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 +0 -22
- 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 +0 -17
- 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 +0 -28
- 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}/session-view.d.ts +0 -0
- /package/dist/{commands → adapters/commands}/types.js +0 -0
- /package/dist/{harness/extensions → adapters/web/admin}/types.js +0 -0
- /package/dist/{tools → adapters/web/login}/types.js +0 -0
- /package/dist/{web/admin → adapters/web/session-view}/types.js +0 -0
- /package/dist/{web → adapters/web}/types.d.ts +0 -0
- /package/dist/{web/login → adapters/web}/types.js +0 -0
- /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
- /package/dist/{web/session-view → cli}/types.js +0 -0
- /package/dist/{web → harness/tools}/types.js +0 -0
|
@@ -1,22 +1,56 @@
|
|
|
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
|
|
6
|
-
import {
|
|
2
|
+
import { WebAPIRateLimitedError, WebClient } from "@slack/web-api";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { basename } from "node:path";
|
|
6
|
+
import { createConversationEvent, createConversationMessage, createOfficeAddress, listRegisteredOffices, } from "../../office/index.js";
|
|
7
|
+
import { COMMAND_MANIFEST } from "../commands/manifest.js";
|
|
8
|
+
import { slackConversationAutoReplyMode, resolveConversationSettings, } from "../../settings/index.js";
|
|
9
|
+
import { evaluateWithJev, JevNotConfiguredError } from "../../harness/jev.js";
|
|
7
10
|
import * as log from "../../log.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
+
import { readTextFileIfExists } from "../../file-guards.js";
|
|
12
|
+
import { PRODUCT_NAME, formatForceStopped } from "../messages.js";
|
|
13
|
+
import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, MessagingIntakeTracker, AttachmentRejectedError, saveIncomingAttachments, writeResponseToFile, withRetry, } from "../shared.js";
|
|
14
|
+
import { matchMagicWord, processMessageIntake } from "../intake.js";
|
|
15
|
+
import { readPlatformChannelKind, recordPlatformChannelKind } from "../../office/projection.js";
|
|
16
|
+
import { AssistantThreadRegistry, handleAgentContextChanged, handleAgentDmOpened, handleAssistantThreadStarted, titleAssistantThread, } from "./assistant.js";
|
|
11
17
|
import { createSlackAdapters } from "./context.js";
|
|
12
|
-
import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/
|
|
18
|
+
import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/chat-history-sync.js";
|
|
19
|
+
import { conversationIdOf } from "../../sessions/session-key.js";
|
|
13
20
|
import { isSlackThreadSessionKey, planSlackAdapterSession, planSlackEventAnchorRun, resolveSlackSessionKey, } from "./session.js";
|
|
14
|
-
import { reportUserFacingError } from "../../observability/
|
|
15
|
-
import {
|
|
21
|
+
import { reportUserFacingError } from "../../observability/index.js";
|
|
22
|
+
import { recordSlackUpdate } from "./update-diagnostics.js";
|
|
23
|
+
import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
|
|
24
|
+
import { querySlackTasks, formatTaskStatus, readTaskRoots, isTaskStatusQuestion, } from "./task-status.js";
|
|
25
|
+
import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
|
|
26
|
+
import { buildTaskIntentState, classifyTaskIntent } from "./task-intent.js";
|
|
27
|
+
import { StreamStartLimiter } from "./stream-limits.js";
|
|
28
|
+
import { errorMessage, isRecord } from "../../unknown-values.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
|
+
const MAX_STREAM_TEXT_CHARS = 12_000;
|
|
42
|
+
export function chunkStreamText(text, limit = MAX_STREAM_TEXT_CHARS) {
|
|
43
|
+
if (text.length <= limit)
|
|
44
|
+
return [text];
|
|
45
|
+
const chunks = [];
|
|
46
|
+
for (let index = 0; index < text.length; index += limit) {
|
|
47
|
+
chunks.push(text.slice(index, index + limit));
|
|
48
|
+
}
|
|
49
|
+
return chunks;
|
|
50
|
+
}
|
|
19
51
|
function slackIsRateLimited(err) {
|
|
52
|
+
if (err instanceof WebAPIRateLimitedError)
|
|
53
|
+
return true;
|
|
20
54
|
if (err.code === "rate_limited")
|
|
21
55
|
return true;
|
|
22
56
|
const data = err.data;
|
|
@@ -55,67 +89,172 @@ function buildSlackAppMessageText(event) {
|
|
|
55
89
|
const deduped = parts.filter((part, index) => parts.indexOf(part) === index);
|
|
56
90
|
return deduped.join("\n");
|
|
57
91
|
}
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Shared mrkdwn truncation helper
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
92
|
const MRKDWN_CONTEXT_TEXT_LIMIT = 3000;
|
|
62
|
-
/**
|
|
63
|
-
* Build a Slack context block whose text is capped at the mrkdwn limit.
|
|
64
|
-
* Used for muted diagnostics and ephemeral command responses.
|
|
65
|
-
*/
|
|
66
93
|
export function buildMrkdwnContextBlock(text) {
|
|
67
94
|
const blockText = text.length > MRKDWN_CONTEXT_TEXT_LIMIT
|
|
68
95
|
? text.substring(0, MRKDWN_CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
|
|
69
96
|
: text;
|
|
70
97
|
return { type: "context", elements: [{ type: "mrkdwn", text: blockText }] };
|
|
71
98
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
class AttachmentDownloadHttpError extends Error {
|
|
100
|
+
constructor(message, status) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.status = status;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function isRetryableAttachmentDownloadError(error) {
|
|
106
|
+
if (error instanceof AttachmentRejectedError)
|
|
107
|
+
return false;
|
|
108
|
+
if (!(error instanceof AttachmentDownloadHttpError))
|
|
109
|
+
return true;
|
|
110
|
+
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
111
|
+
}
|
|
112
|
+
function assertSlackFileUrl(url) {
|
|
113
|
+
const { protocol, hostname } = new URL(url);
|
|
114
|
+
if (protocol === "https:" && (hostname === "slack.com" || hostname.endsWith(".slack.com")))
|
|
115
|
+
return;
|
|
116
|
+
throw new AttachmentRejectedError(`refusing to send the bot token to ${hostname}`);
|
|
117
|
+
}
|
|
75
118
|
export class SlackMessagingBot {
|
|
119
|
+
office(channelId) {
|
|
120
|
+
return this.workspace.office(createOfficeAddress("slack", channelId));
|
|
121
|
+
}
|
|
122
|
+
conversationDir(channelId) {
|
|
123
|
+
return this.office(channelId).dir;
|
|
124
|
+
}
|
|
125
|
+
resolveReplyMode(address) {
|
|
126
|
+
return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
|
|
127
|
+
}
|
|
76
128
|
createContext(event) {
|
|
77
|
-
|
|
78
|
-
replyMode:
|
|
79
|
-
"top-level",
|
|
129
|
+
const context = createSlackAdapters(event, this, {
|
|
130
|
+
replyMode: this.resolveReplyMode(event.address),
|
|
80
131
|
});
|
|
132
|
+
if (event.conversationKind === "direct") {
|
|
133
|
+
context.responder.getTaskStatus = (key) => querySlackTasks(this.conversationDir(event.channel), event.channel, this.handler
|
|
134
|
+
.getRunningSessions()
|
|
135
|
+
.filter((s) => s.address.platform === "slack" && s.address.conversationId === event.channel), key);
|
|
136
|
+
}
|
|
137
|
+
if (event.conversationKind === "direct" &&
|
|
138
|
+
event.thread_ts &&
|
|
139
|
+
this.isTaskThread(event.channel, event.thread_ts)) {
|
|
140
|
+
let notified = false;
|
|
141
|
+
context.responder.notifyCompletion = async () => {
|
|
142
|
+
if (notified)
|
|
143
|
+
return;
|
|
144
|
+
const text = `<@${event.user}> 這一輪處理已結束,請查看上方結果。`;
|
|
145
|
+
const ts = await this.postInThread(event.channel, event.thread_ts, text);
|
|
146
|
+
notified = true;
|
|
147
|
+
this.logBotResponse(event.channel, text, ts, event.thread_ts);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (event.conversationKind === "direct" && !event.thread_ts) {
|
|
151
|
+
context.responder.startTask = async (message, task) => {
|
|
152
|
+
if (this.stopped)
|
|
153
|
+
throw new Error("Slack is shutting down; task was not started.");
|
|
154
|
+
await context.responder.deleteResponse();
|
|
155
|
+
const root = await this.postMessage(event.channel, message);
|
|
156
|
+
this.logToFile(event.channel, {
|
|
157
|
+
date: new Date().toISOString(),
|
|
158
|
+
ts: root,
|
|
159
|
+
user: "bot",
|
|
160
|
+
text: message,
|
|
161
|
+
isMessagingBot: true,
|
|
162
|
+
taskRoot: true,
|
|
163
|
+
});
|
|
164
|
+
const sessionKey = resolveSlackSessionKey(event.channel, root);
|
|
165
|
+
try {
|
|
166
|
+
registerThreadSession({
|
|
167
|
+
conversationDir: this.conversationDir(event.channel),
|
|
168
|
+
sessionKey,
|
|
169
|
+
});
|
|
170
|
+
const child = {
|
|
171
|
+
...event,
|
|
172
|
+
ts: `task:${root}`,
|
|
173
|
+
thread_ts: root,
|
|
174
|
+
sessionKey,
|
|
175
|
+
text: task,
|
|
176
|
+
};
|
|
177
|
+
if (!this.getQueue(sessionKey).enqueue(async () => {
|
|
178
|
+
try {
|
|
179
|
+
await this.handler.handleEvent({
|
|
180
|
+
...child,
|
|
181
|
+
attachments: child.attachments?.map((a) => ({
|
|
182
|
+
name: a.original,
|
|
183
|
+
localPath: a.localPath,
|
|
184
|
+
})),
|
|
185
|
+
}, this, this.createContext(child));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
reportUserFacingError(error, {
|
|
189
|
+
domain: "mikan",
|
|
190
|
+
surface: "task_handoff",
|
|
191
|
+
operation: "start_task_run",
|
|
192
|
+
severity: "error",
|
|
193
|
+
platform: "slack",
|
|
194
|
+
context: { conversationId: event.channel, sessionKey, threadTs: root },
|
|
195
|
+
});
|
|
196
|
+
await this.postInThread(event.channel, root, "Task could not start. Please reply here to try again.");
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}))
|
|
200
|
+
throw new Error("Task queue is closed.");
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
await this.updateMessage(event.channel, root, `${message}\n\nTask could not start.`);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
return sessionKey;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return context;
|
|
81
210
|
}
|
|
82
211
|
constructor(handler, config) {
|
|
212
|
+
this.statusUpdates = new Map();
|
|
83
213
|
this.botUserId = null;
|
|
84
214
|
this.botId = null;
|
|
85
215
|
this.teamId = null;
|
|
86
216
|
this.ownMentionRegex = null;
|
|
87
|
-
this.startupTs = null;
|
|
217
|
+
this.startupTs = null;
|
|
88
218
|
this.users = new Map();
|
|
89
219
|
this.channels = new Map();
|
|
220
|
+
this.assistantThreads = new AssistantThreadRegistry();
|
|
221
|
+
this.streamStarts = new StreamStartLimiter();
|
|
222
|
+
this.stopped = false;
|
|
90
223
|
this.queues = new Map();
|
|
91
|
-
this.
|
|
224
|
+
this.intake = new MessagingIntakeTracker("Slack");
|
|
225
|
+
this.eventScheduler = null;
|
|
92
226
|
this.handler = handler;
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
227
|
+
this.workspace = config.workspace;
|
|
228
|
+
this.botToken = config.botToken;
|
|
229
|
+
this.fetchFn = config.fetch ?? ((input, init) => fetch(input, init));
|
|
230
|
+
this.socketClient =
|
|
231
|
+
config.socket ??
|
|
232
|
+
new SocketModeClient({
|
|
233
|
+
appToken: config.appToken,
|
|
234
|
+
clientPingTimeout: 12_000,
|
|
235
|
+
});
|
|
236
|
+
this.webClient = config.webApi ?? new WebClient(config.botToken, { fetch: config.fetch });
|
|
237
|
+
this.statusClient = new WebClient(config.botToken, {
|
|
238
|
+
timeout: 3000,
|
|
239
|
+
retryConfig: { retries: 0 },
|
|
240
|
+
rejectRateLimitedCalls: true,
|
|
241
|
+
fetch: config.fetch,
|
|
100
242
|
});
|
|
101
|
-
this.webClient = new WebClient(config.botToken);
|
|
102
243
|
}
|
|
103
|
-
|
|
104
|
-
this.
|
|
244
|
+
setEventScheduler(scheduler) {
|
|
245
|
+
this.eventScheduler = scheduler;
|
|
105
246
|
}
|
|
106
|
-
// ==========================================================================
|
|
107
|
-
// Public API
|
|
108
|
-
// ==========================================================================
|
|
109
247
|
async start() {
|
|
248
|
+
this.stopped = false;
|
|
110
249
|
const auth = await this.webClient.auth.test();
|
|
111
250
|
this.botUserId = auth.user_id;
|
|
112
251
|
this.botId = typeof auth.bot_id === "string" ? auth.bot_id : null;
|
|
113
252
|
this.teamId = typeof auth.team_id === "string" ? auth.team_id : null;
|
|
114
253
|
await Promise.all([this.fetchUsers(), this.fetchChannels()]);
|
|
254
|
+
if (this.stopped)
|
|
255
|
+
return;
|
|
115
256
|
log.logInfo(`Loaded ${this.channels.size} channels, ${this.users.size} users`);
|
|
116
|
-
|
|
117
|
-
// those should be logged but not processed. Backfill runs in the background up
|
|
118
|
-
// to this timestamp so startup is not blocked by one history call per channel.
|
|
257
|
+
this.backfillChannelKinds();
|
|
119
258
|
this.startupTs = (Date.now() / 1000).toFixed(6);
|
|
120
259
|
this.setupEventHandlers();
|
|
121
260
|
await this.socketClient.start();
|
|
@@ -124,6 +263,16 @@ export class SlackMessagingBot {
|
|
|
124
263
|
log.logWarning("Slack backfill failed", String(error));
|
|
125
264
|
});
|
|
126
265
|
}
|
|
266
|
+
async stop() {
|
|
267
|
+
this.stopped = true;
|
|
268
|
+
try {
|
|
269
|
+
await this.socketClient.disconnect();
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
await this.intake.close();
|
|
273
|
+
await Promise.all([...this.queues.values()].map((queue) => queue.close()));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
127
276
|
getUser(userId) {
|
|
128
277
|
return this.users.get(userId);
|
|
129
278
|
}
|
|
@@ -145,54 +294,64 @@ export class SlackMessagingBot {
|
|
|
145
294
|
}
|
|
146
295
|
return source.replace(this.ownMentionRegex, "").trim();
|
|
147
296
|
}
|
|
148
|
-
|
|
297
|
+
resolveMentions(text) {
|
|
298
|
+
return resolveSlackMentions(text, this.users.values());
|
|
299
|
+
}
|
|
300
|
+
async postMessage(channel, text, threadTs, identity) {
|
|
149
301
|
return slackRetry(async () => {
|
|
150
|
-
const payload = {
|
|
302
|
+
const payload = {
|
|
303
|
+
channel,
|
|
304
|
+
thread_ts: threadTs,
|
|
305
|
+
username: identity?.username,
|
|
306
|
+
icon_emoji: identity?.iconEmoji,
|
|
307
|
+
...renderSlackBlocks(this.resolveMentions(text)),
|
|
308
|
+
};
|
|
151
309
|
const result = await this.webClient.chat.postMessage(payload);
|
|
152
310
|
return result.ts;
|
|
153
311
|
});
|
|
154
312
|
}
|
|
155
313
|
async addReaction(channel, messageTs, emoji) {
|
|
156
|
-
// Slack reaction names are colon-free short names; strip any wrapping colons.
|
|
157
314
|
const name = emoji.replace(/^:|:$/g, "");
|
|
158
315
|
await slackRetry(async () => {
|
|
159
316
|
try {
|
|
160
317
|
await this.webClient.reactions.add({ channel, timestamp: messageTs, name });
|
|
161
318
|
}
|
|
162
319
|
catch (err) {
|
|
163
|
-
// Re-reacting with the same emoji is not an error worth surfacing.
|
|
164
320
|
if (err?.data?.error === "already_reacted")
|
|
165
321
|
return;
|
|
166
322
|
throw err;
|
|
167
323
|
}
|
|
168
324
|
});
|
|
169
325
|
}
|
|
170
|
-
async postEphemeral(channel, user, text, threadTs) {
|
|
326
|
+
async postEphemeral(channel, user, text, threadTs, blocks) {
|
|
171
327
|
return slackRetry(async () => {
|
|
172
328
|
await this.webClient.chat.postEphemeral({
|
|
173
329
|
channel,
|
|
174
330
|
user,
|
|
175
331
|
text,
|
|
176
|
-
|
|
332
|
+
blocks: blocks,
|
|
333
|
+
thread_ts: threadTs || undefined,
|
|
177
334
|
});
|
|
178
335
|
});
|
|
179
336
|
}
|
|
180
337
|
async postEphemeralBlocks(channel, user, text, blocks, threadTs) {
|
|
338
|
+
return this.postEphemeral(channel, user, text, threadTs, blocks);
|
|
339
|
+
}
|
|
340
|
+
async postMessageBlocks(channel, text, blocks, threadTs) {
|
|
181
341
|
return slackRetry(async () => {
|
|
182
|
-
|
|
342
|
+
const payload = {
|
|
183
343
|
channel,
|
|
184
|
-
user,
|
|
185
344
|
text,
|
|
186
345
|
blocks: blocks,
|
|
187
|
-
|
|
188
|
-
}
|
|
346
|
+
thread_ts: threadTs,
|
|
347
|
+
};
|
|
348
|
+
const result = await this.webClient.chat.postMessage(payload);
|
|
349
|
+
return result.ts;
|
|
189
350
|
});
|
|
190
351
|
}
|
|
191
|
-
async
|
|
352
|
+
async updateMessageBlocks(channel, ts, text, blocks) {
|
|
192
353
|
return slackRetry(async () => {
|
|
193
|
-
|
|
194
|
-
const result = await this.webClient.chat.postMessage(payload);
|
|
195
|
-
return result.ts;
|
|
354
|
+
await this.webClient.chat.update({ channel, ts, text, blocks: blocks });
|
|
196
355
|
});
|
|
197
356
|
}
|
|
198
357
|
async postPrivate(conversationId, userId, text) {
|
|
@@ -205,7 +364,7 @@ export class SlackMessagingBot {
|
|
|
205
364
|
}
|
|
206
365
|
await this.postEphemeralBlocks(conversationId, userId, text, [buildMrkdwnContextBlock(text)]);
|
|
207
366
|
}
|
|
208
|
-
async
|
|
367
|
+
async openDirectConversation(userId) {
|
|
209
368
|
return slackRetry(async () => {
|
|
210
369
|
const result = await this.webClient.conversations.open({ users: userId });
|
|
211
370
|
const channelId = result.channel?.id;
|
|
@@ -215,33 +374,101 @@ export class SlackMessagingBot {
|
|
|
215
374
|
return channelId;
|
|
216
375
|
});
|
|
217
376
|
}
|
|
377
|
+
async fetchHistory(channel, options) {
|
|
378
|
+
const limit = Math.min(Math.max(options?.limit ?? 200, 1), 999);
|
|
379
|
+
const threadTs = options?.threadTs;
|
|
380
|
+
return slackRetry(async () => {
|
|
381
|
+
const result = threadTs
|
|
382
|
+
? await this.webClient.conversations.replies({
|
|
383
|
+
channel,
|
|
384
|
+
ts: threadTs,
|
|
385
|
+
oldest: options?.oldest || undefined,
|
|
386
|
+
inclusive: options?.oldest ? false : undefined,
|
|
387
|
+
limit,
|
|
388
|
+
})
|
|
389
|
+
: await this.webClient.conversations.history({
|
|
390
|
+
channel,
|
|
391
|
+
oldest: options?.oldest || undefined,
|
|
392
|
+
inclusive: options?.oldest ? false : undefined,
|
|
393
|
+
limit,
|
|
394
|
+
});
|
|
395
|
+
const messages = (result.messages ?? []);
|
|
396
|
+
const mapped = messages
|
|
397
|
+
.filter((msg) => !!msg.ts)
|
|
398
|
+
.filter((msg) => msg.ts !== threadTs)
|
|
399
|
+
.map((msg) => {
|
|
400
|
+
const user = msg.user ? this.users.get(msg.user) : undefined;
|
|
401
|
+
const message = {
|
|
402
|
+
ts: msg.ts,
|
|
403
|
+
text: msg.text ?? "",
|
|
404
|
+
isBot: !!msg.bot_id || msg.subtype === "bot_message" || user?.isBot === true,
|
|
405
|
+
};
|
|
406
|
+
if (msg.thread_ts && msg.thread_ts !== msg.ts)
|
|
407
|
+
message.threadTs = msg.thread_ts;
|
|
408
|
+
if (msg.user)
|
|
409
|
+
message.userId = msg.user;
|
|
410
|
+
if (user)
|
|
411
|
+
message.userName = user.userName;
|
|
412
|
+
return message;
|
|
413
|
+
});
|
|
414
|
+
return threadTs ? mapped : mapped.toReversed();
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
async listUsers() {
|
|
418
|
+
await slackRetry(() => this.fetchUsers());
|
|
419
|
+
return this.getAllUsers().map((user) => ({
|
|
420
|
+
id: user.id,
|
|
421
|
+
userName: user.userName,
|
|
422
|
+
displayName: user.displayName,
|
|
423
|
+
isBot: user.isBot === true,
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
218
426
|
async updateMessage(channel, ts, text) {
|
|
219
427
|
return slackRetry(async () => {
|
|
220
|
-
const payload = { channel, ts, ...renderSlackBlocks(text) };
|
|
221
|
-
|
|
428
|
+
const payload = { channel, ts, ...renderSlackBlocks(this.resolveMentions(text)) };
|
|
429
|
+
try {
|
|
430
|
+
await this.webClient.chat.update(payload);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
recordSlackUpdate(this, { channel, ts }, text, payload, { error });
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
recordSlackUpdate(this, { channel, ts }, text, payload, { success: true });
|
|
222
437
|
});
|
|
223
438
|
}
|
|
439
|
+
tryReserveStreamStart() {
|
|
440
|
+
return this.streamStarts.tryReserve();
|
|
441
|
+
}
|
|
224
442
|
async startMessageStream(channel, text, threadTs, recipientUserId) {
|
|
225
|
-
|
|
443
|
+
const [head, ...tail] = chunkStreamText(text);
|
|
444
|
+
const ts = await slackRetry(async () => {
|
|
226
445
|
const result = await this.webClient.apiCall("chat.startStream", {
|
|
227
446
|
channel,
|
|
228
|
-
markdown_text:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
447
|
+
markdown_text: this.resolveMentions(head ?? ""),
|
|
448
|
+
thread_ts: threadTs || undefined,
|
|
449
|
+
recipient_team_id: this.teamId || undefined,
|
|
450
|
+
recipient_user_id: recipientUserId || undefined,
|
|
232
451
|
});
|
|
233
|
-
const
|
|
234
|
-
if (!
|
|
452
|
+
const streamTs = result.ts;
|
|
453
|
+
if (!streamTs)
|
|
235
454
|
throw new Error("Slack chat.startStream did not return ts");
|
|
236
|
-
return
|
|
455
|
+
return streamTs;
|
|
237
456
|
});
|
|
457
|
+
for (const part of tail)
|
|
458
|
+
await this.appendStreamChunk(channel, ts, part);
|
|
459
|
+
return ts;
|
|
238
460
|
}
|
|
239
461
|
async appendMessageStream(channel, ts, text) {
|
|
462
|
+
for (const part of chunkStreamText(text)) {
|
|
463
|
+
await this.appendStreamChunk(channel, ts, part);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async appendStreamChunk(channel, ts, text) {
|
|
240
467
|
return slackRetry(async () => {
|
|
241
468
|
await this.webClient.apiCall("chat.appendStream", {
|
|
242
469
|
channel,
|
|
243
470
|
ts,
|
|
244
|
-
markdown_text: text,
|
|
471
|
+
markdown_text: this.resolveMentions(text),
|
|
245
472
|
});
|
|
246
473
|
});
|
|
247
474
|
}
|
|
@@ -255,38 +482,83 @@ export class SlackMessagingBot {
|
|
|
255
482
|
await this.webClient.chat.delete({ channel, ts });
|
|
256
483
|
});
|
|
257
484
|
}
|
|
258
|
-
// ==========================================================================
|
|
259
|
-
// Slack Assistant API (AI assistant experience)
|
|
260
|
-
// ==========================================================================
|
|
261
|
-
/** Set the status for an assistant thread (shows "thinking" state) */
|
|
262
485
|
async setAssistantStatus(channel, threadTs, status) {
|
|
263
|
-
|
|
264
|
-
|
|
486
|
+
const key = `${channel}:${threadTs}`;
|
|
487
|
+
const previous = this.statusUpdates.get(key) ?? Promise.resolve();
|
|
488
|
+
const update = previous
|
|
489
|
+
.catch(() => undefined)
|
|
490
|
+
.then(async () => {
|
|
491
|
+
await this.statusClient.assistant.threads.setStatus({
|
|
265
492
|
channel_id: channel,
|
|
266
493
|
thread_ts: threadTs,
|
|
267
494
|
status,
|
|
268
495
|
});
|
|
269
496
|
});
|
|
497
|
+
this.statusUpdates.set(key, update);
|
|
498
|
+
try {
|
|
499
|
+
await update;
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
if (this.statusUpdates.get(key) === update)
|
|
503
|
+
this.statusUpdates.delete(key);
|
|
504
|
+
}
|
|
270
505
|
}
|
|
271
|
-
async
|
|
506
|
+
async setAssistantSuggestedPrompts(channel, threadTs, prompts) {
|
|
272
507
|
return slackRetry(async () => {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
508
|
+
await this.webClient.assistant.threads.setSuggestedPrompts({
|
|
509
|
+
channel_id: channel,
|
|
510
|
+
thread_ts: threadTs || undefined,
|
|
511
|
+
prompts,
|
|
512
|
+
});
|
|
276
513
|
});
|
|
277
514
|
}
|
|
278
|
-
async
|
|
515
|
+
async setAssistantTitle(channel, threadTs, title) {
|
|
279
516
|
return slackRetry(async () => {
|
|
280
|
-
|
|
281
|
-
channel,
|
|
517
|
+
await this.webClient.assistant.threads.setTitle({
|
|
518
|
+
channel_id: channel,
|
|
282
519
|
thread_ts: threadTs,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
};
|
|
286
|
-
const result = await this.webClient.chat.postMessage(payload);
|
|
287
|
-
return result.ts;
|
|
520
|
+
title,
|
|
521
|
+
});
|
|
288
522
|
});
|
|
289
523
|
}
|
|
524
|
+
assistantOps() {
|
|
525
|
+
return {
|
|
526
|
+
postInThread: (channel, threadTs, text) => this.postInThread(channel, threadTs, text),
|
|
527
|
+
setSuggestedPrompts: (channel, threadTs, prompts) => this.setAssistantSuggestedPrompts(channel, threadTs, prompts),
|
|
528
|
+
setTitle: (channel, threadTs, title) => this.setAssistantTitle(channel, threadTs, title),
|
|
529
|
+
channelName: (channelId) => this.channels.get(channelId)?.name,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
handleAssistantThreadStarted({ event, ack }) {
|
|
533
|
+
ack();
|
|
534
|
+
const payload = event;
|
|
535
|
+
if (!payload.assistant_thread)
|
|
536
|
+
return;
|
|
537
|
+
void handleAssistantThreadStarted(this.assistantOps(), this.assistantThreads, payload.assistant_thread);
|
|
538
|
+
}
|
|
539
|
+
handleAgentContextChangedEvent({ event, ack }) {
|
|
540
|
+
ack();
|
|
541
|
+
const payload = event;
|
|
542
|
+
if (payload.assistant_thread) {
|
|
543
|
+
handleAgentContextChanged(this.assistantThreads, payload.assistant_thread);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const channelId = payload.channel_id ?? payload.channel;
|
|
547
|
+
if (channelId) {
|
|
548
|
+
handleAgentContextChanged(this.assistantThreads, {
|
|
549
|
+
channel_id: channelId,
|
|
550
|
+
context: payload.context,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async postInThread(channel, threadTs, text, identity) {
|
|
555
|
+
return identity
|
|
556
|
+
? this.postMessage(channel, text, threadTs, identity)
|
|
557
|
+
: this.postMessage(channel, text, threadTs);
|
|
558
|
+
}
|
|
559
|
+
async postInThreadBlocks(channel, threadTs, text, blocks) {
|
|
560
|
+
return this.postMessageBlocks(channel, text, blocks, threadTs);
|
|
561
|
+
}
|
|
290
562
|
async uploadFile(channel, filePath, title, threadTs) {
|
|
291
563
|
return slackRetry(async () => {
|
|
292
564
|
const fileName = title || basename(filePath);
|
|
@@ -301,18 +573,40 @@ export class SlackMessagingBot {
|
|
|
301
573
|
});
|
|
302
574
|
}
|
|
303
575
|
logToFile(channel, entry) {
|
|
304
|
-
appendChannelLog(this.
|
|
576
|
+
appendChannelLog(this.workspace.office(createOfficeAddress("slack", channel)), entry);
|
|
305
577
|
}
|
|
306
578
|
logBotResponse(channel, text, ts, threadTs, slackBlocks) {
|
|
307
|
-
appendBotResponseLog(this.
|
|
579
|
+
appendBotResponseLog(this.workspace.office(createOfficeAddress("slack", channel)), text, ts, threadTs, {
|
|
308
580
|
platform: "slack",
|
|
309
|
-
|
|
581
|
+
slackBlocks,
|
|
310
582
|
});
|
|
311
583
|
}
|
|
584
|
+
ownsBlockKitMessage(channel, ts, threadTs) {
|
|
585
|
+
const content = readTextFileIfExists(this.office(channel).logPath);
|
|
586
|
+
if (content === undefined)
|
|
587
|
+
return false;
|
|
588
|
+
for (const line of content.trim().split("\n").toReversed()) {
|
|
589
|
+
try {
|
|
590
|
+
const entry = JSON.parse(line);
|
|
591
|
+
if (!isRecord(entry) || entry.ts !== ts)
|
|
592
|
+
continue;
|
|
593
|
+
return (entry.isMessagingBot === true &&
|
|
594
|
+
entry.platform === "slack" &&
|
|
595
|
+
Array.isArray(entry.slackBlocks) &&
|
|
596
|
+
entry.threadTs === threadTs);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
312
604
|
getMessagingInfo() {
|
|
313
605
|
return {
|
|
314
606
|
name: "slack",
|
|
315
|
-
|
|
607
|
+
workspaceId: this.teamId || undefined,
|
|
608
|
+
trustModel: "membership",
|
|
609
|
+
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
610
|
channels: this.getAllChannels().map((c) => ({ id: c.id, name: c.name })),
|
|
317
611
|
users: this.getAllUsers().map((u) => ({
|
|
318
612
|
id: u.id,
|
|
@@ -324,29 +618,24 @@ export class SlackMessagingBot {
|
|
|
324
618
|
},
|
|
325
619
|
};
|
|
326
620
|
}
|
|
327
|
-
// ==========================================================================
|
|
328
|
-
// Events Integration
|
|
329
|
-
// ==========================================================================
|
|
330
|
-
/**
|
|
331
|
-
* Enqueue an event for processing. Always queues (no "already working" rejection).
|
|
332
|
-
* Returns true if enqueued, false if queue is full (max 5).
|
|
333
|
-
*/
|
|
334
621
|
enqueueEvent(event) {
|
|
335
|
-
|
|
622
|
+
if (this.stopped)
|
|
623
|
+
return false;
|
|
624
|
+
const conversationId = event.address.conversationId;
|
|
336
625
|
const queue = this.getQueue(conversationId);
|
|
337
626
|
if (queue.size() >= 5) {
|
|
338
627
|
log.logWarning(`Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`);
|
|
339
628
|
return false;
|
|
340
629
|
}
|
|
341
630
|
log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);
|
|
342
|
-
queue.enqueue(async () => {
|
|
631
|
+
return queue.enqueue(async () => {
|
|
343
632
|
let anchorTs;
|
|
344
633
|
if (!event.thread_ts) {
|
|
345
634
|
try {
|
|
346
635
|
anchorTs = await this.postMessage(conversationId, SLACK_EVENT_ANCHOR_TEXT);
|
|
347
636
|
}
|
|
348
637
|
catch (err) {
|
|
349
|
-
log.logWarning(`Failed to post Slack event anchor for ${conversationId}`,
|
|
638
|
+
log.logWarning(`Failed to post Slack event anchor for ${conversationId}`, errorMessage(err));
|
|
350
639
|
reportUserFacingError(err, {
|
|
351
640
|
domain: "events",
|
|
352
641
|
surface: "event_delivery",
|
|
@@ -367,15 +656,17 @@ export class SlackMessagingBot {
|
|
|
367
656
|
const eventForRun = eventPlan.event;
|
|
368
657
|
if (eventPlan.initialMessageTs && eventForRun.sessionKey) {
|
|
369
658
|
registerThreadSession({
|
|
370
|
-
conversationDir:
|
|
659
|
+
conversationDir: this.conversationDir(conversationId),
|
|
371
660
|
sessionKey: eventForRun.sessionKey,
|
|
372
661
|
});
|
|
373
662
|
}
|
|
374
663
|
const runQueueKey = planSlackAdapterSession(eventForRun, {
|
|
375
664
|
initialMessageTs: eventPlan.initialMessageTs,
|
|
376
665
|
}).sessionKey;
|
|
377
|
-
|
|
378
|
-
const slackEvent = {
|
|
666
|
+
const run = async () => {
|
|
667
|
+
const slackEvent = createConversationEvent({
|
|
668
|
+
platform: "slack",
|
|
669
|
+
address: eventForRun.address,
|
|
379
670
|
type: eventForRun.type,
|
|
380
671
|
conversationId,
|
|
381
672
|
conversationKind: eventForRun.conversationKind,
|
|
@@ -384,29 +675,28 @@ export class SlackMessagingBot {
|
|
|
384
675
|
thread_ts: eventForRun.thread_ts,
|
|
385
676
|
user: eventForRun.user,
|
|
386
677
|
text: eventForRun.text,
|
|
387
|
-
attachments: eventForRun.attachments
|
|
388
|
-
original: attachment.name,
|
|
389
|
-
localPath: attachment.localPath,
|
|
390
|
-
})),
|
|
678
|
+
attachments: eventForRun.attachments,
|
|
391
679
|
sessionKey: eventForRun.sessionKey,
|
|
392
|
-
};
|
|
680
|
+
});
|
|
393
681
|
const context = createSlackAdapters(slackEvent, this, {
|
|
394
682
|
initialMessageTs: eventPlan.initialMessageTs,
|
|
395
|
-
replyMode:
|
|
396
|
-
?.replyMode ?? "top-level",
|
|
683
|
+
replyMode: this.resolveReplyMode(eventForRun.address),
|
|
397
684
|
});
|
|
398
685
|
return this.handler.handleEvent(eventForRun, this, context);
|
|
399
|
-
}
|
|
686
|
+
};
|
|
687
|
+
const runQueue = this.getQueue(runQueueKey);
|
|
688
|
+
if (!runQueue.enqueue(run)) {
|
|
689
|
+
await runQueue.close();
|
|
690
|
+
await run();
|
|
691
|
+
}
|
|
400
692
|
});
|
|
401
|
-
return true;
|
|
402
693
|
}
|
|
403
|
-
// ==========================================================================
|
|
404
|
-
// Private - Event Handlers
|
|
405
|
-
// ==========================================================================
|
|
406
694
|
getQueue(channelId) {
|
|
407
695
|
let queue = this.queues.get(channelId);
|
|
408
696
|
if (!queue) {
|
|
409
697
|
queue = new MessagingEventQueue("Slack");
|
|
698
|
+
if (this.stopped)
|
|
699
|
+
void queue.close();
|
|
410
700
|
this.queues.set(channelId, queue);
|
|
411
701
|
}
|
|
412
702
|
return queue;
|
|
@@ -414,21 +704,91 @@ export class SlackMessagingBot {
|
|
|
414
704
|
resolveQueueKey(conversationId, sessionKey) {
|
|
415
705
|
if (!isSlackThreadSessionKey(sessionKey))
|
|
416
706
|
return sessionKey;
|
|
417
|
-
if (this.handler.isRunning(sessionKey))
|
|
707
|
+
if (this.handler.isRunning(createOfficeAddress("slack", conversationId), sessionKey)) {
|
|
418
708
|
return sessionKey;
|
|
709
|
+
}
|
|
419
710
|
return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
|
|
420
711
|
}
|
|
712
|
+
isTaskThread(channel, root) {
|
|
713
|
+
return readTaskRoots(this.conversationDir(channel)).has(root);
|
|
714
|
+
}
|
|
715
|
+
hasRunningTaskThread(channel) {
|
|
716
|
+
const roots = readTaskRoots(this.conversationDir(channel));
|
|
717
|
+
if (!roots.size)
|
|
718
|
+
return false;
|
|
719
|
+
return this.handler
|
|
720
|
+
.getRunningSessions()
|
|
721
|
+
.some((s) => s.address.platform === "slack" &&
|
|
722
|
+
s.address.conversationId === channel &&
|
|
723
|
+
[...roots.keys()].some((root) => resolveSlackSessionKey(channel, root) === s.sessionKey));
|
|
724
|
+
}
|
|
421
725
|
hasKnownThreadSession(conversationId, sessionKey) {
|
|
422
726
|
return hasMaterializedChatSession({
|
|
423
|
-
conversationDir:
|
|
727
|
+
conversationDir: this.conversationDir(conversationId),
|
|
424
728
|
sessionKey,
|
|
425
729
|
});
|
|
426
730
|
}
|
|
731
|
+
channelKindFor(channelId) {
|
|
732
|
+
if (channelId.startsWith("D"))
|
|
733
|
+
return "im";
|
|
734
|
+
const channel = this.channels.get(channelId);
|
|
735
|
+
if (!channel)
|
|
736
|
+
return undefined;
|
|
737
|
+
if (channel.name.startsWith("DM:"))
|
|
738
|
+
return "im";
|
|
739
|
+
if (channel.isExternallyShared)
|
|
740
|
+
return "external";
|
|
741
|
+
if (channel.isPrivate === true)
|
|
742
|
+
return "private_channel";
|
|
743
|
+
if (channel.isPrivate === false)
|
|
744
|
+
return "public_channel";
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
747
|
+
backfillChannelKinds() {
|
|
748
|
+
let recorded = 0;
|
|
749
|
+
for (const record of listRegisteredOffices(this.workspace.stateDir)) {
|
|
750
|
+
if (record.platform !== "slack")
|
|
751
|
+
continue;
|
|
752
|
+
const kind = this.channelKindFor(record.conversationId);
|
|
753
|
+
if (!kind)
|
|
754
|
+
continue;
|
|
755
|
+
try {
|
|
756
|
+
const office = this.workspace.office(record);
|
|
757
|
+
if (readPlatformChannelKind(office) === kind)
|
|
758
|
+
continue;
|
|
759
|
+
recordPlatformChannelKind(office, kind);
|
|
760
|
+
recorded++;
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
log.logWarning("Failed to backfill Slack channel kind", String(err));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (recorded > 0)
|
|
767
|
+
log.logInfo(`Recorded channel kind for ${recorded} Slack offices`);
|
|
768
|
+
}
|
|
427
769
|
processSlackMessageIntake(options) {
|
|
428
|
-
|
|
770
|
+
const kind = this.channelKindFor(options.event.address.conversationId);
|
|
771
|
+
if (kind) {
|
|
772
|
+
try {
|
|
773
|
+
recordPlatformChannelKind(this.workspace.office(options.event.address), kind);
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
log.logWarning("Failed to record Slack channel kind", String(err));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const absorbAttachmentFailure = () => {
|
|
780
|
+
void options.attachmentsPromise.catch((err) => {
|
|
781
|
+
log.logWarning("Failed to log Slack message", String(err));
|
|
782
|
+
});
|
|
783
|
+
};
|
|
784
|
+
return processMessageIntake({
|
|
429
785
|
eventBase: options.event,
|
|
430
|
-
|
|
431
|
-
|
|
786
|
+
addressed: options.addressed,
|
|
787
|
+
magicWord: {
|
|
788
|
+
addressed: options.magicWordAddressed ?? options.addressed,
|
|
789
|
+
scopeFallback: "top-level",
|
|
790
|
+
},
|
|
791
|
+
busyPolicy: "queue",
|
|
432
792
|
logEntryBase: {},
|
|
433
793
|
processAttachments: () => options.attachmentsPromise,
|
|
434
794
|
queueKey: options.queueKey,
|
|
@@ -436,26 +796,16 @@ export class SlackMessagingBot {
|
|
|
436
796
|
handler: this.handler,
|
|
437
797
|
bot: this,
|
|
438
798
|
createContext: (event) => this.createContext(event),
|
|
439
|
-
onNotTriggered: options.onNotTriggered,
|
|
440
799
|
deferAttachmentsUntilRun: true,
|
|
800
|
+
}).then((outcome) => {
|
|
801
|
+
if (outcome !== "enqueued")
|
|
802
|
+
absorbAttachmentFailure();
|
|
803
|
+
}, (err) => {
|
|
804
|
+
log.logWarning("Slack message intake failed", String(err));
|
|
805
|
+
absorbAttachmentFailure();
|
|
441
806
|
});
|
|
442
807
|
}
|
|
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 ---
|
|
808
|
+
appendRunningTasks(blocks) {
|
|
459
809
|
const runningSessions = this.handler.getRunningSessions();
|
|
460
810
|
blocks.push({ type: "divider" }, {
|
|
461
811
|
type: "header",
|
|
@@ -470,61 +820,51 @@ export class SlackMessagingBot {
|
|
|
470
820
|
type: "context",
|
|
471
821
|
elements: [{ type: "mrkdwn", text: "_No tasks running right now._" }],
|
|
472
822
|
});
|
|
823
|
+
return;
|
|
473
824
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
825
|
+
const stuckThresholdMs = 10 * 60 * 1000;
|
|
826
|
+
for (const session of runningSessions) {
|
|
827
|
+
const channelId = conversationIdOf(session.sessionKey);
|
|
828
|
+
const channelName = this.channels.get(channelId)?.name;
|
|
829
|
+
const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
|
|
830
|
+
const elapsedStr = elapsed < 1 ? "<1 min" : `${elapsed} min`;
|
|
831
|
+
const lastActivity = session.lastActivityAt ? Date.now() - session.lastActivityAt : 0;
|
|
832
|
+
const isStuck = lastActivity > stuckThresholdMs;
|
|
833
|
+
let statusLine = `${isStuck ? "_stuck_" : "_running_"} · ${elapsedStr}`;
|
|
834
|
+
if (session.currentTool)
|
|
835
|
+
statusLine += ` · ${session.currentTool}`;
|
|
836
|
+
if (isStuck)
|
|
837
|
+
statusLine += ` · idle ${Math.floor(lastActivity / 60000)}m`;
|
|
838
|
+
blocks.push({
|
|
839
|
+
type: "context",
|
|
840
|
+
elements: [
|
|
841
|
+
{
|
|
842
|
+
type: "mrkdwn",
|
|
843
|
+
text: `*${channelName ? `#${channelName}` : channelId}* · ${statusLine}`,
|
|
844
|
+
},
|
|
845
|
+
],
|
|
846
|
+
});
|
|
847
|
+
if (isStuck) {
|
|
497
848
|
blocks.push({
|
|
498
849
|
type: "context",
|
|
499
850
|
elements: [
|
|
851
|
+
{ type: "mrkdwn", text: " " },
|
|
500
852
|
{
|
|
501
|
-
type: "
|
|
502
|
-
text:
|
|
853
|
+
type: "button",
|
|
854
|
+
text: { type: "plain_text", text: "Force Stop", emoji: true },
|
|
855
|
+
action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
|
|
856
|
+
value: session.sessionKey,
|
|
857
|
+
style: "danger",
|
|
503
858
|
},
|
|
504
859
|
],
|
|
505
860
|
});
|
|
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
861
|
}
|
|
525
862
|
}
|
|
526
|
-
|
|
527
|
-
|
|
863
|
+
}
|
|
864
|
+
appendScheduledJobs(blocks, dmChannelId) {
|
|
865
|
+
const periodicEvents = dmChannelId && this.eventScheduler
|
|
866
|
+
? this.eventScheduler.periodicEvents(createOfficeAddress("slack", dmChannelId))
|
|
867
|
+
: [];
|
|
528
868
|
blocks.push({ type: "divider" }, {
|
|
529
869
|
type: "header",
|
|
530
870
|
text: {
|
|
@@ -538,34 +878,46 @@ export class SlackMessagingBot {
|
|
|
538
878
|
type: "context",
|
|
539
879
|
elements: [{ type: "mrkdwn", text: "_No scheduled jobs._" }],
|
|
540
880
|
});
|
|
881
|
+
return;
|
|
541
882
|
}
|
|
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
|
-
}
|
|
883
|
+
for (const event of periodicEvents) {
|
|
884
|
+
const channel = event.platform === "slack" ? this.channels.get(event.conversationId) : undefined;
|
|
885
|
+
const channelId = channel ? `#${channel.name}` : event.conversationId;
|
|
886
|
+
const channelLabel = `${event.platform}:${channelId}`;
|
|
887
|
+
const nextRun = event.nextRun
|
|
888
|
+
? new Date(event.nextRun).toLocaleString("en-US", {
|
|
889
|
+
month: "short",
|
|
890
|
+
day: "numeric",
|
|
891
|
+
hour: "2-digit",
|
|
892
|
+
minute: "2-digit",
|
|
893
|
+
})
|
|
894
|
+
: "—";
|
|
895
|
+
blocks.push({
|
|
896
|
+
type: "section",
|
|
897
|
+
text: {
|
|
898
|
+
type: "mrkdwn",
|
|
899
|
+
text: `*${event.text}*\n└ \`${event.schedule}\` · ${channelLabel} · Next: ${nextRun}`,
|
|
900
|
+
},
|
|
901
|
+
});
|
|
567
902
|
}
|
|
568
|
-
|
|
903
|
+
}
|
|
904
|
+
buildHomeView(dmChannelId) {
|
|
905
|
+
const blocks = [
|
|
906
|
+
{
|
|
907
|
+
type: "section",
|
|
908
|
+
text: {
|
|
909
|
+
type: "mrkdwn",
|
|
910
|
+
text: `*${PRODUCT_NAME}*\nStart a new task or check on running work.`,
|
|
911
|
+
},
|
|
912
|
+
accessory: {
|
|
913
|
+
type: "image",
|
|
914
|
+
image_url: "https://media1.tenor.com/m/lfDATg4Bhc0AAAAC/happy-cat.gif",
|
|
915
|
+
alt_text: PRODUCT_NAME,
|
|
916
|
+
},
|
|
917
|
+
},
|
|
918
|
+
];
|
|
919
|
+
this.appendRunningTasks(blocks);
|
|
920
|
+
this.appendScheduledJobs(blocks, dmChannelId);
|
|
569
921
|
blocks.push({ type: "divider" }, {
|
|
570
922
|
type: "context",
|
|
571
923
|
elements: [
|
|
@@ -574,55 +926,35 @@ export class SlackMessagingBot {
|
|
|
574
926
|
});
|
|
575
927
|
return { type: "home", blocks: blocks };
|
|
576
928
|
}
|
|
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 = {
|
|
929
|
+
createCommandAdapters(input) {
|
|
930
|
+
const { conversationId, userId, userName, text, ts, ...options } = input;
|
|
931
|
+
const message = createConversationMessage({
|
|
932
|
+
platform: "slack",
|
|
933
|
+
conversationId,
|
|
595
934
|
id: ts,
|
|
596
|
-
sessionKey: conversationId,
|
|
935
|
+
sessionKey: options.sessionKey ?? conversationId,
|
|
597
936
|
conversationKind: options.ephemeralChannelId ? "shared" : "direct",
|
|
598
937
|
userId,
|
|
599
938
|
userName,
|
|
600
939
|
text,
|
|
601
940
|
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)];
|
|
941
|
+
});
|
|
942
|
+
const respond = async (responseText, blocks) => {
|
|
613
943
|
if (options.ephemeralChannelId) {
|
|
614
|
-
await this.
|
|
944
|
+
await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs, blocks);
|
|
615
945
|
return;
|
|
616
946
|
}
|
|
617
|
-
const messageTs =
|
|
947
|
+
const messageTs = blocks
|
|
948
|
+
? await this.postMessageBlocks(conversationId, responseText, blocks)
|
|
949
|
+
: await this.postMessage(conversationId, responseText);
|
|
618
950
|
this.logBotResponse(conversationId, responseText, messageTs);
|
|
619
951
|
};
|
|
620
952
|
const responder = {
|
|
621
953
|
respond,
|
|
622
|
-
replaceResponse: respond,
|
|
954
|
+
replaceResponse: (responseText) => respond(responseText),
|
|
623
955
|
respondDiagnostic: async (responseText, responseOptions) => {
|
|
624
956
|
if (responseOptions?.style === "muted") {
|
|
625
|
-
await
|
|
957
|
+
await respond(responseText, [buildMrkdwnContextBlock(responseText)]);
|
|
626
958
|
return;
|
|
627
959
|
}
|
|
628
960
|
await respond(responseOptions?.style === "error" ? `_${responseText}_` : responseText);
|
|
@@ -639,6 +971,7 @@ export class SlackMessagingBot {
|
|
|
639
971
|
deleteResponse: async () => { },
|
|
640
972
|
};
|
|
641
973
|
return {
|
|
974
|
+
address: message.address,
|
|
642
975
|
message,
|
|
643
976
|
responder,
|
|
644
977
|
platform: this.getMessagingInfo(),
|
|
@@ -664,66 +997,42 @@ export class SlackMessagingBot {
|
|
|
664
997
|
text: commandText,
|
|
665
998
|
attachments: [],
|
|
666
999
|
isMessagingBot: false,
|
|
667
|
-
|
|
1000
|
+
threadTs: threadTs || undefined,
|
|
668
1001
|
});
|
|
669
|
-
const event = {
|
|
670
|
-
|
|
1002
|
+
const event = createConversationEvent({
|
|
1003
|
+
platform: "slack",
|
|
671
1004
|
conversationId,
|
|
1005
|
+
type: options.type ?? (isDirectMessage ? "dm" : "mention"),
|
|
672
1006
|
conversationKind: isDirectMessage ? "direct" : "shared",
|
|
673
1007
|
ts: eventTs,
|
|
674
1008
|
user: payload.user_id,
|
|
675
1009
|
text: commandText,
|
|
676
1010
|
attachments: [],
|
|
677
|
-
|
|
1011
|
+
thread_ts: threadTs || undefined,
|
|
678
1012
|
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
1013
|
});
|
|
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,
|
|
1014
|
+
const context = this.createCommandAdapters({
|
|
1015
|
+
conversationId,
|
|
1016
|
+
userId: payload.user_id,
|
|
703
1017
|
userName,
|
|
704
|
-
text:
|
|
705
|
-
|
|
706
|
-
|
|
1018
|
+
text: commandText,
|
|
1019
|
+
ts: eventTs,
|
|
1020
|
+
ephemeralChannelId: isDirectMessage ? undefined : conversationId,
|
|
1021
|
+
threadTs,
|
|
1022
|
+
sessionKey,
|
|
707
1023
|
});
|
|
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);
|
|
1024
|
+
return { event, context };
|
|
724
1025
|
}
|
|
725
|
-
async
|
|
726
|
-
const { event, context } = this.buildSlashCommandEvent(payload, {
|
|
1026
|
+
async routeSlashCommand(route, payload) {
|
|
1027
|
+
const { event, context } = this.buildSlashCommandEvent(payload, {
|
|
1028
|
+
includeText: route.includeText,
|
|
1029
|
+
thread: route.thread,
|
|
1030
|
+
type: route.privateCommand
|
|
1031
|
+
? payload.channel_id.startsWith("D")
|
|
1032
|
+
? "dm"
|
|
1033
|
+
: "private_command"
|
|
1034
|
+
: undefined,
|
|
1035
|
+
});
|
|
727
1036
|
await this.handler.handleEvent(event, this, context);
|
|
728
1037
|
}
|
|
729
1038
|
setupEventHandlers() {
|
|
@@ -736,38 +1045,39 @@ export class SlackMessagingBot {
|
|
|
736
1045
|
this.socketClient.on("unable_to_socket_mode_start", (err) => {
|
|
737
1046
|
log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
|
|
738
1047
|
});
|
|
739
|
-
this.socketClient.on("app_mention", (payload) => this.handleAppMention(payload));
|
|
740
|
-
this.socketClient.on("message", (payload) => this.handleMessageEvent(payload));
|
|
741
|
-
this.socketClient.on("slash_commands", (payload) =>
|
|
742
|
-
this.socketClient.on("app_home_opened", (payload) => this.handleAppHomeOpened(payload));
|
|
743
|
-
this.socketClient.on("
|
|
744
|
-
this.socketClient.on("
|
|
745
|
-
|
|
746
|
-
|
|
1048
|
+
this.socketClient.on("app_mention", (payload) => this.intake.run(() => this.handleAppMention(payload)));
|
|
1049
|
+
this.socketClient.on("message", (payload) => this.intake.run(() => this.handleMessageEvent(payload)));
|
|
1050
|
+
this.socketClient.on("slash_commands", (payload) => this.intake.run(() => this.handleSlashCommand(payload)));
|
|
1051
|
+
this.socketClient.on("app_home_opened", (payload) => this.intake.run(() => this.handleAppHomeOpened(payload)));
|
|
1052
|
+
this.socketClient.on("assistant_thread_started", (payload) => this.intake.run(() => this.handleAssistantThreadStarted(payload)));
|
|
1053
|
+
this.socketClient.on("assistant_thread_context_changed", (payload) => this.intake.run(() => this.handleAgentContextChangedEvent(payload)));
|
|
1054
|
+
this.socketClient.on("app_context_changed", (payload) => this.intake.run(() => this.handleAgentContextChangedEvent(payload)));
|
|
1055
|
+
const blockAction = ({ body, ack }) => this.intake.run(() => this.handleBlockAction({ body: body, ack }));
|
|
1056
|
+
this.socketClient.on("block_actions", blockAction);
|
|
1057
|
+
this.socketClient.on("interactive", blockAction);
|
|
1058
|
+
}
|
|
1059
|
+
async handleAppMention({ event, ack }) {
|
|
747
1060
|
const e = event;
|
|
748
|
-
// Skip DMs (handled by message event)
|
|
749
1061
|
if (e.channel.startsWith("D")) {
|
|
750
1062
|
ack();
|
|
751
1063
|
return;
|
|
752
1064
|
}
|
|
753
|
-
// Top-level mentions use a persistent channel session.
|
|
754
|
-
// Thread replies get their own isolated session (channelId:thread_ts).
|
|
755
1065
|
const sessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
756
1066
|
const mentionText = this.stripOwnMention(e.text);
|
|
757
|
-
const slackEvent = {
|
|
758
|
-
|
|
1067
|
+
const slackEvent = createConversationEvent({
|
|
1068
|
+
platform: "slack",
|
|
759
1069
|
conversationId: e.channel,
|
|
1070
|
+
type: "mention",
|
|
760
1071
|
conversationKind: "shared",
|
|
761
|
-
channel: e.channel,
|
|
762
1072
|
ts: e.ts,
|
|
763
1073
|
thread_ts: e.thread_ts,
|
|
764
1074
|
user: e.user,
|
|
765
1075
|
text: mentionText || "Please respond to the recent conversation context.",
|
|
766
|
-
files: e.files,
|
|
767
1076
|
sessionKey,
|
|
768
|
-
|
|
1077
|
+
channel: e.channel,
|
|
1078
|
+
files: e.files,
|
|
1079
|
+
});
|
|
769
1080
|
const attachmentsPromise = this.logUserMessage(slackEvent);
|
|
770
|
-
// Only trigger processing for messages AFTER startup (not replayed old messages)
|
|
771
1081
|
if (this.startupTs && e.ts < this.startupTs) {
|
|
772
1082
|
log.logInfo(`[${e.channel}] Logged old message (pre-startup), not triggering: ${slackEvent.text.substring(0, 30)}`);
|
|
773
1083
|
void attachmentsPromise.catch((err) => {
|
|
@@ -776,90 +1086,156 @@ export class SlackMessagingBot {
|
|
|
776
1086
|
ack();
|
|
777
1087
|
return;
|
|
778
1088
|
}
|
|
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({
|
|
1089
|
+
const intake = this.processSlackMessageIntake({
|
|
795
1090
|
event: slackEvent,
|
|
796
1091
|
attachmentsPromise,
|
|
797
1092
|
queueKey: this.resolveQueueKey(e.channel, sessionKey),
|
|
798
|
-
|
|
1093
|
+
addressed: true,
|
|
799
1094
|
});
|
|
800
1095
|
ack();
|
|
1096
|
+
await intake;
|
|
801
1097
|
}
|
|
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) {
|
|
1098
|
+
admitHumanMessage(event, ack) {
|
|
1099
|
+
const hasSlackContent = hasMessageContent(event);
|
|
1100
|
+
const isOwnMessage = event.user === this.botUserId || (!!this.botId && event.bot_id === this.botId);
|
|
1101
|
+
if (isOwnMessage) {
|
|
808
1102
|
ack();
|
|
809
|
-
return;
|
|
1103
|
+
return false;
|
|
810
1104
|
}
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
return;
|
|
1105
|
+
const authorIsKnownHuman = !!event.user && this.users.get(event.user)?.isBot === false;
|
|
1106
|
+
const isExternalMessage = event.subtype === "bot_message" || (!!event.bot_id && !authorIsKnownHuman);
|
|
1107
|
+
if (isExternalMessage) {
|
|
1108
|
+
const supportedSubtype = BOT_MESSAGE_SUBTYPES.has(event.subtype);
|
|
1109
|
+
if (supportedSubtype && hasSlackContent) {
|
|
1110
|
+
void this.logExternalMessagingBotMessage(event).catch((err) => {
|
|
1111
|
+
log.logWarning("Failed to log Slack bot message", String(err));
|
|
1112
|
+
});
|
|
820
1113
|
}
|
|
821
|
-
void this.logExternalMessagingBotMessage(e).catch((err) => {
|
|
822
|
-
log.logWarning("Failed to log Slack bot message", String(err));
|
|
823
|
-
});
|
|
824
1114
|
ack();
|
|
825
|
-
return;
|
|
1115
|
+
return false;
|
|
826
1116
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return;
|
|
1117
|
+
const isSupportedUserMessage = !!event.user && USER_MESSAGE_SUBTYPES.has(event.subtype) && hasSlackContent;
|
|
1118
|
+
if (isSupportedUserMessage)
|
|
1119
|
+
return true;
|
|
1120
|
+
ack();
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
async deliverTaskUpdate(event, attachmentsPromise) {
|
|
1124
|
+
if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
|
|
1125
|
+
return false;
|
|
1126
|
+
event.attachments = await attachmentsPromise;
|
|
1127
|
+
const context = this.createContext(event);
|
|
1128
|
+
const post = (text) => event.thread_ts
|
|
1129
|
+
? this.postInThread(event.channel, event.thread_ts, text)
|
|
1130
|
+
: this.postMessage(event.channel, text);
|
|
1131
|
+
try {
|
|
1132
|
+
const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
|
|
1133
|
+
if (!tasks.length)
|
|
1134
|
+
return false;
|
|
1135
|
+
const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
|
|
1136
|
+
const user = this.users.get(event.user);
|
|
1137
|
+
const intent = event.attachments?.length
|
|
1138
|
+
? event.thread_ts
|
|
1139
|
+
? "steer"
|
|
1140
|
+
: "request"
|
|
1141
|
+
: await classifyTaskIntent(buildTaskIntentState(this.conversationDir(event.channel), event, {
|
|
1142
|
+
speaker: user?.displayName ?? user?.userName,
|
|
1143
|
+
tasks,
|
|
1144
|
+
resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
|
|
1145
|
+
botUserId: this.botUserId,
|
|
1146
|
+
}), event.text, { conversationId: event.channel, inTaskThread: !!event.thread_ts });
|
|
1147
|
+
if (intent === "status") {
|
|
1148
|
+
const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
|
|
1149
|
+
const text = selection.length > 1
|
|
1150
|
+
? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
|
|
1151
|
+
: formatTaskStatus(selection);
|
|
1152
|
+
const ts = await post(text);
|
|
1153
|
+
this.logBotResponse(event.channel, text, ts, event.thread_ts);
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1156
|
+
if (intent === "steer") {
|
|
1157
|
+
const target = event.thread_ts
|
|
1158
|
+
? context.message
|
|
1159
|
+
: active.length === 1
|
|
1160
|
+
? {
|
|
1161
|
+
...context.message,
|
|
1162
|
+
sessionKey: active[0].sessionKey,
|
|
1163
|
+
threadTs: active[0].threadTs,
|
|
1164
|
+
}
|
|
1165
|
+
: undefined;
|
|
1166
|
+
if (target && (await this.handler.steer?.(target))) {
|
|
1167
|
+
const text = "收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。";
|
|
1168
|
+
const ts = await post(text);
|
|
1169
|
+
this.logBotResponse(event.channel, text, ts, event.thread_ts);
|
|
1170
|
+
return true;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
830
1173
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return;
|
|
1174
|
+
catch (error) {
|
|
1175
|
+
await post(error instanceof Error ? error.message : "Could not deliver task update.");
|
|
1176
|
+
return true;
|
|
834
1177
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
async evaluateJevAddressed(event) {
|
|
1181
|
+
if (!event.text.trim() || matchMagicWord(event.text) === "stop")
|
|
1182
|
+
return false;
|
|
1183
|
+
try {
|
|
1184
|
+
const user = this.users.get(event.user);
|
|
1185
|
+
const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
|
|
1186
|
+
speaker: user?.displayName ?? user?.userName,
|
|
1187
|
+
resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
|
|
1188
|
+
botUserId: this.botUserId,
|
|
1189
|
+
});
|
|
1190
|
+
const result = await evaluateWithJev(state, { addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS } }, { caller: "slack_auto_reply" });
|
|
1191
|
+
const probability = result.answers.addressed.probability;
|
|
1192
|
+
log.logInfo(`[${event.channel}] jev auto-reply: probability=${probability.toFixed(2)} addressed=${probability > 0.5} text="${event.text.slice(0, 80)}"`);
|
|
1193
|
+
return probability > 0.5;
|
|
1194
|
+
}
|
|
1195
|
+
catch (err) {
|
|
1196
|
+
if (err instanceof JevNotConfiguredError) {
|
|
1197
|
+
log.logWarning("Slack auto-reply jev mode requires OPENROUTER_API_KEY; treating message as unaddressed", String(err));
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
reportUserFacingError(err, {
|
|
1201
|
+
domain: "chat_platform",
|
|
1202
|
+
surface: "slack_auto_reply_jev",
|
|
1203
|
+
operation: "evaluate",
|
|
1204
|
+
severity: "warning",
|
|
1205
|
+
context: { conversationId: event.address.conversationId },
|
|
1206
|
+
});
|
|
1207
|
+
return false;
|
|
838
1208
|
}
|
|
839
|
-
|
|
1209
|
+
}
|
|
1210
|
+
async handleMessageEvent({ event, ack }) {
|
|
1211
|
+
const e = event;
|
|
1212
|
+
if (!this.admitHumanMessage(e, ack))
|
|
1213
|
+
return;
|
|
1214
|
+
const isDM = e.channel_type === "im" || e.channel.startsWith("D");
|
|
840
1215
|
const conversationKind = isDM ? "direct" : "shared";
|
|
841
1216
|
const isMessagingBotMention = e.text?.includes(`<@${this.botUserId}>`);
|
|
842
|
-
// Skip channel @mentions - already handled by app_mention event
|
|
843
1217
|
if (!isDM && isMessagingBotMention) {
|
|
844
1218
|
ack();
|
|
845
1219
|
return;
|
|
846
1220
|
}
|
|
847
|
-
const isThreadReply = !!e.thread_ts;
|
|
848
1221
|
const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
|
|
849
|
-
|
|
850
|
-
|
|
1222
|
+
if (isDM && e.thread_ts && e.text) {
|
|
1223
|
+
void titleAssistantThread(this.assistantOps(), this.assistantThreads, e.channel, e.thread_ts, this.stripOwnMention(e.text));
|
|
1224
|
+
}
|
|
1225
|
+
const slackEvent = createConversationEvent({
|
|
1226
|
+
platform: "slack",
|
|
851
1227
|
conversationId: e.channel,
|
|
1228
|
+
type: (isDM ? "dm" : "mention"),
|
|
852
1229
|
conversationKind,
|
|
853
|
-
channel: e.channel,
|
|
854
1230
|
ts: e.ts,
|
|
855
1231
|
thread_ts: e.thread_ts,
|
|
856
1232
|
user: e.user,
|
|
857
1233
|
text: this.stripOwnMention(e.text),
|
|
858
|
-
files: e.files,
|
|
859
1234
|
sessionKey,
|
|
860
|
-
|
|
1235
|
+
channel: e.channel,
|
|
1236
|
+
files: e.files,
|
|
1237
|
+
});
|
|
861
1238
|
const attachmentsPromise = this.logUserMessage(slackEvent);
|
|
862
|
-
// Only trigger processing for messages AFTER startup (not replayed old messages)
|
|
863
1239
|
if (this.startupTs && e.ts < this.startupTs) {
|
|
864
1240
|
log.logInfo(`[${e.channel}] Skipping old message (pre-startup): ${slackEvent.text.substring(0, 30)}`);
|
|
865
1241
|
void attachmentsPromise.catch((err) => {
|
|
@@ -868,136 +1244,77 @@ export class SlackMessagingBot {
|
|
|
868
1244
|
ack();
|
|
869
1245
|
return;
|
|
870
1246
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
this.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
this.postMessage(e.channel, formatNothingRunning("slack"));
|
|
879
|
-
}
|
|
880
|
-
void attachmentsPromise.catch((err) => {
|
|
881
|
-
log.logWarning("Failed to log Slack message", String(err));
|
|
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();
|
|
1247
|
+
const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
1248
|
+
slackEvent.sessionKey = activeSessionKey;
|
|
1249
|
+
const taskControl = isDM &&
|
|
1250
|
+
(e.thread_ts
|
|
1251
|
+
? this.isTaskThread(e.channel, e.thread_ts)
|
|
1252
|
+
: this.hasRunningTaskThread(e.channel) || isTaskStatusQuestion(slackEvent.text));
|
|
1253
|
+
if (taskControl) {
|
|
913
1254
|
ack();
|
|
914
|
-
|
|
1255
|
+
if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
|
|
1256
|
+
return;
|
|
915
1257
|
}
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
1258
|
+
const autoReplyMode = isDM
|
|
1259
|
+
? "off"
|
|
1260
|
+
: slackConversationAutoReplyMode(this.workspace.office(slackEvent.address));
|
|
1261
|
+
const autoReply = autoReplyMode === "on"
|
|
1262
|
+
? true
|
|
1263
|
+
: autoReplyMode === "jev"
|
|
1264
|
+
? await this.evaluateJevAddressed(slackEvent)
|
|
1265
|
+
: false;
|
|
1266
|
+
const intake = this.processSlackMessageIntake({
|
|
919
1267
|
event: slackEvent,
|
|
920
1268
|
attachmentsPromise,
|
|
921
1269
|
queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
|
|
922
|
-
|
|
923
|
-
|
|
1270
|
+
addressed: isDM || autoReply,
|
|
1271
|
+
magicWordAddressed: isDM,
|
|
924
1272
|
});
|
|
925
|
-
|
|
1273
|
+
if (!taskControl)
|
|
1274
|
+
ack();
|
|
1275
|
+
await intake;
|
|
926
1276
|
}
|
|
927
|
-
async handleSlashCommand({ body, ack
|
|
1277
|
+
async handleSlashCommand({ body, ack }) {
|
|
928
1278
|
const payload = body;
|
|
929
1279
|
await ack();
|
|
930
1280
|
if (!payload.command || !payload.channel_id || !payload.user_id) {
|
|
931
1281
|
return;
|
|
932
1282
|
}
|
|
933
1283
|
const { command, text, channel_id, user_id, user_name, thread_ts } = payload;
|
|
934
|
-
|
|
935
|
-
if (
|
|
936
|
-
|
|
1284
|
+
const entry = COMMAND_MANIFEST.find((candidate) => candidate.slackCommand === command);
|
|
1285
|
+
if (!entry)
|
|
1286
|
+
return;
|
|
1287
|
+
if (!entry.slackRoute)
|
|
1288
|
+
return;
|
|
1289
|
+
try {
|
|
1290
|
+
await this.routeSlashCommand(entry.slackRoute, {
|
|
937
1291
|
command,
|
|
938
1292
|
text,
|
|
939
1293
|
channel_id,
|
|
940
1294
|
user_id,
|
|
941
1295
|
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
1296
|
thread_ts,
|
|
954
1297
|
});
|
|
955
1298
|
}
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
});
|
|
1299
|
+
catch (err) {
|
|
1300
|
+
log.logWarning("Slack slash command error", errorMessage(err));
|
|
975
1301
|
}
|
|
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) => {
|
|
989
|
-
log.logWarning("Slack slash command error", err instanceof Error ? err.message : String(err));
|
|
990
|
-
});
|
|
991
1302
|
}
|
|
992
1303
|
handleAppHomeOpened({ event, ack }) {
|
|
993
1304
|
const e = event;
|
|
994
1305
|
ack();
|
|
1306
|
+
if (e.tab === "messages") {
|
|
1307
|
+
if (e.channel) {
|
|
1308
|
+
void handleAgentDmOpened(this.assistantOps(), this.assistantThreads, e.channel, e.context);
|
|
1309
|
+
}
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
995
1312
|
if (e.tab !== "home")
|
|
996
1313
|
return;
|
|
997
1314
|
this.webClient.views
|
|
998
1315
|
.publish({
|
|
999
1316
|
user_id: e.user,
|
|
1000
|
-
view: this.buildHomeView(),
|
|
1317
|
+
view: this.buildHomeView(e.channel),
|
|
1001
1318
|
})
|
|
1002
1319
|
.catch((err) => {
|
|
1003
1320
|
log.logWarning(`Failed to publish App Home view`, String(err));
|
|
@@ -1015,21 +1332,18 @@ export class SlackMessagingBot {
|
|
|
1015
1332
|
return;
|
|
1016
1333
|
}
|
|
1017
1334
|
ack();
|
|
1018
|
-
const sessionKey = action.action_id.replace("force_stop_", "").replace(/_/g, ":");
|
|
1335
|
+
const sessionKey = action.value ?? action.action_id.replace("force_stop_", "").replace(/_/g, ":");
|
|
1019
1336
|
const userId = body.user?.id;
|
|
1020
|
-
const channelId = body.container?.channel_id || sessionKey
|
|
1337
|
+
const channelId = body.container?.channel_id || conversationIdOf(sessionKey);
|
|
1021
1338
|
log.logInfo(`[Force Stop] User ${userId} requested force stop for ${sessionKey}`);
|
|
1022
|
-
|
|
1023
|
-
this.handler.forceStop(sessionKey);
|
|
1024
|
-
// Notify in channel
|
|
1339
|
+
this.handler.forceStop(createOfficeAddress("slack", channelId), sessionKey);
|
|
1025
1340
|
await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
|
|
1026
|
-
// Refresh home tab
|
|
1027
1341
|
if (userId) {
|
|
1028
|
-
this.
|
|
1029
|
-
.publish({
|
|
1342
|
+
this.openDirectConversation(userId)
|
|
1343
|
+
.then((dmChannelId) => this.webClient.views.publish({
|
|
1030
1344
|
user_id: userId,
|
|
1031
|
-
view: this.buildHomeView(),
|
|
1032
|
-
})
|
|
1345
|
+
view: this.buildHomeView(dmChannelId),
|
|
1346
|
+
}))
|
|
1033
1347
|
.catch((err) => {
|
|
1034
1348
|
log.logWarning(`Failed to refresh App Home view`, String(err));
|
|
1035
1349
|
});
|
|
@@ -1045,6 +1359,8 @@ export class SlackMessagingBot {
|
|
|
1045
1359
|
const selectedOptions = Array.isArray(action.selected_options)
|
|
1046
1360
|
? action.selected_options
|
|
1047
1361
|
: undefined;
|
|
1362
|
+
const threadTs = container.thread_ts;
|
|
1363
|
+
const sessionKey = resolveSlackSessionKey(channelId, threadTs);
|
|
1048
1364
|
const selectedText = selectedOption?.text?.text ?? selectedOption?.value;
|
|
1049
1365
|
const selectedTexts = selectedOptions?.map((option) => option.text?.text ?? option.value);
|
|
1050
1366
|
const valueText = selectedTexts?.length
|
|
@@ -1052,12 +1368,10 @@ export class SlackMessagingBot {
|
|
|
1052
1368
|
: (selectedText ?? action.value ?? action.action_id);
|
|
1053
1369
|
const text = `[Slack action] ${action.action_id}: ${valueText}`;
|
|
1054
1370
|
const ts = `action:${Date.now()}`;
|
|
1055
|
-
const threadTs = container.thread_ts;
|
|
1056
|
-
const sessionKey = resolveSlackSessionKey(channelId, threadTs);
|
|
1057
1371
|
this.logToFile(channelId, {
|
|
1058
1372
|
date: new Date().toISOString(),
|
|
1059
1373
|
ts,
|
|
1060
|
-
|
|
1374
|
+
threadTs: threadTs || undefined,
|
|
1061
1375
|
user: userId,
|
|
1062
1376
|
userName: body.user?.username ?? body.user?.name,
|
|
1063
1377
|
text,
|
|
@@ -1080,50 +1394,87 @@ export class SlackMessagingBot {
|
|
|
1080
1394
|
messageTs: container.message_ts,
|
|
1081
1395
|
},
|
|
1082
1396
|
});
|
|
1083
|
-
const event = {
|
|
1084
|
-
|
|
1397
|
+
const event = createConversationEvent({
|
|
1398
|
+
platform: "slack",
|
|
1085
1399
|
conversationId: channelId,
|
|
1400
|
+
type: "slack_action",
|
|
1086
1401
|
conversationKind: channelId.startsWith("D") ? "direct" : "shared",
|
|
1087
1402
|
ts,
|
|
1088
1403
|
user: userId,
|
|
1089
1404
|
text,
|
|
1090
1405
|
attachments: [],
|
|
1091
|
-
|
|
1406
|
+
thread_ts: threadTs || undefined,
|
|
1092
1407
|
sessionKey,
|
|
1093
|
-
};
|
|
1408
|
+
});
|
|
1094
1409
|
this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
|
|
1095
1410
|
const slackEvent = {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1411
|
+
...createConversationEvent({
|
|
1412
|
+
platform: "slack",
|
|
1413
|
+
conversationId: channelId,
|
|
1414
|
+
type: (event.conversationKind === "direct" ? "dm" : "mention"),
|
|
1415
|
+
conversationKind: event.conversationKind,
|
|
1416
|
+
ts,
|
|
1417
|
+
thread_ts: threadTs,
|
|
1418
|
+
user: userId,
|
|
1419
|
+
text,
|
|
1420
|
+
attachments: [],
|
|
1421
|
+
sessionKey,
|
|
1422
|
+
}),
|
|
1099
1423
|
channel: channelId,
|
|
1100
|
-
ts,
|
|
1101
|
-
thread_ts: threadTs,
|
|
1102
|
-
user: userId,
|
|
1103
|
-
text,
|
|
1104
1424
|
attachments: [],
|
|
1105
|
-
sessionKey,
|
|
1106
1425
|
};
|
|
1107
1426
|
return this.handler.handleEvent(event, this, this.createContext(slackEvent));
|
|
1108
1427
|
});
|
|
1109
1428
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1429
|
+
async processAttachments(channelId, files, timestamp) {
|
|
1430
|
+
const items = [];
|
|
1431
|
+
for (const file of files) {
|
|
1432
|
+
const url = file.url_private_download || file.url_private;
|
|
1433
|
+
if (!url)
|
|
1434
|
+
continue;
|
|
1435
|
+
if (!file.name) {
|
|
1436
|
+
throw new Error(`Attachment missing name for URL: ${url}`);
|
|
1437
|
+
}
|
|
1438
|
+
items.push({
|
|
1439
|
+
name: file.name,
|
|
1440
|
+
timestampMs: Math.floor(parseFloat(timestamp) * 1000),
|
|
1441
|
+
download: (destPath) => this.downloadSlackFile(url, destPath),
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
const office = this.workspace.office(createOfficeAddress("slack", channelId));
|
|
1445
|
+
const { saved, failed } = await saveIncomingAttachments(office, items);
|
|
1446
|
+
const firstFailure = failed[0];
|
|
1447
|
+
if (firstFailure) {
|
|
1448
|
+
const { name, error } = firstFailure;
|
|
1449
|
+
const errorMsg = errorMessage(error);
|
|
1450
|
+
throw new Error(`Failed to download attachment ${name}: ${errorMsg}`, { cause: error });
|
|
1451
|
+
}
|
|
1452
|
+
return saved;
|
|
1453
|
+
}
|
|
1454
|
+
async downloadSlackFile(url, destPath) {
|
|
1455
|
+
assertSlackFileUrl(url);
|
|
1456
|
+
await withRetry(async () => {
|
|
1457
|
+
const response = await this.fetchFn(url, {
|
|
1458
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
1459
|
+
});
|
|
1460
|
+
if (!response.ok) {
|
|
1461
|
+
throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
|
|
1462
|
+
}
|
|
1463
|
+
await writeResponseToFile(response, destPath);
|
|
1464
|
+
}, { maxAttempts: 3, baseDelayMs: 250, isRateLimited: isRetryableAttachmentDownloadError });
|
|
1465
|
+
}
|
|
1113
1466
|
async logUserMessage(event) {
|
|
1114
1467
|
const user = this.users.get(event.user);
|
|
1115
1468
|
let attachments = [];
|
|
1116
1469
|
let attachmentError;
|
|
1117
1470
|
if (event.files) {
|
|
1118
1471
|
try {
|
|
1119
|
-
attachments = await this.
|
|
1472
|
+
attachments = await this.processAttachments(event.channel, event.files, event.ts);
|
|
1120
1473
|
}
|
|
1121
1474
|
catch (err) {
|
|
1122
1475
|
attachmentError = err;
|
|
1123
1476
|
}
|
|
1124
1477
|
}
|
|
1125
|
-
// Always write the text log, even if attachment processing failed — we want
|
|
1126
|
-
// a record of the user message regardless of file-handling errors.
|
|
1127
1478
|
this.logToFile(event.channel, {
|
|
1128
1479
|
date: new Date(parseFloat(event.ts) * 1000).toISOString(),
|
|
1129
1480
|
ts: event.ts,
|
|
@@ -1141,7 +1492,7 @@ export class SlackMessagingBot {
|
|
|
1141
1492
|
}
|
|
1142
1493
|
async logExternalMessagingBotMessage(event) {
|
|
1143
1494
|
const attachments = event.files
|
|
1144
|
-
? await this.
|
|
1495
|
+
? await this.processAttachments(event.channel, event.files, event.ts)
|
|
1145
1496
|
: [];
|
|
1146
1497
|
const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
|
|
1147
1498
|
this.logToFile(event.channel, {
|
|
@@ -1160,11 +1511,8 @@ export class SlackMessagingBot {
|
|
|
1160
1511
|
});
|
|
1161
1512
|
return attachments;
|
|
1162
1513
|
}
|
|
1163
|
-
// ==========================================================================
|
|
1164
|
-
// Private - Backfill
|
|
1165
|
-
// ==========================================================================
|
|
1166
1514
|
async getExistingTimestamps(channelId) {
|
|
1167
|
-
const logPath =
|
|
1515
|
+
const { logPath } = this.office(channelId);
|
|
1168
1516
|
const timestamps = new Set();
|
|
1169
1517
|
if (!existsSync(logPath))
|
|
1170
1518
|
return timestamps;
|
|
@@ -1172,19 +1520,30 @@ export class SlackMessagingBot {
|
|
|
1172
1520
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1173
1521
|
for (let i = 0; i < lines.length; i++) {
|
|
1174
1522
|
try {
|
|
1175
|
-
const entry = JSON.parse(lines[i]);
|
|
1523
|
+
const entry = JSON.parse(lines[i] ?? "");
|
|
1176
1524
|
if (entry.ts)
|
|
1177
1525
|
timestamps.add(entry.ts);
|
|
1178
1526
|
}
|
|
1179
1527
|
catch (err) {
|
|
1180
|
-
log.logWarning(`Skipping malformed log entry at ${logPath}:${i + 1}`,
|
|
1528
|
+
log.logWarning(`Skipping malformed log entry at ${logPath}:${i + 1}`, errorMessage(err));
|
|
1181
1529
|
}
|
|
1182
1530
|
}
|
|
1183
1531
|
return timestamps;
|
|
1184
1532
|
}
|
|
1533
|
+
isBackfillableMessage(message) {
|
|
1534
|
+
if (message.user === this.botUserId)
|
|
1535
|
+
return true;
|
|
1536
|
+
if (hasBotIdentity(message)) {
|
|
1537
|
+
if (this.botId && message.bot_id === this.botId)
|
|
1538
|
+
return false;
|
|
1539
|
+
return BOT_MESSAGE_SUBTYPES.has(message.subtype) && hasMessageContent(message);
|
|
1540
|
+
}
|
|
1541
|
+
return (!!message.user &&
|
|
1542
|
+
USER_MESSAGE_SUBTYPES.has(message.subtype) &&
|
|
1543
|
+
hasMessageContent(message, false));
|
|
1544
|
+
}
|
|
1185
1545
|
async backfillChannel(channelId, upperBoundTs) {
|
|
1186
1546
|
const existingTs = await this.getExistingTimestamps(channelId);
|
|
1187
|
-
// Find the biggest ts in log.jsonl
|
|
1188
1547
|
let lastLoggedTs;
|
|
1189
1548
|
for (const ts of existingTs) {
|
|
1190
1549
|
if (!lastLoggedTs || parseFloat(ts) > parseFloat(lastLoggedTs))
|
|
@@ -1197,8 +1556,8 @@ export class SlackMessagingBot {
|
|
|
1197
1556
|
do {
|
|
1198
1557
|
const result = await this.webClient.conversations.history({
|
|
1199
1558
|
channel: channelId,
|
|
1200
|
-
oldest: lastLoggedTs,
|
|
1201
|
-
latest: upperBoundTs,
|
|
1559
|
+
oldest: lastLoggedTs,
|
|
1560
|
+
latest: upperBoundTs,
|
|
1202
1561
|
inclusive: false,
|
|
1203
1562
|
limit: 1000,
|
|
1204
1563
|
cursor,
|
|
@@ -1209,40 +1568,15 @@ export class SlackMessagingBot {
|
|
|
1209
1568
|
cursor = result.response_metadata?.next_cursor;
|
|
1210
1569
|
pageCount++;
|
|
1211
1570
|
} while (cursor && pageCount < maxPages);
|
|
1212
|
-
// Filter: include mikan's messages, external app/bot messages, and user messages.
|
|
1213
1571
|
const relevantMessages = allMessages.filter((msg) => {
|
|
1214
1572
|
if (!msg.ts || existingTs.has(msg.ts))
|
|
1215
|
-
return false; // Skip duplicates
|
|
1216
|
-
if (msg.user === this.botUserId)
|
|
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
1573
|
return false;
|
|
1238
|
-
return
|
|
1574
|
+
return this.isBackfillableMessage(msg);
|
|
1239
1575
|
});
|
|
1240
|
-
// Reverse to chronological order
|
|
1241
1576
|
relevantMessages.reverse();
|
|
1242
|
-
// Log each message to log.jsonl
|
|
1243
1577
|
for (const msg of relevantMessages) {
|
|
1244
1578
|
const isMikanMessage = msg.user === this.botUserId;
|
|
1245
|
-
const isExternalMessagingBotMessage = !isMikanMessage && (
|
|
1579
|
+
const isExternalMessagingBotMessage = !isMikanMessage && hasBotIdentity(msg);
|
|
1246
1580
|
if (isExternalMessagingBotMessage) {
|
|
1247
1581
|
await this.logExternalMessagingBotMessage({ ...msg, channel: channelId, ts: msg.ts });
|
|
1248
1582
|
continue;
|
|
@@ -1250,7 +1584,7 @@ export class SlackMessagingBot {
|
|
|
1250
1584
|
const user = this.users.get(msg.user);
|
|
1251
1585
|
const text = this.stripOwnMention(msg.text);
|
|
1252
1586
|
const attachments = msg.files
|
|
1253
|
-
? await this.
|
|
1587
|
+
? await this.processAttachments(channelId, msg.files, msg.ts)
|
|
1254
1588
|
: [];
|
|
1255
1589
|
this.logToFile(channelId, {
|
|
1256
1590
|
date: new Date(parseFloat(msg.ts) * 1000).toISOString(),
|
|
@@ -1268,11 +1602,9 @@ export class SlackMessagingBot {
|
|
|
1268
1602
|
}
|
|
1269
1603
|
async backfillAllChannels(upperBoundTs) {
|
|
1270
1604
|
const startTime = Date.now();
|
|
1271
|
-
// Only backfill channels that already have a log.jsonl (mikan has interacted with them before)
|
|
1272
1605
|
const channelsToBackfill = [];
|
|
1273
1606
|
for (const [channelId, channel] of this.channels) {
|
|
1274
|
-
|
|
1275
|
-
if (existsSync(logPath)) {
|
|
1607
|
+
if (existsSync(this.office(channelId).logPath)) {
|
|
1276
1608
|
channelsToBackfill.push([channelId, channel]);
|
|
1277
1609
|
}
|
|
1278
1610
|
}
|
|
@@ -1288,38 +1620,32 @@ export class SlackMessagingBot {
|
|
|
1288
1620
|
catch (error) {
|
|
1289
1621
|
log.logWarning(`Failed to backfill #${channel.name}`, String(error));
|
|
1290
1622
|
}
|
|
1291
|
-
|
|
1292
|
-
if (channelId !== channelsToBackfill[channelsToBackfill.length - 1][0]) {
|
|
1623
|
+
if (channelId !== channelsToBackfill[channelsToBackfill.length - 1]?.[0]) {
|
|
1293
1624
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1294
1625
|
}
|
|
1295
1626
|
}
|
|
1296
1627
|
const durationMs = Date.now() - startTime;
|
|
1297
1628
|
log.logBackfillComplete(totalMessages, durationMs);
|
|
1298
1629
|
}
|
|
1299
|
-
// ==========================================================================
|
|
1300
|
-
// Private - Fetch Users/Channels
|
|
1301
|
-
// ==========================================================================
|
|
1302
1630
|
async fetchUsers() {
|
|
1303
1631
|
let cursor;
|
|
1304
1632
|
do {
|
|
1305
1633
|
const result = await this.webClient.users.list({ limit: 200, cursor });
|
|
1306
1634
|
const members = result.members;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1635
|
+
for (const u of members ?? []) {
|
|
1636
|
+
if (!u.id || !u.name || u.deleted)
|
|
1637
|
+
continue;
|
|
1638
|
+
this.users.set(u.id, {
|
|
1639
|
+
id: u.id,
|
|
1640
|
+
userName: u.name,
|
|
1641
|
+
displayName: u.real_name || u.name,
|
|
1642
|
+
isBot: !!u.is_bot,
|
|
1643
|
+
});
|
|
1317
1644
|
}
|
|
1318
1645
|
cursor = result.response_metadata?.next_cursor;
|
|
1319
1646
|
} while (cursor);
|
|
1320
1647
|
}
|
|
1321
1648
|
async fetchChannels() {
|
|
1322
|
-
// Fetch public/private channels
|
|
1323
1649
|
let cursor;
|
|
1324
1650
|
do {
|
|
1325
1651
|
const result = await this.webClient.conversations.list({
|
|
@@ -1329,16 +1655,18 @@ export class SlackMessagingBot {
|
|
|
1329
1655
|
cursor,
|
|
1330
1656
|
});
|
|
1331
1657
|
const channels = result.channels;
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1658
|
+
for (const c of channels ?? []) {
|
|
1659
|
+
if (!c.id || !c.name || !c.is_member)
|
|
1660
|
+
continue;
|
|
1661
|
+
this.channels.set(c.id, {
|
|
1662
|
+
id: c.id,
|
|
1663
|
+
name: c.name,
|
|
1664
|
+
isPrivate: typeof c.is_private === "boolean" ? c.is_private : undefined,
|
|
1665
|
+
isExternallyShared: c.is_shared || c.is_ext_shared ? true : undefined,
|
|
1666
|
+
});
|
|
1338
1667
|
}
|
|
1339
1668
|
cursor = result.response_metadata?.next_cursor;
|
|
1340
1669
|
} while (cursor);
|
|
1341
|
-
// Also fetch DM channels (IMs)
|
|
1342
1670
|
cursor = undefined;
|
|
1343
1671
|
do {
|
|
1344
1672
|
const result = await this.webClient.conversations.list({
|
|
@@ -1347,15 +1675,12 @@ export class SlackMessagingBot {
|
|
|
1347
1675
|
cursor,
|
|
1348
1676
|
});
|
|
1349
1677
|
const ims = result.channels;
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
this.channels.set(im.id, { id: im.id, name });
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1678
|
+
for (const im of ims ?? []) {
|
|
1679
|
+
if (!im.id)
|
|
1680
|
+
continue;
|
|
1681
|
+
const user = im.user ? this.users.get(im.user) : undefined;
|
|
1682
|
+
const name = user ? `DM:${user.userName}` : `DM:${im.id}`;
|
|
1683
|
+
this.channels.set(im.id, { id: im.id, name });
|
|
1359
1684
|
}
|
|
1360
1685
|
cursor = result.response_metadata?.next_cursor;
|
|
1361
1686
|
} while (cursor);
|