@openagents-org/agent-launcher 0.2.175 → 0.2.177

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.
@@ -1021,6 +1021,38 @@ function buildPiSystemPrompt({ agentName, workspaceId, channelName, endpoint, to
1021
1021
  return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
1022
1022
  }
1023
1023
 
1024
+ /**
1025
+ * Build the workspace briefing that seeds an OpenWorker session.
1026
+ *
1027
+ * Shape mirrors buildPiSystemPrompt — identity, browser directive,
1028
+ * collaboration, mode, then the REST block — because OpenWorker reaches the
1029
+ * workspace the same way: its own shell tool plus curl, with no MCP wiring from
1030
+ * our side (registering an MCP server there writes into the user's global
1031
+ * OpenWorker config, which is not ours to edit).
1032
+ *
1033
+ * Two differences from every other builder here, both forced by OpenWorker
1034
+ * having no system-prompt hook at all:
1035
+ *
1036
+ * - `execTool` is `run_shell`, the actual name of its shell tool. Naming a
1037
+ * tool the agent does not have is how a model ends up printing curl
1038
+ * commands as text instead of running them.
1039
+ * - This is prepended to the first USER message of a session rather than
1040
+ * installed as a system prompt. That makes it part of the persisted
1041
+ * conversation, so it is sent once and still in context on every later turn
1042
+ * — which is why it must read as a briefing, not as a system directive.
1043
+ */
1044
+ function buildOpenWorkerSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules, browserEnabled = false }) {
1045
+ const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, mode, 'skills');
1046
+ const directive = buildBrowserDirective(browserEnabled);
1047
+ const collab = buildCollaborationPrompt('skills', workspaceSkillName(agentName));
1048
+ const modePrompt = buildModePrompt(mode);
1049
+ const api = buildApiSkillsPrompt({
1050
+ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode,
1051
+ execTool: 'run_shell',
1052
+ });
1053
+ return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
1054
+ }
1055
+
1024
1056
  /**
1025
1057
  * Build the task FILE contents for the DeepSeek Harness adapter.
1026
1058
  *
@@ -1223,6 +1255,7 @@ module.exports = {
1223
1255
  buildOpenCodeSkillMd,
1224
1256
  buildCommandCodeSkillMd,
1225
1257
  buildPiSystemPrompt,
1258
+ buildOpenWorkerSystemPrompt,
1226
1259
  buildDeepSeekTaskFile,
1227
1260
  buildClaudeSkillMd,
1228
1261
  buildCursorSkillMd,
@@ -29,11 +29,10 @@
29
29
  'use strict';
30
30
 
31
31
  const fs = require('fs');
32
+ const os = require('os');
32
33
  const path = require('path');
33
34
  const { execFileSync } = require('child_process');
34
35
 
35
- const IS_MACOS = process.platform === 'darwin';
36
-
37
36
  /** Directories on PATH, minus any the caller wants skipped. */
