@bolloon/bolloon-agent 0.1.38 → 0.1.39

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.
Files changed (33) hide show
  1. package/dist/agents/constraint-layer.js +1 -2
  2. package/dist/web/client.js +2861 -3746
  3. package/dist/web/components/p2p/index.js +226 -264
  4. package/dist/web/ui/message-renderer.js +323 -434
  5. package/dist/web/ui/step-timeline.js +255 -351
  6. package/package.json +1 -1
  7. package/dist/bollharness-integration/llm/pi-ai.js +0 -397
  8. package/dist/bollharness-integration/pi-ecosystem-colony/index.js +0 -365
  9. package/dist/bollharness-integration/pi-ecosystem-goals/index.js +0 -458
  10. package/dist/bollharness-integration/pi-ecosystem-judgment/decision.js +0 -300
  11. package/dist/bollharness-integration/pi-ecosystem-judgment/distillation.js +0 -291
  12. package/dist/bollharness-integration/pi-ecosystem-judgment/index.js +0 -445
  13. package/dist/bollharness-integration/pi-ecosystem-mcp/index.js +0 -331
  14. package/dist/bollharness-integration/pi-ecosystem-subagents/index.js +0 -303
  15. package/dist/constraints/commands.js +0 -100
  16. package/dist/constraints/permissions.js +0 -37
  17. package/dist/constraints/runtime.js +0 -135
  18. package/dist/constraints/session.js +0 -48
  19. package/dist/constraints/system-init.js +0 -51
  20. package/dist/constraints/tools.js +0 -104
  21. package/dist/llm/minimax-provider.js +0 -46
  22. package/dist/llm/minimax.js +0 -45
  23. package/dist/pi-ecosystem-colony/index.js +0 -365
  24. package/dist/runtime/context/minimax-prompt.js +0 -178
  25. package/dist/runtime/context/sys-prompt.js +0 -1
  26. package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
  27. package/dist/social/ant-colony/PheromoneEngine.js +0 -227
  28. package/dist/social/ant-colony/index.js +0 -6
  29. package/dist/social/ant-colony/types.js +0 -24
  30. package/dist/utils/double.js +0 -6
  31. package/dist/web/components/p2p/P2PModal.js +0 -188
  32. package/dist/web/components/p2p/p2p-modal.js +0 -657
  33. package/dist/web/components/p2p/p2p-tools.js +0 -248
