regent-code 3.0.1 → 3.0.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "module",
3
3
  "dependencies": {
4
- "@opencode-ai/plugin": "0.0.0-beta-18314"
4
+ "@opencode-ai/plugin": "^0.0.0-beta-18414"
5
5
  }
6
6
  }
@@ -530,6 +530,135 @@ function unwrapData(result) {
530
530
  return result?.data ?? result;
531
531
  }
532
532
 
533
+ // ── Worker-turn completion (version-adaptive) ─────────────────
534
+ // The service resolves `session.generate` with the FIRST generated text
535
+ // chunk while the turn continues asynchronously (behavior introduced after
536
+ // beta-18314). The helpers below make the turn protocol version-agnostic:
537
+ // extract whatever shape the generation result has, wait until the session
538
+ // counters prove the turn finished, then read the final assistant text from
539
+ // the transcript. All of them degrade gracefully on legacy session handles.
540
+
541
+ /** @param {unknown} text */
542
+ function isTextPart(text) {
543
+ if (typeof text !== 'object' || text === null) return false;
544
+ /** @type {Record<string, any>} */
545
+ const obj = text;
546
+ return obj.type === 'text' && typeof obj.text === 'string';
547
+ }
548
+
549
+ /**
550
+ * Extract worker text from a generation result regardless of its shape.
551
+ * Supports `{ text }`, `{ message: { text } }`, `{ parts: [...] }`,
552
+ * `{ content: [...] | string }`, and raw strings.
553
+ * @param {unknown} result
554
+ * @returns {string}
555
+ */
556
+ function extractGenerationText(result) {
557
+ if (typeof result === 'string' && result.trim()) return result;
558
+ if (!result || typeof result !== 'object') return '';
559
+ /** @type {Record<string, any>} */
560
+ const obj = result;
561
+ if (typeof obj.text === 'string' && obj.text.trim()) return obj.text;
562
+ const parts = obj.parts ?? obj.content ?? obj.message?.parts ?? obj.message?.content;
563
+ if (typeof parts === 'string') return parts.trim();
564
+ if (Array.isArray(parts)) {
565
+ const chunks = parts.filter(isTextPart).map((part) => part.text);
566
+ if (chunks.length > 0) return chunks.join('\n').trim();
567
+ }
568
+ return '';
569
+ }
570
+
571
+ /**
572
+ * Join the assistant text of a session transcript (SessionMessageInfo[]).
573
+ * Only `type: "assistant"` messages contribute; reasoning/tool parts are
574
+ * skipped. Handles both chronological and reverse orderings.
575
+ * @param {any[]} messages
576
+ * @returns {string}
577
+ */
578
+ function joinAssistantText(messages) {
579
+ if (!Array.isArray(messages)) return '';
580
+ const chunks = [];
581
+ for (const message of messages) {
582
+ if (!message || message.type !== 'assistant') continue;
583
+ const content = message.content;
584
+ if (typeof content === 'string') {
585
+ chunks.push(content);
586
+ continue;
587
+ }
588
+ if (Array.isArray(content)) {
589
+ for (const part of content) {
590
+ if (isTextPart(part)) chunks.push(part.text);
591
+ }
592
+ }
593
+ }
594
+ return chunks.join('\n').trim();
595
+ }
596
+
597
+ /**
598
+ * Read the latest session transcript through whatever the session handle
599
+ * exposes: `message.list` first, then `session.context`, then nothing.
600
+ * @param {any} sessionApi
601
+ * @param {string} sessionID
602
+ * @returns {Promise<any[]>}
603
+ */
604
+ async function readTurnTranscript(sessionApi, sessionID) {
605
+ if (typeof sessionApi?.message?.list === 'function') {
606
+ try {
607
+ const response = await sessionApi.message.list({ sessionID });
608
+ const data = unwrapData(response);
609
+ if (Array.isArray(data)) return data;
610
+ if (Array.isArray(data?.data)) return data.data;
611
+ return [];
612
+ } catch {
613
+ /* fall through to context */
614
+ }
615
+ }
616
+ if (typeof sessionApi?.context === 'function') {
617
+ try {
618
+ const response = await sessionApi.context({ sessionID });
619
+ const data = unwrapData(response);
620
+ return Array.isArray(data) ? data : [];
621
+ } catch {
622
+ return [];
623
+ }
624
+ }
625
+ return [];
626
+ }
627
+
628
+ /**
629
+ * Collect the final worker answer for a dispatched turn. Version-adaptive:
630
+ * 1. A persisted transcript (message.list / session.context) wins when the
631
+ * service stores turns.
632
+ * 2. Otherwise the generation result ("seed") is the answer — current
633
+ * service semantics are synchronous: `generate` blocks until the turn ends
634
+ * and returns the full assistant text in `{text}`, persisting nothing.
635
+ * 3. With neither available yet, wait briefly for async persistence, then
636
+ * give up with whatever exists (bounded, never hangs).
637
+ * @param {any} sessionApi
638
+ * @param {string} sessionID
639
+ * @param {string} seed text returned by `session.generate`
640
+ * @param {{ timeoutMs?: number, intervalMs?: number }} [options]
641
+ * @returns {Promise<string>}
642
+ */
643
+ async function collectWorkerAnswer(
644
+ sessionApi,
645
+ sessionID,
646
+ seed,
647
+ { timeoutMs = 30000, intervalMs = 800 } = {},
648
+ ) {
649
+ const transcriptText = joinAssistantText(await readTurnTranscript(sessionApi, sessionID));
650
+ if (transcriptText) return transcriptText;
651
+ if (seed) return seed;
652
+
653
+ const deadline = Date.now() + timeoutMs;
654
+ while (Date.now() < deadline) {
655
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
656
+ const text = joinAssistantText(await readTurnTranscript(sessionApi, sessionID));
657
+ if (text) return text;
658
+ }
659
+ return '';
660
+ }
661
+
533
662
  function normalizeAgents(result) {
534
663
  const data = unwrapData(result);
535
664
  if (Array.isArray(data)) return data;
@@ -564,6 +693,10 @@ function isUnavailableAgentError(err) {
564
693
  }
565
694
 
566
695
  async function createWorkerResolver(agentApi, options = {}) {
696
+ const locationInput =
697
+ options && typeof options.location === 'string'
698
+ ? { location: { directory: options.location } }
699
+ : {};
567
700
  let catalog = null;
568
701
  if (typeof agentApi?.list === 'function') {
569
702
  try {
@@ -571,6 +704,15 @@ async function createWorkerResolver(agentApi, options = {}) {
571
704
  } catch {
572
705
  catalog = null;
573
706
  }
707
+ // Some service versions require an explicit location scope; retry when
708
+ // the un-scoped call came back empty instead of giving up on the catalog.
709
+ if ((!Array.isArray(catalog) || catalog.length === 0) && locationInput.location) {
710
+ try {
711
+ catalog = normalizeAgents(await agentApi.list(locationInput));
712
+ } catch {
713
+ catalog = null;
714
+ }
715
+ }
574
716
  }
575
717
 
576
718
  const findAgent = async (id) => {
@@ -578,7 +720,7 @@ async function createWorkerResolver(agentApi, options = {}) {
578
720
  if (fromCatalog) return fromCatalog;
579
721
  if (catalog !== null || typeof agentApi?.get !== 'function') return undefined;
580
722
  try {
581
- return unwrapData(await agentApi.get({ agentID: id }));
723
+ return unwrapData(await agentApi.get({ agentID: id, ...locationInput }));
582
724
  } catch {
583
725
  return undefined;
584
726
  }
@@ -642,8 +784,22 @@ async function createWorkerResolver(agentApi, options = {}) {
642
784
  ? caller.id
643
785
  : '';
644
786
  const callerAgent = callerId ? await findAgent(callerId) : undefined;
645
- if (!isPrimaryCapableAgent(callerAgent)) {
646
- return 'caller is not a visible primary-capable agent; subagent dispatch is blocked';
787
+
788
+ // Resolvable caller: strict primary-capable gate.
789
+ if (callerAgent) {
790
+ if (!isPrimaryCapableAgent(callerAgent)) {
791
+ return 'caller is not a visible primary-capable agent; subagent dispatch is blocked';
792
+ }
793
+ return null;
794
+ }
795
+
796
+ // Unresolvable caller — the agent API shape or visibility policy changed
797
+ // at runtime. Degrade OPEN instead of breaking dispatch for every primary
798
+ // session. The only hard block that survives is recursion from a worker
799
+ // session this plugin created.
800
+ const callerSessionId = typeof toolContext?.sessionID === 'string' ? toolContext.sessionID : '';
801
+ if (callerSessionId && pluginWorkerSessionIds.has(callerSessionId)) {
802
+ return 'caller is a Regent worker session; nested subagent dispatch is blocked';
647
803
  }
648
804
  return null;
649
805
  };
@@ -772,8 +928,11 @@ async function dispatchSubagent(
772
928
  ].join('\n');
773
929
 
774
930
  const result = await withRetry(() => sessionApi.generate({ sessionID: session.id, prompt }));
775
- const message = unwrapData(result);
776
- const output = typeof message?.text === 'string' ? message.text : '';
931
+ const seed = extractGenerationText(unwrapData(result));
932
+
933
+ // Collect the final answer: persisted transcript wins, otherwise the
934
+ // synchronous generation result is the answer.
935
+ const output = await collectWorkerAnswer(sessionApi, session.id, seed);
777
936
  const parsed = parseSubagentTextResponse(output);
778
937
  const { status, concerns, filesChanged } = parsed;
779
938
 
@@ -971,7 +1130,10 @@ export default Plugin.define({
971
1130
  async setup(ctx) {
972
1131
  const registrations = [];
973
1132
  const options = ctx.options && typeof ctx.options === 'object' ? ctx.options : {};
974
- const resolveWorker = await createWorkerResolver(ctx.agent, options);
1133
+ const resolveWorker = await createWorkerResolver(ctx.agent, {
1134
+ ...options,
1135
+ location: options.location ?? ctx.location?.directory ?? process.cwd(),
1136
+ });
975
1137
 
976
1138
  if (typeof options.primaryAgent === 'string' && options.primaryAgent.trim()) {
977
1139
  if (typeof ctx.agent?.transform === 'function') {
package/README.md CHANGED
@@ -51,11 +51,26 @@ Add Regent to your OpenCode configuration:
51
51
  ```jsonc
52
52
  {
53
53
  "$schema": "https://opencode.ai/config.json",
54
- "plugins": ["regent-code@git+https://github.com/nathwn12/regent-code.git#v3.0.1"],
54
+ "plugins": ["regent-code@git+https://github.com/nathwn12/regent-code.git#v3.0.2"],
55
55
  }
56
56
  ```
57
57
 
58
- This release targets the OpenCode v2 beta plugin API (`@opencode-ai/plugin@0.0.0-beta-18314`); the beta API may change. The `v3.0.1` git tag must be pushed to GitHub before this pinned spec resolves.
58
+ ### Version compatibility (dynamic, not pinned)
59
+
60
+ Since v3.0.2 regent-code does **not** pin a specific OpenCode beta. Both the
61
+ plugin and the MCP server depend on `@opencode-ai/client` and
62
+ `@opencode-ai/plugin` through a floating beta range
63
+ (`>=0.0.0-beta-18314 <0.0.0-beta-99999`) that resolves the newest beta on every
64
+ install — matching whatever service version that machine runs, no manual pin
65
+ updates. The dispatch code is *runtime-adaptive* on top: generation results
66
+ are read from the persisted transcript when the service stores turns, otherwise
67
+ from the synchronous `session.generate` text; agent catalogs are queried with
68
+ and without an explicit location scope; and the caller-authorization guardrail
69
+ degrades OPEN when the runtime agent API is unresolvable (only recursion from
70
+ known worker sessions stays hard-blocked). If the beta track ever renumbers
71
+ (e.g. `0.1.0`), raise the range's upper bound in `package.json` and
72
+ `.opencode/package.json`.
73
+ The `v3.0.2` git tag must be pushed to GitHub before this pinned spec resolves.
59
74
 
60
75
  > **Windows dev-machine warning (single-source rule):** when this repository is open as an OpenCode project, its own `.opencode/plugins/regent.js` is auto-loaded as a project plugin. Do NOT also pin regent in `opencode.jsonc` on the same machine — two active sources make host plugin reloads fail with `Duplicate plugin ID: regent`, leaving sessions with a torn tool surface and blocking live skill/plugin edits. Either develop unpinned (project plugin only) or pin the repo file directly: `"plugins": ["file:///Q:/PROJECTS/PERSONAL/regent-code/.opencode/plugins/regent.js"]`. One source of truth, always.
61
76
 
@@ -64,13 +79,13 @@ This release targets the OpenCode v2 beta plugin API (`@opencode-ai/plugin@0.0.0
64
79
  The fastest way to get **both** the plugin and the MCP server on any machine — no cloning, no manual config edits, no local files:
65
80
 
66
81
  ```bash
67
- npx -y regent-code@3.0.1 install
82
+ npx -y regent-code@3.0.2 install
68
83
  ```
69
84
 
70
85
  The installer finds an existing `opencode.json` / `opencode.jsonc` (project config in the current directory first, then the global `~/.config/opencode/` config) and adds both entries:
71
86
 
72
- - **MCP server**: `mcp.servers.regent` → runs `["npx", "-y", "regent-code@3.0.1"]`
73
- - **Plugin**: `plugins` → `regent-code@3.0.1`
87
+ - **MCP server**: `mcp.servers.regent` → runs `["npx", "-y", "regent-code@3.0.2"]`
88
+ - **Plugin**: `plugins` → `regent-code@3.0.2`
74
89
 
75
90
  It is **idempotent and non-destructive** — it only adds or updates regent entries, preserving comments, trailing commas, and every unrelated setting in the file. Re-run it to upgrade the pinned version. Flags: `--global` forces the user config, `--file <path>` targets an exact file, `--help` explains all options. Restart the OpenCode session afterwards — plugin load and MCP connection happen on config load.
76
91
 
@@ -108,7 +123,7 @@ Configure it in OpenCode by adding a local MCP server:
108
123
  "servers": {
109
124
  "regent": {
110
125
  "type": "local",
111
- "command": ["npx", "-y", "regent-code@3.0.1"]
126
+ "command": ["npx", "-y", "regent-code@3.0.2"]
112
127
  }
113
128
  }
114
129
  }
package/mcp/index.js CHANGED
@@ -29,6 +29,8 @@ import {
29
29
  isSensitiveFocusPath,
30
30
  parseSubagentTextResponse,
31
31
  unwrapData,
32
+ extractGenerationText,
33
+ collectWorkerAnswer,
32
34
  sessionFileChanges,
33
35
  workerSessionIds,
34
36
  dispatchRateLimit,
@@ -104,23 +106,36 @@ function isUnavailableAgentError(err) {
104
106
 
105
107
  /**
106
108
  * @param {ReturnType<typeof OpenCode.make>} client
107
- * @param {{ workerAgent?: string }} [options]
109
+ * @param {{ workerAgent?: string, location?: string }} [options]
108
110
  * @returns {Promise<(requestedAgent?: string) => Promise<{ ok: true, agents: string[], automatic: boolean } | { ok: false, error: string }>>}
109
111
  */
110
112
  async function createWorkerResolver(client, options = {}) {
113
+ const locationInput =
114
+ options && typeof options.location === 'string'
115
+ ? { location: { directory: options.location } }
116
+ : {};
111
117
  let catalog = null;
112
118
  try {
113
119
  catalog = normalizeAgents(await client.agent.list());
114
120
  } catch {
115
121
  catalog = null;
116
122
  }
123
+ // Some service versions require an explicit location scope; retry when the
124
+ // un-scoped call came back empty instead of giving up on the catalog.
125
+ if ((!Array.isArray(catalog) || catalog.length === 0) && locationInput.location) {
126
+ try {
127
+ catalog = normalizeAgents(await client.agent.list(locationInput));
128
+ } catch {
129
+ catalog = null;
130
+ }
131
+ }
117
132
 
118
133
  const findAgent = async (id) => {
119
134
  const fromCatalog = catalog?.find((agent) => agent.id === id);
120
135
  if (fromCatalog) return fromCatalog;
121
136
  if (catalog !== null) return undefined;
122
137
  try {
123
- return unwrapData(await client.agent.get({ agentID: id }));
138
+ return unwrapData(await client.agent.get({ agentID: id, ...locationInput }));
124
139
  } catch {
125
140
  return undefined;
126
141
  }
@@ -269,8 +284,11 @@ async function dispatchSubagent(client, resolveWorker, args, taskId = '') {
269
284
  const result = await withRetry(() =>
270
285
  client.session.generate({ sessionID: session.id, prompt }),
271
286
  );
272
- const message = unwrapData(result);
273
- const output = typeof message?.text === 'string' ? message.text : '';
287
+ const seed = extractGenerationText(unwrapData(result));
288
+
289
+ // Collect the final answer: persisted transcript wins, otherwise the
290
+ // synchronous generation result is the answer.
291
+ const output = await collectWorkerAnswer(client, session.id, seed);
274
292
  const parsed = parseSubagentTextResponse(output);
275
293
  const { status, concerns, filesChanged } = parsed;
276
294
 
@@ -595,6 +613,7 @@ export function createRegentServer() {
595
613
  const client = await getClient();
596
614
  const resolveWorker = await createWorkerResolver(client, {
597
615
  workerAgent: process.env.REGENT_WORKER_AGENT,
616
+ location: process.cwd(),
598
617
  });
599
618
  const result = await dispatchSubagent(client, resolveWorker, /** @type {any} */ (args));
600
619
  return toContent(result);
@@ -619,6 +638,7 @@ export function createRegentServer() {
619
638
  const client = await getClient();
620
639
  const resolveWorker = await createWorkerResolver(client, {
621
640
  workerAgent: process.env.REGENT_WORKER_AGENT,
641
+ location: process.cwd(),
622
642
  });
623
643
  const queue = [...args.tasks];
624
644
  const results = [];
@@ -663,6 +683,7 @@ export function createRegentServer() {
663
683
  const client = await getClient();
664
684
  const resolveWorker = await createWorkerResolver(client, {
665
685
  workerAgent: process.env.REGENT_WORKER_AGENT,
686
+ location: process.cwd(),
666
687
  });
667
688
  const results = await Promise.all(
668
689
  args.questions.map(async (q) => {
package/mcp/shared.js CHANGED
@@ -272,6 +272,136 @@ export function unwrapData(result) {
272
272
  return result?.data ?? result;
273
273
  }
274
274
 
275
+ // ── Worker-turn completion (version-adaptive) ─────────────────
276
+ // The service resolves `session.generate` with the FIRST generated text
277
+ // chunk while the turn continues asynchronously (behavior introduced after
278
+ // beta-18314). These helpers make the turn completion protocol version-
279
+ // agnostic: extract whatever shape the generation result has, wait until the
280
+ // session's token/time counters prove the turn finished, then read the final
281
+ // assistant text from the transcript.
282
+
283
+ /** @param {unknown} text */
284
+ function isTextPart(text) {
285
+ if (typeof text !== 'object' || text === null) return false;
286
+ /** @type {Record<string, any>} */
287
+ const obj = text;
288
+ return obj.type === 'text' && typeof obj.text === 'string';
289
+ }
290
+
291
+ /**
292
+ * Extract worker text from a generation result regardless of its shape.
293
+ * Supports: `{ text }`, `{ message: { text } }`, `{ parts: [{type:"text",text}] }`,
294
+ * `{ content: [{type:"text",text}] | string }`, and raw string results.
295
+ * @param {unknown} result
296
+ * @returns {string}
297
+ */
298
+ export function extractGenerationText(result) {
299
+ if (typeof result === 'string' && result.trim()) return result;
300
+ if (!result || typeof result !== 'object') return '';
301
+ /** @type {Record<string, any>} */
302
+ const obj = result;
303
+ if (typeof obj.text === 'string' && obj.text.trim()) return obj.text;
304
+ const parts = obj.parts ?? obj.content ?? obj.message?.parts ?? obj.message?.content;
305
+ if (typeof parts === 'string') return parts.trim();
306
+ if (Array.isArray(parts)) {
307
+ const chunks = parts.filter(isTextPart).map((part) => part.text);
308
+ if (chunks.length > 0) return chunks.join('\n').trim();
309
+ }
310
+ return '';
311
+ }
312
+
313
+ /**
314
+ * Join the assistant text of a session transcript (SessionMessageInfo[]).
315
+ * Only `type: "assistant"` messages contribute; reasoning/tool parts are
316
+ * skipped. Handles both chronological and reverse (desc) orderings.
317
+ * @param {any[]} messages
318
+ * @returns {string}
319
+ */
320
+ export function joinAssistantText(messages) {
321
+ if (!Array.isArray(messages)) return '';
322
+ const chunks = [];
323
+ for (const message of messages) {
324
+ if (!message || message.type !== 'assistant') continue;
325
+ const content = message.content;
326
+ if (typeof content === 'string') {
327
+ chunks.push(content);
328
+ continue;
329
+ }
330
+ if (Array.isArray(content)) {
331
+ for (const part of content) {
332
+ if (isTextPart(part)) chunks.push(part.text);
333
+ }
334
+ }
335
+ }
336
+ return chunks.join('\n').trim();
337
+ }
338
+
339
+ /**
340
+ * Read the latest session transcript through whatever API the session handle
341
+ * exposes: `message.list` (client) first, then `session.context` (plugin
342
+ * domain), then nothing.
343
+ * @param {any} sessionApi session or client handle
344
+ * @param {string} sessionID
345
+ * @returns {Promise<any[]>}
346
+ */
347
+ export async function readTurnTranscript(sessionApi, sessionID) {
348
+ if (typeof sessionApi?.message?.list === 'function') {
349
+ try {
350
+ const response = await sessionApi.message.list({ sessionID });
351
+ const data = unwrapData(response);
352
+ if (Array.isArray(data)) return data;
353
+ if (Array.isArray(data?.data)) return data.data;
354
+ return [];
355
+ } catch {
356
+ /* fall through to context */
357
+ }
358
+ }
359
+ if (typeof sessionApi?.context === 'function') {
360
+ try {
361
+ const response = await sessionApi.context({ sessionID });
362
+ const data = unwrapData(response);
363
+ return Array.isArray(data) ? data : [];
364
+ } catch {
365
+ return [];
366
+ }
367
+ }
368
+ return [];
369
+ }
370
+
371
+ /**
372
+ * Collect the final worker answer for a dispatched turn. Version-adaptive:
373
+ * 1. A persisted transcript (message.list / session.context) wins when the
374
+ * service stores turns.
375
+ * 2. Otherwise the generation result ("seed") is the answer — current
376
+ * service semantics are synchronous: `generate` blocks until the turn ends
377
+ * and returns the full assistant text in `{text}`, persisting nothing.
378
+ * 3. With neither available yet, wait briefly for async persistence, then
379
+ * give up with whatever exists (bounded, never hangs).
380
+ * @param {any} sessionApi session or client handle
381
+ * @param {string} sessionID
382
+ * @param {string} seed text returned by `session.generate`
383
+ * @param {{ timeoutMs?: number, intervalMs?: number }} [options]
384
+ * @returns {Promise<string>}
385
+ */
386
+ export async function collectWorkerAnswer(
387
+ sessionApi,
388
+ sessionID,
389
+ seed,
390
+ { timeoutMs = 30000, intervalMs = 800 } = {},
391
+ ) {
392
+ const transcriptText = joinAssistantText(await readTurnTranscript(sessionApi, sessionID));
393
+ if (transcriptText) return transcriptText;
394
+ if (seed) return seed;
395
+
396
+ const deadline = Date.now() + timeoutMs;
397
+ while (Date.now() < deadline) {
398
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
399
+ const text = joinAssistantText(await readTurnTranscript(sessionApi, sessionID));
400
+ if (text) return text;
401
+ }
402
+ return '';
403
+ }
404
+
275
405
  // ── State (single MCP process scope; no session lineage) ──
276
406
  /** @type {Map<string, { taskId?: string, files: string[], timestamp: number, verified: boolean }>} */
277
407
  export const sessionFileChanges = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regent-code",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Agent orchestration for OpenCode. From idea to shipped — zero ceremony. Plugin + MCP server.",
5
5
  "type": "module",
6
6
  "main": ".opencode/plugins/regent.js",
@@ -15,8 +15,8 @@
15
15
  "author": "nathwn12",
16
16
  "dependencies": {
17
17
  "@modelcontextprotocol/sdk": "^1.30.0",
18
- "@opencode-ai/client": "0.0.0-beta-18314",
19
- "@opencode-ai/plugin": "0.0.0-beta-18314",
18
+ "@opencode-ai/client": "^0.0.0-beta-18414",
19
+ "@opencode-ai/plugin": "^0.0.0-beta-18414",
20
20
  "jsonc-parser": "3.3.1"
21
21
  },
22
22
  "repository": {
Binary file