@oxygen-agent/cli 1.1014.1035 → 1.1014.1132

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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +1 -0
  3. package/dist/commands/columns.js +1 -1
  4. package/dist/commands/deliverability.js +2 -0
  5. package/dist/commands/sequences.js +30 -2
  6. package/dist/commands/viewers.js +1 -2
  7. package/dist/credentials.js +1 -1
  8. package/dist/customer-operation-commands.js +62 -3
  9. package/dist/help.js +1 -1
  10. package/dist/index.d.ts +0 -97
  11. package/dist/index.js +0 -98
  12. package/dist/workspace-placement-commands.d.ts +3 -0
  13. package/dist/workspace-placement-commands.js +42 -0
  14. package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +6 -1
  15. package/node_modules/@oxygen/shared/dist/agent-sessions.js +18 -6
  16. package/node_modules/@oxygen/shared/dist/agents-enabled.d.ts +8 -9
  17. package/node_modules/@oxygen/shared/dist/agents-enabled.js +12 -8
  18. package/node_modules/@oxygen/shared/dist/capability-discovery.js +24 -6
  19. package/node_modules/@oxygen/shared/dist/column-autofill.js +2 -2
  20. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +1 -1
  21. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
  22. package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +5 -5
  23. package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.d.ts +30 -0
  24. package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.js +53 -0
  25. package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.d.ts +120 -0
  26. package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.js +48 -0
  27. package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.d.ts +267 -0
  28. package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.js +63 -0
  29. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +6 -0
  30. package/node_modules/@oxygen/shared/dist/contracts/operations.js +9 -4
  31. package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.d.ts +2 -0
  32. package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.js +3 -0
  33. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +9 -8
  34. package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +32 -2
  35. package/node_modules/@oxygen/shared/dist/data-suppliers.js +44 -4
  36. package/node_modules/@oxygen/shared/dist/email-tracking-token.d.ts +2 -0
  37. package/node_modules/@oxygen/shared/dist/email-tracking-token.js +1 -0
  38. package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +3 -4
  39. package/node_modules/@oxygen/shared/dist/feature-gates.js +3 -4
  40. package/node_modules/@oxygen/shared/dist/file-import.d.ts +46 -0
  41. package/node_modules/@oxygen/shared/dist/file-import.js +138 -0
  42. package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +8 -0
  43. package/node_modules/@oxygen/shared/dist/guided-company-files.d.ts +13 -0
  44. package/node_modules/@oxygen/shared/dist/guided-company-files.js +14 -0
  45. package/node_modules/@oxygen/shared/dist/guided-first-sourcing.d.ts +116 -0
  46. package/node_modules/@oxygen/shared/dist/guided-first-sourcing.js +11 -0
  47. package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.d.ts +4 -0
  48. package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.js +42 -0
  49. package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.d.ts +22 -0
  50. package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.js +38 -0
  51. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.d.ts +147 -0
  52. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.d.ts +12 -0
  53. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.js +83 -0
  54. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.js +44 -0
  55. package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.d.ts +7 -0
  56. package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.js +13 -0
  57. package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.d.ts +4 -0
  58. package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.js +34 -0
  59. package/node_modules/@oxygen/shared/dist/guided-trial-billing.d.ts +26 -0
  60. package/node_modules/@oxygen/shared/dist/guided-trial-billing.js +19 -0
  61. package/node_modules/@oxygen/shared/dist/hosted-ai.js +108 -118
  62. package/node_modules/@oxygen/shared/dist/knowledge-links.d.ts +29 -0
  63. package/node_modules/@oxygen/shared/dist/knowledge-links.js +84 -1
  64. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +5 -14
  65. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +22 -5
  66. package/node_modules/@oxygen/shared/dist/managed-scraper-backend.d.ts +18 -0
  67. package/node_modules/@oxygen/shared/dist/managed-scraper-backend.js +17 -0
  68. package/node_modules/@oxygen/shared/dist/teammates.d.ts +7 -5
  69. package/node_modules/@oxygen/shared/dist/teammates.js +11 -20
  70. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +88 -0
  71. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  72. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  73. package/node_modules/@oxygen/shared/dist/workspace-placement-view.d.ts +150 -0
  74. package/node_modules/@oxygen/shared/dist/workspace-placement-view.js +102 -0
  75. package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +84 -0
  76. package/node_modules/@oxygen/shared/dist/workspace-placement.js +107 -0
  77. package/package.json +1 -1
