@oxygen-agent/cli 1.1014.619 → 1.1014.771

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 (65) 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/plan-capabilities.js +1 -1
  40. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  41. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +3 -1
  42. package/node_modules/@oxygen/shared/dist/product-doctrine.js +1 -1
  43. package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.d.ts +225 -0
  44. package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.js +578 -0
  45. package/node_modules/@oxygen/shared/dist/sequences.d.ts +1 -226
  46. package/node_modules/@oxygen/shared/dist/sequences.js +2 -576
  47. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +103 -24
  48. package/node_modules/@oxygen/shared/dist/teammate-groups.js +125 -32
  49. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +1 -1
  50. package/node_modules/@oxygen/shared/dist/teammates.d.ts +28 -2
  51. package/node_modules/@oxygen/shared/dist/teammates.js +32 -2
  52. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +40 -8
  53. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  54. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  55. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +1 -16
  56. package/node_modules/@oxygen/workflows/dist/graph/expression.js +4 -224
  57. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -2
  58. package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -63
  59. package/node_modules/@oxygen/workflows/dist/index.js +1 -193
  60. package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.d.ts +18 -0
  61. package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.js +222 -0
  62. package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.d.ts +64 -0
  63. package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.js +204 -0
  64. package/node_modules/@oxygen/workflows/package.json +4 -0
  65. package/package.json +1 -1
@@ -1,55 +1,134 @@
1
1
  /**
2
- * Oxygent group chats (ADR 0032, phase 7), the way Grok Bot's are: one person's
3
- * private conversation with two to six of the workspace's Oxygents (Tim,
4
- * 2026-10-12: "really emulate grokbot"). Each turn is answered by one member:
2
+ * Oxygent group chats (ADR 0032, phase 7), the way Grok Bot's are (Tim,
3
+ * 2026-10-12: "really emulate grokbot"): a conversation with up to six of the
4
+ * workspace's Oxygents and, since 2026-10-15, teammates too, picked the way a
5
+ * WhatsApp group's members are (Tim: "group chat with a bot and your team mates
6
+ * ... that you can select who can join the group"). Each answer is one Oxygent's:
5
7
  *
6
- * - The person picks with @mentions, by name or slug: the first one mentioned
7
- * answers, and each other one mentioned answers after it, in order.
8
- * - With no mention, the member who answered last answers again; in a group
9
- * nobody has answered yet, the first member. No member ranks above the
10
- * others (Tim, 2026-10-13: no Chief of Staff).
11
- * - A member's answer may @mention another member to bring them in. Each member
12
- * answers a person's message at most once, so a message ends after at most
13
- * one turn per member.
8
+ * - An @mention picks: the Oxygents a message mentions answer, in order. A
9
+ * message that @mentions only people is theirs, and no Oxygent answers it.
10
+ * - With no mention, the Oxygents the message is for answer, most fitting first
11
+ * and at most {@link TEAMMATE_GROUP_MAX_ROUTED} (Tim, 2026-10-15: "the agents
12
+ * should know which is assigned based on the query ... it just needs to be fast
13
+ * and multiple agents should be able to answer"). The web asks System One, one
14
+ * yes/no per Oxygent in a single call. When none fits, or System One cannot
15
+ * say: in a group of one person, the Oxygent who answered last answers, else
16
+ * the first one (no member ranks above the others; Tim, 2026-10-13: no Chief of
17
+ * Staff); in a group with other people, nobody does, and they can @mention one.
18
+ * - An answer may @mention another Oxygent to bring it in. Each Oxygent answers a
19
+ * message at most once, so a message ends after at most one turn per Oxygent.
14
20
  *
15
- * Pure rules, shared by the web (which picks the first responder when the
16
- * person sends) and the worker (which relays to the next one after a turn).
21
+ * Pure rules, shared by the web (which routes a person's message as it is sent)
22
+ * and the worker (which relays to the next Oxygent after a turn).
17
23
  */
18
24
  export declare const TEAMMATE_GROUP_MIN_MEMBERS = 2;
19
25
  export declare const TEAMMATE_GROUP_MAX_MEMBERS = 6;
26
+ /** The people a group holds, its creator included. */
27
+ export declare const TEAMMATE_GROUP_MAX_PEOPLE = 50;
20
28
  export declare const TEAMMATE_GROUP_TITLE_MAX_CHARS = 80;
