diff --git a/CHANGELOG.md b/CHANGELOG.md index 5546101c..8beda4e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `@useorgx/openclaw-plugin` are documented in this file. +## 0.7.28 - 2026-03-13 + +### Release Management +- Patch release bump for npm package + plugin manifest metadata. +- Published release tag `v0.7.28`. + +### Plugin Dispatch Bridge +- Added dispatch job reporting to the OrgX server so plugin-driven autonomous work can surface its execution state in the control plane. +- Added plugin-side preflight checks before dispatching work so invalid or incomplete launches fail fast with actionable server-visible diagnostics. + +### QA Tooling +- Added the reusable Mission Control QA harness script for button/card/state audits. +- Ignored generated QA audit artifacts and scratch markdown outputs while keeping the harness itself tracked in the repo. + ## 0.7.27 - 2026-03-12 ### Release Management diff --git a/README.md b/README.md index 57b88e79..ec4f4518 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,9 @@ The plugin currently registers **30 MCP tools** from `src/tools/core-tools.ts`. Core sync/reporting: - `orgx_status` - `orgx_sync` +- `orgx_query_org_memory` +- `orgx_recommend_next_action` +- `orgx_get_morning_brief` - `orgx_emit_activity` - `orgx_report_progress` - `orgx_register_artifact` @@ -195,7 +198,7 @@ Quality, proof, and outcomes: - `orgx_proof_status` - `orgx_verify_completion` - `orgx_record_outcome` -- `orgx_get_outcome_attribution` +- `orgx_get_outcome_attribution` (compatibility alias) Entity and stream management: - `orgx_create_entity` diff --git a/docs/plugin-feature-index.md b/docs/plugin-feature-index.md index 4609b66b..a6c2473e 100644 --- a/docs/plugin-feature-index.md +++ b/docs/plugin-feature-index.md @@ -35,11 +35,14 @@ Scope: current repository state in `src/` and `dashboard/src/` Source of truth: `src/tools/core-tools.ts` (registered by `registerCoreTools`). -Current count: **27 tools** +Current count: **30 tools** - `orgx_status` - `orgx_sentinel_catalog` - `orgx_sync` +- `orgx_query_org_memory` +- `orgx_recommend_next_action` +- `orgx_get_morning_brief` - `orgx_delegation_preflight` - `orgx_run_action` - `orgx_checkpoints_list` @@ -49,7 +52,7 @@ Current count: **27 tools** - `orgx_proof_status` - `orgx_verify_completion` - `orgx_record_outcome` -- `orgx_get_outcome_attribution` +- `orgx_get_outcome_attribution` (compatibility alias) - `orgx_create_entity` - `orgx_update_entity` - `orgx_reassign_stream` diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 781cf1a1..1b0cd752 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,7 +1,7 @@ { "id": "openclaw-plugin", "name": "OrgX Integration", - "version": "0.7.27", + "version": "0.7.28", "description": "Connects Clawdbot to OrgX for agent orchestration, quality gates, and model routing", "entry": "./dist/index.js", "author": "OrgX Team", diff --git a/package-lock.json b/package-lock.json index 2fdfd30f..319c0acb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@useorgx/openclaw-plugin", - "version": "0.7.27", + "version": "0.7.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@useorgx/openclaw-plugin", - "version": "0.7.27", + "version": "0.7.28", "license": "MIT", "dependencies": { "better-sqlite3": "^11.10.0" diff --git a/package.json b/package.json index ebc711d3..72e78c58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@useorgx/openclaw-plugin", - "version": "0.7.27", + "version": "0.7.28", "description": "OrgX plugin for OpenClaw — agent orchestration, quality gates, model routing, and live dashboard", "type": "module", "main": "./dist/index.js", diff --git a/src/agent-suite.ts b/src/agent-suite.ts index 0e151fa8..66907106 100644 --- a/src/agent-suite.ts +++ b/src/agent-suite.ts @@ -393,6 +393,9 @@ function buildManagedFileContent(input: { engineering: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -407,6 +410,9 @@ function buildManagedFileContent(input: { product: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -421,6 +427,9 @@ function buildManagedFileContent(input: { design: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -435,6 +444,9 @@ function buildManagedFileContent(input: { marketing: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -449,6 +461,9 @@ function buildManagedFileContent(input: { sales: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -463,6 +478,9 @@ function buildManagedFileContent(input: { operations: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -479,6 +497,9 @@ function buildManagedFileContent(input: { orchestration: [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "orgx_emit_activity", "orgx_report_progress", "orgx_register_artifact", @@ -500,6 +521,9 @@ function buildManagedFileContent(input: { "Primary tool surface (OrgX MCP tools exposed by this plugin):", "- orgx_status", "- orgx_sync", + "- orgx_query_org_memory", + "- orgx_recommend_next_action", + "- orgx_get_morning_brief", "- orgx_emit_activity", "- orgx_apply_changeset", "- orgx_register_artifact", @@ -508,7 +532,7 @@ function buildManagedFileContent(input: { "- orgx_quality_score", "- orgx_proof_status", "- orgx_record_outcome", - "- orgx_get_outcome_attribution", + "- orgx_get_outcome_attribution (compatibility alias for older ROI workflows)", "- orgx_verify_completion", "- orgx_reassign_stream", "", diff --git a/src/contracts/client.ts b/src/contracts/client.ts index 012d61df..48751a7e 100644 --- a/src/contracts/client.ts +++ b/src/contracts/client.ts @@ -33,6 +33,11 @@ import type { BillingStatus, BillingCheckoutRequest, BillingUrlResult, + OrgMemoryQueryRequest, + OrgMemoryQueryResponse, + RecommendNextActionRequest, + RecommendNextActionResponse, + MorningBriefResponse, UsageControlPlaneSummary, KickoffContextRequest, KickoffContextResponse, @@ -266,6 +271,34 @@ export class OrgXClient { return this.request("PATCH", path, body); } + private async executeClientTool( + toolId: "query_org_memory" | "recommend_next_action", + args: Record, + context?: { + initiativeId?: string | null; + projectId?: string | null; + runId?: string | null; + } + ): Promise { + const response = await this.post<{ + ok: boolean; + data?: T; + error?: string; + }>("/api/client/tools/execute", { + tool_id: toolId, + args, + initiative_id: context?.initiativeId ?? undefined, + project_id: context?.projectId ?? undefined, + run_id: context?.runId ?? undefined, + }); + + if (!response.ok) { + throw new Error(response.error ?? `Client tool ${toolId} failed`); + } + + return response.data as T; + } + private buildQuery(params: Record): string { const entries = Object.entries(params).filter(([, value]) => value !== undefined && value !== null); if (entries.length === 0) return ""; @@ -731,6 +764,54 @@ export class OrgXClient { >("/api/usage/forecast"); } + async queryOrgMemory( + request: OrgMemoryQueryRequest + ): Promise { + return this.executeClientTool( + "query_org_memory", + { + query: request.query, + scope: request.scope, + limit: request.limit, + } + ); + } + + async recommendNextAction( + request: RecommendNextActionRequest + ): Promise { + return this.executeClientTool( + "recommend_next_action", + { + entity_type: request.entity_type, + entity_id: request.entity_id, + workspace_id: request.workspace_id, + command_center_id: request.command_center_id, + limit: request.limit, + cascade: request.cascade, + }, + { + initiativeId: + request.entity_type === "initiative" ? request.entity_id ?? null : null, + } + ); + } + + async getMorningBrief(params: { + workspace_id: string; + session_id?: string; + }): Promise { + const search = new URLSearchParams({ + workspace_id: params.workspace_id, + }); + if (params.session_id) { + search.set("session_id", params.session_id); + } + return this.get( + `/api/flywheel/briefs?${search.toString()}` + ); + } + // =========================================================================== // Reporting Control Plane // =========================================================================== diff --git a/src/contracts/types.ts b/src/contracts/types.ts index 54a3637d..cf24cec4 100644 --- a/src/contracts/types.ts +++ b/src/contracts/types.ts @@ -409,6 +409,68 @@ export interface BillingUrlResult { checkout_url?: string | null; } +// ============================================================================= +// Preferred MCP Read Surfaces +// ============================================================================= + +export type OrgMemoryScope = 'all' | 'artifacts' | 'decisions' | 'initiatives'; + +export interface OrgMemoryQueryRequest { + query: string; + scope?: OrgMemoryScope; + limit?: number; +} + +export interface OrgMemoryQueryResponse { + query: string; + scope: OrgMemoryScope; + plan?: string | null; + retention_window_days?: number | null; + retention_applied?: boolean; + results: Array<{ + id: string; + type: string; + title: string; + summary?: string | null; + relevance_score?: number | null; + created_at?: string | null; + }>; + total_found: number; + message?: string; + suggested_followups?: string[]; +} + +export interface RecommendNextActionRequest { + entity_type?: 'workspace' | 'initiative' | 'workstream' | 'milestone'; + entity_id?: string; + workspace_id?: string; + command_center_id?: string; + limit?: number; + cascade?: boolean; +} + +export interface RecommendNextActionResponse { + entity_type: string; + entity_id?: string | null; + workspace_id?: string | null; + command_center_id?: string | null; + recommendations: Array>; + next_action?: Record | null; + capability_gaps?: Array>; + message?: string; +} + +export interface MorningBriefResponse { + workspace_id: string; + session_summary: Record | null; + intelligence?: Record; + exceptions?: Array>; + trust_events?: Array>; + top_receipts?: Array>; + brief_markdown?: string | null; + message?: string; +} + // ============================================================================= // Usage Control Plane (Actual vs Forecast) // ============================================================================= diff --git a/src/mcp-http-handler.ts b/src/mcp-http-handler.ts index 0314193a..7dbe254c 100644 --- a/src/mcp-http-handler.ts +++ b/src/mcp-http-handler.ts @@ -75,6 +75,9 @@ type ToolScope = { export const ORGX_BASE_TOOLS = [ "orgx_status", "orgx_sync", + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", "list_agent_configs", "get_agent_config", "orgx_emit_activity", diff --git a/src/tools/core-tools.ts b/src/tools/core-tools.ts index cfb0d7eb..6596878f 100644 --- a/src/tools/core-tools.ts +++ b/src/tools/core-tools.ts @@ -278,6 +278,155 @@ export function registerCoreTools(deps: RegisterCoreToolsDeps): Map { + const { registerCoreTools } = await import("../../dist/tools/core-tools.js"); + + const deps = { + registerTool: () => {}, + client: { + syncMemory: async () => ({}), + queryOrgMemory: async () => ({ results: [] }), + recommendNextAction: async () => ({ recommendations: [] }), + getMorningBrief: async () => ({ workspace_id: "ws_123", session_summary: null }), + checkSpawnGuard: async () => ({ ok: true, allowed: true, modelTier: "sonnet", checks: {} }), + createEntity: async () => ({}), + updateEntity: async () => ({}), + updateEntityDetailed: async () => ({ entity: {} }), + listEntities: async () => ({ data: [] }), + emitActivity: async () => ({}), + applyChangeset: async () => ({ applied_count: 1, replayed: false, run_id: "run" }), + rawRequest: async () => ({}), + }, + config: { syncIntervalMs: 10_000, pluginVersion: "test" }, + getCachedSnapshot: () => null, + getLastSnapshotAt: () => 0, + doSync: async () => {}, + text: (v) => ({ content: [{ type: "text", text: v }] }), + json: (l, d) => ({ content: [{ type: "text", text: `${l}\n${JSON.stringify(d)}` }] }), + formatSnapshot: () => "snapshot", + autoAssignEntityForCreate: async () => ({ assignmentSource: "manual", assignedAgents: [], warnings: [] }), + toReportingPhase: () => "execution", + inferReportingInitiativeId: () => undefined, + isUuid: () => true, + pickNonEmptyString: (...vs) => vs.find((v) => typeof v === "string" && v.trim())?.trim(), + resolveReportingContext: () => ({ ok: false, error: "unused" }), + readSkillPackState: () => ({}), + randomUUID: () => "uuid-test", + }; + + const tools = registerCoreTools(deps); + + for (const name of [ + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", + ]) { + const tool = tools.get(name); + assert.ok(tool, `expected ${name} to be registered`); + assert.equal(typeof tool.execute, "function", `${name} should have an execute function`); + assert.ok(tool.description, `${name} should have a description`); + } +}); + +test("preferred read surfaces appear in all 7 domain scopes", async () => { + const mod = await importMcpHandler(); + + const toolNames = [ + "orgx_query_org_memory", + "orgx_recommend_next_action", + "orgx_get_morning_brief", + ]; + + const tools = new Map(); + for (const name of toolNames) { + tools.set(name, { + name, + description: `test ${name}`, + parameters: { type: "object", properties: {} }, + async execute() { + return { content: [{ type: "text", text: "ok" }] }; + }, + }); + } + + const handler = mod.createMcpHttpHandler({ + tools, + serverName: "orgx-test", + serverVersion: "0.0.0", + }); + + const domains = [ + "engineering", + "product", + "design", + "marketing", + "sales", + "operations", + "orchestration", + ]; + + for (const domain of domains) { + const req = { + method: "POST", + url: `/orgx/mcp/${domain}`, + headers: {}, + body: JSON.stringify({ jsonrpc: "2.0", id: `scope-${domain}`, method: "tools/list", params: {} }), + }; + const state = { status: null, headers: null, body: null }; + const res = { + writeHead(s, h) { state.status = s; state.headers = h; }, + end(b) { state.body = b ?? null; }, + }; + await handler(req, res); + assert.equal(state.status, 200, `${domain} should return 200`); + + const payload = JSON.parse(state.body); + const names = payload.result.tools.map((t) => t.name); + + for (const name of toolNames) { + assert.ok(names.includes(name), `${name} should be in ${domain} scope`); + } + } +});