@oxygen-agent/cli 1.1014.985 → 1.1014.1087

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/dist/command-families/program.d.ts +2 -0
  3. package/dist/command-families/program.js +241 -0
  4. package/dist/command-families/tables/automation.d.ts +22 -0
  5. package/dist/command-families/tables/automation.js +250 -0
  6. package/dist/command-families/tables/bundle-export.d.ts +7 -0
  7. package/dist/command-families/tables/bundle-export.js +198 -0
  8. package/dist/command-families/tables/bundle-format.d.ts +66 -0
  9. package/dist/command-families/tables/bundle-format.js +274 -0
  10. package/dist/command-families/tables/bundle-import.d.ts +20 -0
  11. package/dist/command-families/tables/bundle-import.js +260 -0
  12. package/dist/command-families/tables/display.d.ts +107 -0
  13. package/dist/command-families/tables/display.js +229 -0
  14. package/dist/command-families/tables/import-batches.d.ts +26 -0
  15. package/dist/command-families/tables/import-batches.js +94 -0
  16. package/dist/command-families/tables/import-columns.d.ts +24 -0
  17. package/dist/command-families/tables/import-columns.js +131 -0
  18. package/dist/command-families/tables/import-upload.d.ts +30 -0
  19. package/dist/command-families/tables/import-upload.js +203 -0
  20. package/dist/command-families/tables/import-values.d.ts +10 -0
  21. package/dist/command-families/tables/import-values.js +60 -0
  22. package/dist/command-families/tables/imports.d.ts +19 -0
  23. package/dist/command-families/tables/imports.js +200 -0
  24. package/dist/command-families/tables/options.d.ts +314 -0
  25. package/dist/command-families/tables/options.js +1 -0
  26. package/dist/command-families/tables/register-automation.d.ts +2 -0
  27. package/dist/command-families/tables/register-automation.js +127 -0
  28. package/dist/command-families/tables/register-external-imports.d.ts +2 -0
  29. package/dist/command-families/tables/register-external-imports.js +293 -0
  30. package/dist/command-families/tables/register-files.d.ts +2 -0
  31. package/dist/command-families/tables/register-files.js +72 -0
  32. package/dist/command-families/tables/register-followers.d.ts +2 -0
  33. package/dist/command-families/tables/register-followers.js +73 -0
  34. package/dist/command-families/tables/register-lifecycle.d.ts +2 -0
  35. package/dist/command-families/tables/register-lifecycle.js +114 -0
  36. package/dist/command-families/tables/register-reads.d.ts +2 -0
  37. package/dist/command-families/tables/register-reads.js +187 -0
  38. package/dist/command-families/tables/register-relations.d.ts +2 -0
  39. package/dist/command-families/tables/register-relations.js +244 -0
  40. package/dist/command-families/tables/register-row-actions.d.ts +2 -0
  41. package/dist/command-families/tables/register-row-actions.js +53 -0
  42. package/dist/command-families/tables/register-row-writes.d.ts +2 -0
  43. package/dist/command-families/tables/register-row-writes.js +140 -0
  44. package/dist/command-families/tables/register-views.d.ts +2 -0
  45. package/dist/command-families/tables/register-views.js +83 -0
  46. package/dist/command-families/tables/register-watchers.d.ts +2 -0
  47. package/dist/command-families/tables/register-watchers.js +65 -0
  48. package/dist/command-families/tables/register-webhooks.d.ts +2 -0
  49. package/dist/command-families/tables/register-webhooks.js +126 -0
  50. package/dist/command-families/tables/registration-context.d.ts +8 -0
  51. package/dist/command-families/tables/registration-context.js +1 -0
  52. package/dist/command-families/tables/registration.d.ts +3 -0
  53. package/dist/command-families/tables/registration.js +30 -0
  54. package/dist/command-families/tables/row-selection.d.ts +5 -0
  55. package/dist/command-families/tables/row-selection.js +82 -0
  56. package/dist/command-families/tables/send.d.ts +6 -0
  57. package/dist/command-families/tables/send.js +111 -0
  58. package/dist/command-manifest.js +1 -0
  59. package/dist/commands/copilot.js +8 -0
  60. package/dist/commands/deliverability.js +4 -2
  61. package/dist/commands/sequences.js +30 -2
  62. package/dist/commands/tables.d.ts +1 -3
  63. package/dist/commands/tables.js +1 -3505
  64. package/dist/credentials.js +1 -1
  65. package/dist/customer-operation-commands.js +62 -3
  66. package/dist/help.js +1 -1
  67. package/dist/index.d.ts +1 -99
  68. package/dist/index.js +5 -337
  69. package/dist/knowledge-mirror.js +1 -1
  70. package/dist/skills.js +4 -2
  71. package/dist/update.js +2 -1
  72. package/dist/workspace-placement-commands.d.ts +3 -0
  73. package/dist/workspace-placement-commands.js +32 -0
  74. package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +4 -0
  75. package/node_modules/@oxygen/shared/dist/agent-sessions.js +11 -3
  76. package/node_modules/@oxygen/shared/dist/agents-enabled.d.ts +29 -0
  77. package/node_modules/@oxygen/shared/dist/agents-enabled.js +51 -0
  78. package/node_modules/@oxygen/shared/dist/capability-discovery.js +39 -2
  79. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +1 -1
  80. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
  81. package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +5 -5
  82. package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.d.ts +30 -0
  83. package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.js +53 -0
  84. package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.d.ts +120 -0
  85. package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.js +48 -0
  86. package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.d.ts +267 -0
  87. package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.js +63 -0
  88. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +6 -0
  89. package/node_modules/@oxygen/shared/dist/contracts/operations.js +9 -4
  90. package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.js +3 -0
  92. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +17 -15
  93. package/node_modules/@oxygen/shared/dist/email-tracking-token.d.ts +2 -0
  94. package/node_modules/@oxygen/shared/dist/email-tracking-token.js +1 -0
  95. package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +4 -0
  96. package/node_modules/@oxygen/shared/dist/feature-gates.js +5 -0
  97. package/node_modules/@oxygen/shared/dist/file-import.d.ts +46 -0
  98. package/node_modules/@oxygen/shared/dist/file-import.js +138 -0
  99. package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +8 -0
  100. package/node_modules/@oxygen/shared/dist/guided-company-files.d.ts +13 -0
  101. package/node_modules/@oxygen/shared/dist/guided-company-files.js +14 -0
  102. package/node_modules/@oxygen/shared/dist/guided-first-sourcing.d.ts +116 -0
  103. package/node_modules/@oxygen/shared/dist/guided-first-sourcing.js +11 -0
  104. package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.d.ts +4 -0
  105. package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.js +42 -0
  106. package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.d.ts +22 -0
  107. package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.js +38 -0
  108. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.d.ts +147 -0
  109. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.d.ts +12 -0
  110. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.js +83 -0
  111. package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.js +44 -0
  112. package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.d.ts +7 -0
  113. package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.js +13 -0
  114. package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.d.ts +4 -0
  115. package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.js +34 -0
  116. package/node_modules/@oxygen/shared/dist/guided-trial-billing.d.ts +26 -0
  117. package/node_modules/@oxygen/shared/dist/guided-trial-billing.js +19 -0
  118. package/node_modules/@oxygen/shared/dist/index.d.ts +0 -1
  119. package/node_modules/@oxygen/shared/dist/index.js +0 -6
  120. package/node_modules/@oxygen/shared/dist/table-limits.d.ts +1 -0
  121. package/node_modules/@oxygen/shared/dist/table-limits.js +6 -0
  122. package/node_modules/@oxygen/shared/dist/teammates.d.ts +13 -8
  123. package/node_modules/@oxygen/shared/dist/teammates.js +20 -11
  124. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +78 -0
  125. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  126. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  127. package/node_modules/@oxygen/shared/dist/workspace-placement-view.d.ts +149 -0
  128. package/node_modules/@oxygen/shared/dist/workspace-placement-view.js +101 -0
  129. package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +77 -0
  130. package/node_modules/@oxygen/shared/dist/workspace-placement.js +94 -0
  131. package/package.json +1 -1
