mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 09:01:14 +00:00
mom: Thread-based tool details, improved README, fixed message ordering
This commit is contained in:
parent
f53e4fba42
commit
30964e0c33
3 changed files with 224 additions and 5 deletions
|
|
@ -1,3 +1,183 @@
|
|||
# @mariozechner/pi-mom
|
||||
|
||||
Slack bot that delegates channel messages to a pi coding agent instance.
|
||||
A Slack bot powered by Claude that can execute bash commands, read/write files, and interact with your development environment. Designed to be your helpful team assistant.
|
||||
|
||||
## Features
|
||||
|
||||
- **Slack Integration**: Responds to @mentions in channels and DMs
|
||||
- **Full Bash Access**: Execute any command, install tools, configure credentials
|
||||
- **File Operations**: Read, write, and edit files
|
||||
- **Docker Sandbox**: Optional isolation to protect your host machine
|
||||
- **Persistent Workspace**: Each channel gets its own workspace that persists across conversations
|
||||
- **Thread-Based Details**: Clean main messages with verbose tool details in threads
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @mariozechner/pi-mom
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export MOM_SLACK_APP_TOKEN=xapp-...
|
||||
export MOM_SLACK_BOT_TOKEN=xoxb-...
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
# or use your Claude Pro/Max subscription
|
||||
# to get the token install Claude Code and run claude setup-token
|
||||
export ANTHROPIC_OAUTH_TOKEN=sk-ant-...
|
||||
|
||||
# Run mom
|
||||
mom ./data
|
||||
```
|
||||
|
||||
## Slack App Setup
|
||||
|
||||
1. Create a new Slack app at https://api.slack.com/apps
|
||||
2. Enable **Socket Mode** (Settings → Socket Mode → Enable)
|
||||
3. Generate an **App-Level Token** with `connections:write` scope → this is `MOM_SLACK_APP_TOKEN`
|
||||
4. Add **Bot Token Scopes** (OAuth & Permissions):
|
||||
- `app_mentions:read`
|
||||
- `channels:history`
|
||||
- `channels:read`
|
||||
- `chat:write`
|
||||
- `files:read`
|
||||
- `files:write`
|
||||
- `im:history`
|
||||
- `im:read`
|
||||
- `im:write`
|
||||
- `users:read`
|
||||
5. **Subscribe to Bot Events** (Event Subscriptions):
|
||||
- `app_mention`
|
||||
- `message.channels`
|
||||
- `message.im`
|
||||
6. Install the app to your workspace → get the **Bot User OAuth Token** → this is `MOM_SLACK_BOT_TOKEN`
|
||||
|
||||
## Usage
|
||||
|
||||
### Host Mode (Default)
|
||||
|
||||
Run tools directly on your machine:
|
||||
|
||||
```bash
|
||||
mom ./data
|
||||
```
|
||||
|
||||
### Docker Sandbox Mode
|
||||
|
||||
Isolate mom in a container to protect your host:
|
||||
|
||||
```bash
|
||||
# Create the sandbox container
|
||||
./docker.sh create ./data
|
||||
|
||||
# Run mom with sandbox
|
||||
mom --sandbox=docker:mom-sandbox ./data
|
||||
```
|
||||
|
||||
### Talking to Mom
|
||||
|
||||
In Slack:
|
||||
```
|
||||
@mom what's in the current directory?
|
||||
@mom clone the repo https://github.com/example/repo and find all TODO comments
|
||||
@mom install htop and show me system stats
|
||||
```
|
||||
|
||||
Mom will:
|
||||
1. Show brief status updates in the main message
|
||||
2. Post detailed tool calls and results in a thread
|
||||
3. Provide a final response
|
||||
|
||||
### Stopping Mom
|
||||
|
||||
If mom is working on something and you need to stop:
|
||||
```
|
||||
@mom stop
|
||||
```
|
||||
|
||||
## CLI Options
|
||||
|
||||
```bash
|
||||
mom [options] <working-directory>
|
||||
|
||||
Options:
|
||||
--sandbox=host Run tools on host (default)
|
||||
--sandbox=docker:<name> Run tools in Docker container
|
||||
```
|
||||
|
||||
## Docker Sandbox
|
||||
|
||||
The Docker sandbox treats the container as mom's personal computer:
|
||||
|
||||
- **Persistent**: Install tools with `apk add`, configure credentials - changes persist
|
||||
- **Isolated**: Mom can only access `/workspace` (your data directory)
|
||||
- **Self-Managing**: Mom can install what she needs and ask for credentials
|
||||
|
||||
### Container Management
|
||||
|
||||
```bash
|
||||
./docker.sh create <data-dir> # Create and start container
|
||||
./docker.sh start # Start existing container
|
||||
./docker.sh stop # Stop container
|
||||
./docker.sh remove # Remove container
|
||||
./docker.sh status # Check if running
|
||||
./docker.sh shell # Open shell in container
|
||||
```
|
||||
|
||||
### Example Flow
|
||||
|
||||
```
|
||||
User: @mom check the spine-runtimes repo on GitHub
|
||||
Mom: I need gh CLI. Installing...
|
||||
(runs: apk add github-cli)
|
||||
Mom: I need a GitHub token. Please provide one.
|
||||
User: ghp_xxxx...
|
||||
Mom: (configures gh auth)
|
||||
Mom: Done. Here's the repo info...
|
||||
```
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
Each Slack channel gets its own workspace:
|
||||
|
||||
```
|
||||
./data/
|
||||
└── C123ABC/ # Channel ID
|
||||
├── log.jsonl # Message history (managed by mom)
|
||||
├── attachments/ # Files shared in channel
|
||||
└── scratch/ # Mom's working directory
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `MOM_SLACK_APP_TOKEN` | Slack app-level token (xapp-...) |
|
||||
| `MOM_SLACK_BOT_TOKEN` | Slack bot token (xoxb-...) |
|
||||
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
||||
| `ANTHROPIC_OAUTH_TOKEN` | Alternative: Anthropic OAuth token |
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Host Mode**: Mom has full access to your machine. Only use in trusted environments.
|
||||
|
||||
**Docker Mode**: Mom is isolated to the container. She can:
|
||||
- Read/write files in `/workspace` (your data dir)
|
||||
- Make network requests
|
||||
- Install packages in the container
|
||||
|
||||
She cannot:
|
||||
- Access files outside `/workspace`
|
||||
- Access your host credentials
|
||||
- Affect your host system
|
||||
|
||||
**Recommendations**:
|
||||
1. Use Docker mode for shared Slack workspaces
|
||||
2. Create a dedicated GitHub bot account with limited repo access
|
||||
3. Only share necessary credentials with mom
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
|
|
@ -160,6 +160,9 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
|
|||
}),
|
||||
});
|
||||
|
||||
// Track pending tool calls to pair args with results
|
||||
const pendingTools = new Map<string, { toolName: string; args: unknown }>();
|
||||
|
||||
// Subscribe to events
|
||||
agent.subscribe(async (event: AgentEvent) => {
|
||||
switch (event.type) {
|
||||
|
|
@ -167,6 +170,9 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
|
|||
const args = event.args as { label?: string };
|
||||
const label = args.label || event.toolName;
|
||||
|
||||
// Store args to pair with result later
|
||||
pendingTools.set(event.toolCallId, { toolName: event.toolName, args: event.args });
|
||||
|
||||
// Log to console
|
||||
console.log(`\n[Tool] ${event.toolName}: ${JSON.stringify(event.args)}`);
|
||||
|
||||
|
|
@ -179,13 +185,15 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
|
|||
isBot: true,
|
||||
});
|
||||
|
||||
// Show only label to user (italic)
|
||||
// Show label in main message only
|
||||
await ctx.respond(`_${label}_`);
|
||||
break;
|
||||
}
|
||||
|
||||
case "tool_execution_end": {
|
||||
const resultStr = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
|
||||
const pending = pendingTools.get(event.toolCallId);
|
||||
pendingTools.delete(event.toolCallId);
|
||||
|
||||
// Log to console
|
||||
console.log(`[Tool Result] ${event.isError ? "ERROR: " : ""}${truncate(resultStr, 1000)}\n`);
|
||||
|
|
@ -199,7 +207,20 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {
|
|||
isBot: true,
|
||||
});
|
||||
|
||||
// Show brief status to user (only on error)
|
||||
// Post args + result together in thread
|
||||
const argsStr = pending ? JSON.stringify(pending.args, null, 2) : "(args not found)";
|
||||
const threadResult = truncate(resultStr, 2000);
|
||||
await ctx.respondInThread(
|
||||
`*[${event.toolName}]* ${event.isError ? "❌" : "✓"}\n` +
|
||||
"```\n" +
|
||||
argsStr +
|
||||
"\n```\n" +
|
||||
"*Result:*\n```\n" +
|
||||
threadResult +
|
||||
"\n```",
|
||||
);
|
||||
|
||||
// Show brief error in main message if failed
|
||||
if (event.isError) {
|
||||
await ctx.respond(`_Error: ${truncate(resultStr, 200)}_`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ export interface SlackMessage {
|
|||
export interface SlackContext {
|
||||
message: SlackMessage;
|
||||
store: ChannelStore;
|
||||
/** Send a new message */
|
||||
/** Send/update the main message (accumulates text) */
|
||||
respond(text: string): Promise<void>;
|
||||
/** Show/hide typing indicator. If text is provided to respond() after setTyping(true), it updates the typing message instead of posting new. */
|
||||
/** Post a message in the thread under the main message (for verbose details) */
|
||||
respondInThread(text: string): Promise<void>;
|
||||
/** Show/hide typing indicator */
|
||||
setTyping(isTyping: boolean): Promise<void>;
|
||||
/** Upload a file to the channel */
|
||||
uploadFile(filePath: string, title?: string): Promise<void>;
|
||||
|
|
@ -227,6 +229,22 @@ export class MomBot {
|
|||
|
||||
await updatePromise;
|
||||
},
|
||||
respondInThread: async (threadText: string) => {
|
||||
// Queue thread posts to maintain order
|
||||
updatePromise = updatePromise.then(async () => {
|
||||
if (!messageTs) {
|
||||
// No main message yet, just skip
|
||||
return;
|
||||
}
|
||||
// Post in thread under the main message
|
||||
await this.webClient.chat.postMessage({
|
||||
channel: event.channel,
|
||||
thread_ts: messageTs,
|
||||
text: threadText,
|
||||
});
|
||||
});
|
||||
await updatePromise;
|
||||
},
|
||||
setTyping: async (isTyping: boolean) => {
|
||||
if (isTyping && !messageTs) {
|
||||
// Post initial "thinking" message
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue