@log0u7/opencode-mesh 0.6.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/CHANGELOG.md +21 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +12 -0
- package/dist/rpc.js.map +1 -1
- package/dist/sidebar-state.d.ts +17 -0
- package/dist/sidebar-state.d.ts.map +1 -0
- package/dist/sidebar-state.js +26 -0
- package/dist/sidebar-state.js.map +1 -0
- package/dist/tui.js +152 -40
- package/dist/tui.tsx +113 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.1] - 2026-11-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- The TUI no longer crashes on hosts whose storage layer rejects scoped npm
|
|
13
|
+
plugin ids (`TypeError: Invalid storage segment`, anomalyco/opencode#62634).
|
|
14
|
+
The sidebar falls back to in-memory collapse state: functional, but not
|
|
15
|
+
persisted across TUI restarts until the host-side fix lands.
|
|
16
|
+
|
|
17
|
+
## [0.7.0] - 2026-11-06
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Themed mesh sidebar: bold header and muted peer lines using the active
|
|
22
|
+
theme's text colors.
|
|
23
|
+
- Sidebar collapse: the `/mesh` toggle keybind persists its state in durable
|
|
24
|
+
storage, so the collapsed sidebar survives TUI restarts.
|
|
25
|
+
- Event refresh: the sidebar refreshes immediately (debounced) on server
|
|
26
|
+
events, with the 15s polling as fallback.
|
|
27
|
+
- `/mesh` now opens a dialog with the full snapshot instead of a toast.
|
|
28
|
+
|
|
8
29
|
## [0.6.0] - 2026-11-06
|
|
9
30
|
|
|
10
31
|
### Added
|
package/dist/rpc.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export type SnapshotInput = {
|
|
|
25
25
|
export declare function snapshotFromMesh(data: SnapshotInput): MeshSnapshot;
|
|
26
26
|
/** Sidebar lines: the node, one line per paired peer, the active lock count. */
|
|
27
27
|
export declare function formatMeshLines(s: MeshSnapshot): string[];
|
|
28
|
+
/** Collapsible sidebar: when collapsed, one header line with the peer count (plus locks). */
|
|
29
|
+
export declare function formatMeshSidebar(s: MeshSnapshot, collapsed: boolean): string[];
|
|
28
30
|
export declare const MeshRpc: {
|
|
29
31
|
readonly id: "mesh";
|
|
30
32
|
readonly methods: {
|
package/dist/rpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,KAAK,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CASlE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAczD;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlB,CAAC"}
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,KAAK,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CASlE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAczD;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAU/E;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlB,CAAC"}
|
package/dist/rpc.js
CHANGED
|
@@ -26,6 +26,18 @@ export function formatMeshLines(s) {
|
|
|
26
26
|
}
|
|
27
27
|
return lines;
|
|
28
28
|
}
|
|
29
|
+
/** Collapsible sidebar: when collapsed, one header line with the peer count (plus locks). */
|
|
30
|
+
export function formatMeshSidebar(s, collapsed) {
|
|
31
|
+
if (!collapsed) {
|
|
32
|
+
return formatMeshLines(s);
|
|
33
|
+
}
|
|
34
|
+
const self = s.hostname.length > 0 ? s.hostname : s.node_id;
|
|
35
|
+
const lines = [`mesh: ${self} (${s.peers.length} peers)`];
|
|
36
|
+
if (s.active_locks > 0) {
|
|
37
|
+
lines.push(`${s.active_locks} active lock(s)`);
|
|
38
|
+
}
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
29
41
|
export const MeshRpc = Rpc.define({
|
|
30
42
|
id: "mesh",
|
|
31
43
|
methods: {
|
package/dist/rpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAsB3C,MAAM,UAAU,gBAAgB,CAAC,IAAmB;IAClD,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;QAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ;QAChC,KAAK,EAAE,IAAI,CAAC,KAAK;aACd,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;aACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC7D,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;KAChC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,CAAe;IAC7C,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5D,MAAM,KAAK,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,iBAAiB,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;IAChC,EAAE,EAAE,MAAM;IACV,OAAO,EAAE;QACP,QAAQ,EAAE;YACR,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,oBAAoB,EAAE,KAAK,EAAE;YACtE,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE;oBAC1D,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sBAAsB,EAAE;oBACjE,KAAK,EAAE;wBACL,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,UAAU,EAAE;gCACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gCAC3B,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;6BAC7B;4BACD,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;4BACjC,oBAAoB,EAAE,KAAK;yBAC5B;wBACD,WAAW,EAAE,cAAc;qBAC5B;oBACD,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yBAAyB,EAAE;iBACzE;gBACD,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,CAAC;gBAC1D,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,MAAM,EAAE,EAAE;CACX,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
2
|
+
type SidebarState = {
|
|
3
|
+
collapsed: boolean;
|
|
4
|
+
};
|
|
5
|
+
/** Collapse state for the mesh sidebar: durable via the host's TUI storage when
|
|
6
|
+
* available, in-memory fallback when it is not. The host composes storage keys
|
|
7
|
+
* as `plugin.<id>.<key>` and its segment() validation rejects scoped npm ids
|
|
8
|
+
* (`@scope/name.tui`), so `storage.store()` throws on first use for any plugin
|
|
9
|
+
* installed from a scoped package (anomalyco/opencode#62634). The upstream fix
|
|
10
|
+
* removes the throw; until then the fallback keeps the sidebar functional, at
|
|
11
|
+
* the cost of not persisting the collapse state across TUI restarts.
|
|
12
|
+
* Returns the same `[store, mutate]` shape as `storage.store` either way. */
|
|
13
|
+
export declare function createSidebarState(storage: Plugin.Context["storage"]): readonly [{
|
|
14
|
+
readonly collapsed: boolean;
|
|
15
|
+
}, (mutation: (draft: SidebarState) => void) => Promise<void>];
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=sidebar-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar-state.d.ts","sourceRoot":"","sources":["../src/sidebar-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAG9C,KAAK,YAAY,GAAG;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3C;;;;;;;6EAO6E;AAC7E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;wBAYzC,OAAO;cAClB,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,EAG/D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
2
|
+
import { createStore, produce } from "solid-js/store";
|
|
3
|
+
/** Collapse state for the mesh sidebar: durable via the host's TUI storage when
|
|
4
|
+
* available, in-memory fallback when it is not. The host composes storage keys
|
|
5
|
+
* as `plugin.<id>.<key>` and its segment() validation rejects scoped npm ids
|
|
6
|
+
* (`@scope/name.tui`), so `storage.store()` throws on first use for any plugin
|
|
7
|
+
* installed from a scoped package (anomalyco/opencode#62634). The upstream fix
|
|
8
|
+
* removes the throw; until then the fallback keeps the sidebar functional, at
|
|
9
|
+
* the cost of not persisting the collapse state across TUI restarts.
|
|
10
|
+
* Returns the same `[store, mutate]` shape as `storage.store` either way. */
|
|
11
|
+
export function createSidebarState(storage) {
|
|
12
|
+
try {
|
|
13
|
+
return storage.store("mesh-sidebar", { initial: { collapsed: false } });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
const [store, setStore] = createStore({ collapsed: false });
|
|
17
|
+
return [
|
|
18
|
+
store,
|
|
19
|
+
(mutation) => {
|
|
20
|
+
setStore(produce(mutation));
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=sidebar-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar-state.js","sourceRoot":"","sources":["../src/sidebar-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAItD;;;;;;;6EAO6E;AAC7E,MAAM,UAAU,kBAAkB,CAAC,OAAkC;IACnE,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,WAAW,CAAe,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1E,OAAO;YACL,KAAK;YACL,CAAC,QAAuC,EAAE,EAAE;gBAC1C,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC5B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YAC3B,CAAC;SAIF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/dist/tui.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
2
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
3
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
4
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
5
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
1
6
|
import { insert as _$insert } from "@opentui/solid";
|
|
2
7
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
3
8
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
@@ -5,72 +10,179 @@ import { createComponent as _$createComponent } from "@opentui/solid";
|
|
|
5
10
|
|
|
6
11
|
import { Plugin } from "@opencode/plugin/tui";
|
|
7
12
|
import { For, Show, createSignal } from "solid-js";
|
|
8
|
-
import { MeshRpc,
|
|
13
|
+
import { MeshRpc, formatMeshSidebar } from "./rpc.js";
|
|
14
|
+
import { createSidebarState } from "./sidebar-state.js";
|
|
9
15
|
const REFRESH_MS = 15_000;
|
|
16
|
+
/** quota-style event refresh: debounced immediate refresh after activity. */
|
|
17
|
+
const EVENT_DEBOUNCE_MS = 150;
|
|
18
|
+
function themedSidebar(theme, lines) {
|
|
19
|
+
return _$createComponent(Show, {
|
|
20
|
+
get when() {
|
|
21
|
+
return lines();
|
|
22
|
+
},
|
|
23
|
+
keyed: true,
|
|
24
|
+
children: current => (() => {
|
|
25
|
+
var _el$ = _$createElement("box");
|
|
26
|
+
_$insert(_el$, _$createComponent(For, {
|
|
27
|
+
each: current,
|
|
28
|
+
children: (line, index) => (() => {
|
|
29
|
+
var _el$2 = _$createElement("text");
|
|
30
|
+
_$setProp(_el$2, "wrapMode", "none");
|
|
31
|
+
_$insert(_el$2, line);
|
|
32
|
+
_$effect(_$p => _$setProp(_el$2, "fg", index() === 0 ? theme.text.base : theme.text.muted, _$p));
|
|
33
|
+
return _el$2;
|
|
34
|
+
})()
|
|
35
|
+
}));
|
|
36
|
+
return _el$;
|
|
37
|
+
})()
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function dialogBody(theme, snapshot) {
|
|
41
|
+
const self = snapshot.hostname.length > 0 ? snapshot.hostname : snapshot.node_id;
|
|
42
|
+
const peers = snapshot.peers.map(p => p.hostname.length > 0 ? p.hostname : p.node_id);
|
|
43
|
+
return (() => {
|
|
44
|
+
var _el$3 = _$createElement("box"),
|
|
45
|
+
_el$4 = _$createElement("text"),
|
|
46
|
+
_el$5 = _$createElement("b"),
|
|
47
|
+
_el$7 = _$createElement("text"),
|
|
48
|
+
_el$8 = _$createTextNode(`Node: `),
|
|
49
|
+
_el$9 = _$createElement("text"),
|
|
50
|
+
_el$0 = _$createTextNode(`Paired peers: `),
|
|
51
|
+
_el$1 = _$createElement("text"),
|
|
52
|
+
_el$10 = _$createTextNode(`Active locks: `);
|
|
53
|
+
_$insertNode(_el$3, _el$4);
|
|
54
|
+
_$insertNode(_el$3, _el$7);
|
|
55
|
+
_$insertNode(_el$3, _el$9);
|
|
56
|
+
_$insertNode(_el$3, _el$1);
|
|
57
|
+
_$setProp(_el$3, "gap", 1);
|
|
58
|
+
_$setProp(_el$3, "paddingLeft", 2);
|
|
59
|
+
_$setProp(_el$3, "paddingRight", 2);
|
|
60
|
+
_$setProp(_el$3, "paddingBottom", 1);
|
|
61
|
+
_$insertNode(_el$4, _el$5);
|
|
62
|
+
_$insertNode(_el$5, _$createTextNode(`Mesh — coordination`));
|
|
63
|
+
_$insertNode(_el$7, _el$8);
|
|
64
|
+
_$insert(_el$7, self, null);
|
|
65
|
+
_$insertNode(_el$9, _el$0);
|
|
66
|
+
_$insert(_el$9, (() => {
|
|
67
|
+
var _c$ = _$memo(() => peers.length === 0);
|
|
68
|
+
return () => _c$() ? "none" : peers.join(", ");
|
|
69
|
+
})(), null);
|
|
70
|
+
_$insertNode(_el$1, _el$10);
|
|
71
|
+
_$insert(_el$1, () => snapshot.active_locks, null);
|
|
72
|
+
_$effect(_p$ => {
|
|
73
|
+
var _v$ = theme.text.base,
|
|
74
|
+
_v$2 = theme.text.muted,
|
|
75
|
+
_v$3 = theme.text.muted,
|
|
76
|
+
_v$4 = theme.text.muted;
|
|
77
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "fg", _v$, _p$.e));
|
|
78
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "fg", _v$2, _p$.t));
|
|
79
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$9, "fg", _v$3, _p$.a));
|
|
80
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$1, "fg", _v$4, _p$.o));
|
|
81
|
+
return _p$;
|
|
82
|
+
}, {
|
|
83
|
+
e: undefined,
|
|
84
|
+
t: undefined,
|
|
85
|
+
a: undefined,
|
|
86
|
+
o: undefined
|
|
87
|
+
});
|
|
88
|
+
return _el$3;
|
|
89
|
+
})();
|
|
90
|
+
}
|
|
10
91
|
export default Plugin.define({
|
|
11
92
|
id: "@log0u7/opencode-mesh.tui",
|
|
12
93
|
setup(context) {
|
|
94
|
+
const theme = context.theme;
|
|
13
95
|
context.ui.toast.show({
|
|
14
96
|
message: "opencode-mesh loaded",
|
|
15
97
|
variant: "info",
|
|
16
98
|
duration: 2000
|
|
17
99
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
name: "mesh"
|
|
26
|
-
},
|
|
27
|
-
palette: true,
|
|
28
|
-
run: async () => {
|
|
29
|
-
context.ui.toast.show({
|
|
30
|
-
title: "Mesh",
|
|
31
|
-
message: "Multi-session coordination available",
|
|
32
|
-
variant: "info"
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}]
|
|
36
|
-
}));
|
|
100
|
+
|
|
101
|
+
// Durable collapse state when the host allows it (scoped plugin ids hit a
|
|
102
|
+
// host-side segment validation error — anomalyco/opencode#62634); in-memory
|
|
103
|
+
// fallback otherwise: sidebar works, collapse not persisted across restarts.
|
|
104
|
+
const [store, mutate] = createSidebarState(context.storage);
|
|
105
|
+
const [snapshot, setSnapshot] = createSignal(null);
|
|
106
|
+
const [lines, setLines] = createSignal(null);
|
|
37
107
|
|
|
38
108
|
// Live sidebar: poll the server-side snapshot over RPC. On a v1 host (no
|
|
39
109
|
// RPC) or while the server is unreachable, the slot stays hidden.
|
|
40
|
-
const [lines, setLines] = createSignal(null);
|
|
41
110
|
const mesh = context.client.rpc(MeshRpc);
|
|
42
111
|
const refresh = async () => {
|
|
43
112
|
try {
|
|
44
113
|
const snap = await mesh.snapshot({});
|
|
45
|
-
|
|
114
|
+
setSnapshot(snap);
|
|
115
|
+
setLines(formatMeshSidebar(snap, store.collapsed));
|
|
46
116
|
} catch {
|
|
47
117
|
setLines(null);
|
|
48
118
|
}
|
|
49
119
|
};
|
|
50
120
|
void refresh();
|
|
51
121
|
const timer = setInterval(() => void refresh(), REFRESH_MS);
|
|
122
|
+
|
|
123
|
+
// quota-style event refresh: debounced immediate refresh after activity.
|
|
124
|
+
let debounce;
|
|
125
|
+
const schedule = () => {
|
|
126
|
+
if (debounce) clearTimeout(debounce);
|
|
127
|
+
debounce = setTimeout(() => void refresh(), EVENT_DEBOUNCE_MS);
|
|
128
|
+
};
|
|
129
|
+
// context.client.event is an AsyncIterable of V2Event, not an EventEmitter.
|
|
130
|
+
const events = context.client.event.subscribe();
|
|
131
|
+
void (async () => {
|
|
132
|
+
try {
|
|
133
|
+
for await (const _event of events) {
|
|
134
|
+
schedule();
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// stream ended or errored: polling fallback stays active.
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
52
140
|
const unregister = context.ui.slot({
|
|
53
141
|
append: "sidebar.content",
|
|
54
|
-
render: () =>
|
|
55
|
-
get when() {
|
|
56
|
-
return lines();
|
|
57
|
-
},
|
|
58
|
-
keyed: true,
|
|
59
|
-
children: current => (() => {
|
|
60
|
-
var _el$ = _$createElement("box");
|
|
61
|
-
_$insert(_el$, _$createComponent(For, {
|
|
62
|
-
each: current,
|
|
63
|
-
children: line => (() => {
|
|
64
|
-
var _el$2 = _$createElement("text");
|
|
65
|
-
_$insert(_el$2, line);
|
|
66
|
-
return _el$2;
|
|
67
|
-
})()
|
|
68
|
-
}));
|
|
69
|
-
return _el$;
|
|
70
|
-
})()
|
|
71
|
-
})
|
|
142
|
+
render: () => themedSidebar(theme, lines)
|
|
72
143
|
});
|
|
144
|
+
|
|
145
|
+
// Keymap layers are disposed with the plugin; no manual disposer here.
|
|
146
|
+
context.keymap.layer(() => ({
|
|
147
|
+
mode: "global",
|
|
148
|
+
commands: [{
|
|
149
|
+
id: "mesh.status",
|
|
150
|
+
title: "Mesh status",
|
|
151
|
+
group: "Mesh",
|
|
152
|
+
slash: {
|
|
153
|
+
name: "mesh"
|
|
154
|
+
},
|
|
155
|
+
palette: true,
|
|
156
|
+
run: async () => {
|
|
157
|
+
await refresh();
|
|
158
|
+
const current = snapshot();
|
|
159
|
+
if (!current) {
|
|
160
|
+
context.ui.toast.show({
|
|
161
|
+
title: "Mesh",
|
|
162
|
+
message: "Server unreachable",
|
|
163
|
+
variant: "info"
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
context.ui.dialog.show(() => dialogBody(theme, current));
|
|
168
|
+
context.ui.dialog.set({
|
|
169
|
+
size: "medium"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}, {
|
|
173
|
+
id: "mesh.toggleSidebar",
|
|
174
|
+
title: "Toggle mesh sidebar",
|
|
175
|
+
group: "Mesh",
|
|
176
|
+
run: async () => {
|
|
177
|
+
await mutate(draft => {
|
|
178
|
+
draft.collapsed = !draft.collapsed;
|
|
179
|
+
});
|
|
180
|
+
void refresh();
|
|
181
|
+
}
|
|
182
|
+
}]
|
|
183
|
+
}));
|
|
73
184
|
return () => {
|
|
185
|
+
clearTimeout(debounce);
|
|
74
186
|
clearInterval(timer);
|
|
75
187
|
unregister();
|
|
76
188
|
};
|
package/dist/tui.tsx
CHANGED
|
@@ -1,44 +1,75 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
|
|
3
3
|
import { Plugin } from "@opencode/plugin/tui";
|
|
4
|
+
import type { JSX } from "@opentui/solid";
|
|
4
5
|
import { For, Show, createSignal } from "solid-js";
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
type Context = Plugin.Context;
|
|
8
|
+
type Theme = Context["theme"];
|
|
9
|
+
|
|
10
|
+
import { MeshRpc, formatMeshSidebar, type MeshSnapshot } from "./rpc.js";
|
|
11
|
+
import { createSidebarState } from "./sidebar-state.js";
|
|
6
12
|
|
|
7
13
|
const REFRESH_MS = 15_000;
|
|
14
|
+
/** quota-style event refresh: debounced immediate refresh after activity. */
|
|
15
|
+
const EVENT_DEBOUNCE_MS = 150;
|
|
16
|
+
|
|
17
|
+
function themedSidebar(theme: Theme, lines: () => string[] | null): JSX.Element {
|
|
18
|
+
return (
|
|
19
|
+
<Show when={lines()} keyed>
|
|
20
|
+
{(current) => (
|
|
21
|
+
<box>
|
|
22
|
+
<For each={current}>
|
|
23
|
+
{(line, index) => (
|
|
24
|
+
<text fg={index() === 0 ? theme.text.base : theme.text.muted} wrapMode="none">
|
|
25
|
+
{line}
|
|
26
|
+
</text>
|
|
27
|
+
)}
|
|
28
|
+
</For>
|
|
29
|
+
</box>
|
|
30
|
+
)}
|
|
31
|
+
</Show>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function dialogBody(theme: Theme, snapshot: MeshSnapshot): JSX.Element {
|
|
36
|
+
const self = snapshot.hostname.length > 0 ? snapshot.hostname : snapshot.node_id;
|
|
37
|
+
const peers = snapshot.peers.map((p) => (p.hostname.length > 0 ? p.hostname : p.node_id));
|
|
38
|
+
return (
|
|
39
|
+
<box gap={1} paddingLeft={2} paddingRight={2} paddingBottom={1}>
|
|
40
|
+
<text fg={theme.text.base}>
|
|
41
|
+
<b>Mesh — coordination</b>
|
|
42
|
+
</text>
|
|
43
|
+
<text fg={theme.text.muted}>Node: {self}</text>
|
|
44
|
+
<text fg={theme.text.muted}>
|
|
45
|
+
Paired peers: {peers.length === 0 ? "none" : peers.join(", ")}
|
|
46
|
+
</text>
|
|
47
|
+
<text fg={theme.text.muted}>Active locks: {snapshot.active_locks}</text>
|
|
48
|
+
</box>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
8
51
|
|
|
9
52
|
export default Plugin.define({
|
|
10
53
|
id: "@log0u7/opencode-mesh.tui",
|
|
11
|
-
setup(context) {
|
|
54
|
+
setup(context: Context) {
|
|
55
|
+
const theme = context.theme;
|
|
12
56
|
context.ui.toast.show({ message: "opencode-mesh loaded", variant: "info", duration: 2000 });
|
|
13
57
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
group: "Mesh",
|
|
21
|
-
slash: { name: "mesh" },
|
|
22
|
-
palette: true,
|
|
23
|
-
run: async () => {
|
|
24
|
-
context.ui.toast.show({
|
|
25
|
-
title: "Mesh",
|
|
26
|
-
message: "Multi-session coordination available",
|
|
27
|
-
variant: "info",
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
}));
|
|
58
|
+
// Durable collapse state when the host allows it (scoped plugin ids hit a
|
|
59
|
+
// host-side segment validation error — anomalyco/opencode#62634); in-memory
|
|
60
|
+
// fallback otherwise: sidebar works, collapse not persisted across restarts.
|
|
61
|
+
const [store, mutate] = createSidebarState(context.storage);
|
|
62
|
+
const [snapshot, setSnapshot] = createSignal<MeshSnapshot | null>(null);
|
|
63
|
+
const [lines, setLines] = createSignal<string[] | null>(null);
|
|
33
64
|
|
|
34
65
|
// Live sidebar: poll the server-side snapshot over RPC. On a v1 host (no
|
|
35
66
|
// RPC) or while the server is unreachable, the slot stays hidden.
|
|
36
|
-
const [lines, setLines] = createSignal<string[] | null>(null);
|
|
37
67
|
const mesh = context.client.rpc(MeshRpc);
|
|
38
68
|
const refresh = async () => {
|
|
39
69
|
try {
|
|
40
70
|
const snap = (await mesh.snapshot({})) as MeshSnapshot;
|
|
41
|
-
|
|
71
|
+
setSnapshot(snap);
|
|
72
|
+
setLines(formatMeshSidebar(snap, store.collapsed));
|
|
42
73
|
} catch {
|
|
43
74
|
setLines(null);
|
|
44
75
|
}
|
|
@@ -46,20 +77,70 @@ export default Plugin.define({
|
|
|
46
77
|
void refresh();
|
|
47
78
|
const timer = setInterval(() => void refresh(), REFRESH_MS);
|
|
48
79
|
|
|
80
|
+
// quota-style event refresh: debounced immediate refresh after activity.
|
|
81
|
+
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
82
|
+
const schedule = () => {
|
|
83
|
+
if (debounce) clearTimeout(debounce);
|
|
84
|
+
debounce = setTimeout(() => void refresh(), EVENT_DEBOUNCE_MS);
|
|
85
|
+
};
|
|
86
|
+
// context.client.event is an AsyncIterable of V2Event, not an EventEmitter.
|
|
87
|
+
const events = context.client.event.subscribe();
|
|
88
|
+
void (async () => {
|
|
89
|
+
try {
|
|
90
|
+
for await (const _event of events) {
|
|
91
|
+
schedule();
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// stream ended or errored: polling fallback stays active.
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
|
|
49
98
|
const unregister = context.ui.slot({
|
|
50
99
|
append: "sidebar.content",
|
|
51
|
-
render: () => (
|
|
52
|
-
<Show when={lines()} keyed>
|
|
53
|
-
{(current) => (
|
|
54
|
-
<box>
|
|
55
|
-
<For each={current}>{(line) => <text>{line}</text>}</For>
|
|
56
|
-
</box>
|
|
57
|
-
)}
|
|
58
|
-
</Show>
|
|
59
|
-
),
|
|
100
|
+
render: () => themedSidebar(theme, lines),
|
|
60
101
|
});
|
|
61
102
|
|
|
103
|
+
// Keymap layers are disposed with the plugin; no manual disposer here.
|
|
104
|
+
context.keymap.layer(() => ({
|
|
105
|
+
mode: "global",
|
|
106
|
+
commands: [
|
|
107
|
+
{
|
|
108
|
+
id: "mesh.status",
|
|
109
|
+
title: "Mesh status",
|
|
110
|
+
group: "Mesh",
|
|
111
|
+
slash: { name: "mesh" },
|
|
112
|
+
palette: true,
|
|
113
|
+
run: async () => {
|
|
114
|
+
await refresh();
|
|
115
|
+
const current = snapshot();
|
|
116
|
+
if (!current) {
|
|
117
|
+
context.ui.toast.show({
|
|
118
|
+
title: "Mesh",
|
|
119
|
+
message: "Server unreachable",
|
|
120
|
+
variant: "info",
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
context.ui.dialog.show(() => dialogBody(theme, current));
|
|
125
|
+
context.ui.dialog.set({ size: "medium" });
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "mesh.toggleSidebar",
|
|
130
|
+
title: "Toggle mesh sidebar",
|
|
131
|
+
group: "Mesh",
|
|
132
|
+
run: async () => {
|
|
133
|
+
await mutate((draft) => {
|
|
134
|
+
draft.collapsed = !draft.collapsed;
|
|
135
|
+
});
|
|
136
|
+
void refresh();
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
}));
|
|
141
|
+
|
|
62
142
|
return () => {
|
|
143
|
+
clearTimeout(debounce);
|
|
63
144
|
clearInterval(timer);
|
|
64
145
|
unregister();
|
|
65
146
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@log0u7/opencode-mesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Multi-session coordination for OpenCode: session coordination on one machine and a peer pool across LAN or Tailscale, with pairing, mailbox, and worktree locks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|