diff --git a/scenarios/dashboard-session-send-turn-existing-user.json b/scenarios/dashboard-session-send-turn-existing-user.json new file mode 100644 index 0000000..1ea5f79 --- /dev/null +++ b/scenarios/dashboard-session-send-turn-existing-user.json @@ -0,0 +1,62 @@ +{ + "id": "dashboard-session-send-turn-existing-user", + "surface": "dashboard-session-send-turn", + "title": "Dashboard Session Send Existing User Turn", + "objective": "Clone an existing OpenClaw env, move the clone to the target runtime, send a dashboard-style user message through Gateway sessions.send, and measure response latency without mutating the durable source env.", + "tags": ["agent", "message", "dashboard", "sessions", "gateway", "providers", "existing-user", "live"], + "timeoutMs": 240000, + "agent": { + "expectedText": "KOVA_AGENT_OK" + }, + "thresholds": { + "upgradeMs": 120000, + "gatewayReadyMs": 45000, + "agentTurnMs": 45000, + "preProviderMs": 10000, + "providerFinalMs": 3000, + "preProviderDominanceRatio": 0.8, + "statusMs": 10000, + "peakRssMb": 900, + "missingDependencyErrors": 0, + "pluginLoadFailures": 0 + }, + "phases": [ + { + "id": "clone", + "title": "Clone Existing Env", + "intent": "Clone a durable existing user env into a disposable Kova env before runtime or message testing.", + "commands": ["ocm env clone {sourceEnv} {env} --json"], + "evidence": ["source env", "clone root", "cloned OpenClaw config"] + }, + { + "id": "upgrade", + "title": "Move Clone To Target Runtime", + "intent": "Run the real OCM upgrade path so the cloned user state runs the requested OpenClaw target runtime.", + "commands": ["ocm upgrade {env} {upgradeSelector} --json", "ocm service status {env} --json"], + "evidence": ["upgrade JSON", "runtime binding", "post-upgrade service state"] + }, + { + "id": "gateway-start", + "title": "Start Gateway", + "intent": "Start the cloned gateway after upgrade and wait for readiness before sending a dashboard-style message.", + "commands": ["ocm service install {env} --json", "ocm service start {env} --json"], + "evidence": ["gateway service installed", "gateway service started", "startup readiness"] + }, + { + "id": "dashboard-session-turn", + "title": "Dashboard Session Message", + "intent": "Exercise Gateway sessions.send on cloned user state and verify final assistant text appears in chat history.", + "commands": [ + "node {kovaRoot}/support/run-dashboard-session-send-turn.mjs --env {env} --session-key kova-dashboard-existing-user --message 'Reply with exact ASCII text KOVA_AGENT_OK only.' --expected-text KOVA_AGENT_OK --timeout 120000" + ], + "evidence": ["sessions.create timing", "sessions.send timing", "time to assistant text", "provider timing", "gateway health after turn", "role resource samples"] + }, + { + "id": "post-dashboard-health", + "title": "Post-Dashboard Gateway Health", + "intent": "Verify the cloned gateway remains responsive after the dashboard-style turn and collect logs for embedded-run/liveness evidence.", + "commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"], + "evidence": ["gateway status", "embedded-run traces", "liveness warnings", "plugin errors", "memory after dashboard turn"] + } + ] +} diff --git a/src/auth.mjs b/src/auth.mjs index 8fb3549..b113067 100644 --- a/src/auth.mjs +++ b/src/auth.mjs @@ -77,6 +77,31 @@ export async function resolveRunAuthContext(flags = {}) { const store = await loadCredentialStore(); const live = await verifyLiveCredentialStatus(liveCredentialStatus(store)); + if (requestedMode === "live" && !live.available && flags.source_env) { + const inheritedLive = { + available: true, + providerId: null, + method: "source-env", + externalCli: null, + fallbackFrom: null, + fallbackPolicy: null, + envVars: [], + reason: `inherited from cloned source env ${flags.source_env}`, + verification: { + checked: false, + status: "inherited-source-env", + reason: "Kova will clone the source env and preserve its OpenClaw auth/config state" + } + }; + return { + schemaVersion: "kova.auth.context.v1", + requestedMode, + credentialStore: credentialStoreSummary(store), + liveEnv: store.liveEnv, + live: inheritedLive, + redactionValues: secretValues(store.liveEnv) + }; + } if (requestedMode === "live" && !live.available) { throw new Error(`--auth live requires configured live credentials: ${live.reason}`); } @@ -129,8 +154,8 @@ export function scenarioAuthPolicy(context, scenario, state) { externalCli: live.externalCli ?? null, fallbackFrom: live.fallbackFrom ?? null, fallbackPolicy: live.fallbackPolicy ?? null, - setup: true, - setupKind: liveAuthSetupKind(live), + setup: live.method !== "source-env", + setupKind: live.method === "source-env" ? "source-env-inherited" : liveAuthSetupKind(live), commandEnv: env, redactionValues: [...(context.auth?.redactionValues ?? []), ...secretValues(env)], summary: authDisplay({ @@ -140,8 +165,8 @@ export function scenarioAuthPolicy(context, scenario, state) { externalCli: live.externalCli ?? null, fallbackFrom: live.fallbackFrom ?? null, fallbackPolicy: live.fallbackPolicy ?? null, - setup: true, - setupKind: liveAuthSetupKind(live), + setup: live.method !== "source-env", + setupKind: live.method === "source-env" ? "source-env-inherited" : liveAuthSetupKind(live), envVars: live.envVars }) }; diff --git a/src/selfcheck.mjs b/src/selfcheck.mjs index e440953..ea5ee83 100644 --- a/src/selfcheck.mjs +++ b/src/selfcheck.mjs @@ -186,6 +186,20 @@ export async function runSelfCheck(flags = {}) { throw new Error(`missing auth override should not inject auth phases: ${phaseIds.join(", ")}`); } })); + checks.push(await jsonCommandCheck("run-auth-live-source-env-json", `node bin/kova.mjs run --auth live --target runtime:stable --scenario dashboard-session-send-turn-existing-user --source-env 'Team Env' --report-dir ${quoteShell(tmp)} --json`, async (data) => { + const report = JSON.parse(await readFile(data.jsonPath, "utf8")); + const record = report.records?.[0]; + assertEqual(record?.auth?.mode, "live", "source-env live auth mode"); + assertEqual(record?.auth?.source, "source-env", "source-env live auth source"); + assertEqual(record?.auth?.setup, false, "source-env live auth does not patch config"); + const phaseIds = record?.phases?.map((phase) => phase.id) ?? []; + if (phaseIds.includes("auth-setup") || phaseIds.includes("auth-prepare")) { + throw new Error(`source-env live auth should not inject auth phases: ${phaseIds.join(", ")}`); + } + const commands = record?.phases?.flatMap((phase) => phase.commands ?? []) ?? []; + assertEqual(commands.some((command) => command.includes("ocm env clone 'Team Env'")), true, "source env clone command present"); + assertEqual(commands.some((command) => command.includes("run-dashboard-session-send-turn.mjs")), true, "dashboard session helper command present"); + })); for (const item of [ ["agent-gateway-rpc-turn", "agent-gateway-rpc-turn", "ocm @"], ["dashboard-session-send-turn", "dashboard-session-send-turn", "run-dashboard-session-send-turn.mjs"],