29
+ /** The most Oxygents that answer one message nobody @mentioned. */
30
+ export declare const TEAMMATE_GROUP_MAX_ROUTED = 3;
31
+ /**
32
+ * The fewest Oxygents a group holds: two in a group of just its creator, the way
33
+ * group chats began, and one once other people are in it.
34
+ */
35
+ export declare function teammateGroupMinMembers(peopleCount: number): number;
36
+ /** A person's role in a group chat: admins change who is in it and its name (Tim, 2026-10-15). */
37
+ export type TeammateGroupRole = "admin" | "member";
38
+ export declare function isTeammateGroupRole(value: unknown): value is TeammateGroupRole;
21
39
  export type TeammateGroupMember = {
22
40
  agentId: string;
23
41
  slug: string;
24
42
  name: string;
25
43
  };
44
+ /** A person in a group chat, as a mention names them. */
45
+ export type TeammateGroupPerson = {
46
+ actorId: string;
47
+ name: string;
48
+ };
26
49
  /**
27
- * The members a text @mentions, in the order it first mentions them. A mention
28
- * is `@` followed by a member's name or slug, ignoring case; the longest name
29
- * that fits wins, so "@Outbound Lead" is not read as "@Outbound".
50
+ * Who a text @mentions, Oxygents and people, each in the order first mentioned.
51
+ * A mention is `@` followed by an Oxygent's name or slug, or a person's name (or
52
+ * first name, when it is theirs alone), ignoring case; the longest label that
53
+ * fits wins, so "@Outbound Lead" is not read as "@Outbound".
30
54
  */
31
- export declare function teammateGroupMentions(text: string, members: readonly TeammateGroupMember[]): string[];
55
+ export declare function teammateGroupAddressees(text: string, members: readonly TeammateGroupMember[], people?: readonly TeammateGroupPerson[]): {
56
+ agentIds: string[];
57
+ actorIds: string[];
58
+ };
59
+ /** The Oxygents a text @mentions, in the order it first mentions them (people never count). */
60
+ export declare function teammateGroupMentions(text: string, members: readonly TeammateGroupMember[], people?: readonly TeammateGroupPerson[]): string[];
32
61
  /**
33
- * Who answers a person's new message: the first member it mentions, else the
34
- * member who answered last, else the first member. Members are in their group
35
- * order.
62
+ * Who answers a person's new message: the first Oxygent it mentions, else the
63
+ * one who answered last, else the first member. Members are in their group
64
+ * order. The rule of a group of one person when nothing better is known.
36
65
  */
37
66
  export declare function teammateGroupFirstResponder(input: {
38
67
  members: readonly TeammateGroupMember[];
39
68
  message: string;
40
69
  lastResponderAgentId: string | null;
70
+ people?: readonly TeammateGroupPerson[];
41
71
  }): string;
42
72
  /**
43
- * Who answers next after a member's turn on the same message, or null when the
44
- * message is done: the next member the person mentioned who has not answered
45
- * it yet, else a member the latest answer mentioned who has not answered it yet.
73
+ * How a person's message was routed, kept on the turn that answers it (or on
74
+ * the message, when nobody does) so a reader can see why:
75
+ *
76
+ * - `mention`: the Oxygents it @mentioned.
77
+ * - `people`: it @mentioned only people, so no Oxygent answers.
78
+ * - `classifier`: System One said which Oxygents it is for.
79
+ * - `last_responder` / `first_member`: a group of one person, with nothing better known.
80
+ * - `none`: a group with other people, and no Oxygent it is clearly for.
81
+ */
82
+ export type TeammateGroupRouteBy = "mention" | "people" | "classifier" | "last_responder" | "first_member" | "none";
83
+ export type TeammateGroupRoute = {
84
+ by: TeammateGroupRouteBy;
85
+ /** The Oxygents that answer, in order; empty when nobody does. */
86
+ responders: string[];
87
+ };
88
+ /**
89
+ * The part of routing no model is needed for: a message that @mentions
90
+ * Oxygents goes to them, one that @mentions only people goes to nobody, and
91
+ * anything else needs to know what the message is about (null).
92
+ */
93
+ export declare function teammateGroupRouteByMentions(input: {
94
+ message: string;
95
+ members: readonly TeammateGroupMember[];
96
+ people?: readonly TeammateGroupPerson[];
97
+ }): TeammateGroupRoute | null;
98
+ /** System One's yes or no for one Oxygent: whether the message is for it. */
99
+ export type TeammateGroupFit = {
100
+ agentId: string;
101
+ fits: boolean;
102
+ /** How sure, from 0 to 1; null when the answer was unusable. */
103
+ probability: number | null;
104
+ };
105
+ /**
106
+ * Who answers a message no mention settled, from what System One said (null
107
+ * when it said nothing usable: off, unreachable or out of credits): the
108
+ * Oxygents it fits, most fitting first and at most {@link TEAMMATE_GROUP_MAX_ROUTED};
109
+ * when none fits, the one who answered last (else the first member) in a group
110
+ * of one person, and nobody in a group with other people.
111
+ */
112
+ export declare function teammateGroupRouteFromFits(input: {
113
+ members: readonly TeammateGroupMember[];
114
+ fits: readonly TeammateGroupFit[] | null;
115
+ withPeople: boolean;
116
+ lastResponderAgentId: string | null;
117
+ }): TeammateGroupRoute;
118
+ /**
119
+ * Who answers next after an Oxygent's turn on the same message, or null when the
120
+ * message is done: the next Oxygent it was routed to that has not answered yet,
121
+ * else the next one it mentioned, else one the latest answer mentioned.
46
122
  */
