UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

285 lines (284 loc) 12.4 kB
import { t as containsAsciiControlCharacter } from "./string-normalization-DsCfAx8q.js"; import { At as boolean, Ln as strictObject, Rn as string, Xn as union, wn as number, yt as _enum } from "./schemas-zxit8y5H.js"; //#region src/boards/board-layout.ts const BOARD_SIZE_PRESETS = { sm: { sizeW: 3, sizeH: 3 }, md: { sizeW: 6, sizeH: 4 }, lg: { sizeW: 8, sizeH: 6 }, xl: { sizeW: 12, sizeH: 8 }, full: { sizeW: 12, sizeH: 8 } }; var BoardValidationError = class extends Error { constructor(code, message) { super(message); this.name = "BoardValidationError"; this.code = code; } }; function cloneTab(tab) { return { tabId: tab.tabId, title: tab.title, position: tab.position, chatDock: tab.chatDock }; } function cloneWidget(widget) { return { name: widget.name, tabId: widget.tabId, ...widget.title !== void 0 ? { title: widget.title } : {}, contentKind: widget.contentKind, ...widget.contentOwner !== void 0 ? { contentOwner: widget.contentOwner } : {}, ...widget.registeredContentKind !== void 0 ? { registeredContentKind: widget.registeredContentKind } : {}, ...widget.presentation !== void 0 ? { presentation: widget.presentation } : {}, ...widget.heightMode !== void 0 ? { heightMode: widget.heightMode } : {}, ...widget.pluginKind !== void 0 ? { pluginKind: widget.pluginKind } : {}, ...widget.props !== void 0 ? { props: structuredClone(widget.props) } : {}, sizeW: widget.sizeW, sizeH: widget.sizeH, position: widget.position, grantState: widget.grantState, revision: widget.revision, ...widget.instanceId !== void 0 ? { instanceId: widget.instanceId } : {}, ...widget.declaredSummary !== void 0 ? { declaredSummary: [...widget.declaredSummary] } : {}, ...widget.declared !== void 0 ? { declared: { ...widget.declared.netOrigins ? { netOrigins: [...widget.declared.netOrigins] } : {}, ...widget.declared.tools ? { tools: [...widget.declared.tools] } : {} } } : {} }; } function cloneLayout(layout) { return { tabs: layout.tabs.map(cloneTab), widgets: layout.widgets.map(cloneWidget) }; } function clampInteger(value, min, max) { return Math.min(max, Math.max(min, Math.trunc(value))); } function comparePosition(a, b) { return a.position - b.position; } function normalizeBoardLayout(layout) { const tabs = layout.tabs.toSorted(comparePosition).map((tab, position) => { const next = cloneTab(tab); next.position = position; return next; }); const tabPosition = new Map(tabs.map((tab) => [tab.tabId, tab.position])); const widgets = layout.widgets.toSorted((a, b) => { return (tabPosition.get(a.tabId) ?? Number.MAX_SAFE_INTEGER) - (tabPosition.get(b.tabId) ?? Number.MAX_SAFE_INTEGER) || a.position - b.position; }).map(cloneWidget); const nextPosition = /* @__PURE__ */ new Map(); for (const widget of widgets) { const position = nextPosition.get(widget.tabId) ?? 0; widget.position = position; nextPosition.set(widget.tabId, position + 1); } return { tabs, widgets }; } function requireTab(layout, tabId) { const tab = layout.tabs.find((candidate) => candidate.tabId === tabId); if (!tab) throw new BoardValidationError("not_found", `board tab not found: ${tabId}`); return tab; } function requireWidget(layout, name) { const widget = layout.widgets.find((candidate) => candidate.name === name); if (!widget) throw new BoardValidationError("not_found", `board widget not found: ${name}`); return widget; } function moveTab(layout, tab, position) { const ordered = layout.tabs.toSorted(comparePosition).filter((candidate) => candidate !== tab); ordered.splice(clampInteger(position, 0, ordered.length), 0, tab); ordered.forEach((candidate, index) => { candidate.position = index; }); layout.tabs = ordered; } function moveWidget(layout, widget, targetTabId, position, after) { requireTab(layout, targetTabId); if (position !== void 0 && after !== void 0) throw new BoardValidationError("invalid_operation", "widget_move accepts either position or after, not both"); const targetWidgets = layout.widgets.filter((candidate) => candidate.tabId === targetTabId && candidate !== widget).toSorted(comparePosition); let targetPosition = targetWidgets.length; if (after !== void 0) { if (after === widget.name) throw new BoardValidationError("invalid_operation", "widget cannot be placed after itself"); const anchorIndex = targetWidgets.findIndex((candidate) => candidate.name === after); if (anchorIndex < 0) throw new BoardValidationError("not_found", `board widget anchor not found on tab ${targetTabId}: ${after}`); targetPosition = anchorIndex + 1; } else if (position !== void 0) targetPosition = clampInteger(position, 0, targetWidgets.length); widget.tabId = targetTabId; targetWidgets.splice(targetPosition, 0, widget); targetWidgets.forEach((candidate, index) => { candidate.position = index; }); layout.widgets = [...layout.widgets.filter((candidate) => candidate !== widget && candidate.tabId !== targetTabId), ...targetWidgets]; } function applyBoardOp(layout, op) { switch (op.kind) { case "tab_create": if (layout.tabs.some((tab) => tab.tabId === op.tabId)) throw new BoardValidationError("conflict", `board tab already exists: ${op.tabId}`); layout.tabs.push({ tabId: op.tabId, title: op.title, position: layout.tabs.length, chatDock: op.chatDock ?? "right" }); return; case "tab_update": { const tab = requireTab(layout, op.tabId); if (op.title === void 0 && op.chatDock === void 0 && op.position === void 0) throw new BoardValidationError("invalid_operation", "tab_update has no changes"); if (op.title !== void 0) tab.title = op.title; if (op.chatDock !== void 0) tab.chatDock = op.chatDock; if (op.position !== void 0) moveTab(layout, tab, op.position); return; } case "tab_delete": { const tab = requireTab(layout, op.tabId); const remainingTabs = layout.tabs.filter((candidate) => candidate !== tab).toSorted(comparePosition); const tabWidgets = layout.widgets.filter((widget) => widget.tabId === tab.tabId).toSorted(comparePosition); if (remainingTabs.length === 0 && tabWidgets.length > 0) throw new BoardValidationError("invalid_operation", "cannot delete the last board tab while it contains widgets"); layout.tabs = remainingTabs; if (tabWidgets.length > 0) { const fallback = remainingTabs[0]; for (const widget of tabWidgets) { widget.tabId = fallback.tabId; widget.position = Number.MAX_SAFE_INTEGER; } } return; } case "tabs_reorder": { if (op.tabIds.length !== layout.tabs.length || new Set(op.tabIds).size !== op.tabIds.length || op.tabIds.some((tabId) => !layout.tabs.some((tab) => tab.tabId === tabId))) throw new BoardValidationError("invalid_operation", "tabs_reorder must contain every tab exactly once"); const byId = new Map(layout.tabs.map((tab) => [tab.tabId, tab])); layout.tabs = op.tabIds.map((tabId, position) => { const tab = byId.get(tabId); tab.position = position; return tab; }); return; } case "widget_move": { const widget = requireWidget(layout, op.name); moveWidget(layout, widget, op.tabId ?? widget.tabId, op.position, op.after); return; } case "widget_resize": { const widget = requireWidget(layout, op.name); widget.sizeW = clampInteger(op.sizeW, 1, 12); widget.sizeH = clampInteger(op.sizeH, 1, 20); widget.heightMode = op.heightMode ?? "fixed"; return; } case "widget_remove": requireWidget(layout, op.name); layout.widgets = layout.widgets.filter((widget) => widget.name !== op.name); } } function applyBoardOps(layout, ops) { const next = cloneLayout(layout); for (const op of ops) { applyBoardOp(next, op); const normalized = normalizeBoardLayout(next); next.tabs = normalized.tabs; next.widgets = normalized.widgets; } return normalizeBoardLayout(next); } function insertBoardWidget(layout, widget, placement) { const next = cloneLayout(layout); const index = next.widgets.findIndex((candidate) => candidate.name === widget.name); const existing = next.widgets[index]; const inserted = { ...widget, tabId: placement.tabId }; if (existing) { inserted.position = existing.position; next.widgets[index] = inserted; } else next.widgets.push(inserted); if (!existing || placement.move) moveWidget(next, inserted, placement.tabId, void 0, placement.after); return normalizeBoardLayout(next); } //#endregion //#region src/boards/github-actions-capability.ts const GITHUB_ACTIONS_BINDING_ID = "github.actions.runs"; const GITHUB_ACTIONS_GRANT_PREFIX = `${GITHUB_ACTIONS_BINDING_ID}:`; const GITHUB_ACTIONS_AUTHOR_GUIDANCE = "With a usable connected agent GitHub identity: await openclaw.data.read(\"github.actions.runs\",{repository:\"owner/repo\",perPage:20}); grant capabilities.tools:[\"github.actions.runs:owner/repo\"]. Identity is checked before save; reconnect in Settings if unavailable. Optional workflow (ID/filename), branch, status, created (ISO day/comparison/range), excludePullRequests=true (omits PR objects); perPage 1..30. Shares private Actions metadata with the widget/session audience; never preview or My GitHub auth. No netOrigins needed."; const repositorySchema = string().regex(/^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9_.-]{1,100}$/u).refine((value) => ![".", ".."].includes(value.split("/")[1])).transform((value) => value.toLowerCase()); const day = "\\d{4}-\\d{2}-\\d{2}"; const createdSchema = string().regex(new RegExp(`^(?:[<>]=?)?${day}$|^${day}\\.\\.${day}$`, "u")).refine((value) => (value.match(/\d{4}-\d{2}-\d{2}/gu) ?? []).every((date) => { const parsed = /* @__PURE__ */ new Date(`${date}T00:00:00Z`); return Number.isFinite(parsed.getTime()) && parsed.toISOString().startsWith(date); })); const paramsSchema = strictObject({ repository: repositorySchema, workflow: union([number().int().positive().max(Number.MAX_SAFE_INTEGER), string().max(255).regex(/^(?:[1-9]\d*|[A-Za-z0-9_.-]+\.ya?ml)$/u).refine((value) => !/^\d+$/u.test(value) || Number.isSafeInteger(Number(value)))]).optional(), perPage: number().int().min(1).max(30).default(20), branch: string().min(1).max(255).refine((value) => !containsAsciiControlCharacter(value)).optional(), status: _enum([ "completed", "action_required", "cancelled", "failure", "neutral", "skipped", "stale", "success", "timed_out", "in_progress", "queued", "requested", "waiting", "pending" ]).optional(), created: createdSchema.optional(), excludePullRequests: boolean().default(true) }); function normalizeGitHubActionsGrant(tool) { if (!tool.startsWith(GITHUB_ACTIONS_GRANT_PREFIX)) return tool; const parsed = repositorySchema.safeParse(tool.slice(GITHUB_ACTIONS_GRANT_PREFIX.length)); if (!parsed.success) throw new BoardValidationError("invalid_operation", "GitHub Actions grant requires owner/repo"); return `${GITHUB_ACTIONS_GRANT_PREFIX}${parsed.data}`; } /** The same closed contract owns URL construction and the exact repository grant. */ function resolveGitHubActionsRequest(params) { const parsed = paramsSchema.safeParse(params); if (!parsed.success) throw new BoardValidationError("invalid_operation", "Invalid GitHub Actions parameters: use repository (owner/repo), workflow, perPage (1..30), branch, status, created, or excludePullRequests only"); const input = parsed.data; const repositoryPath = input.repository.split("/").map(encodeURIComponent).join("/"); const operation = input.workflow === void 0 ? "runs" : `workflows/${encodeURIComponent(String(input.workflow))}/runs`; const url = new URL(`https://api.github.com/repos/${repositoryPath}/actions/${operation}`); url.searchParams.set("per_page", String(input.perPage)); url.searchParams.set("exclude_pull_requests", String(input.excludePullRequests)); for (const field of [ "branch", "status", "created" ]) if (input[field] !== void 0) url.searchParams.set(field, input[field]); return { ...input, url: url.href, capability: `${GITHUB_ACTIONS_GRANT_PREFIX}${input.repository}` }; } //#endregion export { resolveGitHubActionsRequest as a, applyBoardOps as c, normalizeGitHubActionsGrant as i, insertBoardWidget as l, GITHUB_ACTIONS_BINDING_ID as n, BOARD_SIZE_PRESETS as o, GITHUB_ACTIONS_GRANT_PREFIX as r, BoardValidationError as s, GITHUB_ACTIONS_AUTHOR_GUIDANCE as t, normalizeBoardLayout as u };