feat: tighten main agent execution traceability
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -146,6 +146,18 @@
|
|||||||
|
|
||||||
## 2026-04-04
|
## 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 带进执行链”,还会在主 Agent 完成态后反向记录最近一次执行信息。
|
- 平台 Agent 配置现在不只是“被主 Agent 带进执行链”,还会在主 Agent 完成态后反向记录最近一次执行信息。
|
||||||
|
|||||||
@@ -3598,6 +3598,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
|||||||
"platform_label": str(plan.get("platform_label") or "").strip() or "待判断",
|
"platform_label": str(plan.get("platform_label") or "").strip() or "待判断",
|
||||||
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
|
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
|
||||||
"oneliner_profile_version": {
|
"oneliner_profile_version": {
|
||||||
|
"version_id": str(oneliner_profile_version.get("id") or "").strip(),
|
||||||
"version_no": int(oneliner_profile_version.get("version_no") or 0),
|
"version_no": int(oneliner_profile_version.get("version_no") or 0),
|
||||||
"title": str(oneliner_profile_version.get("title") or "").strip(),
|
"title": str(oneliner_profile_version.get("title") or "").strip(),
|
||||||
"summary": str(oneliner_profile_version.get("summary") 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(),
|
"platform_label": str(platform_agent_profile.get("platform_label") or "").strip(),
|
||||||
"name": str(platform_agent_profile.get("name") or "").strip(),
|
"name": str(platform_agent_profile.get("name") or "").strip(),
|
||||||
"assistant_name": str(platform_agent_profile.get("assistant_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_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_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()),
|
"version_summary": str(((platform_agent_profile.get("current_version") or {}).get("summary") or "").strip()),
|
||||||
|
|||||||
@@ -40,9 +40,24 @@ for path in sorted(pathlib.Path("n8n/workflows").glob("*.json")):
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
echo "[5/6] validate web scripts"
|
echo "[5/6] validate web scripts"
|
||||||
for file in web/storyforge-web-v4/assets/app.js web/storyforge-web-v4/assets/storyforge-*.js; do
|
validate_node_script() {
|
||||||
node --check "$file"
|
for file in "$@"; do
|
||||||
done
|
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
|
node --check scripts/douyin-browser-capture/control_panel.mjs
|
||||||
|
|
||||||
echo "[6/6] validate homepage and workbench tests"
|
echo "[6/6] validate homepage and workbench tests"
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ token_file="$tmp_dir/token.txt"
|
|||||||
|
|
||||||
echo "[1/6] check fnOS web"
|
echo "[1/6] check fnOS web"
|
||||||
curl_fetch "$WEB_URL/" >"$index_file"
|
curl_fetch "$WEB_URL/" >"$index_file"
|
||||||
rg -q "StoryForge" "$index_file"
|
rg -Fq "StoryForge" "$index_file"
|
||||||
echo "web ok"
|
echo "web ok"
|
||||||
|
|
||||||
echo "[2/6] check runtime config"
|
echo "[2/6] check runtime config"
|
||||||
curl_fetch "$WEB_URL/assets/storyforge-runtime-config.js" >"$runtime_file"
|
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 "runtime config ok"
|
||||||
|
|
||||||
echo "[3/6] check collector healthz"
|
echo "[3/6] check collector healthz"
|
||||||
@@ -115,6 +115,10 @@ if not payload:
|
|||||||
raise SystemExit("empty cutvideo bootstrap payload")
|
raise SystemExit("empty cutvideo bootstrap payload")
|
||||||
print("cutvideo bootstrap ok")
|
print("cutvideo bootstrap ok")
|
||||||
' "$bootstrap_file"
|
' "$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 "compat ok"
|
||||||
|
|
||||||
echo "fnOS lan smoke passed:"
|
echo "fnOS lan smoke passed:"
|
||||||
|
|||||||
@@ -905,6 +905,12 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
|||||||
self.assertEqual(detail_response.status_code, 200, detail_response.text)
|
self.assertEqual(detail_response.status_code, 200, detail_response.text)
|
||||||
detail_payload = detail_response.json()
|
detail_payload = detail_response.json()
|
||||||
self.assertEqual(detail_payload["run_status"], "done")
|
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(
|
self.assertEqual(
|
||||||
(((detail_payload.get("result") or {}).get("execution_card") or {}).get("platform_agent_profile") or {}).get("version_no"),
|
(((detail_payload.get("result") or {}).get("execution_card") or {}).get("platform_agent_profile") or {}).get("version_no"),
|
||||||
rollback_profile_payload["current_version"]["version_no"],
|
rollback_profile_payload["current_version"]["version_no"],
|
||||||
|
|||||||
@@ -913,6 +913,9 @@ function ensureActionUi() {
|
|||||||
|
|
||||||
function renderActionFields(fields) {
|
function renderActionFields(fields) {
|
||||||
return fields.map((field) => {
|
return fields.map((field) => {
|
||||||
|
if (field.hidden) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
const common = `data-action-field="${escapeHtml(field.name)}"`;
|
const common = `data-action-field="${escapeHtml(field.name)}"`;
|
||||||
if (field.type === "html") {
|
if (field.type === "html") {
|
||||||
return `
|
return `
|
||||||
@@ -1360,6 +1363,8 @@ function renderOneLinerMessagesHtml() {
|
|||||||
${executionCard.platform_agent_name ? `<span class="tag">${escapeHtml(executionCard.platform_agent_name)}</span>` : ""}
|
${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>` : ""}
|
${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">配置 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.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>` : ""}
|
${executionCard.primary_action?.key ? `<span class="tag clickable-tag" data-action="${escapeHtml(executionCard.primary_action.key)}">${escapeHtml(executionCard.primary_action.label || "执行下一步")}</span>` : ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -2020,6 +2025,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
|
|||||||
${platformAgentProfile.name ? `<span class="tag">${escapeHtml(platformAgentProfile.name)}</span>` : ""}
|
${platformAgentProfile.name ? `<span class="tag">${escapeHtml(platformAgentProfile.name)}</span>` : ""}
|
||||||
${platformAgentProfile.assistant_name ? `<span class="tag green">${escapeHtml(platformAgentProfile.assistant_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.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>` : ""}
|
${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>
|
||||||
</div>
|
</div>
|
||||||
@@ -2728,6 +2734,9 @@ async function refreshTrackedAccountAction(trackedAccountId) {
|
|||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
await bootstrap();
|
await bootstrap();
|
||||||
|
if (payload.sync_job_id) {
|
||||||
|
openJobDetailAction(payload.sync_job_id);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false, "");
|
setBusy(false, "");
|
||||||
}
|
}
|
||||||
@@ -3209,6 +3218,7 @@ function openDashboardProjectSwitcher() {
|
|||||||
}
|
}
|
||||||
const selectedProject = getSelectedProject();
|
const selectedProject = getSelectedProject();
|
||||||
const projects = safeArray(appState.dashboard?.projects);
|
const projects = safeArray(appState.dashboard?.projects);
|
||||||
|
const isMobileViewport = typeof window !== "undefined" && window.matchMedia?.("(max-width: 760px)")?.matches;
|
||||||
const projectCards = projects.map((project) => {
|
const projectCards = projects.map((project) => {
|
||||||
const stats = getProjectStats(project.id);
|
const stats = getProjectStats(project.id);
|
||||||
const reviewCount = getProjectReviews(project.id).length;
|
const reviewCount = getProjectReviews(project.id).length;
|
||||||
@@ -3259,11 +3269,10 @@ function openDashboardProjectSwitcher() {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
{ name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options }
|
{ name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options, hidden: isMobileViewport }
|
||||||
],
|
],
|
||||||
onOpen: ({ fields, submit }) => {
|
onOpen: ({ fields, submit }) => {
|
||||||
const select = fields.querySelector('[data-action-field="projectId"]');
|
const select = fields.querySelector('[data-action-field="projectId"]');
|
||||||
const isMobileViewport = typeof window !== "undefined" && window.matchMedia?.("(max-width: 760px)")?.matches;
|
|
||||||
if (submit && isMobileViewport) {
|
if (submit && isMobileViewport) {
|
||||||
submit.hidden = true;
|
submit.hidden = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()");
|
const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()");
|
||||||
assert.match(APP, /async function applySelectedProject\(projectId = ""\)/);
|
assert.match(APP, /async function applySelectedProject\(projectId = ""\)/);
|
||||||
assert.match(projectSwitcher, /data-project-choice=/);
|
assert.match(projectSwitcher, /data-project-choice=/);
|
||||||
|
assert.match(projectSwitcher, /hidden:\s*isMobileViewport/);
|
||||||
assert.match(projectSwitcher, /onOpen:\s*\(/);
|
assert.match(projectSwitcher, /onOpen:\s*\(/);
|
||||||
assert.match(projectSwitcher, /select\.value = nextProjectId/);
|
assert.match(projectSwitcher, /select\.value = nextProjectId/);
|
||||||
assert.match(projectSwitcher, /window\.matchMedia\?\.\("\(max-width: 760px\)"\)\?\.matches/);
|
assert.match(projectSwitcher, /window\.matchMedia\?\.\("\(max-width: 760px\)"\)\?\.matches/);
|
||||||
assert.match(projectSwitcher, /submit\.hidden = true/);
|
assert.match(projectSwitcher, /submit\.hidden = true/);
|
||||||
assert.match(projectSwitcher, /closeActionModal\(\);/);
|
assert.match(projectSwitcher, /closeActionModal\(\);/);
|
||||||
assert.match(projectSwitcher, /await applySelectedProject\(nextProjectId\);/);
|
assert.match(projectSwitcher, /await applySelectedProject\(nextProjectId\);/);
|
||||||
|
assert.match(APP, /if \(field\.hidden\) \{\s*return "";/);
|
||||||
assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/);
|
assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/);
|
||||||
assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/);
|
assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/);
|
||||||
assert.match(createProject, /onOpen:\s*\(/);
|
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"/);
|
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", () => {
|
test("oneliner runtime shows grouped run health summary above the current run card", () => {
|
||||||
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
|
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
|
||||||
assert.match(runtime, /近期运行概况/);
|
assert.match(runtime, /近期运行概况/);
|
||||||
|
|||||||
Reference in New Issue
Block a user