47
123
  export declare function teammateGroupNextResponder(input: {
48
124
  members: readonly TeammateGroupMember[];
49
125
  /** The person's message the turns answer. */
50
126
  message: string;
51
- /** The members that answered it so far, in order; the last one answered just now. */
127
+ /** The Oxygents that answered it so far, in order; the last one answered just now. */
52
128
  answered: readonly string[];
53
129
  /** The text of the answer just given. */
54
130
  answer: string;
131
+ /** Who the message was routed to, in order (the turn's routing), when known. */
132
+ planned?: readonly string[];
133
+ people?: readonly TeammateGroupPerson[];
55
134
  }): string | null;
@@ -1,36 +1,81 @@
1
1
  /**
2
- * Oxygent group chats (ADR 0032, phase 7), the way Grok Bot's are: one person's
3
- * private conversation with two to six of the workspace's Oxygents (Tim,
4
- * 2026-10-12: "really emulate grokbot"). Each turn is answered by one member:
2
+ * Oxygent group chats (ADR 0032, phase 7), the way Grok Bot's are (Tim,
3
+ * 2026-10-12: "really emulate grokbot"): a conversation with up to six of the
4
+ * workspace's Oxygents and, since 2026-10-15, teammates too, picked the way a
5
+ * WhatsApp group's members are (Tim: "group chat with a bot and your team mates
6
+ * ... that you can select who can join the group"). Each answer is one Oxygent's:
5
7
  *
6
- * - The person picks with @mentions, by name or slug: the first one mentioned
7
- * answers, and each other one mentioned answers after it, in order.
8
- * - With no mention, the member who answered last answers again; in a group
9
- * nobody has answered yet, the first member. No member ranks above the
10
- * others (Tim, 2026-10-13: no Chief of Staff).
11
- * - A member's answer may @mention another member to bring them in. Each member
12
- * answers a person's message at most once, so a message ends after at most
13
- * one turn per member.
8
+ * - An @mention picks: the Oxygents a message mentions answer, in order. A
9
+ * message that @mentions only people is theirs, and no Oxygent answers it.
10
+ * - With no mention, the Oxygents the message is for answer, most fitting first
11
+ * and at most {@link TEAMMATE_GROUP_MAX_ROUTED} (Tim, 2026-10-15: "the agents
12
+ * should know which is assigned based on the query ... it just needs to be fast
13
+ * and multiple agents should be able to answer"). The web asks System One, one
14
+ * yes/no per Oxygent in a single call. When none fits, or System One cannot
15
+ * say: in a group of one person, the Oxygent who answered last answers, else
16
+ * the first one (no member ranks above the others; Tim, 2026-10-13: no Chief of
17
+ * Staff); in a group with other people, nobody does, and they can @mention one.
18
+ * - An answer may @mention another Oxygent to bring it in. Each Oxygent answers a
19
+ * message at most once, so a message ends after at most one turn per Oxygent.
14
20
  *
15
- * Pure rules, shared by the web (which picks the first responder when the
16
- * person sends) and the worker (which relays to the next one after a turn).
21
+ * Pure rules, shared by the web (which routes a person's message as it is sent)
22
+ * and the worker (which relays to the next Oxygent after a turn).
17
23
  */
18
24
  export const TEAMMATE_GROUP_MIN_MEMBERS = 2;
19
25
  export const TEAMMATE_GROUP_MAX_MEMBERS = 6;
26
+ /** The people a group holds, its creator included. */
27
+ export const TEAMMATE_GROUP_MAX_PEOPLE = 50;
20
28
  export const TEAMMATE_GROUP_TITLE_MAX_CHARS = 80;
29
+ /** The most Oxygents that answer one message nobody @mentioned. */
30
+ export const TEAMMATE_GROUP_MAX_ROUTED = 3;
31
+ /**
32
+ * The fewest Oxygents a group holds: two in a group of just its creator, the way
33
+ * group chats began, and one once other people are in it.
34
+ */
35
+ export function teammateGroupMinMembers(peopleCount) {
36
+ return peopleCount > 1 ? 1 : TEAMMATE_GROUP_MIN_MEMBERS;
37
+ }
38
+ export function isTeammateGroupRole(value) {
39
+ return value === "admin" || value === "member";
40
+ }
21
41
  /** What can follow a mention: the end, whitespace or punctuation, never more of a word. */
