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
@@ -0,0 +1,3880 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true,
11
+ configurable: true,
12
+ set: __exportSetter.bind(all, name)
13
+ });
14
+ };
15
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
+
17
+ // ../../packages/opencode-plugin/src/den.ts
18
+ function isRecord(value) {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+ function normalizeBaseUrl(value) {
22
+ let end = value.length;
23
+ while (end > 0 && /\s/.test(value[end - 1] ?? ""))
24
+ end--;
25
+ while (end > 0 && value[end - 1] === "/")
26
+ end--;
27
+ return value.slice(0, end).trim();
28
+ }
29
+ function isAllowedApiBaseUrl(value) {
30
+ let url;
31
+ try {
32
+ url = new URL(value);
33
+ } catch {
34
+ return false;
35
+ }
36
+ if (url.username || url.password)
37
+ return false;
38
+ return url.protocol === "https:" || url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
39
+ }
40
+ function assertAllowedApiBaseUrl(value) {
41
+ if (!isAllowedApiBaseUrl(value))
42
+ throw new DenUrlError(value);
43
+ }
44
+ async function readJson(response) {
45
+ const text = await response.text();
46
+ if (!text.trim())
47
+ return null;
48
+ try {
49
+ return JSON.parse(text);
50
+ } catch {
51
+ return text;
52
+ }
53
+ }
54
+ async function postPublic(fetcher, apiBaseUrl, path, body) {
55
+ assertAllowedApiBaseUrl(apiBaseUrl);
56
+ const response = await fetcher(`${normalizeBaseUrl(apiBaseUrl)}${path}`, {
57
+ method: "POST",
58
+ headers: { accept: "application/json", "content-type": "application/json" },
59
+ body: JSON.stringify(body),
60
+ redirect: "error",
61
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
62
+ });
63
+ return { status: response.status, body: await readJson(response), retryAfter: response.headers.get("retry-after") };
64
+ }
65
+ async function denRequest(fetcher, session, path, init = {}) {
66
+ assertAllowedApiBaseUrl(session.apiBaseUrl);
67
+ const headers = {
68
+ accept: "application/json",
69
+ authorization: `Bearer ${session.token}`
70
+ };
71
+ if (session.orgId) {
72
+ headers["x-openwork-org-id"] = session.orgId;
73
+ headers["x-openwork-legacy-org-id"] = session.orgId;
74
+ }
75
+ if (init.body !== undefined)
76
+ headers["content-type"] = "application/json";
77
+ const response = await fetcher(`${normalizeBaseUrl(session.apiBaseUrl)}${path}`, {
78
+ method: init.method ?? "GET",
79
+ headers,
80
+ body: init.body === undefined ? undefined : JSON.stringify(init.body),
81
+ redirect: "error",
82
+ signal: init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]) : AbortSignal.timeout(REQUEST_TIMEOUT_MS)
83
+ });
84
+ const body = await readJson(response);
85
+ if (response.status === 401)
86
+ throw new DenAuthError(response.status, path);
87
+ if (!response.ok)
88
+ throw new DenRequestError(response.status, path, body);
89
+ return body;
90
+ }
91
+ function readString(value) {
92
+ return typeof value === "string" && value.trim() ? value.trim() : null;
93
+ }
94
+ function readTime(value) {
95
+ const text = readString(value);
96
+ if (!text)
97
+ return null;
98
+ const time = Date.parse(text);
99
+ return Number.isFinite(time) ? time : null;
100
+ }
101
+ async function readMe(fetcher, session) {
102
+ const body = await denRequest(fetcher, session, "/v1/me");
103
+ if (!isRecord(body) || !isRecord(body.user))
104
+ throw new DenRequestError(200, "/v1/me", body);
105
+ const session_ = isRecord(body.session) ? body.session : {};
106
+ return {
107
+ userId: readString(body.user.id) ?? "",
108
+ email: readString(body.user.email) ?? "",
109
+ sessionExpiresAt: readTime(session_.expiresAt),
110
+ activeOrganizationId: readString(session_.activeOrganizationId)
111
+ };
112
+ }
113
+ async function readAccount(fetcher, session) {
114
+ const me = await readMe(fetcher, session);
115
+ const orgId = session.orgId ?? me.activeOrganizationId;
116
+ const orgs = await denRequest(fetcher, { ...session, orgId }, "/v1/me/orgs");
117
+ let org = null;
118
+ if (isRecord(orgs) && Array.isArray(orgs.orgs)) {
119
+ const list = orgs.orgs.filter(isRecord);
120
+ const activeId = readString(orgs.activeOrgId) ?? orgId;
121
+ org = list.find((entry) => entry.id === activeId) ?? list.find((entry) => entry.isActive === true) ?? null;
122
+ }
123
+ return {
124
+ userId: me.userId,
125
+ email: me.email,
126
+ sessionExpiresAt: me.sessionExpiresAt,
127
+ orgId: (org && readString(org.id)) ?? orgId,
128
+ orgName: org ? readString(org.name) ?? readString(org.slug) : null,
129
+ orgSlug: org ? readString(org.slug) : null
130
+ };
131
+ }
132
+ async function signOut(fetcher, apiBaseUrl, token) {
133
+ if (!isAllowedApiBaseUrl(apiBaseUrl))
134
+ return false;
135
+ try {
136
+ const response = await fetcher(`${normalizeBaseUrl(apiBaseUrl)}/api/auth/sign-out`, {
137
+ method: "POST",
138
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
139
+ body: "{}",
140
+ redirect: "error",
141
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
142
+ });
143
+ return response.ok;
144
+ } catch {
145
+ return false;
146
+ }
147
+ }
148
+ var DenAuthError, DenRequestError, REQUEST_TIMEOUT_MS = 1e4, LOOPBACK_HOSTS, DenUrlError;
149
+ var init_den = __esm(() => {
150
+ DenAuthError = class DenAuthError extends Error {
151
+ status;
152
+ constructor(status, path) {
153
+ super(`OpenWork rejected the sign-in (${status} on ${path}). Sign in again: opencode auth login openwork`);
154
+ this.name = "DenAuthError";
155
+ this.status = status;
156
+ }
157
+ };
158
+ DenRequestError = class DenRequestError extends Error {
159
+ status;
160
+ body;
161
+ constructor(status, path, body) {
162
+ super(`OpenWork request failed (${status} on ${path})`);
163
+ this.name = "DenRequestError";
164
+ this.status = status;
165
+ this.body = body;
166
+ }
167
+ };
168
+ LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
169
+ DenUrlError = class DenUrlError extends Error {
170
+ constructor(value) {
171
+ super(`Refusing to send the OpenWork sign-in to ${value}: use an https:// address.`);
172
+ this.name = "DenUrlError";
173
+ }
174
+ };
175
+ });
176
+
177
+ // ../../packages/opencode-plugin/src/device.ts
178
+ function formatUserCode(value) {
179
+ const clean = value.replace(/[\s-]/g, "").toUpperCase();
180
+ return clean.length === 8 ? `${clean.slice(0, 4)}-${clean.slice(4)}` : clean;
181
+ }
182
+ function errorCode(body) {
183
+ return isRecord(body) && typeof body.error === "string" ? body.error : null;
184
+ }
185
+ async function startDeviceAuthorization(input) {
186
+ const now = input.now ?? Date.now;
187
+ const clientIds = input.clientIds ?? [DEVICE_CLIENT_ID, FALLBACK_DEVICE_CLIENT_ID];
188
+ let last = null;
189
+ for (const clientId of clientIds) {
190
+ const started = await postPublic(input.fetcher, input.apiBaseUrl, "/api/auth/device/code", { client_id: clientId });
191
+ last = started;
192
+ if (started.status === 400 && errorCode(started.body) === "invalid_client")
193
+ continue;
194
+ const body = started.body;
195
+ if (started.status !== 200 || !isRecord(body) || typeof body.device_code !== "string" || typeof body.user_code !== "string") {
196
+ break;
197
+ }
198
+ const verificationUri = typeof body.verification_uri === "string" ? body.verification_uri : `${normalizeBaseUrl(input.apiBaseUrl)}/device`;
199
+ const verificationUriComplete = typeof body.verification_uri_complete === "string" ? body.verification_uri_complete : `${verificationUri}?user_code=${encodeURIComponent(body.user_code)}`;
200
+ const interval = Number(body.interval);
201
+ const expiresIn = Number(body.expires_in);
202
+ return {
203
+ clientId,
204
+ deviceCode: body.device_code,
205
+ userCode: formatUserCode(body.user_code),
206
+ verificationUri,
207
+ verificationUriComplete,
208
+ intervalMs: Math.max(1, Number.isFinite(interval) ? interval : 5) * 1000,
209
+ expiresAt: now() + (Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 900) * 1000
210
+ };
211
+ }
212
+ throw new DeviceFlowError(`Could not start OpenWork sign-in (${last?.status ?? "no response"}${errorCode(last?.body) ? `: ${errorCode(last?.body)}` : ""}).`);
213
+ }
214
+ function timerWaker() {
215
+ let wake = null;
216
+ return {
217
+ wait(ms) {
218
+ return new Promise((resolve) => {
219
+ const timer = setTimeout(() => {
220
+ wake = null;
221
+ resolve();
222
+ }, ms);
223
+ wake = () => {
224
+ clearTimeout(timer);
225
+ wake = null;
226
+ resolve();
227
+ };
228
+ });
229
+ },
230
+ wake() {
231
+ wake?.();
232
+ }
233
+ };
234
+ }
235
+ function retryAfterMs(value, now) {
236
+ if (!value)
237
+ return 0;
238
+ const trimmed = value.trim();
239
+ if (/^\d+$/.test(trimmed)) {
240
+ const milliseconds = Number(trimmed) * 1000;
241
+ return Number.isFinite(milliseconds) ? milliseconds : 0;
242
+ }
243
+ if (!/^(?:[A-Za-z]{3}, |[A-Za-z]+, \d{2}-|[A-Za-z]{3} [A-Za-z]{3} )/.test(trimmed))
244
+ return 0;
245
+ const date = Date.parse(/^[A-Za-z]{3} [A-Za-z]{3} /.test(trimmed) ? `${trimmed} GMT` : trimmed);
246
+ return Number.isFinite(date) ? Math.max(0, date - now) : 0;
247
+ }
248
+ async function pollDeviceToken(input) {
249
+ const now = input.now ?? Date.now;
250
+ const waker = input.waker ?? timerWaker();
251
+ let intervalMs = input.authorization.intervalMs;
252
+ let cooldownUntil = 0;
253
+ while (now() < input.authorization.expiresAt) {
254
+ await waker.wait(Math.min(Math.max(intervalMs, cooldownUntil - now()), Math.max(0, input.authorization.expiresAt - now())));
255
+ if (input.isCancelled?.())
256
+ throw new DeviceFlowError("Sign-in was cancelled.");
257
+ while (now() < cooldownUntil && now() < input.authorization.expiresAt) {
258
+ await waker.wait(Math.min(cooldownUntil, input.authorization.expiresAt) - now());
259
+ if (input.isCancelled?.())
260
+ throw new DeviceFlowError("Sign-in was cancelled.");
261
+ }
262
+ if (now() >= input.authorization.expiresAt)
263
+ break;
264
+ const polled = await postPublic(input.fetcher, input.apiBaseUrl, "/api/auth/device/token", {
265
+ grant_type: DEVICE_CODE_GRANT,
266
+ device_code: input.authorization.deviceCode,
267
+ client_id: input.authorization.clientId
268
+ });
269
+ if (polled.status === 200 && isRecord(polled.body) && typeof polled.body.access_token === "string") {
270
+ return polled.body.access_token;
271
+ }
272
+ const error = errorCode(polled.body);
273
+ if (error === "access_denied")
274
+ throw new DeviceFlowError("Sign-in was denied in the browser.");
275
+ if (error === "expired_token")
276
+ break;
277
+ if (polled.status === 429 || error === "slow_down") {
278
+ intervalMs += 5000;
279
+ if (polled.status === 429)
280
+ cooldownUntil = now() + Math.max(intervalMs, retryAfterMs(polled.retryAfter, now()));
281
+ continue;
282
+ }
283
+ if (error === "authorization_pending")
284
+ continue;
285
+ throw new DeviceFlowError(`OpenWork sign-in failed (${polled.status}${error ? `: ${error}` : ""}).`);
286
+ }
287
+ throw new DeviceFlowError("The sign-in code expired before it was approved. Run the sign-in again.");
288
+ }
289
+ var DEVICE_CLIENT_ID = "openwork-opencode-plugin", FALLBACK_DEVICE_CLIENT_ID = "openwork-cli", DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code", DeviceFlowError;
290
+ var init_device = __esm(() => {
291
+ init_den();
292
+ DeviceFlowError = class DeviceFlowError extends Error {
293
+ constructor(message) {
294
+ super(message);
295
+ this.name = "DeviceFlowError";
296
+ }
297
+ };
298
+ });
299
+
300
+ // ../../packages/opencode-plugin/src/loopback.ts
301
+ import { createServer } from "node:http";
302
+ function escapeHtml(value) {
303
+ return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
304
+ }
305
+ function returnPageHtml(result) {
306
+ const title = result === "approved" ? "OpenCode is connected to OpenWork" : result === "denied" ? "Sign-in cancelled" : "Finish signing in";
307
+ const line = result === "approved" ? "You can close this tab and go back to OpenCode." : result === "denied" ? "OpenCode did not get access. You can close this tab." : "Approve the sign-in on the OpenWork page, then come back to OpenCode.";
308
+ return `<!doctype html>
309
+ <html lang="en">
310
+ <head>
311
+ <meta charset="utf-8">
312
+ <meta name="viewport" content="width=device-width, initial-scale=1">
313
+ <title>${escapeHtml(title)}</title>
314
+ <style>
315
+ :root { color-scheme: light dark; }
316
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; }
317
+ main { max-width: 420px; padding: 32px; text-align: center; }
318
+ h1 { font-size: 20px; line-height: 28px; font-weight: 600; margin: 0 0 8px; }
319
+ p { margin: 0; opacity: 0.72; }
320
+ </style>
321
+ </head>
322
+ <body>
323
+ <main>
324
+ <h1>${escapeHtml(title)}</h1>
325
+ <p>${escapeHtml(line)}</p>
326
+ </main>
327
+ </body>
328
+ </html>`;
329
+ }
330
+ async function startReturnPage(onResult) {
331
+ const server = createServer((request, response) => {
332
+ const url = new URL(request.url ?? "/", "http://127.0.0.1");
333
+ if (request.method !== "GET" || url.pathname !== CALLBACK_PATH) {
334
+ response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }).end("Not found");
335
+ return;
336
+ }
337
+ const raw = url.searchParams.get("result");
338
+ const result = raw === "approved" || raw === "denied" ? raw : null;
339
+ response.writeHead(200, {
340
+ "content-type": "text/html; charset=utf-8",
341
+ "cache-control": "no-store",
342
+ "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
343
+ "referrer-policy": "no-referrer"
344
+ }).end(returnPageHtml(result));
345
+ if (result)
346
+ onResult(result);
347
+ });
348
+ await new Promise((resolve, reject) => {
349
+ server.once("error", reject);
350
+ server.listen(0, "127.0.0.1", () => resolve());
351
+ });
352
+ server.unref();
353
+ const address = server.address();
354
+ const port = typeof address === "object" && address ? address.port : 0;
355
+ let closed = false;
356
+ return {
357
+ url: `http://127.0.0.1:${port}${CALLBACK_PATH}`,
358
+ close() {
359
+ if (closed)
360
+ return;
361
+ closed = true;
362
+ server.close();
363
+ server.closeAllConnections?.();
364
+ }
365
+ };
366
+ }
367
+ var CALLBACK_PATH = "/openwork/callback";
368
+ var init_loopback = () => {};
369
+
370
+ // ../../packages/opencode-plugin/src/auth.ts
371
+ function readCredentialMetadata(credential, fallbackApiBaseUrl) {
372
+ const metadata = credential.metadata ?? {};
373
+ const text = (key) => typeof metadata[key] === "string" && metadata[key] ? String(metadata[key]) : null;
374
+ return {
375
+ apiBaseUrl: text("apiBaseUrl") ?? fallbackApiBaseUrl,
376
+ orgId: text("orgId"),
377
+ orgName: text("orgName"),
378
+ orgSlug: text("orgSlug"),
379
+ email: text("email") ?? "",
380
+ deviceClientId: text("deviceClientId") ?? ""
381
+ };
382
+ }
383
+ function credentialLabel(credential) {
384
+ const metadata = credential.metadata ?? {};
385
+ const email = typeof metadata.email === "string" ? metadata.email : "";
386
+ const org = typeof metadata.orgName === "string" ? metadata.orgName : "";
387
+ const label = [email, org].filter(Boolean).join(" · ");
388
+ return label || undefined;
389
+ }
390
+ async function finish(input) {
391
+ const token = await pollDeviceToken({
392
+ fetcher: input.fetcher,
393
+ apiBaseUrl: input.apiBaseUrl,
394
+ authorization: input.authorization,
395
+ waker: { wait: input.wait },
396
+ now: input.now
397
+ });
398
+ const account = await readAccount(input.fetcher, { apiBaseUrl: input.apiBaseUrl, token, orgId: null });
399
+ const metadata = {
400
+ apiBaseUrl: input.apiBaseUrl,
401
+ orgId: account.orgId,
402
+ orgName: account.orgName,
403
+ orgSlug: account.orgSlug,
404
+ email: account.email,
405
+ deviceClientId: input.authorization.clientId
406
+ };
407
+ return {
408
+ type: "oauth",
409
+ methodID: input.methodID,
410
+ access: token,
411
+ refresh: token,
412
+ expires: account.sessionExpiresAt ?? input.now() + FALLBACK_SESSION_MS,
413
+ metadata: { ...metadata }
414
+ };
415
+ }
416
+ function withReturnTo(verificationUriComplete, returnTo) {
417
+ const url = new URL(verificationUriComplete);
418
+ url.searchParams.set("return_to", returnTo);
419
+ return url.toString();
420
+ }
421
+ function signInMethods(input) {
422
+ const now = input.now ?? Date.now;
423
+ const refresh = async (credential) => {
424
+ const metadata = readCredentialMetadata(credential, input.apiBaseUrl());
425
+ try {
426
+ const me = await readMe(input.fetcher, { apiBaseUrl: metadata.apiBaseUrl, token: credential.access, orgId: metadata.orgId });
427
+ return { ...credential, expires: me.sessionExpiresAt ?? now() + FALLBACK_SESSION_MS };
428
+ } catch (error) {
429
+ if (error instanceof DenAuthError)
430
+ throw error;
431
+ return { ...credential, expires: now() + RETRY_REFRESH_MS };
432
+ }
433
+ };
434
+ const code = {
435
+ integrationID: INTEGRATION_ID,
436
+ method: { id: CODE_METHOD_ID, type: "oauth", label: "Code (SSH or headless)" },
437
+ async authorize() {
438
+ const apiBaseUrl = input.apiBaseUrl();
439
+ const authorization = await startDeviceAuthorization({ fetcher: input.fetcher, apiBaseUrl, now });
440
+ const waker = timerWaker();
441
+ return {
442
+ mode: "auto",
443
+ url: authorization.verificationUriComplete,
444
+ instructions: `Open the link and confirm the code ${authorization.userCode}`,
445
+ expiresAt: authorization.expiresAt,
446
+ callback: finish({ fetcher: input.fetcher, apiBaseUrl, methodID: CODE_METHOD_ID, authorization, wait: waker.wait, now })
447
+ };
448
+ },
449
+ refresh,
450
+ label: credentialLabel
451
+ };
452
+ const browser = {
453
+ integrationID: INTEGRATION_ID,
454
+ method: { id: BROWSER_METHOD_ID, type: "oauth", label: "Browser" },
455
+ async authorize() {
456
+ const apiBaseUrl = input.apiBaseUrl();
457
+ const authorization = await startDeviceAuthorization({ fetcher: input.fetcher, apiBaseUrl, now });
458
+ const waker = timerWaker();
459
+ const page = await startReturnPage(() => waker.wake());
460
+ const callback = finish({ fetcher: input.fetcher, apiBaseUrl, methodID: BROWSER_METHOD_ID, authorization, wait: waker.wait, now });
461
+ const closeLater = () => setTimeout(() => page.close(), 30000).unref();
462
+ callback.then(closeLater, closeLater);
463
+ return {
464
+ mode: "auto",
465
+ url: withReturnTo(authorization.verificationUriComplete, page.url),
466
+ instructions: `Sign in to OpenWork in your browser (code ${authorization.userCode})`,
467
+ expiresAt: authorization.expiresAt,
468
+ callback
469
+ };
470
+ },
471
+ refresh,
472
+ label: credentialLabel
473
+ };
474
+ return [browser, code];
475
+ }
476
+ var INTEGRATION_ID = "openwork", INTEGRATION_NAME = "OpenWork Cloud", BROWSER_METHOD_ID = "browser", CODE_METHOD_ID = "code", FALLBACK_SESSION_MS, RETRY_REFRESH_MS;
477
+ var init_auth = __esm(() => {
478
+ init_den();
479
+ init_device();
480
+ init_loopback();
481
+ FALLBACK_SESSION_MS = 6 * 24 * 60 * 60 * 1000;
482
+ RETRY_REFRESH_MS = 15 * 60 * 1000;
483
+ });
484
+
485
+ // ../../packages/remote-sessions/src/types.ts
486
+ function emptyJournal() {
487
+ return { version: 1, commands: [], requests: [] };
488
+ }
489
+ var MAX_COMMAND_ENTRIES = 256, MAX_REQUEST_ENTRIES = 256, REQUEST_RESULT_MAX_BYTES, DEFAULT_OPERATION_TIMEOUT_MS = 15000, SessionRunnerError;
490
+ var init_types = __esm(() => {
491
+ REQUEST_RESULT_MAX_BYTES = 256 * 1024;
492
+ SessionRunnerError = class SessionRunnerError extends Error {
493
+ code;
494
+ constructor(code, message) {
495
+ super(message);
496
+ this.name = "SessionRunnerError";
497
+ this.code = code;
498
+ }
499
+ };
500
+ });
501
+
502
+ // ../../packages/remote-sessions/src/protocol.ts
503
+ function invalid() {
504
+ throw new SessionRunnerError("invalid_data", "Invalid remote-session protocol or journal data.");
505
+ }
506
+ function isObject(value) {
507
+ return typeof value === "object" && value !== null && !Array.isArray(value);
508
+ }
509
+ function object(value) {
510
+ if (!isObject(value))
511
+ return invalid();
512
+ return value;
513
+ }
514
+ function keys(value, names) {
515
+ if (Object.keys(value).some((key) => !names.includes(key)))
516
+ invalid();
517
+ }
518
+ function rawText(value, max, min = 1) {
519
+ if (typeof value !== "string" || value.length < min || value.length > max)
520
+ return invalid();
521
+ return value;
522
+ }
523
+ function text(value, max, min = 1) {
524
+ if (typeof value !== "string")
525
+ return invalid();
526
+ return rawText(min > 0 ? value.trim() : value, max, min);
527
+ }
528
+ function integer(value, min = 0, max = Number.MAX_SAFE_INTEGER) {
529
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max)
530
+ return invalid();
531
+ return value;
532
+ }
533
+ function boolean(value) {
534
+ if (typeof value !== "boolean")
535
+ return invalid();
536
+ return value;
537
+ }
538
+ function nullable(value, parse) {
539
+ return value === null ? null : parse(value);
540
+ }
541
+ function array(value, max, parse) {
542
+ if (!Array.isArray(value) || value.length > max)
543
+ return invalid();
544
+ return value.map(parse);
545
+ }
546
+ function engine(value) {
547
+ if (value !== "v1" && value !== "v2")
548
+ return invalid();
549
+ return value;
550
+ }
551
+ function status(value) {
552
+ if (value !== "running" && value !== "waiting" && value !== "idle" && value !== "error")
553
+ return invalid();
554
+ return value;
555
+ }
556
+ function waiting(value) {
557
+ if (value !== null && value !== "permission" && value !== "question")
558
+ return invalid();
559
+ return value;
560
+ }
561
+ function direction(value) {
562
+ if (value !== "start" && value !== "end")
563
+ return invalid();
564
+ return value;
565
+ }
566
+ function messageId(value) {
567
+ const id = rawText(value, 160);
568
+ if (!/^msg_[a-zA-Z0-9]+$/.test(id))
569
+ return invalid();
570
+ return id;
571
+ }
572
+ function stableMessageId(id) {
573
+ return messageId(`msg_${id.replace(/[^a-zA-Z0-9]/g, "")}`);
574
+ }
575
+ function sessionIdForCommand(commandId) {
576
+ const id = `ses_${text(commandId, 160).replace(/[^a-zA-Z0-9]/g, "")}`;
577
+ if (!/^ses_[a-zA-Z0-9]+$/.test(id))
578
+ return invalid();
579
+ return id;
580
+ }
581
+ function parseError(value) {
582
+ const v = object(value);
583
+ keys(v, ["code", "message"]);
584
+ return { code: text(v.code, 60), message: text(v.message, 2000) };
585
+ }
586
+ function model(value) {
587
+ const v = object(value);
588
+ keys(v, ["providerId", "modelId", "variant"]);
589
+ return {
590
+ providerId: text(v.providerId, 160),
591
+ modelId: text(v.modelId, 160),
592
+ ...v.variant === undefined ? {} : { variant: nullable(v.variant, (value2) => text(value2, 60)) }
593
+ };
594
+ }
595
+ function parseCommand(value) {
596
+ const v = object(value);
597
+ keys(v, ["commandId", "kind", "title", "prompt", "model", "expiresAt", "workspaceId"]);
598
+ if (v.kind !== "remote_session_create")
599
+ return invalid();
600
+ const m = nullable(v.model, model);
601
+ if (m !== null && m.variant === undefined)
602
+ return invalid();
603
+ return {
604
+ commandId: text(v.commandId, 160),
605
+ kind: "remote_session_create",
606
+ title: text(v.title, 120),
607
+ prompt: nullable(v.prompt, (value2) => rawText(value2, 1e5)),
608
+ model: m === null ? null : { providerId: m.providerId, modelId: m.modelId, variant: m.variant ?? null },
609
+ expiresAt: integer(v.expiresAt),
610
+ ...v.workspaceId === undefined ? {} : { workspaceId: nullable(v.workspaceId, (value2) => text(value2, 240)) }
611
+ };
612
+ }
613
+ function parseReceipt(value) {
614
+ const v = object(value);
615
+ keys(v, ["sessionId", "workspaceId"]);
616
+ return { sessionId: text(v.sessionId, 240), workspaceId: text(v.workspaceId, 240) };
617
+ }
618
+ function parseProgress(value) {
619
+ const v = object(value);
620
+ keys(v, ["status", "waitingFor", "engine", "model", "finalText", "error", "messageCount", "observedAt"]);
621
+ return {
622
+ status: status(v.status),
623
+ observedAt: integer(v.observedAt),
624
+ ...v.waitingFor === undefined ? {} : { waitingFor: waiting(v.waitingFor) },
625
+ ...v.engine === undefined ? {} : { engine: engine(v.engine) },
626
+ ...v.model === undefined ? {} : { model: nullable(v.model, model) },
627
+ ...v.finalText === undefined ? {} : { finalText: text(v.finalText, 20000, 0) },
628
+ ...v.error === undefined ? {} : { error: nullable(v.error, parseError) },
629
+ ...v.messageCount === undefined ? {} : { messageCount: integer(v.messageCount) }
630
+ };
631
+ }
632
+ function parseComplete(value) {
633
+ const v = object(value);
634
+ if (v.status === "delivered") {
635
+ keys(v, ["status", "sessionId", "workspaceId", "resultSummary"]);
636
+ return {
637
+ status: "delivered",
638
+ ...parseReceipt({ sessionId: v.sessionId, workspaceId: v.workspaceId }),
639
+ ...v.resultSummary === undefined ? {} : { resultSummary: text(v.resultSummary, 4096, 0) }
640
+ };
641
+ }
642
+ if (v.status !== "failed")
643
+ return invalid();
644
+ keys(v, ["status", "error", "resultSummary"]);
645
+ return {
646
+ status: "failed",
647
+ error: parseError(v.error),
648
+ ...v.resultSummary === undefined ? {} : { resultSummary: text(v.resultSummary, 4096, 0) }
649
+ };
650
+ }
651
+ function parseRequest(value) {
652
+ const v = object(value);
653
+ keys(v, ["requestId", "kind", "commandId", "sessionId", "workspaceId", "engine", "expiresAt", "action", "input"]);
654
+ if (v.kind !== "remote_session_request")
655
+ return invalid();
656
+ const base = {
657
+ requestId: text(v.requestId, 160),
658
+ kind: "remote_session_request",
659
+ commandId: text(v.commandId, 160),
660
+ sessionId: text(v.sessionId, 240),
661
+ workspaceId: text(v.workspaceId, 240),
662
+ engine: nullable(v.engine, engine),
663
+ expiresAt: integer(v.expiresAt)
664
+ };
665
+ const input = object(v.input);
666
+ if (v.action === "read") {
667
+ keys(input, ["from", "cursor", "limit"]);
668
+ return { ...base, action: "read", input: {
669
+ from: direction(input.from),
670
+ cursor: nullable(input.cursor, (value2) => text(value2, 160)),
671
+ limit: integer(input.limit, 1, 100)
672
+ } };
673
+ }
674
+ if (v.action === "send") {
675
+ keys(input, ["prompt", "messageId", "model"]);
676
+ return { ...base, action: "send", input: {
677
+ prompt: rawText(input.prompt, 1e5),
678
+ messageId: nullable(input.messageId, messageId),
679
+ model: nullable(input.model, model)
680
+ } };
681
+ }
682
+ if (v.action !== "stop")
683
+ return invalid();
684
+ keys(input, ["messageId"]);
685
+ return { ...base, action: "stop", input: { messageId: nullable(input.messageId, (value2) => text(value2, 160)) } };
686
+ }
687
+ function toolCall(value) {
688
+ const v = object(value);
689
+ keys(v, ["id", "name", "status", "input", "output", "error", "truncated"]);
690
+ return {
691
+ id: text(v.id, 240, 0),
692
+ name: text(v.name, 240, 0),
693
+ status: nullable(v.status, (value2) => text(value2, 60, 0)),
694
+ input: nullable(v.input, (value2) => text(value2, 2000, 0)),
695
+ output: nullable(v.output, (value2) => text(value2, 2000, 0)),
696
+ error: nullable(v.error, (value2) => text(value2, 2000, 0)),
697
+ truncated: boolean(v.truncated)
698
+ };
699
+ }
700
+ function message(value) {
701
+ const v = object(value);
702
+ keys(v, ["id", "role", "createdAt", "text", "truncated", "toolCalls", "error"]);
703
+ if (v.role !== "user" && v.role !== "assistant")
704
+ return invalid();
705
+ return {
706
+ id: text(v.id, 240, 0),
707
+ role: v.role,
708
+ createdAt: nullable(v.createdAt, integer),
709
+ text: text(v.text, 20000, 0),
710
+ truncated: boolean(v.truncated),
711
+ toolCalls: array(v.toolCalls, 200, toolCall),
712
+ error: nullable(v.error, parseError)
713
+ };
714
+ }
715
+ function parseReadResult(value) {
716
+ const v = object(value);
717
+ keys(v, ["title", "status", "waitingFor", "lastError", "messageCount", "from", "messages", "nextCursor", "historyScope"]);
718
+ if (v.historyScope !== undefined && v.historyScope !== "context" && v.historyScope !== "full")
719
+ return invalid();
720
+ return {
721
+ title: nullable(v.title, (value2) => text(value2, 240, 0)),
722
+ status: status(v.status),
723
+ waitingFor: waiting(v.waitingFor),
724
+ lastError: nullable(v.lastError, parseError),
725
+ messageCount: integer(v.messageCount),
726
+ from: direction(v.from),
727
+ messages: array(v.messages, 100, message),
728
+ nextCursor: nullable(v.nextCursor, (value2) => text(value2, 160, 0)),
729
+ ...v.historyScope === undefined ? {} : { historyScope: v.historyScope }
730
+ };
731
+ }
732
+ function parseSendResult(value) {
733
+ const v = object(value);
734
+ keys(v, ["messageId", "alreadyPresent"]);
735
+ return { messageId: nullable(v.messageId, (value2) => text(value2, 160, 0)), alreadyPresent: boolean(v.alreadyPresent) };
736
+ }
737
+ function parseStopResult(value) {
738
+ const v = object(value);
739
+ keys(v, ["stopped", "reason"]);
740
+ if (v.reason !== null && v.reason !== "different_turn")
741
+ return invalid();
742
+ return { stopped: boolean(v.stopped), reason: v.reason };
743
+ }
744
+ function parseRequestComplete(value) {
745
+ const v = object(value);
746
+ if (v.status === "failed") {
747
+ keys(v, ["status", "error"]);
748
+ return { status: "failed", error: parseError(v.error) };
749
+ }
750
+ if (v.status !== "done")
751
+ return invalid();
752
+ keys(v, ["status", "outcome"]);
753
+ const outcome = object(v.outcome);
754
+ keys(outcome, ["action", "result"]);
755
+ if (new TextEncoder().encode(JSON.stringify(outcome)).byteLength > REQUEST_RESULT_MAX_BYTES)
756
+ return invalid();
757
+ if (outcome.action === "read")
758
+ return { status: "done", outcome: { action: "read", result: parseReadResult(outcome.result) } };
759
+ if (outcome.action === "send")
760
+ return { status: "done", outcome: { action: "send", result: parseSendResult(outcome.result) } };
761
+ if (outcome.action === "stop")
762
+ return { status: "done", outcome: { action: "stop", result: parseStopResult(outcome.result) } };
763
+ return invalid();
764
+ }
765
+ function parseInventory2(value) {
766
+ const v = object(value);
767
+ keys(v, ["computer", "workspaces"]);
768
+ const c = object(v.computer);
769
+ keys(c, ["label", "platform", "appVersion"]);
770
+ if (c.platform !== "darwin" && c.platform !== "win32" && c.platform !== "linux")
771
+ return invalid();
772
+ return {
773
+ computer: { label: text(c.label, 120), platform: c.platform, appVersion: text(c.appVersion, 80) },
774
+ workspaces: array(v.workspaces, 50, (value2) => {
775
+ const w = object(value2);
776
+ keys(w, ["workspaceId", "name", "active", "engine", "defaultModel", "models"]);
777
+ const m = nullable(w.defaultModel, model);
778
+ if (m?.variant === null)
779
+ return invalid();
780
+ return {
781
+ workspaceId: text(w.workspaceId, 240),
782
+ name: text(w.name, 120),
783
+ active: boolean(w.active),
784
+ engine: engine(w.engine),
785
+ defaultModel: m === null ? null : {
786
+ providerId: m.providerId,
787
+ modelId: m.modelId,
788
+ ...m.variant === undefined ? {} : { variant: m.variant }
789
+ },
790
+ models: array(w.models, 200, (value3) => {
791
+ const m2 = object(value3);
792
+ keys(m2, ["providerId", "modelId", "name"]);
793
+ return { providerId: text(m2.providerId, 160), modelId: text(m2.modelId, 160), name: text(m2.name, 200) };
794
+ })
795
+ };
796
+ })
797
+ };
798
+ }
799
+ function parseTurn(value) {
800
+ const t = object(value);
801
+ keys(t, ["messageId", "baselineMessageCount", "seenActive"]);
802
+ return { messageId: messageId(t.messageId), baselineMessageCount: nullable(t.baselineMessageCount, integer), seenActive: boolean(t.seenActive) };
803
+ }
804
+ function parseJournal(value) {
805
+ const v = object(value);
806
+ keys(v, ["version", "commands", "requests"]);
807
+ if (v.version !== 1)
808
+ return invalid();
809
+ const journal = {
810
+ version: 1,
811
+ commands: array(v.commands, MAX_COMMAND_ENTRIES, (value2) => {
812
+ const e = object(value2);
813
+ keys(e, ["command", "creation", "sendReplay", "intendedSessionId", "lastObservedAt", "phase", "receipt", "promptMessageId", "turn", "lastMessageId", "completion", "completionAcknowledged", "progress", "reportedProgress"]);
814
+ if (e.phase !== "prepared" && e.phase !== "created" && e.phase !== "delivered" && e.phase !== "failed")
815
+ return invalid();
816
+ const command = parseCommand(e.command);
817
+ const legacyCreation = e.creation === undefined && e.intendedSessionId === undefined;
818
+ if (!legacyCreation && e.creation !== "idempotent" && e.creation !== "at_most_once")
819
+ return invalid();
820
+ const creation = e.creation === "idempotent" ? "idempotent" : "at_most_once";
821
+ if (e.sendReplay !== undefined && e.sendReplay !== "idempotent" && e.sendReplay !== "at_most_once")
822
+ return invalid();
823
+ const sendReplay = e.sendReplay === "idempotent" ? "idempotent" : "at_most_once";
824
+ const intendedSessionId = legacyCreation ? sessionIdForCommand(command.commandId) : text(e.intendedSessionId, 240);
825
+ if (intendedSessionId !== sessionIdForCommand(command.commandId))
826
+ return invalid();
827
+ const receipt = nullable(e.receipt, parseReceipt);
828
+ const completion = nullable(e.completion, parseComplete);
829
+ const acknowledged = boolean(e.completionAcknowledged);
830
+ const promptMessageId = nullable(e.promptMessageId, messageId);
831
+ const turn = nullable(e.turn, parseTurn);
832
+ const lastMessageId = nullable(e.lastMessageId, messageId);
833
+ const progress = nullable(e.progress, parseProgress);
834
+ const reportedProgress = nullable(e.reportedProgress, parseProgress);
835
+ const previousTimes = [progress?.observedAt, reportedProgress?.observedAt].filter((value3) => value3 !== undefined);
836
+ const lastObservedAt = e.lastObservedAt === undefined ? previousTimes.length === 0 ? null : Math.max(...previousTimes) : nullable(e.lastObservedAt, integer);
837
+ if ((progress || reportedProgress) && (lastObservedAt === null || previousTimes.some((time) => time > lastObservedAt)))
838
+ return invalid();
839
+ if (creation === "idempotent" && receipt && receipt.sessionId !== intendedSessionId)
840
+ return invalid();
841
+ if (promptMessageId !== (command.prompt === null ? null : stableMessageId(command.commandId)))
842
+ return invalid();
843
+ if (e.phase === "prepared" && receipt !== null || (e.phase === "created" || e.phase === "delivered") && receipt === null)
844
+ return invalid();
845
+ if (receipt && command.workspaceId && receipt.workspaceId !== command.workspaceId)
846
+ return invalid();
847
+ if (e.phase === "delivered" !== (completion?.status === "delivered") || e.phase === "failed" !== (completion?.status === "failed"))
848
+ return invalid();
849
+ if (acknowledged && completion === null)
850
+ return invalid();
851
+ if (completion?.status === "delivered" && (completion.sessionId !== receipt?.sessionId || completion.workspaceId !== receipt?.workspaceId))
852
+ return invalid();
853
+ if ((turn || lastMessageId) && !receipt)
854
+ return invalid();
855
+ if (turn === null !== (lastMessageId === null) || turn && turn.messageId !== lastMessageId)
856
+ return invalid();
857
+ if (e.phase === "created" && lastMessageId !== null && lastMessageId !== promptMessageId)
858
+ return invalid();
859
+ if (e.phase === "delivered" && command.prompt !== null && lastMessageId === null)
860
+ return invalid();
861
+ if ((progress || reportedProgress) && e.phase !== "delivered")
862
+ return invalid();
863
+ return {
864
+ command,
865
+ creation,
866
+ sendReplay,
867
+ intendedSessionId,
868
+ lastObservedAt,
869
+ phase: e.phase,
870
+ receipt,
871
+ promptMessageId,
872
+ turn,
873
+ lastMessageId,
874
+ completion,
875
+ completionAcknowledged: acknowledged,
876
+ progress,
877
+ reportedProgress
878
+ };
879
+ }),
880
+ requests: array(v.requests, MAX_REQUEST_ENTRIES, (value2) => {
881
+ const e = object(value2);
882
+ keys(e, ["request", "sendReplay", "messageId", "turn", "completion", "completionAcknowledged"]);
883
+ const request = parseRequest(e.request);
884
+ if (e.sendReplay !== undefined && e.sendReplay !== "idempotent" && e.sendReplay !== "at_most_once")
885
+ return invalid();
886
+ const sendReplay = e.sendReplay === "idempotent" ? "idempotent" : "at_most_once";
887
+ const id = nullable(e.messageId, (value3) => text(value3, 160));
888
+ const turn = nullable(e.turn, parseTurn);
889
+ if (turn && (request.action !== "send" || turn.messageId !== id))
890
+ return invalid();
891
+ const completion = nullable(e.completion, parseRequestComplete);
892
+ const acknowledged = boolean(e.completionAcknowledged);
893
+ if (acknowledged && !completion)
894
+ return invalid();
895
+ if (completion?.status === "done" && completion.outcome.action !== request.action)
896
+ return invalid();
897
+ if (request.action === "read" && id !== null)
898
+ return invalid();
899
+ if (request.action === "send" && id !== (request.input.messageId ?? stableMessageId(request.requestId)))
900
+ return invalid();
901
+ if (request.action === "stop" && request.input.messageId !== null && id !== request.input.messageId)
902
+ return invalid();
903
+ return { request, sendReplay, messageId: id, turn, completion, completionAcknowledged: acknowledged };
904
+ })
905
+ };
906
+ if (new Set(journal.commands.map((e) => e.command.commandId)).size !== journal.commands.length || new Set(journal.requests.map((e) => e.request.requestId)).size !== journal.requests.length)
907
+ return invalid();
908
+ const receipts = journal.commands.flatMap((e) => e.receipt ? [`${e.receipt.workspaceId}\x00${e.receipt.sessionId}`] : []);
909
+ if (new Set(receipts).size !== receipts.length)
910
+ return invalid();
911
+ const intendedIds = journal.commands.filter((e) => e.creation === "idempotent").map((e) => e.intendedSessionId);
912
+ if (new Set(intendedIds).size !== intendedIds.length)
913
+ return invalid();
914
+ for (const e of journal.requests) {
915
+ const c = journal.commands.find((c2) => c2.command.commandId === e.request.commandId);
916
+ if (e.completion?.status === "failed")
917
+ continue;
918
+ if (c?.phase !== "delivered" || c.receipt?.sessionId !== e.request.sessionId || c.receipt.workspaceId !== e.request.workspaceId)
919
+ return invalid();
920
+ }
921
+ return journal;
922
+ }
923
+ var init_protocol = __esm(() => {
924
+ init_types();
925
+ });
926
+
927
+ // ../../packages/remote-sessions/src/timeout.ts
928
+ function throwIfAborted(signal) {
929
+ if (signal.aborted)
930
+ throw new DOMException("Remote-session operation cancelled.", "AbortError");
931
+ }
932
+ async function bounded(signal, timeoutMs, effect) {
933
+ throwIfAborted(signal);
934
+ const controller = new AbortController;
935
+ let timer;
936
+ let onAbort = () => {};
937
+ const interrupted = new Promise((_resolve, reject) => {
938
+ onAbort = () => {
939
+ controller.abort();
940
+ reject(new DOMException("Remote-session operation cancelled.", "AbortError"));
941
+ };
942
+ signal.addEventListener("abort", onAbort, { once: true });
943
+ timer = setTimeout(() => {
944
+ controller.abort();
945
+ reject(new SessionRunnerError("operation_timeout", "Remote-session operation timed out; its outcome may be uncertain."));
946
+ }, timeoutMs);
947
+ });
948
+ try {
949
+ return await Promise.race([Promise.resolve().then(() => {
950
+ throwIfAborted(controller.signal);
951
+ return effect(controller.signal);
952
+ }), interrupted]);
953
+ } finally {
954
+ if (timer !== undefined)
955
+ clearTimeout(timer);
956
+ signal.removeEventListener("abort", onAbort);
957
+ }
958
+ }
959
+ var init_timeout = __esm(() => {
960
+ init_types();
961
+ });
962
+
963
+ // ../../packages/remote-sessions/src/runner.ts
964
+ function failure(code, message2) {
965
+ return { code, message: message2 };
966
+ }
967
+ function ambiguousSend(receipt) {
968
+ return failure("ambiguous_send", `Native send may have been accepted in session ${receipt.sessionId}, workspace ${receipt.workspaceId}. Inspect that local session; this send will never be retried automatically.`);
969
+ }
970
+ function definitive(error) {
971
+ if (!(error instanceof SessionRunnerError) || error.code === "operation_timeout" || error.code === "invalid_data")
972
+ return null;
973
+ return parseError({ code: error.code, message: error.message });
974
+ }
975
+ function same(a, b) {
976
+ return JSON.stringify(a) === JSON.stringify(b);
977
+ }
978
+ function sameProgress(a, b) {
979
+ if (!a)
980
+ return false;
981
+ const { observedAt: _a, ...left } = a;
982
+ const { observedAt: _b, ...right } = b;
983
+ return same(left, right);
984
+ }
985
+ function createSessionRunner(options) {
986
+ const { adapter, store, transport } = options;
987
+ const clock = options.now ?? Date.now;
988
+ const now = () => integer(clock());
989
+ const timeoutMs = integer(options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS, 1, 120000);
990
+ const creation = adapter.creation ?? "at_most_once";
991
+ if (creation !== "idempotent" && creation !== "at_most_once")
992
+ throw new SessionRunnerError("invalid_data", "Invalid adapter creation capability.");
993
+ const canReplayCreation = (entry) => entry.creation === "idempotent" && creation === "idempotent";
994
+ const sendReplay = adapter.sendReplay ?? "idempotent";
995
+ if (sendReplay !== "idempotent" && sendReplay !== "at_most_once")
996
+ throw new SessionRunnerError("invalid_data", "Invalid adapter send replay capability.");
997
+ const canReplaySend = (entry) => entry.sendReplay === "idempotent" && sendReplay === "idempotent";
998
+ let journal;
999
+ let poisoned = false;
1000
+ let queue = Promise.resolve();
1001
+ function state() {
1002
+ if (!journal || poisoned)
1003
+ throw new SessionRunnerError("journal_unavailable", "Reload the runner from a valid durable journal before continuing.");
1004
+ return journal;
1005
+ }
1006
+ async function persist(next) {
1007
+ const validated = parseJournal(next);
1008
+ try {
1009
+ await store.save(structuredClone(validated));
1010
+ } catch (error) {
1011
+ poisoned = true;
1012
+ throw error;
1013
+ }
1014
+ journal = validated;
1015
+ }
1016
+ async function load() {
1017
+ if (poisoned)
1018
+ state();
1019
+ if (journal)
1020
+ return;
1021
+ try {
1022
+ journal = parseJournal(await store.load());
1023
+ } catch (error) {
1024
+ poisoned = true;
1025
+ throw error;
1026
+ }
1027
+ if (journal.commands.some((e) => e.phase === "prepared" && !canReplayCreation(e))) {
1028
+ await persist({ ...journal, commands: journal.commands.map((e) => e.phase !== "prepared" || canReplayCreation(e) ? e : {
1029
+ ...e,
1030
+ phase: "failed",
1031
+ completion: { status: "failed", error: failure("ambiguous_creation", "Creation was interrupted before a receipt was saved. Inspect the native harness; this command will not create another session.") }
1032
+ }) });
1033
+ }
1034
+ await recoverUnsafeSends();
1035
+ }
1036
+ async function recoverUnsafeSends() {
1037
+ const current = state();
1038
+ let changed = false;
1039
+ let commands = current.commands.map((e) => {
1040
+ if (e.phase !== "created" || !e.turn || !e.receipt || canReplaySend(e))
1041
+ return e;
1042
+ changed = true;
1043
+ const completion = { status: "failed", error: ambiguousSend(e.receipt) };
1044
+ const recovered = { ...e, phase: "failed", completion };
1045
+ return recovered;
1046
+ });
1047
+ const requests = current.requests.map((e) => {
1048
+ if (e.completion || e.request.action !== "send" || !e.turn || canReplaySend(e))
1049
+ return e;
1050
+ changed = true;
1051
+ const completion = { status: "failed", error: ambiguousSend(e.request) };
1052
+ commands = commands.map((c) => c.command.commandId === e.request.commandId && c.phase === "delivered" && c.lastMessageId !== e.messageId ? { ...c, turn: e.turn, lastMessageId: e.messageId, progress: null, reportedProgress: null } : c);
1053
+ return { ...e, completion };
1054
+ });
1055
+ if (changed)
1056
+ await persist({ ...current, commands, requests });
1057
+ }
1058
+ function serialized(operation) {
1059
+ const task = queue.then(async () => {
1060
+ await load();
1061
+ return operation();
1062
+ });
1063
+ queue = task.then(() => {}, () => {});
1064
+ return task;
1065
+ }
1066
+ function command(id) {
1067
+ const entry = state().commands.find((e) => e.command.commandId === id);
1068
+ if (!entry)
1069
+ throw new SessionRunnerError("unknown_command", "The command is not in this account's local journal.");
1070
+ return entry;
1071
+ }
1072
+ function request(id) {
1073
+ const entry = state().requests.find((e) => e.request.requestId === id);
1074
+ if (!entry)
1075
+ throw new SessionRunnerError("unknown_request", "The request is not in this account's local journal.");
1076
+ return entry;
1077
+ }
1078
+ async function saveCommand(entry) {
1079
+ await persist({ ...state(), commands: state().commands.map((e) => e.command.commandId === entry.command.commandId ? entry : e) });
1080
+ }
1081
+ async function saveRequest(entry) {
1082
+ await persist({ ...state(), requests: state().requests.map((e) => e.request.requestId === entry.request.requestId ? entry : e) });
1083
+ }
1084
+ function local(signal, effect) {
1085
+ return bounded(signal, timeoutMs, effect);
1086
+ }
1087
+ async function flushCommand(id, signal) {
1088
+ const e = command(id);
1089
+ if (!e.completion || e.completionAcknowledged)
1090
+ return;
1091
+ await local(signal, (s) => transport.complete(id, structuredClone(e.completion), s));
1092
+ await saveCommand({ ...command(id), completionAcknowledged: true });
1093
+ }
1094
+ async function flushRequest(id, signal) {
1095
+ const e = request(id);
1096
+ if (!e.completion || e.completionAcknowledged)
1097
+ return;
1098
+ await local(signal, (s) => transport.completeRequest(id, structuredClone(e.completion), s));
1099
+ await saveRequest({ ...request(id), completionAcknowledged: true });
1100
+ }
1101
+ async function createPrepared(id, signal, firstAttempt = false) {
1102
+ const entry = command(id);
1103
+ if (entry.phase !== "prepared")
1104
+ return;
1105
+ if (!firstAttempt && !canReplayCreation(entry)) {
1106
+ await saveCommand({ ...entry, phase: "failed", completion: { status: "failed", error: failure("ambiguous_creation", "Creation was interrupted before a receipt was saved. Inspect the native harness; this command will not create another session.") } });
1107
+ return;
1108
+ }
1109
+ throwIfAborted(signal);
1110
+ if (entry.command.expiresAt <= now()) {
1111
+ await saveCommand({ ...entry, phase: "failed", completion: {
1112
+ status: "failed",
1113
+ error: failure("expired", "The command expired before creation could be attempted or safely replayed.")
1114
+ } });
1115
+ return;
1116
+ }
1117
+ try {
1118
+ const receipt = parseReceipt(await local(signal, (s) => adapter.create(structuredClone(entry.command), s)));
1119
+ if (entry.command.workspaceId && receipt.workspaceId !== entry.command.workspaceId) {
1120
+ throw new SessionRunnerError("invalid_data", "The adapter created a session in a different workspace.");
1121
+ }
1122
+ if (entry.creation === "idempotent" && receipt.sessionId !== entry.intendedSessionId) {
1123
+ throw new SessionRunnerError("invalid_data", "Idempotent creation must return the journal's deterministic session ID.");
1124
+ }
1125
+ await saveCommand({ ...command(id), phase: "created", receipt });
1126
+ } catch (error) {
1127
+ if (signal.aborted)
1128
+ throw error;
1129
+ const proven = definitive(error);
1130
+ if (!proven)
1131
+ throw error;
1132
+ await saveCommand({ ...command(id), phase: "failed", completion: { status: "failed", error: proven } });
1133
+ }
1134
+ }
1135
+ async function deliver(id, signal) {
1136
+ let e = command(id);
1137
+ if (e.completion)
1138
+ return e.completion;
1139
+ if (e.turn && e.receipt && !canReplaySend(e)) {
1140
+ const completion2 = { status: "failed", error: ambiguousSend(e.receipt) };
1141
+ await saveCommand({ ...e, phase: "failed", completion: completion2 });
1142
+ return completion2;
1143
+ }
1144
+ if (e.command.expiresAt <= now()) {
1145
+ const completion2 = { status: "failed", error: failure("expired", "The command expired before its next native effect.") };
1146
+ await saveCommand({ ...e, phase: "failed", completion: completion2 });
1147
+ return completion2;
1148
+ }
1149
+ if (!e.receipt)
1150
+ throw new SessionRunnerError("ambiguous_creation", "A missing creation receipt cannot be replayed.");
1151
+ if (e.command.prompt !== null) {
1152
+ if (!e.turn) {
1153
+ const baseline = parseProgress(await local(signal, (s) => adapter.observe(structuredClone(e.receipt), s)));
1154
+ throwIfAborted(signal);
1155
+ await saveCommand({
1156
+ ...e,
1157
+ sendReplay: canReplaySend(e) ? "idempotent" : "at_most_once",
1158
+ turn: { messageId: e.promptMessageId, baselineMessageCount: baseline.messageCount ?? null, seenActive: false },
1159
+ lastMessageId: e.promptMessageId
1160
+ });
1161
+ e = command(id);
1162
+ }
1163
+ try {
1164
+ throwIfAborted(signal);
1165
+ if (e.command.expiresAt <= now())
1166
+ return deliver(id, signal);
1167
+ const result = parseSendResult(await local(signal, (s) => adapter.send(structuredClone(e.receipt), {
1168
+ prompt: e.command.prompt,
1169
+ messageId: e.promptMessageId,
1170
+ model: structuredClone(e.command.model)
1171
+ }, s)));
1172
+ if (result.messageId !== null && result.messageId !== e.promptMessageId) {
1173
+ throw new SessionRunnerError("invalid_data", "The adapter returned a different stable message ID.");
1174
+ }
1175
+ } catch (error) {
1176
+ if (poisoned)
1177
+ throw error;
1178
+ const proven = definitive(error);
1179
+ const uncertain = proven?.code === "ambiguous_send" || !canReplaySend(e) && (signal.aborted || !proven);
1180
+ if (!uncertain && signal.aborted)
1181
+ throw error;
1182
+ if (!uncertain && !proven)
1183
+ throw error;
1184
+ const completion2 = { status: "failed", error: uncertain ? ambiguousSend(e.receipt) : proven };
1185
+ await saveCommand({ ...command(id), phase: "failed", completion: completion2 });
1186
+ throwIfAborted(signal);
1187
+ return completion2;
1188
+ }
1189
+ }
1190
+ const receipt = e.receipt;
1191
+ if (!receipt)
1192
+ throw new SessionRunnerError("invalid_data", "The delivered command has no receipt.");
1193
+ const completion = { status: "delivered", sessionId: receipt.sessionId, workspaceId: receipt.workspaceId };
1194
+ await saveCommand({ ...command(id), phase: "delivered", completion });
1195
+ return completion;
1196
+ }
1197
+ function knownReceipt(e) {
1198
+ return state().commands.find((c) => c.command.commandId === e.request.commandId && c.phase === "delivered" && c.receipt?.sessionId === e.request.sessionId && c.receipt.workspaceId === e.request.workspaceId);
1199
+ }
1200
+ async function performRequest(id, signal) {
1201
+ let e = request(id);
1202
+ if (e.completion)
1203
+ return e.completion;
1204
+ throwIfAborted(signal);
1205
+ let completion;
1206
+ let applyTurn = false;
1207
+ const c = knownReceipt(e);
1208
+ if (e.request.action === "send" && e.turn && !canReplaySend(e)) {
1209
+ completion = { status: "failed", error: ambiguousSend(e.request) };
1210
+ applyTurn = true;
1211
+ } else if (e.request.expiresAt <= now()) {
1212
+ completion = { status: "failed", error: failure("expired", "The request expired before its next native effect.") };
1213
+ applyTurn = e.turn !== null;
1214
+ } else if (!c?.receipt)
1215
+ completion = { status: "failed", error: failure("unknown_receipt", "Only a session delivered by this account's local journal can be controlled.") };
1216
+ else {
1217
+ const earlier = state().requests.slice(0, state().requests.findIndex((r) => r.request.requestId === id)).some((r) => r.request.commandId === e.request.commandId && r.request.action !== "read" && r.completion === null);
1218
+ if (earlier && e.request.action !== "read")
1219
+ throw new SessionRunnerError("request_in_progress", "An earlier native control request must be reconciled first.");
1220
+ try {
1221
+ if (e.request.action === "read") {
1222
+ const input = e.request.input;
1223
+ const result = parseReadResult(await local(signal, (s) => adapter.read(structuredClone(c.receipt), structuredClone(input), s)));
1224
+ completion = parseRequestComplete({ status: "done", outcome: { action: "read", result } });
1225
+ } else if (e.request.action === "send") {
1226
+ const input = e.request.input;
1227
+ if (!e.turn) {
1228
+ const baseline = parseProgress(await local(signal, (s) => adapter.observe(structuredClone(c.receipt), s)));
1229
+ throwIfAborted(signal);
1230
+ await saveRequest({
1231
+ ...e,
1232
+ sendReplay: canReplaySend(e) ? "idempotent" : "at_most_once",
1233
+ turn: { messageId: e.messageId, baselineMessageCount: baseline.messageCount ?? null, seenActive: false }
1234
+ });
1235
+ e = request(id);
1236
+ }
1237
+ throwIfAborted(signal);
1238
+ if (e.request.expiresAt <= now())
1239
+ return performRequest(id, signal);
1240
+ const result = parseSendResult(await local(signal, (s) => adapter.send(structuredClone(c.receipt), {
1241
+ ...structuredClone(input),
1242
+ messageId: e.messageId
1243
+ }, s)));
1244
+ if (result.messageId !== null && result.messageId !== e.messageId)
1245
+ throw new SessionRunnerError("invalid_data", "The adapter returned a different stable message ID.");
1246
+ completion = { status: "done", outcome: { action: "send", result } };
1247
+ applyTurn = true;
1248
+ } else if (e.request.input.messageId === null && e.messageId !== null && c.lastMessageId !== e.messageId) {
1249
+ completion = { status: "done", outcome: { action: "stop", result: { stopped: false, reason: "different_turn" } } };
1250
+ } else if (e.messageId === null) {
1251
+ completion = { status: "done", outcome: { action: "stop", result: { stopped: false, reason: null } } };
1252
+ } else {
1253
+ const result = parseStopResult(await local(signal, (s) => adapter.stop(structuredClone(c.receipt), { messageId: e.messageId }, s)));
1254
+ completion = { status: "done", outcome: { action: "stop", result } };
1255
+ }
1256
+ } catch (error) {
1257
+ if (poisoned)
1258
+ throw error;
1259
+ const latest = request(id);
1260
+ const proven = definitive(error);
1261
+ const uncertainSend = latest.request.action === "send" && latest.turn !== null && (proven?.code === "ambiguous_send" || !canReplaySend(latest) && (signal.aborted || !proven));
1262
+ if (uncertainSend) {
1263
+ completion = { status: "failed", error: ambiguousSend(latest.request) };
1264
+ applyTurn = true;
1265
+ } else {
1266
+ if (signal.aborted)
1267
+ throw error;
1268
+ if (!proven)
1269
+ throw error;
1270
+ completion = { status: "failed", error: proven };
1271
+ }
1272
+ }
1273
+ }
1274
+ e = request(id);
1275
+ const updatedCommand = applyTurn && c && e.turn && c.lastMessageId !== e.messageId ? {
1276
+ ...command(c.command.commandId),
1277
+ turn: e.turn,
1278
+ lastMessageId: e.messageId,
1279
+ progress: null,
1280
+ reportedProgress: null
1281
+ } : null;
1282
+ await persist({
1283
+ ...state(),
1284
+ requests: state().requests.map((r) => r.request.requestId === id ? { ...e, completion } : r),
1285
+ commands: state().commands.map((c2) => updatedCommand && c2.command.commandId === updatedCommand.command.commandId ? updatedCommand : c2)
1286
+ });
1287
+ throwIfAborted(signal);
1288
+ return completion;
1289
+ }
1290
+ async function flushProgress(id, progress, signal) {
1291
+ try {
1292
+ await local(signal, (s) => transport.report(id, structuredClone(progress), s));
1293
+ } catch (error) {
1294
+ if (signal.aborted)
1295
+ throw error;
1296
+ if (typeof error !== "object" || error === null || !("status" in error) || error.status !== 404 && error.status !== 409)
1297
+ throw error;
1298
+ await saveCommand({ ...command(id), progress: null, reportedProgress: null });
1299
+ return false;
1300
+ }
1301
+ await saveCommand({ ...command(id), progress: null, reportedProgress: progress });
1302
+ return true;
1303
+ }
1304
+ async function watch(id, signal) {
1305
+ let e = command(id);
1306
+ if (e.phase !== "delivered" || !e.receipt || !e.completionAcknowledged)
1307
+ return;
1308
+ if (state().requests.some((r) => r.request.commandId === id && r.request.action === "send" && r.completion === null))
1309
+ return;
1310
+ if (e.progress) {
1311
+ if (!await flushProgress(id, e.progress, signal))
1312
+ return;
1313
+ e = command(id);
1314
+ }
1315
+ let progress = parseProgress(await local(signal, (s) => adapter.observe(structuredClone(e.receipt), s)));
1316
+ let turn = e.turn;
1317
+ if (turn) {
1318
+ const active = progress.status === "running" || progress.status === "waiting";
1319
+ if (active && !turn.seenActive)
1320
+ turn = { ...turn, seenActive: true };
1321
+ const knownCounts = turn.baselineMessageCount !== null && progress.messageCount !== undefined;
1322
+ const newMessages = turn.baselineMessageCount !== null && progress.messageCount !== undefined && progress.messageCount >= turn.baselineMessageCount + 2;
1323
+ if (!active && !turn.seenActive && !newMessages) {
1324
+ progress = { ...progress, status: "running", waitingFor: null, finalText: "", error: null };
1325
+ } else if (!active && knownCounts && !newMessages) {
1326
+ progress = { ...progress, finalText: "" };
1327
+ }
1328
+ }
1329
+ if (progress.status === "running" || progress.status === "waiting")
1330
+ progress = { ...progress, finalText: "", error: null };
1331
+ if (!sameProgress(e.reportedProgress, progress)) {
1332
+ progress = { ...progress, observedAt: integer(Math.max(progress.observedAt, (e.lastObservedAt ?? -1) + 1)) };
1333
+ await saveCommand({ ...e, turn, progress, lastObservedAt: progress.observedAt });
1334
+ await flushProgress(id, progress, signal);
1335
+ } else if (!same(turn, e.turn))
1336
+ await saveCommand({ ...e, turn });
1337
+ }
1338
+ return {
1339
+ accept(input, signal) {
1340
+ return serialized(async () => {
1341
+ throwIfAborted(signal);
1342
+ const value = parseCommand(input);
1343
+ const existing = state().commands.find((e) => e.command.commandId === value.commandId);
1344
+ if (existing && !same(existing.command, value))
1345
+ throw new SessionRunnerError("command_conflict", "A command ID cannot be reused with different input.");
1346
+ if (!existing) {
1347
+ if (state().commands.length >= MAX_COMMAND_ENTRIES)
1348
+ throw new SessionRunnerError("journal_full", "Prune acknowledged commands before accepting more work.");
1349
+ const expired = value.expiresAt <= now();
1350
+ await persist({ ...state(), commands: [...state().commands, {
1351
+ command: value,
1352
+ creation,
1353
+ sendReplay,
1354
+ intendedSessionId: sessionIdForCommand(value.commandId),
1355
+ lastObservedAt: null,
1356
+ phase: expired ? "failed" : "prepared",
1357
+ receipt: null,
1358
+ promptMessageId: value.prompt === null ? null : stableMessageId(value.commandId),
1359
+ turn: null,
1360
+ lastMessageId: null,
1361
+ completion: expired ? { status: "failed", error: failure("expired", "The command expired before creation.") } : null,
1362
+ completionAcknowledged: false,
1363
+ progress: null,
1364
+ reportedProgress: null
1365
+ }] });
1366
+ }
1367
+ await createPrepared(value.commandId, signal, !existing);
1368
+ throwIfAborted(signal);
1369
+ const completion = await deliver(value.commandId, signal);
1370
+ await flushCommand(value.commandId, signal);
1371
+ return structuredClone(completion);
1372
+ });
1373
+ },
1374
+ execute(input, signal) {
1375
+ return serialized(async () => {
1376
+ throwIfAborted(signal);
1377
+ const value = parseRequest(input);
1378
+ const existing = state().requests.find((e) => e.request.requestId === value.requestId);
1379
+ if (existing && !same(existing.request, value))
1380
+ throw new SessionRunnerError("request_conflict", "A request ID cannot be reused with different input.");
1381
+ if (!existing) {
1382
+ if (state().requests.length >= MAX_REQUEST_ENTRIES)
1383
+ throw new SessionRunnerError("journal_full", "Prune acknowledged requests before accepting more work.");
1384
+ const c = state().commands.find((c2) => c2.command.commandId === value.commandId && c2.phase === "delivered" && c2.receipt?.sessionId === value.sessionId && c2.receipt.workspaceId === value.workspaceId);
1385
+ let completion2 = !c ? { status: "failed", error: failure("unknown_receipt", "Only a session delivered by this account's local journal can be controlled.") } : null;
1386
+ const id = value.action === "send" ? value.input.messageId ?? stableMessageId(value.requestId) : value.action === "stop" ? value.input.messageId ?? c?.lastMessageId ?? null : null;
1387
+ if (c && value.action === "send") {
1388
+ const prior = state().requests.find((r) => r.request.commandId === value.commandId && r.messageId === id && r.request.action === "send");
1389
+ const original = c.promptMessageId === id ? { prompt: c.command.prompt, model: c.command.model } : null;
1390
+ const fingerprint = original ?? (prior?.request.action === "send" ? prior.request.input : null);
1391
+ if (fingerprint && !same({ prompt: fingerprint.prompt, model: fingerprint.model }, { prompt: value.input.prompt, model: value.input.model })) {
1392
+ completion2 = { status: "failed", error: failure("message_conflict", "A stable message ID cannot be reused with different prompt or model input.") };
1393
+ } else if (original || prior?.completion?.status === "done") {
1394
+ completion2 = { status: "done", outcome: { action: "send", result: { messageId: id, alreadyPresent: true } } };
1395
+ }
1396
+ }
1397
+ await persist({ ...state(), requests: [...state().requests, { request: value, sendReplay, messageId: id, turn: null, completion: completion2, completionAcknowledged: false }] });
1398
+ }
1399
+ const completion = await performRequest(value.requestId, signal);
1400
+ await flushRequest(value.requestId, signal);
1401
+ return structuredClone(completion);
1402
+ });
1403
+ },
1404
+ reconcile(signal) {
1405
+ return serialized(async () => {
1406
+ throwIfAborted(signal);
1407
+ let firstError;
1408
+ const attempt = async (operation) => {
1409
+ throwIfAborted(signal);
1410
+ try {
1411
+ await operation();
1412
+ } catch (error) {
1413
+ if (signal.aborted || poisoned)
1414
+ throw error;
1415
+ firstError ??= error;
1416
+ }
1417
+ };
1418
+ for (const e of state().commands)
1419
+ await attempt(async () => {
1420
+ await createPrepared(e.command.commandId, signal);
1421
+ await deliver(e.command.commandId, signal);
1422
+ await flushCommand(e.command.commandId, signal);
1423
+ });
1424
+ for (const e of state().requests)
1425
+ await attempt(async () => {
1426
+ await performRequest(e.request.requestId, signal);
1427
+ await flushRequest(e.request.requestId, signal);
1428
+ });
1429
+ for (const e of state().commands)
1430
+ await attempt(() => watch(e.command.commandId, signal));
1431
+ if (firstError !== undefined)
1432
+ throw firstError;
1433
+ });
1434
+ },
1435
+ inspect() {
1436
+ return serialized(async () => structuredClone(state()));
1437
+ },
1438
+ prune(pruneOptions = {}) {
1439
+ return serialized(async () => {
1440
+ const retainCommands = integer(pruneOptions.retainCommands ?? 128, 0, MAX_COMMAND_ENTRIES);
1441
+ const retainRequests = integer(pruneOptions.retainRequests ?? 128, 0, MAX_REQUEST_ENTRIES);
1442
+ const requests = [...state().requests];
1443
+ let toRemove = Math.max(0, requests.length - retainRequests);
1444
+ const retainedRequests = requests.filter((e) => {
1445
+ if (toRemove > 0 && e.completionAcknowledged && e.request.expiresAt <= now()) {
1446
+ toRemove--;
1447
+ return false;
1448
+ }
1449
+ return true;
1450
+ });
1451
+ toRemove = Math.max(0, state().commands.length - retainCommands);
1452
+ const commands = state().commands.filter((e) => {
1453
+ const terminal = e.phase === "failed" || e.reportedProgress?.status === "idle" || e.reportedProgress?.status === "error";
1454
+ if (toRemove > 0 && terminal && e.completionAcknowledged && e.progress === null && e.command.expiresAt <= now() && !retainedRequests.some((r) => r.request.commandId === e.command.commandId)) {
1455
+ toRemove--;
1456
+ return false;
1457
+ }
1458
+ return true;
1459
+ });
1460
+ await persist({ ...state(), commands, requests: retainedRequests });
1461
+ return structuredClone(state());
1462
+ });
1463
+ }
1464
+ };
1465
+ }
1466
+ var init_runner = __esm(() => {
1467
+ init_types();
1468
+ init_protocol();
1469
+ init_timeout();
1470
+ });
1471
+
1472
+ // ../../packages/remote-sessions/src/remote-client.ts
1473
+ function safeBase(value) {
1474
+ const url = new URL(value);
1475
+ const loopback = url.hostname === "localhost" || url.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(url.hostname);
1476
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash) {
1477
+ throw new SessionRunnerError("unsafe_url", "Use HTTPS (or HTTP on loopback), without credentials, query, or fragment.");
1478
+ }
1479
+ if (!url.pathname.endsWith("/"))
1480
+ url.pathname += "/";
1481
+ return url;
1482
+ }
1483
+ async function smallJson(response, signal) {
1484
+ const length = response.headers.get("content-length");
1485
+ if (length !== null && (!/^\d+$/.test(length) || Number(length) > MAX_RESPONSE_BYTES)) {
1486
+ await response.body?.cancel();
1487
+ throw new SessionRunnerError("invalid_response", "Remote-session response exceeds the size limit.");
1488
+ }
1489
+ if (!response.body)
1490
+ throw new SessionRunnerError("invalid_response", "Remote-session response has no JSON body.");
1491
+ const reader = response.body.getReader();
1492
+ const decoder = new TextDecoder("utf-8", { fatal: true });
1493
+ let size = 0;
1494
+ let body = "";
1495
+ try {
1496
+ for (;; ) {
1497
+ throwIfAborted(signal);
1498
+ const chunk = await reader.read();
1499
+ if (chunk.done)
1500
+ break;
1501
+ size += chunk.value.byteLength;
1502
+ if (size > MAX_RESPONSE_BYTES)
1503
+ throw new SessionRunnerError("invalid_response", "Remote-session response exceeds the size limit.");
1504
+ body += decoder.decode(chunk.value, { stream: true });
1505
+ }
1506
+ body += decoder.decode();
1507
+ try {
1508
+ return JSON.parse(body);
1509
+ } catch {
1510
+ throw new SessionRunnerError("invalid_response", "Remote-session response is not valid JSON.");
1511
+ }
1512
+ } finally {
1513
+ await reader.cancel().catch(() => {});
1514
+ reader.releaseLock();
1515
+ }
1516
+ }
1517
+ function createRemoteSessionTransport(options) {
1518
+ const base = safeBase(options.baseUrl);
1519
+ const fetcher = options.fetch ?? globalThis.fetch;
1520
+ const timeout = integer(options.timeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS, 1, 120000);
1521
+ const id = (value) => {
1522
+ const parsed = text(value, 160);
1523
+ if (parsed === "." || parsed === "..")
1524
+ throw new SessionRunnerError("invalid_data", "A route ID cannot be a dot segment.");
1525
+ return encodeURIComponent(parsed);
1526
+ };
1527
+ async function call(path, method, body, signal) {
1528
+ return bounded(signal, timeout, async (s) => {
1529
+ const token = options.token();
1530
+ if (!token || /[\r\n]/.test(token))
1531
+ throw new SessionRunnerError("missing_token", "A current runner token is required.");
1532
+ const response = await fetcher(new URL(path, base), {
1533
+ method,
1534
+ redirect: "error",
1535
+ signal: s,
1536
+ headers: { authorization: `Bearer ${token}`, accept: "application/json", ...body === undefined ? {} : { "content-type": "application/json" } },
1537
+ ...body === undefined ? {} : { body: JSON.stringify(body) }
1538
+ });
1539
+ if (response.redirected || response.status >= 300 && response.status < 400) {
1540
+ await response.body?.cancel();
1541
+ throw new SessionRunnerError("redirect_refused", "Remote-session requests cannot follow redirects.");
1542
+ }
1543
+ if (!response.ok) {
1544
+ await response.body?.cancel();
1545
+ throw new RemoteSessionHttpError(response.status);
1546
+ }
1547
+ return smallJson(response, s);
1548
+ });
1549
+ }
1550
+ return {
1551
+ async complete(commandId, body, signal) {
1552
+ const parsed = parseComplete(body);
1553
+ const response = object(await call(`v1/remote-session-commands/${id(commandId)}/complete`, "POST", parsed, signal));
1554
+ const c = object(response.command);
1555
+ if (c.id !== commandId || c.status !== parsed.status || (parsed.status === "delivered" ? c.sessionId !== parsed.sessionId || c.workspaceId !== parsed.workspaceId : c.sessionId !== null || c.workspaceId !== null))
1556
+ throw new SessionRunnerError("invalid_response", "Command acknowledgement does not match the submitted result.");
1557
+ },
1558
+ async report(commandId, progress, signal) {
1559
+ const response = object(await call(`v1/remote-session-commands/${id(commandId)}/session`, "POST", parseProgress(progress), signal));
1560
+ if (response.ok !== true)
1561
+ throw new SessionRunnerError("invalid_response", "Progress acknowledgement is invalid.");
1562
+ },
1563
+ async completeRequest(requestId, body, signal) {
1564
+ const parsed = parseRequestComplete(body);
1565
+ const response = object(await call(`v1/remote-session-requests/${id(requestId)}/complete`, "POST", parsed, signal));
1566
+ const r = object(response.request);
1567
+ if (r.id !== requestId || r.status !== parsed.status)
1568
+ throw new SessionRunnerError("invalid_response", "Request acknowledgement does not match the submitted result.");
1569
+ },
1570
+ async claimCommand(commandId, signal) {
1571
+ const response = object(await call(`v1/remote-session-commands/${id(commandId)}/claim`, "POST", {}, signal));
1572
+ const value = parseCommand(response.assignment);
1573
+ if (value.commandId !== commandId)
1574
+ throw new SessionRunnerError("invalid_response", "Claimed command ID does not match.");
1575
+ return value;
1576
+ },
1577
+ async claimRequest(requestId, signal) {
1578
+ const response = object(await call(`v1/remote-session-requests/${id(requestId)}/claim`, "POST", {}, signal));
1579
+ const value = parseRequest(response.assignment);
1580
+ if (value.requestId !== requestId)
1581
+ throw new SessionRunnerError("invalid_response", "Claimed request ID does not match.");
1582
+ return value;
1583
+ },
1584
+ async pendingRequests(signal) {
1585
+ const response = object(await call("v1/remote-session-requests/pending", "GET", undefined, signal));
1586
+ return array(response.items, 5, (value) => {
1587
+ const item = object(value);
1588
+ if (item.kind !== "remote_session_request")
1589
+ throw new SessionRunnerError("invalid_response", "Unexpected pending work kind.");
1590
+ return { kind: "remote_session_request", requestId: text(item.requestId, 160) };
1591
+ });
1592
+ },
1593
+ async publishInventory(inventory, signal) {
1594
+ const response = object(await call("v1/automation-runner/inventory", "PUT", parseInventory2(inventory), signal));
1595
+ if (response.ok !== true)
1596
+ throw new SessionRunnerError("invalid_response", "Inventory acknowledgement is invalid.");
1597
+ integer(response.updatedAt);
1598
+ }
1599
+ };
1600
+ }
1601
+ var RemoteSessionHttpError, MAX_RESPONSE_BYTES;
1602
+ var init_remote_client = __esm(() => {
1603
+ init_types();
1604
+ init_protocol();
1605
+ init_timeout();
1606
+ RemoteSessionHttpError = class RemoteSessionHttpError extends Error {
1607
+ status;
1608
+ constructor(status2) {
1609
+ super(`Remote-session HTTP request failed (${status2}).`);
1610
+ this.name = "RemoteSessionHttpError";
1611
+ this.status = status2;
1612
+ }
1613
+ };
1614
+ MAX_RESPONSE_BYTES = 512 * 1024;
1615
+ });
1616
+
1617
+ // ../../packages/remote-sessions/src/index.ts
1618
+ var init_src = __esm(() => {
1619
+ init_protocol();
1620
+ init_types();
1621
+ init_runner();
1622
+ init_remote_client();
1623
+ });
1624
+
1625
+ // ../../packages/opencode-plugin/src/native-opencode.ts
1626
+ import { createHash as createHash4 } from "node:crypto";
1627
+ import { realpath } from "node:fs/promises";
1628
+ import { basename } from "node:path";
1629
+ function hasNativeSessionHost(ctx) {
1630
+ return typeof ctx.session?.create === "function" && typeof ctx.session.get === "function" && typeof ctx.session.context === "function" && typeof ctx.session.prompt === "function" && typeof ctx.session.switchModel === "function" && typeof ctx.session.interrupt === "function" && typeof ctx.model?.list === "function" && typeof ctx.model.default === "function" && typeof ctx.permission?.list === "function";
1631
+ }
1632
+ function nativeModel(model2) {
1633
+ return { providerID: model2.providerId, id: model2.modelId, ...model2.variant ? { variant: model2.variant } : {} };
1634
+ }
1635
+ function remoteModel(model2) {
1636
+ return model2 ? { providerId: model2.providerID, modelId: model2.id, variant: model2.variant ?? null } : null;
1637
+ }
1638
+ function digest2(value) {
1639
+ return createHash4("sha256").update(value).digest("hex").slice(0, 24);
1640
+ }
1641
+ function clipped(value, max) {
1642
+ return value.slice(0, max);
1643
+ }
1644
+ function errorOf(error) {
1645
+ return error ? { code: clipped(error.type || "native_error", 60), message: clipped(error.message || "OpenCode reported an error.", 2000) } : null;
1646
+ }
1647
+ function summary(value) {
1648
+ return value === undefined ? null : clipped(JSON.stringify(value) ?? "", 2000);
1649
+ }
1650
+ function admission(value) {
1651
+ if (!isRecord(value) || typeof value.messageId !== "string" || typeof value.admitted !== "boolean" || value.baselineIdle !== null && typeof value.baselineIdle !== "number")
1652
+ return null;
1653
+ return { messageId: value.messageId, baselineIdle: value.baselineIdle, admitted: value.admitted };
1654
+ }
1655
+ async function nativeWorkspace(ctx, workspaceId, signal) {
1656
+ signal.throwIfAborted();
1657
+ const [models, selected] = await Promise.all([
1658
+ ctx.model.list(),
1659
+ ctx.model.default()
1660
+ ]);
1661
+ signal.throwIfAborted();
1662
+ if (await realpath(models.location.directory) !== await realpath(ctx.location.directory) || await realpath(selected.location.directory) !== await realpath(ctx.location.directory)) {
1663
+ throw new SessionRunnerError("workspace_unavailable", "OpenCode returned models for a different Location.");
1664
+ }
1665
+ return {
1666
+ workspaceId,
1667
+ name: clipped(basename(ctx.location.directory) || "OpenCode", 120),
1668
+ active: true,
1669
+ engine: "v2",
1670
+ defaultModel: selected.data ? { providerId: selected.data.providerID, modelId: selected.data.id } : null,
1671
+ models: models.data.filter((model2) => model2.enabled).slice(0, 200).map((model2) => ({
1672
+ providerId: clipped(model2.providerID, 160),
1673
+ modelId: clipped(model2.id, 160),
1674
+ name: clipped(model2.name, 200)
1675
+ }))
1676
+ };
1677
+ }
1678
+ function createNativeOpenCodeAdapter(input) {
1679
+ const { ctx, directory, workspaceId } = input;
1680
+ const now = input.now ?? Date.now;
1681
+ const markerKey = (sessionId) => `${input.storagePrefix}/native/${digest2(sessionId)}`;
1682
+ const load = async (receipt) => admission(await ctx.storage.get(markerKey(receipt.sessionId)));
1683
+ const save = (receipt, value) => ctx.storage.set(markerKey(receipt.sessionId), { ...value });
1684
+ async function owned(receipt, signal) {
1685
+ signal.throwIfAborted();
1686
+ if (receipt.workspaceId !== workspaceId)
1687
+ throw new SessionRunnerError("workspace_unavailable", "This runner approves only its current OpenCode directory.");
1688
+ const info = await ctx.session.get({ sessionID: receipt.sessionId }, { signal });
1689
+ signal.throwIfAborted();
1690
+ if (await realpath(info.location.directory) !== directory) {
1691
+ throw new SessionRunnerError("workspace_unavailable", "The session has moved outside this runner's approved directory.");
1692
+ }
1693
+ return info;
1694
+ }
1695
+ async function snapshot(receipt, signal) {
1696
+ const info = await owned(receipt, signal);
1697
+ const [messages, permissions, marker] = await Promise.all([
1698
+ ctx.session.context({ sessionID: receipt.sessionId }, { signal }),
1699
+ ctx.permission.list({ sessionID: receipt.sessionId }, { signal }),
1700
+ load(receipt)
1701
+ ]);
1702
+ signal.throwIfAborted();
1703
+ const users = messages.filter((message2) => message2.type === "user");
1704
+ const latestUser = users.at(-1);
1705
+ const lastIdle = messages.findLast((message2) => message2.type === "idle");
1706
+ const idleAt = Math.max(info.time.idle ?? -1, lastIdle?.time.created ?? -1);
1707
+ const pending = marker !== null && !messages.some((message2) => message2.id === marker.messageId) && idleAt <= (marker.baselineIdle ?? -1);
1708
+ const activityAt = messages.reduce((latest, message2) => message2.type === "assistant" || message2.type === "compaction" ? Math.max(latest, message2.time.created) : latest, latestUser?.time.created ?? -1);
1709
+ const running = pending || activityAt > idleAt;
1710
+ const waitingFor = permissions.length ? "permission" : messages.some((message2) => message2.type === "assistant" && message2.time.created > idleAt && message2.time.created >= (latestUser?.time.created ?? 0) && message2.content.some((part) => part.type === "tool" && part.name === "question" && part.state.status === "running")) ? "question" : null;
1711
+ const assistantIndex = messages.findLastIndex((message2) => message2.type === "assistant");
1712
+ const turnBoundary = Math.max(messages.findLastIndex((message2) => message2.type === "user"), messages.findLastIndex((message2) => message2.type === "compaction"));
1713
+ const assistant = assistantIndex > turnBoundary ? messages[assistantIndex] : undefined;
1714
+ const nativeError = assistant?.type === "assistant" ? errorOf(assistant.error) : null;
1715
+ const status2 = waitingFor ? "waiting" : running ? "running" : info.outcome === "failed" ? "error" : "idle";
1716
+ const finalText = !running && !waitingFor && assistant?.type === "assistant" ? clipped(assistant.content.filter((part) => part.type === "text").map((part) => part.text).join(`
1717
+ `), 20000) : "";
1718
+ const progress = {
1719
+ status: status2,
1720
+ waitingFor,
1721
+ engine: "v2",
1722
+ model: remoteModel(info.model ?? (assistant?.type === "assistant" ? assistant.model : undefined)),
1723
+ finalText,
1724
+ error: status2 === "error" ? nativeError ?? { code: "native_error", message: "OpenCode's last execution failed." } : null,
1725
+ messageCount: messages.length,
1726
+ observedAt: now()
1727
+ };
1728
+ return { info, messages, latestUser, progress };
1729
+ }
1730
+ const adapter = {
1731
+ creation: "idempotent",
1732
+ sendReplay: "idempotent",
1733
+ async create(command, signal) {
1734
+ signal.throwIfAborted();
1735
+ if (command.workspaceId && command.workspaceId !== workspaceId) {
1736
+ throw new SessionRunnerError("workspace_unavailable", "The requested workspace is not this runner's approved directory.");
1737
+ }
1738
+ const id = sessionIdForCommand(command.commandId);
1739
+ const created = await ctx.session.create({
1740
+ id,
1741
+ title: command.title,
1742
+ location: { directory },
1743
+ metadata: { openwork: { commandId: command.commandId } },
1744
+ ...command.model ? { model: nativeModel(command.model) } : {}
1745
+ }, { signal });
1746
+ const owner = created.metadata?.openwork;
1747
+ let actualDirectory = null;
1748
+ try {
1749
+ actualDirectory = await realpath(created.location.directory);
1750
+ } catch {}
1751
+ if (created.id !== id || actualDirectory !== directory || !isRecord(owner) || owner.commandId !== command.commandId) {
1752
+ throw new SessionRunnerError("session_conflict", "The deterministic OpenCode session identity belongs to different work or a different Location.");
1753
+ }
1754
+ return { sessionId: created.id, workspaceId };
1755
+ },
1756
+ async send(receipt, prompt, signal) {
1757
+ const info = await owned(receipt, signal);
1758
+ const [messages, previous] = await Promise.all([
1759
+ ctx.session.context({ sessionID: receipt.sessionId }, { signal }),
1760
+ load(receipt)
1761
+ ]);
1762
+ signal.throwIfAborted();
1763
+ if (messages.some((message2) => message2.id === prompt.messageId) || previous?.messageId === prompt.messageId && previous.admitted) {
1764
+ return { messageId: prompt.messageId, alreadyPresent: true };
1765
+ }
1766
+ const marker = previous?.messageId === prompt.messageId ? previous : { messageId: prompt.messageId, baselineIdle: info.time.idle ?? null, admitted: false };
1767
+ await save(receipt, marker);
1768
+ signal.throwIfAborted();
1769
+ if (prompt.model)
1770
+ await ctx.session.switchModel({ sessionID: receipt.sessionId, model: nativeModel(prompt.model) }, { signal });
1771
+ signal.throwIfAborted();
1772
+ const admitted = await ctx.session.prompt({
1773
+ sessionID: receipt.sessionId,
1774
+ id: prompt.messageId,
1775
+ text: prompt.prompt,
1776
+ delivery: "queue",
1777
+ resume: true
1778
+ }, { signal });
1779
+ if (admitted.id !== prompt.messageId || admitted.sessionID !== receipt.sessionId) {
1780
+ throw new SessionRunnerError("invalid_data", "OpenCode returned a different prompt admission identity.");
1781
+ }
1782
+ await save(receipt, { ...marker, admitted: true });
1783
+ return { messageId: admitted.id, alreadyPresent: false };
1784
+ },
1785
+ async observe(receipt, signal) {
1786
+ return (await snapshot(receipt, signal)).progress;
1787
+ },
1788
+ async stop(receipt, guard, signal) {
1789
+ const { latestUser, progress } = await snapshot(receipt, signal);
1790
+ if (guard.messageId && latestUser?.id !== guard.messageId)
1791
+ return { stopped: false, reason: "different_turn" };
1792
+ if (progress.status === "idle" || progress.status === "error")
1793
+ return { stopped: false, reason: null };
1794
+ signal.throwIfAborted();
1795
+ const result = await ctx.session.interrupt({ sessionID: receipt.sessionId, resume: false }, { signal });
1796
+ return { stopped: result.interrupted, reason: null };
1797
+ },
1798
+ async read(receipt, page, signal) {
1799
+ const { info, messages, progress } = await snapshot(receipt, signal);
1800
+ const transcript = messages.filter((message2) => message2.type === "user" || message2.type === "assistant").map(transcriptMessage);
1801
+ const epoch = digest2(messages.findLast((message2) => message2.type === "compaction")?.id ?? "initial");
1802
+ let edge = page.from === "start" ? 0 : transcript.length;
1803
+ if (page.cursor !== null) {
1804
+ const match = /^oc2:(start|end):([a-f0-9]{24}):([a-f0-9]{24})$/.exec(page.cursor);
1805
+ const anchor2 = match ? transcript.findIndex((message2) => digest2(message2.id) === match[3]) : -1;
1806
+ if (!match || match[1] !== page.from || match[2] !== epoch || anchor2 < 0) {
1807
+ throw new SessionRunnerError("cursor_unavailable", "This cursor is unknown or its messages were compacted. Read again without a cursor; only recent context is available.");
1808
+ }
1809
+ edge = page.from === "start" ? anchor2 + 1 : anchor2;
1810
+ }
1811
+ const start = page.from === "start" ? edge : Math.max(0, edge - page.limit);
1812
+ const end = page.from === "start" ? Math.min(transcript.length, edge + page.limit) : edge;
1813
+ const selected = transcript.slice(start, end);
1814
+ boundTranscript(selected);
1815
+ const anchor = page.from === "start" ? selected.at(-1) : selected[0];
1816
+ const more = page.from === "start" ? end < transcript.length : start > 0;
1817
+ return {
1818
+ title: info.title ? clipped(info.title, 240) : null,
1819
+ status: progress.status,
1820
+ waitingFor: progress.waitingFor ?? null,
1821
+ lastError: progress.error ?? null,
1822
+ historyScope: "context",
1823
+ messageCount: transcript.length,
1824
+ from: page.from,
1825
+ messages: selected,
1826
+ nextCursor: more && anchor ? `oc2:${page.from}:${epoch}:${digest2(anchor.id)}` : null
1827
+ };
1828
+ }
1829
+ };
1830
+ return adapter;
1831
+ }
1832
+ function toolCall2(part) {
1833
+ const state = part.state;
1834
+ const rawInput = typeof state.input === "string" ? state.input : JSON.stringify(state.input);
1835
+ const rawOutput = "content" in state ? JSON.stringify(state.content) : undefined;
1836
+ const rawError = "error" in state ? state.error.message ?? state.error.type : undefined;
1837
+ return {
1838
+ id: clipped(part.id, 240),
1839
+ name: clipped(part.name, 240),
1840
+ status: state.status,
1841
+ input: summary(state.input),
1842
+ output: rawOutput === undefined ? null : clipped(rawOutput, 2000),
1843
+ error: rawError === undefined ? null : clipped(rawError, 2000),
1844
+ truncated: rawInput.length > 2000 || (rawOutput?.length ?? 0) > 2000 || (rawError?.length ?? 0) > 2000
1845
+ };
1846
+ }
1847
+ function transcriptMessage(message2) {
1848
+ const text2 = message2.type === "user" ? message2.text : message2.type === "assistant" ? message2.content.filter((part) => part.type === "text").map((part) => part.text).join(`
1849
+ `) : "";
1850
+ const tools = message2.type === "assistant" ? message2.content.filter((part) => part.type === "tool").map(toolCall2) : [];
1851
+ return {
1852
+ id: clipped(message2.id, 240),
1853
+ role: message2.type === "user" ? "user" : "assistant",
1854
+ createdAt: message2.time.created,
1855
+ text: clipped(text2, 20000),
1856
+ truncated: text2.length > 20000 || tools.length > 200,
1857
+ toolCalls: tools.slice(0, 200),
1858
+ error: message2.type === "assistant" ? errorOf(message2.error) : null
1859
+ };
1860
+ }
1861
+ function boundTranscript(messages) {
1862
+ while (Buffer.byteLength(JSON.stringify(messages), "utf8") > 220000) {
1863
+ let reduced = false;
1864
+ for (const message2 of messages) {
1865
+ if (message2.text.length > 256) {
1866
+ message2.text = message2.text.slice(0, Math.floor(message2.text.length / 2));
1867
+ message2.truncated = true;
1868
+ reduced = true;
1869
+ }
1870
+ if (message2.toolCalls.length > 4) {
1871
+ message2.toolCalls = message2.toolCalls.slice(0, Math.ceil(message2.toolCalls.length / 2));
1872
+ message2.truncated = true;
1873
+ reduced = true;
1874
+ }
1875
+ for (const tool of message2.toolCalls) {
1876
+ for (const key of ["input", "output", "error"]) {
1877
+ const value = tool[key];
1878
+ if (value && value.length > 256) {
1879
+ tool[key] = value.slice(0, Math.floor(value.length / 2));
1880
+ tool.truncated = true;
1881
+ reduced = true;
1882
+ }
1883
+ }
1884
+ }
1885
+ }
1886
+ if (!reduced)
1887
+ throw new SessionRunnerError("transcript_too_large", "The bounded recent context page is still too large. Request fewer messages.");
1888
+ }
1889
+ }
1890
+ var init_native_opencode = __esm(() => {
1891
+ init_src();
1892
+ init_den();
1893
+ });
1894
+
1895
+ // ../../packages/opencode-plugin/src/runner-ownership.ts
1896
+ import { createHash as createHash5, randomUUID } from "node:crypto";
1897
+ import { mkdir, readdir, readFile, rmdir, unlink, writeFile } from "node:fs/promises";
1898
+ import { homedir } from "node:os";
1899
+ import { join } from "node:path";
1900
+ function alive(pid) {
1901
+ try {
1902
+ process.kill(pid, 0);
1903
+ return true;
1904
+ } catch (error) {
1905
+ return !(isRecord(error) && error.code === "ESRCH");
1906
+ }
1907
+ }
1908
+ async function acquireRunnerOwnership(directory, root = join(homedir(), ".local", "share", "opencode", "openwork-session-runner-locks")) {
1909
+ const key = createHash5("sha256").update(directory).digest("hex");
1910
+ const owners = globalThis.__openworkNativeRunnerOwners ??= new Map;
1911
+ if (owners.has(key))
1912
+ return null;
1913
+ const owner = Symbol(key);
1914
+ owners.set(key, owner);
1915
+ const lock = join(root, key);
1916
+ const marker = `${process.pid}-${randomUUID()}.json`;
1917
+ let acquired = false;
1918
+ let ready = false;
1919
+ try {
1920
+ await mkdir(root, { recursive: true, mode: 448 });
1921
+ for (let attempt = 0;attempt < 2; attempt++) {
1922
+ try {
1923
+ await mkdir(lock, { mode: 448 });
1924
+ acquired = true;
1925
+ break;
1926
+ } catch (error) {
1927
+ if (!isRecord(error) || error.code !== "EEXIST")
1928
+ throw error;
1929
+ const entries = await readdir(lock);
1930
+ if (entries.length !== 1 || !/^\d+-[a-f0-9-]+\.json$/.test(entries[0] ?? ""))
1931
+ return null;
1932
+ const file = entries[0];
1933
+ const value = JSON.parse(await readFile(join(lock, file), "utf8"));
1934
+ if (!isRecord(value) || !Number.isSafeInteger(value.pid) || typeof value.pid !== "number" || value.pid < 1 || String(value.pid) !== file.split("-")[0] || alive(value.pid))
1935
+ return null;
1936
+ try {
1937
+ await unlink(join(lock, file));
1938
+ await rmdir(lock);
1939
+ } catch {
1940
+ return null;
1941
+ }
1942
+ }
1943
+ }
1944
+ if (!acquired)
1945
+ return null;
1946
+ await writeFile(join(lock, marker), JSON.stringify({ pid: process.pid }), { flag: "wx", mode: 384 });
1947
+ ready = true;
1948
+ let released = false;
1949
+ return {
1950
+ async release() {
1951
+ if (released)
1952
+ return;
1953
+ released = true;
1954
+ await unlink(join(lock, marker)).catch(() => {});
1955
+ await rmdir(lock).catch(() => {});
1956
+ if (owners.get(key) === owner)
1957
+ owners.delete(key);
1958
+ }
1959
+ };
1960
+ } finally {
1961
+ if (!ready) {
1962
+ if (acquired)
1963
+ await rmdir(lock).catch(() => {});
1964
+ if (owners.get(key) === owner)
1965
+ owners.delete(key);
1966
+ }
1967
+ }
1968
+ }
1969
+ var init_runner_ownership = __esm(() => {
1970
+ init_den();
1971
+ });
1972
+
1973
+ // ../../packages/opencode-plugin/src/remote-runner.ts
1974
+ var exports_remote_runner = {};
1975
+ __export(exports_remote_runner, {
1976
+ hasNativeSessionHost: () => hasNativeSessionHost,
1977
+ createRemoteRunnerController: () => createRemoteRunnerController
1978
+ });
1979
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
1980
+ import { realpath as realpath2 } from "node:fs/promises";
1981
+ import { hostname } from "node:os";
1982
+ function hash(value) {
1983
+ return createHash6("sha256").update(value).digest("hex").slice(0, 32);
1984
+ }
1985
+ function hostRead(read, signal) {
1986
+ const bounded2 = AbortSignal.any([signal, AbortSignal.timeout(1e4)]);
1987
+ bounded2.throwIfAborted();
1988
+ return new Promise((resolve, reject) => {
1989
+ const abort = () => reject(bounded2.reason);
1990
+ bounded2.addEventListener("abort", abort, { once: true });
1991
+ Promise.resolve().then(() => {
1992
+ bounded2.throwIfAborted();
1993
+ return read();
1994
+ }).then(resolve, reject).finally(() => bounded2.removeEventListener("abort", abort));
1995
+ });
1996
+ }
1997
+ function platform() {
1998
+ if (process.platform !== "darwin" && process.platform !== "linux" && process.platform !== "win32") {
1999
+ throw new Error("Remote OpenCode sessions require macOS, Linux, or Windows.");
2000
+ }
2001
+ return process.platform;
2002
+ }
2003
+ function createRemoteRunnerController(input) {
2004
+ const { ctx } = input;
2005
+ const now = input.now ?? Date.now;
2006
+ const random = input.random ?? Math.random;
2007
+ const fetcher = input.fetch ?? ((url, init) => fetch(url, init));
2008
+ const schedule = input.schedule ?? ((callback, delay2) => {
2009
+ const timer = setTimeout(callback, delay2);
2010
+ timer.unref?.();
2011
+ return () => clearTimeout(timer);
2012
+ });
2013
+ let disposed = false;
2014
+ let started = false;
2015
+ let active = null;
2016
+ let verifiedMember = null;
2017
+ let ownership = null;
2018
+ let directory = null;
2019
+ let cancelTimer = null;
2020
+ let operation = null;
2021
+ let flight = null;
2022
+ let queued = false;
2023
+ let failures = 0;
2024
+ let phase = "credentials";
2025
+ let status2 = { state: "waiting", phase, failures: 0, observedAt: now(), error: null };
2026
+ const statusKey = `remoteSessions/status/${hash(ctx.location.directory)}`;
2027
+ const recordStatus = async (state, error = null) => {
2028
+ status2 = { state, phase, failures, observedAt: now(), error };
2029
+ await hostRead(() => ctx.storage.set(statusKey, { ...status2 }), AbortSignal.timeout(1000)).catch(() => {});
2030
+ };
2031
+ async function account(signal) {
2032
+ const connection = await hostRead(() => ctx.integration.connection.active(INTEGRATION_ID), signal);
2033
+ signal.throwIfAborted();
2034
+ if (!connection || connection.type !== "credential" || connection.status?.status === "needs_auth")
2035
+ return null;
2036
+ const credential = await hostRead(() => ctx.integration.connection.resolve(connection), signal);
2037
+ signal.throwIfAborted();
2038
+ if (!credential || credential.type !== "oauth" || !Number.isFinite(credential.expires) || credential.expires <= now())
2039
+ return null;
2040
+ const metadata = readCredentialMetadata(credential, input.apiBaseUrl);
2041
+ if (!metadata.orgId || !isAllowedApiBaseUrl(metadata.apiBaseUrl))
2042
+ return null;
2043
+ const session = { apiBaseUrl: metadata.apiBaseUrl, orgId: metadata.orgId, token: credential.access };
2044
+ const key = hash(JSON.stringify([session.apiBaseUrl, session.orgId, session.token]));
2045
+ if (verifiedMember?.key !== key) {
2046
+ verifiedMember = null;
2047
+ const me = await hostRead(() => readMe((url, init) => fetcher(url, {
2048
+ ...init,
2049
+ signal: init?.signal ? AbortSignal.any([signal, init.signal]) : signal
2050
+ }), session), signal);
2051
+ signal.throwIfAborted();
2052
+ if (!me.userId)
2053
+ throw new Error("OpenWork returned an invalid authenticated member.");
2054
+ verifiedMember = { key, userId: me.userId };
2055
+ }
2056
+ return {
2057
+ identity: hash(JSON.stringify([session.apiBaseUrl, session.orgId, verifiedMember.userId, connection.id])),
2058
+ session,
2059
+ credentialExpiresAt: credential.expires
2060
+ };
2061
+ }
2062
+ async function initialize(value, signal) {
2063
+ directory ??= await realpath2(ctx.location.directory);
2064
+ signal.throwIfAborted();
2065
+ ownership ??= await (input.acquireOwnership ?? acquireRunnerOwnership)(directory);
2066
+ if (!ownership)
2067
+ return null;
2068
+ signal.throwIfAborted();
2069
+ const directoryKey = hash(directory);
2070
+ let installation = await ctx.storage.get("remoteSessions/installation");
2071
+ if (typeof installation !== "string" || !installation) {
2072
+ installation = randomUUID2();
2073
+ await ctx.storage.set("remoteSessions/installation", installation);
2074
+ }
2075
+ const identityKey = `remoteSessions/runner/${hash(JSON.stringify([directoryKey, value.identity]))}`;
2076
+ let runnerId = await ctx.storage.get(identityKey);
2077
+ if (typeof runnerId !== "string" || !/^oc_runner_[a-f0-9]{32}$/.test(runnerId)) {
2078
+ runnerId = `oc_runner_${hash(JSON.stringify([installation, directory, value.identity]))}`;
2079
+ await ctx.storage.set(identityKey, runnerId);
2080
+ }
2081
+ if (typeof runnerId !== "string")
2082
+ throw new Error("Runner identity is unavailable.");
2083
+ const workspaceId = `oc_workspace_${directoryKey}`;
2084
+ const prefix = `remoteSessions/${hash(JSON.stringify([value.identity, runnerId, directory]))}`;
2085
+ const journalKey = `${prefix}/journal`;
2086
+ const native = createNativeOpenCodeAdapter({ ctx, directory, workspaceId, storagePrefix: prefix, now });
2087
+ const state = {
2088
+ account: value,
2089
+ runnerId,
2090
+ workspaceId,
2091
+ runner: createSessionRunner({
2092
+ adapter: native,
2093
+ store: {
2094
+ async load() {
2095
+ const stored = await ctx.storage.get(journalKey);
2096
+ return stored === undefined ? emptyJournal() : parseJournal(stored);
2097
+ },
2098
+ async save(journal) {
2099
+ await ctx.storage.set(journalKey, JSON.parse(JSON.stringify(journal)));
2100
+ }
2101
+ },
2102
+ transport: createRemoteSessionTransport({
2103
+ baseUrl: value.session.apiBaseUrl,
2104
+ token: () => disposed || active !== state || state.account.credentialExpiresAt <= now() || state.tokenExpiresAt <= now() ? null : state.token,
2105
+ fetch: (url, init) => fetcher(String(url), init),
2106
+ timeoutMs: 1e4
2107
+ }),
2108
+ now
2109
+ }),
2110
+ token: null,
2111
+ tokenExpiresAt: 0,
2112
+ registeredAt: 0,
2113
+ inventoryAt: 0
2114
+ };
2115
+ await state.runner.inspect();
2116
+ signal.throwIfAborted();
2117
+ return state;
2118
+ }
2119
+ async function register(state, signal) {
2120
+ const response = await hostRead(() => denRequest(fetcher, state.account.session, "/v1/session-runners/token", {
2121
+ method: "POST",
2122
+ signal,
2123
+ body: {
2124
+ runnerId: state.runnerId,
2125
+ protocolVersion: 1,
2126
+ supportedExecutionTargets: ["desktop"],
2127
+ capabilities: CAPABILITIES,
2128
+ platform: platform(),
2129
+ appVersion: ctx.app.version,
2130
+ concurrency: 1
2131
+ }
2132
+ }), signal);
2133
+ signal.throwIfAborted();
2134
+ if (!isRecord(response) || typeof response.token !== "string" || response.token.length < 32 || response.token.length > 512 || /[\r\n]/.test(response.token) || typeof response.expiresAt !== "number" || !Number.isFinite(response.expiresAt) || response.expiresAt <= now()) {
2135
+ throw new Error("OpenWork returned an invalid session-runner registration.");
2136
+ }
2137
+ state.token = response.token;
2138
+ state.tokenExpiresAt = response.expiresAt;
2139
+ state.registeredAt = now();
2140
+ state.inventoryAt = 0;
2141
+ }
2142
+ async function runnerRequest(state, path, signal, body) {
2143
+ const token = state.token;
2144
+ if (!token || state.tokenExpiresAt <= now())
2145
+ throw new RemoteSessionHttpError(401);
2146
+ return hostRead(() => denRequest(fetcher, { ...state.account.session, token }, path, { signal, ...body === undefined ? {} : { method: "PUT", body } }), signal);
2147
+ }
2148
+ async function cycle(signal) {
2149
+ phase = "credentials";
2150
+ let value;
2151
+ try {
2152
+ value = await account(signal);
2153
+ } catch (error) {
2154
+ if (!signal.aborted)
2155
+ active = null;
2156
+ throw error;
2157
+ }
2158
+ if (!value) {
2159
+ active = null;
2160
+ return;
2161
+ }
2162
+ signal = AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, Math.min(2147483647, value.credentialExpiresAt - now())))]);
2163
+ if (active && active.account.identity !== value.identity)
2164
+ active = null;
2165
+ if (!active)
2166
+ active = await initialize(value, signal);
2167
+ const state = active;
2168
+ if (!state)
2169
+ return;
2170
+ state.account = value;
2171
+ signal.throwIfAborted();
2172
+ if (!state.token || state.tokenExpiresAt <= now() + 2 * 60000 || now() - state.registeredAt >= RENEW_MS) {
2173
+ phase = "registration";
2174
+ await register(state, signal);
2175
+ }
2176
+ const transport = createRemoteSessionTransport({
2177
+ baseUrl: value.session.apiBaseUrl,
2178
+ token: () => state.token,
2179
+ fetch: (url, init) => fetcher(String(url), init),
2180
+ timeoutMs: 1e4
2181
+ });
2182
+ if (state.inventoryAt === 0 || now() - state.inventoryAt >= INVENTORY_MS) {
2183
+ phase = "inventory";
2184
+ const workspace = await hostRead(() => nativeWorkspace(ctx, state.workspaceId, signal), signal);
2185
+ const published = await runnerRequest(state, "/v1/session-runners/inventory", signal, {
2186
+ computer: { label: (input.label?.trim() || hostname() || "OpenCode").slice(0, 120), platform: platform(), appVersion: ctx.app.version },
2187
+ workspaces: [workspace]
2188
+ });
2189
+ signal.throwIfAborted();
2190
+ if (!isRecord(published) || published.ok !== true || typeof published.updatedAt !== "number" || !Number.isFinite(published.updatedAt)) {
2191
+ throw new Error("OpenWork returned an invalid inventory acknowledgement.");
2192
+ }
2193
+ state.inventoryAt = now();
2194
+ }
2195
+ phase = "recovery";
2196
+ await state.runner.reconcile(signal);
2197
+ const journal = await state.runner.inspect();
2198
+ if (journal.commands.length >= 200 || journal.requests.length >= 200) {
2199
+ await state.runner.prune({ retainCommands: 128, retainRequests: 128 });
2200
+ }
2201
+ phase = "work";
2202
+ const response = await runnerRequest(state, "/v1/session-runners/work", signal);
2203
+ signal.throwIfAborted();
2204
+ if (!isRecord(response) || !Array.isArray(response.items) || response.items.length > 10)
2205
+ throw new Error("Invalid session-runner work response.");
2206
+ for (const item of response.items) {
2207
+ signal.throwIfAborted();
2208
+ if (!isRecord(item))
2209
+ throw new Error("Invalid session-runner work item.");
2210
+ try {
2211
+ if (item.kind === "remote_session_create" && typeof item.commandId === "string") {
2212
+ await state.runner.accept(await transport.claimCommand(item.commandId, signal), signal);
2213
+ } else if (item.kind === "remote_session_request" && typeof item.requestId === "string") {
2214
+ await state.runner.execute(await transport.claimRequest(item.requestId, signal), signal);
2215
+ } else {
2216
+ throw new Error("Unexpected session-runner work kind.");
2217
+ }
2218
+ } catch (error) {
2219
+ if (error instanceof RemoteSessionHttpError && error.status === 409)
2220
+ continue;
2221
+ throw error;
2222
+ }
2223
+ }
2224
+ await state.runner.reconcile(signal);
2225
+ }
2226
+ function poll() {
2227
+ if (disposed)
2228
+ return Promise.resolve();
2229
+ if (flight) {
2230
+ queued = true;
2231
+ return flight;
2232
+ }
2233
+ cancelTimer?.();
2234
+ cancelTimer = null;
2235
+ const controller = new AbortController;
2236
+ operation = controller;
2237
+ flight = cycle(controller.signal).then(async () => {
2238
+ failures = 0;
2239
+ await recordStatus(active ? "connected" : "waiting");
2240
+ }).catch(async (error) => {
2241
+ if (!controller.signal.aborted) {
2242
+ failures++;
2243
+ if (error instanceof DenAuthError || error instanceof RemoteSessionHttpError && (error.status === 401 || error.status === 403) || error instanceof DenRequestError && (error.status === 403 || error.status === 404))
2244
+ active = null;
2245
+ await recordStatus("retrying", `${phase}_failed`);
2246
+ }
2247
+ }).finally(() => {
2248
+ flight = null;
2249
+ if (operation === controller)
2250
+ operation = null;
2251
+ if (disposed || !started)
2252
+ return;
2253
+ const delay2 = queued ? 0 : Math.round((failures ? Math.min(30000, POLL_MS * 2 ** Math.min(failures, 4)) : POLL_MS) * (0.8 + random() * 0.4));
2254
+ queued = false;
2255
+ cancelTimer = schedule(() => {
2256
+ poll();
2257
+ }, delay2);
2258
+ });
2259
+ return flight;
2260
+ }
2261
+ return {
2262
+ inspectStatus() {
2263
+ return { ...status2 };
2264
+ },
2265
+ start() {
2266
+ if (started || disposed)
2267
+ return;
2268
+ started = true;
2269
+ poll();
2270
+ },
2271
+ poll,
2272
+ credentialsChanged() {
2273
+ if (disposed)
2274
+ return;
2275
+ operation?.abort(new Error("OpenWork credentials changed."));
2276
+ queued = true;
2277
+ if (!flight)
2278
+ poll();
2279
+ },
2280
+ async close() {
2281
+ if (disposed)
2282
+ return;
2283
+ disposed = true;
2284
+ started = false;
2285
+ cancelTimer?.();
2286
+ operation?.abort(new Error("The OpenWork plugin unloaded."));
2287
+ await flight;
2288
+ active = null;
2289
+ await ownership?.release();
2290
+ ownership = null;
2291
+ await recordStatus("closed");
2292
+ }
2293
+ };
2294
+ }
2295
+ var CAPABILITIES, RENEW_MS, INVENTORY_MS = 60000, POLL_MS = 3000;
2296
+ var init_remote_runner = __esm(() => {
2297
+ init_src();
2298
+ init_auth();
2299
+ init_den();
2300
+ init_native_opencode();
2301
+ init_native_opencode();
2302
+ init_runner_ownership();
2303
+ CAPABILITIES = ["remote_session_v1", "remote_session_control_v1", "remote_session_only_v1", "remote_session_recovery_v1"];
2304
+ RENEW_MS = 30 * 60000;
2305
+ });
2306
+
2307
+ // ../../packages/opencode-plugin/src/plugin.ts
2308
+ import { createHash as createHash7 } from "node:crypto";
2309
+
2310
+ // ../../packages/opencode-plugin/src/desktop.ts
2311
+ import { createHash as createHash2 } from "node:crypto";
2312
+
2313
+ // ../../packages/opencode-plugin/src/desktop-credential-channel.ts
2314
+ import { createRequire } from "node:module";
2315
+ function isBroker(value) {
2316
+ return typeof value === "object" && value !== null && "initialize" in value && typeof value.initialize === "function" && "fetchSession" in value && typeof value.fetchSession === "function" && "fetchInventory" in value && typeof value.fetchInventory === "function";
2317
+ }
2318
+ var filename = process.env.OPENWORK_PROVIDER_SESSION_BROKER;
2319
+ var loaded = filename ? createRequire(import.meta.url)(filename) : null;
2320
+ if (loaded !== null && !isBroker(loaded))
2321
+ throw new Error("Invalid desktop credential broker");
2322
+ var broker = isBroker(loaded) ? loaded : null;
2323
+ async function initializeDesktopCredentialChannel() {
2324
+ await broker?.initialize();
2325
+ }
2326
+ async function fetchDesktopSession(fetcher, endpoint, signal) {
2327
+ return broker ? broker.fetchSession(fetcher, endpoint, signal) : null;
2328
+ }
2329
+ async function fetchDesktopInventory(fetcher, endpoint, path, identity, signal) {
2330
+ return broker ? broker.fetchInventory(fetcher, endpoint, path, identity, signal) : null;
2331
+ }
2332
+
2333
+ // ../../packages/opencode-plugin/src/desktop.ts
2334
+ init_den();
2335
+
2336
+ // ../../packages/opencode-plugin/src/inventory-events.ts
2337
+ init_den();
2338
+ import { setTimeout as delay } from "node:timers/promises";
2339
+ import { createHash } from "node:crypto";
2340
+ var INVENTORY_EVENTS_PATH = "/v1/inference-providers/inventory/events";
2341
+ function inventorySessionKey(session) {
2342
+ return createHash("sha256").update(JSON.stringify([normalizeBaseUrl(session.apiBaseUrl), session.orgId, session.token])).digest("hex");
2343
+ }
2344
+ function joinSharedInventory(shared, options) {
2345
+ if (options.signal.aborted)
2346
+ return Promise.resolve();
2347
+ const key = inventorySessionKey(options.identity);
2348
+ let entry = shared.get(key);
2349
+ const first = !entry;
2350
+ if (!entry) {
2351
+ entry = { controller: new AbortController, listeners: new Map, received: false, denied: null };
2352
+ shared.set(key, entry);
2353
+ }
2354
+ const current = entry;
2355
+ const id = Symbol();
2356
+ current.listeners.set(id, options);
2357
+ const result = new Promise((resolve) => {
2358
+ const release = () => {
2359
+ options.signal.removeEventListener("abort", release);
2360
+ current.listeners.delete(id);
2361
+ if (!current.listeners.size) {
2362
+ if (shared.get(key) === current)
2363
+ shared.delete(key);
2364
+ current.controller.abort();
2365
+ }
2366
+ resolve();
2367
+ };
2368
+ options.signal.addEventListener("abort", release, { once: true });
2369
+ });
2370
+ if (first) {
2371
+ subscribeInventory({
2372
+ fetch: options.fetch,
2373
+ heartbeatMs: options.heartbeatMs,
2374
+ retryMs: options.retryMs,
2375
+ signal: current.controller.signal,
2376
+ session: async () => {
2377
+ for (const listener of current.listeners.values()) {
2378
+ if (listener.signal.aborted)
2379
+ continue;
2380
+ const session = await listener.session().catch(() => null);
2381
+ if (!listener.signal.aborted && session && inventorySessionKey(session) === key)
2382
+ return session;
2383
+ }
2384
+ return null;
2385
+ },
2386
+ unauthorized: () => {
2387
+ current.received = false;
2388
+ current.denied = "session";
2389
+ for (const listener of current.listeners.values()) {
2390
+ if (!listener.signal.aborted) {
2391
+ try {
2392
+ listener.unauthorized?.();
2393
+ } catch {}
2394
+ }
2395
+ }
2396
+ },
2397
+ revoked: () => {
2398
+ current.received = false;
2399
+ current.denied = "organization";
2400
+ for (const listener of current.listeners.values()) {
2401
+ if (!listener.signal.aborted) {
2402
+ try {
2403
+ (listener.revoked ?? listener.unauthorized)?.();
2404
+ } catch {}
2405
+ }
2406
+ }
2407
+ },
2408
+ changed: () => {
2409
+ current.denied = null;
2410
+ current.received = true;
2411
+ for (const listener of current.listeners.values()) {
2412
+ if (!listener.signal.aborted) {
2413
+ try {
2414
+ listener.changed();
2415
+ } catch {}
2416
+ }
2417
+ }
2418
+ }
2419
+ });
2420
+ } else {
2421
+ queueMicrotask(() => {
2422
+ if (options.signal.aborted)
2423
+ return;
2424
+ if (current.denied === "organization")
2425
+ (options.revoked ?? options.unauthorized)?.();
2426
+ else if (current.denied === "session")
2427
+ options.unauthorized?.();
2428
+ else if (current.received)
2429
+ options.changed();
2430
+ });
2431
+ }
2432
+ return result;
2433
+ }
2434
+ function createInventoryHub() {
2435
+ const shared = new Map;
2436
+ const join = (options) => joinSharedInventory(shared, options);
2437
+ return Object.freeze({ join: Object.freeze(join) });
2438
+ }
2439
+ function subscribeSharedInventory(options) {
2440
+ if (options.signal.aborted)
2441
+ return Promise.resolve();
2442
+ let hub = globalThis.__openworkInventorySubscriptions;
2443
+ if (!hub) {
2444
+ hub = createInventoryHub();
2445
+ Object.defineProperty(globalThis, "__openworkInventorySubscriptions", {
2446
+ value: hub,
2447
+ writable: false,
2448
+ configurable: false,
2449
+ enumerable: false
2450
+ });
2451
+ }
2452
+ return hub.join(options);
2453
+ }
2454
+
2455
+ class SseParser {
2456
+ buffer = "";
2457
+ event = "";
2458
+ data = [];
2459
+ receive;
2460
+ constructor(receive) {
2461
+ this.receive = receive;
2462
+ }
2463
+ push(text) {
2464
+ this.buffer += text;
2465
+ if (this.buffer.length > 1024 * 1024)
2466
+ throw new Error("SSE frame too large");
2467
+ while (true) {
2468
+ const match = /[\r\n]/.exec(this.buffer);
2469
+ if (!match)
2470
+ return;
2471
+ const index = match.index;
2472
+ const ending = this.buffer[index];
2473
+ if (ending === "\r" && index === this.buffer.length - 1)
2474
+ return;
2475
+ const line = this.buffer.slice(0, index);
2476
+ this.buffer = this.buffer.slice(index + (ending === "\r" && this.buffer[index + 1] === `
2477
+ ` ? 2 : 1));
2478
+ if (!line) {
2479
+ if (this.data.length)
2480
+ this.receive({ event: this.event || "message", data: this.data.join(`
2481
+ `) });
2482
+ this.event = "";
2483
+ this.data = [];
2484
+ } else if (!line.startsWith(":")) {
2485
+ const colon = line.indexOf(":");
2486
+ const field = colon < 0 ? line : line.slice(0, colon);
2487
+ let value = colon < 0 ? "" : line.slice(colon + 1);
2488
+ if (value.startsWith(" "))
2489
+ value = value.slice(1);
2490
+ if (field === "event")
2491
+ this.event = value;
2492
+ if (field === "data")
2493
+ this.data.push(value);
2494
+ if (this.data.reduce((size, entry) => size + entry.length, 0) > 1024 * 1024)
2495
+ throw new Error("SSE event too large");
2496
+ }
2497
+ }
2498
+ }
2499
+ }
2500
+ async function organizationNotFound(response, signal) {
2501
+ if (!response.body)
2502
+ return false;
2503
+ const reader = response.body.getReader();
2504
+ const cancel = () => {
2505
+ reader.cancel().catch(() => {});
2506
+ };
2507
+ signal.addEventListener("abort", cancel, { once: true });
2508
+ try {
2509
+ const decoder = new TextDecoder;
2510
+ let text = "";
2511
+ let size = 0;
2512
+ while (!signal.aborted) {
2513
+ const chunk = await reader.read();
2514
+ if (chunk.done) {
2515
+ text += decoder.decode();
2516
+ const value = JSON.parse(text);
2517
+ return isRecord(value) && value.error === "organization_not_found";
2518
+ }
2519
+ size += chunk.value.byteLength;
2520
+ if (size > 16 * 1024)
2521
+ return false;
2522
+ text += decoder.decode(chunk.value, { stream: true });
2523
+ }
2524
+ return false;
2525
+ } catch {
2526
+ return false;
2527
+ } finally {
2528
+ signal.removeEventListener("abort", cancel);
2529
+ await reader.cancel().catch(() => {});
2530
+ reader.releaseLock();
2531
+ }
2532
+ }
2533
+ function subscribeInventory(options) {
2534
+ return (async () => {
2535
+ let failures = 0;
2536
+ while (!options.signal.aborted) {
2537
+ const connection = new AbortController;
2538
+ const signal = AbortSignal.any([options.signal, connection.signal]);
2539
+ let watchdog;
2540
+ const touch = () => {
2541
+ clearTimeout(watchdog);
2542
+ watchdog = setTimeout(() => connection.abort(), options.heartbeatMs ?? 45000);
2543
+ watchdog.unref?.();
2544
+ };
2545
+ try {
2546
+ const session = await options.session();
2547
+ if (!session || signal.aborted)
2548
+ return;
2549
+ if (!isAllowedApiBaseUrl(session.apiBaseUrl))
2550
+ return;
2551
+ touch();
2552
+ const response = await options.fetch(`${normalizeBaseUrl(session.apiBaseUrl)}${INVENTORY_EVENTS_PATH}`, {
2553
+ headers: {
2554
+ accept: "text/event-stream",
2555
+ authorization: `Bearer ${session.token}`,
2556
+ ...session.orgId ? { "x-openwork-org-id": session.orgId, "x-openwork-legacy-org-id": session.orgId } : {}
2557
+ },
2558
+ redirect: "error",
2559
+ signal
2560
+ });
2561
+ if (response.status === 404) {
2562
+ if (!await organizationNotFound(response, signal))
2563
+ return;
2564
+ if (signal.aborted)
2565
+ return;
2566
+ const revoked = options.revoked ?? options.unauthorized;
2567
+ revoked?.();
2568
+ throw new Error("Inventory organization access revoked");
2569
+ }
2570
+ if (response.status === 401) {
2571
+ await response.body?.cancel().catch(() => {});
2572
+ if (!signal.aborted)
2573
+ options.unauthorized?.();
2574
+ return;
2575
+ }
2576
+ if (response.status === 403) {
2577
+ await response.body?.cancel().catch(() => {});
2578
+ if (signal.aborted)
2579
+ return;
2580
+ const revoked = options.revoked ?? options.unauthorized;
2581
+ revoked?.();
2582
+ throw new Error("Inventory organization access revoked");
2583
+ }
2584
+ if (!response.ok || !response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
2585
+ await response.body?.cancel();
2586
+ throw new Error("Inventory subscription unavailable");
2587
+ }
2588
+ options.changed();
2589
+ const parser = new SseParser(({ event }) => {
2590
+ if (event === "inventory.changed")
2591
+ options.changed();
2592
+ });
2593
+ const reader = response.body.getReader();
2594
+ const cancel = () => {
2595
+ reader.cancel().catch(() => {});
2596
+ };
2597
+ signal.addEventListener("abort", cancel, { once: true });
2598
+ try {
2599
+ const decoder = new TextDecoder;
2600
+ while (!signal.aborted) {
2601
+ const chunk = await reader.read();
2602
+ if (chunk.done)
2603
+ break;
2604
+ touch();
2605
+ failures = 0;
2606
+ parser.push(decoder.decode(chunk.value, { stream: true }));
2607
+ }
2608
+ } finally {
2609
+ signal.removeEventListener("abort", cancel);
2610
+ await reader.cancel().catch(() => {});
2611
+ reader.releaseLock();
2612
+ }
2613
+ } catch {} finally {
2614
+ clearTimeout(watchdog);
2615
+ connection.abort();
2616
+ }
2617
+ if (options.signal.aborted)
2618
+ return;
2619
+ const base = options.retryMs ?? 1000;
2620
+ const backoff = Math.min(30000, base * 2 ** Math.min(failures++, 5));
2621
+ await delay(backoff, undefined, { signal: options.signal, ref: false }).catch(() => {});
2622
+ }
2623
+ })();
2624
+ }
2625
+
2626
+ // ../../packages/opencode-plugin/src/inventory-retry.ts
2627
+ function inventoryRetry(retry, baseMs = 1000) {
2628
+ let timer;
2629
+ let failures = 0;
2630
+ const cancel = () => {
2631
+ clearTimeout(timer);
2632
+ timer = undefined;
2633
+ };
2634
+ return {
2635
+ cancel,
2636
+ reset() {
2637
+ cancel();
2638
+ failures = 0;
2639
+ },
2640
+ schedule() {
2641
+ if (timer)
2642
+ return;
2643
+ const timeout = Math.min(30000, baseMs * 2 ** Math.min(failures++, 5));
2644
+ timer = setTimeout(() => {
2645
+ timer = undefined;
2646
+ retry();
2647
+ }, timeout);
2648
+ timer.unref?.();
2649
+ }
2650
+ };
2651
+ }
2652
+
2653
+ // ../../packages/opencode-plugin/src/providers.ts
2654
+ init_den();
2655
+ function readString2(value) {
2656
+ return typeof value === "string" && value.trim() ? value.trim() : null;
2657
+ }
2658
+ function parseModel(value) {
2659
+ if (!isRecord(value))
2660
+ return null;
2661
+ const id = readString2(value.id);
2662
+ const name = readString2(value.name);
2663
+ if (!id || !name || !isRecord(value.config))
2664
+ return null;
2665
+ return {
2666
+ id,
2667
+ name,
2668
+ config: value.config,
2669
+ upstreamModelId: readString2(value.upstreamModelId),
2670
+ modelGroupName: readString2(value.modelGroupName),
2671
+ credentialSetName: readString2(value.credentialSetName)
2672
+ };
2673
+ }
2674
+ function parseGatewayProvider(value) {
2675
+ if (!isRecord(value))
2676
+ return null;
2677
+ const id = readString2(value.id);
2678
+ const providerId = readString2(value.providerId);
2679
+ const name = readString2(value.name);
2680
+ if (!id || !/^ipr_/.test(id) || !providerId || !name || !Array.isArray(value.models))
2681
+ return null;
2682
+ const models = [];
2683
+ for (const entry of value.models) {
2684
+ const model = parseModel(entry);
2685
+ if (model)
2686
+ models.push(model);
2687
+ }
2688
+ return {
2689
+ id,
2690
+ providerId,
2691
+ name,
2692
+ credentialStatus: readString2(value.credentialStatus) ?? "ready",
2693
+ providerConfig: isRecord(value.providerConfig) ? value.providerConfig : {},
2694
+ models
2695
+ };
2696
+ }
2697
+ async function fetchGatewayInventory(fetcher, session, signal, credentialScope = "den") {
2698
+ return fetchGatewayInventoryOnce(fetcher, session, signal, false, credentialScope);
2699
+ }
2700
+ async function fetchGatewayInventoryOnce(fetcher, session, signal, relisted, credentialScope) {
2701
+ let listed;
2702
+ try {
2703
+ listed = await denRequest(fetcher, session, "/v1/inference-providers?scope=usable", { signal });
2704
+ } catch (error) {
2705
+ if (error instanceof DenRequestError && error.status === 403)
2706
+ throw new DenAuthError(403, "/v1/inference-providers");
2707
+ if (error instanceof DenRequestError && [404, 405, 501].includes(error.status)) {
2708
+ return { providers: [], apiKey: null, skipped: [] };
2709
+ }
2710
+ throw error;
2711
+ }
2712
+ const rows = isRecord(listed) && Array.isArray(listed.inferenceProviders) ? listed.inferenceProviders : [];
2713
+ const providers = [];
2714
+ const skipped = [];
2715
+ let apiKey = null;
2716
+ for (const row of rows) {
2717
+ const provider = parseGatewayProvider(row);
2718
+ if (!provider)
2719
+ continue;
2720
+ if (provider.models.length === 0) {
2721
+ skipped.push({
2722
+ id: provider.id,
2723
+ name: provider.name,
2724
+ reason: provider.credentialStatus === "member_auth_required" || provider.credentialStatus === "org_credential_missing" ? provider.credentialStatus : "no_accessible_models"
2725
+ });
2726
+ continue;
2727
+ }
2728
+ let connected;
2729
+ try {
2730
+ connected = await denRequest(fetcher, session, `/v1/inference-providers/${encodeURIComponent(provider.id)}/connect`, { signal });
2731
+ } catch (error) {
2732
+ if (!(error instanceof DenRequestError) || error.status !== 403)
2733
+ throw error;
2734
+ if (!relisted)
2735
+ return fetchGatewayInventoryOnce(fetcher, session, signal, true, credentialScope);
2736
+ skipped.push({ id: provider.id, name: provider.name, reason: "missing_credentials" });
2737
+ continue;
2738
+ }
2739
+ const detail = isRecord(connected) ? connected.inferenceProvider : null;
2740
+ const key = isRecord(detail) ? readString2(detail.apiKey) : null;
2741
+ const parsed = parseGatewayProvider(detail) ?? provider;
2742
+ const allowedKey = credentialScope === "desktop" ? key === "ow_local_catalog" : key?.startsWith("ow_gw_");
2743
+ if (!key || !allowedKey || parsed.id !== provider.id) {
2744
+ skipped.push({ id: provider.id, name: provider.name, reason: "missing_credentials" });
2745
+ continue;
2746
+ }
2747
+ apiKey = key;
2748
+ providers.push(parsed);
2749
+ }
2750
+ providers.sort((left, right) => left.id.localeCompare(right.id));
2751
+ return { providers, apiKey, skipped };
2752
+ }
2753
+ var NATIVE_PACKAGES = {
2754
+ "@ai-sdk/amazon-bedrock": "@opencode/ai/providers/amazon-bedrock",
2755
+ "@ai-sdk/anthropic": "@opencode/ai/providers/anthropic",
2756
+ "@ai-sdk/azure": "@opencode/ai/providers/azure/responses",
2757
+ "@ai-sdk/google": "@opencode/ai/providers/google",
2758
+ "@ai-sdk/google-vertex": "@opencode/ai/providers/google-vertex",
2759
+ "@ai-sdk/google-vertex/anthropic": "@opencode/ai/providers/google-vertex/messages",
2760
+ "@ai-sdk/mistral": "@opencode/ai/providers/mistral",
2761
+ "@ai-sdk/openai": "@opencode/ai/providers/openai",
2762
+ "@ai-sdk/openai-compatible": "@opencode/ai/providers/openai-compatible",
2763
+ "@ai-sdk/xai": "@opencode/ai/providers/xai",
2764
+ "@openrouter/ai-sdk-provider": "@opencode/ai/providers/openrouter"
2765
+ };
2766
+ function providerPackage(npm) {
2767
+ if (!npm)
2768
+ return "@opencode/ai/providers/openai-compatible";
2769
+ return NATIVE_PACKAGES[npm] ?? `aisdk:${npm}`;
2770
+ }
2771
+ function stringRecord(value) {
2772
+ if (!isRecord(value))
2773
+ return;
2774
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
2775
+ return entries.length ? Object.fromEntries(entries) : undefined;
2776
+ }
2777
+ function finite(value, fallback) {
2778
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
2779
+ }
2780
+ function int(value, fallback) {
2781
+ return Math.max(0, Math.trunc(finite(value, fallback)));
2782
+ }
2783
+ function costs(value) {
2784
+ if (!isRecord(value))
2785
+ return [];
2786
+ const base = {
2787
+ input: finite(value.input, 0),
2788
+ output: finite(value.output, 0),
2789
+ cache: { read: finite(value.cache_read, 0), write: finite(value.cache_write, 0) }
2790
+ };
2791
+ const result = [base];
2792
+ const over = value.context_over_200k;
2793
+ if (isRecord(over)) {
2794
+ result.push({
2795
+ tier: { type: "context", size: 200000 },
2796
+ input: finite(over.input, base.input),
2797
+ output: finite(over.output, base.output),
2798
+ cache: { read: finite(over.cache_read, base.cache.read), write: finite(over.cache_write, base.cache.write) }
2799
+ });
2800
+ }
2801
+ return result;
2802
+ }
2803
+ function stringList(value, fallback) {
2804
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : [...fallback];
2805
+ }
2806
+ function reasoningEfforts(config) {
2807
+ const options = Array.isArray(config.reasoning_options) ? config.reasoning_options : [];
2808
+ const values = options.flatMap((option) => isRecord(option) && option.type === "effort" && Array.isArray(option.values) ? option.values.filter((value) => typeof value === "string" && value !== "null") : []);
2809
+ return [...new Set(values)];
2810
+ }
2811
+ function manualClaude(upstreamModelId) {
2812
+ const id = upstreamModelId ?? "";
2813
+ const familyFirst = /(?:claude-)?(opus|sonnet|haiku)-(\d+)(?:[.-](\d+))?/i.exec(id);
2814
+ const versionFirst = /claude-(\d+)(?:[.-](\d+))?-(opus|sonnet|haiku)/i.exec(id);
2815
+ const major = Number(familyFirst?.[2] ?? versionFirst?.[1]);
2816
+ const minor = Number(familyFirst?.[3] ?? versionFirst?.[2] ?? 0);
2817
+ return major === 3 && minor === 7 || major === 4 && minor < 6;
2818
+ }
2819
+ function reasoningVariants(pkg, config, upstreamModelId) {
2820
+ if (config.reasoning === false)
2821
+ return [];
2822
+ const efforts = reasoningEfforts(config);
2823
+ if (!efforts.length)
2824
+ return [];
2825
+ const spell = (settings) => efforts.map((effort) => ({ id: effort, settings: settings(effort) }));
2826
+ switch (pkg) {
2827
+ case "@opencode/ai/providers/openai":
2828
+ case "@opencode/ai/providers/azure/responses":
2829
+ case "@opencode/ai/providers/xai":
2830
+ return spell((effort) => ({ reasoningEffort: effort, reasoningSummary: "auto", include: ["reasoning.encrypted_content"] }));
2831
+ case "@opencode/ai/providers/openai-compatible":
2832
+ return spell((effort) => ({ reasoningEffort: effort }));
2833
+ case "@opencode/ai/providers/anthropic":
2834
+ case "@opencode/ai/providers/google-vertex/messages":
2835
+ if (manualClaude(upstreamModelId))
2836
+ return [];
2837
+ return spell((effort) => effort === "none" ? { thinking: { type: "disabled" } } : { thinking: { type: "adaptive", display: "summarized" }, effort });
2838
+ case "@opencode/ai/providers/google":
2839
+ case "@opencode/ai/providers/google-vertex":
2840
+ return spell((effort) => ({ thinkingConfig: { includeThoughts: true, thinkingLevel: effort } }));
2841
+ case "@opencode/ai/providers/openrouter":
2842
+ return spell((effort) => ({ reasoning: { effort } }));
2843
+ default:
2844
+ return [];
2845
+ }
2846
+ }
2847
+ var FAST_VARIANT_PREFIX = "__openwork_fast_v1/";
2848
+ var FAST_DEFAULT_VARIANT = `${FAST_VARIANT_PREFIX}default`;
2849
+ function fastVariantId(effort) {
2850
+ return `${FAST_VARIANT_PREFIX}variant/${encodeURIComponent(effort)}`;
2851
+ }
2852
+ function catalogOffersFast(providerNpm, config) {
2853
+ const modelProvider = isRecord(config.provider) ? config.provider : {};
2854
+ if (providerNpm !== "@ai-sdk/openai")
2855
+ return false;
2856
+ if (modelProvider.npm !== undefined && modelProvider.npm !== providerNpm)
2857
+ return false;
2858
+ const variants = isRecord(config.variants) ? config.variants : {};
2859
+ if (Object.keys(variants).some((id) => id.startsWith(FAST_VARIANT_PREFIX)))
2860
+ return false;
2861
+ const experimental = isRecord(config.experimental) ? config.experimental : {};
2862
+ const modes = isRecord(experimental.modes) ? experimental.modes : {};
2863
+ const fast = isRecord(modes.fast) ? modes.fast : null;
2864
+ if (!fast || Object.keys(fast).some((key) => key !== "provider" && key !== "cost"))
2865
+ return false;
2866
+ const provider = isRecord(fast.provider) ? fast.provider : null;
2867
+ const body = provider && isRecord(provider.body) ? provider.body : null;
2868
+ return Boolean(provider && Object.keys(provider).length === 1 && body && Object.keys(body).length === 1 && body.service_tier === "priority");
2869
+ }
2870
+ function fastVariants(providerNpm, pkg, config, efforts) {
2871
+ if (pkg !== "@opencode/ai/providers/openai" || !catalogOffersFast(providerNpm, config))
2872
+ return [];
2873
+ return [
2874
+ { id: FAST_DEFAULT_VARIANT, settings: { serviceTier: "priority" } },
2875
+ ...efforts.map((variant) => ({ id: fastVariantId(variant.id), settings: { ...variant.settings, serviceTier: "priority" } }))
2876
+ ];
2877
+ }
2878
+ function displayName(model) {
2879
+ const selection = model.modelGroupName && model.credentialSetName ? ` (${model.modelGroupName} / ${model.credentialSetName})` : "";
2880
+ return selection && model.name.endsWith(selection) ? model.name.slice(0, -selection.length) : model.name;
2881
+ }
2882
+ var STATUSES = new Set(["alpha", "beta", "deprecated", "active"]);
2883
+ function toModelInfo(provider, model, providerPkg) {
2884
+ const config = model.config;
2885
+ const modelProvider = isRecord(config.provider) ? config.provider : {};
2886
+ const modelNpm = readString2(modelProvider.npm);
2887
+ const pkg = modelNpm ? providerPackage(modelNpm) : providerPkg;
2888
+ const modalities = isRecord(config.modalities) ? config.modalities : {};
2889
+ const limit = isRecord(config.limit) ? config.limit : {};
2890
+ const status = readString2(config.status);
2891
+ const released = Date.parse(readString2(config.release_date) ?? "");
2892
+ const settings = isRecord(config.options) ? { ...config.options } : undefined;
2893
+ const efforts = reasoningVariants(pkg, config, model.upstreamModelId);
2894
+ const providerNpm = readString2(provider.providerConfig.npm);
2895
+ return {
2896
+ id: model.id,
2897
+ modelID: readString2(config.id) ?? model.id,
2898
+ providerID: provider.id,
2899
+ name: displayName(model),
2900
+ ...readString2(config.family) ? { family: readString2(config.family) ?? undefined } : {},
2901
+ ...pkg !== providerPkg ? { package: pkg } : {},
2902
+ ...settings && Object.keys(settings).length ? { settings } : {},
2903
+ ...stringRecord(config.headers) ? { headers: stringRecord(config.headers) } : {},
2904
+ capabilities: {
2905
+ tools: typeof config.tool_call === "boolean" ? config.tool_call : true,
2906
+ input: stringList(modalities.input, ["text", "image"]),
2907
+ output: stringList(modalities.output, ["text"])
2908
+ },
2909
+ variants: [...efforts, ...fastVariants(providerNpm, pkg, config, efforts)],
2910
+ time: { released: Number.isFinite(released) ? released : 0 },
2911
+ cost: costs(config.cost),
2912
+ status: status && STATUSES.has(status) ? status : "active",
2913
+ enabled: status !== "deprecated",
2914
+ limit: {
2915
+ context: int(limit.context, 200000),
2916
+ ...typeof limit.input === "number" ? { input: int(limit.input, 0) } : {},
2917
+ output: int(limit.output, 32000)
2918
+ }
2919
+ };
2920
+ }
2921
+ var GATEWAY_INTEGRATION_ID = "openwork-gateway";
2922
+ function toProviderRecord(provider, apiKey) {
2923
+ const config = provider.providerConfig;
2924
+ const pkg = providerPackage(readString2(config.npm));
2925
+ const options = isRecord(config.options) ? config.options : {};
2926
+ const { headers, body, ...optionSettings } = options;
2927
+ const baseURL = readString2(config.api) ?? readString2(options.baseURL);
2928
+ const info = {
2929
+ id: provider.id,
2930
+ name: provider.name,
2931
+ activation: "enabled",
2932
+ integrationID: GATEWAY_INTEGRATION_ID,
2933
+ package: pkg,
2934
+ settings: {
2935
+ ...optionSettings,
2936
+ ...baseURL ? { baseURL } : {},
2937
+ ...pkg === "@opencode/ai/providers/openai-compatible" ? { provider: provider.id } : {},
2938
+ apiKey
2939
+ },
2940
+ ...stringRecord(headers) ? { headers: stringRecord(headers) } : {},
2941
+ ...isRecord(body) ? { body: { ...body } } : {}
2942
+ };
2943
+ const models = [...provider.models].sort((left, right) => left.id.localeCompare(right.id)).map((model) => toModelInfo(provider, model, pkg));
2944
+ return { info, models };
2945
+ }
2946
+
2947
+ // ../../packages/opencode-plugin/src/desktop.ts
2948
+ var CACHE_KEY = "desktop/provider-inventory";
2949
+ var digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
2950
+ function isDesktopSessionEndpoint(value) {
2951
+ if (typeof value !== "string")
2952
+ return false;
2953
+ try {
2954
+ const url = new URL(value);
2955
+ return url.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(url.hostname) && !url.username && !url.password && !url.hash;
2956
+ } catch {
2957
+ return false;
2958
+ }
2959
+ }
2960
+ function inventorySession(value) {
2961
+ return { apiBaseUrl: value.apiBaseUrl, token: value.identity, orgId: value.orgId };
2962
+ }
2963
+ async function readDesktopSession(fetcher, endpoint, capability, signal) {
2964
+ if (!isDesktopSessionEndpoint(endpoint) || !capability)
2965
+ return null;
2966
+ const response = await fetcher(endpoint, {
2967
+ headers: { accept: "application/json", authorization: `Bearer ${capability}` },
2968
+ redirect: "error",
2969
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(1500)]) : AbortSignal.timeout(1500)
2970
+ });
2971
+ return parseDesktopSession(response, endpoint);
2972
+ }
2973
+ async function parseDesktopSession(response, endpoint) {
2974
+ if (!response)
2975
+ return null;
2976
+ if (!response.ok)
2977
+ throw new Error("Desktop session unavailable");
2978
+ const value = await response.json();
2979
+ if (value === null)
2980
+ return null;
2981
+ if (!isRecord(value) || typeof value.apiBaseUrl !== "string" || !isAllowedApiBaseUrl(value.apiBaseUrl) || "token" in value || typeof value.identity !== "string" || !/^[a-f0-9]{64}$/.test(value.identity) || !(value.orgId === null || typeof value.orgId === "string") || typeof value.inferenceOrigin !== "string" || value.inferenceOrigin !== new URL(endpoint).origin || typeof value.inferenceKey !== "string" || !/^ow_local_[a-f0-9]{64}$/.test(value.inferenceKey)) {
2982
+ throw new Error("Invalid desktop session");
2983
+ }
2984
+ return {
2985
+ apiBaseUrl: normalizeBaseUrl(value.apiBaseUrl),
2986
+ identity: value.identity,
2987
+ orgId: value.orgId,
2988
+ inferenceOrigin: value.inferenceOrigin,
2989
+ inferenceKey: value.inferenceKey
2990
+ };
2991
+ }
2992
+ function createDesktopProviderPlugin(deps = {}) {
2993
+ const fetcher = deps.fetch ?? fetch;
2994
+ const now = deps.now ?? Date.now;
2995
+ return {
2996
+ id: "openwork",
2997
+ async setup(ctx) {
2998
+ if (ctx.options.liveProviderSync !== true)
2999
+ return;
3000
+ const endpoint = ctx.options.desktopSession;
3001
+ const capability = deps.capability?.();
3002
+ if (!isDesktopSessionEndpoint(endpoint))
3003
+ return;
3004
+ if (typeof ctx.provider?.transform !== "function" || typeof ctx.provider?.reload !== "function" || typeof ctx.storage?.get !== "function") {
3005
+ throw new Error("OpenWork provider sync needs OpenCode V2 provider and storage APIs");
3006
+ }
3007
+ const options = { ...readOptions(ctx.options), mcp: false };
3008
+ const inventoryFetch = (source) => async (input, init) => {
3009
+ const base = normalizeBaseUrl(source.apiBaseUrl);
3010
+ if (!input.startsWith(`${base}/`) || (init?.method ?? "GET") !== "GET" || init?.body != null)
3011
+ return new Response(null, { status: 404 });
3012
+ const path = input.slice(base.length);
3013
+ if (path !== "/v1/inference-providers?scope=usable" && path !== "/v1/inference-providers/inventory/events" && !/^\/v1\/inference-providers\/ipr_[a-z0-9_]{1,128}\/connect$/.test(path))
3014
+ return new Response(null, { status: 404 });
3015
+ const signal = init?.signal ?? undefined;
3016
+ const response = capability ? await fetcher(new URL(endpoint).origin + path, {
3017
+ method: "GET",
3018
+ redirect: "error",
3019
+ signal,
3020
+ headers: {
3021
+ accept: path.endsWith("/inventory/events") ? "text/event-stream" : "application/json",
3022
+ authorization: `Bearer ${capability}`,
3023
+ "x-openwork-provider-identity": source.identity
3024
+ }
3025
+ }) : await fetchDesktopInventory(fetcher, endpoint, path, source.identity, signal);
3026
+ return response ?? new Response(null, { status: 503 });
3027
+ };
3028
+ const lifetime = new AbortController;
3029
+ let session = null;
3030
+ let key = "";
3031
+ let epoch = 0;
3032
+ let stream = null;
3033
+ let refreshController = new AbortController;
3034
+ let providers = [];
3035
+ let applied = digest(providers);
3036
+ let polling = false;
3037
+ let running = null;
3038
+ let queued = false;
3039
+ let inventory = null;
3040
+ const retry = inventoryRetry(() => {
3041
+ refresh();
3042
+ }, deps.retryMs);
3043
+ await ctx.provider.transform((editor) => {
3044
+ for (const provider of providers)
3045
+ editor.add(provider);
3046
+ });
3047
+ const publish = async (next) => {
3048
+ providers = next;
3049
+ const fingerprint = digest(next);
3050
+ if (fingerprint === applied)
3051
+ return;
3052
+ await ctx.provider.reload();
3053
+ if (providers === next)
3054
+ applied = fingerprint;
3055
+ };
3056
+ const safeHeaders = (headers) => headers ? Object.fromEntries(Object.entries(headers).filter(([name]) => !["authorization", "x-api-key", "api-key", "x-goog-api-key", "cookie", "host"].includes(name.toLowerCase()))) : undefined;
3057
+ const records = (value) => {
3058
+ if (!session || value?.gateway?.apiKey !== "ow_local_catalog")
3059
+ return [];
3060
+ const source = session;
3061
+ return snapshotFrom(value, options, { type: "env", name: "openwork-desktop" }).providers.map((provider) => {
3062
+ const baseURL = `${source.inferenceOrigin}/api/v1/providers/${provider.info.id}`;
3063
+ return {
3064
+ info: { ...provider.info, settings: { ...provider.info.settings, baseURL, apiKey: source.inferenceKey }, headers: safeHeaders(provider.info.headers) },
3065
+ models: provider.models.map((model) => ({ ...model, settings: { ...model.settings, baseURL, apiKey: source.inferenceKey }, headers: safeHeaders(model.headers) }))
3066
+ };
3067
+ });
3068
+ };
3069
+ const revoked = () => {
3070
+ epoch++;
3071
+ retry.reset();
3072
+ refreshController.abort();
3073
+ refreshController = new AbortController;
3074
+ inventory = null;
3075
+ queued = false;
3076
+ ctx.storage.remove(CACHE_KEY).catch(() => {});
3077
+ publish([]).catch(() => {});
3078
+ };
3079
+ const unauthorized = () => {
3080
+ epoch++;
3081
+ retry.reset();
3082
+ refreshController.abort();
3083
+ stream?.abort();
3084
+ stream = null;
3085
+ session = null;
3086
+ inventory = null;
3087
+ queued = false;
3088
+ ctx.storage.remove(CACHE_KEY).catch(() => {});
3089
+ publish([]).catch(() => {});
3090
+ };
3091
+ const refresh = () => {
3092
+ if (lifetime.signal.aborted || !session)
3093
+ return Promise.resolve();
3094
+ if (running) {
3095
+ queued = true;
3096
+ return running;
3097
+ }
3098
+ retry.cancel();
3099
+ const source = session;
3100
+ const accountKey = key;
3101
+ const generation = epoch;
3102
+ const current = () => !lifetime.signal.aborted && generation === epoch;
3103
+ running = (async () => {
3104
+ try {
3105
+ const gateway = await fetchGatewayInventory(inventoryFetch(source), inventorySession(source), refreshController.signal, "desktop");
3106
+ if (!current())
3107
+ return;
3108
+ const next = { version: 1, connectionKey: accountKey, apiBaseUrl: source.apiBaseUrl, fetchedAt: now(), gateway, mcp: null };
3109
+ inventory = next;
3110
+ const json = JSON.parse(JSON.stringify(next));
3111
+ await ctx.storage.set(CACHE_KEY, json).catch(() => {});
3112
+ if (!current())
3113
+ return;
3114
+ await publish(records(next));
3115
+ if (current())
3116
+ retry.reset();
3117
+ } catch (error) {
3118
+ if (!current())
3119
+ return;
3120
+ if (error instanceof DenAuthError) {
3121
+ if (error.status === 403)
3122
+ revoked();
3123
+ else
3124
+ unauthorized();
3125
+ } else {
3126
+ retry.schedule();
3127
+ await publish(records(inventory));
3128
+ }
3129
+ }
3130
+ })().catch(() => {}).finally(() => {
3131
+ running = null;
3132
+ if (queued) {
3133
+ queued = false;
3134
+ refresh();
3135
+ }
3136
+ });
3137
+ return running;
3138
+ };
3139
+ const pollSession = async () => {
3140
+ if (polling || lifetime.signal.aborted)
3141
+ return;
3142
+ polling = true;
3143
+ try {
3144
+ const next = await (capability ? readDesktopSession(fetcher, endpoint, capability, lifetime.signal) : fetchDesktopSession(fetcher, endpoint, lifetime.signal).then((response) => parseDesktopSession(response, endpoint))).catch(() => null);
3145
+ if (lifetime.signal.aborted)
3146
+ return;
3147
+ const nextKey = next ? digest({ apiBaseUrl: next.apiBaseUrl, identity: next.identity, orgId: next.orgId }) : "";
3148
+ if (nextKey === key) {
3149
+ if (session)
3150
+ session = next;
3151
+ await publish(records(inventory)).catch(() => {});
3152
+ return;
3153
+ }
3154
+ epoch++;
3155
+ retry.reset();
3156
+ refreshController.abort();
3157
+ refreshController = new AbortController;
3158
+ stream?.abort();
3159
+ stream = null;
3160
+ session = next;
3161
+ key = nextKey;
3162
+ inventory = null;
3163
+ await publish([]).catch(() => {});
3164
+ if (!next) {
3165
+ await ctx.storage.remove(CACHE_KEY).catch(() => {});
3166
+ return;
3167
+ }
3168
+ const cached = parseInventory(await ctx.storage.get(CACHE_KEY).catch(() => {
3169
+ return;
3170
+ }));
3171
+ if (cached?.connectionKey === key && cached.gateway?.apiKey === "ow_local_catalog" && !lifetime.signal.aborted) {
3172
+ inventory = cached;
3173
+ await publish(records(cached)).catch(() => {});
3174
+ }
3175
+ if (lifetime.signal.aborted)
3176
+ return;
3177
+ const controller = new AbortController;
3178
+ stream = controller;
3179
+ subscribeSharedInventory({
3180
+ identity: inventorySession(next),
3181
+ unauthorized,
3182
+ revoked,
3183
+ fetch: inventoryFetch(next),
3184
+ signal: AbortSignal.any([lifetime.signal, controller.signal]),
3185
+ session: async () => {
3186
+ await pollSession();
3187
+ return controller.signal.aborted || !session ? null : inventorySession(session);
3188
+ },
3189
+ changed: () => {
3190
+ refresh();
3191
+ }
3192
+ });
3193
+ refresh();
3194
+ } finally {
3195
+ polling = false;
3196
+ }
3197
+ };
3198
+ pollSession();
3199
+ const timer = setInterval(() => {
3200
+ pollSession().catch(() => {});
3201
+ }, deps.sessionIntervalMs ?? 1000);
3202
+ timer.unref?.();
3203
+ return () => {
3204
+ epoch++;
3205
+ retry.reset();
3206
+ lifetime.abort();
3207
+ refreshController.abort();
3208
+ stream?.abort();
3209
+ clearInterval(timer);
3210
+ };
3211
+ }
3212
+ };
3213
+ }
3214
+ var desktop_default = createDesktopProviderPlugin();
3215
+
3216
+ // ../../packages/opencode-plugin/src/plugin.ts
3217
+ init_auth();
3218
+ init_den();
3219
+
3220
+ // ../../packages/opencode-plugin/src/mcp.ts
3221
+ init_den();
3222
+ import { createHash as createHash3 } from "node:crypto";
3223
+ var CLOUD_SERVER_NAME = "openwork-cloud";
3224
+ var DIRECT_SERVER_PREFIX = "openwork-direct-";
3225
+ var SERVER_INDEX_URI = "openwork://connect/mcp-servers/index.json";
3226
+ var SERVER_INDEX_SCHEMA = "openwork.connect/mcp-servers/1";
3227
+ var MCP_TOKEN_REFRESH_MARGIN_MS = 24 * 60 * 60 * 1000;
3228
+ async function mintMcpToken(fetcher, session) {
3229
+ const body = await denRequest(fetcher, session, "/v1/mcp/token", {
3230
+ method: "POST",
3231
+ body: { scopes: ["mcp:read", "mcp:write"] }
3232
+ });
3233
+ const token = isRecord(body) && typeof body.token === "string" ? body.token : null;
3234
+ const expiresAt = isRecord(body) && typeof body.expiresAt === "string" ? Date.parse(body.expiresAt) : Number.NaN;
3235
+ if (!token || !Number.isFinite(expiresAt))
3236
+ throw new Error("OpenWork returned an unusable MCP token.");
3237
+ return { token, expiresAt };
3238
+ }
3239
+ function mcpTokenNeedsRenewal(token, now) {
3240
+ return !token || token.expiresAt - now <= MCP_TOKEN_REFRESH_MARGIN_MS;
3241
+ }
3242
+ function agentUrl(apiBaseUrl) {
3243
+ return `${normalizeBaseUrl(apiBaseUrl)}/mcp/agent`;
3244
+ }
3245
+ function directServerName(server) {
3246
+ const slug = server.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
3247
+ const digest2 = createHash3("sha256").update(server.connectionId).digest("hex").slice(0, 6);
3248
+ return `${DIRECT_SERVER_PREFIX}${slug ? `${slug}-` : ""}${digest2}`;
3249
+ }
3250
+ function mcpServerEntries(input) {
3251
+ if (!isAllowedApiBaseUrl(input.apiBaseUrl))
3252
+ return {};
3253
+ const headers = { Authorization: `Bearer ${input.token}` };
3254
+ const entries = {
3255
+ [CLOUD_SERVER_NAME]: { type: "remote", url: agentUrl(input.apiBaseUrl), headers, oauth: false }
3256
+ };
3257
+ for (const server of input.servers) {
3258
+ entries[directServerName(server)] = { type: "remote", url: server.url, headers, oauth: false };
3259
+ }
3260
+ return entries;
3261
+ }
3262
+ function parseJsonOrText(raw) {
3263
+ try {
3264
+ return JSON.parse(raw);
3265
+ } catch {
3266
+ return raw;
3267
+ }
3268
+ }
3269
+ async function readMcpPayload(response, requestId) {
3270
+ const matches = (payload) => requestId === undefined || isRecord(payload) && payload.jsonrpc === "2.0" && payload.id === requestId && (payload.result !== undefined || payload.error !== undefined);
3271
+ if (!response.headers.get("content-type")?.toLowerCase().includes("text/event-stream")) {
3272
+ const raw = await response.text();
3273
+ const payload = raw.trim() ? parseJsonOrText(raw) : null;
3274
+ return matches(payload) ? payload : null;
3275
+ }
3276
+ if (!response.body)
3277
+ return null;
3278
+ const reader = response.body.getReader();
3279
+ const decoder = new TextDecoder;
3280
+ let pending = "";
3281
+ let data = [];
3282
+ try {
3283
+ while (true) {
3284
+ const chunk = await reader.read();
3285
+ if (chunk.done)
3286
+ return null;
3287
+ pending += decoder.decode(chunk.value, { stream: true });
3288
+ let end = pending.search(/\r\n|\r|\n/);
3289
+ while (end !== -1) {
3290
+ const line = pending.slice(0, end);
3291
+ pending = pending.slice(end + (pending.startsWith(`\r
3292
+ `, end) ? 2 : 1));
3293
+ if (line === "") {
3294
+ if (data.length) {
3295
+ const payload = parseJsonOrText(data.join(`
3296
+ `));
3297
+ data = [];
3298
+ if (matches(payload))
3299
+ return payload;
3300
+ }
3301
+ } else if (line === "data" || line.startsWith("data:")) {
3302
+ data.push(line.slice(5).replace(/^ /, ""));
3303
+ }
3304
+ end = pending.search(/\r\n|\r|\n/);
3305
+ }
3306
+ }
3307
+ } finally {
3308
+ reader.cancel().catch(() => {});
3309
+ reader.releaseLock();
3310
+ }
3311
+ }
3312
+ function jsonRpcResult(payload) {
3313
+ const record = Array.isArray(payload) ? payload.find(isRecord) : payload;
3314
+ if (!isRecord(record) || record.error !== undefined || !isRecord(record.result))
3315
+ return null;
3316
+ return record.result;
3317
+ }
3318
+ async function mcpPost(fetcher, url, headers, body) {
3319
+ const response = await fetcher(url, {
3320
+ method: "POST",
3321
+ headers: { accept: "application/json, text/event-stream", "content-type": "application/json", ...headers },
3322
+ body: JSON.stringify(body),
3323
+ redirect: "error",
3324
+ signal: AbortSignal.timeout(1e4)
3325
+ });
3326
+ const id = typeof body.id === "number" ? body.id : undefined;
3327
+ return { response, payload: await readMcpPayload(response, id) };
3328
+ }
3329
+
3330
+ class McpIndexError extends Error {
3331
+ status;
3332
+ constructor(message, status = null) {
3333
+ super(message);
3334
+ this.name = "McpIndexError";
3335
+ this.status = status;
3336
+ }
3337
+ }
3338
+ async function readResourceText(fetcher, url, token, uri) {
3339
+ const base = { authorization: `Bearer ${token}` };
3340
+ const initialized = await mcpPost(fetcher, url, base, {
3341
+ id: 1,
3342
+ jsonrpc: "2.0",
3343
+ method: "initialize",
3344
+ params: {
3345
+ capabilities: {},
3346
+ clientInfo: { name: "opencode-openwork", version: "0.1.0" },
3347
+ protocolVersion: "2025-06-18"
3348
+ }
3349
+ });
3350
+ if (!initialized.response.ok)
3351
+ throw new McpIndexError("OpenWork MCP rejected the session.", initialized.response.status);
3352
+ const protocolVersion = jsonRpcResult(initialized.payload)?.protocolVersion;
3353
+ if (protocolVersion !== "2025-06-18" && protocolVersion !== "2025-03-26") {
3354
+ throw new McpIndexError(`Unsupported MCP protocol version ${String(protocolVersion)}.`);
3355
+ }
3356
+ const sessionId = initialized.response.headers.get("mcp-session-id");
3357
+ const headers = {
3358
+ ...base,
3359
+ ...sessionId ? { "mcp-session-id": sessionId } : {},
3360
+ "mcp-protocol-version": protocolVersion
3361
+ };
3362
+ const notified = await mcpPost(fetcher, url, headers, { jsonrpc: "2.0", method: "notifications/initialized", params: {} });
3363
+ if (notified.response.status >= 400)
3364
+ throw new McpIndexError("OpenWork MCP rejected the session.", notified.response.status);
3365
+ const read = await mcpPost(fetcher, url, headers, { id: 2, jsonrpc: "2.0", method: "resources/read", params: { uri } });
3366
+ if (!read.response.ok)
3367
+ throw new McpIndexError("OpenWork MCP could not list connections.", read.response.status);
3368
+ const contents = jsonRpcResult(read.payload)?.contents;
3369
+ const text = Array.isArray(contents) ? contents.find((item) => isRecord(item) && item.uri === uri && typeof item.text === "string") : undefined;
3370
+ if (!isRecord(text) || typeof text.text !== "string")
3371
+ throw new McpIndexError("OpenWork MCP returned no connection list.");
3372
+ return text.text;
3373
+ }
3374
+ function parseServerIndex(text, apiBaseUrl) {
3375
+ const value = parseJsonOrText(text);
3376
+ if (!isRecord(value) || value.schemaVersion !== SERVER_INDEX_SCHEMA || !Array.isArray(value.servers)) {
3377
+ throw new McpIndexError("OpenWork returned an unrecognized connection list.");
3378
+ }
3379
+ const origin = new URL(normalizeBaseUrl(apiBaseUrl)).origin;
3380
+ const servers = [];
3381
+ for (const entry of value.servers) {
3382
+ if (!isRecord(entry) || entry.exposeDirectly !== true)
3383
+ continue;
3384
+ const connectionId = typeof entry.connectionId === "string" ? entry.connectionId : "";
3385
+ const name = typeof entry.name === "string" ? entry.name : "";
3386
+ const url = typeof entry.url === "string" ? entry.url : "";
3387
+ if (!connectionId || !name || !url)
3388
+ continue;
3389
+ let parsed;
3390
+ try {
3391
+ parsed = new URL(url);
3392
+ } catch {
3393
+ continue;
3394
+ }
3395
+ if (parsed.origin !== origin || !parsed.pathname.startsWith("/mcp/agent/connections/"))
3396
+ continue;
3397
+ servers.push({ connectionId, name, url: parsed.toString() });
3398
+ }
3399
+ return servers.sort((left, right) => left.name.localeCompare(right.name) || left.connectionId.localeCompare(right.connectionId));
3400
+ }
3401
+ async function fetchDirectServers(fetcher, apiBaseUrl, token) {
3402
+ if (!isAllowedApiBaseUrl(apiBaseUrl))
3403
+ throw new DenUrlError(apiBaseUrl);
3404
+ const text = await readResourceText(fetcher, agentUrl(apiBaseUrl), token, SERVER_INDEX_URI);
3405
+ return parseServerIndex(text, apiBaseUrl);
3406
+ }
3407
+
3408
+ // ../../packages/opencode-plugin/src/plugin.ts
3409
+ var PLUGIN_ID = "openwork";
3410
+ var DEFAULT_API_BASE_URL = "https://api.openworklabs.com";
3411
+ var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
3412
+ var MIN_REFRESH_INTERVAL_MS = 60 * 1000;
3413
+ var FRESH_MS = 30 * 1000;
3414
+ var INVENTORY_KEY = "inventory";
3415
+ var SESSIONS_KEY = "sessions";
3416
+ function readOptions(options) {
3417
+ const apiBaseUrl = typeof options.apiBaseUrl === "string" && isAllowedApiBaseUrl(normalizeBaseUrl(options.apiBaseUrl)) ? normalizeBaseUrl(options.apiBaseUrl) : DEFAULT_API_BASE_URL;
3418
+ const interval = typeof options.refreshIntervalMs === "number" && Number.isFinite(options.refreshIntervalMs) ? Math.max(MIN_REFRESH_INTERVAL_MS, options.refreshIntervalMs) : DEFAULT_REFRESH_INTERVAL_MS;
3419
+ return {
3420
+ apiBaseUrl,
3421
+ providers: options.providers !== false,
3422
+ mcp: options.mcp !== false,
3423
+ remoteSessions: options.remoteSessions === true,
3424
+ ...typeof options.label === "string" && options.label.trim() ? { label: options.label.trim().slice(0, 120) } : {},
3425
+ refreshIntervalMs: interval
3426
+ };
3427
+ }
3428
+ function parseInventory(value) {
3429
+ if (!isRecord(value) || value.version !== 1 || typeof value.connectionKey !== "string" || typeof value.fetchedAt !== "number") {
3430
+ return null;
3431
+ }
3432
+ return value;
3433
+ }
3434
+ function toJson(value) {
3435
+ return JSON.parse(JSON.stringify(value));
3436
+ }
3437
+ function hash2(value) {
3438
+ return createHash7("sha256").update(value).digest("hex").slice(0, 16);
3439
+ }
3440
+ function connectionId(connection) {
3441
+ return connection.type === "credential" ? connection.id : connection.name;
3442
+ }
3443
+ function connectionKey(connection, token) {
3444
+ return `${connectionId(connection)}:${hash2(token)}`;
3445
+ }
3446
+ var EMPTY = { providers: [], mcp: {}, sourceConnection: undefined };
3447
+ function snapshotFrom(inventory, options, connection) {
3448
+ if (!inventory || !connection)
3449
+ return EMPTY;
3450
+ const providers = options.providers && inventory.gateway?.apiKey ? inventory.gateway.providers.map((provider) => toProviderRecord(provider, inventory.gateway?.apiKey ?? "")) : [];
3451
+ const mcp = options.mcp && inventory.mcp ? mcpServerEntries({ apiBaseUrl: inventory.apiBaseUrl, token: inventory.mcp.token.token, servers: inventory.mcp.servers }) : {};
3452
+ return { providers, mcp, sourceConnection: connection };
3453
+ }
3454
+ function fingerprint(value) {
3455
+ return hash2(JSON.stringify(value));
3456
+ }
3457
+ function readIntegrationConnections(value) {
3458
+ const info = isRecord(value) && isRecord(value.data) ? value.data : value;
3459
+ return isRecord(info) && Array.isArray(info.connections) ? info.connections : [];
3460
+ }
3461
+ function isCredentialEvent(event) {
3462
+ return isRecord(event) && typeof event.type === "string" && event.type.startsWith("credential.");
3463
+ }
3464
+ function createPlugin(deps = {}) {
3465
+ const fetcher = deps.fetch ?? ((input, init) => fetch(input, init));
3466
+ const now = deps.now ?? Date.now;
3467
+ return {
3468
+ id: PLUGIN_ID,
3469
+ async setup(ctx) {
3470
+ if (ctx.options.desktopSession !== undefined) {
3471
+ return await createDesktopProviderPlugin({ fetch: fetcher, now, retryMs: deps.retryMs }).setup(ctx) ?? (() => {});
3472
+ }
3473
+ if (typeof ctx.integration?.transform !== "function" || typeof ctx.integration?.connection?.active !== "function" || typeof ctx.provider?.transform !== "function" || typeof ctx.provider?.reload !== "function" || typeof ctx.mcp?.transform !== "function" || typeof ctx.mcp?.reload !== "function" || typeof ctx.storage?.get !== "function" || typeof ctx.event?.subscribe !== "function") {
3474
+ throw new Error("OpenWork needs OpenCode V2 with the integration, provider and MCP plugin APIs (tested with 2.0.26). The plugin does not install or upgrade OpenCode.");
3475
+ }
3476
+ const options = readOptions(ctx.options);
3477
+ const remote = { current: null };
3478
+ let remoteLoading = null;
3479
+ let snapshot = EMPTY;
3480
+ let applied = { providers: "", mcp: "" };
3481
+ let reportedNeedsAuth = false;
3482
+ let disposed = false;
3483
+ let generation = 0;
3484
+ let refreshController = new AbortController;
3485
+ let stream = null;
3486
+ let streamKey = "";
3487
+ let revokedIdentity = "";
3488
+ let unauthorizedIdentity = "";
3489
+ const stopStream = () => {
3490
+ stream?.abort();
3491
+ stream = null;
3492
+ streamKey = "";
3493
+ };
3494
+ await ctx.integration.transform((editor) => {
3495
+ editor.update(INTEGRATION_ID, (integration) => {
3496
+ integration.name = INTEGRATION_NAME;
3497
+ });
3498
+ for (const method of signInMethods({ fetcher, apiBaseUrl: () => options.apiBaseUrl, now })) {
3499
+ editor.method.update(method);
3500
+ }
3501
+ });
3502
+ const initialConnection = await ctx.integration.connection.active(INTEGRATION_ID).catch(() => {
3503
+ return;
3504
+ });
3505
+ const initialCredential = initialConnection ? await ctx.integration.connection.resolve(initialConnection).catch(() => {
3506
+ return;
3507
+ }) : undefined;
3508
+ const initialMetadata = initialCredential?.type === "oauth" ? readCredentialMetadata(initialCredential, options.apiBaseUrl) : null;
3509
+ const initialKey = initialConnection && initialCredential?.type === "oauth" && initialMetadata ? `${connectionKey(initialConnection, initialCredential.access)}:${hash2(`${initialMetadata.apiBaseUrl}:${initialMetadata.orgId}`)}` : null;
3510
+ const candidate = parseInventory(await ctx.storage.get(INVENTORY_KEY).catch(() => {
3511
+ return;
3512
+ }));
3513
+ const cachedInventory = candidate?.connectionKey === initialKey ? candidate : null;
3514
+ snapshot = snapshotFrom(cachedInventory, options, initialConnection);
3515
+ applied = { providers: fingerprint([snapshot.providers, snapshot.sourceConnection]), mcp: fingerprint(snapshot.mcp) };
3516
+ await ctx.provider.transform((editor) => {
3517
+ for (const record of snapshot.providers) {
3518
+ editor.add({ info: record.info, models: record.models });
3519
+ }
3520
+ });
3521
+ await ctx.mcp.transform((editor) => {
3522
+ for (const [name, config] of Object.entries(snapshot.mcp))
3523
+ editor.set(name, config);
3524
+ });
3525
+ const publish = async (next) => {
3526
+ snapshot = next;
3527
+ const providers = fingerprint([next.providers, next.sourceConnection]);
3528
+ const mcp = fingerprint(next.mcp);
3529
+ const reloads = [];
3530
+ if (providers !== applied.providers)
3531
+ reloads.push(ctx.provider.reload().then(() => {
3532
+ if (snapshot === next)
3533
+ applied.providers = providers;
3534
+ }));
3535
+ if (mcp !== applied.mcp)
3536
+ reloads.push(ctx.mcp.reload().then(() => {
3537
+ if (snapshot === next)
3538
+ applied.mcp = mcp;
3539
+ }));
3540
+ await Promise.all(reloads);
3541
+ };
3542
+ const setNeedsAuth = async (connection, message2) => {
3543
+ if (message2 === null && !reportedNeedsAuth)
3544
+ return;
3545
+ reportedNeedsAuth = message2 !== null;
3546
+ await ctx.integration.connection.status({
3547
+ integrationID: INTEGRATION_ID,
3548
+ connection,
3549
+ status: message2 === null ? undefined : { status: "needs_auth", message: message2 }
3550
+ }).catch(() => {});
3551
+ };
3552
+ const reconcileSessions = async (active) => {
3553
+ const stored = await ctx.storage.get(SESSIONS_KEY).catch(() => {
3554
+ return;
3555
+ });
3556
+ const sessions = isRecord(stored) ? stored : {};
3557
+ const integration = await ctx.integration.get({ integrationID: INTEGRATION_ID }).catch(() => null);
3558
+ if (integration === null)
3559
+ return;
3560
+ const live = new Set(readIntegrationConnections(integration).flatMap((connection) => connection.type === "credential" ? [connection.id] : []));
3561
+ let changed = false;
3562
+ for (const [id, session] of Object.entries(sessions)) {
3563
+ if (live.has(id))
3564
+ continue;
3565
+ await signOut(fetcher, session.apiBaseUrl, session.token);
3566
+ delete sessions[id];
3567
+ changed = true;
3568
+ }
3569
+ if (active && sessions[active.id]?.token !== active.token) {
3570
+ sessions[active.id] = { token: active.token, apiBaseUrl: active.apiBaseUrl };
3571
+ changed = true;
3572
+ }
3573
+ if (changed)
3574
+ await ctx.storage.set(SESSIONS_KEY, toJson(sessions)).catch(() => {});
3575
+ };
3576
+ let running = null;
3577
+ let queued = false;
3578
+ let forceQueued = false;
3579
+ const retry = inventoryRetry(() => {
3580
+ refresh(true);
3581
+ }, deps.retryMs);
3582
+ const revokeAuthority = (identity) => {
3583
+ revokedIdentity = identity;
3584
+ generation++;
3585
+ retry.reset();
3586
+ refreshController.abort();
3587
+ refreshController = new AbortController;
3588
+ queued = false;
3589
+ forceQueued = false;
3590
+ ctx.storage.remove(INVENTORY_KEY).catch(() => {});
3591
+ publish(EMPTY).catch(() => {});
3592
+ };
3593
+ const authorizedFeatureState = async (session) => {
3594
+ try {
3595
+ const body = await denRequest(fetcher, session, "/v1/org");
3596
+ return isRecord(body) && isRecord(body.features) ? body.features.liveProviderSync === true : null;
3597
+ } catch {
3598
+ return null;
3599
+ }
3600
+ };
3601
+ const featureEnabled = async (session) => await authorizedFeatureState(session) === true;
3602
+ const syncStream = (session, enabled) => {
3603
+ const key = fingerprint(session);
3604
+ if (!enabled || !options.providers) {
3605
+ stopStream();
3606
+ return;
3607
+ }
3608
+ if (streamKey === key)
3609
+ return;
3610
+ stopStream();
3611
+ streamKey = key;
3612
+ const controller = new AbortController;
3613
+ stream = controller;
3614
+ subscribeSharedInventory({
3615
+ identity: session,
3616
+ revoked: () => {
3617
+ if (controller.signal.aborted || disposed)
3618
+ return;
3619
+ revokeAuthority(key);
3620
+ },
3621
+ unauthorized: () => {
3622
+ if (controller.signal.aborted || disposed)
3623
+ return;
3624
+ unauthorizedIdentity = key;
3625
+ generation++;
3626
+ retry.reset();
3627
+ refreshController.abort();
3628
+ refreshController = new AbortController;
3629
+ queued = false;
3630
+ forceQueued = false;
3631
+ stopStream();
3632
+ ctx.storage.remove(INVENTORY_KEY).catch(() => {});
3633
+ publish(EMPTY).catch(() => {});
3634
+ },
3635
+ fetch: fetcher,
3636
+ signal: controller.signal,
3637
+ session: async () => {
3638
+ const current = await ctx.integration.connection.active(INTEGRATION_ID).catch(() => {
3639
+ return;
3640
+ });
3641
+ const value = current ? await ctx.integration.connection.resolve(current).catch(() => {
3642
+ return;
3643
+ }) : undefined;
3644
+ if (!value || value.type !== "oauth" || controller.signal.aborted)
3645
+ return null;
3646
+ const metadata = readCredentialMetadata(value, options.apiBaseUrl);
3647
+ const next = { apiBaseUrl: metadata.apiBaseUrl, token: value.access, orgId: metadata.orgId };
3648
+ return controller.signal.aborted ? null : next;
3649
+ },
3650
+ changed: () => {
3651
+ if (controller.signal.aborted || disposed)
3652
+ return;
3653
+ if (revokedIdentity === key)
3654
+ revokedIdentity = "";
3655
+ refresh(true);
3656
+ }
3657
+ }).finally(() => {
3658
+ if (stream === controller) {
3659
+ stream = null;
3660
+ streamKey = "";
3661
+ }
3662
+ });
3663
+ };
3664
+ const refreshOnce = async (force) => {
3665
+ const epoch = generation;
3666
+ const current = () => !disposed && epoch === generation;
3667
+ const connection = await ctx.integration.connection.active(INTEGRATION_ID).catch(() => {
3668
+ return;
3669
+ });
3670
+ if (!current())
3671
+ return;
3672
+ if (!connection) {
3673
+ stopStream();
3674
+ await reconcileSessions(null);
3675
+ if (!current())
3676
+ return;
3677
+ await ctx.storage.remove(INVENTORY_KEY).catch(() => {});
3678
+ await publish(EMPTY);
3679
+ return;
3680
+ }
3681
+ let credential;
3682
+ try {
3683
+ const value = await ctx.integration.connection.resolve(connection);
3684
+ if (!value || value.type !== "oauth") {
3685
+ await publish(EMPTY);
3686
+ return;
3687
+ }
3688
+ if (!current())
3689
+ return;
3690
+ credential = value;
3691
+ } catch {
3692
+ if (!current())
3693
+ return;
3694
+ await setNeedsAuth(connection, "Your OpenWork sign-in expired. Run: opencode auth login openwork");
3695
+ await publish(snapshotFrom(null, options, connection));
3696
+ return;
3697
+ }
3698
+ const metadata = readCredentialMetadata(credential, options.apiBaseUrl);
3699
+ const session = { apiBaseUrl: metadata.apiBaseUrl, token: credential.access, orgId: metadata.orgId };
3700
+ const identity = fingerprint(session);
3701
+ if (identity === unauthorizedIdentity) {
3702
+ await publish(EMPTY);
3703
+ return;
3704
+ }
3705
+ if (identity === revokedIdentity) {
3706
+ const feature = await authorizedFeatureState(session);
3707
+ if (!current())
3708
+ return;
3709
+ if (feature === false) {
3710
+ revokedIdentity = "";
3711
+ stopStream();
3712
+ } else {
3713
+ syncStream(session, true);
3714
+ await publish(EMPTY);
3715
+ return;
3716
+ }
3717
+ }
3718
+ if (revokedIdentity !== identity)
3719
+ revokedIdentity = "";
3720
+ if (unauthorizedIdentity !== identity)
3721
+ unauthorizedIdentity = "";
3722
+ const key = `${connectionKey(connection, credential.access)}:${hash2(`${session.apiBaseUrl}:${session.orgId}`)}`;
3723
+ if (connection.type === "credential") {
3724
+ await reconcileSessions({ id: connection.id, token: credential.access, apiBaseUrl: metadata.apiBaseUrl });
3725
+ }
3726
+ const cached = parseInventory(await ctx.storage.get(INVENTORY_KEY).catch(() => {
3727
+ return;
3728
+ }));
3729
+ if (!current())
3730
+ return;
3731
+ const sameAccount = cached?.connectionKey === key ? cached : null;
3732
+ if (!sameAccount)
3733
+ await publish(EMPTY);
3734
+ if (!current())
3735
+ return;
3736
+ if (!force && sameAccount && now() - sameAccount.fetchedAt < FRESH_MS) {
3737
+ syncStream(session, sameAccount.liveProviderSync === true);
3738
+ await publish(snapshotFrom(sameAccount, options, connection));
3739
+ return;
3740
+ }
3741
+ try {
3742
+ const liveProviderSync = await featureEnabled(session);
3743
+ if (!current())
3744
+ return;
3745
+ syncStream(session, liveProviderSync);
3746
+ const [gateway, mcp] = await Promise.all([
3747
+ options.providers ? fetchGatewayInventory(fetcher, session, refreshController.signal).then((inventory2) => ({ providers: inventory2.providers, apiKey: inventory2.apiKey })) : Promise.resolve(null),
3748
+ options.mcp ? (async () => {
3749
+ const previous = sameAccount?.mcp?.token ?? null;
3750
+ const token = previous && !mcpTokenNeedsRenewal(previous, now()) ? previous : await mintMcpToken(fetcher, session);
3751
+ try {
3752
+ return { token, servers: await fetchDirectServers(fetcher, session.apiBaseUrl, token.token) };
3753
+ } catch {
3754
+ return { token, servers: sameAccount?.mcp?.servers ?? [] };
3755
+ }
3756
+ })() : Promise.resolve(null)
3757
+ ]);
3758
+ if (!current())
3759
+ return;
3760
+ const inventory = {
3761
+ version: 1,
3762
+ liveProviderSync,
3763
+ connectionKey: key,
3764
+ apiBaseUrl: session.apiBaseUrl,
3765
+ fetchedAt: now(),
3766
+ gateway,
3767
+ mcp
3768
+ };
3769
+ await ctx.storage.set(INVENTORY_KEY, toJson(inventory)).catch(() => {});
3770
+ if (!current())
3771
+ return;
3772
+ await setNeedsAuth(connection, null);
3773
+ if (!current())
3774
+ return;
3775
+ await publish(snapshotFrom(inventory, options, connection));
3776
+ if (current())
3777
+ retry.reset();
3778
+ } catch (error) {
3779
+ if (!current())
3780
+ return;
3781
+ if (error instanceof DenAuthError) {
3782
+ if (error.status === 403 && streamKey) {
3783
+ revokeAuthority(fingerprint(session));
3784
+ return;
3785
+ }
3786
+ retry.reset();
3787
+ queued = false;
3788
+ forceQueued = false;
3789
+ stopStream();
3790
+ await ctx.storage.remove(INVENTORY_KEY).catch(() => {});
3791
+ if (!current())
3792
+ return;
3793
+ await setNeedsAuth(connection, "OpenWork rejected your sign-in. Run: opencode auth login openwork");
3794
+ await publish(EMPTY);
3795
+ return;
3796
+ }
3797
+ if (force || streamKey)
3798
+ retry.schedule();
3799
+ await publish(snapshotFrom(sameAccount, options, connection));
3800
+ }
3801
+ };
3802
+ const refresh = (force = false) => {
3803
+ if (disposed)
3804
+ return Promise.resolve();
3805
+ if (running) {
3806
+ queued = true;
3807
+ forceQueued ||= force;
3808
+ return running;
3809
+ }
3810
+ retry.cancel();
3811
+ running = refreshOnce(force).catch(() => {}).finally(() => {
3812
+ running = null;
3813
+ if (queued && !disposed) {
3814
+ queued = false;
3815
+ const force2 = forceQueued;
3816
+ forceQueued = false;
3817
+ refresh(force2);
3818
+ }
3819
+ });
3820
+ return running;
3821
+ };
3822
+ if (options.remoteSessions) {
3823
+ remoteLoading = Promise.resolve().then(() => (init_remote_runner(), exports_remote_runner)).then(async (module) => {
3824
+ if (disposed)
3825
+ return;
3826
+ if (!module.hasNativeSessionHost(ctx))
3827
+ throw new Error("native_host_unavailable");
3828
+ remote.current = module.createRemoteRunnerController({ ctx, apiBaseUrl: options.apiBaseUrl, label: options.label, fetch: fetcher, now });
3829
+ await ctx.storage.set("remoteSessions/status", { status: "enabled", directory: ctx.location.directory });
3830
+ if (!disposed)
3831
+ remote.current.start();
3832
+ }).catch(async () => {
3833
+ if (disposed)
3834
+ return;
3835
+ const message2 = "Remote sessions could not start. Use OpenCode 2.0.26 or newer with native session APIs. For Git installs, run sparse-checkout set packages/opencode-plugin packages/remote-sessions and reload. Ordinary OpenWork sign-in, models, and MCP remain available.";
3836
+ await ctx.storage.set("remoteSessions/status", { status: "unavailable", directory: ctx.location.directory, message: message2 }).catch(() => {});
3837
+ console.warn(`[OpenWork] ${message2}`);
3838
+ });
3839
+ }
3840
+ refresh();
3841
+ const events = new AbortController;
3842
+ (async () => {
3843
+ try {
3844
+ for await (const event of ctx.event.subscribe({ signal: events.signal })) {
3845
+ if (isCredentialEvent(event)) {
3846
+ generation++;
3847
+ retry.reset();
3848
+ refreshController.abort();
3849
+ refreshController = new AbortController;
3850
+ stopStream();
3851
+ publish(EMPTY).catch(() => {});
3852
+ remote.current?.credentialsChanged();
3853
+ refresh(true);
3854
+ }
3855
+ }
3856
+ } catch {}
3857
+ })();
3858
+ const timer = setInterval(() => void refresh(), options.refreshIntervalMs);
3859
+ timer.unref?.();
3860
+ return async () => {
3861
+ disposed = true;
3862
+ generation++;
3863
+ retry.reset();
3864
+ refreshController.abort();
3865
+ stopStream();
3866
+ clearInterval(timer);
3867
+ events.abort();
3868
+ await remoteLoading;
3869
+ await remote.current?.close();
3870
+ };
3871
+ }
3872
+ };
3873
+ }
3874
+
3875
+ // ../../packages/opencode-plugin/src/server.ts
3876
+ await initializeDesktopCredentialChannel();
3877
+ var server_default = createPlugin();
3878
+ export {
3879
+ server_default as default
3880
+ };