@geminixiang/mama 0.2.0-beta.0 → 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 +59 -19
- package/dist/adapter.d.ts +9 -7
- 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 +4 -3
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +68 -30
- 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 +22 -12
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +2 -2
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +54 -33
- 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 +61 -10
- 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 +152 -96
- 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 +32 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +71 -44
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +6 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +8 -8
- 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.map +1 -1
- package/dist/instrument.js +11 -4
- package/dist/instrument.js.map +1 -1
- 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 +2 -2
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +7 -7
- 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.map +1 -1
- package/dist/main.js +243 -56
- 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 +1 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +2 -2
- package/dist/sentry.js.map +1 -1
- package/dist/session-store.d.ts +1 -5
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +7 -10
- package/dist/session-store.js.map +1 -1
- 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 +12 -11
package/dist/agent.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import { getModel } from "@mariozechner/pi-ai";
|
|
3
|
-
import { AgentSession, AuthStorage, convertToLlm, DefaultResourceLoader, formatSkillsForPrompt, loadSkillsFromDir, ModelRegistry, } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { AgentSession, AuthStorage, convertToLlm, DefaultResourceLoader, formatSkillsForPrompt, getAgentDir, loadSkillsFromDir, ModelRegistry, } from "@mariozechner/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
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";
|
|
12
13
|
import { addLifecycleBreadcrumb, metricAttributes } from "./sentry.js";
|
|
13
|
-
import { createManagedSessionFileAtPath, extractSessionSuffix, extractSessionUuid, forkThreadSessionFile,
|
|
14
|
+
import { createManagedSessionFileAtPath, extractSessionSuffix, extractSessionUuid, forkThreadSessionFile, getChannelSessionDir, getThreadSessionFile, openManagedSession, resolveChannelSessionFile, resolveManagedSessionFile, tryResolveThreadSession, } from "./session-store.js";
|
|
14
15
|
import { createMamaTools } from "./tools/index.js";
|
|
15
16
|
import * as Sentry from "@sentry/node";
|
|
16
17
|
const IMAGE_MIME_TYPES = {
|
|
@@ -23,10 +24,10 @@ const IMAGE_MIME_TYPES = {
|
|
|
23
24
|
function getImageMimeType(filename) {
|
|
24
25
|
return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""];
|
|
25
26
|
}
|
|
26
|
-
async function getMemory(
|
|
27
|
+
async function getMemory(conversationDir) {
|
|
27
28
|
const parts = [];
|
|
28
|
-
// Read workspace-level memory (shared across all
|
|
29
|
-
const workspaceMemoryPath = join(
|
|
29
|
+
// Read workspace-level memory (shared across all conversations)
|
|
30
|
+
const workspaceMemoryPath = join(conversationDir, "..", "MEMORY.md");
|
|
30
31
|
if (existsSync(workspaceMemoryPath)) {
|
|
31
32
|
try {
|
|
32
33
|
const content = (await readFile(workspaceMemoryPath, "utf-8")).trim();
|
|
@@ -38,17 +39,17 @@ async function getMemory(channelDir) {
|
|
|
38
39
|
log.logWarning("Failed to read workspace memory", `${workspaceMemoryPath}: ${error}`);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
// Read
|
|
42
|
-
const
|
|
43
|
-
if (existsSync(
|
|
42
|
+
// Read conversation-specific memory
|
|
43
|
+
const conversationMemoryPath = join(conversationDir, "MEMORY.md");
|
|
44
|
+
if (existsSync(conversationMemoryPath)) {
|
|
44
45
|
try {
|
|
45
|
-
const content = (await readFile(
|
|
46
|
+
const content = (await readFile(conversationMemoryPath, "utf-8")).trim();
|
|
46
47
|
if (content) {
|
|
47
|
-
parts.push(`###
|
|
48
|
+
parts.push(`### Conversation-Specific Memory\n${content}`);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
catch (error) {
|
|
51
|
-
log.logWarning("Failed to read
|
|
52
|
+
log.logWarning("Failed to read conversation memory", `${conversationMemoryPath}: ${error}`);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
if (parts.length === 0) {
|
|
@@ -56,12 +57,12 @@ async function getMemory(channelDir) {
|
|
|
56
57
|
}
|
|
57
58
|
return parts.join("\n\n");
|
|
58
59
|
}
|
|
59
|
-
function loadMamaSkills(
|
|
60
|
+
function loadMamaSkills(conversationDir, workspacePath) {
|
|
60
61
|
const skillMap = new Map();
|
|
61
|
-
//
|
|
62
|
+
// conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)
|
|
62
63
|
// hostWorkspacePath is the parent directory on host
|
|
63
64
|
// workspacePath is the container path (e.g., /workspace)
|
|
64
|
-
const hostWorkspacePath = join(
|
|
65
|
+
const hostWorkspacePath = join(conversationDir, "..");
|
|
65
66
|
// Helper to translate host paths to container paths
|
|
66
67
|
const translatePath = (hostPath) => {
|
|
67
68
|
if (hostPath.startsWith(hostWorkspacePath)) {
|
|
@@ -77,20 +78,20 @@ function loadMamaSkills(channelDir, workspacePath) {
|
|
|
77
78
|
skill.baseDir = translatePath(skill.baseDir);
|
|
78
79
|
skillMap.set(skill.name, skill);
|
|
79
80
|
}
|
|
80
|
-
// Load
|
|
81
|
-
const
|
|
82
|
-
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) {
|
|
83
84
|
skill.filePath = translatePath(skill.filePath);
|
|
84
85
|
skill.baseDir = translatePath(skill.baseDir);
|
|
85
86
|
skillMap.set(skill.name, skill);
|
|
86
87
|
}
|
|
87
88
|
return Array.from(skillMap.values());
|
|
88
89
|
}
|
|
89
|
-
function buildSystemPrompt(workspacePath,
|
|
90
|
-
const
|
|
91
|
-
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";
|
|
92
93
|
const isFirecracker = sandboxConfig.type === "firecracker";
|
|
93
|
-
// Format
|
|
94
|
+
// Format platform conversation mappings
|
|
94
95
|
const channelMappings = platform.channels.length > 0
|
|
95
96
|
? platform.channels.map((c) => `${c.id}\t#${c.name}`).join("\n")
|
|
96
97
|
: "(no channels loaded)";
|
|
@@ -98,8 +99,8 @@ function buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, plat
|
|
|
98
99
|
const userMappings = platform.users.length > 0
|
|
99
100
|
? platform.users.map((u) => `${u.id}\t@${u.userName}\t${u.displayName}`).join("\n")
|
|
100
101
|
: "(no users loaded)";
|
|
101
|
-
const envDescription =
|
|
102
|
-
? `You are running inside a Docker
|
|
102
|
+
const envDescription = isContainer
|
|
103
|
+
? `You are running inside a container (Docker runtime, Alpine Linux).
|
|
103
104
|
- Bash working directory: / (use cd or absolute paths)
|
|
104
105
|
- Install tools with: apk add <package>
|
|
105
106
|
- Your changes persist across sessions`
|
|
@@ -135,18 +136,18 @@ ${envDescription}
|
|
|
135
136
|
${workspacePath}/
|
|
136
137
|
├── MEMORY.md # Global memory (all channels)
|
|
137
138
|
├── skills/ # Global CLI tools you create
|
|
138
|
-
└── ${
|
|
139
|
-
├── MEMORY.md #
|
|
139
|
+
└── ${conversationId}/ # This conversation
|
|
140
|
+
├── MEMORY.md # Conversation-specific memory
|
|
140
141
|
├── log.jsonl # Message history (no tool results)
|
|
141
142
|
├── attachments/ # User-shared files
|
|
142
143
|
├── scratch/ # Your working directory
|
|
143
|
-
└── skills/ #
|
|
144
|
+
└── skills/ # Conversation-specific tools
|
|
144
145
|
|
|
145
146
|
## Skills (Custom CLI Tools)
|
|
146
147
|
You can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).
|
|
147
148
|
|
|
148
149
|
### Creating Skills
|
|
149
|
-
Store in \`${workspacePath}/skills/<name>/\` (global) or \`${
|
|
150
|
+
Store in \`${workspacePath}/skills/<name>/\` (global) or \`${conversationPath}/skills/<name>/\` (conversation-specific).
|
|
150
151
|
Each skill directory needs a \`SKILL.md\` with YAML frontmatter:
|
|
151
152
|
|
|
152
153
|
\`\`\`markdown
|
|
@@ -173,19 +174,21 @@ You can schedule events that wake you up at specific times or when external thin
|
|
|
173
174
|
|
|
174
175
|
**Immediate** - Triggers as soon as harness sees the file. Use in scripts/webhooks to signal external events.
|
|
175
176
|
\`\`\`json
|
|
176
|
-
{"type": "immediate", "platform": "${platform.name}", "channelId": "${
|
|
177
|
+
{"type": "immediate", "platform": "${platform.name}", "channelId": "${conversationId}", "userId": "<requester userId>", "text": "New GitHub issue opened"}
|
|
177
178
|
\`\`\`
|
|
178
179
|
|
|
179
180
|
**One-shot** - Triggers once at a specific time. Use for reminders.
|
|
180
181
|
\`\`\`json
|
|
181
|
-
{"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"}
|
|
182
183
|
\`\`\`
|
|
183
184
|
|
|
184
185
|
**Periodic** - Triggers on a cron schedule. Use for recurring tasks.
|
|
185
186
|
\`\`\`json
|
|
186
|
-
{"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}"}
|
|
187
188
|
\`\`\`
|
|
188
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
|
+
|
|
189
192
|
### Cron Format
|
|
190
193
|
\`minute hour day-of-month month day-of-week\`
|
|
191
194
|
- \`0 9 * * *\` = daily at 9:00
|
|
@@ -200,10 +203,19 @@ All \`at\` timestamps must include offset (e.g., \`+01:00\`). Periodic events us
|
|
|
200
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.
|
|
201
204
|
|
|
202
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:
|
|
203
215
|
Use unique filenames to avoid overwriting existing events. Include a timestamp or random suffix:
|
|
204
216
|
\`\`\`bash
|
|
205
217
|
cat > ${workspacePath}/events/dentist-reminder-$(date +%s).json << 'EOF'
|
|
206
|
-
{"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"}
|
|
207
219
|
EOF
|
|
208
220
|
\`\`\`
|
|
209
221
|
Or check if file exists first before creating.
|
|
@@ -232,7 +244,7 @@ Maximum 5 events can be queued. Don't create excessive immediate or periodic eve
|
|
|
232
244
|
## Memory
|
|
233
245
|
Write to MEMORY.md files to persist context across conversations.
|
|
234
246
|
- Global (${workspacePath}/MEMORY.md): skills, preferences, project info
|
|
235
|
-
-
|
|
247
|
+
- Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work
|
|
236
248
|
Update when you learn something important or when asked to remember something.
|
|
237
249
|
|
|
238
250
|
### Current Memory
|
|
@@ -250,7 +262,7 @@ Update this file whenever you modify the environment. On fresh container, read i
|
|
|
250
262
|
## Log Queries (for older history)
|
|
251
263
|
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","isBot":false}\`
|
|
252
264
|
The log contains user messages and your final responses (not tool calls/results).
|
|
253
|
-
${
|
|
265
|
+
${isContainer ? "Install jq: apk add jq" : ""}
|
|
254
266
|
${isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
255
267
|
|
|
256
268
|
\`\`\`bash
|
|
@@ -331,83 +343,106 @@ function formatToolArgsForSlack(_toolName, args) {
|
|
|
331
343
|
// Agent runner
|
|
332
344
|
// ============================================================================
|
|
333
345
|
/**
|
|
334
|
-
* Create a new AgentRunner for a
|
|
346
|
+
* Create a new AgentRunner for a conversation.
|
|
335
347
|
* Sets up the session and subscribes to events once.
|
|
336
348
|
*
|
|
337
|
-
* Runner caching is handled by the caller (
|
|
349
|
+
* Runner caching is handled by the caller (conversationStates in main.ts).
|
|
338
350
|
* This is a stateless factory function.
|
|
339
351
|
*/
|
|
340
|
-
export async function createRunner(sandboxConfig, sessionKey,
|
|
341
|
-
const agentConfig = loadAgentConfig(workspaceDir);
|
|
352
|
+
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, vaultManager, bindingStore, provisioner, stateDir) {
|
|
353
|
+
const agentConfig = loadAgentConfig(stateDir ?? workspaceDir);
|
|
342
354
|
// Initialize logger with settings from config
|
|
343
355
|
log.initLogger({
|
|
344
356
|
logFormat: agentConfig.logFormat,
|
|
345
357
|
logLevel: agentConfig.logLevel,
|
|
346
358
|
});
|
|
347
|
-
const
|
|
348
|
-
|
|
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();
|
|
349
384
|
// Create tools (per-runner, with per-runner upload function setter)
|
|
350
|
-
const { tools, setUploadFunction } = createMamaTools(executor);
|
|
385
|
+
const { tools, setUploadFunction, setEventContext } = createMamaTools(executor, workspaceDir);
|
|
351
386
|
// Resolve model from config
|
|
352
387
|
// Use 'as any' cast because agentConfig.provider/model are plain strings,
|
|
353
388
|
// while getModel() has constrained generic types for known providers.
|
|
354
389
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
355
390
|
const model = getModel(agentConfig.provider, agentConfig.model);
|
|
356
391
|
// Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
|
|
357
|
-
const memory = await getMemory(
|
|
358
|
-
const skills = loadMamaSkills(
|
|
392
|
+
const memory = await getMemory(conversationDir);
|
|
393
|
+
const skills = loadMamaSkills(conversationDir, workspacePath);
|
|
359
394
|
const emptyPlatform = {
|
|
360
395
|
name: "slack",
|
|
361
396
|
formattingGuide: "",
|
|
362
397
|
channels: [],
|
|
363
398
|
users: [],
|
|
364
399
|
};
|
|
365
|
-
const systemPrompt = buildSystemPrompt(workspacePath,
|
|
366
|
-
// Create session manager and settings manager
|
|
367
|
-
//
|
|
368
|
-
// Thread sessions use fixed files: {
|
|
369
|
-
const sessionDir =
|
|
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);
|
|
370
405
|
const isThread = sessionKey.includes(":");
|
|
371
406
|
let sessionManager;
|
|
372
|
-
let
|
|
407
|
+
let sessionFile;
|
|
373
408
|
if (isThread) {
|
|
374
|
-
const threadFile = getThreadSessionFile(
|
|
409
|
+
const threadFile = getThreadSessionFile(conversationDir, sessionKey);
|
|
375
410
|
const existing = tryResolveThreadSession(threadFile);
|
|
376
411
|
if (existing) {
|
|
377
|
-
|
|
378
|
-
sessionManager = openManagedSession(
|
|
412
|
+
sessionFile = existing;
|
|
413
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
379
414
|
}
|
|
380
415
|
else {
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
416
|
+
const conversationSource = resolveChannelSessionFile(conversationDir);
|
|
417
|
+
if (conversationSource) {
|
|
383
418
|
try {
|
|
384
|
-
|
|
385
|
-
sessionManager = openManagedSession(
|
|
419
|
+
sessionFile = forkThreadSessionFile(conversationSource, threadFile, conversationDir);
|
|
420
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
386
421
|
}
|
|
387
422
|
catch {
|
|
388
|
-
|
|
389
|
-
sessionManager = openManagedSession(
|
|
423
|
+
sessionFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
424
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
390
425
|
}
|
|
391
426
|
}
|
|
392
427
|
else {
|
|
393
|
-
|
|
394
|
-
sessionManager = openManagedSession(
|
|
428
|
+
sessionFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
429
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
395
430
|
}
|
|
396
431
|
}
|
|
397
432
|
}
|
|
398
433
|
else {
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
sessionManager = openManagedSession(
|
|
434
|
+
// Top-level conversation session: resolve the current session file.
|
|
435
|
+
sessionFile = resolveManagedSessionFile(sessionDir, conversationDir);
|
|
436
|
+
sessionManager = openManagedSession(sessionFile, sessionDir, conversationDir);
|
|
402
437
|
}
|
|
403
|
-
const sessionUuid = extractSessionUuid(
|
|
438
|
+
const sessionUuid = extractSessionUuid(sessionFile);
|
|
404
439
|
// Used for Slack thread filtering — for non-Slack platforms this is effectively a no-op
|
|
405
440
|
const rootTs = extractSessionSuffix(sessionKey);
|
|
406
|
-
const settingsManager = createMamaSettingsManager(join(
|
|
441
|
+
const settingsManager = createMamaSettingsManager(join(conversationDir, ".."));
|
|
407
442
|
// Create AuthStorage and ModelRegistry
|
|
408
443
|
// Auth stored outside workspace so agent can't access it
|
|
409
444
|
const authStorage = AuthStorage.create(join(homedir(), ".pi", "mama", "auth.json"));
|
|
410
|
-
const modelRegistry =
|
|
445
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
411
446
|
// Create agent
|
|
412
447
|
const agent = new Agent({
|
|
413
448
|
initialState: {
|
|
@@ -427,14 +462,15 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
427
462
|
// Load existing messages
|
|
428
463
|
const loadedSession = sessionManager.buildSessionContext();
|
|
429
464
|
if (loadedSession.messages.length > 0) {
|
|
430
|
-
agent.
|
|
431
|
-
log.logInfo(`[${
|
|
465
|
+
agent.state.messages = loadedSession.messages;
|
|
466
|
+
log.logInfo(`[${conversationId}] Loaded ${loadedSession.messages.length} messages from session file`);
|
|
432
467
|
}
|
|
433
468
|
// Load extensions, skills, prompts, themes via DefaultResourceLoader
|
|
434
469
|
// This reads ~/.pi/agent/settings.json (packages, extensions enable/disable)
|
|
435
470
|
// and discovers resources from standard locations + npm/git packages.
|
|
436
471
|
const resourceLoader = new DefaultResourceLoader({
|
|
437
472
|
cwd: workspaceDir,
|
|
473
|
+
agentDir: getAgentDir(),
|
|
438
474
|
systemPrompt,
|
|
439
475
|
});
|
|
440
476
|
try {
|
|
@@ -442,13 +478,13 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
442
478
|
const extResult = resourceLoader.getExtensions();
|
|
443
479
|
if (extResult.errors.length > 0) {
|
|
444
480
|
for (const err of extResult.errors) {
|
|
445
|
-
log.logWarning(`[${
|
|
481
|
+
log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
|
|
446
482
|
}
|
|
447
483
|
}
|
|
448
|
-
log.logInfo(`[${
|
|
484
|
+
log.logInfo(`[${conversationId}] Loaded ${extResult.extensions.length} extension(s): ${extResult.extensions.map((e) => e.path).join(", ")}`);
|
|
449
485
|
}
|
|
450
486
|
catch (error) {
|
|
451
|
-
log.logWarning(`[${
|
|
487
|
+
log.logWarning(`[${conversationId}] Failed to load resources`, String(error));
|
|
452
488
|
}
|
|
453
489
|
const baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
454
490
|
// Create AgentSession wrapper
|
|
@@ -484,7 +520,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
484
520
|
if (!runState.responseCtx || !runState.logCtx || !runState.queue)
|
|
485
521
|
return;
|
|
486
522
|
const { responseCtx, logCtx, queue, pendingTools } = runState;
|
|
487
|
-
const baseAttrs = { channel_id: logCtx.
|
|
523
|
+
const baseAttrs = { channel_id: logCtx.conversationId, session_id: logCtx.sessionId };
|
|
488
524
|
if (event.type === "tool_execution_start") {
|
|
489
525
|
const agentEvent = event;
|
|
490
526
|
const args = agentEvent.args;
|
|
@@ -694,10 +730,22 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
694
730
|
};
|
|
695
731
|
return {
|
|
696
732
|
async run(message, responseCtx, platform) {
|
|
697
|
-
// Extract
|
|
698
|
-
const
|
|
699
|
-
// Ensure
|
|
700
|
-
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
|
+
}
|
|
701
749
|
// Sync messages from log.jsonl that arrived while we were offline or busy
|
|
702
750
|
// Exclude the current message (it will be added via prompt())
|
|
703
751
|
// Default sync range is 10 days (handled by syncLogToSessionManager)
|
|
@@ -705,33 +753,41 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
705
753
|
const threadFilter = message.sessionKey.includes(":")
|
|
706
754
|
? { scope: "thread", rootTs, threadTs: message.threadTs }
|
|
707
755
|
: { scope: "top-level", rootTs };
|
|
708
|
-
const syncedCount = await syncLogToSessionManager(sessionManager,
|
|
756
|
+
const syncedCount = await syncLogToSessionManager(sessionManager, conversationDir, message.id, undefined, threadFilter);
|
|
709
757
|
if (syncedCount > 0) {
|
|
710
|
-
log.logInfo(`[${
|
|
758
|
+
log.logInfo(`[${conversationId}] Synced ${syncedCount} messages from log.jsonl`);
|
|
711
759
|
}
|
|
712
|
-
// Reload messages from
|
|
713
|
-
// This picks up any messages synced above
|
|
760
|
+
// Reload messages from the session file.
|
|
761
|
+
// This picks up any messages synced above.
|
|
714
762
|
const reloadedSession = sessionManager.buildSessionContext();
|
|
715
763
|
if (reloadedSession.messages.length > 0) {
|
|
716
|
-
agent.
|
|
717
|
-
log.logInfo(`[${
|
|
764
|
+
agent.state.messages = reloadedSession.messages;
|
|
765
|
+
log.logInfo(`[${conversationId}] Reloaded ${reloadedSession.messages.length} messages from context`);
|
|
718
766
|
}
|
|
719
767
|
// Update system prompt with fresh memory, channel/user info, and skills
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const
|
|
723
|
-
|
|
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;
|
|
724
775
|
// Set up file upload function
|
|
725
776
|
setUploadFunction(async (filePath, title) => {
|
|
726
|
-
const hostPath = translateToHostPath(filePath,
|
|
777
|
+
const hostPath = translateToHostPath(filePath, conversationDir, workspacePath, conversationId);
|
|
727
778
|
await responseCtx.uploadFile(hostPath, title);
|
|
728
779
|
});
|
|
780
|
+
setEventContext({
|
|
781
|
+
platform: platform.name,
|
|
782
|
+
conversationId,
|
|
783
|
+
userId: message.userId,
|
|
784
|
+
});
|
|
729
785
|
// Reset per-run state
|
|
730
786
|
runState.responseCtx = responseCtx;
|
|
731
787
|
runState.logCtx = {
|
|
732
|
-
|
|
788
|
+
conversationId: sessionConversationId,
|
|
733
789
|
userName: message.userName,
|
|
734
|
-
|
|
790
|
+
conversationName: undefined,
|
|
735
791
|
sessionId: sessionUuid,
|
|
736
792
|
};
|
|
737
793
|
runState.pendingTools.clear();
|
|
@@ -793,7 +849,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
793
849
|
const imageAttachments = [];
|
|
794
850
|
const nonImagePaths = [];
|
|
795
851
|
for (const a of message.attachments || []) {
|
|
796
|
-
// a.localPath is the path relative to the workspace
|
|
852
|
+
// a.localPath is the path relative to the workspace
|
|
797
853
|
const fullPath = `${workspacePath}/${a.localPath}`;
|
|
798
854
|
const mimeType = getImageMimeType(a.localPath);
|
|
799
855
|
if (mimeType && existsSync(fullPath)) {
|
|
@@ -822,11 +878,11 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
822
878
|
newUserMessage: userMessage,
|
|
823
879
|
imageAttachmentCount: imageAttachments.length,
|
|
824
880
|
};
|
|
825
|
-
await writeFile(join(
|
|
881
|
+
await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
|
|
826
882
|
addLifecycleBreadcrumb("agent.prompt.sent", {
|
|
827
883
|
provider: model.provider,
|
|
828
884
|
model: agentConfig.model,
|
|
829
|
-
channel_id:
|
|
885
|
+
channel_id: sessionConversationId,
|
|
830
886
|
session_id: sessionUuid,
|
|
831
887
|
attachment_count: message.attachments?.length ?? 0,
|
|
832
888
|
image_attachment_count: imageAttachments.length,
|
|
@@ -901,7 +957,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
901
957
|
const runMetricAttributes = metricAttributes({
|
|
902
958
|
provider: model.provider,
|
|
903
959
|
model: agentConfig.model,
|
|
904
|
-
channel_id:
|
|
960
|
+
channel_id: sessionConversationId,
|
|
905
961
|
session_id: sessionUuid,
|
|
906
962
|
stop_reason: runState.stopReason,
|
|
907
963
|
llm_calls: runState.llmCallCount,
|
|
@@ -960,14 +1016,14 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
960
1016
|
/**
|
|
961
1017
|
* Translate container path back to host path for file operations
|
|
962
1018
|
*/
|
|
963
|
-
function translateToHostPath(containerPath,
|
|
1019
|
+
function translateToHostPath(containerPath, conversationDir, workspacePath, conversationId) {
|
|
964
1020
|
if (workspacePath === "/workspace") {
|
|
965
|
-
const prefix = `/workspace/${
|
|
1021
|
+
const prefix = `/workspace/${conversationId}/`;
|
|
966
1022
|
if (containerPath.startsWith(prefix)) {
|
|
967
|
-
return join(
|
|
1023
|
+
return join(conversationDir, containerPath.slice(prefix.length));
|
|
968
1024
|
}
|
|
969
1025
|
if (containerPath.startsWith("/workspace/")) {
|
|
970
|
-
return join(
|
|
1026
|
+
return join(conversationDir, "..", containerPath.slice("/workspace/".length));
|
|
971
1027
|
}
|
|
972
1028
|
}
|
|
973
1029
|
return containerPath;
|