@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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +27 -12
  3. package/dist/index.js +243 -72
  4. package/dist/skills.js +2 -2
  5. package/node_modules/@oxygen/shared/dist/axiom-field-budget.d.ts +99 -0
  6. package/node_modules/@oxygen/shared/dist/axiom-field-budget.js +112 -17
  7. package/node_modules/@oxygen/shared/dist/cli-result.js +2 -0
  8. package/node_modules/@oxygen/shared/dist/crm-activity-events.d.ts +20 -0
  9. package/node_modules/@oxygen/shared/dist/crm-activity-events.js +5 -0
  10. package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +27 -0
  11. package/node_modules/@oxygen/shared/dist/future-signup-events.js +90 -0
  12. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +320 -0
  13. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1233 -0
  14. package/node_modules/@oxygen/shared/dist/index.d.ts +7 -1
  15. package/node_modules/@oxygen/shared/dist/index.js +14 -1
  16. package/node_modules/@oxygen/shared/dist/notetaker-events.d.ts +4 -0
  17. package/node_modules/@oxygen/shared/dist/notetaker-events.js +23 -0
  18. package/node_modules/@oxygen/shared/dist/plain-support-events.d.ts +40 -0
  19. package/node_modules/@oxygen/shared/dist/plain-support-events.js +43 -0
  20. package/node_modules/@oxygen/shared/dist/redaction.js +6 -1
  21. package/node_modules/@oxygen/shared/dist/user-capability-routing.d.ts +2 -0
  22. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +23 -0
  23. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  24. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  25. package/node_modules/@oxygen/shared/dist/webhook-headers.d.ts +10 -0
  26. package/node_modules/@oxygen/shared/dist/webhook-headers.js +48 -0
  27. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.d.ts +31 -1
  28. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +112 -1
  29. package/node_modules/@oxygen/workflows/dist/graph/expression.js +7 -5
  30. package/node_modules/@oxygen/workflows/dist/graph/lint.d.ts +9 -0
  31. package/node_modules/@oxygen/workflows/dist/graph/lint.js +409 -7
  32. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +513 -0
  33. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +104 -1
  34. package/node_modules/@oxygen/workflows/dist/graph/params.d.ts +2 -0
  35. package/node_modules/@oxygen/workflows/dist/graph/params.js +15 -0
  36. package/node_modules/@oxygen/workflows/dist/graph/remap.js +12 -3
  37. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +9 -0
  38. package/node_modules/@oxygen/workflows/dist/graph/topology.js +33 -1
  39. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +123 -16
  40. package/node_modules/@oxygen/workflows/dist/graph/types.js +45 -0
  41. package/node_modules/@oxygen/workflows/dist/index.d.ts +94 -2
  42. package/node_modules/@oxygen/workflows/dist/index.js +169 -26
  43. package/node_modules/@oxygen/workflows/dist/portable.js +3 -1
  44. 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
- validateJsonSchemaValueInto(value, schema, path, issues);
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(10, Math.min(input.timeoutMs ?? 1_000, 10_000));
935
- const maxOutputBytes = Math.max(1_000, Math.min(input.maxOutputBytes ?? 250_000, 2_000_000));
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.push({
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) && schema.enum.length > 0 && !schema.enum.some((entry) => jsonEqual(value, entry))) {
1130
- issues.push({
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.push({
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
- return;
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.push({
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.push({
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
- for (const [key, propertyValue] of Object.entries(value)) {
1177
- const propertySchema = properties[key];
1178
- if (propertySchema) {
1179
- validateJsonSchemaValueInto(propertyValue, propertySchema, `${path}.${key}`, issues);
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.push({
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.forEach((entry, index) => {
1196
- validateJsonSchemaValueInto(entry, schema.items, `${path}.${index}`, issues);
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 JSON.stringify(left) === JSON.stringify(right);
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 Object.values(node.configuration ?? {}).some(valueRefUsesWorkspaceAsset);
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":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.717.11",
3
+ "version": "1.739.0",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",