ur-agent 1.65.4 → 1.65.6
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 +116 -30
- package/README.md +2 -1
- package/RELEASE.md +13 -2
- package/bin/ur.js +77 -5
- package/dist/cli.js +48594 -5460
- package/docs/AGENT_FEATURES.md +1 -0
- package/docs/USAGE.md +3 -1
- package/docs/VALIDATION.md +1 -1
- package/documentation/index.html +1 -1
- package/extensions/jetbrains-ur/build.gradle.kts +1 -1
- package/extensions/vscode-ur-inline-diffs/out/extension.js +358 -105
- package/extensions/vscode-ur-inline-diffs/package.json +1 -1
- package/package.json +2 -1
|
@@ -40,18 +40,75 @@ var vscode17 = __toESM(require("vscode"));
|
|
|
40
40
|
var vscode2 = __toESM(require("vscode"));
|
|
41
41
|
|
|
42
42
|
// src/diffs/store.ts
|
|
43
|
+
var fs2 = __toESM(require("node:fs"));
|
|
44
|
+
var path2 = __toESM(require("node:path"));
|
|
45
|
+
var vscode = __toESM(require("vscode"));
|
|
46
|
+
|
|
47
|
+
// src/util/safeWorkspacePath.ts
|
|
48
|
+
var import_node_crypto = require("node:crypto");
|
|
43
49
|
var fs = __toESM(require("node:fs"));
|
|
44
50
|
var path = __toESM(require("node:path"));
|
|
45
|
-
|
|
51
|
+
function safeWorkspacePath(workspaceRoot2, candidate, label = "UR workspace data") {
|
|
52
|
+
const root = path.resolve(workspaceRoot2);
|
|
53
|
+
const target = path.resolve(candidate);
|
|
54
|
+
const relative3 = path.relative(root, target);
|
|
55
|
+
if (!relative3 || relative3 === ".." || relative3.startsWith(`..${path.sep}`) || path.isAbsolute(relative3)) {
|
|
56
|
+
throw new Error(`${label} path escapes the workspace`);
|
|
57
|
+
}
|
|
58
|
+
let current = root;
|
|
59
|
+
for (const segment of relative3.split(path.sep)) {
|
|
60
|
+
current = path.join(current, segment);
|
|
61
|
+
try {
|
|
62
|
+
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
63
|
+
throw new Error(`${label} path contains a symbolic link: ${current}`);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (isMissingPath(error)) continue;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return target;
|
|
71
|
+
}
|
|
72
|
+
function writeWorkspaceJsonAtomic(workspaceRoot2, candidate, value, label = "UR workspace data") {
|
|
73
|
+
const target = safeWorkspacePath(workspaceRoot2, candidate, label);
|
|
74
|
+
const directory = path.dirname(target);
|
|
75
|
+
safeWorkspacePath(workspaceRoot2, directory, label);
|
|
76
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
77
|
+
safeWorkspacePath(workspaceRoot2, directory, label);
|
|
78
|
+
const temporary = path.join(
|
|
79
|
+
directory,
|
|
80
|
+
`.${path.basename(target)}.${process.pid}.${(0, import_node_crypto.randomUUID)()}.tmp`
|
|
81
|
+
);
|
|
82
|
+
try {
|
|
83
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
|
|
84
|
+
`, {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
flag: "wx",
|
|
87
|
+
mode: 384
|
|
88
|
+
});
|
|
89
|
+
fs.renameSync(temporary, target);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
try {
|
|
92
|
+
fs.rmSync(temporary, { force: true });
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isMissingPath(error) {
|
|
99
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/diffs/store.ts
|
|
46
103
|
function workspaceRoot() {
|
|
47
104
|
const activeUri = vscode.window.activeTextEditor?.document.uri;
|
|
48
105
|
return (activeUri ? vscode.workspace.getWorkspaceFolder(activeUri) : void 0)?.uri.fsPath ?? vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
49
106
|
}
|
|
50
107
|
function diffsRoot(root) {
|
|
51
|
-
return
|
|
108
|
+
return path2.join(root, ".ur", "ide", "diffs");
|
|
52
109
|
}
|
|
53
110
|
function manifestPath(root) {
|
|
54
|
-
return
|
|
111
|
+
return path2.join(diffsRoot(root), "manifest.json");
|
|
55
112
|
}
|
|
56
113
|
function patchPath(root, bundle) {
|
|
57
114
|
return artifactPath(root, bundle, "patch");
|
|
@@ -60,58 +117,91 @@ function metadataPath(root, bundle) {
|
|
|
60
117
|
return artifactPath(root, bundle, "metadata");
|
|
61
118
|
}
|
|
62
119
|
var DIFF_ID_PATTERN = /^diff-[1-9][0-9]*$/u;
|
|
120
|
+
var DIFF_STATUSES = /* @__PURE__ */ new Set(["pending", "commented", "approved", "rejected"]);
|
|
121
|
+
var MAX_DIFF_JSON_BYTES = 16 * 1024 * 1024;
|
|
122
|
+
var MAX_PATCH_BYTES = 64 * 1024 * 1024;
|
|
63
123
|
function artifactPath(root, bundle, kind) {
|
|
64
124
|
if (!DIFF_ID_PATTERN.test(bundle.id)) throw new Error(`Invalid UR diff id: ${bundle.id}`);
|
|
65
|
-
const
|
|
125
|
+
const relative3 = kind === "patch" ? bundle.patchFile : bundle.metadataFile;
|
|
66
126
|
const expected = kind === "patch" ? `patches/${bundle.id}.patch` : `metadata/${bundle.id}.json`;
|
|
67
|
-
if (
|
|
127
|
+
if (relative3.replaceAll("\\", "/") !== expected) {
|
|
68
128
|
throw new Error(`Invalid UR diff ${kind} path for ${bundle.id}`);
|
|
69
129
|
}
|
|
70
|
-
const rootPath =
|
|
71
|
-
const target =
|
|
72
|
-
if (!target.startsWith(`${rootPath}${
|
|
73
|
-
return target;
|
|
130
|
+
const rootPath = path2.resolve(diffsRoot(root));
|
|
131
|
+
const target = path2.resolve(rootPath, relative3);
|
|
132
|
+
if (!target.startsWith(`${rootPath}${path2.sep}`)) throw new Error(`UR diff ${kind} path escapes the diff store`);
|
|
133
|
+
return safeWorkspacePath(root, target, `UR diff ${kind}`);
|
|
74
134
|
}
|
|
75
|
-
function isValidBundle(value) {
|
|
76
|
-
if (!value
|
|
135
|
+
function isValidBundle(root, value) {
|
|
136
|
+
if (!isRecord(value)) return false;
|
|
77
137
|
const bundle = value;
|
|
78
138
|
try {
|
|
79
|
-
patchPath(
|
|
80
|
-
metadataPath(
|
|
81
|
-
return
|
|
139
|
+
patchPath(root, bundle);
|
|
140
|
+
metadataPath(root, bundle);
|
|
141
|
+
return typeof bundle.title === "string" && bundle.title.length > 0 && DIFF_STATUSES.has(bundle.status) && (bundle.baseRef === void 0 || typeof bundle.baseRef === "string") && (bundle.staged === void 0 || typeof bundle.staged === "boolean") && Array.isArray(bundle.files) && bundle.files.every(isValidFileChange) && Array.isArray(bundle.comments) && bundle.comments.every(isValidComment) && typeof bundle.createdAt === "string" && typeof bundle.updatedAt === "string";
|
|
82
142
|
} catch {
|
|
83
143
|
return false;
|
|
84
144
|
}
|
|
85
145
|
}
|
|
86
|
-
function readJson(file, fallback) {
|
|
146
|
+
function readJson(root, file, fallback, maxBytes = MAX_DIFF_JSON_BYTES) {
|
|
87
147
|
try {
|
|
88
|
-
|
|
148
|
+
const safeFile = safeWorkspacePath(root, file, "UR diff");
|
|
149
|
+
const size = fs2.statSync(safeFile).size;
|
|
150
|
+
if (!Number.isSafeInteger(size) || size < 0 || size > maxBytes) {
|
|
151
|
+
return fallback;
|
|
152
|
+
}
|
|
153
|
+
return JSON.parse(fs2.readFileSync(safeFile, "utf8"));
|
|
89
154
|
} catch {
|
|
90
155
|
return fallback;
|
|
91
156
|
}
|
|
92
157
|
}
|
|
93
|
-
function writeJson(file, value) {
|
|
94
|
-
|
|
95
|
-
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
96
|
-
`);
|
|
158
|
+
function writeJson(root, file, value) {
|
|
159
|
+
writeWorkspaceJsonAtomic(root, file, value, "UR diff");
|
|
97
160
|
}
|
|
98
161
|
function loadManifest(root) {
|
|
99
|
-
const manifest = readJson(
|
|
100
|
-
|
|
162
|
+
const manifest = readJson(
|
|
163
|
+
root,
|
|
164
|
+
manifestPath(root),
|
|
165
|
+
{ version: 1, diffs: [] }
|
|
166
|
+
);
|
|
167
|
+
return isRecord(manifest) && Array.isArray(manifest.diffs) ? {
|
|
168
|
+
version: 1,
|
|
169
|
+
diffs: manifest.diffs.filter((bundle) => isValidBundle(root, bundle))
|
|
170
|
+
} : { version: 1, diffs: [] };
|
|
101
171
|
}
|
|
102
172
|
function loadBundleMetadata(root, bundle) {
|
|
103
|
-
const metadata = readJson(
|
|
104
|
-
|
|
173
|
+
const metadata = readJson(
|
|
174
|
+
root,
|
|
175
|
+
metadataPath(root, bundle),
|
|
176
|
+
bundle
|
|
177
|
+
);
|
|
178
|
+
return isValidBundle(root, metadata) && metadata.id === bundle.id ? metadata : bundle;
|
|
105
179
|
}
|
|
106
180
|
function readPatch(root, bundle) {
|
|
107
181
|
const file = patchPath(root, bundle);
|
|
108
|
-
|
|
182
|
+
if (!fs2.existsSync(file)) return "";
|
|
183
|
+
const size = fs2.statSync(file).size;
|
|
184
|
+
if (!Number.isSafeInteger(size) || size < 0 || size > MAX_PATCH_BYTES) {
|
|
185
|
+
throw new Error(`UR diff patch exceeds ${MAX_PATCH_BYTES / (1024 * 1024)} MiB`);
|
|
186
|
+
}
|
|
187
|
+
return fs2.readFileSync(file, "utf8");
|
|
109
188
|
}
|
|
110
189
|
function writeManifest(root, manifest) {
|
|
111
|
-
writeJson(manifestPath(root), manifest);
|
|
190
|
+
writeJson(root, manifestPath(root), manifest);
|
|
112
191
|
}
|
|
113
192
|
function writeBundleMetadata(root, bundle) {
|
|
114
|
-
writeJson(metadataPath(root, bundle), bundle);
|
|
193
|
+
writeJson(root, metadataPath(root, bundle), bundle);
|
|
194
|
+
}
|
|
195
|
+
function isRecord(value) {
|
|
196
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
197
|
+
}
|
|
198
|
+
function isValidFileChange(value) {
|
|
199
|
+
if (!isRecord(value)) return false;
|
|
200
|
+
return typeof value.path === "string" && Number.isSafeInteger(value.additions) && Number(value.additions) >= 0 && Number.isSafeInteger(value.deletions) && Number(value.deletions) >= 0;
|
|
201
|
+
}
|
|
202
|
+
function isValidComment(value) {
|
|
203
|
+
if (!isRecord(value)) return false;
|
|
204
|
+
return typeof value.at === "string" && typeof value.text === "string" && (value.file === void 0 || typeof value.file === "string") && (value.line === void 0 || Number.isSafeInteger(value.line) && Number(value.line) > 0);
|
|
115
205
|
}
|
|
116
206
|
|
|
117
207
|
// src/bridge/urCli.ts
|
|
@@ -234,7 +324,7 @@ function isCapturedNonZeroExit(error) {
|
|
|
234
324
|
|
|
235
325
|
// src/actions/background.ts
|
|
236
326
|
var VALID_STATUSES = ["queued", "running", "completed", "failed", "canceled"];
|
|
237
|
-
function
|
|
327
|
+
function isRecord2(value) {
|
|
238
328
|
return typeof value === "object" && value !== null;
|
|
239
329
|
}
|
|
240
330
|
function tryParseBackgroundListJson(raw) {
|
|
@@ -244,10 +334,10 @@ function tryParseBackgroundListJson(raw) {
|
|
|
244
334
|
} catch {
|
|
245
335
|
return null;
|
|
246
336
|
}
|
|
247
|
-
if (!
|
|
337
|
+
if (!isRecord2(data) || !Array.isArray(data.tasks)) return null;
|
|
248
338
|
const summaries = [];
|
|
249
339
|
for (const entry of data.tasks) {
|
|
250
|
-
if (!
|
|
340
|
+
if (!isRecord2(entry)) continue;
|
|
251
341
|
if (typeof entry.id !== "string" || typeof entry.task !== "string") continue;
|
|
252
342
|
if (!VALID_STATUSES.includes(entry.status)) continue;
|
|
253
343
|
const status = entry.status;
|
|
@@ -652,7 +742,7 @@ var ActionsTreeProvider = class {
|
|
|
652
742
|
};
|
|
653
743
|
|
|
654
744
|
// src/chat/chatController.ts
|
|
655
|
-
var
|
|
745
|
+
var import_node_crypto4 = require("node:crypto");
|
|
656
746
|
var vscode6 = __toESM(require("vscode"));
|
|
657
747
|
|
|
658
748
|
// src/bridge/types.ts
|
|
@@ -670,6 +760,12 @@ function isCanUseToolRequest(message) {
|
|
|
670
760
|
var import_node_child_process3 = require("node:child_process");
|
|
671
761
|
var NdjsonBuffer = class {
|
|
672
762
|
buffer = "";
|
|
763
|
+
discardingOversizedLine = false;
|
|
764
|
+
maxLineLength;
|
|
765
|
+
droppedOversizedLine = false;
|
|
766
|
+
constructor(maxLineLength = 16 * 1024 * 1024) {
|
|
767
|
+
this.maxLineLength = Number.isSafeInteger(maxLineLength) && maxLineLength > 0 ? maxLineLength : 16 * 1024 * 1024;
|
|
768
|
+
}
|
|
673
769
|
/** Feed a raw chunk (may contain zero, one, or many complete lines, and may
|
|
674
770
|
* split a line across two calls). Returns every complete, parseable line
|
|
675
771
|
* found. Malformed lines are dropped, never thrown — the CLI's own
|
|
@@ -677,21 +773,45 @@ var NdjsonBuffer = class {
|
|
|
677
773
|
* to stderr, so a malformed line here means something unexpected slipped
|
|
678
774
|
* through, not a reason to crash the extension. */
|
|
679
775
|
push(chunk) {
|
|
680
|
-
this.buffer += chunk;
|
|
681
776
|
const messages = [];
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
777
|
+
let offset = 0;
|
|
778
|
+
while (offset < chunk.length) {
|
|
779
|
+
const newline = chunk.indexOf("\n", offset);
|
|
780
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
781
|
+
const segment = chunk.slice(offset, end);
|
|
782
|
+
if (this.discardingOversizedLine) {
|
|
783
|
+
if (newline === -1) return messages;
|
|
784
|
+
this.discardingOversizedLine = false;
|
|
785
|
+
offset = newline + 1;
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (this.buffer.length + segment.length > this.maxLineLength) {
|
|
789
|
+
this.buffer = "";
|
|
790
|
+
this.droppedOversizedLine = true;
|
|
791
|
+
if (newline === -1) {
|
|
792
|
+
this.discardingOversizedLine = true;
|
|
793
|
+
return messages;
|
|
794
|
+
}
|
|
795
|
+
offset = newline + 1;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
this.buffer += segment;
|
|
799
|
+
if (newline === -1) return messages;
|
|
800
|
+
const parsed = parseNdjsonLine(this.buffer);
|
|
801
|
+
this.buffer = "";
|
|
688
802
|
if (parsed) messages.push(parsed);
|
|
803
|
+
offset = newline + 1;
|
|
689
804
|
}
|
|
690
805
|
return messages;
|
|
691
806
|
}
|
|
692
807
|
/** Whatever is left with no trailing newline yet (a genuinely partial line
|
|
693
808
|
* stays buffered; call this only once the stream has actually ended). */
|
|
694
809
|
flush() {
|
|
810
|
+
if (this.discardingOversizedLine) {
|
|
811
|
+
this.buffer = "";
|
|
812
|
+
this.discardingOversizedLine = false;
|
|
813
|
+
return [];
|
|
814
|
+
}
|
|
695
815
|
const rest = this.buffer;
|
|
696
816
|
this.buffer = "";
|
|
697
817
|
const parsed = parseNdjsonLine(rest);
|
|
@@ -736,6 +856,7 @@ function buildControlResponse(requestId, decision) {
|
|
|
736
856
|
};
|
|
737
857
|
}
|
|
738
858
|
var defaultSpawn = (command2, args, options) => (0, import_node_child_process3.spawn)(command2, args, options);
|
|
859
|
+
var MAX_CAPTURED_STDERR_CHARS = 16 * 1024;
|
|
739
860
|
function runUrTurn(request, handlers, deps = {}) {
|
|
740
861
|
const spawnFn = deps.spawn ?? defaultSpawn;
|
|
741
862
|
const executable = deps.executable ?? (deps.command ? { command: deps.command, args: [], source: "configured", display: deps.command } : resolveUrCommand({ cwd: request.cwd }));
|
|
@@ -768,7 +889,8 @@ function runUrTurn(request, handlers, deps = {}) {
|
|
|
768
889
|
} };
|
|
769
890
|
}
|
|
770
891
|
const stdoutBuffer = new NdjsonBuffer();
|
|
771
|
-
|
|
892
|
+
let stderrTail = "";
|
|
893
|
+
let omittedStderrChars = 0;
|
|
772
894
|
let sawResult = false;
|
|
773
895
|
let resultIsError = false;
|
|
774
896
|
let canceled = false;
|
|
@@ -776,8 +898,11 @@ function runUrTurn(request, handlers, deps = {}) {
|
|
|
776
898
|
const finish = (exitCode, signal, spawnError) => {
|
|
777
899
|
if (settled) return;
|
|
778
900
|
settled = true;
|
|
779
|
-
const stderr =
|
|
780
|
-
|
|
901
|
+
const stderr = omittedStderrChars > 0 ? `[${omittedStderrChars} earlier stderr characters omitted]
|
|
902
|
+
${stderrTail}` : stderrTail;
|
|
903
|
+
const protocolError = stdoutBuffer.droppedOversizedLine ? "UR emitted an oversized stream-JSON line; the extension refused to buffer unbounded output." : void 0;
|
|
904
|
+
const exitedCleanly = exitCode === 0 && signal === null;
|
|
905
|
+
const ok = !canceled && !spawnError && !protocolError && exitedCleanly && sawResult && !resultIsError;
|
|
781
906
|
handlers.onExit({
|
|
782
907
|
ok,
|
|
783
908
|
exitCode,
|
|
@@ -785,7 +910,22 @@ function runUrTurn(request, handlers, deps = {}) {
|
|
|
785
910
|
canceled,
|
|
786
911
|
sawResult,
|
|
787
912
|
stderr,
|
|
788
|
-
error: spawnError ?? (!ok && !canceled ?
|
|
913
|
+
error: spawnError ?? (!ok && !canceled ? protocolError ? formatTurnFailure({
|
|
914
|
+
executable,
|
|
915
|
+
cwd: request.cwd,
|
|
916
|
+
exitCode,
|
|
917
|
+
signal,
|
|
918
|
+
stderr,
|
|
919
|
+
reason: protocolError
|
|
920
|
+
}) : deriveErrorMessage(
|
|
921
|
+
executable,
|
|
922
|
+
request.cwd,
|
|
923
|
+
sawResult,
|
|
924
|
+
resultIsError,
|
|
925
|
+
exitCode,
|
|
926
|
+
signal,
|
|
927
|
+
stderr
|
|
928
|
+
) : void 0)
|
|
789
929
|
});
|
|
790
930
|
};
|
|
791
931
|
const handleMessage = (message) => {
|
|
@@ -811,7 +951,15 @@ function runUrTurn(request, handlers, deps = {}) {
|
|
|
811
951
|
}
|
|
812
952
|
});
|
|
813
953
|
child.stderr?.on("data", (chunk) => {
|
|
814
|
-
|
|
954
|
+
const text = chunk.toString("utf8");
|
|
955
|
+
const combined = stderrTail + text;
|
|
956
|
+
if (combined.length > MAX_CAPTURED_STDERR_CHARS) {
|
|
957
|
+
const excess = combined.length - MAX_CAPTURED_STDERR_CHARS;
|
|
958
|
+
omittedStderrChars += excess;
|
|
959
|
+
stderrTail = combined.slice(excess);
|
|
960
|
+
} else {
|
|
961
|
+
stderrTail = combined;
|
|
962
|
+
}
|
|
815
963
|
});
|
|
816
964
|
child.on("error", (error) => {
|
|
817
965
|
finish(
|
|
@@ -822,7 +970,8 @@ function runUrTurn(request, handlers, deps = {}) {
|
|
|
822
970
|
cwd: request.cwd,
|
|
823
971
|
exitCode: null,
|
|
824
972
|
signal: null,
|
|
825
|
-
stderr:
|
|
973
|
+
stderr: omittedStderrChars > 0 ? `[${omittedStderrChars} earlier stderr characters omitted]
|
|
974
|
+
${stderrTail}` : stderrTail,
|
|
826
975
|
reason: `Failed to run: ${errorMessage2(error)}`
|
|
827
976
|
})
|
|
828
977
|
);
|
|
@@ -849,7 +998,7 @@ function writeControlResponse(child, requestId, decision) {
|
|
|
849
998
|
}
|
|
850
999
|
}
|
|
851
1000
|
function deriveErrorMessage(executable, cwd, sawResult, resultIsError, exitCode, signal, stderr) {
|
|
852
|
-
const reason = sawResult && resultIsError ? "UR reported an error completing this turn." : "UR exited without producing a successful result.";
|
|
1001
|
+
const reason = sawResult && resultIsError ? "UR reported an error completing this turn." : sawResult ? "UR produced a result, but the process exited unsuccessfully." : "UR exited without producing a successful result.";
|
|
853
1002
|
return formatTurnFailure({ executable, cwd, exitCode, signal, stderr, reason });
|
|
854
1003
|
}
|
|
855
1004
|
function formatTurnFailure(options) {
|
|
@@ -874,7 +1023,7 @@ function errorMessage2(error) {
|
|
|
874
1023
|
}
|
|
875
1024
|
|
|
876
1025
|
// src/context/ideContext.ts
|
|
877
|
-
var
|
|
1026
|
+
var path3 = __toESM(require("node:path"));
|
|
878
1027
|
function formatAttachmentLabel(attachment) {
|
|
879
1028
|
if (attachment.kind === "file") return `@${attachment.file.path}`;
|
|
880
1029
|
const { path: filePath, startLine, endLine } = attachment.selection;
|
|
@@ -918,7 +1067,7 @@ function captureEditorSnapshot() {
|
|
|
918
1067
|
if (!editor) return { workspaceRoot: vscode18.workspace.workspaceFolders?.[0]?.uri.fsPath };
|
|
919
1068
|
const workspaceRoot2 = vscode18.workspace.getWorkspaceFolder(editor.document.uri)?.uri.fsPath;
|
|
920
1069
|
const absolutePath = editor.document.uri.fsPath;
|
|
921
|
-
const relativePath = workspaceRoot2 ?
|
|
1070
|
+
const relativePath = workspaceRoot2 ? path3.relative(workspaceRoot2, absolutePath) : absolutePath;
|
|
922
1071
|
const activeFile = { path: relativePath, languageId: editor.document.languageId };
|
|
923
1072
|
const selection = editor.selection;
|
|
924
1073
|
if (selection.isEmpty) return { workspaceRoot: workspaceRoot2, activeFile };
|
|
@@ -934,48 +1083,67 @@ function captureEditorSnapshot() {
|
|
|
934
1083
|
}
|
|
935
1084
|
|
|
936
1085
|
// src/sessions/sessionStore.ts
|
|
937
|
-
var
|
|
938
|
-
var
|
|
939
|
-
var
|
|
1086
|
+
var import_node_crypto2 = require("node:crypto");
|
|
1087
|
+
var fs3 = __toESM(require("node:fs"));
|
|
1088
|
+
var path4 = __toESM(require("node:path"));
|
|
940
1089
|
var SESSION_ID_PATTERN = /^[a-zA-Z0-9-]{1,128}$/;
|
|
941
1090
|
var TITLE_MAX_LENGTH = 60;
|
|
942
1091
|
var DEFAULT_TITLE = "New Chat";
|
|
1092
|
+
var MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
1093
|
+
var MAX_SESSION_BYTES = 64 * 1024 * 1024;
|
|
943
1094
|
function chatRoot(root) {
|
|
944
|
-
return
|
|
1095
|
+
return path4.join(root, ".ur", "ide", "chat");
|
|
945
1096
|
}
|
|
946
1097
|
function manifestPath2(root) {
|
|
947
|
-
return
|
|
1098
|
+
return path4.join(chatRoot(root), "manifest.json");
|
|
948
1099
|
}
|
|
949
1100
|
function isValidSessionId(id) {
|
|
950
1101
|
return SESSION_ID_PATTERN.test(id);
|
|
951
1102
|
}
|
|
952
1103
|
function sessionFilePath(root, id) {
|
|
953
1104
|
if (!isValidSessionId(id)) return null;
|
|
954
|
-
const sessionsDir =
|
|
955
|
-
const target =
|
|
956
|
-
const resolvedDir =
|
|
957
|
-
const resolvedTarget =
|
|
1105
|
+
const sessionsDir = path4.join(chatRoot(root), "sessions");
|
|
1106
|
+
const target = path4.join(sessionsDir, `${id}.json`);
|
|
1107
|
+
const resolvedDir = path4.resolve(sessionsDir) + path4.sep;
|
|
1108
|
+
const resolvedTarget = path4.resolve(target);
|
|
958
1109
|
if (!resolvedTarget.startsWith(resolvedDir)) return null;
|
|
959
1110
|
return target;
|
|
960
1111
|
}
|
|
961
|
-
function readJson2(file, fallback) {
|
|
1112
|
+
function readJson2(root, file, fallback, maxBytes) {
|
|
962
1113
|
try {
|
|
963
|
-
|
|
1114
|
+
const safeFile = safeWorkspacePath(root, file, "UR chat");
|
|
1115
|
+
const size = fs3.statSync(safeFile).size;
|
|
1116
|
+
if (!Number.isSafeInteger(size) || size < 0 || size > maxBytes) {
|
|
1117
|
+
return fallback;
|
|
1118
|
+
}
|
|
1119
|
+
return JSON.parse(fs3.readFileSync(safeFile, "utf8"));
|
|
964
1120
|
} catch {
|
|
965
1121
|
return fallback;
|
|
966
1122
|
}
|
|
967
1123
|
}
|
|
968
|
-
function writeJson2(file, value) {
|
|
969
|
-
|
|
970
|
-
fs2.writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
971
|
-
`);
|
|
1124
|
+
function writeJson2(root, file, value) {
|
|
1125
|
+
writeWorkspaceJsonAtomic(root, file, value, "UR chat");
|
|
972
1126
|
}
|
|
973
1127
|
function readManifest(root) {
|
|
974
|
-
const manifest = readJson2(
|
|
975
|
-
|
|
1128
|
+
const manifest = readJson2(
|
|
1129
|
+
root,
|
|
1130
|
+
manifestPath2(root),
|
|
1131
|
+
{ version: 1, sessions: [] },
|
|
1132
|
+
MAX_MANIFEST_BYTES
|
|
1133
|
+
);
|
|
1134
|
+
if (!isRecord3(manifest) || !Array.isArray(manifest.sessions)) {
|
|
1135
|
+
return { version: 1, sessions: [] };
|
|
1136
|
+
}
|
|
1137
|
+
const unique = /* @__PURE__ */ new Map();
|
|
1138
|
+
for (const session of manifest.sessions) {
|
|
1139
|
+
if (isValidSession(session, root) && !unique.has(session.id)) {
|
|
1140
|
+
unique.set(session.id, session);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return { version: 1, sessions: [...unique.values()] };
|
|
976
1144
|
}
|
|
977
1145
|
function writeManifest2(root, manifest) {
|
|
978
|
-
writeJson2(manifestPath2(root), manifest);
|
|
1146
|
+
writeJson2(root, manifestPath2(root), manifest);
|
|
979
1147
|
}
|
|
980
1148
|
function upsertManifestEntry(root, session) {
|
|
981
1149
|
const manifest = readManifest(root);
|
|
@@ -989,9 +1157,10 @@ function upsertManifestEntry(root, session) {
|
|
|
989
1157
|
}
|
|
990
1158
|
function createSession(root, options = {}) {
|
|
991
1159
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1160
|
+
const requestedTitle = typeof options.title === "string" ? options.title.trim() : "";
|
|
992
1161
|
const session = {
|
|
993
|
-
id: (0,
|
|
994
|
-
title:
|
|
1162
|
+
id: (0, import_node_crypto2.randomUUID)(),
|
|
1163
|
+
title: requestedTitle ? requestedTitle.slice(0, TITLE_MAX_LENGTH) : DEFAULT_TITLE,
|
|
995
1164
|
workspaceRoot: root,
|
|
996
1165
|
createdAt: now,
|
|
997
1166
|
updatedAt: now
|
|
@@ -999,7 +1168,7 @@ function createSession(root, options = {}) {
|
|
|
999
1168
|
const record = { session, messages: [] };
|
|
1000
1169
|
const file = sessionFilePath(root, session.id);
|
|
1001
1170
|
if (!file) throw new Error(`Generated an invalid session id: ${session.id}`);
|
|
1002
|
-
writeJson2(file, record);
|
|
1171
|
+
writeJson2(root, file, record);
|
|
1003
1172
|
upsertManifestEntry(root, session);
|
|
1004
1173
|
return record;
|
|
1005
1174
|
}
|
|
@@ -1010,10 +1179,17 @@ function listSessions(root, options = {}) {
|
|
|
1010
1179
|
}
|
|
1011
1180
|
function readSession(root, id) {
|
|
1012
1181
|
const file = sessionFilePath(root, id);
|
|
1013
|
-
if (!file
|
|
1014
|
-
|
|
1182
|
+
if (!file) return null;
|
|
1183
|
+
const record = readJson2(
|
|
1184
|
+
root,
|
|
1185
|
+
file,
|
|
1186
|
+
null,
|
|
1187
|
+
MAX_SESSION_BYTES
|
|
1188
|
+
);
|
|
1189
|
+
return isValidRecord(record, root, id) ? record : null;
|
|
1015
1190
|
}
|
|
1016
1191
|
function appendMessage(root, id, message) {
|
|
1192
|
+
if (!isValidMessage(message, id)) return null;
|
|
1017
1193
|
const record = readSession(root, id);
|
|
1018
1194
|
if (!record) return null;
|
|
1019
1195
|
record.messages.push(message);
|
|
@@ -1023,18 +1199,21 @@ function appendMessage(root, id, message) {
|
|
|
1023
1199
|
}
|
|
1024
1200
|
const file = sessionFilePath(root, id);
|
|
1025
1201
|
if (!file) return null;
|
|
1026
|
-
writeJson2(file, record);
|
|
1202
|
+
writeJson2(root, file, record);
|
|
1027
1203
|
upsertManifestEntry(root, record.session);
|
|
1028
1204
|
return record;
|
|
1029
1205
|
}
|
|
1030
1206
|
function setCliSessionId(root, id, cliSessionId) {
|
|
1207
|
+
if (typeof cliSessionId !== "string" || !cliSessionId || cliSessionId.length > 256 || cliSessionId.includes("\0")) {
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1031
1210
|
const record = readSession(root, id);
|
|
1032
1211
|
if (!record) return null;
|
|
1033
1212
|
record.session.cliSessionId = cliSessionId;
|
|
1034
1213
|
record.session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1035
1214
|
const file = sessionFilePath(root, id);
|
|
1036
1215
|
if (!file) return null;
|
|
1037
|
-
writeJson2(file, record);
|
|
1216
|
+
writeJson2(root, file, record);
|
|
1038
1217
|
upsertManifestEntry(root, record.session);
|
|
1039
1218
|
return record;
|
|
1040
1219
|
}
|
|
@@ -1043,9 +1222,66 @@ function deriveTitle(message) {
|
|
|
1043
1222
|
if (!text) return DEFAULT_TITLE;
|
|
1044
1223
|
return text.length > TITLE_MAX_LENGTH ? `${text.slice(0, TITLE_MAX_LENGTH - 1)}\u2026` : text;
|
|
1045
1224
|
}
|
|
1225
|
+
function isRecord3(value) {
|
|
1226
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1227
|
+
}
|
|
1228
|
+
function isValidSession(value, root) {
|
|
1229
|
+
if (!isRecord3(value)) return false;
|
|
1230
|
+
return typeof value.id === "string" && isValidSessionId(value.id) && typeof value.title === "string" && value.title.length > 0 && value.title.length <= TITLE_MAX_LENGTH && typeof value.workspaceRoot === "string" && path4.resolve(value.workspaceRoot) === path4.resolve(root) && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && (value.cliSessionId === void 0 || typeof value.cliSessionId === "string" && value.cliSessionId.length > 0 && value.cliSessionId.length <= 256 && !value.cliSessionId.includes("\0")) && (value.archived === void 0 || typeof value.archived === "boolean");
|
|
1231
|
+
}
|
|
1232
|
+
function isValidRecord(value, root, id) {
|
|
1233
|
+
if (!isRecord3(value) || !isValidSession(value.session, root)) return false;
|
|
1234
|
+
if (value.session.id !== id || !Array.isArray(value.messages)) return false;
|
|
1235
|
+
return value.messages.every((message) => isValidMessage(message, id));
|
|
1236
|
+
}
|
|
1237
|
+
function isValidMessage(value, sessionId) {
|
|
1238
|
+
if (!isRecord3(value)) return false;
|
|
1239
|
+
if (typeof value.id !== "string" || !value.id || value.id.length > 256 || value.sessionId !== sessionId || value.role !== "user" && value.role !== "assistant" && value.role !== "status" || typeof value.createdAt !== "string" || !Array.isArray(value.content)) {
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
return value.content.every(isValidContentBlock);
|
|
1243
|
+
}
|
|
1244
|
+
function isValidContentBlock(value) {
|
|
1245
|
+
if (!isRecord3(value) || typeof value.type !== "string") return false;
|
|
1246
|
+
if (value.type === "text") return typeof value.text === "string";
|
|
1247
|
+
if (value.type === "tool_use") {
|
|
1248
|
+
return typeof value.id === "string" && typeof value.name === "string" && "input" in value;
|
|
1249
|
+
}
|
|
1250
|
+
if (value.type === "tool_result") {
|
|
1251
|
+
return typeof value.toolUseId === "string" && typeof value.ok === "boolean" && typeof value.summary === "string";
|
|
1252
|
+
}
|
|
1253
|
+
if (value.type === "permission_request") {
|
|
1254
|
+
return typeof value.requestId === "string" && typeof value.toolName === "string" && (value.resolved === void 0 || value.resolved === "allow" || value.resolved === "deny");
|
|
1255
|
+
}
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1046
1258
|
|
|
1047
1259
|
// src/chat/chatPanel.ts
|
|
1260
|
+
var import_node_crypto3 = require("node:crypto");
|
|
1048
1261
|
var vscode5 = __toESM(require("vscode"));
|
|
1262
|
+
|
|
1263
|
+
// src/chat/webviewProtocol.ts
|
|
1264
|
+
var MAX_WEBVIEW_PROMPT_LENGTH = 1e6;
|
|
1265
|
+
var MAX_REQUEST_ID_LENGTH = 256;
|
|
1266
|
+
function isWebviewInboundMessage(value) {
|
|
1267
|
+
if (!isRecord4(value) || typeof value.type !== "string") return false;
|
|
1268
|
+
if (value.type === "ready" || value.type === "cancel") return true;
|
|
1269
|
+
if (value.type === "send") {
|
|
1270
|
+
return typeof value.text === "string" && value.text.length <= MAX_WEBVIEW_PROMPT_LENGTH && !value.text.includes("\0");
|
|
1271
|
+
}
|
|
1272
|
+
if (value.type === "permissionDecision") {
|
|
1273
|
+
return typeof value.requestId === "string" && value.requestId.length > 0 && value.requestId.length <= MAX_REQUEST_ID_LENGTH && (value.decision === "allow" || value.decision === "deny");
|
|
1274
|
+
}
|
|
1275
|
+
if (value.type === "removeAttachment") {
|
|
1276
|
+
return Number.isSafeInteger(value.index) && Number(value.index) >= 0 && Number(value.index) <= 1e4;
|
|
1277
|
+
}
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
function isRecord4(value) {
|
|
1281
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/chat/chatPanel.ts
|
|
1049
1285
|
var ChatPanel = class _ChatPanel {
|
|
1050
1286
|
static current;
|
|
1051
1287
|
panel;
|
|
@@ -1055,7 +1291,9 @@ var ChatPanel = class _ChatPanel {
|
|
|
1055
1291
|
this.panel = panel;
|
|
1056
1292
|
this.panel.webview.html = renderChatHtml(this.panel.webview);
|
|
1057
1293
|
this.disposables.push(
|
|
1058
|
-
this.panel.webview.onDidReceiveMessage((message) =>
|
|
1294
|
+
this.panel.webview.onDidReceiveMessage((message) => {
|
|
1295
|
+
if (isWebviewInboundMessage(message)) onMessage(message);
|
|
1296
|
+
}),
|
|
1059
1297
|
this.panel.onDidDispose(() => this.handleDispose())
|
|
1060
1298
|
);
|
|
1061
1299
|
}
|
|
@@ -1086,10 +1324,7 @@ var ChatPanel = class _ChatPanel {
|
|
|
1086
1324
|
}
|
|
1087
1325
|
};
|
|
1088
1326
|
function nonce() {
|
|
1089
|
-
|
|
1090
|
-
let value = "";
|
|
1091
|
-
for (let i = 0; i < 32; i++) value += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1092
|
-
return value;
|
|
1327
|
+
return (0, import_node_crypto3.randomBytes)(24).toString("base64url");
|
|
1093
1328
|
}
|
|
1094
1329
|
function renderChatHtml(webview) {
|
|
1095
1330
|
const scriptNonce = nonce();
|
|
@@ -1212,22 +1447,22 @@ function renderChatHtml(webview) {
|
|
|
1212
1447
|
</style>
|
|
1213
1448
|
</head>
|
|
1214
1449
|
<body>
|
|
1215
|
-
<div id="banner"></div>
|
|
1216
|
-
<div id="messages">
|
|
1450
|
+
<div id="banner" role="alert" aria-live="assertive"></div>
|
|
1451
|
+
<div id="messages" role="log" aria-live="polite" aria-relevant="additions" aria-label="UR chat messages">
|
|
1217
1452
|
<div id="empty-state">Ask UR about this workspace. Use <code>UR: Add Selection to Chat</code> or <code>UR: Add Current File to Chat</code> to attach code first.</div>
|
|
1218
1453
|
</div>
|
|
1219
|
-
<div id="permission-prompt">
|
|
1220
|
-
<div><strong>UR wants to use <span id="permission-tool"></span></strong></div>
|
|
1454
|
+
<div id="permission-prompt" role="alertdialog" aria-modal="true" aria-labelledby="permission-title">
|
|
1455
|
+
<div id="permission-title"><strong>UR wants to use <span id="permission-tool"></span></strong></div>
|
|
1221
1456
|
<div class="input-preview" id="permission-input"></div>
|
|
1222
1457
|
<div class="actions">
|
|
1223
1458
|
<button id="permission-allow">Allow</button>
|
|
1224
1459
|
<button id="permission-deny" class="secondary">Deny</button>
|
|
1225
1460
|
</div>
|
|
1226
1461
|
</div>
|
|
1227
|
-
<div id="attachments"></div>
|
|
1228
|
-
<div id="status-line"></div>
|
|
1462
|
+
<div id="attachments" aria-label="Attached editor context"></div>
|
|
1463
|
+
<div id="status-line" role="status" aria-live="polite"></div>
|
|
1229
1464
|
<form id="composer">
|
|
1230
|
-
<textarea id="input" placeholder="Message UR\u2026" rows="2"></textarea>
|
|
1465
|
+
<textarea id="input" aria-label="Message UR" maxlength="1000000" placeholder="Message UR\u2026" rows="2"></textarea>
|
|
1231
1466
|
<button id="send" type="submit">Send</button>
|
|
1232
1467
|
<button id="cancel" type="button" class="secondary" hidden>Cancel</button>
|
|
1233
1468
|
</form>
|
|
@@ -1246,6 +1481,8 @@ function renderChatHtml(webview) {
|
|
|
1246
1481
|
const inputEl = document.getElementById('input');
|
|
1247
1482
|
const sendButton = document.getElementById('send');
|
|
1248
1483
|
const cancelButton = document.getElementById('cancel');
|
|
1484
|
+
const permissionAllowButton = document.getElementById('permission-allow');
|
|
1485
|
+
const permissionDenyButton = document.getElementById('permission-deny');
|
|
1249
1486
|
|
|
1250
1487
|
let currentStatus = 'idle';
|
|
1251
1488
|
let pendingPermissionRequestId = null;
|
|
@@ -1312,6 +1549,8 @@ function renderChatHtml(webview) {
|
|
|
1312
1549
|
const remove = document.createElement('button');
|
|
1313
1550
|
remove.type = 'button';
|
|
1314
1551
|
remove.textContent = '\\u00d7';
|
|
1552
|
+
remove.title = 'Remove ' + attachment.label;
|
|
1553
|
+
remove.setAttribute('aria-label', 'Remove ' + attachment.label);
|
|
1315
1554
|
remove.addEventListener('click', function () {
|
|
1316
1555
|
vscode.postMessage({ type: 'removeAttachment', index: index });
|
|
1317
1556
|
});
|
|
@@ -1337,9 +1576,15 @@ function renderChatHtml(webview) {
|
|
|
1337
1576
|
bannerEl.classList.add('visible');
|
|
1338
1577
|
}
|
|
1339
1578
|
|
|
1579
|
+
function hideBanner() {
|
|
1580
|
+
bannerEl.textContent = '';
|
|
1581
|
+
bannerEl.classList.remove('visible');
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1340
1584
|
window.addEventListener('message', function (event) {
|
|
1341
1585
|
const message = event.data;
|
|
1342
1586
|
if (message.type === 'init') {
|
|
1587
|
+
hideBanner();
|
|
1343
1588
|
renderAll(message.messages);
|
|
1344
1589
|
renderAttachments(message.attachments);
|
|
1345
1590
|
applyStatus(message.status);
|
|
@@ -1349,9 +1594,12 @@ function renderChatHtml(webview) {
|
|
|
1349
1594
|
applyStatus(message.status);
|
|
1350
1595
|
} else if (message.type === 'permissionRequest') {
|
|
1351
1596
|
pendingPermissionRequestId = message.requestId;
|
|
1597
|
+
permissionAllowButton.disabled = false;
|
|
1598
|
+
permissionDenyButton.disabled = false;
|
|
1352
1599
|
permissionToolEl.textContent = message.toolName;
|
|
1353
1600
|
permissionInputEl.textContent = JSON.stringify(message.input, null, 2);
|
|
1354
1601
|
permissionEl.classList.add('visible');
|
|
1602
|
+
permissionDenyButton.focus();
|
|
1355
1603
|
} else if (message.type === 'permissionResolved') {
|
|
1356
1604
|
if (pendingPermissionRequestId === message.requestId) {
|
|
1357
1605
|
pendingPermissionRequestId = null;
|
|
@@ -1370,6 +1618,7 @@ function renderChatHtml(webview) {
|
|
|
1370
1618
|
event.preventDefault();
|
|
1371
1619
|
const text = inputEl.value.trim();
|
|
1372
1620
|
if (!text || currentStatus === 'running') return;
|
|
1621
|
+
hideBanner();
|
|
1373
1622
|
vscode.postMessage({ type: 'send', text: text });
|
|
1374
1623
|
inputEl.value = '';
|
|
1375
1624
|
});
|
|
@@ -1385,12 +1634,16 @@ function renderChatHtml(webview) {
|
|
|
1385
1634
|
vscode.postMessage({ type: 'cancel' });
|
|
1386
1635
|
});
|
|
1387
1636
|
|
|
1388
|
-
|
|
1637
|
+
permissionAllowButton.addEventListener('click', function () {
|
|
1389
1638
|
if (!pendingPermissionRequestId) return;
|
|
1639
|
+
permissionAllowButton.disabled = true;
|
|
1640
|
+
permissionDenyButton.disabled = true;
|
|
1390
1641
|
vscode.postMessage({ type: 'permissionDecision', requestId: pendingPermissionRequestId, decision: 'allow' });
|
|
1391
1642
|
});
|
|
1392
|
-
|
|
1643
|
+
permissionDenyButton.addEventListener('click', function () {
|
|
1393
1644
|
if (!pendingPermissionRequestId) return;
|
|
1645
|
+
permissionAllowButton.disabled = true;
|
|
1646
|
+
permissionDenyButton.disabled = true;
|
|
1394
1647
|
vscode.postMessage({ type: 'permissionDecision', requestId: pendingPermissionRequestId, decision: 'deny' });
|
|
1395
1648
|
});
|
|
1396
1649
|
|
|
@@ -1649,7 +1902,7 @@ var ChatController = class {
|
|
|
1649
1902
|
this.ensurePanel();
|
|
1650
1903
|
const sessionId = this.record.session.id;
|
|
1651
1904
|
const userMessage = {
|
|
1652
|
-
id: (0,
|
|
1905
|
+
id: (0, import_node_crypto4.randomUUID)(),
|
|
1653
1906
|
sessionId,
|
|
1654
1907
|
role: "user",
|
|
1655
1908
|
content: [{ type: "text", text: promptText }],
|
|
@@ -1708,7 +1961,7 @@ var ChatController = class {
|
|
|
1708
1961
|
}
|
|
1709
1962
|
appendChatMessage(root, sessionId, role, content) {
|
|
1710
1963
|
if (!this.record || this.record.session.id !== sessionId) return;
|
|
1711
|
-
const message = { id: (0,
|
|
1964
|
+
const message = { id: (0, import_node_crypto4.randomUUID)(), sessionId, role, content, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1712
1965
|
appendMessage(root, sessionId, message);
|
|
1713
1966
|
this.record.messages.push(message);
|
|
1714
1967
|
this.panel?.post({ type: "messageAppended", message });
|
|
@@ -1721,10 +1974,10 @@ var ChatController = class {
|
|
|
1721
1974
|
if (turnId !== this.activeTurnId) {
|
|
1722
1975
|
return Promise.resolve({ behavior: "deny", message: "This chat turn is no longer active." });
|
|
1723
1976
|
}
|
|
1724
|
-
return new Promise((
|
|
1977
|
+
return new Promise((resolve4) => {
|
|
1725
1978
|
const toolName = request.request.tool_name ?? "tool";
|
|
1726
1979
|
const input = request.request.input ?? {};
|
|
1727
|
-
this.pendingPermissions.set(request.request_id, { resolve:
|
|
1980
|
+
this.pendingPermissions.set(request.request_id, { resolve: resolve4, toolName, input, turnId });
|
|
1728
1981
|
this.panel?.post({ type: "permissionRequest", requestId: request.request_id, toolName, input });
|
|
1729
1982
|
});
|
|
1730
1983
|
}
|
|
@@ -1881,7 +2134,7 @@ var ChatTreeProvider = class {
|
|
|
1881
2134
|
|
|
1882
2135
|
// src/diffs/actions.ts
|
|
1883
2136
|
var import_node_child_process4 = require("node:child_process");
|
|
1884
|
-
var
|
|
2137
|
+
var fs4 = __toESM(require("node:fs"));
|
|
1885
2138
|
var import_node_util2 = require("node:util");
|
|
1886
2139
|
var vscode8 = __toESM(require("vscode"));
|
|
1887
2140
|
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process4.execFile);
|
|
@@ -1921,7 +2174,7 @@ async function applyDiff(item, provider) {
|
|
|
1921
2174
|
return;
|
|
1922
2175
|
}
|
|
1923
2176
|
const patch = patchPath(root, bundle);
|
|
1924
|
-
if (!
|
|
2177
|
+
if (!fs4.existsSync(patch)) {
|
|
1925
2178
|
vscode8.window.showErrorMessage(`UR patch file missing for ${bundle.id}.`);
|
|
1926
2179
|
return;
|
|
1927
2180
|
}
|
|
@@ -2053,7 +2306,7 @@ async function openDiff(item) {
|
|
|
2053
2306
|
|
|
2054
2307
|
// src/model/modelPicker.ts
|
|
2055
2308
|
var vscode10 = __toESM(require("vscode"));
|
|
2056
|
-
function
|
|
2309
|
+
function isRecord5(value) {
|
|
2057
2310
|
return typeof value === "object" && value !== null;
|
|
2058
2311
|
}
|
|
2059
2312
|
function parseProviderList(raw) {
|
|
@@ -2061,7 +2314,7 @@ function parseProviderList(raw) {
|
|
|
2061
2314
|
const data = JSON.parse(raw);
|
|
2062
2315
|
if (!Array.isArray(data)) return [];
|
|
2063
2316
|
return data.flatMap((entry) => {
|
|
2064
|
-
if (!
|
|
2317
|
+
if (!isRecord5(entry) || typeof entry.id !== "string" || typeof entry.name !== "string") return [];
|
|
2065
2318
|
return [
|
|
2066
2319
|
{
|
|
2067
2320
|
id: entry.id,
|
|
@@ -2079,14 +2332,14 @@ function parseProviderList(raw) {
|
|
|
2079
2332
|
function parseProviderModels(raw) {
|
|
2080
2333
|
try {
|
|
2081
2334
|
const data = JSON.parse(raw);
|
|
2082
|
-
if (!
|
|
2335
|
+
if (!isRecord5(data) || typeof data.provider !== "string" || !Array.isArray(data.models)) return void 0;
|
|
2083
2336
|
const source = data.source === "live" || data.source === "cache" || data.source === "static" ? data.source : "static";
|
|
2084
2337
|
return {
|
|
2085
2338
|
provider: data.provider,
|
|
2086
2339
|
source,
|
|
2087
2340
|
warning: typeof data.warning === "string" ? data.warning : void 0,
|
|
2088
2341
|
models: data.models.flatMap((model) => {
|
|
2089
|
-
if (!
|
|
2342
|
+
if (!isRecord5(model) || typeof model.id !== "string") return [];
|
|
2090
2343
|
return [
|
|
2091
2344
|
{
|
|
2092
2345
|
id: model.id,
|
|
@@ -2104,7 +2357,7 @@ function parseProviderModels(raw) {
|
|
|
2104
2357
|
function parseProviderStatus(raw) {
|
|
2105
2358
|
try {
|
|
2106
2359
|
const data = JSON.parse(raw);
|
|
2107
|
-
if (!
|
|
2360
|
+
if (!isRecord5(data)) return {};
|
|
2108
2361
|
return {
|
|
2109
2362
|
provider: typeof data.provider === "string" ? data.provider : void 0,
|
|
2110
2363
|
model: typeof data.model === "string" ? data.model : void 0
|
|
@@ -2116,8 +2369,8 @@ function parseProviderStatus(raw) {
|
|
|
2116
2369
|
function parseIdeStatus(raw) {
|
|
2117
2370
|
try {
|
|
2118
2371
|
const data = JSON.parse(raw);
|
|
2119
|
-
if (!
|
|
2120
|
-
const provider =
|
|
2372
|
+
if (!isRecord5(data)) return {};
|
|
2373
|
+
const provider = isRecord5(data.provider) ? data.provider : {};
|
|
2121
2374
|
return {
|
|
2122
2375
|
model: typeof provider.model === "string" ? provider.model : void 0
|
|
2123
2376
|
};
|
|
@@ -2333,7 +2586,7 @@ function deriveMultimodalSupport(providerId) {
|
|
|
2333
2586
|
// src/options/providerOptionsLoader.ts
|
|
2334
2587
|
var PROVIDER_KINDS = ["ur-native", "subscription-cli", "subscription-placeholder"];
|
|
2335
2588
|
var ACCESS_TYPES = ["subscription", "api", "local", "server"];
|
|
2336
|
-
function
|
|
2589
|
+
function isRecord6(value) {
|
|
2337
2590
|
return typeof value === "object" && value !== null;
|
|
2338
2591
|
}
|
|
2339
2592
|
function parseProviderListJson(raw) {
|
|
@@ -2346,7 +2599,7 @@ function parseProviderListJson(raw) {
|
|
|
2346
2599
|
if (!Array.isArray(data)) return [];
|
|
2347
2600
|
const options = [];
|
|
2348
2601
|
for (const entry of data) {
|
|
2349
|
-
if (!
|
|
2602
|
+
if (!isRecord6(entry)) continue;
|
|
2350
2603
|
if (typeof entry.id !== "string" || typeof entry.name !== "string") continue;
|
|
2351
2604
|
const providerKind = PROVIDER_KINDS.includes(entry.providerKind) ? entry.providerKind : "subscription-placeholder";
|
|
2352
2605
|
const accessType = ACCESS_TYPES.includes(entry.accessType) ? entry.accessType : "api";
|
|
@@ -2590,13 +2843,13 @@ async function showSearchActions() {
|
|
|
2590
2843
|
var vscode15 = __toESM(require("vscode"));
|
|
2591
2844
|
|
|
2592
2845
|
// src/status/statusData.ts
|
|
2593
|
-
function
|
|
2846
|
+
function isRecord7(value) {
|
|
2594
2847
|
return typeof value === "object" && value !== null;
|
|
2595
2848
|
}
|
|
2596
2849
|
function safeParseRecord(raw) {
|
|
2597
2850
|
try {
|
|
2598
2851
|
const parsed = JSON.parse(raw);
|
|
2599
|
-
return
|
|
2852
|
+
return isRecord7(parsed) ? parsed : {};
|
|
2600
2853
|
} catch {
|
|
2601
2854
|
return {};
|
|
2602
2855
|
}
|
|
@@ -2609,8 +2862,8 @@ function asKnownBoolean(value) {
|
|
|
2609
2862
|
}
|
|
2610
2863
|
function parseIdeStatusJson(raw, fallbackWorkspaceRoot = "") {
|
|
2611
2864
|
const data = safeParseRecord(raw);
|
|
2612
|
-
const acpRaw =
|
|
2613
|
-
const providerRaw =
|
|
2865
|
+
const acpRaw = isRecord7(data.acp) ? data.acp : {};
|
|
2866
|
+
const providerRaw = isRecord7(data.provider) ? data.provider : {};
|
|
2614
2867
|
return {
|
|
2615
2868
|
workspaceRoot: typeof data.workspaceRoot === "string" && data.workspaceRoot ? data.workspaceRoot : fallbackWorkspaceRoot,
|
|
2616
2869
|
acp: {
|