@oxygen-agent/cli 1.1014.620 → 1.1014.773

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 (67) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +15 -0
  3. package/dist/command-support.d.ts +5 -5
  4. package/dist/command-support.js +7 -7
  5. package/dist/commands/billing.js +12 -6
  6. package/dist/commands/columns.js +2 -2
  7. package/dist/commands/companies.js +1 -1
  8. package/dist/commands/copilot.js +1 -1
  9. package/dist/commands/dashboards.js +1 -1
  10. package/dist/commands/enrich-column.js +2 -2
  11. package/dist/commands/find.js +2 -2
  12. package/dist/commands/mailboxes.js +36 -0
  13. package/dist/commands/recipes.js +1 -1
  14. package/dist/commands/sequences.js +6 -6
  15. package/dist/commands/tables.js +1 -1
  16. package/dist/commands/verify.js +112 -11
  17. package/dist/http-client.js +1 -70
  18. package/dist/index.js +1 -1
  19. package/dist/refactor-internals/binary-download.d.ts +5 -0
  20. package/dist/refactor-internals/binary-download.js +71 -0
  21. package/node_modules/@oxygen/formula/dist/evaluate.js +2 -82
  22. package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.d.ts +2 -0
  23. package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.js +87 -0
  24. package/node_modules/@oxygen/shared/dist/billing.d.ts +15 -3
  25. package/node_modules/@oxygen/shared/dist/billing.js +19 -7
  26. package/node_modules/@oxygen/shared/dist/cancellation-reasons.d.ts +35 -0
  27. package/node_modules/@oxygen/shared/dist/cancellation-reasons.js +21 -0
  28. package/node_modules/@oxygen/shared/dist/capability-discovery.js +27 -1
  29. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +6 -6
  30. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
  31. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +1 -0
  32. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +41 -17
  33. package/node_modules/@oxygen/shared/dist/email-verification-batch.d.ts +26 -0
  34. package/node_modules/@oxygen/shared/dist/email-verification-batch.js +55 -0
  35. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +3 -3
  36. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +3 -3
  37. package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.d.ts +70 -0
  38. package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.js +42 -0
  39. package/node_modules/@oxygen/shared/dist/linkedin-url.d.ts +8 -0
  40. package/node_modules/@oxygen/shared/dist/linkedin-url.js +10 -0
  41. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -1
  42. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  43. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +3 -1
  44. package/node_modules/@oxygen/shared/dist/product-doctrine.js +1 -1
  45. package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.d.ts +225 -0
  46. package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.js +578 -0
  47. package/node_modules/@oxygen/shared/dist/sequences.d.ts +1 -226
  48. package/node_modules/@oxygen/shared/dist/sequences.js +2 -576
  49. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +103 -24
  50. package/node_modules/@oxygen/shared/dist/teammate-groups.js +125 -32
  51. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +1 -1
  52. package/node_modules/@oxygen/shared/dist/teammates.d.ts +28 -2
  53. package/node_modules/@oxygen/shared/dist/teammates.js +32 -2
  54. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +40 -8
  55. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  56. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  57. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +1 -16
  58. package/node_modules/@oxygen/workflows/dist/graph/expression.js +4 -224
  59. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -2
  60. package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -63
  61. package/node_modules/@oxygen/workflows/dist/index.js +1 -193
  62. package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.d.ts +18 -0
  63. package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.js +222 -0
  64. package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.d.ts +64 -0
  65. package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.js +204 -0
  66. package/node_modules/@oxygen/workflows/package.json +4 -0
  67. package/package.json +1 -1
