3062 lines
93 KiB
JavaScript
3062 lines
93 KiB
JavaScript
import http from 'node:http'
|
|
import { randomUUID } from 'node:crypto'
|
|
|
|
const port = Number.parseInt(process.env.NEXUS_QA_API_PORT ?? '8080', 10)
|
|
const qaHeader = 'simulated-openclaw-ui-qa'
|
|
const projectId = '11111111-1111-4111-8111-111111111111'
|
|
const secondProjectId = '22222222-2222-4222-8222-222222222222'
|
|
const taskIds = {
|
|
running: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
|
|
review: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
|
|
blocked: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
|
|
done: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd',
|
|
}
|
|
const initialDurableRunId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee'
|
|
|
|
const now = () => new Date().toISOString()
|
|
const minutesAgo = value => new Date(Date.now() - value * 60_000).toISOString()
|
|
const minutesFromNow = value => new Date(Date.now() + value * 60_000).toISOString()
|
|
const clone = value => structuredClone(value)
|
|
|
|
const durableRuns = [
|
|
{
|
|
id: initialDurableRunId,
|
|
title: 'QA SIMULATION · OpenClaw connection readiness audit',
|
|
prompt: '[QA SIMULATION ONLY — no live OpenClaw side effect]\n\nAudit the Nexus control plane, correlate the active task and project, then report the next safe owner decision.',
|
|
agentId: 'programmer',
|
|
sessionKey: 'agent:programmer:main',
|
|
status: 'running',
|
|
taskId: taskIds.running,
|
|
projectId,
|
|
openClawRunId: 'qa-sim-openclaw-run-protocol-v4',
|
|
retriedFromRunId: null,
|
|
correlationId: 'qa-sim-correlation-readiness-audit',
|
|
actor: 'qa-fixture-owner',
|
|
lastError: null,
|
|
lastGatewaySequence: 42,
|
|
sequenceGapDetected: false,
|
|
canStop: true,
|
|
canRetry: false,
|
|
canResume: false,
|
|
resumeCapabilityMessage: 'QA simulation: same-run resume is intentionally unsupported until the Gateway advertises a documented capability.',
|
|
createdAt: minutesAgo(42),
|
|
updatedAt: minutesAgo(2),
|
|
startedAt: minutesAgo(41),
|
|
finishedAt: null,
|
|
},
|
|
]
|
|
|
|
const durableRunHistory = new Map([
|
|
[
|
|
initialDurableRunId,
|
|
[
|
|
{
|
|
id: 1,
|
|
runId: initialDurableRunId,
|
|
action: 'start_requested',
|
|
fromStatus: 'dispatching',
|
|
toStatus: 'dispatching',
|
|
message: 'QA SIMULATION: Nexus durably recorded the run before simulated dispatch.',
|
|
actor: 'qa-fixture-owner',
|
|
correlationId: 'qa-sim-correlation-readiness-audit',
|
|
idempotencyKey: 'qa-sim-initial-start',
|
|
traceParent: '00-11111111111111111111111111111111-2222222222222222-01',
|
|
gatewayEventId: null,
|
|
gatewaySequence: null,
|
|
sequenceGapDetected: false,
|
|
resultRunId: null,
|
|
occurredAt: minutesAgo(42),
|
|
},
|
|
{
|
|
id: 2,
|
|
runId: initialDurableRunId,
|
|
action: 'start_result',
|
|
fromStatus: 'dispatching',
|
|
toStatus: 'running',
|
|
message: 'QA SIMULATION: the fixture accepted the run and projected a simulated Gateway sequence.',
|
|
actor: 'qa-fixture-openclaw',
|
|
correlationId: 'qa-sim-correlation-readiness-audit',
|
|
idempotencyKey: null,
|
|
traceParent: '00-11111111111111111111111111111111-2222222222222222-01',
|
|
gatewayEventId: 'qa-sim-gateway-event-42',
|
|
gatewaySequence: 42,
|
|
sequenceGapDetected: false,
|
|
resultRunId: null,
|
|
occurredAt: minutesAgo(41),
|
|
},
|
|
],
|
|
],
|
|
])
|
|
let durableRunTransitionSequence = 2
|
|
|
|
const chatHistory = [
|
|
{
|
|
role: 'assistant',
|
|
content: 'Nexus is connected to the simulated OpenClaw QA contract. Live credentials are not used.',
|
|
timestamp: minutesAgo(18),
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: 'Summarize the current integration boundary.',
|
|
timestamp: minutesAgo(17),
|
|
},
|
|
{
|
|
role: 'assistant',
|
|
content: 'The browser calls Nexus; Nexus authenticates and normalizes protocol-v4 operations; OpenClaw owns model routing.',
|
|
timestamp: minutesAgo(17),
|
|
},
|
|
]
|
|
|
|
const projects = [
|
|
{
|
|
id: projectId,
|
|
name: 'OpenClaw Core Integration',
|
|
description: 'Typed Nexus control plane, operator actions, and recovery diagnostics.',
|
|
status: 'Active',
|
|
progress: 74,
|
|
updatedAt: minutesAgo(11),
|
|
},
|
|
{
|
|
id: secondProjectId,
|
|
name: 'Agent-first Mission Control',
|
|
description: 'Operational cockpit for delegated work, approvals, and durable evidence.',
|
|
status: 'Active',
|
|
progress: 58,
|
|
updatedAt: minutesAgo(34),
|
|
},
|
|
]
|
|
|
|
const nexusTasks = [
|
|
{
|
|
id: taskIds.running,
|
|
title: 'Validate OpenClaw protocol v4 handshake',
|
|
detail: 'Verify challenge, signed connect, hello-ok, and advertised methods.',
|
|
source: 'iris',
|
|
state: 'In progress',
|
|
priority: 'Critical',
|
|
assignedTo: 'programmer',
|
|
projectId,
|
|
parentTaskId: null,
|
|
dueDate: minutesFromNow(180),
|
|
createdAt: minutesAgo(150),
|
|
updatedAt: minutesAgo(4),
|
|
isAgentTask: true,
|
|
expectedFrom: 'programmer',
|
|
lastActivityMessage: 'Contract tests passed; browser boundary is under review.',
|
|
lastActivityAt: minutesAgo(4),
|
|
childTasks: [],
|
|
childTaskCount: 2,
|
|
openChildTaskCount: 1,
|
|
hasVisibleDelegation: true,
|
|
},
|
|
{
|
|
id: taskIds.review,
|
|
title: 'Approve production cron replay policy',
|
|
detail: 'Owner confirmation is required before force-running scheduled work.',
|
|
source: 'programmer',
|
|
state: 'Review',
|
|
priority: 'High',
|
|
assignedTo: 'bao',
|
|
projectId,
|
|
parentTaskId: taskIds.running,
|
|
dueDate: minutesFromNow(90),
|
|
createdAt: minutesAgo(90),
|
|
updatedAt: minutesAgo(9),
|
|
isAgentTask: true,
|
|
expectedFrom: 'bao',
|
|
lastActivityMessage: 'Waiting for owner decision.',
|
|
lastActivityAt: minutesAgo(9),
|
|
childTasks: [],
|
|
childTaskCount: 0,
|
|
openChildTaskCount: 0,
|
|
hasVisibleDelegation: false,
|
|
},
|
|
{
|
|
id: taskIds.blocked,
|
|
title: 'Pair remote OpenClaw device identity',
|
|
detail: 'Remote gateway topology needs an explicitly paired device identity.',
|
|
source: 'iris',
|
|
state: 'Blocked',
|
|
priority: 'High',
|
|
assignedTo: 'iris',
|
|
projectId,
|
|
parentTaskId: null,
|
|
dueDate: null,
|
|
createdAt: minutesAgo(220),
|
|
updatedAt: minutesAgo(24),
|
|
isAgentTask: true,
|
|
expectedFrom: 'bao',
|
|
lastActivityMessage: 'Blocked until the remote gateway pairing flow is completed.',
|
|
lastActivityAt: minutesAgo(24),
|
|
childTasks: [],
|
|
childTaskCount: 0,
|
|
openChildTaskCount: 0,
|
|
hasVisibleDelegation: false,
|
|
},
|
|
{
|
|
id: taskIds.done,
|
|
title: 'Normalize model catalog through Nexus',
|
|
detail: 'Provider credentials remain inside OpenClaw.',
|
|
source: 'programmer',
|
|
state: 'Done',
|
|
priority: 'Normal',
|
|
assignedTo: 'programmer',
|
|
projectId: secondProjectId,
|
|
parentTaskId: null,
|
|
dueDate: null,
|
|
createdAt: minutesAgo(500),
|
|
updatedAt: minutesAgo(73),
|
|
isAgentTask: true,
|
|
expectedFrom: null,
|
|
lastActivityMessage: 'Model catalog normalization completed.',
|
|
lastActivityAt: minutesAgo(73),
|
|
childTasks: [],
|
|
childTaskCount: 0,
|
|
openChildTaskCount: 0,
|
|
hasVisibleDelegation: false,
|
|
},
|
|
]
|
|
|
|
const openClawTasks = [
|
|
{
|
|
id: 'task-live-001',
|
|
title: 'Protocol compatibility audit',
|
|
status: 'running',
|
|
kind: 'agent',
|
|
runtime: 'openclaw',
|
|
agentId: 'programmer',
|
|
sessionKey: 'agent:programmer:main',
|
|
runId: 'run-protocol-4d9f1a',
|
|
flowId: 'flow-core-integration',
|
|
parentTaskId: null,
|
|
createdAt: minutesAgo(42),
|
|
startedAt: minutesAgo(39),
|
|
updatedAt: minutesAgo(2),
|
|
finishedAt: null,
|
|
progress: 68,
|
|
summary: 'Comparing advertised Gateway methods with the Nexus capability map.',
|
|
error: null,
|
|
canCancel: true,
|
|
},
|
|
{
|
|
id: 'task-live-002',
|
|
title: 'Index mission-control evidence',
|
|
status: 'queued',
|
|
kind: 'subagent',
|
|
runtime: 'openclaw',
|
|
agentId: 'researcher',
|
|
sessionKey: 'agent:researcher:main',
|
|
runId: 'run-evidence-1730ee',
|
|
flowId: 'flow-core-integration',
|
|
parentTaskId: 'task-live-001',
|
|
createdAt: minutesAgo(8),
|
|
startedAt: null,
|
|
updatedAt: minutesAgo(8),
|
|
finishedAt: null,
|
|
progress: 0,
|
|
summary: 'Waiting for one active worker slot.',
|
|
error: null,
|
|
canCancel: true,
|
|
},
|
|
{
|
|
id: 'task-live-003',
|
|
title: 'Remote device pairing probe',
|
|
status: 'failed',
|
|
kind: 'gateway',
|
|
runtime: 'openclaw',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
runId: 'run-pairing-a92c0f',
|
|
flowId: 'flow-connectivity',
|
|
parentTaskId: null,
|
|
createdAt: minutesAgo(92),
|
|
startedAt: minutesAgo(90),
|
|
updatedAt: minutesAgo(84),
|
|
finishedAt: minutesAgo(84),
|
|
progress: 31,
|
|
summary: 'Direct loopback works; remote topology requires a paired device.',
|
|
error: 'Device identity is required for non-loopback Gateway connections.',
|
|
canCancel: false,
|
|
},
|
|
{
|
|
id: 'task-live-004',
|
|
title: 'Model catalog synchronization',
|
|
status: 'succeeded',
|
|
kind: 'agent',
|
|
runtime: 'openclaw',
|
|
agentId: 'programmer',
|
|
sessionKey: 'agent:programmer:main',
|
|
runId: 'run-models-0f9a12',
|
|
flowId: 'flow-model-plane',
|
|
parentTaskId: null,
|
|
createdAt: minutesAgo(140),
|
|
startedAt: minutesAgo(137),
|
|
updatedAt: minutesAgo(119),
|
|
finishedAt: minutesAgo(119),
|
|
progress: 100,
|
|
summary: 'Five configured models normalized for the Nexus frontend.',
|
|
error: null,
|
|
canCancel: false,
|
|
},
|
|
]
|
|
|
|
const sessions = [
|
|
{
|
|
key: 'agent:iris:main',
|
|
sessionId: 'session-iris-main',
|
|
agentId: 'iris',
|
|
title: 'Mission-control orchestration',
|
|
status: 'active',
|
|
kind: 'agent',
|
|
channel: 'web',
|
|
model: 'openai/gpt-5.4',
|
|
provider: 'openai',
|
|
runId: 'run-orchestrator-8cd221',
|
|
updatedAt: minutesAgo(1),
|
|
inputTokens: 18_420,
|
|
outputTokens: 4_980,
|
|
totalTokens: 23_400,
|
|
canAbort: true,
|
|
},
|
|
{
|
|
key: 'agent:programmer:main',
|
|
sessionId: 'session-programmer-main',
|
|
agentId: 'programmer',
|
|
title: 'Protocol compatibility audit',
|
|
status: 'running',
|
|
kind: 'agent',
|
|
channel: 'internal',
|
|
model: 'openai/gpt-5.4-codex',
|
|
provider: 'openai',
|
|
runId: 'run-protocol-4d9f1a',
|
|
updatedAt: minutesAgo(2),
|
|
inputTokens: 41_210,
|
|
outputTokens: 8_640,
|
|
totalTokens: 49_850,
|
|
canAbort: true,
|
|
},
|
|
{
|
|
key: 'agent:researcher:main',
|
|
sessionId: 'session-researcher-main',
|
|
agentId: 'researcher',
|
|
title: 'OpenClaw documentation review',
|
|
status: 'idle',
|
|
kind: 'agent',
|
|
channel: 'internal',
|
|
model: 'openai/gpt-5.4-mini',
|
|
provider: 'openai',
|
|
runId: null,
|
|
updatedAt: minutesAgo(16),
|
|
inputTokens: 9_330,
|
|
outputTokens: 2_110,
|
|
totalTokens: 11_440,
|
|
canAbort: false,
|
|
},
|
|
]
|
|
|
|
const cronJobs = [
|
|
{
|
|
id: 'cron-runtime-health',
|
|
name: 'Runtime health sweep',
|
|
description: 'Checks Gateway reachability, version pin, and stale sessions.',
|
|
schedule: '*/15 * * * *',
|
|
timeZone: 'Europe/Berlin',
|
|
enabled: true,
|
|
status: 'ready',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
nextRunAt: minutesFromNow(7),
|
|
lastRunAt: minutesAgo(8),
|
|
lastRunStatus: 'succeeded',
|
|
lastError: null,
|
|
canRun: true,
|
|
resourceHash: 'qa-cron-runtime-health-v1',
|
|
},
|
|
{
|
|
id: 'cron-daily-brief',
|
|
name: 'Owner operations brief',
|
|
description: 'Summarizes delegated work, blockers, approvals, and cost.',
|
|
schedule: '0 8 * * *',
|
|
timeZone: 'Europe/Berlin',
|
|
enabled: true,
|
|
status: 'ready',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
nextRunAt: minutesFromNow(640),
|
|
lastRunAt: minutesAgo(810),
|
|
lastRunStatus: 'succeeded',
|
|
lastError: null,
|
|
canRun: true,
|
|
resourceHash: 'qa-cron-daily-brief-v1',
|
|
},
|
|
{
|
|
id: 'cron-evidence-backup',
|
|
name: 'Evidence packet backup',
|
|
description: 'Persists audit evidence after an accepted milestone.',
|
|
schedule: '30 23 * * 5',
|
|
timeZone: 'Europe/Berlin',
|
|
enabled: false,
|
|
status: 'disabled',
|
|
agentId: 'archivist',
|
|
sessionKey: null,
|
|
nextRunAt: null,
|
|
lastRunAt: minutesAgo(11_500),
|
|
lastRunStatus: 'failed',
|
|
lastError: 'Destination is intentionally disabled in QA.',
|
|
canRun: true,
|
|
resourceHash: 'qa-cron-evidence-backup-v1',
|
|
},
|
|
]
|
|
|
|
const approvals = [
|
|
{
|
|
id: 'approval-shell-001',
|
|
kind: 'exec',
|
|
title: 'Run production diagnostics',
|
|
description: 'Inspect the remote Gateway service without changing its state.',
|
|
status: 'pending',
|
|
severity: 'high',
|
|
command: 'openclaw gateway status --deep',
|
|
workingDirectory: 'C:\\Noveria\\OpenClaw',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
requestedAt: minutesAgo(6),
|
|
expiresAt: minutesFromNow(24),
|
|
allowedDecisions: ['allow-once', 'allow-always', 'deny'],
|
|
canResolve: true,
|
|
},
|
|
{
|
|
id: 'approval-cron-002',
|
|
kind: 'cron',
|
|
title: 'Force daily brief replay',
|
|
description: 'Re-run the owner brief after a partial provider timeout.',
|
|
status: 'pending',
|
|
severity: 'medium',
|
|
command: 'cron.run cron-daily-brief mode=force',
|
|
workingDirectory: null,
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
requestedAt: minutesAgo(13),
|
|
expiresAt: minutesFromNow(47),
|
|
allowedDecisions: ['allow-once', 'deny'],
|
|
canResolve: true,
|
|
},
|
|
]
|
|
|
|
const activity = [
|
|
{
|
|
id: 'evt-001',
|
|
eventType: 'task.updated',
|
|
kind: 'task',
|
|
action: 'Task progress updated',
|
|
status: 'running',
|
|
message: 'Protocol compatibility audit reached 68 percent.',
|
|
severity: 'info',
|
|
actor: 'programmer',
|
|
agentId: 'programmer',
|
|
sessionKey: 'agent:programmer:main',
|
|
runId: 'run-protocol-4d9f1a',
|
|
occurredAt: minutesAgo(2),
|
|
source: 'OpenClaw Gateway',
|
|
},
|
|
{
|
|
id: 'evt-002',
|
|
eventType: 'approval.requested',
|
|
kind: 'approval',
|
|
action: 'Owner approval requested',
|
|
status: 'pending',
|
|
message: 'Iris requested permission to run production diagnostics.',
|
|
severity: 'high',
|
|
actor: 'iris',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
runId: 'run-orchestrator-8cd221',
|
|
occurredAt: minutesAgo(6),
|
|
source: 'OpenClaw Gateway',
|
|
},
|
|
{
|
|
id: 'evt-003',
|
|
eventType: 'cron.completed',
|
|
kind: 'cron',
|
|
action: 'Runtime sweep completed',
|
|
status: 'succeeded',
|
|
message: 'Gateway protocol v4 and the required version pin are healthy.',
|
|
severity: 'success',
|
|
actor: 'scheduler',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
runId: 'cron-run-913',
|
|
occurredAt: minutesAgo(8),
|
|
source: 'OpenClaw Gateway',
|
|
},
|
|
{
|
|
id: 'evt-004',
|
|
eventType: 'session.model',
|
|
kind: 'session',
|
|
action: 'Session model resolved',
|
|
status: 'ready',
|
|
message: 'Programmer session is using openai/gpt-5.4-codex.',
|
|
severity: 'info',
|
|
actor: 'runtime',
|
|
agentId: 'programmer',
|
|
sessionKey: 'agent:programmer:main',
|
|
runId: 'run-protocol-4d9f1a',
|
|
occurredAt: minutesAgo(19),
|
|
source: 'OpenClaw Gateway',
|
|
},
|
|
{
|
|
id: 'evt-005',
|
|
eventType: 'task.failed',
|
|
kind: 'task',
|
|
action: 'Remote pairing probe failed',
|
|
status: 'failed',
|
|
message: 'A paired device identity is required outside direct loopback.',
|
|
severity: 'high',
|
|
actor: 'gateway',
|
|
agentId: 'iris',
|
|
sessionKey: 'agent:iris:main',
|
|
runId: 'run-pairing-a92c0f',
|
|
occurredAt: minutesAgo(84),
|
|
source: 'OpenClaw Gateway',
|
|
},
|
|
]
|
|
|
|
const models = [
|
|
{
|
|
id: 'openai/gpt-5.4',
|
|
name: 'GPT-5.4',
|
|
provider: 'openai',
|
|
configured: true,
|
|
available: true,
|
|
contextWindow: 1_050_000,
|
|
reason: null,
|
|
},
|
|
{
|
|
id: 'openai/gpt-5.4-codex',
|
|
name: 'GPT-5.4 Codex',
|
|
provider: 'openai',
|
|
configured: true,
|
|
available: true,
|
|
contextWindow: 400_000,
|
|
reason: null,
|
|
},
|
|
{
|
|
id: 'openai/gpt-5.4-mini',
|
|
name: 'GPT-5.4 mini',
|
|
provider: 'openai',
|
|
configured: true,
|
|
available: true,
|
|
contextWindow: 400_000,
|
|
reason: null,
|
|
},
|
|
{
|
|
id: 'anthropic/claude-opus-4.1',
|
|
name: 'Claude Opus 4.1',
|
|
provider: 'anthropic',
|
|
configured: true,
|
|
available: false,
|
|
contextWindow: 200_000,
|
|
reason: 'Provider credential intentionally absent in this QA fixture.',
|
|
},
|
|
{
|
|
id: 'ollama/qwen3:14b',
|
|
name: 'Qwen3 14B',
|
|
provider: 'ollama',
|
|
configured: true,
|
|
available: true,
|
|
contextWindow: 32_768,
|
|
reason: null,
|
|
},
|
|
]
|
|
|
|
const modelAuthProviders = [
|
|
{
|
|
provider: 'openai',
|
|
displayName: 'OpenAI',
|
|
status: 'ok',
|
|
expiry: {
|
|
at: minutesFromNow(1_440),
|
|
remainingMs: 86_400_000,
|
|
label: '1 day',
|
|
},
|
|
profiles: [
|
|
{ type: 'oauth', status: 'ok', count: 1 },
|
|
{ type: 'api_key', status: 'static', count: 1 },
|
|
],
|
|
apiKey: { source: 'env', envVar: 'OPENAI_API_KEY' },
|
|
usage: { summary: 'Healthy provider authorization', plan: 'OpenClaw-managed' },
|
|
},
|
|
{
|
|
provider: 'anthropic',
|
|
displayName: 'Anthropic',
|
|
status: 'missing',
|
|
expiry: null,
|
|
profiles: [],
|
|
apiKey: null,
|
|
usage: null,
|
|
},
|
|
{
|
|
provider: 'ollama',
|
|
displayName: 'Ollama',
|
|
status: 'static',
|
|
expiry: null,
|
|
profiles: [],
|
|
apiKey: { source: 'config', envVar: null },
|
|
usage: { summary: 'Local runtime', plan: null },
|
|
},
|
|
]
|
|
|
|
const cronRunHistory = new Map(cronJobs.map((job, index) => [
|
|
job.id,
|
|
[{
|
|
id: `qa-cron-history-${index + 1}`,
|
|
jobId: job.id,
|
|
jobName: job.name,
|
|
runId: `qa-run-${job.id}`,
|
|
status: job.lastRunStatus ?? 'succeeded',
|
|
action: 'finished',
|
|
summary: job.lastError ? null : 'QA simulation completed without a live OpenClaw side effect.',
|
|
error: job.lastError,
|
|
errorReason: job.lastError ? 'qa_simulation' : null,
|
|
deliveryStatus: 'not_configured',
|
|
deliveryError: null,
|
|
delivered: null,
|
|
triggerFired: true,
|
|
diagnosticsSummary: 'Synthetic browser QA evidence.',
|
|
diagnostics: [],
|
|
sessionId: null,
|
|
sessionKey: job.sessionKey,
|
|
occurredAt: job.lastRunAt,
|
|
runAt: job.lastRunAt,
|
|
durationMs: 1_480 + index * 210,
|
|
nextRunAt: job.nextRunAt,
|
|
model: 'openai/gpt-5.4-mini',
|
|
provider: 'openai',
|
|
inputTokens: 820,
|
|
outputTokens: 144,
|
|
totalTokens: 964,
|
|
}],
|
|
]))
|
|
|
|
const setupStatus = {
|
|
profileId: 'primary',
|
|
state: 'experimental_blocked',
|
|
experimentalBlocked: true,
|
|
hasProfile: true,
|
|
endpoint: 'ws://openclaw-gateway:18789',
|
|
discoverySource: 'docker-dns',
|
|
adoptionState: 'adopted',
|
|
managementEnabled: true,
|
|
requiredVersion: '2026.7.1',
|
|
gatewayVersion: 'QA SIMULATION · OpenClaw 2026.7.1',
|
|
protocolVersion: 4,
|
|
deviceId: 'qa-simulation-device',
|
|
deviceTokenConfigured: true,
|
|
pairingRequired: false,
|
|
pairingRequestId: null,
|
|
grantedScopes: ['operator.read', 'operator.admin'],
|
|
advertisedMethods: [
|
|
'agents.list',
|
|
'agents.files.list',
|
|
'agents.files.get',
|
|
'agents.files.set',
|
|
'agents.workspace.list',
|
|
'agents.workspace.get',
|
|
'config.get',
|
|
'config.schema.lookup',
|
|
'config.patch',
|
|
'cron.list',
|
|
'cron.status',
|
|
'cron.runs',
|
|
'cron.add',
|
|
'cron.update',
|
|
'cron.remove',
|
|
'cron.run',
|
|
'models.authStatus',
|
|
'wizard.start',
|
|
'wizard.next',
|
|
'wizard.status',
|
|
'wizard.cancel',
|
|
],
|
|
capabilityHash: 'qa-capability-hash-v1',
|
|
revision: 7,
|
|
lastProbedAt: minutesAgo(15),
|
|
lastVerifiedAt: minutesAgo(12),
|
|
adoptedAt: minutesAgo(10),
|
|
updatedAt: minutesAgo(2),
|
|
message: 'QA simulation profile. Production attach remains blocked until OpenClaw accepts an external Nexus client ID.',
|
|
recovery: 'Use this fixture only for UI acceptance; it does not represent a live Gateway write.',
|
|
checkedAt: now(),
|
|
}
|
|
|
|
let configSnapshot = {
|
|
exists: true,
|
|
valid: true,
|
|
hash: 'qa-config-hash-v1',
|
|
config: {
|
|
agents: {
|
|
defaults: { model: 'openai/gpt-5.4', workspace: '/data/openclaw/workspace' },
|
|
},
|
|
cron: { enabled: true, maxConcurrentRuns: 2 },
|
|
gateway: { mode: 'local', bind: 'lan' },
|
|
},
|
|
issues: [],
|
|
warnings: ['QA fixture: secrets and provider credentials are intentionally omitted.'],
|
|
checkedAt: now(),
|
|
}
|
|
|
|
const wizardSessions = new Map()
|
|
|
|
const openClawAgents = [
|
|
{
|
|
id: 'iris',
|
|
name: 'Iris',
|
|
description: 'Primary OpenClaw orchestrator and owner interface.',
|
|
model: 'openai/gpt-5.4',
|
|
provider: 'openai',
|
|
workspace: 'C:\\Noveria\\Iris',
|
|
status: 'active',
|
|
},
|
|
{
|
|
id: 'programmer',
|
|
name: 'Programmer',
|
|
description: 'Implements and verifies Nexus integration work.',
|
|
model: 'openai/gpt-5.4-codex',
|
|
provider: 'openai',
|
|
workspace: 'C:\\Noveria\\Nexus',
|
|
status: 'working',
|
|
},
|
|
{
|
|
id: 'researcher',
|
|
name: 'Researcher',
|
|
description: 'Collects primary-source OpenClaw evidence.',
|
|
model: 'openai/gpt-5.4-mini',
|
|
provider: 'openai',
|
|
workspace: 'C:\\Noveria\\Research',
|
|
status: 'queued',
|
|
},
|
|
{
|
|
id: 'archivist',
|
|
name: 'Archivist',
|
|
description: 'Maintains durable project evidence and handoffs.',
|
|
model: 'openai/gpt-5.4-mini',
|
|
provider: 'openai',
|
|
workspace: 'C:\\Noveria\\Evidence',
|
|
status: 'idle',
|
|
},
|
|
]
|
|
|
|
const agentFiles = new Map(openClawAgents.map(agent => [
|
|
agent.id,
|
|
new Map([
|
|
['AGENTS.md', `# ${agent.name} standing orders\n\n## Mission\nOperate as an inspectable OpenClaw agent.\n`],
|
|
['SOUL.md', `# ${agent.name} soul\n\nBe precise, safe, and evidence-led.\n`],
|
|
['TOOLS.md', '# Tools\n\nUse only capabilities advertised by OpenClaw.\n'],
|
|
['IDENTITY.md', `# Identity\n\nName: ${agent.name}\n`],
|
|
['USER.md', '# User\n\nSupport Bao through Nexus Mission Control.\n'],
|
|
['HEARTBEAT.md', '# Heartbeat\n\nReport blockers and verification evidence.\n'],
|
|
['BOOTSTRAP.md', '# Bootstrap\n\nQA fixture only.\n'],
|
|
['MEMORY.md', '# Memory\n\nNo durable secrets belong here.\n'],
|
|
]),
|
|
]))
|
|
|
|
const agentWorkspaceEntries = new Map(openClawAgents.map(agent => [
|
|
agent.id,
|
|
[
|
|
{
|
|
path: 'DREAMS.md',
|
|
name: 'DREAMS.md',
|
|
kind: 'file',
|
|
size: 94,
|
|
updatedAt: minutesAgo(420),
|
|
},
|
|
{
|
|
path: 'memory',
|
|
name: 'memory',
|
|
kind: 'directory',
|
|
size: null,
|
|
updatedAt: minutesAgo(75),
|
|
},
|
|
],
|
|
]))
|
|
|
|
const dashboardAgents = [
|
|
{
|
|
id: 'iris',
|
|
name: 'Iris',
|
|
role: 'orchestrator',
|
|
model: 'openai/gpt-5.4',
|
|
isActive: true,
|
|
currentTask: 'Coordinating core integration',
|
|
description: 'Primary OpenClaw orchestrator.',
|
|
tags: ['owner', 'gateway'],
|
|
progress: 82,
|
|
workload: 64,
|
|
goal: 'Keep the control plane reliable and inspectable.',
|
|
roleBadge: 'badge-violet',
|
|
statusLabel: 'Orchestrating',
|
|
elapsed: '1h 12m',
|
|
think: 'Reviewing active runs. Preparing the next safe owner decision.',
|
|
next: 'Resolve production diagnostics approval',
|
|
},
|
|
{
|
|
id: 'programmer',
|
|
name: 'Programmer',
|
|
role: 'specialist',
|
|
model: 'openai/gpt-5.4-codex',
|
|
isActive: true,
|
|
currentTask: 'Protocol compatibility audit',
|
|
description: 'Implementation and test specialist.',
|
|
tags: ['code', 'tests'],
|
|
progress: 68,
|
|
workload: 72,
|
|
goal: 'Keep Nexus isolated from raw Gateway payloads.',
|
|
roleBadge: 'badge-blue',
|
|
statusLabel: 'Working',
|
|
elapsed: '39m',
|
|
think: 'Validating typed mappings. Comparing mutation recovery states.',
|
|
next: 'Browser interaction proof',
|
|
},
|
|
{
|
|
id: 'researcher',
|
|
name: 'Researcher',
|
|
role: 'specialist',
|
|
model: 'openai/gpt-5.4-mini',
|
|
isActive: true,
|
|
currentTask: 'Index mission-control evidence',
|
|
description: 'Primary-source research and synthesis.',
|
|
tags: ['research', 'evidence'],
|
|
progress: 18,
|
|
workload: 30,
|
|
goal: 'Ground decisions in official OpenClaw contracts.',
|
|
roleBadge: 'badge-cyan',
|
|
statusLabel: 'Queued',
|
|
elapsed: '8m',
|
|
think: 'Waiting for an available OpenClaw worker slot.',
|
|
next: 'Index operator-scope evidence',
|
|
},
|
|
{
|
|
id: 'archivist',
|
|
name: 'Archivist',
|
|
role: 'specialist',
|
|
model: 'openai/gpt-5.4-mini',
|
|
isActive: false,
|
|
currentTask: null,
|
|
description: 'Audit packet and durable handoff specialist.',
|
|
tags: ['docs', 'evidence'],
|
|
progress: 0,
|
|
workload: 8,
|
|
goal: 'Keep repository and project handoff aligned.',
|
|
roleBadge: 'badge-slate',
|
|
statusLabel: 'Ready',
|
|
elapsed: null,
|
|
think: null,
|
|
next: 'Await accepted milestone',
|
|
},
|
|
]
|
|
|
|
const notifications = [
|
|
{
|
|
id: 'notification-001',
|
|
type: 'task_review',
|
|
title: 'Owner approval required',
|
|
message: 'OpenClaw requested production diagnostic access.',
|
|
forUser: 'bao',
|
|
taskId: taskIds.review,
|
|
isRead: false,
|
|
createdAt: minutesAgo(6),
|
|
},
|
|
{
|
|
id: 'notification-002',
|
|
type: 'task_blocked',
|
|
title: 'Remote pairing is blocked',
|
|
message: 'A paired OpenClaw device identity is still required.',
|
|
forUser: 'bao',
|
|
taskId: taskIds.blocked,
|
|
isRead: false,
|
|
createdAt: minutesAgo(24),
|
|
},
|
|
{
|
|
id: 'notification-003',
|
|
type: 'task_assigned',
|
|
title: 'Model catalog normalized',
|
|
message: 'The frontend now reads provider-safe model metadata.',
|
|
forUser: 'bao',
|
|
taskId: taskIds.done,
|
|
isRead: true,
|
|
createdAt: minutesAgo(73),
|
|
},
|
|
]
|
|
|
|
const users = [
|
|
{
|
|
id: 'user-bao',
|
|
email: 'bao@nexus.local',
|
|
displayName: 'Bao',
|
|
role: 'owner',
|
|
createdAt: '2026-07-01T08:00:00.000Z',
|
|
lastLoginAt: minutesAgo(1),
|
|
},
|
|
{
|
|
id: 'user-iris',
|
|
email: 'iris@nexus.local',
|
|
displayName: 'Iris',
|
|
role: 'admin',
|
|
createdAt: '2026-07-02T08:00:00.000Z',
|
|
lastLoginAt: minutesAgo(16),
|
|
},
|
|
]
|
|
|
|
const incidents = [
|
|
{
|
|
name: '2026-07-28-remote-device-pairing.md',
|
|
title: 'Remote Gateway device pairing required',
|
|
date: '2026-07-28',
|
|
severity: 'Major',
|
|
excerpt: 'Direct loopback succeeds, while remote topology requires paired device identity.',
|
|
size: 2_184,
|
|
},
|
|
{
|
|
name: '2026-07-27-provider-timeout.md',
|
|
title: 'Owner brief provider timeout',
|
|
date: '2026-07-27',
|
|
severity: 'Minor',
|
|
excerpt: 'One scheduled summary timed out and was safely left for owner replay.',
|
|
size: 1_318,
|
|
},
|
|
]
|
|
|
|
const memories = [
|
|
{
|
|
name: 'openclaw-integration-boundary.md',
|
|
title: 'OpenClaw integration boundary',
|
|
modifiedAt: minutesAgo(41),
|
|
size: 1_940,
|
|
excerpt: 'Browser to Nexus to OpenClaw. Provider credentials never reach the frontend.',
|
|
},
|
|
{
|
|
name: 'owner-approval-policy.md',
|
|
title: 'Owner approval policy',
|
|
modifiedAt: minutesAgo(132),
|
|
size: 1_224,
|
|
excerpt: 'High-authority OpenClaw operations require Nexus owner confirmation.',
|
|
},
|
|
]
|
|
|
|
const docs = [
|
|
{
|
|
path: 'AGENT_FIRST_MISSION_CONTROL.md',
|
|
name: 'Agent-first mission control',
|
|
title: 'Agent-first mission control',
|
|
modifiedAt: minutesAgo(52),
|
|
size: 3_800,
|
|
excerpt: 'Operating model for delegated work, owner boundaries, and evidence.',
|
|
},
|
|
{
|
|
path: 'MISSION_CONTROL_ROADMAP.md',
|
|
name: 'Mission control roadmap',
|
|
title: 'Mission control roadmap',
|
|
modifiedAt: minutesAgo(110),
|
|
size: 4_520,
|
|
excerpt: 'Ordered delivery path for the OpenClaw-backed operations platform.',
|
|
},
|
|
]
|
|
|
|
const capabilitySpecs = [
|
|
['tasks-read', 'Task-Ledger lesen', 'tasks.list', 'operator.read'],
|
|
['tasks-cancel', 'Tasks abbrechen', 'tasks.cancel', 'operator.write'],
|
|
['sessions-read', 'Sessions lesen', 'sessions.list', 'operator.read'],
|
|
['sessions-abort', 'Runs abbrechen', 'sessions.abort', 'operator.write'],
|
|
['sessions-model', 'Session-Modell ändern', 'sessions.patch', 'operator.write'],
|
|
['activity-read', 'Audit-Aktivität lesen', 'audit.activity.list', 'operator.read'],
|
|
['cron-read', 'Zeitpläne lesen', 'cron.list', 'operator.read'],
|
|
['cron-detail', 'Zeitplan-Details lesen', 'cron.status', 'operator.read'],
|
|
['cron-history', 'Cron-Historie lesen', 'cron.runs', 'operator.read'],
|
|
['cron-create', 'Zeitplan anlegen', 'cron.add', 'operator.admin'],
|
|
['cron-update', 'Zeitplan ändern', 'cron.update', 'operator.admin'],
|
|
['cron-delete', 'Zeitplan löschen', 'cron.remove', 'operator.admin'],
|
|
['cron-run', 'Zeitplan manuell starten', 'cron.run', 'operator.admin'],
|
|
['approvals-read', 'Freigaben prüfen', 'approval.history', 'operator.approvals'],
|
|
['approvals-resolve', 'Freigaben entscheiden', 'approval.resolve', 'operator.approvals'],
|
|
['models-read', 'Modelle lesen', 'models.list', 'operator.read'],
|
|
['agents-read', 'Agenten lesen', 'agents.list', 'operator.read'],
|
|
]
|
|
|
|
function agentFileHash(agentId, fileName, content) {
|
|
return `qa-${agentId}-${fileName.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-')}-${Buffer.byteLength(content, 'utf8')}`
|
|
}
|
|
|
|
function cronDetail(job) {
|
|
const payloadMessage = job.description || `Run ${job.name} through the QA fixture.`
|
|
return {
|
|
id: job.id,
|
|
name: job.name,
|
|
displayName: job.name,
|
|
description: job.description,
|
|
enabled: job.enabled,
|
|
deleteAfterRun: false,
|
|
agentId: job.agentId,
|
|
sessionKey: job.sessionKey,
|
|
sessionTarget: 'isolated',
|
|
wakeMode: 'now',
|
|
schedule: {
|
|
kind: 'cron',
|
|
expression: job.schedule,
|
|
timeZone: job.timeZone,
|
|
at: null,
|
|
everyMs: null,
|
|
anchorMs: null,
|
|
staggerMs: null,
|
|
command: null,
|
|
workingDirectory: null,
|
|
},
|
|
payload: {
|
|
kind: 'agentTurn',
|
|
text: null,
|
|
message: payloadMessage,
|
|
model: 'openai/gpt-5.4-mini',
|
|
fallbacks: [],
|
|
thinking: null,
|
|
timeoutSeconds: 300,
|
|
allowUnsafeExternalContent: false,
|
|
lightContext: true,
|
|
toolsAllow: [],
|
|
arguments: [],
|
|
workingDirectory: null,
|
|
environmentKeys: [],
|
|
inputConfigured: true,
|
|
noOutputTimeoutSeconds: null,
|
|
outputMaxBytes: null,
|
|
},
|
|
delivery: {
|
|
mode: 'none',
|
|
channel: null,
|
|
target: null,
|
|
threadId: null,
|
|
accountId: null,
|
|
bestEffort: true,
|
|
completionDestination: null,
|
|
failureDestination: null,
|
|
},
|
|
trigger: null,
|
|
failureAlert: null,
|
|
createdAt: minutesAgo(10_000),
|
|
updatedAt: job.lastRunAt,
|
|
nextRunAt: job.nextRunAt,
|
|
lastRunAt: job.lastRunAt,
|
|
lastRunStatus: job.lastRunStatus,
|
|
lastError: job.lastError,
|
|
resourceHash: job.resourceHash,
|
|
canUpdate: true,
|
|
canDelete: true,
|
|
canRun: true,
|
|
}
|
|
}
|
|
|
|
function updateCronFromPatch(job, patch) {
|
|
if ('name' in patch) job.name = String(patch.name ?? job.name)
|
|
if ('description' in patch) job.description = patch.description == null ? null : String(patch.description)
|
|
if ('enabled' in patch) {
|
|
job.enabled = Boolean(patch.enabled)
|
|
job.status = job.enabled ? 'ready' : 'disabled'
|
|
}
|
|
if ('agentId' in patch) job.agentId = patch.agentId == null ? null : String(patch.agentId)
|
|
if ('sessionKey' in patch) job.sessionKey = patch.sessionKey == null ? null : String(patch.sessionKey)
|
|
if (patch.schedule && typeof patch.schedule === 'object') {
|
|
if (patch.schedule.kind === 'cron') {
|
|
job.schedule = String(patch.schedule.expr ?? job.schedule)
|
|
job.timeZone = patch.schedule.tz == null ? null : String(patch.schedule.tz)
|
|
} else if (patch.schedule.kind === 'every') {
|
|
job.schedule = `every ${Number(patch.schedule.everyMs ?? 0)} ms`
|
|
job.timeZone = null
|
|
} else if (patch.schedule.kind === 'at') {
|
|
job.schedule = String(patch.schedule.at ?? job.schedule)
|
|
job.timeZone = null
|
|
}
|
|
}
|
|
job.resourceHash = `qa-${job.id}-${Date.now()}`
|
|
}
|
|
|
|
function setupOperation(state, message, data = null, recovery = null, ok = true) {
|
|
return {
|
|
ok,
|
|
state,
|
|
message,
|
|
data: data == null ? null : clone(data),
|
|
recovery,
|
|
completedAt: now(),
|
|
}
|
|
}
|
|
|
|
function wizardResult(session, overrides = {}) {
|
|
return {
|
|
ok: true,
|
|
state: session.done ? 'completed' : 'waiting_for_input',
|
|
message: session.done
|
|
? 'QA wizard flow completed without contacting a live Gateway.'
|
|
: 'QA wizard is waiting for the next safe browser answer.',
|
|
sessionId: session.id,
|
|
done: session.done,
|
|
status: session.done ? 'completed' : 'active',
|
|
error: null,
|
|
step: session.done ? null : clone(session.step),
|
|
recovery: null,
|
|
completedAt: now(),
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function collection(items) {
|
|
return {
|
|
state: 'ready',
|
|
items: clone(items),
|
|
nextCursor: null,
|
|
message: null,
|
|
recovery: null,
|
|
checkedAt: now(),
|
|
}
|
|
}
|
|
|
|
function overview() {
|
|
const checkedAt = now()
|
|
return {
|
|
connection: {
|
|
state: 'connected',
|
|
configured: true,
|
|
credentialConfigured: true,
|
|
connected: true,
|
|
endpoint: 'qa://simulated-openclaw-gateway',
|
|
gatewayVersion: 'QA SIMULATION · OpenClaw 2026.7.1',
|
|
requiredVersion: '2026.7.1',
|
|
versionPinned: true,
|
|
versionMatches: true,
|
|
protocolVersion: 4,
|
|
grantedScopes: [
|
|
'operator.read',
|
|
'operator.write',
|
|
'operator.admin',
|
|
'operator.approvals',
|
|
'operator.questions',
|
|
],
|
|
advertisedEvents: [
|
|
'task.updated',
|
|
'session.updated',
|
|
'approval.requested',
|
|
'cron.completed',
|
|
],
|
|
lastConnectedAt: minutesAgo(21),
|
|
lastEventAt: minutesAgo(2),
|
|
reconnectAttempts: 0,
|
|
message: 'Simulated QA fixture; no live OpenClaw process is represented.',
|
|
recovery: null,
|
|
checkedAt,
|
|
},
|
|
capabilities: capabilitySpecs.map(([id, label, method, requiredScope]) => ({
|
|
id,
|
|
label,
|
|
method,
|
|
requiredScope,
|
|
available: true,
|
|
state: 'ready',
|
|
reason: null,
|
|
})),
|
|
tasks: collection(openClawTasks),
|
|
sessions: collection(sessions),
|
|
cronJobs: collection(cronJobs),
|
|
approvals: collection(approvals),
|
|
activity: collection(activity),
|
|
models: collection(models),
|
|
agents: collection(openClawAgents),
|
|
generatedAt: checkedAt,
|
|
}
|
|
}
|
|
|
|
function operationsSnapshot() {
|
|
const completed = nexusTasks.filter(task => task.state === 'Done').length
|
|
return {
|
|
generatedAt: now(),
|
|
runtime: {
|
|
runtime: 'OpenClaw Gateway v4 · QA simulation',
|
|
status: 'Online',
|
|
latency: '18 ms',
|
|
detail: 'Typed Nexus facade is connected to a simulated QA contract.',
|
|
},
|
|
models: models.map(model => ({
|
|
provider: model.provider,
|
|
model: model.id,
|
|
status: model.available ? 'Online' : 'Offline',
|
|
isLocal: model.provider === 'ollama',
|
|
detail: model.reason ?? 'Resolved by OpenClaw.',
|
|
})),
|
|
metrics: {
|
|
activeAgents: dashboardAgents.filter(agent => agent.isActive).length,
|
|
queuedTasks: nexusTasks.filter(task => task.state !== 'Done').length,
|
|
successRate: Math.round((completed * 1000) / nexusTasks.length) / 10,
|
|
incidents: nexusTasks.filter(task => task.state === 'Blocked').length,
|
|
runtimeHealthy: true,
|
|
lastIncident: {
|
|
taskId: taskIds.blocked,
|
|
title: 'Remote pairing is blocked',
|
|
since: minutesAgo(24),
|
|
},
|
|
},
|
|
projectHealth: { online: 2, offline: 0, degraded: 0, unknown: 0 },
|
|
agents: clone(dashboardAgents),
|
|
projects: clone(projects),
|
|
tasks: nexusTasks.map(task => ({
|
|
id: task.id,
|
|
title: task.title,
|
|
state: task.state,
|
|
priority: task.priority,
|
|
projectId: task.projectId,
|
|
updatedAt: task.updatedAt,
|
|
})),
|
|
activity: [
|
|
{ id: 1, type: 'gateway', message: 'OpenClaw protocol v4 handshake verified.', at: minutesAgo(3) },
|
|
{ id: 2, type: 'task', message: 'Owner approval entered the review queue.', at: minutesAgo(9) },
|
|
{ id: 3, type: 'security', message: 'Remote device pairing remains an explicit boundary.', at: minutesAgo(24) },
|
|
{ id: 4, type: 'model', message: 'OpenAI model catalog normalized through OpenClaw.', at: minutesAgo(73) },
|
|
],
|
|
}
|
|
}
|
|
|
|
function board() {
|
|
const groups = { offen: [], inProgress: [], review: [], blocked: [], done: [] }
|
|
for (const task of nexusTasks) {
|
|
if (task.state === 'Backlog') groups.offen.push(clone(task))
|
|
if (task.state === 'In progress') groups.inProgress.push(clone(task))
|
|
if (task.state === 'Review') groups.review.push(clone(task))
|
|
if (task.state === 'Blocked') groups.blocked.push(clone(task))
|
|
if (task.state === 'Done') groups.done.push(clone(task))
|
|
}
|
|
return groups
|
|
}
|
|
|
|
function operation(message, data = {}) {
|
|
return {
|
|
ok: true,
|
|
state: 'succeeded',
|
|
message,
|
|
data,
|
|
recovery: null,
|
|
completedAt: now(),
|
|
}
|
|
}
|
|
|
|
function requestHeader(request, name, fallback) {
|
|
const value = request.headers[name]
|
|
if (Array.isArray(value)) return value[0] ?? fallback
|
|
return value ? String(value) : fallback
|
|
}
|
|
|
|
function runInvocation(request, prefix) {
|
|
const nonce = randomUUID()
|
|
return {
|
|
actor: 'qa-fixture-owner',
|
|
correlationId: requestHeader(request, 'x-correlation-id', `qa-sim-correlation-${nonce}`),
|
|
idempotencyKey: requestHeader(request, 'idempotency-key', `qa-sim-${prefix}-${nonce}`),
|
|
traceParent: requestHeader(
|
|
request,
|
|
'traceparent',
|
|
`00-${nonce.replaceAll('-', '').slice(0, 32)}-${nonce.replaceAll('-', '').slice(0, 16)}-01`,
|
|
),
|
|
}
|
|
}
|
|
|
|
function addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
action,
|
|
fromStatus,
|
|
toStatus,
|
|
message,
|
|
options = {},
|
|
) {
|
|
const transition = {
|
|
id: ++durableRunTransitionSequence,
|
|
runId: run.id,
|
|
action,
|
|
fromStatus,
|
|
toStatus,
|
|
message,
|
|
actor: invocation.actor,
|
|
correlationId: invocation.correlationId,
|
|
idempotencyKey: options.idempotencyKey === null ? null : invocation.idempotencyKey,
|
|
traceParent: invocation.traceParent,
|
|
gatewayEventId: options.gatewayEventId ?? null,
|
|
gatewaySequence: options.gatewaySequence ?? null,
|
|
sequenceGapDetected: false,
|
|
resultRunId: options.resultRunId ?? null,
|
|
occurredAt: now(),
|
|
}
|
|
const history = durableRunHistory.get(run.id) ?? []
|
|
history.push(transition)
|
|
durableRunHistory.set(run.id, history)
|
|
return transition
|
|
}
|
|
|
|
function durableRunOperation(ok, state, message, run, resultRun = null) {
|
|
return {
|
|
ok,
|
|
state,
|
|
message,
|
|
run: clone(run),
|
|
resultRun: resultRun ? clone(resultRun) : null,
|
|
completedAt: now(),
|
|
}
|
|
}
|
|
|
|
function durableRunHistoryResponse(run) {
|
|
const transitions = durableRunHistory.get(run.id) ?? []
|
|
return {
|
|
run: clone(run),
|
|
transitions: clone(transitions),
|
|
gatewayHistoryState: 'qa_simulation',
|
|
gatewayHistory: {
|
|
qaSimulation: true,
|
|
warning: 'Synthetic QA evidence only; no live OpenClaw Gateway was contacted.',
|
|
sessionKey: run.sessionKey,
|
|
openClawRunId: run.openClawRunId,
|
|
events: transitions
|
|
.filter(item => item.gatewayEventId || item.gatewaySequence !== null)
|
|
.map(item => ({
|
|
id: item.gatewayEventId,
|
|
sequence: item.gatewaySequence,
|
|
status: item.toStatus,
|
|
occurredAt: item.occurredAt,
|
|
})),
|
|
},
|
|
message: 'QA SIMULATION: persisted in memory for positive visual and interaction testing.',
|
|
checkedAt: now(),
|
|
}
|
|
}
|
|
|
|
function addRuntimeActivity(action, message, status = 'succeeded', severity = 'success') {
|
|
activity.unshift({
|
|
id: `evt-qa-${randomUUID()}`,
|
|
eventType: 'qa.operation',
|
|
kind: 'operation',
|
|
action,
|
|
status,
|
|
message,
|
|
severity,
|
|
actor: 'bao',
|
|
agentId: null,
|
|
sessionKey: null,
|
|
runId: null,
|
|
occurredAt: now(),
|
|
source: 'Nexus QA fixture',
|
|
})
|
|
}
|
|
|
|
async function readBody(request) {
|
|
const chunks = []
|
|
for await (const chunk of request) chunks.push(chunk)
|
|
if (!chunks.length) return {}
|
|
const text = Buffer.concat(chunks).toString('utf8')
|
|
if (!text) return {}
|
|
try {
|
|
return JSON.parse(text)
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
function sendJson(response, status, payload) {
|
|
response.writeHead(status, {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
'Cache-Control': 'no-store',
|
|
'X-Nexus-QA-Fixture': qaHeader,
|
|
})
|
|
response.end(JSON.stringify(payload))
|
|
}
|
|
|
|
function streamOpenClawEvents(request, response, url) {
|
|
response.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache, no-store',
|
|
Connection: 'keep-alive',
|
|
'X-Accel-Buffering': 'no',
|
|
'X-Nexus-QA-Fixture': qaHeader,
|
|
})
|
|
|
|
const requestedCursor = requestHeader(
|
|
request,
|
|
'last-event-id',
|
|
url.searchParams.get('lastEventId') || 'origin',
|
|
).replaceAll(/[\r\n]/g, '')
|
|
const cursor = requestedCursor || 'origin'
|
|
|
|
const writeEvent = event => {
|
|
if (response.destroyed || response.writableEnded) return
|
|
response.write(`id: ${event.id}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`)
|
|
}
|
|
const eventBase = (type, eventName, category, payload) => ({
|
|
id: cursor,
|
|
type,
|
|
eventName,
|
|
category,
|
|
sequence: null,
|
|
stateVersion: null,
|
|
previousSequence: null,
|
|
sequenceGapDetected: false,
|
|
sequenceResetDetected: false,
|
|
missingSequenceFrom: null,
|
|
missingSequenceTo: null,
|
|
occurredAt: now(),
|
|
payload: {
|
|
qaSimulation: true,
|
|
warning: 'Synthetic QA event stream; no live OpenClaw Gateway is represented.',
|
|
...payload,
|
|
},
|
|
})
|
|
|
|
response.write('retry: 2000\n\n')
|
|
writeEvent(eventBase('openclaw.connection', 'connection', 'connection', {
|
|
state: 'connected',
|
|
connected: true,
|
|
gatewayVersion: 'QA SIMULATION · OpenClaw 2026.7.1',
|
|
protocolVersion: 4,
|
|
deviceId: 'qa-simulation-device',
|
|
deviceTokenConfigured: true,
|
|
pairingRequired: false,
|
|
pairingRequestId: null,
|
|
lastConnectedAt: minutesAgo(21),
|
|
lastEventAt: minutesAgo(2),
|
|
reconnectAttempts: 0,
|
|
message: 'QA simulation stream connected; no live Gateway process is represented.',
|
|
}))
|
|
|
|
const heartbeatTimer = setInterval(() => {
|
|
writeEvent(eventBase('openclaw.heartbeat', 'heartbeat', 'heartbeat', {
|
|
connected: true,
|
|
lastEventAt: minutesAgo(2),
|
|
sentAt: now(),
|
|
}))
|
|
}, 15_000)
|
|
|
|
let cleanedUp = false
|
|
const cleanup = () => {
|
|
if (cleanedUp) return
|
|
cleanedUp = true
|
|
clearInterval(heartbeatTimer)
|
|
}
|
|
request.once('close', cleanup)
|
|
response.once('close', cleanup)
|
|
response.once('error', cleanup)
|
|
}
|
|
|
|
function sendSseSnapshot(response) {
|
|
response.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
Connection: 'keep-alive',
|
|
'X-Nexus-QA-Fixture': qaHeader,
|
|
})
|
|
response.write('event: snapshot\n')
|
|
response.write(`data: ${JSON.stringify({
|
|
board: board(),
|
|
notifications: {
|
|
notifications: clone(notifications),
|
|
unreadCount: notifications.filter(item => !item.isRead).length,
|
|
forUser: 'bao',
|
|
},
|
|
cursor: { sequence: 1, timestamp: now() },
|
|
simulated: true,
|
|
})}\n\n`)
|
|
response.end()
|
|
}
|
|
|
|
function taskById(id) {
|
|
return nexusTasks.find(task => task.id === id)
|
|
}
|
|
|
|
const server = http.createServer(async (request, response) => {
|
|
const url = new URL(request.url ?? '/', `http://${request.headers.host ?? `127.0.0.1:${port}`}`)
|
|
const path = url.pathname
|
|
const method = request.method ?? 'GET'
|
|
|
|
if (method === 'OPTIONS') {
|
|
response.writeHead(204)
|
|
response.end()
|
|
return
|
|
}
|
|
|
|
if (path === '/health') {
|
|
sendJson(response, 200, { status: 'healthy', fixture: qaHeader })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/auth/refresh' || path === '/api/v1/auth/login') {
|
|
sendJson(response, 200, {
|
|
accessToken: 'nexus-qa-access-token',
|
|
expiresAt: minutesFromNow(120),
|
|
user: { id: 'user-bao', email: 'bao@nexus.local', displayName: 'Bao', role: 'owner' },
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/auth/logout') {
|
|
sendJson(response, 200, { ok: true })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/auth/profile' && method === 'PATCH') {
|
|
const body = await readBody(request)
|
|
sendJson(response, 200, { displayName: String(body.displayName ?? 'Bao') })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/auth/change-password' && method === 'POST') {
|
|
sendJson(response, 200, { ok: true })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup' && method === 'GET') {
|
|
sendJson(response, 200, { ...clone(setupStatus), checkedAt: now() })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/discover' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const candidates = [
|
|
{
|
|
endpoint: 'ws://openclaw-gateway:18789',
|
|
source: 'docker-dns',
|
|
isCurrentConnectorEndpoint: true,
|
|
requiresTlsFingerprint: false,
|
|
isValid: true,
|
|
reason: null,
|
|
},
|
|
{
|
|
endpoint: 'ws://127.0.0.1:18789',
|
|
source: 'loopback',
|
|
isCurrentConnectorEndpoint: false,
|
|
requiresTlsFingerprint: false,
|
|
isValid: true,
|
|
reason: null,
|
|
},
|
|
{
|
|
endpoint: 'ws://host.docker.internal:18789',
|
|
source: 'docker-host',
|
|
isCurrentConnectorEndpoint: false,
|
|
requiresTlsFingerprint: false,
|
|
isValid: true,
|
|
reason: null,
|
|
},
|
|
]
|
|
sendJson(response, 200, {
|
|
candidates,
|
|
mdnsState: body.includeMdns ? 'qa_simulated' : 'not_requested',
|
|
message: 'QA discovery checks only known candidates; no subnet scan or Docker socket is used.',
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/probe' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const endpoint = String(body.endpoint ?? 'ws://openclaw-gateway:18789')
|
|
const probe = {
|
|
endpoint,
|
|
source: endpoint.includes('openclaw-gateway') ? 'docker-dns' : 'manual',
|
|
isCurrentConnectorEndpoint: endpoint === setupStatus.endpoint,
|
|
connected: true,
|
|
gatewayVersion: setupStatus.gatewayVersion,
|
|
requiredVersion: setupStatus.requiredVersion,
|
|
versionMatches: true,
|
|
protocolVersion: 4,
|
|
deviceId: setupStatus.deviceId,
|
|
pairingRequired: false,
|
|
pairingRequestId: null,
|
|
grantedScopes: ['operator.read'],
|
|
advertisedMethods: clone(setupStatus.advertisedMethods),
|
|
capabilityHash: setupStatus.capabilityHash,
|
|
canAttach: false,
|
|
leastPrivilegeSatisfied: true,
|
|
checkedAt: now(),
|
|
}
|
|
sendJson(
|
|
response,
|
|
409,
|
|
setupOperation(
|
|
'experimental_blocked',
|
|
'The known candidate is reachable in QA, but production attach remains blocked.',
|
|
probe,
|
|
'Pin Nexus to the first OpenClaw release that officially accepts an external Nexus client ID.',
|
|
false,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/attach' && method === 'POST') {
|
|
sendJson(
|
|
response,
|
|
409,
|
|
setupOperation(
|
|
'experimental_blocked',
|
|
'QA refused attach before any bootstrap credential could be persisted.',
|
|
null,
|
|
'Use an OpenClaw release with an official external Nexus client ID.',
|
|
false,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/verify' && method === 'POST') {
|
|
sendJson(
|
|
response,
|
|
200,
|
|
setupOperation('verified', 'The QA profile was read back with a matching capability hash.', setupStatus),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/adopt' && method === 'POST') {
|
|
sendJson(
|
|
response,
|
|
200,
|
|
setupOperation('adopted', 'The QA inventory was adopted without copying OpenClaw state.', {
|
|
agentCount: openClawAgents.length,
|
|
agentFileCount: [...agentFiles.values()].reduce((total, files) => total + files.size, 0),
|
|
cronJobCount: cronJobs.length,
|
|
modelCount: models.length,
|
|
channelCount: 2,
|
|
nodeCount: 1,
|
|
diagnostics: ['QA fixture only; no live OpenClaw data was copied.'],
|
|
capturedAt: now(),
|
|
}),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/management' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
setupStatus.managementEnabled = Boolean(body.enabled)
|
|
setupStatus.revision += 1
|
|
setupStatus.updatedAt = now()
|
|
sendJson(
|
|
response,
|
|
200,
|
|
setupOperation(
|
|
setupStatus.managementEnabled ? 'management_enabled' : 'management_disabled',
|
|
setupStatus.managementEnabled
|
|
? 'QA management policy enabled after explicit confirmation.'
|
|
: 'QA management policy returned to read-only.',
|
|
setupStatus,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/connection' && method === 'DELETE') {
|
|
sendJson(
|
|
response,
|
|
200,
|
|
setupOperation(
|
|
'disconnected',
|
|
'QA connection profile and bound device token were removed.',
|
|
{ ...setupStatus, hasProfile: false, adoptionState: 'none', managementEnabled: false },
|
|
'A separately configured server bootstrap secret would still need to be removed at its source.',
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/wizard/start' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const session = {
|
|
id: `qa-wizard-${randomUUID()}`,
|
|
done: false,
|
|
step: {
|
|
id: 'qa-confirm-runtime',
|
|
type: 'confirm',
|
|
title: 'OpenClaw Runtime-Konfiguration bestätigen',
|
|
message: `QA ${body.mode === 'remote' ? 'Remote' : 'Local'} flow: continue without installing a daemon?`,
|
|
options: [],
|
|
initialValue: false,
|
|
placeholder: null,
|
|
sensitive: false,
|
|
executor: 'gateway',
|
|
externalUrl: 'https://docs.openclaw.ai/reference/wizard',
|
|
deviceCode: null,
|
|
canAnswer: true,
|
|
blockedReason: null,
|
|
},
|
|
}
|
|
wizardSessions.set(session.id, session)
|
|
sendJson(response, 200, wizardResult(session))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/setup/wizard/next' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const session = wizardSessions.get(String(body.sessionId ?? ''))
|
|
if (!session) {
|
|
sendJson(response, 404, { detail: 'QA wizard session not found.' })
|
|
return
|
|
}
|
|
session.done = true
|
|
sendJson(response, 200, wizardResult(session))
|
|
return
|
|
}
|
|
|
|
const wizardStatusMatch = path.match(/^\/api\/v1\/openclaw\/setup\/wizard\/([^/]+)$/)
|
|
if (wizardStatusMatch && method === 'GET') {
|
|
const session = wizardSessions.get(decodeURIComponent(wizardStatusMatch[1]))
|
|
if (!session) {
|
|
sendJson(response, 404, { detail: 'QA wizard session not found.' })
|
|
return
|
|
}
|
|
sendJson(response, 200, wizardResult(session))
|
|
return
|
|
}
|
|
|
|
const wizardCancelMatch = path.match(/^\/api\/v1\/openclaw\/setup\/wizard\/([^/]+)\/cancel$/)
|
|
if (wizardCancelMatch && method === 'POST') {
|
|
const session = wizardSessions.get(decodeURIComponent(wizardCancelMatch[1]))
|
|
if (!session) {
|
|
sendJson(response, 404, { detail: 'QA wizard session not found.' })
|
|
return
|
|
}
|
|
session.done = true
|
|
sendJson(response, 200, wizardResult(session, {
|
|
state: 'cancelled',
|
|
status: 'cancelled',
|
|
message: 'QA wizard session cancelled.',
|
|
}))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/config' && method === 'GET') {
|
|
sendJson(response, 200, clone(configSnapshot))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/config/schema' && method === 'GET') {
|
|
const schemaPath = url.searchParams.get('path') || 'agents'
|
|
sendJson(response, 200, {
|
|
path: schemaPath,
|
|
schema: { type: 'object', additionalProperties: true },
|
|
reloadKind: 'gateway',
|
|
hint: 'QA fixture schema; live OpenClaw remains authoritative.',
|
|
children: [
|
|
{
|
|
key: 'defaults',
|
|
path: `${schemaPath}.defaults`,
|
|
type: 'object',
|
|
required: false,
|
|
hasChildren: true,
|
|
reloadKind: 'gateway',
|
|
hint: null,
|
|
},
|
|
],
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/config' && method === 'PATCH') {
|
|
const body = await readBody(request)
|
|
if (body.baseHash !== configSnapshot.hash) {
|
|
sendJson(response, 409, {
|
|
detail: 'QA config hash drift detected.',
|
|
currentHash: configSnapshot.hash,
|
|
})
|
|
return
|
|
}
|
|
configSnapshot = {
|
|
...configSnapshot,
|
|
hash: `qa-config-hash-${Date.now()}`,
|
|
config: {
|
|
...configSnapshot.config,
|
|
...(body.patch && typeof body.patch === 'object' ? body.patch : {}),
|
|
},
|
|
checkedAt: now(),
|
|
}
|
|
sendJson(response, 200, {
|
|
ok: true,
|
|
state: 'succeeded',
|
|
message: 'QA configuration patch was stored and read back.',
|
|
snapshot: clone(configSnapshot),
|
|
restart: null,
|
|
verified: true,
|
|
idempotencyKey: requestHeader(request, 'idempotency-key', `qa-config-${randomUUID()}`),
|
|
correlationId: requestHeader(request, 'x-correlation-id', `qa-correlation-${randomUUID()}`),
|
|
completedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/events' && method === 'GET') {
|
|
streamOpenClawEvents(request, response, url)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/overview' && method === 'GET') {
|
|
sendJson(response, 200, overview())
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/models' && method === 'GET') {
|
|
sendJson(response, 200, collection(models))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/models/auth-status' && method === 'GET') {
|
|
sendJson(response, 200, collection(modelAuthProviders))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/runs' && method === 'GET') {
|
|
const status = url.searchParams.get('status')
|
|
const taskId = url.searchParams.get('taskId')
|
|
const projectFilter = url.searchParams.get('projectId')
|
|
const sessionKey = url.searchParams.get('sessionKey')
|
|
const limit = Math.min(
|
|
Math.max(Number.parseInt(url.searchParams.get('limit') ?? '50', 10) || 50, 1),
|
|
200,
|
|
)
|
|
const items = durableRuns
|
|
.filter(run => !status || run.status === status)
|
|
.filter(run => !taskId || run.taskId === taskId)
|
|
.filter(run => !projectFilter || run.projectId === projectFilter)
|
|
.filter(run => !sessionKey || run.sessionKey === sessionKey)
|
|
.toSorted((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))
|
|
.slice(0, limit)
|
|
sendJson(response, 200, {
|
|
items: clone(items),
|
|
nextCursor: null,
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/runs' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const prompt = String(body.prompt ?? '').trim()
|
|
const agentId = String(body.agentId ?? '').trim()
|
|
const sessionKey = String(body.sessionKey ?? '').trim()
|
|
if (!prompt || !agentId || !sessionKey) {
|
|
sendJson(response, 400, {
|
|
title: 'One or more validation errors occurred.',
|
|
errors: {
|
|
...(!prompt ? { prompt: ['Prompt is required.'] } : {}),
|
|
...(!agentId ? { agentId: ['Agent id is required.'] } : {}),
|
|
...(!sessionKey ? { sessionKey: ['Session key is required.'] } : {}),
|
|
},
|
|
fixture: qaHeader,
|
|
})
|
|
return
|
|
}
|
|
|
|
const invocation = runInvocation(request, 'run-start')
|
|
const id = randomUUID()
|
|
const requestedTitle = String(body.title ?? '').trim()
|
|
const title = requestedTitle || prompt.replaceAll(/\s+/g, ' ').slice(0, 120)
|
|
const timestamp = now()
|
|
const run = {
|
|
id,
|
|
title: title.startsWith('QA SIMULATION') ? title : `QA SIMULATION · ${title}`,
|
|
prompt: prompt.startsWith('[QA SIMULATION')
|
|
? prompt
|
|
: `[QA SIMULATION ONLY — no live OpenClaw side effect]\n\n${prompt}`,
|
|
agentId,
|
|
sessionKey,
|
|
status: 'running',
|
|
taskId: body.taskId || null,
|
|
projectId: body.projectId || null,
|
|
openClawRunId: `qa-sim-openclaw-${id.slice(0, 8)}`,
|
|
retriedFromRunId: null,
|
|
correlationId: invocation.correlationId,
|
|
actor: invocation.actor,
|
|
lastError: null,
|
|
lastGatewaySequence: 1,
|
|
sequenceGapDetected: false,
|
|
canStop: true,
|
|
canRetry: false,
|
|
canResume: false,
|
|
resumeCapabilityMessage: 'QA simulation: same-run resume is intentionally unsupported until the Gateway advertises a documented capability.',
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
startedAt: timestamp,
|
|
finishedAt: null,
|
|
}
|
|
durableRuns.unshift(run)
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'start_requested',
|
|
'dispatching',
|
|
'dispatching',
|
|
'QA SIMULATION: Nexus durably recorded the run before simulated dispatch.',
|
|
)
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'start_result',
|
|
'dispatching',
|
|
'running',
|
|
'QA SIMULATION: the fixture accepted the run; no live OpenClaw Gateway was contacted.',
|
|
{
|
|
idempotencyKey: null,
|
|
gatewayEventId: `qa-sim-gateway-event-${id.slice(0, 8)}`,
|
|
gatewaySequence: 1,
|
|
},
|
|
)
|
|
addRuntimeActivity(
|
|
'Durable run started',
|
|
`${run.title} was started by the QA simulation.`,
|
|
'running',
|
|
'info',
|
|
)
|
|
sendJson(
|
|
response,
|
|
201,
|
|
durableRunOperation(
|
|
true,
|
|
'succeeded',
|
|
'QA SIMULATION: durable run started without contacting a live OpenClaw Gateway.',
|
|
run,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
const durableRunHistoryMatch = path.match(/^\/api\/v1\/openclaw\/runs\/([^/]+)\/history$/)
|
|
if (durableRunHistoryMatch && method === 'GET') {
|
|
const run = durableRuns.find(item => item.id === decodeURIComponent(durableRunHistoryMatch[1]))
|
|
if (!run) {
|
|
sendJson(response, 404, { detail: 'QA durable run not found', fixture: qaHeader })
|
|
return
|
|
}
|
|
sendJson(response, 200, durableRunHistoryResponse(run))
|
|
return
|
|
}
|
|
|
|
const durableRunActionMatch = path.match(/^\/api\/v1\/openclaw\/runs\/([^/]+)\/(stop|resume|retry)$/)
|
|
if (durableRunActionMatch && method === 'POST') {
|
|
const run = durableRuns.find(item => item.id === decodeURIComponent(durableRunActionMatch[1]))
|
|
const action = durableRunActionMatch[2]
|
|
if (!run) {
|
|
sendJson(response, 404, { detail: 'QA durable run not found', fixture: qaHeader })
|
|
return
|
|
}
|
|
|
|
const body = await readBody(request)
|
|
const invocation = runInvocation(request, `run-${action}`)
|
|
const reason = String(body.reason ?? '').trim()
|
|
const reasonSuffix = reason ? ` Reason: ${reason}` : ''
|
|
|
|
if (action === 'resume') {
|
|
const message = 'QA simulation: same-run resume is intentionally unsupported until the Gateway advertises a documented capability.'
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'resume_requested',
|
|
run.status,
|
|
run.status,
|
|
`${message}${reasonSuffix}`,
|
|
)
|
|
sendJson(response, 409, durableRunOperation(false, 'unsupported', message, run))
|
|
return
|
|
}
|
|
|
|
if (action === 'stop') {
|
|
if (['stopped', 'completed', 'failed', 'blocked', 'unsupported'].includes(run.status)) {
|
|
sendJson(
|
|
response,
|
|
200,
|
|
durableRunOperation(
|
|
true,
|
|
'already_terminal',
|
|
`QA SIMULATION: run is already terminal with state '${run.status}'.`,
|
|
run,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
const previousStatus = run.status
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'stop_requested',
|
|
previousStatus,
|
|
'stopping',
|
|
`QA SIMULATION: stop requested.${reasonSuffix}`,
|
|
)
|
|
run.status = 'stopping'
|
|
run.updatedAt = now()
|
|
const gatewaySequence = (run.lastGatewaySequence ?? 0) + 1
|
|
run.status = 'stopped'
|
|
run.lastGatewaySequence = gatewaySequence
|
|
run.canStop = false
|
|
run.canRetry = true
|
|
run.finishedAt = now()
|
|
run.updatedAt = run.finishedAt
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'stop_result',
|
|
'stopping',
|
|
'stopped',
|
|
'QA SIMULATION: the fixture stopped the run; no live OpenClaw Gateway was contacted.',
|
|
{
|
|
idempotencyKey: null,
|
|
gatewayEventId: `qa-sim-gateway-event-${gatewaySequence}`,
|
|
gatewaySequence,
|
|
},
|
|
)
|
|
addRuntimeActivity('Durable run stopped', `${run.title} was stopped by the QA simulation.`, 'stopped', 'warning')
|
|
sendJson(
|
|
response,
|
|
200,
|
|
durableRunOperation(
|
|
true,
|
|
'succeeded',
|
|
'QA SIMULATION: durable run stopped without contacting a live OpenClaw Gateway.',
|
|
run,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
if (!['stopped', 'completed', 'failed', 'blocked', 'unsupported'].includes(run.status)) {
|
|
const message = 'Only a terminal, blocked, or unsupported run can be retried.'
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'retry_rejected',
|
|
run.status,
|
|
run.status,
|
|
`QA SIMULATION: ${message}${reasonSuffix}`,
|
|
)
|
|
sendJson(response, 409, durableRunOperation(false, 'invalid_state', message, run))
|
|
return
|
|
}
|
|
|
|
const retryId = randomUUID()
|
|
const retryTimestamp = now()
|
|
const retry = {
|
|
...clone(run),
|
|
id: retryId,
|
|
status: 'running',
|
|
openClawRunId: `qa-sim-openclaw-${retryId.slice(0, 8)}`,
|
|
retriedFromRunId: run.id,
|
|
correlationId: invocation.correlationId,
|
|
actor: invocation.actor,
|
|
lastError: null,
|
|
lastGatewaySequence: 1,
|
|
sequenceGapDetected: false,
|
|
canStop: true,
|
|
canRetry: false,
|
|
canResume: false,
|
|
createdAt: retryTimestamp,
|
|
updatedAt: retryTimestamp,
|
|
startedAt: retryTimestamp,
|
|
finishedAt: null,
|
|
}
|
|
addDurableRunTransition(
|
|
run,
|
|
invocation,
|
|
'retry_requested',
|
|
run.status,
|
|
run.status,
|
|
`QA SIMULATION: retry created run ${retry.id}.${reasonSuffix}`,
|
|
{ resultRunId: retry.id },
|
|
)
|
|
durableRuns.unshift(retry)
|
|
addDurableRunTransition(
|
|
retry,
|
|
invocation,
|
|
'start_requested',
|
|
'dispatching',
|
|
'dispatching',
|
|
`QA SIMULATION: retry of Nexus run ${run.id} was durably recorded before simulated dispatch.`,
|
|
)
|
|
addDurableRunTransition(
|
|
retry,
|
|
invocation,
|
|
'start_result',
|
|
'dispatching',
|
|
'running',
|
|
'QA SIMULATION: the retry fixture entered running state; no live OpenClaw Gateway was contacted.',
|
|
{
|
|
idempotencyKey: null,
|
|
gatewayEventId: `qa-sim-gateway-event-${retryId.slice(0, 8)}`,
|
|
gatewaySequence: 1,
|
|
},
|
|
)
|
|
addRuntimeActivity('Durable run retried', `${run.title} was retried by the QA simulation.`, 'running', 'info')
|
|
sendJson(
|
|
response,
|
|
201,
|
|
durableRunOperation(
|
|
true,
|
|
'succeeded',
|
|
'QA SIMULATION: correlated retry run created without contacting a live OpenClaw Gateway.',
|
|
run,
|
|
retry,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
const durableRunDetailMatch = path.match(/^\/api\/v1\/openclaw\/runs\/([^/]+)$/)
|
|
if (durableRunDetailMatch && method === 'GET') {
|
|
const run = durableRuns.find(item => item.id === decodeURIComponent(durableRunDetailMatch[1]))
|
|
if (!run) {
|
|
sendJson(response, 404, { detail: 'QA durable run not found', fixture: qaHeader })
|
|
return
|
|
}
|
|
sendJson(response, 200, clone(run))
|
|
return
|
|
}
|
|
|
|
const cancelMatch = path.match(/^\/api\/v1\/openclaw\/tasks\/([^/]+)\/cancel$/)
|
|
if (cancelMatch && method === 'POST') {
|
|
const task = openClawTasks.find(item => item.id === decodeURIComponent(cancelMatch[1]))
|
|
if (!task) {
|
|
sendJson(response, 404, { detail: 'QA task not found' })
|
|
return
|
|
}
|
|
task.status = 'cancelled'
|
|
task.canCancel = false
|
|
task.finishedAt = now()
|
|
task.updatedAt = task.finishedAt
|
|
addRuntimeActivity('Task cancelled', `${task.title} was cancelled through Nexus.`, 'cancelled', 'warning')
|
|
sendJson(response, 200, operation('OpenClaw task cancelled.', clone(task)))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/sessions/abort' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const session = sessions.find(item => item.key === body.sessionKey)
|
|
if (!session) {
|
|
sendJson(response, 404, { detail: 'QA session not found' })
|
|
return
|
|
}
|
|
session.status = 'aborted'
|
|
session.canAbort = false
|
|
session.updatedAt = now()
|
|
addRuntimeActivity('Run aborted', `${session.title} was aborted through Nexus.`, 'cancelled', 'warning')
|
|
sendJson(response, 200, operation('OpenClaw run aborted.', { sessionKey: session.key }))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/sessions/model' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const session = sessions.find(item => item.key === body.sessionKey)
|
|
if (!session) {
|
|
sendJson(response, 404, { detail: 'QA session not found' })
|
|
return
|
|
}
|
|
session.model = String(body.model ?? session.model)
|
|
session.provider = session.model.split('/')[0] ?? session.provider
|
|
session.updatedAt = now()
|
|
const agentId = String(body.sessionKey).split(':')[1]
|
|
const dashboardAgent = dashboardAgents.find(item => item.id === agentId)
|
|
const openClawAgent = openClawAgents.find(item => item.id === agentId)
|
|
if (dashboardAgent) dashboardAgent.model = session.model
|
|
if (openClawAgent) {
|
|
openClawAgent.model = session.model
|
|
openClawAgent.provider = session.provider
|
|
}
|
|
addRuntimeActivity('Session model changed', `${session.title} now uses ${session.model}.`)
|
|
sendJson(response, 200, operation('Session model updated.', { sessionKey: session.key, model: session.model }))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/cron' && method === 'GET') {
|
|
const includeDisabled = url.searchParams.get('includeDisabled') !== 'false'
|
|
const limit = Math.min(Math.max(Number(url.searchParams.get('limit') || 50), 1), 200)
|
|
const items = cronJobs
|
|
.filter(job => includeDisabled || job.enabled)
|
|
.slice(0, limit)
|
|
sendJson(response, 200, collection(items))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/openclaw/cron' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const schedule = body.schedule && typeof body.schedule === 'object'
|
|
? body.schedule
|
|
: { kind: 'cron', expr: '0 7 * * *', tz: 'Europe/Berlin' }
|
|
const scheduleLabel = schedule.kind === 'every'
|
|
? `every ${Number(schedule.everyMs ?? 0)} ms`
|
|
: schedule.kind === 'at'
|
|
? String(schedule.at ?? '')
|
|
: String(schedule.expr ?? '0 7 * * *')
|
|
const id = `qa-cron-${randomUUID()}`
|
|
const job = {
|
|
id,
|
|
name: String(body.name ?? 'QA schedule'),
|
|
description: body.description == null ? null : String(body.description),
|
|
schedule: scheduleLabel,
|
|
timeZone: schedule.tz == null ? null : String(schedule.tz),
|
|
enabled: body.enabled !== false,
|
|
status: body.enabled === false ? 'disabled' : 'ready',
|
|
agentId: body.agentId == null ? null : String(body.agentId),
|
|
sessionKey: body.sessionKey == null ? null : String(body.sessionKey),
|
|
nextRunAt: minutesFromNow(60),
|
|
lastRunAt: null,
|
|
lastRunStatus: null,
|
|
lastError: null,
|
|
canRun: true,
|
|
resourceHash: `qa-${id}-v1`,
|
|
}
|
|
cronJobs.unshift(job)
|
|
cronRunHistory.set(id, [])
|
|
sendJson(response, 201, operation('QA schedule created through the typed OpenClaw contract.', cronDetail(job)))
|
|
return
|
|
}
|
|
|
|
const cronRunsMatch = path.match(/^\/api\/v1\/openclaw\/cron\/([^/]+)\/runs$/)
|
|
if (cronRunsMatch && method === 'GET') {
|
|
const jobId = decodeURIComponent(cronRunsMatch[1])
|
|
const job = cronJobs.find(item => item.id === jobId)
|
|
if (!job) {
|
|
sendJson(response, 404, { detail: 'QA cron job not found' })
|
|
return
|
|
}
|
|
const runId = url.searchParams.get('runId')
|
|
const limit = Math.min(Math.max(Number(url.searchParams.get('limit') || 25), 1), 200)
|
|
const items = (cronRunHistory.get(jobId) ?? [])
|
|
.filter(run => !runId || run.runId === runId)
|
|
.slice(0, limit)
|
|
sendJson(response, 200, collection(items))
|
|
return
|
|
}
|
|
|
|
const cronRunMatch = path.match(/^\/api\/v1\/openclaw\/cron\/([^/]+)\/run$/)
|
|
if (cronRunMatch && method === 'POST') {
|
|
const job = cronJobs.find(item => item.id === decodeURIComponent(cronRunMatch[1]))
|
|
if (!job) {
|
|
sendJson(response, 404, { detail: 'QA cron job not found' })
|
|
return
|
|
}
|
|
const expectedHash = url.searchParams.get('expectedHash')
|
|
if (!expectedHash || expectedHash !== job.resourceHash) {
|
|
sendJson(response, 409, {
|
|
detail: 'QA cron resource hash drift detected.',
|
|
currentHash: job.resourceHash,
|
|
})
|
|
return
|
|
}
|
|
const runId = `qa-cron-run-${randomUUID()}`
|
|
job.status = 'queued'
|
|
job.lastRunStatus = 'queued'
|
|
job.lastRunAt = now()
|
|
const run = {
|
|
id: `history-${runId}`,
|
|
jobId: job.id,
|
|
jobName: job.name,
|
|
runId,
|
|
status: 'queued',
|
|
action: 'enqueued',
|
|
summary: 'Queued in QA; cron history remains the source of the final outcome.',
|
|
error: null,
|
|
errorReason: null,
|
|
deliveryStatus: 'pending',
|
|
deliveryError: null,
|
|
delivered: null,
|
|
triggerFired: null,
|
|
diagnosticsSummary: 'Synthetic queue evidence.',
|
|
diagnostics: [],
|
|
sessionId: null,
|
|
sessionKey: job.sessionKey,
|
|
occurredAt: now(),
|
|
runAt: now(),
|
|
durationMs: null,
|
|
nextRunAt: job.nextRunAt,
|
|
model: null,
|
|
provider: null,
|
|
inputTokens: null,
|
|
outputTokens: null,
|
|
totalTokens: null,
|
|
}
|
|
cronRunHistory.set(job.id, [run, ...(cronRunHistory.get(job.id) ?? [])])
|
|
addRuntimeActivity('Cron job queued', `${job.name} was force-queued by Bao.`, 'queued', 'info')
|
|
sendJson(response, 200, operation('Cron job queued in OpenClaw.', {
|
|
jobId: job.id,
|
|
enqueued: true,
|
|
runId,
|
|
}))
|
|
return
|
|
}
|
|
|
|
const cronDetailMatch = path.match(/^\/api\/v1\/openclaw\/cron\/([^/]+)$/)
|
|
if (cronDetailMatch && ['GET', 'PATCH', 'DELETE'].includes(method)) {
|
|
const jobId = decodeURIComponent(cronDetailMatch[1])
|
|
const job = cronJobs.find(item => item.id === jobId)
|
|
if (!job) {
|
|
sendJson(response, 404, { detail: 'QA cron job not found' })
|
|
return
|
|
}
|
|
if (method === 'GET') {
|
|
sendJson(response, 200, operation('QA cron detail loaded.', cronDetail(job)))
|
|
return
|
|
}
|
|
if (method === 'PATCH') {
|
|
const body = await readBody(request)
|
|
if (!body.expectedHash || body.expectedHash !== job.resourceHash) {
|
|
sendJson(response, 409, {
|
|
detail: 'QA cron resource hash drift detected.',
|
|
currentHash: job.resourceHash,
|
|
})
|
|
return
|
|
}
|
|
const patch = body.patch && typeof body.patch === 'object' ? body.patch : {}
|
|
updateCronFromPatch(job, patch)
|
|
sendJson(response, 200, operation('QA cron schedule updated and read back.', cronDetail(job)))
|
|
return
|
|
}
|
|
const expectedHash = url.searchParams.get('expectedHash')
|
|
if (!expectedHash || expectedHash !== job.resourceHash) {
|
|
sendJson(response, 409, {
|
|
detail: 'QA cron resource hash drift detected.',
|
|
currentHash: job.resourceHash,
|
|
})
|
|
return
|
|
}
|
|
cronJobs.splice(cronJobs.indexOf(job), 1)
|
|
cronRunHistory.delete(job.id)
|
|
sendJson(response, 200, operation('QA cron schedule deleted.', { jobId: job.id }))
|
|
return
|
|
}
|
|
|
|
const approvalMatch = path.match(/^\/api\/v1\/openclaw\/approvals\/([^/]+)\/resolve$/)
|
|
if (approvalMatch && method === 'POST') {
|
|
const approval = approvals.find(item => item.id === decodeURIComponent(approvalMatch[1]))
|
|
const body = await readBody(request)
|
|
if (!approval) {
|
|
sendJson(response, 404, { detail: 'QA approval not found' })
|
|
return
|
|
}
|
|
approval.status = body.decision === 'deny' ? 'denied' : 'resolved'
|
|
approval.canResolve = false
|
|
addRuntimeActivity(
|
|
'Approval resolved',
|
|
`${approval.title}: ${String(body.decision ?? 'allow-once')}.`,
|
|
approval.status,
|
|
body.decision === 'deny' ? 'warning' : 'success',
|
|
)
|
|
sendJson(response, 200, operation('OpenClaw approval resolved.', clone(approval)))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/operations/snapshot') {
|
|
sendJson(response, 200, operationsSnapshot())
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/routing') {
|
|
sendJson(response, 200, models.filter(model => model.available).map((model, index) => ({
|
|
priority: index + 1,
|
|
provider: model.provider,
|
|
model: model.id,
|
|
purpose: index === 0 ? 'Primary orchestration' : index === 1 ? 'Implementation' : 'Fallback',
|
|
status: 'Online',
|
|
detail: 'Resolved and exposed by OpenClaw.',
|
|
})))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/status') {
|
|
sendJson(response, 200, {
|
|
gatewayOk: true,
|
|
irisStatus: 'Orchestrating',
|
|
activeAgents: dashboardAgents.filter(agent => agent.isActive).length,
|
|
pendingTasks: nexusTasks.filter(task => task.state !== 'Done').length,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/agents') {
|
|
sendJson(response, 200, clone(dashboardAgents))
|
|
return
|
|
}
|
|
|
|
const dashboardAgentActivityMatch = path.match(/^\/api\/dashboard\/agents\/([^/]+)\/activity$/)
|
|
if (dashboardAgentActivityMatch) {
|
|
const agentId = decodeURIComponent(dashboardAgentActivityMatch[1])
|
|
sendJson(response, 200, activity
|
|
.filter(item => item.agentId === agentId)
|
|
.slice(0, Number.parseInt(url.searchParams.get('limit') ?? '5', 10))
|
|
.map(item => ({ time: new Date(item.occurredAt).toLocaleTimeString('de-DE'), text: item.message })))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/operations') {
|
|
sendJson(response, 200, activity.map(item => ({
|
|
agent: item.agentId ?? item.actor ?? 'OpenClaw',
|
|
action: item.message,
|
|
timestamp: item.occurredAt,
|
|
time: new Date(item.occurredAt).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }),
|
|
agentId: item.agentId,
|
|
type: item.kind,
|
|
})))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/queue') {
|
|
sendJson(response, 200, nexusTasks
|
|
.filter(task => task.state !== 'Done')
|
|
.map(task => ({
|
|
id: task.id,
|
|
name: task.title,
|
|
status: task.state,
|
|
priority: task.priority,
|
|
source: task.source,
|
|
waitTime: task.state === 'Blocked' ? '24m' : '4m',
|
|
})))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/chat/messages') {
|
|
sendJson(response, 200, clone(chatHistory))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/chat' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
sendJson(response, 200, {
|
|
runtime: 'OpenClaw QA simulation',
|
|
agentId: String(body.agentId ?? 'iris'),
|
|
conversationId: String(body.conversationId ?? randomUUID()),
|
|
content: 'QA response: I would delegate this request through OpenClaw and retain the owner approval boundary.',
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/tasks' && method === 'GET') {
|
|
sendJson(response, 200, clone(nexusTasks))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/tasks/board' && method === 'GET') {
|
|
sendJson(response, 200, board())
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/tasks/agent-overview' && method === 'GET') {
|
|
sendJson(response, 200, {
|
|
waitingForBao: clone(nexusTasks.filter(task => task.expectedFrom === 'bao')),
|
|
waitingForIris: clone(nexusTasks.filter(task => task.expectedFrom === 'iris')),
|
|
waitingForOthers: clone(nexusTasks.filter(task => task.expectedFrom && !['bao', 'iris'].includes(task.expectedFrom))),
|
|
staleTasks: clone(nexusTasks.filter(task => task.state === 'Blocked')),
|
|
staleThreshold: '02:00:00',
|
|
})
|
|
return
|
|
}
|
|
|
|
const dashboardTaskMatch = path.match(/^\/api\/dashboard\/tasks\/([^/]+)$/)
|
|
if (dashboardTaskMatch) {
|
|
const id = decodeURIComponent(dashboardTaskMatch[1])
|
|
const task = taskById(id)
|
|
if (!task) {
|
|
sendJson(response, 404, { detail: 'QA task not found' })
|
|
return
|
|
}
|
|
if (method === 'DELETE') {
|
|
nexusTasks.splice(nexusTasks.indexOf(task), 1)
|
|
sendJson(response, 200, { ok: true })
|
|
return
|
|
}
|
|
if (method === 'PUT') {
|
|
const body = await readBody(request)
|
|
Object.assign(task, body, { updatedAt: now() })
|
|
}
|
|
sendJson(response, 200, clone(task))
|
|
return
|
|
}
|
|
|
|
const dashboardTaskChildrenMatch = path.match(/^\/api\/dashboard\/tasks\/([^/]+)\/children$/)
|
|
if (dashboardTaskChildrenMatch) {
|
|
const id = decodeURIComponent(dashboardTaskChildrenMatch[1])
|
|
sendJson(response, 200, clone(nexusTasks.filter(task => task.parentTaskId === id)))
|
|
return
|
|
}
|
|
|
|
const dashboardTaskActivityMatch = path.match(/^\/api\/dashboard\/tasks\/([^/]+)\/activity$/)
|
|
if (dashboardTaskActivityMatch) {
|
|
const task = taskById(decodeURIComponent(dashboardTaskActivityMatch[1]))
|
|
if (!task) {
|
|
sendJson(response, 404, { detail: 'QA task not found' })
|
|
return
|
|
}
|
|
if (method === 'POST') {
|
|
sendJson(response, 201, { id: randomUUID(), message: 'QA activity added.', createdAt: now() })
|
|
return
|
|
}
|
|
sendJson(response, 200, [
|
|
{ id: 'task-event-1', type: 'task', message: task.lastActivityMessage, createdAt: task.lastActivityAt },
|
|
{ id: 'task-event-2', type: 'delegation', message: `Assigned to ${task.assignedTo ?? 'unassigned'}.`, createdAt: task.createdAt },
|
|
])
|
|
return
|
|
}
|
|
|
|
const dashboardTaskMutationMatch = path.match(/^\/api\/dashboard\/tasks\/([^/]+)\/(move|status)$/)
|
|
if (dashboardTaskMutationMatch && method === 'PATCH') {
|
|
const task = taskById(decodeURIComponent(dashboardTaskMutationMatch[1]))
|
|
const body = await readBody(request)
|
|
if (!task) {
|
|
sendJson(response, 404, { detail: 'QA task not found' })
|
|
return
|
|
}
|
|
const stateMap = {
|
|
offen: 'Backlog',
|
|
inProgress: 'In progress',
|
|
review: 'Review',
|
|
blocked: 'Blocked',
|
|
done: 'Done',
|
|
}
|
|
task.state = stateMap[body.state] ?? body.state ?? task.state
|
|
task.updatedAt = now()
|
|
sendJson(response, 200, clone(task))
|
|
return
|
|
}
|
|
|
|
if ((path === '/api/dashboard/tasks' || path === '/api/dashboard/tasks/agent') && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const created = {
|
|
id: randomUUID(),
|
|
title: String(body.title ?? 'New QA task'),
|
|
detail: body.detail ?? null,
|
|
source: String(body.source ?? 'bao'),
|
|
state: String(body.initialState ?? (body.startsInProgress ? 'In progress' : 'Backlog')),
|
|
priority: String(body.priority ?? 'Normal'),
|
|
assignedTo: body.assignedTo ?? 'bao',
|
|
projectId: null,
|
|
parentTaskId: body.parentTaskId ?? null,
|
|
dueDate: null,
|
|
createdAt: now(),
|
|
updatedAt: now(),
|
|
isAgentTask: path.endsWith('/agent'),
|
|
expectedFrom: body.expectedFrom ?? null,
|
|
lastActivityMessage: 'Created through the Nexus QA fixture.',
|
|
lastActivityAt: now(),
|
|
childTasks: [],
|
|
childTaskCount: 0,
|
|
openChildTaskCount: 0,
|
|
hasVisibleDelegation: false,
|
|
}
|
|
nexusTasks.unshift(created)
|
|
sendJson(response, 201, clone(created))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/notifications') {
|
|
const unreadOnly = url.searchParams.get('unreadOnly') === 'true'
|
|
sendJson(response, 200, clone(unreadOnly ? notifications.filter(item => !item.isRead) : notifications))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/notifications/unread-count') {
|
|
sendJson(response, 200, { count: notifications.filter(item => !item.isRead).length })
|
|
return
|
|
}
|
|
|
|
const notificationReadMatch = path.match(/^\/api\/dashboard\/notifications\/([^/]+)\/read$/)
|
|
if (notificationReadMatch && method === 'PATCH') {
|
|
const notification = notifications.find(item => item.id === decodeURIComponent(notificationReadMatch[1]))
|
|
if (notification) notification.isRead = true
|
|
sendJson(response, 200, { ok: true })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/notifications/read-all' && method === 'PATCH') {
|
|
notifications.forEach(item => { item.isRead = true })
|
|
sendJson(response, 200, { ok: true })
|
|
return
|
|
}
|
|
|
|
if (path === '/api/dashboard/live') {
|
|
sendSseSnapshot(response)
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/security/status') {
|
|
sendJson(response, 200, {
|
|
authMethod: 'JWT bearer + rotating refresh cookie',
|
|
tokenConfig: {
|
|
issuer: 'Nexus',
|
|
audience: 'Nexus.Web',
|
|
refreshTokenDays: 14,
|
|
accessTokenMinutes: 20,
|
|
},
|
|
rateLimit: 'Per-IP and per-route mutation limits',
|
|
passwordPolicy: '10+ characters with breached-password screening',
|
|
cookieConfig: { httpOnly: true, secure: true, sameSite: 'Strict' },
|
|
twoFactorEnabled: false,
|
|
passkeyEnabled: false,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/admin/users' && method === 'GET') {
|
|
sendJson(response, 200, clone(users))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/admin/users' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const created = {
|
|
id: randomUUID(),
|
|
email: body.email,
|
|
displayName: body.displayName || body.email,
|
|
role: body.role || 'user',
|
|
createdAt: now(),
|
|
lastLoginAt: null,
|
|
}
|
|
users.push(created)
|
|
sendJson(response, 201, clone(created))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/projects' && method === 'GET') {
|
|
sendJson(response, 200, clone(projects))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/projects' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const created = {
|
|
id: randomUUID(),
|
|
name: String(body.name ?? 'QA project'),
|
|
description: '',
|
|
status: 'Active',
|
|
progress: 0,
|
|
updatedAt: now(),
|
|
}
|
|
projects.unshift(created)
|
|
sendJson(response, 201, clone(created))
|
|
return
|
|
}
|
|
|
|
const projectMatch = path.match(/^\/api\/v1\/projects\/([^/]+)$/)
|
|
if (projectMatch) {
|
|
const project = projects.find(item => item.id === decodeURIComponent(projectMatch[1]))
|
|
if (!project) {
|
|
sendJson(response, 404, { detail: 'QA project not found' })
|
|
return
|
|
}
|
|
if (method === 'PATCH') {
|
|
const body = await readBody(request)
|
|
Object.assign(project, body, { updatedAt: now() })
|
|
}
|
|
if (method === 'DELETE') {
|
|
projects.splice(projects.indexOf(project), 1)
|
|
sendJson(response, 200, clone(project))
|
|
return
|
|
}
|
|
sendJson(response, 200, clone(project))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/tasks/pending-approval') {
|
|
sendJson(response, 200, nexusTasks.filter(task => task.state === 'Review').map(task => ({
|
|
id: task.id,
|
|
title: task.title,
|
|
state: task.state,
|
|
priority: task.priority,
|
|
projectId: task.projectId,
|
|
updatedAt: task.updatedAt,
|
|
})))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/tasks' && method === 'GET') {
|
|
sendJson(response, 200, nexusTasks.map(task => ({
|
|
id: task.id,
|
|
title: task.title,
|
|
state: task.state,
|
|
priority: task.priority,
|
|
projectId: task.projectId,
|
|
updatedAt: task.updatedAt,
|
|
})))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/tasks' && method === 'POST') {
|
|
const body = await readBody(request)
|
|
const created = {
|
|
id: randomUUID(),
|
|
title: String(body.title ?? 'QA task'),
|
|
detail: null,
|
|
source: 'bao',
|
|
state: 'Backlog',
|
|
priority: String(body.priority ?? 'Normal'),
|
|
assignedTo: 'bao',
|
|
projectId: null,
|
|
parentTaskId: null,
|
|
dueDate: null,
|
|
createdAt: now(),
|
|
updatedAt: now(),
|
|
isAgentTask: false,
|
|
expectedFrom: null,
|
|
lastActivityMessage: 'Created through the Nexus QA fixture.',
|
|
lastActivityAt: now(),
|
|
childTasks: [],
|
|
childTaskCount: 0,
|
|
openChildTaskCount: 0,
|
|
hasVisibleDelegation: false,
|
|
}
|
|
nexusTasks.unshift(created)
|
|
sendJson(response, 201, clone(created))
|
|
return
|
|
}
|
|
|
|
const taskMutationMatch = path.match(/^\/api\/v1\/tasks\/([^/]+)(?:\/(state|approve|reject))?$/)
|
|
if (taskMutationMatch) {
|
|
const task = taskById(decodeURIComponent(taskMutationMatch[1]))
|
|
if (!task) {
|
|
sendJson(response, 404, { detail: 'QA task not found' })
|
|
return
|
|
}
|
|
const action = taskMutationMatch[2]
|
|
if (action === 'state' && method === 'PATCH') {
|
|
const body = await readBody(request)
|
|
task.state = String(body.state ?? task.state)
|
|
} else if (action === 'approve' && method === 'POST') {
|
|
task.state = 'Done'
|
|
} else if (action === 'reject' && method === 'POST') {
|
|
task.state = 'Backlog'
|
|
} else if (!action && method === 'PATCH') {
|
|
const body = await readBody(request)
|
|
Object.assign(task, body)
|
|
} else if (!action && method === 'DELETE') {
|
|
nexusTasks.splice(nexusTasks.indexOf(task), 1)
|
|
sendJson(response, 204, null)
|
|
return
|
|
}
|
|
task.updatedAt = now()
|
|
sendJson(response, 200, clone(task))
|
|
return
|
|
}
|
|
|
|
const openClawAgentFileMatch = path.match(
|
|
/^\/api\/v1\/openclaw\/agents\/([^/]+)\/files\/([^/]+)$/,
|
|
)
|
|
if (openClawAgentFileMatch && ['GET', 'PUT'].includes(method)) {
|
|
const agentId = decodeURIComponent(openClawAgentFileMatch[1])
|
|
const fileName = decodeURIComponent(openClawAgentFileMatch[2])
|
|
const files = agentFiles.get(agentId)
|
|
if (!files || !files.has(fileName)) {
|
|
sendJson(response, 404, { detail: 'QA OpenClaw agent file not found.' })
|
|
return
|
|
}
|
|
let content = files.get(fileName)
|
|
let contentHash = agentFileHash(agentId, fileName, content)
|
|
if (method === 'PUT') {
|
|
const body = await readBody(request)
|
|
if (body.expectedHash !== contentHash) {
|
|
sendJson(response, 409, {
|
|
message: 'OpenClaw contains a newer QA file version.',
|
|
expectedHash: body.expectedHash ?? null,
|
|
currentHash: contentHash,
|
|
})
|
|
return
|
|
}
|
|
content = String(body.content ?? '')
|
|
files.set(fileName, content)
|
|
contentHash = agentFileHash(agentId, fileName, content)
|
|
sendJson(response, 200, {
|
|
ok: true,
|
|
state: 'succeeded',
|
|
message: `${fileName} was saved through the QA OpenClaw RPC and read back.`,
|
|
file: {
|
|
agentId,
|
|
name: fileName,
|
|
missing: false,
|
|
size: Buffer.byteLength(content, 'utf8'),
|
|
updatedAt: now(),
|
|
contentHash,
|
|
content,
|
|
checkedAt: now(),
|
|
},
|
|
verified: true,
|
|
idempotencyKey: requestHeader(request, 'idempotency-key', `qa-agent-file-${randomUUID()}`),
|
|
correlationId: requestHeader(request, 'x-correlation-id', `qa-correlation-${randomUUID()}`),
|
|
completedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
agentId,
|
|
name: fileName,
|
|
missing: false,
|
|
size: Buffer.byteLength(content, 'utf8'),
|
|
updatedAt: minutesAgo(25),
|
|
contentHash,
|
|
content,
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
const openClawAgentFilesMatch = path.match(
|
|
/^\/api\/v1\/openclaw\/agents\/([^/]+)\/files$/,
|
|
)
|
|
if (openClawAgentFilesMatch && method === 'GET') {
|
|
const agentId = decodeURIComponent(openClawAgentFilesMatch[1])
|
|
const files = agentFiles.get(agentId)
|
|
if (!files) {
|
|
sendJson(response, 404, { detail: 'QA OpenClaw agent not found.' })
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
agentId,
|
|
files: [...files.entries()].map(([name, content]) => ({
|
|
name,
|
|
missing: false,
|
|
size: Buffer.byteLength(content, 'utf8'),
|
|
updatedAt: minutesAgo(25),
|
|
contentHash: agentFileHash(agentId, name, content),
|
|
})),
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
const openClawWorkspaceFileMatch = path.match(
|
|
/^\/api\/v1\/openclaw\/agents\/([^/]+)\/workspace\/file$/,
|
|
)
|
|
if (openClawWorkspaceFileMatch && method === 'GET') {
|
|
const agentId = decodeURIComponent(openClawWorkspaceFileMatch[1])
|
|
const filePath = url.searchParams.get('path') || ''
|
|
if (!agentWorkspaceEntries.has(agentId) || !['DREAMS.md', 'memory/2026-07-30.md'].includes(filePath)) {
|
|
sendJson(response, 404, { detail: 'QA read-only workspace file not found.' })
|
|
return
|
|
}
|
|
const content = filePath === 'DREAMS.md'
|
|
? '# Dreams\n\nKeep Nexus agent-first, inspectable, and safe.\n'
|
|
: '# 2026-07-30\n\nQA adoption inventory inspected without copying OpenClaw state.\n'
|
|
sendJson(response, 200, {
|
|
agentId,
|
|
path: filePath,
|
|
name: filePath.split('/').at(-1),
|
|
size: Buffer.byteLength(content, 'utf8'),
|
|
updatedAt: minutesAgo(75),
|
|
mimeType: 'text/markdown',
|
|
encoding: 'utf-8',
|
|
content,
|
|
contentHash: agentFileHash(agentId, filePath, content),
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
const openClawWorkspaceMatch = path.match(
|
|
/^\/api\/v1\/openclaw\/agents\/([^/]+)\/workspace$/,
|
|
)
|
|
if (openClawWorkspaceMatch && method === 'GET') {
|
|
const agentId = decodeURIComponent(openClawWorkspaceMatch[1])
|
|
const currentPath = url.searchParams.get('path') || ''
|
|
if (!agentWorkspaceEntries.has(agentId)) {
|
|
sendJson(response, 404, { detail: 'QA OpenClaw agent workspace not found.' })
|
|
return
|
|
}
|
|
const entries = currentPath === 'memory'
|
|
? [{
|
|
path: 'memory/2026-07-30.md',
|
|
name: '2026-07-30.md',
|
|
kind: 'file',
|
|
size: 82,
|
|
updatedAt: minutesAgo(75),
|
|
}]
|
|
: agentWorkspaceEntries.get(agentId)
|
|
sendJson(response, 200, {
|
|
agentId,
|
|
path: currentPath,
|
|
parentPath: currentPath ? '' : null,
|
|
entries: clone(entries),
|
|
totalEntries: entries.length,
|
|
offset: 0,
|
|
checkedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/agents') {
|
|
sendJson(response, 200, openClawAgents.map(agent => ({
|
|
...agent,
|
|
role: agent.id === 'iris' ? 'orchestrator' : 'specialist',
|
|
lastSeen: minutesAgo(agent.status === 'idle' ? 16 : 1),
|
|
})))
|
|
return
|
|
}
|
|
|
|
const agentMatch = path.match(/^\/api\/v1\/agents\/([^/]+)$/)
|
|
if (agentMatch) {
|
|
const agent = openClawAgents.find(item => item.id === decodeURIComponent(agentMatch[1]))
|
|
if (!agent) {
|
|
sendJson(response, 404, { detail: 'QA agent not found' })
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
...agent,
|
|
role: agent.id === 'iris' ? 'orchestrator' : 'specialist',
|
|
lastSeen: minutesAgo(agent.status === 'idle' ? 16 : 1),
|
|
agentDir: `C:\\Noveria\\Agents\\${agent.id}`,
|
|
subAgents: agent.id === 'iris' ? ['programmer', 'researcher', 'archivist'] : [],
|
|
identityName: agent.name,
|
|
})
|
|
return
|
|
}
|
|
|
|
const agentActivityMatch = path.match(/^\/api\/v1\/agents\/([^/]+)\/activity$/)
|
|
if (agentActivityMatch) {
|
|
const agentId = decodeURIComponent(agentActivityMatch[1])
|
|
sendJson(response, 200, activity.filter(item => item.agentId === agentId).map(item => ({
|
|
id: item.id,
|
|
type: item.kind,
|
|
message: item.message,
|
|
at: item.occurredAt,
|
|
relativeTime: null,
|
|
})))
|
|
return
|
|
}
|
|
|
|
const agentSummaryMatch = path.match(/^\/api\/v1\/agents\/([^/]+)\/summary$/)
|
|
if (agentSummaryMatch) {
|
|
const agentId = decodeURIComponent(agentSummaryMatch[1])
|
|
const agent = openClawAgents.find(item => item.id === agentId)
|
|
const dashboardAgent = dashboardAgents.find(item => item.id === agentId)
|
|
sendJson(response, 200, {
|
|
now: {
|
|
text: dashboardAgent?.currentTask
|
|
? `${agent?.name ?? agentId} arbeitet an: ${dashboardAgent.currentTask}.`
|
|
: `${agent?.name ?? agentId} ist bereit für neue Arbeit.`,
|
|
source: 'gateway-session-history',
|
|
timestamp: minutesAgo(2),
|
|
},
|
|
today: {
|
|
text: agent?.description ?? 'No agent summary available.',
|
|
source: 'derived-mixed',
|
|
timestamp: minutesAgo(73),
|
|
},
|
|
generatedAt: now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
const agentConfigMatch = path.match(/^\/api\/v1\/agents\/([^/]+)\/config(?:\/(.+))?$/)
|
|
if (agentConfigMatch) {
|
|
const fileName = agentConfigMatch[2] ? decodeURIComponent(agentConfigMatch[2]) : null
|
|
if (!fileName) {
|
|
sendJson(response, 200, [
|
|
{ fileName: 'AGENTS.md', size: 412, modifiedAt: minutesAgo(190) },
|
|
{ fileName: 'IDENTITY.md', size: 288, modifiedAt: minutesAgo(215) },
|
|
{ fileName: 'TOOLS.md', size: 346, modifiedAt: minutesAgo(170) },
|
|
])
|
|
return
|
|
}
|
|
if (method === 'PUT') {
|
|
sendJson(response, 200, {
|
|
fileName,
|
|
size: 412,
|
|
modifiedAt: now(),
|
|
validation: {
|
|
status: 'valid',
|
|
fileKind: fileName.replace(/\..+$/, '').toLocaleLowerCase(),
|
|
errors: [],
|
|
},
|
|
backup: {
|
|
status: 'created',
|
|
backupCreated: true,
|
|
},
|
|
reloadCheck: {
|
|
status: 'not_supported',
|
|
message: 'QA fixture does not reload a live agent.',
|
|
},
|
|
})
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
fileName,
|
|
size: 412,
|
|
modifiedAt: minutesAgo(190),
|
|
content: `# ${fileName}\n\nQA fixture for the OpenClaw-backed Nexus mission control.\n`,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/incidents') {
|
|
sendJson(response, 200, clone(incidents))
|
|
return
|
|
}
|
|
|
|
const incidentMatch = path.match(/^\/api\/v1\/incidents\/(.+)$/)
|
|
if (incidentMatch) {
|
|
const incident = incidents.find(item => item.name === decodeURIComponent(incidentMatch[1]))
|
|
if (!incident) {
|
|
sendJson(response, 404, { detail: 'QA incident not found' })
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
...clone(incident),
|
|
content: `# ${incident.title}\n\nThis is simulated QA content. No live OpenClaw incident is represented.\n\n## Boundary\n\n${incident.excerpt}\n`,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/memory/search') {
|
|
const query = (url.searchParams.get('q') ?? '').toLocaleLowerCase()
|
|
sendJson(response, 200, clone(memories.filter(item =>
|
|
item.name.toLocaleLowerCase().includes(query)
|
|
|| item.excerpt.toLocaleLowerCase().includes(query),
|
|
)))
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/memory') {
|
|
sendJson(response, 200, clone(memories))
|
|
return
|
|
}
|
|
|
|
const memoryMatch = path.match(/^\/api\/v1\/memory\/(.+)$/)
|
|
if (memoryMatch) {
|
|
const memory = memories.find(item => item.name === decodeURIComponent(memoryMatch[1]))
|
|
if (!memory) {
|
|
sendJson(response, 404, { detail: 'QA memory not found' })
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
...clone(memory),
|
|
content: `# ${memory.title}\n\n${memory.excerpt}\n\n> Simulated QA content; not a live OpenClaw memory entry.\n`,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (path === '/api/v1/docs') {
|
|
sendJson(response, 200, clone(docs))
|
|
return
|
|
}
|
|
|
|
const docMatch = path.match(/^\/api\/v1\/docs\/(.+)$/)
|
|
if (docMatch) {
|
|
const doc = docs.find(item => item.path === decodeURIComponent(docMatch[1]))
|
|
if (!doc) {
|
|
sendJson(response, 404, { detail: 'QA document not found' })
|
|
return
|
|
}
|
|
sendJson(response, 200, {
|
|
...clone(doc),
|
|
content: `# ${doc.title}\n\n${doc.excerpt}\n\n## Integration contract\n\nNexus authenticates browser actions and delegates typed operations to OpenClaw.\n`,
|
|
})
|
|
return
|
|
}
|
|
|
|
sendJson(response, 404, {
|
|
detail: `No QA fixture exists for ${method} ${path}`,
|
|
fixture: qaHeader,
|
|
})
|
|
})
|
|
|
|
server.listen(port, '127.0.0.1', () => {
|
|
console.log(`[nexus-qa] simulated OpenClaw API listening on http://127.0.0.1:${port}`)
|
|
})
|