@oxygen-agent/cli 1.377.3 → 1.591.1

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 (96) hide show
  1. package/README.md +1 -1
  2. package/dist/column-run-notices.d.ts +11 -0
  3. package/dist/column-run-notices.js +37 -0
  4. package/dist/command-manifest.js +13 -8
  5. package/dist/help.js +79 -16
  6. package/dist/index.js +3812 -460
  7. package/dist/skills.js +106 -1
  8. package/node_modules/@oxygen/formula/dist/coerce.d.ts +8 -0
  9. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  10. package/node_modules/@oxygen/formula/dist/evaluate.d.ts +31 -0
  11. package/node_modules/@oxygen/formula/dist/evaluate.js +248 -0
  12. package/node_modules/@oxygen/formula/dist/expression.d.ts +64 -0
  13. package/node_modules/@oxygen/formula/dist/expression.js +428 -0
  14. package/node_modules/@oxygen/formula/dist/formula-functions.d.ts +71 -0
  15. package/node_modules/@oxygen/formula/dist/formula-functions.js +1100 -0
  16. package/node_modules/@oxygen/formula/dist/index.d.ts +17 -0
  17. package/node_modules/@oxygen/formula/dist/index.js +17 -0
  18. package/node_modules/@oxygen/formula/dist/value-normalizers.d.ts +30 -0
  19. package/node_modules/@oxygen/formula/dist/value-normalizers.js +80 -0
  20. package/node_modules/@oxygen/formula/package.json +26 -0
  21. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +30 -0
  22. package/node_modules/@oxygen/recipe-sdk/dist/index.js +2 -2
  23. package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +60 -0
  24. package/node_modules/@oxygen/shared/dist/billing-anchors.js +135 -0
  25. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -5
  26. package/node_modules/@oxygen/shared/dist/billing.js +185 -8
  27. package/node_modules/@oxygen/shared/dist/call-outcomes.d.ts +59 -0
  28. package/node_modules/@oxygen/shared/dist/call-outcomes.js +73 -0
  29. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  30. package/node_modules/@oxygen/shared/dist/credit-guidance.js +3 -1
  31. package/node_modules/@oxygen/shared/dist/crm-reply-events.d.ts +35 -0
  32. package/node_modules/@oxygen/shared/dist/crm-reply-events.js +31 -0
  33. package/node_modules/@oxygen/shared/dist/dial-guardrail-overrides.d.ts +50 -0
  34. package/node_modules/@oxygen/shared/dist/dial-guardrail-overrides.js +65 -0
  35. package/node_modules/@oxygen/shared/dist/directory.d.ts +1 -1
  36. package/node_modules/@oxygen/shared/dist/directory.js +1 -0
  37. package/node_modules/@oxygen/shared/dist/file-import.js +58 -11
  38. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +15 -0
  39. package/node_modules/@oxygen/shared/dist/hosted-ai.js +19 -0
  40. package/node_modules/@oxygen/shared/dist/index.d.ts +9 -0
  41. package/node_modules/@oxygen/shared/dist/index.js +9 -0
  42. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.d.ts +31 -0
  43. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.js +56 -0
  44. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +5 -4
  45. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +5 -4
  46. package/node_modules/@oxygen/shared/dist/linkedin-url.d.ts +22 -0
  47. package/node_modules/@oxygen/shared/dist/linkedin-url.js +7 -4
  48. package/node_modules/@oxygen/shared/dist/log.js +41 -2
  49. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.d.ts +7 -0
  50. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.js +29 -0
  51. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +31 -0
  52. package/node_modules/@oxygen/shared/dist/object-storage.js +61 -0
  53. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +636 -0
  54. package/node_modules/@oxygen/shared/dist/plan-limits.js +199 -0
  55. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +89 -23
  56. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +88 -24
  57. package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +291 -0
  58. package/node_modules/@oxygen/shared/dist/sequence-crm-events.js +224 -0
  59. package/node_modules/@oxygen/shared/dist/sequence-template.d.ts +42 -1
  60. package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
  61. package/node_modules/@oxygen/shared/dist/sequences.d.ts +287 -24
  62. package/node_modules/@oxygen/shared/dist/sequences.js +940 -60
  63. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +70 -0
  64. package/node_modules/@oxygen/shared/dist/spend-safety.js +106 -0
  65. package/node_modules/@oxygen/shared/dist/tags.d.ts +90 -1
  66. package/node_modules/@oxygen/shared/dist/tags.js +122 -6
  67. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  68. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  69. package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.d.ts +1 -1
  70. package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.js +4 -0
  71. package/node_modules/@oxygen/shared/package.json +95 -0
  72. package/node_modules/@oxygen/workflows/dist/event-dispatch.d.ts +126 -0
  73. package/node_modules/@oxygen/workflows/dist/event-dispatch.js +173 -0
  74. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +78 -0
  75. package/node_modules/@oxygen/workflows/dist/graph/expression.js +700 -0
  76. package/node_modules/@oxygen/workflows/dist/graph/index.d.ts +20 -0
  77. package/node_modules/@oxygen/workflows/dist/graph/index.js +20 -0
  78. package/node_modules/@oxygen/workflows/dist/graph/lint.d.ts +4 -0
  79. package/node_modules/@oxygen/workflows/dist/graph/lint.js +812 -0
  80. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +501 -0
  81. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +200 -0
  82. package/node_modules/@oxygen/workflows/dist/graph/params.d.ts +86 -0
  83. package/node_modules/@oxygen/workflows/dist/graph/params.js +173 -0
  84. package/node_modules/@oxygen/workflows/dist/graph/remap.d.ts +48 -0
  85. package/node_modules/@oxygen/workflows/dist/graph/remap.js +213 -0
  86. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +46 -0
  87. package/node_modules/@oxygen/workflows/dist/graph/topology.js +280 -0
  88. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +270 -0
  89. package/node_modules/@oxygen/workflows/dist/graph/types.js +93 -0
  90. package/node_modules/@oxygen/workflows/dist/index.d.ts +113 -1
  91. package/node_modules/@oxygen/workflows/dist/index.js +179 -13
  92. package/node_modules/@oxygen/workflows/dist/tool-effects.d.ts +1 -0
  93. package/node_modules/@oxygen/workflows/dist/tool-effects.js +19 -0
  94. package/node_modules/@oxygen/workflows/dist/usage-estimate.js +135 -4
  95. package/node_modules/@oxygen/workflows/package.json +4 -0
  96. package/package.json +7 -5
