@geminixiang/mama 0.2.0-beta.0 → 0.2.0-beta.2
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 +94 -27
- package/dist/adapter.d.ts +9 -5
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +9 -6
- 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 +16 -13
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +10 -2
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +196 -32
- 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 +24 -17
- package/dist/adapters/slack/context.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 +109 -29
- 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 +8 -43
- 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 +4 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +141 -92
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts +44 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +74 -0
- package/dist/bindings.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +53 -12
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +7 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +9 -9
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +14 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +45 -10
- 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 +49 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +2 -1
- package/dist/instrument.js.map +1 -1
- package/dist/link-server.d.ts +17 -0
- package/dist/link-server.d.ts.map +1 -0
- package/dist/link-server.js +899 -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 +226 -55
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +52 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +291 -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 +10 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +85 -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 +4 -2
- package/dist/sentry.js.map +1 -1
- package/dist/session-store.d.ts +2 -6
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +3 -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 +22 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +104 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +7 -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 +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 +9 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +52 -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/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,18 +78,19 @@ 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, conversationKind, currentUserId, memory, sandboxConfig, platform, skills) {
|
|
91
|
+
const conversationPath = `${workspacePath}/${conversationId}`;
|
|
92
|
+
const isContainer = sandboxConfig.type === "container" || sandboxConfig.type === "image";
|
|
93
|
+
const isImageSandbox = sandboxConfig.type === "image";
|
|
92
94
|
const isFirecracker = sandboxConfig.type === "firecracker";
|
|
93
95
|
// Format channel mappings
|
|
94
96
|
const channelMappings = platform.channels.length > 0
|
|
@@ -98,17 +100,22 @@ function buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, plat
|
|
|
98
100
|
const userMappings = platform.users.length > 0
|
|
99
101
|
? platform.users.map((u) => `${u.id}\t@${u.userName}\t${u.displayName}`).join("\n")
|
|
100
102
|
: "(no users loaded)";
|
|
101
|
-
const envDescription =
|
|
102
|
-
? `You are running inside a
|
|
103
|
+
const envDescription = isImageSandbox
|
|
104
|
+
? `You are running inside a managed per-user container.
|
|
103
105
|
- Bash working directory: / (use cd or absolute paths)
|
|
104
|
-
- Install tools with
|
|
106
|
+
- Install tools with the image's package manager
|
|
107
|
+
- Your changes persist for this user's container until it is recreated`
|
|
108
|
+
: isContainer
|
|
109
|
+
? `You are running inside a shared container.
|
|
110
|
+
- Bash working directory: / (use cd or absolute paths)
|
|
111
|
+
- Install tools with the container's package manager
|
|
105
112
|
- Your changes persist across sessions`
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
: isFirecracker
|
|
114
|
+
? `You are running inside a Firecracker microVM.
|
|
108
115
|
- Bash working directory: / (use cd or absolute paths)
|
|
109
116
|
- Install tools with: apt-get install <package> (Debian-based)
|
|
110
117
|
- Your changes persist across sessions`
|
|
111
|
-
|
|
118
|
+
: `You are running directly on the host machine.
|
|
112
119
|
- Bash working directory: ${process.cwd()}
|
|
113
120
|
- Be careful with system modifications`;
|
|
114
121
|
return `You are mama, a ${platform.name} bot assistant. Be concise. No emojis.
|
|
@@ -133,20 +140,20 @@ ${envDescription}
|
|
|
133
140
|
|
|
134
141
|
## Workspace Layout
|
|
135
142
|
${workspacePath}/
|
|
136
|
-
├── MEMORY.md # Global memory (all
|
|
143
|
+
├── MEMORY.md # Global memory (all conversations)
|
|
137
144
|
├── skills/ # Global CLI tools you create
|
|
138
|
-
└── ${
|
|
139
|
-
├── MEMORY.md #
|
|
145
|
+
└── ${conversationId}/ # This conversation
|
|
146
|
+
├── MEMORY.md # Conversation-specific memory
|
|
140
147
|
├── log.jsonl # Message history (no tool results)
|
|
141
148
|
├── attachments/ # User-shared files
|
|
142
149
|
├── scratch/ # Your working directory
|
|
143
|
-
└── skills/ #
|
|
150
|
+
└── skills/ # Conversation-specific tools
|
|
144
151
|
|
|
145
152
|
## Skills (Custom CLI Tools)
|
|
146
153
|
You can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).
|
|
147
154
|
|
|
148
155
|
### Creating Skills
|
|
149
|
-
Store in \`${workspacePath}/skills/<name>/\` (global) or \`${
|
|
156
|
+
Store in \`${workspacePath}/skills/<name>/\` (global) or \`${conversationPath}/skills/<name>/\` (conversation-specific).
|
|
150
157
|
Each skill directory needs a \`SKILL.md\` with YAML frontmatter:
|
|
151
158
|
|
|
152
159
|
\`\`\`markdown
|
|
@@ -173,17 +180,17 @@ You can schedule events that wake you up at specific times or when external thin
|
|
|
173
180
|
|
|
174
181
|
**Immediate** - Triggers as soon as harness sees the file. Use in scripts/webhooks to signal external events.
|
|
175
182
|
\`\`\`json
|
|
176
|
-
{"type": "immediate", "platform": "${platform.name}", "
|
|
183
|
+
{"type": "immediate", "platform": "${platform.name}", "conversationId": "${conversationId}", "conversationKind": "${conversationKind}", "userId": "${currentUserId ?? "<requester userId>"}", "text": "New GitHub issue opened"}
|
|
177
184
|
\`\`\`
|
|
178
185
|
|
|
179
186
|
**One-shot** - Triggers once at a specific time. Use for reminders.
|
|
180
187
|
\`\`\`json
|
|
181
|
-
{"type": "one-shot", "platform": "${platform.name}", "
|
|
188
|
+
{"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
189
|
\`\`\`
|
|
183
190
|
|
|
184
191
|
**Periodic** - Triggers on a cron schedule. Use for recurring tasks.
|
|
185
192
|
\`\`\`json
|
|
186
|
-
{"type": "periodic", "platform": "${platform.name}", "
|
|
193
|
+
{"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
194
|
\`\`\`
|
|
188
195
|
|
|
189
196
|
### Cron Format
|
|
@@ -196,14 +203,18 @@ You can schedule events that wake you up at specific times or when external thin
|
|
|
196
203
|
### Timezones
|
|
197
204
|
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
205
|
|
|
199
|
-
### Platform Routing
|
|
206
|
+
### Platform and Credential Routing
|
|
200
207
|
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
208
|
|
|
209
|
+
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
|
+
|
|
211
|
+
Prefer the \`event\` tool over manually writing JSON files; it fills \`platform\`, \`conversationId\`, \`conversationKind\`, and \`userId\` for the current conversation automatically.
|
|
212
|
+
|
|
202
213
|
### Creating Events
|
|
203
214
|
Use unique filenames to avoid overwriting existing events. Include a timestamp or random suffix:
|
|
204
215
|
\`\`\`bash
|
|
205
216
|
cat > ${workspacePath}/events/dentist-reminder-$(date +%s).json << 'EOF'
|
|
206
|
-
{"type": "one-shot", "platform": "${platform.name}", "
|
|
217
|
+
{"type": "one-shot", "platform": "${platform.name}", "conversationId": "${conversationId}", "conversationKind": "${conversationKind}", "userId": "${currentUserId ?? "<requester userId>"}", "text": "Dentist tomorrow", "at": "2025-12-14T09:00:00+01:00"}
|
|
207
218
|
EOF
|
|
208
219
|
\`\`\`
|
|
209
220
|
Or check if file exists first before creating.
|
|
@@ -232,7 +243,7 @@ Maximum 5 events can be queued. Don't create excessive immediate or periodic eve
|
|
|
232
243
|
## Memory
|
|
233
244
|
Write to MEMORY.md files to persist context across conversations.
|
|
234
245
|
- Global (${workspacePath}/MEMORY.md): skills, preferences, project info
|
|
235
|
-
-
|
|
246
|
+
- Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work
|
|
236
247
|
Update when you learn something important or when asked to remember something.
|
|
237
248
|
|
|
238
249
|
### Current Memory
|
|
@@ -250,7 +261,7 @@ Update this file whenever you modify the environment. On fresh container, read i
|
|
|
250
261
|
## Log Queries (for older history)
|
|
251
262
|
Format: \`{"date":"...","ts":"...","user":"...","userName":"...","text":"...","isBot":false}\`
|
|
252
263
|
The log contains user messages and your final responses (not tool calls/results).
|
|
253
|
-
${
|
|
264
|
+
${isContainer ? "Install jq: apk add jq" : ""}
|
|
254
265
|
${isFirecracker ? "Install jq: apt-get install jq" : ""}
|
|
255
266
|
|
|
256
267
|
\`\`\`bash
|
|
@@ -337,77 +348,100 @@ function formatToolArgsForSlack(_toolName, args) {
|
|
|
337
348
|
* Runner caching is handled by the caller (channelStates in main.ts).
|
|
338
349
|
* This is a stateless factory function.
|
|
339
350
|
*/
|
|
340
|
-
export async function createRunner(sandboxConfig, sessionKey,
|
|
351
|
+
export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, vaultManager, bindingStore, provisioner) {
|
|
341
352
|
const agentConfig = loadAgentConfig(workspaceDir);
|
|
342
353
|
// Initialize logger with settings from config
|
|
343
354
|
log.initLogger({
|
|
344
355
|
logFormat: agentConfig.logFormat,
|
|
345
356
|
logLevel: agentConfig.logLevel,
|
|
346
357
|
});
|
|
347
|
-
const
|
|
348
|
-
|
|
358
|
+
const executionResolver = vaultManager &&
|
|
359
|
+
sandboxConfig.type !== "host" &&
|
|
360
|
+
(vaultManager.isEnabled() ||
|
|
361
|
+
!!bindingStore ||
|
|
362
|
+
sandboxConfig.type === "container" ||
|
|
363
|
+
sandboxConfig.type === "image")
|
|
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
|
+
const getWorkspacePath = () => executor.getWorkspacePath(workspaceBase);
|
|
382
|
+
let workspacePath = getWorkspacePath();
|
|
349
383
|
// Create tools (per-runner, with per-runner upload function setter)
|
|
350
|
-
const { tools, setUploadFunction } = createMamaTools(executor);
|
|
384
|
+
const { tools, setUploadFunction, setEventContext } = createMamaTools(executor, workspaceDir);
|
|
351
385
|
// Resolve model from config
|
|
352
386
|
// Use 'as any' cast because agentConfig.provider/model are plain strings,
|
|
353
387
|
// while getModel() has constrained generic types for known providers.
|
|
354
388
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
355
389
|
const model = getModel(agentConfig.provider, agentConfig.model);
|
|
356
390
|
// Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
|
|
357
|
-
const memory = await getMemory(
|
|
358
|
-
const skills = loadMamaSkills(
|
|
391
|
+
const memory = await getMemory(conversationDir);
|
|
392
|
+
const skills = loadMamaSkills(conversationDir, workspacePath);
|
|
359
393
|
const emptyPlatform = {
|
|
360
394
|
name: "slack",
|
|
361
395
|
formattingGuide: "",
|
|
362
396
|
channels: [],
|
|
363
397
|
users: [],
|
|
364
398
|
};
|
|
365
|
-
const systemPrompt = buildSystemPrompt(workspacePath,
|
|
399
|
+
const systemPrompt = buildSystemPrompt(workspacePath, conversationId, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills);
|
|
366
400
|
// Create session manager and settings manager
|
|
367
|
-
//
|
|
368
|
-
// Thread sessions use fixed files: {
|
|
369
|
-
const sessionDir =
|
|
401
|
+
// Conversation sessions use {conversationDir}/sessions/current.
|
|
402
|
+
// Thread sessions use fixed files: {conversationDir}/sessions/{threadTs}.jsonl
|
|
403
|
+
const sessionDir = getChannelSessionDir(conversationDir);
|
|
370
404
|
const isThread = sessionKey.includes(":");
|
|
371
405
|
let sessionManager;
|
|
372
406
|
let contextFile;
|
|
373
407
|
if (isThread) {
|
|
374
|
-
const threadFile = getThreadSessionFile(
|
|
408
|
+
const threadFile = getThreadSessionFile(conversationDir, sessionKey);
|
|
375
409
|
const existing = tryResolveThreadSession(threadFile);
|
|
376
410
|
if (existing) {
|
|
377
411
|
contextFile = existing;
|
|
378
|
-
sessionManager = openManagedSession(contextFile, sessionDir,
|
|
412
|
+
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
379
413
|
}
|
|
380
414
|
else {
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
415
|
+
const conversationSource = resolveChannelSessionFile(conversationDir);
|
|
416
|
+
if (conversationSource) {
|
|
383
417
|
try {
|
|
384
|
-
contextFile = forkThreadSessionFile(
|
|
385
|
-
sessionManager = openManagedSession(contextFile, sessionDir,
|
|
418
|
+
contextFile = forkThreadSessionFile(conversationSource, threadFile, conversationDir);
|
|
419
|
+
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
386
420
|
}
|
|
387
421
|
catch {
|
|
388
|
-
contextFile = createManagedSessionFileAtPath(threadFile,
|
|
389
|
-
sessionManager = openManagedSession(contextFile, sessionDir,
|
|
422
|
+
contextFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
423
|
+
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
390
424
|
}
|
|
391
425
|
}
|
|
392
426
|
else {
|
|
393
|
-
contextFile = createManagedSessionFileAtPath(threadFile,
|
|
394
|
-
sessionManager = openManagedSession(contextFile, sessionDir,
|
|
427
|
+
contextFile = createManagedSessionFileAtPath(threadFile, conversationDir);
|
|
428
|
+
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
395
429
|
}
|
|
396
430
|
}
|
|
397
431
|
}
|
|
398
432
|
else {
|
|
399
|
-
//
|
|
400
|
-
contextFile = resolveManagedSessionFile(sessionDir,
|
|
401
|
-
sessionManager = openManagedSession(contextFile, sessionDir,
|
|
433
|
+
// Direct/shared session: normal resolve
|
|
434
|
+
contextFile = resolveManagedSessionFile(sessionDir, conversationDir);
|
|
435
|
+
sessionManager = openManagedSession(contextFile, sessionDir, conversationDir);
|
|
402
436
|
}
|
|
403
437
|
const sessionUuid = extractSessionUuid(contextFile);
|
|
404
438
|
// Used for Slack thread filtering — for non-Slack platforms this is effectively a no-op
|
|
405
439
|
const rootTs = extractSessionSuffix(sessionKey);
|
|
406
|
-
const settingsManager = createMamaSettingsManager(join(
|
|
440
|
+
const settingsManager = createMamaSettingsManager(join(conversationDir, ".."));
|
|
407
441
|
// Create AuthStorage and ModelRegistry
|
|
408
442
|
// Auth stored outside workspace so agent can't access it
|
|
409
443
|
const authStorage = AuthStorage.create(join(homedir(), ".pi", "mama", "auth.json"));
|
|
410
|
-
const modelRegistry =
|
|
444
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
411
445
|
// Create agent
|
|
412
446
|
const agent = new Agent({
|
|
413
447
|
initialState: {
|
|
@@ -427,14 +461,15 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
427
461
|
// Load existing messages
|
|
428
462
|
const loadedSession = sessionManager.buildSessionContext();
|
|
429
463
|
if (loadedSession.messages.length > 0) {
|
|
430
|
-
agent.
|
|
431
|
-
log.logInfo(`[${
|
|
464
|
+
agent.state.messages = loadedSession.messages;
|
|
465
|
+
log.logInfo(`[${conversationId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);
|
|
432
466
|
}
|
|
433
467
|
// Load extensions, skills, prompts, themes via DefaultResourceLoader
|
|
434
468
|
// This reads ~/.pi/agent/settings.json (packages, extensions enable/disable)
|
|
435
469
|
// and discovers resources from standard locations + npm/git packages.
|
|
436
470
|
const resourceLoader = new DefaultResourceLoader({
|
|
437
471
|
cwd: workspaceDir,
|
|
472
|
+
agentDir: getAgentDir(),
|
|
438
473
|
systemPrompt,
|
|
439
474
|
});
|
|
440
475
|
try {
|
|
@@ -442,13 +477,13 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
442
477
|
const extResult = resourceLoader.getExtensions();
|
|
443
478
|
if (extResult.errors.length > 0) {
|
|
444
479
|
for (const err of extResult.errors) {
|
|
445
|
-
log.logWarning(`[${
|
|
480
|
+
log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
|
|
446
481
|
}
|
|
447
482
|
}
|
|
448
|
-
log.logInfo(`[${
|
|
483
|
+
log.logInfo(`[${conversationId}] Loaded ${extResult.extensions.length} extension(s): ${extResult.extensions.map((e) => e.path).join(", ")}`);
|
|
449
484
|
}
|
|
450
485
|
catch (error) {
|
|
451
|
-
log.logWarning(`[${
|
|
486
|
+
log.logWarning(`[${conversationId}] Failed to load resources`, String(error));
|
|
452
487
|
}
|
|
453
488
|
const baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
454
489
|
// Create AgentSession wrapper
|
|
@@ -484,7 +519,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
484
519
|
if (!runState.responseCtx || !runState.logCtx || !runState.queue)
|
|
485
520
|
return;
|
|
486
521
|
const { responseCtx, logCtx, queue, pendingTools } = runState;
|
|
487
|
-
const baseAttrs = { channel_id: logCtx.
|
|
522
|
+
const baseAttrs = { channel_id: logCtx.conversationId, session_id: logCtx.sessionId };
|
|
488
523
|
if (event.type === "tool_execution_start") {
|
|
489
524
|
const agentEvent = event;
|
|
490
525
|
const args = agentEvent.args;
|
|
@@ -694,10 +729,18 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
694
729
|
};
|
|
695
730
|
return {
|
|
696
731
|
async run(message, responseCtx, platform) {
|
|
697
|
-
// Extract
|
|
698
|
-
const
|
|
699
|
-
// Ensure
|
|
700
|
-
await mkdir(
|
|
732
|
+
// Extract conversationId from sessionKey (format: "conversationId:rootTs" or just "conversationId")
|
|
733
|
+
const sessionConversation = message.sessionKey.split(":")[0];
|
|
734
|
+
// Ensure conversation directory exists
|
|
735
|
+
await mkdir(conversationDir, { recursive: true });
|
|
736
|
+
if (executionResolver) {
|
|
737
|
+
executionResolver.refresh();
|
|
738
|
+
activeExecutor = await executionResolver.resolve({
|
|
739
|
+
platform: platform.name,
|
|
740
|
+
userId: message.userId,
|
|
741
|
+
});
|
|
742
|
+
workspacePath = getWorkspacePath();
|
|
743
|
+
}
|
|
701
744
|
// Sync messages from log.jsonl that arrived while we were offline or busy
|
|
702
745
|
// Exclude the current message (it will be added via prompt())
|
|
703
746
|
// Default sync range is 10 days (handled by syncLogToSessionManager)
|
|
@@ -705,33 +748,39 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
705
748
|
const threadFilter = message.sessionKey.includes(":")
|
|
706
749
|
? { scope: "thread", rootTs, threadTs: message.threadTs }
|
|
707
750
|
: { scope: "top-level", rootTs };
|
|
708
|
-
const syncedCount = await syncLogToSessionManager(sessionManager,
|
|
751
|
+
const syncedCount = await syncLogToSessionManager(sessionManager, conversationDir, message.id, undefined, threadFilter);
|
|
709
752
|
if (syncedCount > 0) {
|
|
710
|
-
log.logInfo(`[${
|
|
753
|
+
log.logInfo(`[${conversationId}] Synced ${syncedCount} messages from log.jsonl`);
|
|
711
754
|
}
|
|
712
755
|
// Reload messages from context.jsonl
|
|
713
756
|
// This picks up any messages synced above
|
|
714
757
|
const reloadedSession = sessionManager.buildSessionContext();
|
|
715
758
|
if (reloadedSession.messages.length > 0) {
|
|
716
|
-
agent.
|
|
717
|
-
log.logInfo(`[${
|
|
759
|
+
agent.state.messages = reloadedSession.messages;
|
|
760
|
+
log.logInfo(`[${conversationId}] Reloaded ${reloadedSession.messages.length} messages from context`);
|
|
718
761
|
}
|
|
719
762
|
// Update system prompt with fresh memory, channel/user info, and skills
|
|
720
|
-
const memory = await getMemory(
|
|
721
|
-
const skills = loadMamaSkills(
|
|
722
|
-
const systemPrompt = buildSystemPrompt(workspacePath,
|
|
723
|
-
session.agent.
|
|
763
|
+
const memory = await getMemory(conversationDir);
|
|
764
|
+
const skills = loadMamaSkills(conversationDir, workspacePath);
|
|
765
|
+
const systemPrompt = buildSystemPrompt(workspacePath, conversationId, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills);
|
|
766
|
+
session.agent.state.systemPrompt = systemPrompt;
|
|
767
|
+
setEventContext({
|
|
768
|
+
platform: platform.name,
|
|
769
|
+
conversationId,
|
|
770
|
+
conversationKind: message.conversationKind,
|
|
771
|
+
userId: message.userId,
|
|
772
|
+
});
|
|
724
773
|
// Set up file upload function
|
|
725
774
|
setUploadFunction(async (filePath, title) => {
|
|
726
|
-
const hostPath = translateToHostPath(filePath,
|
|
775
|
+
const hostPath = translateToHostPath(filePath, conversationDir, workspacePath, conversationId);
|
|
727
776
|
await responseCtx.uploadFile(hostPath, title);
|
|
728
777
|
});
|
|
729
778
|
// Reset per-run state
|
|
730
779
|
runState.responseCtx = responseCtx;
|
|
731
780
|
runState.logCtx = {
|
|
732
|
-
|
|
781
|
+
conversationId: sessionConversation,
|
|
733
782
|
userName: message.userName,
|
|
734
|
-
|
|
783
|
+
conversationName: undefined,
|
|
735
784
|
sessionId: sessionUuid,
|
|
736
785
|
};
|
|
737
786
|
runState.pendingTools.clear();
|
|
@@ -793,7 +842,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
793
842
|
const imageAttachments = [];
|
|
794
843
|
const nonImagePaths = [];
|
|
795
844
|
for (const a of message.attachments || []) {
|
|
796
|
-
// a.localPath is the path relative to the workspace
|
|
845
|
+
// a.localPath is the path relative to the workspace.
|
|
797
846
|
const fullPath = `${workspacePath}/${a.localPath}`;
|
|
798
847
|
const mimeType = getImageMimeType(a.localPath);
|
|
799
848
|
if (mimeType && existsSync(fullPath)) {
|
|
@@ -822,11 +871,11 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
822
871
|
newUserMessage: userMessage,
|
|
823
872
|
imageAttachmentCount: imageAttachments.length,
|
|
824
873
|
};
|
|
825
|
-
await writeFile(join(
|
|
874
|
+
await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
|
|
826
875
|
addLifecycleBreadcrumb("agent.prompt.sent", {
|
|
827
876
|
provider: model.provider,
|
|
828
877
|
model: agentConfig.model,
|
|
829
|
-
channel_id:
|
|
878
|
+
channel_id: sessionConversation,
|
|
830
879
|
session_id: sessionUuid,
|
|
831
880
|
attachment_count: message.attachments?.length ?? 0,
|
|
832
881
|
image_attachment_count: imageAttachments.length,
|
|
@@ -901,7 +950,7 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
901
950
|
const runMetricAttributes = metricAttributes({
|
|
902
951
|
provider: model.provider,
|
|
903
952
|
model: agentConfig.model,
|
|
904
|
-
channel_id:
|
|
953
|
+
channel_id: sessionConversation,
|
|
905
954
|
session_id: sessionUuid,
|
|
906
955
|
stop_reason: runState.stopReason,
|
|
907
956
|
llm_calls: runState.llmCallCount,
|
|
@@ -960,14 +1009,14 @@ export async function createRunner(sandboxConfig, sessionKey, channelId, channel
|
|
|
960
1009
|
/**
|
|
961
1010
|
* Translate container path back to host path for file operations
|
|
962
1011
|
*/
|
|
963
|
-
function translateToHostPath(containerPath,
|
|
1012
|
+
function translateToHostPath(containerPath, conversationDir, workspacePath, conversationId) {
|
|
964
1013
|
if (workspacePath === "/workspace") {
|
|
965
|
-
const prefix = `/workspace/${
|
|
1014
|
+
const prefix = `/workspace/${conversationId}/`;
|
|
966
1015
|
if (containerPath.startsWith(prefix)) {
|
|
967
|
-
return join(
|
|
1016
|
+
return join(conversationDir, containerPath.slice(prefix.length));
|
|
968
1017
|
}
|
|
969
1018
|
if (containerPath.startsWith("/workspace/")) {
|
|
970
|
-
return join(
|
|
1019
|
+
return join(conversationDir, "..", containerPath.slice("/workspace/".length));
|
|
971
1020
|
}
|
|
972
1021
|
}
|
|
973
1022
|
return containerPath;
|