@geminixiang/mama 0.2.0-beta.0 → 0.2.0-beta.10
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 +171 -334
- package/dist/adapter.d.ts +36 -10
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +10 -5
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +349 -114
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +102 -31
- 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 +29 -22
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +620 -186
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +22 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +97 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +136 -71
- 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/slack/tools/attach.d.ts +1 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +2 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +190 -123
- 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 +57 -59
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/html.d.ts +3 -0
- package/dist/adapters/telegram/html.d.ts.map +1 -0
- package/dist/adapters/telegram/html.js +98 -0
- package/dist/adapters/telegram/html.js.map +1 -0
- package/dist/agent.d.ts +9 -10
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +645 -555
- package/dist/agent.js.map +1 -1
- package/dist/commands/auto-reply.d.ts +16 -0
- package/dist/commands/auto-reply.d.ts.map +1 -0
- package/dist/commands/auto-reply.js +69 -0
- package/dist/commands/auto-reply.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +76 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/model.d.ts +14 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +112 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +14 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +88 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts +5 -0
- package/dist/commands/session-view.d.ts.map +1 -0
- package/dist/commands/session-view.js +62 -0
- package/dist/commands/session-view.js.map +1 -0
- package/dist/commands/types.d.ts +41 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/utils.d.ts +8 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +14 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/config.d.ts +53 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +320 -55
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +10 -42
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +15 -128
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +16 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +127 -58
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +24 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +115 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/file-guards.d.ts +6 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +48 -0
- package/dist/file-guards.js.map +1 -0
- 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/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +3 -3
- package/dist/instrument.js.map +1 -1
- package/dist/log.d.ts +3 -7
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +20 -45
- package/dist/log.js.map +1 -1
- package/dist/login/index.d.ts +41 -0
- package/dist/login/index.d.ts.map +1 -0
- package/dist/login/index.js +202 -0
- package/dist/login/index.js.map +1 -0
- package/dist/login/portal.d.ts +19 -0
- 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/login/session.d.ts +33 -0
- package/dist/login/session.d.ts.map +1 -0
- package/dist/login/session.js +68 -0
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +229 -264
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +79 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +437 -0
- package/dist/provisioner.js.map +1 -0
- package/dist/runtime/conversation-orchestrator.d.ts +42 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +150 -0
- package/dist/runtime/conversation-orchestrator.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/session-runtime.d.ts +27 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +211 -0
- package/dist/runtime/session-runtime.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts +15 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -0
- package/dist/sandbox/cloudflare.js +137 -0
- package/dist/sandbox/cloudflare.js.map +1 -0
- package/dist/sandbox/container.d.ts +16 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +126 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/errors.d.ts +6 -0
- package/dist/sandbox/errors.d.ts.map +1 -0
- package/dist/sandbox/errors.js +11 -0
- package/dist/sandbox/errors.js.map +1 -0
- package/dist/sandbox/firecracker.d.ts +17 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +212 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +11 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +89 -0
- package/dist/sandbox/host.js.map +1 -0
- package/dist/sandbox/image.d.ts +5 -0
- package/dist/sandbox/image.d.ts.map +1 -0
- package/dist/sandbox/image.js +30 -0
- package/dist/sandbox/image.js.map +1 -0
- package/dist/sandbox/index.d.ts +22 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +54 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/path-context.d.ts +4 -0
- package/dist/sandbox/path-context.d.ts.map +1 -0
- package/dist/sandbox/path-context.js +20 -0
- package/dist/sandbox/path-context.js.map +1 -0
- package/dist/sandbox/types.d.ts +67 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +2 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/utils.d.ts +4 -0
- package/dist/sandbox/utils.d.ts.map +1 -0
- package/dist/sandbox/utils.js +51 -0
- package/dist/sandbox/utils.js.map +1 -0
- package/dist/sandbox.d.ts +1 -39
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +1 -286
- package/dist/sandbox.js.map +1 -1
- package/dist/sentry.d.ts +2 -2
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +6 -4
- package/dist/sentry.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 +35 -8
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +182 -23
- 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 +16 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +1742 -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 +427 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +18 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +39 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/store.d.ts +4 -7
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +26 -52
- package/dist/store.js.map +1 -1
- package/dist/tool-diagnostics.d.ts +2 -0
- package/dist/tool-diagnostics.d.ts.map +1 -0
- package/dist/tool-diagnostics.js +7 -0
- package/dist/tool-diagnostics.js.map +1 -0
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +62 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +138 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +8 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/trigger.d.ts +31 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +98 -0
- package/dist/trigger.js.map +1 -0
- package/dist/ui-copy.d.ts +12 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +36 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +4 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +16 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +72 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +264 -0
- package/dist/vault.js.map +1 -0
- package/package.json +16 -13
package/dist/agent.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { Agent } from "@
|
|
2
|
-
import { getModel } from "@
|
|
3
|
-
import { AgentSession, AuthStorage, convertToLlm, DefaultResourceLoader, formatSkillsForPrompt, loadSkillsFromDir, ModelRegistry, } from "@
|
|
1
|
+
import { Agent } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { getModel } from "@earendil-works/pi-ai";
|
|
3
|
+
import { AgentSession, AuthStorage, convertToLlm, DefaultResourceLoader, formatSkillsForPrompt, getAgentDir, loadSkillsFromDir, ModelRegistry, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { existsSync, readFileSync } from "fs";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
6
6
|
import { homedir } from "os";
|
|
7
|
-
import { join } from "path";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { join, posix } from "path";
|
|
8
|
+
import { loadAgentConfigForConversation } from "./config.js";
|
|
9
|
+
import { ActorExecutionResolver } from "./execution-resolver.js";
|
|
10
10
|
import * as log from "./log.js";
|
|
11
|
-
import { createExecutor } from "./sandbox.js";
|
|
11
|
+
import { createExecutor, } from "./sandbox.js";
|
|
12
|
+
import { createMountedRuntimePathContext } from "./sandbox/path-context.js";
|
|
12
13
|
import { addLifecycleBreadcrumb, metricAttributes } from "./sentry.js";
|
|
13
|
-
import {
|
|
14
|
+
import { extractSessionUuid, openManagedSession, } from "./session-store.js";
|
|
15
|
+
import { shouldSurfaceToolDiagnostic } from "./tool-diagnostics.js";
|
|
14
16
|
import { createMamaTools } from "./tools/index.js";
|
|
15
17
|
import * as Sentry from "@sentry/node";
|
|
16
18
|
const IMAGE_MIME_TYPES = {
|
|
@@ -23,10 +25,17 @@ const IMAGE_MIME_TYPES = {
|
|
|
23
25
|
function getImageMimeType(filename) {
|
|
24
26
|
return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""];
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
function buildThreadSessionName(message) {
|
|
29
|
+
const text = message?.text?.trim();
|
|
30
|
+
if (!text)
|
|
31
|
+
return undefined;
|
|
32
|
+
const userLabel = message?.userName || message?.user || "unknown";
|
|
33
|
+
return `[${userLabel}]: ${text}`;
|
|
34
|
+
}
|
|
35
|
+
async function getMemory(conversationDir) {
|
|
27
36
|
const parts = [];
|
|
28
|
-
// Read workspace-level memory (shared across all
|
|
29
|
-
const workspaceMemoryPath = join(
|
|
37
|
+
// Read workspace-level memory (shared across all conversations)
|
|
38
|
+
const workspaceMemoryPath = join(conversationDir, "..", "MEMORY.md");
|
|
30
39
|
if (existsSync(workspaceMemoryPath)) {
|
|
31
40
|
try {
|
|
32
41
|
const content = (await readFile(workspaceMemoryPath, "utf-8")).trim();
|
|
@@ -38,17 +47,17 @@ async function getMemory(channelDir) {
|
|
|
38
47
|
log.logWarning("Failed to read workspace memory", `${workspaceMemoryPath}: ${error}`);
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
|
-
// Read
|
|
42
|
-
const
|
|
43
|
-
if (existsSync(
|
|
50
|
+
// Read conversation-specific memory
|
|
51
|
+
const conversationMemoryPath = join(conversationDir, "MEMORY.md");
|
|
52
|
+
if (existsSync(conversationMemoryPath)) {
|
|
44
53
|
try {
|
|
45
|
-
const content = (await readFile(
|
|
54
|
+
const content = (await readFile(conversationMemoryPath, "utf-8")).trim();
|
|
46
55
|
if (content) {
|
|
47
|
-
parts.push(`###
|
|
56
|
+
parts.push(`### Conversation-Specific Memory\n${content}`);
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
catch (error) {
|
|
51
|
-
log.logWarning("Failed to read
|
|
60
|
+
log.logWarning("Failed to read conversation memory", `${conversationMemoryPath}: ${error}`);
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
63
|
if (parts.length === 0) {
|
|
@@ -56,12 +65,12 @@ async function getMemory(channelDir) {
|
|
|
56
65
|
}
|
|
57
66
|
return parts.join("\n\n");
|
|
58
67
|
}
|
|
59
|
-
function loadMamaSkills(
|
|
68
|
+
function loadMamaSkills(conversationDir, workspacePath) {
|
|
60
69
|
const skillMap = new Map();
|
|
61
|
-
//
|
|
70
|
+
// conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)
|
|
62
71
|
// hostWorkspacePath is the parent directory on host
|
|
63
72
|
// workspacePath is the container path (e.g., /workspace)
|
|
64
|
-
const hostWorkspacePath = join(
|
|
73
|
+
const hostWorkspacePath = join(conversationDir, "..");
|
|
65
74
|
// Helper to translate host paths to container paths
|
|
66
75
|
const translatePath = (hostPath) => {
|
|
67
76
|
if (hostPath.startsWith(hostWorkspacePath)) {
|
|
@@ -77,19 +86,87 @@ function loadMamaSkills(channelDir, workspacePath) {
|
|
|
77
86
|
skill.baseDir = translatePath(skill.baseDir);
|
|
78
87
|
skillMap.set(skill.name, skill);
|
|
79
88
|
}
|
|
80
|
-
// Load
|
|
81
|
-
const
|
|
82
|
-
for (const skill of loadSkillsFromDir({ dir:
|
|
89
|
+
// Load conversation-specific skills (override workspace skills on collision)
|
|
90
|
+
const conversationSkillsDir = join(conversationDir, "skills");
|
|
91
|
+
for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: "channel" }).skills) {
|
|
83
92
|
skill.filePath = translatePath(skill.filePath);
|
|
84
93
|
skill.baseDir = translatePath(skill.baseDir);
|
|
85
94
|
skillMap.set(skill.name, skill);
|
|
86
95
|
}
|
|
87
96
|
return Array.from(skillMap.values());
|
|
88
97
|
}
|
|
89
|
-
function
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
98
|
+
function buildRuntimePaths(runtimeWorkspaceRoot, conversationId) {
|
|
99
|
+
const workspaceRoot = runtimeWorkspaceRoot.replace(/\/+$/, "") || "/";
|
|
100
|
+
const conversationPath = posix.join(workspaceRoot, conversationId);
|
|
101
|
+
return {
|
|
102
|
+
workspaceRoot,
|
|
103
|
+
conversationPath,
|
|
104
|
+
scratchPath: posix.join(conversationPath, "scratch"),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function buildEnvDescription(sandboxType, workspaceRoot) {
|
|
108
|
+
switch (sandboxType) {
|
|
109
|
+
case "image":
|
|
110
|
+
return `You are running inside a managed per-user container.
|
|
111
|
+
- Runtime workspace root: ${workspaceRoot}
|
|
112
|
+
- Bash commands start in: ${workspaceRoot}
|
|
113
|
+
- Install tools with the image's package manager
|
|
114
|
+
- Your changes persist for this user's container until it is recreated`;
|
|
115
|
+
case "container":
|
|
116
|
+
return `You are running inside a shared container.
|
|
117
|
+
- Runtime workspace root: ${workspaceRoot}
|
|
118
|
+
- Bash commands start in: ${workspaceRoot}
|
|
119
|
+
- Install tools with the container's package manager
|
|
120
|
+
- Your changes persist across sessions`;
|
|
121
|
+
case "firecracker":
|
|
122
|
+
return `You are running inside a Firecracker microVM.
|
|
123
|
+
- Runtime workspace root: ${workspaceRoot}
|
|
124
|
+
- Use cd or absolute paths; project files are under ${workspaceRoot}
|
|
125
|
+
- Install tools with: apt-get install <package> (Debian-based)
|
|
126
|
+
- Your changes persist across sessions`;
|
|
127
|
+
case "cloudflare":
|
|
128
|
+
return `You are running through a Cloudflare Sandbox bridge.
|
|
129
|
+
- Runtime workspace root: ${workspaceRoot}
|
|
130
|
+
- Bash commands start in: ${workspaceRoot}
|
|
131
|
+
- Your commands run in a remote container managed by Cloudflare
|
|
132
|
+
- Important: the remote filesystem is not automatically synced back to the host workspace`;
|
|
133
|
+
default:
|
|
134
|
+
return `You are running directly on the host machine.
|
|
135
|
+
- Runtime workspace root: ${workspaceRoot}
|
|
136
|
+
- Bash commands start in: ${process.cwd()}
|
|
137
|
+
- Be careful with system modifications`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function buildEventFilesystemInstructions(sandboxType, workspaceRoot) {
|
|
141
|
+
if (sandboxType === "host" || sandboxType === "container" || sandboxType === "image") {
|
|
142
|
+
return `Events live in the host-side mama control plane and are mounted at \`${workspaceRoot}/events/\` in this runtime.
|
|
143
|
+
|
|
144
|
+
Prefer the \`event\` tool over manually writing JSON files; it fills \`platform\`, \`conversationId\`, \`conversationKind\`, and \`userId\` for the current conversation automatically.
|
|
145
|
+
|
|
146
|
+
### Creating Events Manually
|
|
147
|
+
Only do this when you need to create events from a script. Use unique filenames to avoid overwriting existing events. Include a timestamp or random suffix:
|
|
148
|
+
\`\`\`bash
|
|
149
|
+
cat > ${workspaceRoot}/events/dentist-reminder-$(date +%s).json << 'EOF'
|
|
150
|
+
{"type": "one-shot", "platform": "<platform>", "conversationId": "<conversationId>", "conversationKind": "<direct|shared>", "userId": "<requester userId>", "text": "Dentist tomorrow", "at": "2025-12-14T09:00:00+01:00"}
|
|
151
|
+
EOF
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
### Managing Events
|
|
155
|
+
- List: \`ls ${workspaceRoot}/events/\`
|
|
156
|
+
- View: \`cat ${workspaceRoot}/events/foo.json\`
|
|
157
|
+
- Delete/cancel: \`rm ${workspaceRoot}/events/foo.json\``;
|
|
158
|
+
}
|
|
159
|
+
return `Events live in the host-side mama control plane, not necessarily in this runtime filesystem.
|
|
160
|
+
|
|
161
|
+
Use the \`event\` tool to create events. It writes to the correct host-side events directory and fills \`platform\`, \`conversationId\`, \`conversationKind\`, and \`userId\` for the current conversation automatically.
|
|
162
|
+
|
|
163
|
+
Do not create event files with bash in \`${workspaceRoot}/events/\` from this sandbox unless you have explicitly verified that path is mounted back to the host-side mama events directory.`;
|
|
164
|
+
}
|
|
165
|
+
function buildSystemPrompt(workspacePath, conversationId, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, isSyntheticEvent = false) {
|
|
166
|
+
const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, conversationId);
|
|
167
|
+
const sandboxType = sandboxConfig.type;
|
|
168
|
+
const isContainerLike = sandboxType === "container" || sandboxType === "image";
|
|
169
|
+
const isFirecracker = sandboxType === "firecracker";
|
|
93
170
|
// Format channel mappings
|
|
94
171
|
const channelMappings = platform.channels.length > 0
|
|
95
172
|
? platform.channels.map((c) => `${c.id}\t#${c.name}`).join("\n")
|
|
@@ -98,27 +175,29 @@ function buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, plat
|
|
|
98
175
|
const userMappings = platform.users.length > 0
|
|
99
176
|
? platform.users.map((u) => `${u.id}\t@${u.userName}\t${u.displayName}`).join("\n")
|
|
100
177
|
: "(no users loaded)";
|
|
101
|
-
const envDescription =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
- Be careful with system modifications`;
|
|
178
|
+
const envDescription = buildEnvDescription(sandboxType, workspaceRoot);
|
|
179
|
+
const eventFilesystemInstructions = buildEventFilesystemInstructions(sandboxType, workspaceRoot);
|
|
180
|
+
const syntheticEventInstructions = isSyntheticEvent
|
|
181
|
+
? `
|
|
182
|
+
## Synthetic Event Mode
|
|
183
|
+
- You are handling a scheduled/background event, not opening a brand new chat with a stranger.
|
|
184
|
+
- Treat the incoming user message as a self-contained task prepared by an earlier run.
|
|
185
|
+
- Complete the task directly. Avoid generic greetings, self-introductions, or boilerplate offers to help.
|
|
186
|
+
- For reminders/follow-ups, prefer a short direct response that sounds like a continuation of prior intent.
|
|
187
|
+
- If the event text includes tone, brevity, or language instructions, follow them literally.
|
|
188
|
+
`
|
|
189
|
+
: "";
|
|
114
190
|
return `You are mama, a ${platform.name} bot assistant. Be concise. No emojis.
|
|
115
191
|
|
|
116
192
|
## Context
|
|
117
193
|
- For current date/time, use: date
|
|
118
194
|
- You have access to previous conversation context including tool results from prior turns.
|
|
119
|
-
- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).
|
|
120
|
-
-
|
|
121
|
-
|
|
195
|
+
- For older human-readable history beyond your context, search \`log.jsonl\` (contains user messages and your final responses, but not tool results).
|
|
196
|
+
- Structured session history with tool results lives in \`${conversationPath}/sessions/\`.
|
|
197
|
+
- The active top-level session is selected by \`${conversationPath}/sessions/current\`, which points to a timestamped \`.jsonl\` file in the same directory.
|
|
198
|
+
- Scoped/thread sessions use fixed files at \`${conversationPath}/sessions/<scope_id>.jsonl\` (for example \`${conversationPath}/sessions/1777386320.800769.jsonl\`).
|
|
199
|
+
- 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.
|
|
200
|
+
${syntheticEventInstructions}
|
|
122
201
|
${platform.formattingGuide}
|
|
123
202
|
|
|
124
203
|
## Platform IDs
|
|
@@ -130,23 +209,29 @@ When mentioning users, use <@username> format (e.g., <@mario>).
|
|
|
130
209
|
|
|
131
210
|
## Environment
|
|
132
211
|
${envDescription}
|
|
212
|
+
- Default place for clones, downloads, and experiments: ${scratchPath}
|
|
213
|
+
- Do not use host-only paths unless you are running in host mode and verified they exist.
|
|
133
214
|
|
|
134
215
|
## Workspace Layout
|
|
135
|
-
${
|
|
136
|
-
├── MEMORY.md # Global memory (all
|
|
216
|
+
${workspaceRoot}/
|
|
217
|
+
├── MEMORY.md # Global memory (all conversations)
|
|
137
218
|
├── skills/ # Global CLI tools you create
|
|
138
|
-
└── ${
|
|
139
|
-
├── MEMORY.md #
|
|
140
|
-
├── log.jsonl #
|
|
219
|
+
└── ${conversationId}/ # This conversation
|
|
220
|
+
├── MEMORY.md # Conversation-specific memory
|
|
221
|
+
├── log.jsonl # Human-readable message history (no tool results)
|
|
222
|
+
├── sessions/ # Structured session history used for context reconstruction
|
|
223
|
+
│ ├── current # Active top-level session pointer
|
|
224
|
+
│ ├── <timestamp>_<id>.jsonl # Top-level session files
|
|
225
|
+
│ └── <scope_id>.jsonl # Scoped thread/reply session files
|
|
141
226
|
├── attachments/ # User-shared files
|
|
142
|
-
├── scratch/ #
|
|
143
|
-
└── skills/ #
|
|
227
|
+
├── scratch/ # Working directory for clones/downloads/experiments: ${scratchPath}
|
|
228
|
+
└── skills/ # Conversation-specific tools
|
|
144
229
|
|
|
145
230
|
## Skills (Custom CLI Tools)
|
|
146
231
|
You can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).
|
|
147
232
|
|
|
148
233
|
### Creating Skills
|
|
149
|
-
Store in \`${
|
|
234
|
+
Store in \`${workspaceRoot}/skills/<name>/\` (global) or \`${conversationPath}/skills/<name>/\` (conversation-specific).
|
|
150
235
|
Each skill directory needs a \`SKILL.md\` with YAML frontmatter:
|
|
151
236
|
|
|
152
237
|
\`\`\`markdown
|
|
@@ -167,23 +252,24 @@ Scripts are in: {baseDir}/
|
|
|
167
252
|
${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}
|
|
168
253
|
|
|
169
254
|
## Events
|
|
170
|
-
You can schedule events that wake you up at specific times or when external things happen.
|
|
255
|
+
You can schedule events that wake you up at specific times or when external things happen.
|
|
256
|
+
${eventFilesystemInstructions}
|
|
171
257
|
|
|
172
258
|
### Event Types
|
|
173
259
|
|
|
174
260
|
**Immediate** - Triggers as soon as harness sees the file. Use in scripts/webhooks to signal external events.
|
|
175
261
|
\`\`\`json
|
|
176
|
-
{"type": "immediate", "platform": "${platform.name}", "
|
|
262
|
+
{"type": "immediate", "platform": "${platform.name}", "conversationId": "${conversationId}", "conversationKind": "${conversationKind}", "userId": "${currentUserId ?? "<requester userId>"}", "text": "New GitHub issue opened"}
|
|
177
263
|
\`\`\`
|
|
178
264
|
|
|
179
265
|
**One-shot** - Triggers once at a specific time. Use for reminders.
|
|
180
266
|
\`\`\`json
|
|
181
|
-
{"type": "one-shot", "platform": "${platform.name}", "
|
|
267
|
+
{"type": "one-shot", "platform": "${platform.name}", "conversationId": "${conversationId}", "conversationKind": "${conversationKind}", "userId": "${currentUserId ?? "<requester userId>"}", "text": "Remind Mario about dentist", "at": "2025-12-15T09:00:00+01:00"}
|
|
182
268
|
\`\`\`
|
|
183
269
|
|
|
184
270
|
**Periodic** - Triggers on a cron schedule. Use for recurring tasks.
|
|
185
271
|
\`\`\`json
|
|
186
|
-
{"type": "periodic", "platform": "${platform.name}", "
|
|
272
|
+
{"type": "periodic", "platform": "${platform.name}", "conversationId": "${conversationId}", "conversationKind": "${conversationKind}", "userId": "${currentUserId ?? "<requester userId>"}", "text": "Check inbox and summarize", "schedule": "0 9 * * 1-5", "timezone": "${Intl.DateTimeFormat().resolvedOptions().timeZone}"}
|
|
187
273
|
\`\`\`
|
|
188
274
|
|
|
189
275
|
### Cron Format
|
|
@@ -196,22 +282,12 @@ You can schedule events that wake you up at specific times or when external thin
|
|
|
196
282
|
### Timezones
|
|
197
283
|
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}.
|
|
198
284
|
|
|
199
|
-
### Platform Routing
|
|
200
|
-
Set \`platform\` to the target bot platform (\`${platform.name}\` for this conversation).
|
|
285
|
+
### Platform and Credential Routing
|
|
286
|
+
Set \`platform\` to the target bot platform (\`${platform.name}\` for this conversation). Include it explicitly to avoid ambiguity.
|
|
201
287
|
|
|
202
|
-
|
|
203
|
-
Use unique filenames to avoid overwriting existing events. Include a timestamp or random suffix:
|
|
204
|
-
\`\`\`bash
|
|
205
|
-
cat > ${workspacePath}/events/dentist-reminder-$(date +%s).json << 'EOF'
|
|
206
|
-
{"type": "one-shot", "platform": "${platform.name}", "channelId": "${channelId}", "text": "Dentist tomorrow", "at": "2025-12-14T09:00:00+01:00"}
|
|
207
|
-
EOF
|
|
208
|
-
\`\`\`
|
|
209
|
-
Or check if file exists first before creating.
|
|
288
|
+
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
289
|
|
|
211
|
-
|
|
212
|
-
- List: \`ls ${workspacePath}/events/\`
|
|
213
|
-
- View: \`cat ${workspacePath}/events/foo.json\`
|
|
214
|
-
- Delete/cancel: \`rm ${workspacePath}/events/foo.json\`
|
|
290
|
+
When scheduling an event, write \`text\` as a self-contained task for your future self. Include the minimum necessary context, tone, and constraints in the text itself because events do not inherit normal conversation history. Good: \`Please remind the user that break time is over and it is time to return to class. Keep it brief, in Traditional Chinese, and do not ask follow-up questions.\` Bad: \`back to class\`.
|
|
215
291
|
|
|
216
292
|
### When Events Trigger
|
|
217
293
|
You receive a message like:
|
|
@@ -231,16 +307,16 @@ Maximum 5 events can be queued. Don't create excessive immediate or periodic eve
|
|
|
231
307
|
|
|
232
308
|
## Memory
|
|
233
309
|
Write to MEMORY.md files to persist context across conversations.
|
|
234
|
-
- Global (${
|
|
235
|
-
-
|
|
310
|
+
- Global (${workspaceRoot}/MEMORY.md): skills, preferences, project info
|
|
311
|
+
- Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work
|
|
236
312
|
Update when you learn something important or when asked to remember something.
|
|
237
313
|
|
|
238
314
|
### Current Memory
|
|
239
315
|
${memory}
|
|
240
316
|
|
|
241
317
|
## System Configuration Log
|
|
242
|
-
Maintain ${
|
|
243
|
-
- Installed packages (
|
|
318
|
+
Maintain ${workspaceRoot}/SYSTEM.md to log all environment modifications:
|
|
319
|
+
- Installed packages (apt install, npm install, uv pip install)
|
|
244
320
|
- Environment variables set
|
|
245
321
|
- Config files modified (~/.gitconfig, cron jobs, etc.)
|
|
246
322
|
- Skill dependencies installed
|
|
@@ -250,8 +326,8 @@ Update this file whenever you modify the environment. On fresh container, read i
|
|
|
250
326
|
## Log Queries (for older history)
|
|
251
327
|
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","isBot":false}\`
|
|
252
328
|
The log contains user messages and your final responses (not tool calls/results).
|
|
253
|
-
|
|
254
|
-
${isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
329
|
+
Use \`log.jsonl\` for quick grep-style history. Use \`${conversationPath}/sessions/\` when you need structured turns, tool outputs, or branch lineage.
|
|
330
|
+
${isContainerLike || isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
255
331
|
|
|
256
332
|
\`\`\`bash
|
|
257
333
|
# Recent messages
|
|
@@ -262,6 +338,10 @@ grep -i "topic" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user
|
|
|
262
338
|
|
|
263
339
|
# Messages from specific user
|
|
264
340
|
grep '"userName":"mario"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'
|
|
341
|
+
|
|
342
|
+
# Inspect top-level session pointer and available session files
|
|
343
|
+
cat sessions/current
|
|
344
|
+
ls -1 sessions/
|
|
265
345
|
\`\`\`
|
|
266
346
|
|
|
267
347
|
## Tools
|
|
@@ -279,162 +359,80 @@ function truncate(text, maxLen) {
|
|
|
279
359
|
return text;
|
|
280
360
|
return `${text.substring(0, maxLen - 3)}...`;
|
|
281
361
|
}
|
|
282
|
-
function
|
|
283
|
-
if (
|
|
284
|
-
return
|
|
362
|
+
export function getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot) {
|
|
363
|
+
if (sandboxConfig.type === "image") {
|
|
364
|
+
return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
|
|
285
365
|
}
|
|
286
|
-
|
|
287
|
-
typeof result === "object" &&
|
|
288
|
-
"content" in result &&
|
|
289
|
-
Array.isArray(result.content)) {
|
|
290
|
-
const content = result.content;
|
|
291
|
-
const textParts = [];
|
|
292
|
-
for (const part of content) {
|
|
293
|
-
if (part.type === "text" && part.text) {
|
|
294
|
-
textParts.push(part.text);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (textParts.length > 0) {
|
|
298
|
-
return textParts.join("\n");
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return JSON.stringify(result);
|
|
302
|
-
}
|
|
303
|
-
function formatToolArgsForSlack(_toolName, args) {
|
|
304
|
-
const lines = [];
|
|
305
|
-
for (const [key, value] of Object.entries(args)) {
|
|
306
|
-
if (key === "label")
|
|
307
|
-
continue;
|
|
308
|
-
if (key === "path" && typeof value === "string") {
|
|
309
|
-
const offset = args.offset;
|
|
310
|
-
const limit = args.limit;
|
|
311
|
-
if (offset !== undefined && limit !== undefined) {
|
|
312
|
-
lines.push(`${value}:${offset}-${offset + limit}`);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
lines.push(value);
|
|
316
|
-
}
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (key === "offset" || key === "limit")
|
|
320
|
-
continue;
|
|
321
|
-
if (typeof value === "string") {
|
|
322
|
-
lines.push(value);
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
lines.push(JSON.stringify(value));
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return lines.join("\n");
|
|
366
|
+
return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);
|
|
329
367
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const skills = loadMamaSkills(channelDir, workspacePath);
|
|
359
|
-
const emptyPlatform = {
|
|
360
|
-
name: "slack",
|
|
361
|
-
formattingGuide: "",
|
|
362
|
-
channels: [],
|
|
363
|
-
users: [],
|
|
368
|
+
function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath) {
|
|
369
|
+
const executionResolver = vaultManager &&
|
|
370
|
+
sandboxConfig.type !== "host" &&
|
|
371
|
+
(vaultManager.isEnabled() ||
|
|
372
|
+
sandboxConfig.type === "container" ||
|
|
373
|
+
sandboxConfig.type === "image" ||
|
|
374
|
+
sandboxConfig.type === "cloudflare" ||
|
|
375
|
+
sandboxConfig.type === "firecracker")
|
|
376
|
+
? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir)
|
|
377
|
+
: undefined;
|
|
378
|
+
// activeExecutor is replaced at the start of each run() call when executionResolver
|
|
379
|
+
// is present, so the stable `executor` wrapper always delegates to the latest resolved value.
|
|
380
|
+
let activeExecutor = executionResolver !== undefined
|
|
381
|
+
? createExecutor({ type: "host" })
|
|
382
|
+
: createExecutor(sandboxConfig);
|
|
383
|
+
const executor = {
|
|
384
|
+
exec(command, options) {
|
|
385
|
+
return activeExecutor.exec(command, options);
|
|
386
|
+
},
|
|
387
|
+
getWorkspacePath(hostPath) {
|
|
388
|
+
return activeExecutor.getWorkspacePath(hostPath);
|
|
389
|
+
},
|
|
390
|
+
getSandboxConfig() {
|
|
391
|
+
return activeExecutor.getSandboxConfig();
|
|
392
|
+
},
|
|
393
|
+
getPathContext(hostWorkspaceRoot) {
|
|
394
|
+
return activeExecutor.getPathContext(hostWorkspaceRoot);
|
|
395
|
+
},
|
|
364
396
|
};
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
sessionManager = openManagedSession(contextFile, sessionDir, channelDir);
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
const channelSource = resolveChannelSessionFile(channelDir);
|
|
382
|
-
if (channelSource) {
|
|
383
|
-
try {
|
|
384
|
-
contextFile = forkThreadSessionFile(channelSource, threadFile, channelDir);
|
|
385
|
-
sessionManager = openManagedSession(contextFile, sessionDir, channelDir);
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
contextFile = createManagedSessionFileAtPath(threadFile, channelDir);
|
|
389
|
-
sessionManager = openManagedSession(contextFile, sessionDir, channelDir);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
contextFile = createManagedSessionFileAtPath(threadFile, channelDir);
|
|
394
|
-
sessionManager = openManagedSession(contextFile, sessionDir, channelDir);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
// Channel/DM session: normal resolve
|
|
400
|
-
contextFile = resolveManagedSessionFile(sessionDir, channelDir);
|
|
401
|
-
sessionManager = openManagedSession(contextFile, sessionDir, channelDir);
|
|
402
|
-
}
|
|
403
|
-
const sessionUuid = extractSessionUuid(contextFile);
|
|
404
|
-
// Used for Slack thread filtering — for non-Slack platforms this is effectively a no-op
|
|
405
|
-
const rootTs = extractSessionSuffix(sessionKey);
|
|
406
|
-
const settingsManager = createMamaSettingsManager(join(channelDir, ".."));
|
|
407
|
-
// Create AuthStorage and ModelRegistry
|
|
408
|
-
// Auth stored outside workspace so agent can't access it
|
|
397
|
+
return {
|
|
398
|
+
executionResolver,
|
|
399
|
+
executor,
|
|
400
|
+
getPathContext: () => executor.getPathContext(hostWorkspacePath),
|
|
401
|
+
async resolveExecutorForRun(context) {
|
|
402
|
+
if (!executionResolver)
|
|
403
|
+
return;
|
|
404
|
+
activeExecutor = await executionResolver.resolve(context);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
async function createConfiguredAgentSession(params) {
|
|
409
|
+
const { conversationId, workspaceDir, runtimeWorkspaceRoot, systemPrompt, model, thinkingLevel, tools, sessionManager, settingsManager, } = params;
|
|
409
410
|
const authStorage = AuthStorage.create(join(homedir(), ".pi", "mama", "auth.json"));
|
|
410
|
-
const modelRegistry =
|
|
411
|
-
// Create agent
|
|
411
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
412
412
|
const agent = new Agent({
|
|
413
413
|
initialState: {
|
|
414
414
|
systemPrompt,
|
|
415
415
|
model,
|
|
416
|
-
thinkingLevel
|
|
416
|
+
thinkingLevel,
|
|
417
417
|
tools,
|
|
418
418
|
},
|
|
419
419
|
convertToLlm,
|
|
420
420
|
getApiKey: async () => {
|
|
421
421
|
const key = await modelRegistry.getApiKeyForProvider(model.provider);
|
|
422
|
-
if (!key)
|
|
422
|
+
if (!key) {
|
|
423
423
|
throw new Error(`No API key for provider "${model.provider}". Set the appropriate environment variable or configure via auth.json`);
|
|
424
|
+
}
|
|
424
425
|
return key;
|
|
425
426
|
},
|
|
426
427
|
});
|
|
427
|
-
// Load existing messages
|
|
428
428
|
const loadedSession = sessionManager.buildSessionContext();
|
|
429
429
|
if (loadedSession.messages.length > 0) {
|
|
430
|
-
agent.
|
|
431
|
-
log.logInfo(`[${
|
|
430
|
+
agent.state.messages = loadedSession.messages;
|
|
431
|
+
log.logInfo(`[${conversationId}] Reloaded ${loadedSession.messages.length} messages from session context`);
|
|
432
432
|
}
|
|
433
|
-
// Load extensions, skills, prompts, themes via DefaultResourceLoader
|
|
434
|
-
// This reads ~/.pi/agent/settings.json (packages, extensions enable/disable)
|
|
435
|
-
// and discovers resources from standard locations + npm/git packages.
|
|
436
433
|
const resourceLoader = new DefaultResourceLoader({
|
|
437
434
|
cwd: workspaceDir,
|
|
435
|
+
agentDir: getAgentDir(),
|
|
438
436
|
systemPrompt,
|
|
439
437
|
});
|
|
440
438
|
try {
|
|
@@ -442,124 +440,355 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
442
440
|
const extResult = resourceLoader.getExtensions();
|
|
443
441
|
if (extResult.errors.length > 0) {
|
|
444
442
|
for (const err of extResult.errors) {
|
|
445
|
-
log.logWarning(`[${
|
|
443
|
+
log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
|
|
446
444
|
}
|
|
447
445
|
}
|
|
448
|
-
log.logInfo(`[${
|
|
446
|
+
log.logInfo(`[${conversationId}] Loaded ${extResult.extensions.length} extension(s): ${extResult.extensions.map((extension) => extension.path).join(", ")}`);
|
|
449
447
|
}
|
|
450
448
|
catch (error) {
|
|
451
|
-
log.logWarning(`[${
|
|
449
|
+
log.logWarning(`[${conversationId}] Failed to load resources`, String(error));
|
|
452
450
|
}
|
|
453
451
|
const baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
454
|
-
// Create AgentSession wrapper
|
|
455
452
|
const session = new AgentSession({
|
|
456
453
|
agent,
|
|
457
454
|
sessionManager,
|
|
458
455
|
settingsManager,
|
|
459
|
-
cwd:
|
|
456
|
+
cwd: runtimeWorkspaceRoot,
|
|
460
457
|
modelRegistry,
|
|
461
458
|
resourceLoader,
|
|
462
459
|
baseToolsOverride,
|
|
463
460
|
});
|
|
464
|
-
|
|
465
|
-
|
|
461
|
+
return { agent, session };
|
|
462
|
+
}
|
|
463
|
+
function createEmptyUsageTotals() {
|
|
464
|
+
return {
|
|
465
|
+
input: 0,
|
|
466
|
+
output: 0,
|
|
467
|
+
cacheRead: 0,
|
|
468
|
+
cacheWrite: 0,
|
|
469
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function createRunState() {
|
|
473
|
+
return {
|
|
466
474
|
responseCtx: null,
|
|
467
475
|
logCtx: null,
|
|
468
476
|
queue: null,
|
|
469
477
|
pendingTools: new Map(),
|
|
470
|
-
totalUsage:
|
|
471
|
-
input: 0,
|
|
472
|
-
output: 0,
|
|
473
|
-
cacheRead: 0,
|
|
474
|
-
cacheWrite: 0,
|
|
475
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
476
|
-
},
|
|
478
|
+
totalUsage: createEmptyUsageTotals(),
|
|
477
479
|
llmCallCount: 0,
|
|
478
480
|
stopReason: "stop",
|
|
479
481
|
errorMessage: undefined,
|
|
480
482
|
};
|
|
481
|
-
|
|
483
|
+
}
|
|
484
|
+
function resetRunState(runState, responseCtx, sessionConversation, userName, sessionUuid) {
|
|
485
|
+
runState.responseCtx = responseCtx;
|
|
486
|
+
runState.logCtx = {
|
|
487
|
+
conversationId: sessionConversation,
|
|
488
|
+
userName,
|
|
489
|
+
conversationName: undefined,
|
|
490
|
+
sessionId: sessionUuid,
|
|
491
|
+
};
|
|
492
|
+
runState.pendingTools.clear();
|
|
493
|
+
runState.totalUsage = createEmptyUsageTotals();
|
|
494
|
+
runState.llmCallCount = 0;
|
|
495
|
+
runState.stopReason = "stop";
|
|
496
|
+
runState.errorMessage = undefined;
|
|
497
|
+
}
|
|
498
|
+
function createRunQueue(responseCtx) {
|
|
499
|
+
let queueChain = Promise.resolve();
|
|
500
|
+
return {
|
|
501
|
+
queue: {
|
|
502
|
+
enqueue(fn, errorContext) {
|
|
503
|
+
queueChain = queueChain.then(async () => {
|
|
504
|
+
try {
|
|
505
|
+
await fn();
|
|
506
|
+
}
|
|
507
|
+
catch (err) {
|
|
508
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
509
|
+
log.logWarning(`API error (${errorContext})`, errMsg);
|
|
510
|
+
try {
|
|
511
|
+
await responseCtx.respondDiagnostic(`Error: ${errMsg}`, { style: "error" });
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
// Ignore
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
wait: () => queueChain,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function padTwoDigits(n) {
|
|
524
|
+
return n.toString().padStart(2, "0");
|
|
525
|
+
}
|
|
526
|
+
function formatTimestampedUserMessage(message) {
|
|
527
|
+
const now = new Date();
|
|
528
|
+
const offset = -now.getTimezoneOffset();
|
|
529
|
+
const offsetSign = offset >= 0 ? "+" : "-";
|
|
530
|
+
const offsetHours = padTwoDigits(Math.floor(Math.abs(offset) / 60));
|
|
531
|
+
const offsetMins = padTwoDigits(Math.abs(offset) % 60);
|
|
532
|
+
const timestamp = `${now.getFullYear()}-${padTwoDigits(now.getMonth() + 1)}-${padTwoDigits(now.getDate())} ` +
|
|
533
|
+
`${padTwoDigits(now.getHours())}:${padTwoDigits(now.getMinutes())}:${padTwoDigits(now.getSeconds())}` +
|
|
534
|
+
`${offsetSign}${offsetHours}:${offsetMins}`;
|
|
535
|
+
const threadContext = message.threadTs ? ` [in-thread:${message.threadTs}]` : "";
|
|
536
|
+
return `[${timestamp}] [${message.userName || "unknown"}]${threadContext}: ${message.text}`;
|
|
537
|
+
}
|
|
538
|
+
function collectMessageAttachments(message, workspacePath) {
|
|
539
|
+
const imageAttachments = [];
|
|
540
|
+
const nonImagePaths = [];
|
|
541
|
+
for (const attachment of message.attachments || []) {
|
|
542
|
+
const fullPath = `${workspacePath}/${attachment.localPath}`;
|
|
543
|
+
const mimeType = getImageMimeType(attachment.localPath);
|
|
544
|
+
if (mimeType && existsSync(fullPath)) {
|
|
545
|
+
try {
|
|
546
|
+
imageAttachments.push({
|
|
547
|
+
type: "image",
|
|
548
|
+
mimeType,
|
|
549
|
+
data: readFileSync(fullPath).toString("base64"),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
nonImagePaths.push(fullPath);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
nonImagePaths.push(fullPath);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return { imageAttachments, nonImagePaths };
|
|
561
|
+
}
|
|
562
|
+
function buildPromptPayload(message, workspacePath) {
|
|
563
|
+
let userMessage = formatTimestampedUserMessage(message);
|
|
564
|
+
const { imageAttachments, nonImagePaths } = collectMessageAttachments(message, workspacePath);
|
|
565
|
+
if (nonImagePaths.length > 0) {
|
|
566
|
+
userMessage += `\n\n<slack_attachments>\n${nonImagePaths.join("\n")}\n</slack_attachments>`;
|
|
567
|
+
}
|
|
568
|
+
return { userMessage, imageAttachments };
|
|
569
|
+
}
|
|
570
|
+
async function writePromptDebugContext(conversationDir, systemPrompt, session, userMessage, imageAttachmentCount) {
|
|
571
|
+
const debugContext = {
|
|
572
|
+
systemPrompt,
|
|
573
|
+
messages: session.messages,
|
|
574
|
+
newUserMessage: userMessage,
|
|
575
|
+
imageAttachmentCount,
|
|
576
|
+
};
|
|
577
|
+
await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
|
|
578
|
+
}
|
|
579
|
+
function getFinalAssistantText(session) {
|
|
580
|
+
const lastAssistant = session.messages.filter((message) => message.role === "assistant").pop();
|
|
581
|
+
return (lastAssistant?.content
|
|
582
|
+
.filter((content) => content.type === "text")
|
|
583
|
+
.map((content) => content.text)
|
|
584
|
+
.join("\n") || "");
|
|
585
|
+
}
|
|
586
|
+
async function finalizeRunResponse(responseCtx, session, runState) {
|
|
587
|
+
if (runState.stopReason === "error" && runState.errorMessage) {
|
|
588
|
+
try {
|
|
589
|
+
await responseCtx.replaceResponse("_Sorry, something went wrong_");
|
|
590
|
+
await responseCtx.respondDiagnostic(`Error: ${runState.errorMessage}`, {
|
|
591
|
+
style: "error",
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
catch (err) {
|
|
595
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
596
|
+
log.logWarning("Failed to post error message", errMsg);
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const finalText = getFinalAssistantText(session);
|
|
601
|
+
if (finalText.trim() === "[SILENT]" || finalText.trim().startsWith("[SILENT]")) {
|
|
602
|
+
try {
|
|
603
|
+
await responseCtx.deleteResponse();
|
|
604
|
+
log.logInfo("Silent response - deleted message and thread");
|
|
605
|
+
}
|
|
606
|
+
catch (err) {
|
|
607
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
608
|
+
log.logWarning("Failed to delete message for silent response", errMsg);
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (!finalText.trim())
|
|
613
|
+
return;
|
|
614
|
+
try {
|
|
615
|
+
await responseCtx.replaceResponse(finalText);
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
619
|
+
log.logWarning("Failed to replace message with final text", errMsg);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function reportUsageSummary(ctx) {
|
|
623
|
+
const { session, runState, responseCtx, platform, model, agentConfig, sessionConversation, sessionUuid, waitForQueue, } = ctx;
|
|
624
|
+
if (runState.totalUsage.cost.total <= 0)
|
|
625
|
+
return;
|
|
626
|
+
const lastAssistantMessage = session.messages
|
|
627
|
+
.slice()
|
|
628
|
+
.toReversed()
|
|
629
|
+
.find((message) => message.role === "assistant" && message.stopReason !== "aborted");
|
|
630
|
+
const contextTokens = lastAssistantMessage
|
|
631
|
+
? lastAssistantMessage.usage.input +
|
|
632
|
+
lastAssistantMessage.usage.output +
|
|
633
|
+
lastAssistantMessage.usage.cacheRead +
|
|
634
|
+
lastAssistantMessage.usage.cacheWrite
|
|
635
|
+
: 0;
|
|
636
|
+
const contextWindow = model.contextWindow || 200000;
|
|
637
|
+
const { totalUsage } = runState;
|
|
638
|
+
const runMetricAttributes = metricAttributes({
|
|
639
|
+
provider: model.provider,
|
|
640
|
+
model: agentConfig.model,
|
|
641
|
+
channel_id: sessionConversation,
|
|
642
|
+
session_id: sessionUuid,
|
|
643
|
+
stop_reason: runState.stopReason,
|
|
644
|
+
llm_calls: runState.llmCallCount,
|
|
645
|
+
});
|
|
646
|
+
Sentry.metrics.distribution("agent.run.tokens_in", totalUsage.input, {
|
|
647
|
+
attributes: runMetricAttributes,
|
|
648
|
+
});
|
|
649
|
+
Sentry.metrics.distribution("agent.run.tokens_out", totalUsage.output, {
|
|
650
|
+
attributes: runMetricAttributes,
|
|
651
|
+
});
|
|
652
|
+
Sentry.metrics.distribution("agent.run.cache_read", totalUsage.cacheRead, {
|
|
653
|
+
attributes: runMetricAttributes,
|
|
654
|
+
});
|
|
655
|
+
Sentry.metrics.distribution("agent.run.cache_write", totalUsage.cacheWrite, {
|
|
656
|
+
attributes: runMetricAttributes,
|
|
657
|
+
});
|
|
658
|
+
Sentry.metrics.distribution("agent.run.cost", totalUsage.cost.total, {
|
|
659
|
+
attributes: runMetricAttributes,
|
|
660
|
+
});
|
|
661
|
+
Sentry.metrics.gauge("agent.context.utilization", contextTokens / contextWindow, {
|
|
662
|
+
unit: "ratio",
|
|
663
|
+
attributes: runMetricAttributes,
|
|
664
|
+
});
|
|
665
|
+
const summary = log.logUsageSummary(runState.logCtx, runState.totalUsage, contextTokens, contextWindow);
|
|
666
|
+
if (platform.diagnostics?.showUsageSummary === true) {
|
|
667
|
+
runState.queue.enqueue(() => responseCtx.respondDiagnostic(summary, { style: "muted" }), "usage summary");
|
|
668
|
+
await waitForQueue();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function reloadSessionMessages(sessionManager, conversationId, agent) {
|
|
672
|
+
const messages = sessionManager.buildSessionContext().messages;
|
|
673
|
+
if (messages.length > 0) {
|
|
674
|
+
agent.state.messages = messages;
|
|
675
|
+
log.logInfo(`[${conversationId}] Reloaded ${messages.length} messages from context`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async function prepareRunContext(params) {
|
|
679
|
+
const { message, responseCtx, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, sessionManager, session, agent, setEventContext, setUploadFunction, } = params;
|
|
680
|
+
let pathContext = params.pathContext;
|
|
681
|
+
const sessionConversation = message.sessionKey.split(":")[0];
|
|
682
|
+
await mkdir(join(conversationDir, "scratch"), { recursive: true });
|
|
683
|
+
if (executionResolver) {
|
|
684
|
+
await resolveExecutorForRun({
|
|
685
|
+
platform: platform.name,
|
|
686
|
+
userId: message.userId,
|
|
687
|
+
conversationId,
|
|
688
|
+
});
|
|
689
|
+
pathContext = getPathContext();
|
|
690
|
+
}
|
|
691
|
+
reloadSessionMessages(sessionManager, conversationId, agent);
|
|
692
|
+
const memory = await getMemory(conversationDir);
|
|
693
|
+
const skills = loadMamaSkills(conversationDir, pathContext.runtimeWorkspaceRoot);
|
|
694
|
+
const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, message.id.startsWith("event:"));
|
|
695
|
+
session.agent.state.systemPrompt = systemPrompt;
|
|
696
|
+
setEventContext({
|
|
697
|
+
platform: platform.name,
|
|
698
|
+
conversationId,
|
|
699
|
+
conversationKind: message.conversationKind,
|
|
700
|
+
userId: message.userId,
|
|
701
|
+
});
|
|
702
|
+
setUploadFunction(async (filePath, title) => {
|
|
703
|
+
const hostPath = translateRuntimePathToHost(filePath, pathContext);
|
|
704
|
+
await responseCtx.uploadFile(hostPath, title);
|
|
705
|
+
});
|
|
706
|
+
resetRunState(runState, responseCtx, sessionConversation, message.userName, sessionUuid);
|
|
707
|
+
const runQueue = createRunQueue(responseCtx);
|
|
708
|
+
runState.queue = runQueue.queue;
|
|
709
|
+
log.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);
|
|
710
|
+
log.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);
|
|
711
|
+
const { userMessage, imageAttachments } = buildPromptPayload(message, pathContext.runtimeWorkspaceRoot);
|
|
712
|
+
await writePromptDebugContext(conversationDir, systemPrompt, session, userMessage, imageAttachments.length);
|
|
713
|
+
return {
|
|
714
|
+
sessionConversation,
|
|
715
|
+
runQueue,
|
|
716
|
+
userMessage,
|
|
717
|
+
imageAttachments,
|
|
718
|
+
pathContext,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function attachSessionEventHandlers(params) {
|
|
722
|
+
const { session, runState, model, agentConfig } = params;
|
|
482
723
|
session.subscribe(async (event) => {
|
|
483
|
-
// Skip if no active run
|
|
484
724
|
if (!runState.responseCtx || !runState.logCtx || !runState.queue)
|
|
485
725
|
return;
|
|
486
726
|
const { responseCtx, logCtx, queue, pendingTools } = runState;
|
|
487
|
-
const baseAttrs = { channel_id: logCtx.
|
|
727
|
+
const baseAttrs = { channel_id: logCtx.conversationId, session_id: logCtx.sessionId };
|
|
488
728
|
if (event.type === "tool_execution_start") {
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
args: agentEvent.args,
|
|
729
|
+
const args = (event.args ?? {});
|
|
730
|
+
const label = args.label || event.toolName;
|
|
731
|
+
pendingTools.set(event.toolCallId, {
|
|
732
|
+
toolName: event.toolName,
|
|
733
|
+
args: event.args,
|
|
495
734
|
startTime: Date.now(),
|
|
496
735
|
});
|
|
497
736
|
addLifecycleBreadcrumb("agent.tool.started", {
|
|
498
|
-
tool:
|
|
737
|
+
tool: event.toolName,
|
|
499
738
|
...baseAttrs,
|
|
500
739
|
});
|
|
501
|
-
log.logToolStart(logCtx,
|
|
502
|
-
|
|
503
|
-
// Tool execution details are still posted to the thread (see tool_execution_end).
|
|
740
|
+
log.logToolStart(logCtx, event.toolName, label, event.args);
|
|
741
|
+
return;
|
|
504
742
|
}
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
pendingTools.delete(agentEvent.toolCallId);
|
|
743
|
+
if (event.type === "tool_execution_end") {
|
|
744
|
+
const resultStr = extractToolResultText(event.result);
|
|
745
|
+
const pending = pendingTools.get(event.toolCallId);
|
|
746
|
+
pendingTools.delete(event.toolCallId);
|
|
510
747
|
const durationMs = pending ? Date.now() - pending.startTime : 0;
|
|
511
748
|
Sentry.metrics.count("agent.tool.calls", 1, {
|
|
512
749
|
attributes: metricAttributes({
|
|
513
|
-
tool:
|
|
514
|
-
error: String(
|
|
750
|
+
tool: event.toolName,
|
|
751
|
+
error: String(event.isError),
|
|
515
752
|
...baseAttrs,
|
|
516
753
|
}),
|
|
517
754
|
});
|
|
518
755
|
Sentry.metrics.distribution("agent.tool.duration", durationMs, {
|
|
519
756
|
unit: "millisecond",
|
|
520
757
|
attributes: metricAttributes({
|
|
521
|
-
tool:
|
|
758
|
+
tool: event.toolName,
|
|
522
759
|
...baseAttrs,
|
|
523
760
|
}),
|
|
524
761
|
});
|
|
525
762
|
addLifecycleBreadcrumb("agent.tool.completed", {
|
|
526
|
-
tool:
|
|
527
|
-
error:
|
|
763
|
+
tool: event.toolName,
|
|
764
|
+
error: event.isError,
|
|
528
765
|
duration_ms: durationMs,
|
|
529
766
|
...baseAttrs,
|
|
530
767
|
});
|
|
531
|
-
if (
|
|
532
|
-
log.logToolError(logCtx,
|
|
768
|
+
if (event.isError) {
|
|
769
|
+
log.logToolError(logCtx, event.toolName, durationMs, resultStr);
|
|
533
770
|
}
|
|
534
771
|
else {
|
|
535
|
-
log.logToolSuccess(logCtx,
|
|
772
|
+
log.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);
|
|
536
773
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (argsFormatted)
|
|
548
|
-
threadMessage += `\`\`\`\n${argsFormatted}\n\`\`\`\n`;
|
|
549
|
-
threadMessage += `*Result:*\n\`\`\`\n${resultStr}\n\`\`\``;
|
|
550
|
-
// Only post thread details for tools with meaningful output (bash, attach).
|
|
551
|
-
// Skip read/write/edit to reduce Slack noise — their results are in the log.
|
|
552
|
-
const quietTools = new Set(["read", "write", "edit"]);
|
|
553
|
-
if (!quietTools.has(agentEvent.toolName)) {
|
|
554
|
-
queue.enqueueMessage(threadMessage, "thread", "tool result thread", false);
|
|
774
|
+
if (shouldSurfaceToolDiagnostic(event.toolName)) {
|
|
775
|
+
const toolResult = {
|
|
776
|
+
toolName: event.toolName,
|
|
777
|
+
label: pending?.args ? pending.args.label : undefined,
|
|
778
|
+
args: pending?.args,
|
|
779
|
+
result: truncate(resultStr, TOOL_RESULT_DIAGNOSTIC_CAP),
|
|
780
|
+
isError: event.isError,
|
|
781
|
+
durationMs,
|
|
782
|
+
};
|
|
783
|
+
queue.enqueue(() => responseCtx.respondToolResult(toolResult), "tool result diagnostic");
|
|
555
784
|
}
|
|
556
|
-
if (
|
|
785
|
+
if (event.isError) {
|
|
557
786
|
queue.enqueue(() => responseCtx.respond(`_Error: ${truncate(resultStr, 200)}_`), "tool error");
|
|
558
787
|
}
|
|
788
|
+
return;
|
|
559
789
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (agentEvent.message.role === "assistant") {
|
|
790
|
+
if (event.type === "message_start") {
|
|
791
|
+
if (event.message.role === "assistant") {
|
|
563
792
|
runState.llmCallCount += 1;
|
|
564
793
|
addLifecycleBreadcrumb("agent.llm.call.started", {
|
|
565
794
|
call_index: runState.llmCallCount,
|
|
@@ -569,11 +798,11 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
569
798
|
});
|
|
570
799
|
log.logResponseStart(logCtx);
|
|
571
800
|
}
|
|
801
|
+
return;
|
|
572
802
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const assistantMsg = agentEvent.message;
|
|
803
|
+
if (event.type === "message_end") {
|
|
804
|
+
if (event.message.role === "assistant") {
|
|
805
|
+
const assistantMsg = event.message;
|
|
577
806
|
if (assistantMsg.stopReason) {
|
|
578
807
|
runState.stopReason = assistantMsg.stopReason;
|
|
579
808
|
}
|
|
@@ -590,7 +819,6 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
590
819
|
runState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;
|
|
591
820
|
runState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;
|
|
592
821
|
runState.totalUsage.cost.total += assistantMsg.usage.cost.total;
|
|
593
|
-
// Per-turn LLM metrics
|
|
594
822
|
const llmAttributes = metricAttributes({
|
|
595
823
|
provider: model.provider,
|
|
596
824
|
model: agentConfig.model,
|
|
@@ -629,10 +857,9 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
629
857
|
cost_total_usd: assistantMsg.usage.cost.total,
|
|
630
858
|
});
|
|
631
859
|
}
|
|
632
|
-
const content = agentEvent.message.content;
|
|
633
860
|
const thinkingParts = [];
|
|
634
861
|
const textParts = [];
|
|
635
|
-
for (const part of content) {
|
|
862
|
+
for (const part of assistantMsg.content) {
|
|
636
863
|
if (part.type === "thinking") {
|
|
637
864
|
thinkingParts.push(part.thinking);
|
|
638
865
|
}
|
|
@@ -643,296 +870,168 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
643
870
|
const text = textParts.join("\n");
|
|
644
871
|
for (const thinking of thinkingParts) {
|
|
645
872
|
log.logThinking(logCtx, thinking);
|
|
646
|
-
queue.
|
|
647
|
-
queue.
|
|
873
|
+
queue.enqueue(() => responseCtx.respond(`_${thinking}_`), "thinking main");
|
|
874
|
+
queue.enqueue(() => responseCtx.respondDiagnostic(`_${thinking}_`), "thinking diagnostic");
|
|
648
875
|
}
|
|
649
876
|
if (text.trim()) {
|
|
650
877
|
log.logResponse(logCtx, text);
|
|
651
|
-
queue.
|
|
652
|
-
// Only overflow to thread for texts that will be truncated in main
|
|
653
|
-
if (text.length > SLACK_MAX_LENGTH) {
|
|
654
|
-
queue.enqueueMessage(text, "thread", "response thread", false);
|
|
655
|
-
}
|
|
878
|
+
queue.enqueue(() => responseCtx.respond(text), "response main");
|
|
656
879
|
}
|
|
657
880
|
}
|
|
881
|
+
return;
|
|
658
882
|
}
|
|
659
|
-
|
|
883
|
+
if (event.type === "compaction_start") {
|
|
660
884
|
log.logInfo(`Auto-compaction started (reason: ${event.reason})`);
|
|
661
885
|
queue.enqueue(() => responseCtx.respond("_Compacting context..._"), "compaction start");
|
|
886
|
+
return;
|
|
662
887
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
log.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);
|
|
888
|
+
if (event.type === "compaction_end") {
|
|
889
|
+
if (event.result) {
|
|
890
|
+
log.logInfo(`Auto-compaction complete: ${event.result.tokensBefore} tokens compacted`);
|
|
667
891
|
}
|
|
668
|
-
else if (
|
|
892
|
+
else if (event.aborted) {
|
|
669
893
|
log.logInfo("Auto-compaction aborted");
|
|
670
894
|
}
|
|
895
|
+
return;
|
|
671
896
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
queue.enqueue(() => responseCtx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`), "retry");
|
|
897
|
+
if (event.type === "auto_retry_start") {
|
|
898
|
+
log.logWarning(`Retrying (${event.attempt}/${event.maxAttempts})`, event.errorMessage);
|
|
899
|
+
queue.enqueue(() => responseCtx.respond(`_Retrying (${event.attempt}/${event.maxAttempts})..._`), "retry");
|
|
676
900
|
}
|
|
677
901
|
});
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
902
|
+
}
|
|
903
|
+
// Cap raw tool output before handing it to adapters. Bash output can be MB; without
|
|
904
|
+
// this each adapter's splitter would fan it out into many sequential platform posts.
|
|
905
|
+
const TOOL_RESULT_DIAGNOSTIC_CAP = 8000;
|
|
906
|
+
function extractToolResultText(result) {
|
|
907
|
+
if (typeof result === "string") {
|
|
908
|
+
return result;
|
|
909
|
+
}
|
|
910
|
+
if (result &&
|
|
911
|
+
typeof result === "object" &&
|
|
912
|
+
"content" in result &&
|
|
913
|
+
Array.isArray(result.content)) {
|
|
914
|
+
const content = result.content;
|
|
915
|
+
const textParts = [];
|
|
916
|
+
for (const part of content) {
|
|
917
|
+
if (part.type === "text" && part.text) {
|
|
918
|
+
textParts.push(part.text);
|
|
919
|
+
}
|
|
692
920
|
}
|
|
693
|
-
|
|
921
|
+
if (textParts.length > 0) {
|
|
922
|
+
return textParts.join("\n");
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return JSON.stringify(result);
|
|
926
|
+
}
|
|
927
|
+
// ============================================================================
|
|
928
|
+
// Agent runner
|
|
929
|
+
// ============================================================================
|
|
930
|
+
/**
|
|
931
|
+
* Create a new AgentRunner for a channel.
|
|
932
|
+
* Sets up the session and subscribes to events once.
|
|
933
|
+
*
|
|
934
|
+
* Runner caching is handled by the caller (channelStates in main.ts).
|
|
935
|
+
* This is a stateless factory function.
|
|
936
|
+
*/
|
|
937
|
+
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner) {
|
|
938
|
+
const agentConfig = loadAgentConfigForConversation(conversationDir);
|
|
939
|
+
// Initialize logger with settings from config
|
|
940
|
+
log.initLogger({
|
|
941
|
+
logFormat: agentConfig.logFormat,
|
|
942
|
+
logLevel: agentConfig.logLevel,
|
|
943
|
+
});
|
|
944
|
+
const workspaceBase = join(conversationDir, "..");
|
|
945
|
+
const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, workspaceBase);
|
|
946
|
+
let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);
|
|
947
|
+
// Create tools (per-runner, with per-runner upload function setter)
|
|
948
|
+
const { tools, setUploadFunction, setEventContext } = createMamaTools(executor, workspaceDir);
|
|
949
|
+
// Resolve model from config
|
|
950
|
+
// Use 'as any' cast because agentConfig.provider/model are plain strings,
|
|
951
|
+
// while getModel() has constrained generic types for known providers.
|
|
952
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
953
|
+
const model = getModel(agentConfig.provider, agentConfig.model);
|
|
954
|
+
// Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
|
|
955
|
+
const memory = await getMemory(conversationDir);
|
|
956
|
+
const skills = loadMamaSkills(conversationDir, pathContext.runtimeWorkspaceRoot);
|
|
957
|
+
const emptyPlatform = {
|
|
958
|
+
name: "chat",
|
|
959
|
+
formattingGuide: "",
|
|
960
|
+
channels: [],
|
|
961
|
+
users: [],
|
|
694
962
|
};
|
|
963
|
+
const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills);
|
|
964
|
+
// Create session manager and settings manager. Top-level/private sessions
|
|
965
|
+
// use the conversation's current pointer; scoped sessions use fixed files.
|
|
966
|
+
// Platform-specific branch/fork behavior is resolved before runner creation.
|
|
967
|
+
const isThread = sessionKey.includes(":");
|
|
968
|
+
const { sessionDir, contextFile, threadRootMessage } = sessionScope;
|
|
969
|
+
const sessionManager = openManagedSession(contextFile, sessionDir, pathContext.runtimeWorkspaceRoot);
|
|
970
|
+
const threadSessionName = buildThreadSessionName(threadRootMessage);
|
|
971
|
+
if (isThread && threadSessionName && sessionManager.getSessionName() !== threadSessionName) {
|
|
972
|
+
sessionManager.appendSessionInfo(threadSessionName);
|
|
973
|
+
}
|
|
974
|
+
const sessionUuid = extractSessionUuid(contextFile);
|
|
975
|
+
const settingsManager = SettingsManager.inMemory();
|
|
976
|
+
const { agent, session } = await createConfiguredAgentSession({
|
|
977
|
+
conversationId,
|
|
978
|
+
workspaceDir,
|
|
979
|
+
runtimeWorkspaceRoot: pathContext.runtimeWorkspaceRoot,
|
|
980
|
+
systemPrompt,
|
|
981
|
+
model,
|
|
982
|
+
thinkingLevel: agentConfig.thinkingLevel,
|
|
983
|
+
tools,
|
|
984
|
+
sessionManager,
|
|
985
|
+
settingsManager,
|
|
986
|
+
});
|
|
987
|
+
// Mutable per-run state - event handler references this
|
|
988
|
+
const runState = createRunState();
|
|
989
|
+
attachSessionEventHandlers({ session, runState, model, agentConfig });
|
|
695
990
|
return {
|
|
696
991
|
async run(message, responseCtx, platform) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (reloadedSession.messages.length > 0) {
|
|
716
|
-
agent.replaceMessages(reloadedSession.messages);
|
|
717
|
-
log.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);
|
|
718
|
-
}
|
|
719
|
-
// Update system prompt with fresh memory, channel/user info, and skills
|
|
720
|
-
const memory = await getMemory(channelDir);
|
|
721
|
-
const skills = loadMamaSkills(channelDir, workspacePath);
|
|
722
|
-
const systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, platform, skills);
|
|
723
|
-
session.agent.setSystemPrompt(systemPrompt);
|
|
724
|
-
// Set up file upload function
|
|
725
|
-
setUploadFunction(async (filePath, title) => {
|
|
726
|
-
const hostPath = translateToHostPath(filePath, channelDir, workspacePath, channelId);
|
|
727
|
-
await responseCtx.uploadFile(hostPath, title);
|
|
992
|
+
const prepared = await prepareRunContext({
|
|
993
|
+
message,
|
|
994
|
+
responseCtx,
|
|
995
|
+
platform,
|
|
996
|
+
conversationId,
|
|
997
|
+
conversationDir,
|
|
998
|
+
sessionUuid,
|
|
999
|
+
runState,
|
|
1000
|
+
executor,
|
|
1001
|
+
executionResolver,
|
|
1002
|
+
resolveExecutorForRun,
|
|
1003
|
+
getPathContext,
|
|
1004
|
+
sessionManager,
|
|
1005
|
+
session,
|
|
1006
|
+
agent,
|
|
1007
|
+
setEventContext,
|
|
1008
|
+
setUploadFunction,
|
|
1009
|
+
pathContext,
|
|
728
1010
|
});
|
|
729
|
-
|
|
730
|
-
runState.responseCtx = responseCtx;
|
|
731
|
-
runState.logCtx = {
|
|
732
|
-
channelId: sessionChannel,
|
|
733
|
-
userName: message.userName,
|
|
734
|
-
channelName: undefined,
|
|
735
|
-
sessionId: sessionUuid,
|
|
736
|
-
};
|
|
737
|
-
runState.pendingTools.clear();
|
|
738
|
-
runState.totalUsage = {
|
|
739
|
-
input: 0,
|
|
740
|
-
output: 0,
|
|
741
|
-
cacheRead: 0,
|
|
742
|
-
cacheWrite: 0,
|
|
743
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
744
|
-
};
|
|
745
|
-
runState.llmCallCount = 0;
|
|
746
|
-
runState.stopReason = "stop";
|
|
747
|
-
runState.errorMessage = undefined;
|
|
748
|
-
// Create queue for this run
|
|
749
|
-
let queueChain = Promise.resolve();
|
|
750
|
-
runState.queue = {
|
|
751
|
-
enqueue(fn, errorContext) {
|
|
752
|
-
queueChain = queueChain.then(async () => {
|
|
753
|
-
try {
|
|
754
|
-
await fn();
|
|
755
|
-
}
|
|
756
|
-
catch (err) {
|
|
757
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
758
|
-
log.logWarning(`API error (${errorContext})`, errMsg);
|
|
759
|
-
try {
|
|
760
|
-
// Split long error messages to avoid msg_too_long
|
|
761
|
-
const errParts = splitForSlack(`_Error: ${errMsg}_`);
|
|
762
|
-
for (const part of errParts) {
|
|
763
|
-
await responseCtx.respondInThread(part);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
catch {
|
|
767
|
-
// Ignore
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
},
|
|
772
|
-
enqueueMessage(text, target, errorContext, _doLog = true) {
|
|
773
|
-
const parts = splitForSlack(text);
|
|
774
|
-
for (const part of parts) {
|
|
775
|
-
this.enqueue(() => target === "main" ? responseCtx.respond(part) : responseCtx.respondInThread(part), errorContext);
|
|
776
|
-
}
|
|
777
|
-
},
|
|
778
|
-
};
|
|
779
|
-
// Log context info
|
|
780
|
-
log.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);
|
|
781
|
-
log.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);
|
|
782
|
-
// Build user message with timestamp and username prefix
|
|
783
|
-
// Format: "[YYYY-MM-DD HH:MM:SS+HH:MM] [username]: message" so LLM knows when and who
|
|
784
|
-
const now = new Date();
|
|
785
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
786
|
-
const offset = -now.getTimezoneOffset();
|
|
787
|
-
const offsetSign = offset >= 0 ? "+" : "-";
|
|
788
|
-
const offsetHours = pad(Math.floor(Math.abs(offset) / 60));
|
|
789
|
-
const offsetMins = pad(Math.abs(offset) % 60);
|
|
790
|
-
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;
|
|
791
|
-
const threadContext = message.threadTs ? ` [in-thread:${message.threadTs}]` : "";
|
|
792
|
-
let userMessage = `[${timestamp}] [${message.userName || "unknown"}]${threadContext}: ${message.text}`;
|
|
793
|
-
const imageAttachments = [];
|
|
794
|
-
const nonImagePaths = [];
|
|
795
|
-
for (const a of message.attachments || []) {
|
|
796
|
-
// a.localPath is the path relative to the workspace (same as old a.local)
|
|
797
|
-
const fullPath = `${workspacePath}/${a.localPath}`;
|
|
798
|
-
const mimeType = getImageMimeType(a.localPath);
|
|
799
|
-
if (mimeType && existsSync(fullPath)) {
|
|
800
|
-
try {
|
|
801
|
-
imageAttachments.push({
|
|
802
|
-
type: "image",
|
|
803
|
-
mimeType,
|
|
804
|
-
data: readFileSync(fullPath).toString("base64"),
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
catch {
|
|
808
|
-
nonImagePaths.push(fullPath);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
nonImagePaths.push(fullPath);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if (nonImagePaths.length > 0) {
|
|
816
|
-
userMessage += `\n\n<slack_attachments>\n${nonImagePaths.join("\n")}\n</slack_attachments>`;
|
|
817
|
-
}
|
|
818
|
-
// Debug: write context to last_prompt.jsonl
|
|
819
|
-
const debugContext = {
|
|
820
|
-
systemPrompt,
|
|
821
|
-
messages: session.messages,
|
|
822
|
-
newUserMessage: userMessage,
|
|
823
|
-
imageAttachmentCount: imageAttachments.length,
|
|
824
|
-
};
|
|
825
|
-
await writeFile(join(channelDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
|
|
1011
|
+
pathContext = prepared.pathContext;
|
|
826
1012
|
addLifecycleBreadcrumb("agent.prompt.sent", {
|
|
827
1013
|
provider: model.provider,
|
|
828
1014
|
model: agentConfig.model,
|
|
829
|
-
channel_id:
|
|
1015
|
+
channel_id: prepared.sessionConversation,
|
|
830
1016
|
session_id: sessionUuid,
|
|
831
1017
|
attachment_count: message.attachments?.length ?? 0,
|
|
832
|
-
image_attachment_count: imageAttachments.length,
|
|
1018
|
+
image_attachment_count: prepared.imageAttachments.length,
|
|
833
1019
|
});
|
|
834
|
-
await session.prompt(userMessage, imageAttachments.length > 0 ? { images: imageAttachments } : undefined);
|
|
1020
|
+
await session.prompt(prepared.userMessage, prepared.imageAttachments.length > 0 ? { images: prepared.imageAttachments } : undefined);
|
|
835
1021
|
// Wait for queued messages
|
|
836
|
-
await
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
log.logWarning("Failed to post error message", errMsg);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
else {
|
|
853
|
-
// Final message update
|
|
854
|
-
const messages = session.messages;
|
|
855
|
-
const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
|
|
856
|
-
const finalText = lastAssistant?.content
|
|
857
|
-
.filter((c) => c.type === "text")
|
|
858
|
-
.map((c) => c.text)
|
|
859
|
-
.join("\n") || "";
|
|
860
|
-
// Check for [SILENT] marker - delete message and thread instead of posting
|
|
861
|
-
if (finalText.trim() === "[SILENT]" || finalText.trim().startsWith("[SILENT]")) {
|
|
862
|
-
try {
|
|
863
|
-
await responseCtx.deleteResponse();
|
|
864
|
-
log.logInfo("Silent response - deleted message and thread");
|
|
865
|
-
}
|
|
866
|
-
catch (err) {
|
|
867
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
868
|
-
log.logWarning("Failed to delete message for silent response", errMsg);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
else if (finalText.trim()) {
|
|
872
|
-
try {
|
|
873
|
-
const mainText = finalText.length > SLACK_MAX_LENGTH
|
|
874
|
-
? `${finalText.substring(0, SLACK_MAX_LENGTH - 50)}\n\n_(see thread for full response)_`
|
|
875
|
-
: finalText;
|
|
876
|
-
await responseCtx.replaceResponse(mainText);
|
|
877
|
-
}
|
|
878
|
-
catch (err) {
|
|
879
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
880
|
-
log.logWarning("Failed to replace message with final text", errMsg);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
// Log usage summary with context info
|
|
885
|
-
if (runState.totalUsage.cost.total > 0) {
|
|
886
|
-
// Get last non-aborted assistant message for context calculation
|
|
887
|
-
const messages = session.messages;
|
|
888
|
-
const lastAssistantMessage = messages
|
|
889
|
-
.slice()
|
|
890
|
-
.reverse()
|
|
891
|
-
.find((m) => m.role === "assistant" && m.stopReason !== "aborted");
|
|
892
|
-
const contextTokens = lastAssistantMessage
|
|
893
|
-
? lastAssistantMessage.usage.input +
|
|
894
|
-
lastAssistantMessage.usage.output +
|
|
895
|
-
lastAssistantMessage.usage.cacheRead +
|
|
896
|
-
lastAssistantMessage.usage.cacheWrite
|
|
897
|
-
: 0;
|
|
898
|
-
const contextWindow = model.contextWindow || 200000;
|
|
899
|
-
// Run-level Sentry metrics
|
|
900
|
-
const { totalUsage } = runState;
|
|
901
|
-
const runMetricAttributes = metricAttributes({
|
|
902
|
-
provider: model.provider,
|
|
903
|
-
model: agentConfig.model,
|
|
904
|
-
channel_id: sessionChannel,
|
|
905
|
-
session_id: sessionUuid,
|
|
906
|
-
stop_reason: runState.stopReason,
|
|
907
|
-
llm_calls: runState.llmCallCount,
|
|
908
|
-
});
|
|
909
|
-
Sentry.metrics.distribution("agent.run.tokens_in", totalUsage.input, {
|
|
910
|
-
attributes: runMetricAttributes,
|
|
911
|
-
});
|
|
912
|
-
Sentry.metrics.distribution("agent.run.tokens_out", totalUsage.output, {
|
|
913
|
-
attributes: runMetricAttributes,
|
|
914
|
-
});
|
|
915
|
-
Sentry.metrics.distribution("agent.run.cache_read", totalUsage.cacheRead, {
|
|
916
|
-
attributes: runMetricAttributes,
|
|
917
|
-
});
|
|
918
|
-
Sentry.metrics.distribution("agent.run.cache_write", totalUsage.cacheWrite, {
|
|
919
|
-
attributes: runMetricAttributes,
|
|
920
|
-
});
|
|
921
|
-
Sentry.metrics.distribution("agent.run.cost", totalUsage.cost.total, {
|
|
922
|
-
attributes: runMetricAttributes,
|
|
923
|
-
});
|
|
924
|
-
Sentry.metrics.gauge("agent.context.utilization", contextTokens / contextWindow, {
|
|
925
|
-
unit: "ratio",
|
|
926
|
-
attributes: runMetricAttributes,
|
|
927
|
-
});
|
|
928
|
-
const summary = log.logUsageSummary(runState.logCtx, runState.totalUsage, contextTokens, contextWindow);
|
|
929
|
-
// Split long summaries to avoid msg_too_long
|
|
930
|
-
const summaryParts = splitForSlack(summary);
|
|
931
|
-
for (const part of summaryParts) {
|
|
932
|
-
runState.queue.enqueue(() => responseCtx.respondInThread(part, { style: "muted" }), "usage summary");
|
|
933
|
-
}
|
|
934
|
-
await queueChain;
|
|
935
|
-
}
|
|
1022
|
+
await prepared.runQueue.wait();
|
|
1023
|
+
await finalizeRunResponse(responseCtx, session, runState);
|
|
1024
|
+
await reportUsageSummary({
|
|
1025
|
+
session,
|
|
1026
|
+
runState,
|
|
1027
|
+
responseCtx,
|
|
1028
|
+
platform,
|
|
1029
|
+
model,
|
|
1030
|
+
agentConfig,
|
|
1031
|
+
sessionConversation: prepared.sessionConversation,
|
|
1032
|
+
sessionUuid,
|
|
1033
|
+
waitForQueue: () => prepared.runQueue.wait(),
|
|
1034
|
+
});
|
|
936
1035
|
// Clear run state
|
|
937
1036
|
runState.responseCtx = null;
|
|
938
1037
|
runState.logCtx = null;
|
|
@@ -957,19 +1056,10 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
957
1056
|
},
|
|
958
1057
|
};
|
|
959
1058
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
function
|
|
964
|
-
|
|
965
|
-
const prefix = `/workspace/${channelId}/`;
|
|
966
|
-
if (containerPath.startsWith(prefix)) {
|
|
967
|
-
return join(channelDir, containerPath.slice(prefix.length));
|
|
968
|
-
}
|
|
969
|
-
if (containerPath.startsWith("/workspace/")) {
|
|
970
|
-
return join(channelDir, "..", containerPath.slice("/workspace/".length));
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
return containerPath;
|
|
1059
|
+
export function translateRuntimePathToHost(runtimePath, pathContext) {
|
|
1060
|
+
return pathContext.runtimeToHostPath?.(runtimePath) ?? runtimePath;
|
|
1061
|
+
}
|
|
1062
|
+
export function buildInitialPathContextForTest(sandboxConfig, hostWorkspaceRoot) {
|
|
1063
|
+
return getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot);
|
|
974
1064
|
}
|
|
975
1065
|
//# sourceMappingURL=agent.js.map
|