ur-agent 1.35.1 → 1.36.1

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/README.md CHANGED
@@ -68,8 +68,14 @@ handing work off to other tools or agents when needed.
68
68
 
69
69
  ### Requirements
70
70
 
71
- - Bun. This repository is configured with `bun@1.3.14`.
72
- - A Node.js-compatible shell environment (Node 18+ for the npm launcher).
71
+ - Bun `>=1.3.0` (this repository pins `bun@1.3.14`). Bun is mandatory: every
72
+ install path npm, GitHub, or source checkout — runs the CLI through Bun,
73
+ not Node. UR-AGENT is not Node-native.
74
+ - Node.js `>=18.18` only to start the npm-installed launcher script
75
+ (`bin/ur.js`), which immediately checks for Bun and re-execs into it. If Bun
76
+ is missing, the launcher errors out instead of falling back to Node.
77
+ - `sharp` installs automatically as a native runtime dependency (image
78
+ resizing for file reads, pasted images, and multimodal provider input).
73
79
  - For the default local setup: a running Ollama app or server
74
80
  (`http://localhost:11434/api`). Any other supported provider (API key,
75
81
  OpenAI-compatible server, or subscription CLI) works without Ollama.
@@ -252,7 +258,10 @@ identity line in the system prompt reflects it too:
252
258
  for LM Studio/llama.cpp/vLLM; the native API for Ollama).
253
259
  - **Subscription CLIs** (Codex, Claude Code, Gemini, Antigravity) are external
254
260
  app bridges: selecting one dispatches the turn through the vendor's official
255
- CLI using your subscription. Log in with `ur auth <provider>`.
261
+ CLI using your subscription. Log in with `ur auth <provider>`. UR-native
262
+ tool calling, UR-native streaming, UR Bash/File tool execution, and sandbox
263
+ guarantees apply to UR-run tools and final UR output — not to what the
264
+ external CLI does internally (see Provider Guide below).
256
265
  - **Subscription** access does not list fake models. If no independent
257
266
  subscription backend is configured, `/model` marks it unavailable and asks you
258
267
  to choose a connected local, server, or API provider.
@@ -502,9 +511,10 @@ settings, generated indexes, memory, logs, and secrets out of Git.
502
511
 
503
512
  ## Architecture
504
513
 
505
- - `bin/ur.js` is the global launcher. It reads package metadata, uses the
506
- bundled `dist/cli.js` when available, and otherwise starts the TypeScript CLI
507
- through Bun.
514
+ - `bin/ur.js` is the global launcher, invoked via Node's shebang. It always
515
+ executes the CLI through Bun — `bun dist/cli.js` when the bundle is present,
516
+ otherwise `bun run` against the TypeScript entrypoint — and exits with an
517
+ install error if Bun is not found. Node never runs the CLI itself.
508
518
  - `src/entrypoints/cli.tsx` handles fast startup paths such as `--version`,
509
519
  A2A serving, background sessions, bridge mode, and daemon paths before loading
510
520
  the full CLI.
@@ -546,8 +556,16 @@ the permission boundary matters.
546
556
  scripts, instruction files, `.ur/verify.json`, and safety config.
547
557
  - The deep verification subagent is available through `/verify` and can be
548
558
  auto-enabled with `UR_VERIFIER_AUTO_SUBAGENT=1`.
549
- - OS-level sandbox support is available on macOS and Linux through UR's
550
- sandbox settings.
559
+ - UR enforces permission and sandbox policy before running UR Bash/File
560
+ tools, not after. Sandbox mode is `disabled` (default), `recommended`
561
+ (`sandbox.enabled: true`, best-effort), or `required`
562
+ (`sandbox.enabled: true` + `sandbox.failIfUnavailable: true`) — `required`
563
+ fails closed and refuses to start if OS sandbox support is unavailable.
564
+ OS confinement uses `sandbox-exec` (Seatbelt) on macOS or `bwrap`
565
+ (bubblewrap) on Linux/WSL2; see `ur sandbox status`.
566
+ - This sandbox wraps UR-run Bash/File tool commands only. It does not extend
567
+ to actions a subscription CLI provider performs internally — see
568
+ [Provider Guide](docs/providers.md).
551
569
  - MCP servers can access external services; only enable servers you trust.
552
570
 
553
571
  See [Configuration](docs/CONFIGURATION.md), [Validation](docs/VALIDATION.md),
package/bin/ur.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process'
2
+ import { spawn, spawnSync } from 'node:child_process'
3
3
  import { existsSync, readFileSync } from 'node:fs'
4
- import { createServer } from 'node:http'
5
4
  import { dirname, resolve } from 'node:path'
6
5
  import { fileURLToPath } from 'node:url'
7
6
 
