添加调试版本插件,修改gitignore允许提交js文件

This commit is contained in:
ypshidifu
2026-09-22 11:02:38 +08:00
parent d201e3bb97
commit 47c668f446
4 changed files with 247 additions and 0 deletions
+2
View File
@@ -1,3 +1,5 @@
node_modules/ node_modules/
*.js *.js
!tui-plugins/oc-session-all-debug.js
!tui-plugins/oc-session-all.js
src/ src/
+58
View File
@@ -0,0 +1,58 @@
/**
* OpenCode TUI Plugin: Show All Sessions (DEBUG VERSION)
*
* Minimal version to test if plugin loads without crashes.
* Only includes basic MCP toast suppression and keymap.
*/
// ── Plugin ─────────────────────────────────────────────────────────────
const tui = async (api) => {
// Log that we're alive
console.log("[oc-session-all] Plugin loaded successfully!");
// ─── MCP Error Toast Suppression ─────────────────────────────────────
api.event.on("tui.toast.show", (event) => {
const props = event.properties;
if (
props.variant === "error" &&
typeof props.message === "string" &&
(props.message.toLowerCase().includes("mcp") ||
props.message.toLowerCase().includes("mcp server") ||
props.message.toLowerCase().includes("mcp error") ||
props.message.toLowerCase().includes("failed to connect") ||
props.message.toLowerCase().includes("connection refused"))
) {
props.variant = "info";
props.message = "MCP server offline (auto-reconnect when available)";
props.duration = 2000;
}
});
// ─── Minimal keymap (just to test registration works) ─────────────────
try {
api.keymap.registerLayer({
bindings: [
{
key: "ctrl+o",
desc: "All sessions picker (debug)",
preventDefault: true,
cmd: () => {
console.log("[oc-session-all] Ctrl+O pressed!");
api.ui.toast({
message: "Plugin loaded! Ctrl+O works!",
variant: "info",
});
},
},
],
});
console.log("[oc-session-all] Keymap registered successfully!");
} catch (err) {
console.error("[oc-session-all] Keymap registration failed:", err);
}
};
// ── Export ──────────────────────────────────────────────────────────────
export default { id: "oc-session-all-debug", tui };
+186
View File
@@ -0,0 +1,186 @@
/**
* OpenCode TUI Plugin: Show All Sessions
*
* Registers /sessions-all command to show all sessions
* across all projects via a searchable picker dialog.
* Selecting a session navigates directly to it.
*
* TUI format: export default { id, tui }
*
* Also intercepts MCP connection error toasts to keep them quiet.
*/
// ── Helpers ────────────────────────────────────────────────────────────
function formatDateTime(ms) {
const d = new Date(ms);
const pad = (v) => String(v).padStart(2, "0");
const month = pad(d.getMonth() + 1);
const day = pad(d.getDate());
const hour = pad(d.getHours());
const min = pad(d.getMinutes());
return `${d.getFullYear()}/${month}/${day} ${hour}:${min}`;
}
function projectDisplayName(session, projectMap) {
const project = session.projectID
? projectMap.get(session.projectID)
: undefined;
if (project?.name?.trim()) return project.name.trim();
const worktree = project?.worktree
?.replace(/\/+$/, "")
.split(/[\\/]/)
.pop();
if (worktree) return worktree;
const dir = session.directory
?.replace(/\/+$/, "")
.split(/[\\/]/)
.pop();
if (dir) return dir;
return session.projectID ? session.projectID.slice(0, 8) : "unknown";
}
// ── Plugin ─────────────────────────────────────────────────────────────
const tui = async (api) => {
// ─── MCP Error Toast Suppression ─────────────────────────────────────
api.event.on("tui.toast.show", (event) => {
const props = event.properties;
if (
props.variant === "error" &&
typeof props.message === "string" &&
(props.message.toLowerCase().includes("mcp") ||
props.message.toLowerCase().includes("mcp server") ||
props.message.toLowerCase().includes("mcp error") ||
props.message.toLowerCase().includes("failed to connect") ||
props.message.toLowerCase().includes("connection refused"))
) {
props.variant = "info";
props.message = "MCP server offline (auto-reconnect when available)";
props.duration = 2000;
}
});
// ─── Session Picker Dialog ────────────────────────────────────────────
const openPicker = async () => {
if (api.mode.current() !== "base") return;
let sessions = [];
let projects = [];
try {
const [sessionResult, projectResult] = await Promise.all([
api.client.experimental.session.list({
limit: 1000,
roots: true,
directory: "",
}),
api.client.project.list({}),
]);
sessions = sessionResult?.data ?? sessionResult ?? [];
projects = projectResult?.data ?? projectResult ?? [];
} catch (err) {
api.ui.toast({
message: "Failed to load sessions",
variant: "error",
});
return;
}
if (!Array.isArray(sessions) || !Array.isArray(projects)) return;
const projectMap = new Map(projects.map((p) => [p.id, p]));
const visible = sessions.filter(
(s) => !s.time?.archived && !s.parentID,
);
if (visible.length === 0) {
api.ui.toast({
message: "No sessions found",
variant: "warning",
});
return;
}
const grouped = new Map();
for (const session of visible) {
const name = projectDisplayName(session, projectMap);
const list = grouped.get(name) ?? [];
list.push(session);
grouped.set(name, list);
}
for (const list of grouped.values()) {
list.sort(
(a, b) => (b.time?.updated ?? 0) - (a.time?.updated ?? 0),
);
}
const orderedProjects = [...grouped.keys()].sort((a, b) =>
a.localeCompare(b),
);
const options = orderedProjects.flatMap((project) =>
grouped.get(project).map((session) => {
const updated = session.time?.updated
? formatDateTime(session.time.updated)
: "unknown";
const title = session.title || "Untitled";
return {
title: `${project} ${updated} \u2502 ${title}`,
value: session.id,
category: project,
};
}),
);
api.ui.dialog.setSize("xlarge");
api.ui.dialog.replace(
() =>
api.ui.DialogSelect({
title: "All Sessions",
placeholder: "Search sessions across all projects\u2026",
options,
onSelect: (option) => {
api.ui.dialog.clear();
api.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 };
+1
View File
@@ -1,6 +1,7 @@
{ {
"$schema": "https://opencode.ai/tui.json", "$schema": "https://opencode.ai/tui.json",
"plugin": [ "plugin": [
"./tui-plugins/oc-session-all-debug.js",
"./tui-plugins/oc-session-all.js" "./tui-plugins/oc-session-all.js"
] ]
} }