@geminixiang/mama 0.1.0
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/LICENSE +22 -0
- package/README.md +158 -0
- package/dist/adapter.d.ts +38 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +2 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +130 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -0
- package/dist/adapters/slack/bot.js +516 -0
- package/dist/adapters/slack/bot.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +11 -0
- package/dist/adapters/slack/context.d.ts.map +1 -0
- package/dist/adapters/slack/context.js +178 -0
- package/dist/adapters/slack/context.js.map +1 -0
- package/dist/adapters/slack/index.d.ts +3 -0
- package/dist/adapters/slack/index.d.ts.map +1 -0
- package/dist/adapters/slack/index.js +3 -0
- package/dist/adapters/slack/index.js.map +1 -0
- package/dist/adapters/slack/tools/attach.d.ts +12 -0
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -0
- package/dist/adapters/slack/tools/attach.js +38 -0
- package/dist/adapters/slack/tools/attach.js.map +1 -0
- package/dist/agent.d.ts +26 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +763 -0
- package/dist/agent.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +34 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +110 -0
- package/dist/context.js.map +1 -0
- package/dist/download.d.ts +2 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +89 -0
- package/dist/download.js.map +1 -0
- package/dist/events.d.ts +57 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +310 -0
- package/dist/events.js.map +1 -0
- package/dist/log.d.ts +39 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +222 -0
- package/dist/log.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +247 -0
- package/dist/main.js.map +1 -0
- package/dist/sandbox.d.ts +34 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +183 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/store.d.ts +60 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +180 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/bash.d.ts +10 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +78 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +11 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +131 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +19 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.d.ts +11 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +134 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/truncate.d.ts +57 -0
- package/dist/tools/truncate.d.ts.map +1 -0
- package/dist/tools/truncate.js +184 -0
- package/dist/tools/truncate.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +33 -0
- package/dist/tools/write.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent, type AgentEvent } from \"@mariozechner/pi-agent-core\";\nimport { getModel, type ImageContent } from \"@mariozechner/pi-ai\";\nimport {\n\tAgentSession,\n\tAuthStorage,\n\tconvertToLlm,\n\tcreateExtensionRuntime,\n\tformatSkillsForPrompt,\n\tloadSkillsFromDir,\n\tModelRegistry,\n\ttype ResourceLoader,\n\tSessionManager,\n\ttype Skill,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, mkdirSync, readFileSync } from \"fs\";\nimport { mkdir, readFile, writeFile } from \"fs/promises\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport type { ChatMessage, ChatResponseContext, PlatformInfo } from \"./adapter.js\";\nimport type { AgentConfig } from \"./config.js\";\nimport { loadAgentConfig } from \"./config.js\";\nimport { createMamaSettingsManager, syncLogToSessionManager } from \"./context.js\";\nimport * as log from \"./log.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport { createMamaTools } from \"./tools/index.js\";\n\nexport interface PendingMessage {\n\tuserName: string;\n\ttext: string;\n\tattachments: { local: string }[];\n\ttimestamp: number;\n}\n\nexport interface AgentRunner {\n\trun(\n\t\tmessage: ChatMessage,\n\t\tresponseCtx: ChatResponseContext,\n\t\tplatform: PlatformInfo,\n\t): Promise<{ stopReason: string; errorMessage?: string }>;\n\tabort(): void;\n}\n\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n\tjpg: \"image/jpeg\",\n\tjpeg: \"image/jpeg\",\n\tpng: \"image/png\",\n\tgif: \"image/gif\",\n\twebp: \"image/webp\",\n};\n\nfunction getImageMimeType(filename: string): string | undefined {\n\treturn IMAGE_MIME_TYPES[filename.toLowerCase().split(\".\").pop() || \"\"];\n}\n\nasync function getMemory(channelDir: string): Promise<string> {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = (await readFile(workspaceMemoryPath, \"utf-8\")).trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Global Workspace Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = (await readFile(channelMemoryPath, \"utf-8\")).trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Channel-Specific Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction loadMamaSkills(channelDir: string, workspacePath: string): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\n\t// channelDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)\n\t// hostWorkspacePath is the parent directory on host\n\t// workspacePath is the container path (e.g., /workspace)\n\tconst hostWorkspacePath = join(channelDir, \"..\");\n\n\t// Helper to translate host paths to container paths\n\tconst translatePath = (hostPath: string): string => {\n\t\tif (hostPath.startsWith(hostWorkspacePath)) {\n\t\t\treturn workspacePath + hostPath.slice(hostWorkspacePath.length);\n\t\t}\n\t\treturn hostPath;\n\t};\n\n\t// Load workspace-level skills (global)\n\tconst workspaceSkillsDir = join(hostWorkspacePath, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: \"workspace\" }).skills) {\n\t\t// Translate paths to container paths for system prompt\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Load channel-specific skills (override workspace skills on collision)\n\tconst channelSkillsDir = join(channelDir, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: channelSkillsDir, source: \"channel\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n\tplatform: PlatformInfo,\n\tskills: Skill[],\n\tmaxUsersInPrompt: number,\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\t// Format channel mappings\n\tconst channelMappings =\n\t\tplatform.channels.length > 0\n\t\t\t? platform.channels.map((c) => `${c.id}\\t#${c.name}`).join(\"\\n\")\n\t\t\t: \"(no channels loaded)\";\n\n\t// Format user mappings (limited to maxUsersInPrompt)\n\tconst limitedUsers = platform.users.slice(0, maxUsersInPrompt);\n\tconst userCountNote = platform.users.length > maxUsersInPrompt ? ` (showing ${maxUsersInPrompt} of ${platform.users.length})` : \"\";\n\tconst userMappings =\n\t\tlimitedUsers.length > 0\n\t\t\t? limitedUsers.map((u) => `${u.id}\\t@${u.userName}\\t${u.displayName}`).join(\"\\n\")\n\t\t\t: \"(no users loaded)\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Bash working directory: / (use cd or absolute paths)\n- Install tools with: apk add <package>\n- Your changes persist across sessions`\n\t\t: `You are running directly on the host machine.\n- Bash working directory: ${process.cwd()}\n- Be careful with system modifications`;\n\n\treturn `You are mama, a ${platform.name} bot assistant. Be concise. No emojis.\n\n## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\n\n${platform.formattingGuide}\n\n## Platform IDs\nChannels: ${channelMappings}\n\nUsers: ${userMappings}${userCountNote}\n\nWhen mentioning users, use <@username> format (e.g., <@mario>).\n\n## Environment\n${envDescription}\n\n## Workspace Layout\n${workspacePath}/\n├── MEMORY.md # Global memory (all channels)\n├── skills/ # Global CLI tools you create\n└── ${channelId}/ # This channel\n ├── MEMORY.md # Channel-specific memory\n ├── log.jsonl # Message history (no tool results)\n ├── attachments/ # User-shared files\n ├── scratch/ # Your working directory\n └── skills/ # Channel-specific tools\n\n## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\n\n### Creating Skills\nStore in \\`${workspacePath}/skills/<name>/\\` (global) or \\`${channelPath}/skills/<name>/\\` (channel-specific).\nEach skill directory needs a \\`SKILL.md\\` with YAML frontmatter:\n\n\\`\\`\\`markdown\n---\nname: skill-name\ndescription: Short description of what this skill does\n---\n\n# Skill Name\n\nUsage instructions, examples, etc.\nScripts are in: {baseDir}/\n\\`\\`\\`\n\n\\`name\\` and \\`description\\` are required. Use \\`{baseDir}\\` as placeholder for the skill's directory path.\n\n### Available Skills\n${skills.length > 0 ? formatSkillsForPrompt(skills) : \"(no skills installed yet)\"}\n\n## Events\nYou can schedule events that wake you up at specific times or when external things happen. Events are JSON files in \\`${workspacePath}/events/\\`.\n\n### Event Types\n\n**Immediate** - Triggers as soon as harness sees the file. Use in scripts/webhooks to signal external events.\n\\`\\`\\`json\n{\"type\": \"immediate\", \"channelId\": \"${channelId}\", \"text\": \"New GitHub issue opened\"}\n\\`\\`\\`\n\n**One-shot** - Triggers once at a specific time. Use for reminders.\n\\`\\`\\`json\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Remind Mario about dentist\", \"at\": \"2025-12-15T09:00:00+01:00\"}\n\\`\\`\\`\n\n**Periodic** - Triggers on a cron schedule. Use for recurring tasks.\n\\`\\`\\`json\n{\"type\": \"periodic\", \"channelId\": \"${channelId}\", \"text\": \"Check inbox and summarize\", \"schedule\": \"0 9 * * 1-5\", \"timezone\": \"${Intl.DateTimeFormat().resolvedOptions().timeZone}\"}\n\\`\\`\\`\n\n### Cron Format\n\\`minute hour day-of-month month day-of-week\\`\n- \\`0 9 * * *\\` = daily at 9:00\n- \\`0 9 * * 1-5\\` = weekdays at 9:00\n- \\`30 14 * * 1\\` = Mondays at 14:30\n- \\`0 0 1 * *\\` = first of each month at midnight\n\n### Timezones\nAll \\`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}.\n\n### Creating Events\nUse unique filenames to avoid overwriting existing events. Include a timestamp or random suffix:\n\\`\\`\\`bash\ncat > ${workspacePath}/events/dentist-reminder-$(date +%s).json << 'EOF'\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Dentist tomorrow\", \"at\": \"2025-12-14T09:00:00+01:00\"}\nEOF\n\\`\\`\\`\nOr check if file exists first before creating.\n\n### Managing Events\n- List: \\`ls ${workspacePath}/events/\\`\n- View: \\`cat ${workspacePath}/events/foo.json\\`\n- Delete/cancel: \\`rm ${workspacePath}/events/foo.json\\`\n\n### When Events Trigger\nYou receive a message like:\n\\`\\`\\`\n[EVENT:dentist-reminder.json:one-shot:2025-12-14T09:00:00+01:00] Dentist tomorrow\n\\`\\`\\`\nImmediate and one-shot events auto-delete after triggering. Periodic events persist until you delete them.\n\n### Silent Completion\nFor periodic events where there's nothing to report, respond with just \\`[SILENT]\\` (no other text). This deletes the status message and posts nothing to the platform. Use this to avoid spamming the channel when periodic checks find nothing actionable.\n\n### Debouncing\nWhen writing programs that create immediate events (email watchers, webhook handlers, etc.), always debounce. If 50 emails arrive in a minute, don't create 50 immediate events. Instead collect events over a window and create ONE immediate event summarizing what happened, or just signal \"new activity, check inbox\" rather than per-item events. Or simpler: use a periodic event to check for new items every N minutes instead of immediate events.\n\n### Limits\nMaximum 5 events can be queued. Don't create excessive immediate or periodic events.\n\n## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspacePath}/MEMORY.md): skills, preferences, project info\n- Channel (${channelPath}/MEMORY.md): channel-specific decisions, ongoing work\nUpdate when you learn something important or when asked to remember something.\n\n### Current Memory\n${memory}\n\n## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified (~/.gitconfig, cron jobs, etc.)\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment. On fresh container, read it first to restore your setup.\n\n## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Messages from specific user\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'\n\\`\\`\\`\n\n## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files\n- edit: Surgical file edits\n- attach: Share files to the platform\n\nEach tool requires a \"label\" parameter (shown to user).\n`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen - 3)}...`;\n}\n\nfunction extractToolResultText(result: unknown): string {\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\treturn JSON.stringify(result);\n}\n\nfunction formatToolArgsForSlack(_toolName: string, args: Record<string, unknown>): string {\n\tconst lines: string[] = [];\n\n\tfor (const [key, value] of Object.entries(args)) {\n\t\tif (key === \"label\") continue;\n\n\t\tif (key === \"path\" && typeof value === \"string\") {\n\t\t\tconst offset = args.offset as number | undefined;\n\t\t\tconst limit = args.limit as number | undefined;\n\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\tlines.push(`${value}:${offset}-${offset + limit}`);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (key === \"offset\" || key === \"limit\") continue;\n\n\t\tif (typeof value === \"string\") {\n\t\t\tlines.push(value);\n\t\t} else {\n\t\t\tlines.push(JSON.stringify(value));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// ============================================================================\n// Agent runner\n// ============================================================================\n\n/**\n * Create a new AgentRunner for a channel.\n * Sets up the session and subscribes to events once.\n *\n * Runner caching is handled by the caller (channelStates in main.ts).\n * This is a stateless factory function.\n */\nexport async function createRunner(\n\tsandboxConfig: SandboxConfig,\n\tsessionKey: string,\n\tchannelId: string,\n\tchannelDir: string,\n\tworkspaceDir: string,\n): Promise<AgentRunner> {\n\tconst agentConfig = loadAgentConfig(workspaceDir);\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\n\t// Create tools (per-runner, with per-runner upload function setter)\n\tconst { tools, setUploadFunction } = createMamaTools(executor);\n\n\t// Resolve model from config\n\t// Use 'as any' cast because agentConfig.provider/model are plain strings,\n\t// while getModel() has constrained generic types for known providers.\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tconst model = (getModel as any)(agentConfig.provider, agentConfig.model);\n\n\t// Initial system prompt (will be updated each run with fresh memory/channels/users/skills)\n\tconst memory = await getMemory(channelDir);\n\tconst skills = loadMamaSkills(channelDir, workspacePath);\n\tconst emptyPlatform: PlatformInfo = { name: \"slack\", formattingGuide: \"\", channels: [], users: [] };\n\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, emptyPlatform, skills, agentConfig.maxUsersInPrompt ?? 50);\n\n\t// Create session manager and settings manager\n\t// Per-session context file: {channelDir}/sessions/{rootTs}/context.jsonl\n\tconst rootTs = sessionKey.includes(\":\") ? sessionKey.split(\":\").pop()! : sessionKey;\n\tconst sessionDir = join(channelDir, \"sessions\", rootTs);\n\tmkdirSync(sessionDir, { recursive: true });\n\tconst contextFile = join(sessionDir, \"context.jsonl\");\n\tconst sessionManager = SessionManager.open(contextFile, channelDir);\n\tconst settingsManager = createMamaSettingsManager(join(channelDir, \"..\"));\n\n\t// Create AuthStorage and ModelRegistry\n\t// Auth stored outside workspace so agent can't access it\n\tconst authStorage = AuthStorage.create(join(homedir(), \".pi\", \"mama\", \"auth.json\"));\n\tconst modelRegistry = new ModelRegistry(authStorage);\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel,\n\t\t\tthinkingLevel: (agentConfig.thinkingLevel as \"off\" | \"low\" | \"medium\" | \"high\" | undefined) ?? \"off\",\n\t\t\ttools,\n\t\t},\n\t\tconvertToLlm,\n\t\tgetApiKey: async () => {\n\t\t\tconst key = await modelRegistry.getApiKey(model);\n\t\t\tif (!key)\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No API key for provider \"${model.provider}\". Set the appropriate environment variable or configure via auth.json`,\n\t\t\t\t);\n\t\t\treturn key;\n\t\t},\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.buildSessionContext();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\tconst resourceLoader: ResourceLoader = {\n\t\tgetExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),\n\t\tgetSkills: () => ({ skills: [], diagnostics: [] }),\n\t\tgetPrompts: () => ({ prompts: [], diagnostics: [] }),\n\t\tgetThemes: () => ({ themes: [], diagnostics: [] }),\n\t\tgetAgentsFiles: () => ({ agentsFiles: [] }),\n\t\tgetSystemPrompt: () => systemPrompt,\n\t\tgetAppendSystemPrompt: () => [],\n\t\tgetPathMetadata: () => new Map(),\n\t\textendResources: () => {},\n\t\treload: async () => {},\n\t};\n\n\tconst baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));\n\n\t// Create AgentSession wrapper\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd: process.cwd(),\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tbaseToolsOverride,\n\t});\n\n\t// Mutable per-run state - event handler references this\n\tconst runState = {\n\t\tresponseCtx: null as ChatResponseContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event) => {\n\t\t// Skip if no active run\n\t\tif (!runState.responseCtx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { responseCtx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => responseCtx.respond(`_→ ${label}_`), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\t// Post args + result to thread\n\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\tconst argsFormatted = pending\n\t\t\t\t? formatToolArgsForSlack(agentEvent.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t: \"(args not found)\";\n\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\tlet threadMessage = `*${agentEvent.isError ? \"✗\" : \"✓\"} ${agentEvent.toolName}*`;\n\t\t\tif (label) threadMessage += `: ${label}`;\n\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\t\t\tif (argsFormatted) threadMessage += `\\`\\`\\`\\n${argsFormatted}\\n\\`\\`\\`\\n`;\n\t\t\tthreadMessage += `*Result:*\\n\\`\\`\\`\\n${resultStr}\\n\\`\\`\\``;\n\n\t\t\tqueue.enqueueMessage(threadMessage, \"thread\", \"tool result thread\", false);\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(() => responseCtx.respond(`_Error: ${truncate(resultStr, 200)}_`), \"tool error\");\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"main\", \"thinking main\");\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"thread\", \"thinking thread\", false);\n\t\t\t\t}\n\n\t\t\t\tif (text.trim()) {\n\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\tqueue.enqueueMessage(text, \"main\", \"response main\");\n\t\t\t\t\tqueue.enqueueMessage(text, \"thread\", \"response thread\", false);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(() => responseCtx.respond(\"_Compacting context..._\"), \"compaction start\");\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => responseCtx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\t// Message limit constant\n\tconst SLACK_MAX_LENGTH = 40000;\n\tconst splitForSlack = (text: string): string[] => {\n\t\tif (text.length <= SLACK_MAX_LENGTH) return [text];\n\t\tconst parts: string[] = [];\n\t\tlet remaining = text;\n\t\tlet partNum = 1;\n\t\twhile (remaining.length > 0) {\n\t\t\tconst chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);\n\t\t\tremaining = remaining.substring(SLACK_MAX_LENGTH - 50);\n\t\t\tconst suffix = remaining.length > 0 ? `\\n_(continued ${partNum}...)_` : \"\";\n\t\t\tparts.push(chunk + suffix);\n\t\t\tpartNum++;\n\t\t}\n\t\treturn parts;\n\t};\n\n\treturn {\n\t\tasync run(\n\t\t\tmessage: ChatMessage,\n\t\t\tresponseCtx: ChatResponseContext,\n\t\t\tplatform: PlatformInfo,\n\t\t): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Extract channelId from sessionKey (format: \"channelId:rootTs\" or just \"channelId\")\n\t\t\tconst sessionChannel = message.sessionKey.split(\":\")[0];\n\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t// Sync messages from log.jsonl that arrived while we were offline or busy\n\t\t\t// Exclude the current message (it will be added via prompt())\n\t\t\t// Default sync range is 2 days (handled by syncLogToSessionManager)\n\t\t\tconst syncedCount = await syncLogToSessionManager(sessionManager, channelDir, message.id);\n\t\t\tif (syncedCount > 0) {\n\t\t\t\tlog.logInfo(`[${channelId}] Synced ${syncedCount} messages from log.jsonl`);\n\t\t\t}\n\n\t\t\t// Reload messages from context.jsonl\n\t\t\t// This picks up any messages synced above\n\t\t\tconst reloadedSession = sessionManager.buildSessionContext();\n\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t}\n\n\t\t\t// Update system prompt with fresh memory, channel/user info, and skills\n\t\t\tconst memory = await getMemory(channelDir);\n\t\t\tconst skills = loadMamaSkills(channelDir, workspacePath);\n\t\t\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, platform, skills, agentConfig.maxUsersInPrompt ?? 50);\n\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\n\t\t\t// Set up file upload function\n\t\t\tsetUploadFunction(async (filePath: string, title?: string) => {\n\t\t\t\tconst hostPath = translateToHostPath(filePath, channelDir, workspacePath, channelId);\n\t\t\t\tawait responseCtx.uploadFile(hostPath, title);\n\t\t\t});\n\n\t\t\t// Reset per-run state\n\t\t\trunState.responseCtx = responseCtx;\n\t\t\trunState.logCtx = {\n\t\t\t\tchannelId: sessionChannel,\n\t\t\t\tuserName: message.userName,\n\t\t\t\tchannelName: undefined,\n\t\t\t};\n\t\t\trunState.pendingTools.clear();\n\t\t\trunState.totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait fn();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(`API error (${errorContext})`, errMsg);\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait responseCtx.respondInThread(`_Error: ${errMsg}_`);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Ignore\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, _doLog = true): void {\n\t\t\t\t\tconst parts = splitForSlack(text);\n\t\t\t\t\tfor (const part of parts) {\n\t\t\t\t\t\tthis.enqueue(\n\t\t\t\t\t\t\t() => (target === \"main\" ? responseCtx.respond(part) : responseCtx.respondInThread(part)),\n\t\t\t\t\t\t\terrorContext,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Log context info\n\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\t\t\tlog.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);\n\n\t\t\t// Build user message with timestamp and username prefix\n\t\t\t// Format: \"[YYYY-MM-DD HH:MM:SS+HH:MM] [username]: message\" so LLM knows when and who\n\t\t\tconst now = new Date();\n\t\t\tconst pad = (n: number) => n.toString().padStart(2, \"0\");\n\t\t\tconst offset = -now.getTimezoneOffset();\n\t\t\tconst offsetSign = offset >= 0 ? \"+\" : \"-\";\n\t\t\tconst offsetHours = pad(Math.floor(Math.abs(offset) / 60));\n\t\t\tconst offsetMins = pad(Math.abs(offset) % 60);\n\t\t\tconst timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;\n\t\t\tlet userMessage = `[${timestamp}] [${message.userName || \"unknown\"}]: ${message.text}`;\n\n\t\t\tconst imageAttachments: ImageContent[] = [];\n\t\t\tconst nonImagePaths: string[] = [];\n\n\t\t\tfor (const a of message.attachments || []) {\n\t\t\t\t// a.localPath is the path relative to the workspace (same as old a.local)\n\t\t\t\tconst fullPath = `${workspacePath}/${a.localPath}`;\n\t\t\t\tconst mimeType = getImageMimeType(a.localPath);\n\n\t\t\t\tif (mimeType && existsSync(fullPath)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\timageAttachments.push({\n\t\t\t\t\t\t\ttype: \"image\",\n\t\t\t\t\t\t\tmimeType,\n\t\t\t\t\t\t\tdata: readFileSync(fullPath).toString(\"base64\"),\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tnonImagePaths.push(fullPath);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnonImagePaths.push(fullPath);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (nonImagePaths.length > 0) {\n\t\t\t\tuserMessage += `\\n\\n<slack_attachments>\\n${nonImagePaths.join(\"\\n\")}\\n</slack_attachments>`;\n\t\t\t}\n\n\t\t\t// Debug: write context to last_prompt.jsonl\n\t\t\tconst debugContext = {\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: session.messages,\n\t\t\t\tnewUserMessage: userMessage,\n\t\t\t\timageAttachmentCount: imageAttachments.length,\n\t\t\t};\n\t\t\tawait writeFile(join(channelDir, \"last_prompt.jsonl\"), JSON.stringify(debugContext, null, 2));\n\n\t\t\tawait session.prompt(userMessage, imageAttachments.length > 0 ? { images: imageAttachments } : undefined);\n\n\t\t\t// Wait for queued messages\n\t\t\tawait queueChain;\n\n\t\t\t// Handle error case - update main message and post error to thread\n\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage) {\n\t\t\t\ttry {\n\t\t\t\t\tawait responseCtx.replaceResponse(\"_Sorry, something went wrong_\");\n\t\t\t\t\tawait responseCtx.respondInThread(`_Error: ${runState.errorMessage}_`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tlog.logWarning(\"Failed to post error message\", errMsg);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Final message update\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistant = messages.filter((m) => m.role === \"assistant\").pop();\n\t\t\t\tconst finalText =\n\t\t\t\t\tlastAssistant?.content\n\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t.join(\"\\n\") || \"\";\n\n\t\t\t\t// Check for [SILENT] marker - delete message and thread instead of posting\n\t\t\t\tif (finalText.trim() === \"[SILENT]\" || finalText.trim().startsWith(\"[SILENT]\")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait responseCtx.deleteResponse();\n\t\t\t\t\t\tlog.logInfo(\"Silent response - deleted message and thread\");\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\tlog.logWarning(\"Failed to delete message for silent response\", errMsg);\n\t\t\t\t\t}\n\t\t\t\t} else if (finalText.trim()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst mainText =\n\t\t\t\t\t\t\tfinalText.length > SLACK_MAX_LENGTH\n\t\t\t\t\t\t\t\t? `${finalText.substring(0, SLACK_MAX_LENGTH - 50)}\\n\\n_(see thread for full response)_`\n\t\t\t\t\t\t\t\t: finalText;\n\t\t\t\t\t\tawait responseCtx.replaceResponse(mainText);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Log usage summary with context info\n\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t// Get last non-aborted assistant message for context calculation\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t.slice()\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.find((m) => m.role === \"assistant\" && (m as any).stopReason !== \"aborted\") as any;\n\n\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t: 0;\n\t\t\t\tconst contextWindow = model.contextWindow || 200000;\n\n\t\t\t\tconst summary = log.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\trunState.queue.enqueue(() => responseCtx.respondInThread(summary), \"usage summary\");\n\t\t\t\tawait queueChain;\n\t\t\t}\n\n\t\t\t// Clear run state\n\t\t\trunState.responseCtx = null;\n\t\t\trunState.logCtx = null;\n\t\t\trunState.queue = null;\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nfunction translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
provider: string;
|
|
3
|
+
model: string;
|
|
4
|
+
thinkingLevel?: string;
|
|
5
|
+
sessionScope?: "thread" | "channel";
|
|
6
|
+
maxUsersInPrompt?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function loadAgentConfig(workspaceDir: string): AgentConfig;
|
|
9
|
+
export declare function saveAgentConfig(workspaceDir: string, config: Partial<AgentConfig>): void;
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAUD,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAuBjE;AAED,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAwBxF","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nexport interface AgentConfig {\n\tprovider: string;\n\tmodel: string;\n\tthinkingLevel?: string;\n\tsessionScope?: \"thread\" | \"channel\";\n\tmaxUsersInPrompt?: number;\n}\n\nconst DEFAULTS: AgentConfig = {\n\tprovider: \"anthropic\",\n\tmodel: \"claude-sonnet-4-5\",\n\tthinkingLevel: \"off\",\n\tsessionScope: \"thread\",\n\tmaxUsersInPrompt: 50,\n};\n\nexport function loadAgentConfig(workspaceDir: string): AgentConfig {\n\tconst settingsPath = join(workspaceDir, \"settings.json\");\n\n\tlet fromFile: Partial<AgentConfig> = {};\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst raw = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tif (parsed && typeof parsed === \"object\") {\n\t\t\t\tfromFile = parsed as Partial<AgentConfig>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore parse errors, fall through to env/defaults\n\t\t}\n\t}\n\n\tconst provider = fromFile.provider || process.env.MOM_AI_PROVIDER || DEFAULTS.provider;\n\tconst model = fromFile.model || process.env.MOM_AI_MODEL || DEFAULTS.model;\n\tconst thinkingLevel = fromFile.thinkingLevel ?? DEFAULTS.thinkingLevel;\n\tconst sessionScope = fromFile.sessionScope ?? DEFAULTS.sessionScope;\n\tconst maxUsersInPrompt = fromFile.maxUsersInPrompt ?? DEFAULTS.maxUsersInPrompt;\n\n\treturn { provider, model, thinkingLevel, sessionScope, maxUsersInPrompt };\n}\n\nexport function saveAgentConfig(workspaceDir: string, config: Partial<AgentConfig>): void {\n\tconst settingsPath = join(workspaceDir, \"settings.json\");\n\n\tlet existing: Partial<AgentConfig> = {};\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst raw = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tif (parsed && typeof parsed === \"object\") {\n\t\t\t\texisting = parsed as Partial<AgentConfig>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Start fresh if file is malformed\n\t\t}\n\t}\n\n\tconst merged = { ...existing, ...config };\n\n\tconst dir = dirname(settingsPath);\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\twriteFileSync(settingsPath, JSON.stringify(merged, null, 2), \"utf-8\");\n}\n"]}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
provider: "anthropic",
|
|
5
|
+
model: "claude-sonnet-4-5",
|
|
6
|
+
thinkingLevel: "off",
|
|
7
|
+
sessionScope: "thread",
|
|
8
|
+
maxUsersInPrompt: 50,
|
|
9
|
+
};
|
|
10
|
+
export function loadAgentConfig(workspaceDir) {
|
|
11
|
+
const settingsPath = join(workspaceDir, "settings.json");
|
|
12
|
+
let fromFile = {};
|
|
13
|
+
if (existsSync(settingsPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (parsed && typeof parsed === "object") {
|
|
18
|
+
fromFile = parsed;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Ignore parse errors, fall through to env/defaults
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const provider = fromFile.provider || process.env.MOM_AI_PROVIDER || DEFAULTS.provider;
|
|
26
|
+
const model = fromFile.model || process.env.MOM_AI_MODEL || DEFAULTS.model;
|
|
27
|
+
const thinkingLevel = fromFile.thinkingLevel ?? DEFAULTS.thinkingLevel;
|
|
28
|
+
const sessionScope = fromFile.sessionScope ?? DEFAULTS.sessionScope;
|
|
29
|
+
const maxUsersInPrompt = fromFile.maxUsersInPrompt ?? DEFAULTS.maxUsersInPrompt;
|
|
30
|
+
return { provider, model, thinkingLevel, sessionScope, maxUsersInPrompt };
|
|
31
|
+
}
|
|
32
|
+
export function saveAgentConfig(workspaceDir, config) {
|
|
33
|
+
const settingsPath = join(workspaceDir, "settings.json");
|
|
34
|
+
let existing = {};
|
|
35
|
+
if (existsSync(settingsPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (parsed && typeof parsed === "object") {
|
|
40
|
+
existing = parsed;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Start fresh if file is malformed
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const merged = { ...existing, ...config };
|
|
48
|
+
const dir = dirname(settingsPath);
|
|
49
|
+
if (!existsSync(dir)) {
|
|
50
|
+
mkdirSync(dir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
writeFileSync(settingsPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAUrC,MAAM,QAAQ,GAAgB;IAC7B,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,mBAAmB;IAC1B,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,QAAQ;IACtB,gBAAgB,EAAE,EAAE;CACpB,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAe;IAClE,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAEzD,IAAI,QAAQ,GAAyB,EAAE,CAAC;IACxC,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC1C,QAAQ,GAAG,MAA8B,CAAC;YAC3C,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,oDAAoD;QACrD,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,QAAQ,CAAC,QAAQ,CAAC;IACvF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,QAAQ,CAAC,KAAK,CAAC;IAC3E,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC;IACvE,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAC;IACpE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,gBAAgB,IAAI,QAAQ,CAAC,gBAAgB,CAAC;IAEhF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAAA,CAC1E;AAED,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,MAA4B,EAAQ;IACzF,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAEzD,IAAI,QAAQ,GAAyB,EAAE,CAAC;IACxC,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC1C,QAAQ,GAAG,MAA8B,CAAC;YAC3C,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,mCAAmC;QACpC,CAAC;IACF,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IAE1C,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAAA,CACtE","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nexport interface AgentConfig {\n\tprovider: string;\n\tmodel: string;\n\tthinkingLevel?: string;\n\tsessionScope?: \"thread\" | \"channel\";\n\tmaxUsersInPrompt?: number;\n}\n\nconst DEFAULTS: AgentConfig = {\n\tprovider: \"anthropic\",\n\tmodel: \"claude-sonnet-4-5\",\n\tthinkingLevel: \"off\",\n\tsessionScope: \"thread\",\n\tmaxUsersInPrompt: 50,\n};\n\nexport function loadAgentConfig(workspaceDir: string): AgentConfig {\n\tconst settingsPath = join(workspaceDir, \"settings.json\");\n\n\tlet fromFile: Partial<AgentConfig> = {};\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst raw = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tif (parsed && typeof parsed === \"object\") {\n\t\t\t\tfromFile = parsed as Partial<AgentConfig>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore parse errors, fall through to env/defaults\n\t\t}\n\t}\n\n\tconst provider = fromFile.provider || process.env.MOM_AI_PROVIDER || DEFAULTS.provider;\n\tconst model = fromFile.model || process.env.MOM_AI_MODEL || DEFAULTS.model;\n\tconst thinkingLevel = fromFile.thinkingLevel ?? DEFAULTS.thinkingLevel;\n\tconst sessionScope = fromFile.sessionScope ?? DEFAULTS.sessionScope;\n\tconst maxUsersInPrompt = fromFile.maxUsersInPrompt ?? DEFAULTS.maxUsersInPrompt;\n\n\treturn { provider, model, thinkingLevel, sessionScope, maxUsersInPrompt };\n}\n\nexport function saveAgentConfig(workspaceDir: string, config: Partial<AgentConfig>): void {\n\tconst settingsPath = join(workspaceDir, \"settings.json\");\n\n\tlet existing: Partial<AgentConfig> = {};\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst raw = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tif (parsed && typeof parsed === \"object\") {\n\t\t\t\texisting = parsed as Partial<AgentConfig>;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Start fresh if file is malformed\n\t\t}\n\t}\n\n\tconst merged = { ...existing, ...config };\n\n\tconst dir = dirname(settingsPath);\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n\n\twriteFileSync(settingsPath, JSON.stringify(merged, null, 2), \"utf-8\");\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context management for mama.
|
|
3
|
+
*
|
|
4
|
+
* Mama uses two files per channel:
|
|
5
|
+
* - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)
|
|
6
|
+
* - log.jsonl: Human-readable channel history for grep (no tool results)
|
|
7
|
+
*
|
|
8
|
+
* This module provides:
|
|
9
|
+
* - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager
|
|
10
|
+
* - createMamaSettingsManager: Creates a SettingsManager backed by workspace settings.json
|
|
11
|
+
*/
|
|
12
|
+
import { type SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
/**
|
|
14
|
+
* Time range for filtering log messages
|
|
15
|
+
*/
|
|
16
|
+
export interface TimeRange {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sync user messages from log.jsonl to SessionManager.
|
|
22
|
+
*
|
|
23
|
+
* This ensures that messages logged while mama wasn't running (channel chatter,
|
|
24
|
+
* backfilled messages, messages while busy) are added to the LLM context.
|
|
25
|
+
*
|
|
26
|
+
* @param sessionManager - The SessionManager to sync to
|
|
27
|
+
* @param channelDir - Path to channel directory containing log.jsonl
|
|
28
|
+
* @param excludeSlackTs - Slack timestamp of current message (will be added via prompt(), not sync)
|
|
29
|
+
* @param timeRange - Optional time range to filter log entries (defaults to last 2 days)
|
|
30
|
+
* @returns Number of messages synced
|
|
31
|
+
*/
|
|
32
|
+
export declare function syncLogToSessionManager(sessionManager: SessionManager, channelDir: string, excludeSlackTs?: string, timeRange?: TimeRange): Promise<number>;
|
|
33
|
+
export declare function createMamaSettingsManager(_workspaceDir: string): SettingsManager;
|
|
34
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,KAAK,cAAc,EAA4B,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAS/G;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAgBD;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC5C,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,SAAS,GACnB,OAAO,CAAC,MAAM,CAAC,CA8EjB;AASD,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,CAEhF","sourcesContent":["/**\n * Context management for mama.\n *\n * Mama uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - createMamaSettingsManager: Creates a SettingsManager backed by workspace settings.json\n */\n\nimport type { UserMessage } from \"@mariozechner/pi-ai\";\nimport { type SessionManager, type SessionMessageEntry, SettingsManager } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\n/**\n * Time range for filtering log messages\n */\nexport interface TimeRange {\n\tstart: number; // Unix timestamp in ms\n\tend: number;\n}\n\n/**\n * Default number of days to sync when no time range is specified\n */\nconst DEFAULT_SYNC_DAYS = 2;\n\ninterface LogMessage {\n\tdate?: string;\n\tts?: string;\n\tuser?: string;\n\tuserName?: string;\n\ttext?: string;\n\tisBot?: boolean;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * This ensures that messages logged while mama wasn't running (channel chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param channelDir - Path to channel directory containing log.jsonl\n * @param excludeSlackTs - Slack timestamp of current message (will be added via prompt(), not sync)\n * @param timeRange - Optional time range to filter log entries (defaults to last 2 days)\n * @returns Number of messages synced\n */\nexport async function syncLogToSessionManager(\n\tsessionManager: SessionManager,\n\tchannelDir: string,\n\texcludeSlackTs?: string,\n\ttimeRange?: TimeRange,\n): Promise<number> {\n\t// Calculate default time range (last 2 days) if not provided\n\tconst now = Date.now();\n\tconst defaultStart = now - DEFAULT_SYNC_DAYS * 24 * 60 * 60 * 1000;\n\tconst range = timeRange ?? { start: defaultStart, end: now };\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Build set of existing timestamps from session entries\n\t// We use ts (Slack timestamp) as the unique key instead of message content\n\tconst existingTimestamps = new Set<string>();\n\tfor (const entry of sessionManager.getEntries()) {\n\t\tif (entry.type === \"message\") {\n\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\t// SessionMessageEntry has a timestamp field (number, Unix ms)\n\t\t\tif (msgEntry.timestamp) {\n\t\t\t\texistingTimestamps.add(msgEntry.timestamp.toString());\n\t\t\t}\n\t\t}\n\t}\n\n\t// Read log.jsonl and find user messages not in context\n\tconst logContent = await readFile(logFile, \"utf-8\");\n\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\tconst newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n\tfor (const line of logLines) {\n\t\ttry {\n\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\tconst slackTs = logMsg.ts;\n\t\t\tconst date = logMsg.date;\n\t\t\tif (!slackTs || !date) continue;\n\n\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\tif (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n\t\t\t// Skip bot messages - added through agent flow\n\t\t\tif (logMsg.isBot) continue;\n\n\t\t\t// Skip if this Slack timestamp is already in the session (dedupe by ts, not content)\n\t\t\t// Convert Slack ts (e.g., \"1234567890.123456\") to Unix ms for comparison\n\t\t\tconst slackTsMs = Math.floor(parseFloat(slackTs) * 1000).toString();\n\t\t\tif (existingTimestamps.has(slackTsMs)) continue;\n\n\t\t\t// Build the message text as it would appear in context\n\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\n\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\n\t\t\t// Skip messages outside the time range\n\t\t\tif (msgTime < range.start || msgTime > range.end) continue;\n\n\t\t\tconst userMessage: UserMessage = {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: messageText }],\n\t\t\t\ttimestamp: msgTime,\n\t\t\t};\n\n\t\t\tnewMessages.push({ timestamp: msgTime, message: userMessage });\n\t\t\texistingTimestamps.add(slackTsMs); // Track to avoid duplicates within this sync\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\tif (newMessages.length === 0) return 0;\n\n\t// Sort by timestamp and add to session\n\tnewMessages.sort((a, b) => a.timestamp - b.timestamp);\n\n\tfor (const { message } of newMessages) {\n\t\tsessionManager.appendMessage(message);\n\t}\n\n\treturn newMessages.length;\n}\n\n// ============================================================================\n// Settings manager for mama\n// ============================================================================\n\n// Mama manages model/provider config through its own config.ts / settings.json.\n// We use an in-memory SettingsManager so AgentSession has valid defaults\n// without interfering with coding-agent's global settings files.\nexport function createMamaSettingsManager(_workspaceDir: string): SettingsManager {\n\treturn SettingsManager.inMemory();\n}\n"]}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context management for mama.
|
|
3
|
+
*
|
|
4
|
+
* Mama uses two files per channel:
|
|
5
|
+
* - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)
|
|
6
|
+
* - log.jsonl: Human-readable channel history for grep (no tool results)
|
|
7
|
+
*
|
|
8
|
+
* This module provides:
|
|
9
|
+
* - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager
|
|
10
|
+
* - createMamaSettingsManager: Creates a SettingsManager backed by workspace settings.json
|
|
11
|
+
*/
|
|
12
|
+
import { SettingsManager } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
import { existsSync } from "fs";
|
|
14
|
+
import { readFile } from "fs/promises";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
/**
|
|
17
|
+
* Default number of days to sync when no time range is specified
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_SYNC_DAYS = 2;
|
|
20
|
+
/**
|
|
21
|
+
* Sync user messages from log.jsonl to SessionManager.
|
|
22
|
+
*
|
|
23
|
+
* This ensures that messages logged while mama wasn't running (channel chatter,
|
|
24
|
+
* backfilled messages, messages while busy) are added to the LLM context.
|
|
25
|
+
*
|
|
26
|
+
* @param sessionManager - The SessionManager to sync to
|
|
27
|
+
* @param channelDir - Path to channel directory containing log.jsonl
|
|
28
|
+
* @param excludeSlackTs - Slack timestamp of current message (will be added via prompt(), not sync)
|
|
29
|
+
* @param timeRange - Optional time range to filter log entries (defaults to last 2 days)
|
|
30
|
+
* @returns Number of messages synced
|
|
31
|
+
*/
|
|
32
|
+
export async function syncLogToSessionManager(sessionManager, channelDir, excludeSlackTs, timeRange) {
|
|
33
|
+
// Calculate default time range (last 2 days) if not provided
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const defaultStart = now - DEFAULT_SYNC_DAYS * 24 * 60 * 60 * 1000;
|
|
36
|
+
const range = timeRange ?? { start: defaultStart, end: now };
|
|
37
|
+
const logFile = join(channelDir, "log.jsonl");
|
|
38
|
+
if (!existsSync(logFile))
|
|
39
|
+
return 0;
|
|
40
|
+
// Build set of existing timestamps from session entries
|
|
41
|
+
// We use ts (Slack timestamp) as the unique key instead of message content
|
|
42
|
+
const existingTimestamps = new Set();
|
|
43
|
+
for (const entry of sessionManager.getEntries()) {
|
|
44
|
+
if (entry.type === "message") {
|
|
45
|
+
const msgEntry = entry;
|
|
46
|
+
// SessionMessageEntry has a timestamp field (number, Unix ms)
|
|
47
|
+
if (msgEntry.timestamp) {
|
|
48
|
+
existingTimestamps.add(msgEntry.timestamp.toString());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Read log.jsonl and find user messages not in context
|
|
53
|
+
const logContent = await readFile(logFile, "utf-8");
|
|
54
|
+
const logLines = logContent.trim().split("\n").filter(Boolean);
|
|
55
|
+
const newMessages = [];
|
|
56
|
+
for (const line of logLines) {
|
|
57
|
+
try {
|
|
58
|
+
const logMsg = JSON.parse(line);
|
|
59
|
+
const slackTs = logMsg.ts;
|
|
60
|
+
const date = logMsg.date;
|
|
61
|
+
if (!slackTs || !date)
|
|
62
|
+
continue;
|
|
63
|
+
// Skip the current message being processed (will be added via prompt())
|
|
64
|
+
if (excludeSlackTs && slackTs === excludeSlackTs)
|
|
65
|
+
continue;
|
|
66
|
+
// Skip bot messages - added through agent flow
|
|
67
|
+
if (logMsg.isBot)
|
|
68
|
+
continue;
|
|
69
|
+
// Skip if this Slack timestamp is already in the session (dedupe by ts, not content)
|
|
70
|
+
// Convert Slack ts (e.g., "1234567890.123456") to Unix ms for comparison
|
|
71
|
+
const slackTsMs = Math.floor(parseFloat(slackTs) * 1000).toString();
|
|
72
|
+
if (existingTimestamps.has(slackTsMs))
|
|
73
|
+
continue;
|
|
74
|
+
// Build the message text as it would appear in context
|
|
75
|
+
const messageText = `[${logMsg.userName || logMsg.user || "unknown"}]: ${logMsg.text || ""}`;
|
|
76
|
+
const msgTime = new Date(date).getTime() || Date.now();
|
|
77
|
+
// Skip messages outside the time range
|
|
78
|
+
if (msgTime < range.start || msgTime > range.end)
|
|
79
|
+
continue;
|
|
80
|
+
const userMessage = {
|
|
81
|
+
role: "user",
|
|
82
|
+
content: [{ type: "text", text: messageText }],
|
|
83
|
+
timestamp: msgTime,
|
|
84
|
+
};
|
|
85
|
+
newMessages.push({ timestamp: msgTime, message: userMessage });
|
|
86
|
+
existingTimestamps.add(slackTsMs); // Track to avoid duplicates within this sync
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Skip malformed lines
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (newMessages.length === 0)
|
|
93
|
+
return 0;
|
|
94
|
+
// Sort by timestamp and add to session
|
|
95
|
+
newMessages.sort((a, b) => a.timestamp - b.timestamp);
|
|
96
|
+
for (const { message } of newMessages) {
|
|
97
|
+
sessionManager.appendMessage(message);
|
|
98
|
+
}
|
|
99
|
+
return newMessages.length;
|
|
100
|
+
}
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Settings manager for mama
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Mama manages model/provider config through its own config.ts / settings.json.
|
|
105
|
+
// We use an in-memory SettingsManager so AgentSession has valid defaults
|
|
106
|
+
// without interfering with coding-agent's global settings files.
|
|
107
|
+
export function createMamaSettingsManager(_workspaceDir) {
|
|
108
|
+
return SettingsManager.inMemory();
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context management for mama.\n *\n * Mama uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - createMamaSettingsManager: Creates a SettingsManager backed by workspace settings.json\n */\n\nimport type { UserMessage } from \"@mariozechner/pi-ai\";\nimport { type SessionManager, type SessionMessageEntry, SettingsManager } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\n/**\n * Time range for filtering log messages\n */\nexport interface TimeRange {\n\tstart: number; // Unix timestamp in ms\n\tend: number;\n}\n\n/**\n * Default number of days to sync when no time range is specified\n */\nconst DEFAULT_SYNC_DAYS = 2;\n\ninterface LogMessage {\n\tdate?: string;\n\tts?: string;\n\tuser?: string;\n\tuserName?: string;\n\ttext?: string;\n\tisBot?: boolean;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * This ensures that messages logged while mama wasn't running (channel chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param channelDir - Path to channel directory containing log.jsonl\n * @param excludeSlackTs - Slack timestamp of current message (will be added via prompt(), not sync)\n * @param timeRange - Optional time range to filter log entries (defaults to last 2 days)\n * @returns Number of messages synced\n */\nexport async function syncLogToSessionManager(\n\tsessionManager: SessionManager,\n\tchannelDir: string,\n\texcludeSlackTs?: string,\n\ttimeRange?: TimeRange,\n): Promise<number> {\n\t// Calculate default time range (last 2 days) if not provided\n\tconst now = Date.now();\n\tconst defaultStart = now - DEFAULT_SYNC_DAYS * 24 * 60 * 60 * 1000;\n\tconst range = timeRange ?? { start: defaultStart, end: now };\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Build set of existing timestamps from session entries\n\t// We use ts (Slack timestamp) as the unique key instead of message content\n\tconst existingTimestamps = new Set<string>();\n\tfor (const entry of sessionManager.getEntries()) {\n\t\tif (entry.type === \"message\") {\n\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\t// SessionMessageEntry has a timestamp field (number, Unix ms)\n\t\t\tif (msgEntry.timestamp) {\n\t\t\t\texistingTimestamps.add(msgEntry.timestamp.toString());\n\t\t\t}\n\t\t}\n\t}\n\n\t// Read log.jsonl and find user messages not in context\n\tconst logContent = await readFile(logFile, \"utf-8\");\n\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\tconst newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n\tfor (const line of logLines) {\n\t\ttry {\n\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\tconst slackTs = logMsg.ts;\n\t\t\tconst date = logMsg.date;\n\t\t\tif (!slackTs || !date) continue;\n\n\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\tif (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n\t\t\t// Skip bot messages - added through agent flow\n\t\t\tif (logMsg.isBot) continue;\n\n\t\t\t// Skip if this Slack timestamp is already in the session (dedupe by ts, not content)\n\t\t\t// Convert Slack ts (e.g., \"1234567890.123456\") to Unix ms for comparison\n\t\t\tconst slackTsMs = Math.floor(parseFloat(slackTs) * 1000).toString();\n\t\t\tif (existingTimestamps.has(slackTsMs)) continue;\n\n\t\t\t// Build the message text as it would appear in context\n\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\n\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\n\t\t\t// Skip messages outside the time range\n\t\t\tif (msgTime < range.start || msgTime > range.end) continue;\n\n\t\t\tconst userMessage: UserMessage = {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: messageText }],\n\t\t\t\ttimestamp: msgTime,\n\t\t\t};\n\n\t\t\tnewMessages.push({ timestamp: msgTime, message: userMessage });\n\t\t\texistingTimestamps.add(slackTsMs); // Track to avoid duplicates within this sync\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\tif (newMessages.length === 0) return 0;\n\n\t// Sort by timestamp and add to session\n\tnewMessages.sort((a, b) => a.timestamp - b.timestamp);\n\n\tfor (const { message } of newMessages) {\n\t\tsessionManager.appendMessage(message);\n\t}\n\n\treturn newMessages.length;\n}\n\n// ============================================================================\n// Settings manager for mama\n// ============================================================================\n\n// Mama manages model/provider config through its own config.ts / settings.json.\n// We use an in-memory SettingsManager so AgentSession has valid defaults\n// without interfering with coding-agent's global settings files.\nexport function createMamaSettingsManager(_workspaceDir: string): SettingsManager {\n\treturn SettingsManager.inMemory();\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AA6BA,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFxF","sourcesContent":["import { LogLevel, WebClient } from \"@slack/web-api\";\n\ninterface Message {\n\tts: string;\n\tuser?: string;\n\ttext?: string;\n\tthread_ts?: string;\n\treply_count?: number;\n\tfiles?: Array<{ name: string; url_private?: string }>;\n}\n\nfunction formatTs(ts: string): string {\n\tconst date = new Date(parseFloat(ts) * 1000);\n\treturn date\n\t\t.toISOString()\n\t\t.replace(\"T\", \" \")\n\t\t.replace(/\\.\\d+Z$/, \"\");\n}\n\nfunction formatMessage(ts: string, user: string, text: string, indent = \"\"): string {\n\tconst prefix = `[${formatTs(ts)}] ${user}: `;\n\tconst lines = text.split(\"\\n\");\n\tconst firstLine = `${indent}${prefix}${lines[0]}`;\n\tif (lines.length === 1) return firstLine;\n\t// All continuation lines get same indent as content start\n\tconst contentIndent = indent + \" \".repeat(prefix.length);\n\treturn [firstLine, ...lines.slice(1).map((l) => contentIndent + l)].join(\"\\n\");\n}\n\nexport async function downloadChannel(channelId: string, botToken: string): Promise<void> {\n\tconst client = new WebClient(botToken, { logLevel: LogLevel.ERROR });\n\n\tconsole.error(`Fetching channel info for ${channelId}...`);\n\n\t// Get channel info\n\tlet channelName = channelId;\n\ttry {\n\t\tconst info = await client.conversations.info({ channel: channelId });\n\t\tchannelName = (info.channel as any)?.name || channelId;\n\t} catch {\n\t\t// DM channels don't have names, that's fine\n\t}\n\n\tconsole.error(`Downloading history for #${channelName} (${channelId})...`);\n\n\t// Fetch all messages\n\tconst messages: Message[] = [];\n\tlet cursor: string | undefined;\n\n\tdo {\n\t\tconst response = await client.conversations.history({\n\t\t\tchannel: channelId,\n\t\t\tlimit: 200,\n\t\t\tcursor,\n\t\t});\n\n\t\tif (response.messages) {\n\t\t\tmessages.push(...(response.messages as Message[]));\n\t\t}\n\n\t\tcursor = response.response_metadata?.next_cursor;\n\t\tconsole.error(` Fetched ${messages.length} messages...`);\n\t} while (cursor);\n\n\t// Reverse to chronological order\n\tmessages.reverse();\n\n\t// Build map of thread replies\n\tconst threadReplies = new Map<string, Message[]>();\n\tconst threadsToFetch = messages.filter((m) => m.reply_count && m.reply_count > 0);\n\n\tconsole.error(`Fetching ${threadsToFetch.length} threads...`);\n\n\tfor (let i = 0; i < threadsToFetch.length; i++) {\n\t\tconst parent = threadsToFetch[i];\n\t\tconsole.error(` Thread ${i + 1}/${threadsToFetch.length} (${parent.reply_count} replies)...`);\n\n\t\tconst replies: Message[] = [];\n\t\tlet threadCursor: string | undefined;\n\n\t\tdo {\n\t\t\tconst response = await client.conversations.replies({\n\t\t\t\tchannel: channelId,\n\t\t\t\tts: parent.ts,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor: threadCursor,\n\t\t\t});\n\n\t\t\tif (response.messages) {\n\t\t\t\t// Skip the first message (it's the parent)\n\t\t\t\treplies.push(...(response.messages as Message[]).slice(1));\n\t\t\t}\n\n\t\t\tthreadCursor = response.response_metadata?.next_cursor;\n\t\t} while (threadCursor);\n\n\t\tthreadReplies.set(parent.ts, replies);\n\t}\n\n\t// Output messages with thread replies interleaved\n\tlet totalReplies = 0;\n\tfor (const msg of messages) {\n\t\t// Output the message\n\t\tconsole.log(formatMessage(msg.ts, msg.user || \"unknown\", msg.text || \"\"));\n\n\t\t// Output thread replies right after parent (indented)\n\t\tconst replies = threadReplies.get(msg.ts);\n\t\tif (replies) {\n\t\t\tfor (const reply of replies) {\n\t\t\t\tconsole.log(formatMessage(reply.ts, reply.user || \"unknown\", reply.text || \"\", \" \"));\n\t\t\t\ttotalReplies++;\n\t\t\t}\n\t\t}\n\t}\n\n\tconsole.error(`Done! ${messages.length} messages, ${totalReplies} thread replies`);\n}\n"]}
|
package/dist/download.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { LogLevel, WebClient } from "@slack/web-api";
|
|
2
|
+
function formatTs(ts) {
|
|
3
|
+
const date = new Date(parseFloat(ts) * 1000);
|
|
4
|
+
return date
|
|
5
|
+
.toISOString()
|
|
6
|
+
.replace("T", " ")
|
|
7
|
+
.replace(/\.\d+Z$/, "");
|
|
8
|
+
}
|
|
9
|
+
function formatMessage(ts, user, text, indent = "") {
|
|
10
|
+
const prefix = `[${formatTs(ts)}] ${user}: `;
|
|
11
|
+
const lines = text.split("\n");
|
|
12
|
+
const firstLine = `${indent}${prefix}${lines[0]}`;
|
|
13
|
+
if (lines.length === 1)
|
|
14
|
+
return firstLine;
|
|
15
|
+
// All continuation lines get same indent as content start
|
|
16
|
+
const contentIndent = indent + " ".repeat(prefix.length);
|
|
17
|
+
return [firstLine, ...lines.slice(1).map((l) => contentIndent + l)].join("\n");
|
|
18
|
+
}
|
|
19
|
+
export async function downloadChannel(channelId, botToken) {
|
|
20
|
+
const client = new WebClient(botToken, { logLevel: LogLevel.ERROR });
|
|
21
|
+
console.error(`Fetching channel info for ${channelId}...`);
|
|
22
|
+
// Get channel info
|
|
23
|
+
let channelName = channelId;
|
|
24
|
+
try {
|
|
25
|
+
const info = await client.conversations.info({ channel: channelId });
|
|
26
|
+
channelName = info.channel?.name || channelId;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// DM channels don't have names, that's fine
|
|
30
|
+
}
|
|
31
|
+
console.error(`Downloading history for #${channelName} (${channelId})...`);
|
|
32
|
+
// Fetch all messages
|
|
33
|
+
const messages = [];
|
|
34
|
+
let cursor;
|
|
35
|
+
do {
|
|
36
|
+
const response = await client.conversations.history({
|
|
37
|
+
channel: channelId,
|
|
38
|
+
limit: 200,
|
|
39
|
+
cursor,
|
|
40
|
+
});
|
|
41
|
+
if (response.messages) {
|
|
42
|
+
messages.push(...response.messages);
|
|
43
|
+
}
|
|
44
|
+
cursor = response.response_metadata?.next_cursor;
|
|
45
|
+
console.error(` Fetched ${messages.length} messages...`);
|
|
46
|
+
} while (cursor);
|
|
47
|
+
// Reverse to chronological order
|
|
48
|
+
messages.reverse();
|
|
49
|
+
// Build map of thread replies
|
|
50
|
+
const threadReplies = new Map();
|
|
51
|
+
const threadsToFetch = messages.filter((m) => m.reply_count && m.reply_count > 0);
|
|
52
|
+
console.error(`Fetching ${threadsToFetch.length} threads...`);
|
|
53
|
+
for (let i = 0; i < threadsToFetch.length; i++) {
|
|
54
|
+
const parent = threadsToFetch[i];
|
|
55
|
+
console.error(` Thread ${i + 1}/${threadsToFetch.length} (${parent.reply_count} replies)...`);
|
|
56
|
+
const replies = [];
|
|
57
|
+
let threadCursor;
|
|
58
|
+
do {
|
|
59
|
+
const response = await client.conversations.replies({
|
|
60
|
+
channel: channelId,
|
|
61
|
+
ts: parent.ts,
|
|
62
|
+
limit: 200,
|
|
63
|
+
cursor: threadCursor,
|
|
64
|
+
});
|
|
65
|
+
if (response.messages) {
|
|
66
|
+
// Skip the first message (it's the parent)
|
|
67
|
+
replies.push(...response.messages.slice(1));
|
|
68
|
+
}
|
|
69
|
+
threadCursor = response.response_metadata?.next_cursor;
|
|
70
|
+
} while (threadCursor);
|
|
71
|
+
threadReplies.set(parent.ts, replies);
|
|
72
|
+
}
|
|
73
|
+
// Output messages with thread replies interleaved
|
|
74
|
+
let totalReplies = 0;
|
|
75
|
+
for (const msg of messages) {
|
|
76
|
+
// Output the message
|
|
77
|
+
console.log(formatMessage(msg.ts, msg.user || "unknown", msg.text || ""));
|
|
78
|
+
// Output thread replies right after parent (indented)
|
|
79
|
+
const replies = threadReplies.get(msg.ts);
|
|
80
|
+
if (replies) {
|
|
81
|
+
for (const reply of replies) {
|
|
82
|
+
console.log(formatMessage(reply.ts, reply.user || "unknown", reply.text || "", " "));
|
|
83
|
+
totalReplies++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.error(`Done! ${messages.length} messages, ${totalReplies} thread replies`);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.js","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"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","sourcesContent":["import { LogLevel, WebClient } from \"@slack/web-api\";\n\ninterface Message {\n\tts: string;\n\tuser?: string;\n\ttext?: string;\n\tthread_ts?: string;\n\treply_count?: number;\n\tfiles?: Array<{ name: string; url_private?: string }>;\n}\n\nfunction formatTs(ts: string): string {\n\tconst date = new Date(parseFloat(ts) * 1000);\n\treturn date\n\t\t.toISOString()\n\t\t.replace(\"T\", \" \")\n\t\t.replace(/\\.\\d+Z$/, \"\");\n}\n\nfunction formatMessage(ts: string, user: string, text: string, indent = \"\"): string {\n\tconst prefix = `[${formatTs(ts)}] ${user}: `;\n\tconst lines = text.split(\"\\n\");\n\tconst firstLine = `${indent}${prefix}${lines[0]}`;\n\tif (lines.length === 1) return firstLine;\n\t// All continuation lines get same indent as content start\n\tconst contentIndent = indent + \" \".repeat(prefix.length);\n\treturn [firstLine, ...lines.slice(1).map((l) => contentIndent + l)].join(\"\\n\");\n}\n\nexport async function downloadChannel(channelId: string, botToken: string): Promise<void> {\n\tconst client = new WebClient(botToken, { logLevel: LogLevel.ERROR });\n\n\tconsole.error(`Fetching channel info for ${channelId}...`);\n\n\t// Get channel info\n\tlet channelName = channelId;\n\ttry {\n\t\tconst info = await client.conversations.info({ channel: channelId });\n\t\tchannelName = (info.channel as any)?.name || channelId;\n\t} catch {\n\t\t// DM channels don't have names, that's fine\n\t}\n\n\tconsole.error(`Downloading history for #${channelName} (${channelId})...`);\n\n\t// Fetch all messages\n\tconst messages: Message[] = [];\n\tlet cursor: string | undefined;\n\n\tdo {\n\t\tconst response = await client.conversations.history({\n\t\t\tchannel: channelId,\n\t\t\tlimit: 200,\n\t\t\tcursor,\n\t\t});\n\n\t\tif (response.messages) {\n\t\t\tmessages.push(...(response.messages as Message[]));\n\t\t}\n\n\t\tcursor = response.response_metadata?.next_cursor;\n\t\tconsole.error(` Fetched ${messages.length} messages...`);\n\t} while (cursor);\n\n\t// Reverse to chronological order\n\tmessages.reverse();\n\n\t// Build map of thread replies\n\tconst threadReplies = new Map<string, Message[]>();\n\tconst threadsToFetch = messages.filter((m) => m.reply_count && m.reply_count > 0);\n\n\tconsole.error(`Fetching ${threadsToFetch.length} threads...`);\n\n\tfor (let i = 0; i < threadsToFetch.length; i++) {\n\t\tconst parent = threadsToFetch[i];\n\t\tconsole.error(` Thread ${i + 1}/${threadsToFetch.length} (${parent.reply_count} replies)...`);\n\n\t\tconst replies: Message[] = [];\n\t\tlet threadCursor: string | undefined;\n\n\t\tdo {\n\t\t\tconst response = await client.conversations.replies({\n\t\t\t\tchannel: channelId,\n\t\t\t\tts: parent.ts,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor: threadCursor,\n\t\t\t});\n\n\t\t\tif (response.messages) {\n\t\t\t\t// Skip the first message (it's the parent)\n\t\t\t\treplies.push(...(response.messages as Message[]).slice(1));\n\t\t\t}\n\n\t\t\tthreadCursor = response.response_metadata?.next_cursor;\n\t\t} while (threadCursor);\n\n\t\tthreadReplies.set(parent.ts, replies);\n\t}\n\n\t// Output messages with thread replies interleaved\n\tlet totalReplies = 0;\n\tfor (const msg of messages) {\n\t\t// Output the message\n\t\tconsole.log(formatMessage(msg.ts, msg.user || \"unknown\", msg.text || \"\"));\n\n\t\t// Output thread replies right after parent (indented)\n\t\tconst replies = threadReplies.get(msg.ts);\n\t\tif (replies) {\n\t\t\tfor (const reply of replies) {\n\t\t\t\tconsole.log(formatMessage(reply.ts, reply.user || \"unknown\", reply.text || \"\", \" \"));\n\t\t\t\ttotalReplies++;\n\t\t\t}\n\t\t}\n\t}\n\n\tconsole.error(`Done! ${messages.length} messages, ${totalReplies} thread replies`);\n}\n"]}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SlackBot } from "./adapters/slack/index.js";
|
|
2
|
+
export interface ImmediateEvent {
|
|
3
|
+
type: "immediate";
|
|
4
|
+
channelId: string;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OneShotEvent {
|
|
8
|
+
type: "one-shot";
|
|
9
|
+
channelId: string;
|
|
10
|
+
text: string;
|
|
11
|
+
at: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PeriodicEvent {
|
|
14
|
+
type: "periodic";
|
|
15
|
+
channelId: string;
|
|
16
|
+
text: string;
|
|
17
|
+
schedule: string;
|
|
18
|
+
timezone: string;
|
|
19
|
+
}
|
|
20
|
+
export type MamaEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;
|
|
21
|
+
export declare class EventsWatcher {
|
|
22
|
+
private eventsDir;
|
|
23
|
+
private slack;
|
|
24
|
+
private timers;
|
|
25
|
+
private crons;
|
|
26
|
+
private debounceTimers;
|
|
27
|
+
private startTime;
|
|
28
|
+
private watcher;
|
|
29
|
+
private knownFiles;
|
|
30
|
+
constructor(eventsDir: string, slack: SlackBot);
|
|
31
|
+
/**
|
|
32
|
+
* Start watching for events. Call this after SlackBot is ready.
|
|
33
|
+
*/
|
|
34
|
+
start(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Stop watching and cancel all scheduled events.
|
|
37
|
+
*/
|
|
38
|
+
stop(): void;
|
|
39
|
+
private debounce;
|
|
40
|
+
private scanExisting;
|
|
41
|
+
private handleFileChange;
|
|
42
|
+
private handleDelete;
|
|
43
|
+
private cancelScheduled;
|
|
44
|
+
private handleFile;
|
|
45
|
+
private parseEvent;
|
|
46
|
+
private handleImmediate;
|
|
47
|
+
private handleOneShot;
|
|
48
|
+
private handlePeriodic;
|
|
49
|
+
private execute;
|
|
50
|
+
private deleteFile;
|
|
51
|
+
private sleep;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create and start an events watcher.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createEventsWatcher(workspaceDir: string, slack: SlackBot): EventsWatcher;
|
|
57
|
+
//# sourceMappingURL=events.d.ts.map
|