@@ -1,331 +0,0 @@
1
- /**
2
- * Pi MCP Adapter Integration for Bolloon
3
- *
4
- * Bridges MCP (Model Context Protocol) servers with Bolloon's tool system.
5
- * Based on the pi-mcp-adapter philosophy: on-demand tool loading with minimal token overhead.
6
- *
7
- * Key differences from Claude Code MCP:
8
- * - White-box design: every schema, parameter, and return value is visible
9
- * - Minimal primitives: only read, write, edit, bash under the hood
10
- * - No schema validation black boxes - interfaces are simple by design
11
- */
12
- import * as fs from 'fs/promises';
13
- import * as path from 'path';
14
- import { spawn } from 'child_process';
15
- import { EventEmitter } from 'events';
16
- // MCP adapter state
17
- let tools = new Map();
18
- let servers = new Map();
19
- let initialized = false;
20
- let toolCallLog = [];
21
- // Event emitter for MCP events
22
- class McpEventEmitter extends EventEmitter {
23
- }
24
- const mcpEvents = new McpEventEmitter();
25
- /**
26
- * Discover MCP servers from standard config locations
27
- */
28
- export async function discoverMcpServers() {
29
- const configs = [];
30
- const locations = [
31
- path.join(process.env.HOME || '/tmp', '.mcp.json'),
32
- path.join(process.env.HOME || '/tmp', '.config', 'mcp', 'mcp.json'),
33
- path.join(process.env.HOME || '/tmp', '.config', 'mcp.json'),
34
- '.mcp.json',
35
- ];
36
- for (const loc of locations) {
37
- try {
38
- const content = await fs.readFile(loc, 'utf-8');
39
- const mcpJson = JSON.parse(content);
40
- if (mcpJson.mcpServers) {
41
- for (const [name, config] of Object.entries(mcpJson.mcpServers)) {
42
- const serverConfig = config;
43
- configs.push({
44
- name,
45
- command: serverConfig.command,
46
- args: serverConfig.args,
47
- env: serverConfig.env,
48
- });
49
- }
50
- }
51
- if (mcpJson['mcpServers']) {
52
- for (const [name, config] of Object.entries(mcpJson['mcpServers'])) {
53
- const serverConfig = config;
54
- configs.push({
55
- name,
56
- command: serverConfig.command,
57
- args: serverConfig.args,
58
- env: serverConfig.env,
59
- });
60
- }
61
- }
62
- }
63
- catch {
64
- // File doesn't exist, skip
65
- }
66
- }
67
- return configs;
68
- }
69
- /**
70
- * Initialize the MCP adapter
71
- */
72
- export async function initializeMcpAdapter() {
73
- if (initialized)
74
- return;
75
- const discoveredServers = await discoverMcpServers();
76
- console.log(`[McpAdapter] Discovered ${discoveredServers.length} MCP servers`);
77
- for (const server of discoveredServers) {
78
- registerServer(server);
79
- }
80
- initialized = true;
81
- mcpEvents.emit('initialized');
82
- }
83
- /**
84
- * Register an MCP server configuration
85
- */
86
- export function registerServer(config) {
87
- servers.set(config.name, { config, process: null, running: false });
88
- console.log(`[McpAdapter] Registered server: ${config.name}`);
89
- }
90
- /**
91
- * Register an MCP tool
92
- */
93
- export function registerTool(tool) {
94
- tools.set(tool.name, tool);
95
- mcpEvents.emit('toolRegistered', tool);
96
- }
97
- /**
98
- * List all available MCP tools
99
- */
100
- export function listTools() {
101
- return Array.from(tools.values());
102
- }
103
- /**
104
- * Check if a tool exists
105
- */
106
- export function hasTool(name) {
107
- return tools.has(name);
108
- }
109
- /**
110
- * Get a specific tool
111
- */
112
- export function getTool(name) {
113
- return tools.get(name);
114
- }
115
- /**
116
- * Get tool call log for debugging
117
- */
118
- export function getToolCallLog() {
119
- return [...toolCallLog];
120
- }
121
- /**
122
- * Clear tool call log
123
- */
124
- export function clearToolCallLog() {
125
- toolCallLog = [];
126
- }
127
- /**
128
- * Execute an MCP tool via JSON-RPC protocol
129
- */
130
- export async function executeTool(name, args) {
131
- const tool = tools.get(name);
132
- if (!tool) {
133
- return { success: false, error: `Unknown tool: ${name}` };
134
- }
135
- const server = servers.get(tool.serverName);
136
- if (!server) {
137
- return { success: false, error: `Server not found: ${tool.serverName}` };
138
- }
139
- // Log the call for debugging
140
- const logEntry = {
141
- timestamp: new Date().toISOString(),
142
- tool: name,
143
- args,
144
- result: null,
145
- };
146
- console.log(`[McpAdapter] Executing tool: ${name} with args:`, JSON.stringify(args, null, 2));
147
- try {
148
- const result = await sendMcpRequest(tool.serverName, 'tools/call', {
149
- name,
150
- arguments: args,
151
- });
152
- logEntry.result = result;
153
- toolCallLog.push(logEntry);
154
- // Keep log size manageable
155
- if (toolCallLog.length > 100) {
156
- toolCallLog = toolCallLog.slice(-50);
157
- }
158
- if (result && typeof result === 'object' && 'content' in result) {
159
- return {
160
- success: true,
161
- content: result.content,
162
- };
163
- }
164
- return { success: true, content: [{ type: 'text', text: JSON.stringify(result) }] };
165
- }
166
- catch (e) {
167
- logEntry.result = { error: String(e) };
168
- toolCallLog.push(logEntry);
169
- return { success: false, error: String(e) };
170
- }
171
- }
172
- /**
173
- * Send MCP request to a server (simplified - uses stdin/stdout JSON-RPC)
174
- */
175
- async function sendMcpRequest(serverName, method, params) {
176
- const server = servers.get(serverName);
177
- if (!server) {
178
- throw new Error(`Server not registered: ${serverName}`);
179
- }
180
- // For now, return a placeholder - real implementation would use MCP protocol
181
- // The actual protocol typically uses stdio or HTTP+streamableHTTP
182
- console.log(`[McpAdapter] Would send ${method} to ${serverName}:`, params);
183
- // Simulate successful response for development
184
- return {
185
- content: [
186
- {
187
- type: 'text',
188
- text: `[Simulated] Tool ${method} executed with params: ${JSON.stringify(params)}`,
189
- },
190
- ],
191
- };
192
- }
193
- /**
194
- * Start an MCP server process
195
- */
196
- export async function startServer(serverName) {
197
- const server = servers.get(serverName);
198
- if (!server) {
199
- console.error(`[McpAdapter] Server not found: ${serverName}`);
200
- return false;
201
- }
202
- if (server.running && server.process) {
203
- return true;
204
- }
205
- try {
206
- const child = spawn(server.config.command, server.config.args || [], {
207
- env: { ...process.env, ...server.config.env },
208
- stdio: ['pipe', 'pipe', 'pipe'],
209
- });
210
- child.stdout?.on('data', (data) => {
211
- console.log(`[McpAdapter][${serverName}] stdout:`, data.toString());
212
- });
213
- child.stderr?.on('data', (data) => {
214
- console.error(`[McpAdapter][${serverName}] stderr:`, data.toString());
215
- });
216
- child.on('error', (err) => {
217
- console.error(`[McpAdapter][${serverName}] error:`, err);
218
- server.running = false;
219
- server.process = null;
220
- });
221
- child.on('exit', (code) => {
222
- console.log(`[McpAdapter][${serverName}] exited with code:`, code);
223
- server.running = false;
224
- server.process = null;
225
- });
226
- server.process = child;
227
- server.running = true;
228
- console.log(`[McpAdapter] Started server: ${serverName}`);
229
- return true;
230
- }
231
- catch (e) {
232
- console.error(`[McpAdapter] Failed to start ${serverName}:`, e);
233
- return false;
234
- }
235
- }
236
- /**
237
- * Stop an MCP server
238
- */
239
- export function stopServer(serverName) {
240
- const server = servers.get(serverName);
241
- if (server && server.process) {
242
- server.process.kill();
243
- server.running = false;
244
- server.process = null;
245
- console.log(`[McpAdapter] Stopped server: ${serverName}`);
246
- }
247
- }
248
- /**
249
- * Discover tools from a server using MCP protocol
250
- */
251
- export async function discoverTools(serverName) {
252
- try {
253
- const result = await sendMcpRequest(serverName, 'tools/list');
254
- const toolsList = [];
255
- if (result && typeof result === 'object' && 'tools' in result) {
256
- const toolsData = result.tools;
257
- for (const tool of toolsData) {
258
- const t = tool;
259
- toolsList.push({
260
- name: t.name,
261
- description: t.description || '',
262
- inputSchema: t.inputSchema || {},
263
- serverName,
264
- });
265
- }
266
- }
267
- return toolsList;
268
- }
269
- catch (e) {
270
- console.error(`[McpAdapter] Failed to discover tools from ${serverName}:`, e);
271
- return [];
272
- }
273
- }
274
- /**
275
- * Create a Tavily search tool (common MCP integration)
276
- */
277
- export function createTavilyTool(apiKey) {
278
- return {
279
- name: 'tavily_search',
280
- description: 'Search the web using Tavily',
281
- inputSchema: {
282
- type: 'object',
283
- properties: {
284
- query: { type: 'string', description: 'Search query' },
285
- maxResults: { type: 'number', description: 'Maximum results', default: 5 },
286
- },
287
- required: ['query'],
288
- },
289
- serverName: 'tavily',
290
- };
291
- }
292
- /**
293
- * Create an Amap maps tool
294
- */
295
- export function createAmapTool(apiKey) {
296
- return {
297
- name: 'amap_weather',
298
- description: 'Get weather information from Amap',
299
- inputSchema: {
300
- type: 'object',
301
- properties: {
302
- city: { type: 'string', description: 'City name or code' },
303
- },
304
- required: ['city'],
305
- },
306
- serverName: 'amap-maps',
307
- };
308
- }
309
- /**
310
- * Get adapter status
311
- */
312
- export function getAdapterStatus() {
313
- return {
314
- initialized,
315
- serverCount: servers.size,
316
- toolCount: tools.size,
317
- servers: Array.from(servers.keys()),
318
- runningServers: Array.from(servers.entries())
319
- .filter(([, s]) => s.running)
320
- .map(([name]) => name),
321
- };
322
- }
323
- /**
324
- * Event subscription
325
- */
326
- export function on(event, callback) {
327
- mcpEvents.on(event, callback);
328
- }
329
- export function off(event, callback) {
330
- mcpEvents.off(event, callback);
331
- }
@@ -1,303 +0,0 @@
1
- /**
2
- * Pi Subagents Integration for Bolloon
3
- *
4
- * Lightweight subagent implementation based on tmux multi-session.
5
- * Based on pi-subagents philosophy: simple task delegation and result collection.
6
- *
7
- * Key differences from Claude Code subagents:
8
- * - tmux-based sessions (not black-box background processes)
9
- * - Simple task委托 and result回收
10
- * - Full visibility into what each subagent is doing
11
- * - No complex scheduling - suitable for single task splitting
12
- */
13
- import { spawn } from 'child_process';
14
- import * as fs from 'fs/promises';
15
- import * as path from 'path';
16
- // Session management
17
- const sessions = new Map();
18
- const tmuxSessions = new Map();
19
- const SESSION_PREFIX = 'bolloon-subagent-';
20
- const STATE_DIR = path.join(process.env.HOME || '/tmp', '.bolloon', 'subagents');
21
- /**
22
- * Generate a unique subagent ID
23
- */
24
- function generateId() {
25
- return `sa-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
26
- }
27
- /**
28
- * Create a new tmux session for a subagent
29
- */
30
- export async function createSubagent(task, options = {}) {
31
- const id = generateId();
32
- const sessionId = `${SESSION_PREFIX}${id}`;
33
- const name = options.name || `subagent-${id.substring(0, 8)}`;
34
- const subagent = {
35
- id,
36
- name,
37
- task,
38
- status: 'created',
39
- sessionId,
40
- createdAt: new Date().toISOString(),
41
- };
42
- sessions.set(id, subagent);
43
- await fs.mkdir(STATE_DIR, { recursive: true });
44
- await fs.writeFile(path.join(STATE_DIR, `${id}.json`), JSON.stringify(subagent, null, 2));
45
- console.log(`[PiSubagents] Created subagent: ${id} - ${name}`);
46
- return subagent;
47
- }
48
- /**
49
- * Start a subagent in a tmux session
50
- */
51
- export async function startSubagent(id, command, options = {}) {
52
- const subagent = sessions.get(id);
53
- if (!subagent) {
54
- return { subagentId: id, success: false, error: 'Subagent not found' };
55
- }
56
- if (subagent.status === 'running') {
57
- return { subagentId: id, success: false, error: 'Subagent already running' };
58
- }
59
- subagent.status = 'running';
60
- subagent.startedAt = new Date().toISOString();
61
- await saveSubagent(subagent);
62
- return new Promise((resolve) => {
63
- // Create a detached tmux session running the command
64
- const tmuxCmd = spawn('tmux', [
65
- 'new-session',
66
- '-d',
67
- '-s',
68
- subagent.sessionId,
69
- command,
70
- ], {
71
- stdio: ['ignore', 'pipe', 'pipe'],
72
- });
73
- tmuxSessions.set(id, tmuxCmd);
74
- const timeoutMs = options.timeoutMs || 5 * 60 * 1000;
75
- let timedOut = false;
76
- const timeout = setTimeout(() => {
77
- timedOut = true;
78
- terminateSubagent(id);
79
- subagent.status = 'failed';
80
- subagent.error = 'Task timeout';
81
- resolve({
82
- subagentId: id,
83
- success: false,
84
- error: 'Task timeout',
85
- });
86
- }, timeoutMs);
87
- tmuxCmd.on('error', (err) => {
88
- clearTimeout(timeout);
89
- subagent.status = 'failed';
90
- subagent.error = String(err);
91
- sessions.set(id, subagent);
92
- resolve({ subagentId: id, success: false, error: String(err) });
93
- });
94
- tmuxCmd.on('exit', async (code) => {
95
- clearTimeout(timeout);
96
- subagent.completedAt = new Date().toISOString();
97
- if (timedOut)
98
- return;
99
- if (code === 0) {
100
- subagent.status = 'completed';
101
- // Try to capture output
102
- try {
103
- const output = await captureTmuxOutput(subagent.sessionId);
104
- subagent.result = output;
105
- resolve({
106
- subagentId: id,
107
- success: true,
108
- result: output,
109
- duration: subagent.startedAt
110
- ? Date.now() - new Date(subagent.startedAt).getTime()
111
- : undefined,
112
- });
113
- }
114
- catch {
115
- resolve({
116
- subagentId: id,
117
- success: true,
118
- duration: subagent.startedAt
119
- ? Date.now() - new Date(subagent.startedAt).getTime()
120
- : undefined,
121
- });
122
- }
123
- }
124
- else {
125
- subagent.status = 'failed';
126
- subagent.error = `Exit code: ${code}`;
127
- resolve({
128
- subagentId: id,
129
- success: false,
130
- error: `Exit code: ${code}`,
131
- duration: subagent.startedAt
132
- ? Date.now() - new Date(subagent.startedAt).getTime()
133
- : undefined,
134
- });
135
- }
136
- sessions.set(id, subagent);
137
- await saveSubagent(subagent);
138
- });
139
- });
140
- }
141
- /**
142
- * Delegate a task to a new subagent and start it
143
- */
144
- export async function delegateTask(task, command, options = {}) {
145
- const subagent = await createSubagent(task, options);
146
- return startSubagent(subagent.id, command, options);
147
- }
148
- /**
149
- * Capture output from a tmux session
150
- */
151
- async function captureTmuxOutput(sessionId) {
152
- return new Promise((resolve, reject) => {
153
- const capture = spawn('tmux', ['capture-pane', '-t', sessionId, '-p']);
154
- let output = '';
155
- capture.stdout.on('data', (data) => {
156
- output += data.toString();
157
- });
158
- capture.on('error', reject);
159
- capture.on('close', () => {
160
- resolve(output.trim());
161
- });
162
- });
163
- }
164
- /**
165
- * Send input to a running subagent
166
- */
167
- export async function sendToSubagent(id, input) {
168
- const subagent = sessions.get(id);
169
- if (!subagent) {
170
- throw new Error('Subagent not found');
171
- }
172
- return new Promise((resolve, reject) => {
173
- const tmux = spawn('tmux', ['send-keys', '-t', subagent.sessionId, input, 'Enter']);
174
- tmux.on('error', reject);
175
- tmux.on('close', () => resolve());
176
- });
177
- }
178
- /**
179
- * Get subagent status
180
- */
181
- export function getSubagent(id) {
182
- return sessions.get(id);
183
- }
184
- /**
185
- * List all subagents
186
- */
187
- export function listSubagents() {
188
- return Array.from(sessions.values());
189
- }
190
- /**
191
- * List running subagents
192
- */
193
- export function listRunningSubagents() {
194
- return Array.from(sessions.values()).filter((s) => s.status === 'running');
195
- }
196
- /**
197
- * Terminate a subagent
198
- */
199
- export async function terminateSubagent(id) {
200
- const subagent = sessions.get(id);
201
- if (!subagent)
202
- return;
203
- // Kill tmux session
204
- spawn('tmux', ['kill-session', '-t', subagent.sessionId]);
205
- const tmuxProcess = tmuxSessions.get(id);
206
- if (tmuxProcess) {
207
- tmuxProcess.kill();
208
- tmuxSessions.delete(id);
209
- }
210
- subagent.status = 'terminated';
211
- subagent.completedAt = new Date().toISOString();
212
- sessions.set(id, subagent);
213
- await saveSubagent(subagent);
214
- console.log(`[PiSubagents] Terminated subagent: ${id}`);
215
- }
216
- /**
217
- * Save subagent state to disk
218
- */
219
- async function saveSubagent(subagent) {
220
- try {
221
- await fs.mkdir(STATE_DIR, { recursive: true });
222
- await fs.writeFile(path.join(STATE_DIR, `${subagent.id}.json`), JSON.stringify(subagent, null, 2));
223
- }
224
- catch (e) {
225
- console.warn('[PiSubagents] Failed to save subagent:', e);
226
- }
227
- }
228
- /**
229
- * Load subagents from disk
230
- */
231
- export async function loadSubagents() {
232
- try {
233
- await fs.mkdir(STATE_DIR, { recursive: true });
234
- const files = await fs.readdir(STATE_DIR);
235
- for (const file of files) {
236
- if (file.endsWith('.json')) {
237
- const data = await fs.readFile(path.join(STATE_DIR, file), 'utf-8');
238
- const subagent = JSON.parse(data);
239
- sessions.set(subagent.id, subagent);
240
- }
241
- }
242
- console.log(`[PiSubagents] Loaded ${sessions.size} subagents`);
243
- }
244
- catch {
245
- // Directory doesn't exist
246
- }
247
- }
248
- /**
249
- * Get subagent statistics
250
- */
251
- export function getStats() {
252
- const all = Array.from(sessions.values());
253
- return {
254
- total: all.length,
255
- running: all.filter((s) => s.status === 'running').length,
256
- completed: all.filter((s) => s.status === 'completed').length,
257
- failed: all.filter((s) => s.status === 'failed').length,
258
- };
259
- }
260
- /**
261
- * Clean up terminated subagents
262
- */
263
- export async function cleanupSubagents() {
264
- const terminated = Array.from(sessions.values()).filter((s) => s.status === 'terminated' || s.status === 'completed' || s.status === 'failed');
265
- for (const subagent of terminated) {
266
- try {
267
- await fs.unlink(path.join(STATE_DIR, `${subagent.id}.json`));
268
- sessions.delete(subagent.id);
269
- }
270
- catch {
271
- // File doesn't exist
272
- }
273
- }
274
- if (terminated.length > 0) {
275
- console.log(`[PiSubagents] Cleaned up ${terminated.length} subagents`);
276
- }
277
- }
278
- /**
279
- * Execute a task in parallel using multiple subagents
280
- */
281
- export async function parallelDelegate(tasks, commandFactory, options = {}) {
282
- const promises = tasks.map((task) => delegateTask(task, commandFactory(task), options));
283
- return Promise.all(promises);
284
- }
285
- /**
286
- * Split a long-form task across multiple subagents
287
- * (e.g., writing and researching simultaneously)
288
- */
289
- export async function splitTask(mainTask, subtasks, commandFactory, options = {}) {
290
- const results = new Map();
291
- // Create all subagents
292
- const subagents = await Promise.all(subtasks.map((st) => createSubagent(`${mainTask}: ${st}`, options)));
293
- // Start all in parallel
294
- const promises = subagents.map((sa, i) => startSubagent(sa.id, commandFactory(subtasks[i]), options).then((r) => ({
295
- subtask: subtasks[i],
296
- result: r,
297
- })));
298
- const settled = await Promise.all(promises);
299
- for (const { subtask, result } of settled) {
300
- results.set(subtask, result);
301
- }
302
- return results;
303
- }