@@ -0,0 +1,70 @@
1
+ import type { PlanTier } from "./billing.js";
2
+ import type { LimitsTier } from "./plan-limits.js";
3
+ /**
4
+ * Spend-safety defaults: plan-scaled ceilings applied when an autonomous
5
+ * spend path was armed WITHOUT an explicit cap. They restore the ADR-0011
6
+ * doctrine that every cron/webhook/event trigger persists a per-delivery
7
+ * ceiling — absence of a cap is no longer unbounded. Explicit user caps always
8
+ * win and are never clamped down; `null` means no default applies (enterprise
9
+ * runs on custom limits; free has no BYOK).
10
+ *
11
+ * Values anchor to ≥ one day-equivalent of the plan's monthly credits
12
+ * (monthlyCredits / 30), orders of magnitude above typical per-delivery spend,
13
+ * so the ceiling binds on runaway loops, not legitimate heavy runs.
14
+ */
15
+ /** Where an effective cap came from: caller-declared or a plan-tier default. */
16
+ export type EffectiveCapSource = "explicit" | "trigger_default";
17
+ /** Workflow run trigger types that fire without a human in the loop. */
18
+ export declare const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES: ReadonlySet<string>;
19
+ /**
20
+ * Per-run managed-credit ceiling for a LIVE workflow run fired by an autonomous
21
+ * trigger when neither run metadata nor the manifest declares `max_credits`.
22
+ */
23
+ export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<PlanTier, number | null>;
24
+ /**
25
+ * Per-delivery credit ceiling stamped onto a standing/webhook table auto-run
26
+ * batch when the armed configuration carries no explicit `max_credits`.
27
+ */
28
+ export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<PlanTier, number | null>;
29
+ /**
30
+ * Row ceiling for a BYOK AI-column run whose caller gave no explicit row bound
31
+ * (no limit, no row_ids, selection "all"). BYOK bills the customer's own
32
+ * provider account, so a credit ceiling is meaningless — row count is the
33
+ * enforceable unit. An explicit limit (up to the 500k platform row cap) wins.
34
+ */
35
+ export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<PlanTier, number | null>;
36
+ /**
37
+ * Default per-provider DAILY call cap for BYOK provider traffic (the customer's
38
+ * own key). Short-window pacing defaults already exist in provider-fetch; this
39
+ * bounds daily volume. Warn-first: enforcement flips only after telemetry and
40
+ * with the self-serve override surface live. A workspace
41
+ * provider_rate_limit_policies row with windowSeconds=86400 overrides it.
42
+ */
43
+ export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<PlanTier, number | null>;
44
+ export declare const BYOK_PROVIDER_DAILY_WINDOW_SECONDS: number;
45
+ /**
46
+ * Implicit org-level DAILY budget guard, evaluated only when the org has no
47
+ * explicit org-scope daily budget policy and the plan has finite positive
48
+ * monthly credits. Warn when one rolling day spends past WARN_PCT of the
49
+ * monthly grant; hard-block at BLOCK_PCT (a loop burning a whole month in a
50
+ * day is near-certainly runaway — this also protects large top-up balances).
51
+ * Setting any explicit org-daily policy fully suppresses the implicit pair.
52
+ */
53
+ export declare const DEFAULT_ORG_DAILY_SPEND_WARN_PCT = 0.25;
54
+ export declare const DEFAULT_ORG_DAILY_SPEND_BLOCK_PCT = 1;
55
+ export declare function resolveDefaultTriggerRunCreditCeiling(tier: PlanTier): number | null;
56
+ export declare function resolveDefaultAutoRunBatchCreditCeiling(tier: PlanTier): number | null;
57
+ export declare function resolveDefaultByokColumnRunMaxRows(tier: PlanTier): number | null;
58
+ export declare function resolveDefaultByokProviderDailyCallCap(tier: PlanTier): number | null;
59
+ /**
60
+ * The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
61
+ * collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
62
+ * a distinct "enterprise" column (null = runs on custom limits) — so prefer the
63
+ * raw plan_tier whenever it is itself a table key, and fall back to the limits rung
64
+ * (always a valid PlanTier key) when plan_tier is absent or an unknown/legacy
65
+ * string (e.g. a Stripe price nickname). Single source of truth for the
66
+ * `oxygen limits` report, the org-facing provider-limits API, and the BYOK
67
+ * daily-cap enforcement resolver — they must agree on which column an org reads.
68
+ */
69
+ export declare const SPEND_SAFETY_PLAN_TIERS: readonly PlanTier[];
70
+ export declare function resolveSpendSafetyPlanTier(planTier: string | null, limitsTier: LimitsTier): PlanTier;
@@ -0,0 +1,106 @@
1
+ /** Workflow run trigger types that fire without a human in the loop. */
2
+ export const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES = new Set([
3
+ "cron",
4
+ "webhook",
5
+ "event",
6
+ ]);
7
+ /**
8
+ * Per-run managed-credit ceiling for a LIVE workflow run fired by an autonomous
9
+ * trigger when neither run metadata nor the manifest declares `max_credits`.
10
+ */
11
+ export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
12
+ free: 500,
13
+ starter: 5_000,
14
+ pro: 10_000,
15
+ team: 25_000,
16
+ scale: 250_000,
17
+ enterprise: null,
18
+ };
19
+ /**
20
+ * Per-delivery credit ceiling stamped onto a standing/webhook table auto-run
21
+ * batch when the armed configuration carries no explicit `max_credits`.
22
+ */
23
+ export const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING = {
24
+ free: 500,
25
+ starter: 5_000,
26
+ pro: 10_000,
27
+ team: 25_000,
28
+ scale: 250_000,
29
+ enterprise: null,
30
+ };
31
+ /**
32
+ * Row ceiling for a BYOK AI-column run whose caller gave no explicit row bound
33
+ * (no limit, no row_ids, selection "all"). BYOK bills the customer's own
34
+ * provider account, so a credit ceiling is meaningless — row count is the
35
+ * enforceable unit. An explicit limit (up to the 500k platform row cap) wins.
36
+ */
37
+ export const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS = {
38
+ free: null,
39
+ starter: 25_000,
40
+ pro: 50_000,
41
+ team: 100_000,
42
+ scale: 250_000,
43
+ enterprise: null,
44
+ };
45
+ /**
46
+ * Default per-provider DAILY call cap for BYOK provider traffic (the customer's
47
+ * own key). Short-window pacing defaults already exist in provider-fetch; this
48
+ * bounds daily volume. Warn-first: enforcement flips only after telemetry and
49
+ * with the self-serve override surface live. A workspace
50
+ * provider_rate_limit_policies row with windowSeconds=86400 overrides it.
51
+ */
52
+ export const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP = {
53
+ free: null,
54
+ starter: 10_000,
55
+ pro: 25_000,
56
+ team: 50_000,
57
+ scale: 200_000,
58
+ enterprise: null,
59
+ };
60
+ export const BYOK_PROVIDER_DAILY_WINDOW_SECONDS = 24 * 60 * 60;
61
+ /**
62
+ * Implicit org-level DAILY budget guard, evaluated only when the org has no
63
+ * explicit org-scope daily budget policy and the plan has finite positive
64
+ * monthly credits. Warn when one rolling day spends past WARN_PCT of the
65
+ * monthly grant; hard-block at BLOCK_PCT (a loop burning a whole month in a
66
+ * day is near-certainly runaway — this also protects large top-up balances).
67
+ * Setting any explicit org-daily policy fully suppresses the implicit pair.
68
+ */
69
+ export const DEFAULT_ORG_DAILY_SPEND_WARN_PCT = 0.25;
70
+ export const DEFAULT_ORG_DAILY_SPEND_BLOCK_PCT = 1;
71
+ export function resolveDefaultTriggerRunCreditCeiling(tier) {
72
+ return DEFAULT_TRIGGER_RUN_CREDIT_CEILING[tier];
73
+ }
74
+ export function resolveDefaultAutoRunBatchCreditCeiling(tier) {
75
+ return DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING[tier];
76
+ }
77
+ export function resolveDefaultByokColumnRunMaxRows(tier) {
78
+ return DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS[tier];
79
+ }
80
+ export function resolveDefaultByokProviderDailyCallCap(tier) {
81
+ return DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP[tier];
82
+ }
83
+ /**
84
+ * The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
85
+ * collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
86
+ * a distinct "enterprise" column (null = runs on custom limits) — so prefer the
87
+ * raw plan_tier whenever it is itself a table key, and fall back to the limits rung
88
+ * (always a valid PlanTier key) when plan_tier is absent or an unknown/legacy
89
+ * string (e.g. a Stripe price nickname). Single source of truth for the
90
+ * `oxygen limits` report, the org-facing provider-limits API, and the BYOK
91
+ * daily-cap enforcement resolver — they must agree on which column an org reads.
92
+ */
93
+ export const SPEND_SAFETY_PLAN_TIERS = [
94
+ "free",
95
+ "starter",
96
+ "pro",
97
+ "team",
98
+ "scale",
99
+ "enterprise",
100
+ ];
101
+ export function resolveSpendSafetyPlanTier(planTier, limitsTier) {
102
+ if (planTier && SPEND_SAFETY_PLAN_TIERS.includes(planTier)) {
103
+ return planTier;
104
+ }
105
+ return limitsTier;
106
+ }
@@ -1,7 +1,45 @@
1
+ import type { TagColor } from "./select-options.js";
1
2
  /** Primitive kinds that participate in the workspace tag union today. */
