ur-agent 1.45.6 → 1.47.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/QUALITY.md +8 -3
  3. package/README.md +67 -13
  4. package/RELEASE.md +9 -6
  5. package/dist/cli.js +73656 -36080
  6. package/docs/A2A.md +180 -0
  7. package/docs/ACP.md +69 -17
  8. package/docs/AGENT_FEATURES.md +64 -7
  9. package/docs/AGENT_TRENDS.md +91 -26
  10. package/docs/AG_UI.md +132 -0
  11. package/docs/CONFIGURATION.md +123 -3
  12. package/docs/DEVELOPMENT.md +1 -0
  13. package/docs/IDE.md +20 -17
  14. package/docs/TROUBLESHOOTING.md +5 -3
  15. package/docs/USAGE.md +34 -4
  16. package/docs/VALIDATION.md +31 -1
  17. package/docs/providers.md +15 -1
  18. package/documentation/app.js +18 -6
  19. package/documentation/index.html +27 -8
  20. package/examples/agent_features.md +18 -0
  21. package/examples/agent_trends.md +5 -0
  22. package/examples/mcp.md +4 -0
  23. package/examples/memory.md +5 -2
  24. package/extensions/jetbrains-ur/README.md +12 -5
  25. package/extensions/jetbrains-ur/build.gradle.kts +22 -6
  26. package/extensions/jetbrains-ur/gradle.properties +1 -0
  27. package/extensions/jetbrains-ur/settings.gradle.kts +4 -0
  28. package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/SendSelectionAction.kt +39 -5
  29. package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/UrAcpClient.kt +52 -20
  30. package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/UrToolWindowFactory.kt +7 -4
  31. package/extensions/jetbrains-ur/src/main/resources/META-INF/plugin.xml +1 -1
  32. package/extensions/vscode-ur-inline-diffs/README.md +6 -2
  33. package/extensions/vscode-ur-inline-diffs/out/extension.js +328 -157
  34. package/extensions/vscode-ur-inline-diffs/package.json +33 -5
  35. package/package.json +70 -56
  36. package/plugins/core/engineering-discipline/skills/reproducible-release/SKILL.md +2 -1
  37. package/plugins/core/github/skills/github-workflow/SKILL.md +2 -1
  38. package/plugins/core/gitlab/skills/gitlab-workflow/SKILL.md +2 -1
  39. package/plugins/core/huggingface/skills/huggingface-workflow/SKILL.md +2 -1
  40. package/plugins/core/miro/skills/miro-workflow/SKILL.md +2 -1
  41. package/plugins/core/obsidian/skills/second-brain/SKILL.md +2 -1
  42. package/plugins/core/powerpoint/skills/deck-craft/SKILL.md +2 -1
  43. package/plugins/core/skill-forge/skills/skill-authoring/SKILL.md +2 -1
  44. package/plugins/core/word/skills/document-craft/SKILL.md +2 -1
@@ -49,9 +49,9 @@ const featureGroups = [
49
49
  },
50
50
  {
51
51
  title: 'Interoperability',
52
- tags: ['MCP', 'plugins', 'A2A', 'SDK'],
53
- text: 'MCP servers, plugin marketplaces for skills, templates, validators, language adapters, A2A Agent Card and task server, delegation tokens, and a TypeScript SDK wrapper around headless UR.',
54
- commands: ['ur mcp', 'ur plugin', 'ur a2a', 'ur sdk'],
52
+ tags: ['MCP', 'plugins', 'A2A', 'AG-UI', 'SDK'],
53
+ text: 'MCP servers, plugin marketplaces, native and cross-client Agent Skills, ACP editor transport, A2A Agent Card/task bindings, a secure AG-UI frontend stream, delegation tokens, and a TypeScript SDK wrapper around headless UR.',
54
+ commands: ['ur mcp', 'ur plugin', 'ur acp', 'ur a2a', 'ur ag-ui', 'ur sdk'],
55
55
  },
