@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.
Files changed (54) hide show
  1. package/dist/agent-tool-names.d.ts +2 -2
  2. package/dist/agent-tool-names.js +14 -0
  3. package/dist/scheduler-envelope.d.ts +190 -0
  4. package/dist/scheduler-envelope.js +412 -0
  5. package/dist/server.js +28 -2
  6. package/dist/tools/campaign-message-preparation.d.ts +25 -0
  7. package/dist/tools/campaign-message-preparation.js +37 -0
  8. package/dist/tools/campaign-processing.d.ts +74 -1
  9. package/dist/tools/campaign-processing.js +57 -1
  10. package/dist/tools/prompts.d.ts +15 -3
  11. package/dist/tools/prompts.js +25 -0
  12. package/dist/tools/refill-executors.d.ts +43 -70
  13. package/dist/tools/refill-executors.js +201 -273
  14. package/dist/tools/refill-sends.d.ts +3 -3
  15. package/dist/tools/refill-sends.js +1 -1
  16. package/dist/tools/refill-target-plan.js +1 -1
  17. package/dist/tools/refill-v3-advance-contract.d.ts +618 -0
  18. package/dist/tools/refill-v3-advance-contract.js +920 -0
  19. package/dist/tools/refill-v3-advance.d.ts +397 -0
  20. package/dist/tools/refill-v3-advance.js +75 -0
  21. package/dist/tools/refill-v3-attention-packet-contract.d.ts +608 -0
  22. package/dist/tools/refill-v3-attention-packet-contract.js +911 -0
  23. package/dist/tools/refill-v3-campaign-work.d.ts +324 -0
  24. package/dist/tools/refill-v3-campaign-work.js +1015 -0
  25. package/dist/tools/refill-v3-continue-contract.d.ts +433 -0
  26. package/dist/tools/refill-v3-continue-contract.js +112 -0
  27. package/dist/tools/refill-v3-continue.d.ts +501 -0
  28. package/dist/tools/refill-v3-continue.js +654 -0
  29. package/dist/tools/refill-v3-edit-selected-campaign-contract.d.ts +208 -0
  30. package/dist/tools/refill-v3-edit-selected-campaign-contract.js +351 -0
  31. package/dist/tools/refill-v3-edit-selected-campaign.d.ts +232 -0
  32. package/dist/tools/refill-v3-edit-selected-campaign.js +147 -0
  33. package/dist/tools/refill-v3-fill-ready-contract.d.ts +198 -0
  34. package/dist/tools/refill-v3-fill-ready-contract.js +324 -0
  35. package/dist/tools/refill-v3-fill-ready.d.ts +199 -0
  36. package/dist/tools/refill-v3-fill-ready.js +136 -0
  37. package/dist/tools/refill-v3-source-family-dispatch.d.ts +249 -0
  38. package/dist/tools/refill-v3-source-family-dispatch.js +514 -0
  39. package/dist/tools/refill-v3-waterfall-contract.d.ts +156 -0
  40. package/dist/tools/refill-v3-waterfall-contract.js +181 -0
  41. package/dist/tools/refill-v3-waterfall.d.ts +136 -0
  42. package/dist/tools/refill-v3-waterfall.js +161 -0
  43. package/dist/tools/refill-v3-world-state-contract.d.ts +666 -0
  44. package/dist/tools/refill-v3-world-state-contract.js +790 -0
  45. package/dist/tools/refill-v3-world-state.d.ts +592 -0
  46. package/dist/tools/refill-v3-world-state.js +137 -0
  47. package/dist/tools/registry.d.ts +1710 -3
  48. package/dist/tools/registry.js +19 -0
  49. package/package.json +1 -1
  50. package/skills/refill-sends/SKILL.md +89 -4
  51. package/skills/refill-sends-waterfall-order/SKILL.md +174 -0
  52. package/skills/refill-sends-work-campaign/SKILL.md +253 -0
  53. package/skills/refill-sends-workflow/SKILL.md +112 -5
  54. 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";