@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.
- package/README.md +1 -1
- package/dist/command-manifest.js +15 -0
- package/dist/command-support.d.ts +5 -5
- package/dist/command-support.js +7 -7
- package/dist/commands/billing.js +12 -6
- package/dist/commands/columns.js +2 -2
- package/dist/commands/companies.js +1 -1
- package/dist/commands/copilot.js +1 -1
- package/dist/commands/dashboards.js +1 -1
- package/dist/commands/enrich-column.js +2 -2
- package/dist/commands/find.js +2 -2
- package/dist/commands/mailboxes.js +36 -0
- package/dist/commands/recipes.js +1 -1
- package/dist/commands/sequences.js +6 -6
- package/dist/commands/tables.js +1 -1
- package/dist/commands/verify.js +112 -11
- package/dist/http-client.js +1 -70
- package/dist/index.js +1 -1
- package/dist/refactor-internals/binary-download.d.ts +5 -0
- package/dist/refactor-internals/binary-download.js +71 -0
- package/node_modules/@oxygen/formula/dist/evaluate.js +2 -82
- package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.d.ts +2 -0
- package/node_modules/@oxygen/formula/dist/refactor-internals/novelty-score.js +87 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +15 -3
- package/node_modules/@oxygen/shared/dist/billing.js +19 -7
- package/node_modules/@oxygen/shared/dist/cancellation-reasons.d.ts +35 -0
- package/node_modules/@oxygen/shared/dist/cancellation-reasons.js +21 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +27 -1
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +6 -6
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +1 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +41 -17
- package/node_modules/@oxygen/shared/dist/email-verification-batch.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/email-verification-batch.js +55 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +3 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +3 -3
- package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.d.ts +70 -0
- package/node_modules/@oxygen/shared/dist/knowledge-brain-draft.js +42 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +3 -1
- package/node_modules/@oxygen/shared/dist/product-doctrine.js +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.d.ts +225 -0
- package/node_modules/@oxygen/shared/dist/sequence-internals/esp-policy.js +578 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +1 -226
- package/node_modules/@oxygen/shared/dist/sequences.js +2 -576
- package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +103 -24
- package/node_modules/@oxygen/shared/dist/teammate-groups.js +125 -32
- package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +28 -2
- package/node_modules/@oxygen/shared/dist/teammates.js +32 -2
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +40 -8
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +1 -16
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +4 -224
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -2
- package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -63
- package/node_modules/@oxygen/workflows/dist/index.js +1 -193
- package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.d.ts +18 -0
- package/node_modules/@oxygen/workflows/dist/refactor-internals/condition-comparison.js +222 -0
- package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.d.ts +64 -0
- package/node_modules/@oxygen/workflows/dist/refactor-internals/execution-policy.js +204 -0
- package/node_modules/@oxygen/workflows/package.json +4 -0
- 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
|
|
3
|
-
*
|
|
4
|
-
* 2026-10-
|
|
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
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
* - With no mention, the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* one
|
|
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
|
|
16
|
-
*
|
|
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
|
-
*
|
|
28
|
-
* is `@` followed by
|
|
29
|
-
*
|
|
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
|
|
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
|
|
34
|
-
*
|
|
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
|
-
*
|
|
44
|
-
* message
|
|
45
|
-
*
|
|
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
|
|
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
|
|
3
|
-
*
|
|
4
|
-
* 2026-10-
|
|
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
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
* - With no mention, the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* one
|
|
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
|
|
16
|
-
*
|
|
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
|
|
25
|
-
*
|
|
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
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
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
|
|
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 (
|
|
42
|
-
|
|
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
|
|
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
|
|
48
|
-
*
|
|
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
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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
|
|
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
|
|
3
|
-
*
|
|
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
|
|
4
|
-
*
|
|
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"),
|
|
104
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
142
|
-
const
|
|
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.
|
|
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.
|
|
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
|
*
|