feat: add AI Workspace bridge skills and version gate
This commit is contained in:
@@ -1204,6 +1204,22 @@ async function checkHubExecutor(executor, checkedAt) {
|
||||
9000
|
||||
);
|
||||
const health = isPlainObject(healthPayload.payload) ? healthPayload.payload : {};
|
||||
const compatibility = bridgeCompatibilityStatus(health, agent);
|
||||
if (!compatibility.ok) {
|
||||
return executorCheckResult(
|
||||
"error",
|
||||
"agent_update_required",
|
||||
optionalString(health.runtimeCheckedAt) || agent.lastSeenAt || checkedAt,
|
||||
{
|
||||
checkedAt,
|
||||
mode: "hub",
|
||||
pairingCode,
|
||||
agent,
|
||||
health: summarizeBridgeHealth(health),
|
||||
bridgeCompatibility: compatibility,
|
||||
}
|
||||
);
|
||||
}
|
||||
const runtimeOk = health.ok !== false && health.runtimeStatus !== "error";
|
||||
return executorCheckResult(
|
||||
runtimeOk ? "online" : "error",
|
||||
@@ -1254,6 +1270,16 @@ async function checkDirectExecutor(executor, checkedAt) {
|
||||
|
||||
try {
|
||||
const health = await fetchJson(url, { method: "GET", headers: { Accept: "application/json" } }, 7000);
|
||||
const compatibility = bridgeCompatibilityStatus(health, {});
|
||||
if (!compatibility.ok) {
|
||||
return executorCheckResult("error", "agent_update_required", optionalString(health.runtimeCheckedAt) || checkedAt, {
|
||||
checkedAt,
|
||||
mode: "direct",
|
||||
endpoint: executor.endpoint,
|
||||
health: summarizeBridgeHealth(health),
|
||||
bridgeCompatibility: compatibility,
|
||||
});
|
||||
}
|
||||
const runtimeOk = health.ok !== false && health.runtimeStatus !== "error";
|
||||
return executorCheckResult(
|
||||
runtimeOk ? "online" : "error",
|
||||
@@ -2579,6 +2605,9 @@ function summarizeBridgeHealth(health) {
|
||||
return {
|
||||
ok: health.ok !== false,
|
||||
service: optionalString(health.service),
|
||||
agentVersion: optionalString(health.agentVersion),
|
||||
protocolVersion: optionalString(health.protocolVersion),
|
||||
capabilities: Array.isArray(health.capabilities) ? uniqueStrings(health.capabilities).slice(0, 80) : [],
|
||||
host: optionalString(health.host),
|
||||
machineName: optionalString(health.machineName),
|
||||
runtimeStatus: optionalString(health.runtimeStatus),
|
||||
@@ -2589,6 +2618,63 @@ function summarizeBridgeHealth(health) {
|
||||
};
|
||||
}
|
||||
|
||||
function bridgeCompatibilityStatus(health, agent) {
|
||||
const agentVersion = optionalString(health?.agentVersion || agent?.agentVersion);
|
||||
const protocolVersion = optionalString(health?.protocolVersion || agent?.protocolVersion);
|
||||
const capabilities = uniqueStrings([
|
||||
...(Array.isArray(agent?.capabilities) ? agent.capabilities : []),
|
||||
...(Array.isArray(health?.capabilities) ? health.capabilities : []),
|
||||
]);
|
||||
const requiredCapabilities = Array.isArray(config.requiredBridgeCapabilities)
|
||||
? config.requiredBridgeCapabilities
|
||||
: [];
|
||||
const missingCapabilities = requiredCapabilities.filter((item) => !capabilities.includes(item));
|
||||
const minAgentVersion = optionalString(config.minimumBridgeAgentVersion);
|
||||
const requiredProtocolVersion = optionalString(config.requiredBridgeProtocolVersion);
|
||||
|
||||
if (requiredProtocolVersion && protocolVersion !== requiredProtocolVersion) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "protocol_version_required",
|
||||
agentVersion,
|
||||
protocolVersion,
|
||||
requiredProtocolVersion,
|
||||
minimumBridgeAgentVersion: minAgentVersion,
|
||||
missingCapabilities,
|
||||
};
|
||||
}
|
||||
if (minAgentVersion && compareSemanticVersion(agentVersion, minAgentVersion) < 0) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "agent_version_too_old",
|
||||
agentVersion,
|
||||
protocolVersion,
|
||||
requiredProtocolVersion,
|
||||
minimumBridgeAgentVersion: minAgentVersion,
|
||||
missingCapabilities,
|
||||
};
|
||||
}
|
||||
if (missingCapabilities.length) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "capability_required",
|
||||
agentVersion,
|
||||
protocolVersion,
|
||||
requiredProtocolVersion,
|
||||
minimumBridgeAgentVersion: minAgentVersion,
|
||||
missingCapabilities,
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
agentVersion,
|
||||
protocolVersion,
|
||||
requiredProtocolVersion,
|
||||
minimumBridgeAgentVersion: minAgentVersion,
|
||||
missingCapabilities: [],
|
||||
};
|
||||
}
|
||||
|
||||
function cleanBridgeEventForUi(event) {
|
||||
if (!isPlainObject(event)) return event;
|
||||
const kind = optionalString(event.kind) || "";
|
||||
@@ -3634,6 +3720,28 @@ function normalizeKey(value) {
|
||||
return String(value || "").trim().toLowerCase().replace(/_/g, "-");
|
||||
}
|
||||
|
||||
function compareSemanticVersion(actual, minimum) {
|
||||
const actualText = optionalString(actual);
|
||||
const minimumText = optionalString(minimum);
|
||||
if (!minimumText) return 0;
|
||||
if (!actualText) return -1;
|
||||
const parse = (value) => String(value || "")
|
||||
.split(/[.-]/)
|
||||
.slice(0, 4)
|
||||
.map((part) => {
|
||||
const match = part.match(/^\d+/);
|
||||
return match ? Number(match[0]) : 0;
|
||||
});
|
||||
const left = parse(actualText);
|
||||
const right = parse(minimumText);
|
||||
const length = Math.max(left.length, right.length, 3);
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
const delta = Number(left[i] || 0) - Number(right[i] || 0);
|
||||
if (delta !== 0) return delta > 0 ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
@@ -3878,6 +3986,19 @@ function readConfig() {
|
||||
optionalString(process.env.AI_WORKSPACE_BRIDGE_PACKAGE_NAME) ||
|
||||
optionalString(process.env.NDC_AI_WORKSPACE_BRIDGE_PACKAGE_NAME) ||
|
||||
AI_WORKSPACE_BRIDGE_PACKAGE_NAME,
|
||||
minimumBridgeAgentVersion:
|
||||
optionalString(process.env.AI_WORKSPACE_MIN_BRIDGE_AGENT_VERSION) ||
|
||||
optionalString(process.env.NDC_AI_WORKSPACE_MIN_BRIDGE_AGENT_VERSION) ||
|
||||
"",
|
||||
requiredBridgeProtocolVersion:
|
||||
optionalString(process.env.AI_WORKSPACE_REQUIRED_BRIDGE_PROTOCOL_VERSION) ||
|
||||
optionalString(process.env.NDC_AI_WORKSPACE_REQUIRED_BRIDGE_PROTOCOL_VERSION) ||
|
||||
"",
|
||||
requiredBridgeCapabilities: uniqueStrings(String(
|
||||
process.env.AI_WORKSPACE_REQUIRED_BRIDGE_CAPABILITIES ||
|
||||
process.env.NDC_AI_WORKSPACE_REQUIRED_BRIDGE_CAPABILITIES ||
|
||||
""
|
||||
).split(/[\n,;]+/)),
|
||||
setupCodeTtlSeconds: sanitizeInteger(
|
||||
process.env.AI_WORKSPACE_SETUP_CODE_TTL_SECONDS ||
|
||||
process.env.NDC_AI_WORKSPACE_SETUP_CODE_TTL_SECONDS,
|
||||
|
||||
Reference in New Issue
Block a user