ur-agent 1.45.6 → 1.46.0
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/CHANGELOG.md +31 -0
- package/QUALITY.md +8 -3
- package/README.md +8 -7
- package/RELEASE.md +9 -6
- package/dist/cli.js +52660 -33876
- package/docs/A2A.md +143 -0
- package/docs/ACP.md +61 -17
- package/docs/AGENT_FEATURES.md +22 -4
- package/docs/AGENT_TRENDS.md +87 -26
- package/docs/CONFIGURATION.md +37 -0
- package/docs/DEVELOPMENT.md +1 -0
- package/docs/IDE.md +20 -17
- package/docs/TROUBLESHOOTING.md +5 -3
- package/docs/USAGE.md +2 -2
- package/docs/VALIDATION.md +1 -1
- package/docs/providers.md +4 -0
- package/documentation/index.html +2 -2
- package/extensions/jetbrains-ur/README.md +12 -5
- package/extensions/jetbrains-ur/build.gradle.kts +22 -6
- package/extensions/jetbrains-ur/gradle.properties +1 -0
- package/extensions/jetbrains-ur/settings.gradle.kts +4 -0
- package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/SendSelectionAction.kt +39 -5
- package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/UrAcpClient.kt +52 -20
- package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/UrToolWindowFactory.kt +7 -4
- package/extensions/jetbrains-ur/src/main/resources/META-INF/plugin.xml +1 -1
- package/extensions/vscode-ur-inline-diffs/README.md +6 -2
- package/extensions/vscode-ur-inline-diffs/out/extension.js +328 -157
- package/extensions/vscode-ur-inline-diffs/package.json +33 -5
- package/package.json +65 -56
package/docs/IDE.md
CHANGED
|
@@ -13,8 +13,8 @@ stated honestly — nothing here claims support that does not exist:
|
|
|
13
13
|
below. Ships to VS Code, Cursor, and Windsurf (one extension; Cursor and
|
|
14
14
|
Windsurf are VS Code forks). This is the integration everything else on
|
|
15
15
|
this page is compared against.
|
|
16
|
-
- **Stdio ACP** (`ur acp stdio`) —
|
|
17
|
-
|
|
16
|
+
- **Stdio ACP** (`ur acp stdio`) — stable ACP v1 over stdio, backed by the
|
|
17
|
+
official TypeScript SDK and used by Zed and ACP-capable
|
|
18
18
|
Neovim clients. This remains a separate transport from the VS Code
|
|
19
19
|
extension's chat bridge below; the two are independent and neither depends
|
|
20
20
|
on the other.
|
|
@@ -33,14 +33,14 @@ stated honestly — nothing here claims support that does not exist:
|
|
|
33
33
|
| Cursor | UR Inline Diffs extension (VS Code fork) | `.vscode/settings.json` | Same extension as VS Code. |
|
|
34
34
|
| Windsurf | UR Inline Diffs extension (VS Code fork) | `.vscode/settings.json` | Same extension as VS Code. |
|
|
35
35
|
| Zed | stdio ACP | `.zed/settings.json` | Real Agent Client Protocol over stdio. |
|
|
36
|
-
| JetBrains | bundled experimental plugin + HTTP
|
|
36
|
+
| JetBrains | bundled experimental plugin + UR HTTP JSON-RPC | manual zip install | Project-scoped JSON-RPC session and Send Selection action; synchronous task result, not streaming. |
|
|
37
37
|
| Neovim | stdio ACP | snippet | Requires a third-party ACP client plugin. |
|
|
38
|
-
| Generic ACP | stdio ACP
|
|
38
|
+
| Generic ACP | stdio ACP | snippet | `ur acp stdio` is the native ACP transport. `ur acp serve` is a separate UR-specific HTTP API. |
|
|
39
39
|
|
|
40
40
|
## Commands
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
ur ide status # workspace,
|
|
43
|
+
ur ide status # workspace, UR HTTP server, provider/model, sandbox/verifier mode, plugin count, warnings
|
|
44
44
|
ur ide doctor # pass/warn/fail checks; reports missing config clearly
|
|
45
45
|
ur ide config <editor> # print setup + config snippet for the chosen editor
|
|
46
46
|
ur ide open # open the current project/worktree in a detected IDE
|
|
@@ -52,7 +52,7 @@ ur ide diff approve|reject <id>
|
|
|
52
52
|
`ur ide config` targets: `vscode`, `cursor`, `windsurf`, `zed`, `jetbrains`,
|
|
53
53
|
`neovim`, `generic-acp` (aliases like `nvim`, `intellij`, `code` also resolve).
|
|
54
54
|
|
|
55
|
-
`ur ide status` reports the active workspace, whether the
|
|
55
|
+
`ur ide status` reports the active workspace, whether the UR HTTP compatibility server is
|
|
56
56
|
running and on which port, the active provider/model and runtime backend,
|
|
57
57
|
sandbox mode, verifier mode, the number of loaded plugins, and any warnings.
|
|
58
58
|
Add `--json` for machine-readable output — this is the same JSON surface the
|
|
@@ -150,9 +150,12 @@ tasks (`ur bg list --json`) together in one place, with its own refresh
|
|
|
150
150
|
button and a clear empty state when there is nothing captured yet. Diff rows
|
|
151
151
|
support the same Open/Apply/Reject/Comment actions as the Inline Diffs view
|
|
152
152
|
(routed through the CLI, as described above). Background task rows are
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
actionable: **Start Background Task** offers isolated-worktree (recommended),
|
|
154
|
+
offline isolated-worktree, and current-workspace modes, confirms before
|
|
155
|
+
launch, and never enables permission bypass or PR publishing. Active tasks
|
|
156
|
+
can be canceled from their context menu. Opening a task retrieves the latest
|
|
157
|
+
2,000 log lines through `ur bg logs`, so the extension never trusts an
|
|
158
|
+
arbitrary manifest path as a file to open.
|
|
156
159
|
|
|
157
160
|
### Agent status card
|
|
158
161
|
|
|
@@ -170,7 +173,8 @@ CLI does not report renders as the literal word **unknown** — never a guess.
|
|
|
170
173
|
action the extension exposes: New Chat, Open Chat, Explain Selection, Fix
|
|
171
174
|
Selection, Generate Tests, Review Current Diff, Run Verifier, Provider
|
|
172
175
|
Status, Agent Status, Agent Options, Open Settings, Open Docs, Open
|
|
173
|
-
Artifacts, Run Spec, Run Workflow, and Refresh IDE
|
|
176
|
+
Artifacts, Run Spec, Run Workflow, Start Background Task, and Refresh IDE
|
|
177
|
+
Actions.
|
|
174
178
|
|
|
175
179
|
### Agent options panel
|
|
176
180
|
|
|
@@ -202,8 +206,9 @@ success it did not observe.
|
|
|
202
206
|
## Known limitations
|
|
203
207
|
|
|
204
208
|
- **JetBrains integration is experimental and non-streaming.** It uses the
|
|
205
|
-
loopback HTTP
|
|
206
|
-
it does not yet render token or tool-call streams.
|
|
209
|
+
loopback UR HTTP JSON-RPC server and returns the completed synchronous task output;
|
|
210
|
+
it does not yet render token or tool-call streams. Canceling the IDE progress
|
|
211
|
+
action does propagate to the active server-side session.
|
|
207
212
|
- **The lockfile/MCP IDE bridge is Phase 2 and not part of this MVP.** The
|
|
208
213
|
CLI already implements the *client* half of a lockfile-discovered MCP
|
|
209
214
|
connection to a running IDE (`src/utils/ide.ts`), used by the terminal
|
|
@@ -211,20 +216,18 @@ success it did not observe.
|
|
|
211
216
|
terminal-hosted `ur` session. The VS Code extension's chat panel does not
|
|
212
217
|
use this bridge — it talks to UR entirely through the `stream-json`
|
|
213
218
|
subprocess contract described above.
|
|
214
|
-
- **The
|
|
215
|
-
transport.** It is a separate, non-streaming JSON-RPC endpoint intended for
|
|
219
|
+
- **The UR HTTP server (`ur acp serve`) is neither an ACP binding nor the VS
|
|
220
|
+
Code chat streaming transport.** It is a separate, non-streaming JSON-RPC endpoint intended for
|
|
216
221
|
scripts and other clients; the VS Code extension does not route chat
|
|
217
222
|
through it, and `ur acp stdio` (used by Zed and ACP Neovim clients) is
|
|
218
223
|
unaffected by any of the VS Code work described here.
|
|
219
|
-
- **Background task actions are read-only** in the Actions panel — no start
|
|
220
|
-
or cancel from the panel yet.
|
|
221
224
|
- **Run Spec / Run Workflow** open chat with a prompt asking UR to list and
|
|
222
225
|
run the relevant items, rather than shelling out directly — `ur spec` and
|
|
223
226
|
`ur workflow` are full agentic commands, not one-shot JSON lookups.
|
|
224
227
|
|
|
225
228
|
## Troubleshooting
|
|
226
229
|
|
|
227
|
-
- **`ur ide status` shows "
|
|
230
|
+
- **`ur ide status` shows "UR HTTP server: not running":** start it with
|
|
228
231
|
`ur acp serve` (HTTP) or `ur acp stdio` (for ACP editors).
|
|
229
232
|
- **No IDE detected:** ensure the editor is running with the UR extension/plugin
|
|
230
233
|
installed, or generate config with `ur ide config <editor>`.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -208,12 +208,14 @@ ur plugin list
|
|
|
208
208
|
|
|
209
209
|
### ACP / editor connection issues
|
|
210
210
|
|
|
211
|
-
- Likely cause: the
|
|
212
|
-
|
|
213
|
-
- Fix:
|
|
211
|
+
- Likely cause: the native stdio command/config is wrong, or the optional UR
|
|
212
|
+
HTTP server used by JetBrains is not running.
|
|
213
|
+
- Fix: verify the stdio command is registered, check HTTP status when relevant,
|
|
214
|
+
regenerate the editor config, and run the IDE doctor.
|
|
214
215
|
|
|
215
216
|
```sh
|
|
216
217
|
ur acp status
|
|
218
|
+
ur acp stdio --help
|
|
217
219
|
ur ide doctor
|
|
218
220
|
ur ide config zed # or vscode, cursor, windsurf, jetbrains, neovim
|
|
219
221
|
```
|
package/docs/USAGE.md
CHANGED
|
@@ -268,7 +268,7 @@ UR includes slash commands and CLI subcommands for common workflows:
|
|
|
268
268
|
after one attempt and reports how to correct `--cwd`.
|
|
269
269
|
- `ur artifacts ...` to capture reviewable diffs, test runs, notes, and feedback
|
|
270
270
|
- `ur ide diff ...` to capture editor-readable inline diff bundles
|
|
271
|
-
- `ur acp
|
|
271
|
+
- `ur acp stdio` for the official-SDK Agent Client Protocol v1 editor transport, and `ur acp serve|stop|status` for the separate UR HTTP JSON-RPC API
|
|
272
272
|
- `ur exec ...` to run prompts in non-interactive mode with optional concurrency
|
|
273
273
|
- `ur eval run ...` to run a suite, grade results, and capture execution metrics
|
|
274
274
|
- `ur eval report ...` to show a saved report or write a single-suite dashboard
|
|
@@ -292,7 +292,7 @@ UR includes slash commands and CLI subcommands for common workflows:
|
|
|
292
292
|
- `ur trigger ...` to parse GitHub/Slack webhook payloads and optionally launch a headless run
|
|
293
293
|
- `ur agent-templates ...`, `ur agent-task ...`, `ur agent-inspect`, `ur agent-features`, and `ur agent-trends` for agent template, PR handoff, timeline, and coverage utilities
|
|
294
294
|
- `ur role-mode ...` to install built-in Architect, Code, Debug, and Ask role modes
|
|
295
|
-
- `ur a2a ...` for Agent Card metadata, delegation tokens,
|
|
295
|
+
- `ur a2a ...` for Agent Card metadata, issuer-signed delegation tokens, the stable A2A v0.3 JSON-RPC binding, and UR compatibility task routes
|
|
296
296
|
- `ur sdk ...` to scaffold TS/Python headless SDK examples
|
|
297
297
|
- `ur doctor` to inspect CLI health
|
|
298
298
|
- `ur update` or `ur upgrade` to check for updates
|
package/docs/VALIDATION.md
CHANGED
package/docs/providers.md
CHANGED
|
@@ -465,4 +465,8 @@ Common knobs:
|
|
|
465
465
|
PROVIDER_SMOKE_TIMEOUT_MS=30000
|
|
466
466
|
PROVIDER_SMOKE_MAX_RETRIES=0
|
|
467
467
|
PROVIDER_SMOKE_TOOL_CALLS=1
|
|
468
|
+
PROVIDER_SMOKE_OUTPUT=/tmp/ur-provider-smoke.json # --json only
|
|
468
469
|
```
|
|
470
|
+
|
|
471
|
+
Without `PROVIDER_SMOKE_OUTPUT`, `--json` also writes the latest report to
|
|
472
|
+
`diagnostics/provider-smoke/latest.json`.
|
package/documentation/index.html
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<main id="content" class="content">
|
|
45
45
|
<header class="topbar">
|
|
46
46
|
<div>
|
|
47
|
-
<p class="eyebrow">Version 1.
|
|
47
|
+
<p class="eyebrow">Version 1.46.0</p>
|
|
48
48
|
<h1>UR-Nexus Documentation</h1>
|
|
49
49
|
<p class="lead">A practical, tutorial-style reference for installing, configuring, automating, extending, and operating UR-Nexus.</p>
|
|
50
50
|
</div>
|
|
@@ -163,7 +163,7 @@ ur config set provider.fallback ollama</code></pre>
|
|
|
163
163
|
</article>
|
|
164
164
|
<article>
|
|
165
165
|
<h3>Status bar and updates</h3>
|
|
166
|
-
<pre><code>Ollama | llama3 | ask | main | update 1.
|
|
166
|
+
<pre><code>Ollama | llama3 | ask | main | update 1.46.0 available</code></pre>
|
|
167
167
|
<p>The interactive status bar shows only important runtime state: provider, model, mode, branch, active tasks, checks status when known, and update availability. It is hidden in CI, dumb terminals, and print mode.</p>
|
|
168
168
|
</article>
|
|
169
169
|
<article>
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
# UR for JetBrains (experimental scaffold)
|
|
2
2
|
|
|
3
|
-
Connects JetBrains IDEs to a running UR-Nexus agent over the local
|
|
4
|
-
(`ur acp serve --port 9100`).
|
|
3
|
+
Connects JetBrains IDEs to a running UR-Nexus agent over the local UR HTTP
|
|
4
|
+
JSON-RPC server (`ur acp serve --port 9100`). This endpoint is a UR-specific
|
|
5
|
+
compatibility API, not an Agent Client Protocol transport binding. The plugin
|
|
6
|
+
builds with IntelliJ Platform Gradle Plugin 2.18 and IDEA 2024.3. The official
|
|
7
|
+
Plugin Verifier reports the artifact compatible with IntelliJ IDEA 2024.3,
|
|
8
|
+
2025.1, and 2025.2. Install the `buildPlugin` zip via Settings → Plugins → ⚙ →
|
|
9
|
+
Install Plugin from Disk.
|
|
5
10
|
|
|
6
11
|
## What it wires up
|
|
7
12
|
|
|
8
|
-
- A tool window ("UR Agent") that checks the loopback
|
|
13
|
+
- A tool window ("UR Agent") that checks the loopback UR endpoint without
|
|
9
14
|
blocking the IDE event thread.
|
|
10
15
|
- An action (Tools → UR: Send Selection) that posts the current selection as
|
|
11
16
|
a prompt to a project-scoped JSON-RPC session at `/acp`. The current server
|
|
12
17
|
returns a completed synchronous task result; token streaming is not claimed.
|
|
18
|
+
The IDE progress action is cancelable and forwards cancellation to the active
|
|
19
|
+
server-side session instead of merely dismissing the progress UI.
|
|
13
20
|
|
|
14
21
|
## Build
|
|
15
22
|
|
|
16
23
|
```
|
|
17
24
|
cd extensions/jetbrains-ur
|
|
18
|
-
JAVA_HOME=$(brew --prefix openjdk@
|
|
25
|
+
JAVA_HOME=$(brew --prefix openjdk@21) gradle buildPlugin # → build/distributions/*.zip
|
|
19
26
|
```
|
|
20
27
|
|
|
21
|
-
The heavy lifting stays in UR's
|
|
28
|
+
The heavy lifting stays in UR's HTTP server — the plugin is intentionally a
|
|
22
29
|
thin client, so protocol changes land on the CLI side.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
|
|
2
|
+
|
|
1
3
|
plugins {
|
|
2
4
|
id("java")
|
|
3
|
-
id("org.jetbrains.kotlin.jvm") version "2.0
|
|
4
|
-
id("org.jetbrains.intellij.platform") version "2.
|
|
5
|
+
id("org.jetbrains.kotlin.jvm") version "2.4.0"
|
|
6
|
+
id("org.jetbrains.intellij.platform") version "2.18.1"
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
group = "dev.urnexus"
|
|
8
|
-
version = "1.
|
|
10
|
+
version = "1.46.0"
|
|
9
11
|
|
|
10
12
|
repositories {
|
|
11
13
|
mavenCentral()
|
|
@@ -14,21 +16,35 @@ repositories {
|
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
configurations.named("runtimeClasspath") {
|
|
20
|
+
// IntelliJ provides these at runtime. Scoping the exclusions here keeps
|
|
21
|
+
// them out of the distributable without breaking Gradle's compiler tools.
|
|
22
|
+
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
|
|
23
|
+
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-common")
|
|
24
|
+
exclude(group = "org.jetbrains", module = "annotations")
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
dependencies {
|
|
18
28
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
|
19
29
|
intellijPlatform {
|
|
20
|
-
intellijIdeaCommunity("2024.
|
|
30
|
+
intellijIdeaCommunity("2024.3.7.1")
|
|
21
31
|
}
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
intellijPlatform {
|
|
25
35
|
pluginConfiguration {
|
|
26
36
|
ideaVersion {
|
|
27
|
-
sinceBuild.set("
|
|
37
|
+
sinceBuild.set("243")
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
kotlin {
|
|
33
|
-
jvmToolchain(
|
|
43
|
+
jvmToolchain(21)
|
|
44
|
+
compilerOptions {
|
|
45
|
+
// IntelliJ's Java interfaces provide JVM default methods. Kotlin's
|
|
46
|
+
// compatibility mode would otherwise emit synthetic overrides for
|
|
47
|
+
// platform-internal methods and make the verifier reject the plugin.
|
|
48
|
+
jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY)
|
|
49
|
+
}
|
|
34
50
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kotlin.stdlib.default.dependency=false
|
|
@@ -8,8 +8,17 @@ import com.intellij.openapi.progress.ProgressIndicator
|
|
|
8
8
|
import com.intellij.openapi.progress.ProgressManager
|
|
9
9
|
import com.intellij.openapi.progress.Task
|
|
10
10
|
import com.intellij.openapi.application.ApplicationManager
|
|
11
|
+
import com.intellij.openapi.progress.ProcessCanceledException
|
|
12
|
+
import java.util.concurrent.CompletableFuture
|
|
13
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
14
|
+
import java.util.concurrent.locks.LockSupport
|
|
11
15
|
|
|
12
16
|
class SendSelectionAction : AnAction() {
|
|
17
|
+
override fun update(e: AnActionEvent) {
|
|
18
|
+
val selection = e.getData(CommonDataKeys.EDITOR)?.selectionModel?.selectedText
|
|
19
|
+
e.presentation.isEnabledAndVisible = e.project != null && !selection.isNullOrBlank()
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
override fun actionPerformed(e: AnActionEvent) {
|
|
14
23
|
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
|
|
15
24
|
val selection = editor.selectionModel.selectedText ?: return
|
|
@@ -17,12 +26,37 @@ class SendSelectionAction : AnAction() {
|
|
|
17
26
|
val client = project.getService(UrAcpClient::class.java)
|
|
18
27
|
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Sending selection to UR", true) {
|
|
19
28
|
override fun run(indicator: ProgressIndicator) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
indicator.isIndeterminate = true
|
|
30
|
+
val finished = AtomicBoolean(false)
|
|
31
|
+
val cancellationMonitor = CompletableFuture.runAsync {
|
|
32
|
+
while (!finished.get()) {
|
|
33
|
+
if (
|
|
34
|
+
indicator.isCanceled &&
|
|
35
|
+
runCatching { client.cancelPrompt() }.getOrDefault(false)
|
|
36
|
+
) return@runAsync
|
|
37
|
+
LockSupport.parkNanos(100_000_000)
|
|
38
|
+
if (Thread.currentThread().isInterrupted) return@runAsync
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
indicator.checkCanceled()
|
|
43
|
+
val result = client.sendPrompt(selection)
|
|
44
|
+
indicator.checkCanceled()
|
|
45
|
+
ApplicationManager.getApplication().invokeLater {
|
|
46
|
+
Messages.showInfoMessage(project, result.take(2000), "UR Agent")
|
|
47
|
+
}
|
|
48
|
+
} catch (cancelled: ProcessCanceledException) {
|
|
49
|
+
runCatching { client.cancelPrompt() }
|
|
50
|
+
throw cancelled
|
|
51
|
+
} catch (error: Exception) {
|
|
52
|
+
ApplicationManager.getApplication().invokeLater {
|
|
53
|
+
Messages.showErrorDialog(project, error.message ?: error.toString(), "UR Agent")
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
finished.set(true)
|
|
57
|
+
cancellationMonitor.cancel(true)
|
|
24
58
|
}
|
|
25
59
|
}
|
|
26
|
-
})
|
|
60
|
+
}.setCancelText("Cancel UR prompt"))
|
|
27
61
|
}
|
|
28
62
|
}
|
|
@@ -7,6 +7,7 @@ import java.net.http.HttpClient
|
|
|
7
7
|
import java.net.http.HttpRequest
|
|
8
8
|
import java.net.http.HttpResponse
|
|
9
9
|
import java.time.Duration
|
|
10
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
10
11
|
import java.util.concurrent.atomic.AtomicLong
|
|
11
12
|
import kotlinx.serialization.json.Json
|
|
12
13
|
import kotlinx.serialization.json.JsonElement
|
|
@@ -16,12 +17,14 @@ import kotlinx.serialization.json.buildJsonObject
|
|
|
16
17
|
import kotlinx.serialization.json.jsonObject
|
|
17
18
|
import kotlinx.serialization.json.jsonPrimitive
|
|
18
19
|
|
|
19
|
-
/** Project-scoped JSON-RPC client for UR's HTTP
|
|
20
|
+
/** Project-scoped JSON-RPC client for UR's HTTP compatibility endpoint. */
|
|
20
21
|
@Service(Service.Level.PROJECT)
|
|
21
22
|
class UrAcpClient(private val project: Project) {
|
|
22
23
|
private val baseUrl = "http://127.0.0.1:9100"
|
|
23
24
|
private val http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build()
|
|
24
25
|
private val ids = AtomicLong()
|
|
26
|
+
private val promptInFlight = AtomicBoolean(false)
|
|
27
|
+
private val sessionLock = Any()
|
|
25
28
|
private val json = Json { ignoreUnknownKeys = true }
|
|
26
29
|
@Volatile private var initialized = false
|
|
27
30
|
@Volatile private var sessionId: String? = null
|
|
@@ -32,32 +35,56 @@ class UrAcpClient(private val project: Project) {
|
|
|
32
35
|
http.send(req, HttpResponse.BodyHandlers.ofString()).statusCode() == 200
|
|
33
36
|
} catch (_: Exception) { false }
|
|
34
37
|
|
|
35
|
-
@Synchronized
|
|
36
38
|
fun sendPrompt(prompt: String): String {
|
|
37
39
|
require(prompt.isNotBlank()) { "Prompt cannot be empty." }
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
require(prompt.length <= 64_000 && !prompt.contains('\u0000')) {
|
|
41
|
+
"Prompt must contain at most 64,000 characters and no NUL bytes."
|
|
42
|
+
}
|
|
43
|
+
check(promptInFlight.compareAndSet(false, true)) {
|
|
44
|
+
"A UR prompt is already running for this project."
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
val active = ensureSession()
|
|
48
|
+
val result = rpc(
|
|
49
|
+
"session/prompt",
|
|
50
|
+
buildJsonObject {
|
|
51
|
+
put("sessionId", JsonPrimitive(active))
|
|
52
|
+
put("prompt", JsonPrimitive(prompt))
|
|
53
|
+
put("mode", JsonPrimitive("sync"))
|
|
54
|
+
},
|
|
55
|
+
Duration.ofMinutes(121),
|
|
56
|
+
).jsonObject
|
|
57
|
+
val task = result["task"]?.jsonObject ?: error("ACP response did not include a task.")
|
|
58
|
+
val status = task["status"]?.jsonPrimitive?.content ?: "unknown"
|
|
59
|
+
val output = task["result"]?.jsonObject?.get("stdout")?.jsonPrimitive?.content
|
|
60
|
+
val failure = task["result"]?.jsonObject?.get("stderr")?.jsonPrimitive?.content
|
|
61
|
+
return output?.takeIf { it.isNotBlank() }
|
|
62
|
+
?: failure?.takeIf { it.isNotBlank() }
|
|
63
|
+
?: "UR task $status (${task["id"]?.jsonPrimitive?.content ?: "unknown id"})"
|
|
64
|
+
} finally {
|
|
65
|
+
promptInFlight.set(false)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun cancelPrompt(): Boolean {
|
|
70
|
+
val active = sessionId ?: return false
|
|
71
|
+
val result = rpc("session/cancel", buildJsonObject {
|
|
72
|
+
put("sessionId", JsonPrimitive(active))
|
|
43
73
|
}).jsonObject
|
|
44
|
-
|
|
45
|
-
val status = task["status"]?.jsonPrimitive?.content ?: "unknown"
|
|
46
|
-
val output = task["result"]?.jsonObject?.get("stdout")?.jsonPrimitive?.content
|
|
47
|
-
val failure = task["result"]?.jsonObject?.get("stderr")?.jsonPrimitive?.content
|
|
48
|
-
return output?.takeIf { it.isNotBlank() }
|
|
49
|
-
?: failure?.takeIf { it.isNotBlank() }
|
|
50
|
-
?: "UR task $status (${task["id"]?.jsonPrimitive?.content ?: "unknown id"})"
|
|
74
|
+
return result["canceled"]?.jsonPrimitive?.content?.toBooleanStrictOrNull() ?: false
|
|
51
75
|
}
|
|
52
76
|
|
|
53
|
-
@Synchronized
|
|
54
77
|
fun closeSession() {
|
|
55
78
|
val active = sessionId ?: return
|
|
56
|
-
try { rpc("session/
|
|
57
|
-
finally {
|
|
79
|
+
try { rpc("session/close", buildJsonObject { put("sessionId", JsonPrimitive(active)) }) }
|
|
80
|
+
finally {
|
|
81
|
+
synchronized(sessionLock) {
|
|
82
|
+
if (sessionId == active) sessionId = null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
58
85
|
}
|
|
59
86
|
|
|
60
|
-
private fun ensureSession() {
|
|
87
|
+
private fun ensureSession(): String = synchronized(sessionLock) {
|
|
61
88
|
if (!initialized) {
|
|
62
89
|
rpc("initialize")
|
|
63
90
|
initialized = true
|
|
@@ -69,9 +96,14 @@ class UrAcpClient(private val project: Project) {
|
|
|
69
96
|
sessionId = rpc("session/new", params).jsonObject["sessionId"]?.jsonPrimitive?.content
|
|
70
97
|
?: error("ACP session/new response did not include sessionId.")
|
|
71
98
|
}
|
|
99
|
+
sessionId!!
|
|
72
100
|
}
|
|
73
101
|
|
|
74
|
-
private fun rpc(
|
|
102
|
+
private fun rpc(
|
|
103
|
+
method: String,
|
|
104
|
+
params: JsonObject? = null,
|
|
105
|
+
timeout: Duration = Duration.ofSeconds(30),
|
|
106
|
+
): JsonElement {
|
|
75
107
|
val requestId = ids.incrementAndGet()
|
|
76
108
|
val body = buildJsonObject {
|
|
77
109
|
put("jsonrpc", JsonPrimitive("2.0"))
|
|
@@ -80,7 +112,7 @@ class UrAcpClient(private val project: Project) {
|
|
|
80
112
|
if (params != null) put("params", params)
|
|
81
113
|
}
|
|
82
114
|
val request = HttpRequest.newBuilder(URI.create("$baseUrl/acp"))
|
|
83
|
-
.timeout(
|
|
115
|
+
.timeout(timeout)
|
|
84
116
|
.header("content-type", "application/json")
|
|
85
117
|
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
|
|
86
118
|
.build()
|
|
@@ -3,6 +3,7 @@ package dev.urnexus
|
|
|
3
3
|
import com.intellij.openapi.project.Project
|
|
4
4
|
import com.intellij.openapi.wm.ToolWindow
|
|
5
5
|
import com.intellij.openapi.wm.ToolWindowFactory
|
|
6
|
+
import com.intellij.openapi.wm.ToolWindowManager
|
|
6
7
|
import com.intellij.ui.components.JBScrollPane
|
|
7
8
|
import com.intellij.ui.content.ContentFactory
|
|
8
9
|
import javax.swing.JTextArea
|
|
@@ -13,15 +14,17 @@ class UrToolWindowFactory : ToolWindowFactory {
|
|
|
13
14
|
val client = project.getService(UrAcpClient::class.java)
|
|
14
15
|
val text = JTextArea()
|
|
15
16
|
text.isEditable = false
|
|
16
|
-
text.text = "Checking UR
|
|
17
|
+
text.text = "Checking UR HTTP server..."
|
|
17
18
|
val content = ContentFactory.getInstance().createContent(JBScrollPane(text), "", false)
|
|
18
19
|
toolWindow.contentManager.addContent(content)
|
|
19
20
|
ApplicationManager.getApplication().executeOnPooledThread {
|
|
20
21
|
val status = if (client.health())
|
|
21
|
-
"Connected to UR
|
|
22
|
+
"Connected to UR HTTP server (127.0.0.1:9100)."
|
|
22
23
|
else
|
|
23
|
-
"UR
|
|
24
|
-
|
|
24
|
+
"UR HTTP server not running.\nStart it with: ur acp serve --port 9100"
|
|
25
|
+
ToolWindowManager.getInstance(project).invokeLater {
|
|
26
|
+
if (!project.isDisposed) text.text = status
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<id>dev.urnexus.jetbrains</id>
|
|
3
3
|
<name>UR Agent</name>
|
|
4
4
|
<vendor url="https://github.com/Maitham16/UR">UR-Nexus</vendor>
|
|
5
|
-
<description>Connects JetBrains IDEs to a local UR-Nexus agent over
|
|
5
|
+
<description>Connects JetBrains IDEs to a local UR-Nexus agent over its loopback HTTP JSON-RPC API (ur acp serve).</description>
|
|
6
6
|
<depends>com.intellij.modules.platform</depends>
|
|
7
7
|
<extensions defaultExtensionNs="com.intellij">
|
|
8
8
|
<toolWindow id="UR Agent" anchor="right"
|
|
@@ -68,8 +68,12 @@ tasks (`ur bg list`) in one place. Supported actions on a diff bundle:
|
|
|
68
68
|
- **Reject**, via `ur ide diff reject`;
|
|
69
69
|
- add a comment that writes back through `ur ide diff comment`.
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
The Actions title bar can start a background task in an isolated worktree
|
|
72
|
+
(recommended), an offline isolated worktree, or the current workspace. UR
|
|
73
|
+
asks for explicit confirmation, does not enable permission bypass or PR
|
|
74
|
+
publishing, and exposes cancellation only for queued/running rows. Opening a
|
|
75
|
+
row retrieves a bounded log tail through `ur bg logs` instead of trusting a
|
|
76
|
+
manifest-supplied file path.
|
|
73
77
|
|
|
74
78
|
## Status and options
|
|
75
79
|
|