talon-agent 1.0.0 → 1.2.0
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/LICENSE +21 -0
- package/README.md +1 -0
- package/package.json +15 -11
- package/prompts/dream.md +7 -3
- package/prompts/heartbeat.md +30 -0
- package/prompts/identity.md +1 -0
- package/prompts/teams.md +3 -0
- package/prompts/telegram.md +1 -0
- package/src/__tests__/chat-settings.test.ts +108 -2
- package/src/__tests__/cleanup-registry.test.ts +58 -0
- package/src/__tests__/config.test.ts +118 -52
- package/src/__tests__/cron-store-extended.test.ts +661 -0
- package/src/__tests__/cron-store.test.ts +145 -11
- package/src/__tests__/daily-log.test.ts +224 -13
- package/src/__tests__/dispatcher.test.ts +424 -23
- package/src/__tests__/dream.test.ts +1028 -0
- package/src/__tests__/errors-extended.test.ts +428 -0
- package/src/__tests__/errors.test.ts +95 -3
- package/src/__tests__/fuzz.test.ts +87 -15
- package/src/__tests__/gateway-actions.test.ts +1174 -433
- package/src/__tests__/gateway-http.test.ts +210 -19
- package/src/__tests__/gateway-retry.test.ts +359 -0
- package/src/__tests__/gateway-withRetry-extended.test.ts +343 -0
- package/src/__tests__/graph.test.ts +830 -0
- package/src/__tests__/handlers-stream.test.ts +208 -0
- package/src/__tests__/handlers.test.ts +2539 -70
- package/src/__tests__/heartbeat.test.ts +364 -0
- package/src/__tests__/history-extended.test.ts +775 -0
- package/src/__tests__/history-persistence.test.ts +74 -19
- package/src/__tests__/history.test.ts +113 -79
- package/src/__tests__/integration.test.ts +43 -8
- package/src/__tests__/log-init.test.ts +129 -0
- package/src/__tests__/log.test.ts +23 -5
- package/src/__tests__/media-index.test.ts +317 -35
- package/src/__tests__/plugin.test.ts +314 -0
- package/src/__tests__/prompt-builder-extended.test.ts +296 -0
- package/src/__tests__/prompt-builder.test.ts +44 -9
- package/src/__tests__/sessions.test.ts +258 -4
- package/src/__tests__/storage-save-errors.test.ts +342 -0
- package/src/__tests__/teams-frontend.test.ts +526 -31
- package/src/__tests__/telegram-formatting.test.ts +82 -0
- package/src/__tests__/terminal-commands.test.ts +208 -1
- package/src/__tests__/terminal-renderer.test.ts +223 -0
- package/src/__tests__/time.test.ts +107 -0
- package/src/__tests__/workspace-migrate.test.ts +256 -0
- package/src/__tests__/workspace.test.ts +63 -1
- package/src/backend/claude-sdk/tools.ts +64 -18
- package/src/bootstrap.ts +14 -14
- package/src/cli.ts +440 -125
- package/src/core/cron.ts +20 -5
- package/src/core/dispatcher.ts +27 -9
- package/src/core/dream.ts +79 -24
- package/src/core/errors.ts +12 -2
- package/src/core/gateway-actions.ts +182 -46
- package/src/core/gateway.ts +93 -41
- package/src/core/heartbeat.ts +515 -0
- package/src/core/plugin.ts +1 -1
- package/src/core/prompt-builder.ts +1 -4
- package/src/core/pulse.ts +4 -3
- package/src/frontend/teams/actions.ts +3 -1
- package/src/frontend/teams/formatting.ts +47 -8
- package/src/frontend/teams/graph.ts +35 -11
- package/src/frontend/teams/index.ts +155 -57
- package/src/frontend/teams/tools.ts +4 -6
- package/src/frontend/telegram/actions.ts +358 -82
- package/src/frontend/telegram/admin.ts +162 -72
- package/src/frontend/telegram/callbacks.ts +16 -10
- package/src/frontend/telegram/commands.ts +37 -21
- package/src/frontend/telegram/formatting.ts +2 -4
- package/src/frontend/telegram/handlers.ts +262 -66
- package/src/frontend/telegram/index.ts +39 -14
- package/src/frontend/telegram/middleware.ts +14 -4
- package/src/frontend/telegram/userbot.ts +16 -4
- package/src/frontend/terminal/renderer.ts +1 -4
- package/src/index.ts +28 -4
- package/src/storage/chat-settings.ts +32 -9
- package/src/storage/cron-store.ts +53 -11
- package/src/storage/daily-log.ts +72 -19
- package/src/storage/history.ts +39 -21
- package/src/storage/media-index.ts +37 -12
- package/src/storage/sessions.ts +3 -2
- package/src/util/cleanup-registry.ts +34 -0
- package/src/util/config.ts +85 -23
- package/src/util/log.ts +47 -17
- package/src/util/paths.ts +10 -0
- package/src/util/time.ts +29 -6
- package/src/util/watchdog.ts +5 -1
- package/src/util/workspace.ts +51 -10
|
@@ -74,7 +74,9 @@ function loadTokens(): StoredTokens | null {
|
|
|
74
74
|
if (existsSync(TOKEN_FILE)) {
|
|
75
75
|
return JSON.parse(readFileSync(TOKEN_FILE, "utf-8"));
|
|
76
76
|
}
|
|
77
|
-
} catch {
|
|
77
|
+
} catch {
|
|
78
|
+
/* corrupt */
|
|
79
|
+
}
|
|
78
80
|
return null;
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -85,7 +87,10 @@ function saveTokens(tokens: StoredTokens): void {
|
|
|
85
87
|
|
|
86
88
|
// ── OAuth helpers ────────────────────────────────────────────────────────────
|
|
87
89
|
|
|
88
|
-
async function postForm(
|
|
90
|
+
async function postForm(
|
|
91
|
+
url: string,
|
|
92
|
+
params: Record<string, string>,
|
|
93
|
+
): Promise<Record<string, unknown>> {
|
|
89
94
|
const body = new URLSearchParams(params).toString();
|
|
90
95
|
const resp = await proxyFetch(url, {
|
|
91
96
|
method: "POST",
|
|
@@ -96,7 +101,9 @@ async function postForm(url: string, params: Record<string, string>): Promise<Re
|
|
|
96
101
|
return (await resp.json()) as Record<string, unknown>;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
|
-
async function refreshAccessToken(
|
|
104
|
+
async function refreshAccessToken(
|
|
105
|
+
refreshToken: string,
|
|
106
|
+
): Promise<StoredTokens | null> {
|
|
100
107
|
const data = (await postForm(`${AUTH_BASE}/token`, {
|
|
101
108
|
grant_type: "refresh_token",
|
|
102
109
|
client_id: CLIENT_ID,
|
|
@@ -105,7 +112,10 @@ async function refreshAccessToken(refreshToken: string): Promise<StoredTokens |
|
|
|
105
112
|
})) as TokenResponse;
|
|
106
113
|
|
|
107
114
|
if (data.error) {
|
|
108
|
-
logError(
|
|
115
|
+
logError(
|
|
116
|
+
"teams",
|
|
117
|
+
`Token refresh failed: ${data.error_description || data.error}`,
|
|
118
|
+
);
|
|
109
119
|
return null;
|
|
110
120
|
}
|
|
111
121
|
|
|
@@ -129,7 +139,10 @@ export async function deviceCodeAuth(): Promise<StoredTokens> {
|
|
|
129
139
|
console.log(` To sign in, open: ${dcResp.verification_uri}`);
|
|
130
140
|
console.log(` Enter code: ${dcResp.user_code}`);
|
|
131
141
|
console.log();
|
|
132
|
-
log(
|
|
142
|
+
log(
|
|
143
|
+
"teams",
|
|
144
|
+
`Device code auth: go to ${dcResp.verification_uri} and enter ${dcResp.user_code}`,
|
|
145
|
+
);
|
|
133
146
|
|
|
134
147
|
// Poll for token
|
|
135
148
|
const pollInterval = (dcResp.interval || 5) * 1000;
|
|
@@ -161,7 +174,9 @@ export async function deviceCodeAuth(): Promise<StoredTokens> {
|
|
|
161
174
|
continue;
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
throw new Error(
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Auth failed: ${tokenResp.error_description || tokenResp.error}`,
|
|
179
|
+
);
|
|
165
180
|
}
|
|
166
181
|
|
|
167
182
|
throw new Error("Device code auth timed out (15 minutes)");
|
|
@@ -183,7 +198,8 @@ export class GraphClient {
|
|
|
183
198
|
|
|
184
199
|
log("teams", "Refreshing access token...");
|
|
185
200
|
const refreshed = await refreshAccessToken(this.tokens.refreshToken);
|
|
186
|
-
if (!refreshed)
|
|
201
|
+
if (!refreshed)
|
|
202
|
+
throw new Error("Token refresh failed — re-authentication needed");
|
|
187
203
|
|
|
188
204
|
this.tokens = { ...this.tokens, ...refreshed };
|
|
189
205
|
saveTokens(this.tokens);
|
|
@@ -214,7 +230,9 @@ export class GraphClient {
|
|
|
214
230
|
|
|
215
231
|
// ── Chat discovery ─────────────────────────────────────────────────────
|
|
216
232
|
|
|
217
|
-
async listChats(): Promise<
|
|
233
|
+
async listChats(): Promise<
|
|
234
|
+
Array<{ id: string; topic: string | null; chatType: string }>
|
|
235
|
+
> {
|
|
218
236
|
const data = await this.graphGet("/me/chats?$top=50");
|
|
219
237
|
const chats = data.value as Array<Record<string, unknown>>;
|
|
220
238
|
return chats.map((c) => ({
|
|
@@ -259,9 +277,15 @@ export class GraphClient {
|
|
|
259
277
|
|
|
260
278
|
// ── Stored config ──────────────────────────────────────────────────────
|
|
261
279
|
|
|
262
|
-
getStoredChatId(): string | undefined {
|
|
263
|
-
|
|
264
|
-
|
|
280
|
+
getStoredChatId(): string | undefined {
|
|
281
|
+
return this.tokens.chatId;
|
|
282
|
+
}
|
|
283
|
+
getStoredChatTopic(): string | undefined {
|
|
284
|
+
return this.tokens.chatTopic;
|
|
285
|
+
}
|
|
286
|
+
getStoredUserId(): string | undefined {
|
|
287
|
+
return this.tokens.userId;
|
|
288
|
+
}
|
|
265
289
|
|
|
266
290
|
saveChatConfig(chatId: string, chatTopic: string, userId: string): void {
|
|
267
291
|
this.tokens.chatId = chatId;
|
|
@@ -15,7 +15,11 @@ import { log, logError } from "../../util/log.js";
|
|
|
15
15
|
import { deriveNumericChatId } from "../../util/chat-id.js";
|
|
16
16
|
import { createTeamsActionHandler } from "./actions.js";
|
|
17
17
|
import { splitTeamsMessage, buildAdaptiveCard } from "./formatting.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
initGraphClient,
|
|
20
|
+
type GraphClient,
|
|
21
|
+
type ChatMessage,
|
|
22
|
+
} from "./graph.js";
|
|
19
23
|
import { proxyFetch } from "./proxy-fetch.js";
|
|
20
24
|
|
|
21
25
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -36,10 +40,14 @@ export function createTeamsFrontend(
|
|
|
36
40
|
config: TalonConfig,
|
|
37
41
|
gateway: Gateway,
|
|
38
42
|
): TeamsFrontend {
|
|
39
|
-
const webhookUrl = (config as Record<string, unknown>)
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
+
const webhookUrl = (config as Record<string, unknown>)
|
|
44
|
+
.teamsWebhookUrl as string;
|
|
45
|
+
const botDisplayName =
|
|
46
|
+
((config as Record<string, unknown>).teamsBotDisplayName as string) || "";
|
|
47
|
+
const pollIntervalMs =
|
|
48
|
+
((config as Record<string, unknown>).teamsGraphPollMs as number) || 10_000;
|
|
49
|
+
const configChatTopic =
|
|
50
|
+
((config as Record<string, unknown>).teamsChatTopic as string) || "";
|
|
43
51
|
|
|
44
52
|
let graphClient: GraphClient | null = null;
|
|
45
53
|
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -48,7 +56,8 @@ export function createTeamsFrontend(
|
|
|
48
56
|
let polling = false;
|
|
49
57
|
|
|
50
58
|
const context: ContextManager = {
|
|
51
|
-
acquire: (chatId: number, stringId?: string) =>
|
|
59
|
+
acquire: (chatId: number, stringId?: string) =>
|
|
60
|
+
gateway.setContext(chatId, stringId),
|
|
52
61
|
release: (chatId: number) => gateway.clearContext(chatId),
|
|
53
62
|
getMessageCount: (chatId: number) => gateway.getMessageCount(chatId),
|
|
54
63
|
};
|
|
@@ -73,7 +82,10 @@ export function createTeamsFrontend(
|
|
|
73
82
|
});
|
|
74
83
|
}
|
|
75
84
|
} catch (err) {
|
|
76
|
-
logError(
|
|
85
|
+
logError(
|
|
86
|
+
"teams",
|
|
87
|
+
`sendMessage failed: ${err instanceof Error ? err.message : err}`,
|
|
88
|
+
);
|
|
77
89
|
}
|
|
78
90
|
},
|
|
79
91
|
|
|
@@ -110,7 +122,11 @@ export function createTeamsFrontend(
|
|
|
110
122
|
c.topic?.toLowerCase().includes(configChatTopic.toLowerCase()),
|
|
111
123
|
);
|
|
112
124
|
if (match) selectedChat = match;
|
|
113
|
-
else
|
|
125
|
+
else
|
|
126
|
+
log(
|
|
127
|
+
"teams",
|
|
128
|
+
`No chat matching topic "${configChatTopic}", using most recent`,
|
|
129
|
+
);
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
chatId = selectedChat.id;
|
|
@@ -119,7 +135,10 @@ export function createTeamsFrontend(
|
|
|
119
135
|
graphClient.saveChatConfig(chatId, topic, myUserId);
|
|
120
136
|
log("teams", `Configured chat: ${topic} [${selectedChat.chatType}]`);
|
|
121
137
|
} else {
|
|
122
|
-
log(
|
|
138
|
+
log(
|
|
139
|
+
"teams",
|
|
140
|
+
`Using chat: ${graphClient.getStoredChatTopic() || chatId}`,
|
|
141
|
+
);
|
|
123
142
|
}
|
|
124
143
|
|
|
125
144
|
// Seed lastSeenMessageId from current messages (don't process old messages)
|
|
@@ -130,7 +149,10 @@ export function createTeamsFrontend(
|
|
|
130
149
|
log("teams", `Seeded last message ID: ${lastSeenMessageId}`);
|
|
131
150
|
}
|
|
132
151
|
} catch (err) {
|
|
133
|
-
logError(
|
|
152
|
+
logError(
|
|
153
|
+
"teams",
|
|
154
|
+
`Failed to seed messages: ${err instanceof Error ? err.message : err}`,
|
|
155
|
+
);
|
|
134
156
|
}
|
|
135
157
|
},
|
|
136
158
|
|
|
@@ -142,7 +164,10 @@ export function createTeamsFrontend(
|
|
|
142
164
|
|
|
143
165
|
log("teams", "Teams frontend running");
|
|
144
166
|
log("teams", `Send: Power Automate webhook`);
|
|
145
|
-
log(
|
|
167
|
+
log(
|
|
168
|
+
"teams",
|
|
169
|
+
`Receive: Graph API chat polling every ${pollIntervalMs / 1000}s`,
|
|
170
|
+
);
|
|
146
171
|
|
|
147
172
|
// ── Poll loop ──────────────────────────────────────────────────────
|
|
148
173
|
async function poll(): Promise<void> {
|
|
@@ -173,7 +198,10 @@ export function createTeamsFrontend(
|
|
|
173
198
|
// Skip bot/workflow messages by display name (echo loop prevention).
|
|
174
199
|
// We do NOT filter by user ID — the authenticated user also sends
|
|
175
200
|
// real messages that Talon should respond to.
|
|
176
|
-
if (
|
|
201
|
+
if (
|
|
202
|
+
botDisplayName &&
|
|
203
|
+
msg.senderName.toLowerCase() === botDisplayName.toLowerCase()
|
|
204
|
+
) {
|
|
177
205
|
continue;
|
|
178
206
|
}
|
|
179
207
|
|
|
@@ -183,7 +211,8 @@ export function createTeamsFrontend(
|
|
|
183
211
|
// ── Slash commands ──
|
|
184
212
|
const trimmed = msg.text.trim().toLowerCase();
|
|
185
213
|
if (trimmed === "/reset") {
|
|
186
|
-
const { resetSession } =
|
|
214
|
+
const { resetSession } =
|
|
215
|
+
await import("../../storage/sessions.js");
|
|
187
216
|
const { clearHistory } = await import("../../storage/history.js");
|
|
188
217
|
resetSession(talonChatId);
|
|
189
218
|
clearHistory(talonChatId);
|
|
@@ -198,40 +227,85 @@ export function createTeamsFrontend(
|
|
|
198
227
|
continue;
|
|
199
228
|
}
|
|
200
229
|
if (trimmed === "/status") {
|
|
201
|
-
const { getSessionInfo } =
|
|
230
|
+
const { getSessionInfo } =
|
|
231
|
+
await import("../../storage/sessions.js");
|
|
202
232
|
const info = getSessionInfo(talonChatId);
|
|
203
233
|
const u = info.usage;
|
|
204
|
-
const cacheHit =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
234
|
+
const cacheHit =
|
|
235
|
+
u.totalInputTokens + u.totalCacheRead > 0
|
|
236
|
+
? Math.round(
|
|
237
|
+
(u.totalCacheRead /
|
|
238
|
+
(u.totalInputTokens + u.totalCacheRead)) *
|
|
239
|
+
100,
|
|
240
|
+
)
|
|
241
|
+
: 0;
|
|
242
|
+
const { getChatSettings } =
|
|
243
|
+
await import("../../storage/chat-settings.js");
|
|
244
|
+
const model = (
|
|
245
|
+
getChatSettings(talonChatId).model ?? (config.model as string)
|
|
246
|
+
).replace("claude-", "");
|
|
247
|
+
const avgMs =
|
|
248
|
+
info.turns > 0 ? Math.round(u.totalResponseMs / info.turns) : 0;
|
|
209
249
|
const contextUsed = u.lastPromptTokens;
|
|
210
|
-
const contextMax = model.includes("opus")
|
|
211
|
-
|
|
250
|
+
const contextMax = model.includes("opus")
|
|
251
|
+
? 1_000_000
|
|
252
|
+
: model.includes("sonnet")
|
|
253
|
+
? 1_000_000
|
|
254
|
+
: 200_000;
|
|
255
|
+
const contextPct =
|
|
256
|
+
contextMax > 0
|
|
257
|
+
? Math.round((contextUsed / contextMax) * 100)
|
|
258
|
+
: 0;
|
|
212
259
|
const card = {
|
|
213
260
|
type: "message",
|
|
214
|
-
attachments: [
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
261
|
+
attachments: [
|
|
262
|
+
{
|
|
263
|
+
contentType: "application/vnd.microsoft.card.adaptive",
|
|
264
|
+
contentUrl: null,
|
|
265
|
+
content: {
|
|
266
|
+
type: "AdaptiveCard",
|
|
267
|
+
$schema:
|
|
268
|
+
"http://adaptivecards.io/schemas/adaptive-card.json",
|
|
269
|
+
version: "1.4",
|
|
270
|
+
body: [
|
|
271
|
+
{
|
|
272
|
+
type: "TextBlock",
|
|
273
|
+
text: "**Session**",
|
|
274
|
+
wrap: true,
|
|
275
|
+
size: "Medium",
|
|
276
|
+
weight: "Bolder",
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
type: "FactSet",
|
|
280
|
+
facts: [
|
|
281
|
+
{ title: "Model", value: model },
|
|
282
|
+
{ title: "Turns", value: String(info.turns) },
|
|
283
|
+
{
|
|
284
|
+
title: "Context",
|
|
285
|
+
value: `${(contextUsed / 1000).toFixed(0)}K / ${(contextMax / 1000).toFixed(0)}K (${contextPct}%)`,
|
|
286
|
+
},
|
|
287
|
+
{ title: "Cache", value: `${cacheHit}% hit` },
|
|
288
|
+
{
|
|
289
|
+
title: "Input",
|
|
290
|
+
value: `${u.totalInputTokens.toLocaleString()} tokens`,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
title: "Output",
|
|
294
|
+
value: `${u.totalOutputTokens.toLocaleString()} tokens`,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
title: "Avg response",
|
|
298
|
+
value:
|
|
299
|
+
avgMs > 0
|
|
300
|
+
? `${(avgMs / 1000).toFixed(1)}s`
|
|
301
|
+
: "—",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
},
|
|
233
307
|
},
|
|
234
|
-
|
|
308
|
+
],
|
|
235
309
|
};
|
|
236
310
|
await proxyFetch(webhookUrl, {
|
|
237
311
|
method: "POST",
|
|
@@ -242,7 +316,8 @@ export function createTeamsFrontend(
|
|
|
242
316
|
continue;
|
|
243
317
|
}
|
|
244
318
|
if (trimmed === "/help") {
|
|
245
|
-
const helpText =
|
|
319
|
+
const helpText =
|
|
320
|
+
"**Commands:**\n- `/reset` — clear session & history\n- `/status` — session stats\n- `/help` — this message";
|
|
246
321
|
const card = buildAdaptiveCard(helpText);
|
|
247
322
|
await proxyFetch(webhookUrl, {
|
|
248
323
|
method: "POST",
|
|
@@ -253,7 +328,10 @@ export function createTeamsFrontend(
|
|
|
253
328
|
continue;
|
|
254
329
|
}
|
|
255
330
|
|
|
256
|
-
log(
|
|
331
|
+
log(
|
|
332
|
+
"teams",
|
|
333
|
+
`[${msg.senderName}]: ${msg.text.slice(0, 80)}${msg.text.length > 80 ? "..." : ""}`,
|
|
334
|
+
);
|
|
257
335
|
|
|
258
336
|
execute({
|
|
259
337
|
chatId: talonChatId,
|
|
@@ -266,23 +344,43 @@ export function createTeamsFrontend(
|
|
|
266
344
|
if (phase) log("teams", ` phase: ${phase}`);
|
|
267
345
|
},
|
|
268
346
|
onToolUse: (toolName, input) => {
|
|
269
|
-
const detail = (input.description ??
|
|
270
|
-
|
|
347
|
+
const detail = (input.description ??
|
|
348
|
+
input.command ??
|
|
349
|
+
input.action ??
|
|
350
|
+
input.query ??
|
|
351
|
+
input.url ??
|
|
352
|
+
input.name ??
|
|
353
|
+
"") as string;
|
|
354
|
+
log(
|
|
355
|
+
"teams",
|
|
356
|
+
` tool: ${toolName}${detail ? ` — ${String(detail).slice(0, 100)}` : ""}`,
|
|
357
|
+
);
|
|
271
358
|
},
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
359
|
+
})
|
|
360
|
+
.then(async (result) => {
|
|
361
|
+
// Only deliver messages sent via the send_message tool.
|
|
362
|
+
// Do NOT send fallback text — if Claude chose not to use send_message,
|
|
363
|
+
// it's either choosing not to respond or outputting internal reasoning
|
|
364
|
+
// that shouldn't be shown to users.
|
|
365
|
+
if (result.bridgeMessageCount === 0 && result.text?.trim()) {
|
|
366
|
+
log(
|
|
367
|
+
"teams",
|
|
368
|
+
`Suppressed fallback text (${result.text.length} chars) — no send_message tool used`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
.catch((err) => {
|
|
373
|
+
logError(
|
|
374
|
+
"teams",
|
|
375
|
+
`execute failed: ${err instanceof Error ? err.message : err}`,
|
|
376
|
+
);
|
|
377
|
+
});
|
|
283
378
|
}
|
|
284
379
|
} catch (err) {
|
|
285
|
-
logError(
|
|
380
|
+
logError(
|
|
381
|
+
"teams",
|
|
382
|
+
`Poll error: ${err instanceof Error ? err.message : err}`,
|
|
383
|
+
);
|
|
286
384
|
} finally {
|
|
287
385
|
polling = false;
|
|
288
386
|
}
|
|
@@ -77,16 +77,14 @@ Example: send_message_with_buttons(text="Choose:", rows=[[{"text":"Docs","url":"
|
|
|
77
77
|
)
|
|
78
78
|
.describe("Button rows"),
|
|
79
79
|
},
|
|
80
|
-
async (params) =>
|
|
80
|
+
async (params) =>
|
|
81
|
+
textResult(await callBridge("send_message_with_buttons", params)),
|
|
81
82
|
);
|
|
82
83
|
|
|
83
84
|
// ── Chat info ────────────────────────────────────────────────────────────────
|
|
84
85
|
|
|
85
|
-
server.tool(
|
|
86
|
-
"get_chat_info",
|
|
87
|
-
"Get info about the current chat.",
|
|
88
|
-
{},
|
|
89
|
-
async () => textResult(await callBridge("get_chat_info", {})),
|
|
86
|
+
server.tool("get_chat_info", "Get info about the current chat.", {}, async () =>
|
|
87
|
+
textResult(await callBridge("get_chat_info", {})),
|
|
90
88
|
);
|
|
91
89
|
|
|
92
90
|
// ── Web tools ────────────────────────────────────────────────────────────────
|