@geminixiang/mama 0.1.10 → 0.2.0-beta.1
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 +80 -23
- package/dist/adapter.d.ts +11 -9
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +2 -2
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +33 -21
- 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 +20 -13
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +13 -4
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +98 -43
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +25 -20
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +4 -2
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +143 -58
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +124 -29
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts +7 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +303 -89
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts +63 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +94 -0
- package/dist/bindings.js.map +1 -0
- package/dist/config.d.ts +34 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +98 -38
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +23 -14
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +4 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +20 -5
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +20 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +51 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/instrument.d.ts +2 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +14 -0
- package/dist/instrument.js.map +1 -0
- package/dist/link-server.d.ts +16 -0
- package/dist/link-server.d.ts.map +1 -0
- package/dist/link-server.js +839 -0
- package/dist/link-server.js.map +1 -0
- package/dist/link-token.d.ts +32 -0
- package/dist/link-token.d.ts.map +1 -0
- package/dist/link-token.js +68 -0
- package/dist/link-token.js.map +1 -0
- package/dist/log.d.ts +3 -2
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +10 -9
- package/dist/log.js.map +1 -1
- package/dist/login.d.ts +29 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +164 -0
- package/dist/login.js.map +1 -0
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +322 -82
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +93 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +336 -0
- package/dist/provisioner.js.map +1 -0
- package/dist/sandbox/container.d.ts +15 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +122 -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 +16 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +206 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +12 -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 +20 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +51 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/types.d.ts +51 -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 +31 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +205 -0
- package/dist/sentry.js.map +1 -0
- package/dist/session-store.d.ts +72 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +186 -0
- package/dist/session-store.js.map +1 -0
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +8 -8
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +21 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +103 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +6 -1
- 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/ui-copy.d.ts +11 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +33 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +10 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +58 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +106 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +389 -0
- package/dist/vault.js.map +1 -0
- package/dist/vault.test.d.ts +2 -0
- package/dist/vault.test.d.ts.map +1 -0
- package/dist/vault.test.js +67 -0
- package/dist/vault.test.js.map +1 -0
- package/package.json +13 -11
package/dist/agent.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import { getModel } from "@mariozechner/pi-ai";
|
|
3
|
-
import { AgentSession, AuthStorage, convertToLlm,
|
|
4
|
-
import { existsSync,
|
|
3
|
+
import { AgentSession, AuthStorage, convertToLlm, DefaultResourceLoader, formatSkillsForPrompt, getAgentDir, loadSkillsFromDir, ModelRegistry, } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
6
6
|
import { homedir } from "os";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { loadAgentConfig } from "./config.js";
|
|
9
9
|
import { createMamaSettingsManager, syncLogToSessionManager } from "./context.js";
|
|
10
|
+
import { ActorExecutionResolver } from "./execution-resolver.js";
|
|
10
11
|
import * as log from "./log.js";
|
|
11
12
|
import { createExecutor } from "./sandbox.js";
|
|
13
|
+
import { addLifecycleBreadcrumb, metricAttributes } from "./sentry.js";
|
|
14
|
+
import { createManagedSessionFileAtPath, extractSessionSuffix, extractSessionUuid, forkThreadSessionFile, getChannelSessionDir, getThreadSessionFile, openManagedSession, resolveChannelSessionFile, resolveManagedSessionFile, tryResolveThreadSession, } from "./session-store.js";
|
|
12
15
|
import { createMamaTools } from "./tools/index.js";
|
|
16
|
+
import * as Sentry from "@sentry/node";
|
|
13
17
|
const IMAGE_MIME_TYPES = {
|
|
14
18
|
jpg: "image/jpeg",
|
|
15
19
|
jpeg: "image/jpeg",
|
|
@@ -20,10 +24,10 @@ const IMAGE_MIME_TYPES = {
|
|
|
20
24
|
function getImageMimeType(filename) {
|
|
21
25
|
return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""];
|
|
22
26
|
}
|
|
23
|
-
async function getMemory(
|
|
27
|
+
async function getMemory(conversationDir) {
|
|
24
28
|
const parts = [];
|
|
25
|
-
// Read workspace-level memory (shared across all
|
|
26
|
-
const workspaceMemoryPath = join(
|
|
29
|
+
// Read workspace-level memory (shared across all conversations)
|
|
30
|
+
const workspaceMemoryPath = join(conversationDir, "..", "MEMORY.md");
|
|
27
31
|
if (existsSync(workspaceMemoryPath)) {
|
|
28
32
|
try {
|
|
29
33
|
const content = (await readFile(workspaceMemoryPath, "utf-8")).trim();
|
|
@@ -35,17 +39,17 @@ async function getMemory(channelDir) {
|
|
|
35
39
|
log.logWarning("Failed to read workspace memory", `${workspaceMemoryPath}: ${error}`);
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
|
-
// Read
|
|
39
|
-
const
|
|
40
|
-
if (existsSync(
|
|
42
|
+
// Read conversation-specific memory
|
|
43
|
+
const conversationMemoryPath = join(conversationDir, "MEMORY.md");
|
|
44
|
+
if (existsSync(conversationMemoryPath)) {
|
|
41
45
|
try {
|
|
42
|
-
const content = (await readFile(
|
|
46
|
+
const content = (await readFile(conversationMemoryPath, "utf-8")).trim();
|
|
43
47
|
if (content) {
|
|
44
|
-
parts.push(`###
|
|
48
|
+
parts.push(`### Conversation-Specific Memory\n${content}`);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
catch (error) {
|
|
48
|
-
log.logWarning("Failed to read
|
|
52
|
+
log.logWarning("Failed to read conversation memory", `${conversationMemoryPath}: ${error}`);
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
if (parts.length === 0) {
|
|
@@ -53,12 +57,12 @@ async function getMemory(channelDir) {
|
|
|
53
57
|
}
|
|
54
58
|
return parts.join("\n\n");
|
|
55
59
|
}
|
|
56
|
-
function loadMamaSkills(
|
|
60
|
+
function loadMamaSkills(conversationDir, workspacePath) {
|
|
57
61
|
const skillMap = new Map();
|
|
58
|
-
//
|
|
62
|
+
// conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)
|
|
59
63
|
// hostWorkspacePath is the parent directory on host
|
|
60
64
|
// workspacePath is the container path (e.g., /workspace)
|
|
61
|
-
const hostWorkspacePath = join(
|
|
65
|
+
const hostWorkspacePath = join(conversationDir, "..");
|
|
62
66
|
// Helper to translate host paths to container paths
|
|
63
67
|
const translatePath = (hostPath) => {
|
|
64
68
|
if (hostPath.startsWith(hostWorkspacePath)) {
|
|
@@ -74,20 +78,20 @@ function loadMamaSkills(channelDir, workspacePath) {
|
|
|
74
78
|
skill.baseDir = translatePath(skill.baseDir);
|
|
75
79
|
skillMap.set(skill.name, skill);
|
|
76
80
|
}
|
|
77
|
-
// Load
|
|
78
|
-
const
|
|
79
|
-
for (const skill of loadSkillsFromDir({ dir:
|
|
81
|
+
// Load conversation-specific skills (override workspace skills on collision)
|
|
82
|
+
const conversationSkillsDir = join(conversationDir, "skills");
|
|
83
|
+
for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: "channel" }).skills) {
|
|
80
84
|
skill.filePath = translatePath(skill.filePath);
|
|
81
85
|
skill.baseDir = translatePath(skill.baseDir);
|
|
82
86
|
skillMap.set(skill.name, skill);
|
|
83
87
|
}
|
|
84
88
|
return Array.from(skillMap.values());
|
|
85
89
|
}
|
|
86
|
-
function buildSystemPrompt(workspacePath,
|
|
87
|
-
const
|
|
88
|
-
const
|
|
90
|
+
function buildSystemPrompt(workspacePath, conversationId, currentUserId, memory, sandboxConfig, platform, skills) {
|
|
91
|
+
const conversationPath = `${workspacePath}/${conversationId}`;
|
|
92
|
+
const isContainer = sandboxConfig.type === "container" || sandboxConfig.type === "image";
|
|
89
93
|
const isFirecracker = sandboxConfig.type === "firecracker";
|
|
90
|
-
// Format
|
|
94
|
+
// Format platform conversation mappings
|
|
91
95
|
const channelMappings = platform.channels.length > 0
|
|
92
96
|
? platform.channels.map((c) => `${c.id}\t#${c.name}`).join("\n")
|
|
93
97
|
: "(no channels loaded)";
|
|
@@ -95,8 +99,8 @@ function buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, plat
|
|
|
95
99
|
const userMappings = platform.users.length > 0
|
|
96
100
|
? platform.users.map((u) => `${u.id}\t@${u.userName}\t${u.displayName}`).join("\n")
|
|
97
101
|
: "(no users loaded)";
|
|
98
|
-
const envDescription =
|
|
99
|
-
? `You are running inside a Docker
|
|
102
|
+
const envDescription = isContainer
|
|
103
|
+
? `You are running inside a container (Docker runtime, Alpine Linux).
|
|
100
104
|
- Bash working directory: / (use cd or absolute paths)
|
|
101
105
|
- Install tools with: apk add <package>
|
|
102
106
|
- Your changes persist across sessions`
|
|
@@ -132,18 +136,18 @@ ${envDescription}
|
|
|
132
136
|
${workspacePath}/
|
|
133
137
|
├── MEMORY.md # Global memory (all channels)
|
|
134
138
|
├── skills/ # Global CLI tools you create
|
|
135
|
-
└── ${
|
|
136
|
-
├── MEMORY.md #
|
|
139
|
+
└── ${conversationId}/ # This conversation
|
|
140
|
+
├── MEMORY.md # Conversation-specific memory
|
|
137
141
|
├── log.jsonl # Message history (no tool results)
|
|
138
142
|
├── attachments/ # User-shared files
|
|
139
143
|
├── scratch/ # Your working directory
|
|
140
|
-
└── skills/ #
|
|
144
|
+
└── skills/ # Conversation-specific tools
|
|
141
145
|
|
|
142
146
|
## Skills (Custom CLI Tools)
|
|
143
147
|
You can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).
|
|
144
148
|
|
|
145
149
|
### Creating Skills
|
|
146
|
-
Store in \`${workspacePath}/skills/<name>/\` (global) or \`${
|
|
150
|
+
Store in \`${workspacePath}/skills/<name>/\` (global) or \`${conversationPath}/skills/<name>/\` (conversation-specific).
|
|
147
151
|
Each skill directory needs a \`SKILL.md\` with YAML frontmatter:
|
|
148
152
|
|
|
149
153
|
\`\`\`markdown
|
|
@@ -170,19 +174,21 @@ You can schedule events that wake you up at specific times or when external thin
|
|
|
170
174
|
|
|
171
175
|
**Immediate** - Triggers as soon as harness sees the file. Use in scripts/webhooks to signal external events.
|
|
172
176
|
\`\`\`json
|
|
173
|
-
{"type": "immediate", "platform": "${platform.name}", "channelId": "${
|
|
177
|
+
{"type": "immediate", "platform": "${platform.name}", "channelId": "${conversationId}", "userId": "<requester userId>", "text": "New GitHub issue opened"}
|
|
174
178
|
\`\`\`
|
|
175
179
|
|
|
176
180
|
**One-shot** - Triggers once at a specific time. Use for reminders.
|
|
177
181
|
\`\`\`json
|
|
178
|
-
{"type": "one-shot", "platform": "${platform.name}", "channelId": "${
|
|
182
|
+
{"type": "one-shot", "platform": "${platform.name}", "channelId": "${conversationId}", "userId": "<requester userId>", "text": "Remind Mario about dentist", "at": "2025-12-15T09:00:00+01:00"}
|
|
179
183
|
\`\`\`
|
|
180
184
|
|
|
181
185
|
**Periodic** - Triggers on a cron schedule. Use for recurring tasks.
|
|
182
186
|
\`\`\`json
|
|
183
|
-
{"type": "periodic", "platform": "${platform.name}", "channelId": "${
|
|
187
|
+
{"type": "periodic", "platform": "${platform.name}", "channelId": "${conversationId}", "userId": "<requester userId>", "text": "Check inbox and summarize", "schedule": "0 9 * * 1-5", "timezone": "${Intl.DateTimeFormat().resolvedOptions().timeZone}"}
|
|
184
188
|
\`\`\`
|
|
185
189
|
|
|
190
|
+
Set \`userId\` to the platform userId of whoever asked for the event (look it up in the user mappings above). When the event fires, tool execution will route to the sandbox vault selection for that user so the right credentials are available. In shared container mode, all events use the container's single shared vault.
|
|
191
|
+
|
|
186
192
|
### Cron Format
|
|
187
193
|
\`minute hour day-of-month month day-of-week\`
|
|
188
194
|
- \`0 9 * * *\` = daily at 9:00
|
|
@@ -197,10 +203,19 @@ All \`at\` timestamps must include offset (e.g., \`+01:00\`). Periodic events us
|
|
|
197
203
|
Set \`platform\` to the target bot platform (\`${platform.name}\` for this conversation). When only one platform is running, omitting \`platform\` is allowed for backward compatibility, but include it by default to avoid ambiguity.
|
|
198
204
|
|
|
199
205
|
### Creating Events
|
|
206
|
+
Prefer the \`event\` tool. It automatically writes to the correct events directory and fills the current \`platform\`, \`channelId\`, and requester \`userId\`.
|
|
207
|
+
Do not use \`bash\` or \`write\` to hand-create JSON files in \`/events\` unless the user explicitly asks for manual file editing.
|
|
208
|
+
|
|
209
|
+
Current conversation defaults:
|
|
210
|
+
- \`platform\`: \`${platform.name}\`
|
|
211
|
+
- \`channelId\`: \`${conversationId}\`
|
|
212
|
+
- \`userId\`: \`${currentUserId ?? "unknown"}\`
|
|
213
|
+
|
|
214
|
+
Manual file creation is fallback only:
|
|
200
215
|
Use unique filenames to avoid overwriting existing events. Include a timestamp or random suffix:
|
|
201
216
|
\`\`\`bash
|
|
202
217
|
cat > ${workspacePath}/events/dentist-reminder-$(date +%s).json << 'EOF'
|
|
203
|
-
{"type": "one-shot", "platform": "${platform.name}", "channelId": "${
|
|
218
|
+
{"type": "one-shot", "platform": "${platform.name}", "channelId": "${conversationId}", "userId": "<requester userId>", "text": "Dentist tomorrow", "at": "2025-12-14T09:00:00+01:00"}
|
|
204
219
|
EOF
|
|
205
220
|
\`\`\`
|
|
206
221
|
Or check if file exists first before creating.
|
|
@@ -229,7 +244,7 @@ Maximum 5 events can be queued. Don't create excessive immediate or periodic eve
|
|
|
229
244
|
## Memory
|
|
230
245
|
Write to MEMORY.md files to persist context across conversations.
|
|
231
246
|
- Global (${workspacePath}/MEMORY.md): skills, preferences, project info
|
|
232
|
-
-
|
|
247
|
+
- Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work
|
|
233
248
|
Update when you learn something important or when asked to remember something.
|
|
234
249
|
|
|
235
250
|
### Current Memory
|
|
@@ -247,7 +262,7 @@ Update this file whenever you modify the environment. On fresh container, read i
|
|
|
247
262
|
## Log Queries (for older history)
|
|
248
263
|
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","isBot":false}\`
|
|
249
264
|
The log contains user messages and your final responses (not tool calls/results).
|
|
250
|
-
${
|
|
265
|
+
${isContainer ? "Install jq: apk add jq" : ""}
|
|
251
266
|
${isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
252
267
|
|
|
253
268
|
\`\`\`bash
|
|
@@ -328,50 +343,106 @@ function formatToolArgsForSlack(_toolName, args) {
|
|
|
328
343
|
// Agent runner
|
|
329
344
|
// ============================================================================
|
|
330
345
|
/**
|
|
331
|
-
* Create a new AgentRunner for a
|
|
346
|
+
* Create a new AgentRunner for a conversation.
|
|
332
347
|
* Sets up the session and subscribes to events once.
|
|
333
348
|
*
|
|
334
|
-
* Runner caching is handled by the caller (
|
|
349
|
+
* Runner caching is handled by the caller (conversationStates in main.ts).
|
|
335
350
|
* This is a stateless factory function.
|
|
336
351
|
*/
|
|
337
|
-
export async function createRunner(sandboxConfig, sessionKey,
|
|
338
|
-
const agentConfig = loadAgentConfig(workspaceDir);
|
|
352
|
+
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, vaultManager, bindingStore, provisioner, stateDir) {
|
|
353
|
+
const agentConfig = loadAgentConfig(stateDir ?? workspaceDir);
|
|
339
354
|
// Initialize logger with settings from config
|
|
340
355
|
log.initLogger({
|
|
341
356
|
logFormat: agentConfig.logFormat,
|
|
342
357
|
logLevel: agentConfig.logLevel,
|
|
343
358
|
});
|
|
344
|
-
const
|
|
345
|
-
|
|
359
|
+
const executionResolver = vaultManager &&
|
|
360
|
+
(vaultManager.isEnabled() ||
|
|
361
|
+
!!bindingStore ||
|
|
362
|
+
sandboxConfig.type === "image" ||
|
|
363
|
+
sandboxConfig.type === "container")
|
|
364
|
+
? new ActorExecutionResolver(sandboxConfig, vaultManager, bindingStore, provisioner)
|
|
365
|
+
: undefined;
|
|
366
|
+
let activeExecutor = executionResolver !== undefined
|
|
367
|
+
? createExecutor({ type: "host" })
|
|
368
|
+
: createExecutor(sandboxConfig);
|
|
369
|
+
const executor = {
|
|
370
|
+
exec(command, options) {
|
|
371
|
+
return activeExecutor.exec(command, options);
|
|
372
|
+
},
|
|
373
|
+
getWorkspacePath(hostPath) {
|
|
374
|
+
return activeExecutor.getWorkspacePath(hostPath);
|
|
375
|
+
},
|
|
376
|
+
getSandboxConfig() {
|
|
377
|
+
return activeExecutor.getSandboxConfig();
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
const workspaceBase = conversationDir.replace(`/${conversationId}`, "");
|
|
381
|
+
// Compute workspace path from the current executor. This may change per run.
|
|
382
|
+
const getWorkspacePath = () => executor.getWorkspacePath(workspaceBase);
|
|
383
|
+
let workspacePath = getWorkspacePath();
|
|
346
384
|
// Create tools (per-runner, with per-runner upload function setter)
|
|
347
|
-
const { tools, setUploadFunction } = createMamaTools(executor);
|
|
385
|
+
const { tools, setUploadFunction, setEventContext } = createMamaTools(executor, workspaceDir);
|
|
348
386
|
// Resolve model from config
|
|
349
387
|
// Use 'as any' cast because agentConfig.provider/model are plain strings,
|
|
350
388
|
// while getModel() has constrained generic types for known providers.
|
|
351
389
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
352
390
|
const model = getModel(agentConfig.provider, agentConfig.model);
|
|
353
391
|
// Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
|
|
354
|
-
const memory = await getMemory(
|
|
355
|
-
const skills = loadMamaSkills(
|
|
392
|
+
const memory = await getMemory(conversationDir);
|
|
393
|
+
const skills = loadMamaSkills(conversationDir, workspacePath);
|
|
356
394
|
const emptyPlatform = {
|
|
357
395
|
name: "slack",
|
|
358
396
|
formattingGuide: "",
|
|
359
397
|
channels: [],
|
|
360
398
|
users: [],
|
|
361
399
|
};
|
|
362
|
-
const systemPrompt = buildSystemPrompt(workspacePath,
|
|
363
|
-
// Create session manager and settings manager
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
const sessionDir =
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
400
|
+
const systemPrompt = buildSystemPrompt(workspacePath, conversationId, undefined, memory, sandboxConfig, emptyPlatform, skills);
|
|
401
|
+
// Create session manager and settings manager.
|
|
402
|
+
// Top-level conversation sessions use {conversationDir}/sessions/current.
|
|
403
|
+
// Thread sessions use fixed files: {conversationDir}/sessions/{threadTs}.jsonl.
|
|
404
|
+
const sessionDir = getChannelSessionDir(conversationDir);
|
|
405
|
+
const isThread = sessionKey.includes(":");
|
|
406
|
+
let sessionManager;
|
|
407
|
+
let sessionFile;
|
|
408
|
+
if (isThread) {
|
|
409
|
+
const threadFile = getThreadSessionFile(conversationDir, sessionKey);
|
|
410
|
+
const existing = tryResolveThreadSession(threadFile);
|
|
411
|
+
if (existing) {
|
|
412
|
+
sessionFile = existing;
|
|
413
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
const conversationSource = resolveChannelSessionFile(conversationDir);
|
|
417
|
+
if (conversationSource) {
|
|
418
|
+
try {
|
|
419
|
+
sessionFile = forkThreadSessionFile(conversationSource, threadFile, conversationDir);
|
|
420
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
sessionFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
424
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
sessionFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
429
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
// Top-level conversation session: resolve the current session file.
|
|
435
|
+
sessionFile = resolveManagedSessionFile(sessionDir, conversationDir);
|
|
436
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
437
|
+
}
|
|
438
|
+
const sessionUuid = extractSessionUuid(sessionFile);
|
|
439
|
+
// Used for Slack thread filtering — for non-Slack platforms this is effectively a no-op
|
|
440
|
+
const rootTs = extractSessionSuffix(sessionKey);
|
|
441
|
+
const settingsManager = createMamaSettingsManager(join(conversationDir, ".."));
|
|
371
442
|
// Create AuthStorage and ModelRegistry
|
|
372
443
|
// Auth stored outside workspace so agent can't access it
|
|
373
444
|
const authStorage = AuthStorage.create(join(homedir(), ".pi", "mama", "auth.json"));
|
|
374
|
-
const modelRegistry =
|
|
445
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
375
446
|
// Create agent
|
|
376
447
|
const agent = new Agent({
|
|
377
448
|
initialState: {
|
|
@@ -391,27 +462,37 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
391
462
|
// Load existing messages
|
|
392
463
|
const loadedSession = sessionManager.buildSessionContext();
|
|
393
464
|
if (loadedSession.messages.length > 0) {
|
|
394
|
-
agent.
|
|
395
|
-
log.logInfo(`[${
|
|
465
|
+
agent.state.messages = loadedSession.messages;
|
|
466
|
+
log.logInfo(`[${conversationId}] Loaded ${loadedSession.messages.length} messages from session file`);
|
|
467
|
+
}
|
|
468
|
+
// Load extensions, skills, prompts, themes via DefaultResourceLoader
|
|
469
|
+
// This reads ~/.pi/agent/settings.json (packages, extensions enable/disable)
|
|
470
|
+
// and discovers resources from standard locations + npm/git packages.
|
|
471
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
472
|
+
cwd: workspaceDir,
|
|
473
|
+
agentDir: getAgentDir(),
|
|
474
|
+
systemPrompt,
|
|
475
|
+
});
|
|
476
|
+
try {
|
|
477
|
+
await resourceLoader.reload();
|
|
478
|
+
const extResult = resourceLoader.getExtensions();
|
|
479
|
+
if (extResult.errors.length > 0) {
|
|
480
|
+
for (const err of extResult.errors) {
|
|
481
|
+
log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
log.logInfo(`[${conversationId}] Loaded ${extResult.extensions.length} extension(s): ${extResult.extensions.map((e) => e.path).join(", ")}`);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
log.logWarning(`[${conversationId}] Failed to load resources`, String(error));
|
|
396
488
|
}
|
|
397
|
-
const resourceLoader = {
|
|
398
|
-
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
|
|
399
|
-
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
400
|
-
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
401
|
-
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
402
|
-
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
403
|
-
getSystemPrompt: () => systemPrompt,
|
|
404
|
-
getAppendSystemPrompt: () => [],
|
|
405
|
-
extendResources: () => { },
|
|
406
|
-
reload: async () => { },
|
|
407
|
-
};
|
|
408
489
|
const baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
409
490
|
// Create AgentSession wrapper
|
|
410
491
|
const session = new AgentSession({
|
|
411
492
|
agent,
|
|
412
493
|
sessionManager,
|
|
413
494
|
settingsManager,
|
|
414
|
-
cwd:
|
|
495
|
+
cwd: workspaceDir,
|
|
415
496
|
modelRegistry,
|
|
416
497
|
resourceLoader,
|
|
417
498
|
baseToolsOverride,
|
|
@@ -429,6 +510,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
429
510
|
cacheWrite: 0,
|
|
430
511
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
431
512
|
},
|
|
513
|
+
llmCallCount: 0,
|
|
432
514
|
stopReason: "stop",
|
|
433
515
|
errorMessage: undefined,
|
|
434
516
|
};
|
|
@@ -438,6 +520,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
438
520
|
if (!runState.responseCtx || !runState.logCtx || !runState.queue)
|
|
439
521
|
return;
|
|
440
522
|
const { responseCtx, logCtx, queue, pendingTools } = runState;
|
|
523
|
+
const baseAttrs = { channel_id: logCtx.conversationId, session_id: logCtx.sessionId };
|
|
441
524
|
if (event.type === "tool_execution_start") {
|
|
442
525
|
const agentEvent = event;
|
|
443
526
|
const args = agentEvent.args;
|
|
@@ -447,6 +530,10 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
447
530
|
args: agentEvent.args,
|
|
448
531
|
startTime: Date.now(),
|
|
449
532
|
});
|
|
533
|
+
addLifecycleBreadcrumb("agent.tool.started", {
|
|
534
|
+
tool: agentEvent.toolName,
|
|
535
|
+
...baseAttrs,
|
|
536
|
+
});
|
|
450
537
|
log.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args);
|
|
451
538
|
// Tool labels are omitted from the main message to reduce Slack noise.
|
|
452
539
|
// Tool execution details are still posted to the thread (see tool_execution_end).
|
|
@@ -457,6 +544,26 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
457
544
|
const pending = pendingTools.get(agentEvent.toolCallId);
|
|
458
545
|
pendingTools.delete(agentEvent.toolCallId);
|
|
459
546
|
const durationMs = pending ? Date.now() - pending.startTime : 0;
|
|
547
|
+
Sentry.metrics.count("agent.tool.calls", 1, {
|
|
548
|
+
attributes: metricAttributes({
|
|
549
|
+
tool: agentEvent.toolName,
|
|
550
|
+
error: String(agentEvent.isError),
|
|
551
|
+
...baseAttrs,
|
|
552
|
+
}),
|
|
553
|
+
});
|
|
554
|
+
Sentry.metrics.distribution("agent.tool.duration", durationMs, {
|
|
555
|
+
unit: "millisecond",
|
|
556
|
+
attributes: metricAttributes({
|
|
557
|
+
tool: agentEvent.toolName,
|
|
558
|
+
...baseAttrs,
|
|
559
|
+
}),
|
|
560
|
+
});
|
|
561
|
+
addLifecycleBreadcrumb("agent.tool.completed", {
|
|
562
|
+
tool: agentEvent.toolName,
|
|
563
|
+
error: agentEvent.isError,
|
|
564
|
+
duration_ms: durationMs,
|
|
565
|
+
...baseAttrs,
|
|
566
|
+
});
|
|
460
567
|
if (agentEvent.isError) {
|
|
461
568
|
log.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);
|
|
462
569
|
}
|
|
@@ -489,6 +596,13 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
489
596
|
else if (event.type === "message_start") {
|
|
490
597
|
const agentEvent = event;
|
|
491
598
|
if (agentEvent.message.role === "assistant") {
|
|
599
|
+
runState.llmCallCount += 1;
|
|
600
|
+
addLifecycleBreadcrumb("agent.llm.call.started", {
|
|
601
|
+
call_index: runState.llmCallCount,
|
|
602
|
+
provider: model.provider,
|
|
603
|
+
model: agentConfig.model,
|
|
604
|
+
...baseAttrs,
|
|
605
|
+
});
|
|
492
606
|
log.logResponseStart(logCtx);
|
|
493
607
|
}
|
|
494
608
|
}
|
|
@@ -512,6 +626,44 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
512
626
|
runState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;
|
|
513
627
|
runState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;
|
|
514
628
|
runState.totalUsage.cost.total += assistantMsg.usage.cost.total;
|
|
629
|
+
// Per-turn LLM metrics
|
|
630
|
+
const llmAttributes = metricAttributes({
|
|
631
|
+
provider: model.provider,
|
|
632
|
+
model: agentConfig.model,
|
|
633
|
+
...baseAttrs,
|
|
634
|
+
stop_reason: assistantMsg.stopReason,
|
|
635
|
+
error: Boolean(assistantMsg.errorMessage),
|
|
636
|
+
});
|
|
637
|
+
Sentry.metrics.count("agent.llm.calls", 1, { attributes: llmAttributes });
|
|
638
|
+
Sentry.metrics.distribution("agent.llm.tokens_in", assistantMsg.usage.input, {
|
|
639
|
+
attributes: llmAttributes,
|
|
640
|
+
});
|
|
641
|
+
Sentry.metrics.distribution("agent.llm.tokens_out", assistantMsg.usage.output, {
|
|
642
|
+
attributes: llmAttributes,
|
|
643
|
+
});
|
|
644
|
+
if (assistantMsg.usage.cacheRead > 0) {
|
|
645
|
+
Sentry.metrics.distribution("agent.llm.cache_read", assistantMsg.usage.cacheRead, {
|
|
646
|
+
attributes: llmAttributes,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
if (assistantMsg.usage.cacheWrite > 0) {
|
|
650
|
+
Sentry.metrics.distribution("agent.llm.cache_write", assistantMsg.usage.cacheWrite, {
|
|
651
|
+
attributes: llmAttributes,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
Sentry.metrics.distribution("agent.llm.cost_per_turn", assistantMsg.usage.cost.total, {
|
|
655
|
+
attributes: llmAttributes,
|
|
656
|
+
});
|
|
657
|
+
addLifecycleBreadcrumb("agent.llm.call.completed", {
|
|
658
|
+
call_index: runState.llmCallCount,
|
|
659
|
+
provider: model.provider,
|
|
660
|
+
model: agentConfig.model,
|
|
661
|
+
stop_reason: assistantMsg.stopReason,
|
|
662
|
+
error: Boolean(assistantMsg.errorMessage),
|
|
663
|
+
input_tokens: assistantMsg.usage.input,
|
|
664
|
+
output_tokens: assistantMsg.usage.output,
|
|
665
|
+
cost_total_usd: assistantMsg.usage.cost.total,
|
|
666
|
+
});
|
|
515
667
|
}
|
|
516
668
|
const content = agentEvent.message.content;
|
|
517
669
|
const thinkingParts = [];
|
|
@@ -578,41 +730,65 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
578
730
|
};
|
|
579
731
|
return {
|
|
580
732
|
async run(message, responseCtx, platform) {
|
|
581
|
-
// Extract
|
|
582
|
-
const
|
|
583
|
-
// Ensure
|
|
584
|
-
await mkdir(
|
|
733
|
+
// Extract conversationId from sessionKey (format: "conversationId:rootTs" or just "conversationId")
|
|
734
|
+
const sessionConversationId = message.sessionKey.split(":")[0];
|
|
735
|
+
// Ensure the conversation directory exists
|
|
736
|
+
await mkdir(conversationDir, { recursive: true });
|
|
737
|
+
// Refresh vault config and clear executor cache so credential changes
|
|
738
|
+
// (env file updates, vault.json edits, token rotations) take effect.
|
|
739
|
+
// Then set the active actor BEFORE building system prompt, so workspacePath
|
|
740
|
+
// reflects the actor's sandbox type.
|
|
741
|
+
if (executionResolver) {
|
|
742
|
+
executionResolver.refresh();
|
|
743
|
+
activeExecutor = await executionResolver.resolve({
|
|
744
|
+
platform: platform.name,
|
|
745
|
+
userId: message.userId,
|
|
746
|
+
});
|
|
747
|
+
workspacePath = getWorkspacePath();
|
|
748
|
+
}
|
|
585
749
|
// Sync messages from log.jsonl that arrived while we were offline or busy
|
|
586
750
|
// Exclude the current message (it will be added via prompt())
|
|
587
751
|
// Default sync range is 10 days (handled by syncLogToSessionManager)
|
|
588
752
|
// Thread filter ensures only messages from this session's thread are synced
|
|
589
|
-
const
|
|
753
|
+
const threadFilter = message.sessionKey.includes(":")
|
|
754
|
+
? { scope: "thread", rootTs, threadTs: message.threadTs }
|
|
755
|
+
: { scope: "top-level", rootTs };
|
|
756
|
+
const syncedCount = await syncLogToSessionManager(sessionManager, conversationDir, message.id, undefined, threadFilter);
|
|
590
757
|
if (syncedCount > 0) {
|
|
591
|
-
log.logInfo(`[${
|
|
758
|
+
log.logInfo(`[${conversationId}] Synced ${syncedCount} messages from log.jsonl`);
|
|
592
759
|
}
|
|
593
|
-
// Reload messages from
|
|
594
|
-
// This picks up any messages synced above
|
|
760
|
+
// Reload messages from the session file.
|
|
761
|
+
// This picks up any messages synced above.
|
|
595
762
|
const reloadedSession = sessionManager.buildSessionContext();
|
|
596
763
|
if (reloadedSession.messages.length > 0) {
|
|
597
|
-
agent.
|
|
598
|
-
log.logInfo(`[${
|
|
764
|
+
agent.state.messages = reloadedSession.messages;
|
|
765
|
+
log.logInfo(`[${conversationId}] Reloaded ${reloadedSession.messages.length} messages from context`);
|
|
599
766
|
}
|
|
600
767
|
// Update system prompt with fresh memory, channel/user info, and skills
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
|
|
768
|
+
// Use the actual executor's sandbox config, not the initial config,
|
|
769
|
+
// to ensure accurate environment description for the model
|
|
770
|
+
const memory = await getMemory(conversationDir);
|
|
771
|
+
const skills = loadMamaSkills(conversationDir, workspacePath);
|
|
772
|
+
const actualSandboxConfig = executor.getSandboxConfig();
|
|
773
|
+
const systemPrompt = buildSystemPrompt(workspacePath, conversationId, message.userId, memory, actualSandboxConfig, platform, skills);
|
|
774
|
+
session.agent.state.systemPrompt = systemPrompt;
|
|
605
775
|
// Set up file upload function
|
|
606
776
|
setUploadFunction(async (filePath, title) => {
|
|
607
|
-
const hostPath = translateToHostPath(filePath,
|
|
777
|
+
const hostPath = translateToHostPath(filePath, conversationDir, workspacePath, conversationId);
|
|
608
778
|
await responseCtx.uploadFile(hostPath, title);
|
|
609
779
|
});
|
|
780
|
+
setEventContext({
|
|
781
|
+
platform: platform.name,
|
|
782
|
+
conversationId,
|
|
783
|
+
userId: message.userId,
|
|
784
|
+
});
|
|
610
785
|
// Reset per-run state
|
|
611
786
|
runState.responseCtx = responseCtx;
|
|
612
787
|
runState.logCtx = {
|
|
613
|
-
|
|
788
|
+
conversationId: sessionConversationId,
|
|
614
789
|
userName: message.userName,
|
|
615
|
-
|
|
790
|
+
conversationName: undefined,
|
|
791
|
+
sessionId: sessionUuid,
|
|
616
792
|
};
|
|
617
793
|
runState.pendingTools.clear();
|
|
618
794
|
runState.totalUsage = {
|
|
@@ -622,6 +798,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
622
798
|
cacheWrite: 0,
|
|
623
799
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
624
800
|
};
|
|
801
|
+
runState.llmCallCount = 0;
|
|
625
802
|
runState.stopReason = "stop";
|
|
626
803
|
runState.errorMessage = undefined;
|
|
627
804
|
// Create queue for this run
|
|
@@ -672,7 +849,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
672
849
|
const imageAttachments = [];
|
|
673
850
|
const nonImagePaths = [];
|
|
674
851
|
for (const a of message.attachments || []) {
|
|
675
|
-
// a.localPath is the path relative to the workspace
|
|
852
|
+
// a.localPath is the path relative to the workspace
|
|
676
853
|
const fullPath = `${workspacePath}/${a.localPath}`;
|
|
677
854
|
const mimeType = getImageMimeType(a.localPath);
|
|
678
855
|
if (mimeType && existsSync(fullPath)) {
|
|
@@ -701,7 +878,15 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
701
878
|
newUserMessage: userMessage,
|
|
702
879
|
imageAttachmentCount: imageAttachments.length,
|
|
703
880
|
};
|
|
704
|
-
await writeFile(join(
|
|
881
|
+
await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
|
|
882
|
+
addLifecycleBreadcrumb("agent.prompt.sent", {
|
|
883
|
+
provider: model.provider,
|
|
884
|
+
model: agentConfig.model,
|
|
885
|
+
channel_id: sessionConversationId,
|
|
886
|
+
session_id: sessionUuid,
|
|
887
|
+
attachment_count: message.attachments?.length ?? 0,
|
|
888
|
+
image_attachment_count: imageAttachments.length,
|
|
889
|
+
});
|
|
705
890
|
await session.prompt(userMessage, imageAttachments.length > 0 ? { images: imageAttachments } : undefined);
|
|
706
891
|
// Wait for queued messages
|
|
707
892
|
await queueChain;
|
|
@@ -767,6 +952,35 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
767
952
|
lastAssistantMessage.usage.cacheWrite
|
|
768
953
|
: 0;
|
|
769
954
|
const contextWindow = model.contextWindow || 200000;
|
|
955
|
+
// Run-level Sentry metrics
|
|
956
|
+
const { totalUsage } = runState;
|
|
957
|
+
const runMetricAttributes = metricAttributes({
|
|
958
|
+
provider: model.provider,
|
|
959
|
+
model: agentConfig.model,
|
|
960
|
+
channel_id: sessionConversationId,
|
|
961
|
+
session_id: sessionUuid,
|
|
962
|
+
stop_reason: runState.stopReason,
|
|
963
|
+
llm_calls: runState.llmCallCount,
|
|
964
|
+
});
|
|
965
|
+
Sentry.metrics.distribution("agent.run.tokens_in", totalUsage.input, {
|
|
966
|
+
attributes: runMetricAttributes,
|
|
967
|
+
});
|
|
968
|
+
Sentry.metrics.distribution("agent.run.tokens_out", totalUsage.output, {
|
|
969
|
+
attributes: runMetricAttributes,
|
|
970
|
+
});
|
|
971
|
+
Sentry.metrics.distribution("agent.run.cache_read", totalUsage.cacheRead, {
|
|
972
|
+
attributes: runMetricAttributes,
|
|
973
|
+
});
|
|
974
|
+
Sentry.metrics.distribution("agent.run.cache_write", totalUsage.cacheWrite, {
|
|
975
|
+
attributes: runMetricAttributes,
|
|
976
|
+
});
|
|
977
|
+
Sentry.metrics.distribution("agent.run.cost", totalUsage.cost.total, {
|
|
978
|
+
attributes: runMetricAttributes,
|
|
979
|
+
});
|
|
980
|
+
Sentry.metrics.gauge("agent.context.utilization", contextTokens / contextWindow, {
|
|
981
|
+
unit: "ratio",
|
|
982
|
+
attributes: runMetricAttributes,
|
|
983
|
+
});
|
|
770
984
|
const summary = log.logUsageSummary(runState.logCtx, runState.totalUsage, contextTokens, contextWindow);
|
|
771
985
|
// Split long summaries to avoid msg_too_long
|
|
772
986
|
const summaryParts = splitForSlack(summary);
|
|
@@ -802,14 +1016,14 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
802
1016
|
/**
|
|
803
1017
|
* Translate container path back to host path for file operations
|
|
804
1018
|
*/
|
|
805
|
-
function translateToHostPath(containerPath,
|
|
1019
|
+
function translateToHostPath(containerPath, conversationDir, workspacePath, conversationId) {
|
|
806
1020
|
if (workspacePath === "/workspace") {
|
|
807
|
-
const prefix = `/workspace/${
|
|
1021
|
+
const prefix = `/workspace/${conversationId}/`;
|
|
808
1022
|
if (containerPath.startsWith(prefix)) {
|
|
809
|
-
return join(
|
|
1023
|
+
return join(conversationDir, containerPath.slice(prefix.length));
|
|
810
1024
|
}
|
|
811
1025
|
if (containerPath.startsWith("/workspace/")) {
|
|
812
|
-
return join(
|
|
1026
|
+
return join(conversationDir, "..", containerPath.slice("/workspace/".length));
|
|
813
1027
|
}
|
|
814
1028
|
}
|
|
815
1029
|
return containerPath;
|