@op1/threads 0.1.0 → 0.1.2
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 +6 -0
- package/package.json +3 -2
- package/tui.ts +34 -0
package/README.md
CHANGED
|
@@ -75,6 +75,10 @@ Plugin option `maxWorkers` defaults to 4 and accepts integers from 1 through 32.
|
|
|
75
75
|
|
|
76
76
|
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
77
|
|
|
78
|
+
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
|
+
|
|
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. Finished runs remain visible below active work and rise again when resumed or selected. 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. Native tabs do not support divider rows.
|
|
81
|
+
|
|
78
82
|
The read-only RPC definition is `ThreadsRpc` in `src/rpc.ts`, with ID `threads` and method `snapshot`:
|
|
79
83
|
|
|
80
84
|
```ts
|
|
@@ -88,6 +92,8 @@ The input accepts at most 100 coordinator IDs. Raw HTTP RPC requests wrap the in
|
|
|
88
92
|
|
|
89
93
|
Run `bun run typecheck`, `bun test`, and `bun run verify:live`. The live check requires OpenCode 2.0.3, Python, and `uv`. It starts a separate local server, a deterministic model endpoint, and a terminal process with isolated configuration and data. It verifies actual tool calls, durable messages, permission restrictions, worker limits, deleted-worker cleanup, restart behavior, and native tab visibility, busy state, and focus.
|
|
90
94
|
|
|
95
|
+
Run `bun run verify:tabs` to verify project grouping across real git worktrees, activity-based ordering, permission prompts, completed and resumed workers, focus preservation, and TUI reopening.
|
|
96
|
+
|
|
91
97
|
Pass an extracted package directory to test the release artifact: `bun run verify:live /absolute/path/to/package`.
|
|
92
98
|
|
|
93
99
|
The native session and plugin index are separate writes. A crash after session creation but before indexing requires an explicit identical spawn retry. A crash after native report admission but before saving the report view requires an explicit report retry; the native coordinator notification remains canonical and is not duplicated. There is no custom outbox or startup task replay.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@op1/threads",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Visible top-level worker sessions for OpenCode V2, with native tabs and durable reports.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"test": "bun test",
|
|
27
|
-
"verify:live": "uv run --with pyte python scripts/verify-live.py"
|
|
27
|
+
"verify:live": "uv run --with pyte python scripts/verify-live.py",
|
|
28
|
+
"verify:tabs": "uv run --with pyte python scripts/verify-tab-groups.py"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@opencode/plugin": "2.0.3",
|
package/tui.ts
CHANGED
|
@@ -11,6 +11,38 @@ export default Plugin.define({
|
|
|
11
11
|
let running = false;
|
|
12
12
|
let reopenPending = false;
|
|
13
13
|
let lastError: string | undefined;
|
|
14
|
+
function groupTabs() {
|
|
15
|
+
const tabs = ctx.ui.tabs.list().map((tab) => {
|
|
16
|
+
const projectID = ctx.data.session.get(tab.sessionID)?.projectID;
|
|
17
|
+
return {
|
|
18
|
+
sessionID: tab.sessionID,
|
|
19
|
+
priority: tab.busy || tab.active || tab.attention,
|
|
20
|
+
projectID:
|
|
21
|
+
typeof projectID === "string" && projectID.length > 0
|
|
22
|
+
? projectID
|
|
23
|
+
: undefined,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
const groups = new Map<string, typeof tabs>();
|
|
27
|
+
for (const tab of tabs) {
|
|
28
|
+
const key =
|
|
29
|
+
tab.projectID === undefined
|
|
30
|
+
? `session:${tab.sessionID}`
|
|
31
|
+
: `project:${tab.projectID}`;
|
|
32
|
+
const group = groups.get(key);
|
|
33
|
+
if (group) group.push(tab);
|
|
34
|
+
else groups.set(key, [tab]);
|
|
35
|
+
}
|
|
36
|
+
const ordered = [...groups.values()].flatMap((group) =>
|
|
37
|
+
group
|
|
38
|
+
.sort((left, right) => Number(right.priority) - Number(left.priority))
|
|
39
|
+
.map((tab) => tab.sessionID),
|
|
40
|
+
);
|
|
41
|
+
for (const [index, sessionID] of ordered.entries()) {
|
|
42
|
+
if (ctx.ui.tabs.list()[index]?.sessionID === sessionID) continue;
|
|
43
|
+
if (!ctx.ui.tabs.move(sessionID, index)) break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
14
46
|
async function reconcile(reopen = false) {
|
|
15
47
|
if (stopped || !ctx.ui.tabs.enabled()) return;
|
|
16
48
|
if (running) {
|
|
@@ -19,6 +51,7 @@ export default Plugin.define({
|
|
|
19
51
|
}
|
|
20
52
|
running = true;
|
|
21
53
|
try {
|
|
54
|
+
groupTabs();
|
|
22
55
|
const route = ctx.ui.router.current();
|
|
23
56
|
const coordinatorIDs = [
|
|
24
57
|
...new Set([
|
|
@@ -45,6 +78,7 @@ export default Plugin.define({
|
|
|
45
78
|
});
|
|
46
79
|
}
|
|
47
80
|
}
|
|
81
|
+
if (!stopped && ctx.ui.tabs.enabled()) groupTabs();
|
|
48
82
|
} finally {
|
|
49
83
|
running = false;
|
|
50
84
|
if (reopenPending) {
|