thepopebot 1.2.76-beta.2 → 1.2.76-beta.4
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/bin/cli.js +2 -2
- package/lib/ai/index.js +24 -16
- package/lib/ai/sdk-adapters/CLAUDE.md +113 -0
- package/lib/ai/sdk-adapters/claude-code.js +53 -0
- package/lib/ai/system-prompt.js +16 -0
- package/lib/ai/tools.js +3 -0
- package/lib/code/actions.js +6 -0
- package/lib/tools/docker.js +21 -8
- package/lib/utils/render-md.js +1 -1
- package/package.json +2 -2
- package/templates/.gitignore.template +1 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/CLAUDE.md.template +2 -1
- package/templates/agent-job/CLAUDE.md.template +1 -2
- package/templates/agent-job/SYSTEM.md +2 -2
- package/templates/coding-workspace/CLAUDE.md.template +7 -0
- package/templates/coding-workspace/SYSTEM.md +0 -0
- package/templates/agent-job/SOUL.md +0 -17
package/bin/cli.js
CHANGED
|
@@ -420,7 +420,7 @@ function reset(filePath) {
|
|
|
420
420
|
console.log(` ${destPath(file)}`);
|
|
421
421
|
}
|
|
422
422
|
console.log('\nUsage: thepopebot reset <file>');
|
|
423
|
-
console.log('Example: thepopebot reset agent-job/
|
|
423
|
+
console.log('Example: thepopebot reset agent-job/SYSTEM.md\n');
|
|
424
424
|
return;
|
|
425
425
|
}
|
|
426
426
|
|
|
@@ -502,7 +502,7 @@ function diff(filePath) {
|
|
|
502
502
|
console.log(' All files match package templates.');
|
|
503
503
|
}
|
|
504
504
|
console.log('\nUsage: thepopebot diff <file>');
|
|
505
|
-
console.log('Example: thepopebot diff agent-job/
|
|
505
|
+
console.log('Example: thepopebot diff agent-job/SYSTEM.md\n');
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
508
|
|
package/lib/ai/index.js
CHANGED
|
@@ -4,8 +4,10 @@ import { z } from 'zod';
|
|
|
4
4
|
import { getAgentChat, getCodeChat } from './agent.js';
|
|
5
5
|
import { createModel } from './model.js';
|
|
6
6
|
import path from 'path';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
7
8
|
import { PROJECT_ROOT } from '../paths.js';
|
|
8
9
|
import { render_md } from '../utils/render-md.js';
|
|
10
|
+
import { buildCodingAgentSystemPrompt } from './system-prompt.js';
|
|
9
11
|
import { getChatById, createChat, saveMessage, updateChatTitle, linkChatToWorkspace } from '../db/chats.js';
|
|
10
12
|
import { getConfig } from '../config.js';
|
|
11
13
|
import { getSdkAdapter } from './sdk-adapters/index.js';
|
|
@@ -171,35 +173,41 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
|
|
|
171
173
|
const workspace = getCodeWorkspaceById(workspaceId);
|
|
172
174
|
const featureBranch = workspace?.featureBranch;
|
|
173
175
|
|
|
176
|
+
const needsSetup = !existsSync(path.join(repoDir, '.git'));
|
|
174
177
|
const setupToolCallId = `setup-${workspaceId.slice(0, 8)}`;
|
|
175
178
|
const setupArgs = { repo, branch, featureBranch };
|
|
176
|
-
|
|
179
|
+
|
|
180
|
+
if (needsSetup) {
|
|
181
|
+
yield { type: 'tool-call', toolCallId: setupToolCallId, toolName: 'set_up_workspace', args: setupArgs };
|
|
182
|
+
}
|
|
177
183
|
|
|
178
184
|
try {
|
|
179
185
|
await ensureWorkspaceRepo({ workspaceDir: repoDir, repo, branch, featureBranch });
|
|
180
186
|
ensureSkills(repoDir, isCodeMode ? 'code' : 'agent');
|
|
181
|
-
|
|
187
|
+
if (needsSetup) {
|
|
188
|
+
const result = JSON.stringify({ result: `Workspace ready on ${featureBranch || branch}` });
|
|
189
|
+
yield { type: 'tool-result', toolCallId: setupToolCallId, result };
|
|
190
|
+
persistMessage(threadId, 'assistant', JSON.stringify({
|
|
191
|
+
type: 'tool-invocation',
|
|
192
|
+
toolCallId: setupToolCallId,
|
|
193
|
+
toolName: 'set_up_workspace',
|
|
194
|
+
state: 'output-available',
|
|
195
|
+
input: setupArgs,
|
|
196
|
+
output: result,
|
|
197
|
+
}), options);
|
|
198
|
+
}
|
|
182
199
|
} catch (err) {
|
|
183
|
-
|
|
200
|
+
if (needsSetup) {
|
|
201
|
+
yield { type: 'tool-result', toolCallId: setupToolCallId, result: JSON.stringify({ result: `Setup failed: ${err.message}` }) };
|
|
202
|
+
}
|
|
184
203
|
throw err;
|
|
185
204
|
}
|
|
186
205
|
|
|
187
206
|
// 2. Session continuity
|
|
188
207
|
const sessionId = readSessionId(wsBaseDir);
|
|
189
208
|
|
|
190
|
-
// 3. System prompt
|
|
191
|
-
|
|
192
|
-
if (!isCodeMode) {
|
|
193
|
-
const soulPath = path.join(PROJECT_ROOT, 'agent-job/SOUL.md');
|
|
194
|
-
const systemPath = path.join(PROJECT_ROOT, 'agent-job/SYSTEM.md');
|
|
195
|
-
try {
|
|
196
|
-
const soul = render_md(soulPath) || '';
|
|
197
|
-
const system = render_md(systemPath) || '';
|
|
198
|
-
systemPrompt = [soul, system].filter(Boolean).join('\n\n') || null;
|
|
199
|
-
} catch {
|
|
200
|
-
// Files may not exist — proceed without system prompt
|
|
201
|
-
}
|
|
202
|
-
}
|
|
209
|
+
// 3. System prompt
|
|
210
|
+
const systemPrompt = buildCodingAgentSystemPrompt(isCodeMode ? 'code' : 'agent');
|
|
203
211
|
|
|
204
212
|
// 4. Stream from SDK adapter
|
|
205
213
|
let pendingText = '';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# lib/ai/sdk-adapters/ — SDK Adapter System
|
|
2
|
+
|
|
3
|
+
In-process SDK adapters that replace the legacy LangGraph + Docker path for chat. Each adapter wraps a coding agent's SDK and yields a unified chunk stream consumed by `chatStream()` in `lib/ai/index.js`.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Browser → POST /stream/chat (api.js)
|
|
9
|
+
→ chatStream() (index.js)
|
|
10
|
+
→ getSdkAdapter() returns adapter function or null
|
|
11
|
+
→ if adapter: workspace setup → SDK adapter streaming → DB persistence
|
|
12
|
+
→ if null: falls back to legacy LangGraph/Docker path
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The adapter is a pure stream translator — it receives a prompt and options, calls the SDK, and yields normalized chunks. Everything else (workspace setup, DB persistence, session continuity, system prompts) is handled by `chatStream()` in `index.js`.
|
|
16
|
+
|
|
17
|
+
## Existing Adapter
|
|
18
|
+
|
|
19
|
+
| File | Agent | SDK |
|
|
20
|
+
|------|-------|-----|
|
|
21
|
+
| `claude-code.js` | `claude-code` | `@anthropic-ai/claude-agent-sdk` |
|
|
22
|
+
|
|
23
|
+
## Adding a New SDK Adapter
|
|
24
|
+
|
|
25
|
+
### 1. Create the adapter file
|
|
26
|
+
|
|
27
|
+
Create `{agent-name}.js` in this directory. Export a single async generator function:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
export async function* myAgentStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments }) {
|
|
31
|
+
// ... call the SDK, yield chunks
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Required chunk types to yield
|
|
36
|
+
|
|
37
|
+
The adapter MUST yield these chunk types for `chatStream()` and `api.js` to work correctly:
|
|
38
|
+
|
|
39
|
+
| Chunk | Shape | When | Purpose |
|
|
40
|
+
|-------|-------|------|---------|
|
|
41
|
+
| `meta` | `{ type: 'meta', sessionId: string }` | First event | Session ID for continuity across messages. `chatStream()` writes this to disk via `writeSessionId()` so subsequent messages resume the session. |
|
|
42
|
+
| `text` | `{ type: 'text', text: string }` | Text output | Streamed to UI as deltas. Accumulated by `chatStream()` and flushed to DB as assistant messages at tool boundaries and stream end. |
|
|
43
|
+
| `tool-call` | `{ type: 'tool-call', toolCallId: string, toolName: string, args: object }` | Tool invocation starts | Triggers tool UI in the browser. May be yielded twice: once at start with `args: {}`, once at `content_block_stop` with complete args. `chatStream()` tracks these in `pendingToolCalls` for pairing with results. |
|
|
44
|
+
| `tool-result` | `{ type: 'tool-result', toolCallId: string, result: string }` | Tool completes | Paired with the matching `tool-call` by `toolCallId`. `chatStream()` persists the pair as a `tool-invocation` JSON message in the DB. |
|
|
45
|
+
| `result` | `{ type: 'result', text: string, cost?: number, duration?: number, subtype?: string }` | Stream ends | Final summary. Logged by `chatStream()`, not persisted or sent to UI. |
|
|
46
|
+
|
|
47
|
+
Optional:
|
|
48
|
+
| `unknown` | `{ type: 'unknown', raw: any }` | Unrecognized events | `api.js` renders these as collapsible boxes in the UI. Use for debugging unhandled SDK events. |
|
|
49
|
+
|
|
50
|
+
### 3. Register in index.js
|
|
51
|
+
|
|
52
|
+
Add the import and mapping in `getSdkAdapter()`:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { myAgentStream } from './my-agent.js';
|
|
56
|
+
|
|
57
|
+
export function getSdkAdapter(agentType) {
|
|
58
|
+
if (agentType === 'claude-code') return claudeCodeStream;
|
|
59
|
+
if (agentType === 'my-agent') return myAgentStream;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `agentType` string comes from the `CODING_AGENT` config value set in the admin UI.
|
|
65
|
+
|
|
66
|
+
### 4. Auth resolution
|
|
67
|
+
|
|
68
|
+
Use `buildAgentAuthEnv(agentType)` from `lib/tools/docker.js` to get credentials from the settings DB. This returns `{ env: string[], backendApi: string }` where `env` is an array of `KEY=value` strings. Parse them into an env object:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { buildAgentAuthEnv } from '../../tools/docker.js';
|
|
72
|
+
|
|
73
|
+
const env = { ...process.env };
|
|
74
|
+
const { env: authEnvPairs } = buildAgentAuthEnv('my-agent');
|
|
75
|
+
for (const pair of authEnvPairs) {
|
|
76
|
+
const eqIdx = pair.indexOf('=');
|
|
77
|
+
if (eqIdx > 0) env[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The agent's auth config (API keys, OAuth tokens, provider selection) is managed in the admin UI at `/admin/event-handler/coding-agents` and stored in the settings DB. `buildAgentAuthEnv()` reads it — you don't need to access the settings DB directly.
|
|
82
|
+
|
|
83
|
+
### 5. Function parameters
|
|
84
|
+
|
|
85
|
+
| Param | Type | Description |
|
|
86
|
+
|-------|------|-------------|
|
|
87
|
+
| `prompt` | `string` | User message text |
|
|
88
|
+
| `workspaceDir` | `string` | Absolute path to git repo root (the SDK should execute here) |
|
|
89
|
+
| `systemPrompt` | `string\|null` | System prompt for agent mode (null in code mode) |
|
|
90
|
+
| `sessionId` | `string\|null` | Previous session ID to resume (null on first message) |
|
|
91
|
+
| `permissionMode` | `string` | `'plan'` (read-only) or `'code'` (read-write). Map to the SDK's equivalent permission concept. |
|
|
92
|
+
| `attachments` | `Array` | Image attachments: `{ category: 'image', mimeType, dataUrl }` |
|
|
93
|
+
|
|
94
|
+
### 6. Session continuity contract
|
|
95
|
+
|
|
96
|
+
Multi-turn conversation works via session IDs:
|
|
97
|
+
|
|
98
|
+
1. First message: `sessionId` param is `null`. Adapter yields `{ type: 'meta', sessionId: '<new-id>' }`.
|
|
99
|
+
2. `chatStream()` writes the session ID to `{workspaceBaseDir}/.claude-ttyd-sessions/7681`.
|
|
100
|
+
3. Next message: `sessionId` param contains the saved ID. Adapter passes it to the SDK's resume mechanism.
|
|
101
|
+
|
|
102
|
+
If the SDK doesn't support session resume, the adapter can ignore `sessionId` — but multi-turn context will be lost between messages.
|
|
103
|
+
|
|
104
|
+
## What the adapter does NOT handle
|
|
105
|
+
|
|
106
|
+
These are managed by `chatStream()` in `index.js` — adapters should not duplicate them:
|
|
107
|
+
|
|
108
|
+
- **Workspace git setup** — `ensureWorkspaceRepo()` clones/checkouts before the adapter is called
|
|
109
|
+
- **DB persistence** — `chatStream()` saves user messages, assistant text, and tool invocations
|
|
110
|
+
- **Chat creation** — `chatStream()` creates the chat and workspace DB records
|
|
111
|
+
- **Auto-titling** — `chatStream()` generates a title after the first message
|
|
112
|
+
- **System prompt loading** — `chatStream()` calls `buildCodingAgentSystemPrompt()` and passes the result as `systemPrompt`
|
|
113
|
+
- **Skill activation** — `ensureSkills()` runs before the adapter is called
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
1
3
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
4
|
import { getConfig } from '../../config.js';
|
|
3
5
|
import { buildAgentAuthEnv } from '../../tools/docker.js';
|
|
@@ -15,10 +17,61 @@ import { buildAgentAuthEnv } from '../../tools/docker.js';
|
|
|
15
17
|
* @param {Array} [opts.attachments] - Image attachments
|
|
16
18
|
* @yields {{ type: 'text'|'tool-call'|'tool-result'|'meta'|'result'|'unknown', ... }}
|
|
17
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Encode an absolute path the same way Claude Code encodes cwd for session storage.
|
|
22
|
+
* Non-alphanumeric characters are replaced with '-'.
|
|
23
|
+
*/
|
|
24
|
+
function encodeCwd(absolutePath) {
|
|
25
|
+
return absolutePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Ensure the interactive container can find SDK-created sessions on the shared volume.
|
|
30
|
+
*
|
|
31
|
+
* The SDK stores sessions at $HOME/.claude/projects/<encoded-cwd>/. The interactive
|
|
32
|
+
* container uses cwd=/home/coding-agent/workspace, but the SDK adapter uses
|
|
33
|
+
* cwd=/app/data/workspaces/workspace-XXX/workspace — different encoded paths.
|
|
34
|
+
*
|
|
35
|
+
* Creates a symlink so the interactive container's encoded path resolves to the
|
|
36
|
+
* SDK adapter's encoded path, both on the same volume.
|
|
37
|
+
*/
|
|
38
|
+
function ensureSessionSymlink(wsBaseDir, workspaceDir) {
|
|
39
|
+
const projectsDir = path.join(wsBaseDir, '.claude', 'projects');
|
|
40
|
+
const sdkEncoded = encodeCwd(workspaceDir);
|
|
41
|
+
const interactiveEncoded = encodeCwd('/home/coding-agent/workspace');
|
|
42
|
+
|
|
43
|
+
// Both point to the same dir — no symlink needed
|
|
44
|
+
if (sdkEncoded === interactiveEncoded) return;
|
|
45
|
+
|
|
46
|
+
fs.mkdirSync(path.join(projectsDir, sdkEncoded), { recursive: true });
|
|
47
|
+
|
|
48
|
+
const symlinkPath = path.join(projectsDir, interactiveEncoded);
|
|
49
|
+
try {
|
|
50
|
+
const existing = fs.readlinkSync(symlinkPath);
|
|
51
|
+
if (existing === sdkEncoded) return; // already correct
|
|
52
|
+
fs.unlinkSync(symlinkPath);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err.code !== 'ENOENT') {
|
|
55
|
+
// It's a real directory (not a symlink) — don't touch it
|
|
56
|
+
if (err.code === 'EINVAL') return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
fs.symlinkSync(sdkEncoded, symlinkPath);
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
|
|
18
65
|
export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments }) {
|
|
66
|
+
// Point HOME at the workspace volume so the SDK stores session data on the
|
|
67
|
+
// shared volume (not the EH container's ephemeral filesystem).
|
|
68
|
+
const wsBaseDir = path.dirname(workspaceDir);
|
|
69
|
+
ensureSessionSymlink(wsBaseDir, workspaceDir);
|
|
70
|
+
|
|
19
71
|
// Build a local env object with auth credentials from the settings DB.
|
|
20
72
|
// Passed via the SDK's `env` option — no process.env mutation needed.
|
|
21
73
|
const env = { ...process.env };
|
|
74
|
+
env.HOME = wsBaseDir;
|
|
22
75
|
try {
|
|
23
76
|
const { env: authEnvPairs } = buildAgentAuthEnv('claude-code');
|
|
24
77
|
for (const pair of authEnvPairs) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { PROJECT_ROOT } from '../paths.js';
|
|
3
|
+
import { render_md } from '../utils/render-md.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build the system prompt for a coding agent.
|
|
7
|
+
* @param {'agent'|'code'} mode - Chat mode
|
|
8
|
+
* @returns {string|null} Rendered system prompt, or null if not configured
|
|
9
|
+
*/
|
|
10
|
+
export function buildCodingAgentSystemPrompt(mode) {
|
|
11
|
+
const file = mode === 'agent'
|
|
12
|
+
? path.join(PROJECT_ROOT, 'agent-job/SYSTEM.md')
|
|
13
|
+
: path.join(PROJECT_ROOT, 'coding-workspace/SYSTEM.md');
|
|
14
|
+
const rendered = render_md(file);
|
|
15
|
+
return rendered?.trim() || null;
|
|
16
|
+
}
|
package/lib/ai/tools.js
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { createAgentJob } from '../tools/create-agent-job.js';
|
|
4
4
|
|
|
5
5
|
import { getConfig } from '../config.js';
|
|
6
|
+
import { buildCodingAgentSystemPrompt } from './system-prompt.js';
|
|
6
7
|
|
|
7
8
|
const agentJobTool = tool(
|
|
8
9
|
async ({ prompt }) => {
|
|
@@ -63,6 +64,7 @@ const agentChatCodingTool = tool(
|
|
|
63
64
|
workspaceId,
|
|
64
65
|
taskPrompt: prompt,
|
|
65
66
|
mode,
|
|
67
|
+
systemPrompt: buildCodingAgentSystemPrompt('agent'),
|
|
66
68
|
injectSecrets: true,
|
|
67
69
|
});
|
|
68
70
|
|
|
@@ -124,6 +126,7 @@ const codeChatCodingTool = tool(
|
|
|
124
126
|
containerName, repo, branch, featureBranch, workspaceId,
|
|
125
127
|
taskPrompt: prompt,
|
|
126
128
|
mode,
|
|
129
|
+
systemPrompt: buildCodingAgentSystemPrompt('code'),
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
const chunks = [{ type: 'meta', codingAgent, backendApi }];
|
package/lib/code/actions.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
getChatByWorkspaceId,
|
|
16
16
|
} from '../db/chats.js';
|
|
17
|
+
import { buildCodingAgentSystemPrompt } from '../ai/system-prompt.js';
|
|
17
18
|
import {
|
|
18
19
|
addSession,
|
|
19
20
|
getSession as getTermSession,
|
|
@@ -137,6 +138,7 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
137
138
|
// Inject agent job secrets when the linked chat is in agent mode
|
|
138
139
|
const chat = getChatByWorkspaceId(id);
|
|
139
140
|
const injectSecrets = chat?.chatMode === 'agent';
|
|
141
|
+
const systemPrompt = buildCodingAgentSystemPrompt(chat?.chatMode === 'agent' ? 'agent' : 'code');
|
|
140
142
|
|
|
141
143
|
try {
|
|
142
144
|
const { inspectContainer, startContainer, removeContainer, runInteractiveContainer } =
|
|
@@ -153,6 +155,7 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
153
155
|
featureBranch: workspace.featureBranch,
|
|
154
156
|
workspaceId: id,
|
|
155
157
|
injectSecrets,
|
|
158
|
+
systemPrompt,
|
|
156
159
|
});
|
|
157
160
|
return { status: 'created' };
|
|
158
161
|
}
|
|
@@ -181,6 +184,7 @@ export async function ensureCodeWorkspaceContainer(id) {
|
|
|
181
184
|
featureBranch: workspace.featureBranch,
|
|
182
185
|
workspaceId: id,
|
|
183
186
|
injectSecrets,
|
|
187
|
+
systemPrompt,
|
|
184
188
|
});
|
|
185
189
|
return { status: 'created' };
|
|
186
190
|
} catch (err) {
|
|
@@ -208,6 +212,7 @@ export async function startInteractiveMode(id) {
|
|
|
208
212
|
// Inject agent job secrets when the linked chat is in agent mode
|
|
209
213
|
const chat = getChatByWorkspaceId(id);
|
|
210
214
|
const injectSecrets = chat?.chatMode === 'agent';
|
|
215
|
+
const systemPrompt = buildCodingAgentSystemPrompt(chat?.chatMode === 'agent' ? 'agent' : 'code');
|
|
211
216
|
|
|
212
217
|
try {
|
|
213
218
|
const { getConfig } = await import('../config.js');
|
|
@@ -223,6 +228,7 @@ export async function startInteractiveMode(id) {
|
|
|
223
228
|
featureBranch: workspace.featureBranch,
|
|
224
229
|
workspaceId: id,
|
|
225
230
|
injectSecrets,
|
|
231
|
+
systemPrompt,
|
|
226
232
|
});
|
|
227
233
|
|
|
228
234
|
updateContainerName(id, containerName);
|
package/lib/tools/docker.js
CHANGED
|
@@ -196,9 +196,10 @@ async function runContainer({ containerName, image, env = [], workingDir, hostCo
|
|
|
196
196
|
* @param {string} [options.featureBranch] - Feature branch to create after cloning
|
|
197
197
|
* @param {string} [options.workspaceId] - Workspace ID (for bind mount)
|
|
198
198
|
* @param {boolean} [options.injectSecrets] - Inject agent job secrets into container env
|
|
199
|
+
* @param {string} [options.systemPrompt] - Pre-rendered system prompt (written to volume as SYSTEM.md)
|
|
199
200
|
* @returns {Promise<{containerId: string, containerName: string}>}
|
|
200
201
|
*/
|
|
201
|
-
async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, continueSession = true }) {
|
|
202
|
+
async function runInteractiveContainer({ containerName, repo, branch, codingAgent, featureBranch, workspaceId, injectSecrets, systemPrompt, continueSession = true }) {
|
|
202
203
|
const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
|
|
203
204
|
const version = process.env.THEPOPEBOT_VERSION;
|
|
204
205
|
const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
|
|
@@ -246,8 +247,16 @@ async function runInteractiveContainer({ containerName, repo, branch, codingAgen
|
|
|
246
247
|
const dir = workspaceDir(workspaceId);
|
|
247
248
|
const wsDir = path.join(dir, 'workspace');
|
|
248
249
|
fs.mkdirSync(wsDir, { recursive: true });
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
|
|
251
|
+
// Write pre-rendered system prompt to volume (read by container setup scripts)
|
|
252
|
+
const systemPromptPath = path.join(dir, 'SYSTEM.md');
|
|
253
|
+
if (systemPrompt) {
|
|
254
|
+
fs.writeFileSync(systemPromptPath, systemPrompt, 'utf8');
|
|
255
|
+
} else {
|
|
256
|
+
// Remove stale prompt if no system prompt for this mode
|
|
257
|
+
try { fs.unlinkSync(systemPromptPath); } catch {}
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
const hostDir = await resolveHostPath(dir);
|
|
252
261
|
hostConfig.Binds = [`${hostDir}:/home/coding-agent`];
|
|
253
262
|
}
|
|
@@ -418,9 +427,6 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
|
|
|
418
427
|
const permission = mode === 'dangerous' ? 'code' : mode;
|
|
419
428
|
env.push(`PERMISSION=${permission}`);
|
|
420
429
|
}
|
|
421
|
-
if (systemPrompt) {
|
|
422
|
-
env.push(`SYSTEM_PROMPT=${systemPrompt}`);
|
|
423
|
-
}
|
|
424
430
|
if (continueSession) {
|
|
425
431
|
env.push(`CONTINUE_SESSION=1`);
|
|
426
432
|
}
|
|
@@ -456,8 +462,15 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
|
|
|
456
462
|
const dir = workspaceDir(workspaceId);
|
|
457
463
|
const wsDir = path.join(dir, 'workspace');
|
|
458
464
|
fs.mkdirSync(wsDir, { recursive: true });
|
|
459
|
-
|
|
460
|
-
|
|
465
|
+
|
|
466
|
+
// Write pre-rendered system prompt to volume (read by container setup scripts)
|
|
467
|
+
const systemPromptPath = path.join(dir, 'SYSTEM.md');
|
|
468
|
+
if (systemPrompt) {
|
|
469
|
+
fs.writeFileSync(systemPromptPath, systemPrompt, 'utf8');
|
|
470
|
+
} else {
|
|
471
|
+
try { fs.unlinkSync(systemPromptPath); } catch {}
|
|
472
|
+
}
|
|
473
|
+
|
|
461
474
|
const hostDir = await resolveHostPath(dir);
|
|
462
475
|
hostConfig.Binds = [`${hostDir}:/home/coding-agent`];
|
|
463
476
|
}
|
package/lib/utils/render-md.js
CHANGED
|
@@ -32,7 +32,7 @@ function loadSkillDescriptions() {
|
|
|
32
32
|
const frontmatter = frontmatterMatch[1];
|
|
33
33
|
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
34
34
|
if (descMatch) {
|
|
35
|
-
descriptions.push(`- ${descMatch[1].trim()}`);
|
|
35
|
+
descriptions.push(`- **${entry.name}**: ${descMatch[1].trim()}`);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thepopebot",
|
|
3
|
-
"version": "1.2.76-beta.
|
|
3
|
+
"version": "1.2.76-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
|
|
6
6
|
"bin": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"license": "MIT",
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@ai-sdk/react": "^2.0.0",
|
|
71
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
71
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.101",
|
|
72
72
|
"@clack/prompts": "^0.10.0",
|
|
73
73
|
"@dnd-kit/core": "^6.3.1",
|
|
74
74
|
"@dnd-kit/modifiers": "^9.0.0",
|
package/templates/CLAUDE.md
CHANGED
|
@@ -11,7 +11,7 @@ This directory contains files that get copied into user projects when they run `
|
|
|
11
11
|
|
|
12
12
|
## What belongs here
|
|
13
13
|
|
|
14
|
-
- **Agent job config**: `agent-job/
|
|
14
|
+
- **Agent job config**: `agent-job/SYSTEM.md`, `agent-job/CRONS.json`, etc.
|
|
15
15
|
- **Event handler config**: `event-handler/agent-chat/SYSTEM.md`, `event-handler/TRIGGERS.json`, etc.
|
|
16
16
|
- **GitHub Actions workflows**: `.github/workflows/`
|
|
17
17
|
- **Docker compose**: `docker-compose.yml`
|
|
@@ -4,7 +4,8 @@ This is a [thepopebot](https://github.com/stephengpope/thepopebot) project.
|
|
|
4
4
|
|
|
5
5
|
## Directories
|
|
6
6
|
|
|
7
|
-
- **`agent-job/`** — Agent job configuration: system
|
|
7
|
+
- **`agent-job/`** — Agent job configuration: system prompt (`SYSTEM.md`), heartbeat prompt, and cron schedules (`CRONS.json`).
|
|
8
|
+
- **`coding-workspace/`** — Optional system prompt (`SYSTEM.md`) for code mode workspaces. Empty by default.
|
|
8
9
|
- **`agents/`** — Custom agent definitions. Each subdirectory defines an agent (see Managing Agents below).
|
|
9
10
|
- **`event-handler/`** — Event handler configuration: chat system prompts, trigger definitions (`TRIGGERS.json`), cluster templates, and LiteLLM proxy config.
|
|
10
11
|
- **`skills/library/`** — Skill plugins. Activate by symlinking into `skills/active/`.
|
|
@@ -4,8 +4,7 @@ This directory contains your agent job configuration files — system prompts, s
|
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
7
|
-
- **`
|
|
8
|
-
- **`SYSTEM.md`** — Runtime environment documentation injected into the agent's context.
|
|
7
|
+
- **`SYSTEM.md`** — Agent system prompt: identity, runtime environment, and instructions. Rendered with full template support (`{{skills}}`, `{{datetime}}`, file includes).
|
|
9
8
|
- **`HEARTBEAT.md`** — Prompt for the agent's periodic heartbeat cron job.
|
|
10
9
|
- **`CRONS.json`** — Scheduled job definitions, loaded at server startup.
|
|
11
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Job Environment
|
|
2
2
|
|
|
3
|
-
You are an autonomous AI agent running inside a Docker container on thepopebot.
|
|
3
|
+
You are an autonomous AI agent running inside a Docker container on thepopebot. You approach tasks methodically — plan before acting, favor simplicity, and prioritize quality over speed.
|
|
4
4
|
|
|
5
5
|
## Runtime Environment
|
|
6
6
|
|
|
@@ -16,7 +16,7 @@ Everything in the workspace is automatically committed and pushed when your job
|
|
|
16
16
|
## Directory Layout
|
|
17
17
|
|
|
18
18
|
- `agents/` — Agent definitions. Each subdirectory defines an agent with its own prompts.
|
|
19
|
-
- `agent-job/` — Runtime config: system
|
|
19
|
+
- `agent-job/` — Runtime config: system prompt (`SYSTEM.md`), cron schedules (`CRONS.json`), heartbeat prompt.
|
|
20
20
|
- `event-handler/` — Event handler config. Do not edit — managed by the event handler.
|
|
21
21
|
- `skills/library/` — Skill plugins. Active skills are symlinked into `skills/active/`.
|
|
22
22
|
- `data/`, `logs/` — Runtime data and job logs.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# coding-workspace/ — Code Mode System Prompt
|
|
2
|
+
|
|
3
|
+
Optional system prompt for code mode workspaces. Edit `SYSTEM.md` to customize the coding agent's behavior when working on your repos.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- **`SYSTEM.md`** — System prompt for code mode. Empty by default — the coding agent uses its built-in prompt. Add content here to append custom instructions. Supports `{{skills}}`, `{{datetime}}`, and `{{ file.md }}` includes.
|
|
File without changes
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Agent Job Soul
|
|
2
|
-
|
|
3
|
-
## Identity
|
|
4
|
-
|
|
5
|
-
You are a diligent and capable AI worker doing an job. You approach tasks with focus, patience, and craftsmanship.
|
|
6
|
-
|
|
7
|
-
## Personality Traits
|
|
8
|
-
|
|
9
|
-
- **Methodical**: You work through problems systematically, step by step
|
|
10
|
-
- **Reliable**: You follow through on commitments and complete what I start
|
|
11
|
-
- **Curious**: You explore and learn from the codebase I work with
|
|
12
|
-
- **Working Style**: You plan before acting
|
|
13
|
-
|
|
14
|
-
## Values
|
|
15
|
-
|
|
16
|
-
- **Quality over speed**: Better to do it right than do it twice
|
|
17
|
-
- **Simplicity**: The simplest solution that works is usually best
|