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.
Files changed (96) hide show
  1. package/dist/opencode-plugin-deps-1.18.30.tgz +0 -0
  2. package/dist/opencode-plugins/drive-file-tools.js +62 -0
  3. package/dist/opencode-plugins/managed-policy-next.js +18 -1
  4. package/dist/opencode-plugins/opencode-stop-fence-client.js +6 -0
  5. package/dist/opencode-plugins/openwork-agent-permissions-v2.js +14177 -0
  6. package/dist/opencode-plugins/openwork-capabilities-knowledge.js +197 -5
  7. package/dist/opencode-plugins/openwork-chrome-devtools.js +5 -5
  8. package/dist/opencode-plugins/openwork-cloud-skills-v2.js +222 -0
  9. package/dist/opencode-plugins/openwork-codemode-discovery-v2.js +60 -0
  10. package/dist/opencode-plugins/openwork-context-v2.js +13868 -1
  11. package/dist/opencode-plugins/openwork-extensions-preview-steering.js +1 -1
  12. package/dist/opencode-plugins/openwork-extensions-preview.js +195 -49
  13. package/dist/opencode-plugins/openwork-gateway-quota-v2.js +55 -24
  14. package/dist/opencode-plugins/openwork-gateway-quota.js +31 -18
  15. package/dist/opencode-plugins/openwork-mcp-readiness-v2.js +192 -0
  16. package/dist/opencode-plugins/openwork-mcp-results-v2.js +10836 -13
  17. package/dist/opencode-plugins/openwork-office-attachments.js +1 -1
  18. package/dist/opencode-plugins/openwork-provider-filters-v2.js +5 -7
  19. package/dist/opencode-plugins/openwork-provider-sync-v2.js +3880 -0
  20. package/dist/opencode-plugins/openwork-spreadsheets.js +1 -1
  21. package/dist/opencode-plugins/openwork-title-recovery.js +18 -6
  22. package/dist/opencode-plugins/openwork-ui-bridge.js +5 -2
  23. package/dist/opencode-plugins/workspace-file-destinations.js +136 -0
  24. package/dist/openwork-server.mjs +7794 -2938
  25. package/package.json +1 -1
  26. package/web/assets/{abnfDiagram-VCTEODGH-Dv8KxM1F.js → abnfDiagram-VCTEODGH-DwrIJf48.js} +1 -1
  27. package/web/assets/app-CHfH2zRH.css +32 -0
  28. package/web/assets/{app-E_RdTPah.js → app-wI0raXAn.js} +487 -435
  29. package/web/assets/{arc-_WbfJV_K.js → arc-ZosP3oxH.js} +1 -1
  30. package/web/assets/{architectureDiagram-5GKGNRK7-CckpRlSR.js → architectureDiagram-5GKGNRK7-DJ7WZh-0.js} +1 -1
  31. package/web/assets/{artifact-code-view-BbU019Q_.js → artifact-code-view-C_Qx94_t.js} +1 -1
  32. package/web/assets/{artifact-spreadsheet-editor-B3q9ZZoB.js → artifact-spreadsheet-editor-dniMTtEq.js} +1 -1
  33. package/web/assets/{artifact-text-editor-CA2Dgulr.js → artifact-text-editor-CRcxx-eZ.js} +1 -1
  34. package/web/assets/{blockDiagram-OSKFZWR5-BKdntEom.js → blockDiagram-OSKFZWR5-BRRMYQEs.js} +1 -1
  35. package/web/assets/{c4Diagram-7LVT6UL2-BJLyhqO1.js → c4Diagram-7LVT6UL2-gOHlDk2i.js} +1 -1
  36. package/web/assets/channel-CGPaVydu.js +1 -0
  37. package/web/assets/{chunk-2Q5K7J3B-D88ZIDGU.js → chunk-2Q5K7J3B-ndvUlkwq.js} +1 -1
  38. package/web/assets/{chunk-5VM5RSS4-DebGr6SE.js → chunk-5VM5RSS4-BFRve9Ox.js} +1 -1
  39. package/web/assets/{chunk-F27PBJKO-ChkVnT1d.js → chunk-F27PBJKO-CD-z5ZbY.js} +1 -1
  40. package/web/assets/{chunk-HLEWEB6X-ER6ZF6Pd.js → chunk-HLEWEB6X-CoWYhcUt.js} +1 -1
  41. package/web/assets/{chunk-JWPE2WC7-CQw0tJnK.js → chunk-JWPE2WC7-CsOREOhy.js} +1 -1
  42. package/web/assets/{chunk-POPQ4Y6H-Dm94DEFc.js → chunk-POPQ4Y6H-BPOdruz2.js} +1 -1
  43. package/web/assets/{chunk-SVP7TREG-rzCKlvNW.js → chunk-SVP7TREG-B7QwRO46.js} +1 -1
  44. package/web/assets/{chunk-XXDRQBXY-BcXExR9z.js → chunk-XXDRQBXY-DZp-LfVy.js} +1 -1
  45. package/web/assets/{chunk-ZLD2IHE6-CRtSpyr1.js → chunk-ZLD2IHE6-Bo9EgwIQ.js} +1 -1
  46. package/web/assets/classDiagram-CYGNFDIV-Bsii9q3h.js +1 -0
  47. package/web/assets/classDiagram-v2-TLXNO2FR-Bsii9q3h.js +1 -0
  48. package/web/assets/{cose-bilkent-JH36ORCC-1inJ0kKP.js → cose-bilkent-JH36ORCC-H7EUgT4k.js} +1 -1
  49. package/web/assets/{cynefin-OW5HDTMX-BAEIfHgW.js → cynefin-OW5HDTMX-r1tJgDH9.js} +1 -1
  50. package/web/assets/{cynefinDiagram-5FMLGOSQ-Dw_qVKID.js → cynefinDiagram-5FMLGOSQ-Cq4Pjmmj.js} +1 -1
  51. package/web/assets/{dagre-OS7QT2EB-BXAAz87u.js → dagre-OS7QT2EB-Bh2GFhAP.js} +1 -1
  52. package/web/assets/{diagram-S7CK7UJ4-BQt6gteN.js → diagram-S7CK7UJ4-BZNr_IG6.js} +1 -1
  53. package/web/assets/{diagram-UQ7AKVKN-Dh7vrHwF.js → diagram-UQ7AKVKN-BgaDOS70.js} +1 -1
  54. package/web/assets/{diagram-VSXAHHWV-D2kNxmUp.js → diagram-VSXAHHWV-DjUt-msp.js} +1 -1
  55. package/web/assets/{diagram-VX7I27RA-CctQDix_.js → diagram-VX7I27RA-BI3GsO3i.js} +1 -1
  56. package/web/assets/{diagram-Z3DM3KII-CDGlIVG5.js → diagram-Z3DM3KII-BLqI9pS_.js} +1 -1
  57. package/web/assets/{ebnfDiagram-PWID7BFC-0bgAb7z3.js → ebnfDiagram-PWID7BFC-D_CdQ2MS.js} +1 -1
  58. package/web/assets/{erDiagram-2YWLMYGG-Dbur-iWa.js → erDiagram-2YWLMYGG-C1SWDIhQ.js} +1 -1
  59. package/web/assets/{flowDiagram-T62WH6J4-Cw6AeymU.js → flowDiagram-T62WH6J4-BfXQV4xP.js} +1 -1
  60. package/web/assets/{ganttDiagram-EL5Y4UJY-BXYwFWOW.js → ganttDiagram-EL5Y4UJY-Bk-epoGY.js} +1 -1
  61. package/web/assets/{gitGraphDiagram-WWUBYQGX-De6ZCLNz.js → gitGraphDiagram-WWUBYQGX-O1ARqYlP.js} +1 -1
  62. package/web/assets/{infoDiagram-FKFFQAWI-B11KM4en.js → infoDiagram-FKFFQAWI-WZNXnPS1.js} +1 -1
  63. package/web/assets/{ishikawaDiagram-5VMMS53U-kGPWhBux.js → ishikawaDiagram-5VMMS53U-B8j8HgqU.js} +1 -1
  64. package/web/assets/{journeyDiagram-EYS64GPL-K8Wtegvp.js → journeyDiagram-EYS64GPL-dd2d-my9.js} +1 -1
  65. package/web/assets/{kanban-definition-UXKFOSKX-CjtX_cLJ.js → kanban-definition-UXKFOSKX-B6HfzCjr.js} +1 -1
  66. package/web/assets/{layout-Cn0uoRC-.js → layout-CjAP8jGd.js} +1 -1
  67. package/web/assets/{linear-WTNza7Ko.js → linear-DmcE_jj7.js} +1 -1
  68. package/web/assets/{mermaid.core-CbBVVRTZ.js → mermaid.core-DByIm3Os.js} +6 -6
  69. package/web/assets/{mindmap-definition-THT77NOG-DjyO1aA-.js → mindmap-definition-THT77NOG-CJdJrkzY.js} +1 -1
  70. package/web/assets/{pegDiagram-XKGWAZYB-ChPXU87n.js → pegDiagram-XKGWAZYB-DMKRyZmT.js} +1 -1
  71. package/web/assets/{pieDiagram-E7YTZNPT-bPQtXz_p.js → pieDiagram-E7YTZNPT-C0b_9HGX.js} +1 -1
  72. package/web/assets/{quadrantDiagram-AXDQQJYC-DokQ5FGV.js → quadrantDiagram-AXDQQJYC-BqkbJG9P.js} +1 -1
  73. package/web/assets/{railroadDiagram-O6MQD6OU-CcdqGxuL.js → railroadDiagram-O6MQD6OU-DXXz-as-.js} +1 -1
  74. package/web/assets/{requirementDiagram-IS5BZ75X-CGMF9hMZ.js → requirementDiagram-IS5BZ75X-DXkMFIBa.js} +1 -1
  75. package/web/assets/{sankeyDiagram-P5KCCOFB-fc-d15bH.js → sankeyDiagram-P5KCCOFB-CzXdocFr.js} +1 -1
  76. package/web/assets/{sequenceDiagram-WJ2MYXX4-DAmsOR8u.js → sequenceDiagram-WJ2MYXX4-aJBXn8VU.js} +1 -1
  77. package/web/assets/{sizeCapture-INFHLROL-6o7CY1B-.js → sizeCapture-INFHLROL-jgtrjDHs.js} +1 -1
  78. package/web/assets/{stateDiagram-XQSTLZYL-En5t4mkI.js → stateDiagram-XQSTLZYL-DrWWKXiY.js} +1 -1
  79. package/web/assets/stateDiagram-v2-IH3M54BS-D2cYASvR.js +1 -0
  80. package/web/assets/{swimlanes-V6O3JKXN-COBv2NoI.js → swimlanes-V6O3JKXN-D8Ewz1mj.js} +1 -1
  81. package/web/assets/swimlanesDiagram-JKAHXJPX-B9WLoHr4.js +8 -0
  82. package/web/assets/{timeline-definition-24CTP7MA-Qb-aTtSE.js → timeline-definition-24CTP7MA-B25naqcG.js} +1 -1
  83. package/web/assets/{vennDiagram-4TSXK5OY-Br3-0Ze-.js → vennDiagram-4TSXK5OY-3lHwPwrc.js} +1 -1
  84. package/web/assets/{wardleyDiagram-VM6X3IG4-BCeY3AKL.js → wardleyDiagram-VM6X3IG4-RGq5f_bX.js} +1 -1
  85. package/web/assets/{workspace-file-tree-CYzKM8q4.js → workspace-file-tree-Dt4_4AuL.js} +1 -1
  86. package/web/assets/{xychartDiagram-S5SC5T6Z-Bz96OPKc.js → xychartDiagram-S5SC5T6Z-DysrZCDk.js} +1 -1
  87. package/web/ext-microsoft-365.svg +1 -0
  88. package/web/index.html +2 -2
  89. package/dist/opencode-plugins/workspace-file-identity.js +0 -162
  90. package/web/assets/app-B682snBU.css +0 -32
  91. package/web/assets/channel-BZvy6aHa.js +0 -1
  92. package/web/assets/classDiagram-CYGNFDIV-DBAMzIwS.js +0 -1
  93. package/web/assets/classDiagram-v2-TLXNO2FR-DBAMzIwS.js +0 -1
  94. package/web/assets/katex-HP8lGamR.js +0 -257
  95. package/web/assets/stateDiagram-v2-IH3M54BS-DvIXHm_9.js +0 -1
  96. 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/opencode-plugins/workspace-file-identity.ts
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
- return JSON.parse(text + decoder.decode());
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.delete(HEADER);
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
- report(attempt, failure(response.status), response.status);
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, await errorBody(response));
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
- report(attempt, retry.ok ? "accepted_after_recovery" : failure(retry.status), retry.status);
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, options = {}) {
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.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(options.timeoutMs ?? BRIDGE_TIMEOUT_MS)]) : AbortSignal.timeout(options.timeoutMs ?? BRIDGE_TIMEOUT_MS),
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
+ }