@oxygen-agent/cli 1.575.19 → 1.610.7

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 (29) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.d.ts +18 -0
  3. package/dist/command-manifest.js +106 -5
  4. package/dist/help.js +3 -1
  5. package/dist/index.js +722 -138
  6. package/dist/skills.d.ts +6 -0
  7. package/dist/skills.js +200 -1
  8. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +36 -0
  9. package/node_modules/@oxygen/shared/dist/capability-discovery.js +766 -0
  10. package/node_modules/@oxygen/shared/dist/cli-result.js +4 -3
  11. package/node_modules/@oxygen/shared/dist/file-import.js +58 -11
  12. package/node_modules/@oxygen/shared/dist/index.d.ts +2 -0
  13. package/node_modules/@oxygen/shared/dist/index.js +2 -0
  14. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  15. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  16. package/node_modules/@oxygen/shared/dist/recipes.d.ts +1 -1
  17. package/node_modules/@oxygen/shared/dist/recipes.js +4 -2
  18. package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
  19. package/node_modules/@oxygen/shared/dist/sequence-terminal-events.d.ts +47 -0
  20. package/node_modules/@oxygen/shared/dist/sequence-terminal-events.js +70 -0
  21. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  22. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  23. package/node_modules/@oxygen/workflows/dist/graph/lint.js +3 -0
  24. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +5 -0
  25. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +5 -0
  26. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -0
  27. package/node_modules/@oxygen/workflows/dist/index.d.ts +4 -0
  28. package/node_modules/@oxygen/workflows/dist/index.js +5 -0
  29. package/package.json +1 -1