@@ -39,186 +38,40 @@ const bun = process.env.BUN_BIN || process.env.BUN_EXECUTABLE || 'bun'
39
38
  const ollamaModel =
40
39
  process.env.OLLAMA_MODEL || process.env.UR_MODEL
41
40
  const userArgs = process.argv.slice(2)
42
-
43
- function argValue(flag, fallback) {
44
- const index = userArgs.indexOf(flag)
45
- return index === -1 ? fallback : (userArgs[index + 1] ?? fallback)
46
- }
47
-
48
- function isLoopback(host) {
49
- return host === '127.0.0.1' || host === 'localhost' || host === '::1'
50
- }
51
-
52
- function sendJson(res, status, body) {
53
- res.writeHead(status, { 'content-type': 'application/json' })
54
- res.end(JSON.stringify(body, null, 2))
41
+ const requiredBun = packageMetadata.engines?.bun ?? '>=1.3.0'
42
+
43
+ function printBunRuntimeError(detail) {
44
+ const attempted = bun
45
+ const source =
46
+ process.env.BUN_BIN
47
+ ? 'BUN_BIN'
48
+ : process.env.BUN_EXECUTABLE
49
+ ? 'BUN_EXECUTABLE'
50
+ : 'PATH'
51
+ console.error(
52
+ [
53
+ `UR-AGENT requires Bun ${requiredBun} at runtime because the published CLI bundle is built with --target bun.`,
54
+ `Could not execute "${attempted}" from ${source}.${detail ? ` ${detail}` : ''}`,
55
+ 'Install Bun from https://bun.sh, or set BUN_BIN to the absolute path of a Bun executable.',
56
+ ].join('\n'),
57
+ )
55
58
  }
56
59
 
57
- function readJson(req) {
58
- return new Promise(resolve => {
59
- const chunks = []
60
- req.on('data', chunk => chunks.push(Buffer.from(chunk)))
61
- req.on('end', () => {
62
- try {
63
- resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'))
64
- } catch {
65
- resolve(null)
66
- }
67
- })
68
- req.on('error', () => resolve(null))
69
- })
70
- }
71
-
72
- function buildAgentCard(baseUrl) {
73
- return {
74
- protocolVersion: '0.3.0',
75
- name: 'UR-AGENT',
76
- description:
77
- 'Local-first terminal coding agent powered through the local Ollama app, with MCP tools, custom agents, browser workflows, memory, verifier gates, and permission controls.',
78
- url: `${baseUrl}/a2a`,
79
- version,
80
- documentationUrl:
81
- 'https://github.com/Maitham16/UR/blob/master/docs/AGENT_TRENDS.md',
82
- capabilities: {
83
- streaming: true,
84
- pushNotifications: false,
85
- stateTransitionHistory: true,
86
- },
87
- defaultInputModes: ['text/plain', 'text/markdown', 'application/json'],
88
- defaultOutputModes: ['text/plain', 'text/markdown', 'application/json'],
89
- provider: {
90
- organization: 'Maitham Al-rubaye',
91
- url: 'https://github.com/Maitham16/UR',
92
- },
93
- skills: [
94
- {
95
- id: 'coding-agent',
96
- name: 'Coding Agent',
97
- description:
98
- 'Read, edit, test, verify, and explain code inside a local workspace with permission controls.',
99
- tags: ['coding', 'terminal', 'verification'],
100
- examples: [
101
- 'Fix this failing test and run the relevant checks.',
102
- 'Review the current diff for behavioral regressions.',
103
- ],
104
- inputModes: ['text/plain', 'text/markdown'],
105
- outputModes: ['text/plain', 'text/markdown'],
106
- },
107
- ],
108
- }
109
- }
110
-
111
- function runAgentPrompt(prompt) {
112
- const childArgs = existsSync(bundledEntrypoint)
113
- ? [bundledEntrypoint, '-p', '--output-format', 'json', prompt]
114
- : [
115
- 'run',
116
- '--preload',
117
- preload,
118
- '--define',
119
- defineMacro('MACRO.VERSION', version),
120
- '--define',
121
- defineMacro('MACRO.BUILD_TIME', ''),
122
- '--define',
123
- defineMacro('MACRO.PACKAGE_URL', packageName),
124
- '--define',
125
- defineMacro('MACRO.NATIVE_PACKAGE_URL', undefined),
126
- '--define',
127
- defineMacro('MACRO.FEEDBACK_CHANNEL', issuesUrl),
128
- '--define',
129
- defineMacro('MACRO.ISSUES_EXPLAINER', `file an issue at ${issuesUrl}`),
130
- '--define',
131
- defineMacro('MACRO.VERSION_CHANGELOG', ''),
132
- entrypoint,
133
- '-p',
134
- '--output-format',
135
- 'json',
136
- prompt,
137
- ]
138
-
139
- return new Promise(resolve => {
140
- const child = spawn(bun, childArgs, {
141
- cwd: process.cwd(),
142
- env: {
143
- ...process.env,
144
- ...(ollamaModel ? { OLLAMA_MODEL: ollamaModel } : {}),
145
- },
146
- stdio: ['ignore', 'pipe', 'pipe'],
147
- })
148
- const stdout = []
149
- const stderr = []
150
- child.stdout.on('data', chunk => stdout.push(Buffer.from(chunk)))
151
- child.stderr.on('data', chunk => stderr.push(Buffer.from(chunk)))
152
- child.on('error', error => {
153
- resolve({ code: 1, stdout: '', stderr: error.message })
154
- })
155
- child.on('exit', code => {
156
- resolve({
157
- code: code ?? 1,
158
- stdout: Buffer.concat(stdout).toString('utf8'),
159
- stderr: Buffer.concat(stderr).toString('utf8'),
160
- })
161
- })
60
+ function assertBunAvailable() {
61
+ const result = spawnSync(bun, ['--version'], {
62
+ encoding: 'utf8',
63
+ stdio: ['ignore', 'pipe', 'pipe'],
162
64
  })
163
- }
164
65
 
165
- function runA2AServer() {
166
- const host = argValue('--host', '127.0.0.1')
167
- const port = Number(argValue('--port', '8765'))
168
- const token = argValue('--token')
169
- const dryRun = userArgs.includes('--dry-run')
170
- if (!Number.isInteger(port) || port < 0 || port > 65535) {
171
- console.error(`Invalid --port value: ${argValue('--port')}`)
66
+ if (result.error) {
67
+ printBunRuntimeError(result.error.message)
172
68
  process.exit(1)
173
69
  }
174
- if (!isLoopback(host) && !token) {
175
- console.error('Refusing to bind a2a server off-loopback without --token')
176
- process.exit(1)
70
+ if (result.status !== 0) {
71
+ const detail = (result.stderr || result.stdout || '').trim()
72
+ printBunRuntimeError(detail || `Exited with status ${result.status}.`)
73
+ process.exit(result.status ?? 1)
177
74
  }
178
-
179
- const baseUrl = `http://${host}:${port}`
180
- const server = createServer(async (req, res) => {
181
- const url = new URL(req.url ?? '/', baseUrl)
182
- if (req.method === 'GET' && url.pathname === '/healthz') {
183
- sendJson(res, 200, { ok: true })
184
- return
185
- }
186
- if (
187
- req.method === 'GET' &&
188
- (url.pathname === '/.well-known/agent-card.json' ||
189
- url.pathname === '/agent-card.json')
190
- ) {
191
- sendJson(res, 200, buildAgentCard(baseUrl))
192
- return
193
- }
194
- if (req.method === 'POST' && url.pathname === '/a2a/tasks') {
195
- if (token && req.headers.authorization !== `Bearer ${token}`) {
196
- sendJson(res, 401, { error: 'unauthorized' })
197
- return
198
- }
199
- const body = await readJson(req)
200
- const prompt =
201
- body && typeof body.prompt === 'string' ? body.prompt.trim() : ''
202
- if (!prompt) {
203
- sendJson(res, 400, { error: 'missing prompt' })
204
- return
205
- }
206
- const command = [bun, bundledEntrypoint, '-p', '--output-format', 'json', prompt]
207
- if (dryRun) {
208
- sendJson(res, 200, { dryRun: true, command })
209
- return
210
- }
211
- const result = await runAgentPrompt(prompt)
212
- sendJson(res, result.code === 0 ? 200 : 500, result)
213
- return
214
- }
215
- sendJson(res, 404, { error: 'not found' })
216
- })
217
- server.listen(port, host, () => {
218
- const actual = server.address()
219
- const actualPort = actual && typeof actual === 'object' ? actual.port : port
220
- console.log(`A2A server listening on http://${host}:${actualPort}`)
221
- })
222
75
  }
223
76
 
224
77
  const args =
@@ -246,6 +99,8 @@ const args =
246
99
  ...userArgs,
247
100
  ]
248
101
 
102
+ assertBunAvailable()
103
+
249
104
  const child = spawn(bun, args, {
250
105
  cwd: process.cwd(),
251
106
  env: {
@@ -257,9 +112,7 @@ const child = spawn(bun, args, {
257
112
 
258
113
  child.on('error', error => {
259
114
  if (error.code === 'ENOENT') {
260
- console.error(
261
- 'UR-AGENT requires Bun to run. Install Bun from https://bun.sh, then retry.',
262
- )
115
+ printBunRuntimeError(error.message)
263
116
  process.exit(1)
264
117
  }
265
118