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.
@@ -62,7 +62,8 @@ var fs = __toESM(require("node:fs"));
62
62
  var path = __toESM(require("node:path"));
63
63
  var vscode2 = __toESM(require("vscode"));
64
64
  function workspaceRoot() {
65
- return vscode2.workspace.workspaceFolders?.[0]?.uri.fsPath;
65
+ const activeUri = vscode2.window.activeTextEditor?.document.uri;
66
+ return (activeUri ? vscode2.workspace.getWorkspaceFolder(activeUri) : void 0)?.uri.fsPath ?? vscode2.workspace.workspaceFolders?.[0]?.uri.fsPath;
66
67
  }
67
68
  function diffsRoot(root) {
68
69
  return path.join(root, ".ur", "ide", "diffs");
@@ -71,10 +72,34 @@ function manifestPath(root) {
71
72
  return path.join(diffsRoot(root), "manifest.json");
72
73
  }
73
74
  function patchPath(root, bundle) {
74
- return path.join(diffsRoot(root), bundle.patchFile);
75
+ return artifactPath(root, bundle, "patch");
75
76
  }
76
77
  function metadataPath(root, bundle) {
77
- return path.join(diffsRoot(root), bundle.metadataFile);
78
+ return artifactPath(root, bundle, "metadata");
79
+ }
80
+ var DIFF_ID_PATTERN = /^diff-[1-9][0-9]*$/u;
81
+ function artifactPath(root, bundle, kind) {
82
+ if (!DIFF_ID_PATTERN.test(bundle.id)) throw new Error(`Invalid UR diff id: ${bundle.id}`);
83
+ const relative2 = kind === "patch" ? bundle.patchFile : bundle.metadataFile;
84
+ const expected = kind === "patch" ? `patches/${bundle.id}.patch` : `metadata/${bundle.id}.json`;
85
+ if (relative2.replaceAll("\\", "/") !== expected) {
86
+ throw new Error(`Invalid UR diff ${kind} path for ${bundle.id}`);
87
+ }
88
+ const rootPath = path.resolve(diffsRoot(root));
89
+ const target = path.resolve(rootPath, relative2);
90
+ if (!target.startsWith(`${rootPath}${path.sep}`)) throw new Error(`UR diff ${kind} path escapes the diff store`);
91
+ return target;
92
+ }
93
+ function isValidBundle(value) {
94
+ if (!value || typeof value !== "object") return false;
95
+ const bundle = value;
96
+ try {
97
+ patchPath(".", bundle);
98
+ metadataPath(".", bundle);
99
+ return true;
100
+ } catch {
101
+ return false;
102
+ }
78
103
  }
79
104
  function readJson(file, fallback) {
80
105
  try {
@@ -90,10 +115,11 @@ function writeJson(file, value) {
90
115
  }
91
116
  function loadManifest(root) {
92
117
  const manifest = readJson(manifestPath(root), { version: 1, diffs: [] });
93
- return Array.isArray(manifest.diffs) ? manifest : { version: 1, diffs: [] };
118
+ return Array.isArray(manifest.diffs) ? { version: 1, diffs: manifest.diffs.filter(isValidBundle) } : { version: 1, diffs: [] };
94
119
  }
95
120
  function loadBundleMetadata(root, bundle) {
96
- return readJson(metadataPath(root, bundle), bundle);
121
+ const metadata = readJson(metadataPath(root, bundle), bundle);
122
+ return isValidBundle(metadata) && metadata.id === bundle.id ? metadata : bundle;
97
123
  }
98
124
  function readPatch(root, bundle) {
99
125
  const file = patchPath(root, bundle);
@@ -720,9 +746,9 @@ function languageIdToFence(languageId) {
720
746
  }
721
747
  function captureEditorSnapshot() {
722
748
  const vscode18 = require("vscode");
723
- const workspaceRoot2 = vscode18.workspace.workspaceFolders?.[0]?.uri.fsPath;
724
749
  const editor = vscode18.window.activeTextEditor;
725
- if (!editor) return { workspaceRoot: workspaceRoot2 };
750
+ if (!editor) return { workspaceRoot: vscode18.workspace.workspaceFolders?.[0]?.uri.fsPath };
751
+ const workspaceRoot2 = vscode18.workspace.getWorkspaceFolder(editor.document.uri)?.uri.fsPath;
726
752
  const absolutePath = editor.document.uri.fsPath;
727
753
  const relativePath = workspaceRoot2 ? path2.relative(workspaceRoot2, absolutePath) : absolutePath;
728
754
  const activeFile = { path: relativePath, languageId: editor.document.languageId };
@@ -1294,12 +1320,16 @@ var ChatController = class {
1294
1320
  attachments = [];
1295
1321
  status = "idle";
1296
1322
  turnHandle;
1323
+ activeTurnId = 0;
1297
1324
  pendingPermissions = /* @__PURE__ */ new Map();
1298
1325
  // --- commands ---
1299
1326
  async newChat() {
1300
1327
  const root = this.requireWorkspaceRoot();
1301
1328
  if (!root) return;
1302
1329
  this.turnHandle?.cancel();
1330
+ this.activeTurnId++;
1331
+ this.turnHandle = void 0;
1332
+ this.denyAllPending("A new chat was started.");
1303
1333
  this.record = createSession(root);
1304
1334
  this.attachments = [];
1305
1335
  this.status = "idle";
@@ -1418,6 +1448,10 @@ var ChatController = class {
1418
1448
  vscode6.window.showWarningMessage(reason);
1419
1449
  return;
1420
1450
  }
1451
+ if (this.record && snapshot.workspaceRoot !== this.record.session.workspaceRoot) {
1452
+ vscode6.window.showWarningMessage("The active file belongs to a different workspace folder. Start a new chat for that folder.");
1453
+ return;
1454
+ }
1421
1455
  const attachment = kind === "file" ? { kind: "file", file: snapshot.activeFile } : { kind: "selection", selection: snapshot.selection };
1422
1456
  this.attachments.push(attachment);
1423
1457
  this.ensurePanel();
@@ -1437,7 +1471,7 @@ var ChatController = class {
1437
1471
  /** The single pathway every turn goes through — manual sends and editor
1438
1472
  * actions alike. */
1439
1473
  async dispatchTurn(promptText) {
1440
- const root = this.requireWorkspaceRoot();
1474
+ const root = this.record?.session.workspaceRoot ?? this.requireWorkspaceRoot();
1441
1475
  if (!root) return;
1442
1476
  if (this.status === "running") {
1443
1477
  vscode6.window.showWarningMessage("UR is already running a request. Cancel it first or wait for it to finish.");
@@ -1460,17 +1494,24 @@ var ChatController = class {
1460
1494
  this._onDidChangeState.fire();
1461
1495
  this.panel?.post({ type: "statusChanged", status: this.status });
1462
1496
  const resumeSessionId = this.record.session.cliSessionId;
1463
- this.turnHandle = runUrTurn(
1497
+ const turnId = ++this.activeTurnId;
1498
+ let exitedSynchronously = false;
1499
+ const handle = runUrTurn(
1464
1500
  { cwd: root, prompt: promptText, resumeSessionId },
1465
1501
  {
1466
- onMessage: (message) => this.handleStreamMessage(root, sessionId, message),
1467
- onControlRequest: (request) => this.handlePermissionRequest(request),
1468
- onExit: (result) => this.handleTurnExit(root, result)
1502
+ onMessage: (message) => this.handleStreamMessage(root, sessionId, turnId, message),
1503
+ onControlRequest: (request) => this.handlePermissionRequest(turnId, request),
1504
+ onExit: (result) => {
1505
+ exitedSynchronously = true;
1506
+ this.handleTurnExit(root, sessionId, turnId, result);
1507
+ }
1469
1508
  },
1470
1509
  { executable: resolveUrCommand({ cwd: root, config: readUrCommandConfig() }) }
1471
1510
  );
1511
+ this.turnHandle = exitedSynchronously ? void 0 : handle;
1472
1512
  }
1473
- handleStreamMessage(root, sessionId, message) {
1513
+ handleStreamMessage(root, sessionId, turnId, message) {
1514
+ if (turnId !== this.activeTurnId) return;
1474
1515
  if (message.type === "system" && message.subtype === "init" && typeof message.session_id === "string") {
1475
1516
  if (this.record && this.record.session.id === sessionId && !this.record.session.cliSessionId) {
1476
1517
  setCliSessionId(root, sessionId, message.session_id);
@@ -1508,24 +1549,28 @@ var ChatController = class {
1508
1549
  if (!this.record) return;
1509
1550
  this.appendChatMessage(root, this.record.session.id, "status", [{ type: "text", text }]);
1510
1551
  }
1511
- handlePermissionRequest(request) {
1512
- return new Promise((resolve2) => {
1552
+ handlePermissionRequest(turnId, request) {
1553
+ if (turnId !== this.activeTurnId) {
1554
+ return Promise.resolve({ behavior: "deny", message: "This chat turn is no longer active." });
1555
+ }
1556
+ return new Promise((resolve3) => {
1513
1557
  const toolName = request.request.tool_name ?? "tool";
1514
1558
  const input = request.request.input ?? {};
1515
- this.pendingPermissions.set(request.request_id, { resolve: resolve2, toolName, input });
1559
+ this.pendingPermissions.set(request.request_id, { resolve: resolve3, toolName, input, turnId });
1516
1560
  this.panel?.post({ type: "permissionRequest", requestId: request.request_id, toolName, input });
1517
1561
  });
1518
1562
  }
1519
- handleTurnExit(root, result) {
1563
+ handleTurnExit(root, sessionId, turnId, result) {
1564
+ if (turnId !== this.activeTurnId) return;
1520
1565
  this.turnHandle = void 0;
1521
1566
  this.denyAllPending("The chat turn ended before this request was answered.");
1522
1567
  if (result.canceled) {
1523
1568
  this.status = "canceled";
1524
- this.appendStatusText(root, "Canceled.");
1569
+ this.appendChatMessage(root, sessionId, "status", [{ type: "text", text: "Canceled." }]);
1525
1570
  } else if (!result.ok) {
1526
1571
  this.status = "error";
1527
1572
  const message = result.error ?? "UR failed to complete this turn.";
1528
- this.appendStatusText(root, `Error: ${message}`);
1573
+ this.appendChatMessage(root, sessionId, "status", [{ type: "text", text: `Error: ${message}` }]);
1529
1574
  this.panel?.post({ type: "errorBanner", message });
1530
1575
  } else {
1531
1576
  this.status = "idle";
@@ -1535,13 +1580,13 @@ var ChatController = class {
1535
1580
  }
1536
1581
  resolvePermission(requestId, decision) {
1537
1582
  const pending = this.pendingPermissions.get(requestId);
1538
- if (!pending) return;
1583
+ if (!pending || pending.turnId !== this.activeTurnId) return;
1539
1584
  this.pendingPermissions.delete(requestId);
1540
1585
  pending.resolve(
1541
1586
  decision === "allow" ? { behavior: "allow", updatedInput: pending.input } : { behavior: "deny", message: "User denied this tool call from the UR Chat panel." }
1542
1587
  );
1543
1588
  this.panel?.post({ type: "permissionResolved", requestId });
1544
- const root = workspaceRoot();
1589
+ const root = this.record?.session.workspaceRoot;
1545
1590
  if (root) this.appendStatusText(root, `${decision === "allow" ? "Allowed" : "Denied"} ${pending.toolName}.`);
1546
1591
  }
1547
1592
  denyAllPending(reason) {
@@ -2,7 +2,7 @@
2
2
  "name": "ur-inline-diffs",
3
3
  "displayName": "UR Inline Diffs",
4
4
  "description": "Review, apply, and reject UR inline diff bundles from .ur/ide/diffs inside VS Code.",
5
- "version": "1.44.10",
5
+ "version": "1.45.3",
6
6
  "publisher": "ur-nexus",
7
7
  "engines": {
8
8
  "vscode": "^1.92.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ur-agent",
3
- "version": "1.44.10",
3
+ "version": "1.45.3",
4
4
  "description": "UR-Nexus — autonomous engineering workflow engine (plan, execute, test, verify, document, benchmark, reproduce)",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.14",
@@ -71,6 +71,7 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "diff2html": "^3.4.56",
74
+ "playwright-core": "^1.55.0",
74
75
  "sharp": "^0.35.1"
75
76
  },
76
77
  "devDependencies": {