v2: 将 oc-session-all 从 v1 格式转为 v2 Plugin.define 格式

- 使用 import { Plugin } from "@opencode/plugin/tui"
- 使用 Plugin.define({ id, setup(context) }) 格式
- api.* 调用全部替换为 context.*: api.event.on → context.data.on, api.ui.toast → context.ui.toast.show, api.ui.dialog.* → context.ui.dialog.*, api.command.register + api.keymap.registerLayer 合并为 context.keymap.layer({commands, bindings})
- package.json exports 添加 ./tui 入口点,engines.opencode 升级为 >=2.0.0
- 已通过 minimal-v2-test 验证 v2 格式可正确加载
This commit is contained in:
ypshidifu
2026-09-22 15:19:14 +08:00
parent 76d641b17f
commit a5a8c5e011
2 changed files with 140 additions and 151 deletions
+6 -12
View File
@@ -1,11 +1,12 @@
{ {
"name": "oc-session-all", "name": "oc-session-all",
"version": "2.0.0", "version": "2.1.0",
"description": "OpenCode TUI plugin: browse and switch to any session across all projects", "description": "OpenCode v2 TUI plugin: browse and switch to any session across all projects",
"main": "tui-plugins/oc-session-all.ts", "main": "tui.js",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./tui-plugins/oc-session-all.ts" ".": "./tui.js",
"./tui": "./tui.js"
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@@ -20,13 +21,6 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"opencode": ">=1.18.0" "opencode": ">=2.0.0"
},
"dependencies": {
"@opencode-ai/plugin": "latest"
},
"devDependencies": {
"@opentui/solid": "latest",
"typescript": "^5.0.0"
} }
} }
+44 -49
View File
@@ -1,15 +1,15 @@
/** /**
* OpenCode TUI Plugin: Show All Sessions * OpenCode v2 TUI Plugin: Show All Sessions
* *
* Registers /sessions-all command to show all sessions * Registers /sessions-all command to show all sessions
* across all projects via a searchable picker dialog. * across all projects via a searchable picker dialog.
* Selecting a session navigates directly to it. * Selecting a session navigates directly to it.
* *
* TUI format: export default { id, tui }
*
* Also intercepts MCP connection error toasts to keep them quiet. * Also intercepts MCP connection error toasts to keep them quiet.
*/ */
import { Plugin } from "@opencode/plugin/tui"
// ── Helpers ──────────────────────────────────────────────────────────── // ── Helpers ────────────────────────────────────────────────────────────
function formatDateTime(ms) { function formatDateTime(ms) {
@@ -42,9 +42,13 @@ function projectDisplayName(session, projectMap) {
// ── Plugin ───────────────────────────────────────────────────────────── // ── Plugin ─────────────────────────────────────────────────────────────
const tui = async (api) => { export default Plugin.define({
id: "oc-session-all",
setup(context) {
console.log("[oc-session-all] v2 Plugin loaded!")
// ─── MCP Error Toast Suppression ───────────────────────────────────── // ─── MCP Error Toast Suppression ─────────────────────────────────────
api.event.on("tui.toast.show", (event) => { context.data.on("tui.toast.show", (event) => {
const props = event.properties; const props = event.properties;
if ( if (
props.variant === "error" && props.variant === "error" &&
@@ -61,27 +65,49 @@ const tui = async (api) => {
} }
}); });
// ─── Session Picker Dialog ──────────────────────────────────────────── // ─── Register Command ─────────────────────────────────────────────────
const openPicker = async () => { context.keymap.layer({
if (api.mode.current() !== "base") return; commands: [
{
title: "All Sessions",
value: "sessions.all",
description: "Browse and switch to any session across all projects",
category: "Session Picker",
slash: { name: "sessions-all", aliases: ["resume-all", "continue-all"] },
onSelect: () => {
void openPicker();
},
},
],
bindings: [
{
key: "ctrl+o",
desc: "All sessions picker",
preventDefault: true,
cmd: () => void openPicker(),
},
],
});
// ─── Session Picker Dialog ────────────────────────────────────────────
async function openPicker() {
let sessions = []; let sessions = [];
let projects = []; let projects = [];
try { try {
const [sessionResult, projectResult] = await Promise.all([ const [sessionResult, projectResult] = await Promise.all([
api.client.experimental.session.list({ context.client.experimental.session.list({
limit: 1000, limit: 1000,
roots: true, roots: true,
directory: "", directory: "",
}), }),
api.client.project.list({}), context.client.project.list({}),
]); ]);
sessions = sessionResult?.data ?? sessionResult ?? []; sessions = sessionResult?.data ?? sessionResult ?? [];
projects = projectResult?.data ?? projectResult ?? []; projects = projectResult?.data ?? projectResult ?? [];
} catch (err) { } catch (err) {
api.ui.toast({ context.ui.toast.show({
message: "Failed to load sessions", message: "Failed to load sessions",
variant: "error", variant: "error",
}); });
@@ -97,7 +123,7 @@ const tui = async (api) => {
); );
if (visible.length === 0) { if (visible.length === 0) {
api.ui.toast({ context.ui.toast.show({
message: "No sessions found", message: "No sessions found",
variant: "warning", variant: "warning",
}); });
@@ -136,51 +162,20 @@ const tui = async (api) => {
}), }),
); );
api.ui.dialog.setSize("xlarge"); context.ui.dialog.setSize("xlarge");
api.ui.dialog.replace( context.ui.dialog.replace(
() => () =>
api.ui.DialogSelect({ context.ui.DialogSelect({
title: "All Sessions", title: "All Sessions",
placeholder: "Search sessions across all projects\u2026", placeholder: "Search sessions across all projects\u2026",
options, options,
onSelect: (option) => { onSelect: (option) => {
api.ui.dialog.clear(); context.ui.dialog.clear();
api.route.navigate("session", { sessionID: option.value }); context.route.navigate("session", { sessionID: option.value });
}, },
}), }),
() => {}, () => {},
); );
};
// ─── Register Command ─────────────────────────────────────────────────
if (api.command) {
api.command.register(() => [
{
title: "All Sessions",
value: "sessions.all",
description: "Browse and switch to any session across all projects",
category: "Session Picker",
slash: { name: "sessions-all", aliases: ["resume-all", "continue-all"] },
onSelect: () => {
void openPicker();
},
},
]);
} }
// ─── Keybinding ───────────────────────────────────────────────────────
api.keymap.registerLayer({
bindings: [
{
key: "ctrl+o",
desc: "All sessions picker",
preventDefault: true,
cmd: () => void openPicker(),
}, },
], })
});
};
// ── Export ──────────────────────────────────────────────────────────────
export default { id: "oc-session-all", tui };