feat: add generated scene skill platform hardening

This commit is contained in:
木炎
2026-04-21 23:19:06 +08:00
parent 118fc77935
commit 956f0c2b68
439 changed files with 61974 additions and 3645 deletions

View File

@@ -7,6 +7,20 @@ function resolveProjectRoot() {
return path.resolve(envRoot);
}
// Search for Cargo.toml to find the actual project root
const candidates = [
path.resolve(__dirname, "..", ".."), // claw-new (has Cargo.toml)
path.resolve(__dirname, "..", "..", "..", "claw-new"), // sgClaw/claw-new
path.resolve(__dirname, "..", "..", ".."), // sgClaw root
];
for (const p of candidates) {
if (fs.existsSync(path.join(p, "Cargo.toml"))) {
return p;
}
}
// Fallback: directory containing sgclaw_config.json
const configPath = resolveConfigPath();
if (configPath && fs.existsSync(configPath)) {
return path.dirname(configPath);

View File

@@ -489,6 +489,9 @@ function collectDeterministicSignals(files, indexHtml) {
const secondaryRequestMethods = new Set();
const pageTitleKeywords = new Set();
const staticParams = {};
const g1eJoinKeys = new Set();
const g1eAggregateRules = new Set();
const g1eOutputColumns = [];
for (const file of files) {
const content = file.content;
@@ -500,6 +503,25 @@ function collectDeterministicSignals(files, indexHtml) {
}
}
for (const match of content.matchAll(/\b(?:data|item|row)\.(wkOrderNo|countyCodeName|orgNo|orgCode)\b/g)) {
g1eJoinKeys.add(match[1]);
}
for (const match of content.matchAll(/\b(com|batchCom)\s*\([^)]*?["']([A-Za-z_][A-Za-z0-9_]*)["']\s*,\s*["']([A-Za-z_][A-Za-z0-9_]*)["']/g)) {
g1eAggregateRules.add(`${match[1]}:${match[2]},${match[3]}`);
}
const titleListBlock = content.match(/const\s+titleList\s*=\s*\[([\s\S]*?)\]\s*[;\n]/i);
if (titleListBlock) {
for (const triple of titleListBlock[1].matchAll(/\[\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*,\s*["']([^"'`]+)["']\s*,\s*["']([^"'`]*)["']\s*\]/g)) {
const field = triple[1];
const top = String(triple[2] || "").trim();
const leaf = String(triple[3] || "").trim();
if (!field || g1eOutputColumns.some((item) => item[0] === field)) continue;
g1eOutputColumns.push([field, leaf ? `${top}-${leaf}` : top]);
}
}
for (const match of content.matchAll(/\b(type|method)\s*:\s*['"`](GET|POST|PUT|DELETE|PATCH)['"`]/gi)) {
methods.set(match[2].toUpperCase(), true);
}
@@ -557,8 +579,10 @@ function collectDeterministicSignals(files, indexHtml) {
}
const bootstrapCandidates = collectBootstrapCandidates(files, indexHtml, Array.from(urls.values()));
const allEndpoints = Array.from(urls.values());
const g1eRequestRoles = deriveG1eRequestRoles(allEndpoints);
return {
endpoints: Array.from(urls.values()),
endpoints: allEndpoints,
methods: Array.from(methods.keys()),
responsePaths: Array.from(responsePaths),
branchFields: Array.from(branchFields),
@@ -571,9 +595,39 @@ function collectDeterministicSignals(files, indexHtml) {
pageTitleKeywords: Array.from(pageTitleKeywords).slice(0, 10),
staticParams,
bootstrapCandidates,
g1eMainRequest: g1eRequestRoles.mainRequest,
g1eEnrichmentRequests: g1eRequestRoles.enrichmentRequests,
g1eJoinKeys: Array.from(g1eJoinKeys),
g1eOutputColumns: g1eOutputColumns.slice(0, 24),
g1eAggregateRules: Array.from(g1eAggregateRules).slice(0, 12),
};
}
function deriveG1eRequestRoles(endpoints) {
const mainRequest = endpoints.find((endpoint) =>
/getwkorderall|all|list/i.test(endpoint?.name || "") && !containsRowBinding(endpoint?.requestTemplate)
) || null;
const enrichmentRequests = (endpoints || [])
.filter((endpoint) => {
if (mainRequest && endpoint.url === mainRequest.url) return false;
return (
containsRowBinding(endpoint?.requestTemplate) ||
/query|info|acpt/i.test(endpoint?.name || "") ||
/query|info|acpt/i.test(endpoint?.url || "")
);
})
.slice(0, 6);
return { mainRequest, enrichmentRequests };
}
function containsRowBinding(value) {
if (!value) return false;
if (typeof value === "string") return value.includes("${row.");
if (Array.isArray(value)) return value.some(containsRowBinding);
if (typeof value === "object") return Object.values(value).some(containsRowBinding);
return false;
}
function extractEndpoints(content) {
const endpoints = [];
const seen = new Set();
@@ -753,6 +807,9 @@ function buildDeterministicSceneIr(context, sourceDir) {
const normalizeRules = buildNormalizeRules(signals);
const params = buildParams(signals, workflowArchetype);
const confidence = scoreConfidence(signals, workflowArchetype);
const mainRequest = buildG1eMainRequest(signals);
const enrichmentRequests = buildG1eEnrichmentRequests(signals);
const mergePlan = buildG1eMergePlan(signals);
const readiness = buildReadiness({
sceneIdDiagnostics,
workflowArchetype,
@@ -760,6 +817,9 @@ function buildDeterministicSceneIr(context, sourceDir) {
apiEndpoints: signals.endpoints || [],
params,
workflowSteps,
mainRequest,
enrichmentRequests,
mergePlan,
confidence,
});
@@ -776,8 +836,11 @@ function buildDeterministicSceneIr(context, sourceDir) {
modeSwitchField: signals.branchFields?.find((field) => /mode|period/i.test(field)) || null,
workflowSteps,
workflowEvidence,
requestTemplate: {},
responsePath,
mainRequest,
enrichmentRequests,
mergePlan,
requestTemplate: mainRequest?.requestTemplate || {},
responsePath: mainRequest?.responsePath || responsePath,
normalizeRules,
artifactContract: {
type: "report-artifact",
@@ -796,13 +859,54 @@ function buildDeterministicSceneIr(context, sourceDir) {
readiness,
apiEndpoints: signals.endpoints || [],
staticParams: signals.staticParams || {},
columnDefs: [],
columnDefs: mergePlan?.outputColumns || [],
confidence,
uncertainties: buildUncertainties(signals, workflowArchetype),
deterministicSignals: signals,
};
}
function buildG1eMainRequest(signals) {
const endpoint = signals.g1eMainRequest || null;
if (!endpoint) return null;
return {
apiEndpoint: endpoint,
requestTemplate: endpoint.requestTemplate || {},
responsePath: signals.responsePaths?.[0] || "",
columnDefs: signals.g1eOutputColumns || [],
};
}
function buildG1eEnrichmentRequests(signals) {
return (signals.g1eEnrichmentRequests || []).map((endpoint) => ({
name: endpoint.name,
apiEndpoint: endpoint,
paramBindings: endpoint.requestTemplate || {},
responsePath: signals.responsePaths?.[0] || "",
consumedFields: (signals.g1eAggregateRules || [])
.flatMap((rule) => String(rule).split(":")[1]?.split(",") || [])
.map((item) => item.trim())
.filter(Boolean),
}));
}
function buildG1eMergePlan(signals) {
if (!(signals.g1eJoinKeys || []).length && !(signals.g1eOutputColumns || []).length && !(signals.g1eAggregateRules || []).length) {
return null;
}
return {
joinKeys: signals.g1eJoinKeys || [],
fieldMappings: (signals.g1eOutputColumns || []).map(([field]) => ({
outputField: field,
sourceType: (signals.g1eJoinKeys || []).includes(field) ? "main" : "aggregate",
sourceField: field,
requestName: null,
})),
aggregateRules: signals.g1eAggregateRules || [],
outputColumns: signals.g1eOutputColumns || [],
};
}
function deriveSceneIdDiagnostics({ sourceDir, sceneName, signals }) {
const baseName = path.basename(sourceDir || "");
const candidates = [];
@@ -951,6 +1055,12 @@ function classifyWorkflowArchetype(signals) {
(signals.secondaryRequestMethods || []).length > 0 || businessEndpoints.length >= 2;
const hasPostProcess =
(signals.filterExpressions || []).length > 0 || (signals.exportMethods || []).length > 0;
if (signals.g1eMainRequest && (signals.g1eEnrichmentRequests || []).length > 0) {
const hasG1eMergeSignal = (signals.g1eJoinKeys || []).length > 0 || (signals.g1eOutputColumns || []).length > 0;
if (hasG1eMergeSignal) {
return "single_request_enrichment";
}
}
if (hasPagination && hasSecondaryRequest && hasPostProcess) {
return "paginated_enrichment";
}
@@ -970,61 +1080,226 @@ function classifyWorkflowArchetype(signals) {
return "single_request_table";
}
function createEvidenceItem({
kind = "deterministic",
evidenceType = "signal",
layer = "business",
subject = "",
summary = "",
source = "runner",
confidence = 0.7,
payload = null,
}) {
return {
kind,
evidenceType,
layer,
subject: subject || evidenceType,
summary,
source,
confidence,
payload: payload && typeof payload === "object" ? payload : null,
};
}
function buildEvidence(signals, workflowArchetype) {
const evidence = [];
const businessEndpoints = getBusinessEndpoints(signals);
const localDependencies = (signals.endpoints || []).filter((endpoint) => endpoint.role === "local_helper");
const exportServices = (signals.endpoints || []).filter((endpoint) => endpoint.role === "export_service");
const bootstrapCandidate = (signals.bootstrapCandidates || []).find((candidate) => candidate.validForBootstrap);
if (businessEndpoints.length > 0) {
evidence.push({
kind: "deterministic",
summary: `Detected ${businessEndpoints.length} business API endpoint(s).`,
source: "runner",
confidence: 0.9,
});
if (bootstrapCandidate) {
evidence.push(
createEvidenceItem({
evidenceType: "bootstrap_candidate",
layer: "business",
subject: bootstrapCandidate.expectedDomain || bootstrapCandidate.targetUrl || "bootstrap",
summary: `Bootstrap candidate resolved to ${bootstrapCandidate.expectedDomain || bootstrapCandidate.targetUrl}.`,
confidence: 0.92,
payload: bootstrapCandidate,
})
);
}
if ((signals.branchFields || []).length > 0) {
evidence.push({
kind: "deterministic",
summary: `Branch fields: ${signals.branchFields.join(", ")}`,
source: "runner",
confidence: 0.86,
});
for (const endpoint of businessEndpoints) {
evidence.push(
createEvidenceItem({
evidenceType: "endpoint_candidate",
layer: "business",
subject: endpoint.name || endpoint.url,
summary: `Business endpoint ${endpoint.name || endpoint.url} detected.`,
confidence: 0.9,
payload: endpoint,
})
);
}
if ((signals.paginationVars || []).length > 0) {
evidence.push({
kind: "deterministic",
summary: `Pagination vars: ${signals.paginationVars.join(", ")}`,
source: "runner",
confidence: 0.84,
});
if ((signals.branchFields || []).length > 0 || (signals.modeValues || []).length > 0) {
evidence.push(
createEvidenceItem({
evidenceType: "mode_candidate",
layer: "business",
subject: workflowArchetype,
summary: `Mode signals: fields=${(signals.branchFields || []).join(", ") || "none"} values=${(signals.modeValues || []).join(", ") || "none"}`,
confidence: 0.86,
payload: {
branchFields: signals.branchFields || [],
modeValues: signals.modeValues || [],
},
})
);
}
if ((signals.secondaryRequestMethods || []).length > 0) {
evidence.push({
kind: "deterministic",
summary: `Secondary request methods: ${signals.secondaryRequestMethods.join(", ")}`,
source: "runner",
confidence: 0.82,
});
const requestSignals = uniqueStringValues([
...(signals.entryMethods || []),
...Object.keys(signals.staticParams || {}),
]);
if (requestSignals.length > 0) {
evidence.push(
createEvidenceItem({
evidenceType: "request_template_candidate",
layer: "business",
subject: requestSignals[0],
summary: `Request-side signals detected: ${requestSignals.join(", ")}`,
confidence: 0.8,
payload: {
entryMethods: signals.entryMethods || [],
staticParams: signals.staticParams || {},
},
})
);
}
if ((signals.exportMethods || []).length > 0) {
evidence.push({
kind: "deterministic",
summary: `Export methods: ${signals.exportMethods.join(", ")}`,
source: "runner",
confidence: 0.78,
});
if (signals.g1eMainRequest) {
evidence.push(
createEvidenceItem({
evidenceType: "main_request_candidate",
layer: "business",
subject: signals.g1eMainRequest.name,
summary: `G1-E main request candidate resolved to ${signals.g1eMainRequest.name}.`,
confidence: 0.86,
payload: signals.g1eMainRequest,
})
);
}
evidence.push({
kind: "classification",
summary: `Workflow archetype classified as ${workflowArchetype}.`,
source: "runner",
confidence: 0.72,
});
for (const endpoint of signals.g1eEnrichmentRequests || []) {
evidence.push(
createEvidenceItem({
evidenceType: "enrichment_request_candidate",
layer: "business",
subject: endpoint.name,
summary: `G1-E enrichment request candidate resolved to ${endpoint.name}.`,
confidence: 0.84,
payload: endpoint,
})
);
}
if ((signals.g1eJoinKeys || []).length || (signals.g1eOutputColumns || []).length || (signals.g1eAggregateRules || []).length) {
evidence.push(
createEvidenceItem({
evidenceType: "merge_plan_candidate",
layer: "workflow",
subject: "g1e_merge_plan",
summary: "G1-E merge plan candidate detected.",
confidence: 0.83,
payload: {
joinKeys: signals.g1eJoinKeys || [],
outputColumns: signals.g1eOutputColumns || [],
aggregateRules: signals.g1eAggregateRules || [],
},
})
);
}
if ((signals.responsePaths || []).length > 0) {
evidence.push(
createEvidenceItem({
evidenceType: "response_path_candidate",
layer: "business",
subject: signals.responsePaths[0],
summary: `Response paths detected: ${signals.responsePaths.join(", ")}`,
confidence: 0.84,
payload: { responsePaths: signals.responsePaths || [] },
})
);
}
if ((signals.filterExpressions || []).length > 0 || (signals.exportMethods || []).length > 0) {
evidence.push(
createEvidenceItem({
evidenceType: "normalize_rules_candidate",
layer: "business",
subject: "normalize_rules",
summary: `Post-process signals detected: filters=${(signals.filterExpressions || []).length}, exports=${(signals.exportMethods || []).length}`,
confidence: 0.76,
payload: {
filterExpressions: signals.filterExpressions || [],
exportMethods: signals.exportMethods || [],
},
})
);
}
if ((signals.paginationVars || []).length > 0 || (signals.secondaryRequestMethods || []).length > 0) {
evidence.push(
createEvidenceItem({
evidenceType: "workflow_candidate",
layer: "workflow",
subject: workflowArchetype,
summary: `Workflow signals: pagination=${(signals.paginationVars || []).join(", ") || "none"} secondary=${(signals.secondaryRequestMethods || []).join(", ") || "none"}`,
confidence: 0.82,
payload: {
paginationVars: signals.paginationVars || [],
secondaryRequestMethods: signals.secondaryRequestMethods || [],
exportMethods: signals.exportMethods || [],
},
})
);
}
for (const endpoint of [...localDependencies, ...exportServices]) {
evidence.push(
createEvidenceItem({
evidenceType: "localhost_dependency_candidate",
layer: "host_runtime",
subject: endpoint.name || endpoint.url,
summary: `Host runtime dependency detected: ${endpoint.url}`,
confidence: 0.88,
payload: endpoint,
})
);
}
if ((signals.exportMethods || []).length > 0 || exportServices.length > 0) {
evidence.push(
createEvidenceItem({
evidenceType: "export_candidate",
layer: "output",
subject: signals.exportMethods?.[0] || exportServices[0]?.name || "export",
summary: `Export signals detected: ${(signals.exportMethods || []).join(", ") || exportServices.map((item) => item.url).join(", ")}`,
confidence: 0.78,
payload: {
exportMethods: signals.exportMethods || [],
exportEndpoints: exportServices,
},
})
);
}
evidence.push(
createEvidenceItem({
kind: "classification",
evidenceType: "workflow_candidate",
layer: "classification",
subject: workflowArchetype,
summary: `Workflow archetype classified as ${workflowArchetype}.`,
confidence: 0.72,
payload: { workflowArchetype },
})
);
return evidence;
}
@@ -1086,6 +1361,27 @@ function buildWorkflowSteps(signals, workflowArchetype) {
const primaryEndpoint = businessEndpoints[0]?.name || null;
const secondaryEndpoint = businessEndpoints[1]?.name || null;
if (workflowArchetype === "single_request_enrichment") {
steps.push({
type: "request",
entry: signals.entryMethods?.[0] || null,
endpoint: signals.g1eMainRequest?.name || primaryEndpoint,
description: "Query the main list for G1-E workflow.",
});
for (const endpoint of signals.g1eEnrichmentRequests || []) {
steps.push({
type: "enrichment_request",
endpoint: endpoint.name,
description: "Fetch lightweight enrichment payload.",
});
}
steps.push({
type: "transform",
description: "Merge enrichment payloads into aggregate output.",
});
return steps;
}
if (workflowArchetype === "multi_mode_request") {
steps.push({
type: "request",
@@ -1238,6 +1534,9 @@ function buildUncertainties(signals, workflowArchetype) {
if (workflowArchetype === "paginated_enrichment" && !(signals.filterExpressions || []).length && !(signals.exportMethods || []).length) {
issues.push("Paginated enrichment is missing post-process evidence.");
}
if (workflowArchetype === "single_request_enrichment" && !(signals.g1eOutputColumns || []).length) {
issues.push("G1-E output columns are still weakly inferred.");
}
return issues;
}
@@ -1255,10 +1554,17 @@ function scoreConfidence(signals, workflowArchetype) {
) {
score += 0.14;
}
if (
workflowArchetype === "single_request_enrichment" &&
signals.g1eMainRequest &&
(signals.g1eEnrichmentRequests || []).length > 0
) {
score += 0.14;
}
return Math.min(0.95, Number(score.toFixed(2)));
}
function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiEndpoints, params, workflowSteps, confidence }) {
function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiEndpoints, params, workflowSteps, mainRequest, enrichmentRequests, mergePlan, confidence }) {
const risks = [];
const missingPieces = [];
const notes = [];
@@ -1288,6 +1594,37 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
risks.push("Workflow steps are incomplete.");
}
const requestContract = evaluateRequestContract({
workflowArchetype,
apiEndpoints,
params,
workflowSteps,
});
if (!requestContract.passed) {
missingPieces.push(requestContract.reason || "request_contract");
risks.push(requestContract.message);
}
const responseContract = evaluateResponseContract({
workflowArchetype,
workflowSteps,
apiEndpoints,
});
if (!responseContract.passed) {
missingPieces.push(responseContract.reason || "response_contract");
risks.push(responseContract.message);
}
const workflowContract = evaluateWorkflowContract({
workflowArchetype,
workflowSteps,
businessApiEndpoints,
});
if (!workflowContract.passed) {
missingPieces.push(workflowContract.reason || "workflow_contract");
risks.push(workflowContract.message);
}
if (workflowArchetype === "paginated_enrichment") {
const hasPaginate = workflowSteps.some((step) => step.type === "paginate");
const hasSecondary = workflowSteps.some((step) => step.type === "secondary_request");
@@ -1306,6 +1643,21 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
}
}
if (workflowArchetype === "single_request_enrichment") {
if (!mainRequest) {
missingPieces.push("main_request");
risks.push("G1-E workflow is missing a resolved main request.");
}
if (!(enrichmentRequests || []).length) {
missingPieces.push("enrichment_requests");
risks.push("G1-E workflow is missing enrichment request contracts.");
}
if (!mergePlan) {
missingPieces.push("merge_plan");
risks.push("G1-E workflow is missing merge plan evidence.");
}
}
if (workflowArchetype === "multi_mode_request" && !params.some((param) => param.name === "period")) {
risks.push("Mode-aware workflow is missing a resolved period parameter.");
}
@@ -1340,15 +1692,25 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
passed: Boolean(bootstrap.targetUrl || bootstrap.expectedDomain),
reason: bootstrap.targetUrl || bootstrap.expectedDomain ? null : "bootstrap_target",
},
{
name: "request_contract_complete",
passed: requestContract.passed,
reason: requestContract.passed ? null : requestContract.reason,
},
{
name: "response_contract_complete",
passed: responseContract.passed,
reason: responseContract.passed ? null : responseContract.reason,
},
{
name: "workflow_contract_complete",
passed: workflowContract.passed,
reason: workflowContract.passed ? null : workflowContract.reason,
},
{
name: "workflow_complete_for_archetype",
passed: !missingPieces.some((item) =>
["workflow_steps", "paginate_step", "secondary_request", "post_process"].includes(item)
),
reason:
missingPieces.find((item) =>
["workflow_steps", "paginate_step", "secondary_request", "post_process"].includes(item)
) || null,
passed: workflowContract.passed,
reason: workflowContract.passed ? null : workflowContract.reason,
},
{
name: "runtime_contract_compatible",
@@ -1361,6 +1723,26 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
? null
: "runtime_contract_incompatible",
},
{
name: "main_request_resolved",
passed: workflowArchetype !== "single_request_enrichment" || Boolean(mainRequest),
reason: workflowArchetype !== "single_request_enrichment" || mainRequest ? null : "main_request",
},
{
name: "enrichment_requests_resolved",
passed: workflowArchetype !== "single_request_enrichment" || Boolean((enrichmentRequests || []).length),
reason: workflowArchetype !== "single_request_enrichment" || (enrichmentRequests || []).length ? null : "enrichment_requests",
},
{
name: "merge_plan_resolved",
passed: workflowArchetype !== "single_request_enrichment" || Boolean(mergePlan),
reason: workflowArchetype !== "single_request_enrichment" || mergePlan ? null : "merge_plan",
},
{
name: "g1e_scope_compatible",
passed: workflowArchetype !== "single_request_enrichment" || Boolean(mainRequest && (enrichmentRequests || []).length && mergePlan),
reason: workflowArchetype !== "single_request_enrichment" || (mainRequest && (enrichmentRequests || []).length && mergePlan) ? null : "g1e_scope",
},
];
return {
@@ -1373,6 +1755,175 @@ function buildReadiness({ sceneIdDiagnostics, workflowArchetype, bootstrap, apiE
};
}
function evaluateRequestContract({ workflowArchetype, apiEndpoints, params, workflowSteps }) {
const endpointCount = (apiEndpoints || []).filter((endpoint) =>
["business_api", "gateway_api", "business_entry"].includes(endpoint.role)
).length;
const hasRequestStep = (workflowSteps || []).some((step) => ["request", "paginate", "secondary_request"].includes(step.type));
const hasRuntimeInputs = (params || []).length > 0;
if (workflowArchetype === "multi_mode_request") {
const periodParamReady = (params || []).some((param) => param.name === "period");
return periodParamReady && endpointCount > 0
? { passed: true }
: {
passed: false,
reason: endpointCount > 0 ? "request_mode_param" : "request_endpoint",
message: endpointCount > 0
? "Multi-mode request is missing a resolved mode/period contract."
: "Request contract is missing a business endpoint.",
};
}
if (workflowArchetype === "single_request_enrichment") {
return endpointCount >= 2 && hasRequestStep
? { passed: true }
: {
passed: false,
reason: endpointCount >= 2 ? "main_request" : "request_endpoint",
message: endpointCount >= 2
? "G1-E workflow is missing a resolved main request."
: "G1-E workflow requires both main and enrichment business endpoints.",
};
}
if (workflowArchetype === "paginated_enrichment") {
return endpointCount >= 2 && hasRequestStep
? { passed: true }
: {
passed: false,
reason: endpointCount >= 2 ? "request_workflow" : "request_endpoint",
message: endpointCount >= 2
? "Paginated enrichment is missing request-side workflow signals."
: "Paginated enrichment requires both primary and secondary request endpoints.",
};
}
if (workflowArchetype === "page_state_eval") {
return hasRequestStep || endpointCount > 0
? { passed: true }
: {
passed: false,
reason: "request_workflow",
message: "Page-state workflow is missing request or state-evaluation entry signals.",
};
}
return endpointCount > 0 || hasRuntimeInputs
? { passed: true }
: {
passed: false,
reason: "request_endpoint",
message: "Request contract is missing a business endpoint or runtime input.",
};
}
function evaluateResponseContract({ workflowArchetype, workflowSteps, apiEndpoints }) {
const endpointCount = (apiEndpoints || []).filter((endpoint) =>
["business_api", "gateway_api", "business_entry"].includes(endpoint.role)
).length;
const hasTransform = (workflowSteps || []).some((step) => ["transform", "filter", "export"].includes(step.type));
if (workflowArchetype === "single_request_enrichment") {
return endpointCount >= 2 && hasTransform
? { passed: true }
: {
passed: false,
reason: endpointCount >= 2 ? "merge_plan" : "response_path",
message: endpointCount >= 2
? "G1-E workflow is missing merge/transform evidence."
: "G1-E workflow lacks enough response-side endpoint evidence.",
};
}
if (workflowArchetype === "paginated_enrichment") {
return endpointCount >= 2
? { passed: true }
: {
passed: false,
reason: "response_path",
message: "Paginated enrichment lacks enough response-side endpoints to confirm extraction.",
};
}
if (workflowArchetype === "page_state_eval") {
return { passed: true };
}
return endpointCount > 0 || hasTransform
? { passed: true }
: {
passed: false,
reason: "response_path",
message: "Response contract is missing extraction or transform evidence.",
};
}
function evaluateWorkflowContract({ workflowArchetype, workflowSteps, businessApiEndpoints }) {
const hasAnyWorkflow = (workflowSteps || []).length > 0;
if (!hasAnyWorkflow) {
return {
passed: false,
reason: "workflow_steps",
message: "Workflow contract is missing executable steps.",
};
}
if (workflowArchetype === "paginated_enrichment") {
const hasPaginate = workflowSteps.some((step) => step.type === "paginate");
const hasSecondary = workflowSteps.some((step) => step.type === "secondary_request");
const hasPostProcess = workflowSteps.some((step) => ["filter", "transform", "export"].includes(step.type));
if (!hasPaginate) {
return {
passed: false,
reason: "paginate_step",
message: "Paginated enrichment lacks pagination evidence.",
};
}
if (!hasSecondary || (businessApiEndpoints || []).length < 2) {
return {
passed: false,
reason: "secondary_request",
message: "Paginated enrichment lacks a strong secondary request signal.",
};
}
if (!hasPostProcess) {
return {
passed: false,
reason: "post_process",
message: "Paginated enrichment lacks filter/transform/export evidence.",
};
}
}
if (workflowArchetype === "single_request_enrichment") {
const hasRequest = workflowSteps.some((step) => step.type === "request");
const hasEnrichment = workflowSteps.some((step) => step.type === "enrichment_request");
const hasTransform = workflowSteps.some((step) => step.type === "transform");
if (!hasRequest || !hasEnrichment || !hasTransform) {
return {
passed: false,
reason: !hasRequest ? "workflow_request" : !hasEnrichment ? "enrichment_requests" : "workflow_transform",
message: "G1-E workflow lacks a complete main/enrichment/transform chain.",
};
}
}
if (workflowArchetype === "multi_mode_request") {
const hasRequest = workflowSteps.some((step) => step.type === "request");
const hasTransform = workflowSteps.some((step) => step.type === "transform");
if (!hasRequest || !hasTransform) {
return {
passed: false,
reason: !hasRequest ? "workflow_request" : "workflow_transform",
message: "Multi-mode request lacks a complete request/transform workflow.",
};
}
}
return { passed: true };
}
function slugifyAscii(value) {
return String(value || "")
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")

View File

@@ -26,7 +26,7 @@ Schema:
"sceneId": "string",
"sceneName": "string",
"sceneKind": "report_collection|monitoring",
"workflowArchetype": "single_request_table|multi_mode_request|paginated_enrichment|page_state_eval",
"workflowArchetype": "single_request_table|single_request_enrichment|multi_mode_request|paginated_enrichment|page_state_eval",
"bootstrap": {
"expectedDomain": "string",
"targetUrl": "string",
@@ -67,6 +67,27 @@ Schema:
"secondaryRequestEntries": ["string"],
"postProcessSteps": ["string"]
},
"mainRequest": {
"apiEndpoint": { "name": "string", "url": "string", "method": "POST", "contentType": "string", "description": "string" },
"requestTemplate": {},
"responsePath": "string",
"columnDefs": [["field", "label"]]
},
"enrichmentRequests": [
{
"name": "string",
"apiEndpoint": { "name": "string", "url": "string", "method": "POST", "contentType": "string", "description": "string" },
"paramBindings": {},
"responsePath": "string",
"consumedFields": ["string"]
}
],
"mergePlan": {
"joinKeys": ["string"],
"fieldMappings": [{ "outputField": "string", "sourceType": "main|aggregate|enrichment", "sourceField": "string", "requestName": "string|null" }],
"aggregateRules": ["string"],
"outputColumns": [["field", "label"]]
},
"requestTemplate": {},
"responsePath": "string",
"normalizeRules": { "type": "validate_required", "requiredFields": ["string"], "filterNull": true },

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -372,6 +372,14 @@
<label for="instructionInput">任务内容</label>
<textarea id="instructionInput" placeholder="例如:打开百度"></textarea>
</div>
<div class="field">
<label for="pageUrlInput">页面 URL可选</label>
<input id="pageUrlInput" placeholder="例如https://www.zhihu.com 或真实业务页地址" />
</div>
<div class="field">
<label for="pageTitleInput">页面标题(可选)</label>
<input id="pageTitleInput" placeholder="例如:知乎 - 热榜" />
</div>
<div id="validationText" class="validation"></div>
<button id="sendBtn" class="primary-btn" disabled>发送任务</button>
</div>
@@ -458,6 +466,8 @@
connectionState: document.getElementById("connectionState"),
messageStream: document.getElementById("messageStream"),
instructionInput: document.getElementById("instructionInput"),
pageUrlInput: document.getElementById("pageUrlInput"),
pageTitleInput: document.getElementById("pageTitleInput"),
validationText: document.getElementById("validationText"),
sendBtn: document.getElementById("sendBtn"),
emptyState: document.getElementById("emptyState")
@@ -688,14 +698,17 @@
return;
}
const pageUrl = elements.pageUrlInput.value.trim();
const pageTitle = elements.pageTitleInput.value.trim();
setValidation("");
socket.send(JSON.stringify({
type: "submit_task",
instruction,
conversation_id: "",
messages: [],
page_url: "",
page_title: ""
page_url: pageUrl,
page_title: pageTitle
}));
}