@@ -45,6 +45,7 @@ function parseTokenPayload(body) {
45
45
  ...(uh ? { uh } : {}),
46
46
  ...(typeof candidate.seq === "string" && candidate.seq ? { seq: candidate.seq } : {}),
47
47
  ...(typeof candidate.enr === "string" && candidate.enr ? { enr: candidate.enr } : {}),
48
+ ...(candidate.diagnostic === true ? { diagnostic: true } : {}),
48
49
  iat: typeof candidate.iat === "number" && Number.isFinite(candidate.iat) ? candidate.iat : 0,
49
50
  };
50
51
  }
@@ -71,10 +71,9 @@ export type FeatureGateResolution = {
71
71
  * - OXYGEN_AGENTS_ENABLED — (b). Authorizes autonomous Agent run execution —
72
72
  * tool authority AND credit spend (`apps/web/src/lib/agent-runtime.ts`,
73
73
  * `apps/worker/src/agent-cycle.ts`). Same class as publishing.
74
- * - OXYGEN_AGENTS_WORKSPACE_IDS — (b). Which workspaces that execution may run
75
- * in (`packages/shared/src/agents-enabled.ts`); production also requires its
76
- * reviewed PRODUCTION_AGENT_WORKSPACE_IDS, so a wrong "on" for a customer's
77
- * workspace there needs a code change as well.
74
+ * - OXYGEN_AGENTS_WORKSPACE_IDS — (b). Which workspaces the "staff" stage of
75
+ * that execution may run in (`packages/shared/src/agents-enabled.ts`);
76
+ * production also requires its reviewed PRODUCTION_AGENT_WORKSPACE_IDS.
78
77
  * - OXYGEN_REPRICING_2026_09_EFFECTIVE_AT — (b). The instant every held price
79
78
  * rise and limit cut of the 2026-09 repricing takes effect
80
79
  * (`packages/shared/src/repricing.ts`). A remotely flippable value would let
@@ -62,10 +62,9 @@
62
62
  * - OXYGEN_AGENTS_ENABLED — (b). Authorizes autonomous Agent run execution —
63
63
  * tool authority AND credit spend (`apps/web/src/lib/agent-runtime.ts`,
64
64
  * `apps/worker/src/agent-cycle.ts`). Same class as publishing.
65
- * - OXYGEN_AGENTS_WORKSPACE_IDS — (b). Which workspaces that execution may run
66
- * in (`packages/shared/src/agents-enabled.ts`); production also requires its
67
- * reviewed PRODUCTION_AGENT_WORKSPACE_IDS, so a wrong "on" for a customer's
68
- * workspace there needs a code change as well.
65
+ * - OXYGEN_AGENTS_WORKSPACE_IDS — (b). Which workspaces the "staff" stage of
66
+ * that execution may run in (`packages/shared/src/agents-enabled.ts`);
67
+ * production also requires its reviewed PRODUCTION_AGENT_WORKSPACE_IDS.
69
68
  * - OXYGEN_REPRICING_2026_09_EFFECTIVE_AT — (b). The instant every held price
70
69
  * rise and limit cut of the 2026-09 repricing takes effect
71
70
  * (`packages/shared/src/repricing.ts`). A remotely flippable value would let
@@ -76,6 +76,52 @@ export declare function normalizeRowsForNewTable(rows: Record<string, unknown>[]
76
76
  */
77
77
  export declare function dropBlankImportCells(row: Record<string, unknown>, keepKey?: string | null): Record<string, unknown>;
78
78
  export declare function normalizeImportColumnKey(value: string): string;
79
+ /**
80
+ * Read `ignore_source_keys`: the headers a mapping step set to "Don't import".
81
+ * A JSON array (staged body) or its JSON text (multipart form field); headers
82
+ * are matched verbatim, so they are not trimmed. Anything malformed reads as
83
+ * no choice at all, never as a reason to fail the import.
84
+ */
85
+ export declare function readImportHeaderList(value: unknown): string[];
86
+ /** A column of the table an import writes into. */
87
+ export type ImportTargetColumn = {
88
+ key: string;
89
+ label?: string | null;
90
+ };
91
+ /** Why a file column was not written. */
92
+ export type IgnoredImportHeaderReason = "not_selected" | "no_matching_column" | "duplicate_target" | "system_column";
93
+ export type IgnoredImportHeader = {
94
+ header: string;
95
+ reason: IgnoredImportHeaderReason;
96
+ column?: string;
97
+ };
98
+ /**
99
+ * Decide, once per file header, which column of an EXISTING table it lands in.
100
+ *
101
+ * File imports used to hand every header to the row writer verbatim, and the
102
+ * writer refuses an unknown key for the whole chunk. A standard export carries
103
+ * columns the table does not have — Baserow puts an `id` first — so on
104
+ * 2026-10-12 and 2026-10-15 three workspaces' CRM imports wrote nothing at all
105
+ * and showed only `invalid_row_values`. Headers now resolve in a fixed order:
106
+ * an explicit choice (`explicitMap`, `ignore`) first, then the column key, then
107
+ * the column's label or the normalized key. A header that resolves to no
108
+ * column, or to a column another header already fills, is left out and
109
+ * reported, never guessed: two headers writing one column would overwrite each
110
+ * other. Rows keep their cells; only the keys are renamed or dropped.
111
+ */
112
+ export declare function createImportHeaderProjector(input: {
113
+ columns: readonly ImportTargetColumn[];
114
+ explicitMap?: Readonly<Record<string, string>> | null;
115
+ ignore?: readonly string[] | null;
116
+ }): {
117
+ /** Resolve any headers first seen in these rows; earlier decisions stand. */
118
+ observe(rows: readonly Record<string, unknown>[]): void;
119
+ /** The row with every key renamed to its column, unwritten headers dropped. */
120
+ project(row: Record<string, unknown>): Record<string, unknown>;
121
+ /** header → column for every header that writes; for a preview or a receipt. */
122
+ mapping(): Record<string, string>;
123
+ ignored(): IgnoredImportHeader[];
124
+ };
79
125
  /**
80
126
  * Refuse a JSON or XLSX file over the buffered-parse ceiling before anything is
81
127
  * uploaded or queued. CSV and JSONL stream, so they pass at any size and only
@@ -236,6 +236,144 @@ export function dropBlankImportCells(row, keepKey) {
236
236
  export function normalizeImportColumnKey(value) {
237
237
  return toSnakeIdentifier(value, "column");
238
238
  }
239
+ /**
240
+ * Read `ignore_source_keys`: the headers a mapping step set to "Don't import".
241
+ * A JSON array (staged body) or its JSON text (multipart form field); headers
242
+ * are matched verbatim, so they are not trimmed. Anything malformed reads as
243
+ * no choice at all, never as a reason to fail the import.
244
+ */
245
+ export function readImportHeaderList(value) {
246
+ let parsed = value;
247
+ if (typeof value === "string") {
248
+ try {
249
+ parsed = JSON.parse(value);
250
+ }
251
+ catch {
252
+ return [];
253
+ }
254
+ }
255
+ if (!Array.isArray(parsed))
256
+ return [];
257
+ return parsed.filter((entry) => typeof entry === "string" && entry.length > 0).slice(0, 1_000);
258
+ }
259
+ /**
260
+ * Decide, once per file header, which column of an EXISTING table it lands in.
261
+ *
262
+ * File imports used to hand every header to the row writer verbatim, and the
263
+ * writer refuses an unknown key for the whole chunk. A standard export carries
264
+ * columns the table does not have — Baserow puts an `id` first — so on
265
+ * 2026-10-12 and 2026-10-15 three workspaces' CRM imports wrote nothing at all
266
+ * and showed only `invalid_row_values`. Headers now resolve in a fixed order:
267
+ * an explicit choice (`explicitMap`, `ignore`) first, then the column key, then
268
+ * the column's label or the normalized key. A header that resolves to no
269
+ * column, or to a column another header already fills, is left out and
270
+ * reported, never guessed: two headers writing one column would overwrite each
271
+ * other. Rows keep their cells; only the keys are renamed or dropped.
272
+ */
273
+ export function createImportHeaderProjector(input) {
274
+ const exact = new Map();
275
+ const aliases = new Map();
276
+ for (const column of input.columns) {
277
+ if (!column.key || column.key.startsWith("_"))
278
+ continue;
279
+ exact.set(column.key, column.key);
280
+ for (const alias of [
281
+ column.label?.trim(),
282
+ normalizeImportColumnKey(column.key),
283
+ column.label ? normalizeImportColumnKey(column.label) : undefined,
284
+ ]) {
285
+ if (!alias)
286
+ continue;
287
+ const existing = aliases.get(alias);
288
+ if (existing === undefined)
289
+ aliases.set(alias, column.key);
290
+ else if (existing !== column.key)
291
+ aliases.set(alias, null);
292
+ }
293
+ }
294
+ const explicitMap = input.explicitMap ?? {};
295
+ const ignore = new Set(input.ignore ?? []);
296
+ const keyByHeader = new Map();
297
+ const ignored = new Map();
298
+ const claimed = new Set();
299
+ const decide = (headers) => {
300
+ const pending = headers.filter((header) => !keyByHeader.has(header) && !ignored.has(header));
301
+ const aliasCandidates = new Map();
302
+ const skip = (header, reason, column) => ignored.set(header, { header, reason, ...(column ? { column } : {}) });
303
+ const claim = (header, key) => {
304
+ keyByHeader.set(header, key);
305
+ claimed.add(key);
306
+ };
307
+ // Explicit choices and exact keys claim their columns before any alias can.
308
+ for (const header of pending) {
309
+ if (ignore.has(header)) {
310
+ skip(header, "not_selected");
311
+ continue;
312
+ }
313
+ const explicit = explicitMap[header];
314
+ if (explicit) {
315
+ claim(header, explicit);
316
+ continue;
317
+ }
318
+ const key = exact.get(header) ?? exact.get(header.trim());
319
+ if (key && !claimed.has(key))
320
+ claim(header, key);
321
+ }
322
+ for (const header of pending) {
323
+ if (keyByHeader.has(header) || ignored.has(header))
324
+ continue;
325
+ const exactKey = exact.get(header) ?? exact.get(header.trim());
326
+ if (exactKey) {
327
+ skip(header, "duplicate_target", exactKey);
328
+ continue;
329
+ }
330
+ // `_created_at` must not alias `created_at`: system keys are never written.
331
+ if (header.startsWith("_")) {
332
+ skip(header, "system_column");
333
+ continue;
334
+ }
335
+ const alias = aliases.get(header.trim()) ?? aliases.get(normalizeImportColumnKey(header)) ?? null;
336
+ if (!alias) {
337
+ skip(header, "no_matching_column");
338
+ continue;
339
+ }
340
+ aliasCandidates.set(alias, [...(aliasCandidates.get(alias) ?? []), header]);
341
+ }
342
+ for (const [key, candidates] of aliasCandidates) {
343
+ if (candidates.length === 1 && !claimed.has(key)) {
344
+ claim(candidates[0], key);
345
+ continue;
346
+ }
347
+ for (const header of candidates)
348
+ skip(header, "duplicate_target", key);
349
+ }
350
+ };
351
+ return {
352
+ /** Resolve any headers first seen in these rows; earlier decisions stand. */
353
+ observe(rows) {
354
+ decide(inferImportColumnLabels(rows));
355
+ },
356
+ /** The row with every key renamed to its column, unwritten headers dropped. */
357
+ project(row) {
358
+ const projected = {};
359
+ for (const [header, value] of Object.entries(row)) {
360
+ if (!keyByHeader.has(header) && !ignored.has(header))
361
+ decide([header]);
362
+ const key = keyByHeader.get(header);
363
+ if (key !== undefined)
364
+ projected[key] = value;
365
+ }
366
+ return projected;
367
+ },
368
+ /** header → column for every header that writes; for a preview or a receipt. */
369
+ mapping() {
370
+ return Object.fromEntries(keyByHeader);
371
+ },
372
+ ignored() {
373
+ return [...ignored.values()];
374
+ },
375
+ };
376
+ }
239
377
  async function parseXlsxRows(buffer, options) {
240
378
  const rows = await parseXlsxWorkbookBuffer(buffer, options);
241
379
  const [header, ...body] = rows;
@@ -8,6 +8,14 @@ export type GuidedActivationRoutine = {
8
8
  export declare const GUIDED_ACTIVATION_POINTERS: readonly ["knowledge", "first_leads_table", "sequence", "sender", "oxygent", "direct_chat", "scan_routine", "briefing_routine"];
9
9
  export type GuidedActivationPointer = typeof GUIDED_ACTIVATION_POINTERS[number];
10
10
  export type GuidedActivationResources = Partial<Record<GuidedActivationPointer, string>>;
11
+ /** Read projection only. Historical resource/Start identities stay unchanged. */
12
+ export type GuidedActivationConversation = {
13
+ source_chat_id: string;
14
+ canonical_chat_id: string;
15
+ oxygent_id: string;
16
+ visibility: "workspace" | "personal";
17
+ history_migration_pending: boolean;
18
+ };
11
19
  export declare const GUIDED_ACTIVATION_REVISIONS: readonly ["configuration", "sequence", "audience", "recipients", "sender", "copy", "knowledge", "schedule", "authority"];
12
20
  export type GuidedActivationRevisions = Record<typeof GUIDED_ACTIVATION_REVISIONS[number], string>;
13
21
  export type GuidedActivationBounds = {
@@ -0,0 +1,13 @@
1
+ /** Browser-safe budgets for the native guided company-file owner and all transports. */
2
+ export declare const GUIDED_COMPANY_FILE_LIMITS: {
3
+ readonly textBytes: 1500000;
4
+ readonly pdfBytes: 5242880;
5
+ readonly retainedWorkspaceBytes: 1073741824;
6
+ readonly bodyTimeoutMs: 15000;
7
+ readonly storageTimeoutMs: 15000;
8
+ readonly bodyLimitBytes: 5308416;
9
+ readonly nameBytes: 255;
10
+ };
11
+ export declare const GUIDED_COMPANY_FILE_CONTENT_TYPES: readonly ["text/plain", "text/markdown", "application/pdf"];
12
+ export type GuidedCompanyFileContentType = typeof GUIDED_COMPANY_FILE_CONTENT_TYPES[number];
13
+ export declare function guidedCompanyFileByteLimit(contentType: GuidedCompanyFileContentType): number;
@@ -0,0 +1,14 @@
1
+ /** Browser-safe budgets for the native guided company-file owner and all transports. */
2
+ export const GUIDED_COMPANY_FILE_LIMITS = {
3
+ textBytes: 1_500_000,
4
+ pdfBytes: 5_242_880,
5
+ retainedWorkspaceBytes: 1_073_741_824,
6
+ bodyTimeoutMs: 15_000,
7
+ storageTimeoutMs: 15_000,
8
+ bodyLimitBytes: 5_308_416,
9
+ nameBytes: 255,
10
+ };
11
+ export const GUIDED_COMPANY_FILE_CONTENT_TYPES = ["text/plain", "text/markdown", "application/pdf"];
12
+ export function guidedCompanyFileByteLimit(contentType) {
13
+ return contentType === "application/pdf" ? GUIDED_COMPANY_FILE_LIMITS.pdfBytes : GUIDED_COMPANY_FILE_LIMITS.textBytes;
14
+ }
@@ -0,0 +1,116 @@
1
+ import type { GuidedOnboardingAnswers, GuidedOnboardingProspectLimitation } from "./guided-onboarding-journey.js";
2
+ export type GuidedFirstSourcingOwner = {
3
+ organizationId: string;
4
+ actorId: string;
5
+ activationId: string;
6
+ };
7
+ export type GuidedFirstSourcingBounds = {
8
+ targetCount: number;
9
+ maxPages: number;
10
+ maxCredits: number;
11
+ };
12
+ export type GuidedFirstSourcingCriteria = {
13
+ sourceKey: string;
14
+ digest: string;
15
+ company: NonNullable<GuidedOnboardingAnswers["company_review"]>;
16
+ targeting: {
17
+ filters: Record<string, unknown>;
18
+ signals: GuidedOnboardingAnswers["signals"];
19
+ limitations: GuidedOnboardingProspectLimitation[];
20
+ };
21
+ };
22
+ export type GuidedFirstSourcingQuote = {
23
+ toolId: "blitzapi.people_search";
24
+ perPageCredits: number;
25
+ revision: string;
26
+ canRun: boolean;
27
+ };
28
+ export type GuidedFirstSourcingPreview = {
29
+ id: string;
30
+ activationId: string;
31
+ actorId: string;
32
+ activationRevision: number;
33
+ tableId: string;
34
+ criteria: GuidedFirstSourcingCriteria;
35
+ bounds: GuidedFirstSourcingBounds;
36
+ quote: GuidedFirstSourcingQuote & {
37
+ pages: number;
38
+ estimatedCredits: number;
39
+ };
40
+ expiresAt: string;
41
+ digest: string;
42
+ };
43
+ /** Current authority witness, distinct from the provider query's stable identity.
44
+ * Confirmation revisions, journey generation, navigation and preview/cap expiry
45
+ * cannot turn the same approved native source into another first pull. */
46
+ export declare function guidedFirstSourcingAudienceWitness(answers: GuidedOnboardingAnswers): {
47
+ company: string | null;
48
+ company_size: import("./guided-onboarding-journey.js").GuidedOnboardingChoice | null;
49
+ markets: import("./guided-onboarding-journey.js").GuidedOnboardingChoice | null;
50
+ industries: import("./guided-onboarding-journey.js").GuidedOnboardingChoice | null;
51
+ personas: import("./guided-onboarding-journey.js").GuidedOnboardingChoice | null;
52
+ signals: {
53
+ hiring: import("./guided-onboarding-journey.js").GuidedOnboardingSignal;
54
+ technology: import("./guided-onboarding-journey.js").GuidedOnboardingTechnologySignal;
55
+ } | null;
56
+ };
57
+ /** Optional GET scope for a current native NIGHTLY quote. The timezone is a
58
+ * customer/browser IANA suggestion; it never confirms or saves a schedule. */
59
+ export type GuidedFirstSourcingDefaultsRequest = {
60
+ revision: number;
61
+ generation: number;
62
+ timezoneHint: string;
63
+ };
64
+ /** Structural projection of E's actual readonly producer; no native import in shared. */
65
+ export type GuidedFirstSourcingRoutineDefaults = {
66
+ initial: {
67
+ sourceIds: string[];
68
+ limits: {
69
+ max_examined: number;
70
+ max_new_leads: number;
71
+ max_provider_pages: number;
72
+ max_provider_credits: number;
73
+ };
74
+ scanBudget: {
75
+ per_run_credits: number;
76
+ daily_credits: number;
77
+ };
78
+ briefingBudget: {
79
+ per_run_credits: number;
80
+ daily_credits: number;
81
+ };
82
+ };
83
+ sourceNames: Record<string, string>;
84
+ quote: {
85
+ provider_credits_per_run: number;
86
+ inference_credits_per_run: 0;
87
+ source_quotes: Array<{
88
+ source_id: string;
89
+ provider_credits_per_page: number;
90
+ incremental_mode: "cursor" | "watermark";
91
+ }>;
92
+ };
93
+ };
94
+ export type GuidedFirstSourcingRoutineDefaultsScope = GuidedFirstSourcingOwner & GuidedFirstSourcingDefaultsRequest & {
95
+ sourceKey: string;
96
+ };
97
+ /** Observation metadata stays outside form defaults. Quoted time alone must
98
+ * never reset edits; consumers reject responses outside their current scope. */
99
+ export type GuidedFirstSourcingRoutineDefaultsReadback = {
100
+ routineDefaults: null;
101
+ routineDefaultsScope: null;
102
+ } | {
103
+ routineDefaults: null;
104
+ routineDefaultsScope: GuidedFirstSourcingRoutineDefaultsScope & {
105
+ state: "not_ready";
106
+ };
107
+ } | {
108
+ routineDefaults: GuidedFirstSourcingRoutineDefaults;
109
+ routineDefaultsScope: GuidedFirstSourcingRoutineDefaultsScope & {
110
+ state: "ready";
111
+ quoteDigest: string;
112
+ quotedAt: string;
113
+ defaultMaxCredits: number;
114
+ defaultsDigest: string;
115
+ };
116
+ };
@@ -0,0 +1,11 @@
1
+ /** Current authority witness, distinct from the provider query's stable identity.
2
+ * Confirmation revisions, journey generation, navigation and preview/cap expiry
3
+ * cannot turn the same approved native source into another first pull. */
4
+ export function guidedFirstSourcingAudienceWitness(answers) {
5
+ return {
6
+ company: answers.company_review?.statement ?? null,
7
+ company_size: answers.audience_company_size ?? null, markets: answers.audience_markets ?? null,
8
+ industries: answers.audience_industries ?? null, personas: answers.audience_personas ?? null,
9
+ signals: answers.signals ?? null,
10
+ };
11
+ }
@@ -0,0 +1,4 @@
1
+ import type { GuidedOnboardingAnswers, GuidedOnboardingStep } from "./guided-onboarding-journey.js";
2
+ /** Only the current step can be edited, never native billing/resources/research.
3
+ * Validation does not claim a file is owned, a connection exists or results ran. */
4
+ export declare function normalizeGuidedOnboardingAnswers(step: GuidedOnboardingStep, input: unknown): GuidedOnboardingAnswers;
@@ -0,0 +1,42 @@
1
+ import { OxygenError } from "./cli-result.js";
2
+ import { validateContract } from "./contracts/schema.js";
3
+ import { GUIDED_ONBOARDING_ANSWERS_CONTRACT } from "./contracts/guided-onboarding-journey.js";
4
+ import { normalizeGuidedOnboardingCompanyContext } from "./guided-onboarding-company-context.js";
5
+ import { normalizeGuidedOnboardingSignals } from "./guided-onboarding-signals.js";
6
+ const fail = (message) => { throw new OxygenError("invalid_onboarding_answer", message, { exitCode: 2 }); };
7
+ const clean = (value) => value.normalize("NFKC").trim().replace(/\s+/gu, " ");
8
+ const choices = (values) => {
9
+ const names = values.map(clean);
10
+ if (names.some(value => !value))
11
+ return fail("Choose a nonempty audience value.");
12
+ return [...new Map(names.map(value => [value.toLowerCase(), value])).values()];
13
+ };
14
+ /** Only the current step can be edited, never native billing/resources/research.
15
+ * Validation does not claim a file is owned, a connection exists or results ran. */
16
+ export function normalizeGuidedOnboardingAnswers(step, input) {
17
+ const checked = validateContract(GUIDED_ONBOARDING_ANSWERS_CONTRACT, input, { unknown: "reject", overMax: "reject" });
18
+ if (!checked.ok)
19
+ return fail("Use the reviewed answer fields for this onboarding step.");
20
+ const answers = checked.value;
21
+ if (Object.keys(answers).some(key => key !== step))
22
+ return fail("Save only the current step's answers.");
23
+ if (answers.company_context)
24
+ answers.company_context = normalizeGuidedOnboardingCompanyContext(answers.company_context);
25
+ if (answers.company_review) {
26
+ const statement = answers.company_review.statement.trim();
27
+ if (!statement || !/^(?:0|[1-9]\d*)$/.test(answers.company_review.profile_revision))
28
+ return fail("Review a company statement at the current native profile revision.");
29
+ answers.company_review = { ...answers.company_review, statement };
30
+ }
31
+ for (const key of ["audience_company_size", "audience_markets", "audience_industries", "audience_personas"]) {
32
+ if (answers[key])
33
+ answers[key] = { values: choices(answers[key].values) };
34
+ }
35
+ if (answers.signals)
36
+ answers.signals = normalizeGuidedOnboardingSignals(answers.signals);
37
+ if (answers.prospects)
38
+ answers.prospects = { ...answers.prospects, feedback: answers.prospects.feedback.trim() };
39
+ if (answers.tour)
40
+ answers.tour = { visited: [...new Set(answers.tour.visited)] };
41
+ return answers;
42
+ }
@@ -0,0 +1,22 @@
1
+ import type { GuidedOnboardingAnswers } from "./guided-onboarding-journey.js";
2
+ /** One native-supported input budget for web/CLI/MCP and D research preparation. */
3
+ export declare const GUIDED_ONBOARDING_COMPANY_CONTEXT_LIMITS: {
4
+ readonly descriptionChars: 6000;
5
+ readonly contextLinks: 5;
6
+ readonly fileIds: 5;
7
+ readonly nameChars: 160;
8
+ readonly urlChars: 2000;
9
+ };
10
+ export declare const GUIDED_ONBOARDING_COMPANY_CONTEXT_CONTRACT: import("./contracts/schema.js").ContractObject<{
11
+ readonly website: {
12
+ oneOf: [import("./contracts/schema.js").StringProperty, import("./contracts/schema.js").NullProperty];
13
+ description?: string;
14
+ };
15
+ readonly name: import("./contracts/schema.js").StringProperty;
16
+ readonly description: import("./contracts/schema.js").StringProperty;
17
+ readonly context_links: import("./contracts/schema.js").ArrayProperty<import("./contracts/schema.js").StringProperty>;
18
+ readonly file_ids: import("./contracts/schema.js").ArrayProperty<import("./contracts/schema.js").StringProperty>;
19
+ }, "context_links" | "description" | "file_ids" | "name" | "website">;
20
+ /** Input-only rule; existing evidence links retain their independent navigation policy. */
21
+ export declare function normalizeGuidedOnboardingContextLink(value: string): string;
22
+ export declare function normalizeGuidedOnboardingCompanyContext(input: unknown): NonNullable<GuidedOnboardingAnswers["company_context"]>;
@@ -0,0 +1,38 @@
1
+ import { OxygenError } from "./cli-result.js";
2
+ import { arr, nullable, obj, str, validateContract } from "./contracts/schema.js";
3
+ /** One native-supported input budget for web/CLI/MCP and D research preparation. */
4
+ export const GUIDED_ONBOARDING_COMPANY_CONTEXT_LIMITS = {
5
+ descriptionChars: 6000, contextLinks: 5, fileIds: 5, nameChars: 160, urlChars: 2000,
6
+ };
7
+ const limits = GUIDED_ONBOARDING_COMPANY_CONTEXT_LIMITS;
8
+ export const GUIDED_ONBOARDING_COMPANY_CONTEXT_CONTRACT = obj({
9
+ website: nullable(str({ maxLength: limits.urlChars })), name: str({ maxLength: limits.nameChars }),
10
+ description: str({ maxLength: limits.descriptionChars }),
11
+ context_links: arr(str({ minLength: 1, maxLength: limits.urlChars }), { maxItems: limits.contextLinks }),
12
+ file_ids: arr(str({ minLength: 1, maxLength: 160 }), { maxItems: limits.fileIds }),
13
+ }, { required: ["website", "name", "description", "context_links", "file_ids"] });
14
+ const fail = (message) => { throw new OxygenError("invalid_onboarding_answer", message, { exitCode: 2 }); };
15
+ /** Input-only rule; existing evidence links retain their independent navigation policy. */
16
+ export function normalizeGuidedOnboardingContextLink(value) {
17
+ try {
18
+ const url = new URL(value.includes("://") ? value : `https://${value}`);
19
+ if (url.protocol !== "https:" || url.username || url.password || !url.hostname.includes("."))
20
+ return fail("Use an HTTPS website or context link without credentials.");
21
+ return url.href;
22
+ }
23
+ catch {
24
+ return fail("Use a valid HTTPS website or context link.");
25
+ }
26
+ }
27
+ export function normalizeGuidedOnboardingCompanyContext(input) {
28
+ const checked = validateContract(GUIDED_ONBOARDING_COMPANY_CONTEXT_CONTRACT, input, { unknown: "reject", overMax: "reject" });
29
+ if (!checked.ok)
30
+ return fail("Use up to 6000 description characters, five context links and five Knowledge files.");
31
+ const value = checked.value;
32
+ const website = value.website?.trim() ? normalizeGuidedOnboardingContextLink(value.website.trim()) : null;
33
+ const description = value.description.trim();
34
+ if (!website && !description)
35
+ return fail("Provide a website or describe your company.");
36
+ return { website, name: value.name.normalize("NFKC").trim().replace(/\s+/gu, " "), description,
37
+ context_links: [...new Set(value.context_links.map(link => normalizeGuidedOnboardingContextLink(link.trim())))], file_ids: [...new Set(value.file_ids)] };
38
+ }