2
- export declare const TAG_KINDS: readonly ["knowledge_page", "publishing_post", "sequence", "table", "workflow", "recipe", "conversation", "mailbox", "sender", "record"];
3
+ export declare const TAG_KINDS: readonly ["knowledge_page", "publishing_post", "sequence", "table", "workflow", "recipe", "conversation", "mailbox", "sender", "record", "domain", "project", "engagement_watch"];
3
4
  export type TagKind = (typeof TAG_KINDS)[number];
5
+ /**
6
+ * The taggable kinds in prose, for CLI `--help` and MCP tool descriptions.
7
+ *
8
+ * Lives next to {@link TAG_KINDS} because those descriptions are the *discovery*
9
+ * surface: a blind-user eval (2026-07-21) found the tags help still advertising
10
+ * the original seven kinds, so a user reading `tags --help` had no way to learn
11
+ * that domains, projects, or engagement watches were taggable at all — they had
12
+ * to stumble on each owning group's own help. One string, one import, and
13
+ * `TAG_KIND_PROSE_MARKERS` fails the build/test if a new kind skips it.
14
+ */
15
+ export declare const TAG_KINDS_PROSE: string;
16
+ /**
17
+ * The phrase each kind must contribute to {@link TAG_KINDS_PROSE}. Typed as a
18
+ * total map over TagKind, so adding a kind is a compile error until it is named
19
+ * here, and the paired test asserts the phrase actually appears in the prose.
20
+ */
21
+ export declare const TAG_KIND_PROSE_MARKERS: Record<TagKind, string>;
4
22
  export declare function isTagKind(value: unknown): value is TagKind;
