@oxygen-agent/cli 1.982.3 → 1.987.20
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/functions-commands.js +21 -5
- package/dist/help.d.ts +21 -0
- package/dist/help.js +93 -0
- package/dist/index.js +322 -57
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1200
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +71 -1
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +69 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +374 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +27 -27
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +29 -4
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +12 -4
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/index.js +1 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +26 -4
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +48 -8
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +64 -2
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +24 -0
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/package.json +10 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/package.json +5 -2
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
* Professional person evidence remains actor-scoped; ICP/offers are hypotheses.
|
|
53
53
|
* The provider sub-cap covers every external research leg, including fallbacks.
|
|
54
54
|
*/
|
|
55
|
+
import { normalizeLinkedinProfileUrl } from "./linkedin-url.js";
|
|
55
56
|
/** Hard ceiling on managed credits ONE workspace bootstrap may spend, all phases. */
|
|
56
57
|
export const KNOWLEDGE_BOOTSTRAP_MAX_CREDITS = 300;
|
|
57
58
|
/**
|
|
@@ -72,6 +73,19 @@ export const KNOWLEDGE_BOOTSTRAP_SYNTHESIS_TIER = "medium";
|
|
|
72
73
|
* it is a quality/latency knob, and the credit ceilings above remain the money guard.
|
|
73
74
|
*/
|
|
74
75
|
export const KNOWLEDGE_BOOTSTRAP_MAX_PAGES_READ = 5;
|
|
76
|
+
/**
|
|
77
|
+
* Per-call provider abort budget for every paid leg of the pass (LinkedIn company,
|
|
78
|
+
* person search/profile, page search, page read), in milliseconds.
|
|
79
|
+
*
|
|
80
|
+
* The platform default is 120 s, sized for a durable column run. This pass runs
|
|
81
|
+
* inside the worker's tenant-maintenance tick under a 300 s step deadline, so two
|
|
82
|
+
* hung LinkedIn calls at the default (measured 2026-09-15/16: the managed vendor
|
|
83
|
+
* accepted connections and never answered) consumed the whole budget before the
|
|
84
|
+
* page reads and the synthesis got a turn. 45 s keeps the worst case — five legs,
|
|
85
|
+
* every one hanging — inside the step, and a healthy vendor answers in well under
|
|
86
|
+
* it. This bounds latency only; the credit ceilings above remain the money guard.
|
|
87
|
+
*/
|
|
88
|
+
export const KNOWLEDGE_BOOTSTRAP_PROVIDER_TIMEOUT_MS = 45_000;
|
|
75
89
|
/** Fleet-wide kill switch, matching OXYGEN_KNOWLEDGE_RETRIEVAL/AUTO_SOURCES_DISABLED. */
|
|
76
90
|
export const KNOWLEDGE_BOOTSTRAP_DISABLED_ENV_VAR = "OXYGEN_KNOWLEDGE_BOOTSTRAP_DISABLED";
|
|
77
91
|
/**
|
|
@@ -158,14 +172,22 @@ export function readKnowledgeBootstrapConsent(metadata) {
|
|
|
158
172
|
return null;
|
|
159
173
|
if (!Number.isFinite(Date.parse(grantedAt)))
|
|
160
174
|
return null;
|
|
175
|
+
const scope = record.scope === "company_and_creator" ? "company_and_creator" : undefined;
|
|
176
|
+
const grantedBy = typeof record.granted_by_clerk_user_id === "string" && record.granted_by_clerk_user_id.length > 0
|
|
177
|
+
? record.granted_by_clerk_user_id
|
|
178
|
+
: null;
|
|
179
|
+
// A person hint is valid only as part of the creator-scoped grant. Otherwise a
|
|
180
|
+
// stale metadata value could be mistaken for an organization-wide profile hint.
|
|
181
|
+
const creatorLinkedinUrl = scope && grantedBy && typeof record.creator_linkedin_url === "string"
|
|
182
|
+
? normalizeLinkedinProfileUrl(record.creator_linkedin_url)?.dispatchUrl ?? null
|
|
183
|
+
: null;
|
|
161
184
|
return {
|
|
162
185
|
granted_at: grantedAt,
|
|
163
|
-
...(
|
|
186
|
+
...(scope ? { scope } : {}),
|
|
164
187
|
...(typeof record.domain === "string" ? { domain: record.domain } : {}),
|
|
188
|
+
...(creatorLinkedinUrl ? { creator_linkedin_url: creatorLinkedinUrl } : {}),
|
|
165
189
|
source: typeof record.source === "string" && record.source.length > 0 ? record.source : null,
|
|
166
|
-
granted_by_clerk_user_id:
|
|
167
|
-
? record.granted_by_clerk_user_id
|
|
168
|
-
: null,
|
|
190
|
+
granted_by_clerk_user_id: grantedBy,
|
|
169
191
|
};
|
|
170
192
|
}
|
|
171
193
|
/**
|
|
@@ -38,6 +38,8 @@ export type LlmSpanBody = {
|
|
|
38
38
|
userId?: string | null;
|
|
39
39
|
};
|
|
40
40
|
export type LlmGenerationBody = LlmSpanBody & {
|
|
41
|
+
/** Persist this physical id when a later annotation must target this attempt. */
|
|
42
|
+
observationId?: string;
|
|
41
43
|
model?: string | null;
|
|
42
44
|
modelParameters?: Record<string, unknown>;
|
|
43
45
|
completionStartTime?: Date | null;
|
|
@@ -63,9 +65,11 @@ export type LlmEventBody = {
|
|
|
63
65
|
* because that variant requires a *string* value. Widening later stays additive.
|
|
64
66
|
*/
|
|
65
67
|
export type LlmScoreBody = {
|
|
66
|
-
/**
|
|
68
|
+
/** Retry-safe scores need the same id, name AND timestamp on every attempt. */
|
|
67
69
|
id?: string;
|
|
68
70
|
traceId: string;
|
|
71
|
+
observationId?: string;
|
|
72
|
+
timestamp?: Date;
|
|
69
73
|
name: string;
|
|
70
74
|
value: number;
|
|
71
75
|
dataType?: "BOOLEAN" | "NUMERIC";
|
|
@@ -103,6 +107,13 @@ export type LlmTracingClient = {
|
|
|
103
107
|
*/
|
|
104
108
|
export declare function isLlmTracingEnabled(env?: EnvMap): boolean;
|
|
105
109
|
export declare function resolveLlmTracingEnvironment(env?: EnvMap): string;
|
|
110
|
+
/**
|
|
111
|
+
* The private Langfuse provider must identify its own runtime rather than
|
|
112
|
+
* inherit the process-wide OTel resource. Fly identity wins because it is
|
|
113
|
+
* injected per machine and cannot drift; a local CLI/test process is neither
|
|
114
|
+
* the web app nor the worker.
|
|
115
|
+
*/
|
|
116
|
+
export declare function resolveLlmTracingServiceName(env?: EnvMap): string;
|
|
106
117
|
/**
|
|
107
118
|
* Deterministic Langfuse trace id for an external seed (copilot turn id, agent
|
|
108
119
|
* run id, AI-column run id).
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
// * Trace-level input/output is deprecated in v5. Overall IO goes on the ROOT
|
|
33
33
|
// observation instead; setTraceIO()/setActiveTraceIO() are deliberately not
|
|
34
34
|
// used here.
|
|
35
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
35
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
36
36
|
import { OXYGEN_VERSION } from "./version.js";
|
|
37
37
|
import { prepareLlmPayload } from "./llm-payload.js";
|
|
38
38
|
import { log } from "./log.js";
|
|
@@ -83,6 +83,23 @@ export function resolveLlmTracingEnvironment(env = process.env) {
|
|
|
83
83
|
return flyEnv === "production" ? "production" : "development";
|
|
84
84
|
return "development";
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The private Langfuse provider must identify its own runtime rather than
|
|
88
|
+
* inherit the process-wide OTel resource. Fly identity wins because it is
|
|
89
|
+
* injected per machine and cannot drift; a local CLI/test process is neither
|
|
90
|
+
* the web app nor the worker.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveLlmTracingServiceName(env = process.env) {
|
|
93
|
+
const flyApp = env.FLY_APP_NAME?.trim() || env.FLY_APP?.trim();
|
|
94
|
+
if (flyApp)
|
|
95
|
+
return flyApp.includes("-dev") ? "oxygen-worker-dev" : "oxygen-worker";
|
|
96
|
+
const flyEnvironment = env.FLY_ENVIRONMENT?.trim().toLowerCase();
|
|
97
|
+
if (flyEnvironment)
|
|
98
|
+
return flyEnvironment === "production" ? "oxygen-worker" : "oxygen-worker-dev";
|
|
99
|
+
if (env.VERCEL?.trim() || env.VERCEL_ENV?.trim())
|
|
100
|
+
return "oxygen-web";
|
|
101
|
+
return "oxygen-local";
|
|
102
|
+
}
|
|
86
103
|
/**
|
|
87
104
|
* Deterministic Langfuse trace id for an external seed (copilot turn id, agent
|
|
88
105
|
* run id, AI-column run id).
|
|
@@ -140,8 +157,9 @@ function createOtelEmitter(env, warn) {
|
|
|
140
157
|
const init = () => {
|
|
141
158
|
handle ??= (async () => {
|
|
142
159
|
try {
|
|
143
|
-
const [{ LangfuseSpanProcessor }, { BasicTracerProvider, AlwaysOnSampler }, tracing, otel, core] = await Promise.all([
|
|
160
|
+
const [{ LangfuseSpanProcessor }, { BasicTracerProvider, AlwaysOnSampler }, { resourceFromAttributes }, tracing, otel, core] = await Promise.all([
|
|
144
161
|
import("@langfuse/otel"), import("@opentelemetry/sdk-trace-base"),
|
|
162
|
+
import("@opentelemetry/resources"),
|
|
145
163
|
import("@langfuse/tracing"), import("@opentelemetry/api"), import("@langfuse/core"),
|
|
146
164
|
]);
|
|
147
165
|
const processor = new LangfuseSpanProcessor({
|
|
@@ -160,6 +178,11 @@ function createOtelEmitter(env, warn) {
|
|
|
160
178
|
});
|
|
161
179
|
let identity;
|
|
162
180
|
const provider = new BasicTracerProvider({
|
|
181
|
+
// This provider is deliberately isolated from the application's
|
|
182
|
+
// global OTel pipeline, so it also needs its own explicit resource.
|
|
183
|
+
// Never trust OTEL_SERVICE_NAME here: a cross-environment Doppler
|
|
184
|
+
// value must not relabel prompt-bearing Langfuse observations.
|
|
185
|
+
resource: resourceFromAttributes({ "service.name": resolveLlmTracingServiceName(env) }),
|
|
163
186
|
spanProcessors: [{
|
|
164
187
|
onStart: (span) => {
|
|
165
188
|
// Directly pass the private parent claim to the processor. A
|
|
@@ -266,7 +289,7 @@ function createApiScorer(env, warn) {
|
|
|
266
289
|
username: env.LANGFUSE_PUBLIC_KEY,
|
|
267
290
|
password: env.LANGFUSE_SECRET_KEY,
|
|
268
291
|
});
|
|
269
|
-
return client
|
|
292
|
+
return client;
|
|
270
293
|
}
|
|
271
294
|
catch (error) {
|
|
272
295
|
warn(error, { stage: "score_init" });
|
|
@@ -281,15 +304,31 @@ function createApiScorer(env, warn) {
|
|
|
281
304
|
const api = await init();
|
|
282
305
|
if (!api)
|
|
283
306
|
return false;
|
|
284
|
-
|
|
285
|
-
id: body.id,
|
|
307
|
+
const payload = {
|
|
308
|
+
...(body.id ? { id: body.id } : {}),
|
|
286
309
|
traceId: body.traceId,
|
|
310
|
+
...(body.observationId ? { observationId: body.observationId } : {}),
|
|
287
311
|
name: body.name,
|
|
288
312
|
value: body.value,
|
|
289
|
-
dataType: body.dataType,
|
|
290
|
-
|
|
313
|
+
...(body.dataType ? { dataType: body.dataType } : {}),
|
|
314
|
+
...(typeof body.comment === "string" ? { comment: boundComment(body.comment) } : {}),
|
|
291
315
|
environment: resolveLlmTracingEnvironment(env),
|
|
292
|
-
}
|
|
316
|
+
};
|
|
317
|
+
const requestOptions = { timeoutInSeconds: 5, maxRetries: 0 };
|
|
318
|
+
if (body.timestamp) {
|
|
319
|
+
// POST /scores cannot carry a timestamp. Score ingestion remains
|
|
320
|
+
// supported in v4 (unlike trace/observation ingestion) and preserves
|
|
321
|
+
// the date component of the score's deduplication identity.
|
|
322
|
+
if (!body.id || !Number.isFinite(body.timestamp.getTime()))
|
|
323
|
+
return false;
|
|
324
|
+
const eventId = randomUUID();
|
|
325
|
+
const result = await api.ingestion.batch({ batch: [{
|
|
326
|
+
type: "score-create", id: eventId, timestamp: body.timestamp.toISOString(), body: payload,
|
|
327
|
+
}] }, requestOptions);
|
|
328
|
+
// A 207 is transport success even if the event was rejected.
|
|
329
|
+
return result.errors.length === 0 && result.successes.some((event) => event.id === eventId && event.status >= 200 && event.status < 300);
|
|
330
|
+
}
|
|
331
|
+
await api.scores.create(payload, requestOptions);
|
|
293
332
|
return true;
|
|
294
333
|
}
|
|
295
334
|
catch (error) {
|
|
@@ -398,6 +437,7 @@ export function createLlmTracingClient(env = process.env, options) {
|
|
|
398
437
|
emitter.emit({
|
|
399
438
|
kind,
|
|
400
439
|
traceSeed: body.traceId,
|
|
440
|
+
...(body.observationId ? { observationId: body.observationId } : {}),
|
|
401
441
|
name: body.name,
|
|
402
442
|
attributes: compact({
|
|
403
443
|
input: body.input,
|
|
@@ -14,6 +14,34 @@ export declare function deriveResearchOutputContract(prompt: string, options: {
|
|
|
14
14
|
evidenceRequired: boolean;
|
|
15
15
|
}): ResearchOutputContract | null;
|
|
16
16
|
export declare function buildResearchOutputSchema(contract: ResearchOutputContract | null): Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a found answer MUST cite evidence. Shared by the write-path normalizer
|
|
19
|
+
* (which bakes it into the derived research contract), the runner (which
|
|
20
|
+
* validates the model output against it) and the output-field reader, so none
|
|
21
|
+
* of them can disagree.
|
|
22
|
+
*
|
|
23
|
+
* Explicit "strict" always requires it. A FETCH column requires it by default —
|
|
24
|
+
* the evidence is the one page the customer pointed at, so an answer that cites
|
|
25
|
+
* nothing answered from memory — unless the author chose "estimate".
|
|
26
|
+
*/
|
|
27
|
+
export declare function researchEvidenceRequired(webSearch: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* True for a model-facing schema in the research envelope shape — the derived
|
|
30
|
+
* contract, the legacy broad schema, or a catalog template's typed answer
|
|
31
|
+
* envelope — as opposed to a hand-authored schema that replaces the envelope
|
|
32
|
+
* entirely. The output-field reader rewrites only the former into the stored
|
|
33
|
+
* cell shape (`sources` in, `citations` out); the latter is used verbatim.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isResearchEnvelopeSchema(schema: unknown): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The model-facing schema for a research column whose ANSWER is a typed object
|
|
38
|
+
* (a catalog template such as a pricing summary): the same
|
|
39
|
+
* `{answer, found, confidence, citations}` envelope the derived contract uses,
|
|
40
|
+
* with `answer` replaced by the template's own object schema. The runner still
|
|
41
|
+
* resolves `citations` to real sources on the way to the cell, so a typed
|
|
42
|
+
* extraction cannot cite a page that was never fetched either.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildResearchOutputSchemaForAnswer(answerSchema: Record<string, unknown>): Record<string, unknown>;
|
|
17
45
|
/**
|
|
18
46
|
* The schema of the research cell as it is actually STORED — which is not the
|
|
19
47
|
* schema the model answers in.
|
|
@@ -27,7 +55,11 @@ export declare function buildResearchOutputSchema(contract: ResearchOutputContra
|
|
|
27
55
|
* `buildResearchOutputSchema` would offer `citations`, which no cell has, and
|
|
28
56
|
* hide `sources`, which every cell does.
|
|
29
57
|
*/
|
|
30
|
-
export declare function buildResearchCellSchema(contract: ResearchOutputContract | null
|
|
58
|
+
export declare function buildResearchCellSchema(contract: ResearchOutputContract | null,
|
|
59
|
+
/** A column's own model-facing schema (a catalog template's typed answer
|
|
60
|
+
* envelope). When given it replaces the derived one, so the cell shape of a
|
|
61
|
+
* typed extraction exposes `answer.plan_count`, not a string answer. */
|
|
62
|
+
modelSchemaOverride?: Record<string, unknown> | null): Record<string, unknown>;
|
|
31
63
|
/**
|
|
32
64
|
* Is this research column's output schema OWNED BY THE SERVER, i.e. re-derived
|
|
33
65
|
* from the prompt rather than authored by hand?
|
|
@@ -88,6 +88,64 @@ export function buildResearchOutputSchema(contract) {
|
|
|
88
88
|
required: ["answer", "found", "confidence", "citations"],
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether a found answer MUST cite evidence. Shared by the write-path normalizer
|
|
93
|
+
* (which bakes it into the derived research contract), the runner (which
|
|
94
|
+
* validates the model output against it) and the output-field reader, so none
|
|
95
|
+
* of them can disagree.
|
|
96
|
+
*
|
|
97
|
+
* Explicit "strict" always requires it. A FETCH column requires it by default —
|
|
98
|
+
* the evidence is the one page the customer pointed at, so an answer that cites
|
|
99
|
+
* nothing answered from memory — unless the author chose "estimate".
|
|
100
|
+
*/
|
|
101
|
+
export function researchEvidenceRequired(webSearch) {
|
|
102
|
+
if (!isObject(webSearch))
|
|
103
|
+
return false;
|
|
104
|
+
if (webSearch.evidenceMode === "strict")
|
|
105
|
+
return true;
|
|
106
|
+
if (webSearch.evidenceMode === "estimate")
|
|
107
|
+
return false;
|
|
108
|
+
return webSearch.source === "fetch";
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* True for a model-facing schema in the research envelope shape — the derived
|
|
112
|
+
* contract, the legacy broad schema, or a catalog template's typed answer
|
|
113
|
+
* envelope — as opposed to a hand-authored schema that replaces the envelope
|
|
114
|
+
* entirely. The output-field reader rewrites only the former into the stored
|
|
115
|
+
* cell shape (`sources` in, `citations` out); the latter is used verbatim.
|
|
116
|
+
*/
|
|
117
|
+
export function isResearchEnvelopeSchema(schema) {
|
|
118
|
+
if (!isObject(schema) || schema.type !== "object")
|
|
119
|
+
return false;
|
|
120
|
+
const properties = isObject(schema.properties) ? schema.properties : null;
|
|
121
|
+
if (!properties)
|
|
122
|
+
return false;
|
|
123
|
+
return ["answer", "found", "confidence", "citations"].every((key) => isObject(properties[key]));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The model-facing schema for a research column whose ANSWER is a typed object
|
|
127
|
+
* (a catalog template such as a pricing summary): the same
|
|
128
|
+
* `{answer, found, confidence, citations}` envelope the derived contract uses,
|
|
129
|
+
* with `answer` replaced by the template's own object schema. The runner still
|
|
130
|
+
* resolves `citations` to real sources on the way to the cell, so a typed
|
|
131
|
+
* extraction cannot cite a page that was never fetched either.
|
|
132
|
+
*/
|
|
133
|
+
export function buildResearchOutputSchemaForAnswer(answerSchema) {
|
|
134
|
+
const envelope = buildResearchOutputSchema(null);
|
|
135
|
+
const properties = isObject(envelope.properties) ? { ...envelope.properties } : {};
|
|
136
|
+
return {
|
|
137
|
+
...envelope,
|
|
138
|
+
properties: {
|
|
139
|
+
...properties,
|
|
140
|
+
answer: {
|
|
141
|
+
...answerSchema,
|
|
142
|
+
description: typeof answerSchema.description === "string"
|
|
143
|
+
? answerSchema.description
|
|
144
|
+
: "The typed answer read from the evidence; unsupported fields are null or empty.",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
91
149
|
/**
|
|
92
150
|
* The schema of the research cell as it is actually STORED — which is not the
|
|
93
151
|
* schema the model answers in.
|
|
@@ -101,8 +159,12 @@ export function buildResearchOutputSchema(contract) {
|
|
|
101
159
|
* `buildResearchOutputSchema` would offer `citations`, which no cell has, and
|
|
102
160
|
* hide `sources`, which every cell does.
|
|
103
161
|
*/
|
|
104
|
-
export function buildResearchCellSchema(contract
|
|
105
|
-
|
|
162
|
+
export function buildResearchCellSchema(contract,
|
|
163
|
+
/** A column's own model-facing schema (a catalog template's typed answer
|
|
164
|
+
* envelope). When given it replaces the derived one, so the cell shape of a
|
|
165
|
+
* typed extraction exposes `answer.plan_count`, not a string answer. */
|
|
166
|
+
modelSchemaOverride) {
|
|
167
|
+
const modelSchema = modelSchemaOverride ?? buildResearchOutputSchema(contract);
|
|
106
168
|
const properties = isObject(modelSchema.properties) ? { ...modelSchema.properties } : {};
|
|
107
169
|
delete properties.citations;
|
|
108
170
|
return {
|
|
@@ -287,5 +287,5 @@ export declare const SEQUENCE_HUBSPOT_EVENT_SYNC_TEMPLATE_ID = "sequencer-hubspo
|
|
|
287
287
|
export declare const SEQUENCE_WORKFLOW_EVENT_SOURCE = "sequencer";
|
|
288
288
|
export declare const SEQUENCE_CONTACT_ACTIVITY_EVENT = "contact_activity";
|
|
289
289
|
export declare const SEQUENCE_HUBSPOT_LINKEDIN_IDENTITY_PROPERTY = "oxygen_linkedin_url";
|
|
290
|
-
export declare const DEFAULT_SEQUENCE_HUBSPOT_EVENT_MAPPINGS: Readonly<Record<"
|
|
290
|
+
export declare const DEFAULT_SEQUENCE_HUBSPOT_EVENT_MAPPINGS: Readonly<Record<"call_connected" | "call_no_answer" | "call_task_created" | "call_voicemail" | "crm_task_created" | "email_bounced" | "email_campaign_enrolled" | "email_campaign_moved" | "email_campaign_stopped" | "email_clicked" | "email_opened" | "email_reply_received" | "email_sent" | "email_spam_complaint" | "email_unsubscribed" | "linkedin_connection_accepted" | "linkedin_connection_sent" | "linkedin_connection_withdrawn" | "linkedin_followed" | "linkedin_inmail_sent" | "linkedin_message_sent" | "linkedin_post_commented" | "linkedin_post_liked" | "linkedin_profile_visited" | "linkedin_reply_received" | "meeting_booked" | "whatsapp_message_sent" | "whatsapp_reply_received", string>>;
|
|
291
291
|
export declare function isSequenceCrmEventKey(value: unknown): value is SequenceCrmEventKey;
|
|
@@ -17,7 +17,7 @@ export declare const HUBSPOT_DEFAULT_LEAD_STATUS_PROPERTY = "hs_lead_status";
|
|
|
17
17
|
export declare const DEFAULT_HUBSPOT_SEQUENCE_TIMELINE_EVENTS: readonly ["email_sent", "email_reply_received", "email_bounced", "linkedin_message_sent", "linkedin_inmail_sent", "linkedin_reply_received"];
|
|
18
18
|
/** Events for which the HubSpot adapter has a first-class activity object. */
|
|
19
19
|
export declare const HUBSPOT_SEQUENCE_TIMELINE_EVENT_KEYS: readonly ["email_sent", "email_reply_received", "email_bounced", "linkedin_message_sent", "linkedin_inmail_sent", "linkedin_reply_received"];
|
|
20
|
-
export declare const HUBSPOT_CLASSIFIED_REPLY_TIMELINE_EVENTS: Set<"
|
|
20
|
+
export declare const HUBSPOT_CLASSIFIED_REPLY_TIMELINE_EVENTS: Set<"call_connected" | "call_no_answer" | "call_task_created" | "call_voicemail" | "crm_task_created" | "email_bounced" | "email_campaign_enrolled" | "email_campaign_moved" | "email_campaign_stopped" | "email_clicked" | "email_opened" | "email_reply_received" | "email_sent" | "email_spam_complaint" | "email_unsubscribed" | "linkedin_connection_accepted" | "linkedin_connection_sent" | "linkedin_connection_withdrawn" | "linkedin_followed" | "linkedin_inmail_sent" | "linkedin_message_sent" | "linkedin_post_commented" | "linkedin_post_liked" | "linkedin_profile_visited" | "linkedin_reply_received" | "meeting_booked" | "whatsapp_message_sent" | "whatsapp_reply_received">;
|
|
21
21
|
export type HubSpotSequenceSyncConfig = {
|
|
22
22
|
/** Optional latest-occurrence snapshots on HubSpot contact properties. */
|
|
23
23
|
mappings: Record<SequenceCrmEventKey, string>;
|
|
@@ -919,9 +919,28 @@ export type SequenceLintIssue = {
|
|
|
919
919
|
path: string;
|
|
920
920
|
message: string;
|
|
921
921
|
};
|
|
922
|
+
/**
|
|
923
|
+
* A non-blocking fact about how a definition was normalized that its author must
|
|
924
|
+
* be told, as opposed to an issue that rejects it. Today exactly one:
|
|
925
|
+
* `email_reply_rewritten`. The retired kind is accepted and rewritten into a
|
|
926
|
+
* subject-less `email_send`, and a caller that only reads the stored definition
|
|
927
|
+
* back sees a different `kind` than it wrote with nothing in the response to
|
|
928
|
+
* explain it — reported as a silent coercion by a customer (Plain T-163).
|
|
929
|
+
*/
|
|
930
|
+
export type SequenceDefinitionNotice = {
|
|
931
|
+
path: string;
|
|
932
|
+
code: "email_reply_rewritten";
|
|
933
|
+
message: string;
|
|
934
|
+
};
|
|
922
935
|
export type ValidateSequenceOptions = {
|
|
923
936
|
/** When provided, every channel-bearing step must use one of these channels. */
|
|
924
937
|
allowedChannels?: SequenceChannel[];
|
|
938
|
+
/**
|
|
939
|
+
* Sink for non-blocking normalization notices (see SequenceDefinitionNotice).
|
|
940
|
+
* Validation never throws for these; a caller that wants to surface them to
|
|
941
|
+
* the author passes an array and reads it back after the call.
|
|
942
|
+
*/
|
|
943
|
+
notices?: SequenceDefinitionNotice[];
|
|
925
944
|
/**
|
|
926
945
|
* Whether this sequence has NATIVE open/click tracking enabled (a verified
|
|
927
946
|
* tracking domain + EMAIL_TRACKING_SECRET, so the dispatcher injects a pixel +
|
|
@@ -980,6 +999,13 @@ export declare function sequenceStepsMissingCopy(steps: readonly SequenceStep[])
|
|
|
980
999
|
*/
|
|
981
1000
|
export declare function validateSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceDefinition;
|
|
982
1001
|
/** Non-throwing variant for lint surfaces. */
|
|
1002
|
+
/**
|
|
1003
|
+
* The non-blocking notices normalizing this definition would raise, for a
|
|
1004
|
+
* response `warnings` array. Never throws: an invalid definition yields whatever
|
|
1005
|
+
* notices were collected before validation gave up (the caller's own validate
|
|
1006
|
+
* call reports the failure), so this is safe to run beside a persist.
|
|
1007
|
+
*/
|
|
1008
|
+
export declare function sequenceDefinitionNotices(input: unknown, options?: Omit<ValidateSequenceOptions, "notices">): SequenceDefinitionNotice[];
|
|
983
1009
|
export declare function lintSequenceDefinition(input: unknown, options?: ValidateSequenceOptions): SequenceLintIssue[];
|
|
984
1010
|
/** Total base delay in milliseconds a wait step introduces (no jitter). */
|
|
985
1011
|
export declare function sequenceWaitStepDelayMs(step: SequenceWaitStep): number;
|
|
@@ -1176,6 +1176,22 @@ export function validateSequenceDefinition(input, options = {}) {
|
|
|
1176
1176
|
return { steps: normalized };
|
|
1177
1177
|
}
|
|
1178
1178
|
/** Non-throwing variant for lint surfaces. */
|
|
1179
|
+
/**
|
|
1180
|
+
* The non-blocking notices normalizing this definition would raise, for a
|
|
1181
|
+
* response `warnings` array. Never throws: an invalid definition yields whatever
|
|
1182
|
+
* notices were collected before validation gave up (the caller's own validate
|
|
1183
|
+
* call reports the failure), so this is safe to run beside a persist.
|
|
1184
|
+
*/
|
|
1185
|
+
export function sequenceDefinitionNotices(input, options = {}) {
|
|
1186
|
+
const notices = [];
|
|
1187
|
+
try {
|
|
1188
|
+
validateSequenceDefinition(input, { ...options, notices });
|
|
1189
|
+
}
|
|
1190
|
+
catch {
|
|
1191
|
+
// Reported by the caller's own validation; the notices gathered so far stand.
|
|
1192
|
+
}
|
|
1193
|
+
return notices;
|
|
1194
|
+
}
|
|
1179
1195
|
export function lintSequenceDefinition(input, options = {}) {
|
|
1180
1196
|
try {
|
|
1181
1197
|
validateSequenceDefinition(input, options);
|
|
@@ -1378,6 +1394,14 @@ raw, index, options, issues) {
|
|
|
1378
1394
|
const cc = optionalEmailList(raw.cc, `${path}.cc`, issues);
|
|
1379
1395
|
const bcc = optionalEmailList(raw.bcc, `${path}.bcc`, issues);
|
|
1380
1396
|
const sendWindow = normalizeSendWindow(raw.send_window, `${path}.send_window`, issues);
|
|
1397
|
+
// Say so. The rewrite is deliberate compatibility, but a definition read back
|
|
1398
|
+
// with a different `kind` than was written, and nothing in the response to
|
|
1399
|
+
// explain it, reads as data loss to the author.
|
|
1400
|
+
options.notices?.push({
|
|
1401
|
+
path: `${path}.kind`,
|
|
1402
|
+
code: "email_reply_rewritten",
|
|
1403
|
+
message: `step '${id}': kind 'email_reply' is retired and was stored as 'email_send' with no subject_template — the threaded follow-up. It goes out as "Re: <the previous email's subject>" inside the lead's previous thread; give it a subject_template of its own to start a new email instead.`,
|
|
1404
|
+
});
|
|
1381
1405
|
return {
|
|
1382
1406
|
id, channel: "email", kind: "email_send", body_template: body ?? "",
|
|
1383
1407
|
...(bodyHtml !== undefined ? { body_html_template: bodyHtml } : {}),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Release metadata is a string API: a new version must not change the exported
|
|
2
2
|
// declaration type and invalidate every dependent package's compiler state.
|
|
3
|
-
export const OXYGEN_VERSION = "1.
|
|
3
|
+
export const OXYGEN_VERSION = "1.987.20";
|
|
4
4
|
// The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
|
|
5
5
|
// operational route. Raising it hard-rejects every older CLI from the entire
|
|
6
6
|
// product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
|
|
@@ -176,6 +176,16 @@
|
|
|
176
176
|
"import": "./dist/copilot-journeys.js",
|
|
177
177
|
"default": "./dist/copilot-journeys.js"
|
|
178
178
|
},
|
|
179
|
+
"./copilot-playbooks": {
|
|
180
|
+
"types": "./dist/copilot-playbooks.d.ts",
|
|
181
|
+
"import": "./dist/copilot-playbooks.js",
|
|
182
|
+
"default": "./dist/copilot-playbooks.js"
|
|
183
|
+
},
|
|
184
|
+
"./copilot-skills": {
|
|
185
|
+
"types": "./dist/copilot-skills.d.ts",
|
|
186
|
+
"import": "./dist/copilot-skills.js",
|
|
187
|
+
"default": "./dist/copilot-skills.js"
|
|
188
|
+
},
|
|
179
189
|
"./hosted-ai": {
|
|
180
190
|
"types": "./dist/hosted-ai.d.ts",
|
|
181
191
|
"import": "./dist/hosted-ai.js",
|
|
@@ -22,11 +22,10 @@ export declare const workflowCodeV1Schema: {
|
|
|
22
22
|
readonly type: "object";
|
|
23
23
|
readonly maxProperties: 100;
|
|
24
24
|
readonly additionalProperties: {
|
|
25
|
+
readonly type: "object";
|
|
26
|
+
readonly required: readonly ["type"];
|
|
25
27
|
readonly description: "One explicit oxygen-js-v1 input binding. Knowledge context refs are excluded in Code v1; bind trigger, step, loop, literal, template, or formula data explicitly.";
|
|
26
28
|
readonly properties: {
|
|
27
|
-
readonly type: {
|
|
28
|
-
readonly enum: readonly ["literal", "template", "ref", "formula"];
|
|
29
|
-
};
|
|
30
29
|
readonly value: {
|
|
31
30
|
readonly description: "literal: used verbatim. template: interpolated string, e.g. 'Hi {{ steps.find.output.first_name }}'.";
|
|
32
31
|
};
|
|
@@ -46,9 +45,10 @@ export declare const workflowCodeV1Schema: {
|
|
|
46
45
|
readonly type: "string";
|
|
47
46
|
readonly description: "context_asset: the Knowledge-layer asset id.";
|
|
48
47
|
};
|
|
48
|
+
readonly type: {
|
|
49
|
+
readonly enum: readonly ["literal", "template", "ref", "formula"];
|
|
50
|
+
};
|
|
49
51
|
};
|
|
50
|
-
readonly type: "object";
|
|
51
|
-
readonly required: readonly ["type"];
|
|
52
52
|
};
|
|
53
53
|
readonly default: {};
|
|
54
54
|
};
|
|
@@ -680,11 +680,10 @@ export declare const workflowGraphManifestSchema: {
|
|
|
680
680
|
readonly type: "object";
|
|
681
681
|
readonly maxProperties: 100;
|
|
682
682
|
readonly additionalProperties: {
|
|
683
|
+
readonly type: "object";
|
|
684
|
+
readonly required: readonly ["type"];
|
|
683
685
|
readonly description: "One explicit oxygen-js-v1 input binding. Knowledge context refs are excluded in Code v1; bind trigger, step, loop, literal, template, or formula data explicitly.";
|
|
684
686
|
readonly properties: {
|
|
685
|
-
readonly type: {
|
|
686
|
-
readonly enum: readonly ["literal", "template", "ref", "formula"];
|
|
687
|
-
};
|
|
688
687
|
readonly value: {
|
|
689
688
|
readonly description: "literal: used verbatim. template: interpolated string, e.g. 'Hi {{ steps.find.output.first_name }}'.";
|
|
690
689
|
};
|
|
@@ -704,9 +703,10 @@ export declare const workflowGraphManifestSchema: {
|
|
|
704
703
|
readonly type: "string";
|
|
705
704
|
readonly description: "context_asset: the Knowledge-layer asset id.";
|
|
706
705
|
};
|
|
706
|
+
readonly type: {
|
|
707
|
+
readonly enum: readonly ["literal", "template", "ref", "formula"];
|
|
708
|
+
};
|
|
707
709
|
};
|
|
708
|
-
readonly type: "object";
|
|
709
|
-
readonly required: readonly ["type"];
|
|
710
710
|
};
|
|
711
711
|
readonly default: {};
|
|
712
712
|
};
|
|
@@ -1470,11 +1470,10 @@ export declare const portableWorkflowDefinitionSchema: {
|
|
|
1470
1470
|
readonly type: "object";
|
|
1471
1471
|
readonly maxProperties: 100;
|
|
1472
1472
|
readonly additionalProperties: {
|
|
1473
|
+
readonly type: "object";
|
|
1474
|
+
readonly required: readonly ["type"];
|
|
1473
1475
|
readonly description: "One explicit oxygen-js-v1 input binding. Knowledge context refs are excluded in Code v1; bind trigger, step, loop, literal, template, or formula data explicitly.";
|
|
1474
1476
|
readonly properties: {
|
|
1475
|
-
readonly type: {
|
|
1476
|
-
readonly enum: readonly ["literal", "template", "ref", "formula"];
|
|
1477
|
-
};
|
|
1478
1477
|
readonly value: {
|
|
1479
1478
|
readonly description: "literal: used verbatim. template: interpolated string, e.g. 'Hi {{ steps.find.output.first_name }}'.";
|
|
1480
1479
|
};
|
|
@@ -1494,9 +1493,10 @@ export declare const portableWorkflowDefinitionSchema: {
|
|
|
1494
1493
|
readonly type: "string";
|
|
1495
1494
|
readonly description: "context_asset: the Knowledge-layer asset id.";
|
|
1496
1495
|
};
|
|
1496
|
+
readonly type: {
|
|
1497
|
+
readonly enum: readonly ["literal", "template", "ref", "formula"];
|
|
1498
|
+
};
|
|
1497
1499
|
};
|
|
1498
|
-
readonly type: "object";
|
|
1499
|
-
readonly required: readonly ["type"];
|
|
1500
1500
|
};
|
|
1501
1501
|
readonly default: {};
|
|
1502
1502
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxygen-agent/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.987.20",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -38,18 +38,21 @@
|
|
|
38
38
|
"@langfuse/otel": "^5.11.0",
|
|
39
39
|
"@langfuse/tracing": "^5.11.0",
|
|
40
40
|
"@opentelemetry/api": "1.9.1",
|
|
41
|
+
"@opentelemetry/resources": "2.10.0",
|
|
41
42
|
"@opentelemetry/sdk-trace-base": "2.10.0",
|
|
42
43
|
"commander": "14.0.3",
|
|
43
44
|
"esbuild": "0.27.7",
|
|
44
45
|
"read-excel-file": "6.0.3",
|
|
45
|
-
"typescript": "6.0.
|
|
46
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
46
47
|
"yaml": "2.9.0",
|
|
48
|
+
"@oxygen/cli-ugc": "0.0.0",
|
|
47
49
|
"@oxygen/formula": "0.0.0",
|
|
48
50
|
"@oxygen/recipe-sdk": "0.0.0",
|
|
49
51
|
"@oxygen/shared": "0.0.0",
|
|
50
52
|
"@oxygen/workflows": "0.0.0"
|
|
51
53
|
},
|
|
52
54
|
"bundledDependencies": [
|
|
55
|
+
"@oxygen/cli-ugc",
|
|
53
56
|
"@oxygen/formula",
|
|
54
57
|
"@oxygen/recipe-sdk",
|
|
55
58
|
"@oxygen/shared",
|