@sellable/mcp 0.1.513 → 0.1.514
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/refill-journal.d.ts +26 -0
- package/dist/refill-journal.js +69 -1
- package/dist/refill-local-state.d.ts +21 -0
- package/dist/refill-local-state.js +177 -1
- package/dist/refill-run-client.d.ts +62 -0
- package/dist/refill-run-client.js +101 -0
- package/dist/refill-run-loop.d.ts +100 -0
- package/dist/refill-run-loop.js +1238 -0
- package/dist/server.js +1 -1
- package/dist/tools/campaign-processing.js +3 -3
- package/dist/tools/evergreen-refill-plan.d.ts +6 -0
- package/dist/tools/evergreen-refill-plan.js +1 -1
- package/dist/tools/refill-executors.d.ts +178 -0
- package/dist/tools/refill-executors.js +788 -0
- package/dist/tools/refill-sends-v2.d.ts +94 -7
- package/dist/tools/refill-sends-v2.js +143 -30
- package/dist/tools/refill-sends.js +2 -657
- package/dist/tools/registry.d.ts +168 -0
- package/package.json +1 -1
- package/skills/refill-sends-v2/SKILL.md +113 -53
- package/skills/refill-sends-v2-workflow/SKILL.md +112 -101
- package/skills/refill-sends-v2-workflow/core/flow.v1.json +137 -31
package/dist/refill-journal.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ export declare const DRY_RUN_MARKER = "**DRY RUN \u2014 no mutations performed**
|
|
|
2
2
|
export declare const JOURNAL_SECTION_HEADINGS: {
|
|
3
3
|
readonly bootstrap: "## Bootstrap";
|
|
4
4
|
readonly plan: "## Plan";
|
|
5
|
+
readonly execution: "## Execution";
|
|
6
|
+
readonly crashed: "## Crashed/Abandoned";
|
|
5
7
|
readonly terminal: "## Terminal";
|
|
6
8
|
};
|
|
9
|
+
export declare const REFILL_RUNG_LABELS: Record<string, string>;
|
|
7
10
|
export declare function resolveRefillRunsRoot(): string;
|
|
8
11
|
/**
|
|
9
12
|
* Throws on path-safety and permission failures. Callers must wrap the entire
|
|
@@ -43,7 +46,30 @@ export declare function renderPlanSection(params: {
|
|
|
43
46
|
itinerarySummaries?: string[];
|
|
44
47
|
fallbackSummary?: string;
|
|
45
48
|
}): string;
|
|
49
|
+
export declare function renderExecutionEventSection(_params: {
|
|
50
|
+
gate: string;
|
|
51
|
+
rung?: string | null;
|
|
52
|
+
planned: {
|
|
53
|
+
toolName?: string | null;
|
|
54
|
+
inputSummary?: string | null;
|
|
55
|
+
planRevision?: string | null;
|
|
56
|
+
};
|
|
57
|
+
did?: string | null;
|
|
58
|
+
outcome?: string | null;
|
|
59
|
+
}): string;
|
|
60
|
+
export declare function renderCrashedAbandonedSection(_params: {
|
|
61
|
+
runId: string;
|
|
62
|
+
priorGate: string;
|
|
63
|
+
decidedAt: string;
|
|
64
|
+
}): string;
|
|
46
65
|
export declare function renderTerminalSection(params: {
|
|
47
66
|
summary: string;
|
|
48
67
|
finalNumbers?: string;
|
|
68
|
+
executionNumbers?: {
|
|
69
|
+
doneReason?: string;
|
|
70
|
+
sendsLoaded?: number;
|
|
71
|
+
lanesTouched?: string[];
|
|
72
|
+
lanesStarted?: string[];
|
|
73
|
+
gateCycles?: number;
|
|
74
|
+
};
|
|
49
75
|
}): string;
|
package/dist/refill-journal.js
CHANGED
|
@@ -7,8 +7,24 @@ export const DRY_RUN_MARKER = "**DRY RUN — no mutations performed**";
|
|
|
7
7
|
export const JOURNAL_SECTION_HEADINGS = {
|
|
8
8
|
bootstrap: "## Bootstrap",
|
|
9
9
|
plan: "## Plan",
|
|
10
|
+
execution: "## Execution",
|
|
11
|
+
crashed: "## Crashed/Abandoned",
|
|
10
12
|
terminal: "## Terminal",
|
|
11
13
|
};
|
|
14
|
+
export const REFILL_RUNG_LABELS = {
|
|
15
|
+
// Mirrors EVERGREEN_RUNG_ORDER in src/lib/workflow-tables/refill-target-plan.ts.
|
|
16
|
+
start_campaign: "start the paused campaign so ready rows can schedule",
|
|
17
|
+
approve_messages: "approve generated messages that are ready for review",
|
|
18
|
+
wait_for_active_work: "wait for active campaign work to finish",
|
|
19
|
+
enrich_more: "enrich the next batch of unenriched rows",
|
|
20
|
+
reconcile_source_copy: "copy leads already found into the campaign table",
|
|
21
|
+
add_leads_same_source: "find more leads via this campaign's original source",
|
|
22
|
+
paid_lane_fallback: "move paid InMail demand to the connection fallback lane",
|
|
23
|
+
next_campaign: "move to the next campaign in the lane",
|
|
24
|
+
wait_for_scheduler: "wait for the scheduler to pick up ready rows",
|
|
25
|
+
done: "finish the refill run",
|
|
26
|
+
rerun_errored_cells: "re-run errored enrichment/message cells",
|
|
27
|
+
};
|
|
12
28
|
function expandLeadingTilde(value) {
|
|
13
29
|
if (value === "~")
|
|
14
30
|
return os.homedir();
|
|
@@ -129,6 +145,58 @@ export function renderPlanSection(params) {
|
|
|
129
145
|
params.fallbackSummary ? `Fallback: ${params.fallbackSummary}` : null,
|
|
130
146
|
].filter((line) => Boolean(line)));
|
|
131
147
|
}
|
|
148
|
+
export function renderExecutionEventSection(_params) {
|
|
149
|
+
const rungLabel = renderRungLabel(_params.rung);
|
|
150
|
+
return renderLines(`${JOURNAL_SECTION_HEADINGS.execution}: ${_params.gate} — ${rungLabel}`, [
|
|
151
|
+
renderPlannedLine(_params.planned),
|
|
152
|
+
_params.did ? `did: ${_params.did}` : null,
|
|
153
|
+
_params.outcome ? `outcome: ${_params.outcome}` : null,
|
|
154
|
+
].filter((line) => Boolean(line)));
|
|
155
|
+
}
|
|
156
|
+
export function renderCrashedAbandonedSection(_params) {
|
|
157
|
+
return renderLines(JOURNAL_SECTION_HEADINGS.crashed, [
|
|
158
|
+
`Run ${_params.runId} was marked crashed/abandoned.`,
|
|
159
|
+
`Prior gate: ${_params.priorGate}`,
|
|
160
|
+
`Decided at: ${_params.decidedAt}`,
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
132
163
|
export function renderTerminalSection(params) {
|
|
133
|
-
return renderLines(JOURNAL_SECTION_HEADINGS.terminal, [
|
|
164
|
+
return renderLines(JOURNAL_SECTION_HEADINGS.terminal, [
|
|
165
|
+
params.summary,
|
|
166
|
+
params.finalNumbers,
|
|
167
|
+
...renderExecutionNumbers(params.executionNumbers),
|
|
168
|
+
].filter((line) => Boolean(line)));
|
|
169
|
+
}
|
|
170
|
+
function renderExecutionNumbers(numbers) {
|
|
171
|
+
if (!numbers)
|
|
172
|
+
return [];
|
|
173
|
+
return [
|
|
174
|
+
numbers.doneReason ? `doneReason: ${numbers.doneReason}` : null,
|
|
175
|
+
typeof numbers.sendsLoaded === "number"
|
|
176
|
+
? `sendsLoaded: ${numbers.sendsLoaded}`
|
|
177
|
+
: null,
|
|
178
|
+
numbers.lanesTouched
|
|
179
|
+
? `lanesTouched: ${numbers.lanesTouched.join(", ")}`
|
|
180
|
+
: null,
|
|
181
|
+
numbers.lanesStarted
|
|
182
|
+
? `lanesStarted: ${numbers.lanesStarted.join(", ")}`
|
|
183
|
+
: null,
|
|
184
|
+
typeof numbers.gateCycles === "number"
|
|
185
|
+
? `gateCycles: ${numbers.gateCycles}`
|
|
186
|
+
: null,
|
|
187
|
+
].filter((line) => Boolean(line));
|
|
188
|
+
}
|
|
189
|
+
function renderRungLabel(rung) {
|
|
190
|
+
if (!rung)
|
|
191
|
+
return "unknown";
|
|
192
|
+
const label = REFILL_RUNG_LABELS[rung];
|
|
193
|
+
return label ? `${label} (${rung})` : rung;
|
|
194
|
+
}
|
|
195
|
+
function renderPlannedLine(planned) {
|
|
196
|
+
const segments = [
|
|
197
|
+
planned.toolName ?? "read-only",
|
|
198
|
+
planned.inputSummary,
|
|
199
|
+
planned.planRevision ? `planRevision ${planned.planRevision}` : null,
|
|
200
|
+
].filter((segment) => Boolean(segment));
|
|
201
|
+
return `planned: ${segments.join(" — ")}`;
|
|
134
202
|
}
|
|
@@ -23,6 +23,27 @@ export interface RefillWorkspaceState {
|
|
|
23
23
|
passRates: RefillPassRateObservation[];
|
|
24
24
|
recentRuns: RefillRunSummaryEntry[];
|
|
25
25
|
}
|
|
26
|
+
export declare const REFILL_RECENT_RUNS_MAX = 10;
|
|
27
|
+
export type WriteJsonAtomicResult = {
|
|
28
|
+
ok: true;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
error?: unknown;
|
|
32
|
+
};
|
|
33
|
+
export type WriteRefillWorkspaceStateResult = {
|
|
34
|
+
ok: true;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
reason?: "config_unreadable" | "lock_timeout" | "write_failed";
|
|
38
|
+
error?: unknown;
|
|
39
|
+
};
|
|
40
|
+
export interface RefillWorkspaceStateUpdate {
|
|
41
|
+
laneMemory?: RefillLaneMemoryEntry[];
|
|
42
|
+
passRates?: RefillPassRateObservation[];
|
|
43
|
+
runSummary?: RefillRunSummaryEntry;
|
|
44
|
+
}
|
|
45
|
+
export declare function writeJsonAtomic(filePath: string, value: unknown): WriteJsonAtomicResult;
|
|
46
|
+
export declare function writeRefillWorkspaceState(workspaceId: string, update: RefillWorkspaceStateUpdate): WriteRefillWorkspaceStateResult;
|
|
26
47
|
/**
|
|
27
48
|
* Advisory hints only. A missing, stale, corrupt, or malformed file means no
|
|
28
49
|
* hints; live planner reads remain the source of correctness. Phase 85 reads
|
|
@@ -1,4 +1,161 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { getConfig, getConfigPath } from "./auth.js";
|
|
5
|
+
export const REFILL_RECENT_RUNS_MAX = 10;
|
|
6
|
+
const REFILL_CONFIG_LOCK_ATTEMPTS = 5;
|
|
7
|
+
const REFILL_CONFIG_LOCK_RETRY_MS = 10;
|
|
8
|
+
const REFILL_CONFIG_LOCK_STALE_MS = 10_000;
|
|
9
|
+
export function writeJsonAtomic(filePath, value) {
|
|
10
|
+
let tempPath = null;
|
|
11
|
+
try {
|
|
12
|
+
const parentDir = path.dirname(filePath);
|
|
13
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
14
|
+
tempPath = `${filePath}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
15
|
+
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2), "utf8");
|
|
16
|
+
fs.renameSync(tempPath, filePath);
|
|
17
|
+
return { ok: true };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (tempPath) {
|
|
21
|
+
try {
|
|
22
|
+
fs.unlinkSync(tempPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Best effort cleanup only.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { ok: false, error };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function writeRefillWorkspaceState(workspaceId, update) {
|
|
32
|
+
const configPath = getConfigPath();
|
|
33
|
+
const lock = acquireConfigLock(configPath);
|
|
34
|
+
if (!lock.ok)
|
|
35
|
+
return lock;
|
|
36
|
+
try {
|
|
37
|
+
return writeRefillWorkspaceStateLocked(configPath, workspaceId, update);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
releaseConfigLock(lock);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function writeRefillWorkspaceStateLocked(configPath, workspaceId, update) {
|
|
44
|
+
let raw = {};
|
|
45
|
+
if (fs.existsSync(configPath)) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
48
|
+
raw = isRecord(parsed) ? parsed : {};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof SyntaxError) {
|
|
52
|
+
return { ok: false, reason: "config_unreadable", error };
|
|
53
|
+
}
|
|
54
|
+
return { ok: false, reason: "write_failed", error };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const existingSkillState = isRecord(raw.skillState) ? raw.skillState : {};
|
|
58
|
+
const existingRefill = isRecord(existingSkillState.refill) &&
|
|
59
|
+
existingSkillState.refill.version === 1
|
|
60
|
+
? existingSkillState.refill
|
|
61
|
+
: {};
|
|
62
|
+
const existingByWorkspace = isRecord(existingRefill.byWorkspace)
|
|
63
|
+
? existingRefill.byWorkspace
|
|
64
|
+
: {};
|
|
65
|
+
const existingWorkspaceState = isRecord(existingByWorkspace[workspaceId])
|
|
66
|
+
? existingByWorkspace[workspaceId]
|
|
67
|
+
: {};
|
|
68
|
+
const existingLaneMemory = parseArray(existingWorkspaceState.laneMemory, parseLaneMemoryEntry);
|
|
69
|
+
const existingPassRates = parseArray(existingWorkspaceState.passRates, parsePassRateObservation);
|
|
70
|
+
const existingRecentRuns = parseArray(existingWorkspaceState.recentRuns, parseRunSummaryEntry);
|
|
71
|
+
const nextWorkspaceState = {
|
|
72
|
+
laneMemory: update.laneMemory
|
|
73
|
+
? upsertEntries(existingLaneMemory, update.laneMemory, (entry) => [entry.senderId, entry.laneKey].join("\0"))
|
|
74
|
+
: existingLaneMemory,
|
|
75
|
+
passRates: update.passRates
|
|
76
|
+
? upsertEntries(existingPassRates, update.passRates, (entry) => entry.campaignId)
|
|
77
|
+
: existingPassRates,
|
|
78
|
+
recentRuns: update.runSummary
|
|
79
|
+
? [...existingRecentRuns, update.runSummary].slice(-REFILL_RECENT_RUNS_MAX)
|
|
80
|
+
: existingRecentRuns.slice(-REFILL_RECENT_RUNS_MAX),
|
|
81
|
+
};
|
|
82
|
+
raw.skillState = {
|
|
83
|
+
...existingSkillState,
|
|
84
|
+
refill: {
|
|
85
|
+
...existingRefill,
|
|
86
|
+
version: 1,
|
|
87
|
+
byWorkspace: {
|
|
88
|
+
...existingByWorkspace,
|
|
89
|
+
[workspaceId]: nextWorkspaceState,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const result = writeJsonAtomic(configPath, raw);
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
return { ok: false, reason: "write_failed", error: result.error };
|
|
96
|
+
}
|
|
97
|
+
return { ok: true };
|
|
98
|
+
}
|
|
99
|
+
function acquireConfigLock(configPath) {
|
|
100
|
+
const lockPath = `${configPath}.lock`;
|
|
101
|
+
for (let attempt = 0; attempt < REFILL_CONFIG_LOCK_ATTEMPTS; attempt += 1) {
|
|
102
|
+
try {
|
|
103
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
104
|
+
const fileDescriptor = fs.openSync(lockPath, "wx");
|
|
105
|
+
fs.writeSync(fileDescriptor, `${process.pid}\n`);
|
|
106
|
+
return { ok: true, fileDescriptor, lockPath };
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (!isFileExistsError(error)) {
|
|
110
|
+
return { ok: false, reason: "lock_timeout", error };
|
|
111
|
+
}
|
|
112
|
+
if (isStaleConfigLock(lockPath)) {
|
|
113
|
+
try {
|
|
114
|
+
fs.unlinkSync(lockPath);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Fall through to bounded retry.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (attempt < REFILL_CONFIG_LOCK_ATTEMPTS - 1) {
|
|
122
|
+
sleepSync(REFILL_CONFIG_LOCK_RETRY_MS);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { ok: false, reason: "lock_timeout" };
|
|
127
|
+
}
|
|
128
|
+
function releaseConfigLock(lock) {
|
|
129
|
+
try {
|
|
130
|
+
fs.closeSync(lock.fileDescriptor);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Best effort by design.
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
fs.unlinkSync(lock.lockPath);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Best effort by design.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function isFileExistsError(error) {
|
|
143
|
+
return (error !== null &&
|
|
144
|
+
typeof error === "object" &&
|
|
145
|
+
"code" in error &&
|
|
146
|
+
error.code === "EEXIST");
|
|
147
|
+
}
|
|
148
|
+
function isStaleConfigLock(lockPath) {
|
|
149
|
+
try {
|
|
150
|
+
return (Date.now() - fs.statSync(lockPath).mtimeMs > REFILL_CONFIG_LOCK_STALE_MS);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function sleepSync(ms) {
|
|
157
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
158
|
+
}
|
|
2
159
|
function isRecord(value) {
|
|
3
160
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4
161
|
}
|
|
@@ -66,6 +223,25 @@ function parseArray(value, parser) {
|
|
|
66
223
|
return parsed ? [parsed] : [];
|
|
67
224
|
});
|
|
68
225
|
}
|
|
226
|
+
function upsertEntries(existing, updates, keyForEntry) {
|
|
227
|
+
const next = [...existing];
|
|
228
|
+
const indexByKey = new Map();
|
|
229
|
+
next.forEach((entry, index) => {
|
|
230
|
+
indexByKey.set(keyForEntry(entry), index);
|
|
231
|
+
});
|
|
232
|
+
for (const update of updates) {
|
|
233
|
+
const key = keyForEntry(update);
|
|
234
|
+
const existingIndex = indexByKey.get(key);
|
|
235
|
+
if (existingIndex === undefined) {
|
|
236
|
+
indexByKey.set(key, next.length);
|
|
237
|
+
next.push(update);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
next[existingIndex] = update;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return next;
|
|
244
|
+
}
|
|
69
245
|
/**
|
|
70
246
|
* Advisory hints only. A missing, stale, corrupt, or malformed file means no
|
|
71
247
|
* hints; live planner reads remain the source of correctness. Phase 85 reads
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type RefillRunBlockerResult = {
|
|
2
|
+
blocker: "lease_lost";
|
|
3
|
+
} | {
|
|
4
|
+
blocker: "active_run_in_progress";
|
|
5
|
+
runId: string;
|
|
6
|
+
gate?: string;
|
|
7
|
+
gateSeq?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function startRefillRunRemote(input: {
|
|
10
|
+
workspaceId?: string;
|
|
11
|
+
config?: unknown;
|
|
12
|
+
runState?: unknown;
|
|
13
|
+
resume?: {
|
|
14
|
+
runId: string;
|
|
15
|
+
fence: number;
|
|
16
|
+
};
|
|
17
|
+
}): Promise<unknown>;
|
|
18
|
+
export declare function refillRunStatus(input: {
|
|
19
|
+
workspaceId?: string;
|
|
20
|
+
runId: string;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
export declare function heartbeatRefillRun(input: {
|
|
23
|
+
workspaceId?: string;
|
|
24
|
+
runId: string;
|
|
25
|
+
fence: number;
|
|
26
|
+
}): Promise<unknown>;
|
|
27
|
+
export declare function advanceRefillRunGateRemote(input: {
|
|
28
|
+
workspaceId?: string;
|
|
29
|
+
runId: string;
|
|
30
|
+
fence: number;
|
|
31
|
+
expectedSeq: number;
|
|
32
|
+
nextGate: string;
|
|
33
|
+
runState?: unknown;
|
|
34
|
+
planRevision?: string | null;
|
|
35
|
+
stateRevision?: string | null;
|
|
36
|
+
targetShapeRevision?: string | null;
|
|
37
|
+
doneReason?: string | null;
|
|
38
|
+
progress?: unknown;
|
|
39
|
+
}): Promise<unknown>;
|
|
40
|
+
export declare function recordRunPlanned(input: {
|
|
41
|
+
workspaceId?: string;
|
|
42
|
+
runId: string;
|
|
43
|
+
fence: number;
|
|
44
|
+
gate: string;
|
|
45
|
+
gateSeq: number;
|
|
46
|
+
planned: unknown;
|
|
47
|
+
}): Promise<unknown>;
|
|
48
|
+
export declare function recordRunOutcome(input: {
|
|
49
|
+
workspaceId?: string;
|
|
50
|
+
runId: string;
|
|
51
|
+
fence: number;
|
|
52
|
+
eventId: string;
|
|
53
|
+
did?: unknown;
|
|
54
|
+
outcome: unknown;
|
|
55
|
+
}): Promise<unknown>;
|
|
56
|
+
export declare function completeRefillRun(input: {
|
|
57
|
+
workspaceId?: string;
|
|
58
|
+
runId: string;
|
|
59
|
+
fence: number;
|
|
60
|
+
doneReason: string;
|
|
61
|
+
progress?: unknown;
|
|
62
|
+
}): Promise<unknown>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { getApi, SellableApiError } from "./api.js";
|
|
2
|
+
const REFILL_RUNS_ROUTE = "/api/v3/mcp/refill-runs";
|
|
3
|
+
function parseBlockerBody(body) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = JSON.parse(body);
|
|
6
|
+
if (parsed.blocker === "lease_lost") {
|
|
7
|
+
return { blocker: "lease_lost" };
|
|
8
|
+
}
|
|
9
|
+
if (parsed.blocker === "active_run_in_progress" &&
|
|
10
|
+
typeof parsed.runId === "string") {
|
|
11
|
+
return {
|
|
12
|
+
blocker: "active_run_in_progress",
|
|
13
|
+
runId: parsed.runId,
|
|
14
|
+
gate: typeof parsed.gate === "string" ? parsed.gate : undefined,
|
|
15
|
+
gateSeq: typeof parsed.gateSeq === "number" ? parsed.gateSeq : undefined,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
async function postRefillRuns(workspaceId, body) {
|
|
25
|
+
try {
|
|
26
|
+
return await getApi().post(REFILL_RUNS_ROUTE, { workspaceId, ...body }, { workspaceId });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof SellableApiError && error.status === 409) {
|
|
30
|
+
const blocker = parseBlockerBody(error.body);
|
|
31
|
+
if (blocker)
|
|
32
|
+
return blocker;
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function startRefillRunRemote(input) {
|
|
38
|
+
return postRefillRuns(input.workspaceId, {
|
|
39
|
+
action: "start",
|
|
40
|
+
config: input.config,
|
|
41
|
+
runState: input.runState,
|
|
42
|
+
resume: input.resume,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function refillRunStatus(input) {
|
|
46
|
+
return postRefillRuns(input.workspaceId, {
|
|
47
|
+
action: "status",
|
|
48
|
+
runId: input.runId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function heartbeatRefillRun(input) {
|
|
52
|
+
return postRefillRuns(input.workspaceId, {
|
|
53
|
+
action: "heartbeat",
|
|
54
|
+
runId: input.runId,
|
|
55
|
+
fence: input.fence,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function advanceRefillRunGateRemote(input) {
|
|
59
|
+
return postRefillRuns(input.workspaceId, {
|
|
60
|
+
action: "advance-gate",
|
|
61
|
+
runId: input.runId,
|
|
62
|
+
fence: input.fence,
|
|
63
|
+
expectedSeq: input.expectedSeq,
|
|
64
|
+
nextGate: input.nextGate,
|
|
65
|
+
runState: input.runState,
|
|
66
|
+
planRevision: input.planRevision,
|
|
67
|
+
stateRevision: input.stateRevision,
|
|
68
|
+
targetShapeRevision: input.targetShapeRevision,
|
|
69
|
+
doneReason: input.doneReason,
|
|
70
|
+
progress: input.progress,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function recordRunPlanned(input) {
|
|
74
|
+
return postRefillRuns(input.workspaceId, {
|
|
75
|
+
action: "record-planned",
|
|
76
|
+
runId: input.runId,
|
|
77
|
+
fence: input.fence,
|
|
78
|
+
gate: input.gate,
|
|
79
|
+
gateSeq: input.gateSeq,
|
|
80
|
+
planned: input.planned,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function recordRunOutcome(input) {
|
|
84
|
+
return postRefillRuns(input.workspaceId, {
|
|
85
|
+
action: "record-outcome",
|
|
86
|
+
runId: input.runId,
|
|
87
|
+
fence: input.fence,
|
|
88
|
+
eventId: input.eventId,
|
|
89
|
+
did: input.did,
|
|
90
|
+
outcome: input.outcome,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function completeRefillRun(input) {
|
|
94
|
+
return postRefillRuns(input.workspaceId, {
|
|
95
|
+
action: "complete",
|
|
96
|
+
runId: input.runId,
|
|
97
|
+
fence: input.fence,
|
|
98
|
+
doneReason: input.doneReason,
|
|
99
|
+
progress: input.progress,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { appendIndexLine, appendJournalEvent, createRunJournal, renderBootstrapSection, renderCrashedAbandonedSection, renderExecutionEventSection, renderPlanSection, renderTerminalSection } from "./refill-journal.js";
|
|
2
|
+
import type { advanceRefillRunGateRemote, completeRefillRun, heartbeatRefillRun, recordRunOutcome, recordRunPlanned, refillRunStatus, startRefillRunRemote } from "./refill-run-client.js";
|
|
3
|
+
import type { getPrepareCampaignMessagesStatus } from "./tools/campaign-message-preparation.js";
|
|
4
|
+
import type { executeOneYoloPrimitive, executeStartCampaignPrimitive, prepareRowSelectorValue, refillPrepareRequestHash, refreshPaidInmailCreditsWithRetry } from "./tools/refill-executors.js";
|
|
5
|
+
export type RefillLoopGate = "g0_bootstrap" | "g1_plan" | "g2_execute" | "g3_verify" | "g4_terminal";
|
|
6
|
+
export interface RefillV2LoopInput {
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
intent?: "auto" | "evergreen" | "plain" | "active" | string;
|
|
9
|
+
senderIds?: string[];
|
|
10
|
+
resume?: {
|
|
11
|
+
runId: string;
|
|
12
|
+
fence: number;
|
|
13
|
+
};
|
|
14
|
+
approvalMode?: "approve" | "mark_ready";
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface RefillV2LoopBudgets {
|
|
18
|
+
maxGateCyclesPerInvocation: number;
|
|
19
|
+
pollIntervalMs: number;
|
|
20
|
+
maxPollsPerWait: number;
|
|
21
|
+
maxSchedulerReadbacks: number;
|
|
22
|
+
waitWallClockPerInvocationMs: number;
|
|
23
|
+
schedulerWaitRunBudgetMs: number;
|
|
24
|
+
}
|
|
25
|
+
export interface RefillV2RunClient {
|
|
26
|
+
startRefillRunRemote: typeof startRefillRunRemote;
|
|
27
|
+
refillRunStatus: typeof refillRunStatus;
|
|
28
|
+
heartbeatRefillRun: typeof heartbeatRefillRun;
|
|
29
|
+
advanceRefillRunGateRemote: typeof advanceRefillRunGateRemote;
|
|
30
|
+
recordRunPlanned: typeof recordRunPlanned;
|
|
31
|
+
recordRunOutcome: typeof recordRunOutcome;
|
|
32
|
+
completeRefillRun: typeof completeRefillRun;
|
|
33
|
+
}
|
|
34
|
+
export interface RefillV2Executors {
|
|
35
|
+
executeOneYoloPrimitive: typeof executeOneYoloPrimitive;
|
|
36
|
+
executeStartCampaignPrimitive: typeof executeStartCampaignPrimitive;
|
|
37
|
+
refreshPaidInmailCreditsWithRetry: typeof refreshPaidInmailCreditsWithRetry;
|
|
38
|
+
getPrepareCampaignMessagesStatus?: typeof getPrepareCampaignMessagesStatus;
|
|
39
|
+
refillPrepareRequestHash?: typeof refillPrepareRequestHash;
|
|
40
|
+
prepareRowSelectorValue?: typeof prepareRowSelectorValue;
|
|
41
|
+
}
|
|
42
|
+
export interface RefillV2JournalDeps {
|
|
43
|
+
createRunJournal: typeof createRunJournal;
|
|
44
|
+
appendJournalEvent: typeof appendJournalEvent;
|
|
45
|
+
renderBootstrapSection: typeof renderBootstrapSection;
|
|
46
|
+
renderPlanSection: typeof renderPlanSection;
|
|
47
|
+
renderExecutionEventSection: typeof renderExecutionEventSection;
|
|
48
|
+
renderCrashedAbandonedSection: typeof renderCrashedAbandonedSection;
|
|
49
|
+
renderTerminalSection: typeof renderTerminalSection;
|
|
50
|
+
appendIndexLine?: typeof appendIndexLine;
|
|
51
|
+
}
|
|
52
|
+
export interface RefillV2LocalStateDeps {
|
|
53
|
+
writeRunSummary?: (entry: Record<string, unknown>) => void | Promise<void>;
|
|
54
|
+
writeRefillWorkspaceState?: (workspaceId: string, update: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
55
|
+
}
|
|
56
|
+
export interface RefillV2LoopDeps {
|
|
57
|
+
runClient: RefillV2RunClient;
|
|
58
|
+
readPlan: (input: {
|
|
59
|
+
workspaceId: string;
|
|
60
|
+
intent?: string;
|
|
61
|
+
senderIds?: string[];
|
|
62
|
+
runState?: Record<string, unknown>;
|
|
63
|
+
journal?: boolean;
|
|
64
|
+
}) => Promise<Record<string, unknown>>;
|
|
65
|
+
executors: RefillV2Executors;
|
|
66
|
+
journal: RefillV2JournalDeps;
|
|
67
|
+
localState?: RefillV2LocalStateDeps;
|
|
68
|
+
budgets?: Partial<RefillV2LoopBudgets>;
|
|
69
|
+
now?: () => Date;
|
|
70
|
+
sleep?: (ms: number) => Promise<void>;
|
|
71
|
+
requestSchedulerRun?: (workspaceId: string) => Promise<unknown>;
|
|
72
|
+
}
|
|
73
|
+
export type RefillV2LoopResult = {
|
|
74
|
+
status: "dry_run";
|
|
75
|
+
plan: Record<string, unknown>;
|
|
76
|
+
} | {
|
|
77
|
+
status: "in_progress";
|
|
78
|
+
runId: string;
|
|
79
|
+
fence: number;
|
|
80
|
+
gate: RefillLoopGate;
|
|
81
|
+
guidance: string;
|
|
82
|
+
journalPath?: string | null;
|
|
83
|
+
} | {
|
|
84
|
+
status: "blocked";
|
|
85
|
+
blocker: string;
|
|
86
|
+
runId?: string;
|
|
87
|
+
fence?: number;
|
|
88
|
+
gate?: RefillLoopGate;
|
|
89
|
+
guidance: string;
|
|
90
|
+
holderRunId?: string;
|
|
91
|
+
report?: Record<string, unknown>;
|
|
92
|
+
journalPath?: string | null;
|
|
93
|
+
} | {
|
|
94
|
+
status: "terminal";
|
|
95
|
+
doneReason: string;
|
|
96
|
+
report: Record<string, unknown>;
|
|
97
|
+
runId: string;
|
|
98
|
+
journalPath?: string | null;
|
|
99
|
+
};
|
|
100
|
+
export declare function runRefillV2Loop(input: RefillV2LoopInput, deps: RefillV2LoopDeps): Promise<RefillV2LoopResult>;
|