@openagents-org/agent-launcher 0.2.180 → 0.2.182
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/icons/antigravity.svg +1 -0
- package/icons/mini-swe-agent.svg +7 -0
- package/package.json +3 -2
- package/registry.json +1137 -622
- package/src/adapters/aider.js +10 -3
- package/src/adapters/amp.js +11 -3
- package/src/adapters/antigravity.js +10 -2
- package/src/adapters/base.js +46 -0
- package/src/adapters/claude.js +6 -1
- package/src/adapters/cline.js +5 -1
- package/src/adapters/codebuddy.js +6 -1
- package/src/adapters/codex.js +10 -2
- package/src/adapters/commandcode.js +5 -1
- package/src/adapters/copilot-stream-parser.js +58 -8
- package/src/adapters/copilot.js +20 -4
- package/src/adapters/cursor.js +5 -1
- package/src/adapters/deepseek.js +5 -1
- package/src/adapters/gemini.js +10 -2
- package/src/adapters/goose.js +6 -2
- package/src/adapters/hermes.js +23 -53
- package/src/adapters/kimi.js +5 -1
- package/src/adapters/llm-direct.js +6 -0
- package/src/adapters/mini.js +10 -3
- package/src/adapters/openclaw.js +5 -1
- package/src/adapters/opencode.js +6 -1
- package/src/adapters/openworker.js +15 -1
- package/src/adapters/pi.js +6 -1
- package/src/adapters/workspace-prompt.js +23 -12
- package/src/daemon.js +60 -1
- package/src/index.js +120 -0
- package/src/installer.js +57 -16
- package/src/paths.js +98 -34
- package/src/probe.js +4 -1
- package/src/workspace-client.js +38 -0
- package/src/wsl.js +579 -0
package/src/adapters/aider.js
CHANGED
|
@@ -35,7 +35,11 @@
|
|
|
35
35
|
const fs = require('fs');
|
|
36
36
|
const os = require('os');
|
|
37
37
|
const path = require('path');
|
|
38
|
-
const { execSync
|
|
38
|
+
const { execSync } = require('child_process');
|
|
39
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
40
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
41
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
42
|
+
const { spawn, resolveWslBinary } = require('../wsl');
|
|
39
43
|
|
|
40
44
|
const BaseAdapter = require('./base');
|
|
41
45
|
const { whichBinary, getEnhancedEnv, aiderBinDirs } = require('../paths');
|
|
@@ -262,7 +266,7 @@ class AiderAdapter extends BaseAdapter {
|
|
|
262
266
|
// PATH (nvm/fnm/volta/homebrew + the Aider install dirs added to paths.js)
|
|
263
267
|
// — covers the GUI/daemon "not on PATH" case for uv-tool / pipx / pip-user
|
|
264
268
|
// installs.
|
|
265
|
-
const resolved = whichBinary('aider');
|
|
269
|
+
const resolved = whichBinary('aider', { allowWsl: false });
|
|
266
270
|
if (resolved) return resolved;
|
|
267
271
|
|
|
268
272
|
// Explicit fallback over every real install dir (XDG bin, XDG_DATA_HOME/../
|
|
@@ -274,7 +278,10 @@ class AiderAdapter extends BaseAdapter {
|
|
|
274
278
|
if (fs.existsSync(c)) return c;
|
|
275
279
|
}
|
|
276
280
|
}
|
|
277
|
-
|
|
281
|
+
// Nothing native anywhere. Last of all, look inside WSL: a CLI the user
|
|
282
|
+
// installed in their distro is a real install, and the marked path it comes
|
|
283
|
+
// back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
|
|
284
|
+
return resolveWslBinary('aider');
|
|
278
285
|
}
|
|
279
286
|
|
|
280
287
|
// ------------------------------------------------------------------
|
package/src/adapters/amp.js
CHANGED
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
const fs = require('fs');
|
|
26
26
|
const os = require('os');
|
|
27
27
|
const path = require('path');
|
|
28
|
-
const { execSync
|
|
28
|
+
const { execSync } = require('child_process');
|
|
29
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
30
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
31
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
32
|
+
const { spawn, resolveWslBinary } = require('../wsl');
|
|
29
33
|
|
|
30
34
|
const BaseAdapter = require('./base');
|
|
31
35
|
const { buildOpenclawSystemPrompt } = require('./workspace-prompt');
|
|
@@ -123,7 +127,7 @@ class AmpAdapter extends BaseAdapter {
|
|
|
123
127
|
// the Amp installer's ~/.amp/bin. This is what makes detection work inside
|
|
124
128
|
// a GUI- or daemon-spawned process that did NOT inherit the user's
|
|
125
129
|
// interactive shell PATH (the common "installed but not found" case).
|
|
126
|
-
const resolved = whichBinary('amp');
|
|
130
|
+
const resolved = whichBinary('amp', { allowWsl: false });
|
|
127
131
|
if (resolved) return resolved;
|
|
128
132
|
|
|
129
133
|
// Tier 2: explicit known install dirs as a last resort. The official
|
|
@@ -145,7 +149,10 @@ class AmpAdapter extends BaseAdapter {
|
|
|
145
149
|
if (c && fs.existsSync(c)) return c;
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
// Nothing native anywhere. Last of all, look inside WSL: a CLI the user
|
|
153
|
+
// installed in their distro is a real install, and the marked path it comes
|
|
154
|
+
// back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
|
|
155
|
+
return resolveWslBinary('amp');
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
/**
|
|
@@ -181,6 +188,7 @@ class AmpAdapter extends BaseAdapter {
|
|
|
181
188
|
endpoint: this.endpoint,
|
|
182
189
|
token: this.token,
|
|
183
190
|
mode: this._mode,
|
|
191
|
+
model: this.modelLabel(),
|
|
184
192
|
disabledModules: this.disabledModules,
|
|
185
193
|
browserEnabled,
|
|
186
194
|
...this.pinnedPromptOpts(channelName),
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const path = require('path');
|
|
20
|
-
const { execSync
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
22
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
23
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
24
|
+
const { spawn, resolveWslBinary } = require('../wsl');
|
|
21
25
|
|
|
22
26
|
const BaseAdapter = require('./base');
|
|
23
27
|
const { whereBinary } = require('../paths');
|
|
@@ -131,7 +135,10 @@ class AntigravityAdapter extends BaseAdapter {
|
|
|
131
135
|
for (const c of candidates) {
|
|
132
136
|
if (fs.existsSync(c)) return c;
|
|
133
137
|
}
|
|
134
|
-
|
|
138
|
+
// Nothing native anywhere. Last of all, look inside WSL: a CLI the user
|
|
139
|
+
// installed in their distro is a real install, and the marked path it comes
|
|
140
|
+
// back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
|
|
141
|
+
return resolveWslBinary('agy');
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
/**
|
|
@@ -178,6 +185,7 @@ class AntigravityAdapter extends BaseAdapter {
|
|
|
178
185
|
workspaceId: this.workspaceId,
|
|
179
186
|
channelName,
|
|
180
187
|
mode: this._mode,
|
|
188
|
+
model: this.modelLabel(),
|
|
181
189
|
decisionLog: pinned.decisionLog ? { ...pinned.decisionLog, writeAccess: false } : null,
|
|
182
190
|
glossary: pinned.glossary ? { ...pinned.glossary, writeAccess: false } : null,
|
|
183
191
|
});
|
package/src/adapters/base.js
CHANGED
|
@@ -60,6 +60,18 @@ const CURSOR_RESUME_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
|
60
60
|
// heartbeat_failed up to the daemon. A success resets the streak immediately.
|
|
61
61
|
const HEARTBEAT_ERROR_THRESHOLD = 2;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Model variables whose name does not follow `<TYPE>_MODEL`. Only the ones an
|
|
65
|
+
* adapter really reads — see `modelLabel`, which tries `<TYPE>_MODEL` first
|
|
66
|
+
* and `LLM_MODEL` last, so nothing else needs an entry here.
|
|
67
|
+
*/
|
|
68
|
+
const MODEL_ENV_ALIASES = {
|
|
69
|
+
claude: ['ANTHROPIC_MODEL', 'CLAUDE_MODEL'],
|
|
70
|
+
gemini: ['GEMINI_MODEL', 'GOOGLE_GEMINI_MODEL'],
|
|
71
|
+
antigravity: ['ANTIGRAVITY_MODEL', 'AGY_MODEL'],
|
|
72
|
+
codex: ['CODEX_MODEL', 'OPENCLAW_MODEL'],
|
|
73
|
+
};
|
|
74
|
+
|
|
63
75
|
class BaseAdapter {
|
|
64
76
|
/**
|
|
65
77
|
* @param {object} opts
|
|
@@ -220,6 +232,40 @@ class BaseAdapter {
|
|
|
220
232
|
}
|
|
221
233
|
}
|
|
222
234
|
|
|
235
|
+
/**
|
|
236
|
+
* The model this agent is actually running on, for the workspace prompt.
|
|
237
|
+
*
|
|
238
|
+
* The one fact about itself an agent cannot look up. Asked "what model are
|
|
239
|
+
* you?", a CLI-driven agent answers from what its weights remember about
|
|
240
|
+
* their own training, which is a guess — and a user who had configured
|
|
241
|
+
* deepseek was told, confidently, "I am Claude, by Anthropic". We know the
|
|
242
|
+
* real answer: it is the id we hand the CLI.
|
|
243
|
+
*
|
|
244
|
+
* Resolution order is the same one the adapters spawn with: a model chosen
|
|
245
|
+
* in the workspace wins, then this agent type's own model variable, then the
|
|
246
|
+
* generic LLM_MODEL that the LLM-direct types and the daemon's model.set
|
|
247
|
+
* both write. Adapters that qualify the id further (OpenWorker prefixes it
|
|
248
|
+
* with the provider) override this and return exactly what they send.
|
|
249
|
+
*
|
|
250
|
+
* Null when nothing is configured — the CLI is then running its own default
|
|
251
|
+
* and we have nothing truthful to say about it. Better silent than wrong.
|
|
252
|
+
*/
|
|
253
|
+
modelLabel() {
|
|
254
|
+
const fromWorkspace = String(this.workspaceModel || '').trim();
|
|
255
|
+
if (fromWorkspace) return fromWorkspace;
|
|
256
|
+
const type = String(this.agentType || '').toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
257
|
+
const candidates = [
|
|
258
|
+
...(type ? [`${type}_MODEL`] : []),
|
|
259
|
+
...(MODEL_ENV_ALIASES[this.agentType] || []),
|
|
260
|
+
'LLM_MODEL',
|
|
261
|
+
];
|
|
262
|
+
for (const key of candidates) {
|
|
263
|
+
const value = String((this.agentEnv && this.agentEnv[key]) || '').trim();
|
|
264
|
+
if (value) return value;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
223
269
|
async run() {
|
|
224
270
|
this._running = true;
|
|
225
271
|
|
package/src/adapters/claude.js
CHANGED
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const os = require('os');
|
|
16
16
|
const path = require('path');
|
|
17
|
-
const { execSync
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
19
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
20
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
21
|
+
const { spawn } = require('../wsl');
|
|
18
22
|
|
|
19
23
|
const BaseAdapter = require('./base');
|
|
20
24
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle, redactSecrets } = require('./utils');
|
|
@@ -462,6 +466,7 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
462
466
|
workspaceId: this.workspaceId,
|
|
463
467
|
channelName,
|
|
464
468
|
mode: this._mode,
|
|
469
|
+
model: this.modelLabel(),
|
|
465
470
|
browserEnabled,
|
|
466
471
|
toolMode: this.toolMode,
|
|
467
472
|
decisionLog,
|
package/src/adapters/cline.js
CHANGED
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const os = require('os');
|
|
28
28
|
const path = require('path');
|
|
29
|
-
const { execSync, execFile
|
|
29
|
+
const { execSync, execFile } = require('child_process');
|
|
30
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
31
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
32
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
33
|
+
const { spawn } = require('../wsl');
|
|
30
34
|
|
|
31
35
|
const BaseAdapter = require('./base');
|
|
32
36
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
|
|
@@ -49,7 +49,11 @@
|
|
|
49
49
|
const fs = require('fs');
|
|
50
50
|
const os = require('os');
|
|
51
51
|
const path = require('path');
|
|
52
|
-
const { execFileSync
|
|
52
|
+
const { execFileSync } = require('child_process');
|
|
53
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
54
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
55
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
56
|
+
const { spawn } = require('../wsl');
|
|
53
57
|
|
|
54
58
|
const BaseAdapter = require('./base');
|
|
55
59
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
|
|
@@ -824,6 +828,7 @@ class CodeBuddyAdapter extends BaseAdapter {
|
|
|
824
828
|
workspaceId: this.workspaceId,
|
|
825
829
|
channelName: channel,
|
|
826
830
|
mode: this._mode,
|
|
831
|
+
model: this.modelLabel(),
|
|
827
832
|
browserEnabled: await this.getBrowserEnabled(),
|
|
828
833
|
toolMode: 'mcp',
|
|
829
834
|
});
|
package/src/adapters/codex.js
CHANGED
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const os = require('os');
|
|
17
17
|
const path = require('path');
|
|
18
|
-
const { execSync
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
20
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
21
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
22
|
+
const { spawn, resolveWslBinary } = require('../wsl');
|
|
19
23
|
const http = require('http');
|
|
20
24
|
const https = require('https');
|
|
21
25
|
|
|
@@ -165,7 +169,10 @@ class CodexAdapter extends BaseAdapter {
|
|
|
165
169
|
if (fs.existsSync(c)) return c;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
|
|
172
|
+
// Nothing native anywhere. Last of all, look inside WSL: a CLI the user
|
|
173
|
+
// installed in their distro is a real install, and the marked path it comes
|
|
174
|
+
// back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
|
|
175
|
+
return resolveWslBinary('codex');
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
_buildSystemContext(channelName) {
|
|
@@ -176,6 +183,7 @@ class CodexAdapter extends BaseAdapter {
|
|
|
176
183
|
endpoint: this.endpoint,
|
|
177
184
|
token: this.token,
|
|
178
185
|
mode: this._mode,
|
|
186
|
+
model: this.modelLabel(),
|
|
179
187
|
disabledModules: this.disabledModules,
|
|
180
188
|
...this.pinnedPromptOpts(channelName),
|
|
181
189
|
});
|
|
@@ -45,7 +45,11 @@
|
|
|
45
45
|
const fs = require('fs');
|
|
46
46
|
const os = require('os');
|
|
47
47
|
const path = require('path');
|
|
48
|
-
const { execFileSync
|
|
48
|
+
const { execFileSync } = require('child_process');
|
|
49
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
50
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
51
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
52
|
+
const { spawn } = require('../wsl');
|
|
49
53
|
|
|
50
54
|
const BaseAdapter = require('./base');
|
|
51
55
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
|
|
@@ -168,17 +168,50 @@ function parseLine(line) {
|
|
|
168
168
|
* When real samples are available, extend THIS table only.
|
|
169
169
|
*/
|
|
170
170
|
const EVENT_KIND_BY_TYPE = {
|
|
171
|
-
//
|
|
171
|
+
// ── CAPTURED from a real authenticated run (CLI v1.0.83) ────────────────
|
|
172
|
+
// Every name in this block was read off a live JSONL stream, not inferred.
|
|
173
|
+
// The payload of each sits under `data` (see the `data` unwrap in
|
|
174
|
+
// classifyEvent) — except `result`, which is flat.
|
|
175
|
+
//
|
|
176
|
+
// The full type list observed in one turn: assistant.turn_start,
|
|
177
|
+
// assistant.reasoning_delta, assistant.reasoning, assistant.message_start,
|
|
178
|
+
// assistant.message_delta, assistant.turn_end, assistant.idle,
|
|
179
|
+
// model.call_start, model.call_finished, session.auto_mode_resolved,
|
|
180
|
+
// session.usage_checkpoint, session.tools_updated, session.skills_loaded,
|
|
181
|
+
// session.mcp_servers_loaded, session.mcp_server_status_changed,
|
|
182
|
+
// session.custom_agents_updated, user.message, result.
|
|
183
|
+
//
|
|
184
|
+
// The ones with no entry here are deliberate: they carry no content the
|
|
185
|
+
// workspace shows (lifecycle bookkeeping, or `user.message`, which is our
|
|
186
|
+
// own prompt echoed back). They degrade to `unknown`, which is logged and
|
|
187
|
+
// ignored — mapping them would put noise in the chat.
|
|
188
|
+
'assistant.message_delta': 'text_delta', // data.deltaContent — THE answer
|
|
189
|
+
'assistant.reasoning': 'reasoning', // data.content — ONE whole block
|
|
190
|
+
result: 'done', // flat: sessionId, exitCode, usage
|
|
191
|
+
// NOT mapped on purpose: `assistant.reasoning_delta`. Copilot streams at
|
|
192
|
+
// TOKEN granularity — 81 deltas for one short thought in the captured turn —
|
|
193
|
+
// and every adapter here narrates progress in whole blocks (claude sends
|
|
194
|
+
// `block.text`, codex `item.text`). Mapping the deltas posted one chat
|
|
195
|
+
// message per token: a column of "How" / "can" / "I" / "help" / "?" in the
|
|
196
|
+
// workspace. `assistant.reasoning` carries the same content as a single
|
|
197
|
+
// block afterwards, so that is what gets narrated.
|
|
198
|
+
'session.usage_checkpoint': 'usage',
|
|
199
|
+
'session.auto_mode_resolved': 'usage', // data.chosenModel under Auto
|
|
200
|
+
'model.call_finished': 'usage',
|
|
201
|
+
|
|
202
|
+
// ── INFERRED — no live sample yet ───────────────────────────────────────
|
|
203
|
+
// The captured turn was a plain greeting, so no tool/file/shell/permission
|
|
204
|
+
// event appeared in it. The names below predate that capture and are kept
|
|
205
|
+
// as a net: they cost nothing (the real names above are matched first) and
|
|
206
|
+
// an unrecognized event is logged, never rendered. Replace them the moment
|
|
207
|
+
// a tool-using turn is captured — do not add more guesses.
|
|
172
208
|
session: 'session', 'session.created': 'session', 'session.started': 'session',
|
|
173
209
|
session_started: 'session', thread: 'session', 'thread.started': 'session',
|
|
174
|
-
// streaming assistant text (qualified names only — no bare 'delta'/'token')
|
|
175
210
|
'text.delta': 'text_delta', text_delta: 'text_delta',
|
|
176
211
|
'message.delta': 'text_delta', 'assistant.delta': 'text_delta',
|
|
177
212
|
content_block_delta: 'text_delta',
|
|
178
|
-
// final assistant text (qualified/explicit names only — no bare 'message')
|
|
179
213
|
text: 'text', 'message.completed': 'text', 'assistant.message': 'text',
|
|
180
214
|
assistant: 'text', completion: 'text', agent_message: 'text',
|
|
181
|
-
// reasoning / status narration
|
|
182
215
|
reasoning: 'reasoning', thinking: 'reasoning', thought: 'reasoning',
|
|
183
216
|
status: 'reasoning', progress: 'reasoning',
|
|
184
217
|
// tool calls
|
|
@@ -250,7 +283,14 @@ function classifyEvent(obj) {
|
|
|
250
283
|
const itemKind = itemType ? EVENT_KIND_BY_TYPE[itemType.toLowerCase()] : undefined;
|
|
251
284
|
if (itemKind) kind = itemKind;
|
|
252
285
|
}
|
|
253
|
-
|
|
286
|
+
// Copilot puts every event's payload under `data`, with the envelope
|
|
287
|
+
// (type/id/timestamp/parentId) on the outside — so reading fields off the
|
|
288
|
+
// top level found nothing and the answer came back empty. `result` is the
|
|
289
|
+
// one exception: it is flat, and falls through to `obj` here.
|
|
290
|
+
const data = (obj.data && typeof obj.data === 'object' && !Array.isArray(obj.data))
|
|
291
|
+
? obj.data
|
|
292
|
+
: null;
|
|
293
|
+
const src = item || data || obj;
|
|
254
294
|
|
|
255
295
|
switch (kind) {
|
|
256
296
|
case 'session': {
|
|
@@ -261,7 +301,9 @@ function classifyEvent(obj) {
|
|
|
261
301
|
return { kind: 'session', sessionId };
|
|
262
302
|
}
|
|
263
303
|
case 'text_delta': {
|
|
264
|
-
|
|
304
|
+
// `deltaContent` is Copilot's own field name, verified on v1.0.83.
|
|
305
|
+
const text = _coerceText(src.deltaContent) ?? _coerceText(src.delta)
|
|
306
|
+
?? _coerceText(src.text) ?? _coerceText(src.content) ?? _coerceText(src);
|
|
265
307
|
return { kind: 'text_delta', text: text || '' };
|
|
266
308
|
}
|
|
267
309
|
case 'text': {
|
|
@@ -269,7 +311,8 @@ function classifyEvent(obj) {
|
|
|
269
311
|
return { kind: 'text', text: text || '' };
|
|
270
312
|
}
|
|
271
313
|
case 'reasoning': {
|
|
272
|
-
const text = _coerceText(src.
|
|
314
|
+
const text = _coerceText(src.deltaContent) ?? _coerceText(src.text)
|
|
315
|
+
?? _coerceText(src.reasoning) ?? _coerceText(src.message)
|
|
273
316
|
?? _coerceText(src.content) ?? _firstString(src, ['status', 'label', 'title']) ?? '';
|
|
274
317
|
return { kind: 'reasoning', text };
|
|
275
318
|
}
|
|
@@ -313,7 +356,14 @@ function classifyEvent(obj) {
|
|
|
313
356
|
}
|
|
314
357
|
case 'done': {
|
|
315
358
|
const status = _firstString(src, ['status', 'reason', 'result']) || 'completed';
|
|
316
|
-
|
|
359
|
+
// The `result` frame is also where the real session id finally shows up
|
|
360
|
+
// — there is no session event earlier in the stream. Carrying it here is
|
|
361
|
+
// what lets the next turn resume instead of starting over. Read from the
|
|
362
|
+
// top level too: `result` is flat, so `src` is already `obj`, but an
|
|
363
|
+
// `item`-wrapped variant would not be.
|
|
364
|
+
const sessionId = _firstString(src, ['sessionId', 'session_id'])
|
|
365
|
+
?? _firstString(obj, ['sessionId', 'session_id']);
|
|
366
|
+
return sessionId ? { kind: 'done', status, sessionId } : { kind: 'done', status };
|
|
317
367
|
}
|
|
318
368
|
case 'error': {
|
|
319
369
|
const err = (src.error && typeof src.error === 'object') ? src.error : src;
|
package/src/adapters/copilot.js
CHANGED
|
@@ -38,7 +38,11 @@
|
|
|
38
38
|
const fs = require('fs');
|
|
39
39
|
const os = require('os');
|
|
40
40
|
const path = require('path');
|
|
41
|
-
const { execSync, execFileSync
|
|
41
|
+
const { execSync, execFileSync } = require('child_process');
|
|
42
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
43
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
44
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
45
|
+
const { spawn } = require('../wsl');
|
|
42
46
|
|
|
43
47
|
const BaseAdapter = require('./base');
|
|
44
48
|
const { buildOpenclawSystemPrompt } = require('./workspace-prompt');
|
|
@@ -326,6 +330,7 @@ class CopilotAdapter extends BaseAdapter {
|
|
|
326
330
|
endpoint: this.endpoint,
|
|
327
331
|
token: this.token,
|
|
328
332
|
mode: this._mode,
|
|
333
|
+
model: this.modelLabel(),
|
|
329
334
|
disabledModules: this.disabledModules,
|
|
330
335
|
...this.pinnedPromptOpts(channelName),
|
|
331
336
|
});
|
|
@@ -606,10 +611,14 @@ class CopilotAdapter extends BaseAdapter {
|
|
|
606
611
|
}
|
|
607
612
|
break;
|
|
608
613
|
case 'text_delta':
|
|
609
|
-
//
|
|
610
|
-
//
|
|
614
|
+
// Accumulate only — never narrate. These deltas ARE the final
|
|
615
|
+
// answer being streamed a token at a time (Copilot's
|
|
616
|
+
// `assistant.message_delta`), and the answer is posted in full
|
|
617
|
+
// when the turn ends. Sending each one as `thinking` published the
|
|
618
|
+
// reply twice: once as a column of one-word messages, then again
|
|
619
|
+
// whole.
|
|
611
620
|
if (sawToolSinceText) { finalParts.length = 0; deltaBuf = ''; sawToolSinceText = false; }
|
|
612
|
-
if (ev.text)
|
|
621
|
+
if (ev.text) deltaBuf += ev.text;
|
|
613
622
|
break;
|
|
614
623
|
case 'text':
|
|
615
624
|
// A complete message supersedes the deltas streamed for this block.
|
|
@@ -655,6 +664,13 @@ class CopilotAdapter extends BaseAdapter {
|
|
|
655
664
|
errorEvent = errorEvent || { message: ev.message, code: ev.code };
|
|
656
665
|
break;
|
|
657
666
|
case 'done':
|
|
667
|
+
// Copilot reports its session id only in the terminal `result`
|
|
668
|
+
// frame — no session event precedes it — so this is the one place
|
|
669
|
+
// a resumable id can be captured.
|
|
670
|
+
if (ev.sessionId && this._channelSessions[channel] !== ev.sessionId) {
|
|
671
|
+
this._channelSessions[channel] = ev.sessionId;
|
|
672
|
+
this._saveSessions();
|
|
673
|
+
}
|
|
658
674
|
break;
|
|
659
675
|
case 'unknown':
|
|
660
676
|
// Preserve a redacted diagnostic; never crash the task.
|
package/src/adapters/cursor.js
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const os = require('os');
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const { execSync
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
17
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
18
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
19
|
+
const { spawn } = require('../wsl');
|
|
16
20
|
|
|
17
21
|
const BaseAdapter = require('./base');
|
|
18
22
|
const { formatAttachmentsForPrompt, SESSION_DEFAULT_RE, generateSessionTitle } = require('./utils');
|
package/src/adapters/deepseek.js
CHANGED
|
@@ -47,7 +47,11 @@
|
|
|
47
47
|
const fs = require('fs');
|
|
48
48
|
const os = require('os');
|
|
49
49
|
const path = require('path');
|
|
50
|
-
const { execFileSync,
|
|
50
|
+
const { execFileSync, execSync } = require('child_process');
|
|
51
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
52
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
53
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
54
|
+
const { spawn } = require('../wsl');
|
|
51
55
|
|
|
52
56
|
const BaseAdapter = require('./base');
|
|
53
57
|
const { whichBinary, getEnhancedEnv } = require('../paths');
|
package/src/adapters/gemini.js
CHANGED
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const os = require('os');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const { execSync
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
16
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
17
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
18
|
+
const { spawn, resolveWslBinary } = require('../wsl');
|
|
15
19
|
|
|
16
20
|
const BaseAdapter = require('./base');
|
|
17
21
|
const { whereBinary } = require('../paths');
|
|
@@ -183,7 +187,10 @@ class GeminiAdapter extends BaseAdapter {
|
|
|
183
187
|
if (fs.existsSync(c)) return c;
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
|
|
190
|
+
// Nothing native anywhere. Last of all, look inside WSL: a CLI the user
|
|
191
|
+
// installed in their distro is a real install, and the marked path it comes
|
|
192
|
+
// back as is what the spawn bridge in ../wsl turns into a wsl.exe run.
|
|
193
|
+
return resolveWslBinary('gemini');
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
// The Gemini CLI refuses to run non-interactively unless an auth method is
|
|
@@ -232,6 +239,7 @@ class GeminiAdapter extends BaseAdapter {
|
|
|
232
239
|
workspaceId: this.workspaceId,
|
|
233
240
|
channelName,
|
|
234
241
|
mode: this._mode,
|
|
242
|
+
model: this.modelLabel(),
|
|
235
243
|
decisionLog: pinned.decisionLog ? { ...pinned.decisionLog, writeAccess: false } : null,
|
|
236
244
|
glossary: pinned.glossary ? { ...pinned.glossary, writeAccess: false } : null,
|
|
237
245
|
});
|
package/src/adapters/goose.js
CHANGED
|
@@ -28,7 +28,11 @@ const fs = require('fs');
|
|
|
28
28
|
const path = require('path');
|
|
29
29
|
const os = require('os');
|
|
30
30
|
const crypto = require('crypto');
|
|
31
|
-
const {
|
|
31
|
+
const { execSync, execFileSync } = require('child_process');
|
|
32
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
33
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
34
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
35
|
+
const { spawn } = require('../wsl');
|
|
32
36
|
|
|
33
37
|
const BaseAdapter = require('./base');
|
|
34
38
|
const { GooseStreamParser, classifyGooseError, redactSecrets } = require('./goose-stream');
|
|
@@ -249,7 +253,7 @@ class GooseAdapter extends BaseAdapter {
|
|
|
249
253
|
|
|
250
254
|
_buildSystemPrompt(channelName) {
|
|
251
255
|
const pinned = this.pinnedPromptOpts(channelName);
|
|
252
|
-
let prompt = buildWorkspaceIdentity(this.agentName, this.workspaceId, channelName, this._mode)
|
|
256
|
+
let prompt = buildWorkspaceIdentity(this.agentName, this.workspaceId, channelName, this._mode, 'mcp', this.modelLabel())
|
|
253
257
|
+ buildCollaborationPrompt()
|
|
254
258
|
+ buildModePrompt(this._mode)
|
|
255
259
|
+ buildPinnedSections({
|
package/src/adapters/hermes.js
CHANGED
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const path = require('path');
|
|
20
|
-
const { execSync
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
// spawn() here is the WSL bridge from ../wsl: same signature as
|
|
22
|
+
// child_process.spawn, and a straight pass-through unless the resolved CLI
|
|
23
|
+
// lives on the other side of the Windows/WSL boundary.
|
|
24
|
+
const { spawn, bridgeSpawn } = require('../wsl');
|
|
21
25
|
|
|
22
26
|
const BaseAdapter = require('./base');
|
|
23
27
|
const { buildOpenclawSystemPrompt } = require('./workspace-prompt');
|
|
@@ -71,9 +75,6 @@ class HermesAdapter extends BaseAdapter {
|
|
|
71
75
|
|
|
72
76
|
_findHermesBinary() {
|
|
73
77
|
const home = os.homedir();
|
|
74
|
-
// Reset each call. When set, this._hermesBin is a path INSIDE WSL and must
|
|
75
|
-
// be invoked as `wsl -e <path> …` rather than spawned natively.
|
|
76
|
-
this._hermesViaWsl = false;
|
|
77
78
|
|
|
78
79
|
// Tier 1: PATH (enriched env so we see the dirs the launcher adds; a fresh
|
|
79
80
|
// install updates the user PATH, which the running daemon won't pick up).
|
|
@@ -112,40 +113,13 @@ class HermesAdapter extends BaseAdapter {
|
|
|
112
113
|
if (fs.existsSync(c)) return c;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
// Tier 3: Deep scan of every known bin dir
|
|
116
|
+
// Tier 3: Deep scan of every known bin dir — which ends inside WSL when
|
|
117
|
+
// nothing native turned up, so a hermes the user set up in their distro is
|
|
118
|
+
// found here and comes back marked for the ../wsl spawn bridge. Native
|
|
119
|
+
// Windows (Tiers 1-2) is still preferred.
|
|
116
120
|
const viaWhich = whichBinary('hermes');
|
|
117
121
|
if (viaWhich) return viaWhich;
|
|
118
122
|
|
|
119
|
-
// Tier 4 (Windows only): fall back to a WSL install if one exists. Native
|
|
120
|
-
// Windows (Tiers 1-3) is preferred, but a user who set hermes up inside
|
|
121
|
-
// WSL2 still works — resolve its absolute in-WSL path; _runHermes then
|
|
122
|
-
// invokes it as `wsl -e <path> …`.
|
|
123
|
-
if (IS_WINDOWS) {
|
|
124
|
-
const wslPath = this._resolveWslHermes();
|
|
125
|
-
if (wslPath) {
|
|
126
|
-
this._hermesViaWsl = true;
|
|
127
|
-
return wslPath;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Resolve hermes's absolute path inside the default WSL distro, or null.
|
|
136
|
-
* Uses a login shell (`bash -lc`) so the installer's PATH additions
|
|
137
|
-
* (~/.local/bin) are visible. Returns an absolute Linux path like
|
|
138
|
-
* /home/<user>/.local/bin/hermes.
|
|
139
|
-
*/
|
|
140
|
-
_resolveWslHermes() {
|
|
141
|
-
if (!IS_WINDOWS) return null;
|
|
142
|
-
try {
|
|
143
|
-
const out = execSync('wsl.exe -e bash -lc "command -v hermes"', {
|
|
144
|
-
encoding: 'utf-8', timeout: 8000, windowsHide: true,
|
|
145
|
-
}).trim();
|
|
146
|
-
const p = out.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
147
|
-
if (p && p.startsWith('/')) return p;
|
|
148
|
-
} catch {}
|
|
149
123
|
return null;
|
|
150
124
|
}
|
|
151
125
|
|
|
@@ -240,6 +214,7 @@ class HermesAdapter extends BaseAdapter {
|
|
|
240
214
|
endpoint: this.endpoint,
|
|
241
215
|
token: this.token,
|
|
242
216
|
mode: this._mode,
|
|
217
|
+
model: this.modelLabel(),
|
|
243
218
|
disabledModules: this.disabledModules,
|
|
244
219
|
...this.pinnedPromptOpts(channelName),
|
|
245
220
|
}),
|
|
@@ -314,23 +289,17 @@ class HermesAdapter extends BaseAdapter {
|
|
|
314
289
|
|
|
315
290
|
const env = { ...(this.agentEnv || process.env) };
|
|
316
291
|
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
// hermes then uses its own config
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (this._hermesViaWsl) {
|
|
324
|
-
spawnBin = 'wsl.exe';
|
|
325
|
-
spawnArgs = ['-e', this._hermesBin, ...args];
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const proc = spawn(spawnBin, spawnArgs, {
|
|
292
|
+
// A hermes that lives inside WSL is rewritten to `wsl.exe -e <path> …` by
|
|
293
|
+
// the bridge in ../wsl (which also forces detached off, there being no
|
|
294
|
+
// process group on the far side, and hands the distro the agent's env via
|
|
295
|
+
// WSLENV). hermes then uses its own config — ~/.hermes inside the distro —
|
|
296
|
+
// for model and keys. Nothing to special-case here.
|
|
297
|
+
const proc = spawn(this._hermesBin, args, {
|
|
329
298
|
env,
|
|
330
299
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
331
|
-
// No process group on Windows
|
|
332
|
-
//
|
|
333
|
-
detached: !IS_WINDOWS
|
|
300
|
+
// No process group on Windows (can't signal one); windowsHide keeps a
|
|
301
|
+
// console from flashing up.
|
|
302
|
+
detached: !IS_WINDOWS,
|
|
334
303
|
windowsHide: true,
|
|
335
304
|
});
|
|
336
305
|
this._channelProcesses[channelName] = proc;
|
|
@@ -469,9 +438,10 @@ class HermesAdapter extends BaseAdapter {
|
|
|
469
438
|
|
|
470
439
|
const { execFileSync } = require('child_process');
|
|
471
440
|
const run = (args) => {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
441
|
+
// Same rewrite the spawn bridge does, for the one call here that needs to
|
|
442
|
+
// be synchronous: an in-distro hermes is only reachable through wsl.exe.
|
|
443
|
+
const { file, args: argv } = bridgeSpawn(bin, args);
|
|
444
|
+
execFileSync(file, argv, {
|
|
475
445
|
stdio: 'ignore',
|
|
476
446
|
timeout: 20000,
|
|
477
447
|
windowsHide: true,
|