@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/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
- continue: false,
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 { createEffect } from "solid-js";
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
- { location: ctx.location ?? ctx.data.location.default() },
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
- const tab = ctx.ui.tabs.list().find((tab) => tab.sessionID === worker.workerID);
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)) continue;
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?.title) continue;
133
- const title = `[${role}] ${session.title.replace(/^\[(?:Main|Worker)\] /, "")}`;
134
- if (title === session.title) continue;
135
- await ctx.client.session.rename({ sessionID: tab.sessionID, title });
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).catch((error) =>
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().then(
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: async () => {
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();