@oxygen-agent/cli 1.717.11 → 1.739.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/command-manifest.js +27 -12
- package/dist/index.js +243 -72
- package/dist/skills.js +2 -2
- package/node_modules/@oxygen/shared/dist/axiom-field-budget.d.ts +99 -0
- package/node_modules/@oxygen/shared/dist/axiom-field-budget.js +112 -17
- package/node_modules/@oxygen/shared/dist/cli-result.js +2 -0
- package/node_modules/@oxygen/shared/dist/crm-activity-events.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/crm-activity-events.js +5 -0
- package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +27 -0
- package/node_modules/@oxygen/shared/dist/future-signup-events.js +90 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +320 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1233 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +7 -1
- package/node_modules/@oxygen/shared/dist/index.js +14 -1
- package/node_modules/@oxygen/shared/dist/notetaker-events.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/notetaker-events.js +23 -0
- package/node_modules/@oxygen/shared/dist/plain-support-events.d.ts +40 -0
- package/node_modules/@oxygen/shared/dist/plain-support-events.js +43 -0
- package/node_modules/@oxygen/shared/dist/redaction.js +6 -1
- package/node_modules/@oxygen/shared/dist/user-capability-routing.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +23 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/webhook-headers.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/webhook-headers.js +48 -0
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.d.ts +31 -1
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +112 -1
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +7 -5
- package/node_modules/@oxygen/workflows/dist/graph/lint.d.ts +9 -0
- package/node_modules/@oxygen/workflows/dist/graph/lint.js +409 -7
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +513 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +104 -1
- package/node_modules/@oxygen/workflows/dist/graph/params.d.ts +2 -0
- package/node_modules/@oxygen/workflows/dist/graph/params.js +15 -0
- package/node_modules/@oxygen/workflows/dist/graph/remap.js +12 -3
- package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +9 -0
- package/node_modules/@oxygen/workflows/dist/graph/topology.js +33 -1
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +123 -16
- package/node_modules/@oxygen/workflows/dist/graph/types.js +45 -0
- package/node_modules/@oxygen/workflows/dist/index.d.ts +94 -2
- package/node_modules/@oxygen/workflows/dist/index.js +169 -26
- package/node_modules/@oxygen/workflows/dist/portable.js +3 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as vm from "node:vm"; // skipcq: JS-C1003
|
|
3
|
-
import { isWorkflowGraphManifest } from "./graph/types.js";
|
|
3
|
+
import { isWorkflowGraphManifest, WORKFLOW_CODE_V1_DEFAULT_OUTPUT_BYTES, WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS, WORKFLOW_CODE_V1_MAX_OUTPUT_BYTES, WORKFLOW_CODE_V1_MAX_TIMEOUT_MS, WORKFLOW_CODE_V1_MIN_OUTPUT_BYTES, WORKFLOW_CODE_V1_MIN_TIMEOUT_MS, } from "./graph/types.js";
|
|
4
4
|
import { lintWorkflowGraphManifest } from "./graph/lint.js";
|
|
5
5
|
export * from "./usage-estimate.js";
|
|
6
6
|
// Pure event-dispatch semantics, shared by the apps/web and apps/worker event
|
|
@@ -51,6 +51,18 @@ const UNSAFE_RECIPE_BUNDLE_PATTERNS = [
|
|
|
51
51
|
{ token: "setTimeout", pattern: /\bsetTimeout\b/ },
|
|
52
52
|
{ token: "setInterval", pattern: /\bsetInterval\b/ }, // skipcq: SCT-A000
|
|
53
53
|
];
|
|
54
|
+
// Code v1 has no ctx.now()/ctx.uuid() capability and runs as a pure transform,
|
|
55
|
+
// so its determinism/capability denylist is intentionally stricter than the
|
|
56
|
+
// durable-recipe compatibility scan above. Keep this separate: old immutable
|
|
57
|
+
// recipe revisions must not acquire new lint failures merely because Code v1 is
|
|
58
|
+
// introduced.
|
|
59
|
+
const UNSAFE_WORKFLOW_CODE_SOURCE_PATTERNS = [
|
|
60
|
+
...UNSAFE_RECIPE_BUNDLE_PATTERNS,
|
|
61
|
+
{ token: "Date", pattern: /\bDate\b/ },
|
|
62
|
+
{ token: "WebAssembly", pattern: /\bWebAssembly\b/ },
|
|
63
|
+
{ token: "setImmediate", pattern: /\bsetImmediate\b/ },
|
|
64
|
+
{ token: "queueMicrotask", pattern: /\bqueueMicrotask\b/ },
|
|
65
|
+
];
|
|
54
66
|
/** A valid workflow / recipe / trigger status. Optional everywhere it appears. */
|
|
55
67
|
function isValidWorkflowStatus(value) {
|
|
56
68
|
return value === "active" || value === "disabled";
|
|
@@ -910,13 +922,34 @@ export function buildBlueprint(input) {
|
|
|
910
922
|
?? hashWorkflowSource(JSON.stringify(draft, workflowJsonReplacer));
|
|
911
923
|
return { ...draft, source_hash: sourceHash };
|
|
912
924
|
}
|
|
913
|
-
export function validateJsonSchemaValue(value, schema, path = "$") {
|
|
925
|
+
export function validateJsonSchemaValue(value, schema, path = "$", maxIssues = Number.POSITIVE_INFINITY, maxVisits = Number.POSITIVE_INFINITY) {
|
|
914
926
|
if (!schema || typeof schema !== "object" || Array.isArray(schema))
|
|
915
927
|
return [];
|
|
916
928
|
const issues = [];
|
|
917
|
-
|
|
929
|
+
const budget = {
|
|
930
|
+
visits: 0,
|
|
931
|
+
maximum: Math.max(1, Math.floor(maxVisits)),
|
|
932
|
+
exhausted: false,
|
|
933
|
+
};
|
|
934
|
+
validateJsonSchemaValueInto(value, schema, path, issues, Math.max(1, Math.floor(maxIssues)), budget);
|
|
935
|
+
if (budget.exhausted) {
|
|
936
|
+
return [{
|
|
937
|
+
path,
|
|
938
|
+
code: "schema_validation_too_complex",
|
|
939
|
+
message: `${path} exceeded the JSON Schema validation work limit.`,
|
|
940
|
+
}];
|
|
941
|
+
}
|
|
918
942
|
return issues;
|
|
919
943
|
}
|
|
944
|
+
function consumeJsonSchemaValidationBudget(budget, amount = 1) {
|
|
945
|
+
if (budget.exhausted)
|
|
946
|
+
return false;
|
|
947
|
+
budget.visits += amount;
|
|
948
|
+
if (budget.visits <= budget.maximum)
|
|
949
|
+
return true;
|
|
950
|
+
budget.exhausted = true;
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
920
953
|
export function assertRecipeBundleSafe(bundle) {
|
|
921
954
|
const issues = lintRecipeBundleSafety(bundle, "$.bundle");
|
|
922
955
|
const first = issues[0];
|
|
@@ -930,9 +963,29 @@ export function lintRecipeBundleSafety(bundle, path = "$.bundle") {
|
|
|
930
963
|
});
|
|
931
964
|
return issues;
|
|
932
965
|
}
|
|
966
|
+
/** Comment/string-aware author feedback for the stricter oxygen-js-v1 runtime. */
|
|
967
|
+
export function lintWorkflowCodeSourceSafety(source, path = "$.source") {
|
|
968
|
+
const issues = [];
|
|
969
|
+
if (!source.trim()) {
|
|
970
|
+
issues.push({ path, code: "empty_function_source", message: "Typed code source cannot be empty." });
|
|
971
|
+
return issues;
|
|
972
|
+
}
|
|
973
|
+
const scannable = stripCommentsAndStrings(source);
|
|
974
|
+
for (const entry of UNSAFE_WORKFLOW_CODE_SOURCE_PATTERNS) {
|
|
975
|
+
if (!entry.pattern.test(scannable))
|
|
976
|
+
continue;
|
|
977
|
+
issues.push({
|
|
978
|
+
path,
|
|
979
|
+
code: "unsafe_code_source",
|
|
980
|
+
message: `Typed code source cannot reference '${entry.token}'. Bind deterministic JSON inputs and use explicit Tool nodes for capabilities.`,
|
|
981
|
+
});
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
return issues;
|
|
985
|
+
}
|
|
933
986
|
export async function runPureWorkflowFunction(input) {
|
|
934
|
-
const timeoutMs = Math.max(
|
|
935
|
-
const maxOutputBytes = Math.max(
|
|
987
|
+
const timeoutMs = Math.max(WORKFLOW_CODE_V1_MIN_TIMEOUT_MS, Math.min(input.timeoutMs ?? WORKFLOW_CODE_V1_DEFAULT_TIMEOUT_MS, WORKFLOW_CODE_V1_MAX_TIMEOUT_MS));
|
|
988
|
+
const maxOutputBytes = Math.max(WORKFLOW_CODE_V1_MIN_OUTPUT_BYTES, Math.min(input.maxOutputBytes ?? WORKFLOW_CODE_V1_DEFAULT_OUTPUT_BYTES, WORKFLOW_CODE_V1_MAX_OUTPUT_BYTES));
|
|
936
989
|
const issues = [];
|
|
937
990
|
validatePureFunctionSource(input.source, "$.source", (path, code, message) => {
|
|
938
991
|
issues.push({ path, code, message });
|
|
@@ -1027,6 +1080,10 @@ export const workflowApplySchema = {
|
|
|
1027
1080
|
exclusiveMinimum: 0,
|
|
1028
1081
|
description: "Hard credit ceiling for each autonomous delivery.",
|
|
1029
1082
|
},
|
|
1083
|
+
publish: {
|
|
1084
|
+
type: "boolean",
|
|
1085
|
+
description: "False saves an editable draft revision without publishing it. Defaults to true.",
|
|
1086
|
+
},
|
|
1030
1087
|
},
|
|
1031
1088
|
required: ["manifest"],
|
|
1032
1089
|
$defs: {
|
|
@@ -1039,11 +1096,26 @@ export const workflowCallSchema = {
|
|
|
1039
1096
|
type: "object",
|
|
1040
1097
|
additionalProperties: false,
|
|
1041
1098
|
properties: {
|
|
1099
|
+
workflow: { type: "string" },
|
|
1042
1100
|
workflow_id: { type: "string" },
|
|
1043
1101
|
workflow_name: { type: "string" },
|
|
1044
1102
|
input: { type: "object", additionalProperties: true },
|
|
1045
1103
|
mode: { enum: ["dry_run", "live", "smoke_test"] },
|
|
1046
1104
|
idempotency_key: { type: "string" },
|
|
1105
|
+
revision_version: {
|
|
1106
|
+
type: "integer",
|
|
1107
|
+
minimum: 1,
|
|
1108
|
+
description: "Exact saved revision to run. Historical revisions are non-live only.",
|
|
1109
|
+
},
|
|
1110
|
+
approved: {
|
|
1111
|
+
type: "boolean",
|
|
1112
|
+
description: "Explicit approval required for live calls.",
|
|
1113
|
+
},
|
|
1114
|
+
max_credits: {
|
|
1115
|
+
type: "number",
|
|
1116
|
+
exclusiveMinimum: 0,
|
|
1117
|
+
description: "Positive credit ceiling required for live calls.",
|
|
1118
|
+
},
|
|
1047
1119
|
},
|
|
1048
1120
|
required: ["mode"],
|
|
1049
1121
|
};
|
|
@@ -1061,6 +1133,15 @@ export const workflowEventEmitSchema = {
|
|
|
1061
1133
|
external_event_id: { type: "string" },
|
|
1062
1134
|
idempotency_key: { type: "string" },
|
|
1063
1135
|
mode: { enum: ["dry_run", "live", "smoke_test"] },
|
|
1136
|
+
approved: {
|
|
1137
|
+
type: "boolean",
|
|
1138
|
+
description: "Explicit approval required when event deliveries run live.",
|
|
1139
|
+
},
|
|
1140
|
+
max_credits: {
|
|
1141
|
+
type: "number",
|
|
1142
|
+
exclusiveMinimum: 0,
|
|
1143
|
+
description: "Positive per-delivery credit ceiling required for live event emission.",
|
|
1144
|
+
},
|
|
1064
1145
|
},
|
|
1065
1146
|
required: ["source", "event", "payload", "mode"],
|
|
1066
1147
|
};
|
|
@@ -1116,18 +1197,24 @@ export function getWorkflowSchema(subject = "apply") {
|
|
|
1116
1197
|
return schemas[subject];
|
|
1117
1198
|
}
|
|
1118
1199
|
function validateJsonSchemaValueInto(// skipcq: JS-R1005
|
|
1119
|
-
value, schema, path, issues) {
|
|
1200
|
+
value, schema, path, issues, maxIssues, budget) {
|
|
1201
|
+
if (issues.length >= maxIssues || budget.exhausted)
|
|
1202
|
+
return;
|
|
1203
|
+
if (!consumeJsonSchemaValidationBudget(budget))
|
|
1204
|
+
return;
|
|
1120
1205
|
const startCount = issues.length;
|
|
1121
|
-
if (schema.const !== undefined && !jsonEqual(value, schema.const)) {
|
|
1122
|
-
issues
|
|
1206
|
+
if (schema.const !== undefined && !jsonEqual(value, schema.const, budget)) {
|
|
1207
|
+
addJsonSchemaValidationIssue(issues, maxIssues, {
|
|
1123
1208
|
path,
|
|
1124
1209
|
code: "schema_const_mismatch",
|
|
1125
1210
|
message: `${path} must equal the schema const value.`,
|
|
1126
1211
|
});
|
|
1127
1212
|
return;
|
|
1128
1213
|
}
|
|
1129
|
-
if (Array.isArray(schema.enum)
|
|
1130
|
-
|
|
1214
|
+
if (Array.isArray(schema.enum)
|
|
1215
|
+
&& schema.enum.length > 0
|
|
1216
|
+
&& !schema.enum.some((entry) => budget.exhausted || jsonEqual(value, entry, budget))) {
|
|
1217
|
+
addJsonSchemaValidationIssue(issues, maxIssues, {
|
|
1131
1218
|
path,
|
|
1132
1219
|
code: "schema_enum_mismatch",
|
|
1133
1220
|
message: `${path} must be one of the allowed values.`,
|
|
@@ -1136,22 +1223,26 @@ value, schema, path, issues) {
|
|
|
1136
1223
|
}
|
|
1137
1224
|
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
1138
1225
|
const matched = schema.anyOf.some((candidate) => {
|
|
1226
|
+
if (budget.exhausted)
|
|
1227
|
+
return true;
|
|
1139
1228
|
const candidateIssues = [];
|
|
1140
|
-
validateJsonSchemaValueInto(value, candidate, path, candidateIssues);
|
|
1229
|
+
validateJsonSchemaValueInto(value, candidate, path, candidateIssues, 1, budget);
|
|
1141
1230
|
return candidateIssues.length === 0;
|
|
1142
1231
|
});
|
|
1143
1232
|
if (!matched) {
|
|
1144
|
-
issues
|
|
1233
|
+
addJsonSchemaValidationIssue(issues, maxIssues, {
|
|
1145
1234
|
path,
|
|
1146
1235
|
code: "schema_any_of_mismatch",
|
|
1147
1236
|
message: `${path} must match at least one allowed schema.`,
|
|
1148
1237
|
});
|
|
1238
|
+
return;
|
|
1149
1239
|
}
|
|
1150
|
-
|
|
1240
|
+
// A matching anyOf does not erase sibling constraints. JSON Schema applies
|
|
1241
|
+
// them together; returning here silently skipped object/array rules.
|
|
1151
1242
|
}
|
|
1152
1243
|
const expectedTypes = normalizeSchemaTypes(schema);
|
|
1153
1244
|
if (expectedTypes.length > 0 && !expectedTypes.some((type) => matchesJsonSchemaType(value, type))) {
|
|
1154
|
-
issues
|
|
1245
|
+
addJsonSchemaValidationIssue(issues, maxIssues, {
|
|
1155
1246
|
path,
|
|
1156
1247
|
code: "schema_type_mismatch",
|
|
1157
1248
|
message: `${path} must be ${formatSchemaTypes(expectedTypes)}.`,
|
|
@@ -1165,40 +1256,57 @@ value, schema, path, issues) {
|
|
|
1165
1256
|
if (shouldValidateObject && isRecord(value)) {
|
|
1166
1257
|
const properties = schema.properties ?? {};
|
|
1167
1258
|
for (const requiredKey of schema.required ?? []) {
|
|
1259
|
+
if (issues.length >= maxIssues || budget.exhausted)
|
|
1260
|
+
break;
|
|
1261
|
+
if (!consumeJsonSchemaValidationBudget(budget))
|
|
1262
|
+
break;
|
|
1168
1263
|
if (!Object.hasOwn(value, requiredKey) || value[requiredKey] === undefined) {
|
|
1169
|
-
issues
|
|
1264
|
+
addJsonSchemaValidationIssue(issues, maxIssues, {
|
|
1170
1265
|
path: `${path}.${requiredKey}`,
|
|
1171
1266
|
code: "schema_required",
|
|
1172
1267
|
message: `${path}.${requiredKey} is required.`,
|
|
1173
1268
|
});
|
|
1174
1269
|
}
|
|
1175
1270
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1271
|
+
// Object enumeration is real host work. Charge every key before iterating so
|
|
1272
|
+
// a large output tested against many `anyOf` object candidates cannot rescan
|
|
1273
|
+
// the same object hundreds of times outside the sandbox timeout.
|
|
1274
|
+
const valueKeys = Object.keys(value);
|
|
1275
|
+
if (!consumeJsonSchemaValidationBudget(budget, valueKeys.length))
|
|
1276
|
+
return;
|
|
1277
|
+
for (const key of valueKeys) {
|
|
1278
|
+
if (issues.length >= maxIssues || budget.exhausted)
|
|
1279
|
+
break;
|
|
1280
|
+
const propertyValue = value[key];
|
|
1281
|
+
const propertySchema = Object.hasOwn(properties, key) ? properties[key] : undefined;
|
|
1282
|
+
if (isRecord(propertySchema)) {
|
|
1283
|
+
validateJsonSchemaValueInto(propertyValue, propertySchema, `${path}.${key}`, issues, maxIssues, budget);
|
|
1180
1284
|
}
|
|
1181
1285
|
else if (schema.additionalProperties === false) {
|
|
1182
|
-
issues
|
|
1286
|
+
addJsonSchemaValidationIssue(issues, maxIssues, {
|
|
1183
1287
|
path: `${path}.${key}`,
|
|
1184
1288
|
code: "schema_additional_property",
|
|
1185
1289
|
message: `${path}.${key} is not allowed.`,
|
|
1186
1290
|
});
|
|
1187
1291
|
}
|
|
1188
1292
|
else if (isRecord(schema.additionalProperties)) {
|
|
1189
|
-
validateJsonSchemaValueInto(propertyValue, schema.additionalProperties, `${path}.${key}`, issues);
|
|
1293
|
+
validateJsonSchemaValueInto(propertyValue, schema.additionalProperties, `${path}.${key}`, issues, maxIssues, budget);
|
|
1190
1294
|
}
|
|
1191
1295
|
}
|
|
1192
1296
|
}
|
|
1193
1297
|
const shouldValidateArray = expectedTypes.includes("array") || Boolean(schema.items);
|
|
1194
1298
|
if (shouldValidateArray && Array.isArray(value) && schema.items) {
|
|
1195
|
-
value.
|
|
1196
|
-
validateJsonSchemaValueInto(
|
|
1197
|
-
}
|
|
1299
|
+
for (let index = 0; index < value.length && issues.length < maxIssues && !budget.exhausted; index += 1) {
|
|
1300
|
+
validateJsonSchemaValueInto(value[index], schema.items, `${path}.${index}`, issues, maxIssues, budget);
|
|
1301
|
+
}
|
|
1198
1302
|
}
|
|
1199
1303
|
if (issues.length > startCount)
|
|
1200
1304
|
return;
|
|
1201
1305
|
}
|
|
1306
|
+
function addJsonSchemaValidationIssue(issues, maxIssues, issue) {
|
|
1307
|
+
if (issues.length < maxIssues)
|
|
1308
|
+
issues.push(issue);
|
|
1309
|
+
}
|
|
1202
1310
|
function normalizeSchemaTypes(schema) {
|
|
1203
1311
|
if (Array.isArray(schema.type))
|
|
1204
1312
|
return schema.type.filter((entry) => typeof entry === "string");
|
|
@@ -1235,8 +1343,43 @@ function formatSchemaTypes(types) {
|
|
|
1235
1343
|
return `a ${types[0]}`;
|
|
1236
1344
|
return types.map((type) => type === "null" ? "null" : `a ${type}`).join(" or ");
|
|
1237
1345
|
}
|
|
1238
|
-
function jsonEqual(left, right) {
|
|
1239
|
-
return
|
|
1346
|
+
function jsonEqual(left, right, budget) {
|
|
1347
|
+
return jsonEqualInto(left, right, new WeakMap(), budget);
|
|
1348
|
+
}
|
|
1349
|
+
function jsonEqualInto(left, right, seen, budget) {
|
|
1350
|
+
if (!consumeJsonSchemaValidationBudget(budget))
|
|
1351
|
+
return false;
|
|
1352
|
+
if (left === right)
|
|
1353
|
+
return true;
|
|
1354
|
+
if (left === null || right === null || typeof left !== typeof right)
|
|
1355
|
+
return false;
|
|
1356
|
+
if (typeof left !== "object" || typeof right !== "object")
|
|
1357
|
+
return false;
|
|
1358
|
+
const leftArray = Array.isArray(left);
|
|
1359
|
+
if (leftArray !== Array.isArray(right))
|
|
1360
|
+
return false;
|
|
1361
|
+
const compared = seen.get(left);
|
|
1362
|
+
if (compared?.has(right))
|
|
1363
|
+
return true;
|
|
1364
|
+
if (compared)
|
|
1365
|
+
compared.add(right);
|
|
1366
|
+
else
|
|
1367
|
+
seen.set(left, new WeakSet([right]));
|
|
1368
|
+
if (leftArray) {
|
|
1369
|
+
const rightArray = right;
|
|
1370
|
+
return left.length === rightArray.length
|
|
1371
|
+
&& left.every((entry, index) => !budget.exhausted && jsonEqualInto(entry, rightArray[index], seen, budget));
|
|
1372
|
+
}
|
|
1373
|
+
const leftRecord = left;
|
|
1374
|
+
const rightRecord = right;
|
|
1375
|
+
const leftKeys = Object.keys(leftRecord);
|
|
1376
|
+
const rightKeys = Object.keys(rightRecord);
|
|
1377
|
+
if (!consumeJsonSchemaValidationBudget(budget, leftKeys.length + rightKeys.length))
|
|
1378
|
+
return false;
|
|
1379
|
+
return leftKeys.length === rightKeys.length
|
|
1380
|
+
&& leftKeys.every((key) => Object.hasOwn(rightRecord, key)
|
|
1381
|
+
&& !budget.exhausted
|
|
1382
|
+
&& jsonEqualInto(leftRecord[key], rightRecord[key], seen, budget));
|
|
1240
1383
|
}
|
|
1241
1384
|
// Optional idempotency_key_path (webhook + event triggers): when present it must
|
|
1242
1385
|
// be a non-empty string. `label` distinguishes the trigger type in the message.
|
|
@@ -111,7 +111,9 @@ function nodeUsesWorkspaceAsset(node) {
|
|
|
111
111
|
return (node.reason !== undefined && valueRefUsesWorkspaceAsset(node.reason))
|
|
112
112
|
|| Object.values(node.request ?? {}).some(valueRefUsesWorkspaceAsset);
|
|
113
113
|
case "code":
|
|
114
|
-
return
|
|
114
|
+
return node.code !== undefined
|
|
115
|
+
? Object.values(node.code.inputs).some(valueRefUsesWorkspaceAsset)
|
|
116
|
+
: Object.values(node.configuration ?? {}).some(valueRefUsesWorkspaceAsset);
|
|
115
117
|
case "workflow":
|
|
116
118
|
return Object.values(node.input).some(valueRefUsesWorkspaceAsset);
|
|
117
119
|
case "trigger":
|