测试隔离版: keymap.layer commands(无 bindings、无 data.on)
This commit is contained in:
+11
-147
@@ -1,146 +1,16 @@
|
|||||||
/**
|
|
||||||
* OpenCode v2 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.
|
|
||||||
*
|
|
||||||
* Also intercepts MCP connection error toasts to keep them quiet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Plugin } from "@opencode/plugin/tui"
|
import { Plugin } from "@opencode/plugin/tui"
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function formatDateTime(ms) {
|
|
||||||
const d = new Date(ms);
|
|
||||||
const pad = (v) => String(v).padStart(2, "0");
|
|
||||||
return `${d.getFullYear()}/${pad(d.getMonth()+1)}/${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export default Plugin.define({
|
export default Plugin.define({
|
||||||
id: "oc-session-all",
|
id: "oc-session-all",
|
||||||
setup(context) {
|
setup(context) {
|
||||||
console.log("[oc-session-all] v2 Plugin loaded!")
|
console.log("[oc-session-all] v2 Plugin loaded!")
|
||||||
|
context.ui.toast.show({
|
||||||
|
message: "[oc-session-all] v2 loaded! Testing keymap...",
|
||||||
|
variant: "success",
|
||||||
|
duration: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
// ─── MCP Error Toast Suppression ─────────────────────────────────────
|
// 只注册 command,不注册 binding
|
||||||
try {
|
|
||||||
context.data.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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[oc-session-all] data.on not available:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Session Picker Dialog ────────────────────────────────────────────
|
|
||||||
async function openPicker() {
|
|
||||||
let sessions = [];
|
|
||||||
let projects = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [sessionResult, projectResult] = await Promise.all([
|
|
||||||
context.client.experimental.session.list({
|
|
||||||
limit: 1000,
|
|
||||||
roots: true,
|
|
||||||
directory: "",
|
|
||||||
}),
|
|
||||||
context.client.project.list({}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
sessions = sessionResult?.data ?? sessionResult ?? [];
|
|
||||||
projects = projectResult?.data ?? projectResult ?? [];
|
|
||||||
} catch (err) {
|
|
||||||
context.ui.toast.show({
|
|
||||||
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) {
|
|
||||||
context.ui.toast.show({
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
context.ui.dialog.setSize("xlarge");
|
|
||||||
context.ui.dialog.replace(
|
|
||||||
() =>
|
|
||||||
context.ui.DialogSelect({
|
|
||||||
title: "All Sessions",
|
|
||||||
placeholder: "Search sessions across all projects\u2026",
|
|
||||||
options,
|
|
||||||
onSelect: (option) => {
|
|
||||||
context.ui.dialog.clear();
|
|
||||||
context.route.navigate("session", { sessionID: option.value });
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
() => {},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Register Command + Keybinding ────────────────────────────────────
|
|
||||||
context.keymap.layer({
|
context.keymap.layer({
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
@@ -148,18 +18,12 @@ export default Plugin.define({
|
|||||||
value: "sessions.all",
|
value: "sessions.all",
|
||||||
description: "Browse and switch to any session across all projects",
|
description: "Browse and switch to any session across all projects",
|
||||||
category: "Session Picker",
|
category: "Session Picker",
|
||||||
slash: { name: "sessions-all", aliases: ["resume-all", "continue-all"] },
|
slash: { name: "sessions-all" },
|
||||||
onSelect: () => { void openPicker(); },
|
onSelect: () => {
|
||||||
|
context.ui.toast.show({ message: "sessions-all triggered!", variant: "info" });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
bindings: [
|
})
|
||||||
{
|
|
||||||
key: "ctrl+o",
|
|
||||||
desc: "All sessions picker",
|
|
||||||
preventDefault: true,
|
|
||||||
cmd: () => { void openPicker(); },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user