@oxygen-agent/cli 1.365.3 → 1.575.19

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 (99) 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 +78 -16
  6. package/dist/index.js +3873 -514
  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 +101 -5
  26. package/node_modules/@oxygen/shared/dist/billing.js +192 -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/hosted-ai.d.ts +17 -1
  38. package/node_modules/@oxygen/shared/dist/hosted-ai.js +52 -3
  39. package/node_modules/@oxygen/shared/dist/index.d.ts +11 -0
  40. package/node_modules/@oxygen/shared/dist/index.js +15 -0
  41. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +77 -0
  42. package/node_modules/@oxygen/shared/dist/langfuse.js +231 -0
  43. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.d.ts +31 -0
  44. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.js +56 -0
  45. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +5 -4
  46. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +5 -4
  47. package/node_modules/@oxygen/shared/dist/linkedin-url.d.ts +22 -0
  48. package/node_modules/@oxygen/shared/dist/linkedin-url.js +7 -4
  49. package/node_modules/@oxygen/shared/dist/log.js +56 -4
  50. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.d.ts +7 -0
  51. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.js +29 -0
  52. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +31 -0
  53. package/node_modules/@oxygen/shared/dist/object-storage.js +61 -0
  54. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +636 -0
  55. package/node_modules/@oxygen/shared/dist/plan-limits.js +199 -0
  56. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +89 -23
  57. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +88 -24
  58. package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +291 -0
  59. package/node_modules/@oxygen/shared/dist/sequence-crm-events.js +224 -0
  60. package/node_modules/@oxygen/shared/dist/sequence-template.d.ts +42 -1
  61. package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
  62. package/node_modules/@oxygen/shared/dist/sequences.d.ts +287 -24
  63. package/node_modules/@oxygen/shared/dist/sequences.js +940 -60
  64. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +70 -0
  65. package/node_modules/@oxygen/shared/dist/spend-safety.js +106 -0
  66. package/node_modules/@oxygen/shared/dist/tags.d.ts +90 -1
  67. package/node_modules/@oxygen/shared/dist/tags.js +126 -6
  68. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  69. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  70. package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.d.ts +1 -1
  71. package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.js +4 -0
  72. package/node_modules/@oxygen/shared/dist/workspace-agents.d.ts +8 -7
  73. package/node_modules/@oxygen/shared/dist/workspace-agents.js +34 -7
  74. package/node_modules/@oxygen/shared/package.json +95 -0
  75. package/node_modules/@oxygen/workflows/dist/event-dispatch.d.ts +126 -0
  76. package/node_modules/@oxygen/workflows/dist/event-dispatch.js +173 -0
  77. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +78 -0
  78. package/node_modules/@oxygen/workflows/dist/graph/expression.js +700 -0
  79. package/node_modules/@oxygen/workflows/dist/graph/index.d.ts +20 -0
  80. package/node_modules/@oxygen/workflows/dist/graph/index.js +20 -0
  81. package/node_modules/@oxygen/workflows/dist/graph/lint.d.ts +4 -0
  82. package/node_modules/@oxygen/workflows/dist/graph/lint.js +812 -0
  83. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +501 -0
  84. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +200 -0
  85. package/node_modules/@oxygen/workflows/dist/graph/params.d.ts +86 -0
  86. package/node_modules/@oxygen/workflows/dist/graph/params.js +173 -0
  87. package/node_modules/@oxygen/workflows/dist/graph/remap.d.ts +48 -0
  88. package/node_modules/@oxygen/workflows/dist/graph/remap.js +213 -0
  89. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +46 -0
  90. package/node_modules/@oxygen/workflows/dist/graph/topology.js +280 -0
  91. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +270 -0
  92. package/node_modules/@oxygen/workflows/dist/graph/types.js +93 -0
  93. package/node_modules/@oxygen/workflows/dist/index.d.ts +113 -1
  94. package/node_modules/@oxygen/workflows/dist/index.js +179 -13
  95. package/node_modules/@oxygen/workflows/dist/tool-effects.d.ts +1 -0
  96. package/node_modules/@oxygen/workflows/dist/tool-effects.js +19 -0
  97. package/node_modules/@oxygen/workflows/dist/usage-estimate.js +135 -4
  98. package/node_modules/@oxygen/workflows/package.json +4 -0
  99. package/package.json +10 -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"];
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",
@@ -12,10 +18,90 @@ export const TAG_KINDS = [
12
18
  "table",
13
19
  "workflow",
14
20
  "recipe",
21
+ "conversation",
22
+ "mailbox",
23
+ "sender",
24
+ "record",
25
+ "domain",
26
+ "project",
27
+ "engagement_watch",
15
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
+ };
16
62
  export function isTagKind(value) {
17
63
  return typeof value === "string" && TAG_KINDS.includes(value);
18
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
+ }
19
105
  /** Hard cap per tagged item — matches the wiki's long-standing limit. */
