refactor: unify GATEWAY_SECRET + DASHBOARD_API_KEY into WORKFLOW_DASHBOARD_SECRET

This commit is contained in:
2026-05-15 08:31:58 +00:00
parent 9c44c709e9
commit 0f3661b566
6 changed files with 38 additions and 20 deletions
+5 -2
View File
@@ -14,7 +14,10 @@ WORKFLOW_CURSOR_MODEL=
# Timeout in milliseconds for Cursor agent operations # Timeout in milliseconds for Cursor agent operations
WORKFLOW_CURSOR_TIMEOUT= WORKFLOW_CURSOR_TIMEOUT=
# ── Hermes Agent (used by workflow-template-solve-issue) ── # ── Hermes Agent (used by develop tester/committer + solve-issue) ──
# CLI command to invoke the Hermes agent (absolute path required)
WORKFLOW_HERMES_COMMAND=
# Model override for Hermes agent # Model override for Hermes agent
WORKFLOW_HERMES_MODEL= WORKFLOW_HERMES_MODEL=
@@ -29,7 +32,7 @@ WORKFLOW_HERMES_TIMEOUT=
WORKFLOW_STORAGE_ROOT= WORKFLOW_STORAGE_ROOT=
# Gateway secret for the serve command # Gateway secret for the serve command
WORKFLOW_GATEWAY_SECRET= WORKFLOW_DASHBOARD_SECRET=
# ── Display ── # ── Display ──
@@ -23,7 +23,7 @@ function requireNextArg(argv: string[], i: number, flag: string): Result<string,
function parseConnectArgv(argv: string[]): Result<ConnectOptions, string> { function parseConnectArgv(argv: string[]): Result<ConnectOptions, string> {
let name = osHostname().split(".")[0].toLowerCase(); let name = osHostname().split(".")[0].toLowerCase();
let gatewayUrl = DEFAULT_GATEWAY_URL; let gatewayUrl = DEFAULT_GATEWAY_URL;
const gatewaySecret = process.env.WORKFLOW_GATEWAY_SECRET ?? ""; const gatewaySecret = process.env.WORKFLOW_DASHBOARD_SECRET ?? "";
const stringFlags: Record<string, (v: string) => void> = { const stringFlags: Record<string, (v: string) => void> = {
"--name": (v) => { "--name": (v) => {
name = v; name = v;
@@ -56,7 +56,7 @@ export async function dispatchConnect(storageRoot: string, argv: string[]): Prom
const options = parsed.value; const options = parsed.value;
if (options.gatewaySecret === "") { if (options.gatewaySecret === "") {
printCliLine("error: WORKFLOW_GATEWAY_SECRET is required"); printCliLine("error: WORKFLOW_DASHBOARD_SECRET is required");
return 1; return 1;
} }
@@ -4,7 +4,7 @@ import { DurableObject } from "cloudflare:workers";
import { parseWsRequestJson, parseWsResponseJson, type WsResponse } from "./ws-protocol.js"; import { parseWsRequestJson, parseWsResponseJson, type WsResponse } from "./ws-protocol.js";
type ClientSocketEnv = { type ClientSocketEnv = {
GATEWAY_SECRET: string; WORKFLOW_DASHBOARD_SECRET: string;
}; };
export const CLIENT_SOCKET_INTERNAL_STATUS_PATH = "/internal/client-socket/status"; export const CLIENT_SOCKET_INTERNAL_STATUS_PATH = "/internal/client-socket/status";
@@ -37,7 +37,7 @@ export class ClientSocket extends DurableObject<ClientSocketEnv> {
private requireAuth(request: Request): Response | null { private requireAuth(request: Request): Response | null {
const auth = request.headers.get("Authorization"); const auth = request.headers.get("Authorization");
if (auth !== `Bearer ${this.env.GATEWAY_SECRET}`) { if (auth !== `Bearer ${this.env.WORKFLOW_DASHBOARD_SECRET}`) {
return jsonResponse(401, { error: "unauthorized" }); return jsonResponse(401, { error: "unauthorized" });
} }
return null; return null;
+9 -10
View File
@@ -13,8 +13,7 @@ export { ClientSocket };
type Env = { type Env = {
Bindings: { Bindings: {
ENDPOINTS: KVNamespace; ENDPOINTS: KVNamespace;
GATEWAY_SECRET: string; WORKFLOW_DASHBOARD_SECRET: string;
DASHBOARD_API_KEY: string;
CLIENT_SOCKET: DurableObjectNamespace<ClientSocket>; CLIENT_SOCKET: DurableObjectNamespace<ClientSocket>;
}; };
}; };
@@ -40,7 +39,7 @@ function checkDashboardAuth(c: {
const bearer = c.req.header("Authorization")?.replace("Bearer ", ""); const bearer = c.req.header("Authorization")?.replace("Bearer ", "");
const query = c.req.query("key"); const query = c.req.query("key");
const key = bearer ?? query; const key = bearer ?? query;
return key === c.env.DASHBOARD_API_KEY; return key === c.env.WORKFLOW_DASHBOARD_SECRET;
} }
function isLocalClientUrl(url: string): boolean { function isLocalClientUrl(url: string): boolean {
@@ -153,7 +152,7 @@ async function fetchClientSocketStatus(
const resp = await stub.fetch( const resp = await stub.fetch(
new Request(`https://do${CLIENT_SOCKET_INTERNAL_STATUS_PATH}`, { new Request(`https://do${CLIENT_SOCKET_INTERNAL_STATUS_PATH}`, {
method: "GET", method: "GET",
headers: { Authorization: `Bearer ${env.GATEWAY_SECRET}` }, headers: { Authorization: `Bearer ${env.WORKFLOW_DASHBOARD_SECRET}` },
}), }),
); );
if (!resp.ok) { if (!resp.ok) {
@@ -184,14 +183,14 @@ function endpointStatusFromKvAndDo(record: EndpointRecord, doConnected: boolean
// ── Health ────────────────────────────────────────────────────────── // ── Health ──────────────────────────────────────────────────────────
app.get("/healthz", (c) => c.json({ ok: true })); app.get("/healthz", (c) => c.json({ ok: true }));
// ── Client reverse WebSocket (GATEWAY_SECRET query param) ──────────── // ── Client reverse WebSocket (WORKFLOW_DASHBOARD_SECRET query param) ────────────
app.get("/ws/connect", async (c) => { app.get("/ws/connect", async (c) => {
const secret = c.req.query("secret"); const secret = c.req.query("secret");
const name = c.req.query("name"); const name = c.req.query("name");
if (name === undefined || name === "") { if (name === undefined || name === "") {
return c.json({ error: "name required" }, 400); return c.json({ error: "name required" }, 400);
} }
if (secret !== c.env.GATEWAY_SECRET) { if (secret !== c.env.WORKFLOW_DASHBOARD_SECRET) {
return c.json({ error: "unauthorized" }, 401); return c.json({ error: "unauthorized" }, 401);
} }
if (c.req.header("Upgrade") !== "websocket") { if (c.req.header("Upgrade") !== "websocket") {
@@ -202,7 +201,7 @@ app.get("/ws/connect", async (c) => {
return stub.fetch(c.req.raw); return stub.fetch(c.req.raw);
}); });
// ── Gateway management (GATEWAY_SECRET auth) ──────────────────────── // ── Gateway management (WORKFLOW_DASHBOARD_SECRET auth) ────────────────────────
const gateway = new Hono<Env>(); const gateway = new Hono<Env>();
gateway.post("/register", async (c) => { gateway.post("/register", async (c) => {
@@ -217,7 +216,7 @@ gateway.post("/register", async (c) => {
if (!name || !url) { if (!name || !url) {
return c.json({ error: "name and url required" }, 400); return c.json({ error: "name and url required" }, 400);
} }
if (secret !== c.env.GATEWAY_SECRET) { if (secret !== c.env.WORKFLOW_DASHBOARD_SECRET) {
return c.json({ error: "unauthorized" }, 401); return c.json({ error: "unauthorized" }, 401);
} }
@@ -242,7 +241,7 @@ gateway.post("/register", async (c) => {
gateway.delete("/register/:name", async (c) => { gateway.delete("/register/:name", async (c) => {
const auth = c.req.header("Authorization"); const auth = c.req.header("Authorization");
if (auth !== `Bearer ${c.env.GATEWAY_SECRET}`) { if (auth !== `Bearer ${c.env.WORKFLOW_DASHBOARD_SECRET}`) {
return c.json({ error: "unauthorized" }, 401); return c.json({ error: "unauthorized" }, 401);
} }
@@ -308,7 +307,7 @@ app.all("/api/clients/:client/*", async (c) => {
const proxyResp = await fetchThroughClientSocket( const proxyResp = await fetchThroughClientSocket(
c.env, c.env,
client, client,
c.env.GATEWAY_SECRET, c.env.WORKFLOW_DASHBOARD_SECRET,
wsRequest, wsRequest,
); );
if (proxyResp.status !== 503) { if (proxyResp.status !== 503) {
+1 -1
View File
@@ -17,4 +17,4 @@ new_sqlite_classes = ["AgentSocket"]
tag = "rename-agent-to-client" tag = "rename-agent-to-client"
renamed_classes = [{ from = "AgentSocket", to = "ClientSocket" }] renamed_classes = [{ from = "AgentSocket", to = "ClientSocket" }]
# GATEWAY_SECRET is set via `wrangler secret put` # WORKFLOW_DASHBOARD_SECRET is set via `wrangler secret put`
@@ -1,14 +1,16 @@
/** /**
* develop bundle entry — 小橘 🍊 * develop bundle entry — 小橘 🍊
* *
* All roles use cursor-agent with workspace auto-extracted from context. * planner/coder/reviewer → cursor-agent (needs code editing)
* tester/committer → hermes-agent (lightweight, no editing needed)
*/ */
import { createCursorAgent } from "@uncaged/workflow-agent-cursor"; import { createCursorAgent } from "@uncaged/workflow-agent-cursor";
import { createHermesAgent } from "@uncaged/workflow-agent-hermes";
import { createWorkflow } from "@uncaged/workflow-runtime"; import { createWorkflow } from "@uncaged/workflow-runtime";
import { optionalEnv, requireEnv } from "@uncaged/workflow-util"; import { optionalEnv, requireEnv } from "@uncaged/workflow-util";
import { buildDevelopDescriptor, developWorkflowDefinition } from "./src/index.js"; import { buildDevelopDescriptor, developWorkflowDefinition } from "./src/index.js";
const adapter = createCursorAgent({ const cursorAdapter = createCursorAgent({
command: requireEnv("WORKFLOW_CURSOR_COMMAND", "set WORKFLOW_CURSOR_COMMAND (e.g. cursor-agent)"), command: requireEnv("WORKFLOW_CURSOR_COMMAND", "set WORKFLOW_CURSOR_COMMAND (e.g. cursor-agent)"),
model: optionalEnv("WORKFLOW_CURSOR_MODEL"), model: optionalEnv("WORKFLOW_CURSOR_MODEL"),
timeout: optionalEnv("WORKFLOW_CURSOR_TIMEOUT") timeout: optionalEnv("WORKFLOW_CURSOR_TIMEOUT")
@@ -17,7 +19,21 @@ const adapter = createCursorAgent({
workspace: null, workspace: null,
}); });
const wf = createWorkflow(developWorkflowDefinition, { adapter, overrides: null }); const hermesAdapter = createHermesAgent({
command: requireEnv("WORKFLOW_HERMES_COMMAND", "set WORKFLOW_HERMES_COMMAND (absolute path to hermes CLI)"),
model: optionalEnv("WORKFLOW_HERMES_MODEL"),
timeout: optionalEnv("WORKFLOW_HERMES_TIMEOUT")
? Number(optionalEnv("WORKFLOW_HERMES_TIMEOUT"))
: null,
});
const wf = createWorkflow(developWorkflowDefinition, {
adapter: cursorAdapter,
overrides: {
tester: hermesAdapter,
committer: hermesAdapter,
},
});
export const descriptor = buildDevelopDescriptor(); export const descriptor = buildDevelopDescriptor();
export const run = wf; export const run = wf;