@@ -39,7 +39,7 @@ export const CUSTOMER_OPERATIONS = [
39
39
  operation("members.client_role", "/api/cli/collab/members/role", "POST", "approvals client grant", "oxygen_members_client_role_set", "collaboration", "Set or clear the restricted client overlay; admin only. May read pending Clerk invitations; sends no invitation. Free.", { email: text(), role: nullable(enumOf(["client"])) }, ["email", "role"], { ...mcpOnly, annotations: { openWorldHint: true } }),
40
40
  operation("members.workspace_role", "/api/cli/collab/members/workspace-role", "POST", "orgs member-role", "oxygen_members_workspace_role_set", "collaboration", "Change a member's workspace role in Clerk; admin only, with self-role and last-administrator protection. Free.", { email: text(), role: enumOf(["admin", "member"]) }, ["email", "role"], { ...mcpOnly, annotations: { openWorldHint: true } }),
41
41
  operation("copilot.sessions.list", "/api/cli/copilot/sessions", "GET", "copilot list", "oxygen_copilot_sessions_list", "agents", "List attended Copilot sessions in this workspace.", COPILOT_SESSIONS_LIST_CONTRACT.properties, [], mcpOnly),
42
- operation("copilot.sessions.update", "/api/cli/copilot/sessions/{session_id}", "PATCH", "copilot policy set", "oxygen_copilot_session_update", "agents", "Set an attended Copilot session's approval policy. Paid auto-approval is explicit standing authorization.", { auto_approve_policy: enumOf(["off", "paid"]) }, ["auto_approve_policy"], { spends: true }),
42
+ operation("copilot.sessions.update", "/api/cli/copilot/sessions/{session_id}", "PATCH", "copilot policy set", "oxygen_copilot_session_update", "agents", "Set an attended Copilot session's approval policy. `paid` (Auto-approve) is standing authorization for every action, paid and external ones included.", { auto_approve_policy: enumOf(["off", "paid"]) }, ["auto_approve_policy"], { spends: true }),
43
43
  operation("copilot.attachments.list", "/api/cli/copilot/sessions/{session_id}/attachments", "GET", "copilot attachments list", "oxygen_copilot_attachments_list", "agents", "List sent and unsent attachments in one Copilot session.", {}, []),
44
44
  operation("copilot.attachments.prepare", "/api/cli/copilot/sessions/{session_id}/attachments", "POST", "copilot attachments prepare", "oxygen_copilot_attachment_prepare", "agents", "Reserve a Copilot attachment and return its scoped upload URL; complete only after uploading the exact bytes.", { file_name: text(), content_type: text(), content_length: int({ minimum: 1 }) }, ["file_name", "content_length"]),
45
45
  operation("copilot.attachments.complete", "/api/cli/copilot/sessions/{session_id}/attachments/{attachment_id}/complete", "POST", "copilot attachments complete", "oxygen_copilot_attachment_complete", "agents", "Verify and describe an uploaded Copilot attachment in its session.", {}, []),
@@ -119,6 +119,7 @@ export function projectLimitsRead(data, query) {
119
119
  else if (section === "pricing") {
120
120
  selected.plan_bands = bands.map(pricingBand);
121
121
  selected.pricing_schedule = scheduleProjection(data, section);
122
+ selected.pricing_note = "Plan-band prices below are monthly. Full upfront yearly prices and terms: CLI billing balance --json; MCP oxygen_billing_balance (available_plans[].yearly and annual_terms).";
122
123
  }
123
124
  else
124
125
  selected[SECTION_FIELDS[section]] = data[SECTION_FIELDS[section]];
@@ -1,7 +1,7 @@
1
- import { TEAMMATE_GROUP_MAX_MEMBERS, TEAMMATE_GROUP_MIN_MEMBERS, TEAMMATE_GROUP_TITLE_MAX_CHARS } from "../teammate-groups.js";
1
+ import { TEAMMATE_GROUP_MAX_MEMBERS, TEAMMATE_GROUP_MAX_PEOPLE, TEAMMATE_GROUP_MIN_MEMBERS, TEAMMATE_GROUP_TITLE_MAX_CHARS } from "../teammate-groups.js";
2
2
  import { TEAMMATE_SLUG_PATTERN } from "../teammate-messages.js";
3
3
  import { TEAMMATE_TASK_LIMITS } from "../teammate-tasks.js";
4
- import { NEW_TEAMMATE_NAME, TEAMMATE_AVATAR_COLORS, TEAMMATE_AVATAR_KEYS, TEAMMATE_DESCRIPTION_MAX_CHARS, TEAMMATE_NAME_MAX_CHARS, } from "../teammates.js";
4
+ import { NEW_TEAMMATE_NAME, TEAMMATE_AVATAR_COLORS, TEAMMATE_AVATAR_KEYS, TEAMMATE_DESCRIPTION_MAX_CHARS, TEAMMATE_NAME_MAX_CHARS, TEAMMATE_SHARE_ROLES, } from "../teammates.js";
5
5
  import { arr, bool, enumOf, int, obj, str } from "./schema.js";
6
6
  /**
7
7
  * AI teammates (ADR 0032), branded Oxygents: Agents each person talks to in one
@@ -22,9 +22,9 @@ function teammateOperation(id, verb, method, suffix, summary, fields = {}, requi
22
22
  };
23
23
  }
24
24
  /**
25
- * Group chats (phase 7): one person's private chat with two to six Oxygents,
26
- * the way Grok Bot's are. Their own route family, so a group never shadows a
27
- * teammate whose slug happens to read like a route word.
25
+ * Group chats (phase 7): a chat with up to six Oxygents and, since 2026-10-15,
26
+ * teammates too, the way Grok Bot's are. Their own route family, so a group
27
+ * never shadows a teammate whose slug happens to read like a route word.
28
28
  */
29
29
  function teammateGroupOperation(id, verb, method, suffix, summary, fields = {}, required = [], spends = false) {
30
30
  const route = `/api/cli/teammate-groups${suffix}`;
@@ -38,6 +38,7 @@ function teammateGroupOperation(id, verb, method, suffix, summary, fields = {},
38
38
  };
39
39
  }
40
40
  const GROUP_MEMBER_SLUGS = (description) => arr(str({ minLength: 2, maxLength: 49, pattern: TEAMMATE_SLUG_PATTERN }), { maxItems: TEAMMATE_GROUP_MAX_MEMBERS, description });
41
+ const GROUP_PEOPLE = (description) => arr(str({ minLength: 3, maxLength: 320 }), { maxItems: TEAMMATE_GROUP_MAX_PEOPLE, description });
41
42
  export const TEAMMATE_PROFILE_FIELDS = {
42
43
  name: str({ minLength: 1, maxLength: TEAMMATE_NAME_MAX_CHARS, description: "What everyone calls it." }),
43
44
  description: str({
@@ -48,26 +49,43 @@ export const TEAMMATE_PROFILE_FIELDS = {
48
49
  avatar_color: enumOf([...TEAMMATE_AVATAR_COLORS], { description: "The colour of its face." }),
49
50
  };
50
51
  export const TEAMMATE_OPERATIONS = [
51
- teammateOperation("teammates.list", "list", "GET", "", "List this workspace's Oxygents, the first one (is_default) on top: name, what each does, its look and your chat with each (working or waiting for you, unread, its latest words). Pinned ones follow the first; hidden ones are marked."),
52
+ teammateOperation("teammates.list", "list", "GET", "", "List the Oxygents you can use, the first one (is_default) on top: name, what each does, its look, what you are to it (access: owner, manager or collaborator) and your chat with each (working or waiting for you, unread, its latest words). Pinned ones follow the first; hidden ones are marked."),
52
53
  // Adding a teammate (phase 7): a custom Agent that can chat at once and run
53
54
  // on its own, under a policy that asks before any write, send or spend. As in
54
55
  // Grok Bot, it can start as just "New Bot" and learn its job in the chat.
55
- teammateOperation("teammates.create", "create", "POST", "", `Add an Oxygent to the workspace, named "${NEW_TEAMMATE_NAME}" unless you name it; tell it its job in your chat with it. It can chat at once and run on its own (background tasks, routines), reading freely and asking before anything that writes, sends or spends. The whole workspace sees it; you own it. Free: adding one starts no inference.`, {
56
+ teammateOperation("teammates.create", "create", "POST", "", `Add an Oxygent to the workspace, named "${NEW_TEAMMATE_NAME}" unless you name it; tell it its job in your chat with it. It can chat at once and run on its own (background tasks, routines), reading freely and asking before anything that writes, sends or spends. You own it, and only you can use it until you share it. Free: adding one starts no inference.`, {
56
57
  ...TEAMMATE_PROFILE_FIELDS,
57
58
  slug: str({ minLength: 2, maxLength: 49, pattern: TEAMMATE_SLUG_PATTERN, description: "Its permanent handle in commands and links; made from the name when omitted." }),
58
59
  }),
59
60
  teammateOperation("teammates.get", "get", "GET", "/{slug}", "Read one Oxygent's profile and your ongoing private chat with it."),
60
- teammateOperation("teammates.update", "update", "PATCH", "/{slug}", "Edit an Oxygent's name, what it does (description) or its look (avatar_key, avatar_color). Its instructions, tools and versions are unchanged. Free.", TEAMMATE_PROFILE_FIELDS),
61
+ teammateOperation("teammates.update", "update", "PATCH", "/{slug}", "Edit an Oxygent's name, what it does (description) or its look (avatar_key, avatar_color): its owner, its managers or a workspace admin. Its instructions, tools and versions are unchanged. Free.", TEAMMATE_PROFILE_FIELDS),
62
+ // Sharing (Tim, 2026-10-14: "make agents accessible to your team mates"), the
63
+ // way Grok Bot shares a Bot: everyone in the workspace, or people by email,
64
+ // each as a collaborator or a manager. Everyone still has a chat of their own.
65
+ teammateOperation("teammates.access", "access", "GET", "/{slug}/access", "Read who can use an Oxygent: its owner, what everyone in the workspace may do with it, and the people it is shared with. A collaborator chats with it in a chat of their own; a manager also changes its profile and who has access. Free."),
66
+ teammateOperation("teammates.share", "share", "POST", "/{slug}/access", "Share an Oxygent with teammates, or stop sharing it: with everyone in the workspace (everyone), or with people by their workspace email (add, as role; remove). Each person gets their own private chat with it; nobody reads anyone else's. Its owner, its managers or a workspace admin only; never the first Oxygent, which is everyone's. Free.", {
67
+ everyone: enumOf([...TEAMMATE_SHARE_ROLES, "off"], {
68
+ description: "What everyone in the workspace may do with it: collaborator, manager, or off (only its owner and the people it is shared with).",
69
+ }),
70
+ add: arr(str({ minLength: 3, maxLength: 320 }), { maxItems: 50, description: "People to share it with, by their workspace email." }),
71
+ role: enumOf([...TEAMMATE_SHARE_ROLES], {
72
+ description: "What the people in add become: collaborator (the default), who chats with it in a chat of their own, or manager, who also changes its profile and who has access. Adding someone already listed changes their role.",
73
+ }),
74
+ remove: arr(str({ minLength: 3, maxLength: 320 }), {
75
+ maxItems: 50,
76
+ description: "People to stop sharing it with, by their workspace email; for someone who has left the workspace, the user_id that access lists for them. Their chat with it stays, closed to them until it is shared with them again.",
77
+ }),
78
+ }),
61
79
  // Copy and delete (Tim, 2026-10-13: "CRUD actions ... through the right
62
80
  // click"; "'delete' should be 'full delete'"), as Grok Bot's Bot menu has.
63
- teammateOperation("teammates.duplicate", "duplicate", "POST", "/{slug}/duplicate", `Copy an Oxygent as a new one you own, named "<name> copy" unless you name it: its job, look, instructions, skills and tool settings, but not its chats, memory or routines. Free.`, {
81
+ teammateOperation("teammates.duplicate", "duplicate", "POST", "/{slug}/duplicate", `Copy an Oxygent as a new one you own, named "<name> copy" unless you name it: its job, look, instructions, skills and tool settings, but not its chats, memory, routines or who it is shared with. Only you can use the copy until you share it. Free.`, {
64
82
  name: str({
65
83
  minLength: 1, maxLength: TEAMMATE_NAME_MAX_CHARS,
66
84
  description: "What everyone calls the copy. Its slug is made from the name, and a deleted Oxygent's slug stays taken, so a copy can get a numbered one (scout-2).",
67
85
  }),
68
86
  }),
69
- teammateOperation("teammates.delete", "delete", "POST", "/{slug}/delete", "Delete an Oxygent for good, for everyone: its profile, every chat with it (group chats included), its routines, team memory and runs. What it made in the workspace stays. Its owner or a workspace admin only; never the first Oxygent. Preview first to see what would go, then delete with confirm set to its slug. Its slug stays taken afterwards. Free; cannot be undone.", {
70
- preview: bool({ description: "Only say what would go: its chats and the people they belong to, group chats, routines, runs, attached files and team memory entries." }),
87
+ teammateOperation("teammates.delete", "delete", "POST", "/{slug}/delete", "Delete an Oxygent for good, for everyone: its profile, every chat with it (its group chats too, except those other people are in, which it leaves and where only its answers go), its routines, team memory and runs. What it made in the workspace stays. Its owner or a workspace admin only; never the first Oxygent. Preview first to see what would go, then delete with confirm set to its slug. Its slug stays taken afterwards. Free; cannot be undone.", {
88
+ preview: bool({ description: "Only say what would go: its chats and the people they belong to, group chats, the group chats with other people it would leave, routines, runs, attached files and team memory entries." }),
71
89
  confirm: str({ maxLength: 49, description: "Its slug, repeated, to delete it." }),
72
90
  }),
73
91
  teammateOperation("teammates.chat", "chat", "POST", "/{slug}/chat", "Open your ongoing private chat with an Oxygent, creating it the first time. Starts no inference."),
@@ -98,18 +116,24 @@ export const TEAMMATE_OPERATIONS = [
98
116
  teammateOperation("teammates.notes", "notes", "GET", "/{slug}/notes", "Read what an Oxygent keeps about you: private to you, read only in your own chat with it."),
99
117
  teammateOperation("teammates.notes_set", "notes_set", "POST", "/{slug}/notes", "Replace what an Oxygent keeps about you; empty clears it. Private to you.", { notes: str({ maxLength: 4000 }) }, ["notes"]),
100
118
  teammateOperation("teammates.activity", "activity", "GET", "/{slug}/activity", "List an Oxygent's recent work: its runs and the actions taken in its chats, as metadata only (no goals or messages)."),
101
- teammateGroupOperation("teammate_groups.list", "groups", "GET", "", "List your group chats with Oxygents: name, members and the chat (working or waiting for you, unread, the latest words and who said them). Private to you."),
102
- teammateGroupOperation("teammate_groups.create", "group_create", "POST", "", `Start a private group chat with ${TEAMMATE_GROUP_MIN_MEMBERS} to ${TEAMMATE_GROUP_MAX_MEMBERS} Oxygents (members, by slug), named name. Starts no inference: @mention a member in a message to have it answer.`, {
119
+ teammateGroupOperation("teammate_groups.list", "groups", "GET", "", "List the group chats you are in: name, Oxygents, people (each an admin or member) and the chat (working or waiting for you, unread, the latest words and who said them). Candidates are the teammates a new group chat could bring in."),
120
+ teammateGroupOperation("teammate_groups.create", "group_create", "POST", "", `Start a group chat with a name, Oxygents (members, by slug) and, if you like, teammates (people, by workspace email): ${TEAMMATE_GROUP_MIN_MEMBERS} to ${TEAMMATE_GROUP_MAX_MEMBERS} Oxygents with only you, 1 to ${TEAMMATE_GROUP_MAX_MEMBERS} with others. You are its admin. Every Oxygent must be shared with everyone in it. Starts no inference.`, {
103
121
  name: str({ minLength: 1, maxLength: TEAMMATE_GROUP_TITLE_MAX_CHARS }),
104
122
  members: GROUP_MEMBER_SLUGS("The Oxygents in it, by slug, in the order they show."),
123
+ people: GROUP_PEOPLE("Teammates to bring in, by their workspace email; they see the whole chat."),
105
124
  }, ["name", "members"]),
106
- teammateGroupOperation("teammate_groups.get", "group", "GET", "/{group_id}", "Read one of your group chats: its name, members and the chat's state."),
107
- teammateGroupOperation("teammate_groups.update", "group_update", "PATCH", "/{group_id}", `Rename a group chat, or add or remove Oxygents (by slug); it keeps ${TEAMMATE_GROUP_MIN_MEMBERS} to ${TEAMMATE_GROUP_MAX_MEMBERS}. A removed member's earlier messages stay.`, {
125
+ teammateGroupOperation("teammate_groups.get", "group", "GET", "/{group_id}", "Read a group chat you are in: its name, Oxygents, people and the chat's state. Its admins also see who else could be brought in (candidates)."),
126
+ teammateGroupOperation("teammate_groups.update", "group_update", "PATCH", "/{group_id}", `Change a group chat, as one of its admins: add or remove people (by workspace email), make them admins or members, rename it, or add or remove Oxygents (by slug). It keeps ${TEAMMATE_GROUP_MIN_MEMBERS} to ${TEAMMATE_GROUP_MAX_MEMBERS} Oxygents with only you, 1 to ${TEAMMATE_GROUP_MAX_MEMBERS} with others. Someone added sees the whole chat; removed people and Oxygents keep their earlier messages.`, {
108
127
  name: str({ minLength: 1, maxLength: TEAMMATE_GROUP_TITLE_MAX_CHARS }),
109
128
  add: GROUP_MEMBER_SLUGS("Oxygents to add, by slug."),
110
129
  remove: GROUP_MEMBER_SLUGS("Oxygents to remove, by slug."),
130
+ add_people: GROUP_PEOPLE("People to add, by their workspace email."),
131
+ remove_people: GROUP_PEOPLE("People to remove, by their workspace email; for someone who has left the workspace, the user_id the group lists for them."),
132
+ make_admin: GROUP_PEOPLE("People in it to make admins, by workspace email."),
133
+ remove_admin: GROUP_PEOPLE("Admins to make members again, by workspace email; a group keeps at least one admin."),
111
134
  }),
112
- teammateGroupOperation("teammate_groups.message", "group_message", "POST", "/{group_id}/messages", "Send a message in a group chat. The first Oxygent it @mentions answers (else whoever answered last), then each other one it mentions, or one an answer brings in. Each answer is a turn billed from workspace credits; paid or external actions still need approval.", { message: str({ minLength: 1, maxLength: 16000 }) }, ["message"], true),
135
+ teammateGroupOperation("teammate_groups.message", "group_message", "POST", "/{group_id}/messages", "Send a message in a group chat. The Oxygents it @mentions answer, in order; with no mention, the Oxygents it is for answer, or, in a group of only you, whoever answered last. A message that @mentions only people goes to them. In a group with other people, each answer waits for the one before it, and paid or external actions ask whoever sent the message. Each answer is a turn billed from workspace credits.", { message: str({ minLength: 1, maxLength: 16000 }) }, ["message"], true),
113
136
  teammateGroupOperation("teammate_groups.read", "group_read", "POST", "/{group_id}/read", "Mark a group chat read up to its newest turn, or up to turn_index.", { turn_index: int({ minimum: 0 }) }),
114
- teammateGroupOperation("teammate_groups.end", "group_end", "POST", "/{group_id}/end", "End a group chat: nobody answers in it again, and its messages stay readable."),
137
+ teammateGroupOperation("teammate_groups.leave", "group_leave", "POST", "/{group_id}/leave", "Leave a group chat: it disappears for you and its Oxygents stop answering your waiting messages. If you were its last admin, whoever has been in it longest becomes one; if you were the last person, it ends."),
138
+ teammateGroupOperation("teammate_groups.end", "group_end", "POST", "/{group_id}/end", "End a group chat for everyone in it, as one of its admins: nobody answers in it again, and its messages stay readable."),
115
139
  ];
@@ -0,0 +1,26 @@
1
+ /**
2
+ * How a batch of email verifications is sized and read. `/api/cli/verify/run`
3
+ * and the CLI that pages a longer list through it share this, so a one-page and
4
+ * a many-page run can never disagree about the summary or the leading next action.
5
+ */
6
+ /**
7
+ * Addresses one live request verifies. Each one is a paid provider call made in
8
+ * sequence, so the CLI sends a longer list in pages under one credit ceiling.
9
+ */
10
+ export declare const EMAIL_VERIFICATION_LIVE_PAGE_MAX = 100;
11
+ /**
12
+ * Addresses one `verify email` command takes. The free preview prices the whole
13
+ * list in one request, so this also bounds that request body.
14
+ */
15
+ export declare const EMAIL_VERIFICATION_LIST_MAX = 5000;
16
+ type VerificationRow = Readonly<Record<string, unknown>>;
17
+ export declare function summarizeEmailVerificationResults(results: ReadonlyArray<VerificationRow>): {
18
+ total: number;
19
+ valid: number;
20
+ invalid: number;
21
+ catch_all: number;
22
+ unknown: number;
23
+ };
24
+ /** The one per-address `next_action` worth lifting to the top of the envelope. */
25
+ export declare function leadingEmailVerificationNextAction(results: ReadonlyArray<VerificationRow>): string | null;
26
+ export {};
@@ -0,0 +1,55 @@
1
+ /**
2
+ * How a batch of email verifications is sized and read. `/api/cli/verify/run`
3
+ * and the CLI that pages a longer list through it share this, so a one-page and
4
+ * a many-page run can never disagree about the summary or the leading next action.
5
+ */
6
+ /**
7
+ * Addresses one live request verifies. Each one is a paid provider call made in
8
+ * sequence, so the CLI sends a longer list in pages under one credit ceiling.
9
+ */
10
+ export const EMAIL_VERIFICATION_LIVE_PAGE_MAX = 100;
11
+ /**
12
+ * Addresses one `verify email` command takes. The free preview prices the whole
13
+ * list in one request, so this also bounds that request body.
14
+ */
15
+ export const EMAIL_VERIFICATION_LIST_MAX = 5_000;
16
+ export function summarizeEmailVerificationResults(results) {
17
+ const count = (verdict) => results.filter((row) => row.verdict === verdict).length;
18
+ return {
19
+ total: results.length,
20
+ valid: count("valid"),
21
+ invalid: count("invalid"),
22
+ catch_all: count("catch_all"),
23
+ unknown: count("unknown"),
24
+ };
25
+ }
26
+ // Rank order for the batch-level next action: the condition a human has to act
27
+ // on outranks the ones that resolve themselves on a re-run.
28
+ const NEXT_ACTION_RANK = {
29
+ // A held escalation outranks everything: re-running it, as the other skips
30
+ // suggest, could charge the same address twice.
31
+ "escalation_unavailable:outcome_unknown": 7,
32
+ "escalation_unavailable:provider_account_held": 6,
33
+ "escalation_unavailable:provider_account_dry": 5,
34
+ "escalation_unavailable:not_configured": 4,
35
+ "escalation_unavailable:provider_error": 3,
36
+ budget_exhausted: 2,
37
+ not_configured: 1,
38
+ };
39
+ /** The one per-address `next_action` worth lifting to the top of the envelope. */
40
+ export function leadingEmailVerificationNextAction(results) {
41
+ let best = null;
42
+ for (const row of results) {
43
+ const text = typeof row.next_action === "string" ? row.next_action : null;
44
+ if (!text)
45
+ continue;
46
+ const skipped = typeof row.escalation_skipped === "string" ? row.escalation_skipped : "";
47
+ const unavailable = typeof row.escalation_unavailable_reason === "string"
48
+ ? row.escalation_unavailable_reason
49
+ : null;
50
+ const rank = NEXT_ACTION_RANK[unavailable ? `${skipped}:${unavailable}` : skipped] ?? 0;
51
+ if (!best || rank > best.rank)
52
+ best = { rank, text };
53
+ }
54
+ return best?.text ?? null;
55
+ }
@@ -70,9 +70,9 @@ export declare const KNOWLEDGE_BOOTSTRAP_ENRICHMENT_MAX_CREDITS = 6;
70
70
  */
71
71
  export declare const KNOWLEDGE_BOOTSTRAP_SYNTHESIS_TIER: HostedAiLevel;
72
72
  /**
73
- * Most URLs whose text the pass may read — inside ONE `exa.contents` call, which is
74
- * priced per call. Raising this raises latency and prompt size, not provider spend;
75
- * it is a quality/latency knob, and the credit ceilings above remain the money guard.
73
+ * Most URLs whose text the pass may read inside ONE `exa.contents` call. Each URL
74
+ * is billed, so raising this raises the read ceiling as well as latency and prompt
75
+ * size. The credit ceilings above remain the money guard.
76
76
  */
77
77
  export declare const KNOWLEDGE_BOOTSTRAP_MAX_PAGES_READ = 5;
78
78
  /**
@@ -71,9 +71,9 @@ export const KNOWLEDGE_BOOTSTRAP_ENRICHMENT_MAX_CREDITS = 6;
71
71
  */
72
72
  export const KNOWLEDGE_BOOTSTRAP_SYNTHESIS_TIER = "medium";
73
73
  /**
74
- * Most URLs whose text the pass may read — inside ONE `exa.contents` call, which is
75
- * priced per call. Raising this raises latency and prompt size, not provider spend;
76
- * it is a quality/latency knob, and the credit ceilings above remain the money guard.
74
+ * Most URLs whose text the pass may read inside ONE `exa.contents` call. Each URL
75
+ * is billed, so raising this raises the read ceiling as well as latency and prompt
76
+ * size. The credit ceilings above remain the money guard.
77
77
  */
78
78
  export const KNOWLEDGE_BOOTSTRAP_MAX_PAGES_READ = 5;
79
79
  /**
@@ -0,0 +1,70 @@
1
+ /** The draft's centre page: the company itself. Stable, so a rerun never mints a second one. */
2
+ export declare const KNOWLEDGE_BRAIN_DRAFT_COMPANY_SLUG = "company";
3
+ /** What marks a page as written by the draft; the onboarding graph watches for it. */
4
+ export declare const KNOWLEDGE_BRAIN_DRAFT_TAG = "onboarding-draft";
5
+ /** Tags every drafted page carries. */
6
+ export declare const KNOWLEDGE_BRAIN_DRAFT_TAGS: readonly ["agent-derived", "onboarding-draft"];
7
+ /** The hub pages, revealed right after the company and kept at the explorer's root. */
8
+ export declare const KNOWLEDGE_BRAIN_DRAFT_HUBS: readonly ["offers", "icp", "messaging"];
9
+ /** Leaf-page kinds by slug prefix, in the order onboarding reveals them after the hubs. */
10
+ export declare const KNOWLEDGE_BRAIN_DRAFT_KINDS: readonly [{
11
+ readonly prefix: "segment-";
12
+ readonly folder: "Segments";
13
+ readonly type: "strategy";
14
+ readonly tag: "segment";
15
+ }, {
16
+ readonly prefix: "persona-";
17
+ readonly folder: "Personas";
18
+ readonly type: "persona";
19
+ readonly tag: "persona";
20
+ }, {
21
+ readonly prefix: "product-";
22
+ readonly folder: "Products";
23
+ readonly type: "entity";
24
+ readonly tag: "product";
25
+ }, {
26
+ readonly prefix: "use-case-";
27
+ readonly folder: "Use cases";
28
+ readonly type: "topic";
29
+ readonly tag: "use-case";
30
+ }, {
31
+ readonly prefix: "pain-";
32
+ readonly folder: "Pains";
33
+ readonly type: "topic";
34
+ readonly tag: "pain";
35
+ }, {
36
+ readonly prefix: "trigger-";
37
+ readonly folder: "Buying triggers";
38
+ readonly type: "topic";
39
+ readonly tag: "trigger";
40
+ }, {
41
+ readonly prefix: "angle-";
42
+ readonly folder: "Messaging";
43
+ readonly type: "message_playbook";
44
+ readonly tag: "angle";
45
+ }, {
46
+ readonly prefix: "objection-";
47
+ readonly folder: "Messaging";
48
+ readonly type: "message_playbook";
49
+ readonly tag: "objection";
50
+ }, {
51
+ readonly prefix: "filter-";
52
+ readonly folder: "Search filters";
53
+ readonly type: "strategy";
54
+ readonly tag: "filter";
55
+ }, {
56
+ readonly prefix: "customer-";
57
+ readonly folder: "Customers";
58
+ readonly type: "entity";
59
+ readonly tag: "customer";
60
+ }, {
61
+ readonly prefix: "competitor-";
62
+ readonly folder: "Competitors";
63
+ readonly type: "competitor";
64
+ readonly tag: "competitor";
65
+ }];
66
+ /**
67
+ * Where a page falls in the reveal: the company, the hubs, then each leaf kind in turn,
68
+ * then any other page the workspace already holds (the research note, a seeded page).
69
+ */
70
+ export declare function knowledgeBrainDraftRevealRank(slug: string): number;
@@ -0,0 +1,42 @@
1
+ // The second-brain draft's page vocabulary (Tim, 2026-10-14/15): which pages a new
2
+ // workspace's homepage draft writes, the folder each kind is filed in, and the order
3
+ // onboarding's second-brain step reveals them. One browser-safe owner, so the writer
4
+ // (@oxygen/integrations knowledge-brain-draft) and the step that draws the graph
5
+ // (apps/web onboarding-brain) can never disagree.
6
+ /** The draft's centre page: the company itself. Stable, so a rerun never mints a second one. */
7
+ export const KNOWLEDGE_BRAIN_DRAFT_COMPANY_SLUG = "company";
8
+ /** What marks a page as written by the draft; the onboarding graph watches for it. */
9
+ export const KNOWLEDGE_BRAIN_DRAFT_TAG = "onboarding-draft";
10
+ /** Tags every drafted page carries. */
11
+ export const KNOWLEDGE_BRAIN_DRAFT_TAGS = ["agent-derived", KNOWLEDGE_BRAIN_DRAFT_TAG];
12
+ /** The hub pages, revealed right after the company and kept at the explorer's root. */
13
+ export const KNOWLEDGE_BRAIN_DRAFT_HUBS = ["offers", "icp", "messaging"];
14
+ /** Leaf-page kinds by slug prefix, in the order onboarding reveals them after the hubs. */
15
+ export const KNOWLEDGE_BRAIN_DRAFT_KINDS = [
16
+ { prefix: "segment-", folder: "Segments", type: "strategy", tag: "segment" },
17
+ { prefix: "persona-", folder: "Personas", type: "persona", tag: "persona" },
18
+ { prefix: "product-", folder: "Products", type: "entity", tag: "product" },
19
+ { prefix: "use-case-", folder: "Use cases", type: "topic", tag: "use-case" },
20
+ { prefix: "pain-", folder: "Pains", type: "topic", tag: "pain" },
21
+ { prefix: "trigger-", folder: "Buying triggers", type: "topic", tag: "trigger" },
22
+ { prefix: "angle-", folder: "Messaging", type: "message_playbook", tag: "angle" },
23
+ { prefix: "objection-", folder: "Messaging", type: "message_playbook", tag: "objection" },
24
+ { prefix: "filter-", folder: "Search filters", type: "strategy", tag: "filter" },
25
+ { prefix: "customer-", folder: "Customers", type: "entity", tag: "customer" },
26
+ { prefix: "competitor-", folder: "Competitors", type: "competitor", tag: "competitor" },
27
+ ];
28
+ /**
29
+ * Where a page falls in the reveal: the company, the hubs, then each leaf kind in turn,
30
+ * then any other page the workspace already holds (the research note, a seeded page).
31
+ */
32
+ export function knowledgeBrainDraftRevealRank(slug) {
33
+ if (slug === KNOWLEDGE_BRAIN_DRAFT_COMPANY_SLUG)
34
+ return 0;
35
+ const hub = KNOWLEDGE_BRAIN_DRAFT_HUBS.indexOf(slug);
36
+ if (hub >= 0)
37
+ return 1 + hub;
38
+ const kind = KNOWLEDGE_BRAIN_DRAFT_KINDS.findIndex((entry) => slug.startsWith(entry.prefix));
39
+ if (kind >= 0)
40
+ return 1 + KNOWLEDGE_BRAIN_DRAFT_HUBS.length + kind;
41
+ return 1 + KNOWLEDGE_BRAIN_DRAFT_HUBS.length + KNOWLEDGE_BRAIN_DRAFT_KINDS.length;
42
+ }
@@ -32,6 +32,14 @@ export declare function normalizeLinkedinProfileUrl(raw: string | null | undefin
32
32
  * from any other source, so it cannot serve as a LinkedIn URL identity key.
33
33
  */
34
34
  export declare function isLinkedinMemberIdHandle(handle: string | null | undefined): boolean;
35
+ /**
36
+ * True for a Sales Navigator member id (`ACwAA…`). LinkedIn mints one id per
37
+ * product for the same person, and Unipile reports a Classic connection, chat
38
+ * attendee or `new_relation` under the Classic id (`ACoAA…`). A Sales Navigator
39
+ * id therefore never equals any id LinkedIn reports back; convert it with a
40
+ * profile read before keying anything on it (Plain T-282).
41
+ */
42
+ export declare function isLinkedinSalesNavigatorMemberId(value: string | null | undefined): boolean;
35
43
  /**
36
44
  * Canonicalize any LinkedIn PROFILE REFERENCE a customer might put in a cell:
37
45
  * a full `/in/` URL, a scheme-less `linkedin.com/in/…`, or a BARE public handle
@@ -84,6 +84,16 @@ const LINKEDIN_MEMBER_ID_PREFIX_RE = /^AC[OoWw]/;
84
84
  export function isLinkedinMemberIdHandle(handle) {
85
85
  return typeof handle === "string" && LINKEDIN_MEMBER_ID_PREFIX_RE.test(handle.trim());
86
86
  }
87
+ /**
88
+ * True for a Sales Navigator member id (`ACwAA…`). LinkedIn mints one id per
89
+ * product for the same person, and Unipile reports a Classic connection, chat
90
+ * attendee or `new_relation` under the Classic id (`ACoAA…`). A Sales Navigator
91
+ * id therefore never equals any id LinkedIn reports back; convert it with a
92
+ * profile read before keying anything on it (Plain T-282).
93
+ */
94
+ export function isLinkedinSalesNavigatorMemberId(value) {
95
+ return typeof value === "string" && /^ACw[A-Za-z0-9_-]+$/.test(value.trim());
96
+ }
87
97
  // LinkedIn's public identifier ("vanity slug") charset: letters, digits and
88
98
  // hyphens only, 3–100 characters, never leading with a hyphen. Deliberately
89
99
  // NARROWER than "any non-URL string" — no `.`, `@`, `_`, `:`, `/` or whitespace
@@ -97,7 +97,7 @@ export const PLAN_GATED_CAPABILITY_COPY = {
97
97
  },
98
98
  publishing_deliver: {
99
99
  label: "Publishing a post",
100
- freeAlternative: "You can draft, review, and approve posts on Free. Delivering them to a live account is what needs a plan.",
100
+ freeAlternative: "You can draft and review posts on Free. Approving one for delivery to a live account is what needs a plan.",
101
101
  deepLinkPath: "/publishing",
102
102
  },
103
103
  managed_email_infrastructure: {
@@ -155,7 +155,7 @@ export declare const RESEARCH_DEPTH_GROUNDING_CREDITS: {
155
155
  };
156
156
  readonly deep: {
157
157
  readonly typical: 2.4;
158
- readonly reserved: 17.4;
158
+ readonly reserved: 22.4;
159
159
  };
160
160
  };
161
161
  /** Typical per-row totals at each depth's default tier (founder-ratified 2026-09-26). */
@@ -147,7 +147,9 @@ export const RESEARCH_DEPTH_MODEL_CREDITS = { standard: 4.3, deep: 7.6 };
147
147
  */
148
148
  export const RESEARCH_DEPTH_GROUNDING_CREDITS = {
149
149
  standard: { typical: 1.2, reserved: 7.4 },
150
- deep: { typical: 2.4, reserved: 17.4 },
150
+ // Each of the two site_pages calls can fetch its root plus five subpages:
151
+ // 6 x 0.5 credits per call, rather than one 0.5-credit page per call.
152
+ deep: { typical: 2.4, reserved: 22.4 },
151
153
  };
152
154
  /** Typical per-row totals at each depth's default tier (founder-ratified 2026-09-26). */
153
155
  export const RESEARCH_DEPTH_TYPICAL_ROW_CREDITS = { quick: 2.1, standard: 5.5, deep: 10 };
@@ -49,7 +49,7 @@ function rosterLines(routes) {
49
49
  * a grown or shrunk route array and watching the output follow.
50
50
  */
51
51
  export function renderOxygenProductDoctrine(routes = OXYGEN_PRIMITIVE_ROUTES) {
52
- return `OXYGEN is hosted revenue infrastructure for B2B startups; OXYGEN OS has five layers. Control (CLI, MCP, web, desktop, Copilot) calls one contract, owns no state; External holds integrations. Between them, the ${routes.length} primitives, each owns its facts:
52
+ return `OXYGEN is the agentic GTM platform for B2B GTM teams; OXYGEN OS has five layers. Control (CLI, MCP, web, desktop, Copilot) calls one contract, owns no state; External holds integrations. Between them, the ${routes.length} primitives, each owns its facts:
53
53
 
54
54
  ${rosterLines(routes)}
55
55