@openagents-org/agent-launcher 0.2.143 → 0.2.145
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 +274 -66
- package/src/adapters/aider.js +634 -0
- package/src/adapters/amp.js +477 -0
- package/src/adapters/base.js +108 -7
- package/src/adapters/claude.js +36 -34
- package/src/adapters/cline-stream.js +632 -0
- package/src/adapters/cline.js +914 -0
- package/src/adapters/codex.js +14 -16
- package/src/adapters/copilot-stream-parser.js +380 -0
- package/src/adapters/copilot.js +796 -0
- package/src/adapters/cursor.js +9 -15
- package/src/adapters/gemini.js +10 -12
- package/src/adapters/goose-stream.js +268 -0
- package/src/adapters/goose.js +649 -0
- package/src/adapters/health-status.js +166 -0
- package/src/adapters/hermes.js +8 -17
- package/src/adapters/index.js +16 -1
- package/src/adapters/nanoclaw-bridge.js +264 -0
- package/src/adapters/nanoclaw-channel-install.js +411 -0
- package/src/adapters/nanoclaw-control.js +491 -0
- package/src/adapters/nanoclaw-delivery-store.js +0 -0
- package/src/adapters/nanoclaw-protocol.js +349 -0
- package/src/adapters/nanoclaw.js +661 -9
- package/src/adapters/opencode.js +541 -75
- package/src/agent-rows.js +13 -1
- package/src/auth-guidance.js +98 -0
- package/src/cli.js +24 -7
- package/src/config.js +8 -2
- package/src/daemon.js +86 -5
- package/src/installer.js +509 -10
- package/src/paths.js +125 -2
- package/src/tui.js +29 -2
package/src/adapters/cursor.js
CHANGED
|
@@ -17,7 +17,7 @@ const { execSync, spawn } = require('child_process');
|
|
|
17
17
|
const BaseAdapter = require('./base');
|
|
18
18
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
|
|
19
19
|
const { buildCursorSkillMd } = require('./workspace-prompt');
|
|
20
|
-
const { defaultAgentWorkdir, whichBinary,
|
|
20
|
+
const { defaultAgentWorkdir, whichBinary, whereBinary } = require('../paths');
|
|
21
21
|
|
|
22
22
|
const IS_WINDOWS = process.platform === 'win32';
|
|
23
23
|
|
|
@@ -191,19 +191,11 @@ class CursorAdapter extends BaseAdapter {
|
|
|
191
191
|
// which the daemon's already-running process won't see, so `where` against
|
|
192
192
|
// the daemon's own PATH comes up empty even though cursor-agent is installed.
|
|
193
193
|
// windowsHide stops a console window from flashing.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
200
|
-
const hit = r.split(/\r?\n/)[0].trim();
|
|
201
|
-
if (hit) return hit;
|
|
202
|
-
} else {
|
|
203
|
-
const hit = execSync('which cursor-agent || which agent', { encoding: 'utf-8', timeout: 5000, windowsHide: true, env }).trim();
|
|
204
|
-
if (hit) return hit;
|
|
205
|
-
}
|
|
206
|
-
} catch {}
|
|
194
|
+
// Codepage-safe lookup (whereBinary forces UTF-8 output + verifies existence
|
|
195
|
+
// so a non-ASCII/Chinese username isn't mangled into an ENOENT). Tries both
|
|
196
|
+
// cursor-agent and the bare `agent` name across .cmd/.exe/no-ext.
|
|
197
|
+
const viaWhere = whereBinary(names);
|
|
198
|
+
if (viaWhere) return viaWhere;
|
|
207
199
|
|
|
208
200
|
// Tier 2: Next to current Node.js interpreter (npm global)
|
|
209
201
|
const nodeBinDir = path.dirname(process.execPath);
|
|
@@ -273,7 +265,9 @@ class CursorAdapter extends BaseAdapter {
|
|
|
273
265
|
for (const c of candidates) {
|
|
274
266
|
if (fs.existsSync(c)) return c;
|
|
275
267
|
}
|
|
276
|
-
|
|
268
|
+
// Fall back to the node already running this daemon (absolute, always valid).
|
|
269
|
+
// Bare 'node' can be off-PATH in a packaged daemon or CI runner.
|
|
270
|
+
return process.execPath;
|
|
277
271
|
}
|
|
278
272
|
|
|
279
273
|
_resolveToNodeCmd(binPath) {
|
package/src/adapters/gemini.js
CHANGED
|
@@ -14,6 +14,7 @@ const path = require('path');
|
|
|
14
14
|
const { execSync, spawn } = require('child_process');
|
|
15
15
|
|
|
16
16
|
const BaseAdapter = require('./base');
|
|
17
|
+
const { whereBinary } = require('../paths');
|
|
17
18
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
|
|
18
19
|
const { buildClaudeSystemPrompt } = require('./workspace-prompt');
|
|
19
20
|
|
|
@@ -110,7 +111,9 @@ class GeminiAdapter extends BaseAdapter {
|
|
|
110
111
|
for (const c of candidates) {
|
|
111
112
|
if (fs.existsSync(c)) return c;
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
+
// Fall back to the node already running this daemon (absolute, always valid).
|
|
115
|
+
// Bare 'node' can be off-PATH in a packaged daemon or CI runner.
|
|
116
|
+
return process.execPath;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
_resolveToNodeCmd(binPath) {
|
|
@@ -151,17 +154,12 @@ class GeminiAdapter extends BaseAdapter {
|
|
|
151
154
|
const runtimeCandidate = path.join(home, '.openagents', 'runtimes', 'gemini', 'node_modules', '.bin', `gemini${ext}`);
|
|
152
155
|
if (fs.existsSync(runtimeCandidate)) return runtimeCandidate;
|
|
153
156
|
|
|
154
|
-
// Tier 1: PATH search
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return r.split(/\r?\n/)[0].trim();
|
|
161
|
-
} else {
|
|
162
|
-
return execSync('which gemini', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
163
|
-
}
|
|
164
|
-
} catch {}
|
|
157
|
+
// Tier 1: PATH search via a codepage-safe lookup (whereBinary forces UTF-8
|
|
158
|
+
// output + verifies existence so a non-ASCII/Chinese username isn't mangled
|
|
159
|
+
// into an ENOENT; it also no longer returns an empty string on a miss, which
|
|
160
|
+
// used to short-circuit the Node-derived fallback tiers below).
|
|
161
|
+
const viaWhere = whereBinary('gemini');
|
|
162
|
+
if (viaWhere) return viaWhere;
|
|
165
163
|
|
|
166
164
|
// Tier 2: Next to current Node.js interpreter
|
|
167
165
|
const nodeBinDir = path.dirname(process.execPath);
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, line-oriented parser for Goose's `goose run --output-format stream-json`
|
|
3
|
+
* output, plus helpers for secret redaction and error classification.
|
|
4
|
+
*
|
|
5
|
+
* No I/O / process code lives here so it can be unit-tested in isolation and
|
|
6
|
+
* kept behaviourally identical to the Python port
|
|
7
|
+
* (sdk/src/openagents/adapters/goose_stream.py).
|
|
8
|
+
*
|
|
9
|
+
* Verified against block/goose v1.38.0. The CLI emits one JSON object per line
|
|
10
|
+
* (NDJSON). StreamEvent is `#[serde(tag="type", rename_all="snake_case")]`:
|
|
11
|
+
*
|
|
12
|
+
* {"type":"message","message":{"role":"assistant"|"user","created":N,"content":[...]}}
|
|
13
|
+
* {"type":"notification","extension_id":"...","log":{"message":"..."}} // or "progress"
|
|
14
|
+
* {"type":"error","error":"..."}
|
|
15
|
+
* {"type":"complete","total_tokens":N,"input_tokens":N,"output_tokens":N}
|
|
16
|
+
*
|
|
17
|
+
* Content items are `#[serde(tag="type", rename_all="camelCase")]`:
|
|
18
|
+
* text {text}, thinking {thinking,signature}, redactedThinking {data},
|
|
19
|
+
* toolRequest {id, toolCall:{status:"success", value:{name, arguments}}|{status:"error", error}},
|
|
20
|
+
* toolResponse {id, toolResult:{status, ...}}, image / systemNotification / ...
|
|
21
|
+
*
|
|
22
|
+
* Important: an agent/provider error during a run is emitted as an `error`
|
|
23
|
+
* event but `goose run` still exits 0. Callers MUST treat `hadError` (or any
|
|
24
|
+
* event of kind `error`) as a failure regardless of the process exit code.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// Hard cap on a single un-terminated line we will buffer before giving up.
|
|
30
|
+
const MAX_LINE_BYTES = 8 * 1024 * 1024;
|
|
31
|
+
// Cap on how much of a tool-argument preview we surface as workspace status.
|
|
32
|
+
const TOOL_PREVIEW_LIMIT = 160;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Mask credentials in arbitrary text.
|
|
36
|
+
* @param {string} text
|
|
37
|
+
* @param {string[]} [secrets] exact secret values to mask (e.g. the API key)
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function redactSecrets(text, secrets) {
|
|
41
|
+
if (!text) return '';
|
|
42
|
+
let out = String(text);
|
|
43
|
+
for (const secret of secrets || []) {
|
|
44
|
+
if (secret && typeof secret === 'string' && secret.length >= 4) {
|
|
45
|
+
out = out.split(secret).join('***');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
out = out.replace(
|
|
49
|
+
/\b(api[_-]?key|secret|token|authorization|auth|bearer)\b\s*[:=]?\s*['"]?([A-Za-z0-9._-]{8,})['"]?/gi,
|
|
50
|
+
(_m, label) => `${label}=***`,
|
|
51
|
+
);
|
|
52
|
+
out = out.replace(/\bsk-[A-Za-z0-9._-]{6,}\b/g, 'sk-***');
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Map a raw Goose stderr/error string to a readable, actionable message, or
|
|
58
|
+
* null when there's nothing useful. Callers should still pass the result
|
|
59
|
+
* through redactSecrets.
|
|
60
|
+
* @param {string} text
|
|
61
|
+
* @returns {string|null}
|
|
62
|
+
*/
|
|
63
|
+
function classifyGooseError(text) {
|
|
64
|
+
if (!text || !String(text).trim()) return null;
|
|
65
|
+
const low = String(text).toLowerCase();
|
|
66
|
+
const has = (...needles) => needles.some((n) => low.includes(n));
|
|
67
|
+
|
|
68
|
+
if (has('401', '403', 'unauthorized', 'invalid api key', 'invalid_api_key',
|
|
69
|
+
'authentication', 'incorrect api key', 'no api key', 'missing api key')) {
|
|
70
|
+
return "Goose authentication failed. Check this agent's provider API key "
|
|
71
|
+
+ '(GOOSE_PROVIDER__API_KEY) and host — the key may be missing, invalid, or expired.';
|
|
72
|
+
}
|
|
73
|
+
if (has('429', 'rate limit', 'rate_limit', 'too many requests', 'quota', 'overloaded')) {
|
|
74
|
+
return "Goose hit the provider's rate limit or quota. Wait and retry, or use a "
|
|
75
|
+
+ 'different key/provider.';
|
|
76
|
+
}
|
|
77
|
+
if (has('no such model', 'unknown model', 'model not found', 'does not exist') || has('model')) {
|
|
78
|
+
if (has('model')) {
|
|
79
|
+
return 'Goose could not use the configured model. Check GOOSE_MODEL is valid for '
|
|
80
|
+
+ 'the selected GOOSE_PROVIDER.';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (has('provider', 'unknown provider', 'no provider', 'provider not', 'configure')) {
|
|
84
|
+
return 'Goose has no usable provider configured. Set GOOSE_PROVIDER (and a key/host), '
|
|
85
|
+
+ 'or run `goose configure` once outside OpenAgents.';
|
|
86
|
+
}
|
|
87
|
+
if (has('permission denied', 'not permitted', 'tool execution denied', 'denied by')) {
|
|
88
|
+
return 'A Goose tool call was denied. The workspace runs Goose headless with '
|
|
89
|
+
+ 'GOOSE_MODE=auto; check the project directory permissions.';
|
|
90
|
+
}
|
|
91
|
+
if (has('extension', 'mcp', 'failed to start', 'failed to load')) {
|
|
92
|
+
return 'A Goose extension failed to start. The workspace only enables the built-in '
|
|
93
|
+
+ 'developer extension by default; check your Goose extension config.';
|
|
94
|
+
}
|
|
95
|
+
if (has('connection', 'network', 'timed out', 'timeout', 'dns', 'could not resolve',
|
|
96
|
+
'connection refused', 'unreachable')) {
|
|
97
|
+
return 'Goose could not reach the provider endpoint. Check the network and the '
|
|
98
|
+
+ 'GOOSE_PROVIDER__HOST URL.';
|
|
99
|
+
}
|
|
100
|
+
if (has('no such file', 'not a directory', 'os error 13')) {
|
|
101
|
+
return 'Goose hit a filesystem error. Check the agent\'s project directory exists and '
|
|
102
|
+
+ 'is writable.';
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class GooseStreamParser {
|
|
108
|
+
constructor() {
|
|
109
|
+
this._buf = '';
|
|
110
|
+
this._pendingFinal = null;
|
|
111
|
+
this._finalText = null;
|
|
112
|
+
this._finalEmitted = false;
|
|
113
|
+
this.hadError = false;
|
|
114
|
+
this.errorMessage = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get finalText() {
|
|
118
|
+
return this._finalText;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Feed a raw stdout chunk; returns semantic events parsed from completed lines.
|
|
123
|
+
* @param {string} chunk
|
|
124
|
+
* @returns {Array<object>}
|
|
125
|
+
*/
|
|
126
|
+
feed(chunk) {
|
|
127
|
+
const events = [];
|
|
128
|
+
if (!chunk) return events;
|
|
129
|
+
this._buf += chunk;
|
|
130
|
+
if (this._buf.length > MAX_LINE_BYTES && !this._buf.includes('\n')) {
|
|
131
|
+
this._buf = this._buf.slice(-MAX_LINE_BYTES);
|
|
132
|
+
}
|
|
133
|
+
let idx;
|
|
134
|
+
while ((idx = this._buf.indexOf('\n')) >= 0) {
|
|
135
|
+
const line = this._buf.slice(0, idx);
|
|
136
|
+
this._buf = this._buf.slice(idx + 1);
|
|
137
|
+
this._handleLine(line, events);
|
|
138
|
+
}
|
|
139
|
+
return events;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Flush at EOF; emits the final answer if the stream ended without `complete`. */
|
|
143
|
+
finish() {
|
|
144
|
+
const events = [];
|
|
145
|
+
const leftover = this._buf;
|
|
146
|
+
this._buf = '';
|
|
147
|
+
if (leftover.trim()) this._handleLine(leftover, events);
|
|
148
|
+
if (!this._finalEmitted && !this.hadError) this._emitFinal(events);
|
|
149
|
+
return events;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_emitFinal(events) {
|
|
153
|
+
if (this._pendingFinal) {
|
|
154
|
+
this._finalText = this._pendingFinal;
|
|
155
|
+
this._pendingFinal = null;
|
|
156
|
+
}
|
|
157
|
+
if (!this._finalEmitted && this._finalText) {
|
|
158
|
+
events.push({ kind: 'final', text: this._finalText });
|
|
159
|
+
this._finalEmitted = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_handleLine(line, events) {
|
|
164
|
+
line = line.trim();
|
|
165
|
+
if (!line) return;
|
|
166
|
+
let ev;
|
|
167
|
+
try {
|
|
168
|
+
ev = JSON.parse(line);
|
|
169
|
+
} catch {
|
|
170
|
+
return; // half-line / non-JSON noise: ignore defensively
|
|
171
|
+
}
|
|
172
|
+
if (!ev || typeof ev !== 'object' || Array.isArray(ev)) return;
|
|
173
|
+
const etype = ev.type;
|
|
174
|
+
if (etype === 'message') {
|
|
175
|
+
if (ev.message && typeof ev.message === 'object') this._handleMessage(ev.message, events);
|
|
176
|
+
} else if (etype === 'error') {
|
|
177
|
+
const msg = ev.error || 'Goose reported an error';
|
|
178
|
+
this.hadError = true;
|
|
179
|
+
this.errorMessage = String(msg);
|
|
180
|
+
events.push({ kind: 'error', message: String(msg) });
|
|
181
|
+
} else if (etype === 'complete') {
|
|
182
|
+
if (!this.hadError) this._emitFinal(events);
|
|
183
|
+
events.push({ kind: 'complete', tokens: ev.total_tokens ?? null });
|
|
184
|
+
} else if (etype === 'notification') {
|
|
185
|
+
const text = GooseStreamParser._notificationText(ev);
|
|
186
|
+
if (text) events.push({ kind: 'notification', text });
|
|
187
|
+
}
|
|
188
|
+
// Unknown event types are ignored on purpose (forward-compatible).
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static _notificationText(ev) {
|
|
192
|
+
if (ev.progress && typeof ev.progress === 'object' && ev.progress.message) {
|
|
193
|
+
return String(ev.progress.message);
|
|
194
|
+
}
|
|
195
|
+
if (ev.log && typeof ev.log === 'object' && ev.log.message) {
|
|
196
|
+
return String(ev.log.message);
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_handleMessage(message, events) {
|
|
202
|
+
const role = String(message.role || '').toLowerCase();
|
|
203
|
+
const content = message.content;
|
|
204
|
+
if (!Array.isArray(content)) return;
|
|
205
|
+
const texts = [];
|
|
206
|
+
for (const item of content) {
|
|
207
|
+
if (!item || typeof item !== 'object') continue;
|
|
208
|
+
const itype = item.type;
|
|
209
|
+
if (itype === 'text') {
|
|
210
|
+
if (item.text) texts.push(String(item.text));
|
|
211
|
+
} else if (itype === 'thinking') {
|
|
212
|
+
if (item.thinking) events.push({ kind: 'thinking', text: String(item.thinking) });
|
|
213
|
+
} else if (itype === 'toolRequest') {
|
|
214
|
+
events.push(GooseStreamParser._toolEvent(item));
|
|
215
|
+
} else if (itype === 'toolResponse') {
|
|
216
|
+
events.push(GooseStreamParser._toolResultEvent(item));
|
|
217
|
+
}
|
|
218
|
+
// image / redactedThinking / systemNotification / etc → ignore
|
|
219
|
+
}
|
|
220
|
+
if (role === 'assistant' && texts.length) {
|
|
221
|
+
const joined = texts.filter(Boolean).join('\n').trim();
|
|
222
|
+
if (joined) {
|
|
223
|
+
// Intermediate assistant text is interim narration (assistant output),
|
|
224
|
+
// not model-internal reasoning — surface it as `progress` (→ status),
|
|
225
|
+
// NOT `thinking`. Only genuine `thinking` content items emit `thinking`.
|
|
226
|
+
if (this._pendingFinal) events.push({ kind: 'progress', text: this._pendingFinal });
|
|
227
|
+
this._pendingFinal = joined;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static _toolEvent(item) {
|
|
233
|
+
const toolCall = item.toolCall;
|
|
234
|
+
if (!toolCall || typeof toolCall !== 'object') {
|
|
235
|
+
return { kind: 'tool', name: 'tool', summary: '' };
|
|
236
|
+
}
|
|
237
|
+
if (toolCall.status === 'error') {
|
|
238
|
+
return { kind: 'tool', name: 'tool', summary: '(tool call could not be parsed)' };
|
|
239
|
+
}
|
|
240
|
+
const value = toolCall.value;
|
|
241
|
+
if (!value || typeof value !== 'object') {
|
|
242
|
+
return { kind: 'tool', name: 'tool', summary: '' };
|
|
243
|
+
}
|
|
244
|
+
const name = String(value.name || 'tool');
|
|
245
|
+
return { kind: 'tool', name, summary: GooseStreamParser._summarizeArgs(name, value.arguments) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static _toolResultEvent(item) {
|
|
249
|
+
const tr = item.toolResult;
|
|
250
|
+
const ok = !!(tr && typeof tr === 'object' && tr.status === 'success');
|
|
251
|
+
return { kind: 'tool_result', ok };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static _summarizeArgs(_name, args) {
|
|
255
|
+
if (!args || typeof args !== 'object') return '';
|
|
256
|
+
for (const key of ['command', 'path', 'file_path', 'file', 'pattern', 'query', 'uri', 'url']) {
|
|
257
|
+
const val = args[key];
|
|
258
|
+
if (typeof val === 'string' && val) {
|
|
259
|
+
let preview = val.split(/\s+/).join(' ');
|
|
260
|
+
if (preview.length > TOOL_PREVIEW_LIMIT) preview = preview.slice(0, TOOL_PREVIEW_LIMIT) + '…';
|
|
261
|
+
return preview;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return Object.keys(args).filter((k) => typeof k === 'string').slice(0, 6).join(', ');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = { GooseStreamParser, redactSecrets, classifyGooseError };
|