@straiffi/archon 1.11.14 → 1.11.16

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 (91) hide show
  1. package/dist/client/assets/{ActivityView-CXlLfbio.js → ActivityView-TwJeVDII.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-COncogiu.js → GitHubActionsDialog-Umiz7hRS.js} +1 -1
  3. package/dist/client/assets/VerificationDialog-BVaLckVH.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BgpjZv9J.js → architectureDiagram-3BPJPVTR-DG6n6a9N.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-DZOzPj8L.js → blockDiagram-GPEHLZMM-fpqlAUZA.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CH8w_-4O.js → c4Diagram-AAUBKEIU-DD-BA8Fj.js} +1 -1
  7. package/dist/client/assets/channel-BvWs2-ry.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-BhCJUMWk.js → chunk-2J33WTMH-BcwQlxYR.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-B4ljJWcN.js → chunk-3OPIFGDE-DCbOiRKI.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-BokkrzzD.js → chunk-5ZQYHXKU-BAgSocEO.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-V-Kd95sz.js → chunk-727SXJPM-0NBz7Bl7.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-D7ZyW65i.js → chunk-AQP2D5EJ-BgLpjpch.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-3cPiGWR4.js → chunk-CSCIHK7Q-CFV8R2WL.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-fh-t3Kan.js → chunk-KSCS5N6A-B-5OABYB.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-Bz_XmsGr.js → chunk-L5ZTLDWV-BEakyLfX.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-DUQIbmfo.js → chunk-LZXEDZCA-k1JqO5jl.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-BeaXA2cA.js → chunk-ND2GUHAM-CAvkgbq4.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-Cljr0eIp.js → chunk-NZK2D7GU-B1XxeNv6.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-BSZxUONE.js → chunk-O5CBEL6O-xsxGQlBc.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CqeEBV6t.js → chunk-WU5MYG2G-D2RTcS5J.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-CXujfiH2.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BpQoPe_W.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-C684sP5O.js → dagre-BM42HDAG-DQRkT0rW.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-Bsuw7S1w.js → diagram-2AECGRRQ-DLxnmFG_.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-B7TKNAIK.js → diagram-5GNKFQAL-Cu1nMouP.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-RPOTY2uQ.js → diagram-KO2AKTUF-BCpHrNi4.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-Dpk6toIw.js → diagram-LMA3HP47-CIM0-geo.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-DVJDTxyc.js → diagram-OG6HWLK6-DV8jDGdy.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-BTCHMpI9.js → erDiagram-TEJ5UH35-C-5Pkrkl.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DJHpUHaK.js → flowDiagram-I6XJVG4X-CDPh9OIS.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-B3lqQNjh.js → ganttDiagram-6RSMTGT7-jncsu8xZ.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-kSFZuvH6.js → gitGraphDiagram-PVQCEYII-DGWMJkfX.js} +1 -1
  33. package/dist/client/assets/index-C8n_tSJG.js +293 -0
  34. package/dist/client/assets/{infoDiagram-5YYISTIA-D7rhmUR9.js → infoDiagram-5YYISTIA-B4xDH_w0.js} +1 -1
  35. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CFrqWdH6.js → ishikawaDiagram-YF4QCWOH-DVdd9VXs.js} +1 -1
  36. package/dist/client/assets/{journeyDiagram-JHISSGLW-BPgPF00X.js → journeyDiagram-JHISSGLW-nOtumTZv.js} +1 -1
  37. package/dist/client/assets/{kanban-definition-UN3LZRKU-BJU3LTu3.js → kanban-definition-UN3LZRKU-DDOXGouj.js} +1 -1
  38. package/dist/client/assets/{line-DTRSChYM.js → line-Bv23U4ui.js} +1 -1
  39. package/dist/client/assets/{mermaid-parser.core-X8EDtr9G.js → mermaid-parser.core-G9FbMuHU.js} +1 -1
  40. package/dist/client/assets/{mermaid.core-CQDbIF5j.js → mermaid.core-B3CWAqqw.js} +3 -3
  41. package/dist/client/assets/{mindmap-definition-RKZ34NQL-Bcke6M7z.js → mindmap-definition-RKZ34NQL-DjSz56M7.js} +1 -1
  42. package/dist/client/assets/{pieDiagram-4H26LBE5-j8xeTgqq.js → pieDiagram-4H26LBE5-DSWgdOVl.js} +1 -1
  43. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-OlvBffG8.js → quadrantDiagram-W4KKPZXB-DEupU1aE.js} +1 -1
  44. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DA6zNi15.js → requirementDiagram-4Y6WPE33-BhBfAL0L.js} +1 -1
  45. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BsS5qud8.js → sankeyDiagram-5OEKKPKP-CbjG9Vw2.js} +1 -1
  46. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BfZ_W9Jc.js → sequenceDiagram-3UESZ5HK-1Q4xZQSJ.js} +1 -1
  47. package/dist/client/assets/{stateDiagram-AJRCARHV-C7uuqC0M.js → stateDiagram-AJRCARHV-OtJd_YEY.js} +1 -1
  48. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bkxwy8iL.js +1 -0
  49. package/dist/client/assets/{timeline-definition-PNZ67QCA-yuywaFQ6.js → timeline-definition-PNZ67QCA-CABkNCpN.js} +1 -1
  50. package/dist/client/assets/{vennDiagram-CIIHVFJN-Cxlyjlnx.js → vennDiagram-CIIHVFJN-BC2gbfhD.js} +1 -1
  51. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DcJ0jJo4.js → wardleyDiagram-YWT4CUSO-Bkh5qr4Q.js} +1 -1
  52. package/dist/client/assets/{xychartDiagram-2RQKCTM6-Cia16EBC.js → xychartDiagram-2RQKCTM6-BWDBD0AE.js} +1 -1
  53. package/dist/client/index.html +1 -1
  54. package/dist/server/index.js +2 -0
  55. package/dist/server/index.js.map +1 -1
  56. package/dist/server/lib/agent.js +43 -10
  57. package/dist/server/lib/agent.js.map +1 -1
  58. package/dist/server/lib/agentProvider.js.map +1 -1
  59. package/dist/server/lib/opencodeAgentProvider.js +11 -1
  60. package/dist/server/lib/opencodeAgentProvider.js.map +1 -1
  61. package/dist/server/lib/opencodeApprovalContext.js +38 -0
  62. package/dist/server/lib/opencodeApprovalContext.js.map +1 -0
  63. package/dist/server/lib/opencodeApprovalLifecycle.js +49 -0
  64. package/dist/server/lib/opencodeApprovalLifecycle.js.map +1 -0
  65. package/dist/server/lib/opencodeApprovalRoutes.js +51 -0
  66. package/dist/server/lib/opencodeApprovalRoutes.js.map +1 -0
  67. package/dist/server/lib/opencodeApprovalSchema.js +31 -0
  68. package/dist/server/lib/opencodeApprovalSchema.js.map +1 -0
  69. package/dist/server/lib/opencodeApprovalTransport.js +713 -0
  70. package/dist/server/lib/opencodeApprovalTransport.js.map +1 -0
  71. package/dist/server/lib/opencodeApprovalTypes.js +2 -0
  72. package/dist/server/lib/opencodeApprovalTypes.js.map +1 -0
  73. package/dist/server/lib/opencodeToolApprovals.js +371 -0
  74. package/dist/server/lib/opencodeToolApprovals.js.map +1 -0
  75. package/dist/server/lib/planModeQuestions.js +4 -0
  76. package/dist/server/lib/planModeQuestions.js.map +1 -1
  77. package/dist/server/lib/restrictedFilePrompt.js +11 -3
  78. package/dist/server/lib/restrictedFilePrompt.js.map +1 -1
  79. package/dist/server/lib/reviewAgentWatchdog.js +5 -5
  80. package/dist/server/lib/reviewAgentWatchdog.js.map +1 -1
  81. package/dist/server/lib/reviewPromptContext.js +5 -3
  82. package/dist/server/lib/reviewPromptContext.js.map +1 -1
  83. package/dist/server/workers/plan.js +47 -41
  84. package/dist/server/workers/plan.js.map +1 -1
  85. package/package.json +1 -1
  86. package/dist/client/assets/VerificationDialog-DcKjv3mg.js +0 -4
  87. package/dist/client/assets/channel-DOoXSYAr.js +0 -1
  88. package/dist/client/assets/classDiagram-4FO5ZUOK-DKWNm6Cx.js +0 -1
  89. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-JFUHs0Z7.js +0 -1
  90. package/dist/client/assets/index-CXBsxrT1.js +0 -293
  91. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-B2rL_9Nk.js +0 -1
