ai: migrate LLM backbone from Kimi CLI to Codex CLI
Retires the Moonshot/Kimi subscription in favour of the already-paid ChatGPT plan. Both CLI wrappers now run `codex exec`; the kimi-agent container is gone. adolf-llm + hindsight-llm: - runKimi -> runCodex (`codex exec --json --skip-git-repo-check`), resume via `codex exec resume <thread_id>`. - MCP moves from a per-session .mcp.json (a workaround for Kimi having no --mcp-config-file flag) to a $CODEX_HOME/config.toml generated once at startup from shared-mcp.json. Field translation is load-bearing: bearerTokenEnvVar -> bearer_token_env_var, enabledTools -> enabled_tools. - approval_policy="never" + sandbox_mode required, or unattended turns block on an approval prompt nobody can answer. kimi-agent removed. It was the ONLY large-tier deployment behind LiteLLM, so deleting it outright would have silently degraded every large-tier request to the local 4B model via the existing fallbacks. tier-large, the auto_router complex-reasoning route and their fallbacks now point at the codex-backed adolf-llm wrapper (model_name: codex-agent). Three environment blockers fixed along the way: - OpenAI geo-blocks this host (403 unsupported_country_region_territory). Both containers now egress via the host xray proxy, with NO_PROXY keeping MCP and *.alogins.net traffic off the tunnel. - node:22-slim ships no system CA store; the Rust codex binary validates TLS against it, so every HTTPS call failed with a generic transport error while Node's own fetch worked. ca-certificates added to both images. - `codex exec resume` rejects -C/--cd (plain `codex exec` accepts it), which broke follow-up turns while first turns succeeded. Known regression: Kimi's managed-usage API has no Codex equivalent, so the /usage route returns 501 and there is no quota probe for the codex model. The two quota plugins degrade quietly to no output. Also: stop tracking cognee.env (live LLM + JWT secrets) and gitignore it. The secrets remain in earlier history and should be rotated. Verified live: plain turn, SSE streaming, session resume, MCP tool call, bearer-token MCP call, and completions through both LiteLLM routes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Y5QPagv4iun1ghpwM96Ff
This commit is contained in:
102
ai/todoist-capture-plugin/index.js
Normal file
102
ai/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 ai/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 };
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
13
ai/todoist-capture-plugin/openclaw.plugin.json
Normal file
13
ai/todoist-capture-plugin/openclaw.plugin.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "todoist-capture",
|
||||
"name": "Todoist Idea Capture",
|
||||
"description": "Registers /idea: a native-command handler (runs before the agent, zero model calls) that classifies free text (area/urgency/decompose-need, local bge-m3 nearest-centroid — see agap-mcp/src/classifier.js) and creates a labelled Todoist task via agap-mcp's POST /capture-idea.",
|
||||
"activation": {
|
||||
"onStartup": true
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
18
ai/todoist-capture-plugin/package.json
Normal file
18
ai/todoist-capture-plugin/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "openclaw-todoist-capture",
|
||||
"version": "1.0.0",
|
||||
"description": "LLM-free /idea command for Adolf: classifies free text (area/urgency/decompose, local bge-m3 nearest-centroid) via agap-mcp and creates a labelled Todoist task.",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.0"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": ["./index.js"],
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.0.0",
|
||||
"minGatewayVersion": "2026.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user