23
+ /**
24
+ * How each kind is named when a tag's footprint is listed. Total over TagKind,
25
+ * so adding a kind is a compile error until it is labelled — the same guarantee
26
+ * {@link TAG_KIND_PROSE_MARKERS} gives the help text.
27
+ *
28
+ * `many` is sentence-form with proper nouns preserved ("CRM records"), which is
29
+ * what an inline count wants ("3 CRM records"). Headings run it through
30
+ * {@link tagKindHeading}, which only uppercases a leading lowercase letter, so
31
+ * "CRM records" survives intact where a naive capitalize would yield "Crm".
32
+ * Three hand-maintained copies of this map used to drift (the /tags page said
33
+ * "knowledge", the detail page and the MCP widget said "Knowledge pages").
34
+ */
35
+ export declare const TAG_KIND_LABELS: Record<TagKind, {
36
+ one: string;
37
+ many: string;
38
+ }>;
39
+ /** Inline label for a count: `tagKindLabel("sequence", 1)` → "sequence". */
40
+ export declare function tagKindLabel(kind: string, count: number): string;
41
+ /** Section heading for a kind: "Knowledge pages", "CRM records". */
42
+ export declare function tagKindHeading(kind: string): string;
5
43
  /** Hard cap per tagged item — matches the wiki's long-standing limit. */
