thepopebot 1.2.75-beta.1 → 1.2.75-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/bin/CLAUDE.md +1 -1
- package/bin/cli.js +37 -8
- package/bin/docker-build.js +5 -0
- package/bin/managed-paths.js +2 -5
- package/bin/sync.js +84 -0
- package/config/CLAUDE.md +1 -29
- package/config/instrumentation.js +1 -1
- package/lib/CLAUDE.md +3 -3
- package/lib/ai/CLAUDE.md +24 -3
- package/lib/ai/agent.js +8 -5
- package/lib/ai/async-channel.js +51 -0
- package/lib/ai/headless-stream.js +3 -0
- package/lib/ai/index.js +148 -172
- package/lib/ai/line-mappers.js +72 -9
- package/lib/ai/tools.js +40 -28
- package/lib/chat/actions.js +34 -6
- package/lib/chat/components/chat-header.js +4 -0
- package/lib/chat/components/chat-header.jsx +4 -0
- package/lib/chat/components/crons-page.js +1 -1
- package/lib/chat/components/crons-page.jsx +1 -1
- package/lib/chat/components/message.js +3 -2
- package/lib/chat/components/message.jsx +3 -2
- package/lib/chat/components/settings-chat-page.js +2 -1
- package/lib/chat/components/settings-chat-page.jsx +4 -1
- package/lib/chat/components/settings-coding-agents-page.js +139 -1
- package/lib/chat/components/settings-coding-agents-page.jsx +160 -0
- package/lib/chat/components/settings-jobs-page.js +13 -2
- package/lib/chat/components/settings-jobs-page.jsx +15 -1
- package/lib/chat/components/settings-secrets-layout.js +1 -1
- package/lib/chat/components/settings-secrets-layout.jsx +1 -1
- package/lib/chat/components/triggers-page.js +1 -1
- package/lib/chat/components/triggers-page.jsx +1 -1
- package/lib/cluster/actions.js +4 -4
- package/lib/cluster/execute.js +3 -1
- package/lib/code/actions.js +26 -10
- package/lib/code/code-page.js +1 -1
- package/lib/code/code-page.jsx +1 -1
- package/lib/code/port-forwards.js +17 -3
- package/lib/config.js +4 -0
- package/lib/cron.js +3 -3
- package/lib/db/api-keys.js +22 -61
- package/lib/db/config.js +23 -0
- package/lib/db/index.js +3 -1
- package/lib/maintenance.js +34 -11
- package/lib/paths.js +1 -38
- package/lib/tools/create-agent-job.js +0 -4
- package/lib/tools/docker.js +23 -7
- package/lib/triggers.js +4 -3
- package/lib/utils/render-md.js +3 -1
- package/package.json +1 -1
- package/setup/setup-ssl.mjs +414 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +1 -1
- package/templates/.gitignore.template +7 -0
- package/templates/CLAUDE.md +3 -2
- package/templates/agent-job/CLAUDE.md.template +34 -0
- package/templates/{config → agent-job}/CRONS.json +1 -1
- package/templates/docker-compose.custom.yml +41 -62
- package/templates/docker-compose.yml +15 -21
- package/templates/skills/agent-job-secrets/agent-job-secrets.js +18 -9
- package/templates/CLAUDE.md.template +0 -368
- package/templates/README.md +0 -75
- package/templates/config/CLAUDE.md.template +0 -40
- package/templates/cron/CLAUDE.md.template +0 -24
- package/templates/docker-compose.litellm.yml +0 -82
- package/templates/docs/CLAUDE.md.template +0 -12
- package/templates/docs/CLI.md +0 -59
- package/templates/docs/CLUSTERS.md +0 -151
- package/templates/docs/CONFIGURATION.md +0 -181
- package/templates/docs/CRONS_AND_TRIGGERS.md +0 -132
- package/templates/docs/GETTING_STARTED.md +0 -64
- package/templates/docs/SECURITY.md +0 -61
- package/templates/docs/SKILLS.md +0 -113
- package/templates/docs/UPGRADING.md +0 -92
- package/templates/skills/LICENSE +0 -21
- package/templates/traefik-dynamic.yml.example +0 -7
- package/templates/triggers/.gitkeep +0 -0
- package/templates/triggers/CLAUDE.md.template +0 -41
- /package/templates/{config → agent-job}/HEARTBEAT.md +0 -0
- /package/templates/{config/agent-job → agent-job}/SOUL.md +0 -0
- /package/templates/{config/agent-job/AGENT_JOB.md → agent-job/SYSTEM.md} +0 -0
- /package/templates/{cron/.gitkeep → event-handler/CLAUDE.md.template} +0 -0
- /package/templates/{config/agent-job → event-handler}/SUMMARY.md +0 -0
- /package/templates/{config → event-handler}/TRIGGERS.json +0 -0
- /package/templates/{config → event-handler}/agent-chat/SYSTEM.md +0 -0
- /package/templates/{config/cluster → event-handler/clusters}/ROLE.md +0 -0
- /package/templates/{config/cluster → event-handler/clusters}/SYSTEM.md +0 -0
- /package/templates/{config → event-handler}/code-chat/SYSTEM.md +0 -0
- /package/templates/{config → event-handler}/litellm/main.yaml +0 -0
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ The wizard walks you through everything:
|
|
|
106
106
|
- **Web Chat**: Visit your APP_URL to chat with your agent, create jobs, upload files
|
|
107
107
|
- **Telegram** (optional): Run `npm run setup-telegram` to connect a Telegram bot
|
|
108
108
|
- **Webhook**: Send a POST to `/api/create-agent-job` with your API key to create jobs programmatically
|
|
109
|
-
- **Cron**: Edit `
|
|
109
|
+
- **Cron**: Edit `agent-job/CRONS.json` to schedule recurring jobs
|
|
110
110
|
|
|
111
111
|
### Chat vs Agent LLM
|
|
112
112
|
|
|
@@ -192,6 +192,17 @@ See [Different Models](docs/RUNNING_DIFFERENT_MODELS.md) for the full provider r
|
|
|
192
192
|
|
|
193
193
|
---
|
|
194
194
|
|
|
195
|
+
## Known Issues
|
|
196
|
+
|
|
197
|
+
### Windows: `SQLITE_IOERR_SHMOPEN`
|
|
198
|
+
|
|
199
|
+
SQLite can't create or open its shared-memory (`.shm`) file. Common causes:
|
|
200
|
+
|
|
201
|
+
- **Antivirus** (Windows Defender, etc.) locking the database files — add your project folder to the exclusion list
|
|
202
|
+
- **Cloud-synced folders** (OneDrive, Dropbox, Google Drive) — move your project to a non-synced directory like `C:\Projects\`
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
195
206
|
## Docs
|
|
196
207
|
|
|
197
208
|
| Document | Description |
|
package/bin/CLAUDE.md
CHANGED
|
@@ -21,7 +21,7 @@ Entry point: `cli.js` (invoked via `npx thepopebot <command>`).
|
|
|
21
21
|
|
|
22
22
|
`managed-paths.js` defines files auto-synced by `init`. These are overwritten on every init/upgrade — users should not edit them.
|
|
23
23
|
|
|
24
|
-
**Managed paths**: `.github/workflows/`, `docker-compose.yml`, `.dockerignore`, `.gitignore`, `CLAUDE.md`, `
|
|
24
|
+
**Managed paths**: `.github/workflows/`, `docker-compose.yml`, `.dockerignore`, `.gitignore`, `agent-job/CLAUDE.md`, `event-handler/CLAUDE.md`, `skills/CLAUDE.md`.
|
|
25
25
|
|
|
26
26
|
`isManaged(relPath)` — returns true if a path is managed (exact match or directory prefix).
|
|
27
27
|
|
package/bin/cli.js
CHANGED
|
@@ -52,11 +52,13 @@ Commands:
|
|
|
52
52
|
init Scaffold a new thepopebot project
|
|
53
53
|
upgrade|update [@beta|version] Upgrade thepopebot (install, init, build, commit, push)
|
|
54
54
|
setup Run interactive setup wizard
|
|
55
|
+
setup-ssl Configure SSL with Let's Encrypt wildcard cert
|
|
55
56
|
setup-telegram Reconfigure Telegram webhook
|
|
56
57
|
reset-auth Regenerate AUTH_SECRET (invalidates all sessions)
|
|
57
58
|
reset [file] Restore a template file (or list available templates)
|
|
58
59
|
diff [file] Show differences between project files and package templates
|
|
59
60
|
sync <path> Sync local package to a test install (build, pack, Docker)
|
|
61
|
+
sync --fast <path> Fast sync — copy source into running container, rebuild .next
|
|
60
62
|
set-var <KEY> [VALUE] Set a GitHub repository variable
|
|
61
63
|
user:password <email> Change a user's password
|
|
62
64
|
`);
|
|
@@ -246,6 +248,7 @@ async function init() {
|
|
|
246
248
|
const pkg = {
|
|
247
249
|
name: dirName,
|
|
248
250
|
private: true,
|
|
251
|
+
type: 'module',
|
|
249
252
|
scripts: {
|
|
250
253
|
setup: 'thepopebot setup',
|
|
251
254
|
'setup-telegram': 'thepopebot setup-telegram',
|
|
@@ -258,11 +261,19 @@ async function init() {
|
|
|
258
261
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
259
262
|
console.log(' Created package.json');
|
|
260
263
|
} else {
|
|
261
|
-
|
|
264
|
+
// Ensure "type": "module" is set for ESM support
|
|
265
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
266
|
+
if (!pkg.type) {
|
|
267
|
+
pkg.type = 'module';
|
|
268
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
269
|
+
console.log(' Added "type": "module" to package.json');
|
|
270
|
+
} else {
|
|
271
|
+
console.log(' Skipped package.json (already exists)');
|
|
272
|
+
}
|
|
262
273
|
}
|
|
263
274
|
|
|
264
275
|
// Create default skill activation symlinks
|
|
265
|
-
const defaultSkills = [
|
|
276
|
+
const defaultSkills = [];
|
|
266
277
|
const activeDir = path.join(cwd, 'skills', 'active');
|
|
267
278
|
fs.mkdirSync(activeDir, { recursive: true });
|
|
268
279
|
for (const skill of defaultSkills) {
|
|
@@ -337,8 +348,7 @@ AUTH_TRUST_HOST=true
|
|
|
337
348
|
DATABASE_PATH=data/db/thepopebot.sqlite
|
|
338
349
|
THEPOPEBOT_VERSION=${version}
|
|
339
350
|
|
|
340
|
-
#
|
|
341
|
-
# Edit docker-compose.custom.yml with your changes, then uncomment:
|
|
351
|
+
# To enable SSL with Let's Encrypt, run: npx thepopebot setup-ssl
|
|
342
352
|
# COMPOSE_FILE=docker-compose.custom.yml
|
|
343
353
|
`;
|
|
344
354
|
fs.writeFileSync(envPath, seedEnv);
|
|
@@ -375,7 +385,7 @@ function reset(filePath) {
|
|
|
375
385
|
console.log(` ${destPath(file)}`);
|
|
376
386
|
}
|
|
377
387
|
console.log('\nUsage: thepopebot reset <file>');
|
|
378
|
-
console.log('Example: thepopebot reset
|
|
388
|
+
console.log('Example: thepopebot reset agent-job/SOUL.md\n');
|
|
379
389
|
return;
|
|
380
390
|
}
|
|
381
391
|
|
|
@@ -432,7 +442,7 @@ function diff(filePath) {
|
|
|
432
442
|
console.log(' All files match package templates.');
|
|
433
443
|
}
|
|
434
444
|
console.log('\nUsage: thepopebot diff <file>');
|
|
435
|
-
console.log('Example: thepopebot diff
|
|
445
|
+
console.log('Example: thepopebot diff agent-job/SOUL.md\n');
|
|
436
446
|
return;
|
|
437
447
|
}
|
|
438
448
|
|
|
@@ -490,6 +500,15 @@ function setup() {
|
|
|
490
500
|
}
|
|
491
501
|
}
|
|
492
502
|
|
|
503
|
+
function setupSsl() {
|
|
504
|
+
const setupScript = path.join(__dirname, '..', 'setup', 'setup-ssl.mjs');
|
|
505
|
+
try {
|
|
506
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
|
|
507
|
+
} catch {
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
493
512
|
function setupTelegram() {
|
|
494
513
|
const setupScript = path.join(__dirname, '..', 'setup', 'setup-telegram.mjs');
|
|
495
514
|
try {
|
|
@@ -770,6 +789,9 @@ switch (command) {
|
|
|
770
789
|
case 'setup':
|
|
771
790
|
setup();
|
|
772
791
|
break;
|
|
792
|
+
case 'setup-ssl':
|
|
793
|
+
setupSsl();
|
|
794
|
+
break;
|
|
773
795
|
case 'setup-telegram':
|
|
774
796
|
setupTelegram();
|
|
775
797
|
break;
|
|
@@ -787,8 +809,15 @@ switch (command) {
|
|
|
787
809
|
await upgrade();
|
|
788
810
|
break;
|
|
789
811
|
case 'sync': {
|
|
790
|
-
const
|
|
791
|
-
|
|
812
|
+
const fast = args.includes('--fast');
|
|
813
|
+
const syncArgs = args.filter(a => a !== '--fast');
|
|
814
|
+
if (fast) {
|
|
815
|
+
const { syncFast } = await import('./sync.js');
|
|
816
|
+
await syncFast(syncArgs[0]);
|
|
817
|
+
} else {
|
|
818
|
+
const { sync } = await import('./sync.js');
|
|
819
|
+
await sync(syncArgs[0]);
|
|
820
|
+
}
|
|
792
821
|
break;
|
|
793
822
|
}
|
|
794
823
|
case 'set-var':
|
package/bin/docker-build.js
CHANGED
|
@@ -62,6 +62,11 @@ const CODING_AGENTS = [
|
|
|
62
62
|
context: 'docker/coding-agent',
|
|
63
63
|
dockerfile: 'docker/coding-agent/Dockerfile.opencode',
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
name: 'coding-agent-kimi-cli',
|
|
67
|
+
context: 'docker/coding-agent',
|
|
68
|
+
dockerfile: 'docker/coding-agent/Dockerfile.kimi-cli',
|
|
69
|
+
},
|
|
65
70
|
];
|
|
66
71
|
|
|
67
72
|
// Non-coding-agent images (independent, built in parallel)
|
package/bin/managed-paths.js
CHANGED
|
@@ -8,12 +8,9 @@ export const MANAGED_PATHS = [
|
|
|
8
8
|
'docker-compose.yml',
|
|
9
9
|
'.dockerignore',
|
|
10
10
|
'.gitignore',
|
|
11
|
-
'CLAUDE.md',
|
|
12
|
-
'
|
|
11
|
+
'agent-job/CLAUDE.md',
|
|
12
|
+
'event-handler/CLAUDE.md',
|
|
13
13
|
'skills/CLAUDE.md',
|
|
14
|
-
'cron/CLAUDE.md',
|
|
15
|
-
'triggers/CLAUDE.md',
|
|
16
|
-
'docs/',
|
|
17
14
|
];
|
|
18
15
|
|
|
19
16
|
export function isManaged(relPath) {
|
package/bin/sync.js
CHANGED
|
@@ -306,6 +306,90 @@ function buildDockerImage(projectPath) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Fast sync — skip Docker image rebuild entirely.
|
|
311
|
+
*
|
|
312
|
+
* 1. Build package JSX (npm run build)
|
|
313
|
+
* 2. mirrorTemplates() — scaffold using init's managed-path logic
|
|
314
|
+
* 3. docker cp package source (lib/, api/, config/, package.json) into
|
|
315
|
+
* the running container's /app/node_modules/thepopebot/
|
|
316
|
+
* 4. docker cp web/app/ + web/postcss.config.mjs into container
|
|
317
|
+
* 5. docker exec next build inside the container (tailwindcss already there)
|
|
318
|
+
* 6. Clean up copied source from container
|
|
319
|
+
* 7. docker exec pm2 restart all
|
|
320
|
+
*/
|
|
321
|
+
export async function syncFast(projectPath) {
|
|
322
|
+
if (!projectPath) {
|
|
323
|
+
console.error('\n Usage: thepopebot sync --fast <path-to-project>\n');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
projectPath = path.resolve(projectPath);
|
|
328
|
+
|
|
329
|
+
if (!fs.existsSync(path.join(projectPath, 'package.json'))) {
|
|
330
|
+
console.error(`\n Not a project directory (no package.json): ${projectPath}\n`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 1. Build JSX
|
|
335
|
+
console.log('\n Building package...');
|
|
336
|
+
execSync('npm run build', { stdio: 'inherit', cwd: PACKAGE_DIR });
|
|
337
|
+
|
|
338
|
+
// 2. Mirror templates
|
|
339
|
+
console.log('\n Mirroring templates...');
|
|
340
|
+
mirrorTemplates(projectPath);
|
|
341
|
+
|
|
342
|
+
// 3. Get running container ID
|
|
343
|
+
const container = execSync('docker compose ps -q event-handler', {
|
|
344
|
+
encoding: 'utf8',
|
|
345
|
+
cwd: projectPath,
|
|
346
|
+
}).trim();
|
|
347
|
+
|
|
348
|
+
if (!container) {
|
|
349
|
+
console.error('\n event-handler container is not running. Use full sync instead.\n');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 4. Copy package source into container's node_modules/thepopebot/
|
|
354
|
+
const PKG_DEST = '/app/node_modules/thepopebot';
|
|
355
|
+
const PACKAGE_DIRS = ['lib', 'api', 'config'];
|
|
356
|
+
|
|
357
|
+
console.log('\n Copying package source into container...');
|
|
358
|
+
for (const dir of PACKAGE_DIRS) {
|
|
359
|
+
execSync(`docker exec ${container} rm -rf ${PKG_DEST}/${dir}`, { stdio: 'inherit' });
|
|
360
|
+
execSync(`docker cp ${path.join(PACKAGE_DIR, dir)} ${container}:${PKG_DEST}/${dir}`, { stdio: 'inherit' });
|
|
361
|
+
}
|
|
362
|
+
// Also copy package.json for exports resolution
|
|
363
|
+
execSync(`docker cp ${path.join(PACKAGE_DIR, 'package.json')} ${container}:${PKG_DEST}/package.json`, { stdio: 'inherit' });
|
|
364
|
+
|
|
365
|
+
// 5. Copy web/app/ source into container for next build
|
|
366
|
+
const webDir = path.join(PACKAGE_DIR, 'web');
|
|
367
|
+
console.log('\n Copying web source into container...');
|
|
368
|
+
execSync(`docker cp ${path.join(webDir, 'app')} ${container}:/app/app`, { stdio: 'inherit' });
|
|
369
|
+
execSync(`docker cp ${path.join(webDir, 'postcss.config.mjs')} ${container}:/app/postcss.config.mjs`, { stdio: 'inherit' });
|
|
370
|
+
execSync(`docker cp ${path.join(webDir, 'next.config.mjs')} ${container}:/app/next.config.mjs`, { stdio: 'inherit' });
|
|
371
|
+
|
|
372
|
+
// 6. Run next build inside the container
|
|
373
|
+
// Hide data/logs dirs so webpack's FileSystemInfo doesn't crawl them (causes OOM/RangeError
|
|
374
|
+
// when workspaces contain thousands of files). Restored immediately after build.
|
|
375
|
+
console.log('\n Building Next.js inside container...');
|
|
376
|
+
execSync(`docker exec ${container} sh -c 'mv /app/data /app/.data-build-tmp 2>/dev/null; mv /app/logs /app/.logs-build-tmp 2>/dev/null; true'`, { stdio: 'inherit' });
|
|
377
|
+
try {
|
|
378
|
+
execSync(`docker exec ${container} ./node_modules/.bin/next build`, { stdio: 'inherit' });
|
|
379
|
+
} finally {
|
|
380
|
+
execSync(`docker exec ${container} sh -c 'mv /app/.data-build-tmp /app/data 2>/dev/null; mv /app/.logs-build-tmp /app/logs 2>/dev/null; true'`, { stdio: 'inherit' });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 7. Clean up web source from container (not needed at runtime)
|
|
384
|
+
execSync(`docker exec ${container} rm -rf /app/app`, { stdio: 'inherit' });
|
|
385
|
+
|
|
386
|
+
// 8. Restart PM2
|
|
387
|
+
console.log('\n Restarting server...');
|
|
388
|
+
execSync(`docker exec ${container} pm2 restart all`, { stdio: 'inherit' });
|
|
389
|
+
|
|
390
|
+
console.log('\n Fast synced!\n');
|
|
391
|
+
}
|
|
392
|
+
|
|
309
393
|
export async function sync(projectPath) {
|
|
310
394
|
if (!projectPath) {
|
|
311
395
|
console.error('\n Usage: thepopebot sync <path-to-project>\n');
|
package/config/CLAUDE.md
CHANGED
|
@@ -1,32 +1,4 @@
|
|
|
1
|
-
# config/ —
|
|
2
|
-
|
|
3
|
-
## Directory Structure
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
config/
|
|
7
|
-
├── agent-chat/
|
|
8
|
-
│ └── SYSTEM.md # Agent chat system prompt (supports {{skills}}, {{datetime}})
|
|
9
|
-
├── code-chat/
|
|
10
|
-
│ └── SYSTEM.md # Code workspace system prompt
|
|
11
|
-
├── agent-job/
|
|
12
|
-
│ ├── SOUL.md # Agent personality/identity (used by Docker agent)
|
|
13
|
-
│ ├── AGENT_JOB.md # Agent runtime environment docs (used by Docker agent)
|
|
14
|
-
│ └── SUMMARY.md # Prompt for summarizing completed jobs
|
|
15
|
-
├── cluster/
|
|
16
|
-
│ ├── SYSTEM.md # Cluster worker system prompt
|
|
17
|
-
│ └── ROLE.md # Per-role prompt template for cluster workers
|
|
18
|
-
├── HEARTBEAT.md # Self-monitoring behavior (cron task prompt)
|
|
19
|
-
├── CRONS.json # Scheduled job definitions
|
|
20
|
-
└── TRIGGERS.json # Webhook trigger definitions
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Markdown File Includes
|
|
24
|
-
|
|
25
|
-
Markdown files in `config/` support includes and built-in variables, powered by `lib/utils/render-md.js`.
|
|
26
|
-
|
|
27
|
-
- **File includes**: `{{ filepath.md }}` — resolves relative to project root, recursive with circular detection. Missing files are left as-is.
|
|
28
|
-
- **`{{datetime}}`** — Current ISO timestamp.
|
|
29
|
-
- **`{{skills}}`** — Dynamic bullet list of active skill descriptions from `skills/active/*/SKILL.md` frontmatter. Never hardcode skill names — this is resolved at runtime.
|
|
1
|
+
# config/ — Next.js Config Wrapper
|
|
30
2
|
|
|
31
3
|
## Next.js Config Wrapper (index.js)
|
|
32
4
|
|
|
@@ -70,7 +70,7 @@ export async function register() {
|
|
|
70
70
|
const { startClusterRuntime } = await import('../lib/cluster/runtime.js');
|
|
71
71
|
startClusterRuntime();
|
|
72
72
|
|
|
73
|
-
// Start internal maintenance cron (cleanup
|
|
73
|
+
// Start internal maintenance cron (cleanup orphaned agent job keys, etc.)
|
|
74
74
|
const { startMaintenanceCron } = await import('../lib/maintenance.js');
|
|
75
75
|
startMaintenanceCron();
|
|
76
76
|
|
package/lib/CLAUDE.md
CHANGED
|
@@ -12,16 +12,16 @@ If the task needs to *think*, use `agent`. If it just needs to *do*, use `comman
|
|
|
12
12
|
|
|
13
13
|
**Agent**: Creates a Docker Agent job via `createAgentJob()`. Pushes an `agent-job/*` branch and launches a local Docker container. The `job` string is the LLM task prompt. Agent backend selected via `agent_backend` in `agent-job.config.json`.
|
|
14
14
|
|
|
15
|
-
**Command**: Runs a shell command on the event handler. Working directory:
|
|
15
|
+
**Command**: Runs a shell command on the event handler. Working directory: project root.
|
|
16
16
|
|
|
17
17
|
**Webhook**: Makes an HTTP request. `GET` skips the body; `POST` (default) sends `{ ...vars }` or `{ ...vars, data: <payload> }`.
|
|
18
18
|
|
|
19
19
|
## Cron Jobs
|
|
20
20
|
|
|
21
|
-
Defined in `
|
|
21
|
+
Defined in `agent-job/CRONS.json`, loaded by `lib/cron.js` at startup via `node-cron`. Each entry has `name`, `schedule` (cron expression), `type` (`agent`/`command`/`webhook`), and the corresponding action fields (`job`, `command`, or `url`/`method`/`headers`/`vars`). Set `enabled: false` to disable. Agent-type entries support optional `llm_provider` and `llm_model` fields to override the default LLM (passed to Docker agent via `agent-job.config.json`).
|
|
22
22
|
|
|
23
23
|
## Webhook Triggers
|
|
24
24
|
|
|
25
|
-
Defined in `
|
|
25
|
+
Defined in `event-handler/TRIGGERS.json`, loaded by `lib/triggers.js`. Each trigger watches an endpoint path (`watch_path`) and fires an array of actions (fire-and-forget, after auth, before route handler). Actions use the same `type`/`job`/`command`/`url` fields as cron jobs, including optional `llm_provider`/`llm_model` overrides.
|
|
26
26
|
|
|
27
27
|
Template tokens in `job` and `command` strings: `{{body}}`, `{{body.field}}`, `{{query}}`, `{{query.field}}`, `{{headers}}`, `{{headers.field}}`.
|
package/lib/ai/CLAUDE.md
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
Two agent singletons, both using `createReactAgent` from `@langchain/langgraph/prebuilt` with `SqliteSaver` for conversation memory:
|
|
6
6
|
|
|
7
7
|
**Agent Chat** — singleton via `getAgentChat()`:
|
|
8
|
-
- System prompt: `
|
|
8
|
+
- System prompt: `event-handler/agent-chat/SYSTEM.md` (rendered fresh each invocation via `render_md()`)
|
|
9
9
|
- Tools: `agent_job`, `coding_agent`
|
|
10
10
|
- Call `resetAgentChats()` to clear both singletons (required if hot-reloading)
|
|
11
11
|
|
|
12
12
|
**Code Chat** — singleton via `getCodeChat()`:
|
|
13
|
-
- System prompt: `
|
|
13
|
+
- System prompt: `event-handler/code-chat/SYSTEM.md` (rendered fresh each invocation)
|
|
14
14
|
- Tools: `coding_agent` (reads repo/branch/workspace from `runtime.configurable`)
|
|
15
15
|
|
|
16
16
|
## Adding a New Tool
|
|
@@ -73,7 +73,28 @@ Three-layer parser for Claude Code agents running in headless Docker containers:
|
|
|
73
73
|
3. **Event mapper** (`mapLine()`) — Converts each line to chat events:
|
|
74
74
|
- `assistant` messages: `text` blocks → `{ type: 'text' }`, `tool_use` blocks → `{ type: 'tool-call' }`
|
|
75
75
|
- `user` messages: `tool_result` blocks → `{ type: 'tool-result' }` (priority: stdout > string content > array)
|
|
76
|
-
- `result` messages: → `{ type: 'text'
|
|
76
|
+
- `result` messages: → `{ type: 'text' }` (final summary from the agent)
|
|
77
77
|
- Non-JSON lines (e.g. `NO_CHANGES`, `AGENT_FAILED`): wrapped as plain text events
|
|
78
78
|
|
|
79
79
|
`parseHeadlessStream(dockerLogStream)` is an async generator consuming `http.IncomingMessage`. `mapLine()` is also reused by `lib/cluster/stream.js` for worker log parsing.
|
|
80
|
+
|
|
81
|
+
### Tool Return Format
|
|
82
|
+
|
|
83
|
+
The `coding_agent` tool (in `tools.js`) returns the **full container session** as a flat JSON array. This becomes the ToolMessage in LangGraph's checkpoint, giving the LLM complete context on the current turn. The array contains:
|
|
84
|
+
|
|
85
|
+
- `{ type: 'meta', codingAgent, backendApi }` — first event, agent identity
|
|
86
|
+
- `{ type: 'text', text }` — agent text output
|
|
87
|
+
- `{ type: 'tool-call', toolCallId, toolName, args }` — agent tool invocations
|
|
88
|
+
- `{ type: 'tool-result', toolCallId, result }` — tool execution results
|
|
89
|
+
- `{ type: 'exit', exitCode }` — last event, container exit status
|
|
90
|
+
|
|
91
|
+
On error before streaming starts: `[{ type: 'error', message }]`.
|
|
92
|
+
|
|
93
|
+
### Adding a New Agent Mapper (line-mappers.js)
|
|
94
|
+
|
|
95
|
+
Each coding agent CLI has its own mapper function (`mapClaudeCodeLine`, `mapPiLine`, `mapGeminiLine`, `mapCodexLine`, `mapOpenCodeLine`, `mapKimiLine`). When adding a new agent:
|
|
96
|
+
|
|
97
|
+
1. Create `mapXxxLine(parsed)` in `line-mappers.js` that returns an array of `{ type, ... }` events
|
|
98
|
+
2. Register it in `headless-stream.js`: add to imports, re-exports, and the `mapperMap` object
|
|
99
|
+
3. Map the agent's JSON output to three event types: `{ type: 'text', text }`, `{ type: 'tool-call', toolCallId, toolName, args }`, `{ type: 'tool-result', toolCallId, result }`
|
|
100
|
+
4. Return `[{ type: 'skip' }]` for noise events (session init, rate limits, etc.) to suppress them without triggering the unknown fallback
|
package/lib/ai/agent.js
CHANGED
|
@@ -3,7 +3,8 @@ import { SystemMessage } from '@langchain/core/messages';
|
|
|
3
3
|
import { createModel } from './model.js';
|
|
4
4
|
import { agentJobTool, agentChatCodingTool, codeChatCodingTool } from './tools.js';
|
|
5
5
|
import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
|
|
6
|
-
import
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { PROJECT_ROOT } from '../paths.js';
|
|
7
8
|
import { render_md } from '../utils/render-md.js';
|
|
8
9
|
|
|
9
10
|
// Singletons on globalThis to survive Next.js webpack chunk duplication.
|
|
@@ -20,13 +21,14 @@ export async function getAgentChat() {
|
|
|
20
21
|
const model = await createModel();
|
|
21
22
|
const tools = [agentJobTool, agentChatCodingTool];
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
+
const dbPath = process.env.DATABASE_PATH || path.join(PROJECT_ROOT, 'data/db/thepopebot.sqlite');
|
|
25
|
+
const checkpointer = SqliteSaver.fromConnString(dbPath);
|
|
24
26
|
|
|
25
27
|
globalThis.__popebotAgentChat = createReactAgent({
|
|
26
28
|
llm: model,
|
|
27
29
|
tools,
|
|
28
30
|
checkpointSaver: checkpointer,
|
|
29
|
-
prompt: (state) => [new SystemMessage(render_md(
|
|
31
|
+
prompt: (state) => [new SystemMessage(render_md(path.join(PROJECT_ROOT, 'event-handler/agent-chat/SYSTEM.md'))), ...state.messages],
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
return globalThis.__popebotAgentChat;
|
|
@@ -41,13 +43,14 @@ export async function getCodeChat() {
|
|
|
41
43
|
const model = await createModel();
|
|
42
44
|
const tools = [codeChatCodingTool];
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const dbPath = process.env.DATABASE_PATH || path.join(PROJECT_ROOT, 'data/db/thepopebot.sqlite');
|
|
47
|
+
const checkpointer = SqliteSaver.fromConnString(dbPath);
|
|
45
48
|
|
|
46
49
|
globalThis.__popebotCodeChat = createReactAgent({
|
|
47
50
|
llm: model,
|
|
48
51
|
tools,
|
|
49
52
|
checkpointSaver: checkpointer,
|
|
50
|
-
prompt: (state) => [new SystemMessage(render_md(
|
|
53
|
+
prompt: (state) => [new SystemMessage(render_md(path.join(PROJECT_ROOT, 'event-handler/code-chat/SYSTEM.md'))), ...state.messages],
|
|
51
54
|
});
|
|
52
55
|
}
|
|
53
56
|
return globalThis.__popebotCodeChat;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async push/pull queue. Producer calls push()/done(), consumer uses for-await.
|
|
3
|
+
*/
|
|
4
|
+
export function createChannel() {
|
|
5
|
+
const queue = [];
|
|
6
|
+
const waiters = [];
|
|
7
|
+
let isDone = false;
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
push(value) {
|
|
11
|
+
if (waiters.length > 0) waiters.shift()(value);
|
|
12
|
+
else queue.push(value);
|
|
13
|
+
},
|
|
14
|
+
done() {
|
|
15
|
+
isDone = true;
|
|
16
|
+
while (waiters.length > 0) waiters.shift()(Symbol.for('done'));
|
|
17
|
+
},
|
|
18
|
+
async *[Symbol.asyncIterator]() {
|
|
19
|
+
while (true) {
|
|
20
|
+
if (queue.length > 0) {
|
|
21
|
+
yield queue.shift();
|
|
22
|
+
} else if (isDone) {
|
|
23
|
+
return;
|
|
24
|
+
} else {
|
|
25
|
+
const value = await new Promise(resolve => waiters.push(resolve));
|
|
26
|
+
if (value === Symbol.for('done')) return;
|
|
27
|
+
yield value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Merge two async iterables — yields from whichever has data first.
|
|
36
|
+
* Completes when BOTH are exhausted.
|
|
37
|
+
*/
|
|
38
|
+
export async function* mergeAsyncIterables(iter1, iter2) {
|
|
39
|
+
const channel = createChannel();
|
|
40
|
+
let active = 2;
|
|
41
|
+
|
|
42
|
+
const consume = async (iter) => {
|
|
43
|
+
for await (const item of iter) channel.push(item);
|
|
44
|
+
if (--active === 0) channel.done();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
consume(iter1);
|
|
48
|
+
consume(iter2);
|
|
49
|
+
|
|
50
|
+
yield* channel;
|
|
51
|
+
}
|
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
mapGeminiLine,
|
|
11
11
|
mapCodexLine,
|
|
12
12
|
mapOpenCodeLine,
|
|
13
|
+
mapKimiLine,
|
|
13
14
|
} from './line-mappers.js';
|
|
14
15
|
|
|
15
16
|
import {
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
mapGeminiLine,
|
|
20
21
|
mapCodexLine,
|
|
21
22
|
mapOpenCodeLine,
|
|
23
|
+
mapKimiLine,
|
|
22
24
|
} from './line-mappers.js';
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -41,6 +43,7 @@ export async function* parseHeadlessStream(dockerLogStream, codingAgent = 'claud
|
|
|
41
43
|
'gemini-cli': mapGeminiLine,
|
|
42
44
|
'codex-cli': mapCodexLine,
|
|
43
45
|
'opencode': mapOpenCodeLine,
|
|
46
|
+
'kimi-cli': mapKimiLine,
|
|
44
47
|
};
|
|
45
48
|
const mapper = mapperMap[codingAgent] || mapClaudeCodeLine;
|
|
46
49
|
|