openwork-server 0.18.57 → 0.18.59
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/dist/opencode-plugin-deps-1.18.30.tgz +0 -0
- package/dist/opencode-plugins/drive-file-tools.js +62 -0
- package/dist/opencode-plugins/managed-policy-next.js +18 -1
- package/dist/opencode-plugins/opencode-stop-fence-client.js +6 -0
- package/dist/opencode-plugins/openwork-agent-permissions-v2.js +14177 -0
- package/dist/opencode-plugins/openwork-capabilities-knowledge.js +197 -5
- package/dist/opencode-plugins/openwork-chrome-devtools.js +5 -5
- package/dist/opencode-plugins/openwork-cloud-skills-v2.js +222 -0
- package/dist/opencode-plugins/openwork-codemode-discovery-v2.js +60 -0
- package/dist/opencode-plugins/openwork-context-v2.js +13868 -1
- package/dist/opencode-plugins/openwork-extensions-preview-steering.js +1 -1
- package/dist/opencode-plugins/openwork-extensions-preview.js +195 -49
- package/dist/opencode-plugins/openwork-gateway-quota-v2.js +55 -24
- package/dist/opencode-plugins/openwork-gateway-quota.js +31 -18
- package/dist/opencode-plugins/openwork-mcp-readiness-v2.js +192 -0
- package/dist/opencode-plugins/openwork-mcp-results-v2.js +10836 -13
- package/dist/opencode-plugins/openwork-office-attachments.js +1 -1
- package/dist/opencode-plugins/openwork-provider-filters-v2.js +5 -7
- package/dist/opencode-plugins/openwork-provider-sync-v2.js +3880 -0
- package/dist/opencode-plugins/openwork-spreadsheets.js +1 -1
- package/dist/opencode-plugins/openwork-title-recovery.js +18 -6
- package/dist/opencode-plugins/openwork-ui-bridge.js +5 -2
- package/dist/opencode-plugins/workspace-file-destinations.js +136 -0
- package/dist/openwork-server.mjs +7794 -2938
- package/package.json +1 -1
- package/web/assets/{abnfDiagram-VCTEODGH-Dv8KxM1F.js → abnfDiagram-VCTEODGH-DwrIJf48.js} +1 -1
- package/web/assets/app-CHfH2zRH.css +32 -0
- package/web/assets/{app-E_RdTPah.js → app-wI0raXAn.js} +487 -435
- package/web/assets/{arc-_WbfJV_K.js → arc-ZosP3oxH.js} +1 -1
- package/web/assets/{architectureDiagram-5GKGNRK7-CckpRlSR.js → architectureDiagram-5GKGNRK7-DJ7WZh-0.js} +1 -1
- package/web/assets/{artifact-code-view-BbU019Q_.js → artifact-code-view-C_Qx94_t.js} +1 -1
- package/web/assets/{artifact-spreadsheet-editor-B3q9ZZoB.js → artifact-spreadsheet-editor-dniMTtEq.js} +1 -1
- package/web/assets/{artifact-text-editor-CA2Dgulr.js → artifact-text-editor-CRcxx-eZ.js} +1 -1
- package/web/assets/{blockDiagram-OSKFZWR5-BKdntEom.js → blockDiagram-OSKFZWR5-BRRMYQEs.js} +1 -1
- package/web/assets/{c4Diagram-7LVT6UL2-BJLyhqO1.js → c4Diagram-7LVT6UL2-gOHlDk2i.js} +1 -1
- package/web/assets/channel-CGPaVydu.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-D88ZIDGU.js → chunk-2Q5K7J3B-ndvUlkwq.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-DebGr6SE.js → chunk-5VM5RSS4-BFRve9Ox.js} +1 -1
- package/web/assets/{chunk-F27PBJKO-ChkVnT1d.js → chunk-F27PBJKO-CD-z5ZbY.js} +1 -1
- package/web/assets/{chunk-HLEWEB6X-ER6ZF6Pd.js → chunk-HLEWEB6X-CoWYhcUt.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-CQw0tJnK.js → chunk-JWPE2WC7-CsOREOhy.js} +1 -1
- package/web/assets/{chunk-POPQ4Y6H-Dm94DEFc.js → chunk-POPQ4Y6H-BPOdruz2.js} +1 -1
- package/web/assets/{chunk-SVP7TREG-rzCKlvNW.js → chunk-SVP7TREG-B7QwRO46.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-BcXExR9z.js → chunk-XXDRQBXY-DZp-LfVy.js} +1 -1
- package/web/assets/{chunk-ZLD2IHE6-CRtSpyr1.js → chunk-ZLD2IHE6-Bo9EgwIQ.js} +1 -1
- package/web/assets/classDiagram-CYGNFDIV-Bsii9q3h.js +1 -0
- package/web/assets/classDiagram-v2-TLXNO2FR-Bsii9q3h.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-1inJ0kKP.js → cose-bilkent-JH36ORCC-H7EUgT4k.js} +1 -1
- package/web/assets/{cynefin-OW5HDTMX-BAEIfHgW.js → cynefin-OW5HDTMX-r1tJgDH9.js} +1 -1
- package/web/assets/{cynefinDiagram-5FMLGOSQ-Dw_qVKID.js → cynefinDiagram-5FMLGOSQ-Cq4Pjmmj.js} +1 -1
- package/web/assets/{dagre-OS7QT2EB-BXAAz87u.js → dagre-OS7QT2EB-Bh2GFhAP.js} +1 -1
- package/web/assets/{diagram-S7CK7UJ4-BQt6gteN.js → diagram-S7CK7UJ4-BZNr_IG6.js} +1 -1
- package/web/assets/{diagram-UQ7AKVKN-Dh7vrHwF.js → diagram-UQ7AKVKN-BgaDOS70.js} +1 -1
- package/web/assets/{diagram-VSXAHHWV-D2kNxmUp.js → diagram-VSXAHHWV-DjUt-msp.js} +1 -1
- package/web/assets/{diagram-VX7I27RA-CctQDix_.js → diagram-VX7I27RA-BI3GsO3i.js} +1 -1
- package/web/assets/{diagram-Z3DM3KII-CDGlIVG5.js → diagram-Z3DM3KII-BLqI9pS_.js} +1 -1
- package/web/assets/{ebnfDiagram-PWID7BFC-0bgAb7z3.js → ebnfDiagram-PWID7BFC-D_CdQ2MS.js} +1 -1
- package/web/assets/{erDiagram-2YWLMYGG-Dbur-iWa.js → erDiagram-2YWLMYGG-C1SWDIhQ.js} +1 -1
- package/web/assets/{flowDiagram-T62WH6J4-Cw6AeymU.js → flowDiagram-T62WH6J4-BfXQV4xP.js} +1 -1
- package/web/assets/{ganttDiagram-EL5Y4UJY-BXYwFWOW.js → ganttDiagram-EL5Y4UJY-Bk-epoGY.js} +1 -1
- package/web/assets/{gitGraphDiagram-WWUBYQGX-De6ZCLNz.js → gitGraphDiagram-WWUBYQGX-O1ARqYlP.js} +1 -1
- package/web/assets/{infoDiagram-FKFFQAWI-B11KM4en.js → infoDiagram-FKFFQAWI-WZNXnPS1.js} +1 -1
- package/web/assets/{ishikawaDiagram-5VMMS53U-kGPWhBux.js → ishikawaDiagram-5VMMS53U-B8j8HgqU.js} +1 -1
- package/web/assets/{journeyDiagram-EYS64GPL-K8Wtegvp.js → journeyDiagram-EYS64GPL-dd2d-my9.js} +1 -1
- package/web/assets/{kanban-definition-UXKFOSKX-CjtX_cLJ.js → kanban-definition-UXKFOSKX-B6HfzCjr.js} +1 -1
- package/web/assets/{layout-Cn0uoRC-.js → layout-CjAP8jGd.js} +1 -1
- package/web/assets/{linear-WTNza7Ko.js → linear-DmcE_jj7.js} +1 -1
- package/web/assets/{mermaid.core-CbBVVRTZ.js → mermaid.core-DByIm3Os.js} +6 -6
- package/web/assets/{mindmap-definition-THT77NOG-DjyO1aA-.js → mindmap-definition-THT77NOG-CJdJrkzY.js} +1 -1
- package/web/assets/{pegDiagram-XKGWAZYB-ChPXU87n.js → pegDiagram-XKGWAZYB-DMKRyZmT.js} +1 -1
- package/web/assets/{pieDiagram-E7YTZNPT-bPQtXz_p.js → pieDiagram-E7YTZNPT-C0b_9HGX.js} +1 -1
- package/web/assets/{quadrantDiagram-AXDQQJYC-DokQ5FGV.js → quadrantDiagram-AXDQQJYC-BqkbJG9P.js} +1 -1
- package/web/assets/{railroadDiagram-O6MQD6OU-CcdqGxuL.js → railroadDiagram-O6MQD6OU-DXXz-as-.js} +1 -1
- package/web/assets/{requirementDiagram-IS5BZ75X-CGMF9hMZ.js → requirementDiagram-IS5BZ75X-DXkMFIBa.js} +1 -1
- package/web/assets/{sankeyDiagram-P5KCCOFB-fc-d15bH.js → sankeyDiagram-P5KCCOFB-CzXdocFr.js} +1 -1
- package/web/assets/{sequenceDiagram-WJ2MYXX4-DAmsOR8u.js → sequenceDiagram-WJ2MYXX4-aJBXn8VU.js} +1 -1
- package/web/assets/{sizeCapture-INFHLROL-6o7CY1B-.js → sizeCapture-INFHLROL-jgtrjDHs.js} +1 -1
- package/web/assets/{stateDiagram-XQSTLZYL-En5t4mkI.js → stateDiagram-XQSTLZYL-DrWWKXiY.js} +1 -1
- package/web/assets/stateDiagram-v2-IH3M54BS-D2cYASvR.js +1 -0
- package/web/assets/{swimlanes-V6O3JKXN-COBv2NoI.js → swimlanes-V6O3JKXN-D8Ewz1mj.js} +1 -1
- package/web/assets/swimlanesDiagram-JKAHXJPX-B9WLoHr4.js +8 -0
- package/web/assets/{timeline-definition-24CTP7MA-Qb-aTtSE.js → timeline-definition-24CTP7MA-B25naqcG.js} +1 -1
- package/web/assets/{vennDiagram-4TSXK5OY-Br3-0Ze-.js → vennDiagram-4TSXK5OY-3lHwPwrc.js} +1 -1
- package/web/assets/{wardleyDiagram-VM6X3IG4-BCeY3AKL.js → wardleyDiagram-VM6X3IG4-RGq5f_bX.js} +1 -1
- package/web/assets/{workspace-file-tree-CYzKM8q4.js → workspace-file-tree-Dt4_4AuL.js} +1 -1
- package/web/assets/{xychartDiagram-S5SC5T6Z-Bz96OPKc.js → xychartDiagram-S5SC5T6Z-DysrZCDk.js} +1 -1
- package/web/ext-microsoft-365.svg +1 -0
- package/web/index.html +2 -2
- package/dist/opencode-plugins/workspace-file-identity.js +0 -162
- package/web/assets/app-B682snBU.css +0 -32
- package/web/assets/channel-BZvy6aHa.js +0 -1
- package/web/assets/classDiagram-CYGNFDIV-DBAMzIwS.js +0 -1
- package/web/assets/classDiagram-v2-TLXNO2FR-DBAMzIwS.js +0 -1
- package/web/assets/katex-HP8lGamR.js +0 -257
- package/web/assets/stateDiagram-v2-IH3M54BS-DvIXHm_9.js +0 -1
- package/web/assets/swimlanesDiagram-JKAHXJPX-CorMSufQ.js +0 -8
|
@@ -13621,7 +13621,7 @@ function appendAgentInstructions(system, ...groups) {
|
|
|
13621
13621
|
${body}` : body;
|
|
13622
13622
|
}
|
|
13623
13623
|
|
|
13624
|
-
// src/
|
|
13624
|
+
// src/workspace-file-identity.ts
|
|
13625
13625
|
import { constants } from "node:fs";
|
|
13626
13626
|
import { lstat, open, realpath, rm } from "node:fs/promises";
|
|
13627
13627
|
import { dirname, isAbsolute, relative } from "node:path";
|
|
@@ -87,7 +87,12 @@ async function errorBody(response) {
|
|
|
87
87
|
return null;
|
|
88
88
|
text += decoder.decode(chunk.value, { stream: true });
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
text += decoder.decode();
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(text);
|
|
93
|
+
} catch {
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
91
96
|
} catch {
|
|
92
97
|
return null;
|
|
93
98
|
} finally {
|
|
@@ -127,7 +132,11 @@ function correction(body, value) {
|
|
|
127
132
|
delete target[key];
|
|
128
133
|
return { body: JSON.stringify(parsed), parameter };
|
|
129
134
|
}
|
|
130
|
-
function failure(status) {
|
|
135
|
+
function failure(status, value) {
|
|
136
|
+
const messages = typeof value === "string" ? [value] : record(value) ? [value.message, value.__type] : [];
|
|
137
|
+
if (status === 403 && messages.some((part) => typeof part === "string" && /SignatureDoesNotMatch|signature we calculated does not match/i.test(part))) {
|
|
138
|
+
return "signature_rejected";
|
|
139
|
+
}
|
|
131
140
|
if (status === 401 || status === 403)
|
|
132
141
|
return "access_rejected";
|
|
133
142
|
if (status === 404)
|
|
@@ -148,7 +157,8 @@ function install() {
|
|
|
148
157
|
const id = headers.get(HEADER);
|
|
149
158
|
if (!id)
|
|
150
159
|
return base(input, init);
|
|
151
|
-
headers.
|
|
160
|
+
if (!/^AWS4-HMAC-SHA256\s/i.test(headers.get("authorization") ?? ""))
|
|
161
|
+
headers.delete(HEADER);
|
|
152
162
|
const externalSignal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
|
|
153
163
|
const deadline = AbortSignal.timeout(DEADLINE_MS);
|
|
154
164
|
const options = { ...init, headers, signal: externalSignal ? AbortSignal.any([externalSignal, deadline]) : deadline };
|
|
@@ -158,9 +168,10 @@ function install() {
|
|
|
158
168
|
try {
|
|
159
169
|
const response = await base(input, options);
|
|
160
170
|
if (!response.ok) {
|
|
161
|
-
|
|
171
|
+
const value = response.status === 400 || response.status === 403 ? await errorBody(response) : null;
|
|
172
|
+
report(attempt, failure(response.status, value), response.status);
|
|
162
173
|
if (!attempt.retried && response.status === 400 && typeof init?.body === "string") {
|
|
163
|
-
const recovered = correction(init.body,
|
|
174
|
+
const recovered = correction(init.body, value);
|
|
164
175
|
if (recovered && attempts.get(id) === attempt && !options.signal.aborted) {
|
|
165
176
|
attempt.retried = true;
|
|
166
177
|
report(attempt, "retrying_parameter", response.status, recovered.parameter);
|
|
@@ -170,7 +181,8 @@ function install() {
|
|
|
170
181
|
const retryHeaders = new Headers(headers);
|
|
171
182
|
retryHeaders.delete("content-length");
|
|
172
183
|
const retry = await base(input, { ...options, headers: retryHeaders, body: recovered.body });
|
|
173
|
-
|
|
184
|
+
const retryError = retry.status === 403 ? await errorBody(retry) : null;
|
|
185
|
+
report(attempt, retry.ok ? "accepted_after_recovery" : failure(retry.status, retryError), retry.status);
|
|
174
186
|
return retry;
|
|
175
187
|
}
|
|
176
188
|
}
|
|
@@ -49,7 +49,9 @@ async function discoverUiBridge() {
|
|
|
49
49
|
}
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
|
-
export async function uiBridgeRequest(path,
|
|
52
|
+
export async function uiBridgeRequest(path,
|
|
53
|
+
/** `timeoutMs: null` waits until `signal` aborts. */
|
|
54
|
+
options = {}) {
|
|
53
55
|
const bridge = await discoverUiBridge();
|
|
54
56
|
if (!bridge)
|
|
55
57
|
return { ok: false, code: "browser_unavailable", dispatched: false, error: "The built-in browser is unavailable. Open the desktop app on the same machine as this task's server." };
|
|
@@ -57,7 +59,8 @@ export async function uiBridgeRequest(path, options = {}) {
|
|
|
57
59
|
const response = await fetch(`${bridge.baseUrl}${path}`, {
|
|
58
60
|
method: options.method || "GET",
|
|
59
61
|
redirect: "error",
|
|
60
|
-
signal: options.
|
|
62
|
+
signal: options.timeoutMs === null && options.signal ? options.signal
|
|
63
|
+
: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(options.timeoutMs ?? BRIDGE_TIMEOUT_MS)]) : AbortSignal.timeout(options.timeoutMs ?? BRIDGE_TIMEOUT_MS),
|
|
61
64
|
headers: {
|
|
62
65
|
Authorization: `Bearer ${bridge.token}`,
|
|
63
66
|
...(options.body ? { "Content-Type": "application/json" } : {}),
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, relative, win32 } from "node:path";
|
|
3
|
+
import { rulesFromPermissionConfig, winningRule } from "../effective-permissions.js";
|
|
4
|
+
const nativeMutations = new Set(["write", "edit", "apply_patch"]);
|
|
5
|
+
const record = (value) => typeof value === "object" && value !== null;
|
|
6
|
+
function promptWorkspacePath(directory) {
|
|
7
|
+
return directory.replace(/[\u0000-\u001f\u007f\u2028\u2029<>&]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
8
|
+
}
|
|
9
|
+
export function correctWorkspaceRoot(system, directory) {
|
|
10
|
+
for (let index = 0; index < system.length; index++) {
|
|
11
|
+
system[index] = system[index].replace(/<env>[\s\S]*?<\/env>/g, block => block.includes("Is directory a git repo: no")
|
|
12
|
+
? block.replace(/(^[ \t]*Workspace root folder: )\/(\r?$)/m, (_match, prefix, suffix) => `${prefix}${promptWorkspacePath(directory)}${suffix}`)
|
|
13
|
+
: block);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function workspaceFileGuidance(directory) {
|
|
17
|
+
return `The opened workspace directory is ${JSON.stringify(promptWorkspacePath(directory))}. For files requested in this workspace, prefer plain relative names such as report.html; native file tools save them inside this directory. Do not invent /workspace or a drive-root location. On Windows, drive-less rooted file paths such as /report.html or \\report.html are interpreted relative to this workspace, with the actual destination reported. Explicit full drive paths and UNC paths retain external-folder permissions. Do not bypass a denied path using another tool.`;
|
|
18
|
+
}
|
|
19
|
+
/** Only drive-less rooted syntax changes. Fully qualified and shell-drive forms keep native semantics. */
|
|
20
|
+
export function workspaceRootedPath(input, directory) {
|
|
21
|
+
if (!/^[\\/]/.test(input))
|
|
22
|
+
return undefined;
|
|
23
|
+
if (/^[\\/]{2}[^\\/]+[\\/][^\\/]+/.test(input) || /^[\\/]{2}[?.][\\/]/.test(input))
|
|
24
|
+
return undefined;
|
|
25
|
+
if (/^\/(?:(?:mnt|cygdrive)\/[a-z](?:\/|$)|[a-z]:(?:[\\/]|$)|[a-z](?:\/|$))/i.test(input))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (!win32.isAbsolute(directory) || !/^(?:[a-z]:[\\/]|[\\/]{2})/i.test(directory))
|
|
28
|
+
throw new Error("The opened workspace must have a fully qualified Windows path.");
|
|
29
|
+
const target = win32.resolve(directory, input.replace(/^[\\/]+/, ""));
|
|
30
|
+
const child = win32.relative(directory, target);
|
|
31
|
+
if (child === ".." || child.startsWith("..\\") || win32.isAbsolute(child))
|
|
32
|
+
throw new Error(`Workspace-rooted path ${JSON.stringify(input)} escapes ${JSON.stringify(directory)}. Use a filename inside the workspace, or an explicit authorized full path.`);
|
|
33
|
+
return target;
|
|
34
|
+
}
|
|
35
|
+
export async function assertWorkspaceDestination(directory, target) {
|
|
36
|
+
let ancestor = target;
|
|
37
|
+
let actual;
|
|
38
|
+
while (true) {
|
|
39
|
+
try {
|
|
40
|
+
actual = await realpath(ancestor);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!record(error) || error.code !== "ENOENT" || dirname(ancestor) === ancestor)
|
|
45
|
+
throw error;
|
|
46
|
+
ancestor = dirname(ancestor);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const child = relative(await realpath(directory), actual);
|
|
50
|
+
if (child === ".." || child.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) || isAbsolute(child))
|
|
51
|
+
throw new Error(`The workspace destination ${JSON.stringify(target)} follows a link outside ${JSON.stringify(directory)}. No file was changed.`);
|
|
52
|
+
}
|
|
53
|
+
export async function planWorkspacePaths(tool, args, directory, options = {}) {
|
|
54
|
+
if ((options.platform ?? process.platform) !== "win32" || !nativeMutations.has(tool) || !record(args))
|
|
55
|
+
return undefined;
|
|
56
|
+
const resolutions = [];
|
|
57
|
+
const path = async (requested) => {
|
|
58
|
+
const resolved = workspaceRootedPath(requested, directory);
|
|
59
|
+
if (!resolved)
|
|
60
|
+
return requested;
|
|
61
|
+
// Explicit folder grants continue to use the engine's original destination/permission flow.
|
|
62
|
+
const original = tool === "apply_patch" ? win32.resolve(directory, requested) : win32.resolve(options.cwd ?? process.cwd(), requested);
|
|
63
|
+
if (await options.externalAllowed?.(original))
|
|
64
|
+
return requested;
|
|
65
|
+
if (!resolutions.some(entry => entry.requested === requested && entry.resolved === resolved))
|
|
66
|
+
resolutions.push({ requested, resolved });
|
|
67
|
+
return resolved;
|
|
68
|
+
};
|
|
69
|
+
if ((tool === "write" || tool === "edit") && typeof args.filePath === "string") {
|
|
70
|
+
const value = await path(args.filePath);
|
|
71
|
+
return resolutions.length ? { field: "filePath", value, resolutions } : undefined;
|
|
72
|
+
}
|
|
73
|
+
if (tool !== "apply_patch" || typeof args.patchText !== "string")
|
|
74
|
+
return undefined;
|
|
75
|
+
const lines = args.patchText.split("\n");
|
|
76
|
+
for (let index = 0; index < lines.length; index++) {
|
|
77
|
+
// Match the pinned v1 parser's source/move headers, not added/context lines.
|
|
78
|
+
const match = /^(\*\*\* (?:Add File|Update File|Delete File|Move to):[ \t]*)([^\r]*)(\r?)$/.exec(lines[index]);
|
|
79
|
+
if (match)
|
|
80
|
+
lines[index] = match[1] + await path(match[2].trim()) + match[3];
|
|
81
|
+
}
|
|
82
|
+
return resolutions.length ? { field: "patchText", value: lines.join("\n"), resolutions } : undefined;
|
|
83
|
+
}
|
|
84
|
+
export function workspaceFileHooks(context, options = {}) {
|
|
85
|
+
const resolvedCalls = new WeakMap();
|
|
86
|
+
return {
|
|
87
|
+
"tool.definition": async (input, output) => {
|
|
88
|
+
if (!context?.directory || !nativeMutations.has(input.toolID))
|
|
89
|
+
return;
|
|
90
|
+
output.description += `\n\n${workspaceFileGuidance(context.directory)}`;
|
|
91
|
+
// Pinned v1 file schemas; runtime validation remains the engine's original schema.
|
|
92
|
+
const filePath = { type: "string", description: "Path to the file. Prefer a plain workspace-relative name such as report.html; relative paths are saved in the opened workspace. Explicit full external paths retain folder permissions." };
|
|
93
|
+
if (input.toolID === "write")
|
|
94
|
+
output.jsonSchema = { type: "object", properties: { content: { type: "string", description: "The content to write" }, filePath }, required: ["content", "filePath"] };
|
|
95
|
+
if (input.toolID === "edit")
|
|
96
|
+
output.jsonSchema = { type: "object", properties: { filePath, oldString: { type: "string", description: "The exact text to replace" }, newString: { type: "string", description: "The replacement text" }, replaceAll: { type: "boolean", description: "Replace every matching occurrence" } }, required: ["filePath", "oldString", "newString"] };
|
|
97
|
+
},
|
|
98
|
+
"tool.execute.before": async (input, output) => {
|
|
99
|
+
if ((options.platform ?? process.platform) === "win32" && nativeMutations.has(input.tool) && !context?.directory)
|
|
100
|
+
throw new Error("No opened workspace directory is available; no file operation was performed.");
|
|
101
|
+
if (!context?.directory)
|
|
102
|
+
return;
|
|
103
|
+
const plan = await planWorkspacePaths(input.tool, output.args, context.directory, {
|
|
104
|
+
platform: options.platform,
|
|
105
|
+
cwd: options.cwd,
|
|
106
|
+
externalAllowed: async (original) => {
|
|
107
|
+
if (!context.client)
|
|
108
|
+
return false;
|
|
109
|
+
const response = await context.client.config.get();
|
|
110
|
+
if (!response.data || response.error)
|
|
111
|
+
throw new Error("Could not check external-folder grants; no workspace path was changed. Retry the operation.");
|
|
112
|
+
const rules = rulesFromPermissionConfig(response.data.permission);
|
|
113
|
+
const grant = winningRule(rules, "external_directory", win32.join(win32.dirname(original), "*"));
|
|
114
|
+
// A blanket run-mode allow is not an explicit destination choice.
|
|
115
|
+
return grant?.permission === "external_directory" && grant.action === "allow" && grant.pattern !== "*";
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (!plan || !record(output.args))
|
|
119
|
+
return;
|
|
120
|
+
for (const entry of plan.resolutions)
|
|
121
|
+
await (options.validate ?? assertWorkspaceDestination)(context.directory, entry.resolved);
|
|
122
|
+
output.args[plan.field] = plan.value;
|
|
123
|
+
resolvedCalls.set(output.args, plan.resolutions);
|
|
124
|
+
},
|
|
125
|
+
"tool.execute.after": async (input, output) => {
|
|
126
|
+
if (!record(input.args))
|
|
127
|
+
return;
|
|
128
|
+
const resolutions = resolvedCalls.get(input.args);
|
|
129
|
+
if (!resolutions)
|
|
130
|
+
return;
|
|
131
|
+
resolvedCalls.delete(input.args);
|
|
132
|
+
output.metadata.openworkWorkspacePaths = resolutions;
|
|
133
|
+
output.output = resolutions.map(entry => `Workspace destination: ${entry.resolved} (interpreted ${JSON.stringify(entry.requested)} relative to the opened workspace).`).join("\n") + "\n" + output.output;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|