@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.
- package/README.md +14 -0
- package/dist/index.js +432 -19
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
30
|
+
"@dougbots/avenor-core": "^0.9.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@opencode-ai/plugin": "1.15.3"
|