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 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 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"); }, }); }, diff --git a/scripts/docker/install-sh-nonroot/run.sh b/scripts/docker/install-sh-nonroot/run.sh index 93da907b3b8ed..6b067ba05faad 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" | grep -Eo '[0-9]{4}\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?' | 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 diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index 7b2cdd5c4823d..593e6c68533e7 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" | 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 - 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