@oxygen-agent/cli 1.888.6 → 1.893.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/command-manifest.js +11 -1
- package/dist/index.js +276 -16
- package/node_modules/@oxygen/shared/dist/billing.d.ts +3 -3
- package/node_modules/@oxygen/shared/dist/billing.js +2 -2
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +3 -3
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +6 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/index.js +1 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +255 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +238 -0
- package/node_modules/@oxygen/shared/dist/knowledge-constants.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/knowledge-constants.js +19 -0
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +23 -6
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +438 -29
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/command-manifest.js
CHANGED
|
@@ -39,7 +39,17 @@ const MUTATING_VERBS = new Set([
|
|
|
39
39
|
// Exact leaves, not the `billing` prefix — `orgs billing-owners` is a read.
|
|
40
40
|
// Both move a workspace's billing owner, so an agent must treat them as writes.
|
|
41
41
|
"billing-link", "billing-unlink",
|
|
42
|
-
"bind",
|
|
42
|
+
"bind",
|
|
43
|
+
// `knowledge bootstrap --live` researches the workspace's own company and
|
|
44
|
+
// WRITES the typed company profile plus a cited wiki page, on paid provider
|
|
45
|
+
// and AI calls. Its bare form previews (see PREVIEW_BY_DEFAULT via --live),
|
|
46
|
+
// which the rule above already covers: an approval-gated command is a
|
|
47
|
+
// mutation. `knowledge bootstrap status` keeps the leaf verb `status` and
|
|
48
|
+
// stays a read.
|
|
49
|
+
"bootstrap",
|
|
50
|
+
// `support chat` starts or continues a customer-authored Plain Thread. The
|
|
51
|
+
// conversational noun is still a real external write.
|
|
52
|
+
"buy", "call", "cancel", "chat", "chat-action", "claim", "clear",
|
|
43
53
|
"comment", "configure", "connect", "create", "decide", "delete", "delist", "disable",
|
|
44
54
|
"disconnect", "dispatch", "done", "draft", "duplicate", "edit", "emit", "enable", "enroll",
|
|
45
55
|
// Exact leaf, not a `field` prefix: `support admin field-set` writes a Plain
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
9
9
|
import { Command, CommanderError, Option } from "commander";
|
|
10
10
|
import { applyOxygenHelp } from "./help.js";
|
|
11
11
|
import { buildCommandManifest, getCommandManifestEntry, searchCommandManifest, suggestCommandNames, } from "./command-manifest.js";
|
|
12
|
-
import { AGENCY_DIRECTORY_REGIONS, AGENCY_DIRECTORY_SERVICES, COLLAB_GATE_KINDS, COLLAB_GATE_PROSE, COLLAB_SUBJECT_KINDS, COLLAB_SUBJECT_KINDS_PROSE, COLLAB_SUBJECT_LABELS, COLLAB_SUBJECT_PROSE, GATE_KIND_SUBJECTS, describeWorkflowStatusChange, formatCellForDisplay, formatPublicBudgetScopes, SUBJECT_PATH_FORMS_PROSE, formatSubjectPath, exitCodeForOxygenError, parseSubjectPath, parseSubjectRef, parseWorkflowStatusChange, isVersionGreater, isVersionLess, MAX_MCP_TOOL_NAME_LENGTH, OXYGEN_CAPABILITY_ROUTES, OXYGEN_VERSION, OxygenError, getCapabilityRouteMatch, inferUserCapabilityRoute, parseKnowledgePageMarkdown, PLAN_LIMITS, serializeCapabilityRoute, sleep, success, TABLE_IMPORT_ROW_LIMIT, TAG_KINDS_PROSE, toFailure, workflowMcpToolName, } from "@oxygen/shared";
|
|
12
|
+
import { AGENCY_DIRECTORY_REGIONS, AGENCY_DIRECTORY_SERVICES, COLLAB_GATE_KINDS, COLLAB_GATE_PROSE, COLLAB_SUBJECT_KINDS, COLLAB_SUBJECT_KINDS_PROSE, COLLAB_SUBJECT_LABELS, COLLAB_SUBJECT_PROSE, GATE_KIND_SUBJECTS, describeWorkflowStatusChange, formatCellForDisplay, formatPublicBudgetScopes, SUBJECT_PATH_FORMS_PROSE, formatSubjectPath, exitCodeForOxygenError, parseSubjectPath, parseSubjectRef, parseWorkflowStatusChange, isVersionGreater, isVersionLess, KNOWLEDGE_BOOTSTRAP_MAX_CREDITS, MAX_MCP_TOOL_NAME_LENGTH, OXYGEN_CAPABILITY_ROUTES, OXYGEN_VERSION, OxygenError, getCapabilityRouteMatch, inferUserCapabilityRoute, parseKnowledgePageMarkdown, PLAN_LIMITS, serializeCapabilityRoute, sleep, success, TABLE_IMPORT_ROW_LIMIT, TAG_KINDS_PROSE, toFailure, workflowMcpToolName, } from "@oxygen/shared";
|
|
13
13
|
import { TAG_COLORS } from "@oxygen/shared/select-options";
|
|
14
14
|
import { inferImportColumnLabels, inferRowsFileFormat, normalizeImportColumnKey, normalizeRowsForNewTable, normalizeRowsFormat, parseRowsFileBuffer, parseXlsxWorkbookBuffer, } from "@oxygen/shared/file-import";
|
|
15
15
|
import { MAILBOX_IMPORT_FILE_MAX_BYTES as SHARED_MAILBOX_IMPORT_FILE_MAX_BYTES, MAILBOX_IMPORT_ROW_LIMIT as SHARED_MAILBOX_IMPORT_ROW_LIMIT, normalizeMailboxImportFile as normalizeSharedMailboxImportFile, normalizeMailboxImportVendor as normalizeSharedMailboxImportVendor, normalizeMailboxWorkbookRows, parseMailboxImportText, summarizeMailboxImportValidation as summarizeSharedMailboxImportValidation, } from "@oxygen/shared/mailbox-import";
|
|
@@ -2867,9 +2867,40 @@ export function createProgram() {
|
|
|
2867
2867
|
}));
|
|
2868
2868
|
program
|
|
2869
2869
|
.command("support")
|
|
2870
|
-
.description("Open and track Plain support conversations for the active OXYGEN organization. In the app, the support chat is the widget in the bottom-right corner of every page. Retry is the only recovery control; it returns after each failed connection attempt and never creates a Thread.
|
|
2870
|
+
.description("Open and track Plain support conversations for the active OXYGEN organization. In the app, the support chat is the widget in the bottom-right corner of every page. Retry is the only recovery control; it returns after each failed connection attempt and never creates a Thread. `support chat` starts or continues a zero-credit conversation without making you write a ticket title. Guide: https://oxygen-agent.com/docs/surfaces/support.")
|
|
2871
|
+
.addCommand(new Command("chat")
|
|
2872
|
+
.description("Start a Plain Chat conversation from one message, or pass an existing Thread ID to continue it. New conversations derive Plain's internal title from the message; you never have to file each message as a separate titled ticket.")
|
|
2873
|
+
.argument("[threadId]", "Existing Plain Thread ID (th_...) to continue. Omit to start a new conversation.")
|
|
2874
|
+
.requiredOption("-m, --message <message>", "Your message to OXYGEN support.")
|
|
2875
|
+
.option("--severity <severity>", "New conversation only: low | normal | high. Defaults to normal.")
|
|
2876
|
+
.option("--category <category>", "New conversation only: question, bug, feature_request, billing, security_data, configuration, agency_directory, or other. Defaults to question.")
|
|
2877
|
+
.option("--json", "Print a JSON envelope.")
|
|
2878
|
+
.addHelpText("after", `
|
|
2879
|
+
Examples:
|
|
2880
|
+
oxygen support chat --message "The dedicated IP is not being assigned" --category bug
|
|
2881
|
+
oxygen support chat th_... --message "Here is the additional evidence"
|
|
2882
|
+
`)
|
|
2883
|
+
.action(async (threadId, options) => {
|
|
2884
|
+
const message = readOption(options.message);
|
|
2885
|
+
if (threadId) {
|
|
2886
|
+
await handleAsyncAction("support chat", options, () => requestOxygen(`/api/cli/support/tickets/${encodeURIComponent(threadId)}/messages`, {
|
|
2887
|
+
method: "POST",
|
|
2888
|
+
body: { body: message },
|
|
2889
|
+
}));
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
const severity = readOption(options.severity);
|
|
2893
|
+
await handleAsyncAction("support chat", options, () => requestOxygen("/api/cli/support/tickets", {
|
|
2894
|
+
method: "POST",
|
|
2895
|
+
body: {
|
|
2896
|
+
message,
|
|
2897
|
+
category: readOption(options.category) ?? "question",
|
|
2898
|
+
...(severity ? { severity } : {}),
|
|
2899
|
+
},
|
|
2900
|
+
}));
|
|
2901
|
+
}))
|
|
2871
2902
|
.addCommand(new Command("file")
|
|
2872
|
-
.description("Create a
|
|
2903
|
+
.description("Create a structured, titled Plain support Thread immediately. Prefer `support chat` for an ordinary conversation; use this zero-credit filing path when explicit subject, operation, error, run, or table metadata helps triage. Check `support list --status open` first because a new filing can create a second Thread.")
|
|
2873
2904
|
.requiredOption("--subject <subject>", "One-line summary of the problem.")
|
|
2874
2905
|
.option("--body <body>", "What you were doing, what happened, and what you tried.")
|
|
2875
2906
|
.option("--severity <severity>", "low | normal | high. Defaults to normal.")
|
|
@@ -3225,7 +3256,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
3225
3256
|
}), { hidden: true }));
|
|
3226
3257
|
program
|
|
3227
3258
|
.command("feedback")
|
|
3228
|
-
.description("Send feedback or a bug report to the OXYGEN team. This is the same immediate, zero-credit canonical Plain
|
|
3259
|
+
.description("Send feedback or a bug report to the OXYGEN team. This is the same immediate, zero-credit canonical Plain conversation write as `support chat`; transcripts are never attached unless explicitly requested.")
|
|
3229
3260
|
.option("-m, --message <message>", "Your feedback or bug report.")
|
|
3230
3261
|
.option("--severity <severity>", "low | normal | high. Defaults to normal.")
|
|
3231
3262
|
.option("--category <category>", "Optional category label. Defaults to 'feedback'.")
|
|
@@ -6023,6 +6054,51 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
6023
6054
|
body: { id: assetId },
|
|
6024
6055
|
}));
|
|
6025
6056
|
})));
|
|
6057
|
+
// `knowledge bootstrap` is declared here, not inline in the group chain,
|
|
6058
|
+
// because it is a command that both ACTS and owns a subcommand, and both
|
|
6059
|
+
// levels take --json. Commander resolves a repeated option to the outer
|
|
6060
|
+
// command, so `knowledge bootstrap status --json` sets the flag on THIS
|
|
6061
|
+
// command, not on `status` — the same collision the `commands` group above
|
|
6062
|
+
// handles. The named reference lets `status` read the parent's flag back.
|
|
6063
|
+
const knowledgeBootstrapCommand = new Command("bootstrap")
|
|
6064
|
+
.description(`Research THIS workspace's own company from its domain and fill the typed company profile plus one cited wiki page — the empty-Knowledge-Graph fix, so a founder never has to type their own positioning back at us. Two authorization paths, and they are NOT the same: running this command manually is authorized by --live (plus --max-credits), like every other paid Oxygen action; the identical pass that runs ONCE automatically after signup is authorized by the consent recorded at signup instead, and is capped (<= ${KNOWLEDGE_BOOTSTRAP_MAX_CREDITS} credits), journalled (a marker, a system run, and immutable page sources), kill-switched fleet-wide, and reversible. A bare call previews: it resolves the domain, plans the reads, estimates the cost, and spends nothing. Once per workspace ever — any existing marker (completed, partial, skipped, failed) blocks a second pass until --force clears it.`)
|
|
6065
|
+
.option("--domain <domain>", "Company apex domain to research. Defaults to the workspace domain resolved at signup from the org's Clerk metadata or the creator's email; a personal-email signup has none, and a preview says so instead of guessing.")
|
|
6066
|
+
.option("--linkedin <url>", "Company LinkedIn URL, as a second identity signal alongside the domain.")
|
|
6067
|
+
.option("--live", "Authorize this manual run to spend and write. Requires --max-credits. Without it the command previews and nothing is billed.")
|
|
6068
|
+
.option("--approved", "Same as --live. Accepted because the global help footer names --approved as the way to authorize a credit-spending command.")
|
|
6069
|
+
.option("--max-credits <credits>", `Hard credit ceiling for the whole pass. Required with --live, and clamped server-side to ${KNOWLEDGE_BOOTSTRAP_MAX_CREDITS}.`)
|
|
6070
|
+
.option("--force", "Clear an existing once-ever marker first and re-arm the pass. This is the one deliberate way to research the same workspace twice — pair it with --live to actually spend again.")
|
|
6071
|
+
.option("--json", "Print a JSON envelope.")
|
|
6072
|
+
.action(async (options) => {
|
|
6073
|
+
await handleAsyncAction("knowledge bootstrap", options, () => requestOxygen("/api/cli/knowledge/bootstrap", {
|
|
6074
|
+
method: "POST",
|
|
6075
|
+
body: {
|
|
6076
|
+
mode: options.live || options.approved ? "live" : "dry_run",
|
|
6077
|
+
...(readOption(options.domain) ? { domain: readOption(options.domain) } : {}),
|
|
6078
|
+
...(readOption(options.linkedin) ? { linkedin_url: readOption(options.linkedin) } : {}),
|
|
6079
|
+
...(readPositiveNumber(options.maxCredits) !== undefined
|
|
6080
|
+
? { max_credits: readPositiveNumber(options.maxCredits) }
|
|
6081
|
+
: {}),
|
|
6082
|
+
...(options.force ? { force: true } : {}),
|
|
6083
|
+
},
|
|
6084
|
+
}));
|
|
6085
|
+
})
|
|
6086
|
+
.addCommand(new Command("status")
|
|
6087
|
+
.description("Report the once-ever bootstrap marker for this workspace: status (queued, running, completed, partial, skipped, failed, or never attempted), when it ran, its system run id, the credits it actually spent, the domain researched, the reason it stopped, and the pages it wrote. Free, reads only, and the honest way to find out whether the automatic pass already happened before you spend on a manual one.")
|
|
6088
|
+
.option("--json", "Print a JSON envelope.")
|
|
6089
|
+
.action(async (options) => {
|
|
6090
|
+
// Commander hands a repeated --json to the outer command, so read the
|
|
6091
|
+
// parent's flag back before deciding the output shape. Without this a
|
|
6092
|
+
// machine caller asking for an envelope silently gets bare JSON.
|
|
6093
|
+
const outputOptions = {
|
|
6094
|
+
...options,
|
|
6095
|
+
json: options.json || Boolean(knowledgeBootstrapCommand.opts().json),
|
|
6096
|
+
};
|
|
6097
|
+
await handleAsyncAction("knowledge bootstrap status", outputOptions, () => requestOxygen("/api/cli/knowledge/bootstrap", {
|
|
6098
|
+
method: "POST",
|
|
6099
|
+
body: { status: true },
|
|
6100
|
+
}));
|
|
6101
|
+
}));
|
|
6026
6102
|
program
|
|
6027
6103
|
.command("knowledge")
|
|
6028
6104
|
.description("Company knowledge wiki: slug-addressed pages, a [[wikilink]] graph, and a decision log.")
|
|
@@ -6182,7 +6258,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
6182
6258
|
}));
|
|
6183
6259
|
}))
|
|
6184
6260
|
.addCommand(new Command("lint")
|
|
6185
|
-
.description("Structural wiki health report: orphans, dead-ends, unresolved links, missing canonicals, untagged pages, duplicate titles, oversized hubs, unfilled seed stubs, decisions missing a reversal condition, stale
|
|
6261
|
+
.description("Structural wiki health report: orphans (unfilled seed stubs excluded), dead-ends, unresolved links, contradictions (disputed pages, conflicting trust tags, competing live-copy pages), missing canonicals, untagged pages, duplicate titles, oversized hubs, unfilled seed stubs (total plus the most-linked-to), researchable stubs, decisions missing a reversal condition, stale pages (untouched or superseded by newer sources), oversized bodies, and aged proposals.")
|
|
6186
6262
|
.option("--json", "Print a JSON envelope.")
|
|
6187
6263
|
.action(async (options) => {
|
|
6188
6264
|
await handleAsyncAction("knowledge lint", options, () => requestOxygen("/api/cli/knowledge/lint"));
|
|
@@ -6299,7 +6375,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
6299
6375
|
}));
|
|
6300
6376
|
})))
|
|
6301
6377
|
.addCommand(new Command("seed")
|
|
6302
|
-
.description("Seed or upgrade the knowledge wiki scaffold: on first run creates the starter page set — a start-here guide, a conventions schema page, and stub hubs (icp, positioning, offers, competitors, playbooks, campaign-learnings, decisions), all [[wikilinked]]. Idempotent; never overwrites your edits.")
|
|
6378
|
+
.description("Seed or upgrade the knowledge wiki scaffold: on first run creates the starter page set — a start-here guide, a conventions schema page, and 19 draft stub hubs (icp, positioning, offers, competitors, playbooks, campaign-learnings, decisions, voice, brand, messaging, objections, playbook-outbound, playbook-inbound, playbook-nurture, qualification, personas, customers, research, metrics), all [[wikilinked]]. Idempotent; never overwrites your edits.")
|
|
6303
6379
|
.option("--json", "Print a JSON envelope.")
|
|
6304
6380
|
.action(async (options) => {
|
|
6305
6381
|
await handleAsyncAction("knowledge seed", options, () => requestOxygen("/api/cli/knowledge/seed", {
|
|
@@ -6307,6 +6383,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
6307
6383
|
body: {},
|
|
6308
6384
|
}));
|
|
6309
6385
|
}))
|
|
6386
|
+
.addCommand(knowledgeBootstrapCommand)
|
|
6310
6387
|
.addCommand(new Command("synthesize")
|
|
6311
6388
|
.description("Synthesize knowledge from workspace evidence (one low-cost AI call): campaign learnings from auto-filed outreach outcomes, or a voice guide from sent messages. Previews by default; --approved writes the result.")
|
|
6312
6389
|
.option("--kind <kind>", "What to synthesize: campaign_learnings (default) or voice.", "campaign_learnings")
|
|
@@ -9294,7 +9371,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
9294
9371
|
.requiredOption("--instructions <text>", "Workspace Agent instructions for this version.")
|
|
9295
9372
|
.option("--model-policy-json <json>", AGENT_MODEL_POLICY_HELP)
|
|
9296
9373
|
.option("--tool-policy-json <json>", AGENT_TOOL_POLICY_HELP)
|
|
9297
|
-
.option("--context-policy-json <json>", "Context policy JSON: on_demand
|
|
9374
|
+
.option("--context-policy-json <json>", "Context policy JSON: on_demand, prefetch, or grounded (seeds the run from the workspace knowledge wiki); optional resolve_args and file_ids.")
|
|
9298
9375
|
.option("--skills <csv>", "Comma-separated product skill slugs snapshotted into this immutable version.")
|
|
9299
9376
|
.option("--sandbox-policy-json <json>", "Sandbox policy JSON.")
|
|
9300
9377
|
.option("--limits-json <json>", "Optional limits JSON; platform hard ceilings still apply.")
|
|
@@ -9323,7 +9400,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
9323
9400
|
.option("--description <text>", "Optional one-line description of the agent's job.")
|
|
9324
9401
|
.option("--model-policy-json <json>", AGENT_MODEL_POLICY_HELP)
|
|
9325
9402
|
.option("--tool-policy-json <json>", AGENT_TOOL_POLICY_HELP)
|
|
9326
|
-
.option("--context-policy-json <json>", "Context policy JSON: on_demand
|
|
9403
|
+
.option("--context-policy-json <json>", "Context policy JSON: on_demand, prefetch, or grounded (seeds the run from the workspace knowledge wiki); optional resolve_args and file_ids.")
|
|
9327
9404
|
.option("--skills <csv>", "Comma-separated product skill slugs snapshotted into this immutable version.")
|
|
9328
9405
|
.option("--sandbox-policy-json <json>", "Sandbox policy JSON.")
|
|
9329
9406
|
.option("--limits-json <json>", "Optional limits JSON; platform hard ceilings still apply.")
|
|
@@ -12116,17 +12193,21 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
12116
12193
|
program.addCommand(new Command("sequences")
|
|
12117
12194
|
.description("Multichannel outreach sequences: one enrollment per lead can span LinkedIn, email, WhatsApp, human call tasks, and connected-CRM tasks. LinkedIn steps dispatch natively (rate-limited, credit-capped); email steps send natively or place/move/stop the lead in a bound Instantly campaign (BYOK); crm_task can exact-match/upsert/link CRM records and create a mapped external follow-up (HubSpot first), with replay-safe per-operation receipts. Cross-channel reply-stop is intrinsic.")
|
|
12118
12195
|
.addCommand(new Command("list")
|
|
12119
|
-
.description("List sequences with status
|
|
12196
|
+
.description("List sequences with persisted launch status kept separate from current operational state. Fleet counts report marked-active versus operationally-live (active with nonterminal work), and rows explain flowing, paced, blocked, degraded, drained, or empty work without SQL. --stats joins each row's LIFETIME funnel.")
|
|
12120
12197
|
.option("--status <status>", "Filter by status: draft, active, paused, or archived.")
|
|
12198
|
+
.addOption(new Option("--operational-state <state>", "Filter by current work state; fleet counts remain unfiltered: empty, drained, blocked, degraded, flowing, or paced.").choices(["empty", "drained", "blocked", "degraded", "flowing", "paced"]))
|
|
12121
12199
|
.option("--tag <tag>", "Only sequences carrying this workspace tag (see `oxygen tags list`).")
|
|
12122
12200
|
.option("--stats", "Attach lifetime stats per sequence (a stats pass per row — slower on large workspaces).")
|
|
12123
12201
|
.option("--json", "Print a JSON envelope.")
|
|
12124
12202
|
.action(async (options) => {
|
|
12125
|
-
await
|
|
12203
|
+
await handleSequenceReadAction("sequences list", options, () => {
|
|
12126
12204
|
const params = new URLSearchParams();
|
|
12127
12205
|
const status = readOption(options.status);
|
|
12128
12206
|
if (status)
|
|
12129
12207
|
params.set("status", status);
|
|
12208
|
+
const operationalState = readOption(options.operationalState);
|
|
12209
|
+
if (operationalState)
|
|
12210
|
+
params.set("operational_state", operationalState);
|
|
12130
12211
|
const tag = readOption(options.tag);
|
|
12131
12212
|
if (tag)
|
|
12132
12213
|
params.set("tag", tag);
|
|
@@ -12134,7 +12215,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
12134
12215
|
params.set("include", "stats");
|
|
12135
12216
|
const suffix = params.toString();
|
|
12136
12217
|
return requestOxygen(`/api/cli/sequences${suffix ? `?${suffix}` : ""}`);
|
|
12137
|
-
});
|
|
12218
|
+
}, formatSequenceListHealth);
|
|
12138
12219
|
}))
|
|
12139
12220
|
.addCommand(new Command("send")
|
|
12140
12221
|
.description("Initiate one net-new LinkedIn message through a one-recipient, one-step hosted Sequence. First call without a sequence id: pass --recipient, --text/--text-file, and --sender to create the inert draft and preview (no message, no credits). After showing that preview, re-run with the returned sequence id plus --approved --max-credits N. Existing-thread replies stay in `oxygen inbox send`.")
|
|
@@ -12350,14 +12431,14 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
12350
12431
|
});
|
|
12351
12432
|
})))
|
|
12352
12433
|
.addCommand(new Command("analytics")
|
|
12353
|
-
.description("Show
|
|
12434
|
+
.description("Show selected-window sequencer analytics with persisted status kept separate from operational state, fleet marked-active versus operationally-live counts, current open work, sent/terminal-failed/deferral totals, failure classes, and action-kind breakdowns. Also includes per-sequence funnels and native email attribution under analytics.emailAttribution: byMailbox, byDomain, and explicit unattributed facts. With --sequence, analytics.senderPerformance groups channel-conditional counts and rates by sending person; analytics.messagePerformance reports each copy-bearing deterministic step variant, target/actual distribution, contacted recipients, and type-specific accepted/reply/positive outcomes (absent for note-less connect-only campaigns). Message-level reply outcomes require preserved step/variant provenance, so older unattributed replies remain in campaign totals without being guessed into a message row. Provider-owned campaigns appear only after their telemetry is normalized into Oxygen's native Sequence ledgers. Also reports whether the reply → CRM automation is armed, with a link to the workflow.")
|
|
12354
12435
|
.option("--range <range>", "Preset range: 7d, 14d, 28d, 30d, 90d, 180d, 365d, or all. Defaults to 14d. `all` starts at the oldest campaign in scope.")
|
|
12355
12436
|
.option("--from <date>", "Custom start date (YYYY-MM-DD). Windows up to five years are accepted.")
|
|
12356
12437
|
.option("--to <date>", "Custom end date (YYYY-MM-DD).")
|
|
12357
12438
|
.option("--sequence <id-or-slug>", "Limit analytics to one sequence.")
|
|
12358
12439
|
.option("--json", "Print a JSON envelope.")
|
|
12359
12440
|
.action(async (options) => {
|
|
12360
|
-
await
|
|
12441
|
+
await handleSequenceReadAction("sequences analytics", options, () => {
|
|
12361
12442
|
const params = new URLSearchParams();
|
|
12362
12443
|
const range = readOption(options.range);
|
|
12363
12444
|
const from = readOption(options.from);
|
|
@@ -12373,7 +12454,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
12373
12454
|
params.set("sequence", sequence);
|
|
12374
12455
|
const qs = params.toString();
|
|
12375
12456
|
return requestOxygen(`/api/cli/sequences/analytics${qs ? `?${qs}` : ""}`);
|
|
12376
|
-
});
|
|
12457
|
+
}, formatSequenceAnalyticsHealth);
|
|
12377
12458
|
}))
|
|
12378
12459
|
.addCommand(new Command("create")
|
|
12379
12460
|
.description("Create a draft multichannel sequence from a steps JSON file. Supports LinkedIn, email, WhatsApp, human call_task, and connected-CRM crm_task journeys. Assign LinkedIn senders with --senders (optional at create — a draft can sit senderless, but enroll/start require at least one for LinkedIn journeys); bind an Instantly email track with --email-*. Install/read the oxygen-sequencer skill for complete mapped CRM task examples.")
|
|
@@ -12822,11 +12903,11 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
12822
12903
|
});
|
|
12823
12904
|
}))
|
|
12824
12905
|
.addCommand(new Command("stats")
|
|
12825
|
-
.description("Show
|
|
12906
|
+
.description("Show one sequence's persisted launch status and current operational state separately, including reasons, open work, lifetime sent/terminal-failed/deferral counts, and the funnel.")
|
|
12826
12907
|
.argument("<sequence>", "Sequence id or slug.")
|
|
12827
12908
|
.option("--json", "Print a JSON envelope.")
|
|
12828
12909
|
.action(async (sequence, options) => {
|
|
12829
|
-
await
|
|
12910
|
+
await handleSequenceReadAction("sequences stats", options, () => requestOxygen(`/api/cli/sequences/${encodeURIComponent(sequence)}/stats`), formatSequenceStatsHealth);
|
|
12830
12911
|
}))
|
|
12831
12912
|
.addCommand(new Command("esp")
|
|
12832
12913
|
.description("ESP breakdown of this sequence's email sending scope: which provider each enrolled lead's mail routes through (google/microsoft, plus named gateways like Proofpoint/Mimecast and non-matchable hosts), folded against your sending mailbox pool. Shows how many leads the CURRENT --esp-matching mode would defer before you turn it on. Resolves domains from MX + SPF (0 credits, DNS only) and caches the result, warming the send path. Preview before flipping strict/prefer.")
|
|
@@ -21083,6 +21164,185 @@ function renderTextTable(headers, rows) {
|
|
|
21083
21164
|
const separator = ` ${widths.map((width) => "-".repeat(width)).join(" ")}`;
|
|
21084
21165
|
return [renderRow(headers), separator, ...rows.map(renderRow)];
|
|
21085
21166
|
}
|
|
21167
|
+
// Sequence state is derived only by the shared API. These helpers render those
|
|
21168
|
+
// facts; they never infer flowing/blocked/live client-side. JSON remains the
|
|
21169
|
+
// untouched API data inside the standard CLI envelope.
|
|
21170
|
+
const SEQUENCE_OPERATIONAL_STATE_ORDER = ["empty", "drained", "blocked", "degraded", "flowing", "paced"];
|
|
21171
|
+
async function handleSequenceReadAction(command, options, action, render) {
|
|
21172
|
+
try {
|
|
21173
|
+
const data = await action();
|
|
21174
|
+
if (options.json) {
|
|
21175
|
+
writeJson(success(command, data));
|
|
21176
|
+
return;
|
|
21177
|
+
}
|
|
21178
|
+
process.stdout.write(isRecord(data) ? render(data) : `${JSON.stringify(data, null, 2)}\n`);
|
|
21179
|
+
}
|
|
21180
|
+
catch (error) {
|
|
21181
|
+
emitCliFailure(command, error);
|
|
21182
|
+
}
|
|
21183
|
+
}
|
|
21184
|
+
function formatSequenceListHealth(data) {
|
|
21185
|
+
const sequences = Array.isArray(data.sequences) ? data.sequences.filter(isRecord) : [];
|
|
21186
|
+
const lines = [...formatSequenceFleetSummary(data.fleet), ""];
|
|
21187
|
+
if (sequences.length === 0) {
|
|
21188
|
+
lines.push("No sequences match these filters.");
|
|
21189
|
+
}
|
|
21190
|
+
else {
|
|
21191
|
+
lines.push(...renderTextTable(["SEQUENCE", "PERSISTED", "OPERATIONAL", "OPEN WORK", "SENT / FAILED", "REASONS"], sequences.map((sequence) => sequenceHealthRow(sequence))));
|
|
21192
|
+
lines.push("", ...sequenceInspectionLinks(sequences));
|
|
21193
|
+
}
|
|
21194
|
+
const listLink = stringValue(data.web_url) ?? stringValue(data.deepLink);
|
|
21195
|
+
if (listLink)
|
|
21196
|
+
lines.push(listLink);
|
|
21197
|
+
return `${lines.join("\n")}\n`;
|
|
21198
|
+
}
|
|
21199
|
+
function formatSequenceStatsHealth(data) {
|
|
21200
|
+
const stats = recordValue(data.stats);
|
|
21201
|
+
const operational = recordValue(stats.operational);
|
|
21202
|
+
const lines = [
|
|
21203
|
+
stringValue(data.name) ?? stringValue(data.sequence_id) ?? "Sequence",
|
|
21204
|
+
`Persisted status: ${stringValue(data.status) ?? "unknown"}`,
|
|
21205
|
+
`Operational state: ${stringValue(operational.operationalState) ?? "unknown (legacy response)"}`,
|
|
21206
|
+
`Open work: ${formatSequenceOpenWork(operational.openWork)}`,
|
|
21207
|
+
`Reasons: ${formatSequenceReasons(operational)}`,
|
|
21208
|
+
`${formatFailureWindow(operational.failureAnalytics)} outcomes: ${formatSequenceFailureTotals(operational.failureAnalytics)}`,
|
|
21209
|
+
];
|
|
21210
|
+
const failureMix = formatCountRecord(recordValue(recordValue(operational.failureAnalytics).byFailureClass));
|
|
21211
|
+
if (failureMix !== "—")
|
|
21212
|
+
lines.push(`Failure mix: ${failureMix}`);
|
|
21213
|
+
lines.push(...formatActionKindBreakdown(operational.failureAnalytics));
|
|
21214
|
+
lines.push("", ...renderTextTable(["FUNNEL", "COUNT"], [
|
|
21215
|
+
["Enrolled", numberText(stats.enrolled)],
|
|
21216
|
+
["Invites sent", numberText(stats.invitesSent ?? stats.connectionRequestsSent)],
|
|
21217
|
+
["Connected", numberText(stats.connected ?? stats.connectionRequestsAccepted)],
|
|
21218
|
+
["Messages sent", numberText(stats.messagesSent)],
|
|
21219
|
+
["Replies", numberText(stats.totalReplies ?? stats.replied)],
|
|
21220
|
+
]));
|
|
21221
|
+
const link = stringValue(data.web_url) ?? stringValue(data.deepLink);
|
|
21222
|
+
if (link)
|
|
21223
|
+
lines.push("", link);
|
|
21224
|
+
return `${lines.join("\n")}\n`;
|
|
21225
|
+
}
|
|
21226
|
+
function formatSequenceAnalyticsHealth(data) {
|
|
21227
|
+
const analytics = recordValue(data.analytics);
|
|
21228
|
+
const summary = recordValue(analytics.operationalSummary);
|
|
21229
|
+
const failureAnalytics = summary.failureAnalytics;
|
|
21230
|
+
const sequences = Array.isArray(analytics.sequences) ? analytics.sequences.filter(isRecord) : [];
|
|
21231
|
+
const range = recordValue(analytics.range);
|
|
21232
|
+
const from = stringValue(range.from);
|
|
21233
|
+
const to = stringValue(range.to);
|
|
21234
|
+
const rangeLabel = from || to ? `${from ?? "start"} → ${to ?? "now"}` : stringValue(range.preset) ?? "selected window";
|
|
21235
|
+
const lines = [
|
|
21236
|
+
`Sequencer analytics (${rangeLabel})`,
|
|
21237
|
+
...formatSequenceFleetSummary(analytics.fleet),
|
|
21238
|
+
`Open work: ${formatSequenceOpenWork(summary.openWork)}`,
|
|
21239
|
+
`Selected-window outcomes: ${formatSequenceFailureTotals(failureAnalytics)}`,
|
|
21240
|
+
`Failure mix: ${formatCountRecord(recordValue(recordValue(failureAnalytics).byFailureClass))}`,
|
|
21241
|
+
...formatActionKindBreakdown(failureAnalytics),
|
|
21242
|
+
"",
|
|
21243
|
+
];
|
|
21244
|
+
if (sequences.length === 0) {
|
|
21245
|
+
lines.push("No sequences in this analytics scope.");
|
|
21246
|
+
}
|
|
21247
|
+
else {
|
|
21248
|
+
lines.push(...renderTextTable(["SEQUENCE", "PERSISTED", "OPERATIONAL", "OPEN WORK", "SENT / FAILED", "REASONS"], sequences.map((sequence) => sequenceHealthRow(sequence))));
|
|
21249
|
+
lines.push("", ...sequenceInspectionLinks(sequences));
|
|
21250
|
+
}
|
|
21251
|
+
const link = stringValue(data.web_url) ?? stringValue(data.deepLink);
|
|
21252
|
+
if (link)
|
|
21253
|
+
lines.push(link);
|
|
21254
|
+
return `${lines.join("\n")}\n`;
|
|
21255
|
+
}
|
|
21256
|
+
function sequenceHealthRow(sequence) {
|
|
21257
|
+
const operational = recordValue(sequence.operational);
|
|
21258
|
+
return [
|
|
21259
|
+
stringValue(sequence.name) ?? stringValue(sequence.slug) ?? "—",
|
|
21260
|
+
stringValue(sequence.status) ?? "unknown",
|
|
21261
|
+
stringValue(operational.operationalState) ?? "unknown",
|
|
21262
|
+
formatSequenceOpenWork(operational.openWork),
|
|
21263
|
+
formatSequenceFailureTotals(operational.failureAnalytics),
|
|
21264
|
+
formatSequenceReasons(operational),
|
|
21265
|
+
];
|
|
21266
|
+
}
|
|
21267
|
+
function sequenceInspectionLinks(sequences) {
|
|
21268
|
+
return sequences.flatMap((sequence) => {
|
|
21269
|
+
const exactLink = stringValue(sequence.web_url) ?? stringValue(sequence.deepLink);
|
|
21270
|
+
return exactLink ? [`Inspect ${stringValue(sequence.name) ?? stringValue(sequence.slug) ?? "Sequence"}: ${exactLink}`] : [];
|
|
21271
|
+
});
|
|
21272
|
+
}
|
|
21273
|
+
function formatSequenceFleetSummary(value) {
|
|
21274
|
+
const fleet = recordValue(value);
|
|
21275
|
+
if (Object.keys(fleet).length === 0)
|
|
21276
|
+
return ["Fleet counts: unavailable (legacy API response)"];
|
|
21277
|
+
const states = recordValue(fleet.byOperationalState);
|
|
21278
|
+
return [
|
|
21279
|
+
`Fleet: ${numberText(fleet.markedActive)} marked active · ${numberText(fleet.operationallyLive)} operationally live · ${numberText(fleet.total)} total`,
|
|
21280
|
+
`States: ${SEQUENCE_OPERATIONAL_STATE_ORDER.map((state) => `${state} ${numberText(states[state])}`).join(" · ")}`,
|
|
21281
|
+
];
|
|
21282
|
+
}
|
|
21283
|
+
function formatSequenceOpenWork(value) {
|
|
21284
|
+
const work = recordValue(value);
|
|
21285
|
+
if (Object.keys(work).length === 0)
|
|
21286
|
+
return "unknown";
|
|
21287
|
+
return [
|
|
21288
|
+
`enr ${numberText(work.enrollments)}`,
|
|
21289
|
+
`act ${numberText(work.actions)}`,
|
|
21290
|
+
`now ${numberText(work.actionableNow)}`,
|
|
21291
|
+
`flight ${numberText(work.inFlight)}`,
|
|
21292
|
+
`paced ${numberText(work.paced)}`,
|
|
21293
|
+
`blocked ${numberText(work.blocked)}`,
|
|
21294
|
+
`unresolved ${numberText(work.unresolvedFailures)}`,
|
|
21295
|
+
].join(" · ");
|
|
21296
|
+
}
|
|
21297
|
+
function formatSequenceFailureTotals(value) {
|
|
21298
|
+
const analytics = recordValue(value);
|
|
21299
|
+
if (Object.keys(analytics).length === 0)
|
|
21300
|
+
return "unknown";
|
|
21301
|
+
return `sent ${numberText(analytics.sent)} · failed ${numberText(analytics.terminalFailed)} · deferred ${numberText(analytics.deferrals)}`;
|
|
21302
|
+
}
|
|
21303
|
+
function formatFailureWindow(value) {
|
|
21304
|
+
const windowValue = recordValue(recordValue(value).window);
|
|
21305
|
+
const from = stringValue(windowValue.from);
|
|
21306
|
+
const to = stringValue(windowValue.to);
|
|
21307
|
+
return from || to ? `Selected-window ${from ?? "start"} → ${to ?? "now"}` : "Lifetime";
|
|
21308
|
+
}
|
|
21309
|
+
function formatSequenceReasons(operational) {
|
|
21310
|
+
const reasonCodes = Array.isArray(operational.reasonCodes)
|
|
21311
|
+
? operational.reasonCodes.filter((reason) => typeof reason === "string")
|
|
21312
|
+
: [];
|
|
21313
|
+
if (reasonCodes.length > 0)
|
|
21314
|
+
return reasonCodes.join(", ");
|
|
21315
|
+
return stringValue(operational.operationalState) ? "none reported" : "unknown";
|
|
21316
|
+
}
|
|
21317
|
+
function formatActionKindBreakdown(value) {
|
|
21318
|
+
const byActionKind = recordValue(recordValue(value).byActionKind);
|
|
21319
|
+
const entries = Object.entries(byActionKind).filter((entry) => isRecord(entry[1])).sort(([left], [right]) => left.localeCompare(right));
|
|
21320
|
+
if (entries.length === 0)
|
|
21321
|
+
return [];
|
|
21322
|
+
return ["By action kind:", ...entries.map(([kind, bucket]) => {
|
|
21323
|
+
const mix = formatCountRecord(recordValue(bucket.byFailureClass));
|
|
21324
|
+
return ` ${kind}: ${formatSequenceFailureTotals(bucket)}${mix === "—" ? "" : ` · failures ${mix}`}`;
|
|
21325
|
+
})];
|
|
21326
|
+
}
|
|
21327
|
+
function formatCountRecord(value) {
|
|
21328
|
+
const entries = Object.entries(value)
|
|
21329
|
+
.map(([key, count]) => [key, numericValue(count)])
|
|
21330
|
+
.filter((entry) => entry[1] !== null && entry[1] > 0)
|
|
21331
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
|
|
21332
|
+
return entries.length > 0 ? entries.map(([key, count]) => `${key} ${count}`).join(" · ") : "—";
|
|
21333
|
+
}
|
|
21334
|
+
function recordValue(value) {
|
|
21335
|
+
return isRecord(value) ? value : {};
|
|
21336
|
+
}
|
|
21337
|
+
function stringValue(value) {
|
|
21338
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
21339
|
+
}
|
|
21340
|
+
function numericValue(value) {
|
|
21341
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
21342
|
+
}
|
|
21343
|
+
function numberText(value) {
|
|
21344
|
+
return String(numericValue(value) ?? 0);
|
|
21345
|
+
}
|
|
21086
21346
|
function formatSequenceVariants(data) {
|
|
21087
21347
|
const styles = ansi(output.isTTY === true && !process.env.NO_COLOR);
|
|
21088
21348
|
const byVariant = Array.isArray(data.by_variant) ? data.by_variant : [];
|
|
@@ -25,7 +25,7 @@ export type PricingPlanDefinition = {
|
|
|
25
25
|
features: readonly string[];
|
|
26
26
|
};
|
|
27
27
|
export type PricingPlanMetadata = Record<string, unknown> | null | undefined;
|
|
28
|
-
export declare const CONTACT_SALES_URL = "mailto:
|
|
28
|
+
export declare const CONTACT_SALES_URL = "mailto:priority@oxygen-agent.com";
|
|
29
29
|
export declare const CREDITS_PER_USD = 1000;
|
|
30
30
|
export declare const TOPUP_USD_CENTS_PER_1K_CREDITS = 125;
|
|
31
31
|
export declare const CREDIT_TOPUP_MIN_CREDITS = 8000;
|
|
@@ -222,7 +222,7 @@ export declare const BASE_PRICING_PLANS: {
|
|
|
222
222
|
readonly automationOverageEnabledDefault: false;
|
|
223
223
|
readonly description: "For high-volume GTM teams that need custom limits and rollout support.";
|
|
224
224
|
readonly ctaLabel: "Contact sales";
|
|
225
|
-
readonly contactHref: "mailto:
|
|
225
|
+
readonly contactHref: "mailto:priority@oxygen-agent.com";
|
|
226
226
|
readonly features: readonly ["10,000,000+ managed credits / month", "Workflows", "Phone number enrichment on managed credits", "Custom provider routing and BYOK policies", "Dedicated onboarding"];
|
|
227
227
|
readonly monthlyPriceCents: number | null;
|
|
228
228
|
readonly monthlyCredits: number | null;
|
|
@@ -238,7 +238,7 @@ export declare const BASE_PRICING_PLANS: {
|
|
|
238
238
|
readonly automationOverageEnabledDefault: false;
|
|
239
239
|
readonly description: "Custom procurement, invoicing, limits, and rollout support.";
|
|
240
240
|
readonly ctaLabel: "Talk to us";
|
|
241
|
-
readonly contactHref: "mailto:
|
|
241
|
+
readonly contactHref: "mailto:priority@oxygen-agent.com";
|
|
242
242
|
readonly features: readonly ["Everything in Team", "Custom usage limits", "Volume pricing", "Dedicated support"];
|
|
243
243
|
readonly monthlyPriceCents: number | null;
|
|
244
244
|
readonly monthlyCredits: number | null;
|
|
@@ -4,11 +4,11 @@ export const WEEKLY_USAGE_WINDOW_DAYS = 7;
|
|
|
4
4
|
export const BILLING_CURRENCIES = ["usd"];
|
|
5
5
|
export const DEFAULT_BILLING_CURRENCY = "usd";
|
|
6
6
|
// Scale/Enterprise contact routes to the shared support inbox, not to a
|
|
7
|
-
// founder's calendar.
|
|
7
|
+
// founder's calendar. priority@oxygen-agent.com is the Plain Email channel
|
|
8
8
|
// (PLAIN_SUPPORT_EMAIL_ADDRESS), so an enterprise enquiry lands in the support
|
|
9
9
|
// queue as a Thread that either founder can pick up, instead of hard-coding one
|
|
10
10
|
// person's booking page into every public pricing card.
|
|
11
|
-
export const CONTACT_SALES_URL = "mailto:
|
|
11
|
+
export const CONTACT_SALES_URL = "mailto:priority@oxygen-agent.com";
|
|
12
12
|
// One Oxygen credit is worth $0.001 of managed work: $1 = 1,000 credits.
|
|
13
13
|
// Subscription plans convert at face value — every subscription dollar grants
|
|
14
14
|
// exactly 1,000 credits — while on-demand top-up packs price the same credit
|
|
@@ -69,12 +69,12 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
69
69
|
id: "support-and-feedback",
|
|
70
70
|
layer: "Control",
|
|
71
71
|
primitive: null,
|
|
72
|
-
owns: "Customer support tickets, product feedback, transcript-backed reports, and
|
|
72
|
+
owns: "Customer support conversations, structured tickets, product feedback, transcript-backed reports, and Thread follow-up.",
|
|
73
73
|
notFor: "Primitive run diagnosis when no support report is needed; inspect Observability first.",
|
|
74
|
-
execution: "
|
|
74
|
+
execution: "Start or continue a hosted support conversation, optionally file structured metadata, and return its deep link; never send a customer reply automatically.",
|
|
75
75
|
posture: "workspace_write",
|
|
76
76
|
gatewayTools: ["oxygen_feedback", "oxygen_support_ticket_create", "oxygen_support_tickets_list"],
|
|
77
|
-
gatewayCommands: ["feedback", "support file", "support list"],
|
|
77
|
+
gatewayCommands: ["feedback", "support chat", "support file", "support list"],
|
|
78
78
|
skills: ["oxygen-feedback"],
|
|
79
79
|
endpointSections: ["feedback", "support"],
|
|
80
80
|
intentTerms: ["support", "feedback", "bug report", "feature request", "report issue", "ticket", "complaint"],
|
|
@@ -61,6 +61,11 @@ export type FeatureGateResolution = {
|
|
|
61
61
|
* - OXYGEN_PUBLISHING_ENABLED — (b). Fail-closed production switch for posting
|
|
62
62
|
* to real provider accounts (`apps/web/src/app/(app)/(dashboard)/publishing/
|
|
63
63
|
* data.ts`). Wrong "on" = public posts from customer accounts.
|
|
64
|
+
* - OXYGEN_WORKER_AUTOSCALE — (b). Arms the worker fleet autoscaler, which STOPS
|
|
65
|
+
* production Machines (`apps/worker/src/fleet-autoscaler.ts`). Its worst wrong
|
|
66
|
+
* value takes the fleet down while work is queued, so it must never depend on a
|
|
67
|
+
* flag service being reachable — and "the flag service is unreachable" is exactly
|
|
68
|
+
* the kind of incident during which you least want the fleet to move.
|
|
64
69
|
* - OXYGEN_AGENTS_ENABLED — (b). Authorizes autonomous Agent run execution —
|
|
65
70
|
* tool authority AND credit spend (`apps/web/src/lib/agent-runtime.ts`,
|
|
66
71
|
* `apps/worker/src/agent-cycle.ts`). Same class as publishing.
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
* - OXYGEN_PUBLISHING_ENABLED — (b). Fail-closed production switch for posting
|
|
53
53
|
* to real provider accounts (`apps/web/src/app/(app)/(dashboard)/publishing/
|
|
54
54
|
* data.ts`). Wrong "on" = public posts from customer accounts.
|
|
55
|
+
* - OXYGEN_WORKER_AUTOSCALE — (b). Arms the worker fleet autoscaler, which STOPS
|
|
56
|
+
* production Machines (`apps/worker/src/fleet-autoscaler.ts`). Its worst wrong
|
|
57
|
+
* value takes the fleet down while work is queued, so it must never depend on a
|
|
58
|
+
* flag service being reachable — and "the flag service is unreachable" is exactly
|
|
59
|
+
* the kind of incident during which you least want the fleet to move.
|
|
55
60
|
* - OXYGEN_AGENTS_ENABLED — (b). Authorizes autonomous Agent run execution —
|
|
56
61
|
* tool authority AND credit spend (`apps/web/src/lib/agent-runtime.ts`,
|
|
57
62
|
* `apps/worker/src/agent-cycle.ts`). Same class as publishing.
|
|
@@ -60,6 +65,7 @@
|
|
|
60
65
|
* `findFeatureGateRegistryViolations` fails the build's tests if one ever is.
|
|
61
66
|
*/
|
|
62
67
|
export const NEVER_FLAGGABLE = [
|
|
68
|
+
"OXYGEN_WORKER_AUTOSCALE",
|
|
63
69
|
"OXYGEN_PUBLISHING_ENABLED",
|
|
64
70
|
"OXYGEN_AGENTS_ENABLED",
|
|
65
71
|
"OXYGEN_TELEMETRY_ENABLED",
|
|
@@ -35,6 +35,7 @@ export * from "./feature-gates.js";
|
|
|
35
35
|
export * from "./hosted-ai.js";
|
|
36
36
|
export * from "./identifiers.js";
|
|
37
37
|
export * from "./knowledge-constants.js";
|
|
38
|
+
export * from "./knowledge-bootstrap.js";
|
|
38
39
|
export * from "./knowledge-links.js";
|
|
39
40
|
export * from "./knowledge-markdown.js";
|
|
40
41
|
export * from "./knowledge-seed-content.js";
|
|
@@ -35,6 +35,7 @@ export * from "./feature-gates.js";
|
|
|
35
35
|
export * from "./hosted-ai.js";
|
|
36
36
|
export * from "./identifiers.js";
|
|
37
37
|
export * from "./knowledge-constants.js";
|
|
38
|
+
export * from "./knowledge-bootstrap.js";
|
|
38
39
|
export * from "./knowledge-links.js";
|
|
39
40
|
export * from "./knowledge-markdown.js";
|
|
40
41
|
export * from "./knowledge-seed-content.js";
|