@opencode-cockpit/subagents 0.7.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/agent/plugin.js +134 -0
- package/dist/cli/preview.js +99 -0
- package/dist/core/adapt/v1.js +341 -0
- package/dist/core/adapt/v2.js +379 -0
- package/dist/core/model/changes.js +17 -0
- package/dist/core/model/model.js +301 -0
- package/dist/core/sample.js +261 -0
- package/dist/core/view/markdown.js +211 -0
- package/dist/core/view/report.js +58 -0
- package/dist/core/view/rows.js +146 -0
- package/dist/core/view/screen.js +767 -0
- package/dist/core/view/sidebar.js +151 -0
- package/dist/server.js +2 -0
- package/dist/tui/index.js +991 -0
- package/dist/tui/render.js +103 -0
- package/dist/tui/source.js +228 -0
- package/dist/tui/view/overlay.js +87 -0
- package/dist/tui/view/sidebar.js +78 -0
- package/package.json +64 -0
- package/server.js +6 -0
- package/tui.js +6 -0
- package/types/agent/plugin.d.ts +32 -0
- package/types/cli/preview.d.ts +10 -0
- package/types/core/adapt/v1.d.ts +33 -0
- package/types/core/adapt/v2.d.ts +22 -0
- package/types/core/model/changes.d.ts +98 -0
- package/types/core/model/model.d.ts +99 -0
- package/types/core/sample.d.ts +8 -0
- package/types/core/view/markdown.d.ts +22 -0
- package/types/core/view/report.d.ts +15 -0
- package/types/core/view/rows.d.ts +43 -0
- package/types/core/view/screen.d.ts +101 -0
- package/types/core/view/sidebar.d.ts +30 -0
- package/types/server.d.ts +2 -0
- package/types/tui/index.d.ts +26 -0
- package/types/tui/render.d.ts +27 -0
- package/types/tui/source.d.ts +45 -0
- package/types/tui/view/overlay.d.ts +30 -0
- package/types/tui/view/sidebar.d.ts +22 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode 1's events and state, as changes.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode 1 describes a run as parts of messages: a part is created (`message.part.updated`), may
|
|
5
|
+
* stream (`message.part.delta`, which names the part but not its kind — so kinds are remembered), and
|
|
6
|
+
* is updated as it completes. A user message's text is what the session was told; an assistant's
|
|
7
|
+
* `reasoning` and `text` are its thinking and its answer; a `tool` part is a call. Shapes measured on
|
|
8
|
+
* 1.18.32 (test/fixtures/v1.jsonl).
|
|
9
|
+
*
|
|
10
|
+
* Anything it does not recognise it returns nothing for, and says so through `unknown` — a new event
|
|
11
|
+
* shape is logged, never guessed at.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const obj = value => value && typeof value === "object" ? value : {};
|
|
15
|
+
const str = value => typeof value === "string" ? value : undefined;
|
|
16
|
+
const TOOL_STATES = new Set(["pending", "running", "completed", "failed"]);
|
|
17
|
+
const toolState = status => {
|
|
18
|
+
if (status === "error") return "failed";
|
|
19
|
+
return TOOL_STATES.has(status) ? status : undefined;
|
|
20
|
+
};
|
|
21
|
+
export function createV1Translator(unknown = () => {}) {
|
|
22
|
+
/** Parts stream by id alone; what kind of part an id is, and whose. */
|
|
23
|
+
const parts = new Map();
|
|
24
|
+
/** Message roles, since a part does not carry its message's. */
|
|
25
|
+
const roles = new Map();
|
|
26
|
+
const sessionInfo = (info, at) => {
|
|
27
|
+
const id = str(info.id);
|
|
28
|
+
if (!id) return [];
|
|
29
|
+
const out = [{
|
|
30
|
+
type: "session",
|
|
31
|
+
id,
|
|
32
|
+
...(str(info.parentID) ? {
|
|
33
|
+
parentID: str(info.parentID)
|
|
34
|
+
} : {}),
|
|
35
|
+
...(str(info.agent) ? {
|
|
36
|
+
agent: str(info.agent)
|
|
37
|
+
} : {}),
|
|
38
|
+
...(str(info.title) ? {
|
|
39
|
+
title: stripAgentSuffix(str(info.title))
|
|
40
|
+
} : {}),
|
|
41
|
+
...(denied(info.permission) ? {
|
|
42
|
+
denied: denied(info.permission)
|
|
43
|
+
} : {}),
|
|
44
|
+
at: Number(obj(info.time).created) || at
|
|
45
|
+
}];
|
|
46
|
+
const tokens = obj(info.tokens);
|
|
47
|
+
if (typeof info.cost === "number" || Object.keys(tokens).length > 0) {
|
|
48
|
+
out.push({
|
|
49
|
+
type: "usage",
|
|
50
|
+
id,
|
|
51
|
+
tokens: tokenTotal(tokens),
|
|
52
|
+
cost: Number(info.cost) || 0,
|
|
53
|
+
at
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
const part = (p, at) => {
|
|
59
|
+
const id = str(p.sessionID);
|
|
60
|
+
const partID = str(p.id);
|
|
61
|
+
if (!id || !partID) return [];
|
|
62
|
+
const role = roles.get(str(p.messageID) ?? "");
|
|
63
|
+
switch (p.type) {
|
|
64
|
+
case "text":
|
|
65
|
+
if (role === "user") {
|
|
66
|
+
parts.set(partID, {
|
|
67
|
+
kind: "prompt",
|
|
68
|
+
session: id
|
|
69
|
+
});
|
|
70
|
+
return str(p.text) && !p.synthetic ? [{
|
|
71
|
+
type: "prompt",
|
|
72
|
+
id,
|
|
73
|
+
key: partID,
|
|
74
|
+
text: str(p.text),
|
|
75
|
+
at
|
|
76
|
+
}] : [];
|
|
77
|
+
}
|
|
78
|
+
parts.set(partID, {
|
|
79
|
+
kind: "reply",
|
|
80
|
+
session: id
|
|
81
|
+
});
|
|
82
|
+
return [{
|
|
83
|
+
type: "reply",
|
|
84
|
+
id,
|
|
85
|
+
key: partID,
|
|
86
|
+
text: str(p.text) ?? "",
|
|
87
|
+
done: Boolean(obj(p.time).end),
|
|
88
|
+
at
|
|
89
|
+
}];
|
|
90
|
+
case "reasoning":
|
|
91
|
+
parts.set(partID, {
|
|
92
|
+
kind: "thinking",
|
|
93
|
+
session: id
|
|
94
|
+
});
|
|
95
|
+
return [{
|
|
96
|
+
type: "thinking",
|
|
97
|
+
id,
|
|
98
|
+
key: partID,
|
|
99
|
+
text: str(p.text) ?? "",
|
|
100
|
+
done: Boolean(obj(p.time).end),
|
|
101
|
+
at
|
|
102
|
+
}];
|
|
103
|
+
case "tool":
|
|
104
|
+
{
|
|
105
|
+
const state = obj(p.state);
|
|
106
|
+
const metadata = obj(state.metadata);
|
|
107
|
+
const status = toolState(state.status);
|
|
108
|
+
const output = str(state.output) ?? (status === "running" ? str(metadata.output) : undefined);
|
|
109
|
+
const time = obj(state.time);
|
|
110
|
+
const input = obj(state.input);
|
|
111
|
+
const summary = summaryOf(str(p.tool), metadata);
|
|
112
|
+
const out = [{
|
|
113
|
+
type: "tool",
|
|
114
|
+
id,
|
|
115
|
+
call: str(p.callID) ?? partID,
|
|
116
|
+
...(str(p.tool) ? {
|
|
117
|
+
name: str(p.tool)
|
|
118
|
+
} : {}),
|
|
119
|
+
...(status ? {
|
|
120
|
+
state: status
|
|
121
|
+
} : {}),
|
|
122
|
+
...(Object.keys(input).length > 0 ? {
|
|
123
|
+
input
|
|
124
|
+
} : {}),
|
|
125
|
+
...(output !== undefined ? {
|
|
126
|
+
output
|
|
127
|
+
} : {}),
|
|
128
|
+
...(str(state.error) ? {
|
|
129
|
+
error: str(state.error)
|
|
130
|
+
} : {}),
|
|
131
|
+
...(typeof time.start === "number" ? {
|
|
132
|
+
started: time.start
|
|
133
|
+
} : {}),
|
|
134
|
+
...(typeof time.end === "number" ? {
|
|
135
|
+
ended: time.end
|
|
136
|
+
} : {}),
|
|
137
|
+
...(summary ? {
|
|
138
|
+
summary
|
|
139
|
+
} : {}),
|
|
140
|
+
at
|
|
141
|
+
}];
|
|
142
|
+
/** The `task` call that launched a subagent names it; `background` is how it was launched. */
|
|
143
|
+
const child = str(metadata.sessionId);
|
|
144
|
+
if (p.tool === "task" && child && input.background === true) {
|
|
145
|
+
out.push({
|
|
146
|
+
type: "session",
|
|
147
|
+
id: child,
|
|
148
|
+
parentID: id,
|
|
149
|
+
background: true,
|
|
150
|
+
at
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
case "step-finish":
|
|
156
|
+
return [{
|
|
157
|
+
type: "step",
|
|
158
|
+
id,
|
|
159
|
+
at
|
|
160
|
+
}];
|
|
161
|
+
case "step-start":
|
|
162
|
+
case "patch":
|
|
163
|
+
case "snapshot":
|
|
164
|
+
case "file":
|
|
165
|
+
case "agent":
|
|
166
|
+
case "compaction":
|
|
167
|
+
case "subtask":
|
|
168
|
+
case "retry":
|
|
169
|
+
return [];
|
|
170
|
+
default:
|
|
171
|
+
unknown("part", {
|
|
172
|
+
type: p.type
|
|
173
|
+
});
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** A message's role, remembered for its parts; an assistant's says the model it ran on. */
|
|
179
|
+
const message = (info, at) => {
|
|
180
|
+
if (str(info.id) && str(info.role)) roles.set(str(info.id), str(info.role));
|
|
181
|
+
const id = str(info.sessionID);
|
|
182
|
+
return id && info.role === "assistant" && str(info.modelID) ? [{
|
|
183
|
+
type: "session",
|
|
184
|
+
id,
|
|
185
|
+
model: str(info.modelID),
|
|
186
|
+
at: Number(obj(info.time).created) || at
|
|
187
|
+
}] : [];
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
event(raw, at = Date.now()) {
|
|
191
|
+
const event = obj(raw);
|
|
192
|
+
const props = obj(event.properties);
|
|
193
|
+
const sessionID = str(props.sessionID);
|
|
194
|
+
switch (event.type) {
|
|
195
|
+
case "session.created":
|
|
196
|
+
case "session.updated":
|
|
197
|
+
return sessionInfo(obj(props.info), at);
|
|
198
|
+
case "session.status":
|
|
199
|
+
{
|
|
200
|
+
const status = obj(props.status).type;
|
|
201
|
+
if (!sessionID) return [];
|
|
202
|
+
if (status === "busy" || status === "retry") return [{
|
|
203
|
+
type: "status",
|
|
204
|
+
id: sessionID,
|
|
205
|
+
status: "busy",
|
|
206
|
+
at
|
|
207
|
+
}];
|
|
208
|
+
if (status === "idle") return [{
|
|
209
|
+
type: "status",
|
|
210
|
+
id: sessionID,
|
|
211
|
+
status: "idle",
|
|
212
|
+
at
|
|
213
|
+
}];
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
case "session.idle":
|
|
217
|
+
return sessionID ? [{
|
|
218
|
+
type: "status",
|
|
219
|
+
id: sessionID,
|
|
220
|
+
status: "idle",
|
|
221
|
+
at
|
|
222
|
+
}] : [];
|
|
223
|
+
case "session.error":
|
|
224
|
+
{
|
|
225
|
+
if (!sessionID) return [];
|
|
226
|
+
const error = obj(props.error);
|
|
227
|
+
const message = str(obj(error.data).message) ?? str(error.name) ?? "failed";
|
|
228
|
+
return [{
|
|
229
|
+
type: "status",
|
|
230
|
+
id: sessionID,
|
|
231
|
+
status: "failed",
|
|
232
|
+
error: message,
|
|
233
|
+
at
|
|
234
|
+
}];
|
|
235
|
+
}
|
|
236
|
+
case "permission.asked":
|
|
237
|
+
case "question.asked":
|
|
238
|
+
return sessionID ? [{
|
|
239
|
+
type: "status",
|
|
240
|
+
id: sessionID,
|
|
241
|
+
status: "waiting",
|
|
242
|
+
at
|
|
243
|
+
}] : [];
|
|
244
|
+
case "permission.replied":
|
|
245
|
+
case "question.replied":
|
|
246
|
+
case "question.rejected":
|
|
247
|
+
return sessionID ? [{
|
|
248
|
+
type: "status",
|
|
249
|
+
id: sessionID,
|
|
250
|
+
status: "busy",
|
|
251
|
+
at
|
|
252
|
+
}] : [];
|
|
253
|
+
case "message.updated":
|
|
254
|
+
return message(obj(props.info), at);
|
|
255
|
+
case "message.part.updated":
|
|
256
|
+
return part(obj(props.part), at);
|
|
257
|
+
case "message.part.delta":
|
|
258
|
+
{
|
|
259
|
+
const known = parts.get(str(props.partID) ?? "");
|
|
260
|
+
const delta = str(props.delta);
|
|
261
|
+
if (!known || !delta || props.field !== "text" || known.kind === "prompt") return [];
|
|
262
|
+
return [{
|
|
263
|
+
type: known.kind,
|
|
264
|
+
id: known.session,
|
|
265
|
+
key: str(props.partID),
|
|
266
|
+
delta,
|
|
267
|
+
at
|
|
268
|
+
}];
|
|
269
|
+
}
|
|
270
|
+
case "session.deleted":
|
|
271
|
+
case "session.diff":
|
|
272
|
+
case "message.removed":
|
|
273
|
+
case "message.part.removed":
|
|
274
|
+
case "todo.updated":
|
|
275
|
+
return [];
|
|
276
|
+
default:
|
|
277
|
+
unknown("event", {
|
|
278
|
+
type: event.type
|
|
279
|
+
});
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
history(messages, at = Date.now()) {
|
|
284
|
+
const out = [];
|
|
285
|
+
for (const each of messages) {
|
|
286
|
+
const info = obj(each.info);
|
|
287
|
+
const when = Number(obj(info.time).created) || at;
|
|
288
|
+
out.push(...message(info, when));
|
|
289
|
+
for (const p of each.parts) out.push(...part(obj(p), when));
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* A run that was stopped says so on its last answer (`MessageAbortedError`), not in a status the
|
|
293
|
+
* store keeps — read without it, a cancelled subagent loaded as one that finished.
|
|
294
|
+
*/
|
|
295
|
+
const last = obj(messages.at(-1)?.info);
|
|
296
|
+
const error = obj(last.error);
|
|
297
|
+
const id = str(last.sessionID);
|
|
298
|
+
if (id && last.role === "assistant" && (str(error.name) || str(obj(error.data).message))) {
|
|
299
|
+
const ended = Number(obj(last.time).completed) || Number(obj(last.time).created) || at;
|
|
300
|
+
out.push({
|
|
301
|
+
type: "status",
|
|
302
|
+
id,
|
|
303
|
+
status: "failed",
|
|
304
|
+
error: str(obj(error.data).message) ?? str(error.name) ?? "failed",
|
|
305
|
+
at: ended
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return out;
|
|
309
|
+
},
|
|
310
|
+
session(info, at = Date.now()) {
|
|
311
|
+
return sessionInfo(obj(info), at);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** `input + output + reasoning + cache`, the host's own total. */
|
|
317
|
+
export function tokenTotal(tokens) {
|
|
318
|
+
const cache = obj(tokens.cache);
|
|
319
|
+
const n = value => typeof value === "number" ? value : 0;
|
|
320
|
+
return n(tokens.input) + n(tokens.output) + n(tokens.reasoning) + n(cache.read) + n(cache.write);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** OpenCode 1 titles a subagent session "Task title (@explore subagent)"; the agent is shown apart. */
|
|
324
|
+
export function stripAgentSuffix(title) {
|
|
325
|
+
return title.replace(/\s*\(@[\w-]+ subagent\)\s*$/, "");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Permissions an agent's rules deny outright: `[{ permission: "task", action: "deny" }]`. */
|
|
329
|
+
function denied(rules) {
|
|
330
|
+
if (!Array.isArray(rules)) return undefined;
|
|
331
|
+
const names = rules.map(rule => obj(rule)).filter(rule => rule.action === "deny" && (rule.pattern === undefined || rule.pattern === "*")).map(rule => str(rule.permission)).filter(name => Boolean(name));
|
|
332
|
+
return names.length > 0 ? [...new Set(names)] : undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** A call's result in a few words, when the host gives the number. */
|
|
336
|
+
export function summaryOf(tool, metadata) {
|
|
337
|
+
if (typeof metadata.matches === "number") return `${metadata.matches} match${metadata.matches === 1 ? "" : "es"}`;
|
|
338
|
+
if (typeof metadata.count === "number") return `${metadata.count} result${metadata.count === 1 ? "" : "s"}`;
|
|
339
|
+
if ((tool === "bash" || tool === "shell") && typeof metadata.exit === "number" && metadata.exit !== 0) return `exit ${metadata.exit}`;
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|