feat: tighten main agent execution traceability
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled

This commit is contained in:
kris
2026-04-04 06:23:17 +08:00
parent 895e3f3b13
commit 53b1854c21
7 changed files with 72 additions and 7 deletions

View File

@@ -146,6 +146,18 @@
## 2026-04-04
### CI / smoke 护栏加固
- `scripts/check_repo_baseline.sh` 现在会在校验 Web 资产时显式检查 `storyforge-*.js` 是否真的存在,避免后续打包产物变化后只留下一个“看起来在跑、实际漏掉文件”的空洞通过。
- `scripts/smoke_fnos_storyforge_lan.sh` 现在对 `StoryForge` 首页、runtime 配置和 `19181` 兼容入口都做固定字符串断言;其中 `19181` 会校验真实兼容业务台文案,而不是只要抓取成功就算通过。
- 这轮护栏加固保持了现有基线语义不变,只把原来偏宽松的检查收紧成可追踪的真实断言。
### 主 Agent 配置与执行结果继续打通
- `跟踪账号 -> 立即同步` 现在在同步成功后会自动打开对应 `sync_job_id` 的任务详情,不再停留在一条“已同步”的提示上。
- 主 Agent 的执行结果卡、OneLiner 助手消息卡,现在都能直接跳转到 `主配置历史``平台 Agent 配置历史`,把一次执行和当时生效的治理版本真正连起来。
- `execution_card` 里新增了主配置与平台 Agent 配置的 `version_id`,后续继续做更深的版本对比和追溯时不需要再靠标题文本猜版本。
### 平台 Agent 执行回写闭环
- 平台 Agent 配置现在不只是“被主 Agent 带进执行链”,还会在主 Agent 完成态后反向记录最近一次执行信息。

View File

