@dougbots/avenor-opencode 0.8.2 → 0.9.0

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 (3) hide show
  1. package/README.md +14 -0
  2. package/dist/index.js +432 -19
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -42,6 +42,9 @@ When a run is dispatched with `avenor_spawn`, the plugin:
42
42
  1. **Blocks by default** — the tool call stays open, updating its title and metadata as the sub-agent runs, exactly like OpenCode's own sub-agent tool calls. The tool returns when the run reaches a terminal state.
43
43
  2. **Re-prompts on completion** — when a `wait=false` (fire-and-forget) run finishes, the plugin automatically injects a completion message into the orchestrating session so the LLM picks up without manual polling.
44
44
  3. **Routes permissions** — when a sub-agent running under the `opencode-http` backend requests a permission, the plugin injects a re-prompt into the orchestrating session. The LLM can call `avenor_answer_permission` to respond; the normal permission dialog is also shown as a fallback.
45
+ 4. **Formats results** — OpenCode returns concise, bounded prose in its shared `output` for `avenor_status`, `avenor_result`, `avenor_answer_permission`, `avenor_follow_up`, `avenor_events`, `avenor_inspect`, and `avenor_shutdown`. Structured metadata stays in host/session state.
46
+
47
+ MCP clients render their own results.
45
48
 
46
49
  ## Tools
47
50
 
@@ -89,6 +92,17 @@ Wait for a run to finish and return its complete final output without transcript
89
92
 
90
93
  A blocked run returns its pending permission instead of waiting forever. If this tool's own timeout expires, it returns the latest status with `ready: false` and `timed_out: true`.
91
94
 
95
+ ### `avenor_inspect`
96
+
97
+ Use `avenor_inspect` to review a bounded snapshot. The snapshot includes transcript, completed and live tools, permissions, and final output. Use `avenor_events` when raw event records are required.
98
+
99
+ | Argument | Required | Description |
100
+ |---|---|---|
101
+ | `run_id` | yes | Run ID to inspect |
102
+ | `limit` | no | Max events to reduce into the snapshot |
103
+ | `after_seq` | no | Only include events after this sequence number |
104
+ | `supervisor_id` | no | Reuse an existing supervisor by socket path |
105
+
92
106
  ### `avenor_answer_permission`
93
107
 
94
108
  Answer a pending permission request. The `option_id` comes from the `pending_permission.options` array returned by `avenor_status`.
package/dist/index.js CHANGED
@@ -1,6 +1,414 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
2
  import * as crypto from "node:crypto";
