mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(workboard): share control ui contract (#102271)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -87,6 +87,13 @@ operator see how a card moved through the board without opening the linked
|
||||
session; it is local operating context, not a replacement for session
|
||||
transcripts or GitHub issue history.
|
||||
|
||||
The plugin and Control UI use one Workboard card contract. Dashboard refreshes
|
||||
therefore preserve workspace provenance and authority, claim state, diagnostic
|
||||
actions, and notification sequence numbers instead of projecting a smaller
|
||||
UI-only copy of the card. Unknown diagnostic kinds, diagnostic severities, and
|
||||
notification kinds are ignored until both surfaces support them; they are never
|
||||
rewritten into another valid state.
|
||||
|
||||
Cards are stored in the plugin's own Gateway state and move with the rest of
|
||||
that Gateway's OpenClaw state (see [Storage](#storage)).
|
||||
|
||||
|
||||
@@ -278,6 +278,9 @@
|
||||
"@openclaw/retry": [
|
||||
"../dist/plugin-sdk/packages/retry/src/index.d.ts"
|
||||
],
|
||||
"@openclaw/workboard-contract": [
|
||||
"../packages/workboard-contract/src/index.ts"
|
||||
],
|
||||
"@openclaw/acp-core": [
|
||||
"../dist/plugin-sdk/packages/acp-core/src/index.d.ts"
|
||||
],
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*",
|
||||
"@openclaw/workboard-contract": "workspace:*",
|
||||
"openclaw": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -7,4 +7,4 @@ export type {
|
||||
WorkboardListResult,
|
||||
WorkboardPriority,
|
||||
WorkboardStatus,
|
||||
} from "./src/types.js";
|
||||
} from "@openclaw/workboard-contract";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Workboard plugin module implements card lookup behavior.
|
||||
import type { WorkboardCard } from "./types.js";
|
||||
import type { WorkboardCard } from "@openclaw/workboard-contract";
|
||||
|
||||
type WorkboardCardLookupResult =
|
||||
| { card: WorkboardCard; error?: undefined }
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
WORKBOARD_STATUSES,
|
||||
type WorkboardCard,
|
||||
type WorkboardStatus,
|
||||
} from "@openclaw/workboard-contract";
|
||||
// Workboard plugin module implements cli behavior.
|
||||
import type { Command } from "commander";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
@@ -7,7 +12,6 @@ import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveWorkboardCardByIdOrPrefix } from "./card-lookup.js";
|
||||
import type { WorkboardDispatchResult, WorkboardStore } from "./store.js";
|
||||
import { WORKBOARD_STATUSES, type WorkboardCard, type WorkboardStatus } from "./types.js";
|
||||
|
||||
type JsonOptions = {
|
||||
json?: boolean;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
WORKBOARD_STATUSES,
|
||||
type WorkboardCard,
|
||||
type WorkboardStatus,
|
||||
} from "@openclaw/workboard-contract";
|
||||
// Workboard plugin module implements command behavior.
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import { resolveWorkboardCardByIdOrPrefix } from "./card-lookup.js";
|
||||
@@ -7,7 +12,6 @@ import {
|
||||
type WorkboardWorktreeRuntime,
|
||||
} from "./dispatcher.js";
|
||||
import type { WorkboardStore } from "./store.js";
|
||||
import { WORKBOARD_STATUSES, type WorkboardCard, type WorkboardStatus } from "./types.js";
|
||||
import {
|
||||
canonicalizeWorkboardWorkspaceAccess,
|
||||
resolveAgentWorkboardWorkspaceRuntime,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { WorkboardCard } from "@openclaw/workboard-contract";
|
||||
// Workboard dispatch workspace helpers keep authority resolution outside the orchestration loop.
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { canonicalPathFromExistingAncestor } from "openclaw/plugin-sdk/security-runtime";
|
||||
import type { WorkboardStore } from "./store.js";
|
||||
import type { WorkboardCard } from "./types.js";
|
||||
import {
|
||||
assertCanonicalWorkboardRootAccess,
|
||||
canonicalizeWorkboardWorkspaceAccess,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Workboard plugin module implements dispatcher behavior.
|
||||
import path from "node:path";
|
||||
import type {
|
||||
WorkboardCard,
|
||||
WorkboardExecution,
|
||||
WorkboardWorkspace,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { canonicalPathFromExistingAncestor } from "openclaw/plugin-sdk/security-runtime";
|
||||
@@ -10,7 +15,6 @@ import {
|
||||
type ResolveAgentWorkspaceRuntime,
|
||||
} from "./dispatcher-workspace.js";
|
||||
import { WorkboardStore, type WorkboardDispatchResult } from "./store.js";
|
||||
import type { WorkboardCard, WorkboardExecution, WorkboardWorkspace } from "./types.js";
|
||||
import {
|
||||
assertCanonicalWorkboardRootAccess,
|
||||
assertWorkboardWorkspaceSourceAccess,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { WorkboardCard } from "@openclaw/workboard-contract";
|
||||
// Workboard plugin module implements shared gateway request helpers.
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import { dispatchAndStartWorkboardCards } from "./dispatcher.js";
|
||||
import type { WorkboardStore } from "./store.js";
|
||||
import type { WorkboardCard } from "./types.js";
|
||||
import {
|
||||
resolveAgentWorkboardWorkspaceRuntime,
|
||||
resolveConfiguredWorkboardWorkspaceAccess,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { WorkboardCard } from "@openclaw/workboard-contract";
|
||||
// Workboard Gateway methods that can persist workspace-bearing card metadata.
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import {
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
type GatewayMethodContext,
|
||||
} from "./gateway-helpers.js";
|
||||
import type { WorkboardStore } from "./store.js";
|
||||
import type { WorkboardCard } from "./types.js";
|
||||
import {
|
||||
assertWorkboardWorkspaceMutationAccess,
|
||||
canonicalizeWorkboardWorkspaceAccess,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { WORKBOARD_STATUSES, type WorkboardCard } from "@openclaw/workboard-contract";
|
||||
// Workboard plugin module implements gateway behavior.
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import {
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
registerWorkboardWorkspaceWorkflowMethods,
|
||||
} from "./gateway-workspace-methods.js";
|
||||
import { WorkboardStore } from "./store.js";
|
||||
import { WORKBOARD_STATUSES, type WorkboardCard } from "./types.js";
|
||||
|
||||
const READ_SCOPE = "operator.read" as const;
|
||||
const WRITE_SCOPE = "operator.write" as const;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
WorkboardBoardMetadata,
|
||||
WorkboardCard,
|
||||
WorkboardNotificationSubscription,
|
||||
} from "./types.js";
|
||||
} from "@openclaw/workboard-contract";
|
||||
|
||||
export type PersistedWorkboardCard = {
|
||||
version: 1;
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { DatabaseSync, type SQLInputValue } from "node:sqlite";
|
||||
import { configureSqliteConnectionPragmas } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
||||
import type {
|
||||
PersistedWorkboardAttachment,
|
||||
PersistedWorkboardBoard,
|
||||
PersistedWorkboardCard,
|
||||
PersistedWorkboardNotificationSubscription,
|
||||
WorkboardKeyedStore,
|
||||
} from "./persistence-types.js";
|
||||
import type {
|
||||
WorkboardArtifact,
|
||||
WorkboardAttachment,
|
||||
@@ -25,7 +16,16 @@ import type {
|
||||
WorkboardProof,
|
||||
WorkboardRunAttempt,
|
||||
WorkboardWorkerLog,
|
||||
} from "./types.js";
|
||||
} from "@openclaw/workboard-contract";
|
||||
import { configureSqliteConnectionPragmas } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
||||
import type {
|
||||
PersistedWorkboardAttachment,
|
||||
PersistedWorkboardBoard,
|
||||
PersistedWorkboardCard,
|
||||
PersistedWorkboardNotificationSubscription,
|
||||
WorkboardKeyedStore,
|
||||
} from "./persistence-types.js";
|
||||
|
||||
const WORKBOARD_DB_RELATIVE_PATH = ["plugins", "workboard", "workboard.sqlite"] as const;
|
||||
const SCHEMA_VERSION = 2;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { WorkboardAutomation, WorkboardWorkspaceAccess } from "@openclaw/workboard-contract";
|
||||
// Workboard automation helpers normalize trusted host-issued workspace provenance.
|
||||
import type { WorkboardLinkedCreateInput } from "./store-inputs.js";
|
||||
import { normalizeAutomation, normalizeBoundedString } from "./store-normalizers.js";
|
||||
import type { WorkboardAutomation, WorkboardWorkspaceAccess } from "./types.js";
|
||||
import { isAbsoluteWorkspacePath } from "./workspace-path.js";
|
||||
|
||||
function normalizeTrustedWorkspaceAccess(
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import {
|
||||
WORKBOARD_STATUSES,
|
||||
type WorkboardAttemptStatus,
|
||||
type WorkboardCard,
|
||||
type WorkboardDiagnostic,
|
||||
type WorkboardDiagnosticAction,
|
||||
type WorkboardDiagnosticKind,
|
||||
type WorkboardDiagnosticSeverity,
|
||||
type WorkboardEvent,
|
||||
type WorkboardExecution,
|
||||
type WorkboardMetadata,
|
||||
type WorkboardNotification,
|
||||
type WorkboardRunAttempt,
|
||||
type WorkboardStatus,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import {
|
||||
@@ -16,21 +31,6 @@ import {
|
||||
normalizeTimestamp,
|
||||
removeUndefinedMetadataFields,
|
||||
} from "./store-normalizers.js";
|
||||
import {
|
||||
WORKBOARD_STATUSES,
|
||||
type WorkboardAttemptStatus,
|
||||
type WorkboardCard,
|
||||
type WorkboardDiagnostic,
|
||||
type WorkboardDiagnosticAction,
|
||||
type WorkboardDiagnosticKind,
|
||||
type WorkboardDiagnosticSeverity,
|
||||
type WorkboardEvent,
|
||||
type WorkboardExecution,
|
||||
type WorkboardMetadata,
|
||||
type WorkboardNotification,
|
||||
type WorkboardRunAttempt,
|
||||
type WorkboardStatus,
|
||||
} from "./types.js";
|
||||
|
||||
export function compareCards(left: WorkboardCard, right: WorkboardCard): number {
|
||||
if (left.status !== right.status) {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type {
|
||||
WorkboardBoardMetadata,
|
||||
WorkboardCard,
|
||||
WorkboardLink,
|
||||
WorkboardMetadata,
|
||||
WorkboardStatus,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import type {
|
||||
PersistedWorkboardAttachment,
|
||||
PersistedWorkboardBoard,
|
||||
@@ -57,13 +64,6 @@ import {
|
||||
syncExecutionSessionKey,
|
||||
trimMetadataToBudget,
|
||||
} from "./store-normalizers.js";
|
||||
import type {
|
||||
WorkboardBoardMetadata,
|
||||
WorkboardCard,
|
||||
WorkboardLink,
|
||||
WorkboardMetadata,
|
||||
WorkboardStatus,
|
||||
} from "./types.js";
|
||||
|
||||
export class WorkboardCoreStore {
|
||||
private mutationQueue: Promise<unknown> = Promise.resolve();
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type {
|
||||
WorkboardAttachment,
|
||||
WorkboardCard,
|
||||
WorkboardNotification,
|
||||
WorkboardWorkerLog,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import type { PersistedWorkboardAttachment } from "./persistence-types.js";
|
||||
import {
|
||||
assertCanMutateClaimedCard,
|
||||
@@ -30,12 +36,6 @@ import {
|
||||
normalizeBoundedString,
|
||||
normalizeProofInput,
|
||||
} from "./store-normalizers.js";
|
||||
import type {
|
||||
WorkboardAttachment,
|
||||
WorkboardCard,
|
||||
WorkboardNotification,
|
||||
WorkboardWorkerLog,
|
||||
} from "./types.js";
|
||||
|
||||
export class WorkboardEnrichmentStore extends WorkboardCoreStore {
|
||||
async addProof(
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
WorkboardStatus,
|
||||
WorkboardWorkspace,
|
||||
WorkboardWorkspaceAccess,
|
||||
} from "./types.js";
|
||||
} from "@openclaw/workboard-contract";
|
||||
|
||||
type WorkboardCardInput = {
|
||||
title?: unknown;
|
||||
|
||||
@@ -1,24 +1,4 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import {
|
||||
MAX_ATTACHMENT_BYTES,
|
||||
MAX_CARD_ARTIFACTS,
|
||||
MAX_CARD_ATTACHMENTS,
|
||||
MAX_CARD_ATTEMPTS,
|
||||
MAX_CARD_COMMENTS,
|
||||
MAX_CARD_DIAGNOSTICS,
|
||||
MAX_CARD_EVENTS,
|
||||
MAX_CARD_LINKS,
|
||||
MAX_CARD_METADATA_BYTES,
|
||||
MAX_CARD_NOTIFICATIONS,
|
||||
MAX_CARD_PROOF,
|
||||
MAX_CARD_WORKER_LOGS,
|
||||
} from "./store-constants.js";
|
||||
import type {
|
||||
WorkboardAttachmentInput,
|
||||
WorkboardBoardInput,
|
||||
WorkboardNotificationSubscribeInput,
|
||||
WorkboardProofInput,
|
||||
} from "./store-inputs.js";
|
||||
import {
|
||||
WORKBOARD_ATTEMPT_STATUSES,
|
||||
WORKBOARD_DIAGNOSTIC_KINDS,
|
||||
@@ -66,7 +46,27 @@ import {
|
||||
type WorkboardWorkerLog,
|
||||
type WorkboardWorkerProtocol,
|
||||
type WorkboardWorkspace,
|
||||
} from "./types.js";
|
||||
} from "@openclaw/workboard-contract";
|
||||
import {
|
||||
MAX_ATTACHMENT_BYTES,
|
||||
MAX_CARD_ARTIFACTS,
|
||||
MAX_CARD_ATTACHMENTS,
|
||||
MAX_CARD_ATTEMPTS,
|
||||
MAX_CARD_COMMENTS,
|
||||
MAX_CARD_DIAGNOSTICS,
|
||||
MAX_CARD_EVENTS,
|
||||
MAX_CARD_LINKS,
|
||||
MAX_CARD_METADATA_BYTES,
|
||||
MAX_CARD_NOTIFICATIONS,
|
||||
MAX_CARD_PROOF,
|
||||
MAX_CARD_WORKER_LOGS,
|
||||
} from "./store-constants.js";
|
||||
import type {
|
||||
WorkboardAttachmentInput,
|
||||
WorkboardBoardInput,
|
||||
WorkboardNotificationSubscribeInput,
|
||||
WorkboardProofInput,
|
||||
} from "./store-inputs.js";
|
||||
import { isAbsoluteWorkspacePath } from "./workspace-path.js";
|
||||
|
||||
export function normalizeOptionalString(value: unknown): string | undefined {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type {
|
||||
WorkboardNotification,
|
||||
WorkboardNotificationSubscription,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import type { PersistedWorkboardNotificationSubscription } from "./persistence-types.js";
|
||||
import {
|
||||
cardRunId,
|
||||
@@ -16,7 +20,6 @@ import {
|
||||
normalizeNotificationSubscription,
|
||||
} from "./store-normalizers.js";
|
||||
import { WorkboardWorkflowStore } from "./store-workflow.js";
|
||||
import type { WorkboardNotification, WorkboardNotificationSubscription } from "./types.js";
|
||||
|
||||
export class WorkboardNotificationStore extends WorkboardWorkflowStore {
|
||||
async subscribeNotifications(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { WorkboardCard } from "@openclaw/workboard-contract";
|
||||
import { assertCanMutateClaimedCard } from "./store-card-helpers.js";
|
||||
import { MAX_CARD_COMMENTS } from "./store-constants.js";
|
||||
import { WorkboardEnrichmentStore } from "./store-enrichment.js";
|
||||
import type { WorkboardMutationScope, WorkboardPromoteInput } from "./store-inputs.js";
|
||||
import { clearDiagnostics, normalizeBoundedString } from "./store-normalizers.js";
|
||||
import type { WorkboardCard } from "./types.js";
|
||||
|
||||
export class WorkboardPromoteStore extends WorkboardEnrichmentStore {
|
||||
async move(
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import type {
|
||||
WorkboardArtifact,
|
||||
WorkboardCard,
|
||||
WorkboardClaim,
|
||||
WorkboardNotification,
|
||||
WorkboardRunAttempt,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import { isFutureDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
@@ -49,13 +56,6 @@ import {
|
||||
removeUndefinedMetadataFields,
|
||||
} from "./store-normalizers.js";
|
||||
import { WorkboardPromoteStore } from "./store-promote.js";
|
||||
import type {
|
||||
WorkboardArtifact,
|
||||
WorkboardCard,
|
||||
WorkboardClaim,
|
||||
WorkboardNotification,
|
||||
WorkboardRunAttempt,
|
||||
} from "./types.js";
|
||||
|
||||
function assertClaimIdentity(claim: WorkboardClaim, input: WorkboardHeartbeatInput): void {
|
||||
const token = normalizeOptionalString(input.token);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Workboard plugin module implements store behavior.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { WorkboardAttachment, WorkboardCard } from "@openclaw/workboard-contract";
|
||||
import type {
|
||||
PersistedWorkboardAttachment,
|
||||
PersistedWorkboardBoard,
|
||||
@@ -39,7 +40,6 @@ import {
|
||||
trimMetadataToBudget,
|
||||
} from "./store-normalizers.js";
|
||||
import { WorkboardNotificationStore } from "./store-notifications.js";
|
||||
import type { WorkboardAttachment, WorkboardCard } from "./types.js";
|
||||
|
||||
export type {
|
||||
PersistedWorkboardAttachment,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { WORKBOARD_STATUSES, type WorkboardCard } from "@openclaw/workboard-contract";
|
||||
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { AgentToolResult } from "openclaw/plugin-sdk/tool-results";
|
||||
import { Type } from "typebox";
|
||||
import type { WorkboardMutationScope } from "./store-inputs.js";
|
||||
import type { WorkboardStore } from "./store.js";
|
||||
import { WORKBOARD_STATUSES, type WorkboardCard } from "./types.js";
|
||||
|
||||
type ScopedMoveParams = {
|
||||
record: Record<string, unknown>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { WorkboardCard } from "@openclaw/workboard-contract";
|
||||
// Workboard plugin module implements tools behavior.
|
||||
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
||||
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
@@ -6,7 +7,6 @@ import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { Type } from "typebox";
|
||||
import { WorkboardStore } from "./store.js";
|
||||
import { cardIdField, claimTokenField, createWorkboardMoveTool } from "./tools-card-mutations.js";
|
||||
import type { WorkboardCard } from "./types.js";
|
||||
|
||||
function contextOwner(ctx: OpenClawPluginToolContext | undefined): string {
|
||||
const record = (ctx ?? {}) as Record<string, unknown>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { WorkboardWorkspace, WorkboardWorkspaceAccess } from "@openclaw/workboard-contract";
|
||||
// Workboard workspace access follows the caller's canonical filesystem boundary.
|
||||
import {
|
||||
listAgentIds,
|
||||
@@ -14,9 +15,8 @@ import {
|
||||
canonicalPathFromExistingAncestor,
|
||||
isPathInside,
|
||||
} from "openclaw/plugin-sdk/security-runtime";
|
||||
import type { WorkboardWorkspace, WorkboardWorkspaceAccess } from "./types.js";
|
||||
|
||||
export type { WorkboardWorkspaceAccess } from "./types.js";
|
||||
export type { WorkboardWorkspaceAccess } from "@openclaw/workboard-contract";
|
||||
|
||||
type WorkboardConfig = NonNullable<OpenClawPluginToolContext["config"]>;
|
||||
type ResolveSandboxWorkspaceAuthority =
|
||||
|
||||
@@ -261,6 +261,9 @@
|
||||
"@openclaw/retry": [
|
||||
"../../dist/plugin-sdk/packages/retry/src/index.d.ts"
|
||||
],
|
||||
"@openclaw/workboard-contract": [
|
||||
"../../packages/workboard-contract/src/index.ts"
|
||||
],
|
||||
"@openclaw/acp-core": [
|
||||
"../../dist/plugin-sdk/packages/acp-core/src/index.d.ts"
|
||||
],
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/workboard-contract",
|
||||
"version": "0.0.0-private",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/workboard-contract",
|
||||
"version": "0.0.0-private"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@openclaw/workboard-contract",
|
||||
"version": "0.0.0-private",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Workboard type declarations define plugin contracts.
|
||||
// Workboard contract declarations define the plugin and Control UI data model.
|
||||
export const WORKBOARD_STATUSES = [
|
||||
"triage",
|
||||
"backlog",
|
||||
@@ -184,7 +184,7 @@ export type WorkboardWorkerProtocol = {
|
||||
detail?: string;
|
||||
};
|
||||
|
||||
type WorkboardStaleState = {
|
||||
export type WorkboardStaleState = {
|
||||
detectedAt: number;
|
||||
lastSessionUpdatedAt?: number;
|
||||
reason: string;
|
||||
Generated
+8
@@ -1896,6 +1896,9 @@ importers:
|
||||
'@openclaw/plugin-sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/plugin-sdk
|
||||
'@openclaw/workboard-contract':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/workboard-contract
|
||||
openclaw:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
@@ -2132,6 +2135,8 @@ importers:
|
||||
|
||||
packages/web-content-core: {}
|
||||
|
||||
packages/workboard-contract: {}
|
||||
|
||||
ui:
|
||||
dependencies:
|
||||
'@awesome.me/webawesome':
|
||||
@@ -2185,6 +2190,9 @@ importers:
|
||||
'@openclaw/uirouter':
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0
|
||||
'@openclaw/workboard-contract':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/workboard-contract
|
||||
dompurify:
|
||||
specifier: 3.4.11
|
||||
version: 3.4.11
|
||||
|
||||
@@ -189,6 +189,7 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = {
|
||||
],
|
||||
"@openclaw/normalization-core/*": ["../dist/plugin-sdk/packages/normalization-core/src/*.d.ts"],
|
||||
"@openclaw/retry": ["../dist/plugin-sdk/packages/retry/src/index.d.ts"],
|
||||
"@openclaw/workboard-contract": ["../packages/workboard-contract/src/index.ts"],
|
||||
...buildPackageBoundaryDtsPaths({
|
||||
packageName: "@openclaw/acp-core",
|
||||
packageDir: "acp-core",
|
||||
|
||||
@@ -484,6 +484,7 @@ export const sharedVitestConfig = {
|
||||
sourcePackageAlias("media-core", "read-byte-stream-with-limit"),
|
||||
sourcePackageAlias("media-core"),
|
||||
sourcePackageAlias("retry"),
|
||||
sourcePackageAlias("workboard-contract"),
|
||||
...sourcePackageAliasesFromExports("acp-core", acpCorePackageJson.exports),
|
||||
...sourcePluginSdkSubpaths.map((subpath) => ({
|
||||
find: `openclaw/plugin-sdk/${subpath}`,
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
],
|
||||
"@openclaw/normalization-core/*": ["./packages/normalization-core/src/*"],
|
||||
"@openclaw/retry": ["./packages/retry/src/index.ts"],
|
||||
"@openclaw/workboard-contract": ["./packages/workboard-contract/src/index.ts"],
|
||||
"@openclaw/acp-core": ["./packages/acp-core/src/index.ts"],
|
||||
"@openclaw/acp-core/meta": ["./packages/acp-core/src/meta.ts"],
|
||||
"@openclaw/acp-core/numeric-options": ["./packages/acp-core/src/numeric-options.ts"],
|
||||
|
||||
+1
-1
@@ -228,7 +228,7 @@ function shouldAlwaysBundleDependency(id: string): boolean {
|
||||
id === "@openclaw/retry" ||
|
||||
id === "@openclaw/media-core" ||
|
||||
id.startsWith("@openclaw/media-core/") ||
|
||||
id === "@openclaw/acp-core" ||
|
||||
["@openclaw/acp-core", "@openclaw/workboard-contract"].includes(id) ||
|
||||
id.startsWith("@openclaw/acp-core/") ||
|
||||
id === "zod" ||
|
||||
id.startsWith("zod/")
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@openclaw/libterminal": "0.3.1",
|
||||
"@openclaw/media-core": "workspace:*",
|
||||
"@openclaw/normalization-core": "workspace:*",
|
||||
"@openclaw/workboard-contract": "workspace:*",
|
||||
"@openclaw/uirouter": "0.1.0",
|
||||
"dompurify": "3.4.11",
|
||||
"ghostty-web": "0.4.0",
|
||||
|
||||
@@ -2782,7 +2782,7 @@ describe("workboard controller", () => {
|
||||
expect(state.tasksByCardId.has("card-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves automation metadata loaded from the plugin gateway method", async () => {
|
||||
it("preserves contract-owned metadata loaded from the plugin gateway method", async () => {
|
||||
const host = {};
|
||||
const client = createClient({
|
||||
"workboard.cards.list": {
|
||||
@@ -2793,10 +2793,60 @@ describe("workboard controller", () => {
|
||||
automation: {
|
||||
tenant: "qa",
|
||||
skills: ["testing"],
|
||||
workspace: { kind: "scratch" },
|
||||
workspace: {
|
||||
kind: "worktree",
|
||||
path: "/tmp/worktree",
|
||||
branch: "work/card-1",
|
||||
sourcePath: "/repo",
|
||||
sourceBranch: "main",
|
||||
},
|
||||
workspaceAccess: {
|
||||
unrestricted: false,
|
||||
roots: ["/repo"],
|
||||
writable: true,
|
||||
},
|
||||
dispatchCount: 2,
|
||||
lastDispatchAt: 20,
|
||||
},
|
||||
claim: {
|
||||
ownerId: "agent:main",
|
||||
token: "[redacted]",
|
||||
claimedAt: 10,
|
||||
lastHeartbeatAt: 11,
|
||||
},
|
||||
diagnostics: [
|
||||
{
|
||||
kind: "missing_proof",
|
||||
severity: "warning",
|
||||
title: "Proof missing",
|
||||
detail: "Attach focused validation.",
|
||||
firstSeenAt: 12,
|
||||
lastSeenAt: 13,
|
||||
count: 1,
|
||||
actions: [{ kind: "add_proof", label: "Add proof" }],
|
||||
},
|
||||
{ kind: "future_kind", title: "Invalid contract value" },
|
||||
{
|
||||
kind: "missing_proof",
|
||||
severity: "future_severity",
|
||||
title: "Invalid severity value",
|
||||
},
|
||||
],
|
||||
notifications: [
|
||||
{
|
||||
id: "notification-1",
|
||||
kind: "completed",
|
||||
createdAt: 14,
|
||||
sequence: 3,
|
||||
message: "Card completed",
|
||||
},
|
||||
{
|
||||
id: "notification-2",
|
||||
kind: "future_kind",
|
||||
createdAt: 15,
|
||||
message: "Invalid contract value",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -2806,13 +2856,29 @@ describe("workboard controller", () => {
|
||||
|
||||
await loadWorkboard({ host, client: client as never, force: true });
|
||||
|
||||
expect(getWorkboardState(host).cards[0]?.metadata?.automation).toMatchObject({
|
||||
tenant: "qa",
|
||||
skills: ["testing"],
|
||||
workspace: { kind: "scratch" },
|
||||
dispatchCount: 2,
|
||||
lastDispatchAt: 20,
|
||||
expect(getWorkboardState(host).cards[0]?.metadata).toMatchObject({
|
||||
automation: {
|
||||
tenant: "qa",
|
||||
skills: ["testing"],
|
||||
workspace: {
|
||||
kind: "worktree",
|
||||
sourcePath: "/repo",
|
||||
sourceBranch: "main",
|
||||
},
|
||||
workspaceAccess: {
|
||||
unrestricted: false,
|
||||
roots: ["/repo"],
|
||||
writable: true,
|
||||
},
|
||||
dispatchCount: 2,
|
||||
lastDispatchAt: 20,
|
||||
},
|
||||
claim: { token: "[redacted]" },
|
||||
diagnostics: [{ actions: [{ kind: "add_proof", label: "Add proof" }] }],
|
||||
notifications: [{ sequence: 3 }],
|
||||
});
|
||||
expect(getWorkboardState(host).cards[0]?.metadata?.diagnostics).toHaveLength(1);
|
||||
expect(getWorkboardState(host).cards[0]?.metadata?.notifications).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("updates cards from draft state when editing", async () => {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { isRecord } from "./normalization-utils.ts";
|
||||
import type {
|
||||
WorkboardAutomation,
|
||||
WorkboardDiagnosticAction,
|
||||
WorkboardWorkspace,
|
||||
WorkboardWorkspaceAccess,
|
||||
} from "./types.ts";
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === "string" && entry.trim() !== "")
|
||||
: [];
|
||||
}
|
||||
|
||||
function normalizeWorkspaceAccess(value: unknown): WorkboardWorkspaceAccess | undefined {
|
||||
if (!isRecord(value) || typeof value.unrestricted !== "boolean") {
|
||||
return undefined;
|
||||
}
|
||||
if (value.unrestricted) {
|
||||
return { unrestricted: true };
|
||||
}
|
||||
if (!Array.isArray(value.roots) || typeof value.writable !== "boolean") {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
unrestricted: false,
|
||||
roots: value.roots.filter((root): root is string => typeof root === "string"),
|
||||
writable: value.writable,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeAutomation(value: unknown): WorkboardAutomation | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const workspace = isRecord(value.workspace)
|
||||
? {
|
||||
kind:
|
||||
value.workspace.kind === "scratch" ||
|
||||
value.workspace.kind === "dir" ||
|
||||
value.workspace.kind === "worktree"
|
||||
? value.workspace.kind
|
||||
: undefined,
|
||||
...(typeof value.workspace.path === "string" ? { path: value.workspace.path } : {}),
|
||||
...(typeof value.workspace.branch === "string" ? { branch: value.workspace.branch } : {}),
|
||||
...(typeof value.workspace.sourcePath === "string"
|
||||
? { sourcePath: value.workspace.sourcePath }
|
||||
: {}),
|
||||
...(typeof value.workspace.sourceBranch === "string"
|
||||
? { sourceBranch: value.workspace.sourceBranch }
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
const workspaceAccess = normalizeWorkspaceAccess(value.workspaceAccess);
|
||||
const skills = normalizeStringArray(value.skills);
|
||||
const createdCardIds = normalizeStringArray(value.createdCardIds);
|
||||
const automation: WorkboardAutomation = {
|
||||
...(typeof value.tenant === "string" ? { tenant: value.tenant } : {}),
|
||||
...(typeof value.boardId === "string" ? { boardId: value.boardId } : {}),
|
||||
...(typeof value.createdByCardId === "string"
|
||||
? { createdByCardId: value.createdByCardId }
|
||||
: {}),
|
||||
...(typeof value.idempotencyKey === "string" ? { idempotencyKey: value.idempotencyKey } : {}),
|
||||
...(skills.length ? { skills } : {}),
|
||||
...(workspace?.kind ? { workspace: workspace as WorkboardWorkspace } : {}),
|
||||
...(workspaceAccess ? { workspaceAccess } : {}),
|
||||
...(typeof value.maxRuntimeSeconds === "number"
|
||||
? { maxRuntimeSeconds: value.maxRuntimeSeconds }
|
||||
: {}),
|
||||
...(typeof value.maxRetries === "number" ? { maxRetries: value.maxRetries } : {}),
|
||||
...(typeof value.scheduledAt === "number" ? { scheduledAt: value.scheduledAt } : {}),
|
||||
...(typeof value.summary === "string" ? { summary: value.summary } : {}),
|
||||
...(createdCardIds.length ? { createdCardIds } : {}),
|
||||
...(typeof value.dispatchCount === "number" ? { dispatchCount: value.dispatchCount } : {}),
|
||||
...(typeof value.lastDispatchAt === "number" ? { lastDispatchAt: value.lastDispatchAt } : {}),
|
||||
};
|
||||
return Object.keys(automation).length ? automation : undefined;
|
||||
}
|
||||
|
||||
export function normalizeDiagnosticAction(value: unknown): WorkboardDiagnosticAction | null {
|
||||
if (
|
||||
!isRecord(value) ||
|
||||
(value.kind !== "claim" &&
|
||||
value.kind !== "unblock" &&
|
||||
value.kind !== "promote" &&
|
||||
value.kind !== "reclaim" &&
|
||||
value.kind !== "reassign" &&
|
||||
value.kind !== "add_proof" &&
|
||||
value.kind !== "open_session") ||
|
||||
typeof value.label !== "string"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return { kind: value.kind, label: value.label };
|
||||
}
|
||||
@@ -1,21 +1,29 @@
|
||||
import {
|
||||
normalizeAutomation,
|
||||
normalizeDiagnosticAction,
|
||||
} from "./metadata-contract-normalization.ts";
|
||||
import { isRecord } from "./normalization-utils.ts";
|
||||
import {
|
||||
WORKBOARD_ATTEMPT_STATUSES,
|
||||
WORKBOARD_DIAGNOSTIC_KINDS,
|
||||
WORKBOARD_DIAGNOSTIC_SEVERITIES,
|
||||
WORKBOARD_EVENT_KINDS,
|
||||
WORKBOARD_EXECUTION_ENGINES,
|
||||
WORKBOARD_EXECUTION_MODES,
|
||||
WORKBOARD_EXECUTION_STATUSES,
|
||||
WORKBOARD_LINK_TYPES,
|
||||
WORKBOARD_NOTIFICATION_KINDS,
|
||||
WORKBOARD_PROOF_STATUSES,
|
||||
WORKBOARD_STATUSES,
|
||||
WORKBOARD_TEMPLATE_IDS,
|
||||
type WorkboardArtifact,
|
||||
type WorkboardAttachment,
|
||||
type WorkboardAttemptStatus,
|
||||
type WorkboardAutomation,
|
||||
type WorkboardClaim,
|
||||
type WorkboardComment,
|
||||
type WorkboardDiagnostic,
|
||||
type WorkboardDiagnosticAction,
|
||||
type WorkboardDiagnosticKind,
|
||||
type WorkboardDiagnosticSeverity,
|
||||
type WorkboardEvent,
|
||||
type WorkboardEventKind,
|
||||
@@ -27,6 +35,7 @@ import {
|
||||
type WorkboardLinkType,
|
||||
type WorkboardMetadata,
|
||||
type WorkboardNotification,
|
||||
type WorkboardNotificationKind,
|
||||
type WorkboardProof,
|
||||
type WorkboardProofStatus,
|
||||
type WorkboardRunAttempt,
|
||||
@@ -34,7 +43,6 @@ import {
|
||||
type WorkboardTemplateId,
|
||||
type WorkboardWorkerLog,
|
||||
type WorkboardWorkerProtocol,
|
||||
type WorkboardWorkspace,
|
||||
} from "./types.ts";
|
||||
|
||||
export function normalizeExecution(value: unknown): WorkboardExecution | undefined {
|
||||
@@ -106,12 +114,6 @@ export function normalizeEvents(value: unknown): WorkboardEvent[] {
|
||||
: [];
|
||||
}
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === "string" && entry.trim() !== "")
|
||||
: [];
|
||||
}
|
||||
|
||||
function normalizeWorkerProtocolState(
|
||||
value: unknown,
|
||||
): WorkboardWorkerProtocol["state"] | undefined {
|
||||
@@ -124,48 +126,6 @@ function normalizeWorkerProtocolState(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function normalizeAutomation(value: unknown): WorkboardAutomation | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const workspace = isRecord(value.workspace)
|
||||
? {
|
||||
kind:
|
||||
value.workspace.kind === "scratch" ||
|
||||
value.workspace.kind === "dir" ||
|
||||
value.workspace.kind === "worktree"
|
||||
? value.workspace.kind
|
||||
: undefined,
|
||||
...(typeof value.workspace.path === "string" ? { path: value.workspace.path } : {}),
|
||||
...(typeof value.workspace.branch === "string" ? { branch: value.workspace.branch } : {}),
|
||||
}
|
||||
: undefined;
|
||||
const automation: WorkboardAutomation = {
|
||||
...(typeof value.tenant === "string" ? { tenant: value.tenant } : {}),
|
||||
...(typeof value.boardId === "string" ? { boardId: value.boardId } : {}),
|
||||
...(typeof value.createdByCardId === "string"
|
||||
? { createdByCardId: value.createdByCardId }
|
||||
: {}),
|
||||
...(typeof value.idempotencyKey === "string" ? { idempotencyKey: value.idempotencyKey } : {}),
|
||||
...(normalizeStringArray(value.skills).length
|
||||
? { skills: normalizeStringArray(value.skills) }
|
||||
: {}),
|
||||
...(workspace?.kind ? { workspace: workspace as WorkboardWorkspace } : {}),
|
||||
...(typeof value.maxRuntimeSeconds === "number"
|
||||
? { maxRuntimeSeconds: value.maxRuntimeSeconds }
|
||||
: {}),
|
||||
...(typeof value.maxRetries === "number" ? { maxRetries: value.maxRetries } : {}),
|
||||
...(typeof value.scheduledAt === "number" ? { scheduledAt: value.scheduledAt } : {}),
|
||||
...(typeof value.summary === "string" ? { summary: value.summary } : {}),
|
||||
...(normalizeStringArray(value.createdCardIds).length
|
||||
? { createdCardIds: normalizeStringArray(value.createdCardIds) }
|
||||
: {}),
|
||||
...(typeof value.dispatchCount === "number" ? { dispatchCount: value.dispatchCount } : {}),
|
||||
...(typeof value.lastDispatchAt === "number" ? { lastDispatchAt: value.lastDispatchAt } : {}),
|
||||
};
|
||||
return Object.keys(automation).length ? automation : undefined;
|
||||
}
|
||||
|
||||
export function normalizeMetadata(value: unknown): WorkboardMetadata | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
@@ -354,34 +314,48 @@ export function normalizeMetadata(value: unknown): WorkboardMetadata | undefined
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
const claim = isRecord(value.claim)
|
||||
? {
|
||||
ownerId: typeof value.claim.ownerId === "string" ? value.claim.ownerId : "",
|
||||
...(typeof value.claim.token === "string" ? { token: value.claim.token } : {}),
|
||||
claimedAt: typeof value.claim.claimedAt === "number" ? value.claim.claimedAt : 0,
|
||||
lastHeartbeatAt:
|
||||
typeof value.claim.lastHeartbeatAt === "number" ? value.claim.lastHeartbeatAt : 0,
|
||||
...(typeof value.claim.expiresAt === "number" ? { expiresAt: value.claim.expiresAt } : {}),
|
||||
}
|
||||
: undefined;
|
||||
const claim: WorkboardClaim | undefined =
|
||||
isRecord(value.claim) &&
|
||||
typeof value.claim.ownerId === "string" &&
|
||||
typeof value.claim.token === "string" &&
|
||||
typeof value.claim.claimedAt === "number" &&
|
||||
typeof value.claim.lastHeartbeatAt === "number"
|
||||
? {
|
||||
ownerId: value.claim.ownerId,
|
||||
token: value.claim.token,
|
||||
claimedAt: value.claim.claimedAt,
|
||||
lastHeartbeatAt: value.claim.lastHeartbeatAt,
|
||||
...(typeof value.claim.expiresAt === "number"
|
||||
? { expiresAt: value.claim.expiresAt }
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
const diagnostics = Array.isArray(value.diagnostics)
|
||||
? value.diagnostics.flatMap((entry): WorkboardDiagnostic[] => {
|
||||
if (!isRecord(entry) || typeof entry.kind !== "string" || typeof entry.title !== "string") {
|
||||
if (
|
||||
!isRecord(entry) ||
|
||||
!WORKBOARD_DIAGNOSTIC_KINDS.includes(entry.kind as WorkboardDiagnosticKind) ||
|
||||
!WORKBOARD_DIAGNOSTIC_SEVERITIES.includes(
|
||||
entry.severity as WorkboardDiagnosticSeverity,
|
||||
) ||
|
||||
typeof entry.title !== "string"
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
kind: entry.kind,
|
||||
severity: WORKBOARD_DIAGNOSTIC_SEVERITIES.includes(
|
||||
entry.severity as WorkboardDiagnosticSeverity,
|
||||
)
|
||||
? (entry.severity as WorkboardDiagnosticSeverity)
|
||||
: "warning",
|
||||
kind: entry.kind as WorkboardDiagnosticKind,
|
||||
severity: entry.severity as WorkboardDiagnosticSeverity,
|
||||
title: entry.title,
|
||||
detail: typeof entry.detail === "string" ? entry.detail : entry.title,
|
||||
firstSeenAt: typeof entry.firstSeenAt === "number" ? entry.firstSeenAt : Date.now(),
|
||||
lastSeenAt: typeof entry.lastSeenAt === "number" ? entry.lastSeenAt : Date.now(),
|
||||
count: typeof entry.count === "number" ? entry.count : 1,
|
||||
actions: Array.isArray(entry.actions)
|
||||
? entry.actions
|
||||
.map(normalizeDiagnosticAction)
|
||||
.filter((action): action is WorkboardDiagnosticAction => action !== null)
|
||||
: [],
|
||||
},
|
||||
];
|
||||
})
|
||||
@@ -391,7 +365,7 @@ export function normalizeMetadata(value: unknown): WorkboardMetadata | undefined
|
||||
if (
|
||||
!isRecord(entry) ||
|
||||
typeof entry.id !== "string" ||
|
||||
typeof entry.kind !== "string" ||
|
||||
!WORKBOARD_NOTIFICATION_KINDS.includes(entry.kind as WorkboardNotificationKind) ||
|
||||
typeof entry.message !== "string" ||
|
||||
typeof entry.createdAt !== "number"
|
||||
) {
|
||||
@@ -400,9 +374,10 @@ export function normalizeMetadata(value: unknown): WorkboardMetadata | undefined
|
||||
return [
|
||||
{
|
||||
id: entry.id,
|
||||
kind: entry.kind,
|
||||
kind: entry.kind as WorkboardNotificationKind,
|
||||
message: entry.message,
|
||||
createdAt: entry.createdAt,
|
||||
...(typeof entry.sequence === "number" ? { sequence: entry.sequence } : {}),
|
||||
...(typeof entry.sessionKey === "string" ? { sessionKey: entry.sessionKey } : {}),
|
||||
...(typeof entry.runId === "string" ? { runId: entry.runId } : {}),
|
||||
},
|
||||
@@ -438,7 +413,7 @@ export function normalizeMetadata(value: unknown): WorkboardMetadata | undefined
|
||||
...(workerLogs.length ? { workerLogs } : {}),
|
||||
...(workerProtocol ? { workerProtocol } : {}),
|
||||
...(automation ? { automation } : {}),
|
||||
...(claim?.ownerId && claim.claimedAt ? { claim } : {}),
|
||||
...(claim ? { claim } : {}),
|
||||
...(diagnostics.length ? { diagnostics } : {}),
|
||||
...(notifications.length ? { notifications } : {}),
|
||||
...(WORKBOARD_TEMPLATE_IDS.includes(value.templateId as WorkboardTemplateId)
|
||||
|
||||
@@ -1,275 +1,12 @@
|
||||
import type {
|
||||
WorkboardCard,
|
||||
WorkboardPriority,
|
||||
WorkboardStatus,
|
||||
WorkboardTemplateId,
|
||||
} from "@openclaw/workboard-contract";
|
||||
import type { GatewaySessionRow } from "../../api/types.ts";
|
||||
|
||||
export const WORKBOARD_STATUSES = [
|
||||
"triage",
|
||||
"backlog",
|
||||
"todo",
|
||||
"scheduled",
|
||||
"ready",
|
||||
"running",
|
||||
"review",
|
||||
"blocked",
|
||||
"done",
|
||||
] as const;
|
||||
|
||||
export const WORKBOARD_PRIORITIES = ["low", "normal", "high", "urgent"] as const;
|
||||
export const WORKBOARD_EXECUTION_ENGINES = ["codex", "claude"] as const;
|
||||
export const WORKBOARD_EXECUTION_MODES = ["autonomous", "manual"] as const;
|
||||
export const WORKBOARD_EXECUTION_STATUSES = [
|
||||
"idle",
|
||||
"running",
|
||||
"review",
|
||||
"blocked",
|
||||
"done",
|
||||
] as const;
|
||||
export const WORKBOARD_EVENT_KINDS = [
|
||||
"created",
|
||||
"edited",
|
||||
"moved",
|
||||
"linked",
|
||||
"specified",
|
||||
"decomposed",
|
||||
"claimed",
|
||||
"heartbeat",
|
||||
"execution_updated",
|
||||
"attempt_started",
|
||||
"attempt_updated",
|
||||
"comment_added",
|
||||
"link_added",
|
||||
"proof_added",
|
||||
"artifact_added",
|
||||
"attachment_added",
|
||||
"diagnostic",
|
||||
"notification",
|
||||
"dispatch",
|
||||
"orchestration",
|
||||
"protocol_violation",
|
||||
"archived",
|
||||
"unarchived",
|
||||
"stale",
|
||||
] as const;
|
||||
export const WORKBOARD_ATTEMPT_STATUSES = [
|
||||
"running",
|
||||
"succeeded",
|
||||
"failed",
|
||||
"blocked",
|
||||
"stopped",
|
||||
] as const;
|
||||
export const WORKBOARD_LINK_TYPES = [
|
||||
"parent",
|
||||
"child",
|
||||
"blocks",
|
||||
"blocked_by",
|
||||
"relates_to",
|
||||
] as const;
|
||||
export const WORKBOARD_PROOF_STATUSES = ["passed", "failed", "skipped", "unknown"] as const;
|
||||
export const WORKBOARD_TEMPLATE_IDS = ["bugfix", "docs", "release", "pr_review", "plugin"] as const;
|
||||
export const WORKBOARD_DIAGNOSTIC_SEVERITIES = ["warning", "error", "critical"] as const;
|
||||
|
||||
export type WorkboardStatus = (typeof WORKBOARD_STATUSES)[number];
|
||||
export type WorkboardPriority = (typeof WORKBOARD_PRIORITIES)[number];
|
||||
export type WorkboardExecutionEngine = (typeof WORKBOARD_EXECUTION_ENGINES)[number];
|
||||
export type WorkboardExecutionMode = (typeof WORKBOARD_EXECUTION_MODES)[number];
|
||||
export type WorkboardExecutionStatus = (typeof WORKBOARD_EXECUTION_STATUSES)[number];
|
||||
export type WorkboardEventKind = (typeof WORKBOARD_EVENT_KINDS)[number];
|
||||
export type WorkboardAttemptStatus = (typeof WORKBOARD_ATTEMPT_STATUSES)[number];
|
||||
export type WorkboardLinkType = (typeof WORKBOARD_LINK_TYPES)[number];
|
||||
export type WorkboardProofStatus = (typeof WORKBOARD_PROOF_STATUSES)[number];
|
||||
export type WorkboardTemplateId = (typeof WORKBOARD_TEMPLATE_IDS)[number];
|
||||
export type WorkboardDiagnosticSeverity = (typeof WORKBOARD_DIAGNOSTIC_SEVERITIES)[number];
|
||||
|
||||
export type WorkboardExecution = {
|
||||
id: string;
|
||||
kind: "agent-session";
|
||||
engine: WorkboardExecutionEngine;
|
||||
mode: WorkboardExecutionMode;
|
||||
status: WorkboardExecutionStatus;
|
||||
model: string;
|
||||
sessionKey?: string;
|
||||
runId?: string;
|
||||
startedAt: number;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
export type WorkboardEvent = {
|
||||
id: string;
|
||||
kind: WorkboardEventKind;
|
||||
at: number;
|
||||
fromStatus?: WorkboardStatus;
|
||||
toStatus?: WorkboardStatus;
|
||||
sessionKey?: string;
|
||||
runId?: string;
|
||||
};
|
||||
|
||||
export type WorkboardRunAttempt = {
|
||||
id: string;
|
||||
status: WorkboardAttemptStatus;
|
||||
startedAt: number;
|
||||
endedAt?: number;
|
||||
engine?: WorkboardExecutionEngine;
|
||||
mode?: WorkboardExecutionMode;
|
||||
model?: string;
|
||||
sessionKey?: string;
|
||||
runId?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type WorkboardComment = {
|
||||
id: string;
|
||||
body: string;
|
||||
createdAt: number;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
export type WorkboardLink = {
|
||||
id: string;
|
||||
type: WorkboardLinkType;
|
||||
createdAt: number;
|
||||
targetCardId?: string;
|
||||
title?: string;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export type WorkboardProof = {
|
||||
id: string;
|
||||
status: WorkboardProofStatus;
|
||||
createdAt: number;
|
||||
label?: string;
|
||||
command?: string;
|
||||
url?: string;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type WorkboardStaleState = {
|
||||
detectedAt: number;
|
||||
lastSessionUpdatedAt?: number;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
type WorkboardClaim = {
|
||||
ownerId: string;
|
||||
token?: string;
|
||||
claimedAt: number;
|
||||
lastHeartbeatAt: number;
|
||||
expiresAt?: number;
|
||||
};
|
||||
|
||||
export type WorkboardArtifact = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
label?: string;
|
||||
url?: string;
|
||||
path?: string;
|
||||
mimeType?: string;
|
||||
};
|
||||
|
||||
export type WorkboardAttachment = {
|
||||
id: string;
|
||||
cardId: string;
|
||||
createdAt: number;
|
||||
fileName: string;
|
||||
byteSize: number;
|
||||
mimeType?: string;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type WorkboardWorkerLog = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
level: "info" | "warning" | "error";
|
||||
message: string;
|
||||
sessionKey?: string;
|
||||
runId?: string;
|
||||
};
|
||||
|
||||
export type WorkboardWorkerProtocol = {
|
||||
state: "idle" | "running" | "completed" | "blocked" | "violated";
|
||||
updatedAt: number;
|
||||
detail?: string;
|
||||
};
|
||||
|
||||
export type WorkboardDiagnostic = {
|
||||
kind: string;
|
||||
severity: WorkboardDiagnosticSeverity;
|
||||
title: string;
|
||||
detail: string;
|
||||
firstSeenAt: number;
|
||||
lastSeenAt: number;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type WorkboardNotification = {
|
||||
id: string;
|
||||
kind: string;
|
||||
createdAt: number;
|
||||
message: string;
|
||||
sessionKey?: string;
|
||||
runId?: string;
|
||||
};
|
||||
|
||||
export type WorkboardWorkspace = {
|
||||
kind: "scratch" | "dir" | "worktree";
|
||||
path?: string;
|
||||
branch?: string;
|
||||
};
|
||||
|
||||
export type WorkboardAutomation = {
|
||||
tenant?: string;
|
||||
boardId?: string;
|
||||
createdByCardId?: string;
|
||||
idempotencyKey?: string;
|
||||
skills?: string[];
|
||||
workspace?: WorkboardWorkspace;
|
||||
maxRuntimeSeconds?: number;
|
||||
maxRetries?: number;
|
||||
scheduledAt?: number;
|
||||
summary?: string;
|
||||
createdCardIds?: string[];
|
||||
dispatchCount?: number;
|
||||
lastDispatchAt?: number;
|
||||
};
|
||||
|
||||
export type WorkboardMetadata = {
|
||||
attempts?: WorkboardRunAttempt[];
|
||||
comments?: WorkboardComment[];
|
||||
links?: WorkboardLink[];
|
||||
proof?: WorkboardProof[];
|
||||
artifacts?: WorkboardArtifact[];
|
||||
attachments?: WorkboardAttachment[];
|
||||
workerLogs?: WorkboardWorkerLog[];
|
||||
workerProtocol?: WorkboardWorkerProtocol;
|
||||
automation?: WorkboardAutomation;
|
||||
claim?: WorkboardClaim;
|
||||
diagnostics?: WorkboardDiagnostic[];
|
||||
notifications?: WorkboardNotification[];
|
||||
templateId?: WorkboardTemplateId;
|
||||
archivedAt?: number;
|
||||
stale?: WorkboardStaleState;
|
||||
lifecycleStatusSourceUpdatedAt?: number;
|
||||
failureCount?: number;
|
||||
};
|
||||
|
||||
export type WorkboardCard = {
|
||||
id: string;
|
||||
title: string;
|
||||
notes?: string;
|
||||
status: WorkboardStatus;
|
||||
priority: WorkboardPriority;
|
||||
labels: string[];
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
runId?: string;
|
||||
taskId?: string;
|
||||
sourceUrl?: string;
|
||||
execution?: WorkboardExecution;
|
||||
position: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
startedAt?: number;
|
||||
completedAt?: number;
|
||||
events?: WorkboardEvent[];
|
||||
metadata?: WorkboardMetadata;
|
||||
};
|
||||
export * from "@openclaw/workboard-contract";
|
||||
|
||||
type WorkboardLifecycleState =
|
||||
| "unlinked"
|
||||
|
||||
@@ -535,16 +535,22 @@ describe("renderWorkboard", () => {
|
||||
updatedAt: 1,
|
||||
metadata: {
|
||||
attempts: [{ id: "attempt-1", status: "failed", startedAt: 1 }],
|
||||
claim: { ownerId: "agent-1", claimedAt: 1, lastHeartbeatAt: Date.now() },
|
||||
claim: {
|
||||
ownerId: "agent-1",
|
||||
token: "[redacted]",
|
||||
claimedAt: 1,
|
||||
lastHeartbeatAt: Date.now(),
|
||||
},
|
||||
diagnostics: [
|
||||
{
|
||||
kind: "protocol_violation",
|
||||
kind: "orphaned_session",
|
||||
severity: "warning",
|
||||
title: "Old diagnostic",
|
||||
detail: "Older detail.",
|
||||
firstSeenAt: 1,
|
||||
lastSeenAt: 1,
|
||||
count: 1,
|
||||
actions: [],
|
||||
},
|
||||
{
|
||||
kind: "repeated_failures",
|
||||
@@ -554,6 +560,7 @@ describe("renderWorkboard", () => {
|
||||
firstSeenAt: 1,
|
||||
lastSeenAt: 2,
|
||||
count: 1,
|
||||
actions: [],
|
||||
},
|
||||
],
|
||||
notifications: [{ id: "note-1", kind: "failed", createdAt: 1, message: "Needs proof." }],
|
||||
@@ -600,13 +607,14 @@ describe("renderWorkboard", () => {
|
||||
metadata: {
|
||||
diagnostics: [
|
||||
{
|
||||
kind: "protocol_violation",
|
||||
kind: "orphaned_session",
|
||||
severity: "warning",
|
||||
title: `${"x".repeat(62)}🚀tail`,
|
||||
detail: "Boundary detail.",
|
||||
firstSeenAt: 1,
|
||||
lastSeenAt: 1,
|
||||
count: 1,
|
||||
actions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -651,6 +659,7 @@ describe("renderWorkboard", () => {
|
||||
metadata: {
|
||||
claim: {
|
||||
ownerId: "agent-1",
|
||||
token: "[redacted]",
|
||||
claimedAt: 1,
|
||||
lastHeartbeatAt: Date.now() - 42_000,
|
||||
},
|
||||
|
||||
@@ -282,6 +282,7 @@ export function resolveSourcePackageAliasesForVite(): ControlUiViteAlias[] {
|
||||
sourcePackageAlias("normalization-core", "string-normalization"),
|
||||
sourcePackageAlias("normalization-core", "utf16-slice"),
|
||||
sourcePackageAlias("normalization-core"),
|
||||
sourcePackageAlias("workboard-contract"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ const workspaceSourceAliases = [
|
||||
find: "@openclaw/media-core",
|
||||
replacement: path.resolve(repoRoot, "packages/media-core/src/index.ts"),
|
||||
},
|
||||
{
|
||||
find: "@openclaw/workboard-contract",
|
||||
replacement: path.resolve(repoRoot, "packages/workboard-contract/src/index.ts"),
|
||||
},
|
||||
{
|
||||
find: /^@openclaw\/net-policy\/(.+)$/u,
|
||||
replacement: path.resolve(repoRoot, "packages/net-policy/src/$1"),
|
||||
|
||||
Reference in New Issue
Block a user