ur-agent 1.44.10 → 1.45.3
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 +108 -0
- package/README.md +16 -9
- package/dist/cli.js +30223 -22644
- package/docs/AGENT_FEATURES.md +8 -4
- package/docs/CONFIGURATION.md +7 -2
- package/docs/IDE.md +14 -12
- package/docs/TROUBLESHOOTING.md +7 -0
- package/docs/USAGE.md +6 -2
- package/docs/VALIDATION.md +1 -1
- package/docs/providers.md +11 -1
- package/documentation/index.html +1 -1
- package/extensions/jetbrains-ur/README.md +22 -0
- package/extensions/jetbrains-ur/build.gradle.kts +34 -0
- package/extensions/jetbrains-ur/settings.gradle.kts +1 -0
- package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/SendSelectionAction.kt +28 -0
- package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/UrAcpClient.kt +97 -0
- package/extensions/jetbrains-ur/src/main/kotlin/dev/urnexus/UrToolWindowFactory.kt +27 -0
- package/extensions/jetbrains-ur/src/main/resources/META-INF/plugin.xml +17 -0
- package/extensions/vscode-ur-inline-diffs/out/extension.js +66 -21
- package/extensions/vscode-ur-inline-diffs/package.json +1 -1
- package/package.json +2 -1
package/docs/AGENT_FEATURES.md
CHANGED
|
@@ -71,12 +71,16 @@ ur auth gemini
|
|
|
71
71
|
ur auth antigravity
|
|
72
72
|
ur config set provider ollama
|
|
73
73
|
ur config set provider openai-compatible
|
|
74
|
-
ur config set model
|
|
74
|
+
ur config set model qwen2.5-coder:7b
|
|
75
75
|
ur config set base_url http://localhost:11434/v1
|
|
76
76
|
ur config set provider.fallback ollama
|
|
77
77
|
ur upgrade
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
The optional `provider.fallback` value is used only to print an explicit
|
|
81
|
+
recovery recommendation in provider diagnostics. It never silently or
|
|
82
|
+
automatically changes the active provider.
|
|
83
|
+
|
|
80
84
|
## v1.25.x Additions
|
|
81
85
|
|
|
82
86
|
| Addition | Surface | What it adds |
|
|
@@ -135,8 +139,8 @@ components instead of one giant prompt.
|
|
|
135
139
|
|
|
136
140
|
| Addition | Surface | What it adds |
|
|
137
141
|
| --- | --- | --- |
|
|
138
|
-
| Agent skill runner | `src/services/agents/agentSkillRunner.ts` | Reusable
|
|
139
|
-
| Worktree slash skills | `/debug-v2`, `/refactor`, `/paper-implementation`, `/benchmark`, `/security-review`, `/dockerize`, `/latex-paper` | Bundled slash skills
|
|
142
|
+
| Agent skill runner | `src/services/agents/agentSkillRunner.ts` | Reusable isolated-worktree wrapper that polls to completion; PR creation is available only with explicit `createPr: true`. |
|
|
143
|
+
| Worktree slash skills | `/debug-v2`, `/refactor`, `/paper-implementation`, `/benchmark`, `/security-review`, `/dockerize`, `/latex-paper`, `/batch` | Bundled slash skills keep changes local, run focused checks, ask before the final full suite, and never publish automatically. |
|
|
140
144
|
| Agent templates | `ur agent-templates install` | Adds `debug-v2`, `refactor`, `paper-implementation`, `benchmark`, `security-review`, `dockerize`, and `latex-paper` reusable agent templates under `.ur/agents/`. |
|
|
141
145
|
| Worktree command | `ur worktree list\|status\|clean` | Inspect and clean up UR agent worktrees created by `ur bg` or slash skills. |
|
|
142
146
|
|
|
@@ -269,7 +273,7 @@ runners, so the core logic is deterministic and unit-tested offline.
|
|
|
269
273
|
| Spec-driven development | `ur spec init\|generate\|approve\|run\|status` + `.ur/specs/` | requirements -> design -> tasks documents and a phase/approval record; executes the Spec Kit / Kiro `- [ ] T1: ...` task list one task at a time, checking off each PASS |
|
|
270
274
|
| In-loop model escalation | `ur escalate plan\|run\|oracle\|policy` + `.ur/escalation.json` | capability-aware fast/oracle tiers from `model-doctor`; routine work runs fast and auto-escalates hard/failed work to the strong model; `oracle` is a one-shot second opinion |
|
|
271
275
|
| Best-of-N judging | `ur arena "<task>" [--agents N] [--apply]` | runs N agents on one task in isolated worktrees, scores diffs with the self-review gate + verdict/diff heuristics, surfaces (optionally applies) the winner |
|
|
272
|
-
| Self-healing CI loop | `ur ci-loop [--command ...] [--commit] [--push]` | run -> on failure summarize -> fix agent -> re-run, bounded by retries; commits/pushes are self-review gated; `--from-log` seeds the first failure |
|
|
276
|
+
| Self-healing CI loop | `ur ci-loop [--command ...] [--cwd ...] [--commit] [--push]` | run -> on failure summarize -> fix agent -> re-run, bounded by retries; no-test failures stop immediately with cwd guidance; commits/pushes are self-review gated; `--from-log` seeds the first failure |
|
|
273
277
|
| Verifiable artifacts | `ur artifacts add\|capture-diff\|capture-tests\|approve\|reject` + `.ur/artifacts/` | reviewable deliverables with pending/approved/rejected status and threaded feedback; threads into the provenance stack (`claim-ledger`, `trace`, `evidence`) |
|
|
274
278
|
|
|
275
279
|
### Commands
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -58,6 +58,10 @@ ur provider select-model <provider> <model> --json
|
|
|
58
58
|
ur config set base_url <url>
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
`provider.fallback` is diagnostic recovery metadata, not automatic routing.
|
|
62
|
+
When the active provider fails, `ur provider doctor` shows the configured
|
|
63
|
+
recovery command; changing providers remains an explicit user action.
|
|
64
|
+
|
|
61
65
|
Provider values accept canonical IDs and common aliases. Examples:
|
|
62
66
|
`openai-api`, `anthropic-api`, `gemini-api`, `openrouter`, `ollama`,
|
|
63
67
|
`lmstudio`, `LM Studio`, `llama.cpp`, `vllm`, and the subscription CLI
|
|
@@ -88,6 +92,7 @@ it in the environment when you explicitly choose API mode:
|
|
|
88
92
|
|
|
89
93
|
```sh
|
|
90
94
|
OPENAI_API_KEY=...
|
|
95
|
+
OPENAI_COMPATIBLE_API_KEY=...
|
|
91
96
|
ANTHROPIC_API_KEY=...
|
|
92
97
|
GEMINI_API_KEY=...
|
|
93
98
|
OPENROUTER_API_KEY=...
|
|
@@ -117,8 +122,8 @@ ur --settings '{"ollama":{"host":"http://192.168.1.50:11434"}}'
|
|
|
117
122
|
Model selection environment variables still work the same way:
|
|
118
123
|
|
|
119
124
|
```sh
|
|
120
|
-
OLLAMA_MODEL=
|
|
121
|
-
UR_MODEL=
|
|
125
|
+
OLLAMA_MODEL=qwen2.5-coder:7b
|
|
126
|
+
UR_MODEL=qwen2.5-coder:7b
|
|
122
127
|
```
|
|
123
128
|
|
|
124
129
|
`OLLAMA_MODEL` selects the model name and takes precedence over `UR_MODEL` only
|
package/docs/IDE.md
CHANGED
|
@@ -4,8 +4,7 @@ UR's professional IDE integration is the **UR Inline Diffs** VS Code extension,
|
|
|
4
4
|
bundled in this repository (`extensions/vscode-ur-inline-diffs`). It gives VS
|
|
5
5
|
Code, Cursor, and Windsurf a chat panel, inline diff review, an actions panel,
|
|
6
6
|
an agent status card, a searchable command palette, and an agent options
|
|
7
|
-
panel — all driven by the same `ur` CLI you already use from a terminal
|
|
8
|
-
no separate server process and no new network surface.
|
|
7
|
+
panel — all driven by the same `ur` CLI you already use from a terminal.
|
|
9
8
|
|
|
10
9
|
Other editors connect through different mechanisms, chosen per editor and
|
|
11
10
|
stated honestly — nothing here claims support that does not exist:
|
|
@@ -19,12 +18,12 @@ stated honestly — nothing here claims support that does not exist:
|
|
|
19
18
|
Neovim clients. This remains a separate transport from the VS Code
|
|
20
19
|
extension's chat bridge below; the two are independent and neither depends
|
|
21
20
|
on the other.
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
- **JetBrains plugin** — the experimental bundled plugin under
|
|
22
|
+
`extensions/jetbrains-ur` connects to `ur acp serve` on loopback. It uses
|
|
23
|
+
JSON-RPC `initialize`, `session/new`, and `session/prompt` calls at `/acp`,
|
|
24
|
+
preserves a project-scoped session, and performs network work off the IDE UI
|
|
25
|
+
thread. It is installable from its `buildPlugin` zip; marketplace publishing
|
|
26
|
+
is not claimed.
|
|
28
27
|
|
|
29
28
|
## Supported targets
|
|
30
29
|
|
|
@@ -34,7 +33,7 @@ stated honestly — nothing here claims support that does not exist:
|
|
|
34
33
|
| Cursor | UR Inline Diffs extension (VS Code fork) | `.vscode/settings.json` | Same extension as VS Code. |
|
|
35
34
|
| Windsurf | UR Inline Diffs extension (VS Code fork) | `.vscode/settings.json` | Same extension as VS Code. |
|
|
36
35
|
| Zed | stdio ACP | `.zed/settings.json` | Real Agent Client Protocol over stdio. |
|
|
37
|
-
| JetBrains |
|
|
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. |
|
|
38
37
|
| Neovim | stdio ACP | snippet | Requires a third-party ACP client plugin. |
|
|
39
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). |
|
|
40
39
|
|
|
@@ -128,7 +127,9 @@ rather than leaving it hanging.
|
|
|
128
127
|
`.ur/ide/chat/` in the workspace (a manifest plus one file per session).
|
|
129
128
|
**UR: Open Chat** can resume a previous session — using the CLI's own
|
|
130
129
|
`--resume` — or start a new one; sessions survive closing and reopening VS
|
|
131
|
-
Code.
|
|
130
|
+
Code. In multi-root workspaces the chat binds to the folder containing the
|
|
131
|
+
active editor, and stale callbacks from a canceled turn cannot mutate a newer
|
|
132
|
+
chat.
|
|
132
133
|
|
|
133
134
|
**File and selection context.** **UR: Add Current File to Chat** and
|
|
134
135
|
**UR: Add Selection to Chat** attach the active file or selection as explicit
|
|
@@ -200,8 +201,9 @@ success it did not observe.
|
|
|
200
201
|
|
|
201
202
|
## Known limitations
|
|
202
203
|
|
|
203
|
-
- **JetBrains is
|
|
204
|
-
|
|
204
|
+
- **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.
|
|
205
207
|
- **The lockfile/MCP IDE bridge is Phase 2 and not part of this MVP.** The
|
|
206
208
|
CLI already implements the *client* half of a lockfile-discovered MCP
|
|
207
209
|
connection to a running IDE (`src/utils/ide.ts`), used by the terminal
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -186,6 +186,13 @@ ur ci-loop --command "bun test" --max-attempts 3
|
|
|
186
186
|
ur test-first detect
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
If the runner says "No tests found", check the working directory printed by
|
|
190
|
+
the result and run from the test root or pass it explicitly:
|
|
191
|
+
|
|
192
|
+
```sh
|
|
193
|
+
ur ci-loop --command "bun test" --cwd ./packages/app --max-attempts 3
|
|
194
|
+
```
|
|
195
|
+
|
|
189
196
|
## Integrations
|
|
190
197
|
|
|
191
198
|
### Plugin fails to load
|
package/docs/USAGE.md
CHANGED
|
@@ -13,7 +13,7 @@ Use interactive mode for iterative coding, debugging, research, and repository e
|
|
|
13
13
|
Useful options:
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
ur --model
|
|
16
|
+
ur --model qwen2.5-coder:7b
|
|
17
17
|
ur --add-dir ../other-project
|
|
18
18
|
ur --permission-mode ask
|
|
19
19
|
ur --continue
|
|
@@ -63,7 +63,7 @@ Ollama router choose from the models exposed by your local Ollama app.
|
|
|
63
63
|
You can also choose the model for a single session:
|
|
64
64
|
|
|
65
65
|
```sh
|
|
66
|
-
ur --model
|
|
66
|
+
ur --model qwen2.5-coder:7b
|
|
67
67
|
ur --model qwen2.5-coder:latest
|
|
68
68
|
```
|
|
69
69
|
|
|
@@ -108,6 +108,10 @@ ur config set base_url <url>
|
|
|
108
108
|
ur config set provider.fallback ollama
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
`provider.fallback` only controls the recovery suggestion printed by provider
|
|
112
|
+
diagnostics. UR does not switch or retry across providers automatically; use
|
|
113
|
+
`ur config set provider <id>` after reviewing the failure.
|
|
114
|
+
|
|
111
115
|
In the interactive app, `/model` chooses a provider first and then a model from
|
|
112
116
|
that provider only. The saved pair controls the runtime backend for the next
|
|
113
117
|
agent request. There is no cross-provider fallback: OpenAI API does not fall
|
package/docs/VALIDATION.md
CHANGED
|
@@ -287,7 +287,7 @@ ur escalate run "refactor the cache layer" --force-oracle --dry-run
|
|
|
287
287
|
ur test-first --dry-run
|
|
288
288
|
ur safety check --command "rm -rf build"
|
|
289
289
|
ur context-pack scan
|
|
290
|
-
ur ci-loop --command "bun test" --dry-run
|
|
290
|
+
ur ci-loop --command "bun test" --cwd . --dry-run
|
|
291
291
|
ur artifacts capture-tests --command "bun test"
|
|
292
292
|
```
|
|
293
293
|
|
package/docs/providers.md
CHANGED
|
@@ -36,6 +36,7 @@ multimodal input, external CLI boundary, and sandbox scope:
|
|
|
36
36
|
| Claude API | API | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `api:anthropic` | `ANTHROPIC_API_KEY` |
|
|
37
37
|
| Gemini API | API | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `api:gemini` | `GEMINI_API_KEY` |
|
|
38
38
|
| OpenRouter | API/router | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `api:openrouter` | `OPENROUTER_API_KEY` |
|
|
39
|
+
| OpenAI-compatible | server/API | UR-native | no | yes | yes | endpoint-dependent | UR Bash/File sandbox | `openai-compatible` | optional `OPENAI_COMPATIBLE_API_KEY`; never reuses `OPENAI_API_KEY` |
|
|
39
40
|
| Ollama | local | UR-native | no | yes | yes | yes* | UR Bash/File sandbox | `ollama` | localhost Ollama runtime |
|
|
40
41
|
| LM Studio | local/server | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `openai-compatible:lmstudio` | local OpenAI-compatible server |
|
|
41
42
|
| llama.cpp | local/server | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `openai-compatible:llama.cpp` | local OpenAI-compatible server |
|
|
@@ -106,6 +107,10 @@ ur config set base_url <url>
|
|
|
106
107
|
ur config set provider.fallback ollama
|
|
107
108
|
```
|
|
108
109
|
|
|
110
|
+
The fallback setting is a recovery hint for `ur provider doctor`; it does not
|
|
111
|
+
route a failed request to another provider. Review the failure and use
|
|
112
|
+
`ur config set provider <id>` to switch explicitly.
|
|
113
|
+
|
|
109
114
|
## Provider-scoped model selection
|
|
110
115
|
|
|
111
116
|
UR-Nexus shows providers first, then only models available for the selected provider. This prevents incompatible model/provider pairs and keeps API-key, local/server, subscription, and external app bridge model lists separate. The generic `subscription` entry has no models unless a real independent subscription runtime is configured; UR does not list fake subscription models.
|
|
@@ -185,7 +190,7 @@ After selecting a model, the confirmation shows:
|
|
|
185
190
|
|
|
186
191
|
→ Step 2: Select model
|
|
187
192
|
llama3 · discovered from Ollama · live
|
|
188
|
-
|
|
193
|
+
qwen2.5-coder:7b · discovered from Ollama · live
|
|
189
194
|
|
|
190
195
|
→ Select: llama3
|
|
191
196
|
|
|
@@ -401,6 +406,7 @@ API providers require explicit user selection and environment keys:
|
|
|
401
406
|
|
|
402
407
|
```sh
|
|
403
408
|
OPENAI_API_KEY=...
|
|
409
|
+
OPENAI_COMPATIBLE_API_KEY=...
|
|
404
410
|
ANTHROPIC_API_KEY=...
|
|
405
411
|
GEMINI_API_KEY=...
|
|
406
412
|
OPENROUTER_API_KEY=...
|
|
@@ -414,6 +420,10 @@ ur config set base_url http://localhost:1234/v1
|
|
|
414
420
|
ur config set model local-model-name
|
|
415
421
|
```
|
|
416
422
|
|
|
423
|
+
`OPENAI_COMPATIBLE_API_KEY` is intentionally separate from
|
|
424
|
+
`OPENAI_API_KEY`; selecting an arbitrary compatible base URL never forwards
|
|
425
|
+
the OpenAI credential to that host.
|
|
426
|
+
|
|
417
427
|
Local/server providers use their normal endpoints:
|
|
418
428
|
|
|
419
429
|
- Ollama: `http://localhost:11434`
|
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.45.3</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>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# UR for JetBrains (experimental scaffold)
|
|
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.
|
|
5
|
+
|
|
6
|
+
## What it wires up
|
|
7
|
+
|
|
8
|
+
- A tool window ("UR Agent") that checks the loopback ACP endpoint without
|
|
9
|
+
blocking the IDE event thread.
|
|
10
|
+
- An action (Tools → UR: Send Selection) that posts the current selection as
|
|
11
|
+
a prompt to a project-scoped JSON-RPC session at `/acp`. The current server
|
|
12
|
+
returns a completed synchronous task result; token streaming is not claimed.
|
|
13
|
+
|
|
14
|
+
## Build
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
cd extensions/jetbrains-ur
|
|
18
|
+
JAVA_HOME=$(brew --prefix openjdk@17) gradle buildPlugin # → build/distributions/*.zip
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The heavy lifting stays in UR's ACP server — the plugin is intentionally a
|
|
22
|
+
thin client, so protocol changes land on the CLI side.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id("java")
|
|
3
|
+
id("org.jetbrains.kotlin.jvm") version "2.0.21"
|
|
4
|
+
id("org.jetbrains.intellij.platform") version "2.7.2"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
group = "dev.urnexus"
|
|
8
|
+
version = "1.45.3"
|
|
9
|
+
|
|
10
|
+
repositories {
|
|
11
|
+
mavenCentral()
|
|
12
|
+
intellijPlatform {
|
|
13
|
+
defaultRepositories()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
dependencies {
|
|
18
|
+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
|
19
|
+
intellijPlatform {
|
|
20
|
+
intellijIdeaCommunity("2024.2.3")
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
intellijPlatform {
|
|
25
|
+
pluginConfiguration {
|
|
26
|
+
ideaVersion {
|
|
27
|
+
sinceBuild.set("242")
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
kotlin {
|
|
33
|
+
jvmToolchain(17)
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rootProject.name = "jetbrains-ur"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package dev.urnexus
|
|
2
|
+
|
|
3
|
+
import com.intellij.openapi.actionSystem.AnAction
|
|
4
|
+
import com.intellij.openapi.actionSystem.AnActionEvent
|
|
5
|
+
import com.intellij.openapi.actionSystem.CommonDataKeys
|
|
6
|
+
import com.intellij.openapi.ui.Messages
|
|
7
|
+
import com.intellij.openapi.progress.ProgressIndicator
|
|
8
|
+
import com.intellij.openapi.progress.ProgressManager
|
|
9
|
+
import com.intellij.openapi.progress.Task
|
|
10
|
+
import com.intellij.openapi.application.ApplicationManager
|
|
11
|
+
|
|
12
|
+
class SendSelectionAction : AnAction() {
|
|
13
|
+
override fun actionPerformed(e: AnActionEvent) {
|
|
14
|
+
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
|
|
15
|
+
val selection = editor.selectionModel.selectedText ?: return
|
|
16
|
+
val project = e.project ?: return
|
|
17
|
+
val client = project.getService(UrAcpClient::class.java)
|
|
18
|
+
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Sending selection to UR", true) {
|
|
19
|
+
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") }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
package dev.urnexus
|
|
2
|
+
|
|
3
|
+
import com.intellij.openapi.components.Service
|
|
4
|
+
import com.intellij.openapi.project.Project
|
|
5
|
+
import java.net.URI
|
|
6
|
+
import java.net.http.HttpClient
|
|
7
|
+
import java.net.http.HttpRequest
|
|
8
|
+
import java.net.http.HttpResponse
|
|
9
|
+
import java.time.Duration
|
|
10
|
+
import java.util.concurrent.atomic.AtomicLong
|
|
11
|
+
import kotlinx.serialization.json.Json
|
|
12
|
+
import kotlinx.serialization.json.JsonElement
|
|
13
|
+
import kotlinx.serialization.json.JsonObject
|
|
14
|
+
import kotlinx.serialization.json.JsonPrimitive
|
|
15
|
+
import kotlinx.serialization.json.buildJsonObject
|
|
16
|
+
import kotlinx.serialization.json.jsonObject
|
|
17
|
+
import kotlinx.serialization.json.jsonPrimitive
|
|
18
|
+
|
|
19
|
+
/** Project-scoped JSON-RPC client for UR's HTTP ACP endpoint. */
|
|
20
|
+
@Service(Service.Level.PROJECT)
|
|
21
|
+
class UrAcpClient(private val project: Project) {
|
|
22
|
+
private val baseUrl = "http://127.0.0.1:9100"
|
|
23
|
+
private val http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build()
|
|
24
|
+
private val ids = AtomicLong()
|
|
25
|
+
private val json = Json { ignoreUnknownKeys = true }
|
|
26
|
+
@Volatile private var initialized = false
|
|
27
|
+
@Volatile private var sessionId: String? = null
|
|
28
|
+
|
|
29
|
+
fun health(): Boolean = try {
|
|
30
|
+
val req = HttpRequest.newBuilder(URI.create("$baseUrl/healthz"))
|
|
31
|
+
.timeout(Duration.ofSeconds(5)).GET().build()
|
|
32
|
+
http.send(req, HttpResponse.BodyHandlers.ofString()).statusCode() == 200
|
|
33
|
+
} catch (_: Exception) { false }
|
|
34
|
+
|
|
35
|
+
@Synchronized
|
|
36
|
+
fun sendPrompt(prompt: String): String {
|
|
37
|
+
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"))
|
|
43
|
+
}).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"})"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Synchronized
|
|
54
|
+
fun closeSession() {
|
|
55
|
+
val active = sessionId ?: return
|
|
56
|
+
try { rpc("session/cancel", buildJsonObject { put("sessionId", JsonPrimitive(active)) }) }
|
|
57
|
+
finally { sessionId = null }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun ensureSession() {
|
|
61
|
+
if (!initialized) {
|
|
62
|
+
rpc("initialize")
|
|
63
|
+
initialized = true
|
|
64
|
+
}
|
|
65
|
+
if (sessionId == null) {
|
|
66
|
+
val params = project.basePath?.let { cwd ->
|
|
67
|
+
buildJsonObject { put("cwd", JsonPrimitive(cwd)) }
|
|
68
|
+
}
|
|
69
|
+
sessionId = rpc("session/new", params).jsonObject["sessionId"]?.jsonPrimitive?.content
|
|
70
|
+
?: error("ACP session/new response did not include sessionId.")
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private fun rpc(method: String, params: JsonObject? = null): JsonElement {
|
|
75
|
+
val requestId = ids.incrementAndGet()
|
|
76
|
+
val body = buildJsonObject {
|
|
77
|
+
put("jsonrpc", JsonPrimitive("2.0"))
|
|
78
|
+
put("id", JsonPrimitive(requestId))
|
|
79
|
+
put("method", JsonPrimitive(method))
|
|
80
|
+
if (params != null) put("params", params)
|
|
81
|
+
}
|
|
82
|
+
val request = HttpRequest.newBuilder(URI.create("$baseUrl/acp"))
|
|
83
|
+
.timeout(Duration.ofMinutes(31))
|
|
84
|
+
.header("content-type", "application/json")
|
|
85
|
+
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
|
|
86
|
+
.build()
|
|
87
|
+
val response = http.send(request, HttpResponse.BodyHandlers.ofString())
|
|
88
|
+
if (response.statusCode() !in 200..299) {
|
|
89
|
+
error("ACP HTTP ${response.statusCode()}: ${response.body().take(500)}")
|
|
90
|
+
}
|
|
91
|
+
val payload = json.parseToJsonElement(response.body()).jsonObject
|
|
92
|
+
payload["error"]?.jsonObject?.let { rpcError ->
|
|
93
|
+
error("ACP ${rpcError["code"]?.jsonPrimitive?.content}: ${rpcError["message"]?.jsonPrimitive?.content}")
|
|
94
|
+
}
|
|
95
|
+
return payload["result"] ?: error("ACP response did not include a result.")
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package dev.urnexus
|
|
2
|
+
|
|
3
|
+
import com.intellij.openapi.project.Project
|
|
4
|
+
import com.intellij.openapi.wm.ToolWindow
|
|
5
|
+
import com.intellij.openapi.wm.ToolWindowFactory
|
|
6
|
+
import com.intellij.ui.components.JBScrollPane
|
|
7
|
+
import com.intellij.ui.content.ContentFactory
|
|
8
|
+
import javax.swing.JTextArea
|
|
9
|
+
import com.intellij.openapi.application.ApplicationManager
|
|
10
|
+
|
|
11
|
+
class UrToolWindowFactory : ToolWindowFactory {
|
|
12
|
+
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
|
13
|
+
val client = project.getService(UrAcpClient::class.java)
|
|
14
|
+
val text = JTextArea()
|
|
15
|
+
text.isEditable = false
|
|
16
|
+
text.text = "Checking UR ACP server..."
|
|
17
|
+
val content = ContentFactory.getInstance().createContent(JBScrollPane(text), "", false)
|
|
18
|
+
toolWindow.contentManager.addContent(content)
|
|
19
|
+
ApplicationManager.getApplication().executeOnPooledThread {
|
|
20
|
+
val status = if (client.health())
|
|
21
|
+
"Connected to UR ACP server (127.0.0.1:9100)."
|
|
22
|
+
else
|
|
23
|
+
"UR ACP server not running.\nStart it with: ur acp serve --port 9100"
|
|
24
|
+
ApplicationManager.getApplication().invokeLater { text.text = status }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<idea-plugin>
|
|
2
|
+
<id>dev.urnexus.jetbrains</id>
|
|
3
|
+
<name>UR Agent</name>
|
|
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>
|
|
6
|
+
<depends>com.intellij.modules.platform</depends>
|
|
7
|
+
<extensions defaultExtensionNs="com.intellij">
|
|
8
|
+
<toolWindow id="UR Agent" anchor="right"
|
|
9
|
+
factoryClass="dev.urnexus.UrToolWindowFactory"/>
|
|
10
|
+
</extensions>
|
|
11
|
+
<actions>
|
|
12
|
+
<action id="UR.SendSelection" class="dev.urnexus.SendSelectionAction"
|
|
13
|
+
text="UR: Send Selection" description="Send the current selection to the UR agent">
|
|
14
|
+
<add-to-group group-id="ToolsMenu" anchor="last"/>
|
|
15
|
+
</action>
|
|
16
|
+
</actions>
|
|
17
|
+
</idea-plugin>
|