@oxygen-agent/cli 1.1010.954 → 1.1011.95
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 +2 -0
- package/dist/index.js +315 -40
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +5 -5
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +16 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +35 -5
- package/node_modules/@oxygen/shared/dist/company-reply-pause.d.ts +55 -0
- package/node_modules/@oxygen/shared/dist/company-reply-pause.js +146 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +3 -3
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +3 -3
- package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.js +5 -0
- package/node_modules/@oxygen/shared/dist/plan-band.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/plan-band.js +24 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +54 -8
- package/node_modules/@oxygen/shared/dist/sequences.js +101 -9
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +3 -0
- package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +2 -3
- package/node_modules/@oxygen/shared/dist/table-capacity.js +10 -16
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +9 -1
- package/node_modules/@oxygen/shared/package.json +5 -0
- package/node_modules/@oxygen/workflows/dist/graph/lint.js +1 -1
- package/package.json +1 -1
|
@@ -1032,6 +1032,84 @@ export function sequenceChannels(definition) {
|
|
|
1032
1032
|
* stored under `draft`. Empty means nothing to send, so starting would
|
|
1033
1033
|
* either dispatch a blank message or fail per lead at send time.
|
|
1034
1034
|
*/
|
|
1035
|
+
/**
|
|
1036
|
+
* Threaded email steps that would continue another inbox's thread. A provider
|
|
1037
|
+
* thread belongs to the mailbox that opened it, so a step that changes From —
|
|
1038
|
+
* an exact-inbox step, or the return to the lead's assigned inbox after one —
|
|
1039
|
+
* must open a new thread under its own subject. Checked on save and again at
|
|
1040
|
+
* launch, because a draft may be saved half-written.
|
|
1041
|
+
*/
|
|
1042
|
+
export function sequenceSenderThreadConflicts(steps) {
|
|
1043
|
+
const conflicts = [];
|
|
1044
|
+
let precedingSender;
|
|
1045
|
+
let seenEmail = false;
|
|
1046
|
+
steps.forEach((step, index) => {
|
|
1047
|
+
if (step.kind !== "email_send" && step.kind !== "email_reply")
|
|
1048
|
+
return;
|
|
1049
|
+
const sender = step.kind === "email_send" ? step.sender_mailbox_id : undefined;
|
|
1050
|
+
if (seenEmail && sequenceEmailStepThreads(step) && sender !== precedingSender) {
|
|
1051
|
+
conflicts.push({
|
|
1052
|
+
index,
|
|
1053
|
+
stepId: step.id,
|
|
1054
|
+
message: `step '${step.id}' changes the sending inbox. Give it a new subject to start a new thread.`,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
seenEmail = true;
|
|
1058
|
+
precedingSender = sender;
|
|
1059
|
+
});
|
|
1060
|
+
return conflicts;
|
|
1061
|
+
}
|
|
1062
|
+
/** LinkedIn kinds that open or continue a conversation, so bind the lead to one account. */
|
|
1063
|
+
export const LINKEDIN_RELATIONSHIP_STEP_KINDS = new Set(["invite", "message", "inmail", "withdraw_invite"]);
|
|
1064
|
+
/** LinkedIn light touches: they bind nothing, so each may come from any selected person. */
|
|
1065
|
+
export const LINKEDIN_LIGHT_TOUCH_STEP_KINDS = new Set(["visit_profile", "follow", "like_post", "comment_post"]);
|
|
1066
|
+
const LINKEDIN_SENDER_STEP_KINDS = new Set([...LINKEDIN_RELATIONSHIP_STEP_KINDS, ...LINKEDIN_LIGHT_TOUCH_STEP_KINDS]);
|
|
1067
|
+
function normalizeLinkedInStepSender(value, path, issues) {
|
|
1068
|
+
if (value === undefined || value === null || value === "")
|
|
1069
|
+
return {};
|
|
1070
|
+
if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value.trim())) {
|
|
1071
|
+
issues.push({ path, message: "must be a sender profile id." });
|
|
1072
|
+
return {};
|
|
1073
|
+
}
|
|
1074
|
+
return { sender_profile_id: value.trim() };
|
|
1075
|
+
}
|
|
1076
|
+
function stepSenderProfileId(step) {
|
|
1077
|
+
return "sender_profile_id" in step && typeof step.sender_profile_id === "string" ? step.sender_profile_id : undefined;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* The person who owns every lead's LinkedIn conversation in this journey, when
|
|
1081
|
+
* the relationship steps name one (null = each lead's assigned person). Only
|
|
1082
|
+
* meaningful once sequenceLinkedInOwnerConflicts is empty.
|
|
1083
|
+
*/
|
|
1084
|
+
export function sequenceLinkedInOwnerProfileId(steps) {
|
|
1085
|
+
for (const step of steps) {
|
|
1086
|
+
if (!LINKEDIN_RELATIONSHIP_STEP_KINDS.has(step.kind))
|
|
1087
|
+
continue;
|
|
1088
|
+
const owner = stepSenderProfileId(step);
|
|
1089
|
+
if (owner)
|
|
1090
|
+
return owner;
|
|
1091
|
+
}
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Relationship steps that disagree about who sends them. LinkedIn lets one of
|
|
1096
|
+
* the workspace's accounts hold a conversation with a lead (and OXYGEN binds
|
|
1097
|
+
* the lead to the first account that invites or messages it), so a connection
|
|
1098
|
+
* request from one person followed by a message from another would be refused.
|
|
1099
|
+
*/
|
|
1100
|
+
export function sequenceLinkedInOwnerConflicts(steps) {
|
|
1101
|
+
const relationship = steps
|
|
1102
|
+
.map((step, index) => ({ step, index }))
|
|
1103
|
+
.filter(({ step }) => LINKEDIN_RELATIONSHIP_STEP_KINDS.has(step.kind));
|
|
1104
|
+
const owners = new Set(relationship.map(({ step }) => stepSenderProfileId(step) ?? ""));
|
|
1105
|
+
if (owners.size <= 1)
|
|
1106
|
+
return [];
|
|
1107
|
+
return relationship.map(({ step, index }) => ({
|
|
1108
|
+
index,
|
|
1109
|
+
stepId: step.id,
|
|
1110
|
+
message: `step '${step.id}': every LinkedIn connection, message and InMail step must come from the same person (or all from the lead's own). LinkedIn lets one of your accounts hold a conversation with a lead.`,
|
|
1111
|
+
}));
|
|
1112
|
+
}
|
|
1035
1113
|
export function sequenceStepsMissingCopy(steps) {
|
|
1036
1114
|
const missing = [];
|
|
1037
1115
|
const check = (step, field, value) => {
|
|
@@ -1174,6 +1252,12 @@ export function validateSequenceDefinition(input, options = {}) {
|
|
|
1174
1252
|
message: `step '${step.id}' has no subject, which means "continue the previous email" — but no earlier step sends one. Give the first email of the sequence a subject.`,
|
|
1175
1253
|
});
|
|
1176
1254
|
});
|
|
1255
|
+
for (const conflict of sequenceSenderThreadConflicts(normalized)) {
|
|
1256
|
+
issues.push({ path: `steps[${conflict.index}].subject_template`, message: conflict.message });
|
|
1257
|
+
}
|
|
1258
|
+
for (const conflict of sequenceLinkedInOwnerConflicts(normalized)) {
|
|
1259
|
+
issues.push({ path: `steps[${conflict.index}].sender_profile_id`, message: conflict.message });
|
|
1260
|
+
}
|
|
1177
1261
|
}
|
|
1178
1262
|
if (issues.length > 0) {
|
|
1179
1263
|
throw new OxygenError("invalid_sequence", `Sequence definition is invalid: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`, { details: { issues }, exitCode: 1 });
|
|
@@ -1298,14 +1382,17 @@ raw, index, options, issues) {
|
|
|
1298
1382
|
if (channel && options.allowedChannels && !options.allowedChannels.includes(channel)) {
|
|
1299
1383
|
issues.push({ path: `${path}.channel`, message: `channel '${channel}' is not enabled for this sequence.` });
|
|
1300
1384
|
}
|
|
1385
|
+
const linkedInSender = LINKEDIN_SENDER_STEP_KINDS.has(stepKind)
|
|
1386
|
+
? normalizeLinkedInStepSender(raw.sender_profile_id, `${path}.sender_profile_id`, issues)
|
|
1387
|
+
: {};
|
|
1301
1388
|
switch (stepKind) {
|
|
1302
1389
|
case "visit_profile":
|
|
1303
|
-
return { id, channel: "linkedin", kind: "visit_profile" };
|
|
1390
|
+
return { id, channel: "linkedin", kind: "visit_profile", ...linkedInSender };
|
|
1304
1391
|
case "invite": {
|
|
1305
1392
|
const note = optionalTemplate(raw.note_template, `${path}.note_template`, MAX_NOTE_LENGTH, issues);
|
|
1306
1393
|
return note !== undefined
|
|
1307
|
-
? { id, channel: "linkedin", kind: "invite", note_template: note }
|
|
1308
|
-
: { id, channel: "linkedin", kind: "invite" };
|
|
1394
|
+
? { id, channel: "linkedin", kind: "invite", note_template: note, ...linkedInSender }
|
|
1395
|
+
: { id, channel: "linkedin", kind: "invite", ...linkedInSender };
|
|
1309
1396
|
}
|
|
1310
1397
|
case "wait_for_connection": {
|
|
1311
1398
|
const timeoutDays = raw.timeout_days === undefined || raw.timeout_days === null
|
|
@@ -1325,7 +1412,7 @@ raw, index, options, issues) {
|
|
|
1325
1412
|
const autoOptimize = normalizeAutoOptimize(raw.auto_optimize, `${path}.auto_optimize`, variants, options, issues);
|
|
1326
1413
|
const attachments = normalizeLinkedInAttachments(raw.attachments, `${path}.attachments`, issues);
|
|
1327
1414
|
return {
|
|
1328
|
-
id, channel: "linkedin", kind: "message", template: template ?? "",
|
|
1415
|
+
id, channel: "linkedin", kind: "message", template: template ?? "", ...linkedInSender,
|
|
1329
1416
|
...(variants ? { variants: variants } : {}),
|
|
1330
1417
|
...(autoOptimize ? { auto_optimize: autoOptimize } : {}),
|
|
1331
1418
|
...(attachments ? { attachments } : {}),
|
|
@@ -1337,7 +1424,7 @@ raw, index, options, issues) {
|
|
|
1337
1424
|
const variants = normalizeVariants(raw.variants, `${path}.variants`, ["subject_template", "template"], issues, options.draft === true);
|
|
1338
1425
|
const autoOptimize = normalizeAutoOptimize(raw.auto_optimize, `${path}.auto_optimize`, variants, options, issues);
|
|
1339
1426
|
return {
|
|
1340
|
-
id, channel: "linkedin", kind: "inmail", subject_template: subject ?? "", template: template ?? "",
|
|
1427
|
+
id, channel: "linkedin", kind: "inmail", subject_template: subject ?? "", template: template ?? "", ...linkedInSender,
|
|
1341
1428
|
...(variants ? { variants: variants } : {}),
|
|
1342
1429
|
...(autoOptimize ? { auto_optimize: autoOptimize } : {}),
|
|
1343
1430
|
};
|
|
@@ -1372,8 +1459,13 @@ raw, index, options, issues) {
|
|
|
1372
1459
|
const cc = optionalEmailList(raw.cc, `${path}.cc`, issues);
|
|
1373
1460
|
const bcc = optionalEmailList(raw.bcc, `${path}.bcc`, issues);
|
|
1374
1461
|
const sendWindow = normalizeSendWindow(raw.send_window, `${path}.send_window`, issues);
|
|
1462
|
+
const senderMailboxId = optionalString(raw.sender_mailbox_id, `${path}.sender_mailbox_id`, issues);
|
|
1463
|
+
if (senderMailboxId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(senderMailboxId)) {
|
|
1464
|
+
issues.push({ path: `${path}.sender_mailbox_id`, message: "must be an inbox id." });
|
|
1465
|
+
}
|
|
1375
1466
|
return {
|
|
1376
1467
|
id, channel: "email", kind: "email_send",
|
|
1468
|
+
...(senderMailboxId ? { sender_mailbox_id: senderMailboxId } : {}),
|
|
1377
1469
|
...(threaded ? {} : { subject_template: subject }),
|
|
1378
1470
|
body_template: body ?? "",
|
|
1379
1471
|
...(bodyHtml !== undefined ? { body_html_template: bodyHtml } : {}),
|
|
@@ -1557,7 +1649,7 @@ raw, index, options, issues) {
|
|
|
1557
1649
|
};
|
|
1558
1650
|
}
|
|
1559
1651
|
case "follow":
|
|
1560
|
-
return { id, channel: "linkedin", kind: "follow" };
|
|
1652
|
+
return { id, channel: "linkedin", kind: "follow", ...linkedInSender };
|
|
1561
1653
|
case "like_post": {
|
|
1562
1654
|
const reaction = typeof raw.reaction === "string" && LINKEDIN_POST_REACTION_SET.has(raw.reaction)
|
|
1563
1655
|
? raw.reaction
|
|
@@ -1567,7 +1659,7 @@ raw, index, options, issues) {
|
|
|
1567
1659
|
}
|
|
1568
1660
|
const recency = optionalPostRecencyDays(raw.post_recency_days, `${path}.post_recency_days`, issues);
|
|
1569
1661
|
return {
|
|
1570
|
-
id, channel: "linkedin", kind: "like_post",
|
|
1662
|
+
id, channel: "linkedin", kind: "like_post", ...linkedInSender,
|
|
1571
1663
|
...(reaction ? { reaction: reaction } : {}),
|
|
1572
1664
|
...(recency !== undefined ? { post_recency_days: recency } : {}),
|
|
1573
1665
|
...(raw.on_no_post === "stop" ? { on_no_post: "stop" } : {}),
|
|
@@ -1587,7 +1679,7 @@ raw, index, options, issues) {
|
|
|
1587
1679
|
}
|
|
1588
1680
|
const recency = optionalPostRecencyDays(raw.post_recency_days, `${path}.post_recency_days`, issues);
|
|
1589
1681
|
return {
|
|
1590
|
-
id, channel: "linkedin", kind: "comment_post",
|
|
1682
|
+
id, channel: "linkedin", kind: "comment_post", ...linkedInSender,
|
|
1591
1683
|
...(hasText ? { text_template: textTemplate } : {}),
|
|
1592
1684
|
...(hasPrompt ? { ai_prompt: aiPrompt } : {}),
|
|
1593
1685
|
...(recency !== undefined ? { post_recency_days: recency } : {}),
|
|
@@ -1595,7 +1687,7 @@ raw, index, options, issues) {
|
|
|
1595
1687
|
};
|
|
1596
1688
|
}
|
|
1597
1689
|
case "withdraw_invite":
|
|
1598
|
-
return { id, channel: "linkedin", kind: "withdraw_invite" };
|
|
1690
|
+
return { id, channel: "linkedin", kind: "withdraw_invite", ...linkedInSender };
|
|
1599
1691
|
case "stop":
|
|
1600
1692
|
return { id, kind: "stop" };
|
|
1601
1693
|
default:
|
|
@@ -15,6 +15,7 @@ export type SignupLeadWebhookInput = {
|
|
|
15
15
|
email?: string | null;
|
|
16
16
|
firstName?: string | null;
|
|
17
17
|
lastName?: string | null;
|
|
18
|
+
phone?: string | null;
|
|
18
19
|
createdAt?: Date | string | number | null | undefined;
|
|
19
20
|
/** A profile URL the provider already knows for this person; the consent-recorded creator URL still wins at enqueue. */
|
|
20
21
|
linkedinUrl?: string | null;
|
|
@@ -33,6 +33,7 @@ export function buildSignupLeadWebhookPayload(input) {
|
|
|
33
33
|
const email = normalizeString(input.user?.email)?.toLowerCase() ?? null;
|
|
34
34
|
const firstName = normalizeString(input.user?.firstName);
|
|
35
35
|
const lastName = normalizeString(input.user?.lastName);
|
|
36
|
+
const phone = normalizeString(input.user?.phone);
|
|
36
37
|
const fullName = normalizeString([firstName, lastName].filter(Boolean).join(" "));
|
|
37
38
|
const clerkOrgId = normalizeString(input.organization?.clerkOrgId);
|
|
38
39
|
const companyName = normalizeString(input.organization?.name);
|
|
@@ -51,6 +52,7 @@ export function buildSignupLeadWebhookPayload(input) {
|
|
|
51
52
|
email,
|
|
52
53
|
first_name: firstName,
|
|
53
54
|
last_name: lastName,
|
|
55
|
+
phone,
|
|
54
56
|
full_name: fullName,
|
|
55
57
|
created_at: normalizeDate(input.user?.createdAt),
|
|
56
58
|
linkedin_url: normalizeString(input.user?.linkedinUrl) ?? undefined,
|
|
@@ -118,6 +120,7 @@ export function buildSignupLeadWebhookPayload(input) {
|
|
|
118
120
|
email,
|
|
119
121
|
first_name: firstName,
|
|
120
122
|
last_name: lastName,
|
|
123
|
+
phone,
|
|
121
124
|
full_name: fullName,
|
|
122
125
|
company_name: companyName,
|
|
123
126
|
company_domain: companyDomain,
|
|
@@ -37,9 +37,8 @@ export declare const PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST: readonly PlanB
|
|
|
37
37
|
* - A limit that tightens (free, $49, $99) is a scheduled 2026-09 repricing value
|
|
38
38
|
* (`storage.<band>.*` in `REPRICING_2026_09_SCHEDULE`): today's envelope until
|
|
39
39
|
* the effective-date switch, the ratified ladder from it on.
|
|
40
|
-
* - A band awaiting
|
|
41
|
-
* totals and keeps today's rows per Table
|
|
42
|
-
* deployment, where the test runs.
|
|
40
|
+
* - A band awaiting a passing rows-per-Table capacity test has its ladder
|
|
41
|
+
* workspace totals and keeps today's rows per Table everywhere.
|
|
43
42
|
* - Every other band is already at its ladder value.
|
|
44
43
|
*/
|
|
45
44
|
export declare function resolveWorkspaceTableCapacity(band: PlanBand, options?: RepricingOptions): WorkspaceTableCapacityLimits;
|
|
@@ -50,18 +50,13 @@ export const PLAN_BAND_TABLE_CAPACITY_TARGETS = Object.freeze({
|
|
|
50
50
|
*/
|
|
51
51
|
export const PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST = Object.freeze(["999", "1999"]);
|
|
52
52
|
/**
|
|
53
|
-
* The capacity test
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* (not `resolveDeployEnv`, which throws on a typo) so a malformed value keeps
|
|
58
|
-
* the production answer instead of failing a limits read.
|
|
53
|
+
* The dev capacity test has finished without meeting the latency and export
|
|
54
|
+
* requirements (decision F.9). Hold the same 3M limit in every environment;
|
|
55
|
+
* the earlier dev-only 5M/10M override made limits reads promise a capacity
|
|
56
|
+
* that the founder held after that test.
|
|
59
57
|
*/
|
|
60
|
-
function awaitsTableRowCapacityTest(band
|
|
61
|
-
|
|
62
|
-
return false;
|
|
63
|
-
const env = options.env ?? process.env;
|
|
64
|
-
return env.OXYGEN_DEPLOY_ENV?.trim().toLowerCase() !== "selfhost-dev";
|
|
58
|
+
function awaitsTableRowCapacityTest(band) {
|
|
59
|
+
return PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST.includes(band);
|
|
65
60
|
}
|
|
66
61
|
const STORAGE_FIELDS = [
|
|
67
62
|
["tableRowLimit", "table_row_limit"],
|
|
@@ -75,14 +70,13 @@ const STORAGE_FIELDS = [
|
|
|
75
70
|
* - A limit that tightens (free, $49, $99) is a scheduled 2026-09 repricing value
|
|
76
71
|
* (`storage.<band>.*` in `REPRICING_2026_09_SCHEDULE`): today's envelope until
|
|
77
72
|
* the effective-date switch, the ratified ladder from it on.
|
|
78
|
-
* - A band awaiting
|
|
79
|
-
* totals and keeps today's rows per Table
|
|
80
|
-
* deployment, where the test runs.
|
|
73
|
+
* - A band awaiting a passing rows-per-Table capacity test has its ladder
|
|
74
|
+
* workspace totals and keeps today's rows per Table everywhere.
|
|
81
75
|
* - Every other band is already at its ladder value.
|
|
82
76
|
*/
|
|
83
77
|
export function resolveWorkspaceTableCapacity(band, options = {}) {
|
|
84
78
|
const target = PLAN_BAND_TABLE_CAPACITY_TARGETS[band];
|
|
85
|
-
if (awaitsTableRowCapacityTest(band
|
|
79
|
+
if (awaitsTableRowCapacityTest(band)) {
|
|
86
80
|
return Object.freeze({ ...target, tableRowLimit: WORKSPACE_TABLE_CAPACITY.tableRowLimit });
|
|
87
81
|
}
|
|
88
82
|
const resolved = { ...target };
|
|
@@ -95,7 +89,7 @@ export function resolveWorkspaceTableCapacity(band, options = {}) {
|
|
|
95
89
|
return Object.freeze(resolved);
|
|
96
90
|
}
|
|
97
91
|
export function describeWorkspaceTableCapacityStatus(band, options = {}) {
|
|
98
|
-
if (awaitsTableRowCapacityTest(band
|
|
92
|
+
if (awaitsTableRowCapacityTest(band)) {
|
|
99
93
|
return { status: "awaiting_capacity_test", effectiveAt: null, target: PLAN_BAND_TABLE_CAPACITY_TARGETS[band] };
|
|
100
94
|
}
|
|
101
95
|
const state = readRepricingSwitch(options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const OXYGEN_BUILD_VERSION = "1.
|
|
1
|
+
export declare const OXYGEN_BUILD_VERSION = "1.1011.95";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
|
|
2
|
-
export const OXYGEN_BUILD_VERSION = "1.
|
|
2
|
+
export const OXYGEN_BUILD_VERSION = "1.1011.95";
|
|
@@ -79,3 +79,9 @@ export declare function putWorkspaceFileObject(input: {
|
|
|
79
79
|
sizeBytes: number;
|
|
80
80
|
sha256: string;
|
|
81
81
|
}>;
|
|
82
|
+
/** Delete only an object key already proven to belong to this workspace file. */
|
|
83
|
+
export declare function deleteWorkspaceFileObject(input: {
|
|
84
|
+
organizationId: string;
|
|
85
|
+
fileId: string;
|
|
86
|
+
storageKey: string;
|
|
87
|
+
}): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
3
|
+
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
4
4
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
5
5
|
import { OxygenError } from "./cli-result.js";
|
|
6
6
|
import { resolveObjectStorageClient } from "./object-storage.js";
|
|
@@ -171,3 +171,11 @@ export async function putWorkspaceFileObject(input) {
|
|
|
171
171
|
await readWorkspaceFileObject({ ...input, storageKey, sizeBytes: bytes.byteLength, sha256 });
|
|
172
172
|
return { storageKey, sizeBytes: bytes.byteLength, sha256 };
|
|
173
173
|
}
|
|
174
|
+
/** Delete only an object key already proven to belong to this workspace file. */
|
|
175
|
+
export async function deleteWorkspaceFileObject(input) {
|
|
176
|
+
assertWorkspaceFileObjectKey(input);
|
|
177
|
+
const { client, bucket } = resolveObjectStorageClient();
|
|
178
|
+
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: input.storageKey }), {
|
|
179
|
+
abortSignal: AbortSignal.timeout(STORAGE_TIMEOUT_MS),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -121,6 +121,11 @@
|
|
|
121
121
|
"import": "./dist/dnc-identities.js",
|
|
122
122
|
"default": "./dist/dnc-identities.js"
|
|
123
123
|
},
|
|
124
|
+
"./company-reply-pause": {
|
|
125
|
+
"types": "./dist/company-reply-pause.d.ts",
|
|
126
|
+
"import": "./dist/company-reply-pause.js",
|
|
127
|
+
"default": "./dist/company-reply-pause.js"
|
|
128
|
+
},
|
|
124
129
|
"./provider-funding-errors": {
|
|
125
130
|
"types": "./dist/provider-funding-errors.d.ts",
|
|
126
131
|
"import": "./dist/provider-funding-errors.js",
|
|
@@ -812,7 +812,7 @@ function lintToolNode(node, path, add, scope, options) {
|
|
|
812
812
|
add(`${path}.mode`, "invalid_step_mode", "Node mode is invalid.");
|
|
813
813
|
}
|
|
814
814
|
else if (node.mode !== undefined && (node.effect !== "external_write" || node.mode !== "dry_run")) {
|
|
815
|
-
add(`${path}.mode`, "unsupported_step_mode", "
|
|
815
|
+
add(`${path}.mode`, "unsupported_step_mode", "Omit mode to run this step live. The only mode a tool node may set is 'dry_run', on an external write, and it previews without writing.");
|
|
816
816
|
}
|
|
817
817
|
if (node.effect === "external_write" && node.retry !== undefined) {
|
|
818
818
|
add(`${path}.retry`, "unsafe_external_write_retry", "External writes cannot have a node retry policy because an ambiguous failure may already have landed.");
|