@oxygen-agent/cli 1.142.4 → 1.152.15

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.
@@ -1,12 +1,15 @@
1
1
  export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
2
+ export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
2
3
  export * from "./billing.js";
3
4
  export * from "./cell-format.js";
4
5
  export * from "./column-types.js";
5
6
  export * from "./credit-guidance.js";
7
+ export * from "./linkedin-sequences.js";
6
8
  export * from "./log.js";
7
9
  export * from "./provider-request-outcomes.js";
8
10
  export * from "./signup-lead-deliveries.js";
9
11
  export * from "./telemetry.js";
12
+ export declare const MAX_ROW_LOOP_WRITE_ROWS = 500;
10
13
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
11
14
  [key: string]: JsonValue;
12
15
  };
@@ -46,3 +49,24 @@ export declare function failure(command: string, error: {
46
49
  details?: unknown;
47
50
  }, version?: string, minimumCliVersion?: string): CliFailure;
48
51
  export declare function toFailure(command: string, error: unknown, version?: string): CliFailure;
52
+ export type SemanticVersion = {
53
+ major: number;
54
+ minor: number;
55
+ patch: number;
56
+ };
57
+ /**
58
+ * Parse a three-segment semantic version (e.g. `1.142.17`). Pre-release and
59
+ * build metadata suffixes (`-rc.1`, `+build`) are tolerated but ignored.
60
+ * Returns `null` when the input is not a parseable `major.minor.patch` string.
61
+ */
62
+ export declare function parseSemver(version: string): SemanticVersion | null;
63
+ /**
64
+ * Compare two semantic versions. Returns -1 when `a < b`, 1 when `a > b`, and
65
+ * 0 when they are equal. Unparseable inputs compare as equal (0) so callers
66
+ * fail open rather than misordering garbage.
67
+ */
68
+ export declare function compareSemver(a: string, b: string): -1 | 0 | 1;
69
+ /** True when `a` is a strictly greater semantic version than `b`. */
70
+ export declare function isVersionGreater(a: string, b: string): boolean;
71
+ /** True when `a` is a strictly lesser semantic version than `b`. */
72
+ export declare function isVersionLess(a: string, b: string): boolean;
@@ -1,13 +1,21 @@
1
1
  import { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
2
2
  export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
3
+ export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
3
4
  export * from "./billing.js";
4
5
  export * from "./cell-format.js";
5
6
  export * from "./column-types.js";
6
7
  export * from "./credit-guidance.js";
8
+ export * from "./linkedin-sequences.js";
7
9
  export * from "./log.js";
8
10
  export * from "./provider-request-outcomes.js";
9
11
  export * from "./signup-lead-deliveries.js";
10
12
  export * from "./telemetry.js";
13
+ // Maximum rows a single row-loop write (insert/upsert/preview) may process. The
14
+ // row-loop engine issues one DB round-trip per row, so a 500-row write already
15
+ // approaches request timeouts (~50s observed in prod); larger batches must use
16
+ // the COPY-based bulk engine. Tenant-db enforces this and the CLI/API row caps
17
+ // reference it so they never advertise a batch the row-loop will reject.
18
+ export const MAX_ROW_LOOP_WRITE_ROWS = 500;
11
19
  export class OxygenError extends Error {
12
20
  code;
13
21
  details;
@@ -51,3 +59,44 @@ export function toFailure(command, error, version = OXYGEN_VERSION) {
51
59
  }
52
60
  return failure(command, { code: "unexpected_error", message: "An unexpected error occurred." }, version);
53
61
  }
62
+ /**
63
+ * Parse a three-segment semantic version (e.g. `1.142.17`). Pre-release and
64
+ * build metadata suffixes (`-rc.1`, `+build`) are tolerated but ignored.
65
+ * Returns `null` when the input is not a parseable `major.minor.patch` string.
66
+ */
67
+ export function parseSemver(version) {
68
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version);
69
+ if (!match)
70
+ return null;
71
+ return {
72
+ major: Number(match[1]),
73
+ minor: Number(match[2]),
74
+ patch: Number(match[3]),
75
+ };
76
+ }
77
+ /**
78
+ * Compare two semantic versions. Returns -1 when `a < b`, 1 when `a > b`, and
79
+ * 0 when they are equal. Unparseable inputs compare as equal (0) so callers
80
+ * fail open rather than misordering garbage.
81
+ */
82
+ export function compareSemver(a, b) {
83
+ const left = parseSemver(a);
84
+ const right = parseSemver(b);
85
+ if (!left || !right)
86
+ return 0;
87
+ for (const key of ["major", "minor", "patch"]) {
88
+ if (left[key] > right[key])
89
+ return 1;
90
+ if (left[key] < right[key])
91
+ return -1;
92
+ }
93
+ return 0;
94
+ }
95
+ /** True when `a` is a strictly greater semantic version than `b`. */
96
+ export function isVersionGreater(a, b) {
97
+ return compareSemver(a, b) > 0;
98
+ }
99
+ /** True when `a` is a strictly lesser semantic version than `b`. */
100
+ export function isVersionLess(a, b) {
101
+ return compareSemver(a, b) < 0;
102
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * LinkedIn sequence step schema — the shared contract validated identically by
3
+ * CLI, MCP, API, and web. A sequence is an ordered list of steps applied to
4
+ * each enrolled lead; the worker dispatch engine materializes one
5
+ * sequence_action per step as it comes due.
6
+ *
7
+ * Step kinds:
8
+ * - visit_profile — view the lead's profile (warms up before an invite)
9
+ * - invite — send a connection request, optional note_template
10
+ * - wait_for_connection — gate: wait until the invite is accepted (new_relation
11
+ * webhook) or timeout_days elapses; on timeout stop or
12
+ * continue per on_timeout
13
+ * - wait — fixed delay (days/hours) before the next step
14
+ * - message — send a message (opens a chat if none exists);
15
+ * template supports {{column}} interpolation
16
+ * - inmail — send an InMail (works on non-connections); subject +
17
+ * template
18
+ */
19
+ export declare const LINKEDIN_SEQUENCE_STEP_KINDS: readonly ["visit_profile", "invite", "wait_for_connection", "wait", "message", "inmail"];
20
+ export type LinkedInSequenceStepKind = typeof LINKEDIN_SEQUENCE_STEP_KINDS[number];
21
+ export type LinkedInVisitProfileStep = {
22
+ kind: "visit_profile";
23
+ };
24
+ export type LinkedInInviteStep = {
25
+ kind: "invite";
26
+ /** Optional connection-request note. Supports {{column}} interpolation. ~300 char max on LinkedIn. */
27
+ note_template?: string;
28
+ };
29
+ export type LinkedInWaitForConnectionStep = {
30
+ kind: "wait_for_connection";
31
+ /** Days to wait for the invite to be accepted before acting on on_timeout. */
32
+ timeout_days: number;
33
+ /** What to do if the invite is never accepted. Defaults to "stop". */
34
+ on_timeout?: "stop" | "continue";
35
+ };
36
+ export type LinkedInWaitStep = {
37
+ kind: "wait";
38
+ /** Fixed delay before the next step. Provide days and/or hours (>= 1 total). */
39
+ days?: number;
40
+ hours?: number;
41
+ };
42
+ export type LinkedInMessageStep = {
43
+ kind: "message";
44
+ /** Message body. Supports {{column}} interpolation from the source-table row. */
45
+ template: string;
46
+ };
47
+ export type LinkedInInMailStep = {
48
+ kind: "inmail";
49
+ subject_template: string;
50
+ template: string;
51
+ };
52
+ export type LinkedInSequenceStep = LinkedInVisitProfileStep | LinkedInInviteStep | LinkedInWaitForConnectionStep | LinkedInWaitStep | LinkedInMessageStep | LinkedInInMailStep;
53
+ export type LinkedInSequenceDefinition = {
54
+ steps: LinkedInSequenceStep[];
55
+ };
56
+ /**
57
+ * The dispatch-queue action kind a step produces (matches the
58
+ * ox_linkedin.sequence_actions.action_kind enum), or null for gate/wait steps
59
+ * that dispatch nothing. NOTE: this is the *action* kind, not the *quota* kind
60
+ * — visit_profile maps to the quota kind profile_view downstream.
61
+ */
62
+ export declare const LINKEDIN_STEP_ACTION_KIND: Record<LinkedInSequenceStepKind, "visit_profile" | "invite" | "message" | "inmail" | null>;
63
+ export type LinkedInSequenceLintIssue = {
64
+ path: string;
65
+ message: string;
66
+ };
67
+ /**
68
+ * Validate a raw sequence definition. Returns the normalized definition or
69
+ * throws OxygenError("invalid_linkedin_sequence") with per-step issues. Pure —
70
+ * safe to call from any surface.
71
+ */
72
+ export declare function validateLinkedInSequenceDefinition(input: unknown): LinkedInSequenceDefinition;
73
+ /** Non-throwing variant for lint surfaces. */
74
+ export declare function lintLinkedInSequenceDefinition(input: unknown): LinkedInSequenceLintIssue[];
75
+ /** Total delay in milliseconds a `wait` step introduces. */
76
+ export declare function waitStepDelayMs(step: LinkedInWaitStep): number;
77
+ /**
78
+ * Render a {{column}} template against a row's values. Unknown placeholders
79
+ * render empty. Used by the dispatch engine to produce the final message text.
80
+ */
81
+ export declare function renderLinkedInTemplate(template: string, values: Record<string, unknown>): string;
82
+ /** Column keys referenced by {{...}} placeholders across all steps. */
83
+ export declare function linkedInTemplateVariables(definition: LinkedInSequenceDefinition): string[];
@@ -0,0 +1,268 @@
1
+ import { OxygenError } from "./index.js";
2
+ /**
3
+ * LinkedIn sequence step schema — the shared contract validated identically by
4
+ * CLI, MCP, API, and web. A sequence is an ordered list of steps applied to
5
+ * each enrolled lead; the worker dispatch engine materializes one
6
+ * sequence_action per step as it comes due.
7
+ *
8
+ * Step kinds:
9
+ * - visit_profile — view the lead's profile (warms up before an invite)
10
+ * - invite — send a connection request, optional note_template
11
+ * - wait_for_connection — gate: wait until the invite is accepted (new_relation
12
+ * webhook) or timeout_days elapses; on timeout stop or
13
+ * continue per on_timeout
14
+ * - wait — fixed delay (days/hours) before the next step
15
+ * - message — send a message (opens a chat if none exists);
16
+ * template supports {{column}} interpolation
17
+ * - inmail — send an InMail (works on non-connections); subject +
18
+ * template
19
+ */
20
+ export const LINKEDIN_SEQUENCE_STEP_KINDS = [
21
+ "visit_profile",
22
+ "invite",
23
+ "wait_for_connection",
24
+ "wait",
25
+ "message",
26
+ "inmail",
27
+ ];
28
+ /**
29
+ * The dispatch-queue action kind a step produces (matches the
30
+ * ox_linkedin.sequence_actions.action_kind enum), or null for gate/wait steps
31
+ * that dispatch nothing. NOTE: this is the *action* kind, not the *quota* kind
32
+ * — visit_profile maps to the quota kind profile_view downstream.
33
+ */
34
+ export const LINKEDIN_STEP_ACTION_KIND = {
35
+ visit_profile: "visit_profile",
36
+ invite: "invite",
37
+ wait_for_connection: null,
38
+ wait: null,
39
+ message: "message",
40
+ inmail: "inmail",
41
+ };
42
+ const MAX_STEPS = 25;
43
+ const MAX_TEMPLATE_LENGTH = 8_000;
44
+ const MAX_NOTE_LENGTH = 300;
45
+ /**
46
+ * Validate a raw sequence definition. Returns the normalized definition or
47
+ * throws OxygenError("invalid_linkedin_sequence") with per-step issues. Pure —
48
+ * safe to call from any surface.
49
+ */
50
+ export function validateLinkedInSequenceDefinition(input) {
51
+ const issues = [];
52
+ const steps = collectSteps(input, issues);
53
+ const normalized = [];
54
+ steps.forEach((rawStep, index) => {
55
+ const step = normalizeStep(rawStep, index, issues);
56
+ if (step)
57
+ normalized.push(step);
58
+ });
59
+ validateStructure(normalized, issues);
60
+ if (issues.length > 0) {
61
+ throw new OxygenError("invalid_linkedin_sequence", `Sequence definition is invalid: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`, { details: { issues }, exitCode: 1 });
62
+ }
63
+ return { steps: normalized };
64
+ }
65
+ /** Non-throwing variant for lint surfaces. */
66
+ export function lintLinkedInSequenceDefinition(input) {
67
+ try {
68
+ validateLinkedInSequenceDefinition(input);
69
+ return [];
70
+ }
71
+ catch (error) {
72
+ if (error instanceof OxygenError && error.details && typeof error.details === "object") {
73
+ const issues = error.details.issues;
74
+ if (Array.isArray(issues))
75
+ return issues;
76
+ }
77
+ return [{ path: "steps", message: error instanceof Error ? error.message : "Invalid sequence." }];
78
+ }
79
+ }
80
+ function collectSteps(input, issues) {
81
+ const record = isRecord(input) ? input : null;
82
+ const steps = record?.steps;
83
+ if (!Array.isArray(steps)) {
84
+ issues.push({ path: "steps", message: "steps must be an array." });
85
+ return [];
86
+ }
87
+ if (steps.length === 0) {
88
+ issues.push({ path: "steps", message: "A sequence needs at least one step." });
89
+ }
90
+ if (steps.length > MAX_STEPS) {
91
+ issues.push({ path: "steps", message: `A sequence may have at most ${MAX_STEPS} steps.` });
92
+ }
93
+ return steps;
94
+ }
95
+ function normalizeStep(// skipcq: JS-R1005 -- step normalization validates a discriminated sequence DSL with per-step fields.
96
+ raw, index, issues) {
97
+ const path = `steps[${index}]`;
98
+ if (!isRecord(raw)) {
99
+ issues.push({ path, message: "Each step must be an object." });
100
+ return null;
101
+ }
102
+ const kind = raw.kind;
103
+ if (typeof kind !== "string" || !LINKEDIN_SEQUENCE_STEP_KINDS.includes(kind)) {
104
+ issues.push({
105
+ path: `${path}.kind`,
106
+ message: `kind must be one of: ${LINKEDIN_SEQUENCE_STEP_KINDS.join(", ")}.`,
107
+ });
108
+ return null;
109
+ }
110
+ switch (kind) {
111
+ case "visit_profile":
112
+ return { kind: "visit_profile" };
113
+ case "invite": {
114
+ const note = optionalTemplate(raw.note_template, `${path}.note_template`, MAX_NOTE_LENGTH, issues);
115
+ return note !== undefined ? { kind: "invite", note_template: note } : { kind: "invite" };
116
+ }
117
+ case "wait_for_connection": {
118
+ // timeout_days is optional (defaults to 14); only validate when provided.
119
+ const timeoutDays = raw.timeout_days === undefined || raw.timeout_days === null
120
+ ? undefined
121
+ : positiveInt(raw.timeout_days, `${path}.timeout_days`, issues);
122
+ const onTimeout = raw.on_timeout;
123
+ if (onTimeout !== undefined && onTimeout !== "stop" && onTimeout !== "continue") {
124
+ issues.push({ path: `${path}.on_timeout`, message: "on_timeout must be 'stop' or 'continue'." });
125
+ }
126
+ return {
127
+ kind: "wait_for_connection",
128
+ timeout_days: timeoutDays ?? 14,
129
+ on_timeout: onTimeout === "continue" ? "continue" : "stop",
130
+ };
131
+ }
132
+ case "wait": {
133
+ const days = optionalNonNegativeInt(raw.days, `${path}.days`, issues);
134
+ const hours = optionalNonNegativeInt(raw.hours, `${path}.hours`, issues);
135
+ if ((days ?? 0) + (hours ?? 0) <= 0) {
136
+ issues.push({ path, message: "A wait step needs days and/or hours totaling at least 1 hour." });
137
+ }
138
+ return {
139
+ kind: "wait",
140
+ ...(days !== undefined ? { days } : {}),
141
+ ...(hours !== undefined ? { hours } : {}),
142
+ };
143
+ }
144
+ case "message": {
145
+ const template = requiredTemplate(raw.template, `${path}.template`, issues);
146
+ return { kind: "message", template: template ?? "" };
147
+ }
148
+ case "inmail": {
149
+ const subject = requiredTemplate(raw.subject_template, `${path}.subject_template`, issues);
150
+ const template = requiredTemplate(raw.template, `${path}.template`, issues);
151
+ return { kind: "inmail", subject_template: subject ?? "", template: template ?? "" };
152
+ }
153
+ default:
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Structural rules across steps:
159
+ * - The first send step must be invite or visit_profile (you can't message a
160
+ * stranger without connecting); a message before any invite/wait_for_connection
161
+ * is valid for warm lists whose leads are already 1st-degree connections.
162
+ * - wait_for_connection must be preceded by an invite.
163
+ * - Two consecutive wait/wait_for_connection steps are pointless.
164
+ */
165
+ function validateStructure(steps, issues) {
166
+ let sawInvite = false;
167
+ steps.forEach((step, index) => {
168
+ if (step.kind === "invite")
169
+ sawInvite = true;
170
+ if (step.kind === "wait_for_connection" && !sawInvite) {
171
+ issues.push({
172
+ path: `steps[${index}]`,
173
+ message: "wait_for_connection must come after an invite step.",
174
+ });
175
+ }
176
+ if (step.kind === "message" && !sawInvite && !steps.slice(0, index).some((s) => s.kind === "wait_for_connection")) {
177
+ // A message before connecting only works for existing 1st-degree
178
+ // connections. This is valid for warm lists; do not add a fatal issue
179
+ // until the API has a separate warning channel.
180
+ }
181
+ });
182
+ }
183
+ function requiredTemplate(value, path, issues) {
184
+ if (typeof value !== "string" || !value.trim()) {
185
+ issues.push({ path, message: "is required and must be a non-empty string." });
186
+ return undefined;
187
+ }
188
+ if (value.length > MAX_TEMPLATE_LENGTH) {
189
+ issues.push({ path, message: `must be at most ${MAX_TEMPLATE_LENGTH} characters.` });
190
+ return undefined;
191
+ }
192
+ return value;
193
+ }
194
+ function optionalTemplate(value, path, maxLength, issues) {
195
+ if (value === undefined || value === null)
196
+ return undefined;
197
+ if (typeof value !== "string") {
198
+ issues.push({ path, message: "must be a string." });
199
+ return undefined;
200
+ }
201
+ if (value.length > maxLength) {
202
+ issues.push({ path, message: `must be at most ${maxLength} characters.` });
203
+ return undefined;
204
+ }
205
+ return value;
206
+ }
207
+ function positiveInt(value, path, issues) {
208
+ const num = Number(value);
209
+ if (!Number.isInteger(num) || num <= 0) {
210
+ issues.push({ path, message: "must be a positive integer." });
211
+ return undefined;
212
+ }
213
+ return num;
214
+ }
215
+ function optionalNonNegativeInt(value, path, issues) {
216
+ if (value === undefined || value === null)
217
+ return undefined;
218
+ const num = Number(value);
219
+ if (!Number.isInteger(num) || num < 0) {
220
+ issues.push({ path, message: "must be a non-negative integer." });
221
+ return undefined;
222
+ }
223
+ return num;
224
+ }
225
+ /** Total delay in milliseconds a `wait` step introduces. */
226
+ export function waitStepDelayMs(step) {
227
+ const days = step.days ?? 0;
228
+ const hours = step.hours ?? 0;
229
+ return (days * 24 + hours) * 60 * 60 * 1000;
230
+ }
231
+ /**
232
+ * Render a {{column}} template against a row's values. Unknown placeholders
233
+ * render empty. Used by the dispatch engine to produce the final message text.
234
+ */
235
+ export function renderLinkedInTemplate(template, values) {
236
+ return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_match, key) => {
237
+ const value = values[key];
238
+ if (value === null || value === undefined)
239
+ return "";
240
+ return typeof value === "string" ? value : String(value);
241
+ });
242
+ }
243
+ /** Column keys referenced by {{...}} placeholders across all steps. */
244
+ export function linkedInTemplateVariables(definition) {
245
+ const vars = new Set();
246
+ const scan = (template) => {
247
+ if (!template)
248
+ return;
249
+ for (const match of template.matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) {
250
+ if (match[1])
251
+ vars.add(match[1]);
252
+ }
253
+ };
254
+ for (const step of definition.steps) {
255
+ if (step.kind === "invite")
256
+ scan(step.note_template);
257
+ if (step.kind === "message")
258
+ scan(step.template);
259
+ if (step.kind === "inmail") {
260
+ scan(step.subject_template);
261
+ scan(step.template);
262
+ }
263
+ }
264
+ return [...vars];
265
+ }
266
+ function isRecord(value) {
267
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
268
+ }
@@ -1,5 +1,8 @@
1
1
  export type TelemetryAttributes = Record<string, unknown>;
2
- export declare function withTelemetrySpan<T>(tracerName: string, name: string, attributes: TelemetryAttributes | undefined, fn: () => Promise<T>): Promise<T>;
2
+ export type WithTelemetrySpanOptions = {
3
+ isTransient?: (error: unknown) => boolean;
4
+ };
5
+ export declare function withTelemetrySpan<T>(tracerName: string, name: string, attributes: TelemetryAttributes | undefined, fn: () => Promise<T>, options?: WithTelemetrySpanOptions): Promise<T>;
3
6
  export declare function setActiveTelemetryAttributes(attributes: TelemetryAttributes): void;
4
7
  export declare function markActiveTelemetryError(message: string, attributes?: TelemetryAttributes): void;
5
8
  export declare function addTelemetryEvent(name: string, attributes?: TelemetryAttributes): void;
@@ -4,7 +4,7 @@ import { normalizeTelemetryAttributes } from "./redaction.js";
4
4
  import { OXYGEN_VERSION } from "./version.js";
5
5
  const counterCache = new Map();
6
6
  const histogramCache = new Map();
7
- export async function withTelemetrySpan(tracerName, name, attributes, fn) {
7
+ export async function withTelemetrySpan(tracerName, name, attributes, fn, options) {
8
8
  const tracer = trace.getTracer(tracerName, OXYGEN_VERSION);
9
9
  return tracer.startActiveSpan(name, { attributes: normalizeTelemetryAttributes(commonTelemetryAttributes(attributes)) }, async (span) => {
10
10
  try {
@@ -12,8 +12,16 @@ export async function withTelemetrySpan(tracerName, name, attributes, fn) {
12
12
  }
13
13
  catch (error) {
14
14
  span.recordException(error instanceof Error ? error : new Error(String(error)));
15
- span.setStatus({ code: SpanStatusCode.ERROR });
16
- span.setAttributes(normalizeTelemetryAttributes(errorTelemetryAttributes(error)));
15
+ if (options?.isTransient?.(error) === true) {
16
+ span.setAttributes(normalizeTelemetryAttributes({
17
+ ...errorTelemetryAttributes(error),
18
+ outcome: "transient_error",
19
+ }));
20
+ }
21
+ else {
22
+ span.setStatus({ code: SpanStatusCode.ERROR });
23
+ span.setAttributes(normalizeTelemetryAttributes(errorTelemetryAttributes(error)));
24
+ }
17
25
  throw error;
18
26
  }
19
27
  finally {
@@ -97,14 +105,18 @@ function getHistogram(name) {
97
105
  }
98
106
  function errorTelemetryAttributes(error) {
99
107
  if (error instanceof Error) {
108
+ // pg connect timeouts (and some driver errors) surface with an empty
109
+ // message; fall back to the error name so spans are never message-less.
110
+ const message = error.message.trim() ? error.message : error.name || "unknown_error";
100
111
  return {
101
112
  "error.id": errorId(error),
102
113
  "error.name": error.name,
103
- "error.message": error.message,
114
+ "error.message": message,
104
115
  };
105
116
  }
117
+ const text = String(error).trim();
106
118
  return {
107
119
  "error.id": "non_error",
108
- "error.message": String(error),
120
+ "error.message": text || "unknown_error",
109
121
  };
110
122
  }
@@ -1,2 +1,2 @@
1
- export declare const OXYGEN_VERSION = "1.142.4";
1
+ export declare const OXYGEN_VERSION = "1.152.15";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.135.0";
@@ -1,3 +1,3 @@
1
- export const OXYGEN_VERSION = "1.142.4";
1
+ export const OXYGEN_VERSION = "1.152.15";
2
2
  // Bump this only when deployed CLI/API contracts require a newer CLI.
3
3
  export const OXYGEN_MINIMUM_CLI_VERSION = "1.135.0";
@@ -0,0 +1,2 @@
1
+ export declare const WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS: readonly ["consecutive_failure_count", "last_failure_code", "last_failure_at", "auto_paused_at", "auto_pause_reason"];
2
+ export declare function clearWorkflowTriggerAutoPauseMetadata(metadata: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,14 @@
1
+ export const WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS = [
2
+ "consecutive_failure_count",
3
+ "last_failure_code",
4
+ "last_failure_at",
5
+ "auto_paused_at",
6
+ "auto_pause_reason",
7
+ ];
8
+ export function clearWorkflowTriggerAutoPauseMetadata(metadata) {
9
+ const next = { ...metadata };
10
+ for (const key of WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS) {
11
+ delete next[key];
12
+ }
13
+ return next;
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.142.4",
3
+ "version": "1.152.15",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",