@op1/threads 0.1.6 → 0.1.8
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 +68 -6
- package/package.json +13 -5
- package/src/activity-model.ts +152 -0
- package/src/activity-picker.tsx +175 -0
- package/src/activity-rail.ts +135 -0
- package/src/activity-theme.ts +49 -0
- package/src/activity.ts +781 -0
- package/src/threads.ts +1 -1
- package/tui.ts +64 -32
package/src/threads.ts
CHANGED
|
@@ -358,7 +358,7 @@ export function threads(
|
|
|
358
358
|
await owned(actor, input.workerID);
|
|
359
359
|
await ctx.session.interrupt({
|
|
360
360
|
sessionID: input.workerID,
|
|
361
|
-
|
|
361
|
+
resume: false,
|
|
362
362
|
});
|
|
363
363
|
return view(await ctx.session.get({ sessionID: input.workerID }));
|
|
364
364
|
},
|
package/tui.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin/tui";
|
|
2
|
-
import {
|
|
2
|
+
import { getComponentCatalogue } from "@opentui/solid/components";
|
|
3
|
+
import { createEffect, createSignal } from "solid-js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { ThreadsRpc } from "./src/rpc";
|
|
6
|
+
import { activity } from "./src/activity";
|
|
7
|
+
import { cleanRoleTitle } from "./src/activity-model";
|
|
8
|
+
import {
|
|
9
|
+
BoxRenderable,
|
|
10
|
+
ScrollBoxRenderable,
|
|
11
|
+
TextRenderable,
|
|
12
|
+
TextAttributes,
|
|
13
|
+
RGBA,
|
|
14
|
+
} from "@opentui/core";
|
|
5
15
|
|
|
6
16
|
const CoordinatorRef = z.object({ coordinatorID: z.string() });
|
|
7
17
|
|
|
@@ -9,15 +19,26 @@ export default Plugin.define({
|
|
|
9
19
|
id: "op-threads",
|
|
10
20
|
setup(ctx) {
|
|
11
21
|
const rpc = ctx.client.rpc(ThreadsRpc);
|
|
22
|
+
const sidebar = activity(
|
|
23
|
+
ctx,
|
|
24
|
+
{ BoxRenderable, ScrollBoxRenderable, TextRenderable, TextAttributes, RGBA },
|
|
25
|
+
{ createEffect, createSignal },
|
|
26
|
+
getComponentCatalogue().spinner,
|
|
27
|
+
);
|
|
28
|
+
const [cleaned, saveCleaned] = ctx.storage.store("role-title-cleanup", {
|
|
29
|
+
initial: { ids: [] as string[] },
|
|
30
|
+
});
|
|
12
31
|
const initial: { workerIDs: string[] } = { workerIDs: [] };
|
|
13
32
|
const [seen, updateSeen] = ctx.storage.memory("seen-workers", { initial });
|
|
14
33
|
const closing = new Set<string>();
|
|
34
|
+
const abort = new AbortController();
|
|
15
35
|
let stopped = false;
|
|
16
36
|
let running = false;
|
|
17
37
|
let reopenPending = false;
|
|
18
38
|
let lastError: string | undefined;
|
|
19
39
|
let movingFrom: string | undefined;
|
|
20
40
|
function groupTabs() {
|
|
41
|
+
if (sidebar.mounted()) return;
|
|
21
42
|
const tabs = ctx.ui.tabs.list().map((tab) => {
|
|
22
43
|
const projectID = ctx.data.session.get(tab.sessionID)?.projectID;
|
|
23
44
|
return {
|
|
@@ -80,11 +101,20 @@ export default Plugin.define({
|
|
|
80
101
|
if (!coordinatorIDs.length) return;
|
|
81
102
|
const { workers } = await (reopen ? rpc.restore : rpc.snapshot)(
|
|
82
103
|
{ coordinatorIDs },
|
|
83
|
-
{
|
|
104
|
+
{
|
|
105
|
+
location: ctx.location ?? ctx.data.location.default(),
|
|
106
|
+
signal: abort.signal,
|
|
107
|
+
},
|
|
84
108
|
);
|
|
109
|
+
if (reopen)
|
|
110
|
+
await sidebar.restore(workers.map((worker) => worker.workerID));
|
|
111
|
+
sidebar.updateWorkers(workers);
|
|
85
112
|
for (const worker of workers) {
|
|
86
113
|
if (stopped) return;
|
|
87
|
-
|
|
114
|
+
if (!reopen && sidebar.isDismissed(worker.workerID)) continue;
|
|
115
|
+
const tab = ctx.ui.tabs
|
|
116
|
+
.list()
|
|
117
|
+
.find((tab) => tab.sessionID === worker.workerID);
|
|
88
118
|
if (closing.has(worker.workerID) && tab) continue;
|
|
89
119
|
const closed = closing.delete(worker.workerID);
|
|
90
120
|
if (
|
|
@@ -107,7 +137,8 @@ export default Plugin.define({
|
|
|
107
137
|
}
|
|
108
138
|
continue;
|
|
109
139
|
}
|
|
110
|
-
if (!reopen && !closed && seen.workerIDs.includes(worker.workerID))
|
|
140
|
+
if (!reopen && !closed && seen.workerIDs.includes(worker.workerID))
|
|
141
|
+
continue;
|
|
111
142
|
await ctx.data.session.sync(worker.workerID);
|
|
112
143
|
if (
|
|
113
144
|
!stopped &&
|
|
@@ -129,35 +160,43 @@ export default Plugin.define({
|
|
|
129
160
|
if (stopped) return;
|
|
130
161
|
const role = roles.get(tab.sessionID);
|
|
131
162
|
const session = ctx.data.session.get(tab.sessionID);
|
|
132
|
-
if (!role || !session
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
163
|
+
if (!role || !session || cleaned.ids.includes(tab.sessionID))
|
|
164
|
+
continue;
|
|
165
|
+
const fresh = await ctx.client.session.get(
|
|
166
|
+
{ sessionID: tab.sessionID },
|
|
167
|
+
{ signal: abort.signal },
|
|
168
|
+
);
|
|
169
|
+
if (stopped) return;
|
|
170
|
+
const title = cleanRoleTitle(fresh.title ?? "", true);
|
|
171
|
+
if (title && title !== fresh.title)
|
|
172
|
+
await ctx.client.session.update(
|
|
173
|
+
{ sessionID: tab.sessionID, title },
|
|
174
|
+
{ signal: abort.signal },
|
|
175
|
+
);
|
|
176
|
+
if (stopped) return;
|
|
177
|
+
await saveCleaned((draft) => {
|
|
178
|
+
if (!draft.ids.includes(tab.sessionID))
|
|
179
|
+
draft.ids.push(tab.sessionID);
|
|
180
|
+
});
|
|
136
181
|
}
|
|
137
182
|
groupTabs();
|
|
138
183
|
}
|
|
184
|
+
lastError = undefined;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
const message = `Managed worker tabs: ${String(error)}`;
|
|
187
|
+
if (!stopped && message !== lastError)
|
|
188
|
+
ctx.ui.toast.show({ message, variant: "error" });
|
|
189
|
+
lastError = message;
|
|
139
190
|
} finally {
|
|
140
191
|
running = false;
|
|
141
192
|
if (reopenPending) {
|
|
142
193
|
reopenPending = false;
|
|
143
|
-
void reconcile(true)
|
|
144
|
-
ctx.ui.toast.show({ message: String(error), variant: "error" }),
|
|
145
|
-
);
|
|
194
|
+
void reconcile(true);
|
|
146
195
|
}
|
|
147
196
|
}
|
|
148
197
|
}
|
|
149
198
|
const refresh = () => {
|
|
150
|
-
void reconcile()
|
|
151
|
-
() => {
|
|
152
|
-
lastError = undefined;
|
|
153
|
-
},
|
|
154
|
-
(error: unknown) => {
|
|
155
|
-
const message = `Managed worker tabs: ${String(error)}`;
|
|
156
|
-
if (!stopped && message !== lastError)
|
|
157
|
-
ctx.ui.toast.show({ message, variant: "error" });
|
|
158
|
-
lastError = message;
|
|
159
|
-
},
|
|
160
|
-
);
|
|
199
|
+
void reconcile();
|
|
161
200
|
};
|
|
162
201
|
const stopEvents = ctx.data.listen(({ details }) => {
|
|
163
202
|
if (details.type.startsWith("session.")) refresh();
|
|
@@ -178,16 +217,7 @@ export default Plugin.define({
|
|
|
178
217
|
title: "Reopen managed worker tabs",
|
|
179
218
|
palette: true,
|
|
180
219
|
slash: { name: "threads" },
|
|
181
|
-
run:
|
|
182
|
-
try {
|
|
183
|
-
await reconcile(true);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
ctx.ui.toast.show({
|
|
186
|
-
message: String(error),
|
|
187
|
-
variant: "error",
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
},
|
|
220
|
+
run: () => reconcile(true),
|
|
191
221
|
},
|
|
192
222
|
],
|
|
193
223
|
}));
|
|
@@ -197,6 +227,8 @@ export default Plugin.define({
|
|
|
197
227
|
refresh();
|
|
198
228
|
return () => {
|
|
199
229
|
stopped = true;
|
|
230
|
+
abort.abort();
|
|
231
|
+
sidebar.dispose();
|
|
200
232
|
clearInterval(timer);
|
|
201
233
|
stopEvents();
|
|
202
234
|
removeSlot();
|