3
- import { Supervisor, answerPermissionTool, createRunSnapshot, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
3
+ import { Supervisor, answerPermissionTool, createRunSnapshot, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
4
+ //#region src/render.ts
5
+ const FINAL_OUTPUT_CHARS$1 = 1500;
6
+ const SCALAR_CHARS = 240;
7
+ const EVENT_CHARS = 600;
8
+ const PREVIEW_LINES = 12;
9
+ const STATUS_ROWS = 12;
10
+ const COLLECTION_ITEMS = 8;
11
+ const INSPECT_ITEMS = 5;
12
+ const CLEANUP_ITEMS = 8;
13
+ const TERMINAL_STATUSES$1 = new Set([
14
+ "done",
15
+ "failed",
16
+ "timeout",
17
+ "killed"
18
+ ]);
19
+ const ANSI_PATTERN = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
20
+ function asRecord(value) {
21
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
22
+ }
23
+ function stringValue(value) {
24
+ return typeof value === "string" && value.trim() ? value : void 0;
25
+ }
26
+ function numberValue(value) {
27
+ if (typeof value === "number" && Number.isFinite(value)) return value;
28
+ if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) return Number(value);
29
+ }
30
+ function sanitizeText(text) {
31
+ return text.replace(ANSI_PATTERN, "").replaceAll(" ", " ").replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "");
32
+ }
33
+ function itemMarker(count) {
34
+ return `… ${count} items omitted`;
35
+ }
36
+ function lineMarker(count) {
37
+ return `… ${count} lines omitted`;
38
+ }
39
+ function characterMarker(count) {
40
+ return `[preview clipped: ${count} characters omitted]`;
41
+ }
42
+ function cleanMultiline(value) {
43
+ if (typeof value !== "string") return void 0;
44
+ return sanitizeText(value).trim() || void 0;
45
+ }
46
+ function tailClip(value, limit) {
47
+ if (value.length <= limit) return { text: value };
48
+ let markerLength = characterMarker(value.length).length;
49
+ for (let attempt = 0; attempt < 3; attempt++) {
50
+ const visibleLength = Math.max(0, limit - markerLength);
51
+ const nextMarkerLength = characterMarker(value.length - visibleLength).length;
52
+ if (nextMarkerLength === markerLength) return {
53
+ text: value.slice(value.length - visibleLength),
54
+ omitted: value.length - visibleLength
55
+ };
56
+ markerLength = nextMarkerLength;
57
+ }
58
+ const visibleLength = Math.max(0, limit - markerLength);
59
+ return {
60
+ text: value.slice(value.length - visibleLength),
61
+ omitted: value.length - visibleLength
62
+ };
63
+ }
64
+ function scalar(value, fallback = "unknown", limit = SCALAR_CHARS) {
65
+ const clean = cleanMultiline(typeof value === "string" ? value : void 0);
66
+ if (!clean) return fallback;
67
+ const clipped = tailClip(clean.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim(), limit);
68
+ return clipped.omitted === void 0 ? clipped.text : `${clipped.text}${characterMarker(clipped.omitted)}`;
69
+ }
70
+ function optionalScalar(value, limit = SCALAR_CHARS) {
71
+ const clean = cleanMultiline(typeof value === "string" ? value : void 0);
72
+ return clean ? scalar(clean, "unknown", limit) : void 0;
73
+ }
74
+ function preview(value, limit) {
75
+ const clean = cleanMultiline(value);
76
+ if (!clean) return void 0;
77
+ const clipped = tailClip(clean, limit);
78
+ const lines = clipped.text.split("\n").slice(-PREVIEW_LINES);
79
+ const omittedLines = Math.max(0, clean.split("\n").length - lines.length);
80
+ return [
81
+ ...lines,
82
+ ...clipped.omitted === void 0 ? [] : [characterMarker(clipped.omitted)],
83
+ ...omittedLines === 0 ? [] : [lineMarker(omittedLines)]
84
+ ];
85
+ }
86
+ function selected(record, key, fallback, defaultValue = "unknown") {
87
+ return scalar(stringValue(record?.[key]) ?? stringValue(fallback), defaultValue);
88
+ }
89
+ function selectedOptional(record, key) {
90
+ return optionalScalar(record?.[key]);
91
+ }
92
+ function hasStatusIdentity(record) {
93
+ return !!record && !!(stringValue(record.run_id) || stringValue(record.label) || stringValue(record.status));
94
+ }
95
+ function fallback(tool) {
96
+ return [`Avenor ${tool} result unavailable.`, "Retry the tool or use avenor_status/avenor_inspect."];
97
+ }
98
+ function output(lines, tool) {
99
+ return (lines ?? fallback(tool)).join("\n");
100
+ }
101
+ function isTerminal(status) {
102
+ return TERMINAL_STATUSES$1.has(status);
103
+ }
104
+ function permission(record) {
105
+ return asRecord(record?.pending_permission);
106
+ }
107
+ function permissionOptions(record) {
108
+ const options = record?.options;
109
+ return Array.isArray(options) ? options.map(asRecord).filter((value) => !!value) : [];
110
+ }
111
+ function optionRows(options) {
112
+ return options.slice(0, COLLECTION_ITEMS).map((option) => `${selected(option, "option_id")} (${selected(option, "label")})`).join(", ");
113
+ }
114
+ function optionLine(prefix, options) {
115
+ const rows = optionRows(options);
116
+ const omitted = options.length - Math.min(options.length, COLLECTION_ITEMS);
117
+ return `${prefix}${rows ? ` ${rows}` : ""}${omitted > 0 ? ` ${itemMarker(omitted)}` : ""}`;
118
+ }
119
+ function guidance(status, pending, runId) {
120
+ if (status === "waiting") {
121
+ const options = permissionOptions(pending);
122
+ if (options.length > 0) return `Guidance: Call avenor_answer_permission with run_id "${runId}" and option_id "${selected(options[0], "option_id")}".`;
123
+ if (pending) return `Guidance: Permission input is required, but no options are available; call avenor_inspect with run_id "${runId}".`;
124
+ return `Guidance: Call avenor_follow_up or avenor_status with run_id "${runId}".`;
125
+ }
126
+ if (isTerminal(status)) return `Guidance: Call avenor_result or avenor_events with run_id "${runId}".`;
127
+ return `Guidance: Call avenor_result with run_id "${runId}".`;
128
+ }
129
+ function statusExpanded(status) {
130
+ return [
131
+ ["Phase", "phase"],
132
+ ["Phase label", "phase_label"],
133
+ ["Session", "session_id"],
134
+ ["Backend", "backend"],
135
+ ["Agent", "agent"],
136
+ ["Model", "model"],
137
+ ["Latest sequence", "latest_seq"],
138
+ ["Stop reason", "stop_reason"]
139
+ ].flatMap(([label, key]) => {
140
+ const value = key === "latest_seq" ? numberValue(status[key])?.toString() : selectedOptional(status, key);
141
+ return value === void 0 ? [] : [`${label}: ${value}`];
142
+ });
143
+ }
144
+ function statusLines(details, args) {
145
+ if (Array.isArray(details)) {
146
+ const statuses = details.map(asRecord);
147
+ if (statuses.some((status) => !hasStatusIdentity(status))) return void 0;
148
+ const displayed = statuses.slice(0, STATUS_ROWS);
149
+ const lines = [
150
+ `Avenor status — ${statuses.length} runs`,
151
+ ...displayed.map((status) => {
152
+ const runId = selected(status, "run_id", args.run_id);
153
+ return `${selected(status, "label", void 0, runId)} — ${selected(status, "status")} (run_id: ${runId})`;
154
+ }),
155
+ ...statuses.length > displayed.length ? [itemMarker(statuses.length - displayed.length)] : []
156
+ ];
157
+ for (const status of displayed) {
158
+ const runId = selected(status, "run_id", args.run_id);
159
+ lines.push(`Run: ${runId}`, `Label: ${selected(status, "label", void 0, runId)}`, `Status: ${selected(status, "status")}`, ...statusExpanded(status));
160
+ }
161
+ return lines;
162
+ }
163
+ const status = asRecord(details);
164
+ if (!hasStatusIdentity(status)) return void 0;
165
+ const runId = selected(status, "run_id", args.run_id);
166
+ const label = selected(status, "label", void 0, runId);
167
+ const state = selected(status, "status");
168
+ const pending = permission(status);
169
+ const options = permissionOptions(pending);
170
+ const finalPreview = preview(status.final_output, FINAL_OUTPUT_CHARS$1);
171
+ return [
172
+ `Status: ${label} — ${state}`,
173
+ `Run: ${runId}`,
174
+ ...pending ? [`Permission: ${selected(pending, "description")}`, optionLine("Options:", options)] : [],
175
+ ...status.final_output_truncated === true ? [`Warning: final output preview is truncated; call avenor_result with run_id "${runId}".`] : [],
176
+ ...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
177
+ guidance(state, pending, runId),
178
+ ...statusExpanded(status)
179
+ ];
180
+ }
181
+ function resultLines(details, args) {
182
+ const result = asRecord(details);
183
+ if (!hasStatusIdentity(result)) return void 0;
184
+ const runId = selected(result, "run_id", args.run_id);
185
+ const label = selected(result, "label", void 0, runId);
186
+ const state = selected(result, "status");
187
+ const pending = permission(result);
188
+ const finalPreview = preview(result.output, FINAL_OUTPUT_CHARS$1);
189
+ const session = selectedOptional(result, "session_id");
190
+ const stopReason = selectedOptional(result, "stop_reason");
191
+ return [
192
+ `Result: ${label} — ${state}`,
193
+ `Run: ${runId}`,
194
+ ...result.timed_out === true ? ["Warning: wait timed out; the run is still active."] : [],
195
+ ...pending ? [`Permission: ${selected(pending, "description")}`] : [],
196
+ guidance(state, pending, runId),
197
+ ...result.output_truncated === true ? [stringValue(result.output_event_path) ? `Warning: final output may be truncated; retry avenor_result or read the durable event path "${scalar(result.output_event_path)}".` : `Warning: final output may be truncated; call avenor_events with run_id "${runId}".`] : [],
198
+ ...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
199
+ ...session ? [`Session: ${session}`] : [],
200
+ ...stopReason ? [`Stop reason: ${stopReason}`] : []
201
+ ];
202
+ }
203
+ function permissionLines(details, args) {
204
+ const result = asRecord(details);
205
+ if (!result || typeof result.ok !== "boolean") return void 0;
206
+ const runId = scalar(args.run_id);
207
+ return [
208
+ `Permission answer: ${result.ok ? "accepted" : "failed"}.`,
209
+ `Run: ${runId}`,
210
+ `Option: ${scalar(args.option_id)}`,
211
+ ...stringValue(args.request_id) ? [`Request: ${scalar(args.request_id)}`] : [],
212
+ `Guidance: Call avenor_status with run_id "${runId}" to verify the permission state.`
213
+ ];
214
+ }
215
+ function followUpLines(details, args) {
216
+ const result = asRecord(details);
217
+ if (!result || !stringValue(result.run_id) || !stringValue(result.label)) return void 0;
218
+ const runId = selected(result, "run_id", args.run_id);
219
+ const label = selected(result, "label", args.label, runId);
220
+ return [
221
+ `Follow-up: ${label} — dispatched.`,
222
+ `Source run: ${scalar(args.run_id)}`,
223
+ `Run: ${runId}`,
224
+ `Label: ${label}`,
225
+ `Guidance: Call avenor_status with run_id "${runId}".`
226
+ ];
227
+ }
228
+ function eventSequence(value) {
229
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
230
+ return typeof value === "string" && value.trim() && Number.isFinite(Number(value)) ? value : void 0;
231
+ }
232
+ function eventRow(value) {
233
+ const name = stringValue(value.event) ?? stringValue(value.type) ?? "unknown";
234
+ const sequence = eventSequence(value.seq);
235
+ const text = extractEventText(value);
236
+ const previewText = text ? scalar(text, "unknown", EVENT_CHARS) : void 0;
237
+ return `Event ${sequence ?? "?"}: ${scalar(name)}${previewText ? ` — ${previewText}` : ""}`;
238
+ }
239
+ function eventsLines(details, args) {
240
+ const result = asRecord(details);
241
+ if (!result || !Array.isArray(result.events) || result.events.some((event) => !asRecord(event))) return void 0;
242
+ const events = result.events.map((event) => event);
243
+ const runId = scalar(args.run_id);
244
+ const types = Array.isArray(args.types) ? args.types : [];
245
+ const displayedTypes = types.slice(0, COLLECTION_ITEMS).map((type) => scalar(type)).join(", ");
246
+ const lines = [`Events: ${runId} — ${events.length} events`, ...types.length > 0 ? [`Filter: ${displayedTypes}${types.length > COLLECTION_ITEMS ? ` ${itemMarker(types.length - COLLECTION_ITEMS)}` : ""}`] : []];
247
+ if (events.length === 0) return [...lines, "No events."];
248
+ const displayed = events.slice(0, STATUS_ROWS);
249
+ lines.push(...displayed.map(eventRow));
250
+ if (events.length > displayed.length) lines.push(itemMarker(events.length - displayed.length));
251
+ return lines;
252
+ }
253
+ function inspectTranscriptRow(value) {
254
+ const kind = stringValue(value.kind);
255
+ const text = optionalScalar(value.text);
256
+ if (!kind) return void 0;
257
+ if (kind === "tool") {
258
+ const title = selected(value, "title", stringValue(value.kind) ?? stringValue(value.key));
259
+ const status = optionalScalar(value.status);
260
+ return `${title}${status ? ` (${status})` : ""}${text ? `: ${text}` : ""}`;
261
+ }
262
+ if (!text) return void 0;
263
+ if ([
264
+ "assistant",
265
+ "thought",
266
+ "user",
267
+ "permission",
268
+ "status",
269
+ "session"
270
+ ].includes(kind)) return `${kind}: ${text}`;
271
+ }
272
+ function inspectToolRow(prefix, value) {
273
+ const title = selected(value, "title", stringValue(value.kind) ?? stringValue(value.key));
274
+ const status = optionalScalar(value.status);
275
+ const text = optionalScalar(value.preview);
276
+ return `${prefix}: ${title}${status ? ` (${status})` : ""}${text ? `: ${text}` : ""}`;
277
+ }
278
+ function inspectPermissionRow(value) {
279
+ const description = scalar(stringValue(value.description) ?? stringValue(value.question) ?? stringValue(value.tool) ?? stringValue(value.kind));
280
+ const options = permissionOptions(value);
281
+ const rows = optionRows(options);
282
+ return `Permission: ${description} (request_id: ${selected(value, "request_id")}); options:${rows ? ` ${rows}` : ""}${options.length > COLLECTION_ITEMS ? ` ${itemMarker(options.length - COLLECTION_ITEMS)}` : ""}`;
283
+ }
284
+ function inspectGroup(title, values, row) {
285
+ const rows = (Array.isArray(values) ? values.map(asRecord).filter((value) => !!value) : []).map(row).filter((value) => !!value);
286
+ return [
287
+ `${title}:`,
288
+ ...rows.slice(0, INSPECT_ITEMS),
289
+ ...rows.length > INSPECT_ITEMS ? [itemMarker(rows.length - INSPECT_ITEMS)] : []
290
+ ];
291
+ }
292
+ function inspectLines(details, args) {
293
+ const inspect = asRecord(details);
294
+ const status = asRecord(inspect?.status);
295
+ if (!inspect || !status || !hasStatusIdentity(inspect) && !hasStatusIdentity(status)) return void 0;
296
+ const runId = selected(inspect, "run_id", args.run_id);
297
+ const label = selected(inspect, "label", void 0, runId);
298
+ const state = selected(status, "status");
299
+ const finalPreview = preview(inspect.final_output, FINAL_OUTPUT_CHARS$1);
300
+ const transcript = Array.isArray(inspect.transcript) ? inspect.transcript : [];
301
+ const tools = Array.isArray(inspect.tools) ? inspect.tools : [];
302
+ const liveTools = Array.isArray(inspect.live_tools) ? inspect.live_tools : [];
303
+ const permissions = Array.isArray(inspect.permissions) ? inspect.permissions : [];
304
+ return [
305
+ `Inspect: ${label} — ${state}`,
306
+ `Run: ${runId}`,
307
+ `Counts: transcript=${transcript.length}, tools=${tools.length}, live_tools=${liveTools.length}, permissions=${permissions.length}`,
308
+ ...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
309
+ `Guidance: Call avenor_events with run_id "${runId}" for raw events.`,
310
+ ...inspectGroup("Transcript", transcript, inspectTranscriptRow),
311
+ ...inspectGroup("Tools", tools, (value) => inspectToolRow("Tool", value)),
312
+ ...inspectGroup("Live tools", liveTools, (value) => inspectToolRow("Live tool", value)),
313
+ ...inspectGroup("Permissions", permissions, inspectPermissionRow)
314
+ ];
315
+ }
316
+ function shutdownLines(details, args) {
317
+ const result = asRecord(details);
318
+ if (!result || typeof result.ok !== "boolean" || !Array.isArray(result.cleaned_up)) return void 0;
319
+ const paths = result.cleaned_up.filter((path) => typeof path === "string");
320
+ const supervisor = stringValue(args.supervisor_id) ? scalar(args.supervisor_id) : "singleton";
321
+ return [
322
+ `Avenor shutdown: ${result.ok ? "succeeded" : "failed"}.`,
323
+ `Supervisor: ${supervisor}`,
324
+ `Mode: ${args.force === true ? "force" : "graceful"}`,
325
+ paths.length > 0 ? `Cleanup: ${paths.length} paths` : "Cleanup: none",
326
+ ...!result.ok ? ["Warning: shutdown did not complete.", "Guidance: retry avenor_shutdown."] : [],
327
+ ...paths.slice(0, CLEANUP_ITEMS).map((path) => `Cleanup path: ${scalar(path)}`),
328
+ ...paths.length > CLEANUP_ITEMS ? [itemMarker(paths.length - CLEANUP_ITEMS)] : []
329
+ ];
330
+ }
331
+ function formatStatusOutput(args, result) {
332
+ const statuses = Array.isArray(result) ? result : void 0;
333
+ if (statuses) return {
334
+ title: `Avenor status — ${statuses.length} runs`,
335
+ output: output(statusLines(statuses, args), "status"),
336
+ metadata: {
337
+ results: statuses,
338
+ count: statuses.length
339
+ }
340
+ };
341
+ const record = result;
342
+ return {
343
+ title: `${selected(record, "label", void 0, selected(record, "run_id", args.run_id))} — ${selected(record, "status")}`,
344
+ output: output(statusLines(result, args), "status"),
345
+ metadata: { ...result }
346
+ };
347
+ }
348
+ function formatResultOutput(args, result) {
349
+ const record = result;
350
+ return {
351
+ title: `${selected(record, "label", void 0, selected(record, "run_id", args.run_id))} — ${selected(record, "status")}${result.timed_out === true ? " (wait timed out)" : ""}`,
352
+ output: output(resultLines(result, args), "result"),
353
+ metadata: { ...result }
354
+ };
355
+ }
356
+ function formatAnswerPermissionOutput(args, result) {
357
+ return {
358
+ title: `${scalar(args.run_id)} — permission answered`,
359
+ output: output(permissionLines(result, args), "answer_permission"),
360
+ metadata: {
361
+ ...result,
362
+ run_id: args.run_id,
363
+ option_id: args.option_id,
364
+ request_id: args.request_id
365
+ }
366
+ };
367
+ }
368
+ function formatFollowUpOutput(args, result) {
369
+ const record = result;
370
+ const runId = selected(record, "run_id", args.run_id);
371
+ return {
372
+ title: `${selected(record, "label", args.label, runId)} — dispatched`,
373
+ output: output(followUpLines(result, args), "follow_up"),
374
+ metadata: {
375
+ ...result,
376
+ source_run_id: args.run_id
377
+ }
378
+ };
379
+ }
380
+ function formatEventsOutput(args, result) {
381
+ return {
382
+ title: `${scalar(args.run_id)} — ${result.events.length} events`,
383
+ output: output(eventsLines(result, args), "events"),
384
+ metadata: {
385
+ ...result,
386
+ run_id: args.run_id,
387
+ count: result.events.length
388
+ }
389
+ };
390
+ }
391
+ function formatInspectOutput(args, result) {
392
+ const record = result;
393
+ const status = result.status;
394
+ return {
395
+ title: `${selected(record, "label", void 0, selected(record, "run_id", args.run_id))} — ${selected(status, "status")}`,
396
+ output: output(inspectLines(result, args), "inspect"),
397
+ metadata: { ...result }
398
+ };
399
+ }
400
+ function formatShutdownOutput(args, result) {
401
+ return {
402
+ title: "Avenor — shut down",
403
+ output: output(shutdownLines(result, args), "shutdown"),
404
+ metadata: {
405
+ ...result,
406
+ supervisor_id: args.supervisor_id,
407
+ force: args.force ?? false
408
+ }
409
+ };
410
+ }
411
+ //#endregion
4
412
  //#region src/plugin.ts
