opencode-swarm-plugin 0.32.0 → 0.34.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/.hive/issues.jsonl +12 -0
- package/.hive/memories.jsonl +255 -1
- package/.turbo/turbo-build.log +9 -10
- package/.turbo/turbo-test.log +343 -337
- package/CHANGELOG.md +358 -0
- package/README.md +152 -179
- package/bin/swarm.test.ts +303 -1
- package/bin/swarm.ts +473 -16
- package/dist/compaction-hook.d.ts +1 -1
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/index.d.ts +112 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12380 -131
- package/dist/logger.d.ts +34 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/observability-tools.d.ts +116 -0
- package/dist/observability-tools.d.ts.map +1 -0
- package/dist/plugin.js +12254 -119
- package/dist/skills.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts +105 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +113 -2
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-research.d.ts +127 -0
- package/dist/swarm-research.d.ts.map +1 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/dist/swarm.d.ts +73 -1
- package/dist/swarm.d.ts.map +1 -1
- package/evals/compaction-resumption.eval.ts +289 -0
- package/evals/coordinator-behavior.eval.ts +307 -0
- package/evals/fixtures/compaction-cases.ts +350 -0
- package/evals/scorers/compaction-scorers.ts +305 -0
- package/evals/scorers/index.ts +12 -0
- package/examples/plugin-wrapper-template.ts +297 -8
- package/package.json +6 -2
- package/src/compaction-hook.test.ts +617 -1
- package/src/compaction-hook.ts +291 -18
- package/src/index.ts +54 -1
- package/src/logger.test.ts +189 -0
- package/src/logger.ts +135 -0
- package/src/observability-tools.test.ts +346 -0
- package/src/observability-tools.ts +594 -0
- package/src/skills.integration.test.ts +137 -1
- package/src/skills.test.ts +42 -1
- package/src/skills.ts +8 -4
- package/src/swarm-orchestrate.test.ts +123 -0
- package/src/swarm-orchestrate.ts +183 -0
- package/src/swarm-prompts.test.ts +553 -1
- package/src/swarm-prompts.ts +406 -4
- package/src/swarm-research.integration.test.ts +544 -0
- package/src/swarm-research.test.ts +698 -0
- package/src/swarm-research.ts +472 -0
- package/src/swarm-review.test.ts +177 -0
- package/src/swarm-review.ts +12 -47
- package/src/swarm.ts +6 -3
package/bin/swarm.ts
CHANGED
|
@@ -1153,9 +1153,17 @@ const result2 = await Task(subagent_type="swarm/worker", prompt="<from above>")
|
|
|
1153
1153
|
4. **SEND FEEDBACK** - Approve or request changes
|
|
1154
1154
|
\`swarm_review_feedback(project_key, task_id, worker_id, status, issues)\`
|
|
1155
1155
|
|
|
1156
|
-
If approved
|
|
1157
|
-
|
|
1158
|
-
|
|
1156
|
+
**If approved:**
|
|
1157
|
+
- Close cell, spawn next worker
|
|
1158
|
+
|
|
1159
|
+
**If needs_changes:**
|
|
1160
|
+
- \`swarm_review_feedback\` returns \`retry_context\` (NOT sends message - worker is dead)
|
|
1161
|
+
- Generate retry prompt: \`swarm_spawn_retry(retry_context)\`
|
|
1162
|
+
- Spawn NEW worker with Task() using retry prompt
|
|
1163
|
+
- Max 3 attempts before marking task blocked
|
|
1164
|
+
|
|
1165
|
+
**If 3 failures:**
|
|
1166
|
+
- Mark task blocked, escalate to human
|
|
1159
1167
|
|
|
1160
1168
|
5. **ONLY THEN** - Spawn next worker or complete
|
|
1161
1169
|
|
|
@@ -1322,6 +1330,233 @@ hive_update(id="<bead-id>", status="blocked")
|
|
|
1322
1330
|
Begin by reading your full prompt and executing Step 1.
|
|
1323
1331
|
`;
|
|
1324
1332
|
|
|
1333
|
+
const getResearcherAgent = (model: string) => `---
|
|
1334
|
+
name: swarm-researcher
|
|
1335
|
+
description: READ-ONLY research agent - discovers tools, fetches docs, stores findings
|
|
1336
|
+
model: ${model}
|
|
1337
|
+
---
|
|
1338
|
+
|
|
1339
|
+
You are a research agent. Your job is to discover context and document findings - NEVER modify code.
|
|
1340
|
+
|
|
1341
|
+
## CRITICAL: You Are READ-ONLY
|
|
1342
|
+
|
|
1343
|
+
**YOU DO NOT:**
|
|
1344
|
+
- Edit code files
|
|
1345
|
+
- Run tests
|
|
1346
|
+
- Make commits
|
|
1347
|
+
- Reserve files (you don't edit, so no reservations needed)
|
|
1348
|
+
- Implement features
|
|
1349
|
+
|
|
1350
|
+
**YOU DO:**
|
|
1351
|
+
- Discover available tools (MCP servers, skills, CLI tools)
|
|
1352
|
+
- Read lockfiles to get current package versions
|
|
1353
|
+
- Fetch documentation for those versions
|
|
1354
|
+
- Store findings in semantic-memory (full details)
|
|
1355
|
+
- Broadcast summaries via swarm mail (condensed)
|
|
1356
|
+
- Return structured summary for shared context
|
|
1357
|
+
|
|
1358
|
+
## Workflow
|
|
1359
|
+
|
|
1360
|
+
### Step 1: Initialize (MANDATORY FIRST)
|
|
1361
|
+
|
|
1362
|
+
\`\`\`
|
|
1363
|
+
swarmmail_init(project_path="/abs/path/to/project", task_description="Research: <what you're researching>")
|
|
1364
|
+
\`\`\`
|
|
1365
|
+
|
|
1366
|
+
### Step 2: Discover Available Tools
|
|
1367
|
+
|
|
1368
|
+
**DO NOT assume what tools are installed. Discover them:**
|
|
1369
|
+
|
|
1370
|
+
\`\`\`
|
|
1371
|
+
# Check what skills user has installed
|
|
1372
|
+
skills_list()
|
|
1373
|
+
|
|
1374
|
+
# Check what MCP servers are available (look for context7, pdf-brain, fetch, etc.)
|
|
1375
|
+
# Note: No direct MCP listing tool - infer from task context or ask coordinator
|
|
1376
|
+
|
|
1377
|
+
# Check for CLI tools if relevant (bd, cass, ubs, ollama)
|
|
1378
|
+
# Use Bash tool to check: which <tool-name>
|
|
1379
|
+
\`\`\`
|
|
1380
|
+
|
|
1381
|
+
### Step 3: Load Relevant Skills
|
|
1382
|
+
|
|
1383
|
+
Based on research task, load appropriate skills:
|
|
1384
|
+
|
|
1385
|
+
\`\`\`
|
|
1386
|
+
skills_use(name="<skill-name>", context="Researching <topic>")
|
|
1387
|
+
\`\`\`
|
|
1388
|
+
|
|
1389
|
+
### Step 4: Read Lockfiles (if researching dependencies)
|
|
1390
|
+
|
|
1391
|
+
**DO NOT read implementation code.** Only read metadata:
|
|
1392
|
+
|
|
1393
|
+
\`\`\`
|
|
1394
|
+
# For package.json projects
|
|
1395
|
+
read("package.json")
|
|
1396
|
+
read("package-lock.json") or read("bun.lock") or read("pnpm-lock.yaml")
|
|
1397
|
+
|
|
1398
|
+
# For Python
|
|
1399
|
+
read("requirements.txt") or read("pyproject.toml")
|
|
1400
|
+
|
|
1401
|
+
# For Go
|
|
1402
|
+
read("go.mod")
|
|
1403
|
+
\`\`\`
|
|
1404
|
+
|
|
1405
|
+
Extract current version numbers for libraries you need to research.
|
|
1406
|
+
|
|
1407
|
+
### Step 5: Fetch Documentation
|
|
1408
|
+
|
|
1409
|
+
Use available doc tools to get version-specific docs:
|
|
1410
|
+
|
|
1411
|
+
\`\`\`
|
|
1412
|
+
# If context7 available (check skills_list or task context)
|
|
1413
|
+
# Use it for library docs
|
|
1414
|
+
|
|
1415
|
+
# If pdf-brain available
|
|
1416
|
+
pdf-brain_search(query="<library> <version> <topic>", limit=5)
|
|
1417
|
+
|
|
1418
|
+
# If fetch tool available
|
|
1419
|
+
fetch(url="https://docs.example.com/v2.0/...")
|
|
1420
|
+
|
|
1421
|
+
# If repo-crawl available for OSS libraries
|
|
1422
|
+
repo-crawl_readme(repo="owner/repo")
|
|
1423
|
+
repo-crawl_file(repo="owner/repo", path="docs/...")
|
|
1424
|
+
\`\`\`
|
|
1425
|
+
|
|
1426
|
+
### Step 6: Store Full Findings in Semantic Memory
|
|
1427
|
+
|
|
1428
|
+
**Store detailed findings for future agents:**
|
|
1429
|
+
|
|
1430
|
+
\`\`\`
|
|
1431
|
+
semantic-memory_store(
|
|
1432
|
+
information="Researched <library> v<version>. Key findings: <detailed notes with examples, gotchas, patterns>",
|
|
1433
|
+
metadata="<library>, <version>, <topic>, research"
|
|
1434
|
+
)
|
|
1435
|
+
\`\`\`
|
|
1436
|
+
|
|
1437
|
+
**Include:**
|
|
1438
|
+
- Library/framework versions discovered
|
|
1439
|
+
- Key API patterns
|
|
1440
|
+
- Breaking changes from previous versions
|
|
1441
|
+
- Common gotchas
|
|
1442
|
+
- Relevant examples
|
|
1443
|
+
|
|
1444
|
+
### Step 7: Broadcast Condensed Summary via Swarm Mail
|
|
1445
|
+
|
|
1446
|
+
**Send concise summary to coordinator:**
|
|
1447
|
+
|
|
1448
|
+
\`\`\`
|
|
1449
|
+
swarmmail_send(
|
|
1450
|
+
to=["coordinator"],
|
|
1451
|
+
subject="Research Complete: <topic>",
|
|
1452
|
+
body="<3-5 bullet points with key takeaways>",
|
|
1453
|
+
thread_id="<epic-id>"
|
|
1454
|
+
)
|
|
1455
|
+
\`\`\`
|
|
1456
|
+
|
|
1457
|
+
### Step 8: Return Structured Summary
|
|
1458
|
+
|
|
1459
|
+
**Output format for shared_context:**
|
|
1460
|
+
|
|
1461
|
+
\`\`\`json
|
|
1462
|
+
{
|
|
1463
|
+
"researched": "<topic>",
|
|
1464
|
+
"tools_discovered": ["skill-1", "skill-2", "mcp-server-1"],
|
|
1465
|
+
"versions": {
|
|
1466
|
+
"library-1": "1.2.3",
|
|
1467
|
+
"library-2": "4.5.6"
|
|
1468
|
+
},
|
|
1469
|
+
"key_findings": [
|
|
1470
|
+
"Finding 1 with actionable insight",
|
|
1471
|
+
"Finding 2 with actionable insight",
|
|
1472
|
+
"Finding 3 with actionable insight"
|
|
1473
|
+
],
|
|
1474
|
+
"relevant_skills": ["skill-to-use-1", "skill-to-use-2"],
|
|
1475
|
+
"stored_in_memory": true
|
|
1476
|
+
}
|
|
1477
|
+
\`\`\`
|
|
1478
|
+
|
|
1479
|
+
## Tool Discovery Patterns
|
|
1480
|
+
|
|
1481
|
+
### Skills Discovery
|
|
1482
|
+
|
|
1483
|
+
\`\`\`
|
|
1484
|
+
skills_list()
|
|
1485
|
+
# Returns: Available skills from global, project, bundled sources
|
|
1486
|
+
|
|
1487
|
+
# Load relevant skill for research domain
|
|
1488
|
+
skills_use(name="<skill>", context="Researching <topic>")
|
|
1489
|
+
\`\`\`
|
|
1490
|
+
|
|
1491
|
+
### MCP Server Detection
|
|
1492
|
+
|
|
1493
|
+
**No direct listing tool.** Infer from:
|
|
1494
|
+
- Task context (coordinator may mention available tools)
|
|
1495
|
+
- Trial: Try calling a tool and catch error if not available
|
|
1496
|
+
- Read OpenCode config if accessible
|
|
1497
|
+
|
|
1498
|
+
### CLI Tool Detection
|
|
1499
|
+
|
|
1500
|
+
\`\`\`
|
|
1501
|
+
# Check if tool is installed
|
|
1502
|
+
bash("which <tool>", description="Check if <tool> is available")
|
|
1503
|
+
|
|
1504
|
+
# Examples:
|
|
1505
|
+
bash("which cass", description="Check CASS availability")
|
|
1506
|
+
bash("which ubs", description="Check UBS availability")
|
|
1507
|
+
bash("ollama --version", description="Check Ollama availability")
|
|
1508
|
+
\`\`\`
|
|
1509
|
+
|
|
1510
|
+
## Context Efficiency Rules (MANDATORY)
|
|
1511
|
+
|
|
1512
|
+
**NEVER dump raw documentation.** Always summarize.
|
|
1513
|
+
|
|
1514
|
+
| ❌ Bad (Context Bomb) | ✅ Good (Condensed) |
|
|
1515
|
+
|---------------------|-------------------|
|
|
1516
|
+
| Paste entire API reference | "Library uses hooks API. Key hooks: useQuery, useMutation. Breaking change in v2: callbacks removed." |
|
|
1517
|
+
| Copy full changelog | "v2.0 breaking changes: renamed auth() → authenticate(), dropped IE11 support" |
|
|
1518
|
+
| Include all examples | "Common pattern: async/await with error boundaries (stored full example in semantic-memory)" |
|
|
1519
|
+
|
|
1520
|
+
**Storage Strategy:**
|
|
1521
|
+
- **Semantic Memory**: Full details, examples, code snippets
|
|
1522
|
+
- **Swarm Mail**: 3-5 bullet points only
|
|
1523
|
+
- **Return Value**: Structured JSON summary
|
|
1524
|
+
|
|
1525
|
+
## When to Use This Agent
|
|
1526
|
+
|
|
1527
|
+
**DO spawn researcher when:**
|
|
1528
|
+
- Task requires understanding current tech stack versions
|
|
1529
|
+
- Need to fetch library/framework documentation
|
|
1530
|
+
- Discovering project conventions from config files
|
|
1531
|
+
- Researching best practices for unfamiliar domain
|
|
1532
|
+
|
|
1533
|
+
**DON'T spawn researcher when:**
|
|
1534
|
+
- Information is already in semantic memory (query first!)
|
|
1535
|
+
- Task doesn't need external docs
|
|
1536
|
+
- Time-sensitive work (research adds latency)
|
|
1537
|
+
|
|
1538
|
+
## Example Research Tasks
|
|
1539
|
+
|
|
1540
|
+
**"Research Next.js 16 caching APIs"**
|
|
1541
|
+
|
|
1542
|
+
1. Read package.json → extract Next.js version
|
|
1543
|
+
2. Use context7 or fetch to get Next.js 16 cache docs
|
|
1544
|
+
3. Store findings: unstable_cache, revalidatePath, cache patterns
|
|
1545
|
+
4. Broadcast: "Next.js 16 uses native fetch caching + unstable_cache for functions"
|
|
1546
|
+
5. Return structured summary with key APIs
|
|
1547
|
+
|
|
1548
|
+
**"Discover available testing tools"**
|
|
1549
|
+
|
|
1550
|
+
1. Check skills_list for testing-patterns skill
|
|
1551
|
+
2. Check which jest/vitest/bun (bash tool)
|
|
1552
|
+
3. Read package.json devDependencies
|
|
1553
|
+
4. Store findings: test runner, assertion library, coverage tool
|
|
1554
|
+
5. Broadcast: "Project uses Bun test with happy-dom"
|
|
1555
|
+
6. Return tool inventory
|
|
1556
|
+
|
|
1557
|
+
Begin by executing Step 1 (swarmmail_init).
|
|
1558
|
+
`;
|
|
1559
|
+
|
|
1325
1560
|
// ============================================================================
|
|
1326
1561
|
// Commands
|
|
1327
1562
|
// ============================================================================
|
|
@@ -1533,6 +1768,7 @@ async function setup() {
|
|
|
1533
1768
|
const swarmAgentDir = join(agentDir, "swarm");
|
|
1534
1769
|
const plannerAgentPath = join(swarmAgentDir, "planner.md");
|
|
1535
1770
|
const workerAgentPath = join(swarmAgentDir, "worker.md");
|
|
1771
|
+
const researcherAgentPath = join(swarmAgentDir, "researcher.md");
|
|
1536
1772
|
// Legacy flat paths (for detection/cleanup)
|
|
1537
1773
|
const legacyPlannerPath = join(agentDir, "swarm-planner.md");
|
|
1538
1774
|
const legacyWorkerPath = join(agentDir, "swarm-worker.md");
|
|
@@ -1542,13 +1778,14 @@ async function setup() {
|
|
|
1542
1778
|
commandPath,
|
|
1543
1779
|
plannerAgentPath,
|
|
1544
1780
|
workerAgentPath,
|
|
1781
|
+
researcherAgentPath,
|
|
1545
1782
|
legacyPlannerPath,
|
|
1546
1783
|
legacyWorkerPath,
|
|
1547
1784
|
].filter((f) => existsSync(f));
|
|
1548
1785
|
|
|
1549
1786
|
if (existingFiles.length > 0) {
|
|
1550
1787
|
p.log.success("Swarm is already configured!");
|
|
1551
|
-
p.log.message(dim(" Found " + existingFiles.length + "/
|
|
1788
|
+
p.log.message(dim(" Found " + existingFiles.length + "/5 config files"));
|
|
1552
1789
|
|
|
1553
1790
|
const action = await p.select({
|
|
1554
1791
|
message: "What would you like to do?",
|
|
@@ -2015,11 +2252,12 @@ async function setup() {
|
|
|
2015
2252
|
stats[writeFileWithStatus(pluginPath, getPluginWrapper(), "Plugin")]++;
|
|
2016
2253
|
stats[writeFileWithStatus(commandPath, SWARM_COMMAND, "Command")]++;
|
|
2017
2254
|
|
|
2018
|
-
// Write nested agent files (swarm/planner.md, swarm/worker.md)
|
|
2255
|
+
// Write nested agent files (swarm/planner.md, swarm/worker.md, swarm/researcher.md)
|
|
2019
2256
|
// This is the format used by Task(subagent_type="swarm/worker")
|
|
2020
2257
|
p.log.step("Writing agent configuration...");
|
|
2021
2258
|
stats[writeFileWithStatus(plannerAgentPath, getPlannerAgent(coordinatorModel as string), "Planner agent")]++;
|
|
2022
2259
|
stats[writeFileWithStatus(workerAgentPath, getWorkerAgent(workerModel as string), "Worker agent")]++;
|
|
2260
|
+
stats[writeFileWithStatus(researcherAgentPath, getResearcherAgent(workerModel as string), "Researcher agent")]++;
|
|
2023
2261
|
|
|
2024
2262
|
// Clean up legacy flat agent files if they exist
|
|
2025
2263
|
if (existsSync(legacyPlannerPath) || existsSync(legacyWorkerPath)) {
|
|
@@ -2320,8 +2558,10 @@ function config() {
|
|
|
2320
2558
|
const configDir = join(homedir(), ".config", "opencode");
|
|
2321
2559
|
const pluginPath = join(configDir, "plugin", "swarm.ts");
|
|
2322
2560
|
const commandPath = join(configDir, "command", "swarm.md");
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2561
|
+
const swarmAgentDir = join(configDir, "agent", "swarm");
|
|
2562
|
+
const plannerAgentPath = join(swarmAgentDir, "planner.md");
|
|
2563
|
+
const workerAgentPath = join(swarmAgentDir, "worker.md");
|
|
2564
|
+
const researcherAgentPath = join(swarmAgentDir, "researcher.md");
|
|
2325
2565
|
const globalSkillsPath = join(configDir, "skills");
|
|
2326
2566
|
|
|
2327
2567
|
console.log(yellow(BANNER));
|
|
@@ -2333,8 +2573,9 @@ function config() {
|
|
|
2333
2573
|
const files = [
|
|
2334
2574
|
{ path: pluginPath, desc: "Plugin loader", emoji: "🔌" },
|
|
2335
2575
|
{ path: commandPath, desc: "/swarm command prompt", emoji: "📜" },
|
|
2336
|
-
{ path: plannerAgentPath, desc: "@swarm
|
|
2337
|
-
{ path: workerAgentPath, desc: "@swarm
|
|
2576
|
+
{ path: plannerAgentPath, desc: "@swarm/planner agent", emoji: "🤖" },
|
|
2577
|
+
{ path: workerAgentPath, desc: "@swarm/worker agent", emoji: "🐝" },
|
|
2578
|
+
{ path: researcherAgentPath, desc: "@swarm/researcher agent", emoji: "🔬" },
|
|
2338
2579
|
];
|
|
2339
2580
|
|
|
2340
2581
|
for (const { path, desc, emoji } of files) {
|
|
@@ -2473,6 +2714,7 @@ ${cyan("Commands:")}
|
|
|
2473
2714
|
swarm config Show paths to generated config files
|
|
2474
2715
|
swarm agents Update AGENTS.md with skill awareness
|
|
2475
2716
|
swarm migrate Migrate PGlite database to libSQL
|
|
2717
|
+
swarm log View swarm logs with filtering
|
|
2476
2718
|
swarm update Update to latest version
|
|
2477
2719
|
swarm version Show version and banner
|
|
2478
2720
|
swarm tool Execute a tool (for plugin wrapper)
|
|
@@ -2483,17 +2725,27 @@ ${cyan("Tool Execution:")}
|
|
|
2483
2725
|
swarm tool <name> Execute tool with no args
|
|
2484
2726
|
swarm tool <name> --json '<args>' Execute tool with JSON args
|
|
2485
2727
|
|
|
2728
|
+
${cyan("Log Viewing:")}
|
|
2729
|
+
swarm log Tail recent logs (last 50 lines)
|
|
2730
|
+
swarm log <module> Filter by module (e.g., compaction)
|
|
2731
|
+
swarm log --level <level> Filter by level (trace, debug, info, warn, error, fatal)
|
|
2732
|
+
swarm log --since <duration> Time filter (30s, 5m, 2h, 1d)
|
|
2733
|
+
swarm log --json Raw JSON output for jq
|
|
2734
|
+
swarm log --limit <n> Limit output to n lines (default: 50)
|
|
2735
|
+
|
|
2486
2736
|
${cyan("Usage in OpenCode:")}
|
|
2487
2737
|
/swarm "Add user authentication with OAuth"
|
|
2488
|
-
@swarm
|
|
2489
|
-
@swarm
|
|
2738
|
+
@swarm/planner "Decompose this into parallel tasks"
|
|
2739
|
+
@swarm/worker "Execute this specific subtask"
|
|
2740
|
+
@swarm/researcher "Research Next.js caching APIs"
|
|
2490
2741
|
|
|
2491
2742
|
${cyan("Customization:")}
|
|
2492
2743
|
Edit the generated files to customize behavior:
|
|
2493
|
-
${dim("~/.config/opencode/command/swarm.md")}
|
|
2494
|
-
${dim("~/.config/opencode/agent/swarm
|
|
2495
|
-
${dim("~/.config/opencode/agent/swarm
|
|
2496
|
-
${dim("~/.config/opencode/
|
|
2744
|
+
${dim("~/.config/opencode/command/swarm.md")} - /swarm command prompt
|
|
2745
|
+
${dim("~/.config/opencode/agent/swarm/planner.md")} - @swarm/planner (coordinator)
|
|
2746
|
+
${dim("~/.config/opencode/agent/swarm/worker.md")} - @swarm/worker (task executor)
|
|
2747
|
+
${dim("~/.config/opencode/agent/swarm/researcher.md")} - @swarm/researcher (read-only research)
|
|
2748
|
+
${dim("~/.config/opencode/plugin/swarm.ts")} - Plugin loader
|
|
2497
2749
|
|
|
2498
2750
|
${dim("Docs: https://github.com/joelhooks/opencode-swarm-plugin")}
|
|
2499
2751
|
`);
|
|
@@ -2810,7 +3062,7 @@ async function migrate() {
|
|
|
2810
3062
|
// Show results
|
|
2811
3063
|
const showStat = (label: string, stat: { migrated: number; skipped: number; failed: number }) => {
|
|
2812
3064
|
if (stat.migrated > 0 || stat.skipped > 0 || stat.failed > 0) {
|
|
2813
|
-
const parts = [];
|
|
3065
|
+
const parts: string[] = [];
|
|
2814
3066
|
if (stat.migrated > 0) parts.push(green(`${stat.migrated} migrated`));
|
|
2815
3067
|
if (stat.skipped > 0) parts.push(dim(`${stat.skipped} skipped`));
|
|
2816
3068
|
if (stat.failed > 0) parts.push(`\x1b[31m${stat.failed} failed\x1b[0m`);
|
|
@@ -2838,6 +3090,207 @@ async function migrate() {
|
|
|
2838
3090
|
}
|
|
2839
3091
|
}
|
|
2840
3092
|
|
|
3093
|
+
// ============================================================================
|
|
3094
|
+
// Log Command - View swarm logs with filtering
|
|
3095
|
+
// ============================================================================
|
|
3096
|
+
|
|
3097
|
+
interface LogLine {
|
|
3098
|
+
level: number;
|
|
3099
|
+
time: string;
|
|
3100
|
+
module: string;
|
|
3101
|
+
msg: string;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
function parseLogLine(line: string): LogLine | null {
|
|
3105
|
+
try {
|
|
3106
|
+
const parsed = JSON.parse(line);
|
|
3107
|
+
if (typeof parsed.level === "number" && parsed.time && parsed.msg) {
|
|
3108
|
+
return {
|
|
3109
|
+
level: parsed.level,
|
|
3110
|
+
time: parsed.time,
|
|
3111
|
+
module: parsed.module || "unknown",
|
|
3112
|
+
msg: parsed.msg,
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
} catch {
|
|
3116
|
+
// Invalid JSON
|
|
3117
|
+
}
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
function levelToName(level: number): string {
|
|
3122
|
+
if (level >= 60) return "FATAL";
|
|
3123
|
+
if (level >= 50) return "ERROR";
|
|
3124
|
+
if (level >= 40) return "WARN ";
|
|
3125
|
+
if (level >= 30) return "INFO ";
|
|
3126
|
+
if (level >= 20) return "DEBUG";
|
|
3127
|
+
return "TRACE";
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
function levelToColor(level: number): (s: string) => string {
|
|
3131
|
+
if (level >= 50) return (s: string) => `\x1b[31m${s}\x1b[0m`; // red
|
|
3132
|
+
if (level >= 40) return (s: string) => `\x1b[33m${s}\x1b[0m`; // yellow
|
|
3133
|
+
if (level >= 30) return green; // green
|
|
3134
|
+
return dim; // dim for debug/trace
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
function levelNameToNumber(name: string): number {
|
|
3138
|
+
const lower = name.toLowerCase();
|
|
3139
|
+
if (lower === "fatal") return 60;
|
|
3140
|
+
if (lower === "error") return 50;
|
|
3141
|
+
if (lower === "warn") return 40;
|
|
3142
|
+
if (lower === "info") return 30;
|
|
3143
|
+
if (lower === "debug") return 20;
|
|
3144
|
+
if (lower === "trace") return 10;
|
|
3145
|
+
return 30; // default to info
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
function parseDuration(duration: string): number | null {
|
|
3149
|
+
const match = duration.match(/^(\d+)([smhd])$/);
|
|
3150
|
+
if (!match) return null;
|
|
3151
|
+
|
|
3152
|
+
const [, num, unit] = match;
|
|
3153
|
+
const value = parseInt(num, 10);
|
|
3154
|
+
|
|
3155
|
+
const multipliers: Record<string, number> = {
|
|
3156
|
+
s: 1000,
|
|
3157
|
+
m: 60 * 1000,
|
|
3158
|
+
h: 60 * 60 * 1000,
|
|
3159
|
+
d: 24 * 60 * 60 * 1000,
|
|
3160
|
+
};
|
|
3161
|
+
|
|
3162
|
+
return value * multipliers[unit];
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
function formatLogLine(log: LogLine, useColor = true): string {
|
|
3166
|
+
const timestamp = new Date(log.time).toLocaleTimeString();
|
|
3167
|
+
const levelName = levelToName(log.level);
|
|
3168
|
+
const module = log.module.padEnd(12);
|
|
3169
|
+
const levelStr = useColor ? levelToColor(log.level)(levelName) : levelName;
|
|
3170
|
+
|
|
3171
|
+
return `${timestamp} ${levelStr} ${module} ${log.msg}`;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
function readLogFiles(dir: string): string[] {
|
|
3175
|
+
if (!existsSync(dir)) return [];
|
|
3176
|
+
|
|
3177
|
+
const allFiles = readdirSync(dir);
|
|
3178
|
+
const logFiles = allFiles
|
|
3179
|
+
.filter((f: string) => /\.\d+log$/.test(f))
|
|
3180
|
+
.sort()
|
|
3181
|
+
.map((f: string) => join(dir, f));
|
|
3182
|
+
|
|
3183
|
+
const lines: string[] = [];
|
|
3184
|
+
for (const file of logFiles) {
|
|
3185
|
+
try {
|
|
3186
|
+
const content = readFileSync(file, "utf-8");
|
|
3187
|
+
const fileLines = content.split("\n").filter((line: string) => line.trim());
|
|
3188
|
+
lines.push(...fileLines);
|
|
3189
|
+
} catch {
|
|
3190
|
+
// Skip unreadable files
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
return lines;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
async function logs() {
|
|
3198
|
+
const args = process.argv.slice(3);
|
|
3199
|
+
|
|
3200
|
+
// Parse arguments
|
|
3201
|
+
let moduleFilter: string | null = null;
|
|
3202
|
+
let levelFilter: number | null = null;
|
|
3203
|
+
let sinceMs: number | null = null;
|
|
3204
|
+
let jsonOutput = false;
|
|
3205
|
+
let limit = 50;
|
|
3206
|
+
|
|
3207
|
+
for (let i = 0; i < args.length; i++) {
|
|
3208
|
+
const arg = args[i];
|
|
3209
|
+
|
|
3210
|
+
if (arg === "--level" && i + 1 < args.length) {
|
|
3211
|
+
levelFilter = levelNameToNumber(args[++i]);
|
|
3212
|
+
} else if (arg === "--since" && i + 1 < args.length) {
|
|
3213
|
+
const duration = parseDuration(args[++i]);
|
|
3214
|
+
if (duration === null) {
|
|
3215
|
+
p.log.error(`Invalid duration format: ${args[i]}`);
|
|
3216
|
+
p.log.message(dim(" Use format: 30s, 5m, 2h, 1d"));
|
|
3217
|
+
process.exit(1);
|
|
3218
|
+
}
|
|
3219
|
+
sinceMs = duration;
|
|
3220
|
+
} else if (arg === "--json") {
|
|
3221
|
+
jsonOutput = true;
|
|
3222
|
+
} else if (arg === "--limit" && i + 1 < args.length) {
|
|
3223
|
+
limit = parseInt(args[++i], 10);
|
|
3224
|
+
if (isNaN(limit) || limit <= 0) {
|
|
3225
|
+
p.log.error(`Invalid limit: ${args[i]}`);
|
|
3226
|
+
process.exit(1);
|
|
3227
|
+
}
|
|
3228
|
+
} else if (!arg.startsWith("--")) {
|
|
3229
|
+
// Positional arg = module filter
|
|
3230
|
+
moduleFilter = arg;
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
// Read logs from ~/.config/swarm-tools/logs/
|
|
3235
|
+
const logsDir = join(homedir(), ".config", "swarm-tools", "logs");
|
|
3236
|
+
|
|
3237
|
+
if (!existsSync(logsDir)) {
|
|
3238
|
+
if (!jsonOutput) {
|
|
3239
|
+
p.log.warn("No logs directory found");
|
|
3240
|
+
p.log.message(dim(` Expected: ${logsDir}`));
|
|
3241
|
+
} else {
|
|
3242
|
+
console.log(JSON.stringify({ logs: [] }));
|
|
3243
|
+
}
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
const rawLines = readLogFiles(logsDir);
|
|
3248
|
+
|
|
3249
|
+
// Parse and filter
|
|
3250
|
+
let logs: LogLine[] = rawLines
|
|
3251
|
+
.map(parseLogLine)
|
|
3252
|
+
.filter((log): log is LogLine => log !== null);
|
|
3253
|
+
|
|
3254
|
+
// Apply filters
|
|
3255
|
+
if (moduleFilter) {
|
|
3256
|
+
logs = logs.filter((log) => log.module === moduleFilter);
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
if (levelFilter !== null) {
|
|
3260
|
+
logs = logs.filter((log) => log.level >= levelFilter);
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
if (sinceMs !== null) {
|
|
3264
|
+
const cutoffTime = Date.now() - sinceMs;
|
|
3265
|
+
logs = logs.filter((log) => new Date(log.time).getTime() >= cutoffTime);
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
// Apply limit (keep most recent)
|
|
3269
|
+
logs = logs.slice(-limit);
|
|
3270
|
+
|
|
3271
|
+
// Output
|
|
3272
|
+
if (jsonOutput) {
|
|
3273
|
+
console.log(JSON.stringify({ logs }, null, 2));
|
|
3274
|
+
} else {
|
|
3275
|
+
if (logs.length === 0) {
|
|
3276
|
+
p.log.warn("No logs found matching filters");
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
console.log(yellow(BANNER));
|
|
3281
|
+
console.log(dim(` Logs (${logs.length} entries)`));
|
|
3282
|
+
if (moduleFilter) console.log(dim(` Module: ${moduleFilter}`));
|
|
3283
|
+
if (levelFilter !== null) console.log(dim(` Level: >=${levelToName(levelFilter)}`));
|
|
3284
|
+
if (sinceMs !== null) console.log(dim(` Since: last ${args[args.indexOf("--since") + 1]}`));
|
|
3285
|
+
console.log();
|
|
3286
|
+
|
|
3287
|
+
for (const log of logs) {
|
|
3288
|
+
console.log(formatLogLine(log));
|
|
3289
|
+
}
|
|
3290
|
+
console.log();
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
|
|
2841
3294
|
// ============================================================================
|
|
2842
3295
|
// Database Info Command
|
|
2843
3296
|
// ============================================================================
|
|
@@ -2981,6 +3434,10 @@ switch (command) {
|
|
|
2981
3434
|
case "db":
|
|
2982
3435
|
await db();
|
|
2983
3436
|
break;
|
|
3437
|
+
case "log":
|
|
3438
|
+
case "logs":
|
|
3439
|
+
await logs();
|
|
3440
|
+
break;
|
|
2984
3441
|
case "version":
|
|
2985
3442
|
case "--version":
|
|
2986
3443
|
case "-v":
|
|
@@ -67,7 +67,7 @@ export declare const SWARM_DETECTION_FALLBACK = "## \uD83D\uDC1D Swarm Detection
|
|
|
67
67
|
* });
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
export declare function createCompactionHook(): (
|
|
70
|
+
export declare function createCompactionHook(): (input: {
|
|
71
71
|
sessionID: string;
|
|
72
72
|
}, output: {
|
|
73
73
|
context: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AA+BH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,k9DAwDpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0nCAiCpC,CAAC;AAuMF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,KAEhC,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC5B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAmFjB"}
|