@oxygen-agent/cli 1.1014.1465 → 1.1014.1598
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-families/program.js +2 -0
- package/dist/command-manifest.js +12 -2
- package/dist/commands/budget.js +2 -2
- package/dist/commands/capabilities.js +3 -2
- package/dist/commands/columns.js +1 -1
- package/dist/commands/companies.js +3 -0
- package/dist/commands/domains.js +122 -5
- package/dist/commands/feedback.js +1 -1
- package/dist/commands/feeds.js +34 -17
- package/dist/commands/mailboxes.js +19 -9
- package/dist/commands/publishing.js +1 -1
- package/dist/commands/senders.js +2 -2
- package/dist/commands/sequences.js +75 -12
- package/dist/commands/subscriptions.d.ts +10 -0
- package/dist/commands/subscriptions.js +203 -0
- package/dist/commands/tags.js +4 -4
- package/dist/customer-operation-commands.js +17 -0
- package/dist/mailbox-fleet-output.d.ts +3 -0
- package/dist/mailbox-fleet-output.js +80 -0
- package/dist/native-chat-host.d.ts +2 -0
- package/dist/native-chat-host.js +6 -0
- package/dist/workspace-placement-commands.js +67 -2
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +59 -11
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +2 -2
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +18 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +4 -4
- package/node_modules/@oxygen/shared/dist/contracts/native-chat.d.ts +120 -0
- package/node_modules/@oxygen/shared/dist/contracts/native-chat.js +35 -0
- package/node_modules/@oxygen/shared/dist/contracts/sender-limits.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/contracts/sender-limits.js +6 -1
- package/node_modules/@oxygen/shared/dist/contracts/sequence-list.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/contracts/sequence-list.js +2 -1
- package/node_modules/@oxygen/shared/dist/contracts/signal-search-compatibility.d.ts +78 -0
- package/node_modules/@oxygen/shared/dist/contracts/signal-search-compatibility.js +32 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +14 -0
- package/node_modules/@oxygen/shared/dist/email-delivery.d.ts +150 -0
- package/node_modules/@oxygen/shared/dist/email-delivery.js +246 -0
- package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/email-dsn.js +51 -14
- package/node_modules/@oxygen/shared/dist/email-health-summary.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/email-health-summary.js +28 -0
- package/node_modules/@oxygen/shared/dist/email-subscriptions.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/email-subscriptions.js +80 -0
- package/node_modules/@oxygen/shared/dist/email-unsubscribe-token.d.ts +28 -0
- package/node_modules/@oxygen/shared/dist/email-unsubscribe-token.js +47 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +4 -16
- package/node_modules/@oxygen/shared/dist/error-redaction.js +8 -17
- package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +15 -1
- package/node_modules/@oxygen/shared/dist/guided-activation.js +3 -1
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/mailbox-standby-rollout.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/mailbox-standby-rollout.js +9 -0
- package/node_modules/@oxygen/shared/dist/mailbox-standby.d.ts +43 -0
- package/node_modules/@oxygen/shared/dist/mailbox-standby.js +52 -0
- package/node_modules/@oxygen/shared/dist/native-chat-discovery.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/native-chat-discovery.js +27 -0
- package/node_modules/@oxygen/shared/dist/native-chat.d.ts +124 -0
- package/node_modules/@oxygen/shared/dist/native-chat.js +1 -0
- package/node_modules/@oxygen/shared/dist/onboarding-plays.d.ts +14 -3
- package/node_modules/@oxygen/shared/dist/onboarding-plays.js +26 -13
- package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/onboarding-research.js +8 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +6 -0
- package/node_modules/@oxygen/shared/dist/select-options.js +1 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +6 -5
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +44 -2
- package/node_modules/@oxygen/shared/dist/tags.d.ts +7 -3
- package/node_modules/@oxygen/shared/dist/tags.js +31 -6
- 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/workspace-placement-bulk.d.ts +80 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement-bulk.js +68 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement-view.d.ts +2 -1
- package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +11 -1
- package/node_modules/@oxygen/shared/dist/workspace-placement.js +15 -4
- package/node_modules/@oxygen/shared/dist/write-rollout.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/write-rollout.js +59 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +176 -2
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +50 -1
- package/node_modules/@oxygen/workflows/dist/index.js +108 -3
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CapabilityRouteMatch } from "./capability-discovery.js";
|
|
2
|
+
export declare function nativeChatEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
3
|
+
export declare function isNativeChatTool(name: string): boolean;
|
|
4
|
+
/** The compiled support card includes the dev surface; hosted discovery projects it away when closed. */
|
|
5
|
+
export declare function withNativeChatDiscovery<T extends CapabilityRouteMatch>(route: T, enabled: boolean): T;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveDeployEnv } from "./deploy-env.js";
|
|
2
|
+
export function nativeChatEnabled(env = process.env) {
|
|
3
|
+
if (!/^(1|true|on)$/i.test(env.OXYGEN_NATIVE_CHAT_ENABLED?.trim() ?? ""))
|
|
4
|
+
return false;
|
|
5
|
+
const tier = resolveDeployEnv(env).tier;
|
|
6
|
+
return tier === "dev" || tier === "local";
|
|
7
|
+
}
|
|
8
|
+
export function isNativeChatTool(name) {
|
|
9
|
+
return name.startsWith("oxygen_chat_");
|
|
10
|
+
}
|
|
11
|
+
/** The compiled support card includes the dev surface; hosted discovery projects it away when closed. */
|
|
12
|
+
export function withNativeChatDiscovery(route, enabled) {
|
|
13
|
+
if (enabled || route.card.id !== "support-and-feedback")
|
|
14
|
+
return route;
|
|
15
|
+
return {
|
|
16
|
+
...route,
|
|
17
|
+
card: {
|
|
18
|
+
...route.card,
|
|
19
|
+
gatewayTools: route.card.gatewayTools.filter(name => !isNativeChatTool(name)),
|
|
20
|
+
gatewayCommands: route.card.gatewayCommands.filter(name => !name.startsWith("chat ")),
|
|
21
|
+
endpointSections: route.card.endpointSections.filter(name => name !== "chat"),
|
|
22
|
+
intentTerms: route.card.intentTerms.filter(name => !["support chat", "support thread", "chat attachment"].includes(name)),
|
|
23
|
+
},
|
|
24
|
+
recommendedTools: route.recommendedTools.filter(name => !isNativeChatTool(name)),
|
|
25
|
+
recommendedCommands: route.recommendedCommands.filter(name => !name.startsWith("chat ")),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/** Native support chat projections shared by web, CLI and MCP. */
|
|
2
|
+
export type ChatStatus = "todo" | "snoozed" | "done";
|
|
3
|
+
export type ChatPriority = "urgent" | "high" | "normal" | "low";
|
|
4
|
+
export type ChatAgentStage = "investigating" | "fixing" | "testing" | "awaiting_release" | "needs_human" | "completed";
|
|
5
|
+
export type ChatAttachment = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
size_bytes: number;
|
|
9
|
+
content_type: string;
|
|
10
|
+
download_url: string;
|
|
11
|
+
};
|
|
12
|
+
export type ChatAgent = {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
stage: ChatAgentStage;
|
|
16
|
+
last_activity_at: string;
|
|
17
|
+
summary: string | null;
|
|
18
|
+
session_url: string | null;
|
|
19
|
+
};
|
|
20
|
+
export type ChatRelease = {
|
|
21
|
+
status: "none" | "dev" | "prod";
|
|
22
|
+
fix_sha: string | null;
|
|
23
|
+
pr_url: string | null;
|
|
24
|
+
required_roles: string[];
|
|
25
|
+
verified_at: string | null;
|
|
26
|
+
stale: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type ChatTriage = {
|
|
29
|
+
source: "review" | "system_one" | "staff";
|
|
30
|
+
confidence: number | null;
|
|
31
|
+
reason: string | null;
|
|
32
|
+
message_id: string | null;
|
|
33
|
+
};
|
|
34
|
+
export type ChatThread = {
|
|
35
|
+
id: string;
|
|
36
|
+
channel_id: string;
|
|
37
|
+
subject: string;
|
|
38
|
+
status: ChatStatus;
|
|
39
|
+
waiting_for_customer: boolean;
|
|
40
|
+
snoozed_until: string | null;
|
|
41
|
+
assignee_email: string | null;
|
|
42
|
+
priority: ChatPriority;
|
|
43
|
+
triage: ChatTriage;
|
|
44
|
+
agent: ChatAgent | null;
|
|
45
|
+
release: ChatRelease;
|
|
46
|
+
context: Record<string, unknown>;
|
|
47
|
+
created_at: string;
|
|
48
|
+
updated_at: string;
|
|
49
|
+
last_customer_at: string | null;
|
|
50
|
+
unread_count: number;
|
|
51
|
+
};
|
|
52
|
+
export type ChatMessage = {
|
|
53
|
+
id: string;
|
|
54
|
+
thread_id: string;
|
|
55
|
+
body: string;
|
|
56
|
+
author: {
|
|
57
|
+
id: string | null;
|
|
58
|
+
name: string;
|
|
59
|
+
kind: "customer" | "staff" | "agent" | "system";
|
|
60
|
+
};
|
|
61
|
+
visibility: "public" | "internal";
|
|
62
|
+
source: "web" | "cli" | "mcp";
|
|
63
|
+
attachments: ChatAttachment[];
|
|
64
|
+
mentions: string[];
|
|
65
|
+
created_at: string;
|
|
66
|
+
};
|
|
67
|
+
export type ChatSignificance = {
|
|
68
|
+
plan: string | null;
|
|
69
|
+
mrr_cents: number | null;
|
|
70
|
+
net_30d_cents: number | null;
|
|
71
|
+
net_lifetime_cents: number | null;
|
|
72
|
+
currency: string | null;
|
|
73
|
+
billing_owner_name: string | null;
|
|
74
|
+
shared_billing: boolean;
|
|
75
|
+
captured_at: string | null;
|
|
76
|
+
};
|
|
77
|
+
export type ChatChannel = {
|
|
78
|
+
id: string;
|
|
79
|
+
organization_id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
updated_at: string;
|
|
82
|
+
unread_count: number;
|
|
83
|
+
open_count: number;
|
|
84
|
+
priority: ChatPriority;
|
|
85
|
+
oldest_unanswered_at: string | null;
|
|
86
|
+
significance?: ChatSignificance;
|
|
87
|
+
};
|
|
88
|
+
export type ChatMember = {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
email: string | null;
|
|
92
|
+
};
|
|
93
|
+
export type ChatChannelView = {
|
|
94
|
+
channel: ChatChannel;
|
|
95
|
+
threads: ChatThread[];
|
|
96
|
+
members: ChatMember[];
|
|
97
|
+
cursor: string;
|
|
98
|
+
};
|
|
99
|
+
export type ChatThreadView = {
|
|
100
|
+
thread: ChatThread;
|
|
101
|
+
messages: ChatMessage[];
|
|
102
|
+
members: ChatMember[];
|
|
103
|
+
cursor: string;
|
|
104
|
+
has_more: boolean;
|
|
105
|
+
};
|
|
106
|
+
export type ChatThreadPatch = {
|
|
107
|
+
status?: ChatStatus;
|
|
108
|
+
waiting_for_customer?: boolean;
|
|
109
|
+
snoozed_until?: string | null;
|
|
110
|
+
assignee_email?: string | null;
|
|
111
|
+
priority?: ChatPriority;
|
|
112
|
+
agent?: {
|
|
113
|
+
id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
stage: ChatAgentStage;
|
|
116
|
+
summary?: string | null;
|
|
117
|
+
session_url?: string | null;
|
|
118
|
+
} | null;
|
|
119
|
+
release?: {
|
|
120
|
+
fix_sha?: string | null;
|
|
121
|
+
pr_url?: string | null;
|
|
122
|
+
required_roles?: string[];
|
|
123
|
+
};
|
|
124
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,12 +3,15 @@ export declare const ONBOARDING_PLAY_LIMITS: {
|
|
|
3
3
|
readonly minPlays: 2;
|
|
4
4
|
readonly maxPlays: 4;
|
|
5
5
|
readonly titleChars: 80;
|
|
6
|
+
readonly motionChars: 48;
|
|
6
7
|
readonly lineChars: 240;
|
|
7
8
|
readonly slugChars: 120;
|
|
8
9
|
};
|
|
9
10
|
export type OnboardingPlay = {
|
|
10
11
|
id: string;
|
|
11
12
|
title: string;
|
|
13
|
+
/** The motion in a few words, channel and kind, e.g. "Signal-led email + LinkedIn". Null on proposals made before it existed. */
|
|
14
|
+
motion: string | null;
|
|
12
15
|
/** Who it targets: segment and buyer. */
|
|
13
16
|
audience: string;
|
|
14
17
|
/** The signal or trigger it acts on. */
|
|
@@ -44,12 +47,19 @@ export declare function normalizeOnboardingPlayProposal(args: Record<string, unk
|
|
|
44
47
|
export declare function renderOnboardingPlayProposalText(proposal: OnboardingPlayProposal): string;
|
|
45
48
|
/** Read a stored proposal back, refusing anything malformed. */
|
|
46
49
|
export declare function readOnboardingPlayProposal(value: unknown): OnboardingPlayProposal | null;
|
|
50
|
+
/** How a chosen play's message starts: the turn that answers a proposal. */
|
|
51
|
+
export declare const ONBOARDING_PLAY_MESSAGE_PREFIX = "Run the play \"";
|
|
52
|
+
/** The title of a play the person wrote themselves, in place of the proposed ones. */
|
|
53
|
+
export declare const ONBOARDING_OWN_PLAY_TITLE = "My own play";
|
|
54
|
+
/** The message a play the person wrote sends: like a chosen one, so it ends onboarding the same way. */
|
|
55
|
+
export declare function onboardingOwnPlayMessage(description: string): string;
|
|
47
56
|
/** The message a chosen play sends, as the person's own. */
|
|
48
57
|
export declare function onboardingPlayMessage(play: Pick<OnboardingPlay, "title" | "firstStep" | "page">): string;
|
|
49
58
|
type ProjectionTurn = {
|
|
50
59
|
id: string;
|
|
51
60
|
status: string;
|
|
52
61
|
turnIndex: number;
|
|
62
|
+
userMessage?: string;
|
|
53
63
|
};
|
|
54
64
|
type ProjectionEvent = {
|
|
55
65
|
turnId: string | null;
|
|
@@ -57,9 +67,10 @@ type ProjectionEvent = {
|
|
|
57
67
|
payload: Record<string, unknown>;
|
|
58
68
|
};
|
|
59
69
|
/**
|
|
60
|
-
* The plays the chat is waiting on, or null
|
|
61
|
-
* turn
|
|
62
|
-
*
|
|
70
|
+
* The plays the chat is waiting on, or null: the newest proposal of a completed
|
|
71
|
+
* turn, until a play is chosen from it, which sends the play's message. Unlike a
|
|
72
|
+
* question, a message about something else does not end the wait (Philipp,
|
|
73
|
+
* 2026-10-20: a "cool stuff" typed while the report streamed took the card away).
|
|
63
74
|
*/
|
|
64
75
|
export declare function projectPendingOnboardingPlayProposal(input: {
|
|
65
76
|
turns: readonly ProjectionTurn[];
|
|
@@ -10,6 +10,7 @@ export const ONBOARDING_PLAY_LIMITS = {
|
|
|
10
10
|
minPlays: 2,
|
|
11
11
|
maxPlays: 4,
|
|
12
12
|
titleChars: 80,
|
|
13
|
+
motionChars: 48,
|
|
13
14
|
lineChars: 240,
|
|
14
15
|
slugChars: 120,
|
|
15
16
|
};
|
|
@@ -28,14 +29,15 @@ export const ONBOARDING_PROPOSE_PLAYS_INPUT_SCHEMA = {
|
|
|
28
29
|
additionalProperties: false,
|
|
29
30
|
properties: {
|
|
30
31
|
title: { type: "string", minLength: 1, maxLength: ONBOARDING_PLAY_LIMITS.titleChars, description: "Short name, e.g. \"New VP Sales at Series A SaaS\"." },
|
|
32
|
+
motion: { type: "string", minLength: 1, maxLength: ONBOARDING_PLAY_LIMITS.motionChars, description: "The motion in two to five words: the channel and the kind of outreach, e.g. \"Signal-led email + LinkedIn\", \"Cold email to a sourced list\", \"LinkedIn to post engagers\", \"Account-based multichannel\"." },
|
|
31
33
|
audience: line("Who: the segment and the buyer persona."),
|
|
32
34
|
trigger: line("The signal or event that makes now the moment, if any."),
|
|
33
35
|
angle: line("What the outreach offers them, in one line."),
|
|
34
36
|
why: line("Why this fits this company, from the research."),
|
|
35
|
-
first_step: line("What running it does first, e.g. \"
|
|
37
|
+
first_step: line("What running it does first, at real volume, e.g. \"Source 300 matching companies and their VP Sales into a table\"; never a handful."),
|
|
36
38
|
page: { type: "string", maxLength: ONBOARDING_PLAY_LIMITS.slugChars, description: "The play's Knowledge page slug, if you wrote one." },
|
|
37
39
|
},
|
|
38
|
-
required: ["title", "audience", "angle", "first_step"],
|
|
40
|
+
required: ["title", "motion", "audience", "angle", "first_step"],
|
|
39
41
|
},
|
|
40
42
|
},
|
|
41
43
|
},
|
|
@@ -56,10 +58,11 @@ export function normalizeOnboardingPlayProposal(args, mintId) {
|
|
|
56
58
|
const firstStep = clean(record.first_step ?? record.firstStep, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
57
59
|
if (!title || !audience || !angle || !firstStep)
|
|
58
60
|
continue;
|
|
61
|
+
const motion = clean(record.motion, ONBOARDING_PLAY_LIMITS.motionChars);
|
|
59
62
|
const trigger = clean(record.trigger, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
60
63
|
const why = clean(record.why, ONBOARDING_PLAY_LIMITS.lineChars);
|
|
61
64
|
const page = clean(record.page, ONBOARDING_PLAY_LIMITS.slugChars);
|
|
62
|
-
plays.push({ id: typeof record.id === "string" && record.id ? record.id : mintId(), title, audience, trigger: trigger || null,
|
|
65
|
+
plays.push({ id: typeof record.id === "string" && record.id ? record.id : mintId(), title, motion: motion || null, audience, trigger: trigger || null,
|
|
63
66
|
angle, why: why || null, firstStep, page: /^[a-z0-9][a-z0-9-]*$/.test(page) ? page : null });
|
|
64
67
|
}
|
|
65
68
|
if (plays.length < ONBOARDING_PLAY_LIMITS.minPlays || plays.length > ONBOARDING_PLAY_LIMITS.maxPlays) {
|
|
@@ -71,7 +74,7 @@ export function normalizeOnboardingPlayProposal(args, mintId) {
|
|
|
71
74
|
export function renderOnboardingPlayProposalText(proposal) {
|
|
72
75
|
return [
|
|
73
76
|
"Plays I'd run first:",
|
|
74
|
-
...proposal.plays.map((play, index) => `${index + 1}. **${play.title}
|
|
77
|
+
...proposal.plays.map((play, index) => `${index + 1}. **${play.title}**${play.motion ? ` (${play.motion})` : ""}: ${play.audience}. ${play.angle}`),
|
|
75
78
|
"Pick one to start it, or tell me what to change.",
|
|
76
79
|
].join("\n");
|
|
77
80
|
}
|
|
@@ -85,22 +88,28 @@ export function readOnboardingPlayProposal(value) {
|
|
|
85
88
|
const normalized = normalizeOnboardingPlayProposal(record, () => "");
|
|
86
89
|
return normalized.ok && normalized.proposal.plays.every((play) => play.id) ? normalized.proposal : null;
|
|
87
90
|
}
|
|
91
|
+
/** How a chosen play's message starts: the turn that answers a proposal. */
|
|
92
|
+
export const ONBOARDING_PLAY_MESSAGE_PREFIX = "Run the play \"";
|
|
93
|
+
/** The title of a play the person wrote themselves, in place of the proposed ones. */
|
|
94
|
+
export const ONBOARDING_OWN_PLAY_TITLE = "My own play";
|
|
95
|
+
/** The message a play the person wrote sends: like a chosen one, so it ends onboarding the same way. */
|
|
96
|
+
export function onboardingOwnPlayMessage(description) {
|
|
97
|
+
return `${ONBOARDING_PLAY_MESSAGE_PREFIX}${ONBOARDING_OWN_PLAY_TITLE}": ${description.trim()}`;
|
|
98
|
+
}
|
|
88
99
|
/** The message a chosen play sends, as the person's own. */
|
|
89
100
|
export function onboardingPlayMessage(play) {
|
|
90
|
-
return
|
|
101
|
+
return `${ONBOARDING_PLAY_MESSAGE_PREFIX}${play.title}". Start with: ${play.firstStep}${play.page ? ` (see [[${play.page}]])` : ""}`;
|
|
91
102
|
}
|
|
92
103
|
/**
|
|
93
|
-
* The plays the chat is waiting on, or null
|
|
94
|
-
* turn
|
|
95
|
-
*
|
|
104
|
+
* The plays the chat is waiting on, or null: the newest proposal of a completed
|
|
105
|
+
* turn, until a play is chosen from it, which sends the play's message. Unlike a
|
|
106
|
+
* question, a message about something else does not end the wait (Philipp,
|
|
107
|
+
* 2026-10-20: a "cool stuff" typed while the report streamed took the card away).
|
|
96
108
|
*/
|
|
97
109
|
export function projectPendingOnboardingPlayProposal(input) {
|
|
98
|
-
const newest = input.turns.reduce((latest, turn) => (latest === null || turn.turnIndex > latest.turnIndex ? turn : latest), null);
|
|
99
|
-
if (!newest || newest.status !== "completed")
|
|
100
|
-
return null;
|
|
101
110
|
for (let index = input.events.length - 1; index >= 0; index -= 1) {
|
|
102
111
|
const event = input.events[index];
|
|
103
|
-
if (!event
|
|
112
|
+
if (!event?.turnId || event.kind !== "tool_call_finished")
|
|
104
113
|
continue;
|
|
105
114
|
if (event.payload.tool !== ONBOARDING_PROPOSE_PLAYS_TOOL_NAME)
|
|
106
115
|
continue;
|
|
@@ -108,7 +117,11 @@ export function projectPendingOnboardingPlayProposal(input) {
|
|
|
108
117
|
if (!result || typeof result !== "object" || result.ok === false)
|
|
109
118
|
continue;
|
|
110
119
|
const proposal = readOnboardingPlayProposal(result.proposal);
|
|
111
|
-
|
|
120
|
+
const turn = input.turns.find((entry) => entry.id === event.turnId);
|
|
121
|
+
if (!proposal || !turn || turn.status !== "completed")
|
|
122
|
+
return null;
|
|
123
|
+
const chosen = input.turns.some((later) => later.turnIndex > turn.turnIndex && later.userMessage?.startsWith(ONBOARDING_PLAY_MESSAGE_PREFIX));
|
|
124
|
+
return chosen ? null : { ...proposal, turnId: turn.id };
|
|
112
125
|
}
|
|
113
126
|
return null;
|
|
114
127
|
}
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
export declare const ONBOARDING_RESEARCH_PURPOSE = "onboarding_research";
|
|
11
11
|
/** The research sub-agent the onboarding turn delegates to. */
|
|
12
12
|
export declare const ONBOARDING_RESEARCH_SUBAGENT = "onboarding_researcher";
|
|
13
|
+
/**
|
|
14
|
+
* The researchers' model: the Balanced tier's model (Claude Haiku 5.5) at low
|
|
15
|
+
* reasoning effort. Their errand default, DeepSeek V4 Flash, wrote its tool calls
|
|
16
|
+
* as text in 4 of 8 researchers on a live run (2026-10-20) and timed out on the
|
|
17
|
+
* one before, and V4.1 Flash missed tool calls in 1 of 5 probes (hosted-ai.ts).
|
|
18
|
+
* Reliable calls first, then speed: one place to change once measured.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ONBOARDING_RESEARCH_MODEL: {
|
|
21
|
+
readonly level: "medium";
|
|
22
|
+
readonly reasoningEffort: "low";
|
|
23
|
+
};
|
|
13
24
|
/** Provider credits OXYGEN pays for one workspace's onboarding research, over its lifetime. */
|
|
14
25
|
export declare const ONBOARDING_RESEARCH_MAX_CREDITS = 300;
|
|
15
26
|
/** Ceiling on any one research call; a call that names a higher one is lowered to it. */
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
export const ONBOARDING_RESEARCH_PURPOSE = "onboarding_research";
|
|
11
11
|
/** The research sub-agent the onboarding turn delegates to. */
|
|
12
12
|
export const ONBOARDING_RESEARCH_SUBAGENT = "onboarding_researcher";
|
|
13
|
+
/**
|
|
14
|
+
* The researchers' model: the Balanced tier's model (Claude Haiku 5.5) at low
|
|
15
|
+
* reasoning effort. Their errand default, DeepSeek V4 Flash, wrote its tool calls
|
|
16
|
+
* as text in 4 of 8 researchers on a live run (2026-10-20) and timed out on the
|
|
17
|
+
* one before, and V4.1 Flash missed tool calls in 1 of 5 probes (hosted-ai.ts).
|
|
18
|
+
* Reliable calls first, then speed: one place to change once measured.
|
|
19
|
+
*/
|
|
20
|
+
export const ONBOARDING_RESEARCH_MODEL = { level: "medium", reasoningEffort: "low" };
|
|
13
21
|
/** Provider credits OXYGEN pays for one workspace's onboarding research, over its lifetime. */
|
|
14
22
|
export const ONBOARDING_RESEARCH_MAX_CREDITS = 300;
|
|
15
23
|
/** Ceiling on any one research call; a call that names a higher one is lowered to it. */
|
|
@@ -34,6 +34,12 @@ export declare const PRODUCT_EVENTS: {
|
|
|
34
34
|
*/
|
|
35
35
|
readonly AGENT_SETUP_ACTION: "agent_setup_action";
|
|
36
36
|
readonly FIRST_RECORD_CREATED: "first_record_created";
|
|
37
|
+
/**
|
|
38
|
+
* Activation (Philipp, 2026-10-20): the person sent a play's prompt in their
|
|
39
|
+
* onboarding chat, verified from the chat's turn when onboarding finishes.
|
|
40
|
+
* Once per (workspace, person), gated by `organizations.metadata.onboarding_play_sent`.
|
|
41
|
+
*/
|
|
42
|
+
readonly ONBOARDING_PLAY_SENT: "onboarding_play_sent";
|
|
37
43
|
/**
|
|
38
44
|
* The prescribed first play (inbound-led outbound), walked on ANY surface.
|
|
39
45
|
*
|
|
@@ -34,6 +34,12 @@ export const PRODUCT_EVENTS = {
|
|
|
34
34
|
*/
|
|
35
35
|
AGENT_SETUP_ACTION: "agent_setup_action",
|
|
36
36
|
FIRST_RECORD_CREATED: "first_record_created",
|
|
37
|
+
/**
|
|
38
|
+
* Activation (Philipp, 2026-10-20): the person sent a play's prompt in their
|
|
39
|
+
* onboarding chat, verified from the chat's turn when onboarding finishes.
|
|
40
|
+
* Once per (workspace, person), gated by `organizations.metadata.onboarding_play_sent`.
|
|
41
|
+
*/
|
|
42
|
+
ONBOARDING_PLAY_SENT: "onboarding_play_sent",
|
|
37
43
|
/**
|
|
38
44
|
* The prescribed first play (inbound-led outbound), walked on ANY surface.
|
|
39
45
|
*
|
|
@@ -30,6 +30,7 @@ export function tagColorForIndex(index) {
|
|
|
30
30
|
// Deterministic color for a free-form label (workspace tags): same tag, same
|
|
31
31
|
// color on every surface, with no stored color to sync.
|
|
32
32
|
export function tagColorForLabel(label) {
|
|
33
|
+
label = label.toLowerCase();
|
|
33
34
|
let hash = 0;
|
|
34
35
|
for (let index = 0; index < label.length; index += 1) {
|
|
35
36
|
hash = (hash * 31 + label.charCodeAt(index)) | 0;
|
|
@@ -122,6 +122,7 @@ export function readSequenceFailure(error, overrides = {}) {
|
|
|
122
122
|
"statusCode",
|
|
123
123
|
"provider_status",
|
|
124
124
|
"providerStatus",
|
|
125
|
+
"upstream_status",
|
|
125
126
|
]));
|
|
126
127
|
let retryable = overrides.retryable ?? firstBoolean(records, ["retryable"]);
|
|
127
128
|
let effectOutcome = overrides.effectOutcome ?? readEffectOutcome(records);
|
|
@@ -205,14 +206,16 @@ function classifySequenceFailure(input) {
|
|
|
205
206
|
return "authorization_scope";
|
|
206
207
|
}
|
|
207
208
|
if (input.hardBounce === true ||
|
|
208
|
-
/recipient|hard[_-]?bounce|invalid[_-]?(?:profile|recipient|member)|lead[_-]?not[_-]?found|already[_-]?(?:connected|invited)|not[_-]?eligible|cannot[_-]?(?:message|invite)/i.test(codes)) {
|
|
209
|
+
/crm_record_(?:not_found|missing)|recipient|hard[_-]?bounce|invalid[_-]?(?:profile|recipient|member)|lead[_-]?not[_-]?found|already[_-]?(?:connected|invited)|not[_-]?eligible|cannot[_-]?(?:message|invite)/i.test(codes)) {
|
|
209
210
|
return "recipient";
|
|
210
211
|
}
|
|
211
212
|
if (input.httpStatus === 400 ||
|
|
212
213
|
input.httpStatus === 422 ||
|
|
213
|
-
/validation|invalid[_-]?request|bad[_-]?request|malformed|required[_-]?field|unsupported[_-]?(?:input|value)/i.test(codes)) {
|
|
214
|
+
/crm_(?:record_|task_)(?:mapping|association|identity|property)|validation|invalid[_-]?request|bad[_-]?request|malformed|required[_-]?field|unsupported[_-]?(?:input|value)/i.test(codes)) {
|
|
214
215
|
return "validation";
|
|
215
216
|
}
|
|
217
|
+
if (/action[_-]?not[_-]?found/i.test(codes))
|
|
218
|
+
return "internal";
|
|
216
219
|
if ((input.httpStatus !== null && input.httpStatus >= 500) ||
|
|
217
220
|
(input.httpStatus === 404 && input.retryable === true) ||
|
|
218
221
|
input.provider !== null ||
|
|
@@ -220,9 +223,7 @@ function classifySequenceFailure(input) {
|
|
|
220
223
|
return "provider";
|
|
221
224
|
}
|
|
222
225
|
if (/^(?:TypeError|ReferenceError|RangeError|SyntaxError)$/i.test(input.errorName ?? "") ||
|
|
223
|
-
|
|
224
|
-
// (composio_action_not_found) is an OXYGEN routing defect, not the lead's.
|
|
225
|
-
/internal|database|persistence|bookkeeping|invariant|concurrent[_-]?write|action[_-]?not[_-]?found/i.test(codes)) {
|
|
226
|
+
/internal|database|persistence|bookkeeping|invariant|concurrent[_-]?write/i.test(codes)) {
|
|
226
227
|
return "internal";
|
|
227
228
|
}
|
|
228
229
|
return "unknown";
|
|
@@ -263,6 +263,7 @@ export type SequenceLinkedInWaitForConnectionStep = {
|
|
|
263
263
|
kind: "wait_for_connection";
|
|
264
264
|
timeout_days: number;
|
|
265
265
|
on_timeout: "stop" | "continue";
|
|
266
|
+
withdraw_on_timeout?: boolean;
|
|
266
267
|
};
|
|
267
268
|
/** A file to attach to a LinkedIn message (image / document), fetched at send time. */
|
|
268
269
|
export type SequenceLinkedInAttachment = {
|
|
@@ -601,6 +602,7 @@ export type SequenceCrmTaskStep = {
|
|
|
601
602
|
export type SequenceWaitStep = {
|
|
602
603
|
id: string;
|
|
603
604
|
kind: "wait";
|
|
605
|
+
working_days?: boolean;
|
|
604
606
|
days?: number;
|
|
605
607
|
hours?: number;
|
|
606
608
|
/**
|
|
@@ -939,6 +941,12 @@ export declare function sequenceWaitStepJitterMs(step: SequenceWaitStep, enrollm
|
|
|
939
941
|
* spreads a same-instant batch across the jitter window.
|
|
940
942
|
*/
|
|
941
943
|
export declare function sequenceWaitStepDelayWithJitterMs(step: SequenceWaitStep, enrollmentId: string): number;
|
|
944
|
+
/** Working days use the sequence schedule's weekdays and zone, Monday–Friday UTC if absent.
|
|
945
|
+
* Whole days preserve local wall time across DST; hours and jitter remain elapsed time. */
|
|
946
|
+
export declare function sequenceWaitStepReadyAt(step: SequenceWaitStep, enrollmentId: string, now: Date, schedule?: {
|
|
947
|
+
timezone?: string;
|
|
948
|
+
days?: readonly (number | string)[];
|
|
949
|
+
} | null): Date;
|
|
942
950
|
/**
|
|
943
951
|
* Whether `after` changes nothing about `before` except how long its existing
|
|
944
952
|
* wait steps wait. This is the one journey edit a LIVE sequence accepts, and it
|
|
@@ -955,6 +955,8 @@ raw, index, options, issues) {
|
|
|
955
955
|
};
|
|
956
956
|
}
|
|
957
957
|
case "wait_for_connection": {
|
|
958
|
+
if (raw.withdraw_on_timeout !== undefined && typeof raw.withdraw_on_timeout !== "boolean")
|
|
959
|
+
issues.push({ path: `${path}.withdraw_on_timeout`, message: "must be boolean." });
|
|
958
960
|
const timeoutDays = raw.timeout_days === undefined || raw.timeout_days === null
|
|
959
961
|
? 14
|
|
960
962
|
: positiveInt(raw.timeout_days, `${path}.timeout_days`, issues) ?? 14;
|
|
@@ -962,6 +964,7 @@ raw, index, options, issues) {
|
|
|
962
964
|
id,
|
|
963
965
|
channel: "linkedin",
|
|
964
966
|
kind: "wait_for_connection",
|
|
967
|
+
...(raw.withdraw_on_timeout === true ? { withdraw_on_timeout: true } : {}),
|
|
965
968
|
timeout_days: timeoutDays,
|
|
966
969
|
on_timeout: raw.on_timeout === "continue" ? "continue" : "stop",
|
|
967
970
|
};
|
|
@@ -1163,10 +1166,15 @@ raw, index, options, issues) {
|
|
|
1163
1166
|
if ((days ?? 0) + (hours ?? 0) <= 0 && !options.allowZeroWaits) {
|
|
1164
1167
|
issues.push({ path, message: "A wait step needs days and/or hours totaling at least 1 hour." });
|
|
1165
1168
|
}
|
|
1169
|
+
if (raw.working_days !== undefined && typeof raw.working_days !== "boolean")
|
|
1170
|
+
issues.push({ path: `${path}.working_days`, message: "must be boolean." });
|
|
1171
|
+
if (raw.working_days === true && (days ?? 0) > 3650)
|
|
1172
|
+
issues.push({ path: `${path}.days`, message: "Working-day waits must be at most 3650 days." });
|
|
1166
1173
|
const jitterHours = optionalWaitJitterHours(raw.jitter_hours, `${path}.jitter_hours`, issues);
|
|
1167
1174
|
return {
|
|
1168
1175
|
id,
|
|
1169
1176
|
kind: "wait",
|
|
1177
|
+
...(raw.working_days === true ? { working_days: true } : {}),
|
|
1170
1178
|
...(days !== undefined ? { days } : {}),
|
|
1171
1179
|
...(hours !== undefined ? { hours } : {}),
|
|
1172
1180
|
...(jitterHours !== undefined ? { jitter_hours: jitterHours } : {}),
|
|
@@ -1274,7 +1282,7 @@ raw, index, options, issues) {
|
|
|
1274
1282
|
const STEP_KIND_FIELDS = {
|
|
1275
1283
|
visit_profile: [],
|
|
1276
1284
|
invite: ["note_template", "ai_prompt"],
|
|
1277
|
-
wait_for_connection: ["timeout_days", "on_timeout"],
|
|
1285
|
+
wait_for_connection: ["timeout_days", "on_timeout", "withdraw_on_timeout"],
|
|
1278
1286
|
message: ["template", "variants", "auto_optimize", "attachments", "ai_prompt"],
|
|
1279
1287
|
inmail: ["subject_template", "template", "variants", "auto_optimize"],
|
|
1280
1288
|
follow: [],
|
|
@@ -1289,7 +1297,7 @@ const STEP_KIND_FIELDS = {
|
|
|
1289
1297
|
whatsapp_message: ["template", "variants", "auto_optimize"],
|
|
1290
1298
|
call_task: ["note", "priority", "timeout_days", "on_timeout"],
|
|
1291
1299
|
crm_task: ["provider", "connection_id", "property_mappings", "record_mappings", "record_links", "associations"],
|
|
1292
|
-
wait: ["days", "hours", "jitter_hours"],
|
|
1300
|
+
wait: ["days", "hours", "jitter_hours", "working_days"],
|
|
1293
1301
|
wait_for_signal: ["signal", "timeout_days", "on_timeout"],
|
|
1294
1302
|
branch: ["condition", "then", "else", "then_id", "else_id", "timeout_days"],
|
|
1295
1303
|
stop: [],
|
|
@@ -2026,6 +2034,39 @@ export function sequenceWaitStepJitterMs(step, enrollmentId) {
|
|
|
2026
2034
|
export function sequenceWaitStepDelayWithJitterMs(step, enrollmentId) {
|
|
2027
2035
|
return sequenceWaitStepDelayMs(step) + sequenceWaitStepJitterMs(step, enrollmentId);
|
|
2028
2036
|
}
|
|
2037
|
+
/** Working days use the sequence schedule's weekdays and zone, Monday–Friday UTC if absent.
|
|
2038
|
+
* Whole days preserve local wall time across DST; hours and jitter remain elapsed time. */
|
|
2039
|
+
export function sequenceWaitStepReadyAt(step, enrollmentId, now, schedule) {
|
|
2040
|
+
if (!step.working_days || !step.days)
|
|
2041
|
+
return new Date(now.getTime() + sequenceWaitStepDelayWithJitterMs(step, enrollmentId));
|
|
2042
|
+
const zone = schedule?.timezone || "UTC";
|
|
2043
|
+
const configuredDays = schedule?.days ?? [];
|
|
2044
|
+
const days = configuredDays.length > 0
|
|
2045
|
+
? Array.from({ length: 7 }, (_, index) => index + 1).filter(day => sendWindowAllowsWeekday([...configuredDays], day))
|
|
2046
|
+
: [1, 2, 3, 4, 5];
|
|
2047
|
+
if (days.length === 0)
|
|
2048
|
+
days.push(1, 2, 3, 4, 5);
|
|
2049
|
+
let candidate = new Date(now);
|
|
2050
|
+
const start = tzParts(now, zone) ?? tzParts(now, "UTC");
|
|
2051
|
+
let remaining = step.days;
|
|
2052
|
+
while (remaining > 0) {
|
|
2053
|
+
candidate = new Date(candidate.getTime() + 86_400_000);
|
|
2054
|
+
// Correct a daylight-saving offset while retaining the original local hour/minute.
|
|
2055
|
+
for (let round = 0; round < 2; round += 1) {
|
|
2056
|
+
const parts = tzParts(candidate, zone) ?? tzParts(candidate, "UTC");
|
|
2057
|
+
let drift = (parts.hour - start.hour) * 60 + parts.minute - start.minute;
|
|
2058
|
+
if (drift > 720)
|
|
2059
|
+
drift -= 1440;
|
|
2060
|
+
if (drift < -720)
|
|
2061
|
+
drift += 1440;
|
|
2062
|
+
candidate = new Date(candidate.getTime() - drift * 60_000);
|
|
2063
|
+
}
|
|
2064
|
+
const parts = tzParts(candidate, zone) ?? tzParts(candidate, "UTC");
|
|
2065
|
+
if (sendWindowAllowsWeekday([...days], parts.isoWeekday))
|
|
2066
|
+
remaining -= 1;
|
|
2067
|
+
}
|
|
2068
|
+
return new Date(candidate.getTime() + (step.hours ?? 0) * 3_600_000 + sequenceWaitStepJitterMs(step, enrollmentId));
|
|
2069
|
+
}
|
|
2029
2070
|
/** Key-sorted JSON, so two structurally equal steps compare equal whatever order their keys were built in. */
|
|
2030
2071
|
function stableJson(value) {
|
|
2031
2072
|
if (Array.isArray(value))
|
|
@@ -2080,6 +2121,7 @@ export function sequenceDefinitionsDifferOnlyInTiming(before, after) {
|
|
|
2080
2121
|
delete fields.days;
|
|
2081
2122
|
delete fields.hours;
|
|
2082
2123
|
delete fields.jitter_hours;
|
|
2124
|
+
delete fields.working_days;
|
|
2083
2125
|
return stableJson(fields);
|
|
2084
2126
|
};
|
|
2085
2127
|
return timingFree(oldStep) === timingFree(newStep);
|
|
@@ -43,13 +43,18 @@ export declare function tagKindHeading(kind: string): string;
|
|
|
43
43
|
/** Hard cap per tagged item — matches the wiki's long-standing limit. */
|
|
44
44
|
export declare const MAX_TAGS_PER_ITEM = 50;
|
|
45
45
|
/**
|
|
46
|
-
* Normalize a raw tag array:
|
|
46
|
+
* Normalize a raw tag array: preserve case, trim, drop non-strings/empties, dedupe,
|
|
47
47
|
* cap at {@link MAX_TAGS_PER_ITEM}. Callers that must reject non-arrays with a
|
|
48
48
|
* typed error (the wiki does) validate the shape first and delegate here.
|
|
49
49
|
*/
|
|
50
50
|
export declare function normalizeTagList(values: readonly unknown[]): string[];
|
|
51
51
|
/** Lenient variant: `undefined`/`null`/non-arrays normalize to `[]`. */
|
|
52
52
|
export declare function normalizeTags(value: unknown): string[];
|
|
53
|
+
/** Persistence-only normalization; browser label formatting does not activate writes. */
|
|
54
|
+
export declare function normalizeStoredTagList(values: readonly unknown[]): string[];
|
|
55
|
+
export declare function normalizeStoredTags(value: unknown): string[];
|
|
56
|
+
/** Merge/rename targets must use the same rollout representation as ordinary writes. */
|
|
57
|
+
export declare function normalizeStoredTagLabel(value: string): string;
|
|
53
58
|
/**
|
|
54
59
|
* Registry-only caps (ADR 0017). Deliberately NOT enforced by
|
|
55
60
|
* {@link normalizeTagList}: that runs on every writer's hot path, so adding a
|
|
@@ -65,8 +70,7 @@ export declare const MAX_TAG_DESCRIPTION_LENGTH = 500;
|
|
|
65
70
|
* over {@link MAX_TAG_LABEL_LENGTH}. Returns `null` so callers can raise their
|
|
66
71
|
* own typed error.
|
|
67
72
|
*
|
|
68
|
-
*
|
|
69
|
-
* "q3 outbound", space intact. Any character is legal in a tag.
|
|
73
|
+
* Keeps the entered case and spaces; identity comparisons are case-insensitive. Any character is legal in a tag.
|
|
70
74
|
*/
|
|
71
75
|
export declare function normalizeTagLabel(value: unknown): string | null;
|
|
72
76
|
/**
|