20
106
  export const MAX_TAGS_PER_ITEM = 50;
21
107
  /**
@@ -32,3 +118,37 @@ export function normalizeTags(value) {
32
118
  return [];
33
119
  return normalizeTagList(value);
34
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.365.3";
1
+ export declare const OXYGEN_VERSION = "1.575.19";
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.365.3";
1
+ export const OXYGEN_VERSION = "1.575.19";
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",
@@ -1,11 +1,11 @@
1
- /** The three implemented Workspace Agent specialists, by stable slug. */
2
- export type WorkspaceAgentSlug = "inbox-reply-drafts" | "meeting-notetaker" | "knowledge-synthesis";
1
+ /** The implemented general Workspace Agent and specialists (incl. the auto-tagger), by stable slug. */
2
+ export type WorkspaceAgentSlug = "workspace" | "inbox-reply-drafts" | "meeting-notetaker" | "knowledge-synthesis" | "inbox-auto-tagger";
3
3
  /**
4
4
  * How the web layer builds a specialist's deep-link. A pure hint on the
5
5
  * descriptor (shared can't import the web URL helpers) that agent-registry
6
6
  * switches on to produce the actual `https://oxygen-agent.com/...` URL.
7
7
  */
8
- export type WorkspaceAgentDeepLinkKind = "inbox_reply_agent" | "notetaker" | "knowledge_agent";
8
+ export type WorkspaceAgentDeepLinkKind = "workspace_agent" | "inbox_reply_agent" | "notetaker" | "knowledge_agent" | "inbox_auto_tagger";
9
9
  /** A pure, serializable descriptor for one Workspace Agent specialist. */