5
413
  const TERMINAL_STATUSES = new Set([
6
414
  "done",
@@ -432,7 +840,7 @@ const AvenorPlugin = async (ctx) => {
432
840
  },
433
841
  tool: {
434
842
  avenor_spawn: tool({
435
- description: "Dispatch an agent run via avenor. Blocks by default, showing live progress as an updating tool call. Set wait=false for fire-and-forget — you will be re-prompted automatically on completion.",
843
+ description: "Dispatch an agent run via avenor. Optional thinking accepts off, minimal, low, medium, high, xhigh, or max; unsupported backends reject explicit values. Blocks by default, showing live progress as an updating tool call. Set wait=false for fire-and-forget — you will be re-prompted automatically on completion.",
436
844
  args: {
437
845
  agent: tool.schema.string().describe("Agent name (required, no default)"),
438
846
  prompt: tool.schema.string().optional().describe("Prompt text"),
@@ -441,6 +849,15 @@ const AvenorPlugin = async (ctx) => {
441
849
  label: tool.schema.string().optional().describe("Human-readable label for the run"),
442
850
  timeout: tool.schema.string().optional().describe("Timeout duration (e.g. 3600s)"),
443
851
  model: tool.schema.string().optional().describe("Model override"),
852
+ thinking: tool.schema.enum([
853
+ "off",
854
+ "minimal",
855
+ "low",
856
+ "medium",
857
+ "high",
858
+ "xhigh",
859
+ "max"
860
+ ]).optional().describe("Thinking level; omission uses the backend default"),
444
861
  backend: tool.schema.string().optional().describe("Backend override"),
445
862
  server_url: tool.schema.string().optional().describe("Backend server URL"),
446
863
  supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path"),
@@ -456,6 +873,7 @@ const AvenorPlugin = async (ctx) => {
456
873
  dir: args.dir ?? context.directory,
457
874
  timeout: args.timeout,
458
875
  model: args.model,
876
+ thinking: args.thinking,
459
877
  backend: args.backend,
460
878
  serverUrl: args.server_url,
461
879
  supervisorId: args.supervisor_id,
@@ -541,12 +959,11 @@ const AvenorPlugin = async (ctx) => {
541
959
  supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path")
542
960
  },
543
961
  async execute(args, _context) {
544
- const result = await statusTool({
962
+ return formatStatusOutput(args, await statusTool({
545
963
  runId: args.run_id,
546
964
  supervisorId: args.supervisor_id,
547
965
  view: args.view
548
- });
549
- return JSON.stringify(result, null, 2);
966
+ }));
550
967
  }
551
968
  }),
552
969
  avenor_result: tool({
@@ -570,7 +987,7 @@ const AvenorPlugin = async (ctx) => {
570
987
  });
571
988
  if (result.ready) unregisterRun(args.run_id);
572
989
  else if (run) monitorRun(run).catch(console.error);
573
- return JSON.stringify(result, null, 2);
990
+ return formatResultOutput(args, result);
574
991
  } catch (error) {
575
992
  if (run) monitorRun(run).catch(console.error);
576
993
  throw error;
@@ -587,14 +1004,13 @@ const AvenorPlugin = async (ctx) => {
587
1004
  message: tool.schema.string().optional().describe("Optional write-in response text. Required only when the selected option declares requiresMessage: true.")
588
1005
  },
589
1006
  async execute(args, _context) {
590
- const result = await answerPermissionTool({
1007
+ return formatAnswerPermissionOutput(args, await answerPermissionTool({
591
1008
  runId: args.run_id,
592
1009
  optionId: args.option_id,
593
1010
  requestId: args.request_id,
594
1011
  supervisorId: args.supervisor_id,
595
1012
  message: args.message
596
- });
597
- return JSON.stringify(result, null, 2);
1013
+ }));
598
1014
  }
599
1015
  }),
600
1016
  avenor_follow_up: tool({
@@ -624,7 +1040,7 @@ const AvenorPlugin = async (ctx) => {
624
1040
  trackedRuns.set(result.run_id, run);
625
1041
  await primeTrackedRun(run);
626
1042
  monitorRun(run).catch(console.error);
627
- return JSON.stringify(result, null, 2);
1043
+ return formatFollowUpOutput(args, result);
628
1044
  }
629
1045
  }),
630
1046
  avenor_events: tool({
@@ -636,13 +1052,12 @@ const AvenorPlugin = async (ctx) => {
636
1052
  supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path")
637
1053
  },
638
1054
  async execute(args, _context) {
639
- const result = await eventsTool({
1055
+ return formatEventsOutput(args, await eventsTool({
640
1056
  runId: args.run_id,
641
1057
  types: args.types,
642
1058
  limit: args.limit,
643
1059
  supervisorId: args.supervisor_id
644
- });
645
- return JSON.stringify(result, null, 2);
1060
+ }));
646
1061
  }
647
1062
  }),
648
1063
  avenor_inspect: tool({
@@ -654,13 +1069,12 @@ const AvenorPlugin = async (ctx) => {
654
1069
  supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path")
655
1070
  },
656
1071
  async execute(args, _context) {
657
- const result = await inspectTool({
1072
+ return formatInspectOutput(args, await inspectTool({
658
1073
  runId: args.run_id,
659
1074
  limit: args.limit,
660
1075
  after_seq: args.after_seq,
661
1076
  supervisorId: args.supervisor_id
662
- });
663
- return JSON.stringify(result, null, 2);
1077
+ }));
664
1078
  }
665
1079
  }),
666
1080
  avenor_shutdown: tool({
@@ -671,11 +1085,10 @@ const AvenorPlugin = async (ctx) => {
671
1085
  },
672
1086
  async execute(args, _context) {
673
1087
  await stopTrackedRuns((run) => !args.supervisor_id || run.supervisorId === args.supervisor_id);
674
- const result = await shutdownTool({
1088
+ return formatShutdownOutput(args, await shutdownTool({
675
1089
  supervisorId: args.supervisor_id,
676
1090
  force: args.force
677
- });
678
- return JSON.stringify(result, null, 2);
1091
+ }));
679
1092
  }
680
1093
  })
681
1094
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-opencode",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,7 +27,7 @@
27
27
  "dev": "tsdown --watch"
28
28
  },
29
29
  "dependencies": {
30
- "@dougbots/avenor-core": "^0.8.2"
30
+ "@dougbots/avenor-core": "^0.9.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@opencode-ai/plugin": "1.15.3"