@sellable/mcp 0.1.560 → 0.1.561-hermes031.202607110338
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/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/refill-date-window.d.ts +34 -0
- package/dist/refill-date-window.js +210 -0
- package/dist/refill-run-loop.js +0 -17
- package/dist/tools/refill-sends-evergreen.d.ts +28 -0
- package/dist/tools/refill-sends-evergreen.js +47 -0
- package/dist/tools/refill-sends-v2.d.ts +0 -11
- package/dist/tools/refill-sends-v2.js +14 -144
- package/dist/tools/refill-sends.d.ts +0 -17
- package/dist/tools/refill-sends.js +3 -9
- package/dist/tools/workspaces.d.ts +6 -4
- package/dist/tools/workspaces.js +13 -11
- package/package.json +1 -1
- package/skills/research/config.json +9 -0
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type RefillDateWindowSource = "default_scheduler_forward" | "horizon_send_days" | "until_date" | "target_date";
|
|
2
|
+
export type NormalizedRefillDateSelector = {
|
|
3
|
+
source: RefillDateWindowSource;
|
|
4
|
+
targetDate: string | null;
|
|
5
|
+
untilDate: string | null;
|
|
6
|
+
horizonSendDays: number | null;
|
|
7
|
+
};
|
|
8
|
+
export type RefillRunDateWindow = NormalizedRefillDateSelector & {
|
|
9
|
+
version: 1;
|
|
10
|
+
selectedDates: string[];
|
|
11
|
+
senderWindows?: Array<{
|
|
12
|
+
senderId: string | null;
|
|
13
|
+
timeZone: string | null;
|
|
14
|
+
selectedDates: string[];
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
type DateSelectorInput = {
|
|
18
|
+
targetDate?: unknown;
|
|
19
|
+
untilDate?: unknown;
|
|
20
|
+
horizonSendDays?: unknown;
|
|
21
|
+
};
|
|
22
|
+
export declare function normalizeRefillDateSelector(input: DateSelectorInput): NormalizedRefillDateSelector;
|
|
23
|
+
export declare function refillDateSelectorBody(selector: NormalizedRefillDateSelector): {
|
|
24
|
+
targetDate?: string;
|
|
25
|
+
untilDate?: string;
|
|
26
|
+
horizonSendDays?: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function dateWindowFromSelector(selector: NormalizedRefillDateSelector): RefillRunDateWindow;
|
|
29
|
+
export declare function dateWindowFromPlan(plan: Record<string, unknown>, selector: NormalizedRefillDateSelector): RefillRunDateWindow;
|
|
30
|
+
export declare function dateWindowFromRunState(runState: unknown): RefillRunDateWindow | null;
|
|
31
|
+
export declare function selectorFromDateWindow(window: RefillRunDateWindow): NormalizedRefillDateSelector;
|
|
32
|
+
export declare function dateSelectorMatchesWindow(selector: NormalizedRefillDateSelector, window: RefillRunDateWindow): boolean;
|
|
33
|
+
export declare function schedulerSweepTargetDates(window: RefillRunDateWindow): string[];
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function stringValue(value) {
|
|
5
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
6
|
+
}
|
|
7
|
+
function numberValue(value) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
9
|
+
}
|
|
10
|
+
function arrayValue(value) {
|
|
11
|
+
return Array.isArray(value) ? value : [];
|
|
12
|
+
}
|
|
13
|
+
function isValidDateKey(value) {
|
|
14
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
15
|
+
return false;
|
|
16
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
17
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
18
|
+
return (date.getUTCFullYear() === year &&
|
|
19
|
+
date.getUTCMonth() === month - 1 &&
|
|
20
|
+
date.getUTCDate() === day);
|
|
21
|
+
}
|
|
22
|
+
function normalizeDateKey(value, field) {
|
|
23
|
+
if (value === undefined || value === null || value === "")
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof value !== "string") {
|
|
26
|
+
throw new Error(`${field} must be a string in YYYY-MM-DD format.`);
|
|
27
|
+
}
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (!isValidDateKey(trimmed)) {
|
|
30
|
+
throw new Error(`${field} must be a valid YYYY-MM-DD calendar date.`);
|
|
31
|
+
}
|
|
32
|
+
return trimmed;
|
|
33
|
+
}
|
|
34
|
+
function normalizeHorizonSendDays(value) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return null;
|
|
37
|
+
if (typeof value !== "number" ||
|
|
38
|
+
!Number.isInteger(value) ||
|
|
39
|
+
value < 1 ||
|
|
40
|
+
value > 7) {
|
|
41
|
+
throw new Error("horizonSendDays must be an integer between 1 and 7.");
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
export function normalizeRefillDateSelector(input) {
|
|
46
|
+
const targetDate = normalizeDateKey(input.targetDate, "targetDate");
|
|
47
|
+
const untilDate = normalizeDateKey(input.untilDate, "untilDate");
|
|
48
|
+
const horizonSendDays = normalizeHorizonSendDays(input.horizonSendDays);
|
|
49
|
+
if (targetDate) {
|
|
50
|
+
return {
|
|
51
|
+
source: "target_date",
|
|
52
|
+
targetDate,
|
|
53
|
+
untilDate: null,
|
|
54
|
+
horizonSendDays: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (untilDate) {
|
|
58
|
+
return {
|
|
59
|
+
source: "until_date",
|
|
60
|
+
targetDate: null,
|
|
61
|
+
untilDate,
|
|
62
|
+
horizonSendDays: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (horizonSendDays !== null) {
|
|
66
|
+
return {
|
|
67
|
+
source: "horizon_send_days",
|
|
68
|
+
targetDate: null,
|
|
69
|
+
untilDate: null,
|
|
70
|
+
horizonSendDays,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
source: "default_scheduler_forward",
|
|
75
|
+
targetDate: null,
|
|
76
|
+
untilDate: null,
|
|
77
|
+
horizonSendDays: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function refillDateSelectorBody(selector) {
|
|
81
|
+
if (selector.source === "target_date" && selector.targetDate) {
|
|
82
|
+
return { targetDate: selector.targetDate };
|
|
83
|
+
}
|
|
84
|
+
if (selector.source === "until_date" && selector.untilDate) {
|
|
85
|
+
return { untilDate: selector.untilDate };
|
|
86
|
+
}
|
|
87
|
+
if (selector.source === "horizon_send_days" &&
|
|
88
|
+
selector.horizonSendDays !== null) {
|
|
89
|
+
return { horizonSendDays: selector.horizonSendDays };
|
|
90
|
+
}
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
export function dateWindowFromSelector(selector) {
|
|
94
|
+
return {
|
|
95
|
+
version: 1,
|
|
96
|
+
...selector,
|
|
97
|
+
selectedDates: selector.targetDate ? [selector.targetDate] : [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function selectedDatesFromPlan(plan) {
|
|
101
|
+
const packet = isRecord(plan.packet) ? plan.packet : {};
|
|
102
|
+
const target = isRecord(packet.target) ? packet.target : {};
|
|
103
|
+
const window = isRecord(target.window) ? target.window : {};
|
|
104
|
+
const senderWindows = arrayValue(window.senderWindows).filter(isRecord);
|
|
105
|
+
const fromWindows = senderWindows.flatMap((senderWindow) => arrayValue(senderWindow.selectedDates)
|
|
106
|
+
.map((entry) => stringValue(entry))
|
|
107
|
+
.filter((entry) => Boolean(entry)));
|
|
108
|
+
if (fromWindows.length > 0)
|
|
109
|
+
return Array.from(new Set(fromWindows)).sort();
|
|
110
|
+
const senderRefillPlans = arrayValue(target.senderRefillPlans).filter(isRecord);
|
|
111
|
+
return Array.from(new Set(senderRefillPlans.flatMap((senderPlan) => {
|
|
112
|
+
const horizon = isRecord(senderPlan.horizon) ? senderPlan.horizon : {};
|
|
113
|
+
return arrayValue(horizon.selectedDays)
|
|
114
|
+
.filter(isRecord)
|
|
115
|
+
.map((day) => stringValue(day.date))
|
|
116
|
+
.filter((entry) => Boolean(entry));
|
|
117
|
+
}))).sort();
|
|
118
|
+
}
|
|
119
|
+
function senderWindowsFromPlan(plan) {
|
|
120
|
+
const packet = isRecord(plan.packet) ? plan.packet : {};
|
|
121
|
+
const target = isRecord(packet.target) ? packet.target : {};
|
|
122
|
+
const window = isRecord(target.window) ? target.window : {};
|
|
123
|
+
return arrayValue(window.senderWindows)
|
|
124
|
+
.filter(isRecord)
|
|
125
|
+
.map((senderWindow) => ({
|
|
126
|
+
senderId: stringValue(senderWindow.senderId),
|
|
127
|
+
timeZone: stringValue(senderWindow.timeZone),
|
|
128
|
+
selectedDates: arrayValue(senderWindow.selectedDates)
|
|
129
|
+
.map((entry) => stringValue(entry))
|
|
130
|
+
.filter((entry) => Boolean(entry)),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
export function dateWindowFromPlan(plan, selector) {
|
|
134
|
+
const packet = isRecord(plan.packet) ? plan.packet : {};
|
|
135
|
+
const request = isRecord(packet.request) ? packet.request : {};
|
|
136
|
+
const targetDate = stringValue(request.targetDate) ?? selector.targetDate ?? null;
|
|
137
|
+
const untilDate = stringValue(request.untilDate) ?? selector.untilDate ?? null;
|
|
138
|
+
const horizonSendDays = numberValue(request.horizonSendDays) ?? selector.horizonSendDays ?? null;
|
|
139
|
+
const source = targetDate
|
|
140
|
+
? "target_date"
|
|
141
|
+
: untilDate
|
|
142
|
+
? "until_date"
|
|
143
|
+
: horizonSendDays !== null
|
|
144
|
+
? "horizon_send_days"
|
|
145
|
+
: selector.source;
|
|
146
|
+
const selectedDates = selectedDatesFromPlan(plan);
|
|
147
|
+
return {
|
|
148
|
+
version: 1,
|
|
149
|
+
source,
|
|
150
|
+
targetDate: source === "target_date" ? targetDate : null,
|
|
151
|
+
untilDate: source === "until_date" ? untilDate : null,
|
|
152
|
+
horizonSendDays: source === "horizon_send_days" ? horizonSendDays : null,
|
|
153
|
+
selectedDates: selectedDates.length > 0
|
|
154
|
+
? selectedDates
|
|
155
|
+
: targetDate
|
|
156
|
+
? [targetDate]
|
|
157
|
+
: [],
|
|
158
|
+
senderWindows: senderWindowsFromPlan(plan),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export function dateWindowFromRunState(runState) {
|
|
162
|
+
const state = isRecord(runState) ? runState : {};
|
|
163
|
+
const raw = isRecord(state.dateWindow) ? state.dateWindow : null;
|
|
164
|
+
if (!raw || raw.version !== 1)
|
|
165
|
+
return null;
|
|
166
|
+
const source = raw.source === "target_date" ||
|
|
167
|
+
raw.source === "until_date" ||
|
|
168
|
+
raw.source === "horizon_send_days" ||
|
|
169
|
+
raw.source === "default_scheduler_forward"
|
|
170
|
+
? raw.source
|
|
171
|
+
: null;
|
|
172
|
+
if (!source)
|
|
173
|
+
return null;
|
|
174
|
+
return {
|
|
175
|
+
version: 1,
|
|
176
|
+
source,
|
|
177
|
+
targetDate: source === "target_date" ? stringValue(raw.targetDate) : null,
|
|
178
|
+
untilDate: source === "until_date" ? stringValue(raw.untilDate) : null,
|
|
179
|
+
horizonSendDays: source === "horizon_send_days"
|
|
180
|
+
? numberValue(raw.horizonSendDays)
|
|
181
|
+
: null,
|
|
182
|
+
selectedDates: arrayValue(raw.selectedDates)
|
|
183
|
+
.map((entry) => stringValue(entry))
|
|
184
|
+
.filter((entry) => Boolean(entry)),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function selectorFromDateWindow(window) {
|
|
188
|
+
return {
|
|
189
|
+
source: window.source,
|
|
190
|
+
targetDate: window.targetDate,
|
|
191
|
+
untilDate: window.untilDate,
|
|
192
|
+
horizonSendDays: window.horizonSendDays,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function dateSelectorMatchesWindow(selector, window) {
|
|
196
|
+
return (selector.source === window.source &&
|
|
197
|
+
selector.targetDate === window.targetDate &&
|
|
198
|
+
selector.untilDate === window.untilDate &&
|
|
199
|
+
selector.horizonSendDays === window.horizonSendDays);
|
|
200
|
+
}
|
|
201
|
+
export function schedulerSweepTargetDates(window) {
|
|
202
|
+
if (window.source === "target_date" && window.targetDate) {
|
|
203
|
+
return [window.targetDate];
|
|
204
|
+
}
|
|
205
|
+
if (window.source === "until_date" ||
|
|
206
|
+
window.source === "horizon_send_days") {
|
|
207
|
+
return window.selectedDates;
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
}
|
package/dist/refill-run-loop.js
CHANGED
|
@@ -1027,23 +1027,6 @@ async function gatePlan(input, deps, ctx) {
|
|
|
1027
1027
|
fallbackSummary: line.fallbackSummary,
|
|
1028
1028
|
}));
|
|
1029
1029
|
const fingerprint = fingerprintFromPlan(plan);
|
|
1030
|
-
const approvedTargetShapeRevision = stringValue(input.approval?.targetShapeRevision);
|
|
1031
|
-
if (approvedTargetShapeRevision &&
|
|
1032
|
-
approvedTargetShapeRevision !== fingerprint.targetShapeRevision) {
|
|
1033
|
-
return {
|
|
1034
|
-
status: "blocked",
|
|
1035
|
-
blocker: "scope_drift",
|
|
1036
|
-
runId: ctx.runId,
|
|
1037
|
-
fence: ctx.fence,
|
|
1038
|
-
gate: ctx.gate,
|
|
1039
|
-
guidance: "The canonical refill target shape changed after approval; render a new packet before another primitive.",
|
|
1040
|
-
report: {
|
|
1041
|
-
approvedTargetShapeRevision,
|
|
1042
|
-
freshTargetShapeRevision: fingerprint.targetShapeRevision,
|
|
1043
|
-
},
|
|
1044
|
-
journalPath: ctx.journalPath,
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
1030
|
const persistedCircuit = recordValue(runStateProgress(ctx).prepCircuit);
|
|
1048
1031
|
if (stringValue(action.type) === "prepare_messages" &&
|
|
1049
1032
|
openCircuitWaits(persistedCircuit, deps.now?.() ?? new Date())) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type RefillSendsEvergreenInput = {
|
|
2
|
+
workspaceId?: string;
|
|
3
|
+
};
|
|
4
|
+
export declare const refillSendsEvergreenToolDefinitions: {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: string;
|
|
9
|
+
properties: {
|
|
10
|
+
workspaceId: {
|
|
11
|
+
type: string;
|
|
12
|
+
description: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
required: string[];
|
|
16
|
+
additionalProperties: boolean;
|
|
17
|
+
};
|
|
18
|
+
}[];
|
|
19
|
+
export declare function refillSendsEvergreenCommand(input: RefillSendsEvergreenInput): {
|
|
20
|
+
readOnly: boolean;
|
|
21
|
+
workspaceId: string | null;
|
|
22
|
+
firstOperationalSteps: string[];
|
|
23
|
+
approvalContract: string;
|
|
24
|
+
forbiddenActions: string[];
|
|
25
|
+
fillWindow: string;
|
|
26
|
+
hostExamples: string[];
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const refillSendsEvergreenToolDefinitions = [
|
|
2
|
+
{
|
|
3
|
+
name: "refill_sends_evergreen",
|
|
4
|
+
description: "Read-only Phase 85 evergreen refill command contract. It performs no mutations and only tells the operator to call get_evergreen_refill_plan for a dry-run packet and journal.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
workspaceId: {
|
|
9
|
+
type: "string",
|
|
10
|
+
description: "Explicit request-scoped workspace id.",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
required: ["workspaceId"],
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
export function refillSendsEvergreenCommand(input) {
|
|
19
|
+
return {
|
|
20
|
+
readOnly: true,
|
|
21
|
+
workspaceId: input.workspaceId ?? null,
|
|
22
|
+
firstOperationalSteps: [
|
|
23
|
+
"Call get_evergreen_refill_plan with the explicit workspaceId.",
|
|
24
|
+
"Read the returned packet, globalActionQueue, per-sender plans, and itinerary before taking any action.",
|
|
25
|
+
"Review the dry-run journal file path returned by get_evergreen_refill_plan.",
|
|
26
|
+
"Phase 85 is PLAN-ONLY; execution arrives in Phase 86.",
|
|
27
|
+
],
|
|
28
|
+
approvalContract: "Nothing is approved or executable in Phase 85. The evergreen command is read-only; Phase 86 introduces execution approval.",
|
|
29
|
+
forbiddenActions: [
|
|
30
|
+
"Do not schedule sends.",
|
|
31
|
+
"Do not send messages.",
|
|
32
|
+
"Do not approve messages.",
|
|
33
|
+
"Do not prepare messages.",
|
|
34
|
+
"Do not start or launch campaigns.",
|
|
35
|
+
"Do not create campaigns.",
|
|
36
|
+
"Do not switch providers or source families.",
|
|
37
|
+
"Do not lower paid InMail thresholds.",
|
|
38
|
+
"Do not refresh paid InMail credits.",
|
|
39
|
+
"Do not write scheduler fields.",
|
|
40
|
+
],
|
|
41
|
+
fillWindow: "Use only the target window and caps returned by get_evergreen_refill_plan.",
|
|
42
|
+
hostExamples: [
|
|
43
|
+
"refill_sends_evergreen({ workspaceId })",
|
|
44
|
+
"get_evergreen_refill_plan({ workspaceId })",
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -167,17 +167,6 @@ export declare function prepareRefillSendsV2Approval(input: RefillV2ApprovalPrep
|
|
|
167
167
|
response?: undefined;
|
|
168
168
|
}>;
|
|
169
169
|
export declare function refillSendsV2Command(input: RefillSendsV2Input): Promise<{
|
|
170
|
-
status: "blocked";
|
|
171
|
-
blocker: "approval_expired" | "approval_fingerprint_mismatch";
|
|
172
|
-
guidance: string;
|
|
173
|
-
report: {
|
|
174
|
-
fresh?: {} | null | undefined;
|
|
175
|
-
changed: string[];
|
|
176
|
-
};
|
|
177
|
-
readOnly: boolean;
|
|
178
|
-
forbiddenActions: string[];
|
|
179
|
-
boundedAuthority: string;
|
|
180
|
-
} | {
|
|
181
170
|
readOnly: boolean;
|
|
182
171
|
mode: string;
|
|
183
172
|
forbiddenActions: string[];
|
|
@@ -325,11 +325,7 @@ function validateApprovalEcho(envelope, echo, now = new Date()) {
|
|
|
325
325
|
];
|
|
326
326
|
const changed = fields.filter((field) => echo[field] !== envelope.approval[field]);
|
|
327
327
|
if (Date.parse(envelope.approval.approvalExpiresAt) <= now.getTime()) {
|
|
328
|
-
return {
|
|
329
|
-
ok: false,
|
|
330
|
-
blocker: "approval_expired",
|
|
331
|
-
changed,
|
|
332
|
-
};
|
|
328
|
+
return { ok: false, blocker: "approval_expired", changed };
|
|
333
329
|
}
|
|
334
330
|
return changed.length === 0
|
|
335
331
|
? { ok: true }
|
|
@@ -339,85 +335,6 @@ function validateApprovalEcho(envelope, echo, now = new Date()) {
|
|
|
339
335
|
changed,
|
|
340
336
|
};
|
|
341
337
|
}
|
|
342
|
-
function blockedApprovalResult(params) {
|
|
343
|
-
return {
|
|
344
|
-
status: "blocked",
|
|
345
|
-
blocker: params.blocker,
|
|
346
|
-
guidance: "The durable refill approval no longer matches its persisted scope; render and approve a new packet before mutation.",
|
|
347
|
-
report: {
|
|
348
|
-
changed: params.changed,
|
|
349
|
-
...(params.fresh === undefined ? {} : { fresh: params.fresh }),
|
|
350
|
-
},
|
|
351
|
-
readOnly: false,
|
|
352
|
-
forbiddenActions: FORBIDDEN_ACTIONS,
|
|
353
|
-
boundedAuthority: BOUNDED_AUTHORITY,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
function storedApprovalFromStatus(value) {
|
|
357
|
-
const status = record(value) ?? {};
|
|
358
|
-
const config = record(status.config);
|
|
359
|
-
const scope = record(config?.scope);
|
|
360
|
-
const approval = record(config?.approval);
|
|
361
|
-
const scopeHash = text(config?.scopeHash);
|
|
362
|
-
if (!config || !scope || !approval || !scopeHash)
|
|
363
|
-
return null;
|
|
364
|
-
return {
|
|
365
|
-
config,
|
|
366
|
-
scope,
|
|
367
|
-
scopeHash,
|
|
368
|
-
approval,
|
|
369
|
-
hasDispatched: status.hasDispatched === true,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
function validateStoredApprovalEcho(params) {
|
|
373
|
-
const fields = [
|
|
374
|
-
"scopeHash",
|
|
375
|
-
"targetShapeRevision",
|
|
376
|
-
"actionFingerprint",
|
|
377
|
-
"expectedTargetsHash",
|
|
378
|
-
"approvalExpiresAt",
|
|
379
|
-
"approvalFingerprint",
|
|
380
|
-
];
|
|
381
|
-
const changed = fields.filter((field) => text(params.approval[field]) !== params.echo[field]);
|
|
382
|
-
const expiresAt = text(params.approval.approvalExpiresAt);
|
|
383
|
-
if (!params.allowExpired &&
|
|
384
|
-
(!expiresAt ||
|
|
385
|
-
Date.parse(expiresAt) <= (params.now ?? new Date()).getTime())) {
|
|
386
|
-
return {
|
|
387
|
-
ok: false,
|
|
388
|
-
blocker: "approval_expired",
|
|
389
|
-
changed,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
return changed.length === 0
|
|
393
|
-
? { ok: true }
|
|
394
|
-
: {
|
|
395
|
-
ok: false,
|
|
396
|
-
blocker: "approval_fingerprint_mismatch",
|
|
397
|
-
changed,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
function executionScopeFromStored(scope, fallback) {
|
|
401
|
-
const dateSelector = record(scope.dateSelector) ?? {};
|
|
402
|
-
const kind = text(dateSelector.kind);
|
|
403
|
-
const senderIds = list(scope.senderIds)
|
|
404
|
-
.map((value) => text(value))
|
|
405
|
-
.filter((value) => value != null);
|
|
406
|
-
const approvalMode = scope.approvalMode === "mark_ready" ? "mark_ready" : "approve";
|
|
407
|
-
return {
|
|
408
|
-
intent: text(scope.intent) ?? fallback.intent,
|
|
409
|
-
senderIds,
|
|
410
|
-
approvalMode,
|
|
411
|
-
...(kind === "target_date" && text(dateSelector.targetDate)
|
|
412
|
-
? { targetDate: text(dateSelector.targetDate) }
|
|
413
|
-
: kind === "until_date" && text(dateSelector.untilDate)
|
|
414
|
-
? { untilDate: text(dateSelector.untilDate) }
|
|
415
|
-
: kind === "horizon_send_days" &&
|
|
416
|
-
typeof dateSelector.horizonSendDays === "number"
|
|
417
|
-
? { horizonSendDays: dateSelector.horizonSendDays }
|
|
418
|
-
: {}),
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
338
|
async function maybeAddLostFenceGuidance(result, input) {
|
|
422
339
|
if (result.status !== "blocked" ||
|
|
423
340
|
result.blocker !== "lease_lost" ||
|
|
@@ -448,53 +365,7 @@ export async function refillSendsV2Command(input) {
|
|
|
448
365
|
let resolvedScope = input.scope;
|
|
449
366
|
let resolvedScopeHash = input.scopeHash;
|
|
450
367
|
let resolvedApproval = input.approval;
|
|
451
|
-
if (input.
|
|
452
|
-
const status = await refillRunStatus({ workspaceId, runId: input.runId });
|
|
453
|
-
const stored = storedApprovalFromStatus(status);
|
|
454
|
-
if (!stored) {
|
|
455
|
-
return {
|
|
456
|
-
status: "blocked",
|
|
457
|
-
blocker: "approval_run_unavailable",
|
|
458
|
-
guidance: "The durable refill run no longer exposes a valid persisted approval packet.",
|
|
459
|
-
report: { status },
|
|
460
|
-
readOnly: false,
|
|
461
|
-
forbiddenActions: FORBIDDEN_ACTIONS,
|
|
462
|
-
boundedAuthority: BOUNDED_AUTHORITY,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
const validation = validateStoredApprovalEcho({
|
|
466
|
-
approval: stored.approval,
|
|
467
|
-
echo: input.approvalEcho,
|
|
468
|
-
allowExpired: stored.hasDispatched,
|
|
469
|
-
});
|
|
470
|
-
if (!validation.ok) {
|
|
471
|
-
return blockedApprovalResult({
|
|
472
|
-
blocker: validation.blocker,
|
|
473
|
-
changed: validation.changed,
|
|
474
|
-
fresh: stored.approval,
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
const freshTargetShapeRevision = input.targetPlan
|
|
478
|
-
? (text(input.targetPlan.targetShapeRevision) ??
|
|
479
|
-
text(record(input.targetPlan.target)?.targetShapeRevision))
|
|
480
|
-
: null;
|
|
481
|
-
const storedTargetShapeRevision = text(stored.approval.targetShapeRevision);
|
|
482
|
-
if (freshTargetShapeRevision &&
|
|
483
|
-
freshTargetShapeRevision !== storedTargetShapeRevision) {
|
|
484
|
-
return blockedApprovalResult({
|
|
485
|
-
blocker: "approval_fingerprint_mismatch",
|
|
486
|
-
changed: ["targetShapeRevision"],
|
|
487
|
-
fresh: {
|
|
488
|
-
storedTargetShapeRevision,
|
|
489
|
-
freshTargetShapeRevision,
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
resolvedScope = stored.scope;
|
|
494
|
-
resolvedScopeHash = stored.scopeHash;
|
|
495
|
-
resolvedApproval = stored.approval;
|
|
496
|
-
}
|
|
497
|
-
else if (input.targetPlan && input.approvalEcho) {
|
|
368
|
+
if (input.targetPlan && input.approvalEcho) {
|
|
498
369
|
const envelope = buildApprovalEnvelope({
|
|
499
370
|
workspaceId,
|
|
500
371
|
intent: input.intent,
|
|
@@ -508,11 +379,15 @@ export async function refillSendsV2Command(input) {
|
|
|
508
379
|
});
|
|
509
380
|
const validation = validateApprovalEcho(envelope, input.approvalEcho);
|
|
510
381
|
if (!validation.ok) {
|
|
511
|
-
return
|
|
382
|
+
return {
|
|
383
|
+
status: "blocked",
|
|
512
384
|
blocker: validation.blocker,
|
|
513
|
-
|
|
514
|
-
fresh: envelope.approval,
|
|
515
|
-
|
|
385
|
+
guidance: "The durable refill approval no longer matches fresh scope; render and approve a new packet before mutation.",
|
|
386
|
+
report: { changed: validation.changed, fresh: envelope.approval },
|
|
387
|
+
readOnly: false,
|
|
388
|
+
forbiddenActions: FORBIDDEN_ACTIONS,
|
|
389
|
+
boundedAuthority: BOUNDED_AUTHORITY,
|
|
390
|
+
};
|
|
516
391
|
}
|
|
517
392
|
resolvedScope = envelope.scope;
|
|
518
393
|
resolvedScopeHash = envelope.scopeHash;
|
|
@@ -534,18 +409,13 @@ export async function refillSendsV2Command(input) {
|
|
|
534
409
|
boundedAuthority: BOUNDED_AUTHORITY,
|
|
535
410
|
};
|
|
536
411
|
}
|
|
537
|
-
const executionScope = resolvedScope
|
|
538
|
-
? executionScopeFromStored(resolvedScope, input)
|
|
539
|
-
: {
|
|
540
|
-
intent: input.intent,
|
|
541
|
-
senderIds: input.senderIds,
|
|
542
|
-
approvalMode: input.approvalMode ?? "approve",
|
|
543
|
-
...date,
|
|
544
|
-
};
|
|
545
412
|
const result = await runRefillV2Loop({
|
|
546
413
|
workspaceId,
|
|
547
|
-
|
|
414
|
+
intent: input.intent,
|
|
415
|
+
senderIds: input.senderIds,
|
|
416
|
+
approvalMode: input.approvalMode ?? "approve",
|
|
548
417
|
resume: resumeHandle(input),
|
|
418
|
+
...date,
|
|
549
419
|
scope: resolvedScope,
|
|
550
420
|
scopeHash: resolvedScopeHash,
|
|
551
421
|
approval: resolvedApproval,
|
|
@@ -725,23 +725,6 @@ export declare function executeRefillSendsCommand(input?: RefillSendsCommandInpu
|
|
|
725
725
|
intent: RefillSendsIntent;
|
|
726
726
|
} | {
|
|
727
727
|
yoloExecution: {
|
|
728
|
-
selectedAction: Record<string, unknown> | null;
|
|
729
|
-
targetPlanReread: boolean;
|
|
730
|
-
status: "blocked";
|
|
731
|
-
blocker: "approval_expired" | "approval_fingerprint_mismatch";
|
|
732
|
-
guidance: string;
|
|
733
|
-
report: {
|
|
734
|
-
fresh?: {} | null | undefined;
|
|
735
|
-
changed: string[];
|
|
736
|
-
};
|
|
737
|
-
readOnly: boolean;
|
|
738
|
-
forbiddenActions: string[];
|
|
739
|
-
boundedAuthority: string;
|
|
740
|
-
enabled: boolean;
|
|
741
|
-
result?: undefined;
|
|
742
|
-
refusalReason?: undefined;
|
|
743
|
-
postActionFirstAction?: undefined;
|
|
744
|
-
} | {
|
|
745
728
|
selectedAction: Record<string, unknown> | null;
|
|
746
729
|
targetPlanReread: boolean;
|
|
747
730
|
readOnly: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { REFILL_CONTRACT_CACHE_VERSION, REFILL_CONTRACT_HASH, REFILL_CONTRACT_VERSION, renderRefillBootstrapGuidance, renderRefillSafetyGuidance, renderRefillTerminalGuidance, } from "../refill-contract.js";
|
|
2
1
|
import { actionIds, classifyPaidInmailRefreshReceipt, collectPaidInmailRefreshActions, executeOneYoloPrimitive, firstGlobalAction, normalizeStrings, numberValue, recordValue, refreshPaidInmailCreditsWithRetry, stringValue, } from "./refill-executors.js";
|
|
3
2
|
import { prepareRefillSendsV2Approval, refillSendsV2Command, } from "./refill-sends-v2.js";
|
|
3
|
+
import { REFILL_CONTRACT_CACHE_VERSION, REFILL_CONTRACT_HASH, REFILL_CONTRACT_VERSION, renderRefillBootstrapGuidance, renderRefillSafetyGuidance, renderRefillTerminalGuidance, } from "../refill-contract.js";
|
|
4
4
|
import { getRefillTargetPlan } from "./refill-target-plan.js";
|
|
5
5
|
import { createWorkspaceContext, normalizeExplicitWorkspaceId, } from "./workspace-context.js";
|
|
6
6
|
const REFILL_SEND_ACTION_TYPES = ["send_invite", "send_inmail_closed"];
|
|
@@ -563,13 +563,8 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
563
563
|
}
|
|
564
564
|
const targetPlanInput = targetPlanInputFor(scopedInput);
|
|
565
565
|
const targetPlanBeforePaidRefresh = await getRefillTargetPlan(targetPlanInput);
|
|
566
|
-
const durableResumeRequested = Boolean(yolo &&
|
|
567
|
-
workspaceId &&
|
|
568
|
-
scopedInput.runId &&
|
|
569
|
-
typeof scopedInput.fence === "number" &&
|
|
570
|
-
scopedInput.approvalFingerprint);
|
|
571
566
|
const approvedPacket = approvedPacketComparison(scopedInput, targetPlanBeforePaidRefresh);
|
|
572
|
-
if (
|
|
567
|
+
if (approvedPacket.changed) {
|
|
573
568
|
return {
|
|
574
569
|
...command,
|
|
575
570
|
ok: false,
|
|
@@ -600,8 +595,7 @@ export async function executeRefillSendsCommand(input = {}) {
|
|
|
600
595
|
}
|
|
601
596
|
if (yolo &&
|
|
602
597
|
workspaceId &&
|
|
603
|
-
(
|
|
604
|
-
durableV2PlanEligible(targetPlanBeforePaidRefresh))) {
|
|
598
|
+
durableV2PlanEligible(targetPlanBeforePaidRefresh)) {
|
|
605
599
|
const base = durableYoloBaseResult({
|
|
606
600
|
command,
|
|
607
601
|
targetPlan: targetPlanBeforePaidRefresh,
|
|
@@ -142,15 +142,17 @@ export declare function listWorkspaces(): Promise<{
|
|
|
142
142
|
workspaces: WorkspaceSummary[];
|
|
143
143
|
workspaceLock: {
|
|
144
144
|
enabled: boolean;
|
|
145
|
-
workspaceId
|
|
146
|
-
hiddenWorkspaceCount
|
|
145
|
+
workspaceId: string;
|
|
146
|
+
hiddenWorkspaceCount: number;
|
|
147
|
+
siblingInventoryFetched: boolean;
|
|
147
148
|
};
|
|
148
149
|
} | {
|
|
149
150
|
workspaces: WorkspaceSummary[];
|
|
150
151
|
workspaceLock: {
|
|
151
152
|
enabled: boolean;
|
|
152
|
-
workspaceId
|
|
153
|
-
hiddenWorkspaceCount
|
|
153
|
+
workspaceId?: undefined;
|
|
154
|
+
hiddenWorkspaceCount?: undefined;
|
|
155
|
+
siblingInventoryFetched?: undefined;
|
|
154
156
|
};
|
|
155
157
|
}>;
|
|
156
158
|
export declare function getActiveWorkspace(): {
|
package/dist/tools/workspaces.js
CHANGED
|
@@ -80,19 +80,21 @@ export const workspaceToolDefinitions = [
|
|
|
80
80
|
];
|
|
81
81
|
export async function listWorkspaces() {
|
|
82
82
|
const api = getApi();
|
|
83
|
-
const { workspaces } = await api.get("/api/v3/workspaces");
|
|
84
83
|
const lockedWorkspaceId = getLockedWorkspaceId();
|
|
85
|
-
if (
|
|
86
|
-
|
|
84
|
+
if (lockedWorkspaceId) {
|
|
85
|
+
const { workspace } = await api.get(`/api/v3/workspaces/${encodeURIComponent(lockedWorkspaceId)}`);
|
|
86
|
+
return {
|
|
87
|
+
workspaces: [workspace],
|
|
88
|
+
workspaceLock: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
workspaceId: lockedWorkspaceId,
|
|
91
|
+
hiddenWorkspaceCount: 0,
|
|
92
|
+
siblingInventoryFetched: false,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
87
95
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
workspaceLock: {
|
|
91
|
-
enabled: true,
|
|
92
|
-
workspaceId: lockedWorkspaceId,
|
|
93
|
-
hiddenWorkspaceCount: workspaces.filter((ws) => ws.id !== lockedWorkspaceId).length,
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
+
const { workspaces } = await api.get("/api/v3/workspaces");
|
|
97
|
+
return { workspaces, workspaceLock: { enabled: false } };
|
|
96
98
|
}
|
|
97
99
|
export function getActiveWorkspace() {
|
|
98
100
|
const config = getConfig();
|
package/package.json
CHANGED