10
10
  export type WorkspaceAgentDescriptor = {
11
11
  /** Stable identifier used across CLI/MCP/API. Never derived from the title. */
@@ -54,12 +54,13 @@ export type WorkspaceAgentRunItem = {
54
54
  webUrl: string | null;
55
55
  };
56
56
  /**
57
- * The roster. Exactly three entries — one per implemented specialist. Order is
58
- * stable (it drives the default CLI/widget ordering). Adding a fourth entry is a
59
- * deliberate product decision that requires the specialist to actually ship first.
57
+ * The roster. The general Workspace Agent plus the implemented specialists
58
+ * (five entries). Order is stable (it drives the default CLI/widget ordering).
59
+ * Adding an entry is a deliberate product decision that requires the worker to
60
+ * actually ship first.
60
61
  */
61
62
  export declare const WORKSPACE_AGENT_ROSTER: readonly WorkspaceAgentDescriptor[];
62
- /** True when `value` is one of the three implemented specialist slugs. */
63
+ /** True when `value` is one of the implemented specialist slugs. */
63
64
  export declare function isWorkspaceAgentSlug(value: unknown): value is WorkspaceAgentSlug;
64
65
  /** The descriptor for `slug`, or `undefined` when it is not a known specialist. */
65
66
  export declare function getWorkspaceAgentDescriptor(slug: string): WorkspaceAgentDescriptor | undefined;
@@ -1,11 +1,13 @@
1
1
  // The Agents primitive's roster (ADR 0011): the governed registry of OXYGEN's
2
- // built-in workspace agents. Still deliberately a *computed* registry — each
2
+ // built-in workspace agents. The general Workspace Agent is backed by the
3
+ // first-class ox_agents runtime; specialists remain computed from their owning
4
+ // primitive stores. Each
3
5
  // entry is a product-defined agent that ships as an approval-gated, observable
4
6
  // behavior elsewhere in the product, surfaced under /agents and `oxygen agent`.
5
7
  // Agents are governed workspace workers: the runtime envelope (approval posture,
6
8
  // credit budgets, run observability) is platform-enforced, never agent-optional.
7
- // User-defined custom agents are ratified (ADR 0011) but arrive with the unified
8
- // runtime envelope in a later phase — until then there is no create/define verb,
9
+ // User-defined custom agents are ratified (ADR 0011) but arrive after the unified
10
+ // Workspace Agent runtime — until then there is no custom create/delete verb,
9
11
  // and there are never placeholder rows: the roster lists only agents that are
10
12
  // actually implemented today. CRM-hygiene, deliverability, and any other roadmap
11
13
  // agent stay OUT until they ship — the no-placeholder-surface rule.
@@ -15,11 +17,24 @@
15
17
  // (config read/write, run history, deep-links) lives in the web layer's
16
18
  // agent-registry, which composes these descriptors with the existing helpers.
17
19
  /**
18
- * The roster. Exactly three entries — one per implemented specialist. Order is
19
- * stable (it drives the default CLI/widget ordering). Adding a fourth entry is a
20
- * deliberate product decision that requires the specialist to actually ship first.
20
+ * The roster. The general Workspace Agent plus the implemented specialists
21
+ * (five entries). Order is stable (it drives the default CLI/widget ordering).
22
+ * Adding an entry is a deliberate product decision that requires the worker to
23
+ * actually ship first.
21
24
  */
22
25
  export const WORKSPACE_AGENT_ROSTER = [
26
+ {
27
+ slug: "workspace",
28
+ title: "Workspace Agent",
29
+ subtitle: "Runs organization-grounded goals through OXYGEN's governed capability catalog.",
30
+ description: "Takes a workspace goal, plans the work, and operates tables, CRM, context, workflows, sequences, files, and integrations as one durable Agent run.",
31
+ owningPrimitive: "Agents",
32
+ configCommand: "oxygen agent set workspace",
33
+ configApiPath: "/api/cli/agent/workspace",
34
+ mcpConfigTools: ["oxygen_agent_update", "oxygen_agent_run"],
35
+ approvalPosture: "Uses a versioned standing authorization with optional allow, ask, and deny rules; platform previews, caps, deny rules, and run provenance always apply.",
36
+ deepLinkKind: "workspace_agent",
37
+ },
23
38
  {
24
39
  slug: "inbox-reply-drafts",
25
40
  title: "AI Sales Agent",
@@ -56,8 +71,20 @@ export const WORKSPACE_AGENT_ROSTER = [
56
71
  approvalPosture: "Writes working knowledge pages as logged revisions; canonical and pinned pages still require proposal approval.",
57
72
  deepLinkKind: "knowledge_agent",
58
73
  },
74
+ {
75
+ slug: "inbox-auto-tagger",
76
+ title: "AI Inbox Tagger",
77
+ subtitle: "Auto-tags new inbound conversations from your tag allowlist.",
78
+ description: "Classifies every new inbound email, LinkedIn, and WhatsApp conversation against your workspace tag allowlist and applies the matching tags automatically.",
79
+ owningPrimitive: "Messages",
80
+ configCommand: "oxygen inbox auto-tagger",
81
+ configApiPath: "/api/cli/inbox/auto-tagger",
82
+ mcpConfigTools: ["oxygen_inbox_auto_tagger_get", "oxygen_inbox_auto_tagger_set"],
83
+ approvalPosture: "Auto-applies internal workspace tags only — additive, never re-adds a tag you removed, and needs no approval queue because tags never leave the workspace.",
84
+ deepLinkKind: "inbox_auto_tagger",
85
+ },
59
86
  ];
60
- /** True when `value` is one of the three implemented specialist slugs. */
87
+ /** True when `value` is one of the implemented specialist slugs. */
61
88
  export function isWorkspaceAgentSlug(value) {
62
89
  return (typeof value === "string" &&
63
90
  WORKSPACE_AGENT_ROSTER.some((descriptor) => descriptor.slug === value));