@sellable/mcp 0.1.958 → 0.1.959
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/tools/refill-v3-advance.js +1 -1
- package/dist/tools/refill-v3-campaign-work.d.ts +6 -0
- package/dist/tools/refill-v3-continue-contract.d.ts +4 -0
- package/dist/tools/refill-v3-continue-contract.js +3 -0
- package/dist/tools/refill-v3-continue.d.ts +18 -5
- package/dist/tools/refill-v3-continue.js +177 -85
- package/dist/tools/registry.d.ts +4 -0
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +7 -0
- package/skills/refill-sends-workflow/SKILL.md +7 -0
|
@@ -35,7 +35,7 @@ export const REFILL_V3_ADVANCE_TOOL_NAME = "refill_v3_advance";
|
|
|
35
35
|
export const refillV3AdvanceToolDefinitions = [
|
|
36
36
|
{
|
|
37
37
|
name: REFILL_V3_ADVANCE_TOOL_NAME,
|
|
38
|
-
description: "THE DEFAULT REFILL ENTRY POINT. Whenever this tool is exposed, every ordinary request to refill, top up, fill, max out, load up, or otherwise add sends for a workspace, a sender, or a day routes HERE, and `refill_sends` is the superseded V1 fallback that applies only to servers which do NOT expose this tool. Never infer the route: use tool availability. When the operator names a workspace, resolve it with `list_workspaces`; fail closed if no accessible workspace matches or more than one matches, and never mutate the active workspace. Use configured context only when the operator named none. Start here: do not open the refill ladder with `get_refill_target_plan` or `refill_sends`. Advance exactly ONE fresh, fenced Refill V3 step for one workspace and scope. Pass only {workspaceId, scope}; refill defaults to yolo:true. YOLO is an autonomy grant, not a mode. Pass explicit yolo:false only when the operator asks for a conservative, review-first run. MCP carries signed run continuity internally. An advanced result is ALREADY EXECUTED and has reread its authority. For campaign_attention_required, call refill_v3_continue first with no decision: live-job waiting, exact actionable enrichment, and same-template generation are deterministic. Only model_decision_required routes the returned accurate campaign packet to refill-sends-work-campaign for one repair, criteria, rubric, waterfall, or source-expansion judgment. Repeat until complete, awaiting_external_change, or blocked, and report every terminal sender/date/lane row.",
|
|
38
|
+
description: "THE DEFAULT REFILL ENTRY POINT. Whenever this tool is exposed, every ordinary request to refill, top up, fill, max out, load up, or otherwise add sends for a workspace, a sender, or a day routes HERE, and `refill_sends` is the superseded V1 fallback that applies only to servers which do NOT expose this tool. Never infer the route: use tool availability. When the operator names a workspace, resolve it with `list_workspaces`; fail closed if no accessible workspace matches or more than one matches, and never mutate the active workspace. Use configured context only when the operator named none. Start here: do not open the refill ladder with `get_refill_target_plan` or `refill_sends`. Advance exactly ONE fresh, fenced Refill V3 step for one workspace and scope. Pass only {workspaceId, scope}; refill defaults to yolo:true. YOLO is an autonomy grant, not a mode. Pass explicit yolo:false only when the operator asks for a conservative, review-first run. MCP carries signed run continuity internally. An advanced result is ALREADY EXECUTED and has reread its authority. For campaign_attention_required, call refill_v3_continue first with no decision: live-job waiting, exact actionable enrichment, and same-template generation are deterministic. Only model_decision_required routes the returned accurate campaign packet to refill-sends-work-campaign for one repair, criteria, rubric, waterfall, or source-expansion judgment. Repeat until complete, awaiting_external_change, or blocked, and report every terminal sender/date/lane row. A thrown error is not a terminal: call this tool once more with the identical workspaceId and scope (the MCP keeps the run current) and report a blocker only if it fails the same way.",
|
|
39
39
|
inputSchema: REFILL_V3_ADVANCE_INPUT_SCHEMA,
|
|
40
40
|
outputSchema: REFILL_V3_ADVANCE_OUTPUT_SCHEMA,
|
|
41
41
|
annotations: {
|
|
@@ -249,6 +249,12 @@ export type RefillV3AuthorityReceipt = {
|
|
|
249
249
|
*/
|
|
250
250
|
externalReasonCode?: "endpoint_temporarily_unavailable";
|
|
251
251
|
correctiveContext?: RefillV3AttentionCorrection;
|
|
252
|
+
/**
|
|
253
|
+
* Present only when the run's source-cycle bookkeeping for an effect that
|
|
254
|
+
* already happened could not be written. The effect and this receipt stand;
|
|
255
|
+
* the fresh re-observation decides the next step.
|
|
256
|
+
*/
|
|
257
|
+
sourceCycleRecordError?: string;
|
|
252
258
|
/**
|
|
253
259
|
* V31-BATCH-001: the deterministic enrichment batch ladder's decision and
|
|
254
260
|
* its numeric basis, present only on `enrich_bounded_sample` receipts so
|
|
@@ -270,6 +270,10 @@ export declare const REFILL_V3_CONTINUE_OUTPUT_SCHEMA: Readonly<{
|
|
|
270
270
|
readonly receiptPersistence: {
|
|
271
271
|
readonly type: "object";
|
|
272
272
|
};
|
|
273
|
+
readonly recoveredFromError: {
|
|
274
|
+
readonly type: "string";
|
|
275
|
+
readonly minLength: 1;
|
|
276
|
+
};
|
|
273
277
|
};
|
|
274
278
|
readonly additionalProperties: false;
|
|
275
279
|
readonly allOf: readonly [{
|
|
@@ -142,6 +142,9 @@ export const REFILL_V3_CONTINUE_OUTPUT_SCHEMA = Object.freeze({
|
|
|
142
142
|
// consumed a sequence, so the caller can continue the SAME run.
|
|
143
143
|
runToken: { type: "string", minLength: 1 },
|
|
144
144
|
receiptPersistence: { type: "object" },
|
|
145
|
+
// The work's error when its one re-observation found the world had moved;
|
|
146
|
+
// the result is that fresh observation, handled by its own kind.
|
|
147
|
+
recoveredFromError: { type: "string", minLength: 1 },
|
|
145
148
|
},
|
|
146
149
|
additionalProperties: false,
|
|
147
150
|
allOf: [
|
|
@@ -265,6 +265,10 @@ export declare const refillV3ContinueToolDefinitions: {
|
|
|
265
265
|
readonly receiptPersistence: {
|
|
266
266
|
readonly type: "object";
|
|
267
267
|
};
|
|
268
|
+
readonly recoveredFromError: {
|
|
269
|
+
readonly type: "string";
|
|
270
|
+
readonly minLength: 1;
|
|
271
|
+
};
|
|
268
272
|
};
|
|
269
273
|
readonly additionalProperties: false;
|
|
270
274
|
readonly allOf: readonly [{
|
|
@@ -390,31 +394,38 @@ export type RefillV3ContinueInput = {
|
|
|
390
394
|
*/
|
|
391
395
|
decision?: unknown;
|
|
392
396
|
};
|
|
397
|
+
/**
|
|
398
|
+
* Present when this continuation's work failed and its one re-observation
|
|
399
|
+
* found the world had moved; the result is that fresh observation.
|
|
400
|
+
*/
|
|
401
|
+
type RefillV3RecoveredFromError = {
|
|
402
|
+
recoveredFromError?: string;
|
|
403
|
+
};
|
|
393
404
|
export type RefillV3ContinueResult = {
|
|
394
405
|
kind: "continuation_pending";
|
|
395
406
|
retryAfterMs: number;
|
|
396
407
|
runningForSeconds?: number;
|
|
397
|
-
} | {
|
|
408
|
+
} | ({
|
|
398
409
|
kind: "world_advanced";
|
|
399
410
|
advanceResult: Extract<RefillV3AdvanceResult, {
|
|
400
411
|
kind: "advanced";
|
|
401
412
|
}>;
|
|
402
|
-
} | {
|
|
413
|
+
} & RefillV3RecoveredFromError) | ({
|
|
403
414
|
kind: "world_step_pending";
|
|
404
415
|
advanceResult: Extract<RefillV3AdvanceResult, {
|
|
405
416
|
kind: "run_step_pending";
|
|
406
417
|
}>;
|
|
407
|
-
} | {
|
|
418
|
+
} & RefillV3RecoveredFromError) | ({
|
|
408
419
|
kind: "world_terminal";
|
|
409
420
|
advanceResult: Extract<RefillV3AdvanceResult, {
|
|
410
421
|
kind: "awaiting_external_change" | "complete" | "blocked";
|
|
411
422
|
}>;
|
|
412
|
-
} | {
|
|
423
|
+
} & RefillV3RecoveredFromError) | ({
|
|
413
424
|
kind: "attention_refresh_required";
|
|
414
425
|
advanceResult: Extract<RefillV3AdvanceResult, {
|
|
415
426
|
kind: "campaign_attention_required";
|
|
416
427
|
}>;
|
|
417
|
-
} | {
|
|
428
|
+
} & RefillV3RecoveredFromError) | {
|
|
418
429
|
kind: "model_decision_required";
|
|
419
430
|
runToken: string;
|
|
420
431
|
packet: RefillV3CampaignAttentionPacket;
|
|
@@ -566,6 +577,8 @@ type RefillV3ContinueProductionOwners = {
|
|
|
566
577
|
protectedFitDigest: string;
|
|
567
578
|
receipt: Record<string, unknown>;
|
|
568
579
|
}) => Promise<unknown>;
|
|
580
|
+
/** Injectable pause for the bounded transient retry of a source-cycle write. */
|
|
581
|
+
sleep?: (ms: number) => Promise<void>;
|
|
569
582
|
};
|
|
570
583
|
export declare function runRefillV3CampaignWorkWithProductionOwners(input: {
|
|
571
584
|
packet: RefillV3CampaignAttentionPacket;
|
|
@@ -18,7 +18,7 @@ export const REFILL_V3_CONTINUE_TOOL_NAME = "refill_v3_continue";
|
|
|
18
18
|
export const refillV3ContinueToolDefinitions = [
|
|
19
19
|
{
|
|
20
20
|
name: REFILL_V3_CONTINUE_TOOL_NAME,
|
|
21
|
-
description: "Continue exactly one server-issued Refill V3 campaign-attention packet. continuation_pending means the same operation is still running: wait retryAfterMs and call this tool with identical scope and no decision until it returns. Reconcile transport timeouts the same way; do not call advance or dispatch another search while pending. Call first without decision: after a fresh revalidation, the tool deterministically owns live-job waiting, selected-source row copying, exact actionable enrichment, and same-template message generation. It returns model_decision_required only when the accurate packet needs a genuine repair, criteria, rubric, waterfall, or source-expansion judgment; then call again with that one closed decision. Only an exact fresh packet can dispatch one existing campaign-work authority, which re-observes the exact sender/date and settles before a sibling campaign may advance. The caller cannot supply batch sizes, readiness digests, retry counters, fill inputs, scheduler inputs, or idempotency keys.",
|
|
21
|
+
description: "Continue exactly one server-issued Refill V3 campaign-attention packet. continuation_pending means the same operation is still running: wait retryAfterMs and call this tool with identical scope and no decision until it returns. Reconcile transport timeouts the same way; do not call advance or dispatch another search while pending. Call first without decision: after a fresh revalidation, the tool deterministically owns live-job waiting, selected-source row copying, exact actionable enrichment, and same-template message generation. It returns model_decision_required only when the accurate packet needs a genuine repair, criteria, rubric, waterfall, or source-expansion judgment; then call again with that one closed decision. Only an exact fresh packet can dispatch one existing campaign-work authority, which re-observes the exact sender/date and settles before a sibling campaign may advance. The caller cannot supply batch sizes, readiness digests, retry counters, fill inputs, scheduler inputs, or idempotency keys. A result with recoveredFromError already re-observed after a failure: handle it by its kind. A thrown error is not a terminal: call refill_v3_advance once more with the identical scope and report a blocker only if it fails the same way.",
|
|
22
22
|
inputSchema: REFILL_V3_CONTINUE_INPUT_SCHEMA,
|
|
23
23
|
outputSchema: REFILL_V3_CONTINUE_OUTPUT_SCHEMA,
|
|
24
24
|
annotations: {
|
|
@@ -183,6 +183,33 @@ const DEFAULT_PRODUCTION_OWNERS = {
|
|
|
183
183
|
export async function runRefillV3CampaignWorkWithProductionOwners(input, owners = DEFAULT_PRODUCTION_OWNERS) {
|
|
184
184
|
const decoded = decodeRefillV3CampaignWorkOutput(input.output);
|
|
185
185
|
const chosen = decoded.ok ? decoded.value.actions[0] : null;
|
|
186
|
+
// A source-cycle row is the run's BOOKKEEPING for a source effect that has
|
|
187
|
+
// already happened or been refused; it is never the effect itself. Throwing
|
|
188
|
+
// here on 2026-10-05 discarded Justin's applied 396-row engager import and
|
|
189
|
+
// stopped the Slack run with nothing scheduled. A transient failure is
|
|
190
|
+
// retried in place (retrying the whole stage would re-dispatch the search);
|
|
191
|
+
// any other failure rides on the receipt, and the persisted campaign-work
|
|
192
|
+
// receipt plus the fresh re-observation still guard against a repeat.
|
|
193
|
+
const recordSourceCycle = async (record) => {
|
|
194
|
+
if (!owners.recordSourceCycle)
|
|
195
|
+
return undefined;
|
|
196
|
+
const sleep = owners.sleep ??
|
|
197
|
+
((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
198
|
+
for (let attempt = 0;; attempt += 1) {
|
|
199
|
+
try {
|
|
200
|
+
await owners.recordSourceCycle(record);
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
const retryDelay = POST_ADVANCE_TRANSPORT_RETRY_DELAYS_MS[attempt];
|
|
205
|
+
if (retryDelay === undefined ||
|
|
206
|
+
!isRetryablePostAdvanceTransportError(error)) {
|
|
207
|
+
return error instanceof Error ? error.message : String(error);
|
|
208
|
+
}
|
|
209
|
+
await sleep(retryDelay);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
186
213
|
let diagnosticRows = [];
|
|
187
214
|
const record = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
188
215
|
? value
|
|
@@ -195,6 +222,9 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
195
222
|
const scopeEcho = `${input.packet.scope.workspaceId}:${input.packet.scope.campaignId}:${input.packet.scope.tableId}`;
|
|
196
223
|
const receipt = (params) => ({
|
|
197
224
|
...(params.batchPlan === undefined ? {} : { batchPlan: params.batchPlan }),
|
|
225
|
+
...(params.sourceCycleRecordError === undefined
|
|
226
|
+
? {}
|
|
227
|
+
: { sourceCycleRecordError: params.sourceCycleRecordError }),
|
|
198
228
|
outcome: params.outcome,
|
|
199
229
|
result: params.result,
|
|
200
230
|
scopeEcho,
|
|
@@ -813,7 +843,10 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
813
843
|
senderId: request.scope.senderId,
|
|
814
844
|
date: request.scope.date,
|
|
815
845
|
lane: input.packet.scope.lane,
|
|
816
|
-
family
|
|
846
|
+
// The family being SEARCHED, which a family switch makes differ from
|
|
847
|
+
// the packet's observed family. Its negative set is the one that
|
|
848
|
+
// guards this dispatch, and the receipt below names the same family.
|
|
849
|
+
family: request.sourceCommand.family,
|
|
817
850
|
protectedFitDigest,
|
|
818
851
|
};
|
|
819
852
|
// The run fact scopes both sides of reconciliation: writes are stamped
|
|
@@ -884,28 +917,27 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
884
917
|
: null,
|
|
885
918
|
});
|
|
886
919
|
if (sourceOutcome.kind === "dispatched") {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
});
|
|
906
|
-
}
|
|
920
|
+
const sourceCycleRecordError = await recordSourceCycle({
|
|
921
|
+
...sourceCycleScope,
|
|
922
|
+
...sourceCycleRunFact,
|
|
923
|
+
receipt: {
|
|
924
|
+
schemaVersion: 1,
|
|
925
|
+
kind: "search",
|
|
926
|
+
campaignId: request.scope.campaignId,
|
|
927
|
+
family: request.sourceCommand.family,
|
|
928
|
+
protectedFitDigest,
|
|
929
|
+
requestFingerprint: request.sourceCommand.requestFingerprint,
|
|
930
|
+
sourceFingerprint: sourceOutcome.receipt.sourceAfterFingerprint,
|
|
931
|
+
keywordBatch: request.sourceCommand.keywordBatch,
|
|
932
|
+
postIds: request.sourceCommand.selectedPostIds,
|
|
933
|
+
importedCount: sourceOutcome.receipt.importedCount,
|
|
934
|
+
yieldEvidence: sourceOutcome.receipt.yieldEvidence,
|
|
935
|
+
observedAt: request.scope.date,
|
|
936
|
+
},
|
|
937
|
+
});
|
|
907
938
|
const changed = sourceOutcome.receipt.importedCount > 0;
|
|
908
939
|
return receipt({
|
|
940
|
+
sourceCycleRecordError,
|
|
909
941
|
outcome: changed ? "applied" : "no_effect",
|
|
910
942
|
result,
|
|
911
943
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -914,27 +946,26 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
914
946
|
});
|
|
915
947
|
}
|
|
916
948
|
if (sourceOutcome.kind === "recorded_exhaustion") {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
});
|
|
936
|
-
}
|
|
949
|
+
const sourceCycleRecordError = await recordSourceCycle({
|
|
950
|
+
...sourceCycleScope,
|
|
951
|
+
...sourceCycleRunFact,
|
|
952
|
+
receipt: {
|
|
953
|
+
schemaVersion: 1,
|
|
954
|
+
kind: "exhaustion",
|
|
955
|
+
campaignId: request.scope.campaignId,
|
|
956
|
+
family: request.sourceCommand.family,
|
|
957
|
+
protectedFitDigest,
|
|
958
|
+
requestFingerprint: request.idempotencyKey,
|
|
959
|
+
sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
|
|
960
|
+
keywordBatch: null,
|
|
961
|
+
postIds: null,
|
|
962
|
+
importedCount: 0,
|
|
963
|
+
yieldEvidence: sourceOutcome.receipt.yieldEvidence,
|
|
964
|
+
observedAt: request.scope.date,
|
|
965
|
+
},
|
|
966
|
+
});
|
|
937
967
|
return receipt({
|
|
968
|
+
sourceCycleRecordError,
|
|
938
969
|
outcome: "no_effect",
|
|
939
970
|
result,
|
|
940
971
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -949,7 +980,7 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
949
980
|
// returning the retained job wait. Previously the pending branch
|
|
950
981
|
// wrote nothing, so the job could settle and disappear while the
|
|
951
982
|
// next advance still advertised the identical round as untried.
|
|
952
|
-
await
|
|
983
|
+
const sourceCycleRecordError = await recordSourceCycle({
|
|
953
984
|
...sourceCycleScope,
|
|
954
985
|
...sourceCycleRunFact,
|
|
955
986
|
receipt: {
|
|
@@ -975,6 +1006,7 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
975
1006
|
// evidence-free timeout: reobserve so the next coordinator step can
|
|
976
1007
|
// wait on (or consume) the exact job without replaying the search.
|
|
977
1008
|
return receipt({
|
|
1009
|
+
sourceCycleRecordError,
|
|
978
1010
|
outcome: "no_effect",
|
|
979
1011
|
result,
|
|
980
1012
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -995,27 +1027,26 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
995
1027
|
// authority, and let the same outer run re-observe/continue. Returning
|
|
996
1028
|
// `receipt_incomplete` here left settlement null and repeated the
|
|
997
1029
|
// same packet while every sibling sender/lane stayed unreachable.
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1030
|
+
const sourceCycleRecordError = await recordSourceCycle({
|
|
1031
|
+
...sourceCycleScope,
|
|
1032
|
+
...sourceCycleRunFact,
|
|
1033
|
+
receipt: {
|
|
1034
|
+
schemaVersion: 1,
|
|
1035
|
+
kind: "exhaustion",
|
|
1036
|
+
campaignId: request.scope.campaignId,
|
|
1037
|
+
family: request.sourceCommand.family,
|
|
1038
|
+
protectedFitDigest,
|
|
1039
|
+
requestFingerprint: request.idempotencyKey,
|
|
1040
|
+
sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
|
|
1041
|
+
keywordBatch: null,
|
|
1042
|
+
postIds: null,
|
|
1043
|
+
importedCount: 0,
|
|
1044
|
+
yieldEvidence: input.packet.evidence.priorYieldReceipt,
|
|
1045
|
+
observedAt: request.scope.date,
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1018
1048
|
return receipt({
|
|
1049
|
+
sourceCycleRecordError,
|
|
1019
1050
|
outcome: "no_effect",
|
|
1020
1051
|
result: "source_frontier_exhausted",
|
|
1021
1052
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -1059,28 +1090,26 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
1059
1090
|
yieldEvidence: input.packet.evidence.priorYieldReceipt,
|
|
1060
1091
|
});
|
|
1061
1092
|
if (exhaustionVerdict.exhausted) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
},
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1093
|
+
const sourceCycleRecordError = await recordSourceCycle({
|
|
1094
|
+
...sourceCycleScope,
|
|
1095
|
+
...sourceCycleRunFact,
|
|
1096
|
+
receipt: {
|
|
1097
|
+
schemaVersion: 1,
|
|
1098
|
+
kind: "exhaustion",
|
|
1099
|
+
campaignId: request.scope.campaignId,
|
|
1100
|
+
family: request.sourceCommand.family,
|
|
1101
|
+
protectedFitDigest,
|
|
1102
|
+
requestFingerprint: request.idempotencyKey,
|
|
1103
|
+
sourceFingerprint: priorSourceFingerprint ?? request.scope.evidenceFingerprint,
|
|
1104
|
+
keywordBatch: null,
|
|
1105
|
+
postIds: null,
|
|
1106
|
+
importedCount: 0,
|
|
1107
|
+
yieldEvidence: input.packet.evidence.priorYieldReceipt,
|
|
1108
|
+
observedAt: request.scope.date,
|
|
1109
|
+
},
|
|
1110
|
+
});
|
|
1083
1111
|
return receipt({
|
|
1112
|
+
sourceCycleRecordError,
|
|
1084
1113
|
outcome: "no_effect",
|
|
1085
1114
|
result: "source_frontier_exhausted",
|
|
1086
1115
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -1143,12 +1172,13 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
1143
1172
|
});
|
|
1144
1173
|
}
|
|
1145
1174
|
if (request.action === "record_source_frontier_exhausted") {
|
|
1175
|
+
let sourceCycleRecordError;
|
|
1146
1176
|
if (owners.recordSourceCycle) {
|
|
1147
1177
|
const protectedFitDigest = input.packet.evidence.protectedFitDigest ||
|
|
1148
1178
|
`sha256:${createHash("sha256")
|
|
1149
1179
|
.update(input.packet.evidence.campaignObjective)
|
|
1150
1180
|
.digest("hex")}`;
|
|
1151
|
-
await
|
|
1181
|
+
sourceCycleRecordError = await recordSourceCycle({
|
|
1152
1182
|
workspaceId: request.scope.workspaceId,
|
|
1153
1183
|
runToken: input.runToken,
|
|
1154
1184
|
scopeFingerprint: input.scopeFingerprint,
|
|
@@ -1176,6 +1206,7 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
|
|
|
1176
1206
|
});
|
|
1177
1207
|
}
|
|
1178
1208
|
return receipt({
|
|
1209
|
+
sourceCycleRecordError,
|
|
1179
1210
|
outcome: "no_effect",
|
|
1180
1211
|
result,
|
|
1181
1212
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -1368,7 +1399,63 @@ function deterministicCampaignDecision(packet) {
|
|
|
1368
1399
|
consumedDiagnosticRowIds: null,
|
|
1369
1400
|
};
|
|
1370
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* V33-JUSTIN-SELF-HEAL · an unattended refill never ends on a raw error that
|
|
1404
|
+
* a fresh observation can answer.
|
|
1405
|
+
*
|
|
1406
|
+
* On 2026-10-05 Justin's own-post refresh imported 396 engagers and a
|
|
1407
|
+
* bookkeeping 409 then escaped as a raw error, so the Slack host reported a
|
|
1408
|
+
* blocker with nothing scheduled although the run credential was still
|
|
1409
|
+
* current. Once this continuation holds a refreshed credential, any failure
|
|
1410
|
+
* gets ONE re-observation with it. When the world moved, that observation is
|
|
1411
|
+
* returned like any other and names the error it recovered from. When nothing
|
|
1412
|
+
* moved, the same work would fail the same way, so the original error stands:
|
|
1413
|
+
* a deterministic failure surfaces instead of looping. A failure before the
|
|
1414
|
+
* credential was refreshed is never masked.
|
|
1415
|
+
*/
|
|
1371
1416
|
async function executeRefillV3Continuation(input, dependencies) {
|
|
1417
|
+
const progress = {
|
|
1418
|
+
runToken: null,
|
|
1419
|
+
packet: null,
|
|
1420
|
+
};
|
|
1421
|
+
try {
|
|
1422
|
+
return await executeRefillV3ContinuationStep(input, dependencies, progress);
|
|
1423
|
+
}
|
|
1424
|
+
catch (error) {
|
|
1425
|
+
const runToken = progress.runToken;
|
|
1426
|
+
const failedPacket = progress.packet;
|
|
1427
|
+
if (!runToken || !failedPacket)
|
|
1428
|
+
throw error;
|
|
1429
|
+
let observed;
|
|
1430
|
+
try {
|
|
1431
|
+
observed = await reconnectPostAdvanceStage({
|
|
1432
|
+
sleep: dependencies.sleep,
|
|
1433
|
+
operation: () => dependencies.advance({
|
|
1434
|
+
workspaceId: failedPacket.scope.workspaceId,
|
|
1435
|
+
scope: input.scope,
|
|
1436
|
+
...(input.campaignId === undefined
|
|
1437
|
+
? {}
|
|
1438
|
+
: { campaignId: input.campaignId }),
|
|
1439
|
+
...(input.yolo === undefined ? {} : { yolo: input.yolo }),
|
|
1440
|
+
runToken,
|
|
1441
|
+
}),
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
catch {
|
|
1445
|
+
throw error;
|
|
1446
|
+
}
|
|
1447
|
+
if (observed.kind === "campaign_attention_required" &&
|
|
1448
|
+
samePacket(failedPacket, observed.packet)) {
|
|
1449
|
+
throw error;
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
...continuationResultFromAdvance(observed),
|
|
1453
|
+
recoveredFromError: (error instanceof Error ? error.message : String(error)) ||
|
|
1454
|
+
"unknown_error",
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
async function executeRefillV3ContinuationStep(input, dependencies, progress) {
|
|
1372
1459
|
const workspaceId = normalizeExplicitWorkspaceId(input.workspaceId);
|
|
1373
1460
|
if (!workspaceId || workspaceId !== input.packet.scope.workspaceId) {
|
|
1374
1461
|
throw new Error("refill_v3_continue requires one exact workspace matching the server-issued packet.");
|
|
@@ -1422,6 +1509,8 @@ async function executeRefillV3Continuation(input, dependencies) {
|
|
|
1422
1509
|
if (!runToken || !scopeFingerprint) {
|
|
1423
1510
|
throw new Error("refill_v3_continue got no refreshed run credential from its own internal advance, which already consumed this run's sequence. Refusing to return a spent token.");
|
|
1424
1511
|
}
|
|
1512
|
+
progress.runToken = runToken;
|
|
1513
|
+
progress.packet = advanceResult.packet;
|
|
1425
1514
|
const deterministicDecision = deterministicCampaignDecision(advanceResult.packet);
|
|
1426
1515
|
const decision = deterministicDecision ?? input.decision;
|
|
1427
1516
|
if (decision === undefined) {
|
|
@@ -1642,6 +1731,9 @@ async function executeRefillV3Continuation(input, dependencies) {
|
|
|
1642
1731
|
if (input.yolo === true &&
|
|
1643
1732
|
(preparationOwnsSettlement || directlyQueuedRowsOwnSettlement) &&
|
|
1644
1733
|
campaignSettlementIsReady(settlement)) {
|
|
1734
|
+
// This advance spends the refreshed credential, so a failure past here has
|
|
1735
|
+
// no unspent credential left to re-observe with.
|
|
1736
|
+
progress.runToken = null;
|
|
1645
1737
|
const postSettlementAdvance = await reconnectPostAdvanceStage({
|
|
1646
1738
|
sleep: dependencies.sleep,
|
|
1647
1739
|
operation: () => dependencies.advance({
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -11516,6 +11516,10 @@ export declare const allTools: ({
|
|
|
11516
11516
|
readonly receiptPersistence: {
|
|
11517
11517
|
readonly type: "object";
|
|
11518
11518
|
};
|
|
11519
|
+
readonly recoveredFromError: {
|
|
11520
|
+
readonly type: "string";
|
|
11521
|
+
readonly minLength: 1;
|
|
11522
|
+
};
|
|
11519
11523
|
};
|
|
11520
11524
|
readonly additionalProperties: false;
|
|
11521
11525
|
readonly allOf: readonly [{
|
package/package.json
CHANGED
|
@@ -823,6 +823,13 @@ process lives. If a server restart reports that retained attention is missing,
|
|
|
823
823
|
stop with `continuation_reconciliation_required` and the exact scope and last
|
|
824
824
|
receipt; do not invent a replacement run or claim the prior effect failed.
|
|
825
825
|
Never treat a timeout as zero effect.
|
|
826
|
+
A result carrying `recoveredFromError` means the continuation's work failed,
|
|
827
|
+
the MCP re-observed once, and the world had moved: handle that result by its
|
|
828
|
+
own kind and name the error in the final report. A thrown error is not a
|
|
829
|
+
terminal: the MCP still holds the run's current credential, so call
|
|
830
|
+
`refill_v3_advance` once more with the identical workspace, scope, campaignId,
|
|
831
|
+
and yolo grant. Report a blocker only if that fresh call fails with the same
|
|
832
|
+
error.
|
|
826
833
|
Continue the authorized refill across every connected sender and requested
|
|
827
834
|
lane until the coordinator returns row-complete terminal evidence. Preserve the
|
|
828
835
|
rubric, message, caps, and exclusions; pending work needs no renewed approval.
|
|
@@ -456,6 +456,13 @@ Each call answers with exactly one of six results:
|
|
|
456
456
|
restarted MCP reports missing retained attention, stop with
|
|
457
457
|
`continuation_reconciliation_required`, the exact scope, and the last receipt;
|
|
458
458
|
do not open a replacement run for an uncertain effect.
|
|
459
|
+
A result carrying `recoveredFromError` means the continuation's work failed,
|
|
460
|
+
the MCP re-observed once, and the world had moved: handle that result by its
|
|
461
|
+
own kind and name the error in the final report. A thrown error is not a
|
|
462
|
+
terminal: the MCP still holds the run's current credential, so call
|
|
463
|
+
`refill_v3_advance` once more with the identical workspace, scope,
|
|
464
|
+
campaignId, and yolo grant. Report a blocker only if that fresh call fails
|
|
465
|
+
with the same error.
|
|
459
466
|
Interpret its world wrappers
|
|
460
467
|
by their outer kind: `world_terminal` means `advanceResult` is already the
|
|
461
468
|
canonical `awaiting_external_change`, `complete`, or `blocked` terminal, so
|