@sellable/mcp 0.1.770-wip.145.20260727141913 → 0.1.770
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-integration-request-context.d.ts +10 -0
- package/dist/agent-integration-request-context.js +15 -1
- package/dist/agent-scope.d.ts +17 -0
- package/dist/agent-scope.js +40 -0
- package/dist/agent-tool-names.d.ts +19 -1
- package/dist/agent-tool-names.js +42 -4
- package/dist/api.d.ts +2 -0
- package/dist/api.js +24 -3
- package/dist/auth.js +24 -6
- package/dist/refill-run-loop.js +156 -6
- package/dist/server.js +45 -22
- package/dist/tool-result-envelope.d.ts +0 -1
- package/dist/tool-result-envelope.js +3 -120
- package/dist/tools/auth.js +6 -2
- package/dist/tools/campaign-horizon-fill.d.ts +5 -0
- package/dist/tools/campaign-horizon-fill.js +7 -3
- package/dist/tools/campaign-message-preparation.d.ts +6 -0
- package/dist/tools/campaign-message-preparation.js +1 -0
- package/dist/tools/campaigns.d.ts +18 -11
- package/dist/tools/campaigns.js +22 -9
- package/dist/tools/cells.d.ts +10 -2
- package/dist/tools/cells.js +12 -4
- package/dist/tools/inbox.d.ts +11 -0
- package/dist/tools/inbox.js +20 -4
- package/dist/tools/integration-connect.d.ts +23 -0
- package/dist/tools/integration-connect.js +570 -0
- package/dist/tools/integrations.d.ts +69 -0
- package/dist/tools/integrations.js +68 -10
- package/dist/tools/leads.d.ts +20 -15
- package/dist/tools/leads.js +8 -1
- package/dist/tools/prompts.d.ts +3 -19
- package/dist/tools/prompts.js +2 -24
- package/dist/tools/refill-exact-run.d.ts +123 -0
- package/dist/tools/{refill-sends-v2.js → refill-exact-run.js} +6 -66
- package/dist/tools/{evergreen-refill-plan.d.ts → refill-execution-plan.d.ts} +4 -56
- package/dist/tools/{evergreen-refill-plan.js → refill-execution-plan.js} +14 -66
- package/dist/tools/refill-executors.d.ts +1 -0
- package/dist/tools/refill-executors.js +21 -6
- package/dist/tools/refill-sends.d.ts +1398 -1
- package/dist/tools/refill-sends.js +80 -27
- package/dist/tools/refill-target-plan.js +1 -1
- package/dist/tools/registry.d.ts +137 -1058
- package/dist/tools/registry.js +5 -13
- package/dist/tools/sender-routing.d.ts +5 -1
- package/dist/tools/sender-routing.js +6 -1
- package/dist/tools/waterfalls.d.ts +5 -0
- package/dist/tools/waterfalls.js +5 -1
- package/dist/tools/workspaces.d.ts +41 -0
- package/dist/tools/workspaces.js +62 -4
- package/package.json +2 -1
- package/skills/refill-sends/SKILL.md +14 -47
- package/skills/refill-sends-workflow/SKILL.md +17 -8
- package/skills/refill-sends-workflow/core/flow.v1.json +8 -5
- package/dist/tools/refill-sends-v2.d.ts +0 -537
- package/dist/tools/refill-v3-eligibility.d.ts +0 -190
- package/dist/tools/refill-v3-eligibility.js +0 -217
- package/dist/tools/refill-v3-inventory-contract.d.ts +0 -5
- package/dist/tools/refill-v3-inventory-contract.js +0 -26
- package/dist/tools/refill-v3-inventory.d.ts +0 -555
- package/dist/tools/refill-v3-inventory.js +0 -469
- package/skills/refill-sends-evergreen/SKILL.md +0 -24
- package/skills/refill-sends-evergreen-workflow/SKILL.md +0 -25
- package/skills/refill-sends-evergreen-workflow/core/flow.v1.json +0 -259
- package/skills/refill-sends-v2/SKILL.md +0 -207
- package/skills/refill-sends-v2-workflow/SKILL.md +0 -171
- package/skills/refill-sends-v2-workflow/core/flow.v1.json +0 -397
- package/skills/research/config.json +0 -9
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const AGENT_INTEGRATION_CONTEXT_TOOL_NAMES: readonly ["integrations_list_tools", "integrations_describe_tool", "integrations_call_tool"];
|
|
40
40
|
export type AgentIntegrationContextToolName = (typeof AGENT_INTEGRATION_CONTEXT_TOOL_NAMES)[number];
|
|
41
|
+
/**
|
|
42
|
+
* Plan 142-05's connect trio, in a SECOND frozen set rather than appended to the
|
|
43
|
+
* one above: two suites pin `AGENT_INTEGRATION_CONTEXT_TOOL_NAMES` to exactly
|
|
44
|
+
* the three `integrations_*` names 141-06 registers, and those pins are correct
|
|
45
|
+
* — they guard 141's registration, not 142's. `isAgentIntegrationContextTool`
|
|
46
|
+
* spans both sets, so the fail-closed dispatch guard in `server.ts` covers the
|
|
47
|
+
* connect tools with no change to the switch-side plumbing.
|
|
48
|
+
*/
|
|
49
|
+
export declare const AGENT_INTEGRATION_CONNECT_TOOL_NAMES: readonly ["search_available_integrations", "start_integration_connection", "get_integration_connection_status"];
|
|
50
|
+
export type AgentIntegrationConnectToolName = (typeof AGENT_INTEGRATION_CONNECT_TOOL_NAMES)[number];
|
|
41
51
|
/** The trusted actor triple a managed-integration call forwards to the backend. */
|
|
42
52
|
export type AgentIntegrationRequestContext = {
|
|
43
53
|
requesterId: string;
|
|
@@ -41,9 +41,23 @@ export const AGENT_INTEGRATION_CONTEXT_TOOL_NAMES = Object.freeze([
|
|
|
41
41
|
"integrations_describe_tool",
|
|
42
42
|
"integrations_call_tool",
|
|
43
43
|
]);
|
|
44
|
+
/**
|
|
45
|
+
* Plan 142-05's connect trio, in a SECOND frozen set rather than appended to the
|
|
46
|
+
* one above: two suites pin `AGENT_INTEGRATION_CONTEXT_TOOL_NAMES` to exactly
|
|
47
|
+
* the three `integrations_*` names 141-06 registers, and those pins are correct
|
|
48
|
+
* — they guard 141's registration, not 142's. `isAgentIntegrationContextTool`
|
|
49
|
+
* spans both sets, so the fail-closed dispatch guard in `server.ts` covers the
|
|
50
|
+
* connect tools with no change to the switch-side plumbing.
|
|
51
|
+
*/
|
|
52
|
+
export const AGENT_INTEGRATION_CONNECT_TOOL_NAMES = Object.freeze([
|
|
53
|
+
"search_available_integrations",
|
|
54
|
+
"start_integration_connection",
|
|
55
|
+
"get_integration_connection_status",
|
|
56
|
+
]);
|
|
44
57
|
export const AGENT_INTEGRATION_CONTEXT_REQUIRED_ERROR = "agent_request_context_required";
|
|
45
58
|
export function isAgentIntegrationContextTool(name) {
|
|
46
|
-
return AGENT_INTEGRATION_CONTEXT_TOOL_NAMES.includes(name)
|
|
59
|
+
return (AGENT_INTEGRATION_CONTEXT_TOOL_NAMES.includes(name) ||
|
|
60
|
+
AGENT_INTEGRATION_CONNECT_TOOL_NAMES.includes(name));
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
49
63
|
* Resolves the trusted actor for a managed-integration tool call.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type SellableAgentScope = "WORKSPACE" | "CROSS_WORKSPACE";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the last server-reported affordance scope. An unknown scope fails
|
|
4
|
+
* closed to WORKSPACE; authorization still belongs to the API middleware.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getAgentScope(): SellableAgentScope;
|
|
7
|
+
export declare function hasRecordedAgentScope(): boolean;
|
|
8
|
+
export declare function recordAgentScope(value: unknown): SellableAgentScope;
|
|
9
|
+
/**
|
|
10
|
+
* Coalesces the one lazy membership read needed by a fresh Agent session.
|
|
11
|
+
* The loader returns the same response body that carries `agentScope`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureAgentScope(loader: () => Promise<{
|
|
14
|
+
agentScope?: unknown;
|
|
15
|
+
}>): Promise<SellableAgentScope>;
|
|
16
|
+
/** Test-only reset; production keeps one server-reported scope per process. */
|
|
17
|
+
export declare function resetAgentScopeForTests(): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
let reportedAgentScope = null;
|
|
2
|
+
let pendingScopeLoad = null;
|
|
3
|
+
function parseAgentScope(value) {
|
|
4
|
+
return value === "WORKSPACE" || value === "CROSS_WORKSPACE" ? value : null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns the last server-reported affordance scope. An unknown scope fails
|
|
8
|
+
* closed to WORKSPACE; authorization still belongs to the API middleware.
|
|
9
|
+
*/
|
|
10
|
+
export function getAgentScope() {
|
|
11
|
+
return reportedAgentScope ?? "WORKSPACE";
|
|
12
|
+
}
|
|
13
|
+
export function hasRecordedAgentScope() {
|
|
14
|
+
return reportedAgentScope !== null;
|
|
15
|
+
}
|
|
16
|
+
export function recordAgentScope(value) {
|
|
17
|
+
reportedAgentScope = parseAgentScope(value) ?? "WORKSPACE";
|
|
18
|
+
return reportedAgentScope;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Coalesces the one lazy membership read needed by a fresh Agent session.
|
|
22
|
+
* The loader returns the same response body that carries `agentScope`.
|
|
23
|
+
*/
|
|
24
|
+
export async function ensureAgentScope(loader) {
|
|
25
|
+
if (reportedAgentScope)
|
|
26
|
+
return reportedAgentScope;
|
|
27
|
+
if (!pendingScopeLoad) {
|
|
28
|
+
pendingScopeLoad = loader()
|
|
29
|
+
.then((response) => recordAgentScope(response.agentScope))
|
|
30
|
+
.finally(() => {
|
|
31
|
+
pendingScopeLoad = null;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return pendingScopeLoad;
|
|
35
|
+
}
|
|
36
|
+
/** Test-only reset; production keeps one server-reported scope per process. */
|
|
37
|
+
export function resetAgentScopeForTests() {
|
|
38
|
+
reportedAgentScope = null;
|
|
39
|
+
pendingScopeLoad = null;
|
|
40
|
+
}
|
|
@@ -3,4 +3,22 @@
|
|
|
3
3
|
* manifests. Keep this free of tool implementation imports so Next can bundle
|
|
4
4
|
* it; registry parity is enforced in sellable-agent-tool-policy.test.ts.
|
|
5
5
|
*/
|
|
6
|
-
export declare const SELLABLE_MCP_TOOL_NAMES: readonly ["add_column", "add_on_demand_leads", "add_rubric_item", "add_teammate", "add_to_comment_campaign", "add_to_connection_campaign", "add_to_inmail_campaign", "archive_campaign", "attach_recommended_sequence", "attach_sequence", "bootstrap_create_campaign", "bootstrap_engage", "bootstrap_engage_multi", "bootstrap_find_leads", "bulk_enrich_with_prospeo", "calculate_linkedin_hook_preview", "cancel_campaign_message_preparation", "cancel_find_leads", "cancel_lead_import", "capture_post_idea", "check_inbox_reply_eligibility", "check_rubric", "commit_blueprint", "complete_sender_research", "confirm_harvest_job_companies", "confirm_lead_list", "confirm_prospeo_company_accounts", "copy_sender_config", "create_campaign", "create_on_demand_campaign", "create_on_demand_table", "create_workflow_table", "create_workspace", "delete_column", "delete_rubric_item", "draft_rubrics", "duplicate_campaign", "enrich_with_prospeo", "export_table_csv", "export_workspace_csv", "fetch_company", "fetch_company_posts", "fetch_linkedin_posts", "fetch_linkedin_profile", "fetch_post_engagers", "fill_campaign_horizon", "get_active_workspace", "get_auth_status", "get_campaign", "get_campaign_context", "get_campaign_framework", "get_campaign_message_preparation_status", "get_campaign_messages_preview", "get_campaign_navigation_state", "get_campaign_refill_state", "get_campaign_table_schema", "get_campaign_waterfall", "get_campaigns", "get_column_schema", "get_company_info", "get_engage_memory", "get_engage_state", "get_engaged_posts", "get_find_leads_run", "get_inbox_thread", "get_linkedin_profile", "get_or_create_direct_campaign_table", "get_post_draft", "get_post_find_leads_scout_registry", "get_post_idea", "get_prospeo_credits", "get_provider_prompt", "get_published_post", "
|
|
6
|
+
export declare const SELLABLE_MCP_TOOL_NAMES: readonly ["add_column", "add_on_demand_leads", "add_rubric_item", "add_teammate", "add_to_comment_campaign", "add_to_connection_campaign", "add_to_inmail_campaign", "archive_campaign", "attach_recommended_sequence", "attach_sequence", "bootstrap_create_campaign", "bootstrap_engage", "bootstrap_engage_multi", "bootstrap_find_leads", "bulk_enrich_with_prospeo", "calculate_linkedin_hook_preview", "cancel_campaign_message_preparation", "cancel_find_leads", "cancel_lead_import", "capture_post_idea", "check_inbox_reply_eligibility", "check_rubric", "commit_blueprint", "complete_sender_research", "confirm_harvest_job_companies", "confirm_lead_list", "confirm_prospeo_company_accounts", "copy_sender_config", "create_campaign", "create_on_demand_campaign", "create_on_demand_table", "create_workflow_table", "create_workspace", "delete_column", "delete_rubric_item", "draft_rubrics", "duplicate_campaign", "enrich_with_prospeo", "export_table_csv", "export_workspace_csv", "fetch_company", "fetch_company_posts", "fetch_linkedin_posts", "fetch_linkedin_profile", "fetch_post_engagers", "fill_campaign_horizon", "get_active_workspace", "get_auth_status", "get_campaign", "get_campaign_context", "get_campaign_framework", "get_campaign_message_preparation_status", "get_campaign_messages_preview", "get_campaign_navigation_state", "get_campaign_refill_state", "get_campaign_table_schema", "get_campaign_waterfall", "get_campaigns", "get_column_schema", "get_company_info", "get_engage_memory", "get_engage_state", "get_engaged_posts", "get_find_leads_run", "get_inbox_thread", "get_integration_connection_status", "get_linkedin_profile", "get_or_create_direct_campaign_table", "get_post_draft", "get_post_find_leads_scout_registry", "get_post_idea", "get_prospeo_credits", "get_provider_prompt", "get_published_post", "get_refill_target_plan", "get_rows", "get_rows_minimal", "get_runtime_identity", "get_scheduler_fill_capacity", "get_sender", "get_sender_routing", "get_source_scout_registry", "get_subskill_asset", "get_subskill_prompt", "get_table_rows", "get_user_posts", "get_workspace", "import_leads", "init_on_demand_sequence", "integrations_call_tool", "integrations_describe_tool", "integrations_list_tools", "list_column_types", "list_dnc_entries", "list_post_draft_iterations", "list_post_drafts", "list_post_ideas", "list_published_posts", "list_senders", "list_subskill_prompts", "list_tables", "list_workspaces", "load_csv_dnc_entries", "load_csv_domains", "load_csv_linkedin_leads", "lookup_sales_nav_filter", "mark_post_published", "migrate_flat_configs", "pause_campaign", "pause_direct_campaign", "pause_on_demand_campaign", "preflight_find_leads_provider", "prepare_campaign_ab_test", "queue_campaign_cells", "queue_cells", "record_campaign_review_batch", "record_engage_proven_search", "refill_sends", "refresh_paid_inmail_credits", "refresh_sender_engagement", "reissue_find_leads_watch_link", "render_linkedin_post_preview", "reorder_columns", "resolve_campaign_fill_route", "revise_message_template_and_rerun", "run_scheduler_sweep", "save_domain_filters", "save_hook_research", "save_post_draft", "save_rubrics", "search_apollo", "search_available_integrations", "search_engagement_posts", "search_harvest_jobs", "search_inbox_threads", "search_prospeo", "search_prospeo_companies", "search_sales_nav", "search_signals", "search_subskill_prompts", "select_campaign_cells", "select_necessary_rubrics", "select_promising_posts", "send_inbox_draft", "send_inbox_manual_reply", "set_active_workspace", "set_campaign_waterfall_order", "set_company_info", "set_engage_state", "set_engage_style_guide", "set_headline_icp_criteria", "set_sender_calendar_link", "set_sender_routing", "setup_evergreen_campaigns", "start_campaign", "start_campaign_message_preparation", "start_cli_login", "start_direct_campaign", "start_integration_connection", "start_on_demand_campaign", "update_campaign", "update_campaign_brief", "update_cell", "update_column", "update_find_leads_run", "update_inbox_draft", "update_post_draft", "update_published_post_metrics", "update_rubric_item", "upsert_engage_tracked_person", "upsert_rubric", "validate_campaign1_kickoff_handoff", "verify_table_row", "wait_for_campaign_processing", "wait_for_campaign_table_ready", "wait_for_cli_login", "wait_for_find_leads_run", "wait_for_lead_list_ready", "wait_for_rubric_results"];
|
|
7
|
+
/**
|
|
8
|
+
* Explicit mutation classification lives beside (but outside) the flat tool
|
|
9
|
+
* registry. Neither this list nor `isMutatingTool` is an input to
|
|
10
|
+
* `compileAgentToolPolicy`, so editing it cannot move the fleet-pinned
|
|
11
|
+
* `policyHash`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MUTATING_TOOL_NAMES: readonly ["pause_campaign", "archive_campaign", "update_campaign", "fill_campaign_horizon", "set_campaign_waterfall_order", "set_sender_routing", "send_inbox_draft", "send_inbox_manual_reply", "load_csv_dnc_entries", "update_cell", "queue_cells"];
|
|
14
|
+
export declare function isMutatingTool(name: string): boolean;
|
|
15
|
+
export declare const CROSS_WORKSPACE_EXPLICIT_WORKSPACE_REQUIRED: {
|
|
16
|
+
readonly code: "cross_workspace_explicit_workspace_required";
|
|
17
|
+
readonly message: "Cross-workspace mutations require an explicit non-empty workspaceId. Name the target workspace and retry.";
|
|
18
|
+
};
|
|
19
|
+
export declare function crossWorkspaceMutationRefusal(input: {
|
|
20
|
+
scope: "WORKSPACE" | "CROSS_WORKSPACE";
|
|
21
|
+
toolName: string;
|
|
22
|
+
workspaceBinding: string | undefined;
|
|
23
|
+
args: unknown;
|
|
24
|
+
}): typeof CROSS_WORKSPACE_EXPLICIT_WORKSPACE_REQUIRED | null;
|
package/dist/agent-tool-names.js
CHANGED
|
@@ -69,6 +69,7 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
69
69
|
"get_engaged_posts",
|
|
70
70
|
"get_find_leads_run",
|
|
71
71
|
"get_inbox_thread",
|
|
72
|
+
"get_integration_connection_status",
|
|
72
73
|
"get_linkedin_profile",
|
|
73
74
|
"get_or_create_direct_campaign_table",
|
|
74
75
|
"get_post_draft",
|
|
@@ -77,7 +78,6 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
77
78
|
"get_prospeo_credits",
|
|
78
79
|
"get_provider_prompt",
|
|
79
80
|
"get_published_post",
|
|
80
|
-
"get_refill_plan_v2",
|
|
81
81
|
"get_refill_target_plan",
|
|
82
82
|
"get_rows",
|
|
83
83
|
"get_rows_minimal",
|
|
@@ -122,9 +122,6 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
122
122
|
"record_campaign_review_batch",
|
|
123
123
|
"record_engage_proven_search",
|
|
124
124
|
"refill_sends",
|
|
125
|
-
"refill_sends_v2",
|
|
126
|
-
"refill_v3_sender_date_eligibility",
|
|
127
|
-
"refill_v3_workspace_inventory",
|
|
128
125
|
"refresh_paid_inmail_credits",
|
|
129
126
|
"refresh_sender_engagement",
|
|
130
127
|
"reissue_find_leads_watch_link",
|
|
@@ -138,6 +135,7 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
138
135
|
"save_post_draft",
|
|
139
136
|
"save_rubrics",
|
|
140
137
|
"search_apollo",
|
|
138
|
+
"search_available_integrations",
|
|
141
139
|
"search_engagement_posts",
|
|
142
140
|
"search_harvest_jobs",
|
|
143
141
|
"search_inbox_threads",
|
|
@@ -164,6 +162,7 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
164
162
|
"start_campaign_message_preparation",
|
|
165
163
|
"start_cli_login",
|
|
166
164
|
"start_direct_campaign",
|
|
165
|
+
"start_integration_connection",
|
|
167
166
|
"start_on_demand_campaign",
|
|
168
167
|
"update_campaign",
|
|
169
168
|
"update_campaign_brief",
|
|
@@ -185,3 +184,42 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
185
184
|
"wait_for_lead_list_ready",
|
|
186
185
|
"wait_for_rubric_results",
|
|
187
186
|
];
|
|
187
|
+
/**
|
|
188
|
+
* Explicit mutation classification lives beside (but outside) the flat tool
|
|
189
|
+
* registry. Neither this list nor `isMutatingTool` is an input to
|
|
190
|
+
* `compileAgentToolPolicy`, so editing it cannot move the fleet-pinned
|
|
191
|
+
* `policyHash`.
|
|
192
|
+
*/
|
|
193
|
+
export const MUTATING_TOOL_NAMES = [
|
|
194
|
+
"pause_campaign",
|
|
195
|
+
"archive_campaign",
|
|
196
|
+
"update_campaign",
|
|
197
|
+
"fill_campaign_horizon",
|
|
198
|
+
"set_campaign_waterfall_order",
|
|
199
|
+
"set_sender_routing",
|
|
200
|
+
"send_inbox_draft",
|
|
201
|
+
"send_inbox_manual_reply",
|
|
202
|
+
"load_csv_dnc_entries",
|
|
203
|
+
"update_cell",
|
|
204
|
+
"queue_cells",
|
|
205
|
+
];
|
|
206
|
+
const MUTATING_TOOL_NAME_SET = new Set(MUTATING_TOOL_NAMES);
|
|
207
|
+
export function isMutatingTool(name) {
|
|
208
|
+
return MUTATING_TOOL_NAME_SET.has(name);
|
|
209
|
+
}
|
|
210
|
+
export const CROSS_WORKSPACE_EXPLICIT_WORKSPACE_REQUIRED = {
|
|
211
|
+
code: "cross_workspace_explicit_workspace_required",
|
|
212
|
+
message: "Cross-workspace mutations require an explicit non-empty workspaceId. Name the target workspace and retry.",
|
|
213
|
+
};
|
|
214
|
+
export function crossWorkspaceMutationRefusal(input) {
|
|
215
|
+
const workspaceId = input.args && typeof input.args === "object"
|
|
216
|
+
? input.args.workspaceId
|
|
217
|
+
: undefined;
|
|
218
|
+
const explicitWorkspaceNamed = typeof workspaceId === "string" && workspaceId.trim().length > 0;
|
|
219
|
+
return input.scope === "CROSS_WORKSPACE" &&
|
|
220
|
+
input.workspaceBinding !== "" &&
|
|
221
|
+
isMutatingTool(input.toolName) &&
|
|
222
|
+
!explicitWorkspaceNamed
|
|
223
|
+
? CROSS_WORKSPACE_EXPLICIT_WORKSPACE_REQUIRED
|
|
224
|
+
: null;
|
|
225
|
+
}
|
package/dist/api.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare class SellableApiError extends Error {
|
|
|
35
35
|
constructor(status: number, body: string, guidance?: string);
|
|
36
36
|
}
|
|
37
37
|
export declare class SellableApi {
|
|
38
|
+
private readonly explicitWorkspaceContext;
|
|
39
|
+
withWorkspaceId<T>(workspaceId: string | null | undefined, callback: () => Promise<T>): Promise<T>;
|
|
38
40
|
private buildError;
|
|
39
41
|
private requestResponse;
|
|
40
42
|
private request;
|
package/dist/api.js
CHANGED
|
@@ -3,7 +3,9 @@ import { mkdir, rename, rm, stat } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
7
|
import { getConfig, getConfigPath, getEffectiveConfiguredWorkspaceId, resolveWorkspaceIdForRequest, } from "./auth.js";
|
|
8
|
+
import { recordAgentScope } from "./agent-scope.js";
|
|
7
9
|
export class SellableApiError extends Error {
|
|
8
10
|
status;
|
|
9
11
|
body;
|
|
@@ -19,6 +21,13 @@ export class SellableApiError extends Error {
|
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
export class SellableApi {
|
|
24
|
+
explicitWorkspaceContext = new AsyncLocalStorage();
|
|
25
|
+
async withWorkspaceId(workspaceId, callback) {
|
|
26
|
+
const normalized = typeof workspaceId === "string" && workspaceId.trim()
|
|
27
|
+
? workspaceId.trim()
|
|
28
|
+
: null;
|
|
29
|
+
return this.explicitWorkspaceContext.run(normalized, callback);
|
|
30
|
+
}
|
|
22
31
|
buildError(status, errorText) {
|
|
23
32
|
const isAuthError = status === 401 || status === 403;
|
|
24
33
|
const isWorkspaceMembershipError = status === 403 && errorText.includes("No access to workspace");
|
|
@@ -42,10 +51,16 @@ export class SellableApi {
|
|
|
42
51
|
// Re-read config on every request so workspace switches take effect immediately
|
|
43
52
|
const config = getConfig();
|
|
44
53
|
const url = `${config.apiUrl}${path}`;
|
|
54
|
+
const contextualWorkspaceId = this.explicitWorkspaceContext.getStore();
|
|
55
|
+
const hasExplicitWorkspace = (options &&
|
|
56
|
+
Object.prototype.hasOwnProperty.call(options, "workspaceId")) ||
|
|
57
|
+
contextualWorkspaceId !== undefined;
|
|
45
58
|
const requestedWorkspaceId = options && Object.prototype.hasOwnProperty.call(options, "workspaceId")
|
|
46
59
|
? options.workspaceId
|
|
47
|
-
:
|
|
48
|
-
|
|
60
|
+
: contextualWorkspaceId !== undefined
|
|
61
|
+
? contextualWorkspaceId
|
|
62
|
+
: getEffectiveConfiguredWorkspaceId(config);
|
|
63
|
+
const workspaceId = hasExplicitWorkspace
|
|
49
64
|
? resolveWorkspaceIdForRequest(requestedWorkspaceId, `${method} ${path}`)
|
|
50
65
|
: requestedWorkspaceId;
|
|
51
66
|
// Opt-in only: no `signal` is attached unless the caller asked for a bound,
|
|
@@ -93,7 +108,13 @@ export class SellableApi {
|
|
|
93
108
|
}
|
|
94
109
|
async request(method, path, body, options) {
|
|
95
110
|
const response = await this.requestResponse(method, path, body, options);
|
|
96
|
-
|
|
111
|
+
const parsed = (await response.json());
|
|
112
|
+
if (parsed &&
|
|
113
|
+
typeof parsed === "object" &&
|
|
114
|
+
Object.prototype.hasOwnProperty.call(parsed, "agentScope")) {
|
|
115
|
+
recordAgentScope(parsed.agentScope);
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
97
118
|
}
|
|
98
119
|
async get(path, options) {
|
|
99
120
|
return this.request("GET", path, undefined, options);
|
package/dist/auth.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import * as os from "os";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { readMcpAgentServiceContext } from "./agent-service-auth.js";
|
|
6
|
+
import { getAgentScope } from "./agent-scope.js";
|
|
6
7
|
export const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
7
8
|
const WORKSPACE_LOCK_ENV = "SELLABLE_LOCK_WORKSPACE_ID";
|
|
8
9
|
const REQUIRE_WORKSPACE_LOCK_ENV = "SELLABLE_REQUIRE_WORKSPACE_LOCK";
|
|
@@ -177,6 +178,11 @@ function normalizeWorkspaceId(value) {
|
|
|
177
178
|
}
|
|
178
179
|
export function getEffectiveConfiguredWorkspaceId(config) {
|
|
179
180
|
assertWorkspaceLockSatisfied("workspace resolution");
|
|
181
|
+
if (getAgentScope() === "CROSS_WORKSPACE") {
|
|
182
|
+
return (normalizeWorkspaceId(config.activeWorkspaceId) ||
|
|
183
|
+
normalizeWorkspaceId(config.workspaceId) ||
|
|
184
|
+
getLockedWorkspaceId());
|
|
185
|
+
}
|
|
180
186
|
return (getLockedWorkspaceId() ||
|
|
181
187
|
normalizeWorkspaceId(config.activeWorkspaceId) ||
|
|
182
188
|
normalizeWorkspaceId(config.workspaceId));
|
|
@@ -187,6 +193,9 @@ export function resolveWorkspaceIdForRequest(requestedWorkspaceId, action = "Sel
|
|
|
187
193
|
const requested = normalizeWorkspaceId(requestedWorkspaceId);
|
|
188
194
|
if (!lockedWorkspaceId)
|
|
189
195
|
return requested;
|
|
196
|
+
if (getAgentScope() === "CROSS_WORKSPACE") {
|
|
197
|
+
return requested || lockedWorkspaceId;
|
|
198
|
+
}
|
|
190
199
|
if (requested && requested !== lockedWorkspaceId) {
|
|
191
200
|
throw new Error(`Workspace lock violation: ${action} requested workspace ${requested}, but this profile is locked to ${lockedWorkspaceId}.`);
|
|
192
201
|
}
|
|
@@ -248,12 +257,20 @@ export function getConfig() {
|
|
|
248
257
|
const agent = readMcpAgentServiceContext(process.env, config.token);
|
|
249
258
|
if (!agent)
|
|
250
259
|
throw new Error("Invalid Agent service context");
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
260
|
+
const configuredActiveWorkspace = normalizeWorkspaceId(selectedRaw.activeWorkspaceId);
|
|
261
|
+
const configuredWorkspace = normalizeWorkspaceId(selectedRaw.workspaceId);
|
|
262
|
+
if (getAgentScope() === "CROSS_WORKSPACE") {
|
|
263
|
+
config.activeWorkspaceId =
|
|
264
|
+
configuredActiveWorkspace || agent.workspaceId;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const configuredIdentity = configuredActiveWorkspace || configuredWorkspace;
|
|
268
|
+
if (configuredIdentity &&
|
|
269
|
+
configuredIdentity !== agent.workspaceId) {
|
|
270
|
+
throw new Error("Agent service credential workspace binding mismatch");
|
|
271
|
+
}
|
|
272
|
+
config.activeWorkspaceId = agent.workspaceId;
|
|
255
273
|
}
|
|
256
|
-
config.activeWorkspaceId = agent.workspaceId;
|
|
257
274
|
config.workspaceId = agent.workspaceId;
|
|
258
275
|
config.credentialKind = "agent_service";
|
|
259
276
|
}
|
|
@@ -298,7 +315,8 @@ export function getConfiguredCredentialIntent() {
|
|
|
298
315
|
}
|
|
299
316
|
}
|
|
300
317
|
export function updateActiveWorkspace(params) {
|
|
301
|
-
if (getConfig().credentialKind === "agent_service"
|
|
318
|
+
if (getConfig().credentialKind === "agent_service" &&
|
|
319
|
+
getAgentScope() !== "CROSS_WORKSPACE") {
|
|
302
320
|
throw new Error("Agent service profiles cannot switch workspaces");
|
|
303
321
|
}
|
|
304
322
|
resolveWorkspaceIdForRequest(params.workspaceId, "set_active_workspace");
|
package/dist/refill-run-loop.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { SellableApiError } from "./api.js";
|
|
3
3
|
import { decodeRefillReadinessAuthority, refillReadinessExpectationInput, refillReadinessFingerprint, } from "./refill-readiness.js";
|
|
4
4
|
import { normalizeMessageTemplateRevision, normalizeRefillRunHandle, normalizeRefillTargetConfig, } from "./tools/campaign-processing.js";
|
|
5
|
-
import { buildRunStateFromLocalHints } from "./tools/
|
|
5
|
+
import { buildRunStateFromLocalHints } from "./tools/refill-execution-plan.js";
|
|
6
6
|
import { classifyPaidInmailRefreshReceipt, prepareRowSelectorValue as defaultPrepareRowSelectorValue, refillPrepareRequestHash as defaultRefillPrepareRequestHash, validateAgentSuppliedBroadenKeywords, } from "./tools/refill-executors.js";
|
|
7
7
|
import { sanitizeRefillReportingContextProjection, sanitizeRefillReportingProjection, } from "./tools/refill-target-plan.js";
|
|
8
8
|
/**
|
|
@@ -699,7 +699,7 @@ function isSelectedSenderOwnedPostEngagersLane(plan, action) {
|
|
|
699
699
|
stringValue(actionToolInput(action).campaignName) ??
|
|
700
700
|
stringValue(targetCampaign?.campaignName) ??
|
|
701
701
|
stringValue(targetCampaign?.name);
|
|
702
|
-
// Exact
|
|
702
|
+
// Exact refill packets retain target.campaigns identity even when a plain
|
|
703
703
|
// active-campaign route omits the optional evergreen lane projection.
|
|
704
704
|
// Prefer that authority so a resumed g3 checkpoint never falls through to
|
|
705
705
|
// the generic agent-keyword gate for a sender-owned Post Engagers campaign.
|
|
@@ -893,6 +893,39 @@ function runScopedSignalDiscoveryAttemptUpdate(action, runState, now) {
|
|
|
893
893
|
],
|
|
894
894
|
};
|
|
895
895
|
}
|
|
896
|
+
function runScopedSignalSourceValidationUpdate(action, runState, now) {
|
|
897
|
+
const toolInput = actionToolInput(action);
|
|
898
|
+
const provider = stringValue(toolInput.leadSourceProvider);
|
|
899
|
+
if (provider !== "signal-discovery" && provider !== "campaign-tracked-post") {
|
|
900
|
+
return {};
|
|
901
|
+
}
|
|
902
|
+
const campaignId = actionCampaignId(action);
|
|
903
|
+
const payload = resultPayload(runState.lastOutcome);
|
|
904
|
+
const reviewBatch = recordValue(payload.reviewBatch);
|
|
905
|
+
const reviewBatchBasisHash = stringValue(reviewBatch?.basisHash);
|
|
906
|
+
const reviewBatchRowCount = numberValue(reviewBatch?.rowCount);
|
|
907
|
+
if (!campaignId || !reviewBatchBasisHash || !reviewBatchRowCount)
|
|
908
|
+
return {};
|
|
909
|
+
const attempts = arrayValue(runState.signalSourceValidationAttempts).filter(isRecord);
|
|
910
|
+
return {
|
|
911
|
+
signalSourceValidationAttempts: [
|
|
912
|
+
...attempts.filter((attempt) => stringValue(attempt.campaignId) !== campaignId),
|
|
913
|
+
{
|
|
914
|
+
campaignId,
|
|
915
|
+
tableId: actionTableId(action),
|
|
916
|
+
senderId: actionSenderId(action),
|
|
917
|
+
sourceLeadListId: stringValue(toolInput.sourceLeadListId),
|
|
918
|
+
sourceFingerprint: actionSourceFingerprint(action),
|
|
919
|
+
reviewBatchBasisHash,
|
|
920
|
+
reviewBatchRowCount,
|
|
921
|
+
threshold: 0.1,
|
|
922
|
+
status: "pending",
|
|
923
|
+
copiedAt: now.toISOString(),
|
|
924
|
+
scope: "refill_run",
|
|
925
|
+
},
|
|
926
|
+
],
|
|
927
|
+
};
|
|
928
|
+
}
|
|
896
929
|
// Phase 110-05: Sales Nav / Prospeo same-source continuation gets exactly one
|
|
897
930
|
// bounded add per (campaign, sender) per run, mirroring the Signal Discovery
|
|
898
931
|
// run-scoped attempt bound so the loop never repeats an unchanged failed
|
|
@@ -2413,7 +2446,7 @@ async function gateBootstrap(input, deps, ctx) {
|
|
|
2413
2446
|
input.reportingContext = issuedReportingContext;
|
|
2414
2447
|
}
|
|
2415
2448
|
const pinnedRunState = pinInitialSenderLaneChains(plan, ctx.runState);
|
|
2416
|
-
// Exact fenced plans are projected by
|
|
2449
|
+
// Exact fenced plans are projected by projectedRefillExecutionPacket(), whose
|
|
2417
2450
|
// bootstrap.paidCredit array is intentionally empty. The canonical refresh
|
|
2418
2451
|
// authority is packet.target.globalActionQueue; reading the bootstrap array
|
|
2419
2452
|
// minted `{ version: 2, receipts: [] }` on every run, which made the next
|
|
@@ -2524,7 +2557,7 @@ async function gateBootstrap(input, deps, ctx) {
|
|
|
2524
2557
|
})
|
|
2525
2558
|
: runStateWithoutCreditTrust;
|
|
2526
2559
|
await safeJournalAppend(ctx, deps, deps.journal.renderBootstrapSection({
|
|
2527
|
-
summary: "Refill
|
|
2560
|
+
summary: "Refill exact-run execution bootstrap",
|
|
2528
2561
|
senderSummary: safeJson(recordValue(plan.bootstrap)?.senders ?? []),
|
|
2529
2562
|
laneSummary: safeJson(recordValue(plan.bootstrap)?.laneOrder ?? []),
|
|
2530
2563
|
targetSummary: safeJson(recordValue(plan.bootstrap)?.target ?? []),
|
|
@@ -4444,8 +4477,66 @@ async function gateVerify(input, deps, ctx, budgets) {
|
|
|
4444
4477
|
return advanceBackToPlan(input, deps, ctx);
|
|
4445
4478
|
}
|
|
4446
4479
|
const actionType = stringValue(action.type);
|
|
4447
|
-
|
|
4448
|
-
|
|
4480
|
+
let schedulerOutcome = resultPayload(ctx.runState.lastOutcome);
|
|
4481
|
+
let schedulerOutcomeStatus = stringValue(schedulerOutcome.status);
|
|
4482
|
+
// HOTFIX 2026-07-27: executeOneYoloPrimitive deliberately returns
|
|
4483
|
+
// matching_scheduler_active when the exact request key is still running
|
|
4484
|
+
// after its bounded dispatch-site settle. g3_verify previously ignored that
|
|
4485
|
+
// status, reread the ready-buffer plan immediately, and terminalized the
|
|
4486
|
+
// exact fence as loaded_awaiting_scheduler while the scheduler was still
|
|
4487
|
+
// working. That released the workspace coordinator before the request's
|
|
4488
|
+
// official receipt existed, so a later cron/read could show 30/30 even
|
|
4489
|
+
// though the refill command itself stopped at 25/30.
|
|
4490
|
+
//
|
|
4491
|
+
// Keep this exact fence at g3_verify and poll through the SAME action. The
|
|
4492
|
+
// executor's status preflight is request-key scoped and never redispatches a
|
|
4493
|
+
// matching active request. When the per-invocation wait budget is reached,
|
|
4494
|
+
// heartbeat and return the same runId/fence continuation; never convert an
|
|
4495
|
+
// active request into loaded_awaiting_scheduler.
|
|
4496
|
+
if (actionType === "run_scheduler_sweep" &&
|
|
4497
|
+
schedulerOutcomeStatus === "matching_scheduler_active") {
|
|
4498
|
+
for (let poll = 0; poll < budgets.maxPollsPerWait; poll += 1) {
|
|
4499
|
+
const did = await deps.executors.executeOneYoloPrimitive(action, input.workspaceId);
|
|
4500
|
+
schedulerOutcome = resultPayload(did);
|
|
4501
|
+
schedulerOutcomeStatus = stringValue(schedulerOutcome.status);
|
|
4502
|
+
await deps.runClient.recordRunOutcome({
|
|
4503
|
+
workspaceId: input.workspaceId,
|
|
4504
|
+
...exactRunTarget(ctx),
|
|
4505
|
+
runId: ctx.runId,
|
|
4506
|
+
fence: ctx.fence,
|
|
4507
|
+
eventId: lastEventId(ctx),
|
|
4508
|
+
did,
|
|
4509
|
+
outcome: did,
|
|
4510
|
+
});
|
|
4511
|
+
ctx.runState = mergeRunState(ctx.runState, {
|
|
4512
|
+
lastOutcome: did,
|
|
4513
|
+
progress: mergeProgress(ctx, {
|
|
4514
|
+
schedulerRequestLastCheckedAt: (deps.now?.() ?? new Date()).toISOString(),
|
|
4515
|
+
schedulerRequestStatus: schedulerOutcomeStatus ?? "scheduler_status_unreadable",
|
|
4516
|
+
}),
|
|
4517
|
+
});
|
|
4518
|
+
if (schedulerOutcomeStatus !== "matching_scheduler_active")
|
|
4519
|
+
break;
|
|
4520
|
+
const waitResult = await sleepForWait(input, deps, ctx, budgets);
|
|
4521
|
+
if (waitResult) {
|
|
4522
|
+
if (waitResult.status !== "in_progress")
|
|
4523
|
+
return waitResult;
|
|
4524
|
+
return {
|
|
4525
|
+
...waitResult,
|
|
4526
|
+
guidance: "The exact workspace scheduler request is still running. Re-invoke this same runId/fence; verification will poll the same request key until its official terminal receipt, without redispatching it.",
|
|
4527
|
+
};
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
if (schedulerOutcomeStatus === "matching_scheduler_active") {
|
|
4531
|
+
const continuation = await heartbeatAndContinue(input, deps, ctx);
|
|
4532
|
+
if (continuation.status !== "in_progress")
|
|
4533
|
+
return continuation;
|
|
4534
|
+
return {
|
|
4535
|
+
...continuation,
|
|
4536
|
+
guidance: "The exact workspace scheduler request is still running. Re-invoke this same runId/fence; verification will poll the same request key until its official terminal receipt, without redispatching it.",
|
|
4537
|
+
};
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4449
4540
|
if (actionType === "run_scheduler_sweep" &&
|
|
4450
4541
|
(schedulerOutcomeStatus === "uncertain_outcome" ||
|
|
4451
4542
|
schedulerOutcomeStatus === "scheduler_receipt_incomplete")) {
|
|
@@ -4469,6 +4560,57 @@ async function gateVerify(input, deps, ctx, budgets) {
|
|
|
4469
4560
|
guidance: "The exact scheduler request has no authoritative terminal receipt. The request key was not redispatched; use a fresh planner-issued action key after scheduler reconciliation.",
|
|
4470
4561
|
});
|
|
4471
4562
|
}
|
|
4563
|
+
const terminalSchedulerReceipt = actionType === "run_scheduler_sweep"
|
|
4564
|
+
? schedulerStatusTerminalReceipt(schedulerOutcome)
|
|
4565
|
+
: null;
|
|
4566
|
+
if (actionType === "run_scheduler_sweep" && terminalSchedulerReceipt) {
|
|
4567
|
+
// The scheduler request is now officially done. Only this post-receipt
|
|
4568
|
+
// authoritative reread may decide whether the refill target completed.
|
|
4569
|
+
// A still-loaded plan is not success: surface a concrete retryable blocker
|
|
4570
|
+
// with the terminal receipt instead of claiming loaded while leaving the
|
|
4571
|
+
// operator unable to distinguish a scheduler miss from an active run.
|
|
4572
|
+
const fresh = await deps.readPlan(refillPlanReadInput(input, { ctx, journal: false }));
|
|
4573
|
+
ctx.plan = fresh;
|
|
4574
|
+
const rawDoneReason = doneReasonFromPlan(fresh);
|
|
4575
|
+
const normalizedDoneReason = rawDoneReason
|
|
4576
|
+
? normalizeDoneReason(rawDoneReason)
|
|
4577
|
+
: null;
|
|
4578
|
+
if (canonicalReportingCoverageComplete(fresh) ||
|
|
4579
|
+
normalizedDoneReason === "complete" ||
|
|
4580
|
+
normalizedDoneReason === "capped_by_scheduler") {
|
|
4581
|
+
return completeTerminal(input, deps, ctx, normalizedDoneReason ?? "complete", {
|
|
4582
|
+
plan: fresh,
|
|
4583
|
+
schedulerRunReceipt: terminalSchedulerReceipt,
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
const freshHead = firstAction(fresh);
|
|
4587
|
+
if (normalizedDoneReason === "loaded_awaiting_scheduler" ||
|
|
4588
|
+
stringValue(freshHead?.type) === "wait_for_scheduler") {
|
|
4589
|
+
return completeTerminal(input, deps, ctx, "blocked", {
|
|
4590
|
+
blocker: "scheduler_terminal_with_remaining_gap",
|
|
4591
|
+
retryable: true,
|
|
4592
|
+
plan: fresh,
|
|
4593
|
+
schedulerRunReceipt: terminalSchedulerReceipt,
|
|
4594
|
+
requestKey: stringValue(actionToolInput(action).requestKey),
|
|
4595
|
+
actionKey: stringValue(action.actionKey),
|
|
4596
|
+
targetDate: stringValue(actionToolInput(action).targetDate) ?? input.targetDate,
|
|
4597
|
+
coverage: coverageSnapshot(fresh),
|
|
4598
|
+
guidance: "The exact workspace scheduler request produced an official terminal receipt, but the post-receipt target plan still has a projected gap. Do not report loaded_awaiting_scheduler; retry only from a fresh planner-issued scheduler action after the receipt's named capacity, window, cooldown, or eligibility condition changes.",
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
4601
|
+
if (rawDoneReason) {
|
|
4602
|
+
return completeTerminal(input, deps, ctx, rawDoneReason, {
|
|
4603
|
+
plan: fresh,
|
|
4604
|
+
schedulerRunReceipt: terminalSchedulerReceipt,
|
|
4605
|
+
});
|
|
4606
|
+
}
|
|
4607
|
+
return advanceBackToPlan(input, deps, ctx, {
|
|
4608
|
+
lastOutcome: ctx.runState.lastOutcome,
|
|
4609
|
+
progress: mergeProgress(ctx, {
|
|
4610
|
+
schedulerTerminalReceiptObservedAt: (deps.now?.() ?? new Date()).toISOString(),
|
|
4611
|
+
}),
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4472
4614
|
if (!ctx.plan &&
|
|
4473
4615
|
typeof actionType === "string" &&
|
|
4474
4616
|
BROADEN_SIGNAL_SEARCH_TYPES.has(actionType)) {
|
|
@@ -4513,6 +4655,14 @@ async function gateVerify(input, deps, ctx, budgets) {
|
|
|
4513
4655
|
// keep going — instead of terminalizing the invite lane with confirmed-fillable
|
|
4514
4656
|
// slots still open. Once a broadening round proves the universe dry this run, this
|
|
4515
4657
|
// no longer intercepts and the refusal terminalizes typed below.
|
|
4658
|
+
if (actionType === "copy_selected_source_rows" &&
|
|
4659
|
+
stringValue(recordValue(ctx.runState.lastOutcome)?.status) ===
|
|
4660
|
+
"executed_and_reread") {
|
|
4661
|
+
return advanceBackToPlan(input, deps, ctx, {
|
|
4662
|
+
...runScopedSignalSourceValidationUpdate(action, ctx.runState, deps.now?.() ?? new Date()),
|
|
4663
|
+
...clearRunScopedDrySourcePrepAttempts(actionCampaignId(action), ctx.runState),
|
|
4664
|
+
});
|
|
4665
|
+
}
|
|
4516
4666
|
if (actionType === "continue_signal_discovery_source" &&
|
|
4517
4667
|
isSignalSelectionBroadenableRefusal(ctx.runState.lastOutcome) &&
|
|
4518
4668
|
!hasRunScopedSignalUniverseExhausted(action, ctx.runState)) {
|