talon-agent 4.3.1 → 4.3.3

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.
Files changed (133) hide show
  1. package/package.json +1 -1
  2. package/src/app.ts +1 -1
  3. package/src/backend/claude-sdk/doctor.ts +1 -1
  4. package/src/backend/codex/doctor.ts +1 -1
  5. package/src/backend/openai-agents/doctor.ts +1 -1
  6. package/src/bootstrap.ts +3 -3
  7. package/src/cli/doctor.ts +1 -1
  8. package/src/cli/skill.ts +2 -2
  9. package/src/core/agent-runtime/backend-registry.ts +1 -1
  10. package/src/core/auth/expiry-monitor.ts +1 -1
  11. package/src/core/background/cron-spec.ts +1 -1
  12. package/src/core/background/cron.ts +1 -1
  13. package/src/core/background/heartbeat/agent.ts +1 -1
  14. package/src/core/background/triggers/command.ts +1 -1
  15. package/src/core/background/triggers/exit.ts +1 -1
  16. package/src/core/background/triggers/output.ts +1 -1
  17. package/src/core/background/triggers/resume.ts +1 -1
  18. package/src/core/background/triggers/spawn.ts +1 -1
  19. package/src/core/{doctor.ts → doctor/index.ts} +11 -11
  20. package/src/core/engine/gateway-actions/cron.ts +2 -2
  21. package/src/core/engine/gateway-actions/goals.ts +2 -2
  22. package/src/core/engine/gateway-actions/scripts.ts +1 -1
  23. package/src/core/engine/gateway-actions/skills.ts +1 -1
  24. package/src/core/engine/gateway-actions/triggers.ts +2 -2
  25. package/src/core/engine/gateway-actions/{shared.ts → validation.ts} +2 -2
  26. package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
  27. package/src/core/plugin/native-runtimes.ts +1 -1
  28. package/src/core/prompt/assemble.ts +2 -2
  29. package/src/core/scripts/runner.ts +1 -1
  30. package/src/core/soul/forgetting.ts +2 -2
  31. package/src/core/soul/salience.ts +8 -4
  32. package/src/frontend/discord/actions/{shared.ts → buttons.ts} +3 -56
  33. package/src/frontend/discord/actions/channels.ts +34 -0
  34. package/src/frontend/discord/actions/chat-info.ts +1 -1
  35. package/src/frontend/discord/actions/index.ts +1 -1
  36. package/src/frontend/discord/actions/media.ts +1 -1
  37. package/src/frontend/discord/actions/messaging.ts +4 -2
  38. package/src/frontend/discord/actions/try-action.ts +24 -0
  39. package/src/frontend/discord/admin.ts +2 -2
  40. package/src/frontend/discord/callbacks/components/settings.ts +1 -1
  41. package/src/frontend/discord/callbacks/modals.ts +1 -1
  42. package/src/frontend/discord/commands/admin.ts +3 -3
  43. package/src/frontend/discord/commands/info.ts +2 -2
  44. package/src/frontend/discord/commands/{shared.ts → interaction.ts} +2 -1
  45. package/src/frontend/discord/commands/router.ts +1 -1
  46. package/src/frontend/discord/commands/session.ts +2 -2
  47. package/src/frontend/discord/commands/settings.ts +2 -2
  48. package/src/frontend/discord/{helpers.ts → render.ts} +3 -2
  49. package/src/frontend/native/extensions.ts +1 -1
  50. package/src/frontend/teams/chat-discovery.ts +69 -0
  51. package/src/frontend/teams/commands.ts +141 -0
  52. package/src/frontend/teams/index.ts +24 -350
  53. package/src/frontend/teams/outbound.ts +44 -0
  54. package/src/frontend/teams/poll.ts +101 -0
  55. package/src/frontend/teams/runtime.ts +51 -0
  56. package/src/frontend/teams/turn.ts +97 -0
  57. package/src/frontend/telegram/actions/index.ts +1 -1
  58. package/src/frontend/telegram/actions/messaging.ts +1 -1
  59. package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
  60. package/src/frontend/telegram/actions/moderation/index.ts +74 -0
  61. package/src/frontend/telegram/actions/moderation/members.ts +77 -0
  62. package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
  63. package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
  64. package/src/frontend/telegram/actions/moderation/types.ts +16 -0
  65. package/src/frontend/telegram/actions/shared.ts +4 -4
  66. package/src/frontend/telegram/admin/background.ts +66 -0
  67. package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
  68. package/src/frontend/telegram/admin/health.ts +81 -0
  69. package/src/frontend/telegram/admin/sessions.ts +105 -0
  70. package/src/frontend/telegram/admin.ts +56 -261
  71. package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
  72. package/src/frontend/telegram/callbacks/model/control.ts +29 -0
  73. package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
  74. package/src/frontend/telegram/callbacks/model/select.ts +100 -0
  75. package/src/frontend/telegram/callbacks/model/types.ts +39 -0
  76. package/src/frontend/telegram/callbacks/model/views.ts +116 -0
  77. package/src/frontend/telegram/callbacks/model.ts +61 -331
  78. package/src/frontend/telegram/commands/admin.ts +108 -77
  79. package/src/frontend/telegram/commands/settings.ts +65 -43
  80. package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
  81. package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
  82. package/src/frontend/telegram/middleware.ts +166 -201
  83. package/src/frontend/telegram/sticker-library.ts +2 -2
  84. package/src/frontend/terminal/builtins/context.ts +184 -0
  85. package/src/frontend/terminal/builtins/help.ts +35 -0
  86. package/src/frontend/terminal/builtins/index.ts +23 -0
  87. package/src/frontend/terminal/builtins/model.ts +198 -0
  88. package/src/frontend/terminal/builtins/session.ts +101 -0
  89. package/src/frontend/terminal/builtins/status.ts +202 -0
  90. package/src/frontend/terminal/command-registry.ts +80 -0
  91. package/src/frontend/terminal/commands.ts +11 -674
  92. package/src/frontend/terminal/input/history.ts +64 -0
  93. package/src/frontend/terminal/input/keys.ts +143 -0
  94. package/src/frontend/terminal/input/parts.ts +33 -0
  95. package/src/frontend/terminal/input/paste.ts +53 -0
  96. package/src/frontend/terminal/input/render.ts +42 -0
  97. package/src/frontend/terminal/input/state.ts +60 -0
  98. package/src/frontend/terminal/input.ts +27 -342
  99. package/src/frontend/whatsapp/actions/account.ts +3 -6
  100. package/src/frontend/whatsapp/actions/chat-info.ts +2 -1
  101. package/src/frontend/whatsapp/actions/coerce.ts +30 -0
  102. package/src/frontend/whatsapp/actions/history.ts +1 -1
  103. package/src/frontend/whatsapp/actions/media-upload.ts +82 -0
  104. package/src/frontend/whatsapp/actions/media.ts +3 -8
  105. package/src/frontend/whatsapp/actions/messaging.ts +2 -7
  106. package/src/frontend/whatsapp/actions/moderation.ts +2 -1
  107. package/src/frontend/whatsapp/actions/{shared.ts → send.ts} +4 -124
  108. package/src/frontend/whatsapp/actions/try-action.ts +24 -0
  109. package/src/frontend/whatsapp/commands.ts +1 -1
  110. package/src/frontend/whatsapp/connection.ts +1 -1
  111. package/src/frontend/whatsapp/inbound.ts +1 -1
  112. package/src/frontend/whatsapp/index.ts +2 -2
  113. package/src/frontend/whatsapp/pairing-service.ts +1 -1
  114. package/src/plugins/github/provision.ts +1 -1
  115. package/src/plugins/mempalace/provision.ts +1 -1
  116. package/src/plugins/playwright/provision.ts +1 -1
  117. package/src/storage/repositories/cron-repo.ts +1 -1
  118. package/src/storage/repositories/goals-repo.ts +1 -1
  119. package/src/storage/repositories/scripts-repo.ts +1 -1
  120. package/src/storage/repositories/triggers-repo.ts +1 -1
  121. package/src/storage/sql/schema.sql +1 -1
  122. package/src/storage/sql/statements.generated.ts +2 -2
  123. package/src/storage/sql/triggers.sql +1 -1
  124. package/src/frontend/telegram/actions/moderation.ts +0 -255
  125. /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
  126. /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
  127. /package/src/storage/{cron-store.ts → cron.ts} +0 -0
  128. /package/src/storage/{goal-store.ts → goals.ts} +0 -0
  129. /package/src/storage/{scheduled-store.ts → scheduled.ts} +0 -0
  130. /package/src/storage/{script-store.ts → scripts.ts} +0 -0
  131. /package/src/storage/{skill-store.ts → skills.ts} +0 -0
  132. /package/src/storage/{sticker-store.ts → stickers.ts} +0 -0
  133. /package/src/storage/{trigger-store.ts → triggers.ts} +0 -0