38
37
  function pathDirs(exclude = []) {
39
38
  const raw = process.env.PATH || '';
@@ -154,30 +153,106 @@ function probeGit(deps = {}) {
154
153
 
155
154
  /**
156
155
  * How the user fixes each missing dependency, phrased for the install screen.
157
- * `action` is a hint the launcher UI can turn into a button; everything else
158
- * degrades to plain text.
156
+ *
157
+ * `action` is a hint the launcher UI can turn into a button. `summaryKey` and
158
+ * `alternativeKind` are the same thing for text: the launcher is translated
159
+ * and this package is not, so it looks up its own wording by those keys and
160
+ * keeps `summary` only as the fallback — which is also what the CLI and the
161
+ * install log print. `alternativeKind` in particular has to be carried rather
162
+ * than guessed: the alternative is Homebrew only on macOS, and a UI that
163
+ * labels every alternative "if you use Homebrew" is wrong on two of the three
164
+ * platforms we ship.
165
+ *
166
+ * @param {string} [platform] - seam for tests; production passes nothing.
159
167
  */
160
- function gitRemedy() {
161
- if (IS_MACOS) {
168
+ function gitRemedy(platform = process.platform) {
169
+ if (platform === 'darwin') {
162
170
  return {
163
171
  name: 'git',
164
172
  action: 'install-xcode-clt',
173
+ summaryKey: 'gitXcodeClt',
165
174
  summary: 'Git is required, and it comes with the Xcode Command Line Tools.',
166
175
  command: 'xcode-select --install',
167
176
  alternative: 'brew install git',
177
+ alternativeKind: 'homebrew',
168
178
  };
169
179
  }
170
180
  return {
171
181
  name: 'git',
172
182
  action: null,
183
+ summaryKey: 'git',
173
184
  summary: 'Git is required to download this agent.',
174
185
  command: 'sudo apt install git # or dnf / pacman, per your distro',
175
186
  alternative: null,
187
+ alternativeKind: null,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Is `uv` on PATH?
193
+ *
194
+ * Unlike git, nothing provisions uv for us: an agent whose install command IS
195
+ * `uv tool install …` and whose machine has no uv gets the shell's own
196
+ * "command not found" buried in installer output, several seconds after
197
+ * pressing Install. The probe is a plain filesystem check — running `uv
198
+ * --version` would be the same answer at the cost of a process spawn, and uv
199
+ * has no shim behaviour to catch out the way macOS's /usr/bin/git does.
200
+ *
201
+ * Also checks uv's own default install location, which its installer script
202
+ * adds to PATH via a shell profile a running GUI/daemon never re-read.
203
+ */
204
+ function probeUv(deps = {}) {
205
+ const platform = deps.platform || process.platform;
206
+ const exists = deps.exists || ((p) => fs.existsSync(p));
207
+ const home = deps.home || os.homedir();
208
+ const isWindows = platform === 'win32';
209
+ const search = deps.dirs || [
210
+ ...pathDirs(),
211
+ path.join(home, '.local', 'bin'),
212
+ path.join(home, '.cargo', 'bin'),
213
+ ...(isWindows ? [] : ['/opt/homebrew/bin', '/usr/local/bin']),
214
+ ];
215
+ const found = existsIn(search, 'uv', exists, isWindows);
216
+ return found ? { ok: true, detail: found } : { ok: false };
217
+ }
218
+
219
+ /** @param {string} [platform] - seam for tests; production passes nothing. */
220
+ function uvRemedy(platform = process.platform) {
221
+ const summary = 'uv is required to install this agent (it is a Python tool).';
222
+
223
+ if (platform === 'win32') {
224
+ return {
225
+ name: 'uv',
226
+ action: null,
227
+ summaryKey: 'uv',
228
+ summary,
229
+ // Spelled out instead of a bare `irm … | iex`, because "run this in a
230
+ // terminal" on Windows lands in cmd.exe as often as in PowerShell, and
231
+ // the bare form is a syntax error there.
232
+ command:
233
+ 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"',
234
+ // pipx is the obvious parallel to Homebrew, but a machine with no uv
235
+ // seldom has pipx either — whereas winget ships with Windows.
236
+ alternative: 'winget install --id=astral-sh.uv -e',
237
+ alternativeKind: 'winget',
238
+ };
239
+ }
240
+
241
+ const viaHomebrew = platform === 'darwin';
242
+ return {
243
+ name: 'uv',
244
+ action: null,
245
+ summaryKey: 'uv',
246
+ summary,
247
+ command: 'curl -LsSf https://astral.sh/uv/install.sh | sh',
248
+ alternative: viaHomebrew ? 'brew install uv' : 'pipx install uv',
249
+ alternativeKind: viaHomebrew ? 'homebrew' : 'pipx',
176
250
  };
177
251
  }
178
252
 
179
253
  const PROBES = {
180
254
  git: { probe: probeGit, remedy: gitRemedy },
255
+ uv: { probe: probeUv, remedy: uvRemedy },
181
256
  };
182
257
 
183
258
  /**
@@ -230,4 +305,7 @@ module.exports = {
230
305
  checkInstallPrereqs,
231
306
  missingPrereqError,
232
307
  probeGit,
308
+ probeUv,
309
+ gitRemedy,
310
+ uvRemedy,
233
311
  };
package/src/paths.js CHANGED
@@ -58,11 +58,13 @@ function getExtraBinDirs() {
58
58
  // CLIs whose own installer script picks a directory of its own.
59
59
  _addAgentInstallerPaths(dirs);
60
60
 
61
- // Aider (uv tool install) — its executable honors XDG_BIN_HOME /
61
+ // CLIs installed with `uv tool install` — the executable honors XDG_BIN_HOME /
62
62
  // XDG_DATA_HOME/../bin / ~/.local/bin and also always lands in the uv tools
63
63
  // venv. A GUI/daemon process won't see the installer's PATH edit, so add the
64
64
  // real install dirs explicitly (filtered to existing dirs by the caller).
65
+ // `coworker` is OpenWorker's distribution name — see uvToolBinDirs.
65
66
  for (const d of aiderBinDirs()) _push(dirs, d);
67
+ for (const d of uvToolBinDirs('coworker')) _push(dirs, d);
66
68
 
67
69
  // Also add the directory containing the current node binary
68
70
  try {
@@ -787,19 +789,23 @@ function primeBinaryLookup() {
787
789
  }
788
790
 
789
791
  /**
790
- * Directories where the Aider CLI executable can land, in the SAME priority the
791
- * official installer (aider.chat/install.{sh,ps1} → `uv tool install`) uses, so
792
- * detection matches reality on every platform:
792
+ * Directories where a `uv tool install`-ed CLI can land, in the SAME priority uv
793
+ * itself uses, so detection matches reality on every platform:
793
794
  * 1. $XDG_BIN_HOME
794
795
  * 2. $XDG_DATA_HOME/../bin
795
796
  * 3. ~/.local/bin (the default uv-tool / pipx / pip --user bin)
796
- * 4. the uv tools venv for aider-chat (Scripts on Windows, bin on Unix) — the
797
+ * 4. the uv tools venv for `pkg` (Scripts on Windows, bin on Unix) — the
797
798
  * executable always lands here on a successful `uv tool install`, even if
798
799
  * the bin-dir copy/PATH edit didn't happen.
800
+ *
801
+ * `pkg` is the DISTRIBUTION name uv keys its tool dir by, which is not always
802
+ * the command name: aider's CLI is `aider` from `aider-chat`, and OpenWorker's
803
+ * is `openworker-server` from `coworker`.
804
+ *
799
805
  * Uses os.homedir()/live env so it reflects the current process (test-friendly).
800
806
  * Returns directories only; callers join the platform binary names.
801
807
  */
802
- function aiderBinDirs() {
808
+ function uvToolBinDirs(pkg) {
803
809
  const home = os.homedir();
804
810
  const dirs = [];
805
811
  if (process.env.XDG_BIN_HOME) dirs.push(process.env.XDG_BIN_HOME);
@@ -808,17 +814,22 @@ function aiderBinDirs() {
808
814
  if (IS_WINDOWS) {
809
815
  const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
810
816
  const uvTools = process.env.UV_TOOL_DIR || path.join(appData, 'uv', 'tools');
811
- dirs.push(path.join(uvTools, 'aider-chat', 'Scripts'));
817
+ dirs.push(path.join(uvTools, pkg, 'Scripts'));
812
818
  dirs.push(path.join(home, 'bin'));
813
819
  } else {
814
820
  const uvTools = process.env.UV_TOOL_DIR
815
821
  || path.join(home, '.local', 'share', 'uv', 'tools');
816
- dirs.push(path.join(uvTools, 'aider-chat', 'bin'));
822
+ dirs.push(path.join(uvTools, pkg, 'bin'));
817
823
  dirs.push(path.join(home, 'bin'), '/usr/local/bin', '/opt/homebrew/bin');
818
824
  }
819
825
  return dirs;
820
826
  }
821
827
 
828
+ /** Aider's CLI, installed by aider.chat/install.{sh,ps1} → `uv tool install`. */
829
+ function aiderBinDirs() {
830
+ return uvToolBinDirs('aider-chat');
831
+ }
832
+
822
833
  /** Executable suffixes to try for a bare binary name, per platform. */
823
834
  const BIN_EXTS = IS_WINDOWS ? ['.cmd', '.exe', '.bat', ''] : [''];
824
835
 
@@ -889,6 +900,7 @@ module.exports = {
889
900
  getCorePrefix,
890
901
  defaultAgentWorkdir,
891
902
  aiderBinDirs,
903
+ uvToolBinDirs,
892
904
  IS_WINDOWS,
893
905
  IS_MACOS,
894
906
  SEP,
package/src/registry.js CHANGED
@@ -101,7 +101,16 @@ class Registry {
101
101
  // day-old) cached remote copy, same as install.
102
102
  if (b.resolve_env) entry.resolve_env = b.resolve_env;
103
103
  if (b.install) entry.install = { ...b.install };
104
- if (!entry.check_ready && b.check_ready) entry.check_ready = b.check_ready;
104
+ // check_ready is the auth/readiness contract of the adapter shipping in
105
+ // THIS core, so the bundled fields win over a remote copy that may be a
106
+ // release behind — the same rule as install, and for the same reason.
107
+ // Kimi is what proved it: the CLI rewrite added `login_command: kimi
108
+ // login`, but the server was still serving the API-key-only entry, and
109
+ // "fill it in only when absent" meant the sign-in never appeared.
110
+ // Spread rather than replace so anything the server adds on its own
111
+ // (auth_detected_labels and friends) survives.
112
+ if (b.check_ready)
113
+ entry.check_ready = { ...(entry.check_ready || {}), ...b.check_ready };
105
114
  if (!entry.launch && b.launch) entry.launch = b.launch;
106
115
  if (!entry.probe && b.probe) entry.probe = b.probe;
107
116
  // Always take featured/order/support from bundled (source of truth for ordering)
@@ -148,7 +157,8 @@ class Registry {
148
157
  const b = bundled.find((e) => e.name === agentType);
149
158
  if (b) {
150
159
  if (b.install) entry.install = { ...b.install };
151
- if (!entry.check_ready && b.check_ready) entry.check_ready = b.check_ready;
160
+ if (b.check_ready)
161
+ entry.check_ready = { ...(entry.check_ready || {}), ...b.check_ready };
152
162
  if (!entry.launch && b.launch) entry.launch = b.launch;
153
163
  if (!entry.probe && b.probe) entry.probe = b.probe;
154
164
  if ((!entry.env_config || !entry.env_config.length) && b.env_config?.length) entry.env_config = b.env_config;