Files
openclaw/extensions/workboard/src/gateway.ts

574 lines
15 KiB
TypeScript

// Workboard plugin module implements gateway behavior.
import type { OpenClawPluginApi } from "../api.js";
import { redactClaimToken } from "./card-redaction.js";
import {
assertNoCursorAdvance,
createWorkboardDispatchHandler,
listWorkboardCards,
readId,
respondError,
} from "./gateway-helpers.js";
import {
registerWorkboardWorkspaceBoardMethod,
registerWorkboardWorkspaceBulkMethod,
registerWorkboardWorkspaceCardMethods,
registerWorkboardWorkspaceWorkflowMethods,
} from "./gateway-workspace-methods.js";
import { WorkboardStore } from "./store.js";
const READ_SCOPE = "operator.read" as const;
const WRITE_SCOPE = "operator.write" as const;
function redactDiagnosticsRows(result: Awaited<ReturnType<WorkboardStore["diagnostics"]>>) {
return {
...result,
diagnostics: result.diagnostics.map((row) => ({
...row,
card: redactClaimToken(row.card),
})),
};
}
export function registerWorkboardGatewayMethods(params: {
api: OpenClawPluginApi;
store?: WorkboardStore;
}) {
const { api } = params;
const store = params.store ?? WorkboardStore.openSqlite();
const dispatchCards = createWorkboardDispatchHandler({
api,
store,
redactCard: redactClaimToken,
});
api.registerGatewayMethod(
"workboard.cards.list",
async ({ params: requestParams, respond }) => {
try {
respond(true, await listWorkboardCards(store, requestParams.boardId, redactClaimToken));
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
registerWorkboardWorkspaceCardMethods({ api, store, redactCard: redactClaimToken });
api.registerGatewayMethod(
"workboard.cards.move",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(
await store.move(readId(requestParams), requestParams.status, requestParams.position),
),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.delete",
async ({ params: requestParams, respond }) => {
try {
respond(true, await store.delete(readId(requestParams)));
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.comment",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.addComment(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.link",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.addLink(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.linkDependency",
async ({ params: requestParams, respond }) => {
try {
const parentId = requestParams.parentId;
const childId = requestParams.childId;
if (typeof parentId !== "string" || typeof childId !== "string") {
throw new Error("parentId and childId are required.");
}
respond(true, {
card: redactClaimToken(await store.linkCards(parentId, childId)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.proof",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.addProof(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.artifact",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.addArtifact(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.claim",
async ({ params: requestParams, respond }) => {
try {
const claimed = await store.claim(readId(requestParams), requestParams);
respond(true, { ...claimed, card: redactClaimToken(claimed.card) });
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.heartbeat",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.heartbeat(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.release",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.releaseClaim(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.promote",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.promote(readId(requestParams), requestParams, null)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.reassign",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.reassign(readId(requestParams), requestParams, null)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.reclaim",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.reclaim(readId(requestParams), requestParams, null)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.complete",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.complete(readId(requestParams), requestParams, null)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.block",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.block(readId(requestParams), requestParams, null)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.unblock",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.unblock(readId(requestParams))),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
registerWorkboardWorkspaceBulkMethod({ api, store, redactCard: redactClaimToken });
api.registerGatewayMethod(
"workboard.cards.diagnostics",
async ({ respond }) => {
try {
respond(true, redactDiagnosticsRows(await store.diagnostics()));
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.diagnostics.refresh",
async ({ respond }) => {
try {
respond(true, redactDiagnosticsRows(await store.refreshDiagnostics()));
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.dispatch",
async (context) => await dispatchCards(context, { supportsMaxStarts: false }),
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.dispatchWithOptions",
async (context) => await dispatchCards(context, { supportsMaxStarts: true }),
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.boards.list",
async ({ respond }) => {
try {
respond(true, await store.listBoards());
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
registerWorkboardWorkspaceBoardMethod({ api, store, redactCard: redactClaimToken });
api.registerGatewayMethod(
"workboard.boards.archive",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
board: await store.archiveBoard(requestParams.id, requestParams.archived),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.boards.delete",
async ({ params: requestParams, respond }) => {
try {
respond(true, await store.deleteBoard(requestParams.id));
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.stats",
async ({ params: requestParams, respond }) => {
try {
respond(true, await store.stats({ boardId: requestParams.boardId }));
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.runs",
async ({ params: requestParams, respond }) => {
try {
const result = await store.runs(readId(requestParams));
respond(true, { ...result, card: redactClaimToken(result.card) });
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
registerWorkboardWorkspaceWorkflowMethods({ api, store, redactCard: redactClaimToken });
api.registerGatewayMethod(
"workboard.notifications.subscribe",
async ({ params: requestParams, respond }) => {
try {
respond(true, { subscription: await store.subscribeNotifications(requestParams) });
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.notifications.list",
async ({ params: requestParams, respond }) => {
try {
respond(true, await store.listNotificationSubscriptions(requestParams));
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
api.registerGatewayMethod(
"workboard.notifications.delete",
async ({ params: requestParams, respond }) => {
try {
respond(true, await store.deleteNotificationSubscription(readId(requestParams)));
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.notifications.events",
async ({ params: requestParams, respond }) => {
try {
assertNoCursorAdvance(requestParams);
respond(true, await store.notificationEvents(requestParams));
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
api.registerGatewayMethod(
"workboard.notifications.advance",
async ({ params: requestParams, respond }) => {
try {
respond(true, await store.advanceNotificationEvents(requestParams));
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.attachments.list",
async ({ params: requestParams, respond }) => {
try {
const result = await store.listAttachments(readId(requestParams));
respond(true, { ...result, card: redactClaimToken(result.card) });
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.attachments.get",
async ({ params: requestParams, respond }) => {
try {
const attachment = await store.getAttachment(readId(requestParams));
if (!attachment) {
throw new Error(`attachment not found: ${readId(requestParams)}`);
}
respond(true, attachment);
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.attachments.add",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.addAttachment(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.attachments.delete",
async ({ params: requestParams, respond }) => {
try {
const attachmentId = requestParams.attachmentId;
if (typeof attachmentId !== "string" || !attachmentId.trim()) {
throw new Error("attachmentId is required.");
}
respond(true, {
card: redactClaimToken(
await store.deleteAttachment(readId(requestParams), attachmentId.trim()),
),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.workerLog",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(await store.addWorkerLog(readId(requestParams), requestParams)),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.protocolViolation",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(
await store.recordProtocolViolation(readId(requestParams), requestParams),
),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.archive",
async ({ params: requestParams, respond }) => {
try {
respond(true, {
card: redactClaimToken(
await store.archive(readId(requestParams), requestParams.archived),
),
});
} catch (error) {
respondError(respond, error);
}
},
{ scope: WRITE_SCOPE },
);
api.registerGatewayMethod(
"workboard.cards.export",
async ({ respond }) => {
try {
const exported = await store.exportCards();
respond(true, { ...exported, cards: exported.cards.map(redactClaimToken) });
} catch (error) {
respondError(respond, error);
}
},
{ scope: READ_SCOPE },
);
}