6
44
  export declare const MAX_TAGS_PER_ITEM = 50;
7
45
  /**
@@ -12,3 +50,54 @@ export declare const MAX_TAGS_PER_ITEM = 50;
12
50
  export declare function normalizeTagList(values: readonly unknown[]): string[];
13
51
  /** Lenient variant: `undefined`/`null`/non-arrays normalize to `[]`. */
14
52
  export declare function normalizeTags(value: unknown): string[];
53
+ /**
54
+ * Registry-only caps (ADR 0017). Deliberately NOT enforced by
55
+ * {@link normalizeTagList}: that runs on every writer's hot path, so adding a
56
+ * length filter there would silently drop an existing long tag the next time
57
+ * any row carrying it was rewritten. A longer tag stays legal to carry — it
58
+ * just cannot be declared.
59
+ */
60
+ export declare const MAX_TAG_LABEL_LENGTH = 200;
61
+ export declare const MAX_TAG_DESCRIPTION_LENGTH = 500;
62
+ /**
63
+ * Normalize ONE label the way {@link normalizeTagList} normalizes each element,
64
+ * and reject what the registry cannot store: non-strings, blanks, and anything
65
+ * over {@link MAX_TAG_LABEL_LENGTH}. Returns `null` so callers can raise their
66
+ * own typed error.
67
+ *
68
+ * Note this lowercases and trims but does NOT slugify — "Q3 Outbound" becomes
69
+ * "q3 outbound", space intact. Any character is legal in a tag.
70
+ */
71
+ export declare function normalizeTagLabel(value: unknown): string | null;
72
+ /**
73
+ * One tag as every read surface sees it: carrier counts unioned across stores,
74
+ * plus the registry annotation when the tag is declared.
75
+ *
76
+ * Canonical here rather than in the web app because two consumers used to
77
+ * declare it independently (`WorkspaceTagIndexEntry` server-side and
78
+ * `WorkspaceTagVocabularyEntry` in the TagPicker). Extra properties are
79
+ * structurally compatible in TypeScript, so widening one and not the other
80
+ * compiled silently and the picker simply never saw the new fields.
81
+ *
82
+ * `color` is the explicit OVERRIDE or null — never the derived color. Resolving
83
+ * `tagColorForLabel` here would force every chip render to consult the index
84
+ * instead of computing locally, which is the property that lets undeclared tags
85
+ * stay color-stable with nothing stored.
86
+ */
87
+ export type WorkspaceTagEntry = {
88
+ tag: string;
89
+ /** Carriers only. 0 for a declared tag nothing carries yet. */
90
+ total: number;
91
+ counts: Partial<Record<TagKind, number>>;
92
+ /** Has a registry row. */
93
+ declared: boolean;
94
+ description: string | null;
95
+ color: TagColor | null;
96
+ pinned: boolean;
97
+ };
98
+ /**
99
+ * Index ordering, shared by the web page, the CLI/MCP payload, and the picker.
100
+ * Pinned first matters beyond the tags page: the picker truncates its suggestion
101
+ * list, and a curated tag with few carriers would otherwise fall off that cliff.
102
+ */
103
+ export declare function compareWorkspaceTagEntries(a: Pick<WorkspaceTagEntry, "tag" | "total" | "pinned">, b: Pick<WorkspaceTagEntry, "tag" | "total" | "pinned">): number;
@@ -1,9 +1,15 @@
1
- // Workspace tags (Tags primitive, ADR 0010): free-form lowercase labels shared
2
- // across primitives so campaign knowledge links across stores — a sequence, its
3
- // campaign-learnings wiki page, the source table, and the workflow that feeds it
4
- // can all carry `q3-outbound`. Storage stays per-store (`tags text[]` + GIN,
5
- // the 0009/0025 pattern); this module is the one vocabulary: which kinds
6
- // participate, and how a tag list is normalized before it is written anywhere.
1
+ // Workspace tags (Tags primitive, ADR 0010; registry per ADR 0017): free-form
2
+ // lowercase labels shared across primitives so campaign knowledge links across
3
+ // stores — a sequence, its campaign-learnings wiki page, the source table, and
4
+ // the workflow that feeds it can all carry `q3-outbound`. Carrier storage stays
5
+ // per-store (`tags text[]` + GIN, the 0009/0025 pattern); this module is the one
6
+ // vocabulary: which kinds participate, and how a tag is normalized before it is
7
+ // written anywhere.
8
+ //
9
+ // Since ADR 0017 a tag exists iff it is DECLARED (a row in the tenant registry,
10
+ // `ox_context.workspace_tags`) OR CARRIED (some store's tags array holds it).
11
+ // The registry is an ANNOTATION table — description, color override, pinned —
12
+ // and never records which items carry a tag. Drift is legal in both directions.
7
13
  /** Primitive kinds that participate in the workspace tag union today. */