@@ -19,11 +19,11 @@ import {
19
19
  getAllCronJobs,
20
20
  describeSchedule,
21
21
  nextRunAt,
22
- } from "../../storage/cron-store.js";
22
+ } from "../../storage/cron.js";
23
23
  import { getActiveCount } from "../../core/engine/dispatcher.js";
24
24
  import { getPulseStatus } from "../../core/background/pulse.js";
25
25
  import { getHealthStatus, getRecentErrors } from "../../util/watchdog.js";
26
- import { formatDuration, formatModelLabel } from "./helpers.js";
26
+ import { formatDuration, formatModelLabel } from "./render.js";
27
27
  import {
28
28
  DISCORD_MAX_TEXT,
29
29
  DISCORD_SAFE_RESERVE,
@@ -31,7 +31,7 @@ import {
31
31
  enablePulse,
32
32
  isPulseEnabled,
33
33
  } from "../../../../core/background/pulse.js";
34
- import { renderSettingsText, EFFORT_DESCRIPTIONS } from "../../helpers.js";
34
+ import { renderSettingsText, EFFORT_DESCRIPTIONS } from "../../render.js";
35
35
  import {
36
36
  getBackendIdForChat,
37
37
  resolveChatBackend,
@@ -8,7 +8,7 @@ import type { Gateway } from "../../../core/engine/gateway.js";
8
8
  import { setChatPulseInterval } from "../../../storage/chat-settings.js";
9
9
  import { registerChat, enablePulse } from "../../../core/background/pulse.js";
10
10
  import { isInteractionAllowed } from "../handlers/index.js";
11
- import { parseInterval, formatDuration } from "../helpers.js";
11
+ import { parseInterval, formatDuration } from "../render.js";
12
12
  import { logError } from "../../../util/log.js";
13
13
 
14
14
  export async function handleModalSubmit(
@@ -18,8 +18,8 @@ import {
18
18
  formatDuration,
19
19
  renderMetricsMessages,
20
20
  renderDoctorMessages,
21
- } from "../helpers.js";
22
- import { collectDoctorReport } from "../../../core/doctor.js";
21
+ } from "../render.js";
22
+ import { collectDoctorReport } from "../../../core/doctor/index.js";
23
23
  import { getSoul } from "../../../core/soul/service.js";
24
24
  import { getMetrics, getTodayMetrics } from "../../../storage/metrics.js";
25
25
  import { handleAdminSubcommand } from "../admin.js";
@@ -34,7 +34,7 @@ import {
34
34
  getRepoRoot,
35
35
  runSelfUpdate,
36
36
  } from "../../../core/update/self-update.js";
37
- import { reply } from "./shared.js";
37
+ import { reply } from "./interaction.js";
38
38
 
39
39
  export async function handleRestart(
40
40
  i: ChatInputCommandInteraction,
@@ -11,13 +11,13 @@ import {
11
11
  formatDuration,
12
12
  renderMeshReport,
13
13
  renderUsageMessage,
14
- } from "../helpers.js";
14
+ } from "../render.js";
15
15
  import type { TalonConfig } from "../../../util/config.js";
16
16
  import { collectPlanUsage } from "../../shared/plan-usage-report.js";
17
17
  import { getLoadedPlugins } from "../../../core/plugin/index.js";
18
18
  import { getMeshService } from "../../../core/mesh/index.js";
19
19
  import type { MeshPingResult } from "../../../core/mesh/service.js";
20
- import { reply } from "./shared.js";
20
+ import { reply } from "./interaction.js";
21
21
 
22
22
  export async function handleStart(
23
23
  i: ChatInputCommandInteraction,
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Helpers shared by the Discord slash-command handlers + router.
2
+ * Interaction accessors for the Discord slash-command handlers + router:
3
+ * chat-id derivation, chunked reply, client.
3
4
  */
4
5
 
5
6
  import { type ChatInputCommandInteraction, MessageFlags } from "discord.js";
@@ -21,7 +21,7 @@ import {
21
21
  handleComponentInteraction,
22
22
  handleModalSubmit,
23
23
  } from "../callbacks/index.js";
24
- import { chatIdFromInteraction, reply, client } from "./shared.js";
24
+ import { chatIdFromInteraction, reply, client } from "./interaction.js";
25
25
  import {
26
26
  handleStart,
27
27
  handleHelp,
@@ -14,7 +14,7 @@ import {
14
14
  formatTokenCount,
15
15
  formatBytes,
16
16
  formatUsd,
17
- } from "../helpers.js";
17
+ } from "../render.js";
18
18
  import {
19
19
  getBackendIdForChat,
20
20
  resolveChatBackend,
@@ -24,7 +24,7 @@ import {
24
24
  collectSessionStatus,
25
25
  } from "../../shared/session-status.js";
26
26
  import { stopCurrentTurn } from "../../../core/engine/dispatcher.js";
27
- import { reply } from "./shared.js";
27
+ import { reply } from "./interaction.js";
28
28
 
29
29
  export async function handleReset(
30
30
  i: ChatInputCommandInteraction,
@@ -33,7 +33,7 @@ import {
33
33
  parseInterval,
34
34
  renderSettingsText,
35
35
  EFFORT_DESCRIPTIONS,
36
- } from "../helpers.js";
36
+ } from "../render.js";
37
37
  import {
38
38
  displayReasoningEffort,
39
39
  getActiveReasoningLevels,
@@ -45,7 +45,7 @@ import {
45
45
  } from "../../../core/engine/backend-controller/index.js";
46
46
  import { resolveActiveModelForChat } from "../../../core/models/active-model.js";
47
47
  import { safeSlice } from "../formatting.js";
48
- import { reply } from "./shared.js";
48
+ import { reply } from "./interaction.js";
49
49
  import {
50
50
  buildModelPickerView,
51
51
  MODEL_NAV_PREFIX,
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Shared helpers for Discord commands, callbacks, and the settings panel.
2
+ * Message rendering for Discord commands, callbacks, and the settings panel:
3
+ * metrics, doctor, mesh, usage and settings text, sized to Discord's limits.
3
4
  *
4
5
  * Discord-specific quirks vs the Telegram helpers:
5
6
  * - settings panel uses Components (Buttons + Select Menus), not inline keyboard.
@@ -13,7 +14,7 @@ import {
13
14
  DISCORD_SAFE_RESERVE,
14
15
  splitMessage,
15
16
  } from "./formatting.js";
16
- import type { DoctorReport } from "../../core/doctor.js";
17
+ import type { DoctorReport } from "../../core/doctor/index.js";
17
18
  import type { MeshPingResult } from "../../core/mesh/service.js";
18
19
  import type { BackendUsageEntry } from "../shared/plan-usage-report.js";
19
20
  import {
@@ -22,7 +22,7 @@ import { performPluginReload } from "../../core/engine/gateway-actions/plugins.j
22
22
  import { getPluginPromptAdditions } from "../../core/plugin/index.js";
23
23
  import { listPluginItems, setPluginEnabled } from "../../core/plugin/manage.js";
24
24
  import { notifyPromptInputsChanged } from "../../core/prompt/invalidation.js";
25
- import { listSkills, setSkillEnabled } from "../../storage/skill-store.js";
25
+ import { listSkills, setSkillEnabled } from "../../storage/skills.js";
26
26
  import { rebuildSystemPrompt, type TalonConfig } from "../../util/config.js";
27
27
  import { log } from "../../util/log.js";
28
28
  import type { PluginItem, SkillItem, ToggleResult } from "./protocol.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Teams chat discovery — which group chat Talon listens to, and where in
3
+ * its history polling starts.
4
+ */
5
+
6
+ import { log, logError } from "../../util/log.js";
7
+ import type { GraphClient } from "./graph.js";
8
+ import type { TeamsRuntime } from "./runtime.js";
9
+
10
+ /**
11
+ * Load the stored chat, or pick one — by topic when configured, else the
12
+ * most recent — and persist the choice for next boot.
13
+ */
14
+ export async function resolveChatId(
15
+ runtime: TeamsRuntime,
16
+ graphClient: GraphClient,
17
+ userId: string,
18
+ ): Promise<string> {
19
+ const stored = graphClient.getStoredChatId();
20
+ if (stored) {
21
+ log("teams", `Using chat: ${graphClient.getStoredChatTopic() || stored}`);
22
+ return stored;
23
+ }
24
+
25
+ log("teams", "No chat configured, discovering...");
26
+ const chats = await graphClient.listChats();
27
+
28
+ if (chats.length === 0) throw new Error("No chats found");
29
+
30
+ // Try to match by topic name if configured
31
+ let selectedChat = chats[0];
32
+ const { configChatTopic } = runtime;
33
+ if (configChatTopic) {
34
+ const match = chats.find((c) =>
35
+ c.topic?.toLowerCase().includes(configChatTopic.toLowerCase()),
36
+ );
37
+ if (match) selectedChat = match;
38
+ else
39
+ log(
40
+ "teams",
41
+ `No chat matching topic "${configChatTopic}", using most recent`,
42
+ );
43
+ }
44
+
45
+ const topic = selectedChat.topic || "(unnamed chat)";
46
+ graphClient.saveChatConfig(selectedChat.id, topic, userId);
47
+ log("teams", `Configured chat: ${topic} [${selectedChat.chatType}]`);
48
+ return selectedChat.id;
49
+ }
50
+
51
+ /** Start after the newest existing message so old history is never replayed. */
52
+ export async function seedLastSeen(
53
+ runtime: TeamsRuntime,
54
+ graphClient: GraphClient,
55
+ chatId: string,
56
+ ): Promise<void> {
57
+ try {
58
+ const existing = await graphClient.getChatMessages(chatId, 5);
59
+ if (existing.length > 0) {
60
+ runtime.lastSeenMessageId = existing[0].id;
61
+ log("teams", `Seeded last message ID: ${runtime.lastSeenMessageId}`);
62
+ }
63
+ } catch (err) {
64
+ logError(
65
+ "teams",
66
+ `Failed to seed messages: ${err instanceof Error ? err.message : err}`,
67
+ );
68
+ }
69
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Teams slash commands — /reset, /status, /help. Each answers with a card
3
+ * through the webhook and ignores delivery failures; the poll loop moves on
4
+ * either way.
5
+ */
6
+
7
+ import { log } from "../../util/log.js";
8
+ import { resolveModel } from "../../core/models/catalog.js";
9
+ import { resolveChatBackend } from "../../core/engine/backend-controller/index.js";
10
+ import { getSessionInfo } from "../../storage/sessions.js";
11
+ import { getChatSettings } from "../../storage/chat-settings.js";
12
+ import { performSessionReset } from "../shared/session-status.js";
13
+ import { buildCacheDisplay } from "../shared/status-context.js";
14
+ import { buildAdaptiveCard } from "./formatting.js";
15
+ import type { ChatMessage } from "./graph.js";
16
+ import { postCard } from "./outbound.js";
17
+ import type { TeamsRuntime } from "./runtime.js";
18
+
19
+ const HELP_TEXT =
20
+ "**Commands:**\n- `/reset` — clear session & history\n- `/status` — session stats\n- `/help` — this message";
21
+
22
+ type Fact = { title: string; value: string };
23
+
24
+ function reply(
25
+ runtime: TeamsRuntime,
26
+ card: Record<string, unknown>,
27
+ ): Promise<unknown> {
28
+ return postCard(runtime.webhookUrl, card).catch(() => {});
29
+ }
30
+
31
+ async function handleReset(
32
+ runtime: TeamsRuntime,
33
+ msg: ChatMessage,
34
+ talonChatId: string,
35
+ ): Promise<void> {
36
+ await performSessionReset(
37
+ talonChatId,
38
+ resolveChatBackend(talonChatId, runtime.gateway.backend),
39
+ );
40
+ log("teams", `Session reset by ${msg.senderName}`);
41
+ await reply(runtime, buildAdaptiveCard("Session cleared."));
42
+ }
43
+
44
+ function statusFacts(runtime: TeamsRuntime, talonChatId: string): Fact[] {
45
+ const info = getSessionInfo(talonChatId);
46
+ const u = info.usage;
47
+ const cache = buildCacheDisplay({
48
+ cacheMetrics: runtime.gateway.backend?.cacheMetrics,
49
+ inputTokens: u.totalInputTokens,
50
+ cacheRead: u.totalCacheRead,
51
+ cacheWrite: u.totalCacheWrite,
52
+ });
53
+ const rawModel =
54
+ getChatSettings(talonChatId).model ?? (runtime.config.model as string);
55
+ const model = resolveModel(rawModel)?.displayName ?? rawModel;
56
+ const avgMs = info.turns > 0 ? Math.round(u.totalResponseMs / info.turns) : 0;
57
+ const ctxUsed = u.contextTokens || u.lastPromptTokens;
58
+ const ctxMax = u.contextWindow;
59
+ const ctxPct =
60
+ ctxMax > 0 ? Math.min(100, Math.round((ctxUsed / ctxMax) * 100)) : 0;
61
+ return [
62
+ { title: "Model", value: model },
63
+ { title: "Turns", value: String(info.turns) },
64
+ {
65
+ title: "Context",
66
+ value: `${(ctxUsed / 1000).toFixed(0)}K / ${(ctxMax / 1000).toFixed(0)}K (${ctxPct}%)`,
67
+ },
68
+ ...(cache ? [{ title: "Cache", value: `${cache.hitPct}% hit` }] : []),
69
+ { title: "Input", value: `${u.totalInputTokens.toLocaleString()} tokens` },
70
+ {
71
+ title: "Output",
72
+ value: `${u.totalOutputTokens.toLocaleString()} tokens`,
73
+ },
74
+ {
75
+ title: "Avg response",
76
+ value: avgMs > 0 ? `${(avgMs / 1000).toFixed(1)}s` : "—",
77
+ },
78
+ ];
79
+ }
80
+
81
+ function buildStatusCard(
82
+ runtime: TeamsRuntime,
83
+ talonChatId: string,
84
+ ): Record<string, unknown> {
85
+ return {
86
+ type: "message",
87
+ attachments: [
88
+ {
89
+ contentType: "application/vnd.microsoft.card.adaptive",
90
+ contentUrl: null,
91
+ content: {
92
+ type: "AdaptiveCard",
93
+ $schema: "http://adaptivecards.io/schemas/adaptive-card.json",
94
+ version: "1.4",
95
+ body: [
96
+ {
97
+ type: "TextBlock",
98
+ text: "**Session**",
99
+ wrap: true,
100
+ size: "Medium",
101
+ weight: "Bolder",
102
+ },
103
+ { type: "FactSet", facts: statusFacts(runtime, talonChatId) },
104
+ ],
105
+ },
106
+ },
107
+ ],
108
+ };
109
+ }
110
+
111
+ type CommandHandler = (
112
+ runtime: TeamsRuntime,
113
+ msg: ChatMessage,
114
+ talonChatId: string,
115
+ ) => Promise<void>;
116
+
117
+ /** Exact-match table: the whole message, trimmed and lowercased, is the key. */
118
+ export const TEAMS_COMMANDS: Record<string, CommandHandler> = Object.assign(
119
+ Object.create(null) as Record<string, CommandHandler>,
120
+ {
121
+ "/reset": handleReset,
122
+ "/status": async (runtime, _msg, talonChatId) => {
123
+ await reply(runtime, buildStatusCard(runtime, talonChatId));
124
+ },
125
+ "/help": async (runtime) => {
126
+ await reply(runtime, buildAdaptiveCard(HELP_TEXT));
127
+ },
128
+ } satisfies Record<string, CommandHandler>,
129
+ );
130
+
131
+ /** Returns true when `msg` was a slash command and has been answered. */
132
+ export async function handleSlashCommand(
133
+ runtime: TeamsRuntime,
134
+ msg: ChatMessage,
135
+ talonChatId: string,
136
+ ): Promise<boolean> {
137
+ const handler = TEAMS_COMMANDS[msg.text.trim().toLowerCase()];
138
+ if (!handler) return false;
139
+ await handler(runtime, msg, talonChatId);
140
+ return true;
141
+ }