tuna-agent 0.1.0 → 0.1.2

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 (68) hide show
  1. package/dist/agents/claude-code-adapter.d.ts +3 -1
  2. package/dist/agents/claude-code-adapter.js +28 -4
  3. package/dist/agents/factory.d.ts +2 -1
  4. package/dist/agents/factory.js +2 -2
  5. package/dist/browser/actions/download.d.ts +16 -0
  6. package/dist/browser/actions/download.js +39 -0
  7. package/dist/browser/actions/emulation.d.ts +53 -0
  8. package/dist/browser/actions/emulation.js +103 -0
  9. package/dist/browser/actions/evaluate.d.ts +29 -0
  10. package/dist/browser/actions/evaluate.js +92 -0
  11. package/dist/browser/actions/interaction.d.ts +79 -0
  12. package/dist/browser/actions/interaction.js +210 -0
  13. package/dist/browser/actions/keyboard.d.ts +6 -0
  14. package/dist/browser/actions/keyboard.js +9 -0
  15. package/dist/browser/actions/navigation.d.ts +40 -0
  16. package/dist/browser/actions/navigation.js +92 -0
  17. package/dist/browser/actions/wait.d.ts +12 -0
  18. package/dist/browser/actions/wait.js +33 -0
  19. package/dist/browser/browser.d.ts +722 -0
  20. package/dist/browser/browser.js +1066 -0
  21. package/dist/browser/capture/activity.d.ts +22 -0
  22. package/dist/browser/capture/activity.js +39 -0
  23. package/dist/browser/capture/pdf.d.ts +6 -0
  24. package/dist/browser/capture/pdf.js +6 -0
  25. package/dist/browser/capture/response.d.ts +8 -0
  26. package/dist/browser/capture/response.js +28 -0
  27. package/dist/browser/capture/screenshot.d.ts +30 -0
  28. package/dist/browser/capture/screenshot.js +72 -0
  29. package/dist/browser/capture/trace.d.ts +13 -0
  30. package/dist/browser/capture/trace.js +19 -0
  31. package/dist/browser/chrome-launcher.d.ts +8 -0
  32. package/dist/browser/chrome-launcher.js +543 -0
  33. package/dist/browser/connection.d.ts +42 -0
  34. package/dist/browser/connection.js +359 -0
  35. package/dist/browser/index.d.ts +6 -0
  36. package/dist/browser/index.js +3 -0
  37. package/dist/browser/security.d.ts +51 -0
  38. package/dist/browser/security.js +357 -0
  39. package/dist/browser/snapshot/ai-snapshot.d.ts +12 -0
  40. package/dist/browser/snapshot/ai-snapshot.js +47 -0
  41. package/dist/browser/snapshot/aria-snapshot.d.ts +26 -0
  42. package/dist/browser/snapshot/aria-snapshot.js +121 -0
  43. package/dist/browser/snapshot/ref-map.d.ts +31 -0
  44. package/dist/browser/snapshot/ref-map.js +250 -0
  45. package/dist/browser/storage/index.d.ts +36 -0
  46. package/dist/browser/storage/index.js +65 -0
  47. package/dist/browser/types.d.ts +429 -0
  48. package/dist/browser/types.js +2 -0
  49. package/dist/cli/commands/extension.d.ts +10 -0
  50. package/dist/cli/commands/extension.js +86 -0
  51. package/dist/cli/index.js +12 -0
  52. package/dist/daemon/extension-handlers.d.ts +63 -0
  53. package/dist/daemon/extension-handlers.js +630 -0
  54. package/dist/daemon/index.js +173 -44
  55. package/dist/daemon/ws-client.d.ts +28 -8
  56. package/dist/daemon/ws-client.js +68 -62
  57. package/dist/mcp/browser-server.d.ts +11 -0
  58. package/dist/mcp/browser-server.js +467 -0
  59. package/dist/mcp/knowledge-server.d.ts +11 -0
  60. package/dist/mcp/knowledge-server.js +263 -0
  61. package/dist/mcp/setup.d.ts +20 -0
  62. package/dist/mcp/setup.js +94 -0
  63. package/dist/types/index.d.ts +2 -0
  64. package/dist/utils/claude-cli.d.ts +2 -0
  65. package/dist/utils/claude-cli.js +29 -9
  66. package/dist/utils/message-schemas.d.ts +4 -1
  67. package/dist/utils/message-schemas.js +6 -1
  68. package/package.json +2 -1
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Knowledge MCP Server for Tuna Agent
4
+ *
5
+ * Stdio-based MCP server that exposes knowledge tools to Claude Code.
6
+ * Communicates with the Tuna API using agent token auth.
7
+ *
8
+ * Usage:
9
+ * node knowledge-server.js --api-url https://api.tuna.ai --token xxx --agent-id abc123
10
+ */
11
+ function parseArgs() {
12
+ const args = process.argv.slice(2);
13
+ let apiUrl = '';
14
+ let token = '';
15
+ let agentId = '';
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === '--api-url' && args[i + 1])
18
+ apiUrl = args[++i];
19
+ else if (args[i] === '--token' && args[i + 1])
20
+ token = args[++i];
21
+ else if (args[i] === '--agent-id' && args[i + 1])
22
+ agentId = args[++i];
23
+ }
24
+ if (!apiUrl || !token || !agentId) {
25
+ process.stderr.write('Usage: knowledge-server --api-url URL --token TOKEN --agent-id ID\n');
26
+ process.exit(1);
27
+ }
28
+ return { apiUrl, token, agentId };
29
+ }
30
+ // ===== API Client =====
31
+ async function apiCall(config, method, path, body) {
32
+ const url = `${config.apiUrl}${path}`;
33
+ const headers = {
34
+ 'Authorization': `Bearer ${config.token}`,
35
+ 'Content-Type': 'application/json',
36
+ };
37
+ const options = { method, headers };
38
+ if (body)
39
+ options.body = JSON.stringify(body);
40
+ const res = await fetch(url, options);
41
+ const json = await res.json();
42
+ if (!res.ok || (json.code && json.code >= 400)) {
43
+ throw new Error(json.message || `API error: ${res.status}`);
44
+ }
45
+ return json.data;
46
+ }
47
+ function sendResponse(res) {
48
+ // Claude Code uses newline-delimited JSON (not Content-Length framing)
49
+ process.stdout.write(JSON.stringify(res) + '\n');
50
+ }
51
+ function sendResult(id, result) {
52
+ sendResponse({ jsonrpc: '2.0', id: id ?? null, result });
53
+ }
54
+ function sendError(id, code, message) {
55
+ sendResponse({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
56
+ }
57
+ // ===== Tool Definitions =====
58
+ const TOOLS = [
59
+ {
60
+ name: 'list_knowledge',
61
+ description: 'List knowledge items accessible to this agent. Can list root items or items inside a specific folder.',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ parent_id: { type: 'string', description: 'Folder ID to list children of. Omit to list root-level items.' },
66
+ },
67
+ },
68
+ },
69
+ {
70
+ name: 'read_knowledge',
71
+ description: 'Read the full content of a knowledge document by its ID. Returns the markdown content.',
72
+ inputSchema: {
73
+ type: 'object',
74
+ properties: {
75
+ knowledge_id: { type: 'string', description: 'The ID of the knowledge document to read' },
76
+ },
77
+ required: ['knowledge_id'],
78
+ },
79
+ },
80
+ {
81
+ name: 'create_knowledge',
82
+ description: 'Create a new knowledge document or folder. Content should be in markdown format for documents.',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ name: { type: 'string', description: 'Name/title of the knowledge item' },
87
+ content: { type: 'string', description: 'Markdown content (required for documents, omit for folders)' },
88
+ description: { type: 'string', description: 'Short description of what this item contains' },
89
+ kind: { type: 'string', enum: ['document', 'folder'], description: 'Type of knowledge item. Default: document' },
90
+ parent_id: { type: 'string', description: 'Parent folder ID to create inside. Omit for root level.' },
91
+ },
92
+ required: ['name'],
93
+ },
94
+ },
95
+ {
96
+ name: 'update_knowledge',
97
+ description: 'Update an existing knowledge document. Can update name, description, and/or content.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ knowledge_id: { type: 'string', description: 'The ID of the knowledge document to update' },
102
+ name: { type: 'string', description: 'New name (optional)' },
103
+ content: { type: 'string', description: 'New markdown content (optional)' },
104
+ description: { type: 'string', description: 'New description (optional)' },
105
+ },
106
+ required: ['knowledge_id'],
107
+ },
108
+ },
109
+ ];
110
+ // ===== Request Handler =====
111
+ async function handleRequest(config, req) {
112
+ try {
113
+ switch (req.method) {
114
+ case 'initialize':
115
+ sendResult(req.id, {
116
+ protocolVersion: '2024-11-05',
117
+ capabilities: { tools: {} },
118
+ serverInfo: { name: 'tuna-knowledge', version: '1.0.0' },
119
+ });
120
+ break;
121
+ case 'notifications/initialized':
122
+ // No response needed for notifications
123
+ break;
124
+ case 'tools/list':
125
+ sendResult(req.id, { tools: TOOLS });
126
+ break;
127
+ case 'tools/call': {
128
+ const toolName = req.params?.name ?? '';
129
+ const args = req.params?.arguments ?? {};
130
+ const result = await handleToolCall(config, toolName, args);
131
+ sendResult(req.id, result);
132
+ break;
133
+ }
134
+ case 'ping':
135
+ sendResult(req.id, {});
136
+ break;
137
+ default:
138
+ if (req.id !== undefined) {
139
+ sendError(req.id, -32601, `Method not found: ${req.method}`);
140
+ }
141
+ }
142
+ }
143
+ catch (err) {
144
+ const message = err instanceof Error ? err.message : String(err);
145
+ if (req.id !== undefined) {
146
+ sendError(req.id, -32603, message);
147
+ }
148
+ }
149
+ }
150
+ async function handleToolCall(config, toolName, args) {
151
+ try {
152
+ switch (toolName) {
153
+ case 'list_knowledge': {
154
+ let url = `/agent-knowledge?agent_id=${config.agentId}`;
155
+ if (args.parent_id)
156
+ url += `&parent_id=${args.parent_id}`;
157
+ const data = await apiCall(config, 'GET', url);
158
+ const items = data.items || [];
159
+ if (items.length === 0) {
160
+ return { content: [{ type: 'text', text: 'No knowledge items found.' }] };
161
+ }
162
+ const listing = items.map((k) => {
163
+ const icon = k.kind === 'folder' ? '📁' : '📄';
164
+ const meta = k.kind === 'folder'
165
+ ? `${k.children_count || 0} items`
166
+ : `${k.file_size} bytes`;
167
+ return `- ${icon} **${k.name}** (ID: ${k._id}) [${k.kind}]\n ${k.description || 'No description'}\n ${meta} | Updated: ${k.updated_at}`;
168
+ }).join('\n\n');
169
+ return { content: [{ type: 'text', text: `Found ${items.length} knowledge item(s):\n\n${listing}` }] };
170
+ }
171
+ case 'read_knowledge': {
172
+ if (!args.knowledge_id) {
173
+ return { content: [{ type: 'text', text: 'Error: knowledge_id is required' }], isError: true };
174
+ }
175
+ const data = await apiCall(config, 'GET', `/agent-knowledge/${args.knowledge_id}`);
176
+ return {
177
+ content: [{
178
+ type: 'text',
179
+ text: `# ${data.name}\n\n${data.description ? `> ${data.description}\n\n` : ''}${data.content}`,
180
+ }],
181
+ };
182
+ }
183
+ case 'create_knowledge': {
184
+ const isFolder = args.kind === 'folder';
185
+ if (!args.name) {
186
+ return { content: [{ type: 'text', text: 'Error: name is required' }], isError: true };
187
+ }
188
+ if (!isFolder && !args.content) {
189
+ return { content: [{ type: 'text', text: 'Error: content is required for documents' }], isError: true };
190
+ }
191
+ const body = {
192
+ name: args.name,
193
+ agent_id: config.agentId,
194
+ };
195
+ if (args.kind)
196
+ body.kind = args.kind;
197
+ if (args.content)
198
+ body.content = args.content;
199
+ if (args.description)
200
+ body.description = args.description;
201
+ if (args.parent_id)
202
+ body.parent_id = args.parent_id;
203
+ const data = await apiCall(config, 'POST', '/agent-knowledge', body);
204
+ const label = isFolder ? 'Folder' : 'Knowledge';
205
+ return { content: [{ type: 'text', text: `${label} "${data.name}" created (ID: ${data._id})` }] };
206
+ }
207
+ case 'update_knowledge': {
208
+ if (!args.knowledge_id) {
209
+ return { content: [{ type: 'text', text: 'Error: knowledge_id is required' }], isError: true };
210
+ }
211
+ const body = {};
212
+ if (args.name)
213
+ body.name = args.name;
214
+ if (args.content)
215
+ body.content = args.content;
216
+ if (args.description)
217
+ body.description = args.description;
218
+ const data = await apiCall(config, 'PUT', `/agent-knowledge/${args.knowledge_id}`, body);
219
+ return { content: [{ type: 'text', text: `Knowledge "${data.name}" updated (ID: ${data._id})` }] };
220
+ }
221
+ default:
222
+ return { content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], isError: true };
223
+ }
224
+ }
225
+ catch (err) {
226
+ const message = err instanceof Error ? err.message : String(err);
227
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
228
+ }
229
+ }
230
+ // ===== Stdio Transport =====
231
+ function startServer(config) {
232
+ process.stderr.write(`[knowledge-mcp] Starting with agent ${config.agentId}\n`);
233
+ let buffer = '';
234
+ process.stdin.setEncoding('utf-8');
235
+ process.stdin.on('data', (chunk) => {
236
+ buffer += chunk;
237
+ // Claude Code sends newline-delimited JSON (one JSON object per line)
238
+ const lines = buffer.split('\n');
239
+ buffer = lines.pop() ?? ''; // Keep incomplete last line in buffer
240
+ for (const line of lines) {
241
+ const trimmed = line.trim();
242
+ if (!trimmed)
243
+ continue;
244
+ try {
245
+ const req = JSON.parse(trimmed);
246
+ handleRequest(config, req).catch((err) => {
247
+ process.stderr.write(`[knowledge-mcp] Error handling ${req.method}: ${err}\n`);
248
+ });
249
+ }
250
+ catch {
251
+ process.stderr.write(`[knowledge-mcp] Failed to parse JSON: ${trimmed.slice(0, 100)}\n`);
252
+ }
253
+ }
254
+ });
255
+ process.stdin.on('end', () => {
256
+ process.stderr.write('[knowledge-mcp] stdin closed, shutting down\n');
257
+ process.exit(0);
258
+ });
259
+ }
260
+ // ===== Main =====
261
+ const config = parseArgs();
262
+ startServer(config);
263
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { AgentConfig } from '../types/index.js';
2
+ /**
3
+ * Generate MCP server config file for Claude Code.
4
+ * This file is auto-detected by runClaude and passed via --mcp-config.
5
+ */
6
+ export declare function setupMcpConfig(config: AgentConfig): string;
7
+ /**
8
+ * Get the MCP config path if it exists.
9
+ */
10
+ export declare function getMcpConfigPath(): string | undefined;
11
+ /**
12
+ * Write MCP server config into agent's folder .mcp.json.
13
+ * Claude Code reads .mcp.json automatically for project-level MCPs.
14
+ * Note: .claude/settings.json mcpServers is NOT reliably read by Claude Code CLI.
15
+ */
16
+ export declare function writeAgentFolderMcpConfig(agentFolderPath: string, config: AgentConfig): void;
17
+ /**
18
+ * Clean up MCP config file.
19
+ */
20
+ export declare function cleanupMcpConfig(): void;
@@ -0,0 +1,94 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ const MCP_CONFIG_DIR = path.join(process.env.HOME || '', '.tuna-agent');
7
+ const MCP_CONFIG_PATH = path.join(MCP_CONFIG_DIR, 'mcp-config.json');
8
+ /**
9
+ * Generate MCP server config file for Claude Code.
10
+ * This file is auto-detected by runClaude and passed via --mcp-config.
11
+ */
12
+ export function setupMcpConfig(config) {
13
+ const knowledgeServerPath = path.join(__dirname, 'knowledge-server.js');
14
+ const browserServerPath = path.join(__dirname, 'browser-server.js');
15
+ const mcpConfig = {
16
+ mcpServers: {
17
+ 'tuna-knowledge': {
18
+ command: process.execPath, // full path to current node binary
19
+ args: [
20
+ knowledgeServerPath,
21
+ '--api-url', config.apiUrl,
22
+ '--token', config.agentToken,
23
+ '--agent-id', config.agentId,
24
+ ],
25
+ },
26
+ 'tuna-browser': {
27
+ command: process.execPath,
28
+ args: [browserServerPath, '--user-data-dir', path.join(process.env.HOME || '', '.config', 'tuna-browser', 'chrome-profile')],
29
+ },
30
+ },
31
+ };
32
+ if (!fs.existsSync(MCP_CONFIG_DIR)) {
33
+ fs.mkdirSync(MCP_CONFIG_DIR, { recursive: true });
34
+ }
35
+ fs.writeFileSync(MCP_CONFIG_PATH, JSON.stringify(mcpConfig, null, 2));
36
+ console.log(`[MCP] Config written to ${MCP_CONFIG_PATH}`);
37
+ return MCP_CONFIG_PATH;
38
+ }
39
+ /**
40
+ * Get the MCP config path if it exists.
41
+ */
42
+ export function getMcpConfigPath() {
43
+ if (fs.existsSync(MCP_CONFIG_PATH)) {
44
+ return MCP_CONFIG_PATH;
45
+ }
46
+ return undefined;
47
+ }
48
+ /**
49
+ * Write MCP server config into agent's folder .mcp.json.
50
+ * Claude Code reads .mcp.json automatically for project-level MCPs.
51
+ * Note: .claude/settings.json mcpServers is NOT reliably read by Claude Code CLI.
52
+ */
53
+ export function writeAgentFolderMcpConfig(agentFolderPath, config) {
54
+ const knowledgeServerPath = path.join(__dirname, 'knowledge-server.js');
55
+ const browserServerPath = path.join(__dirname, 'browser-server.js');
56
+ try {
57
+ const mcpJsonPath = path.join(agentFolderPath, '.mcp.json');
58
+ // Read existing .mcp.json to preserve other servers (e.g. playwright)
59
+ let existing = {};
60
+ if (fs.existsSync(mcpJsonPath)) {
61
+ existing = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
62
+ }
63
+ const existingServers = existing.mcpServers ?? {};
64
+ existing.mcpServers = {
65
+ ...existingServers,
66
+ 'tuna-knowledge': {
67
+ command: process.execPath,
68
+ args: [
69
+ knowledgeServerPath,
70
+ '--api-url', config.apiUrl,
71
+ '--token', config.agentToken,
72
+ '--agent-id', config.agentId,
73
+ ],
74
+ },
75
+ 'tuna-browser': {
76
+ command: process.execPath,
77
+ args: [browserServerPath, '--user-data-dir', path.join(process.env.HOME || '', '.config', 'tuna-browser', 'chrome-profile')],
78
+ },
79
+ };
80
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2));
81
+ console.log(`[MCP] Agent folder .mcp.json written to ${mcpJsonPath}`);
82
+ }
83
+ catch (err) {
84
+ console.warn(`[MCP] Failed to write agent folder .mcp.json:`, err);
85
+ }
86
+ }
87
+ /**
88
+ * Clean up MCP config file.
89
+ */
90
+ export function cleanupMcpConfig() {
91
+ if (fs.existsSync(MCP_CONFIG_PATH)) {
92
+ fs.unlinkSync(MCP_CONFIG_PATH);
93
+ }
94
+ }
@@ -10,6 +10,7 @@ export interface AgentConfig {
10
10
  agentConfig?: {
11
11
  openclawGatewayUrl?: string;
12
12
  };
13
+ extensionCodes?: string[];
13
14
  }
