@sellable/mcp 0.1.752 → 0.1.754
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 +78 -0
- package/dist/agent-integration-request-context.js +74 -0
- package/dist/agent-tool-names.d.ts +1 -1
- package/dist/agent-tool-names.js +3 -0
- package/dist/api.d.ts +15 -0
- package/dist/api.js +37 -12
- package/dist/refill-run-loop.js +23 -2
- package/dist/server.js +51 -3
- package/dist/tools/integrations.d.ts +322 -0
- package/dist/tools/integrations.js +477 -0
- package/dist/tools/registry.d.ts +155 -0
- package/dist/tools/registry.js +8 -3
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted request-actor plumbing for the managed-integration tools.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS A SEPARATE MODULE. `server.ts` calls `main()` at import time and
|
|
5
|
+
* connects a stdio transport, so it cannot be imported by a unit test. The
|
|
6
|
+
* tool-name set and the fail-closed resolver therefore live here, `server.ts`
|
|
7
|
+
* imports them, and plan 141-06 imports the same constant when it registers the
|
|
8
|
+
* three `case` labels. One source of truth, testable in isolation.
|
|
9
|
+
*
|
|
10
|
+
* THE ACTOR IS NEVER A TOOL INPUT, and there is exactly one mechanism.
|
|
11
|
+
* `packages/sellable-install/lib/sellable-agent/mcp-context-proxy.mjs:352-402`
|
|
12
|
+
* REJECTS any model-supplied `_meta` with `model_metadata_rejected`, validates a
|
|
13
|
+
* signed single-use descriptor, burns a nonce, and only then injects
|
|
14
|
+
* `_meta.sellableAgent` itself. Upstream of that,
|
|
15
|
+
* `packages/sellable-install/lib/sellable-agent/hermes-bridge.mjs:646-665`
|
|
16
|
+
* patches Hermes at the SHARED generic `server.session.call_tool` dispatch site,
|
|
17
|
+
* so the injection is per-call and tool-agnostic and a newly registered tool
|
|
18
|
+
* inherits it with no further change. A model-supplied actor claim is worthless,
|
|
19
|
+
* so this module reads the actor ONLY from the already-verified credential
|
|
20
|
+
* boundary context and NEVER from tool arguments. Do not add a second mechanism.
|
|
21
|
+
*
|
|
22
|
+
* THE BACKEND-FACING CONSEQUENCE, recorded here because plan 141-06's routes
|
|
23
|
+
* depend on it: on the `Bearer sat_` middleware branch, `X-authuserid` and
|
|
24
|
+
* `X-workspaceid` are OVERWRITTEN from `AgentServicePrincipal.subjectUserId`
|
|
25
|
+
* and the five `x-sellable-*` principal headers are DELETED
|
|
26
|
+
* (`src/middleware.ts:80-86`, `:134-140`, `:806-809`), so no caller-supplied
|
|
27
|
+
* identity header can carry the Slack requester. The actor must therefore travel
|
|
28
|
+
* as an EXPLICIT REQUEST BODY FIELD that the route re-verifies against
|
|
29
|
+
* `WorkspaceSlackIdentity`. See
|
|
30
|
+
* `.planning/artifacts/uat/141/current/mcp/actor-propagation.json` for the
|
|
31
|
+
* measured trace and the exact residual gap.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* The provider-neutral managed-integration tools. Plan 141-06 registers the
|
|
35
|
+
* `case` labels and the tool schemas; this set exists in WAVE 2 so the
|
|
36
|
+
* fail-closed guard is already in place when they arrive, and so the two plans
|
|
37
|
+
* do not both try to own the dispatch switch.
|
|
38
|
+
*/
|
|
39
|
+
export declare const AGENT_INTEGRATION_CONTEXT_TOOL_NAMES: readonly ["integrations_list_tools", "integrations_describe_tool", "integrations_call_tool"];
|
|
40
|
+
export type AgentIntegrationContextToolName = (typeof AGENT_INTEGRATION_CONTEXT_TOOL_NAMES)[number];
|
|
41
|
+
/** The trusted actor triple a managed-integration call forwards to the backend. */
|
|
42
|
+
export type AgentIntegrationRequestContext = {
|
|
43
|
+
requesterId: string;
|
|
44
|
+
channelId: string;
|
|
45
|
+
providerRequestId: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The boundary context shape `createMcpCredentialBoundary` returns on success.
|
|
49
|
+
* `undefined` is accepted because the `agent_service` refusal branches at
|
|
50
|
+
* `agent-service-auth.ts:139-157` return no `context` at all.
|
|
51
|
+
*/
|
|
52
|
+
type BoundaryContext = {
|
|
53
|
+
requesterId?: string | null;
|
|
54
|
+
channelId?: string | null;
|
|
55
|
+
providerRequestId?: string | null;
|
|
56
|
+
} | null | undefined;
|
|
57
|
+
export type AgentIntegrationRequestActor = {
|
|
58
|
+
/** True when the called tool is one that must have a trusted actor. */
|
|
59
|
+
required: boolean;
|
|
60
|
+
/** Present only when the actor is fully resolved. */
|
|
61
|
+
context: AgentIntegrationRequestContext | null;
|
|
62
|
+
};
|
|
63
|
+
export declare const AGENT_INTEGRATION_CONTEXT_REQUIRED_ERROR = "agent_request_context_required";
|
|
64
|
+
export declare function isAgentIntegrationContextTool(name: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Resolves the trusted actor for a managed-integration tool call.
|
|
67
|
+
*
|
|
68
|
+
* FAILS CLOSED. A tool in the set with no boundary context — a `human`
|
|
69
|
+
* credential, an `invalid` boundary, or an `agent_service` call whose
|
|
70
|
+
* `_meta.sellableAgent` never arrived — resolves to `context: null`, and the
|
|
71
|
+
* dispatch must refuse with `agent_request_context_required` rather than
|
|
72
|
+
* defaulting to a synthetic or absent actor. `agent-service-auth.ts:128-147`
|
|
73
|
+
* already refuses every `agent_service` call missing the triple with the same
|
|
74
|
+
* code; this guard is the belt for tools whose authorization decision is
|
|
75
|
+
* `AUTO`/read-only and for the non-`agent_service` modes that never reach it.
|
|
76
|
+
*/
|
|
77
|
+
export declare function agentIntegrationRequestActor(name: string, boundaryContext: BoundaryContext): AgentIntegrationRequestActor;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted request-actor plumbing for the managed-integration tools.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS A SEPARATE MODULE. `server.ts` calls `main()` at import time and
|
|
5
|
+
* connects a stdio transport, so it cannot be imported by a unit test. The
|
|
6
|
+
* tool-name set and the fail-closed resolver therefore live here, `server.ts`
|
|
7
|
+
* imports them, and plan 141-06 imports the same constant when it registers the
|
|
8
|
+
* three `case` labels. One source of truth, testable in isolation.
|
|
9
|
+
*
|
|
10
|
+
* THE ACTOR IS NEVER A TOOL INPUT, and there is exactly one mechanism.
|
|
11
|
+
* `packages/sellable-install/lib/sellable-agent/mcp-context-proxy.mjs:352-402`
|
|
12
|
+
* REJECTS any model-supplied `_meta` with `model_metadata_rejected`, validates a
|
|
13
|
+
* signed single-use descriptor, burns a nonce, and only then injects
|
|
14
|
+
* `_meta.sellableAgent` itself. Upstream of that,
|
|
15
|
+
* `packages/sellable-install/lib/sellable-agent/hermes-bridge.mjs:646-665`
|
|
16
|
+
* patches Hermes at the SHARED generic `server.session.call_tool` dispatch site,
|
|
17
|
+
* so the injection is per-call and tool-agnostic and a newly registered tool
|
|
18
|
+
* inherits it with no further change. A model-supplied actor claim is worthless,
|
|
19
|
+
* so this module reads the actor ONLY from the already-verified credential
|
|
20
|
+
* boundary context and NEVER from tool arguments. Do not add a second mechanism.
|
|
21
|
+
*
|
|
22
|
+
* THE BACKEND-FACING CONSEQUENCE, recorded here because plan 141-06's routes
|
|
23
|
+
* depend on it: on the `Bearer sat_` middleware branch, `X-authuserid` and
|
|
24
|
+
* `X-workspaceid` are OVERWRITTEN from `AgentServicePrincipal.subjectUserId`
|
|
25
|
+
* and the five `x-sellable-*` principal headers are DELETED
|
|
26
|
+
* (`src/middleware.ts:80-86`, `:134-140`, `:806-809`), so no caller-supplied
|
|
27
|
+
* identity header can carry the Slack requester. The actor must therefore travel
|
|
28
|
+
* as an EXPLICIT REQUEST BODY FIELD that the route re-verifies against
|
|
29
|
+
* `WorkspaceSlackIdentity`. See
|
|
30
|
+
* `.planning/artifacts/uat/141/current/mcp/actor-propagation.json` for the
|
|
31
|
+
* measured trace and the exact residual gap.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* The provider-neutral managed-integration tools. Plan 141-06 registers the
|
|
35
|
+
* `case` labels and the tool schemas; this set exists in WAVE 2 so the
|
|
36
|
+
* fail-closed guard is already in place when they arrive, and so the two plans
|
|
37
|
+
* do not both try to own the dispatch switch.
|
|
38
|
+
*/
|
|
39
|
+
export const AGENT_INTEGRATION_CONTEXT_TOOL_NAMES = Object.freeze([
|
|
40
|
+
"integrations_list_tools",
|
|
41
|
+
"integrations_describe_tool",
|
|
42
|
+
"integrations_call_tool",
|
|
43
|
+
]);
|
|
44
|
+
export const AGENT_INTEGRATION_CONTEXT_REQUIRED_ERROR = "agent_request_context_required";
|
|
45
|
+
export function isAgentIntegrationContextTool(name) {
|
|
46
|
+
return AGENT_INTEGRATION_CONTEXT_TOOL_NAMES.includes(name);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the trusted actor for a managed-integration tool call.
|
|
50
|
+
*
|
|
51
|
+
* FAILS CLOSED. A tool in the set with no boundary context — a `human`
|
|
52
|
+
* credential, an `invalid` boundary, or an `agent_service` call whose
|
|
53
|
+
* `_meta.sellableAgent` never arrived — resolves to `context: null`, and the
|
|
54
|
+
* dispatch must refuse with `agent_request_context_required` rather than
|
|
55
|
+
* defaulting to a synthetic or absent actor. `agent-service-auth.ts:128-147`
|
|
56
|
+
* already refuses every `agent_service` call missing the triple with the same
|
|
57
|
+
* code; this guard is the belt for tools whose authorization decision is
|
|
58
|
+
* `AUTO`/read-only and for the non-`agent_service` modes that never reach it.
|
|
59
|
+
*/
|
|
60
|
+
export function agentIntegrationRequestActor(name, boundaryContext) {
|
|
61
|
+
if (!isAgentIntegrationContextTool(name)) {
|
|
62
|
+
return { required: false, context: null };
|
|
63
|
+
}
|
|
64
|
+
const requesterId = boundaryContext?.requesterId?.trim();
|
|
65
|
+
const channelId = boundaryContext?.channelId?.trim();
|
|
66
|
+
const providerRequestId = boundaryContext?.providerRequestId?.trim();
|
|
67
|
+
if (!requesterId || !channelId || !providerRequestId) {
|
|
68
|
+
return { required: true, context: null };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
required: true,
|
|
72
|
+
context: { requesterId, channelId, providerRequestId },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -3,4 +3,4 @@
|
|
|
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", "get_refill_plan_v2", "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", "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", "refill_sends_v2", "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_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_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"];
|
|
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", "get_refill_plan_v2", "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", "refill_sends_v2", "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_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_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"];
|
package/dist/agent-tool-names.js
CHANGED
|
@@ -93,6 +93,9 @@ export const SELLABLE_MCP_TOOL_NAMES = [
|
|
|
93
93
|
"get_workspace",
|
|
94
94
|
"import_leads",
|
|
95
95
|
"init_on_demand_sequence",
|
|
96
|
+
"integrations_call_tool",
|
|
97
|
+
"integrations_describe_tool",
|
|
98
|
+
"integrations_list_tools",
|
|
96
99
|
"list_column_types",
|
|
97
100
|
"list_dnc_entries",
|
|
98
101
|
"list_post_draft_iterations",
|
package/dist/api.d.ts
CHANGED
|
@@ -11,6 +11,21 @@ export interface DownloadToFileResult {
|
|
|
11
11
|
export interface ApiRequestOptions {
|
|
12
12
|
workspaceId?: string | null;
|
|
13
13
|
agentEffectId?: string;
|
|
14
|
+
/**
|
|
15
|
+
* An OPT-IN client-side deadline, in milliseconds.
|
|
16
|
+
*
|
|
17
|
+
* Added by plan 141-06 because this client previously set no `timeout`, no
|
|
18
|
+
* `AbortSignal`, and no `signal` anywhere. The old deadline came from
|
|
19
|
+
* `timeout: 120` on a per-binding `mcp_servers` entry in the Hermes profile;
|
|
20
|
+
* that entry no longer exists, so a hung backend would hang the Slack turn
|
|
21
|
+
* forever with nothing on the client side to end it.
|
|
22
|
+
*
|
|
23
|
+
* It is OPTIONAL and defaults to undefined ON PURPOSE: the existing 175 tools
|
|
24
|
+
* keep their current unbounded behaviour byte-for-byte (some legitimately run
|
|
25
|
+
* against 240 s and 300 s backend routes), and only the callers that opt in get
|
|
26
|
+
* a bound. The three `integrations_*` tools opt in.
|
|
27
|
+
*/
|
|
28
|
+
timeoutMs?: number;
|
|
14
29
|
}
|
|
15
30
|
export declare class SellableApiError extends Error {
|
|
16
31
|
status: number;
|
package/dist/api.js
CHANGED
|
@@ -48,18 +48,43 @@ export class SellableApi {
|
|
|
48
48
|
const workspaceId = options && Object.prototype.hasOwnProperty.call(options, "workspaceId")
|
|
49
49
|
? resolveWorkspaceIdForRequest(requestedWorkspaceId, `${method} ${path}`)
|
|
50
50
|
: requestedWorkspaceId;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
// Opt-in only: no `signal` is attached unless the caller asked for a bound,
|
|
52
|
+
// so every existing tool's request is byte-identical to before.
|
|
53
|
+
const controller = typeof options?.timeoutMs === "number" && options.timeoutMs > 0
|
|
54
|
+
? new AbortController()
|
|
55
|
+
: null;
|
|
56
|
+
const timer = controller
|
|
57
|
+
? setTimeout(() => controller.abort(), options.timeoutMs)
|
|
58
|
+
: null;
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await fetch(url, {
|
|
62
|
+
method,
|
|
63
|
+
headers: {
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
Authorization: `Bearer ${config.token}`,
|
|
66
|
+
...(workspaceId ? { "x-workspaceid": workspaceId } : {}),
|
|
67
|
+
...(options?.agentEffectId
|
|
68
|
+
? { "x-sellable-agent-effect-id": options.agentEffectId }
|
|
69
|
+
: {}),
|
|
70
|
+
},
|
|
71
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
72
|
+
...(body && { body: JSON.stringify(body) }),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (controller?.signal.aborted) {
|
|
77
|
+
throw new SellableApiError(504, JSON.stringify({
|
|
78
|
+
error: "integration_client_timeout",
|
|
79
|
+
timeoutMs: options?.timeoutMs ?? null,
|
|
80
|
+
}), "The Sellable API did not respond inside the client deadline. Retry, or report the tool name if it keeps happening.");
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
if (timer)
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
}
|
|
63
88
|
if (!response.ok) {
|
|
64
89
|
const errorText = await response.text();
|
|
65
90
|
throw this.buildError(response.status, errorText);
|
package/dist/refill-run-loop.js
CHANGED
|
@@ -78,6 +78,12 @@ const REFILL_DONE_REASONS = new Set([
|
|
|
78
78
|
"abandoned_stale_checkpoint",
|
|
79
79
|
"signal_search_provider_unavailable",
|
|
80
80
|
"exact_action_complete",
|
|
81
|
+
// fix(112ay): the fence opened, the fresh plan's head had already drifted
|
|
82
|
+
// away from the pinned action, and NOTHING executed. The pinned edge was
|
|
83
|
+
// never attempted, so this terminal must not read as a completion — the
|
|
84
|
+
// coordinator records no claim and the edge stays selectable when the
|
|
85
|
+
// planner exposes it as head again.
|
|
86
|
+
"exact_action_superseded",
|
|
81
87
|
]);
|
|
82
88
|
// fix(110-20d): a resumed scheduler wait must be mortal. Terminalize a stale
|
|
83
89
|
// checkpoint once the persisted wait entry has aged past this multiple of the
|
|
@@ -2636,11 +2642,26 @@ async function gatePlan(input, deps, ctx) {
|
|
|
2636
2642
|
// preflight/fence. Continuing inside this run misattributes later blockers
|
|
2637
2643
|
// to the original target (for example, a Signal outage reported against a
|
|
2638
2644
|
// scheduler action) and is the root of cross-action retry loops.
|
|
2639
|
-
|
|
2645
|
+
//
|
|
2646
|
+
// fix(112ay): head drift BEFORE anything executed is not a completion. Run
|
|
2647
|
+
// 5 (Dotwork td7-28): the sweep fence opened, the in-fence dated capacity
|
|
2648
|
+
// read blocked pending a credit refresh, the fresh head became
|
|
2649
|
+
// approve_messages, and this branch stamped exact_action_complete on a
|
|
2650
|
+
// sweep that NEVER RAN — the coordinator hardened that into a confirmed
|
|
2651
|
+
// claim while a directly-dispatched dated sweep placed 18 cells
|
|
2652
|
+
// immediately. Only a fence that actually executed its pinned (or
|
|
2653
|
+
// sanctioned in-fence) action may claim completion; an unexecuted pin
|
|
2654
|
+
// terminals exact_action_superseded, which records NO claim.
|
|
2655
|
+
const fenceExecutedPinnedAction = Boolean(executedAction);
|
|
2656
|
+
return completeTerminal(input, deps, ctx, fenceExecutedPinnedAction
|
|
2657
|
+
? "exact_action_complete"
|
|
2658
|
+
: "exact_action_superseded", {
|
|
2640
2659
|
expectedActionKey,
|
|
2641
2660
|
nextActionKey,
|
|
2642
2661
|
nextActionType: stringValue(action.type),
|
|
2643
|
-
guidance:
|
|
2662
|
+
guidance: fenceExecutedPinnedAction
|
|
2663
|
+
? "The pinned exact action is no longer the planner head. Return to the workspace coordinator and preflight the fresh action behind a new fence."
|
|
2664
|
+
: "The pinned exact action was superseded before this fence executed anything. No completion is claimed; return to the workspace coordinator and preflight the fresh head behind a new fence.",
|
|
2644
2665
|
plan,
|
|
2645
2666
|
});
|
|
2646
2667
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { HttpAgentApprovalEffectPort } from "./agent-approval-effect-port.js";
|
|
5
|
+
import { AGENT_INTEGRATION_CONTEXT_REQUIRED_ERROR, agentIntegrationRequestActor, } from "./agent-integration-request-context.js";
|
|
4
6
|
import { createMcpCredentialBoundary, resolveMcpCredentialIntent, } from "./agent-service-auth.js";
|
|
5
|
-
import { getConfig, getConfiguredCredentialIntent } from "./auth.js";
|
|
6
7
|
import { compileAgentToolPolicy, createAgentMcpAuthorizationHandlers, } from "./agent-tool-policy.js";
|
|
7
|
-
import {
|
|
8
|
+
import { getConfig, getConfiguredCredentialIntent } from "./auth.js";
|
|
8
9
|
import { MCP_RUNTIME_IDENTITY } from "./runtime-identity.js";
|
|
9
10
|
import { getSkillByName, listSkills } from "./skills.js";
|
|
10
11
|
import { getAuthStatus } from "./tools/auth.js";
|
|
@@ -37,6 +38,7 @@ import { bootstrapFindLeads, cancelFindLeads, getFindLeadsRun, reissueFindLeadsW
|
|
|
37
38
|
import { getCampaignFramework } from "./tools/framework.js";
|
|
38
39
|
import { confirmHarvestJobCompanies, searchHarvestJobs, } from "./tools/harvest-jobs.js";
|
|
39
40
|
import { checkInboxReplyEligibility, getInboxThread, searchInboxThreads, sendInboxDraft, sendInboxManualReply, updateInboxDraft, } from "./tools/inbox.js";
|
|
41
|
+
import { integrationsCallTool, integrationsDescribeTool, integrationsListTools, } from "./tools/integrations.js";
|
|
40
42
|
import { cancelLeadImport, confirmLeadList, confirmProspeoCompanyAccounts, getProviderPrompt, importLeads, listDncEntriesTool, loadCsvDncEntriesTool, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeo, searchProspeoCompanies, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
|
|
41
43
|
import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfile, fetchPostEngagers, getLinkedInProfile, getUserPosts, } from "./tools/linkedin.js";
|
|
42
44
|
import { getCampaignNavigationState } from "./tools/navigation.js";
|
|
@@ -208,7 +210,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
208
210
|
],
|
|
209
211
|
};
|
|
210
212
|
}
|
|
211
|
-
agentEffectId = boundary.authorization
|
|
213
|
+
agentEffectId = boundary.authorization
|
|
214
|
+
.effectId;
|
|
215
|
+
}
|
|
216
|
+
// TRUSTED ACTOR PLUMBING FOR THE MANAGED-INTEGRATION TOOLS, keyed on the
|
|
217
|
+
// TOOL-NAME SET rather than on a `case` label, so plan 141-06's registration
|
|
218
|
+
// inherits the guard instead of re-implementing it. Plan 141-06 owns the
|
|
219
|
+
// three `case` labels and consumes `integrationActor.context`; this wave owns
|
|
220
|
+
// the plumbing and its fail-closed refusal. The two plans must not both try
|
|
221
|
+
// to own the switch.
|
|
222
|
+
//
|
|
223
|
+
// The actor comes ONLY from `boundary.context`, which is derived from
|
|
224
|
+
// `_meta.sellableAgent` — metadata `mcp-context-proxy.mjs:352-402` injects
|
|
225
|
+
// itself after REJECTING any model-supplied `_meta`. It is never read from
|
|
226
|
+
// tool ARGUMENTS, because a model-supplied actor claim is worthless.
|
|
227
|
+
//
|
|
228
|
+
// FAIL CLOSED, never default: a `human` credential, an `invalid` boundary, or
|
|
229
|
+
// a missing actor triple all refuse. A managed-integration call without a
|
|
230
|
+
// resolved Slack requester has no way to be authorized against a PERSONAL
|
|
231
|
+
// connection, so proceeding would be strictly worse than refusing.
|
|
232
|
+
const integrationActor = agentIntegrationRequestActor(name, boundary.mode === "agent_service" ? boundary.context : null);
|
|
233
|
+
if (integrationActor.required && !integrationActor.context) {
|
|
234
|
+
return {
|
|
235
|
+
isError: true,
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: JSON.stringify({
|
|
240
|
+
ok: false,
|
|
241
|
+
error: AGENT_INTEGRATION_CONTEXT_REQUIRED_ERROR,
|
|
242
|
+
decision: "DENY",
|
|
243
|
+
}),
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
};
|
|
212
247
|
}
|
|
213
248
|
let result;
|
|
214
249
|
switch (name) {
|
|
@@ -636,6 +671,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
636
671
|
case "set_sender_routing":
|
|
637
672
|
result = await setSenderRoutingTool(args);
|
|
638
673
|
break;
|
|
674
|
+
// Plan 141-06 owns these three `case` labels ONLY. The fail-closed actor
|
|
675
|
+
// guard above is plan 141-04 T2's and is keyed on the tool-name SET, so it
|
|
676
|
+
// already ran before this switch and `integrationActor.context` is verified.
|
|
677
|
+
// Phase 142-05 adds its connect-tool cases to this same switch additively.
|
|
678
|
+
case "integrations_list_tools":
|
|
679
|
+
result = await integrationsListTools(args, integrationActor.context, agentEffectId);
|
|
680
|
+
break;
|
|
681
|
+
case "integrations_describe_tool":
|
|
682
|
+
result = await integrationsDescribeTool(args, integrationActor.context, agentEffectId);
|
|
683
|
+
break;
|
|
684
|
+
case "integrations_call_tool":
|
|
685
|
+
result = await integrationsCallTool(args, integrationActor.context, agentEffectId);
|
|
686
|
+
break;
|
|
639
687
|
case "get_company_info":
|
|
640
688
|
result = await getCompanyInfoTool();
|
|
641
689
|
if (boundary.mode === "agent_service" &&
|