This document covers the Inter-Tool Invocation feature of Power Platform ToolBox (PPTB). It is split into two parts:
- Part 1 – Callee – For tool developers whose tool receives a launch request from another tool.
- Part 2 – Caller – For tool developers whose tool initiates a launch request against another tool.
- Inter-Tool Invocation
- Table of Contents
- Overview
- Part 1 – Callee (the tool that accepts invocations)
- Part 2 – Caller (the tool that launches other tools)
- Part 3 – MCP Agentic Invocation
- End-to-End Scenario: FXS "Send To" Flyout
- Scenario summary
- Step 1 – Callee tools declare the
"fetchxml"capability - Step 2 – FXS builds its "Send To" flyout from discovered tools
- Step 3 – User selects DMS; FXS launches it with
noReturn: true - Step 4 – DMS reads the prefill data and uses it
- Step 5 – DMS closes; FXS Promise resolves
- Full sequence diagram
- Validation and Tooling
- Troubleshooting
launchToolthrows "Tool not found"launchToolthrows "A callee invocation is already in progress"getLaunchContext()returnsnullwhen expecting prefill data- Caller
Promiseresolves withnullunexpectedly - Changes to
pptb.config.jsonare not picked up returnDataappears to do nothingfindToolsByCapabilityreturns an empty array
- References
Inter-Tool Invocation lets one installed PPTB tool launch another installed tool, pass structured data to pre-populate its state (prefill data), and optionally receive a result back when the launched tool finishes.
Tool A (Caller) Tool B (Callee)
────────────── ───────────────
invocation.launchTool( invocation.getLaunchContext()
"@my-org/entity-picker", ─────► → { entityName: "account" }
{ entityName: "account" }
) // user picks a record …
◄────────────────────────────── invocation.returnData(
result { selectedId: "a1b2c3",
= { selectedId: "a1b2c3", selectedName: "Contoso" }
selectedName: "Contoso" } )
← PPTB auto-closes callee window
Key properties of the feature:
- Promise-based:
invocation.launchTool()returns aPromisethat resolves when the callee callsreturnData(), or resolves tonullif the callee closes without returning data. - Isolated windows: the callee opens in its own BrowserView, just like a normally launched tool.
- Auto-close callee: after the callee calls
returnData(), PPTB automatically closes the callee window — the callee does not need to close itself. - Connection auto-inheritance: the callee automatically inherits the caller's active FXS connection (can be overridden via
options). - One-at-a-time: only one active callee per caller is supported in this phase. A second
launchToolcall while a callee is active rejects with"A callee invocation is already in progress". - Optional contract: the callee declares the shape of its prefill data and return value in
pptb.config.json; this is validated bypptb-validatebut is not enforced at runtime. - Tag-based capability discovery: callee tools declare capability tags; caller tools can discover matching installed tools by tag.
- Shell-level "Return to Caller" banner: PPTB injects a dismissable banner in the callee window so the user can return to the caller at any time — even before
returnDatais called. - Graceful degradation: both the prefill data and the return value are plain JSON objects (
Record<string, unknown>), so missing fields degrade gracefully.
Create a file named pptb.config.json in the root of your tool package (next to package.json). This file declares:
| Field | Required | Description |
|---|---|---|
invocation.version |
Yes (when invocation is present) |
Semantic version of your invocation contract (e.g. "1.0.0"). Bump this when the shape of prefill or returnTopic changes. |
invocation.capabilities |
No | Array of capability tag strings (e.g. ["entity-picker"]). Used by callers to discover this tool. |
invocation.prefill |
No | JSON-schema-style object describing the data a caller can pass in. |
invocation.prefill.properties |
No | Map of property names to { type?, enum?, items? } descriptors. |
invocation.returnTopic |
No | JSON-schema-style object describing the data your tool returns to its caller. |
invocation.returnTopic.properties |
No | Map of property names to { type?, enum?, items? } descriptors. |
Example pptb.config.json:
{
"invocation": {
"version": "1.0.0",
"capabilities": ["entity-picker"],
"prefill": {
"properties": {
"entityName": { "type": "string" },
"allowMultiSelect": { "type": "boolean" }
}
},
"returnTopic": {
"properties": {
"selectedId": { "type": "string" },
"selectedName": { "type": "string" }
}
}
}
}Supported type values: "string", "number", "boolean", "object", "array".
Use "enum" to restrict a string property to a fixed set of values.
Use "items" to describe the element type of an array property.
Tip: Run
pptb-validatein your tool directory to validate bothpackage.jsonandpptb.config.jsonbefore publishing.
When your tool starts, call toolboxAPI.invocation.getLaunchContext() to find out whether it was launched by another tool and to read the prefill data the caller provided.
const ctx = await toolboxAPI.invocation.getLaunchContext();
if (ctx !== null) {
// Tool was launched via inter-tool invocation
const entityName = ctx.entityName as string;
// … use prefill data to set up your UI …
} else {
// Tool was opened normally by the user
}Signature:
getLaunchContext(): Promise<Record<string, unknown> | null>- Returns the prefill data object when the tool was invoked by another tool.
- Returns
nullwhen the tool was opened directly by the user (not via invocation). - All values are
unknown; cast or validate them before use.
Once the user completes their task (e.g. selects a record, fills a form), call toolboxAPI.invocation.returnData() to send the result back to the caller tool.
await toolboxAPI.invocation.returnData({
selectedId: "a1b2c3d4-...",
selectedName: "Contoso Ltd.",
});
// PPTB automatically closes this window after delivering the result.Signature:
returnData(returnData: Record<string, unknown>): Promise<void>- Resolves the
Promisethat the caller is awaiting ininvocation.launchTool(). - PPTB automatically closes the callee window after the result has been delivered to the caller — the callee does not need to close itself.
- If the tool was not launched by another tool, this call is a no-op – it is safe to call unconditionally.
A well-behaved callee works in both modes:
| Mode | getLaunchContext() returns |
Expected behaviour |
|---|---|---|
| Standalone (normal launch) | null |
Show full UI, no pre-populated state |
| Invoked by another tool | Record<string, unknown> |
Pre-populate UI from the context, show a "confirm / return" action |
async function initTool() {
const ctx = await toolboxAPI.invocation.getLaunchContext();
if (ctx) {
// Invoked mode: pre-fill and show a compact picker UI
renderPickerUI({
entityName: ctx.entityName as string,
allowMultiSelect: (ctx.allowMultiSelect as boolean) ?? false,
onConfirm: async (selection) => {
await toolboxAPI.invocation.returnData(selection);
// PPTB will auto-close this window after returnData completes
},
});
} else {
// Standalone mode: show the full explorer UI
renderFullExplorerUI();
}
}The following is a minimal but complete callee implementation for an entity-picker tool.
pptb.config.json
{
"invocation": {
"version": "1.0.0",
"capabilities": ["entity-picker"],
"prefill": {
"properties": {
"entityName": { "type": "string" },
"allowMultiSelect": { "type": "boolean" }
}
},
"returnTopic": {
"properties": {
"selectedId": { "type": "string" },
"selectedName": { "type": "string" }
}
}
}
}Use toolboxAPI.invocation.launchTool() to open another installed tool and pass it prefill data.
const result = await toolboxAPI.invocation.launchTool(
"@my-org/entity-picker", // npm package name of the target tool
{ entityName: "account" }, // prefill data (must match callee's prefill schema)
);Signature:
launchTool(
targetToolId: string,
prefillData?: Record<string, unknown>,
options?: {
primaryConnectionId?: string | null;
secondaryConnectionId?: string | null;
noReturn?: boolean;
},
): Promise<unknown>| Parameter | Type | Description |
|---|---|---|
targetToolId |
string |
The npm package name of the tool to launch (e.g. "@my-org/entity-picker"). Must be installed. |
prefillData |
Record<string, unknown> |
Optional data to pre-populate the callee's state. Shape should match the callee's invocation.prefill schema. |
options.primaryConnectionId |
string | null |
Override the primary Dataverse connection for the callee. Omit to auto-inherit the caller's active FXS connection. |
options.secondaryConnectionId |
string | null |
Override the secondary Dataverse connection for the callee. Omit to let PPTB prompt for it when the callee is a multi-connection tool. |
options.noReturn |
boolean |
When true, signals that the caller does not expect the callee to return data. The "Return to [Caller]" banner is suppressed entirely for the callee. The invocation lifecycle is otherwise identical — the Promise still resolves with null when the callee closes. |
Return value: A Promise that resolves with the Record<string, unknown> passed to returnData() by the callee, or null if:
- the callee closes without calling
returnData, or - the user clicks the "Return to [this tool]" banner before the callee calls
returnData.
Important: Only one callee per caller is active at a time. Calling
launchToola second time while a callee is still active throws"A callee invocation is already in progress".
Important: The target tool must be installed in PPTB. If the tool is not found,
launchToolthrows an error.
Multi-connection auto-prompt: If the callee tool declares
features.multiConnection: "required"or"optional"in its manifest and nooptions.secondaryConnectionIdis provided, PPTB automatically opens the multi-connection selector before launching the callee. If the user cancels the selector,launchToolthrows"Connection selection cancelled".
const result = await toolboxAPI.invocation.launchTool("@my-org/entity-picker", { entityName: "contact" });
if (result !== null) {
const { selectedId, selectedName } = result as { selectedId: string; selectedName: string };
// Use the selection returned by the callee
populateField("regardingobjectid", selectedId, selectedName);
} else {
// User dismissed the picker without selecting – no change needed
}Always check for null before using the result. The Promise resolves to null in two scenarios:
- The user closes the callee tool window without calling
returnData. - The user clicks the "Return to [CallerTool]" banner before the callee calls
returnData.
By default, the callee automatically inherits the caller's active FXS connection. No additional configuration is needed:
// Callee receives the same primary connection as this tool automatically
const result = await toolboxAPI.invocation.launchTool("@my-org/entity-picker", { entityName: "account" });To override with a specific connection, pass options.primaryConnectionId:
const result = await toolboxAPI.invocation.launchTool("@my-org/solution-importer", { solutionName: "MySolution" }, { primaryConnectionId: specificConnectionId });Pass null to launch the callee with no connection:
const result = await toolboxAPI.invocation.launchTool("@my-org/entity-picker", {}, { primaryConnectionId: null });Discover installed tools that support a given capability by querying their declared capabilities tags:
const pickers = await toolboxAPI.invocation.findToolsByCapability("entity-picker");
// pickers: Tool[] — all installed tools with "entity-picker" in their capabilities
if (pickers.length > 0) {
const picker = pickers[0] as { id: string };
const result = await toolboxAPI.invocation.launchTool(picker.id, { entityName: "account" });
}Signature:
findToolsByCapability(tag: CapabilityTag): Promise<unknown[]>
getKnownCapabilityTags(): Promise<Array<{ tag: string; description: string }>>findToolsByCapability returns an array of matching installed Tool objects (empty array if none found).
getKnownCapabilityTags returns the full capability registry (fetched from Supabase at runtime, cached for 5 minutes; falls back to a built-in list when offline).
The following tags are registered in the official capability registry. Use them to ensure your tool is discoverable by callers without typos:
| Tag | Description |
|---|---|
fetchxml |
Accept or process FetchXML queries |
entity-picker |
Browse and select a Dataverse entity (table) |
record-selector |
Browse and select a Dataverse record |
solution-selector |
Pick a Power Platform solution |
Note: The registry is configurable without an app update — new tags are added to the Supabase
capability_tagstable and become immediately discoverable at runtime viagetKnownCapabilityTags(). The TypeScriptKnownCapabilityTagunion type andpptb-validateare updated in the next@pptb/typesrelease.
IDE auto-complete: Import
CapabilityTagfrom@pptb/types/pptbConfigto get IntelliSense for known tags:import type { CapabilityTag } from "@pptb/types/pptbConfig";
async function openEntityPicker(entityName: string) {
let result: unknown;
try {
result = await toolboxAPI.invocation.launchTool(
"@my-org/entity-picker",
{ entityName, allowMultiSelect: false },
// primaryConnectionId omitted → callee inherits this tool's FXS connection
);
} catch (err) {
// Tool not installed, already has an active callee, or launch failed
await toolboxAPI.utils.showNotification({
title: "Cannot open picker",
body: err instanceof Error ? err.message : String(err),
type: "error",
});
return;
}
if (result === null) {
// User dismissed the picker (closed window or clicked "Return to Caller" banner)
return;
}
const { selectedId, selectedName } = result as { selectedId: string; selectedName: string };
setSelectedRecord(selectedId, selectedName);
}For agent/MCP-specific invocation behavior, see MCP Implementation.
MCP agentic invocation is a separate contract from PPTB-to-PPTB tool invocation.
- Inter-tool invocation uses
toolboxAPI.invocation.launchTool(),getLaunchContext(), andreturnData()between PPTB tools. - MCP invocation uses the built-in MCP server to expose selected tools to external agents.
- The runtime pipeline is shared, but the caller contract and discovery metadata are different.
If you are publishing a tool for external automation, read MCP Implementation and the migration guide at MCP Tool Author Migration.
Tool authors do not need to change the core BrowserView runtime for Phase 3–6 work. The required updates are:
- Add an
agentssection topptb.config.jsonwithinvokable: true. - Declare the supported MCP invocation modes in
agents.modes. - Set
agents.defaultModewhen a tool should prefer one mode over another. - Set
agents.timeoutMSwhen a tool needs a longer or shorter default timeout for two-way calls. - Keep
invocation.prefillandinvocation.returnTopicaccurate so MCP input and output validation stays stable.
MCP Inspector is the recommended manual harness for validating the MCP surface in this repo. Use it to confirm:
- the tool appears in
list-tools - the supported mode hints are visible
- one-way and two-way calls behave as expected
- invalid modes and bad payloads return deterministic errors
The repo does not require a dedicated automated MCP test runner for this phase.
index.ts
async function main() {
const ctx = await toolboxAPI.invocation.getLaunchContext();
if (ctx) {
// Invoked by another tool – show a targeted picker
const entityName = (ctx.entityName as string) ?? "account";
const records = await loadRecords(entityName);
renderPicker(records, async (selected) => {
// Send the selection back; PPTB auto-closes this window
await toolboxAPI.invocation.returnData({
selectedId: selected.id,
selectedName: selected.name,
});
});
} else {
// Standalone – show the full entity browser
renderFullBrowser();
}
}
main();This section illustrates a concrete real-world scenario where FetchXML Studio (FXS) exposes a "Send To ▾" flyout button that lets users push the current FetchXML query directly into another installed tool — such as DRB (Dataverse Rest Builder) or DMS (Data Migration Studio) — without expecting a return value.
| Step | What happens |
|---|---|
| 1 | User composes a FetchXML query in FXS. |
| 2 | User clicks the "Send To ▾" flyout button in FXS. |
| 3 | PPTB queries all installed tools that declare the "fetchxml" capability — both DRB and DMS qualify. The flyout lists them as options. |
| 4 | User selects DMS. |
| 5 | PPTB opens DMS, inheriting FXS's active Dataverse connection as the primary connection. |
| 6 | If DMS requires a secondary connection (e.g. it is a multi-connection tool for cross-environment migration), PPTB automatically shows the multi-connection selector before launching DMS — the user picks the second connection. |
| 7 | DMS opens pre-populated with the FetchXML from step 1. |
| 8 | Because noReturn: true was set, no banner is shown in the DMS window — FXS does not expect data back. |
| 9 | The user continues in DMS independently. Closing DMS resolves the Promise on the FXS side with null. |
Both DRB and DMS include the following in their pptb.config.json:
DRB pptb.config.json
{
"invocation": {
"version": "1.0.0",
"capabilities": ["fetchxml"],
"prefill": {
"properties": {
"fetchXml": { "type": "string" }
}
}
}
}DMS pptb.config.json — DMS is a multi-connection tool so it has no returnTopic; it uses the FetchXML purely as input.
{
"invocation": {
"version": "1.0.0",
"capabilities": ["fetchxml"],
"prefill": {
"properties": {
"fetchXml": { "type": "string" }
}
}
}
}DMS's multi-connection requirement is declared in the standard tool manifest (
pptb.package.json):{ "features": { "multiConnection": "required" } }PPTB detects this at launch time and automatically prompts the user to select a secondary connection when none is available from the caller.
FXS calls findToolsByCapability on startup to discover all installed fetchxml-capable tools, then renders a flyout button for each one:
// fxs/index.ts — called during tool initialisation
async function setupSendToFlyout() {
// Discover all installed tools that accept fetchxml
const fetchXmlTools = await toolboxAPI.invocation.findToolsByCapability("fetchxml");
// Render one "Send to [ToolName]" item in the flyout for each discovered tool
renderSendToFlyout(fetchXmlTools as Array<{ id: string; name: string }>);
}When the user picks "Send to DMS" from the flyout, FXS calls launchTool with noReturn: true to signal that it does not expect DMS to return data:
// fxs/index.ts
async function sendCurrentQueryToTool(targetToolId: string) {
const currentFetchXml = getEditorContent(); // e.g. "<fetch>…</fetch>"
try {
// noReturn: true → DMS will NOT call returnData back to FXS.
// The Promise resolves with null when DMS closes or the user clicks "Return to FXS".
await toolboxAPI.invocation.launchTool(
targetToolId,
{ fetchXml: currentFetchXml },
{
// primaryConnectionId omitted → FXS's active connection is inherited by DMS
// secondaryConnectionId omitted → PPTB shows multi-connection selector if DMS requires it
noReturn: true,
},
);
// Execution reaches here once DMS is closed (or user clicks "Return to FXS").
// No result data to process.
} catch (err) {
// Tool not installed, connection selection cancelled, or already has an active callee.
toolboxAPI.utils.showNotification({
title: "Send To failed",
body: err instanceof Error ? err.message : String(err),
type: "error",
});
}
}What PPTB does behind the scenes:
- Looks up the DMS tool manifest.
- Detects that DMS has
features.multiConnection: "required"and no secondary connection was provided → opens the multi-connection selector modal in the PPTB shell. The user selects the target environment connection. - Launches DMS with FXS's primary connection and the user-selected secondary connection.
- Pre-populates DMS with
{ fetchXml: "…" }. - Because
noReturn: truewas set, no "Return to FXS" banner is shown in the DMS window.
DMS starts normally and reads the FetchXML from its launch context:
// dms/index.ts
async function main() {
const ctx = await toolboxAPI.invocation.getLaunchContext();
if (ctx) {
// Launched by FXS (or another tool) with a fetchXml payload
const fetchXml = ctx.fetchXml as string | undefined;
if (fetchXml) {
loadQueryIntoEditor(fetchXml);
}
// DMS does NOT call returnData — FXS launched it with noReturn: true.
// The user works in DMS independently; closing DMS resolves FXS's Promise with null.
} else {
// Standalone launch — show empty editor
renderEmptyEditor();
}
}
main();Note: DMS does not need to detect
noReturnexplicitly. The behaviour is the same as any other invocation: ifreturnDatais never called and the tool is closed, the caller's Promise resolves withnull. ThenoReturnflag only suppresses the banner — it does not change the invocation lifecycle.
Because noReturn: true was set, no banner is shown in the DMS window. The user works in DMS and closes it normally (or closes the tab). PPTB resolves FXS's Promise with null.
| Action | Result |
|---|---|
| Close DMS tab normally | DMS is closed; FXS Promise resolves with null. |
FXS (Caller) PPTB Shell DMS (Callee)
──────────── ────────── ────────────
findToolsByCapability("fetchxml")
→ [DRB, DMS]
User clicks "Send to DMS"
launchTool("dms", { fetchXml },
{ noReturn: true })
│
▼
DMS needs secondary connection
→ show multi-connection selector
← user picks target env
│
▼
Launch DMS (primary = FXS conn,
secondary = user pick)
No banner shown (noReturn: true)
│
▼
getLaunchContext()
→ { fetchXml: "…" }
loadQueryIntoEditor(fetchXml)
// user works in DMS …
User closes DMS tab normally
◄──────────────────────────────────────────────
Promise resolves (null) DMS closed
// No result to process
Understanding the full lifecycle helps when reasoning about edge cases:
Caller tool calls invocation.launchTool(...)
│
▼
One-at-a-time check: rejects if caller already has an active callee
│
▼
PPTB main process creates a new BrowserView for the callee
FXS connection auto-inherited from caller (unless overridden)
│
▼
Callee loads, receives toolContext with:
• toolId, instanceId
• callerInstanceId ← present only for invocations
• prefillData ← the object passed by the caller
• connectionId ← auto-inherited from caller's FXS connection
│
▼
PPTB injects "Return to [CallerToolName]" banner in the callee window
(skipped if launchTool was called with noReturn: true)
│
▼
Callee calls getLaunchContext() → returns prefillData
│
▼ (user interacts with callee UI)
│
┌───┴──────────────────────────────────────┐──────────────────────────┐
│ │ │
▼ ▼ ▼
Callee calls returnData(...) User closes callee window User clicks "Return to Caller"
│ │ banner button
▼ ▼ │
PPTB sends result to caller PPTB sends null to caller ▼
PPTB auto-closes callee window │ PPTB sends null to caller
│ │ PPTB auto-closes callee window
└──────────────────┬──────────────────────┘──────────────────────────┘
│
▼
Caller's Promise resolves (returnData value OR null)
Key points:
- The callee opens in its own window (BrowserView) and is visible as a separate tab in the PPTB tool panel.
- The caller's
launchTool()Promise never rejects under normal operation – it always resolves (possibly withnull). Rejections only occur if the target tool is not installed, if the caller already has an active callee, or if the launch itself fails. - Auto-close: after
returnDatais called, PPTB automatically closes the callee window. The callee does not need to close itself. - Banner early-return: if the user clicks the "Return to [CallerToolName]" banner before the callee calls
returnData, the caller's Promise resolves withnulland the callee window is closed. This is treated the same as closing the window. - Banner dismiss (✕): clicking the dismiss button hides the banner for the session but does not end the invocation. The callee stays open and can still call
returnDatanormally. - One-at-a-time: only one active callee per caller is allowed. A second
launchToolcall from the same caller while a callee is active rejects with"A callee invocation is already in progress". - A callee that never calls
returnDatawill keep the caller's Promise pending until the callee window is closed by the user.
Run pptb-validate from your tool directory to validate both package.json and pptb.config.json:
npx pptb-validate
# or, if you have the @pptb/types package installed locally:
./node_modules/.bin/pptb-validateThe validator checks:
invocation.versionis present and a valid semver string.invocation.capabilities(when present) is an array of non-empty strings where each value is a recognised capability tag (warning issued for unrecognised tags — see well-known tags above).invocation.prefill.propertiesvalues are valid JSON-schema property descriptors.invocation.returnTopic.propertiesvalues are valid JSON-schema property descriptors.
The validator prints the list of known capability tags in its output summary so you can quickly check which tags are available.
The @pptb/types package ships type definitions for the entire invocation API:
// All methods live on toolboxAPI.invocation
toolboxAPI.invocation.getLaunchContext() // Promise<Record<string, unknown> | null>
toolboxAPI.invocation.returnData(data) // Promise<void> (auto-closes callee after call)
toolboxAPI.invocation.launchTool(...) // Promise<unknown>
toolboxAPI.invocation.findToolsByCapability(tag) // Promise<unknown[]> — tag is CapabilityTag
toolboxAPI.invocation.getKnownCapabilityTags() // Promise<Array<{ tag: string; description: string }>>For auto-complete on capability tags, import CapabilityTag / KnownCapabilityTag from the bundled declaration file:
import type { PPTBConfig, InvocationConfig, CapabilityTag, KnownCapabilityTag } from "@pptb/types/pptbConfig";
// IDE will suggest known tags when typing:
const tag: CapabilityTag = "fetchxml";
const tools = await toolboxAPI.invocation.findToolsByCapability(tag);The target tool is not installed. Ask the user to install it from the PPTB Marketplace, or check that the targetToolId matches the exact npm package name (name field in the tool's package.json).
Your tool already has an active callee open. Wait for the current invocation to resolve (or reject) before calling launchTool again. Only one callee per caller is supported in this phase.
The tool was opened by the user directly rather than via launchTool. Ensure the caller is using toolboxAPI.invocation.launchTool() and not the standard tool launch mechanism.
One of the following occurred:
- The callee window was closed by the user before
returnData()was called. - The user clicked the "Return to [CallerTool]" banner button before the callee called
returnData().
Both scenarios are by design — always handle the null case in the caller.
Capabilities and the invocation contract are read when a tool is installed. If you change pptb.config.json in a locally-loaded development tool, reload or reinstall the tool in PPTB.
Confirm that getLaunchContext() returned a non-null value first. If getLaunchContext() returns null, returnData is a no-op because the tool was not launched by another tool.
No installed tools declare the queried capability tag in their pptb.config.json. Check that the target tool's pptb.config.json has the correct tag in invocation.capabilities and was reinstalled after the change.
packages/toolboxAPI.d.ts– Full TypeScript type definitions for the invocation API (InvocationAPI)packages/pptbConfig.d.ts– Type definitions forpptb.config.json(PPTBConfig,InvocationConfig)packages/README.md– Developer guide for the@pptb/typespackage, including the API referencesrc/main/managers/toolWindowManager.ts– Host-side implementation (launchToolWithContext,resolveInvocation,activeCallees)src/main/toolPreloadBridge.ts– Preload-side implementation of theinvocationnamespace