@github/copilot-sdk
Advanced tools
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var toolSet_exports = {}; | ||
| __export(toolSet_exports, { | ||
| BuiltInTools: () => BuiltInTools, | ||
| ToolSet: () => ToolSet | ||
| }); | ||
| module.exports = __toCommonJS(toolSet_exports); | ||
| const VALID_TOOL_NAME = /^[a-zA-Z0-9_-]+$/; | ||
| function validateName(kind, name) { | ||
| if (name === "*") { | ||
| return; | ||
| } | ||
| if (!VALID_TOOL_NAME.test(name)) { | ||
| throw new Error( | ||
| `Invalid ${kind} tool name '${name}': tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'.` | ||
| ); | ||
| } | ||
| } | ||
| class ToolSet { | ||
| items = []; | ||
| addBuiltIn(nameOrNames) { | ||
| const names = typeof nameOrNames === "string" ? [nameOrNames] : nameOrNames; | ||
| for (const name of names) { | ||
| validateName("builtin", name); | ||
| this.items.push(`builtin:${name}`); | ||
| } | ||
| return this; | ||
| } | ||
| /** | ||
| * Adds a custom tool pattern. Matches tools registered via the SDK's | ||
| * `tools` option or via custom agents. | ||
| * | ||
| * @param name A specific custom tool name or `"*"` to match all custom tools. | ||
| */ | ||
| addCustom(name) { | ||
| validateName("custom", name); | ||
| this.items.push(`custom:${name}`); | ||
| return this; | ||
| } | ||
| /** | ||
| * Adds an MCP tool pattern. Matches tools advertised by any configured | ||
| * MCP server. | ||
| * | ||
| * @param toolName The runtime's canonical wire name for the MCP tool | ||
| * (e.g. `"github-list_issues"`), or `"*"` to match all MCP tools from | ||
| * any server. | ||
| */ | ||
| addMcp(toolName) { | ||
| validateName("mcp", toolName); | ||
| this.items.push(`mcp:${toolName}`); | ||
| return this; | ||
| } | ||
| /** | ||
| * Returns a defensive copy of the accumulated filter strings, suitable for | ||
| * passing as {@link SessionConfigBase.availableTools}. | ||
| */ | ||
| toArray() { | ||
| return [...this.items]; | ||
| } | ||
| } | ||
| const BuiltInTools = { | ||
| /** | ||
| * Built-in tools that operate only within the bounds of a single session — | ||
| * no host filesystem access outside the session, no cross-session state, | ||
| * no host environment access, no network. Safe to enable in `Mode = "empty"` | ||
| * scenarios (e.g. multi-tenant servers) without leaking host capabilities. | ||
| * | ||
| * **Contract:** tools in this set MUST NOT be extended (even behind options | ||
| * or args) to read or write state outside the session boundary. Adding | ||
| * cross-session or host-state behavior to one of these tools is a | ||
| * breaking change that requires removing it from this set. | ||
| */ | ||
| Isolated: [ | ||
| "ask_user", | ||
| "task_complete", | ||
| "exit_plan_mode", | ||
| "task", | ||
| "read_agent", | ||
| "write_agent", | ||
| "list_agents", | ||
| "send_inbox", | ||
| "context_board", | ||
| "skill" | ||
| ] | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| BuiltInTools, | ||
| ToolSet | ||
| }); |
| /** | ||
| * Builder that produces a list of source-qualified tool filter strings for | ||
| * {@link SessionConfigBase.availableTools}. | ||
| * | ||
| * Tools are classified by the runtime at registration time (not from name | ||
| * parsing), so `addBuiltIn("foo")` matches only tools the runtime registered | ||
| * as built-in, even if an MCP server or custom-agent extension happens to | ||
| * register a tool with the same wire name. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const tools = new ToolSet() | ||
| * .addBuiltIn(BuiltInTools.Isolated) | ||
| * .addMcp("*") | ||
| * .addCustom("*"); | ||
| * | ||
| * const session = await client.createSession({ | ||
| * availableTools: tools, | ||
| * // ... | ||
| * }); | ||
| * ``` | ||
| */ | ||
| export declare class ToolSet { | ||
| private readonly items; | ||
| /** | ||
| * Adds one or more built-in tool patterns. | ||
| * | ||
| * @param name A specific built-in tool name (e.g. `"bash"`) or `"*"` to match all | ||
| * built-in tools. | ||
| */ | ||
| addBuiltIn(name: string): ToolSet; | ||
| /** | ||
| * Adds a list of built-in tool patterns (e.g. {@link BuiltInTools.Isolated}). | ||
| */ | ||
| addBuiltIn(names: readonly string[]): ToolSet; | ||
| /** | ||
| * Adds a custom tool pattern. Matches tools registered via the SDK's | ||
| * `tools` option or via custom agents. | ||
| * | ||
| * @param name A specific custom tool name or `"*"` to match all custom tools. | ||
| */ | ||
| addCustom(name: string): ToolSet; | ||
| /** | ||
| * Adds an MCP tool pattern. Matches tools advertised by any configured | ||
| * MCP server. | ||
| * | ||
| * @param toolName The runtime's canonical wire name for the MCP tool | ||
| * (e.g. `"github-list_issues"`), or `"*"` to match all MCP tools from | ||
| * any server. | ||
| */ | ||
| addMcp(toolName: string): ToolSet; | ||
| /** | ||
| * Returns a defensive copy of the accumulated filter strings, suitable for | ||
| * passing as {@link SessionConfigBase.availableTools}. | ||
| */ | ||
| toArray(): string[]; | ||
| } | ||
| /** | ||
| * Curated sets of built-in tool names for common scenarios. Each constant is | ||
| * meant to be passed to {@link ToolSet.addBuiltIn}. | ||
| */ | ||
| export declare const BuiltInTools: { | ||
| /** | ||
| * Built-in tools that operate only within the bounds of a single session — | ||
| * no host filesystem access outside the session, no cross-session state, | ||
| * no host environment access, no network. Safe to enable in `Mode = "empty"` | ||
| * scenarios (e.g. multi-tenant servers) without leaking host capabilities. | ||
| * | ||
| * **Contract:** tools in this set MUST NOT be extended (even behind options | ||
| * or args) to read or write state outside the session boundary. Adding | ||
| * cross-session or host-state behavior to one of these tools is a | ||
| * breaking change that requires removing it from this set. | ||
| */ | ||
| readonly Isolated: readonly string[]; | ||
| }; |
| const VALID_TOOL_NAME = /^[a-zA-Z0-9_-]+$/; | ||
| function validateName(kind, name) { | ||
| if (name === "*") { | ||
| return; | ||
| } | ||
| if (!VALID_TOOL_NAME.test(name)) { | ||
| throw new Error( | ||
| `Invalid ${kind} tool name '${name}': tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'.` | ||
| ); | ||
| } | ||
| } | ||
| class ToolSet { | ||
| items = []; | ||
| addBuiltIn(nameOrNames) { | ||
| const names = typeof nameOrNames === "string" ? [nameOrNames] : nameOrNames; | ||
| for (const name of names) { | ||
| validateName("builtin", name); | ||
| this.items.push(`builtin:${name}`); | ||
| } | ||
| return this; | ||
| } | ||
| /** | ||
| * Adds a custom tool pattern. Matches tools registered via the SDK's | ||
| * `tools` option or via custom agents. | ||
| * | ||
| * @param name A specific custom tool name or `"*"` to match all custom tools. | ||
| */ | ||
| addCustom(name) { | ||
| validateName("custom", name); | ||
| this.items.push(`custom:${name}`); | ||
| return this; | ||
| } | ||
| /** | ||
| * Adds an MCP tool pattern. Matches tools advertised by any configured | ||
| * MCP server. | ||
| * | ||
| * @param toolName The runtime's canonical wire name for the MCP tool | ||
| * (e.g. `"github-list_issues"`), or `"*"` to match all MCP tools from | ||
| * any server. | ||
| */ | ||
| addMcp(toolName) { | ||
| validateName("mcp", toolName); | ||
| this.items.push(`mcp:${toolName}`); | ||
| return this; | ||
| } | ||
| /** | ||
| * Returns a defensive copy of the accumulated filter strings, suitable for | ||
| * passing as {@link SessionConfigBase.availableTools}. | ||
| */ | ||
| toArray() { | ||
| return [...this.items]; | ||
| } | ||
| } | ||
| const BuiltInTools = { | ||
| /** | ||
| * Built-in tools that operate only within the bounds of a single session — | ||
| * no host filesystem access outside the session, no cross-session state, | ||
| * no host environment access, no network. Safe to enable in `Mode = "empty"` | ||
| * scenarios (e.g. multi-tenant servers) without leaking host capabilities. | ||
| * | ||
| * **Contract:** tools in this set MUST NOT be extended (even behind options | ||
| * or args) to read or write state outside the session boundary. Adding | ||
| * cross-session or host-state behavior to one of these tools is a | ||
| * breaking change that requires removing it from this set. | ||
| */ | ||
| Isolated: [ | ||
| "ask_user", | ||
| "task_complete", | ||
| "exit_plan_mode", | ||
| "task", | ||
| "read_agent", | ||
| "write_agent", | ||
| "list_agents", | ||
| "send_inbox", | ||
| "context_board", | ||
| "skill" | ||
| ] | ||
| }; | ||
| export { | ||
| BuiltInTools, | ||
| ToolSet | ||
| }; |
+158
-5
@@ -37,2 +37,3 @@ "use strict"; | ||
| var import_telemetry = require("./telemetry.js"); | ||
| var import_toolSet = require("./toolSet.js"); | ||
| var import_types = require("./types.js"); | ||
@@ -71,2 +72,20 @@ const import_meta = {}; | ||
| } | ||
| function toolFilterListToArray(value) { | ||
| if (value === void 0) { | ||
| return void 0; | ||
| } | ||
| return value instanceof import_toolSet.ToolSet ? value.toArray() : value; | ||
| } | ||
| function validateToolFilterList(field, list) { | ||
| if (!list) { | ||
| return; | ||
| } | ||
| for (const entry of list) { | ||
| if (entry === "*") { | ||
| throw new Error( | ||
| `Invalid ${field} entry '*': there is no bare wildcard. Use one or more of \`new ToolSet().addBuiltIn('*')\`, \`.addMcp('*')\`, or \`.addCustom('*')\` to target a specific source.` | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| function extractTransformCallbacks(systemMessage) { | ||
@@ -254,4 +273,15 @@ if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) { | ||
| sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0, | ||
| enableRemoteSessions: options.enableRemoteSessions ?? false | ||
| enableRemoteSessions: options.enableRemoteSessions ?? false, | ||
| mode: options.mode ?? "copilot-cli" | ||
| }; | ||
| if (this.options.mode === "empty") { | ||
| const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK | ||
| // can't enforce it from here. | ||
| conn.kind === "uri" || conn.kind === "parent-process"; | ||
| if (!hasPersistence) { | ||
| throw new Error( | ||
| "CopilotClient was created with mode: 'empty' but neither 'baseDirectory' nor 'sessionFs' was set. Empty mode requires an explicit per-session persistence location; pick one." | ||
| ); | ||
| } | ||
| } | ||
| } | ||
@@ -566,2 +596,112 @@ connectionExtraArgs = []; | ||
| */ | ||
| /** | ||
| * Normalizes session-level tool filter options. Converts {@link ToolSet} | ||
| * instances to plain string arrays, rejects misuse (bare `"*"`) and the | ||
| * missing-availableTools case in `mode = "empty"`. | ||
| * | ||
| * The SDK always sends `toolFilterPrecedence: "excluded"` so callers can | ||
| * compose include + exclude lists naturally (e.g. "everything matching X | ||
| * except Y") regardless of mode. Allowlist-precedence is intentionally not | ||
| * exposed — it's available on the runtime side as a CLI-only concession to | ||
| * legacy behavior, but SDK consumers always get the composable semantics. | ||
| * | ||
| * @internal | ||
| */ | ||
| resolveToolFilterOptions(config) { | ||
| const availableTools = toolFilterListToArray(config.availableTools); | ||
| const excludedTools = toolFilterListToArray(config.excludedTools); | ||
| validateToolFilterList("availableTools", availableTools); | ||
| validateToolFilterList("excludedTools", excludedTools); | ||
| if (this.options.mode === "empty") { | ||
| if (availableTools === void 0) { | ||
| throw new Error( | ||
| "CopilotClient is in mode: 'empty' but the session config did not specify 'availableTools'. Empty mode requires every session to explicitly opt into the tools it wants \u2014 e.g. `new ToolSet().addBuiltIn(BuiltInTools.Isolated)`." | ||
| ); | ||
| } | ||
| } | ||
| return { availableTools, excludedTools, toolFilterPrecedence: "excluded" }; | ||
| } | ||
| /** Mode-specific defaults spread under the caller's config (app values win). */ | ||
| configDefaultsForMode() { | ||
| if (this.options.mode === "empty") { | ||
| return { enableSessionTelemetry: false }; | ||
| } | ||
| return {}; | ||
| } | ||
| /** | ||
| * Returns the systemMessage config to use, adjusted for the current mode. | ||
| * In empty mode we ensure the environment_context section is removed | ||
| * unless the app has already taken control of it. `append` (and | ||
| * unspecified) mode is promoted to `customize` so we can also strip | ||
| * environment_context; the caller's `content` is preserved verbatim | ||
| * because the runtime appends it as additional instructions in both | ||
| * customize and append modes. | ||
| */ | ||
| getSystemMessageConfigForMode(supplied) { | ||
| if (this.options.mode !== "empty") return supplied; | ||
| if (!supplied) { | ||
| return { | ||
| mode: "customize", | ||
| sections: { environment_context: { action: "remove" } } | ||
| }; | ||
| } | ||
| switch (supplied.mode) { | ||
| case "replace": | ||
| return supplied; | ||
| case "customize": | ||
| if (supplied.sections?.environment_context) return supplied; | ||
| return { | ||
| ...supplied, | ||
| sections: { | ||
| ...supplied.sections, | ||
| environment_context: { action: "remove" } | ||
| } | ||
| }; | ||
| case "append": | ||
| case void 0: | ||
| return { | ||
| mode: "customize", | ||
| content: supplied.content, | ||
| sections: { environment_context: { action: "remove" } } | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Mode-specific options applied via session.options.update after create/resume. | ||
| * | ||
| * In empty mode, defaults the four overridable feature flags to safe values | ||
| * (caller values from `config` win). `installedPlugins=[]` is unconditional | ||
| * in empty mode — apps that need custom plugins should switch modes. | ||
| */ | ||
| async updateSessionOptionsForMode(session, config) { | ||
| const patch = {}; | ||
| if (this.options.mode === "empty") { | ||
| patch.skipCustomInstructions = config.skipCustomInstructions ?? true; | ||
| patch.customAgentsLocalOnly = config.customAgentsLocalOnly ?? true; | ||
| patch.coauthorEnabled = config.coauthorEnabled ?? false; | ||
| patch.manageScheduleEnabled = config.manageScheduleEnabled ?? false; | ||
| patch.installedPlugins = []; | ||
| } else { | ||
| if (config.skipCustomInstructions !== void 0) | ||
| patch.skipCustomInstructions = config.skipCustomInstructions; | ||
| if (config.customAgentsLocalOnly !== void 0) | ||
| patch.customAgentsLocalOnly = config.customAgentsLocalOnly; | ||
| if (config.coauthorEnabled !== void 0) | ||
| patch.coauthorEnabled = config.coauthorEnabled; | ||
| if (config.manageScheduleEnabled !== void 0) | ||
| patch.manageScheduleEnabled = config.manageScheduleEnabled; | ||
| } | ||
| if (Object.keys(patch).length === 0) { | ||
| return; | ||
| } | ||
| try { | ||
| await session.rpc.options.update(patch); | ||
| } catch (e) { | ||
| try { | ||
| await session.disconnect(); | ||
| } catch { | ||
| } | ||
| throw e; | ||
| } | ||
| } | ||
| async createSession(config) { | ||
@@ -571,2 +711,4 @@ if (!this.connection) { | ||
| } | ||
| config = { ...this.configDefaultsForMode(), ...config }; | ||
| config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage); | ||
| const sessionId = config.sessionId ?? (0, import_node_crypto.randomUUID)(); | ||
@@ -609,2 +751,3 @@ const session = new import_session.CopilotSession( | ||
| this.setupSessionFs(session, config); | ||
| const toolFilterOptions = this.resolveToolFilterOptions(config); | ||
| try { | ||
@@ -633,4 +776,5 @@ const response = await this.connection.sendRequest("session.create", { | ||
| systemMessage: wireSystemMessage, | ||
| availableTools: config.availableTools, | ||
| excludedTools: config.excludedTools, | ||
| availableTools: toolFilterOptions.availableTools, | ||
| excludedTools: toolFilterOptions.excludedTools, | ||
| toolFilterPrecedence: toolFilterOptions.toolFilterPrecedence, | ||
| provider: config.provider, | ||
@@ -666,2 +810,3 @@ enableSessionTelemetry: config.enableSessionTelemetry, | ||
| session.setCapabilities(capabilities); | ||
| await this.updateSessionOptionsForMode(session, config); | ||
| } catch (e) { | ||
@@ -726,2 +871,4 @@ this.sessions.delete(sessionId); | ||
| } | ||
| config = { ...this.configDefaultsForMode(), ...config }; | ||
| config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage); | ||
| const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks( | ||
@@ -738,2 +885,3 @@ config.systemMessage | ||
| this.setupSessionFs(session, config); | ||
| const toolFilterOptions = this.resolveToolFilterOptions(config); | ||
| try { | ||
@@ -747,4 +895,5 @@ const response = await this.connection.sendRequest("session.resume", { | ||
| systemMessage: wireSystemMessage, | ||
| availableTools: config.availableTools, | ||
| excludedTools: config.excludedTools, | ||
| availableTools: toolFilterOptions.availableTools, | ||
| excludedTools: toolFilterOptions.excludedTools, | ||
| toolFilterPrecedence: toolFilterOptions.toolFilterPrecedence, | ||
| enableSessionTelemetry: config.enableSessionTelemetry, | ||
@@ -798,2 +947,3 @@ tools: config.tools?.map((tool) => ({ | ||
| session.setOpenCanvases(openCanvases ?? []); | ||
| await this.updateSessionOptionsForMode(session, config); | ||
| } catch (e) { | ||
@@ -1177,2 +1327,5 @@ this.sessions.delete(sessionId); | ||
| } | ||
| if (this.options.mode === "empty") { | ||
| envWithoutNodeDebug.COPILOT_DISABLE_KEYTAR = "1"; | ||
| } | ||
| if (!this.resolvedCliPath) { | ||
@@ -1179,0 +1332,0 @@ throw new Error( |
@@ -21,2 +21,3 @@ "use strict"; | ||
| __export(index_exports, { | ||
| BuiltInTools: () => import_toolSet.BuiltInTools, | ||
| Canvas: () => import_canvas.Canvas, | ||
@@ -28,2 +29,3 @@ CanvasError: () => import_canvas.CanvasError, | ||
| SYSTEM_MESSAGE_SECTIONS: () => import_types2.SYSTEM_MESSAGE_SECTIONS, | ||
| ToolSet: () => import_toolSet.ToolSet, | ||
| approveAll: () => import_types2.approveAll, | ||
@@ -38,2 +40,3 @@ convertMcpCallToolResult: () => import_types2.convertMcpCallToolResult, | ||
| var import_types = require("./types.js"); | ||
| var import_toolSet = require("./toolSet.js"); | ||
| var import_session = require("./session.js"); | ||
@@ -44,2 +47,3 @@ var import_canvas = require("./canvas.js"); | ||
| 0 && (module.exports = { | ||
| BuiltInTools, | ||
| Canvas, | ||
@@ -51,2 +55,3 @@ CanvasError, | ||
| SYSTEM_MESSAGE_SECTIONS, | ||
| ToolSet, | ||
| approveAll, | ||
@@ -53,0 +58,0 @@ convertMcpCallToolResult, |
@@ -122,2 +122,3 @@ "use strict"; | ||
| mode: options.mode, | ||
| agentMode: options.agentMode, | ||
| requestHeaders: options.requestHeaders | ||
@@ -743,2 +744,3 @@ }); | ||
| postToolUse: this.hooks.onPostToolUse, | ||
| postToolUseFailure: this.hooks.onPostToolUseFailure, | ||
| userPromptSubmitted: this.hooks.onUserPromptSubmitted, | ||
@@ -745,0 +747,0 @@ sessionStart: this.hooks.onSessionStart, |
+17
-25
@@ -193,30 +193,22 @@ import { createServerRpc } from "./generated/rpc.js"; | ||
| forceStop(): Promise<void>; | ||
| /** Mode-specific defaults spread under the caller's config (app values win). */ | ||
| private configDefaultsForMode; | ||
| /** | ||
| * Creates a new conversation session with the Copilot CLI. | ||
| * Returns the systemMessage config to use, adjusted for the current mode. | ||
| * In empty mode we ensure the environment_context section is removed | ||
| * unless the app has already taken control of it. `append` (and | ||
| * unspecified) mode is promoted to `customize` so we can also strip | ||
| * environment_context; the caller's `content` is preserved verbatim | ||
| * because the runtime appends it as additional instructions in both | ||
| * customize and append modes. | ||
| */ | ||
| private getSystemMessageConfigForMode; | ||
| /** | ||
| * Mode-specific options applied via session.options.update after create/resume. | ||
| * | ||
| * Sessions maintain conversation state, handle events, and manage tool execution. | ||
| * If the client is not connected, this method automatically starts the connection. | ||
| * | ||
| * @param config - Optional configuration for the session | ||
| * @returns A promise that resolves with the created session | ||
| * @throws Error if the client fails to start | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Basic session | ||
| * const session = await client.createSession({ onPermissionRequest: approveAll }); | ||
| * | ||
| * // Session with model and tools | ||
| * const session = await client.createSession({ | ||
| * onPermissionRequest: approveAll, | ||
| * model: "gpt-4", | ||
| * tools: [{ | ||
| * name: "get_weather", | ||
| * description: "Get weather for a location", | ||
| * parameters: { type: "object", properties: { location: { type: "string" } } }, | ||
| * handler: async (args) => ({ temperature: 72 }) | ||
| * }] | ||
| * }); | ||
| * ``` | ||
| * In empty mode, defaults the four overridable feature flags to safe values | ||
| * (caller values from `config` win). `installedPlugins=[]` is unconditional | ||
| * in empty mode — apps that need custom plugins should switch modes. | ||
| */ | ||
| private updateSessionOptionsForMode; | ||
| createSession(config: SessionConfig): Promise<CopilotSession>; | ||
@@ -223,0 +215,0 @@ /** |
+158
-5
@@ -24,2 +24,3 @@ import { spawn } from "node:child_process"; | ||
| import { getTraceContext } from "./telemetry.js"; | ||
| import { ToolSet } from "./toolSet.js"; | ||
| import { defaultJoinSessionPermissionHandler } from "./types.js"; | ||
@@ -57,2 +58,20 @@ const MIN_PROTOCOL_VERSION = 3; | ||
| } | ||
| function toolFilterListToArray(value) { | ||
| if (value === void 0) { | ||
| return void 0; | ||
| } | ||
| return value instanceof ToolSet ? value.toArray() : value; | ||
| } | ||
| function validateToolFilterList(field, list) { | ||
| if (!list) { | ||
| return; | ||
| } | ||
| for (const entry of list) { | ||
| if (entry === "*") { | ||
| throw new Error( | ||
| `Invalid ${field} entry '*': there is no bare wildcard. Use one or more of \`new ToolSet().addBuiltIn('*')\`, \`.addMcp('*')\`, or \`.addCustom('*')\` to target a specific source.` | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| function extractTransformCallbacks(systemMessage) { | ||
@@ -240,4 +259,15 @@ if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) { | ||
| sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0, | ||
| enableRemoteSessions: options.enableRemoteSessions ?? false | ||
| enableRemoteSessions: options.enableRemoteSessions ?? false, | ||
| mode: options.mode ?? "copilot-cli" | ||
| }; | ||
| if (this.options.mode === "empty") { | ||
| const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK | ||
| // can't enforce it from here. | ||
| conn.kind === "uri" || conn.kind === "parent-process"; | ||
| if (!hasPersistence) { | ||
| throw new Error( | ||
| "CopilotClient was created with mode: 'empty' but neither 'baseDirectory' nor 'sessionFs' was set. Empty mode requires an explicit per-session persistence location; pick one." | ||
| ); | ||
| } | ||
| } | ||
| } | ||
@@ -552,2 +582,112 @@ connectionExtraArgs = []; | ||
| */ | ||
| /** | ||
| * Normalizes session-level tool filter options. Converts {@link ToolSet} | ||
| * instances to plain string arrays, rejects misuse (bare `"*"`) and the | ||
| * missing-availableTools case in `mode = "empty"`. | ||
| * | ||
| * The SDK always sends `toolFilterPrecedence: "excluded"` so callers can | ||
| * compose include + exclude lists naturally (e.g. "everything matching X | ||
| * except Y") regardless of mode. Allowlist-precedence is intentionally not | ||
| * exposed — it's available on the runtime side as a CLI-only concession to | ||
| * legacy behavior, but SDK consumers always get the composable semantics. | ||
| * | ||
| * @internal | ||
| */ | ||
| resolveToolFilterOptions(config) { | ||
| const availableTools = toolFilterListToArray(config.availableTools); | ||
| const excludedTools = toolFilterListToArray(config.excludedTools); | ||
| validateToolFilterList("availableTools", availableTools); | ||
| validateToolFilterList("excludedTools", excludedTools); | ||
| if (this.options.mode === "empty") { | ||
| if (availableTools === void 0) { | ||
| throw new Error( | ||
| "CopilotClient is in mode: 'empty' but the session config did not specify 'availableTools'. Empty mode requires every session to explicitly opt into the tools it wants \u2014 e.g. `new ToolSet().addBuiltIn(BuiltInTools.Isolated)`." | ||
| ); | ||
| } | ||
| } | ||
| return { availableTools, excludedTools, toolFilterPrecedence: "excluded" }; | ||
| } | ||
| /** Mode-specific defaults spread under the caller's config (app values win). */ | ||
| configDefaultsForMode() { | ||
| if (this.options.mode === "empty") { | ||
| return { enableSessionTelemetry: false }; | ||
| } | ||
| return {}; | ||
| } | ||
| /** | ||
| * Returns the systemMessage config to use, adjusted for the current mode. | ||
| * In empty mode we ensure the environment_context section is removed | ||
| * unless the app has already taken control of it. `append` (and | ||
| * unspecified) mode is promoted to `customize` so we can also strip | ||
| * environment_context; the caller's `content` is preserved verbatim | ||
| * because the runtime appends it as additional instructions in both | ||
| * customize and append modes. | ||
| */ | ||
| getSystemMessageConfigForMode(supplied) { | ||
| if (this.options.mode !== "empty") return supplied; | ||
| if (!supplied) { | ||
| return { | ||
| mode: "customize", | ||
| sections: { environment_context: { action: "remove" } } | ||
| }; | ||
| } | ||
| switch (supplied.mode) { | ||
| case "replace": | ||
| return supplied; | ||
| case "customize": | ||
| if (supplied.sections?.environment_context) return supplied; | ||
| return { | ||
| ...supplied, | ||
| sections: { | ||
| ...supplied.sections, | ||
| environment_context: { action: "remove" } | ||
| } | ||
| }; | ||
| case "append": | ||
| case void 0: | ||
| return { | ||
| mode: "customize", | ||
| content: supplied.content, | ||
| sections: { environment_context: { action: "remove" } } | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Mode-specific options applied via session.options.update after create/resume. | ||
| * | ||
| * In empty mode, defaults the four overridable feature flags to safe values | ||
| * (caller values from `config` win). `installedPlugins=[]` is unconditional | ||
| * in empty mode — apps that need custom plugins should switch modes. | ||
| */ | ||
| async updateSessionOptionsForMode(session, config) { | ||
| const patch = {}; | ||
| if (this.options.mode === "empty") { | ||
| patch.skipCustomInstructions = config.skipCustomInstructions ?? true; | ||
| patch.customAgentsLocalOnly = config.customAgentsLocalOnly ?? true; | ||
| patch.coauthorEnabled = config.coauthorEnabled ?? false; | ||
| patch.manageScheduleEnabled = config.manageScheduleEnabled ?? false; | ||
| patch.installedPlugins = []; | ||
| } else { | ||
| if (config.skipCustomInstructions !== void 0) | ||
| patch.skipCustomInstructions = config.skipCustomInstructions; | ||
| if (config.customAgentsLocalOnly !== void 0) | ||
| patch.customAgentsLocalOnly = config.customAgentsLocalOnly; | ||
| if (config.coauthorEnabled !== void 0) | ||
| patch.coauthorEnabled = config.coauthorEnabled; | ||
| if (config.manageScheduleEnabled !== void 0) | ||
| patch.manageScheduleEnabled = config.manageScheduleEnabled; | ||
| } | ||
| if (Object.keys(patch).length === 0) { | ||
| return; | ||
| } | ||
| try { | ||
| await session.rpc.options.update(patch); | ||
| } catch (e) { | ||
| try { | ||
| await session.disconnect(); | ||
| } catch { | ||
| } | ||
| throw e; | ||
| } | ||
| } | ||
| async createSession(config) { | ||
@@ -557,2 +697,4 @@ if (!this.connection) { | ||
| } | ||
| config = { ...this.configDefaultsForMode(), ...config }; | ||
| config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage); | ||
| const sessionId = config.sessionId ?? randomUUID(); | ||
@@ -595,2 +737,3 @@ const session = new CopilotSession( | ||
| this.setupSessionFs(session, config); | ||
| const toolFilterOptions = this.resolveToolFilterOptions(config); | ||
| try { | ||
@@ -619,4 +762,5 @@ const response = await this.connection.sendRequest("session.create", { | ||
| systemMessage: wireSystemMessage, | ||
| availableTools: config.availableTools, | ||
| excludedTools: config.excludedTools, | ||
| availableTools: toolFilterOptions.availableTools, | ||
| excludedTools: toolFilterOptions.excludedTools, | ||
| toolFilterPrecedence: toolFilterOptions.toolFilterPrecedence, | ||
| provider: config.provider, | ||
@@ -652,2 +796,3 @@ enableSessionTelemetry: config.enableSessionTelemetry, | ||
| session.setCapabilities(capabilities); | ||
| await this.updateSessionOptionsForMode(session, config); | ||
| } catch (e) { | ||
@@ -712,2 +857,4 @@ this.sessions.delete(sessionId); | ||
| } | ||
| config = { ...this.configDefaultsForMode(), ...config }; | ||
| config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage); | ||
| const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks( | ||
@@ -724,2 +871,3 @@ config.systemMessage | ||
| this.setupSessionFs(session, config); | ||
| const toolFilterOptions = this.resolveToolFilterOptions(config); | ||
| try { | ||
@@ -733,4 +881,5 @@ const response = await this.connection.sendRequest("session.resume", { | ||
| systemMessage: wireSystemMessage, | ||
| availableTools: config.availableTools, | ||
| excludedTools: config.excludedTools, | ||
| availableTools: toolFilterOptions.availableTools, | ||
| excludedTools: toolFilterOptions.excludedTools, | ||
| toolFilterPrecedence: toolFilterOptions.toolFilterPrecedence, | ||
| enableSessionTelemetry: config.enableSessionTelemetry, | ||
@@ -784,2 +933,3 @@ tools: config.tools?.map((tool) => ({ | ||
| session.setOpenCanvases(openCanvases ?? []); | ||
| await this.updateSessionOptionsForMode(session, config); | ||
| } catch (e) { | ||
@@ -1163,2 +1313,5 @@ this.sessions.delete(sessionId); | ||
| } | ||
| if (this.options.mode === "empty") { | ||
| envWithoutNodeDebug.COPILOT_DISABLE_KEYTAR = "1"; | ||
| } | ||
| if (!this.resolvedCliPath) { | ||
@@ -1165,0 +1318,0 @@ throw new Error( |
+2
-1
@@ -8,2 +8,3 @@ /** | ||
| export { RuntimeConnection } from "./types.js"; | ||
| export { BuiltInTools, ToolSet } from "./toolSet.js"; | ||
| export { CopilotSession, type AssistantMessageEvent } from "./session.js"; | ||
@@ -13,2 +14,2 @@ export { Canvas, CanvasError, createCanvas, type CanvasAction, type CanvasDeclaration, type CanvasHostContext, type CanvasJsonSchema, type CanvasOptions, } from "./canvas.js"; | ||
| export type * from "./generated/session-events.js"; | ||
| export type { CommandContext, CommandDefinition, CommandHandler, CloudSessionOptions, CloudSessionRepository, AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, CopilotClientOptions, StdioRuntimeConnection, TcpRuntimeConnection, UriRuntimeConnection, CustomAgentConfig, ElicitationFieldValue, ElicitationHandler, ElicitationParams, ElicitationContext, ElicitationResult, ElicitationSchema, ElicitationSchemaField, ExitPlanModeHandler, ExitPlanModeRequest, ExitPlanModeResult, ExtensionInfo, ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, UiInputOptions, MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, DefaultAgentConfig, MessageOptions, ModelBilling, ModelCapabilities, ModelCapabilitiesOverride, ModelInfo, ModelPolicy, PermissionHandler, PermissionRequest, PermissionRequestResult, ProviderConfig, RemoteSessionMode, ResumeSessionConfig, SectionOverride, SectionOverrideAction, SectionTransformFn, SessionCapabilities, SessionConfig, SessionConfigBase, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionLifecycleEvent, SessionLifecycleEventMetadata, SessionLifecycleEventType, SessionLifecycleHandler, SessionCreatedEvent, SessionDeletedEvent, SessionUpdatedEvent, SessionForegroundEvent, SessionBackgroundEvent, SessionContext, SessionListFilter, SessionMetadata, SessionUiApi, SessionFsConfig, SessionFsProvider, SessionFsFileInfo, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, SessionFsSqliteProvider, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, SystemMessageReplaceConfig, SystemMessageSection, TelemetryConfig, TraceContext, TraceContextProvider, Tool, ToolHandler, ToolInvocation, ToolTelemetry, ToolResultObject, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, } from "./types.js"; | ||
| export type { CommandContext, CommandDefinition, CommandHandler, CloudSessionOptions, CloudSessionRepository, AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, CopilotClientMode, CopilotClientOptions, StdioRuntimeConnection, TcpRuntimeConnection, UriRuntimeConnection, CustomAgentConfig, ElicitationFieldValue, ElicitationHandler, ElicitationParams, ElicitationContext, ElicitationResult, ElicitationSchema, ElicitationSchemaField, ExitPlanModeHandler, ExitPlanModeRequest, ExitPlanModeResult, ExtensionInfo, ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, UiInputOptions, MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, DefaultAgentConfig, MessageOptions, ModelBilling, ModelCapabilities, ModelCapabilitiesOverride, ModelInfo, ModelPolicy, PermissionHandler, PermissionRequest, PermissionRequestResult, ProviderConfig, RemoteSessionMode, ResumeSessionConfig, SectionOverride, SectionOverrideAction, SectionTransformFn, SessionCapabilities, SessionConfig, SessionConfigBase, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionLifecycleEvent, SessionLifecycleEventMetadata, SessionLifecycleEventType, SessionLifecycleHandler, SessionCreatedEvent, SessionDeletedEvent, SessionUpdatedEvent, SessionForegroundEvent, SessionBackgroundEvent, SessionContext, SessionListFilter, SessionMetadata, SessionUiApi, SessionFsConfig, SessionFsProvider, SessionFsFileInfo, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, SessionFsSqliteProvider, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, SystemMessageReplaceConfig, SystemMessageSection, TelemetryConfig, TraceContext, TraceContextProvider, Tool, ToolHandler, ToolInvocation, ToolTelemetry, ToolResultObject, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, } from "./types.js"; |
+3
-0
| import { CopilotClient } from "./client.js"; | ||
| import { RuntimeConnection } from "./types.js"; | ||
| import { BuiltInTools, ToolSet } from "./toolSet.js"; | ||
| import { CopilotSession } from "./session.js"; | ||
@@ -17,2 +18,3 @@ import { | ||
| export { | ||
| BuiltInTools, | ||
| Canvas, | ||
@@ -24,2 +26,3 @@ CanvasError, | ||
| SYSTEM_MESSAGE_SECTIONS, | ||
| ToolSet, | ||
| approveAll, | ||
@@ -26,0 +29,0 @@ convertMcpCallToolResult, |
+2
-0
@@ -99,2 +99,3 @@ import { ConnectionError, ErrorCodes, ResponseError } from "vscode-jsonrpc/node.js"; | ||
| mode: options.mode, | ||
| agentMode: options.agentMode, | ||
| requestHeaders: options.requestHeaders | ||
@@ -720,2 +721,3 @@ }); | ||
| postToolUse: this.hooks.onPostToolUse, | ||
| postToolUseFailure: this.hooks.onPostToolUseFailure, | ||
| userPromptSubmitted: this.hooks.onUserPromptSubmitted, | ||
@@ -722,0 +724,0 @@ sessionStart: this.hooks.onSessionStart, |
+31
-7
@@ -121,2 +121,3 @@ # Agent Extension Authoring Guide | ||
| onPostToolUse: async (input, invocation) => { ... }, | ||
| onPostToolUseFailure: async (input, invocation) => { ... }, | ||
| onSessionStart: async (input, invocation) => { ... }, | ||
@@ -128,3 +129,3 @@ onSessionEnd: async (input, invocation) => { ... }, | ||
| All hook inputs include `timestamp` (unix ms) and `cwd` (working directory). | ||
| All hook inputs include `timestamp` (`Date`) and `workingDirectory`. | ||
| All handlers receive `invocation: { sessionId: string }` as the second argument. | ||
@@ -135,3 +136,3 @@ All handlers may return `void`/`undefined` (no-op) or an output object. | ||
| **Input:** `{ prompt: string, timestamp, cwd }` | ||
| **Input:** `{ prompt: string, timestamp, workingDirectory }` | ||
@@ -146,3 +147,3 @@ **Output (all fields optional):** | ||
| **Input:** `{ toolName: string, toolArgs: unknown, timestamp, cwd }` | ||
| **Input:** `{ toolName: string, toolArgs: unknown, timestamp, workingDirectory }` | ||
@@ -159,4 +160,7 @@ **Output (all fields optional):** | ||
| **Input:** `{ toolName: string, toolArgs: unknown, toolResult: ToolResultObject, timestamp, cwd }` | ||
| **Input:** `{ toolName: string, toolArgs: unknown, toolResult: ToolResultObject, timestamp, workingDirectory }` | ||
| Fires only when the tool returned a successful result. To observe non-success | ||
| outcomes, register `onPostToolUseFailure` as well. | ||
| **Output (all fields optional):** | ||
@@ -168,5 +172,25 @@ | Field | Type | Effect | | ||
| ### onPostToolUseFailure | ||
| **Input:** `{ toolName: string, toolArgs: unknown, error: string, timestamp, workingDirectory }` | ||
| Fires after a tool execution whose result was `"failure"`. `onPostToolUse` | ||
| does **not** fire for these outcomes, so register this handler to observe or | ||
| react to them — useful for telemetry, replay buffers, fault-injection tests, | ||
| or pairing pre/post tool tracking that would otherwise leak when the tool | ||
| fails. Note the input shape differs from `onPostToolUse`: only `error` (the | ||
| stringified failure message) is provided, not the full `toolResult`. | ||
| **Output (all fields optional):** | ||
| | Field | Type | Effect | | ||
| |-------|------|--------| | ||
| | `additionalContext` | `string` | Appended as hidden guidance the model sees alongside the failed tool result | | ||
| Note: only `"failure"` results trigger this hook. Other non-success | ||
| `resultType` values (`"rejected"`, `"denied"`, `"timeout"`) do not currently | ||
| fire it. | ||
| ### onSessionStart | ||
| **Input:** `{ source: "startup" \| "resume" \| "new", initialPrompt?: string, timestamp, cwd }` | ||
| **Input:** `{ source: "startup" \| "resume" \| "new", initialPrompt?: string, timestamp, workingDirectory }` | ||
@@ -180,3 +204,3 @@ **Output (all fields optional):** | ||
| **Input:** `{ reason: "complete" \| "error" \| "abort" \| "timeout" \| "user_exit", finalMessage?: string, error?: string, timestamp, cwd }` | ||
| **Input:** `{ reason: "complete" \| "error" \| "abort" \| "timeout" \| "user_exit", finalMessage?: string, error?: string, timestamp, workingDirectory }` | ||
@@ -191,3 +215,3 @@ **Output (all fields optional):** | ||
| **Input:** `{ error: string, errorContext: "model_call" \| "tool_execution" \| "system" \| "user_input", recoverable: boolean, timestamp, cwd }` | ||
| **Input:** `{ error: string, errorContext: "model_call" \| "tool_execution" \| "system" \| "user_input", recoverable: boolean, timestamp, workingDirectory }` | ||
@@ -194,0 +218,0 @@ **Output (all fields optional):** |
+19
-12
@@ -155,12 +155,13 @@ # Copilot CLI Extension Examples | ||
| | Hook | Fires When | Can Modify | | ||
| | ----------------------- | ------------------------- | ------------------------------------------- | | ||
| | `onUserPromptSubmitted` | User sends a message | The prompt text, add context | | ||
| | `onPreToolUse` | Before a tool executes | Tool args, permission decision, add context | | ||
| | `onPostToolUse` | After a tool executes | Tool result, add context | | ||
| | `onSessionStart` | Session starts or resumes | Add context, modify config | | ||
| | `onSessionEnd` | Session ends | Cleanup actions, summary | | ||
| | `onErrorOccurred` | An error occurs | Error handling strategy (retry/skip/abort) | | ||
| | Hook | Fires When | Can Modify | | ||
| | ----------------------- | ---------------------------------------- | ------------------------------------------- | | ||
| | `onUserPromptSubmitted` | User sends a message | The prompt text, add context | | ||
| | `onPreToolUse` | Before a tool executes | Tool args, permission decision, add context | | ||
| | `onPostToolUse` | After a tool executes successfully | Tool result, add context | | ||
| | `onPostToolUseFailure` | After a tool execution returns a failure | Add hidden guidance to the model | | ||
| | `onSessionStart` | Session starts or resumes | Add context, modify config | | ||
| | `onSessionEnd` | Session ends | Cleanup actions, summary | | ||
| | `onErrorOccurred` | An error occurs | Error handling strategy (retry/skip/abort) | | ||
| All hook inputs include `timestamp` (unix ms) and `cwd` (working directory). | ||
| All hook inputs include `timestamp` (`Date`) and `workingDirectory`. | ||
@@ -271,8 +272,14 @@ ### Modifying the user's message | ||
| ### Augmenting tool results with extra context | ||
| ### Reacting when a tool fails | ||
| `onPostToolUse` only fires for successful tool executions. To observe or react | ||
| to failures, register `onPostToolUseFailure`. The input includes | ||
| `input.error` (the stringified failure message); only `additionalContext` on | ||
| the return value is consumed by the runtime, and it is appended as hidden | ||
| guidance alongside the failed tool result. | ||
| ```js | ||
| hooks: { | ||
| onPostToolUse: async (input) => { | ||
| if (input.toolName === "bash" && input.toolResult?.resultType === "failure") { | ||
| onPostToolUseFailure: async (input) => { | ||
| if (input.toolName === "bash") { | ||
| return { | ||
@@ -279,0 +286,0 @@ additionalContext: "The command failed. Try a different approach.", |
+2
-2
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "1.0.0-beta.8", | ||
| "version": "1.0.0-beta.9", | ||
| "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", | ||
@@ -60,3 +60,3 @@ "main": "./dist/cjs/index.js", | ||
| "dependencies": { | ||
| "@github/copilot": "^1.0.55-1", | ||
| "@github/copilot": "^1.0.55-5", | ||
| "vscode-jsonrpc": "^8.2.1", | ||
@@ -63,0 +63,0 @@ "zod": "^4.3.6" |
+13
-2
@@ -960,3 +960,3 @@ # Copilot SDK for Node.js/TypeScript | ||
| // Called after each tool execution | ||
| // Called after each successful tool execution | ||
| onPostToolUse: async (input, invocation) => { | ||
@@ -970,2 +970,12 @@ console.log(`Tool ${input.toolName} completed`); | ||
| // Called after a tool execution whose result was "failure". | ||
| // onPostToolUse does NOT fire for failed tool calls — register this | ||
| // hook to observe them. Input includes `error` (the failure message | ||
| // extracted from the tool's result), not the full result object. | ||
| onPostToolUseFailure: async (input, invocation) => { | ||
| console.log(`Tool ${input.toolName} failed: ${input.error}`); | ||
| // Optionally append hidden guidance to the model. | ||
| return { additionalContext: "Suggest checking inputs and retrying." }; | ||
| }, | ||
| // Called when user submits a prompt | ||
@@ -1006,3 +1016,4 @@ onUserPromptSubmitted: async (input, invocation) => { | ||
| - `onPreToolUse` - Intercept tool calls before execution. Can allow/deny or modify arguments. | ||
| - `onPostToolUse` - Process tool results after execution. Can modify results or add context. | ||
| - `onPostToolUse` - Process tool results after **successful** execution. Can modify results or add context. | ||
| - `onPostToolUseFailure` - Observe and append hidden guidance to the model after tool executions whose result was `"failure"`. Register this in addition to `onPostToolUse` to see failed tool calls. | ||
| - `onUserPromptSubmitted` - Intercept user prompts. Can modify the prompt before processing. | ||
@@ -1009,0 +1020,0 @@ - `onSessionStart` - Run logic when a session starts or resumes. |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1119026
5.08%42
7.69%30458
4.61%1040
1.07%Updated