From 935a68b7c562628209d1597d3cc10b669a9e12ea Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:05:58 -0400 Subject: [PATCH 1/7] feat: expose Prophet workspace hook RPC methods --- extensions/prophet-workspace/index.ts | 73 +++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/extensions/prophet-workspace/index.ts b/extensions/prophet-workspace/index.ts index 67f75eb78c638..09624d9db662a 100644 --- a/extensions/prophet-workspace/index.ts +++ b/extensions/prophet-workspace/index.ts @@ -1,3 +1,5 @@ +import { createNoopProphetWorkspaceHooks } from "./src/hooks.js"; + const prophetWorkspacePlugin = { id: "prophet-workspace", name: "Prophet Workspace", @@ -30,6 +32,7 @@ const prophetWorkspacePlugin = { }, register(api: any) { const config = this.configSchema.parse(api.pluginConfig); + const hooks = createNoopProphetWorkspaceHooks(api); api.registerGatewayMethod("prophetWorkspace.status", ({ respond }: any) => { respond(true, { @@ -39,15 +42,77 @@ const prophetWorkspacePlugin = { endpoint: config.endpoint ?? null, authMode: config.authMode, workspaceScope: config.workspaceScope ?? null, - phase: "skeleton", + phase: "hook-rpc", + hooks: [ + "emitCarrier", + "evaluateMembraneDecision", + "appendLedgerEvent", + "resolveConnectedAppGrant", + ], }); }); + api.registerGatewayMethod("prophetWorkspace.emitCarrier", async ({ params, respond }: any) => { + try { + const result = await hooks.emitCarrier({ + kind: typeof params?.kind === "string" ? params.kind : "unknown", + scope: typeof params?.scope === "string" ? params.scope : undefined, + payload: params?.payload ?? {}, + }); + respond(true, result); + } catch (err) { + respond(false, { error: err instanceof Error ? err.message : String(err) }); + } + }); + + api.registerGatewayMethod( + "prophetWorkspace.evaluateMembraneDecision", + async ({ params, respond }: any) => { + try { + const result = await hooks.evaluateMembraneDecision({ + action: typeof params?.action === "string" ? params.action : "unknown", + resource: typeof params?.resource === "string" ? params.resource : undefined, + scope: typeof params?.scope === "string" ? params.scope : undefined, + payload: params?.payload, + }); + respond(true, result); + } catch (err) { + respond(false, { error: err instanceof Error ? err.message : String(err) }); + } + }, + ); + + api.registerGatewayMethod("prophetWorkspace.appendLedgerEvent", async ({ params, respond }: any) => { + try { + const result = await hooks.appendLedgerEvent({ + kind: typeof params?.kind === "string" ? params.kind : "unknown", + scope: typeof params?.scope === "string" ? params.scope : undefined, + payload: params?.payload ?? {}, + }); + respond(true, result); + } catch (err) { + respond(false, { error: err instanceof Error ? err.message : String(err) }); + } + }); + + api.registerGatewayMethod( + "prophetWorkspace.resolveConnectedAppGrant", + async ({ params, respond }: any) => { + try { + const scope = typeof params?.scope === "string" ? params.scope : "default"; + const result = await hooks.resolveConnectedAppGrant(scope); + respond(true, result); + } catch (err) { + respond(false, { error: err instanceof Error ? err.message : String(err) }); + } + }, + ); + api.registerCli( ({ program }: any) => { program .command("prophet-workspace-status") - .description("Show Prophet workspace plugin skeleton status") + .description("Show Prophet workspace plugin status") .action(() => { api.logger.info( `[prophet-workspace] enabled=${String(config.enabled)} endpoint=${config.endpoint ?? "unset"} auth=${config.authMode}`, @@ -60,10 +125,10 @@ const prophetWorkspacePlugin = { api.registerService({ id: "prophet-workspace", start: async () => { - api.logger.info("[prophet-workspace] skeleton plugin ready"); + api.logger.info("[prophet-workspace] hook-rpc plugin ready"); }, stop: async () => { - api.logger.info("[prophet-workspace] skeleton plugin stopped"); + api.logger.info("[prophet-workspace] hook-rpc plugin stopped"); }, }); }, From f4a32d755128c54bd15d16a833f8a8d22fc937b0 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:53:14 -0400 Subject: [PATCH 2/7] Add Prophet workspace plugin validation workflow --- .../workflows/prophet-workspace-plugin.yml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/prophet-workspace-plugin.yml diff --git a/.github/workflows/prophet-workspace-plugin.yml b/.github/workflows/prophet-workspace-plugin.yml new file mode 100644 index 0000000000000..2b93c789dc7f0 --- /dev/null +++ b/.github/workflows/prophet-workspace-plugin.yml @@ -0,0 +1,39 @@ +name: Prophet Workspace Plugin + +on: + pull_request: + paths: + - 'extensions/prophet-workspace/**' + - '.github/workflows/prophet-workspace-plugin.yml' + push: + branches: + - main + paths: + - 'extensions/prophet-workspace/**' + - '.github/workflows/prophet-workspace-plugin.yml' + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Validate registered hook RPC methods + run: | + set -euo pipefail + grep -q 'prophetWorkspace.status' extensions/prophet-workspace/index.ts + grep -q 'prophetWorkspace.emitCarrier' extensions/prophet-workspace/index.ts + grep -q 'prophetWorkspace.evaluateMembraneDecision' extensions/prophet-workspace/index.ts + grep -q 'prophetWorkspace.appendLedgerEvent' extensions/prophet-workspace/index.ts + grep -q 'prophetWorkspace.resolveConnectedAppGrant' extensions/prophet-workspace/index.ts + - name: Validate no-op safety posture + run: | + set -euo pipefail + grep -q 'reason: "not-wired"' extensions/prophet-workspace/src/hooks.ts + grep -q 'accepted: false' extensions/prophet-workspace/src/hooks.ts + grep -q 'allow: false' extensions/prophet-workspace/src/hooks.ts + grep -q 'ok: false' extensions/prophet-workspace/src/hooks.ts + grep -q 'granted: false' extensions/prophet-workspace/src/hooks.ts From d2896e764d90ac64ba9a9aa7a5186c844ad0d786 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sat, 23 May 2026 09:36:37 -0400 Subject: [PATCH 3/7] Normalize installer smoke CLI version parsing --- scripts/docker/install-sh-smoke/run.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index 7b2cdd5c4823d..8bae1df387c01 100755 --- a/scripts/docker/install-sh-smoke/run.sh +++ b/scripts/docker/install-sh-smoke/run.sh @@ -59,15 +59,16 @@ fi if [[ -n "${OPENCLAW_INSTALL_LATEST_OUT:-}" ]]; then printf "%s" "$LATEST_VERSION" > "${OPENCLAW_INSTALL_LATEST_OUT:-}" fi -INSTALLED_VERSION="$("$CLI_NAME" --version 2>/dev/null | head -n 1 | tr -d '\r')" -echo "cli=$CLI_NAME installed=$INSTALLED_VERSION expected=$LATEST_VERSION" +INSTALLED_RAW="$("$CLI_NAME" --version 2>/dev/null | head -n 1 | tr -d '\r')" +INSTALLED_VERSION="$(printf '%s\n' "$INSTALLED_RAW" | sed -nE 's/.*([0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?).*/\1/p' | head -n 1)" +echo "cli=$CLI_NAME installed=$INSTALLED_RAW parsed=$INSTALLED_VERSION expected=$LATEST_VERSION" if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then - echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_VERSION}" >&2 + echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_RAW}" >&2 exit 1 fi echo "==> Sanity: CLI runs" "$CLI_NAME" --help >/dev/null -echo "OK" +echo "OK" \ No newline at end of file From 8b060ceb22c0e26ddfd69a4d51002079dab86f58 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sat, 23 May 2026 09:37:40 -0400 Subject: [PATCH 4/7] Normalize non-root installer smoke CLI version parsing --- scripts/docker/install-sh-nonroot/run.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/docker/install-sh-nonroot/run.sh b/scripts/docker/install-sh-nonroot/run.sh index 93da907b3b8ed..3837399cf5277 100644 --- a/scripts/docker/install-sh-nonroot/run.sh +++ b/scripts/docker/install-sh-nonroot/run.sh @@ -37,15 +37,16 @@ if [[ -z "$CMD_PATH" ]]; then exit 1 fi echo "==> Verify CLI installed: $CLI_NAME" -INSTALLED_VERSION="$("$CMD_PATH" --version 2>/dev/null | head -n 1 | tr -d '\r')" +INSTALLED_RAW="$("$CMD_PATH" --version 2>/dev/null | head -n 1 | tr -d '\r')" +INSTALLED_VERSION="$(printf '%s\n' "$INSTALLED_RAW" | sed -nE 's/.*([0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?).*/\1/p' | head -n 1)" -echo "cli=$CLI_NAME installed=$INSTALLED_VERSION expected=$LATEST_VERSION" +echo "cli=$CLI_NAME installed=$INSTALLED_RAW parsed=$INSTALLED_VERSION expected=$LATEST_VERSION" if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then - echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_VERSION}" >&2 + echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_RAW}" >&2 exit 1 fi echo "==> Sanity: CLI runs" "$CMD_PATH" --help >/dev/null -echo "OK" +echo "OK" \ No newline at end of file From 62b5e3f51f1ab539e019540c478357f13f3ebc4e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sat, 23 May 2026 09:41:11 -0400 Subject: [PATCH 5/7] Tighten installer smoke semver extraction --- scripts/docker/install-sh-smoke/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index 8bae1df387c01..593e6c68533e7 100755 --- a/scripts/docker/install-sh-smoke/run.sh +++ b/scripts/docker/install-sh-smoke/run.sh @@ -60,7 +60,7 @@ if [[ -n "${OPENCLAW_INSTALL_LATEST_OUT:-}" ]]; then printf "%s" "$LATEST_VERSION" > "${OPENCLAW_INSTALL_LATEST_OUT:-}" fi INSTALLED_RAW="$("$CLI_NAME" --version 2>/dev/null | head -n 1 | tr -d '\r')" -INSTALLED_VERSION="$(printf '%s\n' "$INSTALLED_RAW" | sed -nE 's/.*([0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?).*/\1/p' | head -n 1)" +INSTALLED_VERSION="$(printf '%s\n' "$INSTALLED_RAW" | grep -Eo '[0-9]{4}\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?' | head -n 1)" echo "cli=$CLI_NAME installed=$INSTALLED_RAW parsed=$INSTALLED_VERSION expected=$LATEST_VERSION" if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then From 546d696715e2ab5374fb06e4ce7421395d80c673 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sat, 23 May 2026 09:42:17 -0400 Subject: [PATCH 6/7] Tighten non-root installer smoke semver extraction --- scripts/docker/install-sh-nonroot/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docker/install-sh-nonroot/run.sh b/scripts/docker/install-sh-nonroot/run.sh index 3837399cf5277..6b067ba05faad 100644 --- a/scripts/docker/install-sh-nonroot/run.sh +++ b/scripts/docker/install-sh-nonroot/run.sh @@ -38,7 +38,7 @@ if [[ -z "$CMD_PATH" ]]; then fi echo "==> Verify CLI installed: $CLI_NAME" INSTALLED_RAW="$("$CMD_PATH" --version 2>/dev/null | head -n 1 | tr -d '\r')" -INSTALLED_VERSION="$(printf '%s\n' "$INSTALLED_RAW" | sed -nE 's/.*([0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?).*/\1/p' | head -n 1)" +INSTALLED_VERSION="$(printf '%s\n' "$INSTALLED_RAW" | grep -Eo '[0-9]{4}\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?' | head -n 1)" echo "cli=$CLI_NAME installed=$INSTALLED_RAW parsed=$INSTALLED_VERSION expected=$LATEST_VERSION" if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then From adb1074b4be069015cf871b68bdaaccfd7ace820 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 24 May 2026 19:43:55 -0400 Subject: [PATCH 7/7] Gate macOS Swift checks to Swift changes --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caa148a9b5775..f02db547a4135 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -367,27 +367,50 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 run: pnpm test + - name: Detect Swift-relevant changes + id: swift_changes + shell: bash + run: | + set -euo pipefail + if [ "${{ github.event_name }}" != "pull_request" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + git fetch --no-tags --prune --depth=50 origin "${{ github.base_ref }}" + git diff --name-only "origin/${{ github.base_ref }}...HEAD" > /tmp/openclaw-changed-files.txt + cat /tmp/openclaw-changed-files.txt + if grep -E '(^|/)(Package\.swift|\.swiftlint\.yml|\.swiftformat|Brewfile)$|\.swift$|\.xcodeproj/|\.xcworkspace/|^apps/macos/' /tmp/openclaw-changed-files.txt; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + fi + # --- Xcode/Swift setup --- - name: Select Xcode 26.1 + if: steps.swift_changes.outputs.present == 'true' run: | sudo xcode-select -s /Applications/Xcode_26.1.app xcodebuild -version - name: Install XcodeGen / SwiftLint / SwiftFormat + if: steps.swift_changes.outputs.present == 'true' run: brew install xcodegen swiftlint swiftformat - name: Show toolchain + if: steps.swift_changes.outputs.present == 'true' run: | sw_vers xcodebuild -version swift --version - name: Swift lint + if: steps.swift_changes.outputs.present == 'true' run: | swiftlint --config .swiftlint.yml swiftformat --lint apps/macos/Sources --config .swiftformat - name: Swift build (release) + if: steps.swift_changes.outputs.present == 'true' run: | set -euo pipefail for attempt in 1 2 3; do @@ -400,6 +423,7 @@ jobs: exit 1 - name: Swift test + if: steps.swift_changes.outputs.present == 'true' run: | set -euo pipefail for attempt in 1 2 3; do