@geminixiang/mama 0.2.0-beta.2 → 0.2.0-beta.4
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/README.md +69 -41
- package/dist/adapter.d.ts +14 -4
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +8 -5
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +252 -98
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +83 -21
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +71 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +168 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +5 -21
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +148 -150
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +21 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +96 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +92 -56
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +3 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +16 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +89 -103
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +40 -14
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts +2 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +71 -142
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +3 -2
- package/dist/bindings.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +11 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +100 -16
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +61 -30
- package/dist/events.js.map +1 -1
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/{login.d.ts → login/index.d.ts} +1 -1
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +1 -1
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +5 -4
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1453 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/{link-token.d.ts → login/session.d.ts} +1 -1
- package/dist/login/session.d.ts.map +1 -0
- package/dist/{link-token.js → login/session.js} +1 -1
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +89 -19
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +17 -2
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +84 -5
- package/dist/provisioner.js.map +1 -1
- package/dist/session-policy.d.ts +13 -0
- package/dist/session-policy.d.ts.map +1 -0
- package/dist/session-policy.js +23 -0
- package/dist/session-policy.js.map +1 -0
- package/dist/session-store.d.ts +31 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +168 -6
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +11 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +11 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +795 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +416 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +16 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +38 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/store.d.ts +3 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +15 -35
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +2 -0
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +21 -3
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +11 -55
- package/dist/vault.js.map +1 -1
- package/package.json +7 -8
- package/dist/link-server.d.ts.map +0 -1
- package/dist/link-server.js +0 -899
- package/dist/link-server.js.map +0 -1
- package/dist/link-token.d.ts.map +0 -1
- package/dist/link-token.js.map +0 -1
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js.map +0 -1
package/dist/agent.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ActorExecutionResolver } from "./execution-resolver.js";
|
|
|
11
11
|
import * as log from "./log.js";
|
|
12
12
|
import { createExecutor } from "./sandbox.js";
|
|
13
13
|
import { addLifecycleBreadcrumb, metricAttributes } from "./sentry.js";
|
|
14
|
-
import {
|
|
14
|
+
import { extractSessionSuffix, extractSessionUuid, openManagedSession, } from "./session-store.js";
|
|
15
15
|
import { createMamaTools } from "./tools/index.js";
|
|
16
16
|
import * as Sentry from "@sentry/node";
|
|
17
17
|
const IMAGE_MIME_TYPES = {
|
|
@@ -24,6 +24,13 @@ const IMAGE_MIME_TYPES = {
|
|
|
24
24
|
function getImageMimeType(filename) {
|
|
25
25
|
return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""];
|
|
26
26
|
}
|
|
27
|
+
function buildThreadSessionName(message) {
|
|
28
|
+
const text = message?.text?.trim();
|
|
29
|
+
if (!text)
|
|
30
|
+
return undefined;
|
|
31
|
+
const userLabel = message?.userName || message?.user || "unknown";
|
|
32
|
+
return `[${userLabel}]: ${text}`;
|
|
33
|
+
}
|
|
27
34
|
async function getMemory(conversationDir) {
|
|
28
35
|
const parts = [];
|
|
29
36
|
// Read workspace-level memory (shared across all conversations)
|
|
@@ -123,8 +130,11 @@ function buildSystemPrompt(workspacePath, conversationId, conversationKind, curr
|
|
|
123
130
|
## Context
|
|
124
131
|
- For current date/time, use: date
|
|
125
132
|
- You have access to previous conversation context including tool results from prior turns.
|
|
126
|
-
- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).
|
|
127
|
-
-
|
|
133
|
+
- For older human-readable history beyond your context, search \`log.jsonl\` (contains user messages and your final responses, but not tool results).
|
|
134
|
+
- Structured session history with tool results lives in \`${conversationPath}/sessions/\`.
|
|
135
|
+
- The active top-level session is selected by \`${conversationPath}/sessions/current\`, which points to a timestamped \`.jsonl\` file in the same directory.
|
|
136
|
+
- Scoped/thread sessions use fixed files at \`${conversationPath}/sessions/<scope_id>.jsonl\` (for example \`${conversationPath}/sessions/1777386320.800769.jsonl\`).
|
|
137
|
+
- User messages include a \`[in-thread:TS]\` marker when sent from within a platform thread/reply (TS is the thread or parent message identifier). Without this marker, the message is a top-level conversation message.
|
|
128
138
|
|
|
129
139
|
${platform.formattingGuide}
|
|
130
140
|
|
|
@@ -144,7 +154,11 @@ ${workspacePath}/
|
|
|
144
154
|
├── skills/ # Global CLI tools you create
|
|
145
155
|
└── ${conversationId}/ # This conversation
|
|
146
156
|
├── MEMORY.md # Conversation-specific memory
|
|
147
|
-
├── log.jsonl #
|
|
157
|
+
├── log.jsonl # Human-readable message history (no tool results)
|
|
158
|
+
├── sessions/ # Structured session history used for context reconstruction
|
|
159
|
+
│ ├── current # Active top-level session pointer
|
|
160
|
+
│ ├── <timestamp>_<id>.jsonl # Top-level session files
|
|
161
|
+
│ └── <scope_id>.jsonl # Scoped thread/reply session files
|
|
148
162
|
├── attachments/ # User-shared files
|
|
149
163
|
├── scratch/ # Your working directory
|
|
150
164
|
└── skills/ # Conversation-specific tools
|
|
@@ -204,7 +218,7 @@ You can schedule events that wake you up at specific times or when external thin
|
|
|
204
218
|
All \`at\` timestamps must include offset (e.g., \`+01:00\`). Periodic events use IANA timezone names. The harness runs in ${Intl.DateTimeFormat().resolvedOptions().timeZone}. When users mention times without timezone, assume ${Intl.DateTimeFormat().resolvedOptions().timeZone}.
|
|
205
219
|
|
|
206
220
|
### Platform and Credential Routing
|
|
207
|
-
Set \`platform\` to the target bot platform (\`${platform.name}\` for this conversation).
|
|
221
|
+
Set \`platform\` to the target bot platform (\`${platform.name}\` for this conversation). Include it explicitly to avoid ambiguity.
|
|
208
222
|
|
|
209
223
|
Set \`userId\` to the platform userId of whoever asked for the event. When the event fires, tool execution routes using that user's vault selection in per-user modes. In \`container:<name>\`, events use the container's single shared vault.
|
|
210
224
|
|
|
@@ -251,7 +265,7 @@ ${memory}
|
|
|
251
265
|
|
|
252
266
|
## System Configuration Log
|
|
253
267
|
Maintain ${workspacePath}/SYSTEM.md to log all environment modifications:
|
|
254
|
-
- Installed packages (
|
|
268
|
+
- Installed packages (apt install, npm install, uv pip install)
|
|
255
269
|
- Environment variables set
|
|
256
270
|
- Config files modified (~/.gitconfig, cron jobs, etc.)
|
|
257
271
|
- Skill dependencies installed
|
|
@@ -261,7 +275,8 @@ Update this file whenever you modify the environment. On fresh container, read i
|
|
|
261
275
|
## Log Queries (for older history)
|
|
262
276
|
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","isBot":false}\`
|
|
263
277
|
The log contains user messages and your final responses (not tool calls/results).
|
|
264
|
-
|
|
278
|
+
Use \`log.jsonl\` for quick grep-style history. Use \`${conversationPath}/sessions/\` when you need structured turns, tool outputs, or branch lineage.
|
|
279
|
+
${isContainer ? "Install jq: apt-get install jq" : ""}
|
|
265
280
|
${isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
266
281
|
|
|
267
282
|
\`\`\`bash
|
|
@@ -273,6 +288,10 @@ grep -i "topic" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user
|
|
|
273
288
|
|
|
274
289
|
# Messages from specific user
|
|
275
290
|
grep '"userName":"mario"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'
|
|
291
|
+
|
|
292
|
+
# Inspect top-level session pointer and available session files
|
|
293
|
+
cat sessions/current
|
|
294
|
+
ls -1 sessions/
|
|
276
295
|
\`\`\`
|
|
277
296
|
|
|
278
297
|
## Tools
|
|
@@ -290,6 +309,12 @@ function truncate(text, maxLen) {
|
|
|
290
309
|
return text;
|
|
291
310
|
return `${text.substring(0, maxLen - 3)}...`;
|
|
292
311
|
}
|
|
312
|
+
// Tools whose output is interesting in the structured session log but too noisy
|
|
313
|
+
// to surface as a per-tool diagnostic to the user.
|
|
314
|
+
const QUIET_TOOLS = new Set(["read", "write", "edit"]);
|
|
315
|
+
// Cap raw tool output before handing it to adapters. Bash output can be MB; without
|
|
316
|
+
// this each adapter's splitter would fan it out into many sequential platform posts.
|
|
317
|
+
const TOOL_RESULT_DIAGNOSTIC_CAP = 8000;
|
|
293
318
|
function extractToolResultText(result) {
|
|
294
319
|
if (typeof result === "string") {
|
|
295
320
|
return result;
|
|
@@ -311,33 +336,6 @@ function extractToolResultText(result) {
|
|
|
311
336
|
}
|
|
312
337
|
return JSON.stringify(result);
|
|
313
338
|
}
|
|
314
|
-
function formatToolArgsForSlack(_toolName, args) {
|
|
315
|
-
const lines = [];
|
|
316
|
-
for (const [key, value] of Object.entries(args)) {
|
|
317
|
-
if (key === "label")
|
|
318
|
-
continue;
|
|
319
|
-
if (key === "path" && typeof value === "string") {
|
|
320
|
-
const offset = args.offset;
|
|
321
|
-
const limit = args.limit;
|
|
322
|
-
if (offset !== undefined && limit !== undefined) {
|
|
323
|
-
lines.push(`${value}:${offset}-${offset + limit}`);
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
lines.push(value);
|
|
327
|
-
}
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (key === "offset" || key === "limit")
|
|
331
|
-
continue;
|
|
332
|
-
if (typeof value === "string") {
|
|
333
|
-
lines.push(value);
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
lines.push(JSON.stringify(value));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return lines.join("\n");
|
|
340
|
-
}
|
|
341
339
|
// ============================================================================
|
|
342
340
|
// Agent runner
|
|
343
341
|
// ============================================================================
|
|
@@ -348,7 +346,7 @@ function formatToolArgsForSlack(_toolName, args) {
|
|
|
348
346
|
* Runner caching is handled by the caller (channelStates in main.ts).
|
|
349
347
|
* This is a stateless factory function.
|
|
350
348
|
*/
|
|
351
|
-
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, vaultManager, bindingStore, provisioner) {
|
|
349
|
+
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, bindingStore, provisioner) {
|
|
352
350
|
const agentConfig = loadAgentConfig(workspaceDir);
|
|
353
351
|
// Initialize logger with settings from config
|
|
354
352
|
log.initLogger({
|
|
@@ -363,6 +361,8 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
363
361
|
sandboxConfig.type === "image")
|
|
364
362
|
? new ActorExecutionResolver(sandboxConfig, vaultManager, bindingStore, provisioner)
|
|
365
363
|
: undefined;
|
|
364
|
+
// activeExecutor is replaced at the start of each run() call when executionResolver
|
|
365
|
+
// is present, so the stable `executor` wrapper always delegates to the latest resolved value.
|
|
366
366
|
let activeExecutor = executionResolver !== undefined
|
|
367
367
|
? createExecutor({ type: "host" })
|
|
368
368
|
: createExecutor(sandboxConfig);
|
|
@@ -391,52 +391,24 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
391
391
|
const memory = await getMemory(conversationDir);
|
|
392
392
|
const skills = loadMamaSkills(conversationDir, workspacePath);
|
|
393
393
|
const emptyPlatform = {
|
|
394
|
-
name: "
|
|
394
|
+
name: "chat",
|
|
395
395
|
formattingGuide: "",
|
|
396
396
|
channels: [],
|
|
397
397
|
users: [],
|
|
398
398
|
};
|
|
399
399
|
const systemPrompt = buildSystemPrompt(workspacePath, conversationId, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills);
|
|
400
|
-
// Create session manager and settings manager
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
const sessionDir = getChannelSessionDir(conversationDir);
|
|
400
|
+
// Create session manager and settings manager. Top-level/private sessions
|
|
401
|
+
// use the conversation's current pointer; scoped sessions use fixed files.
|
|
402
|
+
// Platform-specific branch/fork behavior is resolved before runner creation.
|
|
404
403
|
const isThread = sessionKey.includes(":");
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
contextFile = existing;
|
|
412
|
-
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
413
|
-
}
|
|
414
|
-
else {
|
|
415
|
-
const conversationSource = resolveChannelSessionFile(conversationDir);
|
|
416
|
-
if (conversationSource) {
|
|
417
|
-
try {
|
|
418
|
-
contextFile = forkThreadSessionFile(conversationSource, threadFile, conversationDir);
|
|
419
|
-
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
420
|
-
}
|
|
421
|
-
catch {
|
|
422
|
-
contextFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
423
|
-
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
contextFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
428
|
-
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
// Direct/shared session: normal resolve
|
|
434
|
-
contextFile = resolveManagedSessionFile(sessionDir, conversationDir);
|
|
435
|
-
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
404
|
+
const rootTs = extractSessionSuffix(sessionKey);
|
|
405
|
+
const { sessionDir, contextFile, threadRootMessage } = sessionScope;
|
|
406
|
+
const sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
407
|
+
const threadSessionName = buildThreadSessionName(threadRootMessage);
|
|
408
|
+
if (isThread && threadSessionName && sessionManager.getSessionName() !== threadSessionName) {
|
|
409
|
+
sessionManager.appendSessionInfo(threadSessionName);
|
|
436
410
|
}
|
|
437
411
|
const sessionUuid = extractSessionUuid(contextFile);
|
|
438
|
-
// Used for Slack thread filtering — for non-Slack platforms this is effectively a no-op
|
|
439
|
-
const rootTs = extractSessionSuffix(sessionKey);
|
|
440
412
|
const settingsManager = createMamaSettingsManager(join(conversationDir, ".."));
|
|
441
413
|
// Create AuthStorage and ModelRegistry
|
|
442
414
|
// Auth stored outside workspace so agent can't access it
|
|
@@ -534,8 +506,6 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
534
506
|
...baseAttrs,
|
|
535
507
|
});
|
|
536
508
|
log.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args);
|
|
537
|
-
// Tool labels are omitted from the main message to reduce Slack noise.
|
|
538
|
-
// Tool execution details are still posted to the thread (see tool_execution_end).
|
|
539
509
|
}
|
|
540
510
|
else if (event.type === "tool_execution_end") {
|
|
541
511
|
const agentEvent = event;
|
|
@@ -569,24 +539,16 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
569
539
|
else {
|
|
570
540
|
log.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);
|
|
571
541
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (argsFormatted)
|
|
583
|
-
threadMessage += `\`\`\`\n${argsFormatted}\n\`\`\`\n`;
|
|
584
|
-
threadMessage += `*Result:*\n\`\`\`\n${resultStr}\n\`\`\``;
|
|
585
|
-
// Only post thread details for tools with meaningful output (bash, attach).
|
|
586
|
-
// Skip read/write/edit to reduce Slack noise — their results are in the log.
|
|
587
|
-
const quietTools = new Set(["read", "write", "edit"]);
|
|
588
|
-
if (!quietTools.has(agentEvent.toolName)) {
|
|
589
|
-
queue.enqueueMessage(threadMessage, "thread", "tool result thread", false);
|
|
542
|
+
if (!QUIET_TOOLS.has(agentEvent.toolName)) {
|
|
543
|
+
const toolResult = {
|
|
544
|
+
toolName: agentEvent.toolName,
|
|
545
|
+
label: pending?.args ? pending.args.label : undefined,
|
|
546
|
+
args: pending?.args,
|
|
547
|
+
result: truncate(resultStr, TOOL_RESULT_DIAGNOSTIC_CAP),
|
|
548
|
+
isError: agentEvent.isError,
|
|
549
|
+
durationMs,
|
|
550
|
+
};
|
|
551
|
+
queue.enqueue(() => responseCtx.respondToolResult(toolResult), "tool result diagnostic");
|
|
590
552
|
}
|
|
591
553
|
if (agentEvent.isError) {
|
|
592
554
|
queue.enqueue(() => responseCtx.respond(`_Error: ${truncate(resultStr, 200)}_`), "tool error");
|
|
@@ -678,16 +640,12 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
678
640
|
const text = textParts.join("\n");
|
|
679
641
|
for (const thinking of thinkingParts) {
|
|
680
642
|
log.logThinking(logCtx, thinking);
|
|
681
|
-
queue.
|
|
682
|
-
queue.
|
|
643
|
+
queue.enqueue(() => responseCtx.respond(`_${thinking}_`), "thinking main");
|
|
644
|
+
queue.enqueue(() => responseCtx.respondDiagnostic(`_${thinking}_`), "thinking diagnostic");
|
|
683
645
|
}
|
|
684
646
|
if (text.trim()) {
|
|
685
647
|
log.logResponse(logCtx, text);
|
|
686
|
-
queue.
|
|
687
|
-
// Only overflow to thread for texts that will be truncated in main
|
|
688
|
-
if (text.length > SLACK_MAX_LENGTH) {
|
|
689
|
-
queue.enqueueMessage(text, "thread", "response thread", false);
|
|
690
|
-
}
|
|
648
|
+
queue.enqueue(() => responseCtx.respond(text), "response main");
|
|
691
649
|
}
|
|
692
650
|
}
|
|
693
651
|
}
|
|
@@ -710,23 +668,6 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
710
668
|
queue.enqueue(() => responseCtx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`), "retry");
|
|
711
669
|
}
|
|
712
670
|
});
|
|
713
|
-
// Message limit constant
|
|
714
|
-
const SLACK_MAX_LENGTH = 40000;
|
|
715
|
-
const splitForSlack = (text) => {
|
|
716
|
-
if (text.length <= SLACK_MAX_LENGTH)
|
|
717
|
-
return [text];
|
|
718
|
-
const parts = [];
|
|
719
|
-
let remaining = text;
|
|
720
|
-
let partNum = 1;
|
|
721
|
-
while (remaining.length > 0) {
|
|
722
|
-
const chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);
|
|
723
|
-
remaining = remaining.substring(SLACK_MAX_LENGTH - 50);
|
|
724
|
-
const suffix = remaining.length > 0 ? `\n_(continued ${partNum}...)_` : "";
|
|
725
|
-
parts.push(chunk + suffix);
|
|
726
|
-
partNum++;
|
|
727
|
-
}
|
|
728
|
-
return parts;
|
|
729
|
-
};
|
|
730
671
|
return {
|
|
731
672
|
async run(message, responseCtx, platform) {
|
|
732
673
|
// Extract conversationId from sessionKey (format: "conversationId:rootTs" or just "conversationId")
|
|
@@ -769,6 +710,11 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
769
710
|
conversationId,
|
|
770
711
|
conversationKind: message.conversationKind,
|
|
771
712
|
userId: message.userId,
|
|
713
|
+
sessionKey: message.sessionKey,
|
|
714
|
+
// For Slack scheduled events, preserve thread targeting only when the
|
|
715
|
+
// request was created inside an existing thread. Top-level reminders
|
|
716
|
+
// should come back as top-level messages.
|
|
717
|
+
threadTs: message.threadTs,
|
|
772
718
|
});
|
|
773
719
|
// Set up file upload function
|
|
774
720
|
setUploadFunction(async (filePath, title) => {
|
|
@@ -806,11 +752,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
806
752
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
807
753
|
log.logWarning(`API error (${errorContext})`, errMsg);
|
|
808
754
|
try {
|
|
809
|
-
|
|
810
|
-
const errParts = splitForSlack(`_Error: ${errMsg}_`);
|
|
811
|
-
for (const part of errParts) {
|
|
812
|
-
await responseCtx.respondInThread(part);
|
|
813
|
-
}
|
|
755
|
+
await responseCtx.respondDiagnostic(`Error: ${errMsg}`, { style: "error" });
|
|
814
756
|
}
|
|
815
757
|
catch {
|
|
816
758
|
// Ignore
|
|
@@ -818,12 +760,6 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
818
760
|
}
|
|
819
761
|
});
|
|
820
762
|
},
|
|
821
|
-
enqueueMessage(text, target, errorContext, _doLog = true) {
|
|
822
|
-
const parts = splitForSlack(text);
|
|
823
|
-
for (const part of parts) {
|
|
824
|
-
this.enqueue(() => target === "main" ? responseCtx.respond(part) : responseCtx.respondInThread(part), errorContext);
|
|
825
|
-
}
|
|
826
|
-
},
|
|
827
763
|
};
|
|
828
764
|
// Log context info
|
|
829
765
|
log.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);
|
|
@@ -887,11 +823,9 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
887
823
|
if (runState.stopReason === "error" && runState.errorMessage) {
|
|
888
824
|
try {
|
|
889
825
|
await responseCtx.replaceResponse("_Sorry, something went wrong_");
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
await responseCtx.respondInThread(part);
|
|
894
|
-
}
|
|
826
|
+
await responseCtx.respondDiagnostic(`Error: ${runState.errorMessage}`, {
|
|
827
|
+
style: "error",
|
|
828
|
+
});
|
|
895
829
|
}
|
|
896
830
|
catch (err) {
|
|
897
831
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -919,10 +853,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
919
853
|
}
|
|
920
854
|
else if (finalText.trim()) {
|
|
921
855
|
try {
|
|
922
|
-
|
|
923
|
-
? `${finalText.substring(0, SLACK_MAX_LENGTH - 50)}\n\n_(see thread for full response)_`
|
|
924
|
-
: finalText;
|
|
925
|
-
await responseCtx.replaceResponse(mainText);
|
|
856
|
+
await responseCtx.replaceResponse(finalText);
|
|
926
857
|
}
|
|
927
858
|
catch (err) {
|
|
928
859
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -975,12 +906,10 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
|
|
|
975
906
|
attributes: runMetricAttributes,
|
|
976
907
|
});
|
|
977
908
|
const summary = log.logUsageSummary(runState.logCtx, runState.totalUsage, contextTokens, contextWindow);
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
runState.queue.enqueue(() => responseCtx.respondInThread(part, { style: "muted" }), "usage summary");
|
|
909
|
+
if (platform.diagnostics?.showUsageSummary === true) {
|
|
910
|
+
runState.queue.enqueue(() => responseCtx.respondDiagnostic(summary, { style: "muted" }), "usage summary");
|
|
911
|
+
await queueChain;
|
|
982
912
|
}
|
|
983
|
-
await queueChain;
|
|
984
913
|
}
|
|
985
914
|
// Clear run state
|
|
986
915
|
runState.responseCtx = null;
|