@@ -26,8 +26,8 @@ export class OxygenError extends Error {
26
26
  // 0 ok · 1 unclassified error · 2 usage / input validation · 3 auth or CLI
27
27
  // compatibility (not_authenticated, cli_update_required) · 4 not found ·
28
28
  // 5 provider / server failure · 6 rate limit (details.retry_after_seconds
29
- // says how long to wait) · 7 approval / spend gate (re-run with --approved
30
- // --max-credits <n>) · 8 timeout.
29
+ // says how long to wait) · 7 approval / spend gate / subscription recovery
30
+ // (follow error.details.next_step) · 8 timeout.
31
31
  export const CLI_EXIT_CODE_TABLE = {
32
32
  0: "ok",
33
33
  1: "unclassified error",
@@ -36,7 +36,7 @@ export const CLI_EXIT_CODE_TABLE = {
36
36
  4: "not found",
37
37
  5: "provider or server failure",
38
38
  6: "rate limited (wait details.retry_after_seconds, then retry)",
39
- 7: "approval / spend gate (re-run with --approved --max-credits <n>)",
39
+ 7: "approval / spend gate / subscription recovery (follow error.details.next_step)",
40
40
  8: "timeout",
41
41
  };
42
42
  export function exitCodeForErrorCode(code) {
@@ -56,6 +56,7 @@ export function exitCodeForErrorCode(code) {
56
56
  case "insufficient_credits":
57
57
  case "workspace_credit_cap_exceeded":
58
58
  case "byok_requires_paid_plan":
59
+ case "subscription_access_required":
59
60
  return 7;
60
61
  case "network_timeout":
61
62
  return 8;
@@ -66,8 +66,9 @@ export function parseRowsText(text, format) {
66
66
  if (format === "jsonl") {
67
67
  return normalizeRowObjects(text
68
68
  .split(/\r?\n/)
69
- .filter((line) => line.trim())
70
- .map((line) => JSON.parse(line)));
69
+ .map((line, index) => ({ line, lineNumber: index + 1 }))
70
+ .filter(({ line }) => line.trim())
71
+ .map(({ line, lineNumber }) => parseJsonImportLine(line, lineNumber)));
71
72
  }
72
73
  return parseCsvRows(text);
73
74
  }
@@ -162,12 +163,26 @@ function normalizeXlsxImportCell(value) {
162
163
  return String(value);
163
164
  }
164
165
  function parseJsonArray(text) {
165
- const parsed = JSON.parse(text);
166
+ let parsed;
167
+ try {
168
+ parsed = JSON.parse(text);
169
+ }
170
+ catch {
171
+ throw new OxygenError("invalid_json", "Import file must contain valid JSON. Input contents are omitted from this error.", { exitCode: 1 });
172
+ }
166
173
  if (!Array.isArray(parsed)) {
167
174
  throw new OxygenError("invalid_json", "Expected a JSON array.", { exitCode: 1 });
168
175
  }
169
176
  return parsed;
170
177
  }
178
+ function parseJsonImportLine(line, lineNumber) {
179
+ try {
180
+ return JSON.parse(line);
181
+ }
182
+ catch {
183
+ throw new OxygenError("invalid_jsonl", `Import file contains invalid JSON on line ${lineNumber}. Input contents are omitted from this error.`, { exitCode: 1 });
184
+ }
185
+ }
171
186
  function normalizeRowObjects(rows) {
172
187
  return rows.map((row) => normalizeRowObject(row));
173
188
  }
@@ -182,6 +197,7 @@ function parseCsvRows(text) {
182
197
  const [header, ...rows] = records;
183
198
  if (!header || header.length === 0)
184
199
  return [];
200
+ assertUniqueCsvHeaders(header);
185
201
  return rows
186
202
  .filter((row) => row.some((cell) => cell.trim()))
187
203
  .map((row) => Object.fromEntries(header.map((key, index) => [key, normalizeCsvImportCell(row[index])])));
@@ -209,6 +225,8 @@ function* iterateCsvRowBatches(text, batchSize) {
209
225
  if (ready)
210
226
  yield ready;
211
227
  }
228
+ if (state.inQuotes)
229
+ throw unterminatedCsvError();
212
230
  if (state.field || state.record.length > 0) {
213
231
  const ready = appendCsvRecordToBatch(state, finishCsvRecord(state), batchSize);
214
232
  if (ready)
@@ -249,6 +267,8 @@ async function* iterateCsvRowStreamBatches(chunks, batchSize) {
249
267
  if (ready.batch)
250
268
  yield ready.batch;
251
269
  }
270
+ if (state.inQuotes)
271
+ throw unterminatedCsvError();
252
272
  if (state.field || state.record.length > 0) {
253
273
  const ready = appendCsvRecordToBatch(state, finishCsvRecord(state), batchSize);
254
274
  if (ready)
@@ -313,8 +333,10 @@ function finishCsvRecord(state) {
313
333
  }
314
334
  function appendCsvRecordToBatch(state, record, batchSize) {
315
335
  const row = csvRecordToRow(record, state.header);
316
- if (state.header === null)
336
+ if (state.header === null) {
337
+ assertUniqueCsvHeaders(record);
317
338
  state.header = record;
339
+ }
318
340
  if (row)
319
341
  state.batch.push(row);
320
342
  if (state.batch.length < batchSize)
@@ -333,22 +355,27 @@ function csvRecordToRow(record, header) {
333
355
  function* iterateJsonlRowBatches(text, batchSize) {
334
356
  let batch = [];
335
357
  let lineStart = 0;
358
+ let lineNumber = 1;
336
359
  for (let index = 0; index < text.length; index += 1) {
337
360
  if (text.charAt(index) !== "\n")
338
361
  continue;
339
362
  const line = text.slice(lineStart, index).replace(/\r$/, "").trim();
340
363
  lineStart = index + 1;
364
+ if (line) {
365
+ batch.push(normalizeRowObject(parseJsonImportLine(line, lineNumber)));
366
+ }
367
+ lineNumber += 1;
341
368
  if (!line)
342
369
  continue;
343
- batch.push(normalizeRowObject(JSON.parse(line)));
344
370
  if (batch.length >= batchSize) {
345
371
  yield batch;
346
372
  batch = [];
347
373
  }
348
374
  }
349
375
  const trailingLine = text.slice(lineStart).replace(/\r$/, "").trim();
350
- if (trailingLine)
351
- batch.push(normalizeRowObject(JSON.parse(trailingLine)));
376
+ if (trailingLine) {
377
+ batch.push(normalizeRowObject(parseJsonImportLine(trailingLine, lineNumber)));
378
+ }
352
379
  if (batch.length > 0)
353
380
  yield batch;
354
381
  }
@@ -356,14 +383,17 @@ async function* iterateJsonlRowStreamBatches(chunks, batchSize) {
356
383
  const decoder = new TextDecoder();
357
384
  let pending = "";
358
385
  let batch = [];
386
+ let lineNumber = 1;
359
387
  for await (const chunk of chunks) {
360
388
  pending += decoder.decode(toUint8Array(chunk), { stream: true });
361
389
  let newline = pending.indexOf("\n");
362
390
  while (newline >= 0) {
363
391
  const line = pending.slice(0, newline).replace(/\r$/, "").trim();
364
392
  pending = pending.slice(newline + 1);
365
- if (line)
366
- batch.push(normalizeRowObject(JSON.parse(line)));
393
+ if (line) {
394
+ batch.push(normalizeRowObject(parseJsonImportLine(line, lineNumber)));
395
+ }
396
+ lineNumber += 1;
367
397
  if (batch.length >= batchSize) {
368
398
  yield batch;
369
399
  batch = [];
@@ -372,8 +402,9 @@ async function* iterateJsonlRowStreamBatches(chunks, batchSize) {
372
402
  }
373
403
  }
374
404
  const tail = (pending + decoder.decode()).replace(/\r$/, "").trim();
375
- if (tail)
376
- batch.push(normalizeRowObject(JSON.parse(tail)));
405
+ if (tail) {
406
+ batch.push(normalizeRowObject(parseJsonImportLine(tail, lineNumber)));
407
+ }
377
408
  if (batch.length > 0)
378
409
  yield batch;
379
410
  }
@@ -459,12 +490,28 @@ function parseCsvRecords(text) {
459
490
  field += char;
460
491
  }
461
492
  }
493
+ if (inQuotes) {
494
+ throw unterminatedCsvError();
495
+ }
462
496
  if (field || record.length > 0) {
463
497
  record.push(field);
464
498
  records.push(record);
465
499
  }
466
500
  return records;
467
501
  }
502
+ function unterminatedCsvError() {
503
+ return new OxygenError("invalid_csv", "CSV contains an unterminated quoted field.", { exitCode: 1 });
504
+ }
505
+ function assertUniqueCsvHeaders(header) {
506
+ const seen = new Set();
507
+ for (const value of header) {
508
+ const normalized = value.normalize("NFKC").trim().toLowerCase();
509
+ if (!normalized || seen.has(normalized)) {
510
+ throw new OxygenError("invalid_csv", "CSV headers must be non-empty and unique (case-insensitive).", { exitCode: 1 });
511
+ }
512
+ seen.add(normalized);
513
+ }
514
+ }
468
515
  function makeUniqueImportKey(label, taken) {
469
516
  return makeUniqueIdentifier(label, taken, {
470
517
  exhaustedMessage: "Unable to create a unique column key.",
@@ -4,6 +4,7 @@ export { WORKFLOW_STATUS_CHANGE_METADATA_KEY, type WorkflowStatusChange, type Wo
4
4
  export * from "./billing.js";
5
5
  export * from "./billing-anchors.js";
6
6
  export * from "./budget-scopes.js";
7
+ export * from "./capability-discovery.js";
7
8
  export * from "./plan-limits.js";
8
9
  export * from "./spend-safety.js";
9
10
  export * from "./cell-format.js";
@@ -35,6 +36,7 @@ export * from "./networks.js";
35
36
  export * from "./recipes.js";
36
37
  export * from "./sequence-template.js";
37
38
  export * from "./sequence-crm-events.js";
39
+ export * from "./sequence-terminal-events.js";
38
40
  export * from "./call-outcomes.js";
39
41
  export * from "./dial-guardrail-overrides.js";
40
42
  export * from "./sequences.js";
@@ -4,6 +4,7 @@ export { WORKFLOW_STATUS_CHANGE_METADATA_KEY, describeWorkflowStatusChange, form
4
4
  export * from "./billing.js";
5
5
  export * from "./billing-anchors.js";
6
6
  export * from "./budget-scopes.js";
7
+ export * from "./capability-discovery.js";
7
8
  export * from "./plan-limits.js";
8
9
  export * from "./spend-safety.js";
9
10
  export * from "./cell-format.js";
@@ -35,6 +36,7 @@ export * from "./networks.js";
35
36
  export * from "./recipes.js";
36
37
  export * from "./sequence-template.js";
37
38
  export * from "./sequence-crm-events.js";
39
+ export * from "./sequence-terminal-events.js";
38
40
  export * from "./call-outcomes.js";
39
41
  export * from "./dial-guardrail-overrides.js";
40
42
  export * from "./sequences.js";
@@ -142,7 +142,7 @@ export declare const ENRICHMENT_CREDITS: {
142
142
  readonly company_enrich_typical: 99;
143
143
  readonly web_search: 5;
144
144
  readonly page_scrape: 40;
145
- readonly linkedin_profile_scrape: 10;
145
+ readonly linkedin_profile_scrape: 25.5;
146
146
  };
147
147
  /** Editable calculator default: blended credits per fully-enriched row. */
148
148
  export declare const ENRICHMENT_CREDITS_PER_ROW_TYPICAL = 100;
@@ -134,7 +134,7 @@ export const ENRICHMENT_CREDITS = {
134
134
  company_enrich_typical: 99,
135
135
  web_search: 5, // Serper
136
136
  page_scrape: 40, // Firecrawl (wedge price)
137
- linkedin_profile_scrape: 10, // HarvestAPI, per result
137
+ linkedin_profile_scrape: 25.5, // HarvestAPI Basic full-profile job, exactly 5x COGS
138
138
  };
139
139
  /** Editable calculator default: blended credits per fully-enriched row. */
140
140
  export const ENRICHMENT_CREDITS_PER_ROW_TYPICAL = 100;
@@ -9,7 +9,7 @@ export declare const RECIPE_STATUSES: readonly ["draft", "beta", "stable", "depr
9
9
  export type RecipeStatus = typeof RECIPE_STATUSES[number];
10
10
  export declare const RECIPE_BENCHMARK_BASES: readonly ["estimate", "observed"];
11
11
  export type RecipeBenchmarkBasis = typeof RECIPE_BENCHMARK_BASES[number];
12
- export declare const RECIPE_PRIMITIVES: readonly ["knowledge-graph", "recipes", "records", "tables", "messages", "posts", "signals", "dashboards", "ads", "sequences", "publishing", "workflows", "observability"];
12
+ export declare const RECIPE_PRIMITIVES: readonly ["knowledge-graph", "recipes", "records", "tables", "messages", "posts", "signals", "dashboards", "ads", "tags", "sequences", "publishing", "workflows", "agents", "observability"];
13
13
  export type RecipePrimitive = typeof RECIPE_PRIMITIVES[number];
14
14
  export declare const RECIPE_PREREQUISITE_KINDS: readonly ["sender", "mailbox", "domain", "integration", "knowledge", "asset", "plan", "data"];
15
15
  export type RecipePrerequisiteKind = typeof RECIPE_PREREQUISITE_KINDS[number];
@@ -37,8 +37,8 @@ export const RECIPE_STATUSES = ["draft", "beta", "stable", "deprecated"];
37
37
  // Oxygen cohort data (with provenance). Estimates are the launch posture;
38
38
  // observed values replace them as real usage accrues.
39
39
  export const RECIPE_BENCHMARK_BASES = ["estimate", "observed"];
40
- // The thirteen product primitives a recipe may exercise (five-layer model,
41
- // ADR 0006/0008). Control surfaces and External integrations are not
40
+ // The fifteen product primitives a recipe may exercise (five-layer model,
41
+ // ADR 0006/0008/0011/0012). Control surfaces and External integrations are not
42
42
  // primitives and never appear here.
43
43
  export const RECIPE_PRIMITIVES = [
44
44
  "knowledge-graph",
@@ -50,9 +50,11 @@ export const RECIPE_PRIMITIVES = [
50
50
  "signals",
51
51
  "dashboards",
52
52
  "ads",
53
+ "tags",
53
54
  "sequences",
54
55
  "publishing",
55
56
  "workflows",
57
+ "agents",
56
58
  "observability",
57
59
  ];
58
60
  export const RECIPE_PREREQUISITE_KINDS = [
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Workflow-event contract emitted when one sequence enrollment becomes terminal.
3
+ *
4
+ * A terminal outcome (reply, bounce, failure, timeout) is not necessarily a
5
+ * literal node in the sequence graph. `endpoint_*` is therefore nullable and is
6
+ * populated only when the runtime reaches a visible graph endpoint: the trunk
7
+ * end, a branch-arm end, or an explicit stop step.
8
+ */
9
+ export declare const SEQUENCE_TERMINAL_EVENT_SOURCE = "sequencer";
10
+ export declare const SEQUENCE_ENROLLMENT_TERMINAL_EVENT = "enrollment_terminal";
11
+ export declare const SEQUENCE_TERMINAL_STATUSES: readonly ["completed", "replied", "stopped", "failed", "excluded"];
12
+ export type SequenceTerminalStatus = (typeof SEQUENCE_TERMINAL_STATUSES)[number];
13
+ export declare const SEQUENCE_TERMINAL_ENDPOINT_KINDS: readonly ["end", "branch_end", "stop"];
14
+ export type SequenceTerminalEndpointKind = (typeof SEQUENCE_TERMINAL_ENDPOINT_KINDS)[number];
15
+ export type SequenceTerminalBranchArm = "then" | "else";
16
+ export type SequenceTerminalEndpoint = {
17
+ id: string;
18
+ kind: SequenceTerminalEndpointKind;
19
+ stepId: string | null;
20
+ branchId: string | null;
21
+ branchArm: SequenceTerminalBranchArm | null;
22
+ };
23
+ export type SequenceEnrollmentTerminalPayload = {
24
+ occurred_at: string;
25
+ sequence_id: string;
26
+ enrollment_id: string;
27
+ status: SequenceTerminalStatus;
28
+ reason: string | null;
29
+ endpoint_id: string | null;
30
+ endpoint_kind: SequenceTerminalEndpointKind | null;
31
+ step_id: string | null;
32
+ branch_id: string | null;
33
+ branch_arm: SequenceTerminalBranchArm | null;
34
+ lead_name: string | null;
35
+ email: string | null;
36
+ linkedin_url: string | null;
37
+ };
38
+ export declare function sequenceEndEndpoint(): SequenceTerminalEndpoint;
39
+ export declare function sequenceBranchEndEndpoint(branchId: string, branchArm: SequenceTerminalBranchArm): SequenceTerminalEndpoint;
40
+ export declare function sequenceStopEndpoint(stepId: string): SequenceTerminalEndpoint;
41
+ export declare function readSequenceTerminalEndpoint(value: unknown): SequenceTerminalEndpoint | null;
42
+ export declare function sequenceTerminalEventIdempotencyKey(input: {
43
+ enrollmentId: string;
44
+ status: SequenceTerminalStatus;
45
+ endpointId?: string | null;
46
+ reason?: string | null;
47
+ }): string;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Workflow-event contract emitted when one sequence enrollment becomes terminal.
3
+ *
4
+ * A terminal outcome (reply, bounce, failure, timeout) is not necessarily a
5
+ * literal node in the sequence graph. `endpoint_*` is therefore nullable and is
6
+ * populated only when the runtime reaches a visible graph endpoint: the trunk
7
+ * end, a branch-arm end, or an explicit stop step.
8
+ */
9
+ export const SEQUENCE_TERMINAL_EVENT_SOURCE = "sequencer";
10
+ export const SEQUENCE_ENROLLMENT_TERMINAL_EVENT = "enrollment_terminal";
11
+ export const SEQUENCE_TERMINAL_STATUSES = [
12
+ "completed",
13
+ "replied",
14
+ "stopped",
15
+ "failed",
16
+ "excluded",
17
+ ];
18
+ export const SEQUENCE_TERMINAL_ENDPOINT_KINDS = ["end", "branch_end", "stop"];
19
+ export function sequenceEndEndpoint() {
20
+ return {
21
+ id: "end",
22
+ kind: "end",
23
+ stepId: null,
24
+ branchId: null,
25
+ branchArm: null,
26
+ };
27
+ }
28
+ export function sequenceBranchEndEndpoint(branchId, branchArm) {
29
+ return {
30
+ id: `end:${branchId}:${branchArm}`,
31
+ kind: "branch_end",
32
+ stepId: null,
33
+ branchId,
34
+ branchArm,
35
+ };
36
+ }
37
+ export function sequenceStopEndpoint(stepId) {
38
+ return {
39
+ id: stepId,
40
+ kind: "stop",
41
+ stepId,
42
+ branchId: null,
43
+ branchArm: null,
44
+ };
45
+ }
46
+ export function readSequenceTerminalEndpoint(value) {
47
+ if (!value || typeof value !== "object" || Array.isArray(value))
48
+ return null;
49
+ const row = value;
50
+ const id = typeof row.id === "string" ? row.id.trim() : "";
51
+ const kind = row.kind;
52
+ if (!id || !SEQUENCE_TERMINAL_ENDPOINT_KINDS.includes(kind))
53
+ return null;
54
+ const branchArm = row.branchArm === "then" || row.branchArm === "else" ? row.branchArm : null;
55
+ return {
56
+ id,
57
+ kind: kind,
58
+ stepId: typeof row.stepId === "string" && row.stepId.trim() ? row.stepId.trim() : null,
59
+ branchId: typeof row.branchId === "string" && row.branchId.trim() ? row.branchId.trim() : null,
60
+ branchArm,
61
+ };
62
+ }
63
+ export function sequenceTerminalEventIdempotencyKey(input) {
64
+ return [
65
+ "sequence-terminal",
66
+ input.enrollmentId,
67
+ input.status,
68
+ input.endpointId ?? input.reason ?? "terminal",
69
+ ].join(":");
70
+ }
@@ -1,3 +1,3 @@
1
- export declare const OXYGEN_VERSION = "1.575.19";
1
+ export declare const OXYGEN_VERSION = "1.610.7";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
3
  export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.575.19";
1
+ export const OXYGEN_VERSION = "1.610.7";
2
2
  // The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
3
3
  // operational route. Raising it hard-rejects every older CLI from the entire
4
4
  // product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
@@ -335,6 +335,9 @@ function lintToolNode(node, path, add, options) {
335
335
  if (node.mode !== undefined && (typeof node.mode !== "string" || !STEP_MODES.has(node.mode))) {
336
336
  add(`${path}.mode`, "invalid_step_mode", "Node mode is invalid.");
337
337
  }
338
+ if (node.connection_id !== undefined && !isNonEmptyString(node.connection_id)) {
339
+ add(`${path}.connection_id`, "invalid_connection_id", "Tool node connection_id must be a non-empty string.");
340
+ }
338
341
  validateOptionalMaxCredits(node.max_credits, `${path}.max_credits`, add);
339
342
  validateValueRefRecord(node.params, `${path}.params`, add);
340
343
  }
@@ -108,6 +108,11 @@ export declare const workflowGraphManifestSchema: {
108
108
  readonly mode: {
109
109
  readonly enum: readonly ["dry_run", "live", "smoke_test"];
110
110
  };
111
+ readonly connection_id: {
112
+ readonly type: "string";
113
+ readonly minLength: 1;
114
+ readonly description: "tool nodes: optional active organization connection id to pin for execution.";
115
+ };
111
116
  readonly max_credits: {
112
117
  readonly type: "number";
113
118
  readonly exclusiveMinimum: 0;
@@ -111,6 +111,11 @@ const nodeSchema = {
111
111
  description: "tool nodes: must equal the tool's canonical effect.",
112
112
  },
113
113
  mode: { enum: ["dry_run", "live", "smoke_test"] },
114
+ connection_id: {
115
+ type: "string",
116
+ minLength: 1,
117
+ description: "tool nodes: optional active organization connection id to pin for execution.",
118
+ },
114
119
  max_credits: { type: "number", exclusiveMinimum: 0, description: "Per-node managed-credit ceiling." },
115
120
  params: {
116
121
  type: "object",
@@ -139,6 +139,8 @@ export type WorkflowGraphNode =
139
139
  tool: string;
140
140
  effect: WorkflowStepEffect;
141
141
  mode?: WorkflowMode;
142
+ /** Pin this invocation to one active organization integration connection. */
143
+ connection_id?: string;
142
144
  /** Per-node managed-credit ceiling, same semantics as v1 tool steps. */
143
145
  max_credits?: number;
144
146
  params: Record<string, WorkflowValueRef>;
@@ -74,6 +74,8 @@ export type WorkflowToolStepManifest = {
74
74
  tool: string;
75
75
  effect: WorkflowStepEffect;
76
76
  mode?: WorkflowMode;
77
+ /** Pin this invocation to one active organization integration connection. */
78
+ connection_id?: string;
77
79
  payload_source: string;
78
80
  max_credits?: number;
79
81
  };
@@ -249,6 +251,7 @@ export type WorkflowToolStepDefinition = {
249
251
  tool: string;
250
252
  effect?: WorkflowStepEffect;
251
253
  mode?: WorkflowMode;
254
+ connectionId?: string;
252
255
  maxCredits?: number;
253
256
  payload: WorkflowFunction;
254
257
  };
@@ -341,6 +344,7 @@ export declare function toolStep(input: {
341
344
  tool: string;
342
345
  effect?: WorkflowStepEffect;
343
346
  mode?: WorkflowMode;
347
+ connectionId?: string;
344
348
  maxCredits?: number;
345
349
  payload: WorkflowFunction;
346
350
  }): WorkflowToolStepDefinition;
@@ -166,6 +166,7 @@ export function toolStep(input) {
166
166
  tool: input.tool,
167
167
  ...(input.effect ? { effect: input.effect } : {}),
168
168
  ...(input.mode ? { mode: input.mode } : {}),
169
+ ...(input.connectionId !== undefined ? { connectionId: input.connectionId } : {}),
169
170
  ...(input.maxCredits != null ? { maxCredits: input.maxCredits } : {}),
170
171
  payload: input.payload,
171
172
  };
@@ -260,6 +261,7 @@ definition, options = {}) {
260
261
  tool: step.tool,
261
262
  effect: step.effect ?? "external_read",
262
263
  ...(step.mode ? { mode: step.mode } : {}),
264
+ ...(step.connectionId !== undefined ? { connection_id: step.connectionId } : {}),
263
265
  ...(step.maxCredits != null ? { max_credits: step.maxCredits } : {}),
264
266
  payload_source: serializeWorkflowFunction(step.payload, `steps.${step.id}.payload`),
265
267
  };
@@ -440,6 +442,9 @@ value, options = {}) {
440
442
  && step.mode !== "smoke_test") {
441
443
  add(`${path}.mode`, "invalid_step_mode", "Step mode is invalid.");
442
444
  }
445
+ if (step.connection_id !== undefined && !isNonEmptyString(step.connection_id)) {
446
+ add(`${path}.connection_id`, "invalid_connection_id", "Tool step connection_id must be a non-empty string.");
447
+ }
443
448
  validateOptionalMaxCredits(step.max_credits, `${path}.max_credits`, add);
444
449
  return;
445
450
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.575.19",
3
+ "version": "1.610.7",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",