56
56
  {
57
57
  title: 'Providers and auth',
@@ -86,16 +86,23 @@ const commands = [
86
86
  name: 'acp',
87
87
  category: 'Interop',
88
88
  aliases: [],
89
- summary: 'Agent Communication Protocol server for IDE extensions: serve, stop, and status.',
90
- examples: ['ur acp serve --port 8123', 'ur acp status --json', 'ur acp stop'],
89
+ summary: 'Run official-SDK ACP v1 over stdio, or manage the distinct UR HTTP JSON-RPC API used by bundled IDE integrations.',
90
+ examples: ['ur acp stdio', 'ur acp serve --port 8123', 'ur acp status --json'],
91
91
  },
92
92
  {
93
93
  name: 'a2a',
94
94
  category: 'Interop',
95
95
  aliases: [],
96
- summary: 'A2A interoperability utilities: Agent Card, local task server, and delegation tokens.',
96
+ summary: 'Run negotiated strict A2A v1 JSON-RPC/HTTP+JSON beside the stable-SDK v0.3 binding, or inspect cards and delegation tokens.',
97
97
  examples: ['ur a2a card', 'ur a2a serve --dry-run', 'ur a2a token mint --secret "$UR_A2A_DELEGATION_SECRET" --scope coding,review'],
98
98
  },
99
+ {
100
+ name: 'ag-ui',
101
+ category: 'Interop',
102
+ aliases: [],
103
+ summary: 'Start the secure AG-UI HTTP/SSE adapter for user-facing applications with official events and truthful capability discovery.',
104
+ examples: ['ur ag-ui serve', 'ur ag-ui serve --allow-origin https://app.example', 'UR_AG_UI_TOKEN="$TOKEN" ur ag-ui serve --host 0.0.0.0'],
105
+ },
99
106
  {
100
107
  name: 'bg',
101
108
  category: 'Agent Platform',
@@ -718,6 +725,11 @@ const examples = [
718
725
  text: 'Expose Agent Card discovery and token-gated task execution.',
719
726
  code: 'ur a2a card\nur a2a serve --host 127.0.0.1 --port 8765 --dry-run',
720
727
  },
728
+ {
729
+ title: 'AG-UI frontend stream',
730
+ text: 'Expose the official-schema SSE adapter on loopback, or require a bearer token and exact browser origin for network use.',
731
+ code: 'ur ag-ui serve\nUR_AG_UI_TOKEN="$TOKEN" ur ag-ui serve --host 0.0.0.0 --allow-origin https://app.example',
732
+ },
721
733
  {
722
734
  title: 'Delegated A2A token',
723
735
  text: 'Mint an attenuated token scoped to selected skills.',
@@ -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.47.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>
@@ -71,7 +71,7 @@
71
71
  </article>
72
72
  <article>
73
73
  <strong>Agent platform</strong>
74
- <span>Workflows, patterns, crews, goals, evals, memory, knowledge, A2A, provenance, and templates are first-class commands.</span>
74
+ <span>Workflows, patterns, crews, goals, evals, memory, knowledge, ACP, A2A, AG-UI, provenance, and templates are first-class commands.</span>
75
75
  </article>
76
76
  <article>
77
77
  <strong>Plugin marketplace</strong>
@@ -158,12 +158,14 @@ ur provider doctor agy</code></pre>
158
158
  ur config set provider openai-api
159
159
  ur config set base_url http://localhost:11434/v1
160
160
  ur config set model qwen3-coder:480b-cloud
161
- ur config set provider.fallback ollama</code></pre>
162
- <p>API providers require explicit selection and read keys from a key stored via <code>ur connect</code> (OS keychain) or from environment variables. Local providers include Ollama, LM Studio, llama.cpp, and vLLM.</p>
161
+ ur config set provider.fallback ollama
162
+ ur config set openai_transport responses
163
+ ur config set responses.store false</code></pre>
164
+ <p>API providers require explicit selection and read keys from a key stored via <code>ur connect</code> (OS keychain) or from environment variables. OpenAI Responses is opt-in and privacy-first; Chat Completions remains the default. Local providers include Ollama, LM Studio, llama.cpp, and vLLM.</p>
163
165
  </article>
164
166
  <article>
165
167
  <h3>Status bar and updates</h3>
166
- <pre><code>Ollama | llama3 | ask | main | update 1.45.6 available</code></pre>
168
+ <pre><code>Ollama | llama3 | ask | main | update 1.47.0 available</code></pre>
167
169
  <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
170
  </article>
169
171
  <article>
@@ -298,13 +300,14 @@ ur test-first install</code></pre>
298
300
  ur safety check --command "rm -rf build"
299
301
  ur context-pack scan
300
302
  ur context-pack remember --decision "Use manifest commands first"
303
+ ur context-pack memory verify
301
304
  ur context-pack compress</code></pre>
302
305
  </article>
303
306
 
304
307
  <article>
305
308
  <h3>Start the ACP server and run a prompt pool</h3>
306
309
  <ol>
307
- <li>Start the Agent Communication Protocol server for IDE extensions.</li>
310
+ <li>Start the Agent Client Protocol server for IDE extensions.</li>
308
311
  <li>Run one or more prompts in non-interactive mode with concurrency.</li>
309
312
  <li>Capture per-prompt outputs to a directory.</li>
310
313
  </ol>
@@ -314,6 +317,18 @@ ur exec "add tests for the parser" --concurrency 4 --json
314
317
  ur exec --file prompts.jsonl --output-dir ./outputs --dry-run</code></pre>
315
318
  </article>
316
319
 
320
+ <article>
321
+ <h3>Connect a user-facing app with AG-UI</h3>
322
+ <ol>
323
+ <li>Start the official-schema HTTP/SSE adapter on loopback.</li>
324
+ <li>Inspect its truthful capability document.</li>
325
+ <li>For browser or network use, require a bearer token and exact allowed origin.</li>
326
+ </ol>
327
+ <pre><code>ur ag-ui serve
328
+ curl http://127.0.0.1:8977/ag-ui/capabilities
329
+ UR_AG_UI_TOKEN="$TOKEN" ur ag-ui serve --host 0.0.0.0 --allow-origin https://app.example</code></pre>
330
+ </article>
331
+
317
332
  <article>
318
333
  <h3>Use a multi-agent pattern</h3>
319
334
  <ol>
@@ -477,8 +492,9 @@ ur safety check --command "rm -rf build"</code></pre>
477
492
  <h3>Project context</h3>
478
493
  <pre><code>ur context-pack scan
479
494
  ur context-pack remember --constraint "Run command evidence before success claims"
495
+ ur context-pack memory verify
480
496
  ur context-pack compress</code></pre>
481
- <p>Context packs summarize architecture from manifests and preserve task decisions, constraints, commands, and diffs under <code>.ur/context</code>.</p>
497
+ <p>Context packs summarize architecture and preserve provenance-rich, hash-chained task memory under <code>.ur/context</code>, with verification, quarantine, and rollback recovery.</p>
482
498
  </article>
483
499
  <article>
484
500
  <h3>Verifier</h3>
@@ -493,9 +509,12 @@ UR_VERIFIER_AUTO_SUBAGENT=1</code></pre>
493
509
  <h3>MCP and plugins</h3>
494
510
  <pre><code>ur mcp list
495
511
  ur mcp add-json local-tools '{"command":"node","args":["server.js"]}'
512
+ ur mcp serve-http --port 8976
513
+ ur skill verify &lt;name-or-directory&gt;
514
+ ur ag-ui serve --help
496
515
  ur plugin list
497
516
  ur plugin install &lt;plugin&gt;</code></pre>
498
- <p>MCP servers and marketplace plugins expand tools, executable skills, templates, validators, language adapters, and review workflows. Treat them as trusted code.</p>
517
+ <p>The opt-in stateless MCP HTTP adapter exposes negotiated Tasks and Apps. Portable skills load from native and cross-client roots with strict validation and Ed25519 provenance. AG-UI provides a bounded, opt-in frontend stream. Treat MCP servers and plugins as trusted code.</p>
499
518
  </article>
500
519
  <article>
501
520
  <h3>IDE inline diffs</h3>
@@ -56,6 +56,7 @@ ur safety check --command "rm -rf build"
56
56
  ur context-pack scan
57
57
  ur context-pack remember --decision "Use package scripts before ad hoc commands"
58
58
  ur context-pack remember --constraint "Never expose secret values"
59
+ ur context-pack memory verify
59
60
  ur context-pack compress
60
61
  ```
61
62
 
@@ -90,3 +91,20 @@ Run the opt-in A2A server on loopback:
90
91
  ur a2a serve --dry-run
91
92
  curl http://127.0.0.1:8765/healthz
92
93
  ```
94
+
95
+ Inspect signed skills and the stateless MCP adapter:
96
+
97
+ ```sh
98
+ ur skill verify <name-or-directory>
99
+ ur mcp serve-http --help
100
+ ```
101
+
102
+ Inspect the provider-free AG-UI surface and cross-client skill discovery:
103
+
104
+ ```sh
105
+ ur ag-ui serve --help
106
+ ur skill verify <name-or-directory>
107
+ ```
108
+
109
+ Portable skills may be placed in `.agents/skills/<name>/SKILL.md`; project
110
+ skills override user skills, and native `.ur/skills/` wins at the same scope.
@@ -3,11 +3,16 @@
3
3
  - Run `ur agent-trends` for a human-readable coverage report.
4
4
  - Run `ur agent-trends --json` when another tool needs structured coverage data.
5
5
  - Run `ur a2a card` to print UR's Agent Card metadata for A2A discovery.
6
+ - Run `ur ag-ui serve --help` to inspect the user-facing HTTP/SSE adapter
7
+ without starting a model call.
6
8
  - Run `ur test-first --dry-run` to preview the detected compile/test/lint
7
9
  evidence loop for the current project.
8
10
  - Run `ur safety check --command "rm -rf build"` to preview command permission
9
11
  class, destructive approval, and sandbox posture.
10
12
  - Run `ur context-pack scan` and `ur context-pack compress` to preserve
11
13
  manifest-derived architecture context and task memory.
14
+ - Run `ur context-pack memory verify`, `ur skill verify <name>`, and
15
+ `ur mcp serve-http --help`, and `ur ag-ui serve --help` to inspect the v1.47 integrity and protocol
16
+ surfaces without making a paid model call.
12
17
  - Use `/agent-trends` inside a session before planning upgrades to local
13
18
  runtime, MCP, A2A, memory, browser, eval, identity, or provenance workflows.
package/examples/mcp.md CHANGED
@@ -5,3 +5,7 @@
5
5
  their tools into the registry and runs them through the same approval +
6
6
  evidence-ledger path as built-in tools (so MCP calls appear in `/evidence`).
7
7
  - Risky MCP tools require approval before they run.
8
+ - `ur mcp serve` exposes the fail-closed stdio server. The separate
9
+ `UR_MCP_HTTP_TOKEN='<secret>' ur mcp serve-http` command starts the opt-in
10
+ stateless MCP 2026 HTTP surface with negotiated Tasks and a self-contained
11
+ App; off-loopback use requires bearer authentication.
@@ -9,7 +9,10 @@
9
9
  `.ur/context/architecture.md` from package scripts, instruction files,
10
10
  `.ur/verify.json`, and safety config.
11
11
  - `ur context-pack remember --decision|--constraint|--command|--diff|--note`
12
- appends durable task memory to `.ur/context/task-memory.jsonl`; `compress`
13
- writes `.ur/context/compressed.md`.
12
+ appends provenance-rich, hash-chained task memory to
13
+ `.ur/context/task-memory.jsonl`; `ur context-pack memory verify|quarantine`
14
+ checks or repairs a corrupt tail, `memory rollback --to <entry-id>` preserves
15
+ a private backup before truncation, and `compress` writes
16
+ `.ur/context/compressed.md`.
14
17
  - `/ur-init` scaffolds the `.ur/` asset folder (docs, superpowers, brainstorming,
15
18
  memory, prompts).
@@ -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.47.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