重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。

同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。

Made-with: Cursor
This commit is contained in:
oliver 2026-04-26 08:34:33 +08:00
parent 4a23b715a2
commit dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions

View file

@ -0,0 +1,120 @@
# Config apply restart wake-up
```yaml qa-scenario
id: config-apply-restart-wakeup
title: Config apply restart wake-up
surface: config
coverage:
primary:
- config.restart-apply
secondary:
- runtime.gateway-restart
objective: Verify a restart-required config.apply restarts cleanly and delivers the post-restart wake message back into the QA channel.
successCriteria:
- config.apply schedules a restart-required change.
- Gateway becomes healthy again after restart.
- Restart sentinel wake-up message arrives in the QA channel.
docsRefs:
- docs/gateway/configuration.md
- docs/gateway/protocol.md
codeRefs:
- src/gateway/server-methods/config.ts
- src/gateway/server-restart-sentinel.ts
execution:
kind: flow
summary: Verify a restart-required config.apply restarts cleanly and delivers the post-restart wake message back into the QA channel.
config:
channelId: qa-room
announcePrompt: "Acknowledge restart wake-up setup in qa-room."
```
```yaml qa-flow
steps:
- name: restarts cleanly and posts the restart sentinel back into qa-channel
actions:
- call: reset
- set: sessionKey
value:
expr: "buildAgentSessionKey({ agentId: 'qa', channel: 'qa-channel', peer: { kind: 'channel', id: config.channelId } })"
- call: createSession
args:
- ref: env
- Restart wake-up
- ref: sessionKey
- call: runAgentPrompt
args:
- ref: env
- sessionKey:
ref: sessionKey
to:
expr: "`channel:${config.channelId}`"
message:
expr: config.announcePrompt
timeoutMs:
expr: liveTurnTimeoutMs(env, 30000)
- call: readConfigSnapshot
saveAs: current
args:
- ref: env
- set: nextConfig
value:
expr: "(() => { const nextConfig = structuredClone(current.config); const gatewayConfig = (nextConfig.gateway ??= {}); const controlUi = (gatewayConfig.controlUi ??= {}); const allowedOrigins = Array.isArray(controlUi.allowedOrigins) ? [...controlUi.allowedOrigins] : []; if (!allowedOrigins.includes('http://127.0.0.1:65535')) allowedOrigins.push('http://127.0.0.1:65535'); controlUi.allowedOrigins = allowedOrigins; return nextConfig; })()"
- set: wakeMarker
value:
expr: "`QA-RESTART-${randomUUID().slice(0, 8)}`"
- set: wakeStartIndex
value:
expr: "state.getSnapshot().messages.length"
- call: applyConfig
args:
- env:
ref: env
nextConfig:
ref: nextConfig
sessionKey:
ref: sessionKey
deliveryContext:
expr: "({ channel: 'qa-channel', to: `channel:${config.channelId}` })"
note:
ref: wakeMarker
- try:
actions:
- call: waitForGatewayHealthy
args:
- ref: env
- 60000
catchAs: healthyError
catch:
- throw:
message:
expr: "`gateway never returned healthy after config.apply: ${formatErrorMessage(healthyError)}`"
- try:
actions:
- call: waitForQaChannelReady
args:
- ref: env
- 60000
catchAs: readyError
catch:
- throw:
message:
expr: "`qa-channel never returned ready after config.apply: ${formatErrorMessage(readyError)}`"
- try:
actions:
- call: waitForOutboundMessage
saveAs: outbound
args:
- ref: state
- lambda:
params: [candidate]
expr: "candidate.text.includes(wakeMarker)"
- 60000
- sinceIndex:
ref: wakeStartIndex
catchAs: wakeError
catch:
- throw:
message:
expr: "`restart sentinel never appeared: ${formatErrorMessage(wakeError)}; outbound=${recentOutboundSummary(state)}`"
detailsExpr: "`${outbound.conversation.id}: ${outbound.text}`"
```

View file

