@op1/threads 0.1.2 → 0.1.4
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 +20 -7
- package/index.ts +17 -3
- package/package.json +1 -1
- package/skills/managed-sessions/SKILL.md +14 -5
- package/src/rpc.ts +6 -0
- package/src/threads.ts +35 -6
- package/tui.ts +19 -1
package/README.md
CHANGED
|
@@ -35,11 +35,15 @@ threads_spawn({
|
|
|
35
35
|
})
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
The worker opens in a native tab without taking focus. Select that tab to read its conversation. Use `threads_list` to inspect reports and `/threads` to reopen closed worker tabs.
|
|
38
|
+
The worker opens in a native tab without taking focus. Select that tab to read its conversation. Use `threads_list` to inspect reports and `/threads` to reopen closed worker tabs.
|
|
39
|
+
|
|
40
|
+
Both the parent and managed workers can use native `subagent` for bounded tasks and role-specific reviews. The parent can mix direct subagent calls with managed threads. Each worker can work directly or delegate within its brief and inherited permissions, then review the results and submit its own combined report.
|
|
41
|
+
|
|
42
|
+
The parent writes the `task` brief. The plugin appends an explicit reminder that native delegation is optional and that workers cannot call `threads_spawn`. Include the worker's delegation budget in the brief. Use `No children` only when the task requires it, since that also rules out native subagents.
|
|
39
43
|
|
|
40
44
|
## Tools
|
|
41
45
|
|
|
42
|
-
The native tool names use namespace `threads` and individual names `spawn`, `list`, `send`, `interrupt`, and `report`.
|
|
46
|
+
The native tool names use namespace `threads` and individual names `spawn`, `list`, `send`, `interrupt`, `hide`, and `report`.
|
|
43
47
|
|
|
44
48
|
| Tool | Input | Result |
|
|
45
49
|
| --- | --- | --- |
|
|
@@ -47,13 +51,14 @@ The native tool names use namespace `threads` and individual names `spawn`, `lis
|
|
|
47
51
|
| `threads_list` | `{}` | `{ workers: WorkerView[] }` |
|
|
48
52
|
| `threads_send` | `{ workerID, key, text }` | `{ workerID, messageID }` |
|
|
49
53
|
| `threads_interrupt` | `{ workerID }` | Worker view |
|
|
54
|
+
| `threads_hide` | `{ workerID }` | Worker view |
|
|
50
55
|
| `threads_report` | `{ verdict, summary, evidence }` | `{ workerID, report }` |
|
|
51
56
|
|
|
52
57
|
All fields are strings except `evidence`, which is an array of strings. Verdicts are `PASS`, `PASS WITH NOTES`, `FAIL`, and `INCONCLUSIVE`. Each tool returns JSON in native `content` and the same value in `output`.
|
|
53
58
|
|
|
54
59
|
`directory` must exist and be absolute. Spawn uses native `subagent` permission gating. The worker uses the coordinator's agent and model, with resolved agent permissions followed by session permissions. Its actual tool actions still pass through native permission checks. There is no separate directory-approval flow or agent/model override.
|
|
55
60
|
|
|
56
|
-
Tool identity comes from the calling session. Only the owning coordinator can send or
|
|
61
|
+
Tool identity comes from the calling session. Only the owning coordinator can send, interrupt, or hide a worker. Only the exact original top-level worker can report. Native subagents and managed workers cannot spawn managed workers. Native `subagent` remains available.
|
|
57
62
|
|
|
58
63
|
## Identity and retries
|
|
59
64
|
|
|
@@ -65,28 +70,36 @@ Send keys are scoped to the worker and determine a stable message ID. A retry wi
|
|
|
65
70
|
|
|
66
71
|
## Worker views and limits
|
|
67
72
|
|
|
68
|
-
`WorkerView` contains `workerID`, `coordinatorID`, `key`, `title`, `directory`, `outcome`, and `
|
|
73
|
+
`WorkerView` contains `workerID`, `coordinatorID`, `key`, `title`, `directory`, `outcome`, `report`, and `hidden`. `outcome` is the native last execution outcome, or `null` before one exists. It is not current activity. Native tabs display current busy, attention, and unread state.
|
|
69
74
|
|
|
70
75
|
`report` is the explicit worker claim, or `null`. Native `succeeded` means the agent loop completed, not that the assigned task passed.
|
|
71
76
|
|
|
77
|
+
`hidden` is the desired idle-tab visibility. Current activity, input requests, or selection can keep that tab open.
|
|
78
|
+
|
|
72
79
|
Plugin option `maxWorkers` defaults to 4 and accepts integers from 1 through 32. Admission is serialized by coordinator within the loaded server process. A worker without a report continues to occupy a slot unless its native outcome is `failed` or `interrupted`. A successful run without a report does not silently free its slot.
|
|
73
80
|
|
|
74
81
|
## Terminal and RPC
|
|
75
82
|
|
|
76
83
|
The terminal synchronizes workers before opening native tabs without changing focus. A TUI memory index survives plugin reloads and respects manually closed tabs. `/threads` explicitly reopens workers for open coordinator tabs. A new TUI recovers workers from durable storage. Closing the TUI does not interrupt workers.
|
|
77
84
|
|
|
85
|
+
Workers with `PASS` or `PASS WITH NOTES` reports hide automatically once idle. This also applies to reports saved before upgrading. Unreported workers and `FAIL` or `INCONCLUSIVE` reports stay visible. The selected tab, running workers, and tabs needing input stay open until they are inactive.
|
|
86
|
+
|
|
87
|
+
The coordinator can call `threads_hide` when a worker is no longer needed. Hiding preserves the conversation and report, survives restarts, and does not free an admission slot. `/threads` restores hidden workers and keeps them visible for inspection. A valid `threads_send` follow-up also restores its worker. Visibility overrides belong to the original report message ID, so recreating a deleted worker cannot inherit its hidden state.
|
|
88
|
+
|
|
89
|
+
Reports reach the coordinator through silent synthetic messages. They remain available through `threads_list` and the session history without adding a notification row to the conversation. Older report notification rows remain in native history.
|
|
90
|
+
|
|
78
91
|
All open native root-session tabs are automatically grouped by OpenCode project ID, including sessions not managed by this plugin. Projects follow their first appearance in the current tab order. Worktrees with the same project ID stay together.
|
|
79
92
|
|
|
80
|
-
Within each project, running sessions, the selected tab, and sessions waiting for input come before idle sessions. Tabs with the same priority keep their relative order.
|
|
93
|
+
Within each project, running sessions, the selected tab, and sessions waiting for input come before idle sessions. Tabs with the same priority keep their relative order. Idle is a display priority, not a completion verdict. Each tab with unloaded project metadata stays in its own group until that metadata becomes available. Reconciliation moves only out-of-order tabs, without changing focus or closing and reopening them to reorder. Native tabs do not support divider rows.
|
|
81
94
|
|
|
82
|
-
The
|
|
95
|
+
The RPC definition is `ThreadsRpc` in `src/rpc.ts`, with ID `threads`. `snapshot` is read-only. The user-invoked `restore` method clears hidden state for the supplied coordinators' workers. Both methods accept and return:
|
|
83
96
|
|
|
84
97
|
```ts
|
|
85
98
|
input: { coordinatorIDs: string[] }
|
|
86
99
|
result: { workers: WorkerView[] }
|
|
87
100
|
```
|
|
88
101
|
|
|
89
|
-
The input accepts at most 100 coordinator IDs. Raw HTTP RPC requests wrap the input as `{ "input": { "coordinatorIDs": ["ses_..."] } }`.
|
|
102
|
+
The input accepts at most 100 coordinator IDs. Raw HTTP RPC requests wrap the input as `{ "input": { "coordinatorIDs": ["ses_..."] } }`. Each method declares `errors: {}` and the RPC declares `events: {}`. The TUI subscribes to native session events and reconciles at most one snapshot at a time, with a three-second missed-event refresh.
|
|
90
103
|
|
|
91
104
|
## Verification and limits
|
|
92
105
|
|
package/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Plugin } from "@opencode/plugin";
|
|
|
2
2
|
import type { SessionContext } from "@opencode/plugin/promise/session";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { Report, ThreadsRpc, WorkerView } from "./src/rpc";
|
|
5
|
-
import {
|
|
5
|
+
import { Send, Spawn, WorkerTarget, threads } from "./src/threads";
|
|
6
6
|
|
|
7
7
|
export default Plugin.define({
|
|
8
8
|
id: "op-threads",
|
|
@@ -26,6 +26,9 @@ export default Plugin.define({
|
|
|
26
26
|
snapshot: async ({ coordinatorIDs }) => ({
|
|
27
27
|
workers: (await Promise.all(coordinatorIDs.map(workers.list))).flat(),
|
|
28
28
|
}),
|
|
29
|
+
restore: async ({ coordinatorIDs }) => ({
|
|
30
|
+
workers: (await Promise.all(coordinatorIDs.map(workers.restore))).flat(),
|
|
31
|
+
}),
|
|
29
32
|
});
|
|
30
33
|
await ctx.tool.transform((editor) => {
|
|
31
34
|
editor.namespace({
|
|
@@ -35,7 +38,7 @@ export default Plugin.define({
|
|
|
35
38
|
editor.add({
|
|
36
39
|
name: "spawn",
|
|
37
40
|
description:
|
|
38
|
-
"Delegate a task to a top-level worker in an existing absolute directory. Reuse key only for identical requests.
|
|
41
|
+
"Delegate a task to a top-level worker in an existing absolute directory. Both you and the worker may use native subagent when useful. Include any delegation limits in task. Workers cannot call threads_spawn. Reuse key only for identical requests.",
|
|
39
42
|
input: Spawn,
|
|
40
43
|
output: WorkerView,
|
|
41
44
|
options: {
|
|
@@ -81,7 +84,7 @@ export default Plugin.define({
|
|
|
81
84
|
editor.add({
|
|
82
85
|
name: "interrupt",
|
|
83
86
|
description: "Interrupt your worker without claiming task success.",
|
|
84
|
-
input:
|
|
87
|
+
input: WorkerTarget,
|
|
85
88
|
output: WorkerView,
|
|
86
89
|
options: { namespace: "threads", codemode: false },
|
|
87
90
|
execute: async (input, tool) => {
|
|
@@ -89,6 +92,17 @@ export default Plugin.define({
|
|
|
89
92
|
return { content: JSON.stringify(output), output };
|
|
90
93
|
},
|
|
91
94
|
});
|
|
95
|
+
editor.add({
|
|
96
|
+
name: "hide",
|
|
97
|
+
description: "Hide a worker you no longer need from the sidebar without deleting its conversation or report. Only its coordinator may hide it. Running, selected, or attention-needed tabs stay open until idle. Use /threads to restore hidden tabs.",
|
|
98
|
+
input: WorkerTarget,
|
|
99
|
+
output: WorkerView,
|
|
100
|
+
options: { namespace: "threads", codemode: false },
|
|
101
|
+
execute: async (input, tool) => {
|
|
102
|
+
const output = await workers.hide(tool.sessionID, input);
|
|
103
|
+
return { content: JSON.stringify(output), output };
|
|
104
|
+
},
|
|
105
|
+
});
|
|
92
106
|
editor.add({
|
|
93
107
|
name: "report",
|
|
94
108
|
description:
|
package/package.json
CHANGED
|
@@ -5,36 +5,45 @@ description: Spawn and monitor visible top-level OpenCode workers with op-thread
|
|
|
5
5
|
|
|
6
6
|
# Managed sessions
|
|
7
7
|
|
|
8
|
-
Choose the delegation mode
|
|
8
|
+
Choose the delegation mode for each task. The parent can mix both modes in the same run.
|
|
9
9
|
|
|
10
10
|
- Use native `subagent` for a bounded task or review.
|
|
11
11
|
- Use `threads_spawn` for an independent workstream with its own visible session and assigned directory.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Both the parent and managed workers may use native `subagent` when useful. Delegation is optional. Managed workers cannot create further managed workers. Count both modes and workers' native subagents against the active protocol's delegation budget. Keep every writer in its own assigned worktree. These tools do not create worktrees.
|
|
14
14
|
|
|
15
15
|
## Start work
|
|
16
16
|
|
|
17
17
|
1. Confirm the `threads_spawn` tool is available. If it is unavailable, use native `subagent` or report that the plugin needs activation. Do not substitute a hidden `opencode run` process.
|
|
18
18
|
2. Assign an existing absolute directory and a stable task key.
|
|
19
|
-
3. Call `threads_spawn` with `key`, `title`, `directory`, and `task`. Include the goal, scope, relevant context, constraints, acceptance criteria, verification commands, and expected report in `task`.
|
|
19
|
+
3. Call `threads_spawn` with `key`, `title`, `directory`, and `task`. Include the goal, scope, relevant context, constraints, delegation allowance, acceptance criteria, verification commands, and expected report in `task`.
|
|
20
20
|
4. Save the returned worker session ID with the work unit.
|
|
21
21
|
|
|
22
22
|
An identical spawn key retries the original admission. Different work requires a new key. A worker inherits the coordinator's agent, model, and permission constraints. It has a separate conversation, so include all context it needs in the task brief.
|
|
23
23
|
|
|
24
|
+
## Write the delegation allowance
|
|
25
|
+
|
|
26
|
+
The parent writes `task`; the plugin appends worker instructions. Give workers the option to delegate without requiring a sub-coordinator role. Include this allowance and the worker's share of the remaining delegation budget:
|
|
27
|
+
|
|
28
|
+
> You may work directly or use native `subagent` for bounded tasks and reviews when useful. Pass your scope and constraints to subagents, review their results, and resolve outstanding work before calling `threads_report` yourself.
|
|
29
|
+
|
|
30
|
+
Use a no-delegation restriction only for a task-specific reason or an explicit user constraint, and state the reason. Being a managed worker or having bounded scope does not by itself make the worker a leaf. To limit managed-thread nesting, say `Do not call threads_spawn` rather than `No children`.
|
|
31
|
+
|
|
24
32
|
## Coordinate
|
|
25
33
|
|
|
26
34
|
- Use `threads_list` for a progress snapshot when making a scheduling or delivery decision.
|
|
27
35
|
- Use `threads_send` with a stable message key for a correction or follow-up within the worker's assigned task.
|
|
28
36
|
- Use `threads_interrupt` to stop that worker's execution.
|
|
37
|
+
- Use `threads_hide` with `workerID` when a worker is no longer needed in the sidebar. This preserves its report and conversation. Hiding does not interrupt work or free an admission slot.
|
|
29
38
|
- Do not repeatedly query status while waiting. Workers report to the coordinator through OpenCode's durable inbox.
|
|
30
39
|
- After a service restart, inspect existing worker IDs before deciding whether to resume them. Do not launch replacement tasks solely because a session is idle.
|
|
31
40
|
|
|
32
41
|
## Finish
|
|
33
42
|
|
|
34
|
-
The worker
|
|
43
|
+
The worker reviews its subagents' results and resolves outstanding work before calling `threads_report` with a combined verdict, concise summary, and concrete evidence, then stops. Native subagents return results to the worker. Valid verdicts are `PASS`, `PASS WITH NOTES`, `FAIL`, and `INCONCLUSIVE`. Report partial work and blockers honestly.
|
|
35
44
|
|
|
36
45
|
A worker report is a claim to review. Run the protocol's independent verification before marking the work unit complete. An idle session or a stopped loading indicator does not establish task success.
|
|
37
46
|
|
|
38
47
|
## Visibility
|
|
39
48
|
|
|
40
|
-
The TUI plugin opens managed workers as ordinary tabs without changing focus.
|
|
49
|
+
The TUI plugin opens managed workers as ordinary tabs without changing focus. Workers with `PASS` or `PASS WITH NOTES` reports hide automatically once inactive. Failed, inconclusive, and unreported workers stay visible until the coordinator hides them. Running, selected, and attention-needed tabs stay open. Use `/threads` to restore hidden tabs for inspection. Sending a valid follow-up also restores that worker. Reports are delivered silently to the coordinator and remain available through `threads_list`.
|
package/src/rpc.ts
CHANGED
|
@@ -17,6 +17,7 @@ export const WorkerView = z.object({
|
|
|
17
17
|
directory: z.string(),
|
|
18
18
|
outcome: z.enum(["succeeded", "failed", "interrupted"]).nullable(),
|
|
19
19
|
report: Report.nullable(),
|
|
20
|
+
hidden: z.boolean(),
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
export const ThreadsRpc = Rpc.define({
|
|
@@ -29,6 +30,11 @@ export const ThreadsRpc = Rpc.define({
|
|
|
29
30
|
output: z.object({ workers: z.array(WorkerView) }),
|
|
30
31
|
errors: {},
|
|
31
32
|
},
|
|
33
|
+
restore: {
|
|
34
|
+
input: z.object({ coordinatorIDs: z.array(z.string()).max(100) }).strict(),
|
|
35
|
+
output: z.object({ workers: z.array(WorkerView) }),
|
|
36
|
+
errors: {},
|
|
37
|
+
},
|
|
32
38
|
},
|
|
33
39
|
events: {},
|
|
34
40
|
});
|
package/src/threads.ts
CHANGED
|
@@ -42,7 +42,7 @@ export const Send = z
|
|
|
42
42
|
text: z.string().min(1),
|
|
43
43
|
})
|
|
44
44
|
.strict();
|
|
45
|
-
export const
|
|
45
|
+
export const WorkerTarget = z.object({ workerID: sessionID }).strict();
|
|
46
46
|
|
|
47
47
|
const digest = (parts: string[]) =>
|
|
48
48
|
createHash("sha256").update(JSON.stringify(parts)).digest("hex");
|
|
@@ -94,12 +94,16 @@ export function threads(
|
|
|
94
94
|
`workers/${link.coordinatorID}/${link.workerID}`;
|
|
95
95
|
const reportKey = (link: z.infer<typeof Link>) =>
|
|
96
96
|
`reports/${link.workerID}/${link.reportMessageID}`;
|
|
97
|
+
const visibilityKey = (link: z.infer<typeof Link>) =>
|
|
98
|
+
`visibility/${link.workerID}/${link.reportMessageID}`;
|
|
97
99
|
|
|
98
100
|
async function view(
|
|
99
101
|
session: NativeSession,
|
|
100
102
|
): Promise<z.infer<typeof WorkerView>> {
|
|
101
103
|
const link = workerLink(session);
|
|
102
104
|
const stored = await ctx.storage.get(reportKey(link));
|
|
105
|
+
const report = stored === undefined ? null : Report.parse(stored);
|
|
106
|
+
const visibility = await ctx.storage.get(visibilityKey(link));
|
|
103
107
|
return {
|
|
104
108
|
workerID: link.workerID,
|
|
105
109
|
coordinatorID: link.coordinatorID,
|
|
@@ -107,7 +111,11 @@ export function threads(
|
|
|
107
111
|
title: session.title ?? link.key,
|
|
108
112
|
directory: session.location.directory,
|
|
109
113
|
outcome: session.outcome ?? null,
|
|
110
|
-
report
|
|
114
|
+
report,
|
|
115
|
+
hidden:
|
|
116
|
+
visibility === undefined
|
|
117
|
+
? report?.verdict === "PASS" || report?.verdict === "PASS WITH NOTES"
|
|
118
|
+
: z.boolean().parse(visibility),
|
|
111
119
|
};
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -132,6 +140,7 @@ export function threads(
|
|
|
132
140
|
if (!missing.success || missing.data.sessionID !== link.workerID)
|
|
133
141
|
throw error;
|
|
134
142
|
await ctx.storage.remove(reportKey(link));
|
|
143
|
+
await ctx.storage.remove(visibilityKey(link));
|
|
135
144
|
await ctx.storage.remove(entry.key);
|
|
136
145
|
continue;
|
|
137
146
|
}
|
|
@@ -152,6 +161,22 @@ export function threads(
|
|
|
152
161
|
|
|
153
162
|
return {
|
|
154
163
|
list,
|
|
164
|
+
async hide(actor: string, input: z.infer<typeof WorkerTarget>) {
|
|
165
|
+
const { session, link } = await owned(actor, input.workerID);
|
|
166
|
+
await ctx.storage.set(visibilityKey(link), true);
|
|
167
|
+
return view(session);
|
|
168
|
+
},
|
|
169
|
+
async restore(coordinatorID: string) {
|
|
170
|
+
const workers = await list(coordinatorID);
|
|
171
|
+
return Promise.all(
|
|
172
|
+
workers.map(async (worker) => {
|
|
173
|
+
if (!worker.hidden) return worker;
|
|
174
|
+
const { session, link } = await owned(coordinatorID, worker.workerID);
|
|
175
|
+
await ctx.storage.set(visibilityKey(link), false);
|
|
176
|
+
return view(session);
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
},
|
|
155
180
|
async spawn(
|
|
156
181
|
actor: string,
|
|
157
182
|
input: z.infer<typeof Spawn>,
|
|
@@ -224,13 +249,13 @@ export function threads(
|
|
|
224
249
|
sessionID: link.workerID,
|
|
225
250
|
id: link.initialMessageID,
|
|
226
251
|
delivery: "queue",
|
|
227
|
-
text: `${input.task}\n\nYou are a managed worker assigned to ${input.directory}. Work only within the assigned scope. Do not call threads_spawn.
|
|
252
|
+
text: `${input.task}\n\nYou are a managed worker assigned to ${input.directory}. Work only within the assigned scope. You may use native subagent for bounded tasks or reviews when useful, within the brief's delegation limits and inherited permissions. Delegation is optional. Pass relevant context, scope, and constraints to each subagent. Do not call threads_spawn. Review your subagents' results and resolve any outstanding work before reporting. Only you call threads_report with the combined verdict, summary, and evidence; subagents return results to you. Runtime completion alone does not establish task success.`,
|
|
228
253
|
});
|
|
229
254
|
return view(await ctx.session.get({ sessionID: workerID }));
|
|
230
255
|
});
|
|
231
256
|
},
|
|
232
257
|
async send(actor: string, input: z.infer<typeof Send>) {
|
|
233
|
-
await owned(actor, input.workerID);
|
|
258
|
+
const { link } = await owned(actor, input.workerID);
|
|
234
259
|
const id = SessionMessage.ID.make(
|
|
235
260
|
`msg_${digest([input.workerID, "send", input.key]).slice(0, 32)}`,
|
|
236
261
|
);
|
|
@@ -243,9 +268,14 @@ export function threads(
|
|
|
243
268
|
});
|
|
244
269
|
if (admitted.payload.text !== input.text)
|
|
245
270
|
throw new Error("This send key already belongs to different text");
|
|
271
|
+
if ((await ctx.storage.get(reportKey(link))) !== undefined) {
|
|
272
|
+
await ctx.storage.set(visibilityKey(link), false);
|
|
273
|
+
} else {
|
|
274
|
+
await ctx.storage.remove(visibilityKey(link));
|
|
275
|
+
}
|
|
246
276
|
return { workerID: input.workerID, messageID: admitted.id };
|
|
247
277
|
},
|
|
248
|
-
async interrupt(actor: string, input: z.infer<typeof
|
|
278
|
+
async interrupt(actor: string, input: z.infer<typeof WorkerTarget>) {
|
|
249
279
|
await owned(actor, input.workerID);
|
|
250
280
|
await ctx.session.interrupt({
|
|
251
281
|
sessionID: input.workerID,
|
|
@@ -261,7 +291,6 @@ export function threads(
|
|
|
261
291
|
id: link.reportMessageID,
|
|
262
292
|
text: `Managed worker ${link.workerID} (${link.key}) report:\n${JSON.stringify(input)}`,
|
|
263
293
|
metadata: { opThreadsReport: input, workerID: link.workerID },
|
|
264
|
-
description: `Worker report: ${link.key}`,
|
|
265
294
|
delivery: "queue",
|
|
266
295
|
resume: true,
|
|
267
296
|
});
|
package/tui.ts
CHANGED
|
@@ -60,12 +60,30 @@ export default Plugin.define({
|
|
|
60
60
|
]),
|
|
61
61
|
].slice(0, 100);
|
|
62
62
|
if (!coordinatorIDs.length) return;
|
|
63
|
-
const { workers } = await rpc.snapshot(
|
|
63
|
+
const { workers } = await (reopen ? rpc.restore : rpc.snapshot)(
|
|
64
64
|
{ coordinatorIDs },
|
|
65
65
|
{ location: ctx.location ?? ctx.data.location.default() },
|
|
66
66
|
);
|
|
67
67
|
for (const worker of workers) {
|
|
68
68
|
if (stopped) return;
|
|
69
|
+
const tab = ctx.ui.tabs.list().find((tab) => tab.sessionID === worker.workerID);
|
|
70
|
+
if (
|
|
71
|
+
worker.hidden &&
|
|
72
|
+
!tab?.active &&
|
|
73
|
+
!tab?.busy &&
|
|
74
|
+
!tab?.attention &&
|
|
75
|
+
ctx.data.session.status(worker.workerID) !== "running"
|
|
76
|
+
) {
|
|
77
|
+
if (tab && !ctx.ui.tabs.close(worker.workerID)) continue;
|
|
78
|
+
if (seen.workerIDs.includes(worker.workerID)) {
|
|
79
|
+
updateSeen((draft) => {
|
|
80
|
+
draft.workerIDs = draft.workerIDs.filter(
|
|
81
|
+
(id) => id !== worker.workerID,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
69
87
|
if (!reopen && seen.workerIDs.includes(worker.workerID)) continue;
|
|
70
88
|
await ctx.data.session.sync(worker.workerID);
|
|
71
89
|
if (
|