Still Going

This commit is contained in:
BigBirdReturns 2026-01-25 13:33:28 -08:00
parent d9fd632efe
commit 06f843ac66
3 changed files with 34 additions and 14 deletions

View File

@ -0,0 +1,14 @@
{
"id": "action-receipts",
"name": "Action Receipts",
"description": "Optional reference extension that writes tool-call receipts to local state for debugging/audit.",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean", "default": true },
"receiptsDir": { "type": "string" },
"includeParams": { "type": "boolean", "default": true }
}
}
}

View File

@ -1,7 +1,10 @@
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
import { createReceiptStore } from "./src/store.js";
// Avoid depending on clawdbot/plugin-sdk so this extension can live as a reference
// without publishing/lockfile churn. The runtime will still pass the full API object.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ClawdbotPluginApi = any;
type Issue = { path: Array<string | number>; message: string };
type SafeParseResult =
| { success: true; data?: unknown }
@ -61,26 +64,26 @@ const plugin = {
register(api: ClawdbotPluginApi) {
const store = createReceiptStore({ api });
api.registerHook(["before_tool_call"], async (event, ctx) => {
api.registerHook(["before_tool_call"], async (event: unknown, ctx: unknown) => {
await store.onBeforeToolCall(event as any, ctx as any);
return undefined;
});
api.registerHook(["after_tool_call"], async (event, ctx) => {
api.registerHook(["after_tool_call"], async (event: unknown, ctx: unknown) => {
await store.onAfterToolCall(event as any, ctx as any);
return undefined;
});
api.registerCli((cli) => {
api.registerCli((cli: any) => {
cli.command(
"receipts:list",
"List recent action receipts",
(yargs) =>
(yargs: any) =>
yargs.option("limit", { type: "number", default: 20 }).option("session", {
type: "string",
describe: "Filter by session key"
}),
async (argv) => {
async (argv: any) => {
const rows = await store.list({
limit: argv.limit as number,
sessionKey: argv.session ? String(argv.session) : undefined
@ -94,8 +97,8 @@ const plugin = {
cli.command(
"receipts:show <id>",
"Show a specific receipt",
(yargs) => yargs.positional("id", { type: "string", demandOption: true }),
async (argv) => {
(yargs: any) => yargs.positional("id", { type: "string", demandOption: true }),
async (argv: any) => {
const receipt = await store.read(String(argv.id));
console.log(JSON.stringify(receipt, null, 2));
}

View File

@ -1,8 +1,6 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
type BeforeToolCallEvent = { toolName: string; params: Record<string, unknown> };
type AfterToolCallEvent = {
toolName: string;
@ -41,7 +39,11 @@ async function ensureDir(dir: string) {
await fs.mkdir(dir, { recursive: true });
}
export function createReceiptStore(opts: { api: ClawdbotPluginApi }) {
// Minimal API shape we rely on. The real runtime passes a superset.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type MinimalApi = { runtime: { stateDir: string }; pluginConfig?: any };
export function createReceiptStore(opts: { api: MinimalApi }) {
const api = opts.api;
const enabled = (api.pluginConfig?.enabled as boolean | undefined) ?? true;
const includeParams = (api.pluginConfig?.includeParams as boolean | undefined) ?? true;
@ -53,6 +55,7 @@ export function createReceiptStore(opts: { api: ClawdbotPluginApi }) {
const pending = new Map<string, { id: string; createdAt: string; params?: Record<string, unknown> }>();
function key(ctx: ToolCtx) {
// Prefer per-invocation id to avoid collisions across parallel tool calls.
return ctx.toolCallId && ctx.toolCallId.length > 0
? ctx.toolCallId
: `${ctx.sessionKey ?? ""}::${ctx.toolName}`;
@ -91,7 +94,7 @@ export function createReceiptStore(opts: { api: ClawdbotPluginApi }) {
params: start?.params,
ok: !event.error,
error: event.error,
durationMs: event.durationMs,
durationMs: event.durationMs
};
await writeReceipt(receipt);
@ -134,6 +137,6 @@ export function createReceiptStore(opts: { api: ClawdbotPluginApi }) {
}
}
throw new Error(`receipt not found: ${id}`);
},
}
};
}