14
15
  export interface AgentCapabilities {
15
16
  supports_teams: boolean;
@@ -41,6 +42,7 @@ export interface TaskAssignment {
41
42
  confirmBeforeEdit?: boolean;
42
43
  attachments?: ChatAttachment[];
43
44
  source?: 'manual' | 'skill' | 'scheduled' | 'workflow' | 'api';
45
+ agentId?: string;
44
46
  }
45
47
  export interface ConnectResponse {
46
48
  machine_id: string;
@@ -20,6 +20,8 @@ export interface ClaudeCliOptions {
20
20
  onPermissionRequest?: (tool: string, detail: string) => Promise<boolean>;
21
21
  /** Local file paths to pass as input (e.g., images) via --input-file flags */
22
22
  inputFiles?: string[];
23
+ /** Path to MCP server config JSON file (passed via --mcp-config) */
24
+ mcpConfigPath?: string;
23
25
  }
24
26
  export interface ClaudeCliResult {
25
27
  result: string;
@@ -1,5 +1,6 @@
1
1
  import { spawn, execSync } from 'child_process';
2
2
  import { StringDecoder } from 'string_decoder';
3
+ import { getMcpConfigPath } from '../mcp/setup.js';
3
4
  /** Cached absolute path to `claude` binary, resolved once at first use */
4
5
  let _claudeBinPath = null;
5
6
  function getClaudeBinPath() {
@@ -101,6 +102,11 @@ export function runClaude(options) {
101
102
  args.push('--tools', ''); // skip built-in tools
102
103
  args.push('--disable-slash-commands'); // skip skills/plugins
103
104
  }
105
+ // Auto-detect MCP config for knowledge server (skip if lightweight mode)
106
+ const mcpConfig = options.mcpConfigPath || (!options.lightweight ? getMcpConfigPath() : undefined);
107
+ if (mcpConfig) {
108
+ args.push('--mcp-config', mcpConfig);
109
+ }
104
110
  if (options.permissionMode) {
105
111
  args.push('--permission-mode', options.permissionMode);
106
112
  }
@@ -111,6 +117,7 @@ export function runClaude(options) {
111
117
  }
112
118
  }
113
119
  const useInteractiveStdin = !!options.permissionMode && !!options.onPermissionRequest;
120
+ console.log(`[claude-cli] Spawning with args: ${args.filter(a => !a.startsWith('sk-') && a.length < 200).join(' ')}`);
114
121
  const claudeBin = getClaudeBinPath();
115
122
  // Ensure PATH includes common bin dirs so shebang `#!/usr/bin/env node` resolves
116
123
  const spawnPath = [
@@ -120,18 +127,27 @@ export function runClaude(options) {
120
127
  `${process.env.HOME}/.local/bin`,
121
128
  `${process.env.HOME}/.nvm/versions/node/v20.10.0/bin`,
122
129
  ].filter(Boolean).join(':');
130
+ // Build env: inherit process.env but strip Claude Code session markers
131
+ // so the spawned claude process is not blocked by nested-session detection.
132
+ const spawnEnv = {};
133
+ for (const [k, v] of Object.entries(process.env)) {
134
+ if (v !== undefined)
135
+ spawnEnv[k] = v;
136
+ }
137
+ // Remove nested-session markers
138
+ delete spawnEnv['CLAUDECODE'];
139
+ delete spawnEnv['CLAUDE_CODE_ENTRYPOINT'];
140
+ delete spawnEnv['CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING'];
141
+ spawnEnv['HOME'] = process.env.HOME || '';
142
+ spawnEnv['PATH'] = spawnPath;
143
+ if (options.agentTeam) {
144
+ spawnEnv['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1';
145
+ spawnEnv['CLAUDE_CODE_TEAMMATE_MODE'] = 'in-process';
146
+ }
123
147
  const proc = spawn(claudeBin, args, {
124
148
  cwd: options.cwd,
125
149
  stdio: [useInteractiveStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],
126
- env: {
127
- ...process.env,
128
- HOME: process.env.HOME || '',
129
- PATH: spawnPath,
130
- ...(options.agentTeam ? {
131
- CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
132
- CLAUDE_CODE_TEAMMATE_MODE: 'in-process',
133
- } : {}),
134
- },
150
+ env: spawnEnv,
135
151
  });
136
152
  // 30-minute timeout by default
137
153
  const timeoutMs = options.timeoutMs ?? 30 * 60 * 1000;
@@ -198,6 +214,10 @@ export function runClaude(options) {
198
214
  proc.stderr.on('data', (chunk) => {
199
215
  const text = stderrDecoder.write(chunk);
200
216
  stderr += text;
217
+ // Log stderr for debugging (MCP server startup errors, etc.)
218
+ if (text.trim()) {
219
+ console.log(`[claude-cli] stderr: ${text.trim().substring(0, 300)}`);
220
+ }
201
221
  // Detect permission prompts from Claude Code on stderr
202
222
  if (useInteractiveStdin) {
203
223
  handlePermissionPrompt(text, proc, options.onPermissionRequest);
@@ -29,10 +29,13 @@ export declare const CommandMessageSchema: z.ZodObject<{
29
29
  type: z.ZodLiteral<"command">;
30
30
  command: z.ZodString;
31
31
  workspace_path: z.ZodOptional<z.ZodString>;
32
+ agent_folders: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ agent_id: z.ZodOptional<z.ZodString>;
34
+ folder_path: z.ZodOptional<z.ZodString>;
32
35
  skill_id: z.ZodOptional<z.ZodString>;
33
36
  source_path: z.ZodOptional<z.ZodString>;
34
37
  skill_name: z.ZodOptional<z.ZodString>;
35
- }, z.core.$strip>;
38
+ }, z.core.$loose>;
36
39
  export declare const TaskCancelledSchema: z.ZodObject<{
37
40
  type: z.ZodLiteral<"task_cancelled">;
38
41
  taskId: z.ZodString;
@@ -21,11 +21,16 @@ export const CommandMessageSchema = z.object({
21
21
  type: z.literal('command'),
22
22
  command: z.string().min(1),
23
23
  workspace_path: z.string().optional(),
24
+ // For scan_workspace command
25
+ agent_folders: z.array(z.string()).optional(),
26
+ // For rescan_agent_skills command
27
+ agent_id: z.string().optional(),
28
+ folder_path: z.string().optional(),
24
29
  // For analyze_skill command
25
30
  skill_id: z.string().optional(),
26
31
  source_path: z.string().optional(),
27
32
  skill_name: z.string().optional(),
28
- });
33
+ }).passthrough();
29
34
  export const TaskCancelledSchema = z.object({
30
35
  type: z.literal('task_cancelled'),
31
36
  taskId: z.string().min(1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"
@@ -16,6 +16,7 @@
16
16
  "dependencies": {
17
17
  "chalk": "^5.4.0",
18
18
  "commander": "^13.0.0",
19
+ "playwright-core": "^1.58.2",
19
20
  "ws": "^8.18.0",
20
21
  "zod": "^4.3.6"
21
22
  },