Still Going
This commit is contained in:
parent
d9fd632efe
commit
06f843ac66
14
extensions/action-receipts/clawdbot.plugin.json
Normal file
14
extensions/action-receipts/clawdbot.plugin.json
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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}`);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user