@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.
- package/README.md +1 -1
- package/dist/command-manifest.d.ts +18 -0
- package/dist/command-manifest.js +106 -5
- package/dist/help.js +3 -1
- package/dist/index.js +722 -138
- package/dist/skills.d.ts +6 -0
- package/dist/skills.js +200 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +766 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +4 -3
- package/node_modules/@oxygen/shared/dist/file-import.js +58 -11
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/index.js +2 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/recipes.js +4 -2
- package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
- package/node_modules/@oxygen/shared/dist/sequence-terminal-events.d.ts +47 -0
- package/node_modules/@oxygen/shared/dist/sequence-terminal-events.js +70 -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/workflows/dist/graph/lint.js +3 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +5 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +5 -0
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -0
- package/node_modules/@oxygen/workflows/dist/index.d.ts +4 -0
- package/node_modules/@oxygen/workflows/dist/index.js +5 -0
- 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
|
|
30
|
-
//
|
|
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
|
|
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
|
-
.
|
|
70
|
-
.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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 = [
|
|
Binary file
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
export const OXYGEN_VERSION = "1.
|
|
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
|
}
|