源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
// Agent protocol v2 enum consistency check (agent-gui refactor plan, P2).
|
||||
//
|
||||
// The closed turn/interaction enums are declared twice by design: once in the
|
||||
// OpenAPI contract (services/tuttid/api/openapi/tuttid.v1.yaml) and once in
|
||||
// the event protocol schema (packages/events/protocol/definitions/agent/
|
||||
// activity.updated.event.json). Both sides feed independent generators, so a
|
||||
// drifted enum would silently produce mismatched Go/TS types. This script
|
||||
// fails when the two declarations disagree.
|
||||
//
|
||||
// Run directly or via `pnpm check:agent-protocol-enums`.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import YAML from "yaml";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const openapiPath = resolve(
|
||||
repoRoot,
|
||||
"services/tuttid/api/openapi/tuttid.v1.yaml"
|
||||
);
|
||||
const eventSchemaPath = resolve(
|
||||
repoRoot,
|
||||
"packages/events/protocol/definitions/agent/activity.updated.event.json"
|
||||
);
|
||||
|
||||
const openapi = YAML.parse(readFileSync(openapiPath, "utf8"));
|
||||
const eventSchema = JSON.parse(readFileSync(eventSchemaPath, "utf8"));
|
||||
|
||||
function openapiEnum(schemaName) {
|
||||
const schema = openapi?.components?.schemas?.[schemaName];
|
||||
if (!schema || !Array.isArray(schema.enum)) {
|
||||
throw new Error(`openapi schema ${schemaName} has no enum`);
|
||||
}
|
||||
return schema.enum;
|
||||
}
|
||||
|
||||
function openapiPropertyEnum(schemaName, propertyName) {
|
||||
const property =
|
||||
openapi?.components?.schemas?.[schemaName]?.properties?.[propertyName];
|
||||
if (!property || !Array.isArray(property.enum)) {
|
||||
throw new Error(`openapi schema ${schemaName}.${propertyName} has no enum`);
|
||||
}
|
||||
return property.enum;
|
||||
}
|
||||
|
||||
function eventBranch(eventType) {
|
||||
const branches = eventSchema?.payloadSchema?.oneOf;
|
||||
if (!Array.isArray(branches)) {
|
||||
throw new Error("event schema payloadSchema.oneOf missing");
|
||||
}
|
||||
const branch = branches.find((candidate) => {
|
||||
const type = candidate?.properties?.data?.properties?.eventType;
|
||||
return (
|
||||
type?.const === eventType ||
|
||||
(Array.isArray(type?.enum) && type.enum.includes(eventType))
|
||||
);
|
||||
});
|
||||
if (!branch) {
|
||||
throw new Error(`event schema has no ${eventType} branch`);
|
||||
}
|
||||
return branch.properties.data.properties;
|
||||
}
|
||||
|
||||
function schemaEnum(node, path) {
|
||||
const values = node?.enum;
|
||||
if (!Array.isArray(values)) {
|
||||
throw new Error(`event schema enum missing at ${path}`);
|
||||
}
|
||||
return values.filter((value) => value !== null);
|
||||
}
|
||||
|
||||
const turnProperties = eventBranch("turn_update").turn.properties;
|
||||
const interactionProperties =
|
||||
eventBranch("interaction_update").interaction.properties;
|
||||
|
||||
const pairs = [
|
||||
{
|
||||
name: "turn phase",
|
||||
openapi: openapiEnum("WorkspaceAgentTurnPhase"),
|
||||
event: schemaEnum(turnProperties.phase, "turn_update.turn.phase")
|
||||
},
|
||||
{
|
||||
name: "turn outcome",
|
||||
openapi: openapiEnum("WorkspaceAgentTurnOutcome"),
|
||||
event: schemaEnum(turnProperties.outcome, "turn_update.turn.outcome")
|
||||
},
|
||||
{
|
||||
name: "completed command kind",
|
||||
openapi: openapiPropertyEnum("WorkspaceAgentCompletedCommand", "kind"),
|
||||
event: schemaEnum(
|
||||
turnProperties.completedCommand.properties.kind,
|
||||
"turn_update.turn.completedCommand.kind"
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "completed command status",
|
||||
openapi: openapiPropertyEnum("WorkspaceAgentCompletedCommand", "status"),
|
||||
event: schemaEnum(
|
||||
turnProperties.completedCommand.properties.status,
|
||||
"turn_update.turn.completedCommand.status"
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "interaction kind",
|
||||
openapi: openapiEnum("WorkspaceAgentInteractionKind"),
|
||||
event: schemaEnum(
|
||||
interactionProperties.kind,
|
||||
"interaction_update.interaction.kind"
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "interaction status",
|
||||
openapi: openapiEnum("WorkspaceAgentInteractionStatus"),
|
||||
event: schemaEnum(
|
||||
interactionProperties.status,
|
||||
"interaction_update.interaction.status"
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
for (const pair of pairs) {
|
||||
const openapiValues = [...pair.openapi].sort();
|
||||
const eventValues = [...pair.event].sort();
|
||||
if (JSON.stringify(openapiValues) !== JSON.stringify(eventValues)) {
|
||||
failed = true;
|
||||
console.error(
|
||||
`agent protocol enum drift (${pair.name}): openapi=${JSON.stringify(openapiValues)} event=${JSON.stringify(eventValues)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
console.error(
|
||||
"Fix: keep the closed enums in tuttid.v1.yaml and activity.updated.event.json identical, then regenerate (pnpm generate:api && pnpm generate:event-protocol)."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("agent protocol enums consistent across openapi and event schema");
|
||||
Reference in New Issue
Block a user