22
42
  const MENTION_BOUNDARY = /^(?:$|[\s.,;:!?)\]}'"’…-])/u;
23
43
  /**
24
- * The members a text @mentions, in the order it first mentions them. A mention
25
- * is `@` followed by a member's name or slug, ignoring case; the longest name
26
- * that fits wins, so "@Outbound Lead" is not read as "@Outbound".
44
+ * The labels a person answers to: their name, and their first name while no one
45
+ * else in the group shares it.
27
46
  */
28
- export function teammateGroupMentions(text, members) {
29
- const candidates = members
30
- .flatMap((member) => [member.name, member.slug].map((label) => ({ agentId: member.agentId, label: label.trim() })))
47
+ function personLabels(people) {
48
+ const firstNames = new Map();
49
+ for (const person of people) {
50
+ const first = person.name.trim().split(/\s+/u)[0]?.toLowerCase() ?? "";
51
+ if (first)
52
+ firstNames.set(first, (firstNames.get(first) ?? 0) + 1);
53
+ }
54
+ return people.flatMap((person) => {
55
+ const name = person.name.trim();
56
+ const first = name.split(/\s+/u)[0] ?? "";
57
+ const labels = [name];
58
+ if (first && first !== name && firstNames.get(first.toLowerCase()) === 1)
59
+ labels.push(first);
60
+ return labels.map((label) => ({ kind: "person", id: person.actorId, label }));
61
+ });
62
+ }
63
+ /**
64
+ * Who a text @mentions, Oxygents and people, each in the order first mentioned.
65
+ * A mention is `@` followed by an Oxygent's name or slug, or a person's name (or
66
+ * first name, when it is theirs alone), ignoring case; the longest label that
67
+ * fits wins, so "@Outbound Lead" is not read as "@Outbound".
68
+ */
69
+ export function teammateGroupAddressees(text, members, people = []) {
70
+ const candidates = [
71
+ ...members.flatMap((member) => [member.name, member.slug]
72
+ .map((label) => ({ kind: "oxygent", id: member.agentId, label: label.trim() }))),
73
+ ...personLabels(people),
74
+ ]
31
75
  .filter((candidate) => candidate.label.length > 0)
32
76
  .sort((left, right) => right.label.length - left.label.length);
33
- const found = [];
77
+ const agentIds = [];
78
+ const actorIds = [];
34
79
  for (let at = text.indexOf("@"); at >= 0; at = text.indexOf("@", at + 1)) {
35
80
  // An @ inside a word (an email address) mentions nobody.
36
81
  if (at > 0 && /[\p{L}\p{N}_]/u.test(text[at - 1]))
@@ -38,22 +83,29 @@ export function teammateGroupMentions(text, members) {
38
83
  const rest = text.slice(at + 1);
39
84
  const lower = rest.toLowerCase();
40
85
  const match = candidates.find((candidate) => lower.startsWith(candidate.label.toLowerCase()) && MENTION_BOUNDARY.test(rest.slice(candidate.label.length)));
41
- if (match && !found.includes(match.agentId))
42
- found.push(match.agentId);
86
+ if (!match)
87
+ continue;
88
+ const found = match.kind === "oxygent" ? agentIds : actorIds;
89
+ if (!found.includes(match.id))
90
+ found.push(match.id);
43
91
  }
44
- return found;
92
+ return { agentIds, actorIds };
93
+ }
94
+ /** The Oxygents a text @mentions, in the order it first mentions them (people never count). */
95
+ export function teammateGroupMentions(text, members, people = []) {
96
+ return teammateGroupAddressees(text, members, people).agentIds;
45
97
  }
46
98
  /**
47
- * Who answers a person's new message: the first member it mentions, else the
48
- * member who answered last, else the first member. Members are in their group
49
- * order.
99
+ * Who answers a person's new message: the first Oxygent it mentions, else the
100
+ * one who answered last, else the first member. Members are in their group
101
+ * order. The rule of a group of one person when nothing better is known.
50
102
  */
51
103
  export function teammateGroupFirstResponder(input) {
52
104
  const { members } = input;
53
105
  if (members.length === 0)
54
106
  throw new Error("A group chat needs members to answer.");
55
107
  const ids = new Set(members.map((member) => member.agentId));
56
- const mentioned = teammateGroupMentions(input.message, members)[0];
108
+ const mentioned = teammateGroupMentions(input.message, members, input.people)[0];
57
109
  if (mentioned)
58
110
  return mentioned;
59
111
  if (input.lastResponderAgentId && ids.has(input.lastResponderAgentId))
@@ -61,14 +113,55 @@ export function teammateGroupFirstResponder(input) {
61
113
  return members[0].agentId;
62
114
  }
63
115
  /**
64
- * Who answers next after a member's turn on the same message, or null when the
65
- * message is done: the next member the person mentioned who has not answered
66
- * it yet, else a member the latest answer mentioned who has not answered it yet.
116
+ * The part of routing no model is needed for: a message that @mentions
117
+ * Oxygents goes to them, one that @mentions only people goes to nobody, and
118
+ * anything else needs to know what the message is about (null).
119
+ */
120
+ export function teammateGroupRouteByMentions(input) {
121
+ const { agentIds, actorIds } = teammateGroupAddressees(input.message, input.members, input.people);
122
+ if (agentIds.length > 0)
123
+ return { by: "mention", responders: agentIds };
124
+ if (actorIds.length > 0)
125
+ return { by: "people", responders: [] };
126
+ return null;
127
+ }
128
+ /**
129
+ * Who answers a message no mention settled, from what System One said (null
130
+ * when it said nothing usable: off, unreachable or out of credits): the
131
+ * Oxygents it fits, most fitting first and at most {@link TEAMMATE_GROUP_MAX_ROUTED};
132
+ * when none fits, the one who answered last (else the first member) in a group
133
+ * of one person, and nobody in a group with other people.
134
+ */
135
+ export function teammateGroupRouteFromFits(input) {
136
+ const order = new Map(input.members.map((member, index) => [member.agentId, index]));
137
+ const fitting = (input.fits ?? [])
138
+ .filter((fit) => fit.fits && order.has(fit.agentId))
139
+ .sort((left, right) => (right.probability ?? 0) - (left.probability ?? 0)
140
+ || order.get(left.agentId) - order.get(right.agentId))
141
+ .slice(0, TEAMMATE_GROUP_MAX_ROUTED)
142
+ .map((fit) => fit.agentId);
143
+ if (fitting.length > 0)
144
+ return { by: "classifier", responders: fitting };
145
+ if (input.withPeople || input.members.length === 0)
146
+ return { by: "none", responders: [] };
147
+ if (input.lastResponderAgentId && order.has(input.lastResponderAgentId)) {
148
+ return { by: "last_responder", responders: [input.lastResponderAgentId] };
149
+ }
150
+ return { by: "first_member", responders: [input.members[0].agentId] };
151
+ }
152
+ /**
153
+ * Who answers next after an Oxygent's turn on the same message, or null when the
154
+ * message is done: the next Oxygent it was routed to that has not answered yet,
155
+ * else the next one it mentioned, else one the latest answer mentioned.
67
156
  */
68
157
  export function teammateGroupNextResponder(input) {
69
158
  const answered = new Set(input.answered);
70
- const fromPerson = teammateGroupMentions(input.message, input.members).find((agentId) => !answered.has(agentId));
159
+ const ids = new Set(input.members.map((member) => member.agentId));
160
+ const fromPlan = (input.planned ?? []).find((agentId) => ids.has(agentId) && !answered.has(agentId));
161
+ if (fromPlan)
162
+ return fromPlan;
163
+ const fromPerson = teammateGroupMentions(input.message, input.members, input.people).find((agentId) => !answered.has(agentId));
71
164
  if (fromPerson)
72
165
  return fromPerson;
73
- return teammateGroupMentions(input.answer, input.members).find((agentId) => !answered.has(agentId)) ?? null;
166
+ return teammateGroupMentions(input.answer, input.members, input.people).find((agentId) => !answered.has(agentId)) ?? null;
74
167
  }
@@ -23,7 +23,7 @@ export declare const TEAMMATE_SLUG_PATTERN = "^[a-z][a-z0-9-]{1,48}$";
23
23
  */
24
24
  export declare function teammateMessageInputSchema(teammates: readonly string[]): Record<string, unknown>;
25
25
  /** What the tool returns when it refuses, so the model can say why or do the work itself. */
26
- export type TeammateMessageRefusalCode = "teammate_message_invalid" | "teammate_message_unknown_teammate" | "teammate_message_depth" | "teammate_message_loop" | "teammate_message_chain_full" | "teammate_message_round_full" | "teammate_message_reply_unknown" | "teammate_message_no_chat";
26
+ export type TeammateMessageRefusalCode = "teammate_message_invalid" | "teammate_message_unknown_teammate" | "teammate_message_depth" | "teammate_message_loop" | "teammate_message_chain_full" | "teammate_message_round_full" | "teammate_message_reply_unknown" | "teammate_message_no_chat" | "teammate_message_not_shared";
27
27
  /** One message as the receiving turn's text renders it: fenced, because it is another Oxygent's words, not the person's. */
28
28
  export type TeammateMessageForPrompt = {
29
29
  id: string;
@@ -1,6 +1,7 @@
1
1
  /**
2
- * AI teammates (ADR 0032): Agents the whole workspace talks to, each person in
3
- * one ongoing private chat per teammate, on the Copilot runtime.
2
+ * AI teammates (ADR 0032): Agents people talk to, each person in one ongoing
3
+ * private chat per teammate, on the Copilot runtime. Each is its owner's until
4
+ * shared with teammates (Tim, 2026-10-14); the first one is everyone's.
4
5
  *
5
6
  * Staff-only on dev. `OXYGEN_TEAMMATES_ENABLED` is the switch; when it is
6
7
  * unset the feature follows the Ongoing Agent sessions staff gate, which the
@@ -35,6 +36,31 @@ export declare const TEAMMATE_AVATAR_COLORS: readonly ["forest", "brown", "red",
35
36
  export type TeammateAvatarColor = typeof TEAMMATE_AVATAR_COLORS[number];
36
37
  export declare function isTeammateAvatarKey(value: unknown): value is TeammateAvatarKey;
37
38
  export declare function isTeammateAvatarColor(value: unknown): value is TeammateAvatarColor;
39
+ /**
40
+ * Who may use an Oxygent besides its owner, the way Grok Bot shares a Bot
41
+ * (Tim, 2026-10-14: "make agents accessible to your team mates that you can
42
+ * collaborate with your team mates"). A collaborator chats with it and uses its
43
+ * skills, in a chat of their own; a manager also changes its profile and who
44
+ * has access. Deleting it stays with its owner or a workspace admin.
45
+ */
46
+ export declare const TEAMMATE_SHARE_ROLES: readonly ["collaborator", "manager"];
47
+ export type TeammateShareRole = typeof TEAMMATE_SHARE_ROLES[number];
48
+ /** What a person is to an Oxygent: its owner, or what it was shared with them as. */
49
+ export type TeammateAccessRole = "owner" | TeammateShareRole;
50
+ export declare function isTeammateShareRole(value: unknown): value is TeammateShareRole;
51
+ /**
52
+ * A person's role with an Oxygent, or null when it is not shared with them.
53
+ * The first Oxygent is everyone's to use and manage: Home opens its chat.
54
+ * Otherwise its owner owns it, and the broader of what everyone in the
55
+ * workspace may do (`everyone`) and what the person was given (`personal`)
56
+ * applies.
57
+ */
58
+ export declare function teammateAccessRole(teammate: {
59
+ slug: string;
60
+ ownerActorId: string | null;
61
+ everyone: TeammateShareRole | null;
62
+ personal: TeammateShareRole | null;
63
+ }, actorId: string): TeammateAccessRole | null;
38
64
  /**
39
65
  * The look an Oxygent wears until someone picks one: the first Oxygent wears
40
66
  * OXYGEN's own (an O2 molecule in forest green); any other one a look its slug
@@ -1,7 +1,8 @@
1
1
  import { isStaffEmail } from "./staff.js";
2
2
  /**
3
- * AI teammates (ADR 0032): Agents the whole workspace talks to, each person in
4
- * one ongoing private chat per teammate, on the Copilot runtime.
3
+ * AI teammates (ADR 0032): Agents people talk to, each person in one ongoing
4
+ * private chat per teammate, on the Copilot runtime. Each is its owner's until
5
+ * shared with teammates (Tim, 2026-10-14); the first one is everyone's.
5
6
  *
6
7
  * Staff-only on dev. `OXYGEN_TEAMMATES_ENABLED` is the switch; when it is
7
8
  * unset the feature follows the Ongoing Agent sessions staff gate, which the
@@ -51,6 +52,35 @@ export function isTeammateAvatarKey(value) {
51
52
  export function isTeammateAvatarColor(value) {
52
53
  return typeof value === "string" && TEAMMATE_AVATAR_COLORS.includes(value);
53
54
  }
55
+ /**
56
+ * Who may use an Oxygent besides its owner, the way Grok Bot shares a Bot
57
+ * (Tim, 2026-10-14: "make agents accessible to your team mates that you can
58
+ * collaborate with your team mates"). A collaborator chats with it and uses its
59
+ * skills, in a chat of their own; a manager also changes its profile and who
60
+ * has access. Deleting it stays with its owner or a workspace admin.
61
+ */
62
+ export const TEAMMATE_SHARE_ROLES = ["collaborator", "manager"];
63
+ export function isTeammateShareRole(value) {
64
+ return typeof value === "string" && TEAMMATE_SHARE_ROLES.includes(value);
65
+ }
66
+ /**
67
+ * A person's role with an Oxygent, or null when it is not shared with them.
68
+ * The first Oxygent is everyone's to use and manage: Home opens its chat.
69
+ * Otherwise its owner owns it, and the broader of what everyone in the
70
+ * workspace may do (`everyone`) and what the person was given (`personal`)
71
+ * applies.
72
+ */
73
+ export function teammateAccessRole(teammate, actorId) {
74
+ if (teammate.slug === DEFAULT_TEAMMATE_SLUG)
75
+ return "manager";
76
+ if (teammate.ownerActorId && teammate.ownerActorId === actorId)
77
+ return "owner";
78
+ if (teammate.everyone === "manager" || teammate.personal === "manager")
79
+ return "manager";
80
+ if (teammate.everyone === "collaborator" || teammate.personal === "collaborator")
81
+ return "collaborator";
82
+ return null;
83
+ }
54
84
  /**
55
85
  * The look an Oxygent wears until someone picks one: the first Oxygent wears
56
86
  * OXYGEN's own (an O2 molecule in forest green); any other one a look its slug
@@ -97,18 +97,25 @@ function isUnconfirmedLookupReviewIntent(query) {
97
97
  && /\b(?:lookup|provider|supplier|research)\b/.test(query)
98
98
  && /\b(?:outcome|spend|cost|credits?|effect|run)\b/.test(query);
99
99
  }
100
+ /**
101
+ * Who may use one (2026-10-14), ahead of the removal words a sharing ask
102
+ * carries ("remove Tim's access to it"); "shared" alone describes one.
103
+ */
104
+ const OXYGENT_SHARING_VERBS = [
105
+ ["share", /\b(?:shar(?:e|es|ing)|unshar\w*)\b/],
106
+ ["access", /\b(?:who (?:can|has)|access)\b/],
107
+ ];
100
108
  /**
101
109
  * The `oxygents` operation an ask names (its MCP verb), first match wins: a
102
110
  * removal or copy beats the create words a name can carry ("delete the new
103
- * bot"), a group beats its members' verbs, and reading or opening a chat beats
104
- * the message words its object carries ("read my chat with it").
111
+ * bot"), and reading or opening a chat beats the message words its object
112
+ * carries ("read my chat with it"). An ask about a group reads its own list.
105
113
  */
106
114
  const OXYGENT_VERBS = [
115
+ ...OXYGENT_SHARING_VERBS,
107
116
  ["delete", /\b(?:delet\w*|remov\w*|eras\w*|destroy\w*|get rid of)\b/],
108
117
  ["duplicate", /\b(?:cop(?:y|ies|ying)|duplicat\w*|clon\w*)\b/],
109
118
  ["notify", /\b(?:notif\w*|mute\w*|unmute\w*|silenc\w*)\b/],
110
- ["group_create", /\b(?:creat\w*|start\w*|new|make)\b.*\bgroups?\b|\bgroups?\b.*\b(?:creat\w*|start\w*)\b/],
111
- ["groups", /\bgroups?\b/],
112
119
  ["update", /\b(?:renam\w*|edit\w*|updat\w*|chang\w*)\b/],
113
120
  ["pin", /\b(?:pin|pins|pinned|pinning|unpin\w*)\b/],
114
121
  ["hide", /\b(?:hid(?:e|es|ing|den)|unhid\w*)\b/],
@@ -125,21 +132,46 @@ const OXYGENT_VERBS = [
125
132
  ["message", /\b(?:message\w*|ask|tell|send|talk|chat|hand\w* off|handoff|delegat\w*)\b/],
126
133
  ["create", /\b(?:creat\w*|add|new|make|set up|spawn|hire)\b/],
127
134
  ];
135
+ /**
136
+ * The verb of an ask about a group chat, which holds people since 2026-10-15:
137
+ * its roles and people before the create and removal words they carry ("make
138
+ * Tim an admin", "remove Tim from it"), and its end before them too, so
139
+ * changing a group never suggests deleting an Oxygent for everyone.
140
+ */
141
+ const OXYGENT_GROUP_VERBS = [
142
+ ...OXYGENT_SHARING_VERBS,
143
+ ["group_update", /\b(?:admins?|moderators?)\b/],
144
+ ["group_leave", /\b(?:leav(?:e|es|ing)|quit\w*|exit\w*)\b/],
145
+ ["group_end", /\b(?:end|ends|ending|clos(?:e|es|ing)|delet\w*|eras\w*|destroy\w*|get rid of)\b/],
146
+ ["group_create", /\b(?:creat\w*|start\w*|set up)\b|\b(?:new|make)\b.*\bgroups?\b/],
147
+ ["group_update", /\b(?:add\w*|remov\w*|kick\w*|invit\w*|bring\w*|includ\w*|join\w*|renam\w*)\b/],
148
+ ["group_message", /\b(?:message\w*|send|ask|tell|post|say)\b/],
149
+ ["group_read", /\b(?:unread|mark\w*\b.*\bread)\b/],
150
+ ["group", /\b(?:read|see|view|show|history|transcript)\b/],
151
+ ];
128
152
  /**
129
153
  * Oxygents (AI teammates, ADR 0032) share the Agents card, whose gateway is the
130
154
  * Agent commands, so an ask naming one got `agent list/create/run` and, in
131
155
  * `commands search`, no `oxygents` command at all (blind CLI evals, 2026-10-13
132
- * and 2026-10-14). Only an explicit Oxygent ask moves: a plain "agent" ask,
156
+ * and 2026-10-14). Only an explicit Oxygent ask moves, or one about a group
157
+ * chat that names no other channel's (WhatsApp, Teams): a plain "agent" ask,
133
158
  * and a person's human "teammate", keep their owners.
134
159
  */
135
160
  function oxygentsRoute(normalized) {
136
- if (!/\b(?:oxygents?|ai teammates?)\b/.test(normalized))
161
+ const groupChat = /\bgroup chats?\b/.test(normalized)
162
+ && !/\b(?:whatsapp|teams|slack|telegram|linkedin|discord|signal|sms|e ?mail|inbox|unibox)\b/.test(normalized);
163
+ if (!groupChat && !/\b(?:oxygents?|ai teammates?)\b/.test(normalized))
137
164
  return null;
138
165
  const route = getCapabilityRouteMatch("agents");
139
166
  if (!route)
140
167
  return null;
141
- const verb = OXYGENT_VERBS.find(([, words]) => words.test(normalized))?.[0] ?? "list";
142
- const verbs = [...new Set([verb, "list", "get"])];
168
+ const group = /\bgroups?\b/.test(normalized);
169
+ const verb = (group ? OXYGENT_GROUP_VERBS : OXYGENT_VERBS).find(([, words]) => words.test(normalized))?.[0]
170
+ ?? (group ? "groups" : "list");
171
+ // Sharing is read and changed as a pair: who has access, then share.
172
+ const pair = verb === "share" ? ["access"] : verb === "access" ? ["share"] : [];
173
+ // A group's id comes from its list; the roster reads follow every ask.
174
+ const verbs = [...new Set([verb, ...pair, ...(group ? ["groups"] : []), "list", "get"])];
143
175
  return {
144
176
  ...route,
145
177
  recommendedTools: verbs.map((name) => `oxygen_oxygents_${name}`),
@@ -1 +1 @@
1
- export declare const OXYGEN_BUILD_VERSION = "1.1014.619";
1
+ export declare const OXYGEN_BUILD_VERSION = "1.1014.771";
@@ -1,2 +1,2 @@
1
1
  // GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
2
- export const OXYGEN_BUILD_VERSION = "1.1014.619";
2
+ export const OXYGEN_BUILD_VERSION = "1.1014.771";
@@ -1,3 +1,4 @@
1
+ export { compileConditionPattern } from "../refactor-internals/condition-comparison.js";
1
2
  import type { WorkflowCompareOp, WorkflowCondition, WorkflowValueCoercion, WorkflowValueRef } from "./types.js";
2
3
  /**
3
4
  * The data a v2 run exposes to its value refs.
@@ -77,22 +78,6 @@ export declare function resolveValueRef(ref: WorkflowValueRef, scope: WorkflowRu
77
78
  */
78
79
  export declare function resolveRequiredValueRef(ref: WorkflowValueRef, scope: WorkflowRunScope): unknown;
79
80
  export declare function evaluateCondition(condition: WorkflowCondition, scope: WorkflowRunScope): boolean;
80
- /**
81
- * Compile a `matches` pattern, or return null when it is unsafe or invalid.
82
- *
83
- * Choice: conservative STATIC REJECTION rather than a linear (Thompson NFA)
84
- * matcher. A linear engine would be several hundred lines of new, security-load-
85
- * bearing code in a module the browser also bundles, whereas the patterns real
86
- * GTM authors write (domain suffixes, job-title keywords, ID formats) never need
87
- * the constructs rejected here. Rejected: sources over 200 characters,
88
- * lookaround, backreferences, and a quantified group whose body itself contains
89
- * an unbounded quantifier — the `(a+)+` family that drives catastrophic
90
- * backtracking. Everything else compiles as a normal RegExp.
91
- *
92
- * The linter calls this at authoring time and reports `invalid_condition`, so a
93
- * rejected pattern is a lint error the author sees, not a silent run-time false.
94
- */
95
- export declare function compileConditionPattern(pattern: string): RegExp | null;
96
81
  /**
97
82
  * Node ids a ref or condition reads, in first-seen order.
98
83
  *