This commit is contained in:
parent
e7a85c5bcf
commit
81ea91e387
22 changed files with 197 additions and 170 deletions
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,6 +4,7 @@ coverage
|
|||
*.log
|
||||
.env
|
||||
/.codex/workspace/local/
|
||||
/.codex/workspace/actions/
|
||||
data/
|
||||
apps/*/data/
|
||||
docs/out/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -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<string, string | undefined>): bo
|
|||
);
|
||||
}
|
||||
|
||||
function actionsLocalConfigured(env: Record<string, string | undefined>): boolean {
|
||||
return env.CODEX_WORKSPACE_MODE === "actions" || env.GITHUB_ACTIONS === "true";
|
||||
}
|
||||
|
||||
function workspaceConfig(context: CliContext): WorkspaceDispatchConfig {
|
||||
return {
|
||||
env: context.env,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<Response>;
|
||||
|
|
@ -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<FeedWorkspaceFlowTarget> = {},
|
||||
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<string, string | undefined>): boolean {
|
||||
return env.CODEX_WORKSPACE_MODE === "actions" || env.GITHUB_ACTIONS === "true";
|
||||
}
|
||||
|
||||
function patchFetch(fetchImpl: WorkspaceBackendFetch) {
|
||||
return async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
||||
return fetchImpl(String(input), init ?? {});
|
||||
|
|
@ -190,104 +195,24 @@ class WorkspaceBackendWebSocketFlowClient implements FlowClient {
|
|||
}
|
||||
|
||||
async #request(method: string, params?: unknown): Promise<unknown> {
|
||||
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<unknown> {
|
||||
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<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
14
bun.lock
14
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=="],
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"] & {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue