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/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`) — the real, spec-compliant
17
- [Agent Client Protocol](ACP.md) over stdio, used by Zed and ACP-capable
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 ACP | manual zip install | Project-scoped JSON-RPC session and Send Selection action; synchronous ACP task result, not token streaming. |
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 / HTTP | snippet | `ur acp stdio` (native ACP) or `ur acp serve` (HTTP JSON-RPC; not a streaming transport see Known limitations). |
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, ACP server, provider/model, sandbox/verifier mode, plugin count, warnings
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 ACP server is
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
- read-only in this release click one to open its log file; starting or
154
- canceling a task from the panel is not implemented yet, use
155
- `ur bg run|kill` from a terminal.
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 Actions.
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 ACP server and returns the completed synchronous task output;
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 ACP HTTP server (`ur acp serve`) is not the VS Code chat streaming
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 "ACP server: not running":** start it with
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>`.
@@ -208,12 +208,14 @@ ur plugin list
208
208
 
209
209
  ### ACP / editor connection issues
210
210
 
211
- - Likely cause: the ACP server is not running, or the editor config block is
212
- missing.
213
- - Fix: check status, regenerate the editor config, and run the IDE doctor.
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 ...` to start/stop/status the Agent Communication Protocol server for IDE extensions
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, and the opt-in A2A task server
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
@@ -19,7 +19,7 @@ You need:
19
19
 
20
20
  ```sh
21
21
  ur --version
22
- # expected for this release: "1.45.6 (UR-Nexus)"
22
+ # expected for this release: "1.46.0 (UR-Nexus)"
23
23
  ```
24
24
 
25
25
  ## 0.1 First-workspace model selection (1.45.4)
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`.
@@ -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.45.6</p>
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.45.6 available</code></pre>
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 ACP server
4
- (`ur acp serve --port 9100`). Builds with the IntelliJ Platform Gradle Plugin 2.x (verified: `gradle buildPlugin` produces `build/distributions/jetbrains-ur-<version>.zip` against IDEA 2024.2). Install via Settings → Plugins → ⚙ → Install Plugin from Disk. Mirrors what `extensions/vscode-ur-inline-diffs` does for VS Code.
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 ACP endpoint without
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@17) gradle buildPlugin # → build/distributions/*.zip
25
+ JAVA_HOME=$(brew --prefix openjdk@21) gradle buildPlugin # → build/distributions/*.zip
19
26
  ```
20
27
 
21
- The heavy lifting stays in UR's ACP server — the plugin is intentionally a
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.21"
4
- id("org.jetbrains.intellij.platform") version "2.7.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.45.6"
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.2.3")
30
+ intellijIdeaCommunity("2024.3.7.1")
21
31
  }
22
32
  }
23
33
 
24
34
  intellijPlatform {
25
35
  pluginConfiguration {
26
36
  ideaVersion {
27
- sinceBuild.set("242")
37
+ sinceBuild.set("243")
28
38
  }
29
39
  }
30
40
  }
31
41
 
32
42
  kotlin {
33
- jvmToolchain(17)
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
@@ -1 +1,5 @@
1
+ plugins {
2
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
3
+ }
4
+
1
5
  rootProject.name = "jetbrains-ur"
@@ -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
- val result = runCatching { client.sendPrompt(selection) }
21
- ApplicationManager.getApplication().invokeLater {
22
- result.onSuccess { Messages.showInfoMessage(project, it.take(2000), "UR Agent") }
23
- .onFailure { Messages.showErrorDialog(project, it.message ?: it.toString(), "UR Agent") }
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 ACP endpoint. */
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
- ensureSession()
39
- val result = rpc("session/prompt", buildJsonObject {
40
- put("sessionId", JsonPrimitive(sessionId!!))
41
- put("prompt", JsonPrimitive(prompt))
42
- put("mode", JsonPrimitive("sync"))
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
- val task = result["task"]?.jsonObject ?: error("ACP response did not include a task.")
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/cancel", buildJsonObject { put("sessionId", JsonPrimitive(active)) }) }
57
- finally { sessionId = null }
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(method: String, params: JsonObject? = null): JsonElement {
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(Duration.ofMinutes(31))
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 ACP server..."
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 ACP server (127.0.0.1:9100)."
22
+ "Connected to UR HTTP server (127.0.0.1:9100)."
22
23
  else
23
- "UR ACP server not running.\nStart it with: ur acp serve --port 9100"
24
- ApplicationManager.getApplication().invokeLater { text.text = status }
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 ACP (ur acp serve).</description>
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
- Background task rows in the Actions panel are read-only for now click one
72
- to open its log file.
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