@@ -0,0 +1,713 @@
1
+ import { PassThrough } from 'node:stream';
2
+ import { StringDecoder } from 'node:string_decoder';
3
+ // Runs in the execution guest, not the Archon host. Only JSON frames cross stdio;
4
+ // the per-run Basic auth secret never appears in arguments, logs or relay frames.
5
+ // Keep this self-contained: sandbox launchers do not mount Archon's server files.
6
+ const GUEST_RELAY = String.raw `
7
+ const { spawn } = await import('node:child_process');
8
+ const { randomBytes } = await import('node:crypto');
9
+ const { createInterface } = await import('node:readline');
10
+ const { resolve, basename, extname } = await import('node:path');
11
+ const { pathToFileURL } = await import('node:url');
12
+ const { stat } = await import('node:fs/promises');
13
+ const major = Number(process.argv[1]);
14
+ const password = randomBytes(32).toString('hex');
15
+ const headers = { authorization: 'Basic ' + Buffer.from('opencode:' + password).toString('base64'), 'content-type': 'application/json' };
16
+ const send = value => process.stdout.write(JSON.stringify(value) + '\n');
17
+ if (typeof fetch !== 'function') {
18
+ send({ kind: 'failure', error: 'OpenCode approval streaming requires guest Node.js 18 or newer with built-in fetch.' });
19
+ process.exit(1);
20
+ }
21
+ let runtimeConfig;
22
+ try {
23
+ const config = JSON.parse(process.env.OPENCODE_CONFIG_CONTENT || '{}');
24
+ const rules = JSON.parse(process.argv[2]);
25
+ const permissionObject = value => typeof value === 'string' ? { '*': value } : { ...value };
26
+ if (major === 1) {
27
+ const permission = permissionObject(config.permission);
28
+ for (const rule of rules) {
29
+ if (rule.permission === 'question') {
30
+ permission.question = rule.action;
31
+ continue;
32
+ }
33
+ const patterns = permissionObject(permission[rule.permission]);
34
+ patterns[rule.pattern] = rule.action;
35
+ permission[rule.permission] = patterns;
36
+ }
37
+ const agents = { ...config.agent };
38
+ agents.plan = { ...agents.plan, permission: { ...permissionObject(agents.plan?.permission), edit: 'deny', question: 'deny' } };
39
+ runtimeConfig = { ...config, permission, agent: agents };
40
+ } else {
41
+ const agents = { ...config.agents };
42
+ agents.plan = { ...agents.plan, permissions: [...agents.plan?.permissions || [], { action: 'edit', resource: '*', effect: 'deny' }, { action: 'question', resource: '*', effect: 'deny' }] };
43
+ runtimeConfig = { ...config, permissions: [...config.permissions || [], ...rules], agents };
44
+ }
45
+ } catch (error) {
46
+ send({ kind: 'failure', error: 'Cannot configure OpenCode runtime permissions: ' + error.message });
47
+ process.exit(1);
48
+ }
49
+ let endpoint;
50
+ let closing = false;
51
+ let eventAbort;
52
+ let killTimer;
53
+ const server = spawn('opencode', ['serve', '--hostname', '127.0.0.1', '--port', '0'], {
54
+ env: { ...process.env, OPENCODE_SERVER_USERNAME: 'opencode', OPENCODE_SERVER_PASSWORD: password, OPENCODE_CONFIG_CONTENT: JSON.stringify(runtimeConfig) },
55
+ stdio: ['ignore', 'pipe', 'pipe'],
56
+ });
57
+ const stop = code => {
58
+ if (closing) {
59
+ return;
60
+ }
61
+ closing = true;
62
+ process.exitCode = code;
63
+ eventAbort?.abort();
64
+ server.kill('SIGTERM');
65
+ killTimer = setTimeout(() => server.kill('SIGKILL'), 2000);
66
+ };
67
+ server.on('error', error => {
68
+ send({ kind: 'failure', error: error.message });
69
+ stop(1);
70
+ });
71
+ server.on('close', code => {
72
+ clearTimeout(killTimer);
73
+ if (!closing) {
74
+ send({ kind: 'failure', error: 'OpenCode server exited with code ' + code });
75
+ }
76
+ process.exit(closing ? process.exitCode || 0 : 1);
77
+ });
78
+ // Both releases print their listening URL. Only trust an explicit loopback URL.
79
+ let startup = '';
80
+ const observe = chunk => {
81
+ startup = (startup + chunk.toString()).slice(-65536);
82
+ const match = startup.match(/http:\/\/127\.0\.0\.1:(\d+)/);
83
+ if (!endpoint && match) {
84
+ endpoint = 'http://127.0.0.1:' + match[1];
85
+ void fetch(endpoint + (major === 2 ? '/api/info' : '/session'), {
86
+ signal: AbortSignal.timeout(5000), redirect: 'error',
87
+ }).then(response => {
88
+ if (response.status !== 401) {
89
+ throw new Error('OpenCode server does not enforce per-run HTTP authentication');
90
+ }
91
+ send({ kind: 'listening', major, directory: process.cwd() });
92
+ }).catch(error => {
93
+ send({ kind: 'failure', error: error.message });
94
+ stop(1);
95
+ });
96
+ }
97
+ };
98
+ server.stdout.on('data', observe);
99
+ server.stderr.on('data', chunk => {
100
+ observe(chunk);
101
+ process.stderr.write(chunk);
102
+ });
103
+ const http = async (path, method, body, signal) => {
104
+ if (!endpoint) {
105
+ throw new Error('OpenCode server is not listening');
106
+ }
107
+ const response = await fetch(endpoint + path, {
108
+ method, headers, body: body === undefined ? undefined : JSON.stringify(body),
109
+ signal: signal || AbortSignal.timeout(30000), redirect: 'error',
110
+ });
111
+ if (!response.ok) {
112
+ throw new Error('OpenCode HTTP ' + response.status + ': ' + (await response.text()).slice(0, 4096));
113
+ }
114
+ return response;
115
+ };
116
+ const subscribe = async () => {
117
+ eventAbort = new AbortController();
118
+ const response = await http(major === 2 ? '/api/event' : '/event', 'GET', undefined, eventAbort.signal);
119
+ if (!response.body || !response.headers.get('content-type')?.includes('text/event-stream')) {
120
+ throw new Error('OpenCode server does not support the expected SSE API');
121
+ }
122
+ const reader = response.body.getReader();
123
+ const decoder = new TextDecoder();
124
+ let buffer = '';
125
+ let connected = false;
126
+ while (!closing) {
127
+ const { done, value } = await reader.read();
128
+ if (done) {
129
+ throw new Error('OpenCode event stream disconnected');
130
+ }
131
+ buffer += decoder.decode(value, { stream: true });
132
+ buffer = buffer.replace(/\r\n/g, '\n');
133
+ let end;
134
+ while ((end = buffer.indexOf('\n\n')) >= 0) {
135
+ const frame = buffer.slice(0, end);
136
+ buffer = buffer.slice(end + 2);
137
+ const data = frame.split('\n').filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\n');
138
+ if (!data) {
139
+ continue;
140
+ }
141
+ let event = JSON.parse(data);
142
+ // Effect's v2 encoded event schema can be JSON nested inside an SSE JSON string.
143
+ if (typeof event === 'string') {
144
+ event = JSON.parse(event);
145
+ }
146
+ if (event.type === 'server.connected' && !connected) {
147
+ connected = true;
148
+ send({ kind: 'subscribed' });
149
+ } else {
150
+ send({ kind: 'event', event });
151
+ }
152
+ }
153
+ if (buffer.length > 16 * 1024 * 1024) {
154
+ throw new Error('OpenCode SSE frame exceeds transport limit');
155
+ }
156
+ }
157
+ };
158
+ const input = createInterface({ input: process.stdin });
159
+ input.on('line', line => {
160
+ void (async () => {
161
+ const frame = JSON.parse(line);
162
+ if (frame.kind === 'stop') {
163
+ stop(frame.code || 0);
164
+ return;
165
+ }
166
+ if (frame.kind === 'subscribe') {
167
+ void subscribe().catch(error => {
168
+ if (!closing) {
169
+ send({ kind: 'failure', error: error.message });
170
+ stop(1);
171
+ }
172
+ });
173
+ return;
174
+ }
175
+ try {
176
+ let value;
177
+ if (frame.kind === 'files') {
178
+ value = await Promise.all(frame.files.map(async file => {
179
+ const path = resolve(file);
180
+ const info = await stat(path);
181
+ const mime = info.isDirectory() ? 'application/x-directory' : ({ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf' }[extname(path).toLowerCase()] || 'text/plain');
182
+ return { type: 'file', url: pathToFileURL(path).href, filename: basename(path), mime };
183
+ }));
184
+ } else {
185
+ const response = await http(frame.path, frame.method || 'GET', frame.body);
186
+ const text = await response.text();
187
+ value = text ? JSON.parse(text) : null;
188
+ }
189
+ send({ kind: 'response', id: frame.id, value });
190
+ } catch (error) {
191
+ send({ kind: 'response', id: frame.id, error: error.message });
192
+ }
193
+ })().catch(error => {
194
+ send({ kind: 'failure', error: error.message });
195
+ stop(1);
196
+ });
197
+ });
198
+ input.on('close', () => stop(0));
199
+ process.on('SIGTERM', () => stop(0));
200
+ process.on('SIGINT', () => stop(0));
201
+ `;
202
+ export const buildOpencodeSessionPermissionRules = (major, _mode) => {
203
+ const rules = [
204
+ { permission: 'read', pattern: '*.env', action: 'ask' },
205
+ { permission: 'read', pattern: '*.env.*', action: 'ask' },
206
+ { permission: 'external_directory', pattern: '*', action: 'ask' },
207
+ { permission: 'question', pattern: '*', action: 'deny' },
208
+ ];
209
+ return major === 1 ? rules : rules.map(rule => ({ action: rule.permission, resource: rule.pattern, effect: rule.action }));
210
+ };
211
+ export const buildOpencodeApprovalInvocation = (compatibility, mode) => ({
212
+ command: 'node',
213
+ args: ['--input-type=module', '-e', GUEST_RELAY, String(compatibility.runtimeIdentity.major), JSON.stringify([
214
+ ...buildOpencodeSessionPermissionRules(compatibility.runtimeIdentity.major, mode),
215
+ // Plan edits are invocation-scoped. Never leave this deny in durable session rules.
216
+ ...(mode === 'plan' ? [compatibility.runtimeIdentity.major === 1
217
+ ? { permission: 'edit', pattern: '*', action: 'deny' }
218
+ : { action: 'edit', resource: '*', effect: 'deny' }] : []),
219
+ ])],
220
+ stdio: ['pipe', 'pipe', 'pipe'],
221
+ stdin: null,
222
+ sessionId: null,
223
+ });
224
+ const object = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {});
225
+ const text = (value) => typeof value === 'string' ? value : '';
226
+ const deferred = () => {
227
+ let resolve;
228
+ let reject;
229
+ const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
230
+ // Callers currently observe sessionReady but not always ready.
231
+ void promise.catch(() => { });
232
+ return { promise, resolve, reject };
233
+ };
234
+ export const connectOpencodeStreamingInvocation = (child, request, _context) => {
235
+ if (!request.opencodeApprovalContext) {
236
+ return {
237
+ stdout: child.stdout,
238
+ // Let orchestration use its original placement-aware process interrupt.
239
+ interrupt: async () => { throw new Error('Use the CLI execution handle to interrupt this invocation.'); },
240
+ sessionReady: Promise.resolve(request.resumeSessionId ?? ''),
241
+ ready: Promise.resolve(request.resumeSessionId ?? ''),
242
+ };
243
+ }
244
+ if (!child.stdin || !child.stdout) {
245
+ child.kill('SIGTERM');
246
+ throw new Error('OpenCode approval transport requires guest Node.js with piped stdin/stdout.');
247
+ }
248
+ return connectOpencodeApprovalTransport(child, request);
249
+ };
250
+ const loadBroker = () => {
251
+ const modulePath = './opencodeToolApprovals.js';
252
+ return import(modulePath);
253
+ };
254
+ export const connectOpencodeApprovalTransport = (child, request, approvalBroker = loadBroker()) => {
255
+ const stdout = new PassThrough();
256
+ const session = deferred();
257
+ const ready = deferred();
258
+ const subscribed = deferred();
259
+ const pending = new Map();
260
+ const approvals = new Set();
261
+ const seenApprovals = new Set();
262
+ const nativelySettled = new Set();
263
+ const replying = new Set();
264
+ const reconciling = new Set();
265
+ const sessions = new Set();
266
+ const emitted = new Map();
267
+ const tools = new Map();
268
+ const roles = new Map();
269
+ const bufferedParts = new Map();
270
+ const decoder = new StringDecoder('utf8');
271
+ let sequence = 0;
272
+ let remainder = '';
273
+ let nativeSessionId = request.resumeSessionId ?? '';
274
+ let nativeDirectory = request.cwd;
275
+ let stopped = false;
276
+ let closing = false;
277
+ let promptStarted = false;
278
+ let active = false;
279
+ let admitted = false;
280
+ let idlePending = false;
281
+ let v2ReplyField = null;
282
+ let major = request.opencodeCompatibility?.runtimeIdentity.major ?? request.opencodeRuntimeIdentity?.major;
283
+ const context = request.opencodeApprovalContext;
284
+ const mode = request.mode ?? 'build';
285
+ const broker = approvalBroker;
286
+ void broker.catch(error => finish(error instanceof Error ? error : new Error(String(error))));
287
+ const write = (frame) => {
288
+ if (!stopped) {
289
+ child.stdin.write(`${JSON.stringify(frame)}\n`);
290
+ }
291
+ };
292
+ const rpc = (frame) => {
293
+ if (stopped || (closing && !/\/(abort|interrupt)$/.test(text(frame.path)))) {
294
+ return Promise.reject(new Error('OpenCode transport is closed.'));
295
+ }
296
+ const result = deferred();
297
+ const id = ++sequence;
298
+ pending.set(id, result);
299
+ write({ ...frame, id });
300
+ return result.promise;
301
+ };
302
+ const api = (path, body, method = body === undefined ? 'GET' : 'POST') => rpc({ path, body, method });
303
+ const emit = (type, part, error, sessionID = nativeSessionId) => {
304
+ stdout.write(`${JSON.stringify({ type, timestamp: Date.now(), sessionID, ...(part ? { part } : {}), ...(error ? { error } : {}) })}\n`);
305
+ };
306
+ const cancel = (reason) => {
307
+ void broker.then(module => module.cancelOpencodeToolApprovals(context, reason)).catch(() => { });
308
+ };
309
+ const finish = (error) => {
310
+ if (stopped) {
311
+ return;
312
+ }
313
+ closing = true;
314
+ cancel(error?.message ?? 'OpenCode invocation completed.');
315
+ if (error) {
316
+ emit('error', undefined, { name: 'TransportError', data: { message: error.message } });
317
+ session.reject(error);
318
+ ready.reject(error);
319
+ }
320
+ subscribed.reject(error ?? new Error('OpenCode invocation completed.'));
321
+ write({ kind: 'stop', code: error ? 1 : 0 });
322
+ stopped = true;
323
+ clearTimeout(startupTimeout);
324
+ for (const result of pending.values()) {
325
+ result.reject(error ?? new Error('OpenCode transport is closed.'));
326
+ }
327
+ pending.clear();
328
+ stdout.end();
329
+ };
330
+ const startupTimeout = setTimeout(() => finish(new Error('OpenCode approval server did not become ready within 30 seconds.')), 30_000);
331
+ const finishIfIdle = () => {
332
+ if (!closing && admitted && idlePending && approvals.size === 0 && replying.size === 0 && reconciling.size === 0) {
333
+ finish();
334
+ }
335
+ };
336
+ const emitPart = (part) => {
337
+ const type = text(part.type);
338
+ const key = `${text(part.sessionID)}:${text(part.id)}`;
339
+ const state = object(part.state);
340
+ if (type === 'text' || type === 'reasoning') {
341
+ // Match CLI output: one completed full-value part, not cumulative snapshots.
342
+ if (!object(part.time).end) {
343
+ return;
344
+ }
345
+ }
346
+ else if (type === 'tool' && state.status !== 'completed' && state.status !== 'error') {
347
+ return;
348
+ }
349
+ const normalized = { ...part, ...(type === 'tool' ? { title: state.title ?? part.title } : {}) };
350
+ const fingerprint = JSON.stringify(normalized);
351
+ if (emitted.get(key) === fingerprint) {
352
+ return;
353
+ }
354
+ const eventType = { 'step-start': 'step_start', 'step-finish': 'step_finish', tool: 'tool_use', text: 'text', reasoning: 'reasoning' }[type];
355
+ if (eventType) {
356
+ emitted.set(key, fingerprint);
357
+ emit(eventType, normalized, undefined, text(part.sessionID));
358
+ }
359
+ };
360
+ const consume = (input) => {
361
+ if (stopped || closing) {
362
+ return;
363
+ }
364
+ let event = object(input);
365
+ if (event.payload) {
366
+ event = object(event.payload);
367
+ }
368
+ // v2.0.7 uses session.*; older compatibility bridges used session.next.*.
369
+ const type = text(event.type).replace(/^session\.next\./, 'session.');
370
+ if (major !== 2 && /^session\.(?:next\.|text\.|reasoning\.|step\.|tool\.|execution\.)/.test(text(event.type))) {
371
+ return;
372
+ }
373
+ const data = object(event.properties ?? event.data);
374
+ const part = object(data.part);
375
+ const sid = text(data.sessionID ?? part.sessionID ?? object(data.info).sessionID);
376
+ if (type === 'session.created') {
377
+ const info = object(data.info ?? data);
378
+ if (sessions.has(text(info.parentID))) {
379
+ sessions.add(text(info.id ?? info.sessionID));
380
+ }
381
+ }
382
+ if (!sessions.has(sid)) {
383
+ return;
384
+ }
385
+ if (type === 'permission.replied' || type === 'permission.v2.replied') {
386
+ const id = text(data.requestID);
387
+ const reply = data.reply;
388
+ if (!id || nativelySettled.has(id) || !['once', 'always', 'reject'].includes(text(reply))) {
389
+ return;
390
+ }
391
+ nativelySettled.add(id);
392
+ seenApprovals.add(id);
393
+ reconciling.add(id);
394
+ void broker.then(module => module.settleOpencodeToolApproval(context, sid, id, reply)).then(() => {
395
+ reconciling.delete(id);
396
+ if (!replying.has(id)) {
397
+ approvals.delete(id);
398
+ }
399
+ finishIfIdle();
400
+ }).catch(error => finish(error instanceof Error ? error : new Error(String(error))));
401
+ return;
402
+ }
403
+ if (type === 'permission.asked' || type === 'permission.v2.asked') {
404
+ const id = text(data.id);
405
+ if (!id || seenApprovals.has(id)) {
406
+ return;
407
+ }
408
+ const nativeV2 = major === 2 || type === 'permission.v2.asked';
409
+ const patterns = stringArray(data.patterns ?? data.resources);
410
+ const grantPatterns = stringArray(data.always);
411
+ const sessionScope = !nativeV2 && grantPatterns.length > 0 && samePatterns(patterns, grantPatterns);
412
+ seenApprovals.add(id);
413
+ approvals.add(id);
414
+ void broker.then(module => {
415
+ if (closing || stopped || nativelySettled.has(id)) {
416
+ return undefined;
417
+ }
418
+ return module.requestOpencodeToolApproval(context, {
419
+ nativeRequestId: id,
420
+ nativeSessionId: sid,
421
+ permission: text(data.permission ?? data.action),
422
+ patterns,
423
+ metadata: object(data.metadata),
424
+ supportedScopes: sessionScope ? ['once', 'session'] : ['once'],
425
+ });
426
+ }).then(async (reply) => {
427
+ if (closing || stopped || nativelySettled.has(id) || !reply) {
428
+ return;
429
+ }
430
+ if (!sessionScope && reply === 'always') {
431
+ throw new Error('OpenCode native grant patterns do not support this session-scoped approval.');
432
+ }
433
+ if (nativeV2 && !v2ReplyField) {
434
+ throw new Error('OpenCode server does not expose a supported v2 permission reply schema.');
435
+ }
436
+ replying.add(id);
437
+ try {
438
+ await api(nativeV2
439
+ ? `/api/session/${encodeURIComponent(sid)}/permission/${encodeURIComponent(id)}/reply`
440
+ : `/permission/${encodeURIComponent(id)}/reply`, nativeV2 ? { [v2ReplyField]: reply } : { reply });
441
+ }
442
+ catch (error) {
443
+ if (!nativelySettled.has(id)) {
444
+ throw error;
445
+ }
446
+ }
447
+ finally {
448
+ replying.delete(id);
449
+ }
450
+ approvals.delete(id);
451
+ finishIfIdle();
452
+ }).catch(error => finish(error instanceof Error ? error : new Error(String(error))));
453
+ return;
454
+ }
455
+ if (type === 'session.execution.started') {
456
+ active = true;
457
+ idlePending = false;
458
+ return;
459
+ }
460
+ if (type === 'session.execution.succeeded' || type === 'session.execution.failed' || type === 'session.execution.interrupted') {
461
+ if (type !== 'session.execution.succeeded') {
462
+ emit('error', undefined, data.error ?? { message: 'OpenCode execution interrupted.' }, sid);
463
+ }
464
+ if (sid === nativeSessionId) {
465
+ idlePending = true;
466
+ finishIfIdle();
467
+ }
468
+ return;
469
+ }
470
+ if (type === 'session.error' || type === 'session.step.failed') {
471
+ active = true;
472
+ emit('error', undefined, data.error, sid);
473
+ return;
474
+ }
475
+ if (sid === nativeSessionId && promptStarted && (type === 'session.status' || type === 'session.idle')) {
476
+ const idle = type === 'session.idle' || object(data.status).type === 'idle';
477
+ if (!idle) {
478
+ active = true;
479
+ idlePending = false;
480
+ }
481
+ else if (active) {
482
+ idlePending = true;
483
+ finishIfIdle();
484
+ }
485
+ return;
486
+ }
487
+ if (type === 'message.updated') {
488
+ if (major === 2) {
489
+ return;
490
+ }
491
+ const info = object(data.info);
492
+ const id = text(info.id);
493
+ roles.set(id, text(info.role));
494
+ if (sid === nativeSessionId && info.role === 'assistant') {
495
+ active = true;
496
+ }
497
+ for (const buffered of bufferedParts.get(id) ?? []) {
498
+ if (info.role === 'assistant') {
499
+ emitPart(buffered);
500
+ }
501
+ }
502
+ bufferedParts.delete(id);
503
+ return;
504
+ }
505
+ if (type === 'message.part.updated') {
506
+ if (major === 2) {
507
+ return;
508
+ }
509
+ const id = text(part.messageID);
510
+ if (!roles.has(id)) {
511
+ bufferedParts.set(id, [...bufferedParts.get(id) ?? [], part]);
512
+ }
513
+ else if (roles.get(id) === 'assistant') {
514
+ emitPart(part);
515
+ }
516
+ return;
517
+ }
518
+ // v2 emits native semantic events rather than legacy message parts.
519
+ const toolId = text(data.callID ?? data.id);
520
+ const base = { id: text(data.textID ?? data.reasoningID ?? data.callID ?? data.id) || `${text(data.assistantMessageID)}:${type.split('.')[1]}:${data.ordinal ?? ''}`, sessionID: sid, messageID: data.assistantMessageID };
521
+ if (type === 'session.text.ended' || type === 'session.reasoning.ended') {
522
+ const kind = type.includes('.reasoning.') ? 'reasoning' : 'text';
523
+ emit(kind, { ...base, type: kind, text: data.text }, undefined, sid);
524
+ }
525
+ else if (type === 'session.step.started') {
526
+ active = true;
527
+ emit('step_start', { ...base, type: 'step-start' }, undefined, sid);
528
+ }
529
+ else if (type === 'session.step.ended') {
530
+ emit('step_finish', { ...base, type: 'step-finish', reason: data.finish, tokens: data.tokens, cost: data.cost }, undefined, sid);
531
+ }
532
+ else if (type === 'session.tool.input.started') {
533
+ tools.set(toolId, { ...base, type: 'tool', callID: toolId, tool: data.name, state: { status: 'pending', input: {} } });
534
+ }
535
+ else if (type === 'session.tool.called') {
536
+ tools.set(toolId, { ...tools.get(toolId), ...base, type: 'tool', callID: toolId, tool: data.tool ?? tools.get(toolId)?.tool, state: { status: 'running', input: data.input } });
537
+ }
538
+ else if (type === 'session.tool.success' || type === 'session.tool.failed') {
539
+ const tool = tools.get(toolId);
540
+ if (tool) {
541
+ const content = Array.isArray(data.content) ? data.content : [];
542
+ emit('tool_use', { ...tool, state: { ...object(tool.state), status: data.error ? 'error' : 'completed', error: data.error,
543
+ output: content.map(item => text(object(item).text)).filter(Boolean).join('\n'), metadata: data.metadata ?? data.structured } }, undefined, sid);
544
+ tools.delete(toolId);
545
+ }
546
+ }
547
+ };
548
+ const start = async () => {
549
+ // Derive the observed API generation in-guest when caller uses cached host compatibility.
550
+ if (!major) {
551
+ try {
552
+ const health = object(await api('/global/health'));
553
+ major = text(health.version).startsWith('1.') ? 1 : undefined;
554
+ }
555
+ catch {
556
+ major = 2;
557
+ }
558
+ }
559
+ if (major !== 1 && major !== 2) {
560
+ throw new Error('OpenCode server returned an unsupported API version.');
561
+ }
562
+ write({ kind: 'subscribe' });
563
+ await subscribed.promise;
564
+ const model = resolveModel(request, major);
565
+ const root = major === 2 ? '/api/session' : '/session';
566
+ const spec = object(await api('/doc'));
567
+ const createProperties = operationProperties(spec, root, 'post');
568
+ const patchProperties = operationProperties(spec, `${root}/{sessionID}`, 'patch');
569
+ const permissionField = major === 1 ? 'permission' : 'permissions';
570
+ if (!(permissionField in createProperties) || !(permissionField in patchProperties)) {
571
+ throw new Error('OpenCode server does not support required create/PATCH session permission rules.');
572
+ }
573
+ const replyProperties = operationProperties(spec, '/api/session/{sessionID}/permission/{requestID}/reply', 'post');
574
+ v2ReplyField = 'decision' in replyProperties ? 'decision' : 'reply' in replyProperties ? 'reply' : null;
575
+ const rules = buildOpencodeSessionPermissionRules(major, mode);
576
+ const result = object(await api(nativeSessionId ? `${root}/${encodeURIComponent(nativeSessionId)}` : root, nativeSessionId ? undefined : { [permissionField]: rules, ...(major === 2 ? { location: { directory: nativeDirectory }, agent: mode, model } : {}) }));
577
+ const info = major === 2 ? object(result.data) : result;
578
+ nativeSessionId = text(info.id);
579
+ if (!nativeSessionId) {
580
+ throw new Error('OpenCode server did not return a native session ID.');
581
+ }
582
+ sessions.add(nativeSessionId);
583
+ const path = `${root}/${encodeURIComponent(nativeSessionId)}`;
584
+ if (request.resumeSessionId) {
585
+ const existing = info[permissionField];
586
+ if (existing !== undefined && existing !== null && !Array.isArray(existing)) {
587
+ throw new Error('OpenCode session returned malformed permission rules.');
588
+ }
589
+ // v1 PATCH appends natively; v2 replaces the permissions field.
590
+ await api(path, { [permissionField]: major === 1 ? rules : [...Array.isArray(existing) ? existing : [], ...rules] }, 'PATCH');
591
+ }
592
+ session.resolve(nativeSessionId);
593
+ if (major === 2 && request.resumeSessionId) {
594
+ await api(`${path}/agent`, { agent: mode });
595
+ if (model) {
596
+ await api(`${path}/model`, { model });
597
+ }
598
+ }
599
+ const files = await rpc({ kind: 'files', files: request.files ?? [] });
600
+ promptStarted = true;
601
+ await api(`${path}/${major === 2 ? 'prompt' : 'prompt_async'}`, major === 2
602
+ ? { text: request.prompt, files: files.map(file => ({ uri: file.url, name: file.filename })) }
603
+ : { agent: mode, model, variant: request.variant?.trim() || undefined, parts: [...files, { type: 'text', text: request.prompt }] });
604
+ clearTimeout(startupTimeout);
605
+ admitted = true;
606
+ ready.resolve(nativeSessionId);
607
+ finishIfIdle();
608
+ };
609
+ const onData = (chunk) => {
610
+ remainder += typeof chunk === 'string' ? chunk : decoder.write(chunk);
611
+ const lines = remainder.split('\n');
612
+ remainder = lines.pop() ?? '';
613
+ try {
614
+ for (const line of lines) {
615
+ if (!line.trim()) {
616
+ continue;
617
+ }
618
+ const frame = object(JSON.parse(line));
619
+ if (frame.kind === 'listening') {
620
+ if (typeof frame.directory === 'string' && frame.directory) {
621
+ nativeDirectory = frame.directory;
622
+ }
623
+ if (frame.major === 1 || frame.major === 2) {
624
+ if (major && major !== frame.major) {
625
+ throw new Error('OpenCode relay API generation does not match the selected runtime.');
626
+ }
627
+ major = frame.major;
628
+ }
629
+ void start().catch(error => finish(error instanceof Error ? error : new Error(String(error))));
630
+ }
631
+ else if (frame.kind === 'subscribed') {
632
+ subscribed.resolve();
633
+ }
634
+ else if (frame.kind === 'event') {
635
+ consume(frame.event);
636
+ }
637
+ else if (frame.kind === 'response') {
638
+ const result = pending.get(Number(frame.id));
639
+ pending.delete(Number(frame.id));
640
+ if (frame.error) {
641
+ result?.reject(new Error(text(frame.error)));
642
+ }
643
+ else {
644
+ result?.resolve(frame.value);
645
+ }
646
+ }
647
+ else if (frame.kind === 'failure') {
648
+ finish(new Error(text(frame.error)));
649
+ }
650
+ }
651
+ }
652
+ catch (error) {
653
+ finish(error instanceof Error ? error : new Error(String(error)));
654
+ }
655
+ };
656
+ child.stdout.on('data', onData);
657
+ child.stdout.once('end', () => finish(new Error('OpenCode relay disconnected.')));
658
+ child.once('error', error => finish(error));
659
+ child.once('close', () => finish(new Error('OpenCode relay closed.')));
660
+ child.stdin.on('error', error => finish(error));
661
+ return {
662
+ stdout,
663
+ ready: ready.promise,
664
+ sessionReady: session.promise,
665
+ interrupt: async () => {
666
+ if (stopped || closing) {
667
+ return;
668
+ }
669
+ closing = true;
670
+ cancel('OpenCode invocation stopped.');
671
+ try {
672
+ if (nativeSessionId) {
673
+ await api(major === 2 ? `/api/session/${encodeURIComponent(nativeSessionId)}/interrupt` : `/session/${encodeURIComponent(nativeSessionId)}/abort`, {});
674
+ }
675
+ }
676
+ finally {
677
+ finish(new Error('OpenCode invocation stopped.'));
678
+ }
679
+ },
680
+ };
681
+ };
682
+ const stringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
683
+ const samePatterns = (left, right) => {
684
+ const a = new Set(left);
685
+ const b = new Set(right);
686
+ return a.size === b.size && [...a].every(pattern => b.has(pattern));
687
+ };
688
+ const operationProperties = (spec, path, method) => {
689
+ const operation = object(object(object(spec.paths)[path])[method]);
690
+ let schema = object(object(object(object(operation.requestBody).content)['application/json']).schema);
691
+ if (typeof schema.$ref === 'string') {
692
+ schema = object(object(object(spec.components).schemas)[schema.$ref.split('/').at(-1)]);
693
+ }
694
+ return object(schema.properties);
695
+ };
696
+ const resolveModel = (request, major) => {
697
+ const value = request.model?.trim();
698
+ if (!value) {
699
+ if (request.variant?.trim() && major === 2) {
700
+ throw new Error('OpenCode v2 variant selection requires a model.');
701
+ }
702
+ return undefined;
703
+ }
704
+ const slash = value.indexOf('/');
705
+ if (slash <= 0 || slash === value.length - 1) {
706
+ throw new Error('OpenCode model selection requires provider/model.');
707
+ }
708
+ const [id, suffix] = value.slice(slash + 1).split('#');
709
+ return major === 2
710
+ ? { providerID: value.slice(0, slash), id, ...(request.variant?.trim() || suffix ? { variant: request.variant?.trim() || suffix } : {}) }
711
+ : { providerID: value.slice(0, slash), modelID: id };
712
+ };
713
+ //# sourceMappingURL=opencodeApprovalTransport.js.map