WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
const assert = require('node:assert/strict')
const fs = require('node:fs')
const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
const origin = process.env.VERIFY_FRONTEND_ORIGIN || 'http://127.0.0.1:15173'
const apiOrigin = process.env.VERIFY_API_ORIGIN || 'http://127.0.0.1:18080'
const reportName = process.env.VERIFY_REPORT_NAME || 'agent-creation-ui'
assert.match(reportName, /^[a-zA-Z0-9_-]+$/)
const startedAt = new Date().toISOString()
// Draft AI output is a fixture; other reads use the running local API. No resources are saved.
async function main() {
const directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance'
const credentials = JSON.parse(fs.readFileSync(`${directory}/super-admin-login.json`, 'utf8'))
const response = await fetch(`${apiOrigin}/api/v1/auth/login`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: credentials.email, password: credentials.password }),
})
assert.equal(response.status, 200)
const auth = await response.json()
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
const checks = [], errors = [], failures = [], requests = [], failedRequests = [], slowRequests = []
const pendingRequests = new Map()
let finalPath = ''
const check = (name, passed) => { checks.push({ name, passed: !!passed }); assert.ok(passed, name) }
try {
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } })
await context.addInitScript(a => {
const entries = {
weknora_user: JSON.stringify({ ...a.user, tenant_id: String(a.user.tenant_id) }),
weknora_tenant: JSON.stringify({ ...a.active_tenant, id: '10059' }),
weknora_token: a.token, weknora_refresh_token: a.refresh_token,
weknora_memberships: JSON.stringify(a.memberships),
'weknora:new-user-guide-done:v1': '1',
}
for (const [key, value] of Object.entries(entries)) localStorage.setItem(key, value)
}, auth)
let releaseDraft, draftRequest
await context.route('**/api/v1/agents/draft', route => new Promise(resolve => {
draftRequest = route.request().postDataJSON()
releaseDraft = async () => {
await route.fulfill({ status: 200, json: { success: true, data: {
name: '课程框架规划助手', description: '按用户目标整理课程结构,确保前后衔接。', avatar: '',
config: { role: 'training_map', agent_type: 'custom', agent_mode: 'quick-answer', model_id: draftRequest.model_id, system_prompt: '规划课程框架并说明每一天的前置条件。', kb_selection_mode: 'none', knowledge_bases: [], mcp_selection_mode: 'none', skills_selection_mode: 'none', selected_skills: [], instruction_skill_ids: [] },
} } })
resolve()
}
}))
const page = await context.newPage()
page.on('request', request => {
requests.push({ path: new URL(request.url()).pathname, method: request.method() })
pendingRequests.set(request, Date.now())
})
page.on('requestfinished', request => {
const started = pendingRequests.get(request)
pendingRequests.delete(request)
const duration = started === undefined ? 0 : Date.now() - started
if (duration >= 3000) slowRequests.push({ path: new URL(request.url()).pathname, duration_ms: duration })
})
page.on('requestfailed', request => {
pendingRequests.delete(request)
const failure = { path: new URL(request.url()).pathname, error: request.failure()?.errorText || '' }
failedRequests.push(failure)
if (failure.error !== 'net::ERR_ABORTED') errors.push(failure)
})
page.on('pageerror', error => errors.push({ stage: checks.at(-1)?.name, error: error.stack || error.message }))
await page.goto(`${origin}/platform/agents`)
const ai = page.getByRole('button', { name: 'AI 创建', exact: true })
await ai.waitFor()
check('manual_and_ai_creation_visible', await page.getByRole('button', { name: '手动创建', exact: true }).isVisible())
await page.locator('.agent-installations').first().waitFor()
check('installation_status_visible_on_agent_cards', await page.locator('.agent-installations').count() > 0)
await page.waitForLoadState('networkidle', { timeout: 15000 })
check('hidden_agent_editor_not_requested', !requests.some(item => /\/AgentEditorModal-[^/]+\.js$/.test(item.path)))
check('hidden_draft_dialog_not_requested', !requests.some(item => /\/AgentDraftDialog-[^/]+\.js$/.test(item.path)))
check('retired_resource_versions_menu_absent', await page.locator('.sidebar').getByText('资源版本', { exact: true }).count() === 0)
await page.screenshot({ path: `${directory}/agent-creation-list-desktop.png`, animations: 'disabled' })
await ai.click()
const dialog = page.locator('.t-dialog:visible')
await dialog.getByPlaceholder('例如:面向课程学员,负责将每日课程框架整理为有前后衔接的讲稿,重点解释概念和案例。').fill('请创建负责课程规划与每日框架衔接的导图助手。')
await dialog.getByRole('button', { name: '生成配置', exact: true }).click()
await dialog.locator('progress').waitFor()
check('ai_generation_progress_visible', await dialog.locator('progress').isVisible())
check('ai_double_submit_prevented', await dialog.getByRole('button', { name: /生成配置/ }).isDisabled())
await page.screenshot({ path: `${directory}/agent-draft-progress-desktop.png`, animations: 'disabled' })
await page.setViewportSize({ width: 390, height: 844 })
check('draft_dialog_fits_mobile', await dialog.evaluate(element => { const rect = element.getBoundingClientRect(); return rect.left >= 0 && rect.right <= innerWidth + 2 }))
await page.screenshot({ path: `${directory}/agent-draft-progress-mobile.png`, animations: 'disabled' })
await releaseDraft()
const name = page.locator('[data-guide="agent-create-name"] input')
await name.waitFor()
await page.waitForFunction(() => document.querySelector('[data-guide="agent-create-name"] input')?.value === '课程框架规划助手')
check('ai_draft_loaded_into_editable_editor', await name.inputValue() === '课程框架规划助手')
check('draft_request_preserves_requirement', draftRequest.requirement === '请创建负责课程规划与每日框架衔接的导图助手。')
await page.screenshot({ path: `${directory}/agent-draft-editor-mobile.png`, animations: 'disabled' })
// Restore normal browser caching after the one mocked AI request.
await context.unroute('**/api/v1/agents/draft')
await page.goto(`${origin}/platform/workbench/resource-versions`)
await ai.waitFor()
check('retired_route_redirects_to_agents', new URL(page.url()).pathname === '/platform/agents')
await page.getByRole('button', { name: '手动创建', exact: true }).click()
await name.waitFor()
check('manual_creation_does_not_reuse_ai_draft', await name.inputValue() !== '课程框架规划助手')
check('no_business_resources_saved', !requests.some(item => ['POST', 'PUT', 'PATCH', 'DELETE'].includes(item.method) && item.path.startsWith('/api/') && !item.path.startsWith('/api/v1/auth/') && item.path !== '/api/v1/agents/draft'))
check('no_vue_runtime_errors', errors.length === 0)
} catch (error) {
failures.push(error.message)
const pages = browser.contexts().flatMap(context => context.pages())
await pages[0]?.screenshot({ path: `${directory}/agent-creation-ui-failure.png` })
} finally {
finalPath = browser.contexts().flatMap(context => context.pages()).map(page => new URL(page.url()).pathname).join(',')
const diagnostics = { path: finalPath, slow_requests: slowRequests, failed_requests: failedRequests,
pending_requests: [...pendingRequests].map(([request, started]) => ({ path: new URL(request.url()).pathname, duration_ms: Date.now() - started })) }
await browser.close()
const report = { started_at: startedAt, checked_at: new Date().toISOString(), origin, checks, errors, failures, diagnostics, ai_output_mocked: true, passed: failures.length === 0 && errors.length === 0 && checks.every(item => item.passed) }
fs.writeFileSync(`${directory}/${reportName}.json`, JSON.stringify(report, null, 2))
console.log(JSON.stringify(report))
if (!report.passed) process.exitCode = 1
}
}
main().catch(error => { console.error(error.message); process.exitCode = 1 })