@sellable/mcp 0.1.793 → 0.1.794
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/dist/agent-tool-names.d.ts +2 -2
- package/dist/agent-tool-names.js +14 -0
- package/dist/scheduler-envelope.d.ts +190 -0
- package/dist/scheduler-envelope.js +412 -0
- package/dist/server.js +28 -2
- package/dist/tools/campaign-message-preparation.d.ts +25 -0
- package/dist/tools/campaign-message-preparation.js +37 -0
- package/dist/tools/campaign-processing.d.ts +74 -1
- package/dist/tools/campaign-processing.js +57 -1
- package/dist/tools/prompts.d.ts +15 -3
- package/dist/tools/prompts.js +25 -0
- package/dist/tools/refill-executors.d.ts +43 -70
- package/dist/tools/refill-executors.js +201 -273
- package/dist/tools/refill-sends.d.ts +3 -3
- package/dist/tools/refill-sends.js +1 -1
- package/dist/tools/refill-target-plan.js +1 -1
- package/dist/tools/refill-v3-advance-contract.d.ts +618 -0
- package/dist/tools/refill-v3-advance-contract.js +920 -0
- package/dist/tools/refill-v3-advance.d.ts +397 -0
- package/dist/tools/refill-v3-advance.js +75 -0
- package/dist/tools/refill-v3-attention-packet-contract.d.ts +608 -0
- package/dist/tools/refill-v3-attention-packet-contract.js +911 -0
- package/dist/tools/refill-v3-campaign-work.d.ts +324 -0
- package/dist/tools/refill-v3-campaign-work.js +1015 -0
- package/dist/tools/refill-v3-continue-contract.d.ts +433 -0
- package/dist/tools/refill-v3-continue-contract.js +112 -0
- package/dist/tools/refill-v3-continue.d.ts +501 -0
- package/dist/tools/refill-v3-continue.js +654 -0
- package/dist/tools/refill-v3-edit-selected-campaign-contract.d.ts +208 -0
- package/dist/tools/refill-v3-edit-selected-campaign-contract.js +351 -0
- package/dist/tools/refill-v3-edit-selected-campaign.d.ts +232 -0
- package/dist/tools/refill-v3-edit-selected-campaign.js +147 -0
- package/dist/tools/refill-v3-fill-ready-contract.d.ts +198 -0
- package/dist/tools/refill-v3-fill-ready-contract.js +324 -0
- package/dist/tools/refill-v3-fill-ready.d.ts +199 -0
- package/dist/tools/refill-v3-fill-ready.js +136 -0
- package/dist/tools/refill-v3-source-family-dispatch.d.ts +249 -0
- package/dist/tools/refill-v3-source-family-dispatch.js +514 -0
- package/dist/tools/refill-v3-waterfall-contract.d.ts +156 -0
- package/dist/tools/refill-v3-waterfall-contract.js +181 -0
- package/dist/tools/refill-v3-waterfall.d.ts +136 -0
- package/dist/tools/refill-v3-waterfall.js +161 -0
- package/dist/tools/refill-v3-world-state-contract.d.ts +666 -0
- package/dist/tools/refill-v3-world-state-contract.js +790 -0
- package/dist/tools/refill-v3-world-state.d.ts +592 -0
- package/dist/tools/refill-v3-world-state.js +137 -0
- package/dist/tools/registry.d.ts +1710 -3
- package/dist/tools/registry.js +19 -0
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +89 -4
- package/skills/refill-sends-waterfall-order/SKILL.md +174 -0
- package/skills/refill-sends-work-campaign/SKILL.md +253 -0
- package/skills/refill-sends-workflow/SKILL.md +112 -5
- package/skills/refill-sends-workflow/core/flow.v3.json +209 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { SellableApiError } from "../api.js";
|
|
3
|
+
import { advanceRefillV3Tool, } from "./refill-v3-advance.js";
|
|
4
|
+
import { REFILL_V3_ACTION_RESULTS, REFILL_V3_PREPARATION_REUSE_POINTS, decodeRefillV3CampaignWorkOutput, runRefillV3CampaignWork, } from "./refill-v3-campaign-work.js";
|
|
5
|
+
import { REFILL_V3_CONTINUE_INPUT_SCHEMA, REFILL_V3_CONTINUE_OUTPUT_SCHEMA, decodeRefillV3ContinuePacket, } from "./refill-v3-continue-contract.js";
|
|
6
|
+
import { dispatchRefillV3SourceFamily, } from "./refill-v3-source-family-dispatch.js";
|
|
7
|
+
import { getRefillV3WorldStateTool } from "./refill-v3-world-state.js";
|
|
8
|
+
import { normalizeExplicitWorkspaceId } from "./workspace-context.js";
|
|
9
|
+
export const REFILL_V3_CONTINUE_TOOL_NAME = "refill_v3_continue";
|
|
10
|
+
export const refillV3ContinueToolDefinitions = [
|
|
11
|
+
{
|
|
12
|
+
name: REFILL_V3_CONTINUE_TOOL_NAME,
|
|
13
|
+
description: "Continue exactly one server-issued Refill V3 campaign-attention decision. The tool first calls the deterministic advance owner against a fresh observation. If the world changed, it returns that fresh result or fresh attention packet without executing the stale decision. Only an exact packet match can dispatch one existing campaign-work authority, which then re-observes the exact sender/date. The caller cannot supply batch sizes, readiness digests, retry counters, fill inputs, scheduler inputs, or idempotency keys.",
|
|
14
|
+
inputSchema: REFILL_V3_CONTINUE_INPUT_SCHEMA,
|
|
15
|
+
outputSchema: REFILL_V3_CONTINUE_OUTPUT_SCHEMA,
|
|
16
|
+
annotations: {
|
|
17
|
+
title: "Refill V3 Continue",
|
|
18
|
+
readOnlyHint: false,
|
|
19
|
+
destructiveHint: true,
|
|
20
|
+
idempotentHint: true,
|
|
21
|
+
openWorldHint: false,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
function canonical(value) {
|
|
26
|
+
const normalize = (current) => {
|
|
27
|
+
if (Array.isArray(current))
|
|
28
|
+
return current.map(normalize);
|
|
29
|
+
if (!current || typeof current !== "object")
|
|
30
|
+
return current;
|
|
31
|
+
return Object.fromEntries(Object.entries(current)
|
|
32
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
33
|
+
.map(([key, entry]) => [key, normalize(entry)]));
|
|
34
|
+
};
|
|
35
|
+
return JSON.stringify(normalize(value));
|
|
36
|
+
}
|
|
37
|
+
function samePacket(left, right) {
|
|
38
|
+
return canonical(left) === canonical(right);
|
|
39
|
+
}
|
|
40
|
+
function activePreparationConflict(error) {
|
|
41
|
+
if (!(error instanceof SellableApiError) || error.status !== 409)
|
|
42
|
+
return null;
|
|
43
|
+
let parsed = null;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(error.body);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
parsed = null;
|
|
49
|
+
}
|
|
50
|
+
const body = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
51
|
+
? parsed
|
|
52
|
+
: {};
|
|
53
|
+
const activeJobId = typeof body.activeJobId === "string" && body.activeJobId.trim()
|
|
54
|
+
? body.activeJobId
|
|
55
|
+
: null;
|
|
56
|
+
const message = typeof body.error === "string" ? body.error : error.message;
|
|
57
|
+
return activeJobId ||
|
|
58
|
+
/active prepare messages job already exists/i.test(message)
|
|
59
|
+
? { activeJobId }
|
|
60
|
+
: null;
|
|
61
|
+
}
|
|
62
|
+
const DEFAULT_PRODUCTION_OWNERS = {
|
|
63
|
+
selectCampaignCells,
|
|
64
|
+
selectRepairableCampaignCells,
|
|
65
|
+
getPrepareCampaignMessagesStatus,
|
|
66
|
+
startPrepareCampaignMessages,
|
|
67
|
+
getProviderPrompt,
|
|
68
|
+
executeRerunErroredCellsPrimitive,
|
|
69
|
+
refillV3SourceFamilyOwners,
|
|
70
|
+
dispatchRefillV3SourceFamily,
|
|
71
|
+
broadenRubrics,
|
|
72
|
+
getRefillV3WorldStateTool,
|
|
73
|
+
};
|
|
74
|
+
export async function runRefillV3CampaignWorkWithProductionOwners(input, owners = DEFAULT_PRODUCTION_OWNERS) {
|
|
75
|
+
const decoded = decodeRefillV3CampaignWorkOutput(input.output);
|
|
76
|
+
const chosen = decoded.ok ? decoded.value.actions[0] : null;
|
|
77
|
+
let diagnosticRows = [];
|
|
78
|
+
const record = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
79
|
+
? value
|
|
80
|
+
: null;
|
|
81
|
+
const stringValue = (value) => typeof value === "string" && value.trim() ? value : null;
|
|
82
|
+
const stringArray = (value) => Array.isArray(value)
|
|
83
|
+
? value.filter((entry) => typeof entry === "string" && entry.length > 0)
|
|
84
|
+
: [];
|
|
85
|
+
const fingerprint = (value) => createHash("sha256").update(canonical(value)).digest("hex");
|
|
86
|
+
const scopeEcho = `${input.packet.scope.workspaceId}:${input.packet.scope.campaignId}:${input.packet.scope.tableId}`;
|
|
87
|
+
const receipt = (params) => ({
|
|
88
|
+
outcome: params.outcome,
|
|
89
|
+
result: params.result,
|
|
90
|
+
scopeEcho,
|
|
91
|
+
idempotencyKey: params.idempotencyKey,
|
|
92
|
+
beforeFingerprint: params.outcome === "applied"
|
|
93
|
+
? input.packet.scope.evidenceFingerprint
|
|
94
|
+
: null,
|
|
95
|
+
afterFingerprint: params.outcome === "applied" ? fingerprint(params.after) : null,
|
|
96
|
+
changedFields: params.changedFields ?? [],
|
|
97
|
+
effectKey: params.outcome === "applied" ? params.idempotencyKey : null,
|
|
98
|
+
rollback: params.outcome === "applied"
|
|
99
|
+
? { available: false, input: null, result: null }
|
|
100
|
+
: null,
|
|
101
|
+
reobserveRequired: true,
|
|
102
|
+
preparationDelegation: params.preparationDelegation ?? null,
|
|
103
|
+
systemicError: false,
|
|
104
|
+
lowPass: false,
|
|
105
|
+
refusalReason: params.refusalReason ?? null,
|
|
106
|
+
});
|
|
107
|
+
const dependencies = {
|
|
108
|
+
async loadDiagnosticSample({ workspaceId, campaignId, tableId, limit, }) {
|
|
109
|
+
const regenerate = chosen?.action === "regenerate_stale_messages";
|
|
110
|
+
const repairing = chosen?.action === "rerun_errored_cells";
|
|
111
|
+
const selected = repairing
|
|
112
|
+
? await owners.selectRepairableCampaignCells({
|
|
113
|
+
workspaceId,
|
|
114
|
+
campaignId,
|
|
115
|
+
tableId,
|
|
116
|
+
limit,
|
|
117
|
+
})
|
|
118
|
+
: await owners.selectCampaignCells({
|
|
119
|
+
workspaceId,
|
|
120
|
+
campaignId,
|
|
121
|
+
tableId,
|
|
122
|
+
columnRole: regenerate ? "generateMessage" : "enrich",
|
|
123
|
+
// The diagnostic is the server-owned canonical frontier the model
|
|
124
|
+
// consumed, never the action's chosen subset. `rowIds` names which
|
|
125
|
+
// rows inside this frontier one authority may touch; using it to
|
|
126
|
+
// rebuild the diagnostic made the same identity appear twice with
|
|
127
|
+
// contradictory meanings and rejected otherwise-valid decisions.
|
|
128
|
+
rowSelector: regenerate
|
|
129
|
+
? { type: "needsGeneratedMessage" }
|
|
130
|
+
: { type: "needsEnrichment" },
|
|
131
|
+
diagnostic: { limit },
|
|
132
|
+
});
|
|
133
|
+
const diagnostic = record(record(selected)?.diagnostic);
|
|
134
|
+
const selectedRows = repairing
|
|
135
|
+
? record(selected)?.rows
|
|
136
|
+
: diagnostic?.rows;
|
|
137
|
+
diagnosticRows = Array.isArray(selectedRows)
|
|
138
|
+
? selectedRows.flatMap((entry) => {
|
|
139
|
+
const row = record(entry);
|
|
140
|
+
return row ? [row] : [];
|
|
141
|
+
})
|
|
142
|
+
: [];
|
|
143
|
+
const orderedRowIds = diagnosticRows.flatMap((row) => {
|
|
144
|
+
const rowId = stringValue(row.rowId);
|
|
145
|
+
return rowId ? [rowId] : [];
|
|
146
|
+
});
|
|
147
|
+
const failedRowIds = [];
|
|
148
|
+
const failureEvidence = [];
|
|
149
|
+
const unenrichedRowIds = [];
|
|
150
|
+
for (const row of diagnosticRows) {
|
|
151
|
+
const rowId = stringValue(row.rowId);
|
|
152
|
+
if (!rowId)
|
|
153
|
+
continue;
|
|
154
|
+
const cells = Array.isArray(row.cells)
|
|
155
|
+
? row.cells.flatMap((entry) => {
|
|
156
|
+
const cell = record(entry);
|
|
157
|
+
return cell ? [cell] : [];
|
|
158
|
+
})
|
|
159
|
+
: [];
|
|
160
|
+
const enrich = cells.find((cell) => cell.columnRole === "enrich");
|
|
161
|
+
if (!enrich ||
|
|
162
|
+
!["completed", "approved", "ready"].includes(String(enrich.status ?? "").toLowerCase())) {
|
|
163
|
+
unenrichedRowIds.push(rowId);
|
|
164
|
+
}
|
|
165
|
+
const failed = cells.find((cell) => {
|
|
166
|
+
const status = String(cell.status ?? "").toLowerCase();
|
|
167
|
+
return (Boolean(stringValue(cell.error)) ||
|
|
168
|
+
status === "error" ||
|
|
169
|
+
status === "failed" ||
|
|
170
|
+
status === "timeout");
|
|
171
|
+
});
|
|
172
|
+
if (failed) {
|
|
173
|
+
failedRowIds.push(rowId);
|
|
174
|
+
failureEvidence.push({
|
|
175
|
+
rowId,
|
|
176
|
+
error: stringValue(failed.error),
|
|
177
|
+
statusReason: stringValue(failed.statusReason),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const termination = record(repairing ? record(selected)?.termination : diagnostic?.termination);
|
|
182
|
+
const reason = stringValue(termination?.reason);
|
|
183
|
+
return {
|
|
184
|
+
limit,
|
|
185
|
+
orderedRowIds,
|
|
186
|
+
unenrichedRowIds,
|
|
187
|
+
failedRowIds,
|
|
188
|
+
failureEvidence,
|
|
189
|
+
termination: {
|
|
190
|
+
reason: reason === "frontier_exhausted" ||
|
|
191
|
+
reason === "scan_budget_exhausted" ||
|
|
192
|
+
reason === "limit_reached"
|
|
193
|
+
? reason
|
|
194
|
+
: "limit_reached",
|
|
195
|
+
rowsChecked: typeof termination?.rowsChecked === "number"
|
|
196
|
+
? termination.rowsChecked
|
|
197
|
+
: orderedRowIds.length,
|
|
198
|
+
rowsRemaining: typeof termination?.rowsRemaining === "number"
|
|
199
|
+
? termination.rowsRemaining
|
|
200
|
+
: null,
|
|
201
|
+
hasMore: termination?.hasMore !== false,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
async loadProviderPrompt({ provider, campaignOfferId }) {
|
|
206
|
+
if (provider !== "signal-discovery" &&
|
|
207
|
+
provider !== "apollo" &&
|
|
208
|
+
provider !== "sales-nav" &&
|
|
209
|
+
provider !== "prospeo") {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const promptPackage = owners.getProviderPrompt({
|
|
213
|
+
provider,
|
|
214
|
+
campaignOfferId,
|
|
215
|
+
confirmed: true,
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
provider,
|
|
219
|
+
campaignOfferId,
|
|
220
|
+
promptPackage,
|
|
221
|
+
promptVersion: fingerprint(promptPackage),
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
async readLiveTableJob({ tableId }) {
|
|
225
|
+
const status = record(await owners.getPrepareCampaignMessagesStatus({
|
|
226
|
+
workspaceId: input.packet.scope.workspaceId,
|
|
227
|
+
campaignId: input.packet.scope.campaignId,
|
|
228
|
+
tableId,
|
|
229
|
+
}));
|
|
230
|
+
const state = String(status?.status ?? "").toLowerCase();
|
|
231
|
+
const activity = record(status?.activity);
|
|
232
|
+
const id = stringValue(status?.jobId) ?? stringValue(status?.id);
|
|
233
|
+
if (activity?.terminal === true)
|
|
234
|
+
return null;
|
|
235
|
+
// World-state omits an expired raw running row so the campaign remains
|
|
236
|
+
// inspectable. This status read is also the canonical revival owner,
|
|
237
|
+
// however, and the row still holds the table's active-job uniqueness
|
|
238
|
+
// fence. Surface that newer dispatch-boundary fact as queued so campaign
|
|
239
|
+
// work returns a bounded wait instead of attempting an impossible second
|
|
240
|
+
// start and repeating a no_effect receipt forever.
|
|
241
|
+
if (activity?.state === "expired_retryable") {
|
|
242
|
+
return id ? { id, kind: "message_generation", status: "queued" } : null;
|
|
243
|
+
}
|
|
244
|
+
return id && (state === "queued" || state === "running")
|
|
245
|
+
? { id, kind: "message_generation", status: state }
|
|
246
|
+
: null;
|
|
247
|
+
},
|
|
248
|
+
async dispatchAuthority(request) {
|
|
249
|
+
const result = REFILL_V3_ACTION_RESULTS[request.action];
|
|
250
|
+
if (request.action === "enrich_bounded_sample") {
|
|
251
|
+
let reattached = false;
|
|
252
|
+
let prepared;
|
|
253
|
+
try {
|
|
254
|
+
prepared = await owners.startPrepareCampaignMessages({
|
|
255
|
+
workspaceId: request.scope.workspaceId,
|
|
256
|
+
campaignId: request.scope.campaignId,
|
|
257
|
+
tableId: request.scope.tableId,
|
|
258
|
+
senderId: request.scope.senderId,
|
|
259
|
+
actionType: input.packet.scope.actionType,
|
|
260
|
+
approvalMode: "approve",
|
|
261
|
+
autoContinue: true,
|
|
262
|
+
requestSource: "refill_v3_continue",
|
|
263
|
+
rowSelector: {
|
|
264
|
+
type: "rowIds",
|
|
265
|
+
rowIds: [...(request.rowIds ?? [])],
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
const conflict = activePreparationConflict(error);
|
|
271
|
+
if (!conflict)
|
|
272
|
+
throw error;
|
|
273
|
+
const current = await owners.getPrepareCampaignMessagesStatus({
|
|
274
|
+
workspaceId: request.scope.workspaceId,
|
|
275
|
+
campaignId: request.scope.campaignId,
|
|
276
|
+
tableId: request.scope.tableId,
|
|
277
|
+
...(conflict.activeJobId ? { jobId: conflict.activeJobId } : {}),
|
|
278
|
+
});
|
|
279
|
+
const currentRecord = record(current) ?? {};
|
|
280
|
+
prepared = {
|
|
281
|
+
...currentRecord,
|
|
282
|
+
jobId: stringValue(currentRecord.jobId) ?? conflict.activeJobId,
|
|
283
|
+
};
|
|
284
|
+
reattached = true;
|
|
285
|
+
}
|
|
286
|
+
const preparedRecord = record(prepared);
|
|
287
|
+
const delegation = Object.fromEntries(REFILL_V3_PREPARATION_REUSE_POINTS.map((point) => {
|
|
288
|
+
const facts = {
|
|
289
|
+
frontier: request.rowIds ?? [],
|
|
290
|
+
remainingGapPassEvidence: input.packet.evidence.supply,
|
|
291
|
+
serialBatchCap: null,
|
|
292
|
+
totalRowBudget: null,
|
|
293
|
+
replayKey: stringValue(preparedRecord?.requestHash) ??
|
|
294
|
+
stringValue(preparedRecord?.jobId) ??
|
|
295
|
+
request.idempotencyKey,
|
|
296
|
+
lowPassGuard: "backend_owned",
|
|
297
|
+
reclaimBehavior: "backend_owned",
|
|
298
|
+
};
|
|
299
|
+
return [point, facts[point]];
|
|
300
|
+
}));
|
|
301
|
+
return receipt({
|
|
302
|
+
outcome: reattached ? "no_effect" : "applied",
|
|
303
|
+
result,
|
|
304
|
+
idempotencyKey: request.idempotencyKey,
|
|
305
|
+
changedFields: reattached ? [] : ["campaignMessagePreparationJob"],
|
|
306
|
+
after: prepared,
|
|
307
|
+
preparationDelegation: delegation,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (request.action === "rerun_errored_cells") {
|
|
311
|
+
const selected = new Set(request.rowIds ?? []);
|
|
312
|
+
const operations = new Map();
|
|
313
|
+
for (const row of diagnosticRows) {
|
|
314
|
+
const rowId = stringValue(row.rowId);
|
|
315
|
+
if (!rowId || !selected.has(rowId))
|
|
316
|
+
continue;
|
|
317
|
+
const cells = Array.isArray(row.cells)
|
|
318
|
+
? row.cells.flatMap((entry) => {
|
|
319
|
+
const cell = record(entry);
|
|
320
|
+
return cell ? [cell] : [];
|
|
321
|
+
})
|
|
322
|
+
: [];
|
|
323
|
+
for (const cell of cells) {
|
|
324
|
+
const role = cell.columnRole;
|
|
325
|
+
const status = String(cell.status ?? "").toLowerCase();
|
|
326
|
+
if (role !== "enrich" &&
|
|
327
|
+
role !== "icpScore" &&
|
|
328
|
+
role !== "generateMessage") {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (!stringValue(cell.error) &&
|
|
332
|
+
status !== "error" &&
|
|
333
|
+
status !== "failed" &&
|
|
334
|
+
status !== "timeout") {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const bucket = operations.get(role) ?? {
|
|
338
|
+
rowIds: [],
|
|
339
|
+
cellIds: [],
|
|
340
|
+
};
|
|
341
|
+
bucket.rowIds.push(rowId);
|
|
342
|
+
const cellId = stringValue(cell.cellId);
|
|
343
|
+
if (cellId)
|
|
344
|
+
bucket.cellIds.push(cellId);
|
|
345
|
+
operations.set(role, bucket);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const action = {
|
|
349
|
+
type: "rerun_errored_cells",
|
|
350
|
+
ids: {
|
|
351
|
+
campaignId: request.scope.campaignId,
|
|
352
|
+
tableId: request.scope.tableId,
|
|
353
|
+
},
|
|
354
|
+
toolInput: {
|
|
355
|
+
operations: [...operations.entries()].map(([columnRole, operation]) => ({
|
|
356
|
+
columnRole,
|
|
357
|
+
rowSelector: {
|
|
358
|
+
type: "rowIds",
|
|
359
|
+
rowIds: [...new Set(operation.rowIds)],
|
|
360
|
+
},
|
|
361
|
+
cellIds: [...new Set(operation.cellIds)],
|
|
362
|
+
})),
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
const executed = await owners.executeRerunErroredCellsPrimitive({
|
|
366
|
+
action,
|
|
367
|
+
workspaceId: request.scope.workspaceId,
|
|
368
|
+
});
|
|
369
|
+
return executed.executed
|
|
370
|
+
? receipt({
|
|
371
|
+
outcome: "applied",
|
|
372
|
+
result,
|
|
373
|
+
idempotencyKey: request.idempotencyKey,
|
|
374
|
+
changedFields: ["workflowTableCells"],
|
|
375
|
+
after: executed,
|
|
376
|
+
})
|
|
377
|
+
: receipt({
|
|
378
|
+
outcome: "refused",
|
|
379
|
+
result,
|
|
380
|
+
idempotencyKey: request.idempotencyKey,
|
|
381
|
+
refusalReason: executed.reason,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (request.action === "regenerate_stale_messages") {
|
|
385
|
+
let reattached = false;
|
|
386
|
+
let prepared;
|
|
387
|
+
try {
|
|
388
|
+
prepared = await owners.startPrepareCampaignMessages({
|
|
389
|
+
workspaceId: request.scope.workspaceId,
|
|
390
|
+
campaignId: request.scope.campaignId,
|
|
391
|
+
tableId: request.scope.tableId,
|
|
392
|
+
senderId: request.scope.senderId,
|
|
393
|
+
actionType: input.packet.scope.actionType,
|
|
394
|
+
approvalMode: "mark_ready",
|
|
395
|
+
autoContinue: true,
|
|
396
|
+
requestSource: "refill_v3_continue",
|
|
397
|
+
rowSelector: request.rowIds?.length
|
|
398
|
+
? { type: "rowIds", rowIds: [...request.rowIds] }
|
|
399
|
+
: { type: "needsGeneratedMessage" },
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
const conflict = activePreparationConflict(error);
|
|
404
|
+
if (!conflict)
|
|
405
|
+
throw error;
|
|
406
|
+
const current = await owners.getPrepareCampaignMessagesStatus({
|
|
407
|
+
workspaceId: request.scope.workspaceId,
|
|
408
|
+
campaignId: request.scope.campaignId,
|
|
409
|
+
tableId: request.scope.tableId,
|
|
410
|
+
...(conflict.activeJobId ? { jobId: conflict.activeJobId } : {}),
|
|
411
|
+
});
|
|
412
|
+
const currentRecord = record(current) ?? {};
|
|
413
|
+
prepared = {
|
|
414
|
+
...currentRecord,
|
|
415
|
+
jobId: stringValue(currentRecord.jobId) ?? conflict.activeJobId,
|
|
416
|
+
};
|
|
417
|
+
reattached = true;
|
|
418
|
+
}
|
|
419
|
+
const preparedRecord = record(prepared);
|
|
420
|
+
const delegation = Object.fromEntries(REFILL_V3_PREPARATION_REUSE_POINTS.map((point) => {
|
|
421
|
+
const facts = {
|
|
422
|
+
frontier: request.rowIds ?? "needsGeneratedMessage",
|
|
423
|
+
remainingGapPassEvidence: input.packet.evidence.supply,
|
|
424
|
+
serialBatchCap: null,
|
|
425
|
+
totalRowBudget: null,
|
|
426
|
+
replayKey: stringValue(preparedRecord?.requestHash) ??
|
|
427
|
+
stringValue(preparedRecord?.jobId) ??
|
|
428
|
+
request.idempotencyKey,
|
|
429
|
+
lowPassGuard: "backend_owned",
|
|
430
|
+
reclaimBehavior: "backend_owned",
|
|
431
|
+
};
|
|
432
|
+
return [point, facts[point]];
|
|
433
|
+
}));
|
|
434
|
+
return receipt({
|
|
435
|
+
outcome: reattached ? "no_effect" : "applied",
|
|
436
|
+
result,
|
|
437
|
+
idempotencyKey: request.idempotencyKey,
|
|
438
|
+
changedFields: reattached ? [] : ["campaignMessagePreparationJob"],
|
|
439
|
+
after: prepared,
|
|
440
|
+
preparationDelegation: delegation,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (request.action === "broaden_source_family" ||
|
|
444
|
+
(request.action === "patch_source_criteria" &&
|
|
445
|
+
request.sourceCommand !== null) ||
|
|
446
|
+
(request.action === "record_source_frontier_exhausted" &&
|
|
447
|
+
request.sourceCommand !== null)) {
|
|
448
|
+
if (!request.sourceCommand) {
|
|
449
|
+
return receipt({
|
|
450
|
+
outcome: "refused",
|
|
451
|
+
result,
|
|
452
|
+
idempotencyKey: request.idempotencyKey,
|
|
453
|
+
refusalReason: "source_command_missing",
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
const sourceOwners = owners.refillV3SourceFamilyOwners(request.scope.workspaceId);
|
|
457
|
+
const priorSourceFingerprint = input.packet.evidence.sourceLineage.at(-1) ?? null;
|
|
458
|
+
// The attention packet's sourceLineage is server-loaded from the
|
|
459
|
+
// bounded inventory owner. These inventory identities prove that
|
|
460
|
+
// provenance exists, but they are not themselves completed searches,
|
|
461
|
+
// so they must never enter the searched-negative set.
|
|
462
|
+
const lineage = priorSourceFingerprint === null
|
|
463
|
+
? null
|
|
464
|
+
: input.packet.evidence.sourceLineage.map((sourceFingerprint) => ({
|
|
465
|
+
campaignId: request.scope.campaignId,
|
|
466
|
+
family: input.packet.evidence.leadSourceFamily,
|
|
467
|
+
provider: input.packet.evidence.provider,
|
|
468
|
+
sourceFingerprint,
|
|
469
|
+
semanticFingerprint: sourceFingerprint,
|
|
470
|
+
keywordBatch: null,
|
|
471
|
+
postIds: null,
|
|
472
|
+
importedCount: 0,
|
|
473
|
+
yieldEvidence: null,
|
|
474
|
+
observedAt: request.scope.date,
|
|
475
|
+
searchCompleted: false,
|
|
476
|
+
}));
|
|
477
|
+
const sourceOutcome = await owners.dispatchRefillV3SourceFamily({
|
|
478
|
+
scope: {
|
|
479
|
+
workspaceId: request.scope.workspaceId,
|
|
480
|
+
campaignId: request.scope.campaignId,
|
|
481
|
+
tableId: request.scope.tableId,
|
|
482
|
+
sourceLeadListId: input.packet.evidence.selectedLeadListId ?? null,
|
|
483
|
+
senderId: request.scope.senderId,
|
|
484
|
+
date: request.scope.date,
|
|
485
|
+
count: Math.max(0, input.packet.evidence.remainingGap),
|
|
486
|
+
campaignVersion: request.scope.campaignVersion,
|
|
487
|
+
evidenceFingerprint: request.scope.evidenceFingerprint,
|
|
488
|
+
priorSourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
|
|
489
|
+
idempotencyKey: request.idempotencyKey,
|
|
490
|
+
providerSearchReference: input.packet.evidence.providerSearchReference ?? null,
|
|
491
|
+
},
|
|
492
|
+
action: request.action === "record_source_frontier_exhausted"
|
|
493
|
+
? "record_exhaustion"
|
|
494
|
+
: request.action === "broaden_source_family"
|
|
495
|
+
? "broaden"
|
|
496
|
+
: "continue",
|
|
497
|
+
command: request.sourceCommand,
|
|
498
|
+
owners: sourceOwners,
|
|
499
|
+
lineage,
|
|
500
|
+
observedFamily: input.packet.evidence.leadSourceFamily,
|
|
501
|
+
activeJob: input.packet.evidence.activeJob,
|
|
502
|
+
providerPromptPackage: request.providerPrompt?.promptPackage ?? null,
|
|
503
|
+
candidateVerdicts: request.candidateVerdicts,
|
|
504
|
+
exhaustionEvidence: request.exhaustionVerdict?.exhausted === true
|
|
505
|
+
? request.sourceCommand
|
|
506
|
+
? (chosen?.exhaustionEvidence ?? null)
|
|
507
|
+
: null
|
|
508
|
+
: null,
|
|
509
|
+
});
|
|
510
|
+
if (sourceOutcome.kind === "dispatched") {
|
|
511
|
+
const changed = sourceOutcome.receipt.importedCount > 0;
|
|
512
|
+
return receipt({
|
|
513
|
+
outcome: changed ? "applied" : "no_effect",
|
|
514
|
+
result,
|
|
515
|
+
idempotencyKey: request.idempotencyKey,
|
|
516
|
+
changedFields: changed ? ["sourceInventory"] : [],
|
|
517
|
+
after: sourceOutcome.receipt,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
if (sourceOutcome.kind === "recorded_exhaustion") {
|
|
521
|
+
return receipt({
|
|
522
|
+
outcome: "no_effect",
|
|
523
|
+
result,
|
|
524
|
+
idempotencyKey: request.idempotencyKey,
|
|
525
|
+
after: sourceOutcome.receipt,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
if (sourceOutcome.kind === "refused") {
|
|
529
|
+
return receipt({
|
|
530
|
+
outcome: "refused",
|
|
531
|
+
result,
|
|
532
|
+
idempotencyKey: request.idempotencyKey,
|
|
533
|
+
refusalReason: `${sourceOutcome.refusal}:${sourceOutcome.detail}`,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return receipt({
|
|
537
|
+
outcome: sourceOutcome.failureClass === "uncertain"
|
|
538
|
+
? "uncertain"
|
|
539
|
+
: "refused",
|
|
540
|
+
result,
|
|
541
|
+
idempotencyKey: request.idempotencyKey,
|
|
542
|
+
refusalReason: sourceOutcome.detail,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (request.action === "request_rubric_trial") {
|
|
546
|
+
const trial = await owners.broadenRubrics({
|
|
547
|
+
type: "broaden_rubrics",
|
|
548
|
+
ids: {
|
|
549
|
+
campaignId: request.scope.campaignId,
|
|
550
|
+
tableId: request.scope.tableId,
|
|
551
|
+
},
|
|
552
|
+
toolInput: {
|
|
553
|
+
sampleRowIds: request.rowIds ?? [],
|
|
554
|
+
candidateVerdicts: request.candidateVerdicts ?? [],
|
|
555
|
+
},
|
|
556
|
+
}, request.scope.workspaceId);
|
|
557
|
+
return trial.status === "executed_and_reread"
|
|
558
|
+
? receipt({
|
|
559
|
+
outcome: "applied",
|
|
560
|
+
result,
|
|
561
|
+
idempotencyKey: request.idempotencyKey,
|
|
562
|
+
changedFields: ["campaignRubric"],
|
|
563
|
+
after: trial.result,
|
|
564
|
+
})
|
|
565
|
+
: receipt({
|
|
566
|
+
outcome: "refused",
|
|
567
|
+
result,
|
|
568
|
+
idempotencyKey: request.idempotencyKey,
|
|
569
|
+
refusalReason: trial.refusalReason ?? "rubric_trial_refused",
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
if (request.action === "record_source_frontier_exhausted") {
|
|
573
|
+
return receipt({
|
|
574
|
+
outcome: "no_effect",
|
|
575
|
+
result,
|
|
576
|
+
idempotencyKey: request.idempotencyKey,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return receipt({
|
|
580
|
+
outcome: "refused",
|
|
581
|
+
result,
|
|
582
|
+
idempotencyKey: request.idempotencyKey,
|
|
583
|
+
refusalReason: "This decision needs the narrow campaign-edit authority and no complete edit packet was supplied.",
|
|
584
|
+
});
|
|
585
|
+
},
|
|
586
|
+
async reobserveExactSenderDates({ workspaceId, senderId, date }) {
|
|
587
|
+
const observation = await owners.getRefillV3WorldStateTool({
|
|
588
|
+
workspaceId,
|
|
589
|
+
scope: {
|
|
590
|
+
kind: "exact_sender_dates",
|
|
591
|
+
senderDates: [{ senderId, dates: [date] }],
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
return {
|
|
595
|
+
observed: true,
|
|
596
|
+
senderId,
|
|
597
|
+
date,
|
|
598
|
+
lanes: observation.rows
|
|
599
|
+
.filter((row) => row.sender.id === senderId && row.date === date)
|
|
600
|
+
.map((row) => row.lane),
|
|
601
|
+
evidenceFingerprint: fingerprint(observation),
|
|
602
|
+
};
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
return runRefillV3CampaignWork({
|
|
606
|
+
packet: input.packet,
|
|
607
|
+
output: input.output,
|
|
608
|
+
dependencies,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
const DEFAULT_DEPENDENCIES = {
|
|
612
|
+
advance: advanceRefillV3Tool,
|
|
613
|
+
runCampaignWork: runRefillV3CampaignWorkWithProductionOwners,
|
|
614
|
+
};
|
|
615
|
+
export async function continueRefillV3(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
616
|
+
const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
|
|
617
|
+
if (!workspaceId || workspaceId !== input.packet.scope.workspaceId) {
|
|
618
|
+
throw new Error("refill_v3_continue requires one exact workspace matching the server-issued packet.");
|
|
619
|
+
}
|
|
620
|
+
const advanceResult = await dependencies.advance({
|
|
621
|
+
workspaceId,
|
|
622
|
+
scope: input.scope,
|
|
623
|
+
...(input.yolo === undefined ? {} : { yolo: input.yolo }),
|
|
624
|
+
});
|
|
625
|
+
if (advanceResult.kind !== "campaign_attention_required") {
|
|
626
|
+
return { kind: "world_advanced", advanceResult };
|
|
627
|
+
}
|
|
628
|
+
if (!samePacket(input.packet, advanceResult.packet)) {
|
|
629
|
+
return { kind: "attention_refresh_required", advanceResult };
|
|
630
|
+
}
|
|
631
|
+
const outcome = await dependencies.runCampaignWork({
|
|
632
|
+
packet: advanceResult.packet,
|
|
633
|
+
output: input.decision,
|
|
634
|
+
});
|
|
635
|
+
return { kind: "campaign_work_completed", outcome };
|
|
636
|
+
}
|
|
637
|
+
export async function continueRefillV3Tool(input) {
|
|
638
|
+
const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
|
|
639
|
+
const packet = decodeRefillV3ContinuePacket(input.packet);
|
|
640
|
+
if (!workspaceId || !packet) {
|
|
641
|
+
throw new Error("refill_v3_continue requires an exact workspaceId and a valid server-issued attention packet.");
|
|
642
|
+
}
|
|
643
|
+
return continueRefillV3({
|
|
644
|
+
workspaceId,
|
|
645
|
+
scope: input.scope,
|
|
646
|
+
...(input.yolo === undefined ? {} : { yolo: input.yolo }),
|
|
647
|
+
packet,
|
|
648
|
+
decision: input.decision,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
import { getPrepareCampaignMessagesStatus, startPrepareCampaignMessages, } from "./campaign-message-preparation.js";
|
|
652
|
+
import { selectCampaignCells, selectRepairableCampaignCells, } from "./campaign-processing.js";
|
|
653
|
+
import { getProviderPrompt } from "./leads.js";
|
|
654
|
+
import { broadenRubrics, executeRerunErroredCellsPrimitive, refillV3SourceFamilyOwners, } from "./refill-executors.js";
|