openai: OpenClaw plugins, memory migration tooling, backup and GPU scripts
Plugins for the Adolf gateway:
- hindsight-openclaw-plugin: expanded memory recall/retain surface for the
Cognee -> Hindsight migration
- todoist-capture-plugin: posts captured ideas to agap-mcp's /capture-idea,
sending the kb#180 bearer token when AGAP_MCP_TOKEN is present
- feedback-loop-openclaw-plugin, kimi-quota-footer-plugin, cognee-mcp,
cognee-openclaw-plugin
Plus migrate-adolf-memory-banks.mjs for the memory-bank split,
backup-hindsight-adolf.sh / backup-llm-dbs.sh (the Hindsight and adolf-state
backups that were previously missing), and gpu_preload_check.sh for the
GTX 1070 residency checks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
102
openai/todoist-capture-plugin/index.js
Normal file
102
openai/todoist-capture-plugin/index.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Todoist Idea Capture (kb#170 component 1) — registers `/idea <text>` on
|
||||
* Adolf's Matrix channel.
|
||||
*
|
||||
* Same reasoning as quota-command-openclaw-plugin (kb#62): OpenClaw's
|
||||
* native-command dispatch (`api.registerCommand`) runs BEFORE the agent
|
||||
* turn, so this never spends a Kimi turn. That property is not incidental
|
||||
* here — it's the whole point of kb#170's "encoder-only, not an LLM call"
|
||||
* design: classification runs on bge-m3 (agap-mcp/src/classifier.js), and
|
||||
* routing the capture through a native command means the ENTIRE
|
||||
* capture -> classify -> Todoist path costs zero model tokens, not just the
|
||||
* classification step.
|
||||
*
|
||||
* This plugin does no classification itself — it POSTs the raw text to
|
||||
* agap-mcp's /capture-idea endpoint (same container agap-mcp already
|
||||
* exposes at :3100 for the MCP tool surface; this is a second, plain-REST
|
||||
* entry point to the same todoistCaptureIdea() function, added because a
|
||||
* native command handler is simplest calling plain JSON over HTTP rather
|
||||
* than speaking MCP JSON-RPC to invoke its own tool). See agap-mcp/src/
|
||||
* capture.js for the classify+create logic and agap-mcp/src/server.js for
|
||||
* the /capture-idea route.
|
||||
*
|
||||
* Gating: requireAuth: true (the registerCommand default) restricts the
|
||||
* command to the same Matrix DM allowlist (channels.matrix.dm.allowFrom in
|
||||
* openclaw.json) that already gates every other interaction with Adolf —
|
||||
* no separate tier needed, this creates a task in the operator's own
|
||||
* Todoist inbox, not a privileged/destructive action.
|
||||
*/
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
|
||||
// agap-mcp is a sibling reached via host.docker.internal, same mapping
|
||||
// openclaw.json's mcp.servers.agap.url already uses for this container.
|
||||
const CAPTURE_URL = "http://host.docker.internal:3100/capture-idea";
|
||||
const FETCH_TIMEOUT_MS = 15000; // bge-m3 embed + Todoist create can take a few seconds
|
||||
|
||||
// kb#180: agap-mcp's :3100 listener is authenticated now — /capture-idea is
|
||||
// no longer an open REST endpoint (it never should have been: it reaches
|
||||
// Todoist writes from any LAN peer). This plugin runs inside the adolf
|
||||
// container, so it presents Adolf's own agap-mcp bearer token, injected as
|
||||
// AGAP_MCP_TOKEN by openai/docker-compose.yml from .env (never inlined
|
||||
// here). If the var is unset the request goes out unauthenticated and
|
||||
// agap-mcp answers 401 — a visible failure of /idea, not a silent one.
|
||||
const AGAP_MCP_TOKEN = process.env.AGAP_MCP_TOKEN || "";
|
||||
|
||||
async function captureIdea(text) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
||||
try {
|
||||
const res = await fetch(CAPTURE_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(AGAP_MCP_TOKEN ? { Authorization: `Bearer ${AGAP_MCP_TOKEN}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({ text }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
const body = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(body?.error || `agap-mcp /capture-idea HTTP ${res.status}`);
|
||||
return body;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function formatReply({ task, classification }) {
|
||||
const bits = [
|
||||
`area=${classification.area.label}`,
|
||||
`urgency=${classification.urgency.label}`,
|
||||
];
|
||||
if (classification.decompose.label === "needs-decomposition") bits.push("требует декомпозиции в Kanboard");
|
||||
if (classification.area.ambiguous) bits.push("область — неточно, уточни при ревью");
|
||||
return `Записал в Todoist: «${task.content}» (${bits.join(", ")}).`;
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "todoist-capture",
|
||||
name: "Todoist Idea Capture",
|
||||
description:
|
||||
"LLM-free /idea command: classifies free text via agap-mcp (local bge-m3, no Kimi call) and creates a labelled Todoist task.",
|
||||
register(api) {
|
||||
api.registerCommand({
|
||||
name: "idea",
|
||||
description: "Capture an idea/quick task -> classified (area/urgency/decompose) and filed in Todoist. No Kimi call.",
|
||||
acceptsArgs: true,
|
||||
requireAuth: true,
|
||||
handler: async (ctx) => {
|
||||
const text = (ctx.args || "").trim();
|
||||
if (!text) {
|
||||
return { text: "Использование: /idea <текст идеи>", suppressReply: true };
|
||||
}
|
||||
try {
|
||||
const result = await captureIdea(text);
|
||||
return { text: formatReply(result), suppressReply: true };
|
||||
} catch (e) {
|
||||
api.logger?.warn?.(`todoist-capture: capture failed (${e?.message || e})`);
|
||||
return { text: `Не удалось захватить идею: ${e?.message || e}`, suppressReply: true };
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user