@dougbots/avenor-opencode 0.8.3 → 0.10.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 +34 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.js +500 -38
- 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
|
|
|
@@ -55,17 +58,36 @@ Dispatch an agent run in the current working directory.
|
|
|
55
58
|
|
|
56
59
|
| Argument | Required | Description |
|
|
57
60
|
|---|---|---|
|
|
58
|
-
| `agent` |
|
|
61
|
+
| `agent` | no | Agent name; omission uses the supplied model or runtime defaults |
|
|
59
62
|
| `prompt` | no | Prompt text |
|
|
60
63
|
| `prompt_file` | no | Path to a prompt file |
|
|
61
64
|
| `label` | no | Human-readable label for the run |
|
|
62
65
|
| `timeout` | no | Timeout duration (e.g. `3600s`, `30m`) |
|
|
63
66
|
| `model` | no | Model override |
|
|
67
|
+
| `thinking` | no | Run-level reasoning control; unsupported effective backends reject explicit values |
|
|
68
|
+
| `backend` | no | Backend override; direct mode defaults to Avenor's `opencode-acp` backend when omitted |
|
|
69
|
+
| `roster_file` | no | Path to a top-level roster map |
|
|
70
|
+
| `roster_entry` | no | Key within `roster_file` |
|
|
64
71
|
| `wait` | no | Block until complete (default `true`) |
|
|
65
72
|
| `supervisor_id` | no | Reuse an existing supervisor by socket path |
|
|
66
73
|
|
|
67
74
|
The working directory (`dir`) is injected automatically from your OpenCode session context — it is not a user-facing argument.
|
|
68
75
|
|
|
76
|
+
#### Direct and roster modes
|
|
77
|
+
|
|
78
|
+
Use `roster_file` plus `roster_entry` to select a complete identity. Every roster entry requires `backend` and at least one of `agent` or `model`; roster mode rejects direct `agent`, `model`, and `backend` overrides. Direct mode allows all three identity fields to be omitted independently, including backend-only selection. Roster mode does not apply the direct `opencode-acp` default.
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"prompt": "Review the repository",
|
|
83
|
+
"roster_file": "/repo/roster.json",
|
|
84
|
+
"roster_entry": "reviewer",
|
|
85
|
+
"wait": true
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Roster entries currently support only `backend`, `agent`, and `model`. `system` and `thinking` are rejected; `thinking` remains a run-level value and is validated against the effective backend. A resumed session cannot change backend. The plugin passes `roster_file` as supplied, so an absolute path avoids ambiguity when the OpenCode process directory differs from the project directory.
|
|
90
|
+
|
|
69
91
|
### `avenor_status`
|
|
70
92
|
|
|
71
93
|
Get status of a specific run or all active runs. Use the compact `lifecycle` view for progress and pending permission checks; `full` remains the compatibility default.
|
|
@@ -89,6 +111,17 @@ Wait for a run to finish and return its complete final output without transcript
|
|
|
89
111
|
|
|
90
112
|
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
113
|
|
|
114
|
+
### `avenor_inspect`
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
| Argument | Required | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `run_id` | yes | Run ID to inspect |
|
|
121
|
+
| `limit` | no | Max events to reduce into the snapshot |
|
|
122
|
+
| `after_seq` | no | Only include events after this sequence number |
|
|
123
|
+
| `supervisor_id` | no | Reuse an existing supervisor by socket path |
|
|
124
|
+
|
|
92
125
|
### `avenor_answer_permission`
|
|
93
126
|
|
|
94
127
|
Answer a pending permission request. The `option_id` comes from the `pending_permission.options` array returned by `avenor_status`.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,14 @@ type TrackedRun = {
|
|
|
6
6
|
runId: string;
|
|
7
7
|
runtimeId?: string;
|
|
8
8
|
sessionId?: string;
|
|
9
|
-
agent
|
|
9
|
+
agent?: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
backend?: string;
|
|
12
|
+
rosterFile?: string;
|
|
13
|
+
rosterEntry?: string;
|
|
14
|
+
effectiveAgent?: string;
|
|
15
|
+
effectiveModel?: string;
|
|
16
|
+
effectiveBackend?: string;
|
|
10
17
|
orchestratorSessionId: string;
|
|
11
18
|
label: string;
|
|
12
19
|
supervisorId?: string;
|
|
@@ -15,7 +22,12 @@ type TrackedRun = {
|
|
|
15
22
|
lastSnapshot?: RunSnapshot;
|
|
16
23
|
};
|
|
17
24
|
declare function terminalStatus(snapshot: RunSnapshot, fallback?: StatusResult): string;
|
|
25
|
+
type HostIdentityMetadataInput = Pick<TrackedRun, 'agent' | 'model' | 'backend' | 'rosterFile' | 'rosterEntry' | 'effectiveAgent' | 'effectiveModel' | 'effectiveBackend'>;
|
|
26
|
+
type HostIdentityStatus = Pick<StatusResult, 'roster_file' | 'roster_entry' | 'agent' | 'model' | 'backend' | 'effective_agent' | 'effective_model' | 'effective_backend'>;
|
|
27
|
+
/** Project shared spawn identity into OpenCode metadata without resolving rosters here. */
|
|
28
|
+
declare function hostIdentityMetadata(run?: HostIdentityMetadataInput, status?: HostIdentityStatus): Record<string, unknown>;
|
|
29
|
+
declare function buildSnapshotMetadata(snapshot: RunSnapshot, run?: HostIdentityMetadataInput, status?: HostIdentityStatus): Record<string, unknown>;
|
|
18
30
|
declare function buildWaitingText(run: TrackedRun, snapshot: RunSnapshot): string;
|
|
19
31
|
declare const AvenorPlugin: Plugin;
|
|
20
32
|
//#endregion
|
|
21
|
-
export { AvenorPlugin, buildWaitingText, terminalStatus };
|
|
33
|
+
export { AvenorPlugin, buildSnapshotMetadata, buildWaitingText, hostIdentityMetadata, terminalStatus };
|
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, validateSpawnSelection } 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",
|
|
@@ -54,7 +462,20 @@ function formatTranscriptPreview(snapshot) {
|
|
|
54
462
|
if (lines.length === 0) return void 0;
|
|
55
463
|
return clipTail(lines.join("\n"), METADATA_TEXT_CHARS);
|
|
56
464
|
}
|
|
57
|
-
|
|
465
|
+
/** Project shared spawn identity into OpenCode metadata without resolving rosters here. */
|
|
466
|
+
function hostIdentityMetadata(run = {}, status) {
|
|
467
|
+
const metadata = {};
|
|
468
|
+
const add = (key, value) => {
|
|
469
|
+
if (typeof value === "string" && value.length > 0) metadata[key] = value;
|
|
470
|
+
};
|
|
471
|
+
add("roster_file", status?.roster_file ?? run.rosterFile);
|
|
472
|
+
add("roster_entry", status?.roster_entry ?? run.rosterEntry);
|
|
473
|
+
add("effective_agent", status?.effective_agent ?? status?.agent ?? run.effectiveAgent);
|
|
474
|
+
add("effective_model", status?.effective_model ?? status?.model ?? run.effectiveModel);
|
|
475
|
+
add("effective_backend", status?.effective_backend ?? status?.backend ?? run.effectiveBackend);
|
|
476
|
+
return metadata;
|
|
477
|
+
}
|
|
478
|
+
function buildSnapshotMetadata(snapshot, run, status) {
|
|
58
479
|
const metadata = {};
|
|
59
480
|
if (snapshot.phase) metadata.phase = snapshot.phase;
|
|
60
481
|
if (snapshot.latest_seq !== void 0) metadata.latest_seq = snapshot.latest_seq;
|
|
@@ -64,6 +485,7 @@ function buildSnapshotMetadata(snapshot) {
|
|
|
64
485
|
if (transcriptPreview) metadata.transcript_preview = transcriptPreview;
|
|
65
486
|
const pendingPermission = clipTail(snapshot.pending_permission?.description ?? snapshot.pending_permission?.question ?? snapshot.phase_label, METADATA_TEXT_CHARS);
|
|
66
487
|
if (pendingPermission) metadata.pending_permission = pendingPermission;
|
|
488
|
+
Object.assign(metadata, hostIdentityMetadata(run, status));
|
|
67
489
|
return metadata;
|
|
68
490
|
}
|
|
69
491
|
function buildWaitingText(run, snapshot) {
|
|
@@ -136,6 +558,16 @@ const AvenorPlugin = async (ctx) => {
|
|
|
136
558
|
if (snapshot.latest_seq !== void 0) run.latestSeq = snapshot.latest_seq;
|
|
137
559
|
registerSessionId(snapshot.identity.session_id, snapshot.identity.runtime_id, run.runId);
|
|
138
560
|
}
|
|
561
|
+
function updateRunIdentity(run, status) {
|
|
562
|
+
run.rosterFile = status.roster_file ?? run.rosterFile;
|
|
563
|
+
run.rosterEntry = status.roster_entry ?? run.rosterEntry;
|
|
564
|
+
run.effectiveAgent = status.effective_agent ?? status.agent ?? run.effectiveAgent;
|
|
565
|
+
run.effectiveModel = status.effective_model ?? status.model ?? run.effectiveModel;
|
|
566
|
+
run.effectiveBackend = status.effective_backend ?? status.backend ?? run.effectiveBackend;
|
|
567
|
+
run.agent = run.effectiveAgent ?? run.agent;
|
|
568
|
+
run.model = run.effectiveModel ?? run.model;
|
|
569
|
+
run.backend = run.effectiveBackend ?? run.backend;
|
|
570
|
+
}
|
|
139
571
|
async function primeTrackedRun(run, options = {}) {
|
|
140
572
|
try {
|
|
141
573
|
const raw = await statusTool({
|
|
@@ -145,6 +577,7 @@ const AvenorPlugin = async (ctx) => {
|
|
|
145
577
|
const status = Array.isArray(raw) ? raw[0] : raw;
|
|
146
578
|
if (!status) return void 0;
|
|
147
579
|
registerSessionId(status.session_id, status.runtime_id, run.runId);
|
|
580
|
+
updateRunIdentity(run, status);
|
|
148
581
|
if (status.latest_seq !== void 0 && options.captureLatestSeq) run.latestSeq = status.latest_seq;
|
|
149
582
|
return status;
|
|
150
583
|
} catch {
|
|
@@ -170,10 +603,12 @@ const AvenorPlugin = async (ctx) => {
|
|
|
170
603
|
}
|
|
171
604
|
async function loadInspection(run) {
|
|
172
605
|
try {
|
|
173
|
-
|
|
606
|
+
const inspection = await inspectTool({
|
|
174
607
|
runId: run.runId,
|
|
175
608
|
supervisorId: run.supervisorId
|
|
176
609
|
});
|
|
610
|
+
updateRunIdentity(run, inspection.status);
|
|
611
|
+
return inspection;
|
|
177
612
|
} catch {
|
|
178
613
|
return;
|
|
179
614
|
}
|
|
@@ -432,33 +867,54 @@ const AvenorPlugin = async (ctx) => {
|
|
|
432
867
|
},
|
|
433
868
|
tool: {
|
|
434
869
|
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.",
|
|
870
|
+
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
871
|
args: {
|
|
437
|
-
agent: tool.schema.string().describe("
|
|
872
|
+
agent: tool.schema.string().optional().describe("Optional agent name; omission uses the supplied model or runtime defaults"),
|
|
438
873
|
prompt: tool.schema.string().optional().describe("Prompt text"),
|
|
439
874
|
prompt_file: tool.schema.string().optional().describe("Path to prompt file"),
|
|
440
875
|
dir: tool.schema.string().optional().describe("Working directory for the run (defaults to the session project directory)"),
|
|
441
876
|
label: tool.schema.string().optional().describe("Human-readable label for the run"),
|
|
442
877
|
timeout: tool.schema.string().optional().describe("Timeout duration (e.g. 3600s)"),
|
|
443
878
|
model: tool.schema.string().optional().describe("Model override"),
|
|
879
|
+
thinking: tool.schema.enum([
|
|
880
|
+
"off",
|
|
881
|
+
"minimal",
|
|
882
|
+
"low",
|
|
883
|
+
"medium",
|
|
884
|
+
"high",
|
|
885
|
+
"xhigh",
|
|
886
|
+
"max"
|
|
887
|
+
]).optional().describe("Thinking level; omission uses the backend default"),
|
|
444
888
|
backend: tool.schema.string().optional().describe("Backend override"),
|
|
889
|
+
roster_file: tool.schema.string().optional().describe("Path to the roster map"),
|
|
890
|
+
roster_entry: tool.schema.string().optional().describe("Roster entry to select"),
|
|
445
891
|
server_url: tool.schema.string().optional().describe("Backend server URL"),
|
|
446
892
|
supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path"),
|
|
447
893
|
wait: tool.schema.boolean().default(true).describe("Block until complete with live status updates. False = fire-and-forget.")
|
|
448
894
|
},
|
|
449
895
|
async execute(args, context) {
|
|
450
|
-
|
|
451
|
-
const result = await spawnTool({
|
|
896
|
+
validateSpawnSelection({
|
|
452
897
|
agent: args.agent,
|
|
453
|
-
prompt: args.prompt,
|
|
454
|
-
promptFile: args.prompt_file,
|
|
455
|
-
label: args.label,
|
|
456
|
-
dir: args.dir ?? context.directory,
|
|
457
|
-
timeout: args.timeout,
|
|
458
898
|
model: args.model,
|
|
459
899
|
backend: args.backend,
|
|
460
|
-
|
|
461
|
-
|
|
900
|
+
roster_file: args.roster_file,
|
|
901
|
+
roster_entry: args.roster_entry
|
|
902
|
+
});
|
|
903
|
+
const ownerRunId = ensureParentRunId();
|
|
904
|
+
const result = await spawnTool({
|
|
905
|
+
...args.agent !== void 0 ? { agent: args.agent } : {},
|
|
906
|
+
...args.prompt !== void 0 ? { prompt: args.prompt } : {},
|
|
907
|
+
...args.prompt_file !== void 0 ? { promptFile: args.prompt_file } : {},
|
|
908
|
+
...args.label !== void 0 ? { label: args.label } : {},
|
|
909
|
+
dir: args.dir ?? context.directory,
|
|
910
|
+
...args.timeout !== void 0 ? { timeout: args.timeout } : {},
|
|
911
|
+
...args.model !== void 0 ? { model: args.model } : {},
|
|
912
|
+
...args.thinking !== void 0 ? { thinking: args.thinking } : {},
|
|
913
|
+
...args.backend !== void 0 ? { backend: args.backend } : {},
|
|
914
|
+
...args.roster_file !== void 0 ? { rosterFile: args.roster_file } : {},
|
|
915
|
+
...args.roster_entry !== void 0 ? { rosterEntry: args.roster_entry } : {},
|
|
916
|
+
...args.server_url !== void 0 ? { serverUrl: args.server_url } : {},
|
|
917
|
+
...args.supervisor_id !== void 0 ? { supervisorId: args.supervisor_id } : {},
|
|
462
918
|
parent_run_id: ownerRunId
|
|
463
919
|
});
|
|
464
920
|
if (result.broker_url) brokerUrl = result.broker_url;
|
|
@@ -467,6 +923,10 @@ const AvenorPlugin = async (ctx) => {
|
|
|
467
923
|
runId: result.run_id,
|
|
468
924
|
runtimeId: result.runtime_id,
|
|
469
925
|
agent: args.agent,
|
|
926
|
+
model: args.model,
|
|
927
|
+
backend: args.backend,
|
|
928
|
+
rosterFile: args.roster_file,
|
|
929
|
+
rosterEntry: args.roster_entry,
|
|
470
930
|
orchestratorSessionId: context.sessionID,
|
|
471
931
|
label: result.label,
|
|
472
932
|
supervisorId: result.supervisor_id || args.supervisor_id,
|
|
@@ -479,7 +939,8 @@ const AvenorPlugin = async (ctx) => {
|
|
|
479
939
|
monitorRun(run).catch(console.error);
|
|
480
940
|
return {
|
|
481
941
|
title: `${result.label} — dispatched`,
|
|
482
|
-
output: `Dispatched "${result.label}" (run_id: ${result.run_id}). Completion will be delivered automatically; use avenor_status with view "lifecycle" for progress or avenor_result to wait for the final result
|
|
942
|
+
output: `Dispatched "${result.label}" (run_id: ${result.run_id}). Completion will be delivered automatically; use avenor_status with view "lifecycle" for progress or avenor_result to wait for the final result.`,
|
|
943
|
+
metadata: hostIdentityMetadata(run)
|
|
483
944
|
};
|
|
484
945
|
}
|
|
485
946
|
context.metadata({ title: `Starting ${result.label}…` });
|
|
@@ -488,7 +949,7 @@ const AvenorPlugin = async (ctx) => {
|
|
|
488
949
|
onSnapshot: (snapshot) => {
|
|
489
950
|
context.metadata({
|
|
490
951
|
title: snapshot.pending_permission ? `[blocked] ${result.label}` : result.label,
|
|
491
|
-
metadata: buildSnapshotMetadata(snapshot)
|
|
952
|
+
metadata: buildSnapshotMetadata(snapshot, run)
|
|
492
953
|
});
|
|
493
954
|
}
|
|
494
955
|
});
|
|
@@ -503,7 +964,7 @@ const AvenorPlugin = async (ctx) => {
|
|
|
503
964
|
run_id: result.run_id,
|
|
504
965
|
session_id: inspection?.status.session_id ?? snapshot.identity.session_id,
|
|
505
966
|
final_output: clipTail(inspection?.final_output ?? snapshot.final_output, FINAL_OUTPUT_CHARS),
|
|
506
|
-
...buildSnapshotMetadata(snapshot)
|
|
967
|
+
...buildSnapshotMetadata(snapshot, run, inspection?.status)
|
|
507
968
|
}
|
|
508
969
|
};
|
|
509
970
|
}
|
|
@@ -516,20 +977,26 @@ const AvenorPlugin = async (ctx) => {
|
|
|
516
977
|
metadata: {
|
|
517
978
|
run_id: result.run_id,
|
|
518
979
|
session_id: inspection?.status.session_id ?? snapshot.identity.session_id,
|
|
519
|
-
...buildSnapshotMetadata(snapshot)
|
|
980
|
+
...buildSnapshotMetadata(snapshot, run, inspection?.status)
|
|
520
981
|
}
|
|
521
982
|
};
|
|
522
983
|
}
|
|
523
984
|
if (outcome.kind === "error") return {
|
|
524
985
|
title: `${result.label} — monitoring error`,
|
|
525
986
|
output: `Monitoring failed: ${outcome.error ?? "unknown error"}. The run may still be active — use avenor_status to check.`,
|
|
526
|
-
metadata: {
|
|
987
|
+
metadata: {
|
|
988
|
+
run_id: result.run_id,
|
|
989
|
+
...hostIdentityMetadata(run)
|
|
990
|
+
}
|
|
527
991
|
};
|
|
528
992
|
unregisterRun(result.run_id);
|
|
529
993
|
return {
|
|
530
994
|
title: `${result.label} — aborted`,
|
|
531
995
|
output: `Monitoring of "${result.label}" (run_id: ${result.run_id}) was interrupted. The run may still be active — use avenor_status to check.`,
|
|
532
|
-
metadata: {
|
|
996
|
+
metadata: {
|
|
997
|
+
run_id: result.run_id,
|
|
998
|
+
...hostIdentityMetadata(run)
|
|
999
|
+
}
|
|
533
1000
|
};
|
|
534
1001
|
}
|
|
535
1002
|
}),
|
|
@@ -541,12 +1008,11 @@ const AvenorPlugin = async (ctx) => {
|
|
|
541
1008
|
supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path")
|
|
542
1009
|
},
|
|
543
1010
|
async execute(args, _context) {
|
|
544
|
-
|
|
1011
|
+
return formatStatusOutput(args, await statusTool({
|
|
545
1012
|
runId: args.run_id,
|
|
546
1013
|
supervisorId: args.supervisor_id,
|
|
547
1014
|
view: args.view
|
|
548
|
-
});
|
|
549
|
-
return JSON.stringify(result, null, 2);
|
|
1015
|
+
}));
|
|
550
1016
|
}
|
|
551
1017
|
}),
|
|
552
1018
|
avenor_result: tool({
|
|
@@ -570,7 +1036,7 @@ const AvenorPlugin = async (ctx) => {
|
|
|
570
1036
|
});
|
|
571
1037
|
if (result.ready) unregisterRun(args.run_id);
|
|
572
1038
|
else if (run) monitorRun(run).catch(console.error);
|
|
573
|
-
return
|
|
1039
|
+
return formatResultOutput(args, result);
|
|
574
1040
|
} catch (error) {
|
|
575
1041
|
if (run) monitorRun(run).catch(console.error);
|
|
576
1042
|
throw error;
|
|
@@ -587,14 +1053,13 @@ const AvenorPlugin = async (ctx) => {
|
|
|
587
1053
|
message: tool.schema.string().optional().describe("Optional write-in response text. Required only when the selected option declares requiresMessage: true.")
|
|
588
1054
|
},
|
|
589
1055
|
async execute(args, _context) {
|
|
590
|
-
|
|
1056
|
+
return formatAnswerPermissionOutput(args, await answerPermissionTool({
|
|
591
1057
|
runId: args.run_id,
|
|
592
1058
|
optionId: args.option_id,
|
|
593
1059
|
requestId: args.request_id,
|
|
594
1060
|
supervisorId: args.supervisor_id,
|
|
595
1061
|
message: args.message
|
|
596
|
-
});
|
|
597
|
-
return JSON.stringify(result, null, 2);
|
|
1062
|
+
}));
|
|
598
1063
|
}
|
|
599
1064
|
}),
|
|
600
1065
|
avenor_follow_up: tool({
|
|
@@ -624,7 +1089,7 @@ const AvenorPlugin = async (ctx) => {
|
|
|
624
1089
|
trackedRuns.set(result.run_id, run);
|
|
625
1090
|
await primeTrackedRun(run);
|
|
626
1091
|
monitorRun(run).catch(console.error);
|
|
627
|
-
return
|
|
1092
|
+
return formatFollowUpOutput(args, result);
|
|
628
1093
|
}
|
|
629
1094
|
}),
|
|
630
1095
|
avenor_events: tool({
|
|
@@ -636,13 +1101,12 @@ const AvenorPlugin = async (ctx) => {
|
|
|
636
1101
|
supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path")
|
|
637
1102
|
},
|
|
638
1103
|
async execute(args, _context) {
|
|
639
|
-
|
|
1104
|
+
return formatEventsOutput(args, await eventsTool({
|
|
640
1105
|
runId: args.run_id,
|
|
641
1106
|
types: args.types,
|
|
642
1107
|
limit: args.limit,
|
|
643
1108
|
supervisorId: args.supervisor_id
|
|
644
|
-
});
|
|
645
|
-
return JSON.stringify(result, null, 2);
|
|
1109
|
+
}));
|
|
646
1110
|
}
|
|
647
1111
|
}),
|
|
648
1112
|
avenor_inspect: tool({
|
|
@@ -654,13 +1118,12 @@ const AvenorPlugin = async (ctx) => {
|
|
|
654
1118
|
supervisor_id: tool.schema.string().optional().describe("Reuse an existing supervisor by socket path")
|
|
655
1119
|
},
|
|
656
1120
|
async execute(args, _context) {
|
|
657
|
-
|
|
1121
|
+
return formatInspectOutput(args, await inspectTool({
|
|
658
1122
|
runId: args.run_id,
|
|
659
1123
|
limit: args.limit,
|
|
660
1124
|
after_seq: args.after_seq,
|
|
661
1125
|
supervisorId: args.supervisor_id
|
|
662
|
-
});
|
|
663
|
-
return JSON.stringify(result, null, 2);
|
|
1126
|
+
}));
|
|
664
1127
|
}
|
|
665
1128
|
}),
|
|
666
1129
|
avenor_shutdown: tool({
|
|
@@ -671,15 +1134,14 @@ const AvenorPlugin = async (ctx) => {
|
|
|
671
1134
|
},
|
|
672
1135
|
async execute(args, _context) {
|
|
673
1136
|
await stopTrackedRuns((run) => !args.supervisor_id || run.supervisorId === args.supervisor_id);
|
|
674
|
-
|
|
1137
|
+
return formatShutdownOutput(args, await shutdownTool({
|
|
675
1138
|
supervisorId: args.supervisor_id,
|
|
676
1139
|
force: args.force
|
|
677
|
-
});
|
|
678
|
-
return JSON.stringify(result, null, 2);
|
|
1140
|
+
}));
|
|
679
1141
|
}
|
|
680
1142
|
})
|
|
681
1143
|
}
|
|
682
1144
|
};
|
|
683
1145
|
};
|
|
684
1146
|
//#endregion
|
|
685
|
-
export { AvenorPlugin, buildWaitingText, terminalStatus };
|
|
1147
|
+
export { AvenorPlugin, buildSnapshotMetadata, buildWaitingText, hostIdentityMetadata, terminalStatus };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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.10.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@opencode-ai/plugin": "1.15.3"
|