@opencode-cockpit/subagents 0.7.0 → 0.7.1
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 +2 -2
- package/dist/core/adapt/v2.js +3 -1
- package/dist/core/model/model.js +2 -2
- package/dist/tui/index.js +15 -0
- package/dist/tui/source.js +71 -17
- package/package.json +2 -2
- package/types/core/model/changes.d.ts +6 -0
- package/types/tui/source.d.ts +2 -0
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ Part of [opencode-cockpit](https://github.com/Codestz/opencode-cockpit). Install
|
|
|
10
10
|
through the bundle. Works on OpenCode 1.18+ and 2.0.15+.
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
opencode plugin @opencode-cockpit/subagents@0.7.
|
|
14
|
-
opencode plugin add @opencode-cockpit/subagents@0.7.
|
|
13
|
+
opencode plugin @opencode-cockpit/subagents@0.7.1 --global --force # OpenCode 1
|
|
14
|
+
opencode plugin add @opencode-cockpit/subagents@0.7.1 # OpenCode 2
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## What it does
|
package/dist/core/adapt/v2.js
CHANGED
|
@@ -360,12 +360,14 @@ export function createV2Translator(unknown = () => {}) {
|
|
|
360
360
|
counted = true;
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
|
+
/** Stamped with the run's own last moment: "now" made a reopened run look like it ended now. */
|
|
364
|
+
const latest = out.reduce((most, change) => Math.max(most, change.at), 0) || at;
|
|
363
365
|
if (counted) out.push({
|
|
364
366
|
type: "usage",
|
|
365
367
|
id,
|
|
366
368
|
tokens,
|
|
367
369
|
cost,
|
|
368
|
-
at
|
|
370
|
+
at: latest
|
|
369
371
|
});
|
|
370
372
|
return out;
|
|
371
373
|
},
|
package/dist/core/model/model.js
CHANGED
|
@@ -47,8 +47,8 @@ function applyStatus(s, change) {
|
|
|
47
47
|
if (change.error) s.error = change.error;
|
|
48
48
|
settle(s, change.at);
|
|
49
49
|
} else if (s.status !== "failed") {
|
|
50
|
-
/** Idle before it ever worked is a session that has not started, not one that finished. */
|
|
51
|
-
s.status = s.status === "starting" && s.entries.length === 0 ? "starting" : "done";
|
|
50
|
+
/** Idle before it ever worked is a session that has not started, not one that finished — live. */
|
|
51
|
+
s.status = s.status === "starting" && s.entries.length === 0 && !change.settled ? "starting" : "done";
|
|
52
52
|
s.ended = change.at;
|
|
53
53
|
settle(s, change.at);
|
|
54
54
|
}
|
package/dist/tui/index.js
CHANGED
|
@@ -621,6 +621,21 @@ export function createSubagentsTui({
|
|
|
621
621
|
const session = opened();
|
|
622
622
|
if (!session) return;
|
|
623
623
|
if (!busy(session) && session.status !== "waiting") return remove([session.id]);
|
|
624
|
+
/**
|
|
625
|
+
* Stopping is only for a run OpenCode says is going. One Cockpit had wrong — shown running after a
|
|
626
|
+
* reopen — was "stopped", and the main agent was told about work that had long ended.
|
|
627
|
+
*/
|
|
628
|
+
const said = feed.check(session.id);
|
|
629
|
+
if (said.some(change => change.type === "status" && change.status !== "busy" && change.status !== "waiting")) {
|
|
630
|
+
applyAll(model, said);
|
|
631
|
+
log.info("stop skipped: not running", {
|
|
632
|
+
id: session.id
|
|
633
|
+
});
|
|
634
|
+
return set({
|
|
635
|
+
notice: `${session.agent} had already finished — nothing to stop. x again removes it.`,
|
|
636
|
+
stopping: undefined
|
|
637
|
+
});
|
|
638
|
+
}
|
|
624
639
|
if (surface.stopping !== session.id) {
|
|
625
640
|
surface.stopping = session.id;
|
|
626
641
|
surface.notice = `Press x again to stop ${session.agent}.`;
|
package/dist/tui/source.js
CHANGED
|
@@ -16,14 +16,30 @@ const V1_EVENTS = ["session.created", "session.updated", "session.status", "sess
|
|
|
16
16
|
*/
|
|
17
17
|
// biome-ignore lint/suspicious/noExplicitAny: see above — every access is to a measured field
|
|
18
18
|
|
|
19
|
+
/** The last moment a stored run did anything: its latest change, or `fallback` when it has none. */
|
|
20
|
+
export function endOf(changes, fallback) {
|
|
21
|
+
let last = 0;
|
|
22
|
+
for (const change of changes) {
|
|
23
|
+
last = Math.max(last, change.at);
|
|
24
|
+
if (change.type === "tool" && change.ended !== undefined) last = Math.max(last, change.ended);
|
|
25
|
+
}
|
|
26
|
+
return last || fallback;
|
|
27
|
+
}
|
|
19
28
|
export function createSource(api, log, emit) {
|
|
20
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Each unexpected shape once, not once per event. An event we do not use is not news — OpenCode 2
|
|
31
|
+
* hands every plugin every event in the app (`provider.updated`, `session.viewed`…), and as
|
|
32
|
+
* warnings they filled doctor's report. A part of a kind we did not expect inside an event we do
|
|
33
|
+
* use still is: that is a shape that changed under us.
|
|
34
|
+
*/
|
|
21
35
|
const told = new Set();
|
|
22
36
|
const unknown = (what, detail) => {
|
|
23
37
|
const key = `${what} ${JSON.stringify(detail)}`;
|
|
24
38
|
if (told.has(key)) return;
|
|
25
39
|
told.add(key);
|
|
26
|
-
log.
|
|
40
|
+
if (what === "event") log.debug("unused event", {
|
|
41
|
+
...detail
|
|
42
|
+
});else log.warn("unrecognised event", {
|
|
27
43
|
what,
|
|
28
44
|
...detail
|
|
29
45
|
});
|
|
@@ -64,17 +80,39 @@ export function createSource(api, log, emit) {
|
|
|
64
80
|
for (const child of Array.isArray(children) ? children : []) {
|
|
65
81
|
const id = child.id;
|
|
66
82
|
const messages = v1.state.session.messages(id) ?? [];
|
|
67
|
-
|
|
83
|
+
let history = messages.map(info => ({
|
|
68
84
|
info,
|
|
69
85
|
parts: v1.state.part(info.id) ?? []
|
|
70
86
|
}));
|
|
87
|
+
/**
|
|
88
|
+
* The interface's store holds the messages of sessions it has loaded, which in a long
|
|
89
|
+
* conversation is not every subagent. Read from the server when it has none.
|
|
90
|
+
*/
|
|
91
|
+
if (history.length === 0) {
|
|
92
|
+
const fetched = await v1.client.session.messages({
|
|
93
|
+
sessionID: id
|
|
94
|
+
}).catch(error => {
|
|
95
|
+
log.warn("history fetch failed", {
|
|
96
|
+
id,
|
|
97
|
+
error
|
|
98
|
+
});
|
|
99
|
+
return undefined;
|
|
100
|
+
});
|
|
101
|
+
const list = fetched?.data ?? fetched ?? [];
|
|
102
|
+
if (Array.isArray(list)) history = list;
|
|
103
|
+
log.debug("history fetched", {
|
|
104
|
+
id,
|
|
105
|
+
messages: history.length
|
|
106
|
+
});
|
|
107
|
+
}
|
|
71
108
|
const status = v1.state.session.status(id);
|
|
72
109
|
/**
|
|
73
110
|
* Busy only when the host says so. A finished subagent is not in the host's status store at
|
|
74
111
|
* all — reading "no status" as "unknown" left every old subagent running forever.
|
|
75
112
|
*/
|
|
76
113
|
const busy = status?.type === "busy" || status?.type === "retry";
|
|
77
|
-
|
|
114
|
+
const past = translate.history(history);
|
|
115
|
+
emit([...translate.session(child), ...past, busy ? {
|
|
78
116
|
type: "status",
|
|
79
117
|
id,
|
|
80
118
|
status: "busy",
|
|
@@ -83,7 +121,8 @@ export function createSource(api, log, emit) {
|
|
|
83
121
|
type: "status",
|
|
84
122
|
id,
|
|
85
123
|
status: "idle",
|
|
86
|
-
|
|
124
|
+
settled: true,
|
|
125
|
+
at: endOf(past, Number(child.time?.updated) || Date.now())
|
|
87
126
|
}]);
|
|
88
127
|
await visit(id, depth + 1);
|
|
89
128
|
}
|
|
@@ -137,14 +176,19 @@ export function createSource(api, log, emit) {
|
|
|
137
176
|
});
|
|
138
177
|
},
|
|
139
178
|
check(id) {
|
|
179
|
+
/**
|
|
180
|
+
* OpenCode 1 keeps a status only while a session works: a finished one has none (measured on a
|
|
181
|
+
* reopened conversation). Read as "unknown", a subagent stuck at running was never corrected.
|
|
182
|
+
*/
|
|
140
183
|
const status = v1.state.session.status(id);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
184
|
+
if (status?.type === "busy" || status?.type === "retry") return [];
|
|
185
|
+
return [{
|
|
186
|
+
type: "status",
|
|
187
|
+
id,
|
|
188
|
+
status: "idle",
|
|
189
|
+
settled: true,
|
|
190
|
+
at: Date.now()
|
|
191
|
+
}];
|
|
148
192
|
},
|
|
149
193
|
dispose: () => {
|
|
150
194
|
for (const off of offs.splice(0)) off();
|
|
@@ -171,13 +215,20 @@ export function createSource(api, log, emit) {
|
|
|
171
215
|
}));
|
|
172
216
|
messages = v2.data.session.message.list(id) ?? [];
|
|
173
217
|
}
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
218
|
+
const past = translate.history(id, messages);
|
|
219
|
+
/** When its work ended, not when the store was last touched — reopening touches it. */
|
|
220
|
+
const ended = endOf(past, Number(info.time?.updated) || Date.now());
|
|
221
|
+
const status = translate.status(id, v2.data.session.status(id), ended);
|
|
222
|
+
emit([...translate.session(info), ...past, /** Not known to the host is not running: the same lesson as OpenCode 1's store. */
|
|
223
|
+
...(status.length > 0 ? status.map(change => ({
|
|
224
|
+
...change,
|
|
225
|
+
settled: true
|
|
226
|
+
})) : [{
|
|
177
227
|
type: "status",
|
|
178
228
|
id,
|
|
179
229
|
status: "idle",
|
|
180
|
-
|
|
230
|
+
settled: true,
|
|
231
|
+
at: ended
|
|
181
232
|
}])]);
|
|
182
233
|
}
|
|
183
234
|
},
|
|
@@ -219,7 +270,10 @@ export function createSource(api, log, emit) {
|
|
|
219
270
|
});
|
|
220
271
|
},
|
|
221
272
|
check(id) {
|
|
222
|
-
return translate.status(id, v2.data.session.status(id))
|
|
273
|
+
return translate.status(id, v2.data.session.status(id)).map(change => ({
|
|
274
|
+
...change,
|
|
275
|
+
settled: true
|
|
276
|
+
}));
|
|
223
277
|
},
|
|
224
278
|
dispose: () => {
|
|
225
279
|
for (const each of offs.splice(0)) each();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode-cockpit/subagents",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "See what your subagents are doing: in the sidebar, full screen, and message them",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@opencode-cockpit/client": "0.7.
|
|
51
|
+
"@opencode-cockpit/client": "0.7.1",
|
|
52
52
|
"@opencode-ai/plugin": "1.18.31"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
@@ -37,6 +37,12 @@ export type Change =
|
|
|
37
37
|
id: string;
|
|
38
38
|
status: "busy" | "idle" | "failed" | "waiting";
|
|
39
39
|
error?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Said about a stored run — loaded, or checked after it went quiet — rather than live. Idle then
|
|
42
|
+
* means finished even with nothing recorded: a subagent whose history did not load is not one
|
|
43
|
+
* about to start, and read that way it showed as running for good.
|
|
44
|
+
*/
|
|
45
|
+
settled?: boolean;
|
|
40
46
|
at: number;
|
|
41
47
|
}
|
|
42
48
|
/** Something said *to* it: the first is the task it was given, the rest are messages. */
|
package/types/tui/source.d.ts
CHANGED
|
@@ -42,4 +42,6 @@ export interface Source {
|
|
|
42
42
|
check: (id: string) => Change[];
|
|
43
43
|
dispose: () => void;
|
|
44
44
|
}
|
|
45
|
+
/** The last moment a stored run did anything: its latest change, or `fallback` when it has none. */
|
|
46
|
+
export declare function endOf(changes: readonly Change[], fallback: number): number;
|
|
45
47
|
export declare function createSource(api: Host, log: Log, emit: (changes: Change[]) => void): Source;
|