@@ -0,0 +1,32 @@
1
+ import { Command } from "commander";
2
+ import { resolve } from "node:path";
3
+ import { readJsonFileValue } from "./command-support.js";
4
+ import { requestOxygen } from "./http-client.js";
5
+ export function registerWorkspacePlacementCommands(root, handle) {
6
+ root.addCommand(new Command("workspace-test").description("Compare email copy placement in this workspace's connected Google/Microsoft receiving inboxes: Gmail Primary/Promotions and Microsoft Focused/Other. Native sends, 0 additional credits; preview and exact message approval required.")
7
+ .addCommand(new Command("run").description("Preview a workspace-only copy test. Input JSON: sender_mailbox_ids, receiver_mailbox_ids, variants [{key,subject,body_text,body_html?}]. Approve the same input with returned request_id/plan_hash/rendered_at and max_live_sends. Up to five variants and 50 messages; never buys provider tests or resumes campaigns.")
8
+ .requiredOption("--input <file>", "JSON test input file.").option("--approved", "Execute the exact reviewed matrix.")
9
+ .option("--rendered-at <timestamp>", "Rendering timestamp returned by preview; reuse when approving.").option("--tracking-opens", "Preview with open tracking on verified workspace domain.").option("--tracking-clicks", "Preview with click tracking on verified workspace domain.")
10
+ .option("--request-id <id>", "Request UUID from preview.").option("--plan <hash>", "Exact preview plan hash.").option("--max-live-sends <n>", "Maximum approved diagnostic emails (1–50).")
11
+ .option("--preview-cursor <id>", "Read the next manifest page using the same input, request ID and rendering time.").option("--preview-limit <n>", "Preview manifest page size (1–20); never use with approval.")
12
+ .option("--preview-probe-id <id>", "Read exact rendered copy for a preview probe; reuse request ID and rendering time.").option("--preview-part <part>", "body_text or body_html.").option("--preview-offset <n>", "Unicode character offset returned by copy preview.").option("--preview-copy-limit <n>", "Copy page size (1–3000 characters).")
13
+ .option("--allow-paused-probe", "Explicitly allow diagnostic sends from paused senders, preserving pauses.").option("--json", "Print JSON.")
14
+ .action(async (options) => handle("deliverability workspace-test run", options, () => {
15
+ const input = readJsonFileValue(resolve(options.input), "--input");
16
+ if (!input || typeof input !== "object" || Array.isArray(input))
17
+ throw new Error("--input must contain a JSON object.");
18
+ return requestOxygen("/api/cli/deliverability/workspace-tests", { method: "POST", body: { ...input, approved: options.approved === true,
19
+ ...(options.approved ? { approved: true } : {}), ...(options.requestId ? { request_id: options.requestId } : {}),
20
+ ...(options.plan ? { plan_hash: options.plan } : {}), ...(options.renderedAt ? { rendered_at: options.renderedAt } : {}), ...(options.trackingOpens ? { tracking_opens: true } : {}), ...(options.trackingClicks ? { tracking_clicks: true } : {}), ...(options.maxLiveSends ? { max_live_sends: Number(options.maxLiveSends) } : {}),
21
+ ...(options.allowPausedProbe ? { allow_paused_probe: true } : {}),
22
+ ...(options.previewCursor ? { preview_cursor: options.previewCursor } : {}), ...(options.previewLimit !== undefined ? { preview_limit: Number(options.previewLimit) } : {}),
23
+ ...(options.previewProbeId ? { preview_probe_id: options.previewProbeId } : {}), ...(options.previewPart ? { preview_part: options.previewPart } : {}), ...(options.previewOffset !== undefined ? { preview_offset: Number(options.previewOffset) } : {}), ...(options.previewCopyLimit !== undefined ? { preview_copy_limit: Number(options.previewCopyLimit) } : {}) } });
24
+ })))
25
+ .addCommand(new Command("copy").description("Read a Sequence email step and its copy variants for workspace placement testing. Resolve personalization before preview; no send or charge.").argument("<sequence-id>").option("--step-index <n>", "Zero-based email step index.", "0").option("--json", "Print JSON.")
26
+ .action(async (id, options) => handle("deliverability workspace-test copy", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/copy?sequence_id=${encodeURIComponent(id)}&step_index=${encodeURIComponent(options.stepIndex)}`))))
27
+ .addCommand(new Command("list").description("Read this workspace's recent copy/Primary/Focused placement tests. Saved state only: no send, provider refresh or charge.").option("--cursor <id>", "Next cursor returned by list.").option("--limit <n>", "Tests per page (1–20).").option("--json", "Print JSON.")
28
+ .action(async (options) => handle("deliverability workspace-test list", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests?${new URLSearchParams({ ...options.cursor ? { cursor: options.cursor } : {}, ...options.limit !== undefined ? { limit: String(options.limit) } : {} })}`))))
29
+ .addCommand(new Command("get").description("Read one workspace copy test: variant comparison, per-receiver category, partial results and unknowns. No send or provider refresh.").argument("<id>").option("--cursor <id>", "Next receiver-result cursor.").option("--limit <n>", "Receiver results per page (1–20).")
30
+ .option("--probe <id>", "Read this probe's frozen message copy.").option("--part <part>", "body_text or body_html.").option("--offset <n>", "Returned Unicode character offset.").option("--copy-limit <n>", "Copy page size (1–3000 characters).").option("--json", "Print JSON.")
31
+ .action(async (id, options) => handle("deliverability workspace-test get", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/${encodeURIComponent(id)}?${new URLSearchParams({ ...options.cursor ? { cursor: options.cursor } : {}, ...options.limit !== undefined ? { limit: String(options.limit) } : {}, ...options.probe ? { probe_id: options.probe } : {}, ...options.part ? { body_part: options.part } : {}, ...options.offset !== undefined ? { offset: String(options.offset) } : {}, ...options.copyLimit !== undefined ? { copy_limit: String(options.copyLimit) } : {} })}`)))));
32
+ }
@@ -40,8 +40,12 @@ export type AgentSessionTeammateRoutine = {
40
40
  };
41
41
  /** The routine marker of a session's settings, or null for a general session. */
42
42
  export declare function agentSessionTeammateRoutine(config: Pick<AgentSessionConfig, "teammate_routine">): AgentSessionTeammateRoutine | null;
43
+ /** Staff only: on dev, and in production's staff workspaces (`agentsEnabledIn`; Tim, 2026-10-16). */
43
44
  export declare function agentSessionsEnabled(env?: Record<string, string | undefined>): boolean;
45
+ /** Whether this workspace has Ongoing Agent sessions, and so Oxygent routines. */
46
+ export declare function agentSessionsEnabledIn(organizationId: string | null | undefined, env?: Record<string, string | undefined>): boolean;
44
47
  export declare function agentSessionsAvailableTo(env: Record<string, string | undefined>, viewer: {
48
+ organizationId: string | null;
45
49
  email: string | null | undefined;
46
50
  member: boolean;
47
51
  oxygenRole?: string | null | undefined;
@@ -1,3 +1,4 @@
1
+ import { agentsEnabled, agentsEnabledIn } from "./agents-enabled.js";
1
2
  import { guidedActivationEnabled } from "./guided-activation.js";
2
3
  import { validateContract } from "./contracts/schema.js";
3
4
  import { GUIDED_ACTIVATION_SCAN_POLICY_CONTRACT } from "./contracts/guided-activation.js";
@@ -10,11 +11,18 @@ export function agentSessionTeammateRoutine(config) {
10
11
  const marker = config.teammate_routine;
11
12
  return marker && typeof marker.chat_id === "string" ? marker : null;
12
13
  }
14
+ /** Staff only: on dev, and in production's staff workspaces (`agentsEnabledIn`; Tim, 2026-10-16). */
13
15
  export function agentSessionsEnabled(env = process.env) {
14
- return env.OXYGEN_DEPLOY_ENV === "selfhost-dev" && env.OXYGEN_AGENT_SESSIONS_ENABLED === "staff" && env.OXYGEN_AGENTS_ENABLED?.trim().toLowerCase() === "true";
16
+ return (env.OXYGEN_DEPLOY_ENV === "selfhost-dev" || env.OXYGEN_DEPLOY_ENV === "selfhost-prod")
17
+ && env.OXYGEN_AGENT_SESSIONS_ENABLED === "staff" && agentsEnabled(env);
18
+ }
19
+ /** Whether this workspace has Ongoing Agent sessions, and so Oxygent routines. */
20
+ export function agentSessionsEnabledIn(organizationId, env = process.env) {
21
+ return agentSessionsEnabled(env) && agentsEnabledIn(organizationId, env);
15
22
  }
16
23
  export function agentSessionsAvailableTo(env, viewer) {
17
- return agentSessionsEnabled(env) && viewer.member && viewer.oxygenRole !== "client" && (guidedActivationEnabled(env) || isStaffEmail(viewer.email, env));
24
+ return agentSessionsEnabledIn(viewer.organizationId, env) && viewer.member && viewer.oxygenRole !== "client"
25
+ && (guidedActivationEnabled(env) || isStaffEmail(viewer.email, env));
18
26
  }
19
27
  /** Reviewed native adapters. Unknown tools (including generic dispatch and agent management) fail closed. */
20
28
  export const AGENT_SESSION_ADAPTERS = {
@@ -124,7 +132,7 @@ export function validateAgentSessionConfig(config, requireResources = true) {
124
132
  if (config.guided_activation !== undefined && (!config.teammate_routine
125
133
  || !config.guided_activation || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(config.guided_activation.activation_id)
126
134
  || !["lead_scan", "morning_briefing"].includes(config.guided_activation.purpose))) {
127
- fail("Guided routines require an activation, purpose and private Oxygent chat.");
135
+ fail("Guided routines require an activation, purpose and bound Oxygent chat.");
128
136
  }
129
137
  if (config.guided_activation?.purpose === "lead_scan") {
130
138
  const policy = config.guided_activation.scan_policy;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Where autonomous Agents may run. OXYGEN_AGENTS_ENABLED authorizes Agent run
3
+ * execution, which carries tool authority and credit spend (feature-gates.ts),
4
+ * and Oxygents and their routines need it too (ADR 0032).
5
+ *
6
+ * - "true" opens every workspace on dev and in tests.
7
+ * - "staff" opens only the workspaces listed in OXYGEN_AGENTS_WORKSPACE_IDS.
8
+ * - Production opens only listed workspaces that are also in
9
+ * PRODUCTION_AGENT_WORKSPACE_IDS below, whichever value switches it on, so
10
+ * Doppler can narrow production but never widen it.
11
+ * - Shadow never opens, a deployment name nobody knows never opens every
12
+ * workspace, and anything else is off, as is an empty list where one is needed.
13
+ *
14
+ * Code released before this reads "staff" as off, so the value can be set
15
+ * ahead of a release without opening anything early.
16
+ */
17
+ export type AgentsWorkspaceScope = "off" | "all" | ReadonlySet<string>;
18
+ /**
19
+ * The production workspaces Agents, Oxygents and their routines may run in:
20
+ * staff workspaces only (Tim, 2026-10-16). Adding a customer's workspace opens
21
+ * autonomous runs and their spend there, so it needs a founder decision
22
+ * recorded in OXYGEN_PRODUCT.md and a reviewed change here.
23
+ */
24
+ export declare const PRODUCTION_AGENT_WORKSPACE_IDS: ReadonlySet<string>;
25
+ export declare function agentsWorkspaceScope(env?: Record<string, string | undefined>): AgentsWorkspaceScope;
26
+ /** Whether Agents run in any workspace here: what a worker loop or family checks before it looks at one. */
27
+ export declare function agentsEnabled(env?: Record<string, string | undefined>): boolean;
28
+ /** Whether Agents run in this workspace. */
29
+ export declare function agentsEnabledIn(organizationId: string | null | undefined, env?: Record<string, string | undefined>): boolean;
@@ -0,0 +1,51 @@
1
+ import { isProductionData, resolveDeployEnv } from "./deploy-env.js";
2
+ /**
3
+ * The production workspaces Agents, Oxygents and their routines may run in:
4
+ * staff workspaces only (Tim, 2026-10-16). Adding a customer's workspace opens
5
+ * autonomous runs and their spend there, so it needs a founder decision
6
+ * recorded in OXYGEN_PRODUCT.md and a reviewed change here.
7
+ */
8
+ export const PRODUCTION_AGENT_WORKSPACE_IDS = new Set([
9
+ "4257983c-406e-4658-b899-d7ba3cc760a6", // OXYGEN
10
+ "797d46fe-3e57-4887-98fe-d4793b45dc3a", // Oxygen Demo
11
+ ]);
12
+ export function agentsWorkspaceScope(env = process.env) {
13
+ const flag = env.OXYGEN_AGENTS_ENABLED?.trim().toLowerCase();
14
+ if (flag !== "true" && flag !== "staff")
15
+ return "off";
16
+ const tier = deploymentTier(env);
17
+ if (tier === "shadow")
18
+ return "off";
19
+ if (flag === "true" && tier === "other")
20
+ return "all";
21
+ const listed = (env.OXYGEN_AGENTS_WORKSPACE_IDS ?? "")
22
+ .split(",")
23
+ .map((id) => id.trim().toLowerCase())
24
+ .filter((id) => id.length > 0 && (tier !== "prod" || PRODUCTION_AGENT_WORKSPACE_IDS.has(id)));
25
+ return listed.length > 0 ? new Set(listed) : "off";
26
+ }
27
+ /** Whether Agents run in any workspace here: what a worker loop or family checks before it looks at one. */
28
+ export function agentsEnabled(env = process.env) {
29
+ return agentsWorkspaceScope(env) !== "off";
30
+ }
31
+ /** Whether Agents run in this workspace. */
32
+ export function agentsEnabledIn(organizationId, env = process.env) {
33
+ const scope = agentsWorkspaceScope(env);
34
+ if (scope === "off")
35
+ return false;
36
+ if (scope === "all")
37
+ return true;
38
+ return typeof organizationId === "string" && scope.has(organizationId.trim().toLowerCase());
39
+ }
40
+ function deploymentTier(env) {
41
+ try {
42
+ const { tier } = resolveDeployEnv(env);
43
+ if (tier === "prod" || tier === "shadow")
44
+ return tier;
45
+ // Managed dev telemetry can still carry production data via NODE_ENV.
46
+ return isProductionData(env) ? "prod" : "other";
47
+ }
48
+ catch {
49
+ return "unknown";
50
+ }
51
+ }
@@ -423,7 +423,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
423
423
  primitive: "agents",
424
424
  owns: "Adaptive governed worker definitions, versions, authorization, context, triggers, threads, checkpoints, inference, and first-class runs.",
425
425
  notFor: "A known deterministic graph that belongs in Workflows or an opaque uploaded runtime.",
426
- execution: "Publish and run a governed Agent with explicit tool authority and bounded spend. Staff on dev can create ongoing sessions: draft, exact resource scope, per-run and rolling 24-hour caps, preview/activate, continuing conversation and bounded self-scheduling. Inspect agent session templates; pause/resume preserves history. Staff on dev also talk to Oxygents (AI teammates): the workspace's first one (New Bot until named) and any others added, one ongoing private chat each. A new one is its owner's until shared with everyone in the workspace or named people, as collaborators (a chat of their own) or managers (also its profile and who has access). Group chats hold up to six Oxygents and, if you like, teammates: their admins change who is in them, and each message goes to the Oxygents it @mentions or is for.",
426
+ execution: "Publish and run a governed Agent with explicit tool authority and bounded spend. Where admitted, staff can create ongoing sessions: draft, exact resource scope, per-run and rolling 24-hour caps, preview/activate, continuing conversation and bounded self-scheduling. Inspect agent session templates; pause/resume preserves history. Where admitted, Oxygents (AI teammates) default to Workspace: one ongoing chat shared by everyone with access. Choose visibility personal when creating one for creator-only access; an API key acts as its issuing person. Home stays Workspace. Owners change visibility with confirm_visibility: publishing shares all existing history; Personal keeps history but removes others' future access and cannot retract what they saw. Collaborators use the shared chat; managers also change its profile and access. everyone off restricts Workspace access to its owner and named people, with workspace administrators retaining management access; it does not make it Personal. Group chats hold up to six Oxygents and optional teammates; messages go to the Oxygents mentioned or addressed.",
427
427
  posture: "mixed",
428
428
  gatewayTools: ["oxygen_agent_list", "oxygen_agent_create", "oxygen_agent_run"],
429
429
  gatewayCommands: ["agent list", "agent create", "agent run"],
@@ -1545,6 +1545,26 @@ function sentLinkedInInvitationReadRecommendation(query) {
1545
1545
  execution: "Read one live, free pending-sent invitation page with oxygen_linkedin_invites_sent via oxygen_capabilities_read; CLI twin: oxygen linkedin invitations list --account <ref> --limit <n> --cursor <token> --json. Resolve the sender with oxygen_senders_list. Resume invitations.cursor unchanged with the same account; a full v2 page can need one empty final page. No provider writes or credits. Recurring received/sent capture is linkedin invitations setup; withdrawal is a separate external action.",
1546
1546
  };
1547
1547
  }
1548
+ /** Workspace chat assistants are Oxygents; external app workspace selection stays separate. */
1549
+ function isOxygentChatIntent(query) {
1550
+ if (isWorkspacePagesIntent(query) || isSecondPartyDecisionIntent(query))
1551
+ return false;
1552
+ if (/\boxygents?\b/.test(query))
1553
+ return true;
1554
+ if (/\b(chatgpt|claude|codex|oauth|mcp|connectors?|connected apps?)\b/.test(query))
1555
+ return false;
1556
+ const chatPrivacy = /\b(chats?|conversations?|histor(?:y|ies)|visibility|shared|private|personal)\b/.test(query);
1557
+ if (/\bai (?:assistants?|teammates?)\b/.test(query))
1558
+ return chatPrivacy;
1559
+ // Human membership asks must not re-enter Oxygent mutations through the
1560
+ // generic fallback after the user-facing routine router excludes them.
1561
+ // Explicit Oxygent/AI teammate access above keeps its native owner.
1562
+ if (/\b(?:invite|members?|membership|permissions?|roles?|human|colleague|coworker)\b/.test(query))
1563
+ return false;
1564
+ return /\b(?:assistants?|teammates?)\b/.test(query)
1565
+ && /\b(chats?|conversations?|histor(?:y|ies)|visibility)\b/.test(query)
1566
+ && /\b(shared|private|personal|workspace|team|visibility)\b/.test(query);
1567
+ }
1548
1568
  /**
1549
1569
  * The apps a person connected with OAuth and where each one works: "make
1550
1570
  * ChatGPT always use workspace X", "default workspace for my Claude
@@ -2050,6 +2070,8 @@ function explicitCapabilityIntent(query) {
2050
2070
  const readOnly = readInspectionRecommendation(query);
2051
2071
  if (readOnly)
2052
2072
  return ROUTE_BY_ID.get(readOnly.capabilityId) ?? null;
2073
+ if (isOxygentChatIntent(query))
2074
+ return ROUTE_BY_ID.get("agents") ?? null;
2053
2075
  // Importing an AI answer is GEO evidence, rather than a Table file import.
2054
2076
  if (/\b(?:geo|aeo|ai (?:(?:search|answer) )?visibility|consumer ai answers?|answer engine optimization|generative engine optimization)\b/.test(query))
2055
2077
  return ROUTE_BY_ID.get("geo") ?? null;
@@ -2395,6 +2417,13 @@ function isPublishingPostIntent(query) {
2395
2417
  && !(/\b(find|search|scrape|research)\b/.test(query) && /\b(about|mention|containing)\b/.test(query));
2396
2418
  }
2397
2419
  function recommendationsFor(card, query) {
2420
+ if (card.id === "agents" && isOxygentChatIntent(query)) {
2421
+ const continuing = /\b(continue|resume|talk)\b|\b(?:read|send|post)\b.*\b(?:chat|conversation|message)s?\b/.test(query);
2422
+ return {
2423
+ tools: ["oxygen_oxygents_create", "oxygen_oxygents_update", "oxygen_oxygents_access", ...(continuing ? ["oxygen_oxygents_get", "oxygen_oxygents_message"] : [])],
2424
+ commands: ["oxygents create", "oxygents update", "oxygents access", ...(continuing ? ["oxygents get", "oxygents message"] : [])],
2425
+ };
2426
+ }
2398
2427
  if (card.id === "billing-and-budgets" && isPartnerBonusReadIntent(query)) {
2399
2428
  return { tools: ["oxygen_billing_summary"], commands: ["billing summary"] };
2400
2429
  }
@@ -3291,11 +3320,19 @@ function isConnectionPriceIntent(query) {
3291
3320
  * it. Sending, network, profile and post asks keep their own owners.
3292
3321
  */
3293
3322
  function isLinkedInAccountConnectIntent(query) {
3323
+ // Exact native command discovery has no LinkedIn noun to score.
3324
+ if (/^(?:oxygen )?senders connect$/.test(query))
3325
+ return true;
3294
3326
  if (!/\b(linkedin|unipile)\b/.test(query))
3295
3327
  return false;
3296
3328
  if (/^(linkedin|unipile)( accounts?)?$/.test(query))
3297
3329
  return true;
3298
- if (/\b(send|sending|enroll|sequence|campaign|cadence|messages?|dms?|invites?|invitations?|posts?|profiles?|network|connections|followers|viewers|limits|hours|ads?|scrape|research|company|companies|url|pac(?:e|ing)|spacing|spaced|spread|apart|throttl\w*)\b/.test(query))
3330
+ const authenticationUrl = /\b(?:authentication|authorization|oauth|auth)\b/.test(query)
3331
+ && /\b(?:url|link)\b/.test(query)
3332
+ && /\b(?:connect|reconnect|account|sender)\b/.test(query);
3333
+ if (/\burl\b/.test(query) && !authenticationUrl)
3334
+ return false;
3335
+ if (/\b(send|sending|enroll|sequence|campaign|cadence|messages?|dms?|invites?|invitations?|posts?|profiles?|network|connections|followers|viewers|limits|hours|ads?|scrape|research|company|companies|pac(?:e|ing)|spacing|spaced|spread|apart|throttl\w*)\b/.test(query))
3299
3336
  return false;
3300
3337
  return /\b(connect|reconnect|link|add|accounts?|senders?|seats?|free|plan)\b/.test(query);
3301
3338
  }
@@ -23,7 +23,7 @@ function agentSessionOperation(id, verb, method, suffix, summary, fields = {}, r
23
23
  };
24
24
  }
25
25
  export const AGENT_SESSION_OPERATIONS = [
26
- agentSessionOperation("agent.sessions.templates", "templates", "GET", "/templates", "Discover eight ongoing Agent job templates, reviewed actions, required resources, the full config schema and a draft example. Staff on dev only; legacy Agents remain available."),
26
+ agentSessionOperation("agent.sessions.templates", "templates", "GET", "/templates", "Discover eight ongoing Agent job templates, reviewed actions, required resources, the full config schema and a draft example. Dev only; requires a current non-client workspace member and enabled sessions. Legacy Agents remain available."),
27
27
  agentSessionOperation("agent.sessions.list", "list", "GET", "", "List independent ongoing sessions under an Agent, including status, next check and spend."),
28
28
  agentSessionOperation("agent.sessions.create", "create", "POST", "", "Create a draft ongoing session. Explicit caps are required; review and activate separately.", { config: AGENT_SESSION_CONFIG_CONTRACT }, ["config"]),
29
29
  agentSessionOperation("agent.sessions.get", "get", "GET", "/{session}", "Read this session's configuration revision, conversation, memory, next check, approvals and budget reservations."),
@@ -91,7 +91,7 @@ export const CUSTOMER_OPERATIONS = [
91
91
  operation("tables.import.csv", "/api/cli/tables/import-csv", "POST", "tables import-csv", "oxygen_tables_import_csv", "tables", "Import inline CSV into a hosted Table through its durable ingestion run. Use staged upload for large files.", { csv_text: text(), csv_base64: text(), table: text(), table_name: text(), project: text(), filename: text(), upsert_key: text(), skip_blank_cells: skipBlankCells(), batch_size: int({ minimum: 1, maximum: 5000 }) }, [], cliOnly),
92
92
  operation("tables.import.prepare", "/api/cli/tables/import-url", "POST", "tables import-upload", "oxygen_tables_import_upload", "tables", "Prepare a scoped storage upload for a hosted Table import; uploads and imports are separate, inspectable steps.", { file_name: text(), content_type: text(), byte_length: int({ minimum: 1 }), trace_id: text(), format: enumOf(["csv", "json", "xlsx"]) }, ["file_name", "byte_length"]),
93
93
  operation("tables.import.preview", "/api/cli/tables/import-preview", "POST", "tables import-preview", "oxygen_tables_import_preview", "tables", "Preview a staged file's headers, rows and existing-table mapping; writes no imported rows.", { storage_key: text(), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), byte_length: int({ minimum: 1 }), table: text(), upsert_key: text() }, ["storage_key", "byte_length"], { mutates: false }),
94
- operation("tables.import.staged", "/api/cli/tables/import-staged", "POST", "tables import-staged", "oxygen_tables_import_staged", "tables", "Start a durable hosted import from this workspace's staged file; choose an existing table or create_name.", { table: text(), create_name: text(), project: text(), storage_key: text(), storage_bucket: text(), storage_provider: enumOf(["s3"]), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), sha256: text(), upsert_key: text(), skip_blank_cells: skipBlankCells(), source_key_map: json, byte_length: int({ minimum: 1 }), row_count: int({ minimum: 1 }), max_concurrency: int({ minimum: 1, maximum: 1000 }), source: enumOf(["cli", "mcp", "web"]), batch_size: int({ minimum: 1, maximum: 5000 }) }, ["storage_key", "byte_length"]),
94
+ operation("tables.import.staged", "/api/cli/tables/import-staged", "POST", "tables import-staged", "oxygen_tables_import_staged", "tables", "Import a staged file into a table or a new create_name table.", { table: text(), create_name: text(), project: text(), storage_key: text(), storage_bucket: text(), storage_provider: enumOf(["s3"]), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), sha256: text(), upsert_key: text(), skip_blank_cells: skipBlankCells(), source_key_map: json, ignore_source_keys: list(), byte_length: int({ minimum: 1 }), row_count: int({ minimum: 1 }), max_concurrency: int({ minimum: 1, maximum: 1000 }), source: enumOf(["cli", "mcp", "web"]), batch_size: int({ minimum: 1, maximum: 5000 }) }, ["storage_key", "byte_length"]),
95
95
  operation("tables.import.file", "/api/cli/tables/import-file", "POST", "tables import-file", "oxygen_tables_import_file", "tables", "Import small base64 file bytes through the shared multipart import API; use staged upload for large files.", { content_base64: text(), file_name: text(), content_type: text(), table: text(), table_name: text(), project: text(), format: enumOf(["csv", "json", "xlsx"]), sheet: text(), upsert_key: text(), source_key_map: json, source_hash: text(), batch_size: int({ minimum: 1, maximum: 5000 }), max_concurrency: int({ minimum: 1 }) }, ["content_base64", "file_name"], { multipart: { contentField: "content_base64", nameField: "file_name", typeField: "content_type", fileField: "file" } }),
96
96
  operation("tables.link.suggest", "/api/cli/tables/link/suggest", "POST", "tables link-suggest", "oxygen_tables_link_suggest", "tables", "Plan column links and matching row counts between Tables; creates no relation. Apply with tables link --to (MCP oxygen_tables_link_bulk).", TABLE_LINK_SUGGEST_CONTRACT.properties, ["table", "to"], { mutates: false }),
97
97
  operation("tables.recover_pending", "/api/cli/tables/recover-pending", "POST", "tables recover-pending", "oxygen_tables_recover_pending", "tables", "Recover BetterContact email/phone requests and read verification blockers; no new orders or imports.", TABLE_RECOVER_PENDING_CONTRACT.properties, ["table"], { ...mcpOnly, annotations: { openWorldHint: true } }),
@@ -39,8 +39,8 @@ export const GUIDED_ACTIVATION_OPERATIONS = [
39
39
  cli: { path: ["onboarding", "activation", "status"], summary: "Resume your guided onboarding and native resource links. Enabled genuine dev only; current non-client workspace members." },
40
40
  mcp: { name: "oxygen_onboarding_activation_status", title: "Oxygen: Onboarding status", description: "Read your resumable guided onboarding and native links. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: false, spends: false },
41
41
  { id: "onboarding.activation.start", route: "/api/cli/onboarding/activation", method: "POST", contract: GUIDED_ACTIVATION_START_CONTRACT,
42
- cli: { path: ["onboarding", "activation", "start"], summary: "Prepare your activation and private Oxygent chat. Free; no message send or outreach." },
43
- mcp: { name: "oxygen_onboarding_activation_start", title: "Oxygen: Start onboarding record", description: "Prepare your activation with operating_path own_agent or oxygent. The oxygent path binds its native workspace Agent and creator-private chat. Free; no turn, inference, provider or outreach dispatch. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: true, spends: false },
42
+ cli: { path: ["onboarding", "activation", "start"], summary: "Prepare your creator-only activation and Oxygent chat: Workspace shared, Personal creator-only. Free; no send or outreach." },
43
+ mcp: { name: "oxygen_onboarding_activation_start", title: "Oxygen: Start onboarding record", description: "Prepare your creator-only activation with operating_path own_agent or oxygent. Oxygent binds its native Agent/chat: Workspace shared, Personal creator-only. Free; no turn, inference, provider or outreach dispatch. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: true, spends: false },
44
44
  { id: "onboarding.activation.get", route: "/api/cli/onboarding/activation/{activationId}", method: "GET", parameters: ["activationId"], contract: obj({}),
45
45
  cli: { path: ["onboarding", "activation", "get"], summary: "Read your activation by ID and its native resource links." },
46
46
  mcp: { name: "oxygen_onboarding_activation_get", title: "Oxygen: Read onboarding record", description: "Read your activation by activationId. Only its creator can read it. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: false, spends: false },
@@ -51,9 +51,9 @@ export const GUIDED_ACTIVATION_OPERATIONS = [
51
51
  cli: { path: ["onboarding", "activation", "first-leads"], summary: "Create or resume the activation-owned First leads Table. Free; no discovery, enrichment or outreach." },
52
52
  mcp: { name: "oxygen_onboarding_activation_first_leads", title: "Oxygen: First leads Table", description: "Create or resume one native First leads Table for your activationId. Free; no discovery, enrichment, enrollment or outreach. Use native search/ingestion separately.", pack: "agents" }, mutates: true, spends: false },
53
53
  { id: "onboarding.activation.routines_preview", route: "/api/cli/onboarding/activation/{activationId}/routines/preview", method: "POST", parameters: ["activationId"], contract: GUIDED_ACTIVATION_ROUTINES_PREVIEW_CONTRACT,
54
- cli: { path: ["onboarding", "activation", "routines-preview"], summary: "Review two explicitly configured draft routines, exact native source scope and caps. Arms no schedule." },
55
- mcp: { name: "oxygen_onboarding_activation_routines_preview", title: "Oxygen: Review daily routines", description: "Build native scan/briefing drafts and exact previews for activationId. Requires confirmed IANA timezone, both local times, source_ids and caps. Native Table sources quote zero; original ingestion sources need a current quote. Arms no schedule.", pack: "agents" }, mutates: true, spends: false },
54
+ cli: { path: ["onboarding", "activation", "routines-preview"], summary: "Save two routine draft configurations; review exact native source scope and caps. Arms no schedule." },
55
+ mcp: { name: "oxygen_onboarding_activation_routines_preview", title: "Oxygen: Review daily routines", description: "Save scan/briefing draft configuration and exact previews for activationId. Requires confirmed IANA timezone, both local times, source_ids and caps. Native Table sources quote zero; original ingestion sources need a current quote. Arms no schedule.", pack: "agents" }, mutates: true, spends: false },
56
56
  { id: "onboarding.activation.routine_activate", route: "/api/cli/onboarding/activation/{activationId}/routines/activate", method: "POST", parameters: ["activationId"], contract: GUIDED_ACTIVATION_ROUTINE_ACTIVATE_CONTRACT,
57
57
  cli: { path: ["onboarding", "activation", "routine-activate"], summary: "Activate one exact routine preview at its native revision. Requires approval or valid current creator-chat auto-approve authority." },
58
- mcp: { name: "oxygen_onboarding_activation_routine_activate", title: "Oxygen: Activate daily routine", description: "Activate one lead_scan or morning_briefing at exact native revision and preview_token. Requires approved=true or current auto-approve authority in the creator's bound private chat. Schedules are independent and remain capped; no automatic enrollment.", pack: "agents" }, mutates: true, spends: true },
58
+ mcp: { name: "oxygen_onboarding_activation_routine_activate", title: "Oxygen: Activate daily routine", description: "Activate lead_scan or morning_briefing at exact revision/preview_token. Requires approved=true or current creator auto-approve authority in the bound chat. Workspace shared; Personal creator-only. Separate capped schedules; no automatic enrollment.", pack: "agents" }, mutates: true, spends: true },
59
59
  ];
@@ -0,0 +1,30 @@
1
+ import { type ContractIssue } from "./schema.js";
2
+ import type { OperationSpec } from "./operations.js";
3
+ export declare const GUIDED_COMPANY_FILE_UPLOAD_CONTRACT: import("./schema.js").ContractObject<{
4
+ readonly revision: {
5
+ type: "integer";
6
+ description?: string;
7
+ minimum?: number;
8
+ maximum?: number;
9
+ "x-clamp"?: true;
10
+ };
11
+ readonly upload_id: import("./schema.js").StringProperty;
12
+ readonly sha256: import("./schema.js").StringProperty;
13
+ readonly size_bytes: {
14
+ type: "integer";
15
+ description?: string;
16
+ minimum?: number;
17
+ maximum?: number;
18
+ "x-clamp"?: true;
19
+ };
20
+ readonly file_name: import("./schema.js").StringProperty;
21
+ readonly content_type: {
22
+ type: "string";
23
+ enum: readonly ("application/pdf" | "text/markdown" | "text/plain")[];
24
+ description?: string;
25
+ };
26
+ readonly content_base64: import("./schema.js").StringProperty;
27
+ }, "content_base64" | "content_type" | "file_name" | "revision" | "sha256" | "size_bytes" | "upload_id">;
28
+ /** Raw wire values are never trimmed or renamed; the native owner verifies original SHA/bytes. */
29
+ export declare function guidedCompanyFileUploadInputIssues(args: Readonly<Record<string, unknown>>): readonly ContractIssue[];
30
+ export declare const GUIDED_COMPANY_FILE_UPLOAD_OPERATION: OperationSpec;
@@ -0,0 +1,53 @@
1
+ import { GUIDED_COMPANY_FILE_CONTENT_TYPES, GUIDED_COMPANY_FILE_LIMITS, guidedCompanyFileByteLimit } from "../guided-company-files.js";
2
+ import { enumOf, int, obj, str } from "./schema.js";
3
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
4
+ export const GUIDED_COMPANY_FILE_UPLOAD_CONTRACT = obj({
5
+ revision: int({ minimum: 1 }), upload_id: str({ pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" }),
6
+ sha256: str({ pattern: "^[0-9a-f]{64}$" }), size_bytes: int({ minimum: 1, maximum: GUIDED_COMPANY_FILE_LIMITS.pdfBytes }),
7
+ file_name: str({ minLength: 1, maxLength: GUIDED_COMPANY_FILE_LIMITS.nameBytes }),
8
+ content_type: enumOf(GUIDED_COMPANY_FILE_CONTENT_TYPES),
9
+ content_base64: str({ minLength: 4, maxLength: 4 * Math.ceil(GUIDED_COMPANY_FILE_LIMITS.pdfBytes / 3) }),
10
+ }, { required: ["revision", "upload_id", "sha256", "size_bytes", "file_name", "content_type", "content_base64"] });
11
+ /** Raw wire values are never trimmed or renamed; the native owner verifies original SHA/bytes. */
12
+ export function guidedCompanyFileUploadInputIssues(args) {
13
+ const issues = [];
14
+ const issue = (path, message) => issues.push({ path, message });
15
+ for (const key of ["activationId", "upload_id"])
16
+ if (typeof args[key] !== "string" || !uuid.test(args[key]))
17
+ issue(key, "Use a native UUID.");
18
+ const name = args.file_name;
19
+ if (typeof name !== "string" || !name.trim() || /[\\/\u0000-\u001f\u007f]/u.test(name) || new TextEncoder().encode(name).length > GUIDED_COMPANY_FILE_LIMITS.nameBytes)
20
+ issue("file_name", "Use a filename without paths or controls, at most 255 UTF-8 bytes.");
21
+ const type = args.content_type;
22
+ if (!GUIDED_COMPANY_FILE_CONTENT_TYPES.includes(type))
23
+ issue("content_type", "Use UTF-8 text, Markdown or PDF.");
24
+ else if (typeof args.size_bytes !== "number" || args.size_bytes > guidedCompanyFileByteLimit(type))
25
+ issue("size_bytes", "Use at most 1500000 text bytes or 5242880 PDF bytes.");
26
+ const encoded = args.content_base64;
27
+ if (typeof encoded !== "string" || !encoded || encoded.length % 4 !== 0 || /[^A-Za-z0-9+/=]/u.test(encoded) || !/^[^=]*={0,2}$/u.test(encoded))
28
+ issue("content_base64", "Use canonical non-empty base64 file bytes.");
29
+ else {
30
+ try {
31
+ const decoded = atob(encoded);
32
+ if (btoa(decoded) !== encoded)
33
+ issue("content_base64", "Use canonical base64 without altered padding bits.");
34
+ if (decoded.length !== args.size_bytes)
35
+ issue("size_bytes", "size_bytes must equal the original decoded byte length.");
36
+ }
37
+ catch {
38
+ issue("content_base64", "Use valid base64 file bytes.");
39
+ }
40
+ }
41
+ return issues;
42
+ }
43
+ export const GUIDED_COMPANY_FILE_UPLOAD_OPERATION = {
44
+ id: "onboarding.company_files.upload", route: "/api/cli/onboarding/activation/{activationId}/company-files", method: "POST",
45
+ parameters: ["activationId"], contract: GUIDED_COMPANY_FILE_UPLOAD_CONTRACT, inputCheck: guidedCompanyFileUploadInputIssues,
46
+ multipart: { contentField: "content_base64", nameField: "file_name", typeField: "content_type", fileField: "file",
47
+ fileMetadata: { sha256Field: "sha256", sizeBytesField: "size_bytes" } },
48
+ cli: { path: ["onboarding", "company-files", "upload"], summary: "Upload an optional company source file for your private DEV onboarding activation." },
49
+ mcp: { name: "oxygen_onboarding_company_files_upload", title: "Oxygen: Upload onboarding company file", pack: "agents",
50
+ description: "Current creator and revision only, genuine enabled DEV: upload UTF-8 text/Markdown up to 1500000 bytes or PDF up to 5242880 bytes. Supply original filename, MIME, size and SHA-256 plus stable upload_id UUID; exact retries reuse the native file. PDF retains original bytes with text unavailable. No account connection, parser, model or message send.",
51
+ annotations: { destructiveHint: false, openWorldHint: true, idempotentHint: true } },
52
+ mutates: true, spends: false,
53
+ };
@@ -0,0 +1,120 @@
1
+ import { type ContractIssue } from "./schema.js";
2
+ import type { OperationSpec } from "./operations.js";
3
+ /** Matches the actual native people-search and page safety ceilings. */
4
+ export declare const GUIDED_FIRST_SOURCING_LIMITS: {
5
+ readonly targetCount: 50000;
6
+ readonly maxPages: 1000;
7
+ };
8
+ export declare const GUIDED_FIRST_SOURCING_BOUNDS_CONTRACT: import("./schema.js").ContractObject<{
9
+ readonly targetCount: {
10
+ type: "integer";
11
+ description?: string;
12
+ minimum?: number;
13
+ maximum?: number;
14
+ "x-clamp"?: true;
15
+ };
16
+ readonly maxPages: {
17
+ type: "integer";
18
+ description?: string;
19
+ minimum?: number;
20
+ maximum?: number;
21
+ "x-clamp"?: true;
22
+ };
23
+ readonly maxCredits: {
24
+ readonly type: "number";
25
+ readonly description?: string;
26
+ readonly minimum?: number;
27
+ readonly maximum?: number;
28
+ readonly "x-clamp"?: true;
29
+ readonly exclusiveMinimum: 0;
30
+ };
31
+ }, "maxCredits" | "maxPages" | "targetCount">;
32
+ export declare const GUIDED_FIRST_SOURCING_PREVIEW_CONTRACT: import("./schema.js").ContractObject<{
33
+ revision: {
34
+ type: "integer";
35
+ description?: string;
36
+ minimum?: number;
37
+ maximum?: number;
38
+ "x-clamp"?: true;
39
+ };
40
+ bounds: import("./schema.js").ContractObject<{
41
+ readonly targetCount: {
42
+ type: "integer";
43
+ description?: string;
44
+ minimum?: number;
45
+ maximum?: number;
46
+ "x-clamp"?: true;
47
+ };
48
+ readonly maxPages: {
49
+ type: "integer";
50
+ description?: string;
51
+ minimum?: number;
52
+ maximum?: number;
53
+ "x-clamp"?: true;
54
+ };
55
+ readonly maxCredits: {
56
+ readonly type: "number";
57
+ readonly description?: string;
58
+ readonly minimum?: number;
59
+ readonly maximum?: number;
60
+ readonly "x-clamp"?: true;
61
+ readonly exclusiveMinimum: 0;
62
+ };
63
+ }, "maxCredits" | "maxPages" | "targetCount">;
64
+ }, "bounds" | "revision">;
65
+ export declare const GUIDED_FIRST_SOURCING_RUN_CONTRACT: import("./schema.js").ContractObject<{
66
+ readonly revision: {
67
+ type: "integer";
68
+ description?: string;
69
+ minimum?: number;
70
+ maximum?: number;
71
+ "x-clamp"?: true;
72
+ };
73
+ readonly bounds: import("./schema.js").ContractObject<{
74
+ readonly targetCount: {
75
+ type: "integer";
76
+ description?: string;
77
+ minimum?: number;
78
+ maximum?: number;
79
+ "x-clamp"?: true;
80
+ };
81
+ readonly maxPages: {
82
+ type: "integer";
83
+ description?: string;
84
+ minimum?: number;
85
+ maximum?: number;
86
+ "x-clamp"?: true;
87
+ };
88
+ readonly maxCredits: {
89
+ readonly type: "number";
90
+ readonly description?: string;
91
+ readonly minimum?: number;
92
+ readonly maximum?: number;
93
+ readonly "x-clamp"?: true;
94
+ readonly exclusiveMinimum: 0;
95
+ };
96
+ }, "maxCredits" | "maxPages" | "targetCount">;
97
+ readonly previewId: import("./schema.js").StringProperty;
98
+ readonly previewDigest: import("./schema.js").StringProperty;
99
+ readonly approved: import("./schema.js").BooleanProperty;
100
+ }, "approved" | "bounds" | "previewDigest" | "previewId" | "revision">;
101
+ export declare const GUIDED_FIRST_SOURCING_GET_CONTRACT: import("./schema.js").ContractObject<{
102
+ readonly revision: {
103
+ type: "integer";
104
+ description?: string;
105
+ minimum?: number;
106
+ maximum?: number;
107
+ "x-clamp"?: true;
108
+ };
109
+ readonly generation: {
110
+ type: "integer";
111
+ description?: string;
112
+ minimum?: number;
113
+ maximum?: number;
114
+ "x-clamp"?: true;
115
+ };
116
+ readonly timezoneHint: import("./schema.js").StringProperty;
117
+ }, never>;
118
+ export declare function guidedFirstSourcingGetInputIssues(args: Readonly<Record<string, unknown>>): readonly ContractIssue[];
119
+ export type GuidedFirstSourcingAction = "get" | "preview" | "run";
120
+ export declare const GUIDED_FIRST_SOURCING_OPERATIONS: readonly OperationSpec[];
@@ -0,0 +1,48 @@
1
+ import { bool, int, num, obj, str } from "./schema.js";
2
+ import { isValidTimeZone } from "../zoned-time.js";
3
+ /** Matches the actual native people-search and page safety ceilings. */
4
+ export const GUIDED_FIRST_SOURCING_LIMITS = { targetCount: 50000, maxPages: 1000 };
5
+ export const GUIDED_FIRST_SOURCING_BOUNDS_CONTRACT = obj({
6
+ targetCount: int({ minimum: 1, maximum: GUIDED_FIRST_SOURCING_LIMITS.targetCount }),
7
+ maxPages: int({ minimum: 1, maximum: GUIDED_FIRST_SOURCING_LIMITS.maxPages }),
8
+ maxCredits: { ...num(), exclusiveMinimum: 0 },
9
+ }, { required: ["targetCount", "maxPages", "maxCredits"] });
10
+ const request = { revision: int({ minimum: 1 }), bounds: GUIDED_FIRST_SOURCING_BOUNDS_CONTRACT };
11
+ export const GUIDED_FIRST_SOURCING_PREVIEW_CONTRACT = obj(request, { required: ["revision", "bounds"] });
12
+ export const GUIDED_FIRST_SOURCING_RUN_CONTRACT = obj({ ...request,
13
+ previewId: str({ minLength: 36, maxLength: 36, pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" }),
14
+ previewDigest: str({ minLength: 64, maxLength: 64, pattern: "^[a-f0-9]{64}$" }), approved: bool(),
15
+ }, { required: ["revision", "bounds", "previewId", "previewDigest", "approved"] });
16
+ export const GUIDED_FIRST_SOURCING_GET_CONTRACT = obj({
17
+ revision: int({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
18
+ generation: int({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }),
19
+ timezoneHint: str({ minLength: 1, maxLength: 80, description: "Provisional saved/browser IANA hint for current nightly defaults; never confirms a schedule." }),
20
+ });
21
+ export function guidedFirstSourcingGetInputIssues(args) {
22
+ const fields = ["revision", "generation", "timezoneHint"];
23
+ if (!fields.some(field => args[field] !== undefined))
24
+ return [];
25
+ const issues = fields.filter(field => args[field] === undefined).map(path => ({ path,
26
+ message: "Supply revision, generation and timezoneHint together to read current routine defaults." }));
27
+ if (typeof args.timezoneHint !== "string" || args.timezoneHint !== args.timezoneHint.trim() || !isValidTimeZone(args.timezoneHint)) {
28
+ issues.push({ path: "timezoneHint", message: "Use a valid customer/browser IANA timezone suggestion; no schedule is confirmed." });
29
+ }
30
+ return issues;
31
+ }
32
+ const base = "/api/cli/onboarding/activation/{activationId}/first-sourcing";
33
+ export const GUIDED_FIRST_SOURCING_OPERATIONS = [
34
+ { id: "onboarding.first_sourcing.get", route: base, method: "GET", contract: GUIDED_FIRST_SOURCING_GET_CONTRACT, inputCheck: guidedFirstSourcingGetInputIssues, parameters: ["activationId"],
35
+ cli: { path: ["onboarding", "first-sourcing", "get"], summary: "Read first sourcing; optionally quote current nightly defaults with revision, generation and timezoneHint." },
36
+ mcp: { name: "oxygen_onboarding_first_sourcing_get", title: "Oxygen: Read first sourcing", pack: "agents",
37
+ description: "Read the current creator-private DEV source, native ingestion receipt and actual settled continuation. No sourcing provider call or new sourcing; historical approval cannot authorize another pull. Verifies native billing and may reconcile an earned grant. Optional revision, generation and provisional IANA timezoneHint together return current native nightly defaults after the source transaction; no draft, schedule confirmation or approval.", annotations: { openWorldHint: true } }, mutates: true, spends: false },
38
+ { id: "onboarding.first_sourcing.preview", route: `${base}/preview`, method: "POST", parameters: ["activationId"], contract: GUIDED_FIRST_SOURCING_PREVIEW_CONTRACT,
39
+ cli: { path: ["onboarding", "first-sourcing", "preview"], summary: "Review a bounded native first pull into First leads; no provider search." },
40
+ mcp: { name: "oxygen_onboarding_first_sourcing_preview", title: "Oxygen: Preview first sourcing", pack: "agents",
41
+ description: "Current creator and revision, confirmed company, saved audience and eligible trial/paid billing required. Bounds contain targetCount, maxPages and maxCredits. Creates/reuses the native First leads Table and persists a managed source quote. Returns previewId/digest; no provider search, model or outreach.",
42
+ annotations: { openWorldHint: true, destructiveHint: false } }, mutates: true, spends: false },
43
+ { id: "onboarding.first_sourcing.run", route: `${base}/run`, method: "POST", parameters: ["activationId"], contract: GUIDED_FIRST_SOURCING_RUN_CONTRACT,
44
+ cli: { path: ["onboarding", "first-sourcing", "run"], summary: "Approve the exact stored preview and caps; enqueue or recover the same native first-source run." },
45
+ mcp: { name: "oxygen_onboarding_first_sourcing_run", title: "Oxygen: Run first sourcing", pack: "agents",
46
+ description: "approved=true plus stored previewId/previewDigest, current revision and identical bounds authorize the reviewed managed people source into the activation's First leads Table. Current native admission, billing, quote and worker fences apply. Retry/Back/Restart with the same actual source recovers its native run. No model, enrollment or outreach.",
47
+ annotations: { openWorldHint: true, destructiveHint: false, idempotentHint: true } }, mutates: true, spends: true },
48
+ ];