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
186 lines
5.5 KiB
JavaScript
186 lines
5.5 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = resolve(scriptDir, "../..");
|
|
const schemaPath = resolve(
|
|
repoRoot,
|
|
"packages/agent/daemon/liveprotocol/schema/agent-activity-live-event.schema.json"
|
|
);
|
|
const wireContractPath = resolve(
|
|
repoRoot,
|
|
"packages/agent/daemon/liveprotocol/schema/agent-activity-live-wire-contract.json"
|
|
);
|
|
const canonicalPath = resolve(
|
|
repoRoot,
|
|
"packages/events/protocol/definitions/agent/activity.updated.event.json"
|
|
);
|
|
const goOutputPath = resolve(
|
|
repoRoot,
|
|
"packages/agent/daemon/liveprotocol/protocol_revision.gen.go"
|
|
);
|
|
const tsOutputPath = resolve(
|
|
repoRoot,
|
|
"packages/agent/activity-core/src/liveProtocolRevision.gen.ts"
|
|
);
|
|
const checkOnly = process.argv.includes("--check");
|
|
|
|
const liveSchema = JSON.parse(readFileSync(schemaPath, "utf8"));
|
|
const wireContract = JSON.parse(readFileSync(wireContractPath, "utf8"));
|
|
const canonicalDefinition = JSON.parse(readFileSync(canonicalPath, "utf8"));
|
|
const canonicalVariants = [
|
|
"session_audit",
|
|
"turn_update",
|
|
"interaction_update"
|
|
].map((eventType) => {
|
|
const variant = canonicalDefinition.payloadSchema.oneOf.find(
|
|
(candidate) => candidate?.properties?.eventType?.const === eventType
|
|
);
|
|
if (!variant) {
|
|
throw new Error(`canonical agent activity schema is missing ${eventType}`);
|
|
}
|
|
return variant;
|
|
});
|
|
validateWireContract(wireContract);
|
|
const digest = createHash("sha256")
|
|
.update(JSON.stringify({ liveSchema, wireContract, canonicalVariants }))
|
|
.digest("hex")
|
|
.slice(0, 16);
|
|
const revision = `sha256:${digest}`;
|
|
const goWireFieldConstants = [
|
|
...wireContract.frameFields,
|
|
...wireContract.deliveryFields
|
|
];
|
|
const goWireFieldConstantWidth = Math.max(
|
|
...goWireFieldConstants.map((field) => field.goConstant.length)
|
|
);
|
|
const goWireFieldDeclarations = goWireFieldConstants
|
|
.map(
|
|
(field) =>
|
|
`\t${field.goConstant.padEnd(goWireFieldConstantWidth)} = ${field.number} // ${field.name}: ${field.wireType}`
|
|
)
|
|
.join("\n");
|
|
const goDeliveryKindConstantWidth = Math.max(
|
|
...wireContract.deliveryKinds.map((kind) => kind.goConstant.length)
|
|
);
|
|
const goDeliveryKindConstants = wireContract.deliveryKinds
|
|
.map(
|
|
(kind) =>
|
|
`\t${kind.goConstant.padEnd(goDeliveryKindConstantWidth)} DeliveryKind = ${kind.value} // payload: ${kind.payloadField}`
|
|
)
|
|
.join("\n");
|
|
|
|
const outputs = new Map([
|
|
[
|
|
goOutputPath,
|
|
`// Code generated by tools/scripts/generate-agent-live-protocol.mjs. DO NOT EDIT.
|
|
|
|
package liveprotocol
|
|
|
|
const ProtocolRevision = ${JSON.stringify(revision)}
|
|
|
|
// Protobuf-wire field numbers and delivery kinds are generated from
|
|
// schema/agent-activity-live-wire-contract.json. They participate in
|
|
// ProtocolRevision and must not be edited independently.
|
|
const (
|
|
${goWireFieldDeclarations}
|
|
)
|
|
|
|
const (
|
|
${goDeliveryKindConstants}
|
|
)
|
|
`
|
|
],
|
|
[
|
|
tsOutputPath,
|
|
`// Code generated by tools/scripts/generate-agent-live-protocol.mjs. DO NOT EDIT.
|
|
|
|
export const AGENT_ACTIVITY_LIVE_PROTOCOL_REVISION =
|
|
${JSON.stringify(revision)} as const;
|
|
`
|
|
]
|
|
]);
|
|
|
|
for (const [path, expected] of outputs) {
|
|
if (checkOnly) {
|
|
const actual = readFileSync(path, "utf8");
|
|
if (actual !== expected) {
|
|
throw new Error(
|
|
`${path} is stale; run pnpm generate:agent-live-protocol`
|
|
);
|
|
}
|
|
} else {
|
|
writeFileSync(path, expected);
|
|
}
|
|
}
|
|
|
|
function validateWireContract(contract) {
|
|
if (
|
|
contract?.encoding !== "protobuf-wire" ||
|
|
!Array.isArray(contract.frameFields) ||
|
|
!Array.isArray(contract.deliveryFields) ||
|
|
!Array.isArray(contract.deliveryKinds) ||
|
|
typeof contract.controls !== "object" ||
|
|
contract.controls === null
|
|
) {
|
|
throw new Error("Agent live wire contract has an invalid root shape");
|
|
}
|
|
|
|
validateNumberedEntries(contract.frameFields, "frame field");
|
|
validateNumberedEntries(contract.deliveryFields, "delivery field");
|
|
validateNumberedEntries(contract.deliveryKinds, "delivery kind", "value");
|
|
|
|
const deliveryFieldNames = new Set(
|
|
contract.deliveryFields.map((field) => field.name)
|
|
);
|
|
for (const kind of contract.deliveryKinds) {
|
|
if (!deliveryFieldNames.has(kind.payloadField)) {
|
|
throw new Error(
|
|
`delivery kind ${kind.name} references unknown payload field ${kind.payloadField}`
|
|
);
|
|
}
|
|
}
|
|
for (const requiredControl of [
|
|
"discontinuity",
|
|
"attachmentChanged",
|
|
"attachmentCaughtUp",
|
|
"goalChanged",
|
|
"streamReady",
|
|
"rejected"
|
|
]) {
|
|
if (!(requiredControl in contract.controls)) {
|
|
throw new Error(`wire contract is missing ${requiredControl} control`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateNumberedEntries(entries, label, numberKey = "number") {
|
|
const names = new Set();
|
|
const constants = new Set();
|
|
const numbers = new Set();
|
|
for (const entry of entries) {
|
|
if (
|
|
typeof entry?.name !== "string" ||
|
|
entry.name.length === 0 ||
|
|
typeof entry?.goConstant !== "string" ||
|
|
entry.goConstant.length === 0 ||
|
|
!Number.isSafeInteger(entry?.[numberKey]) ||
|
|
entry[numberKey] <= 0
|
|
) {
|
|
throw new Error(`${label} has an invalid declaration`);
|
|
}
|
|
if (
|
|
names.has(entry.name) ||
|
|
constants.has(entry.goConstant) ||
|
|
numbers.has(entry[numberKey])
|
|
) {
|
|
throw new Error(`${label} declarations must be unique`);
|
|
}
|
|
names.add(entry.name);
|
|
constants.add(entry.goConstant);
|
|
numbers.add(entry[numberKey]);
|
|
}
|
|
}
|