@ -0,0 +1,120 @@
# Config patch skill disable
```yaml qa-scenario
id: config-patch-hot-apply
title: Config patch skill disable
surface: config
coverage:
primary:
- config.hot-apply
secondary:
- plugins.skills
objective: Verify config.patch can disable a workspace skill and the restarted gateway exposes the new disabled state cleanly.
successCriteria:
- config.patch succeeds for the skill toggle change.
- A workspace skill works before the patch.
- The same skill is reported disabled after the restart triggered by the patch.
docsRefs:
- docs/gateway/configuration.md
- docs/gateway/protocol.md
codeRefs:
- src/gateway/server-methods/config.ts
- extensions/qa-lab/src/suite.ts
execution:
kind: flow
summary: Verify config.patch can disable a workspace skill and the restarted gateway exposes the new disabled state cleanly.
config:
skillName: qa-hot-disable-skill
successMarker: HOT-PATCH-DISABLED-OK
skillBody: |-
---
name: qa-hot-disable-skill
description: Hot disable QA marker
---
When the user asks for the hot disable marker exactly, reply with exactly: HOT-PATCH-DISABLED-OK
```
```yaml qa-flow
steps:
- name: disables a workspace skill after config.patch restart
actions:
- call: writeWorkspaceSkill
args:
- env:
ref: env
name:
expr: config.skillName
body:
expr: config.skillBody
- try:
actions:
- call: waitForCondition
args:
- lambda:
async: true
expr: "findSkill(await readSkillStatus(env), config.skillName)?.eligible ? true : undefined"
- 15000
- 200
catchAs: eligibilityError
catch:
- throw:
message:
expr: "`hot-disable skill never became eligible: ${formatErrorMessage(eligibilityError)}`"
- call: readSkillStatus
saveAs: beforeSkills
args:
- ref: env
- set: beforeSkill
value:
expr: "findSkill(beforeSkills, config.skillName)"
- assert:
expr: "Boolean(beforeSkill?.eligible) && beforeSkill?.disabled !== true"
message:
expr: "`unexpected pre-patch skill state: ${JSON.stringify(beforeSkill)}`"
- call: patchConfig
saveAs: patchResult
args:
- env:
ref: env
patch:
skills:
entries:
expr: "({ [config.skillName]: { enabled: false } })"
- try:
actions:
- call: waitForQaChannelReady
args:
- ref: env
- 60000
catchAs: readyError
catch:
- throw:
message:
expr: "`qa-channel never returned ready after config.patch: ${formatErrorMessage(readyError)}`"
- try:
actions:
- call: waitForCondition
args:
- lambda:
async: true
expr: "findSkill(await readSkillStatus(env), config.skillName)?.disabled ? true : undefined"
- 15000
- 200
catchAs: disabledError
catch:
- throw:
message:
expr: "`hot-disable skill never flipped to disabled: ${formatErrorMessage(disabledError)}`"
- call: readSkillStatus
saveAs: afterSkills
args:
- ref: env
- set: afterSkill
value:
expr: "findSkill(afterSkills, config.skillName)"
- assert:
expr: "Boolean(afterSkill?.disabled)"
message:
expr: "`unexpected post-patch skill state: ${JSON.stringify(afterSkill)}`"
detailsExpr: " `restartDelayMs=${String(patchResult.restart?.delayMs ?? '')}\\nmarker=${config.successMarker}\\npre=${JSON.stringify(beforeSkill)}\\npost=${JSON.stringify(afterSkill)}` "
```

View file

