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.
- package/CHANGELOG.md +69 -0
- package/QUALITY.md +8 -3
- package/README.md +67 -13
- package/RELEASE.md +9 -6
- package/dist/cli.js +73656 -36080
- package/docs/A2A.md +180 -0
- package/docs/ACP.md +69 -17
- package/docs/AGENT_FEATURES.md +64 -7
- package/docs/AGENT_TRENDS.md +91 -26
- package/docs/AG_UI.md +132 -0
- package/docs/CONFIGURATION.md +123 -3
- package/docs/DEVELOPMENT.md +1 -0
- package/docs/IDE.md +20 -17
- package/docs/TROUBLESHOOTING.md +5 -3
- package/docs/USAGE.md +34 -4
- package/docs/VALIDATION.md +31 -1
- package/docs/providers.md +15 -1
- package/documentation/app.js +18 -6
- package/documentation/index.html +27 -8
- package/examples/agent_features.md +18 -0
- package/examples/agent_trends.md +5 -0
- package/examples/mcp.md +4 -0
- package/examples/memory.md +5 -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 +70 -56
- package/plugins/core/engineering-discipline/skills/reproducible-release/SKILL.md +2 -1
- package/plugins/core/github/skills/github-workflow/SKILL.md +2 -1
- package/plugins/core/gitlab/skills/gitlab-workflow/SKILL.md +2 -1
- package/plugins/core/huggingface/skills/huggingface-workflow/SKILL.md +2 -1
- package/plugins/core/miro/skills/miro-workflow/SKILL.md +2 -1
- package/plugins/core/obsidian/skills/second-brain/SKILL.md +2 -1
- package/plugins/core/powerpoint/skills/deck-craft/SKILL.md +2 -1
- package/plugins/core/skill-forge/skills/skill-authoring/SKILL.md +2 -1
- package/plugins/core/word/skills/document-craft/SKILL.md +2 -1
package/documentation/app.js
CHANGED
|
@@ -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
|
|
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: '
|
|
90
|
-
examples: ['ur acp serve --port 8123', 'ur acp status --json'
|
|
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
|
|
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.',
|
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.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
|
|
162
|
-
|
|
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.
|
|
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
|
|
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
|
|
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 <name-or-directory>
|
|
514
|
+
ur ag-ui serve --help
|
|
496
515
|
ur plugin list
|
|
497
516
|
ur plugin install <plugin></code></pre>
|
|
498
|
-
<p>MCP
|
|
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.
|
package/examples/agent_trends.md
CHANGED
|
@@ -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.
|
package/examples/memory.md
CHANGED
|
@@ -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
|
|
13
|
-
|
|
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
|
|
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.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.
|
|
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
|
|