8
14
  export const TAG_KINDS = [
9
15
  "knowledge_page",
@@ -16,10 +22,86 @@ export const TAG_KINDS = [
16
22
  "mailbox",
17
23
  "sender",
18
24
  "record",
25
+ "domain",
26
+ "project",
27
+ "engagement_watch",
19
28
  ];
29
+ /**
30
+ * The taggable kinds in prose, for CLI `--help` and MCP tool descriptions.
31
+ *
32
+ * Lives next to {@link TAG_KINDS} because those descriptions are the *discovery*
33
+ * surface: a blind-user eval (2026-07-21) found the tags help still advertising
34
+ * the original seven kinds, so a user reading `tags --help` had no way to learn
35
+ * that domains, projects, or engagement watches were taggable at all — they had
36
+ * to stumble on each owning group's own help. One string, one import, and
37
+ * `TAG_KIND_PROSE_MARKERS` fails the build/test if a new kind skips it.
38
+ */
39
+ export const TAG_KINDS_PROSE = "publishing posts, knowledge pages, sequences, tables, workflows, recipes, " +
40
+ "inbox conversations, CRM records, mailboxes, sender accounts, sending domains, " +
41
+ "projects, and engagement watches";
42
+ /**
43
+ * The phrase each kind must contribute to {@link TAG_KINDS_PROSE}. Typed as a
44
+ * total map over TagKind, so adding a kind is a compile error until it is named
45
+ * here, and the paired test asserts the phrase actually appears in the prose.
46
+ */
47
+ export const TAG_KIND_PROSE_MARKERS = {
48
+ knowledge_page: "knowledge page",
49
+ publishing_post: "publishing post",
50
+ sequence: "sequence",
51
+ table: "table",
52
+ workflow: "workflow",
53
+ recipe: "recipe",
54
+ conversation: "inbox conversation",
55
+ mailbox: "mailbox",
56
+ sender: "sender account",
57
+ record: "CRM record",
58
+ domain: "sending domain",
59
+ project: "project",
60
+ engagement_watch: "engagement watch",
61
+ };
20
62
  export function isTagKind(value) {
21
63
  return typeof value === "string" && TAG_KINDS.includes(value);
22
64
  }
65
+ /**
66
+ * How each kind is named when a tag's footprint is listed. Total over TagKind,
67
+ * so adding a kind is a compile error until it is labelled — the same guarantee
68
+ * {@link TAG_KIND_PROSE_MARKERS} gives the help text.
69
+ *
70
+ * `many` is sentence-form with proper nouns preserved ("CRM records"), which is
71
+ * what an inline count wants ("3 CRM records"). Headings run it through
72
+ * {@link tagKindHeading}, which only uppercases a leading lowercase letter, so
73
+ * "CRM records" survives intact where a naive capitalize would yield "Crm".
74
+ * Three hand-maintained copies of this map used to drift (the /tags page said
75
+ * "knowledge", the detail page and the MCP widget said "Knowledge pages").
76
+ */
77
+ export const TAG_KIND_LABELS = {
78
+ knowledge_page: { one: "knowledge page", many: "knowledge pages" },
79
+ publishing_post: { one: "publishing post", many: "publishing posts" },
80
+ sequence: { one: "sequence", many: "sequences" },
81
+ table: { one: "table", many: "tables" },
82
+ workflow: { one: "workflow", many: "workflows" },
83
+ recipe: { one: "recipe", many: "recipes" },
84
+ conversation: { one: "conversation", many: "conversations" },
85
+ mailbox: { one: "mailbox", many: "mailboxes" },
86
+ sender: { one: "sender account", many: "sender accounts" },
87
+ record: { one: "CRM record", many: "CRM records" },
88
+ domain: { one: "domain", many: "domains" },
89
+ project: { one: "project", many: "projects" },
90
+ engagement_watch: { one: "engagement watch", many: "engagement watches" },
91
+ };
92
+ /** Inline label for a count: `tagKindLabel("sequence", 1)` → "sequence". */
93
+ export function tagKindLabel(kind, count) {
94
+ const entry = isTagKind(kind) ? TAG_KIND_LABELS[kind] : null;
95
+ if (!entry)
96
+ return kind;
97
+ return count === 1 ? entry.one : entry.many;
98
+ }
99
+ /** Section heading for a kind: "Knowledge pages", "CRM records". */
100
+ export function tagKindHeading(kind) {
101
+ const entry = isTagKind(kind) ? TAG_KIND_LABELS[kind] : null;
102
+ const label = entry ? entry.many : kind;
103
+ return label.charAt(0).toUpperCase() + label.slice(1);
104
+ }
23
105
  /** Hard cap per tagged item — matches the wiki's long-standing limit. */
24
106
  export const MAX_TAGS_PER_ITEM = 50;
25
107
  /**
@@ -36,3 +118,37 @@ export function normalizeTags(value) {
36
118
  return [];
37
119
  return normalizeTagList(value);
38
120
  }
121
+ /**
122
+ * Registry-only caps (ADR 0017). Deliberately NOT enforced by
123
+ * {@link normalizeTagList}: that runs on every writer's hot path, so adding a
124
+ * length filter there would silently drop an existing long tag the next time
125
+ * any row carrying it was rewritten. A longer tag stays legal to carry — it
126
+ * just cannot be declared.
127
+ */
128
+ export const MAX_TAG_LABEL_LENGTH = 200;
129
+ export const MAX_TAG_DESCRIPTION_LENGTH = 500;
130
+ /**
131
+ * Normalize ONE label the way {@link normalizeTagList} normalizes each element,
132
+ * and reject what the registry cannot store: non-strings, blanks, and anything
133
+ * over {@link MAX_TAG_LABEL_LENGTH}. Returns `null` so callers can raise their
134
+ * own typed error.
135
+ *
136
+ * Note this lowercases and trims but does NOT slugify — "Q3 Outbound" becomes
137
+ * "q3 outbound", space intact. Any character is legal in a tag.
138
+ */
139
+ export function normalizeTagLabel(value) {
140
+ if (typeof value !== "string")
141
+ return null;
142
+ const normalized = value.trim().toLowerCase();
143
+ if (!normalized || normalized.length > MAX_TAG_LABEL_LENGTH)
144
+ return null;
145
+ return normalized;
146
+ }
147
+ /**
148
+ * Index ordering, shared by the web page, the CLI/MCP payload, and the picker.
149
+ * Pinned first matters beyond the tags page: the picker truncates its suggestion
150
+ * list, and a curated tag with few carriers would otherwise fall off that cliff.
151
+ */
152
+ export function compareWorkspaceTagEntries(a, b) {
153
+ return Number(b.pinned) - Number(a.pinned) || b.total - a.total || a.tag.localeCompare(b.tag);
154
+ }
@@ -1,3 +1,3 @@
1
- export declare const OXYGEN_VERSION = "1.377.3";
1
+ export declare const OXYGEN_VERSION = "1.591.1";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
3
  export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.377.3";
1
+ export const OXYGEN_VERSION = "1.591.1";
2
2
  // The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
3
3
  // operational route. Raising it hard-rejects every older CLI from the entire
4
4
  // product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
@@ -1,2 +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"];
1
+ export declare const WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS: readonly ["consecutive_failure_count", "consecutive_capacity_failure_count", "last_failure_code", "last_failure_at", "auto_paused_at", "auto_pause_reason"];
2
2
  export declare function clearWorkflowTriggerAutoPauseMetadata(metadata: Record<string, unknown>): Record<string, unknown>;
@@ -1,5 +1,9 @@
1
1
  export const WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS = [
2
2
  "consecutive_failure_count",
3
+ // The provider-capacity streak, counted independently of the deterministic one
4
+ // so the two classes cannot borrow each other's budget. Listed here so a
5
+ // success — or a re-enable — clears it through the same single path.
6
+ "consecutive_capacity_failure_count",
3
7
  "last_failure_code",
4
8
  "last_failure_at",
5
9
  "auto_paused_at",
@@ -11,15 +11,110 @@
11
11
  "import": "./dist/index.js",
12
12
  "default": "./dist/index.js"
13
13
  },
14
+ "./directory": {
15
+ "types": "./dist/directory.d.ts",
16
+ "import": "./dist/directory.js",
17
+ "default": "./dist/directory.js"
18
+ },
14
19
  "./file-import": {
15
20
  "types": "./dist/file-import.d.ts",
16
21
  "import": "./dist/file-import.js",
17
22
  "default": "./dist/file-import.js"
18
23
  },
24
+ "./linkedin-url": {
25
+ "types": "./dist/linkedin-url.d.ts",
26
+ "import": "./dist/linkedin-url.js",
27
+ "default": "./dist/linkedin-url.js"
28
+ },
29
+ "./cell-format": {
30
+ "types": "./dist/cell-format.d.ts",
31
+ "import": "./dist/cell-format.js",
32
+ "default": "./dist/cell-format.js"
33
+ },
34
+ "./column-types": {
35
+ "types": "./dist/column-types.d.ts",
36
+ "import": "./dist/column-types.js",
37
+ "default": "./dist/column-types.js"
38
+ },
39
+ "./object-storage": {
40
+ "types": "./dist/object-storage.d.ts",
41
+ "import": "./dist/object-storage.js",
42
+ "default": "./dist/object-storage.js"
43
+ },
44
+ "./select-options": {
45
+ "types": "./dist/select-options.d.ts",
46
+ "import": "./dist/select-options.js",
47
+ "default": "./dist/select-options.js"
48
+ },
49
+ "./suppression-entries": {
50
+ "types": "./dist/suppression-entries.d.ts",
51
+ "import": "./dist/suppression-entries.js",
52
+ "default": "./dist/suppression-entries.js"
53
+ },
54
+ "./cli-result": {
55
+ "types": "./dist/cli-result.d.ts",
56
+ "import": "./dist/cli-result.js",
57
+ "default": "./dist/cli-result.js"
58
+ },
19
59
  "./custom-http-safety": {
20
60
  "types": "./dist/custom-http-safety.d.ts",
21
61
  "import": "./dist/custom-http-safety.js",
22
62
  "default": "./dist/custom-http-safety.js"
63
+ },
64
+ "./search-vocab": {
65
+ "types": "./dist/search-vocab.d.ts",
66
+ "import": "./dist/search-vocab.js",
67
+ "default": "./dist/search-vocab.js"
68
+ },
69
+ "./signup-lead-webhook": {
70
+ "types": "./dist/signup-lead-webhook.d.ts",
71
+ "import": "./dist/signup-lead-webhook.js",
72
+ "default": "./dist/signup-lead-webhook.js"
73
+ },
74
+ "./knowledge-constants": {
75
+ "types": "./dist/knowledge-constants.d.ts",
76
+ "import": "./dist/knowledge-constants.js",
77
+ "default": "./dist/knowledge-constants.js"
78
+ },
79
+ "./workflow-status-change": {
80
+ "types": "./dist/workflow-status-change.d.ts",
81
+ "import": "./dist/workflow-status-change.js",
82
+ "default": "./dist/workflow-status-change.js"
83
+ },
84
+ "./billing": {
85
+ "types": "./dist/billing.d.ts",
86
+ "import": "./dist/billing.js",
87
+ "default": "./dist/billing.js"
88
+ },
89
+ "./pricing-sheet": {
90
+ "types": "./dist/pricing-sheet.d.ts",
91
+ "import": "./dist/pricing-sheet.js",
92
+ "default": "./dist/pricing-sheet.js"
93
+ },
94
+ "./copilot-journeys": {
95
+ "types": "./dist/copilot-journeys.d.ts",
96
+ "import": "./dist/copilot-journeys.js",
97
+ "default": "./dist/copilot-journeys.js"
98
+ },
99
+ "./hosted-ai": {
100
+ "types": "./dist/hosted-ai.d.ts",
101
+ "import": "./dist/hosted-ai.js",
102
+ "default": "./dist/hosted-ai.js"
103
+ },
104
+ "./sequences": {
105
+ "types": "./dist/sequences.d.ts",
106
+ "import": "./dist/sequences.js",
107
+ "default": "./dist/sequences.js"
108
+ },
109
+ "./tags": {
110
+ "types": "./dist/tags.d.ts",
111
+ "import": "./dist/tags.js",
112
+ "default": "./dist/tags.js"
113
+ },
114
+ "./schedule-label": {
115
+ "types": "./dist/schedule-label.d.ts",
116
+ "import": "./dist/schedule-label.js",
117
+ "default": "./dist/schedule-label.js"
23
118
  }
24
119
  },
25
120
  "dependencies": {}