@ -0,0 +1,190 @@
# Config restart capability flip
```yaml qa-scenario
id: config-restart-capability-flip
title: Config restart capability flip
surface: config
coverage:
primary:
- config.restart-apply
secondary:
- plugins.capabilities
objective: Verify a restart-triggering config change flips capability inventory and the same session successfully uses the newly restored tool after wake-up.
successCriteria:
- Capability is absent before the restart-triggering patch.
- Restart sentinel wakes the same session back up after config patch.
- The restored capability appears in tools.effective and works in the follow-up turn.
docsRefs:
- docs/gateway/configuration.md
- docs/gateway/protocol.md
- docs/tools/image-generation.md
codeRefs:
- src/gateway/server-methods/config.ts
- src/gateway/server-restart-sentinel.ts
- src/gateway/server-methods/tools-effective.ts
- extensions/qa-lab/src/suite.ts
execution:
kind: flow
summary: Verify a restart-triggering config change flips capability inventory and the same session successfully uses the newly restored tool after wake-up.
config:
setupPrompt: "Capability flip setup: acknowledge this setup so restart wake-up has a route."
imagePrompt: "Capability flip image check: generate a QA lighthouse image in this turn right now. Do not acknowledge first, do not promise future work, and do not stop before using image_generate. Final reply must include the MEDIA path."
imagePromptSnippet: "Capability flip image check"
deniedTool: image_generate
```
```yaml qa-flow
steps:
- name: restores image_generate after restart and uses it in the same session
actions:
- call: ensureImageGenerationConfigured
args:
- ref: env
- call: readConfigSnapshot
saveAs: original
args:
- ref: env
- set: originalTools
value:
expr: "original.config.tools && typeof original.config.tools === 'object' ? original.config.tools : null"
- set: originalToolsDeny
value:
expr: "originalTools ? (Object.prototype.hasOwnProperty.call(originalTools, 'deny') ? structuredClone(originalTools.deny) : undefined) : undefined"
- set: denied
value:
expr: "Array.isArray(originalToolsDeny) ? originalToolsDeny.map((entry) => String(entry)) : []"
- set: deniedWithImage
value:
expr: "denied.includes(config.deniedTool) ? denied : [...denied, config.deniedTool]"
- set: sessionKey
value: agent:qa:capability-flip
- call: createSession
args:
- ref: env
- Capability flip
- ref: sessionKey
- try:
actions:
- call: patchConfig
args:
- env:
ref: env
patch:
tools:
deny:
ref: deniedWithImage
- call: waitForGatewayHealthy
args:
- ref: env
- call: waitForQaChannelReady
args:
- ref: env
- 60000
- call: runAgentPrompt
args:
- ref: env
- sessionKey:
ref: sessionKey
message:
expr: config.setupPrompt
timeoutMs:
expr: liveTurnTimeoutMs(env, 30000)
- call: readEffectiveTools
saveAs: beforeTools
args:
- ref: env
- ref: sessionKey
- assert:
expr: "!beforeTools.has(config.deniedTool)"
message:
expr: "`${config.deniedTool} still present before capability flip`"
- set: wakeMarker
value:
expr: "`QA-CAPABILITY-${randomUUID().slice(0, 8)}`"
- call: patchConfig
args:
- env:
ref: env
patch:
tools:
deny:
expr: "originalToolsDeny === undefined ? null : originalToolsDeny"
agents:
defaults:
imageGenerationModel:
primary: openai/gpt-image-1
sessionKey:
ref: sessionKey
note:
ref: wakeMarker
- call: waitForGatewayHealthy
args:
- ref: env
- 60000
- call: waitForQaChannelReady
args:
- ref: env
- 60000
- call: waitForCondition
saveAs: afterTools
args:
- lambda:
async: true
expr: "(() => readEffectiveTools(env, sessionKey).then((tools) => (tools.has('image_generate') ? tools : undefined)))()"
- expr: liveTurnTimeoutMs(env, 45000)
- 500
- set: imageStartedAtMs
value:
expr: "Date.now()"
- call: runAgentPrompt
args:
- ref: env
- sessionKey:
ref: sessionKey
message:
expr: config.imagePrompt
timeoutMs:
expr: liveTurnTimeoutMs(env, 45000)
- call: resolveGeneratedImagePath
saveAs: mediaPath
args:
- env:
ref: env
promptSnippet:
expr: config.imagePromptSnippet
startedAtMs:
ref: imageStartedAtMs
timeoutMs:
expr: liveTurnTimeoutMs(env, 45000)
# Tool-call assertion (criterion 2 of the parity completion
# gate in #64227): the restored `image_generate` capability
# must have actually fired as a real tool call. Without this
# assertion, a prose reply that just mentions a MEDIA path
# could satisfy the scenario, so strengthen it by requiring
# the mock to have recorded `plannedToolName: "image_generate"`
# against a post-restart request. The `!env.mock || ...`
# guard means this check only runs in mock mode (where
# `/debug/requests` is available); live-frontier runs skip
# it and still pass the rest of the scenario.
- assert:
expr: "!env.mock || [...(await fetchJson(`${env.mock.baseUrl}/debug/requests`))].some((request) => String(request.allInputText ?? '').toLowerCase().includes('capability flip image check') && request.plannedToolName === 'image_generate')"
message:
expr: "`expected image_generate tool call during capability flip scenario, saw plannedToolNames=${JSON.stringify([...(await fetchJson(`${env.mock.baseUrl}/debug/requests`))].filter((request) => String(request.allInputText ?? '').toLowerCase().includes('capability flip image check')).map((request) => request.plannedToolName ?? null))}`"
finally:
- call: patchConfig
args:
- env:
ref: env
patch:
tools:
deny:
expr: "originalToolsDeny === undefined ? null : originalToolsDeny"
- call: waitForGatewayHealthy
args:
- ref: env
- call: waitForQaChannelReady
args:
- ref: env
- 60000
detailsExpr: "`${wakeMarker}\\n${config.deniedTool}=${String(afterTools.has(config.deniedTool))}\\nMEDIA:${mediaPath}`"
```