@openagents-org/agent-launcher 0.2.128 → 0.2.131
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/package.json +1 -1
- package/registry.json +19 -22
- package/src/adapters/base.js +228 -9
- package/src/adapters/claude.js +30 -3
- package/src/adapters/cursor.js +618 -9
- package/src/adapters/utils.js +34 -10
- package/src/adapters/workspace-prompt.js +271 -83
- package/src/github-client.js +165 -0
- package/src/index.js +2 -1
- package/src/installer.js +151 -19
- package/src/mcp-server.js +42 -14
- package/src/paths.js +31 -3
- package/src/registry.js +4 -4
- package/src/tui.js +1 -16
- package/src/workspace-client.js +59 -6
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -209,49 +209,46 @@
|
|
|
209
209
|
"order": 4,
|
|
210
210
|
"builtin": true,
|
|
211
211
|
"install": {
|
|
212
|
-
"binary": "cursor",
|
|
213
|
-
"
|
|
214
|
-
|
|
215
|
-
"requires": [
|
|
216
|
-
null
|
|
212
|
+
"binary": "cursor-agent",
|
|
213
|
+
"binary_aliases": [
|
|
214
|
+
"agent"
|
|
217
215
|
],
|
|
216
|
+
"verify": "cursor-agent --version 2>/dev/null | head -1",
|
|
217
|
+
"verify_win": "cursor-agent --version 2>nul",
|
|
218
|
+
"requires": [],
|
|
218
219
|
"macos": "curl https://cursor.com/install -fsSL | bash",
|
|
219
220
|
"linux": "curl https://cursor.com/install -fsSL | bash",
|
|
220
|
-
"windows": "
|
|
221
|
+
"windows": "\"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoProfile -ExecutionPolicy Bypass -Command \"Invoke-RestMethod -UseBasicParsing 'https://cursor.com/install?win32=true' | Invoke-Expression\""
|
|
221
222
|
},
|
|
222
223
|
"adapter": {
|
|
223
224
|
"module": "openagents.adapters.cursor",
|
|
224
225
|
"class": "CursorAdapter"
|
|
225
226
|
},
|
|
226
227
|
"launch": {
|
|
227
|
-
"args": [
|
|
228
|
-
null
|
|
229
|
-
]
|
|
228
|
+
"args": []
|
|
230
229
|
},
|
|
231
230
|
"env_config": [
|
|
232
231
|
{
|
|
233
|
-
"name": "
|
|
234
|
-
"description": "API key for
|
|
235
|
-
"required":
|
|
232
|
+
"name": "CURSOR_API_KEY",
|
|
233
|
+
"description": "Cursor API key for CLI authentication",
|
|
234
|
+
"required": false,
|
|
236
235
|
"password": true
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"name": "CURSOR_MODEL",
|
|
239
|
+
"description": "Model to use (e.g. claude-sonnet-4-6, gpt-4o)",
|
|
240
|
+
"required": false
|
|
237
241
|
}
|
|
238
242
|
],
|
|
239
243
|
"check_ready": {
|
|
240
|
-
"
|
|
241
|
-
|
|
242
|
-
],
|
|
243
|
-
"saved_env_key": "OPENAI_API_KEY",
|
|
244
|
-
"not_ready_message": "No API key \u2014 press e to configure"
|
|
244
|
+
"binary": "agent",
|
|
245
|
+
"not_ready_message": "Cursor CLI not found \u2014 install with: curl https://cursor.com/install -fsSL | bash"
|
|
245
246
|
},
|
|
246
247
|
"resolve_env": {
|
|
247
248
|
"rules": [
|
|
248
249
|
{
|
|
249
250
|
"from": "LLM_API_KEY",
|
|
250
|
-
"to": "
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
"from": "LLM_BASE_URL",
|
|
254
|
-
"to": "OPENAI_BASE_URL"
|
|
251
|
+
"to": "CURSOR_API_KEY"
|
|
255
252
|
},
|
|
256
253
|
{
|
|
257
254
|
"from": "LLM_MODEL",
|
package/src/adapters/base.js
CHANGED
|
@@ -42,6 +42,7 @@ class BaseAdapter {
|
|
|
42
42
|
this.workingDir = workingDir || undefined;
|
|
43
43
|
this.client = new WorkspaceClient(this.endpoint);
|
|
44
44
|
this._lastEventId = null;
|
|
45
|
+
this._lastToolResultId = null;
|
|
45
46
|
this._running = false;
|
|
46
47
|
this._sessionId = null; // issued by server on /v1/join; used to prove liveness
|
|
47
48
|
this._processedIds = new Set();
|
|
@@ -52,6 +53,11 @@ class BaseAdapter {
|
|
|
52
53
|
// Per-channel task tracking for parallel execution
|
|
53
54
|
this._channelBusy = new Set();
|
|
54
55
|
this._channelQueues = {};
|
|
56
|
+
// Cached workspace.browser_enabled. Populated lazily on first read so we
|
|
57
|
+
// don't pay an HTTP roundtrip per message — adapters that toggle the
|
|
58
|
+
// workspace flag must reconnect/restart to pick up the change (matches
|
|
59
|
+
// the Python adapter behavior in workspace_prompt.py).
|
|
60
|
+
this._browserEnabledCache = null;
|
|
55
61
|
// Wall-clock timestamp of adapter init, used by the `status` control
|
|
56
62
|
// action to report uptime back to the channel. Reset on reinstantiation
|
|
57
63
|
// (e.g. after a `restart` IPC bounce) so uptime tracks "time since last
|
|
@@ -209,6 +215,8 @@ class BaseAdapter {
|
|
|
209
215
|
async _onControlAction(action, payload) {
|
|
210
216
|
if (action === 'status') {
|
|
211
217
|
await this._postStatusReport(payload);
|
|
218
|
+
} else if (action === 'routines') {
|
|
219
|
+
await this._postRoutinesReport(payload);
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
|
|
@@ -261,6 +269,74 @@ class BaseAdapter {
|
|
|
261
269
|
}
|
|
262
270
|
}
|
|
263
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Post a markdown table of the agent's active routines back to the
|
|
274
|
+
* requesting channel. Used by the `/routines` slash command. Each agent
|
|
275
|
+
* reports only routines it owns (created_by === openagents:<agentName>)
|
|
276
|
+
* so the user sees a clear "my routines" view per agent, mirroring how
|
|
277
|
+
* /status reports per-agent uptime.
|
|
278
|
+
*/
|
|
279
|
+
async _postRoutinesReport(payload) {
|
|
280
|
+
const channel = (payload && typeof payload === 'object') ? payload.channel : null;
|
|
281
|
+
if (!channel) return;
|
|
282
|
+
|
|
283
|
+
let routines = [];
|
|
284
|
+
try {
|
|
285
|
+
const data = await this.client.listRoutines(this.workspaceId, channel, this.token);
|
|
286
|
+
// Accept both the canonical `openagents:<name>` source and the bare
|
|
287
|
+
// `<name>` form. Agents that follow the workspace prompt verbatim
|
|
288
|
+
// produce the prefixed form, but some agents send the bare name when
|
|
289
|
+
// they construct the POST body themselves.
|
|
290
|
+
const prefixed = `openagents:${this.agentName}`;
|
|
291
|
+
routines = ((data && data.routines) || []).filter(
|
|
292
|
+
(r) => r.created_by === prefixed || r.created_by === this.agentName,
|
|
293
|
+
);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
this._log(`Routines: failed to list: ${e && e.message ? e.message : e}`);
|
|
296
|
+
try {
|
|
297
|
+
await this.client.sendMessage(
|
|
298
|
+
this.workspaceId, channel, this.token,
|
|
299
|
+
`**Routines for \`${this.agentName}\`**\n\n_Failed to fetch routines._`,
|
|
300
|
+
{ senderType: 'agent', senderName: this.agentName, messageType: 'chat', sessionId: this._sessionId },
|
|
301
|
+
);
|
|
302
|
+
} catch {}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let content;
|
|
307
|
+
if (!routines.length) {
|
|
308
|
+
content = `**Routines for \`${this.agentName}\`**\n\n_No active routines._`;
|
|
309
|
+
} else {
|
|
310
|
+
const rows = routines.map((r) => {
|
|
311
|
+
const schedule = (r.schedule_interval_minutes != null)
|
|
312
|
+
? `every ${r.schedule_interval_minutes} min`
|
|
313
|
+
: `${String(r.schedule_hour ?? 0).padStart(2, '0')}:${String(r.schedule_minute ?? 0).padStart(2, '0')} UTC` +
|
|
314
|
+
(r.schedule_days ? ` (days [${r.schedule_days.join(',')}])` : ' daily');
|
|
315
|
+
const next = r.next_fires_at || '—';
|
|
316
|
+
const name = String(r.name || '').replace(/\|/g, '\\|');
|
|
317
|
+
const id = String(r.id || '').slice(0, 8);
|
|
318
|
+
return `| \`${id}\` | ${name} | ${schedule} | ${next} |`;
|
|
319
|
+
});
|
|
320
|
+
content =
|
|
321
|
+
`**Routines for \`${this.agentName}\`** (${routines.length})\n\n` +
|
|
322
|
+
'| ID | Name | Schedule | Next fires |\n' +
|
|
323
|
+
'|---|---|---|---|\n' +
|
|
324
|
+
rows.join('\n');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
await this.client.sendMessage(this.workspaceId, channel, this.token, content, {
|
|
329
|
+
senderType: 'agent',
|
|
330
|
+
senderName: this.agentName,
|
|
331
|
+
messageType: 'chat',
|
|
332
|
+
metadata: { agent_mode: this._mode },
|
|
333
|
+
sessionId: this._sessionId,
|
|
334
|
+
});
|
|
335
|
+
} catch (e) {
|
|
336
|
+
this._log(`Routines: failed to post: ${e && e.message ? e.message : e}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
264
340
|
_hasActiveWork() {
|
|
265
341
|
return this._channelBusy.size > 0;
|
|
266
342
|
}
|
|
@@ -305,7 +381,7 @@ class BaseAdapter {
|
|
|
305
381
|
|
|
306
382
|
while (this._running) {
|
|
307
383
|
pollCount++;
|
|
308
|
-
let messages, rawCursor;
|
|
384
|
+
let messages, rawCursor, composingActive = false;
|
|
309
385
|
try {
|
|
310
386
|
const result = await this.client.pollPending(
|
|
311
387
|
this.workspaceId, this.agentName, this.token,
|
|
@@ -313,8 +389,9 @@ class BaseAdapter {
|
|
|
313
389
|
);
|
|
314
390
|
messages = result.messages;
|
|
315
391
|
rawCursor = result.cursor;
|
|
392
|
+
composingActive = !!result.composing;
|
|
316
393
|
if (pollCount <= 3 || pollCount % 20 === 0) {
|
|
317
|
-
this._log(`Poll #${pollCount}: ${messages.length} messages, cursor=${rawCursor || 'none'}`);
|
|
394
|
+
this._log(`Poll #${pollCount}: ${messages.length} messages, cursor=${rawCursor || 'none'}${composingActive ? ' composing' : ''}`);
|
|
318
395
|
}
|
|
319
396
|
} catch (e) {
|
|
320
397
|
this._log(`Poll #${pollCount} failed: ${e.message} \nStack: ${e.stack}`);
|
|
@@ -358,11 +435,51 @@ class BaseAdapter {
|
|
|
358
435
|
idleCount++;
|
|
359
436
|
}
|
|
360
437
|
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
438
|
+
// Sidecar poll: A2UI tool_result events. These are the user's response
|
|
439
|
+
// to a UI spec this agent (or any agent in the network) emitted. We
|
|
440
|
+
// surface each one as a synthetic user message so the LLM sees it as
|
|
441
|
+
// the next turn and can react. Failures here don't break the main
|
|
442
|
+
// message poll.
|
|
443
|
+
try {
|
|
444
|
+
const toolResult = await this.client.pollToolResults(
|
|
445
|
+
this.workspaceId, this.token,
|
|
446
|
+
{ after: this._lastToolResultId }
|
|
447
|
+
);
|
|
448
|
+
if (toolResult.cursor) this._lastToolResultId = toolResult.cursor;
|
|
449
|
+
for (const event of toolResult.events || []) {
|
|
450
|
+
const msgId = event.id;
|
|
451
|
+
if (msgId && this._processedIds.has(msgId)) continue;
|
|
452
|
+
if (msgId) this._processedIds.add(msgId);
|
|
453
|
+
const synth = synthesizeToolResultMessage(event);
|
|
454
|
+
if (synth) await this._dispatchMessage(synth);
|
|
455
|
+
}
|
|
456
|
+
} catch (e) {
|
|
457
|
+
// Non-fatal — log once per poll if it fails
|
|
458
|
+
if (pollCount <= 3 || pollCount % 20 === 0) {
|
|
459
|
+
this._log(`tool_result poll #${pollCount} failed: ${e.message}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Adaptive polling with warm plateau:
|
|
464
|
+
// Active (messages incoming): 2s
|
|
465
|
+
// Warm (≤5 min since last msg): 5s
|
|
466
|
+
// Cooldown (5-7 min): 5s → 15s (ramp 1s per idle poll)
|
|
467
|
+
// Cold (>7 min): 15s
|
|
468
|
+
// The warm plateau keeps the agent responsive during typical user
|
|
469
|
+
// think-time between messages without hammering the backend.
|
|
470
|
+
const WARM_INTERVAL = 5000;
|
|
471
|
+
const WARM_POLLS = 60; // 60 × 5s = 5 minutes warm plateau
|
|
472
|
+
let delay;
|
|
473
|
+
if (incoming.length > 0) {
|
|
474
|
+
delay = 2000;
|
|
475
|
+
} else if (composingActive) {
|
|
476
|
+
delay = 2000;
|
|
477
|
+
idleCount = Math.min(idleCount, WARM_POLLS);
|
|
478
|
+
} else if (idleCount <= WARM_POLLS) {
|
|
479
|
+
delay = WARM_INTERVAL;
|
|
480
|
+
} else {
|
|
481
|
+
delay = Math.min(WARM_INTERVAL + (idleCount - WARM_POLLS) * 1000, 15000);
|
|
482
|
+
}
|
|
366
483
|
await this._sleep(delay);
|
|
367
484
|
}
|
|
368
485
|
}
|
|
@@ -421,6 +538,9 @@ class BaseAdapter {
|
|
|
421
538
|
const queue = this._channelQueues[channel];
|
|
422
539
|
if (!queue || queue.length === 0) break;
|
|
423
540
|
const nextMsg = queue.shift();
|
|
541
|
+
if (nextMsg._queueId) {
|
|
542
|
+
try { await this.sendStatus(channel, 'processing queued message', { queue_id: nextMsg._queueId, queue_status: 'processed' }); } catch {}
|
|
543
|
+
}
|
|
424
544
|
try {
|
|
425
545
|
await this._handleMessage(nextMsg);
|
|
426
546
|
} catch (e) {
|
|
@@ -473,8 +593,15 @@ class BaseAdapter {
|
|
|
473
593
|
}
|
|
474
594
|
|
|
475
595
|
async sendThinking(channel, content) {
|
|
596
|
+
// Strip ```a2ui blocks if they leak into Claude's intermediate thinking
|
|
597
|
+
// trace — the real spec gets emitted via sendResponse with proper
|
|
598
|
+
// payload.spec extraction, so showing the raw block here is just noise
|
|
599
|
+
// (and a duplicate). If stripping leaves the thinking message empty,
|
|
600
|
+
// skip it entirely.
|
|
601
|
+
const { cleanContent } = extractA2UISpec(content);
|
|
602
|
+
if (!cleanContent || !cleanContent.trim()) return;
|
|
476
603
|
try {
|
|
477
|
-
await this.client.sendMessage(this.workspaceId, channel, this.token,
|
|
604
|
+
await this.client.sendMessage(this.workspaceId, channel, this.token, cleanContent, {
|
|
478
605
|
senderType: 'agent',
|
|
479
606
|
senderName: this.agentName,
|
|
480
607
|
messageType: 'thinking',
|
|
@@ -487,11 +614,14 @@ class BaseAdapter {
|
|
|
487
614
|
}
|
|
488
615
|
|
|
489
616
|
async sendResponse(channel, content) {
|
|
617
|
+
const { cleanContent, spec, specToolCallId } = extractA2UISpec(content);
|
|
490
618
|
try {
|
|
491
|
-
await this.client.sendMessage(this.workspaceId, channel, this.token,
|
|
619
|
+
await this.client.sendMessage(this.workspaceId, channel, this.token, cleanContent, {
|
|
492
620
|
senderType: 'agent',
|
|
493
621
|
senderName: this.agentName,
|
|
494
622
|
sessionId: this._sessionId,
|
|
623
|
+
spec,
|
|
624
|
+
specToolCallId,
|
|
495
625
|
});
|
|
496
626
|
} catch (e) {
|
|
497
627
|
if (e instanceof SessionRevokedError) {
|
|
@@ -597,6 +727,95 @@ class BaseAdapter {
|
|
|
597
727
|
_sleep(ms) {
|
|
598
728
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
599
729
|
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Return whether the workspace has the Browser Fabric viewer toggle on.
|
|
733
|
+
* Cached for the lifetime of the adapter — restart to pick up a flip.
|
|
734
|
+
* Falls back to false on error so the prompt builders don't accidentally
|
|
735
|
+
* inject the strong directive against an older backend that can't route
|
|
736
|
+
* to Browser Fabric.
|
|
737
|
+
*/
|
|
738
|
+
async getBrowserEnabled() {
|
|
739
|
+
if (this._browserEnabledCache === null) {
|
|
740
|
+
try {
|
|
741
|
+
const meta = await this.client.getWorkspaceMetadata(this.workspaceId, this.token);
|
|
742
|
+
this._browserEnabledCache = !!(meta && meta.browserEnabled);
|
|
743
|
+
} catch (e) {
|
|
744
|
+
this._browserEnabledCache = false;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return this._browserEnabledCache;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ------------------------------------------------------------------
|
|
752
|
+
// A2UI helpers
|
|
753
|
+
// ------------------------------------------------------------------
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Pull the first ```a2ui ... ``` fenced block out of LLM-produced content.
|
|
757
|
+
* Returns the content with the block stripped, the parsed spec, and a
|
|
758
|
+
* tool-call id derived from `spec.tool_call_id` (if present) or a new one.
|
|
759
|
+
* If no block is present or parsing fails, returns the content unchanged
|
|
760
|
+
* with null spec — the message still goes out as plain markdown.
|
|
761
|
+
*/
|
|
762
|
+
/**
|
|
763
|
+
* Convert a workspace.tool_result event into a synthetic user-message
|
|
764
|
+
* shape that the agent's _handleMessage can dispatch. The LLM sees this
|
|
765
|
+
* as the next user turn — the content is a short, machine-readable line
|
|
766
|
+
* the LLM can parse without ambiguity. The original spec it emitted is
|
|
767
|
+
* already in the LLM's conversation history; the tool_call_id lets the
|
|
768
|
+
* LLM correlate this back.
|
|
769
|
+
*/
|
|
770
|
+
function synthesizeToolResultMessage(event) {
|
|
771
|
+
if (!event || !event.payload) return null;
|
|
772
|
+
const p = event.payload;
|
|
773
|
+
const actionId = p.action_id || '';
|
|
774
|
+
const toolCallId = p.tool_call_id || '';
|
|
775
|
+
let valueStr = '';
|
|
776
|
+
if (p.value !== undefined && p.value !== null) {
|
|
777
|
+
try { valueStr = JSON.stringify(p.value); } catch (_) { valueStr = String(p.value); }
|
|
778
|
+
}
|
|
779
|
+
const lines = [
|
|
780
|
+
'[ui_action]',
|
|
781
|
+
`action=${actionId}`,
|
|
782
|
+
toolCallId ? `tool_call_id=${toolCallId}` : null,
|
|
783
|
+
valueStr ? `value=${valueStr}` : null,
|
|
784
|
+
].filter(Boolean);
|
|
785
|
+
const content = lines.join(' ');
|
|
786
|
+
const target = event.target || '';
|
|
787
|
+
return {
|
|
788
|
+
messageId: event.id || '',
|
|
789
|
+
sessionId: target.startsWith('channel/') ? target.replace('channel/', '') : target,
|
|
790
|
+
senderType: 'human',
|
|
791
|
+
senderName: 'user',
|
|
792
|
+
content,
|
|
793
|
+
mentions: [],
|
|
794
|
+
messageType: 'chat',
|
|
795
|
+
metadata: event.metadata || {},
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function extractA2UISpec(content) {
|
|
800
|
+
if (!content || typeof content !== 'string') {
|
|
801
|
+
return { cleanContent: content, spec: null, specToolCallId: null };
|
|
802
|
+
}
|
|
803
|
+
const match = content.match(/```a2ui\s*\n([\s\S]*?)\n```/);
|
|
804
|
+
if (!match) return { cleanContent: content, spec: null, specToolCallId: null };
|
|
805
|
+
|
|
806
|
+
let spec;
|
|
807
|
+
try {
|
|
808
|
+
spec = JSON.parse(match[1]);
|
|
809
|
+
} catch (_) {
|
|
810
|
+
return { cleanContent: content, spec: null, specToolCallId: null };
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const specToolCallId = (spec && spec.tool_call_id) || `tc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
814
|
+
if (spec && spec.tool_call_id) delete spec.tool_call_id;
|
|
815
|
+
|
|
816
|
+
const cleanContent = content.replace(match[0], '').trim();
|
|
817
|
+
return { cleanContent, spec, specToolCallId };
|
|
600
818
|
}
|
|
601
819
|
|
|
602
820
|
module.exports = BaseAdapter;
|
|
821
|
+
module.exports.extractA2UISpec = extractA2UISpec;
|
package/src/adapters/claude.js
CHANGED
|
@@ -358,7 +358,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
358
358
|
return null;
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
_buildClaudeCmd(prompt, channelName, { skipResume = false } = {}) {
|
|
361
|
+
_buildClaudeCmd(prompt, channelName, { skipResume = false, browserEnabled = false } = {}) {
|
|
362
362
|
const claudeBin = this._findClaudeBinary();
|
|
363
363
|
if (!claudeBin) {
|
|
364
364
|
throw new Error('claude CLI not found. Install with: curl -fsSL https://claude.ai/install.sh | bash');
|
|
@@ -369,6 +369,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
369
369
|
workspaceId: this.workspaceId,
|
|
370
370
|
channelName,
|
|
371
371
|
mode: this._mode,
|
|
372
|
+
browserEnabled,
|
|
372
373
|
});
|
|
373
374
|
|
|
374
375
|
// In skills mode, replace MCP tool references with curl-based instructions
|
|
@@ -432,6 +433,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
432
433
|
agentName: this.agentName,
|
|
433
434
|
channelName,
|
|
434
435
|
disabledModules: this.disabledModules,
|
|
436
|
+
browserEnabled: this._browserEnabledCache === true,
|
|
435
437
|
});
|
|
436
438
|
fs.writeFileSync(skillFile, skillContent, 'utf-8');
|
|
437
439
|
this._log(`Wrote workspace skill to ${skillFile}`);
|
|
@@ -569,7 +571,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
569
571
|
let content = (msg.content || '').trim();
|
|
570
572
|
const attachments = msg.attachments || [];
|
|
571
573
|
|
|
572
|
-
const attText = formatAttachmentsForPrompt(attachments);
|
|
574
|
+
const attText = formatAttachmentsForPrompt(attachments, this.toolMode);
|
|
573
575
|
if (attText) {
|
|
574
576
|
content = content ? content + attText : attText.trim();
|
|
575
577
|
}
|
|
@@ -648,7 +650,11 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
648
650
|
}
|
|
649
651
|
|
|
650
652
|
try {
|
|
651
|
-
const
|
|
653
|
+
const browserEnabled = await this.getBrowserEnabled();
|
|
654
|
+
const built = this._buildClaudeCmd(effectiveContent, msgChannel, {
|
|
655
|
+
skipResume: attempt > 0,
|
|
656
|
+
browserEnabled,
|
|
657
|
+
});
|
|
652
658
|
cmd = built.cmd;
|
|
653
659
|
mcpConfigFile = built.mcpConfigFile;
|
|
654
660
|
} catch (e) {
|
|
@@ -853,6 +859,27 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
853
859
|
}
|
|
854
860
|
}
|
|
855
861
|
|
|
862
|
+
// In plan mode, read the plan file and include it in the response
|
|
863
|
+
if (this._mode === 'plan') {
|
|
864
|
+
try {
|
|
865
|
+
const planDir = path.join(this.workingDir || process.cwd(), '.claude', 'plans');
|
|
866
|
+
if (fs.existsSync(planDir)) {
|
|
867
|
+
const planFiles = fs.readdirSync(planDir)
|
|
868
|
+
.filter((f) => f.endsWith('.md'))
|
|
869
|
+
.map((f) => ({ name: f, mtime: fs.statSync(path.join(planDir, f)).mtimeMs }))
|
|
870
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
871
|
+
if (planFiles.length > 0) {
|
|
872
|
+
const planContent = fs.readFileSync(path.join(planDir, planFiles[0].name), 'utf-8').trim();
|
|
873
|
+
if (planContent) {
|
|
874
|
+
lastResponseText.push('\n\n---\n\n**Plan:**\n\n' + planContent);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
} catch (e) {
|
|
879
|
+
this._log(`Failed to read plan file: ${e.message}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
856
883
|
if (lastResponseText.length > 0) {
|
|
857
884
|
const fullResponse = lastResponseText.join('\n').trim();
|
|
858
885
|
// "Prompt is too long" means the resumed session's context
|