@oxygen-agent/cli 1.1014.1599 → 1.1014.1696
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 +4 -1
- package/dist/commands/custom-integrations.js +13 -0
- package/dist/commands/inbox.js +1 -1
- package/dist/commands/integrations.js +5 -1
- package/dist/commands/mailboxes.js +26 -6
- package/dist/commands/managed-inboxes.js +2 -2
- package/dist/commands/senders.js +4 -4
- package/dist/credential-storage/store.d.ts +10 -0
- package/dist/credential-storage/store.js +282 -0
- package/dist/credentials.d.ts +1 -3
- package/dist/credentials.js +3 -275
- package/dist/mailbox-fleet-output.js +11 -2
- package/dist/workspace-placement-commands.js +3 -2
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +63 -9
- package/node_modules/@oxygen/shared/dist/clay-migration-auth-state.d.ts +7 -1
- package/node_modules/@oxygen/shared/dist/clay-migration-auth-state.js +6 -5
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +1 -0
- package/node_modules/@oxygen/shared/dist/contracts/clay-migration.d.ts +39 -1
- package/node_modules/@oxygen/shared/dist/contracts/clay-migration.js +32 -4
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +2 -1
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +2 -2
- package/node_modules/@oxygen/shared/dist/email-classification-scope.d.ts +35 -0
- package/node_modules/@oxygen/shared/dist/email-classification-scope.js +40 -0
- package/node_modules/@oxygen/shared/dist/native-chat.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/native-chat.js +12 -1
- package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +9 -2
- package/node_modules/@oxygen/shared/dist/onboarding-research.js +9 -2
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +13 -0
- package/node_modules/@oxygen/shared/dist/sending-domain-rollout.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/sending-domain-rollout.js +28 -0
- package/node_modules/@oxygen/shared/dist/tool-call-arguments.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/tool-call-arguments.js +68 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- 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/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement.js +11 -0
- package/package.json +1 -1
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import { arr, enumOf, int, obj, str } from "./schema.js";
|
|
1
|
+
import { arr, bool, enumOf, int, obj, str } from "./schema.js";
|
|
2
2
|
export const CLAY_MIGRATION_ACTIONS = ["connect", "status", "discover", "read", "transfer", "checkpoint", "report", "disconnect"];
|
|
3
3
|
export const CLAY_RESOURCE_TYPES = ["workspace", "folder", "workbook", "table", "audience", "workflow", "function", "claygent", "knowledge", "schedule", "campaign"];
|
|
4
4
|
export const CLAY_RESOURCE_DISPOSITIONS = ["pending", "copied", "rebuilt", "needs_decision", "unsupported", "inaccessible", "excluded", "failed"];
|
|
5
|
+
export const CLAY_GLOBAL_AUDIENCE_SCOPES = {
|
|
6
|
+
"global-people": { entity_type: "people", global: true },
|
|
7
|
+
"global-companies": { entity_type: "companies", global: true },
|
|
8
|
+
"global-deals": { entity_type: "deals", global: true },
|
|
9
|
+
"global-archived-people": { entity_type: "people", global: true, archived: true },
|
|
10
|
+
"global-archived-companies": { entity_type: "companies", global: true, archived: true },
|
|
11
|
+
"global-archived-deals": { entity_type: "deals", global: true, archived: true },
|
|
12
|
+
};
|
|
13
|
+
export function clayGlobalAudienceScope(resourceId) {
|
|
14
|
+
return Object.hasOwn(CLAY_GLOBAL_AUDIENCE_SCOPES, resourceId) ? CLAY_GLOBAL_AUDIENCE_SCOPES[resourceId] : null;
|
|
15
|
+
}
|
|
5
16
|
export const CLAY_MIGRATION_CONTRACT = obj({
|
|
6
17
|
action: enumOf(CLAY_MIGRATION_ACTIONS),
|
|
7
18
|
migration_id: str({ minLength: 1, maxLength: 200 }),
|
|
8
19
|
source_workspace_id: str({ minLength: 1, maxLength: 200 }),
|
|
9
20
|
resource_type: enumOf(CLAY_RESOURCE_TYPES),
|
|
10
|
-
resource_id: str({ minLength: 1, maxLength:
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
resource_id: str({ minLength: 1, maxLength: 256 }),
|
|
22
|
+
row_id: str({ minLength: 1, maxLength: 200, pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]*$", description: "Source row ID for table read section=row; retains structured cell fields separately from displayed values." }),
|
|
23
|
+
section: enumOf(["metadata", "columns", "rows", "row", "graph", "fields", "record_ids", "records", "sequence"]),
|
|
24
|
+
path: str({ minLength: 1, maxLength: 1000, description: "Dotted path or JSON pointer into a source response, e.g. cells.f_1.fields." }),
|
|
13
25
|
text_offset: int({ minimum: 0, maximum: 8388608, description: "Continue a large scalar read at next_text_offset." }),
|
|
14
26
|
entity_type: enumOf(["people", "companies", "deals"]),
|
|
27
|
+
archived: bool({ description: "Read archived Audience IDs instead of the default scope; only read section=record_ids. Transfer scope is pinned by resource_id." }),
|
|
15
28
|
record_ids: arr(int({ minimum: 1 }), { minItems: 1, maxItems: 100 }),
|
|
16
29
|
cursor: str({ maxLength: 4000 }),
|
|
17
30
|
limit: int({ minimum: 1, maximum: 100 }),
|
|
@@ -48,6 +61,21 @@ export function clayMigrationInputIssues(args) {
|
|
|
48
61
|
requireField("entity_type");
|
|
49
62
|
requireField("record_ids");
|
|
50
63
|
}
|
|
64
|
+
if (args.archived !== undefined && !(args.action === "read" && args.resource_type === "audience" && args.section === "record_ids")) {
|
|
65
|
+
issues.push({ path: "archived", message: "archived is only available for Audience read section=record_ids; transfer scope is bound to its discovered resource." });
|
|
66
|
+
}
|
|
67
|
+
const rowRead = args.action === "read" && args.resource_type === "table" && args.section === "row";
|
|
68
|
+
if (args.section === "row" && !rowRead)
|
|
69
|
+
issues.push({ path: "section", message: "section=row requires action=read and resource_type=table." });
|
|
70
|
+
if (rowRead) {
|
|
71
|
+
requireField("row_id");
|
|
72
|
+
for (const key of ["cursor", "limit", "entity_type", "record_ids", "target_table", "mapping", "source_cells_column", "source_row_id_column", "disposition", "target_kind", "target_id", "notes"]) {
|
|
73
|
+
if (args[key] !== undefined)
|
|
74
|
+
issues.push({ path: key, message: `${key} is unavailable for a single row read.` });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (args.row_id !== undefined)
|
|
78
|
+
issues.push({ path: "row_id", message: "row_id is only available for table read section=row." });
|
|
51
79
|
return issues;
|
|
52
80
|
}
|
|
53
81
|
export const CLAY_MIGRATION_OPERATION = {
|
|
@@ -36,8 +36,9 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
36
36
|
operation("sequences.standby.get", "/api/cli/sequences/{sequence}/standby", "GET", "sequences standby get", "oxygen_sequences_standby_get", "sequences", "Read automatic mailbox replacement policy, ready reserve and replacements for a campaign."),
|
|
37
37
|
operation("sequences.standby.set", "/api/cli/sequences/{sequence}/standby", "PATCH", "sequences standby set", "oxygen_sequences_standby_set", "sequences", "Enable or disable automatic replacement after bounce protection pauses a mailbox. Preview first; apply with approved and returned plan. Shared reserves may change sender identity for uncontacted leads. Contacted leads keep their original inbox and hold follow-ups. 0 credits.", { enabled: bool(), approved: bool(), plan: text() }, ["enabled"]),
|
|
38
38
|
operation("chat.channel", "/api/cli/chat", "GET", "chat channel", "oxygen_chat_channel", "workspace", "Dev only: read or initialize this workspace's native support channel, persistent threads, unread counts and members; optional search matches messages.", {}, [], { contract: CHAT_CHANNEL_READ_CONTRACT, mutates: true }),
|
|
39
|
+
operation("chat.threads.list", "/api/cli/chat/threads", "GET", "chat threads list", "oxygen_chat_threads_list", "workspace", "Dev only: list up to 100 recently updated support threads in this workspace. Search retained subjects and visible messages; initialized=false means no channel. Never creates a channel or marks messages read.", {}, [], { contract: CHAT_CHANNEL_READ_CONTRACT }),
|
|
39
40
|
operation("chat.threads.create", "/api/cli/chat/threads", "POST", "chat threads create", "oxygen_chat_thread_create", "workspace", "Dev only: create a persistent native support thread; subject and body are customer-visible. No Plain or Slack transport.", {}, [], { contract: CHAT_THREAD_CREATE_CONTRACT }),
|
|
40
|
-
operation("chat.threads.get", "/api/cli/chat/threads/{thread_id}", "GET", "chat threads get", "oxygen_chat_thread_get", "workspace", "Dev only: read a native support thread, messages, members and release progress. Continue message history with the returned cursor.", {}, [], { contract: CHAT_THREAD_READ_CONTRACT }),
|
|
41
|
+
operation("chat.threads.get", "/api/cli/chat/threads/{thread_id}", "GET", "chat threads get", "oxygen_chat_thread_get", "workspace", "Dev only: read a native support thread, messages, members and release progress (none: no verified deployment, dev: verified on dev, prod: verified in production; stale means recheck). Continue message history with the returned cursor.", {}, [], { contract: CHAT_THREAD_READ_CONTRACT }),
|
|
41
42
|
operation("chat.threads.reply", "/api/cli/chat/threads/{thread_id}/messages", "POST", "chat threads reply", "oxygen_chat_thread_reply", "workspace", "Dev only: reply in a native support thread with uploaded attachment IDs and member mentions. Public by default; internal notes require staff.", {}, [], { contract: CHAT_MESSAGE_CONTRACT }),
|
|
42
43
|
operation("chat.threads.update", "/api/cli/chat/threads/{thread_id}", "PATCH", "chat threads update", "oxygen_chat_thread_update", "workspace", "Staff-only. Dev only: update native support status, assignment, urgency, fixing-agent progress or fix evidence; release status is verified separately.", {}, [], { contract: CHAT_THREAD_PATCH_CONTRACT }),
|
|
43
44
|
operation("chat.threads.read", "/api/cli/chat/threads/{thread_id}/read", "POST", "chat threads read", "oxygen_chat_thread_read", "workspace", "Dev only: mark a native support thread read through the viewed cursor; omit cursor to mark all currently visible messages.", { cursor: text() }, [], { bodyRequired: true }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// GENERATED by apps/web/scripts/generate-copilot-journeys.ts from the recipe
|
|
2
|
-
// catalog (`journey:` blocks in apps/web/src/lib/
|
|
2
|
+
// catalog (`journey:` blocks in apps/web/src/lib/agent-skill-plays.ts).
|
|
3
3
|
// Do not edit by hand — run `npm run skills:metadata -w @oxygen/web`.
|
|
4
4
|
export const COPILOT_JOURNEYS_GENERATED = [
|
|
5
5
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const COPILOT_PLAYBOOKS_GENERATED: readonly [{
|
|
2
2
|
readonly slug: "icp-to-account-map";
|
|
3
3
|
readonly title: "TAM sourcing";
|
|
4
|
-
readonly prompt: "Find about 25 companies that
|
|
4
|
+
readonly prompt: "Find about 25 companies that fit who we sell to, grade how well each one fits and why, and show me which kinds fit best. If I share a list, portfolio page or event list, grade that instead.";
|
|
5
5
|
}, {
|
|
6
6
|
readonly slug: "founder-posting-system";
|
|
7
7
|
readonly title: "LinkedIn content strategy";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// GENERATED by apps/web/scripts/generate-copilot-playbooks.ts from the play
|
|
2
2
|
// catalog (featured plays: `featured_order`, `label`, `prompt` in
|
|
3
|
-
// apps/web/src/lib/
|
|
3
|
+
// apps/web/src/lib/agent-skill-plays.ts).
|
|
4
4
|
// Do not edit by hand — run `npm run skills:metadata -w @oxygen/web`.
|
|
5
5
|
export const COPILOT_PLAYBOOKS_GENERATED = [
|
|
6
6
|
{
|
|
7
7
|
slug: "icp-to-account-map",
|
|
8
8
|
title: "TAM sourcing",
|
|
9
|
-
prompt: "Find about 25 companies that
|
|
9
|
+
prompt: "Find about 25 companies that fit who we sell to, grade how well each one fits and why, and show me which kinds fit best. If I share a list, portfolio page or event list, grade that instead.",
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
slug: "founder-posting-system",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which inbound email conversations of a mailbox the paid inbox classifier
|
|
3
|
+
* triages (Tim, 2026-10-21, T-316). `replies`: Sequence threads and threads the
|
|
4
|
+
* workspace wrote in; every other conversation (newsletters, notifications,
|
|
5
|
+
* cold pitches) stays in Unibox unclassified and costs nothing. `all`: every
|
|
6
|
+
* inbound conversation.
|
|
7
|
+
*
|
|
8
|
+
* Without a choice, a Google mailbox the customer added themselves defaults to
|
|
9
|
+
* `replies`: most of its mail is not outreach, and its poll reads Sent mail, so
|
|
10
|
+
* a thread they wrote in is recognisable. Every other mailbox defaults to `all`:
|
|
11
|
+
* Microsoft's poll reads only the Inbox, and a reply to a campaign sent from
|
|
12
|
+
* another platform has no outbound message here to be recognised by.
|
|
13
|
+
* tenant-db's `emailClassificationInScopeSql` is the SQL twin of this default.
|
|
14
|
+
*/
|
|
15
|
+
export declare const EMAIL_CLASSIFICATION_SCOPES: readonly ["replies", "all"];
|
|
16
|
+
export type EmailClassificationScope = (typeof EMAIL_CLASSIFICATION_SCOPES)[number];
|
|
17
|
+
/** Mailbox metadata key holding an explicit choice. */
|
|
18
|
+
export declare const EMAIL_CLASSIFICATION_SCOPE_KEY = "classification_scope";
|
|
19
|
+
/** The `mailbox_origin` stamp of a mailbox the customer added themselves. */
|
|
20
|
+
export declare const SELF_ADDED_MAILBOX_ORIGIN = "manual";
|
|
21
|
+
/** The provider whose self-added mailboxes default to `replies`. */
|
|
22
|
+
export declare const SELF_ADDED_REPLIES_PROVIDER = "google";
|
|
23
|
+
export type EmailClassificationScopeView = {
|
|
24
|
+
scope: EmailClassificationScope;
|
|
25
|
+
/** `setting` when someone chose it; `default` when it follows the mailbox kind. */
|
|
26
|
+
source: "setting" | "default";
|
|
27
|
+
};
|
|
28
|
+
export declare function isEmailClassificationScope(value: unknown): value is EmailClassificationScope;
|
|
29
|
+
/** True for a connected full-mail inbox: its analysis is included in the monthly connection, so no scope applies. */
|
|
30
|
+
export declare function isMailboxSyncMetadata(metadata: Record<string, unknown> | null | undefined): boolean;
|
|
31
|
+
/** The scope a mailbox's inbound mail is classified under, or null for a connected full-mail inbox. */
|
|
32
|
+
export declare function resolveEmailClassificationScope(mailbox: {
|
|
33
|
+
provider: string;
|
|
34
|
+
metadata: Record<string, unknown> | null | undefined;
|
|
35
|
+
}): EmailClassificationScopeView | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which inbound email conversations of a mailbox the paid inbox classifier
|
|
3
|
+
* triages (Tim, 2026-10-21, T-316). `replies`: Sequence threads and threads the
|
|
4
|
+
* workspace wrote in; every other conversation (newsletters, notifications,
|
|
5
|
+
* cold pitches) stays in Unibox unclassified and costs nothing. `all`: every
|
|
6
|
+
* inbound conversation.
|
|
7
|
+
*
|
|
8
|
+
* Without a choice, a Google mailbox the customer added themselves defaults to
|
|
9
|
+
* `replies`: most of its mail is not outreach, and its poll reads Sent mail, so
|
|
10
|
+
* a thread they wrote in is recognisable. Every other mailbox defaults to `all`:
|
|
11
|
+
* Microsoft's poll reads only the Inbox, and a reply to a campaign sent from
|
|
12
|
+
* another platform has no outbound message here to be recognised by.
|
|
13
|
+
* tenant-db's `emailClassificationInScopeSql` is the SQL twin of this default.
|
|
14
|
+
*/
|
|
15
|
+
export const EMAIL_CLASSIFICATION_SCOPES = ["replies", "all"];
|
|
16
|
+
/** Mailbox metadata key holding an explicit choice. */
|
|
17
|
+
export const EMAIL_CLASSIFICATION_SCOPE_KEY = "classification_scope";
|
|
18
|
+
/** The `mailbox_origin` stamp of a mailbox the customer added themselves. */
|
|
19
|
+
export const SELF_ADDED_MAILBOX_ORIGIN = "manual";
|
|
20
|
+
/** The provider whose self-added mailboxes default to `replies`. */
|
|
21
|
+
export const SELF_ADDED_REPLIES_PROVIDER = "google";
|
|
22
|
+
export function isEmailClassificationScope(value) {
|
|
23
|
+
return typeof value === "string" && EMAIL_CLASSIFICATION_SCOPES.includes(value);
|
|
24
|
+
}
|
|
25
|
+
/** True for a connected full-mail inbox: its analysis is included in the monthly connection, so no scope applies. */
|
|
26
|
+
export function isMailboxSyncMetadata(metadata) {
|
|
27
|
+
return metadata != null && Object.hasOwn(metadata, "mailbox_sync");
|
|
28
|
+
}
|
|
29
|
+
/** The scope a mailbox's inbound mail is classified under, or null for a connected full-mail inbox. */
|
|
30
|
+
export function resolveEmailClassificationScope(mailbox) {
|
|
31
|
+
const { metadata } = mailbox;
|
|
32
|
+
if (isMailboxSyncMetadata(metadata))
|
|
33
|
+
return null;
|
|
34
|
+
const stored = metadata?.[EMAIL_CLASSIFICATION_SCOPE_KEY];
|
|
35
|
+
if (isEmailClassificationScope(stored))
|
|
36
|
+
return { scope: stored, source: "setting" };
|
|
37
|
+
const selfAddedGoogle = metadata?.mailbox_origin === SELF_ADDED_MAILBOX_ORIGIN
|
|
38
|
+
&& mailbox.provider === SELF_ADDED_REPLIES_PROVIDER;
|
|
39
|
+
return { scope: selfAddedGoogle ? "replies" : "all", source: "default" };
|
|
40
|
+
}
|
|
@@ -57,6 +57,7 @@ export type ChatMessage = {
|
|
|
57
57
|
id: string | null;
|
|
58
58
|
name: string;
|
|
59
59
|
kind: "customer" | "staff" | "agent" | "system";
|
|
60
|
+
image_url?: string | null;
|
|
60
61
|
};
|
|
61
62
|
visibility: "public" | "internal";
|
|
62
63
|
source: "web" | "cli" | "mcp";
|
|
@@ -78,6 +79,7 @@ export type ChatChannel = {
|
|
|
78
79
|
id: string;
|
|
79
80
|
organization_id: string;
|
|
80
81
|
name: string;
|
|
82
|
+
icon_url?: string | null;
|
|
81
83
|
updated_at: string;
|
|
82
84
|
unread_count: number;
|
|
83
85
|
open_count: number;
|
|
@@ -89,6 +91,7 @@ export type ChatMember = {
|
|
|
89
91
|
id: string;
|
|
90
92
|
name: string;
|
|
91
93
|
email: string | null;
|
|
94
|
+
image_url?: string | null;
|
|
92
95
|
};
|
|
93
96
|
export type ChatChannelView = {
|
|
94
97
|
channel: ChatChannel;
|
|
@@ -96,6 +99,10 @@ export type ChatChannelView = {
|
|
|
96
99
|
members: ChatMember[];
|
|
97
100
|
cursor: string;
|
|
98
101
|
};
|
|
102
|
+
export type ChatThreadsView = Omit<ChatChannelView, "channel"> & {
|
|
103
|
+
initialized: boolean;
|
|
104
|
+
channel: ChatChannel | null;
|
|
105
|
+
};
|
|
99
106
|
export type ChatThreadView = {
|
|
100
107
|
thread: ChatThread;
|
|
101
108
|
messages: ChatMessage[];
|
|
@@ -122,3 +129,5 @@ export type ChatThreadPatch = {
|
|
|
122
129
|
required_roles?: string[];
|
|
123
130
|
};
|
|
124
131
|
};
|
|
132
|
+
/** Display-only URLs; never fetch these server-side or expose credential-bearing URLs. */
|
|
133
|
+
export declare function nativeChatImageUrl(value: unknown): string | null;
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** Display-only URLs; never fetch these server-side or expose credential-bearing URLs. */
|
|
2
|
+
export function nativeChatImageUrl(value) {
|
|
3
|
+
if (typeof value !== "string" || value.length > 2_048)
|
|
4
|
+
return null;
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(value.trim());
|
|
7
|
+
return ["https:", "http:"].includes(url.protocol) && !url.username && !url.password ? url.href : null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -21,8 +21,15 @@ export declare const ONBOARDING_RESEARCH_MODEL: {
|
|
|
21
21
|
readonly level: "medium";
|
|
22
22
|
readonly reasoningEffort: "low";
|
|
23
23
|
};
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Provider credits OXYGEN pays for one workspace's onboarding research, over its
|
|
26
|
+
* lifetime, restarts included. Raised from 300 (Philipp, 2026-10-21: "we can
|
|
27
|
+
* increase the limit to make the research really good"): a full run took up to
|
|
28
|
+
* 121 credits, and the Oxygen Demo Workspace ran out on its eighth start.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ONBOARDING_RESEARCH_MAX_CREDITS = 2000;
|
|
31
|
+
/** What one onboarding run (one funded turn) may spend of it, so a run has room to go deep and a restart has room too. */
|
|
32
|
+
export declare const ONBOARDING_RESEARCH_RUN_MAX_CREDITS = 500;
|
|
26
33
|
/** Ceiling on any one research call; a call that names a higher one is lowered to it. */
|
|
27
34
|
export declare const ONBOARDING_RESEARCH_MAX_CREDITS_PER_CALL = 15;
|
|
28
35
|
/** The onboarding turn's own and its sub-agents' inference, credited back once the turn settles. */
|
|
@@ -18,8 +18,15 @@ export const ONBOARDING_RESEARCH_SUBAGENT = "onboarding_researcher";
|
|
|
18
18
|
* Reliable calls first, then speed: one place to change once measured.
|
|
19
19
|
*/
|
|
20
20
|
export const ONBOARDING_RESEARCH_MODEL = { level: "medium", reasoningEffort: "low" };
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Provider credits OXYGEN pays for one workspace's onboarding research, over its
|
|
23
|
+
* lifetime, restarts included. Raised from 300 (Philipp, 2026-10-21: "we can
|
|
24
|
+
* increase the limit to make the research really good"): a full run took up to
|
|
25
|
+
* 121 credits, and the Oxygen Demo Workspace ran out on its eighth start.
|
|
26
|
+
*/
|
|
27
|
+
export const ONBOARDING_RESEARCH_MAX_CREDITS = 2_000;
|
|
28
|
+
/** What one onboarding run (one funded turn) may spend of it, so a run has room to go deep and a restart has room too. */
|
|
29
|
+
export const ONBOARDING_RESEARCH_RUN_MAX_CREDITS = 500;
|
|
23
30
|
/** Ceiling on any one research call; a call that names a higher one is lowered to it. */
|
|
24
31
|
export const ONBOARDING_RESEARCH_MAX_CREDITS_PER_CALL = 15;
|
|
25
32
|
/** The onboarding turn's own and its sub-agents' inference, credited back once the turn settles. */
|
|
@@ -14,6 +14,8 @@ export type RecipePrimitive = typeof RECIPE_PRIMITIVES[number];
|
|
|
14
14
|
export declare const RECIPE_PREREQUISITE_KINDS: readonly ["sender", "mailbox", "domain", "integration", "knowledge", "asset", "plan", "data"];
|
|
15
15
|
export type RecipePrerequisiteKind = typeof RECIPE_PREREQUISITE_KINDS[number];
|
|
16
16
|
export declare const RECIPE_BODY_SECTIONS: readonly ["The Play", "Before You Start", "Steps", "If It Goes Wrong"];
|
|
17
|
+
export declare const RECIPE_SKILL_PLAY_BODY_SECTIONS: readonly ["When", "Numbers", "Steps", "Kill and scale"];
|
|
18
|
+
export declare const RECIPE_BODY_SHAPES: readonly (readonly string[])[];
|
|
17
19
|
export declare const RECIPE_SLUG_PATTERN: RegExp;
|
|
18
20
|
export declare function recipeWikiSlug(recipeSlug: string): string;
|
|
19
21
|
export declare const RECIPE_KIT_MAX_STAGES = 6;
|
|
@@ -77,6 +77,19 @@ export const RECIPE_BODY_SECTIONS = [
|
|
|
77
77
|
"Steps",
|
|
78
78
|
"If It Goes Wrong",
|
|
79
79
|
];
|
|
80
|
+
// The four sections of a play written inside its feature skill (the 2026-10-21
|
|
81
|
+
// sourcing, sequencer and signals plays). The lint takes the shape a body's
|
|
82
|
+
// first heading names and holds the body to exactly that shape.
|
|
83
|
+
export const RECIPE_SKILL_PLAY_BODY_SECTIONS = [
|
|
84
|
+
"When",
|
|
85
|
+
"Numbers",
|
|
86
|
+
"Steps",
|
|
87
|
+
"Kill and scale",
|
|
88
|
+
];
|
|
89
|
+
export const RECIPE_BODY_SHAPES = [
|
|
90
|
+
RECIPE_BODY_SECTIONS,
|
|
91
|
+
RECIPE_SKILL_PLAY_BODY_SECTIONS,
|
|
92
|
+
];
|
|
80
93
|
export const RECIPE_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,119}$/;
|
|
81
94
|
// Wiki slug an install writes: slots into the starter wiki's seeded
|
|
82
95
|
// [[playbooks]] hub convention ([[playbook-outbound]] etc.).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const SENDING_DOMAIN_ROLLOUT_CODE = "sending_domain_rollout_unavailable";
|
|
2
|
+
export declare const SENDING_DOMAIN_ROLLOUT_MESSAGE = "Sending-domain setup and delivery are not available yet.";
|
|
3
|
+
export declare function sendingDomainDeliveryAvailable(): boolean;
|
|
4
|
+
export declare function assertSendingDomainDeliveryAvailable(): void;
|
|
5
|
+
/** Inspect raw intent: invalid/partial settings must never fall back to a mailbox. */
|
|
6
|
+
export declare function hasSendingDomainDeliveryIntent(settings: unknown): boolean;
|
|
7
|
+
export declare function assertSendingDomainSequenceReady(settings: unknown): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CliHttpError } from "./cli-http-error.js";
|
|
2
|
+
export const SENDING_DOMAIN_ROLLOUT_CODE = "sending_domain_rollout_unavailable";
|
|
3
|
+
export const SENDING_DOMAIN_ROLLOUT_MESSAGE = "Sending-domain setup and delivery are not available yet.";
|
|
4
|
+
// Source-bound hold: shared SES ownership and the whole consuming cohort must
|
|
5
|
+
// be qualified before a future reviewed change admits this lane. No caller,
|
|
6
|
+
// environment, account role or queue-family selector can qualify it.
|
|
7
|
+
export function sendingDomainDeliveryAvailable() { return false; }
|
|
8
|
+
export function assertSendingDomainDeliveryAvailable() {
|
|
9
|
+
if (!sendingDomainDeliveryAvailable())
|
|
10
|
+
throw new CliHttpError(503, SENDING_DOMAIN_ROLLOUT_CODE, SENDING_DOMAIN_ROLLOUT_MESSAGE, {
|
|
11
|
+
details: { retryable: false, next_step: "Keep sending-domain campaigns stopped until sending-domain delivery is available." }, exitCode: 1,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** Inspect raw intent: invalid/partial settings must never fall back to a mailbox. */
|
|
15
|
+
export function hasSendingDomainDeliveryIntent(settings) {
|
|
16
|
+
if (!settings || typeof settings !== "object")
|
|
17
|
+
return false;
|
|
18
|
+
const value = settings.email_delivery;
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
20
|
+
return false;
|
|
21
|
+
const delivery = value;
|
|
22
|
+
return delivery.mode === "sending_domain" || ["sending_domain_id", "from_address", "from_name", "reply_to_mailbox_id", "subscription_list_id"]
|
|
23
|
+
.some(key => delivery[key] !== undefined);
|
|
24
|
+
}
|
|
25
|
+
export function assertSendingDomainSequenceReady(settings) {
|
|
26
|
+
if (hasSendingDomainDeliveryIntent(settings))
|
|
27
|
+
assertSendingDomainDeliveryAvailable();
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One reader for a model tool call's `arguments` string, shared by the Copilot
|
|
3
|
+
* (packages/copilot model-output.ts) and the Agents runtime host
|
|
4
|
+
* (apps/worker agent-cycle.ts) so an Oxygent run and a Copilot turn accept and
|
|
5
|
+
* refuse exactly the same payloads.
|
|
6
|
+
*
|
|
7
|
+
* Only a plain JSON object is an argument set. A complete object followed only
|
|
8
|
+
* by surplus closing brackets is read as the object: measured on the medium tier
|
|
9
|
+
* (System 1, s1-knowledge-update-page, 2026-10-21), a capability_run with a
|
|
10
|
+
* nested page body closed with one `}` too many four times in one turn, each
|
|
11
|
+
* `Unexpected non-whitespace character after JSON`. The object before the
|
|
12
|
+
* surplus is exactly what the model wrote; any other trailing text still fails.
|
|
13
|
+
*/
|
|
14
|
+
export type ToolCallArgumentsRead = {
|
|
15
|
+
ok: true;
|
|
16
|
+
value: Record<string, unknown>;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
/** The parse error, or why a parsed value is not an argument set. */
|
|
20
|
+
error: string;
|
|
21
|
+
/** The text around the break, from the FULL payload (an echoed head may end before it). */
|
|
22
|
+
near?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function readToolCallArgumentObject(raw: string): ToolCallArgumentsRead;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const NOT_AN_OBJECT = "Tool-call arguments must be a JSON object.";
|
|
2
|
+
export function readToolCallArgumentObject(raw) {
|
|
3
|
+
let parsed;
|
|
4
|
+
let error;
|
|
5
|
+
try {
|
|
6
|
+
parsed = JSON.parse(raw);
|
|
7
|
+
}
|
|
8
|
+
catch (caught) {
|
|
9
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
10
|
+
const trimmed = withoutSurplusClosers(raw);
|
|
11
|
+
if (trimmed !== null) {
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(trimmed);
|
|
14
|
+
error = undefined;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
parsed = undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// An ARRAY and a bare string both survive JSON.parse; only a plain object is a
|
|
22
|
+
// valid argument set, so the check is on the shape rather than on the throw.
|
|
23
|
+
if (error === undefined && parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
24
|
+
return { ok: true, value: parsed };
|
|
25
|
+
}
|
|
26
|
+
const message = error ?? NOT_AN_OBJECT;
|
|
27
|
+
const position = Number(/at position (\d+)/.exec(message)?.[1]);
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: message,
|
|
31
|
+
...(Number.isFinite(position)
|
|
32
|
+
? { near: raw.slice(Math.max(0, position - 60), position + 20) }
|
|
33
|
+
: {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** The payload up to its first complete top-level object when only closers follow, else null. */
|
|
37
|
+
function withoutSurplusClosers(raw) {
|
|
38
|
+
let depth = 0;
|
|
39
|
+
let inString = false;
|
|
40
|
+
let escaped = false;
|
|
41
|
+
const start = raw.search(/\S/);
|
|
42
|
+
if (start < 0 || raw[start] !== "{")
|
|
43
|
+
return null;
|
|
44
|
+
for (let index = start; index < raw.length; index += 1) {
|
|
45
|
+
const char = raw[index];
|
|
46
|
+
if (inString) {
|
|
47
|
+
if (escaped)
|
|
48
|
+
escaped = false;
|
|
49
|
+
else if (char === "\\")
|
|
50
|
+
escaped = true;
|
|
51
|
+
else if (char === '"')
|
|
52
|
+
inString = false;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === '"')
|
|
56
|
+
inString = true;
|
|
57
|
+
else if (char === "{" || char === "[")
|
|
58
|
+
depth += 1;
|
|
59
|
+
else if (char === "}" || char === "]") {
|
|
60
|
+
depth -= 1;
|
|
61
|
+
if (depth === 0) {
|
|
62
|
+
const rest = raw.slice(index + 1);
|
|
63
|
+
return rest.trim() !== "" && /^[\s}\]]*$/.test(rest) ? raw.slice(0, index + 1) : null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const OXYGEN_BUILD_VERSION = "1.1014.
|
|
1
|
+
export declare const OXYGEN_BUILD_VERSION = "1.1014.1696";
|
|
@@ -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.1696";
|
|
@@ -49,7 +49,7 @@ export const OXYGEN_VERSION = OXYGEN_BUILD_VERSION;
|
|
|
49
49
|
// Since 2026-09-28 the release-preparation step advances this literal to the
|
|
50
50
|
// highest CLI version published on npm for at least 30 days. It never lowers
|
|
51
51
|
// the floor. The source change lands on dev before the next production release.
|
|
52
|
-
export const OXYGEN_MINIMUM_CLI_VERSION = "1.
|
|
52
|
+
export const OXYGEN_MINIMUM_CLI_VERSION = "1.1003.12";
|
|
53
53
|
// The internal GEO export first gains required pagination in this dev version.
|
|
54
54
|
// Earlier pilot clients would save a partial file without its continuation.
|
|
55
55
|
export const GEO_EXPORT_MINIMUM_CLI_VERSION = "1.1013.0";
|
|
@@ -20,6 +20,7 @@ export type StaffPlacementGrant = {
|
|
|
20
20
|
source_environment: "dev" | "prod";
|
|
21
21
|
receiver_environment: "dev" | "prod";
|
|
22
22
|
receiver_workspace_id: string;
|
|
23
|
+
allow_daily_limit_override?: true;
|
|
23
24
|
};
|
|
24
25
|
export type WorkspacePlacementProbe = {
|
|
25
26
|
id: string;
|
|
@@ -51,6 +52,8 @@ export declare function workspacePlacementId(value: unknown): value is string;
|
|
|
51
52
|
export declare function readWorkspacePlacementVariants(value: unknown): WorkspacePlacementVariant[];
|
|
52
53
|
export declare function readWorkspacePlacementMailboxIds(value: unknown): string[];
|
|
53
54
|
export declare function workspacePlacementPlanHash(plan: Omit<WorkspacePlacementPlan, "plan_hash">): string;
|
|
55
|
+
/** Exact staff approval can exempt one diagnostic per sender from daily quotas. */
|
|
56
|
+
export declare function workspacePlacementDailyLimitOverride(plan: Pick<WorkspacePlacementPlan, "receiver_scope" | "staff" | "probes" | "message_count">): boolean;
|
|
54
57
|
export declare function buildWorkspacePlacementPlan(input: {
|
|
55
58
|
requestId: string;
|
|
56
59
|
organizationId: string;
|
|
@@ -48,6 +48,15 @@ function canonicalPlacementValue(value) {
|
|
|
48
48
|
export function workspacePlacementPlanHash(plan) {
|
|
49
49
|
return createHash("sha256").update(JSON.stringify(plan.receiver_scope === "staff" ? canonicalPlacementValue(plan) : plan)).digest("hex");
|
|
50
50
|
}
|
|
51
|
+
/** Exact staff approval can exempt one diagnostic per sender from daily quotas. */
|
|
52
|
+
export function workspacePlacementDailyLimitOverride(plan) {
|
|
53
|
+
if (plan.receiver_scope !== "staff" || plan.staff?.allow_daily_limit_override !== true || typeof plan.staff.actor_id !== "string" || !plan.staff.actor_id.trim()
|
|
54
|
+
|| !Array.isArray(plan.probes) || plan.probes.length < 1 || plan.probes.length > 50 || plan.message_count !== plan.probes.length
|
|
55
|
+
|| plan.probes.some(p => typeof p?.sender?.address !== "string" || !workspacePlacementId(p.sender.id)))
|
|
56
|
+
return false;
|
|
57
|
+
return new Set(plan.probes.map(p => p.sender.address.toLowerCase())).size === plan.probes.length
|
|
58
|
+
&& new Set(plan.probes.map(p => `${p.sender.environment}:${p.sender.organization_id}:${p.sender.id}`)).size === plan.probes.length;
|
|
59
|
+
}
|
|
51
60
|
export function buildWorkspacePlacementPlan(input) {
|
|
52
61
|
if (!workspacePlacementId(input.requestId) || !workspacePlacementId(input.organizationId))
|
|
53
62
|
invalid("The workspace test request is invalid.");
|
|
@@ -95,6 +104,8 @@ export function buildWorkspacePlacementPlan(input) {
|
|
|
95
104
|
...(input.network ? { receiver_scope: "network" } : input.staff ? { receiver_scope: "staff", staff: input.staff } : {}), billing_mode: "internal", credits: 0, allow_paused_probe: input.allowPausedProbe,
|
|
96
105
|
...(input.renderedAt ? { rendered_at: input.renderedAt } : {}), ...(input.rendering ? { rendering: input.rendering } : {}),
|
|
97
106
|
probes, excluded_self_sends: excluded, message_count: probes.length };
|
|
107
|
+
if (input.staff?.allow_daily_limit_override && !workspacePlacementDailyLimitOverride(plan))
|
|
108
|
+
invalid("Daily-limit overrides allow only one diagnostic email per sender in the approved staff test.");
|
|
98
109
|
return { ...plan, plan_hash: workspacePlacementPlanHash(plan) };
|
|
99
110
|
}
|
|
100
111
|
export function googleWorkspacePlacement(labels, messageId, observedAt) {
|