From 81ea91e387fbfda9cc0d5799415976a6949115b6 Mon Sep 17 00:00:00 2001 From: matamune Date: Mon, 18 May 2026 00:07:15 +0000 Subject: [PATCH] Adopt codex-flows 0.3.5 surfaces --- .codex/pack-lock.json | 8 +- .forgejo/workflows/check.yml | 9 ++ .gitignore | 1 + README.md | 7 + apps/patch/package.json | 2 +- apps/patch/src/cli.ts | 14 +- apps/patch/src/flow.ts | 14 +- apps/patch/src/run-harness-flow.ts | 2 +- apps/patch/src/types.ts | 2 +- apps/patch/src/workspace-backend.ts | 145 +++++------------- apps/patch/test/cli.test.ts | 2 +- apps/patch/test/feed.test.ts | 35 +++++ apps/patch/test/flow.test.ts | 2 +- apps/patch/test/harness-flow.test.ts | 2 +- bun.lock | 14 +- docs/pages/concepts/flow-boundary.md | 28 +++- docs/pages/reference/cli.md | 13 +- docs/pages/reference/environment.md | 7 +- docs/pages/reference/packages.md | 21 +-- .../tutorials/dispatch-codex-release-flow.md | 30 +++- flows/patch-moi-harness/exec/rebase-fork.ts | 4 +- package.json | 5 +- 22 files changed, 197 insertions(+), 170 deletions(-) diff --git a/.codex/pack-lock.json b/.codex/pack-lock.json index d493ff4..4cbf219 100644 --- a/.codex/pack-lock.json +++ b/.codex/pack-lock.json @@ -7,12 +7,12 @@ "source": { "input": "../codex-flows", "type": "local", - "commit": "5be9b571578409a31af4693caac8c949c06fe388" + "commit": "a96a005617d18d95d6746efdbd6a1f7bcd70ca49" }, "sourcePath": "flows/openai-codex-bindings", "destinationPath": ".codex/flows/openai-codex-bindings", "contentHash": "sha256:5fcb04e9525e94c24ca319fcefc99fd05ed973e9c640a5e27629e263f13ca968", - "installedAt": "2026-05-16T19:56:06.687Z" + "installedAt": "2026-05-18T00:04:17.788Z" }, { "name": "peezy-codex-fork", @@ -20,12 +20,12 @@ "source": { "input": "../codex-flows", "type": "local", - "commit": "5be9b571578409a31af4693caac8c949c06fe388" + "commit": "a96a005617d18d95d6746efdbd6a1f7bcd70ca49" }, "sourcePath": "flows/peezy-codex-fork", "destinationPath": ".codex/flows/peezy-codex-fork", "contentHash": "sha256:521e7766cd1888fd54fc0c10a6d2a3a7f235d9a7c8aa2577b4f4681e3a8fdabe", - "installedAt": "2026-05-16T19:56:06.687Z" + "installedAt": "2026-05-18T00:04:17.788Z" } ] } diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml index c847733..9d7b4a4 100644 --- a/.forgejo/workflows/check.yml +++ b/.forgejo/workflows/check.yml @@ -12,12 +12,21 @@ jobs: runs-on: bun steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install run: bun install --frozen-lockfile - name: Typecheck run: bun run check:types - name: Test run: bun run test + - name: Verify no-backend Actions dispatch + run: | + data_dir="$(mktemp -d)" + CODEX_WORKSPACE_MODE=actions \ + CODEX_FLOW_FETCH=0 \ + CODEX_FLOW_PUSH=0 \ + bun run patch.moi -- run harness --workspace-root "$PWD" --data-dir "$data_dir" --json - name: Build docs run: bun run docs:build - name: Build container diff --git a/.gitignore b/.gitignore index d03953e..662c655 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ coverage *.log .env /.codex/workspace/local/ +/.codex/workspace/actions/ data/ apps/*/data/ docs/out/ diff --git a/README.md b/README.md index 6c4833a..9bde86f 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ PATCH_FLOW_BACKEND_URL= PATCH_FLOW_DISPATCH_URL= PATCH_FLOW_DISPATCH_SECRET= PATCH_ADMIN_TOKEN= +CODEX_WORKSPACE_MODE= ``` Discord notifications are off by default. Set `DISCORD_OUTPUT_ENABLED=true` @@ -75,6 +76,12 @@ configured workspace backend or local adapter, and record dispatch outcomes in patch.moi-owned `DATA_DIR/maintenance-attempts.jsonl` entry that links the upstream update to workspace run ids, final flow outcome, and candidate refs. +Patch can dispatch through a configured workspace backend, through the +codex-flows Actions/local surface with `CODEX_WORKSPACE_MODE=actions`, or +through synchronous local flow execution for development. The backend does not +need to be running in this checkout unless you are explicitly testing or +operating that surface. + The harness can also be run through the repo-native workspace task: ```bash diff --git a/apps/patch/package.json b/apps/patch/package.json index 43c409e..49559ed 100644 --- a/apps/patch/package.json +++ b/apps/patch/package.json @@ -17,7 +17,7 @@ "check": "bun run check:types && bun run test" }, "dependencies": { - "@peezy.tech/flow-runtime": "^0.4.0" + "@peezy.tech/codex-flows": "^0.3.5" }, "devDependencies": { "@types/bun": "catalog:", diff --git a/apps/patch/src/cli.ts b/apps/patch/src/cli.ts index 8f17004..251a11d 100644 --- a/apps/patch/src/cli.ts +++ b/apps/patch/src/cli.ts @@ -3,7 +3,7 @@ import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import path from "node:path"; -import { discoverFlows, matchingSteps, type FlowEvent as RuntimeFlowEvent } from "@peezy.tech/flow-runtime"; +import { discoverFlows, matchingSteps, type FlowEvent as RuntimeFlowEvent } from "@peezy.tech/codex-flows/flow-runtime"; import { dispatchWorkspaceEventDetailed, maintenanceAttemptForWorkspaceDispatch, @@ -462,11 +462,15 @@ async function appendFlowEventIfMissing(store: EventStore, event: FlowEvent): Pr } function assertCodexDispatchAllowed(context: CliContext): void { - if (flagBool(context.parsed, "allow-local") || workspaceBackendConfigured(context.env)) { + if ( + flagBool(context.parsed, "allow-local") || + workspaceBackendConfigured(context.env) || + actionsLocalConfigured(context.env) + ) { return; } throw new UsageError( - "codex-release dispatch requires PATCH_WORKSPACE_BACKEND_URL or --allow-local; use --dry-run to verify matching without executing release work", + "codex-release dispatch requires PATCH_WORKSPACE_BACKEND_URL, CODEX_WORKSPACE_MODE=actions, or --allow-local; use --dry-run to verify matching without executing release work", ); } @@ -478,6 +482,10 @@ function workspaceBackendConfigured(env: Record): bo ); } +function actionsLocalConfigured(env: Record): boolean { + return env.CODEX_WORKSPACE_MODE === "actions" || env.GITHUB_ACTIONS === "true"; +} + function workspaceConfig(context: CliContext): WorkspaceDispatchConfig { return { env: context.env, diff --git a/apps/patch/src/flow.ts b/apps/patch/src/flow.ts index 4a91897..dba8db5 100644 --- a/apps/patch/src/flow.ts +++ b/apps/patch/src/flow.ts @@ -2,7 +2,7 @@ import type { FlowDispatchResult, FlowReplayResult, FlowRunView, -} from "@peezy.tech/flow-runtime/client"; +} from "@peezy.tech/codex-flows/flow-runtime/client"; import type { CandidateRefRecord, FeedWorkspaceFlowTarget, @@ -143,7 +143,7 @@ export async function dispatchWorkspaceEventDetailed( eventId: event.id, eventType: event.type, operation: "dispatch", - target: backend.mode === "local" ? "local" : "workspace-backend", + target: localTransport(backend.mode) ? "local" : "workspace-backend", transport: backend.mode, workspaceBackendUrl: backend.url, url: backend.eventsUrl, @@ -161,7 +161,7 @@ export async function dispatchWorkspaceEventDetailed( eventId: event.id, eventType: event.type, operation: "dispatch", - target: backend.mode === "local" ? "local" : "workspace-backend", + target: localTransport(backend.mode) ? "local" : "workspace-backend", transport: backend.mode, workspaceBackendUrl: backend.url, url: backend.eventsUrl, @@ -210,7 +210,7 @@ export async function replayWorkspaceEventDetailed( eventId: event.id, eventType: event.type, operation: "replay", - target: backend.mode === "local" ? "local" : "workspace-backend", + target: localTransport(backend.mode) ? "local" : "workspace-backend", transport: backend.mode, workspaceBackendUrl: backend.url, url: backend.eventsUrl ? `${backend.eventsUrl}/${encodeURIComponent(event.id)}/replay` : undefined, @@ -228,7 +228,7 @@ export async function replayWorkspaceEventDetailed( eventId: event.id, eventType: event.type, operation: "replay", - target: backend.mode === "local" ? "local" : "workspace-backend", + target: localTransport(backend.mode) ? "local" : "workspace-backend", transport: backend.mode, workspaceBackendUrl: backend.url, url: backend.eventsUrl, @@ -372,6 +372,10 @@ function httpStatusFromError(error: unknown): number | undefined { return match?.[1] ? Number(match[1]) : undefined; } +function localTransport(value: string): boolean { + return value === "local" || value === "actions-local"; +} + function stringValue(value: unknown): string | undefined { return typeof value === "string" && value.trim() ? value : undefined; } diff --git a/apps/patch/src/run-harness-flow.ts b/apps/patch/src/run-harness-flow.ts index f21fa9c..62ccaa5 100644 --- a/apps/patch/src/run-harness-flow.ts +++ b/apps/patch/src/run-harness-flow.ts @@ -5,7 +5,7 @@ import { matchingSteps, runFlowStep, type FlowEvent, -} from "@peezy.tech/flow-runtime"; +} from "@peezy.tech/codex-flows/flow-runtime"; const workspaceRoot = path.resolve(import.meta.dir, "../../.."); const fixturePath = path.resolve( diff --git a/apps/patch/src/types.ts b/apps/patch/src/types.ts index e859645..b2e071d 100644 --- a/apps/patch/src/types.ts +++ b/apps/patch/src/types.ts @@ -83,7 +83,7 @@ export type FlowDispatchRecord = { eventType: string; operation?: "dispatch" | "replay"; target?: "local" | "workspace-backend"; - transport?: "local" | "workspace-http" | "workspace-ws"; + transport?: "local" | "actions-local" | "workspace-http" | "workspace-ws"; workspaceBackendUrl?: string; url?: string; status: "dispatched" | "failed" | "skipped"; diff --git a/apps/patch/src/workspace-backend.ts b/apps/patch/src/workspace-backend.ts index 79bb2e5..a7cf689 100644 --- a/apps/patch/src/workspace-backend.ts +++ b/apps/patch/src/workspace-backend.ts @@ -11,14 +11,16 @@ import { type FlowReplayResult, type FlowRunList, type FlowRunView, -} from "@peezy.tech/flow-runtime/client"; +} from "@peezy.tech/codex-flows/flow-runtime/client"; +import { createActionsLocalFlowClient } from "@peezy.tech/codex-flows/actions"; +import { CodexWorkspaceBackendClient } from "@peezy.tech/codex-flows/workspace-backend"; import { normalizeDispatchResult, normalizeEvent, normalizeEventList, normalizeRun, normalizeRunList, -} from "@peezy.tech/flow-runtime/backend-client"; +} from "@peezy.tech/codex-flows/flow-runtime/backend-client"; import type { FeedWorkspaceFlowTarget, FlowEvent } from "./types"; export type WorkspaceBackendFetch = (url: string, init: RequestInit) => Promise; @@ -30,23 +32,12 @@ export type WorkspaceBackendConfig = { }; export type PatchWorkspaceBackend = { - mode: "local" | "workspace-http" | "workspace-ws"; + mode: "local" | "actions-local" | "workspace-http" | "workspace-ws"; url?: string; eventsUrl?: string; client: FlowClient; }; -type WorkspaceJsonRpcResponse = { - jsonrpc: "2.0"; - id: string | number | null; - result?: unknown; - error?: { - code?: number; - message?: string; - data?: unknown; - }; -}; - export function createPatchWorkspaceBackend( target: Partial = {}, config: WorkspaceBackendConfig = {}, @@ -74,11 +65,21 @@ export function createPatchWorkspaceBackend( }), }; } + const cwd = config.cwd ?? process.cwd(); + if (actionsLocalRequested(env)) { + return { + mode: "actions-local", + client: createActionsLocalFlowClient({ + workspaceRoot: cwd, + env, + }) as unknown as FlowClient, + }; + } return { mode: "local", client: createFlowClient({ mode: "local", - cwd: config.cwd ?? process.cwd(), + cwd, env, codex: { command: env.CODEX_APP_SERVER_CODEX_COMMAND, @@ -136,6 +137,10 @@ function isWebSocketUrl(url: string): boolean { return url.startsWith("ws://") || url.startsWith("wss://"); } +function actionsLocalRequested(env: Record): boolean { + return env.CODEX_WORKSPACE_MODE === "actions" || env.GITHUB_ACTIONS === "true"; +} + function patchFetch(fetchImpl: WorkspaceBackendFetch) { return async (input: string | URL | Request, init?: RequestInit): Promise => { return fetchImpl(String(input), init ?? {}); @@ -190,104 +195,24 @@ class WorkspaceBackendWebSocketFlowClient implements FlowClient { } async #request(method: string, params?: unknown): Promise { - return workspaceBackendWebSocketRequest(this.#url, method, params); + const client = new CodexWorkspaceBackendClient({ + clientName: "patch-moi", + clientTitle: "patch.moi", + clientVersion: "0.1.0", + webSocketTransportOptions: { + url: this.#url, + requestTimeoutMs: 90_000, + }, + }); + try { + await client.connect(); + return await client.workspaceRequest(method, params); + } finally { + client.close(); + } } } -function workspaceBackendWebSocketRequest(url: string, method: string, params?: unknown): Promise { - return new Promise((resolve, reject) => { - const socket = new WebSocket(url); - let nextId = 1; - let initId = 0; - let callId = 0; - let settled = false; - const timer = setTimeout(() => fail(new Error(`Workspace backend ${method} timed out`)), 90_000); - - function requestId(): number { - const id = nextId; - nextId += 1; - return id; - } - - function sendRequest(requestMethod: string, requestParams?: unknown): number { - const id = requestId(); - socket.send(JSON.stringify({ - jsonrpc: "2.0", - id, - method: requestMethod, - params: requestParams, - })); - return id; - } - - function finish(value: unknown): void { - if (settled) { - return; - } - settled = true; - clearTimeout(timer); - socket.close(); - resolve(value); - } - - function fail(error: unknown): void { - if (settled) { - return; - } - settled = true; - clearTimeout(timer); - socket.close(); - reject(error); - } - - socket.addEventListener("open", () => { - initId = sendRequest("workspace.initialize", { - clientInfo: { - name: "patch-moi", - title: "patch.moi", - version: "0.1.0", - }, - capabilities: { - appServerPassThrough: true, - }, - }); - }); - - socket.addEventListener("message", (event) => { - let response: WorkspaceJsonRpcResponse; - try { - response = JSON.parse(String(event.data)) as WorkspaceJsonRpcResponse; - } catch { - fail(new Error("Workspace backend returned invalid JSON-RPC")); - return; - } - if (response.id === undefined || response.id === null) { - return; - } - if (response.error) { - fail(new Error(response.error.message ?? `Workspace backend ${method} failed`)); - return; - } - if (response.id === initId) { - callId = sendRequest(method, params); - return; - } - if (response.id === callId) { - finish(response.result); - } - }); - - socket.addEventListener("error", () => { - fail(new Error(`Workspace backend WebSocket request failed: ${method}`)); - }); - socket.addEventListener("close", () => { - if (!settled) { - fail(new Error(`Workspace backend WebSocket closed before ${method} completed`)); - } - }); - }); -} - function record(value: unknown): Record { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record diff --git a/apps/patch/test/cli.test.ts b/apps/patch/test/cli.test.ts index c06524b..808f064 100644 --- a/apps/patch/test/cli.test.ts +++ b/apps/patch/test/cli.test.ts @@ -69,7 +69,7 @@ describe("patch.moi CLI", () => { workspaceRoot, ]); expect(blocked.code).toBe(2); - expect(blocked.stderr).toContain("requires PATCH_WORKSPACE_BACKEND_URL or --allow-local"); + expect(blocked.stderr).toContain("requires PATCH_WORKSPACE_BACKEND_URL, CODEX_WORKSPACE_MODE=actions, or --allow-local"); const dryRun = await invoke([ "run", diff --git a/apps/patch/test/feed.test.ts b/apps/patch/test/feed.test.ts index e997083..1a99bfe 100644 --- a/apps/patch/test/feed.test.ts +++ b/apps/patch/test/feed.test.ts @@ -404,6 +404,41 @@ describe("feed watcher", () => { }); }); + test("workspace dispatch uses actions-local mode without a running backend", async () => { + const dataDir = await mkdtemp(join(tmpdir(), "patch-flow-actions-")); + await writeDemoFlow(dataDir); + + const record = await dispatchWorkspaceEvent({ + id: "patch:actions:demo", + type: "demo.event", + source: "patch", + receivedAt: "2026-05-15T00:00:00.000Z", + payload: { name: "Grace" }, + }, {}, { + cwd: dataDir, + env: { + CODEX_WORKSPACE_MODE: "actions", + }, + }); + + expect(record).toMatchObject({ + eventId: "patch:actions:demo", + eventType: "demo.event", + status: "dispatched", + target: "local", + transport: "actions-local", + }); + expect(JSON.parse(await readFile(join(dataDir, "local-flow-output.json"), "utf8"))).toEqual({ + name: "Grace", + }); + + const state = JSON.parse( + await readFile(join(dataDir, ".codex/workspace/actions/flow-client/state.json"), "utf8"), + ) as { events: Array<{ event: { id: string } }>; runs: Array<{ id: string }> }; + expect(state.events.map((entry) => entry.event.id)).toContain("patch:actions:demo"); + expect(state.runs.map((entry) => entry.id)).toEqual(record.runIds ?? []); + }); + test("Patch upstream release helper creates deterministic product events", () => { expect(patchUpstreamReleaseEvent({ repo: "openai/codex", diff --git a/apps/patch/test/flow.test.ts b/apps/patch/test/flow.test.ts index f2eb997..14907d0 100644 --- a/apps/patch/test/flow.test.ts +++ b/apps/patch/test/flow.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { FlowRunView } from "@peezy.tech/flow-runtime/client"; +import type { FlowRunView } from "@peezy.tech/codex-flows/flow-runtime/client"; import { maintenanceAttemptForWorkspaceDispatch, maintenanceAttemptWithWorkspaceRuns, diff --git a/apps/patch/test/harness-flow.test.ts b/apps/patch/test/harness-flow.test.ts index 89f1174..034e7d5 100644 --- a/apps/patch/test/harness-flow.test.ts +++ b/apps/patch/test/harness-flow.test.ts @@ -6,7 +6,7 @@ import { matchingSteps, runFlowStep, type FlowEvent, -} from "@peezy.tech/flow-runtime"; +} from "@peezy.tech/codex-flows/flow-runtime"; const workspaceRoot = path.resolve(import.meta.dir, "../../.."); const harnessFork = path.join(workspaceRoot, "harness/fork"); diff --git a/bun.lock b/bun.lock index 270e873..63c827a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,15 +5,17 @@ "": { "name": "@peezy.tech/patch", "devDependencies": { - "@peezy.tech/codex-flows": "^0.3.4", - "@peezy.tech/flow-runtime": "^0.4.0", + "@peezy.tech/codex-flows": "^0.3.5", }, }, "apps/patch": { "name": "@peezy.tech/patch", "version": "0.1.0", + "bin": { + "patch.moi": "./src/cli.ts", + }, "dependencies": { - "@peezy.tech/flow-runtime": "^0.4.0", + "@peezy.tech/codex-flows": "^0.3.5", }, "devDependencies": { "@types/bun": "catalog:", @@ -91,9 +93,7 @@ "@oxc-project/types": ["@oxc-project/types@0.130.0", "", {}, "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q=="], - "@peezy.tech/codex-flows": ["@peezy.tech/codex-flows@0.3.4", "", { "bin": { "codex-flows": "dist/cli/index.js" } }, "sha512-ScggyelL6q9EKxqjrJhvyF7o6A7+naK4GkkB44aF5sSs06fANn1Uw/zN2Qmv3CuCFpSDCeg5I8fL+ro8PQGTFw=="], - - "@peezy.tech/flow-runtime": ["@peezy.tech/flow-runtime@0.4.0", "", { "dependencies": { "@peezy.tech/codex-flows": "^0.3.1" } }, "sha512-HJ0witZkQ2l+tzB/bONB+hV4aPmjc0jOOoUu01gPHo2NFJ/qfJ8bMbUe7jdCvfvDCqStr6RZooS2FiF/OLGs6A=="], + "@peezy.tech/codex-flows": ["@peezy.tech/codex-flows@0.3.5", "", { "bin": { "codex-flows": "dist/cli/index.js", "codex-app": "dist/bin/codex-app.js", "codex-flow-runner": "dist/bin/codex-flow-runner.js", "codex-workspace-backend-local": "dist/bin/codex-workspace-backend-local.js" } }, "sha512-axKyRFn8H7MPM2VD6YEKu1LqnVvjiX9WjNRxnZNwsAf3n8kCofSiXP5p9l0RdKCB2ASex3AgdyboROkWc6zC6g=="], "@peezy.tech/patch": ["@peezy.tech/patch@workspace:apps/patch"], @@ -873,8 +873,6 @@ "@apidevtools/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - "@peezy.tech/flow-runtime/@peezy.tech/codex-flows": ["@peezy.tech/codex-flows@0.3.3", "", { "bin": { "codex-flows": "dist/cli/index.js" } }, "sha512-crhGJ8YsdqxKoqpulkKprIz+rCeULiQ6HmsDZIec4NJPg04AVv6PXmPJPHXlRJN8DhdDAomRZ8Codm0nuRObhQ=="], - "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], diff --git a/docs/pages/concepts/flow-boundary.md b/docs/pages/concepts/flow-boundary.md index 80cd092..fb230b0 100644 --- a/docs/pages/concepts/flow-boundary.md +++ b/docs/pages/concepts/flow-boundary.md @@ -38,13 +38,25 @@ to identify the upstream update. The receiving workspace still reads Git and forge state to discover the maintained branch, patch commits, candidate refs, and current checks. -## Workspace Backends +## Flow Execution Surfaces -When `PATCH_WORKSPACE_BACKEND_URL` is unset, patch.moi uses local flow -execution from the process working directory. When it is set to an HTTP or -WebSocket URL, patch.moi dispatches to that Codex workspace backend. In both -cases patch.moi writes its own dispatch and maintenance-attempt records under -`DATA_DIR`. +patch.moi supports the codex-flows flow surfaces without requiring any one of +them to be running on this checkout: + +| Surface | How patch.moi selects it | Run state | +| --- | --- | --- | +| synchronous local | no backend URL and no Actions mode | in-process local client state | +| Actions/local | `CODEX_WORKSPACE_MODE=actions` or `GITHUB_ACTIONS=true` and no backend URL | `.codex/workspace/actions/flow-client` | +| workspace HTTP | `PATCH_WORKSPACE_BACKEND_URL`, `PATCH_FLOW_BACKEND_URL`, or `PATCH_FLOW_DISPATCH_URL` is an HTTP URL | backend-owned | +| workspace WebSocket | configured workspace URL starts with `ws://` or `wss://` | backend-owned | + +The Actions/local surface is the no-running-backend path for semi-autonomous +fork maintenance in CI. A persistent workspace backend remains optional: use it +when a host, service, or gateway needs durable run inspection, app-server +pass-through, or remote dispatch/replay control. + +In every case patch.moi writes its own dispatch and maintenance-attempt records +under `DATA_DIR`. Workspace backend run state is useful for inspection and sync. It is not the authoritative patch.moi product state. @@ -66,8 +78,8 @@ local harness path. The repository also includes an explicit manual bun run workspace:run:harness-flow ``` -That flow task requires a running workspace backend URL. In -`@peezy.tech/codex-flows@0.3.4`, workspace-owned flow tasks synthesize unique +That flow task requires a running workspace backend URL for the repo-native +workspace automation command. In codex-flows, workspace-owned flow tasks synthesize unique `id`, `occurredAt`, and `receivedAt` fields for every run. Those ids are useful for workspace automation, but patch.moi must not use workspace-generated ids for feed-owned maintenance attempts. diff --git a/docs/pages/reference/cli.md b/docs/pages/reference/cli.md index c5fb3d7..f5ba32a 100644 --- a/docs/pages/reference/cli.md +++ b/docs/pages/reference/cli.md @@ -53,8 +53,15 @@ Verify Codex release flow matching without executing release work: bun run patch.moi -- run codex-release --tag rust-v0.130.0 --dry-run ``` -Dispatching the Codex release task requires a configured workspace backend by -default: +Dispatching the Codex release task requires an explicit execution surface. Use +Actions/local mode when no workspace backend is running: + +```bash +CODEX_WORKSPACE_MODE=actions \ +bun run patch.moi -- run codex-release --tag rust-v0.130.0 +``` + +Or point at a workspace backend: ```bash PATCH_WORKSPACE_BACKEND_URL=ws://127.0.0.1:3586 \ @@ -63,7 +70,7 @@ bun run patch.moi -- run codex-release --tag rust-v0.130.0 Use `--allow-local` only when you intentionally want the local Patch process to execute matching Codex release flows. The Code Mode step still requires its own -`CODEX_FLOWS_ENABLE_CODE_MODE=1` gate. +`CODEX_FLOWS_MODE=code-mode` gate. ## Status diff --git a/docs/pages/reference/environment.md b/docs/pages/reference/environment.md index eb75b5c..70a2e0f 100644 --- a/docs/pages/reference/environment.md +++ b/docs/pages/reference/environment.md @@ -18,7 +18,10 @@ description: Runtime environment variables used by patch.moi. | `PATCH_FLOW_DISPATCH_URL` | unset | Legacy or explicit flow dispatch URL fallback. | | `PATCH_FLOW_DISPATCH_SECRET` | unset | Legacy HMAC secret fallback. | | `PEEZY_CODEX_REPO` | `../codex` | Optional Codex checkout path for `patch.moi setup codex`. | -| `CODEX_FLOWS_ENABLE_CODE_MODE` | unset | Required by Code Mode flow steps before local Code Mode execution. | +| `CODEX_WORKSPACE_MODE` | unset | Set to `actions` to use codex-flows Actions/local flow state when no workspace backend URL is configured. | +| `GITHUB_ACTIONS` | unset | When `true`, also selects the Actions/local no-backend flow surface. | +| `CODEX_FLOWS_MODE` | unset | Set to `code-mode` when Code Mode flow steps should run. | +| `CODEX_FLOWS_ENABLE_CODE_MODE` | unset | Legacy narrow gate accepted by Code Mode flow steps. | | `CODEX_APP_SERVER_CODEX_COMMAND` | unset | Passed to local code-mode flow execution. | | `CODEX_HOME` | unset | Passed to local code-mode flow execution. | @@ -30,4 +33,4 @@ Git topology is intentionally not represented here. Local mode should read upstream, fork, branch, and tag state from Git. Service mode should read remote repository, branch, workflow, and review state from the forge. Environment variables should stay limited to runtime concerns such as workspace backend -URLs, data directories, and Codex execution settings. +URLs, data directories, workspace mode, and Codex execution settings. diff --git a/docs/pages/reference/packages.md b/docs/pages/reference/packages.md index 82f688e..d918139 100644 --- a/docs/pages/reference/packages.md +++ b/docs/pages/reference/packages.md @@ -35,10 +35,13 @@ bun run docs:build ## Root Workspace -The repository root owns shared scripts and dev dependencies. It installs -`@peezy.tech/codex-flows` so repo-native workspace commands are available: +The repository root owns shared scripts and dev dependencies. It installs the +current `@peezy.tech/codex-flows` package surface so repo-native workspace, +flow, app-server, and backend commands are available: ```bash +bun run flow:list +bun run workspace:backend bun run workspace:doctor bun run workspace:tick bun run workspace:run:harness @@ -62,17 +65,15 @@ codex-flows pack doctor --json not patch.moi product state. patch.moi still records feed-owned flow events, workspace dispatches, and maintenance attempts under `DATA_DIR`. -## Related Runtime Packages +## Related Runtime Package -These published packages define the current patch.moi integration baseline: +patch.moi uses the consolidated codex-flows package surface: | Package | Published version | patch.moi use | | --- | --- | --- | -| `@peezy.tech/codex-flows` | `0.3.4` | repo-native workspace commands and CLI automation | -| `@peezy.tech/flow-runtime` | `0.4.0` | local flow discovery, matching, execution, and Bun flow helpers | -| `@peezy.tech/flow-backend-convex` | `0.4.0` | optional generic durable flow backend for future service experiments | +| `@peezy.tech/codex-flows` | `^0.3.5` | flow runtime, Bun flow helpers, workspace backend protocol/client, Actions/local flow state, CLI automation, and backend bins | patch.moi product state still belongs in the Patch service JSONL store by -default. `flow-backend-convex` should be considered only when patch.moi needs a -generic durable flow event/run backend; it is not the default home for feed -signals, workspace dispatch records, or maintenance attempts. +default. Generic flow backend state is execution/run state. It is useful for +inspection and sync, but it is not the default home for feed signals, workspace +dispatch records, or maintenance attempts. diff --git a/docs/pages/tutorials/dispatch-codex-release-flow.md b/docs/pages/tutorials/dispatch-codex-release-flow.md index 8a0aee3..5fcd734 100644 --- a/docs/pages/tutorials/dispatch-codex-release-flow.md +++ b/docs/pages/tutorials/dispatch-codex-release-flow.md @@ -48,15 +48,15 @@ codex-flows pack doctor --json ``` The current local install pins `openai-codex-bindings` and `peezy-codex-fork` -in `.codex/pack-lock.json`. `@peezy.tech/flow-runtime@0.4.0` discovers -installed `.codex/flows/*` before source-owned `flows/*`, so the installed -Codex capabilities are visible to patch.moi while the harness remains a -source-owned repo flow. +in `.codex/pack-lock.json`. The codex-flows runtime discovers installed +`.codex/flows/*` before source-owned `flows/*`, so the installed Codex +capabilities are visible to patch.moi while the harness remains a source-owned +repo flow. Safe local verification stops at event matching and runner gating. The test suite confirms that a stored `upstream.release` event for `openai/codex` selects both installed Codex release steps, and that the Code Mode step still -requires `CODEX_FLOWS_ENABLE_CODE_MODE=1`. Do not fabricate a full +requires `CODEX_FLOWS_MODE=code-mode`. Do not fabricate a full `openai/codex` release lifecycle just to exercise the flow. You can run the same safe match check through the CLI: @@ -65,7 +65,23 @@ You can run the same safe match check through the CLI: bun run patch.moi -- run codex-release --tag rust-v0.130.0 --dry-run ``` -## 3. Point Patch at a workspace backend +## 3. Pick an execution surface + +Patch does not require a Codex workspace backend to be running in this checkout. +For CI-style no-backend maintenance, select the codex-flows Actions/local +surface: + +```bash +CODEX_WORKSPACE_MODE=actions \ +DATA_DIR=./data \ +bun run patch.moi -- run codex-release --tag rust-v0.130.0 +``` + +That writes flow run state under `.codex/workspace/actions/flow-client` and +patch.moi product state under `DATA_DIR`. + +For a service or host-owned execution surface, point Patch at a workspace +backend: ```bash PATCH_WORKSPACE_BACKEND_URL=http://127.0.0.1:3586 \ @@ -81,7 +97,7 @@ workspace flow capability. `PATCH_FLOW_BACKEND_URL` and `PATCH_FLOW_DISPATCH_URL` remain accepted for older feed targets. Leave `PATCH_WORKSPACE_BACKEND_URL` unset only when you intentionally want local -flow execution from the Patch process working directory. +or Actions/local flow execution from the Patch process working directory. ## 4. Inspect the stored event diff --git a/flows/patch-moi-harness/exec/rebase-fork.ts b/flows/patch-moi-harness/exec/rebase-fork.ts index 755551f..bc2c8c1 100644 --- a/flows/patch-moi-harness/exec/rebase-fork.ts +++ b/flows/patch-moi-harness/exec/rebase-fork.ts @@ -1,6 +1,6 @@ import path from "node:path"; -import { defineBunFlow } from "@peezy.tech/flow-runtime/bun"; -import type { FlowResult, FlowResultStatus, FlowRunContext } from "@peezy.tech/flow-runtime"; +import { defineBunFlow } from "@peezy.tech/codex-flows/flow-runtime/bun"; +import type { FlowResult, FlowResultStatus, FlowRunContext } from "@peezy.tech/codex-flows/flow-runtime"; type HarnessFlowContext = FlowRunContext & { flow: FlowRunContext["flow"] & { diff --git a/package.json b/package.json index f76bbd9..a6554c1 100644 --- a/package.json +++ b/package.json @@ -36,13 +36,14 @@ "patch.moi": "bun run --filter @peezy.tech/patch patch.moi", "start": "bun run --filter @peezy.tech/patch start", "test": "bun run --filter @peezy.tech/patch test", + "flow:list": "codex-flow-runner --cwd . list", + "workspace:backend": "codex-workspace-backend-local serve --cwd . --local-app-server", "workspace:doctor": "codex-flows workspace doctor --workspace-root .", "workspace:run:harness": "codex-flows workspace run harness-maintenance-fixture --workspace-root . --mode local", "workspace:run:harness-flow": "codex-flows workspace run harness-maintenance-flow-smoke --workspace-root . --mode local", "workspace:tick": "codex-flows workspace tick --workspace-root . --mode local" }, "devDependencies": { - "@peezy.tech/codex-flows": "^0.3.4", - "@peezy.tech/flow-runtime": "^0.4.0" + "@peezy.tech/codex-flows": "^0.3.5" } }