@@ -3598,6 +3598,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
"platform_label": str(plan.get("platform_label") or "").strip() or "待判断",
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
"oneliner_profile_version": {
"version_id": str(oneliner_profile_version.get("id") or "").strip(),
"version_no": int(oneliner_profile_version.get("version_no") or 0),
"title": str(oneliner_profile_version.get("title") or "").strip(),
"summary": str(oneliner_profile_version.get("summary") or "").strip(),
@@ -3607,6 +3608,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
"platform_label": str(platform_agent_profile.get("platform_label") or "").strip(),
"name": str(platform_agent_profile.get("name") or "").strip(),
"assistant_name": str(platform_agent_profile.get("assistant_name") or "").strip(),
"version_id": str(((platform_agent_profile.get("current_version") or {}).get("id") or "").strip()),
"version_no": int(((platform_agent_profile.get("current_version") or {}).get("version_no") or 0)),
"version_title": str(((platform_agent_profile.get("current_version") or {}).get("title") or "").strip()),
"version_summary": str(((platform_agent_profile.get("current_version") or {}).get("summary") or "").strip()),

View File

@@ -40,9 +40,24 @@ for path in sorted(pathlib.Path("n8n/workflows").glob("*.json")):
PY
echo "[5/6] validate web scripts"
for file in web/storyforge-web-v4/assets/app.js web/storyforge-web-v4/assets/storyforge-*.js; do
node --check "$file"
done
validate_node_script() {
for file in "$@"; do
if [ ! -f "$file" ]; then
echo "missing required script file: $file" >&2
exit 1
fi
node --check "$file"
done
}
validate_node_script web/storyforge-web-v4/assets/app.js
set -- web/storyforge-web-v4/assets/storyforge-*.js
if [ "$1" = 'web/storyforge-web-v4/assets/storyforge-*.js' ]; then
echo "missing required script bundle: web/storyforge-web-v4/assets/storyforge-*.js" >&2
exit 1
fi
validate_node_script "$@"
node --check scripts/douyin-browser-capture/control_panel.mjs
echo "[6/6] validate homepage and workbench tests"

View File

@@ -41,12 +41,12 @@ token_file="$tmp_dir/token.txt"
echo "[1/6] check fnOS web"
curl_fetch "$WEB_URL/" >"$index_file"
rg -q "StoryForge" "$index_file"
rg -Fq "StoryForge" "$index_file"
echo "web ok"
echo "[2/6] check runtime config"
curl_fetch "$WEB_URL/assets/storyforge-runtime-config.js" >"$runtime_file"
rg -q "$BACKEND_URL" "$runtime_file"
rg -Fq "$BACKEND_URL" "$runtime_file"
echo "runtime config ok"
echo "[3/6] check collector healthz"
@@ -115,6 +115,10 @@ if not payload:
raise SystemExit("empty cutvideo bootstrap payload")
print("cutvideo bootstrap ok")
' "$bootstrap_file"
if ! rg -Fq "数字人网页业务台" "$compat_file" && ! rg -Fq "BUSINESS CONSOLE" "$compat_file"; then
echo "compat page does not look like the expected business console" >&2
exit 1
fi
echo "compat ok"
echo "fnOS lan smoke passed:"

View File

@@ -905,6 +905,12 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertEqual(detail_response.status_code, 200, detail_response.text)
detail_payload = detail_response.json()
self.assertEqual(detail_payload["run_status"], "done")
self.assertTrue(
(((detail_payload.get("result") or {}).get("execution_card") or {}).get("oneliner_profile_version") or {}).get("version_id")
)
self.assertTrue(
(((detail_payload.get("result") or {}).get("execution_card") or {}).get("platform_agent_profile") or {}).get("version_id")
)
self.assertEqual(
(((detail_payload.get("result") or {}).get("execution_card") or {}).get("platform_agent_profile") or {}).get("version_no"),
rollback_profile_payload["current_version"]["version_no"],

View File

@@ -913,6 +913,9 @@ function ensureActionUi() {
function renderActionFields(fields) {
return fields.map((field) => {
if (field.hidden) {
return "";
}
const common = `data-action-field="${escapeHtml(field.name)}"`;
if (field.type === "html") {
return `
@@ -1360,6 +1363,8 @@ function renderOneLinerMessagesHtml() {
${executionCard.platform_agent_name ? `<span class="tag">${escapeHtml(executionCard.platform_agent_name)}</span>` : ""}
${executionCard.assistant_name ? `<span class="tag green">${escapeHtml(executionCard.assistant_name)}</span>` : ""}
${profileVersion.version_no ? `<span class="tag">配置 v${escapeHtml(formatNumber(profileVersion.version_no || 0))}</span>` : ""}
${profileVersion.version_no ? `<span class="tag clickable-tag" data-action="open-oneliner-profile-history">看主配置历史</span>` : ""}
${executionCard.platform && executionCard.platform_agent_profile?.version_no ? `<span class="tag clickable-tag" data-action="open-platform-agent-profile-history" data-platform="${escapeHtml(executionCard.platform)}">看平台配置历史</span>` : ""}
${executionCard.readiness_label ? `<span class="tag ${executionCard.readiness_score >= 75 ? "green" : executionCard.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(executionCard.readiness_label)} ${escapeHtml(formatNumber(executionCard.readiness_score || 0))}</span>` : ""}
${executionCard.primary_action?.key ? `<span class="tag clickable-tag" data-action="${escapeHtml(executionCard.primary_action.key)}">${escapeHtml(executionCard.primary_action.label || "执行下一步")}</span>` : ""}
</div>
@@ -2020,6 +2025,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
${platformAgentProfile.name ? `<span class="tag">${escapeHtml(platformAgentProfile.name)}</span>` : ""}
${platformAgentProfile.assistant_name ? `<span class="tag green">${escapeHtml(platformAgentProfile.assistant_name)}</span>` : ""}
${platformAgentProfile.version_no ? `<span class="tag">${escapeHtml(platformLabel(platformAgentProfile.platform || payload.platform || ""))} Agent v${escapeHtml(formatNumber(platformAgentProfile.version_no || 0))}</span>` : ""}
${platformAgentProfile.platform && platformAgentProfile.version_no ? `<span class="tag clickable-tag" data-action="open-platform-agent-profile-history" data-platform="${escapeHtml(platformAgentProfile.platform)}">看平台配置历史</span>` : ""}
${platformAgentProfile.readiness_label ? `<span class="tag ${platformAgentProfile.readiness_score >= 75 ? "green" : platformAgentProfile.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(platformAgentProfile.readiness_label)} ${escapeHtml(formatNumber(platformAgentProfile.readiness_score || 0))}</span>` : ""}
</div>
</div>
@@ -2728,6 +2734,9 @@ async function refreshTrackedAccountAction(trackedAccountId) {
payload
);
await bootstrap();
if (payload.sync_job_id) {
openJobDetailAction(payload.sync_job_id);
}
} finally {
setBusy(false, "");
}
@@ -3209,6 +3218,7 @@ function openDashboardProjectSwitcher() {
}
const selectedProject = getSelectedProject();
const projects = safeArray(appState.dashboard?.projects);
const isMobileViewport = typeof window !== "undefined" && window.matchMedia?.("(max-width: 760px)")?.matches;
const projectCards = projects.map((project) => {
const stats = getProjectStats(project.id);
const reviewCount = getProjectReviews(project.id).length;
@@ -3259,11 +3269,10 @@ function openDashboardProjectSwitcher() {
</div>
`
},
{ name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options }
{ name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options, hidden: isMobileViewport }
],
onOpen: ({ fields, submit }) => {
const select = fields.querySelector('[data-action-field="projectId"]');
const isMobileViewport = typeof window !== "undefined" && window.matchMedia?.("(max-width: 760px)")?.matches;
if (submit && isMobileViewport) {
submit.hidden = true;
}

View File

@@ -138,12 +138,14 @@ test("mobile project sheets support direct project picking and zoom-safe form co
const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()");
assert.match(APP, /async function applySelectedProject\(projectId = ""\)/);
assert.match(projectSwitcher, /data-project-choice=/);
assert.match(projectSwitcher, /hidden:\s*isMobileViewport/);
assert.match(projectSwitcher, /onOpen:\s*\(/);
assert.match(projectSwitcher, /select\.value = nextProjectId/);
assert.match(projectSwitcher, /window\.matchMedia\?\.\("\(max-width: 760px\)"\)\?\.matches/);
assert.match(projectSwitcher, /submit\.hidden = true/);
assert.match(projectSwitcher, /closeActionModal\(\);/);
assert.match(projectSwitcher, /await applySelectedProject\(nextProjectId\);/);
assert.match(APP, /if \(field\.hidden\) \{\s*return "";/);
assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/);
assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/);
assert.match(createProject, /onOpen:\s*\(/);
@@ -832,6 +834,21 @@ test("key workbench screens expose contextual handoff-to-main-agent actions", ()
assert.match(review, /sourceScreen: "review"/);
});
test("tracked-account refresh opens the created sync task when the backend returns one", () => {
const refresh = extractBetween(APP, "async function refreshTrackedAccountAction(trackedAccountId)", "function getSelectedProject()");
assert.match(refresh, /sync_job_id/);
assert.match(refresh, /openJobDetailAction\(payload\.sync_job_id\)/);
});
test("main agent execution cards can jump to oneliner and platform profile history", () => {
const messages = extractBetween(APP, "function renderOneLinerMessagesHtml()", "function renderAutoConnectingScreen(screenTitle, nextStepText)");
assert.match(messages, /data-action="open-oneliner-profile-history"/);
assert.match(messages, /data-action="open-platform-agent-profile-history"/);
assert.match(APP, /function renderOneLinerExecutionPayloadHtml\(payload\)/);
assert.match(APP, /data-action="open-oneliner-profile-history"/);
assert.match(APP, /data-action="open-platform-agent-profile-history"/);
});
test("oneliner runtime shows grouped run health summary above the current run card", () => {
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
assert.match(runtime, /近期运行概况/);